v25-0004-Faster-partition-pruning.patch

text/plain

Filename: v25-0004-Faster-partition-pruning.patch
Type: text/plain
Part: 3
Message: Re: [HACKERS] path toward faster partition pruning

Patch

Format: format-patch
Series: patch v25-0004
Subject: Faster partition pruning
File+
src/backend/catalog/partition.c 669 0
src/backend/nodes/copyfuncs.c 22 0
src/backend/optimizer/path/allpaths.c 16 0
src/backend/optimizer/util/clauses.c 1 3
src/backend/optimizer/util/Makefile 1 1
src/backend/optimizer/util/partprune.c 1403 0
src/backend/optimizer/util/plancat.c 28 14
src/backend/optimizer/util/relnode.c 8 0
src/include/catalog/partition.h 85 0
src/include/catalog/pg_opfamily.h 3 0
src/include/nodes/nodes.h 1 0
src/include/nodes/primnodes.h 33 0
src/include/nodes/relation.h 4 0
src/include/optimizer/clauses.h 2 0
src/include/optimizer/partprune.h 25 0
src/test/regress/expected/inherit.out 4 6
src/test/regress/expected/partition_prune.out 380 50
src/test/regress/sql/partition_prune.sql 76 1
From b57956c7c31aa065b349b3b8c2a6a43fd9d6af6f Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 22 Aug 2017 13:48:13 +0900
Subject: [PATCH v25 4/5] Faster partition pruning

Authors: Amit Langote,
         Dilip Kumar (dilipbalaut@gmail.com),
         David Rowley (david.rowley@2ndquadrant.com)
