From 7051b9cb4760908e23e64969988b58fcb466868e Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Fri, 10 Feb 2017 13:50:14 +0530
Subject: [PATCH 12/14] Multi-level partitioned table expansion.

Construct inheritance hierarchy of a partitioned table to reflect the
partition hierarchy.

Commit d3cc37f1d got rid of AppendRelInfos for *all* partitioned child
child tables.  But we'd need one for recursively expandded partitioned
children.  In its absence, some of the later planning stages would not be
able to access the partitions below the first level, because they are not
directly linked to the root parent.  Fix that by allocating one.  That
means such children also get a RelOptInfo, which also wasn't allocated
previously.

Expanding partitioned table inheritance this new way means we need to
teach a few places how to traverse the hierarchy, such as propagating
lateral join information from the root table down the partition
hierarchy.

Further, inheritance_planner() needs refactoring to enable recursive
invocation of its core logic.  While the query_planner() can handle the
new partition hierarchical structure just fine because of the way the
relevant code works, inheritance_planner() would miss any child tables
below the first level, because it can't do recursion.  Refactor it so
that it can.
---
 src/backend/optimizer/plan/initsplan.c |  14 +-
 src/backend/optimizer/plan/planner.c   | 290 +++++++++++++++++++++------------
 src/backend/optimizer/prep/prepunion.c |  69 ++++++--
 src/test/regress/expected/inherit.out  |  22 +++
 src/test/regress/sql/inherit.sql       |  17 ++
 5 files changed, 292 insertions(+), 120 deletions(-)

diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index b4ac224a7a..20774b29fe 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -630,7 +630,19 @@ create_lateral_join_info(PlannerInfo *root)
 	{
 		RelOptInfo *brel = root->simple_rel_array[rti];
 
-		if (brel == NULL || brel->reloptkind != RELOPT_BASEREL)
+		if (brel == NULL)
+			continue;
+
+		/*
+		 * If an "other rel" RTE is a "partitioned table", we must propagate
+		 * the lateral info inherited all the way from the root parent to its
+		 * children. That's because the children are not linked directly with
+		 * the root parent via AppendRelInfo's unlike in case of a regular
+		 * inheritance set (see expand_inherited_rtentry()).  Failing to
+		 * do this would result in those children not getting marked with the
+		 * appropriate lateral info.
+		 */
+		if (brel->reloptkind != RELOPT_BASEREL && !brel->part_scheme)
 			continue;
 
 		if (root->simple_rte_array[rti]->inh)
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 4487683196..59cb559b9e 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -91,10 +91,28 @@ typedef struct
 	List	   *groupClause;	/* overrides parse->groupClause */
 } standard_qp_extra;
 
+/* Result of a given invocation of inheritance_planner_guts() */
+typedef struct
+{
+	Index 	nominalRelation;
+	List   *partitioned_rels;
+	List   *resultRelations;
+	List   *subpaths;
+	List   *subroots;
+	List   *withCheckOptionLists;
+	List   *returningLists;
+	List   *final_rtable;
+	List   *init_plans;
+	int		save_rel_array_size;
+	RelOptInfo **save_rel_array;
+} inheritance_planner_result;
+
 /* Local functions */
 static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind);
 static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode);
 static void inheritance_planner(PlannerInfo *root);
+static void inheritance_planner_guts(PlannerInfo *root,
+						 inheritance_planner_result *inhpres);
 static void grouping_planner(PlannerInfo *root, bool inheritance_update,
 				 double tuple_fraction);
 static void preprocess_rowmarks(PlannerInfo *root);
