0003-Implement-get_partitions_from_clauses-v1.patch

text/plain

Filename: 0003-Implement-get_partitions_from_clauses-v1.patch
Type: text/plain
Part: 2
Message: Re: path toward faster partition pruning

Patch

Format: format-patch
Series: patch v1-0003
Subject: Implement get_partitions_from_clauses
File+
src/backend/catalog/partition.c 1031 3
From a484fbf69a1debe97bbc3ef724ad858275a44688 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 22 Aug 2017 13:48:13 +0900
Subject: [PATCH 3/5] Implement get_partitions_from_clauses

This now actually processes partclauses and classifies them into
a set of keys that can be used to look up partitions in the
partition descriptor, although there is still no support for the
latter.
---
 src/backend/catalog/partition.c | 1034 ++++++++++++++++++++++++++++++++++++++-
 1 file changed, 1031 insertions(+), 3 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index f8da91d0fe..abccb77393 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -37,6 +37,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 "rewrite/rewriteManip.h"
@@ -111,6 +113,100 @@ typedef struct PartitionRangeBound
 	bool		lower;			/* this is the lower (vs upper) bound */
 } PartitionRangeBound;
 
+/*
+ * Information about a clause matched with a partition key column kept to
+ * avoid repeated recomputation in remove_redundant_clauses().
+ */
+typedef struct
+{
+	OpExpr *op;
+	Expr   *constarg;
+
+	/* cached info. */
+	bool	valid_cache;	/* Is the following information initialized? */
+	int		op_strategy;
+	Oid		op_subtype;
+	FmgrInfo op_func;
+} PartClause;
+
+/*
+ * PartitionScanKeyInfo
+ *		Bounding scan keys to look up a table's partitions obtained from
+ *		mutually-ANDed clauses containing partitioning-compatible operators
+ */
+typedef struct PartitionScanKeyInfo
+{
+	/*
+	 * Constants constituting the *whole* partition key compared using
+	 * partitioning-compatible equality operator(s).  When n_eqkeys > 0, other
+	 * keys (minkeys and maxkeys) are irrelevant.
+	 */
+	Datum	eqkeys[PARTITION_MAX_KEYS];
+	int		n_eqkeys;
+
+	/*
+	 * Constants that constitute the lower bound on the partition key or a
+	 * prefix thereof.  The last of those constants is compared using > or >=
+	 * operator compatible with partitioning, making this the lower bound in
+	 * a range query.
+	 */
+	Datum	minkeys[PARTITION_MAX_KEYS];
+	int		n_minkeys;
+	bool	min_incl;
+
+	/*
+	 * Constants that constitute the upper bound on the partition key or a
+	 * prefix thereof.  The last of those constants is compared using < or <=
+	 * operator compatible with partitioning, making this the upper bound in
+	 * a range query.
+	 */
+	Datum	maxkeys[PARTITION_MAX_KEYS];
+	int		n_maxkeys;
+	bool	max_incl;
+
+	/*
+	 * Specifies the type of NullTest that was applied to each of the
+	 * partition key columns or -1 if none was applied.  Partitioning handles
+	 * null partition keys specially depending on the partitioning method in
+	 * use, so get_partitions_for_keys can return partitions according to
+	 * the nullness condition for partition keys.
+	 */
+	NullTestType	keynullness[PARTITION_MAX_KEYS];
+} PartitionScanKeyInfo;
+
+ /* A data structure to represent a partition set. */
+typedef struct PartitionSet
+{
+	/*
+	 * If either  empty or all_parts is true, values of the other fields are
+	 * invalid.
+	 */
+	bool	empty;				/* contains no partitions */
+	bool	all_parts;			/* contains all partitions */
+
+	/*
+	 * In the case of range partitioning, min_part_index contains the index of
+	 * the lowest partition contained in the set and max_datum_index that of
+	 * the highest partition (all partitions between these two indexes
+	 * inclusive are part of the set.)  Since other types of partitioning do
+	 * not impose order on the data contained in successive partitions, these
+	 * fields are not set in that case.
+	 */
+	bool	use_range;
+	int		min_part_idx;
+	int		max_part_idx;
+
+	/*
+	 * other_parts contains the indexes of partitions that are not covered by
+	 * the range defined by min/max indexes.  For example, in the case of
+	 * range partitoning, it will include default partition index (if any).
+	 * Also, this is the only way to return list partitions, because list
+	 * partitions do not have the same ordering property as range partitions,
+	 * so it's pointless to use the min/max range method.
+	 */
+	Bitmapset *other_parts;
+} PartitionSet;
+
 static int32 qsort_partition_list_value_cmp(const void *a, const void *b,
 							   void *arg);
 static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
