Coverage Report

Created: 2026-11-12 07:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/access/heap/heaptoast.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * heaptoast.c
4
 *    Heap-specific definitions for external and compressed storage
5
 *    of variable size attributes.
6
 *
7
 * Copyright (c) 2000-2026, PostgreSQL Global Development Group
8
 *
9
 *
10
 * IDENTIFICATION
11
 *    src/backend/access/heap/heaptoast.c
12
 *
13
 *
14
 * INTERFACE ROUTINES
15
 *    heap_toast_insert_or_update -
16
 *      Try to make a given tuple fit into one page by compressing
17
 *      or moving off attributes
18
 *
19
 *    heap_toast_delete -
20
 *      Reclaim toast storage when a tuple is deleted
21
 *
22
 *-------------------------------------------------------------------------
23
 */
24
25
#include "postgres.h"
26
27
#include "access/detoast.h"
28
#include "access/genam.h"
29
#include "access/heapam.h"
30
#include "access/heaptoast.h"
31
#include "access/toast_helper.h"
32
#include "access/toast_internals.h"
33
#include "utils/fmgroids.h"
34
35
36
/* ----------
37
 * heap_toast_delete -
38
 *
39
 *  Cascaded delete toast-entries on DELETE
40
 * ----------
41
 */
42
void
43
heap_toast_delete(Relation rel, HeapTuple oldtup, bool is_speculative)
44
0
{
45
0
  TupleDesc tupleDesc;
46
0
  Datum   toast_values[MaxHeapAttributeNumber];
47
0
  bool    toast_isnull[MaxHeapAttributeNumber];
48
49
  /*
50
   * We should only ever be called for tuples of plain relations or
51
   * materialized views --- recursing on a toast rel is bad news.
52
   */
53
0
  Assert(rel->rd_rel->relkind == RELKIND_RELATION ||
54
0
       rel->rd_rel->relkind == RELKIND_MATVIEW);
55
56
  /*
57
   * Get the tuple descriptor and break down the tuple into fields.
58
   *
59
   * NOTE: it's debatable whether to use heap_deform_tuple() here or just
60
   * heap_getattr() only the varlena columns.  The latter could win if there
61
   * are few varlena columns and many non-varlena ones. However,
62
   * heap_deform_tuple costs only O(N) while the heap_getattr way would cost
63
   * O(N^2) if there are many varlena columns, so it seems better to err on
64
   * the side of linear cost.  (We won't even be here unless there's at
65
   * least one varlena column, by the way.)
66
   */
67
0
  tupleDesc = rel->rd_att;
68
69
0
  Assert(tupleDesc->natts <= MaxHeapAttributeNumber);
70
0
  heap_deform_tuple(oldtup, tupleDesc, toast_values, toast_isnull);
71
72
  /* Do the real work. */
73
0
  toast_delete_external(rel, toast_values, toast_isnull, is_speculative);
74
0
}
75
76
77
/* ----------
78
 * heap_toast_insert_or_update -
79
 *
80
 *  Delete no-longer-used toast-entries and create new ones to
81
 *  make the new tuple fit on INSERT or UPDATE
82
 *
83
 * Inputs:
84
 *  newtup: the candidate new tuple to be inserted
85
 *  oldtup: the old row version for UPDATE, or NULL for INSERT
86
 *  options: options to be passed to heap_insert() for toast rows
87
 * Result:
88
 *  either newtup if no toasting is needed, or a palloc'd modified tuple
89
 *  that is what should actually get stored
90
 *
91
 * NOTE: neither newtup nor oldtup will be modified.  This is a change
92
 * from the pre-8.1 API of this routine.
93
 * ----------
94
 */
95
HeapTuple
96
heap_toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
97
              uint32 options)