---
 src/backend/catalog/partition.c               |  669 ++++++++++++
 src/backend/nodes/copyfuncs.c                 |   22 +
 src/backend/optimizer/path/allpaths.c         |   16 +
 src/backend/optimizer/util/Makefile           |    2 +-
 src/backend/optimizer/util/clauses.c          |    4 +-
 src/backend/optimizer/util/partprune.c        | 1403 +++++++++++++++++++++++++
 src/backend/optimizer/util/plancat.c          |   42 +-
 src/backend/optimizer/util/relnode.c          |    8 +
 src/include/catalog/partition.h               |   85 ++
 src/include/catalog/pg_opfamily.h             |    3 +
 src/include/nodes/nodes.h                     |    1 +
 src/include/nodes/primnodes.h                 |   33 +
 src/include/nodes/relation.h                  |    4 +
 src/include/optimizer/clauses.h               |    2 +
 src/include/optimizer/partprune.h             |   25 +
 src/test/regress/expected/inherit.out         |   10 +-
 src/test/regress/expected/partition_prune.out |  430 +++++++-
 src/test/regress/sql/partition_prune.sql      |   77 +-
 18 files changed, 2761 insertions(+), 75 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 dccaa232a9..4648c2c92f 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -196,6 +196,15 @@ static uint64 compute_hash_value(int partnatts, FmgrInfo *partsupfunc,
 /* SQL-callable function for use in hash partition CHECK constraints */
 PG_FUNCTION_INFO_V1(satisfies_hash_partition);
 
+static Bitmapset *get_partitions_for_keys_hash(PartitionPruneContext *context,
+						PartScanKeyInfo *keys);
+static Bitmapset *get_partitions_for_keys_list(PartitionPruneContext *context,
+						PartScanKeyInfo *keys);
+static Bitmapset *get_partitions_for_keys_range(PartitionPruneContext *context,
+						PartScanKeyInfo *keys);
+static Bitmapset *get_partitions_excluded_by_ne_datums(PartitionPruneContext *context,
+						Datum *ne_datums, int n_ne_datums);
+
 /*
  * RelationBuildPartitionDesc
  *		Form rel's partition descriptor
@@ -1563,9 +1572,669 @@ get_partition_qual_relid(Oid relid)
 	return result;
 }
 
+/*
+ * get_partitions_for_keys
+ *		Returns the index of partitions that will need to be scanned for the
+ *		given look up keys
+ *
+ * Input:
+ *	See the comments above the definition of PartScanKeyInfo to see what
+ *	kind of information is contained in 'keys'.
+ *
+ * Outputs:
+ *	Bitmapset containing indexes of the selected partitions
+ */
+Bitmapset *
+get_partitions_for_keys(PartitionPruneContext *context,
+						PartScanKeyInfo *keys)
+{
+	Bitmapset *result;
+
+	switch (context->strategy)
+	{
+		case PARTITION_STRATEGY_HASH:
+			result = get_partitions_for_keys_hash(context, keys);
+			break;
+
+		case PARTITION_STRATEGY_LIST:
+			result = get_partitions_for_keys_list(context, keys);
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			result = get_partitions_for_keys_range(context, keys);
+			break;
+
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+				 context->strategy);
+	}
+
+	if (keys->n_ne_datums > 0)
+	{
+		Bitmapset *ne_parts;
+
+			/*
+			 * Remove the indexes of partitions that can safely be removed due to
+			 * 'ne_clauses' containing not-equal clauses for all possible values
+			 * that the partition can contain.
+			 */
+			ne_parts = get_partitions_excluded_by_ne_datums(context,
+														keys->ne_datums,
+														keys->n_ne_datums);
+			result = bms_del_members(result, ne_parts);
+			bms_free(ne_parts);
+		}
+
+	return result;
+}
+
 /* Module-local functions */
 
 /*
+ * get_partitions_for_keys_hash
+ *		Return partitions of a hash partitioned table for requested
+ *		keys
+ *
+ * This interprets the keys and looks up partitions in the partition bound
+ * descriptor using the hash partitioning semantics.
+ */
+static Bitmapset *
+get_partitions_for_keys_hash(PartitionPruneContext *context,
+							 PartScanKeyInfo *keys)
+{
+	int		partnatts = context->partnatts,
+			nparts = context->nparts,
+			i;
+	bool	keyisnull[PARTITION_MAX_KEYS];
+	FmgrInfo *partsupfunc = context->partsupfunc;
+	PartitionBoundInfo boundinfo = context->boundinfo;
+
+	/*
+	 * Since tuples with NULL values in the partition key columns are stored
+	 * in regular partitions, we'll treat any IS NULL clauses here as regular
+	 * equality clauses.
+	 */
+	memset(keyisnull, false, sizeof(keyisnull));
+	for (i = 0; i < partnatts; i++)
+	{
+		if (bms_is_member(i, keys->keyisnull))
+		{
+			keys->n_eqkeys++;
+			keyisnull[i] = true;
+		}
+	}
+
+	/*
+	 * Can only do pruning if we know all the keys and they're all equality
+	 * keys including the nulls that we just counted above.
+	 */
+	if (keys->n_eqkeys == partnatts)
+	{
+		uint64	rowHash;
+		int 	greatest_modulus = get_greatest_modulus(boundinfo),
+				result_index;
+
+		rowHash = compute_hash_value(partnatts, partsupfunc,
+									 keys->eqkeys, keyisnull);
+		result_index = boundinfo->indexes[rowHash % greatest_modulus];
+		if (result_index >= 0)
+			return bms_make_singleton(result_index);
+	}
+	else
+		/* Can't do pruning otherwise, so return all partitions. */
+		return bms_add_range(NULL, 0, nparts - 1);
+
+	return NULL;
+}
+
+/*
+ * get_partitions_for_keys_list
+ *		Return partitions of a list partitioned table for requested keys
+ *
+ * This interprets the keys and looks up partitions in the partition bound
+ * descriptor using the list partitioning semantics.
+ */
+static Bitmapset *
+get_partitions_for_keys_list(PartitionPruneContext *context,
+							 PartScanKeyInfo *keys)
+{
+	FmgrInfo   *partsupfunc = context->partsupfunc;
+	Oid		   *partcollation = context->partcollation;
+	PartitionBoundInfo boundinfo = context->boundinfo;
+	Bitmapset *result = NULL;
+	int		i,
+			eqoff,
+			minoff,
+			maxoff;
+	bool	is_equal;
+
+	/*
+	 * If the query is looking for null keys, there can only be one such
+	 * partition.  Return the same if one exists.
+	 */
+	if (!bms_is_empty(keys->keyisnull))
+	{
+		/*
+		 * NULLs may only exist in the NULL partition, or in the
+		 * default, if there's no NULL partition.
+		 */
+		if (partition_bound_accepts_nulls(boundinfo))
+			return bms_make_singleton(boundinfo->null_index);
+		else if (partition_bound_has_default(boundinfo))
+			return bms_make_singleton(boundinfo->default_index);
+		else
+			return NULL;
+	}
+
+	/*
+	 * If there are no datums to compare keys with, but there are partitions,
+	 * just return the default partition if one exists.
+	 */
+	if (boundinfo->ndatums == 0)
+	{
+		if (partition_bound_has_default(boundinfo))
+			return bms_make_singleton(boundinfo->default_index);
+		else
+			return NULL;	/* shouldn't happen */
+	}
+
+	/* Equality key. */
+	if (keys->n_eqkeys > 0)
+	{
+		eqoff = partition_list_bsearch(partsupfunc, partcollation,
+									   boundinfo, keys->eqkeys[0],
+									   &is_equal);
+		if (eqoff >= 0 && is_equal)
+		{
+			/* Exactly matching datum exists. */
+			Assert(boundinfo->indexes[eqoff] >= 0);
+			return bms_make_singleton(boundinfo->indexes[eqoff]);
+		}
+		else if (partition_bound_has_default(boundinfo))
+			return bms_make_singleton(boundinfo->default_index);
+		else
+			return NULL;
+	}
+
+	/*
+	 * Find the left-most bound that satisfies the query, i.e., the one that
+	 * satisfies minkeys.
+	 */
+	minoff = 0;
+	if (keys->n_minkeys > 0)
+	{
+		minoff = partition_list_bsearch(partsupfunc, partcollation,
+										boundinfo, keys->minkeys[0],
+										&is_equal);
+		if (minoff >= 0)
+		{
+			/*
+			 * The bound at minoff is <= minkeys, given the way
+			 * partition_bound_bsearch() works.  If it's not equal (<), then
+			 * increment minoff to make it point to the datum on the right
+			 * that necessarily satisfies minkeys.  Also do the same if it is
+			 * equal but minkeys is exclusive.
+			 */
+			if (!is_equal || !keys->min_incl)
+				minoff++;
+		}
+		else
+		{
+			/*
+			 * minoff set to -1 means all datums are greater than minkeys,
+			 * which means all partitions satisfy minkeys.  In that case, set
+			 * minoff to the index of the leftmost datum, viz. 0.
+			 */
+			minoff = 0;
+		}
+
+		/*
+		 * minkeys is greater than the datums of all non-default partitions,
+		 * meaning there isn't one to return.  Return the default partition if
+		 * one exists.
+		 */
+		if (minoff > boundinfo->ndatums - 1)
+			return partition_bound_has_default(boundinfo)
+						? bms_make_singleton(boundinfo->default_index)
+						: NULL;
+	}
+
+	/*
+	 * Find the right-most bound that satisfies the query, i.e., the one that
+	 * satisfies maxkeys.
+	 */
+	maxoff = boundinfo->ndatums - 1;
+	if (keys->n_maxkeys > 0)
+	{
+		maxoff = partition_list_bsearch(partsupfunc, partcollation,
+										boundinfo, keys->maxkeys[0],
+										&is_equal);
+		if (maxoff >= 0)
+		{
+			/*
+			 * The bound at maxoff is <= maxkeys, given the way
+			 * partition_bound_bsearch works.  If the bound at maxoff exactly
+			 * matches maxkey (is_equal), but the maxkey is exclusive, then
+			 * decrement maxoff to point to the bound on the left.
+			 */
+			if (is_equal && !keys->max_incl)
+				maxoff--;
+		}
+
+		/*
+		 * maxkeys is smaller than the datums of all non-default partitions,
+		 * meaning there isn't one to return.  Return the default partition if
+		 * one exists.
+		 */
+		if (maxoff < 0)
+			return partition_bound_has_default(boundinfo)
+						? bms_make_singleton(boundinfo->default_index)
+						: NULL;
+	}
+
+	Assert (minoff >= 0 && maxoff >= 0);
+
+	/*
+	 * All datums between those at minoff and maxoff satisfy query's keys, so
+	 * add the corresponding partitions to the result set.
+	 */
+	for (i = minoff; i <= maxoff; i++)
+		result = bms_add_member(result, boundinfo->indexes[i]);
+
+	/*
+	 * For 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.
+	 */
+	if (partition_bound_has_default(boundinfo))
+		return bms_add_member(result, boundinfo->default_index);
+
+	return result;
+}
+
+/*
+ * get_partitions_for_keys_range
+ *		Return partitions of a range partitioned table for requested keys
+ *
+ * This interprets the keys and looks up partitions in the partition bound
+ * descriptor using the range partitioning semantics.
+ */
+static Bitmapset *
+get_partitions_for_keys_range(PartitionPruneContext *context,
+							  PartScanKeyInfo *keys)
+{
+	FmgrInfo *partsupfunc = context->partsupfunc;
+	Oid *partcollation = context->partcollation;
+	PartitionBoundInfo boundinfo = context->boundinfo;
+	Bitmapset *result = NULL;
+	int		partnatts = context->partnatts,
+			i,
+			eqoff,
+			minoff,
+			maxoff;
+	bool	is_equal,
+			include_def = false;
+
+	/*
+	 * We might be able to get the answer sooner based on the nullness of
+	 * keys, so get that out of the way.
+	 */
+	for (i = 0; i < partnatts; i++)
+	{
+		if (bms_is_member(i, keys->keyisnull))
+		{
+			/* Only the default partition accepts nulls. */
+			if (partition_bound_has_default(boundinfo))
+				return bms_make_singleton(boundinfo->default_index);
+			else
+				return NULL;
+		}
+	}
+
+	/*
+	 * If there are no datums to compare keys with, but there are partitions,
+	 * just return the default partition, if one exists.
+	 */
+	if (boundinfo->ndatums == 0)
+	{
+		if (partition_bound_has_default(boundinfo))
+			return bms_make_singleton(boundinfo->default_index);
+		else
+			return NULL;
+	}
+
+	/* Equality keys. */
+	if (keys->n_eqkeys > 0)
+	{
+		/* Valid iff there are as many as partition key columns. */
+		Assert(keys->n_eqkeys == partnatts);
+		eqoff = partition_range_datum_bsearch(partsupfunc,
+											  partcollation,
+											  boundinfo,
+											  keys->n_eqkeys, keys->eqkeys,
+											  &is_equal);
+		/*
+		 * The bound at eqoff is known to be <= eqkeys, given the way
+		 * partition_bound_bsearch works.  Considering it as the lower bound
+		 * of the partition that eqkeys falls into, the bound at eqoff + 1
+		 * would be its upper bound, so use eqoff + 1 to get the desired
+		 * partition's index.
+		 */
+		if (eqoff >= 0 && boundinfo->indexes[eqoff + 1] >= 0)
+			return bms_make_singleton(boundinfo->indexes[eqoff+1]);
+		/*
+		 * eqkeys falls into a range of values for which no non-default
+		 * partition exists.
+		 */
+		else if (partition_bound_has_default(boundinfo))
+			return bms_make_singleton(boundinfo->default_index);
+		else
+			return NULL;
+	}
+
+	/*
+	 * Find the leftmost bound that satisfies the query, that is, make minoff
+	 * point to the datum corresponding to the upper bound of the left-most
+	 * partition to be selected.
+	 */
+	minoff = 0;
+	if (keys->n_minkeys > 0)
+	{
+		minoff = partition_range_datum_bsearch(partsupfunc, partcollation,
+											   boundinfo,
+											   keys->n_minkeys, keys->minkeys,
+											   &is_equal);
+
+		/*
+		 * If minkeys does not contain values for all partition key columns,
+		 * that is, only a prefix is specified, then there may be multiple
+		 * bounds in boundinfo that share the same prefix.  But
+		 * partition_bound_bsearch would've returned the offset of just one of
+		 * those.  If minkey is inclusive, we must decrement minoff until it
+		 * reaches the leftmost of those bound values, so that partitions
+		 * corresponding to all those bound values are selected.  If minkeys
+		 * is exclusive, we must increment minoff until it reaches the first
+		 * bound greater than this prefix, so that none of the partitions
+		 * corresponding to those bound values are selected.
+		 */
+		if (is_equal && keys->n_minkeys < partnatts)
+		{
+			while (minoff >= 1 && minoff < boundinfo->ndatums - 1)
+			{
+				int32	cmpval;
+				int		nextoff;
+
+				nextoff = keys->min_incl ? minoff - 1 : minoff + 1;
+				cmpval = partition_rbound_datum_cmp(partsupfunc,
+												partcollation,
+												boundinfo->datums[nextoff],
+												boundinfo->kind[nextoff],
+												keys->minkeys,
+												keys->n_minkeys);
+				if (cmpval != 0)
+				{
+					/* Move to the non-equal bound only in this case. */
+					if (!keys->min_incl)
+						minoff += 1;
+					break;
+				}
+
+				if (keys->min_incl)
+					minoff -= 1;
+				else
+					minoff += 1;
+			}
+		}
+		/*
+		 * Assuming minoff currently points to the lower bound of the left-
+		 * most selected partition, increment it so that it points to the
+		 * upper bound.
+		 */
+		else
+			minoff += 1;
+	}
+
+	/*
+	 * Find the rightmost bound that satisfies the query, that is, make maxoff
+	 * maxoff point to the datum corresponding to the upper bound of the
+	 * right-most partition to be selected.
+	 */
+	maxoff = boundinfo->ndatums;
+	if (keys->n_maxkeys > 0)
+	{
+		maxoff = partition_range_datum_bsearch(partsupfunc,
+											   partcollation,
+											   boundinfo,
+											   keys->n_maxkeys, keys->maxkeys,
+											   &is_equal);
+
+		/* See the comment written above for minkeys. */
+		if (is_equal && keys->n_maxkeys < partnatts)
+		{
+			while (maxoff >= 1 && maxoff < boundinfo->ndatums - 1)
+			{
+				int32	cmpval;
+				int		nextoff;
+
+				nextoff = keys->max_incl ? maxoff + 1 : maxoff - 1;
+				cmpval = partition_rbound_datum_cmp(partsupfunc,
+												partcollation,
+												boundinfo->datums[nextoff],
+												boundinfo->kind[nextoff],
+												keys->maxkeys,
+												keys->n_maxkeys);
+				if (cmpval != 0)
+				{
+					/* Move to the non-equal bound only in this case. */
+					if (!keys->max_incl)
+						maxoff -= 1;
+					break;
+				}
+
+				if (keys->max_incl)
+					maxoff += 1;
+				else
+					maxoff -= 1;
+			}
+
+			/*
+			 * Assuming maxoff currently points to the lower bound of the
+			 * right-most partition, increment it so that it points to the
+			 * upper bound.
+			 */
+			maxoff += 1;
+		}
+		/*
+		 * Assuming maxoff currently points to the lower bound of the right-
+		 * most selected partition, increment it so that it points to the
+		 * upper bound.  We do not need to include that partition though if
+		 * maxkeys exactly matched the bound in question and it is exclusive.
+		 * Not incrementing simply means we treat the matched bound itself
+		 * the upper bound of the right-most selected partition.
+		 */
+		else if (!is_equal || keys->max_incl)
+			maxoff += 1;
+	}
+
+	Assert (minoff >= 0 && maxoff >= 0);
+
+	/*
+	 * At this point, we believe that minoff/maxoff point to the upper bound
+	 * of some partition, but it may not be the case.  It might actually be
+	 * the upper bound of an unassigned range of values, which if so, move
+	 * minoff/maxoff to the adjacent bound which must be the upper bound of
+	 * a valid partition.
+	 *
+	 * By doing that, we skip over a portion of values that do indeed satisfy
+	 * the query, but don't have a valid partition assigned.  The default
+	 * partition will have to be included to cover those values.  Although, if
+	 * the original bound in question contains an infinite value, there would
+	 * not be any unassigned range to speak of, because the range us unbounded
+	 * in that direction by definition, so no need to include the default.
+	 */
+	if (boundinfo->indexes[minoff] < 0)
+	{
+		int		lastkey = partnatts - 1;
+
+		if (keys->n_minkeys > 0)
+			lastkey = keys->n_minkeys - 1;
+		if (minoff >=0 && minoff < boundinfo->ndatums &&
+			boundinfo->kind[minoff][lastkey] == PARTITION_RANGE_DATUM_VALUE)
+			include_def = true;
+		minoff += 1;
+	}
+
+	if (maxoff >= 1 && boundinfo->indexes[maxoff] < 0)
+	{
+		int		lastkey = partnatts - 1;
+
+		if (keys->n_maxkeys > 0)
+			lastkey = keys->n_maxkeys - 1;
+		if (maxoff >=0 && maxoff <= boundinfo->ndatums &&
+			boundinfo->kind[maxoff - 1][lastkey] == PARTITION_RANGE_DATUM_VALUE)
+			include_def = true;
+		maxoff -= 1;
+	}
+
+	if (minoff <= maxoff)
+		result = bms_add_range(result,
+							   boundinfo->indexes[minoff],
+							   boundinfo->indexes[maxoff]);
+	/*
+	 * There may exist a range of values unassigned to any non-default
+	 * partition between the datums at minoff and maxoff.
+	 */
+	for (i = minoff; i <= maxoff; i++)
+	{
+		if (boundinfo->indexes[i] < 0)
+		{
+			include_def = true;
+			break;
+		}
+	}
+
+	/*
+	 * Since partition keys with nulls are mapped to the default range
+	 * partition, we must include the default partition if some keys
+	 * could be null.
+	 */
+	if (keys->n_minkeys < partnatts || keys->n_maxkeys < partnatts)
+	{
+		for (i = 0; i < partnatts; i++)
+		{
+			if (!bms_is_member(i, keys->keyisnotnull))
+			{
+				include_def = true;
+				break;
+			}
+		}
+	}
+
+	if (include_def && partition_bound_has_default(boundinfo))
+		result = bms_add_member(result, boundinfo->default_index);
+
+	return result;
+}
+
+/*
+ * get_partitions_excluded_by_ne_datums
+ *
+ * Returns a Bitmapset of indexes of partitions that can safely be removed
+ * due to each such partition's every allowable non-null datum appearing in
+ * a <> opeartor clause.
+ */
+static Bitmapset *
+get_partitions_excluded_by_ne_datums(PartitionPruneContext *context,
+									 Datum *ne_datums, int n_ne_datums)
+{
+	FmgrInfo *partsupfunc = context->partsupfunc;
+	Oid	   *partcollation = context->partcollation;
+	int		nparts = context->nparts,
+			i,
+		   *datums_in_part,
+		   *datums_found;
+	PartitionBoundInfo boundinfo = context->boundinfo;
+	Bitmapset	   *excluded_parts;
+	Bitmapset	   *foundoffsets = NULL;
+
+	Assert(context->strategy == PARTITION_STRATEGY_LIST);
+	Assert(context->partnatts == 1);
+
+	for (i = 0; i < n_ne_datums; i++)
+	{
+		int		offset;
+		bool	is_equal;
+
+		offset = partition_list_bsearch(partsupfunc, partcollation,
+										boundinfo,
+										ne_datums[i], &is_equal);
+		if (offset >= 0 && is_equal)
+		{
+			Assert(boundinfo->indexes[offset] >= 0);
+			foundoffsets = bms_add_member(foundoffsets, offset);
+		}
+	}
+
+	/* No partitions can be excluded if none of the datums were found. */
+	if (bms_is_empty(foundoffsets))
+		return NULL;
+
+	/*
+	 * Since each list partition can permit multiple values, we must ensure
+	 * that we got clauses for all those values before we can eliminate the
+	 * the entire partition.
+	 *
+	 * We'll need two arrays for this, one to count the number of unique
+	 * datums found in the query which belong to each partition, and another
+	 * to record the number of datums permitted in each partition.  Once we've
+	 * counted all this, we can eliminate any partition where the number of
+	 * datums found matches the number of datums allowed in the partition.
+	 */
+	datums_in_part = (int *) palloc0(sizeof(int) * nparts);
+	datums_found = (int *) palloc0(sizeof(int) * nparts);
+
+	i = -1;
+	while ((i = bms_next_member(foundoffsets, i)) >= 0)
+		datums_found[boundinfo->indexes[i]]++;
+
+	/*
+	 * Now, in a single pass over all the datums, count the number of datums
+	 * permitted in each partition.
+	 */
+	for (i = 0; i < boundinfo->ndatums; i++)
+		datums_in_part[boundinfo->indexes[i]]++;
+
+	/*
+	 * Now compare the counts and eliminate any partition for which we found
+	 * clauses for all its permitted values.  We must be careful here not to
+	 * eliminate the default partition.  We can recognize that by it having a
+	 * zero value in both arrays.
+	 */
+	excluded_parts = NULL;
+
+	for (i = 0; i < nparts; i++)
+	{
+		if (datums_found[i] >= datums_in_part[i] && datums_found[i] > 0)
+			excluded_parts = bms_add_member(excluded_parts, i);
+	}
+
+	/*
+	 * Because the above clauses are strict, we can also exclude the NULL
+	 * partition, provided it does not also allow non-NULL values.
+	 */
+	if (partition_bound_accepts_nulls(boundinfo) &&
+		datums_in_part[boundinfo->null_index] == 0)
+		excluded_parts = bms_add_member(excluded_parts,
+										boundinfo->null_index);
+
+	pfree(datums_in_part);
+	pfree(datums_found);
+
+	return excluded_parts;
+}
+
+/*
  * get_partition_operator
  *
  * Return oid of the operator of given strategy for a given partition key
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 82255b0d1d..a3048e46ef 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2132,6 +2132,25 @@ _copyOnConflictExpr(const OnConflictExpr *from)
 	return newnode;
 }
 
+static PartitionClauseInfo *
+_copyPartitionClauseInfo(const PartitionClauseInfo *from)
+{
+	PartitionClauseInfo *newnode = makeNode(PartitionClauseInfo);
+
+	int i;
+	for (i = 0; i < PARTITION_MAX_KEYS; i++)
+		COPY_NODE_FIELD(keyclauses[i]);
+
+	COPY_NODE_FIELD(or_clauses);
+	COPY_NODE_FIELD(ne_clauses);
+	COPY_BITMAPSET_FIELD(keyisnull);
+	COPY_BITMAPSET_FIELD(keyisnotnull);
+	COPY_SCALAR_FIELD(constfalse);
+	COPY_SCALAR_FIELD(foundkeyclauses);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *						relation.h copy functions
  *
@@ -5020,6 +5039,9 @@ copyObjectImpl(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_PartitionClauseInfo:
+			retval = _copyPartitionClauseInfo(from);
+			break;
 
 			/*
 			 * RELATION NODES
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 6e842f93d0..98d7a19dad 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"
@@ -862,6 +863,7 @@ static void
 set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 					Index rti, RangeTblEntry *rte)
 {
+	Bitmapset  *live_children = NULL;
 	int			parentRTindex = rti;
 	bool		has_live_children;
 	double		parent_rows;
@@ -875,6 +877,9 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 
 	Assert(IS_SIMPLE_REL(rel));
 
+	if (rte->relkind == RELKIND_PARTITIONED_TABLE)
+		live_children = prune_append_rel_partitions(root, rel);
+
 	/*
 	 * Initialize to compute size estimates for whole append relation.
 	 *
@@ -1123,6 +1128,17 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 			continue;
 		}
 
+		if (IS_PARTITIONED_REL(rel) &&
+			!bms_is_member(appinfo->child_relid, live_children))
+		{
+			/*
+			 * This child need not be scanned, so we can omit it from the
+			 * appendrel.
+			 */
+			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/clauses.c b/src/backend/optimizer/util/clauses.c
index 89f27ce0eb..0c1f23951a 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -152,8 +152,6 @@ static Node *substitute_actual_parameters(Node *expr, int nargs, List *args,
 static Node *substitute_actual_parameters_mutator(Node *node,
 									 substitute_actual_parameters_context *context);
 static void sql_inline_error_callback(void *arg);
-static Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
-			  Oid result_collation);
 static Query *substitute_actual_srf_parameters(Query *expr,
 								 int nargs, List *args);
 static Node *substitute_actual_srf_parameters_mutator(Node *node,
@@ -4833,7 +4831,7 @@ sql_inline_error_callback(void *arg)
  * We use the executor's routine ExecEvalExpr() to avoid duplication of
  * code and ensure we get the same result as the executor would get.
  */
-static Expr *
+Expr *
 evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
 			  Oid result_collation)
 {
diff --git a/src/backend/optimizer/util/partprune.c b/src/backend/optimizer/util/partprune.c
new file mode 100644
index 0000000000..fcb8d90f48
--- /dev/null
+++ b/src/backend/optimizer/util/partprune.c
@@ -0,0 +1,1403 @@
+/*-------------------------------------------------------------------------
+ *
+ * partprune.c
+ *		Provides functions to prune partitions of a partitioned table by
+ *		comparing provided set of clauses with the table's partitions'
+ *		boundaries
+ *
+ * TODO: write a longer description of things in this file
+ *
+ * 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 "catalog/pg_operator.h"
+#include "catalog/pg_opfamily.h"
+#include "catalog/pg_type.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 "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 column kept to
+ * avoid recomputing it in remove_redundant_clauses().
+ */
+typedef struct PartClause
+{
+	Oid		opno;		/* opno to compare partkey to 'value' */
+	Oid		inputcollid; /* collation to compare partkey to 'value' */
+	Expr   *value;	/* The value the partition key is being compared to */
+
+	/* cached info. */
+	bool	valid_cache;	/* Are the following fields populated? */
+	int		op_strategy;
+	Oid		op_subtype;
+	FmgrInfo op_func;
+} PartClause;
+
+/*
+ * Strategy of a partition clause operator per the partitioning operator class
+ * definition.
+ */
+typedef enum PartOpStrategy
+{
+	PART_OP_EQUAL,
+	PART_OP_LESS,
+	PART_OP_GREATER
+} PartOpStrategy;
+
+static void extract_partition_clauses(PartitionPruneContext *context,
+						   List *clauses);
+static Bitmapset *get_partitions_from_or_args(PartitionPruneContext *context,
+								   List *or_args);
+static void remove_redundant_clauses(PartitionPruneContext *context,
+						 List **minimalclauses);
+static bool partition_cmp_args(Oid parttypid, Oid partopfamily,
+				   PartClause *pc, PartClause *leftarg, PartClause *rightarg,
+				   bool *result);
+static bool extract_bounding_datums(PartitionPruneContext *context,
+						List **minimalclauses, PartScanKeyInfo *keys);
+static PartOpStrategy partition_op_strategy(char part_strategy,
+					PartClause *pc, bool *incl);
+static bool partkey_datum_from_expr(Oid parttypid, Expr *expr, Datum *value);
+
+/*
+ * prune_append_rel_partitions
+ *		Returns a Bitmapset of the RT indexes of relations belonging to the
+ *		minimum set of partitions which must be scanned to satisfy rel's
+ *		baserestrictinfo quals.
+ */
+Bitmapset *
+prune_append_rel_partitions(PlannerInfo *root, RelOptInfo *rel)
+{
+	Bitmapset  *result = NULL;
+	List	   *clauses = rel->baserestrictinfo;
+	int			i;
+
+	if (!clauses)
+	{
+		/* If there are no clauses then include every partition */
+		for (i = 0; i < rel->nparts; i++)
+			result = bms_add_member(result, rel->part_rels[i]->relid);
+	}
+	else
+	{
+		PartitionPruneContext context;
+		int		partnatts = rel->part_scheme->partnatts,
+				i;
+
+		memset(&context, 0, sizeof(context));
+		context.relid = rel->relid;
+		context.strategy = rel->part_scheme->strategy;
+		context.partnatts = rel->part_scheme->partnatts;
+
+		context.partkeys = (Expr **) palloc0(sizeof(Expr *) *
+											 context.partnatts);
+		for (i = 0; i < partnatts; i++)
+			context.partkeys[i] = linitial(rel->partexprs[i]);
+
+		context.parttypid = rel->part_scheme->parttypid;
+		context.partopfamily = rel->part_scheme->partopfamily;
+		context.partcollation = rel->part_scheme->partcollation;
+		context.partsupfunc = rel->part_scheme->partsupfunc;
+		context.nparts = rel->nparts;
+		context.boundinfo = rel->boundinfo;
+		context.has_default_part = rel->has_default_part;
+		context.partition_qual = rel->partition_qual;
+
+		/* process clauses; context.clauseinfo will be set */
+		generate_partition_clauses(&context, clauses);
+
+		if (!context.clauseinfo->constfalse)
+		{
+			Bitmapset *partindexes = get_partitions_from_clauses(&context);
+
+			/* Add selected partitions' RT indexes to result. */
+			while ((i = bms_first_member(partindexes)) >= 0)
+				result = bms_add_member(result, rel->part_rels[i]->relid);
+		}
+	}
+
+	return result;
+}
+
+/*
+ * generate_partition_clauses
+ *		Analyzes clauses to find those that match the partition key and sets
+ *		context->clauseinfo
+ */
+void
+generate_partition_clauses(PartitionPruneContext *context, List *clauses)
+{
+	/* 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 (context->has_default_part && context->partition_qual != NIL)
+	{
+		List *partqual = context->partition_qual;
+
+		partqual = (List *) expression_planner((Expr *) partqual);
+
+		/* Fix Vars to have the desired varno */
+		if (context->relid != 1)
+			ChangeVarNodes((Node *) partqual, 1, context->relid, 0);
+
+		clauses = list_concat(clauses, partqual);
+	}
+
+	/* And away we go to do the real work. */
+	extract_partition_clauses(context, clauses);
+}
+
+/*
+ * get_partitions_from_clauses
+ *		Determine partitions that could possible contain a record that
+ *		satisfies clauses described in context->clauseinfo
+ *
+ * Returns a Bitmapset of the matching partition indexes, or NULL if none can
+ * match.
+ */
+Bitmapset *
+get_partitions_from_clauses(PartitionPruneContext *context)
+{
+	PartitionClauseInfo	*partclauseinfo = context->clauseinfo;
+	PartScanKeyInfo		keys;
+	Bitmapset 		   *result;
+	ListCell *lc;
+
+	Assert(partclauseinfo != NULL);
+	Assert(!partclauseinfo->constfalse);
+
+	if (!partclauseinfo->foundkeyclauses)
+	{
+		/* No interesting clauses were found to eliminate partitions. */
+		result = bms_add_range(NULL, 0, context->nparts - 1);
+	}
+	else
+	{
+		List *minimalclauses[PARTITION_MAX_KEYS];
+
+		/*
+		 * Populate minimal clauses with the most restrictive
+		 * of clauses from context's partclauseinfo.
+		 */
+		remove_redundant_clauses(context, minimalclauses);
+
+		/* Did remove_redundant_clauses find any contradicting clauses? */
+		if (partclauseinfo->constfalse)
+			return NULL;
+
+		if (extract_bounding_datums(context, minimalclauses, &keys))
+		{
+			result = get_partitions_for_keys(context, &keys);
+
+			/*
+			 * No point in trying to look at other conjunctive clauses, if we
+			 * got an empty set in the first place.
+			 */
+			if (bms_is_empty(result))
+				return NULL;
+		}
+		else
+		{
+			/*
+			 * Looks like we didn't have the values we'd need to eliminate
+			 * partitions using get_partitions_for_keys, likely because
+			 * context->clauseinfo only contained <> clauses and/or OR
+			 * clauses, which are handled further below in this function.
+			 */
+			result = bms_add_range(NULL, 0, context->nparts - 1);
+		}
+	}
+
+	/* Select partitions by applying OR clauses. */
+	foreach(lc, partclauseinfo->or_clauses)
+	{
+		List *or_args = (List *) lfirst(lc);
+		Bitmapset *or_parts;
+
+		or_parts = get_partitions_from_or_args(context, or_args);
+
+		/*
+		 * Clauses in or_clauses are mutually conjunctive and also in
+		 * in conjunction with the rest of the clauses above, so combine the
+		 * partitions thus selected with those in result using set
+		 * intersection.
+		 */
+		result = bms_int_members(result, or_parts);
+		bms_free(or_parts);
+	}
+
+	return result;
+}
+
+/* Module-local functions */
+
+/*
+ * 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))
+
+/*
+ * extract_partition_clauses
+ *		Processes 'clauses' to extract clause matching the partition key.
+ *		This adds matched clauses to the list corresponding to particular key
+ *		in context->clauseinfo.  Also collects other useful clauses to assist
+ *		in partition elimination, such as OR clauses, clauses containing <>
+ *		operator, and IS [NOT] NULL clauses
+ *
+ * We may also discover some contradiction in the clauses which means that no
+ * partition can possibly match.  In this case, the function sets
+ * context->clauseinfo's 'constfalse' to true and exits immediately without
+ * processing any further clauses.  In this case, the caller must be careful
+ * not to assume the context->clauseinfo is fully populated with all clauses.
+ */
+static void
+extract_partition_clauses(PartitionPruneContext *context, List *clauses)
+{
+	PartitionClauseInfo *partclauseinfo;
+	ListCell   *lc;
+
+	context->clauseinfo = partclauseinfo = makeNode(PartitionClauseInfo);
+	memset(partclauseinfo->keyclauses, 0, sizeof(partclauseinfo->keyclauses));
+	partclauseinfo->or_clauses = NIL;
+	partclauseinfo->ne_clauses = NIL;
+	partclauseinfo->keyisnull = NULL;
+	partclauseinfo->keyisnotnull = NULL;
+	partclauseinfo->constfalse = false;
+	partclauseinfo->foundkeyclauses = false;
+
+	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))
+			{
+				partclauseinfo->constfalse = true;
+				return;
+			}
+		}
+
+		/* Get the BoolExpr's out of the way.*/
+		if (IsA(clause, BoolExpr))
+		{
+			if (or_clause((Node *) clause))
+			{
+				partclauseinfo->or_clauses =
+										lappend(partclauseinfo->or_clauses,
+												((BoolExpr *) clause)->args);
+				continue;
+			}
+			else if (and_clause((Node *) clause))
+			{
+				/*
+				 * Queue its args to be processed later within the same
+				 * invocation.
+				 */
+				clauses = list_concat(clauses,
+									  list_copy(((BoolExpr *) clause)->args));
+				continue;
+			}
+			/* Fall-through for a NOT clause, which is handled below. */
+		}
+
+		for (i = 0; i < context->partnatts; i++)
+		{
+			Expr	   *partkey = context->partkeys[i];
+			PartClause *pc;
+			Oid			partopfamily = context->partopfamily[i];
+			Oid			partcoll = context->partcollation[i];
+			Oid			commutator = InvalidOid;
+
+			if (IsA(clause, OpExpr))
+			{
+				OpExpr	   *opclause = (OpExpr *) clause;
+				Expr	   *leftop,
+						   *rightop,
+						   *valueexpr;
+				bool		is_ne_listp = false;
+
+				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))
+					valueexpr = rightop;
+				else if (equal(rightop, partkey))
+				{
+					valueexpr = leftop;
+
+					commutator = get_commutator(opclause->opno);
+
+					/* nothing we can do unless we can swap the operands */
+					if (!OidIsValid(commutator))
+						continue;
+				}
+				else
+					/* Clause does not match this partition key. */
+					continue;
+
+				/*
+				 * Also, useless, if the clause's collation is different from
+				 * the partitioning collation.
+				 */
+				if (!PartCollMatchesExprColl(partcoll, opclause->inputcollid))
+					continue;
+
+				/*
+				 * Only allow strict operators.  This will guarantee nulls are
+				 * filtered.
+				 */
+				if (!op_strict(opclause->opno))
+					continue;
+
+				/* We can't use any volatile value to prune partitions. */
+				if (contain_volatile_functions((Node *) valueexpr))
+					continue;
+
+				/*
+				 * Handle cases where the clause's operator does not belong to
+				 * the partitioning operator family.  We currently handle two
+				 * such cases: 1. Operators named '<>' are not listed in any
+				 * operator family whatsoever, 2.  Ordering operators like '<'
+				 * are not listed in the hash operator families.  For 1, check
+				 * if list partitioning is in use and if so, proceed to pass
+				 * the clause to the caller without doing any more processing
+				 * ourselves.  2 cannot be handled at all, so the clause is
+				 * simply skipped.
+				 */
+				if (!op_in_opfamily(opclause->opno, partopfamily))
+				{
+					Oid		negator;
+
+					/*
+					 * To confirm if the operator is really '<>', check if its
+					 * negator is a equality operator.  If it's a btree
+					 * equality operator *and* this is a list partitioned
+					 * table, we can use it prune partitions.
+					 */
+					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 &&
+							context->strategy == PARTITION_STRATEGY_LIST)
+							is_ne_listp = true;
+					}
+
+					/* Cannot handle this clause. */
+					if (!is_ne_listp)
+						continue;
+				}
+
+				pc = (PartClause *) palloc0(sizeof(PartClause));
+				pc->opno = OidIsValid(commutator) ? commutator : opclause->opno;
+				pc->inputcollid = opclause->inputcollid;
+				pc->value = valueexpr;
+
+				/*
+				 * We don't turn a <> operator clause into a key right away.
+				 * Instead, the caller will hand over such clauses to
+				 * get_partitions_excluded_by_ne_clauses().
+				 */
+				if (is_ne_listp)
+					partclauseinfo->ne_clauses =
+										lappend(partclauseinfo->ne_clauses,
+												pc);
+				else
+					partclauseinfo->keyclauses[i] =
+										lappend(partclauseinfo->keyclauses[i],
+												pc);
+
+				/*
+				 * Since we only allow strict operators, check for any
+				 * contradicting IS NULLs.
+				 */
+				if (bms_is_member(i, partclauseinfo->keyisnull))
+				{
+					partclauseinfo->constfalse = true;
+					return;
+				}
+				/* Record that a strict clause has been seen for this key */
+				partclauseinfo->keyisnotnull =
+								bms_add_member(partclauseinfo->keyisnotnull,
+											   i);
+				partclauseinfo->foundkeyclauses = true;
+			}
+			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))
+					continue;
+
+				/*
+				 * Also, useless, if the clause's collation is different from
+				 * the partitioning collation.
+				 */
+				if (!PartCollMatchesExprColl(partcoll, saop->inputcollid))
+					continue;
+
+				/*
+				 * Only allow strict operators.  This will guarantee null are
+				 * filtered.
+				 */
+				if (!op_strict(saop->opno))
+					continue;
+
+				/* Useless if the array has any volatile functions. */
+				if (contain_volatile_functions((Node *) rightop))
+					continue;
+
+				/*
+				 * In case of NOT IN (..), we get a '<>', which while not
+				 * listed as part of any operator family, we are able to
+				 * handle it if its negator is indeed a part of the
+				 * partitioning equality operator.
+				 */
+				if (!op_in_opfamily(saop_op, partopfamily))
+				{
+					Oid		negator = get_negator(saop_op);
+					int		strategy;
+					Oid		lefttype,
+							righttype;
+
+					if (!OidIsValid(negator))
+						continue;
+					get_op_opfamily_properties(negator, partopfamily, false,
+											   &strategy,
+											   &lefttype, &righttype);
+					if (strategy != BTEqualStrategyNumber)
+						continue;
+				}
+
+				/*
+				 * 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;
+
+					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)
+						continue;
+
+					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 the OR clause if needed or add the clauses to the end
+				 * of the list that's being processed currently.
+				 */
+				if (saop->useOr && list_length(elem_clauses) > 1)
+					partclauseinfo->or_clauses =
+										lappend(partclauseinfo->or_clauses,
+												elem_clauses);
+				else
+					clauses = list_concat(clauses, elem_clauses);
+				partclauseinfo->foundkeyclauses = true;
+			}
+			else if (IsA(clause, NullTest))
+			{
+				NullTest *nulltest = (NullTest *) clause;
+				Expr *arg = nulltest->arg;
+
+				if (IsA(arg, RelabelType))
+					arg = ((RelabelType *) arg)->arg;
+
+				/* Does leftop match with this partition key column? */
+				if (equal(arg, partkey))
+				{
+					if (nulltest->nulltesttype == IS_NULL)
+					{
+						/* check for conflicting IS NOT NULLs */
+						if (bms_is_member(i, partclauseinfo->keyisnotnull))
+						{
+							partclauseinfo->constfalse = true;
+							return;
+						}
+						partclauseinfo->keyisnull =
+									bms_add_member(partclauseinfo->keyisnull,
+												   i);
+					}
+					else
+					{
+						/* check for conflicting IS NULLs */
+						if (bms_is_member(i, partclauseinfo->keyisnull))
+						{
+							partclauseinfo->constfalse = true;
+							return;
+						}
+
+						partclauseinfo->keyisnotnull =
+								bms_add_member(partclauseinfo->keyisnotnull,
+											   i);
+					}
+					partclauseinfo->foundkeyclauses = true;
+				}
+			}
+			/*
+			 * Boolean clauses have a special shape, which would've been
+			 * accepted if the partitioning opfamily accepts Boolean
+			 * conditions.
+			 */
+			else if (IsBooleanOpfamily(partopfamily) &&
+					 (IsA(clause, BooleanTest) ||
+					  IsA(clause, Var) ||
+					  not_clause((Node *) clause)))
+			{
+				Expr   *leftop,
+					   *rightop;
+
+				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)
+						continue;
+
+					leftop = btest->arg;
+					if (IsA(leftop, RelabelType))
+						leftop = ((RelabelType *) leftop)->arg;
+
+					/* Clause does not match this partition key. */
+					if (!equal(leftop, partkey))
+						continue;
+
+					rightop = (btest->booltesttype == IS_TRUE ||
+							   btest->booltesttype == IS_NOT_FALSE)
+									? (Expr *) makeBoolConst(true, false)
+									: (Expr *) makeBoolConst(false, false);
+				}
+				else
+				{
+					leftop = IsA(clause, Var)
+								? (Expr *) clause
+								: (Expr *) get_notclausearg((Expr *) clause);
+					if (IsA(leftop, RelabelType))
+						leftop = ((RelabelType *) leftop)->arg;
+
+					/* Clause does not match this partition key. */
+					if (!equal(leftop, partkey))
+						continue;
+
+					rightop = IsA(clause, Var)
+									? (Expr *) makeBoolConst(true, false)
+									: (Expr *) makeBoolConst(false, false);
+				}
+
+				pc = (PartClause *) palloc0(sizeof(PartClause));
+				pc->opno = BooleanEqualOperator;
+				pc->inputcollid = InvalidOid;
+				pc->value = rightop;
+
+				partclauseinfo->keyclauses[i] =
+										lappend(partclauseinfo->keyclauses[i],
+												pc);
+				partclauseinfo->foundkeyclauses = true;
+			}
+		}
+	}
+}
+
+/*
+ * get_partitions_from_args
+ *
+ * Returns the set of partitions of relation, each of which satisfies some
+ * clause in or_args.
+ */
+static Bitmapset *
+get_partitions_from_or_args(PartitionPruneContext *context, List *or_args)
+{
+	Bitmapset	   *result = NULL;
+	ListCell	   *lc;
+
+	/*
+	 * When matching an OR expression, it is only checked if at least one of
+	 * its args matches the partition key, not all.  For arguments that don't
+	 * match, we cannot eliminate any of its partitions using
+	 * get_partitions_from_clauses().  However, if the table is itself a
+	 * partition, we may be able to prove using constraint exclusion that the
+	 * clause refutes its partition constraint, that is, we can eliminate all
+	 * of its partitions.
+	 */
+	foreach(lc, or_args)
+	{
+		List *clauses = list_make1(lfirst(lc));
+		PartitionPruneContext subcontext;
+		Bitmapset *arg_partset;
+
+		/*
+		 * All fields except clauseinfo are same as in the parent context,
+		 * which will be set by calling extract_partition_clauses().
+		 */
+		memcpy(&subcontext, context, sizeof(PartitionPruneContext));
+		extract_partition_clauses(&subcontext, clauses);
+
+		if (!subcontext.clauseinfo->foundkeyclauses)
+		{
+			List *partconstr = context->partition_qual;
+
+			if (partconstr)
+			{
+				partconstr = (List *) expression_planner((Expr *) partconstr);
+				if (context->relid != 1)
+					ChangeVarNodes((Node *) partconstr, 1, context->relid, 0);
+				if (predicate_refuted_by(partconstr, clauses, false))
+					continue;
+			}
+
+			/* Couldn't eliminate any of the partitions. */
+			return bms_add_range(NULL, 0, context->nparts - 1);
+		}
+
+		if (!subcontext.clauseinfo->constfalse)
+			arg_partset = get_partitions_from_clauses(&subcontext);
+		else
+			arg_partset = NULL;
+
+		result = bms_add_members(result, arg_partset);
+		bms_free(arg_partset);
+	}
+
+	return result;
+}
+
+/*
+ * remove_redundant_clauses
+ *		Processes the clauses contained in context->clauseinfo to remove the
+ *		ones that are superseeded by other clauses which are more restrictive.
+ *
+ * Finished lists of clauses are returned in *minimalclauses which is an array
+ * with one slot for each of the partition keys.
+ *
+ * For example, x > 1 AND x > 2 and x >= 5, the latter is the most
+ * restrictive, so 5 is the best minimum bound for x.
+ *
+ * We also look for clauses which contradict one another in a way that proves
+ * that the clauses cannot possibly match any partition.  Impossible clauses
+ * include things like: x = 1 AND x = 2, x > 0 and x < 10.  The function
+ * returns right after finding such a clause and before returning, sets a field
+ * in context->clauseinfo to inform the caller that we found such clause.
+ */
+static void
+remove_redundant_clauses(PartitionPruneContext *context,
+						 List **minimalclauses)
+{
+	PartClause *hash_clause,
+			   *btree_clauses[BTMaxStrategyNumber];
+	PartitionClauseInfo *partclauseinfo = context->clauseinfo;
+	ListCell *lc;
+	int		s;
+	int		i;
+	bool	test_result;
+
+	for (i = 0; i < context->partnatts; i++)
+	{
+		List *keyclauses = partclauseinfo->keyclauses[i];
+
+		minimalclauses[i] = NIL;
+		hash_clause = NULL;
+
+		memset(btree_clauses, 0, sizeof(btree_clauses));
+
+		foreach(lc, keyclauses)
+		{
+			PartClause *pc = (PartClause *) lfirst(lc);
+
+			if (!pc->valid_cache)
+			{
+				Oid		lefttype;
+
+				get_op_opfamily_properties(pc->opno,
+										   context->partopfamily[i],
+										   false,
+										   &pc->op_strategy,
+										   &lefttype,
+										   &pc->op_subtype);
+				fmgr_info(get_opcode(pc->opno), &pc->op_func);
+				pc->valid_cache = true;
+			}
+
+			/*
+			 * Hash-partitioning knows only about equality.  So, if we've
+			 * matched a clause and found another clause whose constant
+			 * operand doesn't match the constant operand of the former, then
+			 * we have found mutually contradictory clauses.
+			 */
+			if (context->strategy == PARTITION_STRATEGY_HASH)
+			{
+				if (hash_clause == NULL)
+					hash_clause = pc;
+				/* check if another clause would contradict the one we have */
+				else if (partition_cmp_args(context->parttypid[i],
+											context->partopfamily[i],
+											pc, pc, hash_clause,
+											&test_result))
+				{
+					if (!test_result)
+					{
+						partclauseinfo->constfalse = true;
+						return;
+					}
+				}
+				/*
+				 * Couldn't compare; keep hash_clause set to the previous value,
+				 * and add this one directly to the result.  Caller would
+				 * arbitrarily choose one of the many and perform
+				 * partition-pruning with it.
+				 */
+				else
+					minimalclauses[i] = lappend(minimalclauses[i], pc);
+
+				/*
+				 * The code below handles btree operators, so not relevant for
+				 * hash partitioning.
+				 */
+				continue;
+			}
+
+			/*
+			 * The code that follows closely mimics similar processing done by
+			 * nbtutils.c: _bt_preprocess_keys().
+			 *
+			 * btree_clauses[s] points currently best clause containing the
+			 * operator strategy type s+1; it is NULL if we haven't yet found
+			 * such a clause.
+			 */
+			s = pc->op_strategy - 1;
+			if (btree_clauses[s] == NULL)
+			{
+				btree_clauses[s] = pc;
+			}
+			else
+			{
+				/*
+				 * Is this one more restrictive than what we already have?
+				 *
+				 * Consider some examples: 1. If btree_clauses[BTLT] now contains
+				 * a < 5, and cur is a < 3, then because 3 < 5 is true, a < 5
+				 * currently at btree_clauses[BTLT] will be replaced by a < 3.
+				 *
+				 * 2. If btree_clauses[BTEQ] now contains a = 5 and cur is a = 7,
+				 * then because 5 = 7 is false, we found a mutual contradiction,
+				 * so we set *constfalse to true and return.
+				 *
+				 * 3. If btree_clauses[BTLT] now contains a < 5 and cur is a < 7,
+				 * then because 7 < 5 is false, we leave a < 5 where it is and
+				 * effectively discard a < 7 as being redundant.
+				 */
+				if (partition_cmp_args(context->parttypid[i],
+									   context->partopfamily[i],
+									   pc, pc, btree_clauses[s],
+									   &test_result))
+				{
+					/* cur is more restrictive, so replace the existing. */
+					if (test_result)
+						btree_clauses[s] = pc;
+					else if (s == BTEqualStrategyNumber - 1)
+					{
+						partclauseinfo->constfalse = true;
+						return;
+					}
+
+					/* Old one is more restrictive, so keep around. */
+				}
+				else
+				{
+					/*
+					 * We couldn't determine which one is more restrictive.  Keep
+					 * the previous one in btree_clauses[s] and push this one directly
+					 * to the output list.
+					 */
+					minimalclauses[i] = lappend(minimalclauses[i], pc);
+				}
+			}
+		}
+
+		if (context->strategy == PARTITION_STRATEGY_HASH)
+		{
+			/* Note we didn't add this one to the result yet. */
+			if (hash_clause)
+				minimalclauses[i] = lappend(minimalclauses[i], hash_clause);
+			continue;
+		}
+
+		/* Compare btree operator clauses across strategies. */
+
+		/* Compare the equality clause with clauses of other strategies. */
+		if (btree_clauses[BTEqualStrategyNumber - 1])
+		{
+			PartClause *eq = btree_clauses[BTEqualStrategyNumber - 1];
+
+			for (s = 0; s < BTMaxStrategyNumber; s++)
+			{
+				PartClause *chk = btree_clauses[s];
+
+				if (!chk || s == (BTEqualStrategyNumber - 1))
+					continue;
+
+				/*
+				 * Suppose btree_clauses[BTLT] contained a < 5 and the eq clause
+				 * is a = 5, then because 5 < 5 is false, we found contradiction.
+				 * That is, a < 5 and a = 5 are mutually contradictory.  OTOH, if
+				 * eq clause is a = 3, then because 3 < 5, we no longer need
+				 * a < 5, because a = 3 is more restrictive.
+				 */
+				if (partition_cmp_args(context->parttypid[i],
+									   context->partopfamily[i],
+									   chk, eq, chk,
+									   &test_result))
+				{
+					if (!test_result)
+					{
+						partclauseinfo->constfalse = true;
+						return;
+					}
+					/* Discard the no longer needed clause. */
+					btree_clauses[s] = NULL;
+				}
+			}
+		}
+
+		/*
+		 * Try to keep only one of <, <=.
+		 *
+		 * Suppose btree_clauses[BTLT] contains a < 3 and btree_clauses[BTLE]
+		 * contains a <= 3 (or a <= 4), then because 3 <= 3 (or 3 <= 4) is true,
+		 * we discard the a <= 3 (or a <= 4) as redundant.  If the latter contains
+		 * contains a <= 2, then because 3 <= 2 is false, we dicard a < 3 as
+		 * redundant.
+		 */
+		if (btree_clauses[BTLessStrategyNumber - 1] &&
+			btree_clauses[BTLessEqualStrategyNumber - 1])
+		{
+			PartClause *lt = btree_clauses[BTLessStrategyNumber - 1],
+					   *le = btree_clauses[BTLessEqualStrategyNumber - 1];
+
+			if (partition_cmp_args(context->parttypid[i],
+								   context->partopfamily[i],
+								   le, lt, le,
+								   &test_result))
+			{
+				if (test_result)
+					btree_clauses[BTLessEqualStrategyNumber - 1] = NULL;
+				else
+					btree_clauses[BTLessStrategyNumber - 1] = NULL;
+			}
+		}
+
+		/* Try to keep only one of >, >=.  See the example above. */
+		if (btree_clauses[BTGreaterStrategyNumber - 1] &&
+			btree_clauses[BTGreaterEqualStrategyNumber - 1])
+		{
+			PartClause *gt = btree_clauses[BTGreaterStrategyNumber - 1],
+					   *ge = btree_clauses[BTGreaterEqualStrategyNumber - 1];
+
+			if (partition_cmp_args(context->parttypid[i],
+								   context->partopfamily[i],
+								   ge, gt, ge,
+								   &test_result))
+			{
+				if (test_result)
+					btree_clauses[BTGreaterEqualStrategyNumber - 1] = NULL;
+				else
+					btree_clauses[BTGreaterStrategyNumber - 1] = NULL;
+			}
+		}
+
+		/*
+		 * btree_clauses now contains the "best" clause or NULL for each btree
+		 * strategy number.  Add to the newlist.
+		 */
+		for (s = 0; s < BTMaxStrategyNumber; s++)
+		{
+			if (btree_clauses[s])
+				minimalclauses[i] = lappend(minimalclauses[i],
+											btree_clauses[s]);
+		}
+	}
+}
+
+/*
+ * partition_cmp_args
+ *		Try to compare the constant arguments of 'leftarg' and 'rightarg', in
+ *		that order, using the operator of 'op' and set *result to the result
+ *		of this comparison.
+ *
+ * Returns true if we could actually perform the comparison; otherwise false.
+ *
+ * Note: We may not be able to perform the comparison if operand values are
+ * unknown in this context or if the type of any of the operands are
+ * incompatible with the operator.
+ */
+static bool
+partition_cmp_args(Oid parttypid, Oid partopfamily,
+				   PartClause *pc, PartClause *leftarg, PartClause *rightarg,
+				   bool *result)
+{
+	Datum	left_value;
+	Datum	right_value;
+
+	Assert(pc->valid_cache && leftarg->valid_cache && rightarg->valid_cache);
+
+	/*
+	 * Try to extract an actual value from each arg.  This may fail if the
+	 * value is unknown in this context, in which case we cannot compare.
+	 */
+	if (!partkey_datum_from_expr(parttypid, leftarg->value, &left_value))
+		return false;
+
+	if (!partkey_datum_from_expr(parttypid, rightarg->value, &right_value))
+		return false;
+
+	/*
+	 * We can compare left_value and right_value using op's operator
+	 * only if both are of the expected type.
+	 */
+	if (leftarg->op_subtype == pc->op_subtype &&
+		rightarg->op_subtype == pc->op_subtype)
+	{
+		*result = DatumGetBool(FunctionCall2Coll(&pc->op_func,
+												 pc->inputcollid,
+												 left_value,
+												 right_value));
+		return true;
+	}
+	else
+	{
+		Oid		cmp_op;
+
+		/* Otherwise, look one up in the partitioning operator family. */
+		cmp_op = get_opfamily_member(partopfamily,
+									 leftarg->op_subtype,
+									 rightarg->op_subtype,
+									 pc->op_strategy);
+		if (OidIsValid(cmp_op))
+		{
+			*result = DatumGetBool(OidFunctionCall2Coll(get_opcode(cmp_op),
+														pc->inputcollid,
+														left_value,
+														right_value));
+			return true;
+		}
+	}
+
+	/* Couldn't do the comparison. */
+	*result = false;
+	return false;
+}
+
+/*
+ * extract_bounding_datums
+ *		Process clauses in context->clauseinfo and populate 'keys' with all
+ *		min/max/equal/not-equal values that we're able to determine.
+ *
+ * *minimalclauses is an array with partnatts members, each of which is a list
+ * of the most restrictive clauses of each operator strategy for the given
+ * partition key.
+ *
+ * For RANGE partitioning we do not need to match and find values for all
+ * partition keys.  We may be able to eliminate some partitions with just a
+ * prefix of the partition keys.  HASH partitioning does require all keys are
+ * matched to with at least some combinations of equality clauses and IS NULL
+ * clauses. LIST partitions don't support multiple partition keys.
+ *
+ * Returns true if at least one key was found; false otherwise.
+ */
+static bool
+extract_bounding_datums(PartitionPruneContext *context,
+						List **minimalclauses, PartScanKeyInfo *keys)
+{
+	PartitionClauseInfo *clauseinfo = context->clauseinfo;
+	bool		need_next_eq,
+				need_next_min,
+				need_next_max;
+	int 		i;
+	ListCell   *lc;
+
+	/*
+	 * Based on the strategies of the clauses' operators (=, </<=, >/>=), try
+	 * to construct a tuple of those datums that serve as the exact lookup
+	 * tuple or two tuples that serve as minimum and maximum bound.
+	 *
+	 * If we find datums for all partition key columns that appear in =
+	 * operator clauses, then we have the exact match lookup tuple, which will
+	 * be used to match just one partition (although that's required only for
+	 * range partitioning, finding datums for just some columns is fine for
+	 * hash partitioning).
+	 *
+	 * If the last datum in a tuple comes from a clause containing </<= or
+	 * >/>= operator, then that constitutes the minimum or maximum bound tuple,
+	 * respectively.  There is one exception -- if we have a tuple containing
+	 * values for only a prefix of partition key columns, where none of its
+	 * values come from a </<= or >/>= operator clause, we still consider such
+	 * tuple as both minimum and maximum bound tuple.
+	 */
+	need_next_eq = true;
+	need_next_min = true;
+	need_next_max = true;
+	memset(keys, 0, sizeof(PartScanKeyInfo));
+	for (i = 0; i < context->partnatts; i++)
+	{
+		List *clauselist = minimalclauses[i];
+
+		/*
+		 * Min and max keys must constitute a prefix of the partition key and
+		 * must appear in the same order as partition keys.  Equal keys have
+		 * to satisfy that requirement only for non-hash partitioning.
+		 */
+		if (i > keys->n_eqkeys &&
+			context->strategy != PARTITION_STRATEGY_HASH)
+			need_next_eq = false;
+
+		if (i > keys->n_minkeys)
+			need_next_min = false;
+
+		if (i > keys->n_maxkeys)
+			need_next_max = false;
+
+		foreach(lc, clauselist)
+		{
+			PartClause *clause = (PartClause *) lfirst(lc);
+			Expr *value = clause->value;
+			bool incl;
+			PartOpStrategy	op_strategy;
+
+			op_strategy = partition_op_strategy(context->strategy, clause,
+												&incl);
+			switch (op_strategy)
+			{
+				case PART_OP_EQUAL:
+					Assert(incl);
+					if (need_next_eq &&
+						partkey_datum_from_expr(context->parttypid[i], value,
+												&keys->eqkeys[i]))
+						keys->n_eqkeys++;
+
+					if (need_next_max &&
+						partkey_datum_from_expr(context->parttypid[i], value,
+												&keys->maxkeys[i]))
+					{
+						keys->n_maxkeys++;
+						keys->max_incl = true;
+					}
+
+					if (need_next_min &&
+						partkey_datum_from_expr(context->parttypid[i], value,
+												&keys->minkeys[i]))
+					{
+						keys->n_minkeys++;
+						keys->min_incl = true;
+					}
+					break;
+
+				case PART_OP_LESS:
+					if (need_next_max &&
+						partkey_datum_from_expr(context->parttypid[i], value,
+												&keys->maxkeys[i]))
+					{
+						keys->n_maxkeys++;
+						keys->max_incl = incl;
+						if (!incl)
+							need_next_eq = need_next_max = false;
+					}
+					break;
+
+				case PART_OP_GREATER:
+					if (need_next_min &&
+						partkey_datum_from_expr(context->parttypid[i], value,
+												&keys->minkeys[i]))
+					{
+						keys->n_minkeys++;
+						keys->min_incl = incl;
+						if (!incl)
+							need_next_eq = need_next_min = false;
+					}
+					break;
+
+				default:
+					Assert(false);
+			}
+		}
+	}
+
+	/*
+	 * To set eqkeys, we must have found matching clauses containing =
+	 * operator for all partition key columns and if present we don't need
+	 * the values in minkeys and maxkeys anymore.  In the case hash
+	 * partitioning, we don't require all of eqkeys to be operator clausses.
+	 * In that case, any IS NULL clauses involving partition key columns are
+	 * also considered as equality keys by the code for hash partition pruning,
+	 * which checks that all partition columns are covered before actually
+	 * performing the pruning.
+	 */
+	if (keys->n_eqkeys == context->partnatts ||
+		context->strategy == PARTITION_STRATEGY_HASH)
+		keys->n_minkeys = keys->n_maxkeys = 0;
+	else
+		keys->n_eqkeys = 0;
+
+	/* Finally, also set the keyisnull and keyisnotnull values. */
+	keys->keyisnull = clauseinfo->keyisnull;
+	keys->keyisnotnull = clauseinfo->keyisnotnull;
+
+	if (clauseinfo->ne_clauses)
+	{
+		keys->ne_datums = (Datum *)
+								palloc0(list_length(clauseinfo->ne_clauses) *
+										sizeof(Datum));
+		i = 0;
+		foreach(lc, clauseinfo->ne_clauses)
+		{
+			PartClause *pc = (PartClause *) lfirst(lc);
+			Datum	datum;
+
+			if (partkey_datum_from_expr(context->parttypid[0], pc->value,
+										&datum))
+				keys->ne_datums[i++] = datum;
+		}
+		keys->n_ne_datums = i;
+	}
+
+	return (keys->n_eqkeys > 0 || keys->n_minkeys > 0 ||
+			keys->n_maxkeys > 0 ||  keys->n_ne_datums > 0 ||
+			!bms_is_empty(keys->keyisnull) ||
+			!bms_is_empty(keys->keyisnotnull));
+}
+
+/*
+ * partition_op_strategy
+ *		Returns whether the clause in 'pc' contains an =, </<=, or >/>=
+ *		operator and set *incl to true if the operator's strategy is
+ *		inclusive.
+ */
+static PartOpStrategy
+partition_op_strategy(char part_strategy, PartClause *pc, bool *incl)
+{
+	*incl = false;	/* may be overwritten below */
+
+	switch (part_strategy)
+	{
+		/* Hash partitioning allows only hash equality. */
+		case PARTITION_STRATEGY_HASH:
+			if (pc->op_strategy == HTEqualStrategyNumber)
+			{
+				*incl = true;
+				return PART_OP_EQUAL;
+			}
+			elog(ERROR, "unexpected operator strategy number: %d",
+				 pc->op_strategy);
+
+		/* List and range partitioning support all btree operators. */
+		case PARTITION_STRATEGY_LIST:
+		case PARTITION_STRATEGY_RANGE:
+			switch (pc->op_strategy)
+			{
+				case BTLessEqualStrategyNumber:
+					*incl = true;
+					/* fall through */
+
+				case BTLessStrategyNumber:
+					return PART_OP_LESS;
+
+				case BTEqualStrategyNumber:
+					*incl = true;
+					return PART_OP_EQUAL;
+
+				case BTGreaterEqualStrategyNumber:
+					*incl = true;
+					/* fall through */
+
+				case BTGreaterStrategyNumber:
+					return PART_OP_GREATER;
+			}
+
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+				 (int) part_strategy);
+	}
+
+	return PART_OP_EQUAL;		/* keep compiler quiet */
+}
+
+/*
+ * partkey_datum_from_expr
+ *		Set *value to the constant value obtained by evaluating 'expr'
+ *
+ * Note that we may not be able to evaluate the input expression, in which
+ * case, the function returns false to indicate that *value has not been
+ * set.  True is returned otherwise.
+ */
+static bool
+partkey_datum_from_expr(Oid parttypid, Expr *expr, Datum *value)
+{
+	Oid		exprtype = exprType((Node *) expr);
+
+	if (exprtype != parttypid)
+	{
+		ParseState *pstate = make_parsestate(NULL);
+
+		expr = (Expr *) coerce_to_target_type(pstate, (Node *) expr,
+									 exprtype,
+									 parttypid, -1,
+									 COERCION_EXPLICIT,
+									 COERCE_IMPLICIT_CAST, -1);
+		free_parsestate(pstate);
+
+		/*
+		 * If we couldn't coerce to the partition key's type, that is, the
+		 * type of the datums stored in PartitionBoundInfo for this partition
+		 * key, there's no hope of using this expression for anything
+		 * partitioning-related.
+		 */
+		if (expr == NULL)
+			return false;
+
+		/*
+		 * Transform into a form that the following code can do something
+		 * useful with.
+		 */
+		expr = evaluate_expr(expr,
+							 exprType((Node *) expr),
+							 exprTypmod((Node *) expr),
+							 exprCollation((Node *) expr));
+	}
+
+	/*
+	 * Add more expression types here as needed to support the requirements
+	 * of the higher-level code.
+	 */
+	if (IsA(expr, Const))
+	{
+		*value = ((Const *) expr)->constvalue;
+		return true;
+	}
+
+	return false;
+}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index dcfc1665a8..f3063be6d9 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,22 +1256,32 @@ 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 each expression through const-simplification and
-		 * canonicalization similar to check constraints.
-		 */
-		pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
-		pcqual = (List *) canonicalize_qual((Expr *) pcqual);
+		List	   *pcqual = RelationGetPartitionQual(relation);
+
+		if (pcqual)
+		{
+			/*
+			 * Run each expression through const-simplification and
+			 * canonicalization similar to check constraints.
+			 */
+			pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
+			pcqual = (List *) canonicalize_qual((Expr *) pcqual);
 
-		/* Fix Vars to have the desired varno */
-		if (varno != 1)
-			ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+			/* Fix Vars to have the desired varno */
+			if (varno != 1)
+				ChangeVarNodes((Node *) pcqual, 1, varno, 0);
 
-		result = list_concat(result, pcqual);
+			result = list_concat(result, pcqual);
+		}
 	}
 
 	heap_close(relation, NoLock);
