From b6cf0ef3fe1c0c95277e7b183edc3afb3bea7b4f Mon Sep 17 00:00:00 2001
From: Amul Sul <sulamul@gmail.com>
Date: Wed, 3 May 2017 18:28:00 +0530
Subject: [PATCH] Declarative hash partitioning v2

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         |  52 ++-
 src/backend/catalog/partition.c            | 573 ++++++++++++++++++++++++++---
 src/backend/commands/tablecmds.c           |  63 +++-
 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                  |  47 ++-
 src/backend/parser/parse_utilcmd.c         |  28 +-
 src/backend/utils/adt/ruleutils.c          |  11 +
 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  |  61 +++
 src/test/regress/expected/create_table.out |  66 +++-
 src/test/regress/expected/insert.out       |  46 +++
 src/test/regress/expected/update.out       |  21 ++
 src/test/regress/sql/alter_table.sql       |  59 +++
 src/test/regress/sql/create_table.sql      |  43 ++-
 src/test/regress/sql/insert.sql            |  39 ++
 src/test/regress/sql/update.sql            |  19 +
 22 files changed, 1064 insertions(+), 93 deletions(-)

diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 56ea830..3b5b5f9 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 postal_code
+    ATTACH PARTITION postal_code_p2 FOR VALUES WITH (modulus 256, remainder 1);
+</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..6c9f87a 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 { HASH | RANGE | LIST } ( { <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 { HASH | RANGE | LIST } ( { <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 { HASH | RANGE | LIST } ( { <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">value</replaceable>, REMAINDER <replaceable class="PARAMETER">value</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,28 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
      </para>
 
      <para>
+      When creating a hash partition, <literal>MODULUS</literal> should be
+      greater than zero and <literal>REMAINDER</literal> should be greater than
+      or equal to zero.  Every <literal>MODULUS</literal> must be a factor of
+      the next larger modulus. For example, if you have a bunch of partitions
+      that all have modulus <literal>5</literal>, you can add a new new
+      partition with modulus <literal>10</literal> or a new partition with
+      modulus <literal>15</literal>, but you cannot add both a partition with
+      modulus <literal>10</literal> and a partition with modulus
+      <literal>15</literal>, because <literal>10</literal> is not a factor of
+      <literal>15</literal>.  However, you could simultaneously use modulus
+      <literal>4</literal>, modulus <literal>8</literal>, modulus
+      <literal>16</literal>, and modulus <literal>32</literal> if you wished,
+      because each modulus is a factor of the next larger one.  You could also
+      use modulus <literal>10</literal>, modulus <literal>20</literal>, and
+      modulus <literal>60</literal>. But you could not use modulus
+      <literal>10</literal>, modulus <literal>15</literal>, and modulus
+      <literal>60</literal> because while both of the smaller moduli are factors
+      of <literal>60</literal>, it is not true that each is a factor of the
+      next.
+     </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 +445,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 { HASH | RANGE | LIST } ( { <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 +1614,16 @@ CREATE TABLE cities (
 </programlisting></para>
 
   <para>
+   Create a hash partitioned table:
+<programlisting>
+CREATE TABLE postal_code (
+    code         int not null,
+    city_id      bigint not null,
+    address      text
+) PARTITION BY HASH (code);
+</programlisting></para>
+
+  <para>
    Create partition of a range partitioned table:
 <programlisting>
 CREATE TABLE measurement_y2016m07
@@ -1641,6 +1674,15 @@ 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 partition of a hash partitioned table:
+<programlisting>
+CREATE TABLE postal_code_p1
+    PARTITION OF postal_code (
+    CONSTRAINT eight_digit CHECK (code > 9999999)
+) FOR VALUES WITH(MODULUS 256, REMAINDER 0);
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 8641ae1..0a99433 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -48,6 +48,7 @@
 #include "utils/rel.h"
 #include "utils/ruleutils.h"
 #include "utils/syscache.h"
+#include "utils/typcache.h"
 
 /*
  * Information about bounds of a partitioned relation
@@ -76,16 +77,18 @@ 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 */
 	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) */
+	int		   *indexes;		/* Partition indexes; in case of hash
+								 * partitioned table array length will be
+								 * value of largest modulus, and for others
+								 * one entry per member of the datums array
+								 * (plus one if range partitioned table) */
 	bool		has_null;		/* Is there a null-accepting partition? false
 								 * for range partitioned tables */
 	int			null_index;		/* Index of the null-accepting partition; -1
@@ -97,6 +100,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 +124,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 +153,11 @@ static int partition_bound_bsearch(PartitionKey key,
 						PartitionBoundInfo boundinfo,
 						void *probe, bool probe_is_bound, bool *is_equal);
 
+static uint32 cal_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 +181,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 +250,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;
 
@@ -456,6 +503,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));
+					memset(boundinfo->indexes, -1,
+						   greatest_modulus * sizeof(int));
+
+					for (i = 0; i < nparts; i++)
+					{
+						int		mod = hbounds[i]->modulus,
+								place = hbounds[i]->remainder;
+
+						boundinfo->datums[i] = (Datum *) palloc(2 *
+																sizeof(Datum));
+						boundinfo->datums[i][0] = Int32GetDatum(mod);
+						boundinfo->datums[i][1] = Int32GetDatum(place);
+						next_index = hbounds[i]->index;
+
+						while (place < greatest_modulus)
+						{
+							/* overlap? */
+							Assert(boundinfo->indexes[place] == -1);
+							boundinfo->indexes[place] = next_index;
+							place = place + mod;
+						}
+
+						mapping[i] = i;
+						pfree(hbounds[i]);
+					}
+					pfree(hbounds);
+					break;
+				}
+
 			case PARTITION_STRATEGY_LIST:
 				{
 					boundinfo->has_null = found_null;
@@ -609,53 +692,77 @@ 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;
 
-		for (j = 0; j < key->partnatts; j++)
+		/*
+		 * Hash partition bound stores modulus and remainder at
+		 * b1->datums[i][0] and b1->datums[i][0] position respectively.
+		 */
+		for (i = 0; i < b1->ndatums; i++)
+			if (!(datumIsEqual(b1->datums[i][0], b2->datums[i][0],
+							   true, sizeof(int)) &&
+				  datumIsEqual(b1->datums[i][0], b2->datums[i][0],
+							   true, sizeof(int))))
+				return false;
+
+		/* 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;
 }
 
@@ -677,6 +784,92 @@ 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						place;
+
+					/*
+					 * 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.  However, you could simultaneously
+					 * use modulus 4, modulus 8, modulus 16, and modulus 32 if
+					 * you wished, because each modulus is a factor of the next
+					 * larger one.  You could also use modulus 10, modulus 20,
+					 * and modulus 60. But you could not use modulus 10,
+					 * modulus 15, and modulus 60 for the same reason.
+					 */
+					{
+						int			offset;
+						bool		equal,
+									valid_bound = true;
+						int			pmod, /* Previous largest modulus */
+									nmod; /* Next largest modulus */
+
+						/*
+						 * 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("invalid bound specification for a hash partition"),
+									 errhint("every modulus must be factor of next largest modulus")));
+					}
+
+					greatest_modulus = DatumGetInt32(datums[ndatums - 1][0]);
+					place = spec->remainder;
+
+					if (place >= greatest_modulus)
+						place = place % greatest_modulus;
+
+					do
+					{
+						if (boundinfo->indexes[place] != -1)
+						{
+							overlap = true;
+							with = boundinfo->indexes[place];
+							break;
+						}
+						place = place + spec->modulus;
+					} while (place < greatest_modulus);
+				}
+
+				break;
+			}
+
 		case PARTITION_STRATEGY_LIST:
 			{
 				Assert(spec->strategy == PARTITION_STRATEGY_LIST);
@@ -899,6 +1092,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);
@@ -1146,6 +1344,97 @@ 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 (pkey int, value char(10))
+ *  PARTITION BY HASH (pkey, value);
+ *
+ * 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, pkey, value)
+ *   for p_p2: satisfies_hash_partition(4, 2, pkey, value)
+ *   for p_p3: satisfies_hash_partition(8, 0, pkey, value)
+ *   for p_p4: satisfies_hash_partition(8, 4, pkey, value)
+ */
+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);
+		}
+		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.
@@ -1738,29 +2027,58 @@ get_partition_for_tuple(PartitionDispatch *pd,
 			cur_index = partdesc->boundinfo->null_index;
 		else if (!isnull[0])
 		{
-			/* Else bsearch in partdesc->boundinfo */
-			bool		equal = false;
-
-			cur_offset = partition_bound_bsearch(key, partdesc->boundinfo,
-												 values, false, &equal);
 			switch (key->strategy)
 			{
+				case PARTITION_STRATEGY_HASH:
+					{
+						PartitionBoundInfo boundinfo = partdesc->boundinfo;
+						int		ndatums = boundinfo->ndatums;
+						Datum	datum = boundinfo->datums[ndatums - 1][0];
+						int		modulus = DatumGetInt32(datum);
+						uint32	rowHash = cal_hash_value(key->partsupfunc,
+														 key->partnatts,
+														 values, isnull);
+
+						cur_index = boundinfo->indexes[rowHash % modulus];
+						break;
+					}
+
 				case PARTITION_STRATEGY_LIST:
-					if (cur_offset >= 0 && equal)
-						cur_index = partdesc->boundinfo->indexes[cur_offset];
-					else
-						cur_index = -1;
-					break;
+					{
+						/* bsearch in partdesc->boundinfo */
+						bool		equal = false;
+
+						cur_offset = partition_bound_bsearch(key,
+															 partdesc->boundinfo,
+															 values,
+															 false,
+															 &equal);
+						if (cur_offset >= 0 && equal)
+							cur_index = partdesc->boundinfo->indexes[cur_offset];
+						else
+							cur_index = -1;
+						break;
+					}
 
 				case PARTITION_STRATEGY_RANGE:
+					{
+						bool		equal = false;
 
-					/*
-					 * Offset returned is such that the bound at offset is
-					 * found to be less or equal with the tuple. So, the bound
-					 * at offset+1 would be the upper bound.
-					 */
-					cur_index = partdesc->boundinfo->indexes[cur_offset + 1];
-					break;
+						/* bsearch in partdesc->boundinfo */
+						cur_offset = partition_bound_bsearch(key,
+															 partdesc->boundinfo,
+															 values,
+															 false,
+															 &equal);
+
+						/*
+						 * Offset returned is such that the bound at offset is
+						 * found to be less or equal with the tuple. So, the bound
+						 * at offset+1 would be the upper bound.
+						 */
+						cur_index = partdesc->boundinfo->indexes[cur_offset + 1];
+						break;
+					}
 
 				default:
 					elog(ERROR, "unexpected partition strategy: %d",
@@ -1794,6 +2112,27 @@ get_partition_for_tuple(PartitionDispatch *pd,
 }
 
 /*
+ * Used when sorting hash bounds across all hash modulus
+ * for hash partitioning
+ */
+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
@@ -1970,6 +2309,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],
@@ -2053,3 +2411,110 @@ partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
 
 	return lo;
 }
+
+/*
+ * Compute the hash value for given partition column values.
+ */
+static uint32
+cal_hash_value(FmgrInfo *partsupfunc, 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])
+		{
+			Datum       colHash;
+
+			Assert(OidIsValid(partsupfunc[i].fn_oid));
+
+			colHash = FunctionCall1(&partsupfunc[i], values[i]);
+			rowHash ^= DatumGetUInt32(colHash);
+		}
+	}
+
+	return rowHash;
+}
+
+/*
+ * 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)
+{
+	typedef struct ColumnsHashData
+	{
+		int			n;			/* allocated length of typentry[] */
+		TypeCacheEntry *typentry[PARTITION_MAX_KEYS];
+	}			ColumnsHashData;
+	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];
+	FmgrInfo	partsupfunc[PARTITION_MAX_KEYS];
+	ColumnsHashData *my_extra;
+	uint32		rowHash = 0;
+
+	/*
+	 * Cache hash function information, similar to how record_eq() caches
+	 * equality operator information.  (Perhaps no SQL syntax could cause
+	 * PG_NARGS()/nkeys to change between calls through the same FmgrInfo.
+	 * Checking nkeys here is just defensiveness.)
+	 */
+	my_extra = (ColumnsHashData *) fcinfo->flinfo->fn_extra;
+	if (my_extra == NULL || my_extra->n != nkeys)
+	{
+		fcinfo->flinfo->fn_extra =
+			MemoryContextAllocZero(fcinfo->flinfo->fn_mcxt,
+								   offsetof(ColumnsHashData, typentry) +
+								   sizeof(TypeCacheEntry *) * nkeys);
+		my_extra = (ColumnsHashData *) fcinfo->flinfo->fn_extra;
+		my_extra->n = nkeys;
+	}
+
+	/* Get TypeCacheEntry for each partition column. */
+	for (i = 0; i < nkeys; i++)
+	{
+		/* keys start from third argument of function. */
+		if (!PG_ARGISNULL(i + 2))
+		{
+			Oid			valtype;
+
+			valtype = get_fn_expr_argtype(fcinfo->flinfo, (i + 2));
+			if (!OidIsValid(valtype))
+				elog(ERROR, "could not determine data type of satisfies_hash_partition() input");
+
+			/* Get the hash function. */
+			if (my_extra->typentry[i] == NULL ||
+				my_extra->typentry[i]->type_id != valtype)
+			{
+				my_extra->typentry[i] =
+					lookup_type_cache(valtype, TYPECACHE_HASH_PROC_FINFO);
+				if (!OidIsValid(my_extra->typentry[i]->hash_proc_finfo.fn_oid))
+					ereport(ERROR,
+							(errcode(ERRCODE_UNDEFINED_FUNCTION),
+							 errmsg("could not identify a hash function for type %s",
+									format_type_be(valtype))));
+			}
+
+			values[i] = PG_GETARG_DATUM(i + 2);
+			isnull[i] = false;
+			partsupfunc[i] = my_extra->typentry[i]->hash_proc_finfo;
+		}
+		else
+			isnull[i] = true;
+	}
+
+	rowHash = cal_hash_value(partsupfunc, nkeys, values, isnull);
+
+	PG_RETURN_BOOL(rowHash % modulus == remainder);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cdcb949..a14c8f9 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -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);
+					  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,
@@ -823,7 +823,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 												&strategy);
 		ComputePartitionAttrs(rel, stmt->partspec->partParams,
 							  partattrs, &partexprs, partopclass,
-							  partcollation);
+							  partcollation, strategy);
 
 		partnatts = list_length(stmt->partspec->partParams);
 		StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs,
