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

