Coverage Report

Created: 2025-07-03 06:49

/src/postgres/src/backend/access/heap/heaptoast.c
Line
Count
Source (jump to first uncovered line)
1
/*-------------------------------------------------------------------------
2
 *
3
 * heaptoast.c
4
 *    Heap-specific definitions for external and compressed storage
5
 *    of variable size attributes.
6
 *
7
 * Copyright (c) 2000-2025, 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
              int 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) > maxDataLen)
186
0
  {
187
0
    int     biggest_attno;
188
189
0
    biggest_attno = toast_tuple_find_biggest_attribute(&ttc, true, false);
190
0
    if (biggest_attno < 0)
191
0
      break;
192
193
    /*
194
     * Attempt to compress it inline, if it has attstorage EXTENDED
195
     */
196
0
    if (TupleDescAttr(tupleDesc, biggest_attno)->attstorage == TYPSTORAGE_EXTENDED)
197
0
      toast_tuple_try_compression(&ttc, biggest_attno);
198
0
    else
199
0
    {
200
      /*
201
       * has attstorage EXTERNAL, ignore on subsequent compression
202
       * passes
203
       */
204
0
      toast_attr[biggest_attno].tai_colflags |= TOASTCOL_INCOMPRESSIBLE;
205
0
    }
206
207
    /*
208
     * If this value is by itself more than maxDataLen (after compression
209
     * if any), push it out to the toast table immediately, if possible.
210
     * This avoids uselessly compressing other fields in the common case
211
     * where we have one long field and several short ones.
212
     *
213
     * XXX maybe the threshold should be less than maxDataLen?
214
     */
215
0
    if (toast_attr[biggest_attno].tai_size > maxDataLen &&
216
0
      rel->rd_rel->reltoastrelid != InvalidOid)
217
0
      toast_tuple_externalize(&ttc, biggest_attno, options);
218
0
  }
219
220
  /*
221
   * Second we look for attributes of attstorage EXTENDED or EXTERNAL that
222
   * are still inline, and make them external.  But skip this if there's no
223
   * toast table to push them to.
224
   */
225
0
  while (heap_compute_data_size(tupleDesc,
226
0
                  toast_values, toast_isnull) > maxDataLen &&
227
0
       rel->rd_rel->reltoastrelid != InvalidOid)
228
0
  {
229
0
    int     biggest_attno;
230
231
0
    biggest_attno = toast_tuple_find_biggest_attribute(&ttc, false, false);
232
0
    if (biggest_attno < 0)
233
0
      break;
234
0
    toast_tuple_externalize(&ttc, biggest_attno, options);
235
0
  }
236
237
  /*
238
   * Round 3 - this time we take attributes with storage MAIN into
239
   * compression
240
   */
241
0
  while (heap_compute_data_size(tupleDesc,
242
0
                  toast_values, toast_isnull) > maxDataLen)
243
0
  {
244
0
    int     biggest_attno;
245
246
0
    biggest_attno = toast_tuple_find_biggest_attribute(&ttc, true, true);
247
0
    if (biggest_attno < 0)
248
0
      break;
249
250
0
    toast_tuple_try_compression(&ttc, biggest_attno);
251
0
  }
252
253
  /*
254
   * Finally we store attributes of type MAIN externally.  At this point we
255
   * increase the target tuple size, so that MAIN attributes aren't stored
256
   * externally unless really necessary.
257
   */
258
0
  maxDataLen = TOAST_TUPLE_TARGET_MAIN - hoff;
259
260
0
  while (heap_compute_data_size(tupleDesc,
261
0
                  toast_values, toast_isnull) > maxDataLen &&
262
0
       rel->rd_rel->reltoastrelid != InvalidOid)
263
0
  {
264
0
    int     biggest_attno;
265
266
0
    biggest_attno = toast_tuple_find_biggest_attribute(&ttc, false, true);
267
0
    if (biggest_attno < 0)
268
0
      break;
269
270
0
    toast_tuple_externalize(&ttc, biggest_attno, options);
271
0
  }
272
273
  /*
274
   * In the case we toasted any values, we need to build a new heap tuple
275
   * with the changed values.
276
   */
277
0
  if ((ttc.ttc_flags & TOAST_NEEDS_CHANGE) != 0)
