agora inbox for [email protected]  
help / color / mirror / Atom feed
Re: tableam vs. TOAST
12+ messages / 4 participants
[nested] [flat]

* Re: tableam vs. TOAST
@ 2019-06-11 16:17 Robert Haas <[email protected]>
  2019-06-25 06:19 ` Re: tableam vs. TOAST Prabhat Sahu <[email protected]>
  0 siblings, 1 reply; 12+ messages in thread

From: Robert Haas @ 2019-06-11 16:17 UTC (permalink / raw)
  To: PostgreSQL Hackers <[email protected]>

On Tue, May 21, 2019 at 2:10 PM Robert Haas <[email protected]> wrote:
> Updated and rebased patches attached.

And again.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


Attachments:

  [application/octet-stream] v3-0003-Allow-TOAST-tables-to-be-implemented-using-table-.patch (36.3K, ../../CA+TgmoasEn823cDOrAsr_udy8qW5K2rYRBhyGsC914V95jg0gg@mail.gmail.com/2-v3-0003-Allow-TOAST-tables-to-be-implemented-using-table-.patch)
  download | inline diff:
From c4ec963a5a83c27dfd1e1a2787d88aa4fd01d95c Mon Sep 17 00:00:00 2001
From: Robert Haas <[email protected]>
Date: Tue, 11 Jun 2019 11:10:15 -0400
Subject: [PATCH v3 3/3] Allow TOAST tables to be implemented using table AMs
 other than heap.

toast_fetch_datum, toast_save_datum, and toast_delete_datum are
adjusted to use tableam rather than heap-specific functions.  This
might have some performance impact, but this patch attempts to
mitigate that by restructuring things so that we don't open and close
the toast table and indexes multiple times per tuple.

tableam now exposes an integer value (not a callback) for the
maximum TOAST chunk size, and has a new callback allowing table
AMs to specify the AM that should be used to implement the TOAST
table. Previously, the toast AM was always the same as the table AM.
---
 src/backend/access/common/detoast.c         |  60 ++++-----
 src/backend/access/common/toast_internals.c | 127 +++++++-------------
 src/backend/access/heap/heapam.c            |   6 +-
 src/backend/access/heap/heapam_handler.c    |  14 ++-
 src/backend/access/heap/heaptoast.c         |  19 ++-
 src/backend/access/index/genam.c            |  20 +++
 src/backend/access/table/toast_helper.c     | 107 ++++++++++++++---
 src/backend/catalog/toasting.c              |   2 +-
 src/include/access/genam.h                  |   5 +-
 src/include/access/heapam.h                 |   3 +-
 src/include/access/heaptoast.h              |   3 +-
 src/include/access/tableam.h                |  31 +++++
 src/include/access/toast_helper.h           |  18 ++-
 src/include/access/toast_internals.h        |  13 +-
 14 files changed, 282 insertions(+), 146 deletions(-)

diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c
index 54727bdf2c..e3a25c55ae 100644
--- a/src/backend/access/common/detoast.c
+++ b/src/backend/access/common/detoast.c
@@ -15,10 +15,11 @@
 
 #include "access/detoast.h"
 #include "access/genam.h"
-#include "access/heaptoast.h"
 #include "access/table.h"
 #include "access/toast_internals.h"
+#include "access/tableam.h"
 #include "common/pg_lzcompress.h"
+#include "executor/tuptable.h"
 #include "utils/expandeddatum.h"
 #include "utils/fmgroids.h"
 #include "utils/rel.h"
@@ -303,7 +304,7 @@ toast_fetch_datum(struct varlena *attr)
 	Relation   *toastidxs;
 	ScanKeyData toastkey;
 	SysScanDesc toastscan;
-	HeapTuple	ttup;
+	TupleTableSlot *slot;
 	TupleDesc	toasttupDesc;
 	struct varlena *result;
 	struct varatt_external toast_pointer;
@@ -312,11 +313,11 @@ toast_fetch_datum(struct varlena *attr)
 				nextidx;
 	int32		numchunks;
 	Pointer		chunk;
-	bool		isnull;
 	char	   *chunkdata;
 	int32		chunksize;
 	int			num_indexes;
 	int			validIndex;
+	int			max_chunk_size;
 	SnapshotData SnapshotToast;
 
 	if (!VARATT_IS_EXTERNAL_ONDISK(attr))
@@ -326,7 +327,6 @@ toast_fetch_datum(struct varlena *attr)
 	VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
 
 	ressize = toast_pointer.va_extsize;
-	numchunks = ((ressize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
 
 	result = (struct varlena *) palloc(ressize + VARHDRSZ);
 
@@ -341,6 +341,9 @@ toast_fetch_datum(struct varlena *attr)
 	toastrel = table_open(toast_pointer.va_toastrelid, AccessShareLock);
 	toasttupDesc = toastrel->rd_att;
 
+	max_chunk_size = toastrel->rd_tableam->toast_max_chunk_size;
+	numchunks = ((ressize - 1) / max_chunk_size) + 1;
+
 	/* Look for the valid index of the toast relation */
 	validIndex = toast_open_indexes(toastrel,
 									AccessShareLock,
@@ -367,15 +370,15 @@ toast_fetch_datum(struct varlena *attr)
 	init_toast_snapshot(&SnapshotToast);
 	toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
 										   &SnapshotToast, 1, &toastkey);
-	while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
+	while ((slot = systable_getnextslot_ordered(toastscan, ForwardScanDirection)) != NULL)
 	{
 		/*
 		 * Have a chunk, extract the sequence number and the data
 		 */
-		residx = DatumGetInt32(fastgetattr(ttup, 2, toasttupDesc, &isnull));
-		Assert(!isnull);
-		chunk = DatumGetPointer(fastgetattr(ttup, 3, toasttupDesc, &isnull));
-		Assert(!isnull);
+		slot_getsomeattrs(slot, 3);
+		Assert(!slot->tts_isnull[1] && !slot->tts_isnull[2]);
+		residx = DatumGetInt32(slot->tts_values[1]);
+		chunk = DatumGetPointer(slot->tts_values[2]);
 		if (!VARATT_IS_EXTENDED(chunk))
 		{
 			chunksize = VARSIZE(chunk) - VARHDRSZ;
@@ -407,19 +410,19 @@ toast_fetch_datum(struct varlena *attr)
 				 RelationGetRelationName(toastrel));
 		if (residx < numchunks - 1)
 		{
-			if (chunksize != TOAST_MAX_CHUNK_SIZE)
+			if (chunksize != max_chunk_size)
 				elog(ERROR, "unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s",
-					 chunksize, (int) TOAST_MAX_CHUNK_SIZE,
+					 chunksize, max_chunk_size,
 					 residx, numchunks,
 					 toast_pointer.va_valueid,
 					 RelationGetRelationName(toastrel));
 		}
 		else if (residx == numchunks - 1)
 		{
-			if ((residx * TOAST_MAX_CHUNK_SIZE + chunksize) != ressize)
+			if ((residx * max_chunk_size + chunksize) != ressize)
 				elog(ERROR, "unexpected chunk size %d (expected %d) in final chunk %d for toast value %u in %s",
 					 chunksize,
-					 (int) (ressize - residx * TOAST_MAX_CHUNK_SIZE),
+					 (int) (ressize - residx * max_chunk_size),
 					 residx,
 					 toast_pointer.va_valueid,
 					 RelationGetRelationName(toastrel));
@@ -434,7 +437,7 @@ toast_fetch_datum(struct varlena *attr)
 		/*
 		 * Copy the data into proper place in our result
 		 */
-		memcpy(VARDATA(result) + residx * TOAST_MAX_CHUNK_SIZE,
+		memcpy(VARDATA(result) + residx * max_chunk_size,
 			   chunkdata,
 			   chunksize);
 
@@ -498,6 +501,7 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length)
 	int32		chcpyend;
 	int			num_indexes;
 	int			validIndex;
+	int			max_chunk_size;
 	SnapshotData SnapshotToast;
 
 	if (!VARATT_IS_EXTERNAL_ONDISK(attr))
@@ -513,7 +517,6 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length)
 	Assert(!VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer));
 
 	attrsize = toast_pointer.va_extsize;
-	totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
 
 	if (sliceoffset >= attrsize)
 	{
@@ -531,19 +534,22 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length)
 	if (length == 0)
 		return result;			/* Can save a lot of work at this point! */
 
-	startchunk = sliceoffset / TOAST_MAX_CHUNK_SIZE;
-	endchunk = (sliceoffset + length - 1) / TOAST_MAX_CHUNK_SIZE;
-	numchunks = (endchunk - startchunk) + 1;
-
-	startoffset = sliceoffset % TOAST_MAX_CHUNK_SIZE;
-	endoffset = (sliceoffset + length - 1) % TOAST_MAX_CHUNK_SIZE;
-
 	/*
 	 * Open the toast relation and its indexes
 	 */
 	toastrel = table_open(toast_pointer.va_toastrelid, AccessShareLock);
 	toasttupDesc = toastrel->rd_att;
 
+	max_chunk_size = toastrel->rd_tableam->toast_max_chunk_size;
+	totalchunks = ((attrsize - 1) / max_chunk_size) + 1;
+
+	startchunk = sliceoffset / max_chunk_size;
+	endchunk = (sliceoffset + length - 1) / max_chunk_size;
+	numchunks = (endchunk - startchunk) + 1;
+
+	startoffset = sliceoffset % max_chunk_size;
+	endoffset = (sliceoffset + length - 1) % max_chunk_size;
+
 	/* Look for the valid index of toast relation */
 	validIndex = toast_open_indexes(toastrel,
 									AccessShareLock,
@@ -632,19 +638,19 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length)
 				 RelationGetRelationName(toastrel));
 		if (residx < totalchunks - 1)
 		{
-			if (chunksize != TOAST_MAX_CHUNK_SIZE)
+			if (chunksize != max_chunk_size)
 				elog(ERROR, "unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s when fetching slice",
-					 chunksize, (int) TOAST_MAX_CHUNK_SIZE,
+					 chunksize, max_chunk_size,
 					 residx, totalchunks,
 					 toast_pointer.va_valueid,
 					 RelationGetRelationName(toastrel));
 		}
 		else if (residx == totalchunks - 1)
 		{
-			if ((residx * TOAST_MAX_CHUNK_SIZE + chunksize) != attrsize)
+			if ((residx * max_chunk_size + chunksize) != attrsize)
 				elog(ERROR, "unexpected chunk size %d (expected %d) in final chunk %d for toast value %u in %s when fetching slice",
 					 chunksize,
-					 (int) (attrsize - residx * TOAST_MAX_CHUNK_SIZE),
+					 (int) (attrsize - residx * max_chunk_size),
 					 residx,
 					 toast_pointer.va_valueid,
 					 RelationGetRelationName(toastrel));
@@ -667,7 +673,7 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length)
 			chcpyend = endoffset;
 
 		memcpy(VARDATA(result) +
-			   (residx * TOAST_MAX_CHUNK_SIZE - sliceoffset) + chcpystrt,
+			   (residx * max_chunk_size - sliceoffset) + chcpystrt,
 			   chunkdata + chcpystrt,
 			   (chcpyend - chcpystrt) + 1);
 
diff --git a/src/backend/access/common/toast_internals.c b/src/backend/access/common/toast_internals.c
index a971242490..beb303034d 100644
--- a/src/backend/access/common/toast_internals.c
+++ b/src/backend/access/common/toast_internals.c
@@ -15,9 +15,8 @@
 
 #include "access/detoast.h"
 #include "access/genam.h"
-#include "access/heapam.h"
-#include "access/heaptoast.h"
 #include "access/table.h"
+#include "access/tableam.h"
 #include "access/toast_internals.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
@@ -100,22 +99,21 @@ toast_compress_datum(Datum value)
  *	Save one single datum into the secondary relation and return
  *	a Datum reference for it.
  *
- * rel: the main relation we're working with (not the toast rel!)
+ * toastrel: the TOAST relation we're working with (not the main rel!)
+ * toastslot: a slot corresponding to 'toastrel'
+ * num_indexes, toastidxs, validIndex: as returned by toast_open_indexes
+ * toastoid: the toast OID that should be inserted into the new TOAST pointer
  * value: datum to be pushed to toast storage
  * oldexternal: if not NULL, toast pointer previously representing the datum
- * options: options to be passed to heap_insert() for toast rows
+ * options: options to be passed to table_tuple_insert() for toast rows
  * ----------
  */
 Datum
-toast_save_datum(Relation rel, Datum value,
-				 struct varlena *oldexternal, int options)
+toast_save_datum(Relation toastrel, TupleTableSlot *toastslot,
+				 int num_indexes, Relation *toastidxs, int validIndex,
+				 Oid toastoid, Datum value, struct varlena *oldexternal,
+				 int options, int max_chunk_size)
 {
-	Relation	toastrel;
-	Relation   *toastidxs;
-	HeapTuple	toasttup;
-	TupleDesc	toasttupDesc;
-	Datum		t_values[3];
-	bool		t_isnull[3];
 	CommandId	mycid = GetCurrentCommandId(true);
 	struct varlena *result;
 	struct varatt_external toast_pointer;
@@ -123,7 +121,7 @@ toast_save_datum(Relation rel, Datum value,
 	{
 		struct varlena hdr;
 		/* this is to make the union big enough for a chunk: */
-		char		data[TOAST_MAX_CHUNK_SIZE + VARHDRSZ];
+		char		data[BLCKSZ + VARHDRSZ];
 		/* ensure union is aligned well enough: */
 		int32		align_it;
 	}			chunk_data;
@@ -132,24 +130,9 @@ toast_save_datum(Relation rel, Datum value,
 	char	   *data_p;
 	int32		data_todo;
 	Pointer		dval = DatumGetPointer(value);
-	int			num_indexes;
-	int			validIndex;
 
 	Assert(!VARATT_IS_EXTERNAL(value));
-
-	/*
-	 * Open the toast relation and its indexes.  We can use the index to check
-	 * uniqueness of the OID we assign to the toasted item, even though it has
-	 * additional columns besides OID.
-	 */
-	toastrel = table_open(rel->rd_rel->reltoastrelid, RowExclusiveLock);
-	toasttupDesc = toastrel->rd_att;
-
-	/* Open all the toast indexes and look for the valid one */
-	validIndex = toast_open_indexes(toastrel,
-									RowExclusiveLock,
-									&toastidxs,
-									&num_indexes);
+	Assert(max_chunk_size <= BLCKSZ);
 
 	/*
 	 * Get the data pointer and length, and compute va_rawsize and va_extsize.
@@ -189,11 +172,11 @@ toast_save_datum(Relation rel, Datum value,
 	 *
 	 * Normally this is the actual OID of the target toast table, but during
 	 * table-rewriting operations such as CLUSTER, we have to insert the OID
-	 * of the table's real permanent toast table instead.  rd_toastoid is set
+	 * of the table's real permanent toast table instead.  toastoid is set
 	 * if we have to substitute such an OID.
 	 */
-	if (OidIsValid(rel->rd_toastoid))
-		toast_pointer.va_toastrelid = rel->rd_toastoid;
+	if (OidIsValid(toastoid))
+		toast_pointer.va_toastrelid = toastoid;
 	else
 		toast_pointer.va_toastrelid = RelationGetRelid(toastrel);
 
@@ -209,7 +192,7 @@ toast_save_datum(Relation rel, Datum value,
 	 * options have been changed), we have to pick a value ID that doesn't
 	 * conflict with either new or existing toast value OIDs.
 	 */
-	if (!OidIsValid(rel->rd_toastoid))
+	if (!OidIsValid(toastoid))
 	{
 		/* normal case: just choose an unused OID */
 		toast_pointer.va_valueid =
@@ -228,7 +211,7 @@ toast_save_datum(Relation rel, Datum value,
 			Assert(VARATT_IS_EXTERNAL_ONDISK(oldexternal));
 			/* Must copy to access aligned fields */
 			VARATT_EXTERNAL_GET_POINTER(old_toast_pointer, oldexternal);
-			if (old_toast_pointer.va_toastrelid == rel->rd_toastoid)
+			if (old_toast_pointer.va_toastrelid == toastoid)
 			{
 				/* This value came from the old toast table; reuse its OID */
 				toast_pointer.va_valueid = old_toast_pointer.va_valueid;
@@ -270,20 +253,11 @@ toast_save_datum(Relation rel, Datum value,
 					GetNewOidWithIndex(toastrel,
 									   RelationGetRelid(toastidxs[validIndex]),
 									   (AttrNumber) 1);
-			} while (toastid_valueid_exists(rel->rd_toastoid,
+			} while (toastid_valueid_exists(toastoid,
 											toast_pointer.va_valueid));
 		}
 	}
 
-	/*
-	 * Initialize constant parts of the tuple data
-	 */
-	t_values[0] = ObjectIdGetDatum(toast_pointer.va_valueid);
-	t_values[2] = PointerGetDatum(&chunk_data);
-	t_isnull[0] = false;
-	t_isnull[1] = false;
-	t_isnull[2] = false;
-
 	/*
 	 * Split up the item into chunks
 	 */
@@ -296,17 +270,22 @@ toast_save_datum(Relation rel, Datum value,
 		/*
 		 * Calculate the size of this chunk
 		 */
-		chunk_size = Min(TOAST_MAX_CHUNK_SIZE, data_todo);
+		chunk_size = Min(max_chunk_size, data_todo);
 
 		/*
 		 * Build a tuple and store it
 		 */
-		t_values[1] = Int32GetDatum(chunk_seq++);
+		toastslot->tts_values[0] = ObjectIdGetDatum(toast_pointer.va_valueid);
+		toastslot->tts_values[1] = Int32GetDatum(chunk_seq++);
 		SET_VARSIZE(&chunk_data, chunk_size + VARHDRSZ);
 		memcpy(VARDATA(&chunk_data), data_p, chunk_size);
-		toasttup = heap_form_tuple(toasttupDesc, t_values, t_isnull);
+		toastslot->tts_values[2] = PointerGetDatum(&chunk_data);
+		toastslot->tts_isnull[0] = false;
+		toastslot->tts_isnull[1] = false;
+		toastslot->tts_isnull[2] = false;
+		ExecStoreVirtualTuple(toastslot);
 
-		heap_insert(toastrel, toasttup, mycid, options, NULL);
+		table_tuple_insert(toastrel, toastslot, mycid, options, NULL);
 
 		/*
 		 * Create the index entry.  We cheat a little here by not using
@@ -323,8 +302,9 @@ toast_save_datum(Relation rel, Datum value,
 		{
 			/* Only index relations marked as ready can be updated */
 			if (toastidxs[i]->rd_index->indisready)
-				index_insert(toastidxs[i], t_values, t_isnull,
-							 &(toasttup->t_self),
+				index_insert(toastidxs[i], toastslot->tts_values,
+							 toastslot->tts_isnull,
+							 &(toastslot->tts_tid),
 							 toastrel,
 							 toastidxs[i]->rd_index->indisunique ?
 							 UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
@@ -332,9 +312,9 @@ toast_save_datum(Relation rel, Datum value,
 		}
 
 		/*
-		 * Free memory
+		 * Clear slot
 		 */
-		heap_freetuple(toasttup);
+		ExecClearTuple(toastslot);
 
 		/*
 		 * Move on to next chunk
@@ -343,12 +323,6 @@ toast_save_datum(Relation rel, Datum value,
 		data_p += chunk_size;
 	}
 
-	/*
-	 * Done - close toast relation and its indexes
-	 */
-	toast_close_indexes(toastidxs, num_indexes, RowExclusiveLock);
-	table_close(toastrel, RowExclusiveLock);
-
 	/*
 	 * Create the TOAST pointer value that we'll return
 	 */
@@ -366,35 +340,24 @@ toast_save_datum(Relation rel, Datum value,
  * ----------
  */
 void
-toast_delete_datum(Relation rel, Datum value, bool is_speculative)
+toast_delete_datum(Relation toastrel, int num_indexes, Relation *toastidxs,
+				   int validIndex, Datum value, bool is_speculative,
+				   uint32 specToken)
 {
 	struct varlena *attr = (struct varlena *) DatumGetPointer(value);
 	struct varatt_external toast_pointer;
-	Relation	toastrel;
-	Relation   *toastidxs;
 	ScanKeyData toastkey;
 	SysScanDesc toastscan;
-	HeapTuple	toasttup;
-	int			num_indexes;
-	int			validIndex;
+	TupleTableSlot *slot;
 	SnapshotData SnapshotToast;
 
-	if (!VARATT_IS_EXTERNAL_ONDISK(attr))
-		return;
+	Assert(VARATT_IS_EXTERNAL_ONDISK(attr));
 
 	/* Must copy to access aligned fields */
 	VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
 
-	/*
-	 * Open the toast relation and its indexes
-	 */
-	toastrel = table_open(toast_pointer.va_toastrelid, RowExclusiveLock);
-
-	/* Fetch valid relation used for process */
-	validIndex = toast_open_indexes(toastrel,
-									RowExclusiveLock,
-									&toastidxs,
-									&num_indexes);
+	/* Check that caller gave us the correct TOAST relation. */
+	Assert(toast_pointer.va_toastrelid == RelationGetRelid(toastrel));
 
 	/*
 	 * Setup a scan key to find chunks with matching va_valueid
@@ -412,23 +375,19 @@ toast_delete_datum(Relation rel, Datum value, bool is_speculative)
 	init_toast_snapshot(&SnapshotToast);
 	toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
 										   &SnapshotToast, 1, &toastkey);
-	while ((toasttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
+	while ((slot = systable_getnextslot_ordered(toastscan, ForwardScanDirection)) != NULL)
 	{
 		/*
 		 * Have a chunk, delete it
 		 */
 		if (is_speculative)
-			heap_abort_speculative(toastrel, &toasttup->t_self);
+			table_tuple_complete_speculative(toastrel, slot, specToken, false);
 		else
-			simple_heap_delete(toastrel, &toasttup->t_self);
+			simple_table_tuple_delete(toastrel, &slot->tts_tid, &SnapshotToast);
 	}
 
-	/*
-	 * End scan and close relations
-	 */
+	/* End scan */
 	systable_endscan_ordered(toastscan);
-	toast_close_indexes(toastidxs, num_indexes, RowExclusiveLock);
-	table_close(toastrel, RowExclusiveLock);
 }
 
 /* ----------
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index d32b638dd9..a5038ff5ad 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2809,7 +2809,7 @@ l1:
 		Assert(!HeapTupleHasExternal(&tp));
 	}
 	else if (HeapTupleHasExternal(&tp))
-		toast_delete(relation, &tp, false);
+		toast_delete(relation, &tp, false, 0);
 
 	/*
 	 * Mark tuple for invalidation from system caches at next command
@@ -5540,7 +5540,7 @@ heap_finish_speculative(Relation relation, ItemPointer tid)
  * confirmation records.
  */
 void
-heap_abort_speculative(Relation relation, ItemPointer tid)
+heap_abort_speculative(Relation relation, ItemPointer tid, uint32 specToken)
 {
 	TransactionId xid = GetCurrentTransactionId();
 	ItemId		lp;
@@ -5649,7 +5649,7 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 	if (HeapTupleHasExternal(&tp))
 	{
 		Assert(!IsToastRelation(relation));
-		toast_delete(relation, &tp, true);
+		toast_delete(relation, &tp, true, specToken);
 	}
 
 	/*
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 468f045434..4cedcf683e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -30,6 +30,7 @@
 #include "access/rewriteheap.h"
 #include "access/tableam.h"
 #include "access/tsmapi.h"
+#include "access/heaptoast.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
 #include "catalog/index.h"
@@ -295,7 +296,7 @@ heapam_tuple_complete_speculative(Relation relation, TupleTableSlot *slot,
 	if (succeeded)
 		heap_finish_speculative(relation, &slot->tts_tid);
 	else
-		heap_abort_speculative(relation, &slot->tts_tid);
+		heap_abort_speculative(relation, &slot->tts_tid, specToken);
 
 	if (shouldFree)
 		pfree(tuple);
@@ -2062,6 +2063,15 @@ heapam_relation_needs_toast_table(Relation rel)
 	return (tuple_length > TOAST_TUPLE_THRESHOLD);
 }
 
+/*
+ * TOAST tables for heap relations are just heap relations.
+ */
+static Oid
+heapam_relation_toast_am(Relation rel)
+{
+	return rel->rd_rel->relam;
+}
+
 
 /* ------------------------------------------------------------------------
  * Planner related callbacks for the heap AM
@@ -2646,6 +2656,8 @@ static const TableAmRoutine heapam_methods = {
 
 	.relation_size = heapam_relation_size,
 	.relation_needs_toast_table = heapam_relation_needs_toast_table,
+	.relation_toast_am = heapam_relation_toast_am,
+	.toast_max_chunk_size = TOAST_MAX_CHUNK_SIZE,
 
 	.relation_estimate_size = heapam_estimate_rel_size,
 
diff --git a/src/backend/access/heap/heaptoast.c b/src/backend/access/heap/heaptoast.c
index 3a2118e1d8..1d4ad5b336 100644
--- a/src/backend/access/heap/heaptoast.c
+++ b/src/backend/access/heap/heaptoast.c
@@ -38,7 +38,8 @@
  * ----------
  */
 void
-toast_delete(Relation rel, HeapTuple oldtup, bool is_speculative)
+toast_delete(Relation rel, HeapTuple oldtup, bool is_speculative,
+			 uint32 specToken)
 {
 	TupleDesc	tupleDesc;
 	Datum		toast_values[MaxHeapAttributeNumber];
@@ -68,7 +69,8 @@ toast_delete(Relation rel, HeapTuple oldtup, bool is_speculative)
 	heap_deform_tuple(oldtup, tupleDesc, toast_values, toast_isnull);
 
 	/* Do the real work. */
-	toast_delete_external(rel, toast_values, toast_isnull, is_speculative);
+	toast_delete_external(rel, toast_values, toast_isnull, is_speculative,
+						  specToken);
 }
 
 
@@ -151,6 +153,8 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 		ttc.ttc_oldvalues = toast_oldvalues;
 		ttc.ttc_oldisnull = toast_oldisnull;
 	}
+	ttc.ttc_toastrel = NULL;
+	ttc.ttc_toastslot = NULL;
 	ttc.ttc_attr = toast_attr;
 	toast_tuple_init(&ttc);
 
@@ -207,7 +211,8 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 		 */
 		if (toast_attr[biggest_attno].tai_size > maxDataLen &&
 			rel->rd_rel->reltoastrelid != InvalidOid)
-			toast_tuple_externalize(&ttc, biggest_attno, options);
+			toast_tuple_externalize(&ttc, biggest_attno, options,
+									TOAST_MAX_CHUNK_SIZE);
 	}
 
 	/*
@@ -224,7 +229,8 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 		biggest_attno = toast_tuple_find_biggest_attribute(&ttc, false, false);
 		if (biggest_attno < 0)
 			break;
-		toast_tuple_externalize(&ttc, biggest_attno, options);
+		toast_tuple_externalize(&ttc, biggest_attno, options,
+								TOAST_MAX_CHUNK_SIZE);
 	}
 
 	/*
@@ -260,7 +266,8 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 		if (biggest_attno < 0)
 			break;
 
-		toast_tuple_externalize(&ttc, biggest_attno, options);
+		toast_tuple_externalize(&ttc, biggest_attno, options,
+								TOAST_MAX_CHUNK_SIZE);
 	}
 
 	/*
@@ -323,7 +330,7 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 	else
 		result_tuple = newtup;
 
-	toast_tuple_cleanup(&ttc);
+	toast_tuple_cleanup(&ttc, true);
 
 	return result_tuple;
 }
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 42aaa5bad6..4264bad4e7 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -642,6 +642,26 @@ systable_getnext_ordered(SysScanDesc sysscan, ScanDirection direction)
 	return htup;
 }
 
+/*
+ * systable_getnextslot_ordered
+ *
+ * Return a slot containing the next tuple from an ordered catalog scan,
+ * or NULL if there are no more tuples.
+ */
+TupleTableSlot *
+systable_getnextslot_ordered(SysScanDesc sysscan, ScanDirection direction)
+{
+	Assert(sysscan->irel);
+	if (!index_getnext_slot(sysscan->iscan, direction, sysscan->slot))
+		return NULL;
+
+	/* See notes in systable_getnext */
+	if (sysscan->iscan->xs_recheck)
+		elog(ERROR, "system catalog scans with lossy index conditions are not implemented");
+
+	return sysscan->slot;
+}
+
 /*
  * systable_endscan_ordered --- close scan, release resources
  */
diff --git a/src/backend/access/table/toast_helper.c b/src/backend/access/table/toast_helper.c
index ee119cea2d..91ea50616d 100644
--- a/src/backend/access/table/toast_helper.c
+++ b/src/backend/access/table/toast_helper.c
@@ -17,6 +17,7 @@
 #include "access/detoast.h"
 #include "access/table.h"
 #include "access/toast_helper.h"
+#include "access/tableam.h"
 #include "access/toast_internals.h"
 
 /*
@@ -247,26 +248,49 @@ toast_tuple_try_compression(ToastTupleContext *ttc, int attribute)
  * Move an attribute to external storage.
  */
 void
-toast_tuple_externalize(ToastTupleContext *ttc, int attribute, int options)
+toast_tuple_externalize(ToastTupleContext *ttc, int attribute, int options,
+						int max_chunk_size)
 {
 	Datum  *value = &ttc->ttc_values[attribute];
 	Datum	old_value = *value;
 	ToastAttrInfo  *attr = &ttc->ttc_attr[attribute];
 
-	attr->tai_colflags |= TOASTCOL_IGNORE;
-	*value = toast_save_datum(ttc->ttc_rel, old_value, attr->tai_oldexternal,
-							  options);
+	/* Initialize for TOAST table access, if not yet done. */
+	if (ttc->ttc_toastrel == NULL)
+	{
+		ttc->ttc_toastrel =
+			table_open(ttc->ttc_rel->rd_rel->reltoastrelid, RowExclusiveLock);
+		ttc->ttc_validtoastidx = toast_open_indexes(ttc->ttc_toastrel,
+													RowExclusiveLock,
+													&ttc->ttc_toastidxs,
+													&ttc->ttc_ntoastidxs);
+	}
+	if (ttc->ttc_toastslot == NULL)
+		ttc->ttc_toastslot = table_slot_create(ttc->ttc_toastrel, NULL);
+
+	/* Do the real work. */
+	*value = toast_save_datum(ttc->ttc_toastrel, ttc->ttc_toastslot,
+							  ttc->ttc_ntoastidxs, ttc->ttc_toastidxs,
+							  ttc->ttc_validtoastidx,
+							  ttc->ttc_rel->rd_toastoid,
+							  old_value, attr->tai_oldexternal,
+							  options, max_chunk_size);
+
+	/* Update bookkeeping information. */
 	if ((attr->tai_colflags & TOASTCOL_NEEDS_FREE) != 0)
 		pfree(DatumGetPointer(old_value));
-	attr->tai_colflags |= TOASTCOL_NEEDS_FREE;
+	attr->tai_colflags |= (TOASTCOL_NEEDS_FREE | TOASTCOL_IGNORE);
 	ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE);
 }
 
 /*
  * Perform appropriate cleanup after one tuple has been subjected to TOAST.
+ *
+ * Pass cleanup_toastrel as true to destroy and clear ttc_toastrel and
+ * ttc_toastslot, or false if caller will do it.
  */
 void
-toast_tuple_cleanup(ToastTupleContext *ttc)
+toast_tuple_cleanup(ToastTupleContext *ttc, bool cleanup_toastrel)
 {
 	TupleDesc	tupleDesc = ttc->ttc_rel->rd_att;
 	int			numAttrs = tupleDesc->natts;
@@ -294,14 +318,46 @@ toast_tuple_cleanup(ToastTupleContext *ttc)
 	{
 		int		i;
 
+		/* Initialize for TOAST table access, if not yet done. */
+		if (ttc->ttc_toastrel == NULL)
+		{
+			ttc->ttc_toastrel =
+				table_open(ttc->ttc_rel->rd_rel->reltoastrelid,
+						   RowExclusiveLock);
+			ttc->ttc_validtoastidx = toast_open_indexes(ttc->ttc_toastrel,
+														RowExclusiveLock,
+														&ttc->ttc_toastidxs,
+														&ttc->ttc_ntoastidxs);
+		}
+
+		/* Delete those attributes which require it. */
 		for (i = 0; i < numAttrs; i++)
 		{
 			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);
+				toast_delete_datum(ttc->ttc_toastrel, ttc->ttc_ntoastidxs,
+								   ttc->ttc_toastidxs, ttc->ttc_validtoastidx,
+								   ttc->ttc_oldvalues[i], false, 0);
 		}
 	}
+
+	/*
+	 * Close toast table and indexes and drop slot, if previously done and
+	 * if caller requests it.
+	 */
+	if (cleanup_toastrel && ttc->ttc_toastrel != NULL)
+	{
+		if (ttc->ttc_toastslot != NULL)
+		{
+			ExecDropSingleTupleTableSlot(ttc->ttc_toastslot);
+			ttc->ttc_toastslot = NULL;
+		}
+		toast_close_indexes(ttc->ttc_toastidxs, ttc->ttc_ntoastidxs,
+							RowExclusiveLock);
+		table_close(ttc->ttc_toastrel, RowExclusiveLock);
+		ttc->ttc_toastrel = NULL;
+	}
 }
 
 /*
@@ -310,22 +366,43 @@ toast_tuple_cleanup(ToastTupleContext *ttc)
  */
 void
 toast_delete_external(Relation rel, Datum *values, bool *isnull,
-					  bool is_speculative)
+					  bool is_speculative, uint32 specToken)
 {
 	TupleDesc	tupleDesc = rel->rd_att;
 	int			numAttrs = tupleDesc->natts;
 	int			i;
+	Relation    toastrel = NULL;
+	Relation   *toastidxs;
+	int         num_indexes;
+	int         validIndex;
 
 	for (i = 0; i < numAttrs; i++)
 	{
-		if (TupleDescAttr(tupleDesc, i)->attlen == -1)
-		{
-			Datum		value = values[i];
+		Datum	value;
+
+		if (isnull[i] || TupleDescAttr(tupleDesc, i)->attlen != -1)
+			continue;
+
+		value = values[i];
+		if (!VARATT_IS_EXTERNAL_ONDISK(PointerGetDatum(value)))
+			continue;
 
-			if (isnull[i])
-				continue;
-			else if (VARATT_IS_EXTERNAL_ONDISK(PointerGetDatum(value)))
-				toast_delete_datum(rel, value, is_speculative);
+		/* Initialize for TOAST table access, if not yet done. */
+		if (toastrel == NULL)
+		{
+			toastrel = table_open(rel->rd_rel->reltoastrelid,
+								  RowExclusiveLock);
+			validIndex = toast_open_indexes(toastrel, RowExclusiveLock,
+											&toastidxs, &num_indexes);
 		}
+
+		toast_delete_datum(toastrel, num_indexes, toastidxs, validIndex,
+						   value, is_speculative, specToken);
+	}
+
+	if (toastrel != NULL)
+	{
+		toast_close_indexes(toastidxs, num_indexes, RowExclusiveLock);
+		table_close(toastrel, RowExclusiveLock);
 	}
 }
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index de6282a667..f082463bf6 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -258,7 +258,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 										   toast_typid,
 										   InvalidOid,
 										   rel->rd_rel->relowner,
-										   rel->rd_rel->relam,
+										   table_relation_toast_am(rel),
 										   tupdesc,
 										   NIL,
 										   RELKIND_TOASTVALUE,
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index 8c053be2ca..a8f5076420 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -21,8 +21,9 @@
 #include "utils/relcache.h"
 #include "utils/snapshot.h"
 
-/* We don't want this file to depend on execnodes.h. */
+/* We don't want this file to depend on execnodes.h or tuptable.h. */
 struct IndexInfo;
+struct TupleTableSlot;
 
 /*
  * Struct for statistics returned by ambuild
@@ -212,6 +213,8 @@ extern SysScanDesc systable_beginscan_ordered(Relation heapRelation,
 											  int nkeys, ScanKey key);
 extern HeapTuple systable_getnext_ordered(SysScanDesc sysscan,
 										  ScanDirection direction);
+extern struct TupleTableSlot *systable_getnextslot_ordered(SysScanDesc sysscan,
+														   ScanDirection direction);
 extern void systable_endscan_ordered(SysScanDesc sysscan);
 
 #endif							/* GENAM_H */
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index dffb57bf11..256655952e 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -143,7 +143,8 @@ extern TM_Result heap_delete(Relation relation, ItemPointer tid,
 							 CommandId cid, Snapshot crosscheck, bool wait,
 							 struct TM_FailureData *tmfd, bool changingPart);
 extern void heap_finish_speculative(Relation relation, ItemPointer tid);
-extern void heap_abort_speculative(Relation relation, ItemPointer tid);
+extern void heap_abort_speculative(Relation relation, ItemPointer tid,
+					   uint32 specToken);
 extern TM_Result heap_update(Relation relation, ItemPointer otid,
 							 HeapTuple newtup,
 							 CommandId cid, Snapshot crosscheck, bool wait,
diff --git a/src/include/access/heaptoast.h b/src/include/access/heaptoast.h
index bf02d2c600..07d36ac968 100644
--- a/src/include/access/heaptoast.h
+++ b/src/include/access/heaptoast.h
@@ -104,7 +104,8 @@ extern HeapTuple toast_insert_or_update(Relation rel,
  *	Called by heap_delete().
  * ----------
  */
-extern void toast_delete(Relation rel, HeapTuple oldtup, bool is_speculative);
+extern void toast_delete(Relation rel, HeapTuple oldtup,
+			 bool is_speculative, uint32 specToken);
 
 /* ----------
  * toast_flatten_tuple -
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index fd07773b74..bf0192f976 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -581,6 +581,27 @@ typedef struct TableAmRoutine
 	 */
 	bool		(*relation_needs_toast_table) (Relation rel);
 
+	/*
+	 * This callback should return the OID of the table AM that implements
+	 * TOAST tables for this AM.  If the relation_needs_toast_table callback
+	 * always returns false, this callback is not required.
+	 */
+	Oid		    (*relation_toast_am) (Relation rel);
+
+	/*
+	 * If this table AM can be used to implement a TOAST table, the following
+	 * field should be set to the maximum number of bytes that can be stored
+	 * in a single TOAST chunk.  It must not be set to a value greater than
+	 * BLCKSZ.  If this table AM is not used to implement a TOAST table, this
+	 * value is ignored.
+	 *
+	 * (Note that there is no requirement that the TOAST table be implemented
+	 * using the same AM as the table to which it is attached.  If this AM
+	 * has TOAST tables but uses some other AM to implement them, this value
+	 * is ignored; it is a property of the TOAST table, not the parent table.)
+	 */
+	int			toast_max_chunk_size;
+
 
 	/* ------------------------------------------------------------------------
 	 * Planner related functions.
@@ -1604,6 +1625,16 @@ table_relation_needs_toast_table(Relation rel)
 	return rel->rd_tableam->relation_needs_toast_table(rel);
 }
 
+/*
+ * Return the OID of the AM that should be used to implement the TOAST table
+ * for this relation.
+ */
+static inline Oid
+table_relation_toast_am(Relation rel)
+{
+	return rel->rd_tableam->relation_toast_am(rel);
+}
+
 
 /* ----------------------------------------------------------------------------
  * Planner related functionality
diff --git a/src/include/access/toast_helper.h b/src/include/access/toast_helper.h
index 0fac6cc772..5d223d36ea 100644
--- a/src/include/access/toast_helper.h
+++ b/src/include/access/toast_helper.h
@@ -14,6 +14,7 @@
 #ifndef TOAST_HELPER_H
 #define TOAST_HELPER_H
 
+#include "executor/tuptable.h"
 #include "utils/rel.h"
 
 /*
@@ -51,6 +52,17 @@ typedef struct
 	Datum	   *ttc_oldvalues;		/* values from previous tuple */
 	bool	   *ttc_oldisnull;		/* null flags from previous tuple */
 
+	/*
+	 * Before calling toast_tuple_init, the caller should either initialize
+	 * all of these fields or else set ttc_toastrel and ttc_toastslot to NULL.
+	 * In the latter case, all of the fields will be initialized as required.
+	 */
+	Relation	ttc_toastrel;		/* the toast table for the relation */
+	TupleTableSlot *ttc_toastslot;	/* a slot for the toast table */
+	int			ttc_ntoastidxs;		/* # of toast indexes for toast table */
+	Relation   *ttc_toastidxs;		/* array of those toast indexes */
+	int			ttc_validtoastidx;	/* the valid toast index */
+
 	/*
 	 * Before calling toast_tuple_init, the caller should set tts_attr to
 	 * point to an array of ToastAttrInfo structures of a length equal to
@@ -105,10 +117,10 @@ extern int toast_tuple_find_biggest_attribute(ToastTupleContext *ttc,
 								   bool for_compression, bool check_main);
 extern void toast_tuple_try_compression(ToastTupleContext *ttc, int attribute);
 extern void toast_tuple_externalize(ToastTupleContext *ttc, int attribute,
-						int options);
-extern void toast_tuple_cleanup(ToastTupleContext *ttc);
+						int options, int max_chunk_size);
+extern void toast_tuple_cleanup(ToastTupleContext *ttc, bool cleanup_toastrel);
 
 extern void toast_delete_external(Relation rel, Datum *values, bool *isnull,
-					  bool is_speculative);
+					  bool is_speculative, uint32 specToken);
 
 #endif
diff --git a/src/include/access/toast_internals.h b/src/include/access/toast_internals.h
index 8ac367fb9f..67ef5a134f 100644
--- a/src/include/access/toast_internals.h
+++ b/src/include/access/toast_internals.h
@@ -16,6 +16,8 @@
 #include "utils/relcache.h"
 #include "utils/snapshot.h"
 
+struct TupleTableSlot;
+
 /*
  *	The information at the start of the compressed toast data.
  */
@@ -39,9 +41,14 @@ typedef struct toast_compress_header
 extern Datum toast_compress_datum(Datum value);
 extern Oid	toast_get_valid_index(Oid toastoid, LOCKMODE lock);
 
-extern void toast_delete_datum(Relation rel, Datum value, bool is_speculative);
-extern Datum toast_save_datum(Relation rel, Datum value,
-				 struct varlena *oldexternal, int options);
+extern void toast_delete_datum(Relation toastrel, int num_indexes,
+				   Relation *toastidxs, int validIndex,
+				   Datum value, bool is_speculative, uint32 specToken);
+extern Datum toast_save_datum(Relation toastrel,
+				 struct TupleTableSlot *toastslot,
+				 int num_indexes, Relation *toastidxs, int validIndex,
+				 Oid toastoid, Datum value, struct varlena *oldexternal,
+				 int options, int max_chunk_size);
 
 extern int toast_open_indexes(Relation toastrel,
 				   LOCKMODE lock,
-- 
2.17.2 (Apple Git-113)



  [application/octet-stream] v3-0001-Split-tuptoaster.c-into-three-separate-files.patch (163.4K, ../../CA+TgmoasEn823cDOrAsr_udy8qW5K2rYRBhyGsC914V95jg0gg@mail.gmail.com/3-v3-0001-Split-tuptoaster.c-into-three-separate-files.patch)
  download | inline diff:
From 1541ef5bc81b22025320a7fb6b1b8412900f3528 Mon Sep 17 00:00:00 2001
From: Robert Haas <[email protected]>
Date: Wed, 15 May 2019 13:33:16 -0400
Subject: [PATCH v3 1/3] Split tuptoaster.c into three separate files.

detoast.c/h contain functions required to detoast a datum, partially
or completely, plus a few other utility functions for examining the
size of toasted datums.

toast_internals.c/h contain functions that are used internally to the
TOAST subsystem but which (mostly) do not need to be accessed from
outside.

heaptoast.c/h contains code that is intrinsically specific to the
heap AM, either because it operates on HeapTuples or is based on the
layout of a heap page.

detoast.c and toast_internals.c are placed in
src/backend/access/common rather than src/backend/access/heap.  At
present, both files still have dependencies on the heap, but that will
be improved in a future commit.
---
 doc/src/sgml/storage.sgml                     |    2 +-
 src/backend/access/common/Makefile            |    5 +-
 src/backend/access/common/detoast.c           |  859 ++++++
 src/backend/access/common/heaptuple.c         |    4 +-
 src/backend/access/common/indextuple.c        |    9 +-
 src/backend/access/common/reloptions.c        |    2 +-
 src/backend/access/common/toast_internals.c   |  632 +++++
 src/backend/access/heap/Makefile              |    2 +-
 src/backend/access/heap/heapam.c              |    2 +-
 src/backend/access/heap/heapam_handler.c      |    2 +-
 src/backend/access/heap/heaptoast.c           |  917 +++++++
 src/backend/access/heap/rewriteheap.c         |    2 +-
 src/backend/access/heap/tuptoaster.c          | 2411 -----------------
 src/backend/access/transam/xlog.c             |    2 +-
 src/backend/commands/analyze.c                |    2 +-
 src/backend/commands/cluster.c                |    2 +-
 src/backend/executor/execExprInterp.c         |    2 +-
 src/backend/executor/execTuples.c             |    2 +-
 src/backend/executor/tstoreReceiver.c         |    2 +-
 .../replication/logical/reorderbuffer.c       |    2 +-
 src/backend/statistics/extended_stats.c       |    2 +-
 src/backend/storage/large_object/inv_api.c    |    3 +-
 src/backend/utils/adt/array_typanalyze.c      |    2 +-
 src/backend/utils/adt/datum.c                 |    2 +-
 src/backend/utils/adt/expandedrecord.c        |    3 +-
 src/backend/utils/adt/rowtypes.c              |    2 +-
 src/backend/utils/adt/tsgistidx.c             |    2 +-
 src/backend/utils/adt/varchar.c               |    2 +-
 src/backend/utils/adt/varlena.c               |    2 +-
 src/backend/utils/cache/catcache.c            |    2 +-
 src/backend/utils/fmgr/fmgr.c                 |    2 +-
 src/bin/pg_resetwal/pg_resetwal.c             |    2 +-
 src/include/access/detoast.h                  |   92 +
 .../access/{tuptoaster.h => heaptoast.h}      |  112 +-
 src/include/access/toast_internals.h          |   54 +
 src/pl/plpgsql/src/pl_exec.c                  |    2 +-
 src/test/regress/regress.c                    |    2 +-
 37 files changed, 2602 insertions(+), 2549 deletions(-)
 create mode 100644 src/backend/access/common/detoast.c
 create mode 100644 src/backend/access/common/toast_internals.c
 create mode 100644 src/backend/access/heap/heaptoast.c
 delete mode 100644 src/backend/access/heap/tuptoaster.c
 create mode 100644 src/include/access/detoast.h
 rename src/include/access/{tuptoaster.h => heaptoast.h} (57%)
 create mode 100644 src/include/access/toast_internals.h

diff --git a/doc/src/sgml/storage.sgml b/doc/src/sgml/storage.sgml
index 1047c77a63..78bd030346 100644
--- a/doc/src/sgml/storage.sgml
+++ b/doc/src/sgml/storage.sgml
@@ -382,7 +382,7 @@ The oldest and most common type is a pointer to out-of-line data stored in
 a <firstterm><acronym>TOAST</acronym> table</firstterm> that is separate from, but
 associated with, the table containing the <acronym>TOAST</acronym> pointer datum
 itself.  These <firstterm>on-disk</firstterm> pointer datums are created by the
-<acronym>TOAST</acronym> management code (in <filename>access/heap/tuptoaster.c</filename>)
+<acronym>TOAST</acronym> management code (in <filename>access/common/toast_internals.c</filename>)
 when a tuple to be stored on disk is too large to be stored as-is.
 Further details appear in <xref linkend="storage-toast-ondisk"/>.
 Alternatively, a <acronym>TOAST</acronym> pointer datum can contain a pointer to
diff --git a/src/backend/access/common/Makefile b/src/backend/access/common/Makefile
index d469504337..9ac19d9f9e 100644
--- a/src/backend/access/common/Makefile
+++ b/src/backend/access/common/Makefile
@@ -12,7 +12,8 @@ subdir = src/backend/access/common
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = bufmask.o heaptuple.o indextuple.o printsimple.o printtup.o \
-	relation.o reloptions.o scankey.o session.o tupconvert.o tupdesc.o
+OBJS = bufmask.o detoast.o heaptuple.o indextuple.o printsimple.o \
+	printtup.o relation.o reloptions.o scankey.o session.o toast_internals.o \
+	tupconvert.o tupdesc.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c
new file mode 100644
index 0000000000..54727bdf2c
--- /dev/null
+++ b/src/backend/access/common/detoast.c
@@ -0,0 +1,859 @@
+/*-------------------------------------------------------------------------
+ *
+ * detoast.c
+ *	  Retrieve compressed or external variable size attributes.
+ *
+ * Copyright (c) 2000-2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/common/detoast.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/detoast.h"
+#include "access/genam.h"
+#include "access/heaptoast.h"
+#include "access/table.h"
+#include "access/toast_internals.h"
+#include "common/pg_lzcompress.h"
+#include "utils/expandeddatum.h"
+#include "utils/fmgroids.h"
+#include "utils/rel.h"
+
+static struct varlena *toast_fetch_datum(struct varlena *attr);
+static struct varlena *toast_fetch_datum_slice(struct varlena *attr,
+						int32 sliceoffset, int32 length);
+static struct varlena *toast_decompress_datum(struct varlena *attr);
+static struct varlena *toast_decompress_datum_slice(struct varlena *attr, int32 slicelength);
+
+/* ----------
+ * heap_tuple_fetch_attr -
+ *
+ *	Public entry point to get back a toasted value from
+ *	external source (possibly still in compressed format).
+ *
+ * This will return a datum that contains all the data internally, ie, not
+ * relying on external storage or memory, but it can still be compressed or
+ * have a short header.  Note some callers assume that if the input is an
+ * EXTERNAL datum, the result will be a pfree'able chunk.
+ * ----------
+ */
+struct varlena *
+heap_tuple_fetch_attr(struct varlena *attr)
+{
+	struct varlena *result;
+
+	if (VARATT_IS_EXTERNAL_ONDISK(attr))
+	{
+		/*
+		 * This is an external stored plain value
+		 */
+		result = toast_fetch_datum(attr);
+	}
+	else if (VARATT_IS_EXTERNAL_INDIRECT(attr))
+	{
+		/*
+		 * This is an indirect pointer --- dereference it
+		 */
+		struct varatt_indirect redirect;
+
+		VARATT_EXTERNAL_GET_POINTER(redirect, attr);
+		attr = (struct varlena *) redirect.pointer;
+
+		/* nested indirect Datums aren't allowed */
+		Assert(!VARATT_IS_EXTERNAL_INDIRECT(attr));
+
+		/* recurse if value is still external in some other way */
+		if (VARATT_IS_EXTERNAL(attr))
+			return heap_tuple_fetch_attr(attr);
+
+		/*
+		 * Copy into the caller's memory context, in case caller tries to
+		 * pfree the result.
+		 */
+		result = (struct varlena *) palloc(VARSIZE_ANY(attr));
+		memcpy(result, attr, VARSIZE_ANY(attr));
+	}
+	else if (VARATT_IS_EXTERNAL_EXPANDED(attr))
+	{
+		/*
+		 * This is an expanded-object pointer --- get flat format
+		 */
+		ExpandedObjectHeader *eoh;
+		Size		resultsize;
+
+		eoh = DatumGetEOHP(PointerGetDatum(attr));
+		resultsize = EOH_get_flat_size(eoh);
+		result = (struct varlena *) palloc(resultsize);
+		EOH_flatten_into(eoh, (void *) result, resultsize);
+	}
+	else
+	{
+		/*
+		 * This is a plain value inside of the main tuple - why am I called?
+		 */
+		result = attr;
+	}
+
+	return result;
+}
+
+
+/* ----------
+ * heap_tuple_untoast_attr -
+ *
+ *	Public entry point to get back a toasted value from compression
+ *	or external storage.  The result is always non-extended varlena form.
+ *
+ * Note some callers assume that if the input is an EXTERNAL or COMPRESSED
+ * datum, the result will be a pfree'able chunk.
+ * ----------
+ */
+struct varlena *
+heap_tuple_untoast_attr(struct varlena *attr)
+{
+	if (VARATT_IS_EXTERNAL_ONDISK(attr))
+	{
+		/*
+		 * This is an externally stored datum --- fetch it back from there
+		 */
+		attr = toast_fetch_datum(attr);
+		/* If it's compressed, decompress it */
+		if (VARATT_IS_COMPRESSED(attr))
+		{
+			struct varlena *tmp = attr;
+
+			attr = toast_decompress_datum(tmp);
+			pfree(tmp);
+		}
+	}
+	else if (VARATT_IS_EXTERNAL_INDIRECT(attr))
+	{
+		/*
+		 * This is an indirect pointer --- dereference it
+		 */
+		struct varatt_indirect redirect;
+
+		VARATT_EXTERNAL_GET_POINTER(redirect, attr);
+		attr = (struct varlena *) redirect.pointer;
+
+		/* nested indirect Datums aren't allowed */
+		Assert(!VARATT_IS_EXTERNAL_INDIRECT(attr));
+
+		/* recurse in case value is still extended in some other way */
+		attr = heap_tuple_untoast_attr(attr);
+
+		/* if it isn't, we'd better copy it */
+		if (attr == (struct varlena *) redirect.pointer)
+		{
+			struct varlena *result;
+
+			result = (struct varlena *) palloc(VARSIZE_ANY(attr));
+			memcpy(result, attr, VARSIZE_ANY(attr));
+			attr = result;
+		}
+	}
+	else if (VARATT_IS_EXTERNAL_EXPANDED(attr))
+	{
+		/*
+		 * This is an expanded-object pointer --- get flat format
+		 */
+		attr = heap_tuple_fetch_attr(attr);
+		/* flatteners are not allowed to produce compressed/short output */
+		Assert(!VARATT_IS_EXTENDED(attr));
+	}
+	else if (VARATT_IS_COMPRESSED(attr))
+	{
+		/*
+		 * This is a compressed value inside of the main tuple
+		 */
+		attr = toast_decompress_datum(attr);
+	}
+	else if (VARATT_IS_SHORT(attr))
+	{
+		/*
+		 * This is a short-header varlena --- convert to 4-byte header format
+		 */
+		Size		data_size = VARSIZE_SHORT(attr) - VARHDRSZ_SHORT;
+		Size		new_size = data_size + VARHDRSZ;
+		struct varlena *new_attr;
+
+		new_attr = (struct varlena *) palloc(new_size);
+		SET_VARSIZE(new_attr, new_size);
+		memcpy(VARDATA(new_attr), VARDATA_SHORT(attr), data_size);
+		attr = new_attr;
+	}
+
+	return attr;
+}
+
+
+/* ----------
+ * heap_tuple_untoast_attr_slice -
+ *
+ *		Public entry point to get back part of a toasted value
+ *		from compression or external storage.
+ * ----------
+ */
+struct varlena *
+heap_tuple_untoast_attr_slice(struct varlena *attr,
+							  int32 sliceoffset, int32 slicelength)
+{
+	struct varlena *preslice;
+	struct varlena *result;
+	char	   *attrdata;
+	int32		attrsize;
+
+	if (VARATT_IS_EXTERNAL_ONDISK(attr))
+	{
+		struct varatt_external toast_pointer;
+
+		VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
+
+		/* fast path for non-compressed external datums */
+		if (!VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))
+			return toast_fetch_datum_slice(attr, sliceoffset, slicelength);
+
+		/* fetch it back (compressed marker will get set automatically) */
+		preslice = toast_fetch_datum(attr);
+	}
+	else if (VARATT_IS_EXTERNAL_INDIRECT(attr))
+	{
+		struct varatt_indirect redirect;
+
+		VARATT_EXTERNAL_GET_POINTER(redirect, attr);
+
+		/* nested indirect Datums aren't allowed */
+		Assert(!VARATT_IS_EXTERNAL_INDIRECT(redirect.pointer));
+
+		return heap_tuple_untoast_attr_slice(redirect.pointer,
+											 sliceoffset, slicelength);
+	}
+	else if (VARATT_IS_EXTERNAL_EXPANDED(attr))
+	{
+		/* pass it off to heap_tuple_fetch_attr to flatten */
+		preslice = heap_tuple_fetch_attr(attr);
+	}
+	else
+		preslice = attr;
+
+	Assert(!VARATT_IS_EXTERNAL(preslice));
+
+	if (VARATT_IS_COMPRESSED(preslice))
+	{
+		struct varlena *tmp = preslice;
+
+		/* Decompress enough to encompass the slice and the offset */
+		if (slicelength > 0 && sliceoffset >= 0)
+			preslice = toast_decompress_datum_slice(tmp, slicelength + sliceoffset);
+		else
+			preslice = toast_decompress_datum(tmp);
+
+		if (tmp != attr)
+			pfree(tmp);
+	}
+
+	if (VARATT_IS_SHORT(preslice))
+	{
+		attrdata = VARDATA_SHORT(preslice);
+		attrsize = VARSIZE_SHORT(preslice) - VARHDRSZ_SHORT;
+	}
+	else
+	{
+		attrdata = VARDATA(preslice);
+		attrsize = VARSIZE(preslice) - VARHDRSZ;
+	}
+
+	/* slicing of datum for compressed cases and plain value */
+
+	if (sliceoffset >= attrsize)
+	{
+		sliceoffset = 0;
+		slicelength = 0;
+	}
+
+	if (((sliceoffset + slicelength) > attrsize) || slicelength < 0)
+		slicelength = attrsize - sliceoffset;
+
+	result = (struct varlena *) palloc(slicelength + VARHDRSZ);
+	SET_VARSIZE(result, slicelength + VARHDRSZ);
+
+	memcpy(VARDATA(result), attrdata + sliceoffset, slicelength);
+
+	if (preslice != attr)
+		pfree(preslice);
+
+	return result;
+}
+
+/* ----------
+ * toast_fetch_datum -
+ *
+ *	Reconstruct an in memory Datum from the chunks saved
+ *	in the toast relation
+ * ----------
+ */
+static struct varlena *
+toast_fetch_datum(struct varlena *attr)
+{
+	Relation	toastrel;
+	Relation   *toastidxs;
+	ScanKeyData toastkey;
+	SysScanDesc toastscan;
+	HeapTuple	ttup;
+	TupleDesc	toasttupDesc;
+	struct varlena *result;
+	struct varatt_external toast_pointer;
+	int32		ressize;
+	int32		residx,
+				nextidx;
+	int32		numchunks;
+	Pointer		chunk;
+	bool		isnull;
+	char	   *chunkdata;
+	int32		chunksize;
+	int			num_indexes;
+	int			validIndex;
+	SnapshotData SnapshotToast;
+
+	if (!VARATT_IS_EXTERNAL_ONDISK(attr))
+		elog(ERROR, "toast_fetch_datum shouldn't be called for non-ondisk datums");
+
+	/* Must copy to access aligned fields */
+	VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
+
+	ressize = toast_pointer.va_extsize;
+	numchunks = ((ressize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
+
+	result = (struct varlena *) palloc(ressize + VARHDRSZ);
+
+	if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))
+		SET_VARSIZE_COMPRESSED(result, ressize + VARHDRSZ);
+	else
+		SET_VARSIZE(result, ressize + VARHDRSZ);
+
+	/*
+	 * Open the toast relation and its indexes
+	 */
+	toastrel = table_open(toast_pointer.va_toastrelid, AccessShareLock);
+	toasttupDesc = toastrel->rd_att;
+
+	/* Look for the valid index of the toast relation */
+	validIndex = toast_open_indexes(toastrel,
+									AccessShareLock,
+									&toastidxs,
+									&num_indexes);
+
+	/*
+	 * Setup a scan key to fetch from the index by va_valueid
+	 */
+	ScanKeyInit(&toastkey,
+				(AttrNumber) 1,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(toast_pointer.va_valueid));
+
+	/*
+	 * Read the chunks by index
+	 *
+	 * Note that because the index is actually on (valueid, chunkidx) we will
+	 * see the chunks in chunkidx order, even though we didn't explicitly ask
+	 * for it.
+	 */
+	nextidx = 0;
+
+	init_toast_snapshot(&SnapshotToast);
+	toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
+										   &SnapshotToast, 1, &toastkey);
+	while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
+	{
+		/*
+		 * Have a chunk, extract the sequence number and the data
+		 */
+		residx = DatumGetInt32(fastgetattr(ttup, 2, toasttupDesc, &isnull));
+		Assert(!isnull);
+		chunk = DatumGetPointer(fastgetattr(ttup, 3, toasttupDesc, &isnull));
+		Assert(!isnull);
+		if (!VARATT_IS_EXTENDED(chunk))
+		{
+			chunksize = VARSIZE(chunk) - VARHDRSZ;
+			chunkdata = VARDATA(chunk);
+		}
+		else if (VARATT_IS_SHORT(chunk))
+		{
+			/* could happen due to heap_form_tuple doing its thing */
+			chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT;
+			chunkdata = VARDATA_SHORT(chunk);
+		}
+		else
+		{
+			/* should never happen */
+			elog(ERROR, "found toasted toast chunk for toast value %u in %s",
+				 toast_pointer.va_valueid,
+				 RelationGetRelationName(toastrel));
+			chunksize = 0;		/* keep compiler quiet */
+			chunkdata = NULL;
+		}
+
+		/*
+		 * Some checks on the data we've found
+		 */
+		if (residx != nextidx)
+			elog(ERROR, "unexpected chunk number %d (expected %d) for toast value %u in %s",
+				 residx, nextidx,
+				 toast_pointer.va_valueid,
+				 RelationGetRelationName(toastrel));
+		if (residx < numchunks - 1)
+		{
+			if (chunksize != TOAST_MAX_CHUNK_SIZE)
+				elog(ERROR, "unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s",
+					 chunksize, (int) TOAST_MAX_CHUNK_SIZE,
+					 residx, numchunks,
+					 toast_pointer.va_valueid,
+					 RelationGetRelationName(toastrel));
+		}
+		else if (residx == numchunks - 1)
+		{
+			if ((residx * TOAST_MAX_CHUNK_SIZE + chunksize) != ressize)
+				elog(ERROR, "unexpected chunk size %d (expected %d) in final chunk %d for toast value %u in %s",
+					 chunksize,
+					 (int) (ressize - residx * TOAST_MAX_CHUNK_SIZE),
+					 residx,
+					 toast_pointer.va_valueid,
+					 RelationGetRelationName(toastrel));
+		}
+		else
+			elog(ERROR, "unexpected chunk number %d (out of range %d..%d) for toast value %u in %s",
+				 residx,
+				 0, numchunks - 1,
+				 toast_pointer.va_valueid,
+				 RelationGetRelationName(toastrel));
+
+		/*
+		 * Copy the data into proper place in our result
+		 */
+		memcpy(VARDATA(result) + residx * TOAST_MAX_CHUNK_SIZE,
+			   chunkdata,
+			   chunksize);
+
+		nextidx++;
+	}
+
+	/*
+	 * Final checks that we successfully fetched the datum
+	 */
+	if (nextidx != numchunks)
+		elog(ERROR, "missing chunk number %d for toast value %u in %s",
+			 nextidx,
+			 toast_pointer.va_valueid,
+			 RelationGetRelationName(toastrel));
+
+	/*
+	 * End scan and close relations
+	 */
+	systable_endscan_ordered(toastscan);
+	toast_close_indexes(toastidxs, num_indexes, AccessShareLock);
+	table_close(toastrel, AccessShareLock);
+
+	return result;
+}
+
+/* ----------
+ * toast_fetch_datum_slice -
+ *
+ *	Reconstruct a segment of a Datum from the chunks saved
+ *	in the toast relation
+ *
+ *	Note that this function only supports non-compressed external datums.
+ * ----------
+ */
+static struct varlena *
+toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length)
+{
+	Relation	toastrel;
+	Relation   *toastidxs;
+	ScanKeyData toastkey[3];
+	int			nscankeys;
+	SysScanDesc toastscan;
+	HeapTuple	ttup;
+	TupleDesc	toasttupDesc;
+	struct varlena *result;
+	struct varatt_external toast_pointer;
+	int32		attrsize;
+	int32		residx;
+	int32		nextidx;
+	int			numchunks;
+	int			startchunk;
+	int			endchunk;
+	int32		startoffset;
+	int32		endoffset;
+	int			totalchunks;
+	Pointer		chunk;
+	bool		isnull;
+	char	   *chunkdata;
+	int32		chunksize;
+	int32		chcpystrt;
+	int32		chcpyend;
+	int			num_indexes;
+	int			validIndex;
+	SnapshotData SnapshotToast;
+
+	if (!VARATT_IS_EXTERNAL_ONDISK(attr))
+		elog(ERROR, "toast_fetch_datum_slice shouldn't be called for non-ondisk datums");
+
+	/* Must copy to access aligned fields */
+	VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
+
+	/*
+	 * It's nonsense to fetch slices of a compressed datum -- this isn't lo_*
+	 * we can't return a compressed datum which is meaningful to toast later
+	 */
+	Assert(!VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer));
+
+	attrsize = toast_pointer.va_extsize;
+	totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
+
+	if (sliceoffset >= attrsize)
+	{
+		sliceoffset = 0;
+		length = 0;
+	}
+
+	if (((sliceoffset + length) > attrsize) || length < 0)
+		length = attrsize - sliceoffset;
+
+	result = (struct varlena *) palloc(length + VARHDRSZ);
+
+	SET_VARSIZE(result, length + VARHDRSZ);
+
+	if (length == 0)
+		return result;			/* Can save a lot of work at this point! */
+
+	startchunk = sliceoffset / TOAST_MAX_CHUNK_SIZE;
+	endchunk = (sliceoffset + length - 1) / TOAST_MAX_CHUNK_SIZE;
+	numchunks = (endchunk - startchunk) + 1;
+
+	startoffset = sliceoffset % TOAST_MAX_CHUNK_SIZE;
+	endoffset = (sliceoffset + length - 1) % TOAST_MAX_CHUNK_SIZE;
+
+	/*
+	 * Open the toast relation and its indexes
+	 */
+	toastrel = table_open(toast_pointer.va_toastrelid, AccessShareLock);
+	toasttupDesc = toastrel->rd_att;
+
+	/* Look for the valid index of toast relation */
+	validIndex = toast_open_indexes(toastrel,
+									AccessShareLock,
+									&toastidxs,
+									&num_indexes);
+
+	/*
+	 * Setup a scan key to fetch from the index. This is either two keys or
+	 * three depending on the number of chunks.
+	 */
+	ScanKeyInit(&toastkey[0],
+				(AttrNumber) 1,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(toast_pointer.va_valueid));
+
+	/*
+	 * Use equality condition for one chunk, a range condition otherwise:
+	 */
+	if (numchunks == 1)
+	{
+		ScanKeyInit(&toastkey[1],
+					(AttrNumber) 2,
+					BTEqualStrategyNumber, F_INT4EQ,
+					Int32GetDatum(startchunk));
+		nscankeys = 2;
+	}
+	else
+	{
+		ScanKeyInit(&toastkey[1],
+					(AttrNumber) 2,
+					BTGreaterEqualStrategyNumber, F_INT4GE,
+					Int32GetDatum(startchunk));
+		ScanKeyInit(&toastkey[2],
+					(AttrNumber) 2,
+					BTLessEqualStrategyNumber, F_INT4LE,
+					Int32GetDatum(endchunk));
+		nscankeys = 3;
+	}
+
+	/*
+	 * Read the chunks by index
+	 *
+	 * The index is on (valueid, chunkidx) so they will come in order
+	 */
+	init_toast_snapshot(&SnapshotToast);
+	nextidx = startchunk;
+	toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
+										   &SnapshotToast, nscankeys, toastkey);
+	while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
+	{
+		/*
+		 * Have a chunk, extract the sequence number and the data
+		 */
+		residx = DatumGetInt32(fastgetattr(ttup, 2, toasttupDesc, &isnull));
+		Assert(!isnull);
+		chunk = DatumGetPointer(fastgetattr(ttup, 3, toasttupDesc, &isnull));
+		Assert(!isnull);
+		if (!VARATT_IS_EXTENDED(chunk))
+		{
+			chunksize = VARSIZE(chunk) - VARHDRSZ;
+			chunkdata = VARDATA(chunk);
+		}
+		else if (VARATT_IS_SHORT(chunk))
+		{
+			/* could happen due to heap_form_tuple doing its thing */
+			chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT;
+			chunkdata = VARDATA_SHORT(chunk);
+		}
+		else
+		{
+			/* should never happen */
+			elog(ERROR, "found toasted toast chunk for toast value %u in %s",
+				 toast_pointer.va_valueid,
+				 RelationGetRelationName(toastrel));
+			chunksize = 0;		/* keep compiler quiet */
+			chunkdata = NULL;
+		}
+
+		/*
+		 * Some checks on the data we've found
+		 */
+		if ((residx != nextidx) || (residx > endchunk) || (residx < startchunk))
+			elog(ERROR, "unexpected chunk number %d (expected %d) for toast value %u in %s",
+				 residx, nextidx,
+				 toast_pointer.va_valueid,
+				 RelationGetRelationName(toastrel));
+		if (residx < totalchunks - 1)
+		{
+			if (chunksize != TOAST_MAX_CHUNK_SIZE)
+				elog(ERROR, "unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s when fetching slice",
+					 chunksize, (int) TOAST_MAX_CHUNK_SIZE,
+					 residx, totalchunks,
+					 toast_pointer.va_valueid,
+					 RelationGetRelationName(toastrel));
+		}
+		else if (residx == totalchunks - 1)
+		{
+			if ((residx * TOAST_MAX_CHUNK_SIZE + chunksize) != attrsize)
+				elog(ERROR, "unexpected chunk size %d (expected %d) in final chunk %d for toast value %u in %s when fetching slice",
+					 chunksize,
+					 (int) (attrsize - residx * TOAST_MAX_CHUNK_SIZE),
+					 residx,
+					 toast_pointer.va_valueid,
+					 RelationGetRelationName(toastrel));
+		}
+		else
+			elog(ERROR, "unexpected chunk number %d (out of range %d..%d) for toast value %u in %s",
+				 residx,
+				 0, totalchunks - 1,
+				 toast_pointer.va_valueid,
+				 RelationGetRelationName(toastrel));
+
+		/*
+		 * Copy the data into proper place in our result
+		 */
+		chcpystrt = 0;
+		chcpyend = chunksize - 1;
+		if (residx == startchunk)
+			chcpystrt = startoffset;
+		if (residx == endchunk)
+			chcpyend = endoffset;
+
+		memcpy(VARDATA(result) +
+			   (residx * TOAST_MAX_CHUNK_SIZE - sliceoffset) + chcpystrt,
+			   chunkdata + chcpystrt,
+			   (chcpyend - chcpystrt) + 1);
+
+		nextidx++;
+	}
+
+	/*
+	 * Final checks that we successfully fetched the datum
+	 */
+	if (nextidx != (endchunk + 1))
+		elog(ERROR, "missing chunk number %d for toast value %u in %s",
+			 nextidx,
+			 toast_pointer.va_valueid,
+			 RelationGetRelationName(toastrel));
+
+	/*
+	 * End scan and close relations
+	 */
+	systable_endscan_ordered(toastscan);
+	toast_close_indexes(toastidxs, num_indexes, AccessShareLock);
+	table_close(toastrel, AccessShareLock);
+
+	return result;
+}
+
+/* ----------
+ * toast_decompress_datum -
+ *
+ * Decompress a compressed version of a varlena datum
+ */
+static struct varlena *
+toast_decompress_datum(struct varlena *attr)
+{
+	struct varlena *result;
+
+	Assert(VARATT_IS_COMPRESSED(attr));
+
+	result = (struct varlena *)
+		palloc(TOAST_COMPRESS_RAWSIZE(attr) + VARHDRSZ);
+	SET_VARSIZE(result, TOAST_COMPRESS_RAWSIZE(attr) + VARHDRSZ);
+
+	if (pglz_decompress(TOAST_COMPRESS_RAWDATA(attr),
+						VARSIZE(attr) - TOAST_COMPRESS_HDRSZ,
+						VARDATA(result),
+						TOAST_COMPRESS_RAWSIZE(attr), true) < 0)
+		elog(ERROR, "compressed data is corrupted");
+
+	return result;
+}
+
+
+/* ----------
+ * toast_decompress_datum_slice -
+ *
+ * Decompress the front of a compressed version of a varlena datum.
+ * offset handling happens in heap_tuple_untoast_attr_slice.
+ * Here we just decompress a slice from the front.
+ */
+static struct varlena *
+toast_decompress_datum_slice(struct varlena *attr, int32 slicelength)
+{
+	struct varlena *result;
+	int32 rawsize;
+
+	Assert(VARATT_IS_COMPRESSED(attr));
+
+	result = (struct varlena *) palloc(slicelength + VARHDRSZ);
+
+	rawsize = pglz_decompress(TOAST_COMPRESS_RAWDATA(attr),
+						VARSIZE(attr) - TOAST_COMPRESS_HDRSZ,
+						VARDATA(result),
+						slicelength, false);
+	if (rawsize < 0)
+		elog(ERROR, "compressed data is corrupted");
+
+	SET_VARSIZE(result, rawsize + VARHDRSZ);
+	return result;
+}
+
+/* ----------
+ * toast_raw_datum_size -
+ *
+ *	Return the raw (detoasted) size of a varlena datum
+ *	(including the VARHDRSZ header)
+ * ----------
+ */
+Size
+toast_raw_datum_size(Datum value)
+{
+	struct varlena *attr = (struct varlena *) DatumGetPointer(value);
+	Size		result;
+
+	if (VARATT_IS_EXTERNAL_ONDISK(attr))
+	{
+		/* va_rawsize is the size of the original datum -- including header */
+		struct varatt_external toast_pointer;
+
+		VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
+		result = toast_pointer.va_rawsize;
+	}
+	else if (VARATT_IS_EXTERNAL_INDIRECT(attr))
+	{
+		struct varatt_indirect toast_pointer;
+
+		VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
+
+		/* nested indirect Datums aren't allowed */
+		Assert(!VARATT_IS_EXTERNAL_INDIRECT(toast_pointer.pointer));
+
+		return toast_raw_datum_size(PointerGetDatum(toast_pointer.pointer));
+	}
+	else if (VARATT_IS_EXTERNAL_EXPANDED(attr))
+	{
+		result = EOH_get_flat_size(DatumGetEOHP(value));
+	}
+	else if (VARATT_IS_COMPRESSED(attr))
+	{
+		/* here, va_rawsize is just the payload size */
+		result = VARRAWSIZE_4B_C(attr) + VARHDRSZ;
+	}
+	else if (VARATT_IS_SHORT(attr))
+	{
+		/*
+		 * we have to normalize the header length to VARHDRSZ or else the
+		 * callers of this function will be confused.
+		 */
+		result = VARSIZE_SHORT(attr) - VARHDRSZ_SHORT + VARHDRSZ;
+	}
+	else
+	{
+		/* plain untoasted datum */
+		result = VARSIZE(attr);
+	}
+	return result;
+}
+
+/* ----------
+ * toast_datum_size
+ *
+ *	Return the physical storage size (possibly compressed) of a varlena datum
+ * ----------
+ */
+Size
+toast_datum_size(Datum value)
+{
+	struct varlena *attr = (struct varlena *) DatumGetPointer(value);
+	Size		result;
+
+	if (VARATT_IS_EXTERNAL_ONDISK(attr))
+	{
+		/*
+		 * Attribute is stored externally - return the extsize whether
+		 * compressed or not.  We do not count the size of the toast pointer
+		 * ... should we?
+		 */
+		struct varatt_external toast_pointer;
+
+		VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
+		result = toast_pointer.va_extsize;
+	}
+	else if (VARATT_IS_EXTERNAL_INDIRECT(attr))
+	{
+		struct varatt_indirect toast_pointer;
+
+		VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
+
+		/* nested indirect Datums aren't allowed */
+		Assert(!VARATT_IS_EXTERNAL_INDIRECT(attr));
+
+		return toast_datum_size(PointerGetDatum(toast_pointer.pointer));
+	}
+	else if (VARATT_IS_EXTERNAL_EXPANDED(attr))
+	{
+		result = EOH_get_flat_size(DatumGetEOHP(value));
+	}
+	else if (VARATT_IS_SHORT(attr))
+	{
+		result = VARSIZE_SHORT(attr);
+	}
+	else
+	{
+		/*
+		 * Attribute is stored inline either compressed or not, just calculate
+		 * the size of the datum in either case.
+		 */
+		result = VARSIZE(attr);
+	}
+	return result;
+}
diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index a48a6cd757..cc948958d7 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -18,7 +18,7 @@
  * (In performance-critical code paths we can use pg_detoast_datum_packed
  * and the appropriate access macros to avoid that overhead.)  Note that this
  * conversion is performed directly in heap_form_tuple, without invoking
- * tuptoaster.c.
+ * heaptoast.c.
  *
  * This change will break any code that assumes it needn't detoast values
  * that have been put into a tuple but never sent to disk.  Hopefully there
@@ -57,9 +57,9 @@
 
 #include "postgres.h"
 
+#include "access/heaptoast.h"
 #include "access/sysattr.h"
 #include "access/tupdesc_details.h"
-#include "access/tuptoaster.h"
 #include "executor/tuptable.h"
 #include "utils/expandeddatum.h"
 
diff --git a/src/backend/access/common/indextuple.c b/src/backend/access/common/indextuple.c
index cb23be859d..07586201b9 100644
--- a/src/backend/access/common/indextuple.c
+++ b/src/backend/access/common/indextuple.c
@@ -16,10 +16,17 @@
 
 #include "postgres.h"
 
+#include "access/detoast.h"
+#include "access/heaptoast.h"
 #include "access/htup_details.h"
 #include "access/itup.h"
-#include "access/tuptoaster.h"
+#include "access/toast_internals.h"
 
+/*
+ * This enables de-toasting of index entries.  Needed until VACUUM is
+ * smart enough to rebuild indexes from scratch.
+ */
+#define TOAST_INDEX_HACK
 
 /* ----------------------------------------------------------------
  *				  index_ tuple interface routines
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index de06c92574..42f793158f 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -19,11 +19,11 @@
 
 #include "access/gist_private.h"
 #include "access/hash.h"
+#include "access/heaptoast.h"
 #include "access/htup_details.h"
 #include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/spgist.h"
-#include "access/tuptoaster.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
 #include "commands/tablespace.h"
diff --git a/src/backend/access/common/toast_internals.c b/src/backend/access/common/toast_internals.c
new file mode 100644
index 0000000000..a971242490
--- /dev/null
+++ b/src/backend/access/common/toast_internals.c
@@ -0,0 +1,632 @@
+/*-------------------------------------------------------------------------
+ *
+ * toast_internals.c
+ *	  Functions for internal use by the TOAST system.
+ *
+ * Copyright (c) 2000-2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/common/toast_internals.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/detoast.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/heaptoast.h"
+#include "access/table.h"
+#include "access/toast_internals.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "common/pg_lzcompress.h"
+#include "miscadmin.h"
+#include "utils/fmgroids.h"
+#include "utils/rel.h"
+#include "utils/snapmgr.h"
+
+static bool toastrel_valueid_exists(Relation toastrel, Oid valueid);
+static bool toastid_valueid_exists(Oid toastrelid, Oid valueid);
+
+/* ----------
+ * toast_compress_datum -
+ *
+ *	Create a compressed version of a varlena datum
+ *
+ *	If we fail (ie, compressed result is actually bigger than original)
+ *	then return NULL.  We must not use compressed data if it'd expand
+ *	the tuple!
+ *
+ *	We use VAR{SIZE,DATA}_ANY so we can handle short varlenas here without
+ *	copying them.  But we can't handle external or compressed datums.
+ * ----------
+ */
+Datum
+toast_compress_datum(Datum value)
+{
+	struct varlena *tmp;
+	int32		valsize = VARSIZE_ANY_EXHDR(DatumGetPointer(value));
+	int32		len;
+
+	Assert(!VARATT_IS_EXTERNAL(DatumGetPointer(value)));
+	Assert(!VARATT_IS_COMPRESSED(DatumGetPointer(value)));
+
+	/*
+	 * No point in wasting a palloc cycle if value size is out of the allowed
+	 * range for compression
+	 */
+	if (valsize < PGLZ_strategy_default->min_input_size ||
+		valsize > PGLZ_strategy_default->max_input_size)
+		return PointerGetDatum(NULL);
+
+	tmp = (struct varlena *) palloc(PGLZ_MAX_OUTPUT(valsize) +
+									TOAST_COMPRESS_HDRSZ);
+
+	/*
+	 * We recheck the actual size even if pglz_compress() reports success,
+	 * because it might be satisfied with having saved as little as one byte
+	 * in the compressed data --- which could turn into a net loss once you
+	 * consider header and alignment padding.  Worst case, the compressed
+	 * format might require three padding bytes (plus header, which is
+	 * included in VARSIZE(tmp)), whereas the uncompressed format would take
+	 * only one header byte and no padding if the value is short enough.  So
+	 * we insist on a savings of more than 2 bytes to ensure we have a gain.
+	 */
+	len = pglz_compress(VARDATA_ANY(DatumGetPointer(value)),
+						valsize,
+						TOAST_COMPRESS_RAWDATA(tmp),
+						PGLZ_strategy_default);
+	if (len >= 0 &&
+		len + TOAST_COMPRESS_HDRSZ < valsize - 2)
+	{
+		TOAST_COMPRESS_SET_RAWSIZE(tmp, valsize);
+		SET_VARSIZE_COMPRESSED(tmp, len + TOAST_COMPRESS_HDRSZ);
+		/* successful compression */
+		return PointerGetDatum(tmp);
+	}
+	else
+	{
+		/* incompressible data */
+		pfree(tmp);
+		return PointerGetDatum(NULL);
+	}
+}
+
+/* ----------
+ * toast_save_datum -
+ *
+ *	Save one single datum into the secondary relation and return
+ *	a Datum reference for it.
+ *
+ * rel: the main relation we're working with (not the toast rel!)
+ * value: datum to be pushed to toast storage
+ * oldexternal: if not NULL, toast pointer previously representing the datum
+ * options: options to be passed to heap_insert() for toast rows
+ * ----------
+ */
+Datum
+toast_save_datum(Relation rel, Datum value,
+				 struct varlena *oldexternal, int options)
+{
+	Relation	toastrel;
+	Relation   *toastidxs;
+	HeapTuple	toasttup;
+	TupleDesc	toasttupDesc;
+	Datum		t_values[3];
+	bool		t_isnull[3];
+	CommandId	mycid = GetCurrentCommandId(true);
+	struct varlena *result;
+	struct varatt_external toast_pointer;
+	union
+	{
+		struct varlena hdr;
+		/* this is to make the union big enough for a chunk: */
+		char		data[TOAST_MAX_CHUNK_SIZE + VARHDRSZ];
+		/* ensure union is aligned well enough: */
+		int32		align_it;
+	}			chunk_data;
+	int32		chunk_size;
+	int32		chunk_seq = 0;
+	char	   *data_p;
+	int32		data_todo;
+	Pointer		dval = DatumGetPointer(value);
+	int			num_indexes;
+	int			validIndex;
+
+	Assert(!VARATT_IS_EXTERNAL(value));
+
+	/*
+	 * Open the toast relation and its indexes.  We can use the index to check
+	 * uniqueness of the OID we assign to the toasted item, even though it has
+	 * additional columns besides OID.
+	 */
+	toastrel = table_open(rel->rd_rel->reltoastrelid, RowExclusiveLock);
+	toasttupDesc = toastrel->rd_att;
+
+	/* Open all the toast indexes and look for the valid one */
+	validIndex = toast_open_indexes(toastrel,
+									RowExclusiveLock,
+									&toastidxs,
+									&num_indexes);
+
+	/*
+	 * Get the data pointer and length, and compute va_rawsize and va_extsize.
+	 *
+	 * va_rawsize is the size of the equivalent fully uncompressed datum, so
+	 * we have to adjust for short headers.
+	 *
+	 * va_extsize is the actual size of the data payload in the toast records.
+	 */
+	if (VARATT_IS_SHORT(dval))
+	{
+		data_p = VARDATA_SHORT(dval);
+		data_todo = VARSIZE_SHORT(dval) - VARHDRSZ_SHORT;
+		toast_pointer.va_rawsize = data_todo + VARHDRSZ;	/* as if not short */
+		toast_pointer.va_extsize = data_todo;
+	}
+	else if (VARATT_IS_COMPRESSED(dval))
+	{
+		data_p = VARDATA(dval);
+		data_todo = VARSIZE(dval) - VARHDRSZ;
+		/* rawsize in a compressed datum is just the size of the payload */
+		toast_pointer.va_rawsize = VARRAWSIZE_4B_C(dval) + VARHDRSZ;
+		toast_pointer.va_extsize = data_todo;
+		/* Assert that the numbers look like it's compressed */
+		Assert(VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer));
+	}
+	else
+	{
+		data_p = VARDATA(dval);
+		data_todo = VARSIZE(dval) - VARHDRSZ;
+		toast_pointer.va_rawsize = VARSIZE(dval);
+		toast_pointer.va_extsize = data_todo;
+	}
+
+	/*
+	 * Insert the correct table OID into the result TOAST pointer.
+	 *
+	 * Normally this is the actual OID of the target toast table, but during
+	 * table-rewriting operations such as CLUSTER, we have to insert the OID
+	 * of the table's real permanent toast table instead.  rd_toastoid is set
+	 * if we have to substitute such an OID.
+	 */
+	if (OidIsValid(rel->rd_toastoid))
+		toast_pointer.va_toastrelid = rel->rd_toastoid;
+	else
+		toast_pointer.va_toastrelid = RelationGetRelid(toastrel);
+
+	/*
+	 * Choose an OID to use as the value ID for this toast value.
+	 *
+	 * Normally we just choose an unused OID within the toast table.  But
+	 * during table-rewriting operations where we are preserving an existing
+	 * toast table OID, we want to preserve toast value OIDs too.  So, if
+	 * rd_toastoid is set and we had a prior external value from that same
+	 * toast table, re-use its value ID.  If we didn't have a prior external
+	 * value (which is a corner case, but possible if the table's attstorage
+	 * options have been changed), we have to pick a value ID that doesn't
+	 * conflict with either new or existing toast value OIDs.
+	 */
+	if (!OidIsValid(rel->rd_toastoid))
+	{
+		/* normal case: just choose an unused OID */
+		toast_pointer.va_valueid =
+			GetNewOidWithIndex(toastrel,
+							   RelationGetRelid(toastidxs[validIndex]),
+							   (AttrNumber) 1);
+	}
+	else
+	{
+		/* rewrite case: check to see if value was in old toast table */
+		toast_pointer.va_valueid = InvalidOid;
+		if (oldexternal != NULL)
+		{
+			struct varatt_external old_toast_pointer;
+
+			Assert(VARATT_IS_EXTERNAL_ONDISK(oldexternal));
+			/* Must copy to access aligned fields */
+			VARATT_EXTERNAL_GET_POINTER(old_toast_pointer, oldexternal);
+			if (old_toast_pointer.va_toastrelid == rel->rd_toastoid)
+			{
+				/* This value came from the old toast table; reuse its OID */
+				toast_pointer.va_valueid = old_toast_pointer.va_valueid;
+
+				/*
+				 * There is a corner case here: the table rewrite might have
+				 * to copy both live and recently-dead versions of a row, and
+				 * those versions could easily reference the same toast value.
+				 * When we copy the second or later version of such a row,
+				 * reusing the OID will mean we select an OID that's already
+				 * in the new toast table.  Check for that, and if so, just
+				 * fall through without writing the data again.
+				 *
+				 * While annoying and ugly-looking, this is a good thing
+				 * because it ensures that we wind up with only one copy of
+				 * the toast value when there is only one copy in the old
+				 * toast table.  Before we detected this case, we'd have made
+				 * multiple copies, wasting space; and what's worse, the
+				 * copies belonging to already-deleted heap tuples would not
+				 * be reclaimed by VACUUM.
+				 */
+				if (toastrel_valueid_exists(toastrel,
+											toast_pointer.va_valueid))
+				{
+					/* Match, so short-circuit the data storage loop below */
+					data_todo = 0;
+				}
+			}
+		}
+		if (toast_pointer.va_valueid == InvalidOid)
+		{
+			/*
+			 * new value; must choose an OID that doesn't conflict in either
+			 * old or new toast table
+			 */
+			do
+			{
+				toast_pointer.va_valueid =
+					GetNewOidWithIndex(toastrel,
+									   RelationGetRelid(toastidxs[validIndex]),
+									   (AttrNumber) 1);
+			} while (toastid_valueid_exists(rel->rd_toastoid,
+											toast_pointer.va_valueid));
+		}
+	}
+
+	/*
+	 * Initialize constant parts of the tuple data
+	 */
+	t_values[0] = ObjectIdGetDatum(toast_pointer.va_valueid);
+	t_values[2] = PointerGetDatum(&chunk_data);
+	t_isnull[0] = false;
+	t_isnull[1] = false;
+	t_isnull[2] = false;
+
+	/*
+	 * Split up the item into chunks
+	 */
+	while (data_todo > 0)
+	{
+		int			i;
+
+		CHECK_FOR_INTERRUPTS();
+
+		/*
+		 * Calculate the size of this chunk
+		 */
+		chunk_size = Min(TOAST_MAX_CHUNK_SIZE, data_todo);
+
+		/*
+		 * Build a tuple and store it
+		 */
+		t_values[1] = Int32GetDatum(chunk_seq++);
+		SET_VARSIZE(&chunk_data, chunk_size + VARHDRSZ);
+		memcpy(VARDATA(&chunk_data), data_p, chunk_size);
+		toasttup = heap_form_tuple(toasttupDesc, t_values, t_isnull);
+
+		heap_insert(toastrel, toasttup, mycid, options, NULL);
+
+		/*
+		 * Create the index entry.  We cheat a little here by not using
+		 * FormIndexDatum: this relies on the knowledge that the index columns
+		 * are the same as the initial columns of the table for all the
+		 * indexes.  We also cheat by not providing an IndexInfo: this is okay
+		 * for now because btree doesn't need one, but we might have to be
+		 * more honest someday.
+		 *
+		 * Note also that there had better not be any user-created index on
+		 * the TOAST table, since we don't bother to update anything else.
+		 */
+		for (i = 0; i < num_indexes; i++)
+		{
+			/* Only index relations marked as ready can be updated */
+			if (toastidxs[i]->rd_index->indisready)
+				index_insert(toastidxs[i], t_values, t_isnull,
+							 &(toasttup->t_self),
+							 toastrel,
+							 toastidxs[i]->rd_index->indisunique ?
+							 UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
+							 NULL);
+		}
+
+		/*
+		 * Free memory
+		 */
+		heap_freetuple(toasttup);
+
+		/*
+		 * Move on to next chunk
+		 */
+		data_todo -= chunk_size;
+		data_p += chunk_size;
+	}
+
+	/*
+	 * Done - close toast relation and its indexes
+	 */
+	toast_close_indexes(toastidxs, num_indexes, RowExclusiveLock);
+	table_close(toastrel, RowExclusiveLock);
+
+	/*
+	 * Create the TOAST pointer value that we'll return
+	 */
+	result = (struct varlena *) palloc(TOAST_POINTER_SIZE);
+	SET_VARTAG_EXTERNAL(result, VARTAG_ONDISK);
+	memcpy(VARDATA_EXTERNAL(result), &toast_pointer, sizeof(toast_pointer));
+
+	return PointerGetDatum(result);
+}
+
+/* ----------
+ * toast_delete_datum -
+ *
+ *	Delete a single external stored value.
+ * ----------
+ */
+void
+toast_delete_datum(Relation rel, Datum value, bool is_speculative)
+{
+	struct varlena *attr = (struct varlena *) DatumGetPointer(value);
+	struct varatt_external toast_pointer;
+	Relation	toastrel;
+	Relation   *toastidxs;
+	ScanKeyData toastkey;
+	SysScanDesc toastscan;
+	HeapTuple	toasttup;
+	int			num_indexes;
+	int			validIndex;
+	SnapshotData SnapshotToast;
+
+	if (!VARATT_IS_EXTERNAL_ONDISK(attr))
+		return;
+
+	/* Must copy to access aligned fields */
+	VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
+
+	/*
+	 * Open the toast relation and its indexes
+	 */
+	toastrel = table_open(toast_pointer.va_toastrelid, RowExclusiveLock);
+
+	/* Fetch valid relation used for process */
+	validIndex = toast_open_indexes(toastrel,
+									RowExclusiveLock,
+									&toastidxs,
+									&num_indexes);
+
+	/*
+	 * Setup a scan key to find chunks with matching va_valueid
+	 */
+	ScanKeyInit(&toastkey,
+				(AttrNumber) 1,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(toast_pointer.va_valueid));
+
+	/*
+	 * Find all the chunks.  (We don't actually care whether we see them in
+	 * sequence or not, but since we've already locked the index we might as
+	 * well use systable_beginscan_ordered.)
+	 */
+	init_toast_snapshot(&SnapshotToast);
+	toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
+										   &SnapshotToast, 1, &toastkey);
+	while ((toasttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
+	{
+		/*
+		 * Have a chunk, delete it
+		 */
+		if (is_speculative)
+			heap_abort_speculative(toastrel, &toasttup->t_self);
+		else
+			simple_heap_delete(toastrel, &toasttup->t_self);
+	}
+
+	/*
+	 * End scan and close relations
+	 */
+	systable_endscan_ordered(toastscan);
+	toast_close_indexes(toastidxs, num_indexes, RowExclusiveLock);
+	table_close(toastrel, RowExclusiveLock);
+}
+
+/* ----------
+ * toastrel_valueid_exists -
+ *
+ *	Test whether a toast value with the given ID exists in the toast relation.
+ *	For safety, we consider a value to exist if there are either live or dead
+ *	toast rows with that ID; see notes for GetNewOidWithIndex().
+ * ----------
+ */
+static bool
+toastrel_valueid_exists(Relation toastrel, Oid valueid)
+{
+	bool		result = false;
+	ScanKeyData toastkey;
+	SysScanDesc toastscan;
+	int			num_indexes;
+	int			validIndex;
+	Relation   *toastidxs;
+
+	/* Fetch a valid index relation */
+	validIndex = toast_open_indexes(toastrel,
+									RowExclusiveLock,
+									&toastidxs,
+									&num_indexes);
+
+	/*
+	 * Setup a scan key to find chunks with matching va_valueid
+	 */
+	ScanKeyInit(&toastkey,
+				(AttrNumber) 1,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(valueid));
+
+	/*
+	 * Is there any such chunk?
+	 */
+	toastscan = systable_beginscan(toastrel,
+								   RelationGetRelid(toastidxs[validIndex]),
+								   true, SnapshotAny, 1, &toastkey);
+
+	if (systable_getnext(toastscan) != NULL)
+		result = true;
+
+	systable_endscan(toastscan);
+
+	/* Clean up */
+	toast_close_indexes(toastidxs, num_indexes, RowExclusiveLock);
+
+	return result;
+}
+
+/* ----------
+ * toastid_valueid_exists -
+ *
+ *	As above, but work from toast rel's OID not an open relation
+ * ----------
+ */
+static bool
+toastid_valueid_exists(Oid toastrelid, Oid valueid)
+{
+	bool		result;
+	Relation	toastrel;
+
+	toastrel = table_open(toastrelid, AccessShareLock);
+
+	result = toastrel_valueid_exists(toastrel, valueid);
+
+	table_close(toastrel, AccessShareLock);
+
+	return result;
+}
+
+/* ----------
+ * toast_get_valid_index
+ *
+ *	Get OID of valid index associated to given toast relation. A toast
+ *	relation can have only one valid index at the same time.
+ */
+Oid
+toast_get_valid_index(Oid toastoid, LOCKMODE lock)
+{
+	int			num_indexes;
+	int			validIndex;
+	Oid			validIndexOid;
+	Relation   *toastidxs;
+	Relation	toastrel;
+
+	/* Open the toast relation */
+	toastrel = table_open(toastoid, lock);
+
+	/* Look for the valid index of the toast relation */
+	validIndex = toast_open_indexes(toastrel,
+									lock,
+									&toastidxs,
+									&num_indexes);
+	validIndexOid = RelationGetRelid(toastidxs[validIndex]);
+
+	/* Close the toast relation and all its indexes */
+	toast_close_indexes(toastidxs, num_indexes, lock);
+	table_close(toastrel, lock);
+
+	return validIndexOid;
+}
+
+/* ----------
+ * toast_open_indexes
+ *
+ *	Get an array of the indexes associated to the given toast relation
+ *	and return as well the position of the valid index used by the toast
+ *	relation in this array. It is the responsibility of the caller of this
+ *	function to close the indexes as well as free them.
+ */
+int
+toast_open_indexes(Relation toastrel,
+				   LOCKMODE lock,
+				   Relation **toastidxs,
+				   int *num_indexes)
+{
+	int			i = 0;
+	int			res = 0;
+	bool		found = false;
+	List	   *indexlist;
+	ListCell   *lc;
+
+	/* Get index list of the toast relation */
+	indexlist = RelationGetIndexList(toastrel);
+	Assert(indexlist != NIL);
+
+	*num_indexes = list_length(indexlist);
+
+	/* Open all the index relations */
+	*toastidxs = (Relation *) palloc(*num_indexes * sizeof(Relation));
+	foreach(lc, indexlist)
+		(*toastidxs)[i++] = index_open(lfirst_oid(lc), lock);
+
+	/* Fetch the first valid index in list */
+	for (i = 0; i < *num_indexes; i++)
+	{
+		Relation	toastidx = (*toastidxs)[i];
+
+		if (toastidx->rd_index->indisvalid)
+		{
+			res = i;
+			found = true;
+			break;
+		}
+	}
+
+	/*
+	 * Free index list, not necessary anymore as relations are opened and a
+	 * valid index has been found.
+	 */
+	list_free(indexlist);
+
+	/*
+	 * The toast relation should have one valid index, so something is going
+	 * wrong if there is nothing.
+	 */
+	if (!found)
+		elog(ERROR, "no valid index found for toast relation with Oid %u",
+			 RelationGetRelid(toastrel));
+
+	return res;
+}
+
+/* ----------
+ * toast_close_indexes
+ *
+ *	Close an array of indexes for a toast relation and free it. This should
+ *	be called for a set of indexes opened previously with toast_open_indexes.
+ */
+void
+toast_close_indexes(Relation *toastidxs, int num_indexes, LOCKMODE lock)
+{
+	int			i;
+
+	/* Close relations and clean up things */
+	for (i = 0; i < num_indexes; i++)
+		index_close(toastidxs[i], lock);
+	pfree(toastidxs);
+}
+
+/* ----------
+ * init_toast_snapshot
+ *
+ *	Initialize an appropriate TOAST snapshot.  We must use an MVCC snapshot
+ *	to initialize the TOAST snapshot; since we don't know which one to use,
+ *	just use the oldest one.  This is safe: at worst, we will get a "snapshot
+ *	too old" error that might have been avoided otherwise.
+ */
+void
+init_toast_snapshot(Snapshot toast_snapshot)
+{
+	Snapshot	snapshot = GetOldestSnapshot();
+
+	if (snapshot == NULL)
+		elog(ERROR, "no known snapshots");
+
+	InitToastSnapshot(*toast_snapshot, snapshot->lsn, snapshot->whenTaken);
+}
diff --git a/src/backend/access/heap/Makefile b/src/backend/access/heap/Makefile
index b2a017249b..38497b09c0 100644
--- a/src/backend/access/heap/Makefile
+++ b/src/backend/access/heap/Makefile
@@ -13,6 +13,6 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = heapam.o heapam_handler.o heapam_visibility.o hio.o pruneheap.o rewriteheap.o \
-	syncscan.o tuptoaster.o vacuumlazy.o visibilitymap.o
+	syncscan.o heaptoast.o vacuumlazy.o visibilitymap.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 8ac0f8a513..d32b638dd9 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -36,6 +36,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/heapam_xlog.h"
+#include "access/heaptoast.h"
 #include "access/hio.h"
 #include "access/multixact.h"
 #include "access/parallel.h"
@@ -43,7 +44,6 @@
 #include "access/sysattr.h"
 #include "access/tableam.h"
 #include "access/transam.h"
-#include "access/tuptoaster.h"
 #include "access/valid.h"
 #include "access/visibilitymap.h"
 #include "access/xact.h"
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index b7d2ddbbdc..468f045434 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -25,11 +25,11 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/heaptoast.h"
 #include "access/multixact.h"
 #include "access/rewriteheap.h"
 #include "access/tableam.h"
 #include "access/tsmapi.h"
-#include "access/tuptoaster.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
 #include "catalog/index.h"
diff --git a/src/backend/access/heap/heaptoast.c b/src/backend/access/heap/heaptoast.c
new file mode 100644
index 0000000000..5d105e3517
--- /dev/null
+++ b/src/backend/access/heap/heaptoast.c
@@ -0,0 +1,917 @@
+/*-------------------------------------------------------------------------
+ *
+ * heaptoast.c
+ *	  Heap-specific definitions for external and compressed storage
+ *	  of variable size attributes.
+ *
+ * Copyright (c) 2000-2019, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/heap/heaptoast.c
+ *
+ *
+ * INTERFACE ROUTINES
+ *		toast_insert_or_update -
+ *			Try to make a given tuple fit into one page by compressing
+ *			or moving off attributes
+ *
+ *		toast_delete -
+ *			Reclaim toast storage when a tuple is deleted
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/detoast.h"
+#include "access/heapam.h"
+#include "access/heaptoast.h"
+#include "access/toast_internals.h"
+
+
+/* ----------
+ * toast_delete -
+ *
+ *	Cascaded delete toast-entries on DELETE
+ * ----------
+ */
+void
+toast_delete(Relation rel, HeapTuple oldtup, bool is_speculative)
+{
+	TupleDesc	tupleDesc;
+	int			numAttrs;
+	int			i;
+	Datum		toast_values[MaxHeapAttributeNumber];
+	bool		toast_isnull[MaxHeapAttributeNumber];
+
+	/*
+	 * We should only ever be called for tuples of plain relations or
+	 * materialized views --- recursing on a toast rel is bad news.
+	 */
+	Assert(rel->rd_rel->relkind == RELKIND_RELATION ||
+		   rel->rd_rel->relkind == RELKIND_MATVIEW);
+
+	/*
+	 * Get the tuple descriptor and break down the tuple into fields.
+	 *
+	 * NOTE: it's debatable whether to use heap_deform_tuple() here or just
+	 * heap_getattr() only the varlena columns.  The latter could win if there
+	 * are few varlena columns and many non-varlena ones. However,
+	 * heap_deform_tuple costs only O(N) while the heap_getattr way would cost
+	 * O(N^2) if there are many varlena columns, so it seems better to err on
+	 * the side of linear cost.  (We won't even be here unless there's at
+	 * least one varlena column, by the way.)
+	 */
+	tupleDesc = rel->rd_att;
+	numAttrs = tupleDesc->natts;
+
+	Assert(numAttrs <= MaxHeapAttributeNumber);
+	heap_deform_tuple(oldtup, tupleDesc, toast_values, toast_isnull);
+
+	/*
+	 * Check for external stored attributes and delete them from the secondary
+	 * relation.
+	 */
+	for (i = 0; i < numAttrs; i++)
+	{
+		if (TupleDescAttr(tupleDesc, i)->attlen == -1)
+		{
+			Datum		value = toast_values[i];
+
+			if (toast_isnull[i])
+				continue;
+			else if (VARATT_IS_EXTERNAL_ONDISK(PointerGetDatum(value)))
+				toast_delete_datum(rel, value, is_speculative);
+		}
+	}
+}
+
+
+/* ----------
+ * toast_insert_or_update -
+ *
+ *	Delete no-longer-used toast-entries and create new ones to
+ *	make the new tuple fit on INSERT or UPDATE
+ *
+ * Inputs:
+ *	newtup: the candidate new tuple to be inserted
+ *	oldtup: the old row version for UPDATE, or NULL for INSERT
+ *	options: options to be passed to heap_insert() for toast rows
+ * Result:
+ *	either newtup if no toasting is needed, or a palloc'd modified tuple
+ *	that is what should actually get stored
+ *
+ * NOTE: neither newtup nor oldtup will be modified.  This is a change
+ * from the pre-8.1 API of this routine.
+ * ----------
+ */
+HeapTuple
+toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
+					   int options)
+{
+	HeapTuple	result_tuple;
+	TupleDesc	tupleDesc;
+	int			numAttrs;
+	int			i;
+
+	bool		need_change = false;
+	bool		need_free = false;
+	bool		need_delold = false;
+	bool		has_nulls = false;
+
+	Size		maxDataLen;
+	Size		hoff;
+
+	char		toast_action[MaxHeapAttributeNumber];
+	bool		toast_isnull[MaxHeapAttributeNumber];
+	bool		toast_oldisnull[MaxHeapAttributeNumber];
+	Datum		toast_values[MaxHeapAttributeNumber];
+	Datum		toast_oldvalues[MaxHeapAttributeNumber];
+	struct varlena *toast_oldexternal[MaxHeapAttributeNumber];
+	int32		toast_sizes[MaxHeapAttributeNumber];
+	bool		toast_free[MaxHeapAttributeNumber];
+	bool		toast_delold[MaxHeapAttributeNumber];
+
+	/*
+	 * Ignore the INSERT_SPECULATIVE option. Speculative insertions/super
+	 * deletions just normally insert/delete the toast values. It seems
+	 * easiest to deal with that here, instead on, potentially, multiple
+	 * callers.
+	 */
+	options &= ~HEAP_INSERT_SPECULATIVE;
+
+	/*
+	 * We should only ever be called for tuples of plain relations or
+	 * materialized views --- recursing on a toast rel is bad news.
+	 */
+	Assert(rel->rd_rel->relkind == RELKIND_RELATION ||
+		   rel->rd_rel->relkind == RELKIND_MATVIEW);
+
+	/*
+	 * Get the tuple descriptor and break down the tuple(s) into fields.
+	 */
+	tupleDesc = rel->rd_att;
+	numAttrs = tupleDesc->natts;
+
+	Assert(numAttrs <= MaxHeapAttributeNumber);
+	heap_deform_tuple(newtup, tupleDesc, toast_values, toast_isnull);
+	if (oldtup != NULL)
+		heap_deform_tuple(oldtup, tupleDesc, toast_oldvalues, toast_oldisnull);
+
+	/* ----------
+	 * Then collect information about the values given
+	 *
+	 * NOTE: toast_action[i] can have these values:
+	 *		' '		default handling
+	 *		'p'		already processed --- don't touch it
+	 *		'x'		incompressible, but OK to move off
+	 *
+	 * NOTE: toast_sizes[i] is only made valid for varlena attributes with
+	 *		toast_action[i] different from 'p'.
+	 * ----------
+	 */
+	memset(toast_action, ' ', numAttrs * sizeof(char));
+	memset(toast_oldexternal, 0, numAttrs * sizeof(struct varlena *));
+	memset(toast_free, 0, numAttrs * sizeof(bool));
+	memset(toast_delold, 0, numAttrs * sizeof(bool));
+
+	for (i = 0; i < numAttrs; i++)
+	{
+		Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
+		struct varlena *old_value;
+		struct varlena *new_value;
+
+		if (oldtup != NULL)
+		{
+			/*
+			 * For UPDATE get the old and new values of this attribute
+			 */
+			old_value = (struct varlena *) DatumGetPointer(toast_oldvalues[i]);
+			new_value = (struct varlena *) DatumGetPointer(toast_values[i]);
+
+			/*
+			 * If the old value is stored on disk, check if it has changed so
+			 * we have to delete it later.
+			 */
+			if (att->attlen == -1 && !toast_oldisnull[i] &&
+				VARATT_IS_EXTERNAL_ONDISK(old_value))
+			{
+				if (toast_isnull[i] || !VARATT_IS_EXTERNAL_ONDISK(new_value) ||
+					memcmp((char *) old_value, (char *) new_value,
+						   VARSIZE_EXTERNAL(old_value)) != 0)
+				{
+					/*
+					 * The old external stored value isn't needed any more
+					 * after the update
+					 */
+					toast_delold[i] = true;
+					need_delold = true;
+				}
+				else
+				{
+					/*
+					 * This attribute isn't changed by this update so we reuse
+					 * the original reference to the old value in the new
+					 * tuple.
+					 */
+					toast_action[i] = 'p';
+					continue;
+				}
+			}
+		}
+		else
+		{
+			/*
+			 * For INSERT simply get the new value
+			 */
+			new_value = (struct varlena *) DatumGetPointer(toast_values[i]);
+		}
+
+		/*
+		 * Handle NULL attributes
+		 */
+		if (toast_isnull[i])
+		{
+			toast_action[i] = 'p';
+			has_nulls = true;
+			continue;
+		}
+
+		/*
+		 * Now look at varlena attributes
+		 */
+		if (att->attlen == -1)
+		{
+			/*
+			 * If the table's attribute says PLAIN always, force it so.
+			 */
+			if (att->attstorage == 'p')
+				toast_action[i] = 'p';
+
+			/*
+			 * We took care of UPDATE above, so any external value we find
+			 * still in the tuple must be someone else's that we cannot reuse
+			 * (this includes the case of an out-of-line in-memory datum).
+			 * Fetch it back (without decompression, unless we are forcing
+			 * PLAIN storage).  If necessary, we'll push it out as a new
+			 * external value below.
+			 */
+			if (VARATT_IS_EXTERNAL(new_value))
+			{
+				toast_oldexternal[i] = new_value;
+				if (att->attstorage == 'p')
+					new_value = heap_tuple_untoast_attr(new_value);
+				else
+					new_value = heap_tuple_fetch_attr(new_value);
+				toast_values[i] = PointerGetDatum(new_value);
+				toast_free[i] = true;
+				need_change = true;
+				need_free = true;
+			}
+
+			/*
+			 * Remember the size of this attribute
+			 */
+			toast_sizes[i] = VARSIZE_ANY(new_value);
+		}
+		else
+		{
+			/*
+			 * Not a varlena attribute, plain storage always
+			 */
+			toast_action[i] = 'p';
+		}
+	}
+
+	/* ----------
+	 * Compress and/or save external until data fits into target length
+	 *
+	 *	1: Inline compress attributes with attstorage 'x', and store very
+	 *	   large attributes with attstorage 'x' or 'e' external immediately
+	 *	2: Store attributes with attstorage 'x' or 'e' external
+	 *	3: Inline compress attributes with attstorage 'm'
+	 *	4: Store attributes with attstorage 'm' external
+	 * ----------
+	 */
+
+	/* compute header overhead --- this should match heap_form_tuple() */
+	hoff = SizeofHeapTupleHeader;
+	if (has_nulls)
+		hoff += BITMAPLEN(numAttrs);
+	hoff = MAXALIGN(hoff);
+	/* now convert to a limit on the tuple data size */
+	maxDataLen = RelationGetToastTupleTarget(rel, TOAST_TUPLE_TARGET) - hoff;
+
+	/*
+	 * Look for attributes with attstorage 'x' to compress.  Also find large
+	 * attributes with attstorage 'x' or 'e', and store them external.
+	 */
+	while (heap_compute_data_size(tupleDesc,
+								  toast_values, toast_isnull) > maxDataLen)
+	{
+		int			biggest_attno = -1;
+		int32		biggest_size = MAXALIGN(TOAST_POINTER_SIZE);
+		Datum		old_value;
+		Datum		new_value;
+
+		/*
+		 * Search for the biggest yet unprocessed internal attribute
+		 */
+		for (i = 0; i < numAttrs; i++)
+		{
+			Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
+
+			if (toast_action[i] != ' ')
+				continue;
+			if (VARATT_IS_EXTERNAL(DatumGetPointer(toast_values[i])))
+				continue;		/* can't happen, toast_action would be 'p' */
+			if (VARATT_IS_COMPRESSED(DatumGetPointer(toast_values[i])))
+				continue;
+			if (att->attstorage != 'x' && att->attstorage != 'e')
+				continue;
+			if (toast_sizes[i] > biggest_size)
+			{
+				biggest_attno = i;
+				biggest_size = toast_sizes[i];
+			}
+		}
+
+		if (biggest_attno < 0)
+			break;
+
+		/*
+		 * Attempt to compress it inline, if it has attstorage 'x'
+		 */
+		i = biggest_attno;
+		if (TupleDescAttr(tupleDesc, i)->attstorage == 'x')
+		{
+			old_value = toast_values[i];
+			new_value = toast_compress_datum(old_value);
+
+			if (DatumGetPointer(new_value) != NULL)
+			{
+				/* successful compression */
+				if (toast_free[i])
+					pfree(DatumGetPointer(old_value));
+				toast_values[i] = new_value;
+				toast_free[i] = true;
+				toast_sizes[i] = VARSIZE(DatumGetPointer(toast_values[i]));
+				need_change = true;
+				need_free = true;
+			}
+			else
+			{
+				/* incompressible, ignore on subsequent compression passes */
+				toast_action[i] = 'x';
+			}
+		}
+		else
+		{
+			/* has attstorage 'e', ignore on subsequent compression passes */
+			toast_action[i] = 'x';
+		}
+
+		/*
+		 * If this value is by itself more than maxDataLen (after compression
+		 * if any), push it out to the toast table immediately, if possible.
+		 * This avoids uselessly compressing other fields in the common case
+		 * where we have one long field and several short ones.
+		 *
+		 * XXX maybe the threshold should be less than maxDataLen?
+		 */
+		if (toast_sizes[i] > maxDataLen &&
+			rel->rd_rel->reltoastrelid != InvalidOid)
+		{
+			old_value = toast_values[i];
+			toast_action[i] = 'p';
+			toast_values[i] = toast_save_datum(rel, toast_values[i],
+											   toast_oldexternal[i], options);
+			if (toast_free[i])
+				pfree(DatumGetPointer(old_value));
+			toast_free[i] = true;
+			need_change = true;
+			need_free = true;
+		}
+	}
+
+	/*
+	 * Second we look for attributes of attstorage 'x' or 'e' that are still
+	 * inline.  But skip this if there's no toast table to push them to.
+	 */
+	while (heap_compute_data_size(tupleDesc,
+								  toast_values, toast_isnull) > maxDataLen &&
+		   rel->rd_rel->reltoastrelid != InvalidOid)
+	{
+		int			biggest_attno = -1;
+		int32		biggest_size = MAXALIGN(TOAST_POINTER_SIZE);
+		Datum		old_value;
+
+		/*------
+		 * Search for the biggest yet inlined attribute with
+		 * attstorage equals 'x' or 'e'
+		 *------
+		 */
+		for (i = 0; i < numAttrs; i++)
+		{
+			Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
+
+			if (toast_action[i] == 'p')
+				continue;
+			if (VARATT_IS_EXTERNAL(DatumGetPointer(toast_values[i])))
+				continue;		/* can't happen, toast_action would be 'p' */
+			if (att->attstorage != 'x' && att->attstorage != 'e')
+				continue;
+			if (toast_sizes[i] > biggest_size)
+			{
+				biggest_attno = i;
+				biggest_size = toast_sizes[i];
+			}
+		}
+
+		if (biggest_attno < 0)
+			break;
+
+		/*
+		 * Store this external
+		 */
+		i = biggest_attno;
+		old_value = toast_values[i];
+		toast_action[i] = 'p';
+		toast_values[i] = toast_save_datum(rel, toast_values[i],
+										   toast_oldexternal[i], options);
+		if (toast_free[i])
+			pfree(DatumGetPointer(old_value));
+		toast_free[i] = true;
+
+		need_change = true;
+		need_free = true;
+	}
+
+	/*
+	 * Round 3 - this time we take attributes with storage 'm' into
+	 * compression
+	 */
+	while (heap_compute_data_size(tupleDesc,
+								  toast_values, toast_isnull) > maxDataLen)
+	{
+		int			biggest_attno = -1;
+		int32		biggest_size = MAXALIGN(TOAST_POINTER_SIZE);
+		Datum		old_value;
+		Datum		new_value;
+
+		/*
+		 * Search for the biggest yet uncompressed internal attribute
+		 */
+		for (i = 0; i < numAttrs; i++)
+		{
+			if (toast_action[i] != ' ')
+				continue;
+			if (VARATT_IS_EXTERNAL(DatumGetPointer(toast_values[i])))
+				continue;		/* can't happen, toast_action would be 'p' */
+			if (VARATT_IS_COMPRESSED(DatumGetPointer(toast_values[i])))
+				continue;
+			if (TupleDescAttr(tupleDesc, i)->attstorage != 'm')
+				continue;
+			if (toast_sizes[i] > biggest_size)
+			{
+				biggest_attno = i;
+				biggest_size = toast_sizes[i];
+			}
+		}
+
+		if (biggest_attno < 0)
+			break;
+
+		/*
+		 * Attempt to compress it inline
+		 */
+		i = biggest_attno;
+		old_value = toast_values[i];
+		new_value = toast_compress_datum(old_value);
+
+		if (DatumGetPointer(new_value) != NULL)
+		{
+			/* successful compression */
+			if (toast_free[i])
+				pfree(DatumGetPointer(old_value));
+			toast_values[i] = new_value;
+			toast_free[i] = true;
+			toast_sizes[i] = VARSIZE(DatumGetPointer(toast_values[i]));
+			need_change = true;
+			need_free = true;
+		}
+		else
+		{
+			/* incompressible, ignore on subsequent compression passes */
+			toast_action[i] = 'x';
+		}
+	}
+
+	/*
+	 * Finally we store attributes of type 'm' externally.  At this point we
+	 * increase the target tuple size, so that 'm' attributes aren't stored
+	 * externally unless really necessary.
+	 */
+	maxDataLen = TOAST_TUPLE_TARGET_MAIN - hoff;
+
+	while (heap_compute_data_size(tupleDesc,
+								  toast_values, toast_isnull) > maxDataLen &&
+		   rel->rd_rel->reltoastrelid != InvalidOid)
+	{
+		int			biggest_attno = -1;
+		int32		biggest_size = MAXALIGN(TOAST_POINTER_SIZE);
+		Datum		old_value;
+
+		/*--------
+		 * Search for the biggest yet inlined attribute with
+		 * attstorage = 'm'
+		 *--------
+		 */
+		for (i = 0; i < numAttrs; i++)
+		{
+			if (toast_action[i] == 'p')
+				continue;
+			if (VARATT_IS_EXTERNAL(DatumGetPointer(toast_values[i])))
+				continue;		/* can't happen, toast_action would be 'p' */
+			if (TupleDescAttr(tupleDesc, i)->attstorage != 'm')
+				continue;
+			if (toast_sizes[i] > biggest_size)
+			{
+				biggest_attno = i;
+				biggest_size = toast_sizes[i];
+			}
+		}
+
+		if (biggest_attno < 0)
+			break;
+
+		/*
+		 * Store this external
+		 */
+		i = biggest_attno;
+		old_value = toast_values[i];
+		toast_action[i] = 'p';
+		toast_values[i] = toast_save_datum(rel, toast_values[i],
+										   toast_oldexternal[i], options);
+		if (toast_free[i])
+			pfree(DatumGetPointer(old_value));
+		toast_free[i] = true;
+
+		need_change = true;
+		need_free = true;
+	}
+
+	/*
+	 * In the case we toasted any values, we need to build a new heap tuple
+	 * with the changed values.
+	 */
+	if (need_change)
+	{
+		HeapTupleHeader olddata = newtup->t_data;
+		HeapTupleHeader new_data;
+		int32		new_header_len;
+		int32		new_data_len;
+		int32		new_tuple_len;
+
+		/*
+		 * Calculate the new size of the tuple.
+		 *
+		 * Note: we used to assume here that the old tuple's t_hoff must equal
+		 * the new_header_len value, but that was incorrect.  The old tuple
+		 * might have a smaller-than-current natts, if there's been an ALTER
+		 * TABLE ADD COLUMN since it was stored; and that would lead to a
+		 * different conclusion about the size of the null bitmap, or even
+		 * whether there needs to be one at all.
+		 */
+		new_header_len = SizeofHeapTupleHeader;
+		if (has_nulls)
+			new_header_len += BITMAPLEN(numAttrs);
+		new_header_len = MAXALIGN(new_header_len);
+		new_data_len = heap_compute_data_size(tupleDesc,
+											  toast_values, toast_isnull);
+		new_tuple_len = new_header_len + new_data_len;
+
+		/*
+		 * Allocate and zero the space needed, and fill HeapTupleData fields.
+		 */
+		result_tuple = (HeapTuple) palloc0(HEAPTUPLESIZE + new_tuple_len);
+		result_tuple->t_len = new_tuple_len;
+		result_tuple->t_self = newtup->t_self;
+		result_tuple->t_tableOid = newtup->t_tableOid;
+		new_data = (HeapTupleHeader) ((char *) result_tuple + HEAPTUPLESIZE);
+		result_tuple->t_data = new_data;
+
+		/*
+		 * Copy the existing tuple header, but adjust natts and t_hoff.
+		 */
+		memcpy(new_data, olddata, SizeofHeapTupleHeader);
+		HeapTupleHeaderSetNatts(new_data, numAttrs);
+		new_data->t_hoff = new_header_len;
+
+		/* Copy over the data, and fill the null bitmap if needed */
+		heap_fill_tuple(tupleDesc,
+						toast_values,
+						toast_isnull,
+						(char *) new_data + new_header_len,
+						new_data_len,
+						&(new_data->t_infomask),
+						has_nulls ? new_data->t_bits : NULL);
+	}
+	else
+		result_tuple = newtup;
+
+	/*
+	 * Free allocated temp values
+	 */
+	if (need_free)
+		for (i = 0; i < numAttrs; i++)
+			if (toast_free[i])
+				pfree(DatumGetPointer(toast_values[i]));
+
+	/*
+	 * Delete external values from the old tuple
+	 */
+	if (need_delold)
+		for (i = 0; i < numAttrs; i++)
+			if (toast_delold[i])
+				toast_delete_datum(rel, toast_oldvalues[i], false);
+
+	return result_tuple;
+}
+
+
+/* ----------
+ * toast_flatten_tuple -
+ *
+ *	"Flatten" a tuple to contain no out-of-line toasted fields.
+ *	(This does not eliminate compressed or short-header datums.)
+ *
+ *	Note: we expect the caller already checked HeapTupleHasExternal(tup),
+ *	so there is no need for a short-circuit path.
+ * ----------
+ */
+HeapTuple
+toast_flatten_tuple(HeapTuple tup, TupleDesc tupleDesc)
+{
+	HeapTuple	new_tuple;
+	int			numAttrs = tupleDesc->natts;
+	int			i;
+	Datum		toast_values[MaxTupleAttributeNumber];
+	bool		toast_isnull[MaxTupleAttributeNumber];
+	bool		toast_free[MaxTupleAttributeNumber];
+
+	/*
+	 * Break down the tuple into fields.
+	 */
+	Assert(numAttrs <= MaxTupleAttributeNumber);
+	heap_deform_tuple(tup, tupleDesc, toast_values, toast_isnull);
+
+	memset(toast_free, 0, numAttrs * sizeof(bool));
+
+	for (i = 0; i < numAttrs; i++)
+	{
+		/*
+		 * Look at non-null varlena attributes
+		 */
+		if (!toast_isnull[i] && TupleDescAttr(tupleDesc, i)->attlen == -1)
+		{
+			struct varlena *new_value;
+
+			new_value = (struct varlena *) DatumGetPointer(toast_values[i]);
+			if (VARATT_IS_EXTERNAL(new_value))
+			{
+				new_value = heap_tuple_fetch_attr(new_value);
+				toast_values[i] = PointerGetDatum(new_value);
+				toast_free[i] = true;
+			}
+		}
+	}
+
+	/*
+	 * Form the reconfigured tuple.
+	 */
+	new_tuple = heap_form_tuple(tupleDesc, toast_values, toast_isnull);
+
+	/*
+	 * Be sure to copy the tuple's identity fields.  We also make a point of
+	 * copying visibility info, just in case anybody looks at those fields in
+	 * a syscache entry.
+	 */
+	new_tuple->t_self = tup->t_self;
+	new_tuple->t_tableOid = tup->t_tableOid;
+
+	new_tuple->t_data->t_choice = tup->t_data->t_choice;
+	new_tuple->t_data->t_ctid = tup->t_data->t_ctid;
+	new_tuple->t_data->t_infomask &= ~HEAP_XACT_MASK;
+	new_tuple->t_data->t_infomask |=
+		tup->t_data->t_infomask & HEAP_XACT_MASK;
+	new_tuple->t_data->t_infomask2 &= ~HEAP2_XACT_MASK;
+	new_tuple->t_data->t_infomask2 |=
+		tup->t_data->t_infomask2 & HEAP2_XACT_MASK;
+
+	/*
+	 * Free allocated temp values
+	 */
+	for (i = 0; i < numAttrs; i++)
+		if (toast_free[i])
+			pfree(DatumGetPointer(toast_values[i]));
+
+	return new_tuple;
+}
+
+
+/* ----------
+ * toast_flatten_tuple_to_datum -
+ *
+ *	"Flatten" a tuple containing out-of-line toasted fields into a Datum.
+ *	The result is always palloc'd in the current memory context.
+ *
+ *	We have a general rule that Datums of container types (rows, arrays,
+ *	ranges, etc) must not contain any external TOAST pointers.  Without
+ *	this rule, we'd have to look inside each Datum when preparing a tuple
+ *	for storage, which would be expensive and would fail to extend cleanly
+ *	to new sorts of container types.
+ *
+ *	However, we don't want to say that tuples represented as HeapTuples
+ *	can't contain toasted fields, so instead this routine should be called
+ *	when such a HeapTuple is being converted into a Datum.
+ *
+ *	While we're at it, we decompress any compressed fields too.  This is not
+ *	necessary for correctness, but reflects an expectation that compression
+ *	will be more effective if applied to the whole tuple not individual
+ *	fields.  We are not so concerned about that that we want to deconstruct
+ *	and reconstruct tuples just to get rid of compressed fields, however.
+ *	So callers typically won't call this unless they see that the tuple has
+ *	at least one external field.
+ *
+ *	On the other hand, in-line short-header varlena fields are left alone.
+ *	If we "untoasted" them here, they'd just get changed back to short-header
+ *	format anyway within heap_fill_tuple.
+ * ----------
+ */
+Datum
+toast_flatten_tuple_to_datum(HeapTupleHeader tup,
+							 uint32 tup_len,
+							 TupleDesc tupleDesc)
+{
+	HeapTupleHeader new_data;
+	int32		new_header_len;
+	int32		new_data_len;
+	int32		new_tuple_len;
+	HeapTupleData tmptup;
+	int			numAttrs = tupleDesc->natts;
+	int			i;
+	bool		has_nulls = false;
+	Datum		toast_values[MaxTupleAttributeNumber];
+	bool		toast_isnull[MaxTupleAttributeNumber];
+	bool		toast_free[MaxTupleAttributeNumber];
+
+	/* Build a temporary HeapTuple control structure */
+	tmptup.t_len = tup_len;
+	ItemPointerSetInvalid(&(tmptup.t_self));
+	tmptup.t_tableOid = InvalidOid;
+	tmptup.t_data = tup;
+
+	/*
+	 * Break down the tuple into fields.
+	 */
+	Assert(numAttrs <= MaxTupleAttributeNumber);
+	heap_deform_tuple(&tmptup, tupleDesc, toast_values, toast_isnull);
+
+	memset(toast_free, 0, numAttrs * sizeof(bool));
+
+	for (i = 0; i < numAttrs; i++)
+	{
+		/*
+		 * Look at non-null varlena attributes
+		 */
+		if (toast_isnull[i])
+			has_nulls = true;
+		else if (TupleDescAttr(tupleDesc, i)->attlen == -1)
+		{
+			struct varlena *new_value;
+
+			new_value = (struct varlena *) DatumGetPointer(toast_values[i]);
+			if (VARATT_IS_EXTERNAL(new_value) ||
+				VARATT_IS_COMPRESSED(new_value))
+			{
+				new_value = heap_tuple_untoast_attr(new_value);
+				toast_values[i] = PointerGetDatum(new_value);
+				toast_free[i] = true;
+			}
+		}
+	}
+
+	/*
+	 * Calculate the new size of the tuple.
+	 *
+	 * This should match the reconstruction code in toast_insert_or_update.
+	 */
+	new_header_len = SizeofHeapTupleHeader;
+	if (has_nulls)
+		new_header_len += BITMAPLEN(numAttrs);
+	new_header_len = MAXALIGN(new_header_len);
+	new_data_len = heap_compute_data_size(tupleDesc,
+										  toast_values, toast_isnull);
+	new_tuple_len = new_header_len + new_data_len;
+
+	new_data = (HeapTupleHeader) palloc0(new_tuple_len);
+
+	/*
+	 * Copy the existing tuple header, but adjust natts and t_hoff.
+	 */
+	memcpy(new_data, tup, SizeofHeapTupleHeader);
+	HeapTupleHeaderSetNatts(new_data, numAttrs);
+	new_data->t_hoff = new_header_len;
+
+	/* Set the composite-Datum header fields correctly */
+	HeapTupleHeaderSetDatumLength(new_data, new_tuple_len);
+	HeapTupleHeaderSetTypeId(new_data, tupleDesc->tdtypeid);
+	HeapTupleHeaderSetTypMod(new_data, tupleDesc->tdtypmod);
+
+	/* Copy over the data, and fill the null bitmap if needed */
+	heap_fill_tuple(tupleDesc,
+					toast_values,
+					toast_isnull,
+					(char *) new_data + new_header_len,
+					new_data_len,
+					&(new_data->t_infomask),
+					has_nulls ? new_data->t_bits : NULL);
+
+	/*
+	 * Free allocated temp values
+	 */
+	for (i = 0; i < numAttrs; i++)
+		if (toast_free[i])
+			pfree(DatumGetPointer(toast_values[i]));
+
+	return PointerGetDatum(new_data);
+}
+
+
+/* ----------
+ * toast_build_flattened_tuple -
+ *
+ *	Build a tuple containing no out-of-line toasted fields.
+ *	(This does not eliminate compressed or short-header datums.)
+ *
+ *	This is essentially just like heap_form_tuple, except that it will
+ *	expand any external-data pointers beforehand.
+ *
+ *	It's not very clear whether it would be preferable to decompress
+ *	in-line compressed datums while at it.  For now, we don't.
+ * ----------
+ */
+HeapTuple
+toast_build_flattened_tuple(TupleDesc tupleDesc,
+							Datum *values,
+							bool *isnull)
+{
+	HeapTuple	new_tuple;
+	int			numAttrs = tupleDesc->natts;
+	int			num_to_free;
+	int			i;
+	Datum		new_values[MaxTupleAttributeNumber];
+	Pointer		freeable_values[MaxTupleAttributeNumber];
+
+	/*
+	 * We can pass the caller's isnull array directly to heap_form_tuple, but
+	 * we potentially need to modify the values array.
+	 */
+	Assert(numAttrs <= MaxTupleAttributeNumber);
+	memcpy(new_values, values, numAttrs * sizeof(Datum));
+
+	num_to_free = 0;
+	for (i = 0; i < numAttrs; i++)
+	{
+		/*
+		 * Look at non-null varlena attributes
+		 */
+		if (!isnull[i] && TupleDescAttr(tupleDesc, i)->attlen == -1)
+		{
+			struct varlena *new_value;
+
+			new_value = (struct varlena *) DatumGetPointer(new_values[i]);
+			if (VARATT_IS_EXTERNAL(new_value))
+			{
+				new_value = heap_tuple_fetch_attr(new_value);
+				new_values[i] = PointerGetDatum(new_value);
+				freeable_values[num_to_free++] = (Pointer) new_value;
+			}
+		}
+	}
+
+	/*
+	 * Form the reconfigured tuple.
+	 */
+	new_tuple = heap_form_tuple(tupleDesc, new_values, isnull);
+
+	/*
+	 * Free allocated temp values
+	 */
+	for (i = 0; i < num_to_free; i++)
+		pfree(freeable_values[i]);
+
+	return new_tuple;
+}
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 369694fa2e..c6f60227d0 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -109,9 +109,9 @@
 
 #include "access/heapam.h"
 #include "access/heapam_xlog.h"
+#include "access/heaptoast.h"
 #include "access/rewriteheap.h"
 #include "access/transam.h"
-#include "access/tuptoaster.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 
diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
deleted file mode 100644
index 55d6e91d1c..0000000000
--- a/src/backend/access/heap/tuptoaster.c
+++ /dev/null
@@ -1,2411 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * tuptoaster.c
- *	  Support routines for external and compressed storage of
- *	  variable size attributes.
- *
- * Copyright (c) 2000-2019, PostgreSQL Global Development Group
- *
- *
- * IDENTIFICATION
- *	  src/backend/access/heap/tuptoaster.c
- *
- *
- * INTERFACE ROUTINES
- *		toast_insert_or_update -
- *			Try to make a given tuple fit into one page by compressing
- *			or moving off attributes
- *
- *		toast_delete -
- *			Reclaim toast storage when a tuple is deleted
- *
- *		heap_tuple_untoast_attr -
- *			Fetch back a given value from the "secondary" relation
- *
- *-------------------------------------------------------------------------
- */
-
-#include "postgres.h"
-
-#include <unistd.h>
-#include <fcntl.h>
-
-#include "access/genam.h"
-#include "access/heapam.h"
-#include "access/tuptoaster.h"
-#include "access/xact.h"
-#include "catalog/catalog.h"
-#include "common/pg_lzcompress.h"
-#include "miscadmin.h"
-#include "utils/expandeddatum.h"
-#include "utils/fmgroids.h"
-#include "utils/rel.h"
-#include "utils/snapmgr.h"
-#include "utils/typcache.h"
-
-
-#undef TOAST_DEBUG
-
-/*
- *	The information at the start of the compressed toast data.
- */
-typedef struct toast_compress_header
-{
-	int32		vl_len_;		/* varlena header (do not touch directly!) */
-	int32		rawsize;
-} toast_compress_header;
-
-/*
- * Utilities for manipulation of header information for compressed
- * toast entries.
- */
-#define TOAST_COMPRESS_HDRSZ		((int32) sizeof(toast_compress_header))
-#define TOAST_COMPRESS_RAWSIZE(ptr) (((toast_compress_header *) (ptr))->rawsize)
-#define TOAST_COMPRESS_RAWDATA(ptr) \
-	(((char *) (ptr)) + TOAST_COMPRESS_HDRSZ)
-#define TOAST_COMPRESS_SET_RAWSIZE(ptr, len) \
-	(((toast_compress_header *) (ptr))->rawsize = (len))
-
-static void toast_delete_datum(Relation rel, Datum value, bool is_speculative);
-static Datum toast_save_datum(Relation rel, Datum value,
-							  struct varlena *oldexternal, int options);
-static bool toastrel_valueid_exists(Relation toastrel, Oid valueid);
-static bool toastid_valueid_exists(Oid toastrelid, Oid valueid);
-static struct varlena *toast_fetch_datum(struct varlena *attr);
-static struct varlena *toast_fetch_datum_slice(struct varlena *attr,
-											   int32 sliceoffset, int32 length);
-static struct varlena *toast_decompress_datum(struct varlena *attr);
-static struct varlena *toast_decompress_datum_slice(struct varlena *attr, int32 slicelength);
-static int	toast_open_indexes(Relation toastrel,
-							   LOCKMODE lock,
-							   Relation **toastidxs,
-							   int *num_indexes);
-static void toast_close_indexes(Relation *toastidxs, int num_indexes,
-								LOCKMODE lock);
-static void init_toast_snapshot(Snapshot toast_snapshot);
-
-
-/* ----------
- * heap_tuple_fetch_attr -
- *
- *	Public entry point to get back a toasted value from
- *	external source (possibly still in compressed format).
- *
- * This will return a datum that contains all the data internally, ie, not
- * relying on external storage or memory, but it can still be compressed or
- * have a short header.  Note some callers assume that if the input is an
- * EXTERNAL datum, the result will be a pfree'able chunk.
- * ----------
- */
-struct varlena *
-heap_tuple_fetch_attr(struct varlena *attr)
-{
-	struct varlena *result;
-
-	if (VARATT_IS_EXTERNAL_ONDISK(attr))
-	{
-		/*
-		 * This is an external stored plain value
-		 */
-		result = toast_fetch_datum(attr);
-	}
-	else if (VARATT_IS_EXTERNAL_INDIRECT(attr))
-	{
-		/*
-		 * This is an indirect pointer --- dereference it
-		 */
-		struct varatt_indirect redirect;
-
-		VARATT_EXTERNAL_GET_POINTER(redirect, attr);
-		attr = (struct varlena *) redirect.pointer;
-
-		/* nested indirect Datums aren't allowed */
-		Assert(!VARATT_IS_EXTERNAL_INDIRECT(attr));
-
-		/* recurse if value is still external in some other way */
-		if (VARATT_IS_EXTERNAL(attr))
-			return heap_tuple_fetch_attr(attr);
-
-		/*
-		 * Copy into the caller's memory context, in case caller tries to
-		 * pfree the result.
-		 */
-		result = (struct varlena *) palloc(VARSIZE_ANY(attr));
-		memcpy(result, attr, VARSIZE_ANY(attr));
-	}
-	else if (VARATT_IS_EXTERNAL_EXPANDED(attr))
-	{
-		/*
-		 * This is an expanded-object pointer --- get flat format
-		 */
-		ExpandedObjectHeader *eoh;
-		Size		resultsize;
-
-		eoh = DatumGetEOHP(PointerGetDatum(attr));
-		resultsize = EOH_get_flat_size(eoh);
-		result = (struct varlena *) palloc(resultsize);
-		EOH_flatten_into(eoh, (void *) result, resultsize);
-	}
-	else
-	{
-		/*
-		 * This is a plain value inside of the main tuple - why am I called?
-		 */
-		result = attr;
-	}
-
-	return result;
-}
-
-
-/* ----------
- * heap_tuple_untoast_attr -
- *
- *	Public entry point to get back a toasted value from compression
- *	or external storage.  The result is always non-extended varlena form.
- *
- * Note some callers assume that if the input is an EXTERNAL or COMPRESSED
- * datum, the result will be a pfree'able chunk.
- * ----------
- */
-struct varlena *
-heap_tuple_untoast_attr(struct varlena *attr)
-{
-	if (VARATT_IS_EXTERNAL_ONDISK(attr))
-	{
-		/*
-		 * This is an externally stored datum --- fetch it back from there
-		 */
-		attr = toast_fetch_datum(attr);
-		/* If it's compressed, decompress it */
-		if (VARATT_IS_COMPRESSED(attr))
-		{
-			struct varlena *tmp = attr;
-
-			attr = toast_decompress_datum(tmp);
-			pfree(tmp);
-		}
-	}
-	else if (VARATT_IS_EXTERNAL_INDIRECT(attr))
-	{
-		/*
-		 * This is an indirect pointer --- dereference it
-		 */
-		struct varatt_indirect redirect;
-
-		VARATT_EXTERNAL_GET_POINTER(redirect, attr);
-		attr = (struct varlena *) redirect.pointer;
-
-		/* nested indirect Datums aren't allowed */
-		Assert(!VARATT_IS_EXTERNAL_INDIRECT(attr));
-
-		/* recurse in case value is still extended in some other way */
-		attr = heap_tuple_untoast_attr(attr);
-
-		/* if it isn't, we'd better copy it */
-		if (attr == (struct varlena *) redirect.pointer)
-		{
-			struct varlena *result;
-
-			result = (struct varlena *) palloc(VARSIZE_ANY(attr));
-			memcpy(result, attr, VARSIZE_ANY(attr));
-			attr = result;
-		}
-	}
-	else if (VARATT_IS_EXTERNAL_EXPANDED(attr))
-	{
-		/*
-		 * This is an expanded-object pointer --- get flat format
-		 */
-		attr = heap_tuple_fetch_attr(attr);
-		/* flatteners are not allowed to produce compressed/short output */
-		Assert(!VARATT_IS_EXTENDED(attr));
-	}
-	else if (VARATT_IS_COMPRESSED(attr))
-	{
-		/*
-		 * This is a compressed value inside of the main tuple
-		 */
-		attr = toast_decompress_datum(attr);
-	}
-	else if (VARATT_IS_SHORT(attr))
-	{
-		/*
-		 * This is a short-header varlena --- convert to 4-byte header format
-		 */
-		Size		data_size = VARSIZE_SHORT(attr) - VARHDRSZ_SHORT;
-		Size		new_size = data_size + VARHDRSZ;
-		struct varlena *new_attr;
-
-		new_attr = (struct varlena *) palloc(new_size);
-		SET_VARSIZE(new_attr, new_size);
-		memcpy(VARDATA(new_attr), VARDATA_SHORT(attr), data_size);
-		attr = new_attr;
-	}
-
-	return attr;
-}
-
-
-/* ----------
- * heap_tuple_untoast_attr_slice -
- *
- *		Public entry point to get back part of a toasted value
- *		from compression or external storage.
- * ----------
- */
-struct varlena *
-heap_tuple_untoast_attr_slice(struct varlena *attr,
-							  int32 sliceoffset, int32 slicelength)
-{
-	struct varlena *preslice;
-	struct varlena *result;
-	char	   *attrdata;
-	int32		attrsize;
-
-	if (VARATT_IS_EXTERNAL_ONDISK(attr))
-	{
-		struct varatt_external toast_pointer;
-
-		VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
-
-		/* fast path for non-compressed external datums */
-		if (!VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))
-			return toast_fetch_datum_slice(attr, sliceoffset, slicelength);
-
-		/* fetch it back (compressed marker will get set automatically) */
-		preslice = toast_fetch_datum(attr);
-	}
-	else if (VARATT_IS_EXTERNAL_INDIRECT(attr))
-	{
-		struct varatt_indirect redirect;
-
-		VARATT_EXTERNAL_GET_POINTER(redirect, attr);
-
-		/* nested indirect Datums aren't allowed */
-		Assert(!VARATT_IS_EXTERNAL_INDIRECT(redirect.pointer));
-
-		return heap_tuple_untoast_attr_slice(redirect.pointer,
-											 sliceoffset, slicelength);
-	}
-	else if (VARATT_IS_EXTERNAL_EXPANDED(attr))
-	{
-		/* pass it off to heap_tuple_fetch_attr to flatten */
-		preslice = heap_tuple_fetch_attr(attr);
-	}
-	else
-		preslice = attr;
-
-	Assert(!VARATT_IS_EXTERNAL(preslice));
-
-	if (VARATT_IS_COMPRESSED(preslice))
-	{
-		struct varlena *tmp = preslice;
-
-		/* Decompress enough to encompass the slice and the offset */
-		if (slicelength > 0 && sliceoffset >= 0)
-			preslice = toast_decompress_datum_slice(tmp, slicelength + sliceoffset);
-		else
-			preslice = toast_decompress_datum(tmp);
-
-		if (tmp != attr)
-			pfree(tmp);
-	}
-
-	if (VARATT_IS_SHORT(preslice))
-	{
-		attrdata = VARDATA_SHORT(preslice);
-		attrsize = VARSIZE_SHORT(preslice) - VARHDRSZ_SHORT;
-	}
-	else
-	{
-		attrdata = VARDATA(preslice);
-		attrsize = VARSIZE(preslice) - VARHDRSZ;
-	}
-
-	/* slicing of datum for compressed cases and plain value */
-
-	if (sliceoffset >= attrsize)
-	{
-		sliceoffset = 0;
-		slicelength = 0;
-	}
-
-	if (((sliceoffset + slicelength) > attrsize) || slicelength < 0)
-		slicelength = attrsize - sliceoffset;
-
-	result = (struct varlena *) palloc(slicelength + VARHDRSZ);
-	SET_VARSIZE(result, slicelength + VARHDRSZ);
-
-	memcpy(VARDATA(result), attrdata + sliceoffset, slicelength);
-
-	if (preslice != attr)
-		pfree(preslice);
-
-	return result;
-}
-
-
-/* ----------
- * toast_raw_datum_size -
- *
- *	Return the raw (detoasted) size of a varlena datum
- *	(including the VARHDRSZ header)
- * ----------
- */
-Size
-toast_raw_datum_size(Datum value)
-{
-	struct varlena *attr = (struct varlena *) DatumGetPointer(value);
-	Size		result;
-
-	if (VARATT_IS_EXTERNAL_ONDISK(attr))
-	{
-		/* va_rawsize is the size of the original datum -- including header */
-		struct varatt_external toast_pointer;
-
-		VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
-		result = toast_pointer.va_rawsize;
-	}
-	else if (VARATT_IS_EXTERNAL_INDIRECT(attr))
-	{
-		struct varatt_indirect toast_pointer;
-
-		VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
-
-		/* nested indirect Datums aren't allowed */
-		Assert(!VARATT_IS_EXTERNAL_INDIRECT(toast_pointer.pointer));
-
-		return toast_raw_datum_size(PointerGetDatum(toast_pointer.pointer));
-	}
-	else if (VARATT_IS_EXTERNAL_EXPANDED(attr))
-	{
-		result = EOH_get_flat_size(DatumGetEOHP(value));
-	}
-	else if (VARATT_IS_COMPRESSED(attr))
-	{
-		/* here, va_rawsize is just the payload size */
-		result = VARRAWSIZE_4B_C(attr) + VARHDRSZ;
-	}
-	else if (VARATT_IS_SHORT(attr))
-	{
-		/*
-		 * we have to normalize the header length to VARHDRSZ or else the
-		 * callers of this function will be confused.
-		 */
-		result = VARSIZE_SHORT(attr) - VARHDRSZ_SHORT + VARHDRSZ;
-	}
-	else
-	{
-		/* plain untoasted datum */
-		result = VARSIZE(attr);
-	}
-	return result;
-}
-
-/* ----------
- * toast_datum_size
- *
- *	Return the physical storage size (possibly compressed) of a varlena datum
- * ----------
- */
-Size
-toast_datum_size(Datum value)
-{
-	struct varlena *attr = (struct varlena *) DatumGetPointer(value);
-	Size		result;
-
-	if (VARATT_IS_EXTERNAL_ONDISK(attr))
-	{
-		/*
-		 * Attribute is stored externally - return the extsize whether
-		 * compressed or not.  We do not count the size of the toast pointer
-		 * ... should we?
-		 */
-		struct varatt_external toast_pointer;
-
-		VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
-		result = toast_pointer.va_extsize;
-	}
-	else if (VARATT_IS_EXTERNAL_INDIRECT(attr))
-	{
-		struct varatt_indirect toast_pointer;
-
-		VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
-
-		/* nested indirect Datums aren't allowed */
-		Assert(!VARATT_IS_EXTERNAL_INDIRECT(attr));
-
-		return toast_datum_size(PointerGetDatum(toast_pointer.pointer));
-	}
-	else if (VARATT_IS_EXTERNAL_EXPANDED(attr))
-	{
-		result = EOH_get_flat_size(DatumGetEOHP(value));
-	}
-	else if (VARATT_IS_SHORT(attr))
-	{
-		result = VARSIZE_SHORT(attr);
-	}
-	else
-	{
-		/*
-		 * Attribute is stored inline either compressed or not, just calculate
-		 * the size of the datum in either case.
-		 */
-		result = VARSIZE(attr);
-	}
-	return result;
-}
-
-
-/* ----------
- * toast_delete -
- *
- *	Cascaded delete toast-entries on DELETE
- * ----------
- */
-void
-toast_delete(Relation rel, HeapTuple oldtup, bool is_speculative)
-{
-	TupleDesc	tupleDesc;
-	int			numAttrs;
-	int			i;
-	Datum		toast_values[MaxHeapAttributeNumber];
-	bool		toast_isnull[MaxHeapAttributeNumber];
-
-	/*
-	 * We should only ever be called for tuples of plain relations or
-	 * materialized views --- recursing on a toast rel is bad news.
-	 */
-	Assert(rel->rd_rel->relkind == RELKIND_RELATION ||
-		   rel->rd_rel->relkind == RELKIND_MATVIEW);
-
-	/*
-	 * Get the tuple descriptor and break down the tuple into fields.
-	 *
-	 * NOTE: it's debatable whether to use heap_deform_tuple() here or just
-	 * heap_getattr() only the varlena columns.  The latter could win if there
-	 * are few varlena columns and many non-varlena ones. However,
-	 * heap_deform_tuple costs only O(N) while the heap_getattr way would cost
-	 * O(N^2) if there are many varlena columns, so it seems better to err on
-	 * the side of linear cost.  (We won't even be here unless there's at
-	 * least one varlena column, by the way.)
-	 */
-	tupleDesc = rel->rd_att;
-	numAttrs = tupleDesc->natts;
-
-	Assert(numAttrs <= MaxHeapAttributeNumber);
-	heap_deform_tuple(oldtup, tupleDesc, toast_values, toast_isnull);
-
-	/*
-	 * Check for external stored attributes and delete them from the secondary
-	 * relation.
-	 */
-	for (i = 0; i < numAttrs; i++)
-	{
-		if (TupleDescAttr(tupleDesc, i)->attlen == -1)
-		{
-			Datum		value = toast_values[i];
-
-			if (toast_isnull[i])
-				continue;
-			else if (VARATT_IS_EXTERNAL_ONDISK(PointerGetDatum(value)))
-				toast_delete_datum(rel, value, is_speculative);
-		}
-	}
-}
-
-
-/* ----------
- * toast_insert_or_update -
- *
- *	Delete no-longer-used toast-entries and create new ones to
- *	make the new tuple fit on INSERT or UPDATE
- *
- * Inputs:
- *	newtup: the candidate new tuple to be inserted
- *	oldtup: the old row version for UPDATE, or NULL for INSERT
- *	options: options to be passed to heap_insert() for toast rows
- * Result:
- *	either newtup if no toasting is needed, or a palloc'd modified tuple
- *	that is what should actually get stored
- *
- * NOTE: neither newtup nor oldtup will be modified.  This is a change
- * from the pre-8.1 API of this routine.
- * ----------
- */
-HeapTuple
-toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
-					   int options)
-{
-	HeapTuple	result_tuple;
-	TupleDesc	tupleDesc;
-	int			numAttrs;
-	int			i;
-
-	bool		need_change = false;
-	bool		need_free = false;
-	bool		need_delold = false;
-	bool		has_nulls = false;
-
-	Size		maxDataLen;
-	Size		hoff;
-
-	char		toast_action[MaxHeapAttributeNumber];
-	bool		toast_isnull[MaxHeapAttributeNumber];
-	bool		toast_oldisnull[MaxHeapAttributeNumber];
-	Datum		toast_values[MaxHeapAttributeNumber];
-	Datum		toast_oldvalues[MaxHeapAttributeNumber];
-	struct varlena *toast_oldexternal[MaxHeapAttributeNumber];
-	int32		toast_sizes[MaxHeapAttributeNumber];
-	bool		toast_free[MaxHeapAttributeNumber];
-	bool		toast_delold[MaxHeapAttributeNumber];
-
-	/*
-	 * Ignore the INSERT_SPECULATIVE option. Speculative insertions/super
-	 * deletions just normally insert/delete the toast values. It seems
-	 * easiest to deal with that here, instead on, potentially, multiple
-	 * callers.
-	 */
-	options &= ~HEAP_INSERT_SPECULATIVE;
-
-	/*
-	 * We should only ever be called for tuples of plain relations or
-	 * materialized views --- recursing on a toast rel is bad news.
-	 */
-	Assert(rel->rd_rel->relkind == RELKIND_RELATION ||
-		   rel->rd_rel->relkind == RELKIND_MATVIEW);
-
-	/*
-	 * Get the tuple descriptor and break down the tuple(s) into fields.
-	 */
-	tupleDesc = rel->rd_att;
-	numAttrs = tupleDesc->natts;
-
-	Assert(numAttrs <= MaxHeapAttributeNumber);
-	heap_deform_tuple(newtup, tupleDesc, toast_values, toast_isnull);
-	if (oldtup != NULL)
-		heap_deform_tuple(oldtup, tupleDesc, toast_oldvalues, toast_oldisnull);
-
-	/* ----------
-	 * Then collect information about the values given
-	 *
-	 * NOTE: toast_action[i] can have these values:
-	 *		' '		default handling
-	 *		'p'		already processed --- don't touch it
-	 *		'x'		incompressible, but OK to move off
-	 *
-	 * NOTE: toast_sizes[i] is only made valid for varlena attributes with
-	 *		toast_action[i] different from 'p'.
-	 * ----------
-	 */
-	memset(toast_action, ' ', numAttrs * sizeof(char));
-	memset(toast_oldexternal, 0, numAttrs * sizeof(struct varlena *));
-	memset(toast_free, 0, numAttrs * sizeof(bool));
-	memset(toast_delold, 0, numAttrs * sizeof(bool));
-
-	for (i = 0; i < numAttrs; i++)
-	{
-		Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
-		struct varlena *old_value;
-		struct varlena *new_value;
-
-		if (oldtup != NULL)
-		{
-			/*
-			 * For UPDATE get the old and new values of this attribute
-			 */
-			old_value = (struct varlena *) DatumGetPointer(toast_oldvalues[i]);
-			new_value = (struct varlena *) DatumGetPointer(toast_values[i]);
-
-			/*
-			 * If the old value is stored on disk, check if it has changed so
-			 * we have to delete it later.
-			 */
-			if (att->attlen == -1 && !toast_oldisnull[i] &&
-				VARATT_IS_EXTERNAL_ONDISK(old_value))
-			{
-				if (toast_isnull[i] || !VARATT_IS_EXTERNAL_ONDISK(new_value) ||
-					memcmp((char *) old_value, (char *) new_value,
-						   VARSIZE_EXTERNAL(old_value)) != 0)
-				{
-					/*
-					 * The old external stored value isn't needed any more
-					 * after the update
-					 */
-					toast_delold[i] = true;
-					need_delold = true;
-				}
-				else
-				{
-					/*
-					 * This attribute isn't changed by this update so we reuse
-					 * the original reference to the old value in the new
-					 * tuple.
-					 */
-					toast_action[i] = 'p';
-					continue;
-				}
-			}
-		}
-		else
-		{
-			/*
-			 * For INSERT simply get the new value
-			 */
-			new_value = (struct varlena *) DatumGetPointer(toast_values[i]);
-		}
-
-		/*
-		 * Handle NULL attributes
-		 */
-		if (toast_isnull[i])
-		{
-			toast_action[i] = 'p';
-			has_nulls = true;
-			continue;
-		}
-
-		/*
-		 * Now look at varlena attributes
-		 */
-		if (att->attlen == -1)
-		{
-			/*
-			 * If the table's attribute says PLAIN always, force it so.
-			 */
-			if (att->attstorage == 'p')
-				toast_action[i] = 'p';
-
-			/*
-			 * We took care of UPDATE above, so any external value we find
-			 * still in the tuple must be someone else's that we cannot reuse
-			 * (this includes the case of an out-of-line in-memory datum).
-			 * Fetch it back (without decompression, unless we are forcing
-			 * PLAIN storage).  If necessary, we'll push it out as a new
-			 * external value below.
-			 */
-			if (VARATT_IS_EXTERNAL(new_value))
-			{
-				toast_oldexternal[i] = new_value;
-				if (att->attstorage == 'p')
-					new_value = heap_tuple_untoast_attr(new_value);
-				else
-					new_value = heap_tuple_fetch_attr(new_value);
-				toast_values[i] = PointerGetDatum(new_value);
-				toast_free[i] = true;
-				need_change = true;
-				need_free = true;
-			}
-
-			/*
-			 * Remember the size of this attribute
-			 */
-			toast_sizes[i] = VARSIZE_ANY(new_value);
-		}
-		else
-		{
-			/*
-			 * Not a varlena attribute, plain storage always
-			 */
-			toast_action[i] = 'p';
-		}
-	}
-
-	/* ----------
-	 * Compress and/or save external until data fits into target length
-	 *
-	 *	1: Inline compress attributes with attstorage 'x', and store very
-	 *	   large attributes with attstorage 'x' or 'e' external immediately
-	 *	2: Store attributes with attstorage 'x' or 'e' external
-	 *	3: Inline compress attributes with attstorage 'm'
-	 *	4: Store attributes with attstorage 'm' external
-	 * ----------
-	 */
-
-	/* compute header overhead --- this should match heap_form_tuple() */
-	hoff = SizeofHeapTupleHeader;
-	if (has_nulls)
-		hoff += BITMAPLEN(numAttrs);
-	hoff = MAXALIGN(hoff);
-	/* now convert to a limit on the tuple data size */
-	maxDataLen = RelationGetToastTupleTarget(rel, TOAST_TUPLE_TARGET) - hoff;
-
-	/*
-	 * Look for attributes with attstorage 'x' to compress.  Also find large
-	 * attributes with attstorage 'x' or 'e', and store them external.
-	 */
-	while (heap_compute_data_size(tupleDesc,
-								  toast_values, toast_isnull) > maxDataLen)
-	{
-		int			biggest_attno = -1;
-		int32		biggest_size = MAXALIGN(TOAST_POINTER_SIZE);
-		Datum		old_value;
-		Datum		new_value;
-
-		/*
-		 * Search for the biggest yet unprocessed internal attribute
-		 */
-		for (i = 0; i < numAttrs; i++)
-		{
-			Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
-
-			if (toast_action[i] != ' ')
-				continue;
-			if (VARATT_IS_EXTERNAL(DatumGetPointer(toast_values[i])))
-				continue;		/* can't happen, toast_action would be 'p' */
-			if (VARATT_IS_COMPRESSED(DatumGetPointer(toast_values[i])))
-				continue;
-			if (att->attstorage != 'x' && att->attstorage != 'e')
-				continue;
-			if (toast_sizes[i] > biggest_size)
-			{
-				biggest_attno = i;
-				biggest_size = toast_sizes[i];
-			}
-		}
-
-		if (biggest_attno < 0)
-			break;
-
-		/*
-		 * Attempt to compress it inline, if it has attstorage 'x'
-		 */
-		i = biggest_attno;
-		if (TupleDescAttr(tupleDesc, i)->attstorage == 'x')
-		{
-			old_value = toast_values[i];
-			new_value = toast_compress_datum(old_value);
-
-			if (DatumGetPointer(new_value) != NULL)
-			{
-				/* successful compression */
-				if (toast_free[i])
-					pfree(DatumGetPointer(old_value));
-				toast_values[i] = new_value;
-				toast_free[i] = true;
-				toast_sizes[i] = VARSIZE(DatumGetPointer(toast_values[i]));
-				need_change = true;
-				need_free = true;
-			}
-			else
-			{
-				/* incompressible, ignore on subsequent compression passes */
-				toast_action[i] = 'x';
-			}
-		}
-		else
-		{
-			/* has attstorage 'e', ignore on subsequent compression passes */
-			toast_action[i] = 'x';
-		}
-
-		/*
-		 * If this value is by itself more than maxDataLen (after compression
-		 * if any), push it out to the toast table immediately, if possible.
-		 * This avoids uselessly compressing other fields in the common case
-		 * where we have one long field and several short ones.
-		 *
-		 * XXX maybe the threshold should be less than maxDataLen?
-		 */
-		if (toast_sizes[i] > maxDataLen &&
-			rel->rd_rel->reltoastrelid != InvalidOid)
-		{
-			old_value = toast_values[i];
-			toast_action[i] = 'p';
-			toast_values[i] = toast_save_datum(rel, toast_values[i],
-											   toast_oldexternal[i], options);
-			if (toast_free[i])
-				pfree(DatumGetPointer(old_value));
-			toast_free[i] = true;
-			need_change = true;
-			need_free = true;
-		}
-	}
-
-	/*
-	 * Second we look for attributes of attstorage 'x' or 'e' that are still
-	 * inline.  But skip this if there's no toast table to push them to.
-	 */
-	while (heap_compute_data_size(tupleDesc,
-								  toast_values, toast_isnull) > maxDataLen &&
-		   rel->rd_rel->reltoastrelid != InvalidOid)
-	{
-		int			biggest_attno = -1;
-		int32		biggest_size = MAXALIGN(TOAST_POINTER_SIZE);
-		Datum		old_value;
-
-		/*------
-		 * Search for the biggest yet inlined attribute with
-		 * attstorage equals 'x' or 'e'
-		 *------
-		 */
-		for (i = 0; i < numAttrs; i++)
-		{
-			Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
-
-			if (toast_action[i] == 'p')
-				continue;
-			if (VARATT_IS_EXTERNAL(DatumGetPointer(toast_values[i])))
-				continue;		/* can't happen, toast_action would be 'p' */
-			if (att->attstorage != 'x' && att->attstorage != 'e')
-				continue;
-			if (toast_sizes[i] > biggest_size)
-			{
-				biggest_attno = i;
-				biggest_size = toast_sizes[i];
-			}
-		}
-
-		if (biggest_attno < 0)
-			break;
-
-		/*
-		 * Store this external
-		 */
-		i = biggest_attno;
-		old_value = toast_values[i];
-		toast_action[i] = 'p';
-		toast_values[i] = toast_save_datum(rel, toast_values[i],
-										   toast_oldexternal[i], options);
-		if (toast_free[i])
-			pfree(DatumGetPointer(old_value));
-		toast_free[i] = true;
-
-		need_change = true;
-		need_free = true;
-	}
-
-	/*
-	 * Round 3 - this time we take attributes with storage 'm' into
-	 * compression
-	 */
-	while (heap_compute_data_size(tupleDesc,
-								  toast_values, toast_isnull) > maxDataLen)
-	{
-		int			biggest_attno = -1;
-		int32		biggest_size = MAXALIGN(TOAST_POINTER_SIZE);
-		Datum		old_value;
-		Datum		new_value;
-
-		/*
-		 * Search for the biggest yet uncompressed internal attribute
-		 */
-		for (i = 0; i < numAttrs; i++)
-		{
-			if (toast_action[i] != ' ')
-				continue;
-			if (VARATT_IS_EXTERNAL(DatumGetPointer(toast_values[i])))
-				continue;		/* can't happen, toast_action would be 'p' */
-			if (VARATT_IS_COMPRESSED(DatumGetPointer(toast_values[i])))
-				continue;
-			if (TupleDescAttr(tupleDesc, i)->attstorage != 'm')
-				continue;
-			if (toast_sizes[i] > biggest_size)
-			{
-				biggest_attno = i;
-				biggest_size = toast_sizes[i];
-			}
-		}
-
-		if (biggest_attno < 0)
-			break;
-
-		/*
-		 * Attempt to compress it inline
-		 */
-		i = biggest_attno;
-		old_value = toast_values[i];
-		new_value = toast_compress_datum(old_value);
-
-		if (DatumGetPointer(new_value) != NULL)
-		{
-			/* successful compression */
-			if (toast_free[i])
-				pfree(DatumGetPointer(old_value));
-			toast_values[i] = new_value;
-			toast_free[i] = true;
-			toast_sizes[i] = VARSIZE(DatumGetPointer(toast_values[i]));
-			need_change = true;
-			need_free = true;
-		}
-		else
-		{
-			/* incompressible, ignore on subsequent compression passes */
-			toast_action[i] = 'x';
-		}
-	}
-
-	/*
-	 * Finally we store attributes of type 'm' externally.  At this point we
-	 * increase the target tuple size, so that 'm' attributes aren't stored
-	 * externally unless really necessary.
-	 */
-	maxDataLen = TOAST_TUPLE_TARGET_MAIN - hoff;
-
-	while (heap_compute_data_size(tupleDesc,
-								  toast_values, toast_isnull) > maxDataLen &&
-		   rel->rd_rel->reltoastrelid != InvalidOid)
-	{
-		int			biggest_attno = -1;
-		int32		biggest_size = MAXALIGN(TOAST_POINTER_SIZE);
-		Datum		old_value;
-
-		/*--------
-		 * Search for the biggest yet inlined attribute with
-		 * attstorage = 'm'
-		 *--------
-		 */
-		for (i = 0; i < numAttrs; i++)
-		{
-			if (toast_action[i] == 'p')
-				continue;
-			if (VARATT_IS_EXTERNAL(DatumGetPointer(toast_values[i])))
-				continue;		/* can't happen, toast_action would be 'p' */
-			if (TupleDescAttr(tupleDesc, i)->attstorage != 'm')
-				continue;
-			if (toast_sizes[i] > biggest_size)
-			{
-				biggest_attno = i;
-				biggest_size = toast_sizes[i];
-			}
-		}
-
-		if (biggest_attno < 0)
-			break;
-
-		/*
-		 * Store this external
-		 */
-		i = biggest_attno;
-		old_value = toast_values[i];
-		toast_action[i] = 'p';
-		toast_values[i] = toast_save_datum(rel, toast_values[i],
-										   toast_oldexternal[i], options);
-		if (toast_free[i])
-			pfree(DatumGetPointer(old_value));
-		toast_free[i] = true;
-
-		need_change = true;
-		need_free = true;
-	}
-
-	/*
-	 * In the case we toasted any values, we need to build a new heap tuple
-	 * with the changed values.
-	 */
-	if (need_change)
-	{
-		HeapTupleHeader olddata = newtup->t_data;
-		HeapTupleHeader new_data;
-		int32		new_header_len;
-		int32		new_data_len;
-		int32		new_tuple_len;
-
-		/*
-		 * Calculate the new size of the tuple.
-		 *
-		 * Note: we used to assume here that the old tuple's t_hoff must equal
-		 * the new_header_len value, but that was incorrect.  The old tuple
-		 * might have a smaller-than-current natts, if there's been an ALTER
-		 * TABLE ADD COLUMN since it was stored; and that would lead to a
-		 * different conclusion about the size of the null bitmap, or even
-		 * whether there needs to be one at all.
-		 */
-		new_header_len = SizeofHeapTupleHeader;
-		if (has_nulls)
-			new_header_len += BITMAPLEN(numAttrs);
-		new_header_len = MAXALIGN(new_header_len);
-		new_data_len = heap_compute_data_size(tupleDesc,
-											  toast_values, toast_isnull);
-		new_tuple_len = new_header_len + new_data_len;
-
-		/*
-		 * Allocate and zero the space needed, and fill HeapTupleData fields.
-		 */
-		result_tuple = (HeapTuple) palloc0(HEAPTUPLESIZE + new_tuple_len);
-		result_tuple->t_len = new_tuple_len;
-		result_tuple->t_self = newtup->t_self;
-		result_tuple->t_tableOid = newtup->t_tableOid;
-		new_data = (HeapTupleHeader) ((char *) result_tuple + HEAPTUPLESIZE);
-		result_tuple->t_data = new_data;
-
-		/*
-		 * Copy the existing tuple header, but adjust natts and t_hoff.
-		 */
-		memcpy(new_data, olddata, SizeofHeapTupleHeader);
-		HeapTupleHeaderSetNatts(new_data, numAttrs);
-		new_data->t_hoff = new_header_len;
-
-		/* Copy over the data, and fill the null bitmap if needed */
-		heap_fill_tuple(tupleDesc,
-						toast_values,
-						toast_isnull,
-						(char *) new_data + new_header_len,
-						new_data_len,
-						&(new_data->t_infomask),
-						has_nulls ? new_data->t_bits : NULL);
-	}
-	else
-		result_tuple = newtup;
-
-	/*
-	 * Free allocated temp values
-	 */
-	if (need_free)
-		for (i = 0; i < numAttrs; i++)
-			if (toast_free[i])
-				pfree(DatumGetPointer(toast_values[i]));
-
-	/*
-	 * Delete external values from the old tuple
-	 */
-	if (need_delold)
-		for (i = 0; i < numAttrs; i++)
-			if (toast_delold[i])
-				toast_delete_datum(rel, toast_oldvalues[i], false);
-
-	return result_tuple;
-}
-
-
-/* ----------
- * toast_flatten_tuple -
- *
- *	"Flatten" a tuple to contain no out-of-line toasted fields.
- *	(This does not eliminate compressed or short-header datums.)
- *
- *	Note: we expect the caller already checked HeapTupleHasExternal(tup),
- *	so there is no need for a short-circuit path.
- * ----------
- */
-HeapTuple
-toast_flatten_tuple(HeapTuple tup, TupleDesc tupleDesc)
-{
-	HeapTuple	new_tuple;
-	int			numAttrs = tupleDesc->natts;
-	int			i;
-	Datum		toast_values[MaxTupleAttributeNumber];
-	bool		toast_isnull[MaxTupleAttributeNumber];
-	bool		toast_free[MaxTupleAttributeNumber];
-
-	/*
-	 * Break down the tuple into fields.
-	 */
-	Assert(numAttrs <= MaxTupleAttributeNumber);
-	heap_deform_tuple(tup, tupleDesc, toast_values, toast_isnull);
-
-	memset(toast_free, 0, numAttrs * sizeof(bool));
-
-	for (i = 0; i < numAttrs; i++)
-	{
-		/*
-		 * Look at non-null varlena attributes
-		 */
-		if (!toast_isnull[i] && TupleDescAttr(tupleDesc, i)->attlen == -1)
-		{
-			struct varlena *new_value;
-
-			new_value = (struct varlena *) DatumGetPointer(toast_values[i]);
-			if (VARATT_IS_EXTERNAL(new_value))
-			{
-				new_value = heap_tuple_fetch_attr(new_value);
-				toast_values[i] = PointerGetDatum(new_value);
-				toast_free[i] = true;
-			}
-		}
-	}
-
-	/*
-	 * Form the reconfigured tuple.
-	 */
-	new_tuple = heap_form_tuple(tupleDesc, toast_values, toast_isnull);
-
-	/*
-	 * Be sure to copy the tuple's identity fields.  We also make a point of
-	 * copying visibility info, just in case anybody looks at those fields in
-	 * a syscache entry.
-	 */
-	new_tuple->t_self = tup->t_self;
-	new_tuple->t_tableOid = tup->t_tableOid;
-
-	new_tuple->t_data->t_choice = tup->t_data->t_choice;
-	new_tuple->t_data->t_ctid = tup->t_data->t_ctid;
-	new_tuple->t_data->t_infomask &= ~HEAP_XACT_MASK;
-	new_tuple->t_data->t_infomask |=
-		tup->t_data->t_infomask & HEAP_XACT_MASK;
-	new_tuple->t_data->t_infomask2 &= ~HEAP2_XACT_MASK;
-	new_tuple->t_data->t_infomask2 |=
-		tup->t_data->t_infomask2 & HEAP2_XACT_MASK;
-
-	/*
-	 * Free allocated temp values
-	 */
-	for (i = 0; i < numAttrs; i++)
-		if (toast_free[i])
-			pfree(DatumGetPointer(toast_values[i]));
-
-	return new_tuple;
-}
-
-
-/* ----------
- * toast_flatten_tuple_to_datum -
- *
- *	"Flatten" a tuple containing out-of-line toasted fields into a Datum.
- *	The result is always palloc'd in the current memory context.
- *
- *	We have a general rule that Datums of container types (rows, arrays,
- *	ranges, etc) must not contain any external TOAST pointers.  Without
- *	this rule, we'd have to look inside each Datum when preparing a tuple
- *	for storage, which would be expensive and would fail to extend cleanly
- *	to new sorts of container types.
- *
- *	However, we don't want to say that tuples represented as HeapTuples
- *	can't contain toasted fields, so instead this routine should be called
- *	when such a HeapTuple is being converted into a Datum.
- *
- *	While we're at it, we decompress any compressed fields too.  This is not
- *	necessary for correctness, but reflects an expectation that compression
- *	will be more effective if applied to the whole tuple not individual
- *	fields.  We are not so concerned about that that we want to deconstruct
- *	and reconstruct tuples just to get rid of compressed fields, however.
- *	So callers typically won't call this unless they see that the tuple has
- *	at least one external field.
- *
- *	On the other hand, in-line short-header varlena fields are left alone.
- *	If we "untoasted" them here, they'd just get changed back to short-header
- *	format anyway within heap_fill_tuple.
- * ----------
- */
-Datum
-toast_flatten_tuple_to_datum(HeapTupleHeader tup,
-							 uint32 tup_len,
-							 TupleDesc tupleDesc)
-{
-	HeapTupleHeader new_data;
-	int32		new_header_len;
-	int32		new_data_len;
-	int32		new_tuple_len;
-	HeapTupleData tmptup;
-	int			numAttrs = tupleDesc->natts;
-	int			i;
-	bool		has_nulls = false;
-	Datum		toast_values[MaxTupleAttributeNumber];
-	bool		toast_isnull[MaxTupleAttributeNumber];
-	bool		toast_free[MaxTupleAttributeNumber];
-
-	/* Build a temporary HeapTuple control structure */
-	tmptup.t_len = tup_len;
-	ItemPointerSetInvalid(&(tmptup.t_self));
-	tmptup.t_tableOid = InvalidOid;
-	tmptup.t_data = tup;
-
-	/*
-	 * Break down the tuple into fields.
-	 */
-	Assert(numAttrs <= MaxTupleAttributeNumber);
-	heap_deform_tuple(&tmptup, tupleDesc, toast_values, toast_isnull);
-
-	memset(toast_free, 0, numAttrs * sizeof(bool));
-
-	for (i = 0; i < numAttrs; i++)
-	{
-		/*
-		 * Look at non-null varlena attributes
-		 */
-		if (toast_isnull[i])
-			has_nulls = true;
-		else if (TupleDescAttr(tupleDesc, i)->attlen == -1)
-		{
-			struct varlena *new_value;
-
-			new_value = (struct varlena *) DatumGetPointer(toast_values[i]);
-			if (VARATT_IS_EXTERNAL(new_value) ||
-				VARATT_IS_COMPRESSED(new_value))
-			{
-				new_value = heap_tuple_untoast_attr(new_value);
-				toast_values[i] = PointerGetDatum(new_value);
-				toast_free[i] = true;
-			}
-		}
-	}
-
-	/*
-	 * Calculate the new size of the tuple.
-	 *
-	 * This should match the reconstruction code in toast_insert_or_update.
-	 */
-	new_header_len = SizeofHeapTupleHeader;
-	if (has_nulls)
-		new_header_len += BITMAPLEN(numAttrs);
-	new_header_len = MAXALIGN(new_header_len);
-	new_data_len = heap_compute_data_size(tupleDesc,
-										  toast_values, toast_isnull);
-	new_tuple_len = new_header_len + new_data_len;
-
-	new_data = (HeapTupleHeader) palloc0(new_tuple_len);
-
-	/*
-	 * Copy the existing tuple header, but adjust natts and t_hoff.
-	 */
-	memcpy(new_data, tup, SizeofHeapTupleHeader);
-	HeapTupleHeaderSetNatts(new_data, numAttrs);
-	new_data->t_hoff = new_header_len;
-
-	/* Set the composite-Datum header fields correctly */
-	HeapTupleHeaderSetDatumLength(new_data, new_tuple_len);
-	HeapTupleHeaderSetTypeId(new_data, tupleDesc->tdtypeid);
-	HeapTupleHeaderSetTypMod(new_data, tupleDesc->tdtypmod);
-
-	/* Copy over the data, and fill the null bitmap if needed */
-	heap_fill_tuple(tupleDesc,
-					toast_values,
-					toast_isnull,
-					(char *) new_data + new_header_len,
-					new_data_len,
-					&(new_data->t_infomask),
-					has_nulls ? new_data->t_bits : NULL);
-
-	/*
-	 * Free allocated temp values
-	 */
-	for (i = 0; i < numAttrs; i++)
-		if (toast_free[i])
-			pfree(DatumGetPointer(toast_values[i]));
-
-	return PointerGetDatum(new_data);
-}
-
-
-/* ----------
- * toast_build_flattened_tuple -
- *
- *	Build a tuple containing no out-of-line toasted fields.
- *	(This does not eliminate compressed or short-header datums.)
- *
- *	This is essentially just like heap_form_tuple, except that it will
- *	expand any external-data pointers beforehand.
- *
- *	It's not very clear whether it would be preferable to decompress
- *	in-line compressed datums while at it.  For now, we don't.
- * ----------
- */
-HeapTuple
-toast_build_flattened_tuple(TupleDesc tupleDesc,
-							Datum *values,
-							bool *isnull)
-{
-	HeapTuple	new_tuple;
-	int			numAttrs = tupleDesc->natts;
-	int			num_to_free;
-	int			i;
-	Datum		new_values[MaxTupleAttributeNumber];
-	Pointer		freeable_values[MaxTupleAttributeNumber];
-
-	/*
-	 * We can pass the caller's isnull array directly to heap_form_tuple, but
-	 * we potentially need to modify the values array.
-	 */
-	Assert(numAttrs <= MaxTupleAttributeNumber);
-	memcpy(new_values, values, numAttrs * sizeof(Datum));
-
-	num_to_free = 0;
-	for (i = 0; i < numAttrs; i++)
-	{
-		/*
-		 * Look at non-null varlena attributes
-		 */
-		if (!isnull[i] && TupleDescAttr(tupleDesc, i)->attlen == -1)
-		{
-			struct varlena *new_value;
-
-			new_value = (struct varlena *) DatumGetPointer(new_values[i]);
-			if (VARATT_IS_EXTERNAL(new_value))
-			{
-				new_value = heap_tuple_fetch_attr(new_value);
-				new_values[i] = PointerGetDatum(new_value);
-				freeable_values[num_to_free++] = (Pointer) new_value;
-			}
-		}
-	}
-
-	/*
-	 * Form the reconfigured tuple.
-	 */
-	new_tuple = heap_form_tuple(tupleDesc, new_values, isnull);
-
-	/*
-	 * Free allocated temp values
-	 */
-	for (i = 0; i < num_to_free; i++)
-		pfree(freeable_values[i]);
-
-	return new_tuple;
-}
-
-
-/* ----------
- * toast_compress_datum -
- *
- *	Create a compressed version of a varlena datum
- *
- *	If we fail (ie, compressed result is actually bigger than original)
- *	then return NULL.  We must not use compressed data if it'd expand
- *	the tuple!
- *
- *	We use VAR{SIZE,DATA}_ANY so we can handle short varlenas here without
- *	copying them.  But we can't handle external or compressed datums.
- * ----------
- */
-Datum
-toast_compress_datum(Datum value)
-{
-	struct varlena *tmp;
-	int32		valsize = VARSIZE_ANY_EXHDR(DatumGetPointer(value));
-	int32		len;
-
-	Assert(!VARATT_IS_EXTERNAL(DatumGetPointer(value)));
-	Assert(!VARATT_IS_COMPRESSED(DatumGetPointer(value)));
-
-	/*
-	 * No point in wasting a palloc cycle if value size is out of the allowed
-	 * range for compression
-	 */
-	if (valsize < PGLZ_strategy_default->min_input_size ||
-		valsize > PGLZ_strategy_default->max_input_size)
-		return PointerGetDatum(NULL);
-
-	tmp = (struct varlena *) palloc(PGLZ_MAX_OUTPUT(valsize) +
-									TOAST_COMPRESS_HDRSZ);
-
-	/*
-	 * We recheck the actual size even if pglz_compress() reports success,
-	 * because it might be satisfied with having saved as little as one byte
-	 * in the compressed data --- which could turn into a net loss once you
-	 * consider header and alignment padding.  Worst case, the compressed
-	 * format might require three padding bytes (plus header, which is
-	 * included in VARSIZE(tmp)), whereas the uncompressed format would take
-	 * only one header byte and no padding if the value is short enough.  So
-	 * we insist on a savings of more than 2 bytes to ensure we have a gain.
-	 */
-	len = pglz_compress(VARDATA_ANY(DatumGetPointer(value)),
-						valsize,
-						TOAST_COMPRESS_RAWDATA(tmp),
-						PGLZ_strategy_default);
-	if (len >= 0 &&
-		len + TOAST_COMPRESS_HDRSZ < valsize - 2)
-	{
-		TOAST_COMPRESS_SET_RAWSIZE(tmp, valsize);
-		SET_VARSIZE_COMPRESSED(tmp, len + TOAST_COMPRESS_HDRSZ);
-		/* successful compression */
-		return PointerGetDatum(tmp);
-	}
-	else
-	{
-		/* incompressible data */
-		pfree(tmp);
-		return PointerGetDatum(NULL);
-	}
-}
-
-
-/* ----------
- * toast_get_valid_index
- *
- *	Get OID of valid index associated to given toast relation. A toast
- *	relation can have only one valid index at the same time.
- */
-Oid
-toast_get_valid_index(Oid toastoid, LOCKMODE lock)
-{
-	int			num_indexes;
-	int			validIndex;
-	Oid			validIndexOid;
-	Relation   *toastidxs;
-	Relation	toastrel;
-
-	/* Open the toast relation */
-	toastrel = table_open(toastoid, lock);
-
-	/* Look for the valid index of the toast relation */
-	validIndex = toast_open_indexes(toastrel,
-									lock,
-									&toastidxs,
-									&num_indexes);
-	validIndexOid = RelationGetRelid(toastidxs[validIndex]);
-
-	/* Close the toast relation and all its indexes */
-	toast_close_indexes(toastidxs, num_indexes, lock);
-	table_close(toastrel, lock);
-
-	return validIndexOid;
-}
-
-
-/* ----------
- * toast_save_datum -
- *
- *	Save one single datum into the secondary relation and return
- *	a Datum reference for it.
- *
- * rel: the main relation we're working with (not the toast rel!)
- * value: datum to be pushed to toast storage
- * oldexternal: if not NULL, toast pointer previously representing the datum
- * options: options to be passed to heap_insert() for toast rows
- * ----------
- */
-static Datum
-toast_save_datum(Relation rel, Datum value,
-				 struct varlena *oldexternal, int options)
-{
-	Relation	toastrel;
-	Relation   *toastidxs;
-	HeapTuple	toasttup;
-	TupleDesc	toasttupDesc;
-	Datum		t_values[3];
-	bool		t_isnull[3];
-	CommandId	mycid = GetCurrentCommandId(true);
-	struct varlena *result;
-	struct varatt_external toast_pointer;
-	union
-	{
-		struct varlena hdr;
-		/* this is to make the union big enough for a chunk: */
-		char		data[TOAST_MAX_CHUNK_SIZE + VARHDRSZ];
-		/* ensure union is aligned well enough: */
-		int32		align_it;
-	}			chunk_data;
-	int32		chunk_size;
-	int32		chunk_seq = 0;
-	char	   *data_p;
-	int32		data_todo;
-	Pointer		dval = DatumGetPointer(value);
-	int			num_indexes;
-	int			validIndex;
-
-	Assert(!VARATT_IS_EXTERNAL(value));
-
-	/*
-	 * Open the toast relation and its indexes.  We can use the index to check
-	 * uniqueness of the OID we assign to the toasted item, even though it has
-	 * additional columns besides OID.
-	 */
-	toastrel = table_open(rel->rd_rel->reltoastrelid, RowExclusiveLock);
-	toasttupDesc = toastrel->rd_att;
-
-	/* Open all the toast indexes and look for the valid one */
-	validIndex = toast_open_indexes(toastrel,
-									RowExclusiveLock,
-									&toastidxs,
-									&num_indexes);
-
-	/*
-	 * Get the data pointer and length, and compute va_rawsize and va_extsize.
-	 *
-	 * va_rawsize is the size of the equivalent fully uncompressed datum, so
-	 * we have to adjust for short headers.
-	 *
-	 * va_extsize is the actual size of the data payload in the toast records.
-	 */
-	if (VARATT_IS_SHORT(dval))
-	{
-		data_p = VARDATA_SHORT(dval);
-		data_todo = VARSIZE_SHORT(dval) - VARHDRSZ_SHORT;
-		toast_pointer.va_rawsize = data_todo + VARHDRSZ;	/* as if not short */
-		toast_pointer.va_extsize = data_todo;
-	}
-	else if (VARATT_IS_COMPRESSED(dval))
-	{
-		data_p = VARDATA(dval);
-		data_todo = VARSIZE(dval) - VARHDRSZ;
-		/* rawsize in a compressed datum is just the size of the payload */
-		toast_pointer.va_rawsize = VARRAWSIZE_4B_C(dval) + VARHDRSZ;
-		toast_pointer.va_extsize = data_todo;
-		/* Assert that the numbers look like it's compressed */
-		Assert(VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer));
-	}
-	else
-	{
-		data_p = VARDATA(dval);
-		data_todo = VARSIZE(dval) - VARHDRSZ;
-		toast_pointer.va_rawsize = VARSIZE(dval);
-		toast_pointer.va_extsize = data_todo;
-	}
-
-	/*
-	 * Insert the correct table OID into the result TOAST pointer.
-	 *
-	 * Normally this is the actual OID of the target toast table, but during
-	 * table-rewriting operations such as CLUSTER, we have to insert the OID
-	 * of the table's real permanent toast table instead.  rd_toastoid is set
-	 * if we have to substitute such an OID.
-	 */
-	if (OidIsValid(rel->rd_toastoid))
-		toast_pointer.va_toastrelid = rel->rd_toastoid;
-	else
-		toast_pointer.va_toastrelid = RelationGetRelid(toastrel);
-
-	/*
-	 * Choose an OID to use as the value ID for this toast value.
-	 *
-	 * Normally we just choose an unused OID within the toast table.  But
-	 * during table-rewriting operations where we are preserving an existing
-	 * toast table OID, we want to preserve toast value OIDs too.  So, if
-	 * rd_toastoid is set and we had a prior external value from that same
-	 * toast table, re-use its value ID.  If we didn't have a prior external
-	 * value (which is a corner case, but possible if the table's attstorage
-	 * options have been changed), we have to pick a value ID that doesn't
-	 * conflict with either new or existing toast value OIDs.
-	 */
-	if (!OidIsValid(rel->rd_toastoid))
-	{
-		/* normal case: just choose an unused OID */
-		toast_pointer.va_valueid =
-			GetNewOidWithIndex(toastrel,
-							   RelationGetRelid(toastidxs[validIndex]),
-							   (AttrNumber) 1);
-	}
-	else
-	{
-		/* rewrite case: check to see if value was in old toast table */
-		toast_pointer.va_valueid = InvalidOid;
-		if (oldexternal != NULL)
-		{
-			struct varatt_external old_toast_pointer;
-
-			Assert(VARATT_IS_EXTERNAL_ONDISK(oldexternal));
-			/* Must copy to access aligned fields */
-			VARATT_EXTERNAL_GET_POINTER(old_toast_pointer, oldexternal);
-			if (old_toast_pointer.va_toastrelid == rel->rd_toastoid)
-			{
-				/* This value came from the old toast table; reuse its OID */
-				toast_pointer.va_valueid = old_toast_pointer.va_valueid;
-
-				/*
-				 * There is a corner case here: the table rewrite might have
-				 * to copy both live and recently-dead versions of a row, and
-				 * those versions could easily reference the same toast value.
-				 * When we copy the second or later version of such a row,
-				 * reusing the OID will mean we select an OID that's already
-				 * in the new toast table.  Check for that, and if so, just
-				 * fall through without writing the data again.
-				 *
-				 * While annoying and ugly-looking, this is a good thing
-				 * because it ensures that we wind up with only one copy of
-				 * the toast value when there is only one copy in the old
-				 * toast table.  Before we detected this case, we'd have made
-				 * multiple copies, wasting space; and what's worse, the
-				 * copies belonging to already-deleted heap tuples would not
-				 * be reclaimed by VACUUM.
-				 */
-				if (toastrel_valueid_exists(toastrel,
-											toast_pointer.va_valueid))
-				{
-					/* Match, so short-circuit the data storage loop below */
-					data_todo = 0;
-				}
-			}
-		}
-		if (toast_pointer.va_valueid == InvalidOid)
-		{
-			/*
-			 * new value; must choose an OID that doesn't conflict in either
-			 * old or new toast table
-			 */
-			do
-			{
-				toast_pointer.va_valueid =
-					GetNewOidWithIndex(toastrel,
-									   RelationGetRelid(toastidxs[validIndex]),
-									   (AttrNumber) 1);
-			} while (toastid_valueid_exists(rel->rd_toastoid,
-											toast_pointer.va_valueid));
-		}
-	}
-
-	/*
-	 * Initialize constant parts of the tuple data
-	 */
-	t_values[0] = ObjectIdGetDatum(toast_pointer.va_valueid);
-	t_values[2] = PointerGetDatum(&chunk_data);
-	t_isnull[0] = false;
-	t_isnull[1] = false;
-	t_isnull[2] = false;
-
-	/*
-	 * Split up the item into chunks
-	 */
-	while (data_todo > 0)
-	{
-		int			i;
-
-		CHECK_FOR_INTERRUPTS();
-
-		/*
-		 * Calculate the size of this chunk
-		 */
-		chunk_size = Min(TOAST_MAX_CHUNK_SIZE, data_todo);
-
-		/*
-		 * Build a tuple and store it
-		 */
-		t_values[1] = Int32GetDatum(chunk_seq++);
-		SET_VARSIZE(&chunk_data, chunk_size + VARHDRSZ);
-		memcpy(VARDATA(&chunk_data), data_p, chunk_size);
-		toasttup = heap_form_tuple(toasttupDesc, t_values, t_isnull);
-
-		heap_insert(toastrel, toasttup, mycid, options, NULL);
-
-		/*
-		 * Create the index entry.  We cheat a little here by not using
-		 * FormIndexDatum: this relies on the knowledge that the index columns
-		 * are the same as the initial columns of the table for all the
-		 * indexes.  We also cheat by not providing an IndexInfo: this is okay
-		 * for now because btree doesn't need one, but we might have to be
-		 * more honest someday.
-		 *
-		 * Note also that there had better not be any user-created index on
-		 * the TOAST table, since we don't bother to update anything else.
-		 */
-		for (i = 0; i < num_indexes; i++)
-		{
-			/* Only index relations marked as ready can be updated */
-			if (toastidxs[i]->rd_index->indisready)
-				index_insert(toastidxs[i], t_values, t_isnull,
-							 &(toasttup->t_self),
-							 toastrel,
-							 toastidxs[i]->rd_index->indisunique ?
-							 UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
-							 NULL);
-		}
-
-		/*
-		 * Free memory
-		 */
-		heap_freetuple(toasttup);
-
-		/*
-		 * Move on to next chunk
-		 */
-		data_todo -= chunk_size;
-		data_p += chunk_size;
-	}
-
-	/*
-	 * Done - close toast relation and its indexes
-	 */
-	toast_close_indexes(toastidxs, num_indexes, RowExclusiveLock);
-	table_close(toastrel, RowExclusiveLock);
-
-	/*
-	 * Create the TOAST pointer value that we'll return
-	 */
-	result = (struct varlena *) palloc(TOAST_POINTER_SIZE);
-	SET_VARTAG_EXTERNAL(result, VARTAG_ONDISK);
-	memcpy(VARDATA_EXTERNAL(result), &toast_pointer, sizeof(toast_pointer));
-
-	return PointerGetDatum(result);
-}
-
-
-/* ----------
- * toast_delete_datum -
- *
- *	Delete a single external stored value.
- * ----------
- */
-static void
-toast_delete_datum(Relation rel, Datum value, bool is_speculative)
-{
-	struct varlena *attr = (struct varlena *) DatumGetPointer(value);
-	struct varatt_external toast_pointer;
-	Relation	toastrel;
-	Relation   *toastidxs;
-	ScanKeyData toastkey;
-	SysScanDesc toastscan;
-	HeapTuple	toasttup;
-	int			num_indexes;
-	int			validIndex;
-	SnapshotData SnapshotToast;
-
-	if (!VARATT_IS_EXTERNAL_ONDISK(attr))
-		return;
-
-	/* Must copy to access aligned fields */
-	VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
-
-	/*
-	 * Open the toast relation and its indexes
-	 */
-	toastrel = table_open(toast_pointer.va_toastrelid, RowExclusiveLock);
-
-	/* Fetch valid relation used for process */
-	validIndex = toast_open_indexes(toastrel,
-									RowExclusiveLock,
-									&toastidxs,
-									&num_indexes);
-
-	/*
-	 * Setup a scan key to find chunks with matching va_valueid
-	 */
-	ScanKeyInit(&toastkey,
-				(AttrNumber) 1,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(toast_pointer.va_valueid));
-
-	/*
-	 * Find all the chunks.  (We don't actually care whether we see them in
-	 * sequence or not, but since we've already locked the index we might as
-	 * well use systable_beginscan_ordered.)
-	 */
-	init_toast_snapshot(&SnapshotToast);
-	toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
-										   &SnapshotToast, 1, &toastkey);
-	while ((toasttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
-	{
-		/*
-		 * Have a chunk, delete it
-		 */
-		if (is_speculative)
-			heap_abort_speculative(toastrel, &toasttup->t_self);
-		else
-			simple_heap_delete(toastrel, &toasttup->t_self);
-	}
-
-	/*
-	 * End scan and close relations
-	 */
-	systable_endscan_ordered(toastscan);
-	toast_close_indexes(toastidxs, num_indexes, RowExclusiveLock);
-	table_close(toastrel, RowExclusiveLock);
-}
-
-
-/* ----------
- * toastrel_valueid_exists -
- *
- *	Test whether a toast value with the given ID exists in the toast relation.
- *	For safety, we consider a value to exist if there are either live or dead
- *	toast rows with that ID; see notes for GetNewOidWithIndex().
- * ----------
- */
-static bool
-toastrel_valueid_exists(Relation toastrel, Oid valueid)
-{
-	bool		result = false;
-	ScanKeyData toastkey;
-	SysScanDesc toastscan;
-	int			num_indexes;
-	int			validIndex;
-	Relation   *toastidxs;
-
-	/* Fetch a valid index relation */
-	validIndex = toast_open_indexes(toastrel,
-									RowExclusiveLock,
-									&toastidxs,
-									&num_indexes);
-
-	/*
-	 * Setup a scan key to find chunks with matching va_valueid
-	 */
-	ScanKeyInit(&toastkey,
-				(AttrNumber) 1,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(valueid));
-
-	/*
-	 * Is there any such chunk?
-	 */
-	toastscan = systable_beginscan(toastrel,
-								   RelationGetRelid(toastidxs[validIndex]),
-								   true, SnapshotAny, 1, &toastkey);
-
-	if (systable_getnext(toastscan) != NULL)
-		result = true;
-
-	systable_endscan(toastscan);
-
-	/* Clean up */
-	toast_close_indexes(toastidxs, num_indexes, RowExclusiveLock);
-
-	return result;
-}
-
-/* ----------
- * toastid_valueid_exists -
- *
- *	As above, but work from toast rel's OID not an open relation
- * ----------
- */
-static bool
-toastid_valueid_exists(Oid toastrelid, Oid valueid)
-{
-	bool		result;
-	Relation	toastrel;
-
-	toastrel = table_open(toastrelid, AccessShareLock);
-
-	result = toastrel_valueid_exists(toastrel, valueid);
-
-	table_close(toastrel, AccessShareLock);
-
-	return result;
-}
-
-
-/* ----------
- * toast_fetch_datum -
- *
- *	Reconstruct an in memory Datum from the chunks saved
- *	in the toast relation
- * ----------
- */
-static struct varlena *
-toast_fetch_datum(struct varlena *attr)
-{
-	Relation	toastrel;
-	Relation   *toastidxs;
-	ScanKeyData toastkey;
-	SysScanDesc toastscan;
-	HeapTuple	ttup;
-	TupleDesc	toasttupDesc;
-	struct varlena *result;
-	struct varatt_external toast_pointer;
-	int32		ressize;
-	int32		residx,
-				nextidx;
-	int32		numchunks;
-	Pointer		chunk;
-	bool		isnull;
-	char	   *chunkdata;
-	int32		chunksize;
-	int			num_indexes;
-	int			validIndex;
-	SnapshotData SnapshotToast;
-
-	if (!VARATT_IS_EXTERNAL_ONDISK(attr))
-		elog(ERROR, "toast_fetch_datum shouldn't be called for non-ondisk datums");
-
-	/* Must copy to access aligned fields */
-	VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
-
-	ressize = toast_pointer.va_extsize;
-	numchunks = ((ressize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
-
-	result = (struct varlena *) palloc(ressize + VARHDRSZ);
-
-	if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))
-		SET_VARSIZE_COMPRESSED(result, ressize + VARHDRSZ);
-	else
-		SET_VARSIZE(result, ressize + VARHDRSZ);
-
-	/*
-	 * Open the toast relation and its indexes
-	 */
-	toastrel = table_open(toast_pointer.va_toastrelid, AccessShareLock);
-	toasttupDesc = toastrel->rd_att;
-
-	/* Look for the valid index of the toast relation */
-	validIndex = toast_open_indexes(toastrel,
-									AccessShareLock,
-									&toastidxs,
-									&num_indexes);
-
-	/*
-	 * Setup a scan key to fetch from the index by va_valueid
-	 */
-	ScanKeyInit(&toastkey,
-				(AttrNumber) 1,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(toast_pointer.va_valueid));
-
-	/*
-	 * Read the chunks by index
-	 *
-	 * Note that because the index is actually on (valueid, chunkidx) we will
-	 * see the chunks in chunkidx order, even though we didn't explicitly ask
-	 * for it.
-	 */
-	nextidx = 0;
-
-	init_toast_snapshot(&SnapshotToast);
-	toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
-										   &SnapshotToast, 1, &toastkey);
-	while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
-	{
-		/*
-		 * Have a chunk, extract the sequence number and the data
-		 */
-		residx = DatumGetInt32(fastgetattr(ttup, 2, toasttupDesc, &isnull));
-		Assert(!isnull);
-		chunk = DatumGetPointer(fastgetattr(ttup, 3, toasttupDesc, &isnull));
-		Assert(!isnull);
-		if (!VARATT_IS_EXTENDED(chunk))
-		{
-			chunksize = VARSIZE(chunk) - VARHDRSZ;
-			chunkdata = VARDATA(chunk);
-		}
-		else if (VARATT_IS_SHORT(chunk))
-		{
-			/* could happen due to heap_form_tuple doing its thing */
-			chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT;
-			chunkdata = VARDATA_SHORT(chunk);
-		}
-		else
-		{
-			/* should never happen */
-			elog(ERROR, "found toasted toast chunk for toast value %u in %s",
-				 toast_pointer.va_valueid,
-				 RelationGetRelationName(toastrel));
-			chunksize = 0;		/* keep compiler quiet */
-			chunkdata = NULL;
-		}
-
-		/*
-		 * Some checks on the data we've found
-		 */
-		if (residx != nextidx)
-			elog(ERROR, "unexpected chunk number %d (expected %d) for toast value %u in %s",
-				 residx, nextidx,
-				 toast_pointer.va_valueid,
-				 RelationGetRelationName(toastrel));
-		if (residx < numchunks - 1)
-		{
-			if (chunksize != TOAST_MAX_CHUNK_SIZE)
-				elog(ERROR, "unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s",
-					 chunksize, (int) TOAST_MAX_CHUNK_SIZE,
-					 residx, numchunks,
-					 toast_pointer.va_valueid,
-					 RelationGetRelationName(toastrel));
-		}
-		else if (residx == numchunks - 1)
-		{
-			if ((residx * TOAST_MAX_CHUNK_SIZE + chunksize) != ressize)
-				elog(ERROR, "unexpected chunk size %d (expected %d) in final chunk %d for toast value %u in %s",
-					 chunksize,
-					 (int) (ressize - residx * TOAST_MAX_CHUNK_SIZE),
-					 residx,
-					 toast_pointer.va_valueid,
-					 RelationGetRelationName(toastrel));
-		}
-		else
-			elog(ERROR, "unexpected chunk number %d (out of range %d..%d) for toast value %u in %s",
-				 residx,
-				 0, numchunks - 1,
-				 toast_pointer.va_valueid,
-				 RelationGetRelationName(toastrel));
-
-		/*
-		 * Copy the data into proper place in our result
-		 */
-		memcpy(VARDATA(result) + residx * TOAST_MAX_CHUNK_SIZE,
-			   chunkdata,
-			   chunksize);
-
-		nextidx++;
-	}
-
-	/*
-	 * Final checks that we successfully fetched the datum
-	 */
-	if (nextidx != numchunks)
-		elog(ERROR, "missing chunk number %d for toast value %u in %s",
-			 nextidx,
-			 toast_pointer.va_valueid,
-			 RelationGetRelationName(toastrel));
-
-	/*
-	 * End scan and close relations
-	 */
-	systable_endscan_ordered(toastscan);
-	toast_close_indexes(toastidxs, num_indexes, AccessShareLock);
-	table_close(toastrel, AccessShareLock);
-
-	return result;
-}
-
-/* ----------
- * toast_fetch_datum_slice -
- *
- *	Reconstruct a segment of a Datum from the chunks saved
- *	in the toast relation
- *
- *	Note that this function only supports non-compressed external datums.
- * ----------
- */
-static struct varlena *
-toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length)
-{
-	Relation	toastrel;
-	Relation   *toastidxs;
-	ScanKeyData toastkey[3];
-	int			nscankeys;
-	SysScanDesc toastscan;
-	HeapTuple	ttup;
-	TupleDesc	toasttupDesc;
-	struct varlena *result;
-	struct varatt_external toast_pointer;
-	int32		attrsize;
-	int32		residx;
-	int32		nextidx;
-	int			numchunks;
-	int			startchunk;
-	int			endchunk;
-	int32		startoffset;
-	int32		endoffset;
-	int			totalchunks;
-	Pointer		chunk;
-	bool		isnull;
-	char	   *chunkdata;
-	int32		chunksize;
-	int32		chcpystrt;
-	int32		chcpyend;
-	int			num_indexes;
-	int			validIndex;
-	SnapshotData SnapshotToast;
-
-	if (!VARATT_IS_EXTERNAL_ONDISK(attr))
-		elog(ERROR, "toast_fetch_datum_slice shouldn't be called for non-ondisk datums");
-
-	/* Must copy to access aligned fields */
-	VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
-
-	/*
-	 * It's nonsense to fetch slices of a compressed datum -- this isn't lo_*
-	 * we can't return a compressed datum which is meaningful to toast later
-	 */
-	Assert(!VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer));
-
-	attrsize = toast_pointer.va_extsize;
-	totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
-
-	if (sliceoffset >= attrsize)
-	{
-		sliceoffset = 0;
-		length = 0;
-	}
-
-	if (((sliceoffset + length) > attrsize) || length < 0)
-		length = attrsize - sliceoffset;
-
-	result = (struct varlena *) palloc(length + VARHDRSZ);
-
-	SET_VARSIZE(result, length + VARHDRSZ);
-
-	if (length == 0)
-		return result;			/* Can save a lot of work at this point! */
-
-	startchunk = sliceoffset / TOAST_MAX_CHUNK_SIZE;
-	endchunk = (sliceoffset + length - 1) / TOAST_MAX_CHUNK_SIZE;
-	numchunks = (endchunk - startchunk) + 1;
-
-	startoffset = sliceoffset % TOAST_MAX_CHUNK_SIZE;
-	endoffset = (sliceoffset + length - 1) % TOAST_MAX_CHUNK_SIZE;
-
-	/*
-	 * Open the toast relation and its indexes
-	 */
-	toastrel = table_open(toast_pointer.va_toastrelid, AccessShareLock);
-	toasttupDesc = toastrel->rd_att;
-
-	/* Look for the valid index of toast relation */
-	validIndex = toast_open_indexes(toastrel,
-									AccessShareLock,
-									&toastidxs,
-									&num_indexes);
-
-	/*
-	 * Setup a scan key to fetch from the index. This is either two keys or
-	 * three depending on the number of chunks.
-	 */
-	ScanKeyInit(&toastkey[0],
-				(AttrNumber) 1,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(toast_pointer.va_valueid));
-
-	/*
-	 * Use equality condition for one chunk, a range condition otherwise:
-	 */
-	if (numchunks == 1)
-	{
-		ScanKeyInit(&toastkey[1],
-					(AttrNumber) 2,
-					BTEqualStrategyNumber, F_INT4EQ,
-					Int32GetDatum(startchunk));
-		nscankeys = 2;
-	}
-	else
-	{
-		ScanKeyInit(&toastkey[1],
-					(AttrNumber) 2,
-					BTGreaterEqualStrategyNumber, F_INT4GE,
-					Int32GetDatum(startchunk));
-		ScanKeyInit(&toastkey[2],
-					(AttrNumber) 2,
-					BTLessEqualStrategyNumber, F_INT4LE,
-					Int32GetDatum(endchunk));
-		nscankeys = 3;
-	}
-
-	/*
-	 * Read the chunks by index
-	 *
-	 * The index is on (valueid, chunkidx) so they will come in order
-	 */
-	init_toast_snapshot(&SnapshotToast);
-	nextidx = startchunk;
-	toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
-										   &SnapshotToast, nscankeys, toastkey);
-	while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
-	{
-		/*
-		 * Have a chunk, extract the sequence number and the data
-		 */
-		residx = DatumGetInt32(fastgetattr(ttup, 2, toasttupDesc, &isnull));
-		Assert(!isnull);
-		chunk = DatumGetPointer(fastgetattr(ttup, 3, toasttupDesc, &isnull));
-		Assert(!isnull);
-		if (!VARATT_IS_EXTENDED(chunk))
-		{
-			chunksize = VARSIZE(chunk) - VARHDRSZ;
-			chunkdata = VARDATA(chunk);
-		}
-		else if (VARATT_IS_SHORT(chunk))
-		{
-			/* could happen due to heap_form_tuple doing its thing */
-			chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT;
-			chunkdata = VARDATA_SHORT(chunk);
-		}
-		else
-		{
-			/* should never happen */
-			elog(ERROR, "found toasted toast chunk for toast value %u in %s",
-				 toast_pointer.va_valueid,
-				 RelationGetRelationName(toastrel));
-			chunksize = 0;		/* keep compiler quiet */
-			chunkdata = NULL;
-		}
-
-		/*
-		 * Some checks on the data we've found
-		 */
-		if ((residx != nextidx) || (residx > endchunk) || (residx < startchunk))
-			elog(ERROR, "unexpected chunk number %d (expected %d) for toast value %u in %s",
-				 residx, nextidx,
-				 toast_pointer.va_valueid,
-				 RelationGetRelationName(toastrel));
-		if (residx < totalchunks - 1)
-		{
-			if (chunksize != TOAST_MAX_CHUNK_SIZE)
-				elog(ERROR, "unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s when fetching slice",
-					 chunksize, (int) TOAST_MAX_CHUNK_SIZE,
-					 residx, totalchunks,
-					 toast_pointer.va_valueid,
-					 RelationGetRelationName(toastrel));
-		}
-		else if (residx == totalchunks - 1)
-		{
-			if ((residx * TOAST_MAX_CHUNK_SIZE + chunksize) != attrsize)
-				elog(ERROR, "unexpected chunk size %d (expected %d) in final chunk %d for toast value %u in %s when fetching slice",
-					 chunksize,
-					 (int) (attrsize - residx * TOAST_MAX_CHUNK_SIZE),
-					 residx,
-					 toast_pointer.va_valueid,
-					 RelationGetRelationName(toastrel));
-		}
-		else
-			elog(ERROR, "unexpected chunk number %d (out of range %d..%d) for toast value %u in %s",
-				 residx,
-				 0, totalchunks - 1,
-				 toast_pointer.va_valueid,
-				 RelationGetRelationName(toastrel));
-
-		/*
-		 * Copy the data into proper place in our result
-		 */
-		chcpystrt = 0;
-		chcpyend = chunksize - 1;
-		if (residx == startchunk)
-			chcpystrt = startoffset;
-		if (residx == endchunk)
-			chcpyend = endoffset;
-
-		memcpy(VARDATA(result) +
-			   (residx * TOAST_MAX_CHUNK_SIZE - sliceoffset) + chcpystrt,
-			   chunkdata + chcpystrt,
-			   (chcpyend - chcpystrt) + 1);
-
-		nextidx++;
-	}
-
-	/*
-	 * Final checks that we successfully fetched the datum
-	 */
-	if (nextidx != (endchunk + 1))
-		elog(ERROR, "missing chunk number %d for toast value %u in %s",
-			 nextidx,
-			 toast_pointer.va_valueid,
-			 RelationGetRelationName(toastrel));
-
-	/*
-	 * End scan and close relations
-	 */
-	systable_endscan_ordered(toastscan);
-	toast_close_indexes(toastidxs, num_indexes, AccessShareLock);
-	table_close(toastrel, AccessShareLock);
-
-	return result;
-}
-
-/* ----------
- * toast_decompress_datum -
- *
- * Decompress a compressed version of a varlena datum
- */
-static struct varlena *
-toast_decompress_datum(struct varlena *attr)
-{
-	struct varlena *result;
-
-	Assert(VARATT_IS_COMPRESSED(attr));
-
-	result = (struct varlena *)
-		palloc(TOAST_COMPRESS_RAWSIZE(attr) + VARHDRSZ);
-	SET_VARSIZE(result, TOAST_COMPRESS_RAWSIZE(attr) + VARHDRSZ);
-
-	if (pglz_decompress(TOAST_COMPRESS_RAWDATA(attr),
-						VARSIZE(attr) - TOAST_COMPRESS_HDRSZ,
-						VARDATA(result),
-						TOAST_COMPRESS_RAWSIZE(attr), true) < 0)
-		elog(ERROR, "compressed data is corrupted");
-
-	return result;
-}
-
-
-/* ----------
- * toast_decompress_datum_slice -
- *
- * Decompress the front of a compressed version of a varlena datum.
- * offset handling happens in heap_tuple_untoast_attr_slice.
- * Here we just decompress a slice from the front.
- */
-static struct varlena *
-toast_decompress_datum_slice(struct varlena *attr, int32 slicelength)
-{
-	struct varlena *result;
-	int32		rawsize;
-
-	Assert(VARATT_IS_COMPRESSED(attr));
-
-	result = (struct varlena *) palloc(slicelength + VARHDRSZ);
-
-	rawsize = pglz_decompress(TOAST_COMPRESS_RAWDATA(attr),
-							  VARSIZE(attr) - TOAST_COMPRESS_HDRSZ,
-							  VARDATA(result),
-							  slicelength, false);
-	if (rawsize < 0)
-		elog(ERROR, "compressed data is corrupted");
-
-	SET_VARSIZE(result, rawsize + VARHDRSZ);
-	return result;
-}
-
-
-/* ----------
- * toast_open_indexes
- *
- *	Get an array of the indexes associated to the given toast relation
- *	and return as well the position of the valid index used by the toast
- *	relation in this array. It is the responsibility of the caller of this
- *	function to close the indexes as well as free them.
- */
-static int
-toast_open_indexes(Relation toastrel,
-				   LOCKMODE lock,
-				   Relation **toastidxs,
-				   int *num_indexes)
-{
-	int			i = 0;
-	int			res = 0;
-	bool		found = false;
-	List	   *indexlist;
-	ListCell   *lc;
-
-	/* Get index list of the toast relation */
-	indexlist = RelationGetIndexList(toastrel);
-	Assert(indexlist != NIL);
-
-	*num_indexes = list_length(indexlist);
-
-	/* Open all the index relations */
-	*toastidxs = (Relation *) palloc(*num_indexes * sizeof(Relation));
-	foreach(lc, indexlist)
-		(*toastidxs)[i++] = index_open(lfirst_oid(lc), lock);
-
-	/* Fetch the first valid index in list */
-	for (i = 0; i < *num_indexes; i++)
-	{
-		Relation	toastidx = (*toastidxs)[i];
-
-		if (toastidx->rd_index->indisvalid)
-		{
-			res = i;
-			found = true;
-			break;
-		}
-	}
-
-	/*
-	 * Free index list, not necessary anymore as relations are opened and a
-	 * valid index has been found.
-	 */
-	list_free(indexlist);
-
-	/*
-	 * The toast relation should have one valid index, so something is going
-	 * wrong if there is nothing.
-	 */
-	if (!found)
-		elog(ERROR, "no valid index found for toast relation with Oid %u",
-			 RelationGetRelid(toastrel));
-
-	return res;
-}
-
-/* ----------
- * toast_close_indexes
- *
- *	Close an array of indexes for a toast relation and free it. This should
- *	be called for a set of indexes opened previously with toast_open_indexes.
- */
-static void
-toast_close_indexes(Relation *toastidxs, int num_indexes, LOCKMODE lock)
-{
-	int			i;
-
-	/* Close relations and clean up things */
-	for (i = 0; i < num_indexes; i++)
-		index_close(toastidxs[i], lock);
-	pfree(toastidxs);
-}
-
-/* ----------
- * init_toast_snapshot
- *
- *	Initialize an appropriate TOAST snapshot.  We must use an MVCC snapshot
- *	to initialize the TOAST snapshot; since we don't know which one to use,
- *	just use the oldest one.  This is safe: at worst, we will get a "snapshot
- *	too old" error that might have been avoided otherwise.
- */
-static void
-init_toast_snapshot(Snapshot toast_snapshot)
-{
-	Snapshot	snapshot = GetOldestSnapshot();
-
-	if (snapshot == NULL)
-		elog(ERROR, "no known snapshots");
-
-	InitToastSnapshot(*toast_snapshot, snapshot->lsn, snapshot->whenTaken);
-}
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index e08320e829..07ade64c8b 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -24,12 +24,12 @@
 
 #include "access/clog.h"
 #include "access/commit_ts.h"
+#include "access/heaptoast.h"
 #include "access/multixact.h"
 #include "access/rewriteheap.h"
 #include "access/subtrans.h"
 #include "access/timeline.h"
 #include "access/transam.h"
-#include "access/tuptoaster.h"
 #include "access/twophase.h"
 #include "access/xact.h"
 #include "access/xlog_internal.h"
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 6cb545c126..e3aa626738 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -16,6 +16,7 @@
 
 #include <math.h>
 
+#include "access/detoast.h"
 #include "access/genam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
@@ -24,7 +25,6 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/tupconvert.h"
-#include "access/tuptoaster.h"
 #include "access/visibilitymap.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index ebaec4f8dd..7b383499e9 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -23,7 +23,7 @@
 #include "access/relscan.h"
 #include "access/tableam.h"
 #include "access/transam.h"
-#include "access/tuptoaster.h"
+#include "access/toast_internals.h"
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/pg_am.h"
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 66a67c72b2..cfd3aa1fb8 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -56,7 +56,7 @@
  */
 #include "postgres.h"
 
-#include "access/tuptoaster.h"
+#include "access/heaptoast.h"
 #include "catalog/pg_type.h"
 #include "commands/sequence.h"
 #include "executor/execExpr.h"
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index a5cb7bba0f..7f2ba08b7e 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -57,9 +57,9 @@
  */
 #include "postgres.h"
 
+#include "access/heaptoast.h"
 #include "access/htup_details.h"
 #include "access/tupdesc_details.h"
-#include "access/tuptoaster.h"
 #include "funcapi.h"
 #include "catalog/pg_type.h"
 #include "nodes/nodeFuncs.h"
diff --git a/src/backend/executor/tstoreReceiver.c b/src/backend/executor/tstoreReceiver.c
index cf79feb6bd..c0c81c82da 100644
--- a/src/backend/executor/tstoreReceiver.c
+++ b/src/backend/executor/tstoreReceiver.c
@@ -20,7 +20,7 @@
 
 #include "postgres.h"
 
-#include "access/tuptoaster.h"
+#include "access/detoast.h"
 #include "executor/tstoreReceiver.h"
 
 
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index e7c32f2a13..717c2f4267 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -56,10 +56,10 @@
 #include <unistd.h>
 #include <sys/stat.h>
 
+#include "access/detoast.h"
 #include "access/heapam.h"
 #include "access/rewriteheap.h"
 #include "access/transam.h"
-#include "access/tuptoaster.h"
 #include "access/xact.h"
 #include "access/xlog_internal.h"
 #include "catalog/catalog.h"
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index ab187915c1..e43f913346 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -16,10 +16,10 @@
  */
 #include "postgres.h"
 
+#include "access/detoast.h"
 #include "access/genam.h"
 #include "access/htup_details.h"
 #include "access/table.h"
-#include "access/tuptoaster.h"
 #include "catalog/indexing.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_statistic_ext.h"
diff --git a/src/backend/storage/large_object/inv_api.c b/src/backend/storage/large_object/inv_api.c
index a477cb9200..e591236343 100644
--- a/src/backend/storage/large_object/inv_api.c
+++ b/src/backend/storage/large_object/inv_api.c
@@ -32,10 +32,11 @@
 
 #include <limits.h>
 
+#include "access/detoast.h"
 #include "access/genam.h"
+#include "access/htup_details.h"
 #include "access/sysattr.h"
 #include "access/table.h"
-#include "access/tuptoaster.h"
 #include "access/xact.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
diff --git a/src/backend/utils/adt/array_typanalyze.c b/src/backend/utils/adt/array_typanalyze.c
index eafb94b697..54f5849629 100644
--- a/src/backend/utils/adt/array_typanalyze.c
+++ b/src/backend/utils/adt/array_typanalyze.c
@@ -14,7 +14,7 @@
  */
 #include "postgres.h"
 
-#include "access/tuptoaster.h"
+#include "access/detoast.h"
 #include "commands/vacuum.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
diff --git a/src/backend/utils/adt/datum.c b/src/backend/utils/adt/datum.c
index 81ea5a48e5..1568658bc9 100644
--- a/src/backend/utils/adt/datum.c
+++ b/src/backend/utils/adt/datum.c
@@ -42,7 +42,7 @@
 
 #include "postgres.h"
 
-#include "access/tuptoaster.h"
+#include "access/detoast.h"
 #include "fmgr.h"
 #include "utils/datum.h"
 #include "utils/expandeddatum.h"
diff --git a/src/backend/utils/adt/expandedrecord.c b/src/backend/utils/adt/expandedrecord.c
index 166c863026..369432d53c 100644
--- a/src/backend/utils/adt/expandedrecord.c
+++ b/src/backend/utils/adt/expandedrecord.c
@@ -18,8 +18,9 @@
  */
 #include "postgres.h"
 
+#include "access/detoast.h"
+#include "access/heaptoast.h"
 #include "access/htup_details.h"
-#include "access/tuptoaster.h"
 #include "catalog/heap.h"
 #include "catalog/pg_type.h"
 #include "utils/builtins.h"
diff --git a/src/backend/utils/adt/rowtypes.c b/src/backend/utils/adt/rowtypes.c
index aa7ec8735c..ea3e40a369 100644
--- a/src/backend/utils/adt/rowtypes.c
+++ b/src/backend/utils/adt/rowtypes.c
@@ -16,8 +16,8 @@
 
 #include <ctype.h>
 
+#include "access/detoast.h"
 #include "access/htup_details.h"
-#include "access/tuptoaster.h"
 #include "catalog/pg_type.h"
 #include "funcapi.h"
 #include "libpq/pqformat.h"
diff --git a/src/backend/utils/adt/tsgistidx.c b/src/backend/utils/adt/tsgistidx.c
index 4f256260fd..6ff71a49b8 100644
--- a/src/backend/utils/adt/tsgistidx.c
+++ b/src/backend/utils/adt/tsgistidx.c
@@ -15,7 +15,7 @@
 #include "postgres.h"
 
 #include "access/gist.h"
-#include "access/tuptoaster.h"
+#include "access/heaptoast.h"
 #include "port/pg_bitutils.h"
 #include "tsearch/ts_utils.h"
 #include "utils/builtins.h"
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 332dc860c4..9d94399323 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -14,7 +14,7 @@
  */
 #include "postgres.h"
 
-#include "access/tuptoaster.h"
+#include "access/detoast.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_type.h"
 #include "libpq/pqformat.h"
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 0864838867..3685223922 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -17,7 +17,7 @@
 #include <ctype.h>
 #include <limits.h>
 
-#include "access/tuptoaster.h"
+#include "access/detoast.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_type.h"
 #include "common/int.h"
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 00def27881..c3e7d94aa5 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -15,10 +15,10 @@
 #include "postgres.h"
 
 #include "access/genam.h"
+#include "access/heaptoast.h"
 #include "access/relscan.h"
 #include "access/sysattr.h"
 #include "access/table.h"
-#include "access/tuptoaster.h"
 #include "access/valid.h"
 #include "access/xact.h"
 #include "catalog/pg_collation.h"
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 9b640ccd40..74e36e62d3 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -15,7 +15,7 @@
 
 #include "postgres.h"
 
-#include "access/tuptoaster.h"
+#include "access/detoast.h"
 #include "catalog/pg_language.h"
 #include "catalog/pg_proc.h"
 #include "executor/functions.h"
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 2734f87318..43b41a9b17 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -45,7 +45,7 @@
 #include <unistd.h>
 
 #include "access/transam.h"
-#include "access/tuptoaster.h"
+#include "access/heaptoast.h"
 #include "access/multixact.h"
 #include "access/xlog.h"
 #include "access/xlog_internal.h"
diff --git a/src/include/access/detoast.h b/src/include/access/detoast.h
new file mode 100644
index 0000000000..582af147ea
--- /dev/null
+++ b/src/include/access/detoast.h
@@ -0,0 +1,92 @@
+/*-------------------------------------------------------------------------
+ *
+ * detoast.h
+ *	  Access to compressed and external varlena values.
+ *
+ * Copyright (c) 2000-2019, PostgreSQL Global Development Group
+ *
+ * src/include/access/detoast.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DETOAST_H 
+#define DETOAST_H
+
+/*
+ * Testing whether an externally-stored value is compressed now requires
+ * comparing extsize (the actual length of the external data) to rawsize
+ * (the original uncompressed datum's size).  The latter includes VARHDRSZ
+ * overhead, the former doesn't.  We never use compression unless it actually
+ * saves space, so we expect either equality or less-than.
+ */
+#define VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer) \
+	((toast_pointer).va_extsize < (toast_pointer).va_rawsize - VARHDRSZ)
+
+/*
+ * Macro to fetch the possibly-unaligned contents of an EXTERNAL datum
+ * into a local "struct varatt_external" toast pointer.  This should be
+ * just a memcpy, but some versions of gcc seem to produce broken code
+ * that assumes the datum contents are aligned.  Introducing an explicit
+ * intermediate "varattrib_1b_e *" variable seems to fix it.
+ */
+#define VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr) \
+do { \
+	varattrib_1b_e *attre = (varattrib_1b_e *) (attr); \
+	Assert(VARATT_IS_EXTERNAL(attre)); \
+	Assert(VARSIZE_EXTERNAL(attre) == sizeof(toast_pointer) + VARHDRSZ_EXTERNAL); \
+	memcpy(&(toast_pointer), VARDATA_EXTERNAL(attre), sizeof(toast_pointer)); \
+} while (0)
+
+/* Size of an EXTERNAL datum that contains a standard TOAST pointer */
+#define TOAST_POINTER_SIZE (VARHDRSZ_EXTERNAL + sizeof(varatt_external))
+
+/* Size of an EXTERNAL datum that contains an indirection pointer */
+#define INDIRECT_POINTER_SIZE (VARHDRSZ_EXTERNAL + sizeof(varatt_indirect))
+
+/* ----------
+ * heap_tuple_fetch_attr() -
+ *
+ *		Fetches an external stored attribute from the toast
+ *		relation. Does NOT decompress it, if stored external
+ *		in compressed format.
+ * ----------
+ */
+extern struct varlena *heap_tuple_fetch_attr(struct varlena *attr);
+
+/* ----------
+ * heap_tuple_untoast_attr() -
+ *
+ *		Fully detoasts one attribute, fetching and/or decompressing
+ *		it as needed.
+ * ----------
+ */
+extern struct varlena *heap_tuple_untoast_attr(struct varlena *attr);
+
+/* ----------
+ * heap_tuple_untoast_attr_slice() -
+ *
+ *		Fetches only the specified portion of an attribute.
+ *		(Handles all cases for attribute storage)
+ * ----------
+ */
+extern struct varlena *heap_tuple_untoast_attr_slice(struct varlena *attr,
+							  int32 sliceoffset,
+							  int32 slicelength);
+
+/* ----------
+ * toast_raw_datum_size -
+ *
+ *	Return the raw (detoasted) size of a varlena datum
+ * ----------
+ */
+extern Size toast_raw_datum_size(Datum value);
+
+/* ----------
+ * toast_datum_size -
+ *
+ *	Return the storage size of a varlena datum
+ * ----------
+ */
+extern Size toast_datum_size(Datum value);
+
+#endif							/* DETOAST_H */
diff --git a/src/include/access/tuptoaster.h b/src/include/access/heaptoast.h
similarity index 57%
rename from src/include/access/tuptoaster.h
rename to src/include/access/heaptoast.h
index f0aea2496b..bf02d2c600 100644
--- a/src/include/access/tuptoaster.h
+++ b/src/include/access/heaptoast.h
@@ -1,29 +1,22 @@
 /*-------------------------------------------------------------------------
  *
- * tuptoaster.h
- *	  POSTGRES definitions for external and compressed storage
+ * heaptoast.h
+ *	  Heap-specific definitions for external and compressed storage
  *	  of variable size attributes.
  *
  * Copyright (c) 2000-2019, PostgreSQL Global Development Group
  *
- * src/include/access/tuptoaster.h
+ * src/include/access/heaptoast.h
  *
  *-------------------------------------------------------------------------
  */
-#ifndef TUPTOASTER_H
-#define TUPTOASTER_H
+#ifndef HEAPTOAST_H
+#define HEAPTOAST_H
 
 #include "access/htup_details.h"
 #include "storage/lockdefs.h"
 #include "utils/relcache.h"
 
-/*
- * This enables de-toasting of index entries.  Needed until VACUUM is
- * smart enough to rebuild indexes from scratch.
- */
-#define TOAST_INDEX_HACK
-
-
 /*
  * Find the maximum size of a tuple if there are to be N tuples per page.
  */
@@ -95,37 +88,6 @@
 	 sizeof(int32) -									\
 	 VARHDRSZ)
 
-/* Size of an EXTERNAL datum that contains a standard TOAST pointer */
-#define TOAST_POINTER_SIZE (VARHDRSZ_EXTERNAL + sizeof(varatt_external))
-
-/* Size of an EXTERNAL datum that contains an indirection pointer */
-#define INDIRECT_POINTER_SIZE (VARHDRSZ_EXTERNAL + sizeof(varatt_indirect))
-
-/*
- * Testing whether an externally-stored value is compressed now requires
- * comparing extsize (the actual length of the external data) to rawsize
- * (the original uncompressed datum's size).  The latter includes VARHDRSZ
- * overhead, the former doesn't.  We never use compression unless it actually
- * saves space, so we expect either equality or less-than.
- */
-#define VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer) \
-	((toast_pointer).va_extsize < (toast_pointer).va_rawsize - VARHDRSZ)
-
-/*
- * Macro to fetch the possibly-unaligned contents of an EXTERNAL datum
- * into a local "struct varatt_external" toast pointer.  This should be
- * just a memcpy, but some versions of gcc seem to produce broken code
- * that assumes the datum contents are aligned.  Introducing an explicit
- * intermediate "varattrib_1b_e *" variable seems to fix it.
- */
-#define VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr) \
-do { \
-	varattrib_1b_e *attre = (varattrib_1b_e *) (attr); \
-	Assert(VARATT_IS_EXTERNAL(attre)); \
-	Assert(VARSIZE_EXTERNAL(attre) == sizeof(toast_pointer) + VARHDRSZ_EXTERNAL); \
-	memcpy(&(toast_pointer), VARDATA_EXTERNAL(attre), sizeof(toast_pointer)); \
-} while (0)
-
 /* ----------
  * toast_insert_or_update -
  *
@@ -144,36 +106,6 @@ extern HeapTuple toast_insert_or_update(Relation rel,
  */
 extern void toast_delete(Relation rel, HeapTuple oldtup, bool is_speculative);
 
-/* ----------
- * heap_tuple_fetch_attr() -
- *
- *		Fetches an external stored attribute from the toast
- *		relation. Does NOT decompress it, if stored external
- *		in compressed format.
- * ----------
- */
-extern struct varlena *heap_tuple_fetch_attr(struct varlena *attr);
-
-/* ----------
- * heap_tuple_untoast_attr() -
- *
- *		Fully detoasts one attribute, fetching and/or decompressing
- *		it as needed.
- * ----------
- */
-extern struct varlena *heap_tuple_untoast_attr(struct varlena *attr);
-
-/* ----------
- * heap_tuple_untoast_attr_slice() -
- *
- *		Fetches only the specified portion of an attribute.
- *		(Handles all cases for attribute storage)
- * ----------
- */
-extern struct varlena *heap_tuple_untoast_attr_slice(struct varlena *attr,
-													 int32 sliceoffset,
-													 int32 slicelength);
-
 /* ----------
  * toast_flatten_tuple -
  *
@@ -204,36 +136,4 @@ extern HeapTuple toast_build_flattened_tuple(TupleDesc tupleDesc,
 											 Datum *values,
 											 bool *isnull);
 
-/* ----------
- * toast_compress_datum -
- *
- *	Create a compressed version of a varlena datum, if possible
- * ----------
- */
-extern Datum toast_compress_datum(Datum value);
-
-/* ----------
- * toast_raw_datum_size -
- *
- *	Return the raw (detoasted) size of a varlena datum
- * ----------
- */
-extern Size toast_raw_datum_size(Datum value);
-
-/* ----------
- * toast_datum_size -
- *
- *	Return the storage size of a varlena datum
- * ----------
- */
-extern Size toast_datum_size(Datum value);
-
-/* ----------
- * toast_get_valid_index -
- *
- *	Return OID of valid index associated to a toast relation
- * ----------
- */
-extern Oid	toast_get_valid_index(Oid toastoid, LOCKMODE lock);
-
-#endif							/* TUPTOASTER_H */
+#endif							/* HEAPTOAST_H */
diff --git a/src/include/access/toast_internals.h b/src/include/access/toast_internals.h
new file mode 100644
index 0000000000..8ac367fb9f
--- /dev/null
+++ b/src/include/access/toast_internals.h
@@ -0,0 +1,54 @@
+/*-------------------------------------------------------------------------
+ *
+ * toast_internals.h
+ *	  Internal definitions for the TOAST system.
+ *
+ * Copyright (c) 2000-2019, PostgreSQL Global Development Group
+ *
+ * src/include/access/toast_internals.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef TOAST_INTERNALS_H
+#define TOAST_INTERNALS_H
+
+#include "storage/lockdefs.h"
+#include "utils/relcache.h"
+#include "utils/snapshot.h"
+
+/*
+ *	The information at the start of the compressed toast data.
+ */
+typedef struct toast_compress_header
+{
+	int32		vl_len_;		/* varlena header (do not touch directly!) */
+	int32		rawsize;
+} toast_compress_header;
+
+/*
+ * Utilities for manipulation of header information for compressed
+ * toast entries.
+ */
+#define TOAST_COMPRESS_HDRSZ		((int32) sizeof(toast_compress_header))
+#define TOAST_COMPRESS_RAWSIZE(ptr) (((toast_compress_header *) (ptr))->rawsize)
+#define TOAST_COMPRESS_RAWDATA(ptr) \
+	(((char *) (ptr)) + TOAST_COMPRESS_HDRSZ)
+#define TOAST_COMPRESS_SET_RAWSIZE(ptr, len) \
+	(((toast_compress_header *) (ptr))->rawsize = (len))
+
+extern Datum toast_compress_datum(Datum value);
+extern Oid	toast_get_valid_index(Oid toastoid, LOCKMODE lock);
+
+extern void toast_delete_datum(Relation rel, Datum value, bool is_speculative);
+extern Datum toast_save_datum(Relation rel, Datum value,
+				 struct varlena *oldexternal, int options);
+
+extern int toast_open_indexes(Relation toastrel,
+				   LOCKMODE lock,
+				   Relation **toastidxs,
+				   int *num_indexes);
+extern void toast_close_indexes(Relation *toastidxs, int num_indexes,
+					LOCKMODE lock);
+extern void init_toast_snapshot(Snapshot toast_snapshot);
+
+#endif							/* TOAST_INTERNALS_H */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 90a2257894..0f50e57dd6 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -17,10 +17,10 @@
 
 #include <ctype.h>
 
+#include "access/detoast.h"
 #include "access/htup_details.h"
 #include "access/transam.h"
 #include "access/tupconvert.h"
-#include "access/tuptoaster.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
index 7f03b7e857..826556eb29 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -19,9 +19,9 @@
 #include <math.h>
 #include <signal.h>
 
+#include "access/detoast.h"
 #include "access/htup_details.h"
 #include "access/transam.h"
-#include "access/tuptoaster.h"
 #include "access/xact.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_type.h"
-- 
2.17.2 (Apple Git-113)



  [application/octet-stream] v3-0002-Create-an-API-for-inserting-and-deleting-rows-in-.patch (30.0K, ../../CA+TgmoasEn823cDOrAsr_udy8qW5K2rYRBhyGsC914V95jg0gg@mail.gmail.com/4-v3-0002-Create-an-API-for-inserting-and-deleting-rows-in-.patch)
  download | inline diff:
From 7f3212a1c57942d8112481cd56c36e83e27e6059 Mon Sep 17 00:00:00 2001
From: Robert Haas <[email protected]>
Date: Tue, 14 May 2019 17:11:28 -0400
Subject: [PATCH v3 2/3] Create an API for inserting and deleting rows in TOAST
 tables.

This moves much of the non-heap-specific logic from toast_delete and
toast_insert_or_update into a helper functions accessible via a new
header, toast_helper.h.  Using the functions in this module, a table
AM can implement creation and deletion of TOAST table rows with
much less code duplication than was possible heretofore.  Some
table AMs won't want to use the TOAST logic at all, but for those
that do this will make that easier.
---
 src/backend/access/heap/heaptoast.c     | 400 +++---------------------
 src/backend/access/table/Makefile       |   2 +-
 src/backend/access/table/toast_helper.c | 331 ++++++++++++++++++++
 src/include/access/toast_helper.h       | 114 +++++++
 4 files changed, 490 insertions(+), 357 deletions(-)
 create mode 100644 src/backend/access/table/toast_helper.c
 create mode 100644 src/include/access/toast_helper.h

diff --git a/src/backend/access/heap/heaptoast.c b/src/backend/access/heap/heaptoast.c
index 5d105e3517..3a2118e1d8 100644
--- a/src/backend/access/heap/heaptoast.c
+++ b/src/backend/access/heap/heaptoast.c
@@ -27,6 +27,7 @@
 #include "access/detoast.h"
 #include "access/heapam.h"
 #include "access/heaptoast.h"
+#include "access/toast_helper.h"
 #include "access/toast_internals.h"
 
 
@@ -40,8 +41,6 @@ void
 toast_delete(Relation rel, HeapTuple oldtup, bool is_speculative)
 {
 	TupleDesc	tupleDesc;
-	int			numAttrs;
-	int			i;
 	Datum		toast_values[MaxHeapAttributeNumber];
 	bool		toast_isnull[MaxHeapAttributeNumber];
 
@@ -64,27 +63,12 @@ toast_delete(Relation rel, HeapTuple oldtup, bool is_speculative)
 	 * least one varlena column, by the way.)
 	 */
 	tupleDesc = rel->rd_att;
-	numAttrs = tupleDesc->natts;
 
-	Assert(numAttrs <= MaxHeapAttributeNumber);
+	Assert(tupleDesc->natts <= MaxHeapAttributeNumber);
 	heap_deform_tuple(oldtup, tupleDesc, toast_values, toast_isnull);
 
-	/*
-	 * Check for external stored attributes and delete them from the secondary
-	 * relation.
-	 */
-	for (i = 0; i < numAttrs; i++)
-	{
-		if (TupleDescAttr(tupleDesc, i)->attlen == -1)
-		{
-			Datum		value = toast_values[i];
-
-			if (toast_isnull[i])
-				continue;
-			else if (VARATT_IS_EXTERNAL_ONDISK(PointerGetDatum(value)))
-				toast_delete_datum(rel, value, is_speculative);
-		}
-	}
+	/* Do the real work. */
+	toast_delete_external(rel, toast_values, toast_isnull, is_speculative);
 }
 
 
@@ -113,25 +97,16 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 	HeapTuple	result_tuple;
 	TupleDesc	tupleDesc;
 	int			numAttrs;
-	int			i;
-
-	bool		need_change = false;
-	bool		need_free = false;
-	bool		need_delold = false;
-	bool		has_nulls = false;
 
 	Size		maxDataLen;
 	Size		hoff;
 
-	char		toast_action[MaxHeapAttributeNumber];
 	bool		toast_isnull[MaxHeapAttributeNumber];
 	bool		toast_oldisnull[MaxHeapAttributeNumber];
 	Datum		toast_values[MaxHeapAttributeNumber];
 	Datum		toast_oldvalues[MaxHeapAttributeNumber];
-	struct varlena *toast_oldexternal[MaxHeapAttributeNumber];
-	int32		toast_sizes[MaxHeapAttributeNumber];
-	bool		toast_free[MaxHeapAttributeNumber];
-	bool		toast_delold[MaxHeapAttributeNumber];
+	ToastAttrInfo	toast_attr[MaxHeapAttributeNumber];
+	ToastTupleContext	ttc;
 
 	/*
 	 * Ignore the INSERT_SPECULATIVE option. Speculative insertions/super
@@ -160,129 +135,24 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 		heap_deform_tuple(oldtup, tupleDesc, toast_oldvalues, toast_oldisnull);
 
 	/* ----------
-	 * Then collect information about the values given
-	 *
-	 * NOTE: toast_action[i] can have these values:
-	 *		' '		default handling
-	 *		'p'		already processed --- don't touch it
-	 *		'x'		incompressible, but OK to move off
-	 *
-	 * NOTE: toast_sizes[i] is only made valid for varlena attributes with
-	 *		toast_action[i] different from 'p'.
+	 * Prepare for toasting
 	 * ----------
 	 */
-	memset(toast_action, ' ', numAttrs * sizeof(char));
-	memset(toast_oldexternal, 0, numAttrs * sizeof(struct varlena *));
-	memset(toast_free, 0, numAttrs * sizeof(bool));
-	memset(toast_delold, 0, numAttrs * sizeof(bool));
-
-	for (i = 0; i < numAttrs; i++)
+	ttc.ttc_rel = rel;
+	ttc.ttc_values = toast_values;
+	ttc.ttc_isnull = toast_isnull;
+	if (oldtup == NULL)
 	{
-		Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
-		struct varlena *old_value;
-		struct varlena *new_value;
-
-		if (oldtup != NULL)
-		{
-			/*
-			 * For UPDATE get the old and new values of this attribute
-			 */
-			old_value = (struct varlena *) DatumGetPointer(toast_oldvalues[i]);
-			new_value = (struct varlena *) DatumGetPointer(toast_values[i]);
-
-			/*
-			 * If the old value is stored on disk, check if it has changed so
-			 * we have to delete it later.
-			 */
-			if (att->attlen == -1 && !toast_oldisnull[i] &&
-				VARATT_IS_EXTERNAL_ONDISK(old_value))
-			{
-				if (toast_isnull[i] || !VARATT_IS_EXTERNAL_ONDISK(new_value) ||
-					memcmp((char *) old_value, (char *) new_value,
-						   VARSIZE_EXTERNAL(old_value)) != 0)
-				{
-					/*
-					 * The old external stored value isn't needed any more
-					 * after the update
-					 */
-					toast_delold[i] = true;
-					need_delold = true;
-				}
-				else
-				{
-					/*
-					 * This attribute isn't changed by this update so we reuse
-					 * the original reference to the old value in the new
-					 * tuple.
-					 */
-					toast_action[i] = 'p';
-					continue;
-				}
-			}
-		}
-		else
-		{
-			/*
-			 * For INSERT simply get the new value
-			 */
-			new_value = (struct varlena *) DatumGetPointer(toast_values[i]);
-		}
-
-		/*
-		 * Handle NULL attributes
-		 */
-		if (toast_isnull[i])
-		{
-			toast_action[i] = 'p';
-			has_nulls = true;
-			continue;
-		}
-
-		/*
-		 * Now look at varlena attributes
-		 */
-		if (att->attlen == -1)
-		{
-			/*
-			 * If the table's attribute says PLAIN always, force it so.
-			 */
-			if (att->attstorage == 'p')
-				toast_action[i] = 'p';
-
-			/*
-			 * We took care of UPDATE above, so any external value we find
-			 * still in the tuple must be someone else's that we cannot reuse
-			 * (this includes the case of an out-of-line in-memory datum).
-			 * Fetch it back (without decompression, unless we are forcing
-			 * PLAIN storage).  If necessary, we'll push it out as a new
-			 * external value below.
-			 */
-			if (VARATT_IS_EXTERNAL(new_value))
-			{
-				toast_oldexternal[i] = new_value;
-				if (att->attstorage == 'p')
-					new_value = heap_tuple_untoast_attr(new_value);
-				else
-					new_value = heap_tuple_fetch_attr(new_value);
-				toast_values[i] = PointerGetDatum(new_value);
-				toast_free[i] = true;
-				need_change = true;
-				need_free = true;
-			}
-
-			/*
-			 * Remember the size of this attribute
-			 */
-			toast_sizes[i] = VARSIZE_ANY(new_value);
-		}
-		else
-		{
-			/*
-			 * Not a varlena attribute, plain storage always
-			 */
-			toast_action[i] = 'p';
-		}
+		ttc.ttc_oldvalues = NULL;
+		ttc.ttc_oldisnull = NULL;
 	}
+	else
+	{
+		ttc.ttc_oldvalues = toast_oldvalues;
+		ttc.ttc_oldisnull = toast_oldisnull;
+	}
+	ttc.ttc_attr = toast_attr;
+	toast_tuple_init(&ttc);
 
 	/* ----------
 	 * Compress and/or save external until data fits into target length
@@ -297,7 +167,7 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 
 	/* compute header overhead --- this should match heap_form_tuple() */
 	hoff = SizeofHeapTupleHeader;
-	if (has_nulls)
+	if ((ttc.ttc_flags & TOAST_HAS_NULLS) != 0)
 		hoff += BITMAPLEN(numAttrs);
 	hoff = MAXALIGN(hoff);
 	/* now convert to a limit on the tuple data size */
@@ -310,66 +180,21 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 	while (heap_compute_data_size(tupleDesc,
 								  toast_values, toast_isnull) > maxDataLen)
 	{
-		int			biggest_attno = -1;
-		int32		biggest_size = MAXALIGN(TOAST_POINTER_SIZE);
-		Datum		old_value;
-		Datum		new_value;
-
-		/*
-		 * Search for the biggest yet unprocessed internal attribute
-		 */
-		for (i = 0; i < numAttrs; i++)
-		{
-			Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
-
-			if (toast_action[i] != ' ')
-				continue;
-			if (VARATT_IS_EXTERNAL(DatumGetPointer(toast_values[i])))
-				continue;		/* can't happen, toast_action would be 'p' */
-			if (VARATT_IS_COMPRESSED(DatumGetPointer(toast_values[i])))
-				continue;
-			if (att->attstorage != 'x' && att->attstorage != 'e')
-				continue;
-			if (toast_sizes[i] > biggest_size)
-			{
-				biggest_attno = i;
-				biggest_size = toast_sizes[i];
-			}
-		}
+		int			biggest_attno;
 
+		biggest_attno = toast_tuple_find_biggest_attribute(&ttc, true, false);
 		if (biggest_attno < 0)
 			break;
 
 		/*
 		 * Attempt to compress it inline, if it has attstorage 'x'
 		 */
-		i = biggest_attno;
-		if (TupleDescAttr(tupleDesc, i)->attstorage == 'x')
-		{
-			old_value = toast_values[i];
-			new_value = toast_compress_datum(old_value);
-
-			if (DatumGetPointer(new_value) != NULL)
-			{
-				/* successful compression */
-				if (toast_free[i])
-					pfree(DatumGetPointer(old_value));
-				toast_values[i] = new_value;
-				toast_free[i] = true;
-				toast_sizes[i] = VARSIZE(DatumGetPointer(toast_values[i]));
-				need_change = true;
-				need_free = true;
-			}
-			else
-			{
-				/* incompressible, ignore on subsequent compression passes */
-				toast_action[i] = 'x';
-			}
-		}
+		if (TupleDescAttr(tupleDesc, biggest_attno)->attstorage == 'x')
+			toast_tuple_try_compression(&ttc, biggest_attno);
 		else
 		{
 			/* has attstorage 'e', ignore on subsequent compression passes */
-			toast_action[i] = 'x';
+			toast_attr[biggest_attno].tai_colflags |= TOASTCOL_INCOMPRESSIBLE;
 		}
 
 		/*
@@ -380,72 +205,26 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 		 *
 		 * XXX maybe the threshold should be less than maxDataLen?
 		 */
-		if (toast_sizes[i] > maxDataLen &&
+		if (toast_attr[biggest_attno].tai_size > maxDataLen &&
 			rel->rd_rel->reltoastrelid != InvalidOid)
-		{
-			old_value = toast_values[i];
-			toast_action[i] = 'p';
-			toast_values[i] = toast_save_datum(rel, toast_values[i],
-											   toast_oldexternal[i], options);
-			if (toast_free[i])
-				pfree(DatumGetPointer(old_value));
-			toast_free[i] = true;
-			need_change = true;
-			need_free = true;
-		}
+			toast_tuple_externalize(&ttc, biggest_attno, options);
 	}
 
 	/*
 	 * Second we look for attributes of attstorage 'x' or 'e' that are still
-	 * inline.  But skip this if there's no toast table to push them to.
+	 * inline, and make them external.  But skip this if there's no toast table
+	 * to push them to.
 	 */
 	while (heap_compute_data_size(tupleDesc,
 								  toast_values, toast_isnull) > maxDataLen &&
 		   rel->rd_rel->reltoastrelid != InvalidOid)
 	{
-		int			biggest_attno = -1;
-		int32		biggest_size = MAXALIGN(TOAST_POINTER_SIZE);
-		Datum		old_value;
-
-		/*------
-		 * Search for the biggest yet inlined attribute with
-		 * attstorage equals 'x' or 'e'
-		 *------
-		 */
-		for (i = 0; i < numAttrs; i++)
-		{
-			Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
-
-			if (toast_action[i] == 'p')
-				continue;
-			if (VARATT_IS_EXTERNAL(DatumGetPointer(toast_values[i])))
-				continue;		/* can't happen, toast_action would be 'p' */
-			if (att->attstorage != 'x' && att->attstorage != 'e')
-				continue;
-			if (toast_sizes[i] > biggest_size)
-			{
-				biggest_attno = i;
-				biggest_size = toast_sizes[i];
-			}
-		}
+		int			biggest_attno;
 
+		biggest_attno = toast_tuple_find_biggest_attribute(&ttc, false, false);
 		if (biggest_attno < 0)
 			break;
-
-		/*
-		 * Store this external
-		 */
-		i = biggest_attno;
-		old_value = toast_values[i];
-		toast_action[i] = 'p';
-		toast_values[i] = toast_save_datum(rel, toast_values[i],
-										   toast_oldexternal[i], options);
-		if (toast_free[i])
-			pfree(DatumGetPointer(old_value));
-		toast_free[i] = true;
-
-		need_change = true;
-		need_free = true;
+		toast_tuple_externalize(&ttc, biggest_attno, options);
 	}
 
 	/*
@@ -455,57 +234,13 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 	while (heap_compute_data_size(tupleDesc,
 								  toast_values, toast_isnull) > maxDataLen)
 	{
-		int			biggest_attno = -1;
-		int32		biggest_size = MAXALIGN(TOAST_POINTER_SIZE);
-		Datum		old_value;
-		Datum		new_value;
-
-		/*
-		 * Search for the biggest yet uncompressed internal attribute
-		 */
-		for (i = 0; i < numAttrs; i++)
-		{
-			if (toast_action[i] != ' ')
-				continue;
-			if (VARATT_IS_EXTERNAL(DatumGetPointer(toast_values[i])))
-				continue;		/* can't happen, toast_action would be 'p' */
-			if (VARATT_IS_COMPRESSED(DatumGetPointer(toast_values[i])))
-				continue;
-			if (TupleDescAttr(tupleDesc, i)->attstorage != 'm')
-				continue;
-			if (toast_sizes[i] > biggest_size)
-			{
-				biggest_attno = i;
-				biggest_size = toast_sizes[i];
-			}
-		}
+		int			biggest_attno;
 
+		biggest_attno = toast_tuple_find_biggest_attribute(&ttc, true, true);
 		if (biggest_attno < 0)
 			break;
 
-		/*
-		 * Attempt to compress it inline
-		 */
-		i = biggest_attno;
-		old_value = toast_values[i];
-		new_value = toast_compress_datum(old_value);
-
-		if (DatumGetPointer(new_value) != NULL)
-		{
-			/* successful compression */
-			if (toast_free[i])
-				pfree(DatumGetPointer(old_value));
-			toast_values[i] = new_value;
-			toast_free[i] = true;
-			toast_sizes[i] = VARSIZE(DatumGetPointer(toast_values[i]));
-			need_change = true;
-			need_free = true;
-		}
-		else
-		{
-			/* incompressible, ignore on subsequent compression passes */
-			toast_action[i] = 'x';
-		}
+		toast_tuple_try_compression(&ttc, biggest_attno);
 	}
 
 	/*
@@ -519,54 +254,20 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 								  toast_values, toast_isnull) > maxDataLen &&
 		   rel->rd_rel->reltoastrelid != InvalidOid)
 	{
-		int			biggest_attno = -1;
-		int32		biggest_size = MAXALIGN(TOAST_POINTER_SIZE);
-		Datum		old_value;
-
-		/*--------
-		 * Search for the biggest yet inlined attribute with
-		 * attstorage = 'm'
-		 *--------
-		 */
-		for (i = 0; i < numAttrs; i++)
-		{
-			if (toast_action[i] == 'p')
-				continue;
-			if (VARATT_IS_EXTERNAL(DatumGetPointer(toast_values[i])))
-				continue;		/* can't happen, toast_action would be 'p' */
-			if (TupleDescAttr(tupleDesc, i)->attstorage != 'm')
-				continue;
-			if (toast_sizes[i] > biggest_size)
-			{
-				biggest_attno = i;
-				biggest_size = toast_sizes[i];
-			}
-		}
+		int			biggest_attno;
 
+		biggest_attno = toast_tuple_find_biggest_attribute(&ttc, false, true);
 		if (biggest_attno < 0)
 			break;
 
-		/*
-		 * Store this external
-		 */
-		i = biggest_attno;
-		old_value = toast_values[i];
-		toast_action[i] = 'p';
-		toast_values[i] = toast_save_datum(rel, toast_values[i],
-										   toast_oldexternal[i], options);
-		if (toast_free[i])
-			pfree(DatumGetPointer(old_value));
-		toast_free[i] = true;
-
-		need_change = true;
-		need_free = true;
+		toast_tuple_externalize(&ttc, biggest_attno, options);
 	}
 
 	/*
 	 * In the case we toasted any values, we need to build a new heap tuple
 	 * with the changed values.
 	 */
-	if (need_change)
+	if ((ttc.ttc_flags & TOAST_NEEDS_CHANGE) != 0)
 	{
 		HeapTupleHeader olddata = newtup->t_data;
 		HeapTupleHeader new_data;
@@ -585,7 +286,7 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 		 * whether there needs to be one at all.
 		 */
 		new_header_len = SizeofHeapTupleHeader;
-		if (has_nulls)
+		if ((ttc.ttc_flags & TOAST_HAS_NULLS) != 0)
 			new_header_len += BITMAPLEN(numAttrs);
 		new_header_len = MAXALIGN(new_header_len);
 		new_data_len = heap_compute_data_size(tupleDesc,
@@ -616,26 +317,13 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 						(char *) new_data + new_header_len,
 						new_data_len,
 						&(new_data->t_infomask),
-						has_nulls ? new_data->t_bits : NULL);
+						((ttc.ttc_flags & TOAST_HAS_NULLS) != 0) ?
+						new_data->t_bits : NULL);
 	}
 	else
 		result_tuple = newtup;
 
-	/*
-	 * Free allocated temp values
-	 */
-	if (need_free)
-		for (i = 0; i < numAttrs; i++)
-			if (toast_free[i])
-				pfree(DatumGetPointer(toast_values[i]));
-
-	/*
-	 * Delete external values from the old tuple
-	 */
-	if (need_delold)
-		for (i = 0; i < numAttrs; i++)
-			if (toast_delold[i])
-				toast_delete_datum(rel, toast_oldvalues[i], false);
+	toast_tuple_cleanup(&ttc);
 
 	return result_tuple;
 }
diff --git a/src/backend/access/table/Makefile b/src/backend/access/table/Makefile
index 55a0e5efad..b29df3f333 100644
--- a/src/backend/access/table/Makefile
+++ b/src/backend/access/table/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/table
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = table.o tableam.o tableamapi.o
+OBJS = table.o tableam.o tableamapi.o toast_helper.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/table/toast_helper.c b/src/backend/access/table/toast_helper.c
new file mode 100644
index 0000000000..ee119cea2d
--- /dev/null
+++ b/src/backend/access/table/toast_helper.c
@@ -0,0 +1,331 @@
+/*-------------------------------------------------------------------------
+ *
+ * toast_helper.c
+ *	  Helper functions for table AMs implementing compressed or
+ *    out-of-line storage of varlena attributes.
+ *
+ * Copyright (c) 2000-2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/common/toast_helper.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/detoast.h"
+#include "access/table.h"
+#include "access/toast_helper.h"
+#include "access/toast_internals.h"
+
+/*
+ * Prepare to TOAST a tuple.
+ *
+ * tupleDesc, toast_values, and toast_isnull are required parameters; they
+ * provide the necessary details about the tuple to be toasted.
+ *
+ * toast_oldvalues and toast_oldisnull should be NULL for a newly-inserted
+ * tuple; for an update, they should describe the existing tuple.
+ *
+ * All of these arrays should have a length equal to tupleDesc->natts.
+ *
+ * On return, toast_flags and toast_attr will have been initialized.
+ * toast_flags is just a single uint8, but toast_attr is an caller-provided
+ * array with a length equal to tupleDesc->natts.  The caller need not
+ * perform any initialization of the array before calling this function.
+ */
+void
+toast_tuple_init(ToastTupleContext *ttc)
+{
+	TupleDesc	tupleDesc = ttc->ttc_rel->rd_att;
+	int			numAttrs = tupleDesc->natts;
+	int		i;
+
+	ttc->ttc_flags = 0;
+
+	for (i = 0; i < numAttrs; i++)
+	{
+		Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
+		struct varlena *old_value;
+		struct varlena *new_value;
+
+		ttc->ttc_attr[i].tai_colflags = 0;
+		ttc->ttc_attr[i].tai_oldexternal = NULL;
+
+		if (ttc->ttc_oldvalues != NULL)
+		{
+			/*
+			 * For UPDATE get the old and new values of this attribute
+			 */
+			old_value =
+				(struct varlena *) DatumGetPointer(ttc->ttc_oldvalues[i]);
+			new_value =
+				(struct varlena *) DatumGetPointer(ttc->ttc_values[i]);
+
+			/*
+			 * If the old value is stored on disk, check if it has changed so
+			 * we have to delete it later.
+			 */
+			if (att->attlen == -1 && !ttc->ttc_oldisnull[i] &&
+				VARATT_IS_EXTERNAL_ONDISK(old_value))
+			{
+				if (ttc->ttc_isnull[i] ||
+					!VARATT_IS_EXTERNAL_ONDISK(new_value) ||
+					memcmp((char *) old_value, (char *) new_value,
+						   VARSIZE_EXTERNAL(old_value)) != 0)
+				{
+					/*
+					 * The old external stored value isn't needed any more
+					 * after the update
+					 */
+					ttc->ttc_attr[i].tai_colflags |= TOASTCOL_NEEDS_DELETE_OLD;
+					ttc->ttc_flags |= TOAST_NEEDS_DELETE_OLD;
+				}
+				else
+				{
+					/*
+					 * This attribute isn't changed by this update so we reuse
+					 * the original reference to the old value in the new
+					 * tuple.
+					 */
+					ttc->ttc_attr[i].tai_colflags |= TOASTCOL_IGNORE;
+					continue;
+				}
+			}
+		}
+		else
+		{
+			/*
+			 * For INSERT simply get the new value
+			 */
+			new_value = (struct varlena *) DatumGetPointer(ttc->ttc_values[i]);
+		}
+
+		/*
+		 * Handle NULL attributes
+		 */
+		if (ttc->ttc_isnull[i])
+		{
+			ttc->ttc_attr[i].tai_colflags |= TOASTCOL_IGNORE;
+			ttc->ttc_flags |= TOAST_HAS_NULLS;
+			continue;
+		}
+
+		/*
+		 * Now look at varlena attributes
+		 */
+		if (att->attlen == -1)
+		{
+			/*
+			 * If the table's attribute says PLAIN always, force it so.
+			 */
+			if (att->attstorage == 'p')
+				ttc->ttc_attr[i].tai_colflags |= TOASTCOL_IGNORE;
+
+			/*
+			 * We took care of UPDATE above, so any external value we find
+			 * still in the tuple must be someone else's that we cannot reuse
+			 * (this includes the case of an out-of-line in-memory datum).
+			 * Fetch it back (without decompression, unless we are forcing
+			 * PLAIN storage).  If necessary, we'll push it out as a new
+			 * external value below.
+			 */
+			if (VARATT_IS_EXTERNAL(new_value))
+			{
+				ttc->ttc_attr[i].tai_oldexternal = new_value;
+				if (att->attstorage == 'p')
+					new_value = heap_tuple_untoast_attr(new_value);
+				else
+					new_value = heap_tuple_fetch_attr(new_value);
+				ttc->ttc_values[i] = PointerGetDatum(new_value);
+				ttc->ttc_attr[i].tai_colflags |= TOASTCOL_NEEDS_FREE;
+				ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE);
+			}
+
+			/*
+			 * Remember the size of this attribute
+			 */
+			ttc->ttc_attr[i].tai_size = VARSIZE_ANY(new_value);
+		}
+		else
+		{
+			/*
+			 * Not a varlena attribute, plain storage always
+			 */
+			ttc->ttc_attr[i].tai_colflags |= TOASTCOL_IGNORE;
+		}
+	}
+}
+
+/*
+ * Find the largest varlena attribute that satisfies certain criteria.
+ *
+ * The relevant column must not be marked TOASTCOL_IGNORE, and if the
+ * for_compression flag is passed as true, it must also not be marked
+ * TOASTCOL_INCOMPRESSIBLE.
+ *
+ * The column must have attstorage 'e' or 'x' if check_main is false, and
+ * must have attstorage 'm' if check_main is true.
+ *
+ * The column must have a minimum size of MAXALIGN(TOAST_POINTER_SIZE);
+ * if not, no benefit is to be expected by compressing it.
+ *
+ * The return value is the index of the biggest suitable column, or
+ * -1 if there is none.
+ */
+int
+toast_tuple_find_biggest_attribute(ToastTupleContext *ttc,
+								   bool for_compression, bool check_main)
+{
+	TupleDesc	tupleDesc = ttc->ttc_rel->rd_att;
+	int			numAttrs = tupleDesc->natts;
+	int			biggest_attno = -1;
+	int32		biggest_size = MAXALIGN(TOAST_POINTER_SIZE);
+	int32		skip_colflags = TOASTCOL_IGNORE;
+	int			i;
+
+	if (for_compression)
+		skip_colflags |= TOASTCOL_INCOMPRESSIBLE;
+
+	for (i = 0; i < numAttrs; i++)
+	{
+		Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
+
+		if ((ttc->ttc_attr[i].tai_colflags & skip_colflags) != 0)
+			continue;
+		if (VARATT_IS_EXTERNAL(DatumGetPointer(ttc->ttc_values[i])))
+			continue;		/* can't happen, toast_action would be 'p' */
+		if (for_compression &&
+			VARATT_IS_COMPRESSED(DatumGetPointer(ttc->ttc_values[i])))
+			continue;
+		if (check_main && att->attstorage != 'm')
+			continue;
+		if (!check_main && att->attstorage != 'x' && att->attstorage != 'e')
+			continue;
+
+		if (ttc->ttc_attr[i].tai_size > biggest_size)
+		{
+			biggest_attno = i;
+			biggest_size = ttc->ttc_attr[i].tai_size;
+		}
+	}
+
+	return biggest_attno;
+}
+
+/*
+ * Try compression for an attribute.
+ *
+ * If we find that the attribute is not compressible, mark it so.
+ */
+void
+toast_tuple_try_compression(ToastTupleContext *ttc, int attribute)
+{
+	Datum  *value = &ttc->ttc_values[attribute];
+	Datum	new_value = toast_compress_datum(*value);
+	ToastAttrInfo  *attr = &ttc->ttc_attr[attribute];
+
+	if (DatumGetPointer(new_value) != NULL)
+	{
+		/* successful compression */
+		if ((attr->tai_colflags & TOASTCOL_NEEDS_FREE) != 0)
+			pfree(DatumGetPointer(*value));
+		*value = new_value;
+		attr->tai_colflags |= TOASTCOL_NEEDS_FREE;
+		attr->tai_size = VARSIZE(DatumGetPointer(*value));
+		ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE);
+	}
+	else
+	{
+		/* incompressible, ignore on subsequent compression passes */
+		attr->tai_colflags |= TOASTCOL_INCOMPRESSIBLE;
+	}
+}
+
+/*
+ * Move an attribute to external storage.
+ */
+void
+toast_tuple_externalize(ToastTupleContext *ttc, int attribute, int options)
+{
+	Datum  *value = &ttc->ttc_values[attribute];
+	Datum	old_value = *value;
+	ToastAttrInfo  *attr = &ttc->ttc_attr[attribute];
+
+	attr->tai_colflags |= TOASTCOL_IGNORE;
+	*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;
+	ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE);
+}
+
+/*
+ * Perform appropriate cleanup after one tuple has been subjected to TOAST.
+ */
+void
+toast_tuple_cleanup(ToastTupleContext *ttc)
+{
+	TupleDesc	tupleDesc = ttc->ttc_rel->rd_att;
+	int			numAttrs = tupleDesc->natts;
+
+	/*
+	 * Free allocated temp values
+	 */
+	if ((ttc->ttc_flags & TOAST_NEEDS_FREE) != 0)
+	{
+		int		i;
+
+		for (i = 0; i < numAttrs; i++)
+		{
+			ToastAttrInfo  *attr = &ttc->ttc_attr[i];
+
+			if ((attr->tai_colflags & TOASTCOL_NEEDS_FREE) != 0)
+				pfree(DatumGetPointer(ttc->ttc_values[i]));
+		}
+	}
+
+	/*
+	 * Delete external values from the old tuple
+	 */
+	if ((ttc->ttc_flags & TOAST_NEEDS_DELETE_OLD) != 0)
+	{
+		int		i;
+
+		for (i = 0; i < numAttrs; i++)
+		{
+			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);
+		}
+	}
+}
+
+/*
+ * Check for external stored attributes and delete them from the secondary
+ * relation.
+ */
+void
+toast_delete_external(Relation rel, Datum *values, bool *isnull,
+					  bool is_speculative)
+{
+	TupleDesc	tupleDesc = rel->rd_att;
+	int			numAttrs = tupleDesc->natts;
+	int			i;
+
+	for (i = 0; i < numAttrs; i++)
+	{
+		if (TupleDescAttr(tupleDesc, i)->attlen == -1)
+		{
+			Datum		value = values[i];
+
+			if (isnull[i])
+				continue;
+			else if (VARATT_IS_EXTERNAL_ONDISK(PointerGetDatum(value)))
+				toast_delete_datum(rel, value, is_speculative);
+		}
+	}
+}
diff --git a/src/include/access/toast_helper.h b/src/include/access/toast_helper.h
new file mode 100644
index 0000000000..0fac6cc772
--- /dev/null
+++ b/src/include/access/toast_helper.h
@@ -0,0 +1,114 @@
+/*-------------------------------------------------------------------------
+ *
+ * toast_helper.h
+ *	  Helper functions for table AMs implementing compressed or
+ *    out-of-line storage of varlena attributes.
+ *
+ * Copyright (c) 2000-2019, PostgreSQL Global Development Group
+ *
+ * src/include/access/toast_helper.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef TOAST_HELPER_H
+#define TOAST_HELPER_H
+
+#include "utils/rel.h"
+
+/*
+ * Information about one column of a tuple being toasted.
+ *
+ * NOTE: toast_action[i] can have these values:
+ *		' '		default handling
+ *		'p'		already processed --- don't touch it
+ *		'x'		incompressible, but OK to move off
+ *
+ * NOTE: toast_attr[i].tai_size is only made valid for varlena attributes with
+ * toast_action[i] different from 'p'.
+ */
+typedef struct
+{
+	struct varlena *tai_oldexternal;
+	int32		tai_size;
+	uint8		tai_colflags;
+} ToastAttrInfo;
+
+/*
+ * Information about one tuple being toasted.
+ */
+typedef struct
+{
+	/*
+	 * Before calling toast_tuple_init, the caller must initialize the
+	 * following fields.  Each array must have a length equal to
+	 * ttc_rel->rd_att->natts.  The tts_oldvalues and tts_oldisnull fields
+	 * should be NULL in the case of an insert.
+	 */
+	Relation	ttc_rel;			/* the relation that contains the tuple */
+	Datum	   *ttc_values;			/* values from the tuple columns */
+	bool	   *ttc_isnull;			/* null flags for the tuple columns */
+	Datum	   *ttc_oldvalues;		/* values from previous tuple */
+	bool	   *ttc_oldisnull;		/* null flags from previous tuple */
+
+	/*
+	 * Before calling toast_tuple_init, the caller should set tts_attr to
+	 * point to an array of ToastAttrInfo structures of a length equal to
+	 * tts_rel->rd_att->natts.  The contents of the array need not be
+	 * initialized.  ttc_flags also does not need to be initialized.
+	 */
+	uint8		ttc_flags;
+	ToastAttrInfo	*ttc_attr;
+} ToastTupleContext;
+
+/*
+ * Flags indicating the overall state of a TOAST operation.
+ *
+ * TOAST_NEEDS_DELETE_OLD indicates that one or more old TOAST datums need
+ * to be deleted.
+ *
+ * TOAST_NEEDS_FREE indicates that one or more TOAST values need to be freed.
+ *
+ * TOAST_HAS_NULLS indicates that nulls were found in the tuple being toasted.
+ *
+ * TOAST_NEEDS_CHANGE indicates that a new tuple needs to built; in other
+ * words, the toaster did something.
+ */
+#define TOAST_NEEDS_DELETE_OLD				0x0001
+#define TOAST_NEEDS_FREE					0x0002
+#define TOAST_HAS_NULLS						0x0004
+#define TOAST_NEEDS_CHANGE					0x0008
+
+/*
+ * Flags indicating the status of a TOAST operation with respect to a
+ * particular column.
+ *
+ * TOASTCOL_NEEDS_DELETE_OLD indicates that the old TOAST datums for this
+ * column need to be deleted.
+ *
+ * TOASTCOL_NEEDS_FREE indicates that the value for this column needs to
+ * be freed.
+ *
+ * TOASTCOL_IGNORE indicates that the toaster should not further process
+ * this column.
+ *
+ * TOASTCOL_INCOMPRESSIBLE indicates that this column has been found to
+ * be incompressible, but could be moved out-of-line.
+ */
+#define TOASTCOL_NEEDS_DELETE_OLD			TOAST_NEEDS_DELETE_OLD
+#define TOASTCOL_NEEDS_FREE					TOAST_NEEDS_FREE
+#define TOASTCOL_IGNORE						0x0010
+#define TOASTCOL_INCOMPRESSIBLE				0x0020
+
+extern void toast_tuple_init(ToastTupleContext *ttc);
+extern int toast_tuple_find_biggest_attribute(ToastTupleContext *ttc,
+								   bool for_compression, bool check_main);
+extern void toast_tuple_try_compression(ToastTupleContext *ttc, int attribute);
+extern void toast_tuple_externalize(ToastTupleContext *ttc, int attribute,
+						int options);
+extern void toast_tuple_cleanup(ToastTupleContext *ttc);
+
+extern void toast_delete_external(Relation rel, Datum *values, bool *isnull,
+					  bool is_speculative);
+
+#endif
-- 
2.17.2 (Apple Git-113)



^ permalink  raw  reply  [nested|flat] 12+ messages in thread

* Re: tableam vs. TOAST
  2019-06-11 16:17 Re: tableam vs. TOAST Robert Haas <[email protected]>
@ 2019-06-25 06:19 ` Prabhat Sahu <[email protected]>
  0 siblings, 0 replies; 12+ messages in thread

