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/common/detoast.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * detoast.c
4
 *    Retrieve compressed or external variable size attributes.
5
 *
6
 * Copyright (c) 2000-2026, PostgreSQL Global Development Group
7
 *
8
 * IDENTIFICATION
9
 *    src/backend/access/common/detoast.c
10
 *
11
 *-------------------------------------------------------------------------
12
 */
13
14
#include "postgres.h"
15
16
#include "access/detoast.h"
17
#include "access/table.h"
18
#include "access/tableam.h"
19
#include "access/toast_compression.h"
20
#include "access/toast_internals.h"
21
#include "common/int.h"
22
#include "common/pg_lzcompress.h"
23
#include "utils/expandeddatum.h"
24
#include "utils/rel.h"
25
26
static varlena *toast_fetch_datum(varlena *attr);
27
static varlena *toast_fetch_datum_slice(varlena *attr,
28
                    int32 sliceoffset,
29
                    int32 slicelength);
30
static varlena *toast_decompress_datum(varlena *attr);
31
static varlena *toast_decompress_datum_slice(varlena *attr, int32 slicelength);
32
33
/* ----------
34
 * detoast_external_attr -
35
 *
36
 *  Public entry point to get back a toasted value from
37
 *  external source (possibly still in compressed format).
38
 *
39
 * This will return a datum that contains all the data internally, ie, not
40
 * relying on external storage or memory, but it can still be compressed or
41
 * have a short header.  Note some callers assume that if the input is an
42
 * EXTERNAL datum, the result will be a pfree'able chunk.
43
 * ----------
44
 */
45
varlena *
46
detoast_external_attr(varlena *attr)
47
0
{
48
0
  varlena    *result;
49
50
0
  if (VARATT_IS_EXTERNAL_ONDISK(attr))
51
0
  {
52
    /*
53
     * This is an external stored plain value
54
     */
55
0
    result = toast_fetch_datum(attr);
56
0
  }
57
0
  else if (VARATT_IS_EXTERNAL_INDIRECT(attr))
58
0
  {
59
    /*
60
     * This is an indirect pointer --- dereference it
61
     */
62
0
    varatt_indirect redirect;
63
64
0
    VARATT_EXTERNAL_GET_POINTER(redirect, attr);
65
0
    attr = (varlena *) redirect.pointer;
66
67
    /* nested indirect Datums aren't allowed */
68
0
    Assert(!VARATT_IS_EXTERNAL_INDIRECT(attr));
69
70
    /* recurse if value is still external in some other way */
71
0
    if (VARATT_IS_EXTERNAL(attr))
72
0
      return detoast_external_attr(attr);
73
74
    /*
75
     * Copy into the caller's memory context, in case caller tries to
76
     * pfree the result.
77
     */
78
0
    result = (varlena *) palloc(VARSIZE_ANY(attr));
79
0
    memcpy(result, attr, VARSIZE_ANY(attr));
80
0
  }
81
0
  else if (VARATT_IS_EXTERNAL_EXPANDED(attr))
82
0
  {
83
    /*
84
     * This is an expanded-object pointer --- get flat format
85
     */
86
0
    ExpandedObjectHeader *eoh;
87
0
    Size    resultsize;
88
89
0
    eoh = DatumGetEOHP(PointerGetDatum(attr));
90
0
    resultsize = EOH_get_flat_size(eoh);
91
0
    result = (varlena *) palloc(resultsize);
92
0
    EOH_flatten_into(eoh, result, resultsize);
93
0
  }
94
0
  else
95
0
  {
96
    /*
97
     * This is a plain value inside of the main tuple - why am I called?
98
     */
99
0
    result = attr;
100
0
  }
101
102
0
  return result;
103
0
}
104
105
106
/* ----------
107
 * detoast_attr -
108
 *
109
 *  Public entry point to get back a toasted value from compression
110
 *  or external storage.  The result is always non-extended varlena form.
111
 *
112
 * Note some callers assume that if the input is an EXTERNAL or COMPRESSED
113
 * datum, the result will be a pfree'able chunk.
114
 * ----------
115
 */
