tableam vs. TOAST

Started by Robert Haasover 6 years ago50 messages
#1Robert Haas
robertmhaas@gmail.com
4 attachment(s)

In a nearby thread[1]/messages/by-id/CALfoeitE+P8UGii8=BsGQLpHch2EZWJhq4M+D-jfaj8YCa_FSw@mail.gmail.com, Ashwin Agrawal complained that there is no way
for a table AM to get rid the TOAST table that the core system thinks
should be created. To that I added a litany of complaints of my own,
including...

- the core system decides whether or not a TOAST table is needed based
on criteria that are very much heap-specific,
- the code for reading and writing values stored in a TOAST table is
heap-specific, and
- the core system assumes that you want to use the same table AM for
the main table and the toast table, but you might not (e.g. you might
want to use the regular old heap for the latter).

Attached as a series of patches which try to improve things in this
area. Except possibly for 0001, this is v13 material; see discussion
on the other thread. These likely need some additional work, but I've
done enough with them that I thought it would be worth publishing them
at this stage, because it seems that I'm not the only one thinking
about the problems that exist in this general area. Here is an
overview:

0001 moves the needs_toast_table() calculation below the table AM
layer. That allows a table AM to decide for itself whether it wants a
TOAST table. The most obvious way in which a table AM might want to
be different from what core expects is to decide that the answer is
always "no," which it can do if it has some other method of storing
large values or doesn't wish to support them. Another possibility is
that it wants logic that is basically similar to the heap, but with a
different size threshold because its tuple format is different. There
are probably other possibilities.

0002 breaks tuptoaster.c into three separate files. It just does code
movement; no functional changes. The three pieces are detoast.c,
which handles detoasting of toast values and inspection of the sizes
of toasted datums; heaptoast.c, which keeps all the functions that are
intrinsically heap-specific; and toast_internals.c, which is intended
to have a very limited audience. A nice fringe benefit of this stuff
is that a lot of other files that current have to include tuptoaster.h
and thus htup_details.h no longer do.

0003 creates a new file toast_helper.c which is intended to help table
AMs implement insertion and deletion of toast table rows. Most of the
AM-independent logic from the functions remaining in heaptoast.c is
moved to this file. This leaves about ~600 of the original ~2400
lines from tuptoaster.c as heap-specific logic, but a new heap AM
actually wouldn't need all of that stuff, because some of the logic
here is in support of stuff like record types, which use HeapTuple
internally and will continue to do so even if those record types are
stored in some other kind of table.

0004 allows TOAST tables to be implemented using a table AM other than
heap. In a certain sense this is the opposite of 0003. 0003 is
intended to help people who are implementing a new kind of main table,
whereas 0004 is intended to help people implementing a new kind of
TOAST table. It teaches the code that inserts, deletes, and retrieves
TOAST row to use slots, and it makes some efficiency improvements in
the hopes of offsetting any performance loss from so doing. See
commit message and/or patch for full details.

I believe that with all of these changes it should be pretty
straightforward for a table AM that wants to use itself to store TOAST
data to do so, or to delegate that task back to say the regular heap.
I haven't really validated that yet, but plan to do so.

In addition to what's in this patch set, I believe that we should
probably rename some of these functions and macros, so that the
heap-specific ones have heap-specific names and the generic ones
don't, but I haven't gone through all of that yet. The existing
patches try to choose good names for the new things they add, but they
don't rename any of the existing stuff. I also think we should
consider removing TOAST_MAX_CHUNK_SIZE from the control file, both
because I'm not sure anybody's really using the ability to vary that
for anything and because that solution doesn't seem entirely sensible
in a world of multiple AMs. However, that is a debatable change, so
maybe others will disagree.

[1]: /messages/by-id/CALfoeitE+P8UGii8=BsGQLpHch2EZWJhq4M+D-jfaj8YCa_FSw@mail.gmail.com

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

Attachments:

v1-0004-Allow-TOAST-tables-to-be-implemented-using-table-.patchapplication/octet-stream; name=v1-0004-Allow-TOAST-tables-to-be-implemented-using-table-.patchDownload
From e8e6bf279ee25cbd183ca60c5b719974f7008be0 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 17 May 2019 09:23:45 -0400
Subject: [PATCH v1 4/4] Allow TOAST tables to be implemented using table AMs
 other than heap.

These changes should make it possible to store data in a TOAST table
that uses some access method other than 'heap'.  There might be some
performance impact from going through the table AM interface here
rather than hand-rolling everything, but we attempt to mitigate that
by restructuring things so that we don't open and close the toast
table and indexes multiple times per tuple.
---
 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    |  18 ++-
 src/backend/access/heap/heaptoast.c         |  19 ++-
 src/backend/access/index/genam.c            |  20 +++
 src/backend/access/table/toast_helper.c     | 107 ++++++++++++++---
 src/include/access/genam.h                  |   5 +-
 src/include/access/heapam.h                 |   3 +-
 src/include/access/heaptoast.h              |   3 +-
 src/include/access/tableam.h                |  21 ++++
 src/include/access/toast_helper.h           |  18 ++-
 src/include/access/toast_internals.h        |  13 +-
 13 files changed, 273 insertions(+), 147 deletions(-)

diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c
index 57ca8afe2a..b89cf7d190 100644
--- a/src/backend/access/common/detoast.c
+++ b/src/backend/access/common/detoast.c
@@ -16,10 +16,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"
@@ -304,7 +305,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;
@@ -313,11 +314,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))
@@ -327,7 +328,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);
 
@@ -342,6 +342,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,
@@ -368,15 +371,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;
@@ -408,19 +411,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));
@@ -435,7 +438,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);
 
@@ -499,6 +502,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))
@@ -514,7 +518,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)
 	{
@@ -532,19 +535,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,
@@ -633,19 +639,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));
@@ -668,7 +674,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..a1d7f3ed66 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_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_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_complete_speculative(toastrel, slot, specToken, false);
 		else
-			simple_heap_delete(toastrel, &toasttup->t_self);
+			simple_table_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 88f165df23..a792f4db74 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2808,7 +2808,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
@@ -5539,7 +5539,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;
@@ -5648,7 +5648,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 9ef7d29035..b6e30d5cb1 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"
@@ -277,7 +278,7 @@ heapam_tuple_insert_speculative(Relation relation, TupleTableSlot *slot,
 
 static void
 heapam_tuple_complete_speculative(Relation relation, TupleTableSlot *slot,
-								  uint32 spekToken, bool succeeded)
+								  uint32 specToken, bool succeeded)
 {
 	bool		shouldFree = true;
 	HeapTuple	tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
@@ -286,7 +287,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);
@@ -2032,6 +2033,15 @@ heapam_needs_toast_table(Relation rel)
 	return (tuple_length > TOAST_TUPLE_THRESHOLD);
 }
 
+/*
+ * TOAST tables for heap relations are just heap relations.
+ */
+static Oid
+heapam_get_toast_table_am(Relation rel)
+{
+	return rel->rd_rel->relam;
+}
+
 
 /* ------------------------------------------------------------------------
  * Planner related callbacks for the heap AM
@@ -2616,11 +2626,13 @@ static const TableAmRoutine heapam_methods = {
 	.relation_estimate_size = heapam_estimate_rel_size,
 
 	.needs_toast_table = heapam_needs_toast_table,
+	.get_toast_table_am = heapam_get_toast_table_am,
+	.toast_max_chunk_size = TOAST_MAX_CHUNK_SIZE,
 
 	.scan_bitmap_next_block = heapam_scan_bitmap_next_block,
 	.scan_bitmap_next_tuple = heapam_scan_bitmap_next_tuple,
 	.scan_sample_next_block = heapam_scan_sample_next_block,
-	.scan_sample_next_tuple = heapam_scan_sample_next_tuple
+	.scan_sample_next_tuple = heapam_scan_sample_next_tuple,
 };
 
 
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/include/access/genam.h b/src/include/access/genam.h
index 9717183ef2..38327bf977 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 77e5e603b0..6b6f7623c7 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -151,7 +151,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 046aac7620..600b226826 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 ac0913c579..c29dbe3615 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -554,6 +554,27 @@ typedef struct TableAmRoutine
 	 */
 	bool	    (*needs_toast_table) (Relation rel);
 
+	/*
+	 * This callback should return the OID of the table AM that implements
+	 * TOAST tables for this AM.  If the needs_toast_table always returns
+	 * false, this callback is not required.
+	 */
+	Oid		    (*get_toast_table_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.
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)

v1-0001-tableam-Move-heap-specific-logic-from-needs_toast.patchapplication/octet-stream; name=v1-0001-tableam-Move-heap-specific-logic-from-needs_toast.patchDownload
From 6ab459b23824e7ff270be360eea0163f4dc16b3a Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 17 May 2019 16:01:47 -0400
Subject: [PATCH v1 1/4] tableam: Move heap-specific logic from
 needs_toast_table below tableam.

This allows table AMs to completely suppress TOAST table creation, or
to modify the conditions under which they are created.
---
 src/backend/access/heap/heapam_handler.c | 59 ++++++++++++++++++++++++
 src/backend/catalog/toasting.c           | 50 ++------------------
 src/include/access/tableam.h             | 30 ++++++++++++
 3 files changed, 92 insertions(+), 47 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 00505ec3f4..b73e9cd6f1 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -29,6 +29,7 @@
 #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"
@@ -1975,6 +1976,62 @@ heapam_scan_get_blocks_done(HeapScanDesc hscan)
 }
 
 
+/* ------------------------------------------------------------------------
+ * TOAST related callbacks for the heap AM
+ * ------------------------------------------------------------------------
+ */
+
+/*
+ * Check to see whether the table needs a TOAST table.  It does only if
+ * (1) there are any toastable attributes, and (2) the maximum length
+ * of a tuple could exceed TOAST_TUPLE_THRESHOLD.  (We don't want to
+ * create a toast table for something like "f1 varchar(20)".)
+ */
+static bool
+heapam_needs_toast_table(Relation rel)
+{
+	int32		data_length = 0;
+	bool		maxlength_unknown = false;
+	bool		has_toastable_attrs = false;
+	TupleDesc	tupdesc = rel->rd_att;
+	int32		tuple_length;
+	int			i;
+
+	for (i = 0; i < tupdesc->natts; i++)
+	{
+		Form_pg_attribute att = TupleDescAttr(tupdesc, i);
+
+		if (att->attisdropped)
+			continue;
+		data_length = att_align_nominal(data_length, att->attalign);
+		if (att->attlen > 0)
+		{
+			/* Fixed-length types are never toastable */
+			data_length += att->attlen;
+		}
+		else
+		{
+			int32		maxlen = type_maximum_size(att->atttypid,
+												   att->atttypmod);
+
+			if (maxlen < 0)
+				maxlength_unknown = true;
+			else
+				data_length += maxlen;
+			if (att->attstorage != 'p')
+				has_toastable_attrs = true;
+		}
+	}
+	if (!has_toastable_attrs)
+		return false;			/* nothing to toast? */
+	if (maxlength_unknown)
+		return true;			/* any unlimited-length attrs? */
+	tuple_length = MAXALIGN(SizeofHeapTupleHeader +
+							BITMAPLEN(tupdesc->natts)) +
+		MAXALIGN(data_length);
+	return (tuple_length > TOAST_TUPLE_THRESHOLD);
+}
+
 
 /* ------------------------------------------------------------------------
  * Planner related callbacks for the heap AM
@@ -2558,6 +2615,8 @@ static const TableAmRoutine heapam_methods = {
 
 	.relation_estimate_size = heapam_estimate_rel_size,
 
+	.needs_toast_table = heapam_needs_toast_table,
+
 	.scan_bitmap_next_block = heapam_scan_bitmap_next_block,
 	.scan_bitmap_next_tuple = heapam_scan_bitmap_next_tuple,
 	.scan_sample_next_block = heapam_scan_sample_next_block,
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 2276d3c5d3..be0a397d9d 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -15,7 +15,6 @@
 #include "postgres.h"
 
 #include "access/heapam.h"
-#include "access/tuptoaster.h"
 #include "access/xact.h"
 #include "catalog/binary_upgrade.h"
 #include "catalog/catalog.h"
@@ -386,21 +385,11 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 }
 
 /*
- * Check to see whether the table needs a TOAST table.  It does only if
- * (1) there are any toastable attributes, and (2) the maximum length
- * of a tuple could exceed TOAST_TUPLE_THRESHOLD.  (We don't want to
- * create a toast table for something like "f1 varchar(20)".)
+ * Check to see whether the table needs a TOAST table.
  */
 static bool
 needs_toast_table(Relation rel)
 {
-	int32		data_length = 0;
-	bool		maxlength_unknown = false;
-	bool		has_toastable_attrs = false;
-	TupleDesc	tupdesc;
-	int32		tuple_length;
-	int			i;
-
 	/*
 	 * No need to create a TOAST table for partitioned tables.
 	 */
@@ -423,39 +412,6 @@ needs_toast_table(Relation rel)
 	if (IsCatalogRelation(rel) && !IsBootstrapProcessingMode())
 		return false;
 
-	tupdesc = rel->rd_att;
-
-	for (i = 0; i < tupdesc->natts; i++)
-	{
-		Form_pg_attribute att = TupleDescAttr(tupdesc, i);
-
-		if (att->attisdropped)
-			continue;
-		data_length = att_align_nominal(data_length, att->attalign);
-		if (att->attlen > 0)
-		{
-			/* Fixed-length types are never toastable */
-			data_length += att->attlen;
-		}
-		else
-		{
-			int32		maxlen = type_maximum_size(att->atttypid,
-												   att->atttypmod);
-
-			if (maxlen < 0)
-				maxlength_unknown = true;
-			else
-				data_length += maxlen;
-			if (att->attstorage != 'p')
-				has_toastable_attrs = true;
-		}
-	}
-	if (!has_toastable_attrs)
-		return false;			/* nothing to toast? */
-	if (maxlength_unknown)
-		return true;			/* any unlimited-length attrs? */
-	tuple_length = MAXALIGN(SizeofHeapTupleHeader +
-							BITMAPLEN(tupdesc->natts)) +
-		MAXALIGN(data_length);
-	return (tuple_length > TOAST_TUPLE_THRESHOLD);
+	/* Otherwise, let the AM decide. */
+	return table_needs_toast_table(rel);
 }
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index ebfa0d5185..ac0913c579 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -540,6 +540,21 @@ typedef struct TableAmRoutine
 										struct ValidateIndexState *state);
 
 
+	/* ------------------------------------------------------------------------
+	 * TOAST support.
+	 * ------------------------------------------------------------------------
+	 */
+
+	/*
+	 * This callback should return true if the relation requires a TOAST table
+	 * and false if it does not.  It may wish to examine the relation's
+	 * tuple descriptor before making a decision, but if it uses some other
+	 * method of storing large values (or if it does not support them) it can
+	 * simply return false.
+	 */
+	bool	    (*needs_toast_table) (Relation rel);
+
+
 	/* ------------------------------------------------------------------------
 	 * Planner related functions.
 	 * ------------------------------------------------------------------------
@@ -1503,6 +1518,21 @@ table_index_validate_scan(Relation heap_rel,
 }
 
 
+/* ----------------------------------------------------------------------------
+ * TOAST
+ * ----------------------------------------------------------------------------
+ */
+
+/*
+ * table_needs_toast_table - does this relation need a toast table?
+ */
+static inline bool
+table_needs_toast_table(Relation rel)
+{
+	return rel->rd_tableam->needs_toast_table(rel);
+}
+
+
 /* ----------------------------------------------------------------------------
  * Planner related functionality
  * ----------------------------------------------------------------------------
-- 
2.17.2 (Apple Git-113)

v1-0003-Create-an-API-for-inserting-and-deleting-rows-in-.patchapplication/octet-stream; name=v1-0003-Create-an-API-for-inserting-and-deleting-rows-in-.patchDownload
From 68235af6856316fdcc2733393101eed668bfa2bd Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Tue, 14 May 2019 17:11:28 -0400
Subject: [PATCH v1 3/4] 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)

v1-0002-Split-tuptoaster.c-into-three-separate-files.patchapplication/octet-stream; name=v1-0002-Split-tuptoaster.c-into-three-separate-files.patchDownload
From b7fae0056cfded2c4799c4a798ffa1e443dc2fdf Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Wed, 15 May 2019 13:33:16 -0400
Subject: [PATCH v1 2/4] 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           |  860 ++++++
 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, 2603 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 c4bac87e80..42411a5379 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..57ca8afe2a
--- /dev/null
+++ b/src/backend/access/common/detoast.c
@@ -0,0 +1,860 @@
+/*-------------------------------------------------------------------------
+ *
+ * detoast.c
+ *	  Support routines for external and compressed storage of
+ *	  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 783b04a3cb..a2197a4b43 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 cfbabb5265..9d78d6ccc9 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 ec9853603f..88f165df23 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 b73e9cd6f1..9ef7d29035 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 bce4274362..9f90830ad5 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 74e957abb7..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 c00b63c751..3bf0875cdd 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 e0ec62c88c..a16fc27cdc 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 3ee7056047..c79a300899 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 a018925d4e..7c35604ee2 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 55d1669db0..41d6e9de18 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 65f86ad73d..7ad5b0610c 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 ac0ae52ecf..d99a10e894 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 a03b7f7860..c7f8bb4ef2 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 9971abd71f..4fdd4d489e 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 4003631d8f..cffb009b9e 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 f82ce92ce3..d190e5f956 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 d05930bc4c..1060a4f50d 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 ead8b371a7..5d0ace0721 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 4bfefffbf3..046aac7620 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 1b1d87e59a..46ced56df0 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 ad3e803899..e364375da9 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)

#2Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#1)
3 attachment(s)
Re: tableam vs. TOAST

Updated and rebased patches attached.

On Fri, May 17, 2019 at 5:21 PM Robert Haas <robertmhaas@gmail.com> wrote:

0001 moves the needs_toast_table() calculation below the table AM
layer. That allows a table AM to decide for itself whether it wants a
TOAST table. The most obvious way in which a table AM might want to
be different from what core expects is to decide that the answer is
always "no," which it can do if it has some other method of storing
large values or doesn't wish to support them. Another possibility is
that it wants logic that is basically similar to the heap, but with a
different size threshold because its tuple format is different. There
are probably other possibilities.

This was committed as 1171d7d58545f26a402f76a05936d572bf29d53b per
discussion on another thread.

0002 breaks tuptoaster.c into three separate files. It just does code
movement; no functional changes. The three pieces are detoast.c,
which handles detoasting of toast values and inspection of the sizes
of toasted datums; heaptoast.c, which keeps all the functions that are
intrinsically heap-specific; and toast_internals.c, which is intended
to have a very limited audience. A nice fringe benefit of this stuff
is that a lot of other files that current have to include tuptoaster.h
and thus htup_details.h no longer do.

Now 0001. No changes.

0003 creates a new file toast_helper.c which is intended to help table
AMs implement insertion and deletion of toast table rows. Most of the
AM-independent logic from the functions remaining in heaptoast.c is
moved to this file. This leaves about ~600 of the original ~2400
lines from tuptoaster.c as heap-specific logic, but a new heap AM
actually wouldn't need all of that stuff, because some of the logic
here is in support of stuff like record types, which use HeapTuple
internally and will continue to do so even if those record types are
stored in some other kind of table.

Now 0002. No changes.

0004 allows TOAST tables to be implemented using a table AM other than
heap. In a certain sense this is the opposite of 0003. 0003 is
intended to help people who are implementing a new kind of main table,
whereas 0004 is intended to help people implementing a new kind of
TOAST table. It teaches the code that inserts, deletes, and retrieves
TOAST row to use slots, and it makes some efficiency improvements in
the hopes of offsetting any performance loss from so doing. See
commit message and/or patch for full details.

Now 0003. Some brain fade repaired.

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

Attachments:

v2-0003-Allow-TOAST-tables-to-be-implemented-using-table-.patchapplication/octet-stream; name=v2-0003-Allow-TOAST-tables-to-be-implemented-using-table-.patchDownload
From 8f42139d1969ebdad21d28833080b684c58ead17 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 17 May 2019 09:23:45 -0400
Subject: [PATCH v2 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    |  16 ++-
 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, 283 insertions(+), 147 deletions(-)

diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c
index 57ca8afe2a..b89cf7d190 100644
--- a/src/backend/access/common/detoast.c
+++ b/src/backend/access/common/detoast.c
@@ -16,10 +16,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"
@@ -304,7 +305,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;
@@ -313,11 +314,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))
@@ -327,7 +328,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);
 
@@ -342,6 +342,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,
@@ -368,15 +371,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;
@@ -408,19 +411,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));
@@ -435,7 +438,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);
 
@@ -499,6 +502,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))
@@ -514,7 +518,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)
 	{
@@ -532,19 +535,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,
@@ -633,19 +639,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));
@@ -668,7 +674,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..a1d7f3ed66 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_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_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_complete_speculative(toastrel, slot, specToken, false);
 		else
-			simple_heap_delete(toastrel, &toasttup->t_self);
+			simple_table_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 469c27ce48..6655b92855 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2818,7 +2818,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
@@ -5549,7 +5549,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;
@@ -5658,7 +5658,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 c7d5ff3850..c88442adee 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"
@@ -286,7 +287,7 @@ heapam_tuple_insert_speculative(Relation relation, TupleTableSlot *slot,
 
 static void
 heapam_tuple_complete_speculative(Relation relation, TupleTableSlot *slot,
-								  uint32 spekToken, bool succeeded)
+								  uint32 specToken, bool succeeded)
 {
 	bool		shouldFree = true;
 	HeapTuple	tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
@@ -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);
@@ -2061,6 +2062,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
@@ -2645,6 +2655,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 cf20ddb4e9..d6033331aa 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 9717183ef2..38327bf977 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 62aaa08eff..970a37e993 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -145,7 +145,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 046aac7620..600b226826 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 53b58c51da..2835beb39f 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -582,6 +582,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)

v2-0002-Create-an-API-for-inserting-and-deleting-rows-in-.patchapplication/octet-stream; name=v2-0002-Create-an-API-for-inserting-and-deleting-rows-in-.patchDownload
From 975b88d0018949805a4309d6af8820ab43620377 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Tue, 14 May 2019 17:11:28 -0400
Subject: [PATCH v2 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)

v2-0001-Split-tuptoaster.c-into-three-separate-files.patchapplication/octet-stream; name=v2-0001-Split-tuptoaster.c-into-three-separate-files.patchDownload
From 856c0a1040bc227e4afaa7a7a3372b6671473b75 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Wed, 15 May 2019 13:33:16 -0400
Subject: [PATCH v2 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           |  860 ++++++
 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, 2603 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 c4bac87e80..42411a5379 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..57ca8afe2a
--- /dev/null
+++ b/src/backend/access/common/detoast.c
@@ -0,0 +1,860 @@
+/*-------------------------------------------------------------------------
+ *
+ * detoast.c
+ *	  Support routines for external and compressed storage of
+ *	  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 783b04a3cb..a2197a4b43 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 cfbabb5265..9d78d6ccc9 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 19d2c529d8..469c27ce48 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 56b2abda5f..c7d5ff3850 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 bce4274362..9f90830ad5 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 74e957abb7..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 527522f165..dd57b78190 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 e0ec62c88c..a16fc27cdc 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 3ee7056047..c79a300899 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 a018925d4e..7c35604ee2 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 55d1669db0..41d6e9de18 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 65f86ad73d..7ad5b0610c 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 ac0ae52ecf..d99a10e894 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 a03b7f7860..c7f8bb4ef2 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 9971abd71f..4fdd4d489e 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 4003631d8f..cffb009b9e 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 f82ce92ce3..d190e5f956 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 d05930bc4c..1060a4f50d 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 ead8b371a7..5d0ace0721 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 4bfefffbf3..046aac7620 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 1b1d87e59a..46ced56df0 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 ad3e803899..e364375da9 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)

#3Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#2)
3 attachment(s)
Re: tableam vs. TOAST

On Tue, May 21, 2019 at 2:10 PM Robert Haas <robertmhaas@gmail.com> wrote:

Updated and rebased patches attached.

And again.

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

Attachments:

v3-0003-Allow-TOAST-tables-to-be-implemented-using-table-.patchapplication/octet-stream; name=v3-0003-Allow-TOAST-tables-to-be-implemented-using-table-.patchDownload
From c4ec963a5a83c27dfd1e1a2787d88aa4fd01d95c Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
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)

v3-0001-Split-tuptoaster.c-into-three-separate-files.patchapplication/octet-stream; name=v3-0001-Split-tuptoaster.c-into-three-separate-files.patchDownload
From 1541ef5bc81b22025320a7fb6b1b8412900f3528 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
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)

v3-0002-Create-an-API-for-inserting-and-deleting-rows-in-.patchapplication/octet-stream; name=v3-0002-Create-an-API-for-inserting-and-deleting-rows-in-.patchDownload
From 7f3212a1c57942d8112481cd56c36e83e27e6059 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
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)

#4Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: Robert Haas (#3)
1 attachment(s)
Re: tableam vs. TOAST

On Tue, Jun 11, 2019 at 9:47 PM Robert Haas <robertmhaas@gmail.com> wrote:

On Tue, May 21, 2019 at 2:10 PM Robert Haas <robertmhaas@gmail.com> 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:

TOAST_table_test.sqlapplication/sql; name=TOAST_table_test.sqlDownload
#5Thomas Munro
thomas.munro@gmail.com
In reply to: Robert Haas (#3)
Re: tableam vs. TOAST

On Wed, Jun 12, 2019 at 4:17 AM Robert Haas <robertmhaas@gmail.com> wrote:

On Tue, May 21, 2019 at 2:10 PM Robert Haas <robertmhaas@gmail.com> wrote:

Updated and rebased patches attached.

And again.

Hi Robert,

Thus spake GCC:

detoast.c: In function ‘toast_fetch_datum’:
detoast.c:308:12: error: variable ‘toasttupDesc’ set but not used
[-Werror=unused-but-set-variable]
TupleDesc toasttupDesc;
^

--
Thomas Munro
https://enterprisedb.com

#6Robert Haas
robertmhaas@gmail.com
In reply to: Prabhat Sahu (#4)
Re: tableam vs. TOAST

On Tue, Jun 25, 2019 at 2:19 AM Prabhat Sahu
<prabhat.sahu@enterprisedb.com> wrote:

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.

Thanks for testing.

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.

Did you run each test just once? How stable are the results?

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

For me, this acquires AccessShareLock and ShareRowExclusiveLock on the
target table.

rhaas=# select locktype, database, relation, pid, mode, granted from
pg_locks where relation = 'b'::regclass;
locktype | database | relation | pid | mode | granted
----------+----------+----------+-------+-----------------------+---------
relation | 16384 | 16872 | 93197 | AccessShareLock | t
relation | 16384 | 16872 | 93197 | ShareRowExclusiveLock | t
(2 rows)

I don't see what that has to do with the topic at hand, though.

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

#7Robert Haas
robertmhaas@gmail.com
In reply to: Thomas Munro (#5)
4 attachment(s)
Re: tableam vs. TOAST

On Sun, Jul 7, 2019 at 11:08 PM Thomas Munro <thomas.munro@gmail.com> wrote:

Thus spake GCC:

detoast.c: In function ‘toast_fetch_datum’:
detoast.c:308:12: error: variable ‘toasttupDesc’ set but not used
[-Werror=unused-but-set-variable]
TupleDesc toasttupDesc;
^

Hmm, fixed, I hope.

Here's an updated patch set. In addition to the above fix, I fixed
things up for the new pgindent rules and added a fourth patch that
renames the detoasting functions to something that doesn't include
'heap.'

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

Attachments:

0004-Rename-attribute-detoasting-functions.patchapplication/octet-stream; name=0004-Rename-attribute-detoasting-functions.patchDownload
From 480b42c5802164c40b44779c897ceaf0d4af8aaa Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 8 Jul 2019 12:34:37 -0400
Subject: [PATCH 4/4] Rename attribute-detoasting functions.

The old names included the word "heap," which seems outdated now that
the heap is only one of potentially many table access methods.

Discussion: http://postgr.es/m/CA+TgmoZv-=2iWM4jcw5ZhJeL18HF96+W1yJeYrnGMYdkFFnEpQ@mail.gmail.com
---
 src/backend/access/common/detoast.c        | 26 +++++++++++-----------
 src/backend/access/common/indextuple.c     |  2 +-
 src/backend/access/heap/heaptoast.c        |  6 ++---
 src/backend/access/table/toast_helper.c    |  4 ++--
 src/backend/executor/tstoreReceiver.c      |  2 +-
 src/backend/storage/large_object/inv_api.c |  2 +-
 src/backend/utils/adt/expandedrecord.c     |  4 ++--
 src/backend/utils/fmgr/fmgr.c              |  8 +++----
 src/include/access/detoast.h               | 16 ++++++-------
 src/pl/plpgsql/src/pl_exec.c               |  2 +-
 src/test/regress/regress.c                 |  2 +-
 11 files changed, 37 insertions(+), 37 deletions(-)

diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c
index 46feb91888..c66920d52b 100644
--- a/src/backend/access/common/detoast.c
+++ b/src/backend/access/common/detoast.c
@@ -31,7 +31,7 @@ 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 -
+ * detoast_external_attr -
  *
  *	Public entry point to get back a toasted value from
  *	external source (possibly still in compressed format).
@@ -43,7 +43,7 @@ static struct varlena *toast_decompress_datum_slice(struct varlena *attr, int32
  * ----------
  */
 struct varlena *
-heap_tuple_fetch_attr(struct varlena *attr)
+detoast_external_attr(struct varlena *attr)
 {
 	struct varlena *result;
 
@@ -69,7 +69,7 @@ heap_tuple_fetch_attr(struct varlena *attr)
 
 		/* recurse if value is still external in some other way */
 		if (VARATT_IS_EXTERNAL(attr))
-			return heap_tuple_fetch_attr(attr);
+			return detoast_external_attr(attr);
 
 		/*
 		 * Copy into the caller's memory context, in case caller tries to
@@ -104,7 +104,7 @@ heap_tuple_fetch_attr(struct varlena *attr)
 
 
 /* ----------
- * heap_tuple_untoast_attr -
+ * detoast_attr -
  *
  *	Public entry point to get back a toasted value from compression
  *	or external storage.  The result is always non-extended varlena form.
@@ -114,7 +114,7 @@ heap_tuple_fetch_attr(struct varlena *attr)
  * ----------
  */
 struct varlena *
-heap_tuple_untoast_attr(struct varlena *attr)
+detoast_attr(struct varlena *attr)
 {
 	if (VARATT_IS_EXTERNAL_ONDISK(attr))
 	{
@@ -145,7 +145,7 @@ heap_tuple_untoast_attr(struct varlena *attr)
 		Assert(!VARATT_IS_EXTERNAL_INDIRECT(attr));
 
 		/* recurse in case value is still extended in some other way */
-		attr = heap_tuple_untoast_attr(attr);
+		attr = detoast_attr(attr);
 
 		/* if it isn't, we'd better copy it */
 		if (attr == (struct varlena *) redirect.pointer)
@@ -162,7 +162,7 @@ heap_tuple_untoast_attr(struct varlena *attr)
 		/*
 		 * This is an expanded-object pointer --- get flat format
 		 */
-		attr = heap_tuple_fetch_attr(attr);
+		attr = detoast_external_attr(attr);
 		/* flatteners are not allowed to produce compressed/short output */
 		Assert(!VARATT_IS_EXTENDED(attr));
 	}
@@ -193,14 +193,14 @@ heap_tuple_untoast_attr(struct varlena *attr)
 
 
 /* ----------
- * heap_tuple_untoast_attr_slice -
+ * detoast_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,
+detoast_attr_slice(struct varlena *attr,
 							  int32 sliceoffset, int32 slicelength)
 {
 	struct varlena *preslice;
@@ -230,13 +230,13 @@ heap_tuple_untoast_attr_slice(struct varlena *attr,
 		/* nested indirect Datums aren't allowed */
 		Assert(!VARATT_IS_EXTERNAL_INDIRECT(redirect.pointer));
 
-		return heap_tuple_untoast_attr_slice(redirect.pointer,
+		return detoast_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);
+		/* pass it off to detoast_external_attr to flatten */
+		preslice = detoast_external_attr(attr);
 	}
 	else
 		preslice = attr;
@@ -727,7 +727,7 @@ toast_decompress_datum(struct varlena *attr)
  * toast_decompress_datum_slice -
  *
  * Decompress the front of a compressed version of a varlena datum.
- * offset handling happens in heap_tuple_untoast_attr_slice.
+ * offset handling happens in detoast_attr_slice.
  * Here we just decompress a slice from the front.
  */
 static struct varlena *
diff --git a/src/backend/access/common/indextuple.c b/src/backend/access/common/indextuple.c
index 07586201b9..8a5f5227a3 100644
--- a/src/backend/access/common/indextuple.c
+++ b/src/backend/access/common/indextuple.c
@@ -89,7 +89,7 @@ index_form_tuple(TupleDesc tupleDescriptor,
 		if (VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
 		{
 			untoasted_values[i] =
-				PointerGetDatum(heap_tuple_fetch_attr((struct varlena *)
+				PointerGetDatum(detoast_external_attr((struct varlena *)
 													  DatumGetPointer(values[i])));
 			untoasted_free[i] = true;
 		}
diff --git a/src/backend/access/heap/heaptoast.c b/src/backend/access/heap/heaptoast.c
index c0acefc97e..9221dc71d7 100644
--- a/src/backend/access/heap/heaptoast.c
+++ b/src/backend/access/heap/heaptoast.c
@@ -376,7 +376,7 @@ toast_flatten_tuple(HeapTuple tup, TupleDesc tupleDesc)
 			new_value = (struct varlena *) DatumGetPointer(toast_values[i]);
 			if (VARATT_IS_EXTERNAL(new_value))
 			{
-				new_value = heap_tuple_fetch_attr(new_value);
+				new_value = detoast_external_attr(new_value);
 				toast_values[i] = PointerGetDatum(new_value);
 				toast_free[i] = true;
 			}
@@ -491,7 +491,7 @@ toast_flatten_tuple_to_datum(HeapTupleHeader tup,
 			if (VARATT_IS_EXTERNAL(new_value) ||
 				VARATT_IS_COMPRESSED(new_value))
 			{
-				new_value = heap_tuple_untoast_attr(new_value);
+				new_value = detoast_attr(new_value);
 				toast_values[i] = PointerGetDatum(new_value);
 				toast_free[i] = true;
 			}
@@ -590,7 +590,7 @@ toast_build_flattened_tuple(TupleDesc tupleDesc,
 			new_value = (struct varlena *) DatumGetPointer(new_values[i]);
 			if (VARATT_IS_EXTERNAL(new_value))
 			{
-				new_value = heap_tuple_fetch_attr(new_value);
+				new_value = detoast_external_attr(new_value);
 				new_values[i] = PointerGetDatum(new_value);
 				freeable_values[num_to_free++] = (Pointer) new_value;
 			}
diff --git a/src/backend/access/table/toast_helper.c b/src/backend/access/table/toast_helper.c
index e33918a7f4..dedc123e31 100644
--- a/src/backend/access/table/toast_helper.c
+++ b/src/backend/access/table/toast_helper.c
@@ -136,9 +136,9 @@ toast_tuple_init(ToastTupleContext *ttc)
 			{
 				ttc->ttc_attr[i].tai_oldexternal = new_value;
 				if (att->attstorage == 'p')
-					new_value = heap_tuple_untoast_attr(new_value);
+					new_value = detoast_attr(new_value);
 				else
-					new_value = heap_tuple_fetch_attr(new_value);
+					new_value = detoast_external_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);
diff --git a/src/backend/executor/tstoreReceiver.c b/src/backend/executor/tstoreReceiver.c
index c0c81c82da..6306b7d0bd 100644
--- a/src/backend/executor/tstoreReceiver.c
+++ b/src/backend/executor/tstoreReceiver.c
@@ -133,7 +133,7 @@ tstoreReceiveSlot_detoast(TupleTableSlot *slot, DestReceiver *self)
 		{
 			if (VARATT_IS_EXTERNAL(DatumGetPointer(val)))
 			{
-				val = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *)
+				val = PointerGetDatum(detoast_external_attr((struct varlena *)
 															DatumGetPointer(val)));
 				myState->tofree[nfree++] = val;
 			}
diff --git a/src/backend/storage/large_object/inv_api.c b/src/backend/storage/large_object/inv_api.c
index e591236343..263d5be12e 100644
--- a/src/backend/storage/large_object/inv_api.c
+++ b/src/backend/storage/large_object/inv_api.c
@@ -180,7 +180,7 @@ getdatafield(Form_pg_largeobject tuple,
 	if (VARATT_IS_EXTENDED(datafield))
 	{
 		datafield = (bytea *)
-			heap_tuple_untoast_attr((struct varlena *) datafield);
+			detoast_attr((struct varlena *) datafield);
 		freeit = true;
 	}
 	len = VARSIZE(datafield) - VARHDRSZ;
diff --git a/src/backend/utils/adt/expandedrecord.c b/src/backend/utils/adt/expandedrecord.c
index 369432d53c..d99d370b17 100644
--- a/src/backend/utils/adt/expandedrecord.c
+++ b/src/backend/utils/adt/expandedrecord.c
@@ -1159,7 +1159,7 @@ expanded_record_set_field_internal(ExpandedRecordHeader *erh, int fnumber,
 			{
 				/* Detoasting should be done in short-lived context. */
 				oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh));
-				newValue = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *) DatumGetPointer(newValue)));
+				newValue = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newValue)));
 				MemoryContextSwitchTo(oldcxt);
 			}
 			else
@@ -1305,7 +1305,7 @@ expanded_record_set_fields(ExpandedRecordHeader *erh,
 					if (expand_external)
 					{
 						/* Detoast as requested while copying the value */
-						newValue = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *) DatumGetPointer(newValue)));
+						newValue = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newValue)));
 					}
 					else
 					{
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 74e36e62d3..aa94380a2c 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -1739,7 +1739,7 @@ struct varlena *
 pg_detoast_datum(struct varlena *datum)
 {
 	if (VARATT_IS_EXTENDED(datum))
-		return heap_tuple_untoast_attr(datum);
+		return detoast_attr(datum);
 	else
 		return datum;
 }
@@ -1748,7 +1748,7 @@ struct varlena *
 pg_detoast_datum_copy(struct varlena *datum)
 {
 	if (VARATT_IS_EXTENDED(datum))
-		return heap_tuple_untoast_attr(datum);
+		return detoast_attr(datum);
 	else
 	{
 		/* Make a modifiable copy of the varlena object */
@@ -1764,14 +1764,14 @@ struct varlena *
 pg_detoast_datum_slice(struct varlena *datum, int32 first, int32 count)
 {
 	/* Only get the specified portion from the toast rel */
-	return heap_tuple_untoast_attr_slice(datum, first, count);
+	return detoast_attr_slice(datum, first, count);
 }
 
 struct varlena *
 pg_detoast_datum_packed(struct varlena *datum)
 {
 	if (VARATT_IS_COMPRESSED(datum) || VARATT_IS_EXTERNAL(datum))
-		return heap_tuple_untoast_attr(datum);
+		return detoast_attr(datum);
 	else
 		return datum;
 }
diff --git a/src/include/access/detoast.h b/src/include/access/detoast.h
index 582af147ea..8c07124b76 100644
--- a/src/include/access/detoast.h
+++ b/src/include/access/detoast.h
@@ -44,34 +44,34 @@ do { \
 #define INDIRECT_POINTER_SIZE (VARHDRSZ_EXTERNAL + sizeof(varatt_indirect))
 
 /* ----------
- * heap_tuple_fetch_attr() -
+ * detoast_external_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);
+extern struct varlena *detoast_external_attr(struct varlena *attr);
 
 /* ----------
- * heap_tuple_untoast_attr() -
+ * detoast_attr() -
  *
  *		Fully detoasts one attribute, fetching and/or decompressing
  *		it as needed.
  * ----------
  */
-extern struct varlena *heap_tuple_untoast_attr(struct varlena *attr);
+extern struct varlena *detoast_attr(struct varlena *attr);
 
 /* ----------
- * heap_tuple_untoast_attr_slice() -
+ * detoast_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);
+extern struct varlena *detoast_attr_slice(struct varlena *attr,
+										  int32 sliceoffset,
+										  int32 slicelength);
 
 /* ----------
  * toast_raw_datum_size -
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 3479fbd550..fe83fc7da9 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8267,7 +8267,7 @@ assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var,
 		 * pain, but there's little choice.
 		 */
 		oldcxt = MemoryContextSwitchTo(get_eval_mcontext(estate));
-		detoasted = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *) DatumGetPointer(newvalue)));
+		detoasted = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newvalue)));
 		MemoryContextSwitchTo(oldcxt);
 		/* Now's a good time to not leak the input value if it's freeable */
 		if (freeable)
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
index 826556eb29..2bbeaa0460 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -558,7 +558,7 @@ make_tuple_indirect(PG_FUNCTION_ARGS)
 
 		/* copy datum, so it still lives later */
 		if (VARATT_IS_EXTERNAL_ONDISK(attr))
