diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c index 86dc9cf51a9..69a22559661 100644 --- a/contrib/amcheck/verify_heapam.c +++ b/contrib/amcheck/verify_heapam.c @@ -1681,6 +1681,7 @@ check_tuple_attribute(HeapCheckContext *ctx) uint32 va_extinfo; CompactAttribute *thisatt; vartag_external va_tag_value; + toast_external_data toast_ext_data; infomask = ctx->tuphdr->t_infomask; thisatt = TupleDescCompactAttr(RelationGetDescr(ctx->rel), ctx->attnum); @@ -1783,31 +1784,12 @@ check_tuple_attribute(HeapCheckContext *ctx) /* It is external, and we're looking at a page on disk */ - /* - * Must copy attr into toast_pointer for alignment considerations. - * Branch on the tag to determine which pointer type to extract. - */ - va_tag_value = VARTAG_EXTERNAL(attr); - if (va_tag_value == VARTAG_ONDISK_OID8) - { - varatt_external_oid8 toast_pointer8; - - /* Must copy to access aligned fields */ - VARATT_EXTERNAL_GET_POINTER(toast_pointer8, attr); - toast_pointer_valueid = VARATT_EXTERNAL_OID8_GET_VALUEID(toast_pointer8); - va_rawsize = toast_pointer8.va_rawsize; - va_extinfo = toast_pointer8.va_extinfo; - } - else - { - varatt_external_oid toast_pointer; - - /* Must copy to access aligned fields */ - VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); - toast_pointer_valueid = toast_pointer.va_valueid; - va_rawsize = toast_pointer.va_rawsize; - va_extinfo = toast_pointer.va_extinfo; - } + /* Must copy attr into a decoded pointer for alignment considerations */ + toast_external_info_get(attr, &toast_ext_data); + va_tag_value = toast_ext_data.tag; + toast_pointer_valueid = toast_ext_data.valueid; + va_rawsize = toast_ext_data.rawsize; + va_extinfo = toast_ext_data.extinfo; /* Toasted attributes too large to be untoasted should never be stored */ if (va_rawsize > VARLENA_SIZE_LIMIT) @@ -1817,13 +1799,13 @@ check_tuple_attribute(HeapCheckContext *ctx) va_rawsize, VARLENA_SIZE_LIMIT)); - if ((va_extinfo & VARLENA_EXTSIZE_MASK) < (Size) (va_rawsize - VARHDRSZ)) + if (VARATT_EXTINFO_IS_COMPRESSED(toast_ext_data.extinfo, toast_ext_data.rawsize)) { ToastCompressionId cmid; bool valid = false; /* Compressed attributes should have a valid compression method */ - cmid = va_extinfo >> VARLENA_EXTSIZE_BITS; + cmid = VARATT_EXTINFO_GET_COMPRESS_METHOD(toast_ext_data.extinfo); switch (cmid) { /* List of all valid compression method IDs */ @@ -1908,31 +1890,16 @@ check_toasted_attribute(HeapCheckContext *ctx, ToastedAttribute *ta) int32 last_chunk_seq; int32 max_chunk_size; Oid8 toast_valueid; + Oid toast_typid; toast_valueid = ta->va_valueid; - extsize = ta->va_extinfo & VARLENA_EXTSIZE_MASK; + extsize = VARATT_EXTINFO_GET_EXTSIZE(ta->va_extinfo); + toast_typid = (ta->tag == VARTAG_ONDISK_OID8) ? OID8OID : OIDOID; - /* - * Setup a scan key to find chunks in toast table with matching va_valueid - */ - if (ta->tag == VARTAG_ONDISK_OID8) - { - max_chunk_size = TOAST_OID8_MAX_CHUNK_SIZE; - - ScanKeyInit(&toastkey, - (AttrNumber) 1, - BTEqualStrategyNumber, F_OID8EQ, - ObjectId8GetDatum(toast_valueid)); - } - else - { - max_chunk_size = TOAST_OID_MAX_CHUNK_SIZE; - - ScanKeyInit(&toastkey, - (AttrNumber) 1, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum((Oid) toast_valueid)); - } + /* Set up a scan key to find chunks in the toast table by value ID */ + max_chunk_size = TOAST_MAX_CHUNK_SIZE(toast_typid); + toast_valueid_scankey_init(&toastkey, (AttrNumber) 1, toast_typid, + toast_valueid); last_chunk_seq = (extsize - 1) / max_chunk_size; diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c index 393135f1b9f..7e5863262b3 100644 --- a/src/backend/access/common/detoast.c +++ b/src/backend/access/common/detoast.c @@ -225,29 +225,15 @@ detoast_attr_slice(varlena *attr, if (VARATT_IS_EXTERNAL_ONDISK(attr)) { + toast_external_data toast_ext_data; int32 extsize; uint32 compress_method; bool is_compressed; - /* Extract extsize and compression info from the appropriate pointer */ - if (VARTAG_EXTERNAL(attr) == VARTAG_ONDISK_OID8) - { - varatt_external_oid8 toast_pointer; - - VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); - extsize = VARATT_EXTERNAL_OID8_GET_EXTSIZE(toast_pointer); - compress_method = VARATT_EXTERNAL_OID8_GET_COMPRESS_METHOD(toast_pointer); - is_compressed = VARATT_EXTERNAL_OID8_IS_COMPRESSED(toast_pointer); - } - else - { - varatt_external_oid toast_pointer; - - VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); - extsize = VARATT_EXTERNAL_OID_GET_EXTSIZE(toast_pointer); - compress_method = VARATT_EXTERNAL_OID_GET_COMPRESS_METHOD(toast_pointer); - is_compressed = VARATT_EXTERNAL_OID_IS_COMPRESSED(toast_pointer); - } + toast_external_info_get(attr, &toast_ext_data); + extsize = VARATT_EXTINFO_GET_EXTSIZE(toast_ext_data.extinfo); + compress_method = VARATT_EXTINFO_GET_COMPRESS_METHOD(toast_ext_data.extinfo); + is_compressed = VARATT_EXTINFO_IS_COMPRESSED(toast_ext_data.extinfo, toast_ext_data.rawsize); /* fast path for non-compressed external datums */ if (!is_compressed) @@ -359,6 +345,7 @@ toast_fetch_datum(varlena *attr) { Relation toastrel; varlena *result; + toast_external_data toast_ext_data; int32 attrsize; Oid toastrelid; Oid8 valueid; @@ -366,40 +353,17 @@ toast_fetch_datum(varlena *attr) if (!VARATT_IS_EXTERNAL_ONDISK(attr)) elog(ERROR, "toast_fetch_datum shouldn't be called for non-ondisk datums"); - if (VARTAG_EXTERNAL(attr) == VARTAG_ONDISK_OID8) - { - varatt_external_oid8 toast_pointer; - - /* Must copy to access aligned fields */ - VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); - attrsize = VARATT_EXTERNAL_OID8_GET_EXTSIZE(toast_pointer); - toastrelid = toast_pointer.va_toastrelid; - valueid = VARATT_EXTERNAL_OID8_GET_VALUEID(toast_pointer); + toast_external_info_get(attr, &toast_ext_data); + attrsize = VARATT_EXTINFO_GET_EXTSIZE(toast_ext_data.extinfo); + toastrelid = toast_ext_data.toastrelid; + valueid = toast_ext_data.valueid; - result = (varlena *) palloc(attrsize + VARHDRSZ); + result = (varlena *) palloc(attrsize + VARHDRSZ); - if (VARATT_EXTERNAL_OID8_IS_COMPRESSED(toast_pointer)) - SET_VARSIZE_COMPRESSED(result, attrsize + VARHDRSZ); - else - SET_VARSIZE(result, attrsize + VARHDRSZ); - } + if (VARATT_EXTINFO_IS_COMPRESSED(toast_ext_data.extinfo, toast_ext_data.rawsize)) + SET_VARSIZE_COMPRESSED(result, attrsize + VARHDRSZ); else - { - varatt_external_oid toast_pointer; - - /* Must copy to access aligned fields */ - VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); - attrsize = VARATT_EXTERNAL_OID_GET_EXTSIZE(toast_pointer); - toastrelid = toast_pointer.va_toastrelid; - valueid = toast_pointer.va_valueid; - - result = (varlena *) palloc(attrsize + VARHDRSZ); - - if (VARATT_EXTERNAL_OID_IS_COMPRESSED(toast_pointer)) - SET_VARSIZE_COMPRESSED(result, attrsize + VARHDRSZ); - else - SET_VARSIZE(result, attrsize + VARHDRSZ); - } + SET_VARSIZE(result, attrsize + VARHDRSZ); if (attrsize == 0) return result; /* Probably shouldn't happen, but just in @@ -437,6 +401,7 @@ toast_fetch_datum_slice(varlena *attr, int32 sliceoffset, { Relation toastrel; varlena *result; + toast_external_data toast_ext_data; int32 attrsize; Oid toastrelid; Oid8 valueid; @@ -445,28 +410,11 @@ toast_fetch_datum_slice(varlena *attr, int32 sliceoffset, if (!VARATT_IS_EXTERNAL_ONDISK(attr)) elog(ERROR, "toast_fetch_datum_slice shouldn't be called for non-ondisk datums"); - if (VARTAG_EXTERNAL(attr) == VARTAG_ONDISK_OID8) - { - varatt_external_oid8 toast_pointer; - - /* Must copy to access aligned fields */ - VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); - attrsize = VARATT_EXTERNAL_OID8_GET_EXTSIZE(toast_pointer); - toastrelid = toast_pointer.va_toastrelid; - valueid = VARATT_EXTERNAL_OID8_GET_VALUEID(toast_pointer); - is_compressed = VARATT_EXTERNAL_OID8_IS_COMPRESSED(toast_pointer); - } - else - { - varatt_external_oid toast_pointer; - - /* Must copy to access aligned fields */ - VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); - attrsize = VARATT_EXTERNAL_OID_GET_EXTSIZE(toast_pointer); - toastrelid = toast_pointer.va_toastrelid; - valueid = toast_pointer.va_valueid; - is_compressed = VARATT_EXTERNAL_OID_IS_COMPRESSED(toast_pointer); - } + toast_external_info_get(attr, &toast_ext_data); + attrsize = VARATT_EXTINFO_GET_EXTSIZE(toast_ext_data.extinfo); + toastrelid = toast_ext_data.toastrelid; + valueid = toast_ext_data.valueid; + is_compressed = VARATT_EXTINFO_IS_COMPRESSED(toast_ext_data.extinfo, toast_ext_data.rawsize); /* * It's nonsense to fetch slices of a compressed datum unless when it's a @@ -609,20 +557,10 @@ toast_raw_datum_size(Datum value) if (VARATT_IS_EXTERNAL_ONDISK(attr)) { /* va_rawsize is the size of the original datum -- including header */ - if (VARTAG_EXTERNAL(attr) == VARTAG_ONDISK_OID8) - { - varatt_external_oid8 toast_pointer; - - VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); - result = toast_pointer.va_rawsize; - } - else - { - varatt_external_oid toast_pointer; + toast_external_data toast_ext_data; - VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); - result = toast_pointer.va_rawsize; - } + toast_external_info_get(attr, &toast_ext_data); + result = toast_ext_data.rawsize; } else if (VARATT_IS_EXTERNAL_INDIRECT(attr)) { @@ -679,20 +617,10 @@ toast_datum_size(Datum value) * compressed or not. We do not count the size of the toast pointer * ... should we? */ - if (VARTAG_EXTERNAL(attr) == VARTAG_ONDISK_OID8) - { - varatt_external_oid8 toast_pointer; + toast_external_data toast_ext_data; - VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); - result = VARATT_EXTERNAL_OID8_GET_EXTSIZE(toast_pointer); - } - else - { - varatt_external_oid toast_pointer; - - VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); - result = VARATT_EXTERNAL_OID_GET_EXTSIZE(toast_pointer); - } + toast_external_info_get(attr, &toast_ext_data); + result = VARATT_EXTINFO_GET_EXTSIZE(toast_ext_data.extinfo); } else if (VARATT_IS_EXTERNAL_INDIRECT(attr)) { diff --git a/src/backend/access/common/toast_compression.c b/src/backend/access/common/toast_compression.c index 200cee6be70..09cdf880e4f 100644 --- a/src/backend/access/common/toast_compression.c +++ b/src/backend/access/common/toast_compression.c @@ -260,23 +260,14 @@ toast_get_compression_id(varlena *attr) * the external toast pointer. If compressed inline, fetch it from the * toast compression header. */ - if (VARATT_IS_EXTERNAL_ONDISK_OID8(attr)) + if (VARATT_IS_EXTERNAL_ONDISK(attr)) { - varatt_external_oid8 toast_pointer; + toast_external_data toast_ext_data; - VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); + toast_external_info_get(attr, &toast_ext_data); - if (VARATT_EXTERNAL_OID8_IS_COMPRESSED(toast_pointer)) - cmid = VARATT_EXTERNAL_OID8_GET_COMPRESS_METHOD(toast_pointer); - } - else if (VARATT_IS_EXTERNAL_ONDISK_OID(attr)) - { - varatt_external_oid toast_pointer; - - VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); - - if (VARATT_EXTERNAL_OID_IS_COMPRESSED(toast_pointer)) - cmid = VARATT_EXTERNAL_OID_GET_COMPRESS_METHOD(toast_pointer); + if (VARATT_EXTINFO_IS_COMPRESSED(toast_ext_data.extinfo, toast_ext_data.rawsize)) + cmid = VARATT_EXTINFO_GET_COMPRESS_METHOD(toast_ext_data.extinfo); } else if (VARATT_IS_COMPRESSED(attr)) cmid = VARDATA_COMPRESSED_GET_COMPRESS_METHOD(attr); diff --git a/src/backend/access/common/toast_internals.c b/src/backend/access/common/toast_internals.c index 84c4361af23..38a50883935 100644 --- a/src/backend/access/common/toast_internals.c +++ b/src/backend/access/common/toast_internals.c @@ -104,6 +104,55 @@ toast_compress_datum(Datum value, char cmethod) } } +/* ---------- + * toast_preserve_valueid - + * + * During a table rewrite that preserves rd_toastoid, we want to preserve + * toast value IDs too. If the datum previously had an external value from + * that same toast table, return its value ID so the caller can re-use it; + * otherwise return InvalidOid8. This works for both Oid and Oid8 value IDs, + * as the value ID is decoded independently of the pointer's vartag. + * + * 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, preserving the value ID means we select one that's already + * in the new toast table. We detect that and set *data_todo to 0 so the + * caller falls 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. + * ---------- + */ +static Oid8 +toast_preserve_valueid(Relation toastrel, Oid toastoid, + varlena *oldexternal, int32 *data_todo) +{ + toast_external_data old; + + if (!OidIsValid(toastoid) || oldexternal == NULL) + return InvalidOid8; + + Assert(VARATT_IS_EXTERNAL_ONDISK(oldexternal)); + toast_external_info_get(oldexternal, &old); + + /* + * Only re-use the value ID if the old pointer came from this same toast + * table. Since that is the very same relation, its chunk_id type + * necessarily matches, so no separate vartag check is needed. + */ + if (old.toastrelid != toastoid) + return InvalidOid8; + + if (toastrel_valueid_exists(toastrel, old.valueid)) + *data_todo = 0; + + return old.valueid; +} + /* ---------- * toast_save_datum - * @@ -190,7 +239,7 @@ toast_save_datum(Relation rel, Datum value, va_extinfo = data_todo | (cmid << VARLENA_EXTSIZE_BITS); /* Assert that the numbers look like it's compressed */ - Assert((va_extinfo & VARLENA_EXTSIZE_MASK) < (Size) (va_rawsize - VARHDRSZ)); + Assert(VARATT_EXTINFO_IS_COMPRESSED(va_extinfo, va_rawsize)); } else { @@ -214,162 +263,45 @@ toast_save_datum(Relation rel, Datum value, va_toastrelid = RelationGetRelid(toastrel); /* - * Choose a new value to use as the value ID for this toast value, and - * determine the maximum chunk size based on the TOAST table's chunk_id - * type. - * - * Normally we just choose an unused value within the toast table. But - * during table-rewriting operations where we are preserving an existing - * toast table OID, we want to preserve toast value IDs 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 IDs. For Oid8 tables, - * value conflicts are not a concern. + * Choose the value ID for this toast value. During a table rewrite that + * preserves rd_toastoid we re-use the prior value ID if we can (see + * toast_preserve_valueid); otherwise we pick a fresh one. For Oid value + * IDs the fresh one must not conflict with old or new toast values; for + * Oid8 the ID space is large enough that conflicts are not a concern. */ - if (toast_typid == OID8OID) + if (toast_typid == OIDOID) { - if (!OidIsValid(rel->rd_toastoid)) - { - /* normal case: just choose a new Oid8 */ - va_valueid = GetNewObjectId8(); - } - else + va_valueid = toast_preserve_valueid(toastrel, rel->rd_toastoid, + oldexternal, &data_todo); + if (va_valueid == InvalidOid) { - /* rewrite case: check to see if value was in old toast table */ - va_valueid = InvalidOid8; - if (oldexternal != NULL) - { - Assert(VARATT_IS_EXTERNAL_ONDISK(oldexternal)); - - /* - * Only consider the old pointer if its vartag matches the - * kind of pointer we are about to build. - */ - if (VARATT_IS_EXTERNAL_ONDISK_OID8(oldexternal)) - { - varatt_external_oid8 old_toast_pointer; - - /* Must copy to access aligned fields */ - VARATT_EXTERNAL_GET_POINTER(old_toast_pointer, oldexternal); - if (old_toast_pointer.va_toastrelid == rel->rd_toastoid) - { - /* - * This value came from the old toast table; reuse its - * ID. - */ - va_valueid = VARATT_EXTERNAL_OID8_GET_VALUEID(old_toast_pointer); - - /* - * 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 (toastrel_valueid_exists(toastrel, va_valueid)) - { - /* - * Match, so short-circuit the data storage loop - * below. - */ - data_todo = 0; - } - } - } - } - if (va_valueid == InvalidOid8) - { - /* - * New value. For Oid8 we just pick a new ID without worrying - * about conflicts. - */ - va_valueid = GetNewObjectId8(); - } + /* + * Choose an unused OID. During a rewrite we must also avoid IDs + * that are still present in the old toast table. + */ + do + va_valueid = + GetNewOidWithIndex(toastrel, + RelationGetRelid(toastidxs[validIndex]), + (AttrNumber) 1); + while (OidIsValid(rel->rd_toastoid) && + toastid_valueid_exists(rel->rd_toastoid, va_valueid)); } - - max_chunk_size = TOAST_OID8_MAX_CHUNK_SIZE; } - else + else if (toast_typid == OID8OID) { - if (!OidIsValid(rel->rd_toastoid)) - { - /* normal case: just choose an unused OID */ - va_valueid = - GetNewOidWithIndex(toastrel, - RelationGetRelid(toastidxs[validIndex]), - (AttrNumber) 1); - } - else + va_valueid = toast_preserve_valueid(toastrel, rel->rd_toastoid, + oldexternal, &data_todo); + if (va_valueid == InvalidOid8) { - /* rewrite case: check to see if value was in old toast table */ - va_valueid = InvalidOid; - if (oldexternal != NULL) - { - Assert(VARATT_IS_EXTERNAL_ONDISK(oldexternal)); - - /* Only reuse a pointer of the matching type; same as above */ - if (VARATT_IS_EXTERNAL_ONDISK_OID(oldexternal)) - { - varatt_external_oid old_toast_pointer; - - /* Must copy to access aligned fields */ - VARATT_EXTERNAL_GET_POINTER(old_toast_pointer, oldexternal); - if (old_toast_pointer.va_toastrelid == rel->rd_toastoid) - { - /* - * This value came from the old toast table; reuse its - * OID. - */ - va_valueid = old_toast_pointer.va_valueid; - - /* - * Corner case during table rewrite with multiple - * versions of the same row. See above for details. - */ - if (toastrel_valueid_exists(toastrel, va_valueid)) - { - /* - * Match, so short-circuit the data storage loop - * below. - */ - data_todo = 0; - } - } - } - } - if (va_valueid == InvalidOid) - { - /* - * new value; must choose a value that doesn't conflict in - * either old or new toast table. - */ - do - { - va_valueid = - GetNewOidWithIndex(toastrel, - RelationGetRelid(toastidxs[validIndex]), - (AttrNumber) 1); - } while (toastid_valueid_exists(rel->rd_toastoid, - va_valueid)); - } + /* The Oid8 space is large enough that we need not check conflicts */ + va_valueid = GetNewObjectId8(); } - - max_chunk_size = TOAST_OID_MAX_CHUNK_SIZE; } + else + elog(ERROR, "unexpected TOAST value ID type %u", toast_typid); + + max_chunk_size = TOAST_MAX_CHUNK_SIZE(toast_typid); /* * Split up the item into chunks @@ -486,6 +418,29 @@ toast_save_datum(Relation rel, Datum value, return PointerGetDatum(result); } +/* ---------- + * toast_valueid_scankey_init - + * + * Initialize a scan key that matches the value ID column of a TOAST table. + * The column type (OIDOID or OID8OID) selects the comparison operator, so + * callers need only supply the value ID widened to Oid8. + * ---------- + */ +void +toast_valueid_scankey_init(ScanKey entry, AttrNumber attno, + Oid toast_typid, Oid8 valueid) +{ + Assert(toast_typid == OIDOID || toast_typid == OID8OID); + if (toast_typid == OID8OID) + ScanKeyInit(entry, attno, + BTEqualStrategyNumber, F_OID8EQ, + ObjectId8GetDatum(valueid)); + else + ScanKeyInit(entry, attno, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum((Oid) valueid)); +} + /* ---------- * toast_delete_datum - * @@ -496,6 +451,7 @@ void toast_delete_datum(Relation rel, Datum value, bool is_speculative) { varlena *attr = (varlena *) DatumGetPointer(value); + toast_external_data toast_ext_data; Relation toastrel; Relation *toastidxs; ScanKeyData toastkey; @@ -503,75 +459,30 @@ toast_delete_datum(Relation rel, Datum value, bool is_speculative) HeapTuple toasttup; int num_indexes; int validIndex; - Oid toastrelid; - vartag_external tag; if (!VARATT_IS_EXTERNAL_ONDISK(attr)) return; /* - * Determine the pointer type from the datum's vartag and extract the - * toast relation OID and value ID accordingly. The vartag tells us - * everything we need - no TOAST table schema lookup required. + * Decode the pointer to get the toast relation OID and value ID. The + * vartag tells us everything we need - no TOAST table schema lookup + * required. */ - tag = VARTAG_EXTERNAL(attr); - - if (tag == VARTAG_ONDISK_OID8) - { - varatt_external_oid8 toast_pointer8; - - /* Must copy to access aligned fields */ - VARATT_EXTERNAL_GET_POINTER(toast_pointer8, attr); - toastrelid = toast_pointer8.va_toastrelid; - - /* - * Open the toast relation and its indexes - */ - toastrel = table_open(toastrelid, RowExclusiveLock); - - /* Fetch valid relation used for process */ - validIndex = toast_open_indexes(toastrel, - RowExclusiveLock, - &toastidxs, - &num_indexes); + toast_external_info_get(attr, &toast_ext_data); - /* - * Setup a scan key to find chunks with matching va_valueid - */ - ScanKeyInit(&toastkey, - (AttrNumber) 1, - BTEqualStrategyNumber, F_OID8EQ, - ObjectId8GetDatum(VARATT_EXTERNAL_OID8_GET_VALUEID(toast_pointer8))); - } - else - { - varatt_external_oid toast_pointer; - - Assert(tag == VARTAG_ONDISK_OID); - - /* Must copy to access aligned fields */ - VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); - toastrelid = toast_pointer.va_toastrelid; - - /* - * Open the toast relation and its indexes - */ - toastrel = table_open(toastrelid, RowExclusiveLock); + /* + * Open the toast relation and its indexes + */ + toastrel = table_open(toast_ext_data.toastrelid, RowExclusiveLock); + validIndex = toast_open_indexes(toastrel, + RowExclusiveLock, + &toastidxs, + &num_indexes); - /* Fetch valid relation used for process */ - validIndex = toast_open_indexes(toastrel, - RowExclusiveLock, - &toastidxs, - &num_indexes); - - /* - * Setup a scan key to find chunks with matching va_valueid - */ - ScanKeyInit(&toastkey, - (AttrNumber) 1, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(toast_pointer.va_valueid)); - } + /* Set up a scan key to find chunks with matching value ID */ + toast_valueid_scankey_init(&toastkey, (AttrNumber) 1, + TupleDescAttr(toastrel->rd_att, 0)->atttypid, + toast_ext_data.valueid); /* * Find all the chunks. (We don't actually care whether we see them in @@ -627,21 +538,9 @@ toastrel_valueid_exists(Relation toastrel, Oid8 valueid) &num_indexes); toast_typid = TupleDescAttr(toastrel->rd_att, 0)->atttypid; - Assert(toast_typid == OIDOID || toast_typid == OID8OID); - /* - * Setup a scan key to find chunks with matching va_valueid - */ - if (toast_typid == OID8OID) - ScanKeyInit(&toastkey, - (AttrNumber) 1, - BTEqualStrategyNumber, F_OID8EQ, - ObjectId8GetDatum(valueid)); - else - ScanKeyInit(&toastkey, - (AttrNumber) 1, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum((Oid) valueid)); + /* Set up a scan key to find chunks with matching value ID */ + toast_valueid_scankey_init(&toastkey, (AttrNumber) 1, toast_typid, valueid); /* * Is there any such chunk? diff --git a/src/backend/access/heap/heaptoast.c b/src/backend/access/heap/heaptoast.c index 31e6115de20..419adc1f3f7 100644 --- a/src/backend/access/heap/heaptoast.c +++ b/src/backend/access/heap/heaptoast.c @@ -650,10 +650,7 @@ heap_fetch_toast_slice(Relation toastrel, Oid8 valueid, int32 attrsize, toast_typid = TupleDescAttr(toastrel->rd_att, 0)->atttypid; Assert(toast_typid == OIDOID || toast_typid == OID8OID); - if (toast_typid == OID8OID) - max_chunk_size = TOAST_OID8_MAX_CHUNK_SIZE; - else - max_chunk_size = TOAST_OID_MAX_CHUNK_SIZE; + max_chunk_size = TOAST_MAX_CHUNK_SIZE(toast_typid); totalchunks = ((attrsize - 1) / max_chunk_size) + 1; startchunk = sliceoffset / max_chunk_size; @@ -661,16 +658,7 @@ heap_fetch_toast_slice(Relation toastrel, Oid8 valueid, int32 attrsize, Assert(endchunk <= totalchunks); /* Set up a scan key to fetch from the index. */ - if (toast_typid == OID8OID) - ScanKeyInit(&toastkey[0], - (AttrNumber) 1, - BTEqualStrategyNumber, F_OID8EQ, - ObjectId8GetDatum(valueid)); - else - ScanKeyInit(&toastkey[0], - (AttrNumber) 1, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum((Oid) valueid)); + toast_valueid_scankey_init(&toastkey[0], (AttrNumber) 1, toast_typid, valueid); /* * No additional condition if fetching all chunks. Otherwise, use an diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c index 086ef31e4f2..69a479cd31d 100644 --- a/src/backend/replication/logical/reorderbuffer.c +++ b/src/backend/replication/logical/reorderbuffer.c @@ -5175,15 +5175,13 @@ ReorderBufferToastReplace(ReorderBuffer *rb, ReorderBufferTXN *txn, varlena *varlena_pointer; /* va_rawsize is the size of the original datum -- including header */ - varatt_external_oid toast_pointer; - varatt_external_oid8 toast_pointer8; + toast_external_data toast_ext_data; varatt_indirect redirect_pointer; varlena *new_datum = NULL; varlena *reconstructed; dlist_iter it; Size data_done = 0; Oid8 toast_valueid; - int32 rawsize; if (attr->attisdropped) continue; @@ -5203,18 +5201,8 @@ ReorderBufferToastReplace(ReorderBuffer *rb, ReorderBufferTXN *txn, if (!VARATT_IS_EXTERNAL_ONDISK(varlena_pointer)) continue; - if (VARATT_IS_EXTERNAL_ONDISK_OID8(varlena_pointer)) - { - VARATT_EXTERNAL_GET_POINTER(toast_pointer8, varlena_pointer); - toast_valueid = VARATT_EXTERNAL_OID8_GET_VALUEID(toast_pointer8); - rawsize = toast_pointer8.va_rawsize; - } - else - { - VARATT_EXTERNAL_GET_POINTER(toast_pointer, varlena_pointer); - toast_valueid = toast_pointer.va_valueid; - rawsize = toast_pointer.va_rawsize; - } + toast_external_info_get(varlena_pointer, &toast_ext_data); + toast_valueid = toast_ext_data.valueid; /* * Check whether the toast tuple changed, replace if so. @@ -5232,7 +5220,7 @@ ReorderBufferToastReplace(ReorderBuffer *rb, ReorderBufferTXN *txn, free[natt] = true; - reconstructed = palloc0(rawsize); + reconstructed = palloc0(toast_ext_data.rawsize); ent->reconstructed = reconstructed; @@ -5258,27 +5246,13 @@ ReorderBufferToastReplace(ReorderBuffer *rb, ReorderBufferTXN *txn, data_done += VARSIZE(chunk) - VARHDRSZ; } - /* Verify size and set compression status based on pointer type */ - if (VARATT_IS_EXTERNAL_ONDISK_OID8(varlena_pointer)) - { - Assert(data_done == VARATT_EXTERNAL_OID8_GET_EXTSIZE(toast_pointer8)); + Assert(data_done == VARATT_EXTINFO_GET_EXTSIZE(toast_ext_data.extinfo)); - /* make sure its marked as compressed or not */ - if (VARATT_EXTERNAL_OID8_IS_COMPRESSED(toast_pointer8)) - SET_VARSIZE_COMPRESSED(reconstructed, data_done + VARHDRSZ); - else - SET_VARSIZE(reconstructed, data_done + VARHDRSZ); - } + /* make sure its marked as compressed or not */ + if (VARATT_EXTINFO_IS_COMPRESSED(toast_ext_data.extinfo, toast_ext_data.rawsize)) + SET_VARSIZE_COMPRESSED(reconstructed, data_done + VARHDRSZ); else - { - Assert(data_done == VARATT_EXTERNAL_OID_GET_EXTSIZE(toast_pointer)); - - /* make sure its marked as compressed or not */ - if (VARATT_EXTERNAL_OID_IS_COMPRESSED(toast_pointer)) - SET_VARSIZE_COMPRESSED(reconstructed, data_done + VARHDRSZ); - else - SET_VARSIZE(reconstructed, data_done + VARHDRSZ); - } + SET_VARSIZE(reconstructed, data_done + VARHDRSZ); memset(&redirect_pointer, 0, sizeof(redirect_pointer)); redirect_pointer.pointer = reconstructed; diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c index 0616db77555..f8c16bd4c87 100644 --- a/src/backend/utils/adt/varlena.c +++ b/src/backend/utils/adt/varlena.c @@ -4261,6 +4261,7 @@ pg_column_toast_chunk_id(PG_FUNCTION_ARGS) { int typlen; varlena *attr; + toast_external_data toast_ext_data; Oid8 result; /* On first call, get the input type's typlen, and save at *fn_extra */ @@ -4288,20 +4289,8 @@ pg_column_toast_chunk_id(PG_FUNCTION_ARGS) if (!VARATT_IS_EXTERNAL_ONDISK(attr)) PG_RETURN_NULL(); - if (VARATT_IS_EXTERNAL_ONDISK_OID8(attr)) - { - varatt_external_oid8 toast_pointer; - - VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); - result = VARATT_EXTERNAL_OID8_GET_VALUEID(toast_pointer); - } - else - { - varatt_external_oid toast_pointer; - - VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); - result = toast_pointer.va_valueid; - } + toast_external_info_get(attr, &toast_ext_data); + result = toast_ext_data.valueid; PG_RETURN_OID8(result); } diff --git a/src/include/access/detoast.h b/src/include/access/detoast.h index f065ea034e9..88301308544 100644 --- a/src/include/access/detoast.h +++ b/src/include/access/detoast.h @@ -12,6 +12,8 @@ #ifndef DETOAST_H #define DETOAST_H +#include "varatt.h" + /* * Macro to fetch the possibly-unaligned contents of an EXTERNAL datum * into a local "varatt_external_oid" toast pointer. This should be @@ -36,6 +38,52 @@ do { \ /* Size of an EXTERNAL datum that contains an indirection pointer */ #define INDIRECT_POINTER_SIZE (VARHDRSZ_EXTERNAL + sizeof(varatt_indirect)) +/* + * Decoded contents of an on-disk TOAST pointer, independent of whether the + * value ID is an Oid or an Oid8. This lets the many callers that only need + * the pointer's fields avoid open-coding the vartag branch. The value ID is + * always widened to Oid8; for a VARTAG_ONDISK_OID pointer it holds the 4-byte + * ID losslessly, and callers that need the narrow form cast back to Oid. + */ +typedef struct toast_external_data +{ + vartag_external tag; /* VARTAG_ONDISK_OID or VARTAG_ONDISK_OID8 */ + int32 rawsize; /* original data size (includes header) */ + uint32 extinfo; /* saved size + compression method */ + Oid8 valueid; /* value ID (widened from Oid if needed) */ + Oid toastrelid; /* OID of the TOAST table containing it */ +} toast_external_data; + +/* + * Decode an on-disk TOAST pointer into a toast_external_data. The datum must + * be VARATT_IS_EXTERNAL_ONDISK. + */ +static inline void +toast_external_info_get(const struct varlena *attr, toast_external_data *toast_ext_data) +{ + toast_ext_data->tag = VARTAG_EXTERNAL(attr); + if (toast_ext_data->tag == VARTAG_ONDISK_OID8) + { + varatt_external_oid8 toast_pointer; + + VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); + toast_ext_data->rawsize = toast_pointer.va_rawsize; + toast_ext_data->extinfo = toast_pointer.va_extinfo; + toast_ext_data->valueid = VARATT_EXTERNAL_OID8_GET_VALUEID(toast_pointer); + toast_ext_data->toastrelid = toast_pointer.va_toastrelid; + } + else + { + varatt_external_oid toast_pointer; + + VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); + toast_ext_data->rawsize = toast_pointer.va_rawsize; + toast_ext_data->extinfo = toast_pointer.va_extinfo; + toast_ext_data->valueid = toast_pointer.va_valueid; + toast_ext_data->toastrelid = toast_pointer.va_toastrelid; + } +} + /* ---------- * detoast_external_attr() - * diff --git a/src/include/access/heaptoast.h b/src/include/access/heaptoast.h index b7ef69a8f56..c58d0fa36a4 100644 --- a/src/include/access/heaptoast.h +++ b/src/include/access/heaptoast.h @@ -96,6 +96,13 @@ sizeof(int32) - \ VARHDRSZ) +/* + * Select the chunk size for a TOAST table from its value ID column type + * (OIDOID or OID8OID). + */ +#define TOAST_MAX_CHUNK_SIZE(toast_typid) \ + ((toast_typid) == OID8OID ? TOAST_OID8_MAX_CHUNK_SIZE : TOAST_OID_MAX_CHUNK_SIZE) + /* ---------- * heap_toast_insert_or_update - * diff --git a/src/include/access/toast_internals.h b/src/include/access/toast_internals.h index bf45889a642..c0015f9cc6c 100644 --- a/src/include/access/toast_internals.h +++ b/src/include/access/toast_internals.h @@ -12,6 +12,7 @@ #ifndef TOAST_INTERNALS_H #define TOAST_INTERNALS_H +#include "access/skey.h" #include "access/toast_compression.h" #include "storage/lockdefs.h" #include "utils/relcache.h" @@ -52,6 +53,9 @@ extern void toast_delete_datum(Relation rel, Datum value, bool is_speculative); extern Datum toast_save_datum(Relation rel, Datum value, varlena *oldexternal, uint32 options); +extern void toast_valueid_scankey_init(ScanKey entry, AttrNumber attno, + Oid toast_typid, Oid8 valueid); + extern int toast_open_indexes(Relation toastrel, LOCKMODE lock, Relation **toastidxs, diff --git a/src/include/varatt.h b/src/include/varatt.h index 1349a54371d..c8895df2651 100644 --- a/src/include/varatt.h +++ b/src/include/varatt.h @@ -564,33 +564,58 @@ VARDATA_COMPRESSED_GET_COMPRESS_METHOD(const void *PTR) } /* - * Same for external Datums; but note argument is a struct - * varatt_external_oid. + * The size and compression method of an externally-stored value are packed + * into a single 32-bit va_extinfo word: the low VARLENA_EXTSIZE_BITS hold the + * saved size, the top two bits the compression method. This encoding does not + * depend on the value ID width, so these helpers operate on the raw word and + * are shared by every on-disk TOAST pointer type. */ static inline Size -VARATT_EXTERNAL_OID_GET_EXTSIZE(varatt_external_oid toast_pointer) +VARATT_EXTINFO_GET_EXTSIZE(uint32 extinfo) { - return toast_pointer.va_extinfo & VARLENA_EXTSIZE_MASK; + return extinfo & VARLENA_EXTSIZE_MASK; } static inline uint32 -VARATT_EXTERNAL_OID_GET_COMPRESS_METHOD(varatt_external_oid toast_pointer) +VARATT_EXTINFO_GET_COMPRESS_METHOD(uint32 extinfo) { - return toast_pointer.va_extinfo >> VARLENA_EXTSIZE_BITS; + return extinfo >> VARLENA_EXTSIZE_BITS; } /* - * Testing whether an externally-stored value is compressed now requires - * comparing size stored in va_extinfo (the actual length of the external data) + * Testing whether an externally-stored value is compressed requires comparing + * the saved size stored in va_extinfo (the actual length of the external data) * to rawsize (the original uncompressed datum's size). The latter includes * VARHDRSZ overhead, the former doesn't. We never use compression unless it * actually saves space, so we expect either equality or less-than. */ +static inline bool +VARATT_EXTINFO_IS_COMPRESSED(uint32 extinfo, int32 rawsize) +{ + return VARATT_EXTINFO_GET_EXTSIZE(extinfo) < (Size) (rawsize - VARHDRSZ); +} + +/* + * Same for external Datums; but note argument is a struct + * varatt_external_oid. + */ +static inline Size +VARATT_EXTERNAL_OID_GET_EXTSIZE(varatt_external_oid toast_pointer) +{ + return VARATT_EXTINFO_GET_EXTSIZE(toast_pointer.va_extinfo); +} + +static inline uint32 +VARATT_EXTERNAL_OID_GET_COMPRESS_METHOD(varatt_external_oid toast_pointer) +{ + return VARATT_EXTINFO_GET_COMPRESS_METHOD(toast_pointer.va_extinfo); +} + static inline bool VARATT_EXTERNAL_OID_IS_COMPRESSED(varatt_external_oid toast_pointer) { - return VARATT_EXTERNAL_OID_GET_EXTSIZE(toast_pointer) < - (Size) (toast_pointer.va_rawsize - VARHDRSZ); + return VARATT_EXTINFO_IS_COMPRESSED(toast_pointer.va_extinfo, + toast_pointer.va_rawsize); } /* @@ -600,20 +625,20 @@ VARATT_EXTERNAL_OID_IS_COMPRESSED(varatt_external_oid toast_pointer) static inline Size VARATT_EXTERNAL_OID8_GET_EXTSIZE(varatt_external_oid8 toast_pointer) { - return toast_pointer.va_extinfo & VARLENA_EXTSIZE_MASK; + return VARATT_EXTINFO_GET_EXTSIZE(toast_pointer.va_extinfo); } static inline uint32 VARATT_EXTERNAL_OID8_GET_COMPRESS_METHOD(varatt_external_oid8 toast_pointer) { - return toast_pointer.va_extinfo >> VARLENA_EXTSIZE_BITS; + return VARATT_EXTINFO_GET_COMPRESS_METHOD(toast_pointer.va_extinfo); } static inline bool VARATT_EXTERNAL_OID8_IS_COMPRESSED(varatt_external_oid8 toast_pointer) { - return VARATT_EXTERNAL_OID8_GET_EXTSIZE(toast_pointer) < - (Size) (toast_pointer.va_rawsize - VARHDRSZ); + return VARATT_EXTINFO_IS_COMPRESSED(toast_pointer.va_extinfo, + toast_pointer.va_rawsize); } #endif diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 7aedaafab90..dd5394ab3e0 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -4357,6 +4357,7 @@ timeout_params timerCA tlist_vinfo toast_compress_header +toast_external_data tokenize_error_callback_arg transferMode transfer_thread_arg @@ -4413,6 +4414,7 @@ vacuumingOptions validate_string_relopt varatt_expanded varatt_external_oid +varatt_external_oid8 varatt_indirect varattrib_1b varattrib_1b_e