Age Owner TLA 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
751 rhaas 47 CBC 70179 : toast_compress_datum(Datum value, char cmethod)
48 : {
49 70179 : struct varlena *tmp = NULL;
50 : int32 valsize;
697 tgl 51 70179 : ToastCompressionId cmid = TOAST_INVALID_COMPRESSION_ID;
52 :
1371 rhaas 53 70179 : Assert(!VARATT_IS_EXTERNAL(DatumGetPointer(value)));
54 70179 : Assert(!VARATT_IS_COMPRESSED(DatumGetPointer(value)));
55 :
751 56 70179 : valsize = VARSIZE_ANY_EXHDR(DatumGetPointer(value));
57 :
58 : /* If the compression method is not valid, use the current default */
682 tgl 59 70179 : if (!CompressionMethodIsValid(cmethod))
60 70143 : cmethod = default_toast_compression;
61 :
62 : /*
63 : * Call appropriate compression routine for the compression method.
64 : */
751 rhaas 65 70179 : switch (cmethod)
66 : {
67 70161 : case TOAST_PGLZ_COMPRESSION:
68 70161 : tmp = pglz_compress_datum((const struct varlena *) value);
69 70161 : cmid = TOAST_PGLZ_COMPRESSION_ID;
70 70161 : break;
71 18 : case TOAST_LZ4_COMPRESSION:
72 18 : tmp = lz4_compress_datum((const struct varlena *) value);
73 18 : cmid = TOAST_LZ4_COMPRESSION_ID;
74 18 : break;
751 rhaas 75 UBC 0 : default:
76 0 : elog(ERROR, "invalid compression method %c", cmethod);
77 : }
78 :
751 rhaas 79 CBC 70179 : if (tmp == NULL)
80 2775 : 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 67404 : if (VARSIZE(tmp) < valsize - 2)
93 : {
94 : /* successful compression */
95 67404 : Assert(cmid != TOAST_INVALID_COMPRESSION_ID);
748 tgl 96 67404 : TOAST_COMPRESS_SET_SIZE_AND_COMPRESS_METHOD(tmp, valsize, cmid);
1371 rhaas 97 67404 : return PointerGetDatum(tmp);
98 : }
99 : else
100 : {
101 : /* incompressible data */
1371 rhaas 102 UBC 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
1371 rhaas 120 CBC 32344 : 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 32344 : 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 32344 : int32 chunk_seq = 0;
142 : char *data_p;
143 : int32 data_todo;
144 32344 : Pointer dval = DatumGetPointer(value);
145 : int num_indexes;
146 : int validIndex;
147 :
148 32344 : 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 32344 : toastrel = table_open(rel->rd_rel->reltoastrelid, RowExclusiveLock);
156 32344 : toasttupDesc = toastrel->rd_att;
157 :
158 : /* Open all the toast indexes and look for the valid one */
159 32344 : 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 32344 : if (VARATT_IS_SHORT(dval))
175 : {
1371 rhaas 176 UBC 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 */
751 179 0 : toast_pointer.va_extinfo = data_todo;
180 : }
1371 rhaas 181 CBC 32344 : else if (VARATT_IS_COMPRESSED(dval))
182 : {
183 29246 : data_p = VARDATA(dval);
184 29246 : data_todo = VARSIZE(dval) - VARHDRSZ;
185 : /* rawsize in a compressed datum is just the size of the payload */
748 tgl 186 29246 : toast_pointer.va_rawsize = VARDATA_COMPRESSED_GET_EXTSIZE(dval) + VARHDRSZ;
187 :
188 : /* set external size and compression method */
189 29246 : 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 */
1371 rhaas 192 29246 : Assert(VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer));
193 : }
194 : else
195 : {
196 3098 : data_p = VARDATA(dval);
197 3098 : data_todo = VARSIZE(dval) - VARHDRSZ;
198 3098 : toast_pointer.va_rawsize = VARSIZE(dval);
751 199 3098 : 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 : */
1371 210 32344 : if (OidIsValid(rel->rd_toastoid))
211 293 : toast_pointer.va_toastrelid = rel->rd_toastoid;
212 : else
213 32051 : 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 32344 : if (!OidIsValid(rel->rd_toastoid))
228 : {
229 : /* normal case: just choose an unused OID */
230 32051 : toast_pointer.va_valueid =
231 32051 : GetNewOidWithIndex(toastrel,
232 32051 : RelationGetRelid(toastidxs[validIndex]),
233 : (AttrNumber) 1);
234 : }
235 : else
236 : {
237 : /* rewrite case: check to see if value was in old toast table */
238 293 : toast_pointer.va_valueid = InvalidOid;
239 293 : if (oldexternal != NULL)
240 : {
241 : struct varatt_external old_toast_pointer;
242 :
243 293 : Assert(VARATT_IS_EXTERNAL_ONDISK(oldexternal));
244 : /* Must copy to access aligned fields */
245 293 : VARATT_EXTERNAL_GET_POINTER(old_toast_pointer, oldexternal);
246 293 : if (old_toast_pointer.va_toastrelid == rel->rd_toastoid)
247 : {
248 : /* This value came from the old toast table; reuse its OID */
249 293 : 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 293 : if (toastrel_valueid_exists(toastrel,
269 : toast_pointer.va_valueid))
270 : {
271 : /* Match, so short-circuit the data storage loop below */
1371 rhaas 272 UBC 0 : data_todo = 0;
273 : }
274 : }
275 : }
1371 rhaas 276 CBC 293 : 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 : {
1371 rhaas 284 UBC 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 : toast_pointer.va_valueid));
290 : }
291 : }
292 :
293 : /*
294 : * Initialize constant parts of the tuple data
295 : */
1371 rhaas 296 CBC 32344 : t_values[0] = ObjectIdGetDatum(toast_pointer.va_valueid);
297 32344 : t_values[2] = PointerGetDatum(&chunk_data);
298 32344 : t_isnull[0] = false;
299 32344 : t_isnull[1] = false;
300 32344 : t_isnull[2] = false;
301 :
302 : /*
303 : * Split up the item into chunks
304 : */
305 139547 : while (data_todo > 0)
306 : {
307 : int i;
308 :
309 107203 : CHECK_FOR_INTERRUPTS();
310 :
311 : /*
312 : * Calculate the size of this chunk
313 : */
314 107203 : chunk_size = Min(TOAST_MAX_CHUNK_SIZE, data_todo);
315 :
316 : /*
317 : * Build a tuple and store it
318 : */
319 107203 : t_values[1] = Int32GetDatum(chunk_seq++);
320 107203 : SET_VARSIZE(&chunk_data, chunk_size + VARHDRSZ);
321 107203 : memcpy(VARDATA(&chunk_data), data_p, chunk_size);
322 107203 : toasttup = heap_form_tuple(toasttupDesc, t_values, t_isnull);
323 :
324 107203 : 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 214406 : for (i = 0; i < num_indexes; i++)
338 : {
339 : /* Only index relations marked as ready can be updated */
340 107203 : if (toastidxs[i]->rd_index->indisready)
341 107203 : index_insert(toastidxs[i], t_values, t_isnull,
342 : &(toasttup->t_self),
343 : toastrel,
344 107203 : toastidxs[i]->rd_index->indisunique ?
345 : UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
346 : false, NULL);
347 : }
348 :
349 : /*
350 : * Free memory
351 : */
352 107203 : heap_freetuple(toasttup);
353 :
354 : /*
355 : * Move on to next chunk
356 : */
357 107203 : data_todo -= chunk_size;
358 107203 : 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 : */
487 michael 366 32344 : toast_close_indexes(toastidxs, num_indexes, NoLock);
367 32344 : table_close(toastrel, NoLock);
368 :
369 : /*
370 : * Create the TOAST pointer value that we'll return
371 : */
1371 rhaas 372 32344 : result = (struct varlena *) palloc(TOAST_POINTER_SIZE);
373 32344 : SET_VARTAG_EXTERNAL(result, VARTAG_ONDISK);
374 32344 : memcpy(VARDATA_EXTERNAL(result), &toast_pointer, sizeof(toast_pointer));
375 :
376 32344 : return PointerGetDatum(result);
377 : }
378 :
379 : /* ----------
380 : * toast_delete_datum -
381 : *
382 : * Delete a single external stored value.
383 : * ----------
384 : */
385 : void
386 458 : toast_delete_datum(Relation rel, Datum value, bool is_speculative)
387 : {
388 458 : 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 458 : if (!VARATT_IS_EXTERNAL_ONDISK(attr))
1371 rhaas 400 UBC 0 : return;
401 :
402 : /* Must copy to access aligned fields */
1371 rhaas 403 CBC 458 : VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
404 :
405 : /*
406 : * Open the toast relation and its indexes
407 : */
408 458 : toastrel = table_open(toast_pointer.va_toastrelid, RowExclusiveLock);
409 :
410 : /* Fetch valid relation used for process */
411 458 : 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 458 : 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 458 : init_toast_snapshot(&SnapshotToast);
430 458 : toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
431 : &SnapshotToast, 1, &toastkey);
432 2022 : while ((toasttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
433 : {
434 : /*
435 : * Have a chunk, delete it
436 : */
437 1564 : if (is_speculative)
438 5 : heap_abort_speculative(toastrel, &toasttup->t_self);
439 : else
440 1559 : 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 458 : systable_endscan_ordered(toastscan);
487 michael 449 458 : toast_close_indexes(toastidxs, num_indexes, NoLock);
450 458 : 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
1371 rhaas 462 293 : toastrel_valueid_exists(Relation toastrel, Oid valueid)
463 : {
464 293 : 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 293 : 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 293 : ScanKeyInit(&toastkey,
481 : (AttrNumber) 1,
482 : BTEqualStrategyNumber, F_OIDEQ,
483 : ObjectIdGetDatum(valueid));
484 :
485 : /*
486 : * Is there any such chunk?
487 : */
488 293 : toastscan = systable_beginscan(toastrel,
489 293 : RelationGetRelid(toastidxs[validIndex]),
490 : true, SnapshotAny, 1, &toastkey);
491 :
492 293 : if (systable_getnext(toastscan) != NULL)
1371 rhaas 493 UBC 0 : result = true;
494 :
1371 rhaas 495 CBC 293 : systable_endscan(toastscan);
496 :
497 : /* Clean up */
498 293 : toast_close_indexes(toastidxs, num_indexes, RowExclusiveLock);
499 :
500 293 : 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
1371 rhaas 510 UBC 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
1371 rhaas 531 CBC 344 : 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 344 : toastrel = table_open(toastoid, lock);
541 :
542 : /* Look for the valid index of the toast relation */
543 344 : validIndex = toast_open_indexes(toastrel,
544 : lock,
545 : &toastidxs,
546 : &num_indexes);
547 344 : validIndexOid = RelationGetRelid(toastidxs[validIndex]);
548 :
549 : /* Close the toast relation and all its indexes */
1114 noah 550 344 : toast_close_indexes(toastidxs, num_indexes, NoLock);
551 344 : table_close(toastrel, NoLock);
552 :
1371 rhaas 553 344 : 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 73142 : toast_open_indexes(Relation toastrel,
566 : LOCKMODE lock,
567 : Relation **toastidxs,
568 : int *num_indexes)
569 : {
570 73142 : int i = 0;
571 73142 : int res = 0;
572 73142 : bool found = false;
573 : List *indexlist;
574 : ListCell *lc;
575 :
576 : /* Get index list of the toast relation */
577 73142 : indexlist = RelationGetIndexList(toastrel);
578 73142 : Assert(indexlist != NIL);
579 :
580 73142 : *num_indexes = list_length(indexlist);
581 :
582 : /* Open all the index relations */
583 73142 : *toastidxs = (Relation *) palloc(*num_indexes * sizeof(Relation));
584 146284 : foreach(lc, indexlist)
585 73142 : (*toastidxs)[i++] = index_open(lfirst_oid(lc), lock);
586 :
587 : /* Fetch the first valid index in list */
588 73142 : for (i = 0; i < *num_indexes; i++)
589 : {
590 73142 : Relation toastidx = (*toastidxs)[i];
591 :
592 73142 : if (toastidx->rd_index->indisvalid)
593 : {
594 73142 : res = i;
595 73142 : found = true;
596 73142 : 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 73142 : 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 73142 : if (!found)
1371 rhaas 611 UBC 0 : elog(ERROR, "no valid index found for toast relation with Oid %u",
612 : RelationGetRelid(toastrel));
613 :
1371 rhaas 614 CBC 73142 : 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 73139 : toast_close_indexes(Relation *toastidxs, int num_indexes, LOCKMODE lock)
625 : {
626 : int i;
627 :
628 : /* Close relations and clean up things */
629 146278 : for (i = 0; i < num_indexes; i++)
630 73139 : index_close(toastidxs[i], lock);
631 73139 : pfree(toastidxs);
632 73139 : }
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 50908 : init_toast_snapshot(Snapshot toast_snapshot)
644 : {
645 50908 : 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 50908 : if (snapshot == NULL)
689 tgl 661 UBC 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 : */
414 andres 670 CBC 50908 : Assert(HaveRegisteredOrActiveSnapshot());
671 :
1371 rhaas 672 50908 : InitToastSnapshot(*toast_snapshot, snapshot->lsn, snapshot->whenTaken);
673 50908 : }
|