116
varlena *
117
detoast_attr(varlena *attr)
118
0
{
119
0
  if (VARATT_IS_EXTERNAL_ONDISK(attr))
120
0
  {
121
    /*
122
     * This is an externally stored datum --- fetch it back from there
123
     */
124
0
    attr = toast_fetch_datum(attr);
125
    /* If it's compressed, decompress it */
126
0
    if (VARATT_IS_COMPRESSED(attr))
127
0
    {
128
0
      varlena    *tmp = attr;
129
130
0
      attr = toast_decompress_datum(tmp);
131
0
      pfree(tmp);
132
0
    }
133
0
  }
134
0
  else if (VARATT_IS_EXTERNAL_INDIRECT(attr))
135
0
  {
136
    /*
137
     * This is an indirect pointer --- dereference it
138
     */
139
0
    varatt_indirect redirect;
140
141
0
    VARATT_EXTERNAL_GET_POINTER(redirect, attr);
142
0
    attr = (varlena *) redirect.pointer;
143
144
    /* nested indirect Datums aren't allowed */
145
0
    Assert(!VARATT_IS_EXTERNAL_INDIRECT(attr));
146
147
    /* recurse in case value is still extended in some other way */
148
0
    attr = detoast_attr(attr);
149
150
    /* if it isn't, we'd better copy it */
151
0
    if (attr == (varlena *) redirect.pointer)
152
0
    {
153
0
      varlena    *result;
154
155
0
      result = (varlena *) palloc(VARSIZE_ANY(attr));
156
0
      memcpy(result, attr, VARSIZE_ANY(attr));
157
0
      attr = result;
158
0
    }
159
0
  }
160
0
  else if (VARATT_IS_EXTERNAL_EXPANDED(attr))
161
0
  {
162
    /*
163
     * This is an expanded-object pointer --- get flat format
164
     */
165
0
    attr = detoast_external_attr(attr);
166
    /* flatteners are not allowed to produce compressed/short output */
167
0
    Assert(!VARATT_IS_EXTENDED(attr));
168
0
  }
169
0
  else if (VARATT_IS_COMPRESSED(attr))
170
0
  {
171
    /*
172
     * This is a compressed value inside of the main tuple
173
     */
174
0
    attr = toast_decompress_datum(attr);
175
0
  }
176
0
  else if (VARATT_IS_SHORT(attr))
177
0
  {
178
    /*
179
     * This is a short-header varlena --- convert to 4-byte header format
180
     */
181
0
    Size    data_size = VARSIZE_SHORT(attr) - VARHDRSZ_SHORT;
182
0
    Size    new_size = data_size + VARHDRSZ;
183
0
    varlena    *new_attr;
184
185
0
    new_attr = (varlena *) palloc(new_size);
186
0
    SET_VARSIZE(new_attr, new_size);
187
0
    memcpy(VARDATA(new_attr), VARDATA_SHORT(attr), data_size);
188
0
    attr = new_attr;
189
0
  }
190
191
0
  return attr;
192
0
}
193
194
195
/* ----------
196
 * detoast_attr_slice -
197
 *
198
 *    Public entry point to get back part of a toasted value
199
 *    from compression or external storage.
200
 *
201
 * sliceoffset is where to start (zero or more)
202
 * If slicelength < 0, return everything beyond sliceoffset
203
 * ----------
204
 */
205
varlena *
206
detoast_attr_slice(varlena *attr,
207
           int32 sliceoffset, int32 slicelength)
