Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit fcf80c5

Browse files
committed
Make new partitions with parent's persistence during MERGE/SPLIT
The createPartitionTable() function is responsible for creating new partitions for ALTER TABLE ... MERGE PARTITIONS, and ALTER TABLE ... SPLIT PARTITION commands. It emulates the behaviour of CREATE TABLE ... (LIKE ...), where new table persistence should be specified by the user. In the table partitioning persistent of the partition and its parent must match. So, this commit makes createPartitionTable() copy the persistence of the parent partition. Also, this commit makes createPartitionTable() recheck the persistence after the new table creation. This is needed because persistence might be affected by pg_temp in search_path. This commit also changes the signature of createPartitionTable() making it take the parent's Relation itself instead of the name of the parent relation, and return the Relation of new partition. That doesn't lead to complications, because both callers have the parent table open and need to open the new partition. Reported-by: Alexander Lakhin Discussion: https://postgr.es/m/dbc8b96c-3cf0-d1ee-860d-0e491da20485%40gmail.com Author: Dmitry Koval Reviewed-by: Alexander Korotkov, Robert Haas, Justin Pryzby, Pavel Borisov
1 parent 842c9b2 commit fcf80c5

File tree

6 files changed

+364
-57
lines changed

6 files changed

+364
-57
lines changed

doc/src/sgml/ref/alter_table.sgml

+6
Original file line numberDiff line numberDiff line change
@@ -1158,6 +1158,9 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
11581158
SQL command <literal>CREATE TABLE <replaceable class="parameter">partition_nameN</replaceable> (LIKE <replaceable class="parameter">name</replaceable> INCLUDING ALL EXCLUDING INDEXES EXCLUDING IDENTITY)</literal>.
11591159
The indexes and identity are created later, after moving the data
11601160
into the new partitions.
1161+
If the parent table is persistent then new partitions are created
1162+
persistent. If the parent table is temporary then new partitions
1163+
are also created temporary.
11611164
</para>
11621165
<note>
11631166
<para>
@@ -1224,6 +1227,9 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
12241227
SQL command <literal>CREATE TABLE <replaceable class="parameter">partition_name</replaceable> (LIKE <replaceable class="parameter">name</replaceable> INCLUDING ALL EXCLUDING INDEXES EXCLUDING IDENTITY)</literal>.
12251228
The indexes and identity are created later, after moving the data
12261229
into the new partition.
1230+
If the parent table is persistent then the new partition is created
1231+
persistent. If the parent table is temporary then the new partition
1232+
is also created temporary.
12271233
</para>
12281234
<note>
12291235
<para>

src/backend/commands/tablecmds.c