@@ -1856,6 +1865,11 @@ 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);
+	if (OidIsValid(get_default_oid_from_partdesc(partdesc)))
+		rel->has_default_part = true;
+	else
+		rel->has_default_part = false;
+	rel->partition_qual = RelationGetPartitionQual(relation);
 }
 
 /*
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 5c368321e6..5b5be8fe16 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -154,6 +154,8 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->part_scheme = NULL;
 	rel->nparts = 0;
 	rel->boundinfo = NULL;
+	rel->has_default_part = false;
+	rel->partition_qual = NIL;
 	rel->part_rels = NULL;
 	rel->partexprs = NULL;
 	rel->nullable_partexprs = NULL;
@@ -567,6 +569,8 @@ build_join_rel(PlannerInfo *root,
 	joinrel->part_scheme = NULL;
 	joinrel->nparts = 0;
 	joinrel->boundinfo = NULL;
+	joinrel->has_default_part = false;
+	joinrel->partition_qual = NIL;
 	joinrel->part_rels = NULL;
 	joinrel->partexprs = NULL;
 	joinrel->nullable_partexprs = NULL;
@@ -734,6 +738,10 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
 	joinrel->has_eclass_joins = false;
 	joinrel->top_parent_relids = NULL;
 	joinrel->part_scheme = NULL;
+	joinrel->nparts = 0;
+	joinrel->boundinfo = NULL;
+	joinrel->has_default_part = false;
+	joinrel->partition_qual = NIL;
 	joinrel->part_rels = NULL;
 	joinrel->partexprs = NULL;
 	joinrel->nullable_partexprs = NULL;
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 2faf0ca26e..0dd6bd3020 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -42,6 +42,87 @@ typedef struct PartitionDescData
 
 typedef struct PartitionDescData *PartitionDesc;
 
+typedef struct PartitionPruneContext
+{
+	/* Table's range table index */
+	int		relid;
+
+	/* Partition key information */
+	char	strategy;
+	int		partnatts;
+	Expr  **partkeys;
+	Oid	   *parttypid;
+	Oid	   *partopfamily;
+	Oid	   *partcollation;
+	FmgrInfo *partsupfunc;
+
+	/* Number of partitions */
+	int		nparts;
+
+	/* Is one of the partitions the default partition */
+	bool	has_default_part;
+
+	/* Partition qual if this's not the root partitioned table */
+	List   *partition_qual;
+
+	/* Partition boundary info */
+	PartitionBoundInfo	boundinfo;
+
+	/* Information about matched clauses */
+	PartitionClauseInfo *clauseinfo;
+} PartitionPruneContext;
+
+/*
+ * PartScanKeyInfo
+ *		Information about partition look up keys to be passed to
+ *		get_partitions_for_keys()
+ *
+ * Stores Datums and nullness properties found in clauses which match the
+ * partition key.  Properties found are cached and are indexed by the
+ * partition key index.
+ */
+typedef struct PartScanKeyInfo
+{
+	/*
+	 * Equality look up key.  Used to store known Datums values from clauses
+	 * compared by an equality operation to the partition key.
+	 */
+	Datum	eqkeys[PARTITION_MAX_KEYS];
+
+	/*
+	 * Lower and upper bounds on a sequence of selected partitions.  These may
+	 * contain values for only a prefix of the partition keys.
+	 */
+	Datum	minkeys[PARTITION_MAX_KEYS];
+	Datum	maxkeys[PARTITION_MAX_KEYS];
+
+	/*
+	 * Number of values stored in the corresponding array above
+	 */
+	int		n_eqkeys;
+	int		n_minkeys;
+	int		n_maxkeys;
+
+	/*
+	 * Properties to mark if the clauses found for the corresponding partition
+	 * are inclusive of the stored value or not.
+	 */
+	bool	min_incl;
+	bool	max_incl;
+
+	/* Datum values from clauses containing <> operator */
+	Datum  *ne_datums;
+	int		n_ne_datums;
+
+	/*
+	 * Information about nullness of the partition keys, either specified
+	 * explicitly in the query (in the form of a IS [NOT] NULL clause) or
+	 * implied from strict clauses matching the partition key.
+	 */
+	Bitmapset   *keyisnull;
+	Bitmapset   *keyisnotnull;
+} PartScanKeyInfo;
+
 extern void RelationBuildPartitionDesc(Relation relation);
 extern bool partition_bounds_equal(int partnatts, int16 *parttyplen,
 					   bool *parttypbyval, PartitionBoundInfo b1,
@@ -73,4 +154,8 @@ 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_partitions_for_keys(PartitionPruneContext *context,
+						PartScanKeyInfo *keys);
+
 #endif							/* PARTITION_H */
diff --git a/src/include/catalog/pg_opfamily.h b/src/include/catalog/pg_opfamily.h
index b544474254..0847df97ff 100644
--- a/src/include/catalog/pg_opfamily.h
+++ b/src/include/catalog/pg_opfamily.h
@@ -188,4 +188,7 @@ DATA(insert OID = 4104 (	3580	box_inclusion_ops		PGNSP PGUID ));
 DATA(insert OID = 5000 (	4000	box_ops		PGNSP PGUID ));
 DATA(insert OID = 5008 (	4000	poly_ops				PGNSP PGUID ));
 
+#define IsBooleanOpfamily(opfamily) \
+	((opfamily) == BOOL_BTREE_FAM_OID || (opfamily) == BOOL_HASH_FAM_OID)
+
 #endif							/* PG_OPFAMILY_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 74b094a9c3..0ac242aeda 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -190,6 +190,7 @@ typedef enum NodeTag
 	T_JoinExpr,
 	T_FromExpr,
 	T_OnConflictExpr,
+	T_PartitionClauseInfo,
 	T_IntoClause,
 
 	/*
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1b4b0d75af..54c678bb43 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1506,4 +1506,37 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*----------
+ * PartitionClauseInfo
+ *
+ * Stores clauses which were matched to a partition key. Each matching clause
+ * is stored in the 'keyclauses' list for the partition key index that it was
+ * matched to.  Other details are also stored, such as OR clauses and
+ * not-equal (<>) clauses.  Nullness properties are also stored.
+ *----------
+ */
+typedef struct PartitionClauseInfo
+{
+	NodeTag		type;
+
+	/* Lists of clauses indexed by the partition key */
+	List   *keyclauses[PARTITION_MAX_KEYS];
+
+	/* Each members is a List itself of a given OR clauses's arguments. */
+	List   *or_clauses;
+
+	/* List of clauses containing <> operator. */
+	List   *ne_clauses;
+
+	/* Nth (0 <= N < partnatts) bit set if the key is NULL or NOT NULL. */
+	Bitmapset   *keyisnull;
+	Bitmapset   *keyisnotnull;
+
+	/* True if at least one of above fields contains valid information. */
+	bool	foundkeyclauses;
+
+	/* True if mutually contradictory clauses were found. */
+	bool	constfalse;
+} PartitionClauseInfo;
+
 #endif							/* PRIMNODES_H */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index ce9975c620..5ee23a5bb5 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -538,6 +538,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
  *
@@ -666,6 +668,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/clauses.h b/src/include/optimizer/clauses.h
index ba4fa4b68b..3c2f54964b 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -84,5 +84,7 @@ extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
 
 extern Query *inline_set_returning_function(PlannerInfo *root,
 							  RangeTblEntry *rte);
+extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
+			  Oid result_collation);
 
 #endif							/* CLAUSES_H */
