diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index b7aff37..e5dd58e 100644
*** a/src/backend/optimizer/path/equivclass.c
--- b/src/backend/optimizer/path/equivclass.c
*************** static List *generate_join_implied_equal
*** 48,54 ****
  										Relids nominal_join_relids,
  										Relids outer_relids,
  										Relids nominal_inner_relids,
! 										AppendRelInfo *inner_appinfo);
  static Oid select_equality_operator(EquivalenceClass *ec,
  						 Oid lefttype, Oid righttype);
  static RestrictInfo *create_join_clause(PlannerInfo *root,
--- 48,54 ----
  										Relids nominal_join_relids,
  										Relids outer_relids,
  										Relids nominal_inner_relids,
! 										RelOptInfo *inner_rel);
  static Oid select_equality_operator(EquivalenceClass *ec,
  						 Oid lefttype, Oid righttype);
  static RestrictInfo *create_join_clause(PlannerInfo *root,
*************** generate_join_implied_equalities(Planner
*** 1000,1021 ****
  	Relids		inner_relids = inner_rel->relids;
  	Relids		nominal_inner_relids;
  	Relids		nominal_join_relids;
- 	AppendRelInfo *inner_appinfo;
  	ListCell   *lc;
  
  	/* If inner rel is a child, extra setup work is needed */
  	if (inner_rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
  	{
! 		/* Lookup parent->child translation data */
! 		inner_appinfo = find_childrel_appendrelinfo(root, inner_rel);
! 		/* Construct relids for the parent rel */
! 		nominal_inner_relids = bms_make_singleton(inner_appinfo->parent_relid);
  		/* ECs will be marked with the parent's relid, not the child's */
  		nominal_join_relids = bms_union(outer_relids, nominal_inner_relids);
  	}
  	else
  	{
- 		inner_appinfo = NULL;
  		nominal_inner_relids = inner_relids;
  		nominal_join_relids = join_relids;
  	}
--- 1000,1017 ----
  	Relids		inner_relids = inner_rel->relids;
  	Relids		nominal_inner_relids;
  	Relids		nominal_join_relids;
  	ListCell   *lc;
  
  	/* If inner rel is a child, extra setup work is needed */
  	if (inner_rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
  	{
! 		/* Fetch relid set for the topmost parent rel */
! 		nominal_inner_relids = find_childrel_top_parent(root, inner_rel)->relids;
  		/* ECs will be marked with the parent's relid, not the child's */
  		nominal_join_relids = bms_union(outer_relids, nominal_inner_relids);
  	}
  	else
  	{
  		nominal_inner_relids = inner_relids;
  		nominal_join_relids = join_relids;
  	}
*************** generate_join_implied_equalities(Planner
*** 1051,1057 ****
  														 nominal_join_relids,
  															  outer_relids,
  														nominal_inner_relids,
! 															  inner_appinfo);
  
  		result = list_concat(result, sublist);
  	}
--- 1047,1053 ----
  														 nominal_join_relids,
  															  outer_relids,
  														nominal_inner_relids,
! 															  inner_rel);
  
  		result = list_concat(result, sublist);
  	}
*************** generate_join_implied_equalities_broken(
*** 1244,1250 ****
  										Relids nominal_join_relids,
  										Relids outer_relids,
  										Relids nominal_inner_relids,
! 										AppendRelInfo *inner_appinfo)
  {
  	List	   *result = NIL;
  	ListCell   *lc;
--- 1240,1246 ----
  										Relids nominal_join_relids,
  										Relids outer_relids,
  										Relids nominal_inner_relids,
! 										RelOptInfo *inner_rel)
  {
  	List	   *result = NIL;
  	ListCell   *lc;
*************** generate_join_implied_equalities_broken(
*** 1266,1275 ****
  	 * RestrictInfos that are not listed in ec_derives, but there shouldn't be
  	 * any duplication, and it's a sufficiently narrow corner case that we
  	 * shouldn't sweat too much over it anyway.
  	 */
! 	if (inner_appinfo)
! 		result = (List *) adjust_appendrel_attrs(root, (Node *) result,
! 												 inner_appinfo);
  
  	return result;
  }
--- 1262,1277 ----
  	 * RestrictInfos that are not listed in ec_derives, but there shouldn't be
  	 * any duplication, and it's a sufficiently narrow corner case that we
  	 * shouldn't sweat too much over it anyway.
+ 	 *
+ 	 * Since inner_rel might be an indirect descendant of the baserel
+ 	 * mentioned in the ec_sources clauses, we have to be prepared to apply
+ 	 * multiple levels of Var translation.
  	 */
! 	if (inner_rel->reloptkind == RELOPT_OTHER_MEMBER_REL &&
! 		result != NIL)
! 		result = (List *) adjust_appendrel_attrs_multilevel(root,
! 															(Node *) result,
! 															inner_rel);
  
  	return result;
  }
*************** generate_implied_equalities_for_column(P
*** 2071,2084 ****
  {
  	List	   *result = NIL;
  	bool		is_child_rel = (rel->reloptkind == RELOPT_OTHER_MEMBER_REL);
! 	Index		parent_relid;
  	ListCell   *lc1;
  
! 	/* If it's a child rel, we'll need to know what its parent is */
  	if (is_child_rel)
! 		parent_relid = find_childrel_appendrelinfo(root, rel)->parent_relid;
  	else
! 		parent_relid = 0;		/* not used, but keep compiler quiet */
  
  	foreach(lc1, root->eq_classes)
  	{
--- 2073,2086 ----
  {
  	List	   *result = NIL;
  	bool		is_child_rel = (rel->reloptkind == RELOPT_OTHER_MEMBER_REL);
! 	Relids		parent_relids;
  	ListCell   *lc1;
  
! 	/* If it's a child rel, we'll need to know what its parent(s) are */
  	if (is_child_rel)
! 		parent_relids = find_childrel_parents(root, rel);
  	else
! 		parent_relids = NULL;	/* not used, but keep compiler quiet */
  
  	foreach(lc1, root->eq_classes)
  	{
*************** generate_implied_equalities_for_column(P
*** 2148,2157 ****
  
  			/*
  			 * Also, if this is a child rel, avoid generating a useless join
! 			 * to its parent rel.
  			 */
  			if (is_child_rel &&
! 				bms_is_member(parent_relid, other_em->em_relids))
  				continue;
  
  			eq_op = select_equality_operator(cur_ec,
--- 2150,2159 ----
  
  			/*
  			 * Also, if this is a child rel, avoid generating a useless join
! 			 * to its parent rel(s).
  			 */
  			if (is_child_rel &&
! 				bms_overlap(parent_relids, other_em->em_relids))
  				continue;
  
  			eq_op = select_equality_operator(cur_ec,
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 42dcb11..9c22d31 100644
*** a/src/backend/optimizer/path/indxpath.c
--- b/src/backend/optimizer/path/indxpath.c
*************** check_partial_indexes(PlannerInfo *root,
*** 2586,2601 ****
  	 * Add on any equivalence-derivable join clauses.  Computing the correct
  	 * relid sets for generate_join_implied_equalities is slightly tricky
  	 * because the rel could be a child rel rather than a true baserel, and in
! 	 * that case we must remove its parent's relid from all_baserels.
  	 */
  	if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
- 	{
- 		/* Lookup parent->child translation data */
- 		AppendRelInfo *appinfo = find_childrel_appendrelinfo(root, rel);
- 
  		otherrels = bms_difference(root->all_baserels,
! 								   bms_make_singleton(appinfo->parent_relid));
! 	}
  	else
  		otherrels = bms_difference(root->all_baserels, rel->relids);
  
--- 2586,2596 ----
  	 * Add on any equivalence-derivable join clauses.  Computing the correct
  	 * relid sets for generate_join_implied_equalities is slightly tricky
  	 * because the rel could be a child rel rather than a true baserel, and in
! 	 * that case we must remove its parents' relid(s) from all_baserels.
  	 */
  	if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
  		otherrels = bms_difference(root->all_baserels,
! 								   find_childrel_parents(root, rel));
  	else
  		otherrels = bms_difference(root->all_baserels, rel->relids);
  
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 0410fdd..58d5333 100644
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
*************** adjust_inherited_tlist(List *tlist, Appe
*** 1979,1981 ****
--- 1979,2002 ----
  
  	return new_tlist;
  }
+ 
+ /*
+  * adjust_appendrel_attrs_multilevel
+  *	  Apply Var translations from a toplevel appendrel parent down to a child.
+  *
+  * In some cases we need to translate expressions referencing a baserel
+  * to reference an appendrel child that's multiple levels removed from it.
+  */
+ Node *
+ adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
+ 								  RelOptInfo *child_rel)
+ {
+ 	AppendRelInfo *appinfo = find_childrel_appendrelinfo(root, child_rel);
+ 	RelOptInfo *parent_rel = find_base_rel(root, appinfo->parent_relid);
+ 
+ 	/* If parent is also a child, first recurse to apply its translations */
+ 	if (parent_rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
+ 		node = adjust_appendrel_attrs_multilevel(root, node, parent_rel);
+ 	/* Now translate for this child */
+ 	return adjust_appendrel_attrs(root, node, appinfo);
+ }
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index c938c27..2594827 100644
*** a/src/backend/optimizer/util/relnode.c
--- b/src/backend/optimizer/util/relnode.c
*************** build_empty_join_rel(PlannerInfo *root)
*** 713,719 ****
   *		Get the AppendRelInfo associated with an appendrel child rel.
   *
   * This search could be eliminated by storing a link in child RelOptInfos,
!  * but for now it doesn't seem performance-critical.
   */
  AppendRelInfo *
  find_childrel_appendrelinfo(PlannerInfo *root, RelOptInfo *rel)
--- 713,720 ----
   *		Get the AppendRelInfo associated with an appendrel child rel.
   *
   * This search could be eliminated by storing a link in child RelOptInfos,
!  * but for now it doesn't seem performance-critical.  (Also, it might be
!  * difficult to maintain such a link during mutation of the append_rel_list.)
   */
  AppendRelInfo *
  find_childrel_appendrelinfo(PlannerInfo *root, RelOptInfo *rel)
*************** find_childrel_appendrelinfo(PlannerInfo 
*** 738,743 ****
--- 739,796 ----
  
  
  /*
+  * find_childrel_top_parent
+  *		Fetch the topmost appendrel parent rel of an appendrel child rel.
+  *
+  * Since appendrels can be nested, a child could have multiple levels of
+  * appendrel ancestors.  This function locates the topmost ancestor,
+  * which will be a regular baserel not an otherrel.
+  */
+ RelOptInfo *
+ find_childrel_top_parent(PlannerInfo *root, RelOptInfo *rel)
+ {
+ 	do
+ 	{
+ 		AppendRelInfo *appinfo = find_childrel_appendrelinfo(root, rel);
+ 		Index		prelid = appinfo->parent_relid;
+ 
+ 		/* traverse up to the parent rel, loop if it's also a child rel */
+ 		rel = find_base_rel(root, prelid);
+ 	} while (rel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ 
+ 	return rel;
+ }
+ 
+ 
+ /*
+  * find_childrel_parents
+  *		Compute the set of parent relids of an appendrel child rel.
+  *
+  * Since appendrels can be nested, a child could have multiple levels of
+  * appendrel ancestors.  This function computes a Relids set of all the
+  * parent relation IDs.
+  */
+ Relids
+ find_childrel_parents(PlannerInfo *root, RelOptInfo *rel)
+ {
+ 	Relids		result = NULL;
+ 
+ 	do
+ 	{
+ 		AppendRelInfo *appinfo = find_childrel_appendrelinfo(root, rel);
+ 		Index		prelid = appinfo->parent_relid;
+ 
+ 		result = bms_add_member(result, prelid);
+ 
+ 		/* traverse up to the parent rel, loop if it's also a child rel */
+ 		rel = find_base_rel(root, prelid);
+ 	} while (rel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ 
+ 	return result;
+ }
+ 
+ 
+ /*
   * get_baserel_parampathinfo
   *		Get the ParamPathInfo for a parameterized path for a base relation,
   *		constructing one if we don't have one already.
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index a0bcc82..26b17f5 100644
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
*************** extern RelOptInfo *build_join_rel(Planne
*** 145,150 ****
--- 145,152 ----
  extern RelOptInfo *build_empty_join_rel(PlannerInfo *root);
  extern AppendRelInfo *find_childrel_appendrelinfo(PlannerInfo *root,
  							RelOptInfo *rel);
+ extern RelOptInfo *find_childrel_top_parent(PlannerInfo *root, RelOptInfo *rel);
+ extern Relids find_childrel_parents(PlannerInfo *root, RelOptInfo *rel);
  extern ParamPathInfo *get_baserel_parampathinfo(PlannerInfo *root,
  						  RelOptInfo *baserel,
  						  Relids required_outer);
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
index f5fc7e8..1891f4d 100644
*** a/src/include/optimizer/prep.h
--- b/src/include/optimizer/prep.h
*************** extern void expand_inherited_tables(Plan
*** 58,61 ****
--- 58,64 ----
  extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node,
  					   AppendRelInfo *appinfo);
  
+ extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
+ 								  RelOptInfo *child_rel);
+ 
  #endif   /* PREP_H */
diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out
index ...b882242 .
*** a/src/test/regress/expected/equivclass.out
--- b/src/test/regress/expected/equivclass.out
***************
*** 0 ****
--- 1,296 ----
+ --
+ -- Tests for the planner's "equivalence class" mechanism
+ --
+ -- One thing that's not tested well during normal querying is the logic
+ -- for handling "broken" ECs.  This is because an EC can only become broken
+ -- if its underlying btree operator family doesn't include a complete set
+ -- of cross-type equality operators.  There are not (and should not be)
+ -- any such families built into Postgres; so we have to hack things up
+ -- to create one.  We do this by making two alias types that are really
+ -- int8 (so we need no new C code) and adding only some operators for them
+ -- into the standard integer_ops opfamily.
+ create type int8alias1;
+ create function int8alias1in(cstring) returns int8alias1
+   strict immutable language internal as 'int8in';
+ NOTICE:  return type int8alias1 is only a shell
+ create function int8alias1out(int8alias1) returns cstring
+   strict immutable language internal as 'int8out';
+ NOTICE:  argument type int8alias1 is only a shell
+ create type int8alias1 (
+     input = int8alias1in,
+     output = int8alias1out,
+     like = int8
+ );
+ create type int8alias2;
+ create function int8alias2in(cstring) returns int8alias2
+   strict immutable language internal as 'int8in';
+ NOTICE:  return type int8alias2 is only a shell
+ create function int8alias2out(int8alias2) returns cstring
+   strict immutable language internal as 'int8out';
+ NOTICE:  argument type int8alias2 is only a shell
+ create type int8alias2 (
+     input = int8alias2in,
+     output = int8alias2out,
+     like = int8
+ );
+ create cast (int8 as int8alias1) without function;
+ create cast (int8 as int8alias2) without function;
+ create cast (int8alias1 as int8) without function;
+ create cast (int8alias2 as int8) without function;
+ create function int8alias1eq(int8alias1, int8alias1) returns bool
+   strict immutable language internal as 'int8eq';
+ create operator = (
+     procedure = int8alias1eq,
+     leftarg = int8alias1, rightarg = int8alias1,
+     commutator = =,
+     restrict = eqsel, join = eqjoinsel,
+     merges
+ );
+ alter operator family integer_ops using btree add
+   operator 3 = (int8alias1, int8alias1);
+ create function int8alias2eq(int8alias2, int8alias2) returns bool
+   strict immutable language internal as 'int8eq';
+ create operator = (
+     procedure = int8alias2eq,
+     leftarg = int8alias2, rightarg = int8alias2,
+     commutator = =,
+     restrict = eqsel, join = eqjoinsel,
+     merges
+ );
+ alter operator family integer_ops using btree add
+   operator 3 = (int8alias2, int8alias2);
+ create function int8alias1eq(int8, int8alias1) returns bool
+   strict immutable language internal as 'int8eq';
+ create operator = (
+     procedure = int8alias1eq,
+     leftarg = int8, rightarg = int8alias1,
+     restrict = eqsel, join = eqjoinsel,
+     merges
+ );
+ alter operator family integer_ops using btree add
+   operator 3 = (int8, int8alias1);
+ create function int8alias1eq(int8alias1, int8alias2) returns bool
+   strict immutable language internal as 'int8eq';
+ create operator = (
+     procedure = int8alias1eq,
+     leftarg = int8alias1, rightarg = int8alias2,
+     restrict = eqsel, join = eqjoinsel,
+     merges
+ );
+ alter operator family integer_ops using btree add
+   operator 3 = (int8alias1, int8alias2);
+ create table ec0 (ff int8 primary key, f1 int8, f2 int8);
+ create table ec1 (ff int8 primary key, f1 int8alias1, f2 int8alias2);
+ create table ec2 (xf int8 primary key, x1 int8alias1, x2 int8alias2);
+ -- we didn't provide enough infrastructure for hashjoin and mergejoin plans
+ set enable_hashjoin = off;
+ set enable_mergejoin = off;
+ --
+ -- Note that for cases where there's a missing operator, we don't care so
+ -- much whether the plan is ideal as that we don't fail or generate an
+ -- outright incorrect plan.
+ --
+ explain (costs off)
+   select * from ec0 where ff = f1 and f1 = '42'::int8;
+             QUERY PLAN            
+ ----------------------------------
+  Index Scan using ec0_pkey on ec0
+    Index Cond: (ff = 42::bigint)
+    Filter: (f1 = 42::bigint)
+ (3 rows)
+ 
+ explain (costs off)
+   select * from ec0 where ff = f1 and f1 = '42'::int8alias1;
+               QUERY PLAN               
+ ---------------------------------------
+  Index Scan using ec0_pkey on ec0
+    Index Cond: (ff = '42'::int8alias1)
+    Filter: (f1 = '42'::int8alias1)
+ (3 rows)
+ 
+ explain (costs off)
+   select * from ec1 where ff = f1 and f1 = '42'::int8alias1;
+               QUERY PLAN               
+ ---------------------------------------
+  Index Scan using ec1_pkey on ec1
+    Index Cond: (ff = '42'::int8alias1)
+    Filter: (f1 = '42'::int8alias1)
+ (3 rows)
+ 
+ explain (costs off)
+   select * from ec1 where ff = f1 and f1 = '42'::int8alias2;
+                     QUERY PLAN                     
+ ---------------------------------------------------
+  Seq Scan on ec1
+    Filter: ((ff = f1) AND (f1 = '42'::int8alias2))
+ (2 rows)
+ 
+ explain (costs off)
+   select * from ec1, ec2 where ff = x1 and ff = '42'::int8;
+                           QUERY PLAN                           
+ ---------------------------------------------------------------
+  Nested Loop
+    Join Filter: (ec1.ff = ec2.x1)
+    ->  Index Scan using ec1_pkey on ec1
+          Index Cond: ((ff = 42::bigint) AND (ff = 42::bigint))
+    ->  Seq Scan on ec2
+ (5 rows)
+ 
+ explain (costs off)
+   select * from ec1, ec2 where ff = x1 and ff = '42'::int8alias1;
+                  QUERY PLAN                  
+ ---------------------------------------------
+  Nested Loop
+    ->  Index Scan using ec1_pkey on ec1
+          Index Cond: (ff = '42'::int8alias1)
+    ->  Seq Scan on ec2
+          Filter: (x1 = '42'::int8alias1)
+ (5 rows)
+ 
+ explain (costs off)
+   select * from ec1, ec2 where ff = x1 and '42'::int8 = x1;
+                QUERY PLAN               
+ ----------------------------------------
+  Nested Loop
+    Join Filter: (ec1.ff = ec2.x1)
+    ->  Index Scan using ec1_pkey on ec1
+          Index Cond: (ff = 42::bigint)
+    ->  Seq Scan on ec2
+          Filter: (42::bigint = x1)
+ (6 rows)
+ 
+ explain (costs off)
+   select * from ec1, ec2 where ff = x1 and x1 = '42'::int8alias1;
+                  QUERY PLAN                  
+ ---------------------------------------------
+  Nested Loop
+    ->  Index Scan using ec1_pkey on ec1
+          Index Cond: (ff = '42'::int8alias1)
+    ->  Seq Scan on ec2
+          Filter: (x1 = '42'::int8alias1)
+ (5 rows)
+ 
+ explain (costs off)
+   select * from ec1, ec2 where ff = x1 and x1 = '42'::int8alias2;
+                QUERY PLAN                
+ -----------------------------------------
+  Nested Loop
+    ->  Seq Scan on ec2
+          Filter: (x1 = '42'::int8alias2)
+    ->  Index Scan using ec1_pkey on ec1
+          Index Cond: (ff = ec2.x1)
+ (5 rows)
+ 
+ create unique index ec1_expr1 on ec1((ff + 1));
+ create unique index ec1_expr2 on ec1((ff + 2 + 1));
+ create unique index ec1_expr3 on ec1((ff + 3 + 1));
+ create unique index ec1_expr4 on ec1((ff + 4));
+ explain (costs off)
+   select * from ec1,
+     (select ff + 1 as x from
+        (select ff + 2 as ff from ec1
+         union all
+         select ff + 3 as ff from ec1) ss0
+      union all
+      select ff + 4 as x from ec1) as ss1
+   where ss1.x = ec1.f1 and ec1.ff = 42::int8;
+                      QUERY PLAN                      
+ -----------------------------------------------------
+  Nested Loop
+    ->  Index Scan using ec1_pkey on ec1
+          Index Cond: (ff = 42::bigint)
+    ->  Append
+          ->  Index Scan using ec1_expr2 on ec1 ec1_1
+                Index Cond: (((ff + 2) + 1) = ec1.f1)
+          ->  Index Scan using ec1_expr3 on ec1 ec1_2
+                Index Cond: (((ff + 3) + 1) = ec1.f1)
+          ->  Index Scan using ec1_expr4 on ec1 ec1_3
+                Index Cond: ((ff + 4) = ec1.f1)
+ (10 rows)
+ 
+ explain (costs off)
+   select * from ec1,
+     (select ff + 1 as x from
+        (select ff + 2 as ff from ec1
+         union all
+         select ff + 3 as ff from ec1) ss0
+      union all
+      select ff + 4 as x from ec1) as ss1
+   where ss1.x = ec1.f1 and ec1.ff = 42::int8 and ec1.ff = ec1.f1;
+                           QUERY PLAN                           
+ ---------------------------------------------------------------
+  Nested Loop
+    Join Filter: ((((ec1_1.ff + 2) + 1)) = ec1.f1)
+    ->  Index Scan using ec1_pkey on ec1
+          Index Cond: ((ff = 42::bigint) AND (ff = 42::bigint))
+          Filter: (ff = f1)
+    ->  Append
+          ->  Index Scan using ec1_expr2 on ec1 ec1_1
+                Index Cond: (((ff + 2) + 1) = 42::bigint)
+          ->  Index Scan using ec1_expr3 on ec1 ec1_2
+                Index Cond: (((ff + 3) + 1) = 42::bigint)
+          ->  Index Scan using ec1_expr4 on ec1 ec1_3
+                Index Cond: ((ff + 4) = 42::bigint)
+ (12 rows)
+ 
+ explain (costs off)
+   select * from ec1,
+     (select ff + 1 as x from
+        (select ff + 2 as ff from ec1
+         union all
+         select ff + 3 as ff from ec1) ss0
+      union all
+      select ff + 4 as x from ec1) as ss1,
+     (select ff + 1 as x from
+        (select ff + 2 as ff from ec1
+         union all
+         select ff + 3 as ff from ec1) ss0
+      union all
+      select ff + 4 as x from ec1) as ss2
+   where ss1.x = ec1.f1 and ss1.x = ss2.x and ec1.ff = 42::int8;
+                              QUERY PLAN                              
+ ---------------------------------------------------------------------
+  Nested Loop
+    ->  Nested Loop
+          ->  Index Scan using ec1_pkey on ec1
+                Index Cond: (ff = 42::bigint)
+          ->  Append
+                ->  Index Scan using ec1_expr2 on ec1 ec1_1
+                      Index Cond: (((ff + 2) + 1) = ec1.f1)
+                ->  Index Scan using ec1_expr3 on ec1 ec1_2
+                      Index Cond: (((ff + 3) + 1) = ec1.f1)
+                ->  Index Scan using ec1_expr4 on ec1 ec1_3
+                      Index Cond: ((ff + 4) = ec1.f1)
+    ->  Append
+          ->  Index Scan using ec1_expr2 on ec1 ec1_4
+                Index Cond: (((ff + 2) + 1) = (((ec1_1.ff + 2) + 1)))
+          ->  Index Scan using ec1_expr3 on ec1 ec1_5
+                Index Cond: (((ff + 3) + 1) = (((ec1_1.ff + 2) + 1)))
+          ->  Index Scan using ec1_expr4 on ec1 ec1_6
+                Index Cond: ((ff + 4) = (((ec1_1.ff + 2) + 1)))
+ (18 rows)
+ 
+ drop index ec1_expr3;
+ explain (costs off)
+   select * from ec1,
+     (select ff + 1 as x from
+        (select ff + 2 as ff from ec1
+         union all
+         select ff + 3 as ff from ec1) ss0
+      union all
+      select ff + 4 as x from ec1) as ss1
+   where ss1.x = ec1.f1 and ec1.ff = 42::int8;
+                      QUERY PLAN                      
+ -----------------------------------------------------
+  Nested Loop
+    ->  Index Scan using ec1_pkey on ec1
+          Index Cond: (ff = 42::bigint)
+    ->  Append
+          ->  Index Scan using ec1_expr2 on ec1 ec1_1
+                Index Cond: (((ff + 2) + 1) = ec1.f1)
+          ->  Seq Scan on ec1 ec1_2
+                Filter: (((ff + 3) + 1) = ec1.f1)
+          ->  Index Scan using ec1_expr4 on ec1 ec1_3
+                Index Cond: ((ff + 4) = ec1.f1)
+ (10 rows)
+ 
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index ab6c4e2..9902dbe 100644
*** a/src/test/regress/parallel_schedule
--- b/src/test/regress/parallel_schedule
*************** test: event_trigger
*** 98,104 ****
  # ----------
  # Another group of parallel tests
  # ----------
! test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb indirect_toast
  # ----------
  # Another group of parallel tests
  # NB: temp.sql does a reconnect which transiently uses 2 connections,
--- 98,104 ----
  # ----------
  # Another group of parallel tests
  # ----------
! test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb indirect_toast equivclass
  # ----------
  # Another group of parallel tests
  # NB: temp.sql does a reconnect which transiently uses 2 connections,
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 5ed2bf0..2902a05 100644
*** a/src/test/regress/serial_schedule
--- b/src/test/regress/serial_schedule
*************** test: advisory_lock
*** 126,131 ****
--- 126,132 ----
  test: json
  test: jsonb
  test: indirect_toast
+ test: equivclass
  test: plancache
  test: limit
  test: plpgsql
diff --git a/src/test/regress/sql/equivclass.sql b/src/test/regress/sql/equivclass.sql
index ...fc775a5 .
*** a/src/test/regress/sql/equivclass.sql
--- b/src/test/regress/sql/equivclass.sql
***************
*** 0 ****
--- 1,172 ----
+ --
+ -- Tests for the planner's "equivalence class" mechanism
+ --
+ 
+ -- One thing that's not tested well during normal querying is the logic
+ -- for handling "broken" ECs.  This is because an EC can only become broken
+ -- if its underlying btree operator family doesn't include a complete set
+ -- of cross-type equality operators.  There are not (and should not be)
+ -- any such families built into Postgres; so we have to hack things up
+ -- to create one.  We do this by making two alias types that are really
+ -- int8 (so we need no new C code) and adding only some operators for them
+ -- into the standard integer_ops opfamily.
+ 
+ create type int8alias1;
+ create function int8alias1in(cstring) returns int8alias1
+   strict immutable language internal as 'int8in';
+ create function int8alias1out(int8alias1) returns cstring
+   strict immutable language internal as 'int8out';
+ create type int8alias1 (
+     input = int8alias1in,
+     output = int8alias1out,
+     like = int8
+ );
+ 
+ create type int8alias2;
+ create function int8alias2in(cstring) returns int8alias2
+   strict immutable language internal as 'int8in';
+ create function int8alias2out(int8alias2) returns cstring
+   strict immutable language internal as 'int8out';
+ create type int8alias2 (
+     input = int8alias2in,
+     output = int8alias2out,
+     like = int8
+ );
+ 
+ create cast (int8 as int8alias1) without function;
+ create cast (int8 as int8alias2) without function;
+ create cast (int8alias1 as int8) without function;
+ create cast (int8alias2 as int8) without function;
+ 
+ create function int8alias1eq(int8alias1, int8alias1) returns bool
+   strict immutable language internal as 'int8eq';
+ create operator = (
+     procedure = int8alias1eq,
+     leftarg = int8alias1, rightarg = int8alias1,
+     commutator = =,
+     restrict = eqsel, join = eqjoinsel,
+     merges
+ );
+ alter operator family integer_ops using btree add
+   operator 3 = (int8alias1, int8alias1);
+ 
+ create function int8alias2eq(int8alias2, int8alias2) returns bool
+   strict immutable language internal as 'int8eq';
+ create operator = (
+     procedure = int8alias2eq,
+     leftarg = int8alias2, rightarg = int8alias2,
+     commutator = =,
+     restrict = eqsel, join = eqjoinsel,
+     merges
+ );
+ alter operator family integer_ops using btree add
+   operator 3 = (int8alias2, int8alias2);
+ 
+ create function int8alias1eq(int8, int8alias1) returns bool
+   strict immutable language internal as 'int8eq';
+ create operator = (
+     procedure = int8alias1eq,
+     leftarg = int8, rightarg = int8alias1,
+     restrict = eqsel, join = eqjoinsel,
+     merges
+ );
+ alter operator family integer_ops using btree add
+   operator 3 = (int8, int8alias1);
+ 
+ create function int8alias1eq(int8alias1, int8alias2) returns bool
+   strict immutable language internal as 'int8eq';
+ create operator = (
+     procedure = int8alias1eq,
+     leftarg = int8alias1, rightarg = int8alias2,
+     restrict = eqsel, join = eqjoinsel,
+     merges
+ );
+ alter operator family integer_ops using btree add
+   operator 3 = (int8alias1, int8alias2);
+ 
+ create table ec0 (ff int8 primary key, f1 int8, f2 int8);
+ create table ec1 (ff int8 primary key, f1 int8alias1, f2 int8alias2);
+ create table ec2 (xf int8 primary key, x1 int8alias1, x2 int8alias2);
+ 
+ -- we didn't provide enough infrastructure for hashjoin and mergejoin plans
+ set enable_hashjoin = off;
+ set enable_mergejoin = off;
+ 
+ --
+ -- Note that for cases where there's a missing operator, we don't care so
+ -- much whether the plan is ideal as that we don't fail or generate an
+ -- outright incorrect plan.
+ --
+ 
+ explain (costs off)
+   select * from ec0 where ff = f1 and f1 = '42'::int8;
+ explain (costs off)
+   select * from ec0 where ff = f1 and f1 = '42'::int8alias1;
+ explain (costs off)
+   select * from ec1 where ff = f1 and f1 = '42'::int8alias1;
+ explain (costs off)
+   select * from ec1 where ff = f1 and f1 = '42'::int8alias2;
+ 
+ explain (costs off)
+   select * from ec1, ec2 where ff = x1 and ff = '42'::int8;
+ explain (costs off)
+   select * from ec1, ec2 where ff = x1 and ff = '42'::int8alias1;
+ explain (costs off)
+   select * from ec1, ec2 where ff = x1 and '42'::int8 = x1;
+ explain (costs off)
+   select * from ec1, ec2 where ff = x1 and x1 = '42'::int8alias1;
+ explain (costs off)
+   select * from ec1, ec2 where ff = x1 and x1 = '42'::int8alias2;
+ 
+ create unique index ec1_expr1 on ec1((ff + 1));
+ create unique index ec1_expr2 on ec1((ff + 2 + 1));
+ create unique index ec1_expr3 on ec1((ff + 3 + 1));
+ create unique index ec1_expr4 on ec1((ff + 4));
+ 
+ explain (costs off)
+   select * from ec1,
+     (select ff + 1 as x from
+        (select ff + 2 as ff from ec1
+         union all
+         select ff + 3 as ff from ec1) ss0
+      union all
+      select ff + 4 as x from ec1) as ss1
+   where ss1.x = ec1.f1 and ec1.ff = 42::int8;
+ 
+ explain (costs off)
+   select * from ec1,
+     (select ff + 1 as x from
+        (select ff + 2 as ff from ec1
+         union all
+         select ff + 3 as ff from ec1) ss0
+      union all
+      select ff + 4 as x from ec1) as ss1
+   where ss1.x = ec1.f1 and ec1.ff = 42::int8 and ec1.ff = ec1.f1;
+ 
+ explain (costs off)
+   select * from ec1,
+     (select ff + 1 as x from
+        (select ff + 2 as ff from ec1
+         union all
+         select ff + 3 as ff from ec1) ss0
+      union all
+      select ff + 4 as x from ec1) as ss1,
+     (select ff + 1 as x from
+        (select ff + 2 as ff from ec1
+         union all
+         select ff + 3 as ff from ec1) ss0
+      union all
+      select ff + 4 as x from ec1) as ss2
+   where ss1.x = ec1.f1 and ss1.x = ss2.x and ec1.ff = 42::int8;
+ 
+ drop index ec1_expr3;
+ 
+ explain (costs off)
+   select * from ec1,
+     (select ff + 1 as x from
+        (select ff + 2 as ff from ec1
+         union all
+         select ff + 3 as ff from ec1) ss0
+      union all
+      select ff + 4 as x from ec1) as ss1
+   where ss1.x = ec1.f1 and ec1.ff = 42::int8;