-			attr = heap_tuple_fetch_attr(attr);
+			attr = detoast_external_attr(attr);
 		else
 		{
 			struct varlena *oldattr = attr;
-- 
2.17.2 (Apple Git-113)

0003-Allow-TOAST-tables-to-be-implemented-using-table-AMs.patchapplication/octet-stream; name=0003-Allow-TOAST-tables-to-be-implemented-using-table-AMs.patchDownload
From d9cf0d906b8d4c171a3a90025480cb8dedd79497 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Tue, 11 Jun 2019 11:10:15 -0400
Subject: [PATCH 3/4] 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.

Patch by me, tested by Prabhat Sabu.

Discussion: http://postgr.es/m/CA+TgmoZv-=2iWM4jcw5ZhJeL18HF96+W1yJeYrnGMYdkFFnEpQ@mail.gmail.com
---
 src/backend/access/common/detoast.c         |  62 +++++-----
 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        |  15 ++-
 14 files changed, 284 insertions(+), 148 deletions(-)

diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c
index 49c94da557..46feb91888 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,8 +304,7 @@ toast_fetch_datum(struct varlena *attr)
 	Relation   *toastidxs;
 	ScanKeyData toastkey;
 	SysScanDesc toastscan;
-	HeapTuple	ttup;
-	TupleDesc	toasttupDesc;
+	TupleTableSlot *slot;
 	struct varlena *result;
 	struct varatt_external toast_pointer;
 	int32		ressize;
@@ -312,11 +312,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 +326,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);
 
@@ -339,7 +338,9 @@ toast_fetch_datum(struct varlena *attr)
 	 * 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;
+	numchunks = ((ressize - 1) / max_chunk_size) + 1;
 
 	/* Look for the valid index of the toast relation */
 	validIndex = toast_open_indexes(toastrel,
@@ -367,15 +368,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 +408,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 +435,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 +499,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 +515,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 +532,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 +636,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 +671,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 ce141b734b..d0f05aada3 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2814,7 +2814,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
@@ -5568,7 +5568,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;
@@ -5677,7 +5677,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 72c8bfcaf1..df710e8212 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 fbf9294598..c0acefc97e 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 7532b4f865..e33918a7f4 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 858bcb6bc9..6ee0c6efa7 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 7cefacb0ea..cfb4ae0385 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
@@ -106,10 +118,10 @@ extern int	toast_tuple_find_biggest_attribute(ToastTupleContext *ttc,
 											   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 494b07a4b1..96f61baf80 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,16 @@ 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)

0002-Create-an-API-for-inserting-and-deleting-rows-in-TOA.patchapplication/octet-stream; name=0002-Create-an-API-for-inserting-and-deleting-rows-in-TOA.patchDownload
From 94bad91ad572e803f2504d287fcbf7e191f6c083 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 8 Jul 2019 12:02:16 -0400
Subject: [PATCH 2/4] 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.

Discussion: http://postgr.es/m/CA+TgmoZv-=2iWM4jcw5ZhJeL18HF96+W1yJeYrnGMYdkFFnEpQ@mail.gmail.com
---
 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       | 115 +++++++
 src/tools/pgindent/typedefs.list        |   2 +
 5 files changed, 493 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..fbf9294598 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..7532b4f865
--- /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..7cefacb0ea
--- /dev/null
+++ b/src/include/access/toast_helper.h
@@ -0,0 +1,115 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 432d2d812e..f3cdfa8a22 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2349,6 +2349,8 @@ TBlockState
 TIDBitmap
 TM_FailureData
 TM_Result
+ToastAttrInfo
+ToastTupleContext
 TOKEN_DEFAULT_DACL
 TOKEN_INFORMATION_CLASS
 TOKEN_PRIVILEGES
-- 
2.17.2 (Apple Git-113)

0001-Split-tuptoaster.c-into-three-separate-files.patchapplication/octet-stream; name=0001-Split-tuptoaster.c-into-three-separate-files.patchDownload
From 1fb29977574a43c18e4d3f73f5c4563989cedd51 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 8 Jul 2019 11:58:05 -0400
Subject: [PATCH 1/4] 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.

Discussion: http://postgr.es/m/CA+TgmoZv-=2iWM4jcw5ZhJeL18HF96+W1yJeYrnGMYdkFFnEpQ@mail.gmail.com
---
 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..49c94da557
--- /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 5773021499..e1ec873600 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 d768b9b061..ce141b734b 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 fc19f40a2e..72c8bfcaf1 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 72a448ad31..9f4f60a7c3 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 b6c9353cbd..aca6accb3d 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 d7004e5313..6b7752788e 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 5ee2a464bb..82b69456ad 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 591377d2cd..5c506386f9 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 c56ed48270..6167f79568 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 ff0f8ea5e7..daed66ad49 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..494b07a4b1
--- /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 a718bccc24..3479fbd550 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)

#8Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: Robert Haas (#6)
Re: tableam vs. TOAST

On Mon, Jul 8, 2019 at 9:06 PM Robert Haas <robertmhaas@gmail.com> wrote:

On Tue, Jun 25, 2019 at 2:19 AM Prabhat Sahu
<prabhat.sahu@enterprisedb.com> wrote:

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.

Thanks for testing.

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.

Did you run each test just once? How stable are the results?

No, I have executed the test multiple times(7times each) and calculated the
result as the median among those,
and the result looks stable(with v3 patches).

--

With Regards,

Prabhat Kumar Sahu
Skype ID: prabhat.sahu1984
EnterpriseDB Software India Pvt. Ltd.

The Postgres Database Company

#9Robert Haas
robertmhaas@gmail.com
In reply to: Prabhat Sahu (#8)
4 attachment(s)
Re: tableam vs. TOAST

On Tue, Jul 9, 2019 at 12:40 AM Prabhat Sahu
<prabhat.sahu@enterprisedb.com> wrote:

Did you run each test just once? How stable are the results?

No, I have executed the test multiple times(7times each) and calculated the result as the median among those,
and the result looks stable(with v3 patches).

I spent some time looking at your SCC test today. I think this isn't
really testing the code that actually got changed in the patch: a
quick CPU profile shows that your SCC test is bottlenecked on
pg_lzcompress, which spends a huge amount of time compressing the
gigantic string of 'a's you've constructed, and that code is exactly
the same with the patch as it in master. So, I think that any
fluctuations between the patched and unpatched results are just random
variation. There's no reason the patch should be slower with one row
count and faster with a different row count, anyway.

I tried to come up with a better test case that uses a more modest
amount of data, and ended up with this:

-- Setup.
CREATE OR REPLACE FUNCTION randomish_string(integer) RETURNS text AS $$
SELECT string_agg(random()::text, '') FROM generate_series(1, $1);
$$ LANGUAGE sql;

CREATE TABLE source_compressed (a int, b text);
INSERT INTO source_compressed
SELECT g, repeat('a', 2000) FROM generate_series(1, 10000) g;
CREATE TABLE sink_compressed (LIKE source_compressed);

CREATE TABLE source_external (a int, b text);
INSERT INTO source_external
SELECT g, randomish_string(400) FROM generate_series(1, 10000) g;

CREATE TABLE sink_external (LIKE source_external);
CREATE TABLE source_external_uncompressed (a int, b text);
ALTER TABLE source_external_uncompressed ALTER COLUMN b SET STORAGE EXTERNAL;
INSERT INTO source_external_uncompressed
SELECT g, randomish_string(400) FROM generate_series(1, 10000) g;
CREATE TABLE sink_external_uncompressed (LIKE source_external_uncompressed);
ALTER TABLE sink_external_uncompressed ALTER COLUMN b SET STORAGE EXTERNAL;

-- Test.
\timing
TRUNCATE sink_compressed, sink_external, sink_external_uncompressed;
CHECKPOINT;
INSERT INTO sink_compressed SELECT * FROM source_compressed;
INSERT INTO sink_external SELECT * FROM source_external;
INSERT INTO sink_external_uncompressed SELECT * FROM
source_external_uncompressed;

Roughly, on both master and with the patches, the first one takes
about 4.2 seconds, the second 7.5, and the third 1.2. The third one
is the fastest because it doesn't do any compression. Since it does
less irrelevant work than the other two cases, it has the best chance
of showing up any performance regression that the patch has caused --
if any regression existed, I suppose that it would be an increased
per-toast-fetch or per-toast-chunk overhead. However, I can't
reproduce any such regression. My first attempt at testing that case
showed the patch about 1% slower, but that wasn't reliably
reproducible when I did it a bunch more times. So as far as I can
figure, this patch does not regress performance in any
easily-measurable way.

Barring objections, I plan to commit the whole series of patches here
(latest rebase attached). They are not perfect and could likely be
improved in various ways, but I think they are an improvement over
what we have now, and it's not like it's set in stone once it's
committed. We can change it more if we come up with a better idea.

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

Attachments:

0004-Rename-attribute-detoasting-functions.patchapplication/octet-stream; name=0004-Rename-attribute-detoasting-functions.patchDownload
From bd43f06a3fadc75a54248a5a54941b9dff3fc576 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 8 Jul 2019 12:34:37 -0400
Subject: [PATCH 4/4] Rename attribute-detoasting functions.

The old names included the word "heap," which seems outdated now that
the heap is only one of potentially many table access methods.

Discussion: http://postgr.es/m/CA+TgmoZv-=2iWM4jcw5ZhJeL18HF96+W1yJeYrnGMYdkFFnEpQ@mail.gmail.com
---
 src/backend/access/common/detoast.c        | 26 +++++++++++-----------
 src/backend/access/common/indextuple.c     |  2 +-
 src/backend/access/heap/heaptoast.c        |  6 ++---
 src/backend/access/table/toast_helper.c    |  4 ++--
 src/backend/executor/tstoreReceiver.c      |  2 +-
 src/backend/storage/large_object/inv_api.c |  2 +-
 src/backend/utils/adt/expandedrecord.c     |  4 ++--
 src/backend/utils/fmgr/fmgr.c              |  8 +++----
 src/include/access/detoast.h               | 16 ++++++-------
 src/pl/plpgsql/src/pl_exec.c               |  2 +-
 src/test/regress/regress.c                 |  2 +-
 11 files changed, 37 insertions(+), 37 deletions(-)

diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c
index 36b68e35fb..78256f91ca 100644
--- a/src/backend/access/common/detoast.c
+++ b/src/backend/access/common/detoast.c
@@ -31,7 +31,7 @@ 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 -
+ * detoast_external_attr -
  *
  *	Public entry point to get back a toasted value from
  *	external source (possibly still in compressed format).
@@ -43,7 +43,7 @@ static struct varlena *toast_decompress_datum_slice(struct varlena *attr, int32
  * ----------
  */
 struct varlena *
-heap_tuple_fetch_attr(struct varlena *attr)
+detoast_external_attr(struct varlena *attr)
 {
 	struct varlena *result;
 
@@ -69,7 +69,7 @@ heap_tuple_fetch_attr(struct varlena *attr)
 
 		/* recurse if value is still external in some other way */
 		if (VARATT_IS_EXTERNAL(attr))
-			return heap_tuple_fetch_attr(attr);
+			return detoast_external_attr(attr);
 
 		/*
 		 * Copy into the caller's memory context, in case caller tries to
@@ -104,7 +104,7 @@ heap_tuple_fetch_attr(struct varlena *attr)
 
 
 /* ----------
- * heap_tuple_untoast_attr -
+ * detoast_attr -
  *
  *	Public entry point to get back a toasted value from compression
  *	or external storage.  The result is always non-extended varlena form.
@@ -114,7 +114,7 @@ heap_tuple_fetch_attr(struct varlena *attr)
  * ----------
  */
 struct varlena *
-heap_tuple_untoast_attr(struct varlena *attr)
+detoast_attr(struct varlena *attr)
 {
 	if (VARATT_IS_EXTERNAL_ONDISK(attr))
 	{
@@ -145,7 +145,7 @@ heap_tuple_untoast_attr(struct varlena *attr)
 		Assert(!VARATT_IS_EXTERNAL_INDIRECT(attr));
 
 		/* recurse in case value is still extended in some other way */
-		attr = heap_tuple_untoast_attr(attr);
+		attr = detoast_attr(attr);
 
 		/* if it isn't, we'd better copy it */
 		if (attr == (struct varlena *) redirect.pointer)
@@ -162,7 +162,7 @@ heap_tuple_untoast_attr(struct varlena *attr)
 		/*
 		 * This is an expanded-object pointer --- get flat format
 		 */
-		attr = heap_tuple_fetch_attr(attr);
+		attr = detoast_external_attr(attr);
 		/* flatteners are not allowed to produce compressed/short output */
 		Assert(!VARATT_IS_EXTENDED(attr));
 	}
@@ -193,14 +193,14 @@ heap_tuple_untoast_attr(struct varlena *attr)
 
 
 /* ----------
- * heap_tuple_untoast_attr_slice -
+ * detoast_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,
+detoast_attr_slice(struct varlena *attr,
 							  int32 sliceoffset, int32 slicelength)
 {
 	struct varlena *preslice;
@@ -230,13 +230,13 @@ heap_tuple_untoast_attr_slice(struct varlena *attr,
 		/* nested indirect Datums aren't allowed */
 		Assert(!VARATT_IS_EXTERNAL_INDIRECT(redirect.pointer));
 
-		return heap_tuple_untoast_attr_slice(redirect.pointer,
+		return detoast_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);
+		/* pass it off to detoast_external_attr to flatten */
+		preslice = detoast_external_attr(attr);
 	}
 	else
 		preslice = attr;
@@ -737,7 +737,7 @@ toast_decompress_datum(struct varlena *attr)
  * toast_decompress_datum_slice -
  *
  * Decompress the front of a compressed version of a varlena datum.
- * offset handling happens in heap_tuple_untoast_attr_slice.
+ * offset handling happens in detoast_attr_slice.
  * Here we just decompress a slice from the front.
  */
 static struct varlena *
diff --git a/src/backend/access/common/indextuple.c b/src/backend/access/common/indextuple.c
index 07586201b9..8a5f5227a3 100644
--- a/src/backend/access/common/indextuple.c
+++ b/src/backend/access/common/indextuple.c
@@ -89,7 +89,7 @@ index_form_tuple(TupleDesc tupleDescriptor,
 		if (VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
 		{
 			untoasted_values[i] =
-				PointerGetDatum(heap_tuple_fetch_attr((struct varlena *)
+				PointerGetDatum(detoast_external_attr((struct varlena *)
 													  DatumGetPointer(values[i])));
 			untoasted_free[i] = true;
 		}
diff --git a/src/backend/access/heap/heaptoast.c b/src/backend/access/heap/heaptoast.c
index c0acefc97e..9221dc71d7 100644
--- a/src/backend/access/heap/heaptoast.c
+++ b/src/backend/access/heap/heaptoast.c
@@ -376,7 +376,7 @@ toast_flatten_tuple(HeapTuple tup, TupleDesc tupleDesc)
 			new_value = (struct varlena *) DatumGetPointer(toast_values[i]);
 			if (VARATT_IS_EXTERNAL(new_value))
 			{
-				new_value = heap_tuple_fetch_attr(new_value);
+				new_value = detoast_external_attr(new_value);
 				toast_values[i] = PointerGetDatum(new_value);
 				toast_free[i] = true;
 			}
@@ -491,7 +491,7 @@ toast_flatten_tuple_to_datum(HeapTupleHeader tup,
 			if (VARATT_IS_EXTERNAL(new_value) ||
 				VARATT_IS_COMPRESSED(new_value))
 			{
-				new_value = heap_tuple_untoast_attr(new_value);
+				new_value = detoast_attr(new_value);
 				toast_values[i] = PointerGetDatum(new_value);
 				toast_free[i] = true;
 			}
@@ -590,7 +590,7 @@ toast_build_flattened_tuple(TupleDesc tupleDesc,
 			new_value = (struct varlena *) DatumGetPointer(new_values[i]);
 			if (VARATT_IS_EXTERNAL(new_value))
 			{
-				new_value = heap_tuple_fetch_attr(new_value);
+				new_value = detoast_external_attr(new_value);
 				new_values[i] = PointerGetDatum(new_value);
 				freeable_values[num_to_free++] = (Pointer) new_value;
 			}
diff --git a/src/backend/access/table/toast_helper.c b/src/backend/access/table/toast_helper.c
index e33918a7f4..dedc123e31 100644
--- a/src/backend/access/table/toast_helper.c
+++ b/src/backend/access/table/toast_helper.c
@@ -136,9 +136,9 @@ toast_tuple_init(ToastTupleContext *ttc)
 			{
 				ttc->ttc_attr[i].tai_oldexternal = new_value;
 				if (att->attstorage == 'p')
-					new_value = heap_tuple_untoast_attr(new_value);
+					new_value = detoast_attr(new_value);
 				else
-					new_value = heap_tuple_fetch_attr(new_value);
+					new_value = detoast_external_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);
diff --git a/src/backend/executor/tstoreReceiver.c b/src/backend/executor/tstoreReceiver.c
index c0c81c82da..6306b7d0bd 100644
--- a/src/backend/executor/tstoreReceiver.c
+++ b/src/backend/executor/tstoreReceiver.c
@@ -133,7 +133,7 @@ tstoreReceiveSlot_detoast(TupleTableSlot *slot, DestReceiver *self)
 		{
 			if (VARATT_IS_EXTERNAL(DatumGetPointer(val)))
 			{
-				val = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *)
+				val = PointerGetDatum(detoast_external_attr((struct varlena *)
 															DatumGetPointer(val)));
 				myState->tofree[nfree++] = val;
 			}
diff --git a/src/backend/storage/large_object/inv_api.c b/src/backend/storage/large_object/inv_api.c
index e591236343..263d5be12e 100644
--- a/src/backend/storage/large_object/inv_api.c
+++ b/src/backend/storage/large_object/inv_api.c
@@ -180,7 +180,7 @@ getdatafield(Form_pg_largeobject tuple,
 	if (VARATT_IS_EXTENDED(datafield))
 	{
 		datafield = (bytea *)
-			heap_tuple_untoast_attr((struct varlena *) datafield);
+			detoast_attr((struct varlena *) datafield);
 		freeit = true;
 	}
 	len = VARSIZE(datafield) - VARHDRSZ;
diff --git a/src/backend/utils/adt/expandedrecord.c b/src/backend/utils/adt/expandedrecord.c
index 369432d53c..d99d370b17 100644
--- a/src/backend/utils/adt/expandedrecord.c
+++ b/src/backend/utils/adt/expandedrecord.c
@@ -1159,7 +1159,7 @@ expanded_record_set_field_internal(ExpandedRecordHeader *erh, int fnumber,
 			{
 				/* Detoasting should be done in short-lived context. */
 				oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh));
-				newValue = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *) DatumGetPointer(newValue)));
+				newValue = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newValue)));
 				MemoryContextSwitchTo(oldcxt);
 			}
 			else
@@ -1305,7 +1305,7 @@ expanded_record_set_fields(ExpandedRecordHeader *erh,
 					if (expand_external)
 					{
 						/* Detoast as requested while copying the value */
-						newValue = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *) DatumGetPointer(newValue)));
+						newValue = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newValue)));
 					}
 					else
 					{
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 0484adb984..099ebd779b 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -1739,7 +1739,7 @@ struct varlena *
 pg_detoast_datum(struct varlena *datum)
 {
 	if (VARATT_IS_EXTENDED(datum))
-		return heap_tuple_untoast_attr(datum);
+		return detoast_attr(datum);
 	else
 		return datum;
 }
@@ -1748,7 +1748,7 @@ struct varlena *
 pg_detoast_datum_copy(struct varlena *datum)
 {
 	if (VARATT_IS_EXTENDED(datum))
-		return heap_tuple_untoast_attr(datum);
+		return detoast_attr(datum);
 	else
 	{
 		/* Make a modifiable copy of the varlena object */
@@ -1764,14 +1764,14 @@ struct varlena *
 pg_detoast_datum_slice(struct varlena *datum, int32 first, int32 count)
 {
 	/* Only get the specified portion from the toast rel */
-	return heap_tuple_untoast_attr_slice(datum, first, count);
+	return detoast_attr_slice(datum, first, count);
 }
 
 struct varlena *
 pg_detoast_datum_packed(struct varlena *datum)
 {
 	if (VARATT_IS_COMPRESSED(datum) || VARATT_IS_EXTERNAL(datum))
-		return heap_tuple_untoast_attr(datum);
+		return detoast_attr(datum);
 	else
 		return datum;
 }
diff --git a/src/include/access/detoast.h b/src/include/access/detoast.h
index 582af147ea..8c07124b76 100644
--- a/src/include/access/detoast.h
+++ b/src/include/access/detoast.h
@@ -44,34 +44,34 @@ do { \
 #define INDIRECT_POINTER_SIZE (VARHDRSZ_EXTERNAL + sizeof(varatt_indirect))
 
 /* ----------
- * heap_tuple_fetch_attr() -
+ * detoast_external_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);
+extern struct varlena *detoast_external_attr(struct varlena *attr);
 
 /* ----------
- * heap_tuple_untoast_attr() -
+ * detoast_attr() -
  *
  *		Fully detoasts one attribute, fetching and/or decompressing
  *		it as needed.
  * ----------
  */
-extern struct varlena *heap_tuple_untoast_attr(struct varlena *attr);
+extern struct varlena *detoast_attr(struct varlena *attr);
 
 /* ----------
- * heap_tuple_untoast_attr_slice() -
+ * detoast_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);
+extern struct varlena *detoast_attr_slice(struct varlena *attr,
+										  int32 sliceoffset,
+										  int32 slicelength);
 
 /* ----------
  * toast_raw_datum_size -
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index db2eff1bbb..870818287a 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8267,7 +8267,7 @@ assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var,
 		 * pain, but there's little choice.
 		 */
 		oldcxt = MemoryContextSwitchTo(get_eval_mcontext(estate));
-		detoasted = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *) DatumGetPointer(newvalue)));
+		detoasted = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newvalue)));
 		MemoryContextSwitchTo(oldcxt);
 		/* Now's a good time to not leak the input value if it's freeable */
 		if (freeable)
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
index 826556eb29..2bbeaa0460 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -558,7 +558,7 @@ make_tuple_indirect(PG_FUNCTION_ARGS)
 
 		/* copy datum, so it still lives later */
 		if (VARATT_IS_EXTERNAL_ONDISK(attr))
-			attr = heap_tuple_fetch_attr(attr);
+			attr = detoast_external_attr(attr);
 		else
 		{
 			struct varlena *oldattr = attr;
-- 
2.17.2 (Apple Git-113)

0002-Create-an-API-for-inserting-and-deleting-rows-in-TOA.patchapplication/octet-stream; name=0002-Create-an-API-for-inserting-and-deleting-rows-in-TOA.patchDownload
From a1b61ddbc1976b079253eb6e0560913bd6259f65 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 8 Jul 2019 12:02:16 -0400
Subject: [PATCH 2/4] 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.

Discussion: http://postgr.es/m/CA+TgmoZv-=2iWM4jcw5ZhJeL18HF96+W1yJeYrnGMYdkFFnEpQ@mail.gmail.com
---
 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       | 115 +++++++
 src/tools/pgindent/typedefs.list        |   2 +
 5 files changed, 493 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..fbf9294598 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..7532b4f865
--- /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..7cefacb0ea
--- /dev/null
+++ b/src/include/access/toast_helper.h
@@ -0,0 +1,115 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 432d2d812e..f3cdfa8a22 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2349,6 +2349,8 @@ TBlockState
 TIDBitmap
 TM_FailureData
 TM_Result
+ToastAttrInfo
+ToastTupleContext
 TOKEN_DEFAULT_DACL
 TOKEN_INFORMATION_CLASS
 TOKEN_PRIVILEGES
-- 
2.17.2 (Apple Git-113)

0003-Allow-TOAST-tables-to-be-implemented-using-table-AMs.patchapplication/octet-stream; name=0003-Allow-TOAST-tables-to-be-implemented-using-table-AMs.patchDownload
From 505989b10c29ea82313dedd1023d24b1d695f642 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Thu, 1 Aug 2019 10:37:02 -0400
Subject: [PATCH 3/4] 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.

Patch by me, tested by Prabhat Sabu.

Discussion: http://postgr.es/m/CA+TgmoZv-=2iWM4jcw5ZhJeL18HF96+W1yJeYrnGMYdkFFnEpQ@mail.gmail.com
---
 src/backend/access/common/detoast.c         |  62 +++++-----
 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        |  15 ++-
 14 files changed, 284 insertions(+), 148 deletions(-)

diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c
index c8b49d6a12..36b68e35fb 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,8 +304,7 @@ toast_fetch_datum(struct varlena *attr)
 	Relation   *toastidxs;
 	ScanKeyData toastkey;
 	SysScanDesc toastscan;
-	HeapTuple	ttup;
-	TupleDesc	toasttupDesc;
+	TupleTableSlot *slot;
 	struct varlena *result;
 	struct varatt_external toast_pointer;
 	int32		ressize;
@@ -312,11 +312,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 +326,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);
 
@@ -339,7 +338,9 @@ toast_fetch_datum(struct varlena *attr)
 	 * 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;