From: Prabhat Sahu @ 2019-06-25 06:19 UTC (permalink / raw)
  To: Robert Haas <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

On Tue, Jun 11, 2019 at 9:47 PM Robert Haas <[email protected]> wrote:

> On Tue, May 21, 2019 at 2:10 PM Robert Haas <[email protected]> wrote:
> > Updated and rebased patches attached.
>
> And again.
>

Hi Robert,

I have tested the TOAST patches(v3) with different storage options
like(MAIN, EXTERNAL, EXTENDED, etc.), and
combinations of compression and out-of-line storage options.
I have used a few dummy tables with various tuple count say 10k, 20k, 40k,
etc. with different column lengths.
Used manual CHECKPOINT option with (checkpoint_timeout = 1d, max_wal_size =
10GB) before the test to avoid performance fluctuations,
and calculated the results as a median value of a few consecutive test
executions.

Please find the SQL script attached herewith, which I have used to perform
the observation.

Below are the test scenarios, how I have checked the behavior and
performance of TOAST patches against PG master.
1. where a single column is compressed(SCC)
2. where multiple columns are compressed(MCC)
        -- ALTER the table column/s for storage as "MAIN" to make sure that
the column values are COMPRESSED.

3. where a single column is pushed to the TOAST table but not
compressed(SCTNC)
4. where multiple columns are pushed to the TOAST table but not
compressed(MCTNC)
        -- ALTER the table column/s for storage as "EXTERNAL" to make sure
