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