+	numchunks = ((ressize - 1) / max_chunk_size) + 1;
 
 	/* Look for the valid index of the toast relation */
 	validIndex = toast_open_indexes(toastrel,
@@ -367,15 +368,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;
@@ -409,23 +410,23 @@ toast_fetch_datum(struct varlena *attr)
 									 RelationGetRelationName(toastrel))));
 		if (residx < numchunks - 1)
 		{
-			if (chunksize != TOAST_MAX_CHUNK_SIZE)
+			if (chunksize != max_chunk_size)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATA_CORRUPTED),
 						 errmsg_internal("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)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATA_CORRUPTED),
 						 errmsg_internal("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))));
@@ -442,7 +443,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);
 
@@ -508,6 +509,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))
@@ -523,7 +525,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)
 	{
@@ -541,19 +542,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,
@@ -642,19 +646,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));
@@ -677,7 +681,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 2c5f58d817..b52e549259 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2814,7 +2814,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
@@ -5568,7 +5568,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;
@@ -5677,7 +5677,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 2dd8821fac..97a7433092 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -28,6 +28,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"
@@ -292,7 +293,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);
@@ -2041,6 +2042,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
@@ -2539,6 +2549,8 @@ static const TableAmRoutine heapam_methods = {
 
 	.relation_size = table_block_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 fbf9294598..c0acefc97e 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 2599b5d342..233ba24261 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 7532b4f865..e33918a7f4 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 858bcb6bc9..6ee0c6efa7 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 7f81703b78..521fd6232d 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.
@@ -1603,6 +1624,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 7cefacb0ea..cfb4ae0385 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
@@ -106,10 +118,10 @@ extern int	toast_tuple_find_biggest_attribute(ToastTupleContext *ttc,
 											   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 494b07a4b1..96f61baf80 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,16 @@ 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)

0001-Split-tuptoaster.c-into-three-separate-files.patchapplication/octet-stream; name=0001-Split-tuptoaster.c-into-three-separate-files.patchDownload
From a4c858c75793f0f8aff7914c572a6615ea5babf8 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 8 Jul 2019 11:58:05 -0400
Subject: [PATCH 1/4] 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.

Discussion: http://postgr.es/m/CA+TgmoZv-=2iWM4jcw5ZhJeL18HF96+W1yJeYrnGMYdkFFnEpQ@mail.gmail.com
---
 doc/src/sgml/storage.sgml                     |    2 +-
 src/backend/access/common/Makefile            |    5 +-
 src/backend/access/common/detoast.c           |  869 ++++++
 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          | 2421 -----------------
 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, 2612 insertions(+), 2559 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..c8b49d6a12
--- /dev/null
+++ b/src/backend/access/common/detoast.c
@@ -0,0 +1,869 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+					 errmsg_internal("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)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATA_CORRUPTED),
+						 errmsg_internal("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)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATA_CORRUPTED),
+						 errmsg_internal("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
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+					 errmsg_internal("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)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_CORRUPTED),
+				 errmsg_internal("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 42647b0526..20f4ed3c38 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 94309949fa..2c5f58d817 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 f1ff01e8cb..2dd8821fac 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -23,11 +23,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 a17508a82f..0172a13957 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 74233bb931..0000000000
--- a/src/backend/access/heap/tuptoaster.c
+++ /dev/null
@@ -1,2421 +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)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATA_CORRUPTED),
-					 errmsg_internal("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)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATA_CORRUPTED),
-						 errmsg_internal("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)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATA_CORRUPTED),
-						 errmsg_internal("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
-			ereport(ERROR,
-					(errcode(ERRCODE_DATA_CORRUPTED),
-					 errmsg_internal("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)
-		ereport(ERROR,
-				(errcode(ERRCODE_DATA_CORRUPTED),
-				 errmsg_internal("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 f553523857..26669ea36d 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 92285e848f..4924085f54 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 cedb4ee844..a59e2553d1 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 5ee2a464bb..82b69456ad 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 e8ffa0492f..0bb008414f 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 23c74f7d90..d0ee5ff405 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 99c34516bd..0484adb984 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 9d9c33d78c..ba192d0f53 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..494b07a4b1
--- /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 f5fdf93d0a..db2eff1bbb 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)

#10Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#9)
Re: tableam vs. TOAST

Hi,

On 2019-08-01 12:23:42 -0400, Robert Haas wrote:

Barring objections, I plan to commit the whole series of patches here
(latest rebase attached). They are not perfect and could likely be
improved in various ways, but I think they are an improvement over
what we have now, and it's not like it's set in stone once it's
committed. We can change it more if we come up with a better idea.

Could you wait until I either had a chance to look again, or until, say,
Monday if I don't get around to it? I'll try to get to it earlier than
that.

Greetings,

Andres Freund

#11Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#10)
Re: tableam vs. TOAST

On Thu, Aug 1, 2019 at 1:53 PM Andres Freund <andres@anarazel.de> wrote:

Could you wait until I either had a chance to look again, or until, say,
Monday if I don't get around to it? I'll try to get to it earlier than
that.

Sure, no problem.

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

#12Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#9)
Re: tableam vs. TOAST

Hi,

On 2019-08-01 12:23:42 -0400, Robert Haas wrote:

Roughly, on both master and with the patches, the first one takes
about 4.2 seconds, the second 7.5, and the third 1.2. The third one
is the fastest because it doesn't do any compression. Since it does
less irrelevant work than the other two cases, it has the best chance
of showing up any performance regression that the patch has caused --
if any regression existed, I suppose that it would be an increased
per-toast-fetch or per-toast-chunk overhead. However, I can't
reproduce any such regression. My first attempt at testing that case
showed the patch about 1% slower, but that wasn't reliably
reproducible when I did it a bunch more times. So as far as I can
figure, this patch does not regress performance in any
easily-measurable way.

Hm, those all include writing, right? And for read-only we don't expect
any additional overhead, correct? The write overhead is probably too
large show a bit of function call overhead - but if so, it'd probably be
on unlogged tables? And with COPY, because that utilizes multi_insert,
which means more toasting in a shorter amount of time?

.oO(why does everyone attach attachements out of order? Is that
a gmail thing?)

From a4c858c75793f0f8aff7914c572a6615ea5babf8 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 8 Jul 2019 11:58:05 -0400
Subject: [PATCH 1/4] 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.

I wonder if toasting.c should be moved too?

If anybody doesn't know git's --color-moved, it makes patches like this
a lot easier to review.

index 00000000000..582af147ea1
--- /dev/null
+++ b/src/include/access/detoast.h
@@ -0,0 +1,92 @@
+/*-------------------------------------------------------------------------
+ *
+ * detoast.h
+ *    Access to compressed and external varlena values.

Hm. Does it matter that that also includes stuff like expanded datums?

+ * Copyright (c) 2000-2019, PostgreSQL Global Development Group
+ *
+ * src/include/access/detoast.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DETOAST_H
+#define DETOAST_H

trailing whitespace after "#ifndef DETOAST_H ".

commit 60d51e6510c66f79c51e43fe22730fe050d87854
Author: Robert Haas <rhaas@postgresql.org>
Date: 2019-07-08 12:02:16 -0400

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.

Discussion: /messages/by-id/CA+TgmoZv-=2iWM4jcw5ZhJeL18HF96+W1yJeYrnGMYdkFFnEpQ@mail.gmail.com

Hm. This leaves toast_insert_or_update() as a name. That makes it sound
like it's generic toast code, rather than heap specific?

It's definitely nice how a lot of repetitive code has been deduplicated.
Also makes it easier to see how algorithmically expensive
toast_insert_or_update() is :(.

/*
* Second we look for attributes of attstorage 'x' or 'e' that are still
* 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)

Shouldn't this condition be the other way round?

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;
}
}

Couldn't most of these be handled via colflags, instead of having
numerous individual checks? I.e. if you had TOASTCOL_COMPRESSED,
TOASTCOL_IGNORED, TOASTCOL_MAIN, TOASTFLAG_EXTERNAL, etc, all but the
size check ought to boil down to a single mask test?

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, int max_chunk_size);
extern void toast_tuple_cleanup(ToastTupleContext *ttc, bool cleanup_toastrel);

I wonder if a better prefix wouldn't be toasting_...

+/*
+ * 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;

I think that comment is outdated?

+/*
+ * 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

I'd make these enums. They're more often accessible in a debugger...

commit 9e4bd383a00e6bb96088666e57673b343049345c
Author: Robert Haas <rhaas@postgresql.org>
Date: 2019-08-01 10:37:02 -0400

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.

Patch by me, tested by Prabhat Sabu.

Discussion: /messages/by-id/CA+TgmoZv-=2iWM4jcw5ZhJeL18HF96+W1yJeYrnGMYdkFFnEpQ@mail.gmail.com

I'm quite unconvinced that making the chunk size specified by the AM is
a good idea to do in isolation. We have TOAST_MAX_CHUNK_SIZE in
pg_control etc. It seems a bit dangerous to let AMs provide the size,
without being very clear that any change of the value will make data
inaccessible. It'd be different if the max were only used during
toasting.

I think the *size* checks should be weakened so we check:
1) After each chunk, whether the already assembled chunks exceed the
expected size.
2) After all chunks have been collected, check that the size is exactly
what we expect.

I don't think that removes a meaningful amount of error
checking. Missing tuples etc get detected by the chunk_ids not being
consecutive. The overall size is still verified.

The obvious problem with this is the slice fetching logic. For slices
with an offset of 0, it's obviously trivial to implement. For the higher
slice logic, I'd assume we'd have to fetch the first slice by estimating
where the start chunk is based on the current suggest chunk size, and
restarting the scan earlier/later if not accurate. In most cases it'll
be accurate, so we'd not loose efficiency.

Greetings,

Andres Freund

#13Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#12)
Re: tableam vs. TOAST

On Fri, Aug 2, 2019 at 6:42 PM Andres Freund <andres@anarazel.de> wrote:

Hm, those all include writing, right? And for read-only we don't expect
any additional overhead, correct? The write overhead is probably too
large show a bit of function call overhead - but if so, it'd probably be
on unlogged tables? And with COPY, because that utilizes multi_insert,
which means more toasting in a shorter amount of time?

Yes and yes. I guess we could test the unlogged case and with COPY,
but in any realistic case you're still looking for a tiny CPU overhead
in a sea of I/O costs. Even if an extremely short COPY on an unlogged
table regresses slightly, we do not normally reject patches that
improve code quality on the grounds that they add function call
overhead in a few places. Code like this hard to optimize and
maintain; as you remarked yourself, there are multiple opportunities
to do this stuff better that are hard to see in the current structure.

.oO(why does everyone attach attachements out of order? Is that
a gmail thing?)

Must be.

I wonder if toasting.c should be moved too?

I mean, we could, but I don't really see a reason. It'd just be
moving it from one fairly-generic place to another, and I'd rather
minimize churn.

trailing whitespace after "#ifndef DETOAST_H ".

Will fix.

Hm. This leaves toast_insert_or_update() as a name. That makes it sound
like it's generic toast code, rather than heap specific?

I'll rename it to heap_toast_insert_or_update(). But I think I'll put
that in 0004 with the other renames.

It's definitely nice how a lot of repetitive code has been deduplicated.
Also makes it easier to see how algorithmically expensive
toast_insert_or_update() is :(.

Yep.

Shouldn't this condition be the other way round?

I had to fight pretty hard to stop myself from tinkering with the
algorithm -- this can clearly be done better, but I wanted to make it
match the existing structure as far as possible. It also only needs to
be tested once, not on every loop iteration, so if we're going to
start changing things, we should go further than just swapping the
order of the tests. For now I prefer to draw a line in the sand and
change nothing.

Couldn't most of these be handled via colflags, instead of having
numerous individual checks? I.e. if you had TOASTCOL_COMPRESSED,
TOASTCOL_IGNORED, TOASTCOL_MAIN, TOASTFLAG_EXTERNAL, etc, all but the
size check ought to boil down to a single mask test?

I'm not really seeing how more flags would significantly simplify this
logic, but I might be missing something.

I wonder if a better prefix wouldn't be toasting_...

I'm open to other votes, but I think it's toast_tuple is more specific
than toasting_ and thus better.

+/*
+ * 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;

I think that comment is outdated?

Oops. Will fix.

+/*
+ * 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

I'd make these enums. They're more often accessible in a debugger...

Ugh, I hate that style. Abusing enums to make flag bits makes my skin
crawl. I always wondered what the appeal was -- I guess now I know.
Blech.

I'm quite unconvinced that making the chunk size specified by the AM is
a good idea to do in isolation. We have TOAST_MAX_CHUNK_SIZE in
pg_control etc. It seems a bit dangerous to let AMs provide the size,
without being very clear that any change of the value will make data
inaccessible. It'd be different if the max were only used during
toasting.

I was actually thinking about proposing that we rip
TOAST_MAX_CHUNK_SIZE out of pg_control. No real effort has been made
here to make this something that users could configure, and I don't
know of a good reason to configure it. It also seems pretty out of
place in a world where there are multiple AMs floating around -- why
should heap, and only heap, be checked there? Granted it does have
some pride of place, but still.

I think the *size* checks should be weakened so we check:
1) After each chunk, whether the already assembled chunks exceed the
expected size.
2) After all chunks have been collected, check that the size is exactly
what we expect.

I don't think that removes a meaningful amount of error
checking. Missing tuples etc get detected by the chunk_ids not being
consecutive. The overall size is still verified.

The obvious problem with this is the slice fetching logic. For slices
with an offset of 0, it's obviously trivial to implement. For the higher
slice logic, I'd assume we'd have to fetch the first slice by estimating
where the start chunk is based on the current suggest chunk size, and
restarting the scan earlier/later if not accurate. In most cases it'll
be accurate, so we'd not loose efficiency.

I don't feel entirely convinced that there's any rush to do all of
this right now, and the more I change the harder it is to make sure
that I haven't broken anything. How strongly do you feel about this
stuff?

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

#14Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#13)
Re: tableam vs. TOAST

Robert Haas <robertmhaas@gmail.com> writes:

I don't feel entirely convinced that there's any rush to do all of
this right now, and the more I change the harder it is to make sure
that I haven't broken anything. How strongly do you feel about this
stuff?

FWIW, I agree with your comment further up that this patch ought to
just refactor, not change any algorithms. The latter can be done
in separate patches.

regards, tom lane

#15Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#13)
Re: tableam vs. TOAST

Hi,

On 2019-08-05 15:36:59 -0400, Robert Haas wrote:

On Fri, Aug 2, 2019 at 6:42 PM Andres Freund <andres@anarazel.de> wrote:

Hm. This leaves toast_insert_or_update() as a name. That makes it sound
like it's generic toast code, rather than heap specific?

I'll rename it to heap_toast_insert_or_update(). But I think I'll put
that in 0004 with the other renames.

Makes sense.

It's definitely nice how a lot of repetitive code has been deduplicated.
Also makes it easier to see how algorithmically expensive
toast_insert_or_update() is :(.

Yep.

Shouldn't this condition be the other way round?

I had to fight pretty hard to stop myself from tinkering with the
algorithm -- this can clearly be done better, but I wanted to make it
match the existing structure as far as possible. It also only needs to
be tested once, not on every loop iteration, so if we're going to
start changing things, we should go further than just swapping the
order of the tests. For now I prefer to draw a line in the sand and
change nothing.

Makes sense.

Couldn't most of these be handled via colflags, instead of having
numerous individual checks? I.e. if you had TOASTCOL_COMPRESSED,
TOASTCOL_IGNORED, TOASTCOL_MAIN, TOASTFLAG_EXTERNAL, etc, all but the
size check ought to boil down to a single mask test?

I'm not really seeing how more flags would significantly simplify this
logic, but I might be missing something.

Well, right now you have a number of ifs for each attribute. If you
encoded all the parameters into flags, you could change that to a single
flag test - as far as I can tell, all the parameters could be
represented as that, if you moved MAIN etc into flags. A single if for
flags (and then the size check) is cheaper than several separate checks.

I'm quite unconvinced that making the chunk size specified by the AM is
a good idea to do in isolation. We have TOAST_MAX_CHUNK_SIZE in
pg_control etc. It seems a bit dangerous to let AMs provide the size,
without being very clear that any change of the value will make data
inaccessible. It'd be different if the max were only used during
toasting.

I was actually thinking about proposing that we rip
TOAST_MAX_CHUNK_SIZE out of pg_control. No real effort has been made
here to make this something that users could configure, and I don't
know of a good reason to configure it. It also seems pretty out of
place in a world where there are multiple AMs floating around -- why
should heap, and only heap, be checked there? Granted it does have
some pride of place, but still.

I think the *size* checks should be weakened so we check:
1) After each chunk, whether the already assembled chunks exceed the
expected size.
2) After all chunks have been collected, check that the size is exactly
what we expect.

I don't think that removes a meaningful amount of error
checking. Missing tuples etc get detected by the chunk_ids not being
consecutive. The overall size is still verified.

The obvious problem with this is the slice fetching logic. For slices
with an offset of 0, it's obviously trivial to implement. For the higher
slice logic, I'd assume we'd have to fetch the first slice by estimating
where the start chunk is based on the current suggest chunk size, and
restarting the scan earlier/later if not accurate. In most cases it'll
be accurate, so we'd not loose efficiency.

I don't feel entirely convinced that there's any rush to do all of
this right now, and the more I change the harder it is to make sure
that I haven't broken anything. How strongly do you feel about this
stuff?

I think we either should leave the hardcoded limit in place, or make it
actually not fixed. Ripping-it-out-but-not-actually just seems like a
trap for the unwary, without much point.

Greetings,

Andres Freund

#16Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Robert Haas (#13)
1 attachment(s)
Re: tableam vs. TOAST

On 2019-Aug-05, Robert Haas wrote:

Shouldn't this condition be the other way round?

I had to fight pretty hard to stop myself from tinkering with the
algorithm -- this can clearly be done better, but I wanted to make it
match the existing structure as far as possible. It also only needs to
be tested once, not on every loop iteration, so if we're going to
start changing things, we should go further than just swapping the
order of the tests. For now I prefer to draw a line in the sand and
change nothing.

I agree, and can we move forward with this 0001? The idea here is to
change no code (as also suggested by Tom elsewhere), and it's the
largest patch in this series by a mile. I checked --color-moved=zebra
and I think the patch looks fine, and also it compiles fine. I ran
src/tools/pginclude/headerscheck on it and found no complaints.

So here's a rebased version, where the DETOAST_H whitespace has been
removed. No other changes from your original. Will you please push
soon?

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

0001-Split-tuptoaster.c-into-three-separate-files.patchtext/x-diff; charset=us-asciiDownload
From 7b08fbe2759c4882be3293ac374e9343a41b776b Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Thu, 5 Sep 2019 10:30:34 -0400
Subject: [PATCH] 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.

Discussion: http://postgr.es/m/CA+TgmoZv-=2iWM4jcw5ZhJeL18HF96+W1yJeYrnGMYdkFFnEpQ@mail.gmail.com
---
 doc/src/sgml/storage.sgml                     |    2 +-
 src/backend/access/common/Makefile            |    5 +-
 src/backend/access/common/detoast.c           |  869 ++++++
 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          | 2419 -----------------
 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, 2612 insertions(+), 2557 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
 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 342a0ff7b7..7802eb82d4 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..c8b49d6a12
--- /dev/null
+++ b/src/backend/access/common/detoast.c
@@ -0,0 +1,869 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+					 errmsg_internal("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)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATA_CORRUPTED),
+						 errmsg_internal("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)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATA_CORRUPTED),
+						 errmsg_internal("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
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+					 errmsg_internal("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)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_CORRUPTED),
+				 errmsg_internal("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 42647b0526..20f4ed3c38 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 34143a6853..e9544822bf 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 f1ff01e8cb..2dd8821fac 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -23,11 +23,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 a17508a82f..0172a13957 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
index 970e78d849..e69de29bb2 100644
--- a/src/backend/access/heap/tuptoaster.c
+++ b/src/backend/access/heap/tuptoaster.c
@@ -1,2419 +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"
-
-
-/*
- *	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)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATA_CORRUPTED),
-					 errmsg_internal("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)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATA_CORRUPTED),
-						 errmsg_internal("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)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATA_CORRUPTED),
-						 errmsg_internal("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
-			ereport(ERROR,
-					(errcode(ERRCODE_DATA_CORRUPTED),
-					 errmsg_internal("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)
-		ereport(ERROR,
-				(errcode(ERRCODE_DATA_CORRUPTED),
-				 errmsg_internal("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 e651a841bb..6876537b62 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 d2fdf447b6..ed0d4e6d4f 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 28985a07ec..a23128d7a0 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 9731dd59ca..293bfb61c3 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 697f1fed71..5900d967a2 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 409f787ac3..85472a98c5 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 bde13e631c..dd29b2f9ff 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 fa08b55eb6..d36156f4e4 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 99c34516bd..0484adb984 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 159a30b520..f85ea2db52 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..02029a991f
--- /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..494b07a4b1
--- /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 a4697dc9b8..cb2e19cda4 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.1

#17Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#16)
3 attachment(s)
Re: tableam vs. TOAST

On Thu, Sep 5, 2019 at 10:52 AM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

I agree, and can we move forward with this 0001? The idea here is to
change no code (as also suggested by Tom elsewhere), and it's the
largest patch in this series by a mile. I checked --color-moved=zebra
and I think the patch looks fine, and also it compiles fine. I ran
src/tools/pginclude/headerscheck on it and found no complaints.

So here's a rebased version, where the DETOAST_H whitespace has been
removed. No other changes from your original. Will you please push
soon?

Done, thanks. Here's the rest again with the additional rename added
to 0003 (formerly 0004). I think it's probably OK to go ahead with
that stuff, too, but I'll wait a bit to see if anyone wants to raise
more objections.

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

Attachments:

v5-0002-Allow-TOAST-tables-to-be-implemented-using-table-.patchapplication/octet-stream; name=v5-0002-Allow-TOAST-tables-to-be-implemented-using-table-.patchDownload
From a1e90893dec0f7ea8635ecdadf5aa7d2f66a234d Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Thu, 1 Aug 2019 10:37:02 -0400
Subject: [PATCH v5 2/3] Allow TOAST tables to be implemented using table AMs
 other than heap.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

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.

Patch by me, reviewed and tested by Prabhat Sabu, Thomas Munro,
Andres Freund, and Álvaro Herrera.

Discussion: http://postgr.es/m/CA+TgmoZv-=2iWM4jcw5ZhJeL18HF96+W1yJeYrnGMYdkFFnEpQ@mail.gmail.com
---
 src/backend/access/common/detoast.c         |  62 +++++-----
 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        |  15 ++-
 14 files changed, 284 insertions(+), 148 deletions(-)

diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c
index c8b49d6a12..36b68e35fb 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,8 +304,7 @@ toast_fetch_datum(struct varlena *attr)
 	Relation   *toastidxs;
 	ScanKeyData toastkey;
 	SysScanDesc toastscan;
-	HeapTuple	ttup;
-	TupleDesc	toasttupDesc;
+	TupleTableSlot *slot;
 	struct varlena *result;
 	struct varatt_external toast_pointer;
 	int32		ressize;
@@ -312,11 +312,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 +326,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);
 
@@ -339,7 +338,9 @@ toast_fetch_datum(struct varlena *attr)
 	 * 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;
+	numchunks = ((ressize - 1) / max_chunk_size) + 1;
 
 	/* Look for the valid index of the toast relation */
 	validIndex = toast_open_indexes(toastrel,
@@ -367,15 +368,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;
@@ -409,23 +410,23 @@ toast_fetch_datum(struct varlena *attr)
 									 RelationGetRelationName(toastrel))));
 		if (residx < numchunks - 1)
 		{
-			if (chunksize != TOAST_MAX_CHUNK_SIZE)
+			if (chunksize != max_chunk_size)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATA_CORRUPTED),
 						 errmsg_internal("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)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATA_CORRUPTED),
 						 errmsg_internal("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))));
@@ -442,7 +443,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);
 
@@ -508,6 +509,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))
@@ -523,7 +525,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)
 	{
@@ -541,19 +542,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,
@@ -642,19 +646,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));
@@ -677,7 +681,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 e9544822bf..9e7cafa8e6 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
@@ -5564,7 +5564,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;
@@ -5673,7 +5673,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 2dd8821fac..97a7433092 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -28,6 +28,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"
@@ -292,7 +293,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);
@@ -2041,6 +2042,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
@@ -2539,6 +2549,8 @@ static const TableAmRoutine heapam_methods = {
 
 	.relation_size = table_block_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 fbf9294598..c0acefc97e 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 2599b5d342..233ba24261 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 7532b4f865..e33918a7f4 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 858bcb6bc9..6ee0c6efa7 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 7f81703b78..521fd6232d 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.
@@ -1603,6 +1624,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 7cefacb0ea..cfb4ae0385 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
@@ -106,10 +118,10 @@ extern int	toast_tuple_find_biggest_attribute(ToastTupleContext *ttc,
 											   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 494b07a4b1..96f61baf80 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,16 @@ 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)

v5-0001-Create-an-API-for-inserting-and-deleting-rows-in-.patchapplication/octet-stream; name=v5-0001-Create-an-API-for-inserting-and-deleting-rows-in-.patchDownload
From 4c3ae35d6ec84ef793d31acb6af94b702f64199b Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 8 Jul 2019 12:02:16 -0400
Subject: [PATCH v5 1/3] Create an API for inserting and deleting rows in TOAST
 tables.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

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.

Patch by me, reviewed and tested by Prabhat Sabu, Thomas Munro,
Andres Freund, and Álvaro Herrera.

Discussion: http://postgr.es/m/CA+TgmoZv-=2iWM4jcw5ZhJeL18HF96+W1yJeYrnGMYdkFFnEpQ@mail.gmail.com
---
 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       | 115 +++++++
 src/tools/pgindent/typedefs.list        |   2 +
 5 files changed, 493 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..fbf9294598 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..7532b4f865
--- /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..7cefacb0ea
--- /dev/null
+++ b/src/include/access/toast_helper.h
@@ -0,0 +1,115 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 432d2d812e..f3cdfa8a22 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2349,6 +2349,8 @@ TBlockState
 TIDBitmap
 TM_FailureData
 TM_Result
+ToastAttrInfo
+ToastTupleContext
 TOKEN_DEFAULT_DACL
 TOKEN_INFORMATION_CLASS
 TOKEN_PRIVILEGES
-- 
2.17.2 (Apple Git-113)

v5-0003-Rename-some-toasting-functions-based-on-whether-t.patchapplication/octet-stream; name=v5-0003-Rename-some-toasting-functions-based-on-whether-t.patchDownload
From 478afef9cd49c66a9ae09f4a800773a8926e4957 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 8 Jul 2019 12:34:37 -0400
Subject: [PATCH v5 3/3] Rename some toasting functions based on whether they
 are heap-specific.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The old names for the attribute-detoasting functions names included the
word "heap," which seems outdated now that the heap is only one of
potentially many table access methods.

On the other hand, toast_insert_or_update is heap-specific, so rename
it to heap_toast_insert_or_update.

Patch by me, reviewed and tested by Prabhat Sabu, Thomas Munro,
Andres Freund, and Álvaro Herrera.

Discussion: http://postgr.es/m/CA+TgmoZv-=2iWM4jcw5ZhJeL18HF96+W1yJeYrnGMYdkFFnEpQ@mail.gmail.com
---
 src/backend/access/common/detoast.c        | 26 +++++++++++-----------
 src/backend/access/common/indextuple.c     |  2 +-
 src/backend/access/heap/heapam.c           |  4 ++--
 src/backend/access/heap/heaptoast.c        | 17 +++++++-------
 src/backend/access/heap/rewriteheap.c      |  4 ++--
 src/backend/access/table/toast_helper.c    |  4 ++--
 src/backend/executor/tstoreReceiver.c      |  2 +-
 src/backend/storage/large_object/inv_api.c |  2 +-
 src/backend/utils/adt/expandedrecord.c     |  4 ++--
 src/backend/utils/fmgr/fmgr.c              |  8 +++----
 src/include/access/detoast.h               | 16 ++++++-------
 src/include/access/heaptoast.h             |  7 +++---
 src/pl/plpgsql/src/pl_exec.c               |  2 +-
 src/test/regress/regress.c                 |  2 +-
 14 files changed, 50 insertions(+), 50 deletions(-)

diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c
index 36b68e35fb..78256f91ca 100644
--- a/src/backend/access/common/detoast.c
+++ b/src/backend/access/common/detoast.c
@@ -31,7 +31,7 @@ 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 -
+ * detoast_external_attr -
  *
  *	Public entry point to get back a toasted value from
  *	external source (possibly still in compressed format).
@@ -43,7 +43,7 @@ static struct varlena *toast_decompress_datum_slice(struct varlena *attr, int32
  * ----------
  */
 struct varlena *
-heap_tuple_fetch_attr(struct varlena *attr)
+detoast_external_attr(struct varlena *attr)
 {
 	struct varlena *result;
 
@@ -69,7 +69,7 @@ heap_tuple_fetch_attr(struct varlena *attr)
 
 		/* recurse if value is still external in some other way */
 		if (VARATT_IS_EXTERNAL(attr))
-			return heap_tuple_fetch_attr(attr);
+			return detoast_external_attr(attr);
 
 		/*
 		 * Copy into the caller's memory context, in case caller tries to
@@ -104,7 +104,7 @@ heap_tuple_fetch_attr(struct varlena *attr)
 
 
 /* ----------
- * heap_tuple_untoast_attr -
+ * detoast_attr -
  *
  *	Public entry point to get back a toasted value from compression
  *	or external storage.  The result is always non-extended varlena form.
@@ -114,7 +114,7 @@ heap_tuple_fetch_attr(struct varlena *attr)
  * ----------
  */
 struct varlena *
-heap_tuple_untoast_attr(struct varlena *attr)
+detoast_attr(struct varlena *attr)
 {
 	if (VARATT_IS_EXTERNAL_ONDISK(attr))
 	{
@@ -145,7 +145,7 @@ heap_tuple_untoast_attr(struct varlena *attr)
 		Assert(!VARATT_IS_EXTERNAL_INDIRECT(attr));
 
 		/* recurse in case value is still extended in some other way */
-		attr = heap_tuple_untoast_attr(attr);
+		attr = detoast_attr(attr);
 
 		/* if it isn't, we'd better copy it */
 		if (attr == (struct varlena *) redirect.pointer)
@@ -162,7 +162,7 @@ heap_tuple_untoast_attr(struct varlena *attr)
 		/*
 		 * This is an expanded-object pointer --- get flat format
 		 */
-		attr = heap_tuple_fetch_attr(attr);
+		attr = detoast_external_attr(attr);
 		/* flatteners are not allowed to produce compressed/short output */
 		Assert(!VARATT_IS_EXTENDED(attr));
 	}
@@ -193,14 +193,14 @@ heap_tuple_untoast_attr(struct varlena *attr)
 
 
 /* ----------
- * heap_tuple_untoast_attr_slice -
+ * detoast_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,
+detoast_attr_slice(struct varlena *attr,
 							  int32 sliceoffset, int32 slicelength)
 {
 	struct varlena *preslice;
@@ -230,13 +230,13 @@ heap_tuple_untoast_attr_slice(struct varlena *attr,
 		/* nested indirect Datums aren't allowed */
 		Assert(!VARATT_IS_EXTERNAL_INDIRECT(redirect.pointer));
 
-		return heap_tuple_untoast_attr_slice(redirect.pointer,
+		return detoast_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);
+		/* pass it off to detoast_external_attr to flatten */
+		preslice = detoast_external_attr(attr);
 	}
 	else
 		preslice = attr;
@@ -737,7 +737,7 @@ toast_decompress_datum(struct varlena *attr)
  * toast_decompress_datum_slice -
  *
  * Decompress the front of a compressed version of a varlena datum.
- * offset handling happens in heap_tuple_untoast_attr_slice.
+ * offset handling happens in detoast_attr_slice.
  * Here we just decompress a slice from the front.
  */
 static struct varlena *
diff --git a/src/backend/access/common/indextuple.c b/src/backend/access/common/indextuple.c
index 07586201b9..8a5f5227a3 100644
--- a/src/backend/access/common/indextuple.c
+++ b/src/backend/access/common/indextuple.c
@@ -89,7 +89,7 @@ index_form_tuple(TupleDesc tupleDescriptor,
 		if (VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
 		{
 			untoasted_values[i] =
-				PointerGetDatum(heap_tuple_fetch_attr((struct varlena *)
+				PointerGetDatum(detoast_external_attr((struct varlena *)
 													  DatumGetPointer(values[i])));
 			untoasted_free[i] = true;
 		}
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 9e7cafa8e6..3b0aa08a36 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2085,7 +2085,7 @@ heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
 		return tup;
 	}
 	else if (HeapTupleHasExternal(tup) || tup->t_len > TOAST_TUPLE_THRESHOLD)
-		return toast_insert_or_update(relation, tup, NULL, options);
+		return heap_toast_insert_or_update(relation, tup, NULL, options);
 	else
 		return tup;
 }
@@ -3504,7 +3504,7 @@ l2:
 		if (need_toast)
 		{
 			/* Note we always use WAL and FSM during updates */
-			heaptup = toast_insert_or_update(relation, newtup, &oldtup, 0);
+			heaptup = heap_toast_insert_or_update(relation, newtup, &oldtup, 0);
 			newtupsize = MAXALIGN(heaptup->t_len);
 		}
 		else
diff --git a/src/backend/access/heap/heaptoast.c b/src/backend/access/heap/heaptoast.c
index c0acefc97e..defa6fb3bb 100644
--- a/src/backend/access/heap/heaptoast.c
+++ b/src/backend/access/heap/heaptoast.c
@@ -12,7 +12,7 @@
  *
  *
  * INTERFACE ROUTINES
- *		toast_insert_or_update -
+ *		heap_toast_insert_or_update -
  *			Try to make a given tuple fit into one page by compressing
  *			or moving off attributes
  *
@@ -75,7 +75,7 @@ toast_delete(Relation rel, HeapTuple oldtup, bool is_speculative,
 
 
 /* ----------
- * toast_insert_or_update -
+ * heap_toast_insert_or_update -
  *
  *	Delete no-longer-used toast-entries and create new ones to
  *	make the new tuple fit on INSERT or UPDATE
@@ -93,8 +93,8 @@ toast_delete(Relation rel, HeapTuple oldtup, bool is_speculative,
  * ----------
  */
 HeapTuple
-toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
-					   int options)
+heap_toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
+							int options)
 {
 	HeapTuple	result_tuple;
 	TupleDesc	tupleDesc;
@@ -376,7 +376,7 @@ toast_flatten_tuple(HeapTuple tup, TupleDesc tupleDesc)
 			new_value = (struct varlena *) DatumGetPointer(toast_values[i]);
 			if (VARATT_IS_EXTERNAL(new_value))
 			{
-				new_value = heap_tuple_fetch_attr(new_value);
+				new_value = detoast_external_attr(new_value);
 				toast_values[i] = PointerGetDatum(new_value);
 				toast_free[i] = true;
 			}
@@ -491,7 +491,7 @@ toast_flatten_tuple_to_datum(HeapTupleHeader tup,
 			if (VARATT_IS_EXTERNAL(new_value) ||
 				VARATT_IS_COMPRESSED(new_value))
 			{
-				new_value = heap_tuple_untoast_attr(new_value);
+				new_value = detoast_attr(new_value);
 				toast_values[i] = PointerGetDatum(new_value);
 				toast_free[i] = true;
 			}
@@ -501,7 +501,8 @@ toast_flatten_tuple_to_datum(HeapTupleHeader tup,
 	/*
 	 * Calculate the new size of the tuple.
 	 *
-	 * This should match the reconstruction code in toast_insert_or_update.
+	 * This should match the reconstruction code in
+	 * heap_toast_insert_or_update.
 	 */
 	new_header_len = SizeofHeapTupleHeader;
 	if (has_nulls)
@@ -590,7 +591,7 @@ toast_build_flattened_tuple(TupleDesc tupleDesc,
 			new_value = (struct varlena *) DatumGetPointer(new_values[i]);
 			if (VARATT_IS_EXTERNAL(new_value))
 			{
-				new_value = heap_tuple_fetch_attr(new_value);
+				new_value = detoast_external_attr(new_value);
 				new_values[i] = PointerGetDatum(new_value);
 				freeable_values[num_to_free++] = (Pointer) new_value;
 			}
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 0172a13957..7c98a42b8b 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -664,8 +664,8 @@ raw_heap_insert(RewriteState state, HeapTuple tup)
 		 */
 		options |= HEAP_INSERT_NO_LOGICAL;
 
-		heaptup = toast_insert_or_update(state->rs_new_rel, tup, NULL,
-										 options);
+		heaptup = heap_toast_insert_or_update(state->rs_new_rel, tup, NULL,
+											  options);
 	}
 	else
 		heaptup = tup;
diff --git a/src/backend/access/table/toast_helper.c b/src/backend/access/table/toast_helper.c
index e33918a7f4..dedc123e31 100644
--- a/src/backend/access/table/toast_helper.c
+++ b/src/backend/access/table/toast_helper.c
@@ -136,9 +136,9 @@ toast_tuple_init(ToastTupleContext *ttc)
 			{
 				ttc->ttc_attr[i].tai_oldexternal = new_value;
 				if (att->attstorage == 'p')
-					new_value = heap_tuple_untoast_attr(new_value);
+					new_value = detoast_attr(new_value);
 				else
-					new_value = heap_tuple_fetch_attr(new_value);
+					new_value = detoast_external_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);
diff --git a/src/backend/executor/tstoreReceiver.c b/src/backend/executor/tstoreReceiver.c
index c0c81c82da..6306b7d0bd 100644
--- a/src/backend/executor/tstoreReceiver.c
+++ b/src/backend/executor/tstoreReceiver.c
@@ -133,7 +133,7 @@ tstoreReceiveSlot_detoast(TupleTableSlot *slot, DestReceiver *self)
 		{
 			if (VARATT_IS_EXTERNAL(DatumGetPointer(val)))
 			{
-				val = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *)
+				val = PointerGetDatum(detoast_external_attr((struct varlena *)
 															DatumGetPointer(val)));
 				myState->tofree[nfree++] = val;
 			}
diff --git a/src/backend/storage/large_object/inv_api.c b/src/backend/storage/large_object/inv_api.c
index e591236343..263d5be12e 100644
--- a/src/backend/storage/large_object/inv_api.c
+++ b/src/backend/storage/large_object/inv_api.c
@@ -180,7 +180,7 @@ getdatafield(Form_pg_largeobject tuple,
 	if (VARATT_IS_EXTENDED(datafield))
 	{
 		datafield = (bytea *)
-			heap_tuple_untoast_attr((struct varlena *) datafield);
+			detoast_attr((struct varlena *) datafield);
 		freeit = true;
 	}
 	len = VARSIZE(datafield) - VARHDRSZ;
diff --git a/src/backend/utils/adt/expandedrecord.c b/src/backend/utils/adt/expandedrecord.c
index 369432d53c..d99d370b17 100644
--- a/src/backend/utils/adt/expandedrecord.c
+++ b/src/backend/utils/adt/expandedrecord.c
@@ -1159,7 +1159,7 @@ expanded_record_set_field_internal(ExpandedRecordHeader *erh, int fnumber,
 			{
 				/* Detoasting should be done in short-lived context. */
 				oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh));
-				newValue = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *) DatumGetPointer(newValue)));
+				newValue = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newValue)));
 				MemoryContextSwitchTo(oldcxt);
 			}
 			else
@@ -1305,7 +1305,7 @@ expanded_record_set_fields(ExpandedRecordHeader *erh,
 					if (expand_external)
 					{
 						/* Detoast as requested while copying the value */
-						newValue = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *) DatumGetPointer(newValue)));
+						newValue = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newValue)));
 					}
 					else
 					{
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 0484adb984..099ebd779b 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -1739,7 +1739,7 @@ struct varlena *
 pg_detoast_datum(struct varlena *datum)
 {
 	if (VARATT_IS_EXTENDED(datum))
-		return heap_tuple_untoast_attr(datum);
+		return detoast_attr(datum);
 	else
 		return datum;
 }
@@ -1748,7 +1748,7 @@ struct varlena *
 pg_detoast_datum_copy(struct varlena *datum)
 {
 	if (VARATT_IS_EXTENDED(datum))
-		return heap_tuple_untoast_attr(datum);
+		return detoast_attr(datum);
 	else
 	{
 		/* Make a modifiable copy of the varlena object */
@@ -1764,14 +1764,14 @@ struct varlena *
 pg_detoast_datum_slice(struct varlena *datum, int32 first, int32 count)
 {
 	/* Only get the specified portion from the toast rel */
-	return heap_tuple_untoast_attr_slice(datum, first, count);
+	return detoast_attr_slice(datum, first, count);
 }
 
 struct varlena *
 pg_detoast_datum_packed(struct varlena *datum)
 {
 	if (VARATT_IS_COMPRESSED(datum) || VARATT_IS_EXTERNAL(datum))
-		return heap_tuple_untoast_attr(datum);
+		return detoast_attr(datum);
 	else
 		return datum;
 }
diff --git a/src/include/access/detoast.h b/src/include/access/detoast.h
index 02029a991f..baf3dc2591 100644
--- a/src/include/access/detoast.h
+++ b/src/include/access/detoast.h
@@ -44,34 +44,34 @@ do { \
 #define INDIRECT_POINTER_SIZE (VARHDRSZ_EXTERNAL + sizeof(varatt_indirect))
 
 /* ----------
- * heap_tuple_fetch_attr() -
+ * detoast_external_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);
+extern struct varlena *detoast_external_attr(struct varlena *attr);
 
 /* ----------
- * heap_tuple_untoast_attr() -
+ * detoast_attr() -
  *
  *		Fully detoasts one attribute, fetching and/or decompressing
  *		it as needed.
  * ----------
  */
-extern struct varlena *heap_tuple_untoast_attr(struct varlena *attr);
+extern struct varlena *detoast_attr(struct varlena *attr);
 
 /* ----------
- * heap_tuple_untoast_attr_slice() -
+ * detoast_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);
+extern struct varlena *detoast_attr_slice(struct varlena *attr,
+										  int32 sliceoffset,
+										  int32 slicelength);
 
 /* ----------
  * toast_raw_datum_size -
diff --git a/src/include/access/heaptoast.h b/src/include/access/heaptoast.h
index 07d36ac968..65765088d6 100644
--- a/src/include/access/heaptoast.h
+++ b/src/include/access/heaptoast.h
@@ -89,14 +89,13 @@
 	 VARHDRSZ)
 
 /* ----------
- * toast_insert_or_update -
+ * heap_toast_insert_or_update -
  *
  *	Called by heap_insert() and heap_update().
  * ----------
  */
-extern HeapTuple toast_insert_or_update(Relation rel,
-										HeapTuple newtup, HeapTuple oldtup,
-										int options);
+extern HeapTuple heap_toast_insert_or_update(Relation rel, HeapTuple newtup,
+											 HeapTuple oldtup, int options);
 
 /* ----------
  * toast_delete -
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index cb2e19cda4..345065e305 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8344,7 +8344,7 @@ assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var,
 		 * pain, but there's little choice.
 		 */
 		oldcxt = MemoryContextSwitchTo(get_eval_mcontext(estate));
-		detoasted = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *) DatumGetPointer(newvalue)));
+		detoasted = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newvalue)));
 		MemoryContextSwitchTo(oldcxt);
 		/* Now's a good time to not leak the input value if it's freeable */
 		if (freeable)
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
index 826556eb29..2bbeaa0460 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -558,7 +558,7 @@ make_tuple_indirect(PG_FUNCTION_ARGS)
 
 		/* copy datum, so it still lives later */
 		if (VARATT_IS_EXTERNAL_ONDISK(attr))
