Should new partitions inherit their tablespace from their parent?

Started by David Rowleyabout 7 years ago24 messages
#1David Rowley
david.rowley@2ndquadrant.com

Dear Hackers,

I've just read the thread in [1]/messages/by-id/20181102003138.uxpaca6qfxzskepi@alvherre.pgsql where there was a discussion on a bug
fix regarding index partitions not being created in the same
tablespace as the partitioned index was created.

One paragraph that stood out when reading the thread was:

On 8 November 2018 at 09:00, Robert Haas <robertmhaas@gmail.com> wrote:

With regard to this patch, I think the new behavior is fine in and of
itself, but I *do not* think it should have been back-patched and I
*do not* think it should work one way for tables and another for
indexes.

While I don't agree with that 100% as there's no option to specify
were an index partition gets automatically created when a new
partitioned table is ATTACHed, whereas with new partitioned tables you
can at least specify the TABLESPACE option.

Anyway, Robert mentioned that he does not think it should work one way
for partitioned tables and another for partitioned indexes.

Here's the current situation with partitioned tables:

create table listp (a int) partition by list(a) tablespace foo;
create table listp1 partition of listp for values in(1);
select relname,reltablespace from pg_class where relname like 'listp%';
relname | reltablespace
---------+---------------
listp | 0
listp1 | 0
(2 rows)

It does not seem very good that when I created the partitioned table
and asked for its tablespace to be "foo" that postgres ignored that.
However, a partitioned table has no storage, so you could entertain
claims that this is not a bug.

I do have experience with using partitioned tables in a production
environment. During my experience a series of events took place that I
don't think is unique to this one case. I think we could make life
easier for this case. Here's the scenario:

1. Start a business
2. Record some time series data.
3. Your business grows faster than you thought.
4. You painfully partition your time-series table so you can easily
get rid of older data.
5. Your business grows even more and the single disk partition you use
for data on the database server is filling up quickly.
6. Panic.
7. Add more disks and be thankful you partitioned your table back in #4.

Now, we can, of course, manage all this today, but new partitions
defaulting to the default tablespace might seem a little surprising.
Imagine the situation of the DBA removing the old partitions, it's
pretty convenient if, once they've done that they can then also glance
at free diskspace and then perhaps decide to change the default
partition tablespace to the disk partition with the most free space so
that the nightly batch job which creates new partitions puts them in
the best place. The DBA could set the default_tablespace GUC, but the
tablespaces for the partitioned table might be on slower storage due
to how large they become and they might not want all new tables to go
in there.

I think we can do better:

How about we record the tablespace option for the partitioned table in
reltablespace instead of saving it as 0. Newly created partitions
which don't have a TABLESPACE mentioned in the CREATE TABLE command
should be created in their direct parent partitioned tables
tablespace.

The above idea was mentioned in [2]/messages/by-id/CAKJS1f9PXYcT+j=oyL-Lquz=ScNwpRtmD7u9svLASUygBdbN8w@mail.gmail.com and many people seemed to like it.
It was never implemented which likely lead to [1]/messages/by-id/20181102003138.uxpaca6qfxzskepi@alvherre.pgsql, which appears to
not be a great situation.

I've no patch, but I'm willing to go and write one if the general
consensus is that we want it to work this way.

[1]: /messages/by-id/20181102003138.uxpaca6qfxzskepi@alvherre.pgsql
[2]: /messages/by-id/CAKJS1f9PXYcT+j=oyL-Lquz=ScNwpRtmD7u9svLASUygBdbN8w@mail.gmail.com

--
David Rowley http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

