Coverage Report

Created: 2026-11-09 07:20

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