diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index a951c55..b8a68b5 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1151,9 +1151,16 @@ ExplainNode(PlanState *planstate, List *ancestors,
 						appendStringInfo(es->str, " %s Join", jointype);
 					else if (!IsA(plan, NestLoop))
 						appendStringInfoString(es->str, " Join");
+					if (((Join *)plan)->inner_unique)
+						appendStringInfoString(es->str, "(inner unique)");
+
 				}
 				else
+				{
 					ExplainPropertyText("Join Type", jointype, es);
+					ExplainPropertyText("Inner unique", 
+							((Join *)plan)->inner_unique?"true":"false", es);
+				}
 			}
 			break;
 		case T_SetOp:
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index 1d78cdf..d3b14e5 100644
--- a/src/backend/executor/nodeHashjoin.c
+++ b/src/backend/executor/nodeHashjoin.c
@@ -306,10 +306,11 @@ ExecHashJoin(HashJoinState *node)
 					}
 
 					/*
-					 * In a semijoin, we'll consider returning the first
-					 * match, but after that we're done with this outer tuple.
+					 * We'll consider returning the first match if the inner
+					 * is unique, but after that we're done with this outer
+					 * tuple.
 					 */
-					if (node->js.jointype == JOIN_SEMI)
+					if (node->js.inner_unique)
 						node->hj_JoinState = HJ_NEED_NEW_OUTER;
 
 					if (otherqual == NIL ||
@@ -451,6 +452,7 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags)
 	hjstate = makeNode(HashJoinState);
 	hjstate->js.ps.plan = (Plan *) node;
 	hjstate->js.ps.state = estate;
+	hjstate->js.inner_unique = node->join.inner_unique;
 
 	/*
 	 * Miscellaneous initialization
@@ -498,8 +500,10 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags)
 	/* set up null tuples for outer joins, if needed */
 	switch (node->join.jointype)
 	{
-		case JOIN_INNER:
 		case JOIN_SEMI:
+			hjstate->js.inner_unique = true;
+			/* fall through */
+		case JOIN_INNER:
 			break;
 		case JOIN_LEFT:
 		case JOIN_ANTI:
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index 15742c5..3c21ffe 100644
--- a/src/backend/executor/nodeMergejoin.c
+++ b/src/backend/executor/nodeMergejoin.c
@@ -840,10 +840,11 @@ ExecMergeJoin(MergeJoinState *node)
 					}
 
 					/*
-					 * In a semijoin, we'll consider returning the first
-					 * match, but after that we're done with this outer tuple.
+					 * We'll consider returning the first match if the inner
+					 * is unique, but after that we're done with this outer
+					 * tuple.
 					 */
-					if (node->js.jointype == JOIN_SEMI)
+					if (node->js.inner_unique)
 						node->mj_JoinState = EXEC_MJ_NEXTOUTER;
 
 					qualResult = (otherqual == NIL ||
@@ -1486,6 +1487,8 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
 	mergestate->js.ps.plan = (Plan *) node;
 	mergestate->js.ps.state = estate;
 
+	mergestate->js.inner_unique = node->join.inner_unique;
+
 	/*
 	 * Miscellaneous initialization
 	 *
@@ -1553,8 +1556,10 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
 
 	switch (node->join.jointype)
 	{
-		case JOIN_INNER:
 		case JOIN_SEMI:
+			mergestate->js.inner_unique = true;
+			/* fall through */
+		case JOIN_INNER:
 			mergestate->mj_FillOuter = false;
 			mergestate->mj_FillInner = false;
 			break;
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index e66bcda..342c448 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -247,10 +247,10 @@ ExecNestLoop(NestLoopState *node)
 			}
 
 			/*
-			 * In a semijoin, we'll consider returning the first match, but
-			 * after that we're done with this outer tuple.
+			 * We'll consider returning the first match if the inner is
+			 * unique, but after that we're done with this outer tuple.
 			 */
-			if (node->js.jointype == JOIN_SEMI)
+			if (node->js.inner_unique)
 				node->nl_NeedNewOuter = true;
 
 			if (otherqual == NIL || ExecQual(otherqual, econtext, false))
