diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index db663203dac7cd167b1b24209581b58c530149dd..7bc1e6e2ba10cc55c9de0fd63dd2cb37fa30b463 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -209,8 +209,10 @@ cost_seqscan(Path *path, PlannerInfo *root,
  *	  Determines and returns the cost of scanning a relation using an index.
  *
  * 'index' is the index to be used
- * 'indexQuals' is the list of applicable qual clauses (implicit AND semantics)
- * 'indexOrderBys' is the list of ORDER BY operators for amcanorderbyop indexes
+ * 'indexQuals' is a list of lists of applicable qual clauses (implicit AND
+ *		semantics, one sub-list per index column)
+ * 'indexOrderBys' is a list of lists of lists of ORDER BY expressions for
+ *		amcanorderbyop indexes (lists per pathkey and index column)
  * 'indexonly' is true if it's an index-only scan
  * 'outer_rel' is the outer relation when we are considering using the index
  *		scan as the inside of a nestloop join (hence, some of the indexQuals
@@ -221,8 +223,8 @@ cost_seqscan(Path *path, PlannerInfo *root,
  * additional fields of the IndexPath besides startup_cost and total_cost.
  * These fields are needed if the IndexPath is used in a BitmapIndexScan.
  *
- * indexQuals is a list of RestrictInfo nodes, but indexOrderBys is a list of
- * bare expressions.
+ * indexQuals is a list of lists of RestrictInfo nodes, but indexOrderBys
+ * is a list of lists of lists of bare expressions.
  *
  * NOTE: 'indexQuals' must contain only clauses usable as index restrictions.
  * Any additional quals evaluated as qpquals may reduce the number of returned
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index fa6749a2f9c875faf2e0d41203fd08b86cf14ff2..26d7c1c2331ef006c03919575d1f733918997595 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -1148,7 +1148,8 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index)
  * Returns a list of sublists of RestrictInfo nodes for clauses that can be
  * used with this index.  Each sublist contains clauses that can be used
  * with one index key (in no particular order); the top list is ordered by
- * index key.  (This is depended on by expand_indexqual_conditions().)
+ * index key.  (This is depended on by expand_indexqual_conditions() and
+ * fix_indexqual_references().)
  *
  * We can use clauses from either the current clauses or outer_clauses lists,
  * but *found_clause is set TRUE only if we used at least one clause from
@@ -1171,8 +1172,11 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index)
  * column C, and no clauses use column B.
  *
  * Note: in some circumstances we may find the same RestrictInfos coming
- * from multiple places.  Defend against redundant outputs by using
- * list_append_unique_ptr (pointer equality should be good enough).
+ * from multiple places.  Defend against redundant outputs by keeping a side
+ * list of already-used clauses (pointer equality should be a good enough
+ * check for this).  That also keeps us from matching the same clause to
+ * multiple columns of a badly-defined index, which is unlikely to be helpful
+ * and is likely to give us an inflated idea of the index's selectivity.
  */
 static List *
 group_clauses_by_indexkey(IndexOptInfo *index,
@@ -1182,6 +1186,7 @@ group_clauses_by_indexkey(IndexOptInfo *index,
 						  bool *found_clause)
 {
 	List	   *clausegroup_list = NIL;
+	List	   *used_clauses = NIL;
 	bool		found_outer_clause = false;
 	int			indexcol;
 
@@ -1201,13 +1206,16 @@ group_clauses_by_indexkey(IndexOptInfo *index,
 			RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
 
 			Assert(IsA(rinfo, RestrictInfo));
+			if (list_member_ptr(used_clauses, rinfo))
+				continue;
 			if (match_clause_to_indexcol(index,
 										 indexcol,
 										 rinfo,
 										 outer_relids,
 										 saop_control))
 			{
-				clausegroup = list_append_unique_ptr(clausegroup, rinfo);
+				clausegroup = lappend(clausegroup, rinfo);
+				used_clauses = lappend(used_clauses, rinfo);
 				if (saop_control != SAOP_REQUIRE ||
 					IsA(rinfo->clause, ScalarArrayOpExpr))
 					*found_clause = true;
@@ -1220,13 +1228,16 @@ group_clauses_by_indexkey(IndexOptInfo *index,
 			RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
 
 			Assert(IsA(rinfo, RestrictInfo));
+			if (list_member_ptr(used_clauses, rinfo))
+				continue;
 			if (match_clause_to_indexcol(index,
 										 indexcol,
 										 rinfo,
 										 outer_relids,
 										 saop_control))
 			{
-				clausegroup = list_append_unique_ptr(clausegroup, rinfo);
+				clausegroup = lappend(clausegroup, rinfo);
+				used_clauses = lappend(used_clauses, rinfo);
 				found_outer_clause = true;
 			}
 		}
@@ -1240,6 +1251,8 @@ group_clauses_by_indexkey(IndexOptInfo *index,
 		clausegroup_list = lappend(clausegroup_list, clausegroup);
 	}
 
+	list_free(used_clauses);
+
 	if (!*found_clause && !found_outer_clause)
 		return NIL;				/* no indexable clauses anywhere */
 
@@ -1293,7 +1306,7 @@ group_clauses_by_indexkey(IndexOptInfo *index,
  *	  target index column.	This is sufficient to guarantee that some index
  *	  condition can be constructed from the RowCompareExpr --- whether the
  *	  remaining columns match the index too is considered in
- *	  expand_indexqual_rowcompare().
+ *	  adjust_rowcompare_for_index().
  *
  *	  It is also possible to match ScalarArrayOpExpr clauses to indexes, when
  *	  the clause is of the form "indexkey op ANY (arrayconst)".  Since not
@@ -1553,13 +1566,15 @@ match_rowcompare_to_indexcol(IndexOptInfo *index,
  *		Test whether an index can produce output ordered according to the
  *		given pathkeys using "ordering operators".
  *
- * If it can, return a list of suitable ORDER BY expressions, each of the form
- * "indexedcol operator pseudoconstant".  If not, return NIL.
+ * If it can, return a list of lists of lists of ORDER BY expressions,
+ * each of the form "indexedcol operator pseudoconstant".  If not, return NIL.
+ * (The top list corresponds to pathkeys and the sub-lists to index columns;
+ * see comments for indexorderbys in nodes/relation.h.)
  */
 static List *
 match_index_to_pathkeys(IndexOptInfo *index, List *pathkeys)
 {
-	List	   *orderbyexprs = NIL;
+	List	   *orderbylists = NIL;
 	ListCell   *lc1;
 
 	/* Only indexes with the amcanorderbyop property are interesting here */
@@ -1606,7 +1621,17 @@ match_index_to_pathkeys(IndexOptInfo *index, List *pathkeys)
 												   pathkey->pk_opfamily);
 				if (expr)
 				{
-					orderbyexprs = lappend(orderbyexprs, expr);
+					/*
+					 * Generate list-of-sublists representation to show which
+					 * index column this expression matches.
+					 */
+					List   *sublist = NIL;
+					int		i;
+
+					for (i = 0; i < indexcol; i++)
+						sublist = lappend(sublist, NIL);
+					sublist = lappend(sublist, list_make1(expr));
+					orderbylists = lappend(orderbylists, sublist);
 					found = true;
 					break;
 				}
@@ -1620,7 +1645,7 @@ match_index_to_pathkeys(IndexOptInfo *index, List *pathkeys)
 			return NIL;
 	}
 
-	return orderbyexprs;		/* success! */
+	return orderbylists;		/* success! */
 }
 
 /*
@@ -2434,9 +2459,11 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
  *	  Given a list of lists of RestrictInfos, flatten it to a list
  *	  of RestrictInfos.
  *
- * This is used to flatten out the result of group_clauses_by_indexkey()
- * to produce an indexclauses list.  The original list structure mustn't
- * be altered, but it's OK to share copies of the underlying RestrictInfos.
+ * This is used to flatten out a list of sublists of index clauses (such as
+ * the result of group_clauses_by_indexkey()) into a single list, for use
+ * where we don't care which clause goes with which index column.  The input
+ * list structure mustn't be altered, but it's OK to share copies of the
+ * underlying RestrictInfos.
  */
 List *
 flatten_clausegroups_list(List *clausegroups)
@@ -2449,6 +2476,38 @@ flatten_clausegroups_list(List *clausegroups)
 	return allclauses;
 }
 
+/*
+ * flatten_indexorderbys_list
+ *	  Given a list of lists of lists of ORDER BY expressions, flatten it.
+ *
+ * This is similar to flatten_clausegroups_list, but is used to flatten the
+ * three-list-level result of match_index_to_pathkeys().  We assume the
+ * bottom lists each have zero or one member.
+ */
+List *
+flatten_indexorderbys_list(List *indexorderbys)
+{
+	List	   *allclauses = NIL;
+	ListCell   *lc1;
+
+	foreach(lc1, indexorderbys)
+	{
+		List	   *sublist = (List *) lfirst(lc1);
+		ListCell   *lc2;
+
+		foreach(lc2, sublist)
+		{
+			List	   *subsublist = (List *) lfirst(lc2);
+
+			if (subsublist == NIL)
+				continue;
+			Assert(list_length(subsublist) == 1);
+			allclauses = lappend(allclauses, (Expr *) linitial(subsublist));
+		}
+	}
+	return allclauses;
+}
+
 
 /****************************************************************************
  *				----  ROUTINES TO CHECK OPERANDS  ----
@@ -2577,8 +2636,8 @@ match_index_to_operand(Node *operand,
  * converted into boolean equality operators.
  *
  * expand_indexqual_conditions() converts a list of lists of RestrictInfo
- * nodes (with implicit AND semantics across list elements) into
- * a list of clauses that the executor can actually handle.  For operators
+ * nodes (with implicit AND semantics across list elements) into a list of
+ * lists of clauses that the executor can actually handle.  For operators
  * that are members of the index's opfamily this transformation is a no-op,
  * but clauses recognized by match_special_index_operator() or
  * match_boolean_index_clause() must be converted into one or more "regular"
@@ -2796,22 +2855,20 @@ match_special_index_operator(Expr *clause, Oid opfamily, Oid idxcollation,
 
 /*
  * expand_indexqual_conditions
- *	  Given a list of sublists of RestrictInfo nodes, produce a flat list
+ *	  Given a list of sublists of RestrictInfo nodes, produce a list of lists
  *	  of index qual clauses.  Standard qual clauses (those in the index's
  *	  opfamily) are passed through unchanged.  Boolean clauses and "special"
  *	  index operators are expanded into clauses that the indexscan machinery
  *	  will know what to do with.  RowCompare clauses are simplified if
  *	  necessary to create a clause that is fully checkable by the index.
  *
- * The input list is ordered by index key, and so the output list is too.
- * (The latter is not depended on by any part of the core planner, I believe,
- * but parts of the executor require it, and so do the amcostestimate
- * functions.)
+ * The input clauses are grouped by index key, and so the output is too.
+ * (This is depended on in various places in both planner and executor.)
  */
 List *
 expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups)
 {
-	List	   *resultquals = NIL;
+	List	   *resultgroups = NIL;
 	ListCell   *lc;
 	int			indexcol;
 
@@ -2827,6 +2884,7 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups)
 		List	   *clausegroup = (List *) lfirst(lc);
 		Oid			curFamily = index->opfamily[indexcol];
 		Oid			curCollation = index->indexcollations[indexcol];
+		List	   *newgroup = NIL;
 		ListCell   *lc2;
 
 		foreach(lc2, clausegroup)
@@ -2844,8 +2902,8 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups)
 													   index);
 				if (boolqual)
 				{
-					resultquals = lappend(resultquals,
-										  make_simple_restrictinfo(boolqual));
+					newgroup = lappend(newgroup,
+									   make_simple_restrictinfo(boolqual));
 					continue;
 				}
 			}
