Coverage Report

Created: 2025-07-03 06:49

/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
}