278
0
  {
279
0
    HeapTupleHeader olddata = newtup->t_data;
280
0
    HeapTupleHeader new_data;
281
0
    int32   new_header_len;
282
0
    int32   new_data_len;
283
0
    int32   new_tuple_len;
284
285
    /*
286
     * Calculate the new size of the tuple.
287
     *
288
     * Note: we used to assume here that the old tuple's t_hoff must equal
289
     * the new_header_len value, but that was incorrect.  The old tuple
290
     * might have a smaller-than-current natts, if there's been an ALTER
291
     * TABLE ADD COLUMN since it was stored; and that would lead to a
292
     * different conclusion about the size of the null bitmap, or even
293
     * whether there needs to be one at all.
294
     */
295
0
    new_header_len = SizeofHeapTupleHeader;
296
0
    if ((ttc.ttc_flags & TOAST_HAS_NULLS) != 0)
297
0
      new_header_len += BITMAPLEN(numAttrs);
298
0
    new_header_len = MAXALIGN(new_header_len);
299
0
    new_data_len = heap_compute_data_size(tupleDesc,
300
0
                        toast_values, toast_isnull);
301
0
    new_tuple_len = new_header_len + new_data_len;
302
303
    /*
304
     * Allocate and zero the space needed, and fill HeapTupleData fields.
305
     */
306
0
    result_tuple = (HeapTuple) palloc0(HEAPTUPLESIZE + new_tuple_len);
307
0
    result_tuple->t_len = new_tuple_len;
308
0
    result_tuple->t_self = newtup->t_self;
309
0
    result_tuple->t_tableOid = newtup->t_tableOid;
310
0
    new_data = (HeapTupleHeader) ((char *) result_tuple + HEAPTUPLESIZE);
311
0
    result_tuple->t_data = new_data;
312
313
    /*
314
     * Copy the existing tuple header, but adjust natts and t_hoff.
315
     */
316
0
    memcpy(new_data, olddata, SizeofHeapTupleHeader);
317
0
    HeapTupleHeaderSetNatts(new_data, numAttrs);
318
0
    new_data->t_hoff = new_header_len;
319
320
    /* Copy over the data, and fill the null bitmap if needed */
321
0
    heap_fill_tuple(tupleDesc,
322
0
            toast_values,
323
0
            toast_isnull,
324
0
            (char *) new_data + new_header_len,
325
0
            new_data_len,
326
0
            &(new_data->t_infomask),
327
0
            ((ttc.ttc_flags & TOAST_HAS_NULLS) != 0) ?
328
0
            new_data->t_bits : NULL);
329
0
  }
330
0
  else
331
0
    result_tuple = newtup;
332
333
0
  toast_tuple_cleanup(&ttc);
334
335
0
  return result_tuple;
336
0
}
337
338
339
/* ----------
340
 * toast_flatten_tuple -
341
 *
342
 *  "Flatten" a tuple to contain no out-of-line toasted fields.
343
 *  (This does not eliminate compressed or short-header datums.)
344
 *
345
 *  Note: we expect the caller already checked HeapTupleHasExternal(tup),
346
 *  so there is no need for a short-circuit path.
347
 * ----------
348
 */
