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