1
0
mirror of https://github.com/postgres/postgres.git synced 2026-10-23 13:29:09 +03:00

Revert support for ALTER TABLE ... MERGE/SPLIT PARTITION(S) commands

This commit reverts f2e4cc4279 and 4b3d173629, and the subsequent fixes and
improvements c5ae07a90a, 713e553e32, 52e629be95, ecb2508aaf, 9354896920,
971017c495, 83df16f1fa, e64a9ba2b4, ff8bec8c46, cdae794af3, 57f19774d6, and
881033ae8b.  d8af730100 and 0392fb900e cancelled each other out and are not
reverted separately.

The feature is reverted due to multiple design issues which are too late to
address in this release cycle.

Discussion: https://postgr.es/m/CAN4CZFNCU%3Dt09M%3D%2Br2t9hHLJuujdM4oQ8hCK_Sx-GpfiwMAicw%40mail.gmail.com
This commit is contained in:
Alexander Korotkov
2026-08-27 00:56:41 +03:00
parent 3c73b272b4
commit 3e8bcc8644
29 changed files with 46 additions and 9379 deletions

View File

@@ -4779,44 +4779,6 @@ ALTER INDEX measurement_city_id_logdate_key
...
</programlisting>
</para>
<para>
There is also an option for merging multiple table partitions into
a single partition using the
<link linkend="sql-altertable-merge-partitions"><command>ALTER TABLE ... MERGE PARTITIONS</command></link>.
This feature simplifies the management of partitioned tables by allowing
users to combine partitions that are no longer needed as
separate entities. It's important to note that this operation is not
supported for hash-partitioned tables and acquires an
<literal>ACCESS EXCLUSIVE</literal> lock, which could impact high-load
systems due to the lock's restrictive nature. For example, we can
merge three monthly partitions into one quarter partition:
<programlisting>
ALTER TABLE measurement
MERGE PARTITIONS (measurement_y2006m01,
measurement_y2006m02,
measurement_y2006m03) INTO measurement_y2006q1;
</programlisting>
</para>
<para>
Similarly to merging multiple table partitions, there is an option for
splitting a single partition into multiple using the
<link linkend="sql-altertable-split-partition"><command>ALTER TABLE ... SPLIT PARTITION</command></link>.
This feature could come in handy when one partition grows too big
and needs to be split into multiple. It's important to note that
this operation is not supported for hash-partitioned tables and acquires
an <literal>ACCESS EXCLUSIVE</literal> 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:
<programlisting>
ALTER TABLE measurement SPLIT PARTITION measurement_y2006q1 INTO
(PARTITION measurement_y2006m01 FOR VALUES FROM ('2006-01-01') TO ('2006-02-01'),
PARTITION measurement_y2006m02 FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'),
PARTITION measurement_y2006m03 FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'));
</programlisting>
</para>
</sect3>
<sect3 id="ddl-partitioning-declarative-limitations">

View File

@@ -37,12 +37,6 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
ATTACH PARTITION <replaceable class="parameter">partition_name</replaceable> { FOR VALUES <replaceable class="parameter">partition_bound_spec</replaceable> | DEFAULT }
ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
DETACH PARTITION <replaceable class="parameter">partition_name</replaceable> [ CONCURRENTLY | FINALIZE ]
ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
MERGE PARTITIONS (<replaceable class="parameter">partition_name1</replaceable>, <replaceable class="parameter">partition_name2</replaceable> [, ...]) INTO <replaceable class="parameter">partition_name</replaceable>
ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
SPLIT PARTITION <replaceable class="parameter">partition_name</replaceable> INTO
(PARTITION <replaceable class="parameter">partition_name1</replaceable> { FOR VALUES <replaceable class="parameter">partition_bound_spec</replaceable> | DEFAULT },
PARTITION <replaceable class="parameter">partition_name2</replaceable> { FOR VALUES <replaceable class="parameter">partition_bound_spec</replaceable> | DEFAULT } [, ...])
<phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
@@ -1185,239 +1179,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
<varlistentry id="sql-altertable-merge-partitions">
<term><literal>MERGE PARTITIONS (<replaceable class="parameter">partition_name1</replaceable>, <replaceable class="parameter">partition_name2</replaceable> [, ...]) INTO <replaceable class="parameter">partition_name</replaceable></literal></term>
<listitem>
<para>
This form merges several partitions of the target table into a new partition.
Hash-partitioned target table is not supported.
Only simple, non-partitioned partitions can be merged.
The new partition (<replaceable class="parameter">partition_name</replaceable>)
can have the same name as one of the merged partitions
(<literal><replaceable class="parameter">partition_name1</replaceable>,
<replaceable class="parameter">partition_name2</replaceable> [, ...]</literal>).
</para>
<para>
If the <literal>DEFAULT</literal> partition is not in the
list of merged partitions:
<itemizedlist>
<listitem>
<para>
For range-partitioned tables, the ranges of merged partitions
must be adjacent; this applies even if the partitioned table
has no default partition.
The partition bounds of merged partitions are combined to form the new partition bound for
<replaceable class="parameter">partition_name</replaceable>.
</para>
</listitem>
<listitem>
<para>
For list-partitioned tables, the partition bounds of
merged partitions are combined to form the new partition bound for
<replaceable class="parameter">partition_name</replaceable>.
</para>
</listitem>
</itemizedlist>
If the <literal>DEFAULT</literal> partition is in the list of merged partitions:
<itemizedlist>
<listitem>
<para>
The partition <replaceable class="parameter">partition_name</replaceable>
will be the new <literal>DEFAULT</literal> partition of the target table.
</para>
</listitem>
<listitem>
<para>
The partition bound specifications for merged partitions can be arbitrary.
</para>
</listitem>
</itemizedlist>
</para>
<para>
All merged partitions must have the same owner.
The owner of merged partitions will be the owner of the new partition.
It is the user's responsibility to setup <acronym>ACL</acronym> on
the new partition.
</para>
<para>
<command>ALTER TABLE MERGE PARTITION</command> uses the partitioned
table itself as the template to construct the new partition.
The new partition inherits the table access method and persistence type
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
columns, indexes, and triggers are copied from the partitioned table to
the new partition. But extended statistics, security policies, etc,
won't be copied from the partitioned table.
Indexes and identity columns copied from the partitioned table will be
created afterward, once the data has been moved into the new partition.
</para>
<para>
When partitions are merged, any objects depending on this partition,
such as constraints, triggers, extended statistics, etc, will be
dropped.
Eventually, we will drop all the merged partitions
(using <literal>RESTRICT</literal> mode) too; therefore, if any objects
are still dependent on them,
<command>ALTER TABLE MERGE PARTITION</command> would fail.
(see <xref linkend="ddl-depend"/>).
</para>
<para>
Extension dependencies on partition indexes (created via
<link linkend="sql-alterindex"><command>ALTER INDEX ... DEPENDS ON
EXTENSION</command></link>) are preserved during merge operations.
All source partition indexes must have the same extension dependencies;
if they differ, an error is raised. This ensures that extension
dependencies are not silently lost during merge.
</para>
<note>
<para>
Merging partitions acquires an <literal>ACCESS EXCLUSIVE</literal> lock on
the parent table, in addition to the <literal>ACCESS EXCLUSIVE</literal>
locks on the tables being merged and on the default partition (if any).
</para>
</note>
<note>
<para>
<command>ALTER TABLE MERGE PARTITIONS</command> creates a new partition and
moves data from all merging partitions into it, which can take a long time.
So it is not recommended to use the command to merge very big partitions
with small ones.
</para>
</note>
</listitem>
</varlistentry>
<varlistentry id="sql-altertable-split-partition">
<term>
<literal>SPLIT PARTITION <replaceable class="parameter">partition_name</replaceable> INTO (
PARTITION <replaceable class="parameter">partition_name1</replaceable> { FOR VALUES <replaceable class="parameter">partition_bound_spec</replaceable> | DEFAULT },
PARTITION <replaceable class="parameter">partition_name2</replaceable> { FOR VALUES <replaceable class="parameter">partition_bound_spec</replaceable> | DEFAULT }
[, ...])</literal>
</term>
<listitem>
<para>
This form splits a single partition of the target table into new
partitions. Hash-partitioned target tables are not supported.
Only a simple, non-partitioned partition can be split.
If the split partition is the <literal>DEFAULT</literal> partition,
one of the new partitions must be <literal>DEFAULT</literal>.
If the partitioned table does not have a <literal>DEFAULT</literal>
partition, a <literal>DEFAULT</literal> partition can be defined as one
of the new partitions.
</para>
<para>
The bounds of new non-<literal>DEFAULT</literal> partitions must not
overlap with those of new or existing partitions, except
<replaceable class="parameter">partition_name</replaceable>, and must be
contained within the bounds of the split partition
<replaceable class="parameter">partition_name</replaceable>.
If no new <literal>DEFAULT</literal> partition is specified, the
combined bounds of the new partitions
<literal>
<replaceable class="parameter">partition_name1</replaceable>,
<replaceable class="parameter">partition_name2</replaceable>[, ...]
</literal> must exactly match the bounds of the split partition
<replaceable class="parameter">partition_name</replaceable>.
One of the new partitions can have the same name as the split partition
<replaceable class="parameter">partition_name</replaceable>.
This is useful when splitting the <literal>DEFAULT</literal> partition,
so that after the split, the <literal>DEFAULT</literal> partition
keeps the same name but its partition bound changes.
</para>
<para>
New partitions will have the same owner as the parent partition.
It is the user's responsibility to setup <acronym>ACL</acronym> on new
partitions.
</para>
<para>
<command>ALTER TABLE SPLIT PARTITION</command> uses the partitioned
table itself as the template to construct new partitions.
New partitions inherit the table access method and persistence type of
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>
Constraints, column defaults, column generation expressions,
identity columns, indexes, and triggers are copied from the partitioned
table to the new partitions. But extended statistics, security
policies, etc, won't be copied from the partitioned table.
Indexes and identity columns copied from the partitioned table will be
created afterward, once the data has been moved into the new partitions.
</para>
<para>
When a partition is split, any objects that depend on this partition,
such as constraints, triggers, extended statistics, etc, will be dropped.
This occurs because <command>ALTER TABLE SPLIT PARTITION</command> uses
the partitioned table itself as the template to reconstruct these
objects later.
Eventually, we will drop the split partition
(using <literal>RESTRICT</literal> mode) too; therefore, if any objects
are still dependent on it, <command>ALTER TABLE SPLIT PARTITION</command>
would fail (see <xref linkend="ddl-depend"/>).
</para>
<para>
Extension dependencies on partition indexes (created via
<link linkend="sql-alterindex"><command>ALTER INDEX ... DEPENDS ON
EXTENSION</command></link>) are preserved during split operations.
The new partitions' indexes will inherit the extension dependencies
from the source partition's indexes.
</para>
<note>
<para>
Split partition acquires an <literal>ACCESS EXCLUSIVE</literal> lock on
the parent table, in addition to the <literal>ACCESS EXCLUSIVE</literal>
lock on the table being split.
</para>
</note>
<note>
<para>
<command>ALTER TABLE SPLIT PARTITION</command> creates new partitions and
moves data from the split partition into them, which can take a long
time. So it is not recommended to use the command for splitting a
small fraction of rows out of a very big partition.
</para>
</note>
</listitem>
</varlistentry>
</variablelist>
</para>
<para>
All the forms of <command>ALTER TABLE</command> that act on a single table,
except <literal>RENAME</literal>, <literal>SET SCHEMA</literal>,
<literal>ATTACH PARTITION</literal>, <literal>DETACH PARTITION</literal>,
<literal>MERGE PARTITIONS</literal>, and <literal>SPLIT PARTITION</literal>
can be combined into
<literal>ATTACH PARTITION</literal>, and
<literal>DETACH PARTITION</literal> can be combined into
a list of multiple alterations to be applied together. For example, it
is possible to add several columns and/or alter the type of several
columns in a single command. This is particularly useful with large
tables, since only one pass over the table needs to be made.
tables, since only one pass over the table need be made.
</para>
<para>
@@ -1656,19 +1429,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
<term><replaceable class="parameter">partition_name</replaceable></term>
<listitem>
<para>
The name of the table to attach as a new partition or to detach from this table,
or the name of split partition, or the name of the new merged partition.
</para>
</listitem>
</varlistentry>
<varlistentry id="sql-altertable-parms-partition-name1">
<term><replaceable class="parameter">partition_name1</replaceable></term>
<term><replaceable class="parameter">partition_name2</replaceable></term>
<listitem>
<para>
The names of the tables being merged into the new partition or split into
new partitions.
The name of the table to attach as a new partition or to detach from this table.
</para>
</listitem>
</varlistentry>
@@ -2101,31 +1862,6 @@ ALTER TABLE measurement
DETACH PARTITION measurement_y2015m12;
</programlisting></para>
<para>
To split a single partition of the range-partitioned table:
<programlisting>
ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2023 INTO
(PARTITION sales_feb2023 FOR VALUES FROM ('2023-02-01') TO ('2023-03-01'),
PARTITION sales_mar2023 FOR VALUES FROM ('2023-03-01') TO ('2023-04-01'),
PARTITION sales_apr2023 FOR VALUES FROM ('2023-04-01') TO ('2023-05-01'));
</programlisting></para>
<para>
To split a single partition of the list-partitioned table:
<programlisting>
ALTER TABLE sales_list SPLIT PARTITION sales_all INTO
(PARTITION sales_west FOR VALUES IN ('Lisbon', 'New York', 'Madrid'),
PARTITION sales_east FOR VALUES IN ('Beijing', 'Delhi', 'Vladivostok'),
PARTITION sales_central FOR VALUES IN ('Warsaw', 'Berlin', 'Kyiv'));
</programlisting></para>
<para>
To merge several partitions into one partition of the target table:
<programlisting>
ALTER TABLE sales_list MERGE PARTITIONS (sales_west, sales_east, sales_central)
INTO sales_all;
</programlisting></para>
</refsect1>
<refsect1>

View File

@@ -326,63 +326,13 @@ performDeletion(const ObjectAddress *object,
}
/*
* performDeletionCheck: Check whether a specific object can be safely deleted.
* This function does not perform any deletion; instead, it raises an error
* if the object cannot be deleted due to existing dependencies.
*
* It can be useful when you need to delete some objects later. See comments
* in performDeletion too.
* The behavior must be specified as DROP_RESTRICT.
*/
void
performDeletionCheck(const ObjectAddress *object,
DropBehavior behavior, int flags)
{
Relation depRel;
ObjectAddresses *targetObjects;
Assert(behavior == DROP_RESTRICT);
depRel = table_open(DependRelationId, RowExclusiveLock);
AcquireDeletionLock(object, 0);
/*
* Construct a list of objects we want to delete later (ie, the given
* object plus everything directly or indirectly dependent on it).
*/
targetObjects = new_object_addresses();
findDependentObjects(object,
DEPFLAG_ORIGINAL,
flags,
NULL, /* empty stack */
targetObjects,
NULL, /* no pendingObjects */
&depRel);
/*
* Check if deletion is allowed.
*/
reportDependentObjects(targetObjects,
behavior,
flags,
object);
/* And clean up */
free_object_addresses(targetObjects);
table_close(depRel, RowExclusiveLock);
}
/*
* performMultipleDeletions: Similar to performDeletion, but acts on multiple
* performMultipleDeletions: Similar to performDeletion, but act on multiple
* objects at once.
*
* The main difference from issuing multiple performDeletion calls is that the
* list of objects that would be implicitly dropped, for each object to be
* dropped, is the union of the implicit-object list for all objects. This
* makes each check more relaxed.
* makes each check be more relaxed.
*/
void
performMultipleDeletions(const ObjectAddresses *objects,

View File

@@ -895,7 +895,7 @@ RelationGetNotNullConstraints(Oid relid, bool cooked, bool include_noinh)
false)));
constr->is_enforced = true;
constr->skip_validation = !conForm->convalidated;
constr->initially_valid = conForm->convalidated;
constr->initially_valid = true;
constr->is_no_inherit = conForm->connoinherit;
notnulls = lappend(notnulls, constr);
}

