From b20b3a3deca2f9469045b2ea89683cb34ad5cc15 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 22 Aug 2017 13:48:13 +0900
Subject: [PATCH v23 2/5] Introduce a get_partitions_from_clauses()

Whereas get_partition_for_tuple() takes a tuple and returns index
of the partition of the table that should contain that tuple,
get_partitions_from_clauses() will take a list of clauses and return
a set of indexes of the partitions that satisfy all of those clauses.
Aforementioned list of clauses must be all clauses that were matched
to the partition key(s) using populate_partition_clauses()

It is meant as a faster alternative to the planner's current method
of selecting a table's partitions by running contraint exclusion
algorithm against the partition constraint of each of the partitions.

Authors: Amit Langote,
         David Rowley (david.rowley@2ndquadrant.com)
---
 src/backend/catalog/partition.c      | 2071 ++++++++++++++++++++++++++++++++++
 src/backend/nodes/copyfuncs.c        |   22 +
 src/backend/optimizer/util/clauses.c |    4 +-
 src/include/catalog/partition.h      |   13 +
 src/include/catalog/pg_opfamily.h    |    3 +
 src/include/nodes/nodes.h            |    1 +
 src/include/nodes/primnodes.h        |   31 +
 src/include/optimizer/clauses.h      |    2 +
 8 files changed, 2144 insertions(+), 3 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index de2b53e0c8..a5179d177e 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -28,6 +28,8 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_opfamily.h"
 #include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_type.h"
 #include "commands/tablecmds.h"
@@ -38,6 +40,8 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planmain.h"
+#include "optimizer/planner.h"
+#include "optimizer/predtest.h"
 #include "optimizer/prep.h"
 #include "optimizer/var.h"
 #include "parser/parse_coerce.h"
@@ -163,6 +167,81 @@ typedef struct PartitionBoundCmpArg
 	int		ndatums;
 } PartitionBoundCmpArg;
 
+/*
+ * 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;
+
+/*
+ * 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;
+
+	/*
+	 * 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;
+
 static int32 qsort_partition_hbound_cmp(const void *a, const void *b);
 static int32 qsort_partition_list_value_cmp(const void *a, const void *b,
 							   void *arg);
@@ -211,6 +290,35 @@ static uint64 compute_hash_value(PartitionKey key, Datum *values, bool *isnull);
 /* SQL-callable function for use in hash partition CHECK constraints */
 PG_FUNCTION_INFO_V1(satisfies_hash_partition);
 
