PostgreSQL Source Code  git master
detoast.c
Go to the documentation of this file.
1 /*-------------------------------------------------------------------------
2  *
3  * detoast.c
4  * Retrieve compressed or external variable size attributes.
5  *
6  * Copyright (c) 2000-2020, 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_internals.h"
20 #include "common/pg_lzcompress.h"
21 #include "utils/expandeddatum.h"
22 #include "utils/rel.h"
23 
24 static struct varlena *toast_fetch_datum(struct varlena *attr);
25 static struct varlena *toast_fetch_datum_slice(struct varlena *attr,
26  int32 sliceoffset,
27  int32 slicelength);
28 static struct varlena *toast_decompress_datum(struct varlena *attr);
29 static struct varlena *toast_decompress_datum_slice(struct varlena *attr, int32 slicelength);
30 
31 /* ----------
32  * detoast_external_attr -
33  *
34  * Public entry point to get back a toasted value from
35  * external source (possibly still in compressed format).
36  *
37  * This will return a datum that contains all the data internally, ie, not
38  * relying on external storage or memory, but it can still be compressed or
39  * have a short header. Note some callers assume that if the input is an
40  * EXTERNAL datum, the result will be a pfree'able chunk.
41  * ----------
42  */
43 struct varlena *
45 {
46  struct varlena *result;
47 
48  if (VARATT_IS_EXTERNAL_ONDISK(attr))
49  {
50  /*
51  * This is an external stored plain value
52  */
53  result = toast_fetch_datum(attr);
54  }
55  else if (VARATT_IS_EXTERNAL_INDIRECT(attr))
56  {
57  /*
58  * This is an indirect pointer --- dereference it
59  */
60  struct varatt_indirect redirect;
61 
62  VARATT_EXTERNAL_GET_POINTER(redirect, attr);
63  attr = (struct varlena *) redirect.pointer;
64 
65  /* nested indirect Datums aren't allowed */
67 
68  /* recurse if value is still external in some other way */
69  if (VARATT_IS_EXTERNAL(attr))
70  return detoast_external_attr(attr);
71 
72  /*
73  * Copy into the caller's memory context, in case caller tries to
74  * pfree the result.
75  */
76  result = (struct varlena *) palloc(VARSIZE_ANY(attr));
77  memcpy(result, attr, VARSIZE_ANY(attr));
78  }
79  else if (VARATT_IS_EXTERNAL_EXPANDED(attr))
80  {
81  /*
82  * This is an expanded-object pointer --- get flat format
83  */
85  Size resultsize;
86 
87  eoh = DatumGetEOHP(PointerGetDatum(attr));
88  resultsize = EOH_get_flat_size(eoh);
89  result = (struct varlena *) palloc(resultsize);
90  EOH_flatten_into(eoh, (void *) result, resultsize);
91  }
92  else
93  {
94  /*
95  * This is a plain value inside of the main tuple - why am I called?
96  */
97  result = attr;
98  }
99 
100  return result;
101 }
102 
103 
104 /* ----------
105  * detoast_attr -
106  *
107  * Public entry point to get back a toasted value from compression
108  * or external storage. The result is always non-extended varlena form.
109  *
110  * Note some callers assume that if the input is an EXTERNAL or COMPRESSED
111  * datum, the result will be a pfree'able chunk.
112  * ----------
113  */
114 struct varlena *
115 detoast_attr(struct varlena *attr)
116 {
117  if (VARATT_IS_EXTERNAL_ONDISK(attr))
118  {
119  /*
120  * This is an externally stored datum --- fetch it back from there
121  */
122  attr = toast_fetch_datum(attr);
123  /* If it's compressed, decompress it */
124  if (VARATT_IS_COMPRESSED(attr))
125  {
126  struct varlena *tmp = attr;
127 
128  attr = toast_decompress_datum(tmp);
129  pfree(tmp);
130  }
131  }
132  else if (VARATT_IS_EXTERNAL_INDIRECT(attr))
133  {
134  /*
135  * This is an indirect pointer --- dereference it
136  */
137  struct varatt_indirect redirect;
138 
139  VARATT_EXTERNAL_GET_POINTER(redirect, attr);
140  attr = (struct varlena *) redirect.pointer;
141 
142  /* nested indirect Datums aren't allowed */
144 
145  /* recurse in case value is still extended in some other way */
146  attr = detoast_attr(attr);
147 
148  /* if it isn't, we'd better copy it */
149  if (attr == (struct varlena *) redirect.pointer)
150  {
151  struct varlena *result;
152 
153  result = (struct varlena *) palloc(VARSIZE_ANY(attr));
154  memcpy(result, attr, VARSIZE_ANY(attr));
155  attr = result;
156  }
157  }
158  else if (VARATT_IS_EXTERNAL_EXPANDED(attr))
159  {
160  /*
161  * This is an expanded-object pointer --- get flat format
162  */
163  attr = detoast_external_attr(attr);
164  /* flatteners are not allowed to produce compressed/short output */
165  Assert(!VARATT_IS_EXTENDED(attr));
166  }
167  else if (VARATT_IS_COMPRESSED(attr))
168  {
169  /*
170  * This is a compressed value inside of the main tuple
171  */
172  attr = toast_decompress_datum(attr);
173  }
174  else if (VARATT_IS_SHORT(attr))
175  {
176  /*
177  * This is a short-header varlena --- convert to 4-byte header format
178  */
179  Size data_size = VARSIZE_SHORT(attr) - VARHDRSZ_SHORT;
180  Size new_size = data_size + VARHDRSZ;
181  struct varlena *new_attr;
182 
183  new_attr = (struct varlena *) palloc(new_size);
184  SET_VARSIZE(new_attr, new_size);
185  memcpy(VARDATA(new_attr), VARDATA_SHORT(attr), data_size);
186  attr = new_attr;
187  }
188 
189  return attr;
190 }
191 
192 
193 /* ----------
194  * detoast_attr_slice -
195  *
196  * Public entry point to get back part of a toasted value
197  * from compression or external storage.
198  *
199  * Note: When slicelength is negative, return suffix of the value.
200  * ----------
201  */
202 struct varlena *
204  int32 sliceoffset, int32 slicelength)
205 {
206  struct varlena *preslice;
207  struct varlena *result;
208  char *attrdata;
209  int32 attrsize;
210 
211  if (VARATT_IS_EXTERNAL_ONDISK(attr))
212  {
213  struct varatt_external toast_pointer;
214 
215  VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
216 
217  /* fast path for non-compressed external datums */
218  if (!VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))
219  return toast_fetch_datum_slice(attr, sliceoffset, slicelength);
220 
221  /*
222  * For compressed values, we need to fetch enough slices to decompress
223  * at least the requested part (when a prefix is requested). Otherwise,
224  * just fetch all slices.
225  */
226  if (slicelength > 0 && sliceoffset >= 0)
227  {
228  int32 max_size;
229 
230  /*
231  * Determine maximum amount of compressed data needed for a prefix
232  * of a given length (after decompression).
233  */
234  max_size = pglz_maximum_compressed_size(sliceoffset + slicelength,
235  toast_pointer.va_extsize);
236 
237  /*
238  * Fetch enough compressed slices (compressed marker will get set
239  * automatically).
240  */
241  preslice = toast_fetch_datum_slice(attr, 0, max_size);
242  }
243  else
244  preslice = toast_fetch_datum(attr);
245  }
246  else if (VARATT_IS_EXTERNAL_INDIRECT(attr))
247  {
248  struct varatt_indirect redirect;
249 
250  VARATT_EXTERNAL_GET_POINTER(redirect, attr);
251 
252  /* nested indirect Datums aren't allowed */
254 
255  return detoast_attr_slice(redirect.pointer,
256  sliceoffset, slicelength);
257  }
258  else if (VARATT_IS_EXTERNAL_EXPANDED(attr))
259  {
260  /* pass it off to detoast_external_attr to flatten */
261  preslice = detoast_external_attr(attr);
262  }
263  else
264  preslice = attr;
265 
266  Assert(!VARATT_IS_EXTERNAL(preslice));
267 
268  if (VARATT_IS_COMPRESSED(preslice))
269  {
270  struct varlena *tmp = preslice;
271 
272  /* Decompress enough to encompass the slice and the offset */
273  if (slicelength > 0 && sliceoffset >= 0)
274  preslice = toast_decompress_datum_slice(tmp, slicelength + sliceoffset);
275  else
276  preslice = toast_decompress_datum(tmp);
277 
278  if (tmp != attr)
279  pfree(tmp);
280  }
281 
282  if (VARATT_IS_SHORT(preslice))
283  {
284  attrdata = VARDATA_SHORT(preslice);
285  attrsize = VARSIZE_SHORT(preslice) - VARHDRSZ_SHORT;
286  }
287  else
288  {
289  attrdata = VARDATA(preslice);
290  attrsize = VARSIZE(preslice) - VARHDRSZ;
291  }
292 
293  /* slicing of datum for compressed cases and plain value */
294 
295  if (sliceoffset >= attrsize)
296  {
297  sliceoffset = 0;
298  slicelength = 0;
299  }
300 
301  if (((sliceoffset + slicelength) > attrsize) || slicelength < 0)
302  slicelength = attrsize - sliceoffset;
303 
304  result = (struct varlena *) palloc(slicelength + VARHDRSZ);
305  SET_VARSIZE(result, slicelength + VARHDRSZ);
306 
307  memcpy(VARDATA(result), attrdata + sliceoffset, slicelength);
308 
309  if (preslice != attr)
310  pfree(preslice);
311 
312  return result;
313 }
314 
315 /* ----------
316  * toast_fetch_datum -
317  *
318  * Reconstruct an in memory Datum from the chunks saved
319  * in the toast relation
320  * ----------
321  */
322 static struct varlena *
324 {
325  Relation toastrel;
326  struct varlena *result;
327  struct varatt_external toast_pointer;
328  int32 attrsize;
329 
330  if (!VARATT_IS_EXTERNAL_ONDISK(attr))
331  elog(ERROR, "toast_fetch_datum shouldn't be called for non-ondisk datums");
332 
333  /* Must copy to access aligned fields */
334  VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
335 
336  attrsize = toast_pointer.va_extsize;
337 
338  result = (struct varlena *) palloc(attrsize + VARHDRSZ);
339 
340  if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))
341  SET_VARSIZE_COMPRESSED(result, attrsize + VARHDRSZ);
342  else
343  SET_VARSIZE(result, attrsize + VARHDRSZ);
344 
345  if (attrsize == 0)
346  return result; /* Probably shouldn't happen, but just in case. */
347 
348  /*
349  * Open the toast relation and its indexes
350  */
351  toastrel = table_open(toast_pointer.va_toastrelid, AccessShareLock);
352 
353  /* Fetch all chunks */
354  table_relation_fetch_toast_slice(toastrel, toast_pointer.va_valueid,
355  attrsize, 0, attrsize, result);
356 
357  /* Close toast table */
358  table_close(toastrel, AccessShareLock);
359 
360  return result;
361 }
362 
363 /* ----------
364  * toast_fetch_datum_slice -
365  *
366  * Reconstruct a segment of a Datum from the chunks saved
367  * in the toast relation
368  *
369  * Note that this function supports non-compressed external datums
370  * and compressed external datums (in which case the requested slice
371  * has to be a prefix, i.e. sliceoffset has to be 0).
372  * ----------
373  */
374 static struct varlena *
375 toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
376  int32 slicelength)
377 {
378  Relation toastrel;
379  struct varlena *result;
380  struct varatt_external toast_pointer;
381  int32 attrsize;
382 
383  if (!VARATT_IS_EXTERNAL_ONDISK(attr))
384  elog(ERROR, "toast_fetch_datum_slice shouldn't be called for non-ondisk datums");
385 
386  /* Must copy to access aligned fields */
387  VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
388 
389  /*
390  * It's nonsense to fetch slices of a compressed datum unless when it's
391  * a prefix -- this isn't lo_* we can't return a compressed datum which
392  * is meaningful to toast later.
393  */
394  Assert(!VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer) || 0 == sliceoffset);
395 
396  attrsize = toast_pointer.va_extsize;
397 
398  if (sliceoffset >= attrsize)
399  {
400  sliceoffset = 0;
401  slicelength = 0;
402  }
403 
404  /*
405  * When fetching a prefix of a compressed external datum, account for the
406  * rawsize tracking amount of raw data, which is stored at the beginning
407  * as an int32 value).
408  */
409  if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer) && slicelength > 0)
410  slicelength = slicelength + sizeof(int32);
411 
412  if (((sliceoffset + slicelength) > attrsize) || slicelength < 0)
413  slicelength = attrsize - sliceoffset;
414 
415  result = (struct varlena *) palloc(slicelength + VARHDRSZ);
416 
417  if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))
418  SET_VARSIZE_COMPRESSED(result, slicelength + VARHDRSZ);
419  else
420  SET_VARSIZE(result, slicelength + VARHDRSZ);
421 
422  if (slicelength == 0)
423  return result; /* Can save a lot of work at this point! */
424 
425  /* Open the toast relation */
426  toastrel = table_open(toast_pointer.va_toastrelid, AccessShareLock);
427 
428  /* Fetch all chunks */
429  table_relation_fetch_toast_slice(toastrel, toast_pointer.va_valueid,
430  attrsize, sliceoffset, slicelength,
431  result);
432 
433  /* Close toast table */
434  table_close(toastrel, AccessShareLock);
435 
436  return result;
437 }
438 
439 /* ----------
440  * toast_decompress_datum -
441  *
442  * Decompress a compressed version of a varlena datum
443  */
444 static struct varlena *
446 {
447  struct varlena *result;
448 
450 
451  result = (struct varlena *)
454 
456  TOAST_COMPRESS_SIZE(attr),
457  VARDATA(result),
458  TOAST_COMPRESS_RAWSIZE(attr), true) < 0)
459  elog(ERROR, "compressed data is corrupted");
460 
461  return result;
462 }
463 
464 
465 /* ----------
466  * toast_decompress_datum_slice -
467  *
468  * Decompress the front of a compressed version of a varlena datum.
469  * offset handling happens in detoast_attr_slice.
470  * Here we just decompress a slice from the front.
471  */
472 static struct varlena *
473 toast_decompress_datum_slice(struct varlena *attr, int32 slicelength)
474 {
475  struct varlena *result;
476  int32 rawsize;
477 
479 
480  result = (struct varlena *) palloc(slicelength + VARHDRSZ);
481 
482  rawsize = pglz_decompress(TOAST_COMPRESS_RAWDATA(attr),
484  VARDATA(result),
485  slicelength, false);
486  if (rawsize < 0)
487  elog(ERROR, "compressed data is corrupted");
488 
489  SET_VARSIZE(result, rawsize + VARHDRSZ);
490  return result;
491 }
492 
493 /* ----------
494  * toast_raw_datum_size -
495  *
496  * Return the raw (detoasted) size of a varlena datum
497  * (including the VARHDRSZ header)
498  * ----------
499  */
500 Size
502 {
503  struct varlena *attr = (struct varlena *) DatumGetPointer(value);
504  Size result;
505 
506  if (VARATT_IS_EXTERNAL_ONDISK(attr))
507  {
508  /* va_rawsize is the size of the original datum -- including header */
509  struct varatt_external toast_pointer;
510 
511  VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
512  result = toast_pointer.va_rawsize;
513  }
514  else if (VARATT_IS_EXTERNAL_INDIRECT(attr))
515  {
516  struct varatt_indirect toast_pointer;
517 
518  VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
519 
520  /* nested indirect Datums aren't allowed */
521  Assert(!VARATT_IS_EXTERNAL_INDIRECT(toast_pointer.pointer));
522 
523  return toast_raw_datum_size(PointerGetDatum(toast_pointer.pointer));
524  }
525  else if (VARATT_IS_EXTERNAL_EXPANDED(attr))
526  {
527  result = EOH_get_flat_size(DatumGetEOHP(value));
528  }
529  else if (VARATT_IS_COMPRESSED(attr))
530  {
531  /* here, va_rawsize is just the payload size */
532  result = VARRAWSIZE_4B_C(attr) + VARHDRSZ;
533  }
534  else if (VARATT_IS_SHORT(attr))
535  {
536  /*
537  * we have to normalize the header length to VARHDRSZ or else the
538  * callers of this function will be confused.
539  */
540  result = VARSIZE_SHORT(attr) - VARHDRSZ_SHORT + VARHDRSZ;
541  }
542  else
543  {
544  /* plain untoasted datum */
545  result = VARSIZE(attr);
546  }
547  return result;
548 }
549 
550 /* ----------
551  * toast_datum_size
552  *
553  * Return the physical storage size (possibly compressed) of a varlena datum
554  * ----------
555  */
556 Size
558 {
559  struct varlena *attr = (struct varlena *) DatumGetPointer(value);
560  Size result;
561 
562  if (VARATT_IS_EXTERNAL_ONDISK(attr))
563  {
564  /*
565  * Attribute is stored externally - return the extsize whether
566  * compressed or not. We do not count the size of the toast pointer
567  * ... should we?
568  */
569  struct varatt_external toast_pointer;
570 
571  VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
572  result = toast_pointer.va_extsize;
573  }
574  else if (VARATT_IS_EXTERNAL_INDIRECT(attr))
575  {
576  struct varatt_indirect toast_pointer;
577 
578  VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
579 
580  /* nested indirect Datums aren't allowed */
582 
583  return toast_datum_size(PointerGetDatum(toast_pointer.pointer));
584  }
585  else if (VARATT_IS_EXTERNAL_EXPANDED(attr))
586  {
587  result = EOH_get_flat_size(DatumGetEOHP(value));
588  }
589  else if (VARATT_IS_SHORT(attr))
590  {
591  result = VARSIZE_SHORT(attr);
592  }
593  else
594  {
595  /*
596  * Attribute is stored inline either compressed or not, just calculate
597  * the size of the datum in either case.
598  */
599  result = VARSIZE(attr);
600  }
601  return result;
602 }
#define VARATT_IS_EXTERNAL_ONDISK(PTR)
Definition: postgres.h:314
#define VARATT_IS_COMPRESSED(PTR)
Definition: postgres.h:312
int32 pglz_maximum_compressed_size(int32 rawsize, int32 total_compressed_size)
void table_close(Relation relation, LOCKMODE lockmode)
Definition: table.c:133
#define VARDATA(PTR)
Definition: postgres.h:302
#define VARATT_IS_EXTERNAL_EXPANDED(PTR)
Definition: postgres.h:322
#define VARHDRSZ_SHORT
Definition: postgres.h:268
#define VARSIZE(PTR)
Definition: postgres.h:303
#define VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr)
Definition: detoast.h:32
#define PointerGetDatum(X)
Definition: postgres.h:556
#define VARHDRSZ
Definition: c.h:561
struct varlena * detoast_external_attr(struct varlena *attr)
Definition: detoast.c:44
static void table_relation_fetch_toast_slice(Relation toastrel, Oid valueid, int32 attrsize, int32 sliceoffset, int32 slicelength, struct varlena *result)
Definition: tableam.h:1672
#define AccessShareLock
Definition: lockdefs.h:36
Oid va_toastrelid
Definition: postgres.h:72
int32 va_rawsize
Definition: postgres.h:69
struct varlena * detoast_attr_slice(struct varlena *attr, int32 sliceoffset, int32 slicelength)
Definition: detoast.c:203
int32 pglz_decompress(const char *source, int32 slen, char *dest, int32 rawsize, bool check_complete)
signed int int32
Definition: c.h:355
Size toast_datum_size(Datum value)
Definition: detoast.c:557
#define VARATT_IS_EXTERNAL(PTR)
Definition: postgres.h:313
struct varlena * detoast_attr(struct varlena *attr)
Definition: detoast.c:115
void pfree(void *pointer)
Definition: mcxt.c:1056
#define VARATT_IS_EXTERNAL_INDIRECT(PTR)
Definition: postgres.h:316
#define ERROR
Definition: elog.h:43
#define TOAST_COMPRESS_RAWDATA(ptr)
#define VARATT_IS_SHORT(PTR)
Definition: postgres.h:326
Size EOH_get_flat_size(ExpandedObjectHeader *eohptr)
Definition: expandeddatum.c:75
Size toast_raw_datum_size(Datum value)
Definition: detoast.c:501
#define VARSIZE_SHORT(PTR)
Definition: postgres.h:305
ExpandedObjectHeader * DatumGetEOHP(Datum d)
Definition: expandeddatum.c:29
#define VARRAWSIZE_4B_C(PTR)
Definition: postgres.h:283
#define TOAST_COMPRESS_SIZE(ptr)
uintptr_t Datum
Definition: postgres.h:367
void EOH_flatten_into(ExpandedObjectHeader *eohptr, void *result, Size allocated_size)
Definition: expandeddatum.c:81
#define VARSIZE_ANY(PTR)
Definition: postgres.h:335
static struct varlena * toast_fetch_datum(struct varlena *attr)
Definition: detoast.c:323
static struct @143 value
#define Assert(condition)
Definition: c.h:738
static struct varlena * toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 slicelength)
Definition: detoast.c:375
int32 va_extsize
Definition: postgres.h:70
size_t Size
Definition: c.h:466
#define TOAST_COMPRESS_HDRSZ
struct varlena * pointer
Definition: postgres.h:86
#define VARATT_IS_EXTENDED(PTR)
Definition: postgres.h:327
#define DatumGetPointer(X)
Definition: postgres.h:549
#define VARDATA_SHORT(PTR)
Definition: postgres.h:306
void * palloc(Size size)
Definition: mcxt.c:949
#define elog(elevel,...)
Definition: elog.h:214
#define SET_VARSIZE_COMPRESSED(PTR, len)
Definition: postgres.h:331
#define VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer)
Definition: detoast.h:22
Definition: c.h:555
#define SET_VARSIZE(PTR, len)
Definition: postgres.h:329
static struct varlena * toast_decompress_datum(struct varlena *attr)
Definition: detoast.c:445
Relation table_open(Oid relationId, LOCKMODE lockmode)
Definition: table.c:39
static struct varlena * toast_decompress_datum_slice(struct varlena *attr, int32 slicelength)
Definition: detoast.c:473
#define TOAST_COMPRESS_RAWSIZE(ptr)