98
0
{
99
0
  HeapTuple result_tuple;
100
0
  TupleDesc tupleDesc;
101
0
  int     numAttrs;
102
103
0
  Size    maxDataLen;
104
0
  Size    hoff;
105
106
0
  bool    toast_isnull[MaxHeapAttributeNumber];
107
0
  bool    toast_oldisnull[MaxHeapAttributeNumber];
108
0
  Datum   toast_values[MaxHeapAttributeNumber];
109
0
  Datum   toast_oldvalues[MaxHeapAttributeNumber];
110
0
  ToastAttrInfo toast_attr[MaxHeapAttributeNumber];
111
0
  ToastTupleContext ttc;
112
113
  /*
114
   * Ignore the INSERT_SPECULATIVE option. Speculative insertions/super
115
   * deletions just normally insert/delete the toast values. It seems
116
   * easiest to deal with that here, instead on, potentially, multiple
117
   * callers.
118
   */
119
0
  options &= ~HEAP_INSERT_SPECULATIVE;
120
121
  /*
122
   * We should only ever be called for tuples of plain relations or
123
   * materialized views --- recursing on a toast rel is bad news.
124
   */
125
0
  Assert(rel->rd_rel->relkind == RELKIND_RELATION ||
126
0
       rel->rd_rel->relkind == RELKIND_MATVIEW);
127
128
  /*
129
   * Get the tuple descriptor and break down the tuple(s) into fields.
130
   */
131
0
  tupleDesc = rel->rd_att;
132
0
  numAttrs = tupleDesc->natts;
133
134
0
  Assert(numAttrs <= MaxHeapAttributeNumber);
135
0
  heap_deform_tuple(newtup, tupleDesc, toast_values, toast_isnull);
136
0
  if (oldtup != NULL)
137
0
    heap_deform_tuple(oldtup, tupleDesc, toast_oldvalues, toast_oldisnull);
138
139
  /* ----------
140
   * Prepare for toasting
141
   * ----------
142
   */
143
0
  ttc.ttc_rel = rel;
144
0
  ttc.ttc_values = toast_values;
145
0
  ttc.ttc_isnull = toast_isnull;
146
0
  if (oldtup == NULL)
147
0
  {
148
0
    ttc.ttc_oldvalues = NULL;
149
0
    ttc.ttc_oldisnull = NULL;
150
0
  }
151
0
  else
152
0
  {
153
0
    ttc.ttc_oldvalues = toast_oldvalues;
154
0
    ttc.ttc_oldisnull = toast_oldisnull;
155
0
  }
156
0
  ttc.ttc_attr = toast_attr;
157
0
  toast_tuple_init(&ttc);
158
159
  /* ----------
160
   * Compress and/or save external until data fits into target length
161
   *
162
   *  1: Inline compress attributes with attstorage EXTENDED, and store very
163
   *     large attributes with attstorage EXTENDED or EXTERNAL external
164
   *     immediately
165
   *  2: Store attributes with attstorage EXTENDED or EXTERNAL external
166
   *  3: Inline compress attributes with attstorage MAIN
167
   *  4: Store attributes with attstorage MAIN external
168
   * ----------
169
   */
170
171
  /* compute header overhead --- this should match heap_form_tuple() */
172
0
  hoff = SizeofHeapTupleHeader;
173
0
  if ((ttc.ttc_flags & TOAST_HAS_NULLS) != 0)
174
0
    hoff += BITMAPLEN(numAttrs);
175
0
  hoff = MAXALIGN(hoff);
176
  /* now convert to a limit on the tuple data size */
177
0
  maxDataLen = RelationGetToastTupleTarget(rel, TOAST_TUPLE_TARGET) - hoff;
178
179
  /*
180
   * Look for attributes with attstorage EXTENDED to compress.  Also find
181
   * large attributes with attstorage EXTENDED or EXTERNAL, and store them
182
   * external.
183
   */
184
0
  while (heap_compute_data_size(tupleDesc,
185
0
                  toast_values, toast_isnull,
186
0
                  true) > maxDataLen)
187
0
  {
188
0
    int     biggest_attno;
189
190
0
    biggest_attno = toast_tuple_find_biggest_attribute(&ttc, true, false);
191
0
    if (biggest_attno < 0)
192
0
      break;
193
194
    /*
195
     * Attempt to compress it inline, if it has attstorage EXTENDED
196
     */
197
0
    if (TupleDescAttr(tupleDesc, biggest_attno)->attstorage == TYPSTORAGE_EXTENDED)
198
0
      toast_tuple_try_compression(&ttc, biggest_attno);
199
0
    else
200
0
    {
201
      /*
202
       * has attstorage EXTERNAL, ignore on subsequent compression
203
       * passes
204
       */
205
0
      toast_attr[biggest_attno].tai_colflags |= TOASTCOL_INCOMPRESSIBLE;
206
0
    }
207
208
    /*
209
     * If this value is by itself more than maxDataLen (after compression
210
     * if any), push it out to the toast table immediately, if possible.
211
     * This avoids uselessly compressing other fields in the common case
212
     * where we have one long field and several short ones.
213
     *
214
     * XXX maybe the threshold should be less than maxDataLen?
215
     */
216
0
    if (toast_attr[biggest_attno].tai_size > maxDataLen &&
217
0
      rel->rd_rel->reltoastrelid != InvalidOid)
218
0
      toast_tuple_externalize(&ttc, biggest_attno, options);
219
0
  }
220
221
  /*
222
   * Second we look for attributes of attstorage EXTENDED or EXTERNAL that
223
   * are still inline, and make them external.  But skip this if there's no
224
   * toast table to push them to.
225
   */
226
0
  while (heap_compute_data_size(tupleDesc,
227
0
                  toast_values, toast_isnull,
228
0
                  true) > maxDataLen &&
229
0
       rel->rd_rel->reltoastrelid != InvalidOid)
230
0
  {
231
0
    int     biggest_attno;
232
233
0
    biggest_attno = toast_tuple_find_biggest_attribute(&ttc, false, false);
234
0
    if (biggest_attno < 0)
235
0
      break;
236
0
    toast_tuple_externalize(&ttc, biggest_attno, options);
237
0
  }
238
239
  /*
240
   * Round 3 - this time we take attributes with storage MAIN into
241
   * compression
242
   */
243
0
  while (heap_compute_data_size(tupleDesc,
244
0
                  toast_values, toast_isnull,
245
0
                  true) > maxDataLen)
246
0
  {
247
0
    int     biggest_attno;
248
249
0
    biggest_attno = toast_tuple_find_biggest_attribute(&ttc, true, true);
250
0
    if (biggest_attno < 0)
251
0
      break;
252
253
0
    toast_tuple_try_compression(&ttc, biggest_attno);
254
0
  }
255
256
  /*
257
   * Finally we store attributes of type MAIN externally.  At this point we
258
   * increase the target tuple size, so that MAIN attributes aren't stored
259
   * externally unless really necessary.
260
   */
261
0
  maxDataLen = TOAST_TUPLE_TARGET_MAIN - hoff;
262
263
0
  while (heap_compute_data_size(tupleDesc,
264
0
                  toast_values, toast_isnull,
265
0
                  true) > maxDataLen &&
266
0
       rel->rd_rel->reltoastrelid != InvalidOid)
267
0
  {
268
0
    int     biggest_attno;
269
270
0
    biggest_attno = toast_tuple_find_biggest_attribute(&ttc, false, true);
271
0
    if (biggest_attno < 0)
272
0
      break;
273
274
0
    toast_tuple_externalize(&ttc, biggest_attno, options);
275
0
  }
276
277
  /*
278
   * In the case we toasted any values, we need to build a new heap tuple
279
   * with the changed values.
280
   */
281
0
  if ((ttc.ttc_flags & TOAST_NEEDS_CHANGE) != 0)
282
0
  {
283
0
    HeapTupleHeader olddata = newtup->t_data;
284
0
    HeapTupleHeader new_data;
285
0
    int32   new_header_len;
286
0
    int32   new_data_len;
287
0
    int32   new_tuple_len;
288
289
    /*
290
     * Calculate the new size of the tuple.
291
     *
292
     * Note: we used to assume here that the old tuple's t_hoff must equal
293
     * the new_header_len value, but that was incorrect.  The old tuple
294
     * might have a smaller-than-current natts, if there's been an ALTER
295
     * TABLE ADD COLUMN since it was stored; and that would lead to a
296
     * different conclusion about the size of the null bitmap, or even
297
     * whether there needs to be one at all.
298
     */
299
0
    new_header_len = SizeofHeapTupleHeader;
300
0
    if ((ttc.ttc_flags & TOAST_HAS_NULLS) != 0)
301
0
      new_header_len += BITMAPLEN(numAttrs);
302
0
    new_header_len = MAXALIGN(new_header_len);
303
0
    new_data_len = heap_compute_data_size(tupleDesc,
304
0
                        toast_values, toast_isnull,
305
0
                        false);
306
0
    new_tuple_len = new_header_len + new_data_len;
307
308
    /*
309
     * Allocate and zero the space needed, and fill HeapTupleData fields.
310
     */
311
0
    result_tuple = (HeapTuple) palloc0(HEAPTUPLESIZE + new_tuple_len);
312
0
    result_tuple->t_len = new_tuple_len;
313
0
    result_tuple->t_self = newtup->t_self;
314
0
    result_tuple->t_tableOid = newtup->t_tableOid;
315
0
    new_data = (HeapTupleHeader) ((char *) result_tuple + HEAPTUPLESIZE);
316
0
    result_tuple->t_data = new_data;
317
318
    /*
319
     * Copy the existing tuple header, but adjust natts and t_hoff.
320
     */
321
0
    memcpy(new_data, olddata, SizeofHeapTupleHeader);
322
0
    HeapTupleHeaderSetNatts(new_data, numAttrs);
323
0
    new_data->t_hoff = new_header_len;
324
325
    /* Copy over the data, and fill the null bitmap if needed */
326
0
    heap_fill_tuple(tupleDesc,
327
0
            toast_values,
328
0
            toast_isnull,
329
0
            (char *) new_data + new_header_len,
330
0
            new_data_len,
331
0
            &(new_data->t_infomask),
332
0
            ((ttc.ttc_flags & TOAST_HAS_NULLS) != 0) ?
333
0
            new_data->t_bits : NULL);
334
0
  }
335
0
  else
336
0
    result_tuple = newtup;
337
338
0
  toast_tuple_cleanup(&ttc);
339
340
0
  return result_tuple;
341
0
}
342
343
344
/* ----------
345
 * toast_flatten_tuple -
346
 *
347
 *  "Flatten" a tuple to contain no out-of-line toasted fields.
348
 *  (This does not eliminate compressed or short-header datums.)
349
 *
350
 *  Note: we expect the caller already checked HeapTupleHasExternal(tup),
351
 *  so there is no need for a short-circuit path.
352
 * ----------
353
 */
