From 105fa76fd903b91c9f7d9cd5a8103e88d00e6103 Mon Sep 17 00:00:00 2001 From: Nikita Malakhov Date: Sun, 13 Jul 2025 11:45:42 +0300 Subject: [PATCH] [POC] TOAST with direct TIDs TOAST table with direct TID chains instead of index. New TOAST pointer structure varatt_external_tid is introduced with corresponding vartag. Default TOAST mechanics is suppressed by setting GUC set toast_tid_enabled to true; New TOAST tables contain 2 attributes - chunk_data and next_tid. TOAST pointer stores first TID of chain, and detoast fetches data chunks by tids. Advantage: no TOAST index, very fast with small data. Disadvantages: 1) VACUUM should be refactored for such tables; 2) Slow on large data because tid chunks are inserted one by one - we need to store inserted tid in previous chunk, this cannot be done as a batch. Usage example: set toast_tid_enabled to true; create table t (id int, t text); alter table t alter column t set storage external; --to disable compression insert into t values (1, repeat('a',20000)); select * from t; --- src/backend/access/common/detoast.c | 19 +- src/backend/access/common/toast_external.c | 65 ++++ src/backend/access/common/toast_internals.c | 384 ++++++++++++++++++++ src/backend/access/heap/heaptoast.c | 8 +- src/backend/access/table/toast_helper.c | 29 +- src/backend/catalog/toasting.c | 190 +++++++++- src/backend/utils/misc/guc_tables.c | 9 + src/include/access/toast_external.h | 2 + src/include/access/toast_helper.h | 2 + src/include/access/toast_internals.h | 10 + src/include/access/toast_type.h | 1 + src/include/varatt.h | 32 +- 12 files changed, 740 insertions(+), 11 deletions(-) diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c index 684e1b0b7d..06402b4b03 100644 --- a/src/backend/access/common/detoast.c +++ b/src/backend/access/common/detoast.c @@ -116,7 +116,11 @@ detoast_external_attr(struct varlena *attr) struct varlena * detoast_attr(struct varlena *attr) { - if (VARATT_IS_EXTERNAL_ONDISK(attr)) + if (VARATT_IS_EXTERNAL_DIRECT_TIDS(attr)) + { + attr = direct_tids_fetch_datum(PointerGetDatum(attr), 0, -1); //VARATT_EXTERNAL_GET_EXTSIZE((varatt_external_tid *) VARDATA_EXTERNAL(attr))); + } + else if (VARATT_IS_EXTERNAL_ONDISK(attr)) { /* * This is an externally stored datum --- fetch it back from there @@ -281,6 +285,12 @@ detoast_attr_slice(struct varlena *attr, /* pass it off to detoast_external_attr to flatten */ preslice = detoast_external_attr(attr); } + else if (VARATT_IS_EXTERNAL_DIRECT_TIDS(attr)) + { + /* pass it off to detoast_external_attr to flatten */ + preslice = detoast_external_attr(attr); + } + else preslice = attr; @@ -457,7 +467,12 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, toastrel = table_open(toast_pointer.toastrelid, AccessShareLock); /* Fetch all chunks */ - table_relation_fetch_toast_slice(toastrel, + if(VARATT_IS_EXTERNAL_DIRECT_TIDS(attr)) + { + result = direct_tids_fetch_datum(PointerGetDatum(attr), sliceoffset, slicelength); + } + else + table_relation_fetch_toast_slice(toastrel, valueid, attrsize, sliceoffset, slicelength, result); diff --git a/src/backend/access/common/toast_external.c b/src/backend/access/common/toast_external.c index 0e79ac8aca..6fc08b12d9 100644 --- a/src/backend/access/common/toast_external.c +++ b/src/backend/access/common/toast_external.c @@ -38,6 +38,8 @@ 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); +static void direct_tids_to_external_data(struct varlena *attr, toast_external_data *data); +static struct varlena *direct_tids_create_external_data(toast_external_data data); /* * Size of an EXTERNAL datum that contains a standard TOAST pointer @@ -51,6 +53,8 @@ static uint64 ondisk_oid_get_new_value(Relation toastrel, Oid indexid, */ #define TOAST_POINTER_OID_SIZE (VARHDRSZ_EXTERNAL + sizeof(varatt_external_oid)) +#define TOAST_POINTER_TID_SIZE (VARHDRSZ_EXTERNAL + sizeof(varatt_external_oid)) + /* * 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. @@ -79,6 +83,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_TID] = { + .toast_pointer_size = TOAST_POINTER_TID_SIZE, + .maximum_chunk_size = TOAST_MAX_CHUNK_SIZE_OID, + .to_external_data = direct_tids_to_external_data, + .create_external_data = direct_tids_create_external_data, + .get_new_value = NULL, + }, }; @@ -264,3 +275,57 @@ ondisk_oid_get_new_value(Relation toastrel, Oid indexid, { return GetNewOidWithIndex(toastrel, indexid, attnum); } + +static struct varlena * +direct_tids_create_external_data(toast_external_data data) +{ + struct varlena *result = NULL; + varatt_external_tid external; + + external.va_rawsize = data.rawsize; + + if (data.compression_method != TOAST_INVALID_COMPRESSION_ID) + { + /* Set size and compression method, in a single field. */ + VARATT_EXTERNAL_SET_SIZE_AND_COMPRESS_METHOD(external, + data.extsize, + data.compression_method); + } + else + external.va_extinfo = data.extsize; + + external.va_toastrelid = data.toastrelid; + + result = (struct varlena *) palloc(TOAST_POINTER_TID_SIZE); + SET_VARTAG_EXTERNAL(result, VARTAG_ONDISK_TID); + memcpy(VARDATA_EXTERNAL(result), &external, sizeof(external)); + + return result; +} + +static void direct_tids_to_external_data(struct varlena *attr, toast_external_data *data) +{ + + varatt_external_tid external; + + VARATT_EXTERNAL_GET_POINTER(external, attr); + data->rawsize = external.va_rawsize; + + /* + * External size and compression methods are stored in the same field, + * extract. + */ + if (VARATT_EXTERNAL_IS_COMPRESSED(external)) + { + data->extsize = VARATT_EXTERNAL_GET_EXTSIZE(external); + data->compression_method = VARATT_EXTERNAL_GET_COMPRESS_METHOD(external); + } + else + { + data->extsize = external.va_extinfo; + data->compression_method = TOAST_INVALID_COMPRESSION_ID; + } + + ItemPointerCopy((ItemPointer) &external.tid, data->tid); + data->toastrelid = external.va_toastrelid; +} diff --git a/src/backend/access/common/toast_internals.c b/src/backend/access/common/toast_internals.c index c6b2d1522c..eb5019eeff 100644 --- a/src/backend/access/common/toast_internals.c +++ b/src/backend/access/common/toast_internals.c @@ -24,11 +24,47 @@ #include "catalog/catalog.h" #include "miscadmin.h" #include "utils/fmgroids.h" +#include "utils/memutils.h" #include "utils/rel.h" #include "utils/snapmgr.h" +#include "varatt.h" static bool toastrel_valueid_exists(Relation toastrel, uint64 valueid); static bool toastid_valueid_exists(Oid toastrelid, uint64 valueid); +static Datum +toast_save_datum_tids(Relation rel, Datum value, + int options); + +#define VARATT_DIRECT_TIDS_HDRSZ \ + sizeof(varatt_external_tid) + +#define VARATT_TIDS_GET_TOASTPOINTER(PTR) \ + ((varatt_external_tid *) VARDATA_EXTERNAL(PTR)) + +#define VARATT_TIDS_GET_DATA_RAW_SIZE(PTR) \ + (VARATT_TIDS_GET_TOASTPOINTER(PTR)->va_rawsize) + +#define VARATT_TIDS_SET_DATA_RAW_SIZE(PTR, V) \ + ((VARATT_TIDS_GET_TOASTPOINTER(PTR))->va_rawsize = (V)) + +#define VARATT_TIDS_GET_DATA_SIZE(PTR) \ + ((VARATT_TIDS_GET_TOASTPOINTER(PTR))->va_extinfo) + +#define VARATT_TIDS_SET_DATA_SIZE(PTR, V) \ + ((VARATT_TIDS_GET_TOASTPOINTER(PTR))->va_extinfo = (V)) + +#define VARATT_DIRECT_TIDS_SIZE \ + ((Size) VARHDRSZ_EXTERNAL + sizeof(varatt_external_tid)) + +#define VARSIZE_TIDS(PTR) VARATT_DIRECT_TIDS_SIZE + +#define VARATT_TID_GET_DATA(attr, data) \ +do { \ + varattrib_1b_e *attrc = (varattrib_1b_e *)(attr); \ + Assert(VARATT_IS_EXTERNAL(attrc)); \ + Assert(VARSIZE_EXTERNAL(attrc) >= sizeof(varatt_external_tid)); \ + memcpy(&(data), VARDATA_EXTERNAL(attrc), sizeof(varatt_external_tid)); \ +} while (0) /* ---------- * toast_compress_datum - @@ -104,6 +140,354 @@ toast_compress_datum(Datum value, char cmethod) } } +/* mostly copy of heap_fetch but works more effective with buffers. */ +static bool +heap_fetch_cached(Relation relation, Snapshot snapshot, + HeapTuple tuple, Buffer *userbuf) +{ + ItemPointer tid = &(tuple->t_self); + ItemId lp; + Page page; + OffsetNumber offnum; + + if (BufferIsValid(*userbuf)) { + if (BufferGetBlockNumber(*userbuf) != ItemPointerGetBlockNumber(tid)) + { + LockBuffer(*userbuf, BUFFER_LOCK_UNLOCK); + *userbuf = ReleaseAndReadBuffer(*userbuf, relation, + ItemPointerGetBlockNumber(tid)); + LockBuffer(*userbuf, BUFFER_LOCK_SHARE); + } + } + else + { + *userbuf = ReadBuffer(relation, ItemPointerGetBlockNumber(tid)); + LockBuffer(*userbuf, BUFFER_LOCK_SHARE); + } + + /* + * Need share lock on buffer to examine tuple commit status. + */ + page = BufferGetPage(*userbuf); + + /* + * We'd better check for out-of-range offnum in case of VACUUM since the + * TID was obtained. + */ + offnum = ItemPointerGetOffsetNumber(tid); + if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page)) + return false; + + /* + * get the item line pointer corresponding to the requested tid + */ + lp = PageGetItemId(page, offnum); + + /* + * Must check for deleted tuple. + */ + if (!ItemIdIsNormal(lp)) + return false; + + /* + * fill in *tuple fields + */ + tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); + tuple->t_len = ItemIdGetLength(lp); + tuple->t_tableOid = RelationGetRelid(relation); + + /* + * check tuple visibility, then release lock + */ + return HeapTupleSatisfiesVisibility(tuple, snapshot, *userbuf); +} + +static void +toast_fetch_toast_slice_tids(Relation toastrel, ItemPointer tid, + struct varlena *attr, int32 attrsize, + int32 sliceoffset, int32 slicelength, + struct varlena *result) +{ + TupleDesc toasttupDesc = toastrel->rd_att; + HeapTuple ttup; + Buffer buf = InvalidBuffer; + bytea *chunk; + char *chunkdata; + int32 chunksize; + int32 copy_offset = 0; + ItemPointer t; + Datum values[2]; + bool isnull[2]; + + ttup = palloc(sizeof(HeapTupleData)); + + ItemPointerCopy(tid, &ttup->t_self); + + while (ItemPointerIsValid(&ttup->t_self) && + copy_offset < sliceoffset + slicelength) + { +// elog(NOTICE, "fetch tid (%d, %d)", ItemPointerGetBlockNumber(&ttup->t_self), ItemPointerGetOffsetNumber(&ttup->t_self)); + if (!heap_fetch_cached(toastrel, get_toast_snapshot(), ttup, &buf)) + elog(ERROR, "could not find chunk (%u,%u)", + ItemPointerGetBlockNumber(&ttup->t_self), + ItemPointerGetOffsetNumber(&ttup->t_self)); + + Assert(ttup->t_data != NULL); + + heap_deform_tuple(ttup, toasttupDesc, values, isnull); + Assert(isnull[0] == false && isnull[1] == false); + + t = (ItemPointer)DatumGetPointer(values[0]); + chunk = DatumGetByteaP(values[1]); + + chunkdata = VARDATA_ANY(chunk); + chunksize = VARSIZE_ANY_EXHDR(chunk); + + if (copy_offset >= sliceoffset) + { + if (copy_offset < sliceoffset + slicelength) + memcpy(VARDATA(result) + copy_offset - sliceoffset, + chunkdata, + Min(sliceoffset + slicelength - copy_offset, chunksize)); + } + else + { + if (copy_offset + chunksize > sliceoffset) + memcpy(VARDATA(result), + chunkdata + sliceoffset - copy_offset, + Min(copy_offset + chunksize - sliceoffset, slicelength)); + } + + copy_offset = copy_offset + chunksize; + + /* + * t points inside buffer, so copy pointer before buffer releasing + */ + ItemPointerCopy(t, &ttup->t_self); + } + + if (BufferIsValid(buf)) + { + LockBuffer(buf, BUFFER_LOCK_UNLOCK); + ReleaseBuffer(buf); + } + + pfree(ttup); +} + +static void +toast_write_slice_tids(Relation toastrel, + int32 slice_length, char *slice_data, int options, + ItemPointer tid /* will return tid of first chunk */) +{ + TupleDesc toasttupDesc = toastrel->rd_att; + bytea *chunk_data; + int32 max_chunks_size = TOAST_MAX_CHUNK_SIZE; + int32 chunk_size; + int32 slice_start; + int32 offset; + Datum t_values[2]; + bool t_isnull[2]; + CommandId mycid = GetCurrentCommandId(true); + + chunk_size = slice_length % max_chunks_size; + slice_start = slice_length - chunk_size; + offset = slice_length - chunk_size; + + chunk_data = palloc(TOAST_MAX_CHUNK_SIZE + VARHDRSZ); + + t_values[0] = PointerGetDatum(tid); + t_values[1] = PointerGetDatum(chunk_data); + t_isnull[0] = false; + t_isnull[1] = false; + + ItemPointerSetInvalid(tid); + + while (slice_start >= 0) + { + HeapTuple toasttup; + + CHECK_FOR_INTERRUPTS(); + + SET_VARSIZE(chunk_data, chunk_size + VARHDRSZ); + + memcpy(VARDATA(chunk_data), slice_data + offset, chunk_size); + + toasttup = heap_form_tuple(toasttupDesc, t_values, t_isnull); + + heap_insert(toastrel, toasttup, mycid, options, NULL); + + ItemPointerCopy(&(toasttup->t_self), tid); + + heap_freetuple(toasttup); + + if(chunk_size != max_chunks_size) + chunk_size = max_chunks_size; + slice_start = slice_start - chunk_size; + offset = offset - chunk_size; + } + + pfree(chunk_data); +} + +static Datum +toast_save_datum_tids(Relation rel, Datum value, + int options) +{ + Relation toastrel; + ItemPointerData tid; + struct varlena *result; + varatt_external_tid toast_data; + char *data_p; + int32 data_todo; + Pointer dval = DatumGetPointer(value); + + data_p = VARDATA(dval); + data_todo = VARSIZE_ANY_EXHDR(dval); + + toastrel = table_open(rel->rd_rel->reltoastrelid, RowExclusiveLock); + + if (OidIsValid(rel->rd_toastoid)) + toast_data.va_toastrelid = rel->rd_toastoid; + else + toast_data.va_toastrelid = RelationGetRelid(toastrel); + + toast_write_slice_tids(toastrel, + data_todo, data_p, + options, &tid); + + ItemPointerCopy(&tid, (ItemPointer) &(toast_data.tid)); + + table_close(toastrel, NoLock); + + result = (struct varlena *) palloc(VARHDRSZ + VARATT_DIRECT_TIDS_HDRSZ); + + SET_VARTAG_EXTERNAL(result, VARTAG_ONDISK_TID); + VARATT_TIDS_SET_DATA_RAW_SIZE(result, VARSIZE_ANY_EXHDR(dval) + VARATT_DIRECT_TIDS_HDRSZ); + VARATT_TIDS_SET_DATA_SIZE(result, VARATT_DIRECT_TIDS_HDRSZ); + memcpy(VARDATA_EXTERNAL(result), &toast_data, VARATT_DIRECT_TIDS_HDRSZ); + + return PointerGetDatum(result); +} + +Datum +direct_tids_save_datum(Relation toast_rel, Datum value, Datum oldvalue, + int options) +{ + Datum detoasted_newval; + Datum toasted_newval; + + Assert(toast_rel != NULL); + detoasted_newval = PointerGetDatum(detoast_attr((struct varlena *) value)); + toasted_newval = toast_save_datum_tids(toast_rel, + detoasted_newval, options); + return toasted_newval; +} + +struct varlena* +direct_tids_fetch_datum(Datum toast_ptr, int offset, int length) +{ + struct varlena *result = 0; + varatt_external_tid data; + int32 attrsize = 0; + int32 toasted_size = 0; + Relation toastrel; + struct varlena *tvalue = (struct varlena*)DatumGetPointer(toast_ptr); + + if(VARATT_IS_EXTERNAL(tvalue)) + { + VARATT_TID_GET_DATA(DatumGetPointer(toast_ptr), data); + + toasted_size = data.va_extinfo; + attrsize = toasted_size; + + if (offset >= attrsize) + { + offset = 0; + length = 0; + } + + if (offset + length > attrsize || length < 0) + length = attrsize - offset; + + result = (struct varlena *) palloc(length + VARHDRSZ); + SET_VARSIZE(result, length + VARHDRSZ); + + if (length > 0) + { + toastrel = table_open(data.va_toastrelid, AccessShareLock); + toast_fetch_toast_slice_tids(toastrel, (ItemPointer) &data.tid, + (struct varlena *) toast_ptr, + toasted_size, offset, length, + result); + table_close(toastrel, NoLock); + } + } + else + result = tvalue; + + return result; +} + +void +direct_tids_delete_datum(Relation rel, Datum value, bool is_speculative) +{ + TupleDesc toasttupDesc; + HeapTuple ttup; + Relation toastrel; + Buffer buf; + ItemPointer tid; + bool fetch_ind = true; + bool isnull; + varatt_external_tid data; + + VARATT_TID_GET_DATA(DatumGetPointer(value), data); + + if(data.va_toastrelid) + toastrel = table_open(data.va_toastrelid, RowExclusiveLock); + else return; + + toasttupDesc = toastrel->rd_att; + ttup = palloc(sizeof(HeapTupleData)); + tid = palloc(sizeof(ItemPointerData)); + + ItemPointerCopy((ItemPointer) &data.tid, tid); + + do + { + ItemPointerCopy(tid, &ttup->t_self); + + if (!heap_fetch(toastrel, get_toast_snapshot(), ttup, &buf, true)) + fetch_ind = false; + + if(fetch_ind) + { + tid = (ItemPointer) DatumGetPointer(fastgetattr(ttup, 1, toasttupDesc, &isnull)); + + Assert(!isnull); + if (is_speculative) + heap_abort_speculative(toastrel, &(ttup->t_self)); + else + simple_heap_delete(toastrel, &(ttup->t_self)); + + if(!ItemPointerIsValid(tid)) + fetch_ind = false; + } + else + fetch_ind = false; + } + while(fetch_ind); + + if (BufferIsValid(buf)) + { + LockBuffer(buf, BUFFER_LOCK_UNLOCK); + ReleaseBuffer(buf); + } + + table_close(toastrel, NoLock); + pfree(ttup); +} + /* ---------- * toast_save_datum - * diff --git a/src/backend/access/heap/heaptoast.c b/src/backend/access/heap/heaptoast.c index 87f1630d85..d4c35214d1 100644 --- a/src/backend/access/heap/heaptoast.c +++ b/src/backend/access/heap/heaptoast.c @@ -165,7 +165,9 @@ heap_toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup, rel->rd_rel->reltoastrelid); atttoast = (Form_pg_attribute) GETSTRUCT(atttuple); - if (atttoast->atttypid == OIDOID) + if(toast_tid_enabled) + vartag = VARTAG_ONDISK_TID; + else if (atttoast->atttypid == OIDOID) vartag = VARTAG_ONDISK_OID; else if (atttoast->atttypid == INT8OID) vartag = VARTAG_ONDISK_INT8; @@ -184,7 +186,9 @@ heap_toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup, */ uint8 vartag = VARTAG_ONDISK_OID; - if (default_toast_type == TOAST_TYPE_INT8) + if(toast_tid_enabled) + vartag = VARTAG_ONDISK_TID; + else if (default_toast_type == TOAST_TYPE_INT8) vartag = VARTAG_ONDISK_INT8; else if (default_toast_type == TOAST_TYPE_OID) vartag = VARTAG_ONDISK_OID; diff --git a/src/backend/access/table/toast_helper.c b/src/backend/access/table/toast_helper.c index a2b44e093d..9cad7707f1 100644 --- a/src/backend/access/table/toast_helper.c +++ b/src/backend/access/table/toast_helper.c @@ -266,8 +266,17 @@ toast_tuple_externalize(ToastTupleContext *ttc, int attribute, int options) ToastAttrInfo *attr = &ttc->ttc_attr[attribute]; attr->tai_colflags |= TOASTCOL_IGNORE; - *value = toast_save_datum(ttc->ttc_rel, old_value, attr->tai_oldexternal, + if(toast_tid_enabled) + { + *value = direct_tids_save_datum(ttc->ttc_rel, old_value, PointerGetDatum(attr->tai_oldexternal), + options); + } + else + { + *value = toast_save_datum(ttc->ttc_rel, old_value, attr->tai_oldexternal, options); + } + if ((attr->tai_colflags & TOASTCOL_NEEDS_FREE) != 0) pfree(DatumGetPointer(old_value)); attr->tai_colflags |= TOASTCOL_NEEDS_FREE; @@ -311,7 +320,14 @@ toast_tuple_cleanup(ToastTupleContext *ttc) ToastAttrInfo *attr = &ttc->ttc_attr[i]; if ((attr->tai_colflags & TOASTCOL_NEEDS_DELETE_OLD) != 0) - toast_delete_datum(ttc->ttc_rel, ttc->ttc_oldvalues[i], false); + { + if(toast_tid_enabled) + { + direct_tids_delete_datum(ttc->ttc_rel, ttc->ttc_oldvalues[i], false); + } + else + toast_delete_datum(ttc->ttc_rel, ttc->ttc_oldvalues[i], false); + } } } } @@ -337,7 +353,14 @@ toast_delete_external(Relation rel, const Datum *values, const bool *isnull, if (isnull[i]) continue; else if (VARATT_IS_EXTERNAL_ONDISK(value)) - toast_delete_datum(rel, value, is_speculative); + { + if(toast_tid_enabled) + { + direct_tids_delete_datum(rel, value, is_speculative); + } + else + toast_delete_datum(rel, value, is_speculative); + } } } } diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c index 3df83c9835..91866d0382 100644 --- a/src/backend/catalog/toasting.c +++ b/src/backend/catalog/toasting.c @@ -33,9 +33,11 @@ #include "utils/fmgroids.h" #include "utils/rel.h" #include "utils/syscache.h" +#include "utils/guc.h" /* GUC support */ int default_toast_type = TOAST_TYPE_OID; +bool toast_tid_enabled = false; static void CheckAndCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode, bool check, @@ -45,6 +47,15 @@ static bool create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, Oid OIDOldToast); static bool needs_toast_table(Relation rel); +static bool +create_toast_table_tid(Relation rel, Oid toastOid, Oid toastIndexOid, + Datum reloptions, LOCKMODE lockmode, bool check, + Oid OIDOldToast); +static bool +create_toast_table_default(Relation rel, Oid toastOid, Oid toastIndexOid, + Datum reloptions, LOCKMODE lockmode, bool check, + Oid OIDOldToast); + /* * CreateToastTable variants @@ -119,7 +130,6 @@ BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid) table_close(rel, NoLock); } - /* * create_toast_table --- internal workhorse * @@ -131,6 +141,184 @@ static bool create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, Datum reloptions, LOCKMODE lockmode, bool check, Oid OIDOldToast) +{ + if(toast_tid_enabled) + { + return create_toast_table_tid(rel, toastOid, toastIndexOid, + reloptions, lockmode, check, + OIDOldToast); + } + + return create_toast_table_default(rel, toastOid, toastIndexOid, + reloptions, lockmode, check, + OIDOldToast); + +} + +static bool +create_toast_table_tid(Relation rel, Oid toastOid, Oid toastIndexOid, + Datum reloptions, LOCKMODE lockmode, bool check, + Oid OIDOldToast) +{ + Oid relOid = RelationGetRelid(rel); + HeapTuple reltup; + TupleDesc tupdesc; + bool shared_relation; + bool mapped_relation; + Relation toast_rel; + Relation class_rel; + Oid toast_relid; + Oid namespaceid; + char toast_relname[NAMEDATALEN]; + ObjectAddress baseobject, + toastobject; + + if (rel->rd_rel->reltoastrelid != InvalidOid) + return false; + + if (!IsBinaryUpgrade) + { + if (!needs_toast_table(rel)) + return false; + } + else + { + if (!OidIsValid(binary_upgrade_next_toast_pg_class_oid)) + return false; + } + + if (check && lockmode != AccessExclusiveLock) + elog(ERROR, "AccessExclusiveLock required to add toast table."); + + snprintf(toast_relname, sizeof(toast_relname), + "pg_toast_%u", relOid); + + tupdesc = CreateTemplateTupleDesc(2); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, + "next_tid", + TIDOID, + -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, + "chunk_data", + BYTEAOID, + -1, 0); + + TupleDescAttr(tupdesc, 0)->attstorage = TYPSTORAGE_PLAIN; + TupleDescAttr(tupdesc, 1)->attstorage = TYPSTORAGE_PLAIN; + + TupleDescAttr(tupdesc, 0)->attcompression = InvalidCompressionMethod; + TupleDescAttr(tupdesc, 1)->attcompression = InvalidCompressionMethod; + + if (isTempOrTempToastNamespace(rel->rd_rel->relnamespace)) + namespaceid = GetTempToastNamespace(); + else + namespaceid = PG_TOAST_NAMESPACE; + + shared_relation = rel->rd_rel->relisshared; + + mapped_relation = RelationIsMapped(rel); + + toast_relid = heap_create_with_catalog(toast_relname, + namespaceid, + rel->rd_rel->reltablespace, + toastOid, + InvalidOid, + InvalidOid, + rel->rd_rel->relowner, + table_relation_toast_am(rel), + tupdesc, + NIL, + RELKIND_TOASTVALUE, + rel->rd_rel->relpersistence, + shared_relation, + mapped_relation, + ONCOMMIT_NOOP, + reloptions, + false, + true, + true, + OIDOldToast, + NULL); + Assert(toast_relid != InvalidOid); + + CommandCounterIncrement(); + + toast_rel = table_open(toast_relid, ShareLock); + + table_close(toast_rel, NoLock); + + class_rel = table_open(RelationRelationId, RowExclusiveLock); + + reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relOid)); + if (!HeapTupleIsValid(reltup)) + elog(ERROR, "cache lookup failed for relation %u", relOid); + + ((Form_pg_class) GETSTRUCT(reltup))->reltoastrelid = toast_relid; + + if (!IsBootstrapProcessingMode()) + { + /* normal case, use a transactional update */ + reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relOid)); + if (!HeapTupleIsValid(reltup)) + elog(ERROR, "cache lookup failed for relation %u", relOid); + + ((Form_pg_class) GETSTRUCT(reltup))->reltoastrelid = toast_relid; + + CatalogTupleUpdate(class_rel, &reltup->t_self, reltup); + } + else + { + /* While bootstrapping, we cannot UPDATE, so overwrite in-place */ + + ScanKeyData key[1]; + void *state; + + ScanKeyInit(&key[0], + Anum_pg_class_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relOid)); + systable_inplace_update_begin(class_rel, ClassOidIndexId, true, + NULL, 1, key, &reltup, &state); + if (!HeapTupleIsValid(reltup)) + elog(ERROR, "cache lookup failed for relation %u", relOid); + + ((Form_pg_class) GETSTRUCT(reltup))->reltoastrelid = toast_relid; + + systable_inplace_update_finish(state, reltup); + } + + heap_freetuple(reltup); + + table_close(class_rel, RowExclusiveLock); + + if (!IsBootstrapProcessingMode()) + { + baseobject.classId = RelationRelationId; + baseobject.objectId = relOid; + baseobject.objectSubId = 0; + toastobject.classId = RelationRelationId; + toastobject.objectId = toast_relid; + toastobject.objectSubId = 0; + + recordDependencyOn(&toastobject, &baseobject, DEPENDENCY_INTERNAL); + } + + CommandCounterIncrement(); + + return true; +} + +/* + * create_toast_table --- internal workhorse + * + * rel is already opened and locked + * toastOid and toastIndexOid are normally InvalidOid, but during + * bootstrap they can be nonzero to specify hand-assigned OIDs + */ +static bool +create_toast_table_default(Relation rel, Oid toastOid, Oid toastIndexOid, + Datum reloptions, LOCKMODE lockmode, bool check, + Oid OIDOldToast) { Oid relOid = RelationGetRelid(rel); HeapTuple reltup; diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index 0999a2b00b..bb13786c42 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -2151,6 +2151,15 @@ struct config_bool ConfigureNamesBool[] = NULL, NULL, NULL }, + { + {"toast_tid_enabled", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Enables TOAST tables with direct TIDs."), + }, + &toast_tid_enabled, + false, + NULL, NULL, NULL + }, + /* End-of-list marker */ { {NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL diff --git a/src/include/access/toast_external.h b/src/include/access/toast_external.h index 1a7c61454f..874e2612da 100644 --- a/src/include/access/toast_external.h +++ b/src/include/access/toast_external.h @@ -17,6 +17,7 @@ #include "access/attnum.h" #include "access/toast_compression.h" +#include "storage/itemptr.h" #include "utils/relcache.h" #include "varatt.h" @@ -43,6 +44,7 @@ typedef struct toast_external_data * them. InvalidToastId if invalid. */ uint64 value; + ItemPointer tid; } toast_external_data; /* diff --git a/src/include/access/toast_helper.h b/src/include/access/toast_helper.h index 729c593afe..a67460acc8 100644 --- a/src/include/access/toast_helper.h +++ b/src/include/access/toast_helper.h @@ -16,6 +16,8 @@ #include "utils/rel.h" +extern PGDLLIMPORT bool toast_tid_enabled; + /* * Information about one column of a tuple being toasted. * diff --git a/src/include/access/toast_internals.h b/src/include/access/toast_internals.h index 06ae8583c1..413ada8667 100644 --- a/src/include/access/toast_internals.h +++ b/src/include/access/toast_internals.h @@ -60,4 +60,14 @@ extern void toast_close_indexes(Relation *toastidxs, int num_indexes, LOCKMODE lock); extern Snapshot get_toast_snapshot(void); +extern struct varlena* +direct_tids_fetch_datum(Datum toast_ptr, int offset, int length); + +extern Datum +direct_tids_save_datum(Relation toast_rel, Datum value, Datum oldvalue, + int options); + +extern void +direct_tids_delete_datum(Relation rel, Datum value, bool is_speculative); + #endif /* TOAST_INTERNALS_H */ diff --git a/src/include/access/toast_type.h b/src/include/access/toast_type.h index 494c2a3e85..d0c3b5e163 100644 --- a/src/include/access/toast_type.h +++ b/src/include/access/toast_type.h @@ -19,6 +19,7 @@ * Detault value type in toast table. */ extern PGDLLIMPORT int default_toast_type; +extern PGDLLIMPORT bool toast_tid_enabled; typedef enum ToastTypeId { diff --git a/src/include/varatt.h b/src/include/varatt.h index aa36e8e1f5..f98ec75e54 100644 --- a/src/include/varatt.h +++ b/src/include/varatt.h @@ -41,6 +41,27 @@ typedef struct varatt_external_oid Oid va_toastrelid; /* RelID of TOAST table containing it */ } varatt_external_oid; +typedef struct TidBlk +{ + uint16 bi_hi; + uint16 bi_lo; +} TidBlk; + +typedef struct TmpTid +{ + TidBlk ip_blkid; + uint16 ip_posid; +} TmpTid; + +typedef struct varatt_external_tid +{ + 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 */ + TmpTid tid; /* Placeholder for ItemPointer */ +} varatt_external_tid; + /* * struct varatt_external_int8 is a "larger" version of "TOAST pointer", * that uses an 8-byte integer as value. @@ -64,7 +85,6 @@ typedef struct varatt_external_int8 Oid va_toastrelid; /* RelID of TOAST table containing it */ } varatt_external_int8; - /* * These macros define the "saved size" portion of va_extinfo. Its remaining * two high-order bits identify the compression method. @@ -115,6 +135,7 @@ typedef enum vartag_external VARTAG_EXPANDED_RO = 2, VARTAG_EXPANDED_RW = 3, VARTAG_ONDISK_INT8 = 4, + VARTAG_ONDISK_TID = 5, VARTAG_ONDISK_OID = 18 } vartag_external; @@ -122,11 +143,15 @@ typedef enum vartag_external #define VARTAG_IS_EXPANDED(tag) \ (((tag) & ~1) == VARTAG_EXPANDED_RO) +#define VARTAG_IS_DIRECTTIDS(tag) \ + (((tag) & ~4) == VARTAG_ONDISK_TID) + #define VARTAG_SIZE(tag) \ ((tag) == VARTAG_INDIRECT ? sizeof(varatt_indirect) : \ 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_TID ? sizeof(varatt_external_tid) : \ (AssertMacro(false), 0)) /* @@ -321,8 +346,10 @@ 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_DIRECT_TIDS(PTR) \ + (VARATT_IS_EXTERNAL(PTR) && VARTAG_EXTERNAL(PTR) == VARTAG_ONDISK_TID) #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_DIRECT_TIDS(PTR)) #define VARATT_IS_EXTERNAL_INDIRECT(PTR) \ (VARATT_IS_EXTERNAL(PTR) && VARTAG_EXTERNAL(PTR) == VARTAG_INDIRECT) #define VARATT_IS_EXTERNAL_EXPANDED_RO(PTR) \ @@ -393,5 +420,4 @@ typedef struct (VARATT_EXTERNAL_GET_EXTSIZE(toast_pointer) < \ (toast_pointer).va_rawsize - VARHDRSZ) - #endif -- 2.34.1