349
HeapTuple
350
toast_flatten_tuple(HeapTuple tup, TupleDesc tupleDesc)
351
0
{
352
0
  HeapTuple new_tuple;
353
0
  int     numAttrs = tupleDesc->natts;
354
0
  int     i;
355
0
  Datum   toast_values[MaxTupleAttributeNumber];
356
0
  bool    toast_isnull[MaxTupleAttributeNumber];
357
0
  bool    toast_free[MaxTupleAttributeNumber];
358
359
  /*
360
   * Break down the tuple into fields.
361
   */
362
0
  Assert(numAttrs <= MaxTupleAttributeNumber);
363
0
  heap_deform_tuple(tup, tupleDesc, toast_values, toast_isnull);
364
365
0
  memset(toast_free, 0, numAttrs * sizeof(bool));
366
367
0
  for (i = 0; i < numAttrs; i++)
368
0
  {
369
    /*
370
     * Look at non-null varlena attributes
371
     */
372
0
    if (!toast_isnull[i] && TupleDescCompactAttr(tupleDesc, i)->attlen == -1)
373
0
    {
374
0
      struct varlena *new_value;
375
376
0
      new_value = (struct varlena *) DatumGetPointer(toast_values[i]);
377
0
      if (VARATT_IS_EXTERNAL(new_value))
378
0
      {
379
0
        new_value = detoast_external_attr(new_value);
380
0
        toast_values[i] = PointerGetDatum(new_value);
381
0
        toast_free[i] = true;
382
0
      }
383
0
    }
384
0
  }
385
386
  /*
387
   * Form the reconfigured tuple.
388
   */
389
0
  new_tuple = heap_form_tuple(tupleDesc, toast_values, toast_isnull);
390
391
  /*
392
   * Be sure to copy the tuple's identity fields.  We also make a point of
393
   * copying visibility info, just in case anybody looks at those fields in
394
   * a syscache entry.
395
   */
396
0
  new_tuple->t_self = tup->t_self;
397
0
  new_tuple->t_tableOid = tup->t_tableOid;
398
399
0
  new_tuple->t_data->t_choice = tup->t_data->t_choice;
400
0
  new_tuple->t_data->t_ctid = tup->t_data->t_ctid;
401
0
  new_tuple->t_data->t_infomask &= ~HEAP_XACT_MASK;
402
0
  new_tuple->t_data->t_infomask |=
403
0
    tup->t_data->t_infomask & HEAP_XACT_MASK;
404
0
  new_tuple->t_data->t_infomask2 &= ~HEAP2_XACT_MASK;
405
0
  new_tuple->t_data->t_infomask2 |=
406
0
    tup->t_data->t_infomask2 & HEAP2_XACT_MASK;
407
408
  /*
409
   * Free allocated temp values
410
   */
411
0
  for (i = 0; i < numAttrs; i++)
412
0
    if (toast_free[i])
413
0
      pfree(DatumGetPointer(toast_values[i]));
414
415
0
  return new_tuple;
416
0
}
417
418
419
/* ----------
420
 * toast_flatten_tuple_to_datum -
421
 *
422
 *  "Flatten" a tuple containing out-of-line toasted fields into a Datum.
423
 *  The result is always palloc'd in the current memory context.
424
 *
425
 *  We have a general rule that Datums of container types (rows, arrays,
426
 *  ranges, etc) must not contain any external TOAST pointers.  Without
427
 *  this rule, we'd have to look inside each Datum when preparing a tuple
428
 *  for storage, which would be expensive and would fail to extend cleanly
429
 *  to new sorts of container types.
430
 *
431
 *  However, we don't want to say that tuples represented as HeapTuples
432
 *  can't contain toasted fields, so instead this routine should be called
433
 *  when such a HeapTuple is being converted into a Datum.
434
 *
435
 *  While we're at it, we decompress any compressed fields too.  This is not
436
 *  necessary for correctness, but reflects an expectation that compression
437
 *  will be more effective if applied to the whole tuple not individual
438
 *  fields.  We are not so concerned about that that we want to deconstruct
439
 *  and reconstruct tuples just to get rid of compressed fields, however.
440
 *  So callers typically won't call this unless they see that the tuple has
441
 *  at least one external field.
442
 *
443
 *  On the other hand, in-line short-header varlena fields are left alone.
444
 *  If we "untoasted" them here, they'd just get changed back to short-header
445
 *  format anyway within heap_fill_tuple.
446
 * ----------
447
 */
448
Datum
449
toast_flatten_tuple_to_datum(HeapTupleHeader tup,
450
               uint32 tup_len,
451
               TupleDesc tupleDesc)
