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