#2Michael Paquier
michael@paquier.xyz
In reply to: David Rowley (#1)
Re: Should new partitions inherit their tablespace from their parent?

On Thu, Nov 08, 2018 at 12:50:40PM +1300, David Rowley wrote:

How about we record the tablespace option for the partitioned table in
reltablespace instead of saving it as 0. Newly created partitions
which don't have a TABLESPACE mentioned in the CREATE TABLE command
should be created in their direct parent partitioned tables
tablespace.

I have seen enough complains on the mailing lists regarding the way
tablespaces are handled for partitioned tables and their partitions that
it looks like a very good idea to make the tablespace being inherited
automatically, by setting up reltablespace to a non-zero value even if
a partitioned table has no physical presence. Of course not on v11 or
older releases, just on HEAD. It is no good to have partitioned indexes
and partitioned tables being handling inconsistently for such things.
--
Michael

#3Robert Haas
robertmhaas@gmail.com
In reply to: Michael Paquier (#2)
Re: Should new partitions inherit their tablespace from their parent?

On Wed, Nov 7, 2018 at 9:43 PM Michael Paquier <michael@paquier.xyz> wrote:

On Thu, Nov 08, 2018 at 12:50:40PM +1300, David Rowley wrote:

How about we record the tablespace option for the partitioned table in
reltablespace instead of saving it as 0. Newly created partitions
which don't have a TABLESPACE mentioned in the CREATE TABLE command
should be created in their direct parent partitioned tables
tablespace.

I have seen enough complains on the mailing lists regarding the way
tablespaces are handled for partitioned tables and their partitions that
it looks like a very good idea to make the tablespace being inherited
automatically, by setting up reltablespace to a non-zero value even if
a partitioned table has no physical presence. Of course not on v11 or
older releases, just on HEAD. It is no good to have partitioned indexes
and partitioned tables being handling inconsistently for such things.

+1.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#4David Rowley
david.rowley@2ndquadrant.com
In reply to: Robert Haas (#3)
1 attachment(s)
Re: Should new partitions inherit their tablespace from their parent?

On Fri, 9 Nov 2018 at 06:58, Robert Haas <robertmhaas@gmail.com> wrote:

On Wed, Nov 7, 2018 at 9:43 PM Michael Paquier <michael@paquier.xyz> wrote:

On Thu, Nov 08, 2018 at 12:50:40PM +1300, David Rowley wrote:

How about we record the tablespace option for the partitioned table in
reltablespace instead of saving it as 0. Newly created partitions
which don't have a TABLESPACE mentioned in the CREATE TABLE command
should be created in their direct parent partitioned tables
tablespace.

I have seen enough complains on the mailing lists regarding the way
tablespaces are handled for partitioned tables and their partitions that
it looks like a very good idea to make the tablespace being inherited
automatically, by setting up reltablespace to a non-zero value even if
a partitioned table has no physical presence. Of course not on v11 or
older releases, just on HEAD. It is no good to have partitioned indexes
and partitioned tables being handling inconsistently for such things.

+1.

Here's a patch for that. Parking here until January's commitfest.

--
David Rowley http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

v1-0001-Allow-newly-created-partitions-to-inherit-their-p.patchapplication/octet-stream; name=v1-0001-Allow-newly-created-partitions-to-inherit-their-p.patchDownload
From a8740cc01d752286020ff3674e8c363479b1e442 Mon Sep 17 00:00:00 2001
From: "dgrowley@gmail.com" <dgrowley@gmail.com>
Date: Fri, 23 Nov 2018 15:58:38 +1300
Subject: [PATCH v1] Allow newly created partitions to inherit their parent's
 tablespace

Previously a partitioned table's tablespace option was never stored in the
catalog and partitions which were newly created would always default to
the default tablespace.  Here we change things so that the tablespace
option for a partitioned table is stored in the catalog.  This is used to
determine which tablespace any newly created partitions which are attached
to the partitioned table are stored.  Of course, the tablespace of newly
created partitions can still be overwritten with the TABLESPACE option
during the CREATE TABLE, we're only changing the behavior when the
tablespace is not otherwise specified.
---
 doc/src/sgml/ref/create_table.sgml        |  6 +++-
 src/backend/catalog/heap.c                | 13 --------
 src/backend/commands/tablecmds.c          | 50 +++++++++++++++++++++++++------
 src/test/regress/input/tablespace.source  | 11 +++++++
 src/test/regress/output/tablespace.source | 17 +++++++++++
 5 files changed, 74 insertions(+), 23 deletions(-)

diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 50d5597002..073c24fbbe 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -1215,7 +1215,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       of the tablespace in which the new table is to be created.
       If not specified,
       <xref linkend="guc-default-tablespace"/> is consulted, or
-      <xref linkend="guc-temp-tablespaces"/> if the table is temporary.
+      <xref linkend="guc-temp-tablespaces"/> if the table is temporary.  For
+      partitioned tables, since no storage is required for the table itself,
+      the tablespace specified here only serves to mark the default tablespace
+      for any newly created partitions when no other tablespace is explicitly
+      specified.
      </para>
     </listitem>
    </varlistentry>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 11debaa780..b718fba540 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -334,20 +334,7 @@ heap_create(const char *relname,
 		case RELKIND_COMPOSITE_TYPE:
 		case RELKIND_FOREIGN_TABLE:
 		case RELKIND_PARTITIONED_TABLE:
-			create_storage = false;
-
-			/*
-			 * Force reltablespace to zero if the relation has no physical
-			 * storage.  This is mainly just for cleanliness' sake.
-			 */
-			reltablespace = InvalidOid;
-			break;
-
 		case RELKIND_PARTITIONED_INDEX:
-			/*
-			 * Preserve tablespace so that it's used as tablespace for indexes
-			 * on future partitions.
-			 */
 			create_storage = false;
 			break;
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a1137a3bf0..3d1b5b561f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -446,7 +446,7 @@ static bool ATPrepChangePersistence(Relation rel, bool toLogged);
 static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
 					const char *tablespacename, LOCKMODE lockmode);
 static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
-static void ATExecPartedIdxSetTableSpace(Relation rel, Oid newTableSpace);
+static void ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace);
 static void ATExecSetRelOptions(Relation rel, List *defList,
 					AlterTableType operation,
 					LOCKMODE lockmode);
@@ -588,6 +588,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	{
 		tablespaceId = get_tablespace_oid(stmt->tablespacename, false);
 	}
+	else if (stmt->partbound)
+	{
+		RangeVar   *parent;
+		Relation	parentrel;
+
+		/*
+		 * For partitions, when no other tablespace is specified, we default
+		 * the tablespace to the parent partitioned table's.
+		 */
+		Assert(list_length(stmt->inhRelations) == 1);
+
+		parent = linitial(stmt->inhRelations);
+
+		parentrel = heap_openrv(parent, AccessExclusiveLock);
+
+		if (OidIsValid(parentrel->rd_rel->reltablespace))
+			tablespaceId = parentrel->rd_rel->reltablespace;
+		else
+			tablespaceId = GetDefaultTablespace(stmt->relation->relpersistence);
+
+		relation_close(parentrel, NoLock);
+	}
 	else
 	{
 		tablespaceId = GetDefaultTablespace(stmt->relation->relpersistence);
@@ -4147,11 +4169,13 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			break;
 		case AT_SetTableSpace:	/* SET TABLESPACE */
 			/*
-			 * Only do this for partitioned indexes, for which this is just
-			 * a catalog change.  Other relation types are handled by Phase 3.
+			 * Only do this for partitioned tables and indexes, for which this
+			 * is just a catalog change.  Other relation types are handled by
+			 * Phase 3.
 			 */
-			if (rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
-				ATExecPartedIdxSetTableSpace(rel, tab->newTableSpace);
+			if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+				rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
+				ATExecSetTableSpaceNoStorage(rel, tab->newTableSpace);
 
 			break;
 		case AT_SetRelOptions:	/* SET (...) */
@@ -10925,11 +10949,12 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 }
 
 /*
- * Special handling of ALTER TABLE SET TABLESPACE for partitioned indexes,
- * which have no storage (so not handled in Phase 3 like other relation types)
+ * Special handling of ALTER TABLE SET TABLESPACE for partitioned tables and
+ * indexes. These have no storage (so not handled in Phase 3 like other
+ * relation types)
  */
 static void
-ATExecPartedIdxSetTableSpace(Relation rel, Oid newTableSpace)
+ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace)
 {
 	HeapTuple	tuple;
 	Oid			oldTableSpace;
@@ -10937,7 +10962,14 @@ ATExecPartedIdxSetTableSpace(Relation rel, Oid newTableSpace)
 	Form_pg_class rd_rel;
 	Oid			indexOid = RelationGetRelid(rel);
 
-	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+		   rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
+
+	/* Can't allow a non-shared relation in pg_global */
+	if (newTableSpace == GLOBALTABLESPACE_OID)
+		ereport(ERROR,
+		(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+			errmsg("only shared relations can be placed in pg_global tablespace")));
 
 	/*
 	 * No work if no change in tablespace.
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 60c87261db..9eb73f5471 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -44,6 +44,17 @@ CREATE INDEX foo_idx on testschema.foo(i) TABLESPACE regress_tblspace;
 SELECT relname, spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c
     where c.reltablespace = t.oid AND c.relname = 'foo_idx';
 
+-- partitioned table
+CREATE TABLE testschema.part (a int) PARTITION BY LIST (a) TABLESPACE regress_tblspace;
+CREATE TABLE testschema.part1 PARTITION OF testschema.part FOR VALUES IN (1);
+ALTER TABLE testschema.part SET TABLESPACE pg_default;
+CREATE TABLE testschema.part2 PARTITION OF testschema.part FOR VALUES IN (2);
+-- Ensure part1 defaulted to regress_tblspace and part2 defaulted to pg_default.
+SELECT relname, spcname FROM pg_catalog.pg_class c
+    LEFT JOIN pg_catalog.pg_tablespace t ON c.reltablespace = t.oid
+    where c.relname LIKE 'part%' order by relname;
+DROP TABLE testschema.part;
+
 -- partitioned index
 CREATE TABLE testschema.part (a int) PARTITION BY LIST (a);
 CREATE TABLE testschema.part1 PARTITION OF testschema.part FOR VALUES IN (1);
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 43962e6f01..f903e38c7f 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -61,6 +61,23 @@ SELECT relname, spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c
  foo_idx | regress_tblspace
 (1 row)
 
+-- partitioned table
+CREATE TABLE testschema.part (a int) PARTITION BY LIST (a) TABLESPACE regress_tblspace;
+CREATE TABLE testschema.part1 PARTITION OF testschema.part FOR VALUES IN (1);
+ALTER TABLE testschema.part SET TABLESPACE pg_default;
+CREATE TABLE testschema.part2 PARTITION OF testschema.part FOR VALUES IN (2);
+-- Ensure part1 defaulted to regress_tblspace and part2 defaulted to pg_default.
+SELECT relname, spcname FROM pg_catalog.pg_class c
+    LEFT JOIN pg_catalog.pg_tablespace t ON c.reltablespace = t.oid
+    where c.relname LIKE 'part%' order by relname;
+ relname |     spcname      
+---------+------------------
+ part    | 
+ part1   | regress_tblspace
+ part2   | 
+(3 rows)
+
+DROP TABLE testschema.part;
 -- partitioned index
 CREATE TABLE testschema.part (a int) PARTITION BY LIST (a);
 CREATE TABLE testschema.part1 PARTITION OF testschema.part FOR VALUES IN (1);
-- 
2.16.2.windows.1

#5David Rowley
david.rowley@2ndquadrant.com
In reply to: David Rowley (#4)
1 attachment(s)
Re: Should new partitions inherit their tablespace from their parent?

On Fri, 23 Nov 2018 at 17:03, David Rowley <david.rowley@2ndquadrant.com> wrote:

Here's a patch for that. Parking here until January's commitfest.

And another, now rebased atop of de38ce1b8 (which I probably should
have waited for).

--
David Rowley http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

v2-0001-Allow-newly-created-partitions-to-inherit-their-p.patchapplication/octet-stream; name=v2-0001-Allow-newly-created-partitions-to-inherit-their-p.patchDownload
From d7d2cd8adce2e4949494bbc65e888681bd9f7da5 Mon Sep 17 00:00:00 2001
From: "dgrowley@gmail.com" <dgrowley@gmail.com>
Date: Fri, 23 Nov 2018 15:58:38 +1300
Subject: [PATCH v2] Allow newly created partitions to inherit their parent's
 tablespace

Previously a partitioned table's tablespace option was never stored in the
catalog and partitions which were newly created would always default to
the default tablespace.  Here we change things so that the tablespace
option for a partitioned table is stored in the catalog.  This is used to
determine which tablespace any newly created partitions which are attached
to the partitioned table are stored.  Of course, the tablespace of newly
created partitions can still be overwritten with the TABLESPACE option
during the CREATE TABLE, we're only changing the behavior when the
tablespace is not otherwise specified.
---
 doc/src/sgml/ref/create_table.sgml        |  6 ++++-
 src/backend/catalog/heap.c                | 13 ---------
 src/backend/commands/tablecmds.c          | 44 ++++++++++++++++++++++++-------
 src/test/regress/input/tablespace.source  | 11 ++++++++
 src/test/regress/output/tablespace.source | 17 ++++++++++++
 5 files changed, 68 insertions(+), 23 deletions(-)

diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 50d5597002..073c24fbbe 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -1215,7 +1215,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       of the tablespace in which the new table is to be created.
       If not specified,
       <xref linkend="guc-default-tablespace"/> is consulted, or
-      <xref linkend="guc-temp-tablespaces"/> if the table is temporary.
+      <xref linkend="guc-temp-tablespaces"/> if the table is temporary.  For
+      partitioned tables, since no storage is required for the table itself,
+      the tablespace specified here only serves to mark the default tablespace
+      for any newly created partitions when no other tablespace is explicitly
+      specified.
      </para>
     </listitem>
    </varlistentry>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 11debaa780..b718fba540 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -334,20 +334,7 @@ heap_create(const char *relname,
 		case RELKIND_COMPOSITE_TYPE:
 		case RELKIND_FOREIGN_TABLE:
 		case RELKIND_PARTITIONED_TABLE:
-			create_storage = false;
-
-			/*
-			 * Force reltablespace to zero if the relation has no physical
-			 * storage.  This is mainly just for cleanliness' sake.
-			 */
-			reltablespace = InvalidOid;
-			break;
-
 		case RELKIND_PARTITIONED_INDEX:
-			/*
-			 * Preserve tablespace so that it's used as tablespace for indexes
-			 * on future partitions.
-			 */
 			create_storage = false;
 			break;
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 843ed48aa7..3d1b5b561f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -446,7 +446,7 @@ static bool ATPrepChangePersistence(Relation rel, bool toLogged);
 static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
 					const char *tablespacename, LOCKMODE lockmode);
 static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
-static void ATExecPartedIdxSetTableSpace(Relation rel, Oid newTableSpace);
+static void ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace);
 static void ATExecSetRelOptions(Relation rel, List *defList,
 					AlterTableType operation,
 					LOCKMODE lockmode);
@@ -588,6 +588,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	{
 		tablespaceId = get_tablespace_oid(stmt->tablespacename, false);
 	}
+	else if (stmt->partbound)
+	{
+		RangeVar   *parent;
+		Relation	parentrel;
+
+		/*
+		 * For partitions, when no other tablespace is specified, we default
+		 * the tablespace to the parent partitioned table's.
+		 */
+		Assert(list_length(stmt->inhRelations) == 1);
+
+		parent = linitial(stmt->inhRelations);
+
+		parentrel = heap_openrv(parent, AccessExclusiveLock);
+
+		if (OidIsValid(parentrel->rd_rel->reltablespace))
+			tablespaceId = parentrel->rd_rel->reltablespace;
+		else
+			tablespaceId = GetDefaultTablespace(stmt->relation->relpersistence);
+
+		relation_close(parentrel, NoLock);
+	}
 	else
 	{
 		tablespaceId = GetDefaultTablespace(stmt->relation->relpersistence);
@@ -4147,11 +4169,13 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			break;
 		case AT_SetTableSpace:	/* SET TABLESPACE */
 			/*
-			 * Only do this for partitioned indexes, for which this is just
-			 * a catalog change.  Other relation types are handled by Phase 3.
+			 * Only do this for partitioned tables and indexes, for which this
+			 * is just a catalog change.  Other relation types are handled by
+			 * Phase 3.
 			 */
-			if (rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
-				ATExecPartedIdxSetTableSpace(rel, tab->newTableSpace);
+			if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+				rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
+				ATExecSetTableSpaceNoStorage(rel, tab->newTableSpace);
 
 			break;
 		case AT_SetRelOptions:	/* SET (...) */
@@ -10925,11 +10949,12 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 }
 
 /*
- * Special handling of ALTER TABLE SET TABLESPACE for partitioned indexes,
- * which have no storage (so not handled in Phase 3 like other relation types)
+ * Special handling of ALTER TABLE SET TABLESPACE for partitioned tables and
+ * indexes. These have no storage (so not handled in Phase 3 like other
+ * relation types)
  */
 static void
-ATExecPartedIdxSetTableSpace(Relation rel, Oid newTableSpace)
+ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace)
 {
 	HeapTuple	tuple;
 	Oid			oldTableSpace;
@@ -10937,7 +10962,8 @@ ATExecPartedIdxSetTableSpace(Relation rel, Oid newTableSpace)
 	Form_pg_class rd_rel;
 	Oid			indexOid = RelationGetRelid(rel);
 
-	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+		   rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
 
 	/* Can't allow a non-shared relation in pg_global */
 	if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 2ac757cfab..96ee3917f6 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -44,6 +44,17 @@ CREATE INDEX foo_idx on testschema.foo(i) TABLESPACE regress_tblspace;
 SELECT relname, spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c
     where c.reltablespace = t.oid AND c.relname = 'foo_idx';
 
+-- partitioned table
+CREATE TABLE testschema.part (a int) PARTITION BY LIST (a) TABLESPACE regress_tblspace;
+CREATE TABLE testschema.part1 PARTITION OF testschema.part FOR VALUES IN (1);
+ALTER TABLE testschema.part SET TABLESPACE pg_default;
+CREATE TABLE testschema.part2 PARTITION OF testschema.part FOR VALUES IN (2);
+-- Ensure part1 defaulted to regress_tblspace and part2 defaulted to pg_default.
+SELECT relname, spcname FROM pg_catalog.pg_class c
+    LEFT JOIN pg_catalog.pg_tablespace t ON c.reltablespace = t.oid
+    where c.relname LIKE 'part%' order by relname;
+DROP TABLE testschema.part;
+
 -- partitioned index
 CREATE TABLE testschema.part (a int) PARTITION BY LIST (a);
 CREATE TABLE testschema.part1 PARTITION OF testschema.part FOR VALUES IN (1);
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 2e78e5ece6..436b42c8f6 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -61,6 +61,23 @@ SELECT relname, spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c
  foo_idx | regress_tblspace
 (1 row)
 
+-- partitioned table
+CREATE TABLE testschema.part (a int) PARTITION BY LIST (a) TABLESPACE regress_tblspace;
+CREATE TABLE testschema.part1 PARTITION OF testschema.part FOR VALUES IN (1);
+ALTER TABLE testschema.part SET TABLESPACE pg_default;
+CREATE TABLE testschema.part2 PARTITION OF testschema.part FOR VALUES IN (2);
+-- Ensure part1 defaulted to regress_tblspace and part2 defaulted to pg_default.
+SELECT relname, spcname FROM pg_catalog.pg_class c
+    LEFT JOIN pg_catalog.pg_tablespace t ON c.reltablespace = t.oid
+    where c.relname LIKE 'part%' order by relname;
+ relname |     spcname      
+---------+------------------
+ part    | 
+ part1   | regress_tblspace
+ part2   | 
+(3 rows)
+
+DROP TABLE testschema.part;
 -- partitioned index
 CREATE TABLE testschema.part (a int) PARTITION BY LIST (a);
 CREATE TABLE testschema.part1 PARTITION OF testschema.part FOR VALUES IN (1);
-- 
2.16.2.windows.1

#6Michael Paquier
michael@paquier.xyz
In reply to: David Rowley (#5)
Re: Should new partitions inherit their tablespace from their parent?

On Sat, Nov 24, 2018 at 12:32:36PM +1300, David Rowley wrote:

On Fri, 23 Nov 2018 at 17:03, David Rowley <david.rowley@2ndquadrant.com> wrote:

Here's a patch for that. Parking here until January's commitfest.

And another, now rebased atop of de38ce1b8 (which I probably should
have waited for).

You have just gained a reviewer. The current inconsistent state is
rather bad I think, so I'll try to look at that before January. Now
there are other things waiting in the queue...
--
Michael

#7Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#6)
Re: Should new partitions inherit their tablespace from their parent?

On Sat, Nov 24, 2018 at 10:18:17AM +0900, Michael Paquier wrote:

On Sat, Nov 24, 2018 at 12:32:36PM +1300, David Rowley wrote:

On Fri, 23 Nov 2018 at 17:03, David Rowley <david.rowley@2ndquadrant.com> wrote:

Here's a patch for that. Parking here until January's commitfest.

And another, now rebased atop of de38ce1b8 (which I probably should
have waited for).

Thanks for the patch, David. I can see that this patch makes the code
more consistent for partitioned tables and partitioned indexes when it
comes to tablespace handling, which is a very good thing.

-ATExecPartedIdxSetTableSpace(Relation rel, Oid newTableSpace)
+ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace)
NoStorage looks strange as routine name for this case.  Would something
like ATExecPartedRelSetTableSpace be more adapted perhaps?
+   else if (stmt->partbound)
+   {
+       RangeVar   *parent;
+       Relation    parentrel;
+
+       /*
+        * For partitions, when no other tablespace is specified, we default
+        * the tablespace to the parent partitioned table's.
+        */
Okay, so the direct parent is what counts, and not the top-most parent.
Could you add a test with multiple level of partitioned tables, like:
- parent is in tablespace 1.
- child is created in tablespace 2.
- grandchild is created, which should inherit tablespace 2 from the
child, but not tablespace 1 from the parent.  In the existing example,
as one partition is used to test the top-most parent and another for the
new default, it looks cleaner to create a third partition which would be
itself a partitioned table.
--
Michael
#8David Rowley
david.rowley@2ndquadrant.com
In reply to: Michael Paquier (#7)
1 attachment(s)
Re: Should new partitions inherit their tablespace from their parent?

Thank you for looking at this.

On Fri, 7 Dec 2018 at 20:15, Michael Paquier <michael@paquier.xyz> wrote:

-ATExecPartedIdxSetTableSpace(Relation rel, Oid newTableSpace)
+ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace)
NoStorage looks strange as routine name for this case.  Would something
like ATExecPartedRelSetTableSpace be more adapted perhaps?

I'd say it's better to name the function in the more general purpose
way. Perhaps we'll get some relkind in the future that's not a
partitioned table or index that might need the reltablespace set. When
that happens, if we name the function the way you're proposing, then
we might end up with some other function which does the same thing, or
the patch adding the new relkind would have to rename the function to
something more like how I have it. To save from either of those
having to happen, would it not be better to give it the generic name
now?

Instead of renaming the function, I've altered the Assert() to allow
all relkinds which don't have storage and also updated the comment to
remove the mention of partitioned tables and indexes.

+   else if (stmt->partbound)
+   {
+       RangeVar   *parent;
+       Relation    parentrel;
+
+       /*
+        * For partitions, when no other tablespace is specified, we default
+        * the tablespace to the parent partitioned table's.
+        */
Okay, so the direct parent is what counts, and not the top-most parent.
Could you add a test with multiple level of partitioned tables, like:
- parent is in tablespace 1.
- child is created in tablespace 2.
- grandchild is created, which should inherit tablespace 2 from the
child, but not tablespace 1 from the parent.  In the existing example,
as one partition is used to test the top-most parent and another for the
new default, it looks cleaner to create a third partition which would be
itself a partitioned table.

Sounds good. I've modified the existing test just to do it that way. I
don't think there's a need to have multiple tests for that.

I've attached an updated patch.

--
David Rowley http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

v2-0001-Allow-newly-created-partitions-to-inherit-their-p.patchapplication/octet-stream; name=v2-0001-Allow-newly-created-partitions-to-inherit-their-p.patchDownload
From d3655c02b0f18cb2c093eda0174d81289cd26f41 Mon Sep 17 00:00:00 2001
From: "dgrowley@gmail.com" <dgrowley@gmail.com>
Date: Fri, 23 Nov 2018 15:58:38 +1300
Subject: [PATCH v2] Allow newly created partitions to inherit their parent's
 tablespace

Previously a partitioned table's tablespace option was never stored in the
catalog and partitions which were newly created would always default to
the default tablespace.  Here we change things so that the tablespace
option for a partitioned table is stored in the catalog.  This is used to
determine which tablespace any newly created partitions which are attached
to the partitioned table are stored.  Of course, the tablespace of newly
created partitions can still be overwritten with the TABLESPACE option
during the CREATE TABLE, we're only changing the behavior when the
tablespace is not otherwise specified.
---
 doc/src/sgml/ref/create_table.sgml        |  6 +++-
 src/backend/catalog/heap.c                | 11 +++-----
 src/backend/commands/tablecmds.c          | 47 +++++++++++++++++++++++++------
 src/test/regress/input/tablespace.source  | 12 ++++++++
 src/test/regress/output/tablespace.source | 19 +++++++++++++
 5 files changed, 78 insertions(+), 17 deletions(-)

diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index d3e33132f3..94f7651c34 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -1216,7 +1216,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       of the tablespace in which the new table is to be created.
       If not specified,
       <xref linkend="guc-default-tablespace"/> is consulted, or
-      <xref linkend="guc-temp-tablespaces"/> if the table is temporary.
+      <xref linkend="guc-temp-tablespaces"/> if the table is temporary.  For
+      partitioned tables, since no storage is required for the table itself,
+      the tablespace specified here only serves to mark the default tablespace
+      for any newly created partitions when no other tablespace is explicitly
+      specified.
      </para>
     </listitem>
    </varlistentry>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 11debaa780..1e0aec3410 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -333,20 +333,17 @@ heap_create(const char *relname,
 		case RELKIND_VIEW:
 		case RELKIND_COMPOSITE_TYPE:
 		case RELKIND_FOREIGN_TABLE:
-		case RELKIND_PARTITIONED_TABLE:
-			create_storage = false;
-
 			/*
 			 * Force reltablespace to zero if the relation has no physical
 			 * storage.  This is mainly just for cleanliness' sake.
 			 */
 			reltablespace = InvalidOid;
-			break;
-
+			/* fall through */
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_PARTITIONED_INDEX:
 			/*
-			 * Preserve tablespace so that it's used as tablespace for indexes
-			 * on future partitions.
+			 * For partitioned tables and indexes, preserve tablespace so that
+			 * it's used as the tablespace for future partitions.
 			 */
 			create_storage = false;
 			break;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d6d0de1b01..b979e3184b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -446,7 +446,7 @@ static bool ATPrepChangePersistence(Relation rel, bool toLogged);
 static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
 					const char *tablespacename, LOCKMODE lockmode);
 static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
-static void ATExecPartedIdxSetTableSpace(Relation rel, Oid newTableSpace);
+static void ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace);
 static void ATExecSetRelOptions(Relation rel, List *defList,
 					AlterTableType operation,
 					LOCKMODE lockmode);
@@ -588,6 +588,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	{
 		tablespaceId = get_tablespace_oid(stmt->tablespacename, false);
 	}
+	else if (stmt->partbound)
+	{
+		RangeVar   *parent;
+		Relation	parentrel;
+
+		/*
+		 * For partitions, when no other tablespace is specified, we default
+		 * the tablespace to the parent partitioned table's.
+		 */
+		Assert(list_length(stmt->inhRelations) == 1);
+
+		parent = linitial(stmt->inhRelations);
+
+		parentrel = heap_openrv(parent, AccessExclusiveLock);
+
+		if (OidIsValid(parentrel->rd_rel->reltablespace))
+			tablespaceId = parentrel->rd_rel->reltablespace;
+		else
+			tablespaceId = GetDefaultTablespace(stmt->relation->relpersistence);
+
+		relation_close(parentrel, NoLock);
+	}
 	else
 	{
 		tablespaceId = GetDefaultTablespace(stmt->relation->relpersistence);
@@ -4150,11 +4172,13 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			break;
 		case AT_SetTableSpace:	/* SET TABLESPACE */
 			/*
-			 * Only do this for partitioned indexes, for which this is just
-			 * a catalog change.  Other relation types are handled by Phase 3.
+			 * Only do this for partitioned tables and indexes, for which this
+			 * is just a catalog change.  Other relation types which have
+			 * storage are handled by Phase 3.
 			 */
-			if (rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
-				ATExecPartedIdxSetTableSpace(rel, tab->newTableSpace);
+			if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+				rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
+				ATExecSetTableSpaceNoStorage(rel, tab->newTableSpace);
 
 			break;
 		case AT_SetRelOptions:	/* SET (...) */
@@ -10928,11 +10952,12 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 }
 
 /*
- * Special handling of ALTER TABLE SET TABLESPACE for partitioned indexes,
- * which have no storage (so not handled in Phase 3 like other relation types)
+ * Special handling of ALTER TABLE SET TABLESPACE for relations which have
+ * no filenode.  Since these have no storage the tablespace can be updated
+ * with a simple metadata only operation to update the tablespace.
  */
 static void
-ATExecPartedIdxSetTableSpace(Relation rel, Oid newTableSpace)
+ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace)
 {
 	HeapTuple	tuple;
 	Oid			oldTableSpace;
@@ -10940,7 +10965,11 @@ ATExecPartedIdxSetTableSpace(Relation rel, Oid newTableSpace)
 	Form_pg_class rd_rel;
 	Oid			indexOid = RelationGetRelid(rel);
 
-	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
+	Assert(rel->rd_rel->relkind == RELKIND_VIEW ||
+		   rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE ||
+		   rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+		   rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+		   rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
 
 	/* Can't allow a non-shared relation in pg_global */
 	if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 2ac757cfab..47ae73af95 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -44,6 +44,18 @@ CREATE INDEX foo_idx on testschema.foo(i) TABLESPACE regress_tblspace;
 SELECT relname, spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c
     where c.reltablespace = t.oid AND c.relname = 'foo_idx';
 
+-- partitioned table
+CREATE TABLE testschema.part (a int) PARTITION BY LIST (a);
+CREATE TABLE testschema.part12 PARTITION OF testschema.part FOR VALUES IN(1,2) PARTITION BY LIST (a) TABLESPACE regress_tblspace;
+CREATE TABLE testschema.part12_1 PARTITION OF testschema.part12 FOR VALUES IN (1);
+ALTER TABLE testschema.part12 SET TABLESPACE pg_default;
+CREATE TABLE testschema.part12_2 PARTITION OF testschema.part12 FOR VALUES IN (2);
+-- Ensure part12_1 defaulted to regress_tblspace and part12_2 defaulted to pg_default.
+SELECT relname, spcname FROM pg_catalog.pg_class c
+    LEFT JOIN pg_catalog.pg_tablespace t ON c.reltablespace = t.oid
+    where c.relname LIKE 'part%' order by relname;
+DROP TABLE testschema.part;
+
 -- partitioned index
 CREATE TABLE testschema.part (a int) PARTITION BY LIST (a);
 CREATE TABLE testschema.part1 PARTITION OF testschema.part FOR VALUES IN (1);
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 2e78e5ece6..29d6d2232b 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -61,6 +61,25 @@ SELECT relname, spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c
  foo_idx | regress_tblspace
 (1 row)
 
+-- partitioned table
+CREATE TABLE testschema.part (a int) PARTITION BY LIST (a);
+CREATE TABLE testschema.part12 PARTITION OF testschema.part FOR VALUES IN(1,2) PARTITION BY LIST (a) TABLESPACE regress_tblspace;
+CREATE TABLE testschema.part12_1 PARTITION OF testschema.part12 FOR VALUES IN (1);
+ALTER TABLE testschema.part12 SET TABLESPACE pg_default;
+CREATE TABLE testschema.part12_2 PARTITION OF testschema.part12 FOR VALUES IN (2);
+-- Ensure part12_1 defaulted to regress_tblspace and part12_2 defaulted to pg_default.
+SELECT relname, spcname FROM pg_catalog.pg_class c
+    LEFT JOIN pg_catalog.pg_tablespace t ON c.reltablespace = t.oid
+    where c.relname LIKE 'part%' order by relname;
+ relname  |     spcname      
+----------+------------------
+ part     | 
+ part12   | 
+ part12_1 | regress_tblspace
+ part12_2 | 
+(4 rows)
+
+DROP TABLE testschema.part;
 -- partitioned index
 CREATE TABLE testschema.part (a int) PARTITION BY LIST (a);
 CREATE TABLE testschema.part1 PARTITION OF testschema.part FOR VALUES IN (1);
-- 
2.16.2.windows.1

#9Michael Paquier
michael@paquier.xyz
In reply to: David Rowley (#8)
Re: Should new partitions inherit their tablespace from their parent?

On Mon, Dec 10, 2018 at 02:05:29AM +1300, David Rowley wrote:

On Fri, 7 Dec 2018 at 20:15, Michael Paquier <michael@paquier.xyz> wrote:

-ATExecPartedIdxSetTableSpace(Relation rel, Oid newTableSpace)
+ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace)
NoStorage looks strange as routine name for this case.  Would something
like ATExecPartedRelSetTableSpace be more adapted perhaps?

I'd say it's better to name the function in the more general purpose
way. Perhaps we'll get some relkind in the future that's not a
partitioned table or index that might need the reltablespace set. When
that happens, if we name the function the way you're proposing, then
we might end up with some other function which does the same thing, or
the patch adding the new relkind would have to rename the function to
something more like how I have it. To save from either of those
having to happen, would it not be better to give it the generic name
now?

Instead of renaming the function, I've altered the Assert() to allow
all relkinds which don't have storage and also updated the comment to
remove the mention of partitioned tables and indexes.

-       Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
+       Assert(rel->rd_rel->relkind == RELKIND_VIEW ||
+                  rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE ||
+                  rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+                  rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+                  rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);

Alvaro has begun a thread about that recently, so it seems to me that
this assertion could become invalid (per se my comments down-thread):
/messages/by-id/20181206215552.fm2ypuxq6nhpwjuc@alvherre.pgsql

Of course, it depends on the conclusion of this other thread. It will
perhaps make sense at some point to review on which relkinds this
function can work on, but I would recommend to review that behavior when
it actually makes sense and keep the current interface simple. So your
first version looked fine to me on those grounds. Another important
thing which would help with a restricted assertion is that if this code
path begins to be taken for other relkinds then the developer who
implements a new feature depending on it will need to look at this code
and consider the use-case behind it, instead of assuming that it will
hopefully just work.

+   else if (stmt->partbound)
+   {
+       RangeVar   *parent;
+       Relation    parentrel;
+
+       /*
+        * For partitions, when no other tablespace is specified, we default
+        * the tablespace to the parent partitioned table's.
+        */
Okay, so the direct parent is what counts, and not the top-most parent.
Could you add a test with multiple level of partitioned tables, like:
- parent is in tablespace 1.
- child is created in tablespace 2.
- grandchild is created, which should inherit tablespace 2 from the
child, but not tablespace 1 from the parent.  In the existing example,
as one partition is used to test the top-most parent and another for the
new default, it looks cleaner to create a third partition which would be
itself a partitioned table.

Sounds good. I've modified the existing test just to do it that way. I
don't think there's a need to have multiple tests for that.

I've attached an updated patch.

Thanks for the new version. The new test case looks fine to me.
--
Michael

#10David Rowley
david.rowley@2ndquadrant.com
In reply to: Michael Paquier (#9)
1 attachment(s)
Re: Should new partitions inherit their tablespace from their parent?

On Mon, 10 Dec 2018 at 15:22, Michael Paquier <michael@paquier.xyz> wrote:

-       Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
+       Assert(rel->rd_rel->relkind == RELKIND_VIEW ||
+                  rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE ||
+                  rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+                  rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+                  rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);

Alvaro has begun a thread about that recently, so it seems to me that
this assertion could become invalid (per se my comments down-thread):
/messages/by-id/20181206215552.fm2ypuxq6nhpwjuc@alvherre.pgsql

Of course, it depends on the conclusion of this other thread. It will
perhaps make sense at some point to review on which relkinds this
function can work on, but I would recommend to review that behavior when
it actually makes sense and keep the current interface simple. So your
first version looked fine to me on those grounds. Another important
thing which would help with a restricted assertion is that if this code
path begins to be taken for other relkinds then the developer who
implements a new feature depending on it will need to look at this code
and consider the use-case behind it, instead of assuming that it will
hopefully just work.

Likely it would be nice if we had a macro to determine if the relkind
should have storage or not, and then just Assert() we get one of those
into the function.

For now, I've just changed the Assert to only pass when we get a
partitioned table or partitioned index. I've left a comment to mention
that other types may need to be added later. Assert failures should
remind us to check that any newly added callers work correctly.

Updated patch attached.

--
David Rowley http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

v3-0001-Allow-newly-created-partitions-to-inherit-their-p.patchapplication/octet-stream; name=v3-0001-Allow-newly-created-partitions-to-inherit-their-p.patchDownload
From 7bb3600ea871268f1f54ac385ffb5a3548ac9556 Mon Sep 17 00:00:00 2001
From: "dgrowley@gmail.com" <dgrowley@gmail.com>
Date: Fri, 23 Nov 2018 15:58:38 +1300
Subject: [PATCH v3] Allow newly created partitions to inherit their parent's
 tablespace

Previously a partitioned table's tablespace option was never stored in the
catalog and partitions which were newly created would always default to
the default tablespace.  Here we change things so that the tablespace
option for a partitioned table is stored in the catalog.  This is used to
determine which tablespace any newly created partitions which are attached
to the partitioned table are stored.  Of course, the tablespace of newly
created partitions can still be overwritten with the TABLESPACE option
during the CREATE TABLE, we're only changing the behavior when the
tablespace is not otherwise specified.
---
 doc/src/sgml/ref/create_table.sgml        |  6 +++-
 src/backend/catalog/heap.c                | 11 +++---
 src/backend/commands/tablecmds.c          | 59 +++++++++++++++++++++++--------
 src/test/regress/input/tablespace.source  | 12 +++++++
 src/test/regress/output/tablespace.source | 19 ++++++++++
 5 files changed, 84 insertions(+), 23 deletions(-)

diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index d3e33132f3..94f7651c34 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -1216,7 +1216,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       of the tablespace in which the new table is to be created.
       If not specified,
       <xref linkend="guc-default-tablespace"/> is consulted, or
-      <xref linkend="guc-temp-tablespaces"/> if the table is temporary.
+      <xref linkend="guc-temp-tablespaces"/> if the table is temporary.  For
+      partitioned tables, since no storage is required for the table itself,
+      the tablespace specified here only serves to mark the default tablespace
+      for any newly created partitions when no other tablespace is explicitly
+      specified.
      </para>
     </listitem>
    </varlistentry>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 11debaa780..1e0aec3410 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -333,20 +333,17 @@ heap_create(const char *relname,
 		case RELKIND_VIEW:
 		case RELKIND_COMPOSITE_TYPE:
 		case RELKIND_FOREIGN_TABLE:
-		case RELKIND_PARTITIONED_TABLE:
-			create_storage = false;
-
 			/*
 			 * Force reltablespace to zero if the relation has no physical
 			 * storage.  This is mainly just for cleanliness' sake.
 			 */
 			reltablespace = InvalidOid;
-			break;
-
+			/* fall through */
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_PARTITIONED_INDEX:
 			/*
-			 * Preserve tablespace so that it's used as tablespace for indexes
-			 * on future partitions.
+			 * For partitioned tables and indexes, preserve tablespace so that
+			 * it's used as the tablespace for future partitions.
 			 */
 			create_storage = false;
 			break;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d6d0de1b01..a783d19f3d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -446,7 +446,7 @@ static bool ATPrepChangePersistence(Relation rel, bool toLogged);
 static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
 					const char *tablespacename, LOCKMODE lockmode);
 static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
-static void ATExecPartedIdxSetTableSpace(Relation rel, Oid newTableSpace);
+static void ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace);
 static void ATExecSetRelOptions(Relation rel, List *defList,
 					AlterTableType operation,
 					LOCKMODE lockmode);
@@ -588,6 +588,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	{
 		tablespaceId = get_tablespace_oid(stmt->tablespacename, false);
 	}
+	else if (stmt->partbound)
+	{
+		RangeVar   *parent;
+		Relation	parentrel;
+
+		/*
+		 * For partitions, when no other tablespace is specified, we default
+		 * the tablespace to the parent partitioned table's.
+		 */
+		Assert(list_length(stmt->inhRelations) == 1);
+
+		parent = linitial(stmt->inhRelations);
+
+		parentrel = heap_openrv(parent, AccessExclusiveLock);
+
+		if (OidIsValid(parentrel->rd_rel->reltablespace))
+			tablespaceId = parentrel->rd_rel->reltablespace;
+		else
+			tablespaceId = GetDefaultTablespace(stmt->relation->relpersistence);
+
+		relation_close(parentrel, NoLock);
+	}
 	else
 	{
 		tablespaceId = GetDefaultTablespace(stmt->relation->relpersistence);
@@ -4150,11 +4172,13 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			break;
 		case AT_SetTableSpace:	/* SET TABLESPACE */
 			/*
-			 * Only do this for partitioned indexes, for which this is just
-			 * a catalog change.  Other relation types are handled by Phase 3.
+			 * Only do this for partitioned tables and indexes, for which this
+			 * is just a catalog change.  Other relation types which have
+			 * storage are handled by Phase 3.
 			 */
-			if (rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
-				ATExecPartedIdxSetTableSpace(rel, tab->newTableSpace);
+			if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+				rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
+				ATExecSetTableSpaceNoStorage(rel, tab->newTableSpace);
 
 			break;
 		case AT_SetRelOptions:	/* SET (...) */
@@ -10928,19 +10952,25 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 }
 
 /*
- * Special handling of ALTER TABLE SET TABLESPACE for partitioned indexes,
- * which have no storage (so not handled in Phase 3 like other relation types)
+ * Special handling of ALTER TABLE SET TABLESPACE for relations which have
+ * no filenode.  Since these have no storage the tablespace can be updated
+ * with a simple metadata only operation to update the tablespace.
  */
 static void
-ATExecPartedIdxSetTableSpace(Relation rel, Oid newTableSpace)
+ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace)
 {
 	HeapTuple	tuple;
 	Oid			oldTableSpace;
 	Relation	pg_class;
 	Form_pg_class rd_rel;
-	Oid			indexOid = RelationGetRelid(rel);
+	Oid			reloid = RelationGetRelid(rel);
 
-	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
+	/*
+	 * For now we only need to work with partitioned tables and indexes.  If
+	 * more relkinds are to be supported then those should be allowed below.
+	 */
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+		   rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
 
 	/* Can't allow a non-shared relation in pg_global */
 	if (newTableSpace == GLOBALTABLESPACE_OID)
@@ -10955,24 +10985,23 @@ ATExecPartedIdxSetTableSpace(Relation rel, Oid newTableSpace)
 	if (newTableSpace == oldTableSpace ||
 		(newTableSpace == MyDatabaseTableSpace && oldTableSpace == 0))
 	{
-		InvokeObjectPostAlterHook(RelationRelationId,
-								  indexOid, 0);
+		InvokeObjectPostAlterHook(RelationRelationId, reloid, 0);
 		return;
 	}
 
 	/* Get a modifiable copy of the relation's pg_class row */
 	pg_class = heap_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(indexOid));
+	tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid));
 	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "cache lookup failed for relation %u", indexOid);
+		elog(ERROR, "cache lookup failed for relation %u", reloid);
 	rd_rel = (Form_pg_class) GETSTRUCT(tuple);
 
 	/* update the pg_class row */
 	rd_rel->reltablespace = (newTableSpace == MyDatabaseTableSpace) ? InvalidOid : newTableSpace;
 	CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 
-	InvokeObjectPostAlterHook(RelationRelationId, indexOid, 0);
+	InvokeObjectPostAlterHook(RelationRelationId, reloid, 0);
 
 	heap_freetuple(tuple);
 
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 2ac757cfab..47ae73af95 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -44,6 +44,18 @@ CREATE INDEX foo_idx on testschema.foo(i) TABLESPACE regress_tblspace;
 SELECT relname, spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c
     where c.reltablespace = t.oid AND c.relname = 'foo_idx';
 
+-- partitioned table
+CREATE TABLE testschema.part (a int) PARTITION BY LIST (a);
+CREATE TABLE testschema.part12 PARTITION OF testschema.part FOR VALUES IN(1,2) PARTITION BY LIST (a) TABLESPACE regress_tblspace;
+CREATE TABLE testschema.part12_1 PARTITION OF testschema.part12 FOR VALUES IN (1);
+ALTER TABLE testschema.part12 SET TABLESPACE pg_default;
+CREATE TABLE testschema.part12_2 PARTITION OF testschema.part12 FOR VALUES IN (2);
+-- Ensure part12_1 defaulted to regress_tblspace and part12_2 defaulted to pg_default.
+SELECT relname, spcname FROM pg_catalog.pg_class c
+    LEFT JOIN pg_catalog.pg_tablespace t ON c.reltablespace = t.oid
+    where c.relname LIKE 'part%' order by relname;
+DROP TABLE testschema.part;
+
 -- partitioned index
 CREATE TABLE testschema.part (a int) PARTITION BY LIST (a);
 CREATE TABLE testschema.part1 PARTITION OF testschema.part FOR VALUES IN (1);
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 2e78e5ece6..29d6d2232b 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -61,6 +61,25 @@ SELECT relname, spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c
  foo_idx | regress_tblspace
 (1 row)
 
+-- partitioned table
+CREATE TABLE testschema.part (a int) PARTITION BY LIST (a);
+CREATE TABLE testschema.part12 PARTITION OF testschema.part FOR VALUES IN(1,2) PARTITION BY LIST (a) TABLESPACE regress_tblspace;
+CREATE TABLE testschema.part12_1 PARTITION OF testschema.part12 FOR VALUES IN (1);
+ALTER TABLE testschema.part12 SET TABLESPACE pg_default;
+CREATE TABLE testschema.part12_2 PARTITION OF testschema.part12 FOR VALUES IN (2);
+-- Ensure part12_1 defaulted to regress_tblspace and part12_2 defaulted to pg_default.
+SELECT relname, spcname FROM pg_catalog.pg_class c
+    LEFT JOIN pg_catalog.pg_tablespace t ON c.reltablespace = t.oid
+    where c.relname LIKE 'part%' order by relname;
+ relname  |     spcname      
+----------+------------------
+ part     | 
+ part12   | 
+ part12_1 | regress_tblspace
+ part12_2 | 
+(4 rows)
+
+DROP TABLE testschema.part;
 -- partitioned index
 CREATE TABLE testschema.part (a int) PARTITION BY LIST (a);
 CREATE TABLE testschema.part1 PARTITION OF testschema.part FOR VALUES IN (1);
-- 
2.16.2.windows.1

#11Michael Paquier
michael@paquier.xyz
In reply to: David Rowley (#10)
Re: Should new partitions inherit their tablespace from their parent?

On Mon, Dec 10, 2018 at 11:37:16PM +1300, David Rowley wrote:

Likely it would be nice if we had a macro to determine if the relkind
should have storage or not, and then just Assert() we get one of those
into the function.

The other thread can deal about that matter I guess.

For now, I've just changed the Assert to only pass when we get a
partitioned table or partitioned index. I've left a comment to mention
that other types may need to be added later. Assert failures should
remind us to check that any newly added callers work correctly.

Updated patch attached.

Cool, thanks for updating the assertion. At quick glance the patch
seems fine to me. Let's keep ATExecSetTableSpaceNoStorage as routine
name as you suggest then.
--
Michael

#12Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#11)
Re: Should new partitions inherit their tablespace from their parent?

On Mon, Dec 10, 2018 at 10:56:47PM +0900, Michael Paquier wrote:

Cool, thanks for updating the assertion. At quick glance the patch
seems fine to me. Let's keep ATExecSetTableSpaceNoStorage as routine
name as you suggest then.

+       /*
+        * For partitions, when no other tablespace is specified, we default
+        * the tablespace to the parent partitioned table's.
+        */
+       Assert(list_length(stmt->inhRelations) == 1);
+
+       parent = linitial(stmt->inhRelations);
+
+       parentrel = heap_openrv(parent, AccessExclusiveLock);
So, in order to determine which tablespace should be used here, an
exclusive lock is taken on the parent because its partition descriptor
gets updated by the addition of the new partition.  This process is
actually done already in MergeAttributes() as well, but it won't get
triggered if a tablespace is defined directly in the CREATE TABLE
statement.  I think that we should add a comment to explain the
dependency between both, as well as why an exclusive lock is needed, so
something among those lines perhaps?  Here is an idea:
+       /*
+        * Grab an exclusive lock on the parent because its partition
+        * descriptor will be changed by the addition of the new partition.
+        * The same lock level is taken as when merging attributes below
+        * in MergeAttributes() to protect from lock upgrade deadlocks.
+        */

The position where the tablespace is chosen is definitely the good one.

What do you think?
--
Michael

#13David Rowley
david.rowley@2ndquadrant.com
In reply to: Michael Paquier (#12)
1 attachment(s)
Re: Should new partitions inherit their tablespace from their parent?

On Tue, 11 Dec 2018 at 15:43, Michael Paquier <michael@paquier.xyz> wrote:

+       parentrel = heap_openrv(parent, AccessExclusiveLock);
So, in order to determine which tablespace should be used here, an
exclusive lock is taken on the parent because its partition descriptor
gets updated by the addition of the new partition.  This process is
actually done already in MergeAttributes() as well, but it won't get
triggered if a tablespace is defined directly in the CREATE TABLE
statement.  I think that we should add a comment to explain the
dependency between both, as well as why an exclusive lock is needed, so
something among those lines perhaps?  Here is an idea:
+       /*
+        * Grab an exclusive lock on the parent because its partition
+        * descriptor will be changed by the addition of the new partition.
+        * The same lock level is taken as when merging attributes below
+        * in MergeAttributes() to protect from lock upgrade deadlocks.
+        */

The position where the tablespace is chosen is definitely the good one.

What do you think?

I think a comment in that location is a good idea. There's work being
done to reduce the lock level required for attaching a partition so a
comment here will help show that it's okay to reduce the lock level
for fetching the tablespace too.

I've attached an updated patch that includes the new comment. I didn't
use your proposed words though.

--
David Rowley http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

v4-0001-Allow-newly-created-partitions-to-inherit-their-p.patchapplication/octet-stream; name=v4-0001-Allow-newly-created-partitions-to-inherit-their-p.patchDownload
From 97ee4eb7cc8ba4baf182b5a10027540ebec931cd Mon Sep 17 00:00:00 2001
From: "dgrowley@gmail.com" <dgrowley@gmail.com>
Date: Fri, 23 Nov 2018 15:58:38 +1300
Subject: [PATCH v4] Allow newly created partitions to inherit their parent's
 tablespace

Previously a partitioned table's tablespace option was never stored in the
catalog and partitions which were newly created would always default to
the default tablespace.  Here we change things so that the tablespace
option for a partitioned table is stored in the catalog.  This is used to
determine which tablespace any newly created partitions which are attached
to the partitioned table are stored.  Of course, the tablespace of newly
created partitions can still be overwritten with the TABLESPACE option
during the CREATE TABLE, we're only changing the behavior when the
tablespace is not otherwise specified.
---
 doc/src/sgml/ref/create_table.sgml        |  6 ++-
 src/backend/catalog/heap.c                | 11 ++----
 src/backend/commands/tablecmds.c          | 66 ++++++++++++++++++++++++-------
 src/test/regress/input/tablespace.source  | 12 ++++++
 src/test/regress/output/tablespace.source | 19 +++++++++
 5 files changed, 91 insertions(+), 23 deletions(-)

diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index d3e33132f3..94f7651c34 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -1216,7 +1216,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       of the tablespace in which the new table is to be created.
       If not specified,
       <xref linkend="guc-default-tablespace"/> is consulted, or
-      <xref linkend="guc-temp-tablespaces"/> if the table is temporary.
+      <xref linkend="guc-temp-tablespaces"/> if the table is temporary.  For
+      partitioned tables, since no storage is required for the table itself,
+      the tablespace specified here only serves to mark the default tablespace
+      for any newly created partitions when no other tablespace is explicitly
+      specified.
      </para>
     </listitem>
    </varlistentry>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 651e86b71e..f4ea2e227f 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -333,20 +333,17 @@ heap_create(const char *relname,
 		case RELKIND_VIEW:
 		case RELKIND_COMPOSITE_TYPE:
 		case RELKIND_FOREIGN_TABLE:
-		case RELKIND_PARTITIONED_TABLE:
-			create_storage = false;
-
 			/*
 			 * Force reltablespace to zero if the relation has no physical
 			 * storage.  This is mainly just for cleanliness' sake.
 			 */
 			reltablespace = InvalidOid;
-			break;
-
+			/* fall through */
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_PARTITIONED_INDEX:
 			/*
-			 * Preserve tablespace so that it's used as tablespace for indexes
-			 * on future partitions.
+			 * For partitioned tables and indexes, preserve tablespace so that
+			 * it's used as the tablespace for future partitions.
 			 */
 			create_storage = false;
 			break;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d6d0de1b01..1f6135beb4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -446,7 +446,7 @@ static bool ATPrepChangePersistence(Relation rel, bool toLogged);
 static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
 					const char *tablespacename, LOCKMODE lockmode);
 static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
-static void ATExecPartedIdxSetTableSpace(Relation rel, Oid newTableSpace);
+static void ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace);
 static void ATExecSetRelOptions(Relation rel, List *defList,
 					AlterTableType operation,
 					LOCKMODE lockmode);
@@ -588,6 +588,35 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	{
 		tablespaceId = get_tablespace_oid(stmt->tablespacename, false);
 	}
+	else if (stmt->partbound)
+	{
+		RangeVar   *parent;
+		Relation	parentrel;
+
+		/*
+		 * For partitions, when no other tablespace is specified, we default
+		 * the tablespace to the parent partitioned table's.
+		 */
+		Assert(list_length(stmt->inhRelations) == 1);
+
+		parent = linitial(stmt->inhRelations);
+
+		/*
+		 * Open the parent in order to determine its tablespace.  We don't
+		 * really need an AccessExclusiveLock to do this, but we must obtain
+		 * an AccessExclusiveLock below in MergeAttributes(), so in order to
+		 * prevent any lock upgrade deadlocks we obtain the same lock level
+		 * here.
+		 */
+		parentrel = heap_openrv(parent, AccessExclusiveLock);
+
+		if (OidIsValid(parentrel->rd_rel->reltablespace))
+			tablespaceId = parentrel->rd_rel->reltablespace;
+		else
+			tablespaceId = GetDefaultTablespace(stmt->relation->relpersistence);
+
+		relation_close(parentrel, NoLock);
+	}
 	else
 	{
 		tablespaceId = GetDefaultTablespace(stmt->relation->relpersistence);
@@ -4150,11 +4179,13 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			break;
 		case AT_SetTableSpace:	/* SET TABLESPACE */
 			/*
-			 * Only do this for partitioned indexes, for which this is just
-			 * a catalog change.  Other relation types are handled by Phase 3.
+			 * Only do this for partitioned tables and indexes, for which this
+			 * is just a catalog change.  Other relation types which have
+			 * storage are handled by Phase 3.
 			 */
-			if (rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
-				ATExecPartedIdxSetTableSpace(rel, tab->newTableSpace);
+			if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+				rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
+				ATExecSetTableSpaceNoStorage(rel, tab->newTableSpace);
 
 			break;
 		case AT_SetRelOptions:	/* SET (...) */
@@ -10928,19 +10959,25 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 }
 
 /*
- * Special handling of ALTER TABLE SET TABLESPACE for partitioned indexes,
- * which have no storage (so not handled in Phase 3 like other relation types)
+ * Special handling of ALTER TABLE SET TABLESPACE for relations which have
+ * no filenode.  Since these have no storage the tablespace can be updated
+ * with a simple metadata only operation to update the tablespace.
  */
 static void
-ATExecPartedIdxSetTableSpace(Relation rel, Oid newTableSpace)
+ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace)
 {
 	HeapTuple	tuple;
 	Oid			oldTableSpace;
 	Relation	pg_class;
 	Form_pg_class rd_rel;
-	Oid			indexOid = RelationGetRelid(rel);
+	Oid			reloid = RelationGetRelid(rel);
 
-	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
+	/*
+	 * For now we only need to work with partitioned tables and indexes.  If
+	 * more relkinds are to be supported then those should be allowed below.
+	 */
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+		   rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
 
 	/* Can't allow a non-shared relation in pg_global */
 	if (newTableSpace == GLOBALTABLESPACE_OID)
@@ -10955,24 +10992,23 @@ ATExecPartedIdxSetTableSpace(Relation rel, Oid newTableSpace)
 	if (newTableSpace == oldTableSpace ||
 		(newTableSpace == MyDatabaseTableSpace && oldTableSpace == 0))
 	{
-		InvokeObjectPostAlterHook(RelationRelationId,
-								  indexOid, 0);
+		InvokeObjectPostAlterHook(RelationRelationId, reloid, 0);
 		return;
 	}
 
 	/* Get a modifiable copy of the relation's pg_class row */
 	pg_class = heap_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(indexOid));
+	tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid));
 	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "cache lookup failed for relation %u", indexOid);
+		elog(ERROR, "cache lookup failed for relation %u", reloid);
 	rd_rel = (Form_pg_class) GETSTRUCT(tuple);
 
 	/* update the pg_class row */
 	rd_rel->reltablespace = (newTableSpace == MyDatabaseTableSpace) ? InvalidOid : newTableSpace;
 	CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 
-	InvokeObjectPostAlterHook(RelationRelationId, indexOid, 0);
+	InvokeObjectPostAlterHook(RelationRelationId, reloid, 0);
 
 	heap_freetuple(tuple);
 
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 2ac757cfab..47ae73af95 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -44,6 +44,18 @@ CREATE INDEX foo_idx on testschema.foo(i) TABLESPACE regress_tblspace;
 SELECT relname, spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c
     where c.reltablespace = t.oid AND c.relname = 'foo_idx';
 
+-- partitioned table
+CREATE TABLE testschema.part (a int) PARTITION BY LIST (a);
+CREATE TABLE testschema.part12 PARTITION OF testschema.part FOR VALUES IN(1,2) PARTITION BY LIST (a) TABLESPACE regress_tblspace;
+CREATE TABLE testschema.part12_1 PARTITION OF testschema.part12 FOR VALUES IN (1);
+ALTER TABLE testschema.part12 SET TABLESPACE pg_default;
+CREATE TABLE testschema.part12_2 PARTITION OF testschema.part12 FOR VALUES IN (2);
+-- Ensure part12_1 defaulted to regress_tblspace and part12_2 defaulted to pg_default.
+SELECT relname, spcname FROM pg_catalog.pg_class c
+    LEFT JOIN pg_catalog.pg_tablespace t ON c.reltablespace = t.oid
+    where c.relname LIKE 'part%' order by relname;
+DROP TABLE testschema.part;
+
 -- partitioned index
 CREATE TABLE testschema.part (a int) PARTITION BY LIST (a);
 CREATE TABLE testschema.part1 PARTITION OF testschema.part FOR VALUES IN (1);
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 2e78e5ece6..29d6d2232b 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -61,6 +61,25 @@ SELECT relname, spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c
  foo_idx | regress_tblspace
 (1 row)
 
+-- partitioned table
+CREATE TABLE testschema.part (a int) PARTITION BY LIST (a);
+CREATE TABLE testschema.part12 PARTITION OF testschema.part FOR VALUES IN(1,2) PARTITION BY LIST (a) TABLESPACE regress_tblspace;
+CREATE TABLE testschema.part12_1 PARTITION OF testschema.part12 FOR VALUES IN (1);
+ALTER TABLE testschema.part12 SET TABLESPACE pg_default;
+CREATE TABLE testschema.part12_2 PARTITION OF testschema.part12 FOR VALUES IN (2);
+-- Ensure part12_1 defaulted to regress_tblspace and part12_2 defaulted to pg_default.
+SELECT relname, spcname FROM pg_catalog.pg_class c
+    LEFT JOIN pg_catalog.pg_tablespace t ON c.reltablespace = t.oid
+    where c.relname LIKE 'part%' order by relname;
+ relname  |     spcname      
+----------+------------------
+ part     | 
+ part12   | 
+ part12_1 | regress_tblspace
+ part12_2 | 
+(4 rows)
+
+DROP TABLE testschema.part;
 -- partitioned index
 CREATE TABLE testschema.part (a int) PARTITION BY LIST (a);
 CREATE TABLE testschema.part1 PARTITION OF testschema.part FOR VALUES IN (1);
-- 
2.16.2.windows.1

#14Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: David Rowley (#13)
1 attachment(s)
Re: Should new partitions inherit their tablespace from their parent?

I didn't like this, so I rewrote it a little bit.

First, I changed the Assert() to use the macro for relations with
storage that I just posted in the other thread that Michael mentioned.

I then noticed that we're doing a heap_openrv() in the parent relation
and closing it before MergeAttribute() does the same thing all over
again; also MergeAttribute has the silly array-of-OIDs return value for
the parents so that DefineRelation can handle it again later. Rube
Goldberg says hi. I changed this so that *before* doing anything with
the parent list, we transform it to a list of OIDs, and lock them; so
MergeAttributes now receives the list of OIDs of parents rather than
RangeVars. We can also move the important comment (about lock level of
parent rels) buried in the bowels of MergeAttribute to the place where
it belongs in DefineRelation; and we no longer have to mess with
transforming names to OIDs multiple times.

Proposed patch attached.

I'll self-review this again tomorrow, 'cause I now have to run.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

v5-0001-Allow-newly-created-partitions-to-inherit-their-p.patchtext/x-diff; charset=us-asciiDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index d3e33132f3..94f7651c34 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -1216,7 +1216,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       of the tablespace in which the new table is to be created.
       If not specified,
       <xref linkend="guc-default-tablespace"/> is consulted, or
-      <xref linkend="guc-temp-tablespaces"/> if the table is temporary.
+      <xref linkend="guc-temp-tablespaces"/> if the table is temporary.  For
+      partitioned tables, since no storage is required for the table itself,
+      the tablespace specified here only serves to mark the default tablespace
+      for any newly created partitions when no other tablespace is explicitly
+      specified.
      </para>
     </listitem>
    </varlistentry>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 651e86b71e..4d5b82aaa9 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -333,7 +333,6 @@ heap_create(const char *relname,
 		case RELKIND_VIEW:
 		case RELKIND_COMPOSITE_TYPE:
 		case RELKIND_FOREIGN_TABLE:
-		case RELKIND_PARTITIONED_TABLE:
 			create_storage = false;
 
 			/*
@@ -343,10 +342,11 @@ heap_create(const char *relname,
 			reltablespace = InvalidOid;
 			break;
 
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_PARTITIONED_INDEX:
 			/*
-			 * Preserve tablespace so that it's used as tablespace for indexes
-			 * on future partitions.
+			 * For partitioned tables and indexes, preserve tablespace so that
+			 * it's used as the tablespace for future partitions.
 			 */
 			create_storage = false;
 			break;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d6d0de1b01..bc98a0b12e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -305,7 +305,7 @@ static void truncate_check_activity(Relation rel);
 static void RangeVarCallbackForTruncate(const RangeVar *relation,
 							Oid relId, Oid oldRelId, void *arg);
 static List *MergeAttributes(List *schema, List *supers, char relpersistence,
-				bool is_partition, List **supOids, List **supconstr);
+				bool is_partition, List **supconstr);
 static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
 static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
 static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
@@ -446,7 +446,7 @@ static bool ATPrepChangePersistence(Relation rel, bool toLogged);
 static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
 					const char *tablespacename, LOCKMODE lockmode);
 static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
