*
* knl_utuptoaster.cpp
* the tuple toaster for ustore.
*
* Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/gausskernel/storage/access/ustore/knl_utuptoaster.cpp
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include <fcntl.h>
#include "access/genam.h"
#include "access/heapam.h"
#include "access/nbtree.h"
#include "access/tableam.h"
#include "nodes/relation.h"
#include "access/tuptoaster.h"
#include "access/ustore/knl_utuptoaster.h"
#include "access/ustore/knl_uheap.h"
#include "access/ustore/knl_utuple.h"
#include "access/ustore/knl_whitebox_test.h"
#include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/index.h"
#include "utils/datum.h"
#include "utils/fmgroids.h"
#include "utils/pg_lzcompress.h"
#include "utils/rel.h"
#include "utils/rel_gs.h"
#include "utils/typcache.h"
#include "commands/vacuum.h"
#include "utils/snapmgr.h"
#include "miscadmin.h"
#include "executor/executor.h"
static void UHeapToastDeleteDatum(Relation rel, Datum value, int options);
static Datum UHeapToastSaveDatum(Relation rel, Datum value, struct varlena *oldexternal, int options);
static Datum UHeapToastCompressDatum(Datum value);
static bool UHeapToastIdValueIdExists(Oid toastrelid, Oid valueid, int2 bucketid);
static bool UHeapToastRelValueidExists(Relation toastrel, Oid valueid);
static Oid UHeapGetNewOidWithIndex(Relation relation, Oid indexId, AttrNumber oidcolumn);
static Datum UHeapToastCompressDatum(Datum value)
{
return toast_compress_datum(value);
}
Oid UHeapGetNewOidWithIndex(Relation relation, Oid indexId, AttrNumber oidcolumn)
{
Oid newOid;
SysScanDesc scan;
ScanKeyData key;
bool collides = false;
Assert(RelationIsUstoreFormat(relation) || RelationIsToast(relation));
TupleTableSlot *slot = MakeSingleTupleTableSlot(RelationGetDescr(relation), false, relation->rd_tam_ops);
do {
CHECK_FOR_INTERRUPTS();
* See comments in GetNewObjectId.
* In the future, we might turn to SnapshotToast when getting new
* chunk_id for toast datum to prevent wrap around.
*/
newOid = GetNewObjectId(IsToastNamespace(RelationGetNamespace(relation)));
ScanKeyInit(&key, oidcolumn, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(newOid));
scan = systable_beginscan(relation, indexId, true, SnapshotAny, ATTR_FIRST, &key);
collides = UHeapSysIndexGetnextSlot(scan, ForwardScanDirection, slot);
systable_endscan(scan);
} while (collides);
ExecDropSingleTupleTableSlot(slot);
return newOid;
}
void UHeapToastDelete(Relation relation, UHeapTuple utuple)
{
TupleDesc tupleDesc;
int numAttrs;
int i;
Datum toastValues[MaxHeapAttributeNumber] = {0};
bool toastIsNull[MaxHeapAttributeNumber] = {0};
WHITEBOX_TEST_STUB(UHEAP_TOAST_DELETE_FAILED, WhiteboxDefaultErrorEmit);
Assert(relation->rd_rel->relkind == RELKIND_RELATION);
Assert(utuple->tupTableType == UHEAP_TUPLE);
tupleDesc = relation->rd_att;
numAttrs = tupleDesc->natts;
Assert(numAttrs <= MaxHeapAttributeNumber);
UHeapDeformTuple(utuple, tupleDesc, toastValues, toastIsNull);
* Check for external stored attributes and delete them from the secondary
* relation.
*/
for (i = 0; i < numAttrs; i++) {
if (((TupleDescAttr(tupleDesc, i)))->attlen == -1) {
Datum value = toastValues[i];
if (toastIsNull[i]) {
continue;
} else if (VARATT_IS_EXTERNAL_ONDISK(PointerGetDatum(value))) {
UHeapToastDeleteDatum(relation, value, 0);
}
}
}
}
UHeapTuple UHeapToastInsertOrUpdate(Relation relation, UHeapTuple newtup, UHeapTuple oldtup, int options)
{
UHeapTuple resTup;
TupleDesc tupleDesc;
int numAttrs;
int i;
bool needChange = false;
bool needFree = false;
bool needDelOld = false;
bool hasNulls = false;
Size maxDataLen;
Size hoff;
char toastAction[MaxHeapAttributeNumber] = {0};
bool toastIsNull[MaxHeapAttributeNumber] = {0};
bool toastOldIsNull[MaxHeapAttributeNumber] = {0};
Datum toastValues[MaxHeapAttributeNumber] = {0};
Datum toastOldValues[MaxHeapAttributeNumber] = {0};
struct varlena *toastOldExternal[MaxHeapAttributeNumber] = {0};
uint32 toastSizes[MaxHeapAttributeNumber] = {0};
bool toastFree[MaxHeapAttributeNumber] = {0};
bool toastDelOld[MaxHeapAttributeNumber] = {0};
bool enableReserve = u_sess->attr.attr_storage.reserve_space_for_nullable_atts;
WHITEBOX_TEST_STUB(UHEAP_TOAST_INSERT_UPDATE_FAILED, WhiteboxDefaultErrorEmit);
* We should only ever be called for tuples of plain relations or
* materialized views --- recursing on a toast rel is bad news.
*/
Assert(relation->rd_rel->relkind == RELKIND_RELATION);
* Get the tuple descriptor and break down the tuple(s) into fields.
*/
tupleDesc = relation->rd_att;
numAttrs = tupleDesc->natts;
bool enableReverseBitmap = NAttrsReserveSpace(numAttrs);
enableReserve = enableReserve && enableReverseBitmap;
Assert(!enableReserve || (enableReserve && enableReverseBitmap));
Assert(enableReverseBitmap || (enableReverseBitmap == false && enableReserve == false));
Assert(numAttrs <= MaxHeapAttributeNumber);
UHeapDeformTuple(newtup, tupleDesc, toastValues, toastIsNull);
if (oldtup != NULL)
UHeapDeformTuple(oldtup, tupleDesc, toastOldValues, toastOldIsNull);
errno_t rc = memset_s(toastAction, sizeof(toastAction), ' ', numAttrs * sizeof(char));
securec_check(rc, "\0", "\0");
rc = memset_s(toastOldExternal, sizeof(toastOldExternal), 0, numAttrs * sizeof(struct varlena *));
securec_check(rc, "\0", "\0");
rc = memset_s(toastFree, sizeof(toastFree), 0, numAttrs * sizeof(bool));
securec_check(rc, "\0", "\0");
rc = memset_s(toastDelOld, sizeof(toastDelOld), 0, numAttrs * sizeof(bool));
securec_check(rc, "\0", "\0");
for (i = 0; i < numAttrs; i++) {
Form_pg_attribute att = (TupleDescAttr(tupleDesc, i));
struct varlena *oldValue = NULL;
struct varlena *newValue = NULL;
if (oldtup != NULL) {
* For UPDATE get the old and new values of this attribute
*/
oldValue = (struct varlena *)DatumGetPointer(toastOldValues[i]);
newValue = (struct varlena *)DatumGetPointer(toastValues[i]);
* If the old value is stored on disk, check if it has changed so
* we have to delete it later.
*/
if (att->attlen == -1 && !toastOldIsNull[i] && VARATT_IS_EXTERNAL_ONDISK(oldValue)) {
if (toastIsNull[i] || !VARATT_IS_EXTERNAL_ONDISK(newValue) || RelationIsLogicallyLogged(relation) ||
memcmp((char *)oldValue, (char *)newValue, VARSIZE_EXTERNAL(oldValue)) != 0) {
* The old external stored value isn't needed any more
* after the update
*/
toastDelOld[i] = true;
needDelOld = true;
} else {
* This attribute isn't changed by this update so we reuse
* the original reference to the old value in the new
* tuple.
*/
toastAction[i] = 'p';
continue;
}
}
} else {
* For INSERT simply get the new value
*/
newValue = (struct varlena *)DatumGetPointer(toastValues[i]);
}
if (toastIsNull[i]) {
toastAction[i] = 'p';
hasNulls = true;
continue;
}
* Now look at varlena attributes
*/
if (att->attlen == -1) {
if (VARATT_IS_HUGE_TOAST_POINTER(newValue)) {
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support clob/blob type more than 1GB of Ustore")));
}
* If the table's attribute says PLAIN always, force it so.
*/
if (att->attstorage == 'p')
toastAction[i] = 'p';
* We took care of UPDATE above, so any external value we find
* still in the tuple must be someone else's that we cannot reuse
* (this includes the case of an out-of-line in-memory datum).
* Fetch it back (without decompression, unless we are forcing
* PLAIN storage). If necessary, we'll push it out as a new
* external value below.
*/
if (VARATT_IS_EXTERNAL(newValue)) {
toastOldExternal[i] = newValue;
if (att->attstorage == 'p')
newValue = heap_tuple_untoast_attr(newValue);
else
newValue = heap_tuple_fetch_attr(newValue);
toastValues[i] = PointerGetDatum(newValue);
toastFree[i] = true;
needChange = true;
needFree = true;
}
* Remember the size of this attribute
*/
toastSizes[i] = VARSIZE_ANY(newValue);
} else {
* Not a varlena attribute, plain storage always
*/
toastAction[i] = 'p';
}
}
* Compress and/or save external until data fits into target length
*
* 1: Inline compress attributes with attstorage 'x', and store very
* large attributes with attstorage 'x' or 'e' external immediately
* 2: Store attributes with attstorage 'x' or 'e' external
* 3: Inline compress attributes with attstorage 'm'
* 4: Store attributes with attstorage 'm' external
* ----------
*/
hoff = SizeOfUHeapDiskTupleData;
if (hasNulls) {
int nullcount = 0;
int i = 0;
for (i = 0; i < numAttrs; i++) {
if (toastIsNull[i]) {
nullcount++;
}
}
if (enableReverseBitmap) {
hoff += BITMAPLEN(numAttrs + nullcount);
} else {
hoff += BITMAPLEN(numAttrs);
}
}
maxDataLen = UTOAST_TUPLE_TARGET - hoff;
* Look for attributes with attstorage 'x' to compress. Also find large
* attributes with attstorage 'x' or 'e', and store them external.
*/
while (UHeapCalcTupleDataSize(tupleDesc, toastValues, toastIsNull, hoff, enableReverseBitmap, enableReserve) >
maxDataLen) {
int biggestAttno = -1;
uint32 biggestSize = MAXALIGN(TOAST_POINTER_SIZE);
Datum oldValue;
Datum newValue;
* Search for the biggest yet unprocessed internal attribute
*/
for (i = 0; i < numAttrs; i++) {
Form_pg_attribute att = (TupleDescAttr(tupleDesc, i));
if (toastAction[i] != ' ')
continue;
if (VARATT_IS_EXTERNAL(DatumGetPointer(toastValues[i])))
continue;
if (VARATT_IS_COMPRESSED(DatumGetPointer(toastValues[i])))
continue;
if (att->attstorage != 'x' && att->attstorage != 'e')
continue;
if (toastSizes[i] > biggestSize) {
biggestAttno = i;
biggestSize = toastSizes[i];
}
}
if (biggestAttno < 0) {
break;
}
* Attempt to compress it inline, if it has attstorage 'x'
*/
i = biggestAttno;
if (((TupleDescAttr(tupleDesc, i)))->attstorage == 'x') {
oldValue = toastValues[i];
newValue = UHeapToastCompressDatum(oldValue);
if (DatumGetPointer(newValue) != NULL) {
if (toastFree[i])
pfree(DatumGetPointer(oldValue));
toastValues[i] = newValue;
toastFree[i] = true;
toastSizes[i] = VARSIZE(DatumGetPointer(toastValues[i]));
needChange = true;
needFree = true;
} else {
toastAction[i] = 'x';
}
} else {
toastAction[i] = 'x';
}
* If this value is by itself more than maxDataLen (after compression
* if any), push it out to the toast table immediately, if possible.
* This avoids uselessly compressing other fields in the common case
* where we have one long field and several short ones.
*
* XXX maybe the threshold should be less than maxDataLen?
*/
if (toastSizes[i] > maxDataLen && relation->rd_rel->reltoastrelid != InvalidOid) {
oldValue = toastValues[i];
toastAction[i] = 'p';
toastValues[i] = UHeapToastSaveDatum(relation, toastValues[i], toastOldExternal[i], options);
if (toastFree[i])
pfree(DatumGetPointer(oldValue));
toastFree[i] = true;
needChange = true;
needFree = true;
}
}
* Second we look for attributes of attstorage 'x' or 'e' that are still
* inline. But skip this if there's no toast table to push them to.
*/
while (UHeapCalcTupleDataSize(tupleDesc, toastValues, toastIsNull, hoff, enableReverseBitmap, enableReserve) >
maxDataLen && relation->rd_rel->reltoastrelid != InvalidOid) {
int biggestAttno = -1;
uint32 biggestSize = MAXALIGN(TOAST_POINTER_SIZE);
Datum oldValue;
* Search for the biggest yet inlined attribute with
* attstorage equals 'x' or 'e'
* ------
*/
for (i = 0; i < numAttrs; i++) {
Form_pg_attribute att = (TupleDescAttr(tupleDesc, i));
if (toastAction[i] == 'p')
continue;
if (VARATT_IS_EXTERNAL(DatumGetPointer(toastValues[i])))
continue;
if (att->attstorage != 'x' && att->attstorage != 'e')
continue;
if (toastSizes[i] > biggestSize) {
biggestAttno = i;
biggestSize = toastSizes[i];
}
}
if (biggestAttno < 0) {
break;
}
* Store this external
*/
i = biggestAttno;
oldValue = toastValues[i];
toastAction[i] = 'p';
toastValues[i] = UHeapToastSaveDatum(relation, toastValues[i], toastOldExternal[i], options);
if (toastFree[i])
pfree(DatumGetPointer(oldValue));
toastFree[i] = true;
needChange = true;
needFree = true;
}
* Round 3 - this time we take attributes with storage 'm' into
* compression
*/
while (UHeapCalcTupleDataSize(tupleDesc, toastValues, toastIsNull, hoff, enableReverseBitmap, enableReserve) >
maxDataLen) {
int biggestAttno = -1;
uint32 biggestSize = MAXALIGN(TOAST_POINTER_SIZE);
Datum oldValue;
Datum newValue;
* Search for the biggest yet uncompressed internal attribute
*/
for (i = 0; i < numAttrs; i++) {
if (toastAction[i] != ' ')
continue;
if (VARATT_IS_EXTERNAL(DatumGetPointer(toastValues[i])))
continue;
if (VARATT_IS_COMPRESSED(DatumGetPointer(toastValues[i])))
continue;
if (((TupleDescAttr(tupleDesc, i)))->attstorage != 'm')
continue;
if (toastSizes[i] > biggestSize) {
biggestAttno = i;
biggestSize = toastSizes[i];
}
}
if (biggestAttno < 0) {
break;
}
* Attempt to compress it inline
*/
i = biggestAttno;
oldValue = toastValues[i];
newValue = UHeapToastCompressDatum(oldValue);
if (DatumGetPointer(newValue) != NULL) {
if (toastFree[i])
pfree(DatumGetPointer(oldValue));
toastValues[i] = newValue;
toastFree[i] = true;
toastSizes[i] = VARSIZE(DatumGetPointer(toastValues[i]));
needChange = true;
needFree = true;
} else {
toastAction[i] = 'x';
}
}
* Finally we store attributes of type 'm' externally. At this point we
* increase the target tuple size, so that 'm' attributes aren't stored
* externally unless really necessary.
*/
maxDataLen = MaxUHeapTupleSize(relation) - hoff;
while (UHeapCalcTupleDataSize(tupleDesc, toastValues, toastIsNull, hoff, enableReverseBitmap,enableReserve) >
maxDataLen && relation->rd_rel->reltoastrelid != InvalidOid) {
int biggestAttno = -1;
uint32 biggestSize = MAXALIGN(TOAST_POINTER_SIZE);
Datum oldValue;
* Search for the biggest yet inlined attribute with
* attstorage = 'm'
* --------
*/
for (i = 0; i < numAttrs; i++) {
if (toastAction[i] == 'p')
continue;
if (VARATT_IS_EXTERNAL(DatumGetPointer(toastValues[i])))
continue;
if (((TupleDescAttr(tupleDesc, i)))->attstorage != 'm')
continue;
if (toastSizes[i] > biggestSize) {
biggestAttno = i;
biggestSize = toastSizes[i];
}
}
if (biggestAttno < 0)
break;
* Store this external
*/
i = biggestAttno;
oldValue = toastValues[i];
toastAction[i] = 'p';
toastValues[i] = UHeapToastSaveDatum(relation, toastValues[i], toastOldExternal[i], options);
if (toastFree[i])
pfree(DatumGetPointer(oldValue));
toastFree[i] = true;
needChange = true;
needFree = true;
}
* In the case we toasted any values, we need to build a new heap tuple
* with the changed values.
*/
if (needChange) {
UHeapDiskTuple oldData = newtup->disk_tuple;
UHeapDiskTuple newData;
int32 newHeaderLen;
int32 newDataLen;
int32 newTupleLen;
errno_t rc;
* Calculate the new size of the tuple.
*
* Note: we used to assume here that the old tuple's t_hoff must equal
* the new_header_len value, but that was incorrect. The old tuple
* might have a smaller-than-current natts, if there's been an ALTER
* TABLE ADD COLUMN since it was stored; and that would lead to a
* different conclusion about the size of the null bitmap, or even
* whether there needs to be one at all.
*/
newHeaderLen = SizeOfUHeapDiskTupleData;
if (hasNulls) {
int nullcount = 0;
int i = 0;
for (i = 0; i < numAttrs; i++) {
if (toastIsNull[i]) {
nullcount++;
}
}
if (enableReverseBitmap) {
newHeaderLen += BITMAPLEN(numAttrs + nullcount);
} else {
newHeaderLen += BITMAPLEN(numAttrs);
}
}
newDataLen = UHeapCalcTupleDataSize(tupleDesc, toastValues, toastIsNull, newHeaderLen, enableReverseBitmap,
enableReserve);
newTupleLen = newHeaderLen + newDataLen;
* Allocate and zero the space needed, and fill InplaceHeapTupleData fields.
*/
resTup = (UHeapTuple)uheaptup_alloc(UHeapTupleDataSize + newTupleLen);
resTup->disk_tuple_size = newTupleLen;
resTup->ctid = newtup->ctid;
resTup->table_oid = newtup->table_oid;
resTup->xc_node_id = newtup->xc_node_id;
newData = (UHeapDiskTuple)((char *)resTup + UHeapTupleDataSize);
resTup->disk_tuple = newData;
* Copy the existing tuple header, but adjust natts and t_hoff. Also clear
* the first three bits of the flag, which are reset in UHeapFillDiskTuple
*/
rc = memcpy_s(newData, SizeOfUHeapDiskTupleData, oldData, SizeOfUHeapDiskTupleData);
securec_check(rc, "\0", "\0");
UHeapTupleHeaderSetNatts(newData, numAttrs);
newData->t_hoff = newHeaderLen;
newData->flag &= UHEAP_VIS_STATUS_MASK;
if (hasNulls) {
UHeapFillDiskTuple<true>(tupleDesc, toastValues, toastIsNull, newData, newDataLen, enableReverseBitmap,
enableReserve);
} else {
UHeapFillDiskTuple<false>(tupleDesc, toastValues, toastIsNull, newData, newDataLen, enableReverseBitmap,
enableReserve);
}
} else {
resTup = newtup;
}
* Free allocated temp values
*/
if (needFree)
for (i = 0; i < numAttrs; i++)
if (toastFree[i])
pfree(DatumGetPointer(toastValues[i]));
* Delete external values from the old tuple
*/
if (needDelOld)
for (i = 0; i < numAttrs; i++)
if (toastDelOld[i])
UHeapToastDeleteDatum(relation, toastOldValues[i], 0);
return resTup;
}
* toast_save_datum -
*
* Save one single datum into the secondary relation and return
* a Datum reference for it.
*
* rel: the main relation we're working with (not the toast rel!)
* value: datum to be pushed to toast storage
* oldexternal: if not NULL, toast pointer previously representing the datum
* options: options to be passed to heap_insert() for toast rows
* ----------
*/
static Datum UHeapToastSaveDatum(Relation rel, Datum value, struct varlena *oldexternal, int options)
{
Relation toastrel;
Relation toastidx;
UHeapTuple toasttup;
TupleDesc toastTupDesc;
Datum tValues[3] = {0};
bool tIsnull[3] = {0};
CommandId mycid = GetCurrentCommandId(true);
struct varlena *result = NULL;
struct varatt_external toastPointer;
union {
struct varlena hdr;
char data[UTOAST_MAX_CHUNK_SIZE + sizeof(struct varlena) + sizeof(int32)];
int32 align_it;
} chunkData;
int32 chunkSize;
int32 chunkSeq = 0;
char *dataP = NULL;
int32 dataTodo;
Pointer dval = DatumGetPointer(value);
errno_t rc;
int2 bucketid = InvalidBktId;
Assert(!VARATT_IS_EXTERNAL(value));
rc = memset_s(&chunkData, sizeof(chunkData), 0, sizeof(chunkData));
securec_check(rc, "", "");
* Open the toast relation and its index. We can use the index to check
* uniqueness of the OID we assign to the toasted item, even though it has
* additional columns besides OID.
*/
if (RelationIsBucket(rel)) {
bucketid = rel->rd_node.bucketNode;
}
toastrel = heap_open(rel->rd_rel->reltoastrelid, RowExclusiveLock, bucketid);
toastTupDesc = toastrel->rd_att;
toastidx = index_open(toastrel->rd_rel->reltoastidxid, RowExclusiveLock, bucketid);
* Get the data pointer and length, and compute va_rawsize and va_extsize.
*
* va_rawsize is the size of the equivalent fully uncompressed datum, so
* we have to adjust for short headers.
*
* va_extsize is the actual size of the data payload in the toast records.
*/
if (VARATT_IS_SHORT(dval)) {
dataP = VARDATA_SHORT(dval);
dataTodo = VARSIZE_SHORT(dval) - VARHDRSZ_SHORT;
toastPointer.va_rawsize = dataTodo + VARHDRSZ;
toastPointer.va_extsize = dataTodo;
} else if (VARATT_IS_COMPRESSED(dval)) {
dataP = VARDATA(dval);
dataTodo = VARSIZE(dval) - VARHDRSZ;
toastPointer.va_rawsize = VARRAWSIZE_4B_C(dval) + VARHDRSZ;
toastPointer.va_extsize = dataTodo;
Assert(VARATT_EXTERNAL_IS_COMPRESSED(toastPointer));
} else {
dataP = VARDATA(dval);
dataTodo = VARSIZE(dval) - VARHDRSZ;
toastPointer.va_rawsize = VARSIZE(dval);
toastPointer.va_extsize = dataTodo;
}
* Insert the correct table OID into the result TOAST pointer.
*
* Normally this is the actual OID of the target toast table, but during
* table-rewriting operations such as CLUSTER, we have to insert the OID
* of the table's real permanent toast table instead. rd_toastoid is set
* if we have to substitute such an OID.
*/
if (OidIsValid(rel->rd_toastoid))
toastPointer.va_toastrelid = rel->rd_toastoid;
else
toastPointer.va_toastrelid = RelationGetRelid(toastrel);
* Choose an OID to use as the value ID for this toast value.
*
* Normally we just choose an unused OID within the toast table. But
* during table-rewriting operations where we are preserving an existing
* toast table OID, we want to preserve toast value OIDs too. So, if
* rd_toastoid is set and we had a prior external value from that same
* toast table, re-use its value ID. If we didn't have a prior external
* value (which is a corner case, but possible if the table's attstorage
* options have been changed), we have to pick a value ID that doesn't
* conflict with either new or existing toast value OIDs.
*/
if (!OidIsValid(rel->rd_toastoid)) {
toastPointer.va_valueid = UHeapGetNewOidWithIndex(toastrel, RelationGetRelid(toastidx), (AttrNumber)1);
} else {
toastPointer.va_valueid = InvalidOid;
if (oldexternal != NULL) {
struct varatt_external oldToastPointer;
int2 toastbid;
Assert(VARATT_IS_EXTERNAL_ONDISK_B(oldexternal));
VARATT_EXTERNAL_GET_POINTER_B(oldToastPointer, oldexternal, toastbid);
if (oldToastPointer.va_toastrelid == rel->rd_toastoid) {
Assert(bucketid == toastbid);
toastPointer.va_valueid = oldToastPointer.va_valueid;
* There is a corner case here: the table rewrite might have
* to copy both live and recently-dead versions of a row, and
* those versions could easily reference the same toast value.
* When we copy the second or later version of such a row,
* reusing the OID will mean we select an OID that's already
* in the new toast table. Check for that, and if so, just
* fall through without writing the data again.
*
* While annoying and ugly-looking, this is a good thing
* because it ensures that we wind up with only one copy of
* the toast value when there is only one copy in the old
* toast table. Before we detected this case, we'd have made
* multiple copies, wasting space; and what's worse, the
* copies belonging to already-deleted heap tuples would not
* be reclaimed by VACUUM.
*/
if (UHeapToastRelValueidExists(toastrel, toastPointer.va_valueid)) {
dataTodo = 0;
}
}
}
if (toastPointer.va_valueid == InvalidOid) {
* new value; must choose an OID that doesn't conflict in either
* old or new toast table
*/
do {
toastPointer.va_valueid = UHeapGetNewOidWithIndex(toastrel, RelationGetRelid(toastidx), (AttrNumber)1);
} while (UHeapToastIdValueIdExists(rel->rd_toastoid, toastPointer.va_valueid, bucketid));
}
}
* Initialize constant parts of the tuple data
*/
tValues[ATTR_FIRST - 1] = ObjectIdGetDatum(toastPointer.va_valueid);
tValues[ATTR_SECOND] = PointerGetDatum(&chunkData);
tIsnull[ATTR_FIRST - 1] = false;
tIsnull[ATTR_SECOND - 1] = false;
tIsnull[ATTR_THIRD - 1] = false;
* Split up the item into chunks
*/
while (dataTodo > 0) {
* Calculate the size of this chunk
*/
chunkSize = Min(UTOAST_MAX_CHUNK_SIZE, (uint32)dataTodo);
* Build a tuple and store it
*/
tValues[1] = Int32GetDatum(chunkSeq++);
SET_VARSIZE(&chunkData, chunkSize + VARHDRSZ);
rc = memcpy_s(VARDATA(&chunkData), UTOAST_MAX_CHUNK_SIZE, dataP, chunkSize);
securec_check(rc, "", "");
toasttup = UHeapFormTuple(toastTupDesc, tValues, tIsnull);
(void)UHeapInsert(toastrel, toasttup, mycid, NULL, true);
* Create the index entry. We cheat a little here by not using
* FormIndexDatum: this relies on the knowledge that the index columns
* are the same as the initial columns of the table.
*
* Note also that there had better not be any user-created index on
* the TOAST table, since we don't bother to update anything else.
*/
(void)index_insert(toastidx, tValues, tIsnull, &(toasttup->ctid), toastrel,
toastidx->rd_index->indisunique ? UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);
* Free memory
*/
UHeapFreeTuple(toasttup);
* Move on to next chunk
*/
dataTodo -= chunkSize;
dataP += chunkSize;
}
* Done - close toast relation
*/
index_close(toastidx, RowExclusiveLock);
heap_close(toastrel, RowExclusiveLock);
* Create the TOAST pointer value that we'll return
*/
bool isBucketRelation = RelationIsBucket(rel);
Size resultSize = TOAST_POINTER_SIZE;
if (isBucketRelation) {
resultSize += sizeof(int2);
}
result = (struct varlena *)palloc(resultSize);
if (isBucketRelation) {
SET_VARTAG_EXTERNAL(result, VARTAG_BUCKET);
} else {
SET_VARTAG_EXTERNAL(result, VARTAG_ONDISK);
}
rc = memcpy_s(VARDATA_EXTERNAL(result), TOAST_POINTER_SIZE, &toastPointer, sizeof(toastPointer));
securec_check(rc, "", "");
if (isBucketRelation) {
rc = memcpy_s((char *)result + TOAST_POINTER_SIZE, sizeof(int2), &bucketid, sizeof(int2));
securec_check(rc, "", "");
}
return PointerGetDatum(result);
}
static void UHeapToastDeleteDatum(Relation rel, Datum value, int options)
{
struct varlena *attr = (struct varlena *)DatumGetPointer(value);
struct varatt_external toastPointer;
Relation toastrel;
Relation toastidx;
ScanKeyData toastkey;
SysScanDesc toastscan;
UHeapTuple toasttup;
int2 bucketid;
if (!VARATT_IS_EXTERNAL_ONDISK_B(attr))
return;
VARATT_EXTERNAL_GET_POINTER_B(toastPointer, attr, bucketid);
* Open the toast relation and its index
*/
Assert(bucketid == rel->rd_node.bucketNode);
toastrel = heap_open(toastPointer.va_toastrelid, RowExclusiveLock, bucketid);
toastidx = index_open(toastrel->rd_rel->reltoastidxid, RowExclusiveLock, bucketid);
IndexInfo *indexInfo = BuildIndexInfo(toastidx);
EState *estate = NULL;
if (indexInfo->ii_Expressions != NIL || indexInfo->ii_ExclusionOps != NULL) {
estate = CreateExecutorState();
}
Assert(RelationIsUstoreFormat(toastrel));
TupleTableSlot *slot = MakeSingleTupleTableSlot(RelationGetDescr(toastrel), false, toastrel->rd_tam_ops);
* Setup a scan key to find chunks with matching va_valueid
*/
ScanKeyInit(&toastkey, (AttrNumber)1, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(toastPointer.va_valueid));
* Find all the chunks. (We don't actually care whether we see them in
* sequence or not, but since we've already locked the index we might as
* well use systable_beginscan_ordered.)
*/
toastscan = systable_beginscan_ordered(toastrel, toastidx, SnapshotToast, 1, &toastkey);
while (UHeapSysIndexGetnextSlot(toastscan, ForwardScanDirection, slot)) {
* Have a chunk, delete it
*/
toasttup = ExecGetUHeapTupleFromSlot(slot);
SimpleUHeapDelete(toastrel, &toasttup->ctid, SnapshotToast);
Datum values[INDEX_MAX_KEYS];
bool isnulls[INDEX_MAX_KEYS];
if (estate != NULL) {
ExprContext *econtext = GetPerTupleExprContext(estate);
econtext->ecxt_scantuple = slot;
}
FormIndexDatum(indexInfo, slot, estate, values, isnulls);
index_delete(toastidx, values, isnulls, &toasttup->ctid, false);
}
* End scan and close relations
*/
systable_endscan_ordered(toastscan);
ExecDropSingleTupleTableSlot(slot);
index_close(toastidx, RowExclusiveLock);
heap_close(toastrel, RowExclusiveLock);
if (PointerIsValid(estate)) {
FreeExecutorState(estate);
}
}
* toast_fetch_datum -
*
* Reconstruct an in memory Datum from the chunks saved
* in the toast relation
* ----------
*/
struct varlena *UHeapInternalToastFetchDatum(struct varatt_external toastPointer, Relation toastrel, Relation toastidx)
{
ScanKeyData toastkey;
SysScanDesc toastscan;
UHeapTuple ttup;
TupleDesc toastTupDesc;
struct varlena *result = NULL;
int32 ressize;
int32 residx, nextidx;
int32 numchunks;
Pointer chunk;
bool isnull = false;
char *chunkdata = NULL;
int32 chunksize;
ressize = toastPointer.va_extsize;
numchunks = ((ressize - 1) / UTOAST_MAX_CHUNK_SIZE) + 1;
result = (struct varlena *)palloc(ressize + VARHDRSZ);
if (VARATT_EXTERNAL_IS_COMPRESSED(toastPointer))
SET_VARSIZE_COMPRESSED(result, ressize + VARHDRSZ);
else
SET_VARSIZE(result, ressize + VARHDRSZ);
toastTupDesc = toastrel->rd_att;
TupleTableSlot *slot = MakeSingleTupleTableSlot(RelationGetDescr(toastrel), false, toastrel->rd_tam_ops);
* Setup a scan key to fetch from the index by va_valueid
*/
ScanKeyInit(&toastkey, (AttrNumber)1, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(toastPointer.va_valueid));
* Read the chunks by index
*
* Note that because the index is actually on (valueid, chunkidx) we will
* see the chunks in chunkidx order, even though we didn't explicitly ask
* for it.
*/
nextidx = 0;
toastscan = systable_beginscan_ordered(toastrel, toastidx, get_toast_snapshot(), 1, &toastkey);
while (UHeapSysIndexGetnextSlot(toastscan, ForwardScanDirection, slot)) {
* Have a chunk, extract the sequence number and the data
*/
ttup = ExecGetUHeapTupleFromSlot(slot);
residx = DatumGetInt32(UHeapFastGetAttr(ttup, ATTR_SECOND, toastTupDesc, &isnull));
Assert(!isnull);
chunk = DatumGetPointer(UHeapFastGetAttr(ttup, ATTR_THIRD, toastTupDesc, &isnull));
Assert(!isnull);
if (!VARATT_IS_EXTENDED(chunk)) {
chunksize = VARSIZE(chunk) - VARHDRSZ;
chunkdata = VARDATA(chunk);
} else if (VARATT_IS_SHORT(chunk)) {
chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT;
chunkdata = VARDATA_SHORT(chunk);
} else {
Assert(0);
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_CHUNK_VALUE), errmsg("found toasted toast chunk for toast value %u in %s",
toastPointer.va_valueid, RelationGetRelationName(toastrel))));
chunksize = 0;
chunkdata = NULL;
}
* Some checks on the data we've found
*/
if (residx != nextidx) {
Assert(0);
ereport(ERROR, (errcode(ERRCODE_UNEXPECTED_CHUNK_VALUE),
errmsg("unexpected chunk number %d (expected %d) for toast value %u in %s", residx, nextidx,
toastPointer.va_valueid, RelationGetRelationName(toastrel))));
}
if (residx < numchunks - 1) {
if (chunksize != UTOAST_MAX_CHUNK_SIZE) {
Assert(0);
ereport(ERROR, (errcode(ERRCODE_UNEXPECTED_CHUNK_VALUE),
errmsg("unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s",
chunksize, (int)UTOAST_MAX_CHUNK_SIZE, residx, numchunks, toastPointer.va_valueid,
RelationGetRelationName(toastrel))));
}
} else if (residx == numchunks - 1) {
if ((residx * UTOAST_MAX_CHUNK_SIZE + chunksize) != (uint32)ressize) {
Assert(0);
ereport(ERROR, (errcode(ERRCODE_UNEXPECTED_CHUNK_VALUE),
errmsg("unexpected chunk size %d (expected %d) in final chunk %d for toast value %u in %s",
chunksize, (int)(ressize - residx * UTOAST_MAX_CHUNK_SIZE), residx, toastPointer.va_valueid,
RelationGetRelationName(toastrel))));
}
} else {
Assert(0);
ereport(ERROR, (errcode(ERRCODE_UNEXPECTED_CHUNK_VALUE),
errmsg("unexpected chunk number %d (out of range %d..%d) for toast value %u in %s", residx, 0,
numchunks - 1, toastPointer.va_valueid, RelationGetRelationName(toastrel))));
}
* Copy the data into proper place in our result
*/
if ((ressize < chunksize) && residx == 0) {
pfree(result);
result = (struct varlena*)palloc(chunksize + VARHDRSZ);
ressize = chunksize;
}
errno_t rc = memcpy_s(VARDATA(result) + residx * UTOAST_MAX_CHUNK_SIZE,
ressize + VARHDRSZ - residx * UTOAST_MAX_CHUNK_SIZE, chunkdata, chunksize);
securec_check(rc, "", "");
nextidx++;
}
* Final checks that we successfully fetched the datum
*/
if (nextidx != numchunks) {
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_CHUNK_VALUE), errmsg("missing chunk number %d for toast value %u in %s",
nextidx, toastPointer.va_valueid, RelationGetRelationName(toastrel))));
}
* End scan and close relations
*/
systable_endscan_ordered(toastscan);
ExecDropSingleTupleTableSlot(slot);
return result;
}
struct varlena *UHeapInternalToastFetchDatumSlice(struct varatt_external toastPointer, Relation toastrel,
Relation toastidx, int64 sliceoffset, int32 length)
{
int32 attrsize;
int32 residx;
int32 nextidx;
int numchunks;
int startchunk;
int endchunk;
int32 startoffset;
int32 endoffset;
int totalchunks;
Pointer chunk;
bool isnull = false;
char *chunkdata = NULL;
int32 chunksize;
int32 chcpystrt;
int32 chcpyend;
errno_t rc = EOK;
ScanKeyData toastkey[3];
int nscankeys;
SysScanDesc toastscan;
UHeapTuple ttup;
TupleDesc toastTupDesc;
struct varlena *result = NULL;
* It's nonsense to fetch slices of a compressed datum -- this isn't lo_*
* we can't return a compressed datum which is meaningful to toast later
*/
Assert(!VARATT_EXTERNAL_IS_COMPRESSED(toastPointer));
attrsize = toastPointer.va_extsize;
totalchunks = ((attrsize - 1) / UTOAST_MAX_CHUNK_SIZE) + 1;
if (sliceoffset >= attrsize) {
sliceoffset = 0;
length = 0;
}
if (((sliceoffset + length) > attrsize) || length < 0)
length = attrsize - sliceoffset;
result = (struct varlena *)palloc(length + VARHDRSZ);
if (VARATT_EXTERNAL_IS_COMPRESSED(toastPointer))
SET_VARSIZE_COMPRESSED(result, length + VARHDRSZ);
else
SET_VARSIZE(result, length + VARHDRSZ);
if (length == 0)
return result;
startchunk = sliceoffset / UTOAST_MAX_CHUNK_SIZE;
endchunk = (sliceoffset + length - 1) / UTOAST_MAX_CHUNK_SIZE;
numchunks = (endchunk - startchunk) + 1;
startoffset = sliceoffset % UTOAST_MAX_CHUNK_SIZE;
endoffset = (sliceoffset + length - 1) % UTOAST_MAX_CHUNK_SIZE;
* Open the toast relation and its index
*/
toastTupDesc = toastrel->rd_att;
TupleTableSlot *slot = MakeSingleTupleTableSlot(RelationGetDescr(toastrel), false, toastrel->rd_tam_ops);
* Setup a scan key to fetch from the index. This is either two keys or
* three depending on the number of chunks.
*/
ScanKeyInit(&toastkey[0], (AttrNumber)1, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(toastPointer.va_valueid));
* Use equality condition for one chunk, a range condition otherwise:
*/
if (numchunks == ATTR_FIRST) {
ScanKeyInit(&toastkey[1], (AttrNumber)ATTR_SECOND, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(startchunk));
nscankeys = ATTR_SECOND;
} else {
ScanKeyInit(&toastkey[1], (AttrNumber)ATTR_SECOND, BTGreaterEqualStrategyNumber, F_INT4GE,
Int32GetDatum(startchunk));
ScanKeyInit(&toastkey[ATTR_SECOND], (AttrNumber)ATTR_SECOND, BTLessEqualStrategyNumber, F_INT4LE,
Int32GetDatum(endchunk));
nscankeys = ATTR_THIRD;
}
* Read the chunks by index
* The index is on (valueid, chunkidx) so they will come in order
*/
nextidx = startchunk;
toastscan = systable_beginscan_ordered(toastrel, toastidx, get_toast_snapshot(), nscankeys, toastkey);
while (UHeapSysIndexGetnextSlot(toastscan, ForwardScanDirection, slot)) {
* Have a chunk, extract the sequence number and the data
*/
ttup = ExecGetUHeapTupleFromSlot(slot);
residx = DatumGetInt32(UHeapFastGetAttr(ttup, CHUNK_ID_ATTR, toastTupDesc, &isnull));
Assert(!isnull);
chunk = DatumGetPointer(UHeapFastGetAttr(ttup, CHUNK_DATA_ATTR, toastTupDesc, &isnull));
Assert(!isnull);
if (!VARATT_IS_EXTENDED(chunk)) {
chunksize = VARSIZE(chunk) - VARHDRSZ;
chunkdata = VARDATA(chunk);
} else if (VARATT_IS_SHORT(chunk)) {
chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT;
chunkdata = VARDATA_SHORT(chunk);
} else {
Assert(0);
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_CHUNK_VALUE), errmsg("found toasted toast chunk for toast value %u in %s",
toastPointer.va_valueid, RelationGetRelationName(toastrel))));
chunksize = 0;
chunkdata = NULL;
}
* Some checks on the data we've found
*/
if ((residx != nextidx) || (residx > endchunk) || (residx < startchunk)) {
Assert(0);
ereport(ERROR, (errcode(ERRCODE_UNEXPECTED_CHUNK_VALUE),
errmsg("unexpected chunk number %d (expected %d) for toast value %u in %s", residx, nextidx,
toastPointer.va_valueid, RelationGetRelationName(toastrel))));
}
if (residx < totalchunks - 1) {
if (chunksize != UTOAST_MAX_CHUNK_SIZE) {
Assert(0);
ereport(ERROR, (errcode(ERRCODE_UNEXPECTED_CHUNK_VALUE),
errmsg("unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s when "
"fetching slice",
chunksize, (int)UTOAST_MAX_CHUNK_SIZE, residx, totalchunks, toastPointer.va_valueid,
RelationGetRelationName(toastrel))));
}
} else if (residx == totalchunks - 1) {
if ((residx * UTOAST_MAX_CHUNK_SIZE + chunksize) != (uint32)attrsize) {
Assert(0);
ereport(ERROR, (errcode(ERRCODE_UNEXPECTED_CHUNK_VALUE),
errmsg("unexpected chunk size %d (expected %d) in final chunk %d for toast value %u in %s when "
"fetching slice",
chunksize, (int)(attrsize - residx * UTOAST_MAX_CHUNK_SIZE), residx, toastPointer.va_valueid,
RelationGetRelationName(toastrel))));
}
} else {
Assert(0);
ereport(ERROR, (errcode(ERRCODE_UNEXPECTED_CHUNK_VALUE),
errmsg("unexpected chunk number %d (out of range %d..%d) for toast value %u in %s", residx, 0,
totalchunks - 1, toastPointer.va_valueid, RelationGetRelationName(toastrel))));
}
* Copy the data into proper place in our result
*/
chcpystrt = 0;
chcpyend = chunksize - 1;
if (residx == startchunk)
chcpystrt = startoffset;
if (residx == endchunk)
chcpyend = endoffset;
rc = memcpy_s(VARDATA(result) + (residx * UTOAST_MAX_CHUNK_SIZE - sliceoffset) + chcpystrt,
length + VARHDRSZ - (residx * UTOAST_MAX_CHUNK_SIZE - sliceoffset + chcpystrt), chunkdata + chcpystrt,
(chcpyend - chcpystrt) + 1);
securec_check(rc, "", "");
nextidx++;
}
* Final checks that we successfully fetched the datum
*/
if (nextidx != (endchunk + 1)) {
Assert(0);
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_CHUNK_VALUE), errmsg("missing chunk number %d for toast value %u in %s",
nextidx, toastPointer.va_valueid, RelationGetRelationName(toastrel))));
}
systable_endscan_ordered(toastscan);
ExecDropSingleTupleTableSlot(slot);
return result;
}
* toastrel_valueid_exists -
*
* Test whether a toast value with the given ID exists in the toast relation.
* For safety, we consider a value to exist if there are either live or dead
* toast rows with that ID; see notes for GetNewOid().
* ----------
*/
static bool UHeapToastRelValueidExists(Relation toastrel, Oid valueid)
{
bool result = false;
ScanKeyData toastkey;
SysScanDesc toastscan;
TupleTableSlot *slot = NULL;
Assert(RelationIsUstoreFormat(toastrel));
slot = MakeSingleTupleTableSlot(RelationGetDescr(toastrel), false, toastrel->rd_tam_ops);
* Setup a scan key to find chunks with matching va_valueid
*/
ScanKeyInit(&toastkey, (AttrNumber)1, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(valueid));
* Is there any such chunk?
*/
toastscan = systable_beginscan(toastrel, toastrel->rd_rel->reltoastidxid, true, SnapshotAny, 1, &toastkey);
result = UHeapSysIndexGetnextSlot(toastscan, ForwardScanDirection, slot);
systable_endscan(toastscan);
ExecDropSingleTupleTableSlot(slot);
return result;
}
* toastid_valueid_exists -
*
* As above, but work from toast rel's OID not an open relation
* ----------
*/
static bool UHeapToastIdValueIdExists(Oid toastrelid, Oid valueid, int2 bucketid)
{
bool result = false;
Relation toastrel;
toastrel = heap_open(toastrelid, AccessShareLock, bucketid);
result = UHeapToastRelValueidExists(toastrel, valueid);
heap_close(toastrel, AccessShareLock);
return result;
}