From 91522ae67843082262d829719c97ca66eb9d546c Mon Sep 17 00:00:00 2001
From: Amul Sul <sulamul@gmail.com>
Date: Fri, 12 May 2017 14:25:12 +0530
Subject: [PATCH 2/2] hash-partitioning_another_design-v3

v3:
  Fixes w.r.t. Ashutosh's and Robert's review comments.

v2:
   Regression test updated.
   Documentation added.
   Added tab complition for FOR VALUES WITH

v1:
   Initial patch
---
 doc/src/sgml/ref/alter_table.sgml          |   7 +
 doc/src/sgml/ref/create_table.sgml         |  57 +++-
 src/backend/catalog/partition.c            | 530 ++++++++++++++++++++++++++---
 src/backend/commands/tablecmds.c           |  87 +++--
 src/backend/nodes/copyfuncs.c              |   2 +
 src/backend/nodes/equalfuncs.c             |   2 +
 src/backend/nodes/outfuncs.c               |   2 +
 src/backend/nodes/readfuncs.c              |   2 +
 src/backend/parser/gram.y                  |  61 +++-
 src/backend/parser/parse_utilcmd.c         | 147 +++++---
 src/backend/utils/adt/ruleutils.c          |  13 +
 src/bin/psql/tab-complete.c                |   2 +-
 src/include/catalog/pg_proc.h              |   4 +
 src/include/nodes/parsenodes.h             |   8 +-
 src/test/regress/expected/alter_table.out  |  58 ++++
 src/test/regress/expected/create_table.out |  63 +++-
 src/test/regress/expected/insert.out       |  90 +++++
 src/test/regress/expected/update.out       |  21 ++
 src/test/regress/sql/alter_table.sql       |  59 ++++
 src/test/regress/sql/create_table.sql      |  44 ++-
 src/test/regress/sql/insert.sql            |  70 ++++
 src/test/regress/sql/update.sql            |  19 ++
 22 files changed, 1191 insertions(+), 157 deletions(-)

diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 56ea830..0468770 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -1396,6 +1396,13 @@ ALTER TABLE cities
 </programlisting></para>
 
   <para>
+   Attach a partition to hash partitioned table:
+<programlisting>
+ALTER TABLE orders
+    ATTACH PARTITION orders_p4 FOR VALUES WITH (modulus 4, remainder 3);
+</programlisting></para>
+
+  <para>
    Detach a partition from partitioned table:
 <programlisting>
 ALTER TABLE cities
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 484f818..1c57e52 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,7 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     [, ... ]
 ] )
 [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
-[ PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
+[ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
@@ -39,7 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     | <replaceable>table_constraint</replaceable> }
     [, ... ]
 ) ]
-[ PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
+[ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
@@ -50,7 +50,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     | <replaceable>table_constraint</replaceable> }
     [, ... ]
 ) ] FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable>