-static void ATExecPartedIdxSetTableSpace(Relation rel, Oid newTableSpace);
+static void ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace);
 static void ATExecSetRelOptions(Relation rel, List *defList,
 					AlterTableType operation,
 					LOCKMODE lockmode);
@@ -536,6 +536,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
 	Oid			ofTypeId;
 	ObjectAddress address;
+	LOCKMODE	parentLockmode;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -581,6 +582,38 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 				 errmsg("cannot create temporary table within security-restricted operation")));
 
 	/*
+	 * Determine the lockmode to use when scanning parents.  A self-exclusive
+	 * lock is needed here.
+	 *
+	 * For regular inheritance, if two backends attempt to add children to the
+	 * same parent simultaneously, and that parent has no pre-existing
+	 * children, then both will attempt to update the parent's relhassubclass
+	 * field, leading to a "tuple concurrently updated" error.  Also, this
+	 * interlocks against a concurrent ANALYZE on the parent table, which
+	 * might otherwise be attempting to clear the parent's relhassubclass
+	 * field, if its previous children were recently dropped.
+	 *
+	 * If the child table is a partition, then we instead grab an exclusive
+	 * lock on the parent because its partition descriptor will be changed by
+	 * addition of the new partition.
+	 */
+	parentLockmode = (stmt->partbound != NULL ? AccessExclusiveLock :
+					  ShareUpdateExclusiveLock);
+
+	/* Determine the list of OIDs of the parents. */
+	inheritOids = NIL;
+	foreach(listptr, stmt->inhRelations)
+	{
+		RangeVar   *rv = (RangeVar *) lfirst(listptr);
+		Oid			parentOid;
+
+		parentOid = RangeVarGetRelidExtended(rv, parentLockmode,
+											 0, NULL, NULL);
+
+		inheritOids = lappend_oid(inheritOids, parentOid);
+	}
+
+	/*
 	 * Select tablespace to use.  If not specified, use default tablespace
 	 * (which may in turn default to database's default).
 	 */
