From 8d587233c593d510edc50783af303831837f6b32 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 v6 7/8] 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/multisort.c            |  22 +++
 src/backend/utils/sort/tuplesortvariants.c    | 146 +----------------
 .../utils/sort/tuplesortvariants_nbts.h       | 150 ++++++++++++++++++
 src/include/access/nbtree.h                   |  18 +++
 4 files changed, 196 insertions(+), 140 deletions(-)
 create mode 100644 src/backend/utils/sort/multisort.c
 create mode 100644 src/backend/utils/sort/tuplesortvariants_nbts.h

diff --git a/src/backend/utils/sort/multisort.c b/src/backend/utils/sort/multisort.c
new file mode 100644
index 0000000000..3c0871faa6
--- /dev/null
+++ b/src/backend/utils/sort/multisort.c
@@ -0,0 +1,22 @@
+#include "postgres.h"
+
+#include "utils/sortsupport.h"
+#include "utils/tuplesort.h"
+
+struct MultiSortData {
+	Tuplesortstate *buffer;
+	void *inner;
+	
+};
+
+
+/*
+ *	MultiSort {
+ *		TupleSort buffer		Contains sorted run of tuples received from
+ *								innerstate. May have dropped tuples since.
+ *		void* innerstate		Inner state of multisort scheme.
+ *		SortSlot low_watermark	all tuples up to X have been received)
+ *		SortSlot high_watermark (no tuples after X have been received)
+ *	}
+ * sort_begin(MultiSort state, 
+ */
diff --git a/src/backend/utils/sort/tuplesortvariants.c b/src/backend/utils/sort/tuplesortvariants.c
index 2933020dcc..06fdeb64c6 100644
--- a/src/backend/utils/sort/tuplesortvariants.c
+++ b/src/backend/utils/sort/tuplesortvariants.c
@@ -57,8 +57,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 writetup_index(Tuplesortstate *state, LogicalTape *tape,
@@ -130,6 +128,10 @@ typedef struct
 	int			datumTypeLen;
 } TuplesortDatumArg;
 
+#define NBT_SPECIALIZE_FILE "../../backend/utils/sort/tuplesortvariants_nbts.h"
+#include "access/nbtree_specialize.h"
+#undef NBT_SPECIALIZE_FILE
+
 Tuplesortstate *
 tuplesort_begin_heap(TupleDesc tupDesc,
 					 int nkeys, AttrNumber *attNums,
@@ -350,7 +352,7 @@ tuplesort_begin_index_btree(Relation heapRel,
 								PARALLEL_SORT(coordinate));
 
 	base->removeabbrev = removeabbrev_index;
-	base->comparetup = comparetup_index_btree;
+	base->comparetup = NBT_SPECIALIZE_NAME(comparetup_index_btree, indexRel);
 	base->writetup = writetup_index;
 	base->readtup = readtup_index;
 	base->haveDatum1 = true;
@@ -475,7 +477,7 @@ tuplesort_begin_index_gist(Relation heapRel,
 	base->nKeys = IndexRelationGetNumberOfKeyAttributes(indexRel);
 
 	base->removeabbrev = removeabbrev_index;
-	base->comparetup = comparetup_index_btree;
+	base->comparetup = NBT_SPECIALIZE_NAME(comparetup_index_btree, indexRel);
 	base->writetup = writetup_index;
 	base->readtup = readtup_index;
 	base->haveDatum1 = true;
@@ -1245,142 +1247,6 @@ removeabbrev_index(Tuplesortstate *state, SortTuple *stups, int count)
 	}
 }
 
-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.
-	 */
-	TuplesortPublic *base = TuplesortstateGetPublic(state);
-	TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
-	SortSupport sortKey = base->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 = base->nKeys;
-	tupDes = RelationGetDescr(arg->index.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 (arg->enforceUnique && !(!arg->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(arg->index.indexRel, values, isnull);
-
-		ereport(ERROR,
-				(errcode(ERRCODE_UNIQUE_VIOLATION),
-				 errmsg("could not create unique index \"%s\"",
-						RelationGetRelationName(arg->index.indexRel)),
-				 key_desc ? errdetail("Key %s is duplicated.", key_desc) :
-				 errdetail("Duplicate keys exist."),
-				 errtableconstraint(arg->index.heapRel,
-									RelationGetRelationName(arg->index.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/tuplesortvariants_nbts.h b/src/backend/utils/sort/tuplesortvariants_nbts.h
new file mode 100644
index 0000000000..d52d34c749
--- /dev/null
+++ b/src/backend/utils/sort/tuplesortvariants_nbts.h
@@ -0,0 +1,150 @@
+#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.
+	 */
+	TuplesortPublic *base = TuplesortstateGetPublic(state);
+	TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
+	SortSupport sortKey = base->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 = base->nKeys;
+	tupDes = RelationGetDescr(arg->index.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 (arg->enforceUnique && !(!arg->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(arg->index.indexRel, values, isnull);
+
+		ereport(ERROR,
+				(errcode(ERRCODE_UNIQUE_VIOLATION),
+					errmsg("could not create unique index \"%s\"",
+						   RelationGetRelationName(arg->index.indexRel)),
+					key_desc ? errdetail("Key %s is duplicated.", key_desc) :
+					errdetail("Duplicate keys exist."),
+					errtableconstraint(arg->index.heapRel,
+									   RelationGetRelationName(arg->index.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 92894e4ea7..11116b47ca 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -1170,6 +1170,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