@@ -994,25 +1012,105 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr)
 static void
 inheritance_planner(PlannerInfo *root)
 {
+	inheritance_planner_result inhpres;
+	Query	   *parse = root->parse;
+	RelOptInfo *final_rel;
+	Index		rti;
+	int			final_rtable_len;
+	ListCell   *lc;
+	List	   *rowMarks;
+
+	/*
+	 * Away we go... Although the inheritance hierarchy to be processed might
+	 * be represented in a non-flat manner, some of the elements needed to
+	 * create the final ModifyTable path are always returned in a flat list
+	 * structure.
+	 */
+	memset(&inhpres, 0, sizeof(inhpres));
+	inheritance_planner_guts(root, &inhpres);
+
+	/* Result path must go into outer query's FINAL upperrel */
+	final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL);
+
+	/*
+	 * We don't currently worry about setting final_rel's consider_parallel
+	 * flag in this case, nor about allowing FDWs or create_upper_paths_hook
+	 * to get control here.
+	 */
+
+	/*
+	 * If we managed to exclude every child rel, return a dummy plan; it
+	 * doesn't even need a ModifyTable node.
+	 */
+	if (inhpres.subpaths == NIL)
+	{
+		set_dummy_rel_pathlist(final_rel);
+		return;
+	}
+
+	/*
+	 * Put back the final adjusted rtable into the master copy of the Query.
+	 * (We mustn't do this if we found no non-excluded children.)
+	 */
+	parse->rtable = inhpres.final_rtable;
+	root->simple_rel_array_size = inhpres.save_rel_array_size;
+	root->simple_rel_array = inhpres.save_rel_array;
+	/* Must reconstruct master's simple_rte_array, too */
+	final_rtable_len = list_length(inhpres.final_rtable);
+	root->simple_rte_array = (RangeTblEntry **)
+								palloc0((final_rtable_len + 1) *
+											sizeof(RangeTblEntry *));
+	rti = 1;
+	foreach(lc, inhpres.final_rtable)
+	{
+		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+
+		root->simple_rte_array[rti++] = rte;
+	}
+
+	/*
+	 * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will
+	 * have dealt with fetching non-locked marked rows, else we need to have
+	 * ModifyTable do that.
+	 */
+	if (parse->rowMarks)
+		rowMarks = NIL;
+	else
+		rowMarks = root->rowMarks;
+
+	/* Create Path representing a ModifyTable to do the UPDATE/DELETE work */
+	add_path(final_rel, (Path *)
+			 create_modifytable_path(root, final_rel,
+									 parse->commandType,
+									 parse->canSetTag,
+									 inhpres.nominalRelation,
+									 inhpres.partitioned_rels,
+									 inhpres.resultRelations,
+									 inhpres.subpaths,
+									 inhpres.subroots,
+									 inhpres.withCheckOptionLists,
+									 inhpres.returningLists,
+									 rowMarks,
+									 NULL,
+									 SS_assign_special_param(root)));
+}
+
+/*
+ * inheritance_planner_guts
+ *	  Recursive guts of inheritance_planner
+ */
+static void
+inheritance_planner_guts(PlannerInfo *root,
+						 inheritance_planner_result *inhpres)
+{
 	Query	   *parse = root->parse;
 	int			parentRTindex = parse->resultRelation;
 	Bitmapset  *subqueryRTindexes;
 	Bitmapset  *modifiableARIindexes;
-	int			nominalRelation = -1;
-	List	   *final_rtable = NIL;
-	int			save_rel_array_size = 0;
-	RelOptInfo **save_rel_array = NULL;
-	List	   *subpaths = NIL;
-	List	   *subroots = NIL;
-	List	   *resultRelations = NIL;
-	List	   *withCheckOptionLists = NIL;
-	List	   *returningLists = NIL;
-	List	   *rowMarks;
-	RelOptInfo *final_rel;
+	bool		nominalRelationSet = false;
 	ListCell   *lc;
 	Index		rti;
 	RangeTblEntry *parent_rte;
-	List		  *partitioned_rels = NIL;
 
 	Assert(parse->commandType != CMD_INSERT);
 
@@ -1080,7 +1178,10 @@ inheritance_planner(PlannerInfo *root)
 	 */
 	parent_rte = rt_fetch(parentRTindex, root->parse->rtable);
 	if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE)
-		nominalRelation = parentRTindex;
+	{
+		inhpres->nominalRelation = parentRTindex;
+		nominalRelationSet = true;
+	}
 
 	/*
 	 * And now we can get on with generating a plan for each child table.
@@ -1089,6 +1190,7 @@ inheritance_planner(PlannerInfo *root)
 	{
 		AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc);
 		PlannerInfo *subroot;
+		Index	childRTindex = appinfo->child_relid;
 		RangeTblEntry *child_rte;
 		RelOptInfo *sub_final_rel;
 		Path	   *subpath;
@@ -1120,7 +1222,7 @@ inheritance_planner(PlannerInfo *root)
 		 * child rel (they've already been transformed properly for that).
 		 */
 		parent_rte = rt_fetch(parentRTindex, subroot->parse->rtable);
-		child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable);
+		child_rte = rt_fetch(childRTindex, subroot->parse->rtable);
 		child_rte->securityQuals = parent_rte->securityQuals;
 		parent_rte->securityQuals = NIL;
 
