From d645ac1939f6538f5e96096857a5dff1f6854b14 Mon Sep 17 00:00:00 2001 From: Hannu Krosing Date: Sun, 23 Aug 2026 20:08:55 +0000 Subject: [PATCH v4 2/9] Add Direct TOAST catalog, GUC, and reloptions infrastructure Introduce base structures, macros, and configuration options for Direct TOAST: - Add struct varatt_direct and VARTAG_DIRECT (tag 19) in varatt.h. - Add toast_flavour reloption ('plain' / 'direct') in reloptions.c. - Add default_toast_flavour GUC in guc_tables.c. - Update create_toast_table() to support chunk_tids and chunk_tid_offsets columns for direct TOAST tables, with partial B-Tree indexing on chunk_id. --- src/backend/access/common/reloptions.c | 20 ++++++++++ src/backend/catalog/toasting.c | 26 ++++++++++++- src/backend/utils/misc/guc_tables.c | 7 ++++ src/include/access/toast_internals.h | 9 +++++ src/include/utils/rel.h | 8 ++++ src/include/varatt.h | 53 +++++++++++++++++++++++++- 6 files changed, 120 insertions(+), 3 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index ea9a0417909..02db33016a2 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -530,6 +530,13 @@ static relopt_enum_elt_def StdRdOptIndexCleanupValues[] = {(const char *) NULL} /* list terminator */ }; +static relopt_enum_elt_def toastFlavourOptValues[] = +{ + {"plain", TOAST_FLAVOUR_PLAIN}, + {"direct", TOAST_FLAVOUR_DIRECT}, + {(const char *) NULL} /* list terminator */ +}; + /* values from GistOptBufferingMode */ static relopt_enum_elt_def gistBufferingOptValues[] = { @@ -581,6 +588,17 @@ static relopt_enum enumRelOpts[] = STDRD_OPTION_TOAST_VALUE_TYPE_OID, gettext_noop("Valid values are \"oid\" and \"oid8\".") }, + { + { + "toast_flavour", + "Sets the TOAST flavour for this table", + RELOPT_KIND_HEAP, + ShareUpdateExclusiveLock + }, + toastFlavourOptValues, + TOAST_FLAVOUR_NOT_SET, + gettext_noop("Valid values are \"plain\" and \"direct\".") + }, { { "buffering", @@ -2114,6 +2132,8 @@ static const relopt_parse_elt stdRdOptionsTab[] = { offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, + {"toast_flavour", RELOPT_TYPE_ENUM, + offsetof(StdRdOptions, toast_flavour)}, {"vacuum_truncate", RELOPT_TYPE_TERNARY, offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c index 3058ec14223..13da87fdcbd 100644 --- a/src/backend/catalog/toasting.c +++ b/src/backend/catalog/toasting.c @@ -254,7 +254,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, } /* this is pretty painful... need a tuple descriptor */ - tupdesc = CreateTemplateTupleDesc(3); + tupdesc = CreateTemplateTupleDesc(5); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "chunk_id", toast_chunkid_typid, @@ -267,6 +267,14 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, "chunk_data", BYTEAOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, + "chunk_tids", + TIDARRAYOID, + -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, + "chunk_tid_offsets", + INT8ARRAYOID, + -1, 0); /* * Ensure that the toast table doesn't itself get toasted, or we'll be @@ -276,15 +284,21 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, TupleDescAttr(tupdesc, 0)->attstorage = TYPSTORAGE_PLAIN; TupleDescAttr(tupdesc, 1)->attstorage = TYPSTORAGE_PLAIN; TupleDescAttr(tupdesc, 2)->attstorage = TYPSTORAGE_PLAIN; + TupleDescAttr(tupdesc, 3)->attstorage = TYPSTORAGE_PLAIN; + TupleDescAttr(tupdesc, 4)->attstorage = TYPSTORAGE_PLAIN; /* Toast field should not be compressed */ TupleDescAttr(tupdesc, 0)->attcompression = InvalidCompressionMethod; TupleDescAttr(tupdesc, 1)->attcompression = InvalidCompressionMethod; TupleDescAttr(tupdesc, 2)->attcompression = InvalidCompressionMethod; + TupleDescAttr(tupdesc, 3)->attcompression = InvalidCompressionMethod; + TupleDescAttr(tupdesc, 4)->attcompression = InvalidCompressionMethod; populate_compact_attribute(tupdesc, 0); populate_compact_attribute(tupdesc, 1); populate_compact_attribute(tupdesc, 2); + populate_compact_attribute(tupdesc, 3); + populate_compact_attribute(tupdesc, 4); TupleDescFinalize(tupdesc); @@ -351,7 +365,15 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, indexInfo->ii_IndexAttrNumbers[1] = 2; indexInfo->ii_Expressions = NIL; indexInfo->ii_ExpressionsState = NIL; - indexInfo->ii_Predicate = NIL; + { + NullTest *ntest = makeNode(NullTest); + + ntest->arg = (Expr *) makeVar(1, 1, OIDOID, -1, InvalidOid, 0); + ntest->nulltesttype = IS_NOT_NULL; + ntest->argisrow = false; + ntest->location = -1; + indexInfo->ii_Predicate = list_make1(ntest); + } indexInfo->ii_PredicateState = NULL; indexInfo->ii_ExclusionOps = NULL; indexInfo->ii_ExclusionProcs = NULL; diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index 342aaeef59a..11d8b71f0a5 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -33,6 +33,7 @@ #include "access/gin.h" #include "access/slru.h" #include "access/toast_compression.h" +#include "access/toast_internals.h" #include "access/twophase.h" #include "access/xlog_internal.h" #include "access/xlogprefetcher.h" @@ -287,6 +288,12 @@ static const struct config_enum_entry xmloption_options[] = { StaticAssertDecl(lengthof(xmloption_options) == (XMLOPTION_CONTENT + 2), "array length mismatch"); +static const struct config_enum_entry toast_flavour_options[] = { + {"plain", TOAST_FLAVOUR_PLAIN, false}, + {"direct", TOAST_FLAVOUR_DIRECT, false}, + {NULL, 0, false} +}; + /* * Although only "on", "off", and "safe_encoding" are documented, we * accept all the likely variants of "on" and "off". diff --git a/src/include/access/toast_internals.h b/src/include/access/toast_internals.h index e03cc1204be..15584781280 100644 --- a/src/include/access/toast_internals.h +++ b/src/include/access/toast_internals.h @@ -17,6 +17,15 @@ #include "storage/lockdefs.h" #include "utils/relcache.h" #include "utils/snapshot.h" +#include "utils/rel.h" + +#define RelationGetToastFlavour(relation) \ + ((relation)->rd_options ? \ + (((StdRdOptions *) (relation)->rd_options)->toast_flavour != TOAST_FLAVOUR_NOT_SET ? \ + ((StdRdOptions *) (relation)->rd_options)->toast_flavour : (ToastFlavour) toast_flavour) \ + : (ToastFlavour) toast_flavour) + +extern PGDLLIMPORT int toast_flavour; /* * The information at the start of the compressed toast data. diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 9c8d17337b8..e7db822e170 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -358,6 +358,13 @@ typedef enum StdRdOptToastValueType STDRD_OPTION_TOAST_VALUE_TYPE_OID8, } StdRdOptToastValueType; +typedef enum ToastFlavour +{ + TOAST_FLAVOUR_NOT_SET = -1, + TOAST_FLAVOUR_PLAIN = 0, + TOAST_FLAVOUR_DIRECT = 1 +} ToastFlavour; + typedef struct StdRdOptions { int32 vl_len_; /* varlena header (do not touch directly!) */ @@ -376,6 +383,7 @@ typedef struct StdRdOptions * to freeze. 0 if disabled, -1 if unspecified. */ double vacuum_max_eager_freeze_failure_rate; + ToastFlavour toast_flavour; } StdRdOptions; #define HEAP_MIN_FILLFACTOR 10 diff --git a/src/include/varatt.h b/src/include/varatt.h index 77041f7673a..c0d1a8444a2 100644 --- a/src/include/varatt.h +++ b/src/include/varatt.h @@ -15,6 +15,8 @@ #ifndef VARATT_H #define VARATT_H +#include "storage/itemptr.h" + /* * varatt_external_oid is a traditional "TOAST pointer", that is, the * information needed to fetch a Datum stored out-of-line in a TOAST table. @@ -80,6 +82,15 @@ VARATT_EXTERNAL_OID8_SET_VALUEID(varatt_external_oid8 *toast_pointer, Oid8 id) toast_pointer->va_valueid_hi = (uint32) (id >> 32); } +typedef struct varatt_direct +{ + int32 va_rawsize; /* Original data size (includes header) */ + uint32 va_extinfo; /* External saved size (without header) and + * compression method */ + Oid va_toastrelid; /* RelID of TOAST table containing it */ + ItemPointerData va_tid; /* Physical TID of the final chunk */ +} varatt_direct; + /* * These macros define the "saved size" portion of va_extinfo. Its remaining * two high-order bits identify the compression method. @@ -130,7 +141,8 @@ typedef enum vartag_external VARTAG_EXPANDED_RO = 2, VARTAG_EXPANDED_RW = 3, VARTAG_ONDISK_OID8 = 4, - VARTAG_ONDISK_OID = 18 + VARTAG_ONDISK_OID = 18, + VARTAG_DIRECT = 19 } vartag_external; /* Is a TOAST pointer either type of expanded-object pointer? */ @@ -153,6 +165,8 @@ 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_DIRECT) + return sizeof(varatt_direct); else { Assert(false); @@ -414,6 +428,24 @@ VARATT_IS_EXTERNAL_ONDISK(const void *PTR) return (tag == VARTAG_ONDISK_OID || tag == VARTAG_ONDISK_OID8); } +static inline bool +VARATT_IS_EXTERNAL_DIRECT(const void *PTR) +{ + return VARATT_IS_EXTERNAL(PTR) && VARTAG_EXTERNAL(PTR) == VARTAG_DIRECT; +} + +static inline bool +VARATT_IS_EXTERNAL_ONDISK_OR_DIRECT(const void *PTR) +{ + vartag_external tag; + + if (!VARATT_IS_EXTERNAL(PTR)) + return false; + tag = VARTAG_EXTERNAL(PTR); + return (tag == VARTAG_ONDISK_OID || tag == VARTAG_ONDISK_OID8 || + tag == VARTAG_DIRECT); +} + /* Is varlena datum an indirect pointer? */ static inline bool VARATT_IS_EXTERNAL_INDIRECT(const void *PTR) @@ -581,4 +613,23 @@ VARATT_EXTINFO_IS_COMPRESSED(uint32 extinfo, int32 rawsize) return VARATT_EXTINFO_GET_EXTSIZE(extinfo) < (Size) (rawsize - VARHDRSZ); } +static inline Size +VARATT_DIRECT_GET_EXTSIZE(varatt_direct toast_pointer) +{ + return toast_pointer.va_extinfo & VARLENA_EXTSIZE_MASK; +} + +static inline uint32 +VARATT_DIRECT_GET_COMPRESS_METHOD(varatt_direct toast_pointer) +{ + return toast_pointer.va_extinfo >> VARLENA_EXTSIZE_BITS; +} + +static inline bool +VARATT_DIRECT_IS_COMPRESSED(varatt_direct toast_pointer) +{ + return VARATT_DIRECT_GET_EXTSIZE(toast_pointer) < + (Size) (toast_pointer.va_rawsize - VARHDRSZ); +} + #endif -- 2.55.0.1082.g2b9226bbc0-goog