From abfbd47c4d5ae6cc6ff538688fe2b85601912e18 Mon Sep 17 00:00:00 2001 From: Koval Dmitry Date: Wed, 10 Apr 2024 18:54:05 +0300 Subject: [PATCH v6] Fix for SPLIT/MERGE partitions of temporary table --- src/backend/commands/tablecmds.c | 84 +++++++++++++------ src/backend/parser/parse_utilcmd.c | 2 +- src/test/regress/expected/partition_merge.out | 75 ++++++++++++++++- src/test/regress/expected/partition_split.out | 29 +++++++ src/test/regress/sql/partition_merge.sql | 69 ++++++++++++++- src/test/regress/sql/partition_split.sql | 23 +++++ 6 files changed, 253 insertions(+), 29 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 000212f24c4..d38eb51002f 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -21124,16 +21124,24 @@ moveSplitTableRows(Relation rel, Relation splitRel, List *partlist, List *newPar * createPartitionTable: create table for a new partition with given name * (newPartName) like table (modelRelName) * - * Emulates command: CREATE TABLE (LIKE + * Emulates command: CREATE [TEMP] TABLE (LIKE * INCLUDING ALL EXCLUDING INDEXES EXCLUDING IDENTITY) */ -static void -createPartitionTable(RangeVar *newPartName, RangeVar *modelRelName, +static Relation +createPartitionTable(Relation rel, RangeVar *newPartName, RangeVar *modelRelName, AlterTableUtilityContext *context) { CreateStmt *createStmt; TableLikeClause *tlc; PlannedStmt *wrapper; + Relation newRel; + + /* If existing rel is temp, it must belong to this session */ + if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP && + !rel->rd_islocaltemp) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot create as partition of temporary relation of another session"))); createStmt = makeNode(CreateStmt); createStmt->relation = newPartName; @@ -21172,6 +21180,35 @@ createPartitionTable(RangeVar *newPartName, RangeVar *modelRelName, NULL, None_Receiver, NULL); + + /* + * Open the new partition and acquire exclusive lock on it. This will + * stop all the operations with partitioned table. This might seem + * excessive, but this is the way we make sure nobody is planning queries + * involving merging partitions. + */ + newRel = table_openrv(newPartName, AccessExclusiveLock); + + /* + * If the parent is permanent, so must be all of its partitions. Note + * that inheritance allows that case. + */ + if (rel->rd_rel->relpersistence != RELPERSISTENCE_TEMP && + newRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot create a temporary relation as partition of permanent relation \"%s\"", + RelationGetRelationName(rel)))); + + /* Permanent rels cannot inherit from temporary ones */ + if (newRel->rd_rel->relpersistence != RELPERSISTENCE_TEMP && + rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot create a permanent relation as partition of temporary relation \"%s\"", + RelationGetRelationName(rel)))); + + return newRel; } /* @@ -21270,11 +21307,7 @@ ATExecSplitPartition(List **wqueue, AlteredTableInfo *tab, Relation rel, SinglePartitionSpec *sps = (SinglePartitionSpec *) lfirst(listptr); Relation newPartRel; - createPartitionTable(sps->name, parentName, context); - - /* Open the new partition and acquire exclusive lock on it. */ - newPartRel = table_openrv(sps->name, AccessExclusiveLock); - + newPartRel = createPartitionTable(rel, sps->name, parentName, context); newPartRels = lappend(newPartRels, newPartRel); } @@ -21466,28 +21499,16 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel, mergePartName = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)), tmpRelName, -1); } - createPartitionTable(mergePartName, - makeRangeVar(get_namespace_name(RelationGetNamespace(rel)), - RelationGetRelationName(rel), -1), - context); - /* - * Open the new partition and acquire exclusive lock on it. This will - * stop all the operations with partitioned table. This might seem - * excessive, but this is the way we make sure nobody is planning queries - * involving merging partitions. - */ - newPartRel = table_openrv(mergePartName, AccessExclusiveLock); + newPartRel = createPartitionTable(rel, + mergePartName, + makeRangeVar(get_namespace_name(RelationGetNamespace(rel)), + RelationGetRelationName(rel), -1), + context); /* Copy data from merged partitions to new partition. */ moveMergedTablesRows(rel, mergingPartitionsList, newPartRel); - /* - * Attach a new partition to the partitioned table. wqueue = NULL: - * verification for each cloned constraint is not need. - */ - attachPartitionTable(NULL, rel, newPartRel, cmd->bound); - /* Unlock and drop merged partitions. */ foreach(listptr, mergingPartitionsList) { @@ -21517,7 +21538,20 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel, /* Rename partition. */ RenameRelationInternal(RelationGetRelid(newPartRel), cmd->name->relname, false, false); + + /* + * Bump the command counter to make the tuple of renamed partition + * visible for attach partition operation. + */ + CommandCounterIncrement(); } + + /* + * Attach a new partition to the partitioned table. wqueue = NULL: + * verification for each cloned constraint is not needed. + */ + attachPartitionTable(NULL, rel, newPartRel, cmd->bound); + /* Keep the lock until commit. */ table_close(newPartRel, NoLock); } diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index ceba0699050..a3bbcc99c02 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3430,7 +3430,7 @@ checkPartition(Relation rel, Oid partRelOid) if (partRel->rd_rel->relkind != RELKIND_RELATION) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a table", + errmsg("\"%s\" is not an ordinary table", RelationGetRelationName(partRel)))); if (!partRel->rd_rel->relispartition) diff --git a/src/test/regress/expected/partition_merge.out b/src/test/regress/expected/partition_merge.out index 60eacf6bf39..b1d0b50b0b4 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: "sales_apr2022" is not a table +-- ERROR: "sales_apr2022" is not an ordinary table ALTER TABLE sales_range MERGE PARTITIONS (sales_feb2022, sales_mar2022, sales_apr2022) INTO sales_feb_mar_apr2022; -ERROR: "sales_apr2022" is not a table +ERROR: "sales_apr2022" is not an ordinary 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; @@ -746,4 +746,75 @@ DROP TABLE t3; DROP TABLE t2; DROP TABLE t1; -- +-- Try to MERGE partitions of temporary table. +-- +CREATE TEMP TABLE t (i int) PARTITION BY RANGE (i); +CREATE TEMP TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1); +CREATE TEMP TABLE tp_1_2 PARTITION OF t FOR VALUES FROM (1) TO (2); +SELECT c.oid::pg_catalog.regclass, pg_catalog.pg_get_expr(c.relpartbound, c.oid), c.relpersistence + FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i + WHERE c.oid = i.inhrelid AND i.inhparent = 't'::regclass + ORDER BY pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT', c.oid::pg_catalog.regclass::pg_catalog.text; + oid | pg_get_expr | relpersistence +--------+----------------------------+---------------- + tp_0_1 | FOR VALUES FROM (0) TO (1) | t + tp_1_2 | FOR VALUES FROM (1) TO (2) | t +(2 rows) + +ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2; +ERROR: cannot create a permanent relation as partition of temporary relation "t" +-- Partition should be temporary. +SELECT c.oid::pg_catalog.regclass, pg_catalog.pg_get_expr(c.relpartbound, c.oid), c.relpersistence + FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i + WHERE c.oid = i.inhrelid AND i.inhparent = 't'::regclass + ORDER BY pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT', c.oid::pg_catalog.regclass::pg_catalog.text; + oid | pg_get_expr | relpersistence +--------+----------------------------+---------------- + tp_0_1 | FOR VALUES FROM (0) TO (1) | t + tp_1_2 | FOR VALUES FROM (1) TO (2) | t +(2 rows) + +DROP TABLE t; +-- +-- Check the partition index name if the partition name is the same as one +-- of the merged partitions. +-- +CREATE TABLE t (i int) PARTITION BY RANGE (i); +CREATE TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1); +CREATE TABLE tp_1_2 PARTITION OF t FOR VALUES FROM (1) TO (2); +CREATE INDEX tidx ON t(i); +ALTER TABLE t MERGE PARTITIONS (tp_1_2, tp_0_1) INTO tp_1_2; +-- Indexname value should be 'tp_1_2_i_idx'. +SELECT indexname FROM pg_indexes WHERE tablename = 'tp_1_2'; + indexname +-------------- + tp_1_2_i_idx +(1 row) + +DROP TABLE t; +-- +-- Try creating a partition in the temporary schema. +-- +SET search_path = public, pg_temp; +CREATE TABLE t (i int) PARTITION BY RANGE (i); +CREATE TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1); +CREATE TABLE tp_1_2 PARTITION OF t FOR VALUES FROM (1) TO (2); +SET search_path = pg_temp, public; +-- Can't merge persistent partitions into a temporary partition +ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2; +ERROR: cannot create a temporary relation as partition of permanent relation "t" +DROP TABLE t; +DROP TAble tp_0_2; +ERROR: table "tp_0_2" does not exist +BEGIN; +CREATE TABLE t (i int) PARTITION BY RANGE (i); +CREATE TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1); +CREATE TABLE tp_1_2 PARTITION OF t FOR VALUES FROM (1) TO (2); +SET search_path = public, pg_temp; +-- Can't merge temporary partitions into a persistent partition +ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2; +ERROR: cannot create a permanent relation as partition of temporary relation "t" +ROLLBACK; +RESET search_path; +-- DROP SCHEMA partitions_merge_schema; diff --git a/src/test/regress/expected/partition_split.out b/src/test/regress/expected/partition_split.out index 26a0d099696..46cd82ce191 100644 --- a/src/test/regress/expected/partition_split.out +++ b/src/test/regress/expected/partition_split.out @@ -1427,4 +1427,33 @@ ERROR: relation "t1pa" is not a partition of relation "t2" DROP TABLE t2; DROP TABLE t1; -- +-- Try to SPLIT partition of temporary table. +-- +CREATE TEMP TABLE t (i int) PARTITION BY RANGE (i); +CREATE TEMP TABLE tp_0_2 PARTITION OF t FOR VALUES FROM (0) TO (2); +SELECT c.oid::pg_catalog.regclass, pg_catalog.pg_get_expr(c.relpartbound, c.oid), c.relpersistence + FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i + WHERE c.oid = i.inhrelid AND i.inhparent = 't'::regclass + ORDER BY pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT', c.oid::pg_catalog.regclass::pg_catalog.text; + oid | pg_get_expr | relpersistence +--------+----------------------------+---------------- + tp_0_2 | FOR VALUES FROM (0) TO (2) | t +(1 row) + +ALTER TABLE t SPLIT PARTITION tp_0_2 INTO + (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), + PARTITION tp_1_2 FOR VALUES FROM (1) TO (2)); +ERROR: cannot create a permanent relation as partition of temporary relation "t" +-- Partitions should be temporary. +SELECT c.oid::pg_catalog.regclass, pg_catalog.pg_get_expr(c.relpartbound, c.oid), c.relpersistence + FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i + WHERE c.oid = i.inhrelid AND i.inhparent = 't'::regclass + ORDER BY pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT', c.oid::pg_catalog.regclass::pg_catalog.text; + oid | pg_get_expr | relpersistence +--------+----------------------------+---------------- + tp_0_2 | FOR VALUES FROM (0) TO (2) | t +(1 row) + +DROP TABLE t; +-- DROP SCHEMA partition_split_schema; diff --git a/src/test/regress/sql/partition_merge.sql b/src/test/regress/sql/partition_merge.sql index 9afed70365f..2d55e0f5a38 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: "sales_apr2022" is not a table +-- ERROR: "sales_apr2022" is not an ordinary 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) @@ -444,5 +444,72 @@ DROP TABLE t3; DROP TABLE t2; DROP TABLE t1; +-- +-- Try to MERGE partitions of temporary table. +-- +CREATE TEMP TABLE t (i int) PARTITION BY RANGE (i); +CREATE TEMP TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1); +CREATE TEMP TABLE tp_1_2 PARTITION OF t FOR VALUES FROM (1) TO (2); + +SELECT c.oid::pg_catalog.regclass, pg_catalog.pg_get_expr(c.relpartbound, c.oid), c.relpersistence + FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i + WHERE c.oid = i.inhrelid AND i.inhparent = 't'::regclass + ORDER BY pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT', c.oid::pg_catalog.regclass::pg_catalog.text; + +ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2; + +-- Partition should be temporary. +SELECT c.oid::pg_catalog.regclass, pg_catalog.pg_get_expr(c.relpartbound, c.oid), c.relpersistence + FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i + WHERE c.oid = i.inhrelid AND i.inhparent = 't'::regclass + ORDER BY pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT', c.oid::pg_catalog.regclass::pg_catalog.text; + +DROP TABLE t; + +-- +-- Check the partition index name if the partition name is the same as one +-- of the merged partitions. +-- +CREATE TABLE t (i int) PARTITION BY RANGE (i); + +CREATE TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1); +CREATE TABLE tp_1_2 PARTITION OF t FOR VALUES FROM (1) TO (2); + +CREATE INDEX tidx ON t(i); +ALTER TABLE t MERGE PARTITIONS (tp_1_2, tp_0_1) INTO tp_1_2; + +-- Indexname value should be 'tp_1_2_i_idx'. +SELECT indexname FROM pg_indexes WHERE tablename = 'tp_1_2'; + +DROP TABLE t; + +-- +-- Try creating a partition in the temporary schema. +-- +SET search_path = public, pg_temp; +CREATE TABLE t (i int) PARTITION BY RANGE (i); +CREATE TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1); +CREATE TABLE tp_1_2 PARTITION OF t FOR VALUES FROM (1) TO (2); + +SET search_path = pg_temp, public; + +-- Can't merge persistent partitions into a temporary partition +ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2; +DROP TABLE t; +DROP TAble tp_0_2; + +BEGIN; +CREATE TABLE t (i int) PARTITION BY RANGE (i); +CREATE TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1); +CREATE TABLE tp_1_2 PARTITION OF t FOR VALUES FROM (1) TO (2); + +SET search_path = public, pg_temp; + +-- Can't merge temporary partitions into a persistent partition +ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2; +ROLLBACK; + +RESET search_path; + -- DROP SCHEMA partitions_merge_schema; diff --git a/src/test/regress/sql/partition_split.sql b/src/test/regress/sql/partition_split.sql index 625b01ddd1f..5dc2fc39cd5 100644 --- a/src/test/regress/sql/partition_split.sql +++ b/src/test/regress/sql/partition_split.sql @@ -844,5 +844,28 @@ ALTER TABLE t2 SPLIT PARTITION t1pa INTO DROP TABLE t2; DROP TABLE t1; +-- +-- Try to SPLIT partition of temporary table. +-- +CREATE TEMP TABLE t (i int) PARTITION BY RANGE (i); +CREATE TEMP TABLE tp_0_2 PARTITION OF t FOR VALUES FROM (0) TO (2); + +SELECT c.oid::pg_catalog.regclass, pg_catalog.pg_get_expr(c.relpartbound, c.oid), c.relpersistence + FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i + WHERE c.oid = i.inhrelid AND i.inhparent = 't'::regclass + ORDER BY pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT', c.oid::pg_catalog.regclass::pg_catalog.text; + +ALTER TABLE t SPLIT PARTITION tp_0_2 INTO + (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), + PARTITION tp_1_2 FOR VALUES FROM (1) TO (2)); + +-- Partitions should be temporary. +SELECT c.oid::pg_catalog.regclass, pg_catalog.pg_get_expr(c.relpartbound, c.oid), c.relpersistence + FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i + WHERE c.oid = i.inhrelid AND i.inhparent = 't'::regclass + ORDER BY pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT', c.oid::pg_catalog.regclass::pg_catalog.text; + +DROP TABLE t; + -- DROP SCHEMA partition_split_schema; -- 2.39.3 (Apple Git-145)