354
HeapTuple
355
toast_flatten_tuple(HeapTuple tup, TupleDesc tupleDesc)
356
0
{
357
0
  HeapTuple new_tuple;
358
0
  int     numAttrs = tupleDesc->natts;
359
0
  int     i;
360
0
  Datum   toast_values[MaxTupleAttributeNumber];
361
0
  bool    toast_isnull[MaxTupleAttributeNumber];
362
0
  bool    toast_free[MaxTupleAttributeNumber];
363
364
  /*
365
   * Break down the tuple into fields.
366
   */
367
0
  Assert(numAttrs <= MaxTupleAttributeNumber);
368
0
  heap_deform_tuple(tup, tupleDesc, toast_values, toast_isnull);
369
370
0
  memset(toast_free, 0, numAttrs * sizeof(bool));
371
372
0
  for (i = 0; i < numAttrs; i++)
373
0
  {
374
    /*
375
     * Look at non-null varlena attributes
376
     */
377
0
    if (!toast_isnull[i] && TupleDescCompactAttr(tupleDesc, i)->attlen == -1)
378
0
    {
379
0
      varlena    *new_value;
380
381
0
      new_value = (varlena *) DatumGetPointer(toast_values[i]);
382
0
      if (VARATT_IS_EXTERNAL(new_value))
383
0
      {
384
0
        new_value = detoast_external_attr(new_value);
385
0
        toast_values[i] = PointerGetDatum(new_value);
386
0
        toast_free[i] = true;
387
0
      }
388
0
    }
389
0
  }
390
391
  /*
392
   * Form the reconfigured tuple.
393
   */
394
0
  new_tuple = heap_form_tuple(tupleDesc, toast_values, toast_isnull);
395
396
  /*
397
   * Be sure to copy the tuple's identity fields.  We also make a point of
398
   * copying visibility info, just in case anybody looks at those fields in
399
   * a syscache entry.
400
   */
401
0
  new_tuple->t_self = tup->t_self;
402
0
  new_tuple->t_tableOid = tup->t_tableOid;
403
404
0
  new_tuple->t_data->t_choice = tup->t_data->t_choice;
405
0
  new_tuple->t_data->t_ctid = tup->t_data->t_ctid;
406
0
  new_tuple->t_data->t_infomask &= ~HEAP_XACT_MASK;
407
0
  new_tuple->t_data->t_infomask |=
408
0
    tup->t_data->t_infomask & HEAP_XACT_MASK;
409
0
  new_tuple->t_data->t_infomask2 &= ~HEAP2_XACT_MASK;
410
0
  new_tuple->t_data->t_infomask2 |=
411
0
    tup->t_data->t_infomask2 & HEAP2_XACT_MASK;
412
413
  /*
414
   * If the input tuple is a datum tuple, need to set its length (which was
415
   * reset by the t_choice assignment above).
416
   *
417
   * If it was versioned and had fewer attributes than the descriptor, it
418
   * will now be full-width and as part of that might have a different
419
   * version.
420
   */
421
0
  if (HeapTupleHeaderIsDatum(tup->t_data))
422
0
    heap_tuple_set_datum(new_tuple->t_data, tupleDesc, new_tuple->t_len);
423
424
  /*
425
   * Free allocated temp values
426
   */
427
0
  for (i = 0; i < numAttrs; i++)
428
0
    if (toast_free[i])
429
0
      pfree(DatumGetPointer(toast_values[i]));
430
431
0
  return new_tuple;
432
0
}
433
434
435
/* ----------
436
 * toast_flatten_tuple_to_datum -
437
 *
438
 *  "Flatten" a tuple containing out-of-line toasted fields into a Datum.
439
 *  The result is always palloc'd in the current memory context.
440
 *
441
 *  We have a general rule that Datums of container types (rows, arrays,
442
 *  ranges, etc) must not contain any external TOAST pointers.  Without
443
 *  this rule, we'd have to look inside each Datum when preparing a tuple
444
 *  for storage, which would be expensive and would fail to extend cleanly
445
 *  to new sorts of container types.
446
 *
447
 *  However, we don't want to say that tuples represented as HeapTuples
448
 *  can't contain toasted fields, so instead this routine should be called
449
 *  when such a HeapTuple is being converted into a Datum.
450
 *
451
 *  While we're at it, we decompress any compressed fields too.  This is not
452
 *  necessary for correctness, but reflects an expectation that compression
453
 *  will be more effective if applied to the whole tuple not individual
454
 *  fields.  We are not so concerned about that that we want to deconstruct
455
 *  and reconstruct tuples just to get rid of compressed fields, however.
456
 *  So callers typically won't call this unless they see that the tuple has
457
 *  at least one external field.
458
 *
459
 *  On the other hand, in-line short-header varlena fields are left alone.
460
 *  If we "untoasted" them here, they'd just get changed back to short-header
461
 *  format anyway within heap_fill_tuple.
462
 * ----------
463
 */
