On 2017/03/13 19:24, Amit Langote wrote:
> On 2017/03/10 17:57, Amit Langote wrote:
>> On 2017/03/08 22:36, Robert Haas wrote:
>>> On Wed, Mar 8, 2017 at 5:36 AM, Amit Langote wrote:
>>>>> -    rel = mtstate->resultRelInfo->ri_RelationDesc;
>>>>> +    nominalRTE = rt_fetch(node->nominalRelation, estate->es_range_table);
>>>>> +    nominalRel = heap_open(nominalRTE->relid, NoLock);
>>>>>
>>>>> No lock?
>>>>
>>>> Hmm, I think I missed that a partitioned parent table would not be locked
>>>> by the *executor* at all after applying this patch.  As of now, InitPlan()
>>>> takes care of locking all the result relations in the
>>>> PlannedStmt.resultRelations list.  This patch removes partitioned
>>>> parent(s) from appearing in this list.  But that makes me wonder - aren't
>>>> the locks taken by expand_inherited_rtentry() kept long enough?  Why does
>>>> InitPlan need to acquire them again?  I see this comment in
>>>> expand_inherited_rtentry:
>>>
>>> Parse-time locks, plan-time locks, and execution-time locks are all
>>> separate.  See the header comments for AcquireRewriteLocks in
>>> rewriteHandler.c; think about a view, where the parsed query has been
>>> stored and is later rewritten and planned.  Similarly, a plan can be
>>> reused even after the transaction that created that plan has
>>> committed; see AcquireExecutorLocks in plancache.c.
>>
>> Thanks for those references.
>>
>> I took a step back here and thought a bit more about the implications this
>> patch.  It occurred to me that the complete absence of partitioned table
>> RT entries in the plan-tree has more consequences than I originally
>> imagined.  I will post an updated patch by Monday latest.
> 
> Here is the updated patch.
> 
> Since this patch proposes to avoid creating scan nodes for non-leaf tables
> in a partition tree, they won't be referenced anywhere in the resulting
> plan tree.  So the executor will not lock those tables in the
> select/update/delete cases. Insert is different since we lock all tables
> in the partition tree when setting up tuple-routing in
> ExecInitModifyTable.  Not taking executor locks on the tables means that
> the cached plans that should be invalidated upon adding/removing a
> partition somewhere in the partition tree won't be.
> 
> First I thought that we could remember just the root table RT index using
> a new Index field partitionRoot in Append, MergeAppend, and ModifyTable
> nodes and use it to locate and lock the root table during executor node
> initialization.  But soon realized that that's not enough, because it
> ignores the fact that adding/removing partitions at lower levels does not
> require taking a lock on the root table; only the immediate parent.  So
> any cached select/update/delete plans won't be invalidated when a new
> lower-level partition is added/removed, because the immediate parent would
> not have been added to the query's range table and hence the
> PlannedStmt.relationOids list.  Remember that the latter is used by
> plancache.c to remember the relations that a given cached plan depends on
> remaining unchanged.  So the patch now adds a list member called
> partitioned_rels to Append, MergeAppend, and ModifyTable nodes and stores
> the RT indexes of all the non-leaf tables in a partition tree with root
> table RT index at the head (note that these RT indexes are of the RTEs
> added by expand_inherited_rtenrty; also see below).  Since the
> partitioned_rels list is constructed when building paths and must be
> propagated to the plan nodes, the same field is also present in the
> corresponding Path nodes.  ExecInit* routines for the aforementioned nodes
> now locate and lock the non-leaf tables using the RT indexes in
> partitioned_rels.  Leaf tables are locked, as before, either by InitPlan
> (update/delete result relations case) or by ExecInitAppend or
> ExecInitMergeAppend when initializing the appendplans/mergeplans (select
> case).
> 
> The previous proposal was for expand_inherited_rtentry to not create RT
> entries and AppendRelInfo's for the non-leaf tables, but I think that
> doesn't work, as I tried to explain above.  We need RTEs because that
> seems to be the only way currently for informing the executor of the
> non-leaf tables. We need AppendRelInfo's to store the RT indexes of those
> RTEs for the latter planning steps to collect them in partitioned_rels
> mentioned above. So with the latest patch, we do create the RT entry and
> AppendRelInfo for non-leaf tables.  AppendRelInfo created in this case is
> a minimal one; only parent_relid and child_relid are valid.  To make the
> latter planning steps ignore these minimal AppendRelInfo's, every
> AppendRelInfo is now marked with child_relkind.  Only
> set_append_rel_pathlist() and inheritance_planner() process them to
> collect the child_relid into the partitioned_rels list to be stored in
> AppendPath/MergeAppendPath and ModifyTablePath, respectively.

Sorry, forgot to attach the patches themselves.  Attached this time.

Thanks,
Amit
>From c1b514b9214775977ef23e0206962f1e8a5e1966 Mon Sep 17 00:00:00 2001
From: amit <amitlangot...@gmail.com>
Date: Fri, 10 Mar 2017 13:48:31 +0900
Subject: [PATCH 1/2] Avoid creating scan nodes for partitioned tables

* Currently, we create scan nodes for inheritance parents in their role
  as an inheritance set member.  Partitioned tables do not contain any
  data, so it's useless to create scan nodes for them.  So we generate
  only a minimal AppendRelInfo for partitioned child tables.  No need
  to generate translated_vars, for example.  We need to hold on to
  the child_relid's, because we want to save it into the Append/
  MergeAppend/ModifyTable node we will generate for the inheritance
  tree.

* The planner prep phase turns off inheritance on the parent RTE if
  there isn't at least one child member other than the parent itself
  which is also a child member.  That means set_rel_size/pathlist will
  not create an Append node but a scan node for the parent RTE.  We want
  to avoid that if the parent is a partitioned  table, by noticing that
  in set_rel_size().  Per suggestion from Ashutosh Bapat.

* Add a new partitioned_rels field to ModifyTable, Append, and MergeAppend
  nodes, which holds the RT indexes of all the non-leaf tables in a
  partition tree.  Use the same to lock those tables during execution.
  The usual method employed by InitPlan to lock the tables does not work
  for partitioned tables, because they do not appear in the subplans list
  of either of those nodes.

* In inheritance_planner, set ModifyTable.nominalRelation to parent RT
  index in the partitioned inheritance parent case, so that EXPLAIN uses
  the root tables's original RT index to label a given ModifyTable node.

Regression test outputs are adjusted to match the new plan shape.
---
 src/backend/executor/nodeAppend.c         |  39 +++++++++
 src/backend/executor/nodeMergeAppend.c    |  39 +++++++++
 src/backend/executor/nodeModifyTable.c    |  36 +++++++-
 src/backend/nodes/copyfuncs.c             |   4 +
 src/backend/nodes/equalfuncs.c            |   1 +
 src/backend/nodes/outfuncs.c              |   7 ++
 src/backend/nodes/readfuncs.c             |   3 +
 src/backend/optimizer/path/allpaths.c     |  49 +++++++++--
 src/backend/optimizer/path/joinrels.c     |   2 +-
 src/backend/optimizer/plan/createplan.c   |  14 ++--
 src/backend/optimizer/plan/initsplan.c    |   6 ++
 src/backend/optimizer/plan/planner.c      |  61 +++++++++++---
 src/backend/optimizer/plan/setrefs.c      |  12 +++
 src/backend/optimizer/prep/prepunion.c    |  59 ++++++++-----
 src/backend/optimizer/util/pathnode.c     |  10 ++-
 src/backend/utils/cache/plancache.c       |   7 ++
 src/include/nodes/plannodes.h             |   6 ++
 src/include/nodes/relation.h              |   4 +
 src/include/optimizer/pathnode.h          |   8 +-
 src/test/regress/expected/inherit.out     | 134 +++++++++++-------------------
 src/test/regress/expected/tablesample.out |   4 +-
 src/test/regress/sql/inherit.sql          |  22 +++++
 22 files changed, 386 insertions(+), 141 deletions(-)

diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c
index 6986caee6b..26be346199 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -59,6 +59,8 @@
 
 #include "executor/execdebug.h"
 #include "executor/nodeAppend.h"
+#include "parser/parsetree.h"
+#include "storage/lmgr.h"
 
 static bool exec_append_initialize_next(AppendState *appendstate);
 
@@ -129,6 +131,43 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
 	Assert(!(eflags & EXEC_FLAG_MARK));
 
 	/*
+	 * Lock the non-leaf tables in the partition tree controlled by this
+	 * node; leaf tables will be locked below when initializing the node's
+	 * subplans.  Note that this occurs only if the root inheritance parent
+	 * is a partitioned table.
+	 */
+	if (node->partitioned_rels)
+	{
+		foreach(lc, node->partitioned_rels)
+		{
+			Index	rti = lfirst_int(lc);
+			PlanRowMark *rc = NULL;
+			ListCell   *l;
+			Oid			relid;
+			LOCKMODE	lockmode;
+
+			relid = getrelid(rti, estate->es_range_table);
+
+			/* If there is a RowMark, we better adjust the lockmode */
+			foreach(l, estate->es_plannedstmt->rowMarks)
+			{
+				rc = (PlanRowMark *) lfirst(l);
+
+				if (rc->rti == rti)
+					break;
+			}
+
+			Assert(rc == NULL || rc->isParent);
+			if (rc != NULL && RowMarkRequiresRowShareLock(rc->markType))
+				lockmode = RowShareLock;
+			else
+				lockmode = AccessShareLock;
+
+			LockRelationOid(relid, lockmode);
+		}
+	}
+
+	/*
 	 * Set up empty vector of subplan states
 	 */
 	nplans = list_length(node->appendplans);
diff --git a/src/backend/executor/nodeMergeAppend.c b/src/backend/executor/nodeMergeAppend.c
index 7a20bf07a4..0b5f82cf20 100644
--- a/src/backend/executor/nodeMergeAppend.c
+++ b/src/backend/executor/nodeMergeAppend.c
@@ -42,6 +42,8 @@
 #include "executor/nodeMergeAppend.h"
 
 #include "lib/binaryheap.h"
+#include "parser/parsetree.h"
+#include "storage/lmgr.h"
 
 /*
  * We have one slot for each item in the heap array.  We use SlotNumber
@@ -72,6 +74,43 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags)
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
 	/*
+	 * Lock the non-leaf tables in the partition tree controlled by this
+	 * node; leaf tables will be locked below when initializing the node's
+	 * subplans.  Note that this occurs only if the root inheritance parent
+	 * is a partitioned table.
+	 */
+	if (node->partitioned_rels)
+	{
+		foreach(lc, node->partitioned_rels)
+		{
+			Index	rti = lfirst_int(lc);
+			PlanRowMark *rc = NULL;
+			ListCell   *l;
+			Oid			relid;
+			LOCKMODE	lockmode;
+
+			relid = getrelid(rti, estate->es_range_table);
+
+			/* If there is a RowMark, we better adjust the lockmode */
+			foreach(l, estate->es_plannedstmt->rowMarks)
+			{
+				rc = (PlanRowMark *) lfirst(l);
+
+				if (rc->rti == rti)
+					break;
+			}
+
+			Assert(rc == NULL || rc->isParent);
+			if (rc != NULL && RowMarkRequiresRowShareLock(rc->markType))
+				lockmode = RowShareLock;
+			else
+				lockmode = AccessShareLock;
+
+			LockRelationOid(relid, lockmode);
+		}
+	}
+
+	/*
 	 * Set up empty vector of subplan states
 	 */
 	nplans = list_length(node->mergeplans);
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 95e158970c..9f548bdfac 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -45,6 +45,7 @@
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
+#include "parser/parsetree.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -1725,8 +1726,34 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	estate->es_result_relation_info = saved_resultRelInfo;
 
+	/*
+	 * During UPDATE/DELETE of a partitioned table, node->partitioned_rels
+	 * contains the RT indexes of non-leaf tables of the partition tree.
+	 * Leaf tables have already been locked by InitPlan when setting up the
+	 * ResultRelInfo's.
+	 */
+	if (node->partitioned_rels)
+	{
+		Index	root_table_rti;
+		Oid		root_table_oid;
+
+		foreach(l, node->partitioned_rels)
+		{
+			Oid		relid;
+
+			relid = getrelid(lfirst_int(l), estate->es_range_table);
+			LockRelationOid(relid, RowExclusiveLock);
+		}
+
+		/* We have the root table RT index at the head of the list */
+		root_table_rti = linitial_int(node->partitioned_rels);
+		root_table_oid = getrelid(root_table_rti, estate->es_range_table);
+		rel = heap_open(root_table_oid, NoLock);	/* locked just above */
+	}
+	else
+		rel = mtstate->resultRelInfo->ri_RelationDesc;
+
 	/* Build state for INSERT tuple routing */
-	rel = mtstate->resultRelInfo->ri_RelationDesc;
 	if (operation == CMD_INSERT &&
 		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
@@ -1898,6 +1925,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	}
 
 	/*
+	 * Close the root partitioned rel if we opened it above, but keep the
+	 * lock.
+	 */
+	if (rel != mtstate->resultRelInfo->ri_RelationDesc)
+		heap_close(rel, NoLock);
+
+	/*
 	 * If needed, Initialize target list, projection and qual for ON CONFLICT
 	 * DO UPDATE.
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index bfc2ac1716..8bcac4f9dd 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -200,6 +200,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_SCALAR_FIELD(operation);
 	COPY_SCALAR_FIELD(canSetTag);
 	COPY_SCALAR_FIELD(nominalRelation);
+	COPY_NODE_FIELD(partitioned_rels);
 	COPY_NODE_FIELD(resultRelations);
 	COPY_SCALAR_FIELD(resultRelIndex);
 	COPY_NODE_FIELD(plans);
@@ -235,6 +236,7 @@ _copyAppend(const Append *from)
 	/*
 	 * copy remainder of node
 	 */
+	COPY_NODE_FIELD(partitioned_rels);
 	COPY_NODE_FIELD(appendplans);
 
 	return newnode;
@@ -256,6 +258,7 @@ _copyMergeAppend(const MergeAppend *from)
 	/*
 	 * copy remainder of node
 	 */
+	COPY_NODE_FIELD(partitioned_rels);
 	COPY_NODE_FIELD(mergeplans);
 	COPY_SCALAR_FIELD(numCols);
 	COPY_POINTER_FIELD(sortColIdx, from->numCols * sizeof(AttrNumber));
@@ -2198,6 +2201,7 @@ _copyAppendRelInfo(const AppendRelInfo *from)
 	COPY_SCALAR_FIELD(child_relid);
 	COPY_SCALAR_FIELD(parent_reltype);
 	COPY_SCALAR_FIELD(child_reltype);
+	COPY_SCALAR_FIELD(child_relkind);
 	COPY_NODE_FIELD(translated_vars);
 	COPY_SCALAR_FIELD(parent_reloid);
 
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 54e9c983a0..93a13952e5 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -889,6 +889,7 @@ _equalAppendRelInfo(const AppendRelInfo *a, const AppendRelInfo *b)
 	COMPARE_SCALAR_FIELD(child_relid);
 	COMPARE_SCALAR_FIELD(parent_reltype);
 	COMPARE_SCALAR_FIELD(child_reltype);
