1 : /*-------------------------------------------------------------------------
2 : *
3 : * tuptoaster.c
4 : * Support routines for external and compressed storage of
5 : * variable size attributes.
6 : *
7 : * Copyright (c) 2000-2008, PostgreSQL Global Development Group
8 : *
9 : *
10 : * IDENTIFICATION
11 : * $PostgreSQL: pgsql/src/backend/access/heap/tuptoaster.c,v 1.80 2007/11/30 21:22:53 tgl Exp $
12 : *
13 : *
14 : * INTERFACE ROUTINES
15 : * toast_insert_or_update -
16 : * Try to make a given tuple fit into one page by compressing
17 : * or moving off attributes
18 : *
19 : * toast_delete -
20 : * Reclaim toast storage when a tuple is deleted
21 : *
22 : * heap_tuple_untoast_attr -
23 : * Fetch back a given value from the "secondary" relation
24 : *
25 : *-------------------------------------------------------------------------
26 : */
27 :
28 : #include "postgres.h"
29 :
30 : #include <unistd.h>
31 : #include <fcntl.h>
32 :
33 : #include "access/genam.h"
34 : #include "access/heapam.h"
35 : #include "access/tuptoaster.h"
36 : #include "access/xact.h"
37 : #include "catalog/catalog.h"
38 : #include "utils/fmgroids.h"
39 : #include "utils/pg_lzcompress.h"
40 : #include "utils/typcache.h"
41 :
42 :
43 : #undef TOAST_DEBUG
44 :
45 : /* Size of an EXTERNAL datum that contains a standard TOAST pointer */
46 : #define TOAST_POINTER_SIZE (VARHDRSZ_EXTERNAL + sizeof(struct varatt_external))
47 :
48 : /*
49 : * Testing whether an externally-stored value is compressed now requires
50 : * comparing extsize (the actual length of the external data) to rawsize
51 : * (the original uncompressed datum's size). The latter includes VARHDRSZ
52 : * overhead, the former doesn't. We never use compression unless it actually
53 : * saves space, so we expect either equality or less-than.
54 : */
55 : #define VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer) \
56 : ((toast_pointer).va_extsize < (toast_pointer).va_rawsize - VARHDRSZ)
57 :
58 : /*
59 : * Macro to fetch the possibly-unaligned contents of an EXTERNAL datum
60 : * into a local "struct varatt_external" toast pointer. This should be
61 : * just a memcpy, but some versions of gcc seem to produce broken code
62 : * that assumes the datum contents are aligned. Introducing an explicit
63 : * intermediate "varattrib_1b_e *" variable seems to fix it.
64 : */
65 : #define VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr) \
66 : do { \
67 : varattrib_1b_e *attre = (varattrib_1b_e *) (attr); \
68 : Assert(VARSIZE_ANY_EXHDR(attre) == sizeof(toast_pointer)); \
69 : memcpy(&(toast_pointer), VARDATA_EXTERNAL(attre), sizeof(toast_pointer)); \
70 : } while (0)
71 :
72 :
73 : static void toast_delete_datum(Relation rel, Datum value);
74 : static Datum toast_save_datum(Relation rel, Datum value,
75 : bool use_wal, bool use_fsm);
76 : static struct varlena *toast_fetch_datum(struct varlena * attr);
77 : static struct varlena *toast_fetch_datum_slice(struct varlena * attr,
78 : int32 sliceoffset, int32 length);
79 :
80 :
81 : /* ----------
82 : * heap_tuple_fetch_attr -
83 : *
84 : * Public entry point to get back a toasted value from
85 : * external storage (possibly still in compressed format).
86 : *
87 : * This will return a datum that contains all the data internally, ie, not
88 : * relying on external storage, but it can still be compressed or have a short
89 : * header.
90 : ----------
91 : */
92 : struct varlena *
93 : heap_tuple_fetch_attr(struct varlena * attr)
94 1 : {
95 : struct varlena *result;
96 :
97 1 : if (VARATT_IS_EXTERNAL(attr))
98 : {
99 : /*
100 : * This is an external stored plain value
101 : */
102 1 : result = toast_fetch_datum(attr);
103 : }
104 : else
105 : {
106 : /*
107 : * This is a plain value inside of the main tuple - why am I called?
108 : */
109 0 : result = attr;
110 : }
111 :
112 1 : return result;
113 : }
114 :
115 :
116 : /* ----------
117 : * heap_tuple_untoast_attr -
118 : *
119 : * Public entry point to get back a toasted value from compression
120 : * or external storage.
121 : * ----------
122 : */
123 : struct varlena *
124 : heap_tuple_untoast_attr(struct varlena * attr)
125 485941 : {
126 485941 : if (VARATT_IS_EXTERNAL(attr))
127 : {
128 : /*
129 : * This is an externally stored datum --- fetch it back from there
130 : */
131 83 : attr = toast_fetch_datum(attr);
132 : /* If it's compressed, decompress it */
133 83 : if (VARATT_IS_COMPRESSED(attr))
134 : {
135 83 : PGLZ_Header *tmp = (PGLZ_Header *) attr;
136 :
137 83 : attr = (struct varlena *) palloc(PGLZ_RAW_SIZE(tmp) + VARHDRSZ);
138 83 : SET_VARSIZE(attr, PGLZ_RAW_SIZE(tmp) + VARHDRSZ);
139 83 : pglz_decompress(tmp, VARDATA(attr));
140 83 : pfree(tmp);
141 : }
142 : }
143 485858 : else if (VARATT_IS_COMPRESSED(attr))
144 : {
145 : /*
146 : * This is a compressed value inside of the main tuple
147 : */
148 2873 : PGLZ_Header *tmp = (PGLZ_Header *) attr;
149 :
150 2873 : attr = (struct varlena *) palloc(PGLZ_RAW_SIZE(tmp) + VARHDRSZ);
151 2873 : SET_VARSIZE(attr, PGLZ_RAW_SIZE(tmp) + VARHDRSZ);
152 2873 : pglz_decompress(tmp, VARDATA(attr));
153 : }
154 482985 : else if (VARATT_IS_SHORT(attr))
155 : {
156 : /*
157 : * This is a short-header varlena --- convert to 4-byte header format
158 : */
159 482985 : Size data_size = VARSIZE_SHORT(attr) - VARHDRSZ_SHORT;
160 482985 : Size new_size = data_size + VARHDRSZ;
161 : struct varlena *new_attr;
162 :
163 482985 : new_attr = (struct varlena *) palloc(new_size);
164 482985 : SET_VARSIZE(new_attr, new_size);
165 482985 : memcpy(VARDATA(new_attr), VARDATA_SHORT(attr), data_size);
166 482985 : attr = new_attr;
167 : }
168 :
169 485941 : return attr;
170 : }
171 :
172 :
173 : /* ----------
174 : * heap_tuple_untoast_attr_slice -
175 : *
176 : * Public entry point to get back part of a toasted value
177 : * from compression or external storage.
178 : * ----------
179 : */
180 : struct varlena *
181 : heap_tuple_untoast_attr_slice(struct varlena * attr,
182 : int32 sliceoffset, int32 slicelength)
183 787 : {
184 : struct varlena *preslice;
185 : struct varlena *result;
186 : char *attrdata;
187 : int32 attrsize;
188 :
189 787 : if (VARATT_IS_EXTERNAL(attr))
190 : {
191 : struct varatt_external toast_pointer;
192 :
193 17 : VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
194 :
195 : /* fast path for non-compressed external datums */
196 17 : if (!VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))
197 12 : return toast_fetch_datum_slice(attr, sliceoffset, slicelength);
198 :
199 : /* fetch it back (compressed marker will get set automatically) */
200 5 : preslice = toast_fetch_datum(attr);
201 : }
202 : else
203 770 : preslice = attr;
204 :
205 775 : if (VARATT_IS_COMPRESSED(preslice))
206 : {
207 17 : PGLZ_Header *tmp = (PGLZ_Header *) preslice;
208 17 : Size size = PGLZ_RAW_SIZE(tmp) + VARHDRSZ;
209 :
210 17 : preslice = (struct varlena *) palloc(size);
211 17 : SET_VARSIZE(preslice, size);
212 17 : pglz_decompress(tmp, VARDATA(preslice));
213 :
214 17 : if (tmp != (PGLZ_Header *) attr)
215 5 : pfree(tmp);
216 : }
217 :
218 775 : if (VARATT_IS_SHORT(preslice))
219 : {
220 1 : attrdata = VARDATA_SHORT(preslice);
221 1 : attrsize = VARSIZE_SHORT(preslice) - VARHDRSZ_SHORT;
222 : }
223 : else
224 : {
225 774 : attrdata = VARDATA(preslice);
226 774 : attrsize = VARSIZE(preslice) - VARHDRSZ;
227 : }
228 :
229 : /* slicing of datum for compressed cases and plain value */
230 :
231 775 : if (sliceoffset >= attrsize)
232 : {
233 3 : sliceoffset = 0;
234 3 : slicelength = 0;
235 : }
236 :
237 775 : if (((sliceoffset + slicelength) > attrsize) || slicelength < 0)
238 25 : slicelength = attrsize - sliceoffset;
239 :
240 775 : result = (struct varlena *) palloc(slicelength + VARHDRSZ);
241 775 : SET_VARSIZE(result, slicelength + VARHDRSZ);
242 :
243 775 : memcpy(VARDATA(result), attrdata + sliceoffset, slicelength);
244 :
245 775 : if (preslice != attr)
246 17 : pfree(preslice);
247 :
248 775 : return result;
249 : }
250 :
251 :
252 : /* ----------
253 : * toast_raw_datum_size -
254 : *
255 : * Return the raw (detoasted) size of a varlena datum
256 : * (including the VARHDRSZ header)
257 : * ----------
258 : */
259 : Size
260 : toast_raw_datum_size(Datum value)
261 15819 : {
262 15819 : struct varlena *attr = (struct varlena *) DatumGetPointer(value);
263 : Size result;
264 :
265 15819 : if (VARATT_IS_EXTERNAL(attr))
266 : {
267 : /* va_rawsize is the size of the original datum -- including header */
268 : struct varatt_external toast_pointer;
269 :
270 39 : VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
271 39 : result = toast_pointer.va_rawsize;
272 : }
273 15780 : else if (VARATT_IS_COMPRESSED(attr))
274 : {
275 : /* here, va_rawsize is just the payload size */
276 37 : result = VARRAWSIZE_4B_C(attr) + VARHDRSZ;
277 : }
278 15743 : else if (VARATT_IS_SHORT(attr))
279 : {
280 : /*
281 : * we have to normalize the header length to VARHDRSZ or else the
282 : * callers of this function will be confused.
283 : */
284 10281 : result = VARSIZE_SHORT(attr) - VARHDRSZ_SHORT + VARHDRSZ;
285 : }
286 : else
287 : {
288 : /* plain untoasted datum */
289 5462 : result = VARSIZE(attr);
290 : }
291 15819 : return result;
292 : }
293 :
294 : /* ----------
295 : * toast_datum_size
296 : *
297 : * Return the physical storage size (possibly compressed) of a varlena datum
298 : * ----------
299 : */
300 : Size
301 : toast_datum_size(Datum value)
302 0 : {
303 0 : struct varlena *attr = (struct varlena *) DatumGetPointer(value);
304 : Size result;
305 :
306 0 : if (VARATT_IS_EXTERNAL(attr))
307 : {
308 : /*
309 : * Attribute is stored externally - return the extsize whether
310 : * compressed or not. We do not count the size of the toast pointer
311 : * ... should we?
312 : */
313 : struct varatt_external toast_pointer;
314 :
315 0 : VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
316 0 : result = toast_pointer.va_extsize;
317 : }
318 0 : else if (VARATT_IS_SHORT(attr))
319 : {
320 0 : result = VARSIZE_SHORT(attr);
321 : }
322 : else
323 : {
324 : /*
325 : * Attribute is stored inline either compressed or not, just calculate
326 : * the size of the datum in either case.
327 : */
328 0 : result = VARSIZE(attr);
329 : }
330 0 : return result;
331 : }
332 :
333 :
334 : /* ----------
335 : * toast_delete -
336 : *
337 : * Cascaded delete toast-entries on DELETE
338 : * ----------
339 : */
340 : void
341 : toast_delete(Relation rel, HeapTuple oldtup)
342 0 : {
343 : TupleDesc tupleDesc;
344 : Form_pg_attribute *att;
345 : int numAttrs;
346 : int i;
347 : Datum toast_values[MaxHeapAttributeNumber];
348 : bool toast_isnull[MaxHeapAttributeNumber];
349 :
350 : /*
351 : * We should only ever be called for tuples of plain relations ---
352 : * recursing on a toast rel is bad news.
353 : */
354 : Assert(rel->rd_rel->relkind == RELKIND_RELATION);
355 :
356 : /*
357 : * Get the tuple descriptor and break down the tuple into fields.
358 : *
359 : * NOTE: it's debatable whether to use heap_deformtuple() here or just
360 : * heap_getattr() only the varlena columns. The latter could win if there
361 : * are few varlena columns and many non-varlena ones. However,
362 : * heap_deformtuple costs only O(N) while the heap_getattr way would cost
363 : * O(N^2) if there are many varlena columns, so it seems better to err on
364 : * the side of linear cost. (We won't even be here unless there's at
365 : * least one varlena column, by the way.)
366 : */
367 0 : tupleDesc = rel->rd_att;
368 0 : att = tupleDesc->attrs;
369 0 : numAttrs = tupleDesc->natts;
370 :
371 : Assert(numAttrs <= MaxHeapAttributeNumber);
372 0 : heap_deform_tuple(oldtup, tupleDesc, toast_values, toast_isnull);
373 :
374 : /*
375 : * Check for external stored attributes and delete them from the secondary
376 : * relation.
377 : */
378 0 : for (i = 0; i < numAttrs; i++)
379 : {
380 0 : if (att[i]->attlen == -1)
381 : {
382 0 : Datum value = toast_values[i];
383 :
384 0 : if (!toast_isnull[i] && VARATT_IS_EXTERNAL(value))
385 0 : toast_delete_datum(rel, value);
386 : }
387 : }
388 0 : }
389 :
390 :
391 : /* ----------
392 : * toast_insert_or_update -
393 : *
394 : * Delete no-longer-used toast-entries and create new ones to
395 : * make the new tuple fit on INSERT or UPDATE
396 : *
397 : * Inputs:
398 : * newtup: the candidate new tuple to be inserted
399 : * oldtup: the old row version for UPDATE, or NULL for INSERT
400 : * use_wal, use_fsm: flags to be passed to heap_insert() for toast rows
401 : * Result:
402 : * either newtup if no toasting is needed, or a palloc'd modified tuple
403 : * that is what should actually get stored
404 : *
405 : * NOTE: neither newtup nor oldtup will be modified. This is a change
406 : * from the pre-8.1 API of this routine.
407 : * ----------
408 : */
409 : HeapTuple
410 : toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
411 : bool use_wal, bool use_fsm)
412 798 : {
413 : HeapTuple result_tuple;
414 : TupleDesc tupleDesc;
415 : Form_pg_attribute *att;
416 : int numAttrs;
417 : int i;
418 :
419 798 : bool need_change = false;
420 798 : bool need_free = false;
421 798 : bool need_delold = false;
422 798 : bool has_nulls = false;
423 :
424 : Size maxDataLen;
425 : Size hoff;
426 :
427 : char toast_action[MaxHeapAttributeNumber];
428 : bool toast_isnull[MaxHeapAttributeNumber];
429 : bool toast_oldisnull[MaxHeapAttributeNumber];
430 : Datum toast_values[MaxHeapAttributeNumber];
431 : Datum toast_oldvalues[MaxHeapAttributeNumber];
432 : int32 toast_sizes[MaxHeapAttributeNumber];
433 : bool toast_free[MaxHeapAttributeNumber];
434 : bool toast_delold[MaxHeapAttributeNumber];
435 :
436 : /*
437 : * We should only ever be called for tuples of plain relations ---
438 : * recursing on a toast rel is bad news.
439 : */
440 : Assert(rel->rd_rel->relkind == RELKIND_RELATION);
441 :
442 : /*
443 : * Get the tuple descriptor and break down the tuple(s) into fields.
444 : */
445 798 : tupleDesc = rel->rd_att;
446 798 : att = tupleDesc->attrs;
447 798 : numAttrs = tupleDesc->natts;
448 :
449 : Assert(numAttrs <= MaxHeapAttributeNumber);
450 798 : heap_deform_tuple(newtup, tupleDesc, toast_values, toast_isnull);
451 798 : if (oldtup != NULL)
452 5 : heap_deform_tuple(oldtup, tupleDesc, toast_oldvalues, toast_oldisnull);
453 :
454 : /* ----------
455 : * Then collect information about the values given
456 : *
457 : * NOTE: toast_action[i] can have these values:
458 : * ' ' default handling
459 : * 'p' already processed --- don't touch it
460 : * 'x' incompressible, but OK to move off
461 : *
462 : * NOTE: toast_sizes[i] is only made valid for varlena attributes with
463 : * toast_action[i] different from 'p'.
464 : * ----------
465 : */
466 798 : memset(toast_action, ' ', numAttrs * sizeof(char));
467 798 : memset(toast_free, 0, numAttrs * sizeof(bool));
468 798 : memset(toast_delold, 0, numAttrs * sizeof(bool));
469 :
470 3825 : for (i = 0; i < numAttrs; i++)
471 : {
472 : struct varlena *old_value;
473 : struct varlena *new_value;
474 :
475 3027 : if (oldtup != NULL)
476 : {
477 : /*
478 : * For UPDATE get the old and new values of this attribute
479 : */
480 20 : old_value = (struct varlena *) DatumGetPointer(toast_oldvalues[i]);
481 20 : new_value = (struct varlena *) DatumGetPointer(toast_values[i]);
482 :
483 : /*
484 : * If the old value is an external stored one, check if it has
485 : * changed so we have to delete it later.
486 : */
487 20 : if (att[i]->attlen == -1 && !toast_oldisnull[i] &&
488 : VARATT_IS_EXTERNAL(old_value))
489 : {
490 0 : if (toast_isnull[i] || !VARATT_IS_EXTERNAL(new_value) ||
491 : memcmp((char *) old_value, (char *) new_value,
492 : VARSIZE_EXTERNAL(old_value)) != 0)
493 : {
494 : /*
495 : * The old external stored value isn't needed any more
496 : * after the update
497 : */
498 0 : toast_delold[i] = true;
499 0 : need_delold = true;
500 : }
501 : else
502 : {
503 : /*
504 : * This attribute isn't changed by this update so we reuse
505 : * the original reference to the old value in the new
506 : * tuple.
507 : */
508 0 : toast_action[i] = 'p';
509 0 : continue;
510 : }
511 : }
512 : }
513 : else
514 : {
515 : /*
516 : * For INSERT simply get the new value
517 : */
518 3007 : new_value = (struct varlena *) DatumGetPointer(toast_values[i]);
519 : }
520 :
521 : /*
522 : * Handle NULL attributes
523 : */
524 3027 : if (toast_isnull[i])
525 : {
526 5 : toast_action[i] = 'p';
527 5 : has_nulls = true;
528 5 : continue;
529 : }
530 :
531 : /*
532 : * Now look at varlena attributes
533 : */
534 3022 : if (att[i]->attlen == -1)
535 : {
536 : /*
537 : * If the table's attribute says PLAIN always, force it so.
538 : */
539 929 : if (att[i]->attstorage == 'p')
540 1 : toast_action[i] = 'p';
541 :
542 : /*
543 : * We took care of UPDATE above, so any external value we find
544 : * still in the tuple must be someone else's we cannot reuse.
545 : * Fetch it back (without decompression, unless we are forcing
546 : * PLAIN storage). If necessary, we'll push it out as a new
547 : * external value below.
548 : */
549 929 : if (VARATT_IS_EXTERNAL(new_value))
550 : {
551 1 : if (att[i]->attstorage == 'p')
552 0 : new_value = heap_tuple_untoast_attr(new_value);
553 : else
554 1 : new_value = heap_tuple_fetch_attr(new_value);
555 1 : toast_values[i] = PointerGetDatum(new_value);
556 1 : toast_free[i] = true;
557 1 : need_change = true;
558 1 : need_free = true;
559 : }
560 :
561 : /*
562 : * Remember the size of this attribute
563 : */
564 929 : toast_sizes[i] = VARSIZE_ANY(new_value);
565 : }
566 : else
567 : {
568 : /*
569 : * Not a varlena attribute, plain storage always
570 : */
571 2093 : toast_action[i] = 'p';
572 : }
573 : }
574 :
575 : /* ----------
576 : * Compress and/or save external until data fits into target length
577 : *
578 : * 1: Inline compress attributes with attstorage 'x'
579 : * 2: Store attributes with attstorage 'x' or 'e' external
580 : * 3: Inline compress attributes with attstorage 'm'
581 : * 4: Store attributes with attstorage 'm' external
582 : * ----------
583 : */
584 :
585 : /* compute header overhead --- this should match heap_form_tuple() */
586 798 : hoff = offsetof(HeapTupleHeaderData, t_bits);
587 798 : if (has_nulls)
588 1 : hoff += BITMAPLEN(numAttrs);
589 798 : if (newtup->t_data->t_infomask & HEAP_HASOID)
590 128 : hoff += sizeof(Oid);
591 798 : hoff = MAXALIGN(hoff);
592 : Assert(hoff == newtup->t_data->t_hoff);
593 : /* now convert to a limit on the tuple data size */
594 798 : maxDataLen = TOAST_TUPLE_TARGET - hoff;
595 :
596 : /*
597 : * Look for attributes with attstorage 'x' to compress
598 : */
599 2390 : while (heap_compute_data_size(tupleDesc,
600 : toast_values, toast_isnull) > maxDataLen)
601 : {
602 836 : int biggest_attno = -1;
603 836 : int32 biggest_size = MAXALIGN(TOAST_POINTER_SIZE);
604 : Datum old_value;
605 : Datum new_value;
606 :
607 : /*
608 : * Search for the biggest yet uncompressed internal attribute
609 : */
610 4150 : for (i = 0; i < numAttrs; i++)
611 : {
612 3314 : if (toast_action[i] != ' ')
613 2312 : continue;
614 1002 : if (VARATT_IS_EXTERNAL(toast_values[i]))
615 0 : continue; /* can't happen, toast_action would be 'p' */
616 1002 : if (VARATT_IS_COMPRESSED(toast_values[i]))
617 39 : continue;
618 963 : if (att[i]->attstorage != 'x')
619 4 : continue;
620 959 : if (toast_sizes[i] > biggest_size)
621 : {
622 800 : biggest_attno = i;
623 800 : biggest_size = toast_sizes[i];
624 : }
625 : }
626 :
627 836 : if (biggest_attno < 0)
628 42 : break;
629 :
630 : /*
631 : * Attempt to compress it inline
632 : */
633 794 : i = biggest_attno;
634 794 : old_value = toast_values[i];
635 794 : new_value = toast_compress_datum(old_value);
636 :
637 794 : if (DatumGetPointer(new_value) != NULL)
638 : {
639 : /* successful compression */
640 794 : if (toast_free[i])
641 0 : pfree(DatumGetPointer(old_value));
642 794 : toast_values[i] = new_value;
643 794 : toast_free[i] = true;
644 794 : toast_sizes[i] = VARSIZE(toast_values[i]);
645 794 : need_change = true;
646 794 : need_free = true;
647 : }
648 : else
649 : {
650 : /*
651 : * incompressible data, ignore on subsequent compression passes
652 : */
653 0 : toast_action[i] = 'x';
654 : }
655 : }
656 :
657 : /*
658 : * Second we look for attributes of attstorage 'x' or 'e' that are still
659 : * inline. But skip this if there's no toast table to push them to.
660 : */
661 840 : while (heap_compute_data_size(tupleDesc,
662 : toast_values, toast_isnull) > maxDataLen &&
663 : rel->rd_rel->reltoastrelid != InvalidOid)
664 : {
665 42 : int biggest_attno = -1;
666 42 : int32 biggest_size = MAXALIGN(TOAST_POINTER_SIZE);
667 : Datum old_value;
668 :
669 : /*------
670 : * Search for the biggest yet inlined attribute with
671 : * attstorage equals 'x' or 'e'
672 : *------
673 : */
674 329 : for (i = 0; i < numAttrs; i++)
675 : {
676 287 : if (toast_action[i] == 'p')
677 209 : continue;
678 78 : if (VARATT_IS_EXTERNAL(toast_values[i]))
679 0 : continue; /* can't happen, toast_action would be 'p' */
680 78 : if (att[i]->attstorage != 'x' && att[i]->attstorage != 'e')
681 0 : continue;
682 78 : if (toast_sizes[i] > biggest_size)
683 : {
684 42 : biggest_attno = i;
685 42 : biggest_size = toast_sizes[i];
686 : }
687 : }
688 :
689 42 : if (biggest_attno < 0)
690 0 : break;
691 :
692 : /*
693 : * Store this external
694 : */
695 42 : i = biggest_attno;
696 42 : old_value = toast_values[i];
697 42 : toast_action[i] = 'p';
698 42 : toast_values[i] = toast_save_datum(rel, toast_values[i],
699 : use_wal, use_fsm);
700 42 : if (toast_free[i])
701 38 : pfree(DatumGetPointer(old_value));
702 42 : toast_free[i] = true;
703 :
704 42 : need_change = true;
705 42 : need_free = true;
706 : }
707 :
708 : /*
709 : * Round 3 - this time we take attributes with storage 'm' into
710 : * compression
711 : */
712 798 : while (heap_compute_data_size(tupleDesc,
713 : toast_values, toast_isnull) > maxDataLen)
714 : {
715 0 : int biggest_attno = -1;
716 0 : int32 biggest_size = MAXALIGN(TOAST_POINTER_SIZE);
717 : Datum old_value;
718 : Datum new_value;
719 :
720 : /*
721 : * Search for the biggest yet uncompressed internal attribute
722 : */
723 0 : for (i = 0; i < numAttrs; i++)
724 : {
725 0 : if (toast_action[i] != ' ')
726 0 : continue;
727 0 : if (VARATT_IS_EXTERNAL(toast_values[i]))
728 0 : continue; /* can't happen, toast_action would be 'p' */
729 0 : if (VARATT_IS_COMPRESSED(toast_values[i]))
730 0 : continue;
731 0 : if (att[i]->attstorage != 'm')
732 0 : continue;
733 0 : if (toast_sizes[i] > biggest_size)
734 : {
735 0 : biggest_attno = i;
736 0 : biggest_size = toast_sizes[i];
737 : }
738 : }
739 :
740 0 : if (biggest_attno < 0)
741 0 : break;
742 :
743 : /*
744 : * Attempt to compress it inline
745 : */
746 0 : i = biggest_attno;
747 0 : old_value = toast_values[i];
748 0 : new_value = toast_compress_datum(old_value);
749 :
750 0 : if (DatumGetPointer(new_value) != NULL)
751 : {
752 : /* successful compression */
753 0 : if (toast_free[i])
754 0 : pfree(DatumGetPointer(old_value));
755 0 : toast_values[i] = new_value;
756 0 : toast_free[i] = true;
757 0 : toast_sizes[i] = VARSIZE(toast_values[i]);
758 0 : need_change = true;
759 0 : need_free = true;
760 : }
761 : else
762 : {
763 : /*
764 : * incompressible data, ignore on subsequent compression passes
765 : */
766 0 : toast_action[i] = 'x';
767 : }
768 : }
769 :
770 : /*
771 : * Finally we store attributes of type 'm' external, if possible.
772 : */
773 798 : while (heap_compute_data_size(tupleDesc,
774 : toast_values, toast_isnull) > maxDataLen &&
775 : rel->rd_rel->reltoastrelid != InvalidOid)
776 : {
777 0 : int biggest_attno = -1;
778 0 : int32 biggest_size = MAXALIGN(TOAST_POINTER_SIZE);
779 : Datum old_value;
780 :
781 : /*--------
782 : * Search for the biggest yet inlined attribute with
783 : * attstorage = 'm'
784 : *--------
785 : */
786 0 : for (i = 0; i < numAttrs; i++)
787 : {
788 0 : if (toast_action[i] == 'p')
789 0 : continue;
790 0 : if (VARATT_IS_EXTERNAL(toast_values[i]))
791 0 : continue; /* can't happen, toast_action would be 'p' */
792 0 : if (att[i]->attstorage != 'm')
793 0 : continue;
794 0 : if (toast_sizes[i] > biggest_size)
795 : {
796 0 : biggest_attno = i;
797 0 : biggest_size = toast_sizes[i];
798 : }
799 : }
800 :
801 0 : if (biggest_attno < 0)
802 0 : break;
803 :
804 : /*
805 : * Store this external
806 : */
807 0 : i = biggest_attno;
808 0 : old_value = toast_values[i];
809 0 : toast_action[i] = 'p';
810 0 : toast_values[i] = toast_save_datum(rel, toast_values[i],
811 : use_wal, use_fsm);
812 0 : if (toast_free[i])
813 0 : pfree(DatumGetPointer(old_value));
814 0 : toast_free[i] = true;
815 :
816 0 : need_change = true;
817 0 : need_free = true;
818 : }
819 :
820 : /*
821 : * In the case we toasted any values, we need to build a new heap tuple
822 : * with the changed values.
823 : */
824 798 : if (need_change)
825 : {
826 798 : HeapTupleHeader olddata = newtup->t_data;
827 : HeapTupleHeader new_data;
828 : int32 new_len;
829 : int32 new_data_len;
830 :
831 : /*
832 : * Calculate the new size of the tuple. Header size should not
833 : * change, but data size might.
834 : */
835 798 : new_len = offsetof(HeapTupleHeaderData, t_bits);
836 798 : if (has_nulls)
837 1 : new_len += BITMAPLEN(numAttrs);
838 798 : if (olddata->t_infomask & HEAP_HASOID)
839 128 : new_len += sizeof(Oid);
840 798 : new_len = MAXALIGN(new_len);
841 : Assert(new_len == olddata->t_hoff);
842 798 : new_data_len = heap_compute_data_size(tupleDesc,
843 : toast_values, toast_isnull);
844 798 : new_len += new_data_len;
845 :
846 : /*
847 : * Allocate and zero the space needed, and fill HeapTupleData fields.
848 : */
849 798 : result_tuple = (HeapTuple) palloc0(HEAPTUPLESIZE + new_len);
850 798 : result_tuple->t_len = new_len;
851 798 : result_tuple->t_self = newtup->t_self;
852 798 : result_tuple->t_tableOid = newtup->t_tableOid;
853 798 : new_data = (HeapTupleHeader) ((char *) result_tuple + HEAPTUPLESIZE);
854 798 : result_tuple->t_data = new_data;
855 :
856 : /*
857 : * Put the existing tuple header and the changed values into place
858 : */
859 798 : memcpy(new_data, olddata, olddata->t_hoff);
860 :
861 798 : heap_fill_tuple(tupleDesc,
862 : toast_values,
863 : toast_isnull,
864 : (char *) new_data + olddata->t_hoff,
865 : new_data_len,
866 : &(new_data->t_infomask),
867 : has_nulls ? new_data->t_bits : NULL);
868 : }
869 : else
870 0 : result_tuple = newtup;
871 :
872 : /*
873 : * Free allocated temp values
874 : */
875 798 : if (need_free)
876 3825 : for (i = 0; i < numAttrs; i++)
877 3027 : if (toast_free[i])
878 799 : pfree(DatumGetPointer(toast_values[i]));
879 :
880 : /*
881 : * Delete external values from the old tuple
882 : */
883 798 : if (need_delold)
884 0 : for (i = 0; i < numAttrs; i++)
885 0 : if (toast_delold[i])
886 0 : toast_delete_datum(rel, toast_oldvalues[i]);
887 :
888 798 : return result_tuple;
889 : }
890 :
891 :
892 : /* ----------
893 : * toast_flatten_tuple_attribute -
894 : *
895 : * If a Datum is of composite type, "flatten" it to contain no toasted fields.
896 : * This must be invoked on any potentially-composite field that is to be
897 : * inserted into a tuple. Doing this preserves the invariant that toasting
898 : * goes only one level deep in a tuple.
899 : *
900 : * Note that flattening does not mean expansion of short-header varlenas,
901 : * so in one sense toasting is allowed within composite datums.
902 : * ----------
903 : */
904 : Datum
905 : toast_flatten_tuple_attribute(Datum value,
906 : Oid typeId, int32 typeMod)
907 8784 : {
908 : TupleDesc tupleDesc;
909 : HeapTupleHeader olddata;
910 : HeapTupleHeader new_data;
911 : int32 new_len;
912 : int32 new_data_len;
913 : HeapTupleData tmptup;
914 : Form_pg_attribute *att;
915 : int numAttrs;
916 : int i;
917 8784 : bool need_change = false;
918 8784 : bool has_nulls = false;
919 : Datum toast_values[MaxTupleAttributeNumber];
920 : bool toast_isnull[MaxTupleAttributeNumber];
921 : bool toast_free[MaxTupleAttributeNumber];
922 :
923 : /*
924 : * See if it's a composite type, and get the tupdesc if so.
925 : */
926 8784 : tupleDesc = lookup_rowtype_tupdesc_noerror(typeId, typeMod, true);
927 8784 : if (tupleDesc == NULL)
928 8768 : return value; /* not a composite type */
929 :
930 16 : att = tupleDesc->attrs;
931 16 : numAttrs = tupleDesc->natts;
932 :
933 : /*
934 : * Break down the tuple into fields.
935 : */
936 16 : olddata = DatumGetHeapTupleHeader(value);
937 : Assert(typeId == HeapTupleHeaderGetTypeId(olddata));
938 : Assert(typeMod == HeapTupleHeaderGetTypMod(olddata));
939 : /* Build a temporary HeapTuple control structure */
940 16 : tmptup.t_len = HeapTupleHeaderGetDatumLength(olddata);
941 16 : ItemPointerSetInvalid(&(tmptup.t_self));
942 16 : tmptup.t_tableOid = InvalidOid;
943 16 : tmptup.t_data = olddata;
944 :
945 : Assert(numAttrs <= MaxTupleAttributeNumber);
946 16 : heap_deform_tuple(&tmptup, tupleDesc, toast_values, toast_isnull);
947 :
948 16 : memset(toast_free, 0, numAttrs * sizeof(bool));
949 :
950 49 : for (i = 0; i < numAttrs; i++)
951 : {
952 : /*
953 : * Look at non-null varlena attributes
954 : */
955 33 : if (toast_isnull[i])
956 5 : has_nulls = true;
957 28 : else if (att[i]->attlen == -1)
958 : {
959 : struct varlena *new_value;
960 :
961 14 : new_value = (struct varlena *) DatumGetPointer(toast_values[i]);
962 14 : if (VARATT_IS_EXTERNAL(new_value) ||
963 : VARATT_IS_COMPRESSED(new_value))
964 : {
965 1 : new_value = heap_tuple_untoast_attr(new_value);
966 1 : toast_values[i] = PointerGetDatum(new_value);
967 1 : toast_free[i] = true;
968 1 : need_change = true;
969 : }
970 : }
971 : }
972 :
973 : /*
974 : * If nothing to untoast, just return the original tuple.
975 : */
976 16 : if (!need_change)
977 : {
978 15 : ReleaseTupleDesc(tupleDesc);
979 15 : return value;
980 : }
981 :
982 : /*
983 : * Calculate the new size of the tuple. Header size should not change,
984 : * but data size might.
985 : */
986 1 : new_len = offsetof(HeapTupleHeaderData, t_bits);
987 1 : if (has_nulls)
988 1 : new_len += BITMAPLEN(numAttrs);
989 1 : if (olddata->t_infomask & HEAP_HASOID)
990 0 : new_len += sizeof(Oid);
991 1 : new_len = MAXALIGN(new_len);
992 : Assert(new_len == olddata->t_hoff);
993 1 : new_data_len = heap_compute_data_size(tupleDesc,
994 : toast_values, toast_isnull);
995 1 : new_len += new_data_len;
996 :
997 1 : new_data = (HeapTupleHeader) palloc0(new_len);
998 :
999 : /*
1000 : * Put the tuple header and the changed values into place
1001 : */
1002 1 : memcpy(new_data, olddata, olddata->t_hoff);
1003 :
1004 1 : HeapTupleHeaderSetDatumLength(new_data, new_len);
1005 :
1006 1 : heap_fill_tuple(tupleDesc,
1007 : toast_values,
1008 : toast_isnull,
1009 : (char *) new_data + olddata->t_hoff,
1010 : new_data_len,
1011 : &(new_data->t_infomask),
1012 : has_nulls ? new_data->t_bits : NULL);
1013 :
1014 : /*
1015 : * Free allocated temp values
1016 : */
1017 4 : for (i = 0; i < numAttrs; i++)
1018 3 : if (toast_free[i])
1019 1 : pfree(DatumGetPointer(toast_values[i]));
1020 1 : ReleaseTupleDesc(tupleDesc);
1021 :
1022 1 : return PointerGetDatum(new_data);
1023 : }
1024 :
1025 :
1026 : /* ----------
1027 : * toast_compress_datum -
1028 : *
1029 : * Create a compressed version of a varlena datum
1030 : *
1031 : * If we fail (ie, compressed result is actually bigger than original)
1032 : * then return NULL. We must not use compressed data if it'd expand
1033 : * the tuple!
1034 : *
1035 : * We use VAR{SIZE,DATA}_ANY so we can handle short varlenas here without
1036 : * copying them. But we can't handle external or compressed datums.
1037 : * ----------
1038 : */
1039 : Datum
1040 : toast_compress_datum(Datum value)
1041 794 : {
1042 : struct varlena *tmp;
1043 794 : int32 valsize = VARSIZE_ANY_EXHDR(value);
1044 :
1045 : Assert(!VARATT_IS_EXTERNAL(value));
1046 : Assert(!VARATT_IS_COMPRESSED(value));
1047 :
1048 : /*
1049 : * No point in wasting a palloc cycle if value is too short for
1050 : * compression
1051 : */
1052 794 : if (valsize < PGLZ_strategy_default->min_input_size)
1053 0 : return PointerGetDatum(NULL);
1054 :
1055 794 : tmp = (struct varlena *) palloc(PGLZ_MAX_OUTPUT(valsize));
1056 794 : if (pglz_compress(VARDATA_ANY(value), valsize,
1057 : (PGLZ_Header *) tmp, PGLZ_strategy_default) &&
1058 : VARSIZE(tmp) < VARSIZE_ANY(value))
1059 : {
1060 : /* successful compression */
1061 794 : return PointerGetDatum(tmp);
1062 : }
1063 : else
1064 : {
1065 : /* incompressible data */
1066 0 : pfree(tmp);
1067 0 : return PointerGetDatum(NULL);
1068 : }
1069 : }
1070 :
1071 :
1072 : /* ----------
1073 : * toast_save_datum -
1074 : *
1075 : * Save one single datum into the secondary relation and return
1076 : * a Datum reference for it.
1077 : * ----------
1078 : */
1079 : static Datum
1080 : toast_save_datum(Relation rel, Datum value,
1081 : bool use_wal, bool use_fsm)
1082 42 : {
1083 : Relation toastrel;
1084 : Relation toastidx;
1085 : HeapTuple toasttup;
1086 : TupleDesc toasttupDesc;
1087 : Datum t_values[3];
1088 : bool t_isnull[3];
1089 42 : CommandId mycid = GetCurrentCommandId(true);
1090 : struct varlena *result;
1091 : struct varatt_external toast_pointer;
1092 : struct
1093 : {
1094 : struct varlena hdr;
1095 : char data[TOAST_MAX_CHUNK_SIZE];
1096 : } chunk_data;
1097 : int32 chunk_size;
1098 42 : int32 chunk_seq = 0;
1099 : char *data_p;
1100 : int32 data_todo;
1101 :
1102 : /*
1103 : * Open the toast relation and its index. We can use the index to check
1104 : * uniqueness of the OID we assign to the toasted item, even though it has
1105 : * additional columns besides OID.
1106 : */
1107 42 : toastrel = heap_open(rel->rd_rel->reltoastrelid, RowExclusiveLock);
1108 42 : toasttupDesc = toastrel->rd_att;
1109 42 : toastidx = index_open(toastrel->rd_rel->reltoastidxid, RowExclusiveLock);
1110 :
1111 : /*
1112 : * Get the data pointer and length, and compute va_rawsize and va_extsize.
1113 : *
1114 : * va_rawsize is the size of the equivalent fully uncompressed datum, so
1115 : * we have to adjust for short headers.
1116 : *
1117 : * va_extsize is the actual size of the data payload in the toast records.
1118 : */
1119 42 : if (VARATT_IS_SHORT(value))
1120 : {
1121 0 : data_p = VARDATA_SHORT(value);
1122 0 : data_todo = VARSIZE_SHORT(value) - VARHDRSZ_SHORT;
1123 0 : toast_pointer.va_rawsize = data_todo + VARHDRSZ; /* as if not short */
1124 0 : toast_pointer.va_extsize = data_todo;
1125 : }
1126 42 : else if (VARATT_IS_COMPRESSED(value))
1127 : {
1128 38 : data_p = VARDATA(value);
1129 38 : data_todo = VARSIZE(value) - VARHDRSZ;
1130 : /* rawsize in a compressed datum is just the size of the payload */
1131 38 : toast_pointer.va_rawsize = VARRAWSIZE_4B_C(value) + VARHDRSZ;
1132 38 : toast_pointer.va_extsize = data_todo;
1133 : /* Assert that the numbers look like it's compressed */
1134 : Assert(VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer));
1135 : }
1136 : else
1137 : {
1138 4 : data_p = VARDATA(value);
1139 4 : data_todo = VARSIZE(value) - VARHDRSZ;
1140 4 : toast_pointer.va_rawsize = VARSIZE(value);
1141 4 : toast_pointer.va_extsize = data_todo;
1142 : }
1143 :
1144 42 : toast_pointer.va_valueid = GetNewOidWithIndex(toastrel, toastidx);
1145 42 : toast_pointer.va_toastrelid = rel->rd_rel->reltoastrelid;
1146 :
1147 : /*
1148 : * Initialize constant parts of the tuple data
1149 : */
1150 42 : t_values[0] = ObjectIdGetDatum(toast_pointer.va_valueid);
1151 42 : t_values[2] = PointerGetDatum(&chunk_data);
1152 42 : t_isnull[0] = false;
1153 42 : t_isnull[1] = false;
1154 42 : t_isnull[2] = false;
1155 :
1156 : /*
1157 : * Split up the item into chunks
1158 : */
1159 385 : while (data_todo > 0)
1160 : {
1161 : /*
1162 : * Calculate the size of this chunk
1163 : */
1164 301 : chunk_size = Min(TOAST_MAX_CHUNK_SIZE, data_todo);
1165 :
1166 : /*
1167 : * Build a tuple and store it
1168 : */
1169 301 : t_values[1] = Int32GetDatum(chunk_seq++);
1170 301 : SET_VARSIZE(&chunk_data, chunk_size + VARHDRSZ);
1171 301 : memcpy(VARDATA(&chunk_data), data_p, chunk_size);
1172 301 : toasttup = heap_form_tuple(toasttupDesc, t_values, t_isnull);
1173 301 : if (!HeapTupleIsValid(toasttup))
1174 0 : elog(ERROR, "failed to build TOAST tuple");
1175 :
1176 301 : heap_insert(toastrel, toasttup, mycid, use_wal, use_fsm);
1177 :
1178 : /*
1179 : * Create the index entry. We cheat a little here by not using
1180 : * FormIndexDatum: this relies on the knowledge that the index columns
1181 : * are the same as the initial columns of the table.
1182 : *
1183 : * Note also that there had better not be any user-created index on
1184 : * the TOAST table, since we don't bother to update anything else.
1185 : */
1186 301 : index_insert(toastidx, t_values, t_isnull,
1187 : &(toasttup->t_self),
1188 : toastrel, toastidx->rd_index->indisunique);
1189 :
1190 : /*
1191 : * Free memory
1192 : */
1193 301 : heap_freetuple(toasttup);
1194 :
1195 : /*
1196 : * Move on to next chunk
1197 : */
1198 301 : data_todo -= chunk_size;
1199 301 : data_p += chunk_size;
1200 : }
1201 :
1202 : /*
1203 : * Done - close toast relation
1204 : */
1205 42 : index_close(toastidx, RowExclusiveLock);
1206 42 : heap_close(toastrel, RowExclusiveLock);
1207 :
1208 : /*
1209 : * Create the TOAST pointer value that we'll return
1210 : */
1211 42 : result = (struct varlena *) palloc(TOAST_POINTER_SIZE);
1212 42 : SET_VARSIZE_EXTERNAL(result, TOAST_POINTER_SIZE);
1213 42 : memcpy(VARDATA_EXTERNAL(result), &toast_pointer, sizeof(toast_pointer));
1214 :
1215 42 : return PointerGetDatum(result);
1216 : }
1217 :
1218 :
1219 : /* ----------
1220 : * toast_delete_datum -
1221 : *
1222 : * Delete a single external stored value.
1223 : * ----------
1224 : */
1225 : static void
1226 : toast_delete_datum(Relation rel, Datum value)
1227 0 : {
1228 0 : struct varlena *attr = (struct varlena *) DatumGetPointer(value);
1229 : struct varatt_external toast_pointer;
1230 : Relation toastrel;
1231 : Relation toastidx;
1232 : ScanKeyData toastkey;
1233 : IndexScanDesc toastscan;
1234 : HeapTuple toasttup;
1235 :
1236 0 : if (!VARATT_IS_EXTERNAL(attr))
1237 0 : return;
1238 :
1239 : /* Must copy to access aligned fields */
1240 0 : VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
1241 :
1242 : /*
1243 : * Open the toast relation and its index
1244 : */
1245 0 : toastrel = heap_open(toast_pointer.va_toastrelid, RowExclusiveLock);
1246 0 : toastidx = index_open(toastrel->rd_rel->reltoastidxid, RowExclusiveLock);
1247 :
1248 : /*
1249 : * Setup a scan key to fetch from the index by va_valueid (we don't
1250 : * particularly care whether we see them in sequence or not)
1251 : */
1252 0 : ScanKeyInit(&toastkey,
1253 : (AttrNumber) 1,
1254 : BTEqualStrategyNumber, F_OIDEQ,
1255 : ObjectIdGetDatum(toast_pointer.va_valueid));
1256 :
1257 : /*
1258 : * Find the chunks by index
1259 : */
1260 0 : toastscan = index_beginscan(toastrel, toastidx,
1261 : SnapshotToast, 1, &toastkey);
1262 0 : while ((toasttup = index_getnext(toastscan, ForwardScanDirection)) != NULL)
1263 : {
1264 : /*
1265 : * Have a chunk, delete it
1266 : */
1267 0 : simple_heap_delete(toastrel, &toasttup->t_self);
1268 : }
1269 :
1270 : /*
1271 : * End scan and close relations
1272 : */
1273 0 : index_endscan(toastscan);
1274 0 : index_close(toastidx, RowExclusiveLock);
1275 0 : heap_close(toastrel, RowExclusiveLock);
1276 : }
1277 :
1278 :
1279 : /* ----------
1280 : * toast_fetch_datum -
1281 : *
1282 : * Reconstruct an in memory Datum from the chunks saved
1283 : * in the toast relation
1284 : * ----------
1285 : */
1286 : static struct varlena *
1287 : toast_fetch_datum(struct varlena * attr)
1288 89 : {
1289 : Relation toastrel;
1290 : Relation toastidx;
1291 : ScanKeyData toastkey;
1292 : IndexScanDesc toastscan;
1293 : HeapTuple ttup;
1294 : TupleDesc toasttupDesc;
1295 : struct varlena *result;
1296 : struct varatt_external toast_pointer;
1297 : int32 ressize;
1298 : int32 residx,
1299 : nextidx;
1300 : int32 numchunks;
1301 : Pointer chunk;
1302 : bool isnull;
1303 : char *chunkdata;
1304 : int32 chunksize;
1305 :
1306 : /* Must copy to access aligned fields */
1307 89 : VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
1308 :
1309 89 : ressize = toast_pointer.va_extsize;
1310 89 : numchunks = ((ressize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
1311 :
1312 89 : result = (struct varlena *) palloc(ressize + VARHDRSZ);
1313 :
1314 89 : if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))
1315 89 : SET_VARSIZE_COMPRESSED(result, ressize + VARHDRSZ);
1316 : else
1317 0 : SET_VARSIZE(result, ressize + VARHDRSZ);
1318 :
1319 : /*
1320 : * Open the toast relation and its index
1321 : */
1322 89 : toastrel = heap_open(toast_pointer.va_toastrelid, AccessShareLock);
1323 89 : toasttupDesc = toastrel->rd_att;
1324 89 : toastidx = index_open(toastrel->rd_rel->reltoastidxid, AccessShareLock);
1325 :
1326 : /*
1327 : * Setup a scan key to fetch from the index by va_valueid
1328 : */
1329 89 : ScanKeyInit(&toastkey,
1330 : (AttrNumber) 1,
1331 : BTEqualStrategyNumber, F_OIDEQ,
1332 : ObjectIdGetDatum(toast_pointer.va_valueid));
1333 :
1334 : /*
1335 : * Read the chunks by index
1336 : *
1337 : * Note that because the index is actually on (valueid, chunkidx) we will
1338 : * see the chunks in chunkidx order, even though we didn't explicitly ask
1339 : * for it.
1340 : */
1341 89 : nextidx = 0;
1342 :
1343 89 : toastscan = index_beginscan(toastrel, toastidx,
1344 : SnapshotToast, 1, &toastkey);
1345 416 : while ((ttup = index_getnext(toastscan, ForwardScanDirection)) != NULL)
1346 : {
1347 : /*
1348 : * Have a chunk, extract the sequence number and the data
1349 : */
1350 238 : residx = DatumGetInt32(fastgetattr(ttup, 2, toasttupDesc, &isnull));
1351 : Assert(!isnull);
1352 238 : chunk = DatumGetPointer(fastgetattr(ttup, 3, toasttupDesc, &isnull));
1353 : Assert(!isnull);
1354 238 : if (!VARATT_IS_EXTENDED(chunk))
1355 : {
1356 238 : chunksize = VARSIZE(chunk) - VARHDRSZ;
1357 238 : chunkdata = VARDATA(chunk);
1358 : }
1359 0 : else if (VARATT_IS_SHORT(chunk))
1360 : {
1361 : /* could happen due to heap_form_tuple doing its thing */
1362 0 : chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT;
1363 0 : chunkdata = VARDATA_SHORT(chunk);
1364 : }
1365 : else
1366 : {
1367 : /* should never happen */
1368 0 : elog(ERROR, "found toasted toast chunk");
1369 0 : chunksize = 0; /* keep compiler quiet */
1370 0 : chunkdata = NULL;
1371 : }
1372 :
1373 : /*
1374 : * Some checks on the data we've found
1375 : */
1376 238 : if (residx != nextidx)
1377 0 : elog(ERROR, "unexpected chunk number %d (expected %d) for toast value %u",
1378 : residx, nextidx,
1379 : toast_pointer.va_valueid);
1380 238 : if (residx < numchunks - 1)
1381 : {
1382 149 : if (chunksize != TOAST_MAX_CHUNK_SIZE)
1383 0 : elog(ERROR, "unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u",
1384 : chunksize, (int) TOAST_MAX_CHUNK_SIZE,
1385 : residx, numchunks,
1386 : toast_pointer.va_valueid);
1387 : }
1388 89 : else if (residx == numchunks - 1)
1389 : {
1390 89 : if ((residx * TOAST_MAX_CHUNK_SIZE + chunksize) != ressize)
1391 0 : elog(ERROR, "unexpected chunk size %d (expected %d) in final chunk %d for toast value %u",
1392 : chunksize,
1393 : (int) (ressize - residx * TOAST_MAX_CHUNK_SIZE),
1394 : residx,
1395 : toast_pointer.va_valueid);
1396 : }
1397 : else
1398 0 : elog(ERROR, "unexpected chunk number %d for toast value %u (out of range %d..%d)",
1399 : residx,
1400 : toast_pointer.va_valueid,
1401 : 0, numchunks - 1);
1402 :
1403 : /*
1404 : * Copy the data into proper place in our result
1405 : */
1406 238 : memcpy(VARDATA(result) + residx * TOAST_MAX_CHUNK_SIZE,
1407 : chunkdata,
1408 : chunksize);
1409 :
1410 238 : nextidx++;
1411 : }
1412 :
1413 : /*
1414 : * Final checks that we successfully fetched the datum
1415 : */
1416 89 : if (nextidx != numchunks)
1417 0 : elog(ERROR, "missing chunk number %d for toast value %u",
1418 : nextidx,
1419 : toast_pointer.va_valueid);
1420 :
1421 : /*
1422 : * End scan and close relations
1423 : */
1424 89 : index_endscan(toastscan);
1425 89 : index_close(toastidx, AccessShareLock);
1426 89 : heap_close(toastrel, AccessShareLock);
1427 :
1428 89 : return result;
1429 : }
1430 :
1431 : /* ----------
1432 : * toast_fetch_datum_slice -
1433 : *
1434 : * Reconstruct a segment of a Datum from the chunks saved
1435 : * in the toast relation
1436 : * ----------
1437 : */
1438 : static struct varlena *
1439 : toast_fetch_datum_slice(struct varlena * attr, int32 sliceoffset, int32 length)
1440 12 : {
1441 : Relation toastrel;
1442 : Relation toastidx;
1443 : ScanKeyData toastkey[3];
1444 : int nscankeys;
1445 : IndexScanDesc toastscan;
1446 : HeapTuple ttup;
1447 : TupleDesc toasttupDesc;
1448 : struct varlena *result;
1449 : struct varatt_external toast_pointer;
1450 : int32 attrsize;
1451 : int32 residx;
1452 : int32 nextidx;
1453 : int numchunks;
1454 : int startchunk;
1455 : int endchunk;
1456 : int32 startoffset;
1457 : int32 endoffset;
1458 : int totalchunks;
1459 : Pointer chunk;
1460 : bool isnull;
1461 : char *chunkdata;
1462 : int32 chunksize;
1463 : int32 chcpystrt;
1464 : int32 chcpyend;
1465 :
1466 : Assert(VARATT_IS_EXTERNAL(attr));
1467 :
1468 : /* Must copy to access aligned fields */
1469 12 : VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
1470 :
1471 : /*
1472 : * It's nonsense to fetch slices of a compressed datum -- this isn't lo_*
1473 : * we can't return a compressed datum which is meaningful to toast later
1474 : */
1475 : Assert(!VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer));
1476 :
1477 12 : attrsize = toast_pointer.va_extsize;
1478 12 : totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
1479 :
1480 12 : if (sliceoffset >= attrsize)
1481 : {
1482 0 : sliceoffset = 0;
1483 0 : length = 0;
1484 : }
1485 :
1486 12 : if (((sliceoffset + length) > attrsize) || length < 0)
1487 8 : length = attrsize - sliceoffset;
1488 :
1489 12 : result = (struct varlena *) palloc(length + VARHDRSZ);
1490 :
1491 12 : if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))
1492 0 : SET_VARSIZE_COMPRESSED(result, length + VARHDRSZ);
1493 : else
1494 12 : SET_VARSIZE(result, length + VARHDRSZ);
1495 :
1496 12 : if (length == 0)
1497 0 : return result; /* Can save a lot of work at this point! */
1498 :
1499 12 : startchunk = sliceoffset / TOAST_MAX_CHUNK_SIZE;
1500 12 : endchunk = (sliceoffset + length - 1) / TOAST_MAX_CHUNK_SIZE;
1501 12 : numchunks = (endchunk - startchunk) + 1;
1502 :
1503 12 : startoffset = sliceoffset % TOAST_MAX_CHUNK_SIZE;
1504 12 : endoffset = (sliceoffset + length - 1) % TOAST_MAX_CHUNK_SIZE;
1505 :
1506 : /*
1507 : * Open the toast relation and its index
1508 : */
1509 12 : toastrel = heap_open(toast_pointer.va_toastrelid, AccessShareLock);
1510 12 : toasttupDesc = toastrel->rd_att;
1511 12 : toastidx = index_open(toastrel->rd_rel->reltoastidxid, AccessShareLock);
1512 :
1513 : /*
1514 : * Setup a scan key to fetch from the index. This is either two keys or
1515 : * three depending on the number of chunks.
1516 : */
1517 12 : ScanKeyInit(&toastkey[0],
1518 : (AttrNumber) 1,
1519 : BTEqualStrategyNumber, F_OIDEQ,
1520 : ObjectIdGetDatum(toast_pointer.va_valueid));
1521 :
1522 : /*
1523 : * Use equality condition for one chunk, a range condition otherwise:
1524 : */
1525 12 : if (numchunks == 1)
1526 : {
1527 12 : ScanKeyInit(&toastkey[1],
1528 : (AttrNumber) 2,
1529 : BTEqualStrategyNumber, F_INT4EQ,
1530 : Int32GetDatum(startchunk));
1531 12 : nscankeys = 2;
1532 : }
1533 : else
1534 : {
1535 0 : ScanKeyInit(&toastkey[1],
1536 : (AttrNumber) 2,
1537 : BTGreaterEqualStrategyNumber, F_INT4GE,
1538 : Int32GetDatum(startchunk));
1539 0 : ScanKeyInit(&toastkey[2],
1540 : (AttrNumber) 2,
1541 : BTLessEqualStrategyNumber, F_INT4LE,
1542 : Int32GetDatum(endchunk));
1543 0 : nscankeys = 3;
1544 : }
1545 :
1546 : /*
1547 : * Read the chunks by index
1548 : *
1549 : * The index is on (valueid, chunkidx) so they will come in order
1550 : */
1551 12 : nextidx = startchunk;
1552 12 : toastscan = index_beginscan(toastrel, toastidx,
1553 : SnapshotToast, nscankeys, toastkey);
1554 36 : while ((ttup = index_getnext(toastscan, ForwardScanDirection)) != NULL)
1555 : {
1556 : /*
1557 : * Have a chunk, extract the sequence number and the data
1558 : */
1559 12 : residx = DatumGetInt32(fastgetattr(ttup, 2, toasttupDesc, &isnull));
1560 : Assert(!isnull);
1561 12 : chunk = DatumGetPointer(fastgetattr(ttup, 3, toasttupDesc, &isnull));
1562 : Assert(!isnull);
1563 12 : if (!VARATT_IS_EXTENDED(chunk))
1564 : {
1565 12 : chunksize = VARSIZE(chunk) - VARHDRSZ;
1566 12 : chunkdata = VARDATA(chunk);
1567 : }
1568 0 : else if (VARATT_IS_SHORT(chunk))
1569 : {
1570 : /* could happen due to heap_form_tuple doing its thing */
1571 0 : chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT;
1572 0 : chunkdata = VARDATA_SHORT(chunk);
1573 : }
1574 : else
1575 : {
1576 : /* should never happen */
1577 0 : elog(ERROR, "found toasted toast chunk");
1578 0 : chunksize = 0; /* keep compiler quiet */
1579 0 : chunkdata = NULL;
1580 : }
1581 :
1582 : /*
1583 : * Some checks on the data we've found
1584 : */
1585 12 : if ((residx != nextidx) || (residx > endchunk) || (residx < startchunk))
1586 0 : elog(ERROR, "unexpected chunk number %d (expected %d) for toast value %u",
1587 : residx, nextidx,
1588 : toast_pointer.va_valueid);
1589 12 : if (residx < totalchunks - 1)
1590 : {
1591 4 : if (chunksize != TOAST_MAX_CHUNK_SIZE)
1592 0 : elog(ERROR, "unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u when fetching slice",
1593 : chunksize, (int) TOAST_MAX_CHUNK_SIZE,
1594 : residx, totalchunks,
1595 : toast_pointer.va_valueid);
1596 : }
1597 8 : else if (residx == totalchunks - 1)
1598 : {
1599 8 : if ((residx * TOAST_MAX_CHUNK_SIZE + chunksize) != attrsize)
1600 0 : elog(ERROR, "unexpected chunk size %d (expected %d) in final chunk %d for toast value %u when fetching slice",
1601 : chunksize,
1602 : (int) (attrsize - residx * TOAST_MAX_CHUNK_SIZE),
1603 : residx,
1604 : toast_pointer.va_valueid);
1605 : }
1606 : else
1607 0 : elog(ERROR, "unexpected chunk number %d for toast value %u (out of range %d..%d)",
1608 : residx,
1609 : toast_pointer.va_valueid,
1610 : 0, totalchunks - 1);
1611 :
1612 : /*
1613 : * Copy the data into proper place in our result
1614 : */
1615 12 : chcpystrt = 0;
1616 12 : chcpyend = chunksize - 1;
1617 12 : if (residx == startchunk)
1618 12 : chcpystrt = startoffset;
1619 12 : if (residx == endchunk)
1620 12 : chcpyend = endoffset;
1621 :
1622 12 : memcpy(VARDATA(result) +
1623 : (residx * TOAST_MAX_CHUNK_SIZE - sliceoffset) + chcpystrt,
1624 : chunkdata + chcpystrt,
1625 : (chcpyend - chcpystrt) + 1);
1626 :
1627 12 : nextidx++;
1628 : }
1629 :
1630 : /*
1631 : * Final checks that we successfully fetched the datum
1632 : */
1633 12 : if (nextidx != (endchunk + 1))
1634 0 : elog(ERROR, "missing chunk number %d for toast value %u",
1635 : nextidx,
1636 : toast_pointer.va_valueid);
1637 :
1638 : /*
1639 : * End scan and close relations
1640 : */
1641 12 : index_endscan(toastscan);
1642 12 : index_close(toastidx, AccessShareLock);
1643 12 : heap_close(toastrel, AccessShareLock);
1644 :
1645 12 : return result;
1646 : }
|