v40-0003-Faster-partition-pruning.patch

text/plain

Filename: v40-0003-Faster-partition-pruning.patch
Type: text/plain
Part: 2
Message: Re: [HACKERS] path toward faster partition pruning

Patch

Format: format-patch
Series: patch v40-0003
Subject: Faster partition pruning
File+
src/backend/catalog/partition.c 1020 0
src/backend/nodes/copyfuncs.c 52 0
src/backend/nodes/nodeFuncs.c 61 0
src/backend/optimizer/path/allpaths.c 28 0
src/backend/optimizer/util/Makefile 1 1
src/backend/optimizer/util/partprune.c 1471 0
src/backend/optimizer/util/plancat.c 28 16
src/backend/optimizer/util/relnode.c 8 0
src/include/catalog/partition.h 25 0
src/include/catalog/pg_opfamily.h 3 0
src/include/nodes/nodes.h 4 0
src/include/nodes/primnodes.h 92 0
src/include/nodes/relation.h 4 0
src/include/optimizer/partprune.h 23 0
src/test/regress/expected/inherit.out 3 1
src/test/regress/expected/partition_prune.out 251 67
src/test/regress/sql/partition_prune.sql 38 1
src/tools/pgindent/typedefs.list 6 0
From ae3fdea538b01414e23225b0ef356582b062e83e Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 22 Aug 2017 13:48:13 +0900
Subject: [PATCH v40 3/4] Faster partition pruning

This adds a new module partprune.c in the optimizer, which is meant
as a replacement for using constraint exclusion to prune individual
partitions.  The new module performs partition pruning using the
information contained in parent/partitioned table's boundinfo, after
extracting clauses that involve partition keys.

With the new module's functionality in place, set_append_rel_size()
calls prune_append_rel_partitions() to get a Bitmapset of partitions
that need to be scanned and processes only the partitions contained
in the set.

Authors: Amit Langote,
         David Rowley (david.rowley@2ndquadrant.com),
         Dilip Kumar (dilipbalaut@gmail.com)
---
 src/backend/catalog/partition.c               | 1020 +++++++++++++++++
 src/backend/nodes/copyfuncs.c                 |   52 +
 src/backend/nodes/nodeFuncs.c                 |   61 +
 src/backend/optimizer/path/allpaths.c         |   28 +
 src/backend/optimizer/util/Makefile           |    2 +-
 src/backend/optimizer/util/partprune.c        | 1471 +++++++++++++++++++++++++
 src/backend/optimizer/util/plancat.c          |   44 +-
 src/backend/optimizer/util/relnode.c          |    8 +
 src/include/catalog/partition.h               |   25 +
 src/include/catalog/pg_opfamily.h             |    3 +
 src/include/nodes/nodes.h                     |    4 +
 src/include/nodes/primnodes.h                 |   92 ++
 src/include/nodes/relation.h                  |    4 +
 src/include/optimizer/partprune.h             |   23 +
 src/test/regress/expected/inherit.out         |    4 +-
 src/test/regress/expected/partition_prune.out |  318 ++++--
 src/test/regress/sql/partition_prune.sql      |   39 +-
 src/tools/pgindent/typedefs.list              |    6 +
 18 files changed, 3118 insertions(+), 86 deletions(-)
 create mode 100644 src/backend/optimizer/util/partprune.c
 create mode 100644 src/include/optimizer/partprune.h

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 53855f5088..0e47d9a0de 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -193,6 +193,30 @@ static int	get_greatest_modulus(PartitionBoundInfo b);
 static uint64 compute_hash_value(int partnatts, FmgrInfo *partsupfunc,
 								 Datum *values, bool *isnull);
 