208
0
{
209
0
  varlena    *preslice;
210
0
  varlena    *result;
211
0
  char     *attrdata;
212
0
  int32   slicelimit;
213
0
  int32   attrsize;
214
215
0
  if (sliceoffset < 0)
216
0
    elog(ERROR, "invalid sliceoffset: %d", sliceoffset);
217
218
  /*
219
   * Compute slicelimit = offset + length, or -1 if we must fetch all of the
220
   * value.  In case of integer overflow, we must fetch all.
221
   */
222
0
  if (slicelength < 0)
223
0
    slicelimit = -1;
224
0
  else if (pg_add_s32_overflow(sliceoffset, slicelength, &slicelimit))
225
0
    slicelength = slicelimit = -1;
226
227
0
  if (VARATT_IS_EXTERNAL_ONDISK(attr))
228
0
  {
229
0
    toast_external_data toast_ext_data;
230
0
    int32   extsize;
231
0
    uint32    compress_method;
232
0
    bool    is_compressed;
233
234
0
    toast_external_info_get(attr, &toast_ext_data);
235
0
    extsize = VARATT_EXTINFO_GET_EXTSIZE(toast_ext_data.extinfo);
236
0
    compress_method = VARATT_EXTINFO_GET_COMPRESS_METHOD(toast_ext_data.extinfo);
237
0
    is_compressed = VARATT_EXTINFO_IS_COMPRESSED(toast_ext_data.extinfo, toast_ext_data.rawsize);
238
239
    /* fast path for non-compressed external datums */
240
0
    if (!is_compressed)
241
0
      return toast_fetch_datum_slice(attr, sliceoffset, slicelength);
242
243
    /*
244
     * For compressed values, we need to fetch enough slices to decompress
245
     * at least the requested part (when a prefix is requested).
246
     * Otherwise, just fetch all slices.
247
     */
248
0
    if (slicelimit >= 0)
249
0
    {
250
0
      int32   max_size = extsize;
251
252
      /*
253
       * Determine maximum amount of compressed data needed for a prefix
254
       * of a given length (after decompression).
255
       *
256
       * At least for now, if it's LZ4 data, we'll have to fetch the
257
       * whole thing, because there doesn't seem to be an API call to
258
       * determine how much compressed data we need to be sure of being
259
       * able to decompress the required slice.
260
       */
261
0
      if (compress_method == TOAST_PGLZ_COMPRESSION_ID)
262
0
        max_size = pglz_maximum_compressed_size(slicelimit, max_size);
263
264
      /*
265
       * Fetch enough compressed slices (compressed marker will get set
266
       * automatically).
267
       */
268
0
      preslice = toast_fetch_datum_slice(attr, 0, max_size);
269
0
    }
270
0
    else
271
0
      preslice = toast_fetch_datum(attr);
272
0
  }
273
0
  else if (VARATT_IS_EXTERNAL_INDIRECT(attr))
274
0
  {
275
0
    varatt_indirect redirect;
276
277
0
    VARATT_EXTERNAL_GET_POINTER(redirect, attr);
278
279
    /* nested indirect Datums aren't allowed */
280
0
    Assert(!VARATT_IS_EXTERNAL_INDIRECT(redirect.pointer));
281
282
0
    return detoast_attr_slice(redirect.pointer,
283
0
                  sliceoffset, slicelength);
284
0
  }
285
0
  else if (VARATT_IS_EXTERNAL_EXPANDED(attr))
286
0
  {
287
    /* pass it off to detoast_external_attr to flatten */
288
0
    preslice = detoast_external_attr(attr);
289
0
  }
290
0
  else
291
0
    preslice = attr;
292
293
0
  Assert(!VARATT_IS_EXTERNAL(preslice));
294
295
0
  if (VARATT_IS_COMPRESSED(preslice))
296
0
  {
297
0
    varlena    *tmp = preslice;
298
299
    /* Decompress enough to encompass the slice and the offset */
300
0
    if (slicelimit >= 0)
301
0
      preslice = toast_decompress_datum_slice(tmp, slicelimit);
302
0
    else
303
0
      preslice = toast_decompress_datum(tmp);
304
305
0
    if (tmp != attr)
306
0
      pfree(tmp);
307
0
  }
308
309
0
  if (VARATT_IS_SHORT(preslice))
310
0
  {
311
0
    attrdata = VARDATA_SHORT(preslice);
312
0
    attrsize = VARSIZE_SHORT(preslice) - VARHDRSZ_SHORT;
313
0
  }
314
0
  else
315
0
  {
316
0
    attrdata = VARDATA(preslice);
317
0
    attrsize = VARSIZE(preslice) - VARHDRSZ;
318
0
  }
319
320
  /* slicing of datum for compressed cases and plain value */
321
322
0
  if (sliceoffset >= attrsize)
323
0
  {
324
0
    sliceoffset = 0;
325
0
    slicelength = 0;
326
0
  }
327
0
  else if (slicelength < 0 || slicelimit > attrsize)
328
0
    slicelength = attrsize - sliceoffset;
329
330
0
  result = (varlena *) palloc(slicelength + VARHDRSZ);
331
0
  SET_VARSIZE(result, slicelength + VARHDRSZ);
332
333
0
  memcpy(VARDATA(result), attrdata + sliceoffset, slicelength);
334
335
0
  if (preslice != attr)
336
0
    pfree(preslice);
337
338
0
  return result;
339
0
}
340
341
/* ----------
342
 * toast_fetch_datum -
343
 *
344
 *  Reconstruct an in memory Datum from the chunks saved
345
 *  in the toast relation
346
 * ----------
347
 */