-[ PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
+[ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
@@ -87,7 +87,8 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 <phrase>and <replaceable class="PARAMETER">partition_bound_spec</replaceable> is:</phrase>
 
 { IN ( { <replaceable class="PARAMETER">bound_literal</replaceable> | NULL } [, ...] ) |
-  FROM ( { <replaceable class="PARAMETER">bound_literal</replaceable> | UNBOUNDED } [, ...] ) TO ( { <replaceable class="PARAMETER">bound_literal</replaceable> | UNBOUNDED } [, ...] ) }
+  FROM ( { <replaceable class="PARAMETER">bound_literal</replaceable> | UNBOUNDED } [, ...] ) TO ( { <replaceable class="PARAMETER">bound_literal</replaceable> | UNBOUNDED } [, ...] ) |
+  WITH ( MODULUS <replaceable class="PARAMETER">modulus</replaceable>, REMAINDER <replaceable class="PARAMETER">remainder</replaceable> ) }
 
 <phrase><replaceable class="PARAMETER">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase>
 
@@ -301,6 +302,29 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
      </para>
 
      <para>
+      When creating a hash partition, a modulus and remainder must be specified.
+      The modulus must be a positive integer, and the remainder must a
+      non-negative integer less than the modulus.  Typically, when initially
+      setting up a hash-partitioned table, you should choose a modulus equal to
+      the number of partitions and assign every table the same modulus and a
+      different remainder (see examples, below).   However, it is not required
+      that every partition have the same modulus, only that every modulus which
+      occurs among the children of a hash-partitioned table is a factor of the
+      next larger modulus.  This allows the number of partitions to be increased
+      incrementally without needing to move all the data at once.  For example,
+      suppose you have a hash-partitioned table with 8 children, each of which
+      has modulus 8, but find it necessary to increase the number of partitions
+      to 16.  You can detach one of the modulus-8 partitions, create two new
+      modulus-16 partitions covering the same portion of the key space (one with
+      a remainder equal to the remainder of the detached partition, and the
+      other with a remainder equal to that value plus 8), and repopulate them
+      with data.  You can then repeat this -- perhaps at a later time -- for
+      each modulus-8 partition until none remain.  While this may still involve
+      a large amount of data movement at each step, it is still better than
+      having to create a whole new table and move all the data at once.
+     </para>
+
+     <para>
       A partition must have the same column names and types as the partitioned
       table to which it belongs.  If the parent is specified <literal>WITH
       OIDS</literal> then all partitions must have OIDs; the parent's OID
@@ -422,7 +446,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
-    <term><literal>PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+    <term><literal>PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
     <listitem>
      <para>
       The optional <literal>PARTITION BY</literal> clause specifies a strategy
@@ -1591,6 +1615,16 @@ CREATE TABLE cities (
 </programlisting></para>
 
   <para>
+   Create a hash partitioned table:
+<programlisting>
+CREATE TABLE orders (
+    order_id     bigint not null,
+    cust_id      bigint not null,
+    status       text
+) PARTITION BY HASH (order_id);
+</programlisting></para>
+
+  <para>
    Create partition of a range partitioned table:
 <programlisting>
 CREATE TABLE measurement_y2016m07
@@ -1641,6 +1675,19 @@ CREATE TABLE cities_ab
 CREATE TABLE cities_ab_10000_to_100000
     PARTITION OF cities_ab FOR VALUES FROM (10000) TO (100000);
 </programlisting></para>
+
+  <para>
+   Create partitions of a hash partitioned table:
+<programlisting>
+CREATE TABLE orders_p1 PARTITION OF orders
+    FOR VALUES WITH(MODULUS 4, REMAINDER 0);
+CREATE TABLE orders_p2 PARTITION OF orders
+    FOR VALUES WITH(MODULUS 4, REMAINDER 1);
+CREATE TABLE orders_p3 PARTITION OF orders
+    FOR VALUES WITH(MODULUS 4, REMAINDER 2);
+CREATE TABLE orders_p4 PARTITION OF orders
+    FOR VALUES WITH(MODULUS 4, REMAINDER 3);
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index f42f287..a86f41c 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -58,12 +58,20 @@
  * In the case of range partitioning, ndatums will typically be far less than
  * 2 * nparts, because a partition's upper bound and the next partition's lower
  * bound are the same in most common cases, and we only store one of them.
+ * In the case of hash partitioning, datums is a 2-D array, stores modulus and
+ * remainder values at datums[x][0] and datums[x][1] respectively for each
+ * partition in the ascending order.
  *
  * In the case of list partitioning, the indexes array stores one entry for
  * every datum, which is the index of the partition that accepts a given datum.
  * In case of range partitioning, it stores one entry per distinct range
  * datum, which is the index of the partition for which a given datum
  * is an upper bound.
+ * In the case of hash partitioning, the number of the entries in the indexes
+ * array is same as the greatest modulus amongst all partitions. For a given
+ * partition key datum-tuple, the index of the partition which would accept that
+ * datum-tuple would be given by the entry pointed by remainder produced when
+ * hash value of the datum-tuple is divided by the greatest modulus.
  */
 
 /* Ternary value to represent what's contained in a range bound datum */
@@ -76,20 +84,20 @@ typedef enum RangeDatumContent
 
 typedef struct PartitionBoundInfoData
 {
-	char		strategy;		/* list or range bounds? */
+	char		strategy;		/* hash, list or range bounds? */
 	int			ndatums;		/* Length of the datums following array */
-	Datum	  **datums;			/* Array of datum-tuples with key->partnatts
-								 * datums each */
+	Datum	  **datums;			/* For hash partitioned table, array of
+								 * modulus and remainder. For range and list
+								 * partitioned table, array of datum-tuples
+								 * with key->partnatts datums each */
 	RangeDatumContent **content;/* what's contained in each range bound datum?
-								 * (see the above enum); NULL for list
-								 * partitioned tables */
-	int		   *indexes;		/* Partition indexes; one entry per member of
-								 * the datums array (plus one if range
-								 * partitioned table) */
+								 * (see the above enum); NULL for hash and
+								 * list partitioned tables */
+	int		   *indexes;		/* Partition indexes */
 	bool		has_null;		/* Is there a null-accepting partition? false
-								 * for range partitioned tables */
+								 * for hash and range partitioned tables */
 	int			null_index;		/* Index of the null-accepting partition; -1
-								 * for range partitioned tables */
+								 * for hash and range partitioned tables */
 } PartitionBoundInfoData;
 
 /*
@@ -97,6 +105,14 @@ typedef struct PartitionBoundInfoData
  * is represented with one of the following structs.
  */
 
+/* One bound of a hash partition */
+typedef struct PartitionHashBound
+{
+	int			modulus;
+	int			remainder;
+	int			index;
+}	PartitionHashBound;
+
 /* One value coming from some (index'th) list partition */
 typedef struct PartitionListValue
 {
@@ -113,11 +129,13 @@ typedef struct PartitionRangeBound
 	bool		lower;			/* this is the lower (vs upper) bound */
 } PartitionRangeBound;
 
+static int32 qsort_partition_hbound_cmp(const void *a, const void *b);
 static int32 qsort_partition_list_value_cmp(const void *a, const void *b,
 							   void *arg);
 static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
 						   void *arg);
 
+static List *get_qual_for_hash(PartitionKey key, PartitionBoundSpec *spec);
 static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static Oid get_partition_operator(PartitionKey key, int col,
@@ -140,6 +158,12 @@ static int partition_bound_bsearch(PartitionKey key,
 						PartitionBoundInfo boundinfo,
 						void *probe, bool probe_is_bound, bool *is_equal);
 
+static uint32 compute_hash_value(FmgrInfo *partsupfunc, int nkeys,
+				   Datum *values, bool *isnull);
+
+/* SQL-callable function for use in hash partition CHECK constraints */
+PG_FUNCTION_INFO_V1(satisfies_hash_partition);
+
 /*
  * RelationBuildPartitionDesc
  *		Form rel's partition descriptor
@@ -163,6 +187,9 @@ RelationBuildPartitionDesc(Relation rel)
 
 	int			ndatums = 0;
 
+	/* Hash partitioning specific */
+	PartitionHashBound **hbounds = NULL;
+
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	bool		found_null = false;
@@ -229,7 +256,33 @@ RelationBuildPartitionDesc(Relation rel)
 			oids[i++] = lfirst_oid(cell);
 
 		/* Convert from node to the internal representation */
-		if (key->strategy == PARTITION_STRATEGY_LIST)
+		if (key->strategy == PARTITION_STRATEGY_HASH)
+		{
+			ndatums = nparts;
+			hbounds = (PartitionHashBound **) palloc(nparts *
+											   sizeof(PartitionHashBound *));
+			i = 0;
+			foreach(cell, boundspecs)
+			{
+				PartitionBoundSpec *spec = lfirst(cell);
+
+				if (spec->strategy != PARTITION_STRATEGY_HASH)
+					elog(ERROR, "invalid strategy in partition bound spec");
+
+				hbounds[i] = (PartitionHashBound *)
+					palloc(sizeof(PartitionHashBound));
+
+				hbounds[i]->modulus = spec->modulus;
+				hbounds[i]->remainder = spec->remainder;
+				hbounds[i]->index = i;
+				i++;
+			}
+
+			/* Sort all the bounds in ascending order */
+			qsort(hbounds, nparts, sizeof(PartitionHashBound *),
+				  qsort_partition_hbound_cmp);
+		}
+		else if (key->strategy == PARTITION_STRATEGY_LIST)
 		{
 			List	   *non_null_values = NIL;
 
@@ -454,6 +507,42 @@ RelationBuildPartitionDesc(Relation rel)
 
 		switch (key->strategy)
 		{
+			case PARTITION_STRATEGY_HASH:
+				{
+					/* Modulus are stored in ascending order */
+					int			greatest_modulus = hbounds[ndatums - 1]->modulus;
+
+					boundinfo->indexes = (int *) palloc(greatest_modulus *
+														sizeof(int));
+
+					for (i = 0; i < greatest_modulus; i++)
+						boundinfo->indexes[i] = -1;
+
+					for (i = 0; i < nparts; i++)
+					{
+						int			modulus = hbounds[i]->modulus;
+						int			remainder = hbounds[i]->remainder;
+
+						boundinfo->datums[i] = (Datum *) palloc(2 *
+															  sizeof(Datum));
+						boundinfo->datums[i][0] = Int32GetDatum(modulus);
+						boundinfo->datums[i][1] = Int32GetDatum(remainder);
+
+						while (remainder < greatest_modulus)
+						{
+							/* overlap? */
+							Assert(boundinfo->indexes[remainder] == -1);
+							boundinfo->indexes[remainder] = hbounds[i]->index;
+							remainder += modulus;
+						}
+
+						mapping[i] = i;
+						pfree(hbounds[i]);
+					}
+					pfree(hbounds);
+					break;
+				}
+
 			case PARTITION_STRATEGY_LIST:
 				{
 					boundinfo->has_null = found_null;
@@ -607,53 +696,81 @@ partition_bounds_equal(PartitionKey key,
 	if (b1->null_index != b2->null_index)
 		return false;
 
-	for (i = 0; i < b1->ndatums; i++)
+	if (key->strategy == PARTITION_STRATEGY_HASH)
 	{
-		int			j;
+		int			greatest_modulus;
+
+		/*
+		 * Hash partition bound stores modulus and remainder at
+		 * b1->datums[i][0] and b1->datums[i][1] position respectively.
+		 */
+		for (i = 0; i < b1->ndatums; i++)
+			if (!(b1->datums[i][0] == b2->datums[i][0] &&
+				  b1->datums[i][1] == b2->datums[i][1]))
+				return false;
 
-		for (j = 0; j < key->partnatts; j++)
+		/* Compare indexes */
+		greatest_modulus = DatumGetInt32(b1->datums[b1->ndatums - 1][0]);
+		for (i = 0; i < greatest_modulus; i++)
+			if (b1->indexes[i] != b2->indexes[i])
+				return false;
+	}
+	else
+	{
+		for (i = 0; i < b1->ndatums; i++)
 		{
-			/* For range partitions, the bounds might not be finite. */
-			if (b1->content != NULL)
+			int			j;
+
+			for (j = 0; j < key->partnatts; j++)
 			{
+				/* For range partitions, the bounds might not be finite. */
+				if (b1->content != NULL)
+				{
+					/*
+					 * A finite bound always differs from an infinite bound,
+					 * and different kinds of infinities differ from each
+					 * other.
+					 */
+					if (b1->content[i][j] != b2->content[i][j])
+						return false;
+
+					/*
+					 * Non-finite bounds are equal without further
+					 * examination.
+					 */
+					if (b1->content[i][j] != RANGE_DATUM_FINITE)
+						continue;
+				}
+
 				/*
-				 * A finite bound always differs from an infinite bound, and
-				 * different kinds of infinities differ from each other.
+				 * Compare the actual values. Note that it would be both
+				 * incorrect and unsafe to invoke the comparison operator
+				 * derived from the partitioning specification here.  It would
+				 * be incorrect because we want the relcache entry to be
+				 * updated for ANY change to the partition bounds, not just
+				 * those that the partitioning operator thinks are
+				 * significant.  It would be unsafe because we might reach
+				 * this code in the context of an aborted transaction, and an
+				 * arbitrary partitioning operator might not be safe in that
+				 * context.  datumIsEqual() should be simple enough to be
+				 * safe.
 				 */
-				if (b1->content[i][j] != b2->content[i][j])
+				if (!datumIsEqual(b1->datums[i][j], b2->datums[i][j],
+								  key->parttypbyval[j],
+								  key->parttyplen[j]))
 					return false;
-
-				/* Non-finite bounds are equal without further examination. */
-				if (b1->content[i][j] != RANGE_DATUM_FINITE)
-					continue;
 			}
 
-			/*
-			 * Compare the actual values. Note that it would be both incorrect
-			 * and unsafe to invoke the comparison operator derived from the
-			 * partitioning specification here.  It would be incorrect because
-			 * we want the relcache entry to be updated for ANY change to the
-			 * partition bounds, not just those that the partitioning operator
-			 * thinks are significant.  It would be unsafe because we might
-			 * reach this code in the context of an aborted transaction, and
-			 * an arbitrary partitioning operator might not be safe in that
-			 * context.  datumIsEqual() should be simple enough to be safe.
-			 */
-			if (!datumIsEqual(b1->datums[i][j], b2->datums[i][j],
-							  key->parttypbyval[j],
-							  key->parttyplen[j]))
+			if (b1->indexes[i] != b2->indexes[i])
 				return false;
 		}
 
-		if (b1->indexes[i] != b2->indexes[i])
+		/* There are ndatums+1 indexes in case of range partitions */
+		if (key->strategy == PARTITION_STRATEGY_RANGE &&
+			b1->indexes[i] != b2->indexes[i])
 			return false;
 	}
 
-	/* There are ndatums+1 indexes in case of range partitions */
-	if (key->strategy == PARTITION_STRATEGY_RANGE &&
-		b1->indexes[i] != b2->indexes[i])
-		return false;
-
 	return true;
 }
 
@@ -675,6 +792,89 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 
 	switch (key->strategy)
 	{
+		case PARTITION_STRATEGY_HASH:
+			{
+				Assert(spec->strategy == PARTITION_STRATEGY_HASH);
+				Assert(spec->remainder >= 0 && spec->remainder < spec->modulus);
+
+				if (partdesc->nparts > 0)
+				{
+					PartitionBoundInfo boundinfo = partdesc->boundinfo;
+					Datum	  **datums = boundinfo->datums;
+					int			ndatums = boundinfo->ndatums;
+					int			greatest_modulus;
+					int			remainder;
+					int			offset;
+					bool		equal,
+								valid_bound = true;
+					int			pmod,	/* Previous largest modulus */
+								nmod;	/* Next largest modulus */
+
+					/*
+					 * Check rule that every modulus must be a factor of the
+					 * next larger modulus.  For example, if you have a bunch
+					 * of partitions that all have modulus 5, you can add a
+					 * new new partition with modulus 10 or a new partition
+					 * with modulus 15, but you cannot add both a partition
+					 * with modulus 10 and a partition with modulus 15,
+					 * because 10 is not a factor of 15.
+					 *
+					 * Get greatest bound in array boundinfo->datums which is
+					 * less than or equal to spec->modulus and
+					 * spec->remainder.
+					 */
+					offset = partition_bound_bsearch(key, boundinfo, spec,
+													 true, &equal);
+					if (offset < 0)
+					{
+						nmod = DatumGetInt32(datums[0][0]);
+						valid_bound = (nmod % spec->modulus) == 0;
+					}
+					else
+					{
+						pmod = DatumGetInt32(datums[offset][0]);
+						valid_bound = (spec->modulus % pmod) == 0;
+
+						if (valid_bound && (offset + 1) < ndatums)
+						{
+							nmod = DatumGetInt32(datums[offset + 1][0]);
+							valid_bound = (nmod % spec->modulus) == 0;
+						}
+					}
+
+					if (!valid_bound)
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+								 errmsg("every hash partition modulus must be a factor of the next larger modulus")));
+
+					greatest_modulus = DatumGetInt32(datums[ndatums - 1][0]);
+					remainder = spec->remainder;
+
+					/*
+					 * Normally, the lowest remainder that could conflict with
+					 * the new partition is equal to the remainder specified
+					 * for the new partition, but when the new partition has a
+					 * modulus higher than any used so far, we need to adjust.
+					 */
+					if (remainder >= greatest_modulus)
+						remainder = remainder % greatest_modulus;
+
+					/* Check every potentially-conflicting remainder. */
+					do
+					{
+						if (boundinfo->indexes[remainder] != -1)
+						{
+							overlap = true;
+							with = boundinfo->indexes[remainder];
+							break;
+						}
+						remainder += spec->modulus;
+					} while (remainder < greatest_modulus);
+				}
+
+				break;
+			}
+
 		case PARTITION_STRATEGY_LIST:
 			{
 				Assert(spec->strategy == PARTITION_STRATEGY_LIST);
@@ -897,6 +1097,11 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 
 	switch (key->strategy)
 	{
+		case PARTITION_STRATEGY_HASH:
+			Assert(spec->strategy == PARTITION_STRATEGY_HASH);
+			my_qual = get_qual_for_hash(key, spec);
+			break;
+
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
 			my_qual = get_qual_for_list(key, spec);
@@ -1144,6 +1349,105 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
 /* Module-local functions */
 
 /*
+ * get_qual_for_hash
+ *
+ * Given a list of partition columns, modulus and remainder this function
+ * returns an expression Node for the partition table's CHECK constraint.
+ *
+ * For example, given a partition definition such as:
+ *  CREATE TABLE simple_hash (a int, b char(10))
+ *  PARTITION BY HASH (a, b);
+ *
+ * CREATE TABLE p_p1 PARTITION OF simple_hash
+ * 	FOR VALUES WITH (modulus 2, remainder 1);
+ * CREATE TABLE p_p2 PARTITION OF simple_hash
+ * 	FOR VALUES WITH (modulus 4, remainder 2);
+ * CREATE TABLE p_p3 PARTITION OF simple_hash
+ * 	FOR VALUES WITH (modulus 8, remainder 0);
+ * CREATE TABLE p_p4 PARTITION OF simple_hash
+ * 	FOR VALUES WITH (modulus 8, remainder 4);
+ *
+ * This function will return one of the following in the form of a
+ * subexpression:
+ *
+ * for p_p1: satisfies_hash_partition(2, 1, hash_fn(a), hash_fn(b))
+ * for p_p2: satisfies_hash_partition(4, 2, hash_fn(a), hash_fn(b))
+ * for p_p3: satisfies_hash_partition(8, 0, hash_fn(a), hash_fn(b))
+ * for p_p4: satisfies_hash_partition(8, 4, hash_fn(a), hash_fn(b))
+ */
+static List *
+get_qual_for_hash(PartitionKey key, PartitionBoundSpec *spec)
+{
+	FuncExpr   *fexpr;
+	Node	   *modulusConst;
+	Node	   *remainderConst;
+	List	   *args;
+	ListCell   *partexprs_item;
+	int			i;
+
+	/* Fixed arguments. */
+	modulusConst = (Node *) makeConst(INT4OID,
+									  -1,
+									  InvalidOid,
+									  sizeof(int32),
+									  Int32GetDatum(spec->modulus),
+									  false,
+									  true);
+
+	remainderConst = (Node *) makeConst(INT4OID,
+										-1,
+										InvalidOid,
+										sizeof(int32),
+										Int32GetDatum(spec->remainder),
+										false,
+										true);
+
+	args = list_make2(modulusConst, remainderConst);
+	partexprs_item = list_head(key->partexprs);
+
+	/* Add an argument for each key column. */
+	for (i = 0; i < key->partnatts; i++)
+	{
+		Node	   *keyCol;
+
+		/* Left operand */
+		if (key->partattrs[i] != 0)
+		{
+			keyCol = (Node *) makeVar(1,
+									  key->partattrs[i],
+									  key->parttypid[i],
+									  key->parttypmod[i],
+									  key->parttypcoll[i],
+									  0);
+
+			/* Form hash_fn(value) expression */
+			keyCol = (Node *) makeFuncExpr(key->partsupfunc[i].fn_oid,
+								   get_fn_expr_rettype(&key->partsupfunc[i]),
+										   list_make1(keyCol),
+										   InvalidOid,
+										   InvalidOid,
+										   COERCE_EXPLICIT_CALL);
+		}
+		else
+		{
+			keyCol = (Node *) copyObject(lfirst(partexprs_item));
+			partexprs_item = lnext(partexprs_item);
+		}
+
+		args = lappend(args, keyCol);
+	}
+
+	fexpr = makeFuncExpr(F_SATISFIES_HASH_PARTITION,
+						 BOOLOID,
+						 args,
+						 InvalidOid,
+						 InvalidOid,
+						 COERCE_EXPLICIT_CALL);
+
+	return list_make1(fexpr);
+}
+
+/*
  * get_qual_for_list
  *
  * Returns a list of expressions to use as a list partition's constraint.
@@ -1717,10 +2021,25 @@ get_partition_for_tuple(PartitionDispatch *pd,
 
 		switch (key->strategy)
 		{
+			case PARTITION_STRATEGY_HASH:
+				{
+					PartitionBoundInfo boundinfo = partdesc->boundinfo;
+					int			ndatums = boundinfo->ndatums;
+					Datum		datum = boundinfo->datums[ndatums - 1][0];
+					int			greatest_modulus = DatumGetInt32(datum);
+					uint32		rowHash = compute_hash_value(key->partsupfunc,
+															 key->partnatts,
+															 values, isnull);
+
+					cur_index = boundinfo->indexes[rowHash % greatest_modulus];
+				}
+				break;
+
 			case PARTITION_STRATEGY_LIST:
+
 				/*
-				 * A null partition key is only acceptable if null-accepting list
-				 * partition exists.
+				 * A null partition key is only acceptable if null-accepting
+				 * list partition exists.
 				 */
 				if (isnull[0])
 					cur_index = partdesc->boundinfo->null_index;
@@ -1731,7 +2050,7 @@ get_partition_for_tuple(PartitionDispatch *pd,
 
 					/* bsearch in partdesc->boundinfo */
 					cur_offset = partition_bound_bsearch(key, partdesc->boundinfo,
-														 values, false, &equal);
+													  values, false, &equal);
 					if (cur_offset >= 0 && equal)
 						cur_index = partdesc->boundinfo->indexes[cur_offset];
 					else
@@ -1754,7 +2073,7 @@ get_partition_for_tuple(PartitionDispatch *pd,
 
 					/* bsearch in partdesc->boundinfo */
 					cur_offset = partition_bound_bsearch(key, partdesc->boundinfo,
-														 values, false, &equal);
+													  values, false, &equal);
 
 					/*
 					 * Offset returned is such that the bound at offset is
@@ -1796,6 +2115,26 @@ get_partition_for_tuple(PartitionDispatch *pd,
 }
 
 /*
+ * We sort hash bounds by modulus, then by remainder.
+ */
+static int32
+qsort_partition_hbound_cmp(const void *a, const void *b)
+{
+	PartitionHashBound *h1 = (*(PartitionHashBound * const *) a);
+	PartitionHashBound *h2 = (*(PartitionHashBound * const *) b);
+	int			v1 = h1->modulus;
+	int			v2 = h2->modulus;
+
+	if (v1 < v2)
+		return -1;
+	if (v1 > v2)
+		return 1;
+	if (v1 == v2 && h1->remainder != h2->remainder)
+		return (h1->remainder > h2->remainder) ? 1 : -1;
+	return 0;
+}
+
+/*
  * qsort_partition_list_value_cmp
  *
  * Compare two list partition bound datums
@@ -1972,6 +2311,25 @@ partition_bound_cmp(PartitionKey key, PartitionBoundInfo boundinfo,
 
 	switch (key->strategy)
 	{
+		case PARTITION_STRATEGY_HASH:
+			{
+				PartitionBoundSpec *spec = (PartitionBoundSpec *) probe;
+				int			mod = DatumGetInt32(bound_datums[0]);
+
+				if (mod < spec->modulus)
+					cmpval = -1;
+				else if (mod > spec->modulus)
+					cmpval = 1;
+				else if (mod == spec->modulus)
+				{
+					int			rem = DatumGetInt32(bound_datums[1]);
+
+					cmpval = rem == spec->remainder ? 0 :
+						(rem < spec->remainder ? -1 : 1);
+				}
+
+				break;
+			}
 		case PARTITION_STRATEGY_LIST:
 			cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
 													 key->partcollation[0],
@@ -2055,3 +2413,81 @@ partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
 
 	return lo;
 }
+
+/* Mix hash bits */
+static uint32
+mix_hash_value(int nkeys, Datum *values, bool *isnull)
+{
+	int			i;
+	uint32		rowHash = 0;
+
+	for (i = 0; i < nkeys; i++)
+	{
+		/* rotate hash left 1 bit before mixing in the next column */
+		rowHash = (rowHash << 1) | ((rowHash & 0x80000000) ? 1 : 0);
+
+		if (!isnull[i])
+			rowHash ^= DatumGetUInt32(values[i]);
+	}
+
+	return rowHash;
+}
+
+/*
+ * Compute the hash value for given not null partition key values.
+ */
+static uint32
+compute_hash_value(FmgrInfo *partsupfunc, int nkeys, Datum *values,
+				   bool *isnull)
+{
+	int			i;
+	Datum		hashValues[PARTITION_MAX_KEYS];
+
+	for (i = 0; i < nkeys; i++)
+	{
+		if (!isnull[i])
+		{
+			Assert(OidIsValid(partsupfunc[i].fn_oid));
+
+			hashValues[i] = FunctionCall1(&partsupfunc[i], values[i]);
+		}
+	}
+
+	/* Combine all hash values in hashValues array in single 32-bit hash value */
+	return mix_hash_value(nkeys, hashValues, isnull);
+}
+
+/*
+ * satisfies_hash_partition
+ *
+ * This is a SQL-callable function for use in hash partition constraints;
+ * see get_qual_for_hash() for usage.
+ */
+Datum
+satisfies_hash_partition(PG_FUNCTION_ARGS)
+{
+	int			modulus = PG_GETARG_INT32(0);
+	int			remainder = PG_GETARG_INT32(1);
+	short		nkeys = PG_NARGS() - 2;
+	int			i;
+	Datum		values[PARTITION_MAX_KEYS];
+	bool		isnull[PARTITION_MAX_KEYS];
+	uint32		rowHash = 0;
+
+	for (i = 0; i < nkeys; i++)
+	{
+		/* key's hash values start from third argument of function. */
+		if (!PG_ARGISNULL(i + 2))
+		{
+			values[i] = PG_GETARG_DATUM(i + 2);
+			isnull[i] = false;
+		}
+		else
+			isnull[i] = true;
+	}
+
+	/* Combine all hash values in single 32-bit hash value */
+	rowHash = mix_hash_value(nkeys, values, isnull);
+
+	PG_RETURN_BOOL(rowHash % modulus == remainder);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 49bb4d7..ebadeee 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -363,9 +363,9 @@ static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
 static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
 					Node *newDefault, LOCKMODE lockmode);
 static ObjectAddress ATExecAddIdentity(Relation rel, const char *colName,
-					Node *def, LOCKMODE lockmode);
+				  Node *def, LOCKMODE lockmode);
 static ObjectAddress ATExecSetIdentity(Relation rel, const char *colName,
-					Node *def, LOCKMODE lockmode);
+				  Node *def, LOCKMODE lockmode);
 static ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode);
 static void ATPrepSetStatistics(Relation rel, const char *colName,
 					Node *newValue, LOCKMODE lockmode);
@@ -459,7 +459,7 @@ static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
 static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr);
 static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
 static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
-					  List **partexprs, Oid *partopclass, Oid *partcollation, char strategy);
+	  List **partexprs, Oid *partopclass, Oid *partcollation, char strategy);
 static void CreateInheritance(Relation child_rel, Relation parent_rel);
 static void RemoveInheritance(Relation child_rel, Relation parent_rel);
 static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
