>From 63352f18ad2c66b64202bf3ba3971ec2f6d634de Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Tue, 2 Jul 2013 16:51:24 +0200
Subject: [PATCH 2/2] Work harder in avoiding retoasting unchanged columns

If the old and new column have the same content but not the same representation
(e.g. the new value is uncompressed, while the old value is an ondisk value),
compare their contents to check whether we need to retoast.
---
 src/backend/access/heap/tuptoaster.c | 91 +++++++++++++++++++++++++++++++-----
 1 file changed, 80 insertions(+), 11 deletions(-)

diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index e759502..12d0f61 100644
--- a/src/backend/access/heap/tuptoaster.c
+++ b/src/backend/access/heap/tuptoaster.c
@@ -458,6 +458,60 @@ toast_delete(Relation rel, HeapTuple oldtup)
 	}
 }
 
+/* ----------
+ * toast_datum_differs -
+ *
+ *  Determine whether two toasted Datums are the same by comparing their
+ *  content and thus don't have to be stored again.
+ * ----------
+ */
+static bool
+toast_datum_differs(struct varlena *old_value, struct varlena *new_value)
+{
+	bool changed;
+	struct varlena *old_data;
+	struct varlena *new_data;
+
+	Assert(VARATT_IS_EXTERNAL_ONDISK(old_value));
+
+	/*
+	 * look at the referenced tuple, so we can know whether untoast_attr had to
+	 * copy data.
+	 */
+	if (VARATT_IS_EXTERNAL_INDIRECT(new_value))
+	{
+		struct varatt_indirect redirect;
+		VARATT_EXTERNAL_GET_POINTER(redirect, new_value);
+		new_value = (struct varlena *)redirect.pointer;
+	}
+
+	/*
+	 * compare size of tuples, so we don't uselessly detoast/decompress tuples
+	 * if they can't be the same anyway.
+	 */
+	if (toast_raw_datum_size(PointerGetDatum(old_value)) !=
+		toast_raw_datum_size(PointerGetDatum(new_value)))
+		return true;
+
+	old_data = heap_tuple_untoast_attr(old_value);
+	new_data = heap_tuple_untoast_attr(new_value);
+
+	Assert(!VARATT_IS_EXTERNAL(old_data));
+	Assert(!VARATT_IS_EXTERNAL(new_data));
+	Assert(!VARATT_IS_COMPRESSED(old_data));
+	Assert(!VARATT_IS_COMPRESSED(new_data));
+	Assert(VARSIZE_ANY_EXHDR(old_data) == VARSIZE_ANY_EXHDR(new_data));
+
+	/* compare payload, we're fine with unaligned data */
+	changed = memcmp(VARDATA_ANY(old_data), VARDATA_ANY(new_data),
+				  VARSIZE_ANY_EXHDR(old_data)) != 0;
+
+	/* heap_tuple_untoast_attr copied data */
+	pfree(old_data);
+	if (new_value != new_data)
+		pfree(new_data);
+	return changed;
+}
 
 /* ----------
  * toast_insert_or_update -
@@ -561,26 +615,41 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 			if (att[i]->attlen == -1 && !toast_oldisnull[i] &&
 				VARATT_IS_EXTERNAL_ONDISK(old_value))
 			{
-				if (toast_isnull[i] || !VARATT_IS_EXTERNAL_ONDISK(new_value) ||
+				/*
+				 * both values are ondisk values, if the new value points to
+				 * the same toast value we don't need to do anything.
+				 */
+				if (!toast_isnull[i] &&
+					VARATT_IS_EXTERNAL_ONDISK(new_value) &&
 					memcmp((char *) old_value, (char *) new_value,
-						   VARSIZE_EXTERNAL(old_value)) != 0)
+						   VARSIZE_EXTERNAL(old_value)) == 0)
+				{
+					toast_action[i] = 'p';
+					continue;
+				}
+				else if (!toast_isnull[i] &&
+						 !toast_datum_differs(old_value, new_value))
 				{
 					/*
-					 * The old external stored value isn't needed any more
-					 * after the update
+					 * The old toast datum has the right data, but isn't
+					 * currently referenced in the new tuple. Change that. We
+					 * might have been passed an untoasted version of the old
+					 * datum or similar.
 					 */
-					toast_delold[i] = true;
-					need_delold = true;
+					toast_values[i] = toast_oldvalues[i];
+					toast_free[i] = false;
+					toast_action[i] = 'p';
+					need_change = true;
+					continue;
 				}
 				else
 				{
 					/*
-					 * This attribute isn't changed by this update so we reuse
-					 * the original reference to the old value in the new
-					 * tuple.
+					 * The old external stored value isn't needed any more
+					 * after the update.
 					 */
-					toast_action[i] = 'p';
-					continue;
+					toast_delold[i] = true;
+					need_delold = true;
 				}
 			}
 		}
-- 
1.8.3.1