that the column values are pushed to the TOAST table but not COMPRESSED.

5. where a single column is pushed to the TOAST table and also
compressed(SCTC)
6. where multiple columns are pushed to the TOAST table and also
compressed(MCTC)
        -- ALTER the table column/s for storage as "EXTENDED" to make sure
that the column values are pushed to the TOAST table and also COMPRESSED.

7. updating the tuples with similar data shouldn't affect the behavior of
storage options.

Please find my observation as below:
System Used: (VCPUs: 8, RAM: 16GB, Size: 640GB)
10000 Tuples 20000 Tuples 40000 Tuples 80000 Tuples
Without Patch With Patch Without Patch With Patch Without Patch With
Patch Without
Patch With Patch
1. SCC INSERT 125921.737 ms (02:05.922) 125992.563 ms (02:05.993) 234263.295
ms (03:54.263) 235952.336 ms (03:55.952) 497290.442 ms (08:17.290) 502820.139
ms (08:22.820) 948470.603 ms (15:48.471) 941778.952 ms (15:41.779)
1. SCC UPDATE 263017.814 ms (04:23.018) 270893.910 ms (04:30.894) 488393.748
ms (08:08.394) 507937.377 ms (08:27.937) 1078862.613 ms (17:58.863) 1053029.428
ms (17:33.029) 2037119.576 ms (33:57.120) 2023633.862 ms (33:43.634)
2. MCC INSERT 35415.089 ms (00:35.415) 35910.552 ms (00:35.911) 70899.737
ms (01:10.900) 70800.964 ms (01:10.801) 142185.996 ms (02:22.186) 142241.913
ms (02:22.242)
2. MCC UPDATE 72043.757 ms (01:12.044) 73848.732 ms (01:13.849) 137717.696
ms (02:17.718) 137577.606 ms (02:17.578) 276358.752 ms (04:36.359) 276520.727
ms (04:36.521)
3. SCTNC INSERT 26377.274 ms (00:26.377) 25600.189 ms (00:25.600) 45702.630
ms (00:45.703) 45163.510 ms (00:45.164) 99903.299 ms (01:39.903) 100013.004
ms (01:40.013)
3. SCTNC UPDATE 78385.225 ms (01:18.385) 76680.325 ms (01:16.680) 151823.250
ms (02:31.823) 153503.971 ms (02:33.504) 308197.734 ms (05:08.198) 308474.937
ms (05:08.475)
4. MCTNC INSERT 26214.069 ms (00:26.214) 25383.522 ms (00:25.384) 50826.522
ms (00:50.827) 50221.669 ms (00:50.222) 106034.338 ms (01:46.034) 106122.827
ms (01:46.123)
4. MCTNC UPDATE 78423.817 ms (01:18.424) 75154.593 ms (01:15.155) 158885.787
ms (02:38.886) 156530.964 ms (02:36.531) 319721.266 ms (05:19.721) 322385.709
ms (05:22.386)
5. SCTC INSERT 38451.022 ms (00:38.451) 38652.520 ms (00:38.653) 71590.748
ms (01:11.591) 71048.975 ms (01:11.049) 143327.913 ms (02:23.328) 142593.207
ms (02:22.593)
5. SCTC UPDATE 82069.311 ms (01:22.069) 81678.131 ms (01:21.678) 138763.508
ms (02:18.764) 138625.473 ms (02:18.625) 277534.080 ms (04:37.534) 277091.611
ms (04:37.092)
6. MCTC INSERT 36325.730 ms (00:36.326) 35803.368 ms (00:35.803) 73285.204
ms (01:13.285) 72728.371 ms (01:12.728) 142324.859 ms (02:22.325) 144368.335
ms (02:24.368)
6. MCTC UPDATE 73740.729 ms (01:13.741) 73002.511 ms (01:13.003) 141309.859
ms (02:21.310) 139676.173 ms (02:19.676) 278906.647 ms (04:38.907) 279522.408
ms (04:39.522)