464
Datum
465
toast_flatten_tuple_to_datum(HeapTupleHeader tup,
466
               uint32 tup_len,
467
               TupleDesc tupleDesc)
468
0
{
469
0
  HeapTupleHeader new_data;
470
0
  int32   new_header_len;
471
0
  int32   new_data_len;
472
0
  int32   new_tuple_len;
473
0
  HeapTupleData tmptup;
474
0
  int     numAttrs = tupleDesc->natts;
475
0
  int     i;
476
0
  bool    has_nulls = false;
477
0
  Datum   toast_values[MaxTupleAttributeNumber];
478
0
  bool    toast_isnull[MaxTupleAttributeNumber];
479
0
  bool    toast_free[MaxTupleAttributeNumber];
480
481
  /* Build a temporary HeapTuple control structure */
482
0
  tmptup.t_len = tup_len;
483
0
  ItemPointerSetInvalid(&(tmptup.t_self));
484
0
  tmptup.t_tableOid = InvalidOid;
485
0
  tmptup.t_data = tup;
486
487
  /*
488
   * Break down the tuple into fields.
489
   */
490
0
  Assert(numAttrs <= MaxTupleAttributeNumber);
491
0
  heap_deform_tuple(&tmptup, tupleDesc, toast_values, toast_isnull);
492
493
0
  memset(toast_free, 0, numAttrs * sizeof(bool));
494
495
0
  for (i = 0; i < numAttrs; i++)
496
0
  {
497
    /*
498
     * Look at non-null varlena attributes
499
     */
500
0
    if (toast_isnull[i])
501
0
      has_nulls = true;
502
0
    else if (TupleDescCompactAttr(tupleDesc, i)->attlen == -1)
503
0
    {
504
0
      varlena    *new_value;
505
506
0
      new_value = (varlena *) DatumGetPointer(toast_values[i]);
507
0
      if (VARATT_IS_EXTERNAL(new_value) ||
508
0
        VARATT_IS_COMPRESSED(new_value))
509
0
      {
510
0
        new_value = detoast_attr(new_value);
511
0
        toast_values[i] = PointerGetDatum(new_value);
512
0
        toast_free[i] = true;
513
0
      }
514
0
    }
515
0
  }
516
517
  /*
518
   * Calculate the new size of the tuple.
519
   *
520
   * This should match the reconstruction code in
521
   * heap_toast_insert_or_update.
522
   */
523
0
  new_header_len = SizeofHeapTupleHeader;
524
0
  if (has_nulls)
525
0
    new_header_len += BITMAPLEN(numAttrs);
526
0
  new_header_len = MAXALIGN(new_header_len);
527
0
  new_data_len = heap_compute_data_size(tupleDesc,
528
0
                      toast_values, toast_isnull,
529
0
                      false);
530
0
  new_tuple_len = new_header_len + new_data_len;
531
532
0
  new_data = (HeapTupleHeader) palloc0(new_tuple_len);
533
534
  /*
535
   * Copy the existing tuple header, but adjust natts and t_hoff.
536
   */
537
0
  memcpy(new_data, tup, SizeofHeapTupleHeader);
538
0
  HeapTupleHeaderSetNatts(new_data, numAttrs);
539
0
  new_data->t_hoff = new_header_len;
540
541
  /* Set the composite-Datum header fields correctly */
542
0
  heap_tuple_set_datum(new_data, tupleDesc, new_tuple_len);
543
544
  /* Copy over the data, and fill the null bitmap if needed */
545
0
  heap_fill_tuple(tupleDesc,
546
0
          toast_values,
547
0
          toast_isnull,
548
0
          (char *) new_data + new_header_len,
549
0
          new_data_len,
550
0
          &(new_data->t_infomask),
551
0
          has_nulls ? new_data->t_bits : NULL);
552
553
  /*
554
   * Free allocated temp values
555
   */
556
0
  for (i = 0; i < numAttrs; i++)
557
0
    if (toast_free[i])
558
0
      pfree(DatumGetPointer(toast_values[i]));
559
560
0
  return PointerGetDatum(new_data);
561
0
}
562
563
564
/* ----------
565
 * toast_build_flattened_tuple -
566
 *
567
 *  Build a tuple containing no out-of-line toasted fields.
568
 *  (This does not eliminate compressed or short-header datums.)
569
 *
570
 *  This is essentially just like heap_form_tuple, except that it will
571
 *  expand any external-data pointers beforehand.
572
 *
573
 *  It's not very clear whether it would be preferable to decompress
574
 *  in-line compressed datums while at it.  For now, we don't.
575
 * ----------
576
 */
