LCOV - code coverage report
Current view: top level - src/backend/access/common - toast_internals.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 95.5 % 155 148
Test Date: 2026-02-28 20:14:43 Functions: 100.0 % 9 9
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * toast_internals.c
       4              :  *    Functions for internal use by the TOAST system.
       5              :  *
       6              :  * Copyright (c) 2000-2026, PostgreSQL Global Development Group
       7              :  *
       8              :  * IDENTIFICATION
       9              :  *    src/backend/access/common/toast_internals.c
      10              :  *
      11              :  *-------------------------------------------------------------------------
      12              :  */
      13              : 
      14              : #include "postgres.h"
      15              : 
      16              : #include "access/detoast.h"
      17              : #include "access/genam.h"
      18              : #include "access/heapam.h"
      19              : #include "access/heaptoast.h"
      20              : #include "access/table.h"
      21              : #include "access/toast_internals.h"
      22              : #include "access/xact.h"
      23              : #include "catalog/catalog.h"
      24              : #include "miscadmin.h"
      25              : #include "utils/fmgroids.h"
      26              : #include "utils/rel.h"
      27              : #include "utils/snapmgr.h"
      28              : 
      29              : static bool toastrel_valueid_exists(Relation toastrel, Oid valueid);
      30              : static bool toastid_valueid_exists(Oid toastrelid, Oid valueid);
      31              : 
      32              : /* ----------
      33              :  * toast_compress_datum -
      34              :  *
      35              :  *  Create a compressed version of a varlena datum
      36              :  *
      37              :  *  If we fail (ie, compressed result is actually bigger than original)
      38              :  *  then return NULL.  We must not use compressed data if it'd expand
      39              :  *  the tuple!
      40              :  *
      41              :  *  We use VAR{SIZE,DATA}_ANY so we can handle short varlenas here without
      42              :  *  copying them.  But we can't handle external or compressed datums.
      43              :  * ----------
      44              :  */
      45              : Datum
      46        26761 : toast_compress_datum(Datum value, char cmethod)
      47              : {
      48        26761 :     varlena    *tmp = NULL;
      49              :     int32       valsize;
      50        26761 :     ToastCompressionId cmid = TOAST_INVALID_COMPRESSION_ID;
      51              : 
      52              :     Assert(!VARATT_IS_EXTERNAL(DatumGetPointer(value)));
      53              :     Assert(!VARATT_IS_COMPRESSED(DatumGetPointer(value)));
      54              : 
      55        26761 :     valsize = VARSIZE_ANY_EXHDR(DatumGetPointer(value));
      56              : 
      57              :     /* If the compression method is not valid, use the current default */
      58        26761 :     if (!CompressionMethodIsValid(cmethod))
      59        26719 :         cmethod = default_toast_compression;
      60              : 
      61              :     /*
      62              :      * Call appropriate compression routine for the compression method.
      63              :      */
      64        26761 :     switch (cmethod)
      65              :     {
      66        26740 :         case TOAST_PGLZ_COMPRESSION:
      67        26740 :             tmp = pglz_compress_datum((const varlena *) DatumGetPointer(value));
      68        26740 :             cmid = TOAST_PGLZ_COMPRESSION_ID;
      69        26740 :             break;
      70           21 :         case TOAST_LZ4_COMPRESSION:
      71           21 :             tmp = lz4_compress_datum((const varlena *) DatumGetPointer(value));
      72           21 :             cmid = TOAST_LZ4_COMPRESSION_ID;
      73           21 :             break;
      74            0 :         default:
      75            0 :             elog(ERROR, "invalid compression method %c", cmethod);
      76              :     }
      77              : 
      78        26761 :     if (tmp == NULL)
      79         6573 :         return PointerGetDatum(NULL);
      80              : 
      81              :     /*
      82              :      * We recheck the actual size even if compression reports success, because
      83              :      * it might be satisfied with having saved as little as one byte in the
      84              :      * compressed data --- which could turn into a net loss once you consider
      85              :      * header and alignment padding.  Worst case, the compressed format might
      86              :      * require three padding bytes (plus header, which is included in
      87              :      * VARSIZE(tmp)), whereas the uncompressed format would take only one
      88              :      * header byte and no padding if the value is short enough.  So we insist
      89              :      * on a savings of more than 2 bytes to ensure we have a gain.
      90              :      */
      91        20188 :     if (VARSIZE(tmp) < valsize - 2)
      92              :     {
      93              :         /* successful compression */
      94              :         Assert(cmid != TOAST_INVALID_COMPRESSION_ID);
      95        20188 :         TOAST_COMPRESS_SET_SIZE_AND_COMPRESS_METHOD(tmp, valsize, cmid);
      96        20188 :         return PointerGetDatum(tmp);
      97              :     }
      98              :     else
      99              :     {
     100              :         /* incompressible data */
     101            0 :         pfree(tmp);
     102            0 :         return PointerGetDatum(NULL);
     103              :     }
     104              : }
     105              : 
     106              : /* ----------
     107              :  * toast_save_datum -
     108              :  *
     109              :  *  Save one single datum into the secondary relation and return
     110              :  *  a Datum reference for it.
     111              :  *
     112              :  * rel: the main relation we're working with (not the toast rel!)
     113              :  * value: datum to be pushed to toast storage
     114              :  * oldexternal: if not NULL, toast pointer previously representing the datum
     115              :  * options: options to be passed to heap_insert() for toast rows
     116              :  * ----------
     117              :  */
     118              : Datum
     119         8548 : toast_save_datum(Relation rel, Datum value,
     120              :                  varlena *oldexternal, int options)
     121              : {
     122              :     Relation    toastrel;
     123              :     Relation   *toastidxs;
     124              :     TupleDesc   toasttupDesc;
     125         8548 :     CommandId   mycid = GetCurrentCommandId(true);
     126              :     varlena    *result;
     127              :     varatt_external toast_pointer;
     128         8548 :     int32       chunk_seq = 0;
     129              :     char       *data_p;
     130              :     int32       data_todo;
     131         8548 :     Pointer     dval = DatumGetPointer(value);
     132              :     int         num_indexes;
     133              :     int         validIndex;
     134              : 
     135              :     Assert(!VARATT_IS_EXTERNAL(dval));
     136              : 
     137              :     /*
     138              :      * Open the toast relation and its indexes.  We can use the index to check
     139              :      * uniqueness of the OID we assign to the toasted item, even though it has
     140              :      * additional columns besides OID.
     141              :      */
     142         8548 :     toastrel = table_open(rel->rd_rel->reltoastrelid, RowExclusiveLock);
     143         8548 :     toasttupDesc = toastrel->rd_att;
     144              : 
     145              :     /* Open all the toast indexes and look for the valid one */
     146         8548 :     validIndex = toast_open_indexes(toastrel,
     147              :                                     RowExclusiveLock,
     148              :                                     &toastidxs,
     149              :                                     &num_indexes);
     150              : 
     151              :     /*
     152              :      * Get the data pointer and length, and compute va_rawsize and va_extinfo.
     153              :      *
     154              :      * va_rawsize is the size of the equivalent fully uncompressed datum, so
     155              :      * we have to adjust for short headers.
     156              :      *
     157              :      * va_extinfo stored the actual size of the data payload in the toast
     158              :      * records and the compression method in first 2 bits if data is
     159              :      * compressed.
     160              :      */
     161         8548 :     if (VARATT_IS_SHORT(dval))
     162              :     {
     163            3 :         data_p = VARDATA_SHORT(dval);
     164            3 :         data_todo = VARSIZE_SHORT(dval) - VARHDRSZ_SHORT;
     165            3 :         toast_pointer.va_rawsize = data_todo + VARHDRSZ;    /* as if not short */
     166            3 :         toast_pointer.va_extinfo = data_todo;
     167              :     }
     168         8545 :     else if (VARATT_IS_COMPRESSED(dval))
     169              :     {
     170         5322 :         data_p = VARDATA(dval);
     171         5322 :         data_todo = VARSIZE(dval) - VARHDRSZ;
     172              :         /* rawsize in a compressed datum is just the size of the payload */
     173         5322 :         toast_pointer.va_rawsize = VARDATA_COMPRESSED_GET_EXTSIZE(dval) + VARHDRSZ;
     174              : 
     175              :         /* set external size and compression method */
     176         5322 :         VARATT_EXTERNAL_SET_SIZE_AND_COMPRESS_METHOD(toast_pointer, data_todo,
     177              :                                                      VARDATA_COMPRESSED_GET_COMPRESS_METHOD(dval));
     178              :         /* Assert that the numbers look like it's compressed */
     179              :         Assert(VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer));
     180              :     }
     181              :     else
     182              :     {
     183         3223 :         data_p = VARDATA(dval);
     184         3223 :         data_todo = VARSIZE(dval) - VARHDRSZ;
     185         3223 :         toast_pointer.va_rawsize = VARSIZE(dval);
     186         3223 :         toast_pointer.va_extinfo = data_todo;
     187              :     }
     188              : 
     189              :     /*
     190              :      * Insert the correct table OID into the result TOAST pointer.
     191              :      *
     192              :      * Normally this is the actual OID of the target toast table, but during
     193              :      * table-rewriting operations such as CLUSTER, we have to insert the OID
     194              :      * of the table's real permanent toast table instead.  rd_toastoid is set
     195              :      * if we have to substitute such an OID.
     196              :      */
     197         8548 :     if (OidIsValid(rel->rd_toastoid))
     198          300 :         toast_pointer.va_toastrelid = rel->rd_toastoid;
     199              :     else
     200         8248 :         toast_pointer.va_toastrelid = RelationGetRelid(toastrel);
     201              : 
     202              :     /*
     203              :      * Choose an OID to use as the value ID for this toast value.
     204              :      *
     205              :      * Normally we just choose an unused OID within the toast table.  But
     206              :      * during table-rewriting operations where we are preserving an existing
     207              :      * toast table OID, we want to preserve toast value OIDs too.  So, if
     208              :      * rd_toastoid is set and we had a prior external value from that same
     209              :      * toast table, re-use its value ID.  If we didn't have a prior external
     210              :      * value (which is a corner case, but possible if the table's attstorage
     211              :      * options have been changed), we have to pick a value ID that doesn't
     212              :      * conflict with either new or existing toast value OIDs.
     213              :      */
     214         8548 :     if (!OidIsValid(rel->rd_toastoid))
     215              :     {
     216              :         /* normal case: just choose an unused OID */
     217         8248 :         toast_pointer.va_valueid =
     218         8248 :             GetNewOidWithIndex(toastrel,
     219         8248 :                                RelationGetRelid(toastidxs[validIndex]),
     220              :                                (AttrNumber) 1);
     221              :     }
     222              :     else
     223              :     {
     224              :         /* rewrite case: check to see if value was in old toast table */
     225          300 :         toast_pointer.va_valueid = InvalidOid;
     226          300 :         if (oldexternal != NULL)
     227              :         {
     228              :             varatt_external old_toast_pointer;
     229              : 
     230              :             Assert(VARATT_IS_EXTERNAL_ONDISK(oldexternal));
     231              :             /* Must copy to access aligned fields */
     232          297 :             VARATT_EXTERNAL_GET_POINTER(old_toast_pointer, oldexternal);
     233          297 :             if (old_toast_pointer.va_toastrelid == rel->rd_toastoid)
     234              :             {
     235              :                 /* This value came from the old toast table; reuse its OID */
     236          297 :                 toast_pointer.va_valueid = old_toast_pointer.va_valueid;
     237              : 
     238              :                 /*
     239              :                  * There is a corner case here: the table rewrite might have
     240              :                  * to copy both live and recently-dead versions of a row, and
     241              :                  * those versions could easily reference the same toast value.
     242              :                  * When we copy the second or later version of such a row,
     243              :                  * reusing the OID will mean we select an OID that's already
     244              :                  * in the new toast table.  Check for that, and if so, just
     245              :                  * fall through without writing the data again.
     246              :                  *
     247              :                  * While annoying and ugly-looking, this is a good thing
     248              :                  * because it ensures that we wind up with only one copy of
     249              :                  * the toast value when there is only one copy in the old
     250              :                  * toast table.  Before we detected this case, we'd have made
     251              :                  * multiple copies, wasting space; and what's worse, the
     252              :                  * copies belonging to already-deleted heap tuples would not
     253              :                  * be reclaimed by VACUUM.
     254              :                  */
     255          297 :                 if (toastrel_valueid_exists(toastrel,
     256              :                                             toast_pointer.va_valueid))
     257              :                 {
     258              :                     /* Match, so short-circuit the data storage loop below */
     259            1 :                     data_todo = 0;
     260              :                 }
     261              :             }
     262              :         }
     263          300 :         if (toast_pointer.va_valueid == InvalidOid)
     264              :         {
     265              :             /*
     266              :              * new value; must choose an OID that doesn't conflict in either
     267              :              * old or new toast table
     268              :              */
     269              :             do
     270              :             {
     271            3 :                 toast_pointer.va_valueid =
     272            3 :                     GetNewOidWithIndex(toastrel,
     273            3 :                                        RelationGetRelid(toastidxs[validIndex]),
     274              :                                        (AttrNumber) 1);
     275            3 :             } while (toastid_valueid_exists(rel->rd_toastoid,
     276              :                                             toast_pointer.va_valueid));
     277              :         }
     278              :     }
     279              : 
     280              :     /*
     281              :      * Split up the item into chunks
     282              :      */
     283        40018 :     while (data_todo > 0)
     284              :     {
     285              :         HeapTuple   toasttup;
     286              :         Datum       t_values[3];
     287        31470 :         bool        t_isnull[3] = {0};
     288              :         union
     289              :         {
     290              :             alignas(int32) varlena hdr;
     291              :             /* this is to make the union big enough for a chunk: */
     292              :             char        data[TOAST_MAX_CHUNK_SIZE + VARHDRSZ];
     293              :         }           chunk_data;
     294              :         int32       chunk_size;
     295              : 
     296        31470 :         CHECK_FOR_INTERRUPTS();
     297              : 
     298              :         /*
     299              :          * Calculate the size of this chunk
     300              :          */
     301        31470 :         chunk_size = Min(TOAST_MAX_CHUNK_SIZE, data_todo);
     302              : 
     303              :         /*
     304              :          * Build a tuple and store it
     305              :          */
     306        31470 :         t_values[0] = ObjectIdGetDatum(toast_pointer.va_valueid);
     307        31470 :         t_values[1] = Int32GetDatum(chunk_seq++);
     308        31470 :         SET_VARSIZE(&chunk_data, chunk_size + VARHDRSZ);
     309        31470 :         memcpy(VARDATA(&chunk_data), data_p, chunk_size);
     310        31470 :         t_values[2] = PointerGetDatum(&chunk_data);
     311              : 
     312        31470 :         toasttup = heap_form_tuple(toasttupDesc, t_values, t_isnull);
     313              : 
     314        31470 :         heap_insert(toastrel, toasttup, mycid, options, NULL);
     315              : 
     316              :         /*
     317              :          * Create the index entry.  We cheat a little here by not using
     318              :          * FormIndexDatum: this relies on the knowledge that the index columns
     319              :          * are the same as the initial columns of the table for all the
     320              :          * indexes.  We also cheat by not providing an IndexInfo: this is okay
     321              :          * for now because btree doesn't need one, but we might have to be
     322              :          * more honest someday.
     323              :          *
     324              :          * Note also that there had better not be any user-created index on
     325              :          * the TOAST table, since we don't bother to update anything else.
     326              :          */
     327        62940 :         for (int i = 0; i < num_indexes; i++)
     328              :         {
     329              :             /* Only index relations marked as ready can be updated */
     330        31470 :             if (toastidxs[i]->rd_index->indisready)
     331        31470 :                 index_insert(toastidxs[i], t_values, t_isnull,
     332              :                              &(toasttup->t_self),
     333              :                              toastrel,
     334        31470 :                              toastidxs[i]->rd_index->indisunique ?
     335              :                              UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
     336              :                              false, NULL);
     337              :         }
     338              : 
     339              :         /*
     340              :          * Free memory
     341              :          */
     342        31470 :         heap_freetuple(toasttup);
     343              : 
     344              :         /*
     345              :          * Move on to next chunk
     346              :          */
     347        31470 :         data_todo -= chunk_size;
     348        31470 :         data_p += chunk_size;
     349              :     }
     350              : 
     351              :     /*
     352              :      * Done - close toast relation and its indexes but keep the lock until
     353              :      * commit, so as a concurrent reindex done directly on the toast relation
     354              :      * would be able to wait for this transaction.
     355              :      */
     356         8548 :     toast_close_indexes(toastidxs, num_indexes, NoLock);
     357         8548 :     table_close(toastrel, NoLock);
     358              : 
     359              :     /*
     360              :      * Create the TOAST pointer value that we'll return
     361              :      */
     362         8548 :     result = (varlena *) palloc(TOAST_POINTER_SIZE);
     363         8548 :     SET_VARTAG_EXTERNAL(result, VARTAG_ONDISK);
     364         8548 :     memcpy(VARDATA_EXTERNAL(result), &toast_pointer, sizeof(toast_pointer));
     365              : 
     366         8548 :     return PointerGetDatum(result);
     367              : }
     368              : 
     369              : /* ----------
     370              :  * toast_delete_datum -
     371              :  *
     372              :  *  Delete a single external stored value.
     373              :  * ----------
     374              :  */
     375              : void
     376          724 : toast_delete_datum(Relation rel, Datum value, bool is_speculative)
     377              : {
     378          724 :     varlena    *attr = (varlena *) DatumGetPointer(value);
     379              :     varatt_external toast_pointer;
     380              :     Relation    toastrel;
     381              :     Relation   *toastidxs;
     382              :     ScanKeyData toastkey;
     383              :     SysScanDesc toastscan;
     384              :     HeapTuple   toasttup;
     385              :     int         num_indexes;
     386              :     int         validIndex;
     387              : 
     388          724 :     if (!VARATT_IS_EXTERNAL_ONDISK(attr))
     389            0 :         return;
     390              : 
     391              :     /* Must copy to access aligned fields */
     392          724 :     VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
     393              : 
     394              :     /*
     395              :      * Open the toast relation and its indexes
     396              :      */
     397          724 :     toastrel = table_open(toast_pointer.va_toastrelid, RowExclusiveLock);
     398              : 
     399              :     /* Fetch valid relation used for process */
     400          724 :     validIndex = toast_open_indexes(toastrel,
     401              :                                     RowExclusiveLock,
     402              :                                     &toastidxs,
     403              :                                     &num_indexes);
     404              : 
     405              :     /*
     406              :      * Setup a scan key to find chunks with matching va_valueid
     407              :      */
     408          724 :     ScanKeyInit(&toastkey,
     409              :                 (AttrNumber) 1,
     410              :                 BTEqualStrategyNumber, F_OIDEQ,
     411              :                 ObjectIdGetDatum(toast_pointer.va_valueid));
     412              : 
     413              :     /*
     414              :      * Find all the chunks.  (We don't actually care whether we see them in
     415              :      * sequence or not, but since we've already locked the index we might as
     416              :      * well use systable_beginscan_ordered.)
     417              :      */
     418          724 :     toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
     419              :                                            get_toast_snapshot(), 1, &toastkey);
     420         3333 :     while ((toasttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
     421              :     {
     422              :         /*
     423              :          * Have a chunk, delete it
     424              :          */
     425         2609 :         if (is_speculative)
     426            5 :             heap_abort_speculative(toastrel, &toasttup->t_self);
     427              :         else
     428         2604 :             simple_heap_delete(toastrel, &toasttup->t_self);
     429              :     }
     430              : 
     431              :     /*
     432              :      * End scan and close relations but keep the lock until commit, so as a
     433              :      * concurrent reindex done directly on the toast relation would be able to
     434              :      * wait for this transaction.
     435              :      */
     436          724 :     systable_endscan_ordered(toastscan);
     437          724 :     toast_close_indexes(toastidxs, num_indexes, NoLock);
     438          724 :     table_close(toastrel, NoLock);
     439              : }
     440              : 
     441              : /* ----------
     442              :  * toastrel_valueid_exists -
     443              :  *
     444              :  *  Test whether a toast value with the given ID exists in the toast relation.
     445              :  *  For safety, we consider a value to exist if there are either live or dead
     446              :  *  toast rows with that ID; see notes for GetNewOidWithIndex().
     447              :  * ----------
     448              :  */
     449              : static bool
     450          300 : toastrel_valueid_exists(Relation toastrel, Oid valueid)
     451              : {
     452          300 :     bool        result = false;
     453              :     ScanKeyData toastkey;
     454              :     SysScanDesc toastscan;
     455              :     int         num_indexes;
     456              :     int         validIndex;
     457              :     Relation   *toastidxs;
     458              : 
     459              :     /* Fetch a valid index relation */
     460          300 :     validIndex = toast_open_indexes(toastrel,
     461              :                                     RowExclusiveLock,
     462              :                                     &toastidxs,
     463              :                                     &num_indexes);
     464              : 
     465              :     /*
     466              :      * Setup a scan key to find chunks with matching va_valueid
     467              :      */
     468          300 :     ScanKeyInit(&toastkey,
     469              :                 (AttrNumber) 1,
     470              :                 BTEqualStrategyNumber, F_OIDEQ,
     471              :                 ObjectIdGetDatum(valueid));
     472              : 
     473              :     /*
     474              :      * Is there any such chunk?
     475              :      */
     476          300 :     toastscan = systable_beginscan(toastrel,
     477          300 :                                    RelationGetRelid(toastidxs[validIndex]),
     478              :                                    true, SnapshotAny, 1, &toastkey);
     479              : 
     480          300 :     if (systable_getnext(toastscan) != NULL)
     481            1 :         result = true;
     482              : 
     483          300 :     systable_endscan(toastscan);
     484              : 
     485              :     /* Clean up */
     486          300 :     toast_close_indexes(toastidxs, num_indexes, RowExclusiveLock);
     487              : 
     488          300 :     return result;
     489              : }
     490              : 
     491              : /* ----------
     492              :  * toastid_valueid_exists -
     493              :  *
     494              :  *  As above, but work from toast rel's OID not an open relation
     495              :  * ----------
     496              :  */
     497              : static bool
     498            3 : toastid_valueid_exists(Oid toastrelid, Oid valueid)
     499              : {
     500              :     bool        result;
     501              :     Relation    toastrel;
     502              : 
     503            3 :     toastrel = table_open(toastrelid, AccessShareLock);
     504              : 
     505            3 :     result = toastrel_valueid_exists(toastrel, valueid);
     506              : 
     507            3 :     table_close(toastrel, AccessShareLock);
     508              : 
     509            3 :     return result;
     510              : }
     511              : 
     512              : /* ----------
     513              :  * toast_get_valid_index
     514              :  *
     515              :  *  Get OID of valid index associated to given toast relation. A toast
     516              :  *  relation can have only one valid index at the same time.
     517              :  */
     518              : Oid
     519          493 : toast_get_valid_index(Oid toastoid, LOCKMODE lock)
     520              : {
     521              :     int         num_indexes;
     522              :     int         validIndex;
     523              :     Oid         validIndexOid;
     524              :     Relation   *toastidxs;
     525              :     Relation    toastrel;
     526              : 
     527              :     /* Open the toast relation */
     528          493 :     toastrel = table_open(toastoid, lock);
     529              : 
     530              :     /* Look for the valid index of the toast relation */
     531          493 :     validIndex = toast_open_indexes(toastrel,
     532              :                                     lock,
     533              :                                     &toastidxs,
     534              :                                     &num_indexes);
     535          493 :     validIndexOid = RelationGetRelid(toastidxs[validIndex]);
     536              : 
     537              :     /* Close the toast relation and all its indexes */
     538          493 :     toast_close_indexes(toastidxs, num_indexes, NoLock);
     539          493 :     table_close(toastrel, NoLock);
     540              : 
     541          493 :     return validIndexOid;
     542              : }
     543              : 
     544              : /* ----------
     545              :  * toast_open_indexes
     546              :  *
     547              :  *  Get an array of the indexes associated to the given toast relation
     548              :  *  and return as well the position of the valid index used by the toast
     549              :  *  relation in this array. It is the responsibility of the caller of this
     550              :  *  function to close the indexes as well as free them.
     551              :  */
     552              : int
     553        23415 : toast_open_indexes(Relation toastrel,
     554              :                    LOCKMODE lock,
     555              :                    Relation **toastidxs,
     556              :                    int *num_indexes)
     557              : {
     558        23415 :     int         i = 0;
     559        23415 :     int         res = 0;
     560        23415 :     bool        found = false;
     561              :     List       *indexlist;
     562              :     ListCell   *lc;
     563              : 
     564              :     /* Get index list of the toast relation */
     565        23415 :     indexlist = RelationGetIndexList(toastrel);
     566              :     Assert(indexlist != NIL);
     567              : 
     568        23415 :     *num_indexes = list_length(indexlist);
     569              : 
     570              :     /* Open all the index relations */
     571        23415 :     *toastidxs = palloc_array(Relation, *num_indexes);
     572        46830 :     foreach(lc, indexlist)
     573        23415 :         (*toastidxs)[i++] = index_open(lfirst_oid(lc), lock);
     574              : 
     575              :     /* Fetch the first valid index in list */
     576        23415 :     for (i = 0; i < *num_indexes; i++)
     577              :     {
     578        23415 :         Relation    toastidx = (*toastidxs)[i];
     579              : 
     580        23415 :         if (toastidx->rd_index->indisvalid)
     581              :         {
     582        23415 :             res = i;
     583        23415 :             found = true;
     584        23415 :             break;
     585              :         }
     586              :     }
     587              : 
     588              :     /*
     589              :      * Free index list, not necessary anymore as relations are opened and a
     590              :      * valid index has been found.
     591              :      */
     592        23415 :     list_free(indexlist);
     593              : 
     594              :     /*
     595              :      * The toast relation should have one valid index, so something is going
     596              :      * wrong if there is nothing.
     597              :      */
     598        23415 :     if (!found)
     599            0 :         elog(ERROR, "no valid index found for toast relation with Oid %u",
     600              :              RelationGetRelid(toastrel));
     601              : 
     602        23415 :     return res;
     603              : }
     604              : 
     605              : /* ----------
     606              :  * toast_close_indexes
     607              :  *
     608              :  *  Close an array of indexes for a toast relation and free it. This should
     609              :  *  be called for a set of indexes opened previously with toast_open_indexes.
     610              :  */
     611              : void
     612        23412 : toast_close_indexes(Relation *toastidxs, int num_indexes, LOCKMODE lock)
     613              : {
     614              :     int         i;
     615              : 
     616              :     /* Close relations and clean up things */
     617        46824 :     for (i = 0; i < num_indexes; i++)
     618        23412 :         index_close(toastidxs[i], lock);
     619        23412 :     pfree(toastidxs);
     620        23412 : }
     621              : 
     622              : /* ----------
     623              :  * get_toast_snapshot
     624              :  *
     625              :  *  Return the TOAST snapshot. Detoasting *must* happen in the same
     626              :  *  transaction that originally fetched the toast pointer.
     627              :  */
     628              : Snapshot
     629        25218 : get_toast_snapshot(void)
     630              : {
     631              :     /*
     632              :      * We cannot directly check that detoasting happens in the same
     633              :      * transaction that originally fetched the toast pointer, but at least
     634              :      * check that the session has some active snapshots. It might not if, for
     635              :      * example, a procedure fetches a toasted value into a local variable,
     636              :      * commits, and then tries to detoast the value. Such coding is unsafe,
     637              :      * because once we commit there is nothing to prevent the toast data from
     638              :      * being deleted. (This is not very much protection, because in many
     639              :      * scenarios the procedure would have already created a new transaction
     640              :      * snapshot, preventing us from detecting the problem. But it's better
     641              :      * than nothing.)
     642              :      */
     643        25218 :     if (!HaveRegisteredOrActiveSnapshot())
     644            0 :         elog(ERROR, "cannot fetch toast data without an active snapshot");
     645              : 
     646        25218 :     return &SnapshotToastData;
     647              : }
        

Generated by: LCOV version 2.0-1