+50-25
Original file line numberDiff line numberDiff line change
@@ -21209,18 +21209,30 @@ moveSplitTableRows(Relation rel, Relation splitRel, List *partlist, List *newPar
2120921209

2121021210
/*
2121121211
* createPartitionTable: create table for a new partition with given name
21212-
* (newPartName) like table (modelRelName)
21212+
* (newPartName) like table (modelRel)
2121321213
*
21214-
* Emulates command: CREATE TABLE <newPartName> (LIKE <modelRelName>
21214+
* Emulates command: CREATE [TEMP] TABLE <newPartName> (LIKE <modelRel's name>
2121521215
* INCLUDING ALL EXCLUDING INDEXES EXCLUDING IDENTITY)
21216+
* Function returns the created relation (locked in AccessExclusiveLock mode).
2121621217
*/
21217-
static void
21218-
createPartitionTable(RangeVar *newPartName, RangeVar *modelRelName,
21218+
static Relation
21219+
createPartitionTable(RangeVar *newPartName, Relation modelRel,
2121921220
AlterTableUtilityContext *context)
2122021221
{
2122121222
CreateStmt *createStmt;
2122221223
TableLikeClause *tlc;
2122321224
PlannedStmt *wrapper;
21225+
Relation newRel;
21226+
21227+
/* If existing rel is temp, it must belong to this session */
21228+
if (modelRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
21229+
!modelRel->rd_islocaltemp)
21230+
ereport(ERROR,
21231+
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
21232+
errmsg("cannot create as partition of temporary relation of another session")));
21233+
21234+
/* New partition should have the same persistence as modelRel */
21235+
newPartName->relpersistence = modelRel->rd_rel->relpersistence;
2122421236

2122521237
createStmt = makeNode(CreateStmt);
2122621238
createStmt->relation = newPartName;
@@ -21233,7 +21245,8 @@ createPartitionTable(RangeVar *newPartName, RangeVar *modelRelName,
2123321245
createStmt->if_not_exists = false;
2123421246

2123521247
tlc = makeNode(TableLikeClause);
21236-
tlc->relation = modelRelName;
21248+
tlc->relation = makeRangeVar(get_namespace_name(RelationGetNamespace(modelRel)),
21249+
RelationGetRelationName(modelRel), -1);
2123721250

2123821251
/*
2123921252
* Indexes will be inherited on "attach new partitions" stage, after data
@@ -21259,6 +21272,35 @@ createPartitionTable(RangeVar *newPartName, RangeVar *modelRelName,
2125921272
NULL,
2126021273
None_Receiver,
2126121274
NULL);
21275+
21276+
/*
21277+
* Open the new partition with no lock, because we already have
21278+
* AccessExclusiveLock placed there after creation.
21279+
*/
21280+
newRel = table_openrv(newPartName, NoLock);
21281+
21282+
/*
21283+
* We intended to create the partition with the same persistence as the
21284+
* parent table, but we still need to recheck because that might be
21285+
* affected by the search_path. If the parent is permanent, so must be
21286+
* all of its partitions.
21287+
*/
21288+
if (modelRel->rd_rel->relpersistence != RELPERSISTENCE_TEMP &&
21289+
newRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
21290+
ereport(ERROR,
21291+
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
21292+
errmsg("cannot create a temporary relation as partition of permanent relation \"%s\"",
21293+
RelationGetRelationName(modelRel))));
21294+
21295+
/* Permanent rels cannot be partitions belonging to temporary parent */
21296+
if (newRel->rd_rel->relpersistence != RELPERSISTENCE_TEMP &&
21297+
modelRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
21298+
ereport(ERROR,
21299+
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
21300+
errmsg("cannot create a permanent relation as partition of temporary relation \"%s\"",
21301+
RelationGetRelationName(modelRel))));
21302+
21303+
return newRel;
2126221304
}
2126321305

2126421306
/*
@@ -21278,7 +21320,6 @@ ATExecSplitPartition(List **wqueue, AlteredTableInfo *tab, Relation rel,
2127821320
char tmpRelName[NAMEDATALEN];
2127921321
List *newPartRels = NIL;
2128021322
ObjectAddress object;
21281-
RangeVar *parentName;
2128221323
Oid defaultPartOid;
2128321324

2128421325
defaultPartOid = get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true));
@@ -21350,18 +21391,12 @@ ATExecSplitPartition(List **wqueue, AlteredTableInfo *tab, Relation rel,
2135021391
}
2135121392

2135221393
/* Create new partitions (like split partition), without indexes. */
21353-
parentName = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
21354-
RelationGetRelationName(rel), -1);
2135521394
foreach(listptr, cmd->partlist)
2135621395
{
2135721396
SinglePartitionSpec *sps = (SinglePartitionSpec *) lfirst(listptr);
2135821397
Relation newPartRel;
2135921398

21360-
createPartitionTable(sps->name, parentName, context);
21361-
21362-
/* Open the new partition and acquire exclusive lock on it. */
21363-
newPartRel = table_openrv(sps->name, AccessExclusiveLock);
21364-
21399+
newPartRel = createPartitionTable(sps->name, rel, context);
2136521400
newPartRels = lappend(newPartRels, newPartRel);
2136621401
}
2136721402

@@ -21565,18 +21600,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
2156521600
DetachPartitionFinalize(rel, mergingPartition, false, defaultPartOid);
2156621601
}
2156721602

21568-
createPartitionTable(cmd->name,
21569-
makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
21570-
RelationGetRelationName(rel), -1),
21571-
context);
21572-
21573-
/*
21574-
* Open the new partition and acquire exclusive lock on it. This will
21575-
* stop all the operations with partitioned table. This might seem
21576-
* excessive, but this is the way we make sure nobody is planning queries
21577-
* involving merging partitions.
21578-
*/
21579-
newPartRel = table_openrv(cmd->name, AccessExclusiveLock);
21603+
/* Create table for new partition, use partitioned table as model. */
21604+
newPartRel = createPartitionTable(cmd->name, rel, context);
2158021605

