1 /*-------------------------------------------------------------------------
4 * Support routines for external and compressed storage of
5 * variable size attributes.
7 * Copyright (c) 2000-2003, PostgreSQL Global Development Group
11 * $Header: /cvsroot/pgsql/src/backend/access/heap/tuptoaster.c,v 1.38 2003/08/04 23:59:37 tgl Exp $
15 * heap_tuple_toast_attrs -
16 * Try to make a given tuple fit into one page by compressing
17 * or moving off attributes
19 * heap_tuple_untoast_attr -
20 * Fetch back a given value from the "secondary" relation
22 *-------------------------------------------------------------------------
30 #include "access/heapam.h"
31 #include "access/genam.h"
32 #include "access/tuptoaster.h"
33 #include "catalog/catalog.h"
34 #include "utils/rel.h"
35 #include "utils/builtins.h"
36 #include "utils/fmgroids.h"
37 #include "utils/pg_lzcompress.h"
42 static void toast_delete(Relation rel, HeapTuple oldtup);
43 static void toast_delete_datum(Relation rel, Datum value);
44 static void toast_insert_or_update(Relation rel, HeapTuple newtup,
46 static Datum toast_save_datum(Relation rel, Datum value);
47 static varattrib *toast_fetch_datum(varattrib *attr);
48 static varattrib *toast_fetch_datum_slice(varattrib *attr,
49 int32 sliceoffset, int32 length);
53 * heap_tuple_toast_attrs -
55 * This is the central public entry point for toasting from heapam.
57 * Calls the appropriate event specific action.
61 heap_tuple_toast_attrs(Relation rel, HeapTuple newtup, HeapTuple oldtup)
64 toast_delete(rel, oldtup);
66 toast_insert_or_update(rel, newtup, oldtup);
71 * heap_tuple_fetch_attr -
73 * Public entry point to get back a toasted value
74 * external storage (possibly still in compressed format).
78 heap_tuple_fetch_attr(varattrib *attr)
82 if (VARATT_IS_EXTERNAL(attr))
85 * This is an external stored plain value
87 result = toast_fetch_datum(attr);
92 * This is a plain value inside of the main tuple - why am I
103 * heap_tuple_untoast_attr -
105 * Public entry point to get back a toasted value from compression
106 * or external storage.
110 heap_tuple_untoast_attr(varattrib *attr)
114 if (VARATT_IS_EXTERNAL(attr))
116 if (VARATT_IS_COMPRESSED(attr))
119 * This is an external stored compressed value
120 * Fetch it from the toast heap and decompress.
125 tmp = toast_fetch_datum(attr);
126 result = (varattrib *) palloc(attr->va_content.va_external.va_rawsize
128 VARATT_SIZEP(result) = attr->va_content.va_external.va_rawsize
130 pglz_decompress((PGLZ_Header *) tmp, VARATT_DATA(result));
137 * This is an external stored plain value
139 result = toast_fetch_datum(attr);
142 else if (VARATT_IS_COMPRESSED(attr))
145 * This is a compressed value inside of the main tuple
147 result = (varattrib *) palloc(attr->va_content.va_compressed.va_rawsize
149 VARATT_SIZEP(result) = attr->va_content.va_compressed.va_rawsize
151 pglz_decompress((PGLZ_Header *) attr, VARATT_DATA(result));
156 * This is a plain value inside of the main tuple - why am I
166 * heap_tuple_untoast_attr_slice -
168 * Public entry point to get back part of a toasted value
169 * from compression or external storage.
173 heap_tuple_untoast_attr_slice(varattrib *attr, int32 sliceoffset, int32 slicelength)
179 if (VARATT_IS_COMPRESSED(attr))
183 if (VARATT_IS_EXTERNAL(attr))
184 tmp = toast_fetch_datum(attr);
187 tmp = attr; /* compressed in main tuple */
190 preslice = (varattrib *) palloc(attr->va_content.va_external.va_rawsize
192 VARATT_SIZEP(preslice) = attr->va_content.va_external.va_rawsize + VARHDRSZ;
193 pglz_decompress((PGLZ_Header *) tmp, VARATT_DATA(preslice));
201 if (VARATT_IS_EXTERNAL(attr))
204 return (toast_fetch_datum_slice(attr, sliceoffset, slicelength));
210 /* slicing of datum for compressed cases and plain value */
212 attrsize = VARSIZE(preslice) - VARHDRSZ;
213 if (sliceoffset >= attrsize)
219 if (((sliceoffset + slicelength) > attrsize) || slicelength < 0)
220 slicelength = attrsize - sliceoffset;
222 result = (varattrib *) palloc(slicelength + VARHDRSZ);
223 VARATT_SIZEP(result) = slicelength + VARHDRSZ;
225 memcpy(VARDATA(result), VARDATA(preslice) + sliceoffset, slicelength);
227 if (preslice != attr)
235 * toast_raw_datum_size -
237 * Return the raw (detoasted) size of a varlena datum
241 toast_raw_datum_size(Datum value)
243 varattrib *attr = (varattrib *) DatumGetPointer(value);
246 if (VARATT_IS_COMPRESSED(attr))
249 * va_rawsize shows the original data size, whether the datum is
252 result = attr->va_content.va_compressed.va_rawsize + VARHDRSZ;
254 else if (VARATT_IS_EXTERNAL(attr))
257 * an uncompressed external attribute has rawsize including the
258 * header (not too consistent!)
260 result = attr->va_content.va_external.va_rawsize;
264 /* plain untoasted datum */
265 result = VARSIZE(attr);
274 * Cascaded delete toast-entries on DELETE
278 toast_delete(Relation rel, HeapTuple oldtup)
281 Form_pg_attribute *att;
288 * Get the tuple descriptor, the number of and attribute descriptors.
290 tupleDesc = rel->rd_att;
291 numAttrs = tupleDesc->natts;
292 att = tupleDesc->attrs;
295 * Check for external stored attributes and delete them from the
296 * secondary relation.
298 for (i = 0; i < numAttrs; i++)
300 if (att[i]->attlen == -1)
302 value = heap_getattr(oldtup, i + 1, tupleDesc, &isnull);
303 if (!isnull && VARATT_IS_EXTERNAL(value))
304 toast_delete_datum(rel, value);
311 * toast_insert_or_update -
313 * Delete no-longer-used toast-entries and create new ones to
314 * make the new tuple fit on INSERT or UPDATE
318 toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup)
321 Form_pg_attribute *att;
327 bool need_change = false;
328 bool need_free = false;
329 bool need_delold = false;
330 bool has_nulls = false;
334 char toast_action[MaxHeapAttributeNumber];
335 char toast_nulls[MaxHeapAttributeNumber];
336 Datum toast_values[MaxHeapAttributeNumber];
337 int32 toast_sizes[MaxHeapAttributeNumber];
338 bool toast_free[MaxHeapAttributeNumber];
339 bool toast_delold[MaxHeapAttributeNumber];
342 * Get the tuple descriptor, the number of and attribute descriptors
343 * and the location of the tuple values.
345 tupleDesc = rel->rd_att;
346 numAttrs = tupleDesc->natts;
347 att = tupleDesc->attrs;
350 * Then collect information about the values given
352 * NOTE: toast_action[i] can have these values:
353 * ' ' default handling
354 * 'p' already processed --- don't touch it
355 * 'x' incompressible, but OK to move off
358 memset(toast_action, ' ', numAttrs * sizeof(char));
359 memset(toast_nulls, ' ', numAttrs * sizeof(char));
360 memset(toast_free, 0, numAttrs * sizeof(bool));
361 memset(toast_delold, 0, numAttrs * sizeof(bool));
362 for (i = 0; i < numAttrs; i++)
364 varattrib *old_value;
365 varattrib *new_value;
370 * For UPDATE get the old and new values of this attribute
372 old_value = (varattrib *) DatumGetPointer(
373 heap_getattr(oldtup, i + 1, tupleDesc, &old_isnull));
375 heap_getattr(newtup, i + 1, tupleDesc, &new_isnull);
376 new_value = (varattrib *) DatumGetPointer(toast_values[i]);
379 * If the old value is an external stored one, check if it has
380 * changed so we have to delete it later.
382 if (!old_isnull && att[i]->attlen == -1 &&
383 VARATT_IS_EXTERNAL(old_value))
385 if (new_isnull || !VARATT_IS_EXTERNAL(new_value) ||
386 old_value->va_content.va_external.va_valueid !=
387 new_value->va_content.va_external.va_valueid ||
388 old_value->va_content.va_external.va_toastrelid !=
389 new_value->va_content.va_external.va_toastrelid)
392 * The old external store value isn't needed any more
395 toast_delold[i] = true;
401 * This attribute isn't changed by this update so we
402 * reuse the original reference to the old value in
405 toast_action[i] = 'p';
406 toast_sizes[i] = VARATT_SIZE(toast_values[i]);
414 * For INSERT simply get the new value
417 heap_getattr(newtup, i + 1, tupleDesc, &new_isnull);
421 * Handle NULL attributes
425 toast_action[i] = 'p';
426 toast_nulls[i] = 'n';
432 * Now look at varsize attributes
434 if (att[i]->attlen == -1)
437 * If the table's attribute says PLAIN always, force it so.
439 if (att[i]->attstorage == 'p')
440 toast_action[i] = 'p';
443 * We took care of UPDATE above, so any external value we find
444 * still in the tuple must be someone else's we cannot reuse.
445 * Expand it to plain (and, probably, toast it again below).
447 if (VARATT_IS_EXTERNAL(DatumGetPointer(toast_values[i])))
449 toast_values[i] = PointerGetDatum(heap_tuple_untoast_attr(
450 (varattrib *) DatumGetPointer(toast_values[i])));
451 toast_free[i] = true;
457 * Remember the size of this attribute
459 toast_sizes[i] = VARATT_SIZE(DatumGetPointer(toast_values[i]));
464 * Not a variable size attribute, plain storage always
466 toast_action[i] = 'p';
467 toast_sizes[i] = att[i]->attlen;
472 * Compress and/or save external until data fits into target length
474 * 1: Inline compress attributes with attstorage 'x'
475 * 2: Store attributes with attstorage 'x' or 'e' external
476 * 3: Inline compress attributes with attstorage 'm'
477 * 4: Store attributes with attstorage 'm' external
480 maxDataLen = offsetof(HeapTupleHeaderData, t_bits);
482 maxDataLen += BITMAPLEN(numAttrs);
483 maxDataLen = TOAST_TUPLE_TARGET - MAXALIGN(maxDataLen);
486 * Look for attributes with attstorage 'x' to compress
488 while (MAXALIGN(ComputeDataSize(tupleDesc, toast_values, toast_nulls)) >
491 int biggest_attno = -1;
492 int32 biggest_size = MAXALIGN(sizeof(varattrib));
497 * Search for the biggest yet uncompressed internal attribute
499 for (i = 0; i < numAttrs; i++)
501 if (toast_action[i] != ' ')
503 if (VARATT_IS_EXTENDED(toast_values[i]))
505 if (att[i]->attstorage != 'x')
507 if (toast_sizes[i] > biggest_size)
510 biggest_size = toast_sizes[i];
514 if (biggest_attno < 0)
518 * Attempt to compress it inline
521 old_value = toast_values[i];
522 new_value = toast_compress_datum(old_value);
524 if (DatumGetPointer(new_value) != NULL)
526 /* successful compression */
528 pfree(DatumGetPointer(old_value));
529 toast_values[i] = new_value;
530 toast_free[i] = true;
531 toast_sizes[i] = VARATT_SIZE(toast_values[i]);
538 * incompressible data, ignore on subsequent compression
541 toast_action[i] = 'x';
546 * Second we look for attributes of attstorage 'x' or 'e' that are
549 while (MAXALIGN(ComputeDataSize(tupleDesc, toast_values, toast_nulls)) >
550 maxDataLen && rel->rd_rel->reltoastrelid != InvalidOid)
552 int biggest_attno = -1;
553 int32 biggest_size = MAXALIGN(sizeof(varattrib));
557 * Search for the biggest yet inlined attribute with
558 * attstorage equals 'x' or 'e'
561 for (i = 0; i < numAttrs; i++)
563 if (toast_action[i] == 'p')
565 if (VARATT_IS_EXTERNAL(toast_values[i]))
567 if (att[i]->attstorage != 'x' && att[i]->attstorage != 'e')
569 if (toast_sizes[i] > biggest_size)
572 biggest_size = toast_sizes[i];
576 if (biggest_attno < 0)
580 * Store this external
583 old_value = toast_values[i];
584 toast_action[i] = 'p';
585 toast_values[i] = toast_save_datum(rel, toast_values[i]);
587 pfree(DatumGetPointer(old_value));
589 toast_free[i] = true;
590 toast_sizes[i] = VARATT_SIZE(toast_values[i]);
597 * Round 3 - this time we take attributes with storage 'm' into
600 while (MAXALIGN(ComputeDataSize(tupleDesc, toast_values, toast_nulls)) >
603 int biggest_attno = -1;
604 int32 biggest_size = MAXALIGN(sizeof(varattrib));
609 * Search for the biggest yet uncompressed internal attribute
611 for (i = 0; i < numAttrs; i++)
613 if (toast_action[i] != ' ')
615 if (VARATT_IS_EXTENDED(toast_values[i]))
617 if (att[i]->attstorage != 'm')
619 if (toast_sizes[i] > biggest_size)
622 biggest_size = toast_sizes[i];
626 if (biggest_attno < 0)
630 * Attempt to compress it inline
633 old_value = toast_values[i];
634 new_value = toast_compress_datum(old_value);
636 if (DatumGetPointer(new_value) != NULL)
638 /* successful compression */
640 pfree(DatumGetPointer(old_value));
641 toast_values[i] = new_value;
642 toast_free[i] = true;
643 toast_sizes[i] = VARATT_SIZE(toast_values[i]);
650 * incompressible data, ignore on subsequent compression
653 toast_action[i] = 'x';
658 * Finally we store attributes of type 'm' external
660 while (MAXALIGN(ComputeDataSize(tupleDesc, toast_values, toast_nulls)) >
661 maxDataLen && rel->rd_rel->reltoastrelid != InvalidOid)
663 int biggest_attno = -1;
664 int32 biggest_size = MAXALIGN(sizeof(varattrib));
668 * Search for the biggest yet inlined attribute with
672 for (i = 0; i < numAttrs; i++)
674 if (toast_action[i] == 'p')
676 if (VARATT_IS_EXTERNAL(toast_values[i]))
678 if (att[i]->attstorage != 'm')
680 if (toast_sizes[i] > biggest_size)
683 biggest_size = toast_sizes[i];
687 if (biggest_attno < 0)
691 * Store this external
694 old_value = toast_values[i];
695 toast_action[i] = 'p';
696 toast_values[i] = toast_save_datum(rel, toast_values[i]);
698 pfree(DatumGetPointer(old_value));
700 toast_free[i] = true;
701 toast_sizes[i] = VARATT_SIZE(toast_values[i]);
708 * In the case we toasted any values, we need to build a new heap
709 * tuple with the changed values.
713 HeapTupleHeader olddata = newtup->t_data;
718 * Calculate the new size of the tuple. Header size should not
719 * change, but data size might.
721 new_len = offsetof(HeapTupleHeaderData, t_bits);
723 new_len += BITMAPLEN(numAttrs);
724 if (olddata->t_infomask & HEAP_HASOID)
725 new_len += sizeof(Oid);
726 new_len = MAXALIGN(new_len);
727 Assert(new_len == olddata->t_hoff);
728 new_len += ComputeDataSize(tupleDesc, toast_values, toast_nulls);
731 * Allocate new tuple in same context as old one.
733 new_data = (char *) MemoryContextAlloc(newtup->t_datamcxt, new_len);
734 newtup->t_data = (HeapTupleHeader) new_data;
735 newtup->t_len = new_len;
738 * Put the tuple header and the changed values into place
740 memcpy(new_data, olddata, olddata->t_hoff);
742 DataFill((char *) new_data + olddata->t_hoff,
746 &(newtup->t_data->t_infomask),
747 has_nulls ? newtup->t_data->t_bits : NULL);
750 * In the case we modified a previously modified tuple again, free
751 * the memory from the previous run
753 if ((char *) olddata != ((char *) newtup + HEAPTUPLESIZE))
758 * Free allocated temp values
761 for (i = 0; i < numAttrs; i++)
763 pfree(DatumGetPointer(toast_values[i]));
766 * Delete external values from the old tuple
769 for (i = 0; i < numAttrs; i++)
771 toast_delete_datum(rel,
772 heap_getattr(oldtup, i + 1, tupleDesc, &old_isnull));
777 * toast_compress_datum -
779 * Create a compressed version of a varlena datum
781 * If we fail (ie, compressed result is actually bigger than original)
782 * then return NULL. We must not use compressed data if it'd expand
787 toast_compress_datum(Datum value)
791 tmp = (varattrib *) palloc(sizeof(PGLZ_Header) + VARATT_SIZE(value));
792 pglz_compress(VARATT_DATA(value), VARATT_SIZE(value) - VARHDRSZ,
794 PGLZ_strategy_default);
795 if (VARATT_SIZE(tmp) < VARATT_SIZE(value))
797 /* successful compression */
798 VARATT_SIZEP(tmp) |= VARATT_FLAG_COMPRESSED;
799 return PointerGetDatum(tmp);
803 /* incompressible data */
805 return PointerGetDatum(NULL);
813 * Save one single datum into the secondary relation and return
814 * a varattrib reference for it.
818 toast_save_datum(Relation rel, Datum value)
823 InsertIndexResult idxres;
824 TupleDesc toasttupDesc;
831 char data[TOAST_MAX_CHUNK_SIZE];
839 * Create the varattrib reference
841 result = (varattrib *) palloc(sizeof(varattrib));
843 result->va_header = sizeof(varattrib) | VARATT_FLAG_EXTERNAL;
844 if (VARATT_IS_COMPRESSED(value))
846 result->va_header |= VARATT_FLAG_COMPRESSED;
847 result->va_content.va_external.va_rawsize =
848 ((varattrib *) value)->va_content.va_compressed.va_rawsize;
851 result->va_content.va_external.va_rawsize = VARATT_SIZE(value);
853 result->va_content.va_external.va_extsize =
854 VARATT_SIZE(value) - VARHDRSZ;
855 result->va_content.va_external.va_valueid = newoid();
856 result->va_content.va_external.va_toastrelid =
857 rel->rd_rel->reltoastrelid;
860 * Initialize constant parts of the tuple data
862 t_values[0] = ObjectIdGetDatum(result->va_content.va_external.va_valueid);
863 t_values[2] = PointerGetDatum(&chunk_data);
869 * Get the data to process
871 data_p = VARATT_DATA(value);
872 data_todo = VARATT_SIZE(value) - VARHDRSZ;
875 * Open the toast relation
877 toastrel = heap_open(rel->rd_rel->reltoastrelid, RowExclusiveLock);
878 toasttupDesc = toastrel->rd_att;
879 toastidx = index_open(toastrel->rd_rel->reltoastidxid);
882 * Split up the item into chunks
884 while (data_todo > 0)
887 * Calculate the size of this chunk
889 chunk_size = Min(TOAST_MAX_CHUNK_SIZE, data_todo);
892 * Build a tuple and store it
894 t_values[1] = Int32GetDatum(chunk_seq++);
895 VARATT_SIZEP(&chunk_data) = chunk_size + VARHDRSZ;
896 memcpy(VARATT_DATA(&chunk_data), data_p, chunk_size);
897 toasttup = heap_formtuple(toasttupDesc, t_values, t_nulls);
898 if (!HeapTupleIsValid(toasttup))
899 elog(ERROR, "failed to build TOAST tuple");
901 simple_heap_insert(toastrel, toasttup);
904 * Create the index entry. We cheat a little here by not using
905 * FormIndexDatum: this relies on the knowledge that the index
906 * columns are the same as the initial columns of the table.
908 * Note also that there had better not be any user-created index on
909 * the TOAST table, since we don't bother to update anything else.
911 idxres = index_insert(toastidx, t_values, t_nulls,
913 toastrel, toastidx->rd_index->indisunique);
915 elog(ERROR, "failed to insert index entry for TOAST tuple");
921 heap_freetuple(toasttup);
924 * Move on to next chunk
926 data_todo -= chunk_size;
927 data_p += chunk_size;
931 * Done - close toast relation and return the reference
933 index_close(toastidx);
934 heap_close(toastrel, RowExclusiveLock);
936 return PointerGetDatum(result);
941 * toast_delete_datum -
943 * Delete a single external stored value.
947 toast_delete_datum(Relation rel, Datum value)
949 varattrib *attr = (varattrib *) DatumGetPointer(value);
952 ScanKeyData toastkey;
953 IndexScanDesc toastscan;
956 if (!VARATT_IS_EXTERNAL(attr))
960 * Open the toast relation and it's index
962 toastrel = heap_open(attr->va_content.va_external.va_toastrelid,
964 toastidx = index_open(toastrel->rd_rel->reltoastidxid);
967 * Setup a scan key to fetch from the index by va_valueid (we don't
968 * particularly care whether we see them in sequence or not)
970 ScanKeyEntryInitialize(&toastkey,
973 (RegProcedure) F_OIDEQ,
974 ObjectIdGetDatum(attr->va_content.va_external.va_valueid));
977 * Find the chunks by index
979 toastscan = index_beginscan(toastrel, toastidx, SnapshotToast,
981 while ((toasttup = index_getnext(toastscan, ForwardScanDirection)) != NULL)
984 * Have a chunk, delete it
986 simple_heap_delete(toastrel, &toasttup->t_self);
990 * End scan and close relations
992 index_endscan(toastscan);
993 index_close(toastidx);
994 heap_close(toastrel, RowExclusiveLock);
999 * toast_fetch_datum -
1001 * Reconstruct an in memory varattrib from the chunks saved
1002 * in the toast relation
1006 toast_fetch_datum(varattrib *attr)
1010 ScanKeyData toastkey;
1011 IndexScanDesc toastscan;
1013 TupleDesc toasttupDesc;
1023 ressize = attr->va_content.va_external.va_extsize;
1024 numchunks = ((ressize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
1026 result = (varattrib *) palloc(ressize + VARHDRSZ);
1027 VARATT_SIZEP(result) = ressize + VARHDRSZ;
1028 if (VARATT_IS_COMPRESSED(attr))
1029 VARATT_SIZEP(result) |= VARATT_FLAG_COMPRESSED;
1032 * Open the toast relation and its index
1034 toastrel = heap_open(attr->va_content.va_external.va_toastrelid,
1036 toasttupDesc = toastrel->rd_att;
1037 toastidx = index_open(toastrel->rd_rel->reltoastidxid);
1040 * Setup a scan key to fetch from the index by va_valueid
1042 ScanKeyEntryInitialize(&toastkey,
1045 (RegProcedure) F_OIDEQ,
1046 ObjectIdGetDatum(attr->va_content.va_external.va_valueid));
1049 * Read the chunks by index
1051 * Note that because the index is actually on (valueid, chunkidx) we will
1052 * see the chunks in chunkidx order, even though we didn't explicitly
1057 toastscan = index_beginscan(toastrel, toastidx, SnapshotToast,
1059 while ((ttup = index_getnext(toastscan, ForwardScanDirection)) != NULL)
1062 * Have a chunk, extract the sequence number and the data
1064 residx = DatumGetInt32(heap_getattr(ttup, 2, toasttupDesc, &isnull));
1066 chunk = DatumGetPointer(heap_getattr(ttup, 3, toasttupDesc, &isnull));
1068 chunksize = VARATT_SIZE(chunk) - VARHDRSZ;
1071 * Some checks on the data we've found
1073 if (residx != nextidx)
1074 elog(ERROR, "unexpected chunk number %d (expected %d) for toast value %u",
1076 attr->va_content.va_external.va_valueid);
1077 if (residx < numchunks - 1)
1079 if (chunksize != TOAST_MAX_CHUNK_SIZE)
1080 elog(ERROR, "unexpected chunk size %d in chunk %d for toast value %u",
1082 attr->va_content.va_external.va_valueid);
1084 else if (residx < numchunks)
1086 if ((residx * TOAST_MAX_CHUNK_SIZE + chunksize) != ressize)
1087 elog(ERROR, "unexpected chunk size %d in chunk %d for toast value %u",
1089 attr->va_content.va_external.va_valueid);
1092 elog(ERROR, "unexpected chunk number %d for toast value %u",
1094 attr->va_content.va_external.va_valueid);
1097 * Copy the data into proper place in our result
1099 memcpy(((char *) VARATT_DATA(result)) + residx * TOAST_MAX_CHUNK_SIZE,
1107 * Final checks that we successfully fetched the datum
1109 if (nextidx != numchunks)
1110 elog(ERROR, "missing chunk number %d for toast value %u",
1112 attr->va_content.va_external.va_valueid);
1115 * End scan and close relations
1117 index_endscan(toastscan);
1118 index_close(toastidx);
1119 heap_close(toastrel, AccessShareLock);
1125 * toast_fetch_datum_slice -
1127 * Reconstruct a segment of a varattrib from the chunks saved
1128 * in the toast relation
1132 toast_fetch_datum_slice(varattrib *attr, int32 sliceoffset, int32 length)
1136 ScanKeyData toastkey[3];
1138 IndexScanDesc toastscan;
1140 TupleDesc toasttupDesc;
1157 attrsize = attr->va_content.va_external.va_extsize;
1158 totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
1160 if (sliceoffset >= attrsize)
1166 if (((sliceoffset + length) > attrsize) || length < 0)
1167 length = attrsize - sliceoffset;
1169 result = (varattrib *) palloc(length + VARHDRSZ);
1170 VARATT_SIZEP(result) = length + VARHDRSZ;
1172 if (VARATT_IS_COMPRESSED(attr))
1173 VARATT_SIZEP(result) |= VARATT_FLAG_COMPRESSED;
1176 return (result); /* Can save a lot of work at this point! */
1178 startchunk = sliceoffset / TOAST_MAX_CHUNK_SIZE;
1179 endchunk = (sliceoffset + length - 1) / TOAST_MAX_CHUNK_SIZE;
1180 numchunks = (endchunk - startchunk) + 1;
1182 startoffset = sliceoffset % TOAST_MAX_CHUNK_SIZE;
1183 endoffset = (sliceoffset + length - 1) % TOAST_MAX_CHUNK_SIZE;
1186 * Open the toast relation and it's index
1188 toastrel = heap_open(attr->va_content.va_external.va_toastrelid,
1190 toasttupDesc = toastrel->rd_att;
1191 toastidx = index_open(toastrel->rd_rel->reltoastidxid);
1194 * Setup a scan key to fetch from the index. This is either two keys
1195 * or three depending on the number of chunks.
1197 ScanKeyEntryInitialize(&toastkey[0],
1200 (RegProcedure) F_OIDEQ,
1201 ObjectIdGetDatum(attr->va_content.va_external.va_valueid));
1204 * Now dependent on number of chunks:
1209 ScanKeyEntryInitialize(&toastkey[1],
1212 (RegProcedure) F_INT4EQ,
1213 Int32GetDatum(startchunk));
1218 ScanKeyEntryInitialize(&toastkey[1],
1221 (RegProcedure) F_INT4GE,
1222 Int32GetDatum(startchunk));
1223 ScanKeyEntryInitialize(&toastkey[2],
1226 (RegProcedure) F_INT4LE,
1227 Int32GetDatum(endchunk));
1232 * Read the chunks by index
1234 * The index is on (valueid, chunkidx) so they will come in order
1236 nextidx = startchunk;
1237 toastscan = index_beginscan(toastrel, toastidx, SnapshotToast,
1238 nscankeys, toastkey);
1239 while ((ttup = index_getnext(toastscan, ForwardScanDirection)) != NULL)
1242 * Have a chunk, extract the sequence number and the data
1244 residx = DatumGetInt32(heap_getattr(ttup, 2, toasttupDesc, &isnull));
1246 chunk = DatumGetPointer(heap_getattr(ttup, 3, toasttupDesc, &isnull));
1248 chunksize = VARATT_SIZE(chunk) - VARHDRSZ;
1251 * Some checks on the data we've found
1253 if ((residx != nextidx) || (residx > endchunk) || (residx < startchunk))
1254 elog(ERROR, "unexpected chunk number %d (expected %d) for toast value %u",
1256 attr->va_content.va_external.va_valueid);
1257 if (residx < totalchunks - 1)
1259 if (chunksize != TOAST_MAX_CHUNK_SIZE)
1260 elog(ERROR, "unexpected chunk size %d in chunk %d for toast value %u",
1262 attr->va_content.va_external.va_valueid);
1266 if ((residx * TOAST_MAX_CHUNK_SIZE + chunksize) != attrsize)
1267 elog(ERROR, "unexpected chunk size %d in chunk %d for toast value %u",
1269 attr->va_content.va_external.va_valueid);
1273 * Copy the data into proper place in our result
1276 chcpyend = chunksize - 1;
1277 if (residx == startchunk)
1278 chcpystrt = startoffset;
1279 if (residx == endchunk)
1280 chcpyend = endoffset;
1282 memcpy(((char *) VARATT_DATA(result)) +
1283 (residx * TOAST_MAX_CHUNK_SIZE - sliceoffset) + chcpystrt,
1284 VARATT_DATA(chunk) + chcpystrt,
1285 (chcpyend - chcpystrt) + 1);
1291 * Final checks that we successfully fetched the datum
1293 if (nextidx != (endchunk + 1))
1294 elog(ERROR, "missing chunk number %d for toast value %u",
1296 attr->va_content.va_external.va_valueid);
1299 * End scan and close relations
1301 index_endscan(toastscan);
1302 index_close(toastidx);
1303 heap_close(toastrel, AccessShareLock);