On 2017/02/21 22:21, Ashutosh Bapat wrote:
> Some comments about 0003 patch.
> 
> @@ -996,10 +996,20 @@ inheritance_planner(PlannerInfo *root)
>     Index       rti;
> +   RangeTblEntry *parent_rte;
> There's already another variable declared in that function within a loop
>     foreach(lc, root->append_rel_list)
>     {
>         ...
>         RangeTblEntry *parent_rte;
>         RangeTblEntry *child_rte;
> 
> You might want to choose a different name or delete the one within the loop.

Deleted the one within the loop.

> I am wondering whether we should deal with inh flat reset in a
> slightly different way. Let expand_inherited_rtentry() mark inh =
> false for the partitioned tables without any partitions and deal with
> those at the time of estimating size by marking those as dummy. That
> might be better than multiple changes here. I will try to cook up a
> patch soon for that.

Are thinking something along the lines of the attached rewritten patch
0003?  I also tend to think that's probably a cleaner patch.  Thanks for
the idea.

> Also we should add tests to make sure the scans on partitioned tables
> without any partitions do not get into problems. PFA patch which adds
> those tests.

I added the test case you suggest, but kept just the first one.

I am including the patches 0001 and 0002 to keep all patches being
discussed on this thread together.

Thanks,
Amit
>From 79d7234743a5414f2b7f2df4ebdde472f2328209 Mon Sep 17 00:00:00 2001
From: amit <amitlangot...@gmail.com>
Date: Mon, 6 Feb 2017 18:03:28 +0900
Subject: [PATCH 1/3] Partitioned tables are empty themselves

So, there is not much point in trying to do things to them that need
accessing files (a later commit will make it so that there is no file
at all to access.)  Things that needed attention are: vacuum, analyze,
truncate, ATRewriteTables.
---
 doc/src/sgml/ddl.sgml               |  7 ++--
 src/backend/commands/analyze.c      | 39 ++++++++++++++------
 src/backend/commands/tablecmds.c    | 15 ++++++--
 src/backend/commands/vacuum.c       | 71 +++++++++++++++++++++++++++++++++----
 src/backend/postmaster/autovacuum.c | 20 +++++++----
 5 files changed, 123 insertions(+), 29 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 5779eac43d..358bf956d0 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -3840,8 +3840,11 @@ UNION ALL SELECT * FROM measurement_y2008m01;
 <programlisting>
 ANALYZE measurement;
 </programlisting>
-      will only process the master table.  This is true even for partitioned
-      tables.
+      will only process the master table.
+     </para>
+
+     <para>
+      This does not apply to partitioned tables though.
      </para>
     </listitem>
 
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index ed3acb1673..12cd0110b0 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,8 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 * locked the relation.
 	 */
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
-		onerel->rd_rel->relkind == RELKIND_MATVIEW ||
-		onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/* Regular table, so we'll use the regular row acquisition function */
 		acquirefunc = acquire_sample_rows;
@@ -234,6 +233,12 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 			return;
 		}
 	}
+	else if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		/*
+		 * For partitioned tables, we want to do the recursive ANALYZE below.
+		 */
+	}
 	else
 	{
 		/* No need for a WARNING if we already complained during VACUUM */
@@ -253,13 +258,15 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	LWLockRelease(ProcArrayLock);
 
 	/*
-	 * Do the normal non-recursive ANALYZE.
+	 * Do the normal non-recursive ANALYZE, non-partitioned relations.
 	 */
-	do_analyze_rel(onerel, options, params, va_cols, acquirefunc, relpages,
-				   false, in_outer_xact, elevel);
+	if (onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		do_analyze_rel(onerel, options, params, va_cols, acquirefunc,
+					   relpages, false, in_outer_xact, elevel);
 
 	/*
-	 * If there are child tables, do recursive ANALYZE.
+	 * If there are child tables, do recursive ANALYZE.  This includes
+	 * partitioned tables.
 	 */
 	if (onerel->rd_rel->relhassubclass)
 		do_analyze_rel(onerel, options, params, va_cols, acquirefunc, relpages,
@@ -1316,10 +1323,20 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 			continue;
 		}
 
+		/* Ignore partitioned tables as there are no tuples to collect */
+		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			/* Don't try to unlock the passed-in root table. */
+			if (childrel == onerel)
+				heap_close(childrel, NoLock);
+			else
+				heap_close(childrel, AccessShareLock);
+			continue;
+		}
+
 		/* Check table type (MATVIEW can't happen, but might as well allow) */
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
-			childrel->rd_rel->relkind == RELKIND_MATVIEW ||
-			childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			/* Regular table, so use the regular row acquisition function */
 			acquirefunc = acquire_sample_rows;
@@ -1366,9 +1383,11 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 	}
 
 	/*
-	 * If we don't have at least two tables to consider, fail.
+	 * If we don't have at least one child table to consider, fail.  If the
+	 * relation is a partitioned table, it's not counted as a child table.
 	 */
