LCOV - code coverage report
Current view: top level - src/backend/access/common - indextuple.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 94.0 % 150 141
Test Date: 2026-03-19 09:16:02 Functions: 100.0 % 7 7
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * indextuple.c
       4              :  *     This file contains index tuple accessor and mutator routines,
       5              :  *     as well as various tuple utilities.
       6              :  *
       7              :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       8              :  * Portions Copyright (c) 1994, Regents of the University of California
       9              :  *
      10              :  *
      11              :  * IDENTIFICATION
      12              :  *    src/backend/access/common/indextuple.c
      13              :  *
      14              :  *-------------------------------------------------------------------------
      15              :  */
      16              : 
      17              : #include "postgres.h"
      18              : 
      19              : #include "access/detoast.h"
      20              : #include "access/heaptoast.h"
      21              : #include "access/htup_details.h"
      22              : #include "access/itup.h"
      23              : #include "access/toast_internals.h"
      24              : 
      25              : /*
      26              :  * This enables de-toasting of index entries.  Needed until VACUUM is
      27              :  * smart enough to rebuild indexes from scratch.
      28              :  */
      29              : #define TOAST_INDEX_HACK
      30              : 
      31              : /* ----------------------------------------------------------------
      32              :  *                index_ tuple interface routines
      33              :  * ----------------------------------------------------------------
      34              :  */
      35              : 
      36              :  /* ----------------
      37              :   *     index_form_tuple
      38              :   *
      39              :   *     As index_form_tuple_context, but allocates the returned tuple in the
      40              :   *     CurrentMemoryContext.
      41              :   * ----------------
      42              :   */
      43              : IndexTuple
      44      8099747 : index_form_tuple(TupleDesc tupleDescriptor,
      45              :                  const Datum *values,
      46              :                  const bool *isnull)
      47              : {
      48      8099747 :     return index_form_tuple_context(tupleDescriptor, values, isnull,
      49              :                                     CurrentMemoryContext);
      50              : }
      51              : 
      52              : /* ----------------
      53              :  *      index_form_tuple_context
      54              :  *
      55              :  *      This shouldn't leak any memory; otherwise, callers such as
      56              :  *      tuplesort_putindextuplevalues() will be very unhappy.
      57              :  *
      58              :  *      This shouldn't perform external table access provided caller
      59              :  *      does not pass values that are stored EXTERNAL.
      60              :  *
      61              :  *      Allocates returned tuple in provided 'context'.
      62              :  * ----------------
      63              :  */
      64              : IndexTuple
      65     15947384 : index_form_tuple_context(TupleDesc tupleDescriptor,
      66              :                          const Datum *values,
      67              :                          const bool *isnull,
      68              :                          MemoryContext context)
      69              : {
      70              :     char       *tp;             /* tuple pointer */
      71              :     IndexTuple  tuple;          /* return tuple */
      72              :     Size        size,
      73              :                 data_size,
      74              :                 hoff;
      75              :     int         i;
      76     15947384 :     unsigned short infomask = 0;
      77     15947384 :     bool        hasnull = false;
      78     15947384 :     uint16      tupmask = 0;
      79     15947384 :     int         numberOfAttributes = tupleDescriptor->natts;
      80              : 
      81              : #ifdef TOAST_INDEX_HACK
      82     15947384 :     Datum       untoasted_values[INDEX_MAX_KEYS] = {0};
      83     15947384 :     bool        untoasted_free[INDEX_MAX_KEYS] = {0};
      84              : #endif
      85              : 
      86     15947384 :     if (numberOfAttributes > INDEX_MAX_KEYS)
      87            0 :         ereport(ERROR,
      88              :                 (errcode(ERRCODE_TOO_MANY_COLUMNS),
      89              :                  errmsg("number of index columns (%d) exceeds limit (%d)",
      90              :                         numberOfAttributes, INDEX_MAX_KEYS)));
      91              : 
      92              : #ifdef TOAST_INDEX_HACK
      93     41057947 :     for (i = 0; i < numberOfAttributes; i++)
      94              :     {
      95     25110563 :         Form_pg_attribute att = TupleDescAttr(tupleDescriptor, i);
      96              : 
      97     25110563 :         untoasted_values[i] = values[i];
      98     25110563 :         untoasted_free[i] = false;
      99              : 
     100              :         /* Do nothing if value is NULL or not of varlena type */
     101     25110563 :         if (isnull[i] || att->attlen != -1)
     102     24273706 :             continue;
     103              : 
     104              :         /*
     105              :          * If value is stored EXTERNAL, must fetch it so we are not depending
     106              :          * on outside storage.  This should be improved someday.
     107              :          */
     108       836857 :         if (VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
     109              :         {
     110          241 :             untoasted_values[i] =
     111          241 :                 PointerGetDatum(detoast_external_attr((varlena *)
     112          241 :                                                       DatumGetPointer(values[i])));
     113          241 :             untoasted_free[i] = true;
     114              :         }
     115              : 
     116              :         /*
     117              :          * If value is above size target, and is of a compressible datatype,
     118              :          * try to compress it in-line.
     119              :          */
     120      1281177 :         if (!VARATT_IS_EXTENDED(DatumGetPointer(untoasted_values[i])) &&
     121       444320 :             VARSIZE(DatumGetPointer(untoasted_values[i])) > TOAST_INDEX_TARGET &&
     122        64887 :             (att->attstorage == TYPSTORAGE_EXTENDED ||
     123        57949 :              att->attstorage == TYPSTORAGE_MAIN))
     124              :         {
     125              :             Datum       cvalue;
     126              : 
     127         6938 :             cvalue = toast_compress_datum(untoasted_values[i],
     128         6938 :                                           att->attcompression);
     129              : 
     130         6938 :             if (DatumGetPointer(cvalue) != NULL)
     131              :             {
     132              :                 /* successful compression */
     133         2024 :                 if (untoasted_free[i])
     134            0 :                     pfree(DatumGetPointer(untoasted_values[i]));
     135         2024 :                 untoasted_values[i] = cvalue;
     136         2024 :                 untoasted_free[i] = true;
     137              :             }
     138              :         }
     139              :     }
     140              : #endif
     141              : 
     142     40999756 :     for (i = 0; i < numberOfAttributes; i++)
     143              :     {
     144     25109027 :         if (isnull[i])
     145              :         {
     146        56655 :             hasnull = true;
     147        56655 :             break;
     148              :         }
     149              :     }
     150              : 
     151     15947384 :     if (hasnull)
     152        56655 :         infomask |= INDEX_NULL_MASK;
     153              : 
     154     15947384 :     hoff = IndexInfoFindDataOffset(infomask);
     155              : #ifdef TOAST_INDEX_HACK
     156     15947384 :     data_size = heap_compute_data_size(tupleDescriptor,
     157              :                                        untoasted_values, isnull);
     158              : #else
     159              :     data_size = heap_compute_data_size(tupleDescriptor,
     160              :                                        values, isnull);
     161              : #endif
     162     15947384 :     size = hoff + data_size;
     163     15947384 :     size = MAXALIGN(size);      /* be conservative */
     164              : 
     165     15947384 :     tp = (char *) MemoryContextAllocZero(context, size);
     166     15947384 :     tuple = (IndexTuple) tp;
     167              : 
     168     15947384 :     heap_fill_tuple(tupleDescriptor,
     169              : #ifdef TOAST_INDEX_HACK
     170              :                     untoasted_values,
     171              : #else
     172              :                     values,
     173              : #endif
     174              :                     isnull,
     175              :                     tp + hoff,
     176              :                     data_size,
     177              :                     &tupmask,
     178              :                     (hasnull ? (bits8 *) tp + sizeof(IndexTupleData) : NULL));
     179              : 
     180              : #ifdef TOAST_INDEX_HACK
     181     41057947 :     for (i = 0; i < numberOfAttributes; i++)
     182              :     {
     183     25110563 :         if (untoasted_free[i])
     184         2265 :             pfree(DatumGetPointer(untoasted_values[i]));
     185              :     }
     186              : #endif
     187              : 
     188              :     /*
     189              :      * We do this because heap_fill_tuple wants to initialize a "tupmask"
     190              :      * which is used for HeapTuples, but we want an indextuple infomask. The
     191              :      * only relevant info is the "has variable attributes" field. We have
     192              :      * already set the hasnull bit above.
     193              :      */
     194     15947384 :     if (tupmask & HEAP_HASVARWIDTH)
     195      2003441 :         infomask |= INDEX_VAR_MASK;
     196              : 
     197              :     /* Also assert we got rid of external attributes */
     198              : #ifdef TOAST_INDEX_HACK
     199              :     Assert((tupmask & HEAP_HASEXTERNAL) == 0);
     200              : #endif
     201              : 
     202              :     /*
     203              :      * Here we make sure that the size will fit in the field reserved for it
     204              :      * in t_info.
     205              :      */
     206     15947384 :     if ((size & INDEX_SIZE_MASK) != size)
     207            0 :         ereport(ERROR,
     208              :                 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
     209              :                  errmsg("index row requires %zu bytes, maximum size is %zu",
     210              :                         size, (Size) INDEX_SIZE_MASK)));
     211              : 
     212     15947384 :     infomask |= size;
     213              : 
     214              :     /*
     215              :      * initialize metadata
     216              :      */
     217     15947384 :     tuple->t_info = infomask;
     218     15947384 :     return tuple;
     219              : }
     220              : 
     221              : /* ----------------
     222              :  *      nocache_index_getattr
     223              :  *
     224              :  *      This gets called from index_getattr() macro, and only in cases
     225              :  *      where we can't use cacheoffset and the value is not null.
     226              :  * ----------------
     227              :  */
     228              : Datum
     229     29246385 : nocache_index_getattr(IndexTuple tup,
     230              :                       int attnum,
     231              :                       TupleDesc tupleDesc)
     232              : {
     233              :     CompactAttribute *cattr;
     234              :     char       *tp;             /* ptr to data part of tuple */
     235     29246385 :     bits8      *bp = NULL;      /* ptr to null bitmap in tuple */
     236              :     int         data_off;       /* tuple data offset */
     237              :     int         off;            /* current offset within data */
     238              :     int         startAttr;
     239              :     int         firstNullAttr;
     240     29246385 :     bool        hasnulls = IndexTupleHasNulls(tup);
     241              :     int         i;
     242              : 
     243              :     /* Did someone forget to call TupleDescFinalize()? */
     244              :     Assert(tupleDesc->firstNonCachedOffsetAttr >= 0);
     245              : 
     246     29246385 :     attnum--;
     247              : 
     248     29246385 :     data_off = IndexInfoFindDataOffset(tup->t_info);
     249     29246385 :     tp = (char *) tup + data_off;
     250              : 
     251              :     /*
     252              :      * To minimize the number of attributes we need to look at, start walking
     253              :      * the tuple at the attribute with the highest attcacheoff prior to attnum
     254              :      * or the first NULL attribute prior to attnum, whichever comes first.
     255              :      */
     256     29246385 :     if (hasnulls)
     257              :     {
     258        64238 :         bp = (bits8 *) ((char *) tup + sizeof(IndexTupleData));
     259        64238 :         firstNullAttr = first_null_attr(bp, attnum);
     260              :     }
     261              :     else
     262     29182147 :         firstNullAttr = attnum;
     263              : 
     264     29246385 :     if (tupleDesc->firstNonCachedOffsetAttr > 0 && firstNullAttr > 0)
     265              :     {
     266              :         /*
     267              :          * Try to start with the highest attribute with an attcacheoff that's
     268              :          * prior to the one we're looking for, or with the attribute prior to
     269              :          * the first NULL attribute, if there is one.
     270              :          */
     271      5484752 :         startAttr = Min(tupleDesc->firstNonCachedOffsetAttr - 1, firstNullAttr - 1);
     272      5484752 :         off = TupleDescCompactAttr(tupleDesc, startAttr)->attcacheoff;
     273              :     }
     274              :     else
     275              :     {
     276              :         /* Otherwise, start at the beginning... */
     277     23761633 :         startAttr = 0;
     278     23761633 :         off = 0;
     279              :     }
     280              : 
     281              :     /*
     282              :      * Calculate 'off' up to the first NULL attr.  We use two cheaper loops
     283              :      * when the tuple has no variable-width columns.  When variable-width
     284              :      * columns exists, we use att_addlength_pointer() to move the offset
     285              :      * beyond the current attribute.
     286              :      */
     287     29246385 :     if (IndexTupleHasVarwidths(tup))
     288              :     {
     289              :         /* Calculate the offset up until the first NULL */
     290     38639441 :         for (i = startAttr; i < firstNullAttr; i++)
     291              :         {
     292      9440003 :             cattr = TupleDescCompactAttr(tupleDesc, i);
     293              : 
     294      9440003 :             off = att_pointer_alignby(off,
     295              :                                       cattr->attalignby,
     296              :                                       cattr->attlen,
     297              :                                       tp + off);
     298      9440003 :             off = att_addlength_pointer(off, cattr->attlen, tp + off);
     299              :         }
     300              : 
     301              :         /* Calculate the offset for any remaining columns. */
     302     29199446 :         for (; i < attnum; i++)
     303              :         {
     304              :             Assert(hasnulls);
     305              : 
     306            8 :             if (att_isnull(i, bp))
     307            8 :                 continue;
     308              : 
     309            0 :             cattr = TupleDescCompactAttr(tupleDesc, i);
     310              : 
     311            0 :             off = att_pointer_alignby(off,
     312              :                                       cattr->attalignby,
     313              :                                       cattr->attlen,
     314              :                                       tp + off);
     315            0 :             off = att_addlength_pointer(off, cattr->attlen, tp + off);
     316              :         }
     317              :     }
     318              :     else
     319              :     {
     320              :         /* Handle tuples with only fixed-width attributes */
     321              : 
     322              :         /* Calculate the offset up until the first NULL */
     323        48207 :         for (i = startAttr; i < firstNullAttr; i++)
     324              :         {
     325         1260 :             cattr = TupleDescCompactAttr(tupleDesc, i);
     326              : 
     327              :             Assert(cattr->attlen > 0);
     328         1260 :             off = att_nominal_alignby(off, cattr->attalignby);
     329         1260 :             off += cattr->attlen;
     330              :         }
     331              : 
     332              :         /* Calculate the offset for any remaining columns. */
     333        47399 :         for (; i < attnum; i++)
     334              :         {
     335              :             Assert(hasnulls);
     336              : 
     337          452 :             if (att_isnull(i, bp))
     338          452 :                 continue;
     339              : 
     340            0 :             cattr = TupleDescCompactAttr(tupleDesc, i);
     341              : 
     342              :             Assert(cattr->attlen > 0);
     343            0 :             off = att_nominal_alignby(off, cattr->attalignby);
     344            0 :             off += cattr->attlen;
     345              :         }
     346              :     }
     347              : 
     348     29246385 :     cattr = TupleDescCompactAttr(tupleDesc, attnum);
     349     29246385 :     off = att_pointer_alignby(off, cattr->attalignby,
     350              :                               cattr->attlen, tp + off);
     351     29246385 :     return fetchatt(cattr, tp + off);
     352              : }
     353              : 
     354              : /*
     355              :  * Convert an index tuple into Datum/isnull arrays.
     356              :  *
     357              :  * The caller must allocate sufficient storage for the output arrays.
     358              :  * (INDEX_MAX_KEYS entries should be enough.)
     359              :  *
     360              :  * This is nearly the same as heap_deform_tuple(), but for IndexTuples.
     361              :  * One difference is that the tuple should never have any missing columns.
     362              :  */
     363              : void
     364      2777246 : index_deform_tuple(IndexTuple tup, TupleDesc tupleDescriptor,
     365              :                    Datum *values, bool *isnull)
     366              : {
     367              :     char       *tp;             /* ptr to tuple data */
     368              :     bits8      *bp;             /* ptr to null bitmap in tuple */
     369              : 
     370              :     /* XXX "knows" t_bits are just after fixed tuple header! */
     371      2777246 :     bp = (bits8 *) ((char *) tup + sizeof(IndexTupleData));
     372              : 
     373      2777246 :     tp = (char *) tup + IndexInfoFindDataOffset(tup->t_info);
     374              : 
     375      2777246 :     index_deform_tuple_internal(tupleDescriptor, values, isnull,
     376      2777246 :                                 tp, bp, IndexTupleHasNulls(tup));
     377      2777246 : }
     378              : 
     379              : /*
     380              :  * Convert an index tuple into Datum/isnull arrays,
     381              :  * without assuming any specific layout of the index tuple header.
     382              :  *
     383              :  * Caller must supply pointer to data area, pointer to nulls bitmap
     384              :  * (which can be NULL if !hasnulls), and hasnulls flag.
     385              :  */
     386              : void
     387      2815696 : index_deform_tuple_internal(TupleDesc tupleDescriptor,
     388              :                             Datum *values, bool *isnull,
     389              :                             char *tp, bits8 *bp, int hasnulls)
     390              : {
     391              :     CompactAttribute *cattr;
     392      2815696 :     int         natts = tupleDescriptor->natts; /* number of atts to extract */
     393      2815696 :     int         attnum = 0;
     394      2815696 :     uint32      off = 0;        /* offset in tuple data */
     395              :     int         firstNonCacheOffsetAttr;
     396              :     int         firstNullAttr;
     397              : 
     398              :     /* Assert to protect callers who allocate fixed-size arrays */
     399              :     Assert(natts <= INDEX_MAX_KEYS);
     400              : 
     401              :     /* Did someone forget to call TupleDescFinalize()? */
     402              :     Assert(tupleDescriptor->firstNonCachedOffsetAttr >= 0);
     403              : 
     404      2815696 :     firstNonCacheOffsetAttr = Min(tupleDescriptor->firstNonCachedOffsetAttr, natts);
     405              : 
     406      2815696 :     if (hasnulls)
     407              :     {
     408         2647 :         firstNullAttr = first_null_attr(bp, natts);
     409         2647 :         firstNonCacheOffsetAttr = Min(firstNonCacheOffsetAttr, firstNullAttr);
     410              :     }
     411              :     else
     412      2813049 :         firstNullAttr = natts;
     413              : 
     414      2815696 :     if (firstNonCacheOffsetAttr > 0)
     415              :     {
     416              : #ifdef USE_ASSERT_CHECKING
     417              :         /* In Assert enabled builds, verify attcacheoff is correct */
     418              :         off = 0;
     419              : #endif
     420              : 
     421              :         do
     422              :         {
     423      3844921 :             isnull[attnum] = false;
     424      3844921 :             cattr = TupleDescCompactAttr(tupleDescriptor, attnum);
     425              : 
     426              : #ifdef USE_ASSERT_CHECKING
     427              :             off = att_nominal_alignby(off, cattr->attalignby);
     428              :             Assert(off == cattr->attcacheoff);
     429              :             off += cattr->attlen;
     430              : #endif
     431              : 
     432      7689842 :             values[attnum] = fetch_att_noerr(tp + cattr->attcacheoff, cattr->attbyval,
     433      3844921 :                                              cattr->attlen);
     434      3844921 :         } while (++attnum < firstNonCacheOffsetAttr);
     435              : 
     436      2778841 :         off = cattr->attcacheoff + cattr->attlen;
     437              :     }
     438              : 
     439      2862491 :     for (; attnum < firstNullAttr; attnum++)
     440              :     {
     441        46795 :         isnull[attnum] = false;
     442        46795 :         cattr = TupleDescCompactAttr(tupleDescriptor, attnum);
     443              : 
     444              :         /* align 'off', fetch the datum, and increment off beyond the datum */
     445        46795 :         values[attnum] = align_fetch_then_add(tp,
     446              :                                               &off,
     447        46795 :                                               cattr->attbyval,
     448        46795 :                                               cattr->attlen,
     449        46795 :                                               cattr->attalignby);
     450              :     }
     451              : 
     452      2820779 :     for (; attnum < natts; attnum++)
     453              :     {
     454              :         Assert(hasnulls);
     455              : 
     456         5083 :         if (att_isnull(attnum, bp))
     457              :         {
     458         2663 :             values[attnum] = (Datum) 0;
     459         2663 :             isnull[attnum] = true;
     460         2663 :             continue;
     461              :         }
     462              : 
     463         2420 :         isnull[attnum] = false;
     464         2420 :         cattr = TupleDescCompactAttr(tupleDescriptor, attnum);
     465              : 
     466              :         /* align 'off', fetch the attr's value, and increment off beyond it */
     467         2420 :         values[attnum] = align_fetch_then_add(tp,
     468              :                                               &off,
     469         2420 :                                               cattr->attbyval,
     470         2420 :                                               cattr->attlen,
     471         2420 :                                               cattr->attalignby);
     472              :     }
     473      2815696 : }
     474              : 
     475              : /*
     476              :  * Create a palloc'd copy of an index tuple.
     477              :  */
     478              : IndexTuple
     479      3266037 : CopyIndexTuple(IndexTuple source)
     480              : {
     481              :     IndexTuple  result;
     482              :     Size        size;
     483              : 
     484      3266037 :     size = IndexTupleSize(source);
     485      3266037 :     result = (IndexTuple) palloc(size);
     486      3266037 :     memcpy(result, source, size);
     487      3266037 :     return result;
     488              : }
     489              : 
     490              : /*
     491              :  * Create a palloc'd copy of an index tuple, leaving only the first
     492              :  * leavenatts attributes remaining.
     493              :  *
     494              :  * Truncation is guaranteed to result in an index tuple that is no
     495              :  * larger than the original.  It is safe to use the IndexTuple with
     496              :  * the original tuple descriptor, but caller must avoid actually
     497              :  * accessing truncated attributes from returned tuple!  In practice
     498              :  * this means that index_getattr() must be called with special care,
     499              :  * and that the truncated tuple should only ever be accessed by code
     500              :  * under caller's direct control.
     501              :  *
     502              :  * It's safe to call this function with a buffer lock held, since it
     503              :  * never performs external table access.  If it ever became possible
     504              :  * for index tuples to contain EXTERNAL TOAST values, then this would
     505              :  * have to be revisited.
     506              :  */
     507              : IndexTuple
     508        37369 : index_truncate_tuple(TupleDesc sourceDescriptor, IndexTuple source,
     509              :                      int leavenatts)
     510              : {
     511              :     TupleDesc   truncdesc;
     512              :     Datum       values[INDEX_MAX_KEYS];
     513              :     bool        isnull[INDEX_MAX_KEYS];
     514              :     IndexTuple  truncated;
     515              : 
     516              :     Assert(leavenatts <= sourceDescriptor->natts);
     517              : 
     518              :     /* Easy case: no truncation actually required */
     519        37369 :     if (leavenatts == sourceDescriptor->natts)
     520        21984 :         return CopyIndexTuple(source);
     521              : 
     522              :     /* Create temporary truncated tuple descriptor */
     523        15385 :     truncdesc = CreateTupleDescTruncatedCopy(sourceDescriptor, leavenatts);
     524              : 
     525              :     /* Deform, form copy of tuple with fewer attributes */
     526        15385 :     index_deform_tuple(source, truncdesc, values, isnull);
     527        15385 :     truncated = index_form_tuple(truncdesc, values, isnull);
     528        15385 :     truncated->t_tid = source->t_tid;
     529              :     Assert(IndexTupleSize(truncated) <= IndexTupleSize(source));
     530              : 
     531              :     /*
     532              :      * Cannot leak memory here, TupleDescCopy() doesn't allocate any inner
     533              :      * structure, so, plain pfree() should clean all allocated memory
     534              :      */
     535        15385 :     pfree(truncdesc);
     536              : 
     537        15385 :     return truncated;
     538              : }
        

Generated by: LCOV version 2.0-1