@@ -588,6 +621,25 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	{
 		tablespaceId = get_tablespace_oid(stmt->tablespacename, false);
 	}
+	else if (stmt->partbound)
+	{
+		HeapTuple	tup;
+
+		/*
+		 * For partitions, when no other tablespace is specified, we default
+		 * the tablespace to the parent partitioned table's.
+		 */
+		Assert(list_length(inheritOids) == 1);
+		tup = SearchSysCache1(RELOID,
+							  DatumGetObjectId(linitial_oid(inheritOids)));
+
+		tablespaceId = ((Form_pg_class) GETSTRUCT(tup))->reltablespace;
+
+		if (!OidIsValid(tablespaceId))
+			tablespaceId = GetDefaultTablespace(stmt->relation->relpersistence);
+
+		ReleaseSysCache(tup);
+	}
 	else
 	{
 		tablespaceId = GetDefaultTablespace(stmt->relation->relpersistence);
@@ -646,10 +698,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * modified by MergeAttributes.)
 	 */
 	stmt->tableElts =
-		MergeAttributes(stmt->tableElts, stmt->inhRelations,
+		MergeAttributes(stmt->tableElts, inheritOids,
 						stmt->relation->relpersistence,
 						stmt->partbound != NULL,
-						&inheritOids, &old_constraints);
+						&old_constraints);
 
 	/*
 	 * Create a tuple descriptor from the relation schema.  Note that this
@@ -1781,12 +1833,11 @@ storage_name(char c)
  * Input arguments:
  * 'schema' is the column/attribute definition for the table. (It's a list
  *		of ColumnDef's.) It is destructively changed.
- * 'supers' is a list of names (as RangeVar nodes) of parent relations.
+ * 'supers' is a list of OIDs of parent relations, already locked by caller.
  * 'relpersistence' is a persistence type of the table.
  * 'is_partition' tells if the table is a partition
  *
  * Output arguments:
- * 'supOids' receives a list of the OIDs of the parent relations.
  * 'supconstr' receives a list of constraints belonging to the parents,
  *		updated as necessary to be valid for the child.
  *
@@ -1834,7 +1885,7 @@ storage_name(char c)
  */
 static List *
 MergeAttributes(List *schema, List *supers, char relpersistence,
-				bool is_partition, List **supOids, List **supconstr)
+				bool is_partition, List **supconstr)
 {
 	ListCell   *entry;
 	List	   *inhSchema = NIL;
@@ -1939,31 +1990,15 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	child_attno = 0;
 	foreach(entry, supers)
 	{
-		RangeVar   *parent = (RangeVar *) lfirst(entry);
+		Oid			parent = lfirst_oid(entry);
 		Relation	relation;
 		TupleDesc	tupleDesc;
 		TupleConstr *constr;
 		AttrNumber *newattno;
 		AttrNumber	parent_attno;
 
-		/*
-		 * A self-exclusive lock is needed here.  If two backends attempt to
-		 * add children to the same parent simultaneously, and that parent has
-		 * no pre-existing children, then both will attempt to update the
-		 * parent's relhassubclass field, leading to a "tuple concurrently
-		 * updated" error.  Also, this interlocks against a concurrent ANALYZE
-		 * on the parent table, which might otherwise be attempting to clear
-		 * the parent's relhassubclass field, if its previous children were
-		 * recently dropped.
-		 *
-		 * If the child table is a partition, then we instead grab an
-		 * exclusive lock on the parent because its partition descriptor will
-		 * be changed by addition of the new partition.
-		 */
-		if (!is_partition)
-			relation = heap_openrv(parent, ShareUpdateExclusiveLock);
-		else
-			relation = heap_openrv(parent, AccessExclusiveLock);
+		/* caller already got lock */
+		relation = heap_open(parent, NoLock);
 
 		/*
 		 * Check for active uses of the parent partitioned table in the
@@ -1982,12 +2017,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot inherit from partitioned table \"%s\"",
-							parent->relname)));
+							RelationGetRelationName(relation))));
 		if (relation->rd_rel->relispartition && !is_partition)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot inherit from partition \"%s\"",
-							parent->relname)));
+							RelationGetRelationName(relation))));
 
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
 			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
@@ -1995,7 +2030,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("inherited relation \"%s\" is not a table or foreign table",
-							parent->relname)));
+							RelationGetRelationName(relation))));
 
 		/*
 		 * If the parent is permanent, so must be all of its partitions.  Note
@@ -2017,7 +2052,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 					 errmsg(!is_partition
 							? "cannot inherit from temporary relation \"%s\""
 							: "cannot create a permanent relation as partition of temporary relation \"%s\"",
-							parent->relname)));
+							RelationGetRelationName(relation))));
 
 		/* If existing rel is temp, it must belong to this session */
 		if (relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
@@ -2043,7 +2078,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_TABLE),
 					 errmsg("relation \"%s\" would be inherited from more than once",
-							parent->relname)));
+							RelationGetRelationName(relation))));
 
 		parentOids = lappend_oid(parentOids, RelationGetRelid(relation));
 