@@ -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),
@@ -13220,7 +13222,8 @@ transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
  */
 static void
 ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
-					  List **partexprs, Oid *partopclass, Oid *partcollation)
+					  List **partexprs, Oid *partopclass, Oid *partcollation,
+					  char strategy)
 {
 	int			attn;
 	ListCell   *lc;
@@ -13365,27 +13368,49 @@ 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.
-		 */
-		if (!pelem->opclass)
+		if (strategy == PARTITION_STRATEGY_HASH)
 		{
-			partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID);
+			/* Identify a hash opclass to use */
+			if (!pelem->opclass)
+			{
+				partopclass[attn] = GetDefaultOpClass(atttype, HASH_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.")));
+				if (!OidIsValid(partopclass[attn]))
+					ereport(ERROR,
+							(errcode(ERRCODE_UNDEFINED_OBJECT),
+							 errmsg("data type %s has no default hash operator class",
+									format_type_be(atttype)),
+							 errhint("You must specify a hash operator class or define a default hash operator class for the data type.")));
+			}
+			else
+				partopclass[attn] = ResolveOpClass(pelem->opclass,
+												   atttype,
+												   "hash",
+												   HASH_AM_OID);
 		}
 		else
-			partopclass[attn] = ResolveOpClass(pelem->opclass,
-											   atttype,
-											   "btree",
-											   BTREE_AM_OID);
+		{
+			/*
+			 * Identify a btree opclass to use. Currently, we use only btree
+			 * operators, which seems enough for list and range partitioning.
+			 */
+			if (!pelem->opclass)
+			{
+				partopclass[attn] = GetDefaultOpClass(atttype, BTREE_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.")));
+			}
+			else
+				partopclass[attn] = ResolveOpClass(pelem->opclass,
+												   atttype,
+												   "btree",
+												   BTREE_AM_OID);
+		}
 		attn++;
 	}
 }
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 35a237a..14bc6d7 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 21dfbb0..c6c2b6a 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 818d2c2..b5a6ad2 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.
@@ -2645,8 +2646,36 @@ 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;
+
+					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)));
+					}
+
+					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);
 