@@ -1165,7 +1267,8 @@ inheritance_planner(PlannerInfo *root)
 		 * These won't be referenced, so there's no need to make them very
 		 * valid-looking.
 		 */
-		while (list_length(subroot->parse->rtable) < list_length(final_rtable))
+		while (list_length(subroot->parse->rtable) <
+										list_length(inhpres->final_rtable))
 			subroot->parse->rtable = lappend(subroot->parse->rtable,
 											 makeNode(RangeTblEntry));
 
@@ -1177,7 +1280,7 @@ inheritance_planner(PlannerInfo *root)
 		 * since subquery RTEs couldn't contain any references to the target
 		 * rel.
 		 */
-		if (final_rtable != NIL && subqueryRTindexes != NULL)
+		if (inhpres->final_rtable != NIL && subqueryRTindexes != NULL)
 		{
 			ListCell   *lr;
 
@@ -1222,6 +1325,47 @@ inheritance_planner(PlannerInfo *root)
 			}
 		}
 
+		/*
+		 * Recurse for a partitioned child table.  We shouldn't be planning
+		 * a partitioned RTE as a child member, which is what the code after
+		 * this block does.
+		 */
+		if (child_rte->inh)
+		{
+			inheritance_planner_result	child_inhpres;
+
+			Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE);
+
+			/* During the recursive invocation, this child is the parent. */
+			subroot->parse->resultRelation = childRTindex;
+			memset(&child_inhpres, 0, sizeof(child_inhpres));
+			inheritance_planner_guts(subroot, &child_inhpres);
+
+			inhpres->partitioned_rels = list_concat(inhpres->partitioned_rels,
+											child_inhpres.partitioned_rels);
+			inhpres->resultRelations = list_concat(inhpres->resultRelations,
+											child_inhpres.resultRelations);
+			inhpres->subpaths = list_concat(inhpres->subpaths,
+											child_inhpres.subpaths);
+			inhpres->subroots = list_concat(inhpres->subroots,
+											child_inhpres.subroots);
+			inhpres->withCheckOptionLists =
+									list_concat(inhpres->withCheckOptionLists,
+										child_inhpres.withCheckOptionLists);
+			inhpres->returningLists = list_concat(inhpres->returningLists,
+											child_inhpres.returningLists);
+			if (child_inhpres.final_rtable != NIL)
+				inhpres->final_rtable = child_inhpres.final_rtable;
+			if (child_inhpres.init_plans != NIL)
+				inhpres->init_plans = child_inhpres.init_plans;
+			if (child_inhpres.save_rel_array_size != 0)
+			{
+				inhpres->save_rel_array_size = child_inhpres.save_rel_array_size;
+				inhpres->save_rel_array = child_inhpres.save_rel_array;
+			}
+			continue;
+		}
+
 		/* There shouldn't be any OJ info to translate, as yet */
 		Assert(subroot->join_info_list == NIL);
 		/* and we haven't created PlaceHolderInfos, either */
@@ -1253,8 +1397,11 @@ inheritance_planner(PlannerInfo *root)
 		 * the duplicate child RTE added for the parent does not appear
 		 * anywhere else in the plan tree.
 		 */
-		if (nominalRelation < 0)
-			nominalRelation = appinfo->child_relid;
+		if (!nominalRelationSet)
+		{
+			inhpres->nominalRelation = childRTindex;
+			nominalRelationSet = true;
+		}
 
 		/*
 		 * Select cheapest path in case there's more than one.  We always run
@@ -1277,12 +1424,12 @@ inheritance_planner(PlannerInfo *root)
 		 * becomes the initial contents of final_rtable; otherwise, append
 		 * just its modified subquery RTEs to final_rtable.
 		 */
-		if (final_rtable == NIL)
-			final_rtable = subroot->parse->rtable;
+		if (inhpres->final_rtable == NIL)
+			inhpres->final_rtable = subroot->parse->rtable;
 		else
-			final_rtable = list_concat(final_rtable,
-									   list_copy_tail(subroot->parse->rtable,
-												 list_length(final_rtable)));
+			inhpres->final_rtable = list_concat(inhpres->final_rtable,
+										list_copy_tail(subroot->parse->rtable,
+										 list_length(inhpres->final_rtable)));
 
 		/*
 		 * We need to collect all the RelOptInfos from all child plans into
@@ -1291,109 +1438,48 @@ inheritance_planner(PlannerInfo *root)
 		 * have to propagate forward the RelOptInfos that were already built
 		 * in previous children.
 		 */