File diff suppressed because it is too large Load Diff

View File

@@ -262,7 +262,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionElem *partelem;
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
SinglePartitionSpec *singlepartspec;
RoleSpec *rolespec;
PublicationObjSpec *publicationobjectspec;
PublicationAllObjSpec *publicationallobjectspec;
@@ -654,8 +653,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <partelem> part_elem
%type <list> part_params
%type <partboundspec> PartitionBoundSpec
%type <singlepartspec> SinglePartitionSpec
%type <list> partitions_list
%type <list> hash_partbound
%type <defelt> hash_partbound_elem
@@ -800,7 +797,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
ORDER ORDINALITY OTHERS OUT_P OUTER_P
OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
PARALLEL PARAMETER PARSER PARTIAL PARTITION PARTITIONS PASSING PASSWORD PATH
PARALLEL PARAMETER PARSER PARTIAL PARTITION PASSING PASSWORD PATH
PERIOD PLACING PLAN PLANS POLICY PORTION
POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PROPERTIES PROPERTY PUBLICATION
@@ -815,7 +812,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT
SEQUENCE SEQUENCES
SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SPLIT SOURCE SQL_P STABLE STANDALONE_P
SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SOURCE SQL_P STABLE STANDALONE_P
START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRING_P STRIP_P
SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_USER
@@ -2436,23 +2433,6 @@ alter_table_cmds:
| alter_table_cmds ',' alter_table_cmd { $$ = lappend($1, $3); }
;
partitions_list:
SinglePartitionSpec { $$ = list_make1($1); }
| partitions_list ',' SinglePartitionSpec { $$ = lappend($1, $3); }
;
SinglePartitionSpec:
PARTITION qualified_name PartitionBoundSpec
{
SinglePartitionSpec *n = makeNode(SinglePartitionSpec);
n->name = $2;
n->bound = $3;
$$ = n;
}
;
partition_cmd:
/* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
ATTACH PARTITION qualified_name PartitionBoundSpec
@@ -2463,7 +2443,6 @@ partition_cmd:
n->subtype = AT_AttachPartition;
cmd->name = $3;
cmd->bound = $4;
cmd->partlist = NIL;
cmd->concurrent = false;
n->def = (Node *) cmd;
@@ -2478,7 +2457,6 @@ partition_cmd:
n->subtype = AT_DetachPartition;
cmd->name = $3;
cmd->bound = NULL;
cmd->partlist = NIL;
cmd->concurrent = $4;
n->def = (Node *) cmd;
@@ -2492,35 +2470,6 @@ partition_cmd:
n->subtype = AT_DetachPartitionFinalize;
cmd->name = $3;
cmd->bound = NULL;
cmd->partlist = NIL;
cmd->concurrent = false;
n->def = (Node *) cmd;
$$ = (Node *) n;
}
/* ALTER TABLE <name> SPLIT PARTITION <partition_name> INTO () */
| SPLIT PARTITION qualified_name INTO '(' partitions_list ')'
{
AlterTableCmd *n = makeNode(AlterTableCmd);
PartitionCmd *cmd = makeNode(PartitionCmd);
n->subtype = AT_SplitPartition;
cmd->name = $3;
cmd->bound = NULL;
cmd->partlist = $6;
cmd->concurrent = false;
n->def = (Node *) cmd;
$$ = (Node *) n;
}
/* ALTER TABLE <name> MERGE PARTITIONS () INTO <partition_name> */
| MERGE PARTITIONS '(' qualified_name_list ')' INTO qualified_name
{
AlterTableCmd *n = makeNode(AlterTableCmd);
PartitionCmd *cmd = makeNode(PartitionCmd);
n->subtype = AT_MergePartitions;
cmd->name = $7;
cmd->bound = NULL;
cmd->partlist = $4;
cmd->concurrent = false;
n->def = (Node *) cmd;
$$ = (Node *) n;
@@ -2537,7 +2486,6 @@ index_partition_cmd:
n->subtype = AT_AttachPartition;
cmd->name = $3;
cmd->bound = NULL;
cmd->partlist = NIL;
cmd->concurrent = false;
n->def = (Node *) cmd;
@@ -19020,7 +18968,6 @@ unreserved_keyword:
| PARSER
| PARTIAL
| PARTITION
| PARTITIONS
| PASSING
| PASSWORD
| PATH
@@ -19095,7 +19042,6 @@ unreserved_keyword:
| SKIP
| SNAPSHOT
| SOURCE
| SPLIT
| SQL_P
| STABLE
| STANDALONE_P
@@ -19664,7 +19610,6 @@ bare_label_keyword:
| PARSER
| PARTIAL
| PARTITION
| PARTITIONS
| PASSING
| PASSWORD
| PATH
@@ -19750,7 +19695,6 @@ bare_label_keyword:
| SNAPSHOT
| SOME
| SOURCE
| SPLIT
| SQL_P
| STABLE
| STANDALONE_P

View File

@@ -33,7 +33,6 @@
#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"
@@ -60,8 +59,6 @@
#include "parser/parse_type.h"
#include "parser/parse_utilcmd.h"
#include "parser/parser.h"
#include "partitioning/partbounds.h"
#include "partitioning/partdesc.h"
#include "rewrite/rewriteManip.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -132,7 +129,7 @@ static void checkSchemaNameList(const char *context_schema,
static CreateStmt *transformCreateSchemaCreateTable(ParseState *pstate,
CreateStmt *stmt,
List **fk_elements);
static void transformPartitionCmd(CreateStmtContext *cxt, PartitionBoundSpec *bound);
static void transformPartitionCmd(CreateStmtContext *cxt, PartitionCmd *cmd);
static List *transformPartitionRangeBounds(ParseState *pstate, List *blist,
Relation parent);
static void validateInfiniteBounds(ParseState *pstate, List *blist);
@@ -3532,287 +3529,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
}
/*
* checkPartition
* Check whether partRelOid is a leaf partition of the parent table (rel).
* isMerge: true indicates the operation is "ALTER TABLE ... MERGE PARTITIONS";
* false indicates the operation is "ALTER TABLE ... SPLIT PARTITION".
*/
static void
checkPartition(Relation rel, Oid partRelOid, bool isMerge)
{
Relation partRel;
partRel = table_open(partRelOid, NoLock);
if (partRel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", RelationGetRelationName(partRel)),
isMerge
? errhint("ALTER TABLE ... MERGE PARTITIONS can only merge partitions that don't have sub-partitions.")
: errhint("ALTER TABLE ... SPLIT PARTITION can only split partitions that don't have sub-partitions."));
if (!partRel->rd_rel->relispartition)
ereport(ERROR,
errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a partition of partitioned table \"%s\"",
RelationGetRelationName(partRel), RelationGetRelationName(rel)),
isMerge
? errhint("ALTER TABLE ... MERGE PARTITIONS can only merge partitions that don't have sub-partitions.")
: errhint("ALTER TABLE ... SPLIT PARTITION can only split partitions that don't have sub-partitions."));
if (get_partition_parent(partRelOid, false) != RelationGetRelid(rel))
ereport(ERROR,
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("relation \"%s\" is not a partition of relation \"%s\"",
RelationGetRelationName(partRel), RelationGetRelationName(rel)),
isMerge
? errhint("ALTER TABLE ... MERGE PARTITIONS can only merge partitions that don't have sub-partitions.")
: errhint("ALTER TABLE ... SPLIT PARTITION can only split partitions that don't have sub-partitions."));
table_close(partRel, NoLock);
}
/*
* transformPartitionCmdForSplit -
* analyze the ALTER TABLE ... SPLIT PARTITION command
*
* For each new partition, sps->bound is set to the transformed value of bound.
* Does checks for bounds of new partitions.
*/
static void
transformPartitionCmdForSplit(CreateStmtContext *cxt, PartitionCmd *partcmd)
{
Relation parent = cxt->rel;
PartitionKey key;
char strategy;
Oid splitPartOid;
Oid defaultPartOid;
int default_index = -1;
bool isSplitPartDefault;
ListCell *listptr,
*listptr2;
List *splitlist;
splitlist = partcmd->partlist;
key = RelationGetPartitionKey(parent);
strategy = get_partition_strategy(key);
defaultPartOid = get_default_oid_from_partdesc(RelationGetPartitionDesc(parent, true));
/* Transform partition bounds for all partitions in the list: */
foreach_node(SinglePartitionSpec, sps, splitlist)
{
cxt->partbound = NULL;
transformPartitionCmd(cxt, sps->bound);
/* Assign the transformed value of the partition bound. */
sps->bound = cxt->partbound;
}
/*
* Open and lock the partition, check ownership along the way. We need to
* use AccessExclusiveLock here because this split partition will be
* detached, then dropped in ATExecSplitPartition.
*/
splitPartOid = RangeVarGetRelidExtended(partcmd->name, AccessExclusiveLock,
0, RangeVarCallbackOwnsRelation,
NULL);
checkPartition(parent, splitPartOid, false);
switch (strategy)
{
case PARTITION_STRATEGY_LIST:
case PARTITION_STRATEGY_RANGE:
{
foreach_node(SinglePartitionSpec, sps, splitlist)
{
if (sps->bound->is_default)
{
if (default_index != -1)
ereport(ERROR,
errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot specify more than one DEFAULT partition"),
parser_errposition(cxt->pstate, sps->name->location));
default_index = foreach_current_index(sps);
}
}
}
break;
case PARTITION_STRATEGY_HASH:
ereport(ERROR,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("partition of hash-partitioned table cannot be split"));
break;
default:
elog(ERROR, "unexpected partition strategy: %d",
(int) key->strategy);
break;
}
/* isSplitPartDefault: is the being split partition a DEFAULT partition? */
isSplitPartDefault = (defaultPartOid == splitPartOid);
if (isSplitPartDefault && default_index == -1)
ereport(ERROR,
errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot split DEFAULT partition \"%s\"",
get_rel_name(splitPartOid)),
errhint("To split a DEFAULT partition, one of the new partitions must be DEFAULT."));
/*
* If the partition being split is not the DEFAULT partition, but the
* DEFAULT partition exists, then none of the resulting split partitions
* can be the DEFAULT.
*/
if (!isSplitPartDefault && (default_index != -1) && OidIsValid(defaultPartOid))
{
SinglePartitionSpec *spsDef =
(SinglePartitionSpec *) list_nth(splitlist, default_index);
ereport(ERROR,
errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot split non-DEFAULT partition \"%s\"",
get_rel_name(splitPartOid)),
errdetail("New partition cannot be DEFAULT because DEFAULT partition \"%s\" already exists.",
get_rel_name(defaultPartOid)),
parser_errposition(cxt->pstate, spsDef->name->location));
}
foreach(listptr, splitlist)
{
Oid nspid;
SinglePartitionSpec *sps = (SinglePartitionSpec *) lfirst(listptr);
RangeVar *name = sps->name;
nspid = RangeVarGetCreationNamespace(sps->name);
/* Partitions in the list should have different names. */
for_each_cell(listptr2, splitlist, lnext(splitlist, listptr))
{
Oid nspid2;
SinglePartitionSpec *sps2 = (SinglePartitionSpec *) lfirst(listptr2);
RangeVar *name2 = sps2->name;
if (equal(name, name2))
ereport(ERROR,
errcode(ERRCODE_DUPLICATE_TABLE),
errmsg("partition with name \"%s\" is already used", name->relname),
parser_errposition(cxt->pstate, name2->location));
nspid2 = RangeVarGetCreationNamespace(sps2->name);
if (nspid2 == nspid && strcmp(name->relname, name2->relname) == 0)
ereport(ERROR,
errcode(ERRCODE_DUPLICATE_TABLE),
errmsg("partition with name \"%s\" is already used", name->relname),
parser_errposition(cxt->pstate, name2->location));
}
}
/* Then we should check partitions with transformed bounds. */
check_partitions_for_split(parent, splitPartOid, splitlist, cxt->pstate);
}
/*
* transformPartitionCmdForMerge -
* analyze the ALTER TABLE ... MERGE PARTITIONS command
*
* Does simple checks for merged partitions. Calculates bound of the resulting
* partition.
*/
static void
transformPartitionCmdForMerge(CreateStmtContext *cxt, PartitionCmd *partcmd)
{
Oid defaultPartOid;
Oid partOid;
Relation parent = cxt->rel;
PartitionKey key;
char strategy;
ListCell *listptr,
*listptr2;
bool isDefaultPart = false;
List *partOids = NIL;
key = RelationGetPartitionKey(parent);
strategy = get_partition_strategy(key);
if (strategy == PARTITION_STRATEGY_HASH)
ereport(ERROR,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("partition of hash-partitioned table cannot be merged"));
/* Does the partitioned table (parent) have a default partition? */
defaultPartOid = get_default_oid_from_partdesc(RelationGetPartitionDesc(parent, true));
foreach(listptr, partcmd->partlist)
{
RangeVar *name = (RangeVar *) lfirst(listptr);
/* Partitions in the list should have different names. */
for_each_cell(listptr2, partcmd->partlist, lnext(partcmd->partlist, listptr))
{
RangeVar *name2 = (RangeVar *) lfirst(listptr2);
if (equal(name, name2))
ereport(ERROR,
errcode(ERRCODE_DUPLICATE_TABLE),
errmsg("partition with name \"%s\" is already used", name->relname),
parser_errposition(cxt->pstate, name2->location));
}
/*
* Search the DEFAULT partition in the list. Open and lock partitions
* before calculating the boundary for resulting partition, we also
* check for ownership along the way. We need to use
* AccessExclusiveLock here, because these merged partitions will be
* detached and then dropped in ATExecMergePartitions.
*/
partOid = RangeVarGetRelidExtended(name, AccessExclusiveLock, 0,
RangeVarCallbackOwnsRelation,
NULL);
/* Is the current partition a DEFAULT partition? */
if (partOid == defaultPartOid)
isDefaultPart = true;
/*
* Extended check because the same partition can have different names
* (for example, "part_name" and "public.part_name").
*/
foreach(listptr2, partOids)
{
Oid curOid = lfirst_oid(listptr2);
if (curOid == partOid)
ereport(ERROR,
errcode(ERRCODE_DUPLICATE_TABLE),
errmsg("partition with name \"%s\" is already used", name->relname),
parser_errposition(cxt->pstate, name->location));
}
checkPartition(parent, partOid, true);
partOids = lappend_oid(partOids, partOid);
}
/* Allocate the bound of the resulting partition. */
Assert(partcmd->bound == NULL);
partcmd->bound = makeNode(PartitionBoundSpec);
/* Fill the partition bound. */
partcmd->bound->strategy = strategy;
partcmd->bound->location = -1;
partcmd->bound->is_default = isDefaultPart;
if (!isDefaultPart)
calculate_partition_bound_for_merge(parent, partcmd->partlist,
partOids, partcmd->bound,
cxt->pstate);
}
/*
* transformAlterTableStmt -
* parse analysis for ALTER TABLE
@@ -4082,48 +3798,20 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
{
PartitionCmd *partcmd = (PartitionCmd *) cmd->def;
transformPartitionCmd(&cxt, partcmd->bound);
/* assign the transformed value of the partition bound */
transformPartitionCmd(&cxt, partcmd);
/* assign transformed value of the partition bound */
partcmd->bound = cxt.partbound;
}
newcmds = lappend(newcmds, cmd);
break;
case AT_MergePartitions:
{
PartitionCmd *partcmd = (PartitionCmd *) cmd->def;
if (list_length(partcmd->partlist) < 2)
ereport(ERROR,
errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("list of partitions to be merged should include at least two partitions"));
transformPartitionCmdForMerge(&cxt, partcmd);
newcmds = lappend(newcmds, cmd);
break;
}
case AT_SplitPartition:
{
PartitionCmd *partcmd = (PartitionCmd *) cmd->def;
if (list_length(partcmd->partlist) < 2)
ereport(ERROR,
errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("list of new partitions must contain at least two partitions"));
transformPartitionCmdForSplit(&cxt, partcmd);
newcmds = lappend(newcmds, cmd);
break;
}
default:
/*
* Currently, we shouldn't actually get here for the
* subcommand types that don't require transformation; but if
* we do, just emit them unchanged.
* Currently, we shouldn't actually get here for subcommand
* types that don't require transformation; but if we do, just
* emit them unchanged.
*/
newcmds = lappend(newcmds, cmd);
break;
@@ -4791,13 +4479,13 @@ transformCreateSchemaCreateTable(ParseState *pstate,
/*
* transformPartitionCmd
* Analyze the ATTACH/DETACH/SPLIT PARTITION command
* Analyze the ATTACH/DETACH PARTITION command
*
* In case of the ATTACH/SPLIT PARTITION command, cxt->partbound is set to the
* transformed value of bound.
* In case of the ATTACH PARTITION command, cxt->partbound is set to the
* transformed value of cmd->bound.
*/
static void
transformPartitionCmd(CreateStmtContext *cxt, PartitionBoundSpec *bound)
transformPartitionCmd(CreateStmtContext *cxt, PartitionCmd *cmd)
{
Relation parentRel = cxt->rel;
@@ -4806,9 +4494,9 @@ transformPartitionCmd(CreateStmtContext *cxt, PartitionBoundSpec *bound)
case RELKIND_PARTITIONED_TABLE:
/* transform the partition bound, if any */
Assert(RelationGetPartitionKey(parentRel) != NULL);
if (bound != NULL)
if (cmd->bound != NULL)
cxt->partbound = transformPartitionBound(cxt->pstate, parentRel,
bound);
cmd->bound);
break;
case RELKIND_PARTITIONED_INDEX:
@@ -4816,7 +4504,7 @@ transformPartitionCmd(CreateStmtContext *cxt, PartitionBoundSpec *bound)
* A partitioned index cannot have a partition bound set. ALTER
* INDEX prevents that with its grammar, but not ALTER TABLE.
*/
if (bound != NULL)
if (cmd->bound != NULL)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("\"%s\" is not a partitioned table",

File diff suppressed because it is too large Load Diff

View File

@@ -2835,7 +2835,6 @@ match_previous_words(int pattern_id,
"OWNER TO", "SET", "VALIDATE CONSTRAINT",
"REPLICA IDENTITY", "ATTACH PARTITION",
"DETACH PARTITION", "FORCE ROW LEVEL SECURITY",
"SPLIT PARTITION", "MERGE PARTITIONS (",
"OF", "NOT OF");
/* ALTER TABLE xxx ADD */
else if (Matches("ALTER", "TABLE", MatchAny, "ADD"))
@@ -3098,10 +3097,10 @@ match_previous_words(int pattern_id,
COMPLETE_WITH("FROM (", "IN (", "WITH (");
/*
* If we have ALTER TABLE <foo> DETACH|SPLIT PARTITION, provide a list of
* If we have ALTER TABLE <foo> DETACH PARTITION, provide a list of
* partitions of <foo>.
*/
else if (Matches("ALTER", "TABLE", MatchAny, "DETACH|SPLIT", "PARTITION"))
else if (Matches("ALTER", "TABLE", MatchAny, "DETACH", "PARTITION"))
{
set_completion_reference(prev3_wd);
COMPLETE_WITH_SCHEMA_QUERY(Query_for_partition_of_table);
@@ -3109,19 +3108,6 @@ match_previous_words(int pattern_id,
else if (Matches("ALTER", "TABLE", MatchAny, "DETACH", "PARTITION", MatchAny))
COMPLETE_WITH("CONCURRENTLY", "FINALIZE");
/* ALTER TABLE <name> SPLIT PARTITION <name> */
else if (Matches("ALTER", "TABLE", MatchAny, "SPLIT", "PARTITION", MatchAny))
COMPLETE_WITH("INTO ( PARTITION");
/* ALTER TABLE <name> MERGE PARTITIONS ( */
else if (Matches("ALTER", "TABLE", MatchAny, "MERGE", "PARTITIONS", "("))
{
set_completion_reference(prev4_wd);
COMPLETE_WITH_SCHEMA_QUERY(Query_for_partition_of_table);
}
else if (Matches("ALTER", "TABLE", MatchAny, "MERGE", "PARTITIONS", "(*)"))
COMPLETE_WITH("INTO");
/* ALTER TABLE <name> OF */
else if (Matches("ALTER", "TABLE", MatchAny, "OF"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_composite_datatypes);

View File

@@ -107,8 +107,6 @@ extern void ReleaseDeletionLock(const ObjectAddress *object);
extern void performDeletion(const ObjectAddress *object,
DropBehavior behavior, int flags);
extern void performDeletionCheck(const ObjectAddress *object,
DropBehavior behavior, int flags);
extern void performMultipleDeletions(const ObjectAddresses *objects,
DropBehavior behavior, int flags);

View File

@@ -984,39 +984,13 @@ typedef struct PartitionRangeDatum
} PartitionRangeDatum;
/*
* PartitionDesc - info about a single partition for the ALTER TABLE SPLIT
* PARTITION command
*/
typedef struct SinglePartitionSpec
{
NodeTag type;
RangeVar *name; /* name of partition */
PartitionBoundSpec *bound; /* FOR VALUES, if attaching */
} SinglePartitionSpec;
/*
* PartitionCmd - info for ALTER TABLE/INDEX ATTACH/DETACH PARTITION and for
* ALTER TABLE SPLIT/MERGE PARTITION(S) commands
* PartitionCmd - info for ALTER TABLE/INDEX ATTACH/DETACH PARTITION commands
*/
typedef struct PartitionCmd
{
NodeTag type;
/* name of partition to attach/detach/merge/split */
RangeVar *name;
/* FOR VALUES, if attaching */
PartitionBoundSpec *bound;
/*
* list of partitions to be split/merged, used in ALTER TABLE MERGE
* PARTITIONS and ALTER TABLE SPLIT PARTITIONS. For merge partitions,
* partlist is a list of RangeVar; For split partition, it is a list of
* SinglePartitionSpec.
*/
List *partlist;
RangeVar *name; /* name of partition to attach/detach */
PartitionBoundSpec *bound; /* FOR VALUES, if attaching */
bool concurrent;
} PartitionCmd;
@@ -2578,8 +2552,6 @@ typedef enum AlterTableType
AT_AttachPartition, /* ATTACH PARTITION */
AT_DetachPartition, /* DETACH PARTITION */
AT_DetachPartitionFinalize, /* DETACH PARTITION FINALIZE */
AT_SplitPartition, /* SPLIT PARTITION */
AT_MergePartitions, /* MERGE PARTITIONS */
AT_AddIdentity, /* ADD IDENTITY */
AT_SetIdentity, /* SET identity column options */
AT_DropIdentity, /* DROP IDENTITY */

View File

@@ -344,7 +344,6 @@ PG_KEYWORD("parameter", PARAMETER, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("partitions", PARTITIONS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("path", PATH, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -434,7 +433,6 @@ PG_KEYWORD("smallint", SMALLINT, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("snapshot", SNAPSHOT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("some", SOME, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("source", SOURCE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("split", SPLIT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("sql", SQL_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("stable", STABLE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("standalone", STANDALONE_P, UNRESERVED_KEYWORD, BARE_LABEL)

View File

@@ -143,14 +143,4 @@ extern int partition_range_datum_bsearch(FmgrInfo *partsupfunc,
extern int partition_hash_bsearch(PartitionBoundInfo boundinfo,
int modulus, int remainder);
extern void check_partitions_for_split(Relation parent,
Oid splitPartOid,
List *partlist,
ParseState *pstate);
extern void calculate_partition_bound_for_merge(Relation parent,
List *partNames,
List *partOids,
PartitionBoundSpec *spec,
ParseState *pstate);
#endif /* PARTBOUNDS_H */

View File

@@ -1,243 +0,0 @@
Parsed test spec with 2 sessions
starting permutation: s2b s2i s2c s1b s1merg s2b s2u s1c s2c s2s
step s2b: BEGIN;
step s2i: INSERT INTO tpart VALUES (1, 'text01');
step s2c: COMMIT;
step s1b: BEGIN;
step s1merg: ALTER TABLE tpart MERGE PARTITIONS (tpart_00_10, tpart_10_20) INTO tpart_00_20;
step s2b: BEGIN;
step s2u: UPDATE tpart SET t = 'text01modif' where i = 1; <waiting ...>
step s1c: COMMIT;
step s2u: <... completed>
step s2c: COMMIT;
step s2s: SELECT tableoid::regclass, * FROM tpart ORDER BY tableoid::regclass::text COLLATE "C", i;
tableoid | i|t
-------------+--+-----------
tpart_00_20 | 1|text01modif
tpart_00_20 | 5|text05
tpart_00_20 |15|text15
tpart_20_30 |25|text25
tpart_default|35|text35
(5 rows)
starting permutation: s2b s2i s2c s1brr s1merg s2b s2u s1c s2c s2s
step s2b: BEGIN;
step s2i: INSERT INTO tpart VALUES (1, 'text01');
step s2c: COMMIT;
step s1brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
step s1merg: ALTER TABLE tpart MERGE PARTITIONS (tpart_00_10, tpart_10_20) INTO tpart_00_20;
step s2b: BEGIN;
step s2u: UPDATE tpart SET t = 'text01modif' where i = 1; <waiting ...>
step s1c: COMMIT;
step s2u: <... completed>
step s2c: COMMIT;
step s2s: SELECT tableoid::regclass, * FROM tpart ORDER BY tableoid::regclass::text COLLATE "C", i;
tableoid | i|t
-------------+--+-----------
tpart_00_20 | 1|text01modif
tpart_00_20 | 5|text05
tpart_00_20 |15|text15
tpart_20_30 |25|text25
tpart_default|35|text35
(5 rows)
starting permutation: s2b s2i s2c s1bs s1merg s2b s2u s1c s2c s2s
step s2b: BEGIN;
step s2i: INSERT INTO tpart VALUES (1, 'text01');
step s2c: COMMIT;
step s1bs: BEGIN ISOLATION LEVEL SERIALIZABLE;
step s1merg: ALTER TABLE tpart MERGE PARTITIONS (tpart_00_10, tpart_10_20) INTO tpart_00_20;
step s2b: BEGIN;
step s2u: UPDATE tpart SET t = 'text01modif' where i = 1; <waiting ...>
step s1c: COMMIT;
step s2u: <... completed>
step s2c: COMMIT;
step s2s: SELECT tableoid::regclass, * FROM tpart ORDER BY tableoid::regclass::text COLLATE "C", i;
tableoid | i|t
-------------+--+-----------
tpart_00_20 | 1|text01modif
tpart_00_20 | 5|text05
tpart_00_20 |15|text15
tpart_20_30 |25|text25
tpart_default|35|text35
(5 rows)
starting permutation: s2brr s2i s2c s1b s1merg s2b s2u s1c s2c s2s
step s2brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
step s2i: INSERT INTO tpart VALUES (1, 'text01');
step s2c: COMMIT;
step s1b: BEGIN;
step s1merg: ALTER TABLE tpart MERGE PARTITIONS (tpart_00_10, tpart_10_20) INTO tpart_00_20;
step s2b: BEGIN;
step s2u: UPDATE tpart SET t = 'text01modif' where i = 1; <waiting ...>
step s1c: COMMIT;
step s2u: <... completed>
step s2c: COMMIT;
step s2s: SELECT tableoid::regclass, * FROM tpart ORDER BY tableoid::regclass::text COLLATE "C", i;
tableoid | i|t
-------------+--+-----------
tpart_00_20 | 1|text01modif
tpart_00_20 | 5|text05
tpart_00_20 |15|text15
tpart_20_30 |25|text25
tpart_default|35|text35
(5 rows)
starting permutation: s2brr s2i s2c s1brr s1merg s2b s2u s1c s2c s2s
step s2brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
step s2i: INSERT INTO tpart VALUES (1, 'text01');
step s2c: COMMIT;
step s1brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
step s1merg: ALTER TABLE tpart MERGE PARTITIONS (tpart_00_10, tpart_10_20) INTO tpart_00_20;
step s2b: BEGIN;
step s2u: UPDATE tpart SET t = 'text01modif' where i = 1; <waiting ...>
step s1c: COMMIT;
step s2u: <... completed>
step s2c: COMMIT;
step s2s: SELECT tableoid::regclass, * FROM tpart ORDER BY tableoid::regclass::text COLLATE "C", i;
tableoid | i|t
-------------+--+-----------
tpart_00_20 | 1|text01modif
tpart_00_20 | 5|text05
tpart_00_20 |15|text15
tpart_20_30 |25|text25
tpart_default|35|text35
(5 rows)
starting permutation: s2brr s2i s2c s1bs s1merg s2b s2u s1c s2c s2s
step s2brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
step s2i: INSERT INTO tpart VALUES (1, 'text01');
step s2c: COMMIT;
step s1bs: BEGIN ISOLATION LEVEL SERIALIZABLE;
step s1merg: ALTER TABLE tpart MERGE PARTITIONS (tpart_00_10, tpart_10_20) INTO tpart_00_20;
step s2b: BEGIN;
step s2u: UPDATE tpart SET t = 'text01modif' where i = 1; <waiting ...>
step s1c: COMMIT;
step s2u: <... completed>
step s2c: COMMIT;
step s2s: SELECT tableoid::regclass, * FROM tpart ORDER BY tableoid::regclass::text COLLATE "C", i;
tableoid | i|t
-------------+--+-----------
tpart_00_20 | 1|text01modif
tpart_00_20 | 5|text05
tpart_00_20 |15|text15
tpart_20_30 |25|text25
tpart_default|35|text35
(5 rows)
starting permutation: s2bs s2i s2c s1b s1merg s2b s2u s1c s2c s2s
step s2bs: BEGIN ISOLATION LEVEL SERIALIZABLE;
step s2i: INSERT INTO tpart VALUES (1, 'text01');
step s2c: COMMIT;
step s1b: BEGIN;
step s1merg: ALTER TABLE tpart MERGE PARTITIONS (tpart_00_10, tpart_10_20) INTO tpart_00_20;
step s2b: BEGIN;
step s2u: UPDATE tpart SET t = 'text01modif' where i = 1; <waiting ...>
step s1c: COMMIT;
step s2u: <... completed>
step s2c: COMMIT;
step s2s: SELECT tableoid::regclass, * FROM tpart ORDER BY tableoid::regclass::text COLLATE "C", i;
tableoid | i|t
-------------+--+-----------
tpart_00_20 | 1|text01modif
tpart_00_20 | 5|text05
tpart_00_20 |15|text15
tpart_20_30 |25|text25
tpart_default|35|text35
(5 rows)
starting permutation: s2bs s2i s2c s1brr s1merg s2b s2u s1c s2c s2s
step s2bs: BEGIN ISOLATION LEVEL SERIALIZABLE;
step s2i: INSERT INTO tpart VALUES (1, 'text01');
step s2c: COMMIT;
step s1brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
step s1merg: ALTER TABLE tpart MERGE PARTITIONS (tpart_00_10, tpart_10_20) INTO tpart_00_20;
step s2b: BEGIN;
step s2u: UPDATE tpart SET t = 'text01modif' where i = 1; <waiting ...>
step s1c: COMMIT;
step s2u: <... completed>
step s2c: COMMIT;
step s2s: SELECT tableoid::regclass, * FROM tpart ORDER BY tableoid::regclass::text COLLATE "C", i;
tableoid | i|t
-------------+--+-----------
tpart_00_20 | 1|text01modif
tpart_00_20 | 5|text05
tpart_00_20 |15|text15
tpart_20_30 |25|text25
tpart_default|35|text35
(5 rows)
starting permutation: s2bs s2i s2c s1bs s1merg s2b s2u s1c s2c s2s
step s2bs: BEGIN ISOLATION LEVEL SERIALIZABLE;
step s2i: INSERT INTO tpart VALUES (1, 'text01');
step s2c: COMMIT;
step s1bs: BEGIN ISOLATION LEVEL SERIALIZABLE;
step s1merg: ALTER TABLE tpart MERGE PARTITIONS (tpart_00_10, tpart_10_20) INTO tpart_00_20;
step s2b: BEGIN;
step s2u: UPDATE tpart SET t = 'text01modif' where i = 1; <waiting ...>
step s1c: COMMIT;
step s2u: <... completed>
step s2c: COMMIT;
step s2s: SELECT tableoid::regclass, * FROM tpart ORDER BY tableoid::regclass::text COLLATE "C", i;
tableoid | i|t
-------------+--+-----------
tpart_00_20 | 1|text01modif
tpart_00_20 | 5|text05
tpart_00_20 |15|text15
tpart_20_30 |25|text25
tpart_default|35|text35
(5 rows)
starting permutation: s2b s2i s2c s1b s1merg s2b s2u2 s1c s2c s2s
step s2b: BEGIN;
step s2i: INSERT INTO tpart VALUES (1, 'text01');
step s2c: COMMIT;
step s1b: BEGIN;
step s1merg: ALTER TABLE tpart MERGE PARTITIONS (tpart_00_10, tpart_10_20) INTO tpart_00_20;
step s2b: BEGIN;
step s2u2: UPDATE tpart SET i = 21 where i = 1; <waiting ...>
step s1c: COMMIT;
step s2u2: <... completed>
step s2c: COMMIT;
step s2s: SELECT tableoid::regclass, * FROM tpart ORDER BY tableoid::regclass::text COLLATE "C", i;
tableoid | i|t
-------------+--+------
tpart_00_20 | 5|text05
tpart_00_20 |15|text15
tpart_20_30 |21|text01
tpart_20_30 |25|text25
tpart_default|35|text35
(5 rows)
starting permutation: s2b s2i s2c s1b s1merg s2b s2u3 s1c s2c s2s
step s2b: BEGIN;
step s2i: INSERT INTO tpart VALUES (1, 'text01');
step s2c: COMMIT;
step s1b: BEGIN;
step s1merg: ALTER TABLE tpart MERGE PARTITIONS (tpart_00_10, tpart_10_20) INTO tpart_00_20;
step s2b: BEGIN;
step s2u3: UPDATE tpart SET i = 11 where i = 1; <waiting ...>
step s1c: COMMIT;
step s2u3: <... completed>
step s2c: COMMIT;
step s2s: SELECT tableoid::regclass, * FROM tpart ORDER BY tableoid::regclass::text COLLATE "C", i;
tableoid | i|t
-------------+--+------
tpart_00_20 | 5|text05
tpart_00_20 |11|text01
tpart_00_20 |15|text15
tpart_20_30 |25|text25
tpart_default|35|text35
(5 rows)

View File

@@ -1,230 +0,0 @@
Parsed test spec with 2 sessions
starting permutation: s1b s1splt s2b s2i s1c s2c s2s
step s1b: BEGIN;
step s1splt: ALTER TABLE tpart SPLIT PARTITION tpart_10_20 INTO
(PARTITION tpart_10_15 FOR VALUES FROM (10) TO (15),
PARTITION tpart_15_20 FOR VALUES FROM (15) TO (20));
step s2b: BEGIN;
step s2i: INSERT INTO tpart VALUES (1, 'text01'); <waiting ...>
step s1c: COMMIT;
step s2i: <... completed>
step s2c: COMMIT;
step s2s: SELECT tableoid::regclass, * FROM tpart ORDER BY tableoid::regclass::text COLLATE "C", i;
tableoid | i|t
-------------+--+------
tpart_00_10 | 1|text01
tpart_00_10 | 5|text05
tpart_15_20 |15|text15
tpart_20_30 |25|text25
tpart_default|35|text35
(5 rows)
starting permutation: s1b s1splt s2brr s2i s1c s2c s2s
step s1b: BEGIN;
step s1splt: ALTER TABLE tpart SPLIT PARTITION tpart_10_20 INTO
(PARTITION tpart_10_15 FOR VALUES FROM (10) TO (15),
PARTITION tpart_15_20 FOR VALUES FROM (15) TO (20));
step s2brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
step s2i: INSERT INTO tpart VALUES (1, 'text01'); <waiting ...>
step s1c: COMMIT;
step s2i: <... completed>
step s2c: COMMIT;
step s2s: SELECT tableoid::regclass, * FROM tpart ORDER BY tableoid::regclass::text COLLATE "C", i;
tableoid | i|t
-------------+--+------
tpart_00_10 | 1|text01
tpart_00_10 | 5|text05
tpart_15_20 |15|text15
tpart_20_30 |25|text25
tpart_default|35|text35
(5 rows)
starting permutation: s1b s1splt s2bs s2i s1c s2c s2s
step s1b: BEGIN;
step s1splt: ALTER TABLE tpart SPLIT PARTITION tpart_10_20 INTO
(PARTITION tpart_10_15 FOR VALUES FROM (10) TO (15),
PARTITION tpart_15_20 FOR VALUES FROM (15) TO (20));
step s2bs: BEGIN ISOLATION LEVEL SERIALIZABLE;
step s2i: INSERT INTO tpart VALUES (1, 'text01'); <waiting ...>
step s1c: COMMIT;
step s2i: <... completed>
step s2c: COMMIT;
step s2s: SELECT tableoid::regclass, * FROM tpart ORDER BY tableoid::regclass::text COLLATE "C", i;
tableoid | i|t
-------------+--+------
tpart_00_10 | 1|text01
tpart_00_10 | 5|text05
tpart_15_20 |15|text15
tpart_20_30 |25|text25
tpart_default|35|text35
(5 rows)
starting permutation: s1brr s1splt s2b s2i s1c s2c s2s
step s1brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
step s1splt: ALTER TABLE tpart SPLIT PARTITION tpart_10_20 INTO
(PARTITION tpart_10_15 FOR VALUES FROM (10) TO (15),
PARTITION tpart_15_20 FOR VALUES FROM (15) TO (20));
step s2b: BEGIN;
step s2i: INSERT INTO tpart VALUES (1, 'text01'); <waiting ...>
step s1c: COMMIT;
step s2i: <... completed>
step s2c: COMMIT;
step s2s: SELECT tableoid::regclass, * FROM tpart ORDER BY tableoid::regclass::text COLLATE "C", i;
tableoid | i|t
-------------+--+------
tpart_00_10 | 1|text01
tpart_00_10 | 5|text05
tpart_15_20 |15|text15
tpart_20_30 |25|text25
tpart_default|35|text35
(5 rows)
starting permutation: s1brr s1splt s2brr s2i s1c s2c s2s
step s1brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
step s1splt: ALTER TABLE tpart SPLIT PARTITION tpart_10_20 INTO
(PARTITION tpart_10_15 FOR VALUES FROM (10) TO (15),
PARTITION tpart_15_20 FOR VALUES FROM (15) TO (20));
step s2brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
step s2i: INSERT INTO tpart VALUES (1, 'text01'); <waiting ...>
step s1c: COMMIT;
step s2i: <... completed>
step s2c: COMMIT;
step s2s: SELECT tableoid::regclass, * FROM tpart ORDER BY tableoid::regclass::text COLLATE "C", i;
tableoid | i|t
-------------+--+------
tpart_00_10 | 1|text01
tpart_00_10 | 5|text05
tpart_15_20 |15|text15
tpart_20_30 |25|text25
tpart_default|35|text35
(5 rows)
starting permutation: s1brr s1splt s2bs s2i s1c s2c s2s
step s1brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
step s1splt: ALTER TABLE tpart SPLIT PARTITION tpart_10_20 INTO
(PARTITION tpart_10_15 FOR VALUES FROM (10) TO (15),
PARTITION tpart_15_20 FOR VALUES FROM (15) TO (20));
step s2bs: BEGIN ISOLATION LEVEL SERIALIZABLE;
step s2i: INSERT INTO tpart VALUES (1, 'text01'); <waiting ...>
step s1c: COMMIT;
step s2i: <... completed>
step s2c: COMMIT;
step s2s: SELECT tableoid::regclass, * FROM tpart ORDER BY tableoid::regclass::text COLLATE "C", i;
tableoid | i|t
-------------+--+------
tpart_00_10 | 1|text01
tpart_00_10 | 5|text05
tpart_15_20 |15|text15
tpart_20_30 |25|text25
tpart_default|35|text35
(5 rows)
starting permutation: s1bs s1splt s2b s2i s1c s2c s2s
step s1bs: BEGIN ISOLATION LEVEL SERIALIZABLE;
step s1splt: ALTER TABLE tpart SPLIT PARTITION tpart_10_20 INTO
(PARTITION tpart_10_15 FOR VALUES FROM (10) TO (15),
PARTITION tpart_15_20 FOR VALUES FROM (15) TO (20));
step s2b: BEGIN;
step s2i: INSERT INTO tpart VALUES (1, 'text01'); <waiting ...>
step s1c: COMMIT;
step s2i: <... completed>
step s2c: COMMIT;
step s2s: SELECT tableoid::regclass, * FROM tpart ORDER BY tableoid::regclass::text COLLATE "C", i;
tableoid | i|t
-------------+--+------
tpart_00_10 | 1|text01
tpart_00_10 | 5|text05
tpart_15_20 |15|text15
tpart_20_30 |25|text25
tpart_default|35|text35
(5 rows)
starting permutation: s1bs s1splt s2brr s2i s1c s2c s2s
step s1bs: BEGIN ISOLATION LEVEL SERIALIZABLE;
step s1splt: ALTER TABLE tpart SPLIT PARTITION tpart_10_20 INTO
(PARTITION tpart_10_15 FOR VALUES FROM (10) TO (15),
PARTITION tpart_15_20 FOR VALUES FROM (15) TO (20));
step s2brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
step s2i: INSERT INTO tpart VALUES (1, 'text01'); <waiting ...>
step s1c: COMMIT;
step s2i: <... completed>
step s2c: COMMIT;
step s2s: SELECT tableoid::regclass, * FROM tpart ORDER BY tableoid::regclass::text COLLATE "C", i;
tableoid | i|t
-------------+--+------
tpart_00_10 | 1|text01
tpart_00_10 | 5|text05
tpart_15_20 |15|text15
tpart_20_30 |25|text25
tpart_default|35|text35
(5 rows)
starting permutation: s1bs s1splt s2bs s2i s1c s2c s2s
step s1bs: BEGIN ISOLATION LEVEL SERIALIZABLE;
step s1splt: ALTER TABLE tpart SPLIT PARTITION tpart_10_20 INTO
(PARTITION tpart_10_15 FOR VALUES FROM (10) TO (15),
PARTITION tpart_15_20 FOR VALUES FROM (15) TO (20));
step s2bs: BEGIN ISOLATION LEVEL SERIALIZABLE;
step s2i: INSERT INTO tpart VALUES (1, 'text01'); <waiting ...>
step s1c: COMMIT;
step s2i: <... completed>
step s2c: COMMIT;
step s2s: SELECT tableoid::regclass, * FROM tpart ORDER BY tableoid::regclass::text COLLATE "C", i;
tableoid | i|t
-------------+--+------
tpart_00_10 | 1|text01
tpart_00_10 | 5|text05
tpart_15_20 |15|text15
tpart_20_30 |25|text25
tpart_default|35|text35
(5 rows)
starting permutation: s1b s1splt s2b s2u s1c s2c s2s
step s1b: BEGIN;
step s1splt: ALTER TABLE tpart SPLIT PARTITION tpart_10_20 INTO
(PARTITION tpart_10_15 FOR VALUES FROM (10) TO (15),
PARTITION tpart_15_20 FOR VALUES FROM (15) TO (20));
step s2b: BEGIN;
step s2u: UPDATE tpart SET i = 16 where i = 5; <waiting ...>
step s1c: COMMIT;
step s2u: <... completed>
step s2c: COMMIT;
step s2s: SELECT tableoid::regclass, * FROM tpart ORDER BY tableoid::regclass::text COLLATE "C", i;
tableoid | i|t
-------------+--+------
tpart_15_20 |15|text15
tpart_15_20 |16|text05
tpart_20_30 |25|text25
tpart_default|35|text35
(4 rows)
starting permutation: s1b s1splt s2b s2u2 s1c s2c s2s
step s1b: BEGIN;
step s1splt: ALTER TABLE tpart SPLIT PARTITION tpart_10_20 INTO
(PARTITION tpart_10_15 FOR VALUES FROM (10) TO (15),
PARTITION tpart_15_20 FOR VALUES FROM (15) TO (20));
step s2b: BEGIN;
step s2u2: UPDATE tpart SET i = 11 where i = 15; <waiting ...>
step s1c: COMMIT;
step s2u2: <... completed>
step s2c: COMMIT;
step s2s: SELECT tableoid::regclass, * FROM tpart ORDER BY tableoid::regclass::text COLLATE "C", i;
tableoid | i|t
-------------+--+------
tpart_00_10 | 5|text05
tpart_10_15 |11|text15
tpart_20_30 |25|text25
tpart_default|35|text35
(4 rows)

View File

@@ -116,8 +116,6 @@ test: partition-key-update-1
test: partition-key-update-2
test: partition-key-update-3
test: partition-key-update-4
test: partition-merge
test: partition-split
test: plpgsql-toast
test: cluster-conflict
test: cluster-conflict-partition

View File

@@ -1,62 +0,0 @@
# Verify that MERGE operation locks DML operations with partitioned table
setup
{
DROP TABLE IF EXISTS tpart;
CREATE TABLE tpart(i int, t text) partition by range(i);
CREATE TABLE tpart_00_10 PARTITION OF tpart FOR VALUES FROM (0) TO (10);
CREATE TABLE tpart_10_20 PARTITION OF tpart FOR VALUES FROM (10) TO (20);
CREATE TABLE tpart_20_30 PARTITION OF tpart FOR VALUES FROM (20) TO (30);
CREATE TABLE tpart_default PARTITION OF tpart DEFAULT;
INSERT INTO tpart VALUES (5, 'text05');
INSERT INTO tpart VALUES (15, 'text15');
INSERT INTO tpart VALUES (25, 'text25');
INSERT INTO tpart VALUES (35, 'text35');
}
teardown
{
DROP TABLE tpart;
}
session s1
step s1b { BEGIN; }
step s1brr { BEGIN ISOLATION LEVEL REPEATABLE READ; }
step s1bs { BEGIN ISOLATION LEVEL SERIALIZABLE; }
step s1merg { ALTER TABLE tpart MERGE PARTITIONS (tpart_00_10, tpart_10_20) INTO tpart_00_20; }
step s1c { COMMIT; }
session s2
step s2b { BEGIN; }
step s2brr { BEGIN ISOLATION LEVEL REPEATABLE READ; }
step s2bs { BEGIN ISOLATION LEVEL SERIALIZABLE; }
step s2i { INSERT INTO tpart VALUES (1, 'text01'); }
step s2u { UPDATE tpart SET t = 'text01modif' where i = 1; }
step s2u2 { UPDATE tpart SET i = 21 where i = 1; }
step s2u3 { UPDATE tpart SET i = 11 where i = 1; }
step s2c { COMMIT; }
step s2s { SELECT tableoid::regclass, * FROM tpart ORDER BY tableoid::regclass::text COLLATE "C", i; }
# s2 inserts row into table. s1 starts MERGE PARTITIONS then
# s2 is trying to update inserted row and waits until s1 finishes
# MERGE operation.
permutation s2b s2i s2c s1b s1merg s2b s2u s1c s2c s2s
permutation s2b s2i s2c s1brr s1merg s2b s2u s1c s2c s2s
permutation s2b s2i s2c s1bs s1merg s2b s2u s1c s2c s2s
permutation s2brr s2i s2c s1b s1merg s2b s2u s1c s2c s2s
permutation s2brr s2i s2c s1brr s1merg s2b s2u s1c s2c s2s
permutation s2brr s2i s2c s1bs s1merg s2b s2u s1c s2c s2s
permutation s2bs s2i s2c s1b s1merg s2b s2u s1c s2c s2s
permutation s2bs s2i s2c s1brr s1merg s2b s2u s1c s2c s2s
permutation s2bs s2i s2c s1bs s1merg s2b s2u s1c s2c s2s
# Tuple routing between partitions.
permutation s2b s2i s2c s1b s1merg s2b s2u2 s1c s2c s2s
# Tuple routing between merging partitions.
permutation s2b s2i s2c s1b s1merg s2b s2u3 s1c s2c s2s

View File

@@ -1,62 +0,0 @@
# Verify that SPLIT operation locks DML operations with partitioned table
setup
{
DROP TABLE IF EXISTS tpart;
CREATE TABLE tpart(i int, t text) partition by range(i);
CREATE TABLE tpart_00_10 PARTITION OF tpart FOR VALUES FROM (0) TO (10);
CREATE TABLE tpart_10_20 PARTITION OF tpart FOR VALUES FROM (10) TO (20);
CREATE TABLE tpart_20_30 PARTITION OF tpart FOR VALUES FROM (20) TO (30);
CREATE TABLE tpart_default PARTITION OF tpart DEFAULT;
INSERT INTO tpart VALUES (5, 'text05');
INSERT INTO tpart VALUES (15, 'text15');
INSERT INTO tpart VALUES (25, 'text25');
INSERT INTO tpart VALUES (35, 'text35');
}
teardown
{
DROP TABLE tpart;
}
session s1
step s1b { BEGIN; }
step s1brr { BEGIN ISOLATION LEVEL REPEATABLE READ; }
step s1bs { BEGIN ISOLATION LEVEL SERIALIZABLE; }
step s1splt { ALTER TABLE tpart SPLIT PARTITION tpart_10_20 INTO
(PARTITION tpart_10_15 FOR VALUES FROM (10) TO (15),
PARTITION tpart_15_20 FOR VALUES FROM (15) TO (20)); }
step s1c { COMMIT; }
session s2
step s2b { BEGIN; }
step s2brr { BEGIN ISOLATION LEVEL REPEATABLE READ; }
step s2bs { BEGIN ISOLATION LEVEL SERIALIZABLE; }
step s2i { INSERT INTO tpart VALUES (1, 'text01'); }
step s2c { COMMIT; }
step s2s { SELECT tableoid::regclass, * FROM tpart ORDER BY tableoid::regclass::text COLLATE "C", i; }
step s2u { UPDATE tpart SET i = 16 where i = 5; }
step s2u2 { UPDATE tpart SET i = 11 where i = 15; }
# s1 starts SPLIT PARTITION then s2 trying to insert row and
# waits until s1 finished SPLIT operation.
permutation s1b s1splt s2b s2i s1c s2c s2s
permutation s1b s1splt s2brr s2i s1c s2c s2s
permutation s1b s1splt s2bs s2i s1c s2c s2s
permutation s1brr s1splt s2b s2i s1c s2c s2s
permutation s1brr s1splt s2brr s2i s1c s2c s2s
permutation s1brr s1splt s2bs s2i s1c s2c s2s
permutation s1bs s1splt s2b s2i s1c s2c s2s
permutation s1bs s1splt s2brr s2i s1c s2c s2s
permutation s1bs s1splt s2bs s2i s1c s2c s2s
# Tuple routing between partitions.
permutation s1b s1splt s2b s2u s1c s2c s2s
# Tuple routing inside splitting partition.
permutation s1b s1splt s2b s2u2 s1c s2c s2s

View File

@@ -113,16 +113,6 @@ ALTER TABLE part DETACH PARTITION part2;
NOTICE: DDL test: type alter table, tag ALTER TABLE
NOTICE: subcommand: type DETACH PARTITION desc table part2
DROP TABLE part2;
CREATE TABLE part2 PARTITION OF part FOR VALUES FROM (100) to (200);
NOTICE: DDL test: type simple, tag CREATE TABLE
ALTER TABLE part MERGE PARTITIONS (part1, part2) INTO part1;
NOTICE: DDL test: type alter table, tag ALTER TABLE
NOTICE: subcommand: type MERGE PARTITIONS desc <NULL>
ALTER TABLE part SPLIT PARTITION part1 INTO
(PARTITION part1 FOR VALUES FROM (1) to (100),
PARTITION part2 FOR VALUES FROM (100) to (200));
NOTICE: DDL test: type alter table, tag ALTER TABLE
NOTICE: subcommand: type SPLIT PARTITION desc <NULL>
ALTER TABLE part ADD PRIMARY KEY (a);
NOTICE: DDL test: type alter table, tag ALTER TABLE
NOTICE: subcommand: type ADD CONSTRAINT (and recurse) desc constraint part_a_not_null on table part

View File

@@ -60,13 +60,6 @@ ALTER TABLE part ATTACH PARTITION part2 FOR VALUES FROM (101) to (200);
ALTER TABLE part DETACH PARTITION part2;
DROP TABLE part2;
CREATE TABLE part2 PARTITION OF part FOR VALUES FROM (100) to (200);
ALTER TABLE part MERGE PARTITIONS (part1, part2) INTO part1;
ALTER TABLE part SPLIT PARTITION part1 INTO
(PARTITION part1 FOR VALUES FROM (1) to (100),
PARTITION part2 FOR VALUES FROM (100) to (200));
ALTER TABLE part ADD PRIMARY KEY (a);
CREATE TABLE tbl (

View File

@@ -297,12 +297,6 @@ get_altertable_subcmdinfo(PG_FUNCTION_ARGS)
case AT_DetachPartitionFinalize:
strtype = "DETACH PARTITION ... FINALIZE";
break;
case AT_SplitPartition:
strtype = "SPLIT PARTITION";
break;
case AT_MergePartitions:
strtype = "MERGE PARTITIONS";
break;
case AT_AddIdentity:
strtype = "ADD IDENTITY";
break;

View File

@@ -186,123 +186,3 @@ DROP MATERIALIZED VIEW d;
DROP INDEX e;
DROP SCHEMA test_ext CASCADE;
NOTICE: drop cascades to table a
-- Fifth test: extension dependencies on partition indexes survive MERGE and
-- SPLIT PARTITION operations, and mismatches between source partitions are
-- reported.
RESET search_path;
CREATE EXTENSION test_ext3;
CREATE EXTENSION test_ext5;
CREATE TABLE part_extdep (i int, x int) PARTITION BY RANGE (i);
CREATE TABLE part_extdep_1 PARTITION OF part_extdep FOR VALUES FROM (1) TO (2);
CREATE TABLE part_extdep_2 PARTITION OF part_extdep FOR VALUES FROM (2) TO (3);
CREATE TABLE part_extdep_3 PARTITION OF part_extdep FOR VALUES FROM (3) TO (4);
CREATE TABLE part_extdep_4 PARTITION OF part_extdep FOR VALUES FROM (4) TO (5);
CREATE TABLE part_extdep_5 PARTITION OF part_extdep FOR VALUES FROM (5) TO (6);
CREATE INDEX part_extdep_i_idx ON part_extdep(i);
CREATE INDEX part_extdep_x_idx ON part_extdep(x);
-- Partitions 1, 2, 3 depend on the same two extensions.
ALTER INDEX part_extdep_1_i_idx DEPENDS ON EXTENSION test_ext3;
ALTER INDEX part_extdep_1_x_idx DEPENDS ON EXTENSION test_ext5;
ALTER INDEX part_extdep_2_i_idx DEPENDS ON EXTENSION test_ext3;
ALTER INDEX part_extdep_2_x_idx DEPENDS ON EXTENSION test_ext5;
ALTER INDEX part_extdep_3_i_idx DEPENDS ON EXTENSION test_ext3;
ALTER INDEX part_extdep_3_x_idx DEPENDS ON EXTENSION test_ext5;
-- Partition 4 depends on a different extension on one index.
ALTER INDEX part_extdep_4_i_idx DEPENDS ON EXTENSION test_ext5;
-- Partition 5 has no dependency at all.
-- Merge matching partitions: should succeed and preserve dependencies on the
-- new partition's indexes (DROP EXTENSION must fail, naming the new index).
ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_1, part_extdep_2)
INTO part_extdep_merged;
DROP EXTENSION test_ext3;
ERROR: cannot drop index part_extdep_merged_i_idx because index part_extdep_i_idx requires it
HINT: You can drop index part_extdep_i_idx instead.
SELECT c.relname, e.extname
FROM pg_depend d
JOIN pg_class c ON d.objid = c.oid
JOIN pg_extension e ON d.refobjid = e.oid
WHERE c.relname IN ('part_extdep_merged_i_idx', 'part_extdep_merged_x_idx')
AND e.extname IN ('test_ext3', 'test_ext5')
AND d.deptype = 'x'
ORDER BY c.relname, e.extname;
relname | extname
--------------------------+-----------
part_extdep_merged_i_idx | test_ext3
part_extdep_merged_x_idx | test_ext5
(2 rows)
-- An index created directly on a partition has no parent in the partitioned
-- index tree. Such an index is dropped with its old partition during merge,
-- and any extension dependency it carries goes away with it: the dep is not
-- promoted to the merged partition. Verify by attaching test_ext9 to such
-- an orphan index, merging, and observing that test_ext9 becomes droppable.
CREATE EXTENSION test_ext9;
CREATE INDEX part_extdep_3_extra_idx ON part_extdep_3(x);
ALTER INDEX part_extdep_3_extra_idx DEPENDS ON EXTENSION test_ext9;
ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_merged, part_extdep_3)
INTO part_extdep_merged2;
DROP EXTENSION test_ext9;
-- Mismatched dependencies: partition 4's index depends on a different
-- extension than partition_merged2's. Both orderings must fail, and the
-- error must cite both partition indexes.
ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_merged2, part_extdep_4)
INTO part_extdep_bad;
ERROR: cannot merge partitions with conflicting extension dependencies
DETAIL: Partition indexes "part_extdep_4_i_idx" and "part_extdep_merged2_i_idx" depend on different extensions.
ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_4, part_extdep_merged2)
INTO part_extdep_bad;
ERROR: cannot merge partitions with conflicting extension dependencies
DETAIL: Partition indexes "part_extdep_4_i_idx" and "part_extdep_merged2_i_idx" depend on different extensions.
-- Empty vs non-empty dependency set (the subset case the earlier linear
-- check missed in one direction).
ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_4, part_extdep_5)
INTO part_extdep_bad;
ERROR: cannot merge partitions with conflicting extension dependencies
DETAIL: Partition indexes "part_extdep_4_i_idx" and "part_extdep_5_i_idx" depend on different extensions.
ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_5, part_extdep_4)
INTO part_extdep_bad;
ERROR: cannot merge partitions with conflicting extension dependencies
DETAIL: Partition indexes "part_extdep_4_i_idx" and "part_extdep_5_i_idx" depend on different extensions.
-- Subset: partition 5's i_idx depends on a strict superset of partition 4's
-- i_idx dependencies. Partition 4 = {test_ext5}, partition 5 will be
-- {test_ext3, test_ext5}. Both orderings must fail; in particular the case
-- where the first partition we walk has fewer extensions than the second
-- must still be rejected.
ALTER INDEX part_extdep_5_i_idx DEPENDS ON EXTENSION test_ext3;
ALTER INDEX part_extdep_5_i_idx DEPENDS ON EXTENSION test_ext5;
ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_4, part_extdep_5)
INTO part_extdep_bad;
ERROR: cannot merge partitions with conflicting extension dependencies
DETAIL: Partition indexes "part_extdep_4_i_idx" and "part_extdep_5_i_idx" depend on different extensions.
ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_5, part_extdep_4)
INTO part_extdep_bad;
ERROR: cannot merge partitions with conflicting extension dependencies
DETAIL: Partition indexes "part_extdep_4_i_idx" and "part_extdep_5_i_idx" depend on different extensions.
-- Reset partition 5 so it doesn't interfere with the SPLIT test below.
ALTER INDEX part_extdep_5_i_idx NO DEPENDS ON EXTENSION test_ext3;
ALTER INDEX part_extdep_5_i_idx NO DEPENDS ON EXTENSION test_ext5;
-- Split: the single source partition's dependencies must appear on every
-- new partition's matching index, identified by extension name.
ALTER TABLE part_extdep SPLIT PARTITION part_extdep_merged2 INTO
(PARTITION part_extdep_s1 FOR VALUES FROM (1) TO (3),
PARTITION part_extdep_s2 FOR VALUES FROM (3) TO (4));
SELECT c.relname, e.extname
FROM pg_depend d
JOIN pg_class c ON d.objid = c.oid
JOIN pg_extension e ON d.refobjid = e.oid
WHERE c.relname IN ('part_extdep_s1_i_idx', 'part_extdep_s1_x_idx',
'part_extdep_s2_i_idx', 'part_extdep_s2_x_idx')
AND e.extname IN ('test_ext3', 'test_ext5')
AND d.deptype = 'x'
ORDER BY c.relname, e.extname;
relname | extname
----------------------+-----------
part_extdep_s1_i_idx | test_ext3
part_extdep_s1_x_idx | test_ext5
part_extdep_s2_i_idx | test_ext3
part_extdep_s2_x_idx | test_ext5
(4 rows)
DROP TABLE part_extdep;
DROP EXTENSION test_ext3;
DROP EXTENSION test_ext5;

View File

@@ -88,107 +88,3 @@ DROP FUNCTION b();
DROP MATERIALIZED VIEW d;
DROP INDEX e;
DROP SCHEMA test_ext CASCADE;
-- Fifth test: extension dependencies on partition indexes survive MERGE and
-- SPLIT PARTITION operations, and mismatches between source partitions are
-- reported.
RESET search_path;
CREATE EXTENSION test_ext3;
CREATE EXTENSION test_ext5;
CREATE TABLE part_extdep (i int, x int) PARTITION BY RANGE (i);
CREATE TABLE part_extdep_1 PARTITION OF part_extdep FOR VALUES FROM (1) TO (2);
CREATE TABLE part_extdep_2 PARTITION OF part_extdep FOR VALUES FROM (2) TO (3);
CREATE TABLE part_extdep_3 PARTITION OF part_extdep FOR VALUES FROM (3) TO (4);
CREATE TABLE part_extdep_4 PARTITION OF part_extdep FOR VALUES FROM (4) TO (5);
CREATE TABLE part_extdep_5 PARTITION OF part_extdep FOR VALUES FROM (5) TO (6);
CREATE INDEX part_extdep_i_idx ON part_extdep(i);
CREATE INDEX part_extdep_x_idx ON part_extdep(x);
-- Partitions 1, 2, 3 depend on the same two extensions.
ALTER INDEX part_extdep_1_i_idx DEPENDS ON EXTENSION test_ext3;
ALTER INDEX part_extdep_1_x_idx DEPENDS ON EXTENSION test_ext5;
ALTER INDEX part_extdep_2_i_idx DEPENDS ON EXTENSION test_ext3;
ALTER INDEX part_extdep_2_x_idx DEPENDS ON EXTENSION test_ext5;
ALTER INDEX part_extdep_3_i_idx DEPENDS ON EXTENSION test_ext3;
ALTER INDEX part_extdep_3_x_idx DEPENDS ON EXTENSION test_ext5;
-- Partition 4 depends on a different extension on one index.
ALTER INDEX part_extdep_4_i_idx DEPENDS ON EXTENSION test_ext5;
-- Partition 5 has no dependency at all.
-- Merge matching partitions: should succeed and preserve dependencies on the
-- new partition's indexes (DROP EXTENSION must fail, naming the new index).
ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_1, part_extdep_2)
INTO part_extdep_merged;
DROP EXTENSION test_ext3;
SELECT c.relname, e.extname
FROM pg_depend d
JOIN pg_class c ON d.objid = c.oid
JOIN pg_extension e ON d.refobjid = e.oid
WHERE c.relname IN ('part_extdep_merged_i_idx', 'part_extdep_merged_x_idx')
AND e.extname IN ('test_ext3', 'test_ext5')
AND d.deptype = 'x'
ORDER BY c.relname, e.extname;
-- An index created directly on a partition has no parent in the partitioned
-- index tree. Such an index is dropped with its old partition during merge,
-- and any extension dependency it carries goes away with it: the dep is not
-- promoted to the merged partition. Verify by attaching test_ext9 to such
-- an orphan index, merging, and observing that test_ext9 becomes droppable.
CREATE EXTENSION test_ext9;
CREATE INDEX part_extdep_3_extra_idx ON part_extdep_3(x);
ALTER INDEX part_extdep_3_extra_idx DEPENDS ON EXTENSION test_ext9;
ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_merged, part_extdep_3)
INTO part_extdep_merged2;
DROP EXTENSION test_ext9;
-- Mismatched dependencies: partition 4's index depends on a different
-- extension than partition_merged2's. Both orderings must fail, and the
-- error must cite both partition indexes.
ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_merged2, part_extdep_4)
INTO part_extdep_bad;
ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_4, part_extdep_merged2)
INTO part_extdep_bad;
-- Empty vs non-empty dependency set (the subset case the earlier linear
-- check missed in one direction).
ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_4, part_extdep_5)
INTO part_extdep_bad;
ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_5, part_extdep_4)
INTO part_extdep_bad;
-- Subset: partition 5's i_idx depends on a strict superset of partition 4's
-- i_idx dependencies. Partition 4 = {test_ext5}, partition 5 will be
-- {test_ext3, test_ext5}. Both orderings must fail; in particular the case
-- where the first partition we walk has fewer extensions than the second
-- must still be rejected.
ALTER INDEX part_extdep_5_i_idx DEPENDS ON EXTENSION test_ext3;
ALTER INDEX part_extdep_5_i_idx DEPENDS ON EXTENSION test_ext5;
ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_4, part_extdep_5)
INTO part_extdep_bad;
ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_5, part_extdep_4)
INTO part_extdep_bad;
-- Reset partition 5 so it doesn't interfere with the SPLIT test below.
ALTER INDEX part_extdep_5_i_idx NO DEPENDS ON EXTENSION test_ext3;
ALTER INDEX part_extdep_5_i_idx NO DEPENDS ON EXTENSION test_ext5;
-- Split: the single source partition's dependencies must appear on every
-- new partition's matching index, identified by extension name.
ALTER TABLE part_extdep SPLIT PARTITION part_extdep_merged2 INTO
(PARTITION part_extdep_s1 FOR VALUES FROM (1) TO (3),
PARTITION part_extdep_s2 FOR VALUES FROM (3) TO (4));
SELECT c.relname, e.extname
FROM pg_depend d
JOIN pg_class c ON d.objid = c.oid
JOIN pg_extension e ON d.refobjid = e.oid
WHERE c.relname IN ('part_extdep_s1_i_idx', 'part_extdep_s1_x_idx',
'part_extdep_s2_i_idx', 'part_extdep_s2_x_idx')
AND e.extname IN ('test_ext3', 'test_ext5')
AND d.deptype = 'x'
ORDER BY c.relname, e.extname;
DROP TABLE part_extdep;
DROP EXTENSION test_ext3;
DROP EXTENSION test_ext5;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -123,7 +123,7 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion tr
# The stats test resets stats, so nothing else needing stats access can be in
# this group.
# ----------
test: partition_merge partition_split partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain memoize stats predicate numa eager_aggregate graph_table_rls planner_est
test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain memoize stats predicate numa eager_aggregate graph_table_rls planner_est
# ----------
# Another group of parallel tests (compression)

View File

@@ -1,846 +0,0 @@
--
-- PARTITIONS_MERGE
-- Tests for "ALTER TABLE ... MERGE PARTITIONS ..." command
--
CREATE SCHEMA partitions_merge_schema;
CREATE SCHEMA partitions_merge_schema2;
SET search_path = partitions_merge_schema, public;
--
-- BY RANGE partitioning
--
--
-- Test for error codes
--
CREATE TABLE sales_range (salesperson_id INT, salesperson_name VARCHAR(30), sales_amount INT, sales_date DATE) PARTITION BY RANGE (sales_date);
CREATE TABLE sales_dec2021 PARTITION OF sales_range FOR VALUES FROM ('2021-12-01') TO ('2021-12-31');
CREATE TABLE sales_jan2022 PARTITION OF sales_range FOR VALUES FROM ('2022-01-01') TO ('2022-02-01');
CREATE TABLE sales_feb2022 PARTITION OF sales_range FOR VALUES FROM ('2022-02-01') TO ('2022-03-01');
CREATE TABLE sales_mar2022 PARTITION OF sales_range FOR VALUES FROM ('2022-03-01') TO ('2022-04-01');
CREATE TABLE sales_apr2022 (salesperson_id INT, salesperson_name VARCHAR(30), sales_amount INT, sales_date DATE) PARTITION BY RANGE (sales_date);
CREATE TABLE sales_apr_1 PARTITION OF sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-04-15');
CREATE TABLE sales_apr_2 PARTITION OF sales_apr2022 FOR VALUES FROM ('2022-04-15') TO ('2022-05-01');
ALTER TABLE sales_range ATTACH PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01');
CREATE TABLE sales_others PARTITION OF sales_range DEFAULT;
-- ERROR
ALTER TABLE sales_range MERGE PARTITIONS (sales_feb2022, sales_mar2022, sales_feb2022) INTO sales_feb_mar_apr2022;
-- ERROR
ALTER TABLE sales_range MERGE PARTITIONS (sales_feb2022, sales_mar2022, sales_apr2022) INTO sales_feb_mar_apr2022;
-- ERROR
-- (space between sections sales_jan2022 and sales_mar2022)
ALTER TABLE sales_range MERGE PARTITIONS (sales_jan2022, sales_mar2022) INTO sales_jan_mar2022;
-- ERROR
-- (space between sections sales_dec2021 and sales_jan2022)
ALTER TABLE sales_range MERGE PARTITIONS (sales_dec2021, sales_jan2022, sales_feb2022) INTO sales_dec_jan_feb2022;
-- ERROR
ALTER TABLE sales_range MERGE PARTITIONS (sales_feb2022, sales_mar2022, partitions_merge_schema.sales_feb2022) INTO sales_feb_mar_apr2022;
-- ERROR
ALTER TABLE sales_range MERGE PARTITIONS (sales_feb2022, sales_mar2022, sales_jan2022) INTO sales_apr_2;
CREATE VIEW jan2022v as SELECT * FROM sales_jan2022;
ALTER TABLE sales_range MERGE PARTITIONS (sales_jan2022, sales_feb2022) INTO sales_dec_jan_feb2022;
DROP VIEW jan2022v;
-- NO ERROR: test for custom partitions order, source partitions not in the search_path
SET search_path = partitions_merge_schema2, public;
ALTER TABLE partitions_merge_schema.sales_range MERGE PARTITIONS (
partitions_merge_schema.sales_feb2022,
partitions_merge_schema.sales_mar2022,
partitions_merge_schema.sales_jan2022) INTO sales_jan_feb_mar2022;
SET search_path = partitions_merge_schema, public;
PREPARE get_partition_info(regclass[]) AS
SELECT c.oid::pg_catalog.regclass,
c.relpersistence,
c.relkind,
i.inhdetachpending,
pg_catalog.pg_get_expr(c.relpartbound, c.oid)
FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i
WHERE c.oid = i.inhrelid AND i.inhparent = ANY($1)
ORDER BY pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT',
c.oid::regclass::text COLLATE "C";
EXECUTE get_partition_info('{sales_range}');
DROP TABLE sales_range;
--
-- Add rows into partitioned table, then merge partitions
--
CREATE TABLE sales_range (salesperson_id INT, salesperson_name VARCHAR(30), sales_amount INT, sales_date DATE) PARTITION BY RANGE (sales_date);
CREATE TABLE sales_jan2022 PARTITION OF sales_range FOR VALUES FROM ('2022-01-01') TO ('2022-02-01');
CREATE TABLE sales_feb2022 PARTITION OF sales_range FOR VALUES FROM ('2022-02-01') TO ('2022-03-01');
CREATE TABLE sales_mar2022 PARTITION OF sales_range FOR VALUES FROM ('2022-03-01') TO ('2022-04-01');
CREATE TABLE sales_apr2022 PARTITION OF sales_range FOR VALUES FROM ('2022-04-01') TO ('2022-05-01');
CREATE TABLE sales_others PARTITION OF sales_range DEFAULT;
CREATE INDEX sales_range_sales_date_idx ON sales_range USING btree (sales_date);
INSERT INTO sales_range VALUES
(1, 'May', 1000, '2022-01-31'),
(2, 'Smirnoff', 500, '2022-02-10'),
(3, 'Ford', 2000, '2022-04-30'),
(4, 'Ivanov', 750, '2022-04-13'),
(5, 'Deev', 250, '2022-04-07'),
(6, 'Poirot', 150, '2022-02-11'),
(7, 'Li', 175, '2022-03-08'),
(8, 'Ericsson', 185, '2022-02-23'),
(9, 'Muller', 250, '2022-03-11'),
(10, 'Halder', 350, '2022-01-28'),
(11, 'Trump', 380, '2022-04-06'),
(12, 'Plato', 350, '2022-03-19'),
(13, 'Gandi', 377, '2022-01-09'),
(14, 'Smith', 510, '2022-05-04');
SELECT pg_catalog.pg_get_partkeydef('sales_range'::regclass);
-- show partitions with conditions:
EXECUTE get_partition_info('{sales_range}');
-- check schema-qualified name of the new partition
ALTER TABLE sales_range MERGE PARTITIONS (sales_feb2022, sales_mar2022, sales_apr2022) INTO partitions_merge_schema2.sales_feb_mar_apr2022;
-- show partitions with conditions:
EXECUTE get_partition_info('{sales_range}');
SELECT * FROM pg_indexes WHERE tablename = 'sales_feb_mar_apr2022' and schemaname = 'partitions_merge_schema2';
SELECT tableoid::regclass, * FROM sales_range ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id;
-- Use indexscan for testing indexes
SET enable_seqscan = OFF;
EXPLAIN (COSTS OFF) SELECT * FROM partitions_merge_schema2.sales_feb_mar_apr2022 where sales_date > '2022-01-01';
SELECT * FROM partitions_merge_schema2.sales_feb_mar_apr2022 where sales_date > '2022-01-01';
RESET enable_seqscan;
DROP TABLE sales_range;
--
-- Merge some partitions into DEFAULT partition
--
CREATE TABLE sales_range (salesperson_id INT, salesperson_name VARCHAR(30), sales_amount INT, sales_date DATE) PARTITION BY RANGE (sales_date);
CREATE TABLE sales_jan2022 PARTITION OF sales_range FOR VALUES FROM ('2022-01-01') TO ('2022-02-01');
CREATE TABLE sales_feb2022 PARTITION OF sales_range FOR VALUES FROM ('2022-02-01') TO ('2022-03-01');
CREATE TABLE sales_mar2022 PARTITION OF sales_range FOR VALUES FROM ('2022-03-01') TO ('2022-04-01');
CREATE TABLE sales_apr2022 PARTITION OF sales_range FOR VALUES FROM ('2022-04-01') TO ('2022-05-01');
CREATE TABLE sales_others PARTITION OF sales_range DEFAULT;
CREATE INDEX sales_range_sales_date_idx ON sales_range USING btree (sales_date);
INSERT INTO sales_range VALUES
(1, 'May', 1000, '2022-01-31'),
(2, 'Smirnoff', 500, '2022-02-10'),
(3, 'Ford', 2000, '2022-04-30'),
(4, 'Ivanov', 750, '2022-04-13'),
(5, 'Deev', 250, '2022-04-07'),
(6, 'Poirot', 150, '2022-02-11'),
(7, 'Li', 175, '2022-03-08'),
(8, 'Ericsson', 185, '2022-02-23'),
(9, 'Muller', 250, '2022-03-11'),
(10, 'Halder', 350, '2022-01-28'),
(11, 'Trump', 380, '2022-04-06'),
(12, 'Plato', 350, '2022-03-19'),
(13, 'Gandi', 377, '2022-01-09'),
(14, 'Smith', 510, '2022-05-04');
-- Merge partitions (include DEFAULT partition) into partition with the same
-- name
ALTER TABLE sales_range MERGE PARTITIONS
(sales_jan2022, sales_mar2022, partitions_merge_schema.sales_others) INTO sales_others;
SELECT * FROM sales_others ORDER BY salesperson_id;
-- show partitions with conditions:
EXECUTE get_partition_info('{sales_range}');
DROP TABLE sales_range;
--
-- Test for:
-- * composite partition key;
-- * GENERATED column;
-- * column with DEFAULT value.
--
CREATE TABLE sales_date (salesperson_name VARCHAR(30), sales_year INT, sales_month INT, sales_day INT,
sales_date VARCHAR(10) GENERATED ALWAYS AS
(LPAD(sales_year::text, 4, '0') || '.' || LPAD(sales_month::text, 2, '0') || '.' || LPAD(sales_day::text, 2, '0')) STORED,
sales_department VARCHAR(30) DEFAULT 'Sales department')
PARTITION BY RANGE (sales_year, sales_month, sales_day);
CREATE TABLE sales_dec2022 PARTITION OF sales_date FOR VALUES FROM (2021, 12, 1) TO (2022, 1, 1);
CREATE TABLE sales_jan2022 PARTITION OF sales_date FOR VALUES FROM (2022, 1, 1) TO (2022, 2, 1);
CREATE TABLE sales_feb2022 PARTITION OF sales_date FOR VALUES FROM (2022, 2, 1) TO (2022, 3, 1);
CREATE TABLE sales_other PARTITION OF sales_date FOR VALUES FROM (2022, 3, 1) TO (MAXVALUE, MAXVALUE, MAXVALUE);
INSERT INTO sales_date(salesperson_name, sales_year, sales_month, sales_day) VALUES
('Manager1', 2021, 12, 7),
('Manager2', 2021, 12, 8),
('Manager3', 2022, 1, 1),
('Manager1', 2022, 2, 4),
('Manager2', 2022, 1, 2),
('Manager3', 2022, 2, 1),
('Manager1', 2022, 3, 3),
('Manager2', 2022, 3, 4),
('Manager3', 2022, 5, 1);
SELECT tableoid::regclass, * FROM sales_date;
ALTER TABLE sales_date MERGE PARTITIONS (sales_jan2022, sales_feb2022) INTO sales_jan_feb2022;
INSERT INTO sales_date(salesperson_name, sales_year, sales_month, sales_day) VALUES
('Manager1', 2022, 1, 10),
('Manager2', 2022, 2, 10);
SELECT tableoid::regclass, * FROM sales_date;
DROP TABLE sales_date;
--
-- Test: merge partitions of partitioned table with triggers
--
CREATE TABLE salespeople(salesperson_id INT PRIMARY KEY, salesperson_name VARCHAR(30)) PARTITION BY RANGE (salesperson_id);
CREATE TABLE salespeople01_10 PARTITION OF salespeople FOR VALUES FROM (1) TO (10);
CREATE TABLE salespeople10_20 PARTITION OF salespeople FOR VALUES FROM (10) TO (20);
CREATE TABLE salespeople20_30 PARTITION OF salespeople FOR VALUES FROM (20) TO (30);
CREATE TABLE salespeople30_40 PARTITION OF salespeople FOR VALUES FROM (30) TO (40);
INSERT INTO salespeople VALUES (1, 'Poirot');
CREATE OR REPLACE FUNCTION after_insert_row_trigger() RETURNS trigger LANGUAGE 'plpgsql' AS $BODY$
BEGIN
RAISE NOTICE 'trigger(%) called: action = %, when = %, level = %', TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL;
RETURN NULL;
END;
$BODY$;
CREATE TRIGGER salespeople_after_insert_statement_trigger
AFTER INSERT
ON salespeople
FOR EACH STATEMENT
EXECUTE PROCEDURE after_insert_row_trigger('salespeople');
CREATE TRIGGER salespeople_after_insert_row_trigger
AFTER INSERT
ON salespeople
FOR EACH ROW
EXECUTE PROCEDURE after_insert_row_trigger('salespeople');
-- 2 triggers should fire here (row + statement):
INSERT INTO salespeople VALUES (10, 'May');
-- 1 trigger should fire here (row):
INSERT INTO salespeople10_20 VALUES (19, 'Ivanov');
ALTER TABLE salespeople MERGE PARTITIONS (salespeople10_20, salespeople20_30, salespeople30_40) INTO salespeople10_40;
-- 2 triggers should fire here (row + statement):
INSERT INTO salespeople VALUES (20, 'Smirnoff');
-- 1 trigger should fire here (row):
INSERT INTO salespeople10_40 VALUES (30, 'Ford');
SELECT * FROM salespeople01_10;
SELECT * FROM salespeople10_40;
DROP TABLE salespeople;
DROP FUNCTION after_insert_row_trigger();
--
-- Test: merge partitions with deleted columns
--
CREATE TABLE salespeople(salesperson_id INT PRIMARY KEY, salesperson_name VARCHAR(30)) PARTITION BY RANGE (salesperson_id);
CREATE TABLE salespeople01_10 PARTITION OF salespeople FOR VALUES FROM (1) TO (10);
-- Create partitions with some deleted columns:
CREATE TABLE salespeople10_20(d1 VARCHAR(30), salesperson_id INT PRIMARY KEY, salesperson_name VARCHAR(30));
CREATE TABLE salespeople20_30(salesperson_id INT PRIMARY KEY, d2 INT, salesperson_name VARCHAR(30));
CREATE TABLE salespeople30_40(salesperson_id INT PRIMARY KEY, d3 DATE, salesperson_name VARCHAR(30));
INSERT INTO salespeople10_20 VALUES ('dummy value 1', 19, 'Ivanov');
INSERT INTO salespeople20_30 VALUES (20, 101, 'Smirnoff');
INSERT INTO salespeople30_40 VALUES (31, now(), 'Popov');
ALTER TABLE salespeople10_20 DROP COLUMN d1;
ALTER TABLE salespeople20_30 DROP COLUMN d2;
ALTER TABLE salespeople30_40 DROP COLUMN d3;
ALTER TABLE salespeople ATTACH PARTITION salespeople10_20 FOR VALUES FROM (10) TO (20);
ALTER TABLE salespeople ATTACH PARTITION salespeople20_30 FOR VALUES FROM (20) TO (30);
ALTER TABLE salespeople ATTACH PARTITION salespeople30_40 FOR VALUES FROM (30) TO (40);
INSERT INTO salespeople VALUES
(1, 'Poirot'),
(10, 'May'),
(30, 'Ford');
ALTER TABLE salespeople MERGE PARTITIONS (salespeople10_20, salespeople20_30, salespeople30_40) INTO salespeople10_40;
select * from salespeople;
select * from salespeople01_10;
select * from salespeople10_40;
DROP TABLE salespeople;
--
-- Test: merge sub-partitions
--
CREATE TABLE sales_range (salesperson_id INT, salesperson_name VARCHAR(30), sales_amount INT, sales_date DATE) PARTITION BY RANGE (sales_date);
CREATE TABLE sales_jan2022 PARTITION OF sales_range FOR VALUES FROM ('2022-01-01') TO ('2022-02-01');
CREATE TABLE sales_feb2022 PARTITION OF sales_range FOR VALUES FROM ('2022-02-01') TO ('2022-03-01');
CREATE TABLE sales_mar2022 PARTITION OF sales_range FOR VALUES FROM ('2022-03-01') TO ('2022-04-01');
CREATE TABLE sales_apr2022 (salesperson_id INT, salesperson_name VARCHAR(30), sales_amount INT, sales_date DATE) PARTITION BY RANGE (sales_date);
CREATE TABLE sales_apr2022_01_10 PARTITION OF sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-04-10');
CREATE TABLE sales_apr2022_10_20 PARTITION OF sales_apr2022 FOR VALUES FROM ('2022-04-10') TO ('2022-04-20');
CREATE TABLE sales_apr2022_20_30 PARTITION OF sales_apr2022 FOR VALUES FROM ('2022-04-20') TO ('2022-05-01');
ALTER TABLE sales_range ATTACH PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01');
CREATE TABLE sales_others PARTITION OF sales_range DEFAULT;
CREATE INDEX sales_range_sales_date_idx ON sales_range USING btree (sales_date);
INSERT INTO sales_range VALUES
(1, 'May', 1000, '2022-01-31'),
(2, 'Smirnoff', 500, '2022-02-10'),
(3, 'Ford', 2000, '2022-04-30'),
(4, 'Ivanov', 750, '2022-04-13'),
(5, 'Deev', 250, '2022-04-07'),
(6, 'Poirot', 150, '2022-02-11'),
(7, 'Li', 175, '2022-03-08'),
(8, 'Ericsson', 185, '2022-02-23'),
(9, 'Muller', 250, '2022-03-11'),
(10, 'Halder', 350, '2022-01-28'),
(11, 'Trump', 380, '2022-04-06'),
(12, 'Plato', 350, '2022-03-19'),
(13, 'Gandi', 377, '2022-01-09'),
(14, 'Smith', 510, '2022-05-04');
SELECT tableoid::regclass, * FROM sales_apr2022 ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id;
ALTER TABLE sales_apr2022 MERGE PARTITIONS (sales_apr2022_01_10, sales_apr2022_10_20, sales_apr2022_20_30) INTO sales_apr_all;
SELECT tableoid::regclass, * FROM sales_apr2022 ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id;
DROP TABLE sales_range;
--
-- BY LIST partitioning
--
--
-- Test: specific errors for BY LIST partitioning
--
CREATE TABLE sales_list
(salesperson_id INT GENERATED ALWAYS AS IDENTITY,
salesperson_name VARCHAR(30),
sales_state VARCHAR(20),
sales_amount INT,
sales_date DATE)
PARTITION BY LIST (sales_state);
CREATE TABLE sales_nord PARTITION OF sales_list FOR VALUES IN ('Oslo', 'St. Petersburg', 'Helsinki');
CREATE TABLE sales_west PARTITION OF sales_list FOR VALUES IN ('Lisbon', 'New York', 'Madrid');
CREATE TABLE sales_east PARTITION OF sales_list FOR VALUES IN ('Beijing', 'Delhi', 'Vladivostok');
CREATE TABLE sales_central PARTITION OF sales_list FOR VALUES IN ('Warsaw', 'Berlin', 'Kyiv');
CREATE TABLE sales_others PARTITION OF sales_list DEFAULT;
CREATE TABLE sales_list2 (LIKE sales_list) PARTITION BY LIST (sales_state);
CREATE TABLE sales_nord2 PARTITION OF sales_list2 FOR VALUES IN ('Oslo', 'St. Petersburg', 'Helsinki');
CREATE TABLE sales_others2 PARTITION OF sales_list2 DEFAULT;
CREATE TABLE sales_external (LIKE sales_list);
CREATE TABLE sales_external2 (vch VARCHAR(5));
-- ERROR
ALTER TABLE sales_list MERGE PARTITIONS (sales_west, sales_east, sales_external) INTO sales_all;
-- ERROR
ALTER TABLE sales_list MERGE PARTITIONS (sales_west, sales_east, sales_external2) INTO sales_all;
-- ERROR
ALTER TABLE sales_list MERGE PARTITIONS (sales_west, sales_nord2, sales_east) INTO sales_all;
DROP TABLE sales_external2;
DROP TABLE sales_external;
DROP TABLE sales_list2;
DROP TABLE sales_list;
--
-- Test: BY LIST partitioning, MERGE PARTITIONS with data
--
CREATE TABLE sales_list
(salesperson_id INT GENERATED ALWAYS AS IDENTITY,
salesperson_name VARCHAR(30),
sales_state VARCHAR(20),
sales_amount INT,
sales_date DATE)
PARTITION BY LIST (sales_state);
CREATE INDEX sales_list_salesperson_name_idx ON sales_list USING btree (salesperson_name);
CREATE INDEX sales_list_sales_state_idx ON sales_list USING btree (sales_state);
CREATE TABLE sales_nord PARTITION OF sales_list FOR VALUES IN ('Oslo', 'St. Petersburg', 'Helsinki');
CREATE TABLE sales_west PARTITION OF sales_list FOR VALUES IN ('Lisbon', 'New York', 'Madrid');
CREATE TABLE sales_east PARTITION OF sales_list FOR VALUES IN ('Beijing', 'Delhi', 'Vladivostok');
CREATE TABLE sales_central PARTITION OF sales_list FOR VALUES IN ('Warsaw', 'Berlin', 'Kyiv');
CREATE TABLE sales_others PARTITION OF sales_list DEFAULT;
INSERT INTO sales_list (salesperson_name, sales_state, sales_amount, sales_date) VALUES
('Trump', 'Beijing', 1000, '2022-03-01'),
('Smirnoff', 'New York', 500, '2022-03-03'),
('Ford', 'St. Petersburg', 2000, '2022-03-05'),
('Ivanov', 'Warsaw', 750, '2022-03-04'),
('Deev', 'Lisbon', 250, '2022-03-07'),
('Poirot', 'Berlin', 1000, '2022-03-01'),
('May', 'Helsinki', 1200, '2022-03-06'),
('Li', 'Vladivostok', 1150, '2022-03-09'),
('May', 'Helsinki', 1200, '2022-03-11'),
('Halder', 'Oslo', 800, '2022-03-02'),
('Muller', 'Madrid', 650, '2022-03-05'),
('Smith', 'Kyiv', 350, '2022-03-10'),
('Gandi', 'Warsaw', 150, '2022-03-08'),
('Plato', 'Lisbon', 950, '2022-03-05');
-- show partitions with conditions:
EXECUTE get_partition_info('{sales_list}');
ALTER TABLE sales_list MERGE PARTITIONS (sales_west, sales_east, sales_central) INTO sales_all;
-- show partitions with conditions:
EXECUTE get_partition_info('{sales_list}');
SELECT tableoid::regclass, * FROM sales_list ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id;
-- Use indexscan for testing indexes after merging partitions
SET enable_seqscan = OFF;
EXPLAIN (COSTS OFF) SELECT * FROM sales_all WHERE sales_state = 'Warsaw';
SELECT * FROM sales_all WHERE sales_state = 'Warsaw';
EXPLAIN (COSTS OFF) SELECT * FROM sales_list WHERE sales_state = 'Warsaw';
SELECT * FROM sales_list WHERE sales_state = 'Warsaw';
EXPLAIN (COSTS OFF) SELECT * FROM sales_list WHERE salesperson_name = 'Ivanov';
SELECT * FROM sales_list WHERE salesperson_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
ALTER TABLE t2 MERGE PARTITIONS (t1p1, t2pa) INTO t2p;
-- ERROR
ALTER TABLE t2 MERGE PARTITIONS (t2pa, t3) INTO t2p;
DROP TABLE t3;
DROP TABLE t2;
DROP TABLE t1;
--
-- Check the partition index name if the partition name is the same as one
-- of the merged partitions.
--
CREATE TABLE t (i int, PRIMARY KEY(i)) 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 values should be 'tp_1_2_pkey' and 'tp_1_2_i_idx'.
\d+ tp_1_2
DROP TABLE t;
--
-- Try to MERGE partitions of temporary table.
--
BEGIN;
SHOW search_path;
CREATE TEMP TABLE t (i int) PARTITION BY RANGE (i) ON COMMIT DROP;
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);
CREATE TEMP TABLE tp_2_3 PARTITION OF t FOR VALUES FROM (2) TO (3);
CREATE TEMP TABLE tp_3_4 PARTITION OF t FOR VALUES FROM (3) TO (4);
ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO pg_temp.tp_0_2;
ALTER TABLE t MERGE PARTITIONS (tp_0_2, tp_2_3) INTO pg_temp.tp_0_3;
-- Partition should be temporary.
EXECUTE get_partition_info('{t}');
-- ERROR
ALTER TABLE t MERGE PARTITIONS (tp_0_3, tp_3_4) INTO tp_0_4;
ROLLBACK;
--
-- Try mixing permanent and temporary partitions.
--
BEGIN;
SET search_path = partitions_merge_schema, pg_temp, public;
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);
SELECT c.oid::pg_catalog.regclass, c.relpersistence FROM pg_catalog.pg_class c WHERE c.oid = 't'::regclass;
EXECUTE get_partition_info('{t}');
SAVEPOINT s;
SET search_path = pg_temp, partitions_merge_schema, 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;
ROLLBACK TO SAVEPOINT s;
SET search_path = partitions_merge_schema, public;
-- Can't merge persistent partitions into a temporary partition
ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO pg_temp.tp_0_2;
ROLLBACK;
BEGIN;
SET search_path = pg_temp, partitions_merge_schema, public;
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);
SELECT c.oid::pg_catalog.regclass, c.relpersistence FROM pg_catalog.pg_class c WHERE c.oid = 't'::regclass;
EXECUTE get_partition_info('{t}');
SET search_path = partitions_merge_schema, pg_temp, public;
-- 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;
DEALLOCATE get_partition_info;
-- Check the new partition inherits parent's tablespace
SET search_path = partitions_merge_schema, public;
CREATE TABLE t (i int PRIMARY KEY USING INDEX TABLESPACE regress_tblspace)
PARTITION BY RANGE (i) TABLESPACE regress_tblspace;
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);
ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2;
SELECT tablename, tablespace FROM pg_tables
WHERE tablename IN ('t', 'tp_0_2') AND schemaname = 'partitions_merge_schema'
ORDER BY tablename COLLATE "C", tablespace COLLATE "C";
SELECT tablename, indexname, tablespace FROM pg_indexes
WHERE tablename IN ('t', 'tp_0_2') AND schemaname = 'partitions_merge_schema'
ORDER BY tablename COLLATE "C", indexname COLLATE "C", tablespace COLLATE "C";
DROP TABLE t;
-- Check the new partition inherits parent's table access method
SET search_path = partitions_merge_schema, public;
CREATE ACCESS METHOD partitions_merge_heap TYPE TABLE HANDLER heap_tableam_handler;
CREATE TABLE t (i int) PARTITION BY RANGE (i) USING partitions_merge_heap;
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);
ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2;
SELECT c.relname, a.amname
FROM pg_class c JOIN pg_am a ON c.relam = a.oid
WHERE c.oid IN ('t'::regclass, 'tp_0_2'::regclass)
ORDER BY c.relname COLLATE "C";
DROP TABLE t;
DROP ACCESS METHOD partitions_merge_heap;
-- Test permission checks. The user needs to own the parent table and all
-- the merging partitions to do the merge.
CREATE ROLE regress_partition_merge_alice;
CREATE ROLE regress_partition_merge_bob;
GRANT ALL ON SCHEMA partitions_merge_schema TO regress_partition_merge_alice;
GRANT ALL ON SCHEMA partitions_merge_schema TO regress_partition_merge_bob;
SET SESSION AUTHORIZATION regress_partition_merge_alice;
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 SESSION AUTHORIZATION regress_partition_merge_bob;
-- ERROR
ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2;
RESET SESSION AUTHORIZATION;
ALTER TABLE t OWNER TO regress_partition_merge_bob;
SET SESSION AUTHORIZATION regress_partition_merge_bob;
-- ERROR
ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2;
RESET SESSION AUTHORIZATION;
ALTER TABLE tp_0_1 OWNER TO regress_partition_merge_bob;
SET SESSION AUTHORIZATION regress_partition_merge_bob;
-- ERROR
ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2;
RESET SESSION AUTHORIZATION;
ALTER TABLE tp_1_2 OWNER TO regress_partition_merge_bob;
SET SESSION AUTHORIZATION regress_partition_merge_bob;
-- Ok:
ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2;
RESET SESSION AUTHORIZATION;
DROP TABLE t;
-- Test: we can't merge partitions with different owners
CREATE TABLE tp_0_1(i int);
ALTER TABLE tp_0_1 OWNER TO regress_partition_merge_alice;
CREATE TABLE tp_1_2(i int);
ALTER TABLE tp_1_2 OWNER TO regress_partition_merge_bob;
CREATE TABLE t (i int) PARTITION BY RANGE (i);
ALTER TABLE t ATTACH PARTITION tp_0_1 FOR VALUES FROM (0) TO (1);
ALTER TABLE t ATTACH PARTITION tp_1_2 FOR VALUES FROM (1) TO (2);
-- Owner is 'regress_partition_merge_alice':
\dt tp_0_1
-- Owner is 'regress_partition_merge_bob':
\dt tp_1_2
-- ERROR
ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2;
DROP TABLE t;
REVOKE ALL ON SCHEMA partitions_merge_schema FROM regress_partition_merge_alice;
REVOKE ALL ON SCHEMA partitions_merge_schema FROM regress_partition_merge_bob;
DROP ROLE regress_partition_merge_alice;
DROP ROLE regress_partition_merge_bob;
-- Test for hash partitioned table
CREATE TABLE t (i int) PARTITION BY HASH(i);
CREATE TABLE tp1 PARTITION OF t FOR VALUES WITH (MODULUS 2, REMAINDER 0);
CREATE TABLE tp2 PARTITION OF t FOR VALUES WITH (MODULUS 2, REMAINDER 1);
-- ERROR
ALTER TABLE t MERGE PARTITIONS (tp1, tp2) INTO tp3;
-- ERROR
ALTER TABLE t MERGE PARTITIONS (tp1) INTO tp3;
DROP TABLE t;
-- Test for merged partition properties:
-- * STATISTICS is empty
-- * COMMENT is empty
-- * DEFAULTS are the same as DEFAULTS for partitioned table
-- * STORAGE is the same as STORAGE for partitioned table
-- * GENERATED and CONSTRAINTS are the same as GENERATED and CONSTRAINTS for partitioned table
-- * TRIGGERS are the same as TRIGGERS for partitioned table
\set HIDE_TOAST_COMPRESSION false
CREATE TABLE t
(i int NOT NULL,
t text STORAGE EXTENDED COMPRESSION pglz DEFAULT 'default_t',
b bigint,
d date GENERATED ALWAYS as ('2022-01-01') STORED) PARTITION BY RANGE (abs(i));
COMMENT ON COLUMN t.i IS 't1.i';
CREATE TABLE tp_0_1
(i int NOT NULL,
t text STORAGE MAIN DEFAULT 'default_tp_0_1',
b bigint,
d date GENERATED ALWAYS as ('2022-02-02') STORED);
ALTER TABLE t ATTACH PARTITION tp_0_1 FOR VALUES FROM (0) TO (1);
COMMENT ON COLUMN tp_0_1.i IS 'tp_0_1.i';
CREATE TABLE tp_1_2
(i int NOT NULL,
t text STORAGE MAIN DEFAULT 'default_tp_1_2',
b bigint,
d date GENERATED ALWAYS as ('2022-03-03') STORED);
ALTER TABLE t ATTACH PARTITION tp_1_2 FOR VALUES FROM (1) TO (2);
COMMENT ON COLUMN tp_1_2.i IS 'tp_1_2.i';
CREATE STATISTICS t_stat (DEPENDENCIES) on i, b from t;
CREATE STATISTICS tp_0_1_stat (DEPENDENCIES) on i, b from tp_0_1;
CREATE STATISTICS tp_1_2_stat (DEPENDENCIES) on i, b from tp_1_2;
ALTER TABLE t ADD CONSTRAINT t_b_check CHECK (b > 0);
ALTER TABLE t ADD CONSTRAINT t_b_check1 CHECK (b > 0) NOT ENFORCED;
ALTER TABLE t ADD CONSTRAINT t_b_check2 CHECK (b > 0) NOT VALID;
ALTER TABLE t ADD CONSTRAINT t_b_nn NOT NULL b NOT VALID;
INSERT INTO tp_0_1(i, t, b) VALUES(0, DEFAULT, 1);
INSERT INTO tp_1_2(i, t, b) VALUES(1, DEFAULT, 2);
CREATE OR REPLACE FUNCTION trigger_function() RETURNS trigger LANGUAGE 'plpgsql' AS
$BODY$
BEGIN
RAISE NOTICE 'trigger(%) called: action = %, when = %, level = %', TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL;
RETURN new;
END;
$BODY$;
CREATE TRIGGER t_before_insert_row_trigger BEFORE INSERT ON t FOR EACH ROW
EXECUTE PROCEDURE trigger_function('t');
CREATE TRIGGER tp_0_1_before_insert_row_trigger BEFORE INSERT ON tp_0_1 FOR EACH ROW
EXECUTE PROCEDURE trigger_function('tp_0_1');
CREATE TRIGGER tp_1_2_before_insert_row_trigger BEFORE INSERT ON tp_1_2 FOR EACH ROW
EXECUTE PROCEDURE trigger_function('tp_1_2');
\d+ tp_0_1
ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_1;
\d+ tp_0_1
INSERT INTO t(i, t, b) VALUES(1, DEFAULT, 3);
SELECT tableoid::regclass, * FROM t ORDER BY b;
DROP TABLE t;
DROP FUNCTION trigger_function();
\set HIDE_TOAST_COMPRESSION true
-- Test MERGE PARTITIONS with not valid foreign key constraint
CREATE TABLE t (i INT PRIMARY KEY) 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);
INSERT INTO t VALUES (0), (1);
CREATE TABLE t_fk (i INT);
INSERT INTO t_fk VALUES (1), (2);
ALTER TABLE t_fk ADD CONSTRAINT t_fk_i_fkey FOREIGN KEY (i) REFERENCES t NOT VALID;
ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2;
-- Should be NOT VALID FOREIGN KEY
\d tp_0_2
-- ERROR
ALTER TABLE t_fk VALIDATE CONSTRAINT t_fk_i_fkey;
DROP TABLE t_fk;
DROP TABLE t;
-- Test MERGE PARTITIONS with not enforced foreign key constraint
CREATE TABLE t (i INT PRIMARY KEY) 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);
INSERT INTO t VALUES (0), (1);
CREATE TABLE t_fk (i INT);
INSERT INTO t_fk VALUES (1), (2);
ALTER TABLE t_fk ADD CONSTRAINT t_fk_i_fkey FOREIGN KEY (i) REFERENCES t NOT ENFORCED;
ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2;
-- Should be NOT ENFORCED FOREIGN KEY
\d tp_0_2
-- ERROR
ALTER TABLE t_fk ALTER CONSTRAINT t_fk_i_fkey ENFORCED;
DROP TABLE t_fk;
DROP TABLE t;
-- Test for recomputation of stored generated columns.
CREATE TABLE t (i int, tab_id int generated always as (tableoid) stored) 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);
ALTER TABLE t ADD CONSTRAINT cc CHECK(tableoid <> 123456789);
INSERT INTO t VALUES (0), (1);
-- Should be 0 because partition identifier for row with i=0 is different from
-- partition identifier for row with i=1.
SELECT count(*) FROM t WHERE i = 0 AND tab_id IN (SELECT tab_id FROM t WHERE i = 1);
-- "tab_id" column (stored generated column) with "tableoid" attribute requires
-- recomputation here.
ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2;
-- Should be 1 because partition identifier for row with i=0 is the same as
-- partition identifier for row with i=1.
SELECT count(*) FROM t WHERE i = 0 AND tab_id IN (SELECT tab_id FROM t WHERE i = 1);
DROP TABLE t;
-- Test for generated columns (different order of columns in partitioned table
-- and partitions).
CREATE TABLE t (i int, g int GENERATED ALWAYS AS (i + tableoid::int)) PARTITION BY RANGE (i);
CREATE TABLE tp_1 (g int GENERATED ALWAYS AS (i + tableoid::int), i int);
CREATE TABLE tp_2 (g int GENERATED ALWAYS AS (i + tableoid::int), i int);
ALTER TABLE t ATTACH PARTITION tp_1 FOR VALUES FROM (-1) TO (10);
ALTER TABLE t ATTACH PARTITION tp_2 FOR VALUES FROM (10) TO (20);
ALTER TABLE t ADD CHECK (g > 0);
ALTER TABLE t ADD CHECK (i > 0);
INSERT INTO t VALUES (5), (15);
ALTER TABLE t MERGE PARTITIONS (tp_1, tp_2) INTO tp_12;
INSERT INTO t VALUES (16);
-- ERROR
INSERT INTO t VALUES (0);
-- Should be 3 rows: (5), (15), (16):
SELECT i FROM t ORDER BY i;
-- Should be 1 because for the same tableoid (15 + tableoid) = (5 + tableoid) + 10:
SELECT count(*) FROM t WHERE i = 15 AND g IN (SELECT g + 10 FROM t WHERE i = 5);
DROP TABLE t;
-- A merged partition needs its own TOAST table; otherwise an out-of-line
-- varlena value carried over from one of the merging partitions has
-- nowhere to be stored. SET STORAGE EXTERNAL forces externalization
-- for any value over the TOAST threshold, so a string over that threshold
-- suffices to exercise the toast-table dependency.
CREATE TABLE t (a text) PARTITION BY RANGE(a);
ALTER TABLE t ALTER COLUMN a SET STORAGE EXTERNAL;
CREATE TABLE tp_def PARTITION OF t DEFAULT;
CREATE TABLE tp_2_3 PARTITION OF t FOR VALUES FROM ('2') TO ('3');
INSERT INTO t SELECT repeat('1', 10000);
ALTER TABLE t MERGE PARTITIONS (tp_def, tp_2_3) INTO tp_merged;
SELECT reltoastrelid <> 0 AS has_toast,
pg_relation_size(reltoastrelid) > 0 AS toast_used
FROM pg_class WHERE relname = 'tp_merged';
SELECT length(a) FROM 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;
--
DROP SCHEMA partitions_merge_schema;
DROP SCHEMA partitions_merge_schema2;

File diff suppressed because it is too large Load Diff

View File

@@ -2211,7 +2211,6 @@ PartitionDirectoryEntry
PartitionDispatch
PartitionElem
PartitionHashBound
PartitionIndexExtDepEntry
PartitionKey
PartitionListValue
PartitionMap
@@ -2924,7 +2923,6 @@ SimpleStats
SimpleStringList
SimpleStringListCell
SingleBoundSortItem
SinglePartitionSpec
Size
SkipPages
SkipSupport
@@ -2996,7 +2994,6 @@ SpinDelayStatus
SplitInterval
SplitLR
SplitPageLayout
SplitPartitionContext
SplitPoint
SplitTextOutputData
SplitVar