+static Bitmapset *perform_pruning_step(PartitionPruneContext *context,
+					 PartitionPruneStep *step);
+static Bitmapset *perform_pruning_base_step(PartitionPruneContext *context,
+						  PartitionPruneStepOp *opstep);
+static Bitmapset *perform_pruning_base_step_ne(PartitionPruneContext *context,
+							 PartitionPruneStepOpNe *nestep);
+static Bitmapset *perform_pruning_combine_step(PartitionPruneContext *context,
+							 PartitionPruneStepCombine *cstep);
+static bool partkey_datum_from_expr(PartitionPruneContext *context,
+						Expr *expr, Datum *value);
+static Bitmapset *get_partitions_for_keys_hash(PartitionPruneContext *context,
+							 int opstrategy, Datum *values, int nvalues,
+							 FmgrInfo *partsupfunc, Bitmapset *nullkeys);
+static Bitmapset *get_partitions_for_keys_list(PartitionPruneContext *context,
+							 int opstrategy, Datum value, int nvalues,
+							 FmgrInfo *partsupfunc, Bitmapset *nullkeys);
+static Bitmapset *get_partitions_for_keys_range(
+							  PartitionPruneContext *context, int opstrategy,
+							  Datum *values, int nvalues,
+							  FmgrInfo *partsupfunc, Bitmapset *nullkeys);
+static Bitmapset *get_partitions_excluded_by_ne_datums(PartitionPruneContext *context,
+									 Datum *ne_datums, int n_ne_datums,
+									 FmgrInfo **partsupfunc);
+
 /*
  * RelationBuildPartitionDesc
  *		Form rel's partition descriptor
@@ -1553,9 +1577,1005 @@ get_partition_qual_relid(Oid relid)
 	return result;
 }
 
+/*
+ * get_matching_partitions
+ *		Determine partitions that survive partition pruning steps
+ *
+ * Returns a Bitmapset of indexes of surviving partitions.
+ */
+Bitmapset *
+get_matching_partitions(PartitionPruneContext *context,
+						List *pruning_steps)
+{
+	/* If there are no pruning steps then all partitions match. */
+	if (pruning_steps == NIL)
+		return bms_add_range(NULL, 0, context->nparts - 1);
+	else
+	{
+		Bitmapset  *result = NULL;
+		ListCell   *lc;
+		bool		firststep = true;
+
+		/*
+		 * Below we process each partition pruning step one by one.  With each
+		 * step we the intersect the result with the previously taken steps so
+		 * that we end up with a minimal set of matching partition indexes.
+		 * When performing the first step, we take the entire result, so we've
+		 * something to intersect on subsequent steps.
+		 */
+
+		foreach(lc, pruning_steps)
+		{
+			PartitionPruneStep *step = lfirst(lc);
+			Bitmapset  *stepresult;
+
+			stepresult = perform_pruning_step(context, step);
+
+			if (firststep)
+			{
+				result = stepresult;
+				firststep = false;
+			}
+			else
+				result = bms_int_members(result, stepresult);
+		}
+		return result;
+	}
+}
+
 /* Module-local functions */
 
 /*
+ * perform_pruning_step
+ *		Performs one PartitionPruneStep
+ */
+static Bitmapset *
+perform_pruning_step(PartitionPruneContext *context,
+					 PartitionPruneStep *step)
+{
+	switch (nodeTag(step))
+	{
+		case T_PartitionPruneStepOp:
+			return perform_pruning_base_step(context,
+											 (PartitionPruneStepOp *) step);
+
+		case T_PartitionPruneStepOpNe:
+			return perform_pruning_base_step_ne(context,
+											(PartitionPruneStepOpNe *) step);
+
+		case T_PartitionPruneStepCombine:
+			return perform_pruning_combine_step(context,
+												(PartitionPruneStepCombine *) step);
+
+		default:
+			elog(ERROR, "invalid partition pruning step: %d", nodeTag(step));
+			return NULL;		/* keep compiler quiet */
+	}
+}
+
+/*
+ * perform_pruning_base_step
+ *		Returns indexes of partitions obtained by executing 'opstep'.
+ */
+static Bitmapset *
+perform_pruning_base_step(PartitionPruneContext *context,
+						  PartitionPruneStepOp *opstep)
+{
+	ListCell   *lc1,
+			   *lc2;
+	int			keyno,
+				nvalues;
+	Datum		values[PARTITION_MAX_KEYS];
+	FmgrInfo	partsupfunc[PARTITION_MAX_KEYS];
+
+	nvalues = 0;
+	lc1 = list_head(opstep->exprs);
+	lc2 = list_head(opstep->cmpfns);
+
+	/*
+	 * Generate the partition look-up key that will be used by one of
+	 * get_partitions_from_keys_* functions called below.
+	 */
+	for (keyno = 0; keyno < context->partnatts; keyno++)
+	{
+		/*
+		 * For hash partitioning, it is possible that values of some keys are
+		 * not provided in operator clauses, but instead the planner found
+		 * that they appeared in a IS NULL clause.
+		 */
+		if (bms_is_member(keyno, opstep->nullkeys))
+			continue;
+
+		/*
+		 * For range partitioning, we must only perform pruning with values
+		 * for either all partition keys or a prefix thereof.
+		 */
+		if (keyno > nvalues && context->strategy == PARTITION_STRATEGY_RANGE)
+			break;
+
+		if (lc1 != NULL)
+		{
+			Expr	   *expr;
+			Datum		datum;
+
+			expr = lfirst(lc1);
+			if (partkey_datum_from_expr(context, expr, &datum))
+			{
+				Oid		cmpfn;
+
+				/*
+				 * If we're going to need a different comparison function
+				 * than the one cached in the PartitionKey, we'll need to
+				 * look up the FmgrInfo.
+				 */
+				if (lc2 == NULL)
+					elog(ERROR, "incomplete cmpfnids list in pruning step");
+				cmpfn = lfirst_oid(lc2);
+				Assert(OidIsValid(cmpfn));
+				if (cmpfn != context->partsupfunc[keyno].fn_oid)
+					fmgr_info(cmpfn, &partsupfunc[keyno]);
+				else
+					partsupfunc[keyno] = context->partsupfunc[keyno];
+
+				values[keyno] = datum;
+				nvalues++;
+			}
+
+			lc1 = lnext(lc1);
+			lc2 = lnext(lc2);
+		}
+	}
+
+	switch (context->strategy)
+	{
+		case PARTITION_STRATEGY_HASH:
+			return get_partitions_for_keys_hash(context,
+												opstep->opstrategy,
+												values, nvalues,
+												partsupfunc,
+												opstep->nullkeys);
+		case PARTITION_STRATEGY_LIST:
+			return get_partitions_for_keys_list(context,
+												opstep->opstrategy,
+												values[0], nvalues,
+												&partsupfunc[0],
+												opstep->nullkeys);
+		case PARTITION_STRATEGY_RANGE:
+			return get_partitions_for_keys_range(context,
+												 opstep->opstrategy,
+												 values, nvalues,
+												 partsupfunc,
+												 opstep->nullkeys);
+
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+				 (int) context->strategy);
+			return NULL;
+	}
+}
+
+/*
+ * perform_pruning_base_step_ne
+ *		Returns indexes of partitions obtained by executing 'nestep'.
+ */
+static Bitmapset *
+perform_pruning_base_step_ne(PartitionPruneContext *context,
+							 PartitionPruneStepOpNe *nestep)
+{
+	Bitmapset  *excluded_parts,
+			   *result;
+	Datum	   *ne_datums;
+	FmgrInfo  **partsupfunc;
+	ListCell   *lc1;
+	ListCell   *lc2;
+	int			n_ne_datums = list_length(nestep->exprs),
+				i;
+
+	/*
+	 * Apply not-equal clauses.  This only applies in the list
+	 * partitioning case as this is the only partition type where
+	 * we have knowledge of the entire set of values that can be
+	 * stored in a given partition.
+	 */
+	ne_datums = (Datum *) palloc(n_ne_datums * sizeof(Datum));
+
+	/*
+	 * Some datums may require different comparison function than
+	 * the default partitioning-specific one.
+	 */
+	partsupfunc = (FmgrInfo **)
+	palloc(n_ne_datums * sizeof(FmgrInfo *));
+
+	i = 0;
+	forboth(lc1, nestep->exprs, lc2, nestep->cmpfns)
+	{
+		Expr	   *expr = (Expr *) lfirst(lc1);
+		Oid			cmpfn = lfirst_oid(lc2);
+		Datum		datum;
+
+		/*
+		 * Note that we're passing 0 for partkeyidx, because there
+		 * can be only one partition key with list partitioning.
+		 */
+		if (partkey_datum_from_expr(context, expr, &datum))
+		{
+			/*
+			 * If this datum is not the same type as the partition
+			 * key then we'll need to use the comparison function
+			 * for that type.  We'll need to lookup the FmgrInfo.
+			 */
+			if (cmpfn != context->partsupfunc[0].fn_oid)
+			{
+				partsupfunc[i] = (FmgrInfo *) palloc(sizeof(FmgrInfo));
+				fmgr_info(cmpfn, partsupfunc[i]);
+			}
+			else
+				partsupfunc[i] = &context->partsupfunc[0];
+		}
+
+		ne_datums[i++] = datum;
+	}
+
+	excluded_parts = get_partitions_excluded_by_ne_datums(context, ne_datums, i,
+													  partsupfunc);
+	pfree(ne_datums);
+
+	/* All partitions apart from those in excluded_parts match */
+	result = bms_add_range(NULL, 0, context->nparts - 1);
+	return bms_del_members(result, excluded_parts);
+}
+
+
+
+/*
+ * perform_pruning_combine_step
+ *		Returns indexes of partitions obtained by executing 'cstep'.
+ */
+static Bitmapset *
+perform_pruning_combine_step(PartitionPruneContext *context,
+							 PartitionPruneStepCombine *cstep)
+{
+	Bitmapset  *result;
+	ListCell   *lc;
+
+	/*
+	 * In some cases, planner generates a combine step that doesn't contain
+	 * any argument steps, to signal us to not prune any partitions.  So,
+	 * return all partitions in that case.
+	 */
+	if (cstep->argsteps == NIL)
+			return bms_add_range(NULL, 0, context->nparts - 1);
+
+	switch (cstep->combineOp)
+	{
+		case COMBINE_OR:
+			{
+				result = NULL;
+				foreach(lc, cstep->argsteps)
+				{
+					PartitionPruneStep *step = lfirst(lc);
+					Bitmapset  *argresult;
+
+					/*
+					 * We need to recurse here to account for the fact that
+					 * the argument's pruning step could arbitrarily be of
+					 * any type.
+					 */
+					argresult = perform_pruning_step(context, step);
+
+					/* Add argresult to result. */
+					result = bms_add_members(result, argresult);
+				}
+
+				return result;
+			}
+
+		case COMBINE_AND:
+			{
+				bool		firststep;
+
+				firststep = true;
+				result = NULL;
+				foreach(lc, cstep->argsteps)
+				{
+					PartitionPruneStep *step = lfirst(lc);
+					Bitmapset  *argresult;
+
+					argresult = perform_pruning_step(context, step);
+
+					if (firststep)
+					{
+						result = argresult;
+						firststep = false;
+					}
+					else
+						result = bms_int_members(result, argresult);
+				}
+
+				return result;
+			}
+
+		default:
+			elog(ERROR, "invalid PartitionPruneCombineOp: %d", (int)
+				 cstep->combineOp);
+			return NULL;		/* keep compiler quiet */
+	}
+}
+
+/*
+ * partkey_datum_from_expr
+ *		Set *value to the constant value if 'expr' provides one
+ */
+static bool
+partkey_datum_from_expr(PartitionPruneContext *context,
+						Expr *expr, Datum *value)
+{
+	switch (nodeTag(expr))
+	{
+		case T_Const:
+			*value = ((Const *) expr)->constvalue;
+			return true;
+
+		default:
+			break;
+	}
+
+	return false;
+}
+
+/*
+ * get_partitions_for_keys_hash
+ *		Determine the minimum set of partitions matching the specified values
+ *		using hash partitioning.
+ *
+ * 'nvalues', if non-zero, denotes the number of values contained in 'values'
+ * 'values' contains values to be used for pruning appearing in the array in
+ * respective partition key position.
+ * 'opstrategy' if non-zero must be HTEqualStrategyNumber.
+ * 'partsupfunc' contains partition hashing functions that can produce correct
+ * hash for the type of the values contained in 'values'
+ * 'nullkeys' is the set of partition keys that are null.
+ */
+static Bitmapset *
+get_partitions_for_keys_hash(PartitionPruneContext *context,
+							 int opstrategy, Datum *values, int nvalues,
+							 FmgrInfo *partsupfunc, Bitmapset *nullkeys)
+{
+	PartitionBoundInfo boundinfo = context->boundinfo;
+	int		   *partindices = boundinfo->indexes;
+	int			partnatts = context->partnatts;
+	bool		isnull[PARTITION_MAX_KEYS];
+	int			i;
+	uint64		rowHash;
+	int			greatest_modulus,
+				result_index;
+
+	Assert(context->strategy == PARTITION_STRATEGY_HASH);
+	/*
+	 * For hash partitioning we can only perform pruning based on equality
+	 * clauses to the partition key or IS NULL clauses.  We also can only
+	 * prune if we have such clauses for all keys, which the planner must have
+	 * found or we wouldn't have gotten here.
+	 */
+	Assert(nvalues + bms_num_members(nullkeys) == partnatts);
+
+	/*
+	 * If there are any values, they must have come from clauses containing
+	 * an equality operator compatible with hash partitioning.
+	 */
+	Assert(opstrategy == HTEqualStrategyNumber || nvalues == 0);
+
+	for (i = 0; i < partnatts; i++)
+		isnull[i] = bms_is_member(i, nullkeys);
+
+	greatest_modulus = get_greatest_modulus(boundinfo);
+	rowHash = compute_hash_value(partnatts, partsupfunc, values, isnull);
+	result_index = partindices[rowHash % greatest_modulus];
+
+	if (result_index >= 0)
+		return bms_make_singleton(result_index);
+
+	return NULL;
+}
+
+/*
+ * get_partitions_for_keys_list
+ *		Determine the minimum set of partitions matching the specified values
+ *		using list partitioning.
+ *
+ * 'nvalues', if non-zero, should be exactly 1, because list partitioning.
+ * 'value' contains the value to use for pruning
+ * 'opstrategy' if non-zero must be a btree strategy number
+ * 'partsupfunc' contains list partitioning comparison function to be used to
+ * perform partition_list_bsearch
+ * 'nullkeys' is the set of partition keys that are null.
+ */
+static Bitmapset *
+get_partitions_for_keys_list(PartitionPruneContext *context,
+							 int opstrategy, Datum value, int nvalues,
+							 FmgrInfo *partsupfunc, Bitmapset *nullkeys)
+{
+	PartitionBoundInfo boundinfo = context->boundinfo;
+	int		   *partindices = boundinfo->indexes;
+	int			off,
+				minoff,
+				maxoff,
+				i;
+	bool		is_equal;
+	bool		inclusive = false;
+	Bitmapset  *result;
+	int			partnatts = context->partnatts;
+	int			default_index = boundinfo->default_index;
+	Oid		   *partcollation = context->partcollation;
+
+	Assert(context->strategy == PARTITION_STRATEGY_LIST);
+	Assert(partnatts == 1);
+
+	if (!bms_is_empty(nullkeys))
+	{
+		/*
+		 * Nulls may exist in only one partition - the partition whose
+		 * accepted set of values includes null or the default partition if
+		 * the former doesn't exist.
+		 */
+		if (partition_bound_accepts_nulls(boundinfo))
+			return bms_make_singleton(boundinfo->null_index);
+		else if (partition_bound_has_default(boundinfo))
+			return bms_make_singleton(default_index);
+		else
+			return NULL;
+	}
+
+	/*
+	 * If there are no datums to compare keys with, but there are partitions,
+	 * just return the default partition if one exists.
+	 */
+	if (boundinfo->ndatums == 0)
+	{
+		if (partition_bound_has_default(boundinfo))
+			return bms_make_singleton(default_index);
+		else
+			return NULL;		/* shouldn't happen */
+	}
+
+	minoff = 0;
+	maxoff = boundinfo->ndatums - 1;
+
+	/*
+	 * With range queries, always include the default list partition, because
+	 * list partitions divide the key space in a discontinuous manner, not all
+	 * values in the given range will have a partition assigned.  This may not
+	 * technically be true for some data types (e.g. integer types), however,
+	 * we currently lack any sort of infrastructure to provide us with proofs
+	 * that would allow us to do anything smarter here.
+	 */
+	if (opstrategy != BTEqualStrategyNumber &&
+		partition_bound_has_default(boundinfo))
+		result = bms_make_singleton(default_index);
+	else
+		result = NULL;
+
+	if (nvalues == 0)
+	{
+		/*
+		 * Add indexes of *all* partitions containing non-null values and
+		 * return.
+		 */
+		for (i = minoff; i <= maxoff; i++)
+			result = bms_add_member(result, partindices[i]);
+
+		return result;
+	}
+
+	switch (opstrategy)
+	{
+		case BTEqualStrategyNumber:
+			off = partition_list_bsearch(partsupfunc,
+										 partcollation,
+										 boundinfo, value,
+										 &is_equal);
+			if (off >= 0 && is_equal)
+			{
+				/* An exact matching datum exists. */
+				Assert(partindices[off] >= 0);
+				return bms_make_singleton(partindices[off]);
+			}
+			else if (partition_bound_has_default(boundinfo))
+				return bms_make_singleton(default_index);
+			else
+				return NULL;
+			break;
+
+		case BTGreaterEqualStrategyNumber:
+			inclusive = true;
+			/* fall through */
+		case BTGreaterStrategyNumber:
+			off = partition_list_bsearch(partsupfunc,
+										 partcollation,
+										 boundinfo, value,
+										 &is_equal);
+			if (off >= 0)
+			{
+				/* We don't want the matched datum to be in the result. */
+				if (!is_equal || !inclusive)
+					off++;
+			}
+			else
+			{
+				/*
+				 * This case means all partition bounds are greater, which in
+				 * turn means that all partition satisfy this key.
+				 */
+				off = 0;
+			}
+
+			/*
+			 * off is greater than the numbers of datums we have partitions
+			 * for.  The only possible partition that could contain a match is
+			 * the default partition, which is already set in 'result' if one
+			 * exists.
+			 */
+			if (off > boundinfo->ndatums - 1)
+				return result;
+
+			minoff = off;
+			break;
+
+		case BTLessEqualStrategyNumber:
+			inclusive = true;
+			/* fall through */
+		case BTLessStrategyNumber:
+			off = partition_list_bsearch(partsupfunc,
+										 partcollation,
+										 boundinfo, value,
+										 &is_equal);
+			if (off >= 0 && is_equal && !inclusive)
+				off--;
+
+			/*
+			 * off is smaller than the datums of all non-default partitions.
+			 * The only possible partition that could contain a match is the
+			 * default partition, which is already set in 'result' if one
+			 * exists.
+			 */
+			if (off < 0)
+				return result;
+
+			maxoff = off;
+			break;
+
+		default:
+			elog(ERROR, "invalid strategy number %d", opstrategy);
+			break;
+	}
+
+	/* Finally add the partition indexes. */
+	for (i = minoff; i <= maxoff; i++)
+		result = bms_add_member(result, partindices[i]);
+
+	return result;
+}
+
+/*
+ * get_partitions_for_keys_range
+ *		Determine the minimum set of partitions matching the specified values
+ *		using range partitioning.
+ *
+ * 'nvalues', if non-zero, should be <= context->partntts - 1
+ * 'values' contains values for partition keys (or a prefix) to be used for
+ * pruning
+ * 'opstrategy' if non-zero must be a btree strategy number
+ * 'partsupfunc' contains range partitioning comparison function to be used to
+ * perform partition_range_datum_bsearch or partition_rbound_datum_cmp
+ * 'nullkeys' is the set of partition keys that are null.
+ */
+static Bitmapset *
+get_partitions_for_keys_range(PartitionPruneContext *context,
+							  int opstrategy, Datum *values, int nvalues,
+							  FmgrInfo *partsupfunc, Bitmapset *nullkeys)
+{
+	PartitionBoundInfo boundinfo = context->boundinfo;
+	Oid		   *partcollation = context->partcollation;
+	int			partnatts = context->partnatts;
+	int			default_index = boundinfo->default_index;
+	int		   *partindices = boundinfo->indexes;
+	int			off,
+				minoff,
+				maxoff,
+				i;
+	bool		is_equal;
+	bool		inclusive = false;
+	Bitmapset  *result = NULL;
+
+	Assert(context->strategy == PARTITION_STRATEGY_RANGE);
+
+	/*
+	 * If there are no datums to compare keys with, or if we got a IS NULL
+	 * clause just return the default partition, if it exists.
+	 */
+	if (boundinfo->ndatums == 0 || !bms_is_empty(nullkeys))
+	{
+		if (partition_bound_has_default(boundinfo))
+			return bms_make_singleton(default_index);
+		else
+			return NULL;
+	}
+
+	minoff = 0;
+	maxoff = boundinfo->ndatums;
+	if (nvalues == 0)
+	{
+		/*
+		 * Add indexes of *all* partitions containing non-null values and
+		 * return.
+		 */
+		if (partindices[minoff] < 0)
+			minoff++;
+		if (partindices[maxoff] < 0)
+			maxoff--;
+
+		result = bms_add_range(result, partindices[minoff],
+							   partindices[maxoff]);
+
+		if (partition_bound_has_default(boundinfo))
+			result = bms_add_member(result, default_index);
+
+		return result;
+	}
+
+	switch (opstrategy)
+	{
+		case BTEqualStrategyNumber:
+			off = partition_range_datum_bsearch(partsupfunc,
+												partcollation,
+												boundinfo,
+												nvalues, values,
+												&is_equal);
+
+			if (off >= 0 && is_equal)
+			{
+				if (nvalues == partnatts)
+				{
+					/* There can only be one partition. */
+					if (partindices[off + 1] >= 0)
+						return bms_make_singleton(partindices[off + 1]);
+					else if (partition_bound_has_default(boundinfo))
+						return bms_make_singleton(default_index);
+					else
+						return NULL;
+				}
+				else
+				{
+					int			saved_off = off;
+
+					/* Matched a prefix of the partition bound at off. */
+					while (off >= 1 && off < boundinfo->ndatums - 1)
+					{
+						int32		cmpval;
+
+						cmpval =
+							partition_rbound_datum_cmp(partsupfunc,
+													   partcollation,
+													   boundinfo->datums[off - 1],
+													   boundinfo->kind[off - 1],
+													   values, nvalues);
+						if (cmpval != 0)
+							break;
+						off--;
+					}
+					minoff = off;
+					off = saved_off;
+					while (off < boundinfo->ndatums - 1)
+					{
+						int32		cmpval;
+
+						cmpval = partition_rbound_datum_cmp(partsupfunc,
+															partcollation,
+															boundinfo->datums[off + 1],
+															boundinfo->kind[off + 1],
+															values, nvalues);
+						if (cmpval != 0)
+							break;
+						off++;
+					}
+					maxoff = off + 1;
+				}
+			}
+			else if (off >= 0)
+			{
+				if (partindices[off + 1] >= 0)
+					minoff = maxoff = off + 1;
+				else if (partition_bound_has_default(boundinfo))
+					return bms_make_singleton(default_index);
+				else
+					return NULL;
+			}
+			else if (partition_bound_has_default(boundinfo))
+				return bms_make_singleton(default_index);
+			else
+				return NULL;
+
+			if (partindices[minoff] < 0 &&
+				minoff < boundinfo->ndatums)
+				minoff++;
+			if (partindices[maxoff] < 0 && maxoff >= 1)
+				maxoff--;
+			break;
+
+		case BTGreaterEqualStrategyNumber:
+			inclusive = true;
+			/* fall through */
+		case BTGreaterStrategyNumber:
+			off = partition_range_datum_bsearch(partsupfunc,
+												partcollation,
+												boundinfo,
+												nvalues, values,
+												&is_equal);
+
+			if (off < 0)
+			{
+				/*
+				 * All partition bounds are greater than the key, so include
+				 * all partitions in the result.
+				 */
+				off = 0;
+			}
+			else
+			{
+				if (is_equal && nvalues < partnatts)
+				{
+					/* Matched a prefix of the partition bound at off. */
+					while (off < boundinfo->ndatums - 1)
+					{
+						int32		cmpval;
+						int			nextoff;
+
+						nextoff = inclusive ? off - 1 : off + 1;
+						cmpval =
+							partition_rbound_datum_cmp(partsupfunc,
+													   partcollation,
+													   boundinfo->datums[nextoff],
+													   boundinfo->kind[nextoff],
+													   values, nvalues);
+						if (cmpval != 0)
+						{
+							if (!inclusive)
+								off++;
+							break;
+						}
+						off = nextoff;
+					}
+				}
+				else
+					off++;
+			}
+
+			minoff = off;
+			break;
+
+		case BTLessEqualStrategyNumber:
+			inclusive = true;
+			/* fall through */
+		case BTLessStrategyNumber:
+			off = partition_range_datum_bsearch(partsupfunc,
+												partcollation,
+												boundinfo,
+												nvalues, values,
+												&is_equal);
+
+			if (off >= 0)
+			{
+				/* Matched prefix of the partition bound at off. */
+				if (is_equal && nvalues < partnatts)
+				{
+					while (off < boundinfo->ndatums - 1)
+					{
+						int32		cmpval;
+						int			nextoff;
+
+						nextoff = inclusive ? off + 1 : off - 1;
+						cmpval = partition_rbound_datum_cmp(partsupfunc,
+															partcollation,
+															boundinfo->datums[nextoff],
+															boundinfo->kind[nextoff],
+															values, nvalues);
+						if (cmpval != 0)
+						{
+							if (!inclusive)
+								off--;
+							break;
+						}
+						off = nextoff;
+					}
+
+					off++;
+				}
+				else if (!is_equal || inclusive)
+					off++;
+			}
+			else
+			{
+				/*
+				 * All partition bounds are greater than the key, so select
+				 * none of the partitions, except the default.
+				 */
+				if (partition_bound_has_default(boundinfo))
+					return bms_make_singleton(default_index);
+				return NULL;
+			}
+
+			maxoff = off;
+			break;
+
+		default:
+			elog(ERROR, "invalid strategy number %d", opstrategy);
+			break;
+	}
+
+	Assert(minoff >= 0 && maxoff >= 0);
+	if (partindices[minoff] < 0)
+	{
+		int			lastkey = nvalues - 1;
+
+		if (minoff >= 0 && minoff < boundinfo->ndatums &&
+			boundinfo->kind[minoff][lastkey] ==
+			PARTITION_RANGE_DATUM_VALUE &&
+			partition_bound_has_default(boundinfo))
+			result = bms_add_member(result, default_index);
+
+		minoff++;
+	}
+
+	if (maxoff >= 1 && partindices[maxoff] < 0)
+	{
+		int			lastkey = nvalues - 1;
+
+		if (maxoff >= 0 && maxoff <= boundinfo->ndatums &&
+			boundinfo->kind[maxoff - 1][lastkey] ==
+			PARTITION_RANGE_DATUM_VALUE &&
+			partition_bound_has_default(boundinfo))
+			result = bms_add_member(result, default_index);
+
+		maxoff--;
+	}
+
+	if (minoff <= maxoff)
+		result = bms_add_range(result,
+							   partindices[minoff],
+							   partindices[maxoff]);
+
+	if (partition_bound_has_default(boundinfo))
+	{
+		/*
+		 * Since partition keys with nulls are mapped to the default range
+		 * partition, we must include the default partition if some keys could
+		 * be null.
+		 */
+		if (nvalues < partnatts)
+			result = bms_add_member(result, default_index);
+
+		/*
+		 * There may exist a range of values unassigned to any non-default
+		 * partition between the datums at minoff and maxoff.  Add the default
+		 * partition in that case.
+		 */
+		for (i = minoff; i <= maxoff; i++)
+		{
+			if (partindices[i] < 0)
+				return bms_add_member(result, default_index);
+		}
+	}
+
+	return result;
+}
+
+/*
+ * get_partitions_excluded_by_ne_datums
+ *
+ * Returns a Bitmapset of partition indexes that can safely be removed due to
+ * the discovery of <> clauses for each datum value allowed in the partition.
+ */
+static Bitmapset *
+get_partitions_excluded_by_ne_datums(PartitionPruneContext *context,
+									 Datum *ne_datums, int n_ne_datums,
+									 FmgrInfo **partsupfunc)
+{
+	PartitionBoundInfo boundinfo = context->boundinfo;
+	Oid		   *partcollation = context->partcollation;
+	int			nparts = context->nparts,
+			   *datums_in_query = NULL,
+			   *datums_in_part = NULL,
+				i;
+	Bitmapset  *excluded_parts = NULL;
+	Bitmapset  *foundoffsets = NULL;
+
+	/*
+	 * We can only do this exclusion for list partitions because that's the
+	 * only case where we require all values to explicitly specified.
+	 */
+	Assert(context->strategy == PARTITION_STRATEGY_LIST);
+	Assert(context->partnatts == 1);
+
+	/*
+	 * First check if the datums in the query are in any of the partitions.
+	 * If found, store their offsets in foundoffsets.
+	 */
+	for (i = 0; i < n_ne_datums; i++)
+	{
+		int			offset;
+		bool		is_equal;
+
+		offset = partition_list_bsearch(partsupfunc[i], partcollation,
+										boundinfo,
+										ne_datums[i], &is_equal);
+		if (offset >= 0 && is_equal)
+		{
+			Assert(boundinfo->indexes[offset] >= 0);
+			foundoffsets = bms_add_member(foundoffsets, offset);
+		}
+	}
+
+	/*
+	 * We must ensure that we got clauses for all the values that a given list
+	 * partition allows before we can eliminate the partition.
+	 *
+	 * We'll need two arrays for this, one to count the number of unique
+	 * datums found in the query which belong to each partition, and another
+	 * to record the number of datums permitted in each partition.  Once we've
+	 * counted all this, we can eliminate any partition where the number of
+	 * datums found in the query matches the number of datums allowed in the
+	 * partition.
+	 */
+	if (!bms_is_empty(foundoffsets))
+	{
+		datums_in_query = (int *) palloc0(sizeof(int) * nparts);
+
+		i = -1;
+		while ((i = bms_next_member(foundoffsets, i)) >= 0)
+			datums_in_query[boundinfo->indexes[i]]++;
+	}
+
+	/*
+	 * Now, in a single pass over all the datums, count the number of datums
+	 * permitted in each partition.
+	 */
+	datums_in_part = (int *) palloc0(sizeof(int) * nparts);
+	for (i = 0; i < boundinfo->ndatums; i++)
+		datums_in_part[boundinfo->indexes[i]]++;
+
+	/*
+	 * Now compare the counts and eliminate any partition for which we
+	 * found clauses for all its permitted values.  We must be careful
+	 * here not to eliminate the default partition.  We can recognize that
+	 * by it having a zero value in both arrays.
+	 */
+	if (datums_in_query)
+	{
+		for (i = 0; i < nparts; i++)
+		{
+			if (datums_in_query[i] >= datums_in_part[i] &&
+				datums_in_query[i] > 0)
+				excluded_parts = bms_add_member(excluded_parts, i);
+		}
+	}
+
+	/*
+	 * Because the clauses from which ne_datums were extracted are all
+	 * strict, we can also exclude the NULL (-only!) partition.
+	 */
+	if (partition_bound_accepts_nulls(boundinfo) &&
+		datums_in_part[boundinfo->null_index] == 0)
+		excluded_parts = bms_add_member(excluded_parts,
+										boundinfo->null_index);
+
+	if (datums_in_query)
+		pfree(datums_in_query);
+	pfree(datums_in_part);
+
+	return excluded_parts;
+}
+
+/*
  * get_partition_operator
  *
  * Return oid of the operator of given strategy for a given partition key
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c7293a60d7..cd8ac22960 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2133,6 +2133,49 @@ _copyOnConflictExpr(const OnConflictExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyPartitionPruneStepOp
+ */
+static PartitionPruneStepOp *
+_copyPartitionPruneStepOp(const PartitionPruneStepOp *from)
+{
+	PartitionPruneStepOp *newnode = makeNode(PartitionPruneStepOp);
+
+	COPY_SCALAR_FIELD(opstrategy);
+	COPY_NODE_FIELD(exprs);
+	COPY_NODE_FIELD(cmpfns);
+	COPY_BITMAPSET_FIELD(nullkeys);
+
+	return newnode;
+}
+
+/*
+ * _copyPartitionPruneStepOpNe
+ */
+static PartitionPruneStepOpNe *
+_copyPartitionPruneStepOpNe(const PartitionPruneStepOpNe *from)
+{
+	PartitionPruneStepOpNe *newnode = makeNode(PartitionPruneStepOpNe);
+
+	COPY_NODE_FIELD(exprs);
+
+	return newnode;
+}
+
+/*
+ * _copyPartitionPruneStepCombine
+ */
+static PartitionPruneStepCombine *
+_copyPartitionPruneStepCombine(const PartitionPruneStepCombine *from)
+{
+	PartitionPruneStepCombine *newnode = makeNode(PartitionPruneStepCombine);
+
+	COPY_SCALAR_FIELD(combineOp);
+	COPY_NODE_FIELD(argsteps);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *						relation.h copy functions
  *
@@ -5024,6 +5067,15 @@ copyObjectImpl(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_PartitionPruneStepOp:
+			retval = _copyPartitionPruneStepOp(from);
+			break;
+		case T_PartitionPruneStepOpNe:
+			retval = _copyPartitionPruneStepOpNe(from);
+			break;
+		case T_PartitionPruneStepCombine:
+			retval = _copyPartitionPruneStepCombine(from);
+			break;
 
 			/*
 			 * RELATION NODES
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 6c76c41ebe..fdab8725aa 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -2146,6 +2146,32 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_PartitionPruneStepOp:
+			{
+				PartitionPruneStepOp *opstep = (PartitionPruneStepOp *) node;
+
+				if (walker((Node *) opstep->exprs, context))
+					return true;
+			}
+			break;
+		case T_PartitionPruneStepOpNe:
+			{
+				PartitionPruneStepOpNe *nestep =
+											(PartitionPruneStepOpNe *) node;
+
+				if (walker((Node *) nestep->exprs, context))
+					return true;
+			}
+			break;
+		case T_PartitionPruneStepCombine:
+			{
+				PartitionPruneStepCombine *cstep =
+										(PartitionPruneStepCombine *) node;
+
+				if (walker((Node *) cstep->argsteps, context))
+					return true;
+			}
+			break;
 		case T_JoinExpr:
 			{
 				JoinExpr   *join = (JoinExpr *) node;
@@ -2932,6 +2958,41 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_PartitionPruneStepOp:
+			{
+				PartitionPruneStepOp *opstep = (PartitionPruneStepOp *) node;
+				PartitionPruneStepOp *newnode;
+
+				FLATCOPY(newnode, opstep, PartitionPruneStepOp);
+				MUTATE(newnode->exprs, opstep->exprs, List *);
+
+				return (Node *) newnode;
+			}
+			break;
+		case T_PartitionPruneStepOpNe:
+			{
+				PartitionPruneStepOpNe *nestep =
+											(PartitionPruneStepOpNe *) node;
+				PartitionPruneStepOpNe *newnode;
+
+				FLATCOPY(newnode, nestep, PartitionPruneStepOpNe);
+				MUTATE(newnode->exprs, nestep->exprs, List *);
+
+				return (Node *) newnode;
+			}
+			break;
+		case T_PartitionPruneStepCombine:
+			{
+				PartitionPruneStepCombine *cstep =
+										(PartitionPruneStepCombine *) node;
+				PartitionPruneStepCombine *newnode;
+
+				FLATCOPY(newnode, cstep, PartitionPruneStepCombine);
+				MUTATE(newnode->argsteps, cstep->argsteps, List *);
+
+				return (Node *) newnode;
+			}
+			break;
 		case T_JoinExpr:
 			{
 				JoinExpr   *join = (JoinExpr *) node;
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 43f4e75748..3fd3cadb01 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -33,6 +33,7 @@
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/geqo.h"
+#include "optimizer/partprune.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/plancat.h"
@@ -867,6 +868,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 	double	   *parent_attrsizes;
 	int			nattrs;
 	ListCell   *l;
+	Relids		live_children = NULL;
+	bool		did_pruning = false;
 
 	/* Guard against stack overflow due to overly deep inheritance tree. */
 	check_stack_depth();
@@ -874,6 +877,20 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 	Assert(IS_SIMPLE_REL(rel));
 
 	/*
+	 * If the partitioned relation has any baserestrictinfo quals then we
+	 * attempt to use these quals to prune away partitions that cannot
+	 * possibly contain any tuples matching these quals.  In this case we'll
+	 * store the relids of all partitions which could possibly contain a
+	 * matching tuple, and skip anything else in the loop below.
+	 */
+	if (rte->relkind == RELKIND_PARTITIONED_TABLE &&
+		rel->baserestrictinfo != NIL)
+	{
+		live_children = prune_append_rel_partitions(rel);
+		did_pruning = true;
+	}
+
+	/*
 	 * Initialize to compute size estimates for whole append relation.
 	 *
 	 * We handle width estimates by weighting the widths of different child
@@ -1121,6 +1138,17 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 			continue;
 		}
 
+		if (did_pruning &&
+			!bms_is_member(appinfo->child_relid, live_children))
+		{
+			/*
+			 * Partition pruning determined this partition cannot possibly
+			 * contain any tuples matching the baserestrictinfo, so skip it.
+			 */
+			set_dummy_rel_pathlist(childrel);
+			continue;
+		}
+
 		if (relation_excluded_by_constraints(root, childrel, childRTE))
 		{
 			/*
diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile
index c54d0a690d..aebd98875e 100644
--- a/src/backend/optimizer/util/Makefile
+++ b/src/backend/optimizer/util/Makefile
@@ -12,7 +12,7 @@ subdir = src/backend/optimizer/util
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = clauses.o joininfo.o orclauses.o pathnode.o placeholder.o \
+OBJS = clauses.o joininfo.o orclauses.o partprune.o pathnode.o placeholder.o \
        plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/util/partprune.c b/src/backend/optimizer/util/partprune.c
new file mode 100644
index 0000000000..7c00d62a56
--- /dev/null
+++ b/src/backend/optimizer/util/partprune.c
@@ -0,0 +1,1471 @@
+/*-------------------------------------------------------------------------
+ *
+ * partprune.c
+ *		Provides the functionality to match the provided set of clauses with
+ *		the partition key to generate steps needed for partition pruning
+ *
+ * If the "steps" contain enough information, partitions can be pruned right
+ * away in this module, which is called "static pruning" as all the needed
+ * information is statically available in the query being planned.
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *		  src/backend/optimizer/util/partprune.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/hash.h"
+#include "access/nbtree.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_opfamily.h"
+#include "catalog/pg_type.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/partprune.h"
+#include "optimizer/planner.h"
+#include "optimizer/predtest.h"
+#include "optimizer/prep.h"
+#include "parser/parse_coerce.h"
+#include "parser/parsetree.h"
+#include "rewrite/rewriteManip.h"
+#include "utils/lsyscache.h"
+
+/*
+ * Information about a clause matched with a partition key.
+ */
+typedef struct PartClauseInfo
+{
+	int			keyno;			/* Partition key number (0 to partnatts - 1)  */
+	Oid			opno;			/* operator used to compare partkey to 'expr' */
+	Expr	   *expr;			/* The expr the partition key is being
+								 * compared to */
+	Oid			cmpfn;			/* Oid of function to compare 'expr' to the
+								 * partition key */
+
+	/* cached info. */
+	int			op_strategy;
+} PartClauseInfo;
+
+typedef enum PartClauseMatchStatus
+{
+	PARTCLAUSE_NOMATCH,
+	PARTCLAUSE_MATCH_CLAUSE,
+	PARTCLAUSE_MATCH_NULLNESS,
+	PARTCLAUSE_MATCH_STEPS,
+	PARTCLAUSE_MATCH_CONTRADICT,
+	PARTCLAUSE_UNSUPPORTED
+}			PartClauseMatchStatus;
+
+static List *generate_partition_pruning_steps_internal(RelOptInfo *rel,
+										  List *clauses,
+										  bool *constfalse);
+static PartClauseMatchStatus match_clause_to_partition_key(RelOptInfo *rel,
+														   Expr *clause, Expr *partkey, int partkeyidx,
+														   bool *key_is_null, bool *key_is_not_null,
+														   PartClauseInfo **pc, List **clause_steps,
+														   bool *is_neop_listp);
+static bool match_boolean_partition_clause(Oid partopfamily, Expr *clause,
+							   Expr *partkey, Expr **rightop);
+static List *get_steps_using_prefix(int step_opstrategy,
+					   Expr *step_lastexpr,
+					   Oid step_lastcmpfn,
+					   int step_lastkeyno,
+					   Bitmapset *step_nullkeys,
+					   List *prefix);
+static List *get_steps_using_prefix_recurse(int step_opstrategy,
+							   Expr *step_lastexpr,
+							   Oid step_lastcmpfn,
+							   int step_lastkeyno,
+							   Bitmapset *step_nullkeys,
+							   ListCell *start,
+							   List *step_exprs,
+							   List *step_cmpfns);
+
+/*
+ * prune_append_rel_partitions
+ *		Returns RT indexes of the minimum set of child partitions which must
+ *		be scanned to satisfy rel's baserestrictinfo quals.
+ *
+ * Callers must ensure that 'rel' is a partitioned table.
+ */
+Relids
+prune_append_rel_partitions(RelOptInfo *rel)
+{
+	Relids		result = NULL;
+	List	   *clauses = rel->baserestrictinfo;
+	List	   *pruning_steps;
+	bool		constfalse;
+
+	Assert(clauses != NIL);
+	Assert(rel->part_scheme != NULL);
+
+	/* Quick exit. */
+	if (rel->nparts == 0)
+		return NULL;
+
+	/* process clauses */
+	pruning_steps = generate_partition_pruning_steps(rel, clauses,
+													 &constfalse);
+
+	if (!constfalse)
+	{
+		/* Actual pruning happens here. */
+		PartitionPruneContext context;
+		Bitmapset  *partindexes;
+		int			i;
+
+		/* Initiate partition pruning using clauses. */
+		memset(&context, 0, sizeof(context));
+		context.strategy = rel->part_scheme->strategy;
+		context.partnatts = rel->part_scheme->partnatts;
+		context.partopfamily = rel->part_scheme->partopfamily;
+		context.partopcintype = rel->part_scheme->partopcintype;
+		context.partcollation = rel->part_scheme->partcollation;
+		context.partsupfunc = rel->part_scheme->partsupfunc;
+		context.nparts = rel->nparts;
+		context.boundinfo = rel->boundinfo;
+
+		partindexes = get_matching_partitions(&context, pruning_steps);
+
+		/* Add selected partitions' RT indexes to result. */
+		i = -1;
+		while ((i = bms_next_member(partindexes, i)) >= 0)
+			result = bms_add_member(result, rel->part_rels[i]->relid);
+	}
+
+	return result;
+}
+
+/*
+ * generate_partition_pruning_steps
+ *		Processes 'clauses' and returns a list of "partition pruning steps"
+ *
+ * If any of the clause in the input list is a pseudo-constant "false",
+ * *constfalse is set to true upon return.
+ */
+List *
+generate_partition_pruning_steps(RelOptInfo *rel, List *clauses,
+								 bool *constfalse)
+{
+	/* The clauses list may be modified below, so better make a copy. */
+	clauses = list_copy(clauses);
+
+	/*
+	 * For sub-partitioned tables there's a corner case where if the
+	 * sub-partitioned table shares any partition keys with its parent, then
+	 * it's possible that the partitioning hierarchy allows the parent
+	 * partition to only contain a narrower range of values than the
+	 * sub-partitioned table does.  In this case it is possible that we'd
+	 * include partitions that could not possibly have any tuples matching
+	 * 'clauses'.  The possibility of such a partition arrangement is perhaps
+	 * unlikely for non-default partitions, but it may be more likely in the
+	 * case of default partitions, so we'll add the parent partition table's
+	 * partition qual to the clause list in this case only.  This may result
+	 * in the default partition being eliminated.
+	 */
+	if (rel->has_default_part && rel->partition_qual != NIL)
+	{
+		List	   *partqual = rel->partition_qual;
+
+		partqual = (List *) expression_planner((Expr *) partqual);
+
+		/* Fix Vars to have the desired varno */
+		if (rel->relid != 1)
+			ChangeVarNodes((Node *) partqual, 1, rel->relid, 0);
+
+		clauses = list_concat(clauses, partqual);
+	}
+
+	/* Down into the rabbit-hole. */
+	return generate_partition_pruning_steps_internal(rel, clauses,
+													 constfalse);
+}
+
+/* Module-local functions */
+
+/*
+ * generate_partition_pruning_steps_internal
+ *		Processes 'clauses' to generate partition pruning steps.
+ *
+ * From OpExpr clauses that are mutually AND'd, we find combinations of those
+ * that match to the partition key columns and for every such combination,
+ * we emit a PartitionPruneStepOp containing a vector of expressions whose
+ * values to be used as a look up key to search partitions with.  Relevant
+ * details of the operator and a vector of (possibly cross-type) comparison
+ * functions is also included with each step.
+ *
+ * For BoolExpr clauses, we recursively generate steps for each of its
+ * arguments and generate PartitionPruneStepCombine step that contains those
+ * steps.
+ *
+ * If when going through clauses, we find any that are marked as pseudoconstant
+ * and contains a constant false value, we stop generating any further steps
+ * and simply return NIL (that is, no pruning steps) after setting *constfalse
+ * to true.  Caller should consider all partitions as pruned in that case.
+ * We may do the same if we find that mutually contradictory clauses are
+ * present, but were not turned into a pseudoconstant at higher levels.
+ *
+ * Note: the 'clauses' List may be modified inside this function. Callers may
+ * like to make a copy of it before passing them to this function.
+ */
+static List *
+generate_partition_pruning_steps_internal(RelOptInfo *rel, List *clauses,
+										  bool *constfalse)
+{
+	PartitionScheme part_scheme = rel->part_scheme;
+	List	   *keyclauses[PARTITION_MAX_KEYS],
+			   *btree_clauses[BTMaxStrategyNumber],
+			   *hash_clauses[HTMaxStrategyNumber],
+			   *ne_clauses = NIL;
+	bool		need_next_less,
+				need_next_eq,
+				need_next_greater;
+	Bitmapset  *nullkeys = NULL,
+			   *notnullkeys = NULL;
+	bool		generate_opsteps = false;
+	List	   *result = NIL,
+			   *opsteps = NIL;
+	ListCell   *lc;
+	int			i;
+
+	*constfalse = false;
+	memset(keyclauses, 0, sizeof(keyclauses));
+	foreach(lc, clauses)
+	{
+		Expr	   *clause = (Expr *) lfirst(lc);
+		int			i;
+
+		if (IsA(clause, RestrictInfo))
+		{
+			RestrictInfo *rinfo = (RestrictInfo *) clause;
+
+			clause = rinfo->clause;
+			if (rinfo->pseudoconstant &&
+				!DatumGetBool(((Const *) clause)->constvalue))
+			{
+				*constfalse = true;
+				return NIL;
+			}
+		}
+
+		/* Get the BoolExpr's out of the way. */
+		if (IsA(clause, BoolExpr))
+		{
+			if (or_clause((Node *) clause))
+			{
+				PartitionPruneStepCombine *combineStep;
+				List	   *all_arg_steps = NIL;
+				bool		all_args_constfalse = true;
+				ListCell   *lc1;
+
+				/*
+				 * Get pruning step for each arg.  If we get constfalse for
+				 * all args, it means the OR expression is false as a whole.
+				 */
+				foreach(lc1, ((BoolExpr *) clause)->args)
+				{
+					Expr	   *arg = lfirst(lc1);
+					bool		arg_constfalse;
+					List	   *argsteps;
+
+					argsteps =
+						generate_partition_pruning_steps_internal(rel,
+																  list_make1(arg),
+																  &arg_constfalse);
+					if (!arg_constfalse)
+						all_args_constfalse = false;
+
+					/*
+					 * If arg was a clause matching this partition key, we'd
+					 * get back the corresponding pruning step.
+					 */
+					if (argsteps != NIL)
+					{
+						Assert(list_length(argsteps) == 1);
+						all_arg_steps = lappend(all_arg_steps,
+												linitial(argsteps));
+					}
+					else
+					{
+						/*
+						 * No steps means the arg wasn't a clause matching
+						 * this partition key.  We cannot prune using such an
+						 * arg.  To indicate that to the pruning code, we must
+						 * construct a PartitionPruneStepCombine and set the
+						 * argsteps to an empty List.  However, if we can
+						 * prove using constraint exclusion that the clause
+						 * refutes the table's partition constraint (if it's
+						 * sub-partitioned), we need not bother with that.
+						 */
+						List	   *partconstr = rel->partition_qual;
+						PartitionPruneStepCombine *orstep;
+
+						if (partconstr)
+						{
+							partconstr = (List *)
+								expression_planner((Expr *) partconstr);
+							if (rel->relid != 1)
+								ChangeVarNodes((Node *) partconstr, 1,
+											   rel->relid, 0);
+							if (predicate_refuted_by(partconstr,
+													 list_make1(arg),
+													 false))
+								continue;
+						}
+
+						orstep = makeNode(PartitionPruneStepCombine);
+						orstep->combineOp = COMBINE_OR;
+						orstep->argsteps = NIL;
+						all_arg_steps = lappend(all_arg_steps, orstep);
+					}
+				}
+
+				*constfalse = all_args_constfalse;
+
+				/* Check if any contradicting clauses were found */
+				if (*constfalse)
+					return NIL;
+
+				combineStep = makeNode(PartitionPruneStepCombine);
+				combineStep->combineOp = COMBINE_OR;
+				combineStep->argsteps = all_arg_steps;
+				result = lappend(result, combineStep);
+				continue;
+			}
+			else if (and_clause((Node *) clause))
+			{
+				PartitionPruneStepCombine *combineStep;
+				List	   *args = ((BoolExpr *) clause)->args;
+				List	   *argsteps;
+
+				/*
+				 * args may itself contain clauses of arbitrary type, so just
+				 * recurse and later combine the component partitions sets
+				 * using a combine step.
+				 */
+				argsteps = generate_partition_pruning_steps_internal(rel,
+																	 args,
+																	 constfalse);
+				if (*constfalse)
+					return NIL;
+
+				combineStep = makeNode(PartitionPruneStepCombine);
+				combineStep->combineOp = COMBINE_AND;
+				combineStep->argsteps = argsteps;
+				result = lappend(result, combineStep);
+				continue;
+			}
+
+			/*
+			 * Fall-through for a NOT clause, which is handled in
+			 * match_clause_to_partition_key().
+			 */
+		}
+
+		/*
+		 * Must be a clause for which we can check if one of its args matches
+		 * the partition key.
+		 */
+		for (i = 0; i < part_scheme->partnatts; i++)
+		{
+			Expr	   *partkey = linitial(rel->partexprs[i]);
+			bool		unsupported_clause = false,
+						key_is_null = false,
+						key_is_not_null = false,
+						is_neop_listp;
+			PartClauseInfo *pc = NULL;
+			List	   *clause_steps = NIL;
+
+			switch (match_clause_to_partition_key(rel, clause, partkey, i,
+												  &key_is_null,
+												  &key_is_not_null,
+												  &pc, &clause_steps,
+												  &is_neop_listp))
+			{
+				case PARTCLAUSE_MATCH_CLAUSE:
+					/*
+					 * Since we only allow strict operators, check for any
+					 * contradicting IS NULL.
+					 */
+					if (bms_is_member(i, nullkeys))
+					{
+						*constfalse = true;
+						return NIL;
+					}
+
+					Assert(pc != NULL);
+					/*
+					 * If the clause was one containing an operator named <>,
+					 * we generate a special pruning steps designed to handle
+					 * those, so collect it in a separate list.
+					 */
+					if (is_neop_listp)
+						ne_clauses = lappend(ne_clauses, pc);
+					else
+					{
+						generate_opsteps = true;
+						keyclauses[i] = lappend(keyclauses[i], pc);
+					}
+					break;
+
+				case PARTCLAUSE_MATCH_NULLNESS:
+					if (key_is_null)
+					{
+						/* check for conflicting IS NOT NULL */
+						if (bms_is_member(i, notnullkeys))
+						{
+							*constfalse = true;
+							return NIL;
+						}
+						nullkeys = bms_add_member(nullkeys, i);
+					}
+					else if (key_is_not_null)
+					{
+						/* check for conflicting IS NULL */
+						if (bms_is_member(i, nullkeys))
+						{
+							*constfalse = true;
+							return NIL;
+						}
+						notnullkeys = bms_add_member(notnullkeys, i);
+					}
+					else
+						Assert(false);
+
+					generate_opsteps = true;
+					break;
+
+				case PARTCLAUSE_MATCH_STEPS:
+					Assert(clause_steps != NIL);
+					result = list_concat(result, clause_steps);
+					break;
+
+				case PARTCLAUSE_MATCH_CONTRADICT:
+					/* We've nothing more to do if a contradiction was found. */
+					*constfalse = true;
+					return NIL;
+
+				case PARTCLAUSE_NOMATCH:
+					/*
+					 * Clause didn't match this key, but it might match the
+					 * next one.
+					 */
+					continue;
+
+				case PARTCLAUSE_UNSUPPORTED:
+					/* This clause cannot be used for pruning. */
+					unsupported_clause = true;
+					break;
+
+				default:
+					Assert(false);
+					break;
+			}
+
+			/* go check the next clause. */
+			if (unsupported_clause)
+				break;
+		}
+	}
+
+	/*
+	 * Combine expressions from all <> operator clauses into one prune step.
+	 */
+	if (ne_clauses != NIL)
+	{
+		List	   *exprs = NIL;
+		List	   *cmpfns = NIL;
+		PartitionPruneStepOpNe *nestep;
+
+		Assert(part_scheme->strategy == PARTITION_STRATEGY_LIST);
+		foreach(lc, ne_clauses)
+		{
+			PartClauseInfo *pc = lfirst(lc);
+
+			exprs = lappend(exprs, pc->expr);
+			cmpfns = lappend_oid(cmpfns, pc->cmpfn);
+		}
+
+		nestep = makeNode(PartitionPruneStepOpNe);
+		nestep->exprs = exprs;
+		nestep->cmpfns = cmpfns;
+		result = lappend(result, nestep);
+	}
+
+	/* There were nothing but combining steps in the clauses we got. */
+	if (!generate_opsteps)
+		return result;
+
+	/*
+	 * Now we have one list of clauses per partition key.  We check here if
+	 * we have found clauses for a valid subset of the partition key. In some
+	 * cases, (depending on the type of partitioning being used) if we didn't
+	 * find clauses for a given key, we discard clauses that may have been
+	 * found for any subsequent keys; see specific notes below.
+	 */
+	memset(btree_clauses, 0, sizeof(btree_clauses));
+	memset(hash_clauses, 0, sizeof(hash_clauses));
+	for (i = 0; i < part_scheme->partnatts; i++)
+	{
+		List   *clauselist = keyclauses[i];
+		bool	consider_next_key = true;
+
+		/*
+		 * To be useful for pruning, we must have clauses for a prefix of
+		 * partition keys in the case of range partitioning.  So, ignore
+		 * clauses for keys after this one.
+		 */
+		if (part_scheme->strategy == PARTITION_STRATEGY_RANGE &&
+			clauselist == NIL)
+			break;
+
+		/*
+		 * For hash partitioning, if a column doesn't have necessary equality
+		 * clause, there should be an IS NULL clause, otherwise pruning is not
+		 * possible.
+		 */
+		if (part_scheme->strategy == PARTITION_STRATEGY_HASH &&
+			clauselist == NIL && !bms_is_member(i, nullkeys))
+			return NIL;
+
+		need_next_eq = need_next_less = need_next_greater = true;
+		foreach(lc, clauselist)
+		{
+			PartClauseInfo *pc = (PartClauseInfo *) lfirst(lc);
+			Oid			lefttype,
+						righttype;
+
+			/* Look up the operator's btree/hash strategy number. */
+			if (pc->op_strategy == InvalidStrategy)
+				get_op_opfamily_properties(pc->opno,
+										   part_scheme->partopfamily[i],
+										   false,
+										   &pc->op_strategy,
+										   &lefttype,
+										   &righttype);
+
+			switch (part_scheme->strategy)
+			{
+				case PARTITION_STRATEGY_LIST:
+				case PARTITION_STRATEGY_RANGE:
+					{
+						PartClauseInfo *last = NULL;
+						bool		inclusive = false;
+
+						/*
+						 * Add this clause to the list of clauses to be used
+						 * for pruning if this is the first such key for this
+						 * operator strategy or if it is consecutively next to
+						 * the last column for which a clause with this
+						 * operator strategy was matched.
+						 */
+						if (btree_clauses[pc->op_strategy - 1] != NIL)
+							last = llast(btree_clauses[pc->op_strategy - 1]);
+
+						if (last == NULL ||
+							i == last->keyno || i == last->keyno + 1)
+							btree_clauses[pc->op_strategy - 1] =
+								lappend(btree_clauses[pc->op_strategy - 1], pc);
+
+						/*
+						 * We may not need the next clause if they're of
+						 * certain strategy.
+						 */
+						switch (pc->op_strategy)
+						{
+							case BTLessEqualStrategyNumber:
+								inclusive = true;
+								/* fall through */
+							case BTLessStrategyNumber:
+								if (!inclusive)
+									need_next_eq = need_next_less = false;
+								break;
+							case BTEqualStrategyNumber:
+								/* always accept clauses for the next key. */
+								break;
+							case BTGreaterEqualStrategyNumber:
+								inclusive = true;
+								/* fall through */
+							case BTGreaterStrategyNumber:
+								if (!inclusive)
+									need_next_eq = need_next_greater = false;
+								break;
+						}
+
+						/* We may want to change our mind. */
+						if (consider_next_key)
+							consider_next_key = (need_next_eq ||
+												 need_next_less ||
+												 need_next_greater);
+						break;
+					}
+
+				case PARTITION_STRATEGY_HASH:
+					if (pc->op_strategy != HTEqualStrategyNumber)
+						elog(ERROR, "invalid clause for hash partitioning");
+					hash_clauses[pc->op_strategy - 1] =
+						lappend(hash_clauses[pc->op_strategy - 1], pc);
+					break;
+
+				default:
+					elog(ERROR, "invalid partition strategy: %c",
+						 part_scheme->strategy);
+					break;
+			}
+		}
+
+		/*
+		 * If we've decided that clauses for subsequent partition keys would't
+		 * be useful for pruning, don't look.
+		 */
+		if (!consider_next_key)
+			break;
+	}
+
+	/*
+	 * Now, we have divided clauses according to their operator strategies.
+	 * Check for each strategy if we can generate pruning step(s) by
+	 * collecting a list of expressions whose values will constitute a vector
+	 * that can be used as a look-up key by a partition bound searching
+	 * function.
+	 */
+	switch (part_scheme->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+		case PARTITION_STRATEGY_RANGE:
+			{
+				List	   *eq_clauses = btree_clauses[BTEqualStrategyNumber - 1];
+				List	   *le_clauses = btree_clauses[BTLessEqualStrategyNumber - 1];
+				List	   *ge_clauses = btree_clauses[BTGreaterEqualStrategyNumber - 1];
+
+				/*
+				 * For each clause under consideration for a given strategy,
+				 * we collect expressions from clauses for earlier keys, whose
+				 * operator strategy is inclusive, into a list called 'prefix'.
+				 * By appending the clause's own expression to the 'prefix',
+				 * we'll generate one step using the so generated vector and
+				 * assign the current strategy to it.  Actually, 'prefix' might
+				 * contain multiple clauses for the same key, in which case,
+				 * we must generate steps for various combinations of
+				 * expressions of different keys, which get_steps_using_prefix
+				 * takes care of doing.
+				 */
+				for (i = 0; i < BTMaxStrategyNumber; i++)
+				{
+					PartClauseInfo *pc;
+					List	   *pc_steps;
+
+					foreach(lc, btree_clauses[i])
+					{
+						ListCell   *lc1;
+						List	   *prefix = NIL;
+
+						/* Clause under consideration. */
+						pc = lfirst(lc);
+
+						/*
+						 * Expressions from = clauses can always be in the
+						 * prefix, provided they're from an earlier key.
+						 */
+						foreach(lc1, eq_clauses)
+						{
+							PartClauseInfo *eqpc = lfirst(lc1);
+
+							if (eqpc->keyno == pc->keyno)
+								break;
+							if (eqpc->keyno < pc->keyno)
+								prefix = lappend(prefix, eqpc);
+						}
+
+						/*
+						 * If we're generating steps for </<= strategy, we can
+						 * add other <= clauses to the prefix, provided they're
+						 * from an earlier key.
+						 */
+						if (i == BTLessStrategyNumber - 1 ||
+							i == BTLessEqualStrategyNumber - 1)
+						{
+							foreach(lc1, le_clauses)
+							{
+								PartClauseInfo *lepc = lfirst(lc1);
+
+								if (lepc->keyno == pc->keyno)
+									break;
+								if (lepc->keyno < pc->keyno)
+									prefix = lappend(prefix, lepc);
+							}
+						}
+
+						/*
+						 * If we're generating steps for >/>= strategy, we can
+						 * add other >= clauses to the prefix, provided they're
+						 * from an earlier key.
+						 */
+						if (i == BTGreaterStrategyNumber - 1 ||
+							i == BTGreaterEqualStrategyNumber - 1)
+						{
+							foreach(lc1, ge_clauses)
+							{
+								PartClauseInfo *gepc = lfirst(lc1);
+
+								if (gepc->keyno == pc->keyno)
+									break;
+								if (gepc->keyno < pc->keyno)
+									prefix = lappend(prefix, gepc);
+							}
+						}
+
+						/*
+						 * As mentioned above, if 'prefix' contains multiple
+						 * expressions for the same key, the following will
+						 * generate multiple steps, one for each combination
+						 * of the expressions for different keys.
+						 *
+						 * Note that we pass NULL for step_nullkeys, because
+						 * we don't search list/range partition bounds where
+						 * some keys are NULL.
+						 */
+						Assert(pc->op_strategy == i + 1);
+						pc_steps = get_steps_using_prefix(i + 1,
+														  pc->expr,
+														  pc->cmpfn,
+														  pc->keyno,
+														  NULL,
+														  prefix);
+						opsteps = list_concat(opsteps, list_copy(pc_steps));
+					}
+				}
+				break;
+			}
+
+		case PARTITION_STRATEGY_HASH:
+			{
+				List *eq_clauses = hash_clauses[HTEqualStrategyNumber - 1];
+
+				/* For hash partitioning, we have just the = strategy. */
+				if (eq_clauses != NIL)
+				{
+					PartClauseInfo *pc;
+					List   *pc_steps;
+					List   *prefix = NIL;
+					int		last_keyno;
+					ListCell *lc1;
+
+					/*
+					 * Locate the clause for the greatest column (whic may not
+					 * be the last partition key column).  Actually, the last
+					 * element of eq_clauses must give us what we need.
+					 */
+					pc = llast(eq_clauses);
+
+					/*
+					 * But there might be multiple clauses that we matched to
+					 * that column; go to the first such clause.  While at it,
+					 * add all the clauses before that one to 'prefix'.
+					 */
+					last_keyno = pc->keyno;
+					foreach(lc, eq_clauses)
+					{
+						pc = lfirst(lc);
+						if (pc->keyno == last_keyno)
+							break;
+						prefix = lappend(prefix, pc);
+					}
+
+					/*
+					 * For each clause for the "last" column, after appending
+					 * the clause's own expression to the 'prefix', we'll
+					 * generate one step using the so generated vector and
+					 * and assign = as its strategy.  Actually, 'prefix' might
+					 * contain multiple clauses for the same key, in which
+					 * case, we must generate steps for various combinations
+					 * of expressions of different keys,
+					 * which get_steps_using_prefix takes care of doing.
+					 */
+					for_each_cell(lc1, lc)
+					{
+						pc = lfirst(lc1);
+
+						/*
+						 * Note that we pass nullkeys for step_nullkeys,
+						 * because we need to tell hash partition bound search
+						 * function which of the keys are NULL.
+						 */
+						Assert(pc->op_strategy == HTEqualStrategyNumber);
+						pc_steps =
+								get_steps_using_prefix(HTEqualStrategyNumber,
+													   pc->expr,
+													   pc->cmpfn,
+													   pc->keyno,
+													   nullkeys,
+													   prefix);
+					}
+
+					opsteps = list_concat(opsteps, list_copy(pc_steps));
+				}
+				break;
+			}
+
+		default:
+			elog(ERROR, "invalid partition strategy: %c",
+				 part_scheme->strategy);
+			break;
+	}
+
+	/*
+	 * Generate one prune step for the information derived from IS NULL and IS
+	 * NOT NULL clauses.  Note that for IS NOT NULL clauses, simply having
+	 * step suffices; there is no need to propagate the exact details of which
+	 * keys are required to be NOT NULL.
+	 */
+	if (opsteps == NIL &&
+		(!bms_is_empty(nullkeys) || !bms_is_empty(notnullkeys)))
+	{
+		PartitionPruneStepOp *opstep;
+
+		opstep = makeNode(PartitionPruneStepOp);
+		opstep->nullkeys = nullkeys;
+		opsteps = lappend(opsteps, opstep);
+	}
+
+	/* Add opsteps to result. */
+	result = list_concat(result, opsteps);
+
+	return result;
+}
+
+/*
+ * If the partition key has a collation, then the clause must have the same
+ * input collation.  If the partition key is non-collatable, we assume the
+ * collation doesn't matter, because while collation wasn't considered when
+ * performing partitioning, the clause still may have a collation assigned
+ * due to the other input being of a collatable type.
+ */
+#define PartCollMatchesExprColl(partcoll, exprcoll) \
+	((partcoll) == InvalidOid || (partcoll) == (exprcoll))
+
+/*
+ * match_clause_to_partition_key
+ *		Match a given clause with the specified partition key
+ *
+ * Return value:
+ *
+ *	one of PARTCLAUSE_MATCH_* enum values if the clause is successfully
+ *	matched to the partition key.  If it is PARTCLAUSE_MATCH_CONTRADICT, then
+ *	this means the clause is self-contradictory (which can happen only if it's
+ *	a BoolExpr whose arguments may be self-contradictory)
+ *
+ * 	PARTCLAUSE_NOMATCH if the clause doesn't match *this* partition key but
+ *	the caller should continue trying because it may match a subsequent key
+ *
+ *	PARTCLAUSE_UNSUPPORTED if the clause cannot be used for pruning at all,
+ *	even if it may have been matched with a key, due to one of its properties,
+ *	such as volatility of the arguments
+ *
+ * Based on the returned enum value, different output arguments are set as
+ * follows:
+ *
+ *	PARTCLAUSE_UNSUPPORTED or
+ *	PARTCLAUSE_NOMATCH or
+ *	PARTCLAUSE_MATCH_CONTRADICT: None set (caller shouldn't rely on any of
+ *	them being set)
+ *
+ *	PARTCLAUSE_MATCH_CLAUSE: *pc set to PartClauseInfo constructed for the
+ *	matched clause and *is_neop_listp set if the clause contained a <>
+ *	operator
+ *
+ *	PARTCLAUSE_MATCH_NULLNESS: either *key_is_null or *key_is_not_null set
+ *	based on whether the matched clause was a IS NULL or IS NOT NULL clause,
+ *	respectively
+ *
+ *	PARTCLAUSE_MATCH_STEPS: *clause_steps set to list of "partition pruning
+ *	step(s)" generated for the clause due to it being a BoolExpr or a
+ *	ScalarArrayOpExpr that's turned into one
+ */
+static PartClauseMatchStatus
+match_clause_to_partition_key(RelOptInfo *rel,
+							  Expr *clause, Expr *partkey, int partkeyidx,
+							  bool *key_is_null, bool *key_is_not_null,
+							  PartClauseInfo **pc, List **clause_steps,
+							  bool *is_neop_listp)
+{
+	PartitionScheme part_scheme = rel->part_scheme;
+	Expr	   *expr;
+	Oid			partopfamily = part_scheme->partopfamily[partkeyidx],
+				partcoll = part_scheme->partcollation[partkeyidx];
+
+	/*
+	 * Recognize specially shaped clauses that match with the Boolean
+	 * partition key.
+	 */
+	if (match_boolean_partition_clause(partopfamily, clause, partkey, &expr))
+	{
+		*is_neop_listp = false;
+		*pc = palloc0(sizeof(PartClauseInfo));
+		(*pc)->keyno = partkeyidx;
+		/* Do pruning with the Boolean equality operator. */
+		(*pc)->opno = BooleanEqualOperator;
+		(*pc)->expr = expr;
+		/* We know that expr is of Boolean type. */
+		(*pc)->cmpfn = rel->part_scheme->partsupfunc[partkeyidx].fn_oid;
+
+		return PARTCLAUSE_MATCH_CLAUSE;
+	}
+	else if (IsA(clause, OpExpr) &&
+			 list_length(((OpExpr *) clause)->args) == 2)
+	{
+		OpExpr	   *opclause = (OpExpr *) clause;
+		Expr	   *leftop,
+				   *rightop;
+		Oid			commutator = InvalidOid,
+					negator = InvalidOid;
+		Oid			cmpfn;
+
+		leftop = (Expr *) get_leftop(clause);
+		if (IsA(leftop, RelabelType))
+			leftop = ((RelabelType *) leftop)->arg;
+		rightop = (Expr *) get_rightop(clause);
+		if (IsA(rightop, RelabelType))
+			rightop = ((RelabelType *) rightop)->arg;
+
+		/* check if the clause matches this partition key */
+		if (equal(leftop, partkey))
+			expr = rightop;
+		else if (equal(rightop, partkey))
+		{
+			expr = leftop;
+			commutator = get_commutator(opclause->opno);
+
+			/* nothing we can do unless we can swap the operands */
+			if (!OidIsValid(commutator))
+				return PARTCLAUSE_UNSUPPORTED;
+		}
+		else
+			/* clause does not match this partition key, but perhaps next. */
+			return PARTCLAUSE_NOMATCH;
+
+		/*
+		 * Partition key also consists of a collation that's specified for it,
+		 * so try to match it too.  There may be multiple keys with the same
+		 * expression but different collations.
+		 */
+		if (!PartCollMatchesExprColl(partcoll, opclause->inputcollid))
+			return PARTCLAUSE_NOMATCH;
+
+		/*
+		 * Matched with this key.  Now check various properties of the clause
+		 * to see if it's sane to use it for pruning.  If any of the
+		 * properties makes it unsuitable for pruning, then the clause is
+		 * useless no matter which key it's matched to.
+		 */
+
+		/*
+		 * Only allow strict operators.  This will guarantee nulls are
+		 * filtered.
+		 */
+		if (!op_strict(opclause->opno))
+			return PARTCLAUSE_UNSUPPORTED;
+
+		/* We can't use any volatile expressions to prune partitions. */
+		if (contain_volatile_functions((Node *) expr))
+			return PARTCLAUSE_UNSUPPORTED;
+
+		/*
+		 * Normally we only bother with operators that are listed as being
+		 * part of the partitioning operator family.  But we make an exception
+		 * in one case -- operators named '<>' are not listed in any operator
+		 * family whatsoever, in which case, we try to perform partition
+		 * pruning with it only if list partitioning is in use.
+		 */
+		*is_neop_listp = false;
+		if (!op_in_opfamily(opclause->opno, partopfamily))
+		{
+			if (part_scheme->strategy != PARTITION_STRATEGY_LIST)
+				return PARTCLAUSE_UNSUPPORTED;
+
+			/*
+			 * To confirm if the operator is really '<>', check if its negator
+			 * is a btree equality operator.
+			 */
+			negator = get_negator(opclause->opno);
+			if (OidIsValid(negator) && op_in_opfamily(negator, partopfamily))
+			{
+				Oid			lefttype;
+				Oid			righttype;
+				int			strategy;
+
+				get_op_opfamily_properties(negator, partopfamily, false,
+										   &strategy, &lefttype, &righttype);
+
+				if (strategy == BTEqualStrategyNumber)
+					*is_neop_listp = true;
+			}
+
+			/* Operator isn't really what we were hoping it'd be. */
+			if (!*is_neop_listp)
+				return PARTCLAUSE_UNSUPPORTED;
+		}
+
+		/* Check if we're going to need a cross-type comparison function. */
+		if (exprType((Node *) expr) != part_scheme->partopcintype[partkeyidx])
+		{
+			int	procnum = (part_scheme->strategy == PARTITION_STRATEGY_HASH)
+							? HASHEXTENDED_PROC
+							: BTORDER_PROC;
+
+			cmpfn = get_opfamily_proc(part_scheme->partopfamily[partkeyidx],
+									  part_scheme->partopcintype[partkeyidx],
+									  exprType((Node *) expr), procnum);
+			/* If we couldn't find one, we cannot use this expression. */
+			if (!OidIsValid(cmpfn))
+				return PARTCLAUSE_UNSUPPORTED;
+		}
+		else
+			cmpfn = part_scheme->partsupfunc[partkeyidx].fn_oid;
+
+		*pc = palloc0(sizeof(PartClauseInfo));
+		(*pc)->keyno = partkeyidx;
+
+		/* For <> operator clauses, pass on the negator. */
+		if (*is_neop_listp)
+		{
+			Assert(OidIsValid(negator));
+			(*pc)->opno = negator;
+		}
+		/* And if commuted before matching, pass on the commutator */
+		else if (OidIsValid(commutator))
+			(*pc)->opno = commutator;
+		else
+			(*pc)->opno = opclause->opno;
+
+		(*pc)->expr = expr;
+		(*pc)->cmpfn = cmpfn;
+
+		return PARTCLAUSE_MATCH_CLAUSE;
+	}
+	else if (IsA(clause, ScalarArrayOpExpr))
+	{
+		ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
+		Oid			saop_op = saop->opno;
+		Oid			saop_coll = saop->inputcollid;
+		Expr	   *leftop = (Expr *) linitial(saop->args),
+				   *rightop = (Expr *) lsecond(saop->args);
+		List	   *elem_exprs,
+				   *elem_clauses;
+		ListCell   *lc1;
+
+		if (IsA(leftop, RelabelType))
+			leftop = ((RelabelType *) leftop)->arg;
+
+		/* Check it matches this partition key */
+		if (!equal(leftop, partkey) ||
+			!PartCollMatchesExprColl(partcoll, saop->inputcollid))
+			return PARTCLAUSE_NOMATCH;
+
+		/*
+		 * Matched with this key.  Check various properties of the clause to
+		 * see if it can sanely be used for partition pruning.
+		 */
+
+		/*
+		 * Only allow strict operators.  This will guarantee nulls are
+		 * filtered.
+		 */
+		if (!op_strict(saop->opno))
+			return PARTCLAUSE_UNSUPPORTED;
+
+		/* Useless if the array has any volatile functions. */
+		if (contain_volatile_functions((Node *) rightop))
+			return PARTCLAUSE_UNSUPPORTED;
+
+		/*
+		 * In case of NOT IN (..), we get a '<>', which we handle if list
+		 * partitioning is in use and we're able to confirm that it's negator
+		 * is a btree equality operator belonging to the partitioning operator
+		 * family.
+		 */
+		if (!op_in_opfamily(saop_op, partopfamily))
+		{
+			Oid			negator;
+
+			if (part_scheme->strategy != PARTITION_STRATEGY_LIST)
+				return PARTCLAUSE_UNSUPPORTED;
+
+			negator = get_negator(saop_op);
+			if (OidIsValid(negator) && op_in_opfamily(negator, partopfamily))
+			{
+				int			strategy;
+				Oid			lefttype,
+							righttype;
+
+				get_op_opfamily_properties(negator, partopfamily,
+										   false, &strategy,
+										   &lefttype, &righttype);
+				if (strategy != BTEqualStrategyNumber)
+					return PARTCLAUSE_UNSUPPORTED;
+			}
+		}
+
+		/*
+		 * First generate a list of Const nodes, one for each array element.
+		 */
+		elem_exprs = NIL;
+		if (IsA(rightop, Const))
+		{
+			Const	   *arr = (Const *) lsecond(saop->args);
+			ArrayType  *arrval = DatumGetArrayTypeP(arr->constvalue);
+			int16		elemlen;
+			bool		elembyval;
+			char		elemalign;
+			Datum	   *elem_values;
+			bool	   *elem_nulls;
+			int			num_elems,
+						i;
+
+			get_typlenbyvalalign(ARR_ELEMTYPE(arrval),
+								 &elemlen, &elembyval, &elemalign);
+			deconstruct_array(arrval,
+							  ARR_ELEMTYPE(arrval),
+							  elemlen, elembyval, elemalign,
+							  &elem_values, &elem_nulls,
+							  &num_elems);
+			for (i = 0; i < num_elems; i++)
+			{
+				/* Only consider non-null values. */
+				if (!elem_nulls[i])
+				{
+					Const	   *elem_expr = makeConst(ARR_ELEMTYPE(arrval),
+													  -1, arr->constcollid,
+													  elemlen,
+													  elem_values[i],
+													  false, elembyval);
+
+					elem_exprs = lappend(elem_exprs, elem_expr);
+				}
+			}
+		}
+		else
+		{
+			ArrayExpr  *arrexpr = castNode(ArrayExpr, rightop);
+
+			/*
+			 * For a nested ArrayExpr, we don't know how to get the actual
+			 * scalar values out into a flat list, so we give up doing
+			 * anything with this ScalarArrayOpExpr.
+			 */
+			if (arrexpr->multidims)
+				return PARTCLAUSE_UNSUPPORTED;
+
+			elem_exprs = arrexpr->elements;
+		}
+
+		/*
+		 * Now generate a list of clauses, one for each array element, of the
+		 * form saop_leftop saop_op elem_expr
+		 */
+		elem_clauses = NIL;
+		foreach(lc1, elem_exprs)
+		{
+			Expr	   *rightop = (Expr *) lfirst(lc1),
+					   *elem_clause;
+
+			elem_clause = (Expr *) make_opclause(saop_op, BOOLOID,
+												 false,
+												 leftop, rightop,
+												 InvalidOid,
+												 saop_coll);
+			elem_clauses = lappend(elem_clauses, elem_clause);
+		}
+
+		/*
+		 * Build a combine step as if for an OR clause or add the clauses to
+		 * the end of the list that's being processed currently.
+		 */
+		if (saop->useOr && list_length(elem_clauses) > 1)
+		{
+			Expr	   *orexpr;
+			bool		constfalse;
+
+			orexpr = makeBoolExpr(OR_EXPR, elem_clauses, -1);
+			*clause_steps = generate_partition_pruning_steps_internal(rel,
+																	  list_make1(orexpr),
+																	  &constfalse);
+			if (constfalse)
+				return PARTCLAUSE_MATCH_CONTRADICT;
+			Assert(list_length(*clause_steps) == 1);
+			return PARTCLAUSE_MATCH_STEPS;
+		}
+		else
+		{
+			bool		constfalse;
+
+			*clause_steps = generate_partition_pruning_steps_internal(rel,
+																	  elem_clauses,
+																	  &constfalse);
+			if (constfalse)
+				return PARTCLAUSE_MATCH_CONTRADICT;
+			Assert(list_length(*clause_steps) >= 1);
+			return PARTCLAUSE_MATCH_STEPS;
+		}
+	}
+	else if (IsA(clause, NullTest))
+	{
+		NullTest   *nulltest = (NullTest *) clause;
+		Expr	   *arg = nulltest->arg;
+
+		if (IsA(arg, RelabelType))
+			arg = ((RelabelType *) arg)->arg;
+
+		/* Does arg match with this partition key column? */
+		if (!equal(arg, partkey))
+			return PARTCLAUSE_NOMATCH;
+
+		if (nulltest->nulltesttype == IS_NULL)
+			*key_is_null = true;
+		else
+			*key_is_not_null = true;
+
+		return PARTCLAUSE_MATCH_NULLNESS;
+	}
+
+	return PARTCLAUSE_UNSUPPORTED;
+}
+
+/*
+ * match_boolean_partition_clause
+ *
+ * Sets *rightop to a Const containing true or false value and returns true if
+ * we're able to match the clause to the partition key as specially-shaped
+ * Boolean clause.  Returns false otherwise with *rightop set to NULL.
+ */
+static bool
+match_boolean_partition_clause(Oid partopfamily, Expr *clause, Expr *partkey,
+							   Expr **rightop)
+{
+	Expr	   *leftop;
+
+	if (!IsBooleanOpfamily(partopfamily))
+		return false;
+
+	*rightop = NULL;
+	if (IsA(clause, BooleanTest))
+	{
+		BooleanTest *btest = (BooleanTest *) clause;
+
+		/* Only IS [NOT] TRUE/FALSE are any good to us */
+		if (btest->booltesttype == IS_UNKNOWN ||
+			btest->booltesttype == IS_NOT_UNKNOWN)
+			return false;
+
+		leftop = btest->arg;
+		if (IsA(leftop, RelabelType))
+			leftop = ((RelabelType *) leftop)->arg;
+
+		if (equal(leftop, partkey))
+			*rightop = (btest->booltesttype == IS_TRUE ||
+						btest->booltesttype == IS_NOT_FALSE)
+				? (Expr *) makeBoolConst(true, false)
+				: (Expr *) makeBoolConst(false, false);
+
+		if (*rightop)
+			return true;
+	}
+	else
+	{
+		leftop = not_clause((Node *) clause)
+			? get_notclausearg(clause)
+			: clause;
+
+		if (IsA(leftop, RelabelType))
+			leftop = ((RelabelType *) leftop)->arg;
+
+		/* Clause does not match this partition key. */
+		if (equal(leftop, partkey))
+			*rightop = not_clause((Node *) clause)
+				? (Expr *) makeBoolConst(false, false)
+				: (Expr *) makeBoolConst(true, false);
+		else if (equal(negate_clause((Node *) leftop), partkey))
+			*rightop = (Expr *) makeBoolConst(false, false);
+
+		if (*rightop)
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * get_steps_using_prefix
+ *		Generate list of PartitionPruneStepOp steps each consisting of given
+ *		opstrategy
+ *
+ * To generate steps, step_lastexpr and step_lastcmpfn are appended to
+ * expressions and cmpfns, respectively, extracted from the clauses in
+ * 'prefix'.  Actually, since 'prefix' may contain multiple clauses for the
+ * same partition key column, we must generate steps for various combinations
+ * of the clauses of different keys.
+ */
+static List *
+get_steps_using_prefix(int step_opstrategy,
+					   Expr *step_lastexpr,
+					   Oid step_lastcmpfn,
+					   int step_lastkeyno,
+					   Bitmapset *step_nullkeys,
+					   List *prefix)
+{
+	/* Quick exit if there are no values to prefix with. */
+	if (list_length(prefix) == 0)
+	{
+		PartitionPruneStepOp *step = makeNode(PartitionPruneStepOp);
+
+		step->opstrategy = step_opstrategy;
+		step->exprs = list_make1(step_lastexpr);
+		step->cmpfns = list_make1_oid(step_lastcmpfn);
+		step->nullkeys = step_nullkeys;
+
+		return list_make1(step);
+	}
+
+	/* Recurse to generate steps for various combinations. */
+	return get_steps_using_prefix_recurse(step_opstrategy,
+										  step_lastexpr,
+										  step_lastcmpfn,
+										  step_lastkeyno,
+										  step_nullkeys,
+										  list_head(prefix),
+										  NIL, NIL);
+}
+
+/*
+ * get_steps_using_prefix_recurse
+ *		Recursively generate combinations of clauses for different partition
+ *		keys and start generating steps upon reaching clauses for the greatest
+ *		column that is less than the one for which we're currently generating
+ *		steps (that is, step_lastkeyno)
+ *
+ * 'start' is where we should start iterating for the current invocation.
+ * 'step_exprs' and 'step_cmpfns' each contains the expressions and cmpfns
+ * we've generated so far from the clauses for the still earlier columns.
+ */
+static List *
+get_steps_using_prefix_recurse(int step_opstrategy,
+							   Expr *step_lastexpr,
+							   Oid step_lastcmpfn,
+							   int step_lastkeyno,
+							   Bitmapset *step_nullkeys,
+							   ListCell *start,
+							   List *step_exprs,
+							   List *step_cmpfns)
+{
+	List	   *result = NIL;
+	ListCell   *lc;
+	int			cur_keyno;
+
+	/* Actually, recursion would be limited by PARTITION_MAX_KEYS. */
+	check_stack_depth();
+
+	/* Check if we need to recurse. */
+	Assert(start != NULL);
+	cur_keyno = ((PartClauseInfo *) lfirst(start))->keyno;
+	if (cur_keyno < step_lastkeyno - 1)
+	{
+		PartClauseInfo *pc;
+		ListCell   *next_start;
+
+		/*
+		 * For each clause with cur_keyno, adds its expr and cmpfn to
+		 * step_exprs and step_cmpfns, respectively, and recurse after setting
+		 * next_start to the ListCell of the first clause for the next
+		 * partition key.
+		 */
+		for_each_cell(lc, start)
+		{
+			pc = lfirst(lc);
+
+			if (pc->keyno > cur_keyno)
+				break;
+		}
+		next_start = lc;
+
+		for_each_cell(lc, start)
+		{
+			pc = lfirst(lc);
+			if (pc->keyno == cur_keyno)
+			{
+				/* clean up before starting a new recursion cycle. */
+				if (cur_keyno == 0)
+				{
+					list_free(step_exprs);
+					list_free(step_cmpfns);
+					step_exprs = list_make1(pc->expr);
+					step_cmpfns = list_make1_oid(pc->cmpfn);
+				}
+				else
+				{
+					step_exprs = lappend(step_exprs, pc->expr);
+					step_cmpfns = lappend_oid(step_cmpfns, pc->cmpfn);
+				}
+			}
+			else
+			{
+				Assert(pc->keyno > cur_keyno);
+				break;
+			}
+
+			result =
+				list_concat(result,
+							get_steps_using_prefix_recurse(step_opstrategy,
+														   step_lastexpr,
+														   step_lastcmpfn,
+														   step_lastkeyno,
+														   step_nullkeys,
+														   next_start,
+														   step_exprs,
+														   step_cmpfns));
+		}
+	}
+	else
+	{
+		/*
+		 * End the current recursion cycle and start generating steps, one
+		 * for each clause with cur_keyno, which is all clauses from here
+		 * onward till the end of the list.
+		 */
+		Assert(list_length(step_exprs) == cur_keyno);
+		for_each_cell(lc, start)
+		{
+			PartClauseInfo *pc = lfirst(lc);
+			PartitionPruneStepOp *step;
+			List   *step_exprs1,
+				   *step_cmpfns1;
+
+			Assert(pc->keyno == cur_keyno);
+
+			/* Leave the original step_exprs unmodified. */
+			step_exprs1 = list_copy(step_exprs);
+			step_exprs1 = lappend(step_exprs1, pc->expr);
+			step_exprs1 = lappend(step_exprs1, step_lastexpr);
+
+			/* Leave the original step_cmpfns unmodified. */
+			step_cmpfns1 = list_copy(step_cmpfns);
+			step_cmpfns1 = lappend_oid(step_cmpfns1, pc->cmpfn);
+			step_cmpfns1 = lappend_oid(step_cmpfns1, step_lastcmpfn);
+
+			step = makeNode(PartitionPruneStepOp);
+			step->opstrategy = step_opstrategy;
+			step->exprs = step_exprs1;
+			step->cmpfns = step_cmpfns1;
+			step->nullkeys = step_nullkeys;
+			result = lappend(result, step);
+		}
+	}
+
+	return result;
+}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 093ca5208e..7c1b0de295 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1171,7 +1171,6 @@ get_relation_constraints(PlannerInfo *root,
 	Index		varno = rel->relid;
 	Relation	relation;
 	TupleConstr *constr;
-	List	   *pcqual;
 
 	/*
 	 * We assume the relation has already been safely locked.
@@ -1257,24 +1256,34 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
-	/* Append partition predicates, if any */
-	pcqual = RelationGetPartitionQual(relation);
-	if (pcqual)
+	/*
+	 * Append partition predicates, if any.
+	 *
+	 * For selects, partition pruning uses the parent table's partition bound
+	 * descriptor, instead of constraint exclusion which is driven by the
+	 * individual partition's partition constraint.
+	 */
+	if (root->parse->commandType != CMD_SELECT)
 	{
-		/*
-		 * Run the partition quals through const-simplification similar to
-		 * check constraints.  We skip canonicalize_qual, though, because
-		 * partition quals should be in canonical form already; also, since
-		 * the qual is in implicit-AND format, we'd have to explicitly convert
-		 * it to explicit-AND format and back again.
-		 */
-		pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
+		List	   *pcqual = RelationGetPartitionQual(relation);
 
-		/* Fix Vars to have the desired varno */
-		if (varno != 1)
-			ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+		if (pcqual)
+		{
+			/*
+			 * Run the partition quals through const-simplification similar to
+			 * check constraints.  We skip canonicalize_qual, though, because
+			 * partition quals should be in canonical form already; also,
+			 * since the qual is in implicit-AND format, we'd have to
+			 * explicitly convert it to explicit-AND format and back again.
+			 */
+			pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
 
-		result = list_concat(result, pcqual);
+			/* Fix Vars to have the desired varno */
+			if (varno != 1)
+				ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+
+			result = list_concat(result, pcqual);
+		}
 	}
 
 	heap_close(relation, NoLock);
@@ -1865,6 +1874,9 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel,
 	rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey);
 	rel->nparts = partdesc->nparts;
 	set_baserel_partition_key_exprs(relation, rel);
+	rel->has_default_part =
+		OidIsValid(get_default_oid_from_partdesc(partdesc));
+	rel->partition_qual = RelationGetPartitionQual(relation);
 }
 
 /*
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index da8f0f93fc..7f1428b8d8 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -154,6 +154,8 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->part_scheme = NULL;
 	rel->nparts = 0;
 	rel->boundinfo = NULL;
+	rel->has_default_part = false;
+	rel->partition_qual = NIL;
 	rel->part_rels = NULL;
 	rel->partexprs = NULL;
 	rel->nullable_partexprs = NULL;
@@ -567,6 +569,8 @@ build_join_rel(PlannerInfo *root,
 	joinrel->part_scheme = NULL;
 	joinrel->nparts = 0;
 	joinrel->boundinfo = NULL;
+	joinrel->has_default_part = false;
+	joinrel->partition_qual = NIL;
 	joinrel->part_rels = NULL;
 	joinrel->partexprs = NULL;
 	joinrel->nullable_partexprs = NULL;
@@ -734,6 +738,10 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
 	joinrel->has_eclass_joins = false;
 	joinrel->top_parent_relids = NULL;
 	joinrel->part_scheme = NULL;
+	joinrel->nparts = 0;
+	joinrel->boundinfo = NULL;
+	joinrel->has_default_part = false;
+	joinrel->partition_qual = NIL;
 	joinrel->part_rels = NULL;
 	joinrel->partexprs = NULL;
 	joinrel->nullable_partexprs = NULL;
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 2faf0ca26e..fb29a66a64 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -42,6 +42,28 @@ typedef struct PartitionDescData
 
 typedef struct PartitionDescData *PartitionDesc;
 
+/*
+ * PartitionPruneContext
+ *
+ * Information about a partitioned table needed to perform partition pruning.
+ */
+typedef struct PartitionPruneContext
+{
+	/* Partition key information */
+	char		strategy;
+	int			partnatts;
+	Oid		   *partopfamily;
+	Oid		   *partopcintype;
+	Oid		   *partcollation;
+	FmgrInfo   *partsupfunc;
+
+	/* Number of partitions */
+	int			nparts;
+
+	/* Partition boundary info */
+	PartitionBoundInfo boundinfo;
+} PartitionPruneContext;
+
 extern void RelationBuildPartitionDesc(Relation relation);
 extern bool partition_bounds_equal(int partnatts, int16 *parttyplen,
 					   bool *parttypbyval, PartitionBoundInfo b1,
@@ -73,4 +95,7 @@ extern List *get_proposed_default_constraint(List *new_part_constaints);
 extern int get_partition_for_tuple(Relation relation, Datum *values,
 						bool *isnull);
 
+/* For partition-pruning */
+extern Bitmapset *get_matching_partitions(PartitionPruneContext *context,
+						List *pruning_steps);
 #endif							/* PARTITION_H */
diff --git a/src/include/catalog/pg_opfamily.h b/src/include/catalog/pg_opfamily.h
index b544474254..0847df97ff 100644
--- a/src/include/catalog/pg_opfamily.h
+++ b/src/include/catalog/pg_opfamily.h
@@ -188,4 +188,7 @@ DATA(insert OID = 4104 (	3580	box_inclusion_ops		PGNSP PGUID ));
 DATA(insert OID = 5000 (	4000	box_ops		PGNSP PGUID ));
 DATA(insert OID = 5008 (	4000	poly_ops				PGNSP PGUID ));
 
+#define IsBooleanOpfamily(opfamily) \
+	((opfamily) == BOOL_BTREE_FAM_OID || (opfamily) == BOOL_HASH_FAM_OID)
+
 #endif							/* PG_OPFAMILY_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 74b094a9c3..f208c11960 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -191,6 +191,10 @@ typedef enum NodeTag
 	T_FromExpr,
 	T_OnConflictExpr,
 	T_IntoClause,
+	T_PartitionPruneStep,
+	T_PartitionPruneStepOp,
+	T_PartitionPruneStepOpNe,
+	T_PartitionPruneStepCombine,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1b4b0d75af..5a9b12b141 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1506,4 +1506,96 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*
+ * Node types to represent a partition pruning step
+ */
+
+/* The base Node type */
+typedef struct PartitionPruneStep
+{
+	NodeTag		type;
+} PartitionPruneStep;
+
+/*----------
+ * PartitionPruneStepOp - Information to prune using a set of mutually AND'd
+ *							OpExpr clauses
+ *
+ * This contains information extracted from up to partnatts OpExpr clauses,
+ * where partnatts is the number of partition key columns.  'opstrategy' is the
+ * strategy of the operator in the clause matched to the last partition key.
+ * 'exprs' contains expressions which comprise the look-up key to be passed to
+ * the partition bound search function.  'cmpfnids' contains the OIDs of
+ * comparison function used to compare aforementioned expressions with
+ * partition bounds.  Both 'exprs' and 'cmpfns' contain up to partnatts
+ * elements.
+ *
+ * Once we find the offset of a partition bound using the look-up key, we
+ * determine which partitions to include in the result based on the value of
+ * 'opstrategy'.  For example, if it were equality, we'd return just the
+ * partition that would contain that key or a set of partitions if the key
+ * didn't consist of all partitioning columns.  For non-equality strategies,
+ * we'd need to include other partitions as appropriate.
+ *
+ * 'nullkeys' is the set containing the offset of the partition keys (0 to
+ * partnatts - 1) that were matched to an IS NULL clause.  This is only
+ * considered for hash partitioning as we need to pass which keys are null
+ * to the hash partition bound search function.  It is never possible to
+ * have an expression be present in 'exprs' for a given partition key and
+ * the corresponding bit set in 'nullkeys'.
+ *----------
+ */
+typedef struct PartitionPruneStepOp
+{
+	PartitionPruneStep step;
+
+	int			opstrategy;
+	List	   *exprs;
+	List	   *cmpfns;
+	Bitmapset  *nullkeys;
+} PartitionPruneStepOp;
+
+/*----------
+ * PartitionPruneStepOpNe - Information to prune using a set of mutually AND'd
+ *							OpExpr clauses each containing a <> operator
+ *
+ * This is a special form of PartitionPruneStepOp, where each of the
+ * expressions in 'expr' is compared using a <> operator.  To prune a given
+ * partition, we must check if each of the values it allows matches the value
+ * of one of the expressions in 'expr' using the corresponding comparison
+ * function in 'cmpfns'.
+ *
+ * Note: Since we must consider every possible value of the partition key a
+ * given partition may contain to be able to prune it using this step, we
+ * consider generating these only for list partitioned tables.
+ *----------
+ */
+typedef struct PartitionPruneStepOpNe
+{
+	PartitionPruneStep step;
+
+	List	   *exprs;
+	List	   *cmpfns;
+} PartitionPruneStepOpNe;
+
+/*----------
+ * PartitionPruneStepCombine - Information to prune using a BoolExpr clause
+ *
+ * For BoolExpr clauses, we determine the set of partitions for each of its
+ * argument clauses, and combine those sets as appropriate.
+ *----------
+ */
+typedef enum PartitionPruneCombineOp
+{
+	COMBINE_OR,
+	COMBINE_AND
+} PartitionPruneCombineOp;
+
+typedef struct PartitionPruneStepCombine
+{
+	PartitionPruneStep step;
+
+	PartitionPruneCombineOp combineOp;
+	List	   *argsteps;
+} PartitionPruneStepCombine;
+
 #endif							/* PRIMNODES_H */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index f151646271..ed0a885370 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -535,6 +535,8 @@ typedef struct PartitionSchemeData *PartitionScheme;
  * 		part_scheme - Partitioning scheme of the relation
  * 		boundinfo - Partition bounds
  * 		nparts - Number of partitions
+ *		has_default_part - Whether the table has a default partition
+ *		partition_qual - Partition constraint if not the root
  * 		part_rels - RelOptInfos for each partition
  * 		partexprs, nullable_partexprs - Partition key expressions
  *
@@ -667,6 +669,8 @@ typedef struct RelOptInfo
 	PartitionScheme part_scheme;	/* Partitioning scheme. */
 	int			nparts;			/* number of partitions */
 	struct PartitionBoundInfoData *boundinfo;	/* Partition bounds */
+	bool		has_default_part;	/* does it have a default partition? */
+	List	   *partition_qual; /* partition constraint */
 	struct RelOptInfo **part_rels;	/* Array of RelOptInfos of partitions,
 									 * stored in the same order of bounds */
 	List	  **partexprs;		/* Non-nullable partition key expressions. */
diff --git a/src/include/optimizer/partprune.h b/src/include/optimizer/partprune.h
new file mode 100644
index 0000000000..1f2fe297a3
--- /dev/null
+++ b/src/include/optimizer/partprune.h
@@ -0,0 +1,23 @@
+/*-------------------------------------------------------------------------
+ *
+ * partprune.h
+ *	  prototypes for partprune.c
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/optimizer/partprune.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTPRUNE_H
+#define PARTPRUNE_H
+
+#include "catalog/partition.h"
+
+extern Relids prune_append_rel_partitions(RelOptInfo *rel);
+extern List *generate_partition_pruning_steps(RelOptInfo *rel, List *clauses,
+								 bool *constfalse);
+
+#endif							/* PARTPRUNE_H */
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index f56151fc1e..d799acb91f 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1926,11 +1926,13 @@ explain (costs off) select * from mcrparted where abs(b) = 5;	-- scans all parti
          Filter: (abs(b) = 5)
    ->  Seq Scan on mcrparted3
          Filter: (abs(b) = 5)
+   ->  Seq Scan on mcrparted4
+         Filter: (abs(b) = 5)
    ->  Seq Scan on mcrparted5
          Filter: (abs(b) = 5)
    ->  Seq Scan on mcrparted_def
          Filter: (abs(b) = 5)
-(13 rows)
+(15 rows)
 
 explain (costs off) select * from mcrparted where a > -1;	-- scans all partitions
              QUERY PLAN              
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index e2b90f3263..d75a23e4a6 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -24,11 +24,13 @@ explain (costs off) select * from lp where a > 'a' and a < 'd';
                         QUERY PLAN                         
 -----------------------------------------------------------
  Append
+   ->  Seq Scan on lp_ad
+         Filter: ((a > 'a'::bpchar) AND (a < 'd'::bpchar))
    ->  Seq Scan on lp_bc
          Filter: ((a > 'a'::bpchar) AND (a < 'd'::bpchar))
    ->  Seq Scan on lp_default
          Filter: ((a > 'a'::bpchar) AND (a < 'd'::bpchar))
-(5 rows)
+(7 rows)
 
 explain (costs off) select * from lp where a > 'a' and a <= 'd';
                          QUERY PLAN                         
@@ -208,16 +210,14 @@ explain (costs off) select * from rlp where 1 > a;	/* commuted */
 (3 rows)
 
 explain (costs off) select * from rlp where a <= 1;
-              QUERY PLAN               
----------------------------------------
+        QUERY PLAN        
+--------------------------
  Append
    ->  Seq Scan on rlp1
          Filter: (a <= 1)
    ->  Seq Scan on rlp2
          Filter: (a <= 1)
-   ->  Seq Scan on rlp_default_default
-         Filter: (a <= 1)
-(7 rows)
+(5 rows)
 
 explain (costs off) select * from rlp where a = 1;
        QUERY PLAN        
@@ -235,7 +235,7 @@ explain (costs off) select * from rlp where a = 1::bigint;		/* same as above */
          Filter: (a = '1'::bigint)
 (3 rows)
 
-explain (costs off) select * from rlp where a = 1::numeric;	/* only null can be pruned */
+explain (costs off) select * from rlp where a = 1::numeric;		/* no pruning */
                   QUERY PLAN                   
 -----------------------------------------------
  Append
@@ -265,9 +265,11 @@ explain (costs off) select * from rlp where a = 1::numeric;	/* only null can be
          Filter: ((a)::numeric = '1'::numeric)
    ->  Seq Scan on rlp_default_30
          Filter: ((a)::numeric = '1'::numeric)
+   ->  Seq Scan on rlp_default_null
+         Filter: ((a)::numeric = '1'::numeric)
    ->  Seq Scan on rlp_default_default
          Filter: ((a)::numeric = '1'::numeric)
-(29 rows)
+(31 rows)
 
 explain (costs off) select * from rlp where a <= 10;
               QUERY PLAN               
@@ -575,7 +577,9 @@ explain (costs off) select * from rlp where a > 20 and a < 27;
          Filter: ((a > 20) AND (a < 27))
    ->  Seq Scan on rlp4_default
          Filter: ((a > 20) AND (a < 27))
-(7 rows)
+   ->  Seq Scan on rlp_default_default
+         Filter: ((a > 20) AND (a < 27))
+(9 rows)
 
 explain (costs off) select * from rlp where a = 29;
            QUERY PLAN           
@@ -714,9 +718,7 @@ explain (costs off) select * from mc3p where a = 1 and abs(b) = 1 and c < 8;
          Filter: ((c < 8) AND (a = 1) AND (abs(b) = 1))
    ->  Seq Scan on mc3p1
          Filter: ((c < 8) AND (a = 1) AND (abs(b) = 1))
-   ->  Seq Scan on mc3p_default
-         Filter: ((c < 8) AND (a = 1) AND (abs(b) = 1))
-(7 rows)
+(5 rows)
 
 explain (costs off) select * from mc3p where a = 10 and abs(b) between 5 and 35;
                            QUERY PLAN                            
@@ -892,6 +894,8 @@ explain (costs off) select * from mc3p where a = 1 or abs(b) = 1 or c = 1;
          Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1))
    ->  Seq Scan on mc3p2
          Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1))
+   ->  Seq Scan on mc3p3
+         Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1))
    ->  Seq Scan on mc3p4
          Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1))
    ->  Seq Scan on mc3p5
@@ -902,7 +906,7 @@ explain (costs off) select * from mc3p where a = 1 or abs(b) = 1 or c = 1;
          Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1))
    ->  Seq Scan on mc3p_default
          Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1))
-(17 rows)
+(19 rows)
 
 explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1) or (a = 10 and abs(b) = 10);
                                   QUERY PLAN                                  
@@ -963,9 +967,11 @@ explain (costs off) select * from mc2p where a = 2 and b < 1;
               QUERY PLAN               
 ---------------------------------------
  Append
+   ->  Seq Scan on mc2p2
+         Filter: ((b < 1) AND (a = 2))
    ->  Seq Scan on mc2p3
          Filter: ((b < 1) AND (a = 2))
-(3 rows)
+(5 rows)
 
 explain (costs off) select * from mc2p where a > 1;
            QUERY PLAN           
@@ -1007,24 +1013,20 @@ explain (costs off) select * from boolpart where a in (true, false);
 (5 rows)
 
 explain (costs off) select * from boolpart where a = false;
-             QUERY PLAN             
-------------------------------------
+          QUERY PLAN          
+------------------------------
  Append
    ->  Seq Scan on boolpart_f
          Filter: (NOT a)
-   ->  Seq Scan on boolpart_default
-         Filter: (NOT a)
-(5 rows)
+(3 rows)
 
 explain (costs off) select * from boolpart where not a = false;
-             QUERY PLAN             
-------------------------------------
+          QUERY PLAN          
+------------------------------
  Append
    ->  Seq Scan on boolpart_t
          Filter: a
-   ->  Seq Scan on boolpart_default
-         Filter: a
-(5 rows)
+(3 rows)
 
 explain (costs off) select * from boolpart where a is true or a is not true;
                     QUERY PLAN                    
@@ -1034,33 +1036,22 @@ explain (costs off) select * from boolpart where a is true or a is not true;
          Filter: ((a IS TRUE) OR (a IS NOT TRUE))
    ->  Seq Scan on boolpart_t
          Filter: ((a IS TRUE) OR (a IS NOT TRUE))
-   ->  Seq Scan on boolpart_default
-         Filter: ((a IS TRUE) OR (a IS NOT TRUE))
-(7 rows)
+(5 rows)
 
 explain (costs off) select * from boolpart where a is not true;
-             QUERY PLAN             
-------------------------------------
+           QUERY PLAN            
+---------------------------------
  Append
    ->  Seq Scan on boolpart_f
          Filter: (a IS NOT TRUE)
-   ->  Seq Scan on boolpart_t
-         Filter: (a IS NOT TRUE)
-   ->  Seq Scan on boolpart_default
-         Filter: (a IS NOT TRUE)
-(7 rows)
+(3 rows)
 
 explain (costs off) select * from boolpart where a is not true and a is not false;
-                       QUERY PLAN                       
---------------------------------------------------------
- Append
-   ->  Seq Scan on boolpart_f
-         Filter: ((a IS NOT TRUE) AND (a IS NOT FALSE))
-   ->  Seq Scan on boolpart_t
-         Filter: ((a IS NOT TRUE) AND (a IS NOT FALSE))
-   ->  Seq Scan on boolpart_default
-         Filter: ((a IS NOT TRUE) AND (a IS NOT FALSE))
-(7 rows)
+        QUERY PLAN        
+--------------------------
+ Result
+   One-Time Filter: false
+(2 rows)
 
 explain (costs off) select * from boolpart where a is unknown;
              QUERY PLAN             
@@ -1098,11 +1089,13 @@ explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2
 -----------------------------------------------------------------------
  Nested Loop
    ->  Append
-         ->  Seq Scan on mc2p1 t1
+         ->  Seq Scan on mc2p0 t1
                Filter: (a = 1)
-         ->  Seq Scan on mc2p2 t1_1
+         ->  Seq Scan on mc2p1 t1_1
                Filter: (a = 1)
-         ->  Seq Scan on mc2p_default t1_2
+         ->  Seq Scan on mc2p2 t1_2
+               Filter: (a = 1)
+         ->  Seq Scan on mc2p_default t1_3
                Filter: (a = 1)
    ->  Aggregate
          ->  Append
@@ -1110,13 +1103,21 @@ explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2
                      Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1))
                ->  Seq Scan on mc3p1 t2_1
                      Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1))
-               ->  Seq Scan on mc3p5 t2_2
+               ->  Seq Scan on mc3p2 t2_2
                      Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1))
-               ->  Seq Scan on mc3p7 t2_3
+               ->  Seq Scan on mc3p3 t2_3
                      Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1))
-               ->  Seq Scan on mc3p_default t2_4
+               ->  Seq Scan on mc3p4 t2_4
                      Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1))
-(20 rows)
+               ->  Seq Scan on mc3p5 t2_5
+                     Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1))
+               ->  Seq Scan on mc3p6 t2_6
+                     Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1))
+               ->  Seq Scan on mc3p7 t2_7
+                     Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1))
+               ->  Seq Scan on mc3p_default t2_8
+                     Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1))
+(30 rows)
 
 -- pruning should work fine, because prefix of keys is available
 explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 where t2.c = t1.b and abs(t2.b) = 1 and t2.a = 1) s where t1.a = 1;
@@ -1124,11 +1125,13 @@ explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2
 -----------------------------------------------------------------------
  Nested Loop
    ->  Append
-         ->  Seq Scan on mc2p1 t1
+         ->  Seq Scan on mc2p0 t1
                Filter: (a = 1)
-         ->  Seq Scan on mc2p2 t1_1
+         ->  Seq Scan on mc2p1 t1_1
                Filter: (a = 1)
-         ->  Seq Scan on mc2p_default t1_2
+         ->  Seq Scan on mc2p2 t1_2
+               Filter: (a = 1)
+         ->  Seq Scan on mc2p_default t1_3
                Filter: (a = 1)
    ->  Aggregate
          ->  Append
@@ -1138,7 +1141,7 @@ explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2
                      Filter: ((c = t1.b) AND (a = 1) AND (abs(b) = 1))
                ->  Seq Scan on mc3p_default t2_2
                      Filter: ((c = t1.b) AND (a = 1) AND (abs(b) = 1))
-(16 rows)
+(18 rows)
 
 -- pruning should work fine in this case, too.
 explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 where t2.a = 1 and abs(t2.b) = 1 and t2.c = 1) s where t1.a = 1;
@@ -1150,13 +1153,15 @@ explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2
                ->  Seq Scan on mc3p1 t2
                      Filter: ((a = 1) AND (c = 1) AND (abs(b) = 1))
    ->  Append
-         ->  Seq Scan on mc2p1 t1
+         ->  Seq Scan on mc2p0 t1
                Filter: (a = 1)
-         ->  Seq Scan on mc2p2 t1_1
+         ->  Seq Scan on mc2p1 t1_1
                Filter: (a = 1)
-         ->  Seq Scan on mc2p_default t1_2
+         ->  Seq Scan on mc2p2 t1_2
                Filter: (a = 1)
-(12 rows)
+         ->  Seq Scan on mc2p_default t1_3
+               Filter: (a = 1)
+(14 rows)
 
 --
 -- pruning with clauses containing <> operator
@@ -1271,22 +1276,16 @@ explain (costs off) select * from coll_pruning_multi where substr(a, 1) = 'a' co
          Filter: (substr(a, 1) = 'a'::text COLLATE "POSIX")
    ->  Seq Scan on coll_pruning_multi2
          Filter: (substr(a, 1) = 'a'::text COLLATE "POSIX")
-   ->  Seq Scan on coll_pruning_multi3
-         Filter: (substr(a, 1) = 'a'::text COLLATE "POSIX")
-(7 rows)
+(5 rows)
 
 -- pruning with just both columns constrained
 explain (costs off) select * from coll_pruning_multi where substr(a, 1) = 'e' collate "C" and substr(a, 1) = 'a' collate "POSIX";
                                                QUERY PLAN                                                
 ---------------------------------------------------------------------------------------------------------
  Append
-   ->  Seq Scan on coll_pruning_multi1
-         Filter: ((substr(a, 1) = 'e'::text COLLATE "C") AND (substr(a, 1) = 'a'::text COLLATE "POSIX"))
    ->  Seq Scan on coll_pruning_multi2
          Filter: ((substr(a, 1) = 'e'::text COLLATE "C") AND (substr(a, 1) = 'a'::text COLLATE "POSIX"))
-   ->  Seq Scan on coll_pruning_multi3
-         Filter: ((substr(a, 1) = 'e'::text COLLATE "C") AND (substr(a, 1) = 'a'::text COLLATE "POSIX"))
-(7 rows)
+(3 rows)
 
 --
 -- LIKE operators don't prune
@@ -1340,3 +1339,188 @@ explain (costs off) select * from rparted_by_int2 where a > 100000000000000;
 (3 rows)
 
 drop table lp, coll_pruning, rlp, mc3p, mc2p, boolpart, rp, coll_pruning_multi, like_op_noprune, lparted_by_int2, rparted_by_int2;
+-- hash partitioning
+create table hp (a int, b text) partition by hash (a, b);
+create table hp0 partition of hp for values with (modulus 4, remainder 0);
+create table hp3 partition of hp for values with (modulus 4, remainder 3);
+create table hp1 partition of hp for values with (modulus 4, remainder 1);
+create table hp2 partition of hp for values with (modulus 4, remainder 2);
+insert into hp values (null, null);
+insert into hp values (1, null);
+insert into hp values (1, 'xxx');
+insert into hp values (null, 'xxx');
+insert into hp values (10, 'xxx');
+insert into hp values (10, 'yyy');
+select tableoid::regclass, * from hp order by 1;
+ tableoid | a  |  b  
+----------+----+-----
+ hp0      |    | 
+ hp0      |  1 | 
+ hp0      |  1 | xxx
+ hp3      | 10 | yyy
+ hp1      |    | xxx
+ hp2      | 10 | xxx
+(6 rows)
+
+-- partial keys won't prune, nor would non-equality conditions
+explain (costs off) select * from hp where a = 1;
+       QUERY PLAN        
+-------------------------
+ Append
+   ->  Seq Scan on hp0
+         Filter: (a = 1)
+   ->  Seq Scan on hp1
+         Filter: (a = 1)
+   ->  Seq Scan on hp2
+         Filter: (a = 1)
+   ->  Seq Scan on hp3
+         Filter: (a = 1)
+(9 rows)
+
+explain (costs off) select * from hp where b = 'xxx';
+            QUERY PLAN             
+-----------------------------------
+ Append
+   ->  Seq Scan on hp0
+         Filter: (b = 'xxx'::text)
+   ->  Seq Scan on hp1
+         Filter: (b = 'xxx'::text)
+   ->  Seq Scan on hp2
+         Filter: (b = 'xxx'::text)
+   ->  Seq Scan on hp3
+         Filter: (b = 'xxx'::text)
+(9 rows)
+
+explain (costs off) select * from hp where a is null;
+         QUERY PLAN          
+-----------------------------
+ Append
+   ->  Seq Scan on hp0
+         Filter: (a IS NULL)
+   ->  Seq Scan on hp1
+         Filter: (a IS NULL)
+   ->  Seq Scan on hp2
+         Filter: (a IS NULL)
+   ->  Seq Scan on hp3
+         Filter: (a IS NULL)
+(9 rows)
+
+explain (costs off) select * from hp where b is null;
+         QUERY PLAN          
+-----------------------------
+ Append
+   ->  Seq Scan on hp0
+         Filter: (b IS NULL)
+   ->  Seq Scan on hp1
+         Filter: (b IS NULL)
+   ->  Seq Scan on hp2
+         Filter: (b IS NULL)
+   ->  Seq Scan on hp3
+         Filter: (b IS NULL)
+(9 rows)
+
+explain (costs off) select * from hp where a < 1 and b = 'xxx';
+                   QUERY PLAN                    
+-------------------------------------------------
+ Append
+   ->  Seq Scan on hp0
+         Filter: ((a < 1) AND (b = 'xxx'::text))
+   ->  Seq Scan on hp1
+         Filter: ((a < 1) AND (b = 'xxx'::text))
+   ->  Seq Scan on hp2
+         Filter: ((a < 1) AND (b = 'xxx'::text))
+   ->  Seq Scan on hp3
+         Filter: ((a < 1) AND (b = 'xxx'::text))
+(9 rows)
+
+explain (costs off) select * from hp where a <> 1 and b = 'yyy';
+                    QUERY PLAN                    
+--------------------------------------------------
+ Append
+   ->  Seq Scan on hp0
+         Filter: ((a <> 1) AND (b = 'yyy'::text))
+   ->  Seq Scan on hp1
+         Filter: ((a <> 1) AND (b = 'yyy'::text))
+   ->  Seq Scan on hp2
+         Filter: ((a <> 1) AND (b = 'yyy'::text))
+   ->  Seq Scan on hp3
+         Filter: ((a <> 1) AND (b = 'yyy'::text))
+(9 rows)
+
+-- pruning should work if non-null values are provided for all the keys
+explain (costs off) select * from hp where a is null and b is null;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Append
+   ->  Seq Scan on hp0
+         Filter: ((a IS NULL) AND (b IS NULL))
+(3 rows)
+
+explain (costs off) select * from hp where a = 1 and b is null;
+                QUERY PLAN                 
+-------------------------------------------
+ Append
+   ->  Seq Scan on hp0
+         Filter: ((b IS NULL) AND (a = 1))
+(3 rows)
+
+explain (costs off) select * from hp where a = 1 and b = 'xxx';
+                   QUERY PLAN                    
+-------------------------------------------------
+ Append
+   ->  Seq Scan on hp0
+         Filter: ((a = 1) AND (b = 'xxx'::text))
+(3 rows)
+
+explain (costs off) select * from hp where a is null and b = 'xxx';
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Append
+   ->  Seq Scan on hp1
+         Filter: ((a IS NULL) AND (b = 'xxx'::text))
+(3 rows)
+
+explain (costs off) select * from hp where a = 10 and b = 'xxx';
+                    QUERY PLAN                    
+--------------------------------------------------
+ Append
+   ->  Seq Scan on hp2
+         Filter: ((a = 10) AND (b = 'xxx'::text))
+(3 rows)
+
+explain (costs off) select * from hp where a = 10 and b = 'yyy';
+                    QUERY PLAN                    
+--------------------------------------------------
+ Append
+   ->  Seq Scan on hp3
+         Filter: ((a = 10) AND (b = 'yyy'::text))
+(3 rows)
+
+explain (costs off) select * from hp where (a = 10 and b = 'yyy') or (a = 10 and b = 'xxx') or (a is null and b is null);
+                                                       QUERY PLAN                                                        
+-------------------------------------------------------------------------------------------------------------------------
+ Append
+   ->  Seq Scan on hp0
+         Filter: (((a = 10) AND (b = 'yyy'::text)) OR ((a = 10) AND (b = 'xxx'::text)) OR ((a IS NULL) AND (b IS NULL)))
+   ->  Seq Scan on hp2
+         Filter: (((a = 10) AND (b = 'yyy'::text)) OR ((a = 10) AND (b = 'xxx'::text)) OR ((a IS NULL) AND (b IS NULL)))
+   ->  Seq Scan on hp3
+         Filter: (((a = 10) AND (b = 'yyy'::text)) OR ((a = 10) AND (b = 'xxx'::text)) OR ((a IS NULL) AND (b IS NULL)))
+(7 rows)
+
+-- hash partitiong pruning doesn't occur with <> operator clauses
+explain (costs off) select * from hp where a <> 1 and b <> 'xxx';
+                    QUERY PLAN                     
+---------------------------------------------------
+ Append
+   ->  Seq Scan on hp0
+         Filter: ((a <> 1) AND (b <> 'xxx'::text))
+   ->  Seq Scan on hp1
+         Filter: ((a <> 1) AND (b <> 'xxx'::text))
+   ->  Seq Scan on hp2
+         Filter: ((a <> 1) AND (b <> 'xxx'::text))
+   ->  Seq Scan on hp3
+         Filter: ((a <> 1) AND (b <> 'xxx'::text))
+(9 rows)
+
+drop table hp;
diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql
index 38b5f68658..86a3a3e7ce 100644
--- a/src/test/regress/sql/partition_prune.sql
+++ b/src/test/regress/sql/partition_prune.sql
@@ -60,7 +60,7 @@ explain (costs off) select * from rlp where 1 > a;	/* commuted */
 explain (costs off) select * from rlp where a <= 1;
 explain (costs off) select * from rlp where a = 1;
 explain (costs off) select * from rlp where a = 1::bigint;		/* same as above */
-explain (costs off) select * from rlp where a = 1::numeric;	/* only null can be pruned */
+explain (costs off) select * from rlp where a = 1::numeric;		/* no pruning */
 explain (costs off) select * from rlp where a <= 10;
 explain (costs off) select * from rlp where a > 10;
 explain (costs off) select * from rlp where a < 15;
@@ -237,3 +237,40 @@ create table rparted_by_int2_maxvalue partition of rparted_by_int2 for values fr
 explain (costs off) select * from rparted_by_int2 where a > 100000000000000;
 
 drop table lp, coll_pruning, rlp, mc3p, mc2p, boolpart, rp, coll_pruning_multi, like_op_noprune, lparted_by_int2, rparted_by_int2;
+
+-- hash partitioning
+create table hp (a int, b text) partition by hash (a, b);
+create table hp0 partition of hp for values with (modulus 4, remainder 0);
+create table hp3 partition of hp for values with (modulus 4, remainder 3);
+create table hp1 partition of hp for values with (modulus 4, remainder 1);
+create table hp2 partition of hp for values with (modulus 4, remainder 2);
+
+insert into hp values (null, null);
+insert into hp values (1, null);
+insert into hp values (1, 'xxx');
+insert into hp values (null, 'xxx');
+insert into hp values (10, 'xxx');
+insert into hp values (10, 'yyy');
+select tableoid::regclass, * from hp order by 1;
+
+-- partial keys won't prune, nor would non-equality conditions
+explain (costs off) select * from hp where a = 1;
+explain (costs off) select * from hp where b = 'xxx';
+explain (costs off) select * from hp where a is null;
+explain (costs off) select * from hp where b is null;
+explain (costs off) select * from hp where a < 1 and b = 'xxx';
+explain (costs off) select * from hp where a <> 1 and b = 'yyy';
+
+-- pruning should work if non-null values are provided for all the keys
+explain (costs off) select * from hp where a is null and b is null;
+explain (costs off) select * from hp where a = 1 and b is null;
+explain (costs off) select * from hp where a = 1 and b = 'xxx';
+explain (costs off) select * from hp where a is null and b = 'xxx';
+explain (costs off) select * from hp where a = 10 and b = 'xxx';
+explain (costs off) select * from hp where a = 10 and b = 'yyy';
+explain (costs off) select * from hp where (a = 10 and b = 'yyy') or (a = 10 and b = 'xxx') or (a is null and b is null);
+
+-- hash partitiong pruning doesn't occur with <> operator clauses
+explain (costs off) select * from hp where a <> 1 and b <> 'xxx';
+
+drop table hp;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 17bf55c1f5..71e86fc254 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1584,6 +1584,7 @@ ParsedText
 ParsedWord
 ParserSetupHook
 ParserState
+PartClauseInfo
 PartitionBoundInfo
 PartitionBoundInfoData
 PartitionBoundSpec
@@ -1596,6 +1597,11 @@ PartitionElem
 PartitionHashBound
 PartitionKey
 PartitionListValue
+PartitionPruneContext
+PartitionPruneStep
+PartitionPruneStepCombine
+PartitionPruneStepNoop
+PartitionPruneStepOp
 PartitionRangeBound
 PartitionRangeDatum
 PartitionRangeDatumKind
-- 
2.11.0