From b9d6f5ab89ea9f4e8565bdeb286751d3270bf32c Mon Sep 17 00:00:00 2001 From: Nikhil Kumar Veldanda Date: Thu, 5 Jun 2025 03:44:47 +0000 Subject: [PATCH v25 2/3] Design to extend the varattrib_4b/varatt_external to support of multiple TOAST compression algorithms. --- contrib/amcheck/verify_heapam.c | 2 +- src/backend/access/common/detoast.c | 6 +- src/backend/access/common/toast_internals.c | 10 +- src/backend/access/table/toast_helper.c | 4 +- src/include/access/detoast.h | 10 +- src/include/access/toast_compression.h | 26 ++-- src/include/access/toast_internals.h | 36 ++--- src/include/varatt.h | 145 +++++++++++++++++--- src/tools/pgindent/typedefs.list | 1 - 9 files changed, 176 insertions(+), 64 deletions(-) diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c index aa9cccd1da4..2161d129502 100644 --- a/contrib/amcheck/verify_heapam.c +++ b/contrib/amcheck/verify_heapam.c @@ -1786,7 +1786,7 @@ check_tuple_attribute(HeapCheckContext *ctx) bool valid = false; /* Compressed attributes should have a valid compression method */ - cmid = TOAST_COMPRESS_METHOD(&toast_pointer); + cmid = VARATT_EXTERNAL_GET_COMPRESS_METHOD(toast_pointer); switch (cmid) { /* List of all valid compression method IDs */ diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c index 62651787742..01419d1c65f 100644 --- a/src/backend/access/common/detoast.c +++ b/src/backend/access/common/detoast.c @@ -478,7 +478,7 @@ toast_decompress_datum(struct varlena *attr) * Fetch the compression method id stored in the compression header and * decompress the data using the appropriate decompression routine. */ - cmid = TOAST_COMPRESS_METHOD(attr); + cmid = VARDATA_COMPRESSED_GET_COMPRESS_METHOD(attr); switch (cmid) { case TOAST_PGLZ_COMPRESSION_ID: @@ -514,14 +514,14 @@ toast_decompress_datum_slice(struct varlena *attr, int32 slicelength) * have been seen to give wrong results if passed an output size that is * more than the data's true decompressed size. */ - if ((uint32) slicelength >= TOAST_COMPRESS_EXTSIZE(attr)) + if ((uint32) slicelength >= VARDATA_COMPRESSED_GET_EXTSIZE(attr)) return toast_decompress_datum(attr); /* * Fetch the compression method id stored in the compression header and * decompress the data slice using the appropriate decompression routine. */ - cmid = TOAST_COMPRESS_METHOD(attr); + cmid = VARDATA_COMPRESSED_GET_COMPRESS_METHOD(attr); switch (cmid) { case TOAST_PGLZ_COMPRESSION_ID: diff --git a/src/backend/access/common/toast_internals.c b/src/backend/access/common/toast_internals.c index 7d8be8346ce..32653af2e9e 100644 --- a/src/backend/access/common/toast_internals.c +++ b/src/backend/access/common/toast_internals.c @@ -143,6 +143,7 @@ toast_save_datum(Relation rel, Datum value, Pointer dval = DatumGetPointer(value); int num_indexes; int validIndex; + ToastCompressionId cm = TOAST_INVALID_COMPRESSION_ID; Assert(!VARATT_IS_EXTERNAL(value)); @@ -183,10 +184,11 @@ toast_save_datum(Relation rel, Datum value, data_todo = VARSIZE(dval) - VARHDRSZ; /* rawsize in a compressed datum is just the size of the payload */ toast_pointer.va_rawsize = VARDATA_COMPRESSED_GET_EXTSIZE(dval) + VARHDRSZ; + cm = VARDATA_COMPRESSED_GET_COMPRESS_METHOD(dval); /* set external size and compression method */ - VARATT_EXTERNAL_SET_SIZE_AND_COMPRESS_METHOD(toast_pointer, data_todo, - VARDATA_COMPRESSED_GET_COMPRESS_METHOD(dval)); + VARATT_EXTERNAL_SET_SIZE_AND_COMPRESS_METHOD(toast_pointer, data_todo, cm); + /* Assert that the numbers look like it's compressed */ Assert(VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer)); } @@ -368,9 +370,9 @@ toast_save_datum(Relation rel, Datum value, /* * Create the TOAST pointer value that we'll return */ - result = (struct varlena *) palloc(TOAST_POINTER_SIZE); + result = (struct varlena *) palloc(TOAST_POINTER_SIZE(cm)); SET_VARTAG_EXTERNAL(result, VARTAG_ONDISK); - memcpy(VARDATA_EXTERNAL(result), &toast_pointer, sizeof(toast_pointer)); + memcpy(VARDATA_EXTERNAL(result), &toast_pointer, TOAST_POINTER_SIZE(cm) - VARHDRSZ_EXTERNAL); return PointerGetDatum(result); } diff --git a/src/backend/access/table/toast_helper.c b/src/backend/access/table/toast_helper.c index b60fab0a4d2..5a52bb1b67f 100644 --- a/src/backend/access/table/toast_helper.c +++ b/src/backend/access/table/toast_helper.c @@ -171,7 +171,7 @@ toast_tuple_init(ToastTupleContext *ttc) * The column must have attstorage EXTERNAL or EXTENDED if check_main is * false, and must have attstorage MAIN if check_main is true. * - * The column must have a minimum size of MAXALIGN(TOAST_POINTER_SIZE); + * The column must have a minimum size of MAXALIGN(TOAST_POINTER_NOEXT_SIZE); * if not, no benefit is to be expected by compressing it. * * The return value is the index of the biggest suitable column, or @@ -184,7 +184,7 @@ toast_tuple_find_biggest_attribute(ToastTupleContext *ttc, TupleDesc tupleDesc = ttc->ttc_rel->rd_att; int numAttrs = tupleDesc->natts; int biggest_attno = -1; - int32 biggest_size = MAXALIGN(TOAST_POINTER_SIZE); + int32 biggest_size = MAXALIGN(TOAST_POINTER_NOEXT_SIZE); int32 skip_colflags = TOASTCOL_IGNORE; int i; diff --git a/src/include/access/detoast.h b/src/include/access/detoast.h index e603a2276c3..ca8abaad644 100644 --- a/src/include/access/detoast.h +++ b/src/include/access/detoast.h @@ -23,12 +23,16 @@ do { \ varattrib_1b_e *attre = (varattrib_1b_e *) (attr); \ Assert(VARATT_IS_EXTERNAL(attre)); \ - Assert(VARSIZE_EXTERNAL(attre) == sizeof(toast_pointer) + VARHDRSZ_EXTERNAL); \ - memcpy(&(toast_pointer), VARDATA_EXTERNAL(attre), sizeof(toast_pointer)); \ + memset(&(toast_pointer), 0, sizeof(toast_pointer)); \ + memcpy(&(toast_pointer), VARDATA_EXTERNAL(attre), VARSIZE_EXTERNAL(attre) - VARHDRSZ_EXTERNAL); \ } while (0) /* Size of an EXTERNAL datum that contains a standard TOAST pointer */ -#define TOAST_POINTER_SIZE (VARHDRSZ_EXTERNAL + sizeof(varatt_external)) +#define TOAST_POINTER_NOEXT_SIZE (VARHDRSZ_EXTERNAL + offsetof(varatt_external, extended)) +#define TOAST_POINTER_EXT_SIZE (TOAST_POINTER_NOEXT_SIZE + MEMBER_SIZE(varatt_external, extended.cmp)) + +#define TOAST_POINTER_SIZE(cm) \ + (TOAST_CMPID_EXTENDED(cm) ? TOAST_POINTER_EXT_SIZE : TOAST_POINTER_NOEXT_SIZE) /* Size of an EXTERNAL datum that contains an indirection pointer */ #define INDIRECT_POINTER_SIZE (VARHDRSZ_EXTERNAL + sizeof(varatt_indirect)) diff --git a/src/include/access/toast_compression.h b/src/include/access/toast_compression.h index 13c4612ceed..62b77edf372 100644 --- a/src/include/access/toast_compression.h +++ b/src/include/access/toast_compression.h @@ -23,16 +23,21 @@ 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. * - * 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. + * For TOAST-compressed values: + * - If using a non-extended method, the first 2 bits of the raw length + * field store this ID. + * - If using an extended method, it is stored in the extended 1-byte header. + * + * For varlena attributes using extended compression (varatt_external and varattr_4b): + * - The compression method ID occupies the first seven bits of va_extinfo. + * + * These IDs map directly to the built-in compression methods. + * + * Note: Do not use these values for anything other than interpreting the + * raw bits from a varlena. To identify a compression method in code, use + * the named constants (e.g., TOAST_PGLZ_COMPRESSION) instead. */ typedef enum ToastCompressionId { @@ -51,6 +56,9 @@ typedef enum ToastCompressionId #define InvalidCompressionMethod '\0' #define CompressionMethodIsValid(cm) ((cm) != InvalidCompressionMethod) +#define TOAST_CMPID_EXTENDED(cmpid) (!(cmpid == TOAST_PGLZ_COMPRESSION_ID || \ + cmpid == TOAST_LZ4_COMPRESSION_ID || \ + cmpid == TOAST_INVALID_COMPRESSION_ID)) /* pglz compression/decompression routines */ diff --git a/src/include/access/toast_internals.h b/src/include/access/toast_internals.h index 06ae8583c1e..857b53431c8 100644 --- a/src/include/access/toast_internals.h +++ b/src/include/access/toast_internals.h @@ -17,32 +17,24 @@ #include "utils/relcache.h" #include "utils/snapshot.h" -/* - * The information at the start of the compressed toast data. - */ -typedef struct toast_compress_header -{ - int32 vl_len_; /* varlena header (do not touch directly!) */ - uint32 tcinfo; /* 2 bits for compression method and 30 bits - * external size; see va_extinfo */ -} toast_compress_header; - /* * Utilities for manipulation of header information for compressed * toast entries. */ -#define TOAST_COMPRESS_EXTSIZE(ptr) \ - (((toast_compress_header *) (ptr))->tcinfo & VARLENA_EXTSIZE_MASK) -#define TOAST_COMPRESS_METHOD(ptr) \ - (((toast_compress_header *) (ptr))->tcinfo >> VARLENA_EXTSIZE_BITS) - -#define TOAST_COMPRESS_SET_SIZE_AND_COMPRESS_METHOD(ptr, len, cm_method) \ - do { \ - Assert((len) > 0 && (len) <= VARLENA_EXTSIZE_MASK); \ - Assert((cm_method) == TOAST_PGLZ_COMPRESSION_ID || \ - (cm_method) == TOAST_LZ4_COMPRESSION_ID); \ - ((toast_compress_header *) (ptr))->tcinfo = \ - (len) | ((uint32) (cm_method) << VARLENA_EXTSIZE_BITS); \ +#define TOAST_COMPRESS_SET_SIZE_AND_COMPRESS_METHOD(ptr, len, cm_method) \ + do { \ + Assert((len) > 0 && (len) <= VARLENA_EXTSIZE_MASK); \ + Assert((cm_method) == TOAST_PGLZ_COMPRESSION_ID || \ + (cm_method) == TOAST_LZ4_COMPRESSION_ID); \ + if (!TOAST_CMPID_EXTENDED((cm_method))) \ + ((varattrib_4b *)(ptr))->va_compressed.va_tcinfo = ((uint32)(len)) | ((uint32)(cm_method) << VARLENA_EXTSIZE_BITS); \ + else \ + { \ + /* extended path: mark EXT flag in tcinfo */ \ + ((varattrib_4b *)(ptr))->va_compressed_ext.va_tcinfo = \ + ((uint32)(len)) | ((uint32)(VARATT_4BCE_EXTFLAG) << VARLENA_EXTSIZE_BITS); \ + VARATT_4BCE_SET_COMPRESS_METHOD(((varattrib_4b *)(ptr))->va_compressed_ext.va_ecinfo, (cm_method)); \ + } \ } while (0) extern Datum toast_compress_datum(Datum value, char cmethod); diff --git a/src/include/varatt.h b/src/include/varatt.h index 2e8564d4998..39dcfc4b4b8 100644 --- a/src/include/varatt.h +++ b/src/include/varatt.h @@ -28,14 +28,28 @@ * you need to memcpy from the tuple into a local struct variable before * you can look at these fields! (The reason we use memcmp is to avoid * having to do that just to detect equality of two TOAST pointers...) + * + * Optional trailer (only when va_extinfo top bits = 11): + * extended.cmp.va_ecinfo – 1 byte where: + * 1. Bits 7–1 encode (cmid − 2), so cmid ∈ [2…129]. + * 2. Bit 0 is a flag indicating if the algorithm expects extra metadata. */ typedef struct varatt_external { int32 va_rawsize; /* Original data size (includes header) */ uint32 va_extinfo; /* External saved size (without header) and - * compression method */ + * compression method or VARATT_4BCE_EXTFLAG + * flag */ Oid va_valueid; /* Unique ID of value within TOAST table */ Oid va_toastrelid; /* RelID of TOAST table containing it */ + /* -------- optional trailer -------- */ + union + { + struct /* compression-method trailer */ + { + uint8 va_ecinfo; /* Extended compression methods info */ + } cmp; + } extended; /* "extended" = optional bytes */ } varatt_external; /* @@ -93,11 +107,18 @@ typedef enum vartag_external #define VARTAG_IS_EXPANDED(tag) \ (((tag) & ~1) == VARTAG_EXPANDED_RO) -#define VARTAG_SIZE(tag) \ - ((tag) == VARTAG_INDIRECT ? sizeof(varatt_indirect) : \ - VARTAG_IS_EXPANDED(tag) ? sizeof(varatt_expanded) : \ - (tag) == VARTAG_ONDISK ? sizeof(varatt_external) : \ - (AssertMacro(false), 0)) +#define MEMBER_SIZE(type, member) sizeof( ((type *)0)->member ) + +#define VARTAG_SIZE(PTR) \ +( \ + VARTAG_EXTERNAL(PTR) == VARTAG_INDIRECT ? sizeof(varatt_indirect) : \ + VARTAG_IS_EXPANDED(VARTAG_EXTERNAL(PTR)) ? sizeof(varatt_expanded) : \ + VARTAG_EXTERNAL(PTR) == VARTAG_ONDISK ? \ + (offsetof(varatt_external, extended) + \ + ((READ_U32_UNALIGNED((const uint8 *)(PTR) + VARHDRSZ_EXTERNAL + \ + offsetof(varatt_external, va_extinfo)) >> VARLENA_EXTSIZE_BITS) == VARATT_4BCE_EXTFLAG \ + ? MEMBER_SIZE(varatt_external, extended.cmp) : 0)) : (AssertMacro(false), 0) \ +) /* * These structs describe the header of a varlena object that may have been @@ -122,6 +143,17 @@ typedef union * compression method; see va_extinfo */ char va_data[FLEXIBLE_ARRAY_MEMBER]; /* Compressed data */ } va_compressed; + struct + { + uint32 va_header; + uint32 va_tcinfo; /* Original data size (excludes header) and + * compression method or VARATT_4BCE_EXTFLAG + * flag; see va_extinfo */ + uint8 va_ecinfo; /** va_ecinfo – 1 byte where: + * 1. Bits 7–1 encode (cmid − 2), so cmid ∈ [2…129]. + * 2. Bit 0 is a flag indicating if the algorithm expects extra metadata. */ + char va_data[FLEXIBLE_ARRAY_MEMBER]; + } va_compressed_ext; } varattrib_4b; typedef struct @@ -206,6 +238,18 @@ typedef struct (((varattrib_1b_e *) (PTR))->va_header = 0x80, \ ((varattrib_1b_e *) (PTR))->va_tag = (tag)) +/** + * Safely read a 32-bit unsigned integer from *any* address, even when + * that address is **not** naturally aligned to 4 bytes. We do the load + * one byte at a time and re-assemble the word in *host* byte order. + * For BIG ENDIAN systems. + */ +#define READ_U32_UNALIGNED(ptr) \ + ( (uint32) (((const uint8 *)(ptr))[3]) \ + | ((uint32)(((const uint8 *)(ptr))[2]) << 8) \ + | ((uint32)(((const uint8 *)(ptr))[1]) << 16) \ + | ((uint32)(((const uint8 *)(ptr))[0]) << 24) ) + #else /* !WORDS_BIGENDIAN */ #define VARATT_IS_4B(PTR) \ @@ -238,6 +282,17 @@ typedef struct #define SET_VARTAG_1B_E(PTR,tag) \ (((varattrib_1b_e *) (PTR))->va_header = 0x01, \ ((varattrib_1b_e *) (PTR))->va_tag = (tag)) +/** + * Safely read a 32-bit unsigned integer from *any* address, even when + * that address is **not** naturally aligned to 4 bytes. We do the load + * one byte at a time and re-assemble the word in *host* byte order. + * For LITTLE ENDIAN systems + */ +#define READ_U32_UNALIGNED(ptr) \ + ( (uint32) (((const uint8 *)(ptr))[0]) \ + | ((uint32)(((const uint8 *)(ptr))[1]) << 8) \ + | ((uint32)(((const uint8 *)(ptr))[2]) << 16) \ + | ((uint32)(((const uint8 *)(ptr))[3]) << 24) ) #endif /* WORDS_BIGENDIAN */ @@ -282,7 +337,7 @@ typedef struct #define VARDATA_SHORT(PTR) VARDATA_1B(PTR) #define VARTAG_EXTERNAL(PTR) VARTAG_1B_E(PTR) -#define VARSIZE_EXTERNAL(PTR) (VARHDRSZ_EXTERNAL + VARTAG_SIZE(VARTAG_EXTERNAL(PTR))) +#define VARSIZE_EXTERNAL(PTR) (VARHDRSZ_EXTERNAL + VARTAG_SIZE(PTR)) #define VARDATA_EXTERNAL(PTR) VARDATA_1B_E(PTR) #define VARATT_IS_COMPRESSED(PTR) VARATT_IS_4B_C(PTR) @@ -325,23 +380,38 @@ typedef struct (VARATT_IS_1B(PTR) ? VARDATA_1B(PTR) : VARDATA_4B(PTR)) /* Decompressed size and compression method of a compressed-in-line Datum */ -#define VARDATA_COMPRESSED_GET_EXTSIZE(PTR) \ - (((varattrib_4b *) (PTR))->va_compressed.va_tcinfo & VARLENA_EXTSIZE_MASK) +#define VARDATA_COMPRESSED_GET_EXTSIZE(PTR) \ + ( \ + (VARATT_IS_4BCE(PTR)) \ + ? ( ((varattrib_4b *)(PTR))->va_compressed_ext.va_tcinfo & VARLENA_EXTSIZE_MASK ) \ + : ( ((varattrib_4b *)(PTR))->va_compressed.va_tcinfo & VARLENA_EXTSIZE_MASK ) \ + ) #define VARDATA_COMPRESSED_GET_COMPRESS_METHOD(PTR) \ - (((varattrib_4b *) (PTR))->va_compressed.va_tcinfo >> VARLENA_EXTSIZE_BITS) + ( (VARATT_IS_4BCE(PTR)) ? VARATT_4BCE_GET_COMPRESS_METHOD(((varattrib_4b *) (PTR))->va_compressed_ext.va_ecinfo) \ + : (((varattrib_4b *) (PTR))->va_compressed.va_tcinfo >> VARLENA_EXTSIZE_BITS)) /* Same for external Datums; but note argument is a struct varatt_external */ #define VARATT_EXTERNAL_GET_EXTSIZE(toast_pointer) \ ((toast_pointer).va_extinfo & VARLENA_EXTSIZE_MASK) -#define VARATT_EXTERNAL_GET_COMPRESS_METHOD(toast_pointer) \ - ((toast_pointer).va_extinfo >> VARLENA_EXTSIZE_BITS) - -#define VARATT_EXTERNAL_SET_SIZE_AND_COMPRESS_METHOD(toast_pointer, len, cm) \ - do { \ - Assert((cm) == TOAST_PGLZ_COMPRESSION_ID || \ - (cm) == TOAST_LZ4_COMPRESSION_ID); \ - ((toast_pointer).va_extinfo = \ - (len) | ((uint32) (cm) << VARLENA_EXTSIZE_BITS)); \ +#define VARATT_EXTERNAL_GET_COMPRESS_METHOD(toast_pointer) \ + ( ((toast_pointer).va_extinfo >> VARLENA_EXTSIZE_BITS) == VARATT_4BCE_EXTFLAG \ + ? VARATT_4BCE_GET_COMPRESS_METHOD((toast_pointer).extended.cmp.va_ecinfo) \ + : (toast_pointer).va_extinfo >> VARLENA_EXTSIZE_BITS ) + +#define VARATT_EXTERNAL_SET_SIZE_AND_COMPRESS_METHOD(toast_pointer, len, cm) \ + do { \ + Assert((cm) == TOAST_PGLZ_COMPRESSION_ID || \ + (cm) == TOAST_LZ4_COMPRESSION_ID); \ + if (!TOAST_CMPID_EXTENDED((cm))) \ + /* method fits in the low bits of va_extinfo */ \ + (toast_pointer).va_extinfo = (uint32)(len) | ((uint32) (cm) << VARLENA_EXTSIZE_BITS); \ + else \ + { \ + /* set “extended” flag and store the extra byte */ \ + (toast_pointer).va_extinfo = (uint32)(len) | \ + (VARATT_4BCE_EXTFLAG << VARLENA_EXTSIZE_BITS); \ + VARATT_4BCE_SET_COMPRESS_METHOD((toast_pointer).extended.cmp.va_ecinfo, (cm)); \ + } \ } while (0) /* @@ -355,4 +425,41 @@ typedef struct (VARATT_EXTERNAL_GET_EXTSIZE(toast_pointer) < \ (toast_pointer).va_rawsize - VARHDRSZ) +/* Upper-two-bit pattern 0b11 marks “extended compression methods used. */ +#define VARATT_4BCE_EXTFLAG 0x3 + +/* + * Layout of the extra 1-byte trailer for extended compression info: + * + * bit 7 6 5 4 3 2 1 0 + * +---+---+---+---+---+---+---+---+ + * | cmid_minus2 | F | + * +---+---+---+---+---+---+---+---+ + * + * • Bits 7–1 (cmid_minus2): + * 7-bit field holding (cmid − 2). The actual compression‐method ID (cmid) + * is (raw + 2), so raw ∈ [0…127] maps to cmid ∈ [2…129]. + * + * • Bit 0 (F): + * Single flag bit reserved for indicating whether this compression method has associated metadata. + */ +#define VARATT_4BCE_SET_COMPRESS_METHOD(va_ecinfo, cmid) \ + do { \ + bool meta = false; \ + (va_ecinfo) = (uint8)((((cmid) - 2) << 1) | ((meta) & 0x01)); \ + } while (0) + +#define VARATT_4BCE_GET_COMPRESS_METHOD(raw) ((((raw) >> 1) & 0x7F) + 2) + +/* Does this varattrib use the “compressed-extended” format? */ +#define VARATT_IS_4BCE(ptr) \ + ((((varattrib_4b *)(ptr))->va_compressed_ext.va_tcinfo >> VARLENA_EXTSIZE_BITS) \ + == VARATT_4BCE_EXTFLAG) + +/* Access the start of the compressed payload */ +#define VARDATA_4BCE(ptr) \ + (((varattrib_4b *)(ptr))->va_compressed_ext.va_data) + +#define VARHDRSZ_4BCE (offsetof(varattrib_4b, va_compressed_ext.va_data)) + #endif diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index a8346cda633..eb53118b72b 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -4097,7 +4097,6 @@ timeout_handler_proc timeout_params timerCA tlist_vinfo -toast_compress_header tokenize_error_callback_arg transferMode transfer_thread_arg -- 2.47.1