From 8caee4fa759430b4f1ea5e6ca6cff8ea0f67bb80 Mon Sep 17 00:00:00 2001
From: Matthias van de Meent <boekewurm+postgres@gmail.com>
Date: Thu, 21 Apr 2022 16:22:07 +0200
Subject: [PATCH v3 8/9] Add specialization to btree index creation.

This was an oversight that is corrected easily; but an oversight nonetheless.
This increases the (re)build performance of indexes by another few percents.
---
 src/backend/utils/sort/tuplesort.c      | 147 ++---------------------
 src/backend/utils/sort/tuplesort_nbts.h | 148 ++++++++++++++++++++++++
 src/include/access/nbtree.h             |  18 +++
 3 files changed, 175 insertions(+), 138 deletions(-)
 create mode 100644 src/backend/utils/sort/tuplesort_nbts.h

diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 816165217e..8adc734f1c 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -651,8 +651,6 @@ static void writetup_cluster(Tuplesortstate *state, LogicalTape *tape,
 							 SortTuple *stup);
 static void readtup_cluster(Tuplesortstate *state, SortTuple *stup,
 							LogicalTape *tape, unsigned int len);
-static int	comparetup_index_btree(const SortTuple *a, const SortTuple *b,
-								   Tuplesortstate *state);
 static int	comparetup_index_hash(const SortTuple *a, const SortTuple *b,
 								  Tuplesortstate *state);
 static void copytup_index(Tuplesortstate *state, SortTuple *stup, void *tup);
@@ -675,6 +673,10 @@ static void free_sort_tuple(Tuplesortstate *state, SortTuple *stup);
 static void tuplesort_free(Tuplesortstate *state);
 static void tuplesort_updatemax(Tuplesortstate *state);
 
+#define NBT_SPECIALIZE_FILE "../../backend/utils/sort/tuplesort_nbts.h"
+#include "access/nbtree_specialize.h"
+#undef NBT_SPECIALIZE_FILE
+
 /*
  * Specialized comparators that we can inline into specialized sorts.  The goal
  * is to try to sort two tuples without having to follow the pointers to the
@@ -1208,7 +1210,7 @@ tuplesort_begin_index_btree(Relation heapRel,
 								sortopt & TUPLESORT_RANDOMACCESS,
 								PARALLEL_SORT(state));
 
-	state->comparetup = comparetup_index_btree;
+	state->comparetup = NBT_SPECIALIZE_NAME(comparetup_index_btree, indexRel);
 	state->copytup = copytup_index;
 	state->writetup = writetup_index;
 	state->readtup = readtup_index;
@@ -1326,7 +1328,7 @@ tuplesort_begin_index_gist(Relation heapRel,
 
 	state->nKeys = IndexRelationGetNumberOfKeyAttributes(indexRel);
 
-	state->comparetup = comparetup_index_btree;
+	state->comparetup = NBT_SPECIALIZE_NAME(comparetup_index_btree, indexRel);
 	state->copytup = copytup_index;
 	state->writetup = writetup_index;
 	state->readtup = readtup_index;
@@ -4292,142 +4294,11 @@ readtup_cluster(Tuplesortstate *state, SortTuple *stup,
  * The btree and hash cases require separate comparison functions, but the
  * IndexTuple representation is the same so the copy/write/read support
  * functions can be shared.
+ *
+ * nbtree function can be found in tuplesort_nbts.h, and is included
+ * through the nbtree specialization functions.
  */
 
