From 098dce72e7f73f91ee04447c589da1177ec49bd7 Mon Sep 17 00:00:00 2001 From: Nikhil Kumar Veldanda Date: Sat, 19 Jul 2025 23:07:41 +0000 Subject: [PATCH v26 14/15] Design to extend the varattrib_4b and toast pointer to support of multiple TOAST compression algorithms. --- contrib/amcheck/verify_heapam.c | 5 +- src/backend/access/common/detoast.c | 6 +- src/backend/access/common/toast_external.c | 176 ++++++++++++++++++-- src/backend/access/common/toast_internals.c | 27 ++- src/backend/access/heap/heaptoast.c | 49 ++---- src/backend/access/table/toast_helper.c | 37 +++- src/include/access/toast_compression.h | 6 + src/include/access/toast_helper.h | 4 +- src/include/access/toast_internals.h | 19 +-- src/include/varatt.h | 115 +++++++++++-- 10 files changed, 335 insertions(+), 109 deletions(-) diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c index 958b1451b4f..3a23dddcff4 100644 --- a/contrib/amcheck/verify_heapam.c +++ b/contrib/amcheck/verify_heapam.c @@ -1733,7 +1733,10 @@ check_tuple_attribute(HeapCheckContext *ctx) { uint8 va_tag = VARTAG_EXTERNAL(tp + ctx->offset); - if (va_tag != VARTAG_ONDISK_OID && va_tag != VARTAG_ONDISK_INT8) + if (va_tag != VARTAG_ONDISK_OID && + va_tag != VARTAG_ONDISK_INT8 && + va_tag != VARTAG_ONDISK_CE_INT8 && + va_tag != VARTAG_ONDISK_CE_OID) { report_corruption(ctx, psprintf("toasted attribute has unexpected TOAST tag %u", diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c index 684e1b0b7d3..34a3f7c6694 100644 --- a/src/backend/access/common/detoast.c +++ b/src/backend/access/common/detoast.c @@ -484,7 +484,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: @@ -520,14 +520,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_external.c b/src/backend/access/common/toast_external.c index 0e79ac8acae..5bf17ed7182 100644 --- a/src/backend/access/common/toast_external.c +++ b/src/backend/access/common/toast_external.c @@ -38,6 +38,15 @@ static struct varlena *ondisk_oid_create_external_data(toast_external_data data) static uint64 ondisk_oid_get_new_value(Relation toastrel, Oid indexid, AttrNumber attnum); +/* Callbacks for VARTAG_ONDISK_CE_OID */ +static void ondisk_ce_oid_to_external_data(struct varlena *attr, + toast_external_data *data); +static struct varlena *ondisk_ce_oid_create_external_data(toast_external_data data); + +/* Callbacks for VARTAG_ONDISK_CE_INT8 */ +static void ondisk_ce_int8_to_external_data(struct varlena *attr, + toast_external_data *data); +static struct varlena *ondisk_ce_int8_create_external_data(toast_external_data data); /* * Size of an EXTERNAL datum that contains a standard TOAST pointer @@ -51,6 +60,18 @@ static uint64 ondisk_oid_get_new_value(Relation toastrel, Oid indexid, */ #define TOAST_POINTER_OID_SIZE (VARHDRSZ_EXTERNAL + sizeof(varatt_external_oid)) +/* + * Size of an EXTERNAL datum that contains a TOAST pointer which supports extended compression methods + * (OID value). + */ +#define TOAST_POINTER_CE_OID_SIZE (VARHDRSZ_EXTERNAL + sizeof(varatt_external_ce_oid)) + +/* + * Size of an EXTERNAL datum that contains a TOAST pointer which supports extended compression methods + * (int8 value). + */ +#define TOAST_POINTER_CE_INT8_SIZE (VARHDRSZ_EXTERNAL + sizeof(varatt_external_ce_int8)) + /* * For now there are only two types, all defined in this file. For now this * is the maximum value of vartag_external, which is a historical choice. @@ -72,6 +93,13 @@ static const toast_external_info toast_external_infos[TOAST_EXTERNAL_INFO_SIZE] .create_external_data = ondisk_int8_create_external_data, .get_new_value = ondisk_int8_get_new_value, }, + [VARTAG_ONDISK_CE_INT8] = { + .toast_pointer_size = TOAST_POINTER_CE_INT8_SIZE, + .maximum_chunk_size = TOAST_MAX_CHUNK_SIZE_INT8, + .to_external_data = ondisk_ce_int8_to_external_data, + .create_external_data = ondisk_ce_int8_create_external_data, + .get_new_value = ondisk_int8_get_new_value, + }, [VARTAG_ONDISK_OID] = { .toast_pointer_size = TOAST_POINTER_OID_SIZE, .maximum_chunk_size = TOAST_MAX_CHUNK_SIZE_OID, @@ -79,6 +107,13 @@ static const toast_external_info toast_external_infos[TOAST_EXTERNAL_INFO_SIZE] .create_external_data = ondisk_oid_create_external_data, .get_new_value = ondisk_oid_get_new_value, }, + [VARTAG_ONDISK_CE_OID] = { + .toast_pointer_size = TOAST_POINTER_CE_OID_SIZE, + .maximum_chunk_size = TOAST_MAX_CHUNK_SIZE_OID, + .to_external_data = ondisk_ce_oid_to_external_data, + .create_external_data = ondisk_ce_oid_create_external_data, + .get_new_value = ondisk_oid_get_new_value, + }, }; @@ -108,7 +143,7 @@ toast_external_info_get_pointer_size(uint8 tag) static void ondisk_int8_to_external_data(struct varlena *attr, toast_external_data *data) { - varatt_external_int8 external; + varatt_external_int8 external; VARATT_EXTERNAL_GET_POINTER(external, attr); data->rawsize = external.va_rawsize; @@ -117,7 +152,7 @@ ondisk_int8_to_external_data(struct varlena *attr, toast_external_data *data) if (VARATT_EXTERNAL_IS_COMPRESSED(external)) { data->extsize = VARATT_EXTERNAL_GET_EXTSIZE(external); - data->compression_method = VARATT_EXTERNAL_GET_COMPRESS_METHOD(external); + data->compression_method = external.va_extinfo >> VARLENA_EXTSIZE_BITS; } else { @@ -141,10 +176,10 @@ ondisk_int8_create_external_data(toast_external_data data) if (data.compression_method != TOAST_INVALID_COMPRESSION_ID) { + /* Regular variants only support basic compression methods */ + Assert(!CompressionMethodIdIsExtended(data.compression_method)); /* Set size and compression method, in a single field. */ - VARATT_EXTERNAL_SET_SIZE_AND_COMPRESS_METHOD(external, - data.extsize, - data.compression_method); + external.va_extinfo = (uint32) data.extsize | ((uint32) data.compression_method << VARLENA_EXTSIZE_BITS); } else external.va_extinfo = data.extsize; @@ -165,8 +200,8 @@ ondisk_int8_get_new_value(Relation toastrel, Oid indexid, AttrNumber attnum) { uint64 new_value; - SysScanDesc scan; - ScanKeyData key; + SysScanDesc scan; + ScanKeyData key; bool collides = false; retry: @@ -181,8 +216,8 @@ retry: CHECK_FOR_INTERRUPTS(); /* - * Check if the new value picked already exists in the toast relation. - * If there is a conflict, retry. + * Check if the new value picked already exists in the toast relation. If + * there is a conflict, retry. */ ScanKeyInit(&key, attnum, @@ -206,7 +241,7 @@ retry: static void ondisk_oid_to_external_data(struct varlena *attr, toast_external_data *data) { - varatt_external_oid external; + varatt_external_oid external; VARATT_EXTERNAL_GET_POINTER(external, attr); data->rawsize = external.va_rawsize; @@ -218,7 +253,7 @@ ondisk_oid_to_external_data(struct varlena *attr, toast_external_data *data) if (VARATT_EXTERNAL_IS_COMPRESSED(external)) { data->extsize = VARATT_EXTERNAL_GET_EXTSIZE(external); - data->compression_method = VARATT_EXTERNAL_GET_COMPRESS_METHOD(external); + data->compression_method = external.va_extinfo >> VARLENA_EXTSIZE_BITS; } else { @@ -240,10 +275,10 @@ ondisk_oid_create_external_data(toast_external_data data) if (data.compression_method != TOAST_INVALID_COMPRESSION_ID) { + /* Regular variants only support basic compression methods */ + Assert(!CompressionMethodIdIsExtended(data.compression_method)); /* Set size and compression method, in a single field. */ - VARATT_EXTERNAL_SET_SIZE_AND_COMPRESS_METHOD(external, - data.extsize, - data.compression_method); + external.va_extinfo = (uint32) data.extsize | ((uint32) data.compression_method << VARLENA_EXTSIZE_BITS); } else external.va_extinfo = data.extsize; @@ -264,3 +299,116 @@ ondisk_oid_get_new_value(Relation toastrel, Oid indexid, { return GetNewOidWithIndex(toastrel, indexid, attnum); } + +/* Callbacks for VARTAG_ONDISK_CE_OID */ +static void +ondisk_ce_oid_to_external_data(struct varlena *attr, toast_external_data *data) +{ + varatt_external_ce_oid external; + + VARATT_EXTERNAL_GET_POINTER(external, attr); + data->rawsize = external.va_rawsize; + + /* + * External size and compression methods are stored in the different + * fields, extract. + */ + if (VARATT_EXTERNAL_IS_COMPRESSED(external)) + { + data->extsize = VARATT_EXTERNAL_GET_EXTSIZE(external); + data->compression_method = VARATT_CE_GET_COMPRESS_METHOD(external.va_ecinfo); + } + else + { + data->extsize = external.va_extinfo; + data->compression_method = TOAST_INVALID_COMPRESSION_ID; + } + + data->value = (uint64) external.va_valueid; + data->toastrelid = external.va_toastrelid; +} + +static struct varlena * +ondisk_ce_oid_create_external_data(toast_external_data data) +{ + struct varlena *result = NULL; + varatt_external_ce_oid external; + + external.va_rawsize = data.rawsize; + + if (data.compression_method != TOAST_INVALID_COMPRESSION_ID) + { + Assert(CompressionMethodIdIsExtended(data.compression_method)); + /* Set size and compression method. */ + external.va_extinfo = (uint32) data.extsize | (VARATT_CE_FLAG << VARLENA_EXTSIZE_BITS); + VARATT_CE_SET_COMPRESS_METHOD(external.va_ecinfo, data.compression_method); + } + else + external.va_extinfo = data.extsize; + + external.va_toastrelid = data.toastrelid; + external.va_valueid = (Oid) data.value; + + result = (struct varlena *) palloc(TOAST_POINTER_CE_OID_SIZE); + SET_VARTAG_EXTERNAL(result, VARTAG_ONDISK_CE_OID); + memcpy(VARDATA_EXTERNAL(result), &external, sizeof(external)); + + return result; +} + +/* Callbacks for VARTAG_ONDISK_CE_INT8 */ +static void +ondisk_ce_int8_to_external_data(struct varlena *attr, toast_external_data *data) +{ + varatt_external_ce_int8 external; + + VARATT_EXTERNAL_GET_POINTER(external, attr); + data->rawsize = external.va_rawsize; + + /* + * External size and compression methods are stored in the different + * fields + */ + if (VARATT_EXTERNAL_IS_COMPRESSED(external)) + { + data->extsize = VARATT_EXTERNAL_GET_EXTSIZE(external); + data->compression_method = VARATT_CE_GET_COMPRESS_METHOD(external.va_ecinfo); + } + else + { + data->extsize = external.va_extinfo; + data->compression_method = TOAST_INVALID_COMPRESSION_ID; + } + + data->value = (((uint64) external.va_valueid_hi) << 32) | + external.va_valueid_lo; + data->toastrelid = external.va_toastrelid; +} + +static struct varlena * +ondisk_ce_int8_create_external_data(toast_external_data data) +{ + struct varlena *result = NULL; + varatt_external_ce_int8 external; + + external.va_rawsize = data.rawsize; + + if (data.compression_method != TOAST_INVALID_COMPRESSION_ID) + { + /* Set size and compression method. */ + external.va_extinfo = (uint32) data.extsize | (VARATT_CE_FLAG << VARLENA_EXTSIZE_BITS); + VARATT_CE_SET_COMPRESS_METHOD(external.va_ecinfo, data.compression_method); + } + else + external.va_extinfo = data.extsize; + + external.va_toastrelid = data.toastrelid; + external.va_valueid_hi = (((uint64) data.value) >> 32); + external.va_valueid_lo = (uint32) data.value; + + result = (struct varlena *) palloc(TOAST_POINTER_CE_INT8_SIZE); + SET_VARTAG_EXTERNAL(result, VARTAG_ONDISK_CE_INT8); + memcpy(VARDATA_EXTERNAL(result), &external, sizeof(external)); + + return result; +} diff --git a/src/backend/access/common/toast_internals.c b/src/backend/access/common/toast_internals.c index c6b2d1522ce..468aae64676 100644 --- a/src/backend/access/common/toast_internals.c +++ b/src/backend/access/common/toast_internals.c @@ -160,18 +160,6 @@ toast_save_datum(Relation rel, Datum value, toast_typid = TupleDescAttr(toasttupDesc, 0)->atttypid; Assert(toast_typid == OIDOID || toast_typid == INT8OID); - /* - * Grab the information for toast_external_data. - * - * Note: if we support multiple external vartags for a single value - * type, we would need to be smarter in the vartag selection. - */ - if (toast_typid == OIDOID) - tag = VARTAG_ONDISK_OID; - else if (toast_typid == INT8OID) - tag = VARTAG_ONDISK_INT8; - info = toast_external_get_info(tag); - /* Open all the toast indexes and look for the valid one */ validIndex = toast_open_indexes(toastrel, RowExclusiveLock, @@ -242,6 +230,18 @@ toast_save_datum(Relation rel, Datum value, else toast_pointer.toastrelid = RelationGetRelid(toastrel); + /* + * Grab the information for toast_external_data. + * + * Note: if we support multiple external vartags for a single value type, + * we would need to be smarter in the vartag selection. + */ + if (toast_typid == OIDOID) + tag = CompressionMethodIdIsExtended(toast_pointer.compression_method) ? VARTAG_ONDISK_CE_OID : VARTAG_ONDISK_OID; + else if (toast_typid == INT8OID) + tag = CompressionMethodIdIsExtended(toast_pointer.compression_method) ? VARTAG_ONDISK_CE_INT8 : VARTAG_ONDISK_INT8; + info = toast_external_get_info(tag); + /* * Choose a new value to use as the value ID for this toast value, be it * for OID or int8-based TOAST relations. @@ -254,8 +254,7 @@ toast_save_datum(Relation rel, Datum value, * 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. If the TOAST - * table uses 8-byte value IDs, we should not really care much about - * that. + * table uses 8-byte value IDs, we should not really care much about that. */ if (!OidIsValid(rel->rd_toastoid)) { diff --git a/src/backend/access/heap/heaptoast.c b/src/backend/access/heap/heaptoast.c index 87f1630d85f..7b03b27341f 100644 --- a/src/backend/access/heap/heaptoast.c +++ b/src/backend/access/heap/heaptoast.c @@ -152,7 +152,7 @@ heap_toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup, { HeapTuple atttuple; Form_pg_attribute atttoast; - uint8 vartag = VARTAG_ONDISK_OID; + ToastTypeId toast_type = TOAST_TYPE_INVALID; /* * XXX: This is very unlikely efficient, but it is not possible to @@ -166,32 +166,22 @@ heap_toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup, atttoast = (Form_pg_attribute) GETSTRUCT(atttuple); if (atttoast->atttypid == OIDOID) - vartag = VARTAG_ONDISK_OID; + toast_type = TOAST_TYPE_OID; else if (atttoast->atttypid == INT8OID) - vartag = VARTAG_ONDISK_INT8; + toast_type = TOAST_TYPE_INT8; else Assert(false); - ttc.ttc_toast_pointer_size = - toast_external_info_get_pointer_size(vartag); + ttc.toast_type = toast_type; ReleaseSysCache(atttuple); } else { /* - * No TOAST relation to rely on, which is a case possible when - * dealing with partitioned tables, for example. Hence, do a best - * guess based on the GUC default_toast_type. + * No TOAST relation to rely on, which is a case possible when dealing + * with partitioned tables, for example. Hence, do a best guess based + * on the GUC default_toast_type. */ - uint8 vartag = VARTAG_ONDISK_OID; - - if (default_toast_type == TOAST_TYPE_INT8) - vartag = VARTAG_ONDISK_INT8; - else if (default_toast_type == TOAST_TYPE_OID) - vartag = VARTAG_ONDISK_OID; - else - Assert(false); - ttc.ttc_toast_pointer_size = - toast_external_info_get_pointer_size(vartag); + ttc.toast_type = default_toast_type; } ttc.ttc_rel = rel; @@ -693,9 +683,7 @@ heap_fetch_toast_slice(Relation toastrel, uint64 valueid, int32 attrsize, int endchunk; int num_indexes; int validIndex; - int32 max_chunk_size; - const toast_external_info *info; - uint8 tag = VARTAG_INDIRECT; /* init value does not matter */ + int32 max_chunk_size = TOAST_MAX_CHUNK_SIZE_OID; Oid toast_typid = InvalidOid; /* Look for the valid index of toast relation */ @@ -708,26 +696,23 @@ heap_fetch_toast_slice(Relation toastrel, uint64 valueid, int32 attrsize, * Grab the information for toast_external_data. * * Note: there is no access to the vartag of the original varlena from - * which we are trying to retrieve the chunks from the TOAST relation, - * so guess the external TOAST pointer information to use depending - * on the attribute of the TOAST value. If we begin to support multiple - * external TOAST pointers for a single attribute type, we would need - * to pass down this information from the upper callers. This is - * currently on required for the maximum chunk_size. + * which we are trying to retrieve the chunks from the TOAST relation, so + * guess the external TOAST pointer information to use depending on the + * attribute of the TOAST value. If we begin to support multiple external + * TOAST pointers for a single attribute type, we would need to pass down + * this information from the upper callers. This is currently on required + * for the maximum chunk_size. */ toast_typid = TupleDescAttr(toastrel->rd_att, 0)->atttypid; Assert(toast_typid == OIDOID || toast_typid == INT8OID); if (toast_typid == OIDOID) - tag = VARTAG_ONDISK_OID; + max_chunk_size = TOAST_MAX_CHUNK_SIZE_OID; else if (toast_typid == INT8OID) - tag = VARTAG_ONDISK_INT8; + max_chunk_size = TOAST_MAX_CHUNK_SIZE_INT8; else Assert(false); - info = toast_external_get_info(tag); - max_chunk_size = info->maximum_chunk_size; - totalchunks = ((attrsize - 1) / max_chunk_size) + 1; startchunk = sliceoffset / max_chunk_size; endchunk = (sliceoffset + slicelength - 1) / max_chunk_size; diff --git a/src/backend/access/table/toast_helper.c b/src/backend/access/table/toast_helper.c index a2b44e093d7..b87d3177bc3 100644 --- a/src/backend/access/table/toast_helper.c +++ b/src/backend/access/table/toast_helper.c @@ -15,6 +15,7 @@ #include "postgres.h" #include "access/detoast.h" +#include "access/toast_external.h" #include "access/toast_helper.h" #include "access/toast_internals.h" #include "catalog/pg_type_d.h" @@ -51,10 +52,19 @@ toast_tuple_init(ToastTupleContext *ttc) Form_pg_attribute att = TupleDescAttr(tupleDesc, i); struct varlena *old_value; struct varlena *new_value; + uint8 vartag = VARTAG_ONDISK_OID; + + if (ttc->toast_type == TOAST_TYPE_OID) + vartag = CompressionMethodIsExtended(att->attcompression) ? VARTAG_ONDISK_CE_OID : VARTAG_ONDISK_OID; + else if (ttc->toast_type == TOAST_TYPE_INT8) + vartag = CompressionMethodIsExtended(att->attcompression) ? VARTAG_ONDISK_CE_INT8 : VARTAG_ONDISK_INT8; + else + Assert(false); ttc->ttc_attr[i].tai_colflags = 0; ttc->ttc_attr[i].tai_oldexternal = NULL; ttc->ttc_attr[i].tai_compression = att->attcompression; + ttc->ttc_attr[i].toast_pointer_size = toast_external_info_get_pointer_size(vartag); if (ttc->ttc_oldvalues != NULL) { @@ -171,10 +181,10 @@ 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(tcc_toast_pointer_size); - * if not, no benefit is to be expected by compressing it. The TOAST - * pointer size is given by the caller, depending on the type of TOAST - * table we are dealing with. + * Each column must have a minimum size of MAXALIGN(toast_pointer_size) for + * that specific column; if not, no benefit is to be expected by compressing it. + * The TOAST pointer size varies per column based on the TOAST table type + * (OID vs INT8) and different variants used for that specific attribute. * * The return value is the index of the biggest suitable column, or * -1 if there is none. @@ -190,16 +200,13 @@ toast_tuple_find_biggest_attribute(ToastTupleContext *ttc, int32 skip_colflags = TOASTCOL_IGNORE; int i; - /* Define the lower-bound */ - biggest_size = MAXALIGN(ttc->ttc_toast_pointer_size); - Assert(biggest_size != 0); - if (for_compression) skip_colflags |= TOASTCOL_INCOMPRESSIBLE; for (i = 0; i < numAttrs; i++) { Form_pg_attribute att = TupleDescAttr(tupleDesc, i); + int32 min_size_for_column; if ((ttc->ttc_attr[i].tai_colflags & skip_colflags) != 0) continue; @@ -214,7 +221,19 @@ toast_tuple_find_biggest_attribute(ToastTupleContext *ttc, att->attstorage != TYPSTORAGE_EXTERNAL) continue; - if (ttc->ttc_attr[i].tai_size > biggest_size) + /* + * Each column has its own minimum size threshold based on its TOAST + * pointer size + */ + min_size_for_column = MAXALIGN(ttc->ttc_attr[i].toast_pointer_size); + Assert(min_size_for_column > 0); + + /* + * Only consider this column if it's bigger than its specific + * threshold AND bigger than current biggest + */ + if (ttc->ttc_attr[i].tai_size > min_size_for_column && + ttc->ttc_attr[i].tai_size > biggest_size) { biggest_attno = i; biggest_size = ttc->ttc_attr[i].tai_size; diff --git a/src/include/access/toast_compression.h b/src/include/access/toast_compression.h index 13c4612ceed..494e1b0dce6 100644 --- a/src/include/access/toast_compression.h +++ b/src/include/access/toast_compression.h @@ -51,6 +51,12 @@ typedef enum ToastCompressionId #define InvalidCompressionMethod '\0' #define CompressionMethodIsValid(cm) ((cm) != InvalidCompressionMethod) +#define CompressionMethodIsExtended(cm) (!(cm == TOAST_PGLZ_COMPRESSION || \ + cm == TOAST_LZ4_COMPRESSION || \ + cm == InvalidCompressionMethod)) +#define CompressionMethodIdIsExtended(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_helper.h b/src/include/access/toast_helper.h index 729c593afeb..e0ae11a581c 100644 --- a/src/include/access/toast_helper.h +++ b/src/include/access/toast_helper.h @@ -14,6 +14,7 @@ #ifndef TOAST_HELPER_H #define TOAST_HELPER_H +#include "access/toast_type.h" #include "utils/rel.h" /* @@ -33,6 +34,7 @@ typedef struct int32 tai_size; uint8 tai_colflags; char tai_compression; + int32 toast_pointer_size; } ToastAttrInfo; /* @@ -47,11 +49,11 @@ typedef struct * should be NULL in the case of an insert. */ Relation ttc_rel; /* the relation that contains the tuple */ - int32 ttc_toast_pointer_size; /* size of external TOAST pointer */ Datum *ttc_values; /* values from the tuple columns */ bool *ttc_isnull; /* null flags for the tuple columns */ Datum *ttc_oldvalues; /* values from previous tuple */ bool *ttc_oldisnull; /* null flags from previous tuple */ + ToastTypeId toast_type; /* toast table type */ /* * Before calling toast_tuple_init, the caller should set ttc_attr to diff --git a/src/include/access/toast_internals.h b/src/include/access/toast_internals.h index 06ae8583c1e..27bc8a0b816 100644 --- a/src/include/access/toast_internals.h +++ b/src/include/access/toast_internals.h @@ -17,32 +17,17 @@ #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); \ + ((varattrib_4b *)(ptr))->va_compressed.va_tcinfo = \ + ((uint32)(len)) | ((uint32)(cm_method) << VARLENA_EXTSIZE_BITS); \ } while (0) extern Datum toast_compress_datum(Datum value, char cmethod); diff --git a/src/include/varatt.h b/src/include/varatt.h index aa36e8e1f56..52b17c349c3 100644 --- a/src/include/varatt.h +++ b/src/include/varatt.h @@ -53,17 +53,41 @@ typedef struct varatt_external_int8 int32 va_rawsize; /* Original data size (includes header) */ uint32 va_extinfo; /* External saved size (without header) and * compression method */ + /* - * Unique ID of value within TOAST table, as two uint32 for alignment - * and padding. - * XXX: think for example about the addition of an extra field for - * meta-data and/or more compression data, even if it's OK here). + * Unique ID of value within TOAST table, as two uint32 for alignment and + * padding. */ uint32 va_valueid_lo; uint32 va_valueid_hi; Oid va_toastrelid; /* RelID of TOAST table containing it */ } varatt_external_int8; +typedef struct varatt_external_ce_oid +{ + int32 va_rawsize; /* Original data size (includes header) */ + uint32 va_extinfo; /* External saved size (without header) and + * VARATT_CE_FLAG in top 2 bits */ + uint32 va_ecinfo; /* Extended compression info */ + Oid va_valueid; /* Unique ID of value within TOAST table */ + Oid va_toastrelid; /* RelID of TOAST table containing it */ +} varatt_external_ce_oid; + +typedef struct varatt_external_ce_int8 +{ + int32 va_rawsize; /* Original data size (includes header) */ + uint32 va_extinfo; /* External saved size (without header) and + * VARATT_CE_FLAG in top 2 bits */ + uint32 va_ecinfo; /* Extended compression info */ + + /* + * Unique ID of value within TOAST table, as two uint32 for alignment and + * padding. + */ + uint32 va_valueid_lo; + uint32 va_valueid_hi; + Oid va_toastrelid; /* RelID of TOAST table containing it */ +} varatt_external_ce_int8; /* * These macros define the "saved size" portion of va_extinfo. Its remaining @@ -115,6 +139,8 @@ typedef enum vartag_external VARTAG_EXPANDED_RO = 2, VARTAG_EXPANDED_RW = 3, VARTAG_ONDISK_INT8 = 4, + VARTAG_ONDISK_CE_OID = 5, + VARTAG_ONDISK_CE_INT8 = 6, VARTAG_ONDISK_OID = 18 } vartag_external; @@ -127,6 +153,8 @@ typedef enum vartag_external VARTAG_IS_EXPANDED(tag) ? sizeof(varatt_expanded) : \ (tag) == VARTAG_ONDISK_OID ? sizeof(varatt_external_oid) : \ (tag) == VARTAG_ONDISK_INT8 ? sizeof(varatt_external_int8) : \ + (tag) == VARTAG_ONDISK_CE_OID ? sizeof(varatt_external_ce_oid): \ + (tag) == VARTAG_ONDISK_CE_INT8 ? sizeof(varatt_external_ce_int8): \ (AssertMacro(false), 0)) /* @@ -152,6 +180,21 @@ 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_CE_FLAG flag; + * see va_extinfo */ + uint32 va_ecinfo; /* Extended compression info: 32-bit field + * where only the lower 8 bits are used for + * compression method. Upper 24 bits are + * reserved/unused. Lower 8 bits layout: Bits + * 7–1: encode (cmid − 2), so cmid is + * [2…129] Bit 0: flag for extra metadata + */ + char va_data[FLEXIBLE_ARRAY_MEMBER]; + } va_compressed_ext; } varattrib_4b; typedef struct @@ -321,8 +364,13 @@ typedef struct (VARATT_IS_EXTERNAL(PTR) && VARTAG_EXTERNAL(PTR) == VARTAG_ONDISK_OID) #define VARATT_IS_EXTERNAL_ONDISK_INT8(PTR) \ (VARATT_IS_EXTERNAL(PTR) && VARTAG_EXTERNAL(PTR) == VARTAG_ONDISK_INT8) +#define VARATT_IS_EXTERNAL_ONDISK_CE_OID(PTR) \ + (VARATT_IS_EXTERNAL(PTR) && VARTAG_EXTERNAL(PTR) == VARTAG_ONDISK_CE_OID) +#define VARATT_IS_EXTERNAL_ONDISK_CE_INT8(PTR) \ + (VARATT_IS_EXTERNAL(PTR) && VARTAG_EXTERNAL(PTR) == VARTAG_ONDISK_CE_INT8) #define VARATT_IS_EXTERNAL_ONDISK(PTR) \ - (VARATT_IS_EXTERNAL_ONDISK_OID(PTR) || VARATT_IS_EXTERNAL_ONDISK_INT8(PTR)) + (VARATT_IS_EXTERNAL_ONDISK_OID(PTR) || VARATT_IS_EXTERNAL_ONDISK_INT8(PTR) \ + || VARATT_IS_EXTERNAL_ONDISK_CE_OID(PTR) || VARATT_IS_EXTERNAL_ONDISK_CE_INT8(PTR)) #define VARATT_IS_EXTERNAL_INDIRECT(PTR) \ (VARATT_IS_EXTERNAL(PTR) && VARTAG_EXTERNAL(PTR) == VARTAG_INDIRECT) #define VARATT_IS_EXTERNAL_EXPANDED_RO(PTR) \ @@ -359,10 +407,15 @@ 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_EXTENDED_COMPRESSED(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_EXTENDED_COMPRESSED(PTR)) ? VARATT_CE_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 @@ -370,16 +423,6 @@ typedef struct */ #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)); \ - } while (0) /* * Testing whether an externally-stored value is compressed now requires @@ -393,5 +436,41 @@ typedef struct (VARATT_EXTERNAL_GET_EXTSIZE(toast_pointer) < \ (toast_pointer).va_rawsize - VARHDRSZ) +/* Extended compression flag (0b11) marks use of extended compression methods */ +#define VARATT_CE_FLAG 0x3 + +/* + * Extended compression info encoding (8-bit layout): + * + * bit 7 6 5 4 3 2 1 0 + * +---+---+---+---+---+---+---+---+ + * | compression_id | M | + * +---+---+---+---+---+---+---+---+ + * + * • Bits 7–1: Compression method ID offset (cmid − 2) + * Range: [0…127] maps to compression ID [2…129] + * + * • Bit 0 (M): Metadata flag (currently unused, always 0) + * Reserved for future use to indicate extra compression metadata + */ +#define VARATT_CE_SET_COMPRESS_METHOD(va_ecinfo, cmid) \ + do { \ + uint8 _cmid = (uint8)(cmid); \ + Assert(_cmid >= 2 && _cmid <= 129); \ + (va_ecinfo) = (uint32)((_cmid - 2) << 1); \ + } while (0) + +#define VARATT_CE_GET_COMPRESS_METHOD(ecinfo) ((((uint8)(ecinfo) >> 1) & 0x7F) + 2) + +/* Test if varattrib_4b uses extended compression format */ +#define VARATT_IS_EXTENDED_COMPRESSED(ptr) \ + ((((varattrib_4b *)(ptr))->va_compressed_ext.va_tcinfo >> VARLENA_EXTSIZE_BITS) \ + == VARATT_CE_FLAG) + +/* Access compressed data payload in extended format */ +#define VARDATA_EXTENDED_COMPRESSED(ptr) \ + (((varattrib_4b *)(ptr))->va_compressed_ext.va_data) + +#define VARHDRSZ_EXTENDED_COMPRESSED (offsetof(varattrib_4b, va_compressed_ext.va_data)) #endif -- 2.47.1