@@ -2463,7 +2498,6 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		}
 	}
 
-	*supOids = parentOids;
 	*supconstr = constraints;
 	return schema;
 }
@@ -4150,11 +4184,13 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			break;
 		case AT_SetTableSpace:	/* SET TABLESPACE */
 			/*
-			 * Only do this for partitioned indexes, for which this is just
-			 * a catalog change.  Other relation types are handled by Phase 3.
+			 * Only do this for partitioned tables and indexes, for which this
+			 * is just a catalog change.  Other relation types which have
+			 * storage are handled by Phase 3.
 			 */
-			if (rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
-				ATExecPartedIdxSetTableSpace(rel, tab->newTableSpace);
+			if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+				rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
+				ATExecSetTableSpaceNoStorage(rel, tab->newTableSpace);
 
 			break;
 		case AT_SetRelOptions:	/* SET (...) */
@@ -10928,19 +10964,26 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 }
 
 /*
- * Special handling of ALTER TABLE SET TABLESPACE for partitioned indexes,
- * which have no storage (so not handled in Phase 3 like other relation types)
+ * Special handling of ALTER TABLE SET TABLESPACE for relations with no
+ * storage that have an interest in preserving tablespace.
+ *
+ * Since these have no storage the tablespace can be updated with a simple
+ * metadata only operation to update the tablespace.
  */
 static void
