LCOV - code coverage report
Current view: top level - src/backend/access/common - toast_internals.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 95.1 % 224 213
Test Date: 2026-01-26 10:56:24 Functions: 100.0 % 9 9
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 59.6 % 114 68

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

Generated by: LCOV version 2.3.2-1