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-09-26 18:18:05 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       51808 : toast_compress_datum(Datum value, char cmethod)
      47             : {
      48       51808 :     struct varlena *tmp = NULL;
      49             :     int32       valsize;
      50       51808 :     ToastCompressionId cmid = TOAST_INVALID_COMPRESSION_ID;
      51             : 
      52             :     Assert(!VARATT_IS_EXTERNAL(DatumGetPointer(value)));
      53             :     Assert(!VARATT_IS_COMPRESSED(DatumGetPointer(value)));
      54             : 
      55       51808 :     valsize = VARSIZE_ANY_EXHDR(DatumGetPointer(value));
      56             : 
      57             :     /* If the compression method is not valid, use the current default */
      58       51808 :     if (!CompressionMethodIsValid(cmethod))
      59       51724 :         cmethod = default_toast_compression;
      60             : 
      61             :     /*
      62             :      * Call appropriate compression routine for the compression method.
      63             :      */
      64       51808 :     switch (cmethod)
      65             :     {
      66       51766 :         case TOAST_PGLZ_COMPRESSION:
      67       51766 :             tmp = pglz_compress_datum((const struct varlena *) DatumGetPointer(value));
      68       51766 :             cmid = TOAST_PGLZ_COMPRESSION_ID;
      69       51766 :             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       51808 :     if (tmp == NULL)
      79       12726 :         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       39082 :     if (VARSIZE(tmp) < valsize - 2)
      92             :     {
      93             :         /* successful compression */
      94             :         Assert(cmid != TOAST_INVALID_COMPRESSION_ID);
      95       39082 :         TOAST_COMPRESS_SET_SIZE_AND_COMPRESS_METHOD(tmp, valsize, cmid);
      96       39082 :         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       16722 : toast_save_datum(Relation rel, Datum value,
     120             :                  struct varlena *oldexternal, int options)
     121             : {
     122             :     Relation    toastrel;
     123             :     Relation   *toastidxs;
     124             :     TupleDesc   toasttupDesc;
     125       16722 :     CommandId   mycid = GetCurrentCommandId(true);
     126             :     struct varlena *result;
     127             :     struct varatt_external toast_pointer;
     128       16722 :     int32       chunk_seq = 0;
     129             :     char       *data_p;
     130             :     int32       data_todo;
     131       16722 :     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       16722 :     toastrel = table_open(rel->rd_rel->reltoastrelid, RowExclusiveLock);
     143       16722 :     toasttupDesc = toastrel->rd_att;
     144             : 
     145             :     /* Open all the toast indexes and look for the valid one */
     146       16722 :     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       16722 :     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       16716 :     else if (VARATT_IS_COMPRESSED(dval))
     169             :     {
     170       10266 :         data_p = VARDATA(dval);
     171       10266 :         data_todo = VARSIZE(dval) - VARHDRSZ;
     172             :         /* rawsize in a compressed datum is just the size of the payload */
     173       10266 :         toast_pointer.va_rawsize = VARDATA_COMPRESSED_GET_EXTSIZE(dval) + VARHDRSZ;
     174             : 
     175             :         /* set external size and compression method */
     176       10266 :         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        6450 :         data_p = VARDATA(dval);
     184        6450 :         data_todo = VARSIZE(dval) - VARHDRSZ;
     185        6450 :         toast_pointer.va_rawsize = VARSIZE(dval);
     186        6450 :         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       16722 :     if (OidIsValid(rel->rd_toastoid))
     198         598 :         toast_pointer.va_toastrelid = rel->rd_toastoid;
     199             :     else
     200       16124 :         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       16722 :     if (!OidIsValid(rel->rd_toastoid))
     215             :     {
     216             :         /* normal case: just choose an unused OID */
     217       16124 :         toast_pointer.va_valueid =
     218       16124 :             GetNewOidWithIndex(toastrel,
     219       16124 :                                RelationGetRelid(toastidxs[validIndex]),
     220             :                                (AttrNumber) 1);
     221             :     }
     222             :     else
     223             :     {
     224             :         /* rewrite case: check to see if value was in old toast table */
     225         598 :         toast_pointer.va_valueid = InvalidOid;
     226         598 :         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         592 :             VARATT_EXTERNAL_GET_POINTER(old_toast_pointer, oldexternal);
     233         592 :             if (old_toast_pointer.va_toastrelid == rel->rd_toastoid)
     234             :             {
     235             :                 /* This value came from the old toast table; reuse its OID */
     236         592 :                 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         592 :                 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         598 :         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       78244 :     while (data_todo > 0)
     284             :     {
     285             :         HeapTuple   toasttup;
     286             :         Datum       t_values[3];
     287       61522 :         bool        t_isnull[3] = {0};
     288             :         union
     289             :         {
     290             :             struct varlena hdr;
     291             :             /* this is to make the union big enough for a chunk: */
     292             :             char        data[TOAST_MAX_CHUNK_SIZE + VARHDRSZ];
     293             :             /* ensure union is aligned well enough: */
     294             :             int32       align_it;
     295             :         }           chunk_data;
     296             :         int32       chunk_size;
     297             : 
     298       61522 :         CHECK_FOR_INTERRUPTS();
     299             : 
     300             :         /*
     301             :          * Calculate the size of this chunk
     302             :          */
     303       61522 :         chunk_size = Min(TOAST_MAX_CHUNK_SIZE, data_todo);
     304             : 
     305             :         /*
     306             :          * Build a tuple and store it
     307             :          */
     308       61522 :         t_values[0] = ObjectIdGetDatum(toast_pointer.va_valueid);
     309       61522 :         t_values[1] = Int32GetDatum(chunk_seq++);
     310       61522 :         SET_VARSIZE(&chunk_data, chunk_size + VARHDRSZ);
     311       61522 :         memcpy(VARDATA(&chunk_data), data_p, chunk_size);
     312       61522 :         t_values[2] = PointerGetDatum(&chunk_data);
     313             : 
     314       61522 :         toasttup = heap_form_tuple(toasttupDesc, t_values, t_isnull);
     315             : 
     316       61522 :         heap_insert(toastrel, toasttup, mycid, options, NULL);
     317             : 
     318             :         /*
     319             :          * Create the index entry.  We cheat a little here by not using
     320             :          * FormIndexDatum: this relies on the knowledge that the index columns
     321             :          * are the same as the initial columns of the table for all the
     322             :          * indexes.  We also cheat by not providing an IndexInfo: this is okay
     323             :          * for now because btree doesn't need one, but we might have to be
     324             :          * more honest someday.
     325             :          *
     326             :          * Note also that there had better not be any user-created index on
     327             :          * the TOAST table, since we don't bother to update anything else.
     328             :          */
     329      123044 :         for (int i = 0; i < num_indexes; i++)
     330             :         {
     331             :             /* Only index relations marked as ready can be updated */
     332       61522 :             if (toastidxs[i]->rd_index->indisready)
     333       61522 :                 index_insert(toastidxs[i], t_values, t_isnull,
     334             :                              &(toasttup->t_self),
     335             :                              toastrel,
     336       61522 :                              toastidxs[i]->rd_index->indisunique ?
     337             :                              UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
     338             :                              false, NULL);
     339             :         }
     340             : 
     341             :         /*
     342             :          * Free memory
     343             :          */
     344       61522 :         heap_freetuple(toasttup);
     345             : 
     346             :         /*
     347             :          * Move on to next chunk
     348             :          */
     349       61522 :         data_todo -= chunk_size;
     350       61522 :         data_p += chunk_size;
     351             :     }
     352             : 
     353             :     /*
     354             :      * Done - close toast relation and its indexes but keep the lock until
     355             :      * commit, so as a concurrent reindex done directly on the toast relation
     356             :      * would be able to wait for this transaction.
     357             :      */
     358       16722 :     toast_close_indexes(toastidxs, num_indexes, NoLock);
     359       16722 :     table_close(toastrel, NoLock);
     360             : 
     361             :     /*
     362             :      * Create the TOAST pointer value that we'll return
     363             :      */
     364       16722 :     result = (struct varlena *) palloc(TOAST_POINTER_SIZE);
     365       16722 :     SET_VARTAG_EXTERNAL(result, VARTAG_ONDISK);
     366       16722 :     memcpy(VARDATA_EXTERNAL(result), &toast_pointer, sizeof(toast_pointer));
     367             : 
     368       16722 :     return PointerGetDatum(result);
     369             : }
     370             : 
     371             : /* ----------
     372             :  * toast_delete_datum -
     373             :  *
     374             :  *  Delete a single external stored value.
     375             :  * ----------
     376             :  */
     377             : void
     378        1388 : toast_delete_datum(Relation rel, Datum value, bool is_speculative)
     379             : {
     380        1388 :     struct varlena *attr = (struct varlena *) DatumGetPointer(value);
     381             :     struct varatt_external toast_pointer;
     382             :     Relation    toastrel;
     383             :     Relation   *toastidxs;
     384             :     ScanKeyData toastkey;
     385             :     SysScanDesc toastscan;
     386             :     HeapTuple   toasttup;
     387             :     int         num_indexes;
     388             :     int         validIndex;
     389             : 
     390        1388 :     if (!VARATT_IS_EXTERNAL_ONDISK(attr))
     391           0 :         return;
     392             : 
     393             :     /* Must copy to access aligned fields */
     394        1388 :     VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
     395             : 
     396             :     /*
     397             :      * Open the toast relation and its indexes
     398             :      */
     399        1388 :     toastrel = table_open(toast_pointer.va_toastrelid, RowExclusiveLock);
     400             : 
     401             :     /* Fetch valid relation used for process */
     402        1388 :     validIndex = toast_open_indexes(toastrel,
     403             :                                     RowExclusiveLock,
     404             :                                     &toastidxs,
     405             :                                     &num_indexes);
     406             : 
     407             :     /*
     408             :      * Setup a scan key to find chunks with matching va_valueid
     409             :      */
     410        1388 :     ScanKeyInit(&toastkey,
     411             :                 (AttrNumber) 1,
     412             :                 BTEqualStrategyNumber, F_OIDEQ,
     413             :                 ObjectIdGetDatum(toast_pointer.va_valueid));
     414             : 
     415             :     /*
     416             :      * Find all the chunks.  (We don't actually care whether we see them in
     417             :      * sequence or not, but since we've already locked the index we might as
     418             :      * well use systable_beginscan_ordered.)
     419             :      */
     420        1388 :     toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
     421             :                                            get_toast_snapshot(), 1, &toastkey);
     422        6508 :     while ((toasttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
     423             :     {
     424             :         /*
     425             :          * Have a chunk, delete it
     426             :          */
     427        5120 :         if (is_speculative)
     428          10 :             heap_abort_speculative(toastrel, &toasttup->t_self);
     429             :         else
     430        5110 :             simple_heap_delete(toastrel, &toasttup->t_self);
     431             :     }
     432             : 
     433             :     /*
     434             :      * End scan and close relations but keep the lock until commit, so as a
     435             :      * concurrent reindex done directly on the toast relation would be able to
     436             :      * wait for this transaction.
     437             :      */
     438        1388 :     systable_endscan_ordered(toastscan);
     439        1388 :     toast_close_indexes(toastidxs, num_indexes, NoLock);
     440        1388 :     table_close(toastrel, NoLock);
     441             : }
     442             : 
     443             : /* ----------
     444             :  * toastrel_valueid_exists -
     445             :  *
     446             :  *  Test whether a toast value with the given ID exists in the toast relation.
     447             :  *  For safety, we consider a value to exist if there are either live or dead
     448             :  *  toast rows with that ID; see notes for GetNewOidWithIndex().
     449             :  * ----------
     450             :  */
     451             : static bool
     452         598 : toastrel_valueid_exists(Relation toastrel, Oid valueid)
     453             : {
     454         598 :     bool        result = false;
     455             :     ScanKeyData toastkey;
     456             :     SysScanDesc toastscan;
     457             :     int         num_indexes;
     458             :     int         validIndex;
     459             :     Relation   *toastidxs;
     460             : 
     461             :     /* Fetch a valid index relation */
     462         598 :     validIndex = toast_open_indexes(toastrel,
     463             :                                     RowExclusiveLock,
     464             :                                     &toastidxs,
     465             :                                     &num_indexes);
     466             : 
     467             :     /*
     468             :      * Setup a scan key to find chunks with matching va_valueid
     469             :      */
     470         598 :     ScanKeyInit(&toastkey,
     471             :                 (AttrNumber) 1,
     472             :                 BTEqualStrategyNumber, F_OIDEQ,
     473             :                 ObjectIdGetDatum(valueid));
     474             : 
     475             :     /*
     476             :      * Is there any such chunk?
     477             :      */
     478         598 :     toastscan = systable_beginscan(toastrel,
     479         598 :                                    RelationGetRelid(toastidxs[validIndex]),
     480             :                                    true, SnapshotAny, 1, &toastkey);
     481             : 
     482         598 :     if (systable_getnext(toastscan) != NULL)
     483           2 :         result = true;
     484             : 
     485         598 :     systable_endscan(toastscan);
     486             : 
     487             :     /* Clean up */
     488         598 :     toast_close_indexes(toastidxs, num_indexes, RowExclusiveLock);
     489             : 
     490         598 :     return result;
     491             : }
     492             : 
     493             : /* ----------
     494             :  * toastid_valueid_exists -
     495             :  *
     496             :  *  As above, but work from toast rel's OID not an open relation
     497             :  * ----------
     498             :  */
     499             : static bool
     500           6 : toastid_valueid_exists(Oid toastrelid, Oid valueid)
     501             : {
     502             :     bool        result;
     503             :     Relation    toastrel;
     504             : 
     505           6 :     toastrel = table_open(toastrelid, AccessShareLock);
     506             : 
     507           6 :     result = toastrel_valueid_exists(toastrel, valueid);
     508             : 
     509           6 :     table_close(toastrel, AccessShareLock);
     510             : 
     511           6 :     return result;
     512             : }
     513             : 
     514             : /* ----------
     515             :  * toast_get_valid_index
     516             :  *
     517             :  *  Get OID of valid index associated to given toast relation. A toast
     518             :  *  relation can have only one valid index at the same time.
     519             :  */
     520             : Oid
     521         984 : toast_get_valid_index(Oid toastoid, LOCKMODE lock)
     522             : {
     523             :     int         num_indexes;
     524             :     int         validIndex;
     525             :     Oid         validIndexOid;
     526             :     Relation   *toastidxs;
     527             :     Relation    toastrel;
     528             : 
     529             :     /* Open the toast relation */
     530         984 :     toastrel = table_open(toastoid, lock);
     531             : 
     532             :     /* Look for the valid index of the toast relation */
     533         984 :     validIndex = toast_open_indexes(toastrel,
     534             :                                     lock,
     535             :                                     &toastidxs,
     536             :                                     &num_indexes);
     537         984 :     validIndexOid = RelationGetRelid(toastidxs[validIndex]);
     538             : 
     539             :     /* Close the toast relation and all its indexes */
     540         984 :     toast_close_indexes(toastidxs, num_indexes, NoLock);
     541         984 :     table_close(toastrel, NoLock);
     542             : 
     543         984 :     return validIndexOid;
     544             : }
     545             : 
     546             : /* ----------
     547             :  * toast_open_indexes
     548             :  *
     549             :  *  Get an array of the indexes associated to the given toast relation
     550             :  *  and return as well the position of the valid index used by the toast
     551             :  *  relation in this array. It is the responsibility of the caller of this
     552             :  *  function to close the indexes as well as free them.
     553             :  */
     554             : int
     555       44468 : toast_open_indexes(Relation toastrel,
     556             :                    LOCKMODE lock,
     557             :                    Relation **toastidxs,
     558             :                    int *num_indexes)
     559             : {
     560       44468 :     int         i = 0;
     561       44468 :     int         res = 0;
     562       44468 :     bool        found = false;
     563             :     List       *indexlist;
     564             :     ListCell   *lc;
     565             : 
     566             :     /* Get index list of the toast relation */
     567       44468 :     indexlist = RelationGetIndexList(toastrel);
     568             :     Assert(indexlist != NIL);
     569             : 
     570       44468 :     *num_indexes = list_length(indexlist);
     571             : 
     572             :     /* Open all the index relations */
     573       44468 :     *toastidxs = (Relation *) palloc(*num_indexes * sizeof(Relation));
     574       88936 :     foreach(lc, indexlist)
     575       44468 :         (*toastidxs)[i++] = index_open(lfirst_oid(lc), lock);
     576             : 
     577             :     /* Fetch the first valid index in list */
     578       44468 :     for (i = 0; i < *num_indexes; i++)
     579             :     {
     580       44468 :         Relation    toastidx = (*toastidxs)[i];
     581             : 
     582       44468 :         if (toastidx->rd_index->indisvalid)
     583             :         {
     584       44468 :             res = i;
     585       44468 :             found = true;
     586       44468 :             break;
     587             :         }
     588             :     }
     589             : 
     590             :     /*
     591             :      * Free index list, not necessary anymore as relations are opened and a
     592             :      * valid index has been found.
     593             :      */
     594       44468 :     list_free(indexlist);
     595             : 
     596             :     /*
     597             :      * The toast relation should have one valid index, so something is going
     598             :      * wrong if there is nothing.
     599             :      */
     600       44468 :     if (!found)
     601           0 :         elog(ERROR, "no valid index found for toast relation with Oid %u",
     602             :              RelationGetRelid(toastrel));
     603             : 
     604       44468 :     return res;
     605             : }
     606             : 
     607             : /* ----------
     608             :  * toast_close_indexes
     609             :  *
     610             :  *  Close an array of indexes for a toast relation and free it. This should
     611             :  *  be called for a set of indexes opened previously with toast_open_indexes.
     612             :  */
     613             : void
     614       44462 : toast_close_indexes(Relation *toastidxs, int num_indexes, LOCKMODE lock)
     615             : {
     616             :     int         i;
     617             : 
     618             :     /* Close relations and clean up things */
     619       88924 :     for (i = 0; i < num_indexes; i++)
     620       44462 :         index_close(toastidxs[i], lock);
     621       44462 :     pfree(toastidxs);
     622       44462 : }
     623             : 
     624             : /* ----------
     625             :  * get_toast_snapshot
     626             :  *
     627             :  *  Return the TOAST snapshot. Detoasting *must* happen in the same
     628             :  *  transaction that originally fetched the toast pointer.
     629             :  */
     630             : Snapshot
     631       48266 : get_toast_snapshot(void)
     632             : {
     633             :     /*
     634             :      * We cannot directly check that detoasting happens in the same
     635             :      * transaction that originally fetched the toast pointer, but at least
     636             :      * check that the session has some active snapshots. It might not if, for
     637             :      * example, a procedure fetches a toasted value into a local variable,
     638             :      * commits, and then tries to detoast the value. Such coding is unsafe,
     639             :      * because once we commit there is nothing to prevent the toast data from
     640             :      * being deleted. (This is not very much protection, because in many
     641             :      * scenarios the procedure would have already created a new transaction
     642             :      * snapshot, preventing us from detecting the problem. But it's better
     643             :      * than nothing.)
     644             :      */
     645       48266 :     if (!HaveRegisteredOrActiveSnapshot())
     646           0 :         elog(ERROR, "cannot fetch toast data without an active snapshot");
     647             : 
     648       48266 :     return &SnapshotToastData;
     649             : }

Generated by: LCOV version 1.16