All the observation looks good to me,
except for the "Test1" for SCC UPDATE with tuple count(10K/20K), for SCC
INSERT with tuple count(40K)  there was a slightly increse in time taken
incase of "with patch" result. For a better observation, I also have ran
the same "Test 1" for higher tuple count(i.e. 80K), and it also looks fine.

I also have performed the below test with TOAST table objects.
8. pg_dump/restore, pg_upgrade with these
9. Streaming Replication setup
10. Concurrent Transactions

While testing few concurrent transactions I have below query:
-- Concurrent transactions acquire a lock for TOAST option(ALTER TABLE ..
SET STORAGE .. MAIN/EXTERNAL/EXTENDED/ etc)

-- Session 1:
CREATE TABLE a (a_id text PRIMARY KEY);
CREATE TABLE b (b_id text);
INSERT INTO a VALUES ('a'), ('b');
INSERT INTO b VALUES ('a'), ('b'), ('b');

BEGIN;
ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (b_id) REFERENCES a (a_id);
 -- Not Acquiring any lock

-- Session 2:
SELECT * FROM b WHERE b_id = 'a';             -- Shows result

-- Session 1:
ALTER TABLE b ALTER COLUMN b_id SET STORAGE EXTERNAL;        -- Acquire a
lock

-- Session 2:
SELECT * FROM b WHERE b_id = 'a';            -- Hang/Waiting for lock in
session 1

