HeapTuple toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup, MemTupleBinding *pbind, int toast_tuple_target, bool isFrozen) { HeapTuple result_tuple; TupleDesc tupleDesc; Form_pg_attribute *att; int numAttrs; int i; bool need_change = false; bool need_free = false; bool need_delold = false; bool has_nulls = false; Size maxDataLen; char toast_action[MaxHeapAttributeNumber]; bool toast_isnull[MaxHeapAttributeNumber]; bool toast_oldisnull[MaxHeapAttributeNumber]; Datum toast_values[MaxHeapAttributeNumber]; Datum toast_oldvalues[MaxHeapAttributeNumber]; int32 toast_sizes[MaxHeapAttributeNumber]; bool toast_free[MaxHeapAttributeNumber]; bool toast_delold[MaxHeapAttributeNumber]; bool ismemtuple = is_heaptuple_memtuple(newtup); AssertImply(ismemtuple, pbind); AssertImply(!ismemtuple, !pbind); AssertImply(ismemtuple && oldtup, is_heaptuple_memtuple(oldtup)); Assert(toast_tuple_target > 0); /* * We should only ever be called for tuples of plain relations --- * recursing on a toast rel is bad news. */ //Assert(rel->rd_rel->relkind == RELKIND_RELATION); if (rel->rd_rel->relkind != RELKIND_RELATION) elog(LOG,"Why are we toasting a non-relation! %c ",rel->rd_rel->relkind); /* * Get the tuple descriptor and break down the tuple(s) into fields. */ tupleDesc = rel->rd_att; att = tupleDesc->attrs; numAttrs = tupleDesc->natts; Assert(numAttrs <= MaxHeapAttributeNumber); if(ismemtuple) memtuple_deform((MemTuple) newtup, pbind, toast_values, toast_isnull); else heap_deform_tuple(newtup, tupleDesc, toast_values, toast_isnull); if (oldtup != NULL) { if(ismemtuple) memtuple_deform((MemTuple) oldtup, pbind, toast_oldvalues, toast_oldisnull); else heap_deform_tuple(oldtup, tupleDesc, toast_oldvalues, toast_oldisnull); } /* ---------- * Then collect information about the values given * * NOTE: toast_action[i] can have these values: * ' ' default handling * 'p' already processed --- don't touch it * 'x' incompressible, but OK to move off * * NOTE: toast_sizes[i] is only made valid for varlena attributes with * toast_action[i] different from 'p'. * ---------- */ memset(toast_action, ' ', numAttrs * sizeof(char)); memset(toast_free, 0, numAttrs * sizeof(bool)); memset(toast_delold, 0, numAttrs * sizeof(bool)); for (i = 0; i < numAttrs; i++) { varattrib *old_value; varattrib *new_value; if (oldtup != NULL) { /* * For UPDATE get the old and new values of this attribute */ old_value = (varattrib *) DatumGetPointer(toast_oldvalues[i]); new_value = (varattrib *) DatumGetPointer(toast_values[i]); /* * If the old value is an external stored one, check if it has * changed so we have to delete it later. */ if (att[i]->attlen == -1 && !toast_oldisnull[i] && VARATT_IS_EXTERNAL(old_value)) { if (toast_isnull[i] || !VARATT_IS_EXTERNAL(new_value) || memcmp((char *) old_value, (char *) new_value, VARSIZE_EXTERNAL(old_value)) != 0) { /* * The old external stored value isn't needed any more * after the update */ toast_delold[i] = true; need_delold = true; } else { /* * This attribute isn't changed by this update so we reuse * the original reference to the old value in the new * tuple. */ toast_action[i] = 'p'; continue; } } } else { /* * For INSERT simply get the new value */ new_value = (varattrib *) DatumGetPointer(toast_values[i]); } /* * Handle NULL attributes */ if (toast_isnull[i]) { toast_action[i] = 'p'; has_nulls = true; continue; } /* * Now look at varlena attributes */ if (att[i]->attlen == -1) { /* * If the table's attribute says PLAIN always, force it so. */ if (att[i]->attstorage == 'p') toast_action[i] = 'p'; /* * We took care of UPDATE above, so any external value we find * still in the tuple must be someone else's we cannot reuse. * 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(new_value)) { if (att[i]->attstorage == 'p') new_value = (varattrib *)heap_tuple_untoast_attr((struct varlena *)new_value); else new_value = (varattrib *)heap_tuple_fetch_attr((struct varlena *)new_value); toast_values[i] = PointerGetDatum(new_value); toast_free[i] = true; need_change = true; need_free = true; } /* * Remember the size of this attribute */ toast_sizes[i] = VARSIZE_ANY(new_value); } else { /* * Not a varlena attribute, plain storage always */ toast_action[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 * ---------- */ if(!ismemtuple) { /* compute header overhead --- this should match heap_form_tuple() */ maxDataLen = offsetof(HeapTupleHeaderData, t_bits); if (has_nulls) maxDataLen += BITMAPLEN(numAttrs); if (newtup->t_data->t_infomask & HEAP_HASOID) maxDataLen += sizeof(Oid); maxDataLen = MAXALIGN(maxDataLen); Assert(maxDataLen == newtup->t_data->t_hoff); /* now convert to a limit on the tuple data size */ maxDataLen = toast_tuple_target - maxDataLen; } else maxDataLen = toast_tuple_target; /* * Look for attributes with attstorage 'x' to compress. Also find large * attributes with attstorage 'x' or 'e', and store them external. */ while (compute_dest_tuplen(tupleDesc, pbind, has_nulls, toast_values, toast_isnull) > maxDataLen) { int biggest_attno = -1; int32 biggest_size = MAXALIGN(sizeof(varattrib)); Datum old_value; Datum new_value; /* * Search for the biggest yet unprocessed internal attribute */ for (i = 0; i < numAttrs; i++) { if (toast_action[i] != ' ') continue; if (VARATT_IS_EXTERNAL_D(toast_values[i])) continue; if (VARATT_IS_COMPRESSED_D(toast_values[i])) continue; if (att[i]->attstorage != 'x') continue; if (toast_sizes[i] > biggest_size) { biggest_attno = i; biggest_size = toast_sizes[i]; } } if (biggest_attno < 0) break; /* * Attempt to compress it inline, if it has attstorage 'x' */ i = biggest_attno; old_value = toast_values[i]; new_value = toast_compress_datum(old_value); if (DatumGetPointer(new_value) != NULL) { /* successful compression */ if (toast_free[i]) pfree(DatumGetPointer(old_value)); toast_values[i] = new_value; toast_free[i] = true; toast_sizes[i] = VARSIZE_D(toast_values[i]); need_change = true; need_free = true; } else { /* * incompressible data, ignore on subsequent compression passes */ toast_action[i] = 'x'; } } /* * Second we look for attributes of attstorage 'x' or 'e' that are still * inline. */ while (compute_dest_tuplen(tupleDesc, pbind, has_nulls, toast_values, toast_isnull) > maxDataLen && rel->rd_rel->reltoastrelid != InvalidOid) { int biggest_attno = -1; int32 biggest_size = MAXALIGN(sizeof(varattrib)); Datum old_value; /*------ * Search for the biggest yet inlined attribute with * attstorage equals 'x' or 'e' *------ */ for (i = 0; i < numAttrs; i++) { if (toast_action[i] == 'p') continue; if (VARATT_IS_EXTERNAL_D(toast_values[i])) continue; if (att[i]->attstorage != 'x' && att[i]->attstorage != 'e') continue; if (toast_sizes[i] > biggest_size) { biggest_attno = i; biggest_size = toast_sizes[i]; } } if (biggest_attno < 0) break; /* * Store this external */ i = biggest_attno; old_value = toast_values[i]; toast_action[i] = 'p'; toast_values[i] = toast_save_datum(rel, toast_values[i], isFrozen); if (toast_free[i]) pfree(DatumGetPointer(old_value)); toast_free[i] = true; need_change = true; need_free = true; } /* * Round 3 - this time we take attributes with storage 'm' into * compression */ while (compute_dest_tuplen(tupleDesc, pbind, has_nulls, toast_values, toast_isnull) > maxDataLen) { int biggest_attno = -1; int32 biggest_size = MAXALIGN(sizeof(varattrib)); Datum old_value; Datum new_value; /* * Search for the biggest yet uncompressed internal attribute */ for (i = 0; i < numAttrs; i++) { if (toast_action[i] != ' ') continue; if (VARATT_IS_EXTERNAL_D(toast_values[i])) continue; /* can't happen, toast_action would be 'p' */ if (VARATT_IS_COMPRESSED_D(toast_values[i])) continue; if (att[i]->attstorage != 'm') continue; if (toast_sizes[i] > biggest_size) { biggest_attno = i; biggest_size = toast_sizes[i]; } } if (biggest_attno < 0) break; /* * Attempt to compress it inline */ i = biggest_attno; old_value = toast_values[i]; new_value = toast_compress_datum(old_value); if (DatumGetPointer(new_value) != NULL) { /* successful compression */ if (toast_free[i]) pfree(DatumGetPointer(old_value)); toast_values[i] = new_value; toast_free[i] = true; toast_sizes[i] = VARSIZE_D(toast_values[i]); need_change = true; need_free = true; } else { /* incompressible, ignore on subsequent compression passes */ toast_action[i] = 'x'; } } /* * Finally we store attributes of type 'm' external, if possible. */ while (compute_dest_tuplen(tupleDesc, pbind, has_nulls, toast_values, toast_isnull) > maxDataLen && rel->rd_rel->reltoastrelid != InvalidOid) { int biggest_attno = -1; int32 biggest_size = MAXALIGN(sizeof(varattrib)); Datum old_value; /*-------- * Search for the biggest yet inlined attribute with * attstorage = 'm' *-------- */ for (i = 0; i < numAttrs; i++) { if (toast_action[i] == 'p') continue; if (VARATT_IS_EXTERNAL_D(toast_values[i])) continue; /* can't happen, toast_action would be 'p' */ if (att[i]->attstorage != 'm') continue; if (toast_sizes[i] > biggest_size) { biggest_attno = i; biggest_size = toast_sizes[i]; } } if (biggest_attno < 0) break; /* * Store this external */ i = biggest_attno; old_value = toast_values[i]; toast_action[i] = 'p'; toast_values[i] = toast_save_datum(rel, toast_values[i], isFrozen); if (toast_free[i]) pfree(DatumGetPointer(old_value)); toast_free[i] = true; need_change = true; need_free = true; } /* XXX Maybe we should check here for any compressed inline attributes that * didn't save enough to warrant keeping. In particular attributes whose * rawsize is < 128 bytes and didn't save at least 3 bytes... or even maybe * more given alignment issues */ /* * In the case we toasted any values, we need to build a new heap tuple * with the changed values. */ if (need_change) { if(ismemtuple) result_tuple = (HeapTuple) memtuple_form_to(pbind, toast_values, toast_isnull, NULL, NULL, false); else { HeapTupleHeader olddata = newtup->t_data; HeapTupleHeader new_data; int32 new_len; /* * Calculate the new size of the tuple. Header size should not * change, but data size might. */ new_len = offsetof(HeapTupleHeaderData, t_bits); if (has_nulls) new_len += BITMAPLEN(numAttrs); if (olddata->t_infomask & HEAP_HASOID) new_len += sizeof(Oid); new_len = MAXALIGN(new_len); Assert(new_len == olddata->t_hoff); new_len += heap_compute_data_size(tupleDesc, toast_values, toast_isnull); /* * Allocate and zero the space needed, and fill HeapTupleData fields. */ result_tuple = (HeapTuple) palloc0(HEAPTUPLESIZE + new_len); result_tuple->t_len = new_len; result_tuple->t_self = newtup->t_self; new_data = (HeapTupleHeader) ((char *) result_tuple + HEAPTUPLESIZE); result_tuple->t_data = new_data; /* * Put the existing tuple header and the changed values into place */ memcpy(new_data, olddata, olddata->t_hoff); heap_fill_tuple(tupleDesc, toast_values, toast_isnull, (char *) new_data + olddata->t_hoff, &(new_data->t_infomask), has_nulls ? new_data->t_bits : NULL); } } else result_tuple = newtup; /* * Free allocated temp values */ if (need_free) for (i = 0; i < numAttrs; i++) if (toast_free[i]) pfree(DatumGetPointer(toast_values[i])); /* * Delete external values from the old tuple */ if (need_delold) for (i = 0; i < numAttrs; i++) if (toast_delold[i]) toast_delete_datum(rel, toast_oldvalues[i]); return result_tuple; }
/* ---------------- * index_form_tuple * ---------------- */ IndexTuple index_form_tuple(TupleDesc tupleDescriptor, Datum *values, bool *isnull) { char *tp; /* tuple pointer */ IndexTuple tuple; /* return tuple */ Size size, hoff; int i; unsigned short infomask = 0; bool hasnull = false; uint16 tupmask = 0; int numberOfAttributes = tupleDescriptor->natts; #ifdef TOAST_INDEX_HACK Datum untoasted_values[INDEX_MAX_KEYS]; bool untoasted_free[INDEX_MAX_KEYS]; #endif if (numberOfAttributes > INDEX_MAX_KEYS) ereport(ERROR, (errcode(ERRCODE_TOO_MANY_COLUMNS), errmsg("number of index columns (%d) exceeds limit (%d)", numberOfAttributes, INDEX_MAX_KEYS))); #ifdef TOAST_INDEX_HACK for (i = 0; i < numberOfAttributes; i++) { Form_pg_attribute att = tupleDescriptor->attrs[i]; untoasted_values[i] = values[i]; untoasted_free[i] = false; /* Do nothing if value is NULL or not of varlena type */ if (isnull[i] || att->attlen != -1) continue; /* * If value is stored EXTERNAL, must fetch it so we are not depending * on outside storage. This should be improved someday. */ if (VARATT_IS_EXTERNAL_D(values[i])) { untoasted_values[i] = PointerGetDatum(heap_tuple_fetch_attr(DatumGetPointer(values[i]))); untoasted_free[i] = true; } /* * If value is above size target, and is of a compressible datatype, * try to compress it in-line. */ if (!VARATT_IS_SHORT_D(untoasted_values[i]) && VARSIZE_D(untoasted_values[i]) > TOAST_INDEX_TARGET && !VARATT_IS_COMPRESSED_D(untoasted_values[i]) && (att->attstorage == 'x' || att->attstorage == 'm')) { Datum cvalue = toast_compress_datum(untoasted_values[i]); if (DatumGetPointer(cvalue) != NULL) { /* successful compression */ if (untoasted_free[i]) pfree(DatumGetPointer(untoasted_values[i])); untoasted_values[i] = cvalue; untoasted_free[i] = true; } } } #endif for (i = 0; i < numberOfAttributes; i++) { if (isnull[i]) { hasnull = true; break; } } if (hasnull) infomask |= INDEX_NULL_MASK; hoff = IndexInfoFindDataOffset(infomask); #ifdef TOAST_INDEX_HACK size = hoff + heap_compute_data_size(tupleDescriptor, untoasted_values, isnull); #else size = hoff + heap_compute_data_size(tupleDescriptor, values, isnull); #endif size = MAXALIGN(size); /* be conservative */ tp = (char *) palloc0(size); tuple = (IndexTuple) tp; heap_fill_tuple(tupleDescriptor, #ifdef TOAST_INDEX_HACK untoasted_values, #else values, #endif isnull, (char *) tp + hoff, &tupmask, (hasnull ? (bits8 *) tp + sizeof(IndexTupleData) : NULL)); #ifdef TOAST_INDEX_HACK for (i = 0; i < numberOfAttributes; i++) { if (untoasted_free[i]) pfree(DatumGetPointer(untoasted_values[i])); } #endif /* * We do this because heap_fill_tuple wants to initialize a "tupmask" * which is used for HeapTuples, but we want an indextuple infomask. The * only relevant info is the "has variable attributes" field. We have * already set the hasnull bit above. */ if (tupmask & HEAP_HASVARWIDTH) infomask |= INDEX_VAR_MASK; /* * Here we make sure that the size will fit in the field reserved for it * in t_info. */ if ((size & INDEX_SIZE_MASK) != size) ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("index row requires %lu bytes, maximum size is %lu", (unsigned long) size, (unsigned long) INDEX_SIZE_MASK))); infomask |= size; /* * initialize metadata */ tuple->t_info = infomask; return tuple; }
/* ---------- * toast_save_datum - * * Save one single datum into the secondary relation and return * a Datum reference for it. * ---------- */ static Datum toast_save_datum(Relation rel, Datum value, bool isFrozen) { Relation toastrel; Relation toastidx; HeapTuple toasttup; TupleDesc toasttupDesc; Datum t_values[3]; bool t_isnull[3]; varattrib *result; struct { struct varlena hdr; char data[TOAST_MAX_CHUNK_SIZE]; /* make struct big enough */ int32 align_it; /* ensure struct is aligned well enough */ } chunk_data; int32 chunk_size; int32 chunk_seq = 0; char *data_p; int32 data_todo; int32 rawsize, extsize; /* * 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. */ toastrel = heap_open(rel->rd_rel->reltoastrelid, RowExclusiveLock); toasttupDesc = toastrel->rd_att; toastidx = index_open(toastrel->rd_rel->reltoastidxid, RowExclusiveLock); /* * Create the varattrib reference */ result = (varattrib *) palloc(sizeof(varattrib)); /* rawsize is the size of the datum that will result after decompression -- * including the full header. so we have to adjust for short headers. * * extsize is the actual size of the data payload in the toast records * without any headers */ if (VARATT_IS_SHORT_D(value)) { rawsize = VARSIZE_SHORT_D(value) - VARHDRSZ_SHORT + VARHDRSZ; extsize = VARSIZE_SHORT_D(value) - VARHDRSZ_SHORT; data_p = VARDATA_SHORT_D(value); data_todo = VARSIZE_SHORT_D(value) - VARHDRSZ_SHORT; } else if (VARATT_IS_COMPRESSED_D(value)) { /* rawsize in a compressed datum is the just the size of the payload */ rawsize = ((varattrib *) DatumGetPointer(value))->va_compressed.va_rawsize + VARHDRSZ; extsize = VARSIZE_D(value) - VARHDRSZ; data_p = VARDATA_D(value); data_todo = VARSIZE_D(value) - VARHDRSZ; /* we used to set result->va_header |= VARATT_FLAG_COMPRESSED; down * below. we don't any longer and depend on the equality holding: * extsize = rawsize + VARHDRSZ*/ } else { rawsize = VARSIZE_D(value); extsize = VARSIZE_D(value) - VARHDRSZ; data_p = VARDATA_D(value); data_todo = VARSIZE_D(value) - VARHDRSZ; } SET_VARSIZE_EXTERNAL(result, TOAST_POINTER_SIZE); result->va_external.va_rawsize = rawsize; result->va_external.va_extsize = extsize; result->va_external.va_valueid = GetNewOidWithIndex(toastrel, toastidx); result->va_external.va_toastrelid = rel->rd_rel->reltoastrelid; #ifdef USE_ASSERT_CHECKING Assert( (VARATT_IS_COMPRESSED_D(value)||0) == (VARATT_EXTERNAL_IS_COMPRESSED(result)||0) ); if (VARATT_IS_COMPRESSED_D(value)) { Assert(VARATT_EXTERNAL_IS_COMPRESSED(result)); elog(DEBUG4, "saved toast datum, original varsize %ud rawsize %ud new extsize %ud rawsize %uld\n", VARSIZE_D(value), ((varattrib *) DatumGetPointer(value))->va_compressed.va_rawsize, result->va_external.va_extsize, result->va_external.va_rawsize); } else { Assert(!VARATT_EXTERNAL_IS_COMPRESSED(result)); elog(DEBUG4, "saved toast datum, original varsize %ud new extsize %ud rawsize %ud\n", VARSIZE_D(value), result->va_external.va_extsize, result->va_external.va_rawsize); } #endif /* * Initialize constant parts of the tuple data */ t_values[0] = ObjectIdGetDatum(result->va_external.va_valueid); t_values[2] = PointerGetDatum(&chunk_data); t_isnull[0] = false; t_isnull[1] = false; t_isnull[2] = false; /* * Split up the item into chunks */ while (data_todo > 0) { /* * Calculate the size of this chunk */ chunk_size = Min(TOAST_MAX_CHUNK_SIZE, data_todo); /* * Build a tuple and store it */ t_values[1] = Int32GetDatum(chunk_seq++); SET_VARSIZE(&chunk_data, chunk_size + VARHDRSZ); memcpy(VARDATA(&chunk_data), data_p, chunk_size); toasttup = heap_form_tuple(toasttupDesc, t_values, t_isnull); if (!HeapTupleIsValid(toasttup)) elog(ERROR, "failed to build TOAST tuple"); if(!isFrozen) { /* the normal case. regular insert */ simple_heap_insert(toastrel, toasttup); } else { /* insert and freeze the tuple. used for errtables and their related toast data */ frozen_heap_insert(toastrel, toasttup); } //heap_insert(relation, tup, GetCurrentCommandId(), // true, true, GetCurrentTransactionId()); /* * 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. */ index_insert(toastidx, t_values, t_isnull, &(toasttup->t_self), toastrel, toastidx->rd_index->indisunique); /* * Free memory */ heap_freetuple(toasttup); /* * Move on to next chunk */ data_todo -= chunk_size; data_p += chunk_size; } /* * Done - close toast relation */ index_close(toastidx, RowExclusiveLock); heap_close(toastrel, RowExclusiveLock); return PointerGetDatum(result); }
/* * heap_fill_tuple * Load data portion of a tuple from values/isnull arrays * * We also fill the null bitmap (if any) and set the infomask bits * that reflect the tuple's data contents. * * NOTE: it is now REQUIRED that the caller have pre-zeroed the data area. * * * @param isnull will only be used if <code>bit</code> is non-NULL * @param bit should be non-NULL (refer to td->t_bits) if isnull is set and contains non-null values */ Size heap_fill_tuple(TupleDesc tupleDesc, Datum *values, bool *isnull, char *data, uint16 *infomask, bits8 *bit) { char *start = data; bits8 *bitP; int bitmask; int i; int numberOfAttributes = tupleDesc->natts; Form_pg_attribute *att = tupleDesc->attrs; if (bit != NULL) { bitP = &bit[-1]; bitmask = HIGHBIT; } else { /* just to keep compiler quiet */ bitP = NULL; bitmask = 0; } *infomask &= ~(HEAP_HASNULL | HEAP_HASVARWIDTH | HEAP_HASEXTENDED); for (i = 0; i < numberOfAttributes; i++) { Size data_length; if (bit != NULL) { if (bitmask != HIGHBIT) bitmask <<= 1; else { bitP += 1; *bitP = 0x0; bitmask = 1; } if (isnull[i]) { *infomask |= HEAP_HASNULL; continue; } *bitP |= bitmask; } /* * XXX we use the att_align macros on the pointer value itself, not on * an offset. This is a bit of a hack. */ if (att[i]->attbyval) { /* pass-by-value */ data = (char *) att_align_zero(data, att[i]->attalign); store_att_byval(data, values[i], att[i]->attlen); data_length = att[i]->attlen; } else if (att[i]->attlen == -1) { /* varlena */ *infomask |= HEAP_HASVARWIDTH; if (VARATT_IS_COMPRESSED_D(values[i])) *infomask |= HEAP_HASCOMPRESSED; if (VARATT_IS_EXTERNAL_D(values[i])) { *infomask |= HEAP_HASEXTERNAL; data = (char *) att_align_zero(data, att[i]->attalign); data_length = VARSIZE_EXTERNAL(DatumGetPointer(values[i])); memcpy(data, DatumGetPointer(values[i]), data_length); } else if (VARATT_IS_SHORT_D(values[i])) { /* no alignment for short varlenas */ data_length = VARSIZE_SHORT(DatumGetPointer(values[i])); memcpy(data, DatumGetPointer(values[i]), data_length); } else if (VARATT_COULD_SHORT_D(values[i]) && att[i]->atttypid != INT2VECTOROID && att[i]->atttypid != OIDVECTOROID && att[i]->atttypid < FirstNormalObjectId) { /* convert to short varlena -- no alignment */ data_length = VARSIZE_D(values[i]) - VARHDRSZ + VARHDRSZ_SHORT; *data = VARSIZE_TO_SHORT_D(values[i]); memcpy(data+1, VARDATA_D(values[i]), data_length-1); } else { /* must store full 4-byte header varlena */ data = (char *) att_align_zero(data, att[i]->attalign); data_length = VARSIZE(DatumGetPointer(values[i])); memcpy(data, DatumGetPointer(values[i]), data_length); } } else if (att[i]->attlen == -2) { /* cstring */ data = (char *) att_align_zero(data, att[i]->attalign); *infomask |= HEAP_HASVARWIDTH; data_length = strlen(DatumGetCString(values[i])) + 1; memcpy(data, DatumGetPointer(values[i]), data_length); } else { /* fixed-length pass-by-reference */ data = (char *) att_align_zero(data, att[i]->attalign); Assert(att[i]->attlen > 0); data_length = att[i]->attlen; memcpy(data, DatumGetPointer(values[i]), data_length); } data += data_length; } return data - start; }