@@ -643,8 +643,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
 	/*
-	 * If a partitioned table doesn't have the system OID column, then none
-	 * of its partitions should have it.
+	 * If a partitioned table doesn't have the system OID column, then none of
+	 * its partitions should have it.
 	 */
 	if (stmt->partbound && parentOidCount == 0 && localHasOids)
 		ereport(ERROR,
@@ -1112,9 +1112,9 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 	}
 
 	/*
-	 * Similarly, if we previously locked some other partition's heap, and
-	 * the name we're looking up no longer refers to that relation, release
-	 * the now-useless lock.
+	 * Similarly, if we previously locked some other partition's heap, and the
+	 * name we're looking up no longer refers to that relation, release the
+	 * now-useless lock.
 	 */
 	if (relOid != oldRelOid && OidIsValid(state->partParentOid))
 	{
@@ -2219,8 +2219,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 					else
 						ereport(ERROR,
 								(errcode(ERRCODE_DUPLICATE_COLUMN),
-								 errmsg("column \"%s\" specified more than once",
-										coldef->colname)));
+							 errmsg("column \"%s\" specified more than once",
+									coldef->colname)));
 				}
 				prev = rest;
 				rest = next;
@@ -4541,7 +4541,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 
 					values[ex->attnum - 1] = ExecEvalExpr(ex->exprstate,
 														  econtext,
-													 &isnull[ex->attnum - 1]);
+													&isnull[ex->attnum - 1]);
 				}
 
 				/*
@@ -5589,12 +5589,12 @@ static void
 ATPrepDropNotNull(Relation rel, bool recurse, bool recursing)
 {
 	/*
-	 * If the parent is a partitioned table, like check constraints, we do
-	 * not support removing the NOT NULL while partitions exist.
+	 * If the parent is a partitioned table, like check constraints, we do not
+	 * support removing the NOT NULL while partitions exist.
 	 */
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		PartitionDesc	partdesc = RelationGetPartitionDesc(rel);
+		PartitionDesc partdesc = RelationGetPartitionDesc(rel);
 
 		Assert(partdesc != NULL);
 		if (partdesc->nparts > 0 && !recurse && !recursing)
