LCOV - code coverage report
Current view: top level - src/backend/access/brin - brin_tuple.c (source / functions) Hit Total Coverage
Test: PostgreSQL 13beta1 Lines: 176 183 96.2 %
Date: 2020-06-03 11:07:14 Functions: 10 10 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  * brin_tuple.c
       3             :  *      Method implementations for tuples in BRIN indexes.
       4             :  *
       5             :  * Intended usage is that code outside this file only deals with
       6             :  * BrinMemTuples, and convert to and from the on-disk representation through
       7             :  * functions in this file.
       8             :  *
       9             :  * NOTES
      10             :  *
      11             :  * A BRIN tuple is similar to a heap tuple, with a few key differences.  The
      12             :  * first interesting difference is that the tuple header is much simpler, only
      13             :  * containing its total length and a small area for flags.  Also, the stored
      14             :  * data does not match the relation tuple descriptor exactly: for each
      15             :  * attribute in the descriptor, the index tuple carries an arbitrary number
      16             :  * of values, depending on the opclass.
      17             :  *
      18             :  * Also, for each column of the index relation there are two null bits: one
      19             :  * (hasnulls) stores whether any tuple within the page range has that column
      20             :  * set to null; the other one (allnulls) stores whether the column values are
      21             :  * all null.  If allnulls is true, then the tuple data area does not contain
      22             :  * values for that column at all; whereas it does if the hasnulls is set.
      23             :  * Note the size of the null bitmask may not be the same as that of the
      24             :  * datum array.
      25             :  *
      26             :  * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
      27             :  * Portions Copyright (c) 1994, Regents of the University of California
      28             :  *
      29             :  * IDENTIFICATION
      30             :  *    src/backend/access/brin/brin_tuple.c
      31             :  */
      32             : #include "postgres.h"
      33             : 
      34             : #include "access/brin_tuple.h"
      35             : #include "access/htup_details.h"
      36             : #include "access/tupdesc.h"
      37             : #include "access/tupmacs.h"
      38             : #include "utils/datum.h"
      39             : #include "utils/memutils.h"
      40             : 
      41             : static inline void brin_deconstruct_tuple(BrinDesc *brdesc,
      42             :                                           char *tp, bits8 *nullbits, bool nulls,
      43             :                                           Datum *values, bool *allnulls, bool *hasnulls);
      44             : 
      45             : 
      46             : /*
      47             :  * Return a tuple descriptor used for on-disk storage of BRIN tuples.
      48             :  */
      49             : static TupleDesc
      50      111332 : brtuple_disk_tupdesc(BrinDesc *brdesc)
      51             : {
      52             :     /* We cache these in the BrinDesc */
      53      111332 :     if (brdesc->bd_disktdesc == NULL)
      54             :     {
      55             :         int         i;
      56             :         int         j;
      57        1370 :         AttrNumber  attno = 1;
      58             :         TupleDesc   tupdesc;
      59             :         MemoryContext oldcxt;
      60             : 
      61             :         /* make sure it's in the bdesc's context */
      62        1370 :         oldcxt = MemoryContextSwitchTo(brdesc->bd_context);
      63             : 
      64        1370 :         tupdesc = CreateTemplateTupleDesc(brdesc->bd_totalstored);
      65             : 
      66       32146 :         for (i = 0; i < brdesc->bd_tupdesc->natts; i++)
      67             :         {
      68       96400 :             for (j = 0; j < brdesc->bd_info[i]->oi_nstored; j++)
      69       65624 :                 TupleDescInitEntry(tupdesc, attno++, NULL,
      70       65624 :                                    brdesc->bd_info[i]->oi_typcache[j]->type_id,
      71             :                                    -1, 0);
      72             :         }
      73             : 
      74        1370 :         MemoryContextSwitchTo(oldcxt);
      75             : 
      76        1370 :         brdesc->bd_disktdesc = tupdesc;
      77             :     }
      78             : 
      79      111332 :     return brdesc->bd_disktdesc;
      80             : }
      81             : 
      82             : /*
      83             :  * Generate a new on-disk tuple to be inserted in a BRIN index.
      84             :  *
      85             :  * See brin_form_placeholder_tuple if you touch this.
      86             :  */
      87             : BrinTuple *
      88        1566 : brin_form_tuple(BrinDesc *brdesc, BlockNumber blkno, BrinMemTuple *tuple,
      89             :                 Size *size)
      90             : {
      91             :     Datum      *values;
      92             :     bool       *nulls;
      93        1566 :     bool        anynulls = false;
      94             :     BrinTuple  *rettuple;
      95             :     int         keyno;
      96             :     int         idxattno;
      97        1566 :     uint16      phony_infomask = 0;
      98             :     bits8      *phony_nullbitmap;
      99             :     Size        len,
     100             :                 hoff,
     101             :                 data_len;
     102             : 
     103             :     Assert(brdesc->bd_totalstored > 0);
     104             : 
     105        1566 :     values = (Datum *) palloc(sizeof(Datum) * brdesc->bd_totalstored);
     106        1566 :     nulls = (bool *) palloc0(sizeof(bool) * brdesc->bd_totalstored);
     107             :     phony_nullbitmap = (bits8 *)
     108        1566 :         palloc(sizeof(bits8) * BITMAPLEN(brdesc->bd_totalstored));
     109             : 
     110             :     /*
     111             :      * Set up the values/nulls arrays for heap_fill_tuple
     112             :      */
     113        1566 :     idxattno = 0;
     114       39962 :     for (keyno = 0; keyno < brdesc->bd_tupdesc->natts; keyno++)
     115             :     {
     116             :         int         datumno;
     117             : 
     118             :         /*
     119             :          * "allnulls" is set when there's no nonnull value in any row in the
     120             :          * column; when this happens, there is no data to store.  Thus set the
     121             :          * nullable bits for all data elements of this column and we're done.
     122             :          */
     123       38396 :         if (tuple->bt_columns[keyno].bv_allnulls)
     124             :         {
     125          80 :             for (datumno = 0;
     126         252 :                  datumno < brdesc->bd_info[keyno]->oi_nstored;
     127         172 :                  datumno++)
     128         172 :                 nulls[idxattno++] = true;
     129          80 :             anynulls = true;
     130          80 :             continue;
     131             :         }
     132             : 
     133             :         /*
     134             :          * The "hasnulls" bit is set when there are some null values in the
     135             :          * data.  We still need to store a real value, but the presence of
     136             :          * this means we need a null bitmap.
     137             :          */
     138       38316 :         if (tuple->bt_columns[keyno].bv_hasnulls)
     139        2300 :             anynulls = true;
     140             : 
     141       38316 :         for (datumno = 0;
     142      120032 :              datumno < brdesc->bd_info[keyno]->oi_nstored;
     143       81716 :              datumno++)
     144       81716 :             values[idxattno++] = tuple->bt_columns[keyno].bv_values[datumno];
     145             :     }
     146             : 
     147             :     /* Assert we did not overrun temp arrays */
     148             :     Assert(idxattno <= brdesc->bd_totalstored);
     149             : 
     150             :     /* compute total space needed */
     151        1566 :     len = SizeOfBrinTuple;
     152        1566 :     if (anynulls)
     153             :     {
     154             :         /*
     155             :          * We need a double-length bitmap on an on-disk BRIN index tuple; the
     156             :          * first half stores the "allnulls" bits, the second stores
     157             :          * "hasnulls".
     158             :          */
     159         114 :         len += BITMAPLEN(brdesc->bd_tupdesc->natts * 2);
     160             :     }
     161             : 
     162        1566 :     len = hoff = MAXALIGN(len);
     163             : 
     164        1566 :     data_len = heap_compute_data_size(brtuple_disk_tupdesc(brdesc),
     165             :                                       values, nulls);
     166        1566 :     len += data_len;
     167             : 
     168        1566 :     len = MAXALIGN(len);
     169             : 
     170        1566 :     rettuple = palloc0(len);
     171        1566 :     rettuple->bt_blkno = blkno;
     172        1566 :     rettuple->bt_info = hoff;
     173             : 
     174             :     /* Assert that hoff fits in the space available */
     175             :     Assert((rettuple->bt_info & BRIN_OFFSET_MASK) == hoff);
     176             : 
     177             :     /*
     178             :      * The infomask and null bitmap as computed by heap_fill_tuple are useless
     179             :      * to us.  However, that function will not accept a null infomask; and we
     180             :      * need to pass a valid null bitmap so that it will correctly skip
     181             :      * outputting null attributes in the data area.
     182             :      */
     183        1566 :     heap_fill_tuple(brtuple_disk_tupdesc(brdesc),
     184             :                     values,
     185             :                     nulls,
     186             :                     (char *) rettuple + hoff,
     187             :                     data_len,
     188             :                     &phony_infomask,
     189             :                     phony_nullbitmap);
     190             : 
     191             :     /* done with these */
     192        1566 :     pfree(values);
     193        1566 :     pfree(nulls);
     194        1566 :     pfree(phony_nullbitmap);
     195             : 
     196             :     /*
     197             :      * Now fill in the real null bitmasks.  allnulls first.
     198             :      */
     199        1566 :     if (anynulls)
     200             :     {
     201             :         bits8      *bitP;
     202             :         int         bitmask;
     203             : 
     204         114 :         rettuple->bt_info |= BRIN_NULLS_MASK;
     205             : 
     206             :         /*
     207             :          * Note that we reverse the sense of null bits in this module: we
     208             :          * store a 1 for a null attribute rather than a 0.  So we must reverse
     209             :          * the sense of the att_isnull test in brin_deconstruct_tuple as well.
     210             :          */
     211         114 :         bitP = ((bits8 *) ((char *) rettuple + SizeOfBrinTuple)) - 1;
     212         114 :         bitmask = HIGHBIT;
     213        2954 :         for (keyno = 0; keyno < brdesc->bd_tupdesc->natts; keyno++)
     214             :         {
     215        2840 :             if (bitmask != HIGHBIT)
     216        2444 :                 bitmask <<= 1;
     217             :             else
     218             :             {
     219         396 :                 bitP += 1;
     220         396 :                 *bitP = 0x0;
     221         396 :                 bitmask = 1;
     222             :             }
     223             : 
     224        2840 :             if (!tuple->bt_columns[keyno].bv_allnulls)
     225        2760 :                 continue;
     226             : 
     227          80 :             *bitP |= bitmask;
     228             :         }
     229             :         /* hasnulls bits follow */
     230        2954 :         for (keyno = 0; keyno < brdesc->bd_tupdesc->natts; keyno++)
     231             :         {
     232        2840 :             if (bitmask != HIGHBIT)
     233        2464 :                 bitmask <<= 1;
     234             :             else
     235             :             {
     236         376 :                 bitP += 1;
     237         376 :                 *bitP = 0x0;
     238         376 :                 bitmask = 1;
     239             :             }
     240             : 
     241        2840 :             if (!tuple->bt_columns[keyno].bv_hasnulls)
     242         540 :                 continue;
     243             : 
     244        2300 :             *bitP |= bitmask;
     245             :         }
     246         114 :         bitP = ((bits8 *) (rettuple + SizeOfBrinTuple)) - 1;
     247             :     }
     248             : 
     249        1566 :     if (tuple->bt_placeholder)
     250           0 :         rettuple->bt_info |= BRIN_PLACEHOLDER_MASK;
     251             : 
     252        1566 :     *size = len;
     253        1566 :     return rettuple;
     254             : }
     255             : 
     256             : /*
     257             :  * Generate a new on-disk tuple with no data values, marked as placeholder.
     258             :  *
     259             :  * This is a cut-down version of brin_form_tuple.
     260             :  */
     261             : BrinTuple *
     262          42 : brin_form_placeholder_tuple(BrinDesc *brdesc, BlockNumber blkno, Size *size)
     263             : {
     264             :     Size        len;
     265             :     Size        hoff;
     266             :     BrinTuple  *rettuple;
     267             :     int         keyno;
     268             :     bits8      *bitP;
     269             :     int         bitmask;
     270             : 
     271             :     /* compute total space needed: always add nulls */
     272          42 :     len = SizeOfBrinTuple;
     273          42 :     len += BITMAPLEN(brdesc->bd_tupdesc->natts * 2);
     274          42 :     len = hoff = MAXALIGN(len);
     275             : 
     276          42 :     rettuple = palloc0(len);
     277          42 :     rettuple->bt_blkno = blkno;
     278          42 :     rettuple->bt_info = hoff;
     279          42 :     rettuple->bt_info |= BRIN_NULLS_MASK | BRIN_PLACEHOLDER_MASK;
     280             : 
     281          42 :     bitP = ((bits8 *) ((char *) rettuple + SizeOfBrinTuple)) - 1;
     282          42 :     bitmask = HIGHBIT;
     283             :     /* set allnulls true for all attributes */
     284         896 :     for (keyno = 0; keyno < brdesc->bd_tupdesc->natts; keyno++)
     285             :     {
     286         854 :         if (bitmask != HIGHBIT)
     287         728 :             bitmask <<= 1;
     288             :         else
     289             :         {
     290         126 :             bitP += 1;
     291         126 :             *bitP = 0x0;
     292         126 :             bitmask = 1;
     293             :         }
     294             : 
     295         854 :         *bitP |= bitmask;
     296             :     }
     297             :     /* no need to set hasnulls */
     298             : 
     299          42 :     *size = len;
     300          42 :     return rettuple;
     301             : }
     302             : 
     303             : /*
     304             :  * Free a tuple created by brin_form_tuple
     305             :  */
     306             : void
     307          84 : brin_free_tuple(BrinTuple *tuple)
     308             : {
     309          84 :     pfree(tuple);
     310          84 : }
     311             : 
     312             : /*
     313             :  * Given a brin tuple of size len, create a copy of it.  If 'dest' is not
     314             :  * NULL, its size is destsz, and can be used as output buffer; if the tuple
     315             :  * to be copied does not fit, it is enlarged by repalloc, and the size is
     316             :  * updated to match.  This avoids palloc/free cycles when many brin tuples
     317             :  * are being processed in loops.
     318             :  */
     319             : BrinTuple *
     320      100104 : brin_copy_tuple(BrinTuple *tuple, Size len, BrinTuple *dest, Size *destsz)
     321             : {
     322      100104 :     if (!destsz || *destsz == 0)
     323      100104 :         dest = palloc(len);
     324           0 :     else if (len > *destsz)
     325             :     {
     326           0 :         dest = repalloc(dest, len);
     327           0 :         *destsz = len;
     328             :     }
     329             : 
     330      100104 :     memcpy(dest, tuple, len);
     331             : 
     332      100104 :     return dest;
     333             : }
     334             : 
     335             : /*
     336             :  * Return whether two BrinTuples are bitwise identical.
     337             :  */
     338             : bool
     339         946 : brin_tuples_equal(const BrinTuple *a, Size alen, const BrinTuple *b, Size blen)
     340             : {
     341         946 :     if (alen != blen)
     342           0 :         return false;
     343         946 :     if (memcmp(a, b, alen) != 0)
     344           0 :         return false;
     345         946 :     return true;
     346             : }
     347             : 
     348             : /*
     349             :  * Create a new BrinMemTuple from scratch, and initialize it to an empty
     350             :  * state.
     351             :  *
     352             :  * Note: we don't provide any means to free a deformed tuple, so make sure to
     353             :  * use a temporary memory context.
     354             :  */
     355             : BrinMemTuple *
     356       10056 : brin_new_memtuple(BrinDesc *brdesc)
     357             : {
     358             :     BrinMemTuple *dtup;
     359             :     long        basesize;
     360             : 
     361       10056 :     basesize = MAXALIGN(sizeof(BrinMemTuple) +
     362             :                         sizeof(BrinValues) * brdesc->bd_tupdesc->natts);
     363       10056 :     dtup = palloc0(basesize + sizeof(Datum) * brdesc->bd_totalstored);
     364             : 
     365       10056 :     dtup->bt_values = palloc(sizeof(Datum) * brdesc->bd_totalstored);
     366       10056 :     dtup->bt_allnulls = palloc(sizeof(bool) * brdesc->bd_tupdesc->natts);
     367       10056 :     dtup->bt_hasnulls = palloc(sizeof(bool) * brdesc->bd_tupdesc->natts);
     368             : 
     369       10056 :     dtup->bt_context = AllocSetContextCreate(CurrentMemoryContext,
     370             :                                              "brin dtuple",
     371             :                                              ALLOCSET_DEFAULT_SIZES);
     372             : 
     373       10056 :     brin_memtuple_initialize(dtup, brdesc);
     374             : 
     375       10056 :     return dtup;
     376             : }
     377             : 
     378             : /*
     379             :  * Reset a BrinMemTuple to initial state.  We return the same tuple, for
     380             :  * notational convenience.
     381             :  */
     382             : BrinMemTuple *
     383      109940 : brin_memtuple_initialize(BrinMemTuple *dtuple, BrinDesc *brdesc)
     384             : {
     385             :     int         i;
     386             :     char       *currdatum;
     387             : 
     388      109940 :     MemoryContextReset(dtuple->bt_context);
     389             : 
     390      109940 :     currdatum = (char *) dtuple +
     391      109940 :         MAXALIGN(sizeof(BrinMemTuple) +
     392             :                  sizeof(BrinValues) * brdesc->bd_tupdesc->natts);
     393     3162916 :     for (i = 0; i < brdesc->bd_tupdesc->natts; i++)
     394             :     {
     395     3052976 :         dtuple->bt_columns[i].bv_allnulls = true;
     396     3052976 :         dtuple->bt_columns[i].bv_hasnulls = false;
     397             : 
     398     3052976 :         dtuple->bt_columns[i].bv_attno = i + 1;
     399     3052976 :         dtuple->bt_columns[i].bv_allnulls = true;
     400     3052976 :         dtuple->bt_columns[i].bv_hasnulls = false;
     401     3052976 :         dtuple->bt_columns[i].bv_values = (Datum *) currdatum;
     402     3052976 :         currdatum += sizeof(Datum) * brdesc->bd_info[i]->oi_nstored;
     403             :     }
     404             : 
     405      109940 :     return dtuple;
     406             : }
     407             : 
     408             : /*
     409             :  * Convert a BrinTuple back to a BrinMemTuple.  This is the reverse of
     410             :  * brin_form_tuple.
     411             :  *
     412             :  * As an optimization, the caller can pass a previously allocated 'dMemtuple'.
     413             :  * This avoids having to allocate it here, which can be useful when this
     414             :  * function is called many times in a loop.  It is caller's responsibility
     415             :  * that the given BrinMemTuple matches what we need here.
     416             :  *
     417             :  * Note we don't need the "on disk tupdesc" here; we rely on our own routine to
     418             :  * deconstruct the tuple from the on-disk format.
     419             :  */
     420             : BrinMemTuple *
     421      108200 : brin_deform_tuple(BrinDesc *brdesc, BrinTuple *tuple, BrinMemTuple *dMemtuple)
     422             : {
     423             :     BrinMemTuple *dtup;
     424             :     Datum      *values;
     425             :     bool       *allnulls;
     426             :     bool       *hasnulls;
     427             :     char       *tp;
     428             :     bits8      *nullbits;
     429             :     int         keyno;
     430             :     int         valueno;
     431             :     MemoryContext oldcxt;
     432             : 
     433      108200 :     dtup = dMemtuple ? brin_memtuple_initialize(dMemtuple, brdesc) :
     434        9000 :         brin_new_memtuple(brdesc);
     435             : 
     436      108200 :     if (BrinTupleIsPlaceholder(tuple))
     437           0 :         dtup->bt_placeholder = true;
     438      108200 :     dtup->bt_blkno = tuple->bt_blkno;
     439             : 
     440      108200 :     values = dtup->bt_values;
     441      108200 :     allnulls = dtup->bt_allnulls;
     442      108200 :     hasnulls = dtup->bt_hasnulls;
     443             : 
     444      108200 :     tp = (char *) tuple + BrinTupleDataOffset(tuple);
     445             : 
     446      108200 :     if (BrinTupleHasNulls(tuple))
     447        7016 :         nullbits = (bits8 *) ((char *) tuple + SizeOfBrinTuple);
     448             :     else
     449      101184 :         nullbits = NULL;
     450      108200 :     brin_deconstruct_tuple(brdesc,
     451      108200 :                            tp, nullbits, BrinTupleHasNulls(tuple),
     452             :                            values, allnulls, hasnulls);
     453             : 
     454             :     /*
     455             :      * Iterate to assign each of the values to the corresponding item in the
     456             :      * values array of each column.  The copies occur in the tuple's context.
     457             :      */
     458      108200 :     oldcxt = MemoryContextSwitchTo(dtup->bt_context);
     459     3117560 :     for (valueno = 0, keyno = 0; keyno < brdesc->bd_tupdesc->natts; keyno++)
     460             :     {
     461             :         int         i;
     462             : 
     463     3009360 :         if (allnulls[keyno])
     464             :         {
     465          16 :             valueno += brdesc->bd_info[keyno]->oi_nstored;
     466          16 :             continue;
     467             :         }
     468             : 
     469             :         /*
     470             :          * We would like to skip datumCopy'ing the values datum in some cases,
     471             :          * caller permitting ...
     472             :          */
     473     9428200 :         for (i = 0; i < brdesc->bd_info[keyno]->oi_nstored; i++)
     474     6418856 :             dtup->bt_columns[keyno].bv_values[i] =
     475    19256568 :                 datumCopy(values[valueno++],
     476     6418856 :                           brdesc->bd_info[keyno]->oi_typcache[i]->typbyval,
     477     6418856 :                           brdesc->bd_info[keyno]->oi_typcache[i]->typlen);
     478             : 
     479     3009344 :         dtup->bt_columns[keyno].bv_hasnulls = hasnulls[keyno];
     480     3009344 :         dtup->bt_columns[keyno].bv_allnulls = false;
     481             :     }
     482             : 
     483      108200 :     MemoryContextSwitchTo(oldcxt);
     484             : 
     485      108200 :     return dtup;
     486             : }
     487             : 
     488             : /*
     489             :  * brin_deconstruct_tuple
     490             :  *      Guts of attribute extraction from an on-disk BRIN tuple.
     491             :  *
     492             :  * Its arguments are:
     493             :  *  brdesc      BRIN descriptor for the stored tuple
     494             :  *  tp          pointer to the tuple data area
     495             :  *  nullbits    pointer to the tuple nulls bitmask
     496             :  *  nulls       "has nulls" bit in tuple infomask
     497             :  *  values      output values, array of size brdesc->bd_totalstored
     498             :  *  allnulls    output "allnulls", size brdesc->bd_tupdesc->natts
     499             :  *  hasnulls    output "hasnulls", size brdesc->bd_tupdesc->natts
     500             :  *
     501             :  * Output arrays must have been allocated by caller.
     502             :  */
     503             : static inline void
     504      108200 : brin_deconstruct_tuple(BrinDesc *brdesc,
     505             :                        char *tp, bits8 *nullbits, bool nulls,
     506             :                        Datum *values, bool *allnulls, bool *hasnulls)
     507             : {
     508             :     int         attnum;
     509             :     int         stored;
     510             :     TupleDesc   diskdsc;
     511             :     long        off;
     512             : 
     513             :     /*
     514             :      * First iterate to natts to obtain both null flags for each attribute.
     515             :      * Note that we reverse the sense of the att_isnull test, because we store
     516             :      * 1 for a null value (rather than a 1 for a not null value as is the
     517             :      * att_isnull convention used elsewhere.)  See brin_form_tuple.
     518             :      */
     519     3117560 :     for (attnum = 0; attnum < brdesc->bd_tupdesc->natts; attnum++)
     520             :     {
     521             :         /*
     522             :          * the "all nulls" bit means that all values in the page range for
     523             :          * this column are nulls.  Therefore there are no values in the tuple
     524             :          * data area.
     525             :          */
     526     3009360 :         allnulls[attnum] = nulls && !att_isnull(attnum, nullbits);
     527             : 
     528             :         /*
     529             :          * the "has nulls" bit means that some tuples have nulls, but others
     530             :          * have not-null values.  Therefore we know the tuple contains data
     531             :          * for this column.
     532             :          *
     533             :          * The hasnulls bits follow the allnulls bits in the same bitmask.
     534             :          */
     535     6018720 :         hasnulls[attnum] =
     536     3009360 :             nulls && !att_isnull(brdesc->bd_tupdesc->natts + attnum, nullbits);
     537             :     }
     538             : 
     539             :     /*
     540             :      * Iterate to obtain each attribute's stored values.  Note that since we
     541             :      * may reuse attribute entries for more than one column, we cannot cache
     542             :      * offsets here.
     543             :      */
     544      108200 :     diskdsc = brtuple_disk_tupdesc(brdesc);
     545      108200 :     stored = 0;
     546      108200 :     off = 0;
     547     3117560 :     for (attnum = 0; attnum < brdesc->bd_tupdesc->natts; attnum++)
     548             :     {
     549             :         int         datumno;
     550             : 
     551     3009360 :         if (allnulls[attnum])
     552             :         {
     553          16 :             stored += brdesc->bd_info[attnum]->oi_nstored;
     554          16 :             continue;
     555             :         }
     556             : 
     557     3009344 :         for (datumno = 0;
     558     9428200 :              datumno < brdesc->bd_info[attnum]->oi_nstored;
     559     6418856 :              datumno++)
     560             :         {
     561     6418856 :             Form_pg_attribute thisatt = TupleDescAttr(diskdsc, stored);
     562             : 
     563     6418856 :             if (thisatt->attlen == -1)
     564             :             {
     565     1900768 :                 off = att_align_pointer(off, thisatt->attalign, -1,
     566             :                                         tp + off);
     567             :             }
     568             :             else
     569             :             {
     570             :                 /* not varlena, so safe to use att_align_nominal */
     571     4518088 :                 off = att_align_nominal(off, thisatt->attalign);
     572             :             }
     573             : 
     574     6418856 :             values[stored++] = fetchatt(thisatt, tp + off);
     575             : 
     576     6418856 :             off = att_addlength_pointer(off, thisatt->attlen, tp + off);
     577             :         }
     578             :     }
     579      108200 : }

Generated by: LCOV version 1.13