+	COMPARE_SCALAR_FIELD(child_relkind);
 	COMPARE_NODE_FIELD(translated_vars);
 	COMPARE_SCALAR_FIELD(parent_reloid);
 
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 7418fbeded..afa36a00ec 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -344,6 +344,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_ENUM_FIELD(operation, CmdType);
 	WRITE_BOOL_FIELD(canSetTag);
 	WRITE_UINT_FIELD(nominalRelation);
+	WRITE_NODE_FIELD(partitioned_rels);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_INT_FIELD(resultRelIndex);
 	WRITE_NODE_FIELD(plans);
@@ -368,6 +369,7 @@ _outAppend(StringInfo str, const Append *node)
 
 	_outPlanInfo(str, (const Plan *) node);
 
+	WRITE_NODE_FIELD(partitioned_rels);
 	WRITE_NODE_FIELD(appendplans);
 }
 
@@ -380,6 +382,7 @@ _outMergeAppend(StringInfo str, const MergeAppend *node)
 
 	_outPlanInfo(str, (const Plan *) node);
 
+	WRITE_NODE_FIELD(partitioned_rels);
 	WRITE_NODE_FIELD(mergeplans);
 
 	WRITE_INT_FIELD(numCols);
@@ -1808,6 +1811,7 @@ _outAppendPath(StringInfo str, const AppendPath *node)
 
 	_outPathInfo(str, (const Path *) node);
 
+	WRITE_NODE_FIELD(partitioned_rels);
 	WRITE_NODE_FIELD(subpaths);
 }
 
@@ -1818,6 +1822,7 @@ _outMergeAppendPath(StringInfo str, const MergeAppendPath *node)
 
 	_outPathInfo(str, (const Path *) node);
 
+	WRITE_NODE_FIELD(partitioned_rels);
 	WRITE_NODE_FIELD(subpaths);
 	WRITE_FLOAT_FIELD(limit_tuples, "%.0f");
 }
@@ -2023,6 +2028,7 @@ _outModifyTablePath(StringInfo str, const ModifyTablePath *node)
 	WRITE_ENUM_FIELD(operation, CmdType);
 	WRITE_BOOL_FIELD(canSetTag);
 	WRITE_UINT_FIELD(nominalRelation);
+	WRITE_NODE_FIELD(partitioned_rels);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(subpaths);
 	WRITE_NODE_FIELD(subroots);
@@ -2415,6 +2421,7 @@ _outAppendRelInfo(StringInfo str, const AppendRelInfo *node)
 	WRITE_UINT_FIELD(child_relid);
 	WRITE_OID_FIELD(parent_reltype);
 	WRITE_OID_FIELD(child_reltype);
+	WRITE_CHAR_FIELD(child_relkind);
 	WRITE_NODE_FIELD(translated_vars);
 	WRITE_OID_FIELD(parent_reloid);
 }
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index d3bbc02f24..f2ec174216 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1535,6 +1535,7 @@ _readModifyTable(void)
 	READ_ENUM_FIELD(operation, CmdType);
 	READ_BOOL_FIELD(canSetTag);
 	READ_UINT_FIELD(nominalRelation);
+	READ_NODE_FIELD(partitioned_rels);
 	READ_NODE_FIELD(resultRelations);
 	READ_INT_FIELD(resultRelIndex);
 	READ_NODE_FIELD(plans);
@@ -1564,6 +1565,7 @@ _readAppend(void)
 
 	ReadCommonPlan(&local_node->plan);
 
+	READ_NODE_FIELD(partitioned_rels);
 	READ_NODE_FIELD(appendplans);
 
 	READ_DONE();
@@ -1579,6 +1581,7 @@ _readMergeAppend(void)
 
 	ReadCommonPlan(&local_node->plan);
 
+	READ_NODE_FIELD(partitioned_rels);
 	READ_NODE_FIELD(mergeplans);
 	READ_INT_FIELD(numCols);
 	READ_ATTRNUMBER_ARRAY(sortColIdx, local_node->numCols);
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index b263359fde..566d1860aa 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -95,7 +95,8 @@ static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 						Index rti, RangeTblEntry *rte);
 static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
 						   List *live_childrels,
-						   List *all_child_pathkeys);
+						   List *all_child_pathkeys,
+						   List *partitioned_rels);
 static Path *get_cheapest_parameterized_child_path(PlannerInfo *root,
 									  RelOptInfo *rel,
 									  Relids required_outer);
@@ -344,6 +345,14 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
 					/* Foreign table */
 					set_foreign_size(root, rel, rte);
 				}
+				else if (rte->relkind == RELKIND_PARTITIONED_TABLE)
+				{
+					/*
+					 * A partitioned table without leaf partitions is marked
+					 * as a dummy rel.
+					 */
+					set_dummy_rel_pathlist(rel);
+				}
 				else if (rte->tablesample != NULL)
 				{
 					/* Sampled relation */
@@ -878,6 +887,10 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 		if (appinfo->parent_relid != parentRTindex)
 			continue;
 
+		/* Nothing to see here. */
+		if (appinfo->child_relkind == RELKIND_PARTITIONED_TABLE)
+			continue;
+
 		childRTindex = appinfo->child_relid;
 		childRTE = root->simple_rte_array[childRTindex];
 
@@ -1189,6 +1202,7 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 	List	   *all_child_pathkeys = NIL;
 	List	   *all_child_outers = NIL;
 	ListCell   *l;
+	List	   *partitioned_rels = NIL;
 
 	/*
 	 * Generate access paths for each member relation, and remember the
@@ -1208,6 +1222,17 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 		if (appinfo->parent_relid != parentRTindex)
 			continue;
 
+		/*
+		 * Nothing to do here for partitioned child tables; just remember
+		 * the RT index to put into the AppendPath.
+		 */
+		if (appinfo->child_relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			partitioned_rels = lappend_int(partitioned_rels,
+										   appinfo->child_relid);
+			continue;
+		}
+
 		/* Re-locate the child RTE and RelOptInfo */
 		childRTindex = appinfo->child_relid;
 		childRTE = root->simple_rte_array[childRTindex];
@@ -1327,7 +1352,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 	 * if we have zero or one live subpath due to constraint exclusion.)
 	 */
 	if (subpaths_valid)
-		add_path(rel, (Path *) create_append_path(rel, subpaths, NULL, 0));
+		add_path(rel, (Path *) create_append_path(rel, subpaths, NULL, 0,
+												  partitioned_rels));
 
 	/*
 	 * Consider an append of partial unordered, unparameterized partial paths.
@@ -1354,7 +1380,7 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 
 		/* Generate a partial append path. */
 		appendpath = create_append_path(rel, partial_subpaths, NULL,
-										parallel_workers);
+										parallel_workers, partitioned_rels);
 		add_partial_path(rel, (Path *) appendpath);
 	}
 