Is this an expected behavior?


-- 

With Regards,
Prabhat Kumar Sahu


Attachments:

  [application/sql] TOAST_table_test.sql (7.3K, ../../CANEvxPopRBwM6RoEdkc4njuFU8PYJ0gKEHtgX1x4_wSu26Nk7g@mail.gmail.com/3-TOAST_table_test.sql)
  download

^ permalink  raw  reply  [nested|flat] 12+ messages in thread

* [PATCH v11 1/2] Move extra code out of the Pre/PostRestoreCommand() block.
@ 2023-02-23 22:27 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 12+ messages in thread

From: Nathan Bossart @ 2023-02-23 22:27 UTC (permalink / raw)

If SIGTERM is received within this block, the startup process will
immediately proc_exit() in the signal handler, so it is inadvisable
to include any more code than is required in this section.  This
change moves the code recently added to this block (see 1b06d7b and
7fed801) to outside of the block.  This ensures that only system()
is called while proc_exit() might be called in the SIGTERM handler,
which is how this code worked from v8.4 to v14.
---
 src/backend/access/transam/xlogarchive.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index f3fb92c8f9..b998cd651c 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -159,20 +159,27 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 			(errmsg_internal("executing restore command \"%s\"",
 							 xlogRestoreCmd)));
 
+	fflush(NULL);
+	pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
 	/*
-	 * Check signals before restore command and reset afterwards.
+	 * PreRestoreCommand() informs the SIGTERM handler for the startup process
+	 * that it should proc_exit() right away.  This is done for the duration of
+	 * the system() call because there isn't a good way to break out while it
+	 * is executing.  Since we might call proc_exit() in a signal handler, it
+	 * is best to put any additional logic before or after the
+	 * PreRestoreCommand()/PostRestoreCommand() section.
 	 */
 	PreRestoreCommand();
 
 	/*
 	 * Copy xlog from archival storage to XLOGDIR
 	 */