-		Assert(subroot->simple_rel_array_size >= save_rel_array_size);
-		for (rti = 1; rti < save_rel_array_size; rti++)
+		Assert(subroot->simple_rel_array_size >= inhpres->save_rel_array_size);
+		for (rti = 1; rti < inhpres->save_rel_array_size; rti++)
 		{
-			RelOptInfo *brel = save_rel_array[rti];
+			RelOptInfo *brel = inhpres->save_rel_array[rti];
 
 			if (brel)
 				subroot->simple_rel_array[rti] = brel;
 		}
-		save_rel_array_size = subroot->simple_rel_array_size;
-		save_rel_array = subroot->simple_rel_array;
+		inhpres->save_rel_array_size = subroot->simple_rel_array_size;
+		inhpres->save_rel_array = subroot->simple_rel_array;
 
 		/* Make sure any initplans from this rel get into the outer list */
-		root->init_plans = subroot->init_plans;
+		inhpres->init_plans = subroot->init_plans;
 
 		/* Build list of sub-paths */
-		subpaths = lappend(subpaths, subpath);
+		inhpres->subpaths = lappend(inhpres->subpaths, subpath);
 
 		/* Build list of modified subroots, too */
-		subroots = lappend(subroots, subroot);
+		inhpres->subroots = lappend(inhpres->subroots, subroot);
 
 		/* Build list of target-relation RT indexes */
-		resultRelations = lappend_int(resultRelations, appinfo->child_relid);
+		inhpres->resultRelations = lappend_int(inhpres->resultRelations,
+											   childRTindex);
 
 		/* Build lists of per-relation WCO and RETURNING targetlists */
 		if (parse->withCheckOptions)
-			withCheckOptionLists = lappend(withCheckOptionLists,
-										   subroot->parse->withCheckOptions);
+			inhpres->withCheckOptionLists =
+										lappend(inhpres->withCheckOptionLists,
+											subroot->parse->withCheckOptions);
 		if (parse->returningList)
-			returningLists = lappend(returningLists,
-									 subroot->parse->returningList);
-
+			inhpres->returningLists = lappend(inhpres->returningLists,
+											  subroot->parse->returningList);
 		Assert(!parse->onConflict);
 	}
 
 	if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		partitioned_rels = get_partitioned_child_rels(root, parentRTindex);
+		inhpres->partitioned_rels = get_partitioned_child_rels(root,
+															parentRTindex);
 		/* The root partitioned table is included as a child rel */
-		Assert(list_length(partitioned_rels) >= 1);
-	}
-
-	/* Result path must go into outer query's FINAL upperrel */
-	final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL);
-
-	/*
-	 * We don't currently worry about setting final_rel's consider_parallel
-	 * flag in this case, nor about allowing FDWs or create_upper_paths_hook
-	 * to get control here.
-	 */
-
-	/*
-	 * If we managed to exclude every child rel, return a dummy plan; it
-	 * doesn't even need a ModifyTable node.
-	 */
-	if (subpaths == NIL)
-	{
-		set_dummy_rel_pathlist(final_rel);
-		return;
+		Assert(list_length(inhpres->partitioned_rels) >= 1);
 	}
-
-	/*
-	 * Put back the final adjusted rtable into the master copy of the Query.
-	 * (We mustn't do this if we found no non-excluded children.)
-	 */
-	parse->rtable = final_rtable;
-	root->simple_rel_array_size = save_rel_array_size;
-	root->simple_rel_array = save_rel_array;
-	/* Must reconstruct master's simple_rte_array, too */
-	root->simple_rte_array = (RangeTblEntry **)
-		palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *));
-	rti = 1;
-	foreach(lc, final_rtable)
-	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
-
-		root->simple_rte_array[rti++] = rte;
-	}
-
-	/*
-	 * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will
-	 * have dealt with fetching non-locked marked rows, else we need to have
-	 * ModifyTable do that.
-	 */
-	if (parse->rowMarks)
-		rowMarks = NIL;
-	else
-		rowMarks = root->rowMarks;
-
-	/* Create Path representing a ModifyTable to do the UPDATE/DELETE work */
-	add_path(final_rel, (Path *)
-			 create_modifytable_path(root, final_rel,
-									 parse->commandType,
-									 parse->canSetTag,
-									 nominalRelation,
-									 partitioned_rels,
-									 resultRelations,
-									 subpaths,
-									 subroots,
-									 withCheckOptionLists,
-									 returningLists,
-									 rowMarks,
-									 NULL,
-									 SS_assign_special_param(root)));
 }
 
 /*--------------------
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 2bbef3acbd..cb7d9b0c43 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -97,7 +97,7 @@ static List *generate_append_tlist(List *colTypes, List *colCollations,
 					  List *input_tlists,
 					  List *refnames_tlist);
 static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist);
-static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte,
+static List *expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte,
 						 Index rti);
 static void make_inh_translation_list(Relation oldrelation,
 						  Relation newrelation,
@@ -1319,19 +1319,22 @@ expand_inherited_tables(PlannerInfo *root)
 	Index		nrtes;
 	Index		rti;
 	ListCell   *rl;
+	Query	   *parse = root->parse;
 
 	/*
 	 * expand_inherited_rtentry may add RTEs to parse->rtable; there is no
 	 * need to scan them since they can't have inh=true.  So just scan as far
 	 * as the original end of the rtable list.
 	 */
