From 578b3fae50baffa3626570447d55ce4177fc6e7d Mon Sep 17 00:00:00 2001 From: Koval Dmitry Date: Mon, 8 Apr 2024 23:10:52 +0300 Subject: [PATCH v1] Fixes for ALTER TABLE ... SPLIT/MERGE PARTITIONS ... commands --- doc/src/sgml/ddl.sgml | 2 +- src/backend/commands/tablecmds.c | 12 ------ src/backend/parser/parse_utilcmd.c | 38 +++++++++++++++++++ src/test/regress/expected/partition_merge.out | 29 +++++++++++--- src/test/regress/expected/partition_split.out | 13 +++++++ src/test/regress/sql/partition_merge.sql | 24 ++++++++++-- src/test/regress/sql/partition_split.sql | 15 ++++++++ 7 files changed, 111 insertions(+), 22 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 8ff9a520ca..cd8304ef75 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -4410,7 +4410,7 @@ ALTER TABLE measurement this operation is not supported for hash-partitioned tables and acquires an ACCESS EXCLUSIVE lock, which could impact high-load systems due to the lock's restrictive nature. For example, we can split - the quarter partition back to monthly partitions: + the quarter partition back to monthly partitions: ALTER TABLE measurement SPLIT PARTITION measurement_y2006q1 INTO (PARTITION measurement_y2006m01 FOR VALUES FROM ('2006-01-01') TO ('2006-02-01'), diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 865c6331c1..8a98a0af48 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -21223,12 +21223,6 @@ ATExecSplitPartition(List **wqueue, AlteredTableInfo *tab, Relation rel, */ splitRel = table_openrv(cmd->name, AccessExclusiveLock); - if (splitRel->rd_rel->relkind != RELKIND_RELATION) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot split non-table partition \"%s\"", - RelationGetRelationName(splitRel)))); - splitRelOid = RelationGetRelid(splitRel); /* Check descriptions of new partitions. */ @@ -21463,12 +21457,6 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel, */ mergingPartition = table_openrv(name, AccessExclusiveLock); - if (mergingPartition->rd_rel->relkind != RELKIND_RELATION) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot merge non-table partition \"%s\"", - RelationGetRelationName(mergingPartition)))); - /* * Checking that two partitions have the same name was before, in * function transformPartitionCmdForMerge(). diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 9e3e14087f..0d5ed0079c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -32,6 +32,7 @@ #include "catalog/heap.h" #include "catalog/index.h" #include "catalog/namespace.h" +#include "catalog/partition.h" #include "catalog/pg_am.h" #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" @@ -3415,6 +3416,38 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString, } +/* + * checkPartition: check that partRelOid is partition of rel + */ +static void +checkPartition(Relation rel, Oid partRelOid) +{ + Relation partRel; + + partRel = relation_open(partRelOid, AccessShareLock); + + if (partRel->rd_rel->relkind != RELKIND_RELATION) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a table", + RelationGetRelationName(partRel)))); + + if (!partRel->rd_rel->relispartition) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a partition", + RelationGetRelationName(partRel)))); + + if (get_partition_parent(partRelOid, false) != RelationGetRelid(rel)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("relation \"%s\" is not a partition of relation \"%s\"", + RelationGetRelationName(partRel), + RelationGetRelationName(rel)))); + + relation_close(partRel, AccessShareLock); +} + /* * transformPartitionCmdForSplit * Analyze the ALTER TABLLE ... SPLIT PARTITION command @@ -3447,6 +3480,8 @@ transformPartitionCmdForSplit(CreateStmtContext *cxt, PartitionCmd *partcmd) splitPartOid = RangeVarGetRelid(partcmd->name, NoLock, false); + checkPartition(parent, splitPartOid); + /* Then we should check partitions with transformed bounds. */ check_partitions_for_split(parent, splitPartOid, partcmd->name, partcmd->partlist, cxt->pstate); } @@ -3509,6 +3544,9 @@ transformPartitionCmdForMerge(CreateStmtContext *cxt, PartitionCmd *partcmd) partOid = RangeVarGetRelid(name, NoLock, false); if (partOid == defaultPartOid) isDefaultPart = true; + + checkPartition(parent, partOid); + partOids = lappend_oid(partOids, partOid); } diff --git a/src/test/regress/expected/partition_merge.out b/src/test/regress/expected/partition_merge.out index 2ba0ec47d9..60eacf6bf3 100644 --- a/src/test/regress/expected/partition_merge.out +++ b/src/test/regress/expected/partition_merge.out @@ -25,9 +25,9 @@ ALTER TABLE sales_range MERGE PARTITIONS (sales_feb2022, sales_mar2022, sales_fe ERROR: partition with name "sales_feb2022" already used LINE 1: ...e MERGE PARTITIONS (sales_feb2022, sales_mar2022, sales_feb2... ^ --- ERROR: cannot merge non-table partition "sales_apr2022" +-- ERROR: "sales_apr2022" is not a table ALTER TABLE sales_range MERGE PARTITIONS (sales_feb2022, sales_mar2022, sales_apr2022) INTO sales_feb_mar_apr2022; -ERROR: cannot merge non-table partition "sales_apr2022" +ERROR: "sales_apr2022" is not a table -- ERROR: invalid partitions order, partition "sales_mar2022" can not be merged -- (space between sections sales_jan2022 and sales_mar2022) ALTER TABLE sales_range MERGE PARTITIONS (sales_jan2022, sales_mar2022) INTO sales_jan_mar2022; @@ -590,12 +590,12 @@ CREATE TABLE sales_nord2 PARTITION OF sales_list2 FOR VALUES IN ('Oslo', 'St. Pe CREATE TABLE sales_others2 PARTITION OF sales_list2 DEFAULT; CREATE TABLE sales_external (LIKE sales_list); CREATE TABLE sales_external2 (vch VARCHAR(5)); --- ERROR: partition bound for relation "sales_external" is null +-- ERROR: "sales_external" is not a partition ALTER TABLE sales_list MERGE PARTITIONS (sales_west, sales_east, sales_external) INTO sales_all; -ERROR: partition bound for relation "sales_external" is null --- ERROR: partition bound for relation "sales_external2" is null +ERROR: "sales_external" is not a partition +-- ERROR: "sales_external2" is not a partition ALTER TABLE sales_list MERGE PARTITIONS (sales_west, sales_east, sales_external2) INTO sales_all; -ERROR: partition bound for relation "sales_external2" is null +ERROR: "sales_external2" is not a partition -- ERROR: relation "sales_nord2" is not a partition of relation "sales_list" ALTER TABLE sales_list MERGE PARTITIONS (sales_west, sales_nord2, sales_east) INTO sales_all; ERROR: relation "sales_nord2" is not a partition of relation "sales_list" @@ -729,4 +729,21 @@ SELECT * FROM sales_list WHERE salesman_name = 'Ivanov'; RESET enable_seqscan; DROP TABLE sales_list; -- +-- Try to MERGE partitions of another table. +-- +CREATE TABLE t1 (i int, a int, b int, c int) PARTITION BY RANGE (a, b); +CREATE TABLE t1p1 PARTITION OF t1 FOR VALUES FROM (1, 1) TO (1, 2); +CREATE TABLE t2 (i int, t text) PARTITION BY RANGE (t); +CREATE TABLE t2pa PARTITION OF t2 FOR VALUES FROM ('A') TO ('C'); +CREATE TABLE t3 (i int, t text); +-- ERROR: relation "t1p1" is not a partition of relation "t2" +ALTER TABLE t2 MERGE PARTITIONS (t1p1, t2pa) INTO t2p; +ERROR: relation "t1p1" is not a partition of relation "t2" +-- ERROR: "t3" is not a partition +ALTER TABLE t2 MERGE PARTITIONS (t2pa, t3) INTO t2p; +ERROR: "t3" is not a partition +DROP TABLE t3; +DROP TABLE t2; +DROP TABLE t1; +-- DROP SCHEMA partitions_merge_schema; diff --git a/src/test/regress/expected/partition_split.out b/src/test/regress/expected/partition_split.out index 675a1453c3..26a0d09969 100644 --- a/src/test/regress/expected/partition_split.out +++ b/src/test/regress/expected/partition_split.out @@ -1414,4 +1414,17 @@ SELECT * FROM sales_others; DROP TABLE sales_range; -- +-- Try to SPLIT partition of another table. +-- +CREATE TABLE t1(i int, t text) PARTITION BY LIST (t); +CREATE TABLE t1pa PARTITION OF t1 FOR VALUES IN ('A'); +CREATE TABLE t2 (i int, t text) PARTITION BY RANGE (t); +-- ERROR: relation "t1pa" is not a partition of relation "t2" +ALTER TABLE t2 SPLIT PARTITION t1pa INTO + (PARTITION t2a FOR VALUES FROM ('A') TO ('B'), + PARTITION t2b FOR VALUES FROM ('B') TO ('C')); +ERROR: relation "t1pa" is not a partition of relation "t2" +DROP TABLE t2; +DROP TABLE t1; +-- DROP SCHEMA partition_split_schema; diff --git a/src/test/regress/sql/partition_merge.sql b/src/test/regress/sql/partition_merge.sql index bb461e6623..9afed70365 100644 --- a/src/test/regress/sql/partition_merge.sql +++ b/src/test/regress/sql/partition_merge.sql @@ -28,7 +28,7 @@ CREATE TABLE sales_others PARTITION OF sales_range DEFAULT; -- ERROR: partition with name "sales_feb2022" already used ALTER TABLE sales_range MERGE PARTITIONS (sales_feb2022, sales_mar2022, sales_feb2022) INTO sales_feb_mar_apr2022; --- ERROR: cannot merge non-table partition "sales_apr2022" +-- ERROR: "sales_apr2022" is not a table ALTER TABLE sales_range MERGE PARTITIONS (sales_feb2022, sales_mar2022, sales_apr2022) INTO sales_feb_mar_apr2022; -- ERROR: invalid partitions order, partition "sales_mar2022" can not be merged -- (space between sections sales_jan2022 and sales_mar2022) @@ -350,9 +350,9 @@ CREATE TABLE sales_others2 PARTITION OF sales_list2 DEFAULT; CREATE TABLE sales_external (LIKE sales_list); CREATE TABLE sales_external2 (vch VARCHAR(5)); --- ERROR: partition bound for relation "sales_external" is null +-- ERROR: "sales_external" is not a partition ALTER TABLE sales_list MERGE PARTITIONS (sales_west, sales_east, sales_external) INTO sales_all; --- ERROR: partition bound for relation "sales_external2" is null +-- ERROR: "sales_external2" is not a partition ALTER TABLE sales_list MERGE PARTITIONS (sales_west, sales_east, sales_external2) INTO sales_all; -- ERROR: relation "sales_nord2" is not a partition of relation "sales_list" ALTER TABLE sales_list MERGE PARTITIONS (sales_west, sales_nord2, sales_east) INTO sales_all; @@ -426,5 +426,23 @@ RESET enable_seqscan; DROP TABLE sales_list; +-- +-- Try to MERGE partitions of another table. +-- +CREATE TABLE t1 (i int, a int, b int, c int) PARTITION BY RANGE (a, b); +CREATE TABLE t1p1 PARTITION OF t1 FOR VALUES FROM (1, 1) TO (1, 2); +CREATE TABLE t2 (i int, t text) PARTITION BY RANGE (t); +CREATE TABLE t2pa PARTITION OF t2 FOR VALUES FROM ('A') TO ('C'); +CREATE TABLE t3 (i int, t text); + +-- ERROR: relation "t1p1" is not a partition of relation "t2" +ALTER TABLE t2 MERGE PARTITIONS (t1p1, t2pa) INTO t2p; +-- ERROR: "t3" is not a partition +ALTER TABLE t2 MERGE PARTITIONS (t2pa, t3) INTO t2p; + +DROP TABLE t3; +DROP TABLE t2; +DROP TABLE t1; + -- DROP SCHEMA partitions_merge_schema; diff --git a/src/test/regress/sql/partition_split.sql b/src/test/regress/sql/partition_split.sql index 8864f6ddaa..625b01ddd1 100644 --- a/src/test/regress/sql/partition_split.sql +++ b/src/test/regress/sql/partition_split.sql @@ -829,5 +829,20 @@ SELECT * FROM sales_others; DROP TABLE sales_range; +-- +-- Try to SPLIT partition of another table. +-- +CREATE TABLE t1(i int, t text) PARTITION BY LIST (t); +CREATE TABLE t1pa PARTITION OF t1 FOR VALUES IN ('A'); +CREATE TABLE t2 (i int, t text) PARTITION BY RANGE (t); + +-- ERROR: relation "t1pa" is not a partition of relation "t2" +ALTER TABLE t2 SPLIT PARTITION t1pa INTO + (PARTITION t2a FOR VALUES FROM ('A') TO ('B'), + PARTITION t2b FOR VALUES FROM ('B') TO ('C')); + +DROP TABLE t2; +DROP TABLE t1; + -- DROP SCHEMA partition_split_schema; -- 2.40.1.windows.1