577
HeapTuple
578
toast_build_flattened_tuple(TupleDesc tupleDesc,
579
              const Datum *values,
580
              const bool *isnull)
581
0
{
582
0
  HeapTuple new_tuple;
583
0
  int     numAttrs = tupleDesc->natts;
584
0
  int     num_to_free;
585
0
  int     i;
586
0
  Datum   new_values[MaxTupleAttributeNumber];
587
0
  void     *freeable_values[MaxTupleAttributeNumber];
588
589
  /*
590
   * We can pass the caller's isnull array directly to heap_form_tuple, but
591
   * we potentially need to modify the values array.
592
   */
593
0
  Assert(numAttrs <= MaxTupleAttributeNumber);
594
0
  memcpy(new_values, values, numAttrs * sizeof(Datum));
595
596
0
  num_to_free = 0;
597
0
  for (i = 0; i < numAttrs; i++)
598
0
  {
599
    /*
600
     * Look at non-null varlena attributes
601
     */
602
0
    if (!isnull[i] && TupleDescCompactAttr(tupleDesc, i)->attlen == -1)
603
0
    {
604
0
      varlena    *new_value;
605
606
0
      new_value = (varlena *) DatumGetPointer(new_values[i]);
607
0
      if (VARATT_IS_EXTERNAL(new_value))
608
0
      {
609
0
        new_value = detoast_external_attr(new_value);
610
0
        new_values[i] = PointerGetDatum(new_value);
611
0
        freeable_values[num_to_free++] = new_value;
612
0
      }
613
0
    }
614
0
  }
615
616
  /*
617
   * Form the reconfigured tuple.
618
   */
619
0
  new_tuple = heap_form_tuple(tupleDesc, new_values, isnull);
620
621
  /*
622
   * Free allocated temp values
623
   */
624
0
  for (i = 0; i < num_to_free; i++)
625
0
    pfree(freeable_values[i]);
626
627
0
  return new_tuple;
628
0
}
629
630
/*
631
 * Fetch a TOAST slice from a heap table.
632
 *
633
 * toastrel is the relation from which chunks are to be fetched.
634
 * valueid identifies the TOAST value from which chunks are being fetched.
635
 * attrsize is the total size of the TOAST value.
636
 * sliceoffset is the byte offset within the TOAST value from which to fetch.
637
 * slicelength is the number of bytes to be fetched from the TOAST value.
638
 * result is the varlena into which the results should be written.
639
 */