@@ -1364,7 +1390,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 	 */
 	if (subpaths_valid)
 		generate_mergeappend_paths(root, rel, live_childrels,
-								   all_child_pathkeys);
+								   all_child_pathkeys,
+								   partitioned_rels);
 
 	/*
 	 * Build Append paths for each parameterization seen among the child rels.
@@ -1406,7 +1433,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 
 		if (subpaths_valid)
 			add_path(rel, (Path *)
-					 create_append_path(rel, subpaths, required_outer, 0));
+					 create_append_path(rel, subpaths, required_outer, 0,
+										partitioned_rels));
 	}
 }
 
@@ -1436,7 +1464,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 static void
 generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
 						   List *live_childrels,
-						   List *all_child_pathkeys)
+						   List *all_child_pathkeys,
+						   List *partitioned_rels)
 {
 	ListCell   *lcp;
 
@@ -1500,13 +1529,15 @@ generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
 														rel,
 														startup_subpaths,
 														pathkeys,
-														NULL));
+														NULL,
+														partitioned_rels));
 		if (startup_neq_total)
 			add_path(rel, (Path *) create_merge_append_path(root,
 															rel,
 															total_subpaths,
 															pathkeys,
-															NULL));
+															NULL,
+															partitioned_rels));
 	}
 }
 
@@ -1639,7 +1670,7 @@ set_dummy_rel_pathlist(RelOptInfo *rel)
 	rel->pathlist = NIL;
 	rel->partial_pathlist = NIL;
 
-	add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0));
+	add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0, NIL));
 
 	/*
 	 * We set the cheapest path immediately, to ensure that IS_DUMMY_REL()
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0d0068360c..7dfbb7a970 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -1197,7 +1197,7 @@ mark_dummy_rel(RelOptInfo *rel)
 	rel->partial_pathlist = NIL;
 
 	/* Set up the dummy path */
-	add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0));
+	add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0, NIL));
 
 	/* Set or update cheapest_total_path and related fields */
 	set_cheapest(rel);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index d002e6d567..6b29ccc0c4 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -199,7 +199,7 @@ static CteScan *make_ctescan(List *qptlist, List *qpqual,
 			 Index scanrelid, int ctePlanId, int cteParam);
 static WorkTableScan *make_worktablescan(List *qptlist, List *qpqual,
 				   Index scanrelid, int wtParam);
-static Append *make_append(List *appendplans, List *tlist);
+static Append *make_append(List *appendplans, List *tlist, List *partitioned_rels);
 static RecursiveUnion *make_recursive_union(List *tlist,
 					 Plan *lefttree,
 					 Plan *righttree,
@@ -273,7 +273,7 @@ static Result *make_result(List *tlist, Node *resconstantqual, Plan *subplan);
 static ProjectSet *make_project_set(List *tlist, Plan *subplan);
 static ModifyTable *make_modifytable(PlannerInfo *root,
 				 CmdType operation, bool canSetTag,
-				 Index nominalRelation,
+				 Index nominalRelation, List *partitioned_rels,
 				 List *resultRelations, List *subplans,
 				 List *withCheckOptionLists, List *returningLists,
 				 List *rowMarks, OnConflictExpr *onconflict, int epqParam);
@@ -1026,7 +1026,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path)
 	 * parent-rel Vars it'll be asked to emit.
 	 */
 
-	plan = make_append(subplans, tlist);
+	plan = make_append(subplans, tlist, best_path->partitioned_rels);
 
 	copy_generic_path_info(&plan->plan, (Path *) best_path);
 
@@ -1134,6 +1134,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path)
 		subplans = lappend(subplans, subplan);
 	}
 
+	node->partitioned_rels = best_path->partitioned_rels;
 	node->mergeplans = subplans;
 
 	return (Plan *) node;
@@ -2340,6 +2341,7 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
 							best_path->operation,
 							best_path->canSetTag,
 							best_path->nominalRelation,
+							best_path->partitioned_rels,
 							best_path->resultRelations,
 							subplans,
 							best_path->withCheckOptionLists,
@@ -5187,7 +5189,7 @@ make_foreignscan(List *qptlist,
 }
 
 static Append *
