LCOV - code coverage report
Current view: top level - src/backend/access/common - toast_internals.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 96.7 % 181 175
Test Date: 2026-09-25 09:15:45 Functions: 100.0 % 11 11
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 77.9 % 77 60

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

Generated by: LCOV version 2.0-1