-static int
-comparetup_index_btree(const SortTuple *a, const SortTuple *b,
-					   Tuplesortstate *state)
-{
-	/*
-	 * This is similar to comparetup_heap(), but expects index tuples.  There
-	 * is also special handling for enforcing uniqueness, and special
-	 * treatment for equal keys at the end.
-	 */
-	SortSupport sortKey = state->sortKeys;
-	IndexTuple	tuple1;
-	IndexTuple	tuple2;
-	int			keysz;
-	TupleDesc	tupDes;
-	bool		equal_hasnull = false;
-	int			nkey;
-	int32		compare;
-	Datum		datum1,
-				datum2;
-	bool		isnull1,
-				isnull2;
-
-
-	/* Compare the leading sort key */
-	compare = ApplySortComparator(a->datum1, a->isnull1,
-								  b->datum1, b->isnull1,
-								  sortKey);
-	if (compare != 0)
-		return compare;
-
-	/* Compare additional sort keys */
-	tuple1 = (IndexTuple) a->tuple;
-	tuple2 = (IndexTuple) b->tuple;
-	keysz = state->nKeys;
-	tupDes = RelationGetDescr(state->indexRel);
-
-	if (sortKey->abbrev_converter)
-	{
-		datum1 = index_getattr(tuple1, 1, tupDes, &isnull1);
-		datum2 = index_getattr(tuple2, 1, tupDes, &isnull2);
-
-		compare = ApplySortAbbrevFullComparator(datum1, isnull1,
-												datum2, isnull2,
-												sortKey);
-		if (compare != 0)
-			return compare;
-	}
-
-	/* they are equal, so we only need to examine one null flag */
-	if (a->isnull1)
-		equal_hasnull = true;
-
-	sortKey++;
-	for (nkey = 2; nkey <= keysz; nkey++, sortKey++)
-	{
-		datum1 = index_getattr(tuple1, nkey, tupDes, &isnull1);
-		datum2 = index_getattr(tuple2, nkey, tupDes, &isnull2);
-
-		compare = ApplySortComparator(datum1, isnull1,
-									  datum2, isnull2,
-									  sortKey);
-		if (compare != 0)
-			return compare;		/* done when we find unequal attributes */
-
-		/* they are equal, so we only need to examine one null flag */
-		if (isnull1)
-			equal_hasnull = true;
-	}
-
-	/*
-	 * If btree has asked us to enforce uniqueness, complain if two equal
-	 * tuples are detected (unless there was at least one NULL field and NULLS
-	 * NOT DISTINCT was not set).
-	 *
-	 * It is sufficient to make the test here, because if two tuples are equal
-	 * they *must* get compared at some stage of the sort --- otherwise the
-	 * sort algorithm wouldn't have checked whether one must appear before the
-	 * other.
-	 */
-	if (state->enforceUnique && !(!state->uniqueNullsNotDistinct && equal_hasnull))
-	{
-		Datum		values[INDEX_MAX_KEYS];
-		bool		isnull[INDEX_MAX_KEYS];
-		char	   *key_desc;
-
-		/*
-		 * Some rather brain-dead implementations of qsort (such as the one in
-		 * QNX 4) will sometimes call the comparison routine to compare a
-		 * value to itself, but we always use our own implementation, which
-		 * does not.
-		 */
-		Assert(tuple1 != tuple2);
-
-		index_deform_tuple(tuple1, tupDes, values, isnull);
-
-		key_desc = BuildIndexValueDescription(state->indexRel, values, isnull);
-
-		ereport(ERROR,
-				(errcode(ERRCODE_UNIQUE_VIOLATION),
-				 errmsg("could not create unique index \"%s\"",
-						RelationGetRelationName(state->indexRel)),
-				 key_desc ? errdetail("Key %s is duplicated.", key_desc) :
-				 errdetail("Duplicate keys exist."),
-				 errtableconstraint(state->heapRel,
-									RelationGetRelationName(state->indexRel))));
-	}
-
-	/*
-	 * If key values are equal, we sort on ItemPointer.  This is required for
-	 * btree indexes, since heap TID is treated as an implicit last key
-	 * attribute in order to ensure that all keys in the index are physically
-	 * unique.
-	 */
-	{
-		BlockNumber blk1 = ItemPointerGetBlockNumber(&tuple1->t_tid);
-		BlockNumber blk2 = ItemPointerGetBlockNumber(&tuple2->t_tid);
-
-		if (blk1 != blk2)
-			return (blk1 < blk2) ? -1 : 1;
-	}
-	{
-		OffsetNumber pos1 = ItemPointerGetOffsetNumber(&tuple1->t_tid);
-		OffsetNumber pos2 = ItemPointerGetOffsetNumber(&tuple2->t_tid);
-
-		if (pos1 != pos2)
-			return (pos1 < pos2) ? -1 : 1;
-	}
-
-	/* ItemPointer values should never be equal */
-	Assert(false);
-
-	return 0;
-}
-
 static int
 comparetup_index_hash(const SortTuple *a, const SortTuple *b,
 					  Tuplesortstate *state)