-	fflush(NULL);
-	pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
 	rc = system(xlogRestoreCmd);
-	pgstat_report_wait_end();
 
 	PostRestoreCommand();
+
+	pgstat_report_wait_end();
 	pfree(xlogRestoreCmd);
 
 	if (rc == 0)
-- 
2.25.1


--jRHKVT23PllUwdXP
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
	filename="v11-0002-Don-t-proc_exit-in-startup-s-SIGTERM-handler-if-.patch"



^ permalink  raw  reply  [nested|flat] 12+ messages in thread

* [PATCH v6 1/2] Move extra code out of the Pre/PostRestoreCommand() block.
@ 2023-02-23 22:27 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 12+ messages in thread

From: Nathan Bossart @ 2023-02-23 22:27 UTC (permalink / raw)

If SIGTERM is received within this block, the startup process will
immediately proc_exit() in the signal handler, so it is inadvisable
to include any more code than is required in this section.  This
change moves the code recently added to this block (see 1b06d7b and
7fed801) to outside of the block.  This ensures that only system()
is called while proc_exit() might be called in the SIGTERM handler,
which is how this code worked from v8.4 to v14.
---
 src/backend/access/transam/xlogarchive.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index fcc87ff44f..41684418b6 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -159,20 +159,27 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 			(errmsg_internal("executing restore command \"%s\"",
 							 xlogRestoreCmd)));
 
