Coverage Report

Created: 2026-10-16 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}