+static Bitmapset *get_partitions_excluded_by_ne_clauses(
+									  PartitionPruneContext *context,
+									  List *ne_clauses);
+static Bitmapset *get_partitions_from_or_clause_args(
+								   PartitionPruneContext *context,
+								   List *or_clause_args);
+static void extract_partition_key_clauses(PartitionKey partkey, List *clauses,
+							  int rt_index, PartitionClauseInfo *partclauses);
+static bool extract_bounding_datums(PartitionKey partkey,
+						PartitionPruneContext *context,
+						PartScanKeyInfo *keys);
+static bool partition_cmp_args(PartitionKey key, int partkeyidx,
+				   PartClause *pc, PartClause *leftarg, PartClause *rightarg,
+				   bool *result);
+static PartOpStrategy partition_op_strategy(PartitionKey key, PartClause *pc,
+					bool *incl);
+static bool partkey_datum_from_expr(PartitionKey key, int partkeyidx,
+						Expr *expr, Datum *value);
+static void remove_redundant_clauses(PartitionKey partkey,
+						 PartitionPruneContext *context);
+static Bitmapset *get_partitions_for_keys(Relation rel,
+						PartScanKeyInfo *keys);
+static Bitmapset *get_partitions_for_keys_hash(Relation rel,
+						PartScanKeyInfo *keys);
+static Bitmapset *get_partitions_for_keys_list(Relation rel,
+						PartScanKeyInfo *keys);
+static Bitmapset *get_partitions_for_keys_range(Relation rel,
+						PartScanKeyInfo *keys);
+
 /*
  * RelationBuildPartitionDesc
  *		Form rel's partition descriptor
@@ -1581,9 +1689,1972 @@ get_partition_qual_relid(Oid relid)
 	return result;
 }
 
+/*
+ * populate_partition_clauses
+ *		Processes 'clauses' to try to match them to relation's partition
+ *		keys.  If any compatible clauses are found which match a partition
+ *		key, then these clauses are stored in 'partclauseinfo'.
+ *
+ * The caller must ensure that 'clauses' is not an empty List.  Upon return,
+ * callers must also check if the 'partclauseinfo' constfalse has been set, if
+ * so, then they must be aware that the 'partclauseinfo' may only be partially
+ * populated.
+ */
+void
+populate_partition_clauses(Relation relation,
+						   int rt_index, List *clauses,
+						   PartitionClauseInfo *partclauseinfo)
+{
+	PartitionDesc		partdesc;
+	PartitionKey		partkey;
+	PartitionBoundInfo	boundinfo;
+
+	Assert(clauses != NIL);
+
+	partkey = RelationGetPartitionKey(relation);
+	partdesc = RelationGetPartitionDesc(relation);
+
+	/* Some functions called below modify this list */
+	clauses = list_copy(clauses);
+	boundinfo = partdesc->boundinfo;
+
+	/*
+	 * 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 (partition_bound_has_default(boundinfo))
+	{
+		List *partqual = RelationGetPartitionQual(relation);
+
+		partqual = (List *) expression_planner((Expr *) partqual);
+
+		/* Fix Vars to have the desired varno */
+		if (rt_index != 1)
+			ChangeVarNodes((Node *) partqual, 1, rt_index, 0);
+
+		clauses = list_concat(clauses, partqual);
+	}
+
+	extract_partition_key_clauses(partkey, clauses, rt_index, partclauseinfo);
+}
+
+/*
+ * get_partitions_from_clauses
+ *		Determine all partitions of context->relation that could possibly
+ *		contain a record that matches clauses as 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;
+	PartitionDesc		partdesc;
+	PartScanKeyInfo		keys;
+	Bitmapset 		   *result;
+	ListCell *lc;
+
+	Assert(partclauseinfo != NULL);
+
+	/*
+	 * Check if there were proofs that no partitions can match due to some
+	 * clause items contradicting another.
+	 */
+	if (partclauseinfo->constfalse)
+		return NULL;
+
+	partdesc = RelationGetPartitionDesc(context->relation);
+
+	if (!partclauseinfo->foundkeyclauses)
+	{
+		/* No interesting clauses were found to eliminate partitions. */
+		result = bms_add_range(NULL, 0, partdesc->nparts - 1);
+	}
+	else
+	{
+		PartitionKey	partkey = RelationGetPartitionKey(context->relation);
+
+		/* collapse clauses down to the most restrictive set */
+		remove_redundant_clauses(partkey, context);
+
+		/* Did remove_redundant_clauses find any contradicting clauses? */
+		if (partclauseinfo->constfalse)
+			return NULL;
+
+		if (extract_bounding_datums(partkey, context, &keys))
+		{
+			result = get_partitions_for_keys(context->relation, &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, partdesc->nparts - 1);
+		}
+	}
+
+	/* Select partitions by applying the clauses containing <> operators. */
+	if (partclauseinfo->ne_clauses)
+	{
+		Bitmapset *ne_parts;
+
+		ne_parts = get_partitions_excluded_by_ne_clauses(context,
+												partclauseinfo->ne_clauses);
+
+		/* Remove any partitions we found to not be needed */
+		result = bms_del_members(result, ne_parts);
+		bms_free(ne_parts);
+	}
+
+	/* Select partitions by applying OR clauses. */
+	foreach(lc, partclauseinfo->or_clauses)
+	{
+		List *or_args = (List *) lfirst(lc);
+		PartitionPruneContext orcontext;
+		Bitmapset *or_parts;
+
+		orcontext.rt_index = context->rt_index;
+		orcontext.relation = context->relation;
+		orcontext.clauseinfo = NULL;
+
+		or_parts = get_partitions_from_or_clause_args(&orcontext, 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 */
 
 /*
+ * get_partitions_excluded_by_ne_clauses
+ *
+ * Returns a Bitmapset of partition indexes of any partition that can safely
+ * be removed due to 'ne_clauses' containing not-equal clauses for all
+ * possible values that the partition can contain.
+ */
+static Bitmapset *
+get_partitions_excluded_by_ne_clauses(PartitionPruneContext *context,
+									  List *ne_clauses)
+{
+	ListCell	   *lc;
+	Bitmapset	   *excluded_parts;
+	Bitmapset	   *foundoffsets = NULL;
+	Relation		relation = context->relation;
+	PartitionKey	partkey = RelationGetPartitionKey(relation);
+	PartitionDesc	partdesc = RelationGetPartitionDesc(relation);
+	PartitionBoundInfo boundinfo = partdesc->boundinfo;
+	PartitionBoundCmpArg arg;
+	int			   *datums_in_part;
+	int			   *datums_found;
+	int				i;
+
+	Assert(partkey->strategy == PARTITION_STRATEGY_LIST);
+	Assert(partkey->partnatts == 1);
+
+	memset(&arg, 0, sizeof(arg));
+
+	/*
+	 * Build a Bitmapset to record the indexes of all datums of the
+	 * query that are found in boundinfo.
+	 */
+	foreach(lc, ne_clauses)
+	{
+		PartClause *pc = (PartClause *) lfirst(lc);
+		Datum	datum;
+
+		if (partkey_datum_from_expr(partkey, 0, pc->value, &datum))
+		{
+			int		offset;
+			bool	is_equal;
+
+			arg.datums = &datum;
+			arg.ndatums = 1;
+			offset = partition_bound_bsearch(partkey, boundinfo, &arg,
+											 &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) * partdesc->nparts);
+	datums_found = (int *) palloc0(sizeof(int) * partdesc->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 < partdesc->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_partitions_from_or_clause_args
+ *
+ * Returns the set of partitions of relation, each of which satisfies some
+ * clause in or_clause_args.
+ */
+static Bitmapset *
+get_partitions_from_or_clause_args(PartitionPruneContext *context,
+								   List *or_clause_args)
+{
+	PartitionKey	partkey = RelationGetPartitionKey(context->relation);
+	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_clause_args)
+	{
+		List *clauses = list_make1(lfirst(lc));
+		PartitionClauseInfo partclauseinfo;
+		PartitionPruneContext subcontext;
+		Bitmapset *arg_partset;
+
+		extract_partition_key_clauses(partkey, clauses, context->rt_index,
+									  &partclauseinfo);
+
+		if (!partclauseinfo.foundkeyclauses)
+		{
+			List *partconstr = RelationGetPartitionQual(context->relation);
+			PartitionDesc partdesc;
+
+			if (partconstr)
+			{
+				partconstr = (List *) expression_planner((Expr *) partconstr);
+				if (context->rt_index != 1)
+					ChangeVarNodes((Node *) partconstr, 1, context->rt_index,
+								   0);
+				if (predicate_refuted_by(partconstr, clauses, false))
+					continue;
+			}
+
+			/* Couldn't eliminate any of the partitions. */
+			partdesc = RelationGetPartitionDesc(context->relation);
+			return bms_add_range(NULL, 0, partdesc->nparts - 1);
+		}
+
+		subcontext.rt_index = context->rt_index;
+		subcontext.relation = context->relation;
+		subcontext.clauseinfo = &partclauseinfo;
+		arg_partset = get_partitions_from_clauses(&subcontext);
+
+		result = bms_add_members(result, arg_partset);
+		bms_free(arg_partset);
+	}
+
+	return result;
+}
+
+/* Match partition key (partattno/partexpr) to an expression (expr). */
+#define EXPR_MATCHES_PARTKEY(expr, partattno, partexpr) \
+		((partattno) != 0 ? \
+		 (IsA((expr), Var) && \
+		 ((Var *) (expr))->varattno == (partattno)) : \
+		 equal((expr), (partexpr)))
+
+#define COLLATION_MATCH(partcoll, exprcoll) \
+	(!OidIsValid(partcoll) || (partcoll) == (exprcoll))
+
+/*
+ * extract_partition_key_clauses
+ *		Processes 'clauses' to extract clause matching the partition key.
+ *		This adds matched clauses to the list corresponding to particular key
+ *		in 'partclauseinfo'.  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
+ * partclauseinfo's 'constfalse' to true and exits immediately without
+ * processing any further clauses.  In this case, the caller must be careful
+ * not to assume the PartitionClauseInfo is fully populated with all clauses.
+ */
+static void
+extract_partition_key_clauses(PartitionKey partkey, List *clauses,
+							  int rt_index,
+							  PartitionClauseInfo *partclauseinfo)
+{
+	int			i;
+	ListCell   *lc;
+
+	memset(partclauseinfo, 0, sizeof(PartitionClauseInfo));
+
+	foreach(lc, clauses)
+	{
+		Expr	   *clause = (Expr *) lfirst(lc);
+		ListCell   *partexprs_item;
+
+		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))
+			{
+				clauses = list_concat(clauses,
+									  list_copy(((BoolExpr *) clause)->args));
+				continue;
+			}
+			/* Fall-through for a NOT clause, which is handled below. */
+		}
+
+		partexprs_item = list_head(partkey->partexprs);
+		for (i = 0; i < partkey->partnatts; i++)
+		{
+			PartClause *pc;
+			Oid			partopfamily = partkey->partopfamily[i];
+			Oid			partcoll = partkey->partcollation[i];
+			Oid			commutator = InvalidOid;
+			AttrNumber	partattno = partkey->partattrs[i];
+			Expr	   *partexpr = NULL;
+
+			/*
+			 * A zero attno means the partition key is an expression, so grab
+			 * the next expression from the list.
+			 */
+			if (partattno == 0)
+			{
+				if (partexprs_item == NULL)
+					elog(ERROR, "wrong number of partition key expressions");
+
+				partexpr = (Expr *) lfirst(partexprs_item);
+
+				/*
+				 * Expressions stored for the PartitionKey in the relcache are
+				 * all stored with the dummy varno of 1. Change that to what
+				 * we need.
+				 */
+				if (rt_index != 1)
+				{
+					/* make a copy so as not to overwrite the relcache */
+					partexpr = (Expr *) copyObject(partexpr);
+					ChangeVarNodes((Node *) partexpr, 1, rt_index, 0);
+				}
+
+				partexprs_item = lnext(partexprs_item);
+			}
+
+			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 (EXPR_MATCHES_PARTKEY(leftop, partattno, partexpr))
+					valueexpr = rightop;
+				else if (EXPR_MATCHES_PARTKEY(rightop, partattno, partexpr))
+				{
+					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 (!COLLATION_MATCH(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 &&
+							partkey->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 (!EXPR_MATCHES_PARTKEY(leftop, partattno, partexpr))
+					continue;
+
+				/*
+				 * Also, useless, if the clause's collation is different from
+				 * the partitioning collation.
+				 */
+				if (!COLLATION_MATCH(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 (EXPR_MATCHES_PARTKEY(arg, partattno, partexpr))
+				{
+					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 (!EXPR_MATCHES_PARTKEY(leftop, partattno, partexpr))
+						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 (!EXPR_MATCHES_PARTKEY(leftop, partattno, partexpr))
+						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;
+			}
+		}
+	}
+}
+
+/*
+ * extract_bounding_datums
+ *		Process clauses in context->clauseinfo and populate 'keys' with all
+ *		min/max/equal values that we're able to determine.
+ *
+ * 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(PartitionKey partkey, PartitionPruneContext *context,
+						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 < partkey->partnatts; i++)
+	{
+		List *clauselist = clauseinfo->keyclauses[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 &&
+			partkey->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(partkey, clause, &incl);
+			switch (op_strategy)
+			{
+				case PART_OP_EQUAL:
+					Assert(incl);
+					if (need_next_eq &&
+						partkey_datum_from_expr(partkey, i, value,
+												&keys->eqkeys[i]))
+						keys->n_eqkeys++;
+
+					if (need_next_max &&
+						partkey_datum_from_expr(partkey, i, value,
+												&keys->maxkeys[i]))
+					{
+						keys->n_maxkeys++;
+						keys->max_incl = true;
+					}
+
+					if (need_next_min &&
+						partkey_datum_from_expr(partkey, 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(partkey, 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(partkey, 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 == partkey->partnatts ||
+		partkey->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;
+
+	return (keys->n_eqkeys > 0 || keys->n_minkeys > 0 ||
+			keys->n_maxkeys > 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(PartitionKey key, PartClause *pc, bool *incl)
+{
+	*incl = false;	/* may be overwritten below */
+
+	switch (key->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) key->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(PartitionKey key, int partkeyidx,
+						Expr *expr, Datum *value)
+{
+	Oid		exprtype = exprType((Node *) expr);
+
+	if (exprtype != key->parttypid[partkeyidx])
+	{
+		ParseState *pstate = make_parsestate(NULL);
+
+		expr = (Expr *) coerce_to_target_type(pstate, (Node *) expr,
+									 exprtype,
+									 key->parttypid[partkeyidx], -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 higher-level
+	 * code.
+	 */
+	if (IsA(expr, Const))
+	{
+		*value = ((Const *) expr)->constvalue;
+		return true;
+	}
+
+	return false;
+}
+
+/*
+ * remove_redundant_clauses
+ *		Processes the clauses contained in context->clauseinfo to remove the
+ *		ones that are superseeded by other clauses which are more restrictive.
+ *
+ * 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(PartitionKey partkey,
+						 PartitionPruneContext *context)
+{
+	PartClause *hash_clause,
+			   *btree_clauses[BTMaxStrategyNumber];
+	PartitionClauseInfo *partclauseinfo = context->clauseinfo;
+	ListCell *lc;
+	int		s;
+	int		i;
+	bool	test_result;
+	List   *newlist;
+
+	for (i = 0; i < partkey->partnatts; i++)
+	{
+		List *keyclauses = partclauseinfo->keyclauses[i];
+
+		hash_clause = NULL;
+		newlist = NIL;
+
+		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,
+										   partkey->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 (partkey->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(partkey, 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
+					newlist = lappend(newlist, 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(partkey, 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.
+					 */
+					newlist = lappend(newlist, pc);
+				}
+			}
+		}
+
+		if (partkey->strategy == PARTITION_STRATEGY_HASH)
+		{
+			/* Note we didn't add this one to the result yet. */
+			if (hash_clause)
+				newlist = lappend(newlist, hash_clause);
+			list_free(partclauseinfo->keyclauses[i]);
+			partclauseinfo->keyclauses[i] = newlist;
+			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(partkey, 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(partkey, 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(partkey, 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])
+				newlist = lappend(newlist, btree_clauses[s]);
+		}
+
+		/*
+		 * Replace the old List with the new one with the redundant clauses
+		 * removed.
+		 */
+		list_free(partclauseinfo->keyclauses[i]);
+		partclauseinfo->keyclauses[i] = newlist;
+	}
+}
+
+/*
+ * 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(PartitionKey key, int partkeyidx,
+				   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(key, partkeyidx,
+								 leftarg->value, &left_value))
+		return false;
+
+	if (!partkey_datum_from_expr(key, partkeyidx,
+								 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		partopfamily = key->partopfamily[partkeyidx];
+		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;
+}
+
+/*
+ * get_partitions_for_keys
+ *		Returns the partitions of 'rel' 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 selceted partitions
+ */
+static Bitmapset *
+get_partitions_for_keys(Relation rel, PartScanKeyInfo *keys)
+{
+	/* Return an empty set if no partitions to see. */
+	if (RelationGetPartitionDesc(rel)->nparts == 0)
+		return NULL;
+
+	switch (RelationGetPartitionKey(rel)->strategy)
+	{
+		case PARTITION_STRATEGY_HASH:
+			return get_partitions_for_keys_hash(rel, keys);
+
+		case PARTITION_STRATEGY_LIST:
+			return get_partitions_for_keys_list(rel, keys);
+
+		case PARTITION_STRATEGY_RANGE:
+			return get_partitions_for_keys_range(rel, keys);
+
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+						RelationGetPartitionKey(rel)->strategy);
+	}
+
+	return NULL;	/* keep compiler quiet */
+}
+
+/*
+ * 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(Relation rel, PartScanKeyInfo *keys)
+{
+	PartitionKey	partkey = RelationGetPartitionKey(rel);
+	PartitionDesc	partdesc = RelationGetPartitionDesc(rel);
+	PartitionBoundInfo boundinfo = partdesc->boundinfo;
+	bool			keyisnull[PARTITION_MAX_KEYS];
+	int				i;
+
+	Assert(partdesc->nparts > 0);
+
+	/*
+	 * 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 < partkey->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 == partkey->partnatts)
+	{
+		uint64	rowHash;
+		int 	greatest_modulus = get_greatest_modulus(boundinfo),
+				result_index;
+
+		rowHash = compute_hash_value(partkey, 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, partdesc->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(Relation rel, PartScanKeyInfo *keys)
+{
+	Bitmapset			   *result = NULL;
+	PartitionKey			partkey = RelationGetPartitionKey(rel);
+	PartitionBoundInfo		boundinfo = RelationGetPartitionDesc(rel)->boundinfo;
+	PartitionBoundCmpArg	arg;
+	int		i,
+			eqoff,
+			minoff,
+			maxoff;
+	bool	is_equal;
+
+	Assert(RelationGetPartitionDesc(rel)->nparts > 0);
+	Assert(partkey->partnatts == 1);
+
+	/*
+	 * 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)
+	{
+		memset(&arg, 0, sizeof(PartitionBoundCmpArg));
+		arg.datums = keys->eqkeys;
+		arg.ndatums = keys->n_eqkeys;
+		eqoff = partition_bound_bsearch(partkey, boundinfo, &arg, &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)
+	{
+		memset(&arg, 0, sizeof(PartitionBoundCmpArg));
+		arg.datums = keys->minkeys;
+		arg.ndatums = keys->n_minkeys;
+		minoff = partition_bound_bsearch(partkey, boundinfo, &arg, &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)
+	{
+		memset(&arg, 0, sizeof(PartitionBoundCmpArg));
+		arg.datums = keys->maxkeys;
+		arg.ndatums = keys->n_maxkeys;
+		maxoff = partition_bound_bsearch(partkey, boundinfo, &arg, &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(Relation rel, PartScanKeyInfo *keys)
+{
+	Bitmapset			   *result = NULL;
+	PartitionKey			partkey = RelationGetPartitionKey(rel);
+	PartitionBoundInfo 		boundinfo = RelationGetPartitionDesc(rel)->boundinfo;
+	PartitionBoundCmpArg	arg;
+	int		i,
+			eqoff,
+			minoff,
+			maxoff;
+	bool	is_equal,
+			include_def = false;
+
+	Assert(RelationGetPartitionDesc(rel)->nparts > 0);
+
+	/*
+	 * 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 < partkey->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 == partkey->partnatts);
+		memset(&arg, 0, sizeof(PartitionBoundCmpArg));
+		arg.datums = keys->eqkeys;
+		arg.ndatums = keys->n_eqkeys;
+		eqoff = partition_bound_bsearch(partkey, boundinfo, &arg, &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)
+	{
+		memset(&arg, 0, sizeof(PartitionBoundCmpArg));
+		arg.datums = keys->minkeys;
+		arg.ndatums = keys->n_minkeys;
+		minoff = partition_bound_bsearch(partkey, boundinfo, &arg, &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 && arg.ndatums < partkey->partnatts)
+		{
+			while (minoff >= 1 && minoff < boundinfo->ndatums - 1)
+			{
+				int32	cmpval;
+
+				cmpval = partition_bound_cmp(partkey, boundinfo,
+											 keys->min_incl
+												? minoff - 1 : minoff + 1,
+											 &arg);
+				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)
+	{
+		memset(&arg, 0, sizeof(PartitionBoundCmpArg));
+		arg.datums = keys->maxkeys;
+		arg.ndatums = keys->n_maxkeys;
+		maxoff = partition_bound_bsearch(partkey, boundinfo, &arg, &is_equal);
+
+		/* See the comment written above for minkeys. */
+		if (is_equal && arg.ndatums < partkey->partnatts)
+		{
+			while (maxoff >= 1 && maxoff < boundinfo->ndatums - 1)
+			{
+				int32	cmpval;
+
+				cmpval = partition_bound_cmp(partkey, boundinfo,
+											 keys->max_incl
+												? maxoff + 1 : maxoff - 1,
+											 &arg);
+				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 = partkey->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 = partkey->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 < partkey->partnatts ||
+		keys->n_maxkeys < partkey->partnatts)
+	{
+		for (i = 0; i < partkey->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_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 fd3001c493..2fc54defbd 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2127,6 +2127,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
  *
@@ -5009,6 +5028,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/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/include/catalog/partition.h b/src/include/catalog/partition.h
index 2faf0ca26e..78d43ea07c 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -40,6 +40,13 @@ typedef struct PartitionDescData
 	PartitionBoundInfo boundinfo;	/* collection of partition bounds */
 } PartitionDescData;
 
+typedef struct PartitionPruneContext
+{
+	int			rt_index;
+	Relation	relation;
+	PartitionClauseInfo *clauseinfo;
+} PartitionPruneContext;
+
 typedef struct PartitionDescData *PartitionDesc;
 
 extern void RelationBuildPartitionDesc(Relation relation);
@@ -73,4 +80,10 @@ 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 void populate_partition_clauses(Relation relation,
+							   int rt_index, List *clauses,
+							   PartitionClauseInfo *partclauseinfo);
+extern Bitmapset *get_partitions_from_clauses(PartitionPruneContext *context);
+
 #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..642ea0fbde 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1506,4 +1506,35 @@ 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
+{
+	/* 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/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 */
-- 
2.11.0

