mirror of
https://github.com/postgres/postgres.git
synced 2026-10-23 13:29:09 +03:00
Take into account default_tablespace during MERGE/SPLIT PARTITION(S)
createPartitionTable() passed the partitioned parent's reltablespace straight to heap_create_with_catalog(), bypassing the default_tablespace GUC fallback that DefineRelation() applies for CREATE TABLE ... PARTITION OF. When the parent had no explicit tablespace (reltablespace = 0), the new partition unconditionally landed in the database default, even if default_tablespace was set to something else; merging or splitting a set of partitions that all lived in a non-default tablespace produced a new partition in the database default. Mirror DefineRelation()'s logic: take parent's reltablespace if set, otherwise check GetDefaultTablespace() (which reads default_tablespace and normalises pg_default / MyDatabaseTableSpace to InvalidOid). Also add the CREATE ACL check on the resolved tablespace and the pg_global rejection, matching DefineRelation()'s behavior. Update the documentation for MERGE/SPLIT PARTITION to spell out the tablespace-selection rule explicitly. Reported-by: Justin Pryzby <pryzby@telsasoft.com> Reviewed-by: Pavel Borisov <pashkin.elfe@gmail.com> Discussion: https://postgr.es/m/ajQTklv8QArzTp3h%40pryzbyj2023
This commit is contained in:
@@ -1234,8 +1234,13 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
|
|||||||
<para>
|
<para>
|
||||||
<command>ALTER TABLE MERGE PARTITION</command> uses the partitioned
|
<command>ALTER TABLE MERGE PARTITION</command> uses the partitioned
|
||||||
table itself as the template to construct the new partition.
|
table itself as the template to construct the new partition.
|
||||||
The new partition will inherit the same table access method, persistence
|
The new partition inherits the table access method and persistence type
|
||||||
type, and tablespace as the partitioned table.
|
of the partitioned table. Its tablespace is selected as for a
|
||||||
|
<command>CREATE TABLE ... PARTITION OF</command> command issued
|
||||||
|
without a <literal>TABLESPACE</literal> clause: if the partitioned
|
||||||
|
table has an explicit tablespace, the new partition uses it;
|
||||||
|
otherwise the value of <xref linkend="guc-default-tablespace"/> is
|
||||||
|
taken into account, falling back to the database's default tablespace.
|
||||||
|
|
||||||
Constraints, column defaults, column generation expressions, identity
|
Constraints, column defaults, column generation expressions, identity
|
||||||
columns, indexes, and triggers are copied from the partitioned table to
|
columns, indexes, and triggers are copied from the partitioned table to
|
||||||
@@ -1332,8 +1337,13 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
|
|||||||
<para>
|
<para>
|
||||||
<command>ALTER TABLE SPLIT PARTITION</command> uses the partitioned
|
<command>ALTER TABLE SPLIT PARTITION</command> uses the partitioned
|
||||||
table itself as the template to construct new partitions.
|
table itself as the template to construct new partitions.
|
||||||
New partitions will inherit the same table access method, persistence
|
New partitions inherit the table access method and persistence type of
|
||||||
type, and tablespace as the partitioned table.
|
the partitioned table. Their tablespace is selected as for a
|
||||||
|
<command>CREATE TABLE ... PARTITION OF</command> command issued
|
||||||
|
without a <literal>TABLESPACE</literal> clause: if the partitioned
|
||||||
|
table has an explicit tablespace, the new partitions use it;
|
||||||
|
otherwise the value of <xref linkend="guc-default-tablespace"/> is
|
||||||
|
taken into account, falling back to the database's default tablespace.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
|
|||||||
@@ -22735,6 +22735,7 @@ createPartitionTable(List **wqueue, RangeVar *newPartName,
|
|||||||
Relation newRel;
|
Relation newRel;
|
||||||
Oid newRelId;
|
Oid newRelId;
|
||||||
Oid existingRelid;
|
Oid existingRelid;
|
||||||
|
Oid tablespaceId;
|
||||||
TupleDesc descriptor;
|
TupleDesc descriptor;
|
||||||
List *colList = NIL;
|
List *colList = NIL;
|
||||||
Oid relamId;
|
Oid relamId;
|
||||||
@@ -22786,10 +22787,38 @@ createPartitionTable(List **wqueue, RangeVar *newPartName,
|
|||||||
errmsg("cannot create a permanent relation as partition of temporary relation \"%s\"",
|
errmsg("cannot create a permanent relation as partition of temporary relation \"%s\"",
|
||||||
RelationGetRelationName(parent_rel)));
|
RelationGetRelationName(parent_rel)));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Select the tablespace for the new partition. Mirror the logic that
|
||||||
|
* CREATE TABLE foo PARTITION OF ... uses in DefineRelation: take the
|
||||||
|
* partitioned parent's explicit tablespace if it has one, otherwise take
|
||||||
|
* default_tablespace into account, and finally use the database default.
|
||||||
|
*/
|
||||||
|
tablespaceId = parent_relform->reltablespace;
|
||||||
|
if (!OidIsValid(tablespaceId))
|
||||||
|
tablespaceId = GetDefaultTablespace(newPartName->relpersistence, false);
|
||||||
|
|
||||||
|
/* Check permissions except when using database's default */
|
||||||
|
if (OidIsValid(tablespaceId) && tablespaceId != MyDatabaseTableSpace)
|
||||||
|
{
|
||||||
|
AclResult aclresult;
|
||||||
|
|
||||||
|
aclresult = object_aclcheck(TableSpaceRelationId, tablespaceId,
|
||||||
|
GetUserId(), ACL_CREATE);
|
||||||
|
if (aclresult != ACLCHECK_OK)
|
||||||
|
aclcheck_error(aclresult, OBJECT_TABLESPACE,
|
||||||
|
get_tablespace_name(tablespaceId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* In all cases disallow placing user relations in pg_global */
|
||||||
|
if (tablespaceId == GLOBALTABLESPACE_OID)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||||
|
errmsg("only shared relations can be placed in pg_global tablespace")));
|
||||||
|
|
||||||
/* Create the relation. */
|
/* Create the relation. */
|
||||||
newRelId = heap_create_with_catalog(newPartName->relname,
|
newRelId = heap_create_with_catalog(newPartName->relname,
|
||||||
namespaceId,
|
namespaceId,
|
||||||
parent_relform->reltablespace,
|
tablespaceId,
|
||||||
InvalidOid,
|
InvalidOid,
|
||||||
InvalidOid,
|
InvalidOid,
|
||||||
InvalidOid,
|
InvalidOid,
|
||||||
|
|||||||
@@ -1114,6 +1114,59 @@ SELECT length(a) FROM t;
|
|||||||
10000
|
10000
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
DROP TABLE t;
|
||||||
|
-- Tablespace selection for the new merged partition mirrors
|
||||||
|
-- CREATE TABLE ... PARTITION OF: the partitioned root's explicit
|
||||||
|
-- tablespace wins; otherwise default_tablespace applies; otherwise the
|
||||||
|
-- database default is used.
|
||||||
|
CREATE TABLE t (i int) PARTITION BY RANGE(i) TABLESPACE regress_tblspace;
|
||||||
|
CREATE TABLE tp_0_5 PARTITION OF t FOR VALUES FROM (0) TO (5);
|
||||||
|
CREATE TABLE tp_5_10 PARTITION OF t FOR VALUES FROM (5) TO (10);
|
||||||
|
INSERT INTO t SELECT generate_series(0, 9);
|
||||||
|
ALTER TABLE t MERGE PARTITIONS (tp_0_5, tp_5_10) INTO tp_merged;
|
||||||
|
SELECT spcname FROM pg_class c LEFT JOIN pg_tablespace s
|
||||||
|
ON c.reltablespace = s.oid WHERE c.relname = 'tp_merged';
|
||||||
|
spcname
|
||||||
|
------------------
|
||||||
|
regress_tblspace
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
DROP TABLE t;
|
||||||
|
-- Parent has no explicit tablespace, but default_tablespace is set: the
|
||||||
|
-- new partition lands on default_tablespace.
|
||||||
|
CREATE TABLE t (i int) PARTITION BY RANGE(i);
|
||||||
|
CREATE TABLE tp_0_5 PARTITION OF t FOR VALUES FROM (0) TO (5);
|
||||||
|
CREATE TABLE tp_5_10 PARTITION OF t FOR VALUES FROM (5) TO (10);
|
||||||
|
INSERT INTO t SELECT generate_series(0, 9);
|
||||||
|
SET default_tablespace TO regress_tblspace;
|
||||||
|
ALTER TABLE t MERGE PARTITIONS (tp_0_5, tp_5_10) INTO tp_merged;
|
||||||
|
RESET default_tablespace;
|
||||||
|
SELECT spcname FROM pg_class c LEFT JOIN pg_tablespace s
|
||||||
|
ON c.reltablespace = s.oid WHERE c.relname = 'tp_merged';
|
||||||
|
spcname
|
||||||
|
------------------
|
||||||
|
regress_tblspace
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
DROP TABLE t;
|
||||||
|
CREATE TABLE t (i int) PARTITION BY RANGE(i);
|
||||||
|
CREATE TABLE tp_0_5 PARTITION OF t FOR VALUES FROM (0) TO (5);
|
||||||
|
CREATE TABLE tp_5_10 PARTITION OF t FOR VALUES FROM (5) TO (10);
|
||||||
|
INSERT INTO t SELECT generate_series(0, 9);
|
||||||
|
-- pg_global is rejected when picked up from default_tablespace.
|
||||||
|
SET default_tablespace TO pg_global;
|
||||||
|
ALTER TABLE t MERGE PARTITIONS (tp_0_5, tp_5_10) INTO tp_merged; -- fails
|
||||||
|
ERROR: only shared relations can be placed in pg_global tablespace
|
||||||
|
RESET default_tablespace;
|
||||||
|
-- Parent has no explicit tablespace and default_tablespace is empty: the
|
||||||
|
-- new partition uses the database default (reltablespace = 0).
|
||||||
|
ALTER TABLE t MERGE PARTITIONS (tp_0_5, tp_5_10) INTO tp_merged;
|
||||||
|
SELECT reltablespace FROM pg_class WHERE relname = 'tp_merged';
|
||||||
|
reltablespace
|
||||||
|
---------------
|
||||||
|
0
|
||||||
|
(1 row)
|
||||||
|
|
||||||
DROP TABLE t;
|
DROP TABLE t;
|
||||||
RESET search_path;
|
RESET search_path;
|
||||||
--
|
--
|
||||||
|
|||||||
@@ -1683,6 +1683,74 @@ SELECT length(a) FROM t;
|
|||||||
10000
|
10000
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
DROP TABLE t;
|
||||||
|
-- Tablespace selection for the new partitions mirrors
|
||||||
|
-- CREATE TABLE ... PARTITION OF: the partitioned root's explicit
|
||||||
|
-- tablespace wins; otherwise default_tablespace applies; otherwise the
|
||||||
|
-- database default is used.
|
||||||
|
CREATE TABLE t (i int) PARTITION BY RANGE(i) TABLESPACE regress_tblspace;
|
||||||
|
CREATE TABLE tp_all PARTITION OF t FOR VALUES FROM (0) TO (10);
|
||||||
|
INSERT INTO t SELECT generate_series(0, 9);
|
||||||
|
ALTER TABLE t SPLIT PARTITION tp_all INTO (
|
||||||
|
PARTITION tp_lo FOR VALUES FROM (0) TO (5),
|
||||||
|
PARTITION tp_hi FOR VALUES FROM (5) TO (10)
|
||||||
|
);
|
||||||
|
SELECT c.relname, s.spcname FROM pg_class c LEFT JOIN pg_tablespace s
|
||||||
|
ON c.reltablespace = s.oid WHERE c.relname IN ('tp_lo', 'tp_hi')
|
||||||
|
ORDER BY c.relname;
|
||||||
|
relname | spcname
|
||||||
|
---------+------------------
|
||||||
|
tp_hi | regress_tblspace
|
||||||
|
tp_lo | regress_tblspace
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
DROP TABLE t;
|
||||||
|
-- Parent has no explicit tablespace, but default_tablespace is set: the
|
||||||
|
-- new partitions land on default_tablespace.
|
||||||
|
CREATE TABLE t (i int) PARTITION BY RANGE(i);
|
||||||
|
CREATE TABLE tp_all PARTITION OF t FOR VALUES FROM (0) TO (10);
|
||||||
|
INSERT INTO t SELECT generate_series(0, 9);
|
||||||
|
SET default_tablespace TO regress_tblspace;
|
||||||
|
ALTER TABLE t SPLIT PARTITION tp_all INTO (
|
||||||
|
PARTITION tp_lo FOR VALUES FROM (0) TO (5),
|
||||||
|
PARTITION tp_hi FOR VALUES FROM (5) TO (10)
|
||||||
|
);
|
||||||
|
RESET default_tablespace;
|
||||||
|
SELECT c.relname, s.spcname FROM pg_class c LEFT JOIN pg_tablespace s
|
||||||
|
ON c.reltablespace = s.oid WHERE c.relname IN ('tp_lo', 'tp_hi')
|
||||||
|
ORDER BY c.relname;
|
||||||
|
relname | spcname
|
||||||
|
---------+------------------
|
||||||
|
tp_hi | regress_tblspace
|
||||||
|
tp_lo | regress_tblspace
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
DROP TABLE t;
|
||||||
|
CREATE TABLE t (i int) PARTITION BY RANGE(i);
|
||||||
|
CREATE TABLE tp_all PARTITION OF t FOR VALUES FROM (0) TO (10);
|
||||||
|
INSERT INTO t SELECT generate_series(0, 9);
|
||||||
|
-- pg_global is rejected when picked up from default_tablespace.
|
||||||
|
SET default_tablespace TO pg_global;
|
||||||
|
ALTER TABLE t SPLIT PARTITION tp_all INTO (
|
||||||
|
PARTITION tp_lo FOR VALUES FROM (0) TO (5),
|
||||||
|
PARTITION tp_hi FOR VALUES FROM (5) TO (10)
|
||||||
|
); -- fails
|
||||||
|
ERROR: only shared relations can be placed in pg_global tablespace
|
||||||
|
RESET default_tablespace;
|
||||||
|
-- Parent has no explicit tablespace and default_tablespace is empty: new
|
||||||
|
-- partitions use the database default (reltablespace = 0).
|
||||||
|
ALTER TABLE t SPLIT PARTITION tp_all INTO (
|
||||||
|
PARTITION tp_lo FOR VALUES FROM (0) TO (5),
|
||||||
|
PARTITION tp_hi FOR VALUES FROM (5) TO (10)
|
||||||
|
);
|
||||||
|
SELECT relname, reltablespace FROM pg_class
|
||||||
|
WHERE relname IN ('tp_lo', 'tp_hi') ORDER BY relname;
|
||||||
|
relname | reltablespace
|
||||||
|
---------+---------------
|
||||||
|
tp_hi | 0
|
||||||
|
tp_lo | 0
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
DROP TABLE t;
|
DROP TABLE t;
|
||||||
RESET search_path;
|
RESET search_path;
|
||||||
--
|
--
|
||||||
|
|||||||
@@ -798,6 +798,46 @@ SELECT reltoastrelid <> 0 AS has_toast,
|
|||||||
SELECT length(a) FROM t;
|
SELECT length(a) FROM t;
|
||||||
DROP TABLE t;
|
DROP TABLE t;
|
||||||
|
|
||||||
|
-- Tablespace selection for the new merged partition mirrors
|
||||||
|
-- CREATE TABLE ... PARTITION OF: the partitioned root's explicit
|
||||||
|
-- tablespace wins; otherwise default_tablespace applies; otherwise the
|
||||||
|
-- database default is used.
|
||||||
|
CREATE TABLE t (i int) PARTITION BY RANGE(i) TABLESPACE regress_tblspace;
|
||||||
|
CREATE TABLE tp_0_5 PARTITION OF t FOR VALUES FROM (0) TO (5);
|
||||||
|
CREATE TABLE tp_5_10 PARTITION OF t FOR VALUES FROM (5) TO (10);
|
||||||
|
INSERT INTO t SELECT generate_series(0, 9);
|
||||||
|
ALTER TABLE t MERGE PARTITIONS (tp_0_5, tp_5_10) INTO tp_merged;
|
||||||
|
SELECT spcname FROM pg_class c LEFT JOIN pg_tablespace s
|
||||||
|
ON c.reltablespace = s.oid WHERE c.relname = 'tp_merged';
|
||||||
|
DROP TABLE t;
|
||||||
|
|
||||||
|
-- Parent has no explicit tablespace, but default_tablespace is set: the
|
||||||
|
-- new partition lands on default_tablespace.
|
||||||
|
CREATE TABLE t (i int) PARTITION BY RANGE(i);
|
||||||
|
CREATE TABLE tp_0_5 PARTITION OF t FOR VALUES FROM (0) TO (5);
|
||||||
|
CREATE TABLE tp_5_10 PARTITION OF t FOR VALUES FROM (5) TO (10);
|
||||||
|
INSERT INTO t SELECT generate_series(0, 9);
|
||||||
|
SET default_tablespace TO regress_tblspace;
|
||||||
|
ALTER TABLE t MERGE PARTITIONS (tp_0_5, tp_5_10) INTO tp_merged;
|
||||||
|
RESET default_tablespace;
|
||||||
|
SELECT spcname FROM pg_class c LEFT JOIN pg_tablespace s
|
||||||
|
ON c.reltablespace = s.oid WHERE c.relname = 'tp_merged';
|
||||||
|
DROP TABLE t;
|
||||||
|
|
||||||
|
CREATE TABLE t (i int) PARTITION BY RANGE(i);
|
||||||
|
CREATE TABLE tp_0_5 PARTITION OF t FOR VALUES FROM (0) TO (5);
|
||||||
|
CREATE TABLE tp_5_10 PARTITION OF t FOR VALUES FROM (5) TO (10);
|
||||||
|
INSERT INTO t SELECT generate_series(0, 9);
|
||||||
|
-- pg_global is rejected when picked up from default_tablespace.
|
||||||
|
SET default_tablespace TO pg_global;
|
||||||
|
ALTER TABLE t MERGE PARTITIONS (tp_0_5, tp_5_10) INTO tp_merged; -- fails
|
||||||
|
RESET default_tablespace;
|
||||||
|
-- Parent has no explicit tablespace and default_tablespace is empty: the
|
||||||
|
-- new partition uses the database default (reltablespace = 0).
|
||||||
|
ALTER TABLE t MERGE PARTITIONS (tp_0_5, tp_5_10) INTO tp_merged;
|
||||||
|
SELECT reltablespace FROM pg_class WHERE relname = 'tp_merged';
|
||||||
|
DROP TABLE t;
|
||||||
|
|
||||||
|
|
||||||
RESET search_path;
|
RESET search_path;
|
||||||
|
|
||||||
|
|||||||
@@ -1204,6 +1204,58 @@ SELECT relname,
|
|||||||
SELECT length(a) FROM t;
|
SELECT length(a) FROM t;
|
||||||
DROP TABLE t;
|
DROP TABLE t;
|
||||||
|
|
||||||
|
-- Tablespace selection for the new partitions mirrors
|
||||||
|
-- CREATE TABLE ... PARTITION OF: the partitioned root's explicit
|
||||||
|
-- tablespace wins; otherwise default_tablespace applies; otherwise the
|
||||||
|
-- database default is used.
|
||||||
|
CREATE TABLE t (i int) PARTITION BY RANGE(i) TABLESPACE regress_tblspace;
|
||||||
|
CREATE TABLE tp_all PARTITION OF t FOR VALUES FROM (0) TO (10);
|
||||||
|
INSERT INTO t SELECT generate_series(0, 9);
|
||||||
|
ALTER TABLE t SPLIT PARTITION tp_all INTO (
|
||||||
|
PARTITION tp_lo FOR VALUES FROM (0) TO (5),
|
||||||
|
PARTITION tp_hi FOR VALUES FROM (5) TO (10)
|
||||||
|
);
|
||||||
|
SELECT c.relname, s.spcname FROM pg_class c LEFT JOIN pg_tablespace s
|
||||||
|
ON c.reltablespace = s.oid WHERE c.relname IN ('tp_lo', 'tp_hi')
|
||||||
|
ORDER BY c.relname;
|
||||||
|
DROP TABLE t;
|
||||||
|
|
||||||
|
-- Parent has no explicit tablespace, but default_tablespace is set: the
|
||||||
|
-- new partitions land on default_tablespace.
|
||||||
|
CREATE TABLE t (i int) PARTITION BY RANGE(i);
|
||||||
|
CREATE TABLE tp_all PARTITION OF t FOR VALUES FROM (0) TO (10);
|
||||||
|
INSERT INTO t SELECT generate_series(0, 9);
|
||||||
|
SET default_tablespace TO regress_tblspace;
|
||||||
|
ALTER TABLE t SPLIT PARTITION tp_all INTO (
|
||||||
|
PARTITION tp_lo FOR VALUES FROM (0) TO (5),
|
||||||
|
PARTITION tp_hi FOR VALUES FROM (5) TO (10)
|
||||||
|
);
|
||||||
|
RESET default_tablespace;
|
||||||
|
SELECT c.relname, s.spcname FROM pg_class c LEFT JOIN pg_tablespace s
|
||||||
|
ON c.reltablespace = s.oid WHERE c.relname IN ('tp_lo', 'tp_hi')
|
||||||
|
ORDER BY c.relname;
|
||||||
|
DROP TABLE t;
|
||||||
|
|
||||||
|
CREATE TABLE t (i int) PARTITION BY RANGE(i);
|
||||||
|
CREATE TABLE tp_all PARTITION OF t FOR VALUES FROM (0) TO (10);
|
||||||
|
INSERT INTO t SELECT generate_series(0, 9);
|
||||||
|
-- pg_global is rejected when picked up from default_tablespace.
|
||||||
|
SET default_tablespace TO pg_global;
|
||||||
|
ALTER TABLE t SPLIT PARTITION tp_all INTO (
|
||||||
|
PARTITION tp_lo FOR VALUES FROM (0) TO (5),
|
||||||
|
PARTITION tp_hi FOR VALUES FROM (5) TO (10)
|
||||||
|
); -- fails
|
||||||
|
RESET default_tablespace;
|
||||||
|
-- Parent has no explicit tablespace and default_tablespace is empty: new
|
||||||
|
-- partitions use the database default (reltablespace = 0).
|
||||||
|
ALTER TABLE t SPLIT PARTITION tp_all INTO (
|
||||||
|
PARTITION tp_lo FOR VALUES FROM (0) TO (5),
|
||||||
|
PARTITION tp_hi FOR VALUES FROM (5) TO (10)
|
||||||
|
);
|
||||||
|
SELECT relname, reltablespace FROM pg_class
|
||||||
|
WHERE relname IN ('tp_lo', 'tp_hi') ORDER BY relname;
|
||||||
|
DROP TABLE t;
|
||||||
|
|
||||||
RESET search_path;
|
RESET search_path;
|
||||||
|
|
||||||
--
|
--
|
||||||
|
|||||||
Reference in New Issue
Block a user