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

Generated by: LCOV version 2.0-1