348
static varlena *
349
toast_fetch_datum(varlena *attr)
350
0
{
351
0
  Relation  toastrel;
352
0
  varlena    *result;
353
0
  toast_external_data toast_ext_data;
354
0
  int32   attrsize;
355
0
  Oid     toastrelid;
356
0
  Oid8    valueid;
357
358
0
  if (!VARATT_IS_EXTERNAL_ONDISK(attr))
359
0
    elog(ERROR, "toast_fetch_datum shouldn't be called for non-ondisk datums");
360
361
0
  toast_external_info_get(attr, &toast_ext_data);
362
0
  attrsize = VARATT_EXTINFO_GET_EXTSIZE(toast_ext_data.extinfo);
363
0
  toastrelid = toast_ext_data.toastrelid;
364
0
  valueid = toast_ext_data.valueid;
365
366
0
  result = (varlena *) palloc(attrsize + VARHDRSZ);
367
368
0
  if (VARATT_EXTINFO_IS_COMPRESSED(toast_ext_data.extinfo, toast_ext_data.rawsize))
369
0
    SET_VARSIZE_COMPRESSED(result, attrsize + VARHDRSZ);
370
0
  else
371
0
    SET_VARSIZE(result, attrsize + VARHDRSZ);
372
373
0
  if (attrsize == 0)
374
0
    return result;     /* Probably shouldn't happen, but just in
375
                 * case. */
376
377
  /*
378
   * Open the toast relation and its indexes
379
   */
380
0
  toastrel = table_open(toastrelid, AccessShareLock);
381
382
  /* Fetch all chunks */
383
0
  table_relation_fetch_toast_slice(toastrel, valueid,
384
0
                   attrsize, 0, attrsize, result);
385
386
  /* Close toast table */
387
0
  table_close(toastrel, AccessShareLock);
388
389
0
  return result;
390
0
}
391
392
/* ----------
393
 * toast_fetch_datum_slice -
394
 *
395
 *  Reconstruct a segment of a Datum from the chunks saved
396
 *  in the toast relation
397
 *
398
 *  Note that this function supports non-compressed external datums
399
 *  and compressed external datums (in which case the requested slice
400
 *  has to be a prefix, i.e. sliceoffset has to be 0).
401
 * ----------
402
 */
403
static varlena *
404
toast_fetch_datum_slice(varlena *attr, int32 sliceoffset,
405
            int32 slicelength)