+	fflush(NULL);
+	pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
 	/*
-	 * Check signals before restore command and reset afterwards.
+	 * PreRestoreCommand() informs the SIGTERM handler for the startup process
+	 * that it should proc_exit() right away.  This is done for the duration of
+	 * the system() call because there isn't a good way to break out while it
+	 * is executing.  Since we might call proc_exit() in a signal handler, it
+	 * is best to put any additional logic before or after the
+	 * PreRestoreCommand()/PostRestoreCommand() section.
 	 */
 	PreRestoreCommand();
 
 	/*
 	 * Copy xlog from archival storage to XLOGDIR
 	 */
-	fflush(NULL);
-	pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
 	rc = system(xlogRestoreCmd);
-	pgstat_report_wait_end();
 
 	PostRestoreCommand();
+
+	pgstat_report_wait_end();
 	pfree(xlogRestoreCmd);
 
 	if (rc == 0)
-- 
2.25.1


--wac7ysb48OaltWcw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
	filename="v6-0002-Don-t-proc_exit-in-startup-s-SIGTERM-handler-if-f.patch"



^ permalink  raw  reply  [nested|flat] 12+ messages in thread

* [PATCH v7 1/2] Move extra code out of the Pre/PostRestoreCommand() block.
@ 2023-02-23 22:27 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 12+ messages in thread