452
0
{
453
0
  HeapTupleHeader new_data;
454
0
  int32   new_header_len;
455
0
  int32   new_data_len;
456
0
  int32   new_tuple_len;
457
0
  HeapTupleData tmptup;
458
0
  int     numAttrs = tupleDesc->natts;
459
0
  int     i;
460
0
  bool    has_nulls = false;
461
0
  Datum   toast_values[MaxTupleAttributeNumber];
462
0
  bool    toast_isnull[MaxTupleAttributeNumber];
463
0
  bool    toast_free[MaxTupleAttributeNumber];
464
465
  /* Build a temporary HeapTuple control structure */
466
0
  tmptup.t_len = tup_len;
467
0
  ItemPointerSetInvalid(&(tmptup.t_self));
468
0
  tmptup.t_tableOid = InvalidOid;
469
0
  tmptup.t_data = tup;
470
471
  /*
472
   * Break down the tuple into fields.
473
   */
474
0
  Assert(numAttrs <= MaxTupleAttributeNumber);
475
0
  heap_deform_tuple(&tmptup, tupleDesc, toast_values, toast_isnull);
476
477
0
  memset(toast_free, 0, numAttrs * sizeof(bool));
478
479
0
  for (i = 0; i < numAttrs; i++)
480
0
  {
481
    /*
482
     * Look at non-null varlena attributes
483
     */
484
0
    if (toast_isnull[i])
485
0
      has_nulls = true;
486
0
    else if (TupleDescCompactAttr(tupleDesc, i)->attlen == -1)
487
0
    {
488
0
      struct varlena *new_value;
489
490
0
      new_value = (struct varlena *) DatumGetPointer(toast_values[i]);
491
0
      if (VARATT_IS_EXTERNAL(new_value) ||
492
0
        VARATT_IS_COMPRESSED(new_value))
493
0
      {
494
0
        new_value = detoast_attr(new_value);
495
0
        toast_values[i] = PointerGetDatum(new_value);
496
0
        toast_free[i] = true;
497
0
      }
498
0
    }
499
0
  }
500
501
  /*
502
   * Calculate the new size of the tuple.
503
   *
504
   * This should match the reconstruction code in
505
   * heap_toast_insert_or_update.
506
   */
507
0
  new_header_len = SizeofHeapTupleHeader;
508
0
  if (has_nulls)
509
0
    new_header_len += BITMAPLEN(numAttrs);
510
0
  new_header_len = MAXALIGN(new_header_len);
511
0
  new_data_len = heap_compute_data_size(tupleDesc,
512
0
                      toast_values, toast_isnull);
513
0
  new_tuple_len = new_header_len + new_data_len;
514
515
0
  new_data = (HeapTupleHeader) palloc0(new_tuple_len);
516
517
  /*
518
   * Copy the existing tuple header, but adjust natts and t_hoff.
519
   */
520
0
  memcpy(new_data, tup, SizeofHeapTupleHeader);
521
0
  HeapTupleHeaderSetNatts(new_data, numAttrs);
522
0
  new_data->t_hoff = new_header_len;
523
524
  /* Set the composite-Datum header fields correctly */
525
0
  HeapTupleHeaderSetDatumLength(new_data, new_tuple_len);
526
0
  HeapTupleHeaderSetTypeId(new_data, tupleDesc->tdtypeid);
527
0
  HeapTupleHeaderSetTypMod(new_data, tupleDesc->tdtypmod);
528
529
  /* Copy over the data, and fill the null bitmap if needed */
530
0
  heap_fill_tuple(tupleDesc,
531
0
          toast_values,
532
0
          toast_isnull,
533
0
          (char *) new_data + new_header_len,
534
0
          new_data_len,
535
0
          &(new_data->t_infomask),
536
0
          has_nulls ? new_data->t_bits : NULL);
537
538
  /*
539
   * Free allocated temp values
540
   */
541
0
  for (i = 0; i < numAttrs; i++)
542
0
    if (toast_free[i])
543
0
      pfree(DatumGetPointer(toast_values[i]));
544
545
0
  return PointerGetDatum(new_data);
546
0
}
547
548
549
/* ----------
550
 * toast_build_flattened_tuple -
551
 *
552
 *  Build a tuple containing no out-of-line toasted fields.
553
 *  (This does not eliminate compressed or short-header datums.)
554
 *
555
 *  This is essentially just like heap_form_tuple, except that it will
556
 *  expand any external-data pointers beforehand.
557
 *
558
 *  It's not very clear whether it would be preferable to decompress
559
 *  in-line compressed datums while at it.  For now, we don't.
560
 * ----------
561
 */
562
HeapTuple
563
toast_build_flattened_tuple(TupleDesc tupleDesc,
564
              Datum *values,
565
              bool *isnull)