@@ -310,6 +310,8 @@ ExecInitNestLoop(NestLoop *node, EState *estate, int eflags)
 	nlstate->js.ps.plan = (Plan *) node;
 	nlstate->js.ps.state = estate;
 
+	nlstate->js.inner_unique = node->join.inner_unique;
+
 	/*
 	 * Miscellaneous initialization
 	 *
@@ -354,8 +356,10 @@ ExecInitNestLoop(NestLoop *node, EState *estate, int eflags)
 
 	switch (node->join.jointype)
 	{
-		case JOIN_INNER:
 		case JOIN_SEMI:
+			nlstate->js.inner_unique = true;
+			/* fall through */
+		case JOIN_INNER:
 			break;
 		case JOIN_LEFT:
 		case JOIN_ANTI:
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 1da953f..8363216 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -26,18 +26,21 @@
 	((path)->param_info && bms_overlap(PATH_REQ_OUTER(path), (rel)->relids))
 
 static void sort_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
-					 RelOptInfo *outerrel, RelOptInfo *innerrel,
+					 RelOptInfo *outerrel,
+					 RelOptInfo *innerrel, bool inner_unique,
 					 List *restrictlist, List *mergeclause_list,
 					 JoinType jointype, SpecialJoinInfo *sjinfo,
 					 Relids param_source_rels, Relids extra_lateral_rels);
 static void match_unsorted_outer(PlannerInfo *root, RelOptInfo *joinrel,
-					 RelOptInfo *outerrel, RelOptInfo *innerrel,
+					 RelOptInfo *outerrel,
+					 RelOptInfo *innerrel, bool inner_unique,
 					 List *restrictlist, List *mergeclause_list,
 					 JoinType jointype, SpecialJoinInfo *sjinfo,
 					 SemiAntiJoinFactors *semifactors,
 					 Relids param_source_rels, Relids extra_lateral_rels);
 static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
-					 RelOptInfo *outerrel, RelOptInfo *innerrel,
+					 RelOptInfo *outerrel,
+					 RelOptInfo *innerrel, bool inner_unique,
 					 List *restrictlist,
 					 JoinType jointype, SpecialJoinInfo *sjinfo,
 					 SemiAntiJoinFactors *semifactors,
@@ -49,7 +52,8 @@ static List *select_mergejoin_clauses(PlannerInfo *root,
 						 List *restrictlist,
 						 JoinType jointype,
 						 bool *mergejoin_allowed);
-
+static inline bool clause_sides_match_join(RestrictInfo *rinfo, RelOptInfo *outerrel,
+										   RelOptInfo *innerrel);
 
 /*
  * add_paths_to_joinrel
@@ -89,6 +93,7 @@ add_paths_to_joinrel(PlannerInfo *root,
 	Relids		param_source_rels = NULL;
 	Relids		extra_lateral_rels = NULL;
 	ListCell   *lc;
+	bool		inner_unique = false;
 
 	/*
 	 * Find potential mergejoin clauses.  We can skip this if we are not
@@ -115,6 +120,31 @@ add_paths_to_joinrel(PlannerInfo *root,
 									   &semifactors);
 
 	/*
+	 * We can optimize inner loop execution for joins on which the inner rel
+	 * is unique on the restrictlist.
+	 */
+	if (jointype == JOIN_INNER &&
+ 		innerrel->rtekind == RTE_RELATION &&
+		restrictlist)
+	{
+		/* relation_has_unique_index_for adds some restrictions */
+		int org_len = list_length(restrictlist);
+		ListCell *lc;
+
+		foreach (lc, restrictlist)
+		{
+			clause_sides_match_join((RestrictInfo *) lfirst(lc),
+									outerrel, innerrel);
+		}
+		if (relation_has_unique_index_for(root, innerrel, restrictlist,
+										  NIL, NIL))
+			inner_unique = true;
+
+		/* Remove restirictions added by the function */
+		list_truncate(restrictlist, org_len);
+	}
+
+	/*
 	 * Decide whether it's sensible to generate parameterized paths for this
 	 * joinrel, and if so, which relations such paths should require.  There
 	 * is usually no need to create a parameterized result path unless there
@@ -212,7 +242,7 @@ add_paths_to_joinrel(PlannerInfo *root,
 	 * sorted.  Skip this if we can't mergejoin.
 	 */
 	if (mergejoin_allowed)