-			attr = heap_tuple_fetch_attr(attr);
+			attr = detoast_external_attr(attr);
 		else
 		{
 			struct varlena *oldattr = attr;
-- 
2.17.2 (Apple Git-113)

#18Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#17)
Re: tableam vs. TOAST

Hi,

On 2019-09-05 13:42:40 -0400, Robert Haas wrote:

Done, thanks. Here's the rest again with the additional rename added
to 0003 (formerly 0004). I think it's probably OK to go ahead with
that stuff, too, but I'll wait a bit to see if anyone wants to raise
more objections.

Well, I still dislike making the toast chunk size configurable in a
halfhearted manner.

Greetings,

Andres Freund

#19Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#18)
Re: tableam vs. TOAST

On Thu, Sep 5, 2019 at 3:10 PM Andres Freund <andres@anarazel.de> wrote:

On 2019-09-05 13:42:40 -0400, Robert Haas wrote:

Done, thanks. Here's the rest again with the additional rename added
to 0003 (formerly 0004). I think it's probably OK to go ahead with
that stuff, too, but I'll wait a bit to see if anyone wants to raise
more objections.

Well, I still dislike making the toast chunk size configurable in a
halfhearted manner.

So, I'd be willing to look into that some more. But how about if I
commit the next patch in the series first? I think this comment is
really about the second patch in the series, "Allow TOAST tables to be
implemented using table AMs other than heap," and it's fair to point
out that, since that patch extends table AM, we're somewhat committed
to it once we put it in. But "Create an API for inserting and
deleting rows in TOAST tables." is just refactoring, and I don't see
what we gain from waiting to commit that part.

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

#20Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#18)
Re: tableam vs. TOAST

Andres Freund <andres@anarazel.de> writes:

Well, I still dislike making the toast chunk size configurable in a
halfhearted manner.

It's hard to make it fully configurable without breaking our on-disk
storage, because of the lack of any explicit representation of the chunk
size in TOAST data. You have to "just know" how big the chunks are
supposed to be.

However, it's reasonable to ask why we should treat it as an AM property,
especially a fixed AM property as this has it. If somebody does
reimplement toast logic in some other AM, they might well decide it's
worth the storage cost to be more flexible about the chunk size ... but
too bad, this design won't let them do it.

I don't entirely understand why relation_toast_am is a callback
while toast_max_chunk_size isn't, either. Why would they not both
be callbacks? That would at least let an AM set a per-relation
max chunk size, if it wanted.

It seems like this design throws away most of the benefit of a fixed
chunk size (mostly, being able to do relevant modulo arithmetic with
shifts and masks rather than full-fledged integer division) without
getting much of anything in return.

regards, tom lane

#21Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#20)
Re: tableam vs. TOAST

On Thu, Sep 5, 2019 at 3:36 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

Andres Freund <andres@anarazel.de> writes:

Well, I still dislike making the toast chunk size configurable in a
halfhearted manner.

It's hard to make it fully configurable without breaking our on-disk
storage, because of the lack of any explicit representation of the chunk
size in TOAST data. You have to "just know" how big the chunks are
supposed to be.

There was a concrete proposal about this from Andres here, down at the
bottom of the email:

/messages/by-id/20190802224251.lsxw4o5ebn2ng5ey@alap3.anarazel.de

Basically, detoasting would tolerate whatever chunk size it finds, and
the slice-fetching logic would get complicated.

However, it's reasonable to ask why we should treat it as an AM property,
especially a fixed AM property as this has it. If somebody does
reimplement toast logic in some other AM, they might well decide it's
worth the storage cost to be more flexible about the chunk size ... but
too bad, this design won't let them do it.

Fair complaint. The main reason I want to treat it as an AM property
is that TOAST_TUPLE_THRESHOLD is defined in terms of heap-specific
constants, and having other AMs include heap-specific header files
seems like a thing we should try hard to avoid. Once you're indirectly
including htup_details.h in every AM in existence, it's going to be
hard to be sure that you've got no other dependencies on the current
heap AM. But I agree that making it not a fixed value could be useful.
One benefit of it would be that you could just change the value, even
for the current heap, without breaking access to already-toasted data.

It seems like this design throws away most of the benefit of a fixed
chunk size (mostly, being able to do relevant modulo arithmetic with
shifts and masks rather than full-fledged integer division) without
getting much of anything in return.

I don't think you're really getting that particular benefit, because
TOAST_TUPLE_THRESHOLD and TOAST_TUPLE_TARGET are not going to end up
as powers of two. But you do get the benefit of working with
constants instead of a value determined at runtime.

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

#22Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#19)
Re: tableam vs. TOAST

On 2019-09-05 15:27:28 -0400, Robert Haas wrote:

On Thu, Sep 5, 2019 at 3:10 PM Andres Freund <andres@anarazel.de> wrote:

On 2019-09-05 13:42:40 -0400, Robert Haas wrote:

Done, thanks. Here's the rest again with the additional rename added
to 0003 (formerly 0004). I think it's probably OK to go ahead with
that stuff, too, but I'll wait a bit to see if anyone wants to raise
more objections.

Well, I still dislike making the toast chunk size configurable in a
halfhearted manner.

So, I'd be willing to look into that some more. But how about if I
commit the next patch in the series first? I think this comment is
really about the second patch in the series, "Allow TOAST tables to be
implemented using table AMs other than heap," and it's fair to point
out that, since that patch extends table AM, we're somewhat committed
to it once we put it in. But "Create an API for inserting and
deleting rows in TOAST tables." is just refactoring, and I don't see
what we gain from waiting to commit that part.

Yea, makes sense to me.

#23Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#22)
2 attachment(s)
Re: tableam vs. TOAST

On Thu, Sep 5, 2019 at 4:07 PM Andres Freund <andres@anarazel.de> wrote:

Yea, makes sense to me.

OK, done. Here's the remaining patches again, with a slight update to
the renaming patch (now 0002). In the last version, I renamed
toast_insert_or_update to heap_toast_insert_or_update but did not
rename toast_delete to heap_toast_delete. Actually, I'm not seeing
any particular reason not to go ahead and push the renaming patch at
this point also. I guess there's a question as to whether I should
more aggressively add "heap" to the names of the other functions in
heaptoast.h, but I'm kinda "meh" about that. It seems likely that
other AMs will need their own versions of toast_insert_or_update() and
toast_delete(), but they shouldn't really need their own version of,
say, toast_flatten_tuple_to_datum(), and the point there is that we're
building a DatumTuple, so calling it
heap_toast_flatten_tuple_to_datum() seems almost misleading. I'm
inclined to leave all that stuff alone for now.

0001 needs more thought, as discussed.

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

Attachments:

v6-0002-Rename-some-toasting-functions-based-on-whether-t.patchapplication/octet-stream; name=v6-0002-Rename-some-toasting-functions-based-on-whether-t.patchDownload
From 227ddd67a57758d9fa55c1b4b0dea33e297f454b Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 8 Jul 2019 12:34:37 -0400
Subject: [PATCH v6 2/2] Rename some toasting functions based on whether they
 are heap-specific.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The old names for the attribute-detoasting functions names included the
word "heap," which seems outdated now that the heap is only one of
potentially many table access methods.

On the other hand, toast_insert_or_update and toast_delete are
heap-specific, so rename them by adding "heap_" as a prefix.

Patch by me, reviewed and tested by Prabhat Sabu, Thomas Munro,
Andres Freund, and Álvaro Herrera.

Discussion: http://postgr.es/m/CA+TgmoZv-=2iWM4jcw5ZhJeL18HF96+W1yJeYrnGMYdkFFnEpQ@mail.gmail.com
---
 src/backend/access/common/detoast.c        | 26 +++++++++++-----------
 src/backend/access/common/indextuple.c     |  2 +-
 src/backend/access/heap/heapam.c           |  8 +++----
 src/backend/access/heap/heaptoast.c        | 25 +++++++++++----------
 src/backend/access/heap/rewriteheap.c      |  4 ++--
 src/backend/access/table/toast_helper.c    |  4 ++--
 src/backend/executor/tstoreReceiver.c      |  2 +-
 src/backend/storage/large_object/inv_api.c |  2 +-
 src/backend/utils/adt/expandedrecord.c     |  4 ++--
 src/backend/utils/fmgr/fmgr.c              |  8 +++----
 src/include/access/detoast.h               | 16 ++++++-------
 src/include/access/heaptoast.h             | 13 +++++------
 src/pl/plpgsql/src/pl_exec.c               |  2 +-
 src/test/regress/regress.c                 |  2 +-
 14 files changed, 59 insertions(+), 59 deletions(-)

diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c
index 36b68e35fb..78256f91ca 100644
--- a/src/backend/access/common/detoast.c
+++ b/src/backend/access/common/detoast.c
@@ -31,7 +31,7 @@ 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 -
+ * detoast_external_attr -
  *
  *	Public entry point to get back a toasted value from
  *	external source (possibly still in compressed format).
@@ -43,7 +43,7 @@ static struct varlena *toast_decompress_datum_slice(struct varlena *attr, int32
  * ----------
  */
 struct varlena *
-heap_tuple_fetch_attr(struct varlena *attr)
+detoast_external_attr(struct varlena *attr)
 {
 	struct varlena *result;
 
@@ -69,7 +69,7 @@ heap_tuple_fetch_attr(struct varlena *attr)
 
 		/* recurse if value is still external in some other way */
 		if (VARATT_IS_EXTERNAL(attr))
-			return heap_tuple_fetch_attr(attr);
+			return detoast_external_attr(attr);
 
 		/*
 		 * Copy into the caller's memory context, in case caller tries to
@@ -104,7 +104,7 @@ heap_tuple_fetch_attr(struct varlena *attr)
 
 
 /* ----------
- * heap_tuple_untoast_attr -
+ * detoast_attr -
  *
  *	Public entry point to get back a toasted value from compression
  *	or external storage.  The result is always non-extended varlena form.
@@ -114,7 +114,7 @@ heap_tuple_fetch_attr(struct varlena *attr)
  * ----------
  */
 struct varlena *
-heap_tuple_untoast_attr(struct varlena *attr)
+detoast_attr(struct varlena *attr)
 {
 	if (VARATT_IS_EXTERNAL_ONDISK(attr))
 	{
@@ -145,7 +145,7 @@ heap_tuple_untoast_attr(struct varlena *attr)
 		Assert(!VARATT_IS_EXTERNAL_INDIRECT(attr));
 
 		/* recurse in case value is still extended in some other way */
-		attr = heap_tuple_untoast_attr(attr);
+		attr = detoast_attr(attr);
 
 		/* if it isn't, we'd better copy it */
 		if (attr == (struct varlena *) redirect.pointer)
@@ -162,7 +162,7 @@ heap_tuple_untoast_attr(struct varlena *attr)
 		/*
 		 * This is an expanded-object pointer --- get flat format
 		 */
-		attr = heap_tuple_fetch_attr(attr);
+		attr = detoast_external_attr(attr);
 		/* flatteners are not allowed to produce compressed/short output */
 		Assert(!VARATT_IS_EXTENDED(attr));
 	}
@@ -193,14 +193,14 @@ heap_tuple_untoast_attr(struct varlena *attr)
 
 
 /* ----------
- * heap_tuple_untoast_attr_slice -
+ * detoast_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,
+detoast_attr_slice(struct varlena *attr,
 							  int32 sliceoffset, int32 slicelength)
 {
 	struct varlena *preslice;
@@ -230,13 +230,13 @@ heap_tuple_untoast_attr_slice(struct varlena *attr,
 		/* nested indirect Datums aren't allowed */
 		Assert(!VARATT_IS_EXTERNAL_INDIRECT(redirect.pointer));
 
-		return heap_tuple_untoast_attr_slice(redirect.pointer,
+		return detoast_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);
+		/* pass it off to detoast_external_attr to flatten */
+		preslice = detoast_external_attr(attr);
 	}
 	else
 		preslice = attr;
@@ -737,7 +737,7 @@ toast_decompress_datum(struct varlena *attr)
  * toast_decompress_datum_slice -
  *
  * Decompress the front of a compressed version of a varlena datum.
- * offset handling happens in heap_tuple_untoast_attr_slice.
+ * offset handling happens in detoast_attr_slice.
  * Here we just decompress a slice from the front.
  */
 static struct varlena *
diff --git a/src/backend/access/common/indextuple.c b/src/backend/access/common/indextuple.c
index 07586201b9..8a5f5227a3 100644
--- a/src/backend/access/common/indextuple.c
+++ b/src/backend/access/common/indextuple.c
@@ -89,7 +89,7 @@ index_form_tuple(TupleDesc tupleDescriptor,
 		if (VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
 		{
 			untoasted_values[i] =
-				PointerGetDatum(heap_tuple_fetch_attr((struct varlena *)
+				PointerGetDatum(detoast_external_attr((struct varlena *)
 													  DatumGetPointer(values[i])));
 			untoasted_free[i] = true;
 		}
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 9e7cafa8e6..08bd087bc8 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2085,7 +2085,7 @@ heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
 		return tup;
 	}
 	else if (HeapTupleHasExternal(tup) || tup->t_len > TOAST_TUPLE_THRESHOLD)
-		return toast_insert_or_update(relation, tup, NULL, options);
+		return heap_toast_insert_or_update(relation, tup, NULL, options);
 	else
 		return tup;
 }
@@ -2809,7 +2809,7 @@ l1:
 		Assert(!HeapTupleHasExternal(&tp));
 	}
 	else if (HeapTupleHasExternal(&tp))
-		toast_delete(relation, &tp, false, 0);
+		heap_toast_delete(relation, &tp, false, 0);
 
 	/*
 	 * Mark tuple for invalidation from system caches at next command
@@ -3504,7 +3504,7 @@ l2:
 		if (need_toast)
 		{
 			/* Note we always use WAL and FSM during updates */
-			heaptup = toast_insert_or_update(relation, newtup, &oldtup, 0);
+			heaptup = heap_toast_insert_or_update(relation, newtup, &oldtup, 0);
 			newtupsize = MAXALIGN(heaptup->t_len);
 		}
 		else
@@ -5673,7 +5673,7 @@ heap_abort_speculative(Relation relation, ItemPointer tid, uint32 specToken)
 	if (HeapTupleHasExternal(&tp))
 	{
 		Assert(!IsToastRelation(relation));
-		toast_delete(relation, &tp, true, specToken);
+		heap_toast_delete(relation, &tp, true, specToken);
 	}
 
 	/*
diff --git a/src/backend/access/heap/heaptoast.c b/src/backend/access/heap/heaptoast.c
index c0acefc97e..cca916a39f 100644
--- a/src/backend/access/heap/heaptoast.c
+++ b/src/backend/access/heap/heaptoast.c
@@ -12,11 +12,11 @@
  *
  *
  * INTERFACE ROUTINES
- *		toast_insert_or_update -
+ *		heap_toast_insert_or_update -
  *			Try to make a given tuple fit into one page by compressing
  *			or moving off attributes
  *
- *		toast_delete -
+ *		heap_toast_delete -
  *			Reclaim toast storage when a tuple is deleted
  *
  *-------------------------------------------------------------------------
@@ -32,14 +32,14 @@
 
 
 /* ----------
- * toast_delete -
+ * heap_toast_delete -
  *
  *	Cascaded delete toast-entries on DELETE
  * ----------
  */
 void
-toast_delete(Relation rel, HeapTuple oldtup, bool is_speculative,
-			 uint32 specToken)
+heap_toast_delete(Relation rel, HeapTuple oldtup, bool is_speculative,
+				  uint32 specToken)
 {
 	TupleDesc	tupleDesc;
 	Datum		toast_values[MaxHeapAttributeNumber];
@@ -75,7 +75,7 @@ toast_delete(Relation rel, HeapTuple oldtup, bool is_speculative,
 
 
 /* ----------
- * toast_insert_or_update -
+ * heap_toast_insert_or_update -
  *
  *	Delete no-longer-used toast-entries and create new ones to
  *	make the new tuple fit on INSERT or UPDATE
@@ -93,8 +93,8 @@ toast_delete(Relation rel, HeapTuple oldtup, bool is_speculative,
  * ----------
  */
 HeapTuple
-toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
-					   int options)
+heap_toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
+							int options)
 {
 	HeapTuple	result_tuple;
 	TupleDesc	tupleDesc;
@@ -376,7 +376,7 @@ toast_flatten_tuple(HeapTuple tup, TupleDesc tupleDesc)
 			new_value = (struct varlena *) DatumGetPointer(toast_values[i]);
 			if (VARATT_IS_EXTERNAL(new_value))
 			{
-				new_value = heap_tuple_fetch_attr(new_value);
+				new_value = detoast_external_attr(new_value);
 				toast_values[i] = PointerGetDatum(new_value);
 				toast_free[i] = true;
 			}
@@ -491,7 +491,7 @@ toast_flatten_tuple_to_datum(HeapTupleHeader tup,
 			if (VARATT_IS_EXTERNAL(new_value) ||
 				VARATT_IS_COMPRESSED(new_value))
 			{
-				new_value = heap_tuple_untoast_attr(new_value);
+				new_value = detoast_attr(new_value);
 				toast_values[i] = PointerGetDatum(new_value);
 				toast_free[i] = true;
 			}
@@ -501,7 +501,8 @@ toast_flatten_tuple_to_datum(HeapTupleHeader tup,
 	/*
 	 * Calculate the new size of the tuple.
 	 *
-	 * This should match the reconstruction code in toast_insert_or_update.
+	 * This should match the reconstruction code in
+	 * heap_toast_insert_or_update.
 	 */
 	new_header_len = SizeofHeapTupleHeader;
 	if (has_nulls)
@@ -590,7 +591,7 @@ toast_build_flattened_tuple(TupleDesc tupleDesc,
 			new_value = (struct varlena *) DatumGetPointer(new_values[i]);
 			if (VARATT_IS_EXTERNAL(new_value))
 			{
-				new_value = heap_tuple_fetch_attr(new_value);
+				new_value = detoast_external_attr(new_value);
 				new_values[i] = PointerGetDatum(new_value);
 				freeable_values[num_to_free++] = (Pointer) new_value;
 			}
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 0172a13957..7c98a42b8b 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -664,8 +664,8 @@ raw_heap_insert(RewriteState state, HeapTuple tup)
 		 */
 		options |= HEAP_INSERT_NO_LOGICAL;
 
-		heaptup = toast_insert_or_update(state->rs_new_rel, tup, NULL,
-										 options);
+		heaptup = heap_toast_insert_or_update(state->rs_new_rel, tup, NULL,
+											  options);
 	}
 	else
 		heaptup = tup;
diff --git a/src/backend/access/table/toast_helper.c b/src/backend/access/table/toast_helper.c
index e33918a7f4..dedc123e31 100644
--- a/src/backend/access/table/toast_helper.c
+++ b/src/backend/access/table/toast_helper.c
@@ -136,9 +136,9 @@ toast_tuple_init(ToastTupleContext *ttc)
 			{
 				ttc->ttc_attr[i].tai_oldexternal = new_value;
 				if (att->attstorage == 'p')
-					new_value = heap_tuple_untoast_attr(new_value);
+					new_value = detoast_attr(new_value);
 				else
-					new_value = heap_tuple_fetch_attr(new_value);
+					new_value = detoast_external_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);
diff --git a/src/backend/executor/tstoreReceiver.c b/src/backend/executor/tstoreReceiver.c
index c0c81c82da..6306b7d0bd 100644
--- a/src/backend/executor/tstoreReceiver.c
+++ b/src/backend/executor/tstoreReceiver.c
@@ -133,7 +133,7 @@ tstoreReceiveSlot_detoast(TupleTableSlot *slot, DestReceiver *self)
 		{
 			if (VARATT_IS_EXTERNAL(DatumGetPointer(val)))
 			{
-				val = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *)
+				val = PointerGetDatum(detoast_external_attr((struct varlena *)
 															DatumGetPointer(val)));
 				myState->tofree[nfree++] = val;
 			}
diff --git a/src/backend/storage/large_object/inv_api.c b/src/backend/storage/large_object/inv_api.c
index e591236343..263d5be12e 100644
--- a/src/backend/storage/large_object/inv_api.c
+++ b/src/backend/storage/large_object/inv_api.c
@@ -180,7 +180,7 @@ getdatafield(Form_pg_largeobject tuple,
 	if (VARATT_IS_EXTENDED(datafield))
 	{
 		datafield = (bytea *)
-			heap_tuple_untoast_attr((struct varlena *) datafield);
+			detoast_attr((struct varlena *) datafield);
 		freeit = true;
 	}
 	len = VARSIZE(datafield) - VARHDRSZ;
diff --git a/src/backend/utils/adt/expandedrecord.c b/src/backend/utils/adt/expandedrecord.c
index 369432d53c..d99d370b17 100644
--- a/src/backend/utils/adt/expandedrecord.c
+++ b/src/backend/utils/adt/expandedrecord.c
@@ -1159,7 +1159,7 @@ expanded_record_set_field_internal(ExpandedRecordHeader *erh, int fnumber,
 			{
 				/* Detoasting should be done in short-lived context. */
 				oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh));
-				newValue = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *) DatumGetPointer(newValue)));
+				newValue = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newValue)));
 				MemoryContextSwitchTo(oldcxt);
 			}
 			else
@@ -1305,7 +1305,7 @@ expanded_record_set_fields(ExpandedRecordHeader *erh,
 					if (expand_external)
 					{
 						/* Detoast as requested while copying the value */
-						newValue = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *) DatumGetPointer(newValue)));
+						newValue = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newValue)));
 					}
 					else
 					{
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 0484adb984..099ebd779b 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -1739,7 +1739,7 @@ struct varlena *
 pg_detoast_datum(struct varlena *datum)
 {
 	if (VARATT_IS_EXTENDED(datum))
-		return heap_tuple_untoast_attr(datum);
+		return detoast_attr(datum);
 	else
 		return datum;
 }
@@ -1748,7 +1748,7 @@ struct varlena *
 pg_detoast_datum_copy(struct varlena *datum)
 {
 	if (VARATT_IS_EXTENDED(datum))
-		return heap_tuple_untoast_attr(datum);
+		return detoast_attr(datum);
 	else
 	{
 		/* Make a modifiable copy of the varlena object */
@@ -1764,14 +1764,14 @@ struct varlena *
 pg_detoast_datum_slice(struct varlena *datum, int32 first, int32 count)
 {
 	/* Only get the specified portion from the toast rel */
-	return heap_tuple_untoast_attr_slice(datum, first, count);
+	return detoast_attr_slice(datum, first, count);
 }
 
 struct varlena *
 pg_detoast_datum_packed(struct varlena *datum)
 {
 	if (VARATT_IS_COMPRESSED(datum) || VARATT_IS_EXTERNAL(datum))
-		return heap_tuple_untoast_attr(datum);
+		return detoast_attr(datum);
 	else
 		return datum;
 }
diff --git a/src/include/access/detoast.h b/src/include/access/detoast.h
index 02029a991f..baf3dc2591 100644
--- a/src/include/access/detoast.h
+++ b/src/include/access/detoast.h
@@ -44,34 +44,34 @@ do { \
 #define INDIRECT_POINTER_SIZE (VARHDRSZ_EXTERNAL + sizeof(varatt_indirect))
 
 /* ----------
- * heap_tuple_fetch_attr() -
+ * detoast_external_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);
+extern struct varlena *detoast_external_attr(struct varlena *attr);
 
 /* ----------
- * heap_tuple_untoast_attr() -
+ * detoast_attr() -
  *
  *		Fully detoasts one attribute, fetching and/or decompressing
  *		it as needed.
  * ----------
  */
-extern struct varlena *heap_tuple_untoast_attr(struct varlena *attr);
+extern struct varlena *detoast_attr(struct varlena *attr);
 
 /* ----------
- * heap_tuple_untoast_attr_slice() -
+ * detoast_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);
+extern struct varlena *detoast_attr_slice(struct varlena *attr,
+										  int32 sliceoffset,
+										  int32 slicelength);
 
 /* ----------
  * toast_raw_datum_size -
diff --git a/src/include/access/heaptoast.h b/src/include/access/heaptoast.h
index 07d36ac968..23f62dc4af 100644
--- a/src/include/access/heaptoast.h
+++ b/src/include/access/heaptoast.h
@@ -89,23 +89,22 @@
 	 VARHDRSZ)
 
 /* ----------
- * toast_insert_or_update -
+ * heap_toast_insert_or_update -
  *
  *	Called by heap_insert() and heap_update().
  * ----------
  */
-extern HeapTuple toast_insert_or_update(Relation rel,
-										HeapTuple newtup, HeapTuple oldtup,
-										int options);
+extern HeapTuple heap_toast_insert_or_update(Relation rel, HeapTuple newtup,
+											 HeapTuple oldtup, int options);
 
 /* ----------
- * toast_delete -
+ * heap_toast_delete -
  *
  *	Called by heap_delete().
  * ----------
  */
-extern void toast_delete(Relation rel, HeapTuple oldtup,
-			 bool is_speculative, uint32 specToken);
+extern void heap_toast_delete(Relation rel, HeapTuple oldtup,
+							  bool is_speculative, uint32 specToken);
 
 /* ----------
  * toast_flatten_tuple -
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index cb2e19cda4..345065e305 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8344,7 +8344,7 @@ assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var,
 		 * pain, but there's little choice.
 		 */
 		oldcxt = MemoryContextSwitchTo(get_eval_mcontext(estate));
-		detoasted = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *) DatumGetPointer(newvalue)));
+		detoasted = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newvalue)));
 		MemoryContextSwitchTo(oldcxt);
 		/* Now's a good time to not leak the input value if it's freeable */
 		if (freeable)
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
index 826556eb29..2bbeaa0460 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -558,7 +558,7 @@ make_tuple_indirect(PG_FUNCTION_ARGS)
 
 		/* copy datum, so it still lives later */
 		if (VARATT_IS_EXTERNAL_ONDISK(attr))
-			attr = heap_tuple_fetch_attr(attr);
+			attr = detoast_external_attr(attr);
 		else
 		{
 			struct varlena *oldattr = attr;
-- 
2.17.2 (Apple Git-113)

v6-0001-Allow-TOAST-tables-to-be-implemented-using-table-.patchapplication/octet-stream; name=v6-0001-Allow-TOAST-tables-to-be-implemented-using-table-.patchDownload
From 9449efff4983b9650984855863a4ca09bd8c9a2f Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Thu, 1 Aug 2019 10:37:02 -0400
Subject: [PATCH v6 1/2] Allow TOAST tables to be implemented using table AMs
 other than heap.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

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.

Patch by me, reviewed and tested by Prabhat Sabu, Thomas Munro,
Andres Freund, and Álvaro Herrera.

Discussion: http://postgr.es/m/CA+TgmoZv-=2iWM4jcw5ZhJeL18HF96+W1yJeYrnGMYdkFFnEpQ@mail.gmail.com
---
 src/backend/access/common/detoast.c         |  62 +++++-----
 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        |  15 ++-
 14 files changed, 284 insertions(+), 148 deletions(-)

diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c
index c8b49d6a12..36b68e35fb 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,8 +304,7 @@ toast_fetch_datum(struct varlena *attr)
 	Relation   *toastidxs;
 	ScanKeyData toastkey;
 	SysScanDesc toastscan;
-	HeapTuple	ttup;
-	TupleDesc	toasttupDesc;
+	TupleTableSlot *slot;
 	struct varlena *result;
 	struct varatt_external toast_pointer;
 	int32		ressize;
@@ -312,11 +312,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 +326,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);
 
@@ -339,7 +338,9 @@ toast_fetch_datum(struct varlena *attr)
 	 * 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;
+	numchunks = ((ressize - 1) / max_chunk_size) + 1;
 
 	/* Look for the valid index of the toast relation */
 	validIndex = toast_open_indexes(toastrel,
@@ -367,15 +368,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;
@@ -409,23 +410,23 @@ toast_fetch_datum(struct varlena *attr)
 									 RelationGetRelationName(toastrel))));
 		if (residx < numchunks - 1)
 		{
-			if (chunksize != TOAST_MAX_CHUNK_SIZE)
+			if (chunksize != max_chunk_size)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATA_CORRUPTED),
 						 errmsg_internal("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)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATA_CORRUPTED),
 						 errmsg_internal("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))));
@@ -442,7 +443,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);
 
@@ -508,6 +509,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))
@@ -523,7 +525,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)
 	{
@@ -541,19 +542,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,
@@ -642,19 +646,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));
@@ -677,7 +681,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 e9544822bf..9e7cafa8e6 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
@@ -5564,7 +5564,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;
@@ -5673,7 +5673,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 2dd8821fac..97a7433092 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -28,6 +28,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"
@@ -292,7 +293,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);
@@ -2041,6 +2042,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
@@ -2539,6 +2549,8 @@ static const TableAmRoutine heapam_methods = {
 
 	.relation_size = table_block_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 fbf9294598..c0acefc97e 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 2599b5d342..233ba24261 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 7532b4f865..e33918a7f4 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 858bcb6bc9..6ee0c6efa7 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 7f81703b78..521fd6232d 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.
@@ -1603,6 +1624,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 7cefacb0ea..cfb4ae0385 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
@@ -106,10 +118,10 @@ extern int	toast_tuple_find_biggest_attribute(ToastTupleContext *ttc,
 											   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 494b07a4b1..96f61baf80 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,16 @@ 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)

#24Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#12)
Re: tableam vs. TOAST

On Fri, Aug 2, 2019 at 6:42 PM Andres Freund <andres@anarazel.de> wrote:

The obvious problem with this is the slice fetching logic. For slices
with an offset of 0, it's obviously trivial to implement. For the higher
slice logic, I'd assume we'd have to fetch the first slice by estimating
where the start chunk is based on the current suggest chunk size, and
restarting the scan earlier/later if not accurate. In most cases it'll
be accurate, so we'd not loose efficiency.

Dilip and I were discussing this proposal this morning, and after
talking to him, I don't quite see how to make this work without
significant loss of efficiency. Suppose that, based on the estimated
chunk size, you decide that there are probably 10 chunks and that the
value that you need is probably located in chunk 6. So you fetch chunk
6. Happily, chunk 6 is the size that you expect, so you extract the
bytes that you need and go on your way.

But ... what if there are actually 6 chunks, not 10, and the first
five are bigger than you expected, and the reason why the size of
chunk 6 matched your expectation because it was the last chunk and
thus smaller than the rest? Now you've just returned the wrong answer.

AFAICS, there's no way to detect that except to always read at least
two chunks, which seems like a pretty heavy price to pay. It doesn't
cost if you were going to need to read at least 2 chunks anyway, but
if you were only going to need to read 1, having to fetch 2 stinks.

Actually, when I initially read your proposal, I thought you were
proposing to relax things such that the chunks didn't even have to all
be the same size. That seems like it would be something cool that
could potentially be leveraged not only by AMs but perhaps also by
data types that want to break up their data strategically to cater to
future access patterns. But then there's really no way to make slicing
work without always reading from the beginning.

There would be no big problem here - in either scenario - if each
chunk contained the byte-offset of that chunk relative to the start of
the datum. You could guess wildly and always know whether or not you
had got it right without reading any extra data. But that's not the
case.

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

#25Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#23)
1 attachment(s)
Re: tableam vs. TOAST

On Fri, Sep 6, 2019 at 10:59 AM Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Sep 5, 2019 at 4:07 PM Andres Freund <andres@anarazel.de> wrote:

Yea, makes sense to me.

OK, done. Here's the remaining patches again, with a slight update to
the renaming patch (now 0002). In the last version, I renamed
toast_insert_or_update to heap_toast_insert_or_update but did not
rename toast_delete to heap_toast_delete. Actually, I'm not seeing
any particular reason not to go ahead and push the renaming patch at
this point also.

And, hearing no objections, done.

Here's the last patch back, rebased over that renaming. Although I
think that Andres (and Tom) are probably right that there's room for
improvement here, I currently don't see a way around the issues I
wrote about in /messages/by-id/CA+Tgmoa0zFcaCpOJCsSpOLLGpzTVfSyvcVB-USS8YoKzMO51Yw@mail.gmail.com
-- so not quite sure where to go next. Hopefully Andres or someone
else will give me a quick whack with the cluebat if I'm missing
something obvious.

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

Attachments:

v7-0001-Allow-TOAST-tables-to-be-implemented-using-table-.patchapplication/octet-stream; name=v7-0001-Allow-TOAST-tables-to-be-implemented-using-table-.patchDownload
From 1d3dc0c37714f7d9ba1f168e0dbc54578fab3c88 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 4 Oct 2019 14:24:28 -0400
Subject: [PATCH v7] Allow TOAST tables to be implemented using table AMs other
 than heap.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

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.

Patch by me, reviewed and tested by Prabhat Sabu, Thomas Munro,
Andres Freund, and Álvaro Herrera.

Discussion: http://postgr.es/m/CA+TgmoZv-=2iWM4jcw5ZhJeL18HF96+W1yJeYrnGMYdkFFnEpQ@mail.gmail.com
---
 src/backend/access/common/detoast.c         |  62 +++++-----
 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              |   2 +-
 src/include/access/tableam.h                |  31 +++++
 src/include/access/toast_helper.h           |  18 ++-
 src/include/access/toast_internals.h        |  15 ++-
 14 files changed, 283 insertions(+), 148 deletions(-)

diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c
index b25ca6810b..8550587c08 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"
@@ -327,8 +328,7 @@ toast_fetch_datum(struct varlena *attr)
 	Relation   *toastidxs;
 	ScanKeyData toastkey;
 	SysScanDesc toastscan;
-	HeapTuple	ttup;
-	TupleDesc	toasttupDesc;
+	TupleTableSlot *slot;
 	struct varlena *result;
 	struct varatt_external toast_pointer;
 	int32		ressize;
@@ -336,11 +336,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))
@@ -350,7 +350,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);
 
@@ -363,7 +362,9 @@ toast_fetch_datum(struct varlena *attr)
 	 * 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;
+	numchunks = ((ressize - 1) / max_chunk_size) + 1;
 
 	/* Look for the valid index of the toast relation */
 	validIndex = toast_open_indexes(toastrel,
@@ -391,15 +392,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;
@@ -433,23 +434,23 @@ toast_fetch_datum(struct varlena *attr)
 									 RelationGetRelationName(toastrel))));
 		if (residx < numchunks - 1)
 		{
-			if (chunksize != TOAST_MAX_CHUNK_SIZE)
+			if (chunksize != max_chunk_size)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATA_CORRUPTED),
 						 errmsg_internal("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)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATA_CORRUPTED),
 						 errmsg_internal("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))));
@@ -466,7 +467,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);
 
@@ -534,6 +535,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))
@@ -550,7 +552,6 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length)
 	Assert(!VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer) || 0 == sliceoffset);
 
 	attrsize = toast_pointer.va_extsize;
-	totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
 
 	if (sliceoffset >= attrsize)
 	{
@@ -579,19 +580,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,
@@ -680,19 +684,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));
@@ -715,7 +719,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 0128bb34ef..08bd087bc8 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))
-		heap_toast_delete(relation, &tp, false);
+		heap_toast_delete(relation, &tp, false, 0);
 
 	/*
 	 * Mark tuple for invalidation from system caches at next command
@@ -5564,7 +5564,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;
@@ -5673,7 +5673,7 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 	if (HeapTupleHasExternal(&tp))
 	{
 		Assert(!IsToastRelation(relation));
-		heap_toast_delete(relation, &tp, true);
+		heap_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 2dd8821fac..97a7433092 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -28,6 +28,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"
@@ -292,7 +293,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);
@@ -2041,6 +2042,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
@@ -2539,6 +2549,8 @@ static const TableAmRoutine heapam_methods = {
 
 	.relation_size = table_block_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 dcfdee4467..cca916a39f 100644
--- a/src/backend/access/heap/heaptoast.c
+++ b/src/backend/access/heap/heaptoast.c
@@ -38,7 +38,8 @@
  * ----------
  */
 void
-heap_toast_delete(Relation rel, HeapTuple oldtup, bool is_speculative)
+heap_toast_delete(Relation rel, HeapTuple oldtup, bool is_speculative,
+				  uint32 specToken)
 {
 	TupleDesc	tupleDesc;
 	Datum		toast_values[MaxHeapAttributeNumber];
@@ -68,7 +69,8 @@ heap_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 @@ heap_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 @@ heap_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 @@ heap_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 @@ heap_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 @@ heap_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 2599b5d342..233ba24261 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 7381be8669..dedc123e31 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 a813b004be..128df5e916 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
@@ -220,6 +221,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 858bcb6bc9..6ee0c6efa7 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 488a2e4a7f..23f62dc4af 100644
--- a/src/include/access/heaptoast.h
+++ b/src/include/access/heaptoast.h
@@ -104,7 +104,7 @@ extern HeapTuple heap_toast_insert_or_update(Relation rel, HeapTuple newtup,
  * ----------
  */
 extern void heap_toast_delete(Relation rel, HeapTuple oldtup,
-							  bool is_speculative);
+							  bool is_speculative, uint32 specToken);
 
 /* ----------
  * toast_flatten_tuple -
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 7f81703b78..521fd6232d 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.
@@ -1603,6 +1624,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 7cefacb0ea..cfb4ae0385 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
@@ -106,10 +118,10 @@ extern int	toast_tuple_find_biggest_attribute(ToastTupleContext *ttc,
 											   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 9bd1c97771..eb6137dfab 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.
  */
@@ -40,9 +42,16 @@ 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)

#26Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: Robert Haas (#25)
Re: tableam vs. TOAST

Hi All,

While testing the Toast patch(PG+v7 patch) I found below server crash.
System configuration:
VCPUs: 4, RAM: 8GB, Storage: 320GB

This issue is not frequently reproducible, we need to repeat the same
testcase multiple times.

CREATE OR REPLACE FUNCTION toast_chunks_cnt_func(p1 IN text)
RETURNS int AS $$
DECLARE
chunks_cnt int;
v_tbl text;
BEGIN
SELECT reltoastrelid::regclass INTO v_tbl FROM pg_class WHERE RELNAME =
p1;
EXECUTE 'SELECT count(*) FROM ' || v_tbl::regclass INTO chunks_cnt;
RETURN chunks_cnt;
END; $$ LANGUAGE PLPGSQL;

-- Server crash after multiple run of below testcase
-- ------------------------------------------------------------------------
CHECKPOINT;
CREATE TABLE toast_tab (c1 text);
\d+ toast_tab
-- ALTER table column c1 for storage as "EXTERNAL" to make sure that the
column value is pushed to the TOAST table but not COMPRESSED.
ALTER TABLE toast_tab ALTER COLUMN c1 SET STORAGE EXTERNAL;
\d+ toast_tab
\timing
INSERT INTO toast_tab
( select repeat('a', 200000)
from generate_series(1,40000) x);
\timing
SELECT reltoastrelid::regclass FROM pg_class WHERE RELNAME = 'toast_tab';
SELECT toast_chunks_cnt_func('toast_tab') "Number of chunks";
SELECT pg_column_size(t1.*) FROM toast_tab t1 limit 1;
SELECT DISTINCT SUBSTR(c1, 90000,10) FROM toast_tab;

CHECKPOINT;
\timing
UPDATE toast_tab SET c1 = UPPER(c1);
\timing
SELECT toast_chunks_cnt_func('toast_tab') "Number of chunks";
SELECT pg_column_size(t1.*) FROM toast_tab t1 limit 1;
SELECT DISTINCT SUBSTR(c1, 90000,10) FROM toast_tab;

DROP TABLE toast_tab;
-- ------------------------------------------------------------------------