-ATExecPartedIdxSetTableSpace(Relation rel, Oid newTableSpace)
+ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace)
 {
 	HeapTuple	tuple;
 	Oid			oldTableSpace;
 	Relation	pg_class;
 	Form_pg_class rd_rel;
-	Oid			indexOid = RelationGetRelid(rel);
+	Oid			reloid = RelationGetRelid(rel);
 
-	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
+	/*
+	 * Shouldn't be called on relations having storage; these are processed
+	 * in phase 3.
+	 */
+	Assert(!RELKIND_CAN_HAVE_STORAGE(rel->rd_rel->relkind));
 
 	/* Can't allow a non-shared relation in pg_global */
 	if (newTableSpace == GLOBALTABLESPACE_OID)
@@ -10955,24 +10998,23 @@ ATExecPartedIdxSetTableSpace(Relation rel, Oid newTableSpace)
 	if (newTableSpace == oldTableSpace ||
 		(newTableSpace == MyDatabaseTableSpace && oldTableSpace == 0))
 	{
-		InvokeObjectPostAlterHook(RelationRelationId,
-								  indexOid, 0);
+		InvokeObjectPostAlterHook(RelationRelationId, reloid, 0);
 		return;
 	}
 
 	/* Get a modifiable copy of the relation's pg_class row */
 	pg_class = heap_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(indexOid));