diff --git a/src/include/optimizer/partprune.h b/src/include/optimizer/partprune.h
new file mode 100644
index 0000000000..5c0d469600
--- /dev/null
+++ b/src/include/optimizer/partprune.h
@@ -0,0 +1,25 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 Bitmapset *prune_append_rel_partitions(PlannerInfo *root,
+							RelOptInfo *rel);
+extern void generate_partition_clauses(PartitionPruneContext *context,
+							List *clauses);
+extern Bitmapset *get_partitions_from_clauses(PartitionPruneContext *context);
+
+#endif							/* PARTPRUNE_H */
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index a79f891da7..11a259ca25 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1715,11 +1715,7 @@ explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd'
  Append
    ->  Seq Scan on part_ab_cd
          Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
-   ->  Seq Scan on part_ef_gh
-         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
-   ->  Seq Scan on part_null_xy
-         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
-(7 rows)
+(3 rows)
 
 explain (costs off) select * from list_parted where a = 'ab';
                 QUERY PLAN                
@@ -1906,11 +1902,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 348719bd62..bc9ff38253 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        
@@ -519,15 +517,13 @@ explain (costs off) select * from rlp where a <= 31;
          Filter: (a <= 31)
    ->  Seq Scan on rlp5_1
          Filter: (a <= 31)