2158121606
/* Copy data from merged partitions to new partition. */
2158221607
moveMergedTablesRows(rel, mergingPartitionsList, newPartRel);

src/test/regress/expected/partition_merge.out

+114-20
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
-- Tests for "ALTER TABLE ... MERGE PARTITIONS ..." command
44
--
55
CREATE SCHEMA partitions_merge_schema;
6+
CREATE SCHEMA partitions_merge_schema2;
67
SET search_path = partitions_merge_schema, public;
78
--
89
-- BY RANGE partitioning
@@ -36,18 +37,23 @@ ERROR: lower bound of partition "sales_mar2022" conflicts with upper bound of p
3637
-- (space between sections sales_dec2021 and sales_jan2022)
3738
ALTER TABLE sales_range MERGE PARTITIONS (sales_dec2021, sales_jan2022, sales_feb2022) INTO sales_dec_jan_feb2022;
3839
ERROR: lower bound of partition "sales_jan2022" conflicts with upper bound of previous partition "sales_dec2021"
39-
-- NO ERROR: test for custom partitions order
40-
ALTER TABLE sales_range MERGE PARTITIONS (sales_feb2022, sales_mar2022, sales_jan2022) INTO sales_jan_feb_mar2022;
40+
-- NO ERROR: test for custom partitions order, source partitions not in the search_path
41+
SET search_path = partitions_merge_schema2, public;
42+
ALTER TABLE partitions_merge_schema.sales_range MERGE PARTITIONS (
43+
partitions_merge_schema.sales_feb2022,
44+
partitions_merge_schema.sales_mar2022,
45+
partitions_merge_schema.sales_jan2022) INTO sales_jan_feb_mar2022;
46+
SET search_path = partitions_merge_schema, public;
4147
SELECT c.oid::pg_catalog.regclass, c.relkind, inhdetachpending, pg_catalog.pg_get_expr(c.relpartbound, c.oid)
4248
FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i
4349
WHERE c.oid = i.inhrelid AND i.inhparent = 'sales_range'::regclass
4450
ORDER BY pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT', c.oid::pg_catalog.regclass::pg_catalog.text;
45-
oid | relkind | inhdetachpending | pg_get_expr
46-
-----------------------+---------+------------------+--------------------------------------------------
47-
sales_apr2022 | p | f | FOR VALUES FROM ('04-01-2022') TO ('05-01-2022')
48-
sales_dec2021 | r | f | FOR VALUES FROM ('12-01-2021') TO ('12-31-2021')
49-
sales_jan_feb_mar2022 | r | f | FOR VALUES FROM ('01-01-2022') TO ('04-01-2022')
50-
sales_others | r | f | DEFAULT
51+
oid | relkind | inhdetachpending | pg_get_expr
52+
------------------------------------------------+---------+------------------+--------------------------------------------------
53+
partitions_merge_schema2.sales_jan_feb_mar2022 | r | f | FOR VALUES FROM ('01-01-2022') TO ('04-01-2022')
54+
sales_apr2022 | p | f | FOR VALUES FROM ('04-01-2022') TO ('05-01-2022')
55+
sales_dec2021 | r | f | FOR VALUES FROM ('12-01-2021') TO ('12-31-2021')
56+
sales_others | r | f | DEFAULT
5157
(4 rows)
5258

5359
DROP TABLE sales_range;
@@ -95,23 +101,24 @@ SELECT c.oid::pg_catalog.regclass, c.relkind, inhdetachpending, pg_catalog.pg_ge
95101
sales_others | r | f | DEFAULT
96102
(5 rows)
97103