-- Stacktrace as below:
[centos@host-192-168-1-249 bin]$ gdb -q -c data2/core.3151 postgres
Reading symbols from
/home/centos/PG/PGsrc/postgresql/inst/bin/postgres...done.
[New LWP 3151]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Core was generated by `postgres: checkpointer
'.
Program terminated with signal 6, Aborted.
#0 0x00007f2267d33207 in raise () from /lib64/libc.so.6
Missing separate debuginfos, use: debuginfo-install
glibc-2.17-260.el7_6.5.x86_64 keyutils-libs-1.5.8-3.el7.x86_64
krb5-libs-1.15.1-37.el7_6.x86_64 libcom_err-1.42.9-13.el7.x86_64
libselinux-2.5-14.1.el7.x86_64 openssl-libs-1.0.2k-16.el7_6.1.x86_64
pcre-8.32-17.el7.x86_64 zlib-1.2.7-18.el7.x86_64
(gdb) bt
#0 0x00007f2267d33207 in raise () from /lib64/libc.so.6
#1 0x00007f2267d348f8 in abort () from /lib64/libc.so.6
#2 0x0000000000eb3a80 in errfinish (dummy=0) at elog.c:552
#3 0x0000000000c26530 in ProcessSyncRequests () at sync.c:393
#4 0x0000000000bbbc57 in CheckPointBuffers (flags=256) at bufmgr.c:2589
#5 0x0000000000604634 in CheckPointGuts (checkPointRedo=51448358328,
flags=256) at xlog.c:8992
#6 0x0000000000603b5e in CreateCheckPoint (flags=256) at xlog.c:8781
#7 0x0000000000aed8fa in CheckpointerMain () at checkpointer.c:481
#8 0x00000000006240de in AuxiliaryProcessMain (argc=2,
argv=0x7ffe887c0880) at bootstrap.c:461
#9 0x0000000000b0e834 in StartChildProcess (type=CheckpointerProcess) at
postmaster.c:5414
#10 0x0000000000b09283 in reaper (postgres_signal_arg=17) at
postmaster.c:2995
#11 <signal handler called>
#12 0x00007f2267df1f53 in __select_nocancel () from /lib64/libc.so.6
#13 0x0000000000b05000 in ServerLoop () at postmaster.c:1682
#14 0x0000000000b0457b in PostmasterMain (argc=5, argv=0x349bce0) at
postmaster.c:1391
#15 0x0000000000971c9f in main (argc=5, argv=0x349bce0) at main.c:210
(gdb)

On Sat, Oct 5, 2019 at 12:03 AM Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Sep 6, 2019 at 10:59 AM Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Sep 5, 2019 at 4:07 PM Andres Freund <andres@anarazel.de> wrote:

Yea, makes sense to me.

OK, done. Here's the remaining patches again, with a slight update to
the renaming patch (now 0002). In the last version, I renamed
toast_insert_or_update to heap_toast_insert_or_update but did not
rename toast_delete to heap_toast_delete. Actually, I'm not seeing
any particular reason not to go ahead and push the renaming patch at
this point also.

And, hearing no objections, done.

Here's the last patch back, rebased over that renaming. Although I
think that Andres (and Tom) are probably right that there's room for
improvement here, I currently don't see a way around the issues I
wrote about in
/messages/by-id/CA+Tgmoa0zFcaCpOJCsSpOLLGpzTVfSyvcVB-USS8YoKzMO51Yw@mail.gmail.com
-- so not quite sure where to go next. Hopefully Andres or someone
else will give me a quick whack with the cluebat if I'm missing
something obvious.

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

--

With Regards,

Prabhat Kumar Sahu
Skype ID: prabhat.sahu1984
EnterpriseDB Software India Pvt. Ltd.

The Postgres Database Company

#27Robert Haas
robertmhaas@gmail.com
In reply to: Prabhat Sahu (#26)
Re: tableam vs. TOAST

On Wed, Oct 30, 2019 at 3:49 AM Prabhat Sahu <prabhat.sahu@enterprisedb.com>
wrote:

While testing the Toast patch(PG+v7 patch) I found below server crash.
System configuration:
VCPUs: 4, RAM: 8GB, Storage: 320GB

This issue is not frequently reproducible, we need to repeat the same
testcase multiple times.

I wonder if this is an independent bug, because the backtrace doesn't look
like it's related to the stuff this is changing. Your report doesn't
specify whether you can also reproduce the problem without the patch, which
is something that you should always check before reporting a bug in a
particular patch.

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

#28Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: Robert Haas (#27)
Re: tableam vs. TOAST

On Wed, Oct 30, 2019 at 9:46 PM Robert Haas <robertmhaas@gmail.com> wrote:

On Wed, Oct 30, 2019 at 3:49 AM Prabhat Sahu <
prabhat.sahu@enterprisedb.com> wrote:

While testing the Toast patch(PG+v7 patch) I found below server crash.
System configuration:
VCPUs: 4, RAM: 8GB, Storage: 320GB

This issue is not frequently reproducible, we need to repeat the same
testcase multiple times.

I wonder if this is an independent bug, because the backtrace doesn't look
like it's related to the stuff this is changing. Your report doesn't
specify whether you can also reproduce the problem without the patch, which
is something that you should always check before reporting a bug in a
particular patch.

Hi Robert,

My sincere apologize that I have not mentioned the issue in more detail.
I have ran the same case against both PG HEAD and HEAD+Patch multiple
times(7, 10, 20nos), and
as I found this issue was not failing in HEAD and same case is reproducible
in HEAD+Patch (again I was not sure about the backtrace whether its related
to patch or not).

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

--

With Regards,

Prabhat Kumar Sahu
Skype ID: prabhat.sahu1984
EnterpriseDB Software India Pvt. Ltd.

The Postgres Database Company

#29Ashutosh Sharma
ashu.coek88@gmail.com
In reply to: Prabhat Sahu (#28)
Re: tableam vs. TOAST

From the stack trace shared by Prabhat, I understand that the checkpointer
process panicked due to one of the following two reasons:

1) The fsync() failed in the first attempt itself and the reason for the
failure was not due to file being dropped or truncated i.e. fsync failed
with the error other than ENOENT. Refer to ProcessSyncRequests() for
details esp. the code inside for (failures = 0; !entry->canceled;
failures++) loop.

2) The first attempt to fsync() failed with ENOENT error because just
before the fsync function was called, the file being synced either got
dropped or truncated. When this happened, the checkpointer process called
AbsorbSyncRequests() to update the entry for deleted file in the hash table
but it seems like AbsorbSyncRequests() failed to do so and that's why the
"entry->canceled" couldn't be set to true. Due to this, fsync() was
performed on the same file twice and that failed too. As checkpointer
process doesn't expect the fsync on the same file to fail twice, it
panicked. Again, please check ProcessSyncRequests() for details esp. the
code inside for (failures = 0; !entry->canceled; failures++) loop.

Now, the point of discussion here is, which one of the above two reasons
could the cause for panic? According to me, point #2 doesn't look like the
possible reason for panic. The reason being just before a file is unlinked,
backend first sends a SYNC_FORGET_REQUEST to the checkpointer process which
marks the entry for this file in the hash table as cancelled and then
removes the file. So, with this understanding it is hard to believe that
once the first fsync() for a file has failed with error ENOENT, a call to
AbsorbSyncRequests() made immediately after that wouldn't update the entry
for this file in the hash table because the backend only removes the file
once it has successfully sent the SYNC_FORGET_REQUEST for that file to the
checkpointer process. See mdunlinkfork()->register_forget_request() for
details on this.

So, I think the first point that I mentioned above could be the probable
reason for the checkpointer process getting panicked. But, having said all
that, it would be good to have some evidence for it which can be confirmed
by inspecting the server logfile.

Prabhat, is it possible for you to re-run the test-case with
log_min_messages set to DEBUG1 and save the logfile for the test-case that
crashes. This would be helpful in knowing if the fsync was performed just
once or twice i.e. whether point #1 is the reason for the panic or point
#2.

Thanks,

--
With Regards,
Ashutosh Sharma
EnterpriseDB:http://www.enterprisedb.com

On Thu, Oct 31, 2019 at 10:26 AM Prabhat Sahu <prabhat.sahu@enterprisedb.com>
wrote:

Show quoted text

On Wed, Oct 30, 2019 at 9:46 PM Robert Haas <robertmhaas@gmail.com> wrote:

On Wed, Oct 30, 2019 at 3:49 AM Prabhat Sahu <
prabhat.sahu@enterprisedb.com> wrote:

While testing the Toast patch(PG+v7 patch) I found below server crash.
System configuration:
VCPUs: 4, RAM: 8GB, Storage: 320GB

This issue is not frequently reproducible, we need to repeat the same
testcase multiple times.

I wonder if this is an independent bug, because the backtrace doesn't
look like it's related to the stuff this is changing. Your report doesn't
specify whether you can also reproduce the problem without the patch, which
is something that you should always check before reporting a bug in a
particular patch.

Hi Robert,

My sincere apologize that I have not mentioned the issue in more detail.
I have ran the same case against both PG HEAD and HEAD+Patch multiple
times(7, 10, 20nos), and
as I found this issue was not failing in HEAD and same case is
reproducible in HEAD+Patch (again I was not sure about the backtrace
whether its related to patch or not).

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

--

With Regards,

Prabhat Kumar Sahu
Skype ID: prabhat.sahu1984
EnterpriseDB Software India Pvt. Ltd.

The Postgres Database Company

#30Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Robert Haas (#25)
Re: tableam vs. TOAST

On 2019-10-04 20:32, Robert Haas wrote:

Here's the last patch back, rebased over that renaming. Although I
think that Andres (and Tom) are probably right that there's room for
improvement here, I currently don't see a way around the issues I
wrote about in/messages/by-id/CA+Tgmoa0zFcaCpOJCsSpOLLGpzTVfSyvcVB-USS8YoKzMO51Yw@mail.gmail.com
-- so not quite sure where to go next. Hopefully Andres or someone
else will give me a quick whack with the cluebat if I'm missing
something obvious.

This patch seems sound as far as the API restructuring goes.

If I may summarize the remaining discussion: This patch adds a field
toast_max_chunk_size to TableAmRoutine, to take the place of the
hardcoded TOAST_MAX_CHUNK_SIZE. The heapam_methods implementation then
sets this to TOAST_MAX_CHUNK_SIZE, thus preserving existing behavior.
Other table AMs can set this to some other value that they find
suitable. Currently, TOAST_MAX_CHUNK_SIZE is computed based on
heap-specific values and assumptions, so it's likely that other AMs
won't want to use that value. (Side note: Maybe rename
TOAST_MAX_CHUNK_SIZE then.) The concern was raised that while
TOAST_MAX_CHUNK_SIZE is stored in pg_control, values chosen by other
table AMs won't be, and so they won't have any safe-guards against
starting a server with incompatible disk layout. Then, various ways to
detect or check the TOAST chunk size at run time were discussed, but
none seemed satisfactory.

I think AMs are probably going to need a general mechanism to store
pg_control-like data somewhere. There are going to be chunk sizes,
block sizes, segment sizes, and so on. This one is just a particular
case of that.

This particular patch doesn't need to be held up by that, though.
Providing that mechanism can be a separate subproject of pluggable storage.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#31Robert Haas
robertmhaas@gmail.com
In reply to: Peter Eisentraut (#30)
Re: tableam vs. TOAST

On Wed, Nov 6, 2019 at 4:01 AM Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

This patch seems sound as far as the API restructuring goes.

Thanks. And thanks for weighing in.

If I may summarize the remaining discussion: This patch adds a field
toast_max_chunk_size to TableAmRoutine, to take the place of the
hardcoded TOAST_MAX_CHUNK_SIZE. The heapam_methods implementation then
sets this to TOAST_MAX_CHUNK_SIZE, thus preserving existing behavior.
Other table AMs can set this to some other value that they find
suitable. Currently, TOAST_MAX_CHUNK_SIZE is computed based on
heap-specific values and assumptions, so it's likely that other AMs
won't want to use that value. (Side note: Maybe rename
TOAST_MAX_CHUNK_SIZE then.)

Yeah.

The concern was raised that while
TOAST_MAX_CHUNK_SIZE is stored in pg_control, values chosen by other
table AMs won't be, and so they won't have any safe-guards against
starting a server with incompatible disk layout. Then, various ways to
detect or check the TOAST chunk size at run time were discussed, but
none seemed satisfactory.

Yeah. I've been nervous about trying to proceed with this patch
because Andres seemed confident there was a better approach than what
I did here, but as I wrote about back on September 12th, it doesn't
seem like his idea will work. I'm not clear whether I'm being stupid
and there's a way to salvage his idea, or whether he just made a
mistake.

One possible approach would be to move more of the logic below the
tableam layer. For example, toast_fetch_datum() could do this:

toastrel = table_open(toast_pointer.va_toastrelid, AccessShareLock);
call_a_new_tableam_method_here(toast_rel, &toast_pointer);
table_close(toastrel, AccessShareLock);

...and then it becomes the tableam's job to handle everything that
needs to be done in the middle. That might be better than what I've
got now; it's certainly more flexible. It does mean that an AM that
just wants to reuse the existing logic with a different chunk size has
got to repeat some code, but it's probably <~150 lines, so that's
perhaps not a catastrophe.

Alternatively, we could (a) stick with the current approach, (b) use
the current approach but make the table AM member a callback rather
than a constant, or (c) something else entirely. I don't want to give
up on making the TOAST infrastructure pluggable; requiring every AM to
use the heap as its TOAST implementation seems too constraining.

I think AMs are probably going to need a general mechanism to store
pg_control-like data somewhere. There are going to be chunk sizes,
block sizes, segment sizes, and so on. This one is just a particular
case of that.

That's an interesting point. I don't know for sure to what extent we
need that; I think that the toast chunk size is actually not very
interesting to vary, and the fact that we technically allow it to be
varied seems like it isn't buying us much. I think as much as possible
we should allow settings that actually need to be varied to differ
table-by-table, not require a recompile or re-initdb. But if we are
going to have some that do require that, then what you're talking
about here would certainly make that easier to secure.

This particular patch doesn't need to be held up by that, though.
Providing that mechanism can be a separate subproject of pluggable storage.

+1.

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

#32Andres Freund
andres@anarazel.de
In reply to: Peter Eisentraut (#30)
Re: tableam vs. TOAST

Hi,

On 2019-11-06 10:01:40 +0100, Peter Eisentraut wrote:

On 2019-10-04 20:32, Robert Haas wrote:

Here's the last patch back, rebased over that renaming. Although I
think that Andres (and Tom) are probably right that there's room for
improvement here, I currently don't see a way around the issues I
wrote about in/messages/by-id/CA+Tgmoa0zFcaCpOJCsSpOLLGpzTVfSyvcVB-USS8YoKzMO51Yw@mail.gmail.com
-- so not quite sure where to go next. Hopefully Andres or someone
else will give me a quick whack with the cluebat if I'm missing
something obvious.

This patch seems sound as far as the API restructuring goes.

If I may summarize the remaining discussion: This patch adds a field
toast_max_chunk_size to TableAmRoutine, to take the place of the hardcoded
TOAST_MAX_CHUNK_SIZE. The heapam_methods implementation then sets this to
TOAST_MAX_CHUNK_SIZE, thus preserving existing behavior. Other table AMs can
set this to some other value that they find suitable. Currently,
TOAST_MAX_CHUNK_SIZE is computed based on heap-specific values and
assumptions, so it's likely that other AMs won't want to use that value.
(Side note: Maybe rename TOAST_MAX_CHUNK_SIZE then.) The concern was raised
that while TOAST_MAX_CHUNK_SIZE is stored in pg_control, values chosen by
other table AMs won't be, and so they won't have any safe-guards against
starting a server with incompatible disk layout. Then, various ways to
detect or check the TOAST chunk size at run time were discussed, but none
seemed satisfactory.

I think it's more than just that. It's also that I think presenting a
hardcoded value to the outside of / above an AM is architecturally
wrong. If anything this is an implementation detail of the AM, that the
AM ought to be concerned with internally, not something it should
present to the outside.

I also, and separately from that architectural concern, think that
hardcoding values like this in the control file is a bad practice, and
we shouldn't expand it. It basically makes it practically impossible to
ever change their default value.

I think AMs are probably going to need a general mechanism to store
pg_control-like data somewhere. There are going to be chunk sizes, block
sizes, segment sizes, and so on. This one is just a particular case of
that.

That's imo best done as a meta page within the table.

This particular patch doesn't need to be held up by that, though. Providing
that mechanism can be a separate subproject of pluggable storage.

Again seems like something that the AM ought to handle below it.

Greetings,

Andres Freund

#33Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#32)
Re: tableam vs. TOAST

On Wed, Nov 6, 2019 at 11:25 AM Andres Freund <andres@anarazel.de> wrote:

I think it's more than just that. It's also that I think presenting a
hardcoded value to the outside of / above an AM is architecturally
wrong. If anything this is an implementation detail of the AM, that the
AM ought to be concerned with internally, not something it should
present to the outside.

I mean, it depends on your vision of how things ought to be
abstracted. If you want the TOAST stuff to be logically "below" the
table AM layer, then this is an abstraction violation. But if you
think of TOAST as being a parallel system to table AM, then it's fine.
It also depends on your goals. If you want to give the table AM
maximum freedom to do what it likes, the design I proposed is not very
good. If you want to make it easy for someone to plug in a new AM that
does toasting like the current heap but with a different chunk size,
that design lets you do so with a very minimal amount of code.

I don't really care very much about the details here, but I don't want
to just keep kicking the can down the road. If we can agree on *some*
design that lets a new table AM have a TOAST table that uses an AM
other than the heap, and that I can understand and implement with some
halfway-reasonable amount of work, I'll do it. It doesn't have to be
the thing I proposed. But I think it would be better to do that thing
than nothing. We're not engraving anything we do here on stone
tablets.

I also, and separately from that architectural concern, think that
hardcoding values like this in the control file is a bad practice, and
we shouldn't expand it. It basically makes it practically impossible to
ever change their default value.

I generally agree, although I think there might be exceptions.

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

#34Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#31)
Re: tableam vs. TOAST

Hi,

On 2019-11-06 10:38:58 -0500, Robert Haas wrote:

Yeah. I've been nervous about trying to proceed with this patch
because Andres seemed confident there was a better approach than what
I did here, but as I wrote about back on September 12th, it doesn't
seem like his idea will work. I'm not clear whether I'm being stupid
and there's a way to salvage his idea, or whether he just made a
mistake.

(still trying to get back to this)

One possible approach would be to move more of the logic below the
tableam layer. For example, toast_fetch_datum() could do this:

toastrel = table_open(toast_pointer.va_toastrelid, AccessShareLock);
call_a_new_tableam_method_here(toast_rel, &toast_pointer);
table_close(toastrel, AccessShareLock);

...and then it becomes the tableam's job to handle everything that
needs to be done in the middle.

I think that's a good direction to go in, for more than just the the
discussion we're having here about the fixed chunking size.

I'm fairly sure that plenty AMs e.g. wouldn't want to actually store
toasted datums in a separate relation. And this would at least go more
in the direction of making that possible. And I think the above ought to
not even increase the overhead compared to the patch, as the number of
indirect function calls ought to stay the same or even be lower. The
indirection would otherwise have to happen within toast_fetch_datum(),
whereas with the above, it ought to be possible to avoid needing to do
so processing toast chunks.

It seems, unfortunately, that atm an AM not wanting to store toast
datums in a separate file, would still need to create a toast relation,
just to get a distinct toast oid, to make sure that toasted datums from
the old and new relation are distinct. Which seems like it could be
important e.g. for table rewrites. There's probably some massaging
of table rewrites and cluster needed.

I suspect the new callback ought to allow sliced and non-sliced access,
perhaps by just allowing to specify slice offset / length to 0 and
INT32_MAX respectively (or maybe just -1, -1?). That'd also allow an AM
to make slicing possible in cases that's not possible for heap. And
there seems little point in having two callbacks.

That might be better than what I've got now; it's certainly more
flexible. It does mean that an AM that just wants to reuse the
existing logic with a different chunk size has got to repeat some
code, but it's probably <~150 lines, so that's perhaps not a
catastrophe.

Also seems that the relevant code can be made reusable (opting in into
the current logic), in line with where you've been going with this code
already.

Alternatively, we could (a) stick with the current approach, (b) use
the current approach but make the table AM member a callback rather
than a constant, or (c) something else entirely.

A callback returning the chunk size does not seem like an improvement to
me.

I don't want to give up on making the TOAST infrastructure pluggable;
requiring every AM to use the heap as its TOAST implementation seems
too constraining.

+1

I think AMs are probably going to need a general mechanism to store
pg_control-like data somewhere. There are going to be chunk sizes,
block sizes, segment sizes, and so on. This one is just a particular
case of that.

That's an interesting point. I don't know for sure to what extent we
need that; I think that the toast chunk size is actually not very
interesting to vary, and the fact that we technically allow it to be
varied seems like it isn't buying us much.

Whether I agree with that statement depends a bit on what you mean by
varying the chunk size. If you mean that there's not much need for a
value other than a adjusted computation of what's currently used, then I
don't agree:

We currently make toast a lot more expensive by quadrupling the number
of separate heap fetches.

And e.g. compressing chunks separately, to allow for sliced access even
when compressed, would also be hard to do with the current sizes.

Additionally, I *very* strongly suspect that, while it makes sense to
use chunks sizes where multiple chunks fit a page for a heavily updated
toast table full of small-ish values, that it makes no sense whatsoever
to do so when toasting a 10MB value that's going to be appended to the
toast relation, because there's no space available for reuse anyway. And
smaller chunking isn't going to help with space reuse either, because
the whole toast datum is going to be deleted together.

I think the right way for toast creation to behave really would be to
check whether there's free space available that'd benefit from using
smaller chunks and do so if available, and otherwise use all the space
in a page for each chunk.

That'd obviously make sliced access harder, so it surely isn't a
panacea.

Greetings,

Andres Freund

#35Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#33)
Re: tableam vs. TOAST

Hi,

On 2019-11-06 11:49:10 -0500, Robert Haas wrote:

On Wed, Nov 6, 2019 at 11:25 AM Andres Freund <andres@anarazel.de> wrote:

I think it's more than just that. It's also that I think presenting a
hardcoded value to the outside of / above an AM is architecturally
wrong. If anything this is an implementation detail of the AM, that the
AM ought to be concerned with internally, not something it should
present to the outside.

I mean, it depends on your vision of how things ought to be
abstracted. If you want the TOAST stuff to be logically "below" the
table AM layer, then this is an abstraction violation. But if you
think of TOAST as being a parallel system to table AM, then it's fine.
It also depends on your goals. If you want to give the table AM
maximum freedom to do what it likes, the design I proposed is not very
good. If you want to make it easy for someone to plug in a new AM that
does toasting like the current heap but with a different chunk size,
that design lets you do so with a very minimal amount of code.

I'd like an AM to have the *option* of implementing something better, or
at least go in the direction of making that possible.

It seems perfectly possible to have a helper function implementing the
current logic that you just can call with the fixed chunk size as an
additional parameter. Which'd basically mean there's no meaningful
difference in complexity compared to providing the chunk size as an
external AM property. In one case you have a callback that just calls a
helper function with one parameter, in the other you fill in a member of
the struct.

Greetings,

Andres Freund

#36Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: Ashutosh Sharma (#29)
Re: tableam vs. TOAST

On Tue, Nov 5, 2019 at 4:48 PM Ashutosh Sharma <ashu.coek88@gmail.com>
wrote:

From the stack trace shared by Prabhat, I understand that the checkpointer
process panicked due to one of the following two reasons:

1) The fsync() failed in the first attempt itself and the reason for the
failure was not due to file being dropped or truncated i.e. fsync failed
with the error other than ENOENT. Refer to ProcessSyncRequests() for
details esp. the code inside for (failures = 0; !entry->canceled;
failures++) loop.

2) The first attempt to fsync() failed with ENOENT error because just
before the fsync function was called, the file being synced either got
dropped or truncated. When this happened, the checkpointer process called
AbsorbSyncRequests() to update the entry for deleted file in the hash table
but it seems like AbsorbSyncRequests() failed to do so and that's why the
"entry->canceled" couldn't be set to true. Due to this, fsync() was
performed on the same file twice and that failed too. As checkpointer
process doesn't expect the fsync on the same file to fail twice, it
panicked. Again, please check ProcessSyncRequests() for details esp. the
code inside for (failures = 0; !entry->canceled; failures++) loop.

Now, the point of discussion here is, which one of the above two reasons
could the cause for panic? According to me, point #2 doesn't look like the
possible reason for panic. The reason being just before a file is unlinked,
backend first sends a SYNC_FORGET_REQUEST to the checkpointer process which
marks the entry for this file in the hash table as cancelled and then
removes the file. So, with this understanding it is hard to believe that
once the first fsync() for a file has failed with error ENOENT, a call to
AbsorbSyncRequests() made immediately after that wouldn't update the entry
for this file in the hash table because the backend only removes the file
once it has successfully sent the SYNC_FORGET_REQUEST for that file to the
checkpointer process. See mdunlinkfork()->register_forget_request() for
details on this.

So, I think the first point that I mentioned above could be the probable
reason for the checkpointer process getting panicked. But, having said all
that, it would be good to have some evidence for it which can be confirmed
by inspecting the server logfile.

Prabhat, is it possible for you to re-run the test-case with
log_min_messages set to DEBUG1 and save the logfile for the test-case that
crashes. This would be helpful in knowing if the fsync was performed just
once or twice i.e. whether point #1 is the reason for the panic or point
#2.

I have ran the same testcases with and without patch multiple times with
debug option (log_min_messages = DEBUG1), but this time I am not able to
reproduce the crash.

Thanks,

--
With Regards,
Ashutosh Sharma
EnterpriseDB:http://www.enterprisedb.com

On Thu, Oct 31, 2019 at 10:26 AM Prabhat Sahu <
prabhat.sahu@enterprisedb.com> wrote:

On Wed, Oct 30, 2019 at 9:46 PM Robert Haas <robertmhaas@gmail.com>
wrote:

On Wed, Oct 30, 2019 at 3:49 AM Prabhat Sahu <
prabhat.sahu@enterprisedb.com> wrote:

While testing the Toast patch(PG+v7 patch) I found below server crash.
System configuration:
VCPUs: 4, RAM: 8GB, Storage: 320GB

This issue is not frequently reproducible, we need to repeat the same
testcase multiple times.

I wonder if this is an independent bug, because the backtrace doesn't
look like it's related to the stuff this is changing. Your report doesn't
specify whether you can also reproduce the problem without the patch, which
is something that you should always check before reporting a bug in a
particular patch.

Hi Robert,

My sincere apologize that I have not mentioned the issue in more detail.
I have ran the same case against both PG HEAD and HEAD+Patch multiple
times(7, 10, 20nos), and
as I found this issue was not failing in HEAD and same case is
reproducible in HEAD+Patch (again I was not sure about the backtrace
whether its related to patch or not).

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

--

With Regards,

Prabhat Kumar Sahu
Skype ID: prabhat.sahu1984
EnterpriseDB Software India Pvt. Ltd.

The Postgres Database Company

--

With Regards,

Prabhat Kumar Sahu
Skype ID: prabhat.sahu1984
EnterpriseDB Software India Pvt. Ltd.

The Postgres Database Company

#37Ashutosh Sharma
ashu.coek88@gmail.com
In reply to: Prabhat Sahu (#36)
Re: tableam vs. TOAST

On Thu, Nov 7, 2019 at 10:57 AM Prabhat Sahu
<prabhat.sahu@enterprisedb.com> wrote:

On Tue, Nov 5, 2019 at 4:48 PM Ashutosh Sharma <ashu.coek88@gmail.com> wrote:

From the stack trace shared by Prabhat, I understand that the checkpointer process panicked due to one of the following two reasons:

1) The fsync() failed in the first attempt itself and the reason for the failure was not due to file being dropped or truncated i.e. fsync failed with the error other than ENOENT. Refer to ProcessSyncRequests() for details esp. the code inside for (failures = 0; !entry->canceled; failures++) loop.

2) The first attempt to fsync() failed with ENOENT error because just before the fsync function was called, the file being synced either got dropped or truncated. When this happened, the checkpointer process called AbsorbSyncRequests() to update the entry for deleted file in the hash table but it seems like AbsorbSyncRequests() failed to do so and that's why the "entry->canceled" couldn't be set to true. Due to this, fsync() was performed on the same file twice and that failed too. As checkpointer process doesn't expect the fsync on the same file to fail twice, it panicked. Again, please check ProcessSyncRequests() for details esp. the code inside for (failures = 0; !entry->canceled; failures++) loop.

Now, the point of discussion here is, which one of the above two reasons could the cause for panic? According to me, point #2 doesn't look like the possible reason for panic. The reason being just before a file is unlinked, backend first sends a SYNC_FORGET_REQUEST to the checkpointer process which marks the entry for this file in the hash table as cancelled and then removes the file. So, with this understanding it is hard to believe that once the first fsync() for a file has failed with error ENOENT, a call to AbsorbSyncRequests() made immediately after that wouldn't update the entry for this file in the hash table because the backend only removes the file once it has successfully sent the SYNC_FORGET_REQUEST for that file to the checkpointer process. See mdunlinkfork()->register_forget_request() for details on this.

So, I think the first point that I mentioned above could be the probable reason for the checkpointer process getting panicked. But, having said all that, it would be good to have some evidence for it which can be confirmed by inspecting the server logfile.

Prabhat, is it possible for you to re-run the test-case with log_min_messages set to DEBUG1 and save the logfile for the test-case that crashes. This would be helpful in knowing if the fsync was performed just once or twice i.e. whether point #1 is the reason for the panic or point #2.

I have ran the same testcases with and without patch multiple times with debug option (log_min_messages = DEBUG1), but this time I am not able to reproduce the crash.

Okay, no problem. Thanks for re-running the test-cases.

@Robert, Myself and Prabhat have tried running the test-cases that
caused the checkpointer process to crash earlier multiple times but we
are not able to reproduce it both with and without the patch. However,
from the stack trace shared earlier by Prabhat, it is clear that the
checkpointer process panicked due to fsync failure. But, there is no
further data to know the exact reason for the fsync failure. From the
code of checkpointer process (basically the function to process fsync
requests) it is understood that, the checkpointer process can PANIC
due to one of the following two reasons.

1) The fsync call made by checkpointer process has failed with error
other than ENOENT.

2) The fsync call made by checkpointer process failed with ENOENT
error which caused the checkpointer process to invoke
AbsorbSyncRequests() to update the entry for deleted file in the hash
table (basically to mark the entry as cancelled). But, seems like it
couldn't do so either because - a) possibly there was no
SYNC_FORGET_REQUEST sent by the backend to the checkpointer process or
b) the request was sent but due to some reason the checkpointer
process couldn't absorb the request. This caused the checkpointer
process to perform fsync on the same file once again which is bound to
fail resulting into a panic.

Now, if checkpointer process panicked due to reason #1 then I don't
think it has anything to do with postgres because postgres only cares
when fsync fails with ENOENT error. If in case checkpointer process
panicked due reason #2 then possibly there is some bug in postgres
code which I assume has to be some problem with the way backend is
sending fsync request to the checkpointer for deleted files and the
way checkpointer is handling the requests. At least for me, it is hard
to believe that reason #2 could be the cause for the checkpointer
process getting panicked here - for the reason that before a file is
unlinked by backend, it first sends a SYNC_FORGET_REQUEST to the
checkpointer process, when this is done successfully then only backend
removes the file. So, with this understanding it is hard to believe
that once the first fsync() for a file has failed with error ENOENT, a
call to AbsorbSyncRequests() made immediately after that wouldn't
update the entry for this file in the hash table. And even if reason
#2 is the cause for this failure, I don't think it has anything to do
with your changes, although I haven't studied your patches in detail
but considering the purpose of the patch and from a quick look it
doesn't seem to change anything in the area of the code that might be
causing this crash.

--
With Regards,
Ashutosh Sharma
EnterpriseDB:http://www.enterprisedb.com

Show quoted text

Thanks,

--
With Regards,
Ashutosh Sharma
EnterpriseDB:http://www.enterprisedb.com

On Thu, Oct 31, 2019 at 10:26 AM Prabhat Sahu <prabhat.sahu@enterprisedb.com> wrote:

On Wed, Oct 30, 2019 at 9:46 PM Robert Haas <robertmhaas@gmail.com> wrote:

On Wed, Oct 30, 2019 at 3:49 AM Prabhat Sahu <prabhat.sahu@enterprisedb.com> wrote:

While testing the Toast patch(PG+v7 patch) I found below server crash.
System configuration:
VCPUs: 4, RAM: 8GB, Storage: 320GB

This issue is not frequently reproducible, we need to repeat the same testcase multiple times.

I wonder if this is an independent bug, because the backtrace doesn't look like it's related to the stuff this is changing. Your report doesn't specify whether you can also reproduce the problem without the patch, which is something that you should always check before reporting a bug in a particular patch.

Hi Robert,

My sincere apologize that I have not mentioned the issue in more detail.
I have ran the same case against both PG HEAD and HEAD+Patch multiple times(7, 10, 20nos), and
as I found this issue was not failing in HEAD and same case is reproducible in HEAD+Patch (again I was not sure about the backtrace whether its related to patch or not).

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

--

With Regards,

Prabhat Kumar Sahu
Skype ID: prabhat.sahu1984
EnterpriseDB Software India Pvt. Ltd.

The Postgres Database Company

--

With Regards,

Prabhat Kumar Sahu
Skype ID: prabhat.sahu1984
EnterpriseDB Software India Pvt. Ltd.

The Postgres Database Company

#38Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Andres Freund (#35)
Re: tableam vs. TOAST

On 2019-11-06 18:00, Andres Freund wrote:

I'd like an AM to have the *option* of implementing something better, or
at least go in the direction of making that possible.

I don't think the presented design prevents that. An AM can just return
false from relation_needs_toast_table in all cases and implement
something internally.

It seems perfectly possible to have a helper function implementing the
current logic that you just can call with the fixed chunk size as an
additional parameter. Which'd basically mean there's no meaningful
difference in complexity compared to providing the chunk size as an
external AM property. In one case you have a callback that just calls a
helper function with one parameter, in the other you fill in a member of
the struct.

I can see a "moral" concern about having TOAST be part of the table AM
API. It should be an implementation concern of the AM. How much more
work would it be to refactor TOAST into a separate API that an AM
implementation could use or not? How much more complicated would the
result be? I guess you would like to at least have it explored.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#39Robert Haas
robertmhaas@gmail.com
In reply to: Ashutosh Sharma (#37)
Re: tableam vs. TOAST

On Thu, Nov 7, 2019 at 1:15 AM Ashutosh Sharma <ashu.coek88@gmail.com> wrote:

@Robert, Myself and Prabhat have tried running the test-cases that
caused the checkpointer process to crash earlier multiple times but we
are not able to reproduce it both with and without the patch. However,
from the stack trace shared earlier by Prabhat, it is clear that the
checkpointer process panicked due to fsync failure. But, there is no
further data to know the exact reason for the fsync failure. From the
code of checkpointer process (basically the function to process fsync
requests) it is understood that, the checkpointer process can PANIC
due to one of the following two reasons.

Oh, I didn't realize this was a panic due to an fsync() failure when I
looked at the stack trace before. I think it's concerning that
fsync() failed on Prabhat's machine, and it would be interesting to
know why that happened, but I don't see how this patch could possibly
*cause* fsync() to fail, so I think we can say that whatever is
happening on his machine is unrelated to this patch -- and probably
also unrelated to PostgreSQL.

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

#40Ashutosh Sharma
ashu.coek88@gmail.com
In reply to: Robert Haas (#39)
Re: tableam vs. TOAST

On Thu, Nov 7, 2019 at 7:35 PM Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Nov 7, 2019 at 1:15 AM Ashutosh Sharma <ashu.coek88@gmail.com> wrote:

@Robert, Myself and Prabhat have tried running the test-cases that
caused the checkpointer process to crash earlier multiple times but we
are not able to reproduce it both with and without the patch. However,
from the stack trace shared earlier by Prabhat, it is clear that the
checkpointer process panicked due to fsync failure. But, there is no
further data to know the exact reason for the fsync failure. From the
code of checkpointer process (basically the function to process fsync
requests) it is understood that, the checkpointer process can PANIC
due to one of the following two reasons.

Oh, I didn't realize this was a panic due to an fsync() failure when I
looked at the stack trace before. I think it's concerning that
fsync() failed on Prabhat's machine, and it would be interesting to
know why that happened, but I don't see how this patch could possibly
*cause* fsync() to fail, so I think we can say that whatever is
happening on his machine is unrelated to this patch -- and probably
also unrelated to PostgreSQL.

That's right and that's exactly what I mentioned in my conclusion too.

--
With Regards,
Ashutosh Sharma
EnterpriseDB:http://www.enterprisedb.com

#41Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#35)
2 attachment(s)
Re: tableam vs. TOAST

On Wed, Nov 6, 2019 at 12:00 PM Andres Freund <andres@anarazel.de> wrote:

I'd like an AM to have the *option* of implementing something better, or
at least go in the direction of making that possible.

OK. Could you see what you think of the attached patches? 0001 does
some refactoring of toast_fetch_datum() and toast_fetch_datum_slice()
to make them look more like each other and clean up a bunch of stuff
that I thought was annoying, and 0002 then pulls out the common logic
into a heap-specific function. If you like this direction, we could
then push the heap-specific function below tableam, but I haven't done
that yet.

It seems perfectly possible to have a helper function implementing the
current logic that you just can call with the fixed chunk size as an
additional parameter. Which'd basically mean there's no meaningful
difference in complexity compared to providing the chunk size as an
external AM property. In one case you have a callback that just calls a
helper function with one parameter, in the other you fill in a member of
the struct.

I haven't tried to do this yet. I think that to make it work, the
helper function would have to operate in terms of slots instead of
using fastgetattr() as this logic does now. I don't know whether that
would be faster (because the current code might have a little less in
terms of indirect function calls) or slower (because the current code
makes two calls to fastgetattr and if we used slots here we could just
deform once). I suspect it might be a small enough difference not to
worry too much about it either way.

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

Attachments:

0002-Move-heap-specific-detoasting-logic-into-a-separate-.patchapplication/octet-stream; name=0002-Move-heap-specific-detoasting-logic-into-a-separate-.patchDownload
From ebdb3fbf034eefa0ca8eb34d863d959f320f26cc Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 8 Nov 2019 11:50:52 -0500
Subject: [PATCH 2/2] Move heap-specific detoasting logic into a separate
 function.

The new function, heap_fetch_toast_slice, is shared between
toast_fetch_datum_slice and toast_fetch_datum, and does all the
work of scanning the TOAST table, fetching chunks, and storing
them into the space allocated for the result varlena.

As an incidental side effect, this allows toast_fetch_datum_slice
to perform the scan with only a single scankey if all chunks are
being fetched, which might have some tiny performance benefit.
---
 src/backend/access/common/detoast.c | 244 ++++++++--------------------
 1 file changed, 71 insertions(+), 173 deletions(-)

diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c
index ae7daa24de..0914efee3e 100644
--- a/src/backend/access/common/detoast.c
+++ b/src/backend/access/common/detoast.c
@@ -27,6 +27,9 @@ static struct varlena *toast_fetch_datum(struct varlena *attr);
 static struct varlena *toast_fetch_datum_slice(struct varlena *attr,
 											   int32 sliceoffset,
 											   int32 slicelength);
+static void heap_fetch_toast_slice(Relation toastrel, Oid valueid,
+								   int32 attrsize, int32 sliceoffset,
+								   int32 slicelength, struct varlena *result);
 static struct varlena *toast_decompress_datum(struct varlena *attr);
 static struct varlena *toast_decompress_datum_slice(struct varlena *attr, int32 slicelength);
 
@@ -325,19 +328,9 @@ 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		attrsize;
-	int32		nextidx;
-	int32		totalchunks;
-	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");
@@ -346,7 +339,6 @@ toast_fetch_datum(struct varlena *attr)
 	VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
 
 	attrsize = toast_pointer.va_extsize;
-	totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
 
 	result = (struct varlena *) palloc(attrsize + VARHDRSZ);
 
@@ -355,130 +347,19 @@ toast_fetch_datum(struct varlena *attr)
 	else
 		SET_VARSIZE(result, attrsize + VARHDRSZ);
 
+	if (attrsize == 0)
+		return result;		/* Probably shouldn't happen, but just in case. */
+
 	/*
 	 * 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)
-	{
-		int32		residx;
-		Pointer		chunk;
-		bool		isnull;
-		char	   *chunkdata;
-		int32		chunksize;
-		int32		expected_size;
+	/* Fetch all chunks */
+	heap_fetch_toast_slice(toastrel, toast_pointer.va_valueid, attrsize, 0,
+						   attrsize, result);
 
-		/*
-		 * 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)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATA_CORRUPTED),
-					 errmsg_internal("unexpected chunk number %d (expected %d) for toast value %u in %s",
-									 residx, nextidx,
-									 toast_pointer.va_valueid,
-									 RelationGetRelationName(toastrel))));
-		if (residx > totalchunks - 1)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATA_CORRUPTED),
-					 errmsg_internal("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))));
-		expected_size = residx < totalchunks - 1 ? TOAST_MAX_CHUNK_SIZE
-			: attrsize % TOAST_MAX_CHUNK_SIZE;
-		if (chunksize != expected_size)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATA_CORRUPTED),
-					 errmsg_internal("unexpected chunk size %d (expected %d) in chunk %d for toast value %u in %s",
-									 chunksize, expected_size,
-									 residx,
-									 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 != totalchunks)
-		ereport(ERROR,
-				(errcode(ERRCODE_DATA_CORRUPTED),
-				 errmsg_internal("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);
+	/* Close toast table */
 	table_close(toastrel, AccessShareLock);
 
 	return result;
@@ -500,22 +381,9 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 						int32 slicelength)
 {
 	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		nextidx;
-	int			startchunk;
-	int			endchunk;
-	int			totalchunks;
-	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");
@@ -531,7 +399,6 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 	Assert(!VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer) || 0 == sliceoffset);
 
 	attrsize = toast_pointer.va_extsize;
-	totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
 
 	if (sliceoffset >= attrsize)
 	{
@@ -560,15 +427,47 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 	if (slicelength == 0)
 		return result;			/* Can save a lot of work at this point! */
 
-	startchunk = sliceoffset / TOAST_MAX_CHUNK_SIZE;
-	endchunk = (sliceoffset + slicelength - 1) / TOAST_MAX_CHUNK_SIZE;
-	Assert(endchunk <= totalchunks);
-
-	/*
-	 * Open the toast relation and its indexes
-	 */
+	/* Open the toast relation */
 	toastrel = table_open(toast_pointer.va_toastrelid, AccessShareLock);
-	toasttupDesc = toastrel->rd_att;
+
+	/* Fetch all chunks */
+	heap_fetch_toast_slice(toastrel, toast_pointer.va_valueid, attrsize,
+						   sliceoffset, slicelength, result);
+
+	/* Close toast table */
+	table_close(toastrel, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * Fetch a TOAST slice from a heap table.
+ *
+ * toastrel is the relation from which chunks are to be fetched.
+ * valueid identifies the TOAST value from which chunks are being fetched.
+ * attrsize is the total size of the TOAST value.
+ * sliceoffset is the byte offset within the TOAST value from which to fetch.
+ * slicelength is the number of bytes to be fetched from the TOAST value.
+ * result is the varlena into which the results should be written.
+ */
+static void
+heap_fetch_toast_slice(Relation toastrel, Oid valueid, int32 attrsize,
+					   int32 sliceoffset, int32 slicelength,
+					   struct varlena *result)
+{
+	Relation   *toastidxs;
+	ScanKeyData toastkey[3];
+	TupleDesc	toasttupDesc = toastrel->rd_att;
+	int			nscankeys;
+	SysScanDesc toastscan;
+	HeapTuple	ttup;
+	int32		nextidx;
+	int32		totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
+	int			startchunk;
+	int			endchunk;
+	int			num_indexes;
+	int			validIndex;
+	SnapshotData SnapshotToast;
 
 	/* Look for the valid index of toast relation */
 	validIndex = toast_open_indexes(toastrel,
@@ -576,6 +475,10 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 									&toastidxs,
 									&num_indexes);
 
+	startchunk = sliceoffset / TOAST_MAX_CHUNK_SIZE;
+	endchunk = (sliceoffset + slicelength - 1) / TOAST_MAX_CHUNK_SIZE;
+	Assert(endchunk <= totalchunks);
+
 	/*
 	 * Setup a scan key to fetch from the index. This is either two keys or
 	 * three depending on the number of chunks.
@@ -583,12 +486,15 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 	ScanKeyInit(&toastkey[0],
 				(AttrNumber) 1,
 				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(toast_pointer.va_valueid));
+				ObjectIdGetDatum(valueid));
 
 	/*
-	 * Use equality condition for one chunk, a range condition otherwise:
+	 * No addition condition if fetching all chuns. Otherwise, use an
+	 * equality condition for one chunk, and a range condition otherwise.
 	 */
-	if (startchunk == endchunk)
+	if (startchunk == 0 && endchunk == totalchunks - 1)
+		nscankeys = 1;
+	else if (startchunk == endchunk)
 	{
 		ScanKeyInit(&toastkey[1],
 					(AttrNumber) 2,
@@ -609,15 +515,17 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 		nscankeys = 3;
 	}
 
+	/* Prepare for scan */
+	init_toast_snapshot(&SnapshotToast);
+	toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
+										   &SnapshotToast, nscankeys, toastkey);
+
 	/*
 	 * 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)
 	{
 		int32		residx;
@@ -651,8 +559,7 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 		{
 			/* should never happen */
 			elog(ERROR, "found toasted toast chunk for toast value %u in %s",
-				 toast_pointer.va_valueid,
-				 RelationGetRelationName(toastrel));
+				 valueid, RelationGetRelationName(toastrel));
 			chunksize = 0;		/* keep compiler quiet */
 			chunkdata = NULL;
 		}
@@ -664,16 +571,14 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 			ereport(ERROR,
 					(errcode(ERRCODE_DATA_CORRUPTED),
 					 errmsg_internal("unexpected chunk number %d (expected %d) for toast value %u in %s",
-									 residx, nextidx,
-									 toast_pointer.va_valueid,
+									 residx, nextidx, valueid,
 									 RelationGetRelationName(toastrel))));
 		if (residx > endchunk)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATA_CORRUPTED),
 					 errmsg_internal("unexpected chunk number %d (out of range %d..%d) for toast value %u in %s",
 									 residx,
-									 startchunk, endchunk,
-									 toast_pointer.va_valueid,
+									 startchunk, endchunk, valueid,
 									 RelationGetRelationName(toastrel))));
 		expected_size = residx < totalchunks - 1 ? TOAST_MAX_CHUNK_SIZE
 			: attrsize % TOAST_MAX_CHUNK_SIZE;
@@ -682,8 +587,7 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 					(errcode(ERRCODE_DATA_CORRUPTED),
 					 errmsg_internal("unexpected chunk size %d (expected %d) in chunk %d for toast value %u in %s",
 									 chunksize, expected_size,
-									 residx,
-									 toast_pointer.va_valueid,
+									 residx, valueid,
 									 RelationGetRelationName(toastrel))));
 
 		/*
@@ -711,18 +615,12 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 		ereport(ERROR,
 				(errcode(ERRCODE_DATA_CORRUPTED),
 				 errmsg_internal("missing chunk number %d for toast value %u in %s",
-								 nextidx,
-								 toast_pointer.va_valueid,
+								 nextidx, valueid,
 								 RelationGetRelationName(toastrel))));
 
-	/*
-	 * End scan and close relations
-	 */
+	/* End scan and close indexes. */
 	systable_endscan_ordered(toastscan);
 	toast_close_indexes(toastidxs, num_indexes, AccessShareLock);
-	table_close(toastrel, AccessShareLock);
-
-	return result;
 }
 
 /* ----------
-- 
2.17.2 (Apple Git-113)

0001-Code-cleanup-for-toast_fetch_datum-and-toast_fetch_d.patchapplication/octet-stream; name=0001-Code-cleanup-for-toast_fetch_datum-and-toast_fetch_d.patchDownload
From d3cd945e2571aea730b7fc4285a1685e905375fc Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 8 Nov 2019 10:24:34 -0500
Subject: [PATCH 1/2] Code cleanup for toast_fetch_datum and
 toast_fetch_datum_slice.

Rework some of the checks for bad TOAST chunks to be a bit simpler
and easier to understand. These checks verify that (1) we get all
and only the chunk numbers we expect to see and (2) each chunk has
the expected size. However, the existing code was a bit hard to
understand, at least for me; try to make it clearer.

As part of that, have toast_fetch_datum_slice check the relationship
between endchunk and totalchunks only with an Assert() rather than
checking every chunk number against both values. There's no need to
check that relationship in production builds because it's not a
function of whether on-disk corruption is present; it's just a
question of whether the code does the right math.

Also, have toast_fetch_datum_slice() use ereport(ERROR) rather than
elog(ERROR). Commit fd6ec93bf890314ac694dc8a7f3c45702ecc1bbd made
the two functions inconsistent with each other.

In toast_fetch_datum, rename two variables for better consistency with
toast_fetch_datum_slice. In toast_fetch_datum_slice, eliminate several
variables that are used only once, and rename length to slicelength
for clarity. In both functions, move some variables from the function
scope into the function's main loop.
---
 src/backend/access/common/detoast.c | 188 +++++++++++++---------------
 1 file changed, 86 insertions(+), 102 deletions(-)

diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c
index f752ac7bbc..ae7daa24de 100644
--- a/src/backend/access/common/detoast.c
+++ b/src/backend/access/common/detoast.c
@@ -25,7 +25,8 @@
 
 static struct varlena *toast_fetch_datum(struct varlena *attr);
 static struct varlena *toast_fetch_datum_slice(struct varlena *attr,
-											   int32 sliceoffset, int32 length);
+											   int32 sliceoffset,
+											   int32 slicelength);
 static struct varlena *toast_decompress_datum(struct varlena *attr);
 static struct varlena *toast_decompress_datum_slice(struct varlena *attr, int32 slicelength);
 
@@ -331,14 +332,9 @@ toast_fetch_datum(struct varlena *attr)
 	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;
+	int32		attrsize;
+	int32		nextidx;
+	int32		totalchunks;
 	int			num_indexes;
 	int			validIndex;
 	SnapshotData SnapshotToast;
@@ -349,15 +345,15 @@ toast_fetch_datum(struct varlena *attr)
 	/* 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;
+	attrsize = toast_pointer.va_extsize;
+	totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
 
-	result = (struct varlena *) palloc(ressize + VARHDRSZ);
+	result = (struct varlena *) palloc(attrsize + VARHDRSZ);
 
 	if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))
-		SET_VARSIZE_COMPRESSED(result, ressize + VARHDRSZ);
+		SET_VARSIZE_COMPRESSED(result, attrsize + VARHDRSZ);
 	else
-		SET_VARSIZE(result, ressize + VARHDRSZ);
+		SET_VARSIZE(result, attrsize + VARHDRSZ);
 
 	/*
 	 * Open the toast relation and its indexes
@@ -393,6 +389,13 @@ toast_fetch_datum(struct varlena *attr)
 										   &SnapshotToast, 1, &toastkey);
 	while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
 	{
+		int32		residx;
+		Pointer		chunk;
+		bool		isnull;
+		char	   *chunkdata;
+		int32		chunksize;
+		int32		expected_size;
+
 		/*
 		 * Have a chunk, extract the sequence number and the data
 		 */
@@ -431,35 +434,22 @@ toast_fetch_datum(struct varlena *attr)
 									 residx, nextidx,
 									 toast_pointer.va_valueid,
 									 RelationGetRelationName(toastrel))));
-		if (residx < numchunks - 1)
-		{
-			if (chunksize != TOAST_MAX_CHUNK_SIZE)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATA_CORRUPTED),
-						 errmsg_internal("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)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATA_CORRUPTED),
-						 errmsg_internal("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
+		if (residx > totalchunks - 1)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATA_CORRUPTED),
 					 errmsg_internal("unexpected chunk number %d (out of range %d..%d) for toast value %u in %s",
 									 residx,
-									 0, numchunks - 1,
+									 0, totalchunks - 1,
+									 toast_pointer.va_valueid,
+									 RelationGetRelationName(toastrel))));
+		expected_size = residx < totalchunks - 1 ? TOAST_MAX_CHUNK_SIZE
+			: attrsize % TOAST_MAX_CHUNK_SIZE;
+		if (chunksize != expected_size)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+					 errmsg_internal("unexpected chunk size %d (expected %d) in chunk %d for toast value %u in %s",
+									 chunksize, expected_size,
+									 residx,
 									 toast_pointer.va_valueid,
 									 RelationGetRelationName(toastrel))));
 
@@ -476,7 +466,7 @@ toast_fetch_datum(struct varlena *attr)
 	/*
 	 * Final checks that we successfully fetched the datum
 	 */
-	if (nextidx != numchunks)
+	if (nextidx != totalchunks)
 		ereport(ERROR,
 				(errcode(ERRCODE_DATA_CORRUPTED),
 				 errmsg_internal("missing chunk number %d for toast value %u in %s",
@@ -506,7 +496,8 @@ toast_fetch_datum(struct varlena *attr)
  * ----------
  */
 static struct varlena *
-toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length)
+toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
+						int32 slicelength)
 {
 	Relation	toastrel;
 	Relation   *toastidxs;
@@ -518,20 +509,10 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length)
 	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;
@@ -555,7 +536,7 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length)
 	if (sliceoffset >= attrsize)
 	{
 		sliceoffset = 0;
-		length = 0;
+		slicelength = 0;
 	}
 
 	/*
@@ -563,28 +544,25 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length)
 	 * rawsize tracking amount of raw data, which is stored at the beginning
 	 * as an int32 value).
 	 */
-	if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer) && length > 0)
-		length = length + sizeof(int32);
+	if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer) && slicelength > 0)
+		slicelength = slicelength + sizeof(int32);
 
-	if (((sliceoffset + length) > attrsize) || length < 0)
-		length = attrsize - sliceoffset;
+	if (((sliceoffset + slicelength) > attrsize) || slicelength < 0)
+		slicelength = attrsize - sliceoffset;
 
-	result = (struct varlena *) palloc(length + VARHDRSZ);
+	result = (struct varlena *) palloc(slicelength + VARHDRSZ);
 
 	if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))
-		SET_VARSIZE_COMPRESSED(result, length + VARHDRSZ);
+		SET_VARSIZE_COMPRESSED(result, slicelength + VARHDRSZ);
 	else
-		SET_VARSIZE(result, length + VARHDRSZ);
+		SET_VARSIZE(result, slicelength + VARHDRSZ);
 
-	if (length == 0)
+	if (slicelength == 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;
+	endchunk = (sliceoffset + slicelength - 1) / TOAST_MAX_CHUNK_SIZE;
+	Assert(endchunk <= totalchunks);
 
 	/*
 	 * Open the toast relation and its indexes
@@ -610,7 +588,7 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length)
 	/*
 	 * Use equality condition for one chunk, a range condition otherwise:
 	 */
-	if (numchunks == 1)
+	if (startchunk == endchunk)
 	{
 		ScanKeyInit(&toastkey[1],
 					(AttrNumber) 2,
@@ -642,6 +620,15 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length)
 										   &SnapshotToast, nscankeys, toastkey);
 	while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
 	{
+		int32		residx;
+		Pointer		chunk;
+		bool		isnull;
+		char	   *chunkdata;
+		int32		chunksize;
+		int32		expected_size;
+		int32		chcpystrt;
+		int32		chcpyend;
+
 		/*
 		 * Have a chunk, extract the sequence number and the data
 		 */
@@ -673,36 +660,31 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length)
 		/*
 		 * 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));
+		if (residx != nextidx)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+					 errmsg_internal("unexpected chunk number %d (expected %d) for toast value %u in %s",
+									 residx, nextidx,
+									 toast_pointer.va_valueid,
+									 RelationGetRelationName(toastrel))));
+		if (residx > endchunk)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+					 errmsg_internal("unexpected chunk number %d (out of range %d..%d) for toast value %u in %s",
+									 residx,
+									 startchunk, endchunk,
+									 toast_pointer.va_valueid,
+									 RelationGetRelationName(toastrel))));
+		expected_size = residx < totalchunks - 1 ? TOAST_MAX_CHUNK_SIZE
+			: attrsize % TOAST_MAX_CHUNK_SIZE;
+		if (chunksize != expected_size)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+					 errmsg_internal("unexpected chunk size %d (expected %d) in chunk %d for toast value %u in %s",
+									 chunksize, expected_size,
+									 residx,
+									 toast_pointer.va_valueid,
+									 RelationGetRelationName(toastrel))));
 
 		/*
 		 * Copy the data into proper place in our result
@@ -710,9 +692,9 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length)
 		chcpystrt = 0;
 		chcpyend = chunksize - 1;
 		if (residx == startchunk)
-			chcpystrt = startoffset;
+			chcpystrt = sliceoffset % TOAST_MAX_CHUNK_SIZE;
 		if (residx == endchunk)
-			chcpyend = endoffset;
+			chcpyend = (sliceoffset + slicelength - 1) % TOAST_MAX_CHUNK_SIZE;
 
 		memcpy(VARDATA(result) +
 			   (residx * TOAST_MAX_CHUNK_SIZE - sliceoffset) + chcpystrt,
@@ -726,10 +708,12 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length)
 	 * 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));
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_CORRUPTED),
+				 errmsg_internal("missing chunk number %d for toast value %u in %s",
+								 nextidx,
+								 toast_pointer.va_valueid,
+								 RelationGetRelationName(toastrel))));
 
 	/*
 	 * End scan and close relations
-- 
2.17.2 (Apple Git-113)

#42Craig Ringer
craig@2ndquadrant.com
In reply to: Ashutosh Sharma (#40)
Re: tableam vs. TOAST

On Thu, 7 Nov 2019 at 22:45, Ashutosh Sharma <ashu.coek88@gmail.com> wrote:

On Thu, Nov 7, 2019 at 7:35 PM Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Nov 7, 2019 at 1:15 AM Ashutosh Sharma <ashu.coek88@gmail.com>

wrote:

@Robert, Myself and Prabhat have tried running the test-cases that
caused the checkpointer process to crash earlier multiple times but we
are not able to reproduce it both with and without the patch. However,
from the stack trace shared earlier by Prabhat, it is clear that the
checkpointer process panicked due to fsync failure. But, there is no
further data to know the exact reason for the fsync failure. From the
code of checkpointer process (basically the function to process fsync
requests) it is understood that, the checkpointer process can PANIC
due to one of the following two reasons.

Oh, I didn't realize this was a panic due to an fsync() failure when I
looked at the stack trace before. I think it's concerning that
fsync() failed on Prabhat's machine, and it would be interesting to
know why that happened, but I don't see how this patch could possibly
*cause* fsync() to fail, so I think we can say that whatever is
happening on his machine is unrelated to this patch -- and probably
also unrelated to PostgreSQL.

That's right and that's exactly what I mentioned in my conclusion too.

In fact, I suspect this is PostgreSQL successfully protecting itself from
an unsafe situation.

Does the host have thin-provisioned storage? lvmthin, thin-provisioned SAN,
etc?

Is the DB on NFS?

--
Craig Ringer http://www.2ndQuadrant.com/
2ndQuadrant - PostgreSQL Solutions for the Enterprise

#43Ashutosh Sharma
ashu.coek88@gmail.com
In reply to: Craig Ringer (#42)
Re: tableam vs. TOAST

Hi Craig,

Please find my response inline below.

On Sun, Nov 10, 2019 at 2:39 PM Craig Ringer <craig@2ndquadrant.com> wrote:

On Thu, 7 Nov 2019 at 22:45, Ashutosh Sharma <ashu.coek88@gmail.com> wrote:

In fact, I suspect this is PostgreSQL successfully protecting itself from an unsafe situation.

Does the host have thin-provisioned storage? lvmthin, thin-provisioned SAN, etc?

No, It doesn't. Infact the machine on which the issue was reproduced
once/twice doesn't have any LVMs. The other machine on which the issue
never got reproduced have some LVMs but they are thick-provisioned not
thin-provisioned.

Is the DB on NFS?

No.

--
With Regards,
Ashutosh Sharma
EnterpriseDB:http://www.enterprisedb.com

#44Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Robert Haas (#41)
Re: tableam vs. TOAST

On 2019-11-08 17:59, Robert Haas wrote:

On Wed, Nov 6, 2019 at 12:00 PM Andres Freund <andres@anarazel.de> wrote:

I'd like an AM to have the *option* of implementing something better, or
at least go in the direction of making that possible.

OK. Could you see what you think of the attached patches? 0001 does
some refactoring of toast_fetch_datum() and toast_fetch_datum_slice()
to make them look more like each other and clean up a bunch of stuff
that I thought was annoying, and 0002 then pulls out the common logic
into a heap-specific function. If you like this direction, we could
then push the heap-specific function below tableam, but I haven't done
that yet.

Compared to the previous patch (v7) where the API just had a "use this
AM for TOAST" field and the other extreme of pushing TOAST entirely
inside the heap AM, this seems like the worst of both worlds, with the
maximum additional complexity.

I don't think we need to nail down this API for eternity, so I'd be
happy to err on the side of practicality here. However, it seems it's
not quite clear what for example the requirements and wishes from zheap
would be. What's the simplest way to move this forward?

The refactorings you proposed seem reasonable on their own, and I have
some additional comments on that if we decide to go forward in this
direction. One thing that's confusing is that the TOAST tables have
fields chunk_id and chunk_seq, but when an error message talks about
"chunk %d" or "chunk number %d", they usually mean the "seq" and not the
"id".

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#45Robert Haas
robertmhaas@gmail.com
In reply to: Peter Eisentraut (#44)
Re: tableam vs. TOAST

On Mon, Nov 11, 2019 at 8:51 AM Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

Compared to the previous patch (v7) where the API just had a "use this
AM for TOAST" field and the other extreme of pushing TOAST entirely
inside the heap AM, this seems like the worst of both worlds, with the
maximum additional complexity.

There might be a misunderstanding here. These patches would still have
a "use this AM for TOAST" callback, just as the previous set did, but
I didn't include that here, because this is talking about a different
part of the problem. The purpose of that callback is to determine
which AM will be used to create the toast table. The purpose of these
patches is to be able to detoast a value given nothing but the TOAST
pointer extracted from the heap tuple, while removing the present
assumption that the TOAST table is a heap table.

(The current coding is actually seriously inconsistent, because right
now, the code that creates TOAST tables always uses the same AM as the
main heap; but the detoasting code only works with heap tables, which
means that no non-heap AM can use the TOAST system at all. If
necessary, we could commit the patch to allow the TOAST table AM to be
changed first, and then handle allowing the detoasting logic to cope
with a non-heap AM as a separate matter.)

I don't think we need to nail down this API for eternity, so I'd be
happy to err on the side of practicality here. However, it seems it's
not quite clear what for example the requirements and wishes from zheap
would be. What's the simplest way to move this forward?

The only thing zheap needs - in the current design, anyway - is the
ability to change the chunk size. However, I think that's mostly
because we haven't spent a lot of time thinking about how to do TOAST
better than the heap does TOAST today. I think it is desirable to
allow for more options than that. That's why I like this approach more
than the previous one. The previous approach allowed the chunk size to
be variable, but permitted no other AM-specific variation; this one
allows the AM to detoast in any way that it likes. The downside of
that is that if you really do only want to vary the chunk size, you'll
have to repeat somewhat more code. That's sad, but we're never likely
to have enough AMs for that to be a really serious problem, and if we
do, the AM-specific callbacks for those AMs that just want a different
chunk size could call a common helper function.

The refactorings you proposed seem reasonable on their own, and I have
some additional comments on that if we decide to go forward in this
direction. One thing that's confusing is that the TOAST tables have
fields chunk_id and chunk_seq, but when an error message talks about
"chunk %d" or "chunk number %d", they usually mean the "seq" and not the
"id".

Well, we've got errors like this right now:

unexpected chunk number %d (expected %d) for toast value %u in %s

So at least in this case, and I think in many cases, we're referring
to the chunk_id as "toast value %u" and the chunk_seq as "chunk number
%d". I think that's pretty good terminology. It's unfortunate that the
TOAST table columns are called chunk_id and chunk_seq rather than,
say, value_id and chunk_number, and I guess we could possibly change
that without breaking too many things, but I'm not sure that changing
the error messages would help anybody. We could try to rephrase the
error message to mention the two value in the opposite order, which to
me would be more clear, but I'm not exactly sure how to do that
without writing rather awkward English.

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

#46Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Robert Haas (#41)
Re: tableam vs. TOAST

On 2019-11-08 17:59, Robert Haas wrote:

OK. Could you see what you think of the attached patches? 0001 does
some refactoring of toast_fetch_datum() and toast_fetch_datum_slice()
to make them look more like each other and clean up a bunch of stuff
that I thought was annoying, and 0002 then pulls out the common logic
into a heap-specific function. If you like this direction, we could
then push the heap-specific function below tableam, but I haven't done
that yet.

Partial review: The 0001 patch seems very sensible. Some minor comments
on that:

Perhaps rename the residx variable (in both functions). You have gotten
rid of all the res* variables except that one. That name as it is right
now isn't very helpful at all.

You have collapsed the error messages for "chunk %d of %d" and "final
chunk %d" and replaced it with just "chunk %d". I think it might be
better to keep the "chunk %d of %d" wording, for more context, or was
there a reason why you wanted to remove the total count from the message?

I believe this assertion

+ Assert(endchunk <= totalchunks);

should be < (strictly less).

In the commit message you state that this assertion replaces a run-time
check, but I couldn't quite make out which one you are referring to
because all the existing run-time checks are kept, with slightly
refactored conditions.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#47Robert Haas
robertmhaas@gmail.com
In reply to: Peter Eisentraut (#46)
4 attachment(s)
Re: tableam vs. TOAST

On Thu, Nov 21, 2019 at 5:37 AM Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

Partial review: The 0001 patch seems very sensible. Some minor comments
on that:

Thanks for the review. Updated patches attached. This version is more
complete than the last set of patches I posted. It looks like this:

0001 - Lets a table AM that needs a toast table choose the AM that
will be used to implement the toast table.
0002 - Refactoring and code cleanup for TOAST code.
0003 - Move heap-specific portion of logic refactored by previous
patch to a separate function.
0004 - Lets a table AM arrange to call a different function when
detoasting, instead of the one created by 0003.

Perhaps rename the residx variable (in both functions). You have gotten
rid of all the res* variables except that one. That name as it is right
now isn't very helpful at all.

OK, I renamed residx to curchunk and nextidx to expectedchunk.

You have collapsed the error messages for "chunk %d of %d" and "final
chunk %d" and replaced it with just "chunk %d". I think it might be
better to keep the "chunk %d of %d" wording, for more context, or was
there a reason why you wanted to remove the total count from the message?

No, not really. Adjusted.

I believe this assertion

+ Assert(endchunk <= totalchunks);

should be < (strictly less).

I think you're right. Fixed.

In the commit message you state that this assertion replaces a run-time
check, but I couldn't quite make out which one you are referring to
because all the existing run-time checks are kept, with slightly
refactored conditions.

Pre-patch, there is this condition:

- if ((residx != nextidx) || (residx > endchunk) || (residx < startchunk)

This checks that the expected chunk number is equal to the one we
want, but not greater than the last one we were expecting nor less
than the first one we were expecting. The check is redundant if you
suppose that we never compute nextidx so that it is outside the bounds
of startchunk..endchunk. Since nextidx (or expectedchunk as these
patches now call it) is initialized to startchunk and then only
incremented, it seems impossible for the first condition to ever fail,
so it is no longer tested there. The latter chunk is also no longer
tested there; instead, we do this:

+ Assert(endchunk < totalchunks);

That's what the commit message is on about.

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

Attachments:

v9-0002-Code-cleanup-for-toast_fetch_datum-and-toast_fetc.patchapplication/octet-stream; name=v9-0002-Code-cleanup-for-toast_fetch_datum-and-toast_fetc.patchDownload
From 14034570a0c314f4c9d5de458b796bb0f4343006 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 22 Nov 2019 08:43:28 -0500
Subject: [PATCH v9 2/4] Code cleanup for toast_fetch_datum and
 toast_fetch_datum_slice.

Rework some of the checks for bad TOAST chunks to be a bit simpler
and easier to understand. These checks verify that (1) we get all
and only the chunk numbers we expect to see and (2) each chunk has
the expected size. However, the existing code was a bit hard to
understand, at least for me; try to make it clearer.

As part of that, have toast_fetch_datum_slice check the relationship
between endchunk and totalchunks only with an Assert() rather than
checking every chunk number against both values. There's no need to
check that relationship in production builds because it's not a
function of whether on-disk corruption is present; it's just a
question of whether the code does the right math.

Also, have toast_fetch_datum_slice() use ereport(ERROR) rather than
elog(ERROR). Commit fd6ec93bf890314ac694dc8a7f3c45702ecc1bbd made
the two functions inconsistent with each other.

Rename assorted variables for better clarity and consistency, and
move assorted variables from function scope to the function's main
loop. Remove a few variables that are used only once entirely.

Patch by me, reviewed by Peter Eisentraut.

Discussion: http://postgr.es/m/CA+TgmobBzxwFojJ0zV0Own3dr09y43hp+OzU2VW+nos4PMXWEg@mail.gmail.com
---
 src/backend/access/common/detoast.c | 220 +++++++++++++---------------
 1 file changed, 102 insertions(+), 118 deletions(-)

diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c
index 8c89fc2a55..61d7e64c44 100644
--- a/src/backend/access/common/detoast.c
+++ b/src/backend/access/common/detoast.c
@@ -25,7 +25,8 @@
 
 static struct varlena *toast_fetch_datum(struct varlena *attr);
 static struct varlena *toast_fetch_datum_slice(struct varlena *attr,
-											   int32 sliceoffset, int32 length);
+											   int32 sliceoffset,
+											   int32 slicelength);
 static struct varlena *toast_decompress_datum(struct varlena *attr);
 static struct varlena *toast_decompress_datum_slice(struct varlena *attr, int32 slicelength);
 
@@ -331,14 +332,9 @@ toast_fetch_datum(struct varlena *attr)
 	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;
+	int32		attrsize;
+	int32		expectedchunk;
+	int32		totalchunks;
 	int			num_indexes;
 	int			validIndex;
 	SnapshotData SnapshotToast;
@@ -349,15 +345,15 @@ toast_fetch_datum(struct varlena *attr)
 	/* 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;
+	attrsize = toast_pointer.va_extsize;
+	totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
 
-	result = (struct varlena *) palloc(ressize + VARHDRSZ);
+	result = (struct varlena *) palloc(attrsize + VARHDRSZ);
 
 	if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))
-		SET_VARSIZE_COMPRESSED(result, ressize + VARHDRSZ);
+		SET_VARSIZE_COMPRESSED(result, attrsize + VARHDRSZ);
 	else
-		SET_VARSIZE(result, ressize + VARHDRSZ);
+		SET_VARSIZE(result, attrsize + VARHDRSZ);
 
 	/*
 	 * Open the toast relation and its indexes
@@ -386,17 +382,24 @@ toast_fetch_datum(struct varlena *attr)
 	 * see the chunks in chunkidx order, even though we didn't explicitly ask
 	 * for it.
 	 */
-	nextidx = 0;
+	expectedchunk = 0;
 
 	init_toast_snapshot(&SnapshotToast);
 	toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
 										   &SnapshotToast, 1, &toastkey);
 	while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
 	{
+		int32		curchunk;
+		Pointer		chunk;
+		bool		isnull;
+		char	   *chunkdata;
+		int32		chunksize;
+		int32		expected_size;
+
 		/*
 		 * Have a chunk, extract the sequence number and the data
 		 */
-		residx = DatumGetInt32(fastgetattr(ttup, 2, toasttupDesc, &isnull));
+		curchunk = DatumGetInt32(fastgetattr(ttup, 2, toasttupDesc, &isnull));
 		Assert(!isnull);
 		chunk = DatumGetPointer(fastgetattr(ttup, 3, toasttupDesc, &isnull));
 		Assert(!isnull);
@@ -424,63 +427,50 @@ toast_fetch_datum(struct varlena *attr)
 		/*
 		 * Some checks on the data we've found
 		 */
-		if (residx != nextidx)
+		if (curchunk != expectedchunk)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATA_CORRUPTED),
 					 errmsg_internal("unexpected chunk number %d (expected %d) for toast value %u in %s",
-									 residx, nextidx,
+									 curchunk, expectedchunk,
 									 toast_pointer.va_valueid,
 									 RelationGetRelationName(toastrel))));
-		if (residx < numchunks - 1)
-		{
-			if (chunksize != TOAST_MAX_CHUNK_SIZE)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATA_CORRUPTED),
-						 errmsg_internal("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)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATA_CORRUPTED),
-						 errmsg_internal("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
+		if (curchunk > totalchunks - 1)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATA_CORRUPTED),
 					 errmsg_internal("unexpected chunk number %d (out of range %d..%d) for toast value %u in %s",
-									 residx,
-									 0, numchunks - 1,
+									 curchunk,
+									 0, totalchunks - 1,
+									 toast_pointer.va_valueid,
+									 RelationGetRelationName(toastrel))));
+		expected_size = curchunk < totalchunks - 1 ? TOAST_MAX_CHUNK_SIZE
+			: attrsize % TOAST_MAX_CHUNK_SIZE;
+		if (chunksize != expected_size)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+					 errmsg_internal("unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s",
+									 chunksize, expected_size,
+									 curchunk, totalchunks,
 									 toast_pointer.va_valueid,
 									 RelationGetRelationName(toastrel))));
 
 		/*
 		 * Copy the data into proper place in our result
 		 */
-		memcpy(VARDATA(result) + residx * TOAST_MAX_CHUNK_SIZE,
+		memcpy(VARDATA(result) + curchunk * TOAST_MAX_CHUNK_SIZE,
 			   chunkdata,
 			   chunksize);
 
-		nextidx++;
+		expectedchunk++;
 	}
 
 	/*
 	 * Final checks that we successfully fetched the datum
 	 */
-	if (nextidx != numchunks)
+	if (expectedchunk != totalchunks)
 		ereport(ERROR,
 				(errcode(ERRCODE_DATA_CORRUPTED),
 				 errmsg_internal("missing chunk number %d for toast value %u in %s",
-								 nextidx,
+								 expectedchunk,
 								 toast_pointer.va_valueid,
 								 RelationGetRelationName(toastrel))));
 
@@ -506,7 +496,8 @@ toast_fetch_datum(struct varlena *attr)
  * ----------
  */
 static struct varlena *
-toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length)
+toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
+						int32 slicelength)
 {
 	Relation	toastrel;
 	Relation   *toastidxs;
@@ -518,20 +509,10 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length)
 	struct varlena *result;
 	struct varatt_external toast_pointer;
 	int32		attrsize;
-	int32		residx;
-	int32		nextidx;
-	int			numchunks;
+	int32		expectedchunk;
 	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;
@@ -555,7 +536,7 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length)
 	if (sliceoffset >= attrsize)
 	{
 		sliceoffset = 0;
-		length = 0;
+		slicelength = 0;
 	}
 
 	/*
@@ -563,28 +544,25 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length)
 	 * rawsize tracking amount of raw data, which is stored at the beginning
 	 * as an int32 value).
 	 */
-	if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer) && length > 0)
-		length = length + sizeof(int32);
+	if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer) && slicelength > 0)
+		slicelength = slicelength + sizeof(int32);
 
-	if (((sliceoffset + length) > attrsize) || length < 0)
-		length = attrsize - sliceoffset;
+	if (((sliceoffset + slicelength) > attrsize) || slicelength < 0)
+		slicelength = attrsize - sliceoffset;
 
-	result = (struct varlena *) palloc(length + VARHDRSZ);
+	result = (struct varlena *) palloc(slicelength + VARHDRSZ);
 
 	if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))
-		SET_VARSIZE_COMPRESSED(result, length + VARHDRSZ);
+		SET_VARSIZE_COMPRESSED(result, slicelength + VARHDRSZ);
 	else
-		SET_VARSIZE(result, length + VARHDRSZ);
+		SET_VARSIZE(result, slicelength + VARHDRSZ);
 
-	if (length == 0)
+	if (slicelength == 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;
+	endchunk = (sliceoffset + slicelength - 1) / TOAST_MAX_CHUNK_SIZE;
+	Assert(endchunk < totalchunks);
 
 	/*
 	 * Open the toast relation and its indexes
@@ -610,7 +588,7 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length)
 	/*
 	 * Use equality condition for one chunk, a range condition otherwise:
 	 */
-	if (numchunks == 1)
+	if (startchunk == endchunk)
 	{
 		ScanKeyInit(&toastkey[1],
 					(AttrNumber) 2,
@@ -637,15 +615,24 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length)
 	 * The index is on (valueid, chunkidx) so they will come in order
 	 */
 	init_toast_snapshot(&SnapshotToast);
-	nextidx = startchunk;
+	expectedchunk = startchunk;
 	toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
 										   &SnapshotToast, nscankeys, toastkey);
 	while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
 	{
+		int32		curchunk;
+		Pointer		chunk;
+		bool		isnull;
+		char	   *chunkdata;
+		int32		chunksize;
+		int32		expected_size;
+		int32		chcpystrt;
+		int32		chcpyend;
+
 		/*
 		 * Have a chunk, extract the sequence number and the data
 		 */
-		residx = DatumGetInt32(fastgetattr(ttup, 2, toasttupDesc, &isnull));
+		curchunk = DatumGetInt32(fastgetattr(ttup, 2, toasttupDesc, &isnull));
 		Assert(!isnull);
 		chunk = DatumGetPointer(fastgetattr(ttup, 3, toasttupDesc, &isnull));
 		Assert(!isnull);
@@ -673,63 +660,60 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length)
 		/*
 		 * 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));
+		if (curchunk != expectedchunk)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+					 errmsg_internal("unexpected chunk number %d (expected %d) for toast value %u in %s",
+									 curchunk, expectedchunk,
+									 toast_pointer.va_valueid,
+									 RelationGetRelationName(toastrel))));
+		if (curchunk > endchunk)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+					 errmsg_internal("unexpected chunk number %d (out of range %d..%d) for toast value %u in %s",
+									 curchunk,
+									 startchunk, endchunk,
+									 toast_pointer.va_valueid,
+									 RelationGetRelationName(toastrel))));
+		expected_size = curchunk < totalchunks - 1 ? TOAST_MAX_CHUNK_SIZE
+			: attrsize % TOAST_MAX_CHUNK_SIZE;
+		if (chunksize != expected_size)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+					 errmsg_internal("unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s",
+									 chunksize, expected_size,
+									 curchunk, totalchunks,
+									 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;
+		if (curchunk == startchunk)
+			chcpystrt = sliceoffset % TOAST_MAX_CHUNK_SIZE;
+		if (curchunk == endchunk)
+			chcpyend = (sliceoffset + slicelength - 1) % TOAST_MAX_CHUNK_SIZE;
 
 		memcpy(VARDATA(result) +
-			   (residx * TOAST_MAX_CHUNK_SIZE - sliceoffset) + chcpystrt,
+			   (curchunk * TOAST_MAX_CHUNK_SIZE - sliceoffset) + chcpystrt,
 			   chunkdata + chcpystrt,
 			   (chcpyend - chcpystrt) + 1);
 
-		nextidx++;
+		expectedchunk++;
 	}
 
 	/*
 	 * 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));
+	if (expectedchunk != (endchunk + 1))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_CORRUPTED),
+				 errmsg_internal("missing chunk number %d for toast value %u in %s",
+								 expectedchunk,
+								 toast_pointer.va_valueid,
+								 RelationGetRelationName(toastrel))));
 
 	/*
 	 * End scan and close relations
-- 
2.17.2 (Apple Git-113)

v9-0003-Move-heap-specific-detoasting-logic-into-a-separa.patchapplication/octet-stream; name=v9-0003-Move-heap-specific-detoasting-logic-into-a-separa.patchDownload
From 2b03d85ec3d20f5e63ac4ce624c01368743811c1 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 8 Nov 2019 11:50:52 -0500
Subject: [PATCH v9 3/4] Move heap-specific detoasting logic into a separate
 function.

The new function, heap_fetch_toast_slice, is shared between
toast_fetch_datum_slice and toast_fetch_datum, and does all the
work of scanning the TOAST table, fetching chunks, and storing
them into the space allocated for the result varlena.

As an incidental side effect, this allows toast_fetch_datum_slice
to perform the scan with only a single scankey if all chunks are
being fetched, which might have some tiny performance benefit.

Discussion: http://postgr.es/m/CA+TgmobBzxwFojJ0zV0Own3dr09y43hp+OzU2VW+nos4PMXWEg@mail.gmail.com
---
 src/backend/access/common/detoast.c | 244 ++++++++--------------------
 1 file changed, 71 insertions(+), 173 deletions(-)

diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c
index 61d7e64c44..04b195149f 100644
--- a/src/backend/access/common/detoast.c
+++ b/src/backend/access/common/detoast.c
@@ -27,6 +27,9 @@ static struct varlena *toast_fetch_datum(struct varlena *attr);
 static struct varlena *toast_fetch_datum_slice(struct varlena *attr,
 											   int32 sliceoffset,
 											   int32 slicelength);
+static void heap_fetch_toast_slice(Relation toastrel, Oid valueid,
+								   int32 attrsize, int32 sliceoffset,
+								   int32 slicelength, struct varlena *result);
 static struct varlena *toast_decompress_datum(struct varlena *attr);
 static struct varlena *toast_decompress_datum_slice(struct varlena *attr, int32 slicelength);
 
@@ -325,19 +328,9 @@ 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		attrsize;
-	int32		expectedchunk;
-	int32		totalchunks;
-	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");
@@ -346,7 +339,6 @@ toast_fetch_datum(struct varlena *attr)
 	VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
 
 	attrsize = toast_pointer.va_extsize;
-	totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
 
 	result = (struct varlena *) palloc(attrsize + VARHDRSZ);
 
@@ -355,130 +347,19 @@ toast_fetch_datum(struct varlena *attr)
 	else
 		SET_VARSIZE(result, attrsize + VARHDRSZ);
 
+	if (attrsize == 0)
+		return result;		/* Probably shouldn't happen, but just in case. */
+
 	/*
 	 * 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));
+	/* Fetch all chunks */
+	heap_fetch_toast_slice(toastrel, toast_pointer.va_valueid, attrsize, 0,
+						   attrsize, result);
 
-	/*
-	 * 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.
-	 */
-	expectedchunk = 0;
-
-	init_toast_snapshot(&SnapshotToast);
-	toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
-										   &SnapshotToast, 1, &toastkey);
-	while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
-	{
-		int32		curchunk;
-		Pointer		chunk;
-		bool		isnull;
-		char	   *chunkdata;
-		int32		chunksize;
-		int32		expected_size;
-
-		/*
-		 * Have a chunk, extract the sequence number and the data
-		 */
-		curchunk = 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 (curchunk != expectedchunk)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATA_CORRUPTED),
-					 errmsg_internal("unexpected chunk number %d (expected %d) for toast value %u in %s",
-									 curchunk, expectedchunk,
-									 toast_pointer.va_valueid,
-									 RelationGetRelationName(toastrel))));
-		if (curchunk > totalchunks - 1)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATA_CORRUPTED),
-					 errmsg_internal("unexpected chunk number %d (out of range %d..%d) for toast value %u in %s",
-									 curchunk,
-									 0, totalchunks - 1,
-									 toast_pointer.va_valueid,
-									 RelationGetRelationName(toastrel))));
-		expected_size = curchunk < totalchunks - 1 ? TOAST_MAX_CHUNK_SIZE
-			: attrsize % TOAST_MAX_CHUNK_SIZE;
-		if (chunksize != expected_size)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATA_CORRUPTED),
-					 errmsg_internal("unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s",
-									 chunksize, expected_size,
-									 curchunk, totalchunks,
-									 toast_pointer.va_valueid,
-									 RelationGetRelationName(toastrel))));
-
-		/*
-		 * Copy the data into proper place in our result
-		 */
-		memcpy(VARDATA(result) + curchunk * TOAST_MAX_CHUNK_SIZE,
-			   chunkdata,
-			   chunksize);
-
-		expectedchunk++;
-	}
-
-	/*
-	 * Final checks that we successfully fetched the datum
-	 */
-	if (expectedchunk != totalchunks)
-		ereport(ERROR,
-				(errcode(ERRCODE_DATA_CORRUPTED),
-				 errmsg_internal("missing chunk number %d for toast value %u in %s",
-								 expectedchunk,
-								 toast_pointer.va_valueid,
-								 RelationGetRelationName(toastrel))));
-
-	/*
-	 * End scan and close relations
-	 */
-	systable_endscan_ordered(toastscan);
-	toast_close_indexes(toastidxs, num_indexes, AccessShareLock);
+	/* Close toast table */
 	table_close(toastrel, AccessShareLock);
 
 	return result;
@@ -500,22 +381,9 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 						int32 slicelength)
 {
 	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		expectedchunk;
-	int			startchunk;
-	int			endchunk;
-	int			totalchunks;
-	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");
@@ -531,7 +399,6 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 	Assert(!VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer) || 0 == sliceoffset);
 
 	attrsize = toast_pointer.va_extsize;
-	totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
 
 	if (sliceoffset >= attrsize)
 	{
@@ -560,15 +427,47 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 	if (slicelength == 0)
 		return result;			/* Can save a lot of work at this point! */
 
-	startchunk = sliceoffset / TOAST_MAX_CHUNK_SIZE;
-	endchunk = (sliceoffset + slicelength - 1) / TOAST_MAX_CHUNK_SIZE;
-	Assert(endchunk < totalchunks);
-
-	/*
-	 * Open the toast relation and its indexes
-	 */
+	/* Open the toast relation */
 	toastrel = table_open(toast_pointer.va_toastrelid, AccessShareLock);
-	toasttupDesc = toastrel->rd_att;
+
+	/* Fetch all chunks */
+	heap_fetch_toast_slice(toastrel, toast_pointer.va_valueid, attrsize,
+						   sliceoffset, slicelength, result);
+
+	/* Close toast table */
+	table_close(toastrel, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * Fetch a TOAST slice from a heap table.
+ *
+ * toastrel is the relation from which chunks are to be fetched.
+ * valueid identifies the TOAST value from which chunks are being fetched.
+ * attrsize is the total size of the TOAST value.
+ * sliceoffset is the byte offset within the TOAST value from which to fetch.
+ * slicelength is the number of bytes to be fetched from the TOAST value.
+ * result is the varlena into which the results should be written.
+ */
+static void
+heap_fetch_toast_slice(Relation toastrel, Oid valueid, int32 attrsize,
+					   int32 sliceoffset, int32 slicelength,
+					   struct varlena *result)
+{
+	Relation   *toastidxs;
+	ScanKeyData toastkey[3];
+	TupleDesc	toasttupDesc = toastrel->rd_att;
+	int			nscankeys;
+	SysScanDesc toastscan;
+	HeapTuple	ttup;
+	int32		expectedchunk;
+	int32		totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
+	int			startchunk;
+	int			endchunk;
+	int			num_indexes;
+	int			validIndex;
+	SnapshotData SnapshotToast;
 
 	/* Look for the valid index of toast relation */
 	validIndex = toast_open_indexes(toastrel,
@@ -576,6 +475,10 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 									&toastidxs,
 									&num_indexes);
 
+	startchunk = sliceoffset / TOAST_MAX_CHUNK_SIZE;
+	endchunk = (sliceoffset + slicelength - 1) / TOAST_MAX_CHUNK_SIZE;
+	Assert(endchunk <= totalchunks);
+
 	/*
 	 * Setup a scan key to fetch from the index. This is either two keys or
 	 * three depending on the number of chunks.
@@ -583,12 +486,15 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 	ScanKeyInit(&toastkey[0],
 				(AttrNumber) 1,
 				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(toast_pointer.va_valueid));
+				ObjectIdGetDatum(valueid));
 
 	/*
-	 * Use equality condition for one chunk, a range condition otherwise:
+	 * No addition condition if fetching all chuns. Otherwise, use an
+	 * equality condition for one chunk, and a range condition otherwise.
 	 */
-	if (startchunk == endchunk)
+	if (startchunk == 0 && endchunk == totalchunks - 1)
+		nscankeys = 1;
+	else if (startchunk == endchunk)
 	{
 		ScanKeyInit(&toastkey[1],
 					(AttrNumber) 2,
@@ -609,15 +515,17 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 		nscankeys = 3;
 	}
 
+	/* Prepare for scan */
+	init_toast_snapshot(&SnapshotToast);
+	toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
+										   &SnapshotToast, nscankeys, toastkey);
+
 	/*
 	 * Read the chunks by index
 	 *
 	 * The index is on (valueid, chunkidx) so they will come in order
 	 */
-	init_toast_snapshot(&SnapshotToast);
 	expectedchunk = startchunk;
-	toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
-										   &SnapshotToast, nscankeys, toastkey);
 	while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
 	{
 		int32		curchunk;
@@ -651,8 +559,7 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 		{
 			/* should never happen */
 			elog(ERROR, "found toasted toast chunk for toast value %u in %s",
-				 toast_pointer.va_valueid,
-				 RelationGetRelationName(toastrel));
+				 valueid, RelationGetRelationName(toastrel));
 			chunksize = 0;		/* keep compiler quiet */
 			chunkdata = NULL;
 		}
@@ -664,16 +571,14 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 			ereport(ERROR,
 					(errcode(ERRCODE_DATA_CORRUPTED),
 					 errmsg_internal("unexpected chunk number %d (expected %d) for toast value %u in %s",
-									 curchunk, expectedchunk,
-									 toast_pointer.va_valueid,
+									 curchunk, expectedchunk, valueid,
 									 RelationGetRelationName(toastrel))));
 		if (curchunk > endchunk)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATA_CORRUPTED),
 					 errmsg_internal("unexpected chunk number %d (out of range %d..%d) for toast value %u in %s",
 									 curchunk,
-									 startchunk, endchunk,
-									 toast_pointer.va_valueid,
+									 startchunk, endchunk, valueid,
 									 RelationGetRelationName(toastrel))));
 		expected_size = curchunk < totalchunks - 1 ? TOAST_MAX_CHUNK_SIZE
 			: attrsize % TOAST_MAX_CHUNK_SIZE;
@@ -682,8 +587,7 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 					(errcode(ERRCODE_DATA_CORRUPTED),
 					 errmsg_internal("unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s",
 									 chunksize, expected_size,
-									 curchunk, totalchunks,
-									 toast_pointer.va_valueid,
+									 curchunk, totalchunks, valueid,
 									 RelationGetRelationName(toastrel))));
 
 		/*
@@ -711,18 +615,12 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 		ereport(ERROR,
 				(errcode(ERRCODE_DATA_CORRUPTED),
 				 errmsg_internal("missing chunk number %d for toast value %u in %s",
-								 expectedchunk,
-								 toast_pointer.va_valueid,
+								 expectedchunk, valueid,
 								 RelationGetRelationName(toastrel))));
 
-	/*
-	 * End scan and close relations
-	 */
+	/* End scan and close indexes. */
 	systable_endscan_ordered(toastscan);
 	toast_close_indexes(toastidxs, num_indexes, AccessShareLock);
-	table_close(toastrel, AccessShareLock);
-
-	return result;
 }
 
 /* ----------
-- 
2.17.2 (Apple Git-113)

v9-0004-tableam-New-callback-relation_fetch_toast_slice.patchapplication/octet-stream; name=v9-0004-tableam-New-callback-relation_fetch_toast_slice.patchDownload
From f9e9c2e0e4a546eab8638643045e54a7da9147da Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 22 Nov 2019 10:12:22 -0500
Subject: [PATCH v9 4/4] tableam: New callback relation_fetch_toast_slice.

Instead of always calling heap_fetch_toast_slice during detoasting,
invoke a table AM callback which, when the toast table is a heap
table, will be heap_fetch_toast_slice.

This makes it possible for a table AM other than heap to be used
as a TOAST table.
---
 src/backend/access/common/detoast.c      | 199 +----------------------
 src/backend/access/heap/heapam_handler.c |   1 +
 src/backend/access/heap/heaptoast.c      | 185 +++++++++++++++++++++
 src/include/access/heaptoast.h           |  10 ++
 src/include/access/tableam.h             |  46 ++++++
 5 files changed, 248 insertions(+), 193 deletions(-)

diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c
index 04b195149f..3df0cf2512 100644
--- a/src/backend/access/common/detoast.c
+++ b/src/backend/access/common/detoast.c
@@ -14,22 +14,17 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "access/genam.h"
-#include "access/heaptoast.h"
 #include "access/table.h"
+#include "access/tableam.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 slicelength);
-static void heap_fetch_toast_slice(Relation toastrel, Oid valueid,
-								   int32 attrsize, int32 sliceoffset,
-								   int32 slicelength, struct varlena *result);
 static struct varlena *toast_decompress_datum(struct varlena *attr);
 static struct varlena *toast_decompress_datum_slice(struct varlena *attr, int32 slicelength);
 
@@ -356,8 +351,8 @@ toast_fetch_datum(struct varlena *attr)
 	toastrel = table_open(toast_pointer.va_toastrelid, AccessShareLock);
 
 	/* Fetch all chunks */
-	heap_fetch_toast_slice(toastrel, toast_pointer.va_valueid, attrsize, 0,
-						   attrsize, result);
+	table_relation_fetch_toast_slice(toastrel, toast_pointer.va_valueid,
+									 attrsize, 0, attrsize, result);
 
 	/* Close toast table */
 	table_close(toastrel, AccessShareLock);
@@ -431,8 +426,9 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 	toastrel = table_open(toast_pointer.va_toastrelid, AccessShareLock);
 
 	/* Fetch all chunks */
-	heap_fetch_toast_slice(toastrel, toast_pointer.va_valueid, attrsize,
-						   sliceoffset, slicelength, result);
+	table_relation_fetch_toast_slice(toastrel, toast_pointer.va_valueid,
+									 attrsize, sliceoffset, slicelength,
+									 result);
 
 	/* Close toast table */
 	table_close(toastrel, AccessShareLock);
@@ -440,189 +436,6 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 	return result;
 }
 
-/*
- * Fetch a TOAST slice from a heap table.
- *
- * toastrel is the relation from which chunks are to be fetched.
- * valueid identifies the TOAST value from which chunks are being fetched.
- * attrsize is the total size of the TOAST value.
- * sliceoffset is the byte offset within the TOAST value from which to fetch.
- * slicelength is the number of bytes to be fetched from the TOAST value.
- * result is the varlena into which the results should be written.
- */
-static void
-heap_fetch_toast_slice(Relation toastrel, Oid valueid, int32 attrsize,
-					   int32 sliceoffset, int32 slicelength,
-					   struct varlena *result)
-{
-	Relation   *toastidxs;
-	ScanKeyData toastkey[3];
-	TupleDesc	toasttupDesc = toastrel->rd_att;
-	int			nscankeys;
-	SysScanDesc toastscan;
-	HeapTuple	ttup;
-	int32		expectedchunk;
-	int32		totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
-	int			startchunk;
-	int			endchunk;
-	int			num_indexes;
-	int			validIndex;
-	SnapshotData SnapshotToast;
-
-	/* Look for the valid index of toast relation */
-	validIndex = toast_open_indexes(toastrel,
-									AccessShareLock,
-									&toastidxs,
-									&num_indexes);
-
-	startchunk = sliceoffset / TOAST_MAX_CHUNK_SIZE;
-	endchunk = (sliceoffset + slicelength - 1) / TOAST_MAX_CHUNK_SIZE;
-	Assert(endchunk <= totalchunks);
-
-	/*
-	 * 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(valueid));
-
-	/*
-	 * No addition condition if fetching all chuns. Otherwise, use an
-	 * equality condition for one chunk, and a range condition otherwise.
-	 */
-	if (startchunk == 0 && endchunk == totalchunks - 1)
-		nscankeys = 1;
-	else if (startchunk == endchunk)
-	{
-		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;
-	}
-
-	/* Prepare for scan */
-	init_toast_snapshot(&SnapshotToast);
-	toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
-										   &SnapshotToast, nscankeys, toastkey);
-
-	/*
-	 * Read the chunks by index
-	 *
-	 * The index is on (valueid, chunkidx) so they will come in order
-	 */
-	expectedchunk = startchunk;
-	while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
-	{
-		int32		curchunk;
-		Pointer		chunk;
-		bool		isnull;
-		char	   *chunkdata;
-		int32		chunksize;
-		int32		expected_size;
-		int32		chcpystrt;
-		int32		chcpyend;
-
-		/*
-		 * Have a chunk, extract the sequence number and the data
-		 */
-		curchunk = 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",
-				 valueid, RelationGetRelationName(toastrel));
-			chunksize = 0;		/* keep compiler quiet */
-			chunkdata = NULL;
-		}
-
-		/*
-		 * Some checks on the data we've found
-		 */
-		if (curchunk != expectedchunk)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATA_CORRUPTED),
-					 errmsg_internal("unexpected chunk number %d (expected %d) for toast value %u in %s",
-									 curchunk, expectedchunk, valueid,
-									 RelationGetRelationName(toastrel))));
-		if (curchunk > endchunk)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATA_CORRUPTED),
-					 errmsg_internal("unexpected chunk number %d (out of range %d..%d) for toast value %u in %s",
-									 curchunk,
-									 startchunk, endchunk, valueid,
-									 RelationGetRelationName(toastrel))));
-		expected_size = curchunk < totalchunks - 1 ? TOAST_MAX_CHUNK_SIZE
-			: attrsize % TOAST_MAX_CHUNK_SIZE;
-		if (chunksize != expected_size)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATA_CORRUPTED),
-					 errmsg_internal("unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s",
-									 chunksize, expected_size,
-									 curchunk, totalchunks, valueid,
-									 RelationGetRelationName(toastrel))));
-
-		/*
-		 * Copy the data into proper place in our result
-		 */
-		chcpystrt = 0;
-		chcpyend = chunksize - 1;
-		if (curchunk == startchunk)
-			chcpystrt = sliceoffset % TOAST_MAX_CHUNK_SIZE;
-		if (curchunk == endchunk)
-			chcpyend = (sliceoffset + slicelength - 1) % TOAST_MAX_CHUNK_SIZE;
-
-		memcpy(VARDATA(result) +
-			   (curchunk * TOAST_MAX_CHUNK_SIZE - sliceoffset) + chcpystrt,
-			   chunkdata + chcpystrt,
-			   (chcpyend - chcpystrt) + 1);
-
-		expectedchunk++;
-	}
-
-	/*
-	 * Final checks that we successfully fetched the datum
-	 */
-	if (expectedchunk != (endchunk + 1))
-		ereport(ERROR,
-				(errcode(ERRCODE_DATA_CORRUPTED),
-				 errmsg_internal("missing chunk number %d for toast value %u in %s",
-								 expectedchunk, valueid,
-								 RelationGetRelationName(toastrel))));
-
-	/* End scan and close indexes. */
-	systable_endscan_ordered(toastscan);
-	toast_close_indexes(toastidxs, num_indexes, AccessShareLock);
-}
-
 /* ----------
  * toast_decompress_datum -
  *
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 1d053e74e6..b443c6c1d5 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -2545,6 +2545,7 @@ static const TableAmRoutine heapam_methods = {
 	.relation_size = table_block_relation_size,
 	.relation_needs_toast_table = heapam_relation_needs_toast_table,
 	.relation_toast_am = heapam_relation_toast_am,
+	.relation_fetch_toast_slice = heap_fetch_toast_slice,
 
 	.relation_estimate_size = heapam_estimate_rel_size,
 
diff --git a/src/backend/access/heap/heaptoast.c b/src/backend/access/heap/heaptoast.c
index dcfdee4467..2ee639081a 100644
--- a/src/backend/access/heap/heaptoast.c
+++ b/src/backend/access/heap/heaptoast.c
@@ -25,10 +25,12 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
+#include "access/genam.h"
 #include "access/heapam.h"
 #include "access/heaptoast.h"
 #include "access/toast_helper.h"
 #include "access/toast_internals.h"
+#include "utils/fmgroids.h"
 
 
 /* ----------
@@ -604,3 +606,186 @@ toast_build_flattened_tuple(TupleDesc tupleDesc,
 
 	return new_tuple;
 }
+
+/*
+ * Fetch a TOAST slice from a heap table.
+ *
+ * toastrel is the relation from which chunks are to be fetched.
+ * valueid identifies the TOAST value from which chunks are being fetched.
+ * attrsize is the total size of the TOAST value.
+ * sliceoffset is the byte offset within the TOAST value from which to fetch.
+ * slicelength is the number of bytes to be fetched from the TOAST value.
+ * result is the varlena into which the results should be written.
+ */
+void
+heap_fetch_toast_slice(Relation toastrel, Oid valueid, int32 attrsize,
+					   int32 sliceoffset, int32 slicelength,
+					   struct varlena *result)
+{
+	Relation   *toastidxs;
+	ScanKeyData toastkey[3];
+	TupleDesc	toasttupDesc = toastrel->rd_att;
+	int			nscankeys;
+	SysScanDesc toastscan;
+	HeapTuple	ttup;
+	int32		expectedchunk;
+	int32		totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
+	int			startchunk;
+	int			endchunk;
+	int			num_indexes;
+	int			validIndex;
+	SnapshotData SnapshotToast;
+
+	/* Look for the valid index of toast relation */
+	validIndex = toast_open_indexes(toastrel,
+									AccessShareLock,
+									&toastidxs,
+									&num_indexes);
+
+	startchunk = sliceoffset / TOAST_MAX_CHUNK_SIZE;
+	endchunk = (sliceoffset + slicelength - 1) / TOAST_MAX_CHUNK_SIZE;
+	Assert(endchunk <= totalchunks);
+
+	/*
+	 * 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(valueid));
+
+	/*
+	 * No addition condition if fetching all chuns. Otherwise, use an
+	 * equality condition for one chunk, and a range condition otherwise.
+	 */
+	if (startchunk == 0 && endchunk == totalchunks - 1)
+		nscankeys = 1;
+	else if (startchunk == endchunk)
+	{
+		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;
+	}
+
+	/* Prepare for scan */
+	init_toast_snapshot(&SnapshotToast);
+	toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
+										   &SnapshotToast, nscankeys, toastkey);
+
+	/*
+	 * Read the chunks by index
+	 *
+	 * The index is on (valueid, chunkidx) so they will come in order
+	 */
+	expectedchunk = startchunk;
+	while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
+	{
+		int32		curchunk;
+		Pointer		chunk;
+		bool		isnull;
+		char	   *chunkdata;
+		int32		chunksize;
+		int32		expected_size;
+		int32		chcpystrt;
+		int32		chcpyend;
+
+		/*
+		 * Have a chunk, extract the sequence number and the data
+		 */
+		curchunk = 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",
+				 valueid, RelationGetRelationName(toastrel));
+			chunksize = 0;		/* keep compiler quiet */
+			chunkdata = NULL;
+		}
+
+		/*
+		 * Some checks on the data we've found
+		 */
+		if (curchunk != expectedchunk)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+					 errmsg_internal("unexpected chunk number %d (expected %d) for toast value %u in %s",
+									 curchunk, expectedchunk, valueid,
+									 RelationGetRelationName(toastrel))));
+		if (curchunk > endchunk)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+					 errmsg_internal("unexpected chunk number %d (out of range %d..%d) for toast value %u in %s",
+									 curchunk,
+									 startchunk, endchunk, valueid,
+									 RelationGetRelationName(toastrel))));
+		expected_size = curchunk < totalchunks - 1 ? TOAST_MAX_CHUNK_SIZE
+			: attrsize % TOAST_MAX_CHUNK_SIZE;
+		if (chunksize != expected_size)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+					 errmsg_internal("unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s",
+									 chunksize, expected_size,
+									 curchunk, totalchunks, valueid,
+									 RelationGetRelationName(toastrel))));
+
+		/*
+		 * Copy the data into proper place in our result
+		 */
+		chcpystrt = 0;
+		chcpyend = chunksize - 1;
+		if (curchunk == startchunk)
+			chcpystrt = sliceoffset % TOAST_MAX_CHUNK_SIZE;
+		if (curchunk == endchunk)
+			chcpyend = (sliceoffset + slicelength - 1) % TOAST_MAX_CHUNK_SIZE;
+
+		memcpy(VARDATA(result) +
+			   (curchunk * TOAST_MAX_CHUNK_SIZE - sliceoffset) + chcpystrt,
+			   chunkdata + chcpystrt,
+			   (chcpyend - chcpystrt) + 1);
+
+		expectedchunk++;
+	}
+
+	/*
+	 * Final checks that we successfully fetched the datum
+	 */
+	if (expectedchunk != (endchunk + 1))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_CORRUPTED),
+				 errmsg_internal("missing chunk number %d for toast value %u in %s",
+								 expectedchunk, valueid,
+								 RelationGetRelationName(toastrel))));
+
+	/* End scan and close indexes. */
+	systable_endscan_ordered(toastscan);
+	toast_close_indexes(toastidxs, num_indexes, AccessShareLock);
+}
diff --git a/src/include/access/heaptoast.h b/src/include/access/heaptoast.h
index 488a2e4a7f..0bf6b23c8f 100644
--- a/src/include/access/heaptoast.h
+++ b/src/include/access/heaptoast.h
@@ -136,4 +136,14 @@ extern HeapTuple toast_build_flattened_tuple(TupleDesc tupleDesc,
 											 Datum *values,
 											 bool *isnull);
 
+/* ----------
+ * heap_fetch_toast_slice
+ *
+ *	Fetch a slice from a toast value stored in a heap table.
+ * ----------
+ */
+extern void heap_fetch_toast_slice(Relation toastrel, Oid valueid,
+								   int32 attrsize, int32 sliceoffset,
+								   int32 slicelength, struct varlena *result);
+
 #endif							/* HEAPTOAST_H */
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 7ac9bb6d65..b784aaf5a9 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -588,6 +588,17 @@ typedef struct TableAmRoutine
 	 */
 	Oid		    (*relation_toast_am) (Relation rel);
 
+	/*
+	 * This callback is invoked when detoasting a value stored in a toast
+	 * table implemented by this AM.  See table_relation_fetch_toast_slice()
+	 * for more details.
+	 */
+	void		(*relation_fetch_toast_slice) (Relation toastrel, Oid valueid,
+											   int32 attrsize,
+											   int32 sliceoffset,
+											   int32 slicelength,
+											   struct varlena *result);
+
 
 	/* ------------------------------------------------------------------------
 	 * Planner related functions.
@@ -1620,6 +1631,41 @@ table_relation_toast_am(Relation rel)
 	return rel->rd_tableam->relation_toast_am(rel);
 }
 
+/*
+ * Fetch all or part of a TOAST value from a TOAST table.
+ *
+ * If this AM is never used to implement a TOAST table, then this callback
+ * is not needed. But, if toasted values are ever stored in a table of this
+ * type, then you will need this callback.
+ *
+ * toastrel is the relation in which the toasted value is stored.
+ *
+ * valueid identifes which toast value is to be fetched. For the heap,
+ * this corresponds to the values stored in the chunk_id column.
+ *
+ * attrsize is the total size of the toast value to be fetched.
+ *
+ * sliceoffset is the offset within the toast value of the first byte that
+ * should be fetched.
+ *
+ * slicelength is the number of bytes from the toast value that should be
+ * fetched.
+ *
+ * result is caller-allocated space into which the fetched bytes should be
+ * stored.
+ */
+static inline void
+table_relation_fetch_toast_slice(Relation toastrel, Oid valueid,
+								 int32 attrsize, int32 sliceoffset,
+								 int32 slicelength, struct varlena *result)
+{
+	return toastrel->rd_tableam->relation_fetch_toast_slice(toastrel, valueid,
+															attrsize,
+															sliceoffset,
+															slicelength,
+															result);
+}
+
 
 /* ----------------------------------------------------------------------------
  * Planner related functionality
-- 
2.17.2 (Apple Git-113)

v9-0001-tableam-Allow-choice-of-toast-AM.patchapplication/octet-stream; name=v9-0001-tableam-Allow-choice-of-toast-AM.patchDownload
From 1e92076fe0b73423b572db9dfd34b6e4d2a13deb Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 22 Nov 2019 09:23:34 -0500
Subject: [PATCH v9 1/4] tableam: Allow choice of toast AM.

Previously, the toast table had to be implemented by the same AM that
was used for the main table, which was bad, because the detoasting
code won't work with anything but heap. This commit doesn't fix the
latter problem, though I plan to do that in a later commit, but it
does let you choose what AM you'd like to use, so that a table AM
can at least choose heap and get something that works.

Patch by me, reviewed by Andres Freund.

Discussion: http://postgr.es/m/CA+TgmoZv-=2iWM4jcw5ZhJeL18HF96+W1yJeYrnGMYdkFFnEpQ@mail.gmail.com
---
 src/backend/access/heap/heapam_handler.c | 10 ++++++++++
 src/backend/catalog/toasting.c           |  2 +-
 src/include/access/tableam.h             | 17 +++++++++++++++++
 3 files changed, 28 insertions(+), 1 deletion(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 92073fec54..1d053e74e6 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -2037,6 +2037,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
@@ -2535,6 +2544,7 @@ static const TableAmRoutine heapam_methods = {
 
 	.relation_size = table_block_relation_size,
 	.relation_needs_toast_table = heapam_relation_needs_toast_table,
+	.relation_toast_am = heapam_relation_toast_am,
 
 	.relation_estimate_size = heapam_estimate_rel_size,
 
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/tableam.h b/src/include/access/tableam.h
index 64022917e2..7ac9bb6d65 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -581,6 +581,13 @@ 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);
+
 
 	/* ------------------------------------------------------------------------
 	 * Planner related functions.
@@ -1603,6 +1610,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
-- 
2.17.2 (Apple Git-113)

#48Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#47)
3 attachment(s)
Re: tableam vs. TOAST

On Fri, Nov 22, 2019 at 10:41 AM Robert Haas <robertmhaas@gmail.com> wrote:

Thanks for the review. Updated patches attached. This version is more
complete than the last set of patches I posted. It looks like this:

0001 - Lets a table AM that needs a toast table choose the AM that
will be used to implement the toast table.
0002 - Refactoring and code cleanup for TOAST code.
0003 - Move heap-specific portion of logic refactored by previous
patch to a separate function.
0004 - Lets a table AM arrange to call a different function when
detoasting, instead of the one created by 0003.

Hearing no further comments, I went ahead and pushed 0002 today. That
turned out to have a bug, so I pushed a fix for that. Hopefully the
buildfarm will agree that it's fixed.

Meanwhile, here are the remaining patches again, rebased over the bug fix.

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

Attachments:

v10-0003-tableam-New-callback-relation_fetch_toast_slice.patchapplication/octet-stream; name=v10-0003-tableam-New-callback-relation_fetch_toast_slice.patchDownload
From 41c08290d25eebc5e2aad9e6211db4bc7a9053b4 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 22 Nov 2019 10:12:22 -0500
Subject: [PATCH v10 3/3] tableam: New callback relation_fetch_toast_slice.

Instead of always calling heap_fetch_toast_slice during detoasting,
invoke a table AM callback which, when the toast table is a heap
table, will be heap_fetch_toast_slice.

This makes it possible for a table AM other than heap to be used
as a TOAST table.
---
 src/backend/access/common/detoast.c      | 199 +----------------------
 src/backend/access/heap/heapam_handler.c |   1 +
 src/backend/access/heap/heaptoast.c      | 185 +++++++++++++++++++++
 src/include/access/heaptoast.h           |  10 ++
 src/include/access/tableam.h             |  46 ++++++
 5 files changed, 248 insertions(+), 193 deletions(-)

diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c
index c1e6eabe89..3df0cf2512 100644
--- a/src/backend/access/common/detoast.c
+++ b/src/backend/access/common/detoast.c
@@ -14,22 +14,17 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "access/genam.h"
-#include "access/heaptoast.h"
 #include "access/table.h"
+#include "access/tableam.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 slicelength);
-static void heap_fetch_toast_slice(Relation toastrel, Oid valueid,
-								   int32 attrsize, int32 sliceoffset,
-								   int32 slicelength, struct varlena *result);
 static struct varlena *toast_decompress_datum(struct varlena *attr);
 static struct varlena *toast_decompress_datum_slice(struct varlena *attr, int32 slicelength);
 
@@ -356,8 +351,8 @@ toast_fetch_datum(struct varlena *attr)
 	toastrel = table_open(toast_pointer.va_toastrelid, AccessShareLock);
 
 	/* Fetch all chunks */
-	heap_fetch_toast_slice(toastrel, toast_pointer.va_valueid, attrsize, 0,
-						   attrsize, result);
+	table_relation_fetch_toast_slice(toastrel, toast_pointer.va_valueid,
+									 attrsize, 0, attrsize, result);
 
 	/* Close toast table */
 	table_close(toastrel, AccessShareLock);
@@ -431,8 +426,9 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 	toastrel = table_open(toast_pointer.va_toastrelid, AccessShareLock);
 
 	/* Fetch all chunks */
-	heap_fetch_toast_slice(toastrel, toast_pointer.va_valueid, attrsize,
-						   sliceoffset, slicelength, result);
+	table_relation_fetch_toast_slice(toastrel, toast_pointer.va_valueid,
+									 attrsize, sliceoffset, slicelength,
+									 result);
 
 	/* Close toast table */
 	table_close(toastrel, AccessShareLock);
@@ -440,189 +436,6 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 	return result;
 }
 
-/*
- * Fetch a TOAST slice from a heap table.
- *
- * toastrel is the relation from which chunks are to be fetched.
- * valueid identifies the TOAST value from which chunks are being fetched.
- * attrsize is the total size of the TOAST value.
- * sliceoffset is the byte offset within the TOAST value from which to fetch.
- * slicelength is the number of bytes to be fetched from the TOAST value.
- * result is the varlena into which the results should be written.
- */
-static void
-heap_fetch_toast_slice(Relation toastrel, Oid valueid, int32 attrsize,
-					   int32 sliceoffset, int32 slicelength,
-					   struct varlena *result)
-{
-	Relation   *toastidxs;
-	ScanKeyData toastkey[3];
-	TupleDesc	toasttupDesc = toastrel->rd_att;
-	int			nscankeys;
-	SysScanDesc toastscan;
-	HeapTuple	ttup;
-	int32		expectedchunk;
-	int32		totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
-	int			startchunk;
-	int			endchunk;
-	int			num_indexes;
-	int			validIndex;
-	SnapshotData SnapshotToast;
-
-	/* Look for the valid index of toast relation */
-	validIndex = toast_open_indexes(toastrel,
-									AccessShareLock,
-									&toastidxs,
-									&num_indexes);
-
-	startchunk = sliceoffset / TOAST_MAX_CHUNK_SIZE;
-	endchunk = (sliceoffset + slicelength - 1) / TOAST_MAX_CHUNK_SIZE;
-	Assert(endchunk <= totalchunks);
-
-	/*
-	 * 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(valueid));
-
-	/*
-	 * No addition condition if fetching all chuns. Otherwise, use an
-	 * equality condition for one chunk, and a range condition otherwise.
-	 */
-	if (startchunk == 0 && endchunk == totalchunks - 1)
-		nscankeys = 1;
-	else if (startchunk == endchunk)
-	{
-		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;
-	}
-
-	/* Prepare for scan */
-	init_toast_snapshot(&SnapshotToast);
-	toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
-										   &SnapshotToast, nscankeys, toastkey);
-
-	/*
-	 * Read the chunks by index
-	 *
-	 * The index is on (valueid, chunkidx) so they will come in order
-	 */
-	expectedchunk = startchunk;
-	while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
-	{
-		int32		curchunk;
-		Pointer		chunk;
-		bool		isnull;
-		char	   *chunkdata;
-		int32		chunksize;
-		int32		expected_size;
-		int32		chcpystrt;
-		int32		chcpyend;
-
-		/*
-		 * Have a chunk, extract the sequence number and the data
-		 */
-		curchunk = 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",
-				 valueid, RelationGetRelationName(toastrel));
-			chunksize = 0;		/* keep compiler quiet */
-			chunkdata = NULL;
-		}
-
-		/*
-		 * Some checks on the data we've found
-		 */
-		if (curchunk != expectedchunk)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATA_CORRUPTED),
-					 errmsg_internal("unexpected chunk number %d (expected %d) for toast value %u in %s",
-									 curchunk, expectedchunk, valueid,
-									 RelationGetRelationName(toastrel))));
-		if (curchunk > endchunk)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATA_CORRUPTED),
-					 errmsg_internal("unexpected chunk number %d (out of range %d..%d) for toast value %u in %s",
-									 curchunk,
-									 startchunk, endchunk, valueid,
-									 RelationGetRelationName(toastrel))));
-		expected_size = curchunk < totalchunks - 1 ? TOAST_MAX_CHUNK_SIZE
-			: attrsize - ((totalchunks - 1) * TOAST_MAX_CHUNK_SIZE);
-		if (chunksize != expected_size)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATA_CORRUPTED),
-					 errmsg_internal("unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s",
-									 chunksize, expected_size,
-									 curchunk, totalchunks, valueid,
-									 RelationGetRelationName(toastrel))));
-
-		/*
-		 * Copy the data into proper place in our result
-		 */
-		chcpystrt = 0;
-		chcpyend = chunksize - 1;
-		if (curchunk == startchunk)
-			chcpystrt = sliceoffset % TOAST_MAX_CHUNK_SIZE;
-		if (curchunk == endchunk)
-			chcpyend = (sliceoffset + slicelength - 1) % TOAST_MAX_CHUNK_SIZE;
-
-		memcpy(VARDATA(result) +
-			   (curchunk * TOAST_MAX_CHUNK_SIZE - sliceoffset) + chcpystrt,
-			   chunkdata + chcpystrt,
-			   (chcpyend - chcpystrt) + 1);
-
-		expectedchunk++;
-	}
-
-	/*
-	 * Final checks that we successfully fetched the datum
-	 */
-	if (expectedchunk != (endchunk + 1))
-		ereport(ERROR,
-				(errcode(ERRCODE_DATA_CORRUPTED),
-				 errmsg_internal("missing chunk number %d for toast value %u in %s",
-								 expectedchunk, valueid,
-								 RelationGetRelationName(toastrel))));
-
-	/* End scan and close indexes. */
-	systable_endscan_ordered(toastscan);
-	toast_close_indexes(toastidxs, num_indexes, AccessShareLock);
-}
-
 /* ----------
  * toast_decompress_datum -
  *
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 1d053e74e6..b443c6c1d5 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -2545,6 +2545,7 @@ static const TableAmRoutine heapam_methods = {
 	.relation_size = table_block_relation_size,
 	.relation_needs_toast_table = heapam_relation_needs_toast_table,
 	.relation_toast_am = heapam_relation_toast_am,
+	.relation_fetch_toast_slice = heap_fetch_toast_slice,
 
 	.relation_estimate_size = heapam_estimate_rel_size,
 
diff --git a/src/backend/access/heap/heaptoast.c b/src/backend/access/heap/heaptoast.c
index dcfdee4467..410920c1a7 100644
--- a/src/backend/access/heap/heaptoast.c
+++ b/src/backend/access/heap/heaptoast.c
@@ -25,10 +25,12 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
+#include "access/genam.h"
 #include "access/heapam.h"
 #include "access/heaptoast.h"
 #include "access/toast_helper.h"
 #include "access/toast_internals.h"
+#include "utils/fmgroids.h"
 
 
 /* ----------
@@ -604,3 +606,186 @@ toast_build_flattened_tuple(TupleDesc tupleDesc,
 
 	return new_tuple;
 }
+
+/*
+ * Fetch a TOAST slice from a heap table.
+ *
+ * toastrel is the relation from which chunks are to be fetched.
+ * valueid identifies the TOAST value from which chunks are being fetched.
+ * attrsize is the total size of the TOAST value.
+ * sliceoffset is the byte offset within the TOAST value from which to fetch.
+ * slicelength is the number of bytes to be fetched from the TOAST value.
+ * result is the varlena into which the results should be written.
+ */
+void
+heap_fetch_toast_slice(Relation toastrel, Oid valueid, int32 attrsize,
+					   int32 sliceoffset, int32 slicelength,
+					   struct varlena *result)
+{
+	Relation   *toastidxs;
+	ScanKeyData toastkey[3];
+	TupleDesc	toasttupDesc = toastrel->rd_att;
+	int			nscankeys;
+	SysScanDesc toastscan;
+	HeapTuple	ttup;
+	int32		expectedchunk;
+	int32		totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
+	int			startchunk;
+	int			endchunk;
+	int			num_indexes;
+	int			validIndex;
+	SnapshotData SnapshotToast;
+
+	/* Look for the valid index of toast relation */
+	validIndex = toast_open_indexes(toastrel,
+									AccessShareLock,
+									&toastidxs,
+									&num_indexes);
+
+	startchunk = sliceoffset / TOAST_MAX_CHUNK_SIZE;
+	endchunk = (sliceoffset + slicelength - 1) / TOAST_MAX_CHUNK_SIZE;
+	Assert(endchunk <= totalchunks);
+
+	/*
+	 * 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(valueid));
+
+	/*
+	 * No addition condition if fetching all chuns. Otherwise, use an
+	 * equality condition for one chunk, and a range condition otherwise.
+	 */
+	if (startchunk == 0 && endchunk == totalchunks - 1)
+		nscankeys = 1;
+	else if (startchunk == endchunk)
+	{
+		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;
+	}
+
+	/* Prepare for scan */
+	init_toast_snapshot(&SnapshotToast);
+	toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
+										   &SnapshotToast, nscankeys, toastkey);
+
+	/*
+	 * Read the chunks by index
+	 *
+	 * The index is on (valueid, chunkidx) so they will come in order
+	 */
+	expectedchunk = startchunk;
+	while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
+	{
+		int32		curchunk;
+		Pointer		chunk;
+		bool		isnull;
+		char	   *chunkdata;
+		int32		chunksize;
+		int32		expected_size;
+		int32		chcpystrt;
+		int32		chcpyend;
+
+		/*
+		 * Have a chunk, extract the sequence number and the data
+		 */
+		curchunk = 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",
+				 valueid, RelationGetRelationName(toastrel));
+			chunksize = 0;		/* keep compiler quiet */
+			chunkdata = NULL;
+		}
+
+		/*
+		 * Some checks on the data we've found
+		 */
+		if (curchunk != expectedchunk)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+					 errmsg_internal("unexpected chunk number %d (expected %d) for toast value %u in %s",
+									 curchunk, expectedchunk, valueid,
+									 RelationGetRelationName(toastrel))));
+		if (curchunk > endchunk)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+					 errmsg_internal("unexpected chunk number %d (out of range %d..%d) for toast value %u in %s",
+									 curchunk,
+									 startchunk, endchunk, valueid,
+									 RelationGetRelationName(toastrel))));
+		expected_size = curchunk < totalchunks - 1 ? TOAST_MAX_CHUNK_SIZE
+			: attrsize - ((totalchunks - 1) * TOAST_MAX_CHUNK_SIZE);
+		if (chunksize != expected_size)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+					 errmsg_internal("unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s",
+									 chunksize, expected_size,
+									 curchunk, totalchunks, valueid,
+									 RelationGetRelationName(toastrel))));
+
+		/*
+		 * Copy the data into proper place in our result
+		 */
+		chcpystrt = 0;
+		chcpyend = chunksize - 1;
+		if (curchunk == startchunk)
+			chcpystrt = sliceoffset % TOAST_MAX_CHUNK_SIZE;
+		if (curchunk == endchunk)
+			chcpyend = (sliceoffset + slicelength - 1) % TOAST_MAX_CHUNK_SIZE;
+
+		memcpy(VARDATA(result) +
+			   (curchunk * TOAST_MAX_CHUNK_SIZE - sliceoffset) + chcpystrt,
+			   chunkdata + chcpystrt,
+			   (chcpyend - chcpystrt) + 1);
+
+		expectedchunk++;
+	}
+
+	/*
+	 * Final checks that we successfully fetched the datum
+	 */
+	if (expectedchunk != (endchunk + 1))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_CORRUPTED),
+				 errmsg_internal("missing chunk number %d for toast value %u in %s",
+								 expectedchunk, valueid,
+								 RelationGetRelationName(toastrel))));
+
+	/* End scan and close indexes. */
+	systable_endscan_ordered(toastscan);
+	toast_close_indexes(toastidxs, num_indexes, AccessShareLock);
+}
diff --git a/src/include/access/heaptoast.h b/src/include/access/heaptoast.h
index 488a2e4a7f..0bf6b23c8f 100644
--- a/src/include/access/heaptoast.h
+++ b/src/include/access/heaptoast.h
@@ -136,4 +136,14 @@ extern HeapTuple toast_build_flattened_tuple(TupleDesc tupleDesc,
 											 Datum *values,
 											 bool *isnull);
 
+/* ----------
+ * heap_fetch_toast_slice
+ *
+ *	Fetch a slice from a toast value stored in a heap table.
+ * ----------
+ */
+extern void heap_fetch_toast_slice(Relation toastrel, Oid valueid,
+								   int32 attrsize, int32 sliceoffset,
+								   int32 slicelength, struct varlena *result);
+
 #endif							/* HEAPTOAST_H */
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 7ac9bb6d65..b784aaf5a9 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -588,6 +588,17 @@ typedef struct TableAmRoutine
 	 */
 	Oid		    (*relation_toast_am) (Relation rel);
 
+	/*
+	 * This callback is invoked when detoasting a value stored in a toast
+	 * table implemented by this AM.  See table_relation_fetch_toast_slice()
+	 * for more details.
+	 */
+	void		(*relation_fetch_toast_slice) (Relation toastrel, Oid valueid,
+											   int32 attrsize,
+											   int32 sliceoffset,
+											   int32 slicelength,
+											   struct varlena *result);
+
 
 	/* ------------------------------------------------------------------------
 	 * Planner related functions.
@@ -1620,6 +1631,41 @@ table_relation_toast_am(Relation rel)
 	return rel->rd_tableam->relation_toast_am(rel);
 }
 
+/*
+ * Fetch all or part of a TOAST value from a TOAST table.
+ *
+ * If this AM is never used to implement a TOAST table, then this callback
+ * is not needed. But, if toasted values are ever stored in a table of this
+ * type, then you will need this callback.
+ *
+ * toastrel is the relation in which the toasted value is stored.
+ *
+ * valueid identifes which toast value is to be fetched. For the heap,
+ * this corresponds to the values stored in the chunk_id column.
+ *
+ * attrsize is the total size of the toast value to be fetched.
+ *
+ * sliceoffset is the offset within the toast value of the first byte that
+ * should be fetched.
+ *
+ * slicelength is the number of bytes from the toast value that should be
+ * fetched.
+ *
+ * result is caller-allocated space into which the fetched bytes should be
+ * stored.
+ */
+static inline void
+table_relation_fetch_toast_slice(Relation toastrel, Oid valueid,
+								 int32 attrsize, int32 sliceoffset,
+								 int32 slicelength, struct varlena *result)
+{
+	return toastrel->rd_tableam->relation_fetch_toast_slice(toastrel, valueid,
+															attrsize,
+															sliceoffset,
+															slicelength,
+															result);
+}
+
 
 /* ----------------------------------------------------------------------------
  * Planner related functionality
-- 
2.17.2 (Apple Git-113)

v10-0001-tableam-Allow-choice-of-toast-AM.patchapplication/octet-stream; name=v10-0001-tableam-Allow-choice-of-toast-AM.patchDownload
From 97618036ff22138aefb3ba4e94c02ef8f1fdcdde Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 22 Nov 2019 09:23:34 -0500
Subject: [PATCH v10 1/3] tableam: Allow choice of toast AM.

Previously, the toast table had to be implemented by the same AM that
was used for the main table, which was bad, because the detoasting
code won't work with anything but heap. This commit doesn't fix the
latter problem, though I plan to do that in a later commit, but it
does let you choose what AM you'd like to use, so that a table AM
can at least choose heap and get something that works.

Patch by me, reviewed by Andres Freund.

Discussion: http://postgr.es/m/CA+TgmoZv-=2iWM4jcw5ZhJeL18HF96+W1yJeYrnGMYdkFFnEpQ@mail.gmail.com
---
 src/backend/access/heap/heapam_handler.c | 10 ++++++++++
 src/backend/catalog/toasting.c           |  2 +-
 src/include/access/tableam.h             | 17 +++++++++++++++++
 3 files changed, 28 insertions(+), 1 deletion(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 92073fec54..1d053e74e6 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -2037,6 +2037,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
@@ -2535,6 +2544,7 @@ static const TableAmRoutine heapam_methods = {
 
 	.relation_size = table_block_relation_size,
 	.relation_needs_toast_table = heapam_relation_needs_toast_table,
+	.relation_toast_am = heapam_relation_toast_am,
 
 	.relation_estimate_size = heapam_estimate_rel_size,
 
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/tableam.h b/src/include/access/tableam.h
index 64022917e2..7ac9bb6d65 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -581,6 +581,13 @@ 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);
+
 
 	/* ------------------------------------------------------------------------
 	 * Planner related functions.
@@ -1603,6 +1610,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
-- 
2.17.2 (Apple Git-113)

v10-0002-Move-heap-specific-detoasting-logic-into-a-separ.patchapplication/octet-stream; name=v10-0002-Move-heap-specific-detoasting-logic-into-a-separ.patchDownload
From 064308827edc8dd98e222226dcad2a0cf15b679c Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 8 Nov 2019 11:50:52 -0500
Subject: [PATCH v10 2/3] Move heap-specific detoasting logic into a separate
 function.

The new function, heap_fetch_toast_slice, is shared between
toast_fetch_datum_slice and toast_fetch_datum, and does all the
work of scanning the TOAST table, fetching chunks, and storing
them into the space allocated for the result varlena.

As an incidental side effect, this allows toast_fetch_datum_slice
to perform the scan with only a single scankey if all chunks are
being fetched, which might have some tiny performance benefit.

Discussion: http://postgr.es/m/CA+TgmobBzxwFojJ0zV0Own3dr09y43hp+OzU2VW+nos4PMXWEg@mail.gmail.com
---
 src/backend/access/common/detoast.c | 244 ++++++++--------------------
 1 file changed, 71 insertions(+), 173 deletions(-)

diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c
index 6341107e88..c1e6eabe89 100644
--- a/src/backend/access/common/detoast.c
+++ b/src/backend/access/common/detoast.c
@@ -27,6 +27,9 @@ static struct varlena *toast_fetch_datum(struct varlena *attr);
 static struct varlena *toast_fetch_datum_slice(struct varlena *attr,
 											   int32 sliceoffset,
 											   int32 slicelength);
+static void heap_fetch_toast_slice(Relation toastrel, Oid valueid,
+								   int32 attrsize, int32 sliceoffset,
+								   int32 slicelength, struct varlena *result);
 static struct varlena *toast_decompress_datum(struct varlena *attr);
 static struct varlena *toast_decompress_datum_slice(struct varlena *attr, int32 slicelength);
 
@@ -325,19 +328,9 @@ 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		attrsize;
-	int32		expectedchunk;
-	int32		totalchunks;
-	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");
@@ -346,7 +339,6 @@ toast_fetch_datum(struct varlena *attr)
 	VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
 
 	attrsize = toast_pointer.va_extsize;
-	totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
 
 	result = (struct varlena *) palloc(attrsize + VARHDRSZ);
 
@@ -355,130 +347,19 @@ toast_fetch_datum(struct varlena *attr)
 	else
 		SET_VARSIZE(result, attrsize + VARHDRSZ);
 
+	if (attrsize == 0)
+		return result;		/* Probably shouldn't happen, but just in case. */
+
 	/*
 	 * 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));
+	/* Fetch all chunks */
+	heap_fetch_toast_slice(toastrel, toast_pointer.va_valueid, attrsize, 0,
+						   attrsize, result);
 
-	/*
-	 * 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.
-	 */
-	expectedchunk = 0;
-
-	init_toast_snapshot(&SnapshotToast);
-	toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
-										   &SnapshotToast, 1, &toastkey);
-	while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
-	{
-		int32		curchunk;
-		Pointer		chunk;
-		bool		isnull;
-		char	   *chunkdata;
-		int32		chunksize;
-		int32		expected_size;
-
-		/*
-		 * Have a chunk, extract the sequence number and the data
-		 */
-		curchunk = 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 (curchunk != expectedchunk)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATA_CORRUPTED),
-					 errmsg_internal("unexpected chunk number %d (expected %d) for toast value %u in %s",
-									 curchunk, expectedchunk,
-									 toast_pointer.va_valueid,
-									 RelationGetRelationName(toastrel))));
-		if (curchunk > totalchunks - 1)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATA_CORRUPTED),
-					 errmsg_internal("unexpected chunk number %d (out of range %d..%d) for toast value %u in %s",
-									 curchunk,
-									 0, totalchunks - 1,
-									 toast_pointer.va_valueid,
-									 RelationGetRelationName(toastrel))));
-		expected_size = curchunk < totalchunks - 1 ? TOAST_MAX_CHUNK_SIZE
-			: attrsize - ((totalchunks - 1) * TOAST_MAX_CHUNK_SIZE);
-		if (chunksize != expected_size)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATA_CORRUPTED),
-					 errmsg_internal("unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s",
-									 chunksize, expected_size,
-									 curchunk, totalchunks,
-									 toast_pointer.va_valueid,
-									 RelationGetRelationName(toastrel))));
-
-		/*
-		 * Copy the data into proper place in our result
-		 */
-		memcpy(VARDATA(result) + curchunk * TOAST_MAX_CHUNK_SIZE,
-			   chunkdata,
-			   chunksize);
-
-		expectedchunk++;
-	}
-
-	/*
-	 * Final checks that we successfully fetched the datum
-	 */
-	if (expectedchunk != totalchunks)
-		ereport(ERROR,
-				(errcode(ERRCODE_DATA_CORRUPTED),
-				 errmsg_internal("missing chunk number %d for toast value %u in %s",
-								 expectedchunk,
-								 toast_pointer.va_valueid,
-								 RelationGetRelationName(toastrel))));
-
-	/*
-	 * End scan and close relations
-	 */
-	systable_endscan_ordered(toastscan);
-	toast_close_indexes(toastidxs, num_indexes, AccessShareLock);
+	/* Close toast table */
 	table_close(toastrel, AccessShareLock);
 
 	return result;
@@ -500,22 +381,9 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 						int32 slicelength)
 {
 	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		expectedchunk;
-	int			startchunk;
-	int			endchunk;
-	int			totalchunks;
-	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");
@@ -531,7 +399,6 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 	Assert(!VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer) || 0 == sliceoffset);
 
 	attrsize = toast_pointer.va_extsize;
-	totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
 
 	if (sliceoffset >= attrsize)
 	{
@@ -560,15 +427,47 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 	if (slicelength == 0)
 		return result;			/* Can save a lot of work at this point! */
 
-	startchunk = sliceoffset / TOAST_MAX_CHUNK_SIZE;
-	endchunk = (sliceoffset + slicelength - 1) / TOAST_MAX_CHUNK_SIZE;
-	Assert(endchunk < totalchunks);
-
-	/*
-	 * Open the toast relation and its indexes
-	 */
+	/* Open the toast relation */
 	toastrel = table_open(toast_pointer.va_toastrelid, AccessShareLock);
-	toasttupDesc = toastrel->rd_att;
+
+	/* Fetch all chunks */
+	heap_fetch_toast_slice(toastrel, toast_pointer.va_valueid, attrsize,
+						   sliceoffset, slicelength, result);
+
+	/* Close toast table */
+	table_close(toastrel, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * Fetch a TOAST slice from a heap table.
+ *
+ * toastrel is the relation from which chunks are to be fetched.
+ * valueid identifies the TOAST value from which chunks are being fetched.
+ * attrsize is the total size of the TOAST value.
+ * sliceoffset is the byte offset within the TOAST value from which to fetch.
+ * slicelength is the number of bytes to be fetched from the TOAST value.
+ * result is the varlena into which the results should be written.
+ */
+static void
+heap_fetch_toast_slice(Relation toastrel, Oid valueid, int32 attrsize,
+					   int32 sliceoffset, int32 slicelength,
+					   struct varlena *result)
+{
+	Relation   *toastidxs;
+	ScanKeyData toastkey[3];
+	TupleDesc	toasttupDesc = toastrel->rd_att;
+	int			nscankeys;
+	SysScanDesc toastscan;
+	HeapTuple	ttup;
+	int32		expectedchunk;
+	int32		totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
+	int			startchunk;
+	int			endchunk;
+	int			num_indexes;
+	int			validIndex;
+	SnapshotData SnapshotToast;
 
 	/* Look for the valid index of toast relation */
 	validIndex = toast_open_indexes(toastrel,
@@ -576,6 +475,10 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 									&toastidxs,
 									&num_indexes);
 
+	startchunk = sliceoffset / TOAST_MAX_CHUNK_SIZE;
+	endchunk = (sliceoffset + slicelength - 1) / TOAST_MAX_CHUNK_SIZE;
+	Assert(endchunk <= totalchunks);
+
 	/*
 	 * Setup a scan key to fetch from the index. This is either two keys or
 	 * three depending on the number of chunks.
@@ -583,12 +486,15 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 	ScanKeyInit(&toastkey[0],
 				(AttrNumber) 1,
 				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(toast_pointer.va_valueid));
+				ObjectIdGetDatum(valueid));
 
 	/*
-	 * Use equality condition for one chunk, a range condition otherwise:
+	 * No addition condition if fetching all chuns. Otherwise, use an
+	 * equality condition for one chunk, and a range condition otherwise.
 	 */
-	if (startchunk == endchunk)
+	if (startchunk == 0 && endchunk == totalchunks - 1)
+		nscankeys = 1;
+	else if (startchunk == endchunk)
 	{
 		ScanKeyInit(&toastkey[1],
 					(AttrNumber) 2,
@@ -609,15 +515,17 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 		nscankeys = 3;
 	}
 
+	/* Prepare for scan */
+	init_toast_snapshot(&SnapshotToast);
+	toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
+										   &SnapshotToast, nscankeys, toastkey);
+
 	/*
 	 * Read the chunks by index
 	 *
 	 * The index is on (valueid, chunkidx) so they will come in order
 	 */
-	init_toast_snapshot(&SnapshotToast);
 	expectedchunk = startchunk;
-	toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
-										   &SnapshotToast, nscankeys, toastkey);
 	while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
 	{
 		int32		curchunk;
@@ -651,8 +559,7 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 		{
 			/* should never happen */
 			elog(ERROR, "found toasted toast chunk for toast value %u in %s",
-				 toast_pointer.va_valueid,
-				 RelationGetRelationName(toastrel));
+				 valueid, RelationGetRelationName(toastrel));
 			chunksize = 0;		/* keep compiler quiet */
 			chunkdata = NULL;
 		}
@@ -664,16 +571,14 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 			ereport(ERROR,
 					(errcode(ERRCODE_DATA_CORRUPTED),
 					 errmsg_internal("unexpected chunk number %d (expected %d) for toast value %u in %s",
-									 curchunk, expectedchunk,
-									 toast_pointer.va_valueid,
+									 curchunk, expectedchunk, valueid,
 									 RelationGetRelationName(toastrel))));
 		if (curchunk > endchunk)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATA_CORRUPTED),
 					 errmsg_internal("unexpected chunk number %d (out of range %d..%d) for toast value %u in %s",
 									 curchunk,
-									 startchunk, endchunk,
-									 toast_pointer.va_valueid,
+									 startchunk, endchunk, valueid,
 									 RelationGetRelationName(toastrel))));
 		expected_size = curchunk < totalchunks - 1 ? TOAST_MAX_CHUNK_SIZE
 			: attrsize - ((totalchunks - 1) * TOAST_MAX_CHUNK_SIZE);
@@ -682,8 +587,7 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 					(errcode(ERRCODE_DATA_CORRUPTED),
 					 errmsg_internal("unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s",
 									 chunksize, expected_size,
-									 curchunk, totalchunks,
-									 toast_pointer.va_valueid,
+									 curchunk, totalchunks, valueid,
 									 RelationGetRelationName(toastrel))));
 
 		/*
@@ -711,18 +615,12 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 		ereport(ERROR,
 				(errcode(ERRCODE_DATA_CORRUPTED),
 				 errmsg_internal("missing chunk number %d for toast value %u in %s",
-								 expectedchunk,
-								 toast_pointer.va_valueid,
+								 expectedchunk, valueid,
 								 RelationGetRelationName(toastrel))));
 
-	/*
-	 * End scan and close relations
-	 */
+	/* End scan and close indexes. */
 	systable_endscan_ordered(toastscan);
 	toast_close_indexes(toastidxs, num_indexes, AccessShareLock);
-	table_close(toastrel, AccessShareLock);
-
-	return result;
 }
 
 /* ----------
-- 
2.17.2 (Apple Git-113)

#49Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#48)
2 attachment(s)
Re: tableam vs. TOAST

On Tue, Dec 17, 2019 at 4:12 PM Robert Haas <robertmhaas@gmail.com> wrote:

Hearing no further comments, I went ahead and pushed 0002 today. That
turned out to have a bug, so I pushed a fix for that. Hopefully the
buildfarm will agree that it's fixed.

Meanwhile, here are the remaining patches again, rebased over the bug fix.

OK, I've now pushed the last of the refactoring patches. Here are the
two main patches back, which are actually quite small, though the
second one looks bigger than it is because it moves a function from
detoast.c into heaptoast.c. This is slightly rebased again because the
other refactoring patch I just pushed had a couple of typos which I
fixed.

If nobody has further comments or objections, I plan to commit these
in early January.

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

Attachments:

v11-0002-tableam-New-callback-relation_fetch_toast_slice.patchapplication/octet-stream; name=v11-0002-tableam-New-callback-relation_fetch_toast_slice.patchDownload
From a594dcc09614c26ffd7c149349aba97afb845cc9 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Wed, 18 Dec 2019 11:22:51 -0500
Subject: [PATCH v11 2/2] tableam: New callback relation_fetch_toast_slice.

Instead of always calling heap_fetch_toast_slice during detoasting,
invoke a table AM callback which, when the toast table is a heap
table, will be heap_fetch_toast_slice.

This makes it possible for a table AM other than heap to be used
as a TOAST table.

Patch by me, reviewed by Andres Freund and Peter Eisentraut.

Discussion: http://postgr.es/m/CA+TgmoZv-=2iWM4jcw5ZhJeL18HF96+W1yJeYrnGMYdkFFnEpQ@mail.gmail.com
---
 src/backend/access/common/detoast.c      | 199 +----------------------
 src/backend/access/heap/heapam_handler.c |   1 +
 src/backend/access/heap/heaptoast.c      | 185 +++++++++++++++++++++
 src/include/access/heaptoast.h           |  10 ++
 src/include/access/tableam.h             |  46 ++++++
 5 files changed, 248 insertions(+), 193 deletions(-)

diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c
index 78fcc876da..3df0cf2512 100644
--- a/src/backend/access/common/detoast.c
+++ b/src/backend/access/common/detoast.c
@@ -14,22 +14,17 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "access/genam.h"
-#include "access/heaptoast.h"
 #include "access/table.h"
+#include "access/tableam.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 slicelength);
-static void heap_fetch_toast_slice(Relation toastrel, Oid valueid,
-								   int32 attrsize, int32 sliceoffset,
-								   int32 slicelength, struct varlena *result);
 static struct varlena *toast_decompress_datum(struct varlena *attr);
 static struct varlena *toast_decompress_datum_slice(struct varlena *attr, int32 slicelength);
 
@@ -356,8 +351,8 @@ toast_fetch_datum(struct varlena *attr)
 	toastrel = table_open(toast_pointer.va_toastrelid, AccessShareLock);
 
 	/* Fetch all chunks */
-	heap_fetch_toast_slice(toastrel, toast_pointer.va_valueid, attrsize, 0,
-						   attrsize, result);
+	table_relation_fetch_toast_slice(toastrel, toast_pointer.va_valueid,
+									 attrsize, 0, attrsize, result);
 
 	/* Close toast table */
 	table_close(toastrel, AccessShareLock);
@@ -431,8 +426,9 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 	toastrel = table_open(toast_pointer.va_toastrelid, AccessShareLock);
 
 	/* Fetch all chunks */
-	heap_fetch_toast_slice(toastrel, toast_pointer.va_valueid, attrsize,
-						   sliceoffset, slicelength, result);
+	table_relation_fetch_toast_slice(toastrel, toast_pointer.va_valueid,
+									 attrsize, sliceoffset, slicelength,
+									 result);
 
 	/* Close toast table */
 	table_close(toastrel, AccessShareLock);
@@ -440,189 +436,6 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 	return result;
 }
 
-/*
- * Fetch a TOAST slice from a heap table.
- *
- * toastrel is the relation from which chunks are to be fetched.
- * valueid identifies the TOAST value from which chunks are being fetched.
- * attrsize is the total size of the TOAST value.
- * sliceoffset is the byte offset within the TOAST value from which to fetch.
- * slicelength is the number of bytes to be fetched from the TOAST value.
- * result is the varlena into which the results should be written.
- */
-static void
-heap_fetch_toast_slice(Relation toastrel, Oid valueid, int32 attrsize,
-					   int32 sliceoffset, int32 slicelength,
-					   struct varlena *result)
-{
-	Relation   *toastidxs;
-	ScanKeyData toastkey[3];
-	TupleDesc	toasttupDesc = toastrel->rd_att;
-	int			nscankeys;
-	SysScanDesc toastscan;
-	HeapTuple	ttup;
-	int32		expectedchunk;
-	int32		totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
-	int			startchunk;
-	int			endchunk;
-	int			num_indexes;
-	int			validIndex;
-	SnapshotData SnapshotToast;
-
-	/* Look for the valid index of toast relation */
-	validIndex = toast_open_indexes(toastrel,
-									AccessShareLock,
-									&toastidxs,
-									&num_indexes);
-
-	startchunk = sliceoffset / TOAST_MAX_CHUNK_SIZE;
-	endchunk = (sliceoffset + slicelength - 1) / TOAST_MAX_CHUNK_SIZE;
-	Assert(endchunk <= totalchunks);
-
-	/*
-	 * 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(valueid));
-
-	/*
-	 * No additional condition if fetching all chunks. Otherwise, use an
-	 * equality condition for one chunk, and a range condition otherwise.
-	 */
-	if (startchunk == 0 && endchunk == totalchunks - 1)
-		nscankeys = 1;
-	else if (startchunk == endchunk)
-	{
-		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;
-	}
-
-	/* Prepare for scan */
-	init_toast_snapshot(&SnapshotToast);
-	toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
-										   &SnapshotToast, nscankeys, toastkey);
-
-	/*
-	 * Read the chunks by index
-	 *
-	 * The index is on (valueid, chunkidx) so they will come in order
-	 */
-	expectedchunk = startchunk;
-	while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
-	{
-		int32		curchunk;
-		Pointer		chunk;
-		bool		isnull;
-		char	   *chunkdata;
-		int32		chunksize;
-		int32		expected_size;
-		int32		chcpystrt;
-		int32		chcpyend;
-
-		/*
-		 * Have a chunk, extract the sequence number and the data
-		 */
-		curchunk = 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",
-				 valueid, RelationGetRelationName(toastrel));
-			chunksize = 0;		/* keep compiler quiet */
-			chunkdata = NULL;
-		}
-
-		/*
-		 * Some checks on the data we've found
-		 */
-		if (curchunk != expectedchunk)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATA_CORRUPTED),
-					 errmsg_internal("unexpected chunk number %d (expected %d) for toast value %u in %s",
-									 curchunk, expectedchunk, valueid,
-									 RelationGetRelationName(toastrel))));
-		if (curchunk > endchunk)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATA_CORRUPTED),
-					 errmsg_internal("unexpected chunk number %d (out of range %d..%d) for toast value %u in %s",
-									 curchunk,
-									 startchunk, endchunk, valueid,
-									 RelationGetRelationName(toastrel))));
-		expected_size = curchunk < totalchunks - 1 ? TOAST_MAX_CHUNK_SIZE
-			: attrsize - ((totalchunks - 1) * TOAST_MAX_CHUNK_SIZE);
-		if (chunksize != expected_size)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATA_CORRUPTED),
-					 errmsg_internal("unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s",
-									 chunksize, expected_size,
-									 curchunk, totalchunks, valueid,
-									 RelationGetRelationName(toastrel))));
-
-		/*
-		 * Copy the data into proper place in our result
-		 */
-		chcpystrt = 0;
-		chcpyend = chunksize - 1;
-		if (curchunk == startchunk)
-			chcpystrt = sliceoffset % TOAST_MAX_CHUNK_SIZE;
-		if (curchunk == endchunk)
-			chcpyend = (sliceoffset + slicelength - 1) % TOAST_MAX_CHUNK_SIZE;
-
-		memcpy(VARDATA(result) +
-			   (curchunk * TOAST_MAX_CHUNK_SIZE - sliceoffset) + chcpystrt,
-			   chunkdata + chcpystrt,
-			   (chcpyend - chcpystrt) + 1);
-
-		expectedchunk++;
-	}
-
-	/*
-	 * Final checks that we successfully fetched the datum
-	 */
-	if (expectedchunk != (endchunk + 1))
-		ereport(ERROR,
-				(errcode(ERRCODE_DATA_CORRUPTED),
-				 errmsg_internal("missing chunk number %d for toast value %u in %s",
-								 expectedchunk, valueid,
-								 RelationGetRelationName(toastrel))));
-
-	/* End scan and close indexes. */
-	systable_endscan_ordered(toastscan);
-	toast_close_indexes(toastidxs, num_indexes, AccessShareLock);
-}
-
 /* ----------
  * toast_decompress_datum -
  *
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 1d053e74e6..b443c6c1d5 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -2545,6 +2545,7 @@ static const TableAmRoutine heapam_methods = {
 	.relation_size = table_block_relation_size,
 	.relation_needs_toast_table = heapam_relation_needs_toast_table,
 	.relation_toast_am = heapam_relation_toast_am,
+	.relation_fetch_toast_slice = heap_fetch_toast_slice,
 
 	.relation_estimate_size = heapam_estimate_rel_size,
 
diff --git a/src/backend/access/heap/heaptoast.c b/src/backend/access/heap/heaptoast.c
index dcfdee4467..3382425281 100644
--- a/src/backend/access/heap/heaptoast.c
+++ b/src/backend/access/heap/heaptoast.c
@@ -25,10 +25,12 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
+#include "access/genam.h"
 #include "access/heapam.h"
 #include "access/heaptoast.h"
 #include "access/toast_helper.h"
 #include "access/toast_internals.h"
+#include "utils/fmgroids.h"
 
 
 /* ----------
@@ -604,3 +606,186 @@ toast_build_flattened_tuple(TupleDesc tupleDesc,
 
 	return new_tuple;
 }
+
+/*
+ * Fetch a TOAST slice from a heap table.
+ *
+ * toastrel is the relation from which chunks are to be fetched.
+ * valueid identifies the TOAST value from which chunks are being fetched.
+ * attrsize is the total size of the TOAST value.
+ * sliceoffset is the byte offset within the TOAST value from which to fetch.
+ * slicelength is the number of bytes to be fetched from the TOAST value.
+ * result is the varlena into which the results should be written.
+ */
+void
+heap_fetch_toast_slice(Relation toastrel, Oid valueid, int32 attrsize,
+					   int32 sliceoffset, int32 slicelength,
+					   struct varlena *result)
+{
+	Relation   *toastidxs;
+	ScanKeyData toastkey[3];
+	TupleDesc	toasttupDesc = toastrel->rd_att;
+	int			nscankeys;
+	SysScanDesc toastscan;
+	HeapTuple	ttup;
+	int32		expectedchunk;
+	int32		totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
+	int			startchunk;
+	int			endchunk;
+	int			num_indexes;
+	int			validIndex;
+	SnapshotData SnapshotToast;
+
+	/* Look for the valid index of toast relation */
+	validIndex = toast_open_indexes(toastrel,
+									AccessShareLock,
+									&toastidxs,
+									&num_indexes);
+
+	startchunk = sliceoffset / TOAST_MAX_CHUNK_SIZE;
+	endchunk = (sliceoffset + slicelength - 1) / TOAST_MAX_CHUNK_SIZE;
+	Assert(endchunk <= totalchunks);
+
+	/*
+	 * 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(valueid));
+
+	/*
+	 * No additional condition if fetching all chunks. Otherwise, use an
+	 * equality condition for one chunk, and a range condition otherwise.
+	 */
+	if (startchunk == 0 && endchunk == totalchunks - 1)
+		nscankeys = 1;
+	else if (startchunk == endchunk)
+	{
+		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;
+	}
+
+	/* Prepare for scan */
+	init_toast_snapshot(&SnapshotToast);
+	toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
+										   &SnapshotToast, nscankeys, toastkey);
+
+	/*
+	 * Read the chunks by index
+	 *
+	 * The index is on (valueid, chunkidx) so they will come in order
+	 */
+	expectedchunk = startchunk;
+	while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
+	{
+		int32		curchunk;
+		Pointer		chunk;
+		bool		isnull;
+		char	   *chunkdata;
+		int32		chunksize;
+		int32		expected_size;
+		int32		chcpystrt;
+		int32		chcpyend;
+
+		/*
+		 * Have a chunk, extract the sequence number and the data
+		 */
+		curchunk = 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",
+				 valueid, RelationGetRelationName(toastrel));
+			chunksize = 0;		/* keep compiler quiet */
+			chunkdata = NULL;
+		}
+
+		/*
+		 * Some checks on the data we've found
+		 */
+		if (curchunk != expectedchunk)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+					 errmsg_internal("unexpected chunk number %d (expected %d) for toast value %u in %s",
+									 curchunk, expectedchunk, valueid,
+									 RelationGetRelationName(toastrel))));
+		if (curchunk > endchunk)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+					 errmsg_internal("unexpected chunk number %d (out of range %d..%d) for toast value %u in %s",
+									 curchunk,
+									 startchunk, endchunk, valueid,
+									 RelationGetRelationName(toastrel))));
+		expected_size = curchunk < totalchunks - 1 ? TOAST_MAX_CHUNK_SIZE
+			: attrsize - ((totalchunks - 1) * TOAST_MAX_CHUNK_SIZE);
+		if (chunksize != expected_size)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+					 errmsg_internal("unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s",
+									 chunksize, expected_size,
+									 curchunk, totalchunks, valueid,
+									 RelationGetRelationName(toastrel))));
+
+		/*
+		 * Copy the data into proper place in our result
+		 */
+		chcpystrt = 0;
+		chcpyend = chunksize - 1;
+		if (curchunk == startchunk)
+			chcpystrt = sliceoffset % TOAST_MAX_CHUNK_SIZE;
+		if (curchunk == endchunk)
+			chcpyend = (sliceoffset + slicelength - 1) % TOAST_MAX_CHUNK_SIZE;
+
+		memcpy(VARDATA(result) +
+			   (curchunk * TOAST_MAX_CHUNK_SIZE - sliceoffset) + chcpystrt,
+			   chunkdata + chcpystrt,
+			   (chcpyend - chcpystrt) + 1);
+
+		expectedchunk++;
+	}
+
+	/*
+	 * Final checks that we successfully fetched the datum
+	 */
+	if (expectedchunk != (endchunk + 1))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_CORRUPTED),
+				 errmsg_internal("missing chunk number %d for toast value %u in %s",
+								 expectedchunk, valueid,
+								 RelationGetRelationName(toastrel))));
+
+	/* End scan and close indexes. */
+	systable_endscan_ordered(toastscan);
+	toast_close_indexes(toastidxs, num_indexes, AccessShareLock);
+}
diff --git a/src/include/access/heaptoast.h b/src/include/access/heaptoast.h
index 488a2e4a7f..0bf6b23c8f 100644
--- a/src/include/access/heaptoast.h
+++ b/src/include/access/heaptoast.h
@@ -136,4 +136,14 @@ extern HeapTuple toast_build_flattened_tuple(TupleDesc tupleDesc,
 											 Datum *values,
 											 bool *isnull);
 
+/* ----------
+ * heap_fetch_toast_slice
+ *
+ *	Fetch a slice from a toast value stored in a heap table.
+ * ----------
+ */
+extern void heap_fetch_toast_slice(Relation toastrel, Oid valueid,
+								   int32 attrsize, int32 sliceoffset,
+								   int32 slicelength, struct varlena *result);
+
 #endif							/* HEAPTOAST_H */
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 7ac9bb6d65..b784aaf5a9 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -588,6 +588,17 @@ typedef struct TableAmRoutine
 	 */
 	Oid		    (*relation_toast_am) (Relation rel);
 
+	/*
+	 * This callback is invoked when detoasting a value stored in a toast
+	 * table implemented by this AM.  See table_relation_fetch_toast_slice()
+	 * for more details.
+	 */
+	void		(*relation_fetch_toast_slice) (Relation toastrel, Oid valueid,
+											   int32 attrsize,
+											   int32 sliceoffset,
+											   int32 slicelength,
+											   struct varlena *result);
+
 
 	/* ------------------------------------------------------------------------
 	 * Planner related functions.
@@ -1620,6 +1631,41 @@ table_relation_toast_am(Relation rel)
 	return rel->rd_tableam->relation_toast_am(rel);
 }
 
+/*
+ * Fetch all or part of a TOAST value from a TOAST table.
+ *
+ * If this AM is never used to implement a TOAST table, then this callback
+ * is not needed. But, if toasted values are ever stored in a table of this
+ * type, then you will need this callback.
+ *
+ * toastrel is the relation in which the toasted value is stored.
+ *
+ * valueid identifes which toast value is to be fetched. For the heap,
+ * this corresponds to the values stored in the chunk_id column.
+ *
+ * attrsize is the total size of the toast value to be fetched.
+ *
+ * sliceoffset is the offset within the toast value of the first byte that
+ * should be fetched.
+ *
+ * slicelength is the number of bytes from the toast value that should be
+ * fetched.
+ *
+ * result is caller-allocated space into which the fetched bytes should be
+ * stored.
+ */
+static inline void
+table_relation_fetch_toast_slice(Relation toastrel, Oid valueid,
+								 int32 attrsize, int32 sliceoffset,
+								 int32 slicelength, struct varlena *result)
+{
+	return toastrel->rd_tableam->relation_fetch_toast_slice(toastrel, valueid,
+															attrsize,
+															sliceoffset,
+															slicelength,
+															result);
+}
+
 
 /* ----------------------------------------------------------------------------
  * Planner related functionality
-- 
2.17.2 (Apple Git-113)

v11-0001-tableam-Allow-choice-of-toast-AM.patchapplication/octet-stream; name=v11-0001-tableam-Allow-choice-of-toast-AM.patchDownload
From 7889fa4ca1713a0b075255f0ea1a9fb17c591cce Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Wed, 18 Dec 2019 11:22:46 -0500
Subject: [PATCH v11 1/2] tableam: Allow choice of toast AM.

Previously, the toast table had to be implemented by the same AM that
was used for the main table, which was bad, because the detoasting
code won't work with anything but heap. This commit doesn't fix the
latter problem, though I plan to do that in a later commit, but it
does let you choose what AM you'd like to use, so that a table AM
can at least choose heap and get something that works.

Patch by me, reviewed by Andres Freund.

Discussion: http://postgr.es/m/CA+TgmoZv-=2iWM4jcw5ZhJeL18HF96+W1yJeYrnGMYdkFFnEpQ@mail.gmail.com
---
 src/backend/access/heap/heapam_handler.c | 10 ++++++++++
 src/backend/catalog/toasting.c           |  2 +-
 src/include/access/tableam.h             | 17 +++++++++++++++++
 3 files changed, 28 insertions(+), 1 deletion(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 92073fec54..1d053e74e6 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -2037,6 +2037,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
@@ -2535,6 +2544,7 @@ static const TableAmRoutine heapam_methods = {
 
 	.relation_size = table_block_relation_size,
 	.relation_needs_toast_table = heapam_relation_needs_toast_table,
+	.relation_toast_am = heapam_relation_toast_am,
 
 	.relation_estimate_size = heapam_estimate_rel_size,
 
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/tableam.h b/src/include/access/tableam.h
index 64022917e2..7ac9bb6d65 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -581,6 +581,13 @@ 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);
+
 
 	/* ------------------------------------------------------------------------
 	 * Planner related functions.
@@ -1603,6 +1610,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
-- 
2.17.2 (Apple Git-113)

#50Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#49)
Re: tableam vs. TOAST

On Wed, Dec 18, 2019 at 11:37 AM Robert Haas <robertmhaas@gmail.com> wrote:

If nobody has further comments or objections, I plan to commit these
in early January.

Done.

Which, I think, wraps up the work I felt needed to be done here.

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