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 revertsf2e4cc4279and4b3d173629, and the subsequent fixes and improvementsc5ae07a90a,713e553e32,52e629be95,ecb2508aaf,9354896920,971017c495,83df16f1fa,e64a9ba2b4,ff8bec8c46,cdae794af3,57f19774d6, and881033ae8b.d8af730100and0392fb900ecancelled 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:
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user