LCOV - code coverage report
Current view: top level - src/backend/access/common - indextuple.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 92.0 % 150 138
Test Date: 2026-03-15 23:16:31 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      6907283 : index_form_tuple(TupleDesc tupleDescriptor,
      45              :                  const Datum *values,
      46              :                  const bool *isnull)
      47              : {
      48      6907283 :     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     13649259 : 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     13649259 :     unsigned short infomask = 0;
      77     13649259 :     bool        hasnull = false;
      78     13649259 :     uint16      tupmask = 0;
      79     13649259 :     int         numberOfAttributes = tupleDescriptor->natts;
      80              : 
      81              : #ifdef TOAST_INDEX_HACK
      82     13649259 :     Datum       untoasted_values[INDEX_MAX_KEYS] = {0};
      83     13649259 :     bool        untoasted_free[INDEX_MAX_KEYS] = {0};
      84              : #endif
      85              : 
      86     13649259 :     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     35218575 :     for (i = 0; i < numberOfAttributes; i++)
      94              :     {
      95     21569316 :         Form_pg_attribute att = TupleDescAttr(tupleDescriptor, i);
      96              : 
      97     21569316 :         untoasted_values[i] = values[i];
      98     21569316 :         untoasted_free[i] = false;
      99              : 
     100              :         /* Do nothing if value is NULL or not of varlena type */
     101     21569316 :         if (isnull[i] || att->attlen != -1)
     102     20842472 :             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       726844 :         if (VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
     109              :         {
     110          181 :             untoasted_values[i] =
     111          181 :                 PointerGetDatum(detoast_external_attr((varlena *)
     112          181 :                                                       DatumGetPointer(values[i])));
     113          181 :             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      1153681 :         if (!VARATT_IS_EXTENDED(DatumGetPointer(untoasted_values[i])) &&
     121       426837 :             VARSIZE(DatumGetPointer(untoasted_values[i])) > TOAST_INDEX_TARGET &&
     122        64492 :             (att->attstorage == TYPSTORAGE_EXTENDED ||
     123        57738 :              att->attstorage == TYPSTORAGE_MAIN))
     124              :         {
     125              :             Datum       cvalue;
     126              : 
     127         6754 :             cvalue = toast_compress_datum(untoasted_values[i],
     128         6754 :                                           att->attcompression);
     129              : 
     130         6754 :             if (DatumGetPointer(cvalue) != NULL)
     131              :             {
     132              :                 /* successful compression */
     133         1867 :                 if (untoasted_free[i])
     134            0 :                     pfree(DatumGetPointer(untoasted_values[i]));
     135         1867 :                 untoasted_values[i] = cvalue;
     136         1867 :                 untoasted_free[i] = true;
     137              :             }
     138              :         }
     139              :     }
     140              : #endif
     141              : 
     142     35163995 :     for (i = 0; i < numberOfAttributes; i++)
     143              :     {
     144     21568145 :         if (isnull[i])
     145              :         {
     146        53409 :             hasnull = true;
     147        53409 :             break;
     148              :         }
     149              :     }
     150              : 
     151     13649259 :     if (hasnull)
     152        53409 :         infomask |= INDEX_NULL_MASK;
     153              : 
     154     13649259 :     hoff = IndexInfoFindDataOffset(infomask);
     155              : #ifdef TOAST_INDEX_HACK
     156     13649259 :     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     13649259 :     size = hoff + data_size;
     163     13649259 :     size = MAXALIGN(size);      /* be conservative */
     164              : 
     165     13649259 :     tp = (char *) MemoryContextAllocZero(context, size);
     166     13649259 :     tuple = (IndexTuple) tp;
     167              : 
     168     13649259 :     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     35218575 :     for (i = 0; i < numberOfAttributes; i++)
     182              :     {
     183     21569316 :         if (untoasted_free[i])
     184         2048 :             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     13649259 :     if (tupmask & HEAP_HASVARWIDTH)
     195      1762969 :         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     13649259 :     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     13649259 :     infomask |= size;
     213              : 
     214              :     /*
     215              :      * initialize metadata
     216              :      */
     217     13649259 :     tuple->t_info = infomask;
     218     13649259 :     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     25627340 : 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     25627340 :     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     25627340 :     bool        hasnulls = IndexTupleHasNulls(tup);
     241              :     int         i;
     242              : 
     243              :     /* Did someone forget to call TupleDescFinalize()? */
     244              :     Assert(tupleDesc->firstNonCachedOffsetAttr >= 0);
     245              : 
     246     25627340 :     attnum--;
     247              : 
     248     25627340 :     data_off = IndexInfoFindDataOffset(tup->t_info);
     249     25627340 :     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     25627340 :     if (hasnulls)
     257              :     {
     258        57298 :         bp = (bits8 *) ((char *) tup + sizeof(IndexTupleData));
     259        57298 :         firstNullAttr = first_null_attr(bp, attnum);
     260              :     }
     261              :     else
     262     25570042 :         firstNullAttr = attnum;
     263              : 
     264     25627340 :     if (tupleDesc->firstNonCachedOffsetAttr > 0)
     265              :     {
     266              :         /*
     267              :          * Start at the highest attcacheoff attribute with no NULLs in prior
     268              :          * attributes.
     269              :          */
     270      4871028 :         startAttr = Min(tupleDesc->firstNonCachedOffsetAttr - 1, firstNullAttr);
     271      4871028 :         off = TupleDescCompactAttr(tupleDesc, startAttr)->attcacheoff;
     272              :     }
     273              :     else
     274              :     {
     275              :         /* Otherwise, start at the beginning... */
     276     20756312 :         startAttr = 0;
     277     20756312 :         off = 0;
     278              :     }
     279              : 
     280              :     /*
     281              :      * Calculate 'off' up to the first NULL attr.  We use two cheaper loops
     282              :      * when the tuple has no variable-width columns.  When variable-width
     283              :      * columns exists, we use att_addlength_pointer() to move the offset
     284              :      * beyond the current attribute.
     285              :      */
     286     25627340 :     if (IndexTupleHasVarwidths(tup))
     287              :     {
     288              :         /* Calculate the offset up until the first NULL */
     289     33983496 :         for (i = startAttr; i < firstNullAttr; i++)
     290              :         {
     291      8399236 :             cattr = TupleDescCompactAttr(tupleDesc, i);
     292              : 
     293      8399236 :             off = att_pointer_alignby(off,
     294              :                                       cattr->attalignby,
     295              :                                       cattr->attlen,
     296              :                                       tp + off);
     297      8399236 :             off = att_addlength_pointer(off, cattr->attlen, tp + off);
     298              :         }
     299              : 
     300              :         /* Calculate the offset for any remaining columns. */
     301     25584266 :         for (; i < attnum; i++)
     302              :         {
     303              :             Assert(hasnulls);
     304              : 
     305            6 :             if (att_isnull(i, bp))
     306            6 :                 continue;
     307              : 
     308            0 :             cattr = TupleDescCompactAttr(tupleDesc, i);
     309              : 
     310            0 :             off = att_pointer_alignby(off,
     311              :                                       cattr->attalignby,
     312              :                                       cattr->attlen,
     313              :                                       tp + off);
     314            0 :             off = att_addlength_pointer(off, cattr->attlen, tp + off);
     315              :         }
     316              :     }
     317              :     else
     318              :     {
     319              :         /* Handle tuples with only fixed-width attributes */
     320              : 
     321              :         /* Calculate the offset up until the first NULL */
     322        43080 :         for (i = startAttr; i < firstNullAttr; i++)
     323              :         {
     324            0 :             cattr = TupleDescCompactAttr(tupleDesc, i);
     325              : 
     326              :             Assert(cattr->attlen > 0);
     327            0 :             off = att_nominal_alignby(off, cattr->attalignby);
     328            0 :             off += cattr->attlen;
     329              :         }
     330              : 
     331              :         /* Calculate the offset for any remaining columns. */
     332        43419 :         for (; i < attnum; i++)
     333              :         {
     334              :             Assert(hasnulls);
     335              : 
     336          339 :             if (att_isnull(i, bp))
     337          339 :                 continue;
     338              : 
     339            0 :             cattr = TupleDescCompactAttr(tupleDesc, i);
     340              : 
     341              :             Assert(cattr->attlen > 0);
     342            0 :             off = att_nominal_alignby(off, cattr->attalignby);
     343            0 :             off += cattr->attlen;
     344              :         }
     345              :     }
     346              : 
     347     25627340 :     cattr = TupleDescCompactAttr(tupleDesc, attnum);
     348     25627340 :     off = att_pointer_alignby(off, cattr->attalignby,
     349              :                               cattr->attlen, tp + off);
     350     25627340 :     return fetchatt(cattr, tp + off);
     351              : }
     352              : 
     353              : /*
     354              :  * Convert an index tuple into Datum/isnull arrays.
     355              :  *
     356              :  * The caller must allocate sufficient storage for the output arrays.
     357              :  * (INDEX_MAX_KEYS entries should be enough.)
     358              :  *
     359              :  * This is nearly the same as heap_deform_tuple(), but for IndexTuples.
     360              :  * One difference is that the tuple should never have any missing columns.
     361              :  */
     362              : void
     363      2027940 : index_deform_tuple(IndexTuple tup, TupleDesc tupleDescriptor,
     364              :                    Datum *values, bool *isnull)
     365              : {
     366              :     char       *tp;             /* ptr to tuple data */
     367              :     bits8      *bp;             /* ptr to null bitmap in tuple */
     368              : 
     369              :     /* XXX "knows" t_bits are just after fixed tuple header! */
     370      2027940 :     bp = (bits8 *) ((char *) tup + sizeof(IndexTupleData));
     371              : 
     372      2027940 :     tp = (char *) tup + IndexInfoFindDataOffset(tup->t_info);
     373              : 
     374      2027940 :     index_deform_tuple_internal(tupleDescriptor, values, isnull,
     375      2027940 :                                 tp, bp, IndexTupleHasNulls(tup));
     376      2027940 : }
     377              : 
     378              : /*
     379              :  * Convert an index tuple into Datum/isnull arrays,
     380              :  * without assuming any specific layout of the index tuple header.
     381              :  *
     382              :  * Caller must supply pointer to data area, pointer to nulls bitmap
     383              :  * (which can be NULL if !hasnulls), and hasnulls flag.
     384              :  */
     385              : void
     386      2058835 : index_deform_tuple_internal(TupleDesc tupleDescriptor,
     387              :                             Datum *values, bool *isnull,
     388              :                             char *tp, bits8 *bp, int hasnulls)
     389              : {
     390              :     CompactAttribute *cattr;
     391      2058835 :     int         natts = tupleDescriptor->natts; /* number of atts to extract */
     392      2058835 :     int         attnum = 0;
     393      2058835 :     uint32      off = 0;        /* offset in tuple data */
     394              :     int         firstNonCacheOffsetAttr;
     395              :     int         firstNullAttr;
     396              : 
     397              :     /* Assert to protect callers who allocate fixed-size arrays */
     398              :     Assert(natts <= INDEX_MAX_KEYS);
     399              : 
     400              :     /* Did someone forget to call TupleDescFinalize()? */
     401              :     Assert(tupleDescriptor->firstNonCachedOffsetAttr >= 0);
     402              : 
     403      2058835 :     firstNonCacheOffsetAttr = Min(tupleDescriptor->firstNonCachedOffsetAttr, natts);
     404              : 
     405      2058835 :     if (hasnulls)
     406              :     {
     407         2657 :         firstNullAttr = first_null_attr(bp, natts);
     408         2657 :         firstNonCacheOffsetAttr = Min(firstNonCacheOffsetAttr, firstNullAttr);
     409              :     }
     410              :     else
     411      2056178 :         firstNullAttr = natts;
     412              : 
     413      2058835 :     if (firstNonCacheOffsetAttr > 0)
     414              :     {
     415              : #ifdef USE_ASSERT_CHECKING
     416              :         /* In Assert enabled builds, verify attcacheoff is correct */
     417              :         off = 0;
     418              : #endif
     419              : 
     420              :         do
     421              :         {
     422      2821409 :             isnull[attnum] = false;
     423      2821409 :             cattr = TupleDescCompactAttr(tupleDescriptor, attnum);
     424              : 
     425              : #ifdef USE_ASSERT_CHECKING
     426              :             off = att_nominal_alignby(off, cattr->attalignby);
     427              :             Assert(off == cattr->attcacheoff);
     428              :             off += cattr->attlen;
     429              : #endif
     430              : 
     431      5642818 :             values[attnum] = fetch_att_noerr(tp + cattr->attcacheoff, cattr->attbyval,
     432      2821409 :                                              cattr->attlen);
     433      2821409 :         } while (++attnum < firstNonCacheOffsetAttr);
     434              : 
     435      2028615 :         off = cattr->attcacheoff + cattr->attlen;
     436              :     }
     437              : 
     438      2098474 :     for (; attnum < firstNullAttr; attnum++)
     439              :     {
     440        39639 :         isnull[attnum] = false;
     441        39639 :         cattr = TupleDescCompactAttr(tupleDescriptor, attnum);
     442              : 
     443              :         /* align 'off', fetch the datum, and increment off beyond the datum */
     444        39639 :         values[attnum] = align_fetch_then_add(tp,
     445              :                                               &off,
     446        39639 :                                               cattr->attbyval,
     447        39639 :                                               cattr->attlen,
     448        39639 :                                               cattr->attalignby);
     449              :     }
     450              : 
     451      2063957 :     for (; attnum < natts; attnum++)
     452              :     {
     453              :         Assert(hasnulls);
     454              : 
     455         5122 :         if (att_isnull(attnum, bp))
     456              :         {
     457         2669 :             values[attnum] = (Datum) 0;
     458         2669 :             isnull[attnum] = true;
     459         2669 :             continue;
     460              :         }
     461              : 
     462         2453 :         isnull[attnum] = false;
     463         2453 :         cattr = TupleDescCompactAttr(tupleDescriptor, attnum);
     464              : 
     465              :         /* align 'off', fetch the attr's value, and increment off beyond it */
     466         2453 :         values[attnum] = align_fetch_then_add(tp,
     467              :                                               &off,
     468         2453 :                                               cattr->attbyval,
     469         2453 :                                               cattr->attlen,
     470         2453 :                                               cattr->attalignby);
     471              :     }
     472      2058835 : }
     473              : 
     474              : /*
     475              :  * Create a palloc'd copy of an index tuple.
     476              :  */
     477              : IndexTuple
     478      2538783 : CopyIndexTuple(IndexTuple source)
     479              : {
     480              :     IndexTuple  result;
     481              :     Size        size;
     482              : 
     483      2538783 :     size = IndexTupleSize(source);
     484      2538783 :     result = (IndexTuple) palloc(size);
     485      2538783 :     memcpy(result, source, size);
     486      2538783 :     return result;
     487              : }
     488              : 
     489              : /*
     490              :  * Create a palloc'd copy of an index tuple, leaving only the first
     491              :  * leavenatts attributes remaining.
     492              :  *
     493              :  * Truncation is guaranteed to result in an index tuple that is no
     494              :  * larger than the original.  It is safe to use the IndexTuple with
     495              :  * the original tuple descriptor, but caller must avoid actually
     496              :  * accessing truncated attributes from returned tuple!  In practice
     497              :  * this means that index_getattr() must be called with special care,
     498              :  * and that the truncated tuple should only ever be accessed by code
     499              :  * under caller's direct control.
     500              :  *
     501              :  * It's safe to call this function with a buffer lock held, since it
     502              :  * never performs external table access.  If it ever became possible
     503              :  * for index tuples to contain EXTERNAL TOAST values, then this would
     504              :  * have to be revisited.
     505              :  */
     506              : IndexTuple
     507        32477 : index_truncate_tuple(TupleDesc sourceDescriptor, IndexTuple source,
     508              :                      int leavenatts)
     509              : {
     510              :     TupleDesc   truncdesc;
     511              :     Datum       values[INDEX_MAX_KEYS];
     512              :     bool        isnull[INDEX_MAX_KEYS];
     513              :     IndexTuple  truncated;
     514              : 
     515              :     Assert(leavenatts <= sourceDescriptor->natts);
     516              : 
     517              :     /* Easy case: no truncation actually required */
     518        32477 :     if (leavenatts == sourceDescriptor->natts)
     519        18943 :         return CopyIndexTuple(source);
     520              : 
     521              :     /* Create temporary truncated tuple descriptor */
     522        13534 :     truncdesc = CreateTupleDescTruncatedCopy(sourceDescriptor, leavenatts);
     523              : 
     524              :     /* Deform, form copy of tuple with fewer attributes */
     525        13534 :     index_deform_tuple(source, truncdesc, values, isnull);
     526        13534 :     truncated = index_form_tuple(truncdesc, values, isnull);
     527        13534 :     truncated->t_tid = source->t_tid;
     528              :     Assert(IndexTupleSize(truncated) <= IndexTupleSize(source));
     529              : 
     530              :     /*
     531              :      * Cannot leak memory here, TupleDescCopy() doesn't allocate any inner
     532              :      * structure, so, plain pfree() should clean all allocated memory
     533              :      */
     534        13534 :     pfree(truncdesc);
     535              : 
     536        13534 :     return truncated;
     537              : }
        

Generated by: LCOV version 2.0-1