@@ -2856,38 +2914,40 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups)
 			 */
 			if (is_opclause(clause))
 			{
-				resultquals = list_concat(resultquals,
-										  expand_indexqual_opclause(rinfo,
-																	curFamily,
+				newgroup = list_concat(newgroup,
+									   expand_indexqual_opclause(rinfo,
+																 curFamily,
 															  curCollation));
 			}
 			else if (IsA(clause, ScalarArrayOpExpr))
 			{
 				/* no extra work at this time */
-				resultquals = lappend(resultquals, rinfo);
+				newgroup = lappend(newgroup, rinfo);
 			}
 			else if (IsA(clause, RowCompareExpr))
 			{
-				resultquals = lappend(resultquals,
-									  expand_indexqual_rowcompare(rinfo,
-																  index,
-																  indexcol));
+				newgroup = lappend(newgroup,
+								   expand_indexqual_rowcompare(rinfo,
+															   index,
+															   indexcol));
 			}
 			else if (IsA(clause, NullTest))
 			{
 				Assert(index->amsearchnulls);
-				resultquals = lappend(resultquals,
-									  make_simple_restrictinfo(clause));
+				newgroup = lappend(newgroup,
+								   make_simple_restrictinfo(clause));
 			}
 			else
 				elog(ERROR, "unsupported indexqual type: %d",
 					 (int) nodeTag(clause));
 		}
 
+		resultgroups = lappend(resultgroups, newgroup);
+
 		indexcol++;
 	}
 
