diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index f0d9e94..995bc6c 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1340,9 +1340,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
 			if (((NestLoop *) plan)->join.joinqual)
 				show_instrumentation_count("Rows Removed by Join Filter", 1,
 										   planstate, es);
-			show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
-			if (plan->qual)
-				show_instrumentation_count("Rows Removed by Filter", 2,
+			show_upper_qual(plan->qual, "Other Filter", planstate, ancestors, es);
+			show_upper_qual(((NestLoop *) plan)->join.filterqual,
+							"Inner Filter", planstate, ancestors, es);
+			if (plan->qual || ((NestLoop *) plan)->join.filterqual)
+				show_instrumentation_count("Rows Removed by Inner/Other Filter", 2,
 										   planstate, es);
 			break;
 		case T_MergeJoin:
@@ -1353,9 +1355,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
 			if (((MergeJoin *) plan)->join.joinqual)
 				show_instrumentation_count("Rows Removed by Join Filter", 1,
 										   planstate, es);
-			show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
-			if (plan->qual)
-				show_instrumentation_count("Rows Removed by Filter", 2,
+			show_upper_qual(plan->qual, "Other Filter", planstate, ancestors, es);
+			show_upper_qual(((MergeJoin *) plan)->join.filterqual,
+							"Inner Filter", planstate, ancestors, es);
+			if (plan->qual || ((MergeJoin *) plan)->join.filterqual)
+				show_instrumentation_count("Rows Removed by Inner/Other Filters", 2,
 										   planstate, es);
 			break;
 		case T_HashJoin:
@@ -1366,9 +1370,9 @@ ExplainNode(PlanState *planstate, List *ancestors,
 			if (((HashJoin *) plan)->join.joinqual)
 				show_instrumentation_count("Rows Removed by Join Filter", 1,
 										   planstate, es);
-			show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
+			show_upper_qual(plan->qual, "Other Filter", planstate, ancestors, es);
 			if (plan->qual)
-				show_instrumentation_count("Rows Removed by Filter", 2,
+				show_instrumentation_count("Rows Removed by Other Filter", 2,
 										   planstate, es);
 			break;
 		case T_Agg:
@@ -1407,6 +1411,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
 			break;
 		case T_Hash:
 			show_hash_info((HashState *) planstate, es);
+			show_upper_qual(((Hash *) plan)->filterqual, "Inner Filter",
+							planstate, ancestors, es);
+			if (((Hash *) plan)->filterqual)
+				show_instrumentation_count("Rows Removed by Inner Filter", 2,
+										   planstate, es);
 			break;
 		default:
 			break;
diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c
index 0b2c139..87a71a0 100644
--- a/src/backend/executor/nodeHash.c
+++ b/src/backend/executor/nodeHash.c
@@ -75,6 +75,7 @@ MultiExecHash(HashState *node)
 {
 	PlanState  *outerNode;
 	List	   *hashkeys;
+	List	   *filterqual;
 	HashJoinTable hashtable;
 	TupleTableSlot *slot;
 	ExprContext *econtext;
@@ -95,6 +96,7 @@ MultiExecHash(HashState *node)
 	 */
 	hashkeys = node->hashkeys;
 	econtext = node->ps.ps_ExprContext;
+	filterqual = node->filterqual;
 
 	/*
 	 * get all inner tuples and insert into the hash table (or temp files)
@@ -104,8 +106,31 @@ MultiExecHash(HashState *node)
 		slot = ExecProcNode(outerNode);
 		if (TupIsNull(slot))
 			break;
+
+		/*
+		 * Sub node is connected to this node as "OUTER",
+		 * so we temporary specify slot as outer tuple during ExecQual.
+		 */
+		econtext->ecxt_outertuple = slot;
+
+		/*
+		 * Now, we filter with filterqual.
+		 */
+		if (filterqual == NIL || ExecQual(filterqual, econtext, false))
+		{
+			/* Nothing to do. No-op */
+		}
+		else
+		{
+			/* filterqual is neither scanqual nor joinqual. */
+			InstrCountFiltered2(node, 1);
+			continue;
+		}
+
 		/* We have to compute the hash value */
 		econtext->ecxt_innertuple = slot;
+		econtext->ecxt_outertuple = NULL;
+
 		if (ExecHashGetHashValue(hashtable, econtext, hashkeys,
 								 false, hashtable->keepNulls,
 								 &hashvalue))
@@ -206,6 +231,9 @@ ExecInitHash(Hash *node, EState *estate, int eflags)
 	hashstate->ps.qual = (List *)
 		ExecInitExpr((Expr *) node->plan.qual,
 					 (PlanState *) hashstate);
+	hashstate->filterqual = (List *)
+		ExecInitExpr((Expr *) node->filterqual,
+					  (PlanState *) hashstate);
 
 	/*
 	 * initialize child nodes
@@ -273,7 +301,7 @@ ExecHashTableCreate(Hash *node, List *hashOperators, bool keepNulls)
 	 */
 	outerNode = outerPlan(node);
 
-	ExecChooseHashTableSize(outerNode->plan_rows, outerNode->plan_width,
+	ExecChooseHashTableSize(node->plan.plan_rows, outerNode->plan_width,
 							OidIsValid(node->skewTable),
 							&nbuckets, &nbatch, &num_skew_mcvs);
 
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index 1d78cdf..eb2f250 100644
--- a/src/backend/executor/nodeHashjoin.c
+++ b/src/backend/executor/nodeHashjoin.c
@@ -472,6 +472,8 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags)
 	hjstate->js.joinqual = (List *)
 		ExecInitExpr((Expr *) node->join.joinqual,
 					 (PlanState *) hjstate);
+	 /* filterqual is not needed here, needed in Hash instead*/
+	hjstate->js.filterqual = NIL;
 	hjstate->hashclauses = (List *)
 		ExecInitExpr((Expr *) node->hashclauses,
 					 (PlanState *) hjstate);
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index 34b6cf6..cd858f5 100644
--- a/src/backend/executor/nodeMergejoin.c
+++ b/src/backend/executor/nodeMergejoin.c
@@ -355,6 +355,22 @@ MJEvalInnerValues(MergeJoinState *mergestate, TupleTableSlot *innerslot)
 
 	econtext->ecxt_innertuple = innerslot;
 
+	/*
+	 * We filter inner tuple with filterqual here.
+	 */
+	if (mergestate->js.filterqual == NIL ||
+			ExecQual(mergestate->js.filterqual, econtext, false))
+	{
+		/* Nothing to do. No-op */
+	}
+	else
+	{
+		/* Filtered. */
+		MemoryContextSwitchTo(oldContext);
+		InstrCountFiltered2(mergestate, 1);
+		return MJEVAL_NONMATCHABLE;
+	}
+
 	for (i = 0; i < mergestate->mj_NumClauses; i++)
 	{
 		MergeJoinClause clause = &mergestate->mj_Clauses[i];
@@ -1514,6 +1530,9 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
 	mergestate->js.joinqual = (List *)
 		ExecInitExpr((Expr *) node->join.joinqual,
 					 (PlanState *) mergestate);
+	mergestate->js.filterqual = (List *)
+		ExecInitExpr((Expr *) node->join.filterqual,
+					 (PlanState *) mergestate);
 	mergestate->mj_ConstFalseJoin = false;
 	/* mergeclauses are handled below */
 
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index e66bcda..93ed171 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -65,6 +65,7 @@ ExecNestLoop(NestLoopState *node)
 	TupleTableSlot *outerTupleSlot;
 	TupleTableSlot *innerTupleSlot;
 	List	   *joinqual;
+	List	   *filterqual;
 	List	   *otherqual;
 	ExprContext *econtext;
 	ListCell   *lc;
@@ -76,6 +77,7 @@ ExecNestLoop(NestLoopState *node)
 
 	nl = (NestLoop *) node->js.ps.plan;
 	joinqual = node->js.joinqual;
+	filterqual = node->js.filterqual;
 	otherqual = node->js.ps.qual;
 	outerPlan = outerPlanState(node);
 	innerPlan = innerPlanState(node);
@@ -174,6 +176,20 @@ ExecNestLoop(NestLoopState *node)
 		innerTupleSlot = ExecProcNode(innerPlan);
 		econtext->ecxt_innertuple = innerTupleSlot;
 
+		/*
+		 * We filter inner tuple with filterqual here.
+		 */
+		if (filterqual == NIL || ExecQual(filterqual, econtext, false))
+		{
+			/* Nothing to do. No-op */
+		}
+		else
+		{
+			/* Filtered. */
+			InstrCountFiltered2(node, 1);
+			continue;
+		}
+
 		if (TupIsNull(innerTupleSlot))
 		{
 			ENL1_printf("no inner tuple, need new outer tuple");
@@ -330,6 +346,9 @@ ExecInitNestLoop(NestLoop *node, EState *estate, int eflags)
 	nlstate->js.joinqual = (List *)
 		ExecInitExpr((Expr *) node->join.joinqual,
 					 (PlanState *) nlstate);
+	nlstate->js.filterqual = (List *)
+		ExecInitExpr((Expr *) node->join.filterqual,
+					 (PlanState *) nlstate);
 
 	/*
 	 * initialize child nodes
diff --git a/src/backend/optimizer/geqo/geqo_eval.c b/src/backend/optimizer/geqo/geqo_eval.c
index d9a20da..faadb69 100644
--- a/src/backend/optimizer/geqo/geqo_eval.c
+++ b/src/backend/optimizer/geqo/geqo_eval.c
@@ -261,7 +261,8 @@ merge_clump(PlannerInfo *root, List *clumps, Clump *new_clump, bool force)
 			 */
 			joinrel = make_join_rel(root,
 									old_clump->joinrel,
-									new_clump->joinrel);
+									new_clump->joinrel,
+									NIL);
 
 			/* Keep searching if join order is not valid */
 			if (joinrel)
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index d107d76..e9bd7ec 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -1995,6 +1995,7 @@ final_cost_nestloop(PlannerInfo *root, NestPath *path,
 
 	/* CPU costs */
 	cost_qual_eval(&restrict_qual_cost, path->joinrestrictinfo, root);
+	cost_qual_eval(&restrict_qual_cost, path->filterrestrictinfo, root);
 	startup_cost += restrict_qual_cost.startup;
 	cpu_per_tuple = cpu_tuple_cost + restrict_qual_cost.per_tuple;
 	run_cost += cpu_per_tuple * ntuples;
@@ -2306,6 +2307,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 	 */
 	cost_qual_eval(&merge_qual_cost, mergeclauses, root);
 	cost_qual_eval(&qp_qual_cost, path->jpath.joinrestrictinfo, root);
+	cost_qual_eval(&qp_qual_cost, path->jpath.filterrestrictinfo, root);
 	qp_qual_cost.startup -= merge_qual_cost.startup;
 	qp_qual_cost.per_tuple -= merge_qual_cost.per_tuple;
 
@@ -2547,6 +2549,7 @@ void
 initial_cost_hashjoin(PlannerInfo *root, JoinCostWorkspace *workspace,
 					  JoinType jointype,
 					  List *hashclauses,
+					  List *added_restrictinfo,
 					  Path *outer_path, Path *inner_path,
 					  SpecialJoinInfo *sjinfo,
 					  SemiAntiJoinFactors *semifactors)
@@ -2565,6 +2568,18 @@ initial_cost_hashjoin(PlannerInfo *root, JoinCostWorkspace *workspace,
 	run_cost += outer_path->total_cost - outer_path->startup_cost;
 	startup_cost += inner_path->total_cost;
 
+	/* estimate nrows of inner_path filtered by added_restrictlist */
+	if (added_restrictinfo != NIL)
+	{
+		inner_path_rows *=
+				clauselist_selectivity(root,
+									   added_restrictinfo,
+									   0,
+									   JOIN_INNER,
+									   NULL);
+		inner_path_rows = clamp_row_est(inner_path_rows);
+	}
+
 	/*
 	 * Cost of computing hash function: must do it once per input tuple. We
 	 * charge one cpu_operator_cost for each column's hash function.  Also,
@@ -2655,6 +2670,7 @@ final_cost_hashjoin(PlannerInfo *root, HashPath *path,
 	Cost		cpu_per_tuple;
 	QualCost	hash_qual_cost;
 	QualCost	qp_qual_cost;
+	QualCost	filter_qual_cost;
 	double		hashjointuples;
 	double		virtualbuckets;
 	Selectivity innerbucketsize;
@@ -2674,6 +2690,18 @@ final_cost_hashjoin(PlannerInfo *root, HashPath *path,
 	if (!enable_hashjoin)
 		startup_cost += disable_cost;
 
+	/* estimate nrows of inner_path filtered by filter restrict info */
+	if (path->jpath.filterrestrictinfo != NIL)
+	{
+		inner_path_rows *=
+				clauselist_selectivity(root,
+									   path->jpath.filterrestrictinfo,
+									   0,
+									   JOIN_INNER,
+									   NULL);
+		inner_path_rows = clamp_row_est(inner_path_rows);
+	}
+
 	/* mark the path with estimated # of batches */
 	path->num_batches = numbatches;
 
@@ -2753,6 +2781,7 @@ final_cost_hashjoin(PlannerInfo *root, HashPath *path,
 	 */
 	cost_qual_eval(&hash_qual_cost, hashclauses, root);
 	cost_qual_eval(&qp_qual_cost, path->jpath.joinrestrictinfo, root);
+	cost_qual_eval(&filter_qual_cost, path->jpath.filterrestrictinfo, root);
 	qp_qual_cost.startup -= hash_qual_cost.startup;
 	qp_qual_cost.per_tuple -= hash_qual_cost.per_tuple;
 
@@ -2835,6 +2864,8 @@ final_cost_hashjoin(PlannerInfo *root, HashPath *path,
 	 * not all of the quals may get evaluated at each tuple.)
 	 */
 	startup_cost += qp_qual_cost.startup;
+	startup_cost += filter_qual_cost.startup +
+			filter_qual_cost.per_tuple * inner_path_rows;
 	cpu_per_tuple = cpu_tuple_cost + qp_qual_cost.per_tuple;
 	run_cost += cpu_per_tuple * hashjointuples;
 
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index a35c881..23de7f2 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -18,9 +18,22 @@
 
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/nodes.h"
+#include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
+#include "optimizer/plancat.h"
+#include "optimizer/restrictinfo.h"
+#include "rewrite/rewriteManip.h"
+#include "utils/lsyscache.h"
+
+typedef struct
+{
+	List	*joininfo;
+	bool	 is_mutated;
+} check_constraint_mutator_context;
 
 /* Hook for plugins to get control in add_paths_to_joinrel() */
 set_join_pathlist_hook_type set_join_pathlist_hook = NULL;
@@ -45,6 +58,11 @@ static List *select_mergejoin_clauses(PlannerInfo *root,
 						 JoinType jointype,
 						 bool *mergejoin_allowed);
 
+static void try_join_pushdown(PlannerInfo *root,
+						  RelOptInfo *joinrel, RelOptInfo *outer_rel,
+						  RelOptInfo *inner_rel,
+						  List *restrictlist);
+
 
 /*
  * add_paths_to_joinrel
@@ -76,13 +94,33 @@ add_paths_to_joinrel(PlannerInfo *root,
 					 RelOptInfo *innerrel,
 					 JoinType jointype,
 					 SpecialJoinInfo *sjinfo,
-					 List *restrictlist)
+					 List *restrictlist,
+					 List *added_restrictlist,
+					 bool  added_rinfo_for_outer)
 {
 	JoinPathExtraData extra;
 	bool		mergejoin_allowed = true;
 	ListCell   *lc;
 
-	extra.restrictlist = restrictlist;
+	/*
+	 * Try to push Join down under Append
+	 */
+	if (!IS_OUTER_JOIN(jointype))
+	{
+		try_join_pushdown(root, joinrel, outerrel, innerrel, restrictlist);
+	}
+
+	if (added_restrictlist != NIL && added_rinfo_for_outer)
+	{
+		extra.restrictlist =
+				list_concat(list_copy(restrictlist), added_restrictlist);
+		extra.added_restrictlist = NIL;
+	}
+	else
+	{
+		extra.restrictlist = restrictlist;
+		extra.added_restrictlist = added_restrictlist;
+	}
 	extra.mergeclause_list = NIL;
 	extra.sjinfo = sjinfo;
 	extra.param_source_rels = NULL;
@@ -417,6 +455,7 @@ try_nestloop_path(PlannerInfo *root,
 									  outer_path,
 									  inner_path,
 									  extra->restrictlist,
+									  extra->added_restrictlist,
 									  pathkeys,
 									  required_outer));
 	}
@@ -499,6 +538,7 @@ try_mergejoin_path(PlannerInfo *root,
 									   outer_path,
 									   inner_path,
 									   extra->restrictlist,
+									   extra->added_restrictlist,
 									   pathkeys,
 									   required_outer,
 									   mergeclauses,
@@ -554,6 +594,7 @@ try_hashjoin_path(PlannerInfo *root,
 	 * never have any output pathkeys, per comments in create_hashjoin_path.
 	 */
 	initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
+						  extra->added_restrictlist,
 						  outer_path, inner_path,
 						  extra->sjinfo, &extra->semifactors);
 
@@ -571,6 +612,7 @@ try_hashjoin_path(PlannerInfo *root,
 									  outer_path,
 									  inner_path,
 									  extra->restrictlist,
+									  extra->added_restrictlist,
 									  required_outer,
 									  hashclauses));
 	}
@@ -1474,3 +1516,250 @@ select_mergejoin_clauses(PlannerInfo *root,
 
 	return result_list;
 }
+
+static Node *
+check_constraint_mutator(Node *node, check_constraint_mutator_context *context)
+{
+	/* Failed to mutate. Abort. */
+	if (!context->is_mutated)
+		return (Node *) copyObject(node);
+
+	if (node == NULL)
+		return NULL;
+
+	if (IsA(node, Var))
+	{
+		List		*l = context->joininfo;
+		ListCell	*lc;
+
+		Assert(list_length(l) > 0);
+
+		foreach (lc, l)
+		{
+			RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+			Expr *expr = rinfo->clause;
+
+			if (!rinfo->can_join ||
+				!IsA(expr, OpExpr) ||
+				!op_hashjoinable(((OpExpr *) expr)->opno,
+								exprType(get_leftop(expr))))
+				continue;
+
+			if (equal(get_leftop(expr), node))
+			{
+				/*
+				 * This node is equal to LEFT of join clause,
+				 * thus will be replaced with RIGHT clause.
+				 */
+				return (Node *) copyObject(get_rightop(expr));
+			}
+			else
+			if (equal(get_rightop(expr), node))
+			{
+				/*
+				 * This node is equal to RIGHT of join clause,
+				 * thus will be replaced with LEFT clause.
+				 */
+				return (Node *) copyObject(get_leftop(expr));
+			}
+		}
+
+		/* Unfortunately, mutating is failed. */
+		context->is_mutated = false;
+		return (Node *) copyObject(node);
+	}
+
+	return expression_tree_mutator(node, check_constraint_mutator, context);
+}
+
+/*
+ * Make RestrictInfo_List from CHECK() constraints.
+ */
+static List *
+make_restrictinfos_from_check_constr(PlannerInfo *root,
+									List *joininfo, RelOptInfo *outer_rel)
+{
+	List			*result = NIL;
+	RangeTblEntry	*childRTE = root->simple_rte_array[outer_rel->relid];
+	List			*check_constr =
+						get_relation_constraints(root, childRTE->relid,
+													outer_rel, false);
+	ListCell		*lc;
+
+	check_constraint_mutator_context	context;
+
+	context.joininfo = joininfo;
+	context.is_mutated = true;
+
+	/*
+	 * Try to change CHECK() constraints to filter expressions.
+	 */
+	foreach(lc, check_constr)
+	{
+		Node *mutated =
+				expression_tree_mutator((Node *) lfirst(lc),
+										check_constraint_mutator,
+										(void *) &context);
+
+		if (context.is_mutated)
+			result = lappend(result, mutated);
+	}
+
+	Assert(list_length(check_constr) == list_length(result));
+	list_free_deep(check_constr);
+
+	return make_restrictinfos_from_actual_clauses(root, result);
+}
+
+/*
+ * Mutate parent's relid to child one.
+ */
+static List *
+mutate_parent_relid_to_child(PlannerInfo *root, List *join_clauses,
+								RelOptInfo *outer_rel)
+{
+	Index		parent_relid =
+					find_childrel_appendrelinfo(root, outer_rel)->parent_relid;
+	List		*old_clauses = get_actual_clauses(join_clauses);
+	List		*new_clauses = NIL;
+	ListCell	*lc;
+
+	foreach(lc, old_clauses)
+	{
+		Node	*new_clause = (Node *) copyObject(lfirst(lc));
+
+		ChangeVarNodes(new_clause, parent_relid, outer_rel->relid, 0);
+		new_clauses = lappend(new_clauses, new_clause);
+	}
+
+	return make_restrictinfos_from_actual_clauses(root, new_clauses);
+}
+
+static inline List *
+extract_join_clauses(List *restrictlist, RelOptInfo *outer_prel,
+						RelOptInfo *inner_rel)
+{
+	List		*result = NIL;
+	ListCell	*lc;
+
+	foreach (lc, restrictlist)
+	{
+		RestrictInfo	*rinfo = (RestrictInfo *) lfirst(lc);
+
+		if (clause_sides_match_join(rinfo, outer_prel, inner_rel))
+			result = lappend(result, rinfo);
+	}
+
+	return result;
+}
+
+/*
+  Try to push JoinPath down under AppendPath.
+*/
+static void
+try_join_pushdown(PlannerInfo *root,
+				  RelOptInfo *joinrel, RelOptInfo *outer_rel,
+				  RelOptInfo *inner_rel,
+				  List *restrictlist)
+{
+	AppendPath	*outer_path;
+	ListCell	*lc;
+	List		*old_joinclauses;
+	List		*new_append_subpaths = NIL;
+
+	Assert(outer_rel->cheapest_total_path != NULL);
+
+	/* When specified outer path is not an AppendPath, nothing to do here. */
+	if (!IsA(outer_rel->cheapest_total_path, AppendPath))
+	{
+		elog(DEBUG1, "Outer path is not an AppendPath. Do nothing.");
+		return;
+	}
+
+	outer_path = (AppendPath *) outer_rel->cheapest_total_path;
+
+	/*
+	 * Extract join clauses to mutate CHECK() constraints.
+	 * We don't have to clobber this list to mutate CHECK() constraints,
+	 * so we need to do only once.
+	 */
+	old_joinclauses = extract_join_clauses(restrictlist, outer_rel, inner_rel);
+
+	/*
+	  * Make new joinrel between each of outer path's sub-paths and inner path.
+	  */
+	foreach(lc, outer_path->subpaths)
+	{
+		RelOptInfo	*old_outer_rel = ((Path *) lfirst(lc))->parent;
+		RelOptInfo	*new_outer_rel;
+		List		*new_joinclauses;
+		List		*added_restrictlist;
+		List		**join_rel_level;
+
+		Assert(!IS_DUMMY_REL(old_outer_rel));
+
+		/*
+		 * Join clause points parent's relid,
+		 * so we must change it to child's one.
+		 */
+		new_joinclauses = mutate_parent_relid_to_child(root, old_joinclauses,
+													old_outer_rel);
+
+		/*
+		 * Make RestrictInfo list from CHECK() constraints of outer table.
+		 */
+		added_restrictlist =
+				make_restrictinfos_from_check_constr(root, new_joinclauses,
+													old_outer_rel);
+
+		/* XXX This is workaround for failing assertion at allpaths.c */
+		join_rel_level = root->join_rel_level;
+		root->join_rel_level = NULL;
+
+		/*
+		 * Create new joinrel with restriction made above.
+		 */
+		new_outer_rel =
+				make_join_rel(root, old_outer_rel, inner_rel,
+						added_restrictlist);
+
+		root->join_rel_level = join_rel_level;
+
+		Assert(new_outer_rel != NULL);
+
+		if (IS_DUMMY_REL(new_outer_rel))
+		{
+			pfree(new_outer_rel);
+			continue;
+		}
+
+		/*
+		 * We must check if each of all new joinrels have one path at least.
+		 * add_path() sometime rejects to add new path to parent RelOptInfo.
+		 */
+		if (list_length(new_outer_rel->pathlist) <= 0)
+		{
+			/*
+			 * Sadly, No paths added. This means that pushdown is failed,
+			 * thus clean up here.
+			 */
+			list_free_deep(new_append_subpaths);
+			pfree(new_outer_rel);
+			list_free(old_joinclauses);
+			elog(DEBUG1, "Join pushdown failed.");
+			return;
+		}
+
+		set_cheapest(new_outer_rel);
+		Assert(new_outer_rel->cheapest_total_path != NULL);
+		new_append_subpaths = lappend(new_append_subpaths,
+									new_outer_rel->cheapest_total_path);
+	}
+
+	/* Join Pushdown is succeeded. Add path to original joinrel. */
+	add_path(joinrel,
+			(Path *) create_append_path(joinrel, new_append_subpaths, NULL));
+
+	list_free(old_joinclauses);
+	elog(DEBUG1, "Join pushdown succeeded.");
+}
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index b2cc9f0..7075552 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -33,7 +33,6 @@ static void mark_dummy_rel(RelOptInfo *rel);
 static bool restriction_is_constant_false(List *restrictlist,
 							  bool only_pushed_down);
 
-
 /*
  * join_search_one_level
  *	  Consider ways to produce join relations containing exactly 'level'
@@ -170,7 +169,7 @@ join_search_one_level(PlannerInfo *root, int level)
 					if (have_relevant_joinclause(root, old_rel, new_rel) ||
 						have_join_order_restriction(root, old_rel, new_rel))
 					{
-						(void) make_join_rel(root, old_rel, new_rel);
+						(void) make_join_rel(root, old_rel, new_rel, NIL);
 					}
 				}
 			}
@@ -271,7 +270,7 @@ make_rels_by_clause_joins(PlannerInfo *root,
 			(have_relevant_joinclause(root, old_rel, other_rel) ||
 			 have_join_order_restriction(root, old_rel, other_rel)))
 		{
-			(void) make_join_rel(root, old_rel, other_rel);
+			(void) make_join_rel(root, old_rel, other_rel, NIL);
 		}
 	}
 }
@@ -303,7 +302,7 @@ make_rels_by_clauseless_joins(PlannerInfo *root,
 
 		if (!bms_overlap(other_rel->relids, old_rel->relids))
 		{
-			(void) make_join_rel(root, old_rel, other_rel);
+			(void) make_join_rel(root, old_rel, other_rel, NIL);
 		}
 	}
 }
@@ -589,7 +588,8 @@ join_is_legal(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
  * turned into joins.
  */
 RelOptInfo *
-make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
+make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
+			  List *added_restrictlist)
 {
 	Relids		joinrelids;
 	SpecialJoinInfo *sjinfo;
@@ -691,10 +691,14 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
 			}
 			add_paths_to_joinrel(root, joinrel, rel1, rel2,
 								 JOIN_INNER, sjinfo,
-								 restrictlist);
+								 restrictlist,
+								 added_restrictlist,
+								 false);
 			add_paths_to_joinrel(root, joinrel, rel2, rel1,
 								 JOIN_INNER, sjinfo,
-								 restrictlist);
+								 restrictlist,
+								 added_restrictlist,
+								 true);
 			break;
 		case JOIN_LEFT:
 			if (is_dummy_rel(rel1) ||
@@ -708,10 +712,14 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
 				mark_dummy_rel(rel2);
 			add_paths_to_joinrel(root, joinrel, rel1, rel2,
 								 JOIN_LEFT, sjinfo,
-								 restrictlist);
+								 restrictlist,
+								 added_restrictlist,
+								 false);
 			add_paths_to_joinrel(root, joinrel, rel2, rel1,
 								 JOIN_RIGHT, sjinfo,
-								 restrictlist);
+								 restrictlist,
+								 added_restrictlist,
+								 true);
 			break;
 		case JOIN_FULL:
 			if ((is_dummy_rel(rel1) && is_dummy_rel(rel2)) ||
@@ -722,10 +730,14 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
 			}
 			add_paths_to_joinrel(root, joinrel, rel1, rel2,
 								 JOIN_FULL, sjinfo,
-								 restrictlist);
+								 restrictlist,
+								 added_restrictlist,
+								 false);
 			add_paths_to_joinrel(root, joinrel, rel2, rel1,
 								 JOIN_FULL, sjinfo,
-								 restrictlist);
+								 restrictlist,
+								 added_restrictlist,
+								 true);
 
 			/*
 			 * If there are join quals that aren't mergeable or hashable, we
@@ -758,7 +770,9 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
 				}
 				add_paths_to_joinrel(root, joinrel, rel1, rel2,
 									 JOIN_SEMI, sjinfo,
-									 restrictlist);
+									 restrictlist,
+									 added_restrictlist,
+									 false);
 			}
 
 			/*
@@ -781,10 +795,14 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
 				}
 				add_paths_to_joinrel(root, joinrel, rel1, rel2,
 									 JOIN_UNIQUE_INNER, sjinfo,
-									 restrictlist);
+									 restrictlist,
+									 added_restrictlist,
+									 false);
 				add_paths_to_joinrel(root, joinrel, rel2, rel1,
 									 JOIN_UNIQUE_OUTER, sjinfo,
-									 restrictlist);
+									 restrictlist,
+									 added_restrictlist,
+									 true);
 			}
 			break;
 		case JOIN_ANTI:
@@ -799,7 +817,9 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
 				mark_dummy_rel(rel2);
 			add_paths_to_joinrel(root, joinrel, rel1, rel2,
 								 JOIN_ANTI, sjinfo,
-								 restrictlist);
+								 restrictlist,
+								 added_restrictlist,
+								 false);
 			break;
 		default:
 			/* other values not expected here */
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 404c6f5..8cbf86e 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -135,7 +135,8 @@ static WorkTableScan *make_worktablescan(List *qptlist, List *qpqual,
 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,
+			  List *joinclauses,  List *filterclauses,
+			  List *otherclauses, List *nestParams,
 			  Plan *lefttree, Plan *righttree,
 			  JoinType jointype);
 static HashJoin *make_hashjoin(List *tlist,
@@ -144,14 +145,15 @@ static HashJoin *make_hashjoin(List *tlist,
 			  Plan *lefttree, Plan *righttree,
 			  JoinType jointype);
 static Hash *make_hash(Plan *lefttree,
+		  List *filterclauses,
 		  Oid skewTable,
 		  AttrNumber skewColumn,
 		  bool skewInherit,
 		  Oid skewColType,
 		  int32 skewColTypmod);
 static MergeJoin *make_mergejoin(List *tlist,
-			   List *joinclauses, List *otherclauses,
-			   List *mergeclauses,
+			   List *joinclauses, List *filterclauses,
+			   List *otherclauses, List *mergeclauses,
 			   Oid *mergefamilies,
 			   Oid *mergecollations,
 			   int *mergestrategies,
@@ -2239,6 +2241,7 @@ create_nestloop_plan(PlannerInfo *root,
 	List	   *tlist = build_path_tlist(root, &best_path->path);
 	List	   *joinrestrictclauses = best_path->joinrestrictinfo;
 	List	   *joinclauses;
+	List	   *filterclauses;
 	List	   *otherclauses;
 	Relids		outerrelids;
 	List	   *nestParams;
@@ -2248,6 +2251,7 @@ create_nestloop_plan(PlannerInfo *root,
 
 	/* Sort join qual clauses into best execution order */
 	joinrestrictclauses = order_qual_clauses(root, joinrestrictclauses);
+	filterclauses = order_qual_clauses(root, best_path->filterrestrictinfo);
 
 	/* Get the join qual clauses (in plain expression form) */
 	/* Any pseudoconstant clauses are ignored here */
@@ -2263,6 +2267,8 @@ create_nestloop_plan(PlannerInfo *root,
 		otherclauses = NIL;
 	}
 
+	filterclauses = extract_actual_clauses(filterclauses, false);
+
 	/* Replace any outer-relation variables with nestloop params */
 	if (best_path->path.param_info)
 	{
@@ -2309,6 +2315,7 @@ create_nestloop_plan(PlannerInfo *root,
 
 	join_plan = make_nestloop(tlist,
 							  joinclauses,
+							  filterclauses,
 							  otherclauses,
 							  nestParams,
 							  outer_plan,
@@ -2328,6 +2335,7 @@ create_mergejoin_plan(PlannerInfo *root,
 {
 	List	   *tlist = build_path_tlist(root, &best_path->jpath.path);
 	List	   *joinclauses;
+	List	   *filterclauses;
 	List	   *otherclauses;
 	List	   *mergeclauses;
 	List	   *outerpathkeys;
@@ -2346,6 +2354,7 @@ create_mergejoin_plan(PlannerInfo *root,
 	/* Sort join qual clauses into best execution order */
 	/* NB: do NOT reorder the mergeclauses */
 	joinclauses = order_qual_clauses(root, best_path->jpath.joinrestrictinfo);
+	filterclauses = order_qual_clauses(root, best_path->jpath.filterrestrictinfo);
 
 	/* Get the join qual clauses (in plain expression form) */
 	/* Any pseudoconstant clauses are ignored here */
@@ -2361,6 +2370,7 @@ create_mergejoin_plan(PlannerInfo *root,
 		otherclauses = NIL;
 	}
 
+	filterclauses = extract_actual_clauses(filterclauses, false);
 	/*
 	 * Remove the mergeclauses from the list of join qual clauses, leaving the
 	 * list of quals that must be checked as qpquals.
@@ -2599,6 +2609,7 @@ create_mergejoin_plan(PlannerInfo *root,
 	 */
 	join_plan = make_mergejoin(tlist,
 							   joinclauses,
+							   filterclauses,
 							   otherclauses,
 							   mergeclauses,
 							   mergefamilies,
@@ -2623,6 +2634,7 @@ create_hashjoin_plan(PlannerInfo *root,
 {
 	List	   *tlist = build_path_tlist(root, &best_path->jpath.path);
 	List	   *joinclauses;
+	List	   *filterclauses;
 	List	   *otherclauses;
 	List	   *hashclauses;
 	Oid			skewTable = InvalidOid;
@@ -2635,6 +2647,7 @@ create_hashjoin_plan(PlannerInfo *root,
 
 	/* Sort join qual clauses into best execution order */
 	joinclauses = order_qual_clauses(root, best_path->jpath.joinrestrictinfo);
+	filterclauses = order_qual_clauses(root, best_path->jpath.filterrestrictinfo);
 	/* There's no point in sorting the hash clauses ... */
 
 	/* Get the join qual clauses (in plain expression form) */
@@ -2720,8 +2733,11 @@ create_hashjoin_plan(PlannerInfo *root,
 
 	/*
 	 * Build the hash node and hash join node.
+	 *
+	 * In HashJoin, filterclauses is needed by Hash, not HashJoin.
 	 */
 	hash_plan = make_hash(inner_plan,
+						  filterclauses,
 						  skewTable,
 						  skewColumn,
 						  skewInherit,
@@ -3862,6 +3878,7 @@ make_bitmap_or(List *bitmapplans)
 static NestLoop *
 make_nestloop(List *tlist,
 			  List *joinclauses,
+			  List *filterclauses,
 			  List *otherclauses,
 			  List *nestParams,
 			  Plan *lefttree,
@@ -3878,6 +3895,7 @@ make_nestloop(List *tlist,
 	plan->righttree = righttree;
 	node->join.jointype = jointype;
 	node->join.joinqual = joinclauses;
+	node->join.filterqual = filterclauses;
 	node->nestParams = nestParams;
 
 	return node;
@@ -3903,12 +3921,15 @@ make_hashjoin(List *tlist,
 	node->hashclauses = hashclauses;
 	node->join.jointype = jointype;
 	node->join.joinqual = joinclauses;
+	/* filterqual is not needed for HashJoin node */
+	node->join.filterqual = NIL;
 
 	return node;
 }
 
 static Hash *
 make_hash(Plan *lefttree,
+		  List *filterclauses,
 		  Oid skewTable,
 		  AttrNumber skewColumn,
 		  bool skewInherit,
@@ -3919,6 +3940,27 @@ make_hash(Plan *lefttree,
 	Plan	   *plan = &node->plan;
 
 	copy_plan_costsize(plan, lefttree);
+	/*
+	 * estimate nrows of 'lefttree' rel filtered by 'filterclauses'.
+	 * All selectivity values maybe cached, because clauselist_selectivity()
+	 * had already been called at this timing.
+	 * Thus, clauselist_selectivity() need not to specify real PlannerInfo here.
+	 */
+	if (filterclauses != NIL)
+	{
+#ifdef USE_ASSERT_CHECKING
+		ListCell *lc;
+
+		foreach (lc, filterclauses)
+		{
+			Assert(IsA(lfirst(lc), RestrictInfo));
+			Assert(((RestrictInfo *)lfirst(lc))->norm_selec != -1);
+		}
+#endif
+		plan->plan_rows *=
+				clauselist_selectivity(NULL, filterclauses, 0, JOIN_INNER, NULL);
+		plan->plan_rows = clamp_row_est(plan->plan_rows);
+	}
 
 	/*
 	 * For plausibility, make startup & total costs equal total cost of input
@@ -3930,6 +3972,7 @@ make_hash(Plan *lefttree,
 	plan->lefttree = lefttree;
 	plan->righttree = NULL;
 
+	node->filterqual = extract_actual_clauses(filterclauses, false);
 	node->skewTable = skewTable;
 	node->skewColumn = skewColumn;
 	node->skewInherit = skewInherit;
@@ -3942,6 +3985,7 @@ make_hash(Plan *lefttree,
 static MergeJoin *
 make_mergejoin(List *tlist,
 			   List *joinclauses,
+			   List *filterclauses,
 			   List *otherclauses,
 			   List *mergeclauses,
 			   Oid *mergefamilies,
@@ -3967,6 +4011,7 @@ make_mergejoin(List *tlist,
 	node->mergeNullsFirst = mergenullsfirst;
 	node->join.jointype = jointype;
 	node->join.joinqual = joinclauses;
+	node->join.filterqual = filterclauses;
 
 	return node;
 }
@@ -4011,6 +4056,21 @@ make_sort(PlannerInfo *root, Plan *lefttree, int numCols,
 	return node;
 }
 
+static inline bool
+should_ignore_ec_member(EquivalenceMember *em, Relids relids)
+{
+	/*
+	 * If this is called from make_sort_from_pathkeys, relids may be NULL.
+	 * In this case, we must not ignore child members because inner/outer plan
+	 * of pushed-down merge join is always child table.
+	 */
+	if (!relids)
+		return false;
+
+	return (em->em_is_child &&
+		!bms_equal(em->em_relids, relids));
+}
+
 /*
  * prepare_sort_from_pathkeys
  *	  Prepare to sort according to given pathkeys
@@ -4190,8 +4250,7 @@ prepare_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys,
 				 * Ignore child members unless they match the rel being
 				 * sorted.
 				 */
-				if (em->em_is_child &&
-					!bms_equal(em->em_relids, relids))
+				if (should_ignore_ec_member(em, relids))
 					continue;
 
 				sortexpr = em->em_expr;
@@ -4304,8 +4363,7 @@ find_ec_member_for_tle(EquivalenceClass *ec,
 		/*
 		 * Ignore child members unless they match the rel being sorted.
 		 */
-		if (em->em_is_child &&
-			!bms_equal(em->em_relids, relids))
+		if (should_ignore_ec_member(em, relids))
 			continue;
 
 		/* Match if same expression (after stripping relabel) */
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index daeb584..3d9eb00 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -104,6 +104,7 @@ static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
 static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
 static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
 static void set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset);
+static void set_hash_references(PlannerInfo *root, Hash *hash, int rtoffset);
 static void set_dummy_tlist_references(Plan *plan, int rtoffset);
 static indexed_tlist *build_tlist_index(List *tlist);
 static Var *search_indexed_tlist_for_var(Var *var,
@@ -598,6 +599,12 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 			break;
 
 		case T_Hash:
+			/*
+			 * For Hash node, we need to set INNER_VAR to varno of filterqual.
+			 */
+			set_hash_references(root, (Hash *) plan, rtoffset);
+			/* FALL THRU */
+
 		case T_Material:
 		case T_Sort:
 		case T_Unique:
@@ -1493,6 +1500,14 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset)
 								   (Index) 0,
 								   rtoffset);
 
+	/* We need not to do for HashJoin */
+	if (!IsA(join, HashJoin) || join->filterqual != NIL)
+		join->filterqual = fix_upper_expr(root,
+										  (Node *) join->filterqual,
+										  inner_itlist,
+										  INNER_VAR,
+										  rtoffset);
+
 	/* Now do join-type-specific stuff */
 	if (IsA(join, NestLoop))
 	{
@@ -1655,6 +1670,30 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset)
 	pfree(subplan_itlist);
 }
 
+static void
+set_hash_references(PlannerInfo *root, Hash *hash, int rtoffset)
+{
+	Plan	   *subplan = hash->plan.lefttree;
+	indexed_tlist *subplan_itlist;
+	List	   *output_targetlist;
+	ListCell   *l;
+
+	subplan_itlist = build_tlist_index(subplan->targetlist);
+
+	/*
+	 * Sub plan node is connected to this plan node as "OUTER",
+	 * so we specify OUTER_VAR instead of INNER_VAR.
+	 */
+	hash->filterqual =
+		(List *) fix_upper_expr(root,
+								(Node *) hash->filterqual,
+								subplan_itlist,
+								OUTER_VAR,
+								rtoffset);
+
+	pfree(subplan_itlist);
+}
+
 /*
  * set_dummy_tlist_references
  *	  Replace the targetlist of an upper-level plan node with a simple
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 935bc2b..4d8fe4b 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1562,6 +1562,7 @@ create_nestloop_path(PlannerInfo *root,
 					 Path *outer_path,
 					 Path *inner_path,
 					 List *restrict_clauses,
+					 List *filtering_clauses,
 					 List *pathkeys,
 					 Relids required_outer)
 {
@@ -1609,6 +1610,7 @@ create_nestloop_path(PlannerInfo *root,
 	pathnode->outerjoinpath = outer_path;
 	pathnode->innerjoinpath = inner_path;
 	pathnode->joinrestrictinfo = restrict_clauses;
+	pathnode->filterrestrictinfo = filtering_clauses;
 
 	final_cost_nestloop(root, pathnode, workspace, sjinfo, semifactors);
 
@@ -1643,6 +1645,7 @@ create_mergejoin_path(PlannerInfo *root,
 					  Path *outer_path,
 					  Path *inner_path,
 					  List *restrict_clauses,
+					  List *filtering_clauses,
 					  List *pathkeys,
 					  Relids required_outer,
 					  List *mergeclauses,
@@ -1666,6 +1669,7 @@ create_mergejoin_path(PlannerInfo *root,
 	pathnode->jpath.outerjoinpath = outer_path;
 	pathnode->jpath.innerjoinpath = inner_path;
 	pathnode->jpath.joinrestrictinfo = restrict_clauses;
+	pathnode->jpath.filterrestrictinfo = filtering_clauses;
 	pathnode->path_mergeclauses = mergeclauses;
 	pathnode->outersortkeys = outersortkeys;
 	pathnode->innersortkeys = innersortkeys;
@@ -1702,6 +1706,7 @@ create_hashjoin_path(PlannerInfo *root,
 					 Path *outer_path,
 					 Path *inner_path,
 					 List *restrict_clauses,
+					 List *filtering_clauses,
 					 Relids required_outer,
 					 List *hashclauses)
 {
@@ -1734,6 +1739,7 @@ create_hashjoin_path(PlannerInfo *root,
 	pathnode->jpath.outerjoinpath = outer_path;
 	pathnode->jpath.innerjoinpath = inner_path;
 	pathnode->jpath.joinrestrictinfo = restrict_clauses;
+	pathnode->jpath.filterrestrictinfo = filtering_clauses;
 	pathnode->path_hashclauses = hashclauses;
 	/* final_cost_hashjoin will fill in pathnode->num_batches */
 
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 9442e5f..c137b09 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -54,9 +54,6 @@ get_relation_info_hook_type get_relation_info_hook = NULL;
 static bool infer_collation_opclass_match(InferenceElem *elem, Relation idxRel,
 							  List *idxExprs);
 static int32 get_rel_data_width(Relation rel, int32 *attr_widths);
-static List *get_relation_constraints(PlannerInfo *root,
-						 Oid relationObjectId, RelOptInfo *rel,
-						 bool include_notnull);
 static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
 				  Relation heapRelation);
 
@@ -1022,7 +1019,7 @@ get_relation_data_width(Oid relid, int32 *attr_widths)
  * run, and in many cases it won't be invoked at all, so there seems no
  * point in caching the data in RelOptInfo.
  */
-static List *
+List *
 get_relation_constraints(PlannerInfo *root,
 						 Oid relationObjectId, RelOptInfo *rel,
 						 bool include_notnull)
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 68a93a1..d6717db 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -496,19 +496,24 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
 {
 	Relids		relids = joinrel->relids;
 	ListCell   *vars;
+	int			nth = 0;
 
 	foreach(vars, input_rel->reltargetlist)
 	{
 		Var		   *var = (Var *) lfirst(vars);
 		RelOptInfo *baserel;
 		int			ndx;
+		bool		is_needed = false;
 
 		/*
 		 * Ignore PlaceHolderVars in the input tlists; we'll make our own
 		 * decisions about whether to copy them.
 		 */
 		if (IsA(var, PlaceHolderVar))
+		{
+			nth++;
 			continue;
+		}
 
 		/*
 		 * Otherwise, anything in a baserel or joinrel targetlist ought to be
@@ -521,15 +526,83 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
 
 		/* Get the Var's original base rel */
 		baserel = find_base_rel(root, var->varno);
+		ndx = var->varattno - baserel->min_attr;
+
+		/*
+		 * We must handle case of join pushdown.
+		 */
+		if (input_rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
+		{
+			/* Get the Var's PARENT base rel */
+			Index	parent_relid =
+						find_childrel_appendrelinfo(root, input_rel)->parent_relid;
+			RelOptInfo *parent_rel = find_base_rel(root, parent_relid);
+			Var		*parent_var =
+						(Var *) list_nth(parent_rel->reltargetlist, nth);
+			int		parent_ndx = parent_var->varattno - parent_rel->min_attr;
+			/* Relids have included parent_rel's instead of input_rel's. */
+			Relids	relids_tmp =
+					bms_del_members(bms_copy(relids), input_rel->relids);
+
+			relids_tmp = bms_union(relids_tmp, parent_rel->relids);
+
+			Assert(ndx == parent_ndx);
+			is_needed =
+					(bms_nonempty_difference(
+							parent_rel->attr_needed[parent_ndx],
+							relids_tmp));
+
+			bms_free(relids_tmp);
+		}
+		else
+		{
+			Relids	relids_tmp =
+					bms_del_members(bms_copy(relids), input_rel->relids);
+			Index	another_relid = -1;
+
+			/* Try to detect Inner relation of pushed-down join. */
+			if (bms_get_singleton_member(relids_tmp, &another_relid))
+			{
+				RelOptInfo	*another_rel =
+						find_base_rel(root, another_relid);
+
+				if (another_rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
+				{
+					/* This may be inner relation of pushed-down join. */
+					Index	parent_relid =
+								find_childrel_appendrelinfo(root, another_rel)->parent_relid;
+					RelOptInfo *parent_rel = find_base_rel(root, parent_relid);
+
+					bms_free(relids_tmp);
+					relids_tmp =
+							bms_union(input_rel->relids, parent_rel->relids);
+				}
+			}
+
+			if (!bms_is_subset(input_rel->relids, relids_tmp))
+			{
+				/* Can't detect inner relation of pushed-down join */
+				bms_free(relids_tmp);
+				relids_tmp = bms_copy(relids);
+			}
+
+			is_needed =
+					(bms_nonempty_difference(
+							baserel->attr_needed[ndx],
+							relids_tmp));
+
+			bms_free(relids_tmp);
+		}
 
 		/* Is it still needed above this joinrel? */
-		ndx = var->varattno - baserel->min_attr;
-		if (bms_nonempty_difference(baserel->attr_needed[ndx], relids))
+		if (is_needed)
 		{
 			/* Yup, add it to the output */
 			joinrel->reltargetlist = lappend(joinrel->reltargetlist, var);
 			joinrel->width += baserel->attr_widths[ndx];
 		}
+
+		nth++;
 	}
 }
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 4ae2f3e..f2b56d0 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1644,6 +1644,7 @@ typedef struct JoinState
 	PlanState	ps;
 	JoinType	jointype;
 	List	   *joinqual;		/* JOIN quals (in addition to ps.qual) */
+	List	   *filterqual;		/* FILTER quals (in addition to ps.qual) */
 } JoinState;
 
 /* ----------------
@@ -1957,6 +1958,7 @@ typedef struct UniqueState
 typedef struct HashState
 {
 	PlanState	ps;				/* its first field is NodeTag */
+	List	   *filterqual;		/* FILTER quals (in addition to ps.qual) */
 	HashJoinTable hashtable;	/* hash table for the hashjoin */
 	List	   *hashkeys;		/* list of ExprState nodes */
 	/* hashkeys is same as parent's hj_InnerHashKeys */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index cc259f1..37cef10 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -593,6 +593,7 @@ typedef struct Join
 	Plan		plan;
 	JoinType	jointype;
 	List	   *joinqual;		/* JOIN quals (in addition to plan.qual) */
+	List	   *filterqual;		/* FILTER quals (in addition to plan.qual) */
 } Join;
 
 /* ----------------
@@ -764,6 +765,7 @@ typedef struct Unique
 typedef struct Hash
 {
 	Plan		plan;
+	List	   *filterqual;		/* FILTER quals (in addition to plan.qual) */
 	Oid			skewTable;		/* outer join key's table OID, or InvalidOid */
 	AttrNumber	skewColumn;		/* outer join key's column #, or zero */
 	bool		skewInherit;	/* is outer join rel an inheritance tree? */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 79bed33..83d41de 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -1058,6 +1058,7 @@ typedef struct JoinPath
 	Path	   *innerjoinpath;	/* path for the inner side of the join */
 
 	List	   *joinrestrictinfo;		/* RestrictInfos to apply to join */
+	List	   *filterrestrictinfo;		/* RestrictInfos to filter at join */
 
 	/*
 	 * See the notes for RelOptInfo and ParamPathInfo to understand why
@@ -1706,6 +1707,7 @@ typedef struct JoinPathExtraData
 {
 	List	   *restrictlist;
 	List	   *mergeclause_list;
+	List	   *added_restrictlist;
 	SpecialJoinInfo *sjinfo;
 	SemiAntiJoinFactors semifactors;
 	Relids		param_source_rels;
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index dd43e45..e685a8d 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -137,6 +137,7 @@ extern void initial_cost_hashjoin(PlannerInfo *root,
 					  JoinCostWorkspace *workspace,
 					  JoinType jointype,
 					  List *hashclauses,
+					  List *added_restrictlist,
 					  Path *outer_path, Path *inner_path,
 					  SpecialJoinInfo *sjinfo,
 					  SemiAntiJoinFactors *semifactors);
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 161644c..9d3718d 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -97,6 +97,7 @@ extern NestPath *create_nestloop_path(PlannerInfo *root,
 					 Path *outer_path,
 					 Path *inner_path,
 					 List *restrict_clauses,
+					 List *filtering_clauses,
 					 List *pathkeys,
 					 Relids required_outer);
 
@@ -108,6 +109,7 @@ extern MergePath *create_mergejoin_path(PlannerInfo *root,
 					  Path *outer_path,
 					  Path *inner_path,
 					  List *restrict_clauses,
+					  List *filtering_clauses,
 					  List *pathkeys,
 					  Relids required_outer,
 					  List *mergeclauses,
@@ -123,6 +125,7 @@ extern HashPath *create_hashjoin_path(PlannerInfo *root,
 					 Path *outer_path,
 					 Path *inner_path,
 					 List *restrict_clauses,
+					 List *filtering_clauses,
 					 Relids required_outer,
 					 List *hashclauses);
 
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 87123a5..f038f5d 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -87,7 +87,8 @@ extern void create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel);
 extern void add_paths_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
 					 RelOptInfo *outerrel, RelOptInfo *innerrel,
 					 JoinType jointype, SpecialJoinInfo *sjinfo,
-					 List *restrictlist);
+					 List *restrictlist, List *added_restrictlist,
+					 bool added_rinfo_for_outer);
 
 /*
  * joinrels.c
@@ -95,7 +96,7 @@ extern void add_paths_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
  */
 extern void join_search_one_level(PlannerInfo *root, int level);
 extern RelOptInfo *make_join_rel(PlannerInfo *root,
-			  RelOptInfo *rel1, RelOptInfo *rel2);
+			  RelOptInfo *rel1, RelOptInfo *rel2, List *added_restrictlist);
 extern bool have_join_order_restriction(PlannerInfo *root,
 							RelOptInfo *rel1, RelOptInfo *rel2);
 
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index 11e7d4d..f799a5b 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -28,6 +28,10 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook;
 extern void get_relation_info(PlannerInfo *root, Oid relationObjectId,
 				  bool inhparent, RelOptInfo *rel);
 
+extern List *get_relation_constraints(PlannerInfo *root,
+						 Oid relationObjectId, RelOptInfo *rel,
+						 bool include_notnull);
+
 extern List *infer_arbiter_indexes(PlannerInfo *root);
 
 extern void estimate_rel_size(Relation rel, int32 *attr_widths,