98-
ALTER TABLE sales_range MERGE PARTITIONS (sales_feb2022, sales_mar2022, sales_apr2022) INTO sales_feb_mar_apr2022;
104+
-- check schema-qualified name of the new partition
105+
ALTER TABLE sales_range MERGE PARTITIONS (sales_feb2022, sales_mar2022, sales_apr2022) INTO partitions_merge_schema2.sales_feb_mar_apr2022;
99106
-- show partitions with conditions:
100107
SELECT c.oid::pg_catalog.regclass, c.relkind, inhdetachpending, pg_catalog.pg_get_expr(c.relpartbound, c.oid)
101108
FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i
102109
WHERE c.oid = i.inhrelid AND i.inhparent = 'sales_range'::regclass
103110
ORDER BY pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT', c.oid::pg_catalog.regclass::pg_catalog.text;
104-
oid | relkind | inhdetachpending | pg_get_expr
105-
-----------------------+---------+------------------+--------------------------------------------------
106-
sales_feb_mar_apr2022 | r | f | FOR VALUES FROM ('02-01-2022') TO ('05-01-2022')
107-
sales_jan2022 | r | f | FOR VALUES FROM ('01-01-2022') TO ('02-01-2022')
108-
sales_others | r | f | DEFAULT
111+
oid | relkind | inhdetachpending | pg_get_expr
112+
------------------------------------------------+---------+------------------+--------------------------------------------------
113+
partitions_merge_schema2.sales_feb_mar_apr2022 | r | f | FOR VALUES FROM ('02-01-2022') TO ('05-01-2022')
114+
sales_jan2022 | r | f | FOR VALUES FROM ('01-01-2022') TO ('02-01-2022')
115+
sales_others | r | f | DEFAULT
109116
(3 rows)
110117

111-
SELECT * FROM pg_indexes WHERE tablename = 'sales_feb_mar_apr2022' and schemaname = 'partitions_merge_schema';
112-
schemaname | tablename | indexname | tablespace | indexdef
113-
-------------------------+-----------------------+--------------------------------------+------------+-----------------------------------------------------------------------------------------------------------------------------
114-
partitions_merge_schema | sales_feb_mar_apr2022 | sales_feb_mar_apr2022_sales_date_idx | | CREATE INDEX sales_feb_mar_apr2022_sales_date_idx ON partitions_merge_schema.sales_feb_mar_apr2022 USING btree (sales_date)
118+
SELECT * FROM pg_indexes WHERE tablename = 'sales_feb_mar_apr2022' and schemaname = 'partitions_merge_schema2';
119+
schemaname | tablename | indexname | tablespace | indexdef
120+
--------------------------+-----------------------+--------------------------------------+------------+------------------------------------------------------------------------------------------------------------------------------
121+
partitions_merge_schema2 | sales_feb_mar_apr2022 | sales_feb_mar_apr2022_sales_date_idx | | CREATE INDEX sales_feb_mar_apr2022_sales_date_idx ON partitions_merge_schema2.sales_feb_mar_apr2022 USING btree (sales_date)
115122
(1 row)
116123

117124
SELECT * FROM sales_range;
@@ -141,7 +148,7 @@ SELECT * FROM sales_jan2022;
141148
13 | Gandi | 377 | 01-09-2022
142149
(3 rows)
143150

144-
SELECT * FROM sales_feb_mar_apr2022;
151+
SELECT * FROM partitions_merge_schema2.sales_feb_mar_apr2022;
145152
salesman_id | salesman_name | sales_amount | sales_date
146153
-------------+---------------+--------------+------------
147154
2 | Smirnoff | 500 | 02-10-2022
@@ -164,7 +171,7 @@ SELECT * FROM sales_others;
164171

165172
-- Use indexscan for testing indexes
166173
SET enable_seqscan = OFF;
167-
SELECT * FROM sales_feb_mar_apr2022 where sales_date > '2022-01-01';
174+
SELECT * FROM partitions_merge_schema2.sales_feb_mar_apr2022 where sales_date > '2022-01-01';
168175
salesman_id | salesman_name | sales_amount | sales_date
169176
-------------+---------------+--------------+------------
170177
2 | Smirnoff | 500 | 02-10-2022
@@ -746,6 +753,34 @@ DROP TABLE t3;
746753
DROP TABLE t2;
747754
DROP TABLE t1;
748755
--
756+
-- Try to MERGE partitions of temporary table.
757+
--
758+
CREATE TEMP TABLE t (i int) PARTITION BY RANGE (i);
759+
CREATE TEMP TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1);
760+
CREATE TEMP TABLE tp_1_2 PARTITION OF t FOR VALUES FROM (1) TO (2);
761+
SELECT c.oid::pg_catalog.regclass, pg_catalog.pg_get_expr(c.relpartbound, c.oid), c.relpersistence
762+
FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i
763+
WHERE c.oid = i.inhrelid AND i.inhparent = 't'::regclass
764+
ORDER BY pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT', c.oid::pg_catalog.regclass::pg_catalog.text;
765+
oid | pg_get_expr | relpersistence
766+
--------+----------------------------+----------------
767+
tp_0_1 | FOR VALUES FROM (0) TO (1) | t
768+
tp_1_2 | FOR VALUES FROM (1) TO (2) | t
769+
(2 rows)
770+
771+
ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2;
772+
-- Partition should be temporary.
773+
SELECT c.oid::pg_catalog.regclass, pg_catalog.pg_get_expr(c.relpartbound, c.oid), c.relpersistence
774+
FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i
775+
WHERE c.oid = i.inhrelid AND i.inhparent = 't'::regclass
776+
ORDER BY pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT', c.oid::pg_catalog.regclass::pg_catalog.text;
777+
oid | pg_get_expr | relpersistence
778+
--------+----------------------------+----------------
779+
tp_0_2 | FOR VALUES FROM (0) TO (2) | t
780+
(1 row)
781+
782+
DROP TABLE t;
783+
--
749784
-- Check the partition index name if the partition name is the same as one
750785
-- of the merged partitions.
751786
--
@@ -771,4 +806,63 @@ Not-null constraints:
771806

