Branch data Line data Source code
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 : 547 : heap_toast_delete(Relation rel, HeapTuple oldtup, bool is_speculative)
44 : : {
45 : : TupleDesc tupleDesc;
46 : : Datum toast_values[MaxHeapAttributeNumber];
47 : : 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 : : Assert(rel->rd_rel->relkind == RELKIND_RELATION ||
54 : : 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 : 547 : tupleDesc = rel->rd_att;
68 : :
69 : : Assert(tupleDesc->natts <= MaxHeapAttributeNumber);
70 : 547 : heap_deform_tuple(oldtup, tupleDesc, toast_values, toast_isnull);
71 : :
72 : : /* Do the real work. */
73 : 547 : toast_delete_external(rel, toast_values, toast_isnull, is_speculative);
74 : 547 : }
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 : 24984 : heap_toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
97 : : uint32 options)
98 : : {
99 : : HeapTuple result_tuple;
100 : : TupleDesc tupleDesc;
101 : : int numAttrs;
102 : :
103 : : Size maxDataLen;
104 : : Size hoff;
105 : :
106 : : bool toast_isnull[MaxHeapAttributeNumber];
107 : : bool toast_oldisnull[MaxHeapAttributeNumber];
108 : : Datum toast_values[MaxHeapAttributeNumber];
109 : : Datum toast_oldvalues[MaxHeapAttributeNumber];
110 : : ToastAttrInfo toast_attr[MaxHeapAttributeNumber];
111 : : 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 : 24984 : 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 : : Assert(rel->rd_rel->relkind == RELKIND_RELATION ||
126 : : rel->rd_rel->relkind == RELKIND_MATVIEW);
127 : :
128 : : /*
129 : : * Get the tuple descriptor and break down the tuple(s) into fields.
130 : : */
131 : 24984 : tupleDesc = rel->rd_att;
132 : 24984 : numAttrs = tupleDesc->natts;
133 : :
134 : : Assert(numAttrs <= MaxHeapAttributeNumber);
135 : 24984 : heap_deform_tuple(newtup, tupleDesc, toast_values, toast_isnull);
136 [ + + ]: 24984 : if (oldtup != NULL)
137 : 2050 : heap_deform_tuple(oldtup, tupleDesc, toast_oldvalues, toast_oldisnull);
138 : :
139 : : /* ----------
140 : : * Prepare for toasting
141 : : * ----------
142 : : */
143 : 24984 : ttc.ttc_rel = rel;
144 : 24984 : ttc.ttc_values = toast_values;
145 : 24984 : ttc.ttc_isnull = toast_isnull;
146 [ + + ]: 24984 : if (oldtup == NULL)
147 : : {
148 : 22934 : ttc.ttc_oldvalues = NULL;
149 : 22934 : ttc.ttc_oldisnull = NULL;
150 : : }
151 : : else
152 : : {
153 : 2050 : ttc.ttc_oldvalues = toast_oldvalues;
154 : 2050 : ttc.ttc_oldisnull = toast_oldisnull;
155 : : }
156 : 24984 : ttc.ttc_attr = toast_attr;
157 : 24984 : 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 : 24984 : hoff = SizeofHeapTupleHeader;
173 [ + + ]: 24984 : if ((ttc.ttc_flags & TOAST_HAS_NULLS) != 0)
174 : 3991 : hoff += BITMAPLEN(numAttrs);
175 : 24984 : hoff = MAXALIGN(hoff);
176 : : /* now convert to a limit on the tuple data size */
177 [ + + ]: 24984 : 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 : 52794 : while (heap_compute_data_size(tupleDesc,
185 [ + + ]: 52794 : toast_values, toast_isnull) > maxDataLen)
186 : : {
187 : : int biggest_attno;
188 : :
189 : 29012 : biggest_attno = toast_tuple_find_biggest_attribute(&ttc, true, false);
190 [ + + ]: 29012 : if (biggest_attno < 0)
191 : 1202 : break;
192 : :
193 : : /*
194 : : * Attempt to compress it inline, if it has attstorage EXTENDED
195 : : */
196 [ + + ]: 27810 : if (TupleDescAttr(tupleDesc, biggest_attno)->attstorage == TYPSTORAGE_EXTENDED)
197 : 24756 : toast_tuple_try_compression(&ttc, biggest_attno);
198 : : else
199 : : {
200 : : /*
201 : : * has attstorage EXTERNAL, ignore on subsequent compression
202 : : * passes
203 : : */
204 : 3054 : toast_attr[biggest_attno].tai_colflags |= TOASTCOL_INCOMPRESSIBLE;
205 : : }
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 [ + + ]: 27810 : if (toast_attr[biggest_attno].tai_size > maxDataLen &&
216 [ + - ]: 10416 : rel->rd_rel->reltoastrelid != InvalidOid)
217 : 10416 : toast_tuple_externalize(&ttc, biggest_attno, options);
218 : : }
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 : 26155 : while (heap_compute_data_size(tupleDesc,
226 [ + + ]: 26155 : toast_values, toast_isnull) > maxDataLen &&
227 [ + + ]: 1230 : rel->rd_rel->reltoastrelid != InvalidOid)
228 : : {
229 : : int biggest_attno;
230 : :
231 : 1212 : biggest_attno = toast_tuple_find_biggest_attribute(&ttc, false, false);
232 [ + + ]: 1212 : if (biggest_attno < 0)
233 : 41 : break;
234 : 1171 : toast_tuple_externalize(&ttc, biggest_attno, options);
235 : : }
236 : :
237 : : /*
238 : : * Round 3 - this time we take attributes with storage MAIN into
239 : : * compression
240 : : */
241 : 25020 : while (heap_compute_data_size(tupleDesc,
242 [ + + ]: 25020 : toast_values, toast_isnull) > maxDataLen)
243 : : {
244 : : int biggest_attno;
245 : :
246 : 67 : biggest_attno = toast_tuple_find_biggest_attribute(&ttc, true, true);
247 [ + + ]: 67 : if (biggest_attno < 0)
248 : 31 : break;
249 : :
250 : 36 : toast_tuple_try_compression(&ttc, biggest_attno);
251 : : }
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 : 24984 : maxDataLen = TOAST_TUPLE_TARGET_MAIN - hoff;
259 : :
260 : 24984 : while (heap_compute_data_size(tupleDesc,
261 [ - + ]: 24984 : toast_values, toast_isnull) > maxDataLen &&
262 [ # # ]: 0 : rel->rd_rel->reltoastrelid != InvalidOid)
263 : : {
264 : : 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 : : }
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 [ + + ]: 24984 : if ((ttc.ttc_flags & TOAST_NEEDS_CHANGE) != 0)
278 : : {
279 : 24876 : HeapTupleHeader olddata = newtup->t_data;
280 : : HeapTupleHeader new_data;
281 : : int32 new_header_len;
282 : : int32 new_data_len;
283 : : 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 : 24876 : new_header_len = SizeofHeapTupleHeader;
296 [ + + ]: 24876 : if ((ttc.ttc_flags & TOAST_HAS_NULLS) != 0)
297 : 3937 : new_header_len += BITMAPLEN(numAttrs);
298 : 24876 : new_header_len = MAXALIGN(new_header_len);
299 : 24876 : new_data_len = heap_compute_data_size(tupleDesc,
300 : : toast_values, toast_isnull);
301 : 24876 : new_tuple_len = new_header_len + new_data_len;
302 : :
303 : : /*
304 : : * Allocate and zero the space needed, and fill HeapTupleData fields.
305 : : */
306 : 24876 : result_tuple = (HeapTuple) palloc0(HEAPTUPLESIZE + new_tuple_len);
307 : 24876 : result_tuple->t_len = new_tuple_len;
308 : 24876 : result_tuple->t_self = newtup->t_self;
309 : 24876 : result_tuple->t_tableOid = newtup->t_tableOid;
310 : 24876 : new_data = (HeapTupleHeader) ((char *) result_tuple + HEAPTUPLESIZE);
311 : 24876 : result_tuple->t_data = new_data;
312 : :
313 : : /*
314 : : * Copy the existing tuple header, but adjust natts and t_hoff.
315 : : */
316 : 24876 : memcpy(new_data, olddata, SizeofHeapTupleHeader);
317 : 24876 : HeapTupleHeaderSetNatts(new_data, numAttrs);
318 : 24876 : new_data->t_hoff = new_header_len;
319 : :
320 : : /* Copy over the data, and fill the null bitmap if needed */
321 : 24876 : heap_fill_tuple(tupleDesc,
322 : : toast_values,
323 : : toast_isnull,
324 : : (char *) new_data + new_header_len,
325 : : new_data_len,
326 : : &(new_data->t_infomask),
327 [ + + ]: 24876 : ((ttc.ttc_flags & TOAST_HAS_NULLS) != 0) ?
328 : : new_data->t_bits : NULL);
329 : : }
330 : : else
331 : 108 : result_tuple = newtup;
332 : :
333 : 24984 : toast_tuple_cleanup(&ttc);
334 : :
335 : 24984 : return result_tuple;
336 : : }
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 : 3645 : toast_flatten_tuple(HeapTuple tup, TupleDesc tupleDesc)
351 : : {
352 : : HeapTuple new_tuple;
353 : 3645 : int numAttrs = tupleDesc->natts;
354 : : int i;
355 : : Datum toast_values[MaxTupleAttributeNumber];
356 : : bool toast_isnull[MaxTupleAttributeNumber];
357 : : bool toast_free[MaxTupleAttributeNumber];
358 : :
359 : : /*
360 : : * Break down the tuple into fields.
361 : : */
362 : : Assert(numAttrs <= MaxTupleAttributeNumber);
363 : 3645 : heap_deform_tuple(tup, tupleDesc, toast_values, toast_isnull);
364 : :
365 : 3645 : memset(toast_free, 0, numAttrs * sizeof(bool));
366 : :
367 [ + + ]: 114180 : for (i = 0; i < numAttrs; i++)
368 : : {
369 : : /*
370 : : * Look at non-null varlena attributes
371 : : */
372 [ + + + + ]: 110535 : if (!toast_isnull[i] && TupleDescCompactAttr(tupleDesc, i)->attlen == -1)
373 : : {
374 : : varlena *new_value;
375 : :
376 : 14927 : new_value = (varlena *) DatumGetPointer(toast_values[i]);
377 [ + + ]: 14927 : if (VARATT_IS_EXTERNAL(new_value))
378 : : {
379 : 3785 : new_value = detoast_external_attr(new_value);
380 : 3785 : toast_values[i] = PointerGetDatum(new_value);
381 : 3785 : toast_free[i] = true;
382 : : }
383 : : }
384 : : }
385 : :
386 : : /*
387 : : * Form the reconfigured tuple.
388 : : */
389 : 3645 : 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 : 3645 : new_tuple->t_self = tup->t_self;
397 : 3645 : new_tuple->t_tableOid = tup->t_tableOid;
398 : :
399 : 3645 : new_tuple->t_data->t_choice = tup->t_data->t_choice;
400 : 3645 : new_tuple->t_data->t_ctid = tup->t_data->t_ctid;
401 : 3645 : new_tuple->t_data->t_infomask &= ~HEAP_XACT_MASK;
402 : 3645 : new_tuple->t_data->t_infomask |=
403 : 3645 : tup->t_data->t_infomask & HEAP_XACT_MASK;
404 : 3645 : new_tuple->t_data->t_infomask2 &= ~HEAP2_XACT_MASK;
405 : 3645 : new_tuple->t_data->t_infomask2 |=
406 : 3645 : tup->t_data->t_infomask2 & HEAP2_XACT_MASK;
407 : :
408 : : /*
409 : : * Free allocated temp values
410 : : */
411 [ + + ]: 114180 : for (i = 0; i < numAttrs; i++)
412 [ + + ]: 110535 : if (toast_free[i])
413 : 3785 : pfree(DatumGetPointer(toast_values[i]));
414 : :
415 : 3645 : return new_tuple;
416 : : }
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 : 8 : toast_flatten_tuple_to_datum(HeapTupleHeader tup,
450 : : uint32 tup_len,
451 : : TupleDesc tupleDesc)
452 : : {
453 : : HeapTupleHeader new_data;
454 : : int32 new_header_len;
455 : : int32 new_data_len;
456 : : int32 new_tuple_len;
457 : : HeapTupleData tmptup;
458 : 8 : int numAttrs = tupleDesc->natts;
459 : : int i;
460 : 8 : bool has_nulls = false;
461 : : Datum toast_values[MaxTupleAttributeNumber];
462 : : bool toast_isnull[MaxTupleAttributeNumber];
463 : : bool toast_free[MaxTupleAttributeNumber];
464 : :
465 : : /* Build a temporary HeapTuple control structure */
466 : 8 : tmptup.t_len = tup_len;
467 : 8 : ItemPointerSetInvalid(&(tmptup.t_self));
468 : 8 : tmptup.t_tableOid = InvalidOid;
469 : 8 : tmptup.t_data = tup;
470 : :
471 : : /*
472 : : * Break down the tuple into fields.
473 : : */
474 : : Assert(numAttrs <= MaxTupleAttributeNumber);
475 : 8 : heap_deform_tuple(&tmptup, tupleDesc, toast_values, toast_isnull);
476 : :
477 : 8 : memset(toast_free, 0, numAttrs * sizeof(bool));
478 : :
479 [ + + ]: 28 : for (i = 0; i < numAttrs; i++)
480 : : {
481 : : /*
482 : : * Look at non-null varlena attributes
483 : : */
484 [ + + ]: 20 : if (toast_isnull[i])
485 : 4 : has_nulls = true;
486 [ + - ]: 16 : else if (TupleDescCompactAttr(tupleDesc, i)->attlen == -1)
487 : : {
488 : : varlena *new_value;
489 : :
490 : 16 : new_value = (varlena *) DatumGetPointer(toast_values[i]);
491 [ + + - + ]: 20 : if (VARATT_IS_EXTERNAL(new_value) ||
492 : 4 : VARATT_IS_COMPRESSED(new_value))
493 : : {
494 : 12 : new_value = detoast_attr(new_value);
495 : 12 : toast_values[i] = PointerGetDatum(new_value);
496 : 12 : toast_free[i] = true;
497 : : }
498 : : }
499 : : }
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 : 8 : new_header_len = SizeofHeapTupleHeader;
508 [ + + ]: 8 : if (has_nulls)
509 : 4 : new_header_len += BITMAPLEN(numAttrs);
510 : 8 : new_header_len = MAXALIGN(new_header_len);
511 : 8 : new_data_len = heap_compute_data_size(tupleDesc,
512 : : toast_values, toast_isnull);
513 : 8 : new_tuple_len = new_header_len + new_data_len;
514 : :
515 : 8 : new_data = (HeapTupleHeader) palloc0(new_tuple_len);
516 : :
517 : : /*
518 : : * Copy the existing tuple header, but adjust natts and t_hoff.
519 : : */
520 : 8 : memcpy(new_data, tup, SizeofHeapTupleHeader);
521 : 8 : HeapTupleHeaderSetNatts(new_data, numAttrs);
522 : 8 : new_data->t_hoff = new_header_len;
523 : :
524 : : /* Set the composite-Datum header fields correctly */
525 : 8 : HeapTupleHeaderSetDatumLength(new_data, new_tuple_len);
526 : 8 : HeapTupleHeaderSetTypeId(new_data, tupleDesc->tdtypeid);
527 : 8 : HeapTupleHeaderSetTypMod(new_data, tupleDesc->tdtypmod);
528 : :
529 : : /* Copy over the data, and fill the null bitmap if needed */
530 [ + + ]: 8 : heap_fill_tuple(tupleDesc,
531 : : toast_values,
532 : : toast_isnull,
533 : : (char *) new_data + new_header_len,
534 : : new_data_len,
535 : : &(new_data->t_infomask),
536 : : has_nulls ? new_data->t_bits : NULL);
537 : :
538 : : /*
539 : : * Free allocated temp values
540 : : */
541 [ + + ]: 28 : for (i = 0; i < numAttrs; i++)
542 [ + + ]: 20 : if (toast_free[i])
543 : 12 : pfree(DatumGetPointer(toast_values[i]));
544 : :
545 : 8 : return PointerGetDatum(new_data);
546 : : }
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 : 34978 : toast_build_flattened_tuple(TupleDesc tupleDesc,
564 : : const Datum *values,
565 : : const bool *isnull)
566 : : {
567 : : HeapTuple new_tuple;
568 : 34978 : int numAttrs = tupleDesc->natts;
569 : : int num_to_free;
570 : : int i;
571 : : Datum new_values[MaxTupleAttributeNumber];
572 : : void *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 : : Assert(numAttrs <= MaxTupleAttributeNumber);
579 : 34978 : memcpy(new_values, values, numAttrs * sizeof(Datum));
580 : :
581 : 34978 : num_to_free = 0;
582 [ + + ]: 165317 : for (i = 0; i < numAttrs; i++)
583 : : {
584 : : /*
585 : : * Look at non-null varlena attributes
586 : : */
587 [ + + + + ]: 130339 : if (!isnull[i] && TupleDescCompactAttr(tupleDesc, i)->attlen == -1)
588 : : {
589 : : varlena *new_value;
590 : :
591 : 43813 : new_value = (varlena *) DatumGetPointer(new_values[i]);
592 [ + + ]: 43813 : if (VARATT_IS_EXTERNAL(new_value))
593 : : {
594 : 268 : new_value = detoast_external_attr(new_value);
595 : 268 : new_values[i] = PointerGetDatum(new_value);
596 : 268 : freeable_values[num_to_free++] = new_value;
597 : : }
598 : : }
599 : : }
600 : :
601 : : /*
602 : : * Form the reconfigured tuple.
603 : : */
604 : 34978 : new_tuple = heap_form_tuple(tupleDesc, new_values, isnull);
605 : :
606 : : /*
607 : : * Free allocated temp values
608 : : */
609 [ + + ]: 35246 : for (i = 0; i < num_to_free; i++)
610 : 268 : pfree(freeable_values[i]);
611 : :
612 : 34978 : return new_tuple;
613 : : }
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 : 17493 : heap_fetch_toast_slice(Relation toastrel, Oid valueid, int32 attrsize,
627 : : int32 sliceoffset, int32 slicelength,
628 : : varlena *result)
629 : : {
630 : : Relation *toastidxs;
631 : : ScanKeyData toastkey[3];
632 : 17493 : TupleDesc toasttupDesc = toastrel->rd_att;
633 : : int nscankeys;
634 : : SysScanDesc toastscan;
635 : : HeapTuple ttup;
636 : : int32 expectedchunk;
637 : : int32 totalchunks;
638 : : int startchunk;
639 : : int endchunk;
640 : : int num_indexes;
641 : : int validIndex;
642 : : int32 max_chunk_size;
643 : :
644 : : /* Look for the valid index of toast relation */
645 : 17493 : validIndex = toast_open_indexes(toastrel,
646 : : AccessShareLock,
647 : : &toastidxs,
648 : : &num_indexes);
649 : :
650 : 17493 : max_chunk_size = TOAST_MAX_CHUNK_SIZE;
651 : :
652 : 17493 : totalchunks = ((attrsize - 1) / max_chunk_size) + 1;
653 : 17493 : startchunk = sliceoffset / max_chunk_size;
654 : 17493 : endchunk = (sliceoffset + slicelength - 1) / max_chunk_size;
655 : : Assert(endchunk <= totalchunks);
656 : :
657 : : /* Set up a scan key to fetch from the index. */
658 : 17493 : ScanKeyInit(&toastkey[0],
659 : : (AttrNumber) 1,
660 : : BTEqualStrategyNumber, F_OIDEQ,
661 : : ObjectIdGetDatum(valueid));
662 : :
663 : : /*
664 : : * No additional condition if fetching all chunks. Otherwise, use an
665 : : * equality condition for one chunk, and a range condition otherwise.
666 : : */
667 [ + + + + ]: 17493 : if (startchunk == 0 && endchunk == totalchunks - 1)
668 : 17441 : nscankeys = 1;
669 [ + - ]: 52 : else if (startchunk == endchunk)
670 : : {
671 : 52 : ScanKeyInit(&toastkey[1],
672 : : (AttrNumber) 2,
673 : : BTEqualStrategyNumber, F_INT4EQ,
674 : : Int32GetDatum(startchunk));
675 : 52 : nscankeys = 2;
676 : : }
677 : : else
678 : : {
679 : 0 : ScanKeyInit(&toastkey[1],
680 : : (AttrNumber) 2,
681 : : BTGreaterEqualStrategyNumber, F_INT4GE,
682 : : Int32GetDatum(startchunk));
683 : 0 : ScanKeyInit(&toastkey[2],
684 : : (AttrNumber) 2,
685 : : BTLessEqualStrategyNumber, F_INT4LE,
686 : : Int32GetDatum(endchunk));
687 : 0 : nscankeys = 3;
688 : : }
689 : :
690 : : /* Prepare for scan */
691 : 17493 : toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
692 : : get_toast_snapshot(), nscankeys, toastkey);
693 : :
694 : : /*
695 : : * Read the chunks by index
696 : : *
697 : : * The index is on (valueid, chunkidx) so they will come in order
698 : : */
699 : 17493 : expectedchunk = startchunk;
700 [ + + ]: 65299 : while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
701 : : {
702 : : int32 curchunk;
703 : : Pointer chunk;
704 : : bool isnull;
705 : : char *chunkdata;
706 : : int32 chunksize;
707 : : int32 expected_size;
708 : : int32 chcpystrt;
709 : : int32 chcpyend;
710 : :
711 : : /*
712 : : * Have a chunk, extract the sequence number and the data
713 : : */
714 : 47806 : curchunk = DatumGetInt32(fastgetattr(ttup, 2, toasttupDesc, &isnull));
715 : : Assert(!isnull);
716 : 47806 : chunk = DatumGetPointer(fastgetattr(ttup, 3, toasttupDesc, &isnull));
717 : : Assert(!isnull);
718 [ + - ]: 47806 : if (!VARATT_IS_EXTENDED(chunk))
719 : : {
720 : 47806 : chunksize = VARSIZE(chunk) - VARHDRSZ;
721 : 47806 : chunkdata = VARDATA(chunk);
722 : : }
723 [ # # ]: 0 : else if (VARATT_IS_SHORT(chunk))
724 : : {
725 : : /* could happen due to heap_form_tuple doing its thing */
726 : 0 : chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT;
727 : 0 : chunkdata = VARDATA_SHORT(chunk);
728 : : }
729 : : else
730 : : {
731 : : /* should never happen */
732 [ # # ]: 0 : elog(ERROR, "found toasted toast chunk for toast value %u in %s",
733 : : valueid, RelationGetRelationName(toastrel));
734 : : chunksize = 0; /* keep compiler quiet */
735 : : chunkdata = NULL;
736 : : }
737 : :
738 : : /*
739 : : * Some checks on the data we've found
740 : : */
741 [ - + ]: 47806 : if (curchunk != expectedchunk)
742 [ # # ]: 0 : ereport(ERROR,
743 : : (errcode(ERRCODE_DATA_CORRUPTED),
744 : : errmsg_internal("unexpected chunk number %d (expected %d) for toast value %u in %s",
745 : : curchunk, expectedchunk, valueid,
746 : : RelationGetRelationName(toastrel))));
747 [ - + ]: 47806 : if (curchunk > endchunk)
748 [ # # ]: 0 : ereport(ERROR,
749 : : (errcode(ERRCODE_DATA_CORRUPTED),
750 : : errmsg_internal("unexpected chunk number %d (out of range %d..%d) for toast value %u in %s",
751 : : curchunk,
752 : : startchunk, endchunk, valueid,
753 : : RelationGetRelationName(toastrel))));
754 : 95612 : expected_size = curchunk < totalchunks - 1 ? max_chunk_size
755 [ + + ]: 47806 : : attrsize - ((totalchunks - 1) * max_chunk_size);
756 [ - + ]: 47806 : if (chunksize != expected_size)
757 [ # # ]: 0 : ereport(ERROR,
758 : : (errcode(ERRCODE_DATA_CORRUPTED),
759 : : errmsg_internal("unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s",
760 : : chunksize, expected_size,
761 : : curchunk, totalchunks, valueid,
762 : : RelationGetRelationName(toastrel))));
763 : :
764 : : /*
765 : : * Copy the data into proper place in our result
766 : : */
767 : 47806 : chcpystrt = 0;
768 : 47806 : chcpyend = chunksize - 1;
769 [ + + ]: 47806 : if (curchunk == startchunk)
770 : 17493 : chcpystrt = sliceoffset % max_chunk_size;
771 [ + + ]: 47806 : if (curchunk == endchunk)
772 : 17493 : chcpyend = (sliceoffset + slicelength - 1) % max_chunk_size;
773 : :
774 : 95612 : memcpy(VARDATA(result) +
775 : 47806 : (curchunk * max_chunk_size - sliceoffset) + chcpystrt,
776 : 47806 : chunkdata + chcpystrt,
777 : 47806 : (chcpyend - chcpystrt) + 1);
778 : :
779 : 47806 : expectedchunk++;
780 : : }
781 : :
782 : : /*
783 : : * Final checks that we successfully fetched the datum
784 : : */
785 [ - + ]: 17493 : if (expectedchunk != (endchunk + 1))
786 [ # # ]: 0 : ereport(ERROR,
787 : : (errcode(ERRCODE_DATA_CORRUPTED),
788 : : errmsg_internal("missing chunk number %d for toast value %u in %s",
789 : : expectedchunk, valueid,
790 : : RelationGetRelationName(toastrel))));
791 : :
792 : : /* End scan and close indexes. */
793 : 17493 : systable_endscan_ordered(toastscan);
794 : 17493 : toast_close_indexes(toastidxs, num_indexes, AccessShareLock);
795 : 17493 : }
|