@@ -150,6 +246,25 @@ static int partition_bound_bsearch(PartitionKey key,
 static void get_partition_dispatch_recurse(Relation rel, Relation parent,
 							   List **pds, List **leaf_part_oids);
 
+static PartitionSet *get_partitions_from_clauses_guts(Relation relation,
+								int rt_index, List *clauses);
+static int classify_partition_bounding_keys(Relation relation, List *clauses,
+								 PartitionScanKeyInfo *keys, bool *constfalse,
+								 List **or_clauses);
+static void remove_redundant_clauses(PartitionKey partkey,
+						 int partattoff, List *all_clauses,
+						 List **result, bool *constfalse);
+static bool partition_cmp_args(Oid partopfamily, Oid partopcintype,
+				   PartClause *op, PartClause *leftarg, PartClause *rightarg,
+				   bool *result);
+static Datum partkey_datum_from_expr(const Expr *expr);
+static PartitionSet *partset_copy(const PartitionSet *in);
+static PartitionSet *partset_intersect(PartitionSet *a, const PartitionSet *b);
+static PartitionSet *partset_union(PartitionSet *a, const PartitionSet *b);
+static PartitionSet *partset_new(bool empty, bool all_parts);
+static PartitionSet *get_partitions_for_keys(Relation rel,
+						PartitionScanKeyInfo *keys);
+
 /*
  * RelationBuildPartitionDesc
  *		Form rel's partition descriptor
@@ -1439,15 +1554,928 @@ get_partitions_from_clauses(Relation relation, int rt_index,
 							Bitmapset **other_parts)
 {
 	PartitionDesc	partdesc = RelationGetPartitionDesc(relation);
+	PartitionSet   *partset;
+
+	partset = get_partitions_from_clauses_guts(relation, rt_index,
+											   partclauses);
+	if (partset->empty)
+	{
+		*min_part_idx = *max_part_idx = -1;
+		*other_parts = NULL;
+	}
+	else if (partset->all_parts)
+	{
+		*min_part_idx = 0;
+		*max_part_idx = partdesc->nparts - 1;
+		*other_parts = NULL;
+	}
+	else
+	{
+		if (partset->use_range)
+		{
+			*min_part_idx = partset->min_part_idx;
+			*max_part_idx = partset->max_part_idx;
+		}
+		else
+			*min_part_idx = *max_part_idx = -1;
 
-	*min_part_idx = 0;
-	*max_part_idx = partdesc->nparts - 1;
-	*other_parts = NULL;
+		*other_parts = partset->other_parts;
+	}
 }
 
 /* Module-local functions */
 
 /*
+ * get_partitions_using_clauses_guts
+ *		Determine relation's partitions that satisfy *all* of the clauses
+ *		in the list (return value describes the set of such partitions)
+ *
+ * rt_index is the table's range table position needed to set varno of Vars
+ * contained in the table's partition constraint that is used in certain
+ * cases.
+ */
+static PartitionSet *
+get_partitions_from_clauses_guts(Relation relation, int rt_index,
+								 List *clauses)
+{
+	PartitionSet *partset;
+	PartitionScanKeyInfo keys;
+	int		nkeys;
+	bool	constfalse;
+	List *or_clauses;
+	ListCell *lc;
+
+	nkeys = classify_partition_bounding_keys(relation, clauses,
+											 &keys, &constfalse,
+											 &or_clauses);
+	if (constfalse)
+		/* None of the partitions will satisfy the clauses. */
+		partset = partset_new(true, false);
+	else if (nkeys > 0)
+		/*
+		 * Only look up in the partition decriptor if the query provides
+		 * constraints on the keys at all.
+		 */
+		partset = get_partitions_for_keys(relation, &keys);
+	else
+		/* No constraints on the keys, so, return *all* partitions. */
+		partset = partset_new(false, true);
+
+	foreach(lc, or_clauses)
+	{
+		BoolExpr *or = (BoolExpr *) lfirst(lc);
+		ListCell *lc1;
+		PartitionSet *or_partset = partset_new(true, false);
+
+		foreach(lc1, or->args)
+		{
+			Expr *orarg = lfirst(lc1);
+			PartitionSet  *arg_partset = partset_new(true, false);
+			List   *partconstr = RelationGetPartitionQual(relation);
+
+			/*
+			 * If this orarg refutes the table's partition constraint (if the
+			 * the table is a partition at all), don't go looking for its
+			 * partitions, that is, leave the partition set we're building
+			 * for this OR clause untouched.
+			 */
+			if (partconstr)
+			{
+				partconstr = (List *) expression_planner((Expr *) partconstr);
+				partconstr = (List *) canonicalize_qual((Expr *) partconstr);
+				Assert(rt_index > 0);
+				if (rt_index != 1)
+					ChangeVarNodes((Node *) partconstr, 1, rt_index, 0);
+
+				/*
+				 * NB: if the clause may contain Param, replace them with
+				 * equivalent Vars before proceeding, because predtest.c does
+				 * not know about Params.
+				 */
+				if (predicate_refuted_by(partconstr,
+										 list_make1(orarg), false))
+					continue;
+			}
+
+			arg_partset = get_partitions_from_clauses_guts(relation, 0,
+														   list_make1(orarg));
+
+			/* Combine partition sets obtained from mutually ORed clauses. */
+			or_partset = partset_union(or_partset, arg_partset);
+		}
+
+		/* Combine partition sets obtained from mutually ANDed clauses. */
+		partset = partset_intersect(partset, or_partset);
+	}
+
+	return partset;
+}
+
+/*
+ * partkey_datum_from_expr
+ *		Extract constant value from expr and set *datum to that value
+ */
+
+static Datum
+partkey_datum_from_expr(const Expr *expr)
+{
+	/*
+	 * Add more expression types here as needed to support higher-level
+	 * code.
+	 */
+	switch (nodeTag(expr))
+	{
+		case T_Const:
+			return ((Const *) expr)->constvalue;
+
+		default:
+			elog(ERROR, "invalid expression for partition key");
+	}
+
+	Assert(false);	/* should never get here! */
+	return 0;
+}
+
+/* Partition set manipulation functions. */
+
+static PartitionSet *
+partset_new(bool empty, bool all_parts)
+{
+	PartitionSet   *result = palloc0(sizeof(PartitionSet));
+
+	result->empty = empty;
+	result->all_parts = all_parts;
+	/*
+	 * Remains true until we explicitly turn it off in partset_union in a
+	 * certain case.
+	 */
+	result->use_range = true;
+	result->min_part_idx = result->max_part_idx = -1;
+	result->other_parts = NULL;
+
+	return result;
+}
+
+static PartitionSet *
+partset_copy(const PartitionSet *in)
+{
+	PartitionSet *result;
+
+	if (in == NULL)
+		return NULL;
+
+	result = partset_new(in->empty, in->all_parts);
+	result->min_part_idx = in->min_part_idx;
+	result->max_part_idx = in->max_part_idx;
+	result->other_parts = in->other_parts;	/* not bms_copy. */
+
+	return result;
+}
+
+/*
+ * Macros to manipulate the range of partitions specified in a given
+ * PartitionSet (s) using its min_part_idx and max_part_idx fields, which are
+ * both inclusive ends of the range.
+ */
+
+#define partset_range_empty(s)\
+		((s)->min_part_idx < 0 && (s)->max_part_idx < 0)
+
+#define partset_range_overlap(s1, s2)\
+		(!partset_range_empty((s1)) && !partset_range_empty((s2)) &&\
+		 (((s1)->min_part_idx >= (s2)->min_part_idx &&\
+		   (s1)->min_part_idx <= (s2)->max_part_idx) ||\
+		  ((s2)->min_part_idx >= (s1)->min_part_idx &&\
+		   (s2)->min_part_idx <= (s1)->max_part_idx)))
+
+#define partset_range_adjacent(s1, s2)\
+		(!partset_range_empty((s1)) && !partset_range_empty((s2)) &&\
+		 (((s1)->max_part_idx == (s2)->min_part_idx) || \
+		  ((s2)->max_part_idx == (s1)->min_part_idx)))
+
+/* The result after intersection is stuffed back into 'a'. */
+static PartitionSet *
+partset_intersect(PartitionSet *a, const PartitionSet *b)
+{
+	Assert(a != NULL && b != NULL);
+
+	if (a->all_parts || b->empty)
+		a = partset_copy(b);
+	else
+	{
+		/*
+		 * Partition set is specified by min_part_idx, max_part_idx and/or
+		 * other_parts, so make the result set using those fields.
+		 */
+
+		/*
+		 * If one or both sets' range is empty, or if they don't overlap,
+		 * then the result's range is empty.
+		 */
+		if (partset_range_empty(a) ||
+			partset_range_empty(a) ||
+			!partset_range_overlap(a, b))
+		{
+			a->min_part_idx = a->max_part_idx = -1;
+		}
+		else
+		{
+			a->min_part_idx = Max(a->min_part_idx, b->min_part_idx);
+			a->max_part_idx = Min(a->max_part_idx, b->max_part_idx);
+		}
+
+		a->other_parts = bms_intersect(a->other_parts, b->other_parts);
+	}
+
+	return a;
+}
+
+/* The result after union is stuffed back into 'a'. */
+static PartitionSet *
+partset_union(PartitionSet *a, const PartitionSet *b)
+{
+	Assert(a != NULL && b != NULL);
+
+	if (a->empty || b->all_parts)
+		a = partset_copy(b);
+	else
+	{
+		/*
+		 * Partition set is specified by min_part_idx, max_part_idx and/or
+		 * other_parts, so make the result set using those fields.
+		 */
+		int		i;
+
+		/*
+		 * Combine b's range into a's only if we're still using the range
+		 * representation.
+		 */
+		if (a->use_range)
+		{
+			if(!partset_range_empty(a) && !partset_range_empty(b))
+			{
+				/*
+				 * Unify into one range using range union only if it makes
+				 * sense, that is only if they are adjacent to or overlap with
+				 * each other.  If not, unify them by adding indexes within
+				 * both ranges to the other_parts bitmap and mark the set as
+				 * no longer using the range representation, because, the
+				 * indexes in this no longer have the property of being
+				 * contiguous.
+				 */
+				if (partset_range_overlap(a, b) ||
+					partset_range_adjacent(a, b))
+				{
+					a->min_part_idx = Min(a->min_part_idx, b->min_part_idx);
+					a->max_part_idx = Max(a->max_part_idx, b->max_part_idx);
+				}
+				else
+				{
+					for (i = a->min_part_idx; i <= a->max_part_idx; i++)
+						a->other_parts = bms_add_member(a->other_parts, i);
+					for (i = b->min_part_idx; i <= b->max_part_idx; i++)
+						a->other_parts = bms_add_member(a->other_parts, i);
+
+					/* The set is no longer to be represented as range. */
+					a->use_range = false;
+					a->min_part_idx = a->max_part_idx = -1;
+				}
+			}
+			else if (partset_range_empty(a))
+			{
+				a->min_part_idx = b->min_part_idx;
+				a->max_part_idx = b->max_part_idx;
+			}
+		}
+		else
+		{
+			if (!partset_range_empty(b))
+			{
+				for (i = b->min_part_idx; i <= b->max_part_idx; i++)
+					a->other_parts = bms_add_member(a->other_parts, i);
+			}
+		}
+
+		a->other_parts = bms_union(a->other_parts, b->other_parts);
+	}
+
+	return a;
+}
+
+/*
+ * classify_partition_bounding_keys
+ *		Classify partition clauses into equal, min, max keys, along with any
+ *		Nullness constraints and return that informatin in the output argument
+ *		*keys (number of keys is the return value)
+ *
+ * Clauses in the provided list are implicitly ANDed, each of which is known
+ * to match some partition key column.  Map them to individual key columns
+ * and for each column, determine the equal bound or "best" min and max bound.
+ * For example, of a > 1, a > 2, and a >= 5, "5" is the best min bound for
+ * for the column a, which also happens to be an inclusive bound.
+ *
+ * For multi-column keys, an equal bound is returned only if all the columns
+ * are constrained by equality clauses.  Min and maximum bounds could contain
+ * bound values for only a prefix of key columns.
+ *
+ * If the list contains a pseudo-constant clause, *constfalse is set to true
+ * and no keys are set.  It is also set if we encounter mutually contradictory
+ * clauses in this function ourselves, for example, having both a > 1 and
+ * a = 0 the list.
+ *
+ * All the OR clauses encountered in the list are added to *or_clauses.  It's
+ * the responsibility of the caller to process the argument clauses of each of
+ * the OR clauses, which would involve recursively calling this function.
+ */
+static int
+classify_partition_bounding_keys(Relation relation, List *clauses,
+								 PartitionScanKeyInfo *keys, bool *constfalse,
+								 List **or_clauses)
+{
+	PartitionKey partkey = RelationGetPartitionKey(relation);
+	int		i;
+	ListCell *lc;
+	List   *keyclauses_all[PARTITION_MAX_KEYS],
+		   *keyclauses[PARTITION_MAX_KEYS];
+	bool	only_bool_clauses = true;
+	Expr   *eqkey_exprs[PARTITION_MAX_KEYS],
+		   *minkey_exprs[PARTITION_MAX_KEYS],
+		   *maxkey_exprs[PARTITION_MAX_KEYS];
+	NullTestType keynullness[PARTITION_MAX_KEYS];
+	bool	need_next_eq,
+			need_next_min,
+			need_next_max,
+			eqkey_set[PARTITION_MAX_KEYS],
+			minkey_set[PARTITION_MAX_KEYS],
+			maxkey_set[PARTITION_MAX_KEYS],
+			min_incl,
+			max_incl;
+	int		n_eqkeys = 0,
+			n_minkeys = 0,
+			n_maxkeys = 0,
+			n_keynullness = 0;
+
+	*or_clauses = NIL;
+	*constfalse = false;
+	memset(keyclauses_all, 0, PARTITION_MAX_KEYS * sizeof(List *));
+	memset(keynullness, 0, PARTITION_MAX_KEYS * sizeof(NullTestType *));
+
+	foreach(lc, clauses)
+	{
+		Expr	   *clause;
+		ListCell   *partexprs_item;
+
+		if (IsA(lfirst(lc), RestrictInfo))
+		{
+			RestrictInfo *rinfo = lfirst(lc);
+
+			clause = rinfo->clause;
+			if (rinfo->pseudoconstant &&
+				!DatumGetBool(((Const *) clause)->constvalue))
+			{
+				*constfalse = true;
+				continue;
+			}
+		}
+		else
+			clause = (Expr *) lfirst(lc);
+
+		/* Get the BoolExpr's out of the way.*/
+		if (IsA(clause, BoolExpr))
+		{
+			if (or_clause((Node *) clause))
+				*or_clauses = lappend(*or_clauses, clause);
+			else
+				clauses = list_concat(clauses,
+									  list_copy(((BoolExpr *) clause)->args));
+			continue;
+		}
+
+		partexprs_item = list_head(partkey->partexprs);
+		for (i = 0; i < partkey->partnatts; i++)
+		{
+			Oid		partopfamily = partkey->partopfamily[i];
+			AttrNumber	partattno = partkey->partattrs[i];
+			Expr *partexpr = NULL;
+			PartClause *pc = palloc0(sizeof(PartClause));
+
+			if (partattno == 0)
+			{
+				partexpr = lfirst(partexprs_item);
+				partexprs_item = lnext(partexprs_item);
+			}
+
+			keynullness[i] = -1;
+
+			if (IsA(clause, OpExpr))
+			{
+				OpExpr	   *opclause;
+				Expr	   *leftop,
+						   *rightop;
+
+				opclause = (OpExpr *) clause;
+				leftop = linitial(opclause->args);
+				if (IsA(leftop, RelabelType))
+					leftop = ((RelabelType *) leftop)->arg;
+				rightop = lsecond(opclause->args);
+				/* Does leftop match with this partition key column? */
+				if ((IsA(leftop, Var) && partattno != 0 &&
+					((Var *) leftop)->varattno == partattno) ||
+					equal(leftop, partexpr))
+				{
+					pc->op = opclause;
+					pc->constarg = rightop;
+					keyclauses_all[i] = lappend(keyclauses_all[i], pc);
+
+					/* A strict operator implies NOT NULL argument. */
+					keynullness[i] = IS_NOT_NULL;
+					n_keynullness++;
+					only_bool_clauses = false;
+				}
+			}
+			else if (IsA(clause, ScalarArrayOpExpr))
+			{
+				ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
+				Oid		saop_op = saop->opno;
+				Oid		saop_opfuncid = saop->opfuncid;
+				Oid		saop_coll = saop->inputcollid;
+				Node   *leftop = (Node *) linitial(saop->args);
+				Const  *arrconst = (Const *) lsecond(saop->args);
+				ArrayType *arrval = DatumGetArrayTypeP(arrconst->constvalue);
+				int16	elemlen;
+				bool	elembyval;
+				char	elemalign;
+				Datum  *elem_values;
+				bool   *elem_nulls;
+				int		num_elems;
+				List   *elem_exprs;
+				bool	negated = false;
+
+				/*
+				 * We would've accepted this saop only if its operator's
+				 * negator was found to be a valid partopfamily member.
+				 */
+				if (!op_in_opfamily(saop_op, partopfamily))
+					negated = true;
+
+				/* Build clauses for the individual values in the array. */
+				get_typlenbyvalalign(ARR_ELEMTYPE(arrval),
+									 &elemlen, &elembyval, &elemalign);
+				deconstruct_array(arrval,
+								  ARR_ELEMTYPE(arrval),
+								  elemlen, elembyval, elemalign,
+								  &elem_values, &elem_nulls,
+								  &num_elems);
+				elem_exprs = NIL;
+				for (i = 0; i < num_elems; i++)
+				{
+					Expr *elem_expr;
+
+					if (!elem_nulls[i])
+					{
+						Const  *rightop;
+						OpExpr *opexpr = makeNode(OpExpr);
+
+						rightop = makeConst(ARR_ELEMTYPE(arrval),
+											-1, arrconst->constcollid,
+											elemlen, elem_values[i],
+											false, elembyval);
+
+						opexpr->opno = saop_op;
+						opexpr->opfuncid = saop_opfuncid;
+						opexpr->opresulttype = BOOLOID;
+						opexpr->opretset = false;
+						opexpr->opcollid = InvalidOid;
+						opexpr->inputcollid = saop_coll;
+						opexpr->args = list_make2(leftop, rightop);
+						opexpr->location = -1;
+						elem_expr = (Expr *) opexpr;
+					}
+					else
+					{
+						NullTest *nulltest = makeNode(NullTest);
+
+						nulltest->arg = (Expr *) leftop;
+						nulltest->nulltesttype = !negated ? IS_NULL
+														  : IS_NOT_NULL;
+						nulltest->argisrow = false;
+						nulltest->location = -1;
+						elem_expr = (Expr *) nulltest;
+					}
+
+					elem_exprs = lappend(elem_exprs, elem_expr);
+				}
+
+				/* Build the OR clause and generate its PartClauseSetOr. */
+				if (saop->useOr)
+				{
+					BoolExpr *orexpr;
+
+					Assert(elem_exprs != NIL);
+					orexpr = (BoolExpr *) makeBoolExpr(OR_EXPR, elem_exprs,
+													   -1);
+					*or_clauses = lappend(*or_clauses, orexpr);
+				}
+				else
+					/*
+					 * To be ANDed with the clauses in the original list, just
+					 * like what we do for the arguments of Boolean AND clause
+					 * above.
+					 */
+					clauses = list_concat(clauses, elem_exprs);
+			}
+			else if (IsA(clause, NullTest))
+			{
+				NullTest *nulltest = (NullTest *) clause;
+				Expr *arg = nulltest->arg;
+
+				/* Does leftop match with this partition key column? */
+				if ((IsA(arg, Var) && partattno != 0 &&
+					((Var *) arg)->varattno == partattno) ||
+					!equal(arg, partexpr))
+				{
+					keynullness[i] = nulltest->nulltesttype;
+					n_keynullness++;
+					only_bool_clauses = false;
+				}
+			}
+		}
+	}
+
+	/* Return if no work to do below. */
+	if (only_bool_clauses || *constfalse)
+		return 0;
+
+	/*
+	 * Redundant key elimination using btree-semantics based tricks.
+	 *
+	 * Only list and range partitioning use btree operator semantics, so
+	 * skip otherwise.   Also, if there are expressions whose value is yet
+	 * unknown, skip this step, because we need to compare actual values
+	 * below.
+	 */
+	memset(keyclauses, 0, PARTITION_MAX_KEYS * sizeof(List *));
+	if (partkey->strategy == PARTITION_STRATEGY_LIST ||
+		partkey->strategy == PARTITION_STRATEGY_RANGE)
+	{
+		for (i = 0; i < partkey->partnatts; i++)
+		{
+			remove_redundant_clauses(partkey, i,
+									 keyclauses_all[i],
+									 &keyclauses[i],
+									 constfalse);
+			if (*constfalse)
+				return 0;
+		}
+	}
+
+	/*
+	 * Now, generate the bounding tuples that can serve as equal, min, and
+	 * max keys.  An equal bounding key must contain all partition key
+	 * columns, whereas a prefix of all partition key columns is addmissible
+	 * as min and max keys.
+	 */
+	memset(eqkey_exprs, 0, sizeof(eqkey_exprs));
+	memset(minkey_exprs, 0, sizeof(minkey_exprs));
+	memset(maxkey_exprs, 0, sizeof(maxkey_exprs));
+	memset(eqkey_set, false, sizeof(eqkey_set));
+	memset(minkey_set, false, sizeof(minkey_set));
+	memset(maxkey_set, false, sizeof(maxkey_set));
+
+	need_next_eq = true;
+	need_next_min = true;
+	need_next_max = true;
+	for (i = 0; i < partkey->partnatts; i++)
+	{
+		/*
+		 * If no scan key existed for the previous column, we are done.
+		 */
+		if (i > n_eqkeys)
+			need_next_eq = false;
+
+		if (i > n_minkeys)
+			need_next_min = false;
+
+		if (i > n_maxkeys)
+			need_next_max = false;
+
+		foreach(lc, keyclauses[i])
+		{
+			PartClause *clause = lfirst(lc);
+			int		strategy = clause->op_strategy;
+
+			switch (strategy)
+			{
+				case BTLessStrategyNumber:
+				case BTLessEqualStrategyNumber:
+					if (need_next_max)
+					{
+						maxkey_exprs[i] = clause->constarg;
+						if (!maxkey_set[i])
+							n_maxkeys++;
+						maxkey_set[i] = true;
+						max_incl = (strategy == BTLessEqualStrategyNumber);
+
+						if (strategy == BTLessStrategyNumber)
+							need_next_eq = need_next_max = false;
+					}
+					break;
+
+				case BTGreaterStrategyNumber:
+				case BTGreaterEqualStrategyNumber:
+					if (need_next_min)
+					{
+						minkey_exprs[i] = clause->constarg;
+						if (!minkey_set[i])
+							n_minkeys++;
+						minkey_set[i] = true;
+						min_incl = (strategy == BTGreaterEqualStrategyNumber);
+
+						if (strategy == BTGreaterStrategyNumber)
+							need_next_eq = need_next_min = false;
+					}
+					break;
+
+				case BTEqualStrategyNumber:
+					if (need_next_eq)
+					{
+						eqkey_exprs[i] = clause->constarg;
+						if (!eqkey_set[i])
+							n_eqkeys++;
+						eqkey_set[i] = true;
+					}
+
+					if (need_next_min)
+					{
+						minkey_exprs[i] = clause->constarg;
+						if (!minkey_set[i])
+							n_minkeys++;
+						minkey_set[i] = true;
+						min_incl = true;
+					}
+
+					if (need_next_max)
+					{
+						maxkey_exprs[i] = clause->constarg;
+						if (!maxkey_set[i])
+							n_maxkeys++;
+						maxkey_set[i] = true;
+						max_incl = true;
+					}
+					break;
+
+				/*
+				 * Ideally, never get here, because 1. we don't support
+				 * operators that are not btree operators and 2. clauses
+				 * containing '<>' which are not listed in the btree operator
+				 * families have already been handled by the higher-level
+				 * code.
+				 */
+				default:
+					break;
+			}
+		}
+	}
+
+	/*
+	 * If we have equal keys for all the partition key columns, then mark
+	 * their copies in minkeys and maxkeys as invalid, so that we perform
+	 * partition lookup using only eqkeys.  Don't pass as the equal key
+	 * otherwise.
+	 */
+	if (n_eqkeys == partkey->partnatts)
+		n_minkeys = n_maxkeys = 0;
+	else
+		n_eqkeys = 0;
+
+	/* Populate PartClauseValSet */
+	for (i = 0; i < n_eqkeys; i++)
+		keys->eqkeys[i] = partkey_datum_from_expr(eqkey_exprs[i]);
+	keys->n_eqkeys = n_eqkeys;
+
+	for (i = 0; i < n_minkeys; i++)
+		keys->minkeys[i] = partkey_datum_from_expr(minkey_exprs[i]);
+	keys->n_minkeys = n_minkeys;
+	keys->min_incl = min_incl;
+
+	for (i = 0; i < n_maxkeys; i++)
+		keys->maxkeys[i] = partkey_datum_from_expr(maxkey_exprs[i]);
+	keys->n_maxkeys = n_maxkeys;
+	keys->max_incl = max_incl;
+
+	for (i = 0; i < partkey->partnatts; i++)
+		keys->keynullness[i] = keynullness[i];
+
+	return n_eqkeys + n_minkeys + n_maxkeys + n_keynullness;
+}
+
+static void
+remove_redundant_clauses(PartitionKey partkey,
+						 int partattoff, List *all_clauses,
+						 List **result, bool *constfalse)
+{
+	Oid			partopfamily = partkey->partopfamily[partattoff];
+	Oid			partopcintype = partkey->partopcintype[partattoff];
+	PartClause *xform[BTMaxStrategyNumber];
+	ListCell *lc;
+	int		s;
+	bool	test_result;
+
+	*result = NIL;
+
+	/*
+	 * xform[s] points to the currently best scan key of strategy type s+1; it
+	 * is NULL if we haven't yet found such a key for this attr.
+	 */
+	memset(xform, 0, sizeof(xform));
+	foreach(lc, all_clauses)
+	{
+		PartClause *cur = lfirst(lc);
+
+		if (!cur->valid_cache)
+		{
+			Oid		lefttype;
+			get_op_opfamily_properties(cur->op->opno, partopfamily, false,
+									   &cur->op_strategy,
+									   &lefttype,
+									   &cur->op_subtype);
+			fmgr_info(get_opcode(cur->op->opno), &cur->op_func);
+			cur->valid_cache = true;
+		}
+
+		s = cur->op_strategy - 1;
+		/* Have we seen a clause of this strategy before?. */
+		if (xform[s] == NULL)
+		{
+			/* nope, so assign. */
+			xform[s] = cur;
+		}
+		else
+		{
+			/* yup, keep only the more restrictive key. */
+			if (partition_cmp_args(partopfamily, partopcintype,
+								   cur, cur, xform[s],
+								   &test_result))
+			{
+				if (test_result)
+					xform[s] = cur;
+				else if (s == BTEqualStrategyNumber - 1)
+				{
+					*constfalse = true;
+					return;
+				}
+				/* else the old key is more restrictive, keep around. */
+			}
+			else
+			{
+				/*
+				 * we couldn't determine which one is more restrictive.  Keep
+				 * the previous one in xform[s] and push this one directly
+				 * to the output list.
+				 */
+				*result = lappend(*result, cur);
+			}
+		}
+	}
+
+	/* Finished processing all clauses.  Now compare across strategies. */
+	if (xform[BTEqualStrategyNumber - 1])
+	{
+		PartClause *eq = xform[BTEqualStrategyNumber - 1];
+
+		for (s = BTMaxStrategyNumber; --s >= 0;)
+		{
+			PartClause *chk = xform[s];
+
+			if (!chk || s == (BTEqualStrategyNumber - 1))
+				continue;
+
+			if (partition_cmp_args(partopfamily, partopcintype, chk, eq, chk,
+								   &test_result))
+			{
+				if (!test_result)
+				{
+					*constfalse = true;
+					return;
+				}
+				/* discard the redundant key. */
+				xform[s] = NULL;
+			}
+		}
+	}
+
+	/* try to keep only one of <, <= */
+	if (xform[BTLessStrategyNumber - 1] &&
+		xform[BTLessEqualStrategyNumber - 1])
+	{
+		PartClause *lt = xform[BTLessStrategyNumber - 1],
+				   *le = xform[BTLessEqualStrategyNumber - 1];
+
+		if (partition_cmp_args(partopfamily, partopcintype, le, lt, le,
+							   &test_result))
+		{
+			if (test_result)
+				xform[BTLessEqualStrategyNumber - 1] = NULL;
+			else
+				xform[BTLessStrategyNumber - 1] = NULL;
+		}
+	}
+
+	/* try to keep only one of >, >= */
+	if (xform[BTGreaterStrategyNumber - 1] &&
+		xform[BTGreaterEqualStrategyNumber - 1])
+	{
+		PartClause *gt = xform[BTGreaterStrategyNumber - 1],
+				   *ge = xform[BTGreaterEqualStrategyNumber - 1];
+
+		if (partition_cmp_args(partopfamily, partopcintype, ge, gt, ge,
+							   &test_result))
+		{
+			if (test_result)
+				xform[BTGreaterEqualStrategyNumber - 1] = NULL;
+			else
+				xform[BTGreaterStrategyNumber - 1] = NULL;
+		}
+	}
+
+	/*
+	 * xform now contains "best" clauses for i'th partition key column
+	 * for given btree strategy number.  Copy them to keyclauses[i].
+	 */
+	for (s = BTMaxStrategyNumber; --s >= 0;)
+		if (xform[s])
+			*result = lappend(*result, xform[s]);
+}
+
+static bool
+partition_cmp_args(Oid partopfamily, Oid partopcintype,
+				   PartClause *op, PartClause *leftarg, PartClause *rightarg,
+				   bool *result)
+{
+	Datum	leftarg_const,
+			rightarg_const;
+
+	Assert(op->valid_cache && leftarg->valid_cache && rightarg->valid_cache);
+	Assert(IsA(leftarg->constarg, Const) &&
+		   IsA(rightarg->constarg, Const));
+	leftarg_const = partkey_datum_from_expr(leftarg->constarg);
+	rightarg_const = partkey_datum_from_expr(rightarg->constarg);
+
+	/*
+	 * If the leftarg and rightarg clauses' constants are both of the type
+	 * expected by "op" clause's operator, then compare then using the
+	 * latter's comparison function.
+	 */
+	if (leftarg->op_subtype == partopcintype &&
+		rightarg->op_subtype == op->op_subtype)
+	{
+		*result = DatumGetBool(FunctionCall2Coll(&op->op_func,
+												 op->op->inputcollid,
+												 leftarg_const,
+												 rightarg_const));
+		return true;
+	}
+	else
+	{
+		/* Otherwise, look one up in the partitioning operator family. */
+		Oid		cmp_op = get_opfamily_member(partopfamily,
+											 leftarg->op_subtype,
+											 rightarg->op_subtype,
+											 op->op_strategy);
+		if (OidIsValid(cmp_op))
+		{
+			*result = DatumGetBool(OidFunctionCall2Coll(get_opcode(cmp_op),
+														op->op->inputcollid,
+														leftarg_const,
+														rightarg_const));
+			return true;
+		}
+	}
+
+	/* Couldn't do the comparison. */
+	*result = false;
+	return false;
+}
+
+/*
+ * get_partitions_for_keys
+ *		Returns the partitions hat will need to be scanned for the given
+ *		bounding keys
+ *
+ * Input:
+ *	See the comments above the definition of PartitionScanKeyInfo to see what
+ *	kind of information is received here.
+ *
+ * Outputs:
+ *	Partition set satisfying the keys.
+ */
+static PartitionSet *
+get_partitions_for_keys(Relation rel, PartitionScanKeyInfo *keys)
+{
+	return partset_new(false, true);
+}
+
+/*
  * get_partition_operator
  *
  * Return oid of the operator of given strategy for a given partition key
-- 
2.11.0