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