v1-0001-direct-tids-toast.patch
application/octet-stream
Filename: v1-0001-direct-tids-toast.patch
Type: application/octet-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v1-0001
Subject: [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.
| File | + | − |
|---|---|---|
| src/backend/access/common/detoast.c | 17 | 2 |
| src/backend/access/common/toast_external.c | 65 | 0 |
| src/backend/access/common/toast_internals.c | 384 | 0 |
| src/backend/access/heap/heaptoast.c | 6 | 2 |
| src/backend/access/table/toast_helper.c | 26 | 3 |
| src/backend/catalog/toasting.c | 189 | 1 |
| src/backend/utils/misc/guc_tables.c | 9 | 0 |
| src/include/access/toast_external.h | 2 | 0 |
| src/include/access/toast_helper.h | 2 | 0 |
| src/include/access/toast_internals.h | 10 | 0 |
| src/include/access/toast_type.h | 1 | 0 |
| src/include/varatt.h | 29 | 3 |
From 105fa76fd903b91c9f7d9cd5a8103e88d00e6103 Mon Sep 17 00:00:00 2001
From: Nikita Malakhov <n.malakhov@postgrespro.ru>
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