From 9fc0de0f47384bc5392eb04bed4e8e6d629019db Mon Sep 17 00:00:00 2001 From: Nikhil Kumar Veldanda Date: Fri, 25 Sep 2026 01:26:02 -0700 Subject: [PATCH v4 3/4] Allow more than four TOAST compression methods A compressed varlena identifies its compression method in the two high-order bits of the tcinfo word of its header, and an external TOAST pointer does the same in va_extinfo. Two of the four possible values are taken by pglz and lz4, and a third is reserved as TOAST_INVALID_COMPRESSION_ID, so adding even one more method would use up the last one. Introduce a "long" form of both structures, in which the two method bits hold VARLENA_COMPRESS_METHOD_LONG and the actual method ID is stored in a byte of its own following the fixed part: - For compressed-in-line datums the long form is described by a new struct, varattrib_4b_long: the va_compressed layout with a va_cmid byte inserted before the data, giving a header of VARHDRSZ_COMPRESSED_LONG bytes. It is deliberately not a member of the varattrib_4b union, as a member with the extra byte would pad to 12 bytes and so increase sizeof(varattrib_4b) from 8; code all over the place looks at varlena headers of unknown or smaller size through pointers of that type, and compilers use the type's size to check such accesses (-Warray-bounds). Static assertions tie its layout to va_compressed. VARDATA_COMPRESSED_SET_TCINFO_LONG() writes this header, next to VARDATA_COMPRESSED_SET_TCINFO() for the plain one, and VARDATA_COMPRESSED_GET_COMPRESS_METHOD() reads both forms. - Two new TOAST pointer tags, VARTAG_ONDISK_OID_LONG and VARTAG_ONDISK_OID8_LONG, denote a plain varatt_external_oid or varatt_external_oid8 followed by the method ID byte. Using tags rather than a variable-size struct keeps VARTAG_SIZE() a pure function of the tag, which matters for tuple deforming, and avoids a struct with trailing padding that must not reach disk. VARTAG_IS_ONDISK_LONG() tells the two forms apart, and VARTAG_IS_ONDISK_OID8() accepts both forms of an Oid8 pointer for the places that need to pick the right struct. Only pglz and lz4 keep using the two bits directly, so existing data is unaffected and neither form changes size for them. Every other method, present or future, uses the long form; which is decided in one place, toast_compression_id_needs_cmid_byte(). This leaves room for IDs up to 255. TOAST_INVALID_COMPRESSION_ID moves from 2 to 3, so as to be equal to VARLENA_COMPRESS_METHOD_LONG: code that mistakenly reads the raw method bits of a long-form value as a method ID then ends up with an invalid ID rather than a real method. This also frees ID 2 for the next method. amcheck now verifies that the compression method of an external value is stored in the pointer form appropriate for it, and that only compressed values use the long form. --- contrib/amcheck/verify_heapam.c | 35 +++-- doc/src/sgml/storage.sgml | 5 +- src/backend/access/common/toast_compression.c | 8 ++ src/backend/access/common/toast_internals.c | 62 +++++++-- src/include/access/detoast.h | 33 ++++- src/include/access/toast_compression.h | 45 ++++++- src/include/varatt.h | 121 +++++++++++++++++- src/tools/pgindent/typedefs.list | 1 + 8 files changed, 266 insertions(+), 44 deletions(-) diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c index cb7938aed6c..d60f549d274 100644 --- a/contrib/amcheck/verify_heapam.c +++ b/contrib/amcheck/verify_heapam.c @@ -77,7 +77,7 @@ typedef struct ToastedAttribute { Oid8 va_valueid; /* value ID (works for both Oid and Oid8) */ uint32 va_extinfo; /* external size and compression method */ - vartag_external tag; /* VARTAG_ONDISK_OID or VARTAG_ONDISK_OID8 */ + vartag_external tag; /* one of the VARTAG_ONDISK_* tags */ BlockNumber blkno; /* block in main table */ OffsetNumber offnum; /* offset in main table */ AttrNumber attnum; /* attribute in main table */ @@ -1752,7 +1752,7 @@ check_tuple_attribute(HeapCheckContext *ctx) { uint8 va_tag = VARTAG_EXTERNAL(tp + ctx->offset); - if (va_tag != VARTAG_ONDISK_OID && va_tag != VARTAG_ONDISK_OID8) + if (!VARATT_IS_EXTERNAL_ONDISK(tp + ctx->offset)) { report_corruption(ctx, psprintf("toasted attribute has unexpected TOAST tag %u", @@ -1816,14 +1816,22 @@ check_tuple_attribute(HeapCheckContext *ctx) ToastCompressionId cmid; bool valid = false; - /* Compressed attributes should have a valid compression method */ + /* + * Compressed attributes should have a valid compression method, and + * it must be stored in the TOAST pointer form appropriate for it: the + * methods that fit in the two method bits of va_extinfo use the plain + * pointer, all others the long form. A plain pointer whose method + * bits hold VARLENA_COMPRESS_METHOD_LONG decodes as + * TOAST_INVALID_COMPRESSION_ID and so is caught here too. + */ cmid = toast_ext_data.compress_method; switch (cmid) { /* List of all valid compression method IDs */ case TOAST_PGLZ_COMPRESSION_ID: case TOAST_LZ4_COMPRESSION_ID: - valid = true; + valid = (toast_compression_id_needs_cmid_byte(cmid) == + VARTAG_IS_ONDISK_LONG(va_tag_value)); break; /* Recognized but invalid compression method ID */ @@ -1837,6 +1845,13 @@ check_tuple_attribute(HeapCheckContext *ctx) psprintf("toast value " OID8_FORMAT " has invalid compression method id %d", toast_pointer_valueid, cmid)); } + else if (VARTAG_IS_ONDISK_LONG(va_tag_value)) + { + /* Only compressed values get the long form of the pointer */ + report_corruption(ctx, + psprintf("toast value " OID8_FORMAT " is not compressed but has a long TOAST pointer", + toast_pointer_valueid)); + } /* The tuple header better claim to contain toasted values */ if (!(infomask & HEAP_HASEXTERNAL)) @@ -1903,7 +1918,6 @@ check_toasted_attribute(HeapCheckContext *ctx, ToastedAttribute *ta) int32 max_chunk_size; Oid8 toast_valueid; Oid toast_typid; - vartag_external expected_tag; toast_valueid = ta->va_valueid; extsize = VARATT_EXTINFO_GET_EXTSIZE(ta->va_extinfo); @@ -1911,14 +1925,11 @@ check_toasted_attribute(HeapCheckContext *ctx, ToastedAttribute *ta) /* * Take the chunk_id type from the TOAST table's own definition, not from * the vartag in the main table as that pointer is the very thing under - * scrutiny here. The two must agree. + * scrutiny here. The two must agree, whether the pointer is of the plain + * or of the long form. */ toast_typid = TupleDescAttr(ctx->toast_rel->rd_att, 0)->atttypid; - if (toast_typid == OID8OID) - expected_tag = VARTAG_ONDISK_OID8; - else if (toast_typid == OIDOID) - expected_tag = VARTAG_ONDISK_OID; - else + if (toast_typid != OIDOID && toast_typid != OID8OID) { report_toast_corruption(ctx, ta, psprintf("toast value " OID8_FORMAT " stored in toast table whose chunk_id has unexpected type %u", @@ -1926,7 +1937,7 @@ check_toasted_attribute(HeapCheckContext *ctx, ToastedAttribute *ta) return; } - if (ta->tag != expected_tag) + if (VARTAG_IS_ONDISK_OID8(ta->tag) != (toast_typid == OID8OID)) { report_toast_corruption(ctx, ta, psprintf("toast value " OID8_FORMAT " has TOAST tag %u, but chunk_id of toast table has type %u", diff --git a/doc/src/sgml/storage.sgml b/doc/src/sgml/storage.sgml index 83de016eaa5..d301251d2b5 100644 --- a/doc/src/sgml/storage.sgml +++ b/doc/src/sgml/storage.sgml @@ -439,7 +439,10 @@ any. Allowing for the varlena header bytes, the total size of an on-disk TOAST pointer datum is 18 bytes when using an OID as chunk_id, or 22 bytes when using an OID8 as chunk_id, regardless of the -actual size of the represented value. +actual size of the represented value. Values compressed with a method other +than pglz or lz4 need one additional +byte to identify the compression method, both in the pointer datum and in +the header of the compressed data itself. diff --git a/src/backend/access/common/toast_compression.c b/src/backend/access/common/toast_compression.c index 8bb871c9791..9bd4404fcf7 100644 --- a/src/backend/access/common/toast_compression.c +++ b/src/backend/access/common/toast_compression.c @@ -84,6 +84,8 @@ pglz_decompress_datum(const varlena *value) varlena *result; int32 rawsize; + Assert(VARDATA_COMPRESSED_GET_COMPRESS_METHOD(value) == TOAST_PGLZ_COMPRESSION_ID); + /* allocate memory for the uncompressed data */ result = (varlena *) palloc(VARDATA_COMPRESSED_GET_EXTSIZE(value) + VARHDRSZ); @@ -112,6 +114,8 @@ pglz_decompress_datum_slice(const varlena *value, varlena *result; int32 rawsize; + Assert(VARDATA_COMPRESSED_GET_COMPRESS_METHOD(value) == TOAST_PGLZ_COMPRESSION_ID); + /* allocate memory for the uncompressed data */ result = (varlena *) palloc(slicelength + VARHDRSZ); @@ -188,6 +192,8 @@ lz4_decompress_datum(const varlena *value) int32 rawsize; varlena *result; + Assert(VARDATA_COMPRESSED_GET_COMPRESS_METHOD(value) == TOAST_LZ4_COMPRESSION_ID); + /* allocate memory for the uncompressed data */ result = (varlena *) palloc(VARDATA_COMPRESSED_GET_EXTSIZE(value) + VARHDRSZ); @@ -221,6 +227,8 @@ lz4_decompress_datum_slice(const varlena *value, int32 slicelength) int32 rawsize; varlena *result; + Assert(VARDATA_COMPRESSED_GET_COMPRESS_METHOD(value) == TOAST_LZ4_COMPRESSION_ID); + /* slice decompression not supported prior to 1.8.3 */ if (LZ4_versionNumber() < 10803) return lz4_decompress_datum(value); diff --git a/src/backend/access/common/toast_internals.c b/src/backend/access/common/toast_internals.c index 353fbd45f20..8194fa98bf8 100644 --- a/src/backend/access/common/toast_internals.c +++ b/src/backend/access/common/toast_internals.c @@ -29,7 +29,8 @@ static bool toastrel_valueid_exists(Relation toastrel, Oid8 valueid); static bool toastid_valueid_exists(Oid toastrelid, Oid8 valueid); -static varlena *toast_pointer_build(vartag_external tag, const void *ptr); +static varlena *toast_pointer_build(vartag_external tag, const void *ptr, + ToastCompressionId cmid); /* ---------- * toast_compress_datum - @@ -92,9 +93,15 @@ toast_compress_datum(Datum value, char cmethod) */ if (VARSIZE(tmp) < valsize - 2) { - /* successful compression */ + /* + * Successful compression. The compression routine has laid out the + * header in the form its method requires; fill it in accordingly. + */ Assert(cmid != TOAST_INVALID_COMPRESSION_ID); - VARDATA_COMPRESSED_SET_TCINFO(tmp, valsize, cmid); + if (toast_compression_id_needs_cmid_byte(cmid)) + VARDATA_COMPRESSED_SET_TCINFO_LONG(tmp, valsize, cmid); + else + VARDATA_COMPRESSED_SET_TCINFO(tmp, valsize, cmid); return PointerGetDatum(tmp); } else @@ -190,6 +197,8 @@ toast_save_datum(Relation rel, Datum value, uint32 va_extinfo; Oid8 va_valueid; Oid va_toastrelid; + ToastCompressionId cmid = TOAST_INVALID_COMPRESSION_ID; + bool pointer_long = false; Assert(!VARATT_IS_EXTERNAL(dval)); @@ -215,7 +224,9 @@ toast_save_datum(Relation rel, Datum value, * * va_extinfo stored the actual size of the data payload in the toast * records and the compression method in first 2 bits if data is - * compressed. + * compressed. Methods that don't fit in those bits are flagged there + * with VARLENA_COMPRESS_METHOD_LONG and require the long form of the + * TOAST pointer, which carries the actual method ID in an extra byte. */ if (VARATT_IS_SHORT(dval)) { @@ -226,7 +237,7 @@ toast_save_datum(Relation rel, Datum value, } else if (VARATT_IS_COMPRESSED(dval)) { - uint32 cmid = VARDATA_COMPRESSED_GET_COMPRESS_METHOD(dval); + cmid = VARDATA_COMPRESSED_GET_COMPRESS_METHOD(dval); data_p = VARDATA(dval); data_todo = VARSIZE(dval) - VARHDRSZ; @@ -236,7 +247,14 @@ toast_save_datum(Relation rel, Datum value, /* set external size and compression method */ Assert(cmid == TOAST_PGLZ_COMPRESSION_ID || cmid == TOAST_LZ4_COMPRESSION_ID); - va_extinfo = data_todo | (cmid << VARLENA_EXTSIZE_BITS); + if (toast_compression_id_needs_cmid_byte(cmid)) + { + pointer_long = true; + va_extinfo = data_todo | + ((uint32) VARLENA_COMPRESS_METHOD_LONG << VARLENA_EXTSIZE_BITS); + } + else + va_extinfo = data_todo | ((uint32) cmid << VARLENA_EXTSIZE_BITS); /* Assert that the numbers look like it's compressed */ Assert(VARATT_EXTINFO_IS_COMPRESSED(va_extinfo, va_rawsize)); @@ -381,7 +399,8 @@ toast_save_datum(Relation rel, Datum value, table_close(toastrel, NoLock); /* - * Create the TOAST pointer value that we'll return + * Create the TOAST pointer value that we'll return. The long form is the + * plain one followed by the compression method ID byte. */ if (toast_typid == OID8OID) { @@ -392,7 +411,8 @@ toast_save_datum(Relation rel, Datum value, VARATT_EXTERNAL_OID8_SET_VALUEID(&toast_pointer, va_valueid); toast_pointer.va_toastrelid = va_toastrelid; - result = toast_pointer_build(VARTAG_ONDISK_OID8, &toast_pointer); + result = toast_pointer_build(pointer_long ? VARTAG_ONDISK_OID8_LONG : VARTAG_ONDISK_OID8, + &toast_pointer, cmid); } else { @@ -403,7 +423,8 @@ toast_save_datum(Relation rel, Datum value, toast_pointer.va_valueid = (Oid) va_valueid; toast_pointer.va_toastrelid = va_toastrelid; - result = toast_pointer_build(VARTAG_ONDISK_OID, &toast_pointer); + result = toast_pointer_build(pointer_long ? VARTAG_ONDISK_OID_LONG : VARTAG_ONDISK_OID, + &toast_pointer, cmid); } return PointerGetDatum(result); @@ -413,18 +434,33 @@ toast_save_datum(Relation rel, Datum value, * toast_pointer_build - * * Build an on-disk TOAST pointer datum of the given tag from the - * varatt_external_oid or varatt_external_oid8 at ptr. + * varatt_external_oid or varatt_external_oid8 at ptr. For the long-form + * tags, the compression method ID byte is appended to it. * ---------- */ static varlena * -toast_pointer_build(vartag_external tag, const void *ptr) +toast_pointer_build(vartag_external tag, const void *ptr, + ToastCompressionId cmid) { varlena *result; + char *data; + Size size = VARTAG_SIZE(tag); - result = (varlena *) palloc(VARHDRSZ_EXTERNAL + VARTAG_SIZE(tag)); + result = (varlena *) palloc(VARHDRSZ_EXTERNAL + size); SET_VARTAG_EXTERNAL(result, tag); Assert(VARATT_IS_EXTERNAL_ONDISK(result)); - memcpy(VARDATA_EXTERNAL(result), ptr, VARTAG_SIZE(tag)); + data = VARDATA_EXTERNAL(result); + + if (VARTAG_IS_ONDISK_LONG(tag)) + { + uint8 cmid_byte = (uint8) cmid; + + Assert(cmid != TOAST_INVALID_COMPRESSION_ID && + toast_compression_id_needs_cmid_byte(cmid)); + size -= VARATT_EXTERNAL_CMID_SIZE; + memcpy(data + size, &cmid_byte, sizeof(cmid_byte)); + } + memcpy(data, ptr, size); return result; } diff --git a/src/include/access/detoast.h b/src/include/access/detoast.h index d8edcdf259b..dff3dee7ac2 100644 --- a/src/include/access/detoast.h +++ b/src/include/access/detoast.h @@ -43,7 +43,8 @@ do { \ * Decoded contents of an on-disk external TOAST pointer. * * compress_method is only meaningful if the value is compressed, that is if - * VARATT_EXTINFO_IS_COMPRESSED(extinfo, rawsize). + * VARATT_EXTINFO_IS_COMPRESSED(extinfo, rawsize). Unlike the raw method bits + * of extinfo, it is the actual method ID whichever pointer form was used. */ typedef struct toast_external_data { @@ -61,32 +62,56 @@ typedef struct toast_external_data static inline void toast_external_info_get(const struct varlena *attr, toast_external_data *toast_ext_data) { + const char *ptr = VARDATA_EXTERNAL(attr); + Size fixedsize; + Assert(VARATT_IS_EXTERNAL_ONDISK(attr)); + /* + * The long form of a pointer is the plain form followed by the + * compression method ID byte, so decoding the fixed part is the same for + * both. We can't use VARATT_EXTERNAL_GET_POINTER() here because it + * insists on the datum being exactly the size of the struct. + */ toast_ext_data->tag = VARTAG_EXTERNAL(attr); - if (toast_ext_data->tag == VARTAG_ONDISK_OID8) + if (VARTAG_IS_ONDISK_OID8(toast_ext_data->tag)) { varatt_external_oid8 toast_pointer; - VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); + memcpy(&toast_pointer, ptr, sizeof(toast_pointer)); 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; + fixedsize = sizeof(toast_pointer); } else { varatt_external_oid toast_pointer; - VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); + memcpy(&toast_pointer, ptr, sizeof(toast_pointer)); 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; + fixedsize = sizeof(toast_pointer); } + /* + * Resolve the compression method: it is the two method bits of extinfo, + * except in the long form of a pointer, where those bits only flag the + * form and the method is in the trailing byte. + */ toast_ext_data->compress_method = (ToastCompressionId) (toast_ext_data->extinfo >> VARLENA_EXTSIZE_BITS); + if (VARTAG_IS_ONDISK_LONG(toast_ext_data->tag)) + { + uint8 cmid; + + Assert(toast_ext_data->compress_method == VARLENA_COMPRESS_METHOD_LONG); + memcpy(&cmid, ptr + fixedsize, sizeof(cmid)); + toast_ext_data->compress_method = (ToastCompressionId) cmid; + } } /* ---------- diff --git a/src/include/access/toast_compression.h b/src/include/access/toast_compression.h index 3265f10b734..e848d72805b 100644 --- a/src/include/access/toast_compression.h +++ b/src/include/access/toast_compression.h @@ -13,6 +13,8 @@ #ifndef TOAST_COMPRESSION_H #define TOAST_COMPRESSION_H +#include "varatt.h" + /* * GUC support. * @@ -23,24 +25,53 @@ extern PGDLLIMPORT int default_toast_compression; /* - * Built-in compression method ID. The toast compression header will store - * this in the first 2 bits of the raw length. These built-in compression - * method IDs are directly mapped to the built-in compression methods. + * Built-in compression method ID. These built-in compression method IDs are + * directly mapped to the built-in compression methods. + * + * A compressed varlena identifies its method in the two high-order bits of + * its tcinfo/extinfo word. Only pglz and lz4 are stored there directly; all + * other methods use the long form of the header, flagged by + * VARLENA_COMPRESS_METHOD_LONG in those bits, which stores the ID in a byte + * of its own (see varatt.h) and so leaves room for IDs up to 255. + * toast_compression_id_needs_cmid_byte() tells which form a given ID uses. + * + * TOAST_INVALID_COMPRESSION_ID is not a real compression method and is never + * stored on disk; it only serves to report "this value is not compressed". + * It is deliberately equal to VARLENA_COMPRESS_METHOD_LONG, so that code + * that mistakenly interprets the raw two-bit field of a long-form value as a + * method ID ends up with an invalid ID rather than a real method. * * Don't use these values for anything other than understanding the meaning * of the raw bits from a varlena; in particular, if the goal is to identify * a compression method, use the constants TOAST_PGLZ_COMPRESSION, etc. - * below. We might someday support more than 4 compression methods, but - * we can never have more than 4 values in this enum, because there are - * only 2 bits available in the places where this is stored. + * below. */ typedef enum ToastCompressionId { TOAST_PGLZ_COMPRESSION_ID = 0, TOAST_LZ4_COMPRESSION_ID = 1, - TOAST_INVALID_COMPRESSION_ID = 2, + TOAST_INVALID_COMPRESSION_ID = 3, } ToastCompressionId; +StaticAssertDecl(TOAST_INVALID_COMPRESSION_ID == VARLENA_COMPRESS_METHOD_LONG, + "TOAST_INVALID_COMPRESSION_ID must match VARLENA_COMPRESS_METHOD_LONG"); + +/* + * Does this compression method ID need a byte of its own? + * + * Only the two original methods fit in the two method bits of the header; + * every other method needs the long form of the compressed-in-line header + * and of the TOAST pointer, which carry the ID in a separate byte. Not + * meaningful for TOAST_INVALID_COMPRESSION_ID. + */ +static inline bool +toast_compression_id_needs_cmid_byte(ToastCompressionId cmid) +{ + Assert(cmid != TOAST_INVALID_COMPRESSION_ID); + return (cmid != TOAST_PGLZ_COMPRESSION_ID && + cmid != TOAST_LZ4_COMPRESSION_ID); +} + /* * Built-in compression methods. pg_attribute will store these in the * attcompression column. In attcompression, InvalidCompressionMethod diff --git a/src/include/varatt.h b/src/include/varatt.h index 57175b39b24..62ace34d83b 100644 --- a/src/include/varatt.h +++ b/src/include/varatt.h @@ -83,9 +83,27 @@ VARATT_EXTERNAL_OID8_SET_VALUEID(varatt_external_oid8 *toast_pointer, Oid8 id) /* * These macros define the "saved size" portion of va_extinfo. Its remaining * two high-order bits identify the compression method. + * + * Only the two original compression methods (pglz and lz4) are identified + * directly by those two bits. Any other method uses the "long" form of the + * header, which is flagged by the value VARLENA_COMPRESS_METHOD_LONG in those + * bits and carries the actual method ID in a byte of its own following the + * fixed part; see varattrib_4b_long and the VARTAG_ONDISK_*_LONG TOAST + * pointers below. */ #define VARLENA_EXTSIZE_BITS 30 #define VARLENA_EXTSIZE_MASK ((1U << VARLENA_EXTSIZE_BITS) - 1) +#define VARLENA_COMPRESS_METHOD_LONG 3 + +/* + * Size of the compression method ID byte that the VARTAG_ONDISK_*_LONG TOAST + * pointers append to a varatt_external_oid or varatt_external_oid8. No + * struct is declared for those pointers, because a struct containing that + * trailing byte would carry padding that we must not store on disk; decode + * the fixed part as usual and then read the extra byte (see + * toast_external_info_get()). + */ +#define VARATT_EXTERNAL_CMID_SIZE sizeof(uint8) /* * varatt_indirect is a "TOAST pointer" representing an out-of-line @@ -123,6 +141,13 @@ typedef struct varatt_expanded * value for VARTAG_ONDISK_OID comes from a requirement for on-disk * compatibility with a previous notion that the tag field was the pointer * datum's length. + * + * The _LONG variants of the on-disk tags denote the long form of a TOAST + * pointer, which appends a compression method ID byte to the corresponding + * plain form (see VARATT_EXTERNAL_CMID_SIZE). It is used only for values + * whose compression method does not fit in the two method bits of + * va_extinfo. Each _LONG tag is numbered next to its plain counterpart, + * but nothing relies on that. */ typedef enum vartag_external { @@ -130,7 +155,9 @@ typedef enum vartag_external VARTAG_EXPANDED_RO = 2, VARTAG_EXPANDED_RW = 3, VARTAG_ONDISK_OID8 = 4, - VARTAG_ONDISK_OID = 18 + VARTAG_ONDISK_OID8_LONG = 5, + VARTAG_ONDISK_OID = 18, + VARTAG_ONDISK_OID_LONG = 19 } vartag_external; /* Is a TOAST pointer either type of expanded-object pointer? */ @@ -141,6 +168,23 @@ VARTAG_IS_EXPANDED(vartag_external tag) return ((tag & ~1) == VARTAG_EXPANDED_RO); } +/* + * Is a TOAST pointer an on-disk one with an Oid8 value ID, in either form? + * (Any other on-disk pointer has an Oid value ID.) + */ +static inline bool +VARTAG_IS_ONDISK_OID8(vartag_external tag) +{ + return (tag == VARTAG_ONDISK_OID8 || tag == VARTAG_ONDISK_OID8_LONG); +} + +/* Is a TOAST pointer the long form of an on-disk pointer? */ +static inline bool +VARTAG_IS_ONDISK_LONG(vartag_external tag) +{ + return (tag == VARTAG_ONDISK_OID_LONG || tag == VARTAG_ONDISK_OID8_LONG); +} + /* Size of the data part of a "TOAST pointer" datum */ static inline Size VARTAG_SIZE(vartag_external tag) @@ -153,6 +197,10 @@ VARTAG_SIZE(vartag_external tag) return sizeof(varatt_external_oid); else if (tag == VARTAG_ONDISK_OID8) return sizeof(varatt_external_oid8); + else if (tag == VARTAG_ONDISK_OID_LONG) + return sizeof(varatt_external_oid) + VARATT_EXTERNAL_CMID_SIZE; + else if (tag == VARTAG_ONDISK_OID8_LONG) + return sizeof(varatt_external_oid8) + VARATT_EXTERNAL_CMID_SIZE; else { Assert(false); @@ -185,6 +233,33 @@ typedef union } va_compressed; } varattrib_4b; +/* + * Long form of the compressed-in-line format, used when the method bits of + * va_tcinfo hold VARLENA_COMPRESS_METHOD_LONG. It is the va_compressed + * layout above with the compression method ID inserted before the data. + * + * This is deliberately not a member of the varattrib_4b union: a member with + * the extra byte would pad to 12 bytes and so increase sizeof(varattrib_4b) + * from 8, and code all over the place inspects varlena headers of unknown or + * smaller size through pointers of that type. Only ever use offsetof() on + * this struct, never sizeof(), as it has trailing padding. + */ +typedef struct +{ + uint32 va_header; + uint32 va_tcinfo; /* As in va_compressed, with the method bits + * set to VARLENA_COMPRESS_METHOD_LONG */ + uint8 va_cmid; /* Compression method ID */ + char va_data[FLEXIBLE_ARRAY_MEMBER]; /* Compressed data */ +} varattrib_4b_long; + +StaticAssertDecl(offsetof(varattrib_4b_long, va_tcinfo) == + offsetof(varattrib_4b, va_compressed.va_tcinfo), + "varattrib_4b_long must extend va_compressed"); +StaticAssertDecl(offsetof(varattrib_4b_long, va_cmid) == + offsetof(varattrib_4b, va_compressed.va_data), + "varattrib_4b_long must extend va_compressed"); + typedef struct { uint8 va_header; @@ -321,6 +396,7 @@ typedef struct #define VARHDRSZ_EXTERNAL offsetof(varattrib_1b_e, va_data) #define VARHDRSZ_COMPRESSED offsetof(varattrib_4b, va_compressed.va_data) +#define VARHDRSZ_COMPRESSED_LONG offsetof(varattrib_4b_long, va_data) #define VARHDRSZ_SHORT offsetof(varattrib_1b, va_data) #define VARATT_SHORT_MAX 0x7F @@ -411,7 +487,8 @@ VARATT_IS_EXTERNAL_ONDISK(const void *PTR) if (!VARATT_IS_EXTERNAL(PTR)) return false; tag = VARTAG_EXTERNAL(PTR); - return (tag == VARTAG_ONDISK_OID || tag == VARTAG_ONDISK_OID8); + return (tag == VARTAG_ONDISK_OID || tag == VARTAG_ONDISK_OID8 || + VARTAG_IS_ONDISK_LONG(tag)); } /* Is varlena datum an indirect pointer? */ @@ -546,33 +623,63 @@ VARDATA_COMPRESSED_GET_EXTSIZE(const void *PTR) return ((const varattrib_4b *) PTR)->va_compressed.va_tcinfo & VARLENA_EXTSIZE_MASK; } -/* Compression method of a compressed-in-line varlena datum */ +/* + * Compression method of a compressed-in-line varlena datum. This handles + * both header forms, so it always returns the actual method ID. + */ static inline uint32 VARDATA_COMPRESSED_GET_COMPRESS_METHOD(const void *PTR) { - return ((const varattrib_4b *) PTR)->va_compressed.va_tcinfo >> VARLENA_EXTSIZE_BITS; + const varattrib_4b *va = (const varattrib_4b *) PTR; + uint32 method = va->va_compressed.va_tcinfo >> VARLENA_EXTSIZE_BITS; + + if (method == VARLENA_COMPRESS_METHOD_LONG) + method = ((const varattrib_4b_long *) PTR)->va_cmid; + return method; } /* * Set the decompressed size and the compression method of a * compressed-in-line varlena datum. The length word is not touched here; the * caller is expected to have set it with SET_VARSIZE_COMPRESSED(). + * + * This writes the plain form of the header, so the method must be one that + * fits in the two method bits; VARLENA_COMPRESS_METHOD_LONG is not a method. */ static inline void VARDATA_COMPRESSED_SET_TCINFO(void *PTR, uint32 extsize, uint32 cmethod) { Assert(extsize > 0 && extsize <= VARLENA_EXTSIZE_MASK); - Assert(cmethod < (1U << (32 - VARLENA_EXTSIZE_BITS))); + Assert(cmethod < VARLENA_COMPRESS_METHOD_LONG); ((varattrib_4b *) PTR)->va_compressed.va_tcinfo = extsize | (cmethod << VARLENA_EXTSIZE_BITS); } +/* + * Same for the long form of the header: the method bits are set to + * VARLENA_COMPRESS_METHOD_LONG and the method goes into va_cmid. The datum + * must have been laid out with VARHDRSZ_COMPRESSED_LONG bytes of header. + */ +static inline void +VARDATA_COMPRESSED_SET_TCINFO_LONG(void *PTR, uint32 extsize, uint8 cmethod) +{ + varattrib_4b_long *va = (varattrib_4b_long *) PTR; + + Assert(extsize > 0 && extsize <= VARLENA_EXTSIZE_MASK); + + va->va_tcinfo = extsize | + ((uint32) VARLENA_COMPRESS_METHOD_LONG << VARLENA_EXTSIZE_BITS); + va->va_cmid = cmethod; +} + /* * Same for the saved size of external Datums, stored in va_extinfo. * - * There is deliberately no equivalent for the compression method: decode the - * pointer with toast_external_info_get() and use its compress_method field. + * There is deliberately no equivalent for the compression method: in the long + * form of a TOAST pointer it lives in a separate byte, so it cannot be + * derived from va_extinfo alone. Decode the pointer with + * toast_external_info_get() and use its compress_method field instead. */ static inline Size VARATT_EXTINFO_GET_EXTSIZE(uint32 extinfo) diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 656f1f60862..771fa598183 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -4419,6 +4419,7 @@ varatt_indirect varattrib_1b varattrib_1b_e varattrib_4b +varattrib_4b_long varlena vartag_external vbits -- 2.54.0 (Apple Git-157)