-	return resultquals;
+	return resultgroups;
 }
 
 /*
@@ -3054,6 +3114,41 @@ expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily, Oid idxcollation)
  * expand_indexqual_rowcompare --- expand a single indexqual condition
  *		that is a RowCompareExpr
  *
+ * This is a thin wrapper around adjust_rowcompare_for_index; we export the
+ * latter so that createplan.c can use it to re-discover which columns of the
+ * index are used by a row comparison indexqual.
+ */
+static RestrictInfo *
+expand_indexqual_rowcompare(RestrictInfo *rinfo,
+							IndexOptInfo *index,
+							int indexcol)
+{
+	RowCompareExpr *clause = (RowCompareExpr *) rinfo->clause;
+	Expr	   *newclause;
+	List	   *indexcolnos;
+	bool		var_on_left;
+
+	newclause = adjust_rowcompare_for_index(clause,
+											index,
+											indexcol,
+											&indexcolnos,
+											&var_on_left);
+
+	/*
+	 * If we didn't have to change the RowCompareExpr, return the original
+	 * RestrictInfo.
+	 */
+	if (newclause == (Expr *) clause)
+		return rinfo;
+
+	/* Else we need a new RestrictInfo */
+	return make_simple_restrictinfo(newclause);
+}
+
+/*
+ * adjust_rowcompare_for_index --- expand a single indexqual condition
+ *		that is a RowCompareExpr
+ *
  * It's already known that the first column of the row comparison matches
  * the specified column of the index.  We can use additional columns of the
  * row comparison as index qualifications, so long as they match the index
@@ -3066,13 +3161,23 @@ expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily, Oid idxcollation)
  * even when the original was "<" or ">" --- this is necessary to match all
  * the rows that could match the original.	(We are essentially building a
  * lossy version of the row comparison when we do this.)
+ *
+ * *indexcolnos receives an integer list of the index column numbers (zero
+ * based) used in the resulting expression.  The reason we need to return
+ * that is that if the index is selected for use, createplan.c will need to
+ * call this again to extract that list.  (This is a bit grotty, but row
+ * comparison indexquals aren't used enough to justify finding someplace to
+ * keep the information in the Path representation.)  Since createplan.c
+ * also needs to know which side of the RowCompareExpr is the index side,
+ * we also return *var_on_left_p rather than re-deducing that there.
  */
-static RestrictInfo *
-expand_indexqual_rowcompare(RestrictInfo *rinfo,
+Expr *
+adjust_rowcompare_for_index(RowCompareExpr *clause,
 							IndexOptInfo *index,
-							int indexcol)
+							int indexcol,
+							List **indexcolnos,
+							bool *var_on_left_p)
 {
-	RowCompareExpr *clause = (RowCompareExpr *) rinfo->clause;
 	bool		var_on_left;
 	int			op_strategy;
 	Oid			op_lefttype;
@@ -3094,6 +3199,8 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo,
 	Assert(var_on_left ||
 		   match_index_to_operand((Node *) linitial(clause->rargs),
 								  indexcol, index));
+	*var_on_left_p = var_on_left;
+
 	expr_op = linitial_oid(clause->opnos);
 	if (!var_on_left)
 		expr_op = get_commutator(expr_op);
@@ -3101,6 +3208,10 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo,
 							   &op_strategy,
 							   &op_lefttype,
 							   &op_righttype);
+
+	/* Initialize returned list of which index columns are used */
+	*indexcolnos = list_make1_int(indexcol);
+
 	/* Build lists of the opfamilies and operator datatypes in case needed */
 	opfamilies = list_make1_oid(index->opfamily[indexcol]);
 	lefttypes = list_make1_oid(op_lefttype);
@@ -3147,28 +3258,22 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo,
 			break;				/* no good, volatile comparison value */
 
 		/*
-		 * The Var side can match any column of the index.	If the user does
-		 * something weird like having multiple identical index columns, we
-		 * insist the match be on the first such column, to avoid confusing
-		 * the executor.
+		 * The Var side can match any column of the index.
 		 */
 		for (i = 0; i < index->ncolumns; i++)
 		{
-			if (match_index_to_operand(varop, i, index))
+			if (match_index_to_operand(varop, i, index) &&
+				get_op_opfamily_strategy(expr_op,
+										 index->opfamily[i]) == op_strategy &&
+				IndexCollMatchesExprColl(index->indexcollations[i],
+										 lfirst_oid(collids_cell)))
 				break;
 		}
 		if (i >= index->ncolumns)
 			break;				/* no match found */
 