From: Nathan Bossart @ 2023-02-23 22:27 UTC (permalink / raw)

If SIGTERM is received within this block, the startup process will
immediately proc_exit() in the signal handler, so it is inadvisable
to include any more code than is required in this section.  This
change moves the code recently added to this block (see 1b06d7b and
7fed801) to outside of the block.  This ensures that only system()
is called while proc_exit() might be called in the SIGTERM handler,
which is how this code worked from v8.4 to v14.
---
 src/backend/access/transam/xlogarchive.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index fcc87ff44f..41684418b6 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -159,20 +159,27 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 			(errmsg_internal("executing restore command \"%s\"",
 							 xlogRestoreCmd)));
 
+	fflush(NULL);
+	pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
 	/*
-	 * Check signals before restore command and reset afterwards.
+	 * PreRestoreCommand() informs the SIGTERM handler for the startup process
+	 * that it should proc_exit() right away.  This is done for the duration of
+	 * the system() call because there isn't a good way to break out while it
+	 * is executing.  Since we might call proc_exit() in a signal handler, it
+	 * is best to put any additional logic before or after the
+	 * PreRestoreCommand()/PostRestoreCommand() section.
 	 */
 	PreRestoreCommand();
 
 	/*
 	 * Copy xlog from archival storage to XLOGDIR
 	 */
-	fflush(NULL);
-	pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
 	rc = system(xlogRestoreCmd);
-	pgstat_report_wait_end();
 
 	PostRestoreCommand();
+
+	pgstat_report_wait_end();
 	pfree(xlogRestoreCmd);
 
 	if (rc == 0)
-- 
2.25.1


--u3/rZRmxL6MmkK24
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
	filename="v7-0002-Don-t-proc_exit-in-startup-s-SIGTERM-handler-if-f.patch"



^ permalink  raw  reply  [nested|flat] 12+ messages in thread

* [PATCH v9 1/2] Move extra code out of the Pre/PostRestoreCommand() block.
@ 2023-02-23 22:27 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 12+ messages in thread

From: Nathan Bossart @ 2023-02-23 22:27 UTC (permalink / raw)

If SIGTERM is received within this block, the startup process will
immediately proc_exit() in the signal handler, so it is inadvisable
to include any more code than is required in this section.  This
change moves the code recently added to this block (see 1b06d7b and
7fed801) to outside of the block.  This ensures that only system()
is called while proc_exit() might be called in the SIGTERM handler,
which is how this code worked from v8.4 to v14.
---
 src/backend/access/transam/xlogarchive.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index fcc87ff44f..41684418b6 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -159,20 +159,27 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 			(errmsg_internal("executing restore command \"%s\"",
 							 xlogRestoreCmd)));
 
+	fflush(NULL);
+	pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
 	/*
-	 * Check signals before restore command and reset afterwards.
+	 * PreRestoreCommand() informs the SIGTERM handler for the startup process
+	 * that it should proc_exit() right away.  This is done for the duration of
+	 * the system() call because there isn't a good way to break out while it
+	 * is executing.  Since we might call proc_exit() in a signal handler, it
+	 * is best to put any additional logic before or after the
+	 * PreRestoreCommand()/PostRestoreCommand() section.
 	 */
 	PreRestoreCommand();
 
 	/*
 	 * Copy xlog from archival storage to XLOGDIR
 	 */
-	fflush(NULL);
-	pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
 	rc = system(xlogRestoreCmd);
-	pgstat_report_wait_end();
 
 	PostRestoreCommand();
+
+	pgstat_report_wait_end();
 	pfree(xlogRestoreCmd);
 
 	if (rc == 0)
-- 
2.25.1


--IS0zKkzwUGydFO0o
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
	filename="v9-0002-Don-t-proc_exit-in-startup-s-SIGTERM-handler-if-f.patch"



^ permalink  raw  reply  [nested|flat] 12+ messages in thread

* [PATCH v13 1/7] Move extra code out of the Pre/PostRestoreCommand() block.
@ 2023-02-23 22:27 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 12+ messages in thread

From: Nathan Bossart @ 2023-02-23 22:27 UTC (permalink / raw)

If SIGTERM is received within this block, the startup process will
immediately proc_exit() in the signal handler, so it is inadvisable
to include any more code than is required in this section.  This
change moves the code recently added to this block (see 1b06d7b and
7fed801) to outside of the block.  This ensures that only system()
is called while proc_exit() might be called in the SIGTERM handler,
which is how this code worked from v8.4 to v14.
---
 src/backend/access/transam/xlogarchive.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index fcc87ff44f..41684418b6 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -159,20 +159,27 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 			(errmsg_internal("executing restore command \"%s\"",
 							 xlogRestoreCmd)));
 
+	fflush(NULL);
+	pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
 	/*
-	 * Check signals before restore command and reset afterwards.
+	 * PreRestoreCommand() informs the SIGTERM handler for the startup process
+	 * that it should proc_exit() right away.  This is done for the duration of
+	 * the system() call because there isn't a good way to break out while it
+	 * is executing.  Since we might call proc_exit() in a signal handler, it
+	 * is best to put any additional logic before or after the
+	 * PreRestoreCommand()/PostRestoreCommand() section.
 	 */
 	PreRestoreCommand();
 
 	/*
 	 * Copy xlog from archival storage to XLOGDIR
 	 */
-	fflush(NULL);
-	pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
 	rc = system(xlogRestoreCmd);
-	pgstat_report_wait_end();
 
 	PostRestoreCommand();
+
+	pgstat_report_wait_end();
 	pfree(xlogRestoreCmd);
 
 	if (rc == 0)
-- 
2.25.1


--5vNYLRcllDrimb99
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
	filename="v13-0002-Don-t-proc_exit-in-startup-s-SIGTERM-handler-if-.patch"



^ permalink  raw  reply  [nested|flat] 12+ messages in thread

* [PATCH v15 1/7] Move extra code out of the Pre/PostRestoreCommand() block.
@ 2023-02-23 22:27 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 12+ messages in thread

From: Nathan Bossart @ 2023-02-23 22:27 UTC (permalink / raw)

If SIGTERM is received within this block, the startup process will
immediately proc_exit() in the signal handler, so it is inadvisable
to include any more code than is required in this section.  This
change moves the code recently added to this block (see 1b06d7b and
7fed801) to outside of the block.  This ensures that only system()
is called while proc_exit() might be called in the SIGTERM handler,
which is how this code worked from v8.4 to v14.
---
 src/backend/access/transam/xlogarchive.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index f3fb92c8f9..b998cd651c 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -159,20 +159,27 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 			(errmsg_internal("executing restore command \"%s\"",
 							 xlogRestoreCmd)));
 
+	fflush(NULL);
+	pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
 	/*
-	 * Check signals before restore command and reset afterwards.
+	 * PreRestoreCommand() informs the SIGTERM handler for the startup process
+	 * that it should proc_exit() right away.  This is done for the duration of
+	 * the system() call because there isn't a good way to break out while it
+	 * is executing.  Since we might call proc_exit() in a signal handler, it
+	 * is best to put any additional logic before or after the
+	 * PreRestoreCommand()/PostRestoreCommand() section.
 	 */
 	PreRestoreCommand();
 
 	/*
 	 * Copy xlog from archival storage to XLOGDIR
 	 */
-	fflush(NULL);
-	pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
 	rc = system(xlogRestoreCmd);
-	pgstat_report_wait_end();
 
 	PostRestoreCommand();
+
+	pgstat_report_wait_end();
 	pfree(xlogRestoreCmd);
 
 	if (rc == 0)
-- 
2.25.1


--k+w/mQv8wyuph6w0
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
	filename="v15-0002-Don-t-proc_exit-in-startup-s-SIGTERM-handler-if-.patch"



^ permalink  raw  reply  [nested|flat] 12+ messages in thread

* [PATCH v10 1/2] Move extra code out of the Pre/PostRestoreCommand() block.
@ 2023-02-23 22:27 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 12+ messages in thread

From: Nathan Bossart @ 2023-02-23 22:27 UTC (permalink / raw)

If SIGTERM is received within this block, the startup process will
immediately proc_exit() in the signal handler, so it is inadvisable
to include any more code than is required in this section.  This
change moves the code recently added to this block (see 1b06d7b and
7fed801) to outside of the block.  This ensures that only system()
is called while proc_exit() might be called in the SIGTERM handler,
which is how this code worked from v8.4 to v14.
---
 src/backend/access/transam/xlogarchive.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index fcc87ff44f..41684418b6 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -159,20 +159,27 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 			(errmsg_internal("executing restore command \"%s\"",
 							 xlogRestoreCmd)));
 
+	fflush(NULL);
+	pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
 	/*
-	 * Check signals before restore command and reset afterwards.
+	 * PreRestoreCommand() informs the SIGTERM handler for the startup process
+	 * that it should proc_exit() right away.  This is done for the duration of
+	 * the system() call because there isn't a good way to break out while it
+	 * is executing.  Since we might call proc_exit() in a signal handler, it
+	 * is best to put any additional logic before or after the
+	 * PreRestoreCommand()/PostRestoreCommand() section.
 	 */
 	PreRestoreCommand();
 
 	/*
 	 * Copy xlog from archival storage to XLOGDIR
 	 */
-	fflush(NULL);
-	pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
 	rc = system(xlogRestoreCmd);
-	pgstat_report_wait_end();
 
 	PostRestoreCommand();
+
+	pgstat_report_wait_end();
 	pfree(xlogRestoreCmd);
 
 	if (rc == 0)
-- 
2.25.1


--Q68bSM7Ycu6FN28Q
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
	filename="v10-0002-Don-t-proc_exit-in-startup-s-SIGTERM-handler-if-.patch"



^ permalink  raw  reply  [nested|flat] 12+ messages in thread

* [PATCH v14 1/7] Move extra code out of the Pre/PostRestoreCommand() block.
@ 2023-02-23 22:27 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 12+ messages in thread

From: Nathan Bossart @ 2023-02-23 22:27 UTC (permalink / raw)

If SIGTERM is received within this block, the startup process will
immediately proc_exit() in the signal handler, so it is inadvisable
to include any more code than is required in this section.  This
change moves the code recently added to this block (see 1b06d7b and
7fed801) to outside of the block.  This ensures that only system()
is called while proc_exit() might be called in the SIGTERM handler,
which is how this code worked from v8.4 to v14.
---
 src/backend/access/transam/xlogarchive.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index fcc87ff44f..41684418b6 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -159,20 +159,27 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 			(errmsg_internal("executing restore command \"%s\"",
 							 xlogRestoreCmd)));
 
+	fflush(NULL);
+	pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
 	/*
-	 * Check signals before restore command and reset afterwards.
+	 * PreRestoreCommand() informs the SIGTERM handler for the startup process
+	 * that it should proc_exit() right away.  This is done for the duration of
+	 * the system() call because there isn't a good way to break out while it
+	 * is executing.  Since we might call proc_exit() in a signal handler, it
+	 * is best to put any additional logic before or after the
+	 * PreRestoreCommand()/PostRestoreCommand() section.
 	 */
 	PreRestoreCommand();
 
 	/*
 	 * Copy xlog from archival storage to XLOGDIR
 	 */
-	fflush(NULL);
-	pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
 	rc = system(xlogRestoreCmd);
-	pgstat_report_wait_end();
 
 	PostRestoreCommand();
+
+	pgstat_report_wait_end();
 	pfree(xlogRestoreCmd);
 
 	if (rc == 0)
-- 
2.25.1


--IS0zKkzwUGydFO0o
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
	filename="v14-0002-Don-t-proc_exit-in-startup-s-SIGTERM-handler-if-.patch"



^ permalink  raw  reply  [nested|flat] 12+ messages in thread

* [PATCH v8 1/2] Move extra code out of the Pre/PostRestoreCommand() block.
@ 2023-02-23 22:27 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 12+ messages in thread

From: Nathan Bossart @ 2023-02-23 22:27 UTC (permalink / raw)

If SIGTERM is received within this block, the startup process will
immediately proc_exit() in the signal handler, so it is inadvisable
to include any more code than is required in this section.  This
change moves the code recently added to this block (see 1b06d7b and
7fed801) to outside of the block.  This ensures that only system()
is called while proc_exit() might be called in the SIGTERM handler,
which is how this code worked from v8.4 to v14.
---
 src/backend/access/transam/xlogarchive.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index fcc87ff44f..41684418b6 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -159,20 +159,27 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 			(errmsg_internal("executing restore command \"%s\"",
 							 xlogRestoreCmd)));
 
+	fflush(NULL);
+	pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
 	/*
-	 * Check signals before restore command and reset afterwards.
+	 * PreRestoreCommand() informs the SIGTERM handler for the startup process
+	 * that it should proc_exit() right away.  This is done for the duration of
+	 * the system() call because there isn't a good way to break out while it
+	 * is executing.  Since we might call proc_exit() in a signal handler, it
+	 * is best to put any additional logic before or after the
+	 * PreRestoreCommand()/PostRestoreCommand() section.
 	 */
 	PreRestoreCommand();
 
 	/*
 	 * Copy xlog from archival storage to XLOGDIR
 	 */
-	fflush(NULL);
-	pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
 	rc = system(xlogRestoreCmd);
-	pgstat_report_wait_end();
 
 	PostRestoreCommand();
+
+	pgstat_report_wait_end();
 	pfree(xlogRestoreCmd);
 
 	if (rc == 0)
-- 
2.25.1


--FCuugMFkClbJLl1L
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
	filename="v8-0002-Don-t-proc_exit-in-startup-s-SIGTERM-handler-if-f.patch"



^ permalink  raw  reply  [nested|flat] 12+ messages in thread

* Re: Built-in Raft replication
@ 2025-04-17 09:50 Yura Sokolov <[email protected]>
  0 siblings, 0 replies; 12+ messages in thread

From: Yura Sokolov @ 2025-04-17 09:50 UTC (permalink / raw)
  To: Hannu Krosing <[email protected]>; Tom Lane <[email protected]>; +Cc: Andrey Borodin <[email protected]>; Konstantin Osipov <[email protected]>; Greg Sabino Mullane <[email protected]>; Nikolay Samokhvalov <[email protected]>; [email protected]

17.04.2025 00:24, Hannu Krosing пишет:
> But regarding weather to use RAFT I would just define a "coordinator
> API" and leave it up to the specific coordinator/consensus extension
> to decide how the consensus is achieved
> 
> 
> So to summarize:
> 
> # Core should provide
> 
> - way tomove to new node,
>   - for switchover a WAL-based switchover
>   - for failover something similar which also writes the WAL record so
> all histories are synced
> - a libpq message informing clients about "new write head node"
> - node IDs and more general c;luster-awareness inside the PostgreSQL
> node (I had a shoutout about this in a recent pgconf.dev unconference
> talk)
> - a new write-node field in WAL to track write head movement
> - API for a joining node to find out which cluster it joins and the
> switchover history
>   - in WAL it is always switchover, maybe with some info saying that
> it was a forces switchover because we lost old write head
>   - if some lost node comes back it may need to rewind or
> re-initialize if it finds out it had been following a lost timeline
> that is not fully part of

Great summary!

I'd like to add:
- ability to switch to specific Timeline (to match with Raft Term).
  Timeline numbering is really better to be coordinated.
  It is not so unusual when different replicas become leader in the same
Timeline with current mechanism, and it should be forbidden at any cost.

- remove of forced checkpoint at replica promotion, or make it background
instead of blocking.
  It is really huge issue reported by our support.

- as possible improvement, WAL record of "certainly replicated LSN" - ie,
LSN known to settled in quorum's WALs. RAFT uses it as an optimisation of
server start/leader promotion: it reduces amount of log to search last such
point.

> NOTE: switchovers in WAL would be very similar to timeline changes. I
> am not sure how much extra info is needed there.
> 
> # Extension can provide
> - agreeing on new leader node in case of failover
>   - protocol can be RAFT, PAXOS or "the DBA says so" :)
> - sharing fresh info about current leader and switch timelines (though
> this should more likely be in core)
> - anything else ???
> 
> # external apps is (likely?) needed for
> - setting up cluster, provisioning machines / VMs
> - setting up networking
> - starting PostgreSQL servers.
> - spinning up and down clients,
> - communicating current leader and replica set (could be done by DNS
> with agreed conventions)

-- 
regards
Yura Sokolov aka funny-falcon






^ permalink  raw  reply  [nested|flat] 12+ messages in thread


end of thread, other threads:[~2025-04-17 09:50 UTC | newest]

Thread overview: 12+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2019-06-11 16:17 Re: tableam vs. TOAST Robert Haas <[email protected]>
2019-06-25 06:19 ` Prabhat Sahu <[email protected]>
2023-02-23 22:27 [PATCH v11 1/2] Move extra code out of the Pre/PostRestoreCommand() block. Nathan Bossart <[email protected]>
2023-02-23 22:27 [PATCH v6 1/2] Move extra code out of the Pre/PostRestoreCommand() block. Nathan Bossart <[email protected]>
2023-02-23 22:27 [PATCH v7 1/2] Move extra code out of the Pre/PostRestoreCommand() block. Nathan Bossart <[email protected]>
2023-02-23 22:27 [PATCH v9 1/2] Move extra code out of the Pre/PostRestoreCommand() block. Nathan Bossart <[email protected]>
2023-02-23 22:27 [PATCH v13 1/7] Move extra code out of the Pre/PostRestoreCommand() block. Nathan Bossart <[email protected]>
2023-02-23 22:27 [PATCH v15 1/7] Move extra code out of the Pre/PostRestoreCommand() block. Nathan Bossart <[email protected]>
2023-02-23 22:27 [PATCH v10 1/2] Move extra code out of the Pre/PostRestoreCommand() block. Nathan Bossart <[email protected]>
2023-02-23 22:27 [PATCH v14 1/7] Move extra code out of the Pre/PostRestoreCommand() block. Nathan Bossart <[email protected]>
2023-02-23 22:27 [PATCH v8 1/2] Move extra code out of the Pre/PostRestoreCommand() block. Nathan Bossart <[email protected]>
2025-04-17 09:50 Re: Built-in Raft replication Yura Sokolov <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox