/* ---------- * 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; }