@@ -5639,8 +5639,8 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 	if (get_attidentity(RelationGetRelid(rel), attnum))
 		ereport(ERROR,
 				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("column \"%s\" of relation \"%s\" is an identity column",
-						colName, RelationGetRelationName(rel))));
+			 errmsg("column \"%s\" of relation \"%s\" is an identity column",
+					colName, RelationGetRelationName(rel))));
 
 	/*
 	 * Check that the attribute is not in a primary key
@@ -5768,7 +5768,7 @@ ATPrepSetNotNull(Relation rel, bool recurse, bool recursing)
 	 */
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		PartitionDesc	partdesc = RelationGetPartitionDesc(rel);
+		PartitionDesc partdesc = RelationGetPartitionDesc(rel);
 
 		if (partdesc && partdesc->nparts > 0 && !recurse && !recursing)
 			ereport(ERROR,
@@ -5867,8 +5867,8 @@ ATExecColumnDefault(Relation rel, const char *colName,
 	if (get_attidentity(RelationGetRelid(rel), attnum))
 		ereport(ERROR,
 				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("column \"%s\" of relation \"%s\" is an identity column",
-						colName, RelationGetRelationName(rel)),
+			 errmsg("column \"%s\" of relation \"%s\" is an identity column",
+					colName, RelationGetRelationName(rel)),
 				 newDefault ? 0 : errhint("Use ALTER TABLE ... ALTER COLUMN ... DROP IDENTITY instead.")));
 
 	/*
@@ -5959,8 +5959,8 @@ ATExecAddIdentity(Relation rel, const char *colName,
 	if (attTup->atthasdef)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("column \"%s\" of relation \"%s\" already has a default value",
-						colName, RelationGetRelationName(rel))));
+		errmsg("column \"%s\" of relation \"%s\" already has a default value",
+			   colName, RelationGetRelationName(rel))));
 
 	attTup->attidentity = cdef->identity;
 	CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
@@ -5986,7 +5986,7 @@ static ObjectAddress
 ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmode)
 {
 	ListCell   *option;
-	DefElem	   *generatedEl = NULL;
+	DefElem    *generatedEl = NULL;
 	HeapTuple	tuple;
 	Form_pg_attribute attTup;
 	AttrNumber	attnum;
@@ -5995,7 +5995,7 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
 
 	foreach(option, castNode(List, def))
 	{
-		DefElem	   *defel = lfirst_node(DefElem, option);
+		DefElem    *defel = lfirst_node(DefElem, option);
 
 		if (strcmp(defel->defname, "generated") == 0)
 		{
@@ -6036,8 +6036,8 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
 	if (!attTup->attidentity)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("column \"%s\" of relation \"%s\" is not an identity column",
-						colName, RelationGetRelationName(rel))));
+		 errmsg("column \"%s\" of relation \"%s\" is not an identity column",
+				colName, RelationGetRelationName(rel))));
 
 	if (generatedEl)
 	{
@@ -9746,7 +9746,7 @@ RebuildConstraintComment(AlteredTableInfo *tab, int pass, Oid objid,
 	cmd = makeNode(CommentStmt);
 	cmd->objtype = OBJECT_TABCONSTRAINT;
 	cmd->object = (Node *) list_make3(makeString(get_namespace_name(RelationGetNamespace(rel))),
-									  makeString(RelationGetRelationName(rel)),
+									makeString(RelationGetRelationName(rel)),
 									  makeString(conname));
 	cmd->comment = comment_str;
 
@@ -11119,7 +11119,7 @@ CreateInheritance(Relation child_rel, Relation parent_rel)
 							 inhseqno + 1,
 							 catalogRelation,
 							 parent_rel->rd_rel->relkind ==
-											RELKIND_PARTITIONED_TABLE);
+							 RELKIND_PARTITIONED_TABLE);
 
 	/* Now we're done with pg_inherits */
 	heap_close(catalogRelation, RowExclusiveLock);
@@ -13165,6 +13165,8 @@ transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
 		*strategy = PARTITION_STRATEGY_LIST;
 	else if (!pg_strcasecmp(partspec->strategy, "range"))
 		*strategy = PARTITION_STRATEGY_RANGE;
+	else if (!pg_strcasecmp(partspec->strategy, "hash"))
+		*strategy = PARTITION_STRATEGY_HASH;
 	else
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -13225,6 +13227,8 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 {
 	int			attn;
 	ListCell   *lc;
+	char	   *am_method;
+	Oid			am_oid;
 
 	attn = 0;
 	foreach(lc, partParams)
@@ -13367,25 +13371,38 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 		partcollation[attn] = attcollation;
 
 		/*
-		 * Identify a btree opclass to use. Currently, we use only btree
-		 * operators, which seems enough for list and range partitioning.
+		 * Identify a btree or hash opclass to use. Currently, we use only
+		 * btree operators, which seems enough for list and range
+		 * partitioning, and hash operators for hash partitioning.
 		 */
+		if (strategy == PARTITION_STRATEGY_HASH)
+		{
+			am_method = "hash";
+			am_oid = HASH_AM_OID;
+		}
+		else
+		{
+			am_method = "btree";
+			am_oid = BTREE_AM_OID;
+		}
+
 		if (!pelem->opclass)
 		{
-			partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID);
+			partopclass[attn] = GetDefaultOpClass(atttype, am_oid);
 
 			if (!OidIsValid(partopclass[attn]))
 				ereport(ERROR,
 						(errcode(ERRCODE_UNDEFINED_OBJECT),
-				   errmsg("data type %s has no default btree operator class",
-						  format_type_be(atttype)),
-						 errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
+					  errmsg("data type %s has no default %s operator class",
+							 format_type_be(atttype), am_method),
+						 errhint("You must specify a %s operator class or define a default %s operator class for the data type.",
+								 am_method, am_method)));
 		}
 		else
 			partopclass[attn] = ResolveOpClass(pelem->opclass,
 											   atttype,
-											   "btree",
-											   BTREE_AM_OID);
+											   am_method,
+											   am_oid);
 
 		attn++;
 	}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 2d2a9d0..660441f 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4446,6 +4446,8 @@ _copyPartitionBoundSpec(const PartitionBoundSpec *from)
 	PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
 
 	COPY_SCALAR_FIELD(strategy);
+	COPY_SCALAR_FIELD(modulus);
+	COPY_SCALAR_FIELD(remainder);
 	COPY_NODE_FIELD(listdatums);
 	COPY_NODE_FIELD(lowerdatums);
 	COPY_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index b5459cd..92aad4e 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2838,6 +2838,8 @@ static bool
 _equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
 {
 	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_SCALAR_FIELD(modulus);
+	COMPARE_SCALAR_FIELD(remainder);
 	COMPARE_NODE_FIELD(listdatums);
 	COMPARE_NODE_FIELD(lowerdatums);
 	COMPARE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 98f6768..808813b 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3545,6 +3545,8 @@ _outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
 	WRITE_NODE_TYPE("PARTITIONBOUND");
 
 	WRITE_CHAR_FIELD(strategy);
+	WRITE_INT_FIELD(modulus);
+	WRITE_INT_FIELD(remainder);
 	WRITE_NODE_FIELD(listdatums);
 	WRITE_NODE_FIELD(lowerdatums);
 	WRITE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index f9a227e..ede6306 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2375,6 +2375,8 @@ _readPartitionBoundSpec(void)
 	READ_LOCALS(PartitionBoundSpec);
 
 	READ_CHAR_FIELD(strategy);
+	READ_INT_FIELD(modulus);
+	READ_INT_FIELD(remainder);
 	READ_NODE_FIELD(listdatums);
 	READ_NODE_FIELD(lowerdatums);
 	READ_NODE_FIELD(upperdatums);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 65c004c..18b69b4 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -580,7 +580,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>		partbound_datum
 %type <list>		partbound_datum_list
 %type <partrange_datum>	PartitionRangeDatum
-%type <list>		range_datum_list
+%type <list>		range_datum_list hash_partbound
+%type <defelt>		hash_partbound_elem
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -2653,8 +2654,46 @@ alter_identity_column_option:
 		;
 
 ForValues:
+			/* a HASH partition*/
+			FOR VALUES WITH '(' hash_partbound ')' /*TODO: syntax is not finalised*/
+				{
+					ListCell   *lc;
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->strategy = PARTITION_STRATEGY_HASH;
+					n->modulus = n->remainder = -1;
+
+					foreach (lc, $5)
+					{
+						DefElem    *opt = (DefElem *) lfirst(lc);
+
+						if (strcmp(opt->defname, "modulus") == 0)
+							n->modulus = defGetInt32(opt);
+						else if (strcmp(opt->defname, "remainder") == 0)
+							n->remainder = defGetInt32(opt);
+						else
+							ereport(ERROR,
+									(errcode(ERRCODE_SYNTAX_ERROR),
+									 errmsg("unrecognized hash partition bound specification \"%s\"",
+											opt->defname),
+									 parser_errposition(opt->location)));
+					}
+
+					if (n->modulus == -1)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("hash partition modulus must be specified")));
+					if (n->remainder == -1)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("hash partition remainder must be specified")));
+
+					n->location = @1;
+
+					$$ = (Node *) n;
+				}
 			/* a LIST partition */
-			FOR VALUES IN_P '(' partbound_datum_list ')'
+			| FOR VALUES IN_P '(' partbound_datum_list ')'
 				{
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
@@ -2679,6 +2718,24 @@ ForValues:
 				}
 		;
 