-   ->  Seq Scan on rlp5_default
-         Filter: (a <= 31)
    ->  Seq Scan on rlp_default_10
          Filter: (a <= 31)
    ->  Seq Scan on rlp_default_30
          Filter: (a <= 31)
    ->  Seq Scan on rlp_default_default
          Filter: (a <= 31)
-(29 rows)
+(27 rows)
 
 explain (costs off) select * from rlp where a = 1 or a = 7;
               QUERY PLAN              
@@ -575,9 +571,7 @@ explain (costs off) select * from rlp where a > 20 and a < 27;
          Filter: ((a > 20) AND (a < 27))
    ->  Seq Scan on rlp4_2
          Filter: ((a > 20) AND (a < 27))
-   ->  Seq Scan on rlp4_default
-         Filter: ((a > 20) AND (a < 27))
-(7 rows)
+(5 rows)
 
 explain (costs off) select * from rlp where a = 29;
            QUERY PLAN           
@@ -651,8 +645,6 @@ explain (costs off) select * from rlp where (a = 1 and a = 3) or (a > 1 and a =
                             QUERY PLAN                             
 -------------------------------------------------------------------
  Append
-   ->  Seq Scan on rlp2
-         Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15)))
    ->  Seq Scan on rlp3abcd
          Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15)))
    ->  Seq Scan on rlp3efgh