-		/* Now, do we have the right operator for this column? */
-		if (get_op_opfamily_strategy(expr_op, index->opfamily[i])
-			!= op_strategy)
-			break;
-
-		/* Does collation match? */
-		if (!IndexCollMatchesExprColl(index->indexcollations[i],
-									  lfirst_oid(collids_cell)))
-			break;
+		/* Add column number to returned list */
+		*indexcolnos = lappend_int(*indexcolnos, i);
 
 		/* Add opfamily and datatypes to lists */
 		get_op_opfamily_properties(expr_op, index->opfamily[i], false,
@@ -3189,7 +3294,7 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo,
 
 	/* Return clause as-is if it's all usable as index quals */
 	if (matching_cols == list_length(clause->opnos))
-		return rinfo;
+		return (Expr *) clause;
 
 	/*
 	 * We have to generate a subset rowcompare (possibly just one OpExpr). The
@@ -3260,18 +3365,15 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo,
 								  matching_cols);
 		rc->rargs = list_truncate((List *) copyObject(clause->rargs),
 								  matching_cols);
-		return make_simple_restrictinfo((Expr *) rc);
+		return (Expr *) rc;
 	}
 	else
 	{
-		Expr	   *opexpr;
-
-		opexpr = make_opclause(linitial_oid(new_ops), BOOLOID, false,
-							   copyObject(linitial(clause->largs)),
-							   copyObject(linitial(clause->rargs)),
-							   InvalidOid,
-							   linitial_oid(clause->inputcollids));
-		return make_simple_restrictinfo(opexpr);
+		return make_opclause(linitial_oid(new_ops), BOOLOID, false,
+							 copyObject(linitial(clause->largs)),
+							 copyObject(linitial(clause->rargs)),
+							 InvalidOid,
+							 linitial_oid(clause->inputcollids));
 	}
 }
 
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 8138b0118b46883a5c439486a9522355a968f36b..04024cc4939383ad6c10454f4d6fa3c5818f6fa3 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -87,7 +87,7 @@ static List *fix_indexqual_references(PlannerInfo *root, IndexPath *index_path,
 						 List *indexquals);
 static List *fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path,
 							List *indexorderbys);
-static Node *fix_indexqual_operand(Node *node, IndexOptInfo *index);
+static Node *fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol);
 static List *get_switched_clauses(List *clauses, Relids outerrelids);
 static List *order_qual_clauses(PlannerInfo *root, List *clauses);
 static void copy_path_costsize(Plan *dest, Path *src);
@@ -1073,10 +1073,6 @@ create_seqscan_plan(PlannerInfo *root, Path *best_path,
  * qual preprocessing work is the same for both.  Note that the caller tells
  * us which to build --- we don't look at best_path->path.pathtype, because
  * create_bitmap_subplan needs to be able to override the prior decision.
- *
- * The indexquals list of the path contains implicitly-ANDed qual conditions.
- * The list can be empty --- then no index restrictions will be applied during
- * the scan.
  */
 static Scan *
 create_indexscan_plan(PlannerInfo *root,
@@ -1086,11 +1082,11 @@ create_indexscan_plan(PlannerInfo *root,
 					  bool indexonly)
 {
 	Scan	   *scan_plan;
-	List	   *indexquals = best_path->indexquals;
 	List	   *indexorderbys = best_path->indexorderbys;
 	Index		baserelid = best_path->path.parent->relid;
 	Oid			indexoid = best_path->indexinfo->indexoid;
 	List	   *qpqual;
+	List	   *indexquals;
 	List	   *stripped_indexquals;
 	List	   *fixed_indexquals;
 	List	   *fixed_indexorderbys;
@@ -1100,6 +1096,13 @@ create_indexscan_plan(PlannerInfo *root,
 	Assert(baserelid > 0);
 	Assert(best_path->path.parent->rtekind == RTE_RELATION);
 
+	/*
+	 * We need to flatten the indexquals list-of-sublists, since most of the
+	 * processing below doesn't care which index column each qual is
+	 * associated with.
+	 */
+	indexquals = flatten_clausegroups_list(best_path->indexquals);
+
 	/*
 	 * Build "stripped" indexquals structure (no RestrictInfos) to pass to
 	 * executor as indexqualorig
@@ -1108,14 +1111,23 @@ create_indexscan_plan(PlannerInfo *root,
 
 	/*
 	 * The executor needs a copy with the indexkey on the left of each clause
-	 * and with index Vars substituted for table ones.
+	 * and with index Vars substituted for table ones.  Here we use the
+	 * unflattened list so we can conveniently tell which index column each
+	 * clause is for.
 	 */
-	fixed_indexquals = fix_indexqual_references(root, best_path, indexquals);
+	fixed_indexquals = fix_indexqual_references(root, best_path,
+												best_path->indexquals);
 
 	/*
 	 * Likewise fix up index attr references in the ORDER BY expressions.
 	 */
-	fixed_indexorderbys = fix_indexorderby_references(root, best_path, indexorderbys);
+	fixed_indexorderbys = fix_indexorderby_references(root, best_path,
+													  indexorderbys);
+
+	/*
+	 * Also produce a flat list to become the indexorderbyorig.
+	 */
+	indexorderbys = flatten_indexorderbys_list(indexorderbys);
 
 	/*
 	 * If this is an innerjoin scan, the indexclauses will contain join
@@ -1494,7 +1506,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
 			clamp_row_est(ipath->indexselectivity * ipath->path.parent->tuples);
 		plan->plan_width = 0;	/* meaningless */
 		*qual = get_actual_clauses(ipath->indexclauses);
-		*indexqual = get_actual_clauses(ipath->indexquals);
+		*indexqual = get_actual_clauses(flatten_clausegroups_list(ipath->indexquals));
 		foreach(l, ipath->indexinfo->indpred)
 		{
 			Expr	   *pred = (Expr *) lfirst(l);
@@ -2472,7 +2484,8 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root)
  *	  Adjust indexqual clauses to the form the executor's indexqual
  *	  machinery needs.
  *
- * We have four tasks here:
+ * We have five tasks here:
+ *	* Flatten the list-of-sublists structure of indexquals into a simple list.
  *	* Remove RestrictInfo nodes from the input clauses.
  *	* Replace any outer-relation Var or PHV nodes with nestloop Params.
  *	  (XXX eventually, that responsibility should go elsewhere?)
@@ -2492,96 +2505,129 @@ fix_indexqual_references(PlannerInfo *root, IndexPath *index_path,
 {
 	IndexOptInfo *index = index_path->indexinfo;
 	List	   *fixed_indexquals;
-	ListCell   *l;
+	ListCell   *lc1;
+	int			indexcol;
 
 	fixed_indexquals = NIL;
 
-	foreach(l, indexquals)
-	{
-		RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
-		Node	   *clause;
-
-		Assert(IsA(rinfo, RestrictInfo));
+	/* clausegroups must correspond to index columns */
+	Assert(list_length(indexquals) <= index->ncolumns);
 
-		/*
-		 * Replace any outer-relation variables with nestloop params.
-		 *
-		 * This also makes a copy of the clause, so it's safe to modify it
-		 * in-place below.
-		 */
-		clause = replace_nestloop_params(root, (Node *) rinfo->clause);
+	indexcol = 0;
+	foreach(lc1, indexquals)
+	{
+		List	   *clausegroup = (List *) lfirst(lc1);
+		ListCell   *lc2;
 
-		if (IsA(clause, OpExpr))
+		foreach(lc2, clausegroup)
 		{
-			OpExpr	   *op = (OpExpr *) clause;
+			RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc2);
+			Node	   *clause;
 
-			if (list_length(op->args) != 2)
-				elog(ERROR, "indexqual clause is not binary opclause");
+			Assert(IsA(rinfo, RestrictInfo));
 
 			/*
-			 * Check to see if the indexkey is on the right; if so, commute
-			 * the clause. The indexkey should be the side that refers to
-			 * (only) the base relation.
+			 * Replace any outer-relation variables with nestloop params.
+			 *
+			 * This also makes a copy of the clause, so it's safe to modify it
+			 * in-place below.
 			 */
-			if (!bms_equal(rinfo->left_relids, index->rel->relids))
-				CommuteOpExpr(op);
+			clause = replace_nestloop_params(root, (Node *) rinfo->clause);
 
-			/*
-			 * Now, determine which index attribute this is and change the
-			 * indexkey operand as needed.
-			 */
-			linitial(op->args) = fix_indexqual_operand(linitial(op->args),
-													   index);
-		}
-		else if (IsA(clause, RowCompareExpr))
-		{
-			RowCompareExpr *rc = (RowCompareExpr *) clause;
-			ListCell   *lc;
+			if (IsA(clause, OpExpr))
+			{
+				OpExpr	   *op = (OpExpr *) clause;
 
-			/*
-			 * Check to see if the indexkey is on the right; if so, commute
-			 * the clause. The indexkey should be the side that refers to
-			 * (only) the base relation.
-			 */
-			if (!bms_overlap(pull_varnos(linitial(rc->largs)),
-							 index->rel->relids))
-				CommuteRowCompareExpr(rc);
+				if (list_length(op->args) != 2)
+					elog(ERROR, "indexqual clause is not binary opclause");
 
-			/*
-			 * For each column in the row comparison, determine which index
-			 * attribute this is and change the indexkey operand as needed.
-			 */
-			foreach(lc, rc->largs)
+				/*
+				 * Check to see if the indexkey is on the right; if so,
+				 * commute the clause. The indexkey should be the side that
+				 * refers to (only) the base relation.
+				 */
+				if (!bms_equal(rinfo->left_relids, index->rel->relids))
+					CommuteOpExpr(op);
+
+				/*
+				 * Now replace the indexkey expression with an index Var.
+				 */
+				linitial(op->args) = fix_indexqual_operand(linitial(op->args),
+														   index,
+														   indexcol);
+			}
+			else if (IsA(clause, RowCompareExpr))
 			{
-				lfirst(lc) = fix_indexqual_operand(lfirst(lc),
-												   index);
+				RowCompareExpr *rc = (RowCompareExpr *) clause;
+				Expr	   *newrc;
+				List	   *indexcolnos;
+				bool		var_on_left;
+				ListCell   *lca,
+						   *lci;
+
+				/*
+				 * Re-discover which index columns are used in the rowcompare.
+				 */
+				newrc = adjust_rowcompare_for_index(rc,
+													index,
+													indexcol,
+													&indexcolnos,
+													&var_on_left);
+
+				/*
+				 * Trouble if adjust_rowcompare_for_index thought the
+				 * RowCompareExpr didn't match the index as-is; the clause
+				 * should have gone through that routine already.
+				 */
+				if (newrc != (Expr *) rc)
+					elog(ERROR, "inconsistent results from adjust_rowcompare_for_index");
+
+				/*
+				 * Check to see if the indexkey is on the right; if so,
+				 * commute the clause.
+				 */
+				if (!var_on_left)
+					CommuteRowCompareExpr(rc);
+
+				/*
+				 * Now replace the indexkey expressions with index Vars.
+				 */
+				Assert(list_length(rc->largs) == list_length(indexcolnos));
+				forboth(lca, rc->largs, lci, indexcolnos)
+				{
+					lfirst(lca) = fix_indexqual_operand(lfirst(lca),
+														index,
+														lfirst_int(lci));
+				}
 			}
-		}
-		else if (IsA(clause, ScalarArrayOpExpr))
-		{
-			ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
+			else if (IsA(clause, ScalarArrayOpExpr))
+			{
+				ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
 
-			/* Never need to commute... */
+				/* Never need to commute... */
 
-			/*
-			 * Determine which index attribute this is and change the indexkey
-			 * operand as needed.
-			 */
-			linitial(saop->args) = fix_indexqual_operand(linitial(saop->args),
-														 index);
-		}
-		else if (IsA(clause, NullTest))
-		{
-			NullTest   *nt = (NullTest *) clause;
+				/* Replace the indexkey expression with an index Var. */
+				linitial(saop->args) = fix_indexqual_operand(linitial(saop->args),
+															 index,
+															 indexcol);
+			}
+			else if (IsA(clause, NullTest))
+			{
+				NullTest   *nt = (NullTest *) clause;
+
+				/* Replace the indexkey expression with an index Var. */
+				nt->arg = (Expr *) fix_indexqual_operand((Node *) nt->arg,
+														 index,
+														 indexcol);
+			}
+			else
+				elog(ERROR, "unsupported indexqual type: %d",
+					 (int) nodeTag(clause));
 
-			nt->arg = (Expr *) fix_indexqual_operand((Node *) nt->arg,
-													 index);
+			fixed_indexquals = lappend(fixed_indexquals, clause);
 		}
-		else
-			elog(ERROR, "unsupported indexqual type: %d",
-				 (int) nodeTag(clause));
 
-		fixed_indexquals = lappend(fixed_indexquals, clause);
+		indexcol++;
 	}
 
 	return fixed_indexquals;
@@ -2593,7 +2639,7 @@ fix_indexqual_references(PlannerInfo *root, IndexPath *index_path,
  *	  machinery needs.
  *
  * This is a simplified version of fix_indexqual_references.  The input does
- * not have RestrictInfo nodes, and we assume that indxqual.c already
+ * not have RestrictInfo nodes, and we assume that indxpath.c already
  * commuted the clauses to put the index keys on the left.	Also, we don't
  * bother to support any cases except simple OpExprs, since nothing else
  * is allowed for ordering operators.
@@ -2604,41 +2650,62 @@ fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path,
 {
 	IndexOptInfo *index = index_path->indexinfo;
 	List	   *fixed_indexorderbys;
-	ListCell   *l;
+	ListCell   *lc1;
 
 	fixed_indexorderbys = NIL;
 
-	foreach(l, indexorderbys)
+	foreach(lc1, indexorderbys)
 	{
-		Node	   *clause = (Node *) lfirst(l);
+		List	   *percollists = (List *) lfirst(lc1);
+		ListCell   *lc2;
+		int			indexcol;
 
-		/*
-		 * Replace any outer-relation variables with nestloop params.
-		 *
-		 * This also makes a copy of the clause, so it's safe to modify it
-		 * in-place below.
-		 */
-		clause = replace_nestloop_params(root, clause);
+		/* percollists must correspond to index columns */
+		Assert(list_length(percollists) <= index->ncolumns);
 
-		if (IsA(clause, OpExpr))
+		indexcol = 0;
+		foreach(lc2, percollists)
 		{
-			OpExpr	   *op = (OpExpr *) clause;
+			List	   *percollist = (List *) lfirst(lc2);
 
-			if (list_length(op->args) != 2)
-				elog(ERROR, "indexorderby clause is not binary opclause");
+			if (percollist != NIL)
+			{
+				Node	   *clause = (Node *) linitial(percollist);
 
-			/*
-			 * Now, determine which index attribute this is and change the
-			 * indexkey operand as needed.
-			 */
-			linitial(op->args) = fix_indexqual_operand(linitial(op->args),
-													   index);
-		}
-		else
-			elog(ERROR, "unsupported indexorderby type: %d",
-				 (int) nodeTag(clause));
+				/* Should have only one clause per index column */
+				Assert(list_length(percollist) == 1);
+
+				/*
+				 * Replace any outer-relation variables with nestloop params.
+				 *
+				 * This also makes a copy of the clause, so it's safe to
+				 * modify it in-place below.
+				 */
+				clause = replace_nestloop_params(root, clause);
+
+				if (IsA(clause, OpExpr))
+				{
+					OpExpr	   *op = (OpExpr *) clause;
+
+					if (list_length(op->args) != 2)
+						elog(ERROR, "indexorderby clause is not binary opclause");
+
+					/*
+					 * Now replace the indexkey expression with an index Var.
+					 */
+					linitial(op->args) = fix_indexqual_operand(linitial(op->args),
+															   index,
+															   indexcol);
+				}
+				else
+					elog(ERROR, "unsupported indexorderby type: %d",
+						 (int) nodeTag(clause));
+
+				fixed_indexorderbys = lappend(fixed_indexorderbys, clause);
+			}
 
-		fixed_indexorderbys = lappend(fixed_indexorderbys, clause);
+			indexcol++;
+		}
 	}
 
 	return fixed_indexorderbys;
@@ -2650,9 +2717,12 @@ fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path,
  *
  * We represent index keys by Var nodes having varno == INDEX_VAR and varattno
  * equal to the index's attribute number (index column position).
+ *
+ * Most of the code here is just for sanity cross-checking that the given
+ * expression actually matches the index column it's claimed to.
  */
 static Node *
-fix_indexqual_operand(Node *node, IndexOptInfo *index)
+fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol)
 {
 	Var		   *result;
 	int			pos;
@@ -2664,55 +2734,56 @@ fix_indexqual_operand(Node *node, IndexOptInfo *index)
 	if (IsA(node, RelabelType))
 		node = (Node *) ((RelabelType *) node)->arg;
 
-	if (IsA(node, Var) &&
-		((Var *) node)->varno == index->rel->relid)
-	{
-		/* Try to match against simple index columns */
-		int			varatt = ((Var *) node)->varattno;
+	Assert(indexcol >= 0 && indexcol < index->ncolumns);
 
-		if (varatt != 0)
+	if (index->indexkeys[indexcol] != 0)
+	{
+		/* It's a simple index column */
+		if (IsA(node, Var) &&
+			((Var *) node)->varno == index->rel->relid &&
+			((Var *) node)->varattno == index->indexkeys[indexcol])
 		{
-			for (pos = 0; pos < index->ncolumns; pos++)
-			{
-				if (index->indexkeys[pos] == varatt)
-				{
-					result = (Var *) copyObject(node);
-					result->varno = INDEX_VAR;
-					result->varattno = pos + 1;
-					return (Node *) result;
-				}
-			}
+			result = (Var *) copyObject(node);
+			result->varno = INDEX_VAR;
+			result->varattno = indexcol + 1;
+			return (Node *) result;
 		}
+		else
+			elog(ERROR, "index key does not match expected index column");
 	}
 
-	/* Try to match against index expressions */
+	/* It's an index expression, so find and cross-check the expression */
 	indexpr_item = list_head(index->indexprs);
 	for (pos = 0; pos < index->ncolumns; pos++)
 	{
 		if (index->indexkeys[pos] == 0)
 		{
-			Node	   *indexkey;
-
 			if (indexpr_item == NULL)
 				elog(ERROR, "too few entries in indexprs list");
-			indexkey = (Node *) lfirst(indexpr_item);
-			if (indexkey && IsA(indexkey, RelabelType))
-				indexkey = (Node *) ((RelabelType *) indexkey)->arg;
-			if (equal(node, indexkey))
+			if (pos == indexcol)
 			{
-				/* Found a match */
-				result = makeVar(INDEX_VAR, pos + 1,
-								 exprType(lfirst(indexpr_item)), -1,
-								 exprCollation(lfirst(indexpr_item)),
-								 0);
-				return (Node *) result;
+				Node	   *indexkey;
+
+				indexkey = (Node *) lfirst(indexpr_item);
+				if (indexkey && IsA(indexkey, RelabelType))
+					indexkey = (Node *) ((RelabelType *) indexkey)->arg;
+				if (equal(node, indexkey))
+				{
+					result = makeVar(INDEX_VAR, indexcol + 1,
+									 exprType(lfirst(indexpr_item)), -1,
+									 exprCollation(lfirst(indexpr_item)),
+									 0);
+					return (Node *) result;
+				}
+				else
+					elog(ERROR, "index key does not match expected index column");
 			}
 			indexpr_item = lnext(indexpr_item);
 		}
 	}
 
 	/* Ooops... */
-	elog(ERROR, "node is not an index attribute");
+	elog(ERROR, "index key does not match expected index column");
 	return NULL;				/* keep compiler quiet */
 }
 
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 1e7aac95ef4fba83b97b5a47ab0f4b6f29bee693..9e99c9d9d7b5e4c1863e2d3ca8fdb5f54787f338 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -412,8 +412,8 @@ create_seqscan_path(PlannerInfo *root, RelOptInfo *rel)
  * 'index' is a usable index.
  * 'clause_groups' is a list of lists of RestrictInfo nodes
  *			to be used as index qual conditions in the scan.
- * 'indexorderbys' is a list of bare expressions (no RestrictInfos)
- *			to be used as index ordering operators in the scan.
+ * 'indexorderbys' is a list of lists of lists of bare expressions (not
+ *			RestrictInfos) to be used as index ordering operators.
  * 'pathkeys' describes the ordering of the path.
  * 'indexscandir' is ForwardScanDirection or BackwardScanDirection
  *			for an ordered index, or NoMovementScanDirection for
diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c
index 63ff431900e195fe09ef5e38509d2d215ca6e687..8e3d4bb8453d92a5cba2e81e7c63901d869f8f29 100644
--- a/src/backend/optimizer/util/restrictinfo.c
+++ b/src/backend/optimizer/util/restrictinfo.c
@@ -630,7 +630,7 @@ extract_actual_join_clauses(List *restrictinfo_list,
  * being used in an inner indexscan need not be checked again at the join.
  *
  * "Redundant" means either equal() or derived from the same EquivalenceClass.
- * We have to check the latter because indxqual.c may select different derived
+ * We have to check the latter because indxpath.c may select different derived
  * clauses than were selected by generate_join_implied_equalities().
  *
  * Note that we are *not* checking for local redundancies within the given
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index bb411f9ad1d08fcc9a040d2919ba7cedfc692182..3e6cabf7e7408c33ed9de57fdfaac19216f8b7db 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5991,6 +5991,14 @@ genericcostestimate(PlannerInfo *root,
 	List	   *selectivityQuals;
 	ListCell   *l;
 
+	/*
+	 * For our purposes here, it doesn't matter which index columns the
+	 * individual quals and order-by expressions go with, so flatten the
+	 * lists for convenience.
+	 */
+	indexQuals = flatten_clausegroups_list(indexQuals);
+	indexOrderBys = flatten_indexorderbys_list(indexOrderBys);
+
 	/*----------
 	 * If the index is partial, AND the index predicate with the explicitly
 	 * given indexquals to produce a more accurate idea of the index
@@ -6022,7 +6030,7 @@ genericcostestimate(PlannerInfo *root,
 			if (!predicate_implied_by(oneQual, indexQuals))
 				predExtraQuals = list_concat(predExtraQuals, oneQual);
 		}
-		/* list_concat avoids modifying the passed-in indexQuals list */
+		/* list_concat avoids modifying the indexQuals list */
 		selectivityQuals = list_concat(predExtraQuals, indexQuals);
 	}
 	else
@@ -6250,7 +6258,7 @@ btcostestimate(PG_FUNCTION_ARGS)
 	bool		found_saop;
 	bool		found_is_null_op;
 	double		num_sa_scans;
-	ListCell   *l;
+	ListCell   *lc1;
 
 	/*
 	 * For a btree scan, only leading '=' quals plus inequality quals for the
@@ -6259,8 +6267,7 @@ btcostestimate(PG_FUNCTION_ARGS)
 	 * the index scan).  Additional quals can suppress visits to the heap, so
 	 * it's OK to count them in indexSelectivity, but they should not count
 	 * for estimating numIndexTuples.  So we must examine the given indexQuals
-	 * to find out which ones count as boundary quals.	We rely on the
-	 * knowledge that they are given in index column order.
+	 * to find out which ones count as boundary quals.
 	 *
 	 * For a RowCompareExpr, we consider only the first column, just as
 	 * rowcomparesel() does.
@@ -6270,120 +6277,119 @@ btcostestimate(PG_FUNCTION_ARGS)
 	 * considered to act the same as it normally does.
 	 */
 	indexBoundQuals = NIL;
-	indexcol = 0;
 	eqQualHere = false;
 	found_saop = false;
 	found_is_null_op = false;
 	num_sa_scans = 1;
-	foreach(l, indexQuals)
+
+	/* clausegroups must correspond to index columns */
+	Assert(list_length(indexQuals) <= index->ncolumns);
+
+	indexcol = 0;
+	foreach(lc1, indexQuals)
 	{
-		RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
-		Expr	   *clause;
-		Node	   *leftop,
-				   *rightop;
-		Oid			clause_op;
-		int			op_strategy;
-		bool		is_null_op = false;
+		List	   *clausegroup = (List *) lfirst(lc1);
+		ListCell   *lc2;
 
-		Assert(IsA(rinfo, RestrictInfo));
-		clause = rinfo->clause;
-		if (IsA(clause, OpExpr))
-		{
-			leftop = get_leftop(clause);
-			rightop = get_rightop(clause);
-			clause_op = ((OpExpr *) clause)->opno;
-		}
-		else if (IsA(clause, RowCompareExpr))
-		{
-			RowCompareExpr *rc = (RowCompareExpr *) clause;
+		eqQualHere = false;
 
-			leftop = (Node *) linitial(rc->largs);
-			rightop = (Node *) linitial(rc->rargs);
-			clause_op = linitial_oid(rc->opnos);
-		}
-		else if (IsA(clause, ScalarArrayOpExpr))
+		foreach(lc2, clausegroup)
 		{
-			ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
+			RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc2);
+			Expr	   *clause;
+			Node	   *leftop,
+					   *rightop;
+			Oid			clause_op;
+			int			op_strategy;
+			bool		is_null_op = false;
+
+			Assert(IsA(rinfo, RestrictInfo));
+			clause = rinfo->clause;
+			if (IsA(clause, OpExpr))
+			{
+				leftop = get_leftop(clause);
+				rightop = get_rightop(clause);
+				clause_op = ((OpExpr *) clause)->opno;
+			}
+			else if (IsA(clause, RowCompareExpr))
+			{
+				RowCompareExpr *rc = (RowCompareExpr *) clause;
 
-			leftop = (Node *) linitial(saop->args);
-			rightop = (Node *) lsecond(saop->args);
-			clause_op = saop->opno;
-			found_saop = true;
-		}
-		else if (IsA(clause, NullTest))
-		{
-			NullTest   *nt = (NullTest *) clause;
+				leftop = (Node *) linitial(rc->largs);
+				rightop = (Node *) linitial(rc->rargs);
+				clause_op = linitial_oid(rc->opnos);
+			}
+			else if (IsA(clause, ScalarArrayOpExpr))
+			{
+				ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
 
-			leftop = (Node *) nt->arg;
-			rightop = NULL;
-			clause_op = InvalidOid;
-			if (nt->nulltesttype == IS_NULL)
+				leftop = (Node *) linitial(saop->args);
+				rightop = (Node *) lsecond(saop->args);
+				clause_op = saop->opno;
+				found_saop = true;
+			}
+			else if (IsA(clause, NullTest))
 			{
-				found_is_null_op = true;
-				is_null_op = true;
+				NullTest   *nt = (NullTest *) clause;
+
+				leftop = (Node *) nt->arg;
+				rightop = NULL;
+				clause_op = InvalidOid;
+				if (nt->nulltesttype == IS_NULL)
+				{
+					found_is_null_op = true;
+					is_null_op = true;
+				}
 			}
-		}
-		else
-		{
-			elog(ERROR, "unsupported indexqual type: %d",
-				 (int) nodeTag(clause));
-			continue;			/* keep compiler quiet */
-		}
-		if (match_index_to_operand(leftop, indexcol, index))
-		{
-			/* clause_op is correct */
-		}
-		else if (match_index_to_operand(rightop, indexcol, index))
-		{
-			/* Must flip operator to get the opfamily member */
-			clause_op = get_commutator(clause_op);
-		}
-		else
-		{
-			/* Must be past the end of quals for indexcol, try next */
-			if (!eqQualHere)
-				break;			/* done if no '=' qual for indexcol */
-			indexcol++;
-			eqQualHere = false;
+			else
+			{
+				elog(ERROR, "unsupported indexqual type: %d",
+					 (int) nodeTag(clause));
+				continue;			/* keep compiler quiet */
+			}
+
 			if (match_index_to_operand(leftop, indexcol, index))
 			{
 				/* clause_op is correct */
 			}
-			else if (match_index_to_operand(rightop, indexcol, index))
+			else
 			{
+				Assert(match_index_to_operand(rightop, indexcol, index));
 				/* Must flip operator to get the opfamily member */
 				clause_op = get_commutator(clause_op);
 			}
-			else
+
+			/* check for equality operator */
+			if (OidIsValid(clause_op))
 			{
-				/* No quals for new indexcol, so we are done */
-				break;
-			}
-		}
-		/* check for equality operator */
-		if (OidIsValid(clause_op))
-		{
-			op_strategy = get_op_opfamily_strategy(clause_op,
+				op_strategy = get_op_opfamily_strategy(clause_op,
 												   index->opfamily[indexcol]);
-			Assert(op_strategy != 0);	/* not a member of opfamily?? */
-			if (op_strategy == BTEqualStrategyNumber)
+				Assert(op_strategy != 0);	/* not a member of opfamily?? */
+				if (op_strategy == BTEqualStrategyNumber)
+					eqQualHere = true;
+			}
+			else if (is_null_op)
+			{
+				/* IS NULL is like = for selectivity determination */
 				eqQualHere = true;
-		}
-		else if (is_null_op)
-		{
-			/* IS NULL is like = for purposes of selectivity determination */
-			eqQualHere = true;
-		}
-		/* count up number of SA scans induced by indexBoundQuals only */
-		if (IsA(clause, ScalarArrayOpExpr))
-		{
-			ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
-			int			alength = estimate_array_length(lsecond(saop->args));
+			}
+			/* count up number of SA scans induced by indexBoundQuals only */
+			if (IsA(clause, ScalarArrayOpExpr))
+			{
+				ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
+				int		alength = estimate_array_length(lsecond(saop->args));
 
-			if (alength > 1)
-				num_sa_scans *= alength;
+				if (alength > 1)
+					num_sa_scans *= alength;
+			}
+			indexBoundQuals = lappend(indexBoundQuals, rinfo);
 		}
-		indexBoundQuals = lappend(indexBoundQuals, rinfo);
+
+		/* Done with this indexcol, continue to next only if it had = qual */
+		if (!eqQualHere)
+			break;
+
+		indexcol++;
 	}
 
 	/*
@@ -6393,7 +6399,7 @@ btcostestimate(PG_FUNCTION_ARGS)
 	 * NullTest invalidates that theory, even though it sets eqQualHere.
 	 */
 	if (index->unique &&
-		indexcol == index->ncolumns - 1 &&
+		indexcol == index->ncolumns &&
 		eqQualHere &&
 		!found_saop &&
 		!found_is_null_op)
@@ -6924,6 +6930,14 @@ gincostestimate(PG_FUNCTION_ARGS)
 	Relation	indexRel;
 	GinStatsData ginStats;
 
+	/*
+	 * For our purposes here, it doesn't matter which index columns the
+	 * individual quals and order-by expressions go with, so flatten the
+	 * lists for convenience.
+	 */
+	indexQuals = flatten_clausegroups_list(indexQuals);
+	indexOrderBys = flatten_indexorderbys_list(indexOrderBys);
+
 	/*
 	 * Obtain statistic information from the meta page
 	 */
@@ -6980,7 +6994,7 @@ gincostestimate(PG_FUNCTION_ARGS)
 			if (!predicate_implied_by(oneQual, indexQuals))
 				predExtraQuals = list_concat(predExtraQuals, oneQual);
 		}
-		/* list_concat avoids modifying the passed-in indexQuals list */
+		/* list_concat avoids modifying the indexQuals list */
 		selectivityQuals = list_concat(predExtraQuals, indexQuals);
 	}
 	else
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 74c060b9b66e937b8e0adda147b02a797fc2be75..b137142f3eac08b4a3112b9ae179f3e2711c829d 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -659,18 +659,25 @@ typedef struct Path
  * AND semantics across the list.  Each clause is a RestrictInfo node from
  * the query's WHERE or JOIN conditions.
  *
- * 'indexquals' has the same structure as 'indexclauses', but it contains
- * the actual indexqual conditions that can be used with the index.
- * In simple cases this is identical to 'indexclauses', but when special
- * indexable operators appear in 'indexclauses', they are replaced by the
- * derived indexscannable conditions in 'indexquals'.
- *
- * 'indexorderbys', if not NIL, is a list of ORDER BY expressions that have
- * been found to be usable as ordering operators for an amcanorderbyop index.
- * Note that these are not RestrictInfos, just bare expressions, since they
- * generally won't yield booleans.  The list will match the path's pathkeys.
- * Also, unlike the case for quals, it's guaranteed that each expression has
- * the index key on the left side of the operator.
+ * 'indexquals' is a list of sub-lists of the actual index qual conditions
+ * that can be used with the index.  There is one possibly-empty sub-list
+ * for each index column (but empty sub-lists for trailing columns can be
+ * omitted).  The qual conditions are RestrictInfos, and in simple cases
+ * are the same RestrictInfos that appear in the flat indexclauses list.
+ * But when special indexable operators appear in 'indexclauses', they are
+ * replaced by their derived indexscannable conditions in 'indexquals'.
+ * Note that an entirely empty indexquals list denotes a full-index scan.
+ *
+ * 'indexorderbys', if not NIL, is a list of lists of lists of ORDER BY
+ * expressions that have been found to be usable as ordering operators for an
+ * amcanorderbyop index.  These are not RestrictInfos, just bare expressions,
+ * since they generally won't yield booleans.  Also, unlike the case for
+ * quals, it's guaranteed that each expression has the index key on the left
+ * side of the operator.  The top list has one entry per pathkey in the
+ * path's pathkeys, and the sub-lists have one sub-sublist per index column.
+ * This representation is a bit of overkill, since there will be only one
+ * actual expression per pathkey, but it's convenient because each sub-list
+ * has the same structure as the indexquals list.
  *
  * 'isjoininner' is TRUE if the path is a nestloop inner scan (that is,
  * some of the index conditions are join rather than restriction clauses).
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index c62f4a8122a993a52f5373e4a4e61c013a3818a8..b0075d786540364913ea31cedfee419018d7cea7 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -61,6 +61,12 @@ extern List *expand_indexqual_conditions(IndexOptInfo *index,
 							List *clausegroups);
 extern void check_partial_indexes(PlannerInfo *root, RelOptInfo *rel);
 extern List *flatten_clausegroups_list(List *clausegroups);
+extern List *flatten_indexorderbys_list(List *indexorderbys);
+extern Expr *adjust_rowcompare_for_index(RowCompareExpr *clause,
+							IndexOptInfo *index,
+							int indexcol,
+							List **indexcolnos,
+							bool *var_on_left_p);
 
 /*
  * orindxpath.c
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index 36198b8edd56a417c176a1da45ee416e79ee9088..18457e0ad92f4367f226c3b8fbb2b7988b7ef71b 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -2459,3 +2459,27 @@ RESET enable_seqscan;
 RESET enable_indexscan;
 RESET enable_bitmapscan;
 DROP TABLE onek_with_null;
+--
+-- Check behavior with duplicate index column contents
+--
+CREATE TABLE dupindexcols AS
+  SELECT unique1 as id, stringu2::text as f1 FROM tenk1;
+CREATE INDEX dupindexcols_i ON dupindexcols (f1, id, f1 text_pattern_ops);
+VACUUM ANALYZE dupindexcols;
+EXPLAIN (COSTS OFF)
+  SELECT count(*) FROM dupindexcols
+    WHERE f1 > 'LX' and id < 1000 and f1 ~<~ 'YX';
+                                   QUERY PLAN                                    
+---------------------------------------------------------------------------------
+ Aggregate
+   ->  Index Only Scan using dupindexcols_i on dupindexcols
+         Index Cond: ((f1 > 'LX'::text) AND (id < 1000) AND (f1 ~<~ 'YX'::text))
+(3 rows)
+
+SELECT count(*) FROM dupindexcols
+  WHERE f1 > 'LX' and id < 1000 and f1 ~<~ 'YX';
+ count 
+-------
+   500
+(1 row)
+
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 9cae9d8bf100b5f5a878fab134db181d3097dc6b..05ac11cfb0cbaee50ab0d534629c13287922647e 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -39,6 +39,7 @@ SELECT relname, relhasindex
  default_tbl             | f
  defaultexpr_tbl         | f
  dept                    | f
+ dupindexcols            | t
  e_star                  | f
  emp                     | f
  equipment_r             | f
@@ -164,7 +165,7 @@ SELECT relname, relhasindex
  timetz_tbl              | f
  tinterval_tbl           | f
  varchar_tbl             | f
-(153 rows)
+(154 rows)
 
 --
 -- another sanity check: every system catalog that has OIDs should have
diff --git a/src/test/regress/output/misc.source b/src/test/regress/output/misc.source
index b57c5546ded01c5ce2eda077f43d6ea88b329477..03aa10df083695e27c25ae5cb925af3b2c1c98d4 100644
--- a/src/test/regress/output/misc.source
+++ b/src/test/regress/output/misc.source
@@ -610,6 +610,7 @@ SELECT user_relns() AS user_relns
  default_tbl
  defaultexpr_tbl
  dept
+ dupindexcols
  e_star
  emp
  equipment_r
@@ -685,7 +686,7 @@ SELECT user_relns() AS user_relns
  toyemp
  varchar_tbl
  xacttest
-(107 rows)
+(108 rows)
 
 SELECT name(equipment(hobby_construct(text 'skywalking', text 'mer')));
  name 
diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql
index babde51d2c35ac60c83c055c1064491616e828b3..8c60cb614595aae0a7e7fdbf3bfd2345989db82c 100644
--- a/src/test/regress/sql/create_index.sql
+++ b/src/test/regress/sql/create_index.sql
@@ -804,3 +804,18 @@ RESET enable_indexscan;
 RESET enable_bitmapscan;
 
 DROP TABLE onek_with_null;
+
+--
+-- Check behavior with duplicate index column contents
+--
+
+CREATE TABLE dupindexcols AS
+  SELECT unique1 as id, stringu2::text as f1 FROM tenk1;
+CREATE INDEX dupindexcols_i ON dupindexcols (f1, id, f1 text_pattern_ops);
+VACUUM ANALYZE dupindexcols;
+
+EXPLAIN (COSTS OFF)
+  SELECT count(*) FROM dupindexcols
+    WHERE f1 > 'LX' and id < 1000 and f1 ~<~ 'YX';
+SELECT count(*) FROM dupindexcols
+  WHERE f1 > 'LX' and id < 1000 and f1 ~<~ 'YX';