+	tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid));
 	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "cache lookup failed for relation %u", indexOid);
+		elog(ERROR, "cache lookup failed for relation %u", reloid);
 	rd_rel = (Form_pg_class) GETSTRUCT(tuple);
 
 	/* update the pg_class row */
 	rd_rel->reltablespace = (newTableSpace == MyDatabaseTableSpace) ? InvalidOid : newTableSpace;
 	CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 
-	InvokeObjectPostAlterHook(RelationRelationId, indexOid, 0);
+	InvokeObjectPostAlterHook(RelationRelationId, reloid, 0);
 
 	heap_freetuple(tuple);
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 84e63c6d06..321c9a1973 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -122,6 +122,19 @@ typedef FormData_pg_class *Form_pg_class;
  */
 #define		  REPLICA_IDENTITY_INDEX	'i'
 
+/*
+ * Relation kinds that have physical storage. These relations normally have
+ * relfilenode set to non-zero, but it can also be zero if the relation is
+ * mapped.
+ */
+#define RELKIND_CAN_HAVE_STORAGE(relkind) \
+	((relkind) == RELKIND_RELATION || \
+	 (relkind) == RELKIND_INDEX || \
+	 (relkind) == RELKIND_SEQUENCE || \
+	 (relkind) == RELKIND_TOASTVALUE || \
+	 (relkind) == RELKIND_MATVIEW)
+
+
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
 #endif							/* PG_CLASS_H */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 2ac757cfab..47ae73af95 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -44,6 +44,18 @@ CREATE INDEX foo_idx on testschema.foo(i) TABLESPACE regress_tblspace;
 SELECT relname, spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c
     where c.reltablespace = t.oid AND c.relname = 'foo_idx';
 