406
0
{
407
0
  Relation  toastrel;
408
0
  varlena    *result;
409
0
  toast_external_data toast_ext_data;
410
0
  int32   attrsize;
411
0
  Oid     toastrelid;
412
0
  Oid8    valueid;
413
0
  bool    is_compressed;
414
415
0
  if (!VARATT_IS_EXTERNAL_ONDISK(attr))
416
0
    elog(ERROR, "toast_fetch_datum_slice shouldn't be called for non-ondisk datums");
417
418
0
  toast_external_info_get(attr, &toast_ext_data);
419
0
  attrsize = VARATT_EXTINFO_GET_EXTSIZE(toast_ext_data.extinfo);
420
0
  toastrelid = toast_ext_data.toastrelid;
421
0
  valueid = toast_ext_data.valueid;
422
0
  is_compressed = VARATT_EXTINFO_IS_COMPRESSED(toast_ext_data.extinfo, toast_ext_data.rawsize);
423
424
  /*
425
   * It's nonsense to fetch slices of a compressed datum unless when it's a
426
   * prefix -- this isn't lo_* we can't return a compressed datum which is
427
   * meaningful to toast later.
428
   */
429
0
  Assert(!is_compressed || 0 == sliceoffset);
430
431
0
  if (sliceoffset >= attrsize)
432
0
  {
433
0
    sliceoffset = 0;
434
0
    slicelength = 0;
435
0
  }
436
437
  /*
438
   * When fetching a prefix of a compressed external datum, account for the
439
   * space required by va_tcinfo, which is stored at the beginning as an
440
   * int32 value.
441
   */
442
0
  if (is_compressed && slicelength > 0)
443
0
    slicelength = slicelength + sizeof(int32);
444
445
  /*
446
   * Adjust length request if needed.  (Note: our sole caller,
447
   * detoast_attr_slice, protects us against sliceoffset + slicelength
448
   * overflowing.)
449
   */
450
0
  if (((sliceoffset + slicelength) > attrsize) || slicelength < 0)
451
0
    slicelength = attrsize - sliceoffset;
452
453
0
  result = (varlena *) palloc(slicelength + VARHDRSZ);
454
455
0
  if (is_compressed)
456
0
    SET_VARSIZE_COMPRESSED(result, slicelength + VARHDRSZ);
457
0
  else
458
0
    SET_VARSIZE(result, slicelength + VARHDRSZ);
459
460
0
  if (slicelength == 0)
461
0
    return result;     /* Can save a lot of work at this point! */
462
463
  /* Open the toast relation */
464
0
  toastrel = table_open(toastrelid, AccessShareLock);
465
466
  /* Fetch all chunks */
467
0
  table_relation_fetch_toast_slice(toastrel, valueid,
468
0
                   attrsize, sliceoffset, slicelength,
469
0
                   result);
470
471
  /* Close toast table */
472
0
  table_close(toastrel, AccessShareLock);
473
474
0
  return result;
475
0
}
476
477
/* ----------
478
 * toast_decompress_datum -
479
 *
480
 * Decompress a compressed version of a varlena datum
481
 */
482
static varlena *
483
toast_decompress_datum(varlena *attr)
484
0
{
485
0
  ToastCompressionId cmid;
486
487
0
  Assert(VARATT_IS_COMPRESSED(attr));
488
489
  /*
490
   * Fetch the compression method id stored in the compression header and
491
   * decompress the data using the appropriate decompression routine.
492
   */
493
0
  cmid = VARDATA_COMPRESSED_GET_COMPRESS_METHOD(attr);
494
0
  switch (cmid)
495
0
  {
496
0
    case TOAST_PGLZ_COMPRESSION_ID:
497
0
      return pglz_decompress_datum(attr);
498
0
    case TOAST_LZ4_COMPRESSION_ID:
499
0
      return lz4_decompress_datum(attr);
500
0
    default:
501
0
      elog(ERROR, "invalid compression method id %d", cmid);
502
0
      return NULL;   /* keep compiler quiet */
503
0
  }
504
0
}
505
506
507
/* ----------
508
 * toast_decompress_datum_slice -
509
 *
510
 * Decompress the front of a compressed version of a varlena datum.
511
 * offset handling happens in detoast_attr_slice.
512
 * Here we just decompress a slice from the front.
513
 */
514
static varlena *
515
toast_decompress_datum_slice(varlena *attr, int32 slicelength)
516
0
{
517
0
  ToastCompressionId cmid;
518
519
0
  Assert(VARATT_IS_COMPRESSED(attr));
520
521
  /*
522
   * Some callers may pass a slicelength that's more than the actual
523
   * decompressed size.  If so, just decompress normally.  This avoids
524
   * possibly allocating a larger-than-necessary result object, and may be
525
   * faster and/or more robust as well.  Notably, some versions of liblz4
526
   * have been seen to give wrong results if passed an output size that is
527
   * more than the data's true decompressed size.
528
   */
529
0
  if ((uint32) slicelength >= VARDATA_COMPRESSED_GET_EXTSIZE(attr))
530
0
    return toast_decompress_datum(attr);
531
532
  /*
533
   * Fetch the compression method id stored in the compression header and
534
   * decompress the data slice using the appropriate decompression routine.
535
   */
536
0
  cmid = VARDATA_COMPRESSED_GET_COMPRESS_METHOD(attr);
537
0
  switch (cmid)
538
0
  {
539
0
    case TOAST_PGLZ_COMPRESSION_ID:
540
0
      return pglz_decompress_datum_slice(attr, slicelength);
541
0
    case TOAST_LZ4_COMPRESSION_ID:
542
0
      return lz4_decompress_datum_slice(attr, slicelength);
543
0
    default:
544
0
      elog(ERROR, "invalid compression method id %d", cmid);
545
0
      return NULL;   /* keep compiler quiet */
546
0
  }
547
0
}
548
549
/* ----------
550
 * toast_raw_datum_size -
551
 *
552
 *  Return the raw (detoasted) size of a varlena datum
553
 *  (including the VARHDRSZ header)
554
 * ----------
555
 */