-	nrtes = list_length(root->parse->rtable);
-	rl = list_head(root->parse->rtable);
+	nrtes = list_length(parse->rtable);
+	rl = list_head(parse->rtable);
 	for (rti = 1; rti <= nrtes; rti++)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl);
+		List		  *appinfos;
 
-		expand_inherited_rtentry(root, rte, rti);
+		appinfos = expand_inherited_rtentry(root, rte, rti);
+		root->append_rel_list = list_concat(root->append_rel_list, appinfos);
 		rl = lnext(rl);
 	}
 }
@@ -1351,8 +1354,10 @@ expand_inherited_tables(PlannerInfo *root)
  *
  * A childless table is never considered to be an inheritance set; therefore
  * a parent RTE must always have at least two associated AppendRelInfos.
+ *
+ * Returns a list of AppendRelInfos, or NIL.
  */
-static void
+static List*
 expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 {
 	Query	   *parse = root->parse;
@@ -1369,12 +1374,12 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 
 	/* Does RT entry allow inheritance? */
 	if (!rte->inh)
-		return;
+		return NIL;
 	/* Ignore any already-expanded UNION ALL nodes */
 	if (rte->rtekind != RTE_RELATION)
 	{
 		Assert(rte->rtekind == RTE_SUBQUERY);
-		return;
+		return NIL;
 	}
 	/* Fast path for common case of childless table */
 	parentOID = rte->relid;
@@ -1382,7 +1387,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	{
 		/* Clear flag before returning */
 		rte->inh = false;
-		return;
+		return NIL;
 	}
 
 	/*
@@ -1406,8 +1411,19 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	else
 		lockmode = AccessShareLock;
 
-	/* Scan for all members of inheritance set, acquire needed locks */
-	inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+	/*
+	 * Expand partitioned table level-wise to help optimizations like
+	 * partition-wise join which match partitions at every level. Otherwise,
+	 * scan for all members of inheritance set. Acquire needed locks
+	 */
+	if (rte->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		inhOIDs = list_make1_oid(parentOID);
+		inhOIDs = list_concat(inhOIDs,
+							  find_inheritance_children(parentOID, lockmode));
+	}
+	else
+		inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
 
 	/*
 	 * Check that there's at least one descendant, else treat as no-child
@@ -1418,7 +1434,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	{
 		/* Clear flag before returning */
 		rte->inh = false;
-		return;
+		return NIL;
 	}
 
 	/*
@@ -1446,6 +1462,12 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 		Index		childRTindex;
 		AppendRelInfo *appinfo;
 
+		/*
+		 * If this child is a partitioned table, this contains AppendRelInfos
+		 * for its own children.
+		 */
+		List		  *myappinfos;
+
 		/* Open rel if needed; we already have required locks */
 		if (childOID != parentOID)
 			newrelation = heap_open(childOID, NoLock);
@@ -1479,7 +1501,12 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 		childrte = copyObject(rte);
 		childrte->relid = childOID;
 		childrte->relkind = newrelation->rd_rel->relkind;
-		childrte->inh = false;
+		/* A partitioned child will need to be expanded further. */
+		if (childOID != parentOID &&
+			childrte->relkind == RELKIND_PARTITIONED_TABLE)
+			childrte->inh = true;
+		else
+			childrte->inh = false;
 		childrte->requiredPerms = 0;
 		childrte->securityQuals = NIL;
 		parse->rtable = lappend(parse->rtable, childrte);
