v44-0003-Faster-partition-pruning.patch

text/plain

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

Patch

Format: format-patch
Series: patch v44-0003
Subject: Faster partition pruning
File+
src/backend/catalog/partition.c 1144 0
src/backend/nodes/copyfuncs.c 36 0
src/backend/nodes/nodeFuncs.c 25 0
src/backend/optimizer/path/allpaths.c 28 0
src/backend/optimizer/util/Makefile 1 1
src/backend/optimizer/util/partprune.c 1694 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 3 0
src/include/nodes/primnodes.h 74 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 228 54
src/test/regress/sql/partition_prune.sql 38 1
src/tools/pgindent/typedefs.list 7 0
From 2bd9b37a5576f54d213699f53dacfb0841f1a7fe Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 22 Aug 2017 13:48:13 +0900
Subject: [PATCH v44 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               | 1144 +++++++++++++++++
 src/backend/nodes/copyfuncs.c                 |   36 +
 src/backend/nodes/nodeFuncs.c                 |   25 +
 src/backend/optimizer/path/allpaths.c         |   28 +
 src/backend/optimizer/util/Makefile           |    2 +-
 src/backend/optimizer/util/partprune.c        | 1694 +++++++++++++++++++++++++
 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                     |    3 +
 src/include/nodes/primnodes.h                 |   74 ++
 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 |  282 +++-
 src/test/regress/sql/partition_prune.sql      |   39 +-
 src/tools/pgindent/typedefs.list              |    7 +
 18 files changed, 3372 insertions(+), 73 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 39ee773d93..363b12836b 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -138,6 +138,18 @@ typedef struct PartitionRangeBound
 	bool		lower;			/* this is the lower (vs upper) bound */
 } PartitionRangeBound;
 
