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