From c9253d7bc94e376048e911b43fc4fc9dcee1147b Mon Sep 17 00:00:00 2001
From: B Sadhu Prasad Patro <b.sadhuprasadp@enterprisedb.com>
Date: Fri, 27 Aug 2021 03:26:23 -0700
Subject: [PATCH v1] support multi column for hash index

Create a single hashkey for all key columns
---
 src/backend/access/hash/hash.c       |  8 +++++---
 src/backend/access/hash/hashinsert.c |  3 +++
 src/backend/access/hash/hashsearch.c | 11 +++++++----
 src/backend/access/hash/hashutil.c   | 27 +++++++++++++++++++--------
 src/bin/pgbench/pgbench.c            |  3 ++-
 src/include/access/hash.h            |  4 ++--
 6 files changed, 38 insertions(+), 18 deletions(-)

diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 0752fb3..c25a0ff 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 Keys 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,
@@ -216,7 +218,7 @@ hashbuildCallback(Relation index,
 	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;
@@ -255,7 +257,7 @@ hashinsert(Relation rel, Datum *values, bool *isnull,
 	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/hashinsert.c b/src/backend/access/hash/hashinsert.c
index d254a00..da24b73 100644
--- a/src/backend/access/hash/hashinsert.c
+++ b/src/backend/access/hash/hashinsert.c
@@ -25,6 +25,7 @@
 
 static void _hash_vacuum_one_page(Relation rel, Relation hrel,
 								  Buffer metabuf, Buffer buf);
+static uint32 counter=0;
 
 /*
  *	_hash_doinsert() -- Handle insertion of a single index tuple.
@@ -49,6 +50,8 @@ _hash_doinsert(Relation rel, IndexTuple itup, Relation heapRel)
 	Bucket		bucket;
 	OffsetNumber itup_off;
 
+	counter++;
+
 	/*
 	 * Get the hash key for the item (it's stored in the index tuple itself).
 	 */
diff --git a/src/backend/access/hash/hashsearch.c b/src/backend/access/hash/hashsearch.c
index 2ffa28e..dbcce2b 100644
--- a/src/backend/access/hash/hashsearch.c
+++ b/src/backend/access/hash/hashsearch.c
@@ -300,6 +300,7 @@ _hash_first(IndexScanDesc scan, ScanDirection dir)
 	Page		page;
 	HashPageOpaque opaque;
 	HashScanPosItem *currItem;
+	Datum		keyData[INDEX_MAX_KEYS];
 
 	pgstat_count_index_scan(rel);
 
@@ -314,11 +315,13 @@ _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 */
+	/* SADHU  There may be more than one index qual, but we hash only the first */
 	cur = &scan->keyData[0];
+	for (int i = 0; i < scan->numberOfKeys; i++)
+	{
+		keyData[i] = scan->keyData[i].sk_argument;
+	}
 
-	/* 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);
 
@@ -341,7 +344,7 @@ _hash_first(IndexScanDesc scan, ScanDirection dir)
 	 */
 	if (cur->sk_subtype == rel->rd_opcintype[0] ||
 		cur->sk_subtype == InvalidOid)
-		hashkey = _hash_datum2hashkey(rel, cur->sk_argument);
+		hashkey = _hash_datum2hashkey(rel, scan->numberOfKeys, keyData);
 	else
 		hashkey = _hash_datum2hashkey_type(rel, cur->sk_argument,
 										   cur->sk_subtype);
diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c
index 5198728..ff6f782 100644
--- a/src/backend/access/hash/hashutil.c
+++ b/src/backend/access/hash/hashutil.c
@@ -80,16 +80,23 @@ _hash_checkqual(IndexScanDesc scan, IndexTuple itup)
  * "primary" hash function that's tracked for us by the generic index code.
  */
 uint32
-_hash_datum2hashkey(Relation rel, Datum key)
+_hash_datum2hashkey(Relation rel, int nkeys, Datum* key)
 {
 	FmgrInfo   *procinfo;
 	Oid			collation;
+	uint32			hashkey = 0;
 
-	/* XXX assumes index has only one attribute */
-	procinfo = index_getprocinfo(rel, 1, HASHSTANDARD_PROC);
 	collation = rel->rd_indcollation[0];
 
-	return DatumGetUInt32(FunctionCall1Coll(procinfo, collation, key));
+	/* Generate Hash for all given attributes */
+	for (int i = 0; i < nkeys; i++)
+	{
+		procinfo = index_getprocinfo(rel, (i + 1), HASHSTANDARD_PROC);
+		hashkey = hash_combine(hashkey,
+					FunctionCall1Coll(procinfo, collation, key[i]));
+	}
+
+	return hashkey;
 }
 
 /*
@@ -316,20 +323,24 @@ _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[0]);
+	hashkey = _hash_datum2hashkey(index, nkeys, user_values);
 	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..b74b383 100644
--- a/src/include/access/hash.h
+++ b/src/include/access/hash.h
@@ -452,7 +452,7 @@ extern void _h_indexbuild(HSpool *hspool, Relation heapRel);
 
 /* hashutil.c */
 extern bool _hash_checkqual(IndexScanDesc scan, IndexTuple itup);
-extern uint32 _hash_datum2hashkey(Relation rel, Datum key);
+extern uint32 _hash_datum2hashkey(Relation rel, int nkeys, Datum* key);
 extern uint32 _hash_datum2hashkey_type(Relation rel, Datum key, Oid keytype);
 extern Bucket _hash_hashkey2bucket(uint32 hashkey, uint32 maxbucket,
 								   uint32 highmask, uint32 lowmask);
@@ -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