-	if (nrels < 2)
+	if ((onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE && nrels < 2) ||
+		nrels < 1)
 	{
 		ereport(elevel,
 				(errmsg("skipping analyze of \"%s.%s\" inheritance tree --- this inheritance tree contains no analyzable child tables",
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3cea220421..317012068b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1349,6 +1349,10 @@ ExecuteTruncate(TruncateStmt *stmt)
 	{
 		Relation	rel = (Relation) lfirst(cell);
 
+		/* Skip partitioned tables as there is nothing to do */
+		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -1459,7 +1463,11 @@ truncate_check_rel(Relation rel)
 {
 	AclResult	aclresult;
 
-	/* Only allow truncate on regular tables */
+	/*
+	 * Only allow truncate on regular tables and partitioned tables (although,
+	 * the latter are only being included here for the following checks; no
+	 * physical truncation will occur in their case.)
+	 */
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
 		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
@@ -4006,8 +4014,9 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
 	{
 		AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
 
-		/* Foreign tables have no storage. */
-		if (tab->relkind == RELKIND_FOREIGN_TABLE)
+		/* Foreign tables have no storage, nor do partitioned tables. */
+		if (tab->relkind == RELKIND_FOREIGN_TABLE ||
+			tab->relkind == RELKIND_PARTITIONED_TABLE)
 			continue;
 
 		/*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 812fb4a48f..856673fb58 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -32,6 +32,7 @@
 #include "access/xact.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
+#include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_namespace.h"
 #include "commands/cluster.h"
 #include "commands/vacuum.h"
@@ -66,7 +67,7 @@ static BufferAccessStrategy vac_strategy;
 
 
 /* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static List *get_rel_oids(Oid relid, const RangeVar *vacrel, bool is_vacuum);
 static void vac_truncate_clog(TransactionId frozenXID,
 				  MultiXactId minMulti,
 				  TransactionId lastSaneFrozenXid,
@@ -226,9 +227,12 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 
 	/*
 	 * Build list of relations to process, unless caller gave us one. (If we
-	 * build one, we put it in vac_context for safekeeping.)
+	 * build one, we put it in vac_context for safekeeping.)  Also pass
+	 * whether we are going to be doing VACUUM, in which case we must include
+	 * partitions in the list (considered only in the case of a database-wide
+	 * invocation).
 	 */
-	relations = get_rel_oids(relid, relation);
+	relations = get_rel_oids(relid, relation, options & VACOPT_VACUUM);
 
 	/*
 	 * Decide whether we need to start/commit our own transactions.
@@ -378,7 +382,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
  * per-relation transactions.
  */
 static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+get_rel_oids(Oid relid, const RangeVar *vacrel, bool is_vacuum)
 {
 	List	   *oid_list = NIL;
 	MemoryContext oldcontext;
@@ -394,6 +398,9 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
 	{
 		/* Process a specific relation */
 		Oid			relid;
+		HeapTuple	tuple;
+		Form_pg_class classForm;
+		bool		include_parts;
 
 		/*
 		 * Since we don't take a lock here, the relation might be gone, or the
@@ -406,9 +413,29 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
 		 */
 		relid = RangeVarGetRelid(vacrel, NoLock, false);
 
-		/* Make a relation list entry for this guy */
+		/*
+		 * To check whether the relation is a partitioned table, fetch its
+		 * syscache entry.
+		 */
+		tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+		classForm = (Form_pg_class) GETSTRUCT(tuple);
+		include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+		ReleaseSysCache(tuple);
+
+		/*
+		 * Make relation list entries for this guy and its partitions, if any.
+		 * Note that the list returned by find_all_inheritors() include the
+		 * passed-in OID at its head.  Also note that we did not request a
+		 * lock to be taken to match what would be done otherwise.
+		 */
 		oldcontext = MemoryContextSwitchTo(vac_context);
-		oid_list = lappend_oid(oid_list, relid);
+		if (include_parts)
+			oid_list = list_concat(oid_list,
+								   find_all_inheritors(relid, NoLock, NULL));
+		else
+			oid_list = lappend_oid(oid_list, relid);
 		MemoryContextSwitchTo(oldcontext);
 	}
 	else
@@ -429,8 +456,23 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
 		{
 			Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
 
+			/*
+			 * We include partitioned tables here; depending on which
+			 * operation is to be performed, caller will decide whether to
+			 * process or ignore them.
+			 */
 			if (classForm->relkind != RELKIND_RELATION &&
-				classForm->relkind != RELKIND_MATVIEW)
+				classForm->relkind != RELKIND_MATVIEW &&
+				classForm->relkind != RELKIND_PARTITIONED_TABLE)
+				continue;
+
+			/*
+			 * If only ANALYZE is to be performed, there is no need to include
+			 * partitions in the list.  In a database-wide ANALYZE, we only
+			 * update the inheritance statistics of partitioned tables, not
+			 * the statistics of individual partitions.
+			 */
+			if (!is_vacuum && classForm->relispartition)
 				continue;
 
 			/* Make a relation list entry for this guy */
@@ -1350,6 +1392,21 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	}
 
 	/*
+	 * Ignore partitioned tables as there is no work to be done.  Since we
+	 * release the lock here, it's possible that any partitions added from
+	 * this point on will not get processed, but that seems harmless.
+	 */
+	if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+
+		/* It's OK for other commands to look at this table */
+		return true;
+	}
+
+	/*
 	 * Get a session-level lock too. This will protect our access to the
 	 * relation across multiple transactions, so that we can vacuum the
 	 * relation's TOAST table (if any) secure in the knowledge that no one is
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 0c5ffa086c..d7d1f5ea6f 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1986,11 +1986,11 @@ do_autovacuum(void)
 	 * Scan pg_class to determine which tables to vacuum.
 	 *
 	 * We do this in two passes: on the first one we collect the list of plain
-	 * relations and materialized views, and on the second one we collect
-	 * TOAST tables. The reason for doing the second pass is that during it we
-	 * want to use the main relation's pg_class.reloptions entry if the TOAST
-	 * table does not have any, and we cannot obtain it unless we know
-	 * beforehand what's the main table OID.
+	 * relations, partitioned tables, and materialized views, and on the
+	 * second one we collect TOAST tables. The reason for doing the second
+	 * pass is that during it we want to use the main relation's
+	 * pg_class.reloptions entry if the TOAST table does not have any, and we
+	 * cannot obtain it unless we know beforehand what's the main table OID.
 	 *
 	 * We need to check TOAST tables separately because in cases with short,
 	 * wide tables there might be proportionally much more activity in the
@@ -2013,7 +2013,8 @@ do_autovacuum(void)
 		bool		wraparound;
 
 		if (classForm->relkind != RELKIND_RELATION &&
-			classForm->relkind != RELKIND_MATVIEW)
+			classForm->relkind != RELKIND_MATVIEW &&
+			classForm->relkind != RELKIND_PARTITIONED_TABLE)
 			continue;
 
 		relid = HeapTupleGetOid(tuple);
@@ -2195,7 +2196,8 @@ do_autovacuum(void)
 		 * completely unrelated to the one we saw before.
 		 */
 		if (!((classForm->relkind == RELKIND_RELATION ||
-			   classForm->relkind == RELKIND_MATVIEW) &&
+			   classForm->relkind == RELKIND_MATVIEW ||
+			   classForm->relkind == RELKIND_PARTITIONED_TABLE) &&
 			  classForm->relpersistence == RELPERSISTENCE_TEMP))
 		{
 			UnlockRelationOid(relid, AccessExclusiveLock);
@@ -2614,6 +2616,10 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
 	if (classForm->relkind == RELKIND_TOASTVALUE)
 		doanalyze = false;
 
+	/* ignore VACUUM for partitioned tables */
+	if (classForm->relkind == RELKIND_PARTITIONED_TABLE)
+		dovacuum = false;
+
 	/* OK, it needs something done */
 	if (doanalyze || dovacuum)
 	{
-- 
2.11.0

>From 61e2c64b9626c4b98a4346bdbbca49d6e3ed3c45 Mon Sep 17 00:00:00 2001
From: amit <amitlangot...@gmail.com>
Date: Tue, 24 Jan 2017 14:22:34 +0900
Subject: [PATCH 2/3] 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 816483ef8b..ec90d2b625 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,7 +930,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:
@@ -940,6 +939,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:
@@ -1382,7 +1382,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

>From 254f96967188123851f4324d46d30c36e6637edd Mon Sep 17 00:00:00 2001
From: amit <amitlangot...@gmail.com>
Date: Mon, 6 Feb 2017 17:26:48 +0900
Subject: [PATCH 3/3] 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 need not
create AppendRelInfo's for them in the planner prep phase.

Further, 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 in normal cases is also a child member), which means the latter
phases will not consider creating an Append plan and instead create a
scan node. Avoid this if the RTE is a partitioned table, by noticing
such cases in set_rel_size().  Per suggestion from Ashutosh Bapat.

Next, since we do not add the RTE corresponding to the root partitioned
table as the 1st child member of the inheritance set, inheritance_planner()
must not assume the same when assigning nominalRelation to a ModifyTable
node.
---
 src/backend/optimizer/path/allpaths.c  |   9 +++
 src/backend/optimizer/plan/planner.c   |  16 ++++-
 src/backend/optimizer/prep/prepunion.c |  19 +++++-
 src/test/regress/expected/inherit.out  | 116 +++++++++------------------------
 src/test/regress/sql/inherit.sql       |   4 ++
 5 files changed, 76 insertions(+), 88 deletions(-)

diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 633b5c1608..759e14d5fe 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -342,6 +342,15 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
 					/* Foreign table */
 					set_foreign_size(root, rel, rte);
 				}
+				else if (rte->relkind == RELKIND_PARTITIONED_TABLE)
+				{
+					/*
+					 * An empty partitioned table, i.e., one without any leaf
+					 * partitions, as signaled by rte->inh being set to false
+					 * by the prep phase (see expand_inherited_rtentry).
+					 */
+					set_dummy_rel_pathlist(rel);
+				}
 				else if (rte->tablesample != NULL)
 				{
 					/* Sampled relation */
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index ca0ae7883e..9918d2ba8c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -996,10 +996,20 @@ inheritance_planner(PlannerInfo *root)
 	RelOptInfo *final_rel;
 	ListCell   *lc;
 	Index		rti;
+	RangeTblEntry *parent_rte;
 
 	Assert(parse->commandType != CMD_INSERT);
 
 	/*
+	 * 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;
+
+	/*
 	 * We generate a modified instance of the original Query for each target
 	 * relation, plan that, and put all the plans into a list that will be
 	 * controlled by a single ModifyTable node.  All the instances share the
@@ -1060,7 +1070,6 @@ inheritance_planner(PlannerInfo *root)
 	{
 		AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc);
 		PlannerInfo *subroot;
-		RangeTblEntry *parent_rte;
 		RangeTblEntry *child_rte;
 		RelOptInfo *sub_final_rel;
 		Path	   *subpath;
@@ -1214,6 +1223,11 @@ inheritance_planner(PlannerInfo *root)
 		 * 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.)
+		 *
+		 * If the parent is a partitioned table, we do not have a separate RTE
+		 * representing it as a member of the inheritance set, because we do
+		 * not create a scan plan for it.  As mentioned at the top of this
+		 * function, we make the parent RTE itself the nominal relation.
 		 */
 		if (nominalRelation < 0)
 			nominalRelation = appinfo->child_relid;
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 1389db18ba..78f51c762b 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -1450,6 +1450,18 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 			newrelation = oldrelation;
 
 		/*
+		 * Ignore partitioned tables here, because we do not want them to
+		 * be considered as child tables in the inheritance set.  No scan
+		 * plans will be created for them.
+		 */
+		if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			if (newrelation != oldrelation)
+				heap_close(newrelation, lockmode);
+			continue;
+		}
+
+		/*
 		 * It is possible that the parent table has children that are temp
 		 * tables of other backends.  We cannot safely access such tables
 		 * (because of buffering issues), and the best thing to do seems to be
@@ -1548,9 +1560,12 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	 * If all the children were temp tables, pretend it's a non-inheritance
 	 * situation.  The duplicate RTE we added for the parent table is
 	 * harmless, so we don't bother to get rid of it; ditto for the useless
-	 * PlanRowMark node.
+	 * PlanRowMark node.  Since we do not create appinfos for partitioned
+	 * tables above, we may end up with just one appinfo corresponding to
+	 * the only leaf partition, which is fine.
 	 */
-	if (list_length(appinfos) < 2)
+	if ((rte->relkind != RELKIND_PARTITIONED_TABLE &&
+		 list_length(appinfos) < 2) || list_length(appinfos) < 1)
 	{
 		/* Clear flag before returning */
 		rte->inh = false;
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index a8c8b28a75..cd33a1a7ec 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1598,6 +1598,14 @@ reset enable_bitmapscan;
 create table list_parted (
 	a	varchar
 ) partition by list (a);
+-- test scanning partitioned table without any partitions
+explain (costs off) select * from list_parted;
+        QUERY PLAN        
+--------------------------
+ Result
+   One-Time Filter: false
+(2 rows)
+
 create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
 create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
 create table part_null_xy partition of list_parted for values in (null, 'xy');
@@ -1605,71 +1613,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,
@@ -1689,14 +1686,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
@@ -1706,36 +1698,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
@@ -1744,27 +1722,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;
@@ -1776,37 +1746,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
@@ -1825,23 +1775,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 cascade;
 NOTICE:  drop cascades to 3 other objects
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index a8b7eb1c8d..34274746d3 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -570,6 +570,10 @@ reset enable_bitmapscan;
 create table list_parted (
 	a	varchar
 ) partition by list (a);
+
+-- test scanning partitioned table without any partitions
+explain (costs off) select * from list_parted;
+
 create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
 create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
 create table part_null_xy partition of list_parted for values in (null, 'xy');
-- 
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