/src/postgres/src/backend/access/table/toast_helper.c
Line | Count | Source (jump to first uncovered line) |
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-2025, 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 | struct varlena *old_value; |
53 | 0 | struct 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 | (struct varlena *) DatumGetPointer(ttc->ttc_oldvalues[i]); |
66 | 0 | new_value = |
67 | 0 | (struct 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 | 0 | if (att->attlen == -1 && !ttc->ttc_oldisnull[i] && |
74 | 0 | VARATT_IS_EXTERNAL_ONDISK(old_value)) |
75 | 0 | { |
76 | 0 | if (ttc->ttc_isnull[i] || |
77 | 0 | !VARATT_IS_EXTERNAL_ONDISK(new_value) || |
78 | 0 | memcmp(old_value, new_value, |
79 | 0 | VARSIZE_EXTERNAL(old_value)) != 0) |
80 | 0 | { |
81 | | /* |
82 | | * The old external stored value isn't needed any more |
83 | | * after the update |
84 | | */ |
85 | 0 | ttc->ttc_attr[i].tai_colflags |= TOASTCOL_NEEDS_DELETE_OLD; |
86 | 0 | ttc->ttc_flags |= TOAST_NEEDS_DELETE_OLD; |
87 | 0 | } |
88 | 0 | else |
89 | 0 | { |
90 | | /* |
91 | | * This attribute isn't changed by this update so we reuse |
92 | | * the original reference to the old value in the new |
93 | | * tuple. |
94 | | */ |
95 | 0 | ttc->ttc_attr[i].tai_colflags |= TOASTCOL_IGNORE; |
96 | 0 | continue; |
97 | 0 | } |
98 | 0 | } |
99 | 0 | } |
100 | 0 | else |
101 | 0 | { |
102 | | /* |
103 | | * For INSERT simply get the new value |
104 | | */ |
105 | 0 | new_value = (struct varlena *) DatumGetPointer(ttc->ttc_values[i]); |
106 | 0 | } |
107 | | |
108 | | /* |
109 | | * Handle NULL attributes |
110 | | */ |
111 | 0 | if (ttc->ttc_isnull[i]) |
112 | 0 | { |
113 | 0 | ttc->ttc_attr[i].tai_colflags |= TOASTCOL_IGNORE; |
114 | 0 | ttc->ttc_flags |= TOAST_HAS_NULLS; |
115 | 0 | continue; |
116 | 0 | } |
117 | | |
118 | | /* |
119 | | * Now look at varlena attributes |
120 | | */ |
121 | 0 | if (att->attlen == -1) |
122 | 0 | { |
123 | | /* |
124 | | * If the table's attribute says PLAIN always, force it so. |
125 | | */ |
126 | 0 | if (att->attstorage == TYPSTORAGE_PLAIN) |
127 | 0 | ttc->ttc_attr[i].tai_colflags |= TOASTCOL_IGNORE; |
128 | | |
129 | | /* |
130 | | * We took care of UPDATE above, so any external value we find |
131 | | * still in the tuple must be someone else's that we cannot reuse |
132 | | * (this includes the case of an out-of-line in-memory datum). |
133 | | * Fetch it back (without decompression, unless we are forcing |
134 | | * PLAIN storage). If necessary, we'll push it out as a new |
135 | | * external value below. |
136 | | */ |
137 | 0 | if (VARATT_IS_EXTERNAL(new_value)) |
138 | 0 | { |
139 | 0 | ttc->ttc_attr[i].tai_oldexternal = new_value; |
140 | 0 | if (att->attstorage == TYPSTORAGE_PLAIN) |
141 | 0 | new_value = detoast_attr(new_value); |
142 | 0 | else |
143 | 0 | new_value = detoast_external_attr(new_value); |
144 | 0 | ttc->ttc_values[i] = PointerGetDatum(new_value); |
145 | 0 | ttc->ttc_attr[i].tai_colflags |= TOASTCOL_NEEDS_FREE; |
146 | 0 | ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE); |
147 | 0 | } |
148 | | |
149 | | /* |
150 | | * Remember the size of this attribute |
151 | | */ |
152 | 0 | ttc->ttc_attr[i].tai_size = VARSIZE_ANY(new_value); |
153 | 0 | } |
154 | 0 | else |
155 | 0 | { |
156 | | /* |
157 | | * Not a varlena attribute, plain storage always |
158 | | */ |
159 | 0 | ttc->ttc_attr[i].tai_colflags |= TOASTCOL_IGNORE; |
160 | 0 | } |
161 | 0 | } |
162 | 0 | } |
163 | | |
164 | | /* |
165 | | * Find the largest varlena attribute that satisfies certain criteria. |
166 | | * |
167 | | * The relevant column must not be marked TOASTCOL_IGNORE, and if the |
168 | | * for_compression flag is passed as true, it must also not be marked |
169 | | * TOASTCOL_INCOMPRESSIBLE. |
170 | | * |
171 | | * The column must have attstorage EXTERNAL or EXTENDED if check_main is |
172 | | * false, and must have attstorage MAIN if check_main is true. |
173 | | * |
174 | | * The column must have a minimum size of MAXALIGN(TOAST_POINTER_SIZE); |
175 | | * if not, no benefit is to be expected by compressing it. |
176 | | * |
177 | | * The return value is the index of the biggest suitable column, or |
178 | | * -1 if there is none. |
179 | | */ |
180 | | int |
181 | | toast_tuple_find_biggest_attribute(ToastTupleContext *ttc, |
182 | | bool for_compression, bool check_main) |
183 | 0 | { |
184 | 0 | TupleDesc tupleDesc = ttc->ttc_rel->rd_att; |
185 | 0 | int numAttrs = tupleDesc->natts; |
186 | 0 | int biggest_attno = -1; |
187 | 0 | int32 biggest_size = MAXALIGN(TOAST_POINTER_SIZE); |
188 | 0 | int32 skip_colflags = TOASTCOL_IGNORE; |
189 | 0 | int i; |
190 | |
|
191 | 0 | if (for_compression) |
192 | 0 | skip_colflags |= TOASTCOL_INCOMPRESSIBLE; |
193 | |
|
194 | 0 | for (i = 0; i < numAttrs; i++) |
195 | 0 | { |
196 | 0 | Form_pg_attribute att = TupleDescAttr(tupleDesc, i); |
197 | |
|
198 | 0 | if ((ttc->ttc_attr[i].tai_colflags & skip_colflags) != 0) |
199 | 0 | continue; |
200 | 0 | if (VARATT_IS_EXTERNAL(DatumGetPointer(ttc->ttc_values[i]))) |
201 | 0 | continue; /* can't happen, toast_action would be PLAIN */ |
202 | 0 | if (for_compression && |
203 | 0 | VARATT_IS_COMPRESSED(DatumGetPointer(ttc->ttc_values[i]))) |
204 | 0 | continue; |
205 | 0 | if (check_main && att->attstorage != TYPSTORAGE_MAIN) |
206 | 0 | continue; |
207 | 0 | if (!check_main && att->attstorage != TYPSTORAGE_EXTENDED && |
208 | 0 | att->attstorage != TYPSTORAGE_EXTERNAL) |
209 | 0 | continue; |
210 | | |
211 | 0 | if (ttc->ttc_attr[i].tai_size > biggest_size) |
212 | 0 | { |
213 | 0 | biggest_attno = i; |
214 | 0 | biggest_size = ttc->ttc_attr[i].tai_size; |
215 | 0 | } |
216 | 0 | } |
217 | |
|
218 | 0 | return biggest_attno; |
219 | 0 | } |
220 | | |
221 | | /* |
222 | | * Try compression for an attribute. |
223 | | * |
224 | | * If we find that the attribute is not compressible, mark it so. |
225 | | */ |
226 | | void |
227 | | toast_tuple_try_compression(ToastTupleContext *ttc, int attribute) |
228 | 0 | { |
229 | 0 | Datum *value = &ttc->ttc_values[attribute]; |
230 | 0 | Datum new_value; |
231 | 0 | ToastAttrInfo *attr = &ttc->ttc_attr[attribute]; |
232 | |
|
233 | 0 | new_value = toast_compress_datum(*value, attr->tai_compression); |
234 | |
|
235 | 0 | if (DatumGetPointer(new_value) != NULL) |
236 | 0 | { |
237 | | /* successful compression */ |
238 | 0 | if ((attr->tai_colflags & TOASTCOL_NEEDS_FREE) != 0) |
239 | 0 | pfree(DatumGetPointer(*value)); |
240 | 0 | *value = new_value; |
241 | 0 | attr->tai_colflags |= TOASTCOL_NEEDS_FREE; |
242 | 0 | attr->tai_size = VARSIZE(DatumGetPointer(*value)); |
243 | 0 | ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE); |
244 | 0 | } |
245 | 0 | else |
246 | 0 | { |
247 | | /* incompressible, ignore on subsequent compression passes */ |
248 | 0 | attr->tai_colflags |= TOASTCOL_INCOMPRESSIBLE; |
249 | 0 | } |
250 | 0 | } |
251 | | |
252 | | /* |
253 | | * Move an attribute to external storage. |
254 | | */ |
255 | | void |
256 | | toast_tuple_externalize(ToastTupleContext *ttc, int attribute, int options) |
257 | 0 | { |
258 | 0 | Datum *value = &ttc->ttc_values[attribute]; |
259 | 0 | Datum old_value = *value; |
260 | 0 | ToastAttrInfo *attr = &ttc->ttc_attr[attribute]; |
261 | |
|
262 | 0 | attr->tai_colflags |= TOASTCOL_IGNORE; |
263 | 0 | *value = toast_save_datum(ttc->ttc_rel, old_value, attr->tai_oldexternal, |
264 | 0 | options); |
265 | 0 | if ((attr->tai_colflags & TOASTCOL_NEEDS_FREE) != 0) |
266 | 0 | pfree(DatumGetPointer(old_value)); |
267 | 0 | attr->tai_colflags |= TOASTCOL_NEEDS_FREE; |
268 | 0 | ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE); |
269 | 0 | } |
270 | | |
271 | | /* |
272 | | * Perform appropriate cleanup after one tuple has been subjected to TOAST. |
273 | | */ |
274 | | void |
275 | | toast_tuple_cleanup(ToastTupleContext *ttc) |
276 | 0 | { |
277 | 0 | TupleDesc tupleDesc = ttc->ttc_rel->rd_att; |
278 | 0 | int numAttrs = tupleDesc->natts; |
279 | | |
280 | | /* |
281 | | * Free allocated temp values |
282 | | */ |
283 | 0 | if ((ttc->ttc_flags & TOAST_NEEDS_FREE) != 0) |
284 | 0 | { |
285 | 0 | int i; |
286 | |
|
287 | 0 | for (i = 0; i < numAttrs; i++) |
288 | 0 | { |
289 | 0 | ToastAttrInfo *attr = &ttc->ttc_attr[i]; |
290 | |
|
291 | 0 | if ((attr->tai_colflags & TOASTCOL_NEEDS_FREE) != 0) |
292 | 0 | pfree(DatumGetPointer(ttc->ttc_values[i])); |
293 | 0 | } |
294 | 0 | } |
295 | | |
296 | | /* |
297 | | * Delete external values from the old tuple |
298 | | */ |
299 | 0 | if ((ttc->ttc_flags & TOAST_NEEDS_DELETE_OLD) != 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_DELETE_OLD) != 0) |
308 | 0 | toast_delete_datum(ttc->ttc_rel, ttc->ttc_oldvalues[i], false); |
309 | 0 | } |
310 | 0 | } |
311 | 0 | } |
312 | | |
313 | | /* |
314 | | * Check for external stored attributes and delete them from the secondary |
315 | | * relation. |
316 | | */ |
317 | | void |
318 | | toast_delete_external(Relation rel, const Datum *values, const bool *isnull, |
319 | | bool is_speculative) |
320 | 0 | { |
321 | 0 | TupleDesc tupleDesc = rel->rd_att; |
322 | 0 | int numAttrs = tupleDesc->natts; |
323 | 0 | int i; |
324 | |
|
325 | 0 | for (i = 0; i < numAttrs; i++) |
326 | 0 | { |
327 | 0 | if (TupleDescCompactAttr(tupleDesc, i)->attlen == -1) |
328 | 0 | { |
329 | 0 | Datum value = values[i]; |
330 | |
|
331 | 0 | if (isnull[i]) |
332 | 0 | continue; |
333 | 0 | else if (VARATT_IS_EXTERNAL_ONDISK(value)) |
334 | 0 | toast_delete_datum(rel, value, is_speculative); |
335 | 0 | } |
336 | 0 | } |
337 | 0 | } |