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