+hash_partbound_elem:
+		NonReservedWord Iconst
+			{
+				$$ = makeDefElem($1, (Node *)makeInteger($2), @1);
+			}
+		;
+
+hash_partbound:
+		hash_partbound_elem
+			{
+				$$ = list_make1($1);
+			}
+		| hash_partbound ',' hash_partbound_elem
+			{
+				$$ = lappend($1, $3);
+			}
+		;
+
 partbound_datum:
 			Sconst			{ $$ = makeStringConst($1, @1); }
 			| NumericOnly	{ $$ = makeAConst($1, @1); }
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 882955b..61878a0 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -363,7 +363,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 						 char **snamespace_p, char **sname_p)
 {
 	ListCell   *option;
-	DefElem	   *nameEl = NULL;
+	DefElem    *nameEl = NULL;
 	Oid			snamespaceid;
 	char	   *snamespace;
 	char	   *sname;
@@ -378,12 +378,12 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	 * used by pg_dump.  Else, generate a name.
 	 *
 	 * Although we use ChooseRelationName, it's not guaranteed that the
-	 * selected sequence name won't conflict; given sufficiently long
-	 * field names, two different serial columns in the same table could
-	 * be assigned the same sequence name, and we'd not notice since we
-	 * aren't creating the sequence quite yet.  In practice this seems
-	 * quite unlikely to be a problem, especially since few people would
-	 * need two serial columns in one table.
+	 * selected sequence name won't conflict; given sufficiently long field
+	 * names, two different serial columns in the same table could be assigned
+	 * the same sequence name, and we'd not notice since we aren't creating
+	 * the sequence quite yet.  In practice this seems quite unlikely to be a
+	 * problem, especially since few people would need two serial columns in
+	 * one table.
 	 */
 
 	foreach(option, seqoptions)
@@ -402,7 +402,8 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 
 	if (nameEl)
 	{
-		RangeVar *rv = makeRangeVarFromNameList(castNode(List, nameEl->arg));
+		RangeVar   *rv = makeRangeVarFromNameList(castNode(List, nameEl->arg));
+
 		snamespace = rv->schemaname;
 		sname = rv->relname;
 		seqoptions = list_delete_ptr(seqoptions, nameEl);
@@ -429,14 +430,14 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 					cxt->relation->relname, column->colname)));
 
 	/*
-	 * Build a CREATE SEQUENCE command to create the sequence object, and
-	 * add it to the list of things to be done before this CREATE/ALTER
-	 * TABLE.
+	 * Build a CREATE SEQUENCE command to create the sequence object, and add
+	 * it to the list of things to be done before this CREATE/ALTER TABLE.
 	 */
 	seqstmt = makeNode(CreateSeqStmt);
 	seqstmt->for_identity = for_identity;
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -448,11 +449,11 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 								 seqstmt->options);
 
 	/*
-	 * If this is ALTER ADD COLUMN, make sure the sequence will be owned
-	 * by the table's owner.  The current user might be someone else
-	 * (perhaps a superuser, or someone who's only a member of the owning
-	 * role), but the SEQUENCE OWNED BY mechanisms will bleat unless table
-	 * and sequence have exactly the same owning role.
+	 * If this is ALTER ADD COLUMN, make sure the sequence will be owned by
+	 * the table's owner.  The current user might be someone else (perhaps a
+	 * superuser, or someone who's only a member of the owning role), but the
+	 * SEQUENCE OWNED BY mechanisms will bleat unless table and sequence have
+	 * exactly the same owning role.
 	 */
 	if (cxt->rel)
 		seqstmt->ownerId = cxt->rel->rd_rel->relowner;
@@ -462,9 +463,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	cxt->blist = lappend(cxt->blist, seqstmt);
 
 	/*
-	 * Build an ALTER SEQUENCE ... OWNED BY command to mark the sequence
-	 * as owned by this column, and add it to the list of things to be
-	 * done after this CREATE/ALTER TABLE.
+	 * Build an ALTER SEQUENCE ... OWNED BY command to mark the sequence as
+	 * owned by this column, and add it to the list of things to be done after
+	 * this CREATE/ALTER TABLE.
 	 */
 	altseqstmt = makeNode(AlterSeqStmt);
 	altseqstmt->sequence = makeRangeVar(snamespace, sname, -1);
@@ -647,31 +648,31 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 				break;
 
 			case CONSTR_IDENTITY:
-			{
-				Type		ctype;
-				Oid			typeOid;
+				{
+					Type		ctype;
+					Oid			typeOid;
 
-				ctype = typenameType(cxt->pstate, column->typeName, NULL);
-				typeOid = HeapTupleGetOid(ctype);
-				ReleaseSysCache(ctype);
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typeOid = HeapTupleGetOid(ctype);
+					ReleaseSysCache(ctype);
 
-				if (saw_identity)
-					ereport(ERROR,
-							(errcode(ERRCODE_SYNTAX_ERROR),
-							 errmsg("multiple identity specifications for column \"%s\" of table \"%s\"",
+					if (saw_identity)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("multiple identity specifications for column \"%s\" of table \"%s\"",
 									column->colname, cxt->relation->relname),
-							 parser_errposition(cxt->pstate,
-												constraint->location)));
+								 parser_errposition(cxt->pstate,
+													constraint->location)));
 
-				generateSerialExtraStmts(cxt, column,
-										 typeOid, constraint->options, true,
-										 NULL, NULL);
+					generateSerialExtraStmts(cxt, column,
+										  typeOid, constraint->options, true,
+											 NULL, NULL);
 
-				column->identity = constraint->generated_when;
-				saw_identity = true;
-				column->is_not_null = TRUE;
-				break;
-			}
+					column->identity = constraint->generated_when;
+					saw_identity = true;
+					column->is_not_null = TRUE;
+					break;
+				}
 
 			case CONSTR_CHECK:
 				cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
@@ -1036,7 +1037,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 		if (attribute->attidentity &&
 			(table_like_clause->options & CREATE_TABLE_LIKE_IDENTITY))
 		{
-			Oid         seq_relid;
+			Oid			seq_relid;
 			List	   *seq_options;
 
 			/*
@@ -1067,7 +1068,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 
 			stmt->objtype = OBJECT_COLUMN;
 			stmt->object = (Node *) list_make3(makeString(cxt->relation->schemaname),
-											   makeString(cxt->relation->relname),
+										  makeString(cxt->relation->relname),
 											   makeString(def->colname));
 			stmt->comment = comment;
 
@@ -1132,7 +1133,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 
 				stmt->objtype = OBJECT_TABCONSTRAINT;
 				stmt->object = (Node *) list_make3(makeString(cxt->relation->schemaname),
-												   makeString(cxt->relation->relname),
+										  makeString(cxt->relation->relname),
 												   makeString(n->conname));
 				stmt->comment = comment;
 
@@ -2766,7 +2767,11 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					 * change the data type of the sequence.
 					 */
 					attnum = get_attnum(relid, cmd->name);
-					/* if attribute not found, something will error about it later */
+
+					/*
+					 * if attribute not found, something will error about it
+					 * later
+					 */
 					if (attnum != InvalidAttrNumber && get_attidentity(relid, attnum))
 					{
 						Oid			seq_relid = getOwnedSequence(relid, attnum);
@@ -2774,7 +2779,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 						AlterSeqStmt *altseqstmt = makeNode(AlterSeqStmt);
 
 						altseqstmt->sequence = makeRangeVar(get_namespace_name(get_rel_namespace(seq_relid)),
-															get_rel_name(seq_relid),
+													 get_rel_name(seq_relid),
 															-1);
 						altseqstmt->options = list_make1(makeDefElem("as", (Node *) makeTypeNameFromOid(typeOid, -1), -1));
 						altseqstmt->for_identity = true;
@@ -2787,8 +2792,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 			case AT_AddIdentity:
 				{
-					Constraint  *def = castNode(Constraint, cmd->def);
-					ColumnDef *newdef = makeNode(ColumnDef);
+					Constraint *def = castNode(Constraint, cmd->def);
+					ColumnDef  *newdef = makeNode(ColumnDef);
 					AttrNumber	attnum;
 
 					newdef->colname = cmd->name;
@@ -2796,7 +2801,11 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					cmd->def = (Node *) newdef;
 
 					attnum = get_attnum(relid, cmd->name);
-					/* if attribute not found, something will error about it later */
+
+					/*
+					 * if attribute not found, something will error about it
+					 * later
+					 */
 					if (attnum != InvalidAttrNumber)
 						generateSerialExtraStmts(&cxt, newdef,
 												 get_atttype(relid, attnum),
@@ -2825,7 +2834,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					 */
 					foreach(lc, castNode(List, cmd->def))
 					{
-						DefElem	   *def = lfirst_node(DefElem, lc);
+						DefElem    *def = lfirst_node(DefElem, lc);
 
 						if (strcmp(def->defname, "generated") == 0)
 							newdef = lappend(newdef, def);
@@ -2846,7 +2855,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 							seqstmt = makeNode(AlterSeqStmt);
 							seq_relid = linitial_oid(seqlist);
 							seqstmt->sequence = makeRangeVar(get_namespace_name(get_rel_namespace(seq_relid)),
-															 get_rel_name(seq_relid), -1);
+												get_rel_name(seq_relid), -1);
 							seqstmt->options = newseqopts;
 							seqstmt->for_identity = true;
 							seqstmt->missing_ok = false;
@@ -2854,8 +2863,11 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 							cxt.alist = lappend(cxt.alist, seqstmt);
 						}
 					}
-					/* If column was not found or was not an identity column, we
-					 * just let the ALTER TABLE command error out later. */
+
+					/*
+					 * If column was not found or was not an identity column,
+					 * we just let the ALTER TABLE command error out later.
+					 */
 
 					cmd->def = (Node *) newdef;
 					newcmds = lappend(newcmds, cmd);
@@ -3282,7 +3294,30 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
 
 	result_spec = copyObject(spec);
 
-	if (strategy == PARTITION_STRATEGY_LIST)
+	if (strategy == PARTITION_STRATEGY_HASH)
+	{
+		if (spec->strategy != PARTITION_STRATEGY_HASH)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				  errmsg("invalid bound specification for a hash partition"),
+					 parser_errposition(pstate, exprLocation(bound))));
+
+		if (spec->modulus <= 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				   errmsg("hash partition modulus must be greater than 0")));
+
+		if (spec->remainder < 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+			  errmsg("hash partition remainder must be less than modulus")));
+
+		if (spec->remainder >= spec->modulus)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+			errmsg("hash partition modulus must be greater than remainder")));
+	}
+	else if (strategy == PARTITION_STRATEGY_LIST)
 	{
 		ListCell   *cell;
 		char	   *colname;
@@ -3392,8 +3427,8 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
 			else if (seen_unbounded)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("cannot specify finite value after UNBOUNDED"),
-						 parser_errposition(pstate, exprLocation((Node *) ldatum))));
+					   errmsg("cannot specify finite value after UNBOUNDED"),
+				 parser_errposition(pstate, exprLocation((Node *) ldatum))));
 		}
 		seen_unbounded = false;
 		foreach(cell1, spec->upperdatums)
@@ -3406,8 +3441,8 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
 			else if (seen_unbounded)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("cannot specify finite value after UNBOUNDED"),