640
void
641
heap_fetch_toast_slice(Relation toastrel, Oid8 valueid, int32 attrsize,
642
             int32 sliceoffset, int32 slicelength,
643
             varlena *result)
644
0
{
645
0
  Relation   *toastidxs;
646
0
  ScanKeyData toastkey[3];
647
0
  TupleDesc toasttupDesc = toastrel->rd_att;
648
0
  int     nscankeys;
649
0
  SysScanDesc toastscan;
650
0
  HeapTuple ttup;
651
0
  int32   expectedchunk;
652
0
  int32   totalchunks;
653
0
  int     startchunk;
654
0
  int     endchunk;
655
0
  int     num_indexes;
656
0
  int     validIndex;
657
0
  int32   max_chunk_size;
658
0
  Oid     toast_typid;
659
660
  /* Look for the valid index of toast relation */
661
0
  validIndex = toast_open_indexes(toastrel,
662
0
                  AccessShareLock,
663
0
                  &toastidxs,
664
0
                  &num_indexes);
665
666
0
  toast_typid = TupleDescAttr(toastrel->rd_att, 0)->atttypid;
667
0
  max_chunk_size = TOAST_MAX_CHUNK_SIZE(toast_typid);
668
669
0
  totalchunks = ((attrsize - 1) / max_chunk_size) + 1;
670
0
  startchunk = sliceoffset / max_chunk_size;
671
0
  endchunk = (sliceoffset + slicelength - 1) / max_chunk_size;
672
0
  Assert(endchunk <= totalchunks);
673
674
  /* Set up a scan key to fetch from the index. */
675
0
  toast_valueid_scankey_init(&toastkey[0], toast_typid, valueid);
676
677
  /*
678
   * No additional condition if fetching all chunks. Otherwise, use an
679
   * equality condition for one chunk, and a range condition otherwise.
680
   */
681
0
  if (startchunk == 0 && endchunk == totalchunks - 1)
682
0
    nscankeys = 1;
683
0
  else if (startchunk == endchunk)
684
0
  {
685
0
    ScanKeyInit(&toastkey[1],
686
0
          (AttrNumber) 2,
687
0
          BTEqualStrategyNumber, F_INT4EQ,
688
0
          Int32GetDatum(startchunk));
689
0
    nscankeys = 2;
690
0
  }
691
0
  else
692
0
  {
693
0
    ScanKeyInit(&toastkey[1],
694
0
          (AttrNumber) 2,
695
0
          BTGreaterEqualStrategyNumber, F_INT4GE,
696
0
          Int32GetDatum(startchunk));
697
0
    ScanKeyInit(&toastkey[2],
698
0
          (AttrNumber) 2,
699
0
          BTLessEqualStrategyNumber, F_INT4LE,
700
0
          Int32GetDatum(endchunk));
701
0
    nscankeys = 3;
702
0
  }
703
704
  /* Prepare for scan */
705
0
  toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
706
0
                       get_toast_snapshot(), nscankeys, toastkey);