@@ -661,7 +653,7 @@ explain (costs off) select * from rlp where (a = 1 and a = 3) or (a > 1 and a =
          Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15)))
    ->  Seq Scan on rlp3_default
          Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15)))
-(11 rows)
+(9 rows)
 
 -- multi-column keys
 create table mc3p (a int, b int, c int) partition by range (a, abs(b), c);
@@ -716,9 +708,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                            
@@ -894,6 +884,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
@@ -904,7 +896,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                                  
@@ -965,9 +957,11 @@ explain (costs off) select * from mc2p where a = 2 and b < 1;
               QUERY PLAN               
 ---------------------------------------
  Append
+   ->  Seq Scan on mc2p2
+         Filter: ((b < 1) AND (a = 2))
    ->  Seq Scan on mc2p3
          Filter: ((b < 1) AND (a = 2))
-(3 rows)
+(5 rows)
 
 explain (costs off) select * from mc2p where a > 1;
            QUERY PLAN           
@@ -1009,24 +1003,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                    
@@ -1036,33 +1026,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             
@@ -1088,4 +1067,355 @@ explain (costs off) select * from boolpart where a is not unknown;
          Filter: (a IS NOT UNKNOWN)
 (7 rows)
 
-drop table lp, coll_pruning, rlp, mc3p, mc2p, boolpart;
+-- 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 in all cases below
+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)
+
+--
+-- some more cases
+--
+--
+-- pruning for partitioned table appearing inside a sub-query
+--
+-- pruning won't work for mc3p, because some keys are Params
+explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 where t2.a = t1.b and abs(t2.b) = 1 and t2.c = 1) s where t1.a = 1;
+                              QUERY PLAN                               
+-----------------------------------------------------------------------
+ Nested Loop
+   ->  Append
+         ->  Seq Scan on mc2p0 t1
+               Filter: (a = 1)
+         ->  Seq Scan on mc2p1 t1_1
+               Filter: (a = 1)
+         ->  Seq Scan on mc2p2 t1_2
+               Filter: (a = 1)
+         ->  Seq Scan on mc2p_default t1_3
+               Filter: (a = 1)
+   ->  Aggregate
+         ->  Append
+               ->  Seq Scan on mc3p0 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 mc3p2 t2_2
+                     Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1))
+               ->  Seq Scan on mc3p3 t2_3
+                     Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1))
+               ->  Seq Scan on mc3p4 t2_4
+                     Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1))
+               ->  Seq Scan on mc3p5 t2_5
+                     Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1))
+               ->  Seq Scan on mc3p6 t2_6
+                     Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1))
+               ->  Seq Scan on mc3p7 t2_7
+                     Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1))
+               ->  Seq Scan on mc3p_default t2_8
+                     Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1))
+(30 rows)
+
+-- pruning should work fine, because prefix of keys is available
+explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 where t2.c = t1.b and abs(t2.b) = 1 and t2.a = 1) s where t1.a = 1;
+                              QUERY PLAN                               
+-----------------------------------------------------------------------
+ Nested Loop
+   ->  Append
+         ->  Seq Scan on mc2p0 t1
+               Filter: (a = 1)
+         ->  Seq Scan on mc2p1 t1_1
+               Filter: (a = 1)
+         ->  Seq Scan on mc2p2 t1_2
+               Filter: (a = 1)
+         ->  Seq Scan on mc2p_default t1_3
+               Filter: (a = 1)
+   ->  Aggregate
+         ->  Append
+               ->  Seq Scan on mc3p0 t2
+                     Filter: ((c = t1.b) AND (a = 1) AND (abs(b) = 1))
+               ->  Seq Scan on mc3p1 t2_1
+                     Filter: ((c = t1.b) AND (a = 1) AND (abs(b) = 1))
+(16 rows)
+
+-- pruning should work fine in this case, too.
+explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 where t2.a = 1 and abs(t2.b) = 1 and t2.c = 1) s where t1.a = 1;
+                             QUERY PLAN                             
+--------------------------------------------------------------------
+ Nested Loop
+   ->  Aggregate
+         ->  Append
+               ->  Seq Scan on mc3p1 t2
+                     Filter: ((a = 1) AND (c = 1) AND (abs(b) = 1))
+   ->  Append
+         ->  Seq Scan on mc2p0 t1
+               Filter: (a = 1)
+         ->  Seq Scan on mc2p1 t1_1
+               Filter: (a = 1)
+         ->  Seq Scan on mc2p2 t1_2
+               Filter: (a = 1)
+         ->  Seq Scan on mc2p_default t1_3
+               Filter: (a = 1)
+(14 rows)
+
+--
+-- pruning with clauses containing <> operator
+--
+-- doesn't prune range or hash partitions
+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)
+
+create table rp (a int) partition by range (a);
+create table rp0 partition of rp for values from (minvalue) to (1);
+create table rp1 partition of rp for values from (1) to (2);
+create table rp2 partition of rp for values from (2) to (maxvalue);
+explain (costs off) select * from rp where a <> 1;
+        QUERY PLAN        
+--------------------------
+ Append
+   ->  Seq Scan on rp0
+         Filter: (a <> 1)
+   ->  Seq Scan on rp1
+         Filter: (a <> 1)
+   ->  Seq Scan on rp2
+         Filter: (a <> 1)
+(7 rows)
+
+explain (costs off) select * from rp where a <> 1 and a <> 2;
+               QUERY PLAN                
+-----------------------------------------
+ Append
+   ->  Seq Scan on rp0
+         Filter: ((a <> 1) AND (a <> 2))
+   ->  Seq Scan on rp1
+         Filter: ((a <> 1) AND (a <> 2))
+   ->  Seq Scan on rp2
+         Filter: ((a <> 1) AND (a <> 2))
+(7 rows)
+
+-- null partition should be eliminated due to strict <> clause.
+explain (costs off) select * from lp where a <> 'a';
+             QUERY PLAN             
+------------------------------------
+ Append
+   ->  Seq Scan on lp_ad
+         Filter: (a <> 'a'::bpchar)
+   ->  Seq Scan on lp_bc
+         Filter: (a <> 'a'::bpchar)
+   ->  Seq Scan on lp_ef
+         Filter: (a <> 'a'::bpchar)
+   ->  Seq Scan on lp_g
+         Filter: (a <> 'a'::bpchar)
+   ->  Seq Scan on lp_default
+         Filter: (a <> 'a'::bpchar)
+(11 rows)
+
+-- ensure we detect contradictions in clauses; a can't be NULL and NOT NULL.
+explain (costs off) select * from lp where a <> 'a' and a is null;
+        QUERY PLAN        
+--------------------------
+ Result
+   One-Time Filter: false
+(2 rows)
+
+explain (costs off) select * from lp where (a <> 'a' and a <> 'd') or a is null;
+                                  QUERY PLAN                                  
+------------------------------------------------------------------------------
+ Append
+   ->  Seq Scan on lp_bc
+         Filter: (((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) OR (a IS NULL))
+   ->  Seq Scan on lp_ef
+         Filter: (((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) OR (a IS NULL))
+   ->  Seq Scan on lp_g
+         Filter: (((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) OR (a IS NULL))
+   ->  Seq Scan on lp_null
+         Filter: (((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) OR (a IS NULL))
+   ->  Seq Scan on lp_default
+         Filter: (((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) OR (a IS NULL))
+(11 rows)
+
+-- case for list partitioned table that's not root
+explain (costs off) select * from rlp where a = 15 and b <> 'ab' and b <> 'cd' and b <> 'xy' and b is not null;
+                                                                QUERY PLAN                                                                
+------------------------------------------------------------------------------------------------------------------------------------------
+ Append
+   ->  Seq Scan on rlp3efgh
+         Filter: ((b IS NOT NULL) AND ((b)::text <> 'ab'::text) AND ((b)::text <> 'cd'::text) AND ((b)::text <> 'xy'::text) AND (a = 15))
+   ->  Seq Scan on rlp3_default
+         Filter: ((b IS NOT NULL) AND ((b)::text <> 'ab'::text) AND ((b)::text <> 'cd'::text) AND ((b)::text <> 'xy'::text) AND (a = 15))
+(5 rows)
+
+drop table lp, coll_pruning, rlp, mc3p, mc2p, boolpart, hp, rp;
diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql
index 514f8e5ce1..b7c5abf378 100644
--- a/src/test/regress/sql/partition_prune.sql
+++ b/src/test/regress/sql/partition_prune.sql
@@ -152,4 +152,79 @@ explain (costs off) select * from boolpart where a is not true and a is not fals
 explain (costs off) select * from boolpart where a is unknown;
 explain (costs off) select * from boolpart where a is not unknown;
 
-drop table lp, coll_pruning, rlp, mc3p, mc2p, boolpart;
+-- 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 in all cases below
+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);
+
+--
+-- some more cases
+--
+
+--
+-- pruning for partitioned table appearing inside a sub-query
+--
+
+-- pruning won't work for mc3p, because some keys are Params
+explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 where t2.a = t1.b and abs(t2.b) = 1 and t2.c = 1) s where t1.a = 1;
+
+-- pruning should work fine, because prefix of keys is available
+explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 where t2.c = t1.b and abs(t2.b) = 1 and t2.a = 1) s where t1.a = 1;
+
+-- pruning should work fine in this case, too.
+explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 where t2.a = 1 and abs(t2.b) = 1 and t2.c = 1) s where t1.a = 1;
+
+--
+-- pruning with clauses containing <> operator
+--
+
+-- doesn't prune range or hash partitions
+explain (costs off) select * from hp where a <> 1 and b <> 'xxx';
+
+create table rp (a int) partition by range (a);
+create table rp0 partition of rp for values from (minvalue) to (1);
+create table rp1 partition of rp for values from (1) to (2);
+create table rp2 partition of rp for values from (2) to (maxvalue);
+
+explain (costs off) select * from rp where a <> 1;
+explain (costs off) select * from rp where a <> 1 and a <> 2;
+
+-- null partition should be eliminated due to strict <> clause.
+explain (costs off) select * from lp where a <> 'a';
+
+-- ensure we detect contradictions in clauses; a can't be NULL and NOT NULL.
+explain (costs off) select * from lp where a <> 'a' and a is null;
+
+explain (costs off) select * from lp where (a <> 'a' and a <> 'd') or a is null;
+
+-- case for list partitioned table that's not root
+explain (costs off) select * from rlp where a = 15 and b <> 'ab' and b <> 'cd' and b <> 'xy' and b is not null;
+
+drop table lp, coll_pruning, rlp, mc3p, mc2p, boolpart, hp, rp;
-- 
2.11.0