LCOV - code coverage report
Current view: top level - src/backend/access/common - toast_internals.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 148 155 95.5 %
Date: 2025-12-13 03:17:34 Functions: 9 9 100.0 %
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-2025, 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       51980 : toast_compress_datum(Datum value, char cmethod)
      47             : {
      48       51980 :     struct varlena *tmp = NULL;
      49             :     int32       valsize;
      50       51980 :     ToastCompressionId cmid = TOAST_INVALID_COMPRESSION_ID;
      51             : 
      52             :     Assert(!VARATT_IS_EXTERNAL(DatumGetPointer(value)));
      53             :     Assert(!VARATT_IS_COMPRESSED(DatumGetPointer(value)));
      54             : 
      55       51980 :     valsize = VARSIZE_ANY_EXHDR(DatumGetPointer(value));
      56             : 
      57             :     /* If the compression method is not valid, use the current default */
      58       51980 :     if (!CompressionMethodIsValid(cmethod))
      59       51896 :         cmethod = default_toast_compression;
      60             : 
      61             :     /*
      62             :      * Call appropriate compression routine for the compression method.
      63             :      */
      64       51980 :     switch (cmethod)
      65             :     {
      66       51938 :         case TOAST_PGLZ_COMPRESSION:
      67       51938 :             tmp = pglz_compress_datum((const struct varlena *) DatumGetPointer(value));
      68       51938 :             cmid = TOAST_PGLZ_COMPRESSION_ID;
      69       51938 :             break;
      70          42 :         case TOAST_LZ4_COMPRESSION:
      71          42 :             tmp = lz4_compress_datum((const struct varlena *) DatumGetPointer(value));
      72          42 :             cmid = TOAST_LZ4_COMPRESSION_ID;
      73          42 :             break;
      74           0 :         default:
      75           0 :             elog(ERROR, "invalid compression method %c", cmethod);
      76             :     }
      77             : 
      78       51980 :     if (tmp == NULL)
      79       12324 :         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       39656 :     if (VARSIZE(tmp) < valsize - 2)
      92             :     {
      93             :         /* successful compression */
      94             :         Assert(cmid != TOAST_INVALID_COMPRESSION_ID);
      95       39656 :         TOAST_COMPRESS_SET_SIZE_AND_COMPRESS_METHOD(tmp, valsize, cmid);
      96       39656 :         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       17026 : toast_save_datum(Relation rel, Datum value,
     120             :                  struct varlena *oldexternal, int options)
     121             : {
     122             :     Relation    toastrel;
     123             :     Relation   *toastidxs;
     124             :     TupleDesc   toasttupDesc;
     125       17026 :     CommandId   mycid = GetCurrentCommandId(true);
     126             :     struct varlena *result;
     127             :     struct varatt_external toast_pointer;
     128       17026 :     int32       chunk_seq = 0;
     129             :     char       *data_p;
     130             :     int32       data_todo;
     131       17026 :     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       17026 :     toastrel = table_open(rel->rd_rel->reltoastrelid, RowExclusiveLock);
     143       17026 :     toasttupDesc = toastrel->rd_att;
     144             : 
     145             :     /* Open all the toast indexes and look for the valid one */
     146       17026 :     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       17026 :     if (VARATT_IS_SHORT(dval))
     162             :     {
     163           6 :         data_p = VARDATA_SHORT(dval);
     164           6 :         data_todo = VARSIZE_SHORT(dval) - VARHDRSZ_SHORT;
     165           6 :         toast_pointer.va_rawsize = data_todo + VARHDRSZ;    /* as if not short */
     166           6 :         toast_pointer.va_extinfo = data_todo;
     167             :     }
     168       17020 :     else if (VARATT_IS_COMPRESSED(dval))
     169             :     {
     170       10574 :         data_p = VARDATA(dval);
     171       10574 :         data_todo = VARSIZE(dval) - VARHDRSZ;
     172             :         /* rawsize in a compressed datum is just the size of the payload */
     173       10574 :         toast_pointer.va_rawsize = VARDATA_COMPRESSED_GET_EXTSIZE(dval) + VARHDRSZ;
     174             : 
     175             :         /* set external size and compression method */
     176       10574 :         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        6446 :         data_p = VARDATA(dval);
     184        6446 :         data_todo = VARSIZE(dval) - VARHDRSZ;
     185        6446 :         toast_pointer.va_rawsize = VARSIZE(dval);
     186        6446 :         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       17026 :     if (OidIsValid(rel->rd_toastoid))
     198         600 :         toast_pointer.va_toastrelid = rel->rd_toastoid;
     199             :     else
     200       16426 :         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       17026 :     if (!OidIsValid(rel->rd_toastoid))
     215             :     {
     216             :         /* normal case: just choose an unused OID */
     217       16426 :         toast_pointer.va_valueid =
     218       16426 :             GetNewOidWithIndex(toastrel,
     219       16426 :                                RelationGetRelid(toastidxs[validIndex]),
     220             :                                (AttrNumber) 1);
     221             :     }
     222             :     else
     223             :     {
     224             :         /* rewrite case: check to see if value was in old toast table */
     225         600 :         toast_pointer.va_valueid = InvalidOid;
     226         600 :         if (oldexternal != NULL)
     227             :         {
     228             :             struct varatt_external old_toast_pointer;
     229             : 
     230             :             Assert(VARATT_IS_EXTERNAL_ONDISK(oldexternal));
     231             :             /* Must copy to access aligned fields */
     232         594 :             VARATT_EXTERNAL_GET_POINTER(old_toast_pointer, oldexternal);
     233         594 :             if (old_toast_pointer.va_toastrelid == rel->rd_toastoid)
     234             :             {
     235             :                 /* This value came from the old toast table; reuse its OID */
     236         594 :                 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         594 :                 if (toastrel_valueid_exists(toastrel,
     256             :                                             toast_pointer.va_valueid))
     257             :                 {
     258             :                     /* Match, so short-circuit the data storage loop below */
     259           2 :                     data_todo = 0;
     260             :                 }
     261             :             }
     262             :         }
     263         600 :         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           6 :                 toast_pointer.va_valueid =
     272           6 :                     GetNewOidWithIndex(toastrel,
     273           6 :                                        RelationGetRelid(toastidxs[validIndex]),
     274             :                                        (AttrNumber) 1);
     275           6 :             } 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       79784 :     while (data_todo > 0)
     284             :     {
     285             :         HeapTuple   toasttup;
     286             :         Datum       t_values[3];
     287       62758 :         bool        t_isnull[3] = {0};
     288             :         union
     289             :         {
     290             :             alignas(int32) struct 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       62758 :         CHECK_FOR_INTERRUPTS();
     297             : 
     298             :         /*
     299             :          * Calculate the size of this chunk
     300             :          */
     301       62758 :         chunk_size = Min(TOAST_MAX_CHUNK_SIZE, data_todo);
     302             : 
     303             :         /*
     304             :          * Build a tuple and store it
     305             :          */
     306       62758 :         t_values[0] = ObjectIdGetDatum(toast_pointer.va_valueid);
     307       62758 :         t_values[1] = Int32GetDatum(chunk_seq++);
     308       62758 :         SET_VARSIZE(&chunk_data, chunk_size + VARHDRSZ);
     309       62758 :         memcpy(VARDATA(&chunk_data), data_p, chunk_size);
     310       62758 :         t_values[2] = PointerGetDatum(&chunk_data);
     311             : 
     312       62758 :         toasttup = heap_form_tuple(toasttupDesc, t_values, t_isnull);
     313             : 
     314       62758 :         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      125516 :         for (int i = 0; i < num_indexes; i++)
     328             :         {
     329             :             /* Only index relations marked as ready can be updated */
     330       62758 :             if (toastidxs[i]->rd_index->indisready)
     331       62758 :                 index_insert(toastidxs[i], t_values, t_isnull,
     332             :                              &(toasttup->t_self),
     333             :                              toastrel,
     334       62758 :                              toastidxs[i]->rd_index->indisunique ?
     335             :                              UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
     336             :                              false, NULL);
     337             :         }
     338             : 
     339             :         /*
     340             :          * Free memory
     341             :          */
     342       62758 :         heap_freetuple(toasttup);
     343             : 
     344             :         /*
     345             :          * Move on to next chunk
     346             :          */
     347       62758 :         data_todo -= chunk_size;
     348       62758 :         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       17026 :     toast_close_indexes(toastidxs, num_indexes, NoLock);
     357       17026 :     table_close(toastrel, NoLock);
     358             : 
     359             :     /*
     360             :      * Create the TOAST pointer value that we'll return
     361             :      */
     362       17026 :     result = (struct varlena *) palloc(TOAST_POINTER_SIZE);
     363       17026 :     SET_VARTAG_EXTERNAL(result, VARTAG_ONDISK);
     364       17026 :     memcpy(VARDATA_EXTERNAL(result), &toast_pointer, sizeof(toast_pointer));
     365             : 
     366       17026 :     return PointerGetDatum(result);
     367             : }
     368             : 
     369             : /* ----------
     370             :  * toast_delete_datum -
     371             :  *
     372             :  *  Delete a single external stored value.
     373             :  * ----------
     374             :  */
     375             : void
     376        1408 : toast_delete_datum(Relation rel, Datum value, bool is_speculative)
     377             : {
     378        1408 :     struct varlena *attr = (struct varlena *) DatumGetPointer(value);
     379             :     struct 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        1408 :     if (!VARATT_IS_EXTERNAL_ONDISK(attr))
     389           0 :         return;
     390             : 
     391             :     /* Must copy to access aligned fields */
     392        1408 :     VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
     393             : 
     394             :     /*
     395             :      * Open the toast relation and its indexes
     396             :      */
     397        1408 :     toastrel = table_open(toast_pointer.va_toastrelid, RowExclusiveLock);
     398             : 
     399             :     /* Fetch valid relation used for process */
     400        1408 :     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        1408 :     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        1408 :     toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
     419             :                                            get_toast_snapshot(), 1, &toastkey);
     420        6570 :     while ((toasttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
     421             :     {
     422             :         /*
     423             :          * Have a chunk, delete it
     424             :          */
     425        5162 :         if (is_speculative)
     426          10 :             heap_abort_speculative(toastrel, &toasttup->t_self);
     427             :         else
     428        5152 :             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        1408 :     systable_endscan_ordered(toastscan);
     437        1408 :     toast_close_indexes(toastidxs, num_indexes, NoLock);
     438        1408 :     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         600 : toastrel_valueid_exists(Relation toastrel, Oid valueid)
     451             : {
     452         600 :     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         600 :     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         600 :     ScanKeyInit(&toastkey,
     469             :                 (AttrNumber) 1,
     470             :                 BTEqualStrategyNumber, F_OIDEQ,
     471             :                 ObjectIdGetDatum(valueid));
     472             : 
     473             :     /*
     474             :      * Is there any such chunk?
     475             :      */
     476         600 :     toastscan = systable_beginscan(toastrel,
     477         600 :                                    RelationGetRelid(toastidxs[validIndex]),
     478             :                                    true, SnapshotAny, 1, &toastkey);
     479             : 
     480         600 :     if (systable_getnext(toastscan) != NULL)
     481           2 :         result = true;
     482             : 
     483         600 :     systable_endscan(toastscan);
     484             : 
     485             :     /* Clean up */
     486         600 :     toast_close_indexes(toastidxs, num_indexes, RowExclusiveLock);
     487             : 
     488         600 :     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           6 : toastid_valueid_exists(Oid toastrelid, Oid valueid)
     499             : {
     500             :     bool        result;
     501             :     Relation    toastrel;
     502             : 
     503           6 :     toastrel = table_open(toastrelid, AccessShareLock);
     504             : 
     505           6 :     result = toastrel_valueid_exists(toastrel, valueid);
     506             : 
     507           6 :     table_close(toastrel, AccessShareLock);
     508             : 
     509           6 :     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         986 : 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         986 :     toastrel = table_open(toastoid, lock);
     529             : 
     530             :     /* Look for the valid index of the toast relation */
     531         986 :     validIndex = toast_open_indexes(toastrel,
     532             :                                     lock,
     533             :                                     &toastidxs,
     534             :                                     &num_indexes);
     535         986 :     validIndexOid = RelationGetRelid(toastidxs[validIndex]);
     536             : 
     537             :     /* Close the toast relation and all its indexes */
     538         986 :     toast_close_indexes(toastidxs, num_indexes, NoLock);
     539         986 :     table_close(toastrel, NoLock);
     540             : 
     541         986 :     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       45416 : toast_open_indexes(Relation toastrel,
     554             :                    LOCKMODE lock,
     555             :                    Relation **toastidxs,
     556             :                    int *num_indexes)
     557             : {
     558       45416 :     int         i = 0;
     559       45416 :     int         res = 0;
     560       45416 :     bool        found = false;
     561             :     List       *indexlist;
     562             :     ListCell   *lc;
     563             : 
     564             :     /* Get index list of the toast relation */
     565       45416 :     indexlist = RelationGetIndexList(toastrel);
     566             :     Assert(indexlist != NIL);
     567             : 
     568       45416 :     *num_indexes = list_length(indexlist);
     569             : 
     570             :     /* Open all the index relations */
     571       45416 :     *toastidxs = palloc_array(Relation, *num_indexes);
     572       90832 :     foreach(lc, indexlist)
     573       45416 :         (*toastidxs)[i++] = index_open(lfirst_oid(lc), lock);
     574             : 
     575             :     /* Fetch the first valid index in list */
     576       45416 :     for (i = 0; i < *num_indexes; i++)
     577             :     {
     578       45416 :         Relation    toastidx = (*toastidxs)[i];
     579             : 
     580       45416 :         if (toastidx->rd_index->indisvalid)
     581             :         {
     582       45416 :             res = i;
     583       45416 :             found = true;
     584       45416 :             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       45416 :     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       45416 :     if (!found)
     599           0 :         elog(ERROR, "no valid index found for toast relation with Oid %u",
     600             :              RelationGetRelid(toastrel));
     601             : 
     602       45416 :     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       45410 : toast_close_indexes(Relation *toastidxs, int num_indexes, LOCKMODE lock)
     613             : {
     614             :     int         i;
     615             : 
     616             :     /* Close relations and clean up things */
     617       90820 :     for (i = 0; i < num_indexes; i++)
     618       45410 :         index_close(toastidxs[i], lock);
     619       45410 :     pfree(toastidxs);
     620       45410 : }
     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       48948 : 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       48948 :     if (!HaveRegisteredOrActiveSnapshot())
     644           0 :         elog(ERROR, "cannot fetch toast data without an active snapshot");
     645             : 
     646       48948 :     return &SnapshotToastData;
     647             : }

Generated by: LCOV version 1.16