-						 parser_errposition(pstate, exprLocation((Node *) rdatum))));
+					   errmsg("cannot specify finite value after UNBOUNDED"),
+				 parser_errposition(pstate, exprLocation((Node *) rdatum))));
 		}
 
 		i = j = 0;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index cbde1ff..9456d48 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -1653,6 +1653,10 @@ pg_get_partkeydef_worker(Oid relid, int prettyFlags,
 
 	switch (form->partstrat)
 	{
+		case PARTITION_STRATEGY_HASH:
+			if (!attrsOnly)
+				appendStringInfo(&buf, "HASH");
+			break;
 		case PARTITION_STRATEGY_LIST:
 			if (!attrsOnly)
 				appendStringInfo(&buf, "LIST");
@@ -8619,6 +8623,15 @@ get_rule_expr(Node *node, deparse_context *context,
 
 				switch (spec->strategy)
 				{
+					case PARTITION_STRATEGY_HASH:
+						Assert(spec->modulus > 0 && spec->remainder >= 0);
+						Assert(spec->modulus > spec->remainder);
+
+						appendStringInfoString(buf, "FOR VALUES");
+						appendStringInfo(buf, " WITH (modulus %d, remainder %d)",
+										 spec->modulus, spec->remainder);
+						break;
+
 					case PARTITION_STRATEGY_LIST:
 						Assert(spec->listdatums != NIL);
 
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 3bd5277..046385b 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2003,7 +2003,7 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
 		COMPLETE_WITH_CONST("FOR VALUES");
 	else if (TailMatches2("FOR", "VALUES"))
-		COMPLETE_WITH_LIST2("FROM (", "IN (");
+		COMPLETE_WITH_LIST3("FROM (", "IN (", "WITH (");
 	/*
 	 * If we have ALTER TABLE <foo> DETACH PARTITION, provide a list of
 	 * partitions of <foo>.
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 77d8ed5..d459f0b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5468,6 +5468,10 @@ DESCR("list files in the log directory");
 DATA(insert OID = 3354 (  pg_ls_waldir               PGNSP PGUID 12 10 20 0 0 f f f f t t v s 0 0 2249 "" "{25,20,1184}" "{o,o,o}" "{name,size,modification}" _null_ _null_ pg_ls_waldir _null_ _null_ _null_ ));
 DESCR("list of files in the WAL directory");
 
+/* hash partitioning constraint function */
+DATA(insert OID = 5028 ( satisfies_hash_partition PGNSP PGUID 12 1 0 2276 0 f f f f f f i s 3 0 16 "23 23 2276" _null_ _null_ _null_ _null_ _null_ satisfies_hash_partition _null_ _null_ _null_ ));
+DESCR("hash partition CHECK constraint");
+
 /*
  * Symbolic values for provolatile column: these indicate whether the result
  * of a function is dependent *only* on the values of its explicit arguments,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 46c23c2..ec9b082 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -773,11 +773,13 @@ typedef struct PartitionElem
 typedef struct PartitionSpec
 {
 	NodeTag		type;
-	char	   *strategy;		/* partitioning strategy ('list' or 'range') */
+	char	   *strategy;		/* partitioning strategy
+								 * ('hash', 'list' or 'range') */
 	List	   *partParams;		/* List of PartitionElems */
 	int			location;		/* token location, or -1 if unknown */
 } PartitionSpec;
 
+#define PARTITION_STRATEGY_HASH		'h'
 #define PARTITION_STRATEGY_LIST		'l'
 #define PARTITION_STRATEGY_RANGE	'r'
 
@@ -790,6 +792,10 @@ typedef struct PartitionBoundSpec
 
 	char		strategy;
 
+	/* Hash partition specs */
+	int 		modulus;
+	int			remainder;
+
 	/* List partition values */
 	List	   *listdatums;
 
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 41df9f0..8d24d8b 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3198,6 +3198,7 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
 ERROR:  partition "fail_part" would overlap partition "part_1"
+DROP TABLE fail_part;
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3276,6 +3277,54 @@ DETAIL:  "part_5" is already a child of "list_parted2".
 ALTER TABLE list_parted2 ATTACH PARTITION list_parted2 FOR VALUES IN (0);
 ERROR:  circular inheritance not allowed
 DETAIL:  "list_parted2" is already a child of "list_parted2".
+-- check validation when attaching hash partitions
+-- check that the new partition won't overlap with an existing partition
+CREATE TABLE hash_parted (
+	a int NOT NULL,
+	b char(2) COLLATE "C",
+	CONSTRAINT hcheck_a CHECK (a > 0)
+) PARTITION BY HASH (a);
+CREATE TABLE hpart_1 PARTITION OF hash_parted FOR VALUES WITH (modulus 8, remainder 0);
+CREATE TABLE fail_part (LIKE hpart_1 INCLUDING CONSTRAINTS);
+ALTER TABLE hash_parted ATTACH PARTITION fail_part FOR VALUES WITH (modulus 4, remainder 0);
+ERROR:  partition "fail_part" would overlap partition "hpart_1"
+DROP TABLE fail_part;
+-- check validation when attaching hash partitions
+CREATE TABLE hash_parted2 (
+	a int,
+	b char
+) PARTITION BY HASH (a);
+-- check that violating rows are correctly reported
+CREATE TABLE hpart_2 (LIKE hash_parted2);
+INSERT INTO hpart_2 VALUES (3, 'a');
+ALTER TABLE hash_parted2 ATTACH PARTITION hpart_2 FOR VALUES WITH (modulus 4, remainder 0);
+ERROR:  partition constraint is violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM hpart_2;
+ALTER TABLE hash_parted2 ATTACH PARTITION hpart_2 FOR VALUES WITH (modulus 4, remainder 0);
+-- check that leaf partitions are scanned when attaching a partitioned
+-- table
+CREATE TABLE hpart_5 (
+	LIKE hash_parted2
+) PARTITION BY LIST (b);
+-- check that violating rows are correctly reported
+CREATE TABLE hpart_5_a PARTITION OF hpart_5 FOR VALUES IN ('a');
+INSERT INTO hpart_5_a (a, b) VALUES (6, 'a');
+ALTER TABLE hash_parted2 ATTACH PARTITION hpart_5 FOR VALUES WITH (modulus 4, remainder 2);
+ERROR:  partition constraint is violated by some row
+-- delete the faulting row and also add a constraint to skip the scan
+DELETE FROM hpart_5_a WHERE a NOT IN (3);
+ALTER TABLE hpart_5 ADD CONSTRAINT hcheck_a CHECK (a IN (5)), ALTER a SET NOT NULL;
+ALTER TABLE hash_parted2 ATTACH PARTITION hpart_5 FOR VALUES WITH (modulus 4, remainder 2);
+-- check that the table being attach is with valid modulus and remainder value
+CREATE TABLE fail_part(LIKE hash_parted2);
+ALTER TABLE hash_parted2 ATTACH PARTITION fail_part FOR VALUES WITH (modulus 0, remainder 1);
+ERROR:  hash partition modulus must be greater than 0
+ALTER TABLE hash_parted2 ATTACH PARTITION fail_part FOR VALUES WITH (modulus 8, remainder 8);
+ERROR:  hash partition modulus must be greater than remainder
+ALTER TABLE hash_parted2 ATTACH PARTITION fail_part FOR VALUES WITH (modulus 3, remainder 2);
+ERROR:  every hash partition modulus must be a factor of the next larger modulus
+DROP TABLE fail_part;
 --
 -- DETACH PARTITION
 --
@@ -3287,12 +3336,19 @@ DROP TABLE regular_table;
 -- check that the partition being detached exists at all
 ALTER TABLE list_parted2 DETACH PARTITION part_4;
 ERROR:  relation "part_4" does not exist
+ALTER TABLE hash_parted2 DETACH PARTITION hpart_4;
+ERROR:  relation "hpart_4" does not exist
 -- check that the partition being detached is actually a partition of the parent
 CREATE TABLE not_a_part (a int);
 ALTER TABLE list_parted2 DETACH PARTITION not_a_part;
 ERROR:  relation "not_a_part" is not a partition of relation "list_parted2"
 ALTER TABLE list_parted2 DETACH PARTITION part_1;
 ERROR:  relation "part_1" is not a partition of relation "list_parted2"
+ALTER TABLE hash_parted2 DETACH PARTITION not_a_part;
+ERROR:  relation "not_a_part" is not a partition of relation "hash_parted2"
+ALTER TABLE hash_parted2 DETACH PARTITION hpart_1;
+ERROR:  relation "hpart_1" is not a partition of relation "hash_parted2"
+DROP TABLE not_a_part;
 -- check that, after being detached, attinhcount/coninhcount is dropped to 0 and
 -- attislocal/conislocal is set to true
 ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
@@ -3375,6 +3431,8 @@ ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 ERROR:  cannot alter type of column named in partition key
 -- cleanup
 DROP TABLE list_parted, list_parted2, range_parted;
+DROP TABLE hash_parted, hpart_1;
+DROP TABLE hash_parted2, hpart_2, hpart_5, hpart_5_a;
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
 create table p1 (b int, a int not null) partition by range (b);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 15d4ce5..5aebb3d 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -340,11 +340,6 @@ CREATE TABLE partitioned (
 ) PARTITION BY RANGE (const_func());
 ERROR:  cannot use constant expression as partition key
 DROP FUNCTION const_func();
--- only accept "list" and "range" as partitioning strategy
-CREATE TABLE partitioned (
-	a int
-) PARTITION BY HASH (a);
-ERROR:  unrecognized partitioning strategy "hash"
 -- specified column must be present in the table
 CREATE TABLE partitioned (
 	a int
@@ -479,6 +474,11 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 ERROR:  invalid bound specification for a list partition
 LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
                                                              ^
+-- trying to specify modulus and remainder for list partitioned table
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES WITH (modulus 10, remainder 1);
+ERROR:  invalid bound specification for a list partition
+LINE 1: CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES W...
+                                                        ^
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -512,6 +512,30 @@ ERROR:  cannot specify finite value after UNBOUNDED
 LINE 1: ...ge_parted_multicol FOR VALUES FROM (1, UNBOUNDED, 1) TO (UNB...
                                                              ^
 DROP TABLE range_parted_multicol;
+-- trying to specify modulus and remainder for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES WITH (modulus 10, remainder 1);
+ERROR:  invalid bound specification for a range partition
+LINE 1: CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES ...
+                                                         ^
+-- check partition bound syntax for the hash partition
+CREATE TABLE hash_parted (
+	a int
+) PARTITION BY HASH (a);
+CREATE TABLE hpart_1 PARTITION OF hash_parted FOR VALUES WITH (modulus 10, remainder 1);
+CREATE TABLE hpart_2 PARTITION OF hash_parted FOR VALUES WITH (modulus 50, remainder 0);
+-- modulus 25 is factor of modulus of 50 but 10 is not factor of 25.
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES WITH (modulus 25, remainder 2);
+ERROR:  every hash partition modulus must be a factor of the next larger modulus
+-- trying to specify range for hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES FROM ('a', 1) TO ('z');
+ERROR:  invalid bound specification for a hash partition
+LINE 1: ...BLE fail_part PARTITION OF hash_parted FOR VALUES FROM ('a',...
+                                                             ^
+-- trying to specify list value for hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES IN (1000);
+ERROR:  invalid bound specification for a hash partition
+LINE 1: ...BLE fail_part PARTITION OF hash_parted FOR VALUES IN (1000);
+                                                             ^
 -- check if compatible with the specified parent
 -- cannot create as partition of a non-partitioned table
 CREATE TABLE unparted (
@@ -519,6 +543,8 @@ CREATE TABLE unparted (
 );
 CREATE TABLE fail_part PARTITION OF unparted FOR VALUES IN ('a');
 ERROR:  "unparted" is not partitioned
+CREATE TABLE fail_part PARTITION OF unparted FOR VALUES WITH (modulus 2, remainder 1);
+ERROR:  "unparted" is not partitioned
 DROP TABLE unparted;
 -- cannot create a permanent rel as partition of a temp rel
 CREATE TEMP TABLE temp_parted (
@@ -526,6 +552,8 @@ CREATE TEMP TABLE temp_parted (
 ) PARTITION BY LIST (a);
 CREATE TABLE fail_part PARTITION OF temp_parted FOR VALUES IN ('a');
 ERROR:  cannot create a permanent relation as partition of temporary relation "temp_parted"
+CREATE TABLE fail_part PARTITION OF temp_parted FOR VALUES WITH (modulus 2, remainder 1);
+ERROR:  cannot create a permanent relation as partition of temporary relation "temp_parted"
 DROP TABLE temp_parted;
 -- cannot create a table with oids as partition of table without oids
 CREATE TABLE no_oids_parted (
@@ -533,6 +561,8 @@ CREATE TABLE no_oids_parted (
 ) PARTITION BY RANGE (a) WITHOUT OIDS;
 CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES FROM (1) TO (10) WITH OIDS;
 ERROR:  cannot create table with OIDs as partition of table without OIDs
+CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES WITH (modulus 2, remainder 1) WITH OIDS;
+ERROR:  cannot create table with OIDs as partition of table without OIDs
 DROP TABLE no_oids_parted;
 -- If the partitioned table has oids, then the partition must have them.
 -- If the WITHOUT OIDS option is specified for partition, it is overridden.
@@ -540,6 +570,10 @@ CREATE TABLE oids_parted (
 	a int
 ) PARTITION BY RANGE (a) WITH OIDS;
 CREATE TABLE part_forced_oids PARTITION OF oids_parted FOR VALUES FROM (1) TO (10) WITHOUT OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES WITH (modulus 2, remainder 1) WITHOUT OIDS;
+ERROR:  invalid bound specification for a range partition
+LINE 1: CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES W...
+                                                        ^
 \d+ part_forced_oids
                              Table "public.part_forced_oids"
  Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
@@ -598,6 +632,23 @@ ERROR:  partition "fail_part" would overlap partition "part12"
 -- more specific ranges
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, unbounded);
 ERROR:  partition "fail_part" would overlap partition "part10"
+-- check for partition bound overlap and other invalid specifications for the hash partition
+CREATE TABLE hash_parted2 (
+	a varchar
+) PARTITION BY HASH (a);
+CREATE TABLE h2part_1 PARTITION OF hash_parted2 FOR VALUES WITH (modulus 4, remainder 2);
+CREATE TABLE h2part_2 PARTITION OF hash_parted2 FOR VALUES WITH (modulus 8, remainder 0);
+CREATE TABLE h2part_3 PARTITION OF hash_parted2 FOR VALUES WITH (modulus 8, remainder 4);
+CREATE TABLE h2part_4 PARTITION OF hash_parted2 FOR VALUES WITH (modulus 8, remainder 5);
+-- overlap with part_4
+CREATE TABLE fail_part PARTITION OF hash_parted2 FOR VALUES WITH (modulus 2, remainder 1);
+ERROR:  partition "fail_part" would overlap partition "h2part_4"
+-- modulus must be greater than zero
+CREATE TABLE fail_part PARTITION OF hash_parted2 FOR VALUES WITH (modulus 0, remainder 1);
+ERROR:  hash partition modulus must be greater than 0
+-- remainder must be greater than or equal to zero and less than modulus
+CREATE TABLE fail_part PARTITION OF hash_parted2 FOR VALUES WITH (modulus 8, remainder 8);
+ERROR:  hash partition modulus must be greater than remainder
 -- check schema propagation from parent
 CREATE TABLE parted (
 	a text,
@@ -684,6 +735,8 @@ Number of partitions: 3 (Use \d+ to list them.)
 
 -- cleanup
 DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3;
+DROP TABLE hash_parted;
+DROP TABLE hash_parted2;
 -- comments on partitioned tables columns
 CREATE TABLE parted_col_comment (a int, b text) PARTITION BY LIST (a);
 COMMENT ON TABLE parted_col_comment IS 'Am partitioned table';
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 6f34b1c..ecad15d 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -313,8 +313,98 @@ select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_p
  part_null     |    |     1 |     1
 (9 rows)
 
+-- direct partition inserts should check hash partition bound constraint
+create table hash_parted (
+	a text,
+	b int
+) partition by hash (a, b);
+create table hpart1 partition of hash_parted for values with (modulus 2, remainder 1);
+create table hpart2 partition of hash_parted for values with (modulus 4, remainder 2);
+create table hpart3 partition of hash_parted for values with (modulus 8, remainder 4);
+-- fail
+insert into hpart1 values ('a', 13);
+ERROR:  new row for relation "hpart1" violates partition constraint
+DETAIL:  Failing row contains (a, 13).
+insert into hpart1 values ('b', 3);
+ERROR:  new row for relation "hpart1" violates partition constraint
+DETAIL:  Failing row contains (b, 3).
+-- ok
+insert into hpart1 values ('b', 1);
+insert into hpart2 values ('c', 1);
+-- fail
+insert into hpart3 values ('b', 21);
+ERROR:  new row for relation "hpart3" violates partition constraint
+DETAIL:  Failing row contains (b, 21).
+insert into hpart3 values ('a', 10);
+ERROR:  new row for relation "hpart3" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+insert into hpart3 values ('c', 6);
+-- fail
+insert into hpart2 values (1);
+ERROR:  new row for relation "hpart2" violates partition constraint
+DETAIL:  Failing row contains (1, null).
+-- ok
+insert into hpart1 values (1);
+-- fail due to no partition found.
+insert into hash_parted values ('c', 5);
+ERROR:  no partition of relation "hash_parted" found for row
+DETAIL:  Partition key of the failing row contains (a, b) = (c, 5).
+insert into hpart1 values (null);
+ERROR:  new row for relation "hpart1" violates partition constraint
+DETAIL:  Failing row contains (null, null).
+-- to fix above error add new partition with (modulus 8, remainder 0) bound
+create table hpart4 partition of hash_parted for values with (modulus 8, remainder 0);
+-- ok
+insert into hash_parted values ('c', 5);
+insert into hpart4 values (null);
 -- cleanup
 drop table range_parted, list_parted;
+drop table hash_parted;
+-- Operator class test
+create or replace function opcl_test(a int4) returns int4 as
+$$ begin return a; end; $$ language 'plpgsql' immutable;
+create operator class custom_opclass for type int4 using hash as
+operator 1 = , function 1 opcl_test(int4);
+create table hash_parted (
+	a int
+) partition by hash (a custom_opclass);
+create table hpart1 partition of hash_parted for values with (modulus 4, remainder 0);
+create table hpart2 partition of hash_parted for values with (modulus 4, remainder 1);
+create table hpart3 partition of hash_parted for values with (modulus 4, remainder 2);
+create table hpart4 partition of hash_parted for values with (modulus 4, remainder 3);
+insert into hash_parted values(generate_series(1,10));
+-- direct insert of values divisible by 4 - ok;
+insert into hpart1 values(12),(16);
+-- fail;
+insert into hpart1 values(11);
+ERROR:  new row for relation "hpart1" violates partition constraint
+DETAIL:  Failing row contains (11).
+-- 11 % 4 -> 3 remainder i.e. valid data for hpart4 partition
+insert into hpart4 values(11);
+-- view data
+select tableoid::regclass as part, a from hash_parted order by part;
+  part  | a  
+--------+----
+ hpart1 |  4
+ hpart1 |  8
+ hpart1 | 12
+ hpart1 | 16
+ hpart2 |  1
+ hpart2 |  5
+ hpart2 |  9
+ hpart3 |  2
+ hpart3 |  6
+ hpart3 | 10
+ hpart4 |  3
+ hpart4 |  7
+ hpart4 | 11
+(13 rows)
+
+-- cleanup
+drop table hash_parted;
+drop operator class custom_opclass using hash;
+drop function opcl_test(int4);
 -- more tests for certain multi-level partitioning scenarios
 create table mlparted (a int, b int) partition by range (a, b);
 create table mlparted1 (b int not null, a int not null) partition by range ((b+0));
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 9366f04..352e87a 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,5 +218,26 @@ ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
 DETAIL:  Failing row contains (b, 9).
 -- ok
 update range_parted set b = b + 1 where b = 10;
+create table hash_parted (
+	a text,
+	b int
+) partition by hash (a, b);
+create table hpart1 partition of hash_parted for values with (modulus 2, remainder 1);
+create table hpart2 partition of hash_parted for values with (modulus 4, remainder 2);
+create table hpart3 partition of hash_parted for values with (modulus 8, remainder 0);
+create table hpart4 partition of hash_parted for values with (modulus 8, remainder 4);
+insert into hpart1 values ('b', 1);
+insert into hpart2 values ('c', 1);
+insert into hpart4 values ('c', 6);
+-- fail
+update hpart1 set a = 'c' where a = 'b';
+ERROR:  new row for relation "hpart1" violates partition constraint
+DETAIL:  Failing row contains (c, 1).
+update hash_parted set b = b - 1 where b = 1;
+ERROR:  new row for relation "hpart1" violates partition constraint
+DETAIL:  Failing row contains (b, 0).
+-- ok
+update hash_parted set b = b + 8 where b = 1;
 -- cleanup
 drop table range_parted;
+drop table hash_parted, hpart1, hpart2, hpart3, hpart4;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 24d1d4d..f6b95ac 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2065,6 +2065,7 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 -- check that the new partition won't overlap with an existing partition
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
 
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
@@ -2150,6 +2151,57 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 ALTER TABLE part_5 ATTACH PARTITION list_parted2 FOR VALUES IN ('b');
 ALTER TABLE list_parted2 ATTACH PARTITION list_parted2 FOR VALUES IN (0);
 
+-- check validation when attaching hash partitions
+
+-- check that the new partition won't overlap with an existing partition
+CREATE TABLE hash_parted (
+	a int NOT NULL,
+	b char(2) COLLATE "C",
+	CONSTRAINT hcheck_a CHECK (a > 0)
+) PARTITION BY HASH (a);
+CREATE TABLE hpart_1 PARTITION OF hash_parted FOR VALUES WITH (modulus 8, remainder 0);
+CREATE TABLE fail_part (LIKE hpart_1 INCLUDING CONSTRAINTS);
+ALTER TABLE hash_parted ATTACH PARTITION fail_part FOR VALUES WITH (modulus 4, remainder 0);
+DROP TABLE fail_part;
+
+-- check validation when attaching hash partitions
+CREATE TABLE hash_parted2 (
+	a int,
+	b char
+) PARTITION BY HASH (a);
+
+-- check that violating rows are correctly reported
+CREATE TABLE hpart_2 (LIKE hash_parted2);
+INSERT INTO hpart_2 VALUES (3, 'a');
+ALTER TABLE hash_parted2 ATTACH PARTITION hpart_2 FOR VALUES WITH (modulus 4, remainder 0);
+
+-- should be ok after deleting the bad row
+DELETE FROM hpart_2;
+ALTER TABLE hash_parted2 ATTACH PARTITION hpart_2 FOR VALUES WITH (modulus 4, remainder 0);
+
+-- check that leaf partitions are scanned when attaching a partitioned
+-- table
+CREATE TABLE hpart_5 (
+	LIKE hash_parted2
+) PARTITION BY LIST (b);
+
+-- check that violating rows are correctly reported
+CREATE TABLE hpart_5_a PARTITION OF hpart_5 FOR VALUES IN ('a');
+INSERT INTO hpart_5_a (a, b) VALUES (6, 'a');
+ALTER TABLE hash_parted2 ATTACH PARTITION hpart_5 FOR VALUES WITH (modulus 4, remainder 2);
+
+-- delete the faulting row and also add a constraint to skip the scan
+DELETE FROM hpart_5_a WHERE a NOT IN (3);
+ALTER TABLE hpart_5 ADD CONSTRAINT hcheck_a CHECK (a IN (5)), ALTER a SET NOT NULL;
+ALTER TABLE hash_parted2 ATTACH PARTITION hpart_5 FOR VALUES WITH (modulus 4, remainder 2);
+
+-- check that the table being attach is with valid modulus and remainder value
+CREATE TABLE fail_part(LIKE hash_parted2);
+ALTER TABLE hash_parted2 ATTACH PARTITION fail_part FOR VALUES WITH (modulus 0, remainder 1);
+ALTER TABLE hash_parted2 ATTACH PARTITION fail_part FOR VALUES WITH (modulus 8, remainder 8);
+ALTER TABLE hash_parted2 ATTACH PARTITION fail_part FOR VALUES WITH (modulus 3, remainder 2);
+DROP TABLE fail_part;
+
 --
 -- DETACH PARTITION
 --
@@ -2161,12 +2213,17 @@ DROP TABLE regular_table;
 
 -- check that the partition being detached exists at all
 ALTER TABLE list_parted2 DETACH PARTITION part_4;
+ALTER TABLE hash_parted2 DETACH PARTITION hpart_4;
 
 -- check that the partition being detached is actually a partition of the parent
 CREATE TABLE not_a_part (a int);
 ALTER TABLE list_parted2 DETACH PARTITION not_a_part;
 ALTER TABLE list_parted2 DETACH PARTITION part_1;
 
+ALTER TABLE hash_parted2 DETACH PARTITION not_a_part;
+ALTER TABLE hash_parted2 DETACH PARTITION hpart_1;
+DROP TABLE not_a_part;
+
 -- check that, after being detached, attinhcount/coninhcount is dropped to 0 and
 -- attislocal/conislocal is set to true
 ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
@@ -2228,6 +2285,8 @@ ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 
 -- cleanup
 DROP TABLE list_parted, list_parted2, range_parted;
+DROP TABLE hash_parted, hpart_1;
+DROP TABLE hash_parted2, hpart_2, hpart_5, hpart_5_a;
 
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 95035c5..361c240 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -350,11 +350,6 @@ CREATE TABLE partitioned (
 ) PARTITION BY RANGE (const_func());
 DROP FUNCTION const_func();
 
--- only accept "list" and "range" as partitioning strategy
-CREATE TABLE partitioned (
-	a int
-) PARTITION BY HASH (a);
-
 -- specified column must be present in the table
 CREATE TABLE partitioned (
 	a int
@@ -451,6 +446,8 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 -- trying to specify range for list partitioned table
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
+-- trying to specify modulus and remainder for list partitioned table
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES WITH (modulus 10, remainder 1);
 
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
@@ -478,6 +475,22 @@ CREATE TABLE range_parted_multicol (a int, b int, c int) PARTITION BY RANGE (a,
 CREATE TABLE fail_part PARTITION OF range_parted_multicol FOR VALUES FROM (1, UNBOUNDED, 1) TO (UNBOUNDED, 1, 1);
 DROP TABLE range_parted_multicol;
 
+-- trying to specify modulus and remainder for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES WITH (modulus 10, remainder 1);
+
+-- check partition bound syntax for the hash partition
+CREATE TABLE hash_parted (
+	a int
+) PARTITION BY HASH (a);
+CREATE TABLE hpart_1 PARTITION OF hash_parted FOR VALUES WITH (modulus 10, remainder 1);
+CREATE TABLE hpart_2 PARTITION OF hash_parted FOR VALUES WITH (modulus 50, remainder 0);
+-- modulus 25 is factor of modulus of 50 but 10 is not factor of 25.
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES WITH (modulus 25, remainder 2);
+-- trying to specify range for hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES FROM ('a', 1) TO ('z');
+-- trying to specify list value for hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES IN (1000);
+
 -- check if compatible with the specified parent
 
 -- cannot create as partition of a non-partitioned table
@@ -485,6 +498,7 @@ CREATE TABLE unparted (
 	a int
 );
 CREATE TABLE fail_part PARTITION OF unparted FOR VALUES IN ('a');
+CREATE TABLE fail_part PARTITION OF unparted FOR VALUES WITH (modulus 2, remainder 1);
 DROP TABLE unparted;
 
 -- cannot create a permanent rel as partition of a temp rel
@@ -492,6 +506,7 @@ CREATE TEMP TABLE temp_parted (
 	a int
 ) PARTITION BY LIST (a);
 CREATE TABLE fail_part PARTITION OF temp_parted FOR VALUES IN ('a');
+CREATE TABLE fail_part PARTITION OF temp_parted FOR VALUES WITH (modulus 2, remainder 1);
 DROP TABLE temp_parted;
 
 -- cannot create a table with oids as partition of table without oids
@@ -499,6 +514,7 @@ CREATE TABLE no_oids_parted (
 	a int
 ) PARTITION BY RANGE (a) WITHOUT OIDS;
 CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES FROM (1) TO (10) WITH OIDS;
+CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES WITH (modulus 2, remainder 1) WITH OIDS;
 DROP TABLE no_oids_parted;
 
 -- If the partitioned table has oids, then the partition must have them.
@@ -507,6 +523,7 @@ CREATE TABLE oids_parted (
 	a int
 ) PARTITION BY RANGE (a) WITH OIDS;
 CREATE TABLE part_forced_oids PARTITION OF oids_parted FOR VALUES FROM (1) TO (10) WITHOUT OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES WITH (modulus 2, remainder 1) WITHOUT OIDS;
 \d+ part_forced_oids
 DROP TABLE oids_parted, part_forced_oids;
 
@@ -558,6 +575,21 @@ CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1,
 -- more specific ranges
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, unbounded);
 
+-- check for partition bound overlap and other invalid specifications for the hash partition
+CREATE TABLE hash_parted2 (
+	a varchar
+) PARTITION BY HASH (a);
+CREATE TABLE h2part_1 PARTITION OF hash_parted2 FOR VALUES WITH (modulus 4, remainder 2);
+CREATE TABLE h2part_2 PARTITION OF hash_parted2 FOR VALUES WITH (modulus 8, remainder 0);
+CREATE TABLE h2part_3 PARTITION OF hash_parted2 FOR VALUES WITH (modulus 8, remainder 4);
+CREATE TABLE h2part_4 PARTITION OF hash_parted2 FOR VALUES WITH (modulus 8, remainder 5);
+-- overlap with part_4
+CREATE TABLE fail_part PARTITION OF hash_parted2 FOR VALUES WITH (modulus 2, remainder 1);
+-- modulus must be greater than zero
+CREATE TABLE fail_part PARTITION OF hash_parted2 FOR VALUES WITH (modulus 0, remainder 1);
+-- remainder must be greater than or equal to zero and less than modulus
+CREATE TABLE fail_part PARTITION OF hash_parted2 FOR VALUES WITH (modulus 8, remainder 8);
+
 -- check schema propagation from parent
 
 CREATE TABLE parted (
@@ -611,6 +643,8 @@ CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
 
 -- cleanup
 DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3;
+DROP TABLE hash_parted;
+DROP TABLE hash_parted2;
 
 -- comments on partitioned tables columns
 CREATE TABLE parted_col_comment (a int, b text) PARTITION BY LIST (a);
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 020854c..d361909 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -185,8 +185,78 @@ insert into list_parted select 'gg', s.a from generate_series(1, 9) s(a);
 insert into list_parted (b) values (1);
 select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_parted group by 1, 2 order by 1;
 
+-- direct partition inserts should check hash partition bound constraint
+create table hash_parted (
+	a text,
+	b int
+) partition by hash (a, b);
+create table hpart1 partition of hash_parted for values with (modulus 2, remainder 1);
+create table hpart2 partition of hash_parted for values with (modulus 4, remainder 2);
+create table hpart3 partition of hash_parted for values with (modulus 8, remainder 4);
+
+-- fail
+insert into hpart1 values ('a', 13);
+insert into hpart1 values ('b', 3);
+-- ok
+insert into hpart1 values ('b', 1);
+insert into hpart2 values ('c', 1);
+-- fail
+insert into hpart3 values ('b', 21);
+insert into hpart3 values ('a', 10);
+-- ok
+insert into hpart3 values ('c', 6);
+
+-- fail
+insert into hpart2 values (1);
+
+-- ok
+insert into hpart1 values (1);
+
+-- fail due to no partition found.
+insert into hash_parted values ('c', 5);
+insert into hpart1 values (null);
+
+-- to fix above error add new partition with (modulus 8, remainder 0) bound
+create table hpart4 partition of hash_parted for values with (modulus 8, remainder 0);
+
+-- ok
+insert into hash_parted values ('c', 5);
+insert into hpart4 values (null);
+
 -- cleanup
 drop table range_parted, list_parted;
+drop table hash_parted;
+
+-- Operator class test
+create or replace function opcl_test(a int4) returns int4 as
+$$ begin return a; end; $$ language 'plpgsql' immutable;
+create operator class custom_opclass for type int4 using hash as
+operator 1 = , function 1 opcl_test(int4);
+
+create table hash_parted (
+	a int
+) partition by hash (a custom_opclass);
+create table hpart1 partition of hash_parted for values with (modulus 4, remainder 0);
+create table hpart2 partition of hash_parted for values with (modulus 4, remainder 1);
+create table hpart3 partition of hash_parted for values with (modulus 4, remainder 2);
+create table hpart4 partition of hash_parted for values with (modulus 4, remainder 3);
+
+insert into hash_parted values(generate_series(1,10));
+
+-- direct insert of values divisible by 4 - ok;
+insert into hpart1 values(12),(16);
+-- fail;
+insert into hpart1 values(11);
+-- 11 % 4 -> 3 remainder i.e. valid data for hpart4 partition
+insert into hpart4 values(11);
+
+-- view data
+select tableoid::regclass as part, a from hash_parted order by part;
+
+-- cleanup
+drop table hash_parted;
+drop operator class custom_opclass using hash;
+drop function opcl_test(int4);
 
 -- more tests for certain multi-level partitioning scenarios
 create table mlparted (a int, b int) partition by range (a, b);
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 6637119..e875192 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,5 +125,24 @@ update range_parted set b = b - 1 where b = 10;
 -- ok
 update range_parted set b = b + 1 where b = 10;
 
+create table hash_parted (
+	a text,
+	b int
+) partition by hash (a, b);
+create table hpart1 partition of hash_parted for values with (modulus 2, remainder 1);
+create table hpart2 partition of hash_parted for values with (modulus 4, remainder 2);
+create table hpart3 partition of hash_parted for values with (modulus 8, remainder 0);
+create table hpart4 partition of hash_parted for values with (modulus 8, remainder 4);
+insert into hpart1 values ('b', 1);
+insert into hpart2 values ('c', 1);
+insert into hpart4 values ('c', 6);
+
+-- fail
+update hpart1 set a = 'c' where a = 'b';
+update hash_parted set b = b - 1 where b = 1;
+-- ok
+update hash_parted set b = b + 8 where b = 1;
+
 -- cleanup
 drop table range_parted;
+drop table hash_parted, hpart1, hpart2, hpart3, hpart4;
-- 
2.6.2