diff --git a/src/backend/utils/sort/tuplesort_nbts.h b/src/backend/utils/sort/tuplesort_nbts.h
new file mode 100644
index 0000000000..d1b2670747
--- /dev/null
+++ b/src/backend/utils/sort/tuplesort_nbts.h
@@ -0,0 +1,148 @@
+#ifndef NBTS_SPECIALIZING_DEFAULT
+
+static int NBTS_FUNCTION(comparetup_index_btree)(const SortTuple *a,
+												 const SortTuple *b,
+												 Tuplesortstate *state);
+
+static int
+NBTS_FUNCTION(comparetup_index_btree)(const SortTuple *a, const SortTuple *b,
+									  Tuplesortstate *state)
+{
+	/*
+	 * This is similar to comparetup_heap(), but expects index tuples.  There
+	 * is also special handling for enforcing uniqueness, and special
+	 * treatment for equal keys at the end.
+	 */
+	SortSupport sortKey = state->sortKeys;
+	IndexTuple	tuple1;
+	IndexTuple	tuple2;
+	int			keysz;
+	TupleDesc	tupDes;
+	bool		equal_hasnull = false;
+	int			nkey;
+	int32		compare;
+	nbts_attiterdeclare(tuple1);
+	nbts_attiterdeclare(tuple2);
+
+	/* Compare the leading sort key */
+	compare = ApplySortComparator(a->datum1, a->isnull1,
+								  b->datum1, b->isnull1,
+								  sortKey);
+	if (compare != 0)
+		return compare;
+
+	/* Compare additional sort keys */
+	tuple1 = (IndexTuple) a->tuple;
+	tuple2 = (IndexTuple) b->tuple;
+	keysz = state->nKeys;
+	tupDes = RelationGetDescr(state->indexRel);
+
+	if (!sortKey->abbrev_converter)
+	{
+		nkey = 2;
+		sortKey++;
+	}
+	else
+		nkey = 1;
+
+	if (a->isnull1)
+		equal_hasnull = true;
+
+	nbts_attiterinit(tuple1, nkey, tupDes);
+	nbts_attiterinit(tuple2, nkey, tupDes);
+
+	nbts_foreachattr(nkey, keysz)
+	{
+		Datum		datum1,
+					datum2;
+		datum1 = nbts_attiter_nextattdatum(tuple1, tupDes);
+		datum2 = nbts_attiter_nextattdatum(tuple2, tupDes);
+
+		if (nbts_attiter_attnum == 1)
+		{
+			compare = ApplySortAbbrevFullComparator(datum1, nbts_attiter_curattisnull(tuple1),
+													datum2, nbts_attiter_curattisnull(tuple2),
+													sortKey);
+		}
+		else
+		{
+			compare = ApplySortComparator(datum1, nbts_attiter_curattisnull(tuple1),
+										  datum2, nbts_attiter_curattisnull(tuple2),
+										  sortKey);
+		}
+
+		if (compare != 0)
+			return compare;
+
+		if (nbts_attiter_curattisnull(tuple1))
+			equal_hasnull = true;
+
+		sortKey++;
+	}
+
+	/*
+	 * If btree has asked us to enforce uniqueness, complain if two equal
+	 * tuples are detected (unless there was at least one NULL field and NULLS
+	 * NOT DISTINCT was not set).
+	 *
+	 * It is sufficient to make the test here, because if two tuples are equal
+	 * they *must* get compared at some stage of the sort --- otherwise the
+	 * sort algorithm wouldn't have checked whether one must appear before the
+	 * other.
+	 */
+	if (state->enforceUnique && !(!state->uniqueNullsNotDistinct && equal_hasnull))
+	{
+		Datum		values[INDEX_MAX_KEYS];
+		bool		isnull[INDEX_MAX_KEYS];
+		char	   *key_desc;
+
+		/*
+		 * Some rather brain-dead implementations of qsort (such as the one in
+		 * QNX 4) will sometimes call the comparison routine to compare a
+		 * value to itself, but we always use our own implementation, which
+		 * does not.
+		 */
+		Assert(tuple1 != tuple2);
+
+		index_deform_tuple(tuple1, tupDes, values, isnull);
+
+		key_desc = BuildIndexValueDescription(state->indexRel, values, isnull);
+
+		ereport(ERROR,
+				(errcode(ERRCODE_UNIQUE_VIOLATION),
+					errmsg("could not create unique index \"%s\"",
+						   RelationGetRelationName(state->indexRel)),
+					key_desc ? errdetail("Key %s is duplicated.", key_desc) :
+					errdetail("Duplicate keys exist."),
+					errtableconstraint(state->heapRel,
+									   RelationGetRelationName(state->indexRel))));
+	}
+
+	/*
+	 * If key values are equal, we sort on ItemPointer.  This is required for
+	 * btree indexes, since heap TID is treated as an implicit last key
+	 * attribute in order to ensure that all keys in the index are physically
+	 * unique.
+	 */
+	{
+		BlockNumber blk1 = ItemPointerGetBlockNumber(&tuple1->t_tid);
+		BlockNumber blk2 = ItemPointerGetBlockNumber(&tuple2->t_tid);
+
+		if (blk1 != blk2)
+			return (blk1 < blk2) ? -1 : 1;
+	}
+	{
+		OffsetNumber pos1 = ItemPointerGetOffsetNumber(&tuple1->t_tid);
+		OffsetNumber pos2 = ItemPointerGetOffsetNumber(&tuple2->t_tid);
+
+		if (pos1 != pos2)
+			return (pos1 < pos2) ? -1 : 1;
+	}
+
+	/* ItemPointer values should never be equal */
+	Assert(false);
+
+	return 0;
+}
+
+#endif
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 80f2575884..3e291c84fd 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -1169,6 +1169,24 @@ do { \
 	) \
 )
 
+#define NBT_SPECIALIZE_NAME(name, rel) \
+( \
+	IndexRelationGetNumberOfKeyAttributes(rel) == 1 ? ( \
+		NBTS_MAKE_NAME(name, NBTS_TYPE_SINGLE_COLUMN) \
+	) \
+	: \
+	( \
+		TupleDescAttr(RelationGetDescr(rel), \
+					  IndexRelationGetNumberOfKeyAttributes(rel) - 1)->attcacheoff > 0 ? ( \
+			NBTS_MAKE_NAME(name, NBTS_TYPE_CACHED) \
+		) \
+		: \
+		( \
+			NBTS_MAKE_NAME(name, NBTS_TYPE_UNCACHED) \
+		) \
+	) \
+)
+
 #else /* not defined NBTS_ENABLED */
 
 #define nbt_opt_specialize(rel)
-- 
2.30.2