@@ -1487,9 +1514,9 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 
 		/*
 		 * Build an AppendRelInfo for this parent and child, unless the child
-		 * is a partitioned table.
+		 * RTE simply duplicates the parent *partitioned* table.
 		 */
-		if (childrte->relkind != RELKIND_PARTITIONED_TABLE)
+		if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh)
 		{
 			need_append = true;
 			appinfo = makeNode(AppendRelInfo);
@@ -1559,6 +1586,14 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 		/* Close child relations, but keep locks */
 		if (childOID != parentOID)
 			heap_close(newrelation, NoLock);
+
+		/* Expand partitioned children recursively. */
+		if (childrte->inh)
+		{
+			myappinfos = expand_inherited_rtentry(root, childrte,
+												  childRTindex);
+			appinfos = list_concat(appinfos, myappinfos);
+		}
 	}
 
 	heap_close(oldrelation, NoLock);
@@ -1574,7 +1609,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	{
 		/* Clear flag before returning */
 		rte->inh = false;
-		return;
+		return NIL;
 	}
 
 	/*
@@ -1595,8 +1630,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 		root->pcinfo_list = lappend(root->pcinfo_list, pcinfo);
 	}
 
-	/* Otherwise, OK to add to root->append_rel_list */
-	root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+	/* The following will be concatenated to root->append_rel_list. */
+	return appinfos;
 }
 
 /*
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 6163ed8117..7a969f2f10 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -625,6 +625,28 @@ select tableoid::regclass::text as relname, parted_tab.* from parted_tab order b
 (3 rows)
 
 drop table parted_tab;
+-- Check UPDATE with *multi-level partitioned* inherited target
+create table mlparted_tab (a int, b char, c text) partition by list (a);
+create table mlparted_tab_part1 partition of mlparted_tab for values in (1);
+create table mlparted_tab_part2 partition of mlparted_tab for values in (2) partition by list (b);
+create table mlparted_tab_part3 partition of mlparted_tab for values in (3);
+create table mlparted_tab_part2a partition of mlparted_tab_part2 for values in ('a');
+create table mlparted_tab_part2b partition of mlparted_tab_part2 for values in ('b');
+insert into mlparted_tab values (1, 'a'), (2, 'a'), (2, 'b'), (3, 'a');
+update mlparted_tab mlp set c = 'xxx'
+from
+  (select a from some_tab union all select a+1 from some_tab) ss (a)
+where (mlp.a = ss.a and mlp.b = 'b') or mlp.a = 3;
+select tableoid::regclass::text as relname, mlparted_tab.* from mlparted_tab order by 1,2;
+       relname       | a | b |  c  
+---------------------+---+---+-----
+ mlparted_tab_part1  | 1 | a | 
+ mlparted_tab_part2a | 2 | a | 
+ mlparted_tab_part2b | 2 | b | xxx
+ mlparted_tab_part3  | 3 | a | xxx
+(4 rows)
+
+drop table mlparted_tab;
 drop table some_tab cascade;
 NOTICE:  drop cascades to table some_tab_child
 /* Test multiple inheritance of column defaults */
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index d43b75c4a7..b814a4c471 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -154,6 +154,23 @@ where parted_tab.a = ss.a;
 select tableoid::regclass::text as relname, parted_tab.* from parted_tab order by 1,2;
 
 drop table parted_tab;
+
+-- Check UPDATE with *multi-level partitioned* inherited target
+create table mlparted_tab (a int, b char, c text) partition by list (a);
+create table mlparted_tab_part1 partition of mlparted_tab for values in (1);
+create table mlparted_tab_part2 partition of mlparted_tab for values in (2) partition by list (b);
+create table mlparted_tab_part3 partition of mlparted_tab for values in (3);
+create table mlparted_tab_part2a partition of mlparted_tab_part2 for values in ('a');
+create table mlparted_tab_part2b partition of mlparted_tab_part2 for values in ('b');
+insert into mlparted_tab values (1, 'a'), (2, 'a'), (2, 'b'), (3, 'a');
+
+update mlparted_tab mlp set c = 'xxx'
+from
+  (select a from some_tab union all select a+1 from some_tab) ss (a)
+where (mlp.a = ss.a and mlp.b = 'b') or mlp.a = 3;
+select tableoid::regclass::text as relname, mlparted_tab.* from mlparted_tab order by 1,2;
+
+drop table mlparted_tab;
 drop table some_tab cascade;
 
 /* Test multiple inheritance of column defaults */
-- 
2.11.0

