1 /*-------------------------------------------------------------------------
4 * Support routines for external and compressed storage of
5 * variable size attributes.
7 * Copyright (c) 2000-2005, PostgreSQL Global Development Group
11 * $PostgreSQL: pgsql/src/backend/access/heap/tuptoaster.c,v 1.47 2005/01/01 05:43:06 momjian 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"
38 #include "utils/typcache.h"
43 static void toast_delete(Relation rel, HeapTuple oldtup);
44 static void toast_delete_datum(Relation rel, Datum value);
45 static void toast_insert_or_update(Relation rel, HeapTuple newtup,
47 static Datum toast_save_datum(Relation rel, Datum value);
48 static varattrib *toast_fetch_datum(varattrib *attr);
49 static varattrib *toast_fetch_datum_slice(varattrib *attr,
50 int32 sliceoffset, int32 length);
54 * heap_tuple_toast_attrs -
56 * This is the central public entry point for toasting from heapam.
58 * Calls the appropriate event specific action.
62 heap_tuple_toast_attrs(Relation rel, HeapTuple newtup, HeapTuple oldtup)
65 toast_delete(rel, oldtup);
67 toast_insert_or_update(rel, newtup, oldtup);
72 * heap_tuple_fetch_attr -
74 * Public entry point to get back a toasted value
75 * external storage (possibly still in compressed format).
79 heap_tuple_fetch_attr(varattrib *attr)
83 if (VARATT_IS_EXTERNAL(attr))
86 * This is an external stored plain value
88 result = toast_fetch_datum(attr);
93 * This is a plain value inside of the main tuple - why am I
104 * heap_tuple_untoast_attr -
106 * Public entry point to get back a toasted value from compression
107 * or external storage.
111 heap_tuple_untoast_attr(varattrib *attr)
115 if (VARATT_IS_EXTERNAL(attr))
117 if (VARATT_IS_COMPRESSED(attr))
120 * This is an external stored compressed value
121 * Fetch it from the toast heap and decompress.
126 tmp = toast_fetch_datum(attr);
127 result = (varattrib *) palloc(attr->va_content.va_external.va_rawsize
129 VARATT_SIZEP(result) = attr->va_content.va_external.va_rawsize
131 pglz_decompress((PGLZ_Header *) tmp, VARATT_DATA(result));
138 * This is an external stored plain value
140 result = toast_fetch_datum(attr);
143 else if (VARATT_IS_COMPRESSED(attr))
146 * This is a compressed value inside of the main tuple
148 result = (varattrib *) palloc(attr->va_content.va_compressed.va_rawsize
150 VARATT_SIZEP(result) = attr->va_content.va_compressed.va_rawsize
152 pglz_decompress((PGLZ_Header *) attr, VARATT_DATA(result));
157 * This is a plain value inside of the main tuple - why am I
167 * heap_tuple_untoast_attr_slice -
169 * Public entry point to get back part of a toasted value
170 * from compression or external storage.
174 heap_tuple_untoast_attr_slice(varattrib *attr, int32 sliceoffset, int32 slicelength)
180 if (VARATT_IS_COMPRESSED(attr))
184 if (VARATT_IS_EXTERNAL(attr))
185 tmp = toast_fetch_datum(attr);
188 tmp = attr; /* compressed in main tuple */
191 preslice = (varattrib *) palloc(attr->va_content.va_external.va_rawsize
193 VARATT_SIZEP(preslice) = attr->va_content.va_external.va_rawsize + VARHDRSZ;
194 pglz_decompress((PGLZ_Header *) tmp, VARATT_DATA(preslice));
202 if (VARATT_IS_EXTERNAL(attr))
205 return (toast_fetch_datum_slice(attr, sliceoffset, slicelength));
211 /* slicing of datum for compressed cases and plain value */
213 attrsize = VARSIZE(preslice) - VARHDRSZ;
214 if (sliceoffset >= attrsize)
220 if (((sliceoffset + slicelength) > attrsize) || slicelength < 0)
221 slicelength = attrsize - sliceoffset;
223 result = (varattrib *) palloc(slicelength + VARHDRSZ);
224 VARATT_SIZEP(result) = slicelength + VARHDRSZ;
226 memcpy(VARDATA(result), VARDATA(preslice) + sliceoffset, slicelength);
228 if (preslice != attr)
236 * toast_raw_datum_size -
238 * Return the raw (detoasted) size of a varlena datum
242 toast_raw_datum_size(Datum value)
244 varattrib *attr = (varattrib *) DatumGetPointer(value);
247 if (VARATT_IS_COMPRESSED(attr))
250 * va_rawsize shows the original data size, whether the datum is
253 result = attr->va_content.va_compressed.va_rawsize + VARHDRSZ;
255 else if (VARATT_IS_EXTERNAL(attr))
258 * an uncompressed external attribute has rawsize including the
259 * header (not too consistent!)
261 result = attr->va_content.va_external.va_rawsize;
265 /* plain untoasted datum */
266 result = VARSIZE(attr);
275 * Cascaded delete toast-entries on DELETE
279 toast_delete(Relation rel, HeapTuple oldtup)
282 Form_pg_attribute *att;
285 Datum toast_values[MaxHeapAttributeNumber];
286 char toast_nulls[MaxHeapAttributeNumber];
289 * Get the tuple descriptor and break down the tuple into fields.
291 * NOTE: it's debatable whether to use heap_deformtuple() here or just
292 * heap_getattr() only the varlena columns. The latter could win if
293 * there are few varlena columns and many non-varlena ones. However,
294 * heap_deformtuple costs only O(N) while the heap_getattr way would
295 * cost O(N^2) if there are many varlena columns, so it seems better
296 * to err on the side of linear cost. (We won't even be here unless
297 * there's at least one varlena column, by the way.)
299 tupleDesc = rel->rd_att;
300 att = tupleDesc->attrs;
301 numAttrs = tupleDesc->natts;
303 Assert(numAttrs <= MaxHeapAttributeNumber);
304 heap_deformtuple(oldtup, tupleDesc, toast_values, toast_nulls);
307 * Check for external stored attributes and delete them from the
308 * secondary relation.
310 for (i = 0; i < numAttrs; i++)
312 if (att[i]->attlen == -1)
314 Datum value = toast_values[i];
316 if (toast_nulls[i] != 'n' && VARATT_IS_EXTERNAL(value))
317 toast_delete_datum(rel, value);
324 * toast_insert_or_update -
326 * Delete no-longer-used toast-entries and create new ones to
327 * make the new tuple fit on INSERT or UPDATE
331 toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup)
334 Form_pg_attribute *att;
338 bool need_change = false;
339 bool need_free = false;
340 bool need_delold = false;
341 bool has_nulls = false;
345 char toast_action[MaxHeapAttributeNumber];
346 char toast_nulls[MaxHeapAttributeNumber];
347 char toast_oldnulls[MaxHeapAttributeNumber];
348 Datum toast_values[MaxHeapAttributeNumber];
349 Datum toast_oldvalues[MaxHeapAttributeNumber];
350 int32 toast_sizes[MaxHeapAttributeNumber];
351 bool toast_free[MaxHeapAttributeNumber];
352 bool toast_delold[MaxHeapAttributeNumber];
355 * Get the tuple descriptor and break down the tuple(s) into fields.
357 tupleDesc = rel->rd_att;
358 att = tupleDesc->attrs;
359 numAttrs = tupleDesc->natts;
361 Assert(numAttrs <= MaxHeapAttributeNumber);
362 heap_deformtuple(newtup, tupleDesc, toast_values, toast_nulls);
364 heap_deformtuple(oldtup, tupleDesc, toast_oldvalues, toast_oldnulls);
367 * Then collect information about the values given
369 * NOTE: toast_action[i] can have these values:
370 * ' ' default handling
371 * 'p' already processed --- don't touch it
372 * 'x' incompressible, but OK to move off
374 * NOTE: toast_sizes[i] is only made valid for varlena attributes with
375 * toast_action[i] different from 'p'.
378 memset(toast_action, ' ', numAttrs * sizeof(char));
379 memset(toast_free, 0, numAttrs * sizeof(bool));
380 memset(toast_delold, 0, numAttrs * sizeof(bool));
382 for (i = 0; i < numAttrs; i++)
384 varattrib *old_value;
385 varattrib *new_value;
390 * For UPDATE get the old and new values of this attribute
392 old_value = (varattrib *) DatumGetPointer(toast_oldvalues[i]);
393 new_value = (varattrib *) DatumGetPointer(toast_values[i]);
396 * If the old value is an external stored one, check if it has
397 * changed so we have to delete it later.
399 if (att[i]->attlen == -1 && toast_oldnulls[i] != 'n' &&
400 VARATT_IS_EXTERNAL(old_value))
402 if (toast_nulls[i] == 'n' || !VARATT_IS_EXTERNAL(new_value) ||
403 old_value->va_content.va_external.va_valueid !=
404 new_value->va_content.va_external.va_valueid ||
405 old_value->va_content.va_external.va_toastrelid !=
406 new_value->va_content.va_external.va_toastrelid)
409 * The old external stored value isn't needed any more
412 toast_delold[i] = true;
418 * This attribute isn't changed by this update so we
419 * reuse the original reference to the old value in
422 toast_action[i] = 'p';
423 toast_sizes[i] = VARATT_SIZE(toast_values[i]);
431 * For INSERT simply get the new value
433 new_value = (varattrib *) DatumGetPointer(toast_values[i]);
437 * Handle NULL attributes
439 if (toast_nulls[i] == 'n')
441 toast_action[i] = 'p';
447 * Now look at varlena attributes
449 if (att[i]->attlen == -1)
452 * If the table's attribute says PLAIN always, force it so.
454 if (att[i]->attstorage == 'p')
455 toast_action[i] = 'p';
458 * We took care of UPDATE above, so any external value we find
459 * still in the tuple must be someone else's we cannot reuse.
460 * Expand it to plain (and, probably, toast it again below).
462 if (VARATT_IS_EXTERNAL(new_value))
464 new_value = heap_tuple_untoast_attr(new_value);
465 toast_values[i] = PointerGetDatum(new_value);
466 toast_free[i] = true;
472 * Remember the size of this attribute
474 toast_sizes[i] = VARATT_SIZE(new_value);
479 * Not a varlena attribute, plain storage always
481 toast_action[i] = 'p';
486 * Compress and/or save external until data fits into target length
488 * 1: Inline compress attributes with attstorage 'x'
489 * 2: Store attributes with attstorage 'x' or 'e' external
490 * 3: Inline compress attributes with attstorage 'm'
491 * 4: Store attributes with attstorage 'm' external
494 maxDataLen = offsetof(HeapTupleHeaderData, t_bits);
496 maxDataLen += BITMAPLEN(numAttrs);
497 maxDataLen = TOAST_TUPLE_TARGET - MAXALIGN(maxDataLen);
500 * Look for attributes with attstorage 'x' to compress
502 while (MAXALIGN(ComputeDataSize(tupleDesc, toast_values, toast_nulls)) >
505 int biggest_attno = -1;
506 int32 biggest_size = MAXALIGN(sizeof(varattrib));
511 * Search for the biggest yet uncompressed internal attribute
513 for (i = 0; i < numAttrs; i++)
515 if (toast_action[i] != ' ')
517 if (VARATT_IS_EXTENDED(toast_values[i]))
519 if (att[i]->attstorage != 'x')
521 if (toast_sizes[i] > biggest_size)
524 biggest_size = toast_sizes[i];
528 if (biggest_attno < 0)
532 * Attempt to compress it inline
535 old_value = toast_values[i];
536 new_value = toast_compress_datum(old_value);
538 if (DatumGetPointer(new_value) != NULL)
540 /* successful compression */
542 pfree(DatumGetPointer(old_value));
543 toast_values[i] = new_value;
544 toast_free[i] = true;
545 toast_sizes[i] = VARATT_SIZE(toast_values[i]);
552 * incompressible data, ignore on subsequent compression
555 toast_action[i] = 'x';
560 * Second we look for attributes of attstorage 'x' or 'e' that are
563 while (MAXALIGN(ComputeDataSize(tupleDesc, toast_values, toast_nulls)) >
564 maxDataLen && rel->rd_rel->reltoastrelid != InvalidOid)
566 int biggest_attno = -1;
567 int32 biggest_size = MAXALIGN(sizeof(varattrib));
571 * Search for the biggest yet inlined attribute with
572 * attstorage equals 'x' or 'e'
575 for (i = 0; i < numAttrs; i++)
577 if (toast_action[i] == 'p')
579 if (VARATT_IS_EXTERNAL(toast_values[i]))
581 if (att[i]->attstorage != 'x' && att[i]->attstorage != 'e')
583 if (toast_sizes[i] > biggest_size)
586 biggest_size = toast_sizes[i];
590 if (biggest_attno < 0)
594 * Store this external
597 old_value = toast_values[i];
598 toast_action[i] = 'p';
599 toast_values[i] = toast_save_datum(rel, toast_values[i]);
601 pfree(DatumGetPointer(old_value));
603 toast_free[i] = true;
604 toast_sizes[i] = VARATT_SIZE(toast_values[i]);
611 * Round 3 - this time we take attributes with storage 'm' into
614 while (MAXALIGN(ComputeDataSize(tupleDesc, toast_values, toast_nulls)) >
617 int biggest_attno = -1;
618 int32 biggest_size = MAXALIGN(sizeof(varattrib));
623 * Search for the biggest yet uncompressed internal attribute
625 for (i = 0; i < numAttrs; i++)
627 if (toast_action[i] != ' ')
629 if (VARATT_IS_EXTENDED(toast_values[i]))
631 if (att[i]->attstorage != 'm')
633 if (toast_sizes[i] > biggest_size)
636 biggest_size = toast_sizes[i];
640 if (biggest_attno < 0)
644 * Attempt to compress it inline
647 old_value = toast_values[i];
648 new_value = toast_compress_datum(old_value);
650 if (DatumGetPointer(new_value) != NULL)
652 /* successful compression */
654 pfree(DatumGetPointer(old_value));
655 toast_values[i] = new_value;
656 toast_free[i] = true;
657 toast_sizes[i] = VARATT_SIZE(toast_values[i]);
664 * incompressible data, ignore on subsequent compression
667 toast_action[i] = 'x';
672 * Finally we store attributes of type 'm' external
674 while (MAXALIGN(ComputeDataSize(tupleDesc, toast_values, toast_nulls)) >
675 maxDataLen && rel->rd_rel->reltoastrelid != InvalidOid)
677 int biggest_attno = -1;
678 int32 biggest_size = MAXALIGN(sizeof(varattrib));
682 * Search for the biggest yet inlined attribute with
686 for (i = 0; i < numAttrs; i++)
688 if (toast_action[i] == 'p')
690 if (VARATT_IS_EXTERNAL(toast_values[i]))
692 if (att[i]->attstorage != 'm')
694 if (toast_sizes[i] > biggest_size)
697 biggest_size = toast_sizes[i];
701 if (biggest_attno < 0)
705 * Store this external
708 old_value = toast_values[i];
709 toast_action[i] = 'p';
710 toast_values[i] = toast_save_datum(rel, toast_values[i]);
712 pfree(DatumGetPointer(old_value));
714 toast_free[i] = true;
715 toast_sizes[i] = VARATT_SIZE(toast_values[i]);
722 * In the case we toasted any values, we need to build a new heap
723 * tuple with the changed values.
727 HeapTupleHeader olddata = newtup->t_data;
732 * Calculate the new size of the tuple. Header size should not
733 * change, but data size might.
735 new_len = offsetof(HeapTupleHeaderData, t_bits);
737 new_len += BITMAPLEN(numAttrs);
738 if (olddata->t_infomask & HEAP_HASOID)
739 new_len += sizeof(Oid);
740 new_len = MAXALIGN(new_len);
741 Assert(new_len == olddata->t_hoff);
742 new_len += ComputeDataSize(tupleDesc, toast_values, toast_nulls);
745 * Allocate new tuple in same context as old one.
747 new_data = (char *) MemoryContextAlloc(newtup->t_datamcxt, new_len);
748 newtup->t_data = (HeapTupleHeader) new_data;
749 newtup->t_len = new_len;
752 * Put the tuple header and the changed values into place
754 memcpy(new_data, olddata, olddata->t_hoff);
756 DataFill((char *) new_data + olddata->t_hoff,
760 &(newtup->t_data->t_infomask),
761 has_nulls ? newtup->t_data->t_bits : NULL);
764 * In the case we modified a previously modified tuple again, free
765 * the memory from the previous run
767 if ((char *) olddata != ((char *) newtup + HEAPTUPLESIZE))
772 * Free allocated temp values
775 for (i = 0; i < numAttrs; i++)
777 pfree(DatumGetPointer(toast_values[i]));
780 * Delete external values from the old tuple
783 for (i = 0; i < numAttrs; i++)
785 toast_delete_datum(rel, toast_oldvalues[i]);
790 * toast_flatten_tuple_attribute -
792 * If a Datum is of composite type, "flatten" it to contain no toasted fields.
793 * This must be invoked on any potentially-composite field that is to be
794 * inserted into a tuple. Doing this preserves the invariant that toasting
795 * goes only one level deep in a tuple.
799 toast_flatten_tuple_attribute(Datum value,
800 Oid typeId, int32 typeMod)
803 HeapTupleHeader olddata;
804 HeapTupleHeader new_data;
806 HeapTupleData tmptup;
807 Form_pg_attribute *att;
810 bool need_change = false;
811 bool has_nulls = false;
812 Datum toast_values[MaxTupleAttributeNumber];
813 char toast_nulls[MaxTupleAttributeNumber];
814 bool toast_free[MaxTupleAttributeNumber];
817 * See if it's a composite type, and get the tupdesc if so.
819 tupleDesc = lookup_rowtype_tupdesc_noerror(typeId, typeMod, true);
820 if (tupleDesc == NULL)
821 return value; /* not a composite type */
823 att = tupleDesc->attrs;
824 numAttrs = tupleDesc->natts;
827 * Break down the tuple into fields.
829 olddata = DatumGetHeapTupleHeader(value);
830 Assert(typeId == HeapTupleHeaderGetTypeId(olddata));
831 Assert(typeMod == HeapTupleHeaderGetTypMod(olddata));
832 /* Build a temporary HeapTuple control structure */
833 tmptup.t_len = HeapTupleHeaderGetDatumLength(olddata);
834 ItemPointerSetInvalid(&(tmptup.t_self));
835 tmptup.t_tableOid = InvalidOid;
836 tmptup.t_data = olddata;
838 Assert(numAttrs <= MaxTupleAttributeNumber);
839 heap_deformtuple(&tmptup, tupleDesc, toast_values, toast_nulls);
841 memset(toast_free, 0, numAttrs * sizeof(bool));
843 for (i = 0; i < numAttrs; i++)
846 * Look at non-null varlena attributes
848 if (toast_nulls[i] == 'n')
850 else if (att[i]->attlen == -1)
852 varattrib *new_value;
854 new_value = (varattrib *) DatumGetPointer(toast_values[i]);
855 if (VARATT_IS_EXTENDED(new_value))
857 new_value = heap_tuple_untoast_attr(new_value);
858 toast_values[i] = PointerGetDatum(new_value);
859 toast_free[i] = true;
866 * If nothing to untoast, just return the original tuple.
872 * Calculate the new size of the tuple. Header size should not
873 * change, but data size might.
875 new_len = offsetof(HeapTupleHeaderData, t_bits);
877 new_len += BITMAPLEN(numAttrs);
878 if (olddata->t_infomask & HEAP_HASOID)
879 new_len += sizeof(Oid);
880 new_len = MAXALIGN(new_len);
881 Assert(new_len == olddata->t_hoff);
882 new_len += ComputeDataSize(tupleDesc, toast_values, toast_nulls);
884 new_data = (HeapTupleHeader) palloc0(new_len);
887 * Put the tuple header and the changed values into place
889 memcpy(new_data, olddata, olddata->t_hoff);
891 HeapTupleHeaderSetDatumLength(new_data, new_len);
893 DataFill((char *) new_data + olddata->t_hoff,
897 &(new_data->t_infomask),
898 has_nulls ? new_data->t_bits : NULL);
901 * Free allocated temp values
903 for (i = 0; i < numAttrs; i++)
905 pfree(DatumGetPointer(toast_values[i]));
907 return PointerGetDatum(new_data);
912 * toast_compress_datum -
914 * Create a compressed version of a varlena datum
916 * If we fail (ie, compressed result is actually bigger than original)
917 * then return NULL. We must not use compressed data if it'd expand
922 toast_compress_datum(Datum value)
926 tmp = (varattrib *) palloc(sizeof(PGLZ_Header) + VARATT_SIZE(value));
927 pglz_compress(VARATT_DATA(value), VARATT_SIZE(value) - VARHDRSZ,
929 PGLZ_strategy_default);
930 if (VARATT_SIZE(tmp) < VARATT_SIZE(value))
932 /* successful compression */
933 VARATT_SIZEP(tmp) |= VARATT_FLAG_COMPRESSED;
934 return PointerGetDatum(tmp);
938 /* incompressible data */
940 return PointerGetDatum(NULL);
948 * Save one single datum into the secondary relation and return
949 * a varattrib reference for it.
953 toast_save_datum(Relation rel, Datum value)
958 InsertIndexResult idxres;
959 TupleDesc toasttupDesc;
966 char data[TOAST_MAX_CHUNK_SIZE];
974 * Create the varattrib reference
976 result = (varattrib *) palloc(sizeof(varattrib));
978 result->va_header = sizeof(varattrib) | VARATT_FLAG_EXTERNAL;
979 if (VARATT_IS_COMPRESSED(value))
981 result->va_header |= VARATT_FLAG_COMPRESSED;
982 result->va_content.va_external.va_rawsize =
983 ((varattrib *) value)->va_content.va_compressed.va_rawsize;
986 result->va_content.va_external.va_rawsize = VARATT_SIZE(value);
988 result->va_content.va_external.va_extsize =
989 VARATT_SIZE(value) - VARHDRSZ;
990 result->va_content.va_external.va_valueid = newoid();
991 result->va_content.va_external.va_toastrelid =
992 rel->rd_rel->reltoastrelid;
995 * Initialize constant parts of the tuple data
997 t_values[0] = ObjectIdGetDatum(result->va_content.va_external.va_valueid);
998 t_values[2] = PointerGetDatum(&chunk_data);
1004 * Get the data to process
1006 data_p = VARATT_DATA(value);
1007 data_todo = VARATT_SIZE(value) - VARHDRSZ;
1010 * Open the toast relation. We must explicitly lock the toast index
1011 * because we aren't using an index scan here.
1013 toastrel = heap_open(rel->rd_rel->reltoastrelid, RowExclusiveLock);
1014 toasttupDesc = toastrel->rd_att;
1015 toastidx = index_open(toastrel->rd_rel->reltoastidxid);
1016 LockRelation(toastidx, RowExclusiveLock);
1019 * Split up the item into chunks
1021 while (data_todo > 0)
1024 * Calculate the size of this chunk
1026 chunk_size = Min(TOAST_MAX_CHUNK_SIZE, data_todo);
1029 * Build a tuple and store it
1031 t_values[1] = Int32GetDatum(chunk_seq++);
1032 VARATT_SIZEP(&chunk_data) = chunk_size + VARHDRSZ;
1033 memcpy(VARATT_DATA(&chunk_data), data_p, chunk_size);
1034 toasttup = heap_formtuple(toasttupDesc, t_values, t_nulls);
1035 if (!HeapTupleIsValid(toasttup))
1036 elog(ERROR, "failed to build TOAST tuple");
1038 simple_heap_insert(toastrel, toasttup);
1041 * Create the index entry. We cheat a little here by not using
1042 * FormIndexDatum: this relies on the knowledge that the index
1043 * columns are the same as the initial columns of the table.
1045 * Note also that there had better not be any user-created index on
1046 * the TOAST table, since we don't bother to update anything else.
1048 idxres = index_insert(toastidx, t_values, t_nulls,
1049 &(toasttup->t_self),
1050 toastrel, toastidx->rd_index->indisunique);
1052 elog(ERROR, "failed to insert index entry for TOAST tuple");
1058 heap_freetuple(toasttup);
1061 * Move on to next chunk
1063 data_todo -= chunk_size;
1064 data_p += chunk_size;
1068 * Done - close toast relation and return the reference
1070 UnlockRelation(toastidx, RowExclusiveLock);
1071 index_close(toastidx);
1072 heap_close(toastrel, RowExclusiveLock);
1074 return PointerGetDatum(result);
1079 * toast_delete_datum -
1081 * Delete a single external stored value.
1085 toast_delete_datum(Relation rel, Datum value)
1087 varattrib *attr = (varattrib *) DatumGetPointer(value);
1090 ScanKeyData toastkey;
1091 IndexScanDesc toastscan;
1094 if (!VARATT_IS_EXTERNAL(attr))
1098 * Open the toast relation and it's index
1100 toastrel = heap_open(attr->va_content.va_external.va_toastrelid,
1102 toastidx = index_open(toastrel->rd_rel->reltoastidxid);
1105 * Setup a scan key to fetch from the index by va_valueid (we don't
1106 * particularly care whether we see them in sequence or not)
1108 ScanKeyInit(&toastkey,
1110 BTEqualStrategyNumber, F_OIDEQ,
1111 ObjectIdGetDatum(attr->va_content.va_external.va_valueid));
1114 * Find the chunks by index
1116 toastscan = index_beginscan(toastrel, toastidx, SnapshotToast,
1118 while ((toasttup = index_getnext(toastscan, ForwardScanDirection)) != NULL)
1121 * Have a chunk, delete it
1123 simple_heap_delete(toastrel, &toasttup->t_self);
1127 * End scan and close relations
1129 index_endscan(toastscan);
1130 index_close(toastidx);
1131 heap_close(toastrel, RowExclusiveLock);
1136 * toast_fetch_datum -
1138 * Reconstruct an in memory varattrib from the chunks saved
1139 * in the toast relation
1143 toast_fetch_datum(varattrib *attr)
1147 ScanKeyData toastkey;
1148 IndexScanDesc toastscan;
1150 TupleDesc toasttupDesc;
1160 ressize = attr->va_content.va_external.va_extsize;
1161 numchunks = ((ressize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
1163 result = (varattrib *) palloc(ressize + VARHDRSZ);
1164 VARATT_SIZEP(result) = ressize + VARHDRSZ;
1165 if (VARATT_IS_COMPRESSED(attr))
1166 VARATT_SIZEP(result) |= VARATT_FLAG_COMPRESSED;
1169 * Open the toast relation and its index
1171 toastrel = heap_open(attr->va_content.va_external.va_toastrelid,
1173 toasttupDesc = toastrel->rd_att;
1174 toastidx = index_open(toastrel->rd_rel->reltoastidxid);
1177 * Setup a scan key to fetch from the index by va_valueid
1179 ScanKeyInit(&toastkey,
1181 BTEqualStrategyNumber, F_OIDEQ,
1182 ObjectIdGetDatum(attr->va_content.va_external.va_valueid));
1185 * Read the chunks by index
1187 * Note that because the index is actually on (valueid, chunkidx) we will
1188 * see the chunks in chunkidx order, even though we didn't explicitly
1193 toastscan = index_beginscan(toastrel, toastidx, SnapshotToast,
1195 while ((ttup = index_getnext(toastscan, ForwardScanDirection)) != NULL)
1198 * Have a chunk, extract the sequence number and the data
1200 residx = DatumGetInt32(heap_getattr(ttup, 2, toasttupDesc, &isnull));
1202 chunk = DatumGetPointer(heap_getattr(ttup, 3, toasttupDesc, &isnull));
1204 chunksize = VARATT_SIZE(chunk) - VARHDRSZ;
1207 * Some checks on the data we've found
1209 if (residx != nextidx)
1210 elog(ERROR, "unexpected chunk number %d (expected %d) for toast value %u",
1212 attr->va_content.va_external.va_valueid);
1213 if (residx < numchunks - 1)
1215 if (chunksize != TOAST_MAX_CHUNK_SIZE)
1216 elog(ERROR, "unexpected chunk size %d in chunk %d for toast value %u",
1218 attr->va_content.va_external.va_valueid);
1220 else if (residx < numchunks)
1222 if ((residx * TOAST_MAX_CHUNK_SIZE + chunksize) != ressize)
1223 elog(ERROR, "unexpected chunk size %d in chunk %d for toast value %u",
1225 attr->va_content.va_external.va_valueid);
1228 elog(ERROR, "unexpected chunk number %d for toast value %u",
1230 attr->va_content.va_external.va_valueid);
1233 * Copy the data into proper place in our result
1235 memcpy(((char *) VARATT_DATA(result)) + residx * TOAST_MAX_CHUNK_SIZE,
1243 * Final checks that we successfully fetched the datum
1245 if (nextidx != numchunks)
1246 elog(ERROR, "missing chunk number %d for toast value %u",
1248 attr->va_content.va_external.va_valueid);
1251 * End scan and close relations
1253 index_endscan(toastscan);
1254 index_close(toastidx);
1255 heap_close(toastrel, AccessShareLock);
1261 * toast_fetch_datum_slice -
1263 * Reconstruct a segment of a varattrib from the chunks saved
1264 * in the toast relation
1268 toast_fetch_datum_slice(varattrib *attr, int32 sliceoffset, int32 length)
1272 ScanKeyData toastkey[3];
1274 IndexScanDesc toastscan;
1276 TupleDesc toasttupDesc;
1293 attrsize = attr->va_content.va_external.va_extsize;
1294 totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
1296 if (sliceoffset >= attrsize)
1302 if (((sliceoffset + length) > attrsize) || length < 0)
1303 length = attrsize - sliceoffset;
1305 result = (varattrib *) palloc(length + VARHDRSZ);
1306 VARATT_SIZEP(result) = length + VARHDRSZ;
1308 if (VARATT_IS_COMPRESSED(attr))
1309 VARATT_SIZEP(result) |= VARATT_FLAG_COMPRESSED;
1312 return (result); /* Can save a lot of work at this point! */
1314 startchunk = sliceoffset / TOAST_MAX_CHUNK_SIZE;
1315 endchunk = (sliceoffset + length - 1) / TOAST_MAX_CHUNK_SIZE;
1316 numchunks = (endchunk - startchunk) + 1;
1318 startoffset = sliceoffset % TOAST_MAX_CHUNK_SIZE;
1319 endoffset = (sliceoffset + length - 1) % TOAST_MAX_CHUNK_SIZE;
1322 * Open the toast relation and it's index
1324 toastrel = heap_open(attr->va_content.va_external.va_toastrelid,
1326 toasttupDesc = toastrel->rd_att;
1327 toastidx = index_open(toastrel->rd_rel->reltoastidxid);
1330 * Setup a scan key to fetch from the index. This is either two keys
1331 * or three depending on the number of chunks.
1333 ScanKeyInit(&toastkey[0],
1335 BTEqualStrategyNumber, F_OIDEQ,
1336 ObjectIdGetDatum(attr->va_content.va_external.va_valueid));
1339 * Use equality condition for one chunk, a range condition otherwise:
1343 ScanKeyInit(&toastkey[1],
1345 BTEqualStrategyNumber, F_INT4EQ,
1346 Int32GetDatum(startchunk));
1351 ScanKeyInit(&toastkey[1],
1353 BTGreaterEqualStrategyNumber, F_INT4GE,
1354 Int32GetDatum(startchunk));
1355 ScanKeyInit(&toastkey[2],
1357 BTLessEqualStrategyNumber, F_INT4LE,
1358 Int32GetDatum(endchunk));
1363 * Read the chunks by index
1365 * The index is on (valueid, chunkidx) so they will come in order
1367 nextidx = startchunk;
1368 toastscan = index_beginscan(toastrel, toastidx, SnapshotToast,
1369 nscankeys, toastkey);
1370 while ((ttup = index_getnext(toastscan, ForwardScanDirection)) != NULL)
1373 * Have a chunk, extract the sequence number and the data
1375 residx = DatumGetInt32(heap_getattr(ttup, 2, toasttupDesc, &isnull));
1377 chunk = DatumGetPointer(heap_getattr(ttup, 3, toasttupDesc, &isnull));
1379 chunksize = VARATT_SIZE(chunk) - VARHDRSZ;
1382 * Some checks on the data we've found
1384 if ((residx != nextidx) || (residx > endchunk) || (residx < startchunk))
1385 elog(ERROR, "unexpected chunk number %d (expected %d) for toast value %u",
1387 attr->va_content.va_external.va_valueid);
1388 if (residx < totalchunks - 1)
1390 if (chunksize != TOAST_MAX_CHUNK_SIZE)
1391 elog(ERROR, "unexpected chunk size %d in chunk %d for toast value %u",
1393 attr->va_content.va_external.va_valueid);
1397 if ((residx * TOAST_MAX_CHUNK_SIZE + chunksize) != attrsize)
1398 elog(ERROR, "unexpected chunk size %d in chunk %d for toast value %u",
1400 attr->va_content.va_external.va_valueid);
1404 * Copy the data into proper place in our result
1407 chcpyend = chunksize - 1;
1408 if (residx == startchunk)
1409 chcpystrt = startoffset;
1410 if (residx == endchunk)
1411 chcpyend = endoffset;
1413 memcpy(((char *) VARATT_DATA(result)) +
1414 (residx * TOAST_MAX_CHUNK_SIZE - sliceoffset) + chcpystrt,
1415 VARATT_DATA(chunk) + chcpystrt,
1416 (chcpyend - chcpystrt) + 1);
1422 * Final checks that we successfully fetched the datum
1424 if (nextidx != (endchunk + 1))
1425 elog(ERROR, "missing chunk number %d for toast value %u",
1427 attr->va_content.va_external.va_valueid);
1430 * End scan and close relations
1432 index_endscan(toastscan);
1433 index_close(toastidx);
1434 heap_close(toastrel, AccessShareLock);