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