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