@@ -2671,6 +2700,20 @@ ForValues:
 				}
 		;
 
+hash_partbound_elem:
+		NonReservedWord Iconst
+			{
+				$$ = makeDefElem($1, (Node *)makeInteger($2), @1);
+			}
+		;
+
+hash_partbound:
+		hash_partbound_elem ',' hash_partbound_elem
+			{
+				$$ = list_make2($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 e187409..7aed36d 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3282,7 +3282,33 @@ 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("invalid bound specification for a hash partition"),
+					 errhint("modulus must be greater than zero")));
+
+		if (spec->remainder < 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("invalid bound specification for a hash partition"),
+					 errhint("remainder must be greater than or equal to zero")));
+
+		if (spec->remainder >= spec->modulus)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("invalid bound specification for a hash partition"),
+					 errhint("modulus must be greater than remainder")));
+	}
+	else if (strategy == PARTITION_STRATEGY_LIST)
 	{
 		ListCell   *cell;
 		char	   *colname;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index cbde1ff..af0d32b 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -1653,6 +1653,9 @@ pg_get_partkeydef_worker(Oid relid, int prettyFlags,
 
 	switch (form->partstrat)
 	{
+		case PARTITION_STRATEGY_HASH:
+			appendStringInfo(&buf, "HASH");
+			break;
 		case PARTITION_STRATEGY_LIST:
 			if (!attrsOnly)
 				appendStringInfo(&buf, "LIST");
@@ -8619,6 +8622,14 @@ get_rule_expr(Node *node, deparse_context *context,
 
 				switch (spec->strategy)
 				{
+					case PARTITION_STRATEGY_HASH:
+						Assert(spec->modulus > 0 && spec->remainder >= 0);
+
+						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 e2a3351..195ea16 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2006,7 +2006,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 82562ad..660e8bd 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 e1d454a..4e8eb4f 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..5f0e6a8 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,57 @@ 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 list 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:  invalid bound specification for a hash partition
+HINT:  modulus must be greater than zero
+ALTER TABLE hash_parted2 ATTACH PARTITION fail_part FOR VALUES WITH (modulus 8, remainder 8);
+ERROR:  invalid bound specification for a hash partition
+HINT:  modulus must be greater than remainder
+ALTER TABLE hash_parted2 ATTACH PARTITION fail_part FOR VALUES WITH (modulus 3, remainder 2);
+ERROR:  invalid bound specification for a hash partition
+HINT:  every modulus must be factor of next largest modulus
+DROP TABLE fail_part;
 --
 -- DETACH PARTITION
 --
@@ -3287,12 +3339,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 +3434,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 dda0d7e..50a1b08 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
@@ -505,6 +505,31 @@ ERROR:  TO must specify exactly one value per partitioning column
 -- cannot specify null values in range bounds
 CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
 ERROR:  cannot specify NULL in range bound
+-- 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:  invalid bound specification for a hash partition
+HINT:  every modulus must be factor of next largest 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 (
@@ -512,6 +537,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 (
@@ -519,6 +546,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 (
@@ -526,6 +555,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.
@@ -533,6 +564,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 
@@ -591,6 +626,25 @@ 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:  invalid bound specification for a hash partition
+HINT:  modulus must be greater than zero
+-- 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:  invalid bound specification for a hash partition
+HINT:  modulus must be greater than remainder
 -- check schema propagation from parent
 CREATE TABLE parted (
 	a text,
@@ -677,6 +731,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, hpart_1, hpart_2;
+DROP TABLE hash_parted2, h2part_1, h2part_2, h2part_3, h2part_4;
 -- 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..fbfec10 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -313,8 +313,54 @@ 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 (HASHa, 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, hpart1, hpart2, hpart3, hpart4;
 -- 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..bcdb036 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 list 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 caf5ddb..3f1745f 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 (
@@ -472,6 +469,21 @@ CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z',
 
 -- cannot specify null values in range bounds
 CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
+-- 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
 
@@ -480,6 +492,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
@@ -487,6 +500,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
@@ -494,6 +508,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.
@@ -502,6 +517,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;
 
@@ -553,6 +569,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 (
@@ -606,6 +637,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, hpart_1, hpart_2;
+DROP TABLE hash_parted2, h2part_1, h2part_2, h2part_3, h2part_4;
 
 -- 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..1ae8973 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -185,8 +185,47 @@ 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, hpart1, hpart2, hpart3, hpart4;
 
 -- 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