-		sort_inner_and_outer(root, joinrel, outerrel, innerrel,
+		sort_inner_and_outer(root, joinrel, outerrel, innerrel, inner_unique,
 							 restrictlist, mergeclause_list, jointype,
 							 sjinfo,
 							 param_source_rels, extra_lateral_rels);
@@ -225,7 +255,7 @@ add_paths_to_joinrel(PlannerInfo *root,
 	 * joins at all, so it wouldn't work in the prohibited cases either.)
 	 */
 	if (mergejoin_allowed)
-		match_unsorted_outer(root, joinrel, outerrel, innerrel,
+		match_unsorted_outer(root, joinrel, outerrel, innerrel, inner_unique,
 							 restrictlist, mergeclause_list, jointype,
 							 sjinfo, &semifactors,
 							 param_source_rels, extra_lateral_rels);
@@ -256,7 +286,7 @@ add_paths_to_joinrel(PlannerInfo *root,
 	 * joins, because there may be no other alternative.
 	 */
 	if (enable_hashjoin || jointype == JOIN_FULL)
-		hash_inner_and_outer(root, joinrel, outerrel, innerrel,
+		hash_inner_and_outer(root, joinrel, outerrel, innerrel, inner_unique,
 							 restrictlist, jointype,
 							 sjinfo, &semifactors,
 							 param_source_rels, extra_lateral_rels);
@@ -277,6 +307,7 @@ try_nestloop_path(PlannerInfo *root,
 				  Relids extra_lateral_rels,
 				  Path *outer_path,
 				  Path *inner_path,
+				  bool  inner_unique,
 				  List *restrict_clauses,
 				  List *pathkeys)
 {
@@ -349,6 +380,7 @@ try_nestloop_path(PlannerInfo *root,
 									  semifactors,
 									  outer_path,
 									  inner_path,
+									  inner_unique,
 									  restrict_clauses,
 									  pathkeys,
 									  required_outer));
@@ -374,6 +406,7 @@ try_mergejoin_path(PlannerInfo *root,
 				   Relids extra_lateral_rels,
 				   Path *outer_path,
 				   Path *inner_path,
+				   bool	 inner_unique,
 				   List *restrict_clauses,
 				   List *pathkeys,
 				   List *mergeclauses,
@@ -434,6 +467,7 @@ try_mergejoin_path(PlannerInfo *root,
 									   sjinfo,
 									   outer_path,
 									   inner_path,
+									   inner_unique,
 									   restrict_clauses,
 									   pathkeys,
 									   required_outer,
@@ -463,6 +497,7 @@ try_hashjoin_path(PlannerInfo *root,
 				  Relids extra_lateral_rels,
 				  Path *outer_path,
 				  Path *inner_path,
+				  bool  inner_unique,
 				  List *restrict_clauses,
 				  List *hashclauses)
 {
@@ -510,6 +545,7 @@ try_hashjoin_path(PlannerInfo *root,
 									  semifactors,
 									  outer_path,
 									  inner_path,
+									  inner_unique,
 									  restrict_clauses,
 									  required_outer,
 									  hashclauses));
@@ -574,6 +610,7 @@ sort_inner_and_outer(PlannerInfo *root,
 					 RelOptInfo *joinrel,
 					 RelOptInfo *outerrel,
 					 RelOptInfo *innerrel,
+					 bool inner_unique,
 					 List *restrictlist,
 					 List *mergeclause_list,
 					 JoinType jointype,
@@ -629,6 +666,7 @@ sort_inner_and_outer(PlannerInfo *root,
 												 inner_path, sjinfo);
 		Assert(inner_path);
 		jointype = JOIN_INNER;
+		inner_unique = true;
 	}
 
 	/*
@@ -712,6 +750,7 @@ sort_inner_and_outer(PlannerInfo *root,
 						   extra_lateral_rels,
 						   outer_path,
 						   inner_path,
+						   inner_unique,
 						   restrictlist,
 						   merge_pathkeys,
 						   cur_mergeclauses,
@@ -762,6 +801,7 @@ match_unsorted_outer(PlannerInfo *root,
 					 RelOptInfo *joinrel,
 					 RelOptInfo *outerrel,
 					 RelOptInfo *innerrel,
+					 bool  inner_unique,
 					 List *restrictlist,
 					 List *mergeclause_list,
 					 JoinType jointype,
@@ -832,6 +872,7 @@ match_unsorted_outer(PlannerInfo *root,
 		inner_cheapest_total = (Path *)
 			create_unique_path(root, innerrel, inner_cheapest_total, sjinfo);
 		Assert(inner_cheapest_total);
+		inner_unique = true;
 	}
 	else if (nestjoinOK)
 	{
@@ -901,6 +942,7 @@ match_unsorted_outer(PlannerInfo *root,
 							  extra_lateral_rels,
 							  outerpath,
 							  inner_cheapest_total,
+							  inner_unique,
 							  restrictlist,
 							  merge_pathkeys);
 		}
@@ -927,6 +969,7 @@ match_unsorted_outer(PlannerInfo *root,
 								  extra_lateral_rels,
 								  outerpath,
 								  innerpath,
+								  inner_unique,
 								  restrictlist,
 								  merge_pathkeys);
 			}
@@ -942,6 +985,7 @@ match_unsorted_outer(PlannerInfo *root,
 								  extra_lateral_rels,
 								  outerpath,
 								  matpath,
+								  inner_unique,
 								  restrictlist,
 								  merge_pathkeys);
 		}
@@ -998,6 +1042,7 @@ match_unsorted_outer(PlannerInfo *root,
 						   extra_lateral_rels,
 						   outerpath,
 						   inner_cheapest_total,
+						   inner_unique,
 						   restrictlist,
 						   merge_pathkeys,
 						   mergeclauses,
@@ -1097,6 +1142,7 @@ match_unsorted_outer(PlannerInfo *root,
 								   extra_lateral_rels,
 								   outerpath,
 								   innerpath,
+								   inner_unique,
 								   restrictlist,
 								   merge_pathkeys,
 								   newclauses,
@@ -1143,6 +1189,7 @@ match_unsorted_outer(PlannerInfo *root,
 									   extra_lateral_rels,
 									   outerpath,
 									   innerpath,
+									   inner_unique,
 									   restrictlist,
 									   merge_pathkeys,
 									   newclauses,
@@ -1182,6 +1229,7 @@ hash_inner_and_outer(PlannerInfo *root,
 					 RelOptInfo *joinrel,
 					 RelOptInfo *outerrel,
 					 RelOptInfo *innerrel,
+					 bool inner_unique,
 					 List *restrictlist,
 					 JoinType jointype,
 					 SpecialJoinInfo *sjinfo,
@@ -1264,6 +1312,7 @@ hash_inner_and_outer(PlannerInfo *root,
 							  extra_lateral_rels,
 							  cheapest_total_outer,
 							  cheapest_total_inner,
+							  inner_unique,
 							  restrictlist,
 							  hashclauses);
 			/* no possibility of cheap startup here */
@@ -1284,6 +1333,7 @@ hash_inner_and_outer(PlannerInfo *root,
 							  extra_lateral_rels,
 							  cheapest_total_outer,
 							  cheapest_total_inner,
+							  true,
 							  restrictlist,
 							  hashclauses);
 			if (cheapest_startup_outer != NULL &&
@@ -1297,6 +1347,7 @@ hash_inner_and_outer(PlannerInfo *root,
 								  extra_lateral_rels,
 								  cheapest_startup_outer,
 								  cheapest_total_inner,
+								  true,
 								  restrictlist,
 								  hashclauses);
 		}
@@ -1322,6 +1373,7 @@ hash_inner_and_outer(PlannerInfo *root,
 								  extra_lateral_rels,
 								  cheapest_startup_outer,
 								  cheapest_total_inner,
+								  inner_unique,
 								  restrictlist,
 								  hashclauses);
 
@@ -1360,6 +1412,7 @@ hash_inner_and_outer(PlannerInfo *root,
 									  extra_lateral_rels,
 									  outerpath,
 									  innerpath,
+									  inner_unique,
 									  restrictlist,
 									  hashclauses);
 				}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index cb69c03..448c556 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -131,12 +131,12 @@ static BitmapAnd *make_bitmap_and(List *bitmapplans);
 static BitmapOr *make_bitmap_or(List *bitmapplans);
 static NestLoop *make_nestloop(List *tlist,
 			  List *joinclauses, List *otherclauses, List *nestParams,
-			  Plan *lefttree, Plan *righttree,
+			  Plan *lefttree, Plan *righttree, bool inner_unique,
 			  JoinType jointype);
 static HashJoin *make_hashjoin(List *tlist,
 			  List *joinclauses, List *otherclauses,
 			  List *hashclauses,
-			  Plan *lefttree, Plan *righttree,
+			  Plan *lefttree, Plan *righttree, bool inner_unique,
 			  JoinType jointype);
 static Hash *make_hash(Plan *lefttree,
 		  Oid skewTable,
@@ -151,7 +151,7 @@ static MergeJoin *make_mergejoin(List *tlist,
 			   Oid *mergecollations,
 			   int *mergestrategies,
 			   bool *mergenullsfirst,
-			   Plan *lefttree, Plan *righttree,
+			   Plan *lefttree, Plan *righttree, bool inner_unique,
 			   JoinType jointype);
 static Sort *make_sort(PlannerInfo *root, Plan *lefttree, int numCols,
 		  AttrNumber *sortColIdx, Oid *sortOperators,
@@ -2192,6 +2192,7 @@ create_nestloop_plan(PlannerInfo *root,
 							  nestParams,
 							  outer_plan,
 							  inner_plan,
+							  best_path->inner_unique,
 							  best_path->jointype);
 
 	copy_path_costsize(&join_plan->join.plan, &best_path->path);
@@ -2486,6 +2487,7 @@ create_mergejoin_plan(PlannerInfo *root,
 							   mergenullsfirst,
 							   outer_plan,
 							   inner_plan,
+							   best_path->jpath.inner_unique,
 							   best_path->jpath.jointype);
 
 	/* Costs of sort and material steps are included in path cost already */
@@ -2612,6 +2614,7 @@ create_hashjoin_plan(PlannerInfo *root,
 							  hashclauses,
 							  outer_plan,
 							  (Plan *) hash_plan,
+							  best_path->jpath.inner_unique,
 							  best_path->jpath.jointype);
 
 	copy_path_costsize(&join_plan->join.plan, &best_path->jpath.path);
@@ -3717,6 +3720,7 @@ make_nestloop(List *tlist,
 			  List *nestParams,
 			  Plan *lefttree,
 			  Plan *righttree,
+			  bool  inner_unique,
 			  JoinType jointype)
 {
 	NestLoop   *node = makeNode(NestLoop);
@@ -3729,6 +3733,7 @@ make_nestloop(List *tlist,
 	plan->righttree = righttree;
 	node->join.jointype = jointype;
 	node->join.joinqual = joinclauses;
+	node->join.inner_unique = inner_unique;
 	node->nestParams = nestParams;
 
 	return node;
@@ -3741,6 +3746,7 @@ make_hashjoin(List *tlist,
 			  List *hashclauses,
 			  Plan *lefttree,
 			  Plan *righttree,
+			  bool  inner_unique,
 			  JoinType jointype)
 {
 	HashJoin   *node = makeNode(HashJoin);
@@ -3754,6 +3760,7 @@ make_hashjoin(List *tlist,
 	node->hashclauses = hashclauses;
 	node->join.jointype = jointype;
 	node->join.joinqual = joinclauses;
+	node->join.inner_unique = inner_unique;
 
 	return node;
 }
@@ -3801,6 +3808,7 @@ make_mergejoin(List *tlist,
 			   bool *mergenullsfirst,
 			   Plan *lefttree,
 			   Plan *righttree,
+			   bool inner_unique,
 			   JoinType jointype)
 {
 	MergeJoin  *node = makeNode(MergeJoin);
@@ -3818,6 +3826,7 @@ make_mergejoin(List *tlist,
 	node->mergeNullsFirst = mergenullsfirst;
 	node->join.jointype = jointype;
 	node->join.joinqual = joinclauses;
+	node->join.inner_unique = inner_unique;
 
 	return node;
 }
@@ -4586,7 +4595,6 @@ make_unique(Plan *lefttree, List *distinctList)
 	node->numCols = numCols;
 	node->uniqColIdx = uniqColIdx;
 	node->uniqOperators = uniqOperators;
-
 	return node;
 }
 
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index faca30b..299a51d 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1135,8 +1135,8 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
 	 */
 	if (rel->rtekind == RTE_RELATION && sjinfo->semi_can_btree &&
 		relation_has_unique_index_for(root, rel, NIL,
-									  sjinfo->semi_rhs_exprs,
-									  sjinfo->semi_operators))
+									   sjinfo->semi_rhs_exprs,
+									   sjinfo->semi_operators))
 	{
 		pathnode->umethod = UNIQUE_PATH_NOOP;
 		pathnode->path.rows = rel->rows;
@@ -1534,6 +1534,7 @@ create_nestloop_path(PlannerInfo *root,
 					 SemiAntiJoinFactors *semifactors,
 					 Path *outer_path,
 					 Path *inner_path,
+                     bool  inner_unique,
 					 List *restrict_clauses,
 					 List *pathkeys,
 					 Relids required_outer)
@@ -1581,6 +1582,7 @@ create_nestloop_path(PlannerInfo *root,
 	pathnode->jointype = jointype;
 	pathnode->outerjoinpath = outer_path;
 	pathnode->innerjoinpath = inner_path;
+	pathnode->inner_unique = inner_unique;
 	pathnode->joinrestrictinfo = restrict_clauses;
 
 	final_cost_nestloop(root, pathnode, workspace, sjinfo, semifactors);
@@ -1615,6 +1617,7 @@ create_mergejoin_path(PlannerInfo *root,
 					  SpecialJoinInfo *sjinfo,
 					  Path *outer_path,
 					  Path *inner_path,
+					  bool  inner_unique,
 					  List *restrict_clauses,
 					  List *pathkeys,
 					  Relids required_outer,
@@ -1638,6 +1641,7 @@ create_mergejoin_path(PlannerInfo *root,
 	pathnode->jpath.jointype = jointype;
 	pathnode->jpath.outerjoinpath = outer_path;
 	pathnode->jpath.innerjoinpath = inner_path;
+	pathnode->jpath.inner_unique = inner_unique;
 	pathnode->jpath.joinrestrictinfo = restrict_clauses;
 	pathnode->path_mergeclauses = mergeclauses;
 	pathnode->outersortkeys = outersortkeys;
@@ -1674,6 +1678,7 @@ create_hashjoin_path(PlannerInfo *root,
 					 SemiAntiJoinFactors *semifactors,
 					 Path *outer_path,
 					 Path *inner_path,
+					 bool  inner_unique,
 					 List *restrict_clauses,
 					 Relids required_outer,
 					 List *hashclauses)
@@ -1706,6 +1711,7 @@ create_hashjoin_path(PlannerInfo *root,
 	pathnode->jpath.jointype = jointype;
 	pathnode->jpath.outerjoinpath = outer_path;
 	pathnode->jpath.innerjoinpath = inner_path;
+	pathnode->jpath.inner_unique = inner_unique;
 	pathnode->jpath.joinrestrictinfo = restrict_clauses;
 	pathnode->path_hashclauses = hashclauses;
 	/* final_cost_hashjoin will fill in pathnode->num_batches */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 59b17f3..f86f806 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1562,6 +1562,7 @@ typedef struct JoinState
 	PlanState	ps;
 	JoinType	jointype;
 	List	   *joinqual;		/* JOIN quals (in addition to ps.qual) */
+	bool		inner_unique;
 } JoinState;
 
 /* ----------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 21cbfa8..122f2f4 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -543,6 +543,7 @@ typedef struct Join
 	Plan		plan;
 	JoinType	jointype;
 	List	   *joinqual;		/* JOIN quals (in addition to plan.qual) */
+	bool		inner_unique;
 } Join;
 
 /* ----------------
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 334cf51..c1ebfdb 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -1030,6 +1030,7 @@ typedef struct JoinPath
 
 	Path	   *outerjoinpath;	/* path for the outer side of the join */
 	Path	   *innerjoinpath;	/* path for the inner side of the join */
+	bool		inner_unique;
 
 	List	   *joinrestrictinfo;		/* RestrictInfos to apply to join */
 
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 9923f0e..cefcecc 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -94,6 +94,7 @@ extern NestPath *create_nestloop_path(PlannerInfo *root,
 					 SemiAntiJoinFactors *semifactors,
 					 Path *outer_path,
 					 Path *inner_path,
+					 bool  inner_unique,
 					 List *restrict_clauses,
 					 List *pathkeys,
 					 Relids required_outer);
@@ -105,6 +106,7 @@ extern MergePath *create_mergejoin_path(PlannerInfo *root,
 					  SpecialJoinInfo *sjinfo,
 					  Path *outer_path,
 					  Path *inner_path,
+					  bool  inner_unique,
 					  List *restrict_clauses,
 					  List *pathkeys,
 					  Relids required_outer,
@@ -120,6 +122,7 @@ extern HashPath *create_hashjoin_path(PlannerInfo *root,
 					 SemiAntiJoinFactors *semifactors,
 					 Path *outer_path,
 					 Path *inner_path,
+					 bool  inner_unique,
 					 List *restrict_clauses,
 					 Relids required_outer,
 					 List *hashclauses);
diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out
index dfae84e..ad1d673 100644
--- a/src/test/regress/expected/equivclass.out
+++ b/src/test/regress/expected/equivclass.out
@@ -186,7 +186,7 @@ explain (costs off)
   select * from ec1, ec2 where ff = x1 and x1 = '42'::int8alias2;
                QUERY PLAN                
 -----------------------------------------
- Nested Loop
+ Nested Loop(inner unique)
    ->  Seq Scan on ec2
          Filter: (x1 = '42'::int8alias2)
    ->  Index Scan using ec1_pkey on ec1
@@ -310,7 +310,7 @@ explain (costs off)
          ->  Index Scan using ec1_expr3 on ec1 ec1_5
          ->  Index Scan using ec1_expr4 on ec1 ec1_6
    ->  Materialize
-         ->  Merge Join
+         ->  Merge Join(inner unique)
                Merge Cond: ((((ec1_1.ff + 2) + 1)) = ec1.f1)
                ->  Merge Append
                      Sort Key: (((ec1_1.ff + 2) + 1))
@@ -365,7 +365,7 @@ explain (costs off)
   where ss1.x = ec1.f1 and ec1.ff = 42::int8;
                      QUERY PLAN                      
 -----------------------------------------------------
- Merge Join
+ Merge Join(inner unique)
    Merge Cond: ((((ec1_1.ff + 2) + 1)) = ec1.f1)
    ->  Merge Append
          Sort Key: (((ec1_1.ff + 2) + 1))
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index 57fc910..b6e3024 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -2614,8 +2614,8 @@ from nt3 as nt3
 where nt3.id = 1 and ss2.b3;
                   QUERY PLAN                   
 -----------------------------------------------
- Nested Loop
-   ->  Nested Loop
+ Nested Loop(inner unique)
+   ->  Nested Loop(inner unique)
          ->  Index Scan using nt3_pkey on nt3
                Index Cond: (id = 1)
          ->  Index Scan using nt2_pkey on nt2
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index f41bef1..aaf585d 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -248,7 +248,7 @@ EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);
 EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
                      QUERY PLAN                     
 ----------------------------------------------------
- Nested Loop
+ Nested Loop(inner unique)
    ->  Subquery Scan on document
          Filter: f_leak(document.dtitle)
          ->  Seq Scan on document document_1