566
0
{
567
0
  HeapTuple new_tuple;
568
0
  int     numAttrs = tupleDesc->natts;
569
0
  int     num_to_free;
570
0
  int     i;
571
0
  Datum   new_values[MaxTupleAttributeNumber];
572
0
  Pointer   freeable_values[MaxTupleAttributeNumber];
573
574
  /*
575
   * We can pass the caller's isnull array directly to heap_form_tuple, but
576
   * we potentially need to modify the values array.
577
   */
578
0
  Assert(numAttrs <= MaxTupleAttributeNumber);
579
0
  memcpy(new_values, values, numAttrs * sizeof(Datum));
580
581
0
  num_to_free = 0;
582
0
  for (i = 0; i < numAttrs; i++)
583
0
  {
584
    /*
585
     * Look at non-null varlena attributes
586
     */
587
0
    if (!isnull[i] && TupleDescCompactAttr(tupleDesc, i)->attlen == -1)
588
0
    {
589
0
      struct varlena *new_value;
590
591
0
      new_value = (struct varlena *) DatumGetPointer(new_values[i]);
592
0
      if (VARATT_IS_EXTERNAL(new_value))
593
0
      {
594
0
        new_value = detoast_external_attr(new_value);
595
0
        new_values[i] = PointerGetDatum(new_value);
596
0
        freeable_values[num_to_free++] = (Pointer) new_value;
597
0
      }
598
0
    }
599
0
  }
600
601
  /*
602
   * Form the reconfigured tuple.
603
   */
604
0
  new_tuple = heap_form_tuple(tupleDesc, new_values, isnull);
605
606
  /*
607
   * Free allocated temp values
608
   */
609
0
  for (i = 0; i < num_to_free; i++)
610
0
    pfree(freeable_values[i]);
611
612
0
  return new_tuple;
613
0
}
614
615
/*
616
 * Fetch a TOAST slice from a heap table.
617
 *
618
 * toastrel is the relation from which chunks are to be fetched.
619
 * valueid identifies the TOAST value from which chunks are being fetched.
620
 * attrsize is the total size of the TOAST value.
621
 * sliceoffset is the byte offset within the TOAST value from which to fetch.
622
 * slicelength is the number of bytes to be fetched from the TOAST value.
623
 * result is the varlena into which the results should be written.
624
 */
625
void
626
heap_fetch_toast_slice(Relation toastrel, Oid valueid, int32 attrsize,
627
             int32 sliceoffset, int32 slicelength,
628
             struct varlena *result)
629
0
{
630
0
  Relation   *toastidxs;
631
0
  ScanKeyData toastkey[3];
632
0
  TupleDesc toasttupDesc = toastrel->rd_att;
633
0
  int     nscankeys;
634
0
  SysScanDesc toastscan;
635
0
  HeapTuple ttup;
636
0
  int32   expectedchunk;
637
0
  int32   totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
638
0
  int     startchunk;
639
0
  int     endchunk;
640
0
  int     num_indexes;
641
0
  int     validIndex;
642
643
  /* Look for the valid index of toast relation */
644
0
  validIndex = toast_open_indexes(toastrel,
645
0
                  AccessShareLock,
646
0
                  &toastidxs,
647
0
                  &num_indexes);
648
649
0
  startchunk = sliceoffset / TOAST_MAX_CHUNK_SIZE;
650
0
  endchunk = (sliceoffset + slicelength - 1) / TOAST_MAX_CHUNK_SIZE;
651
0
  Assert(endchunk <= totalchunks);
652
653
  /* Set up a scan key to fetch from the index. */
654
0
  ScanKeyInit(&toastkey[0],
655
0
        (AttrNumber) 1,
656
0
        BTEqualStrategyNumber, F_OIDEQ,
657
0
        ObjectIdGetDatum(valueid));
658
659
  /*
660
   * No additional condition if fetching all chunks. Otherwise, use an
661
   * equality condition for one chunk, and a range condition otherwise.
662
   */
663
0
  if (startchunk == 0 && endchunk == totalchunks - 1)
664
0
    nscankeys = 1;
665
0
  else if (startchunk == endchunk)
666
0
  {
667
0
    ScanKeyInit(&toastkey[1],
668
0
          (AttrNumber) 2,
669
0
          BTEqualStrategyNumber, F_INT4EQ,
670
0
          Int32GetDatum(startchunk));
671
0
    nscankeys = 2;
672
0
  }
673
0
  else
674
0
  {
675
0
    ScanKeyInit(&toastkey[1],
676
0
          (AttrNumber) 2,
677
0
          BTGreaterEqualStrategyNumber, F_INT4GE,
678
0
          Int32GetDatum(startchunk));
679
0
    ScanKeyInit(&toastkey[2],
680
0
          (AttrNumber) 2,
681
0
          BTLessEqualStrategyNumber, F_INT4LE,
682
0
          Int32GetDatum(endchunk));
683
0
    nscankeys = 3;
684
0
  }
685
686
  /* Prepare for scan */
687
0
  toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
688
0
                       get_toast_snapshot(), nscankeys, toastkey);
689
690
  /*
691
   * Read the chunks by index
692
   *
693
   * The index is on (valueid, chunkidx) so they will come in order
694
   */
695
0
  expectedchunk = startchunk;
696
0
  while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