772807
DROP TABLE t;
773808
--
809+
-- Try mixing permanent and temporary partitions.
810+
--
811+
SET search_path = partitions_merge_schema, pg_temp, public;
812+
CREATE TABLE t (i int) PARTITION BY RANGE (i);
813+
CREATE TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1);
814+
CREATE TABLE tp_1_2 PARTITION OF t FOR VALUES FROM (1) TO (2);
815+
SELECT c.oid::pg_catalog.regclass, c.relpersistence FROM pg_catalog.pg_class c WHERE c.oid = 't'::regclass;
816+
oid | relpersistence
817+
-----+----------------
818+
t | p
819+
(1 row)
820+
821+
SELECT c.oid::pg_catalog.regclass, pg_catalog.pg_get_expr(c.relpartbound, c.oid), c.relpersistence
822+
FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i
823+
WHERE c.oid = i.inhrelid AND i.inhparent = 't'::regclass
824+
ORDER BY pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT', c.oid::pg_catalog.regclass::pg_catalog.text;
825+
oid | pg_get_expr | relpersistence
826+
--------+----------------------------+----------------
827+
tp_0_1 | FOR VALUES FROM (0) TO (1) | p
828+
tp_1_2 | FOR VALUES FROM (1) TO (2) | p
829+
(2 rows)
830+
831+
SET search_path = pg_temp, partitions_merge_schema, public;
832+
-- Can't merge persistent partitions into a temporary partition
833+
ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2;
834+
ERROR: cannot create a temporary relation as partition of permanent relation "t"
835+
SET search_path = partitions_merge_schema, public;
836+
-- Can't merge persistent partitions into a temporary partition
837+
ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO pg_temp.tp_0_2;
838+
ERROR: cannot create a temporary relation as partition of permanent relation "t"
839+
DROP TABLE t;
840+
SET search_path = pg_temp, partitions_merge_schema, public;
841+
BEGIN;
842+
CREATE TABLE t (i int) PARTITION BY RANGE (i);
843+
CREATE TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1);
844+
CREATE TABLE tp_1_2 PARTITION OF t FOR VALUES FROM (1) TO (2);
845+
SELECT c.oid::pg_catalog.regclass, c.relpersistence FROM pg_catalog.pg_class c WHERE c.oid = 't'::regclass;
846+
oid | relpersistence
847+
-----+----------------
848+
t | t
849+
(1 row)
850+
851+
SELECT c.oid::pg_catalog.regclass, pg_catalog.pg_get_expr(c.relpartbound, c.oid), c.relpersistence
852+
FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i
853+
WHERE c.oid = i.inhrelid AND i.inhparent = 't'::regclass
854+
ORDER BY pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT', c.oid::pg_catalog.regclass::pg_catalog.text;
855+
oid | pg_get_expr | relpersistence
856+
--------+----------------------------+----------------
857+
tp_0_1 | FOR VALUES FROM (0) TO (1) | t
858+
tp_1_2 | FOR VALUES FROM (1) TO (2) | t
859+
(2 rows)
860+
861+
SET search_path = partitions_merge_schema, pg_temp, public;
862+
-- Can't merge temporary partitions into a persistent partition
863+
ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2;
864+
ROLLBACK;
865+
RESET search_path;
866+
--
774867
DROP SCHEMA partitions_merge_schema;
868+
DROP SCHEMA partitions_merge_schema2;

0 commit comments

Comments
 (0)