diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 26264cb..27f7348 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1964,12 +1964,78 @@ _copyOnConflictExpr(const OnConflictExpr *from)
 /* ****************************************************************
  *						relation.h copy functions
  *
- * We don't support copying RelOptInfo, IndexOptInfo, or Path nodes.
+ * We don't support copying RelOptInfo, IndexOptInfo or Path node.
  * There are some subsidiary structs that are useful to copy, though.
  * ****************************************************************
  */
 
 /*
+ * CopyPathFields
+ */
+static void
+CopyPathFields(const Path *from, Path *newnode)
+{
+	COPY_SCALAR_FIELD(pathtype);
+
+	/*
+	 * We use COPY_SCALAR_FIELDS() for parent instead of COPY_NODE_FIELDS()
+	 * because RelOptInfo contains Path which is made from, so
+	 * jump into the infinite loop.
+	 */
+	COPY_SCALAR_FIELD(parent);
+
+	COPY_SCALAR_FIELD(param_info);
+
+	COPY_SCALAR_FIELD(rows);
+	COPY_SCALAR_FIELD(startup_cost);
+	COPY_SCALAR_FIELD(total_cost);
+
+	COPY_NODE_FIELD(pathkeys);
+}
+
+/*
+ * _copyPath
+ */
+static Path *
+_copyPath(const Path *from)
+{
+	Path *newnode = makeNode(Path);
+
+	CopyPathFields(from, newnode);
+
+	return newnode;
+}
+
+/*
+ * _copyIndexPath
+ * XXX Need to make copy function for IndexOptInfo, etc.
+ */
+static IndexPath *
+_copyIndexPath(const IndexPath *from)
+{
+	IndexPath *newnode = makeNode(IndexPath);
+
+	CopyPathFields(&from->path, &newnode->path);
+
+	COPY_NODE_FIELD(indexinfo);
+	COPY_NODE_FIELD(indexclauses);
+	COPY_NODE_FIELD(indexquals);
+	COPY_NODE_FIELD(indexqualcols);
+	COPY_NODE_FIELD(indexorderbys);
+	COPY_NODE_FIELD(indexorderbycols);
+	COPY_SCALAR_FIELD(indexscandir);
+	COPY_SCALAR_FIELD(indextotalcost);
+	COPY_SCALAR_FIELD(indexselectivity);
+
+	return newnode;
+}
+
+/*
+ * XXX Need to make copy function for BitmapHeapPath, TidPath
+ * and GatherPath.
+ */
+
+/*
  * _copyPathKey
  */
 static PathKey *
@@ -4507,6 +4573,12 @@ copyObject(const void *from)
 			/*
 			 * RELATION NODES
 			 */
+		case T_Path:
+			retval = _copyPath(from);
+			break;
+		case T_IndexPath:
+			retval = _copyIndexPath(from);
+			break;
 		case T_PathKey:
 			retval = _copyPathKey(from);
 			break;
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index a35c881..6dec33c 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/prep.h"
+#include "optimizer/restrictinfo.h"
+#include "utils/lsyscache.h"
+
+typedef struct
+{
+	List	*joininfo;
+	bool	 is_substituted;
+} substitution_node_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_append_pullup_across_join(PlannerInfo *root,
+						  RelOptInfo *joinrel, RelOptInfo *outer_rel,
+						  RelOptInfo *inner_rel,
+						  List *restrictlist);
+
 
 /*
  * add_paths_to_joinrel
@@ -82,6 +100,18 @@ add_paths_to_joinrel(PlannerInfo *root,
 	bool		mergejoin_allowed = true;
 	ListCell   *lc;
 
+	/*
+	 * Try to pull-up Append across Join
+	 */
+	if (!IS_OUTER_JOIN(jointype))
+	{
+		try_append_pullup_across_join(root,
+									  joinrel,
+									  outerrel,
+									  innerrel,
+									  restrictlist);
+	}
+
 	extra.restrictlist = restrictlist;
 	extra.mergeclause_list = NIL;
 	extra.sjinfo = sjinfo;
@@ -1474,3 +1504,616 @@ select_mergejoin_clauses(PlannerInfo *root,
 
 	return result_list;
 }