697
0
  {
698
0
    int32   curchunk;
699
0
    Pointer   chunk;
700
0
    bool    isnull;
701
0
    char     *chunkdata;
702
0
    int32   chunksize;
703
0
    int32   expected_size;
704
0
    int32   chcpystrt;
705
0
    int32   chcpyend;
706
707
    /*
708
     * Have a chunk, extract the sequence number and the data
709
     */
710
0
    curchunk = DatumGetInt32(fastgetattr(ttup, 2, toasttupDesc, &isnull));
711
0
    Assert(!isnull);
712
0
    chunk = DatumGetPointer(fastgetattr(ttup, 3, toasttupDesc, &isnull));
713
0
    Assert(!isnull);
714
0
    if (!VARATT_IS_EXTENDED(chunk))
715
0
    {
716
0
      chunksize = VARSIZE(chunk) - VARHDRSZ;
717
0
      chunkdata = VARDATA(chunk);
718
0
    }
719
0
    else if (VARATT_IS_SHORT(chunk))
720
0
    {
721
      /* could happen due to heap_form_tuple doing its thing */
722
0
      chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT;
723
0
      chunkdata = VARDATA_SHORT(chunk);
724
0
    }
725
0
    else
726
0
    {
727
      /* should never happen */
728
0
      elog(ERROR, "found toasted toast chunk for toast value %u in %s",
729
0
         valueid, RelationGetRelationName(toastrel));
730
0
      chunksize = 0;    /* keep compiler quiet */
731
0
      chunkdata = NULL;
732
0
    }
733
734
    /*
735
     * Some checks on the data we've found
736
     */
737
0
    if (curchunk != expectedchunk)
738
0
      ereport(ERROR,
739
0
          (errcode(ERRCODE_DATA_CORRUPTED),
740
0
           errmsg_internal("unexpected chunk number %d (expected %d) for toast value %u in %s",
741
0
                   curchunk, expectedchunk, valueid,
742
0
                   RelationGetRelationName(toastrel))));
743
0
    if (curchunk > endchunk)
744
0
      ereport(ERROR,
745
0
          (errcode(ERRCODE_DATA_CORRUPTED),
746
0
           errmsg_internal("unexpected chunk number %d (out of range %d..%d) for toast value %u in %s",
747
0
                   curchunk,
748
0
                   startchunk, endchunk, valueid,
749
0
                   RelationGetRelationName(toastrel))));
750
0
    expected_size = curchunk < totalchunks - 1 ? TOAST_MAX_CHUNK_SIZE
751
0
      : attrsize - ((totalchunks - 1) * TOAST_MAX_CHUNK_SIZE);
752
0
    if (chunksize != expected_size)
753
0
      ereport(ERROR,
754
0
          (errcode(ERRCODE_DATA_CORRUPTED),
755
0
           errmsg_internal("unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s",
756
0
                   chunksize, expected_size,
757
0
                   curchunk, totalchunks, valueid,
758
0
                   RelationGetRelationName(toastrel))));
759
760
    /*
761
     * Copy the data into proper place in our result
762
     */
763
0
    chcpystrt = 0;
764
0
    chcpyend = chunksize - 1;
765
0
    if (curchunk == startchunk)
766
0
      chcpystrt = sliceoffset % TOAST_MAX_CHUNK_SIZE;
767
0
    if (curchunk == endchunk)
768
0
      chcpyend = (sliceoffset + slicelength - 1) % TOAST_MAX_CHUNK_SIZE;
769
770
0
    memcpy(VARDATA(result) +
771
0
         (curchunk * TOAST_MAX_CHUNK_SIZE - sliceoffset) + chcpystrt,
772
0
         chunkdata + chcpystrt,
773
0
         (chcpyend - chcpystrt) + 1);
774
775
0
    expectedchunk++;
776
0
  }
777
778
  /*
779
   * Final checks that we successfully fetched the datum
780
   */
781
0
  if (expectedchunk != (endchunk + 1))
782
0
    ereport(ERROR,
783
0
        (errcode(ERRCODE_DATA_CORRUPTED),
784
0
         errmsg_internal("missing chunk number %d for toast value %u in %s",
785
0
                 expectedchunk, valueid,
786
0
                 RelationGetRelationName(toastrel))));
787
788
  /* End scan and close indexes. */
789
0
  systable_endscan_ordered(toastscan);
790
0
  toast_close_indexes(toastidxs, num_indexes, AccessShareLock);
791
0
}