From 09a86076e09beaab5aa338b178e005adb0b885b1 Mon Sep 17 00:00:00 2001
From: B Sadhu Prasad Patro <b.sadhuprasadp@enterprisedb.com>
Date: Fri, 27 Aug 2021 03:29:27 -0700
Subject: [PATCH v1] Support Multi column for Hash index

Create separate hash values for all the key columns.
---
 src/backend/access/hash/hash.c       | 16 ++++---
 src/backend/access/hash/hashsearch.c | 93 +++++++++++++++++++++++-------------
 src/backend/access/hash/hashutil.c   | 17 ++++---
 src/bin/pgbench/pgbench.c            |  3 +-
 src/include/access/hash.h            |  6 +--
 5 files changed, 84 insertions(+), 51 deletions(-)

diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 0752fb3..c1d6dc8 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -38,6 +38,7 @@ typedef struct
 	HSpool	   *spool;			/* NULL if not using spooling */
 	double		indtuples;		/* # tuples accepted into index */
 	Relation	heapRel;		/* heap relation descriptor */
+	int		NumIdxKeyAttrs;		/* number of key in index */
 } HashBuildState;
 
 static void hashbuildCallback(Relation index,
@@ -64,7 +65,7 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = true;
 	amroutine->amcanunique = false;
-	amroutine->amcanmulticol = false;
+	amroutine->amcanmulticol = true;
 	amroutine->amoptionalkey = false;
 	amroutine->amsearcharray = false;
 	amroutine->amsearchnulls = false;
@@ -164,6 +165,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	/* prepare to build the index */
 	buildstate.indtuples = 0;
 	buildstate.heapRel = heap;
+	buildstate.NumIdxKeyAttrs = indexInfo->ii_NumIndexKeyAttrs;
 
 	/* do the heap scan */
 	reltuples = table_index_build_scan(heap, index, indexInfo, true, true,
@@ -211,12 +213,12 @@ hashbuildCallback(Relation index,
 				  void *state)
 {
 	HashBuildState *buildstate = (HashBuildState *) state;
-	Datum		index_values[1];
-	bool		index_isnull[1];
+	Datum		index_values[INDEX_MAX_KEYS];
+	bool		index_isnull[INDEX_MAX_KEYS];
 	IndexTuple	itup;
 
 	/* convert data to a hash key; on failure, do not insert anything */
-	if (!_hash_convert_tuple(index,
+	if (!_hash_convert_tuple(index, buildstate->NumIdxKeyAttrs,
 							 values, isnull,
 							 index_values, index_isnull))
 		return;
@@ -250,12 +252,12 @@ hashinsert(Relation rel, Datum *values, bool *isnull,
 		   bool indexUnchanged,
 		   IndexInfo *indexInfo)
 {
-	Datum		index_values[1];
-	bool		index_isnull[1];
+	Datum		index_values[INDEX_MAX_KEYS];
+	bool		index_isnull[INDEX_MAX_KEYS];
 	IndexTuple	itup;
 
 	/* convert data to a hash key; on failure, do not insert anything */
-	if (!_hash_convert_tuple(rel,
+	if (!_hash_convert_tuple(rel, indexInfo->ii_NumIndexKeyAttrs,
 							 values, isnull,
 							 index_values, index_isnull))
 		return false;
diff --git a/src/backend/access/hash/hashsearch.c b/src/backend/access/hash/hashsearch.c
index 2ffa28e..724d5b1 100644
--- a/src/backend/access/hash/hashsearch.c
+++ b/src/backend/access/hash/hashsearch.c
@@ -314,39 +314,43 @@ _hash_first(IndexScanDesc scan, ScanDirection dir)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("hash indexes do not support whole-index scans")));
 
-	/* There may be more than one index qual, but we hash only the first */
-	cur = &scan->keyData[0];
+	/* SADHU There may be more than one index qual, but we hash only the first */
+	for (int i = 0; i < scan->numberOfKeys; i++)
+	{
+		cur = &scan->keyData[i];
 
-	/* We support only single-column hash indexes */
-	Assert(cur->sk_attno == 1);
-	/* And there's only one operator strategy, too */
-	Assert(cur->sk_strategy == HTEqualStrategyNumber);
+		/* And there's only one operator strategy, too */
+		Assert(cur->sk_strategy == HTEqualStrategyNumber);
 
-	/*
-	 * If the constant in the index qual is NULL, assume it cannot match any
-	 * items in the index.
-	 */
-	if (cur->sk_flags & SK_ISNULL)
-		return false;
+		/*
+		 * If the constant in the index qual is NULL, assume it cannot match any
+		 * items in the index.
+		 */
+		if (cur->sk_flags & SK_ISNULL)
+			return false;
 
-	/*
-	 * Okay to compute the hash key.  We want to do this before acquiring any
-	 * locks, in case a user-defined hash function happens to be slow.
-	 *
-	 * If scankey operator is not a cross-type comparison, we can use the
-	 * cached hash function; otherwise gotta look it up in the catalogs.
-	 *
-	 * We support the convention that sk_subtype == InvalidOid means the
-	 * opclass input type; this is a hack to simplify life for ScanKeyInit().
-	 */
-	if (cur->sk_subtype == rel->rd_opcintype[0] ||
-		cur->sk_subtype == InvalidOid)
-		hashkey = _hash_datum2hashkey(rel, cur->sk_argument);
-	else
-		hashkey = _hash_datum2hashkey_type(rel, cur->sk_argument,
+		/*
+		 * Okay to compute the hash key.  We want to do this before acquiring any
+		 * locks, in case a user-defined hash function happens to be slow.
+		 *
+		 * If scankey operator is not a cross-type comparison, we can use the
+		 * cached hash function; otherwise gotta look it up in the catalogs.
+		 *
+		 * We support the convention that sk_subtype == InvalidOid means the
+		 * opclass input type; this is a hack to simplify life for ScanKeyInit().
+		 */
+		if (cur->sk_subtype == rel->rd_opcintype[0] ||
+			cur->sk_subtype == InvalidOid)
+			hashkey = _hash_datum2hashkey(rel, cur->sk_argument);
+		else
+			hashkey = _hash_datum2hashkey_type(rel, cur->sk_argument,
 										   cur->sk_subtype);
 
-	so->hashso_sk_hash = hashkey;
+		so->hashso_sk_hash[i] = hashkey;
+	}
+
+	/* consider the first key column hash to seach a qualify bucket */
+	hashkey = so->hashso_sk_hash[0];
 
 	buf = _hash_getbucketbuf_from_hashkey(rel, hashkey, HASH_READ, NULL);
 	PredicateLockPage(rel, BufferGetBlockNumber(buf), scan->xs_snapshot);
@@ -475,7 +479,7 @@ _hash_readpage(IndexScanDesc scan, Buffer *bufP, ScanDirection dir)
 		for (;;)
 		{
 			/* new page, locate starting position by binary search */
-			offnum = _hash_binsearch(page, so->hashso_sk_hash);
+			offnum = _hash_binsearch(page, so->hashso_sk_hash[0]);
 
 			itemIndex = _hash_load_qualified_items(scan, page, offnum, dir);
 
@@ -534,7 +538,7 @@ _hash_readpage(IndexScanDesc scan, Buffer *bufP, ScanDirection dir)
 		for (;;)
 		{
 			/* new page, locate starting position by binary search */
-			offnum = _hash_binsearch_last(page, so->hashso_sk_hash);
+			offnum = _hash_binsearch_last(page, so->hashso_sk_hash[0]);
 
 			itemIndex = _hash_load_qualified_items(scan, page, offnum, dir);
 
@@ -612,6 +616,8 @@ _hash_load_qualified_items(IndexScanDesc scan, Page page,
 	IndexTuple	itup;
 	int			itemIndex;
 	OffsetNumber maxoff;
+	bool		isMatch = false;
+	char		*attp = NULL;
 
 	maxoff = PageGetMaxOffsetNumber(page);
 
@@ -639,8 +645,17 @@ _hash_load_qualified_items(IndexScanDesc scan, Page page,
 				continue;
 			}
 
-			if (so->hashso_sk_hash == _hash_get_indextuple_hashkey(itup) &&
-				_hash_checkqual(scan, itup))
+			attp = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
+			for (int i = 0; i < scan->numberOfKeys; i++)
+			{
+				if (so->hashso_sk_hash[i] == *((uint32 *) (attp + (i * sizeof(uint32)))) &&
+					_hash_checkqual(scan, itup))
+				{
+					isMatch = true;
+				}
+			}
+
+			if (isMatch)
 			{
 				/* tuple is qualified, so remember it */
 				_hash_saveitem(so, itemIndex, offnum, itup);
@@ -685,8 +700,18 @@ _hash_load_qualified_items(IndexScanDesc scan, Page page,
 				continue;
 			}
 
-			if (so->hashso_sk_hash == _hash_get_indextuple_hashkey(itup) &&
-				_hash_checkqual(scan, itup))
+			attp = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
+			for (int i = 0; i < scan->numberOfKeys; i++)
+			{
+				if (so->hashso_sk_hash[i]
+					== *((uint32 *) (attp + (i * sizeof(uint32))))
+								 && _hash_checkqual(scan, itup))
+				{
+					isMatch = true;
+				}
+			}
+
+			if (isMatch)
 			{
 				itemIndex--;
 				/* tuple is qualified, so remember it */
diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c
index 5198728..535ea06 100644
--- a/src/backend/access/hash/hashutil.c
+++ b/src/backend/access/hash/hashutil.c
@@ -316,22 +316,27 @@ _hash_get_indextuple_hashkey(IndexTuple itup)
  * currently support that.
  */
 bool
-_hash_convert_tuple(Relation index,
+_hash_convert_tuple(Relation index, int nkeys,
 					Datum *user_values, bool *user_isnull,
 					Datum *index_values, bool *index_isnull)
 {
 	uint32		hashkey;
+	int 		i;
 
 	/*
 	 * We do not insert null values into hash indexes.  This is okay because
 	 * the only supported search operator is '=', and we assume it is strict.
 	 */
-	if (user_isnull[0])
-		return false;
+	for (i = 0; i < nkeys; i++)
+	{
+		if (user_isnull[i])
+			return false;
+
+		hashkey = _hash_datum2hashkey(index, user_values[i]);
+		index_values[i] = UInt32GetDatum(hashkey);
+		index_isnull[i] = false;
+	}
 
-	hashkey = _hash_datum2hashkey(index, user_values[0]);
-	index_values[0] = UInt32GetDatum(hashkey);
-	index_isnull[0] = false;
 	return true;
 }
 
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index b0e20c4..09bd98f 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -643,7 +643,8 @@ static const BuiltinScript builtin_script[] =
 		"select-only",
 		"<builtin: select only>",
 		"\\set aid random(1, " CppAsString2(naccounts) " * :scale)\n"
-		"SELECT abalance FROM pgbench_accounts WHERE aid = :aid;\n"
+		"\\set bid random(1, " CppAsString2(nbranches) " * :scale)\n"
+		"SELECT abalance FROM pgbench_accounts WHERE aid = :aid and bid = :bid;\n"
 	}
 };
 
diff --git a/src/include/access/hash.h b/src/include/access/hash.h
index 1cce865..42ba1c3 100644
--- a/src/include/access/hash.h
+++ b/src/include/access/hash.h
@@ -153,10 +153,10 @@ typedef struct HashScanPosData
 /*
  *	HashScanOpaqueData is private state for a hash index scan.
  */
-typedef struct HashScanOpaqueData
+typedef struct
 {
 	/* Hash value of the scan key, ie, the hash key we seek */
-	uint32		hashso_sk_hash;
+	uint32		hashso_sk_hash[INDEX_MAX_KEYS];
 
 	/* remember the buffer associated with primary bucket */
 	Buffer		hashso_bucket_buf;
@@ -460,7 +460,7 @@ extern uint32 _hash_spareindex(uint32 num_bucket);
 extern uint32 _hash_get_totalbuckets(uint32 splitpoint_phase);
 extern void _hash_checkpage(Relation rel, Buffer buf, int flags);
 extern uint32 _hash_get_indextuple_hashkey(IndexTuple itup);
-extern bool _hash_convert_tuple(Relation index,
+extern bool _hash_convert_tuple(Relation index, int nkeys,
 								Datum *user_values, bool *user_isnull,
 								Datum *index_values, bool *index_isnull);
 extern OffsetNumber _hash_binsearch(Page page, uint32 hash_value);
-- 
1.8.3.1