+
+/*
+ * Try to substitute Var node according to join conditions.
+ * This process is from following steps.
+ *
+ * 1. Try to find whether Var node matches to left/right Var node of
+ *    one join condition.
+ * 2. If found, replace Var node with the opposite expression node of
+ *    the join condition.
+ *
+ * For example, let's assume that we have following expression and
+ * join condition.
+ * Expression       : A.num % 4 = 1
+ * Join condition   : A.num = B.data + 2
+ * In this case, we can get following expression.
+ *    (B.data + 2) % 4 = 1
+ */
+static Node *
+substitute_node_with_join_cond(Node *node, substitution_node_context *context)
+{
+	/* Failed to substitute. Abort. */
+	if (!context->is_substituted)
+		return (Node *) copyObject(node);
+
+	if (node == NULL)
+		return NULL;
+
+	if (IsA(node, Var))
+	{
+		List		*join_cond = context->joininfo;
+		ListCell	*lc;
+
+		Assert(list_length(join_cond) > 0);
+
+		foreach (lc, join_cond)
+		{
+			RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+			Expr *expr = rinfo->clause;
+
+			/*
+			 * Make sure whether OpExpr of Join clause means "=".
+			 */
+			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 node of join condition,
+				 * 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 node of join condition,
+				 * thus will be replaced with LEFT clause.
+				 */
+				return (Node *) copyObject(get_leftop(expr));
+			}
+		}
+
+		/* Unfortunately, substituting is failed. */
+		context->is_substituted = false;
+		return (Node *) copyObject(node);
+	}
+
+	return expression_tree_mutator(node, substitute_node_with_join_cond, context);
+}
+
+/*
+ * Create RestrictInfo_List from CHECK() constraints.
+ *
+ * This function creates list of RestrictInfo from CHECK() constraints
+ * according to expression of join clause.
+ *
+ * For example, let's assume that we have following CHECK() constraints
+ * for table A and join clause between table A and B.
+ * CHECK of table A      : 0 <= num AND num <= 100
+ * JOIN CLAUSE           : A.num = B.data
+ * In this conditions, we can get below by mathematical substituting.
+ *    0 <= B.data AND B.data <= 100
+ *
+ * We can use this restrictions to reduce result rows.
+ * This means that we can make Sort faster by reducing rows in MergeJoin,
+ * and also means that we can make HashTable smaller in HashJoin to fit
+ * to smaller work_mem environments.
+ */
+static List *
+create_rinfo_from_check_constr(PlannerInfo *root, List *joininfo,
+									 RelOptInfo *outer_rel, bool *succeed)
+{
+	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;
+	substitution_node_context	context;
+
+	if (list_length(check_constr) <= 0)
+	{
+		*succeed = true;
+		return NIL;
+	}
+
+	context.joininfo = joininfo;
+	context.is_substituted = true;
+
+	/*
+	 * Try to convert CHECK() constraints to filter expressions.
+	 */
+	foreach(lc, check_constr)
+	{
+		Node *substituted =
+				expression_tree_mutator((Node *) lfirst(lc),
+										substitute_node_with_join_cond,
+										(void *) &context);
+
+		if (!context.is_substituted)
+		{
+			*succeed = false;
+			list_free_deep(check_constr);
+			return NIL;
+		}
+		result = lappend(result, substituted);
+	}
+
+	Assert(list_length(check_constr) == list_length(result));
+	list_free_deep(check_constr);
+
+	return make_restrictinfos_from_actual_clauses(root, result);
+}
+
+/*
+ * Convert parent's join clauses to child's.
+ */
+static List *
+convert_parent_joinclauses_to_child(PlannerInfo *root, List *join_clauses,
+									RelOptInfo *outer_rel)
+{
+	AppendRelInfo	*appinfo = find_childrel_appendrelinfo(root, outer_rel);
+	List			*clauses_parent = get_actual_clauses(join_clauses);
+	List			*clauses_child = NIL;
+	ListCell		*lc;
+
+	foreach(lc, clauses_parent)
+	{
+		Node	*one_clause_child =
+					adjust_appendrel_attrs(root, lfirst(lc), appinfo);
+		clauses_child = lappend(clauses_child, one_clause_child);
+	}
+
+	return make_restrictinfos_from_actual_clauses(root, clauses_child);
+}
+
+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;
+}
+
+/*
+ * Copy path node for try_append_pullup_across_join()
+ *
+ * This includes following steps.
+ * (a) Prepare ParamPathInfo for RestrictInfos by CHECK constraints.
+ *     (See comment below.)
+ * (b) Copy path node and specify ParamPathInfo node made at (a) to it.
+ * (c) Re-calculate costs for path node copied at (b).
+ *
+ * NOTE : "nworkers" argument is used for the (Parallel)SeqScan node under
+ * the Gather node, which calls this function recursively. Therefore,
+ * "nworkers" argument should be 0, except for recursive call for
+ * the Gather node.
+ */
+static Path *
+copy_inner_path_for_append_pullup(PlannerInfo *root,
+		Path *orig_inner_path, RelOptInfo *inner_rel,
+		List *restrictlist_by_check_constr, int nworkers)
+{
+	/*
+	 * Prepare ParamPathInfo for RestrictInfos by CHECK constraints.
+	 *
+	 * For specifying additional restrictions to inner path,
+	 * we attach ParamPathInfo not to RelOptInfo but only to Path.
+	 *
+	 * PPI is generally used for parameterizing Scan node under Join node
+	 * for example, the purpose of this usage is to extract rows which
+	 * satisfies join conditions fixed one side.
+	 *
+	 * In this function, PPI is used for specifying additional restrictions
+	 * to inner path, and using join conditions and using converted CHECK()
+	 * constraints differ, however these don't differ from a point that
+	 * it extracts rows. Therefore it looks good to use PPI for this purpose.
+	 *
+	 */
+	ParamPathInfo	*newppi = makeNode(ParamPathInfo);
+	Path			*alter_inner_path;
+
+	newppi->ppi_req_outer = NULL;
+	newppi->ppi_rows =
+			get_parameterized_baserel_size(root,
+											inner_rel,
+											restrictlist_by_check_constr);
+	newppi->ppi_clauses = restrictlist_by_check_constr;
+
+	/* Copy Path of inner relation, and specify newppi to it. */
+	alter_inner_path = copyObject(orig_inner_path);
+	alter_inner_path->param_info = newppi;
+
+	/* Re-calculate costs of alter_inner_path */
+	switch (orig_inner_path->pathtype)
+	{
+	case T_SeqScan :
+		cost_seqscan(alter_inner_path, root, inner_rel, newppi, nworkers);
+		break;
+	case T_SampleScan :
+		cost_samplescan(alter_inner_path, root, inner_rel, newppi);
+		break;
+	case T_IndexScan :
+	case T_IndexOnlyScan :
+		{
+			IndexPath *ipath = (IndexPath *) alter_inner_path;
+
+			cost_index(ipath, root, 1.0);
+		}
+		break;
+	case T_BitmapHeapScan :
+		{
+			BitmapHeapPath *bpath =
+					(BitmapHeapPath *) alter_inner_path;
+
+			cost_bitmap_heap_scan(&bpath->path, root, inner_rel,
+					newppi, bpath->bitmapqual, 1.0);
+		}
+		break;
+	case T_TidScan :
+		{
+			TidPath *tpath = (TidPath *) alter_inner_path;
+
+			cost_tidscan(&tpath->path, root, inner_rel,
+					tpath->tidquals, newppi);
+		}
+		break;
+	case T_Gather :
+		{
+			GatherPath	*orig_gpath = (GatherPath *) orig_inner_path;
+			GatherPath	*alter_gpath = (GatherPath *) alter_inner_path;
+
+			Path	*alter_sub_path =
+					copy_inner_path_for_append_pullup(root,
+														orig_gpath->subpath,
+														inner_rel,
+														restrictlist_by_check_constr,
+														orig_gpath->num_workers);
+
+			alter_gpath->subpath = alter_sub_path;
+
+			cost_gather(alter_gpath, root, inner_rel, newppi);
+		}
+		break;
+	default:
+		Assert(false);
+		break;
+	}
+
+	return alter_inner_path;
+}
+
+/*
+ * try_append_pullup_across_join
+ *
+ * When outer-path of JOIN is AppendPath, we can rewrite path-tree with
+ * relocation of JoinPath across AppendPath, to generate equivalent
+ * results, like a diagram below.
+ * This adjustment gives us a few performance benefits when the relations
+ * scaned by sub-plan of Append-node have CHECK() constraints - typically,
+ * configured as partitioned table.
+ *
+ * In case of INNER JOIN with equivalent join condition, like A = B, we
+ * can exclude a part of inner rows that are obviously unreferenced, if
+ * outer side has CHECK() constraints that contains join keys.
+ * The CHECK() constraints ensures all the rows within outer relation
+ * satisfies the condition, in other words, any inner rows that does not
+ * satisfies the condition (with adjustment using equivalence of join keys)
+ * never match any outer rows.
+ *
+ * Once we can reduce number of inner rows, here are two beneficial scenario.
+ * 1. HashJoin may avoid split of hash-table even if preload of entire
+ *    inner relation exceeds work_mem.
+ * 2. MergeJoin may be able to take smaller scale of Sort, because quick-sort
+ *    is O(NlogN) scale problem. Reduction of rows to be sorted on both side
+ *    reduces CPU cost more than liner.
+ *
+ * [BEFORE]
+ * JoinPath ... (parent.X = inner.Y)
+ *  -> AppendPath on parent
+ *    -> ScanPath on child_1 ... CHECK(hash(X) % 3 = 0)
+ *    -> ScanPath on child_2 ... CHECK(hash(X) % 3 = 1)
+ *    -> ScanPath on child_3 ... CHECK(hash(X) % 3 = 2)
+ *  -> ScanPath on inner
+ *
+ * [AFTER]
+ * AppendPath
+ *  -> JoinPath ... (child_1.X = inner.Y)
+ *    -> ScanPath on child_1 ... CHECK(hash(X) % 3 = 0)
+ *    -> ScanPath on inner ... filter (hash(Y) % 3 = 0)
+ *  -> JoinPath ... (child_2.X = inner.Y)
+ *    -> ScanPath on child_2 ... CHECK(hash(X) % 3 = 1)
+ *    -> ScanPath on inner ... filter (hash(Y) % 3 = 1)
+ *  -> JoinPath ... (child_3.X = inner.Y)
+ *    -> ScanPath on child_3 ... CHECK(hash(X) % 3 = 2)
+ *    -> ScanPath on inner ... filter (hash(Y) % 3 = 2)
+ *
+ * Point to be focused on is filter condition attached on child relation's
+ * scan. It is clause of CHECK() constraint, but X is replaced by Y using
+ * equivalence join condition.
+ */
+static void
+try_append_pullup_across_join(PlannerInfo *root,
+				  RelOptInfo *joinrel, RelOptInfo *outer_rel,
+				  RelOptInfo *inner_rel,
+				  List *restrictlist)
+{
+	AppendPath	*outer_path;
+	ListCell	*lc_subpath;
+	ListCell	*lc_outer_path, *lc_inner_path;
+	List		*joinclauses_parent;
+	List		*alter_append_subpaths = NIL;
+	int			num_pathlist_join = list_length(joinrel->pathlist);
+
+	if (outer_rel->rtekind != RTE_RELATION)
+	{
+		elog(DEBUG1, "Outer Relation is not for table scan. Give up.");
+		return;
+	}
+
+	/*
+	 * Extract join clauses to convert CHECK() constraints.
+	 * We don't have to clobber this list to convert CHECK() constraints,
+	 * so we need to do only once.
+	 */
+	joinclauses_parent = extract_join_clauses(restrictlist, outer_rel, inner_rel);
+	if (list_length(joinclauses_parent) <= 0)
+	{
+		elog(DEBUG1, "No join clauses specified. Give up.");
+		return;
+	}
+
+	/*
+	 * We use ParamPathInfo for specifying additional RestrictInfos
+	 * created from CHECK constraints to inner relation. Therefore,
+	 * we can NOT perform append pull-up when PPI has already specified
+	 * to inner relation.
+	 */
+	if (list_length(inner_rel->ppilist) > 0)
+	{
+		elog(DEBUG1, "ParamPathInfo is already set in inner_rel. Can't pull-up.");
+		return;
+	}
+
+	foreach(lc_outer_path, outer_rel->pathlist)
+	{
+		/* When specified outer path is not an AppendPath, nothing to do here. */
+		if (!IsA(lfirst(lc_outer_path), AppendPath))
+		{
+			elog(DEBUG1, "Outer path is not an AppendPath. Do nothing.");
+			continue;
+		}
+
+		outer_path = (AppendPath *) lfirst(lc_outer_path);
+
+		foreach(lc_inner_path, inner_rel->pathlist)
+		{
+			switch (((Path *) lfirst(lc_inner_path))->pathtype)
+			{
+			case T_SeqScan :
+			case T_SampleScan :
+			case T_IndexScan :
+			case T_IndexOnlyScan :
+			case T_BitmapHeapScan :
+			case T_TidScan :
+			case T_Gather :
+				/* These types are supported. Pass through. */
+				break;
+			default :
+				{
+					elog(DEBUG1, "Type of Inner path is not supported yet."
+								" Give up.");
+					continue;
+				}
+			}
+
+			/*
+			 * Make new joinrel between each of outer path's sub-paths and
+			 * inner path.
+			 */
+			foreach(lc_subpath, outer_path->subpaths)
+			{
+				RelOptInfo	*orig_outer_sub_rel =
+						((Path *) lfirst(lc_subpath))->parent;
+				RelOptInfo	*alter_outer_sub_rel;
+				Path		*alter_inner_path = NULL;
+				List		*joinclauses_child;
+				List		*restrictlist_by_check_constr;
+				bool		is_valid;
+				List		**join_rel_level;
+
+				ListCell	*parentvars, *childvars;
+
+				Assert(!IS_DUMMY_REL(orig_outer_sub_rel));
+
+				/*
+				 * Join clause points parent's relid,
+				 * so we must change it to child's one.
+				 */
+				joinclauses_child =
+						convert_parent_joinclauses_to_child(root,
+													joinclauses_parent,
+													orig_outer_sub_rel);
+
+				/*
+				 * Make RestrictInfo list from CHECK() constraints of outer table.
+				 * "is_valid" indicates whether making RestrictInfo list succeeded
+				 * or not.
+				 */
+				restrictlist_by_check_constr =
+						create_rinfo_from_check_constr(root, joinclauses_child,
+													orig_outer_sub_rel, &is_valid);
+
+				if (!is_valid)
+				{
+					elog(DEBUG1, "Join clause doesn't match with CHECK() constraint. "
+									"Can't pull-up.");
+					list_free_deep(alter_append_subpaths);
+					list_free(joinclauses_parent);
+					return;
+				}
+
+				if (list_length(restrictlist_by_check_constr) > 0)
+				{
+					/* Copy Path of inner relation, and specify newppi to it. */
+					alter_inner_path = copy_inner_path_for_append_pullup(root,
+													(Path *) lfirst(lc_inner_path),
+													inner_rel,
+													restrictlist_by_check_constr,
+													0);
+
+					/*
+					 * Append this path to pathlist temporary.
+					 * This path will be removed after returning from make_join_rel().
+					 */
+					inner_rel->pathlist =
+							lappend(inner_rel->pathlist, alter_inner_path);
+					set_cheapest(inner_rel);
+				}
+
+				/*
+				 * Add relids, which are marked as needed not in child's attribute
+				 * but in parent's one, to child's attribute.
+				 *
+				 * attr_needed[] fields of all RelOptInfo under the Append node
+				 * are originally empty sets, therefore unintentional target list
+				 * is made by build_rel_tlist() for new joinrel; because
+				 * bms_noempty_difference() always returns false for outer
+				 * relation, no target is enumerated for it.
+				 *
+				 * We make really needed relids from parent RelOptInfo, and add
+				 * these relids to child's attr_needed[] to get intended target
+				 * list for new joinrel.
+				 *
+				 * This behavior may be harmless for considering other paths,
+				 * so we don't remove these relids from child after processing
+				 * append pulling-up.
+				 */
+				forboth(parentvars, outer_rel->reltargetlist,
+						childvars, orig_outer_sub_rel->reltargetlist)
+				{
+					Var		*parentvar = (Var *) lfirst(parentvars);
+					Var		*childvar = (Var *) lfirst(childvars);
+					int		p_ndx;
+					Relids	required_relids;
+
+					if (!IsA(parentvar, Var) || !IsA(childvar, Var))
+						continue;
+
+					Assert(find_base_rel(root, parentvar->varno) == outer_rel);
+					p_ndx = parentvar->varattno - outer_rel->min_attr;
+
+					required_relids = bms_del_members(
+							bms_copy(outer_rel->attr_needed[p_ndx]),
+							joinrel->relids);
+
+					if (!bms_is_empty(required_relids))
+					{
+						RelOptInfo	*baserel =
+								find_base_rel(root, childvar->varno);
+						int			c_ndx =
+								childvar->varattno - baserel->min_attr;
+
+						baserel->attr_needed[c_ndx] = bms_add_members(
+								baserel->attr_needed[c_ndx],
+								required_relids);
+					}
+				}
+
+				/*
+				 * NOTE: root->join_rel_level is used to track candidate of join
+				 * relations for each level, then these relations are consolidated
+				 * to one relation.
+				 * (See the comment in standard_join_search)
+				 *
+				 * Even though we construct RelOptInfo of child relations of the
+				 * Append node, these relations should not appear as candidate of
+				 * relations join in the later stage. So, we once save the list
+				 * during make_join_rel() for the child relations.
+				 */
+				join_rel_level = root->join_rel_level;
+				root->join_rel_level = NULL;
+
+				/*
+				 * Create new joinrel (as a sub-path of Append).
+				 */
+				alter_outer_sub_rel =
+						make_join_rel(root, orig_outer_sub_rel, inner_rel);
+
+				/* restore the join_rel_level */
+				root->join_rel_level = join_rel_level;
+
+				Assert(alter_outer_sub_rel != NULL);
+
+				if (alter_inner_path)
+				{
+					/*
+					 * Remove (temporary added) alter_inner_path from pathlist.
+					 *
+					 * The alter_inner_path may be inner/outer path of JoinPath
+					 * made by make_join_rel() above, thus we MUST NOT free
+					 * alter_inner_path itself.
+					 */
+					inner_rel->pathlist =
+							list_delete_ptr(inner_rel->pathlist, alter_inner_path);
+					set_cheapest(inner_rel);
+				}
+
+				if (IS_DUMMY_REL(alter_outer_sub_rel))
+				{
+					pfree(alter_outer_sub_rel);
+					continue;
+				}
+
+				/*
+				 * We must check if alter_outer_sub_rel has one or more paths.
+				 * add_path() sometime rejects to add new path to parent RelOptInfo.
+				 */
+				if (list_length(alter_outer_sub_rel->pathlist) <= 0)
+				{
+					/*
+					 * Sadly, No paths added. This means that pull-up is failed,
+					 * thus clean up here.
+					 */
+					list_free_deep(alter_append_subpaths);
+					pfree(alter_outer_sub_rel);
+					list_free(joinclauses_parent);
+					elog(DEBUG1, "Append pull-up failed.");
+					return;
+				}
+
+				set_cheapest(alter_outer_sub_rel);
+				Assert(alter_outer_sub_rel->cheapest_total_path != NULL);
+				alter_append_subpaths = lappend(alter_append_subpaths,
+											alter_outer_sub_rel->cheapest_total_path);
+			} /* End of foreach(outer_path->subpaths) */
+
+			/* Append pull-up is succeeded. Add path to original joinrel. */
+			add_path(joinrel,
+					(Path *) create_append_path(joinrel, alter_append_subpaths, NULL));
+
+			list_free(joinclauses_parent);
+			elog(DEBUG1, "Append pull-up succeeded.");
+		} /* End of foreach(inner_path->pathlist) */
+
+		/*
+		 * We check length of joinrel's pathlist here.
+		 * If it is equal to or lesser than before trying above,
+		 * all inner_paths are not suitable for append pulling-up,
+		 * thus we decide to abort trying anymore.
+		 */
+		if (list_length(joinrel->pathlist) > num_pathlist_join)
+		{
+			elog(DEBUG1, "No paths are added. Abort now.");
+			return;
+		}
+	} /* End of foreach(outer_path->pathlist) */
+}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 411b36c..b088ba9 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4235,8 +4235,18 @@ prepare_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys,
 				/*
 				 * Ignore child members unless they match the rel being
 				 * sorted.
+				 *
+				 * For append pull-up, we must not ignore child members
+				 * when this is called from make_sort_from_pathkeys().
+				 * Because "em_is_child" fields of all "ec_members" are true
+				 * in this case, thus it may fail to find pathkey
+				 * (and raise an error).
+				 *
+				 * In this condition, "relids" field may be NULL. So we don't
+				 * ignore child members when "relids" field is NULL.
 				 */
-				if (em->em_is_child &&
+				if (relids != NULL &&
+					em->em_is_child &&
 					!bms_equal(em->em_relids, relids))
 					continue;
 
@@ -4349,8 +4359,17 @@ find_ec_member_for_tle(EquivalenceClass *ec,
 
 		/*
 		 * Ignore child members unless they match the rel being sorted.
+		 *
+		 * For append pull-up, we must not ignore child members when this is
+		 * called from make_sort_from_pathkeys(). Because "em_is_child" fields
+		 * of all "ec_members" are true in this case, thus it will fail to
+		 * find pathkey (and raise an error).
+		 *
+		 * In this condition, "relids" field may be NULL. So we don't ignore
+		 * child members when "relids" field is NULL.
 		 */
-		if (em->em_is_child &&
+		if (relids != NULL &&
+			em->em_is_child &&
 			!bms_equal(em->em_relids, relids))
 			continue;
 
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)