556
Size
557
toast_raw_datum_size(Datum value)
558
0
{
559
0
  varlena    *attr = (varlena *) DatumGetPointer(value);
560
0
  Size    result;
561
562
0
  if (VARATT_IS_EXTERNAL_ONDISK(attr))
563
0
  {
564
    /* va_rawsize is the size of the original datum -- including header */
565
0
    toast_external_data toast_ext_data;
566
567
0
    toast_external_info_get(attr, &toast_ext_data);
568
0
    result = toast_ext_data.rawsize;
569
0
  }
570
0
  else if (VARATT_IS_EXTERNAL_INDIRECT(attr))
571
0
  {
572
0
    varatt_indirect toast_pointer;
573
574
0
    VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
575
576
    /* nested indirect Datums aren't allowed */
577
0
    Assert(!VARATT_IS_EXTERNAL_INDIRECT(toast_pointer.pointer));
578
579
0
    return toast_raw_datum_size(PointerGetDatum(toast_pointer.pointer));
580
0
  }
581
0
  else if (VARATT_IS_EXTERNAL_EXPANDED(attr))
582
0
  {
583
0
    result = EOH_get_flat_size(DatumGetEOHP(value));
584
0
  }
585
0
  else if (VARATT_IS_COMPRESSED(attr))
586
0
  {
587
    /* here, va_rawsize is just the payload size */
588
0
    result = VARDATA_COMPRESSED_GET_EXTSIZE(attr) + VARHDRSZ;
589
0
  }
590
0
  else if (VARATT_IS_SHORT(attr))
591
0
  {
592
    /*
593
     * we have to normalize the header length to VARHDRSZ or else the
594
     * callers of this function will be confused.
595
     */
596
0
    result = VARSIZE_SHORT(attr) - VARHDRSZ_SHORT + VARHDRSZ;
597
0
  }
598
0
  else
599
0
  {
600
    /* plain untoasted datum */
601
0
    result = VARSIZE(attr);
602
0
  }
603
0
  return result;
604
0
}
605
606
/* ----------
607
 * toast_datum_size
608
 *
609
 *  Return the physical storage size (possibly compressed) of a varlena datum
610
 * ----------
611
 */
612
Size
613
toast_datum_size(Datum value)
614
0
{
615
0
  varlena    *attr = (varlena *) DatumGetPointer(value);
616
0
  Size    result;
617
618
0
  if (VARATT_IS_EXTERNAL_ONDISK(attr))
619
0
  {
620
    /*
621
     * Attribute is stored externally - return the extsize whether
622
     * compressed or not.  We do not count the size of the toast pointer
623
     * ... should we?
624
     */
625
0
    toast_external_data toast_ext_data;
626
627
0
    toast_external_info_get(attr, &toast_ext_data);
628
0
    result = VARATT_EXTINFO_GET_EXTSIZE(toast_ext_data.extinfo);
629
0
  }
630
0
  else if (VARATT_IS_EXTERNAL_INDIRECT(attr))
631
0
  {
632
0
    varatt_indirect toast_pointer;
633
634
0
    VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
635
636
    /* nested indirect Datums aren't allowed */
637
0
    Assert(!VARATT_IS_EXTERNAL_INDIRECT(attr));
638
639
0
    return toast_datum_size(PointerGetDatum(toast_pointer.pointer));
640
0
  }
641
0
  else if (VARATT_IS_EXTERNAL_EXPANDED(attr))
642
0
  {
643
0
    result = EOH_get_flat_size(DatumGetEOHP(value));
644
0
  }
645
0
  else if (VARATT_IS_SHORT(attr))
646
0
  {
647
0
    result = VARSIZE_SHORT(attr);
648
0
  }
649
0
  else
650
0
  {
651
    /*
652
     * Attribute is stored inline either compressed or not, just calculate
653
     * the size of the datum in either case.
654
     */
655
0
    result = VARSIZE(attr);
656
0
  }
657
0
  return result;
658
0
}