707
708
  /*
709
   * Read the chunks by index
710
   *
711
   * The index is on (valueid, chunkidx) so they will come in order
712
   */
713
0
  expectedchunk = startchunk;
714
0
  while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
715
0
  {
716
0
    int32   curchunk;
717
0
    Pointer   chunk;
718
0
    bool    isnull;
719
0
    char     *chunkdata;
720
0
    int32   chunksize;
721
0
    int32   expected_size;
722
0
    int32   chcpystrt;
723
0
    int32   chcpyend;
724
725
    /*
726
     * Have a chunk, extract the sequence number and the data
727
     */
728
0
    curchunk = DatumGetInt32(fastgetattr(ttup, 2, toasttupDesc, &isnull));
729
0
    Assert(!isnull);
730
0
    chunk = DatumGetPointer(fastgetattr(ttup, 3, toasttupDesc, &isnull));
731
0
    Assert(!isnull);
732
0
    if (!VARATT_IS_EXTENDED(chunk))
733
0
    {
734
0
      chunksize = VARSIZE(chunk) - VARHDRSZ;
735
0
      chunkdata = VARDATA(chunk);
736
0
    }
737
0
    else if (VARATT_IS_SHORT(chunk))
738
0
    {
739
      /* could happen due to heap_form_tuple doing its thing */
740
0
      chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT;
741
0
      chunkdata = VARDATA_SHORT(chunk);
742
0
    }
743
0
    else
744
0
    {
745
      /* should never happen */
746
0
      elog(ERROR, "found toasted toast chunk for toast value %" PRIu64 " in %s",
747
0
         valueid, RelationGetRelationName(toastrel));
748
0
      chunksize = 0;    /* keep compiler quiet */
749
0
      chunkdata = NULL;
750
0
    }
751
752
    /*
753
     * Some checks on the data we've found
754
     */
755
0
    if (curchunk != expectedchunk)
756
0
      ereport(ERROR,
757
0
          (errcode(ERRCODE_DATA_CORRUPTED),
758
0
           errmsg_internal("unexpected chunk number %d (expected %d) for toast value %" PRIu64 " in %s",
759
0
                   curchunk, expectedchunk, valueid,
760
0
                   RelationGetRelationName(toastrel))));
761
0
    if (curchunk > endchunk)
762
0
      ereport(ERROR,
763
0
          (errcode(ERRCODE_DATA_CORRUPTED),
764
0
           errmsg_internal("unexpected chunk number %d (out of range %d..%d) for toast value %" PRIu64 " in %s",
765
0
                   curchunk,
766
0
                   startchunk, endchunk, valueid,
767
0
                   RelationGetRelationName(toastrel))));
768
0
    expected_size = curchunk < totalchunks - 1 ? max_chunk_size
769
0
      : attrsize - ((totalchunks - 1) * max_chunk_size);
770
0
    if (chunksize != expected_size)
771
0
      ereport(ERROR,
772
0
          (errcode(ERRCODE_DATA_CORRUPTED),
773
0
           errmsg_internal("unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %" PRIu64 " in %s",
774
0
                   chunksize, expected_size,
775
0
                   curchunk, totalchunks, valueid,
776
0
                   RelationGetRelationName(toastrel))));
777
778
    /*
779
     * Copy the data into proper place in our result
780
     */
781
0
    chcpystrt = 0;
782
0
    chcpyend = chunksize - 1;
783
0
    if (curchunk == startchunk)
784
0
      chcpystrt = sliceoffset % max_chunk_size;
785
0
    if (curchunk == endchunk)
786
0
      chcpyend = (sliceoffset + slicelength - 1) % max_chunk_size;
787
788
0
    memcpy(VARDATA(result) +
789
0
         (curchunk * max_chunk_size - sliceoffset) + chcpystrt,
790
0
         chunkdata + chcpystrt,
791
0
         (chcpyend - chcpystrt) + 1);
792
793
0
    expectedchunk++;
794
0
  }
795
796
  /*
797
   * Final checks that we successfully fetched the datum
798
   */
799
0
  if (expectedchunk != (endchunk + 1))
800
0
    ereport(ERROR,
801
0
        (errcode(ERRCODE_DATA_CORRUPTED),
802
0
         errmsg_internal("missing chunk number %d for toast value %" PRIu64 " in %s",
803
0
                 expectedchunk, valueid,
804
0
                 RelationGetRelationName(toastrel))));
805
806
  /* End scan and close indexes. */
807
0
  systable_endscan_ordered(toastscan);
808
0
  toast_close_indexes(toastidxs, num_indexes, AccessShareLock);
809
0
}