From 27275a6a55857aa7543c4571d3315c6ad462c15c Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Fri, 1 Aug 2025 17:17:51 +0900 Subject: [PATCH v4 11/15] Add support for bigint TOAST values This commit adds the possibility to define TOAST tables with bigint as value ID, based on the GUC default_toast_type. All the external TOAST pointers still rely on varatt_external and a single vartag, with all the values inserted in the bigint TOAST tables fed from the existing OID value generator. This will be changed in an upcoming patch that adds more vartag_external types and its associated structures, with the code being able to use a different external TOAST pointer depending on the attribute type of chunk_id in TOAST relations. All the changes done here are mechanical, with all the TOAST code able to do chunk ID lookups based on the two types supported. XXX: Catalog version bump required. --- src/backend/access/common/toast_internals.c | 67 +++++++++++++++------ src/backend/access/heap/heaptoast.c | 20 ++++-- src/backend/catalog/toasting.c | 32 +++++++++- doc/src/sgml/storage.sgml | 7 ++- contrib/amcheck/verify_heapam.c | 19 ++++-- 5 files changed, 115 insertions(+), 30 deletions(-) diff --git a/src/backend/access/common/toast_internals.c b/src/backend/access/common/toast_internals.c index b0a0ce1e1b9a..98a240242127 100644 --- a/src/backend/access/common/toast_internals.c +++ b/src/backend/access/common/toast_internals.c @@ -18,6 +18,7 @@ #include "access/heapam.h" #include "access/heaptoast.h" #include "access/table.h" +#include "access/toast_counter.h" #include "access/toast_external.h" #include "access/toast_internals.h" #include "access/xact.h" @@ -26,6 +27,7 @@ #include "utils/fmgroids.h" #include "utils/rel.h" #include "utils/snapmgr.h" +#include "utils/lsyscache.h" static bool toastrel_valueid_exists(Relation toastrel, uint64 valueid); static bool toastid_valueid_exists(Oid toastrelid, uint64 valueid); @@ -146,8 +148,10 @@ toast_save_datum(Relation rel, Datum value, int validIndex; const toast_external_info *info; uint8 tag = VARTAG_INDIRECT; /* init value does not matter */ + Oid toast_typid = get_atttype(rel->rd_rel->reltoastrelid, 1); Assert(!VARATT_IS_EXTERNAL(dval)); + Assert(OidIsValid(toast_typid)); /* * Open the toast relation and its indexes. We can use the index to check @@ -237,20 +241,23 @@ toast_save_datum(Relation rel, Datum value, info = toast_external_get_info(tag); /* - * Choose an OID to use as the value ID for this toast value. + * Choose a new value to use as the value ID for this toast value, be it + * for OID or int8-based TOAST relations. * - * Normally we just choose an unused OID within the toast table. But + * 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 OIDs too. So, if + * 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 OIDs. + * 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. */ if (!OidIsValid(rel->rd_toastoid)) { - /* normal case: just choose an unused OID */ + /* normal case: just choose an unused ID */ toast_pointer.value = info->get_new_value(toastrel, RelationGetRelid(toastidxs[validIndex]), @@ -269,7 +276,7 @@ toast_save_datum(Relation rel, Datum value, if (old_toast_pointer.toastrelid == rel->rd_toastoid) { - /* This value came from the old toast table; reuse its OID */ + /* This value came from the old toast table; reuse its ID */ toast_pointer.value = old_toast_pointer.value; /* @@ -300,8 +307,8 @@ toast_save_datum(Relation rel, Datum value, if (toast_pointer.value == InvalidToastId) { /* - * new value; must choose an OID that doesn't conflict in either - * old or new toast table + * new value; must choose a value that doesn't conflict in either + * old or new toast table. */ do { @@ -317,7 +324,10 @@ toast_save_datum(Relation rel, Datum value, /* * Initialize constant parts of the tuple data */ - t_values[0] = ObjectIdGetDatum(toast_pointer.value); + if (toast_typid == OIDOID) + t_values[0] = ObjectIdGetDatum(toast_pointer.value); + else if (toast_typid == INT8OID) + t_values[0] = Int64GetDatum(toast_pointer.value); t_values[2] = PointerGetDatum(&chunk_data); t_isnull[0] = false; t_isnull[1] = false; @@ -424,6 +434,7 @@ toast_delete_datum(Relation rel, Datum value, bool is_speculative) HeapTuple toasttup; int num_indexes; int validIndex; + Oid toast_typid; if (!VARATT_IS_EXTERNAL_ONDISK(attr)) return; @@ -435,6 +446,8 @@ toast_delete_datum(Relation rel, Datum value, bool is_speculative) * Open the toast relation and its indexes */ toastrel = table_open(toast_pointer.toastrelid, RowExclusiveLock); + toast_typid = TupleDescAttr(toastrel->rd_att, 0)->atttypid; + Assert(toast_typid == OIDOID || toast_typid == INT8OID); /* Fetch valid relation used for process */ validIndex = toast_open_indexes(toastrel, @@ -445,10 +458,18 @@ toast_delete_datum(Relation rel, Datum value, bool is_speculative) /* * Setup a scan key to find chunks with matching va_valueid */ - ScanKeyInit(&toastkey, - (AttrNumber) 1, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(toast_pointer.value)); + if (toast_typid == OIDOID) + ScanKeyInit(&toastkey, + (AttrNumber) 1, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(toast_pointer.value)); + else if (toast_typid == INT8OID) + ScanKeyInit(&toastkey, + (AttrNumber) 1, + BTEqualStrategyNumber, F_INT8EQ, + Int64GetDatum(toast_pointer.value)); + else + Assert(false); /* * Find all the chunks. (We don't actually care whether we see them in @@ -495,6 +516,7 @@ toastrel_valueid_exists(Relation toastrel, uint64 valueid) int num_indexes; int validIndex; Relation *toastidxs; + Oid toast_typid; /* Fetch a valid index relation */ validIndex = toast_open_indexes(toastrel, @@ -502,13 +524,24 @@ toastrel_valueid_exists(Relation toastrel, uint64 valueid) &toastidxs, &num_indexes); + toast_typid = TupleDescAttr(toastrel->rd_att, 0)->atttypid; + Assert(toast_typid == OIDOID || toast_typid == INT8OID); + /* * Setup a scan key to find chunks with matching va_valueid */ - ScanKeyInit(&toastkey, - (AttrNumber) 1, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(valueid)); + if (toast_typid == OIDOID) + ScanKeyInit(&toastkey, + (AttrNumber) 1, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(valueid)); + else if (toast_typid == INT8OID) + ScanKeyInit(&toastkey, + (AttrNumber) 1, + BTEqualStrategyNumber, F_INT8EQ, + Int64GetDatum(valueid)); + else + Assert(false); /* * Is there any such chunk? diff --git a/src/backend/access/heap/heaptoast.c b/src/backend/access/heap/heaptoast.c index c215263eb76a..a01c1d627c6f 100644 --- a/src/backend/access/heap/heaptoast.c +++ b/src/backend/access/heap/heaptoast.c @@ -654,6 +654,7 @@ heap_fetch_toast_slice(Relation toastrel, uint64 valueid, int32 attrsize, int32 max_chunk_size; const toast_external_info *info; uint8 tag = VARTAG_INDIRECT; /* init value does not matter */ + Oid toast_typid; /* Look for the valid index of toast relation */ validIndex = toast_open_indexes(toastrel, @@ -667,16 +668,27 @@ heap_fetch_toast_slice(Relation toastrel, uint64 valueid, int32 attrsize, max_chunk_size = info->maximum_chunk_size; + toast_typid = TupleDescAttr(toastrel->rd_att, 0)->atttypid; + Assert(toast_typid == OIDOID || toast_typid == INT8OID); + totalchunks = ((attrsize - 1) / max_chunk_size) + 1; startchunk = sliceoffset / max_chunk_size; endchunk = (sliceoffset + slicelength - 1) / max_chunk_size; Assert(endchunk <= totalchunks); /* Set up a scan key to fetch from the index. */ - ScanKeyInit(&toastkey[0], - (AttrNumber) 1, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(valueid)); + if (toast_typid == OIDOID) + ScanKeyInit(&toastkey[0], + (AttrNumber) 1, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(valueid)); + else if (toast_typid == INT8OID) + ScanKeyInit(&toastkey[0], + (AttrNumber) 1, + BTEqualStrategyNumber, F_INT8EQ, + Int64GetDatum(valueid)); + else + Assert(false); /* * No additional condition if fetching all chunks. Otherwise, use an diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c index e595cb61b375..27295866c490 100644 --- a/src/backend/catalog/toasting.c +++ b/src/backend/catalog/toasting.c @@ -32,6 +32,7 @@ #include "nodes/makefuncs.h" #include "utils/fmgroids.h" #include "utils/rel.h" +#include "utils/lsyscache.h" #include "utils/syscache.h" /* GUC support */ @@ -149,6 +150,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, int16 coloptions[2]; ObjectAddress baseobject, toastobject; + Oid toast_typid = InvalidOid; /* * Is it already toasted? @@ -204,11 +206,34 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, snprintf(toast_idxname, sizeof(toast_idxname), "pg_toast_%u_index", relOid); + /* + * Determine the type OID to use for the value. If OIDOldToast is + * defined, we need to rely on the existing table for the job because + * we do not want to create an inconsistent relation that would conflict + * with the parent and break the world. + */ + if (!OidIsValid(OIDOldToast)) + { + if (default_toast_type == TOAST_TYPE_OID) + toast_typid = OIDOID; + else if (default_toast_type == TOAST_TYPE_INT8) + toast_typid = INT8OID; + else + Assert(false); + } + else + { + /* For the chunk_id type */ + toast_typid = get_atttype(OIDOldToast, 1); + if (!OidIsValid(toast_typid)) + elog(ERROR, "cache lookup failed for relation %u", OIDOldToast); + } + /* this is pretty painful... need a tuple descriptor */ tupdesc = CreateTemplateTupleDesc(3); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "chunk_id", - OIDOID, + toast_typid, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 2, "chunk_seq", @@ -316,7 +341,10 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, collationIds[0] = InvalidOid; collationIds[1] = InvalidOid; - opclassIds[0] = OID_BTREE_OPS_OID; + if (toast_typid == OIDOID) + opclassIds[0] = OID_BTREE_OPS_OID; + else if (toast_typid == INT8OID) + opclassIds[0] = INT8_BTREE_OPS_OID; opclassIds[1] = INT4_BTREE_OPS_OID; coloptions[0] = 0; diff --git a/doc/src/sgml/storage.sgml b/doc/src/sgml/storage.sgml index f3c6cd8860b5..564783a1c559 100644 --- a/doc/src/sgml/storage.sgml +++ b/doc/src/sgml/storage.sgml @@ -419,14 +419,15 @@ most TOAST_MAX_CHUNK_SIZE_OID bytes (by default this value is c so that four chunk rows will fit on a page, making it about 2000 bytes). Each chunk is stored as a separate row in the TOAST table belonging to the owning table. Every -TOAST table has the columns chunk_id (an OID -identifying the particular TOASTed value), +TOAST table has the columns +chunk_id (an OID or an 8-byte integer identifying +the particular TOASTed value), chunk_seq (a sequence number for the chunk within its value), and chunk_data (the actual data of the chunk). A unique index on chunk_id and chunk_seq provides fast retrieval of the values. A pointer datum representing an out-of-line on-disk TOASTed value therefore needs to store the OID of the -TOAST table in which to look and the OID of the specific value +TOAST table in which to look and the specific value (its chunk_id). For convenience, pointer datums also store the logical datum size (original uncompressed data length), physical stored size (different if compression was applied), and the compression method used, if diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c index 11c4507ae6e2..833811c75437 100644 --- a/contrib/amcheck/verify_heapam.c +++ b/contrib/amcheck/verify_heapam.c @@ -1880,6 +1880,9 @@ check_toasted_attribute(HeapCheckContext *ctx, ToastedAttribute *ta) int32 last_chunk_seq; uint64 toast_valueid; int32 max_chunk_size; + Oid toast_typid; + + toast_typid = TupleDescAttr(ctx->toast_rel->rd_att, 0)->atttypid; extsize = ta->toast_pointer.extsize; @@ -1889,10 +1892,18 @@ check_toasted_attribute(HeapCheckContext *ctx, ToastedAttribute *ta) /* * Setup a scan key to find chunks in toast table with matching va_valueid */ - ScanKeyInit(&toastkey, - (AttrNumber) 1, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(ta->toast_pointer.value)); + if (toast_typid == OIDOID) + ScanKeyInit(&toastkey, + (AttrNumber) 1, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(ta->toast_pointer.value)); + else if (toast_typid == INT8OID) + ScanKeyInit(&toastkey, + (AttrNumber) 1, + BTEqualStrategyNumber, F_INT8EQ, + Int64GetDatum(ta->toast_pointer.value)); + else + Assert(false); /* * Check if any chunks for this toasted object exist in the toast table, -- 2.50.0