1/*-------------------------------------------------------------------------
4 * Heap-specific definitions for external and compressed storage
5 * of variable size attributes.
7 * Copyright (c) 2000-2025, PostgreSQL Global Development Group
11 * src/backend/access/heap/heaptoast.c
15 * heap_toast_insert_or_update -
16 * Try to make a given tuple fit into one page by compressing
17 * or moving off attributes
20 * Reclaim toast storage when a tuple is deleted
22 *-------------------------------------------------------------------------
33#include "utils/fmgroids.h"
39 * Cascaded delete toast-entries on DELETE
50 * We should only ever be called for tuples of plain relations or
51 * materialized views --- recursing on a toast rel is bad news.
54 rel->
rd_rel->relkind == RELKIND_MATVIEW);
57 * Get the tuple descriptor and break down the tuple into fields.
59 * NOTE: it's debatable whether to use heap_deform_tuple() here or just
60 * heap_getattr() only the varlena columns. The latter could win if there
61 * are few varlena columns and many non-varlena ones. However,
62 * heap_deform_tuple costs only O(N) while the heap_getattr way would cost
63 * O(N^2) if there are many varlena columns, so it seems better to err on
64 * the side of linear cost. (We won't even be here unless there's at
65 * least one varlena column, by the way.)
72 /* Do the real work. */
78 * heap_toast_insert_or_update -
80 * Delete no-longer-used toast-entries and create new ones to
81 * make the new tuple fit on INSERT or UPDATE
84 * newtup: the candidate new tuple to be inserted
85 * oldtup: the old row version for UPDATE, or NULL for INSERT
86 * options: options to be passed to heap_insert() for toast rows
88 * either newtup if no toasting is needed, or a palloc'd modified tuple
89 * that is what should actually get stored
91 * NOTE: neither newtup nor oldtup will be modified. This is a change
92 * from the pre-8.1 API of this routine.
114 * Ignore the INSERT_SPECULATIVE option. Speculative insertions/super
115 * deletions just normally insert/delete the toast values. It seems
116 * easiest to deal with that here, instead on, potentially, multiple
119 options &= ~HEAP_INSERT_SPECULATIVE;
122 * We should only ever be called for tuples of plain relations or
123 * materialized views --- recursing on a toast rel is bad news.
126 rel->
rd_rel->relkind == RELKIND_MATVIEW);
129 * Get the tuple descriptor and break down the tuple(s) into fields.
132 numAttrs = tupleDesc->
natts;
140 * Prepare for toasting
160 * Compress and/or save external until data fits into target length
162 * 1: Inline compress attributes with attstorage EXTENDED, and store very
163 * large attributes with attstorage EXTENDED or EXTERNAL external
165 * 2: Store attributes with attstorage EXTENDED or EXTERNAL external
166 * 3: Inline compress attributes with attstorage MAIN
167 * 4: Store attributes with attstorage MAIN external
171 /* compute header overhead --- this should match heap_form_tuple() */
176 /* now convert to a limit on the tuple data size */
180 * Look for attributes with attstorage EXTENDED to compress. Also find
181 * large attributes with attstorage EXTENDED or EXTERNAL, and store them
185 toast_values, toast_isnull) > maxDataLen)
190 if (biggest_attno < 0)
194 * Attempt to compress it inline, if it has attstorage EXTENDED
201 * has attstorage EXTERNAL, ignore on subsequent compression
208 * If this value is by itself more than maxDataLen (after compression
209 * if any), push it out to the toast table immediately, if possible.
210 * This avoids uselessly compressing other fields in the common case
211 * where we have one long field and several short ones.
213 * XXX maybe the threshold should be less than maxDataLen?
215 if (toast_attr[biggest_attno].tai_size > maxDataLen &&
221 * Second we look for attributes of attstorage EXTENDED or EXTERNAL that
222 * are still inline, and make them external. But skip this if there's no
223 * toast table to push them to.
226 toast_values, toast_isnull) > maxDataLen &&
232 if (biggest_attno < 0)
238 * Round 3 - this time we take attributes with storage MAIN into
242 toast_values, toast_isnull) > maxDataLen)
247 if (biggest_attno < 0)
254 * Finally we store attributes of type MAIN externally. At this point we
255 * increase the target tuple size, so that MAIN attributes aren't stored
256 * externally unless really necessary.
261 toast_values, toast_isnull) > maxDataLen &&
267 if (biggest_attno < 0)
274 * In the case we toasted any values, we need to build a new heap tuple
275 * with the changed values.
281 int32 new_header_len;
286 * Calculate the new size of the tuple.
288 * Note: we used to assume here that the old tuple's t_hoff must equal
289 * the new_header_len value, but that was incorrect. The old tuple
290 * might have a smaller-than-current natts, if there's been an ALTER
291 * TABLE ADD COLUMN since it was stored; and that would lead to a
292 * different conclusion about the size of the null bitmap, or even
293 * whether there needs to be one at all.
298 new_header_len =
MAXALIGN(new_header_len);
300 toast_values, toast_isnull);
301 new_tuple_len = new_header_len + new_data_len;
304 * Allocate and zero the space needed, and fill HeapTupleData fields.
307 result_tuple->
t_len = new_tuple_len;
311 result_tuple->
t_data = new_data;
314 * Copy the existing tuple header, but adjust natts and t_hoff.
318 new_data->
t_hoff = new_header_len;
320 /* Copy over the data, and fill the null bitmap if needed */
324 (
char *) new_data + new_header_len,
331 result_tuple = newtup;
340 * toast_flatten_tuple -
342 * "Flatten" a tuple to contain no out-of-line toasted fields.
343 * (This does not eliminate compressed or short-header datums.)
345 * Note: we expect the caller already checked HeapTupleHasExternal(tup),
346 * so there is no need for a short-circuit path.
353 int numAttrs = tupleDesc->
natts;
360 * Break down the tuple into fields.
365 memset(toast_free, 0, numAttrs *
sizeof(
bool));
367 for (
i = 0;
i < numAttrs;
i++)
370 * Look at non-null varlena attributes
381 toast_free[
i] =
true;
387 * Form the reconfigured tuple.
392 * Be sure to copy the tuple's identity fields. We also make a point of
393 * copying visibility info, just in case anybody looks at those fields in
409 * Free allocated temp values
411 for (
i = 0;
i < numAttrs;
i++)
420 * toast_flatten_tuple_to_datum -
422 * "Flatten" a tuple containing out-of-line toasted fields into a Datum.
423 * The result is always palloc'd in the current memory context.
425 * We have a general rule that Datums of container types (rows, arrays,
426 * ranges, etc) must not contain any external TOAST pointers. Without
427 * this rule, we'd have to look inside each Datum when preparing a tuple
428 * for storage, which would be expensive and would fail to extend cleanly
429 * to new sorts of container types.
431 * However, we don't want to say that tuples represented as HeapTuples
432 * can't contain toasted fields, so instead this routine should be called
433 * when such a HeapTuple is being converted into a Datum.
435 * While we're at it, we decompress any compressed fields too. This is not
436 * necessary for correctness, but reflects an expectation that compression
437 * will be more effective if applied to the whole tuple not individual
438 * fields. We are not so concerned about that that we want to deconstruct
439 * and reconstruct tuples just to get rid of compressed fields, however.
440 * So callers typically won't call this unless they see that the tuple has
441 * at least one external field.
443 * On the other hand, in-line short-header varlena fields are left alone.
444 * If we "untoasted" them here, they'd just get changed back to short-header
445 * format anyway within heap_fill_tuple.
454 int32 new_header_len;
458 int numAttrs = tupleDesc->
natts;
460 bool has_nulls =
false;
465 /* Build a temporary HeapTuple control structure */
466 tmptup.
t_len = tup_len;
472 * Break down the tuple into fields.
477 memset(toast_free, 0, numAttrs *
sizeof(
bool));
479 for (
i = 0;
i < numAttrs;
i++)
482 * Look at non-null varlena attributes
496 toast_free[
i] =
true;
502 * Calculate the new size of the tuple.
504 * This should match the reconstruction code in
505 * heap_toast_insert_or_update.
510 new_header_len =
MAXALIGN(new_header_len);
512 toast_values, toast_isnull);
513 new_tuple_len = new_header_len + new_data_len;
518 * Copy the existing tuple header, but adjust natts and t_hoff.
522 new_data->
t_hoff = new_header_len;
524 /* Set the composite-Datum header fields correctly */
529 /* Copy over the data, and fill the null bitmap if needed */
533 (
char *) new_data + new_header_len,
536 has_nulls ? new_data->
t_bits : NULL);
539 * Free allocated temp values
541 for (
i = 0;
i < numAttrs;
i++)
550 * toast_build_flattened_tuple -
552 * Build a tuple containing no out-of-line toasted fields.
553 * (This does not eliminate compressed or short-header datums.)
555 * This is essentially just like heap_form_tuple, except that it will
556 * expand any external-data pointers beforehand.
558 * It's not very clear whether it would be preferable to decompress
559 * in-line compressed datums while at it. For now, we don't.
568 int numAttrs = tupleDesc->
natts;
575 * We can pass the caller's isnull array directly to heap_form_tuple, but
576 * we potentially need to modify the values array.
579 memcpy(new_values,
values, numAttrs *
sizeof(
Datum));
582 for (
i = 0;
i < numAttrs;
i++)
585 * Look at non-null varlena attributes
596 freeable_values[num_to_free++] = (
Pointer) new_value;
602 * Form the reconfigured tuple.
607 * Free allocated temp values
609 for (
i = 0;
i < num_to_free;
i++)
610 pfree(freeable_values[
i]);
616 * Fetch a TOAST slice from a heap table.
618 * toastrel is the relation from which chunks are to be fetched.
619 * valueid identifies the TOAST value from which chunks are being fetched.
620 * attrsize is the total size of the TOAST value.
621 * sliceoffset is the byte offset within the TOAST value from which to fetch.
622 * slicelength is the number of bytes to be fetched from the TOAST value.
623 * result is the varlena into which the results should be written.
643 /* Look for the valid index of toast relation */
651 Assert(endchunk <= totalchunks);
653 /* Set up a scan key to fetch from the index. */
660 * No additional condition if fetching all chunks. Otherwise, use an
661 * equality condition for one chunk, and a range condition otherwise.
663 if (startchunk == 0 && endchunk == totalchunks - 1)
665 else if (startchunk == endchunk)
686 /* Prepare for scan */
691 * Read the chunks by index
693 * The index is on (valueid, chunkidx) so they will come in order
695 expectedchunk = startchunk;
708 * Have a chunk, extract the sequence number and the data
721 /* could happen due to heap_form_tuple doing its thing */
727 /* should never happen */
728 elog(
ERROR,
"found toasted toast chunk for toast value %u in %s",
730 chunksize = 0;
/* keep compiler quiet */
735 * Some checks on the data we've found
737 if (curchunk != expectedchunk)
740 errmsg_internal(
"unexpected chunk number %d (expected %d) for toast value %u in %s",
741 curchunk, expectedchunk, valueid,
743 if (curchunk > endchunk)
746 errmsg_internal(
"unexpected chunk number %d (out of range %d..%d) for toast value %u in %s",
748 startchunk, endchunk, valueid,
752 if (chunksize != expected_size)
755 errmsg_internal(
"unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s",
756 chunksize, expected_size,
757 curchunk, totalchunks, valueid,
761 * Copy the data into proper place in our result
764 chcpyend = chunksize - 1;
765 if (curchunk == startchunk)
767 if (curchunk == endchunk)
772 chunkdata + chcpystrt,
773 (chcpyend - chcpystrt) + 1);
779 * Final checks that we successfully fetched the datum
781 if (expectedchunk != (endchunk + 1))
785 expectedchunk, valueid,
788 /* End scan and close indexes. */
static Datum values[MAXATTR]
struct varlena * detoast_attr(struct varlena *attr)
struct varlena * detoast_external_attr(struct varlena *attr)
int errmsg_internal(const char *fmt,...)
int errcode(int sqlerrcode)
#define ereport(elevel,...)
SysScanDesc systable_beginscan_ordered(Relation heapRelation, Relation indexRelation, Snapshot snapshot, int nkeys, ScanKey key)
void systable_endscan_ordered(SysScanDesc sysscan)
HeapTuple systable_getnext_ordered(SysScanDesc sysscan, ScanDirection direction)
Assert(PointerIsAligned(start, uint64))
void heap_fetch_toast_slice(Relation toastrel, Oid valueid, int32 attrsize, int32 sliceoffset, int32 slicelength, struct varlena *result)
void heap_toast_delete(Relation rel, HeapTuple oldtup, bool is_speculative)
HeapTuple heap_toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup, int options)
HeapTuple toast_build_flattened_tuple(TupleDesc tupleDesc, Datum *values, bool *isnull)
HeapTuple toast_flatten_tuple(HeapTuple tup, TupleDesc tupleDesc)
Datum toast_flatten_tuple_to_datum(HeapTupleHeader tup, uint32 tup_len, TupleDesc tupleDesc)
#define TOAST_TUPLE_TARGET
#define TOAST_TUPLE_TARGET_MAIN
#define TOAST_MAX_CHUNK_SIZE
Size heap_compute_data_size(TupleDesc tupleDesc, const Datum *values, const bool *isnull)
HeapTuple heap_form_tuple(TupleDesc tupleDescriptor, const Datum *values, const bool *isnull)
void heap_fill_tuple(TupleDesc tupleDesc, const Datum *values, const bool *isnull, char *data, Size data_size, uint16 *infomask, bits8 *bit)
void heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc, Datum *values, bool *isnull)
HeapTupleData * HeapTuple
HeapTupleHeaderData * HeapTupleHeader
#define SizeofHeapTupleHeader
static void HeapTupleHeaderSetTypMod(HeapTupleHeaderData *tup, int32 typmod)
static int BITMAPLEN(int NATTS)
static void HeapTupleHeaderSetTypeId(HeapTupleHeaderData *tup, Oid datum_typeid)
#define MaxTupleAttributeNumber
static void HeapTupleHeaderSetDatumLength(HeapTupleHeaderData *tup, uint32 len)
#define HeapTupleHeaderSetNatts(tup, natts)
#define MaxHeapAttributeNumber
static Datum fastgetattr(HeapTuple tup, int attnum, TupleDesc tupleDesc, bool *isnull)
static void ItemPointerSetInvalid(ItemPointerData *pointer)
void pfree(void *pointer)
void * palloc0(Size size)
#define ERRCODE_DATA_CORRUPTED
static Datum PointerGetDatum(const void *X)
static Datum ObjectIdGetDatum(Oid X)
static Pointer DatumGetPointer(Datum X)
static Datum Int32GetDatum(int32 X)
static int32 DatumGetInt32(Datum X)
#define RelationGetRelationName(relation)
#define RelationGetToastTupleTarget(relation, defaulttarg)
void ScanKeyInit(ScanKey entry, AttrNumber attributeNumber, StrategyNumber strategy, RegProcedure procedure, Datum argument)
#define BTEqualStrategyNumber
#define BTLessEqualStrategyNumber
#define BTGreaterEqualStrategyNumber
void toast_tuple_init(ToastTupleContext *ttc)
void toast_delete_external(Relation rel, const Datum *values, const bool *isnull, bool is_speculative)
void toast_tuple_try_compression(ToastTupleContext *ttc, int attribute)
void toast_tuple_externalize(ToastTupleContext *ttc, int attribute, int options)
void toast_tuple_cleanup(ToastTupleContext *ttc)
int toast_tuple_find_biggest_attribute(ToastTupleContext *ttc, bool for_compression, bool check_main)
#define TOAST_NEEDS_CHANGE
#define TOASTCOL_INCOMPRESSIBLE
void toast_close_indexes(Relation *toastidxs, int num_indexes, LOCKMODE lock)
int toast_open_indexes(Relation toastrel, LOCKMODE lock, Relation **toastidxs, int *num_indexes)
Snapshot get_toast_snapshot(void)
static FormData_pg_attribute * TupleDescAttr(TupleDesc tupdesc, int i)
static CompactAttribute * TupleDescCompactAttr(TupleDesc tupdesc, int i)
static bool VARATT_IS_SHORT(const void *PTR)
static bool VARATT_IS_EXTENDED(const void *PTR)
static bool VARATT_IS_EXTERNAL(const void *PTR)
static Size VARSIZE(const void *PTR)
static char * VARDATA(const void *PTR)
static bool VARATT_IS_COMPRESSED(const void *PTR)
static char * VARDATA_SHORT(const void *PTR)
static Size VARSIZE_SHORT(const void *PTR)