/src/postgres/src/backend/access/table/toast_helper.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * toast_helper.c |
4 | | * Helper functions for table AMs implementing compressed or |
5 | | * out-of-line storage of varlena attributes. |
6 | | * |
7 | | * Copyright (c) 2000-2026, PostgreSQL Global Development Group |
8 | | * |
9 | | * IDENTIFICATION |
10 | | * src/backend/access/table/toast_helper.c |
11 | | * |
12 | | *------------------------------------------------------------------------- |
13 | | */ |
14 | | |
15 | | #include "postgres.h" |
16 | | |
17 | | #include "access/detoast.h" |
18 | | #include "access/toast_helper.h" |
19 | | #include "access/toast_internals.h" |
20 | | #include "catalog/pg_type_d.h" |
21 | | #include "varatt.h" |
22 | | |
23 | | |
24 | | /* |
25 | | * Prepare to TOAST a tuple. |
26 | | * |
27 | | * tupleDesc, toast_values, and toast_isnull are required parameters; they |
28 | | * provide the necessary details about the tuple to be toasted. |
29 | | * |
30 | | * toast_oldvalues and toast_oldisnull should be NULL for a newly-inserted |
31 | | * tuple; for an update, they should describe the existing tuple. |
32 | | * |
33 | | * All of these arrays should have a length equal to tupleDesc->natts. |
34 | | * |
35 | | * On return, toast_flags and toast_attr will have been initialized. |
36 | | * toast_flags is just a single uint8, but toast_attr is a caller-provided |
37 | | * array with a length equal to tupleDesc->natts. The caller need not |
38 | | * perform any initialization of the array before calling this function. |
39 | | */ |
40 | | void |
41 | | toast_tuple_init(ToastTupleContext *ttc) |
42 | 0 | { |
43 | 0 | TupleDesc tupleDesc = ttc->ttc_rel->rd_att; |
44 | 0 | int numAttrs = tupleDesc->natts; |
45 | 0 | int i; |
46 | |
|
47 | 0 | ttc->ttc_flags = 0; |
48 | |
|
49 | 0 | for (i = 0; i < numAttrs; i++) |
50 | 0 | { |
51 | 0 | Form_pg_attribute att = TupleDescAttr(tupleDesc, i); |
52 | 0 | varlena *old_value; |
53 | 0 | varlena *new_value; |
54 | |
|
55 | 0 | ttc->ttc_attr[i].tai_colflags = 0; |
56 | 0 | ttc->ttc_attr[i].tai_oldexternal = NULL; |
57 | 0 | ttc->ttc_attr[i].tai_compression = att->attcompression; |
58 | |
|
59 | 0 | if (ttc->ttc_oldvalues != NULL) |
60 | 0 | { |
61 | | /* |
62 | | * For UPDATE get the old and new values of this attribute |
63 | | */ |
64 | 0 | old_value = |
65 | 0 | (varlena *) DatumGetPointer(ttc->ttc_oldvalues[i]); |
66 | 0 | new_value = |
67 | 0 | (varlena *) DatumGetPointer(ttc->ttc_values[i]); |
68 | | |
69 | | /* |
70 | | * If the old value is stored on disk, check if it has changed so |
71 | | * we have to delete it later. |
72 | | * |
73 | | * Note that TOAST pointers could have different vartags, for oid |
74 | | * or oid8, and these can have different sizes. |
75 | | */ |
76 | 0 | if (att->attlen == -1 && !ttc->ttc_oldisnull[i] && |
77 | 0 | VARATT_IS_EXTERNAL_ONDISK(old_value)) |
78 | 0 | { |
79 | 0 | if (ttc->ttc_isnull[i] || |
80 | 0 | !VARATT_IS_EXTERNAL_ONDISK(new_value) || |
81 | 0 | VARTAG_EXTERNAL(old_value) != VARTAG_EXTERNAL(new_value) || |
82 | 0 | memcmp(old_value, new_value, |
83 | 0 | VARSIZE_EXTERNAL(old_value)) != 0) |
84 | 0 | { |
85 | | /* |
86 | | * The old external stored value isn't needed any more |
87 | | * after the update |
88 | | */ |
89 | 0 | ttc->ttc_attr[i].tai_colflags |= TOASTCOL_NEEDS_DELETE_OLD; |
90 | 0 | ttc->ttc_flags |= TOAST_NEEDS_DELETE_OLD; |
91 | 0 | } |
92 | 0 | else |
93 | 0 | { |
94 | | /* |
95 | | * This attribute isn't changed by this update so we reuse |
96 | | * the original reference to the old value in the new |
97 | | * tuple. |
98 | | */ |
99 | 0 | ttc->ttc_attr[i].tai_colflags |= TOASTCOL_IGNORE; |
100 | 0 | continue; |
101 | 0 | } |
102 | 0 | } |
103 | 0 | } |
104 | 0 | else |
105 | 0 | { |
106 | | /* |
107 | | * For INSERT simply get the new value |
108 | | */ |
109 | 0 | new_value = (varlena *) DatumGetPointer(ttc->ttc_values[i]); |
110 | 0 | } |
111 | | |
112 | | /* |
113 | | * Handle NULL attributes |
114 | | */ |
115 | 0 | if (ttc->ttc_isnull[i]) |
116 | 0 | { |
117 | 0 | ttc->ttc_attr[i].tai_colflags |= TOASTCOL_IGNORE; |
118 | 0 | ttc->ttc_flags |= TOAST_HAS_NULLS; |
119 | 0 | continue; |
120 | 0 | } |
121 | | |
122 | | /* |
123 | | * Now look at varlena attributes |
124 | | */ |
125 | 0 | if (att->attlen == -1) |
126 | 0 | { |
127 | | /* |
128 | | * If the table's attribute says PLAIN always, force it so. |
129 | | */ |
130 | 0 | if (att->attstorage == TYPSTORAGE_PLAIN) |
131 | 0 | ttc->ttc_attr[i].tai_colflags |= TOASTCOL_IGNORE; |
132 | | |
133 | | /* |
134 | | * We took care of UPDATE above, so any external value we find |
135 | | * still in the tuple must be someone else's that we cannot reuse |
136 | | * (this includes the case of an out-of-line in-memory datum). |
137 | | * Fetch it back (without decompression, unless we are forcing |
138 | | * PLAIN storage). If necessary, we'll push it out as a new |
139 | | * external value below. |
140 | | */ |
141 | 0 | if (VARATT_IS_EXTERNAL(new_value)) |
142 | 0 | { |
143 | 0 | ttc->ttc_attr[i].tai_oldexternal = new_value; |
144 | 0 | if (att->attstorage == TYPSTORAGE_PLAIN) |
145 | 0 | new_value = detoast_attr(new_value); |
146 | 0 | else |
147 | 0 | new_value = detoast_external_attr(new_value); |
148 | 0 | ttc->ttc_values[i] = PointerGetDatum(new_value); |
149 | 0 | ttc->ttc_attr[i].tai_colflags |= TOASTCOL_NEEDS_FREE; |
150 | 0 | ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE); |
151 | 0 | } |
152 | | |
153 | | /* |
154 | | * Remember the size of this attribute |
155 | | */ |
156 | 0 | ttc->ttc_attr[i].tai_size = VARSIZE_ANY(new_value); |
157 | 0 | } |
158 | 0 | else |
159 | 0 | { |
160 | | /* |
161 | | * Not a varlena attribute, plain storage always |
162 | | */ |
163 | 0 | ttc->ttc_attr[i].tai_colflags |= TOASTCOL_IGNORE; |
164 | 0 | } |
165 | 0 | } |
166 | 0 | } |
167 | | |
168 | | /* |
169 | | * Find the largest varlena attribute that satisfies certain criteria. |
170 | | * |
171 | | * The relevant column must not be marked TOASTCOL_IGNORE, and if the |
172 | | * for_compression flag is passed as true, it must also not be marked |
173 | | * TOASTCOL_INCOMPRESSIBLE. |
174 | | * |
175 | | * The column must have attstorage EXTERNAL or EXTENDED if check_main is |
176 | | * false, and must have attstorage MAIN if check_main is true. |
177 | | * |
178 | | * The column must be larger than the TOAST pointer that would replace it; |
179 | | * if not, no benefit is to be expected by compressing it. Note that this |
180 | | * choice depends on the TOAST value type, oid or oid8. |
181 | | * |
182 | | * The return value is the index of the biggest suitable column, or |
183 | | * -1 if there is none. |
184 | | */ |
185 | | int |
186 | | toast_tuple_find_biggest_attribute(ToastTupleContext *ttc, |
187 | | bool for_compression, bool check_main) |
188 | 0 | { |
189 | 0 | TupleDesc tupleDesc = ttc->ttc_rel->rd_att; |
190 | 0 | int numAttrs = tupleDesc->natts; |
191 | 0 | int biggest_attno = -1; |
192 | 0 | int32 biggest_size; |
193 | 0 | int32 skip_colflags = TOASTCOL_IGNORE; |
194 | 0 | int i; |
195 | | |
196 | | /* |
197 | | * Size of the TOAST pointer this relation would use. A relation without |
198 | | * a TOAST table cannot have any of its attributes moved out-of-line, but |
199 | | * it can still have some of them compressed. Fall back to the oid size |
200 | | * in that case. |
201 | | */ |
202 | 0 | if (RelationGetToastChunkIdType(ttc->ttc_rel) == OID8OID) |
203 | 0 | biggest_size = MAXALIGN(TOAST_OID8_POINTER_SIZE); |
204 | 0 | else |
205 | 0 | biggest_size = MAXALIGN(TOAST_OID_POINTER_SIZE); |
206 | |
|
207 | 0 | if (for_compression) |
208 | 0 | skip_colflags |= TOASTCOL_INCOMPRESSIBLE; |
209 | |
|
210 | 0 | for (i = 0; i < numAttrs; i++) |
211 | 0 | { |
212 | 0 | Form_pg_attribute att = TupleDescAttr(tupleDesc, i); |
213 | |
|
214 | 0 | if ((ttc->ttc_attr[i].tai_colflags & skip_colflags) != 0) |
215 | 0 | continue; |
216 | 0 | if (VARATT_IS_EXTERNAL(DatumGetPointer(ttc->ttc_values[i]))) |
217 | 0 | continue; /* can't happen, toast_action would be PLAIN */ |
218 | 0 | if (for_compression && |
219 | 0 | VARATT_IS_COMPRESSED(DatumGetPointer(ttc->ttc_values[i]))) |
220 | 0 | continue; |
221 | 0 | if (check_main && att->attstorage != TYPSTORAGE_MAIN) |
222 | 0 | continue; |
223 | 0 | if (!check_main && att->attstorage != TYPSTORAGE_EXTENDED && |
224 | 0 | att->attstorage != TYPSTORAGE_EXTERNAL) |
225 | 0 | continue; |
226 | | |
227 | 0 | if (ttc->ttc_attr[i].tai_size > biggest_size) |
228 | 0 | { |
229 | 0 | biggest_attno = i; |
230 | 0 | biggest_size = ttc->ttc_attr[i].tai_size; |
231 | 0 | } |
232 | 0 | } |
233 | |
|
234 | 0 | return biggest_attno; |
235 | 0 | } |
236 | | |
237 | | /* |
238 | | * Try compression for an attribute. |
239 | | * |
240 | | * If we find that the attribute is not compressible, mark it so. |
241 | | */ |
242 | | void |
243 | | toast_tuple_try_compression(ToastTupleContext *ttc, int attribute) |
244 | 0 | { |
245 | 0 | Datum *value = &ttc->ttc_values[attribute]; |
246 | 0 | Datum new_value; |
247 | 0 | ToastAttrInfo *attr = &ttc->ttc_attr[attribute]; |
248 | |
|
249 | 0 | new_value = toast_compress_datum(*value, attr->tai_compression); |
250 | |
|
251 | 0 | if (DatumGetPointer(new_value) != NULL) |
252 | 0 | { |
253 | | /* successful compression */ |
254 | 0 | if ((attr->tai_colflags & TOASTCOL_NEEDS_FREE) != 0) |
255 | 0 | pfree(DatumGetPointer(*value)); |
256 | 0 | *value = new_value; |
257 | 0 | attr->tai_colflags |= TOASTCOL_NEEDS_FREE; |
258 | 0 | attr->tai_size = VARSIZE(DatumGetPointer(*value)); |
259 | 0 | ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE); |
260 | 0 | } |
261 | 0 | else |
262 | 0 | { |
263 | | /* incompressible, ignore on subsequent compression passes */ |
264 | 0 | attr->tai_colflags |= TOASTCOL_INCOMPRESSIBLE; |
265 | 0 | } |
266 | 0 | } |
267 | | |
268 | | /* |
269 | | * Move an attribute to external storage. |
270 | | */ |
271 | | void |
272 | | toast_tuple_externalize(ToastTupleContext *ttc, int attribute, uint32 options) |
273 | 0 | { |
274 | 0 | Datum *value = &ttc->ttc_values[attribute]; |
275 | 0 | Datum old_value = *value; |
276 | 0 | ToastAttrInfo *attr = &ttc->ttc_attr[attribute]; |
277 | |
|
278 | 0 | attr->tai_colflags |= TOASTCOL_IGNORE; |
279 | 0 | *value = toast_save_datum(ttc->ttc_rel, old_value, attr->tai_oldexternal, |
280 | 0 | options); |
281 | 0 | if ((attr->tai_colflags & TOASTCOL_NEEDS_FREE) != 0) |
282 | 0 | pfree(DatumGetPointer(old_value)); |
283 | 0 | attr->tai_colflags |= TOASTCOL_NEEDS_FREE; |
284 | 0 | ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE); |
285 | 0 | } |
286 | | |
287 | | /* |
288 | | * Perform appropriate cleanup after one tuple has been subjected to TOAST. |
289 | | */ |
290 | | void |
291 | | toast_tuple_cleanup(ToastTupleContext *ttc) |
292 | 0 | { |
293 | 0 | TupleDesc tupleDesc = ttc->ttc_rel->rd_att; |
294 | 0 | int numAttrs = tupleDesc->natts; |
295 | | |
296 | | /* |
297 | | * Free allocated temp values |
298 | | */ |
299 | 0 | if ((ttc->ttc_flags & TOAST_NEEDS_FREE) != 0) |
300 | 0 | { |
301 | 0 | int i; |
302 | |
|
303 | 0 | for (i = 0; i < numAttrs; i++) |
304 | 0 | { |
305 | 0 | ToastAttrInfo *attr = &ttc->ttc_attr[i]; |
306 | |
|
307 | 0 | if ((attr->tai_colflags & TOASTCOL_NEEDS_FREE) != 0) |
308 | 0 | pfree(DatumGetPointer(ttc->ttc_values[i])); |
309 | 0 | } |
310 | 0 | } |
311 | | |
312 | | /* |
313 | | * Delete external values from the old tuple |
314 | | */ |
315 | 0 | if ((ttc->ttc_flags & TOAST_NEEDS_DELETE_OLD) != 0) |
316 | 0 | { |
317 | 0 | int i; |
318 | |
|
319 | 0 | for (i = 0; i < numAttrs; i++) |
320 | 0 | { |
321 | 0 | ToastAttrInfo *attr = &ttc->ttc_attr[i]; |
322 | |
|
323 | 0 | if ((attr->tai_colflags & TOASTCOL_NEEDS_DELETE_OLD) != 0) |
324 | 0 | toast_delete_datum(ttc->ttc_rel, ttc->ttc_oldvalues[i], false); |
325 | 0 | } |
326 | 0 | } |
327 | 0 | } |
328 | | |
329 | | /* |
330 | | * Check for external stored attributes and delete them from the secondary |
331 | | * relation. |
332 | | */ |
333 | | void |
334 | | toast_delete_external(Relation rel, const Datum *values, const bool *isnull, |
335 | | bool is_speculative) |
336 | 0 | { |
337 | 0 | TupleDesc tupleDesc = rel->rd_att; |
338 | 0 | int numAttrs = tupleDesc->natts; |
339 | 0 | int i; |
340 | |
|
341 | 0 | for (i = 0; i < numAttrs; i++) |
342 | 0 | { |
343 | 0 | if (TupleDescCompactAttr(tupleDesc, i)->attlen == -1) |
344 | 0 | { |
345 | 0 | Datum value = values[i]; |
346 | |
|
347 | 0 | if (isnull[i]) |
348 | 0 | continue; |
349 | 0 | else if (VARATT_IS_EXTERNAL_ONDISK(DatumGetPointer(value))) |
350 | 0 | toast_delete_datum(rel, value, is_speculative); |
351 | 0 | } |
352 | 0 | } |
353 | 0 | } |