+/*
+ * The following struct describes the result of performing one
+ * PartitionPruneStep.
+ */
+typedef struct PruneStepResult
+{
+	Bitmapset *datum_offsets;
+
+	/* Set if we need to scan the default and/or the null partition, resp. */
+	bool	   scan_default;
+	bool	   scan_null;
+} PruneStepResult;
 
 static Oid	get_partition_parent_worker(Relation inhRel, Oid relid);
 static void get_partition_ancestors_worker(Relation inhRel, Oid relid,
@@ -197,6 +209,23 @@ static int	get_greatest_modulus(PartitionBoundInfo b);
 static uint64 compute_hash_value(int partnatts, FmgrInfo *partsupfunc,
 								 Datum *values, bool *isnull);
 
+static PruneStepResult *perform_pruning_base_step(PartitionPruneContext *context,
+						  PartitionPruneStepOp *opstep);
+static PruneStepResult *perform_pruning_combine_step(PartitionPruneContext *context,
+							 PartitionPruneStepCombine *cstep,
+							 PruneStepResult **step_results);
+static bool partkey_datum_from_expr(PartitionPruneContext *context,
+						Expr *expr, Datum *value);
+static PruneStepResult *get_matching_hash_bound(PartitionPruneContext *context,
+							int opstrategy, Datum *values, int nvalues,
+							FmgrInfo *partsupfunc, Bitmapset *nullkeys);
+static PruneStepResult *get_matching_list_bounds(PartitionPruneContext *context,
+							int opstrategy, Datum value, int nvalues,
+							FmgrInfo *partsupfunc, Bitmapset *nullkeys);
+static PruneStepResult *get_matching_range_bounds(PartitionPruneContext *context,
+							int opstrategy, Datum *values, int nvalues,
+							FmgrInfo *partsupfunc, Bitmapset *nullkeys);
+
 /*
  * RelationBuildPartitionDesc
  *		Form rel's partition descriptor
@@ -1620,9 +1649,1124 @@ 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)
+{
+	Bitmapset  *result;
+	int			num_steps = list_length(pruning_steps),
+				i;
+	PruneStepResult **step_results,
+					 *last_step_result;
+	ListCell   *lc;
+
+	/* If there are no pruning steps then all partitions match. */
+	if (num_steps == 0)
+		return bms_add_range(NULL, 0, context->nparts - 1);
+
+	/*
+	 * Allocate space for individual pruning steps to store its result.  Each
+	 * slot will hold a PruneStepResult after performing a given pruning step.
+	 * Later steps may use the result of one or more earlier steps.  Result of
+	 * of applying all pruning steps is the value contained in the slot of the
+	 * last pruning step.
+	 */
+	step_results = (PruneStepResult **)
+							palloc0(num_steps * sizeof(PruneStepResult *));
+	foreach(lc, pruning_steps)
+	{
+		PartitionPruneStep *step = lfirst(lc);
+
+		switch (nodeTag(step))
+		{
+			case T_PartitionPruneStepOp:
+				step_results[step->step_id] =
+					perform_pruning_base_step(context,
+											  (PartitionPruneStepOp *) step);
+				break;
+
+			case T_PartitionPruneStepCombine:
+				step_results[step->step_id] =
+					perform_pruning_combine_step(context,
+											(PartitionPruneStepCombine *) step,
+												 step_results);
+				break;
+
+			default:
+				elog(ERROR, "invalid pruning step type: %d",
+					 (int) nodeTag(step));
+		}
+	}
+
+	/*
+	 * At this point we know that offsets of all the datums whose
+	 * corresponding partitions need to be in the result, including special
+	 * null-accepting and default partitions.  Collect the actual partition
+	 * indexes now.
+	 */
+	i = -1;
+	result = NULL;
+	last_step_result = step_results[num_steps - 1];
+	while ((i = bms_next_member(last_step_result->datum_offsets, i)) >= 0)
+	{
+		int		partindex = context->boundinfo->indexes[i];
+
+		/*
+		 * In range and hash partitioning cases, some slots may contain -1,
+		 * indicating that no partition has been defined to accept a
+		 * given range of data or for a given remainder, respectively.
+		 * The default partition, if any, in case of range partitioning, will
+		 * be added to the result, because the specified range still satisfies
+		 * the query's conditions.
+		 */
+		if (partindex >= 0)
+			result = bms_add_member(result, partindex);
+	}
+
+	/* Add the null and/or default partition if needed and if present. */
+	if (last_step_result->scan_null)
+	{
+		Assert(context->strategy == PARTITION_STRATEGY_LIST);
+		if (partition_bound_accepts_nulls(context->boundinfo))
+			result = bms_add_member(result, context->boundinfo->null_index);
+	}
+	if (last_step_result->scan_default)
+	{
+		Assert(context->strategy == PARTITION_STRATEGY_LIST ||
+			   context->strategy == PARTITION_STRATEGY_RANGE);
+		if (partition_bound_has_default(context->boundinfo))
+			result = bms_add_member(result,
+									context->boundinfo->default_index);
+	}
+
+	return result;
+}
+
 /* Module-local functions */
 
 /*
+ * perform_pruning_base_step
+ *		Determines the indexes of datums that satisfy conditions specified in
+ *		'opstep'.
+ *
+ * Result also contains whether special null-accepting and/or default
+ * partition need to be scanned.
+ */
+static PruneStepResult *
+perform_pruning_base_step(PartitionPruneContext *context,
+						  PartitionPruneStepOp *opstep)
+{
+	ListCell   *lc1,
+			   *lc2;
+	int			keyno,
+				nvalues;
+	Datum		values[PARTITION_MAX_KEYS];
+	FmgrInfo	partsupfunc[PARTITION_MAX_KEYS];
+
+	/*
+	 * There better be the same number of expressions and compare functions.
+	 */
+	Assert(list_length(opstep->exprs) == list_length(opstep->cmpfns));
+
+	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
+	 * the 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.
+				 */
+				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_matching_hash_bound(context,
+										   opstep->opstrategy,
+										   values, nvalues,
+										   partsupfunc,
+										   opstep->nullkeys);
+		case PARTITION_STRATEGY_LIST:
+			return get_matching_list_bounds(context,
+											opstep->opstrategy,
+											values[0], nvalues,
+											&partsupfunc[0],
+											opstep->nullkeys);
+		case PARTITION_STRATEGY_RANGE:
+			return get_matching_range_bounds(context,
+											 opstep->opstrategy,
+											 values, nvalues,
+											 partsupfunc,
+											 opstep->nullkeys);
+
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+				 (int) context->strategy);
+			return NULL;
+	}
+}
+
+/*
+ * perform_pruning_combine_step
+ *		Determines the indexes of datums obtained by combining those given
+ *		by the steps identified by cstep->source_stepids
+ *
+ * Since cstep may refer to the result of earlier steps, we also receive
+ * step_results here.
+ */
+static PruneStepResult *
+perform_pruning_combine_step(PartitionPruneContext *context,
+							 PartitionPruneStepCombine *cstep,
+							 PruneStepResult **step_results)
+{
+	ListCell *lc1;
+	PruneStepResult *result = NULL;
+
+	/*
+	 * In some cases, the planner generates a combine step that doesn't
+	 * contain any argument steps, to signal us to not prune any partitions.
+	 * So, return indexes of all datums in that case, including null and/or
+	 * default partition, if any.
+	 */
+	result = (PruneStepResult *) palloc0(sizeof(PruneStepResult));
+	if (cstep->source_stepids == NIL)
+	{
+		PartitionBoundInfo boundinfo = context->boundinfo;
+
+		result->datum_offsets = bms_add_range(NULL, 0, boundinfo->ndatums - 1);
+		result->scan_default = partition_bound_has_default(boundinfo);
+		result->scan_null = partition_bound_accepts_nulls(boundinfo);
+		return result;
+	}
+	else
+	{
+		bool	firststep;
+
+		switch (cstep->combineOp)
+		{
+			case COMBINE_UNION:
+				foreach(lc1, cstep->source_stepids)
+				{
+					int step_id = lfirst_int(lc1);
+					PruneStepResult *step_result;
+
+					/*
+					 * step_results[step_id] must contain a valid result,
+					 * which is confirmed by the fact that cstep's step_id is
+					 * greater than step_id.
+					 */
+					if (step_id >= cstep->step.step_id)
+						elog(ERROR, "invalid pruning combine step argument");
+					step_result = step_results[step_id];
+					Assert(step_result != NULL);
+
+					/*
+					 * Add indexes of datums given by the argument step's
+					 * result.
+					 */
+					result->datum_offsets =
+								bms_add_members(result->datum_offsets,
+												step_result->datum_offsets);
+					/* Update whether to scan null and default partitions. */
+					if (!result->scan_null)
+						result->scan_null = step_result->scan_null;
+					if (!result->scan_default)
+						result->scan_default = step_result->scan_default;
+				}
+				break;
+
+			case COMBINE_INTERSECT:
+				firststep = true;
+				foreach(lc1, cstep->source_stepids)
+				{
+					int step_id = lfirst_int(lc1);
+					PruneStepResult *step_result;
+
+					if (step_id >= cstep->step.step_id)
+						elog(ERROR, "invalid pruning combine step argument");
+					step_result = step_results[step_id];
+					Assert(step_result != NULL);
+
+					if (firststep)
+					{
+						/* Copy step's result the first time. */
+						result->datum_offsets = step_result->datum_offsets;
+						result->scan_null = step_result->scan_null;
+						result->scan_default = step_result->scan_default;
+						firststep = false;
+					}
+					else
+					{
+						/*
+						 * Only keep indexes of datums that are in argument
+						 * step's result.
+						 */
+						result->datum_offsets =
+								bms_int_members(result->datum_offsets,
+												step_result->datum_offsets);
+						/*
+						 * Update whether to scan null and default partitions.
+						 */
+						if (result->scan_null)
+							result->scan_null = step_result->scan_null;
+						if (result->scan_default)
+							result->scan_default =
+												step_result->scan_default;
+					}
+				}
+				break;
+
+			case COMBINE_INVERT:
+				{
+					PartitionBoundInfo boundinfo = context->boundinfo;
+					int		source_step_id;
+					PruneStepResult *source;
+
+					/*
+					 * There should only ever be one source step to invert the
+					 * result of.
+					 */
+					Assert(list_length(cstep->source_stepids) == 1);
+					source_step_id = linitial_int(cstep->source_stepids);
+					if (source_step_id >= cstep->step.step_id)
+						elog(ERROR, "invalid pruning combine step argument");
+					source = step_results[source_step_id];
+					Assert(source != NULL);
+
+					/* First add possible datum offsets. */
+					result->datum_offsets =
+										bms_add_range(NULL, 0,
+													  boundinfo->ndatums - 1);
+					/* Then remove from it those present in the source. */
+					result->datum_offsets =
+									bms_del_members(result->datum_offsets,
+													source->datum_offsets);
+					Assert(!source->scan_null ||
+							partition_bound_accepts_nulls(boundinfo));
+					result->scan_null = !source->scan_null;
+					Assert(!source->scan_default ||
+							partition_bound_has_default(boundinfo));
+					result->scan_default = !source->scan_default;
+				}
+				break;
+
+			default:
+				elog(ERROR, "invalid pruning combine op: %d",
+					 (int) cstep->combineOp);
+		}
+	}
+
+	return result;
+}
+
+/*
+ * 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_matching_hash_bound
+ *		Determine offset of the hash bound matching the specified value,
+ *		considering that all the non-null values come from clauses containing
+ *		a compatible hash eqaulity operator and any keys that are null come
+ *		from a IS NULL clause
+ *
+ * In most cases, the result would contain just one bound's offset, although
+ * the set may be empty if the corresponding hash partition has not been
+ * created.
+ *
+ * Since there are neither of the special partitions (null and default) in
+ * case of hash partitioning, scan_null and scan_default are not set.
+ *
+ * '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 PruneStepResult *
+get_matching_hash_bound(PartitionPruneContext *context,
+						int opstrategy, Datum *values, int nvalues,
+						FmgrInfo *partsupfunc, Bitmapset *nullkeys)
+{
+	PruneStepResult *result;
+	PartitionBoundInfo boundinfo = context->boundinfo;
+	int		   *partindices = boundinfo->indexes;
+	int			partnatts = context->partnatts;
+	bool		isnull[PARTITION_MAX_KEYS];
+	int			i;
+	uint64		rowHash;
+	int			greatest_modulus;
+
+	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);
+
+	result = (PruneStepResult *) palloc0(sizeof(PruneStepResult));
+
+	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);
+
+	if (partindices[rowHash % greatest_modulus] >= 0)
+		result->datum_offsets = bms_make_singleton(rowHash % greatest_modulus);
+	result->scan_null = result->scan_default = false;
+
+	return result;
+}
+
+/*
+ * get_matching_list_bounds
+ *		Determine the offsets of list bounds matching the specified value,
+ *		according to the semantics of the given operator strategy
+ *
+ * If special partitions (null and default) need to be scanned for given
+ * values, set scan_null and scan_default in result if present.
+ *
+ * 'nvalues', if non-zero, should be exactly 1, because of list partitioning.
+
+ * 'value' contains the value to use for pruning
+
+ * 'opstrategy' if non-zero must be a btree strategy number
+
+ * 'partsupfunc' contains the list partitioning comparison function to be used
+ * to perform partition_list_bsearch
+
+ * 'nullkeys' is the set of partition keys that are null.
+ */
+static PruneStepResult *
+get_matching_list_bounds(PartitionPruneContext *context,
+						 int opstrategy, Datum value, int nvalues,
+						 FmgrInfo *partsupfunc, Bitmapset *nullkeys)
+{
+	PruneStepResult *result;
+	PartitionBoundInfo boundinfo = context->boundinfo;
+	int			off,
+				minoff,
+				maxoff;
+	bool		is_equal;
+	bool		inclusive = false;
+	Oid		   *partcollation = context->partcollation;
+
+	Assert(context->strategy == PARTITION_STRATEGY_LIST);
+	Assert(context->partnatts == 1);
+
+	result = (PruneStepResult *) palloc0(sizeof(PruneStepResult));
+
+	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))
+		{
+			result->scan_null = true;
+			return result;
+		}
+		else if (partition_bound_has_default(boundinfo))
+		{
+			result->scan_default = true;
+			return result;
+		}
+		else
+			return result;
+	}
+
+	/*
+	 * 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))
+			result->scan_default = true;
+		return result;
+	}
+
+	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->scan_default = true;
+
+	/*
+	 * If there are no values to compare with the datums in boundinfo, it
+	 * means the caller asked for partitions for all non-null datums.  Add
+	 * indexes of *all* partitions.
+	 */
+	if (nvalues == 0)
+	{
+		result->datum_offsets = bms_add_range(NULL, minoff, maxoff);
+		return result;
+	}
+
+	switch (opstrategy)
+	{
+		case BTEqualStrategyNumber:
+			off = partition_list_bsearch(partsupfunc,
+										 partcollation,
+										 boundinfo, value,
+										 &is_equal);
+			if (off >= 0 && is_equal)
+			{
+				Assert(boundinfo->indexes[off] >= 0);
+				result->datum_offsets = bms_make_singleton(off);
+				return result;
+			}
+			else if (partition_bound_has_default(boundinfo))
+			{
+				result->scan_default = true;
+				return result;
+			}
+			else
+				return result;
+			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 partitions 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, but we must've set context->scan_default
+			 * above anyway 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, but we must've set context->scan_default
+			 * above anyway if one exists.
+			 */
+			if (off < 0)
+				return result;
+
+			maxoff = off;
+			break;
+
+		default:
+			elog(ERROR, "invalid strategy number %d", opstrategy);
+			break;
+	}
+
+	result->datum_offsets = bms_add_range(NULL, minoff, maxoff);
+	return result;
+}
+
+/*
+ * get_matching_range_datums
+ *		Determine the offsets of range bounds matching the specified values,
+ *		according to the semantics of the given operator strategy
+ *
+ * Each datum whose offset is in result is to be treated as the upper bound of
+ * the partition that will contain the desired values.
+ *
+ * If default partition needs to be scanned for given values, set scan_default
+ * in result if present.
+ *
+ * 'nvalues', if non-zero, should be <= context->partnatts - 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 the range partitioning comparison functions to be
+ * used to perform partition_range_datum_bsearch or partition_rbound_datum_cmp
+ * using.
+ *
+ * 'nullkeys' is the set of partition keys that are null.
+ */
+static PruneStepResult *
+get_matching_range_bounds(PartitionPruneContext *context,
+						  int opstrategy, Datum *values, int nvalues,
+						  FmgrInfo *partsupfunc, Bitmapset *nullkeys)
+{
+	PruneStepResult *result;
+	PartitionBoundInfo boundinfo = context->boundinfo;
+	Oid		   *partcollation = context->partcollation;
+	int			partnatts = context->partnatts;
+	int		   *partindices = boundinfo->indexes;
+	int			off,
+				minoff,
+				maxoff,
+				i;
+	bool		is_equal;
+	bool		inclusive = false;
+
+	Assert(context->strategy == PARTITION_STRATEGY_RANGE);
+	Assert(nvalues <= partnatts);
+
+	result = (PruneStepResult *) palloc0(sizeof(PruneStepResult));
+
+	/*
+	 * 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))
+			result->scan_default = true;
+		return result;
+	}
+
+	minoff = 0;
+	maxoff = boundinfo->ndatums;
+
+	/*
+	 * If there are no values to compare with the datums in boundinfo, it
+	 * means the caller asked for partitions for all non-null datums.  Add
+	 * indexes of *all* partitions, including the default partition if one
+	 * exists.
+	 */
+	if (nvalues == 0)
+	{
+		if (partindices[minoff] < 0)
+			minoff++;
+		if (partindices[maxoff] < 0)
+			maxoff--;
+
+		if (partition_bound_has_default(boundinfo))
+			result->scan_default = true;
+
+		result->datum_offsets = bms_add_range(NULL, minoff, maxoff);
+		return result;
+	}
+
+	/*
+	 * If the query does not constrain all key columns, we'll need to scan the
+	 * the default partition, if any.
+	 */
+	if (nvalues < partnatts && partition_bound_has_default(boundinfo))
+		result->scan_default = true;
+
+	switch (opstrategy)
+	{
+		case BTEqualStrategyNumber:
+			/*
+			 * Look for the smallest bound that is = look-up value.
+			 */
+			off = partition_range_datum_bsearch(partsupfunc,
+												partcollation,
+												boundinfo,
+												nvalues, values,
+												&is_equal);
+
+			if (off >= 0 && is_equal)
+			{
+				if (nvalues == partnatts)
+				{
+					/* There can only be zero or one matching partition. */
+					if (partindices[off + 1] >= 0)
+					{
+						result->datum_offsets = bms_make_singleton(off + 1);
+						return result;
+					}
+					else if (partition_bound_has_default(boundinfo))
+					{
+						result->scan_default = true;
+						return result;
+					}
+					else
+						return result;
+				}
+				else
+				{
+					int			saved_off = off;
+
+					/*
+					 * Since the look-up value contains only a prefix of keys,
+					 * we must find other bounds that may also match the
+					 * prefix.  partition_range_datum_bsearch() returns the
+					 * offset of one of them, find others by checking adjacent
+					 * bounds.
+					 */
+
+					/*
+					 * First find greatest bound that's smaller than the
+					 * look-up value.
+					 */
+					while (off >= 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--;
+					}
+
+					Assert(0 ==
+							partition_rbound_datum_cmp(partsupfunc,
+													   partcollation,
+													   boundinfo->datums[off],
+													   boundinfo->kind[off],
+													   values, nvalues));
+					/*
+					 * We can treat off as the offset of the smallest bound to
+					 * be included in the result, if we know it is the upper
+					 * bound of the partition in which the look-up value could
+					 * possibly exist.  One case it couldn't is if the bound,
+					 * or precisely the matched portion of its prefix, is not
+					 * inclusive.
+					 */
+					if (boundinfo->kind[off][nvalues] ==
+						PARTITION_RANGE_DATUM_MINVALUE)
+						off++;
+
+					minoff = off;
+
+					/*
+					 * Now find smallest bound that's greater than the look-up
+					 * value.
+					 */
+					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++;
+					}
+
+					Assert(0 ==
+							partition_rbound_datum_cmp(partsupfunc,
+													   partcollation,
+													   boundinfo->datums[off],
+													   boundinfo->kind[off],
+													   values, nvalues));
+
+					/*
+					 * off + 1, then would be the offset of the greatest bound
+					 * to be included in the result.
+					 */
+					maxoff = off + 1;
+				}
+
+				/*
+				 * Skip if minoff/maxoff are actually the upper bound of a
+				 * un-assigned portion of values.
+				 */
+				if (partindices[minoff] < 0 && minoff < boundinfo->ndatums)
+					minoff++;
+				if (partindices[maxoff] < 0 && maxoff >= 1)
+					maxoff--;
+
+				/*
+				 * 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.
+				 */
+				if (partition_bound_has_default(boundinfo))
+				{
+					for (i = minoff; i <= maxoff; i++)
+						if (partindices[i] < 0)
+							result->scan_default = true;
+				}
+
+				Assert(minoff >= 0 && maxoff >= 0);
+				result->datum_offsets = bms_add_range(NULL, minoff, maxoff);
+				return result;
+			}
+			else if (off >= 0)	/* !is_equal */
+			{
+				/*
+				 * Look-up value falls in the range between some bounds in
+				 * boundinfo.  off would be the offset of the greatest
+				 * bound that is <= look-up value, so add off + 1 to the
+				 * result instead as the offset of the upper bound of the
+				 * only partition that may contain the look-up value.
+				 */
+				if (partindices[off + 1] >= 0)
+				{
+					result->datum_offsets = bms_make_singleton(off + 1);
+					return result;
+				}
+				else if (partition_bound_has_default(boundinfo))
+				{
+					result->scan_default = true;
+					return result;
+				}
+				else
+					return result;
+			}
+			/*
+			 * off < 0, meaning the look-up value is smaller that all bounds,
+			 * so only the default partition, if any, qualifies.
+			 */
+			else if (partition_bound_has_default(boundinfo))
+			{
+				result->scan_default = true;
+				return result;
+			}
+			/* none qualifies. */
+			else
+				return result;
+
+		case BTGreaterEqualStrategyNumber:
+			inclusive = true;
+			/* fall through */
+		case BTGreaterStrategyNumber:
+			/*
+			 * Look for the smallest bound that is > or >= look-up value
+			 * and set minoff to its offset.
+			 */
+			off = partition_range_datum_bsearch(partsupfunc,
+												partcollation,
+												boundinfo,
+												nvalues, values,
+												&is_equal);
+			if (off < 0)
+			{
+				/*
+				 * All bounds are greater than the look-up value, so include
+				 * all of them in the result.
+				 */
+				minoff = 0;
+			}
+			else
+			{
+				if (is_equal && nvalues < partnatts)
+				{
+					/*
+					 * Since the look-up value contains only a prefix of keys,
+					 * we must find other bounds that may also match the
+					 * prefix.  partition_range_datum_bsearch() returns the
+					 * offset of one of them, find others by checking adjacent
+					 * bounds.
+					 *
+					 * Based on whether the look-up values is inclusive or
+					 * not, we must either include the indexes of all such
+					 * bounds in the result (that is, set minoff to the index
+					 * of smallest such bound) or find the smallest one that's
+					 * greater than the look-up value and set minoff to that.
+					 */
+					while (off >= 1 && 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)
+							break;
+
+						off = nextoff;
+					}
+
+					Assert(0 ==
+							partition_rbound_datum_cmp(partsupfunc,
+													   partcollation,
+													   boundinfo->datums[off],
+													   boundinfo->kind[off],
+													   values, nvalues));
+
+					minoff = inclusive ? off : off + 1;
+				}
+				/*
+				 * Look-up value falls in the range between some bounds in
+				 * boundinfo.  off would be the offset of the greatest
+				 * bound that is <= look-up value, so add off + 1 to the
+				 * result instead as the offset of the upper bound of the
+				 * smallest partition that may contain the look-up value.
+				 */
+				else
+					minoff = off + 1;
+			}
+			break;
+
+		case BTLessEqualStrategyNumber:
+			inclusive = true;
+			/* fall through */
+		case BTLessStrategyNumber:
+			/*
+			 * Look for the greatest bound that is < or <= look-up value
+			 * and set minoff to its offset.
+			 */
+			off = partition_range_datum_bsearch(partsupfunc,
+												partcollation,
+												boundinfo,
+												nvalues, values,
+												&is_equal);
+			if (off < 0)
+			{
+				/*
+				 * All bounds are greater than the key, so we could only
+				 * expect to find the look-up key in the default partition.
+				 */
+				if (partition_bound_has_default(boundinfo))
+					result->scan_default = true;
+				return result;
+			}
+			else
+			{
+				/*
+				 * See the comment above.
+				 */
+				if (is_equal && nvalues < partnatts)
+				{
+					while (off >= 1 && 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)
+							break;
+
+						off = nextoff;
+					}
+
+					Assert(0 ==
+							partition_rbound_datum_cmp(partsupfunc,
+													   partcollation,
+													   boundinfo->datums[off],
+													   boundinfo->kind[off],
+													   values, nvalues));
+
+					maxoff = inclusive ? off + 1: off;
+				}
+				/*
+				 * Look-up value falls in the range between some bounds in
+				 * boundinfo.  off would be the offset of the greatest
+				 * bound that is <= look-up value, so add off + 1 to the
+				 * result instead as the offset of the upper bound of the
+				 * greatest partition that may contain look-up value.  If
+				 * the look-up value had exactly matched the bound, but it
+				 * isn't inclusive, no need add the adjacent partition.
+				 */
+				else if (!is_equal || inclusive)
+					maxoff = off + 1;
+				else
+					maxoff = off;
+			}
+			break;
+
+		default:
+			elog(ERROR, "invalid strategy number %d", opstrategy);
+			break;
+	}
+
+	/*
+	 * Skip a gap and when doing so, check if the bound contains a finite
+	 * value to decide if we need to add the default partition.  If it's an
+	 * infinite bound, we need not add the default partition, as having an
+	 * infinite bound means the partition in question catches any values
+	 * that would otherwise be in the default partition.
+	 */
+	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->scan_default = true;
+		}
+
+		minoff++;
+	}
+
+	/*
+	 * Skip a gap.  See the above comment about how we decide whether or
+	 * or not to scan the default partition based whether the datum that
+	 * will become the maximum datum is finite or not.
+	 */
+	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->scan_default = true;
+		}
+
+		maxoff--;
+	}
+
+	if (partition_bound_has_default(boundinfo))
+	{
+		/*
+		 * 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)
+				result->scan_default = true;
+	}
+
+	Assert(minoff >= 0 && maxoff >= 0);
+	if (minoff > maxoff)
+		return result;
+	result->datum_offsets = bms_add_range(NULL, minoff, maxoff);
+	return result;
+}
+
+/*
  * 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..d0ab4273c8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2133,6 +2133,36 @@ _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;
+}
+
+/*
+ * _copyPartitionPruneStepCombine
+ */
+static PartitionPruneStepCombine *
+_copyPartitionPruneStepCombine(const PartitionPruneStepCombine *from)
+{
+	PartitionPruneStepCombine *newnode = makeNode(PartitionPruneStepCombine);
+
+	COPY_SCALAR_FIELD(combineOp);
+	COPY_NODE_FIELD(source_stepids);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *						relation.h copy functions
  *
@@ -5024,6 +5054,12 @@ copyObjectImpl(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_PartitionPruneStepOp:
+			retval = _copyPartitionPruneStepOp(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..52de893e89 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -2146,6 +2146,17 @@ 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_PartitionPruneStepCombine:
+			/* no expression subnodes */
+			break;
 		case T_JoinExpr:
 			{
 				JoinExpr   *join = (JoinExpr *) node;
@@ -2932,6 +2943,20 @@ 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_PartitionPruneStepCombine:
+			/* no expression sub-nodes */
+			return (Node *) copyObject(node);
 		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..48fdeacac3
--- /dev/null
+++ b/src/backend/optimizer/util/partprune.c
@@ -0,0 +1,1694 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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;
+
+/*
+ * PartClauseMatchStatus
+ *		Describes the result match_clause_to_partition_key produces for a
+ *		given clause and the partition key to match with that are passed to it
+ */
+typedef enum PartClauseMatchStatus
+{
+	PARTCLAUSE_NOMATCH,
+	PARTCLAUSE_MATCH_CLAUSE,
+	PARTCLAUSE_MATCH_NULLNESS,
+	PARTCLAUSE_MATCH_STEPS,
+	PARTCLAUSE_MATCH_CONTRADICT,
+	PARTCLAUSE_UNSUPPORTED
+}			PartClauseMatchStatus;
+
+/*
+ * GeneratePruningStepsContext
+ *		Information about the current state of generation of "pruning steps"
+ *		for a given set of clauses
+ *
+ * generate_partition_pruning_steps() initializes an instance of this struct,
+ * which is used throughout the step generation process.
+ */
+typedef struct GeneratePruningStepsContext
+{
+	int		next_step_id;
+	List   *steps;
+}			GeneratePruningStepsContext;
+
+static List *generate_partition_pruning_steps_internal(RelOptInfo *rel,
+									  GeneratePruningStepsContext *context,
+									  List *clauses,
+									  bool *constfalse);
+static PartitionPruneStep *generate_pruning_steps_from_opexprs(
+								PartitionScheme part_scheme,
+								GeneratePruningStepsContext *context,
+								List **keyclauses,
+								Bitmapset *nullkeys);
+static PartClauseMatchStatus match_clause_to_partition_key(RelOptInfo *rel,
+								GeneratePruningStepsContext *context,
+								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(GeneratePruningStepsContext *context,
+					   int step_opstrategy,
+					   Expr *step_lastexpr,
+					   Oid step_lastcmpfn,
+					   int step_lastkeyno,
+					   Bitmapset *step_nullkeys,
+					   List *prefix);
+static List *get_steps_using_prefix_recurse(GeneratePruningStepsContext *context,
+							   int step_opstrategy,
+							   Expr *step_lastexpr,
+							   Oid step_lastcmpfn,
+							   int step_lastkeyno,
+							   Bitmapset *step_nullkeys,
+							   ListCell *start,
+							   List *step_exprs,
+							   List *step_cmpfns);
+static Node *generate_pruning_step_op(GeneratePruningStepsContext *context,
+							int opstrategy,
+							List *exprs, List *cmpfns, Bitmapset *nullkeys);
+static Node *generate_pruning_step_combine(GeneratePruningStepsContext *context,
+							List *source_stepids,
+							PartitionPruneCombineOp combineOp);
+
+/*
+ * 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)
+{
+	GeneratePruningStepsContext context;
+
+	context.next_step_id = 0;
+	context.steps = NIL;
+
+	/* 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. */
+	(void) generate_partition_pruning_steps_internal(rel, &context, clauses,
+													 constfalse);
+
+	return context.steps;
+}
+
+/* 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 are used as a look up key to search partitions by comparing the
+ * values with partition bounds.  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 will combine
+ * results of those steps.
+ *
+ * All of the generated steps are added to the global array context->steps and
+ * each one gets an identifier which is unique across all recursive
+ * invocations.
+ *
+ * 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,
+									GeneratePruningStepsContext *context,
+										  List *clauses,
+										  bool *constfalse)
+{
+	PartitionScheme part_scheme = rel->part_scheme;
+	List	   *keyclauses[PARTITION_MAX_KEYS],
+			   *ne_clauses = NIL;
+	Bitmapset  *nullkeys = NULL,
+			   *notnullkeys = NULL;
+	bool		generate_opsteps = false;
+	List	   *result = NIL;
+	ListCell   *lc;
+
+	*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))
+		{
+			/*
+			 * Generate steps for arguments.
+			 *
+			 * While steps generated for the arguments themselves will be
+			 * added to context->steps during recursion and will be evaluated
+			 * indepdently, collect their step IDs to be stored in the combine
+			 * step we'll be creating.
+			 */
+			if (or_clause((Node *) clause))
+			{
+				List	   *arg_stepids = 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, context,
+																  list_make1(arg),
+																  &arg_constfalse);
+					if (!arg_constfalse)
+						all_args_constfalse = false;
+
+					if (argsteps != NIL)
+					{
+						PartitionPruneStep *step;
+
+						Assert(list_length(argsteps) == 1);
+						step =  (PartitionPruneStep *) linitial(argsteps);
+						arg_stepids = lappend_int(arg_stepids, step->step_id);
+					}
+					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
+						 * source_stepids 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.  That is, we effectively ignore
+						 * this OR arm.
+						 */
+						List	   *partconstr = rel->partition_qual;
+						PartitionPruneStep *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 = (PartitionPruneStep *)
+							generate_pruning_step_combine(context,
+														  NIL,
+														  COMBINE_UNION);
+						arg_stepids = lappend_int(arg_stepids,
+												  orstep->step_id);
+					}
+				}
+
+				*constfalse = all_args_constfalse;
+
+				/* Check if any contradicting clauses were found */
+				if (*constfalse)
+					return NIL;
+
+				result =
+					lappend(result,
+							generate_pruning_step_combine(context,
+														  arg_stepids,
+														  COMBINE_UNION));
+				continue;
+			}
+			else if (and_clause((Node *) clause))
+			{
+				List	   *args = ((BoolExpr *) clause)->args;
+				List	   *argsteps,
+						   *arg_stepids = NIL;
+				ListCell   *lc1;
+
+				/*
+				 * 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,
+															  context,
+															  args,
+															  constfalse);
+				if (*constfalse)
+					return NIL;
+
+				foreach (lc1, argsteps)
+				{
+					PartitionPruneStep *step = lfirst(lc1);
+
+					arg_stepids = lappend_int(arg_stepids, step->step_id);
+				}
+
+				result =
+					lappend(result,
+							generate_pruning_step_combine(context,
+														  arg_stepids,
+														  COMBINE_UNION));
+				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, context,
+												  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);
+					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.
+	 * What we do is we convert what would originally be:
+	 *
+	 *		ne_clause1 AND ne_clause2 .. AND ne_clauseN
+	 *
+	 * into:
+	 *
+	 *		NOT (eq_clause1 OR eq_clause2 .. OR eq_clauseN)
+	 *
+	 * where each of the eq_clauses are constructed with valid negator of the
+	 * <> operator appearing in corresponding ne_clauses.
+	 */
+	if (ne_clauses != NIL)
+	{
+		List   *step_ids = NIL;
+		PartitionPruneStep *unionStep,
+						   *diffStep,
+						   *nullpartStep;
+
+		Assert(part_scheme->strategy == PARTITION_STRATEGY_LIST);
+		foreach(lc, ne_clauses)
+		{
+			PartClauseInfo *pc = lfirst(lc);
+			PartitionPruneStep *step;
+
+			/*
+			 * Generate a opstep using what must be a btree = operator, that
+			 * is, the negator of <> originally appearing in the clause.
+			 */
+			step = (PartitionPruneStep *)
+						generate_pruning_step_op(context,
+												 BTEqualStrategyNumber,
+												 list_make1(pc->expr),
+												 list_make1_oid(pc->cmpfn),
+												 NULL);
+			step_ids = lappend_int(step_ids, step->step_id);
+		}
+
+		/*
+		 * Moreover, we must add an explicit step as an argument of the union
+		 * step being built to select the NULL-only partition (if any), so
+		 * that it is excluded from the final result by subsequent inversion.
+		 * That's because all these <> clauses are strict and hence won't
+		 * select any records of the NULL-only partition.
+		 */
+		Assert(part_scheme->partnatts == 1);
+		nullpartStep = (PartitionPruneStep *)
+						   generate_pruning_step_op(context, 0, NIL, NIL,
+													bms_make_singleton(0));
+		step_ids = lappend_int(step_ids, nullpartStep->step_id);
+
+		/* Combine all opsteps above using a UNION combine step first. */
+		unionStep = (PartitionPruneStep *)
+						generate_pruning_step_combine(context, step_ids,
+													  COMBINE_UNION);
+		/* Then slap on a DIFFERENCE combine step. */
+		diffStep = (PartitionPruneStep *)
+					generate_pruning_step_combine(context,
+										  list_make1_int(unionStep->step_id),
+												  COMBINE_INVERT);
+
+		result = lappend(result, diffStep);
+	}
+
+	/*
+	 * generate_opsteps set to false means no OpExprs were directly presemt in
+	 * the input list.
+	 */
+	if (!generate_opsteps)
+	{
+		/*
+		 * Generate one prune step for the information derived from IS NULL, if
+		 * any.  To prune hash partitions, we must have found IS NULL clauses
+		 * for all partition keys.
+		 */
+		if (!bms_is_empty(nullkeys) &&
+			(part_scheme->strategy != PARTITION_STRATEGY_HASH ||
+			 bms_num_members(nullkeys) == part_scheme->partnatts))
+			result = lappend(result,
+							 generate_pruning_step_op(context, 0, NIL, NIL,
+													  nullkeys));
+
+		/*
+		 * 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.  Hash partitioning expects to see actual
+		 * values to perform any pruning.
+		 */
+		if (!bms_is_empty(notnullkeys) &&
+			part_scheme->strategy != PARTITION_STRATEGY_HASH)
+			result = lappend(result,
+							 generate_pruning_step_op(context, 0, NIL, NIL,
+													  NULL));
+	}
+	else
+	{
+		PartitionPruneStep *step;
+
+		/* Generate pruning steps from OpExpr clauses in keyclauses. */
+		step = generate_pruning_steps_from_opexprs(part_scheme, context,
+												   keyclauses, nullkeys);
+		if (step != NULL)
+			result = lappend(result, step);
+	}
+
+	/*
+	 * Finally, results from all entries appearing in result should be
+	 * combined using an AND combine step, if there are more than 1.
+	 */
+	if (list_length(result) > 1)
+	{
+		List *step_ids = NIL;
+
+		foreach(lc, result)
+		{
+			PartitionPruneStep *step = lfirst(lc);
+
+			step_ids = lappend_int(step_ids, step->step_id);
+		}
+
+		result = lappend(result,
+						 generate_pruning_step_combine(context, step_ids,
+													   COMBINE_INTERSECT));
+	}
+
+	return result;
+}
+
+/*
+ * generate_pruning_steps_from_opexprs
+ *
+ * 'keyclauses' contains 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.
+ */
+static PartitionPruneStep *
+generate_pruning_steps_from_opexprs(PartitionScheme part_scheme,
+									GeneratePruningStepsContext *context,
+									List **keyclauses,
+									Bitmapset *nullkeys)
+{
+	ListCell   *lc;
+	List	   *opsteps = NIL;
+	List	   *btree_clauses[BTMaxStrategyNumber],
+			   *hash_clauses[HTMaxStrategyNumber];
+	bool		need_next_less,
+				need_next_eq,
+				need_next_greater;
+	int			i;
+
+	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 NULL;
+
+		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(context, 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 (which 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(context,
+													   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;
+	}
+
+	/* Finally, add a combine step to mutualy AND opsteps, if needed. */
+	if (list_length(opsteps) > 1)
+	{
+		List *opstep_ids = NIL;
+
+		foreach(lc, opsteps)
+		{
+			PartitionPruneStep *step = lfirst(lc);
+
+			opstep_ids = lappend_int(opstep_ids, step->step_id);
+		}
+
+		return (PartitionPruneStep *)
+					generate_pruning_step_combine(context, opstep_ids,
+												  COMBINE_INTERSECT);
+	}
+	else if (opsteps != NIL)
+		return linitial(opsteps);
+
+	return NULL;
+}
+
+/*
+ * 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,
+							  GeneratePruningStepsContext *context,
+							  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;
+		Oid			exprtype;
+
+		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. */
+		exprtype = exprType((Node *) expr);
+		if (exprtype != 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, 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, context,
+														  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, context,
+														  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(GeneratePruningStepsContext *context,
+					   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)
+		return list_make1(generate_pruning_step_op(context, step_opstrategy,
+											   list_make1(step_lastexpr),
+											   list_make1_oid(step_lastcmpfn),
+											   step_nullkeys));
+
+	/* Recurse to generate steps for various combinations. */
+	return get_steps_using_prefix_recurse(context,
+										  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(GeneratePruningStepsContext *context,
+							   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(context,
+														   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);
+			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);
+
+			result = lappend(result,
+							 generate_pruning_step_op(context,
+													  step_opstrategy,
+													  step_exprs1,
+													  step_cmpfns1,
+													  step_nullkeys));
+		}
+	}
+
+	return result;
+}
+
+/*
+ * Following functions generate pruning steps of various types.  Each step
+ * that's created is added to a global context->steps and receive a globally
+ * unique identifier that's sourced from context->next_step_id.
+ */
+
+static Node *
+generate_pruning_step_op(GeneratePruningStepsContext *context,
+						 int opstrategy, List *exprs, List *cmpfns,
+						 Bitmapset *nullkeys)
+{
+	PartitionPruneStepOp *opstep = makeNode(PartitionPruneStepOp);
+
+	opstep->step.step_id = context->next_step_id++;
+	opstep->opstrategy = opstrategy;
+	opstep->exprs = exprs;
+	opstep->cmpfns = cmpfns;
+	opstep->nullkeys = nullkeys;
+
+	context->steps = lappend(context->steps, opstep);
+
+	return (Node *) opstep;
+}
+
+static Node *
+generate_pruning_step_combine(GeneratePruningStepsContext *context,
+							List *source_stepids,
+							PartitionPruneCombineOp combineOp)
+{
+	PartitionPruneStepCombine *cstep = makeNode(PartitionPruneStepCombine);
+
+	cstep->step.step_id = context->next_step_id++;
+	cstep->combineOp = combineOp;
+	cstep->source_stepids = source_stepids;
+
+	context->steps = lappend(context->steps, cstep);
+
+	return (Node *) cstep;
+}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 30459f7ba9..155be722f6 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 cd15faa7a1..8981901272 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,
@@ -74,4 +96,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 443de22704..adb0d3a45f 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -192,6 +192,9 @@ typedef enum NodeTag
 	T_FromExpr,
 	T_OnConflictExpr,
 	T_IntoClause,
+	T_PartitionPruneStep,
+	T_PartitionPruneStepOp,
+	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..a71d729e72 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1506,4 +1506,78 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*
+ * Node types to represent a partition pruning step
+ */
+
+/*
+ * The base Node type.  step_id is the global identifier of a given step
+ * within a given pruning context.
+ */
+typedef struct PartitionPruneStep
+{
+	NodeTag		type;
+	int			step_id;
+} 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.  'cmpfns' contains the OIDs of
+ * comparison function used to compare aforementioned expressions with
+ * partition bounds.  Both 'exprs' and 'cmpfns' contain the same number of
+ * items up to partnatts items.
+ *
+ * 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;
+
+/*----------
+ * PartitionPruneStepCombine - Information to prune using a BoolExpr clause
+ *
+ * For BoolExpr clauses, we combine the set of partitions determined for each
+ * of its argument clauses.
+ *----------
+ */
+typedef enum PartitionPruneCombineOp
+{
+	COMBINE_UNION,
+	COMBINE_INTERSECT,
+	COMBINE_INVERT
+} PartitionPruneCombineOp;
+
+typedef struct PartitionPruneStepCombine
+{
+	PartitionPruneStep step;
+
+	PartitionPruneCombineOp combineOp;
+	List	   *source_stepids;
+} 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 a0edba291f..0be31cce7e 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -208,16 +208,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 +233,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 +263,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 +575,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 +716,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 +892,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 +904,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                                  
@@ -1007,24 +1009,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 +1032,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             
@@ -1111,13 +1098,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))
+(28 rows)
 
 -- pruning should work fine, because values for a prefix of keys (a, b) are
 -- available
@@ -1275,22 +1270,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 values provided for both keys
 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
@@ -1343,3 +1332,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 d2b4561530..8377671cde 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;
@@ -239,3 +239,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..5006babc6c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -830,6 +830,7 @@ GatherMergeState
 GatherPath
 GatherState
 Gene
+GeneratePruningStepsContext
 GenerationBlock
 GenerationChunk
 GenerationContext
@@ -1584,6 +1585,7 @@ ParsedText
 ParsedWord
 ParserSetupHook
 ParserState
+PartClauseInfo
 PartitionBoundInfo
 PartitionBoundInfoData
 PartitionBoundSpec
@@ -1596,6 +1598,10 @@ PartitionElem
 PartitionHashBound
 PartitionKey
 PartitionListValue
+PartitionPruneContext
+PartitionPruneStep
+PartitionPruneStepCombine
+PartitionPruneStepOp
 PartitionRangeBound
 PartitionRangeDatum
 PartitionRangeDatumKind
@@ -1749,6 +1755,7 @@ ProjectionPath
 ProtocolVersion
 PrsStorage
 PruneState
+PruneStepResult
 PsqlScanCallbacks
 PsqlScanQuoteType
 PsqlScanResult
-- 
2.11.0