LCOV - code coverage report
Current view: top level - src/backend/access/spgist - spgutils.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 91.1 % 537 489
Test Date: 2026-01-26 10:56:24 Functions: 100.0 % 26 26
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 59.6 % 344 205

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * spgutils.c
       4                 :             :  *        various support functions for SP-GiST
       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/spgist/spgutils.c
      12                 :             :  *
      13                 :             :  *-------------------------------------------------------------------------
      14                 :             :  */
      15                 :             : 
      16                 :             : #include "postgres.h"
      17                 :             : 
      18                 :             : #include "access/amvalidate.h"
      19                 :             : #include "access/htup_details.h"
      20                 :             : #include "access/reloptions.h"
      21                 :             : #include "access/spgist_private.h"
      22                 :             : #include "access/toast_compression.h"
      23                 :             : #include "access/transam.h"
      24                 :             : #include "access/xact.h"
      25                 :             : #include "catalog/pg_amop.h"
      26                 :             : #include "commands/vacuum.h"
      27                 :             : #include "nodes/nodeFuncs.h"
      28                 :             : #include "parser/parse_coerce.h"
      29                 :             : #include "storage/bufmgr.h"
      30                 :             : #include "storage/indexfsm.h"
      31                 :             : #include "utils/catcache.h"
      32                 :             : #include "utils/fmgrprotos.h"
      33                 :             : #include "utils/index_selfuncs.h"
      34                 :             : #include "utils/lsyscache.h"
      35                 :             : #include "utils/rel.h"
      36                 :             : #include "utils/syscache.h"
      37                 :             : 
      38                 :             : 
      39                 :             : /*
      40                 :             :  * SP-GiST handler function: return IndexAmRoutine with access method parameters
      41                 :             :  * and callbacks.
      42                 :             :  */
      43                 :             : Datum
      44                 :         144 : spghandler(PG_FUNCTION_ARGS)
      45                 :             : {
      46                 :             :         static const IndexAmRoutine amroutine = {
      47                 :             :                 .type = T_IndexAmRoutine,
      48                 :             :                 .amstrategies = 0,
      49                 :             :                 .amsupport = SPGISTNProc,
      50                 :             :                 .amoptsprocnum = SPGIST_OPTIONS_PROC,
      51                 :             :                 .amcanorder = false,
      52                 :             :                 .amcanorderbyop = true,
      53                 :             :                 .amcanhash = false,
      54                 :             :                 .amconsistentequality = false,
      55                 :             :                 .amconsistentordering = false,
      56                 :             :                 .amcanbackward = false,
      57                 :             :                 .amcanunique = false,
      58                 :             :                 .amcanmulticol = false,
      59                 :             :                 .amoptionalkey = true,
      60                 :             :                 .amsearcharray = false,
      61                 :             :                 .amsearchnulls = true,
      62                 :             :                 .amstorage = true,
      63                 :             :                 .amclusterable = false,
      64                 :             :                 .ampredlocks = false,
      65                 :             :                 .amcanparallel = false,
      66                 :             :                 .amcanbuildparallel = false,
      67                 :             :                 .amcaninclude = true,
      68                 :             :                 .amusemaintenanceworkmem = false,
      69                 :             :                 .amsummarizing = false,
      70                 :             :                 .amparallelvacuumoptions =
      71                 :             :                 VACUUM_OPTION_PARALLEL_BULKDEL | VACUUM_OPTION_PARALLEL_COND_CLEANUP,
      72                 :             :                 .amkeytype = InvalidOid,
      73                 :             : 
      74                 :             :                 .ambuild = spgbuild,
      75                 :             :                 .ambuildempty = spgbuildempty,
      76                 :             :                 .aminsert = spginsert,
      77                 :             :                 .aminsertcleanup = NULL,
      78                 :             :                 .ambulkdelete = spgbulkdelete,
      79                 :             :                 .amvacuumcleanup = spgvacuumcleanup,
      80                 :             :                 .amcanreturn = spgcanreturn,
      81                 :             :                 .amcostestimate = spgcostestimate,
      82                 :             :                 .amgettreeheight = NULL,
      83                 :             :                 .amoptions = spgoptions,
      84                 :             :                 .amproperty = spgproperty,
      85                 :             :                 .ambuildphasename = NULL,
      86                 :             :                 .amvalidate = spgvalidate,
      87                 :             :                 .amadjustmembers = spgadjustmembers,
      88                 :             :                 .ambeginscan = spgbeginscan,
      89                 :             :                 .amrescan = spgrescan,
      90                 :             :                 .amgettuple = spggettuple,
      91                 :             :                 .amgetbitmap = spggetbitmap,
      92                 :             :                 .amendscan = spgendscan,
      93                 :             :                 .ammarkpos = NULL,
      94                 :             :                 .amrestrpos = NULL,
      95                 :             :                 .amestimateparallelscan = NULL,
      96                 :             :                 .aminitparallelscan = NULL,
      97                 :             :                 .amparallelrescan = NULL,
      98                 :             :                 .amtranslatestrategy = NULL,
      99                 :             :                 .amtranslatecmptype = NULL,
     100                 :             :         };
     101                 :             : 
     102                 :         144 :         PG_RETURN_POINTER(&amroutine);
     103                 :             : }
     104                 :             : 
     105                 :             : /*
     106                 :             :  * GetIndexInputType
     107                 :             :  *              Determine the nominal input data type for an index column
     108                 :             :  *
     109                 :             :  * We define the "nominal" input type as the associated opclass's opcintype,
     110                 :             :  * or if that is a polymorphic type, the base type of the heap column or
     111                 :             :  * expression that is the index's input.  The reason for preferring the
     112                 :             :  * opcintype is that non-polymorphic opclasses probably don't want to hear
     113                 :             :  * about binary-compatible input types.  For instance, if a text opclass
     114                 :             :  * is being used with a varchar heap column, we want to report "text" not
     115                 :             :  * "varchar".  Likewise, opclasses don't want to hear about domain types,
     116                 :             :  * so if we do consult the actual input type, we make sure to flatten domains.
     117                 :             :  *
     118                 :             :  * At some point maybe this should go somewhere else, but it's not clear
     119                 :             :  * if any other index AMs have a use for it.
     120                 :             :  */
     121                 :             : static Oid
     122                 :          51 : GetIndexInputType(Relation index, AttrNumber indexcol)
     123                 :             : {
     124                 :          51 :         Oid                     opcintype;
     125                 :          51 :         AttrNumber      heapcol;
     126                 :          51 :         List       *indexprs;
     127                 :          51 :         ListCell   *indexpr_item;
     128                 :             : 
     129         [ +  - ]:          51 :         Assert(index->rd_index != NULL);
     130         [ +  - ]:          51 :         Assert(indexcol > 0 && indexcol <= index->rd_index->indnkeyatts);
     131                 :          51 :         opcintype = index->rd_opcintype[indexcol - 1];
     132   [ +  -  +  -  :          51 :         if (!IsPolymorphicType(opcintype))
          +  -  +  -  +  
          +  +  -  +  -  
          +  -  +  -  +  
                -  -  + ]
     133                 :          44 :                 return opcintype;
     134                 :           7 :         heapcol = index->rd_index->indkey.values[indexcol - 1];
     135         [ +  + ]:           7 :         if (heapcol != 0)                       /* Simple index column? */
     136                 :           5 :                 return getBaseType(get_atttype(index->rd_index->indrelid, heapcol));
     137                 :             : 
     138                 :             :         /*
     139                 :             :          * If the index expressions are already cached, skip calling
     140                 :             :          * RelationGetIndexExpressions, as it will make a copy which is overkill.
     141                 :             :          * We're not going to modify the trees, and we're not going to do anything
     142                 :             :          * that would invalidate the relcache entry before we're done.
     143                 :             :          */
     144         [ -  + ]:           2 :         if (index->rd_indexprs)
     145                 :           0 :                 indexprs = index->rd_indexprs;
     146                 :             :         else
     147                 :           2 :                 indexprs = RelationGetIndexExpressions(index);
     148                 :           2 :         indexpr_item = list_head(indexprs);
     149   [ +  -  +  - ]:           4 :         for (int i = 1; i <= index->rd_index->indnkeyatts; i++)
     150                 :             :         {
     151         [ -  + ]:           2 :                 if (index->rd_index->indkey.values[i - 1] == 0)
     152                 :             :                 {
     153                 :             :                         /* expression column */
     154         [ +  - ]:           2 :                         if (indexpr_item == NULL)
     155   [ #  #  #  # ]:           0 :                                 elog(ERROR, "wrong number of index expressions");
     156         [ -  + ]:           2 :                         if (i == indexcol)
     157                 :           2 :                                 return getBaseType(exprType((Node *) lfirst(indexpr_item)));
     158                 :           0 :                         indexpr_item = lnext(indexprs, indexpr_item);
     159                 :           0 :                 }
     160                 :           0 :         }
     161   [ #  #  #  # ]:           0 :         elog(ERROR, "wrong number of index expressions");
     162                 :           0 :         return InvalidOid;                      /* keep compiler quiet */
     163                 :          51 : }
     164                 :             : 
     165                 :             : /* Fill in a SpGistTypeDesc struct with info about the specified data type */
     166                 :             : static void
     167                 :         156 : fillTypeDesc(SpGistTypeDesc *desc, Oid type)
     168                 :             : {
     169                 :         156 :         HeapTuple       tp;
     170                 :         156 :         Form_pg_type typtup;
     171                 :             : 
     172                 :         156 :         desc->type = type;
     173                 :         156 :         tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type));
     174         [ +  - ]:         156 :         if (!HeapTupleIsValid(tp))
     175   [ #  #  #  # ]:           0 :                 elog(ERROR, "cache lookup failed for type %u", type);
     176                 :         156 :         typtup = (Form_pg_type) GETSTRUCT(tp);
     177                 :         156 :         desc->attlen = typtup->typlen;
     178                 :         156 :         desc->attbyval = typtup->typbyval;
     179                 :         156 :         desc->attalign = typtup->typalign;
     180                 :         156 :         desc->attstorage = typtup->typstorage;
     181                 :         156 :         ReleaseSysCache(tp);
     182                 :         156 : }
     183                 :             : 
     184                 :             : /*
     185                 :             :  * Fetch local cache of AM-specific info about the index, initializing it
     186                 :             :  * if necessary
     187                 :             :  */
     188                 :             : SpGistCache *
     189                 :      443951 : spgGetCache(Relation index)
     190                 :             : {
     191                 :      443951 :         SpGistCache *cache;
     192                 :             : 
     193         [ +  + ]:      443951 :         if (index->rd_amcache == NULL)
     194                 :             :         {
     195                 :          51 :                 Oid                     atttype;
     196                 :          51 :                 spgConfigIn in;
     197                 :          51 :                 FmgrInfo   *procinfo;
     198                 :             : 
     199                 :          51 :                 cache = MemoryContextAllocZero(index->rd_indexcxt,
     200                 :             :                                                                            sizeof(SpGistCache));
     201                 :             : 
     202                 :             :                 /* SPGiST must have one key column and can also have INCLUDE columns */
     203         [ +  - ]:          51 :                 Assert(IndexRelationGetNumberOfKeyAttributes(index) == 1);
     204         [ +  - ]:          51 :                 Assert(IndexRelationGetNumberOfAttributes(index) <= INDEX_MAX_KEYS);
     205                 :             : 
     206                 :             :                 /*
     207                 :             :                  * Get the actual (well, nominal) data type of the key column.  We
     208                 :             :                  * pass this to the opclass config function so that polymorphic
     209                 :             :                  * opclasses are possible.
     210                 :             :                  */
     211                 :          51 :                 atttype = GetIndexInputType(index, spgKeyColumn + 1);
     212                 :             : 
     213                 :             :                 /* Call the config function to get config info for the opclass */
     214                 :          51 :                 in.attType = atttype;
     215                 :             : 
     216                 :          51 :                 procinfo = index_getprocinfo(index, 1, SPGIST_CONFIG_PROC);
     217                 :         102 :                 FunctionCall2Coll(procinfo,
     218                 :          51 :                                                   index->rd_indcollation[spgKeyColumn],
     219                 :          51 :                                                   PointerGetDatum(&in),
     220                 :          51 :                                                   PointerGetDatum(&cache->config));
     221                 :             : 
     222                 :             :                 /*
     223                 :             :                  * If leafType isn't specified, use the declared index column type,
     224                 :             :                  * which index.c will have derived from the opclass's opcintype.
     225                 :             :                  * (Although we now make spgvalidate.c warn if these aren't the same,
     226                 :             :                  * old user-defined opclasses may not set the STORAGE parameter
     227                 :             :                  * correctly, so believe leafType if it's given.)
     228                 :             :                  */
     229         [ +  + ]:          51 :                 if (!OidIsValid(cache->config.leafType))
     230                 :             :                 {
     231                 :          48 :                         cache->config.leafType =
     232                 :          48 :                                 TupleDescAttr(RelationGetDescr(index), spgKeyColumn)->atttypid;
     233                 :             : 
     234                 :             :                         /*
     235                 :             :                          * If index column type is binary-coercible to atttype (for
     236                 :             :                          * example, it's a domain over atttype), treat it as plain atttype
     237                 :             :                          * to avoid thinking we need to compress.
     238                 :             :                          */
     239   [ +  +  -  + ]:          48 :                         if (cache->config.leafType != atttype &&
     240                 :           2 :                                 IsBinaryCoercible(cache->config.leafType, atttype))
     241                 :           2 :                                 cache->config.leafType = atttype;
     242                 :          48 :                 }
     243                 :             : 
     244                 :             :                 /* Get the information we need about each relevant datatype */
     245                 :          51 :                 fillTypeDesc(&cache->attType, atttype);
     246                 :             : 
     247         [ +  + ]:          51 :                 if (cache->config.leafType != atttype)
     248                 :             :                 {
     249         [ +  - ]:           3 :                         if (!OidIsValid(index_getprocid(index, 1, SPGIST_COMPRESS_PROC)))
     250   [ #  #  #  # ]:           0 :                                 ereport(ERROR,
     251                 :             :                                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     252                 :             :                                                  errmsg("compress method must be defined when leaf type is different from input type")));
     253                 :             : 
     254                 :           3 :                         fillTypeDesc(&cache->attLeafType, cache->config.leafType);
     255                 :           3 :                 }
     256                 :             :                 else
     257                 :             :                 {
     258                 :             :                         /* Save lookups in this common case */
     259                 :          48 :                         cache->attLeafType = cache->attType;
     260                 :             :                 }
     261                 :             : 
     262                 :          51 :                 fillTypeDesc(&cache->attPrefixType, cache->config.prefixType);
     263                 :          51 :                 fillTypeDesc(&cache->attLabelType, cache->config.labelType);
     264                 :             : 
     265                 :             :                 /*
     266                 :             :                  * Finally, if it's a real index (not a partitioned one), get the
     267                 :             :                  * lastUsedPages data from the metapage
     268                 :             :                  */
     269         [ +  + ]:          51 :                 if (index->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
     270                 :             :                 {
     271                 :          50 :                         Buffer          metabuffer;
     272                 :          50 :                         SpGistMetaPageData *metadata;
     273                 :             : 
     274                 :          50 :                         metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO);
     275                 :          50 :                         LockBuffer(metabuffer, BUFFER_LOCK_SHARE);
     276                 :             : 
     277                 :          50 :                         metadata = SpGistPageGetMeta(BufferGetPage(metabuffer));
     278                 :             : 
     279         [ +  - ]:          50 :                         if (metadata->magicNumber != SPGIST_MAGIC_NUMBER)
     280   [ #  #  #  # ]:           0 :                                 elog(ERROR, "index \"%s\" is not an SP-GiST index",
     281                 :             :                                          RelationGetRelationName(index));
     282                 :             : 
     283                 :          50 :                         cache->lastUsedPages = metadata->lastUsedPages;
     284                 :             : 
     285                 :          50 :                         UnlockReleaseBuffer(metabuffer);
     286                 :          50 :                 }
     287                 :             : 
     288                 :          51 :                 index->rd_amcache = cache;
     289                 :          51 :         }
     290                 :             :         else
     291                 :             :         {
     292                 :             :                 /* assume it's up to date */
     293                 :      443900 :                 cache = (SpGistCache *) index->rd_amcache;
     294                 :             :         }
     295                 :             : 
     296                 :      887902 :         return cache;
     297                 :      443951 : }
     298                 :             : 
     299                 :             : /*
     300                 :             :  * Compute a tuple descriptor for leaf tuples or index-only-scan result tuples.
     301                 :             :  *
     302                 :             :  * We can use the relcache's tupdesc as-is in many cases, and it's always
     303                 :             :  * OK so far as any INCLUDE columns are concerned.  However, the entry for
     304                 :             :  * the key column has to match leafType in the first case or attType in the
     305                 :             :  * second case.  While the relcache's tupdesc *should* show leafType, this
     306                 :             :  * might not hold for legacy user-defined opclasses, since before v14 they
     307                 :             :  * were not allowed to declare their true storage type in CREATE OPCLASS.
     308                 :             :  * Also, attType can be different from what is in the relcache.
     309                 :             :  *
     310                 :             :  * This function gives back either a pointer to the relcache's tupdesc
     311                 :             :  * if that is suitable, or a palloc'd copy that's been adjusted to match
     312                 :             :  * the specified key column type.  We can avoid doing any catalog lookups
     313                 :             :  * here by insisting that the caller pass an SpGistTypeDesc not just an OID.
     314                 :             :  */
     315                 :             : TupleDesc
     316                 :       39622 : getSpGistTupleDesc(Relation index, SpGistTypeDesc *keyType)
     317                 :             : {
     318                 :       39622 :         TupleDesc       outTupDesc;
     319                 :       39622 :         Form_pg_attribute att;
     320                 :             : 
     321   [ +  +  +  + ]:       79244 :         if (keyType->type ==
     322                 :       39622 :                 TupleDescAttr(RelationGetDescr(index), spgKeyColumn)->atttypid)
     323                 :       39602 :                 outTupDesc = RelationGetDescr(index);
     324                 :             :         else
     325                 :             :         {
     326                 :          20 :                 outTupDesc = CreateTupleDescCopy(RelationGetDescr(index));
     327                 :          20 :                 att = TupleDescAttr(outTupDesc, spgKeyColumn);
     328                 :             :                 /* It's sufficient to update the type-dependent fields of the column */
     329                 :          20 :                 att->atttypid = keyType->type;
     330                 :          20 :                 att->atttypmod = -1;
     331                 :          20 :                 att->attlen = keyType->attlen;
     332                 :          20 :                 att->attbyval = keyType->attbyval;
     333                 :          20 :                 att->attalign = keyType->attalign;
     334                 :          20 :                 att->attstorage = keyType->attstorage;
     335                 :             :                 /* We shouldn't need to bother with making these valid: */
     336                 :          20 :                 att->attcompression = InvalidCompressionMethod;
     337                 :          20 :                 att->attcollation = InvalidOid;
     338                 :             :                 /* In case we changed typlen, we'd better reset following offsets */
     339         [ -  + ]:          20 :                 for (int i = spgFirstIncludeColumn; i < outTupDesc->natts; i++)
     340                 :           0 :                         TupleDescCompactAttr(outTupDesc, i)->attcacheoff = -1;
     341                 :             : 
     342                 :          20 :                 populate_compact_attribute(outTupDesc, spgKeyColumn);
     343                 :             :         }
     344                 :       79244 :         return outTupDesc;
     345                 :       39622 : }
     346                 :             : 
     347                 :             : /* Initialize SpGistState for working with the given index */
     348                 :             : void
     349                 :       39472 : initSpGistState(SpGistState *state, Relation index)
     350                 :             : {
     351                 :       39472 :         SpGistCache *cache;
     352                 :             : 
     353                 :       39472 :         state->index = index;
     354                 :             : 
     355                 :             :         /* Get cached static information about index */
     356                 :       39472 :         cache = spgGetCache(index);
     357                 :             : 
     358                 :       39472 :         state->config = cache->config;
     359                 :       39472 :         state->attType = cache->attType;
     360                 :       39472 :         state->attLeafType = cache->attLeafType;
     361                 :       39472 :         state->attPrefixType = cache->attPrefixType;
     362                 :       39472 :         state->attLabelType = cache->attLabelType;
     363                 :             : 
     364                 :             :         /* Ensure we have a valid descriptor for leaf tuples */
     365                 :       39472 :         state->leafTupDesc = getSpGistTupleDesc(state->index, &state->attLeafType);
     366                 :             : 
     367                 :             :         /* Make workspace for constructing dead tuples */
     368                 :       39472 :         state->deadTupleStorage = palloc0(SGDTSIZE);
     369                 :             : 
     370                 :             :         /*
     371                 :             :          * Set horizon XID to use in redirection tuples.  Use our own XID if we
     372                 :             :          * have one, else use InvalidTransactionId.  The latter case can happen in
     373                 :             :          * VACUUM or REINDEX CONCURRENTLY, and in neither case would it be okay to
     374                 :             :          * force an XID to be assigned.  VACUUM won't create any redirection
     375                 :             :          * tuples anyway, but REINDEX CONCURRENTLY can.  Fortunately, REINDEX
     376                 :             :          * CONCURRENTLY doesn't mark the index valid until the end, so there could
     377                 :             :          * never be any concurrent scans "in flight" to a redirection tuple it has
     378                 :             :          * inserted.  And it locks out VACUUM until the end, too.  So it's okay
     379                 :             :          * for VACUUM to immediately expire a redirection tuple that contains an
     380                 :             :          * invalid xid.
     381                 :             :          */
     382                 :       39472 :         state->redirectXid = GetTopTransactionIdIfAny();
     383                 :             : 
     384                 :             :         /* Assume we're not in an index build (spgbuild will override) */
     385                 :       39472 :         state->isBuild = false;
     386                 :       39472 : }
     387                 :             : 
     388                 :             : /*
     389                 :             :  * Allocate a new page (either by recycling, or by extending the index file).
     390                 :             :  *
     391                 :             :  * The returned buffer is already pinned and exclusive-locked.
     392                 :             :  * Caller is responsible for initializing the page by calling SpGistInitBuffer.
     393                 :             :  */
     394                 :             : Buffer
     395                 :        1021 : SpGistNewBuffer(Relation index)
     396                 :             : {
     397                 :        1021 :         Buffer          buffer;
     398                 :             : 
     399                 :             :         /* First, try to get a page from FSM */
     400                 :        1021 :         for (;;)
     401                 :             :         {
     402                 :        1021 :                 BlockNumber blkno = GetFreeIndexPage(index);
     403                 :             : 
     404         [ +  + ]:        1021 :                 if (blkno == InvalidBlockNumber)
     405                 :        1020 :                         break;                          /* nothing known to FSM */
     406                 :             : 
     407                 :             :                 /*
     408                 :             :                  * The fixed pages shouldn't ever be listed in FSM, but just in case
     409                 :             :                  * one is, ignore it.
     410                 :             :                  */
     411         [ -  + ]:           1 :                 if (SpGistBlockIsFixed(blkno))
     412                 :           0 :                         continue;
     413                 :             : 
     414                 :           1 :                 buffer = ReadBuffer(index, blkno);
     415                 :             : 
     416                 :             :                 /*
     417                 :             :                  * We have to guard against the possibility that someone else already
     418                 :             :                  * recycled this page; the buffer may be locked if so.
     419                 :             :                  */
     420         [ -  + ]:           1 :                 if (ConditionalLockBuffer(buffer))
     421                 :             :                 {
     422                 :           1 :                         Page            page = BufferGetPage(buffer);
     423                 :             : 
     424         [ -  + ]:           1 :                         if (PageIsNew(page))
     425                 :           0 :                                 return buffer;  /* OK to use, if never initialized */
     426                 :             : 
     427   [ +  -  +  - ]:           1 :                         if (SpGistPageIsDeleted(page) || PageIsEmpty(page))
     428                 :           1 :                                 return buffer;  /* OK to use */
     429                 :             : 
     430                 :           0 :                         LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
     431         [ +  - ]:           1 :                 }
     432                 :             : 
     433                 :             :                 /* Can't use it, so release buffer and try again */
     434                 :           0 :                 ReleaseBuffer(buffer);
     435   [ -  -  +  + ]:        1021 :         }
     436                 :             : 
     437                 :        1020 :         buffer = ExtendBufferedRel(BMR_REL(index), MAIN_FORKNUM, NULL,
     438                 :             :                                                            EB_LOCK_FIRST);
     439                 :             : 
     440                 :        1020 :         return buffer;
     441                 :        1021 : }
     442                 :             : 
     443                 :             : /*
     444                 :             :  * Update index metapage's lastUsedPages info from local cache, if possible
     445                 :             :  *
     446                 :             :  * Updating meta page isn't critical for index working, so
     447                 :             :  * 1 use ConditionalLockBuffer to improve concurrency
     448                 :             :  * 2 don't WAL-log metabuffer changes to decrease WAL traffic
     449                 :             :  */
     450                 :             : void
     451                 :       39322 : SpGistUpdateMetaPage(Relation index)
     452                 :             : {
     453                 :       39322 :         SpGistCache *cache = (SpGistCache *) index->rd_amcache;
     454                 :             : 
     455         [ -  + ]:       39322 :         if (cache != NULL)
     456                 :             :         {
     457                 :       39322 :                 Buffer          metabuffer;
     458                 :             : 
     459                 :       39322 :                 metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO);
     460                 :             : 
     461         [ +  - ]:       39322 :                 if (ConditionalLockBuffer(metabuffer))
     462                 :             :                 {
     463                 :       39322 :                         Page            metapage = BufferGetPage(metabuffer);
     464                 :       39322 :                         SpGistMetaPageData *metadata = SpGistPageGetMeta(metapage);
     465                 :             : 
     466                 :       39322 :                         metadata->lastUsedPages = cache->lastUsedPages;
     467                 :             : 
     468                 :             :                         /*
     469                 :             :                          * Set pd_lower just past the end of the metadata.  This is
     470                 :             :                          * essential, because without doing so, metadata will be lost if
     471                 :             :                          * xlog.c compresses the page.  (We must do this here because
     472                 :             :                          * pre-v11 versions of PG did not set the metapage's pd_lower
     473                 :             :                          * correctly, so a pg_upgraded index might contain the wrong
     474                 :             :                          * value.)
     475                 :             :                          */
     476                 :       39322 :                         ((PageHeader) metapage)->pd_lower =
     477                 :       39322 :                                 ((char *) metadata + sizeof(SpGistMetaPageData)) - (char *) metapage;
     478                 :             : 
     479                 :       39322 :                         MarkBufferDirty(metabuffer);
     480                 :       39322 :                         UnlockReleaseBuffer(metabuffer);
     481                 :       39322 :                 }
     482                 :             :                 else
     483                 :             :                 {
     484                 :           0 :                         ReleaseBuffer(metabuffer);
     485                 :             :                 }
     486                 :       39322 :         }
     487                 :       39322 : }
     488                 :             : 
     489                 :             : /* Macro to select proper element of lastUsedPages cache depending on flags */
     490                 :             : /* Masking flags with SPGIST_CACHED_PAGES is just for paranoia's sake */
     491                 :             : #define GET_LUP(c, f)  (&(c)->lastUsedPages.cachedPage[((unsigned int) (f)) % SPGIST_CACHED_PAGES])
     492                 :             : 
     493                 :             : /*
     494                 :             :  * Allocate and initialize a new buffer of the type and parity specified by
     495                 :             :  * flags.  The returned buffer is already pinned and exclusive-locked.
     496                 :             :  *
     497                 :             :  * When requesting an inner page, if we get one with the wrong parity,
     498                 :             :  * we just release the buffer and try again.  We will get a different page
     499                 :             :  * because GetFreeIndexPage will have marked the page used in FSM.  The page
     500                 :             :  * is entered in our local lastUsedPages cache, so there's some hope of
     501                 :             :  * making use of it later in this session, but otherwise we rely on VACUUM
     502                 :             :  * to eventually re-enter the page in FSM, making it available for recycling.
     503                 :             :  * Note that such a page does not get marked dirty here, so unless it's used
     504                 :             :  * fairly soon, the buffer will just get discarded and the page will remain
     505                 :             :  * as it was on disk.
     506                 :             :  *
     507                 :             :  * When we return a buffer to the caller, the page is *not* entered into
     508                 :             :  * the lastUsedPages cache; we expect the caller will do so after it's taken
     509                 :             :  * whatever space it will use.  This is because after the caller has used up
     510                 :             :  * some space, the page might have less space than whatever was cached already
     511                 :             :  * so we'd rather not trash the old cache entry.
     512                 :             :  */
     513                 :             : static Buffer
     514                 :         948 : allocNewBuffer(Relation index, int flags)
     515                 :             : {
     516                 :         948 :         SpGistCache *cache = spgGetCache(index);
     517                 :         948 :         uint16          pageflags = 0;
     518                 :             : 
     519         [ +  + ]:         948 :         if (GBUF_REQ_LEAF(flags))
     520                 :         931 :                 pageflags |= SPGIST_LEAF;
     521         [ +  - ]:         948 :         if (GBUF_REQ_NULLS(flags))
     522                 :           0 :                 pageflags |= SPGIST_NULLS;
     523                 :             : 
     524                 :         961 :         for (;;)
     525                 :             :         {
     526                 :         961 :                 Buffer          buffer;
     527                 :             : 
     528                 :         961 :                 buffer = SpGistNewBuffer(index);
     529                 :         961 :                 SpGistInitBuffer(buffer, pageflags);
     530                 :             : 
     531         [ +  + ]:         961 :                 if (pageflags & SPGIST_LEAF)
     532                 :             :                 {
     533                 :             :                         /* Leaf pages have no parity concerns, so just use it */
     534                 :         931 :                         return buffer;
     535                 :             :                 }
     536                 :             :                 else
     537                 :             :                 {
     538                 :          30 :                         BlockNumber blkno = BufferGetBlockNumber(buffer);
     539                 :          30 :                         int                     blkFlags = GBUF_INNER_PARITY(blkno);
     540                 :             : 
     541         [ +  + ]:          30 :                         if ((flags & GBUF_PARITY_MASK) == blkFlags)
     542                 :             :                         {
     543                 :             :                                 /* Page has right parity, use it */
     544                 :          17 :                                 return buffer;
     545                 :             :                         }
     546                 :             :                         else
     547                 :             :                         {
     548                 :             :                                 /* Page has wrong parity, record it in cache and try again */
     549         [ +  - ]:          13 :                                 if (pageflags & SPGIST_NULLS)
     550                 :           0 :                                         blkFlags |= GBUF_NULLS;
     551                 :          13 :                                 cache->lastUsedPages.cachedPage[blkFlags].blkno = blkno;
     552                 :          13 :                                 cache->lastUsedPages.cachedPage[blkFlags].freeSpace =
     553                 :          13 :                                         PageGetExactFreeSpace(BufferGetPage(buffer));
     554                 :          13 :                                 UnlockReleaseBuffer(buffer);
     555                 :             :                         }
     556         [ +  + ]:          30 :                 }
     557         [ +  + ]:         961 :         }
     558                 :         948 : }
     559                 :             : 
     560                 :             : /*
     561                 :             :  * Get a buffer of the type and parity specified by flags, having at least
     562                 :             :  * as much free space as indicated by needSpace.  We use the lastUsedPages
     563                 :             :  * cache to assign the same buffer previously requested when possible.
     564                 :             :  * The returned buffer is already pinned and exclusive-locked.
     565                 :             :  *
     566                 :             :  * *isNew is set true if the page was initialized here, false if it was
     567                 :             :  * already valid.
     568                 :             :  */
     569                 :             : Buffer
     570                 :        1725 : SpGistGetBuffer(Relation index, int flags, int needSpace, bool *isNew)
     571                 :             : {
     572                 :        1725 :         SpGistCache *cache = spgGetCache(index);
     573                 :        1725 :         SpGistLastUsedPage *lup;
     574                 :             : 
     575                 :             :         /* Bail out if even an empty page wouldn't meet the demand */
     576         [ +  - ]:        1725 :         if (needSpace > SPGIST_PAGE_CAPACITY)
     577   [ #  #  #  # ]:           0 :                 elog(ERROR, "desired SPGiST tuple size is too big");
     578                 :             : 
     579                 :             :         /*
     580                 :             :          * If possible, increase the space request to include relation's
     581                 :             :          * fillfactor.  This ensures that when we add unrelated tuples to a page,
     582                 :             :          * we try to keep 100-fillfactor% available for adding tuples that are
     583                 :             :          * related to the ones already on it.  But fillfactor mustn't cause an
     584                 :             :          * error for requests that would otherwise be legal.
     585                 :             :          */
     586   [ +  -  +  + ]:        1725 :         needSpace += SpGistGetTargetPageFreeSpace(index);
     587         [ +  + ]:        1725 :         needSpace = Min(needSpace, SPGIST_PAGE_CAPACITY);
     588                 :             : 
     589                 :             :         /* Get the cache entry for this flags setting */
     590                 :        1725 :         lup = GET_LUP(cache, flags);
     591                 :             : 
     592                 :             :         /* If we have nothing cached, just turn it over to allocNewBuffer */
     593         [ +  + ]:        1725 :         if (lup->blkno == InvalidBlockNumber)
     594                 :             :         {
     595                 :          29 :                 *isNew = true;
     596                 :          29 :                 return allocNewBuffer(index, flags);
     597                 :             :         }
     598                 :             : 
     599                 :             :         /* fixed pages should never be in cache */
     600         [ +  - ]:        1696 :         Assert(!SpGistBlockIsFixed(lup->blkno));
     601                 :             : 
     602                 :             :         /* If cached freeSpace isn't enough, don't bother looking at the page */
     603         [ +  + ]:        1696 :         if (lup->freeSpace >= needSpace)
     604                 :             :         {
     605                 :         777 :                 Buffer          buffer;
     606                 :         777 :                 Page            page;
     607                 :             : 
     608                 :         777 :                 buffer = ReadBuffer(index, lup->blkno);
     609                 :             : 
     610         [ +  - ]:         777 :                 if (!ConditionalLockBuffer(buffer))
     611                 :             :                 {
     612                 :             :                         /*
     613                 :             :                          * buffer is locked by another process, so return a new buffer
     614                 :             :                          */
     615                 :           0 :                         ReleaseBuffer(buffer);
     616                 :           0 :                         *isNew = true;
     617                 :           0 :                         return allocNewBuffer(index, flags);
     618                 :             :                 }
     619                 :             : 
     620                 :         777 :                 page = BufferGetPage(buffer);
     621                 :             : 
     622   [ +  -  +  -  :         777 :                 if (PageIsNew(page) || SpGistPageIsDeleted(page) || PageIsEmpty(page))
                   +  + ]
     623                 :             :                 {
     624                 :             :                         /* OK to initialize the page */
     625                 :          24 :                         uint16          pageflags = 0;
     626                 :             : 
     627         [ +  + ]:          24 :                         if (GBUF_REQ_LEAF(flags))
     628                 :          23 :                                 pageflags |= SPGIST_LEAF;
     629         [ +  - ]:          24 :                         if (GBUF_REQ_NULLS(flags))
     630                 :           0 :                                 pageflags |= SPGIST_NULLS;
     631                 :          24 :                         SpGistInitBuffer(buffer, pageflags);
     632                 :          24 :                         lup->freeSpace = PageGetExactFreeSpace(page) - needSpace;
     633                 :          24 :                         *isNew = true;
     634                 :          24 :                         return buffer;
     635                 :          24 :                 }
     636                 :             : 
     637                 :             :                 /*
     638                 :             :                  * Check that page is of right type and has enough space.  We must
     639                 :             :                  * recheck this since our cache isn't necessarily up to date.
     640                 :             :                  */
     641   [ +  +  +  +  :         753 :                 if ((GBUF_REQ_LEAF(flags) ? SpGistPageIsLeaf(page) : !SpGistPageIsLeaf(page)) &&
                   -  + ]
     642         [ +  + ]:         753 :                         (GBUF_REQ_NULLS(flags) ? SpGistPageStoresNulls(page) : !SpGistPageStoresNulls(page)))
     643                 :             :                 {
     644                 :         753 :                         int                     freeSpace = PageGetExactFreeSpace(page);
     645                 :             : 
     646         [ +  - ]:         753 :                         if (freeSpace >= needSpace)
     647                 :             :                         {
     648                 :             :                                 /* Success, update freespace info and return the buffer */
     649                 :         753 :                                 lup->freeSpace = freeSpace - needSpace;
     650                 :         753 :                                 *isNew = false;
     651                 :         753 :                                 return buffer;
     652                 :             :                         }
     653         [ -  + ]:         753 :                 }
     654                 :             : 
     655                 :             :                 /*
     656                 :             :                  * fallback to allocation of new buffer
     657                 :             :                  */
     658                 :        1762 :                 UnlockReleaseBuffer(buffer);
     659         [ +  - ]:        2539 :         }
     660                 :             : 
     661                 :             :         /* No success with cache, so return a new buffer */
     662                 :         919 :         *isNew = true;
     663                 :         919 :         return allocNewBuffer(index, flags);
     664                 :        3487 : }
     665                 :             : 
     666                 :             : /*
     667                 :             :  * Update lastUsedPages cache when done modifying a page.
     668                 :             :  *
     669                 :             :  * We update the appropriate cache entry if it already contained this page
     670                 :             :  * (its freeSpace is likely obsolete), or if this page has more space than
     671                 :             :  * whatever we had cached.
     672                 :             :  */
     673                 :             : void
     674                 :      401503 : SpGistSetLastUsedPage(Relation index, Buffer buffer)
     675                 :             : {
     676                 :      401503 :         SpGistCache *cache = spgGetCache(index);
     677                 :      401503 :         SpGistLastUsedPage *lup;
     678                 :      401503 :         int                     freeSpace;
     679                 :      401503 :         Page            page = BufferGetPage(buffer);
     680                 :      401503 :         BlockNumber blkno = BufferGetBlockNumber(buffer);
     681                 :      401503 :         int                     flags;
     682                 :             : 
     683                 :             :         /* Never enter fixed pages (root pages) in cache, though */
     684         [ +  + ]:      401503 :         if (SpGistBlockIsFixed(blkno))
     685                 :      131932 :                 return;
     686                 :             : 
     687         [ +  + ]:      269571 :         if (SpGistPageIsLeaf(page))
     688                 :      139828 :                 flags = GBUF_LEAF;
     689                 :             :         else
     690                 :      129743 :                 flags = GBUF_INNER_PARITY(blkno);
     691         [ +  - ]:      269571 :         if (SpGistPageStoresNulls(page))
     692                 :           0 :                 flags |= GBUF_NULLS;
     693                 :             : 
     694                 :      269571 :         lup = GET_LUP(cache, flags);
     695                 :             : 
     696                 :      269571 :         freeSpace = PageGetExactFreeSpace(page);
     697   [ +  +  +  +  :      269571 :         if (lup->blkno == InvalidBlockNumber || lup->blkno == blkno ||
                   +  + ]
     698                 :       76662 :                 lup->freeSpace < freeSpace)
     699                 :             :         {
     700                 :      194327 :                 lup->blkno = blkno;
     701                 :      194327 :                 lup->freeSpace = freeSpace;
     702                 :      194327 :         }
     703         [ -  + ]:      401503 : }
     704                 :             : 
     705                 :             : /*
     706                 :             :  * Initialize an SPGiST page to empty, with specified flags
     707                 :             :  */
     708                 :             : void
     709                 :        1110 : SpGistInitPage(Page page, uint16 f)
     710                 :             : {
     711                 :        1110 :         SpGistPageOpaque opaque;
     712                 :             : 
     713                 :        1110 :         PageInit(page, BLCKSZ, sizeof(SpGistPageOpaqueData));
     714                 :        1110 :         opaque = SpGistPageGetOpaque(page);
     715                 :        1110 :         opaque->flags = f;
     716                 :        1110 :         opaque->spgist_page_id = SPGIST_PAGE_ID;
     717                 :        1110 : }
     718                 :             : 
     719                 :             : /*
     720                 :             :  * Initialize a buffer's page to empty, with specified flags
     721                 :             :  */
     722                 :             : void
     723                 :        1087 : SpGistInitBuffer(Buffer b, uint16 f)
     724                 :             : {
     725         [ +  - ]:        1087 :         Assert(BufferGetPageSize(b) == BLCKSZ);
     726                 :        1087 :         SpGistInitPage(BufferGetPage(b), f);
     727                 :        1087 : }
     728                 :             : 
     729                 :             : /*
     730                 :             :  * Initialize metadata page
     731                 :             :  */
     732                 :             : void
     733                 :          21 : SpGistInitMetapage(Page page)
     734                 :             : {
     735                 :          21 :         SpGistMetaPageData *metadata;
     736                 :          21 :         int                     i;
     737                 :             : 
     738                 :          21 :         SpGistInitPage(page, SPGIST_META);
     739                 :          21 :         metadata = SpGistPageGetMeta(page);
     740                 :          21 :         memset(metadata, 0, sizeof(SpGistMetaPageData));
     741                 :          21 :         metadata->magicNumber = SPGIST_MAGIC_NUMBER;
     742                 :             : 
     743                 :             :         /* initialize last-used-page cache to empty */
     744         [ +  + ]:         189 :         for (i = 0; i < SPGIST_CACHED_PAGES; i++)
     745                 :         168 :                 metadata->lastUsedPages.cachedPage[i].blkno = InvalidBlockNumber;
     746                 :             : 
     747                 :             :         /*
     748                 :             :          * Set pd_lower just past the end of the metadata.  This is essential,
     749                 :             :          * because without doing so, metadata will be lost if xlog.c compresses
     750                 :             :          * the page.
     751                 :             :          */
     752                 :          21 :         ((PageHeader) page)->pd_lower =
     753                 :          21 :                 ((char *) metadata + sizeof(SpGistMetaPageData)) - (char *) page;
     754                 :          21 : }
     755                 :             : 
     756                 :             : /*
     757                 :             :  * reloptions processing for SPGiST
     758                 :             :  */
     759                 :             : bytea *
     760                 :          18 : spgoptions(Datum reloptions, bool validate)
     761                 :             : {
     762                 :             :         static const relopt_parse_elt tab[] = {
     763                 :             :                 {"fillfactor", RELOPT_TYPE_INT, offsetof(SpGistOptions, fillfactor)},
     764                 :             :         };
     765                 :             : 
     766                 :          18 :         return (bytea *) build_reloptions(reloptions, validate,
     767                 :             :                                                                           RELOPT_KIND_SPGIST,
     768                 :             :                                                                           sizeof(SpGistOptions),
     769                 :             :                                                                           tab, lengthof(tab));
     770                 :             : }
     771                 :             : 
     772                 :             : /*
     773                 :             :  * Get the space needed to store a non-null datum of the indicated type
     774                 :             :  * in an inner tuple (that is, as a prefix or node label).
     775                 :             :  * Note the result is already rounded up to a MAXALIGN boundary.
     776                 :             :  * Here we follow the convention that pass-by-val types are just stored
     777                 :             :  * in their Datum representation (compare memcpyInnerDatum).
     778                 :             :  */
     779                 :             : unsigned int
     780                 :        1904 : SpGistGetInnerTypeSize(SpGistTypeDesc *att, Datum datum)
     781                 :             : {
     782                 :        1904 :         unsigned int size;
     783                 :             : 
     784         [ +  + ]:        1904 :         if (att->attbyval)
     785                 :         940 :                 size = sizeof(Datum);
     786         [ +  + ]:         964 :         else if (att->attlen > 0)
     787                 :         663 :                 size = att->attlen;
     788                 :             :         else
     789                 :         301 :                 size = VARSIZE_ANY(DatumGetPointer(datum));
     790                 :             : 
     791                 :        3808 :         return MAXALIGN(size);
     792                 :        1904 : }
     793                 :             : 
     794                 :             : /*
     795                 :             :  * Copy the given non-null datum to *target, in the inner-tuple case
     796                 :             :  */
     797                 :             : static void
     798                 :        1904 : memcpyInnerDatum(void *target, SpGistTypeDesc *att, Datum datum)
     799                 :             : {
     800                 :        1904 :         unsigned int size;
     801                 :             : 
     802         [ +  + ]:        1904 :         if (att->attbyval)
     803                 :             :         {
     804                 :         940 :                 memcpy(target, &datum, sizeof(Datum));
     805                 :         940 :         }
     806                 :             :         else
     807                 :             :         {
     808         [ +  + ]:         964 :                 size = (att->attlen > 0) ? att->attlen : VARSIZE_ANY(DatumGetPointer(datum));
     809                 :         964 :                 memcpy(target, DatumGetPointer(datum), size);
     810                 :             :         }
     811                 :        1904 : }
     812                 :             : 
     813                 :             : /*
     814                 :             :  * Compute space required for a leaf tuple holding the given data.
     815                 :             :  *
     816                 :             :  * This must match the size-calculation portion of spgFormLeafTuple.
     817                 :             :  */
     818                 :             : Size
     819                 :     3237982 : SpGistGetLeafTupleSize(TupleDesc tupleDescriptor,
     820                 :             :                                            const Datum *datums, const bool *isnulls)
     821                 :             : {
     822                 :     3237982 :         Size            size;
     823                 :     3237982 :         Size            data_size;
     824                 :     3237982 :         bool            needs_null_mask = false;
     825                 :     3237982 :         int                     natts = tupleDescriptor->natts;
     826                 :             : 
     827                 :             :         /*
     828                 :             :          * Decide whether we need a nulls bitmask.
     829                 :             :          *
     830                 :             :          * If there is only a key attribute (natts == 1), never use a bitmask, for
     831                 :             :          * compatibility with the pre-v14 layout of leaf tuples.  Otherwise, we
     832                 :             :          * need one if any attribute is null.
     833                 :             :          */
     834         [ +  + ]:     3237982 :         if (natts > 1)
     835                 :             :         {
     836         [ +  + ]:      152943 :                 for (int i = 0; i < natts; i++)
     837                 :             :                 {
     838         [ +  + ]:      101961 :                         if (isnulls[i])
     839                 :             :                         {
     840                 :           3 :                                 needs_null_mask = true;
     841                 :           3 :                                 break;
     842                 :             :                         }
     843                 :      101958 :                 }
     844                 :       50982 :         }
     845                 :             : 
     846                 :             :         /*
     847                 :             :          * Calculate size of the data part; same as for heap tuples.
     848                 :             :          */
     849                 :     3237982 :         data_size = heap_compute_data_size(tupleDescriptor, datums, isnulls);
     850                 :             : 
     851                 :             :         /*
     852                 :             :          * Compute total size.
     853                 :             :          */
     854                 :     3237982 :         size = SGLTHDRSZ(needs_null_mask);
     855                 :     3237982 :         size += data_size;
     856                 :     3237982 :         size = MAXALIGN(size);
     857                 :             : 
     858                 :             :         /*
     859                 :             :          * Ensure that we can replace the tuple with a dead tuple later. This test
     860                 :             :          * is unnecessary when there are any non-null attributes, but be safe.
     861                 :             :          */
     862         [ +  - ]:     3237982 :         if (size < SGDTSIZE)
     863                 :           0 :                 size = SGDTSIZE;
     864                 :             : 
     865                 :     6475964 :         return size;
     866                 :     3237982 : }
     867                 :             : 
     868                 :             : /*
     869                 :             :  * Construct a leaf tuple containing the given heap TID and datum values
     870                 :             :  */
     871                 :             : SpGistLeafTuple
     872                 :      250478 : spgFormLeafTuple(SpGistState *state, const ItemPointerData *heapPtr,
     873                 :             :                                  const Datum *datums, const bool *isnulls)
     874                 :             : {
     875                 :      250478 :         SpGistLeafTuple tup;
     876                 :      250478 :         TupleDesc       tupleDescriptor = state->leafTupDesc;
     877                 :      250478 :         Size            size;
     878                 :      250478 :         Size            hoff;
     879                 :      250478 :         Size            data_size;
     880                 :      250478 :         bool            needs_null_mask = false;
     881                 :      250478 :         int                     natts = tupleDescriptor->natts;
     882                 :      250478 :         char       *tp;                         /* ptr to tuple data */
     883                 :      250478 :         uint16          tupmask = 0;    /* unused heap_fill_tuple output */
     884                 :             : 
     885                 :             :         /*
     886                 :             :          * Decide whether we need a nulls bitmask.
     887                 :             :          *
     888                 :             :          * If there is only a key attribute (natts == 1), never use a bitmask, for
     889                 :             :          * compatibility with the pre-v14 layout of leaf tuples.  Otherwise, we
     890                 :             :          * need one if any attribute is null.
     891                 :             :          */
     892         [ +  + ]:      250478 :         if (natts > 1)
     893                 :             :         {
     894         [ +  + ]:       59508 :                 for (int i = 0; i < natts; i++)
     895                 :             :                 {
     896         [ +  + ]:       39671 :                         if (isnulls[i])
     897                 :             :                         {
     898                 :           3 :                                 needs_null_mask = true;
     899                 :           3 :                                 break;
     900                 :             :                         }
     901                 :       39668 :                 }
     902                 :       19837 :         }
     903                 :             : 
     904                 :             :         /*
     905                 :             :          * Calculate size of the data part; same as for heap tuples.
     906                 :             :          */
     907                 :      250478 :         data_size = heap_compute_data_size(tupleDescriptor, datums, isnulls);
     908                 :             : 
     909                 :             :         /*
     910                 :             :          * Compute total size.
     911                 :             :          */
     912                 :      250478 :         hoff = SGLTHDRSZ(needs_null_mask);
     913                 :      250478 :         size = hoff + data_size;
     914                 :      250478 :         size = MAXALIGN(size);
     915                 :             : 
     916                 :             :         /*
     917                 :             :          * Ensure that we can replace the tuple with a dead tuple later. This test
     918                 :             :          * is unnecessary when there are any non-null attributes, but be safe.
     919                 :             :          */
     920         [ +  - ]:      250478 :         if (size < SGDTSIZE)
     921                 :           0 :                 size = SGDTSIZE;
     922                 :             : 
     923                 :             :         /* OK, form the tuple */
     924                 :      250478 :         tup = (SpGistLeafTuple) palloc0(size);
     925                 :             : 
     926                 :      250478 :         tup->size = size;
     927                 :      250478 :         SGLT_SET_NEXTOFFSET(tup, InvalidOffsetNumber);
     928                 :      250478 :         tup->heapPtr = *heapPtr;
     929                 :             : 
     930                 :      250478 :         tp = (char *) tup + hoff;
     931                 :             : 
     932         [ +  + ]:      250478 :         if (needs_null_mask)
     933                 :             :         {
     934                 :           3 :                 bits8      *bp;                 /* ptr to null bitmap in tuple */
     935                 :             : 
     936                 :             :                 /* Set nullmask presence bit in SpGistLeafTuple header */
     937                 :           3 :                 SGLT_SET_HASNULLMASK(tup, true);
     938                 :             :                 /* Fill the data area and null mask */
     939                 :           3 :                 bp = (bits8 *) ((char *) tup + sizeof(SpGistLeafTupleData));
     940                 :           6 :                 heap_fill_tuple(tupleDescriptor, datums, isnulls, tp, data_size,
     941                 :           3 :                                                 &tupmask, bp);
     942                 :           3 :         }
     943   [ +  +  +  + ]:      250475 :         else if (natts > 1 || !isnulls[spgKeyColumn])
     944                 :             :         {
     945                 :             :                 /* Fill data area only */
     946                 :      250463 :                 heap_fill_tuple(tupleDescriptor, datums, isnulls, tp, data_size,
     947                 :             :                                                 &tupmask, (bits8 *) NULL);
     948                 :      250463 :         }
     949                 :             :         /* otherwise we have no data, nor a bitmap, to fill */
     950                 :             : 
     951                 :      500956 :         return tup;
     952                 :      250478 : }
     953                 :             : 
     954                 :             : /*
     955                 :             :  * Construct a node (to go into an inner tuple) containing the given label
     956                 :             :  *
     957                 :             :  * Note that the node's downlink is just set invalid here.  Caller will fill
     958                 :             :  * it in later.
     959                 :             :  */
     960                 :             : SpGistNodeTuple
     961                 :        6629 : spgFormNodeTuple(SpGistState *state, Datum label, bool isnull)
     962                 :             : {
     963                 :        6629 :         SpGistNodeTuple tup;
     964                 :        6629 :         unsigned int size;
     965                 :        6629 :         unsigned short infomask = 0;
     966                 :             : 
     967                 :             :         /* compute space needed (note result is already maxaligned) */
     968                 :        6629 :         size = SGNTHDRSZ;
     969         [ +  + ]:        6629 :         if (!isnull)
     970                 :         825 :                 size += SpGistGetInnerTypeSize(&state->attLabelType, label);
     971                 :             : 
     972                 :             :         /*
     973                 :             :          * Here we make sure that the size will fit in the field reserved for it
     974                 :             :          * in t_info.
     975                 :             :          */
     976         [ +  - ]:        6629 :         if ((size & INDEX_SIZE_MASK) != size)
     977   [ #  #  #  # ]:           0 :                 ereport(ERROR,
     978                 :             :                                 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
     979                 :             :                                  errmsg("index row requires %zu bytes, maximum size is %zu",
     980                 :             :                                                 (Size) size, (Size) INDEX_SIZE_MASK)));
     981                 :             : 
     982                 :        6629 :         tup = (SpGistNodeTuple) palloc0(size);
     983                 :             : 
     984         [ +  + ]:        6629 :         if (isnull)
     985                 :        5804 :                 infomask |= INDEX_NULL_MASK;
     986                 :             :         /* we don't bother setting the INDEX_VAR_MASK bit */
     987                 :        6629 :         infomask |= size;
     988                 :        6629 :         tup->t_info = infomask;
     989                 :             : 
     990                 :             :         /* The TID field will be filled in later */
     991                 :        6629 :         ItemPointerSetInvalid(&tup->t_tid);
     992                 :             : 
     993         [ +  + ]:        6629 :         if (!isnull)
     994                 :         825 :                 memcpyInnerDatum(SGNTDATAPTR(tup), &state->attLabelType, label);
     995                 :             : 
     996                 :       13258 :         return tup;
     997                 :        6629 : }
     998                 :             : 
     999                 :             : /*
    1000                 :             :  * Construct an inner tuple containing the given prefix and node array
    1001                 :             :  */
    1002                 :             : SpGistInnerTuple
    1003                 :        1362 : spgFormInnerTuple(SpGistState *state, bool hasPrefix, Datum prefix,
    1004                 :             :                                   int nNodes, SpGistNodeTuple *nodes)
    1005                 :             : {
    1006                 :        1362 :         SpGistInnerTuple tup;
    1007                 :        1362 :         unsigned int size;
    1008                 :        1362 :         unsigned int prefixSize;
    1009                 :        1362 :         int                     i;
    1010                 :        1362 :         char       *ptr;
    1011                 :             : 
    1012                 :             :         /* Compute size needed */
    1013         [ +  + ]:        1362 :         if (hasPrefix)
    1014                 :        1079 :                 prefixSize = SpGistGetInnerTypeSize(&state->attPrefixType, prefix);
    1015                 :             :         else
    1016                 :         283 :                 prefixSize = 0;
    1017                 :             : 
    1018                 :        1362 :         size = SGITHDRSZ + prefixSize;
    1019                 :             : 
    1020                 :             :         /* Note: we rely on node tuple sizes to be maxaligned already */
    1021         [ +  + ]:        9338 :         for (i = 0; i < nNodes; i++)
    1022                 :        7976 :                 size += IndexTupleSize(nodes[i]);
    1023                 :             : 
    1024                 :             :         /*
    1025                 :             :          * Ensure that we can replace the tuple with a dead tuple later.  This
    1026                 :             :          * test is unnecessary given current tuple layouts, but let's be safe.
    1027                 :             :          */
    1028         [ +  - ]:        1362 :         if (size < SGDTSIZE)
    1029                 :           0 :                 size = SGDTSIZE;
    1030                 :             : 
    1031                 :             :         /*
    1032                 :             :          * Inner tuple should be small enough to fit on a page
    1033                 :             :          */
    1034         [ +  - ]:        1362 :         if (size > SPGIST_PAGE_CAPACITY - sizeof(ItemIdData))
    1035   [ #  #  #  # ]:           0 :                 ereport(ERROR,
    1036                 :             :                                 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
    1037                 :             :                                  errmsg("SP-GiST inner tuple size %zu exceeds maximum %zu",
    1038                 :             :                                                 (Size) size,
    1039                 :             :                                                 SPGIST_PAGE_CAPACITY - sizeof(ItemIdData)),
    1040                 :             :                                  errhint("Values larger than a buffer page cannot be indexed.")));
    1041                 :             : 
    1042                 :             :         /*
    1043                 :             :          * Check for overflow of header fields --- probably can't fail if the
    1044                 :             :          * above succeeded, but let's be paranoid
    1045                 :             :          */
    1046         [ +  - ]:        1362 :         if (size > SGITMAXSIZE ||
    1047                 :        1362 :                 prefixSize > SGITMAXPREFIXSIZE ||
    1048                 :        1362 :                 nNodes > SGITMAXNNODES)
    1049   [ #  #  #  # ]:           0 :                 elog(ERROR, "SPGiST inner tuple header field is too small");
    1050                 :             : 
    1051                 :             :         /* OK, form the tuple */
    1052                 :        1362 :         tup = (SpGistInnerTuple) palloc0(size);
    1053                 :             : 
    1054                 :        1362 :         tup->nNodes = nNodes;
    1055                 :        1362 :         tup->prefixSize = prefixSize;
    1056                 :        1362 :         tup->size = size;
    1057                 :             : 
    1058         [ +  + ]:        1362 :         if (hasPrefix)
    1059         [ +  - ]:        1079 :                 memcpyInnerDatum(SGITDATAPTR(tup), &state->attPrefixType, prefix);
    1060                 :             : 
    1061                 :        1362 :         ptr = (char *) SGITNODEPTR(tup);
    1062                 :             : 
    1063         [ +  + ]:        9338 :         for (i = 0; i < nNodes; i++)
    1064                 :             :         {
    1065                 :        7976 :                 SpGistNodeTuple node = nodes[i];
    1066                 :             : 
    1067                 :        7976 :                 memcpy(ptr, node, IndexTupleSize(node));
    1068                 :        7976 :                 ptr += IndexTupleSize(node);
    1069                 :        7976 :         }
    1070                 :             : 
    1071                 :        2724 :         return tup;
    1072                 :        1362 : }
    1073                 :             : 
    1074                 :             : /*
    1075                 :             :  * Construct a "dead" tuple to replace a tuple being deleted.
    1076                 :             :  *
    1077                 :             :  * The state can be SPGIST_REDIRECT, SPGIST_DEAD, or SPGIST_PLACEHOLDER.
    1078                 :             :  * For a REDIRECT tuple, a pointer (blkno+offset) must be supplied, and
    1079                 :             :  * the xid field is filled in automatically.
    1080                 :             :  *
    1081                 :             :  * This is called in critical sections, so we don't use palloc; the tuple
    1082                 :             :  * is built in preallocated storage.  It should be copied before another
    1083                 :             :  * call with different parameters can occur.
    1084                 :             :  */
    1085                 :             : SpGistDeadTuple
    1086                 :        1783 : spgFormDeadTuple(SpGistState *state, int tupstate,
    1087                 :             :                                  BlockNumber blkno, OffsetNumber offnum)
    1088                 :             : {
    1089                 :        1783 :         SpGistDeadTuple tuple = (SpGistDeadTuple) state->deadTupleStorage;
    1090                 :             : 
    1091                 :        1783 :         tuple->tupstate = tupstate;
    1092                 :        1783 :         tuple->size = SGDTSIZE;
    1093                 :        1783 :         SGLT_SET_NEXTOFFSET(tuple, InvalidOffsetNumber);
    1094                 :             : 
    1095         [ +  + ]:        1783 :         if (tupstate == SPGIST_REDIRECT)
    1096                 :             :         {
    1097                 :         278 :                 ItemPointerSet(&tuple->pointer, blkno, offnum);
    1098                 :         278 :                 tuple->xid = state->redirectXid;
    1099                 :         278 :         }
    1100                 :             :         else
    1101                 :             :         {
    1102                 :        1505 :                 ItemPointerSetInvalid(&tuple->pointer);
    1103                 :        1505 :                 tuple->xid = InvalidTransactionId;
    1104                 :             :         }
    1105                 :             : 
    1106                 :        3566 :         return tuple;
    1107                 :        1783 : }
    1108                 :             : 
    1109                 :             : /*
    1110                 :             :  * Convert an SPGiST leaf tuple into Datum/isnull arrays.
    1111                 :             :  *
    1112                 :             :  * The caller must allocate sufficient storage for the output arrays.
    1113                 :             :  * (INDEX_MAX_KEYS entries should be enough.)
    1114                 :             :  */
    1115                 :             : void
    1116                 :        8843 : spgDeformLeafTuple(SpGistLeafTuple tup, TupleDesc tupleDescriptor,
    1117                 :             :                                    Datum *datums, bool *isnulls, bool keyColumnIsNull)
    1118                 :             : {
    1119                 :        8843 :         bool            hasNullsMask = SGLT_GET_HASNULLMASK(tup);
    1120                 :        8843 :         char       *tp;                         /* ptr to tuple data */
    1121                 :        8843 :         bits8      *bp;                         /* ptr to null bitmap in tuple */
    1122                 :             : 
    1123   [ -  +  #  # ]:        8843 :         if (keyColumnIsNull && tupleDescriptor->natts == 1)
    1124                 :             :         {
    1125                 :             :                 /*
    1126                 :             :                  * Trivial case: there is only the key attribute and we're in a nulls
    1127                 :             :                  * tree.  The hasNullsMask bit in the tuple header should not be set
    1128                 :             :                  * (and thus we can't use index_deform_tuple_internal), but
    1129                 :             :                  * nonetheless the result is NULL.
    1130                 :             :                  *
    1131                 :             :                  * Note: currently this is dead code, because noplace calls this when
    1132                 :             :                  * there is only the key attribute.  But we should cover the case.
    1133                 :             :                  */
    1134         [ #  # ]:           0 :                 Assert(!hasNullsMask);
    1135                 :             : 
    1136                 :           0 :                 datums[spgKeyColumn] = (Datum) 0;
    1137                 :           0 :                 isnulls[spgKeyColumn] = true;
    1138                 :           0 :                 return;
    1139                 :             :         }
    1140                 :             : 
    1141                 :        8843 :         tp = (char *) tup + SGLTHDRSZ(hasNullsMask);
    1142                 :        8843 :         bp = (bits8 *) ((char *) tup + sizeof(SpGistLeafTupleData));
    1143                 :             : 
    1144                 :       17686 :         index_deform_tuple_internal(tupleDescriptor,
    1145                 :        8843 :                                                                 datums, isnulls,
    1146                 :        8843 :                                                                 tp, bp, hasNullsMask);
    1147                 :             : 
    1148                 :             :         /*
    1149                 :             :          * Key column isnull value from the tuple should be consistent with
    1150                 :             :          * keyColumnIsNull flag from the caller.
    1151                 :             :          */
    1152         [ +  - ]:        8843 :         Assert(keyColumnIsNull == isnulls[spgKeyColumn]);
    1153         [ -  + ]:        8843 : }
    1154                 :             : 
    1155                 :             : /*
    1156                 :             :  * Extract the label datums of the nodes within innerTuple
    1157                 :             :  *
    1158                 :             :  * Returns NULL if label datums are NULLs
    1159                 :             :  */
    1160                 :             : Datum *
    1161                 :     3110587 : spgExtractNodeLabels(SpGistState *state, SpGistInnerTuple innerTuple)
    1162                 :             : {
    1163                 :     3110587 :         Datum      *nodeLabels;
    1164                 :     3110587 :         int                     i;
    1165                 :     3110587 :         SpGistNodeTuple node;
    1166                 :             : 
    1167                 :             :         /* Either all the labels must be NULL, or none. */
    1168                 :     3110587 :         node = SGITNODEPTR(innerTuple);
    1169         [ +  + ]:     3110587 :         if (IndexTupleHasNulls(node))
    1170                 :             :         {
    1171         [ +  + ]:    16783040 :                 SGITITERATE(innerTuple, i, node)
    1172                 :             :                 {
    1173         [ +  - ]:    13707809 :                         if (!IndexTupleHasNulls(node))
    1174   [ #  #  #  # ]:           0 :                                 elog(ERROR, "some but not all node labels are null in SPGiST inner tuple");
    1175                 :    13707809 :                 }
    1176                 :             :                 /* They're all null, so just return NULL */
    1177                 :     3075231 :                 return NULL;
    1178                 :             :         }
    1179                 :             :         else
    1180                 :             :         {
    1181                 :       35356 :                 nodeLabels = palloc_array(Datum, innerTuple->nNodes);
    1182         [ +  + ]:      383721 :                 SGITITERATE(innerTuple, i, node)
    1183                 :             :                 {
    1184         [ +  - ]:      348365 :                         if (IndexTupleHasNulls(node))
    1185   [ #  #  #  # ]:           0 :                                 elog(ERROR, "some but not all node labels are null in SPGiST inner tuple");
    1186         [ +  - ]:      348365 :                         nodeLabels[i] = SGNTDATUM(node, state);
    1187                 :      348365 :                 }
    1188                 :       35356 :                 return nodeLabels;
    1189                 :             :         }
    1190                 :     3110587 : }
    1191                 :             : 
    1192                 :             : /*
    1193                 :             :  * Add a new item to the page, replacing a PLACEHOLDER item if possible.
    1194                 :             :  * Return the location it's inserted at, or InvalidOffsetNumber on failure.
    1195                 :             :  *
    1196                 :             :  * If startOffset isn't NULL, we start searching for placeholders at
    1197                 :             :  * *startOffset, and update that to the next place to search.  This is just
    1198                 :             :  * an optimization for repeated insertions.
    1199                 :             :  *
    1200                 :             :  * If errorOK is false, we throw error when there's not enough room,
    1201                 :             :  * rather than returning InvalidOffsetNumber.
    1202                 :             :  */
    1203                 :             : OffsetNumber
    1204                 :      264853 : SpGistPageAddNewItem(SpGistState *state, Page page, const void *item, Size size,
    1205                 :             :                                          OffsetNumber *startOffset, bool errorOK)
    1206                 :             : {
    1207                 :      264853 :         SpGistPageOpaque opaque = SpGistPageGetOpaque(page);
    1208                 :      264853 :         OffsetNumber i,
    1209                 :             :                                 maxoff,
    1210                 :             :                                 offnum;
    1211                 :             : 
    1212   [ +  +  -  + ]:      264853 :         if (opaque->nPlaceholder > 0 &&
    1213                 :       76042 :                 PageGetExactFreeSpace(page) + SGDTSIZE >= MAXALIGN(size))
    1214                 :             :         {
    1215                 :             :                 /* Try to replace a placeholder */
    1216                 :       76042 :                 maxoff = PageGetMaxOffsetNumber(page);
    1217                 :       76042 :                 offnum = InvalidOffsetNumber;
    1218                 :             : 
    1219                 :       76042 :                 for (;;)
    1220                 :             :                 {
    1221   [ +  +  +  + ]:       76042 :                         if (startOffset && *startOffset != InvalidOffsetNumber)
    1222                 :       18802 :                                 i = *startOffset;
    1223                 :             :                         else
    1224                 :       57240 :                                 i = FirstOffsetNumber;
    1225         [ -  + ]:     5230764 :                         for (; i <= maxoff; i++)
    1226                 :             :                         {
    1227                 :    10461528 :                                 SpGistDeadTuple it = (SpGistDeadTuple) PageGetItem(page,
    1228                 :     5230764 :                                                                                                                                    PageGetItemId(page, i));
    1229                 :             : 
    1230         [ +  + ]:     5230764 :                                 if (it->tupstate == SPGIST_PLACEHOLDER)
    1231                 :             :                                 {
    1232                 :       76042 :                                         offnum = i;
    1233                 :       76042 :                                         break;
    1234                 :             :                                 }
    1235      [ -  +  + ]:     5230764 :                         }
    1236                 :             : 
    1237                 :             :                         /* Done if we found a placeholder */
    1238         [ -  + ]:       76042 :                         if (offnum != InvalidOffsetNumber)
    1239                 :       76042 :                                 break;
    1240                 :             : 
    1241   [ #  #  #  # ]:           0 :                         if (startOffset && *startOffset != InvalidOffsetNumber)
    1242                 :             :                         {
    1243                 :             :                                 /* Hint was no good, re-search from beginning */
    1244                 :           0 :                                 *startOffset = InvalidOffsetNumber;
    1245                 :           0 :                                 continue;
    1246                 :             :                         }
    1247                 :             : 
    1248                 :             :                         /* Hmm, no placeholder found? */
    1249                 :           0 :                         opaque->nPlaceholder = 0;
    1250                 :           0 :                         break;
    1251                 :             :                 }
    1252                 :             : 
    1253         [ +  - ]:       76042 :                 if (offnum != InvalidOffsetNumber)
    1254                 :             :                 {
    1255                 :             :                         /* Replace the placeholder tuple */
    1256                 :       76042 :                         PageIndexTupleDelete(page, offnum);
    1257                 :             : 
    1258                 :       76042 :                         offnum = PageAddItem(page, item, size, offnum, false, false);
    1259                 :             : 
    1260                 :             :                         /*
    1261                 :             :                          * We should not have failed given the size check at the top of
    1262                 :             :                          * the function, but test anyway.  If we did fail, we must PANIC
    1263                 :             :                          * because we've already deleted the placeholder tuple, and
    1264                 :             :                          * there's no other way to keep the damage from getting to disk.
    1265                 :             :                          */
    1266         [ +  - ]:       76042 :                         if (offnum != InvalidOffsetNumber)
    1267                 :             :                         {
    1268         [ +  - ]:       76042 :                                 Assert(opaque->nPlaceholder > 0);
    1269                 :       76042 :                                 opaque->nPlaceholder--;
    1270         [ +  + ]:       76042 :                                 if (startOffset)
    1271                 :       19233 :                                         *startOffset = offnum + 1;
    1272                 :       76042 :                         }
    1273                 :             :                         else
    1274   [ #  #  #  # ]:           0 :                                 elog(PANIC, "failed to add item of size %zu to SPGiST index page",
    1275                 :             :                                          size);
    1276                 :             : 
    1277                 :       76042 :                         return offnum;
    1278                 :             :                 }
    1279                 :           0 :         }
    1280                 :             : 
    1281                 :             :         /* No luck in replacing a placeholder, so just add it to the page */
    1282                 :      188811 :         offnum = PageAddItem(page, item, size,
    1283                 :             :                                                  InvalidOffsetNumber, false, false);
    1284                 :             : 
    1285   [ -  +  #  # ]:      188811 :         if (offnum == InvalidOffsetNumber && !errorOK)
    1286   [ #  #  #  # ]:           0 :                 elog(ERROR, "failed to add item of size %zu to SPGiST index page",
    1287                 :             :                          size);
    1288                 :             : 
    1289                 :      188811 :         return offnum;
    1290                 :      264853 : }
    1291                 :             : 
    1292                 :             : /*
    1293                 :             :  *      spgproperty() -- Check boolean properties of indexes.
    1294                 :             :  *
    1295                 :             :  * This is optional for most AMs, but is required for SP-GiST because the core
    1296                 :             :  * property code doesn't support AMPROP_DISTANCE_ORDERABLE.
    1297                 :             :  */
    1298                 :             : bool
    1299                 :          31 : spgproperty(Oid index_oid, int attno,
    1300                 :             :                         IndexAMProperty prop, const char *propname,
    1301                 :             :                         bool *res, bool *isnull)
    1302                 :             : {
    1303                 :          31 :         Oid                     opclass,
    1304                 :             :                                 opfamily,
    1305                 :             :                                 opcintype;
    1306                 :          31 :         CatCList   *catlist;
    1307                 :          31 :         int                     i;
    1308                 :             : 
    1309                 :             :         /* Only answer column-level inquiries */
    1310         [ +  + ]:          31 :         if (attno == 0)
    1311                 :          11 :                 return false;
    1312                 :             : 
    1313         [ +  + ]:          20 :         switch (prop)
    1314                 :             :         {
    1315                 :             :                 case AMPROP_DISTANCE_ORDERABLE:
    1316                 :             :                         break;
    1317                 :             :                 default:
    1318                 :          18 :                         return false;
    1319                 :             :         }
    1320                 :             : 
    1321                 :             :         /*
    1322                 :             :          * Currently, SP-GiST distance-ordered scans require that there be a
    1323                 :             :          * distance operator in the opclass with the default types. So we assume
    1324                 :             :          * that if such an operator exists, then there's a reason for it.
    1325                 :             :          */
    1326                 :             : 
    1327                 :             :         /* First we need to know the column's opclass. */
    1328                 :           2 :         opclass = get_index_column_opclass(index_oid, attno);
    1329         [ +  - ]:           2 :         if (!OidIsValid(opclass))
    1330                 :             :         {
    1331                 :           0 :                 *isnull = true;
    1332                 :           0 :                 return true;
    1333                 :             :         }
    1334                 :             : 
    1335                 :             :         /* Now look up the opclass family and input datatype. */
    1336         [ -  + ]:           2 :         if (!get_opclass_opfamily_and_input_type(opclass, &opfamily, &opcintype))
    1337                 :             :         {
    1338                 :           0 :                 *isnull = true;
    1339                 :           0 :                 return true;
    1340                 :             :         }
    1341                 :             : 
    1342                 :             :         /* And now we can check whether the operator is provided. */
    1343                 :           2 :         catlist = SearchSysCacheList1(AMOPSTRATEGY,
    1344                 :             :                                                                   ObjectIdGetDatum(opfamily));
    1345                 :             : 
    1346                 :           2 :         *res = false;
    1347                 :             : 
    1348         [ +  + ]:          17 :         for (i = 0; i < catlist->n_members; i++)
    1349                 :             :         {
    1350                 :          16 :                 HeapTuple       amoptup = &catlist->members[i]->tuple;
    1351                 :          16 :                 Form_pg_amop amopform = (Form_pg_amop) GETSTRUCT(amoptup);
    1352                 :             : 
    1353         [ +  + ]:          16 :                 if (amopform->amoppurpose == AMOP_ORDER &&
    1354         [ -  + ]:           1 :                         (amopform->amoplefttype == opcintype ||
    1355         [ -  + ]:           1 :                          amopform->amoprighttype == opcintype) &&
    1356                 :           2 :                         opfamily_can_sort_type(amopform->amopsortfamily,
    1357                 :           1 :                                                                    get_op_rettype(amopform->amopopr)))
    1358                 :             :                 {
    1359                 :           1 :                         *res = true;
    1360                 :           1 :                         break;
    1361                 :             :                 }
    1362      [ -  +  + ]:          16 :         }
    1363                 :             : 
    1364                 :           2 :         ReleaseSysCacheList(catlist);
    1365                 :             : 
    1366                 :           2 :         *isnull = false;
    1367                 :             : 
    1368                 :           2 :         return true;
    1369                 :          31 : }
        

Generated by: LCOV version 2.3.2-1