+-- partitioned table
+CREATE TABLE testschema.part (a int) PARTITION BY LIST (a);
+CREATE TABLE testschema.part12 PARTITION OF testschema.part FOR VALUES IN(1,2) PARTITION BY LIST (a) TABLESPACE regress_tblspace;
+CREATE TABLE testschema.part12_1 PARTITION OF testschema.part12 FOR VALUES IN (1);
+ALTER TABLE testschema.part12 SET TABLESPACE pg_default;
+CREATE TABLE testschema.part12_2 PARTITION OF testschema.part12 FOR VALUES IN (2);
+-- Ensure part12_1 defaulted to regress_tblspace and part12_2 defaulted to pg_default.
+SELECT relname, spcname FROM pg_catalog.pg_class c
+    LEFT JOIN pg_catalog.pg_tablespace t ON c.reltablespace = t.oid
+    where c.relname LIKE 'part%' order by relname;
+DROP TABLE testschema.part;
+
 -- partitioned index
 CREATE TABLE testschema.part (a int) PARTITION BY LIST (a);
 CREATE TABLE testschema.part1 PARTITION OF testschema.part FOR VALUES IN (1);
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 2e78e5ece6..29d6d2232b 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -61,6 +61,25 @@ SELECT relname, spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c
  foo_idx | regress_tblspace
 (1 row)
 
+-- partitioned table
+CREATE TABLE testschema.part (a int) PARTITION BY LIST (a);
+CREATE TABLE testschema.part12 PARTITION OF testschema.part FOR VALUES IN(1,2) PARTITION BY LIST (a) TABLESPACE regress_tblspace;
+CREATE TABLE testschema.part12_1 PARTITION OF testschema.part12 FOR VALUES IN (1);
+ALTER TABLE testschema.part12 SET TABLESPACE pg_default;
+CREATE TABLE testschema.part12_2 PARTITION OF testschema.part12 FOR VALUES IN (2);
+-- Ensure part12_1 defaulted to regress_tblspace and part12_2 defaulted to pg_default.
+SELECT relname, spcname FROM pg_catalog.pg_class c
+    LEFT JOIN pg_catalog.pg_tablespace t ON c.reltablespace = t.oid
+    where c.relname LIKE 'part%' order by relname;
+ relname  |     spcname      
+----------+------------------
+ part     | 
+ part12   | 
+ part12_1 | regress_tblspace
+ part12_2 | 
+(4 rows)
+
+DROP TABLE testschema.part;
 -- partitioned index
 CREATE TABLE testschema.part (a int) PARTITION BY LIST (a);
 CREATE TABLE testschema.part1 PARTITION OF testschema.part FOR VALUES IN (1);
#15Michael Paquier
michael@paquier.xyz
In reply to: Alvaro Herrera (#14)
Re: Should new partitions inherit their tablespace from their parent?

On Sun, Dec 16, 2018 at 07:07:35PM -0300, Alvaro Herrera wrote:

I'll self-review this again tomorrow, 'cause I now have to run.

-		if (!is_partition)
-			relation = heap_openrv(parent, ShareUpdateExclusiveLock);
-		else
-			relation = heap_openrv(parent, AccessExclusiveLock);
+		/* caller already got lock */
+		relation = heap_open(parent, NoLock);

Okay, I think that you should add an assertion on
CheckRelationLockedByMe() as MergeAttributes()'s only caller is
DefineRelation(). Better safe than sorry later.
--
Michael

#16David Rowley
david.rowley@2ndquadrant.com
In reply to: Michael Paquier (#15)
Re: Should new partitions inherit their tablespace from their parent?

On Mon, 17 Dec 2018 at 12:59, Michael Paquier <michael@paquier.xyz> wrote:

Okay, I think that you should add an assertion on
CheckRelationLockedByMe() as MergeAttributes()'s only caller is
DefineRelation(). Better safe than sorry later.

Would that not just double up the work that's already done in relation_open()?

--
David Rowley http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

#17David Rowley
david.rowley@2ndquadrant.com
In reply to: Alvaro Herrera (#14)
Re: Should new partitions inherit their tablespace from their parent?

On Mon, 17 Dec 2018 at 11:07, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

First, I changed the Assert() to use the macro for relations with
storage that I just posted in the other thread that Michael mentioned.

I then noticed that we're doing a heap_openrv() in the parent relation
and closing it before MergeAttribute() does the same thing all over
again; also MergeAttribute has the silly array-of-OIDs return value for
the parents so that DefineRelation can handle it again later. Rube
Goldberg says hi. I changed this so that *before* doing anything with
the parent list, we transform it to a list of OIDs, and lock them; so
MergeAttributes now receives the list of OIDs of parents rather than
RangeVars. We can also move the important comment (about lock level of
parent rels) buried in the bowels of MergeAttribute to the place where
it belongs in DefineRelation; and we no longer have to mess with
transforming names to OIDs multiple times.

Proposed patch attached.

I like this idea. Seems much better than resolving the relation name twice.

I've read over the patch and the only two things that I noted were:

1. Shouldn't you be using the RangeVarGetRelid() macro instead of
calling RangeVarGetRelidExtended()?
2. In MergeAttributes(), the parentOids list still exists and is
populated. This is now only used to determine if the "supers" list
contains any duplicate Oids. Maybe it's better to rename that list to
something like "seenOids" to avoid any confusion with the "supers"
list. Or maybe it's worth thinking of a better way to detect duplicate
items in the "supers" list.

--
David Rowley http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

#18Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: David Rowley (#17)
Re: Should new partitions inherit their tablespace from their parent?

On 2018-Dec-18, David Rowley wrote:

1. Shouldn't you be using the RangeVarGetRelid() macro instead of
calling RangeVarGetRelidExtended()?

This should have been obvious but I didn't notice.

2. In MergeAttributes(), the parentOids list still exists and is
populated. This is now only used to determine if the "supers" list
contains any duplicate Oids. Maybe it's better to rename that list to
something like "seenOids" to avoid any confusion with the "supers"
list. Or maybe it's worth thinking of a better way to detect duplicate
items in the "supers" list.

Good catch.

What I did was move the duplicate detection to the loop doing
RangeVarGetRelid, and remove parentOids from MergeAttributes.

Pushed with those changes. Thanks for the patch!

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#19David Rowley
david.rowley@2ndquadrant.com
In reply to: Alvaro Herrera (#18)
Re: Should new partitions inherit their tablespace from their parent?

On Tue, 18 Dec 2018 at 08:21, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

Pushed with those changes. Thanks for the patch!

Thanks for committing it and thanks for reviewing it, Michael.

--
David Rowley http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

#20Michael Paquier
michael@paquier.xyz
In reply to: David Rowley (#19)
Re: Should new partitions inherit their tablespace from their parent?

On Tue, Dec 18, 2018 at 09:36:17AM +1300, David Rowley wrote:

On Tue, 18 Dec 2018 at 08:21, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

Pushed with those changes. Thanks for the patch!

Thanks for committing it and thanks for reviewing it, Michael.

Perhaps too early to speak, tests of sepgsql are broken after this
commit as per rhinoceros report:
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=rhinoceros&amp;dt=2018-12-18%2000%3A45%3A01
--
Michael

#21Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Michael Paquier (#20)
Re: Should new partitions inherit their tablespace from their parent?

On 2018-Dec-18, Michael Paquier wrote:

On Tue, Dec 18, 2018 at 09:36:17AM +1300, David Rowley wrote:

On Tue, 18 Dec 2018 at 08:21, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

Pushed with those changes. Thanks for the patch!

Thanks for committing it and thanks for reviewing it, Michael.

Perhaps too early to speak, tests of sepgsql are broken after this
commit as per rhinoceros report:
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=rhinoceros&amp;dt=2018-12-18%2000%3A45%3A01

Ah, thanks.

I think we need to accept the new output. It is saying that previously
we would not invoke the hook on SET TABLESPACE for a partitioned table,
which makes sense, and now we do invoke it, which also makes sense.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#22Michael Paquier
michael@paquier.xyz
In reply to: Alvaro Herrera (#21)
Re: Should new partitions inherit their tablespace from their parent?

On Mon, Dec 17, 2018 at 10:13:42PM -0300, Alvaro Herrera wrote:

I think we need to accept the new output. It is saying that previously
we would not invoke the hook on SET TABLESPACE for a partitioned table,
which makes sense, and now we do invoke it, which also makes sense.

Indeed, that looks right.
--
Michael

#23Tom Lane
tgl@sss.pgh.pa.us
In reply to: Michael Paquier (#22)
Re: Should new partitions inherit their tablespace from their parent?

Michael Paquier <michael@paquier.xyz> writes:

On Mon, Dec 17, 2018 at 10:13:42PM -0300, Alvaro Herrera wrote:

I think we need to accept the new output. It is saying that previously
we would not invoke the hook on SET TABLESPACE for a partitioned table,
which makes sense, and now we do invoke it, which also makes sense.

Indeed, that looks right.

Agreed, done.

regards, tom lane

#24Michael Paquier
michael@paquier.xyz
In reply to: Tom Lane (#23)
Re: Should new partitions inherit their tablespace from their parent?

On Tue, Dec 18, 2018 at 11:40:49AM -0500, Tom Lane wrote:

Agreed, done.

Thanks Tom for stepping in!
--
Michael