-make_append(List *appendplans, List *tlist)
+make_append(List *appendplans, List *tlist, List *partitioned_rels)
 {
 	Append	   *node = makeNode(Append);
 	Plan	   *plan = &node->plan;
@@ -5196,6 +5198,7 @@ make_append(List *appendplans, List *tlist)
 	plan->qual = NIL;
 	plan->lefttree = NULL;
 	plan->righttree = NULL;
+	node->partitioned_rels = partitioned_rels;
 	node->appendplans = appendplans;
 
 	return node;
@@ -6308,7 +6311,7 @@ make_project_set(List *tlist,
 static ModifyTable *
 make_modifytable(PlannerInfo *root,
 				 CmdType operation, bool canSetTag,
-				 Index nominalRelation,
+				 Index nominalRelation, List *partitioned_rels,
 				 List *resultRelations, List *subplans,
 				 List *withCheckOptionLists, List *returningLists,
 				 List *rowMarks, OnConflictExpr *onconflict, int epqParam)
@@ -6334,6 +6337,7 @@ make_modifytable(PlannerInfo *root,
 	node->operation = operation;
 	node->canSetTag = canSetTag;
 	node->nominalRelation = nominalRelation;
+	node->partitioned_rels = partitioned_rels;
 	node->resultRelations = resultRelations;
 	node->resultRelIndex = -1;	/* will be set correctly in setrefs.c */
 	node->plans = subplans;
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index b4ac224a7a..79792c406c 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -14,6 +14,7 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_class.h"
 #include "catalog/pg_type.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -642,6 +643,11 @@ create_lateral_join_info(PlannerInfo *root)
 
 				if (appinfo->parent_relid != rti)
 					continue;
+
+				/* Need not consider these. */
+				if (appinfo->child_relkind == RELKIND_PARTITIONED_TABLE)
+					continue;
+
 				childrel = root->simple_rel_array[appinfo->child_relid];
 				Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
 				Assert(childrel->direct_lateral_relids == NULL);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 02286d9c52..4d43756ff7 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1007,6 +1007,8 @@ inheritance_planner(PlannerInfo *root)
 	RelOptInfo *final_rel;
 	ListCell   *lc;
 	Index		rti;
+	RangeTblEntry *parent_rte;
+	List		  *partitioned_rels = NIL;
 
 	Assert(parse->commandType != CMD_INSERT);
 
@@ -1055,6 +1057,13 @@ inheritance_planner(PlannerInfo *root)
 		{
 			AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc);
 
+			/*
+			 * Need not consider these, because the latter planning steps
+			 * simply ignore them.
+			 */
+			if (appinfo->child_relkind == RELKIND_PARTITIONED_TABLE)
+				continue;
+
 			if (bms_is_member(appinfo->parent_relid, subqueryRTindexes) ||
 				bms_is_member(appinfo->child_relid, subqueryRTindexes) ||
 				bms_overlap(pull_varnos((Node *) appinfo->translated_vars),
@@ -1065,13 +1074,21 @@ inheritance_planner(PlannerInfo *root)
 	}
 
 	/*
+	 * If the parent RTE is a partitioned table, we should use that as the
+	 * nominal relation, because we do not have the duplicate parent RTE,
+	 * unlike in the case of a non-partitioned inheritance parent.
+	 */
+	parent_rte = rt_fetch(parentRTindex, root->parse->rtable);
+	if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE)
+		nominalRelation = parentRTindex;
+
+	/*
 	 * And now we can get on with generating a plan for each child table.
 	 */
 	foreach(lc, root->append_rel_list)
 	{
 		AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc);
 		PlannerInfo *subroot;
-		RangeTblEntry *parent_rte;
 		RangeTblEntry *child_rte;
 		RelOptInfo *sub_final_rel;
 		Path	   *subpath;
@@ -1081,6 +1098,17 @@ inheritance_planner(PlannerInfo *root)
 			continue;
 
 		/*
+		 * Nothing to do here for partitioned child tables; just remember
+		 * the RT index to put into the ModifyTablePath.
+		 */
+		if (appinfo->child_relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			partitioned_rels = lappend_int(partitioned_rels,
+										   appinfo->child_relid);
+			continue;
+		}
+
+		/*
 		 * We need a working copy of the PlannerInfo so that we can control
 		 * propagation of information back to the main copy.
 		 */
@@ -1216,15 +1244,23 @@ inheritance_planner(PlannerInfo *root)
 		grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ );
 
 		/*
-		 * We'll use the first child relation (even if it's excluded) as the
-		 * nominal target relation of the ModifyTable node.  Because of the
-		 * way expand_inherited_rtentry works, this should always be the RTE
-		 * representing the parent table in its role as a simple member of the
-		 * inheritance set.  (It would be logically cleaner to use the
-		 * inheritance parent RTE as the nominal target; but since that RTE
-		 * will not be otherwise referenced in the plan, doing so would give
-		 * rise to confusing use of multiple aliases in EXPLAIN output for
-		 * what the user will think is the "same" table.)
+		 * Set the nomimal target relation of the ModifyTable node if not
+		 * already done.  We use the inheritance parent RTE as the nominal
+		 * target relation if it's a partitioned table (see just above this
+		 * loop).  In the non-partitioned parent case, we'll use the first
+		 * child relation (even if it's excluded) as the nominal target
+		 * relation.  Because of the way expand_inherited_rtentry works, the
+		 * latter should be the RTE representing the parent table in its role
+		 * as a simple member of the inheritance set.
+		 *
+		 * It would be logically cleaner to *always* use the inheritance
+		 * parent RTE as the nominal relation; but that RTE is not otherwise
+		 * referenced in the plan in the non-partitioned inheritance case.
+		 * Instead the duplicate RTE is used elsewhere in the plan, so using
+		 * the original parent RTE would give rise to confusing use of multiple
+		 * aliases in EXPLAIN output for what the user will think is the "same"
+		 * table.  It's not a problem in the partitioned inheritance case,
+		 * because there is duplicate RTE for the parent in that case.
 		 */
 		if (nominalRelation < 0)
 			nominalRelation = appinfo->child_relid;
@@ -1351,6 +1387,7 @@ inheritance_planner(PlannerInfo *root)
 									 parse->commandType,
 									 parse->canSetTag,
 									 nominalRelation,
+									 partitioned_rels,
 									 resultRelations,
 									 subpaths,
 									 subroots,
@@ -2046,6 +2083,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
 										parse->commandType,
 										parse->canSetTag,
 										parse->resultRelation,
+										NIL,
 										list_make1_int(parse->resultRelation),
 										list_make1(path),
 										list_make1(root),
@@ -3348,7 +3386,8 @@ create_grouping_paths(PlannerInfo *root,
 				create_append_path(grouped_rel,
 								   paths,
 								   NULL,
-								   0);
+								   0,
+								   NIL);
 			path->pathtarget = target;
 		}
 		else
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 5f3027e96f..d148c2d37d 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -835,6 +835,10 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 				splan->nominalRelation += rtoffset;
 				splan->exclRelRTI += rtoffset;
 
+				foreach(l, splan->partitioned_rels)
+				{
+					lfirst_int(l) += rtoffset;
+				}
 				foreach(l, splan->resultRelations)
 				{
 					lfirst_int(l) += rtoffset;
@@ -875,6 +879,10 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 				 */
 				set_dummy_tlist_references(plan, rtoffset);
 				Assert(splan->plan.qual == NIL);
+				foreach(l, splan->partitioned_rels)
+				{
+					lfirst_int(l) += rtoffset;
+				}
 				foreach(l, splan->appendplans)
 				{
 					lfirst(l) = set_plan_refs(root,
@@ -893,6 +901,10 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 				 */
 				set_dummy_tlist_references(plan, rtoffset);
 				Assert(splan->plan.qual == NIL);
+				foreach(l, splan->partitioned_rels)
+				{
+					lfirst_int(l) += rtoffset;
+				}
 				foreach(l, splan->mergeplans)
 				{
 					lfirst(l) = set_plan_refs(root,
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 1389db18ba..ece5fa7c7a 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -566,7 +566,7 @@ generate_union_path(SetOperationStmt *op, PlannerInfo *root,
 	/*
 	 * Append the child results together.
 	 */
-	path = (Path *) create_append_path(result_rel, pathlist, NULL, 0);
+	path = (Path *) create_append_path(result_rel, pathlist, NULL, 0, NIL);
 
 	/* We have to manually jam the right tlist into the path; ick */
 	path->pathtarget = create_pathtarget(root, tlist);
@@ -678,7 +678,7 @@ generate_nonunion_path(SetOperationStmt *op, PlannerInfo *root,
 	/*
 	 * Append the child results together.
 	 */
-	path = (Path *) create_append_path(result_rel, pathlist, NULL, 0);
+	path = (Path *) create_append_path(result_rel, pathlist, NULL, 0, NIL);
 
 	/* We have to manually jam the right tlist into the path; ick */
 	path->pathtarget = create_pathtarget(root, tlist);
@@ -1352,6 +1352,12 @@ 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.
+ *
+ * AppendRelInfo's created for each child table normally contains information
+ * necessary to create a scan plan for the table.  However, for child tables
+ * that are partitioned (which contains no data), we need not create scan
+ * plans; so create a minimal AppendRelInfo in that case containing only the
+ * parent-child RT index pair.
  */
 static void
 expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
@@ -1488,32 +1494,42 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 		appinfo = makeNode(AppendRelInfo);
 		appinfo->parent_relid = rti;
 		appinfo->child_relid = childRTindex;
-		appinfo->parent_reltype = oldrelation->rd_rel->reltype;
-		appinfo->child_reltype = newrelation->rd_rel->reltype;
-		make_inh_translation_list(oldrelation, newrelation, childRTindex,
-								  &appinfo->translated_vars);
-		appinfo->parent_reloid = parentOID;
-		appinfos = lappend(appinfos, appinfo);
+		appinfo->child_relkind = childrte->relkind;
 
 		/*
-		 * Translate the column permissions bitmaps to the child's attnums (we
-		 * have to build the translated_vars list before we can do this). But
-		 * if this is the parent table, leave copyObject's result alone.
-		 *
-		 * Note: we need to do this even though the executor won't run any
-		 * permissions checks on the child RTE.  The insertedCols/updatedCols
-		 * bitmaps may be examined for trigger-firing purposes.
+		 * No need to fill the following information for partitioned table
+		 * appinfos, because we won't be needing it.
 		 */
-		if (childOID != parentOID)
+		if (appinfo->child_relkind != RELKIND_PARTITIONED_TABLE)
 		{
-			childrte->selectedCols = translate_col_privs(rte->selectedCols,
+			appinfo->parent_reltype = oldrelation->rd_rel->reltype;
+			appinfo->child_reltype = newrelation->rd_rel->reltype;
+			make_inh_translation_list(oldrelation, newrelation, childRTindex,
+									  &appinfo->translated_vars);
+			appinfo->parent_reloid = parentOID;
+
+			/*
+			 * Translate the column permissions bitmaps to the child's attnums
+			 * (we have to build the translated_vars list before we can do
+			 * this). But if this is the parent table, leave copyObject's
+			 * result alone. Note: we need to do this even though the executor
+			 * won't run any permissions checks on the child RTE.  The
+			 * insertedCols/updatedCols bitmaps may be examined for
+			 * trigger-firing purposes.
+			 */
+			if (childOID != parentOID)
+			{
+				childrte->selectedCols = translate_col_privs(rte->selectedCols,
 												   appinfo->translated_vars);
-			childrte->insertedCols = translate_col_privs(rte->insertedCols,
+				childrte->insertedCols = translate_col_privs(rte->insertedCols,
 												   appinfo->translated_vars);
-			childrte->updatedCols = translate_col_privs(rte->updatedCols,
+				childrte->updatedCols = translate_col_privs(rte->updatedCols,
 												   appinfo->translated_vars);
+			}
 		}
 
+		appinfos = lappend(appinfos, appinfo);
+
 		/*
 		 * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
 		 */
@@ -1529,7 +1545,10 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 			newrc->allMarkTypes = (1 << newrc->markType);
 			newrc->strength = oldrc->strength;
 			newrc->waitPolicy = oldrc->waitPolicy;
-			newrc->isParent = false;
+			/*
+			 * If child is a partitioned table, this is a "dummy" parent entry
+			 */
+			newrc->isParent = (rte->relkind == RELKIND_PARTITIONED_TABLE);
 
 			/* Include child's rowmark type in parent's allMarkTypes */
 			oldrc->allMarkTypes |= newrc->allMarkTypes;
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 8ce772d274..fca96eb001 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1201,7 +1201,7 @@ create_tidscan_path(PlannerInfo *root, RelOptInfo *rel, List *tidquals,
  */
 AppendPath *
 create_append_path(RelOptInfo *rel, List *subpaths, Relids required_outer,
-				   int parallel_workers)
+				   int parallel_workers, List *partitioned_rels)
 {
 	AppendPath *pathnode = makeNode(AppendPath);
 	ListCell   *l;
@@ -1216,6 +1216,7 @@ create_append_path(RelOptInfo *rel, List *subpaths, Relids required_outer,
 	pathnode->path.parallel_workers = parallel_workers;
 	pathnode->path.pathkeys = NIL;		/* result is always considered
 										 * unsorted */
+	pathnode->partitioned_rels = partitioned_rels;
 	pathnode->subpaths = subpaths;
 
 	/*
@@ -1258,7 +1259,8 @@ create_merge_append_path(PlannerInfo *root,
 						 RelOptInfo *rel,
 						 List *subpaths,
 						 List *pathkeys,
-						 Relids required_outer)
+						 Relids required_outer,
+						 List *partitioned_rels)
 {
 	MergeAppendPath *pathnode = makeNode(MergeAppendPath);
 	Cost		input_startup_cost;
@@ -1274,6 +1276,7 @@ create_merge_append_path(PlannerInfo *root,
 	pathnode->path.parallel_safe = rel->consider_parallel;
 	pathnode->path.parallel_workers = 0;
 	pathnode->path.pathkeys = pathkeys;
+	pathnode->partitioned_rels = partitioned_rels;
 	pathnode->subpaths = subpaths;
 
 	/*
@@ -3105,7 +3108,7 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
 ModifyTablePath *
 create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 						CmdType operation, bool canSetTag,
-						Index nominalRelation,
+						Index nominalRelation, List *partitioned_rels,
 						List *resultRelations, List *subpaths,
 						List *subroots,
 						List *withCheckOptionLists, List *returningLists,
@@ -3172,6 +3175,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 	pathnode->operation = operation;
 	pathnode->canSetTag = canSetTag;
 	pathnode->nominalRelation = nominalRelation;
+	pathnode->partitioned_rels = partitioned_rels;
 	pathnode->resultRelations = resultRelations;
 	pathnode->subpaths = subpaths;
 	pathnode->subroots = subroots;
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index dffc92762b..1db784784f 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -1509,6 +1509,13 @@ AcquireExecutorLocks(List *stmt_list, bool acquire)
 				continue;
 
 			/*
+			 * We lock the partitioned tables in the respective control nodes
+			 * such as Append, ModifyTable, etc.
+			 */
+			if (rte->relkind == RELKIND_PARTITIONED_TABLE)
+				continue;
+
+			/*
 			 * Acquire the appropriate type of lock on each relation OID. Note
 			 * that we don't actually try to open the rel, and hence will not
 			 * fail if it's been dropped entirely --- we'll just transiently
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index b880dc16cf..8e8b8e5314 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -202,6 +202,8 @@ typedef struct ModifyTable
 	CmdType		operation;		/* INSERT, UPDATE, or DELETE */
 	bool		canSetTag;		/* do we set the command tag/es_processed? */
 	Index		nominalRelation;	/* Parent RT index for use of EXPLAIN */
+	/* RT indexes of non-leaf tables in a partition tree */
+	List	   *partitioned_rels;
 	List	   *resultRelations;	/* integer list of RT indexes */
 	int			resultRelIndex; /* index of first resultRel in plan's list */
 	List	   *plans;			/* plan(s) producing source data */
@@ -227,6 +229,8 @@ typedef struct ModifyTable
 typedef struct Append
 {
 	Plan		plan;
+	/* RT indexes of non-leaf tables in a partition tree */
+	List	   *partitioned_rels;
 	List	   *appendplans;
 } Append;
 
@@ -238,6 +242,8 @@ typedef struct Append
 typedef struct MergeAppend
 {
 	Plan		plan;
+	/* RT indexes of non-leaf tables in a partition tree */
+	List	   *partitioned_rels;
 	List	   *mergeplans;
 	/* remaining fields are just like the sort-key info in struct Sort */
 	int			numCols;		/* number of sort-key columns */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 05d6f07aea..4e9caf7e77 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -1116,6 +1116,7 @@ typedef struct CustomPath
 typedef struct AppendPath
 {
 	Path		path;
+	List	   *partitioned_rels;
 	List	   *subpaths;		/* list of component Paths */
 } AppendPath;
 
@@ -1134,6 +1135,7 @@ typedef struct AppendPath
 typedef struct MergeAppendPath
 {
 	Path		path;
+	List	   *partitioned_rels;
 	List	   *subpaths;		/* list of component Paths */
 	double		limit_tuples;	/* hard limit on output tuples, or -1 */
 } MergeAppendPath;
@@ -1482,6 +1484,7 @@ typedef struct ModifyTablePath
 	CmdType		operation;		/* INSERT, UPDATE, or DELETE */
 	bool		canSetTag;		/* do we set the command tag/es_processed? */
 	Index		nominalRelation;	/* Parent RT index for use of EXPLAIN */
+	List	   *partitioned_rels;	/* integer list of RT indexes */
 	List	   *resultRelations;	/* integer list of RT indexes */
 	List	   *subpaths;		/* Path(s) producing source data */
 	List	   *subroots;		/* per-target-table PlannerInfos */
@@ -1886,6 +1889,7 @@ typedef struct AppendRelInfo
 	 */
 	Oid			parent_reltype; /* OID of parent's composite type */
 	Oid			child_reltype;	/* OID of child's composite type */
+	char		child_relkind;	/* relkind of append child rel */
 
 	/*
 	 * The N'th element of this list is a Var or expression representing the
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 373c7221a8..81640de7ab 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -64,12 +64,14 @@ extern BitmapOrPath *create_bitmap_or_path(PlannerInfo *root,
 extern TidPath *create_tidscan_path(PlannerInfo *root, RelOptInfo *rel,
 					List *tidquals, Relids required_outer);
 extern AppendPath *create_append_path(RelOptInfo *rel, List *subpaths,
-				   Relids required_outer, int parallel_workers);
+				   Relids required_outer, int parallel_workers,
+				   List *partitioned_rels);
 extern MergeAppendPath *create_merge_append_path(PlannerInfo *root,
 						 RelOptInfo *rel,
 						 List *subpaths,
 						 List *pathkeys,
-						 Relids required_outer);
+						 Relids required_outer,
+						 List *partitioned_rels);
 extern ResultPath *create_result_path(PlannerInfo *root, RelOptInfo *rel,
 				   PathTarget *target, List *resconstantqual);
 extern MaterialPath *create_material_path(RelOptInfo *rel, Path *subpath);
@@ -232,7 +234,7 @@ extern LockRowsPath *create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
 extern ModifyTablePath *create_modifytable_path(PlannerInfo *root,
 						RelOptInfo *rel,
 						CmdType operation, bool canSetTag,
-						Index nominalRelation,
+						Index nominalRelation, List *partitioned_rels,
 						List *resultRelations, List *subpaths,
 						List *subroots,
 						List *withCheckOptionLists, List *returningLists,
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 6494b205c4..6737f925c3 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -588,6 +588,32 @@ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
  bar2    |  4 | 104
 (8 rows)
 
+-- Check UPDATE with *partitioned* inherited target and an appendrel subquery
+create table some_tab (a int);
+insert into some_tab values (0);
+create table some_tab_child () inherits (some_tab);
+insert into some_tab_child values (1);
+create table parted_tab (a int, b char) partition by list (a);
+create table parted_tab_part1 partition of parted_tab for values in (1);
+create table parted_tab_part2 partition of parted_tab for values in (2);
+create table parted_tab_part3 partition of parted_tab for values in (3);
+insert into parted_tab values (1, 'a'), (2, 'a'), (3, 'a');
+update parted_tab set b = 'b'
+from
+  (select a from some_tab union all select a+1 from some_tab) ss
+where parted_tab.a = ss.a;
+select tableoid::regclass::text as relname, parted_tab.*
+from parted_tab order by 1,2;
+     relname      | a | b 
+------------------+---+---
+ parted_tab_part1 | 1 | b
+ parted_tab_part2 | 2 | b
+ parted_tab_part3 | 3 | a
+(3 rows)
+
+drop table parted_tab;
+drop table some_tab cascade;
+NOTICE:  drop cascades to table some_tab_child
 /* Test multiple inheritance of column defaults */
 CREATE TABLE firstparent (tomorrow date default now()::date + 1);
 CREATE TABLE secondparent (tomorrow date default  now() :: date  +  1);
@@ -1611,71 +1637,60 @@ explain (costs off) select * from list_parted;
            QUERY PLAN           
 --------------------------------
  Append
-   ->  Seq Scan on list_parted
    ->  Seq Scan on part_ab_cd
    ->  Seq Scan on part_ef_gh
    ->  Seq Scan on part_null_xy
-(5 rows)
+(4 rows)
 
 explain (costs off) select * from list_parted where a is null;
            QUERY PLAN           
 --------------------------------
  Append
-   ->  Seq Scan on list_parted
-         Filter: (a IS NULL)
    ->  Seq Scan on part_null_xy
          Filter: (a IS NULL)
-(5 rows)
+(3 rows)
 
 explain (costs off) select * from list_parted where a is not null;
            QUERY PLAN            
 ---------------------------------
  Append
-   ->  Seq Scan on list_parted
-         Filter: (a IS NOT NULL)
    ->  Seq Scan on part_ab_cd
          Filter: (a IS NOT NULL)
    ->  Seq Scan on part_ef_gh
          Filter: (a IS NOT NULL)
    ->  Seq Scan on part_null_xy
          Filter: (a IS NOT NULL)
-(9 rows)
+(7 rows)
 
 explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
                         QUERY PLAN                        
 ----------------------------------------------------------
  Append
-   ->  Seq Scan on list_parted
-         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
    ->  Seq Scan on part_ab_cd
          Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
    ->  Seq Scan on part_ef_gh
          Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
-(7 rows)
+(5 rows)
 
 explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
                                       QUERY PLAN                                       
 ---------------------------------------------------------------------------------------
  Append
-   ->  Seq Scan on list_parted
-         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
    ->  Seq Scan on part_ab_cd
          Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
    ->  Seq Scan on part_ef_gh
          Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
    ->  Seq Scan on part_null_xy
          Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
-(9 rows)
+(7 rows)
 
 explain (costs off) select * from list_parted where a = 'ab';
                 QUERY PLAN                
 ------------------------------------------
  Append
-   ->  Seq Scan on list_parted
-         Filter: ((a)::text = 'ab'::text)
    ->  Seq Scan on part_ab_cd
          Filter: ((a)::text = 'ab'::text)
-(5 rows)
+(3 rows)
 
 create table range_list_parted (
 	a	int,
@@ -1695,14 +1710,9 @@ create table part_40_inf_ab partition of part_40_inf for values in ('ab');
 create table part_40_inf_cd partition of part_40_inf for values in ('cd');
 create table part_40_inf_null partition of part_40_inf for values in (null);
 explain (costs off) select * from range_list_parted;
-             QUERY PLAN              
--------------------------------------
+             QUERY PLAN             
+------------------------------------
  Append
-   ->  Seq Scan on range_list_parted
-   ->  Seq Scan on part_1_10
-   ->  Seq Scan on part_10_20
-   ->  Seq Scan on part_21_30
-   ->  Seq Scan on part_40_inf
    ->  Seq Scan on part_1_10_ab
    ->  Seq Scan on part_1_10_cd
    ->  Seq Scan on part_10_20_ab
@@ -1712,36 +1722,22 @@ explain (costs off) select * from range_list_parted;
    ->  Seq Scan on part_40_inf_ab
    ->  Seq Scan on part_40_inf_cd
    ->  Seq Scan on part_40_inf_null
-(15 rows)
+(10 rows)
 
 explain (costs off) select * from range_list_parted where a = 5;
-             QUERY PLAN              
--------------------------------------
+           QUERY PLAN           
+--------------------------------
  Append
-   ->  Seq Scan on range_list_parted
-         Filter: (a = 5)
-   ->  Seq Scan on part_1_10
-         Filter: (a = 5)
    ->  Seq Scan on part_1_10_ab
          Filter: (a = 5)
    ->  Seq Scan on part_1_10_cd
          Filter: (a = 5)
-(9 rows)
+(5 rows)
 
 explain (costs off) select * from range_list_parted where b = 'ab';
-             QUERY PLAN              
--------------------------------------
+             QUERY PLAN             
+------------------------------------
  Append
-   ->  Seq Scan on range_list_parted
-         Filter: (b = 'ab'::bpchar)
-   ->  Seq Scan on part_1_10
-         Filter: (b = 'ab'::bpchar)
-   ->  Seq Scan on part_10_20
-         Filter: (b = 'ab'::bpchar)
-   ->  Seq Scan on part_21_30
-         Filter: (b = 'ab'::bpchar)
-   ->  Seq Scan on part_40_inf
-         Filter: (b = 'ab'::bpchar)
    ->  Seq Scan on part_1_10_ab
          Filter: (b = 'ab'::bpchar)
    ->  Seq Scan on part_10_20_ab
@@ -1750,27 +1746,19 @@ explain (costs off) select * from range_list_parted where b = 'ab';
          Filter: (b = 'ab'::bpchar)
    ->  Seq Scan on part_40_inf_ab
          Filter: (b = 'ab'::bpchar)
-(19 rows)
+(9 rows)
 
 explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
                            QUERY PLAN                            
 -----------------------------------------------------------------
  Append
-   ->  Seq Scan on range_list_parted
-         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
-   ->  Seq Scan on part_1_10
-         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
-   ->  Seq Scan on part_10_20
-         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
-   ->  Seq Scan on part_21_30
-         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
    ->  Seq Scan on part_1_10_ab
          Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
    ->  Seq Scan on part_10_20_ab
          Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
    ->  Seq Scan on part_21_30_ab
          Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
-(15 rows)
+(7 rows)
 
 /* Should select no rows because range partition key cannot be null */
 explain (costs off) select * from range_list_parted where a is null;
@@ -1782,37 +1770,17 @@ explain (costs off) select * from range_list_parted where a is null;
 
 /* Should only select rows from the null-accepting partition */
 explain (costs off) select * from range_list_parted where b is null;
-             QUERY PLAN              
--------------------------------------
+             QUERY PLAN             
+------------------------------------
  Append
-   ->  Seq Scan on range_list_parted
-         Filter: (b IS NULL)
-   ->  Seq Scan on part_1_10
-         Filter: (b IS NULL)
-   ->  Seq Scan on part_10_20
-         Filter: (b IS NULL)
-   ->  Seq Scan on part_21_30
-         Filter: (b IS NULL)
-   ->  Seq Scan on part_40_inf
-         Filter: (b IS NULL)
    ->  Seq Scan on part_40_inf_null
          Filter: (b IS NULL)
-(13 rows)
+(3 rows)
 
 explain (costs off) select * from range_list_parted where a is not null and a < 67;
                    QUERY PLAN                   
 ------------------------------------------------
  Append
-   ->  Seq Scan on range_list_parted
-         Filter: ((a IS NOT NULL) AND (a < 67))
-   ->  Seq Scan on part_1_10
-         Filter: ((a IS NOT NULL) AND (a < 67))
-   ->  Seq Scan on part_10_20
-         Filter: ((a IS NOT NULL) AND (a < 67))
-   ->  Seq Scan on part_21_30
-         Filter: ((a IS NOT NULL) AND (a < 67))
-   ->  Seq Scan on part_40_inf
-         Filter: ((a IS NOT NULL) AND (a < 67))
    ->  Seq Scan on part_1_10_ab
          Filter: ((a IS NOT NULL) AND (a < 67))
    ->  Seq Scan on part_1_10_cd
@@ -1831,23 +1799,19 @@ explain (costs off) select * from range_list_parted where a is not null and a <
          Filter: ((a IS NOT NULL) AND (a < 67))
    ->  Seq Scan on part_40_inf_null
          Filter: ((a IS NOT NULL) AND (a < 67))
-(29 rows)
+(19 rows)
 
 explain (costs off) select * from range_list_parted where a >= 30;
-             QUERY PLAN              
--------------------------------------
+             QUERY PLAN             
+------------------------------------
  Append
-   ->  Seq Scan on range_list_parted
-         Filter: (a >= 30)
-   ->  Seq Scan on part_40_inf
-         Filter: (a >= 30)
    ->  Seq Scan on part_40_inf_ab
          Filter: (a >= 30)
    ->  Seq Scan on part_40_inf_cd
          Filter: (a >= 30)
    ->  Seq Scan on part_40_inf_null
          Filter: (a >= 30)
-(11 rows)
+(7 rows)
 
 drop table list_parted;
 drop table range_list_parted;
diff --git a/src/test/regress/expected/tablesample.out b/src/test/regress/expected/tablesample.out
index b18e420e9b..d3794140fb 100644
--- a/src/test/regress/expected/tablesample.out
+++ b/src/test/regress/expected/tablesample.out
@@ -322,12 +322,10 @@ explain (costs off)
                 QUERY PLAN                 
 -------------------------------------------
  Append
-   ->  Sample Scan on parted_sample
-         Sampling: bernoulli ('100'::real)
    ->  Sample Scan on parted_sample_1
          Sampling: bernoulli ('100'::real)
    ->  Sample Scan on parted_sample_2
          Sampling: bernoulli ('100'::real)
-(7 rows)
+(5 rows)
 
 drop table parted_sample, parted_sample_1, parted_sample_2;
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index e3e9e34895..ff94c4d4c3 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -128,6 +128,28 @@ where bar.f1 = ss.f1;
 
 select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
 
+-- Check UPDATE with *partitioned* inherited target and an appendrel subquery
+create table some_tab (a int);
+insert into some_tab values (0);
+create table some_tab_child () inherits (some_tab);
+insert into some_tab_child values (1);
+create table parted_tab (a int, b char) partition by list (a);
+create table parted_tab_part1 partition of parted_tab for values in (1);
+create table parted_tab_part2 partition of parted_tab for values in (2);
+create table parted_tab_part3 partition of parted_tab for values in (3);
+insert into parted_tab values (1, 'a'), (2, 'a'), (3, 'a');
+
+update parted_tab set b = 'b'
+from
+  (select a from some_tab union all select a+1 from some_tab) ss
+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;
+drop table some_tab cascade;
+
 /* Test multiple inheritance of column defaults */
 
 CREATE TABLE firstparent (tomorrow date default now()::date + 1);
-- 
2.11.0

>From 8cbd2eb1911431d39516e1e58965f6594c060a64 Mon Sep 17 00:00:00 2001
From: amit <amitlangot...@gmail.com>
Date: Tue, 24 Jan 2017 14:22:34 +0900
Subject: [PATCH 2/2] Do not allocate storage for partitioned tables.

Currently, it is not possible to insert any data into a partitioned
table.  So, they're empty at all times, which means it is wasteful to
allocate relfilenode and related storage objects.
---
 src/backend/access/common/reloptions.c |  3 +--
 src/backend/catalog/heap.c             | 17 ++++++++++-------
 2 files changed, 11 insertions(+), 9 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 72e12532ab..d3b847e2e5 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -967,7 +967,6 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
-		case RELKIND_PARTITIONED_TABLE:
 			options = heap_reloptions(classForm->relkind, datum, false);
 			break;
 		case RELKIND_VIEW:
@@ -977,6 +976,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 			options = index_reloptions(amoptions, datum, false);
 			break;
 		case RELKIND_FOREIGN_TABLE:
+		case RELKIND_PARTITIONED_TABLE:
 			options = NULL;
 			break;
 		default:
@@ -1418,7 +1418,6 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
 			return (bytea *) rdopts;
 		case RELKIND_RELATION:
 		case RELKIND_MATVIEW:
-		case RELKIND_PARTITIONED_TABLE:
 			return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
 		default:
 			/* other relkinds are not supported */
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 41c0056556..2f5090b183 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -291,6 +291,7 @@ heap_create(const char *relname,
 		case RELKIND_VIEW:
 		case RELKIND_COMPOSITE_TYPE:
 		case RELKIND_FOREIGN_TABLE:
+		case RELKIND_PARTITIONED_TABLE:
 			create_storage = false;
 
 			/*
@@ -1345,14 +1346,13 @@ heap_create_with_catalog(const char *relname,
 	if (oncommit != ONCOMMIT_NOOP)
 		register_on_commit_action(relid, oncommit);
 
-	if (relpersistence == RELPERSISTENCE_UNLOGGED)
-	{
-		Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
-			   relkind == RELKIND_TOASTVALUE ||
-			   relkind == RELKIND_PARTITIONED_TABLE);
-
+	/*
+	 * We do not want to create any storage objects for a partitioned
+	 * table, including the init fork.
+	 */
+	if (relpersistence == RELPERSISTENCE_UNLOGGED &&
+		relkind != RELKIND_PARTITIONED_TABLE)
 		heap_create_init_fork(new_rel_desc);
-	}
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1376,6 +1376,9 @@ heap_create_with_catalog(const char *relname,
 void
 heap_create_init_fork(Relation rel)
 {
+	Assert(rel->rd_rel->relkind == RELKIND_RELATION ||
+		   rel->rd_rel->relkind == RELKIND_MATVIEW ||
+		   rel->rd_rel->relkind == RELKIND_TOASTVALUE);
 	RelationOpenSmgr(rel);
 	smgrcreate(rel->rd_smgr, INIT_FORKNUM, false);
 	log_smgrcreate(&rel->rd_smgr->smgr_rnode.node, INIT_FORKNUM);
-- 
2.11.0

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to