diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index dadf5e0dcb9..291545ee008 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -4779,44 +4779,6 @@ ALTER INDEX measurement_city_id_logdate_key ... - - - There is also an option for merging multiple table partitions into - a single partition using the - ALTER TABLE ... MERGE PARTITIONS. - 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 - ACCESS EXCLUSIVE 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: - -ALTER TABLE measurement - MERGE PARTITIONS (measurement_y2006m01, - measurement_y2006m02, - measurement_y2006m03) INTO measurement_y2006q1; - - - - - Similarly to merging multiple table partitions, there is an option for - splitting a single partition into multiple using the - ALTER TABLE ... SPLIT PARTITION. - 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 ACCESS EXCLUSIVE lock, which could impact high-load - systems due to the lock's restrictive nature. For example, we can split - the quarter partition back to monthly partitions: - -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')); - - - diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index ff7071bef5b..0f9d698d170 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -37,12 +37,6 @@ ALTER TABLE [ IF EXISTS ] name ATTACH PARTITION partition_name { FOR VALUES partition_bound_spec | DEFAULT } ALTER TABLE [ IF EXISTS ] name DETACH PARTITION partition_name [ CONCURRENTLY | FINALIZE ] -ALTER TABLE [ IF EXISTS ] name - MERGE PARTITIONS (partition_name1, partition_name2 [, ...]) INTO partition_name -ALTER TABLE [ IF EXISTS ] name - SPLIT PARTITION partition_name INTO - (PARTITION partition_name1 { FOR VALUES partition_bound_spec | DEFAULT }, - PARTITION partition_name2 { FOR VALUES partition_bound_spec | DEFAULT } [, ...]) where action is one of: @@ -1185,239 +1179,18 @@ WITH ( MODULUS numeric_literal, REM - - MERGE PARTITIONS (partition_name1, partition_name2 [, ...]) INTO partition_name - - - - 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 (partition_name) - can have the same name as one of the merged partitions - (partition_name1, - partition_name2 [, ...]). - - - - If the DEFAULT partition is not in the - list of merged partitions: - - - - 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 - partition_name. - - - - - For list-partitioned tables, the partition bounds of - merged partitions are combined to form the new partition bound for - partition_name. - - - - If the DEFAULT partition is in the list of merged partitions: - - - - The partition partition_name - will be the new DEFAULT partition of the target table. - - - - - The partition bound specifications for merged partitions can be arbitrary. - - - - - - 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 ACL on - the new partition. - - - - ALTER TABLE MERGE PARTITION 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 - CREATE TABLE ... PARTITION OF command issued - without a TABLESPACE clause: if the partitioned - table has an explicit tablespace, the new partition uses it; - otherwise the value of 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. - - - - 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 RESTRICT mode) too; therefore, if any objects - are still dependent on them, - ALTER TABLE MERGE PARTITION would fail. - (see ). - - - - Extension dependencies on partition indexes (created via - ALTER INDEX ... DEPENDS ON - EXTENSION) 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. - - - - - Merging partitions acquires an ACCESS EXCLUSIVE lock on - the parent table, in addition to the ACCESS EXCLUSIVE - locks on the tables being merged and on the default partition (if any). - - - - - ALTER TABLE MERGE PARTITIONS 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. - - - - - - - - SPLIT PARTITION partition_name INTO ( - PARTITION partition_name1 { FOR VALUES partition_bound_spec | DEFAULT }, - PARTITION partition_name2 { FOR VALUES partition_bound_spec | DEFAULT } - [, ...]) - - - - - 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 DEFAULT partition, - one of the new partitions must be DEFAULT. - If the partitioned table does not have a DEFAULT - partition, a DEFAULT partition can be defined as one - of the new partitions. - - - - The bounds of new non-DEFAULT partitions must not - overlap with those of new or existing partitions, except - partition_name, and must be - contained within the bounds of the split partition - partition_name. - If no new DEFAULT partition is specified, the - combined bounds of the new partitions - - partition_name1, - partition_name2[, ...] - must exactly match the bounds of the split partition - partition_name. - One of the new partitions can have the same name as the split partition - partition_name. - This is useful when splitting the DEFAULT partition, - so that after the split, the DEFAULT partition - keeps the same name but its partition bound changes. - - - - New partitions will have the same owner as the parent partition. - It is the user's responsibility to setup ACL on new - partitions. - - - - ALTER TABLE SPLIT PARTITION 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 - CREATE TABLE ... PARTITION OF command issued - without a TABLESPACE clause: if the partitioned - table has an explicit tablespace, the new partitions use it; - otherwise the value of 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 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. - - - - 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 ALTER TABLE SPLIT PARTITION uses - the partitioned table itself as the template to reconstruct these - objects later. - Eventually, we will drop the split partition - (using RESTRICT mode) too; therefore, if any objects - are still dependent on it, ALTER TABLE SPLIT PARTITION - would fail (see ). - - - - Extension dependencies on partition indexes (created via - ALTER INDEX ... DEPENDS ON - EXTENSION) are preserved during split operations. - The new partitions' indexes will inherit the extension dependencies - from the source partition's indexes. - - - - - Split partition acquires an ACCESS EXCLUSIVE lock on - the parent table, in addition to the ACCESS EXCLUSIVE - lock on the table being split. - - - - - - ALTER TABLE SPLIT PARTITION 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. - - - - - All the forms of ALTER TABLE that act on a single table, except RENAME, SET SCHEMA, - ATTACH PARTITION, DETACH PARTITION, - MERGE PARTITIONS, and SPLIT PARTITION - can be combined into + ATTACH PARTITION, and + DETACH PARTITION 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. @@ -1656,19 +1429,7 @@ WITH ( MODULUS numeric_literal, REM partition_name - 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. - - - - - - partition_name1 - partition_name2 - - - 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. @@ -2101,31 +1862,6 @@ ALTER TABLE measurement DETACH PARTITION measurement_y2015m12; - - To split a single partition of the range-partitioned table: - -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')); - - - - To split a single partition of the list-partitioned table: - -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')); - - - - To merge several partitions into one partition of the target table: - -ALTER TABLE sales_list MERGE PARTITIONS (sales_west, sales_east, sales_central) - INTO sales_all; - - diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 026b743275f..40114d2ddb3 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -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, diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index 8aba9cbbc57..c2fcc81a24a 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -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); } diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index cad394b3540..2680acf4d94 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -40,7 +40,6 @@ #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" #include "catalog/pg_depend.h" -#include "catalog/pg_extension_d.h" #include "catalog/pg_foreign_table.h" #include "catalog/pg_inherits.h" #include "catalog/pg_largeobject.h" @@ -61,7 +60,6 @@ #include "commands/comment.h" #include "commands/defrem.h" #include "commands/event_trigger.h" -#include "commands/extension.h" #include "commands/repack.h" #include "commands/sequence.h" #include "commands/tablecmds.h" @@ -368,27 +366,6 @@ typedef enum addFkConstraintSides addFkBothSides, } addFkConstraintSides; -/* - * Hold extension dependencies of one partition index, during - * MERGE/SPLIT PARTITION processing. - * - * collectPartitionIndexExtDeps() builds a list of these entries sorted by - * parentIndexOid with exactly one entry per parent partitioned index; the - * list is then consumed by applyPartitionIndexExtDeps() to re-record the - * same dependencies on the newly created partition's indexes. - * - * extensionOids is kept sorted ascending so that equality checks between - * entries from different partitions can be done in a single pass. - * indexOid is carried only so that conflict errors can cite specific - * partition index names. - */ -typedef struct PartitionIndexExtDepEntry -{ - Oid parentIndexOid; /* OID of the parent partitioned index */ - Oid indexOid; /* OID of a representative partition index */ - List *extensionOids; /* OIDs of dependent extensions, sorted asc */ -} PartitionIndexExtDepEntry; - /* * Partition tables are expected to be dropped when the parent partitioned * table gets dropped. Hence for partitioning we use AUTO dependency. @@ -787,14 +764,6 @@ static void ATDetachCheckNoForeignKeyRefs(Relation partition); static char GetAttributeCompression(Oid atttypid, const char *compression); static char GetAttributeStorage(Oid atttypid, const char *storagemode); -static void ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel, - PartitionCmd *cmd, AlterTableUtilityContext *context); -static void ATExecSplitPartition(List **wqueue, AlteredTableInfo *tab, - Relation rel, PartitionCmd *cmd, - AlterTableUtilityContext *context); -static List *collectPartitionIndexExtDeps(List *partitionOids); -static void applyPartitionIndexExtDeps(Oid newPartOid, List *extDepState); -static void freePartitionIndexExtDeps(List *extDepState); /* ---------------------------------------------------------------- * DefineRelation @@ -4918,11 +4887,6 @@ AlterTableGetLockLevel(List *cmds) cmd_lockmode = ShareUpdateExclusiveLock; break; - case AT_MergePartitions: - case AT_SplitPartition: - cmd_lockmode = AccessExclusiveLock; - break; - default: /* oops */ elog(ERROR, "unrecognized alter table type: %d", (int) cmd->subtype); @@ -5358,12 +5322,6 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, /* No command-specific prep needed */ pass = AT_PASS_MISC; break; - case AT_MergePartitions: - case AT_SplitPartition: - ATSimplePermissions(cmd->subtype, rel, ATT_PARTITIONED_TABLE); - /* No command-specific prep needed */ - pass = AT_PASS_MISC; - break; default: /* oops */ elog(ERROR, "unrecognized alter table type: %d", (int) cmd->subtype); @@ -5760,22 +5718,6 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, case AT_DetachPartitionFinalize: address = ATExecDetachPartitionFinalize(rel, ((PartitionCmd *) cmd->def)->name); break; - case AT_MergePartitions: - cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode, - cur_pass, context); - Assert(cmd != NULL); - Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); - ATExecMergePartitions(wqueue, tab, rel, (PartitionCmd *) cmd->def, - context); - break; - case AT_SplitPartition: - cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode, - cur_pass, context); - Assert(cmd != NULL); - Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); - ATExecSplitPartition(wqueue, tab, rel, (PartitionCmd *) cmd->def, - context); - break; default: /* oops */ elog(ERROR, "unrecognized alter table type: %d", (int) cmd->subtype); @@ -6818,10 +6760,6 @@ alter_table_type_to_string(AlterTableType cmdtype) return "DETACH PARTITION"; case AT_DetachPartitionFinalize: return "DETACH PARTITION ... FINALIZE"; - case AT_MergePartitions: - return "MERGE PARTITIONS"; - case AT_SplitPartition: - return "SPLIT PARTITION"; case AT_AddIdentity: return "ALTER COLUMN ... ADD IDENTITY"; case AT_SetIdentity: @@ -20948,40 +20886,6 @@ QueuePartitionConstraintValidation(List **wqueue, Relation scanrel, } } -/* - * attachPartitionTable: attach a new partition to the partitioned table - * - * wqueue: the ALTER TABLE work queue; can be NULL when not running as part - * of an ALTER TABLE sequence. - * rel: partitioned relation; - * attachrel: relation of attached partition; - * bound: bounds of attached relation. - */ -static void -attachPartitionTable(List **wqueue, Relation rel, Relation attachrel, PartitionBoundSpec *bound) -{ - /* - * Create an inheritance; the relevant checks are performed inside the - * function. - */ - CreateInheritance(attachrel, rel, true); - - /* Update the pg_class entry. */ - StorePartitionBound(attachrel, rel, bound); - - /* Ensure there exists a correct set of indexes in the partition. */ - AttachPartitionEnsureIndexes(wqueue, rel, attachrel); - - /* and triggers */ - CloneRowTriggersToPartition(rel, attachrel); - - /* - * Clone foreign key constraints. Callee is responsible for setting up - * for phase 3 constraint verification. - */ - CloneForeignKeyConstraints(wqueue, rel, attachrel); -} - /* * ALTER TABLE ATTACH PARTITION FOR VALUES * @@ -21221,10 +21125,26 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd, check_new_partition_bound(RelationGetRelationName(attachrel), rel, cmd->bound, pstate); - attachPartitionTable(wqueue, rel, attachrel, cmd->bound); + /* OK to create inheritance. Rest of the checks performed there */ + CreateInheritance(attachrel, rel, true); + + /* Update the pg_class entry. */ + StorePartitionBound(attachrel, rel, cmd->bound); + + /* Ensure there exists a correct set of indexes in the partition. */ + AttachPartitionEnsureIndexes(wqueue, rel, attachrel); + + /* and triggers */ + CloneRowTriggersToPartition(rel, attachrel); /* - * Generate a partition constraint from the partition bound specification. + * Clone foreign key constraints. Callee is responsible for setting up + * for phase 3 constraint verification. + */ + CloneForeignKeyConstraints(wqueue, rel, attachrel); + + /* + * Generate partition constraint from the partition bound specification. * If the parent itself is a partition, make sure to include its * constraint as well. */ @@ -22832,1536 +22752,3 @@ GetAttributeStorage(Oid atttypid, const char *storagemode) return cstorage; } - -/* - * buildExpressionExecutionStates: build the needed expression execution states - * for new partition (newPartRel) checks and initialize expressions for - * generated columns. All expressions should be created in "tab" - * (AlteredTableInfo structure). - */ -static void -buildExpressionExecutionStates(AlteredTableInfo *tab, Relation newPartRel, EState *estate) -{ - /* - * Build the needed expression execution states. Here, we expect only NOT - * NULL and CHECK constraint. - */ - foreach_ptr(NewConstraint, con, tab->constraints) - { - switch (con->contype) - { - case CONSTR_CHECK: - - /* - * We already expanded virtual expression in - * createTableConstraints. - */ - con->qualstate = ExecPrepareExpr((Expr *) con->qual, estate); - break; - case CONSTR_NOTNULL: - /* Nothing to do here. */ - break; - default: - elog(ERROR, "unrecognized constraint type: %d", - (int) con->contype); - } - } - - /* Expression already planned in createTableConstraints */ - foreach_ptr(NewColumnValue, ex, tab->newvals) - ex->exprstate = ExecInitExpr((Expr *) ex->expr, NULL); -} - -/* - * evaluateGeneratedExpressionsAndCheckConstraints: evaluate any generated - * expressions for "tab" (AlteredTableInfo structure) whose inputs come from - * the new tuple (insertslot) of the new partition (newPartRel). - */ -static void -evaluateGeneratedExpressionsAndCheckConstraints(AlteredTableInfo *tab, - Relation newPartRel, - TupleTableSlot *insertslot, - ExprContext *econtext) -{ - econtext->ecxt_scantuple = insertslot; - - foreach_ptr(NewColumnValue, ex, tab->newvals) - { - if (!ex->is_generated) - continue; - - insertslot->tts_values[ex->attnum - 1] - = ExecEvalExpr(ex->exprstate, - econtext, - &insertslot->tts_isnull[ex->attnum - 1]); - } - - foreach_ptr(NewConstraint, con, tab->constraints) - { - switch (con->contype) - { - case CONSTR_CHECK: - if (!ExecCheck(con->qualstate, econtext)) - ereport(ERROR, - errcode(ERRCODE_CHECK_VIOLATION), - errmsg("check constraint \"%s\" of relation \"%s\" is violated by some row", - con->name, RelationGetRelationName(newPartRel)), - errtableconstraint(newPartRel, con->name)); - break; - case CONSTR_NOTNULL: - case CONSTR_FOREIGN: - /* Nothing to do here */ - break; - default: - elog(ERROR, "unrecognized constraint type: %d", - (int) con->contype); - } - } -} - -/* - * getAttributesList: build a list of columns (ColumnDef) based on parent_rel - */ -static List * -getAttributesList(Relation parent_rel) -{ - AttrNumber parent_attno; - TupleDesc modelDesc; - List *colList = NIL; - - modelDesc = RelationGetDescr(parent_rel); - - for (parent_attno = 1; parent_attno <= modelDesc->natts; - parent_attno++) - { - Form_pg_attribute attribute = TupleDescAttr(modelDesc, - parent_attno - 1); - ColumnDef *def; - - /* Ignore dropped columns in the parent. */ - if (attribute->attisdropped) - continue; - - def = makeColumnDef(NameStr(attribute->attname), attribute->atttypid, - attribute->atttypmod, attribute->attcollation); - - def->is_not_null = attribute->attnotnull; - - /* Copy identity. */ - def->identity = attribute->attidentity; - - /* Copy attgenerated. */ - def->generated = attribute->attgenerated; - - def->storage = attribute->attstorage; - - /* Likewise, copy compression. */ - if (CompressionMethodIsValid(attribute->attcompression)) - def->compression = - pstrdup(GetCompressionMethodName(attribute->attcompression)); - else - def->compression = NULL; - - /* Add to column list. */ - colList = lappend(colList, def); - } - - return colList; -} - -/* - * createTableConstraints: - * create check constraints, default values, and generated values for newRel - * based on parent_rel. tab is pending-work queue for newRel, we may need it in - * MergePartitionsMoveRows. - */ -static void -createTableConstraints(List **wqueue, AlteredTableInfo *tab, - Relation parent_rel, Relation newRel) -{ - TupleDesc tupleDesc; - TupleConstr *constr; - AttrMap *attmap; - AttrNumber parent_attno; - int ccnum; - List *constraints = NIL; - List *cookedConstraints = NIL; - - tupleDesc = RelationGetDescr(parent_rel); - constr = tupleDesc->constr; - - if (!constr) - return; - - /* - * Construct a map from the parent relation's attnos to the child rel's. - * This re-checks type match, etc, although it shouldn't be possible to - * have a failure since both tables are locked. - */ - attmap = build_attrmap_by_name(RelationGetDescr(newRel), - tupleDesc, - false); - - /* Cycle for default values. */ - for (parent_attno = 1; parent_attno <= tupleDesc->natts; parent_attno++) - { - Form_pg_attribute attribute = TupleDescAttr(tupleDesc, - parent_attno - 1); - - /* Ignore dropped columns in the parent. */ - if (attribute->attisdropped) - continue; - - /* Copy the default, if present, and it should be copied. */ - if (attribute->atthasdef) - { - Node *this_default = NULL; - bool found_whole_row; - AttrNumber num; - Node *def; - NewColumnValue *newval; - - if (attribute->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL) - this_default = build_generation_expression(parent_rel, attribute->attnum); - else - { - this_default = TupleDescGetDefault(tupleDesc, attribute->attnum); - if (this_default == NULL) - elog(ERROR, "default expression not found for attribute %d of relation \"%s\"", - attribute->attnum, RelationGetRelationName(parent_rel)); - } - - num = attmap->attnums[parent_attno - 1]; - def = map_variable_attnos(this_default, 1, 0, attmap, InvalidOid, &found_whole_row); - - if (found_whole_row && attribute->attgenerated != '\0') - elog(ERROR, "cannot convert whole-row table reference"); - - /* Add a pre-cooked default expression. */ - StoreAttrDefault(newRel, num, def, false); - - /* - * Stored generated column expressions in parent_rel might - * reference the tableoid. newRel, parent_rel tableoid clear is - * not the same. If so, these stored generated columns require - * recomputation for newRel within MergePartitionsMoveRows. - */ - if (attribute->attgenerated == ATTRIBUTE_GENERATED_STORED) - { - newval = palloc0_object(NewColumnValue); - newval->attnum = num; - newval->expr = expression_planner((Expr *) def); - newval->is_generated = (attribute->attgenerated != '\0'); - tab->newvals = lappend(tab->newvals, newval); - } - } - } - - /* Cycle for CHECK constraints. */ - for (ccnum = 0; ccnum < constr->num_check; ccnum++) - { - char *ccname = constr->check[ccnum].ccname; - char *ccbin = constr->check[ccnum].ccbin; - bool ccenforced = constr->check[ccnum].ccenforced; - bool ccnoinherit = constr->check[ccnum].ccnoinherit; - bool ccvalid = constr->check[ccnum].ccvalid; - Node *ccbin_node; - bool found_whole_row; - Constraint *con; - - /* - * The partitioned table can not have a NO INHERIT check constraint - * (see StoreRelCheck function for details). - */ - Assert(!ccnoinherit); - - ccbin_node = map_variable_attnos(stringToNode(ccbin), - 1, 0, - attmap, - InvalidOid, &found_whole_row); - - /* - * For the moment we have to reject whole-row variables (as for CREATE - * TABLE LIKE and inheritances). - */ - if (found_whole_row) - elog(ERROR, "Constraint \"%s\" contains a whole-row reference to table \"%s\".", - ccname, - RelationGetRelationName(parent_rel)); - - con = makeNode(Constraint); - con->contype = CONSTR_CHECK; - con->conname = pstrdup(ccname); - con->deferrable = false; - con->initdeferred = false; - con->is_enforced = ccenforced; - con->skip_validation = !ccvalid; - con->initially_valid = ccvalid; - con->is_no_inherit = ccnoinherit; - con->raw_expr = NULL; - con->cooked_expr = nodeToString(ccbin_node); - con->location = -1; - constraints = lappend(constraints, con); - } - - /* Install all CHECK constraints. */ - cookedConstraints = AddRelationNewConstraints(newRel, NIL, constraints, - false, true, false, NULL); - - /* Make the additional catalog changes visible. */ - CommandCounterIncrement(); - - /* - * parent_rel check constraint expression may reference tableoid, so later - * in MergePartitionsMoveRows, we need to evaluate the check constraint - * again for the newRel. We can check whether the check constraint - * contains a tableoid reference via pull_varattnos. - */ - foreach_ptr(CookedConstraint, ccon, cookedConstraints) - { - if (!ccon->skip_validation) - { - Node *qual; - Bitmapset *attnums = NULL; - - Assert(ccon->contype == CONSTR_CHECK); - qual = expand_generated_columns_in_expr(ccon->expr, newRel, 1); - pull_varattnos(qual, 1, &attnums); - - /* - * Add a check only if it contains a tableoid - * (TableOidAttributeNumber). - */ - if (bms_is_member(TableOidAttributeNumber - FirstLowInvalidHeapAttributeNumber, - attnums)) - { - NewConstraint *newcon; - - newcon = palloc0_object(NewConstraint); - newcon->name = ccon->name; - newcon->contype = CONSTR_CHECK; - newcon->qual = qual; - - tab->constraints = lappend(tab->constraints, newcon); - } - } - } - - /* Don't need the cookedConstraints anymore. */ - list_free_deep(cookedConstraints); - - /* Reproduce not-null constraints. */ - if (constr->has_not_null) - { - List *nnconstraints; - - /* - * The "include_noinh" argument is false because a partitioned table - * can't have NO INHERIT constraint. - */ - nnconstraints = RelationGetNotNullConstraints(RelationGetRelid(parent_rel), - false, false); - - Assert(list_length(nnconstraints) > 0); - - /* - * We already set pg_attribute.attnotnull in createPartitionTable. No - * need call set_attnotnull again. - */ - AddRelationNewConstraints(newRel, NIL, nnconstraints, false, true, false, NULL); - } -} - -/* - * createPartitionTable: - * - * Create a new partition (newPartName) for the partitioned table (parent_rel). - * ownerId is determined by the partition on which the operation is performed, - * so it is passed separately. The new partition will inherit the access method - * and persistence type from the parent table. - * - * Returns the created relation (locked in AccessExclusiveLock mode). - */ -static Relation -createPartitionTable(List **wqueue, RangeVar *newPartName, - Relation parent_rel, Oid ownerId) -{ - Relation newRel; - Oid newRelId; - Oid existingRelid; - Oid tablespaceId; - TupleDesc descriptor; - List *colList = NIL; - Oid relamId; - Oid namespaceId; - AlteredTableInfo *new_partrel_tab; - Form_pg_class parent_relform = parent_rel->rd_rel; - - /* If the existing rel is temp, it must belong to this session. */ - if (RELATION_IS_OTHER_TEMP(parent_rel)) - ereport(ERROR, - errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot create as partition of temporary relation of another session")); - - /* Look up inheritance ancestors and generate the relation schema. */ - colList = getAttributesList(parent_rel); - - /* Create a tuple descriptor from the relation schema. */ - descriptor = BuildDescForRelation(colList); - - /* Look up the access method for the new relation. */ - relamId = (parent_relform->relam != InvalidOid) ? parent_relform->relam : HEAP_TABLE_AM_OID; - - /* Look up the namespace in which we are supposed to create the relation. */ - namespaceId = - RangeVarGetAndCheckCreationNamespace(newPartName, NoLock, &existingRelid); - if (OidIsValid(existingRelid)) - ereport(ERROR, - errcode(ERRCODE_DUPLICATE_TABLE), - errmsg("relation \"%s\" already exists", newPartName->relname)); - - /* - * We intended to create the partition with the same persistence as the - * parent table, but we still need to recheck because that might be - * affected by the search_path. If the parent is permanent, so must be - * all of its partitions. - */ - if (parent_relform->relpersistence != RELPERSISTENCE_TEMP && - newPartName->relpersistence == RELPERSISTENCE_TEMP) - ereport(ERROR, - errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot create a temporary relation as partition of permanent relation \"%s\"", - RelationGetRelationName(parent_rel))); - - /* Permanent rels cannot be partitions belonging to a temporary parent. */ - if (newPartName->relpersistence != RELPERSISTENCE_TEMP && - parent_relform->relpersistence == RELPERSISTENCE_TEMP) - ereport(ERROR, - errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot create a permanent relation as partition of temporary relation \"%s\"", - RelationGetRelationName(parent_rel))); - - /* - * Select the tablespace for the new partition. Mirror the logic that - * CREATE TABLE foo PARTITION OF ... uses in DefineRelation: take the - * partitioned parent's explicit tablespace if it has one, otherwise take - * default_tablespace into account, and finally use the database default. - */ - tablespaceId = parent_relform->reltablespace; - if (!OidIsValid(tablespaceId)) - tablespaceId = GetDefaultTablespace(newPartName->relpersistence, false); - - /* Check permissions except when using database's default */ - if (OidIsValid(tablespaceId) && tablespaceId != MyDatabaseTableSpace) - { - AclResult aclresult; - - aclresult = object_aclcheck(TableSpaceRelationId, tablespaceId, - GetUserId(), ACL_CREATE); - if (aclresult != ACLCHECK_OK) - aclcheck_error(aclresult, OBJECT_TABLESPACE, - get_tablespace_name(tablespaceId)); - } - - /* In all cases disallow placing user relations in pg_global */ - if (tablespaceId == GLOBALTABLESPACE_OID) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("only shared relations can be placed in pg_global tablespace"))); - - /* Create the relation. */ - newRelId = heap_create_with_catalog(newPartName->relname, - namespaceId, - tablespaceId, - InvalidOid, - InvalidOid, - InvalidOid, - ownerId, - relamId, - descriptor, - NIL, - RELKIND_RELATION, - newPartName->relpersistence, - false, - false, - ONCOMMIT_NOOP, - (Datum) 0, - true, - allowSystemTableMods, - false, /* is_internal */ - InvalidOid, - NULL); - - /* - * We must bump the command counter to make the newly-created relation - * tuple visible for opening. - */ - CommandCounterIncrement(); - - /* - * Create a TOAST table if the table needs one. MERGE/SPLIT PARTITION - * moves rows from existing partition(s) into new partition(s), which may - * carry out-of-line varlena values that the new relation must be able to - * store. Also, the new partition must be able to receive out-of-line - * varlena values after the DDL operation is complete. - */ - NewRelationCreateToastTable(newRelId, (Datum) 0); - - /* - * Open the new partition with no lock, because we already have an - * AccessExclusiveLock placed there after creation. - */ - newRel = table_open(newRelId, NoLock); - - /* Find or create a work queue entry for the newly created table. */ - new_partrel_tab = ATGetQueueEntry(wqueue, newRel); - - /* Create constraints, default values, and generated values. */ - createTableConstraints(wqueue, new_partrel_tab, parent_rel, newRel); - - /* - * Need to call CommandCounterIncrement, so a fresh relcache entry has - * newly installed constraint info. - */ - CommandCounterIncrement(); - - return newRel; -} - -/* - * MergePartitionsMoveRows: scan partitions to be merged (mergingPartitions) - * of the partitioned table and move rows into the new partition - * (newPartRel). We also verify check constraints against these rows. - */ -static void -MergePartitionsMoveRows(List **wqueue, List *mergingPartitions, Relation newPartRel) -{ - CommandId mycid; - EState *estate; - AlteredTableInfo *tab; - ListCell *ltab; - - /* The FSM is empty, so don't bother using it. */ - uint32 ti_options = TABLE_INSERT_SKIP_FSM; - BulkInsertState bistate; /* state of bulk inserts for partition */ - TupleTableSlot *dstslot; - - /* Find the work queue entry for the new partition table: newPartRel. */ - tab = ATGetQueueEntry(wqueue, newPartRel); - - /* Generate the constraint and default execution states. */ - estate = CreateExecutorState(); - - buildExpressionExecutionStates(tab, newPartRel, estate); - - mycid = GetCurrentCommandId(true); - - /* Prepare a BulkInsertState for table_tuple_insert. */ - bistate = GetBulkInsertState(); - - /* Create the necessary tuple slot. */ - dstslot = table_slot_create(newPartRel, NULL); - - foreach_oid(merging_oid, mergingPartitions) - { - ExprContext *econtext; - TupleTableSlot *srcslot; - TupleConversionMap *tuple_map; - TableScanDesc scan; - MemoryContext oldCxt; - Snapshot snapshot; - Relation mergingPartition; - - econtext = GetPerTupleExprContext(estate); - - /* - * Partition is already locked in the transformPartitionCmdForMerge - * function. - */ - mergingPartition = table_open(merging_oid, NoLock); - - /* Create a source tuple slot for the partition being merged. */ - srcslot = table_slot_create(mergingPartition, NULL); - - /* - * Map computing for moving attributes of the merged partition to the - * new partition. - */ - tuple_map = convert_tuples_by_name(RelationGetDescr(mergingPartition), - RelationGetDescr(newPartRel)); - - /* Scan through the rows. */ - snapshot = RegisterSnapshot(GetLatestSnapshot()); - scan = table_beginscan(mergingPartition, snapshot, 0, NULL, - SO_NONE); - - /* - * Switch to per-tuple memory context and reset it for each tuple - * produced, so we don't leak memory. - */ - oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); - - while (table_scan_getnextslot(scan, ForwardScanDirection, srcslot)) - { - TupleTableSlot *insertslot; - - CHECK_FOR_INTERRUPTS(); - - if (tuple_map) - { - /* Need to use a map to copy attributes. */ - insertslot = execute_attr_map_slot(tuple_map->attrMap, srcslot, dstslot); - } - else - { - slot_getallattrs(srcslot); - - /* Copy attributes directly. */ - insertslot = dstslot; - - ExecClearTuple(insertslot); - - memcpy(insertslot->tts_values, srcslot->tts_values, - sizeof(Datum) * srcslot->tts_nvalid); - memcpy(insertslot->tts_isnull, srcslot->tts_isnull, - sizeof(bool) * srcslot->tts_nvalid); - - ExecStoreVirtualTuple(insertslot); - } - - /* - * Constraints and GENERATED expressions might reference the - * tableoid column, so fill tts_tableOid with the desired value. - * (We must do this each time, because it gets overwritten with - * newrel's OID during storing.) - */ - insertslot->tts_tableOid = RelationGetRelid(newPartRel); - - /* - * Now, evaluate any generated expressions whose inputs come from - * the new tuple. We assume these columns won't reference each - * other, so that there's no ordering dependency. - */ - evaluateGeneratedExpressionsAndCheckConstraints(tab, newPartRel, - insertslot, econtext); - - /* Write the tuple out to the new relation. */ - table_tuple_insert(newPartRel, insertslot, mycid, - ti_options, bistate); - - ResetExprContext(econtext); - } - - MemoryContextSwitchTo(oldCxt); - table_endscan(scan); - UnregisterSnapshot(snapshot); - - if (tuple_map) - free_conversion_map(tuple_map); - - ExecDropSingleTupleTableSlot(srcslot); - table_close(mergingPartition, NoLock); - } - - FreeExecutorState(estate); - ExecDropSingleTupleTableSlot(dstslot); - FreeBulkInsertState(bistate); - - table_finish_bulk_insert(newPartRel, ti_options); - - /* - * We don't need to process this newPartRel since we already processed it - * here, so delete the ALTER TABLE queue for it. - */ - foreach(ltab, *wqueue) - { - tab = (AlteredTableInfo *) lfirst(ltab); - if (tab->relid == RelationGetRelid(newPartRel)) - { - *wqueue = list_delete_cell(*wqueue, ltab); - break; - } - } -} - -/* - * detachPartitionTable: detach partition "child_rel" from partitioned table - * "parent_rel" with default partition identifier "defaultPartOid" - */ -static void -detachPartitionTable(Relation parent_rel, Relation child_rel, Oid defaultPartOid) -{ - /* Remove the pg_inherits row first. */ - RemoveInheritance(child_rel, parent_rel, false); - - /* - * Detaching the partition might involve TOAST table access, so ensure we - * have a valid snapshot. - */ - PushActiveSnapshot(GetTransactionSnapshot()); - - /* Do the final part of detaching. */ - DetachPartitionFinalize(parent_rel, child_rel, false, defaultPartOid); - - PopActiveSnapshot(); -} - -/* - * equal_oid_lists: return true if two OID lists, each sorted in ascending - * order, contain the same OIDs in the same order. - */ -static bool -equal_oid_lists(const List *a, const List *b) -{ - ListCell *la, - *lb; - - if (list_length(a) != list_length(b)) - return false; - - forboth(la, a, lb, b) - { - if (lfirst_oid(la) != lfirst_oid(lb)) - return false; - } - return true; -} - -/* - * Comparator for list_sort() on a list of PartitionIndexExtDepEntry *. - * Orders by parentIndexOid, then by indexOid as a tiebreaker so conflict - * reports for different parent indexes are deterministic. - */ -static int -cmp_partition_index_ext_dep(const ListCell *a, const ListCell *b) -{ - const PartitionIndexExtDepEntry *ea = lfirst(a); - const PartitionIndexExtDepEntry *eb = lfirst(b); - - if (ea->parentIndexOid != eb->parentIndexOid) - return pg_cmp_u32(ea->parentIndexOid, eb->parentIndexOid); - return pg_cmp_u32(ea->indexOid, eb->indexOid); -} - -/* - * collectPartitionIndexExtDeps: collect extension dependencies from indexes - * on the given partitions. - * - * For each partition index that has a parent partitioned index, we collect - * extension dependencies. All source partition indexes sharing the same - * parent partitioned index must depend on exactly the same set of - * extensions; otherwise an error is raised so that we neither silently drop - * nor silently add dependencies on the merged partition's index. - * - * Indexes that don't have a parent partitioned index (i.e., indexes created - * directly on a partition without a corresponding parent index) are skipped. - * - * The returned list is sorted by parentIndexOid with exactly one entry per - * parent partitioned index, so applyPartitionIndexExtDeps() can scan it - * linearly. - */ -static List * -collectPartitionIndexExtDeps(List *partitionOids) -{ - List *collected = NIL; - List *result = NIL; - PartitionIndexExtDepEntry *prev = NULL; - - /* - * Phase 1: collect one entry per (partition index -> parent index) pair, - * with its extension dependency OIDs sorted ascending. - */ - foreach_oid(partOid, partitionOids) - { - Relation partRel; - List *indexList; - - /* - * Use NoLock since the caller already holds AccessExclusiveLock on - * these partitions. - */ - partRel = table_open(partOid, NoLock); - indexList = RelationGetIndexList(partRel); - - foreach_oid(indexOid, indexList) - { - Oid parentIndexOid; - PartitionIndexExtDepEntry *entry; - - if (!get_rel_relispartition(indexOid)) - continue; - - parentIndexOid = get_partition_parent(indexOid, true); - if (!OidIsValid(parentIndexOid)) - continue; - - entry = palloc(sizeof(PartitionIndexExtDepEntry)); - entry->parentIndexOid = parentIndexOid; - entry->indexOid = indexOid; - entry->extensionOids = getAutoExtensionsOfObject(RelationRelationId, - indexOid); - list_sort(entry->extensionOids, list_oid_cmp); - - collected = lappend(collected, entry); - } - - list_free(indexList); - table_close(partRel, NoLock); - } - - /* - * Phase 2: sort by parentIndexOid so entries sharing a parent index sit - * adjacent. - */ - list_sort(collected, cmp_partition_index_ext_dep); - - /* - * Phase 3: single linear pass verifying that adjacent entries sharing a - * parent index have identical extension dependencies, and keeping one - * representative entry per parent index. - */ - foreach_ptr(PartitionIndexExtDepEntry, entry, collected) - { - if (prev != NULL && prev->parentIndexOid == entry->parentIndexOid) - { - if (!equal_oid_lists(prev->extensionOids, entry->extensionOids)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot merge partitions with conflicting extension dependencies"), - errdetail("Partition indexes \"%s\" and \"%s\" depend on different extensions.", - get_rel_name(prev->indexOid), - get_rel_name(entry->indexOid)))); - - /* Duplicate entry for the same parent index; discard. */ - list_free(entry->extensionOids); - pfree(entry); - continue; - } - - result = lappend(result, entry); - prev = entry; - } - - list_free(collected); - - return result; -} - -/* - * applyPartitionIndexExtDeps: apply collected extension dependencies to - * indexes on a new partition. - * - * For each index on the new partition, look up its parent index in the - * extDepState list. If found, record extension dependencies on the new index. - * extDepState is sorted by parentIndexOid, so the inner scan can bail out - * as soon as it passes the target OID. - */ -static void -applyPartitionIndexExtDeps(Oid newPartOid, List *extDepState) -{ - Relation partRel; - List *indexList; - - if (extDepState == NIL) - return; - - /* - * Use NoLock since the caller already holds AccessExclusiveLock on the - * new partition. - */ - partRel = table_open(newPartOid, NoLock); - indexList = RelationGetIndexList(partRel); - - foreach_oid(indexOid, indexList) - { - Oid parentIdxOid; - - if (!get_rel_relispartition(indexOid)) - continue; - - parentIdxOid = get_partition_parent(indexOid, true); - if (!OidIsValid(parentIdxOid)) - continue; - - foreach_ptr(PartitionIndexExtDepEntry, entry, extDepState) - { - ObjectAddress indexAddr; - - if (entry->parentIndexOid > parentIdxOid) - break; - if (entry->parentIndexOid < parentIdxOid) - continue; - - ObjectAddressSet(indexAddr, RelationRelationId, indexOid); - - foreach_oid(extOid, entry->extensionOids) - { - ObjectAddress extAddr; - - ObjectAddressSet(extAddr, ExtensionRelationId, extOid); - recordDependencyOn(&indexAddr, &extAddr, - DEPENDENCY_AUTO_EXTENSION); - } - break; - } - } - - list_free(indexList); - table_close(partRel, NoLock); -} - -/* - * freePartitionIndexExtDeps: free memory allocated by collectPartitionIndexExtDeps. - */ -static void -freePartitionIndexExtDeps(List *extDepState) -{ - foreach_ptr(PartitionIndexExtDepEntry, entry, extDepState) - { - list_free(entry->extensionOids); - pfree(entry); - } - list_free(extDepState); -} - -/* - * ALTER TABLE MERGE PARTITIONS INTO - */ -static void -ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel, - PartitionCmd *cmd, AlterTableUtilityContext *context) -{ - Relation newPartRel; - List *mergingPartitions = NIL; - List *extDepState = NIL; - Oid defaultPartOid; - Oid existingRelid; - Oid ownerId = InvalidOid; - Oid save_userid; - int save_sec_context; - int save_nestlevel; - - /* - * Check ownership of merged partitions - partitions with different owners - * cannot be merged. Also, collect the OIDs of these partitions during the - * check. - */ - foreach_node(RangeVar, name, cmd->partlist) - { - Relation mergingPartition; - - /* - * We are going to detach and remove this partition. We already took - * AccessExclusiveLock lock on transformPartitionCmdForMerge, so here, - * NoLock is fine. - */ - mergingPartition = table_openrv_extended(name, NoLock, false); - Assert(CheckRelationLockedByMe(mergingPartition, AccessExclusiveLock, false)); - - if (OidIsValid(ownerId)) - { - /* Do the partitions being merged have different owners? */ - if (ownerId != mergingPartition->rd_rel->relowner) - ereport(ERROR, - errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("partitions being merged have different owners")); - } - else - ownerId = mergingPartition->rd_rel->relowner; - - /* Store the next merging partition into the list. */ - mergingPartitions = lappend_oid(mergingPartitions, - RelationGetRelid(mergingPartition)); - - table_close(mergingPartition, NoLock); - } - - /* Look up the existing relation by the new partition name. */ - RangeVarGetAndCheckCreationNamespace(cmd->name, NoLock, &existingRelid); - - /* - * Check if this name is already taken. This helps us to detect the - * situation when one of the merging partitions has the same name as the - * new partition. Otherwise, this would fail later on anyway, but - * catching this here allows us to emit a nicer error message. - */ - if (OidIsValid(existingRelid)) - { - if (list_member_oid(mergingPartitions, existingRelid)) - { - /* - * The new partition has the same name as one of the merging - * partitions. - */ - char tmpRelName[NAMEDATALEN]; - - /* Generate a temporary name. */ - sprintf(tmpRelName, "merge-%u-%X-tmp", RelationGetRelid(rel), MyProcPid); - - /* - * Rename the existing partition with a temporary name, leaving it - * free for the new partition. We don't need to care about this - * in the future because we're going to eventually drop the - * existing partition anyway. - */ - RenameRelationInternal(existingRelid, tmpRelName, true, false); - - /* - * We must bump the command counter to make the new partition - * tuple visible for rename. - */ - CommandCounterIncrement(); - } - else - { - ereport(ERROR, - errcode(ERRCODE_DUPLICATE_TABLE), - errmsg("relation \"%s\" already exists", cmd->name->relname)); - } - } - - defaultPartOid = - get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true)); - - /* - * Collect extension dependencies from indexes on the merging partitions. - * We must do this before detaching them, so we can restore the - * dependencies on the new partition's indexes later. - */ - extDepState = collectPartitionIndexExtDeps(mergingPartitions); - - /* Detach all merging partitions. */ - foreach_oid(mergingPartitionOid, mergingPartitions) - { - Relation child_rel; - - child_rel = table_open(mergingPartitionOid, NoLock); - - detachPartitionTable(rel, child_rel, defaultPartOid); - - table_close(child_rel, NoLock); - } - - /* - * Perform a preliminary check to determine whether it's safe to drop all - * merging partitions before we actually do so later. After merging rows - * into the new partitions via MergePartitionsMoveRows, all old partitions - * need to be dropped. However, since the drop behavior is DROP_RESTRICT - * and the merge process (MergePartitionsMoveRows) can be time-consuming, - * performing an early check on the drop eligibility of old partitions is - * preferable. - */ - foreach_oid(mergingPartitionOid, mergingPartitions) - { - ObjectAddress object; - - /* Get oid of the later to be dropped relation. */ - object.objectId = mergingPartitionOid; - object.classId = RelationRelationId; - object.objectSubId = 0; - - performDeletionCheck(&object, DROP_RESTRICT, 0); - } - - /* - * Create a table for the new partition, using the partitioned table as a - * model. - */ - Assert(OidIsValid(ownerId)); - newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId); - - /* - * Switch to the table owner's userid, so that any index functions are run - * as that user. Also, lockdown security-restricted operations and - * arrange to make GUC variable changes local to this command. - * - * Need to do it after determining the namespace in the - * createPartitionTable() call. - */ - GetUserIdAndSecContext(&save_userid, &save_sec_context); - SetUserIdAndSecContext(ownerId, - save_sec_context | SECURITY_RESTRICTED_OPERATION); - save_nestlevel = NewGUCNestLevel(); - RestrictSearchPath(); - - /* Copy data from merged partitions to the new partition. */ - MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel); - - /* Drop the current partitions before attaching the new one. */ - foreach_oid(mergingPartitionOid, mergingPartitions) - { - ObjectAddress object; - - object.objectId = mergingPartitionOid; - object.classId = RelationRelationId; - object.objectSubId = 0; - - performDeletion(&object, DROP_RESTRICT, 0); - } - - list_free(mergingPartitions); - - /* - * Attach a new partition to the partitioned table. wqueue = NULL: - * verification for each cloned constraint is not needed. - */ - attachPartitionTable(NULL, rel, newPartRel, cmd->bound); - - /* - * Apply extension dependencies to the new partition's indexes. This - * preserves any "DEPENDS ON EXTENSION" settings from the merged - * partitions. - */ - applyPartitionIndexExtDeps(RelationGetRelid(newPartRel), extDepState); - - freePartitionIndexExtDeps(extDepState); - - /* Keep the lock until commit. */ - table_close(newPartRel, NoLock); - - /* Roll back any GUC changes executed by index functions. */ - AtEOXact_GUC(false, save_nestlevel); - - /* Restore the userid and security context. */ - SetUserIdAndSecContext(save_userid, save_sec_context); -} - -/* - * Struct with the context of the new partition for inserting rows from the - * split partition. - */ -typedef struct SplitPartitionContext -{ - ExprState *partqualstate; /* expression for checking a slot for a - * partition (NULL for DEFAULT partition) */ - BulkInsertState bistate; /* state of bulk inserts for partition */ - TupleTableSlot *dstslot; /* slot for inserting row into partition */ - AlteredTableInfo *tab; /* structure with generated column expressions - * and check constraint expressions. */ - Relation partRel; /* relation for partition */ -} SplitPartitionContext; - -/* - * createSplitPartitionContext: create context for partition and fill it - */ -static SplitPartitionContext * -createSplitPartitionContext(Relation partRel) -{ - SplitPartitionContext *pc; - - pc = palloc0_object(SplitPartitionContext); - pc->partRel = partRel; - - /* - * Prepare a BulkInsertState for table_tuple_insert. The FSM is empty, so - * don't bother using it. - */ - pc->bistate = GetBulkInsertState(); - - /* Create a destination tuple slot for the new partition. */ - pc->dstslot = table_slot_create(pc->partRel, NULL); - - return pc; -} - -/* - * deleteSplitPartitionContext: delete context for partition - */ -static void -deleteSplitPartitionContext(SplitPartitionContext *pc, List **wqueue, uint32 ti_options) -{ - ListCell *ltab; - - ExecDropSingleTupleTableSlot(pc->dstslot); - FreeBulkInsertState(pc->bistate); - - table_finish_bulk_insert(pc->partRel, ti_options); - - /* - * We don't need to process this pc->partRel so delete the ALTER TABLE - * queue of it. - */ - foreach(ltab, *wqueue) - { - AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab); - - if (tab->relid == RelationGetRelid(pc->partRel)) - { - *wqueue = list_delete_cell(*wqueue, ltab); - break; - } - } - - pfree(pc); -} - -/* - * SplitPartitionMoveRows: scan split partition (splitRel) of partitioned table - * (rel) and move rows into new partitions. - * - * New partitions description: - * partlist: list of pointers to SinglePartitionSpec structures. It contains - * the partition specification details for all new partitions. - * newPartRels: list of Relations, new partitions created in - * ATExecSplitPartition. - */ -static void -SplitPartitionMoveRows(List **wqueue, Relation rel, Relation splitRel, - List *partlist, List *newPartRels) -{ - /* The FSM is empty, so don't bother using it. */ - uint32 ti_options = TABLE_INSERT_SKIP_FSM; - CommandId mycid; - EState *estate; - ListCell *listptr, - *listptr2; - TupleTableSlot *srcslot; - ExprContext *econtext; - TableScanDesc scan; - Snapshot snapshot; - MemoryContext oldCxt; - List *partContexts = NIL; - TupleConversionMap *tuple_map; - SplitPartitionContext *defaultPartCtx = NULL, - *pc; - - mycid = GetCurrentCommandId(true); - - estate = CreateExecutorState(); - - forboth(listptr, partlist, listptr2, newPartRels) - { - SinglePartitionSpec *sps = (SinglePartitionSpec *) lfirst(listptr); - - pc = createSplitPartitionContext((Relation) lfirst(listptr2)); - - /* Find the work queue entry for the new partition table: newPartRel. */ - pc->tab = ATGetQueueEntry(wqueue, pc->partRel); - - buildExpressionExecutionStates(pc->tab, pc->partRel, estate); - - if (sps->bound->is_default) - { - /* - * We should not create a structure to check the partition - * constraint for the new DEFAULT partition. - */ - defaultPartCtx = pc; - } - else - { - List *partConstraint; - - /* Build expression execution states for partition check quals. */ - partConstraint = get_qual_from_partbound(rel, sps->bound); - partConstraint = - (List *) eval_const_expressions(NULL, - (Node *) partConstraint); - /* Make a boolean expression for ExecCheck(). */ - partConstraint = list_make1(make_ands_explicit(partConstraint)); - - /* - * Map the vars in the constraint expression from rel's attnos to - * splitRel's. - */ - partConstraint = map_partition_varattnos(partConstraint, - 1, splitRel, rel); - - pc->partqualstate = - ExecPrepareExpr((Expr *) linitial(partConstraint), estate); - Assert(pc->partqualstate != NULL); - } - - /* Store partition context into a list. */ - partContexts = lappend(partContexts, pc); - } - - econtext = GetPerTupleExprContext(estate); - - /* Create the necessary tuple slot. */ - srcslot = table_slot_create(splitRel, NULL); - - /* - * Map computing for moving attributes of the split partition to the new - * partition (for the first new partition, but other new partitions can - * use the same map). - */ - pc = (SplitPartitionContext *) lfirst(list_head(partContexts)); - tuple_map = convert_tuples_by_name(RelationGetDescr(splitRel), - RelationGetDescr(pc->partRel)); - - /* Scan through the rows. */ - snapshot = RegisterSnapshot(GetLatestSnapshot()); - scan = table_beginscan(splitRel, snapshot, 0, NULL, - SO_NONE); - - /* - * Switch to per-tuple memory context and reset it for each tuple - * produced, so we don't leak memory. - */ - oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); - - while (table_scan_getnextslot(scan, ForwardScanDirection, srcslot)) - { - bool found = false; - TupleTableSlot *insertslot; - - CHECK_FOR_INTERRUPTS(); - - econtext->ecxt_scantuple = srcslot; - - /* Search partition for the current slot, srcslot. */ - foreach(listptr, partContexts) - { - pc = (SplitPartitionContext *) lfirst(listptr); - - /* skip DEFAULT partition */ - if (pc->partqualstate && ExecCheck(pc->partqualstate, econtext)) - { - found = true; - break; - } - } - if (!found) - { - /* Use the DEFAULT partition if it exists. */ - if (defaultPartCtx) - pc = defaultPartCtx; - else - ereport(ERROR, - errcode(ERRCODE_CHECK_VIOLATION), - errmsg("cannot find partition for split partition row"), - errtable(splitRel)); - } - - if (tuple_map) - { - /* Need to use a map to copy attributes. */ - insertslot = execute_attr_map_slot(tuple_map->attrMap, srcslot, pc->dstslot); - } - else - { - /* Extract data from the old tuple. */ - slot_getallattrs(srcslot); - - /* Copy attributes directly. */ - insertslot = pc->dstslot; - - ExecClearTuple(insertslot); - - memcpy(insertslot->tts_values, srcslot->tts_values, - sizeof(Datum) * srcslot->tts_nvalid); - memcpy(insertslot->tts_isnull, srcslot->tts_isnull, - sizeof(bool) * srcslot->tts_nvalid); - - ExecStoreVirtualTuple(insertslot); - } - - /* - * Constraints and GENERATED expressions might reference the tableoid - * column, so fill tts_tableOid with the desired value. (We must do - * this each time, because it gets overwritten with newrel's OID - * during storing.) - */ - insertslot->tts_tableOid = RelationGetRelid(pc->partRel); - - /* - * Now, evaluate any generated expressions whose inputs come from the - * new tuple. We assume these columns won't reference each other, so - * that there's no ordering dependency. - */ - evaluateGeneratedExpressionsAndCheckConstraints(pc->tab, pc->partRel, - insertslot, econtext); - - /* Write the tuple out to the new relation. */ - table_tuple_insert(pc->partRel, insertslot, mycid, - ti_options, pc->bistate); - - ResetExprContext(econtext); - } - - MemoryContextSwitchTo(oldCxt); - - table_endscan(scan); - UnregisterSnapshot(snapshot); - - if (tuple_map) - free_conversion_map(tuple_map); - - ExecDropSingleTupleTableSlot(srcslot); - - FreeExecutorState(estate); - - foreach_ptr(SplitPartitionContext, spc, partContexts) - deleteSplitPartitionContext(spc, wqueue, ti_options); -} - -/* - * ALTER TABLE SPLIT PARTITION INTO - */ -static void -ATExecSplitPartition(List **wqueue, AlteredTableInfo *tab, Relation rel, - PartitionCmd *cmd, AlterTableUtilityContext *context) -{ - Relation splitRel; - Oid splitRelOid; - ListCell *listptr, - *listptr2; - bool isSameName = false; - char tmpRelName[NAMEDATALEN]; - List *newPartRels = NIL; - List *extDepState = NIL; - ObjectAddress object; - Oid defaultPartOid; - Oid save_userid; - int save_sec_context; - int save_nestlevel; - List *splitPartList; - - defaultPartOid = get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true)); - - /* - * Partition is already locked in the transformPartitionCmdForSplit - * function. - */ - splitRel = table_openrv(cmd->name, NoLock); - - splitRelOid = RelationGetRelid(splitRel); - - /* Check descriptions of new partitions. */ - foreach_node(SinglePartitionSpec, sps, cmd->partlist) - { - Oid existingRelid; - - /* Look up the existing relation by the new partition name. */ - RangeVarGetAndCheckCreationNamespace(sps->name, NoLock, &existingRelid); - - /* - * This would fail later on anyway if the relation already exists. But - * by catching it here, we can emit a nicer error message. - */ - if (existingRelid == splitRelOid && !isSameName) - /* One new partition can have the same name as a split partition. */ - isSameName = true; - else if (OidIsValid(existingRelid)) - ereport(ERROR, - errcode(ERRCODE_DUPLICATE_TABLE), - errmsg("relation \"%s\" already exists", sps->name->relname)); - } - - /* - * Collect extension dependencies from indexes on the split partition. We - * must do this before detaching it, so we can restore the dependencies on - * the new partitions' indexes later. - */ - splitPartList = list_make1_oid(splitRelOid); - - extDepState = collectPartitionIndexExtDeps(splitPartList); - list_free(splitPartList); - - /* Detach the split partition. */ - detachPartitionTable(rel, splitRel, defaultPartOid); - - /* - * Perform a preliminary check to determine whether it's safe to drop the - * split partition before we actually do so later. After merging rows into - * the new partitions via SplitPartitionMoveRows, all old partitions need - * to be dropped. However, since the drop behavior is DROP_RESTRICT and - * the merge process (SplitPartitionMoveRows) can be time-consuming, - * performing an early check on the drop eligibility of old partitions is - * preferable. - */ - object.objectId = splitRelOid; - object.classId = RelationRelationId; - object.objectSubId = 0; - performDeletionCheck(&object, DROP_RESTRICT, 0); - - /* - * If a new partition has the same name as the split partition, then we - * should rename the split partition to reuse its name. - */ - if (isSameName) - { - /* - * We must bump the command counter to make the split partition tuple - * visible for renaming. - */ - CommandCounterIncrement(); - /* Rename partition. */ - sprintf(tmpRelName, "split-%u-%X-tmp", RelationGetRelid(rel), MyProcPid); - RenameRelationInternal(splitRelOid, tmpRelName, true, false); - - /* - * We must bump the command counter to make the split partition tuple - * visible after renaming. - */ - CommandCounterIncrement(); - } - - /* Create new partitions (like a split partition), without indexes. */ - foreach_node(SinglePartitionSpec, sps, cmd->partlist) - { - Relation newPartRel; - - newPartRel = createPartitionTable(wqueue, sps->name, rel, - splitRel->rd_rel->relowner); - newPartRels = lappend(newPartRels, newPartRel); - } - - /* - * Switch to the table owner's userid, so that any index functions are run - * as that user. Also, lockdown security-restricted operations and - * arrange to make GUC variable changes local to this command. - * - * Need to do it after determining the namespace in the - * createPartitionTable() call. - */ - GetUserIdAndSecContext(&save_userid, &save_sec_context); - SetUserIdAndSecContext(splitRel->rd_rel->relowner, - save_sec_context | SECURITY_RESTRICTED_OPERATION); - save_nestlevel = NewGUCNestLevel(); - RestrictSearchPath(); - - /* Copy data from the split partition to the new partitions. */ - SplitPartitionMoveRows(wqueue, rel, splitRel, cmd->partlist, newPartRels); - /* Keep the lock until commit. */ - table_close(splitRel, NoLock); - - /* Attach new partitions to the partitioned table. */ - forboth(listptr, cmd->partlist, listptr2, newPartRels) - { - SinglePartitionSpec *sps = (SinglePartitionSpec *) lfirst(listptr); - Relation newPartRel = (Relation) lfirst(listptr2); - - /* - * wqueue = NULL: verification for each cloned constraint is not - * needed. - */ - attachPartitionTable(NULL, rel, newPartRel, sps->bound); - - /* - * Apply extension dependencies to the new partition's indexes. This - * preserves any "DEPENDS ON EXTENSION" settings from the split - * partition. - */ - applyPartitionIndexExtDeps(RelationGetRelid(newPartRel), extDepState); - - /* Keep the lock until commit. */ - table_close(newPartRel, NoLock); - } - - freePartitionIndexExtDeps(extDepState); - - /* Drop the split partition. */ - object.classId = RelationRelationId; - object.objectId = splitRelOid; - object.objectSubId = 0; - /* Probably DROP_CASCADE is not needed. */ - performDeletion(&object, DROP_RESTRICT, 0); - - /* Roll back any GUC changes executed by index functions. */ - AtEOXact_GUC(false, save_nestlevel); - - /* Restore the userid and security context. */ - SetUserIdAndSecContext(save_userid, save_sec_context); -} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 4b152c294ca..091d4423592 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -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 part_elem %type part_params %type PartitionBoundSpec -%type SinglePartitionSpec -%type partitions_list %type hash_partbound %type 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 ATTACH PARTITION 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 SPLIT PARTITION 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 MERGE PARTITIONS () INTO */ - | 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 diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 5d90c6692c5..424cb1dc878 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -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", diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c index a7822a4192b..c400ba206c4 100644 --- a/src/backend/partitioning/partbounds.c +++ b/src/backend/partitioning/partbounds.c @@ -17,7 +17,6 @@ #include "access/relation.h" #include "access/table.h" #include "access/tableam.h" -#include "catalog/namespace.h" #include "catalog/partition.h" #include "catalog/pg_inherits.h" #include "catalog/pg_type.h" @@ -4982,1056 +4981,3 @@ satisfies_hash_partition(PG_FUNCTION_ARGS) PG_RETURN_BOOL(rowHash % modulus == remainder); } - -/* - * check_two_partitions_bounds_range - * - * (function for BY RANGE partitioning) - * - * This is a helper function for check_partitions_for_split() and - * calculate_partition_bound_for_merge(). This function compares the upper - * bound of first_bound and the lower bound of second_bound. These bounds - * should be equal except when "defaultPart == true" (this means that one of - * the split partitions is DEFAULT). In this case, the upper bound of - * first_bound can be less than the lower bound of second_bound because - * the space between these bounds will be included in the DEFAULT partition. - * - * parent: partitioned table - * first_name: name of the first partition - * first_bound: bound of the first partition - * second_name: name of the second partition - * second_bound: bound of the second partition - * defaultPart: true if one of the new partitions is DEFAULT - * is_merge: true indicates the operation is MERGE PARTITIONS; - * false indicates the operation is SPLIT PARTITION. - * pstate: pointer to ParseState struct for determining error position - */ -static void -check_two_partitions_bounds_range(Relation parent, - RangeVar *first_name, - PartitionBoundSpec *first_bound, - RangeVar *second_name, - PartitionBoundSpec *second_bound, - bool defaultPart, - bool is_merge, - ParseState *pstate) -{ - PartitionKey key = RelationGetPartitionKey(parent); - PartitionRangeBound *first_upper; - PartitionRangeBound *second_lower; - int cmpval; - - Assert(key->strategy == PARTITION_STRATEGY_RANGE); - - first_upper = make_one_partition_rbound(key, -1, first_bound->upperdatums, false); - second_lower = make_one_partition_rbound(key, -1, second_bound->lowerdatums, true); - - /* - * lower1 argument of partition_rbound_cmp() is set to false for the - * correct comparison result of the lower and upper bounds. - */ - cmpval = partition_rbound_cmp(key->partnatts, - key->partsupfunc, - key->partcollation, - second_lower->datums, second_lower->kind, - false, first_upper); - if ((!defaultPart && cmpval) || (defaultPart && cmpval < 0)) - { - PartitionRangeDatum *datum = linitial(second_bound->lowerdatums); - - if (is_merge) - ereport(ERROR, - errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("cannot merge partition \"%s\" together with partition \"%s\"", - second_name->relname, first_name->relname), - errdetail("The lower bound of partition \"%s\" is not equal to the upper bound of partition \"%s\".", - second_name->relname, first_name->relname), - errhint("ALTER TABLE ... MERGE PARTITIONS requires the partition bounds to be adjacent."), - parser_errposition(pstate, datum->location)); - else - ereport(ERROR, - errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("cannot split to partition \"%s\" together with partition \"%s\"", - second_name->relname, first_name->relname), - errdetail("The lower bound of partition \"%s\" is not equal to the upper bound of partition \"%s\".", - second_name->relname, first_name->relname), - errhint("ALTER TABLE ... SPLIT PARTITION requires the partition bounds to be adjacent."), - parser_errposition(pstate, datum->location)); - } -} - -/* - * get_partition_bound_spec - * - * Returns the PartitionBoundSpec for the partition with the given OID partOid. - */ -static PartitionBoundSpec * -get_partition_bound_spec(Oid partOid) -{ - HeapTuple tuple; - Datum datum; - bool isnull; - PartitionBoundSpec *boundspec = NULL; - - /* Try fetching the tuple from the catcache, for speed. */ - tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(partOid)); - if (!HeapTupleIsValid(tuple)) - elog(ERROR, "cache lookup failed for relation %u", partOid); - - datum = SysCacheGetAttr(RELOID, tuple, - Anum_pg_class_relpartbound, - &isnull); - if (isnull) - elog(ERROR, "partition bound for relation %u is null", - partOid); - - boundspec = stringToNode(TextDatumGetCString(datum)); - - if (!IsA(boundspec, PartitionBoundSpec)) - elog(ERROR, "expected PartitionBoundSpec for relation %u", - partOid); - - ReleaseSysCache(tuple); - return boundspec; -} - -/* - * calculate_partition_bound_for_merge - * - * Calculates the bound of the merged partition "spec" by using the bounds of - * the partitions to be merged. - * - * parent: partitioned table - * partNames: names of partitions to be merged - * partOids: Oids of partitions to be merged - * spec (out): bounds specification of the merged partition - * pstate: pointer to ParseState struct to determine error position - */ -void -calculate_partition_bound_for_merge(Relation parent, - List *partNames, - List *partOids, - PartitionBoundSpec *spec, - ParseState *pstate) -{ - PartitionKey key = RelationGetPartitionKey(parent); - PartitionBoundSpec *bound; - - Assert(!spec->is_default); - - switch (key->strategy) - { - case PARTITION_STRATEGY_RANGE: - { - int i; - PartitionRangeBound **lower_bounds; - int nparts = list_length(partOids); - List *bounds = NIL; - - lower_bounds = palloc0_array(PartitionRangeBound *, nparts); - - /* - * Create an array of lower bounds and a list of - * PartitionBoundSpec. - */ - foreach_oid(partoid, partOids) - { - bound = get_partition_bound_spec(partoid); - i = foreach_current_index(partoid); - - lower_bounds[i] = make_one_partition_rbound(key, i, bound->lowerdatums, true); - bounds = lappend(bounds, bound); - } - - /* Sort the array of lower bounds. */ - qsort_arg(lower_bounds, nparts, sizeof(PartitionRangeBound *), - qsort_partition_rbound_cmp, key); - - /* Ranges of partitions should be adjacent. */ - for (i = 1; i < nparts; i++) - { - int index = lower_bounds[i]->index; - int prev_index = lower_bounds[i - 1]->index; - - check_two_partitions_bounds_range(parent, - (RangeVar *) list_nth(partNames, prev_index), - (PartitionBoundSpec *) list_nth(bounds, prev_index), - (RangeVar *) list_nth(partNames, index), - (PartitionBoundSpec *) list_nth(bounds, index), - false, - true, - pstate); - } - - /* - * The lower bound of the first partition is the lower bound - * of the merged partition. - */ - spec->lowerdatums = - ((PartitionBoundSpec *) list_nth(bounds, lower_bounds[0]->index))->lowerdatums; - - /* - * The upper bound of the last partition is the upper bound of - * the merged partition. - */ - spec->upperdatums = - ((PartitionBoundSpec *) list_nth(bounds, lower_bounds[nparts - 1]->index))->upperdatums; - - pfree(lower_bounds); - list_free(bounds); - break; - } - - case PARTITION_STRATEGY_LIST: - { - /* Consolidate bounds for all partitions in the list. */ - foreach_oid(partoid, partOids) - { - bound = get_partition_bound_spec(partoid); - spec->listdatums = list_concat(spec->listdatums, bound->listdatums); - } - break; - } - - default: - elog(ERROR, "unexpected partition strategy: %d", - (int) key->strategy); - } -} - -/* - * partitions_listdatum_intersection - * - * (function for BY LIST partitioning) - * - * Function compares lists of values for different partitions. - * Return a list that contains *one* cell that is present in both list1 and - * list2. The returned list is freshly allocated via palloc(), but the - * cells themselves point to the same objects as the cells of the - * input lists. - * - * Currently, there is no need to collect all common partition datums from the - * two lists. - */ -static List * -partitions_listdatum_intersection(FmgrInfo *partsupfunc, Oid *partcollation, - const List *list1, const List *list2) -{ - List *result = NIL; - - if (list1 == NIL || list2 == NIL) - return result; - - foreach_node(Const, val1, list1) - { - bool isnull1 = val1->constisnull; - - foreach_node(Const, val2, list2) - { - if (val2->constisnull) - { - if (isnull1) - { - result = lappend(result, val1); - return result; - } - continue; - } - else if (isnull1) - continue; - - /* Compare two datum values. */ - if (DatumGetInt32(FunctionCall2Coll(&partsupfunc[0], - partcollation[0], - val1->constvalue, - val2->constvalue)) == 0) - { - result = lappend(result, val1); - return result; - } - } - } - - return result; -} - -/* - * check_partitions_not_overlap_list - * - * (function for BY LIST partitioning) - * - * This is a helper function for check_partitions_for_split(). - * Checks that the values of the new partitions do not overlap. - * - * parent: partitioned table - * parts: array of SinglePartitionSpec structs with info about split partitions - * nparts: size of array "parts" - */ -static void -check_partitions_not_overlap_list(Relation parent, - SinglePartitionSpec **parts, - int nparts, - ParseState *pstate) -{ - PartitionKey key PG_USED_FOR_ASSERTS_ONLY = RelationGetPartitionKey(parent); - int i, - j; - SinglePartitionSpec *sps1, - *sps2; - List *overlap; - - Assert(key->strategy == PARTITION_STRATEGY_LIST); - - for (i = 0; i < nparts; i++) - { - sps1 = parts[i]; - - for (j = i + 1; j < nparts; j++) - { - sps2 = parts[j]; - - overlap = partitions_listdatum_intersection(&key->partsupfunc[0], - key->partcollation, - sps1->bound->listdatums, - sps2->bound->listdatums); - if (list_length(overlap) > 0) - { - Const *val = (Const *) linitial_node(Const, overlap); - - ereport(ERROR, - errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("new partition \"%s\" would overlap with another new partition \"%s\"", - sps1->name->relname, sps2->name->relname), - parser_errposition(pstate, exprLocation((Node *) val))); - } - } - } -} - -/* - * check_partition_bounds_for_split_range - * - * (function for BY RANGE partitioning) - * - * Checks that bounds of new partition "spec" are inside bounds of split - * partition (with Oid splitPartOid). If first=true (this means that "spec" is - * the first of the new partitions), then the lower bound of "spec" should be - * equal (or greater than or equal in case defaultPart=true) to the lower - * bound of the split partition. If last=true (this means that "spec" is the - * last of the new partitions), then the upper bound of "spec" should be - * equal (or less than or equal in case defaultPart=true) to the upper bound - * of the split partition. - * - * parent: partitioned table - * relname: name of the new partition - * spec: bounds specification of the new partition - * splitPartOid: split partition Oid - * first: true iff the new partition "spec" is the first of the - * new partitions - * last: true iff the new partition "spec" is the last of the - * new partitions - * defaultPart: true iff new partitions contain the DEFAULT partition - * pstate: pointer to ParseState struct to determine error position - */ -static void -check_partition_bounds_for_split_range(Relation parent, - char *relname, - PartitionBoundSpec *spec, - Oid splitPartOid, - bool first, - bool last, - bool defaultPart, - ParseState *pstate) -{ - PartitionKey key = RelationGetPartitionKey(parent); - PartitionRangeBound *lower, - *upper; - int cmpval; - - Assert(key->strategy == PARTITION_STRATEGY_RANGE); - Assert(spec->strategy == PARTITION_STRATEGY_RANGE); - - lower = make_one_partition_rbound(key, -1, spec->lowerdatums, true); - upper = make_one_partition_rbound(key, -1, spec->upperdatums, false); - - /* - * First, check if the resulting range would be empty with the specified - * lower and upper bounds. partition_rbound_cmp cannot return zero here, - * since the lower-bound flags are different. - */ - cmpval = partition_rbound_cmp(key->partnatts, - key->partsupfunc, - key->partcollation, - lower->datums, lower->kind, - true, upper); - Assert(cmpval != 0); - if (cmpval > 0) - { - /* Point to the problematic key in the lower datums list. */ - PartitionRangeDatum *datum = list_nth(spec->lowerdatums, cmpval - 1); - - ereport(ERROR, - errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("empty range bound specified for partition \"%s\"", - relname), - errdetail("Specified lower bound %s is greater than or equal to upper bound %s.", - get_range_partbound_string(spec->lowerdatums), - get_range_partbound_string(spec->upperdatums)), - parser_errposition(pstate, exprLocation((Node *) datum))); - } - - /* - * Need to check first and last partitions (from the set of new - * partitions) - */ - if (first || last) - { - PartitionBoundSpec *split_spec = get_partition_bound_spec(splitPartOid); - PartitionRangeDatum *datum; - - if (first) - { - PartitionRangeBound *split_lower; - - split_lower = make_one_partition_rbound(key, -1, split_spec->lowerdatums, true); - - cmpval = partition_rbound_cmp(key->partnatts, - key->partsupfunc, - key->partcollation, - lower->datums, lower->kind, - true, split_lower); - if (cmpval != 0) - datum = list_nth(spec->lowerdatums, abs(cmpval) - 1); - - /* - * The lower bound of "spec" must equal the lower bound of the - * split partition. However, if one of the new partitions is - * DEFAULT, then it is ok for the new partition's lower bound to - * be greater than that of the split partition. - */ - if (!defaultPart) - { - if (cmpval != 0) - ereport(ERROR, - errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("lower bound of partition \"%s\" is not equal to lower bound of split partition \"%s\"", - relname, - get_rel_name(splitPartOid)), - errhint("%s requires the combined bounds of the new partitions to exactly match the bound of the split partition.", - "ALTER TABLE ... SPLIT PARTITION"), - parser_errposition(pstate, exprLocation((Node *) datum))); - } - else if (cmpval < 0) - ereport(ERROR, - errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("lower bound of partition \"%s\" is less than lower bound of split partition \"%s\"", - relname, - get_rel_name(splitPartOid)), - errhint("Explicit partition bounds must be contained within the bounds of the split partition when a DEFAULT partition is specified."), - parser_errposition(pstate, exprLocation((Node *) datum))); - } - - if (last) - { - PartitionRangeBound *split_upper; - - split_upper = make_one_partition_rbound(key, -1, split_spec->upperdatums, false); - - cmpval = partition_rbound_cmp(key->partnatts, - key->partsupfunc, - key->partcollation, - upper->datums, upper->kind, - false, split_upper); - if (cmpval != 0) - datum = list_nth(spec->upperdatums, abs(cmpval) - 1); - - /* - * The upper bound of "spec" must equal the upper bound of the - * split partition. However, if one of the new partitions is - * DEFAULT, then it is ok for the new partition's upper bound to - * be less than that of the split partition. - */ - if (!defaultPart) - { - if (cmpval != 0) - ereport(ERROR, - errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("upper bound of partition \"%s\" is not equal to upper bound of split partition \"%s\"", - relname, - get_rel_name(splitPartOid)), - errhint("%s requires the combined bounds of the new partitions to exactly match the bound of the split partition.", - "ALTER TABLE ... SPLIT PARTITION"), - parser_errposition(pstate, exprLocation((Node *) datum))); - } - else if (cmpval > 0) - ereport(ERROR, - errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("upper bound of partition \"%s\" is greater than upper bound of split partition \"%s\"", - relname, - get_rel_name(splitPartOid)), - errhint("Explicit partition bounds must be contained within the bounds of the split partition when a DEFAULT partition is specified."), - parser_errposition(pstate, exprLocation((Node *) datum))); - } - } -} - -/* - * check_partition_bounds_for_split_list - * - * (function for BY LIST partitioning) - * - * Checks that the bounds of the new partition are inside the bounds of the - * split partition (with Oid splitPartOid). - * - * parent: partitioned table - * relname: name of the new partition - * spec: bounds specification of the new partition - * splitPartOid: split partition Oid - * pstate: pointer to ParseState struct to determine error position - */ -static void -check_partition_bounds_for_split_list(Relation parent, char *relname, - PartitionBoundSpec *spec, - Oid splitPartOid, - ParseState *pstate) -{ - PartitionKey key = RelationGetPartitionKey(parent); - PartitionDesc partdesc = RelationGetPartitionDesc(parent, false); - PartitionBoundInfo boundinfo = partdesc->boundinfo; - int with = -1; - bool overlap = false; - int overlap_location = -1; - - Assert(key->strategy == PARTITION_STRATEGY_LIST); - Assert(spec->strategy == PARTITION_STRATEGY_LIST); - Assert(boundinfo && boundinfo->strategy == PARTITION_STRATEGY_LIST); - - /* - * Search each value of the new partition "spec" in the existing - * partitions. All of them should be in the split partition (with Oid - * splitPartOid). - */ - foreach_node(Const, val, spec->listdatums) - { - overlap_location = exprLocation((Node *) val); - if (!val->constisnull) - { - int offset; - bool equal; - - offset = partition_list_bsearch(&key->partsupfunc[0], - key->partcollation, - boundinfo, - val->constvalue, - &equal); - if (offset >= 0 && equal) - { - with = boundinfo->indexes[offset]; - if (partdesc->oids[with] != splitPartOid) - { - overlap = true; - break; - } - } - else - ereport(ERROR, - errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("new partition \"%s\" cannot have this value because split partition \"%s\" does not have it", - relname, - get_rel_name(splitPartOid)), - parser_errposition(pstate, overlap_location)); - } - else if (partition_bound_accepts_nulls(boundinfo)) - { - with = boundinfo->null_index; - if (partdesc->oids[with] != splitPartOid) - { - overlap = true; - break; - } - } - else - ereport(ERROR, - errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("new partition \"%s\" cannot have NULL value because split partition \"%s\" does not have it", - relname, - get_rel_name(splitPartOid)), - parser_errposition(pstate, overlap_location)); - } - - if (overlap) - { - Assert(with >= 0); - ereport(ERROR, - errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("new partition \"%s\" would overlap with another (not split) partition \"%s\"", - relname, get_rel_name(partdesc->oids[with])), - parser_errposition(pstate, overlap_location)); - } -} - -/* - * find_value_in_new_partitions_list - * - * (function for BY LIST partitioning) - * - * Function returns true iff any of the new partitions contains the value - * "value". - * - * partsupfunc: information about the comparison function associated with - * the partition key - * partcollation: partitioning collation - * parts: pointer to an array with new partition descriptions - * nparts: number of new partitions - * value: the value that we are looking for - * isnull: true if the value that we are looking for is NULL - */ -static bool -find_value_in_new_partitions_list(FmgrInfo *partsupfunc, - Oid *partcollation, - SinglePartitionSpec **parts, - int nparts, - Datum value, - bool isnull) -{ - for (int i = 0; i < nparts; i++) - { - SinglePartitionSpec *sps = parts[i]; - - foreach_node(Const, val, sps->bound->listdatums) - { - if (isnull && val->constisnull) - return true; - - if (!isnull && !val->constisnull) - { - if (DatumGetInt32(FunctionCall2Coll(&partsupfunc[0], - partcollation[0], - val->constvalue, - value)) == 0) - return true; - } - } - } - return false; -} - -/* - * check_parent_values_in_new_partitions - * - * (function for BY LIST partitioning) - * - * Checks that all values of split partition (with Oid partOid) are contained - * in new partitions. - * - * parent: partitioned table - * partOid: split partition Oid - * parts: pointer to an array with new partition descriptions - * nparts: number of new partitions - * pstate: pointer to ParseState struct to determine error position - */ -static void -check_parent_values_in_new_partitions(Relation parent, - Oid partOid, - SinglePartitionSpec **parts, - int nparts, - ParseState *pstate) -{ - PartitionKey key = RelationGetPartitionKey(parent); - PartitionDesc partdesc = RelationGetPartitionDesc(parent, false); - PartitionBoundInfo boundinfo = partdesc->boundinfo; - int i; - bool found = true; - Datum datum = PointerGetDatum(NULL); - - Assert(key->strategy == PARTITION_STRATEGY_LIST); - - /* - * Special processing for NULL value. Search for a NULL value if the split - * partition (partOid) contains it. - */ - if (partition_bound_accepts_nulls(boundinfo) && - partdesc->oids[boundinfo->null_index] == partOid) - { - if (!find_value_in_new_partitions_list(&key->partsupfunc[0], - key->partcollation, parts, nparts, datum, true)) - found = false; - } - - if (!found) - ereport(ERROR, - errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("new partitions' combined partition bounds do not contain value (%s) but split partition \"%s\" does", - "NULL", - get_rel_name(partOid)), - errhint("%s requires the combined bounds of the new partitions to exactly match the bound of the split partition.", - "ALTER TABLE ... SPLIT PARTITION")); - - /* - * Search all values of split partition with partOid in the PartitionDesc - * of partitioned table. - */ - for (i = 0; i < boundinfo->ndatums; i++) - { - if (partdesc->oids[boundinfo->indexes[i]] == partOid) - { - /* We found the value that the split partition contains. */ - datum = boundinfo->datums[i][0]; - if (!find_value_in_new_partitions_list(&key->partsupfunc[0], - key->partcollation, parts, nparts, datum, false)) - { - found = false; - break; - } - } - } - - if (!found) - { - Const *notFoundVal; - - /* - * Make a Const for getting the string representation of the missing - * value. - */ - notFoundVal = makeConst(key->parttypid[0], - key->parttypmod[0], - key->parttypcoll[0], - key->parttyplen[0], - datum, - false, /* isnull */ - key->parttypbyval[0]); - - ereport(ERROR, - errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("new partitions' combined partition bounds do not contain value (%s) but split partition \"%s\" does", - deparse_expression((Node *) notFoundVal, NIL, false, false), - get_rel_name(partOid)), - errhint("%s requires the combined bounds of the new partitions to exactly match the bound of the split partition.", - "ALTER TABLE ... SPLIT PARTITION")); - } -} - -/* - * split_partition_values_contained_in_new_part - * - * (function for BY LIST partitioning) - * - * Returns true if all values in the LIST bound of the partition being split - * are contained in the specified non-DEFAULT replacement partition's bound. - * - * The caller must already have verified containment in the other direction, - * so this check is sufficient to prove that the two LIST bounds are equal. - */ -static bool -split_partition_values_contained_in_new_part(Relation parent, - Oid splitPartOid, - SinglePartitionSpec *part) -{ - PartitionKey key = RelationGetPartitionKey(parent); - PartitionDesc partdesc = RelationGetPartitionDesc(parent, false); - PartitionBoundInfo boundinfo = partdesc->boundinfo; - SinglePartitionSpec *parts[1]; - Datum datum = PointerGetDatum(NULL); - - Assert(key->strategy == PARTITION_STRATEGY_LIST); - - parts[0] = part; - - /* - * Special processing for NULL value. Search for a NULL value if the - * split partition contains it. - */ - if (partition_bound_accepts_nulls(boundinfo) && - partdesc->oids[boundinfo->null_index] == splitPartOid) - { - if (!find_value_in_new_partitions_list(&key->partsupfunc[0], - key->partcollation, parts, 1, - datum, true)) - return false; - } - - /* - * Search all values of the split partition in the single non-DEFAULT - * replacement partition. - */ - for (int i = 0; i < boundinfo->ndatums; i++) - { - if (partdesc->oids[boundinfo->indexes[i]] == splitPartOid) - { - datum = boundinfo->datums[i][0]; - - if (!find_value_in_new_partitions_list(&key->partsupfunc[0], - key->partcollation, parts, 1, - datum, false)) - return false; - } - } - - return true; -} - -/* - * check_split_partition_not_same_bound - * - * Reject splitting a non-DEFAULT partition into one non-DEFAULT partition - * with the original bound plus a DEFAULT partition. That form does not - * perform a real split; it merely adds a DEFAULT partition to the parent - * table through the split-partition path. Users should use - * CREATE TABLE ... PARTITION OF ... DEFAULT or ALTER TABLE ... ATTACH - * PARTITION ... DEFAULT for that. - * - * Must be called after the per-partition bound validation in - * check_partitions_for_split() so that containment of new bounds within the - * split partition is already established. Given containment, RANGE bounds - * are equal iff their lower and upper rbounds match; LIST bound sets are - * equal iff the split partition's values are also contained in the new - * partition (the containment is then bidirectional). Both checks go - * through the partition operator family (partition_rbound_cmp / - * find_value_in_new_partitions_list) rather than byte equality, so e.g. - * -0.0 and 0.0 -- which have different bit patterns but compare equal - * under float8 -- are correctly recognised as the same bound. - */ -static void -check_split_partition_not_same_bound(Relation parent, - Oid splitPartOid, - SinglePartitionSpec **parts, - int nparts, - ParseState *pstate) -{ - PartitionKey key = RelationGetPartitionKey(parent); - PartitionBoundSpec *new_spec; - PartitionBoundSpec *split_spec; - - if (nparts != 1) - return; - - new_spec = parts[0]->bound; - split_spec = get_partition_bound_spec(splitPartOid); - - Assert(new_spec->strategy == split_spec->strategy); - - if (key->strategy == PARTITION_STRATEGY_RANGE) - { - PartitionRangeBound *new_lower; - PartitionRangeBound *new_upper; - PartitionRangeBound *split_lower; - PartitionRangeBound *split_upper; - - new_lower = make_one_partition_rbound(key, -1, new_spec->lowerdatums, true); - new_upper = make_one_partition_rbound(key, -1, new_spec->upperdatums, false); - split_lower = make_one_partition_rbound(key, -1, split_spec->lowerdatums, true); - split_upper = make_one_partition_rbound(key, -1, split_spec->upperdatums, false); - - if (partition_rbound_cmp(key->partnatts, key->partsupfunc, - key->partcollation, - new_lower->datums, new_lower->kind, true, - split_lower) != 0) - return; - if (partition_rbound_cmp(key->partnatts, key->partsupfunc, - key->partcollation, - new_upper->datums, new_upper->kind, false, - split_upper) != 0) - return; - } - else - { - Assert(key->strategy == PARTITION_STRATEGY_LIST); - - if (!split_partition_values_contained_in_new_part(parent, splitPartOid, - parts[0])) - return; - } - - ereport(ERROR, - errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("cannot split partition \"%s\" only to add a DEFAULT partition", - get_rel_name(splitPartOid)), - errdetail("The non-DEFAULT partition would keep the same partition bound."), - errhint("Use CREATE TABLE ... PARTITION OF ... DEFAULT to add a DEFAULT partition."), - parser_errposition(pstate, parts[0]->name->location)); -} - -/* - * check_partitions_for_split - * - * Checks new partitions for the SPLIT PARTITION command: - * 1. Bounds of new partitions should not overlap with new and existing - * partitions. - * 2. In the case when new or existing partitions contain the DEFAULT - * partition, new partitions can have any bounds inside the split partition - * bound (can be spaces between partition bounds). - * 3. In case new partitions don't contain the DEFAULT partition and the - * partitioned table does not have the DEFAULT partition, the following - * should be true: the sum of the bounds of new partitions should be equal - * to the bound of the split partition. - * - * parent: partitioned table - * splitPartOid: split partition Oid - * partlist: list of new partitions after partition split - * pstate: pointer to ParseState struct for determine error position - */ -void -check_partitions_for_split(Relation parent, - Oid splitPartOid, - List *partlist, - ParseState *pstate) -{ - PartitionKey key; - char strategy; - Oid defaultPartOid; - bool isSplitPartDefault; - bool createDefaultPart = false; - int default_index = -1; - int i; - SinglePartitionSpec **new_parts; - SinglePartitionSpec *spsPrev = NULL; - - /* - * nparts counts the number of split partitions, but it exclude the - * default partition. - */ - int nparts = 0; - - key = RelationGetPartitionKey(parent); - strategy = get_partition_strategy(key); - - defaultPartOid = - get_default_oid_from_partdesc(RelationGetPartitionDesc(parent, true)); - - Assert(strategy == PARTITION_STRATEGY_RANGE || - strategy == PARTITION_STRATEGY_LIST); - - /* - * Make an array new_parts with new partitions except the DEFAULT - * partition. - */ - new_parts = palloc0_array(SinglePartitionSpec *, list_length(partlist)); - - /* isSplitPartDefault flag: is split partition a DEFAULT partition? */ - isSplitPartDefault = (defaultPartOid == splitPartOid); - - foreach_node(SinglePartitionSpec, sps, partlist) - { - if (sps->bound->is_default) - default_index = foreach_current_index(sps); - else - new_parts[nparts++] = sps; - } - - /* An indicator that the DEFAULT partition will be created. */ - if (default_index != -1) - { - createDefaultPart = true; - Assert(nparts == list_length(partlist) - 1); - } - - if (strategy == PARTITION_STRATEGY_RANGE) - { - PartitionRangeBound **lower_bounds; - SinglePartitionSpec **tmp_new_parts; - - /* - * To simplify the check for ranges of new partitions, we need to sort - * all partitions in ascending order of their bounds (we compare the - * lower bound only). - */ - lower_bounds = palloc0_array(PartitionRangeBound *, nparts); - - /* Create an array of lower bounds. */ - for (i = 0; i < nparts; i++) - { - lower_bounds[i] = make_one_partition_rbound(key, i, - new_parts[i]->bound->lowerdatums, true); - } - - /* Sort the array of lower bounds. */ - qsort_arg(lower_bounds, nparts, sizeof(PartitionRangeBound *), - qsort_partition_rbound_cmp, (void *) key); - - /* Reorder the array of partitions. */ - tmp_new_parts = new_parts; - new_parts = palloc0_array(SinglePartitionSpec *, nparts); - for (i = 0; i < nparts; i++) - new_parts[i] = tmp_new_parts[lower_bounds[i]->index]; - - pfree(tmp_new_parts); - pfree(lower_bounds); - } - - for (i = 0; i < nparts; i++) - { - SinglePartitionSpec *sps = new_parts[i]; - - if (isSplitPartDefault) - { - /* - * When the split partition is the DEFAULT partition, we can use - * any free ranges - as when creating a new partition. - */ - check_new_partition_bound(sps->name->relname, parent, sps->bound, - pstate); - } - else - { - /* - * Checks that the bounds of the current partition are inside the - * bounds of the split partition. For range partitioning: checks - * that the upper bound of the previous partition is equal to the - * lower bound of the current partition. For list partitioning: - * checks that the split partition contains all values of the - * current partition. - */ - if (strategy == PARTITION_STRATEGY_RANGE) - { - bool first = (i == 0); - bool last = (i == (nparts - 1)); - - check_partition_bounds_for_split_range(parent, sps->name->relname, sps->bound, - splitPartOid, first, last, - createDefaultPart, pstate); - } - else - check_partition_bounds_for_split_list(parent, sps->name->relname, - sps->bound, splitPartOid, pstate); - } - - /* Ranges of new partitions should not overlap. */ - if (strategy == PARTITION_STRATEGY_RANGE && spsPrev) - check_two_partitions_bounds_range(parent, spsPrev->name, spsPrev->bound, - sps->name, sps->bound, - createDefaultPart, - false, - pstate); - - spsPrev = sps; - } - - if (strategy == PARTITION_STRATEGY_LIST) - { - /* Values of new partitions should not overlap. */ - check_partitions_not_overlap_list(parent, new_parts, nparts, - pstate); - - /* - * Need to check that all values of the split partition are contained - * in the new partitions. Skip this check if the DEFAULT partition - * exists. - */ - if (!createDefaultPart) - check_parent_values_in_new_partitions(parent, splitPartOid, - new_parts, nparts, pstate); - } - - /* - * Reject the degenerate form where the single non-DEFAULT replacement - * partition keeps the bound of the split partition; the command then does - * nothing beyond adding a DEFAULT partition. Containment was established - * by the per-partition validation above, so an equality check is enough. - */ - if (!isSplitPartDefault && createDefaultPart) - check_split_partition_not_same_bound(parent, splitPartOid, new_parts, - nparts, pstate); - - pfree(new_parts); -} diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c index 807d1d3b527..5b914d603bb 100644 --- a/src/bin/psql/tab-complete.in.c +++ b/src/bin/psql/tab-complete.in.c @@ -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 DETACH|SPLIT PARTITION, provide a list of + * If we have ALTER TABLE DETACH PARTITION, provide a list of * partitions of . */ - 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 SPLIT PARTITION */ - else if (Matches("ALTER", "TABLE", MatchAny, "SPLIT", "PARTITION", MatchAny)) - COMPLETE_WITH("INTO ( PARTITION"); - - /* ALTER TABLE 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 OF */ else if (Matches("ALTER", "TABLE", MatchAny, "OF")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_composite_datatypes); diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index 214ef1e7e2d..7da6a3942e1 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -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); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 2fcea826003..f48a0c62d49 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -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 */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 51ead54f015..a12d550ef60 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -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) diff --git a/src/include/partitioning/partbounds.h b/src/include/partitioning/partbounds.h index 25c926f4c8b..d66918f89c1 100644 --- a/src/include/partitioning/partbounds.h +++ b/src/include/partitioning/partbounds.h @@ -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 */ diff --git a/src/test/isolation/expected/partition-merge.out b/src/test/isolation/expected/partition-merge.out deleted file mode 100644 index 5f6472671b9..00000000000 --- a/src/test/isolation/expected/partition-merge.out +++ /dev/null @@ -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; -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; -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; -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; -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; -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; -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; -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; -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; -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; -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; -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) - diff --git a/src/test/isolation/expected/partition-split.out b/src/test/isolation/expected/partition-split.out deleted file mode 100644 index 02a5bb4f1f5..00000000000 --- a/src/test/isolation/expected/partition-split.out +++ /dev/null @@ -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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; -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; -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) - diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule index 6469aafa2e1..1fcf4e63238 100644 --- a/src/test/isolation/isolation_schedule +++ b/src/test/isolation/isolation_schedule @@ -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 diff --git a/src/test/isolation/specs/partition-merge.spec b/src/test/isolation/specs/partition-merge.spec deleted file mode 100644 index f3c5ce2fbf1..00000000000 --- a/src/test/isolation/specs/partition-merge.spec +++ /dev/null @@ -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 diff --git a/src/test/isolation/specs/partition-split.spec b/src/test/isolation/specs/partition-split.spec deleted file mode 100644 index af954be5dc0..00000000000 --- a/src/test/isolation/specs/partition-split.spec +++ /dev/null @@ -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 diff --git a/src/test/modules/test_ddl_deparse/expected/alter_table.out b/src/test/modules/test_ddl_deparse/expected/alter_table.out index 3a2f576f3b6..f1c6f05fe17 100644 --- a/src/test/modules/test_ddl_deparse/expected/alter_table.out +++ b/src/test/modules/test_ddl_deparse/expected/alter_table.out @@ -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 -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 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 diff --git a/src/test/modules/test_ddl_deparse/sql/alter_table.sql b/src/test/modules/test_ddl_deparse/sql/alter_table.sql index 0980097048e..380ba266075 100644 --- a/src/test/modules/test_ddl_deparse/sql/alter_table.sql +++ b/src/test/modules/test_ddl_deparse/sql/alter_table.sql @@ -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 ( diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c index 64a1dfa9f79..105872a3517 100644 --- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c +++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c @@ -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; diff --git a/src/test/modules/test_extensions/expected/test_extdepend.out b/src/test/modules/test_extensions/expected/test_extdepend.out index ede5dc64c04..0b62015d18c 100644 --- a/src/test/modules/test_extensions/expected/test_extdepend.out +++ b/src/test/modules/test_extensions/expected/test_extdepend.out @@ -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; diff --git a/src/test/modules/test_extensions/sql/test_extdepend.sql b/src/test/modules/test_extensions/sql/test_extdepend.sql index ad734af1e71..63240a1af5d 100644 --- a/src/test/modules/test_extensions/sql/test_extdepend.sql +++ b/src/test/modules/test_extensions/sql/test_extdepend.sql @@ -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; diff --git a/src/test/regress/expected/partition_merge.out b/src/test/regress/expected/partition_merge.out deleted file mode 100644 index ccda2b5843b..00000000000 --- a/src/test/regress/expected/partition_merge.out +++ /dev/null @@ -1,1174 +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: partition with name "sales_feb2022" is already used -LINE 1: ...e MERGE PARTITIONS (sales_feb2022, sales_mar2022, sales_feb2... - ^ --- ERROR -ALTER TABLE sales_range MERGE PARTITIONS (sales_feb2022, sales_mar2022, sales_apr2022) INTO sales_feb_mar_apr2022; -ERROR: "sales_apr2022" is not a table -HINT: ALTER TABLE ... MERGE PARTITIONS can only merge partitions that don't have sub-partitions. --- ERROR --- (space between sections sales_jan2022 and sales_mar2022) -ALTER TABLE sales_range MERGE PARTITIONS (sales_jan2022, sales_mar2022) INTO sales_jan_mar2022; -ERROR: cannot merge partition "sales_mar2022" together with partition "sales_jan2022" -DETAIL: The lower bound of partition "sales_mar2022" is not equal to the upper bound of partition "sales_jan2022". -HINT: ALTER TABLE ... MERGE PARTITIONS requires the partition bounds to be adjacent. --- 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: cannot merge partition "sales_jan2022" together with partition "sales_dec2021" -DETAIL: The lower bound of partition "sales_jan2022" is not equal to the upper bound of partition "sales_dec2021". -HINT: ALTER TABLE ... MERGE PARTITIONS requires the partition bounds to be adjacent. --- ERROR -ALTER TABLE sales_range MERGE PARTITIONS (sales_feb2022, sales_mar2022, partitions_merge_schema.sales_feb2022) INTO sales_feb_mar_apr2022; -ERROR: partition with name "sales_feb2022" is already used -LINE 1: ...e MERGE PARTITIONS (sales_feb2022, sales_mar2022, partitions... - ^ --- ERROR -ALTER TABLE sales_range MERGE PARTITIONS (sales_feb2022, sales_mar2022, sales_jan2022) INTO sales_apr_2; -ERROR: relation "sales_apr_2" already exists -CREATE VIEW jan2022v as SELECT * FROM sales_jan2022; -ALTER TABLE sales_range MERGE PARTITIONS (sales_jan2022, sales_feb2022) INTO sales_dec_jan_feb2022; -ERROR: cannot drop table sales_jan2022 because other objects depend on it -DETAIL: view jan2022v depends on table sales_jan2022 -HINT: Use DROP ... CASCADE to drop the dependent objects too. -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}'); - oid | relpersistence | relkind | inhdetachpending | pg_get_expr -------------------------------------------------+----------------+---------+------------------+-------------------------------------------------- - partitions_merge_schema2.sales_jan_feb_mar2022 | p | r | f | FOR VALUES FROM ('01-01-2022') TO ('04-01-2022') - sales_apr2022 | p | p | f | FOR VALUES FROM ('04-01-2022') TO ('05-01-2022') - sales_dec2021 | p | r | f | FOR VALUES FROM ('12-01-2021') TO ('12-31-2021') - sales_others | p | r | f | DEFAULT -(4 rows) - -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); - pg_get_partkeydef --------------------- - RANGE (sales_date) -(1 row) - --- show partitions with conditions: -EXECUTE get_partition_info('{sales_range}'); - oid | relpersistence | relkind | inhdetachpending | pg_get_expr ----------------+----------------+---------+------------------+-------------------------------------------------- - sales_apr2022 | p | r | f | FOR VALUES FROM ('04-01-2022') TO ('05-01-2022') - sales_feb2022 | p | r | f | FOR VALUES FROM ('02-01-2022') TO ('03-01-2022') - sales_jan2022 | p | r | f | FOR VALUES FROM ('01-01-2022') TO ('02-01-2022') - sales_mar2022 | p | r | f | FOR VALUES FROM ('03-01-2022') TO ('04-01-2022') - sales_others | p | r | f | DEFAULT -(5 rows) - --- 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}'); - oid | relpersistence | relkind | inhdetachpending | pg_get_expr -------------------------------------------------+----------------+---------+------------------+-------------------------------------------------- - partitions_merge_schema2.sales_feb_mar_apr2022 | p | r | f | FOR VALUES FROM ('02-01-2022') TO ('05-01-2022') - sales_jan2022 | p | r | f | FOR VALUES FROM ('01-01-2022') TO ('02-01-2022') - sales_others | p | r | f | DEFAULT -(3 rows) - -SELECT * FROM pg_indexes WHERE tablename = 'sales_feb_mar_apr2022' and schemaname = 'partitions_merge_schema2'; - schemaname | tablename | indexname | tablespace | indexdef ---------------------------+-----------------------+--------------------------------------+------------+------------------------------------------------------------------------------------------------------------------------------ - partitions_merge_schema2 | sales_feb_mar_apr2022 | sales_feb_mar_apr2022_sales_date_idx | | CREATE INDEX sales_feb_mar_apr2022_sales_date_idx ON partitions_merge_schema2.sales_feb_mar_apr2022 USING btree (sales_date) -(1 row) - -SELECT tableoid::regclass, * FROM sales_range ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - tableoid | salesperson_id | salesperson_name | sales_amount | sales_date -------------------------------------------------+----------------+------------------+--------------+------------ - partitions_merge_schema2.sales_feb_mar_apr2022 | 2 | Smirnoff | 500 | 02-10-2022 - partitions_merge_schema2.sales_feb_mar_apr2022 | 3 | Ford | 2000 | 04-30-2022 - partitions_merge_schema2.sales_feb_mar_apr2022 | 4 | Ivanov | 750 | 04-13-2022 - partitions_merge_schema2.sales_feb_mar_apr2022 | 5 | Deev | 250 | 04-07-2022 - partitions_merge_schema2.sales_feb_mar_apr2022 | 6 | Poirot | 150 | 02-11-2022 - partitions_merge_schema2.sales_feb_mar_apr2022 | 7 | Li | 175 | 03-08-2022 - partitions_merge_schema2.sales_feb_mar_apr2022 | 8 | Ericsson | 185 | 02-23-2022 - partitions_merge_schema2.sales_feb_mar_apr2022 | 9 | Muller | 250 | 03-11-2022 - partitions_merge_schema2.sales_feb_mar_apr2022 | 11 | Trump | 380 | 04-06-2022 - partitions_merge_schema2.sales_feb_mar_apr2022 | 12 | Plato | 350 | 03-19-2022 - sales_jan2022 | 1 | May | 1000 | 01-31-2022 - sales_jan2022 | 10 | Halder | 350 | 01-28-2022 - sales_jan2022 | 13 | Gandi | 377 | 01-09-2022 - sales_others | 14 | Smith | 510 | 05-04-2022 -(14 rows) - --- 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'; - QUERY PLAN --------------------------------------------------------------------------------- - Index Scan using sales_feb_mar_apr2022_sales_date_idx on sales_feb_mar_apr2022 - Index Cond: (sales_date > '01-01-2022'::date) -(2 rows) - -SELECT * FROM partitions_merge_schema2.sales_feb_mar_apr2022 where sales_date > '2022-01-01'; - salesperson_id | salesperson_name | sales_amount | sales_date -----------------+------------------+--------------+------------ - 2 | Smirnoff | 500 | 02-10-2022 - 6 | Poirot | 150 | 02-11-2022 - 8 | Ericsson | 185 | 02-23-2022 - 7 | Li | 175 | 03-08-2022 - 9 | Muller | 250 | 03-11-2022 - 12 | Plato | 350 | 03-19-2022 - 11 | Trump | 380 | 04-06-2022 - 5 | Deev | 250 | 04-07-2022 - 4 | Ivanov | 750 | 04-13-2022 - 3 | Ford | 2000 | 04-30-2022 -(10 rows) - -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; - salesperson_id | salesperson_name | sales_amount | sales_date -----------------+------------------+--------------+------------ - 1 | May | 1000 | 01-31-2022 - 7 | Li | 175 | 03-08-2022 - 9 | Muller | 250 | 03-11-2022 - 10 | Halder | 350 | 01-28-2022 - 12 | Plato | 350 | 03-19-2022 - 13 | Gandi | 377 | 01-09-2022 - 14 | Smith | 510 | 05-04-2022 -(7 rows) - --- show partitions with conditions: -EXECUTE get_partition_info('{sales_range}'); - oid | relpersistence | relkind | inhdetachpending | pg_get_expr ----------------+----------------+---------+------------------+-------------------------------------------------- - sales_apr2022 | p | r | f | FOR VALUES FROM ('04-01-2022') TO ('05-01-2022') - sales_feb2022 | p | r | f | FOR VALUES FROM ('02-01-2022') TO ('03-01-2022') - sales_others | p | r | f | DEFAULT -(3 rows) - -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; - tableoid | salesperson_name | sales_year | sales_month | sales_day | sales_date | sales_department ----------------+------------------+------------+-------------+-----------+------------+------------------ - sales_dec2022 | Manager1 | 2021 | 12 | 7 | 2021.12.07 | Sales department - sales_dec2022 | Manager2 | 2021 | 12 | 8 | 2021.12.08 | Sales department - sales_jan2022 | Manager3 | 2022 | 1 | 1 | 2022.01.01 | Sales department - sales_jan2022 | Manager2 | 2022 | 1 | 2 | 2022.01.02 | Sales department - sales_feb2022 | Manager1 | 2022 | 2 | 4 | 2022.02.04 | Sales department - sales_feb2022 | Manager3 | 2022 | 2 | 1 | 2022.02.01 | Sales department - sales_other | Manager1 | 2022 | 3 | 3 | 2022.03.03 | Sales department - sales_other | Manager2 | 2022 | 3 | 4 | 2022.03.04 | Sales department - sales_other | Manager3 | 2022 | 5 | 1 | 2022.05.01 | Sales department -(9 rows) - -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; - tableoid | salesperson_name | sales_year | sales_month | sales_day | sales_date | sales_department --------------------+------------------+------------+-------------+-----------+------------+------------------ - sales_dec2022 | Manager1 | 2021 | 12 | 7 | 2021.12.07 | Sales department - sales_dec2022 | Manager2 | 2021 | 12 | 8 | 2021.12.08 | Sales department - sales_jan_feb2022 | Manager3 | 2022 | 1 | 1 | 2022.01.01 | Sales department - sales_jan_feb2022 | Manager2 | 2022 | 1 | 2 | 2022.01.02 | Sales department - sales_jan_feb2022 | Manager1 | 2022 | 2 | 4 | 2022.02.04 | Sales department - sales_jan_feb2022 | Manager3 | 2022 | 2 | 1 | 2022.02.01 | Sales department - sales_jan_feb2022 | Manager1 | 2022 | 1 | 10 | 2022.01.10 | Sales department - sales_jan_feb2022 | Manager2 | 2022 | 2 | 10 | 2022.02.10 | Sales department - sales_other | Manager1 | 2022 | 3 | 3 | 2022.03.03 | Sales department - sales_other | Manager2 | 2022 | 3 | 4 | 2022.03.04 | Sales department - sales_other | Manager3 | 2022 | 5 | 1 | 2022.05.01 | Sales department -(11 rows) - -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'); -NOTICE: trigger(salespeople) called: action = INSERT, when = AFTER, level = ROW -NOTICE: trigger(salespeople) called: action = INSERT, when = AFTER, level = STATEMENT --- 1 trigger should fire here (row): -INSERT INTO salespeople10_20 VALUES (19, 'Ivanov'); -NOTICE: trigger(salespeople) called: action = INSERT, when = AFTER, level = ROW -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'); -NOTICE: trigger(salespeople) called: action = INSERT, when = AFTER, level = ROW -NOTICE: trigger(salespeople) called: action = INSERT, when = AFTER, level = STATEMENT --- 1 trigger should fire here (row): -INSERT INTO salespeople10_40 VALUES (30, 'Ford'); -NOTICE: trigger(salespeople) called: action = INSERT, when = AFTER, level = ROW -SELECT * FROM salespeople01_10; - salesperson_id | salesperson_name -----------------+------------------ - 1 | Poirot -(1 row) - -SELECT * FROM salespeople10_40; - salesperson_id | salesperson_name -----------------+------------------ - 10 | May - 19 | Ivanov - 20 | Smirnoff - 30 | Ford -(4 rows) - -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; - salesperson_id | salesperson_name -----------------+------------------ - 1 | Poirot - 19 | Ivanov - 10 | May - 20 | Smirnoff - 31 | Popov - 30 | Ford -(6 rows) - -select * from salespeople01_10; - salesperson_id | salesperson_name -----------------+------------------ - 1 | Poirot -(1 row) - -select * from salespeople10_40; - salesperson_id | salesperson_name -----------------+------------------ - 19 | Ivanov - 10 | May - 20 | Smirnoff - 31 | Popov - 30 | Ford -(5 rows) - -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; - tableoid | salesperson_id | salesperson_name | sales_amount | sales_date ----------------------+----------------+------------------+--------------+------------ - sales_apr2022_01_10 | 5 | Deev | 250 | 04-07-2022 - sales_apr2022_01_10 | 11 | Trump | 380 | 04-06-2022 - sales_apr2022_10_20 | 4 | Ivanov | 750 | 04-13-2022 - sales_apr2022_20_30 | 3 | Ford | 2000 | 04-30-2022 -(4 rows) - -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; - tableoid | salesperson_id | salesperson_name | sales_amount | sales_date ----------------+----------------+------------------+--------------+------------ - sales_apr_all | 3 | Ford | 2000 | 04-30-2022 - sales_apr_all | 4 | Ivanov | 750 | 04-13-2022 - sales_apr_all | 5 | Deev | 250 | 04-07-2022 - sales_apr_all | 11 | Trump | 380 | 04-06-2022 -(4 rows) - -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: "sales_external" is not a partition of partitioned table "sales_list" -HINT: ALTER TABLE ... MERGE PARTITIONS can only merge partitions that don't have sub-partitions. --- ERROR -ALTER TABLE sales_list MERGE PARTITIONS (sales_west, sales_east, sales_external2) INTO sales_all; -ERROR: "sales_external2" is not a partition of partitioned table "sales_list" -HINT: ALTER TABLE ... MERGE PARTITIONS can only merge partitions that don't have sub-partitions. --- ERROR -ALTER TABLE sales_list MERGE PARTITIONS (sales_west, sales_nord2, sales_east) INTO sales_all; -ERROR: relation "sales_nord2" is not a partition of relation "sales_list" -HINT: ALTER TABLE ... MERGE PARTITIONS can only merge partitions that don't have sub-partitions. -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}'); - oid | relpersistence | relkind | inhdetachpending | pg_get_expr ----------------+----------------+---------+------------------+------------------------------------------------------ - sales_central | p | r | f | FOR VALUES IN ('Warsaw', 'Berlin', 'Kyiv') - sales_east | p | r | f | FOR VALUES IN ('Beijing', 'Delhi', 'Vladivostok') - sales_nord | p | r | f | FOR VALUES IN ('Oslo', 'St. Petersburg', 'Helsinki') - sales_west | p | r | f | FOR VALUES IN ('Lisbon', 'New York', 'Madrid') - sales_others | p | r | f | DEFAULT -(5 rows) - -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}'); - oid | relpersistence | relkind | inhdetachpending | pg_get_expr ---------------+----------------+---------+------------------+--------------------------------------------------------------------------------------------------------------- - sales_all | p | r | f | FOR VALUES IN ('Lisbon', 'New York', 'Madrid', 'Beijing', 'Delhi', 'Vladivostok', 'Warsaw', 'Berlin', 'Kyiv') - sales_nord | p | r | f | FOR VALUES IN ('Oslo', 'St. Petersburg', 'Helsinki') - sales_others | p | r | f | DEFAULT -(3 rows) - -SELECT tableoid::regclass, * FROM sales_list ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - tableoid | salesperson_id | salesperson_name | sales_state | sales_amount | sales_date -------------+----------------+------------------+----------------+--------------+------------ - sales_all | 1 | Trump | Beijing | 1000 | 03-01-2022 - sales_all | 2 | Smirnoff | New York | 500 | 03-03-2022 - sales_all | 4 | Ivanov | Warsaw | 750 | 03-04-2022 - sales_all | 5 | Deev | Lisbon | 250 | 03-07-2022 - sales_all | 6 | Poirot | Berlin | 1000 | 03-01-2022 - sales_all | 8 | Li | Vladivostok | 1150 | 03-09-2022 - sales_all | 11 | Muller | Madrid | 650 | 03-05-2022 - sales_all | 12 | Smith | Kyiv | 350 | 03-10-2022 - sales_all | 13 | Gandi | Warsaw | 150 | 03-08-2022 - sales_all | 14 | Plato | Lisbon | 950 | 03-05-2022 - sales_nord | 3 | Ford | St. Petersburg | 2000 | 03-05-2022 - sales_nord | 7 | May | Helsinki | 1200 | 03-06-2022 - sales_nord | 9 | May | Helsinki | 1200 | 03-11-2022 - sales_nord | 10 | Halder | Oslo | 800 | 03-02-2022 -(14 rows) - --- Use indexscan for testing indexes after merging partitions -SET enable_seqscan = OFF; -EXPLAIN (COSTS OFF) SELECT * FROM sales_all WHERE sales_state = 'Warsaw'; - QUERY PLAN ---------------------------------------------------------- - Index Scan using sales_all_sales_state_idx on sales_all - Index Cond: ((sales_state)::text = 'Warsaw'::text) -(2 rows) - -SELECT * FROM sales_all WHERE sales_state = 'Warsaw'; - salesperson_id | salesperson_name | sales_state | sales_amount | sales_date -----------------+------------------+-------------+--------------+------------ - 4 | Ivanov | Warsaw | 750 | 03-04-2022 - 13 | Gandi | Warsaw | 150 | 03-08-2022 -(2 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM sales_list WHERE sales_state = 'Warsaw'; - QUERY PLAN --------------------------------------------------------------------- - Index Scan using sales_all_sales_state_idx on sales_all sales_list - Index Cond: ((sales_state)::text = 'Warsaw'::text) -(2 rows) - -SELECT * FROM sales_list WHERE sales_state = 'Warsaw'; - salesperson_id | salesperson_name | sales_state | sales_amount | sales_date -----------------+------------------+-------------+--------------+------------ - 4 | Ivanov | Warsaw | 750 | 03-04-2022 - 13 | Gandi | Warsaw | 150 | 03-08-2022 -(2 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM sales_list WHERE salesperson_name = 'Ivanov'; - QUERY PLAN ---------------------------------------------------------------------------------- - Append - -> Index Scan using sales_all_salesperson_name_idx on sales_all sales_list_1 - Index Cond: ((salesperson_name)::text = 'Ivanov'::text) - -> Bitmap Heap Scan on sales_nord sales_list_2 - Recheck Cond: ((salesperson_name)::text = 'Ivanov'::text) - -> Bitmap Index Scan on sales_nord_salesperson_name_idx - Index Cond: ((salesperson_name)::text = 'Ivanov'::text) - -> Bitmap Heap Scan on sales_others sales_list_3 - Recheck Cond: ((salesperson_name)::text = 'Ivanov'::text) - -> Bitmap Index Scan on sales_others_salesperson_name_idx - Index Cond: ((salesperson_name)::text = 'Ivanov'::text) -(11 rows) - -SELECT * FROM sales_list WHERE salesperson_name = 'Ivanov'; - salesperson_id | salesperson_name | sales_state | sales_amount | sales_date -----------------+------------------+-------------+--------------+------------ - 4 | Ivanov | Warsaw | 750 | 03-04-2022 -(1 row) - -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: relation "t1p1" is not a partition of relation "t2" -HINT: ALTER TABLE ... MERGE PARTITIONS can only merge partitions that don't have sub-partitions. --- ERROR -ALTER TABLE t2 MERGE PARTITIONS (t2pa, t3) INTO t2p; -ERROR: "t3" is not a partition of partitioned table "t2" -HINT: ALTER TABLE ... MERGE PARTITIONS can only merge partitions that don't have sub-partitions. -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 - Table "partitions_merge_schema.tp_1_2" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------+---------+-----------+----------+---------+---------+--------------+------------- - i | integer | | not null | | plain | | -Partition of: t FOR VALUES FROM (0) TO (2) -Partition constraint: ((i IS NOT NULL) AND (i >= 0) AND (i < 2)) -Indexes: - "tp_1_2_pkey" PRIMARY KEY, btree (i) - "tp_1_2_i_idx" btree (i) -Not-null constraints: - "t_i_not_null" NOT NULL "i" (inherited) - -DROP TABLE t; --- --- Try to MERGE partitions of temporary table. --- -BEGIN; -SHOW search_path; - search_path ---------------------------------- - partitions_merge_schema, public -(1 row) - -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}'); - oid | relpersistence | relkind | inhdetachpending | pg_get_expr ---------+----------------+---------+------------------+---------------------------- - tp_0_3 | t | r | f | FOR VALUES FROM (0) TO (3) - tp_3_4 | t | r | f | FOR VALUES FROM (3) TO (4) -(2 rows) - --- ERROR -ALTER TABLE t MERGE PARTITIONS (tp_0_3, tp_3_4) INTO tp_0_4; -ERROR: cannot create a permanent relation as partition of temporary relation "t" -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; - oid | relpersistence ------+---------------- - t | p -(1 row) - -EXECUTE get_partition_info('{t}'); - oid | relpersistence | relkind | inhdetachpending | pg_get_expr ---------+----------------+---------+------------------+---------------------------- - tp_0_1 | p | r | f | FOR VALUES FROM (0) TO (1) - tp_1_2 | p | r | f | FOR VALUES FROM (1) TO (2) -(2 rows) - -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; -ERROR: cannot create a temporary relation as partition of permanent relation "t" -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; -ERROR: cannot create a temporary relation as partition of permanent relation "t" -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; - oid | relpersistence ------+---------------- - t | t -(1 row) - -EXECUTE get_partition_info('{t}'); - oid | relpersistence | relkind | inhdetachpending | pg_get_expr ---------+----------------+---------+------------------+---------------------------- - tp_0_1 | t | r | f | FOR VALUES FROM (0) TO (1) - tp_1_2 | t | r | f | FOR VALUES FROM (1) TO (2) -(2 rows) - -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; -ERROR: cannot create a permanent relation as partition of temporary relation "t" -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"; - tablename | tablespace ------------+------------------ - t | regress_tblspace - tp_0_2 | regress_tblspace -(2 rows) - -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"; - tablename | indexname | tablespace ------------+-------------+------------------ - t | t_pkey | regress_tblspace - tp_0_2 | tp_0_2_pkey | regress_tblspace -(2 rows) - -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"; - relname | amname ----------+----------------------- - t | partitions_merge_heap - tp_0_2 | partitions_merge_heap -(2 rows) - -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; -ERROR: must be owner of table t -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; -ERROR: must be owner of table tp_0_1 -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; -ERROR: must be owner of table tp_1_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 - List of tables - Schema | Name | Type | Owner --------------------------+--------+-------+------------------------------- - partitions_merge_schema | tp_0_1 | table | regress_partition_merge_alice -(1 row) - --- Owner is 'regress_partition_merge_bob': -\dt tp_1_2 - List of tables - Schema | Name | Type | Owner --------------------------+--------+-------+----------------------------- - partitions_merge_schema | tp_1_2 | table | regress_partition_merge_bob -(1 row) - --- ERROR -ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2; -ERROR: partitions being merged have different owners -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: partition of hash-partitioned table cannot be merged --- ERROR -ALTER TABLE t MERGE PARTITIONS (tp1) INTO tp3; -ERROR: list of partitions to be merged should include at least two partitions -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 - Table "partitions_merge_schema.tp_0_1" - Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description ---------+---------+-----------+----------+-------------------------------------------------+---------+-------------+--------------+------------- - i | integer | | not null | | plain | | | tp_0_1.i - t | text | | | 'default_tp_0_1'::text | main | | | - b | bigint | | not null | | plain | | | - d | date | | | generated always as ('02-02-2022'::date) stored | plain | | | -Partition of: t FOR VALUES FROM (0) TO (1) -Partition constraint: ((abs(i) IS NOT NULL) AND (abs(i) >= 0) AND (abs(i) < 1)) -Check constraints: - "t_b_check" CHECK (b > 0) - "t_b_check1" CHECK (b > 0) NOT ENFORCED - "t_b_check2" CHECK (b > 0) NOT VALID -Statistics objects: - "partitions_merge_schema.tp_0_1_stat" (dependencies) ON i, b FROM tp_0_1 -Not-null constraints: - "tp_0_1_i_not_null" NOT NULL "i" (inherited) - "t_b_nn" NOT NULL "b" (inherited) NOT VALID -Triggers: - t_before_insert_row_trigger BEFORE INSERT ON tp_0_1 FOR EACH ROW EXECUTE FUNCTION trigger_function('t'), ON TABLE t - tp_0_1_before_insert_row_trigger BEFORE INSERT ON tp_0_1 FOR EACH ROW EXECUTE FUNCTION trigger_function('tp_0_1') - -ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_1; -\d+ tp_0_1 - Table "partitions_merge_schema.tp_0_1" - Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description ---------+---------+-----------+----------+-------------------------------------------------+----------+-------------+--------------+------------- - i | integer | | not null | | plain | | | - t | text | | | 'default_t'::text | extended | pglz | | - b | bigint | | not null | | plain | | | - d | date | | | generated always as ('01-01-2022'::date) stored | plain | | | -Partition of: t FOR VALUES FROM (0) TO (2) -Partition constraint: ((abs(i) IS NOT NULL) AND (abs(i) >= 0) AND (abs(i) < 2)) -Check constraints: - "t_b_check" CHECK (b > 0) - "t_b_check1" CHECK (b > 0) NOT ENFORCED - "t_b_check2" CHECK (b > 0) NOT VALID -Not-null constraints: - "t_i_not_null" NOT NULL "i" (inherited) - "t_b_nn" NOT NULL "b" (inherited) NOT VALID -Triggers: - t_before_insert_row_trigger BEFORE INSERT ON tp_0_1 FOR EACH ROW EXECUTE FUNCTION trigger_function('t'), ON TABLE t - -INSERT INTO t(i, t, b) VALUES(1, DEFAULT, 3); -NOTICE: trigger(t) called: action = INSERT, when = BEFORE, level = ROW -SELECT tableoid::regclass, * FROM t ORDER BY b; - tableoid | i | t | b | d -----------+---+----------------+---+------------ - tp_0_1 | 0 | default_tp_0_1 | 1 | 01-01-2022 - tp_0_1 | 1 | default_tp_1_2 | 2 | 01-01-2022 - tp_0_1 | 1 | default_t | 3 | 01-01-2022 -(3 rows) - -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 - Table "partitions_merge_schema.tp_0_2" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - i | integer | | not null | -Partition of: t FOR VALUES FROM (0) TO (2) -Indexes: - "tp_0_2_pkey" PRIMARY KEY, btree (i) -Referenced by: - TABLE "t_fk" CONSTRAINT "t_fk_i_fkey" FOREIGN KEY (i) REFERENCES t(i) NOT VALID - --- ERROR -ALTER TABLE t_fk VALIDATE CONSTRAINT t_fk_i_fkey; -ERROR: insert or update on table "t_fk" violates foreign key constraint "t_fk_i_fkey" -DETAIL: Key (i)=(2) is not present in table "t". -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 - Table "partitions_merge_schema.tp_0_2" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - i | integer | | not null | -Partition of: t FOR VALUES FROM (0) TO (2) -Indexes: - "tp_0_2_pkey" PRIMARY KEY, btree (i) -Referenced by: - TABLE "t_fk" CONSTRAINT "t_fk_i_fkey" FOREIGN KEY (i) REFERENCES t(i) NOT ENFORCED - --- ERROR -ALTER TABLE t_fk ALTER CONSTRAINT t_fk_i_fkey ENFORCED; -ERROR: insert or update on table "t_fk" violates foreign key constraint "t_fk_i_fkey" -DETAIL: Key (i)=(2) is not present in table "t". -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); - count -------- - 0 -(1 row) - --- "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); - count -------- - 1 -(1 row) - -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); -ERROR: new row for relation "tp_12" violates check constraint "t_i_check" -DETAIL: Failing row contains (0, virtual). --- Should be 3 rows: (5), (15), (16): -SELECT i FROM t ORDER BY i; - i ----- - 5 - 15 - 16 -(3 rows) - --- 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); - count -------- - 1 -(1 row) - -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'; - has_toast | toast_used ------------+------------ - t | t -(1 row) - -SELECT length(a) FROM t; - length --------- - 10000 -(1 row) - -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'; - spcname ------------------- - regress_tblspace -(1 row) - -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'; - spcname ------------------- - regress_tblspace -(1 row) - -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 -ERROR: only shared relations can be placed in pg_global tablespace -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'; - reltablespace ---------------- - 0 -(1 row) - -DROP TABLE t; -RESET search_path; --- -DROP SCHEMA partitions_merge_schema; -DROP SCHEMA partitions_merge_schema2; diff --git a/src/test/regress/expected/partition_split.out b/src/test/regress/expected/partition_split.out deleted file mode 100644 index 089f89ed6ac..00000000000 --- a/src/test/regress/expected/partition_split.out +++ /dev/null @@ -1,1758 +0,0 @@ --- --- PARTITION_SPLIT --- Tests for "ALTER TABLE ... SPLIT PARTITION ..." command --- -CREATE SCHEMA partition_split_schema; -CREATE SCHEMA partition_split_schema2; -SET search_path = partition_split_schema, public; --- --- BY RANGE partitioning --- --- --- Test for error codes --- -CREATE TABLE sales_range (salesperson_id 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_feb_mar_apr2022 PARTITION OF sales_range FOR VALUES FROM ('2022-02-01') TO ('2022-05-01'); -CREATE TABLE sales_others PARTITION OF sales_range DEFAULT; --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_xxx INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); -ERROR: relation "sales_xxx" does not exist --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_jan2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); -ERROR: relation "sales_jan2022" already exists --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_jan2022 FOR VALUES IN ('2022-05-01', '2022-06-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); -ERROR: invalid bound specification for a range partition -LINE 2: (PARTITION sales_jan2022 FOR VALUES IN ('2022-05-01', '202... - ^ --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-02-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); -ERROR: empty range bound specified for partition "sales_mar2022" -LINE 3: PARTITION sales_mar2022 FOR VALUES FROM ('2022-03-01') TO... - ^ -DETAIL: Specified lower bound ('03-01-2022') is greater than or equal to upper bound ('02-01-2022'). --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-10-01')); -ERROR: list of new partitions must contain at least two partitions --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-01-01') TO ('2022-03-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); -ERROR: lower bound of partition "sales_feb2022" is not equal to lower bound of split partition "sales_feb_mar_apr2022" -LINE 2: (PARTITION sales_feb2022 FOR VALUES FROM ('2022-01-01') TO... - ^ -HINT: ALTER TABLE ... SPLIT PARTITION requires the combined bounds of the new partitions to exactly match the bound of the split partition. --- ERROR --- (We can create partition with the same name as split partition, but can't create two partitions with the same name) -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_feb_mar_apr2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_feb_mar_apr2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); -ERROR: partition with name "sales_feb_mar_apr2022" is already used -LINE 3: PARTITION sales_feb_mar_apr2022 FOR VALUES FROM ('2022-03... - ^ --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_feb2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); -ERROR: partition with name "sales_feb2022" is already used -LINE 3: PARTITION sales_feb2022 FOR VALUES FROM ('2022-03-01') TO... - ^ --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION partition_split_schema.sales_feb2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); -ERROR: partition with name "sales_feb2022" is already used -LINE 3: PARTITION partition_split_schema.sales_feb2022 FOR VALUES... - ^ --- ERROR -ALTER TABLE sales_feb_mar_apr2022 SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_jan2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_feb2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); -ERROR: ALTER action SPLIT PARTITION cannot be performed on relation "sales_feb_mar_apr2022" -DETAIL: This operation is not supported for tables. --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-06-01')); -ERROR: upper bound of partition "sales_apr2022" is not equal to upper bound of split partition "sales_feb_mar_apr2022" -LINE 4: ... sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-06-0... - ^ -HINT: ALTER TABLE ... SPLIT PARTITION requires the combined bounds of the new partitions to exactly match the bound of the split partition. --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-02-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); -ERROR: cannot split to partition "sales_mar2022" together with partition "sales_feb2022" -LINE 3: PARTITION sales_mar2022 FOR VALUES FROM ('2022-02-01') TO... - ^ -DETAIL: The lower bound of partition "sales_mar2022" is not equal to the upper bound of partition "sales_feb2022". -HINT: ALTER TABLE ... SPLIT PARTITION requires the partition bounds to be adjacent. --- Tests for spaces between partitions, them should be executed without DEFAULT partition -ALTER TABLE sales_range DETACH PARTITION sales_others; --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-02') TO ('2022-03-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); -ERROR: lower bound of partition "sales_feb2022" is not equal to lower bound of split partition "sales_feb_mar_apr2022" -LINE 2: (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-02') TO... - ^ -HINT: ALTER TABLE ... SPLIT PARTITION requires the combined bounds of the new partitions to exactly match the bound of the split partition. --- Check the source partition not in the search path -SET search_path = partition_split_schema2, public; -ALTER TABLE partition_split_schema.sales_range -SPLIT PARTITION partition_split_schema.sales_feb_mar_apr2022 INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); -SET search_path = partition_split_schema, public; -\d+ sales_range - Partitioned table "partition_split_schema.sales_range" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description -----------------+---------+-----------+----------+---------+---------+--------------+------------- - salesperson_id | integer | | | | plain | | - sales_date | date | | | | plain | | -Partition key: RANGE (sales_date) -Partitions: - partition_split_schema2.sales_apr2022 FOR VALUES FROM ('04-01-2022') TO ('05-01-2022') - partition_split_schema2.sales_feb2022 FOR VALUES FROM ('02-01-2022') TO ('03-01-2022') - partition_split_schema2.sales_mar2022 FOR VALUES FROM ('03-01-2022') TO ('04-01-2022') - sales_jan2022 FOR VALUES FROM ('01-01-2022') TO ('02-01-2022') - -DROP TABLE sales_range; -DROP TABLE sales_others; --- Additional tests for error messages, no default partition -CREATE TABLE sales_range (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_feb_mar_apr2022 PARTITION OF sales_range FOR VALUES FROM ('2022-02-01') TO ('2022-05-01'); --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-06-01')); -ERROR: upper bound of partition "sales_apr2022" is not equal to upper bound of split partition "sales_feb_mar_apr2022" -LINE 4: ... sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-06-0... - ^ -HINT: ALTER TABLE ... SPLIT PARTITION requires the combined bounds of the new partitions to exactly match the bound of the split partition. -DROP TABLE sales_range; --- --- Add rows into partitioned table then split 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_feb_mar_apr2022 PARTITION OF sales_range FOR VALUES FROM ('2022-02-01') TO ('2022-05-01'); -CREATE TABLE sales_others PARTITION OF sales_range DEFAULT; -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'); -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); -SELECT tableoid::regclass, * FROM sales_range ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - tableoid | salesperson_id | salesperson_name | sales_amount | sales_date ----------------+----------------+------------------+--------------+------------ - sales_apr2022 | 3 | Ford | 2000 | 04-30-2022 - sales_apr2022 | 4 | Ivanov | 750 | 04-13-2022 - sales_apr2022 | 5 | Deev | 250 | 04-07-2022 - sales_apr2022 | 11 | Trump | 380 | 04-06-2022 - sales_feb2022 | 2 | Smirnoff | 500 | 02-10-2022 - sales_feb2022 | 6 | Poirot | 150 | 02-11-2022 - sales_feb2022 | 8 | Ericsson | 185 | 02-23-2022 - sales_jan2022 | 1 | May | 1000 | 01-31-2022 - sales_jan2022 | 10 | Halder | 350 | 01-28-2022 - sales_jan2022 | 13 | Gandi | 377 | 01-09-2022 - sales_mar2022 | 7 | Li | 175 | 03-08-2022 - sales_mar2022 | 9 | Muller | 250 | 03-11-2022 - sales_mar2022 | 12 | Plato | 350 | 03-19-2022 - sales_others | 14 | Smith | 510 | 05-04-2022 -(14 rows) - -DROP TABLE sales_range CASCADE; --- --- Add split partition, then add rows into partitioned table --- -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_feb_mar_apr2022 PARTITION OF sales_range FOR VALUES FROM ('2022-02-01') TO ('2022-05-01'); -CREATE TABLE sales_others PARTITION OF sales_range DEFAULT; -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'); --- Split partition, also check schema qualification of new partitions -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION partition_split_schema.sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION partition_split_schema2.sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); -\d+ sales_range - Partitioned table "partition_split_schema.sales_range" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description -------------------+-----------------------+-----------+----------+---------+----------+--------------+------------- - salesperson_id | integer | | | | plain | | - salesperson_name | character varying(30) | | | | extended | | - sales_amount | integer | | | | plain | | - sales_date | date | | | | plain | | -Partition key: RANGE (sales_date) -Partitions: - partition_split_schema2.sales_mar2022 FOR VALUES FROM ('03-01-2022') TO ('04-01-2022') - sales_apr2022 FOR VALUES FROM ('04-01-2022') TO ('05-01-2022') - sales_feb2022 FOR VALUES FROM ('02-01-2022') TO ('03-01-2022') - sales_jan2022 FOR VALUES FROM ('01-01-2022') TO ('02-01-2022') - sales_others DEFAULT - -SELECT tableoid::regclass, * FROM sales_range ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - tableoid | salesperson_id | salesperson_name | sales_amount | sales_date ----------------------------------------+----------------+------------------+--------------+------------ - partition_split_schema2.sales_mar2022 | 7 | Li | 175 | 03-08-2022 - partition_split_schema2.sales_mar2022 | 9 | Muller | 250 | 03-11-2022 - partition_split_schema2.sales_mar2022 | 12 | Plato | 350 | 03-19-2022 - sales_apr2022 | 3 | Ford | 2000 | 04-30-2022 - sales_apr2022 | 4 | Ivanov | 750 | 04-13-2022 - sales_apr2022 | 5 | Deev | 250 | 04-07-2022 - sales_apr2022 | 11 | Trump | 380 | 04-06-2022 - sales_feb2022 | 2 | Smirnoff | 500 | 02-10-2022 - sales_feb2022 | 6 | Poirot | 150 | 02-11-2022 - sales_feb2022 | 8 | Ericsson | 185 | 02-23-2022 - sales_jan2022 | 1 | May | 1000 | 01-31-2022 - sales_jan2022 | 10 | Halder | 350 | 01-28-2022 - sales_jan2022 | 13 | Gandi | 377 | 01-09-2022 - sales_others | 14 | Smith | 510 | 05-04-2022 -(14 rows) - -DROP TABLE sales_range CASCADE; --- --- 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_dec2021 PARTITION OF sales_date FOR VALUES FROM (2021, 12, 1) TO (2022, 1, 1); -CREATE TABLE sales_jan_feb2022 PARTITION OF sales_date FOR VALUES FROM (2022, 1, 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 ORDER BY tableoid::regclass::text COLLATE "C", sales_year, sales_month, sales_day; - tableoid | salesperson_name | sales_year | sales_month | sales_day | sales_date | sales_department --------------------+------------------+------------+-------------+-----------+------------+------------------ - sales_dec2021 | Manager1 | 2021 | 12 | 7 | 2021.12.07 | Sales department - sales_dec2021 | Manager2 | 2021 | 12 | 8 | 2021.12.08 | Sales department - sales_jan_feb2022 | Manager3 | 2022 | 1 | 1 | 2022.01.01 | Sales department - sales_jan_feb2022 | Manager2 | 2022 | 1 | 2 | 2022.01.02 | Sales department - sales_jan_feb2022 | Manager3 | 2022 | 2 | 1 | 2022.02.01 | Sales department - sales_jan_feb2022 | Manager1 | 2022 | 2 | 4 | 2022.02.04 | Sales department - sales_other | Manager1 | 2022 | 3 | 3 | 2022.03.03 | Sales department - sales_other | Manager2 | 2022 | 3 | 4 | 2022.03.04 | Sales department - sales_other | Manager3 | 2022 | 5 | 1 | 2022.05.01 | Sales department -(9 rows) - -ALTER TABLE sales_date SPLIT PARTITION sales_jan_feb2022 INTO - (PARTITION sales_jan2022 FOR VALUES FROM (2022, 1, 1) TO (2022, 2, 1), - PARTITION sales_feb2022 FOR VALUES FROM (2022, 2, 1) TO (2022, 3, 1)); -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 ORDER BY tableoid::regclass::text COLLATE "C", sales_year, sales_month, sales_day; - tableoid | salesperson_name | sales_year | sales_month | sales_day | sales_date | sales_department ----------------+------------------+------------+-------------+-----------+------------+------------------ - sales_dec2021 | Manager1 | 2021 | 12 | 7 | 2021.12.07 | Sales department - sales_dec2021 | Manager2 | 2021 | 12 | 8 | 2021.12.08 | Sales department - sales_feb2022 | Manager3 | 2022 | 2 | 1 | 2022.02.01 | Sales department - sales_feb2022 | Manager1 | 2022 | 2 | 4 | 2022.02.04 | Sales department - sales_feb2022 | Manager2 | 2022 | 2 | 10 | 2022.02.10 | Sales department - sales_jan2022 | Manager3 | 2022 | 1 | 1 | 2022.01.01 | Sales department - sales_jan2022 | Manager2 | 2022 | 1 | 2 | 2022.01.02 | Sales department - sales_jan2022 | Manager1 | 2022 | 1 | 10 | 2022.01.10 | Sales department - sales_other | Manager1 | 2022 | 3 | 3 | 2022.03.03 | Sales department - sales_other | Manager2 | 2022 | 3 | 4 | 2022.03.04 | Sales department - sales_other | Manager3 | 2022 | 5 | 1 | 2022.05.01 | Sales department -(11 rows) - -DROP TABLE sales_date CASCADE; --- --- Test: split DEFAULT partition; use an index on partition key; check index after split --- -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_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 * FROM sales_others; - salesperson_id | salesperson_name | sales_amount | sales_date -----------------+------------------+--------------+------------ - 2 | Smirnoff | 500 | 02-10-2022 - 3 | Ford | 2000 | 04-30-2022 - 4 | Ivanov | 750 | 04-13-2022 - 5 | Deev | 250 | 04-07-2022 - 6 | Poirot | 150 | 02-11-2022 - 7 | Li | 175 | 03-08-2022 - 8 | Ericsson | 185 | 02-23-2022 - 9 | Muller | 250 | 03-11-2022 - 11 | Trump | 380 | 04-06-2022 - 12 | Plato | 350 | 03-19-2022 - 14 | Smith | 510 | 05-04-2022 -(11 rows) - -SELECT * FROM pg_indexes WHERE tablename = 'sales_others' and schemaname = 'partition_split_schema' ORDER BY indexname COLLATE "C"; - schemaname | tablename | indexname | tablespace | indexdef -------------------------+--------------+-----------------------------+------------+---------------------------------------------------------------------------------------------------------- - partition_split_schema | sales_others | sales_others_sales_date_idx | | CREATE INDEX sales_others_sales_date_idx ON partition_split_schema.sales_others USING btree (sales_date) -(1 row) - -ALTER TABLE sales_range SPLIT PARTITION sales_others INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01'), - PARTITION sales_others DEFAULT); --- Use indexscan for testing indexes -SET enable_seqscan = OFF; -EXPLAIN (COSTS OFF) SELECT * FROM sales_feb2022 where sales_date > '2022-01-01'; - QUERY PLAN ----------------------------------------------------------------- - Index Scan using sales_feb2022_sales_date_idx on sales_feb2022 - Index Cond: (sales_date > '01-01-2022'::date) -(2 rows) - -SELECT * FROM sales_feb2022 where sales_date > '2022-01-01'; - salesperson_id | salesperson_name | sales_amount | sales_date -----------------+------------------+--------------+------------ - 2 | Smirnoff | 500 | 02-10-2022 - 6 | Poirot | 150 | 02-11-2022 - 8 | Ericsson | 185 | 02-23-2022 -(3 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM sales_mar2022 where sales_date > '2022-01-01'; - QUERY PLAN ----------------------------------------------------------------- - Index Scan using sales_mar2022_sales_date_idx on sales_mar2022 - Index Cond: (sales_date > '01-01-2022'::date) -(2 rows) - -SELECT * FROM sales_mar2022 where sales_date > '2022-01-01'; - salesperson_id | salesperson_name | sales_amount | sales_date -----------------+------------------+--------------+------------ - 7 | Li | 175 | 03-08-2022 - 9 | Muller | 250 | 03-11-2022 - 12 | Plato | 350 | 03-19-2022 -(3 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM sales_apr2022 where sales_date > '2022-01-01'; - QUERY PLAN ----------------------------------------------------------------- - Index Scan using sales_apr2022_sales_date_idx on sales_apr2022 - Index Cond: (sales_date > '01-01-2022'::date) -(2 rows) - -SELECT * FROM sales_apr2022 where sales_date > '2022-01-01'; - salesperson_id | salesperson_name | sales_amount | sales_date -----------------+------------------+--------------+------------ - 11 | Trump | 380 | 04-06-2022 - 5 | Deev | 250 | 04-07-2022 - 4 | Ivanov | 750 | 04-13-2022 - 3 | Ford | 2000 | 04-30-2022 -(4 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM sales_others where sales_date > '2022-01-01'; - QUERY PLAN ---------------------------------------------------------------- - Index Scan using sales_others_sales_date_idx1 on sales_others - Index Cond: (sales_date > '01-01-2022'::date) -(2 rows) - -SELECT * FROM sales_others where sales_date > '2022-01-01'; - salesperson_id | salesperson_name | sales_amount | sales_date -----------------+------------------+--------------+------------ - 14 | Smith | 510 | 05-04-2022 -(1 row) - -RESET enable_seqscan; -SELECT * FROM pg_indexes -WHERE tablename in ('sales_feb2022', 'sales_mar2022', 'sales_apr2022', 'sales_others') -AND schemaname = 'partition_split_schema' -ORDER BY indexname COLLATE "C"; - schemaname | tablename | indexname | tablespace | indexdef -------------------------+---------------+------------------------------+------------+------------------------------------------------------------------------------------------------------------ - partition_split_schema | sales_apr2022 | sales_apr2022_sales_date_idx | | CREATE INDEX sales_apr2022_sales_date_idx ON partition_split_schema.sales_apr2022 USING btree (sales_date) - partition_split_schema | sales_feb2022 | sales_feb2022_sales_date_idx | | CREATE INDEX sales_feb2022_sales_date_idx ON partition_split_schema.sales_feb2022 USING btree (sales_date) - partition_split_schema | sales_mar2022 | sales_mar2022_sales_date_idx | | CREATE INDEX sales_mar2022_sales_date_idx ON partition_split_schema.sales_mar2022 USING btree (sales_date) - partition_split_schema | sales_others | sales_others_sales_date_idx1 | | CREATE INDEX sales_others_sales_date_idx1 ON partition_split_schema.sales_others USING btree (sales_date) -(4 rows) - -DROP TABLE sales_range CASCADE; --- --- Test: some cases for splitting DEFAULT partition (different bounds) --- -CREATE TABLE sales_range (salesperson_id INT, sales_date date) PARTITION BY RANGE (sales_date); -CREATE TABLE sales_others PARTITION OF sales_range DEFAULT; --- sales_error intersects with sales_dec2021 (lower bound) --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_others INTO - (PARTITION sales_dec2021 FOR VALUES FROM ('2021-12-01') TO ('2022-01-01'), - PARTITION sales_error FOR VALUES FROM ('2021-12-30') TO ('2022-02-01'), - PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_others DEFAULT); -ERROR: cannot split to partition "sales_error" together with partition "sales_dec2021" -LINE 3: PARTITION sales_error FOR VALUES FROM ('2021-12-30') TO (... - ^ -DETAIL: The lower bound of partition "sales_error" is not equal to the upper bound of partition "sales_dec2021". -HINT: ALTER TABLE ... SPLIT PARTITION requires the partition bounds to be adjacent. --- sales_error intersects with sales_feb2022 (upper bound) --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_others INTO - (PARTITION sales_dec2021 FOR VALUES FROM ('2021-12-01') TO ('2022-01-01'), - PARTITION sales_error FOR VALUES FROM ('2022-01-01') TO ('2022-02-02'), - PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_others DEFAULT); -ERROR: cannot split to partition "sales_feb2022" together with partition "sales_error" -LINE 4: PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO... - ^ -DETAIL: The lower bound of partition "sales_feb2022" is not equal to the upper bound of partition "sales_error". -HINT: ALTER TABLE ... SPLIT PARTITION requires the partition bounds to be adjacent. --- sales_error intersects with sales_dec2021 (inside bound) --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_others INTO - (PARTITION sales_dec2021 FOR VALUES FROM ('2021-12-01') TO ('2022-01-01'), - PARTITION sales_error FOR VALUES FROM ('2021-12-10') TO ('2021-12-20'), - PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_others DEFAULT); -ERROR: cannot split to partition "sales_error" together with partition "sales_dec2021" -LINE 3: PARTITION sales_error FOR VALUES FROM ('2021-12-10') TO (... - ^ -DETAIL: The lower bound of partition "sales_error" is not equal to the upper bound of partition "sales_dec2021". -HINT: ALTER TABLE ... SPLIT PARTITION requires the partition bounds to be adjacent. --- sales_error intersects with sales_dec2021 (exactly the same bounds) --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_others INTO - (PARTITION sales_dec2021 FOR VALUES FROM ('2021-12-01') TO ('2022-01-01'), - PARTITION sales_error FOR VALUES FROM ('2021-12-01') TO ('2022-01-01'), - PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_others DEFAULT); -ERROR: cannot split to partition "sales_error" together with partition "sales_dec2021" -LINE 3: PARTITION sales_error FOR VALUES FROM ('2021-12-01') TO (... - ^ -DETAIL: The lower bound of partition "sales_error" is not equal to the upper bound of partition "sales_dec2021". -HINT: ALTER TABLE ... SPLIT PARTITION requires the partition bounds to be adjacent. --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_others INTO - (PARTITION sales_dec2021 FOR VALUES FROM ('2021-12-01') TO ('2022-01-01'), - PARTITION sales_jan2022 FOR VALUES FROM ('2022-01-01') TO ('2022-02-01'), - PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01')); -ERROR: cannot split DEFAULT partition "sales_others" -HINT: To split a DEFAULT partition, one of the new partitions must be DEFAULT. --- no error: bounds of sales_noerror are between sales_dec2021 and sales_feb2022 -ALTER TABLE sales_range SPLIT PARTITION sales_others INTO - (PARTITION sales_dec2021 FOR VALUES FROM ('2021-12-01') TO ('2022-01-01'), - PARTITION sales_noerror FOR VALUES FROM ('2022-01-10') TO ('2022-01-20'), - PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_others DEFAULT); -DROP TABLE sales_range; -CREATE TABLE sales_range (sales_date date) PARTITION BY RANGE (sales_date); -CREATE TABLE sales_others PARTITION OF sales_range DEFAULT; --- no error: bounds of sales_noerror are equal to lower and upper bounds of sales_dec2021 and sales_feb2022 -ALTER TABLE sales_range SPLIT PARTITION sales_others INTO - (PARTITION sales_dec2021 FOR VALUES FROM ('2021-12-01') TO ('2022-01-01'), - PARTITION sales_noerror FOR VALUES FROM ('2022-01-01') TO ('2022-02-01'), - PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_others DEFAULT); -DROP TABLE sales_range; --- --- Test: split partition with CHECK and FOREIGN KEY CONSTRAINTs on partitioned table --- -CREATE TABLE salespeople(salesperson_id INT PRIMARY KEY, salesperson_name VARCHAR(30)); -INSERT INTO salespeople VALUES (1, 'Poirot'); -CREATE TABLE sales_range ( -salesperson_id INT REFERENCES salespeople(salesperson_id), -sales_amount INT CHECK (sales_amount > 1), -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_feb_mar_apr2022 PARTITION OF sales_range FOR VALUES FROM ('2022-02-01') TO ('2022-05-01'); -CREATE TABLE sales_others PARTITION OF sales_range DEFAULT; -SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'sales_feb_mar_apr2022'::regclass::oid ORDER BY conname COLLATE "C"; - pg_get_constraintdef | conname | conkey ----------------------------------------------------------------------+---------------------------------+-------- - CHECK ((sales_amount > 1)) | sales_range_sales_amount_check | {2} - FOREIGN KEY (salesperson_id) REFERENCES salespeople(salesperson_id) | sales_range_salesperson_id_fkey | {1} -(2 rows) - -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); --- We should see the same CONSTRAINTs as on sales_feb_mar_apr2022 partition -SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'sales_feb2022'::regclass::oid ORDER BY conname COLLATE "C"; - pg_get_constraintdef | conname | conkey ----------------------------------------------------------------------+---------------------------------+-------- - CHECK ((sales_amount > 1)) | sales_range_sales_amount_check | {2} - FOREIGN KEY (salesperson_id) REFERENCES salespeople(salesperson_id) | sales_range_salesperson_id_fkey | {1} -(2 rows) - -SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'sales_mar2022'::regclass::oid ORDER BY conname COLLATE "C"; - pg_get_constraintdef | conname | conkey ----------------------------------------------------------------------+---------------------------------+-------- - CHECK ((sales_amount > 1)) | sales_range_sales_amount_check | {2} - FOREIGN KEY (salesperson_id) REFERENCES salespeople(salesperson_id) | sales_range_salesperson_id_fkey | {1} -(2 rows) - -SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'sales_apr2022'::regclass::oid ORDER BY conname COLLATE "C"; - pg_get_constraintdef | conname | conkey ----------------------------------------------------------------------+---------------------------------+-------- - CHECK ((sales_amount > 1)) | sales_range_sales_amount_check | {2} - FOREIGN KEY (salesperson_id) REFERENCES salespeople(salesperson_id) | sales_range_salesperson_id_fkey | {1} -(2 rows) - --- ERROR -INSERT INTO sales_range VALUES (1, 0, '2022-03-11'); -ERROR: new row for relation "sales_mar2022" violates check constraint "sales_range_sales_amount_check" -DETAIL: Failing row contains (1, 0, 03-11-2022). --- ERROR -INSERT INTO sales_range VALUES (-1, 10, '2022-03-11'); -ERROR: insert or update on table "sales_mar2022" violates foreign key constraint "sales_range_salesperson_id_fkey" -DETAIL: Key (salesperson_id)=(-1) is not present in table "salespeople". --- ok -INSERT INTO sales_range VALUES (1, 10, '2022-03-11'); -DROP TABLE sales_range CASCADE; -DROP TABLE salespeople CASCADE; --- --- Test: split partition on partitioned table in case of existing FOREIGN KEY reference from another table --- -CREATE TABLE salespeople(salesperson_id INT PRIMARY KEY, salesperson_name VARCHAR(30)) PARTITION BY RANGE (salesperson_id); -CREATE TABLE sales (salesperson_id INT REFERENCES salespeople(salesperson_id), sales_amount INT, sales_date DATE); -CREATE TABLE salespeople01_10 PARTITION OF salespeople FOR VALUES FROM (1) TO (10); -CREATE TABLE salespeople10_40 PARTITION OF salespeople FOR VALUES FROM (10) TO (40); -INSERT INTO salespeople VALUES - (1, 'Poirot'), - (10, 'May'), - (19, 'Ivanov'), - (20, 'Smirnoff'), - (30, 'Ford'); -INSERT INTO sales VALUES - (1, 100, '2022-03-01'), - (1, 110, '2022-03-02'), - (10, 150, '2022-03-01'), - (10, 90, '2022-03-03'), - (19, 200, '2022-03-04'), - (20, 50, '2022-03-12'), - (20, 170, '2022-03-02'), - (30, 30, '2022-03-04'); -SELECT tableoid::regclass, * FROM salespeople ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - tableoid | salesperson_id | salesperson_name -------------------+----------------+------------------ - salespeople01_10 | 1 | Poirot - salespeople10_40 | 10 | May - salespeople10_40 | 19 | Ivanov - salespeople10_40 | 20 | Smirnoff - salespeople10_40 | 30 | Ford -(5 rows) - -ALTER TABLE salespeople SPLIT PARTITION salespeople10_40 INTO - (PARTITION salespeople10_20 FOR VALUES FROM (10) TO (20), - PARTITION salespeople20_30 FOR VALUES FROM (20) TO (30), - PARTITION salespeople30_40 FOR VALUES FROM (30) TO (40)); -SELECT tableoid::regclass, * FROM salespeople ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - tableoid | salesperson_id | salesperson_name -------------------+----------------+------------------ - salespeople01_10 | 1 | Poirot - salespeople10_20 | 10 | May - salespeople10_20 | 19 | Ivanov - salespeople20_30 | 20 | Smirnoff - salespeople30_40 | 30 | Ford -(5 rows) - --- ERROR -INSERT INTO sales VALUES (40, 50, '2022-03-04'); -ERROR: insert or update on table "sales" violates foreign key constraint "sales_salesperson_id_fkey" -DETAIL: Key (salesperson_id)=(40) is not present in table "salespeople". --- ok -INSERT INTO sales VALUES (30, 50, '2022-03-04'); -DROP TABLE sales CASCADE; -DROP TABLE salespeople CASCADE; --- --- Test: split partition 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_40 PARTITION OF salespeople FOR VALUES FROM (10) 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'); -NOTICE: trigger(salespeople) called: action = INSERT, when = AFTER, level = ROW -NOTICE: trigger(salespeople) called: action = INSERT, when = AFTER, level = STATEMENT --- 1 trigger should fire here (row): -INSERT INTO salespeople10_40 VALUES (19, 'Ivanov'); -NOTICE: trigger(salespeople) called: action = INSERT, when = AFTER, level = ROW -ALTER TABLE salespeople SPLIT PARTITION salespeople10_40 INTO - (PARTITION salespeople10_20 FOR VALUES FROM (10) TO (20), - PARTITION salespeople20_30 FOR VALUES FROM (20) TO (30), - PARTITION salespeople30_40 FOR VALUES FROM (30) TO (40)); --- 2 triggers should fire here (row + statement): -INSERT INTO salespeople VALUES (20, 'Smirnoff'); -NOTICE: trigger(salespeople) called: action = INSERT, when = AFTER, level = ROW -NOTICE: trigger(salespeople) called: action = INSERT, when = AFTER, level = STATEMENT --- 1 trigger should fire here (row): -INSERT INTO salespeople30_40 VALUES (30, 'Ford'); -NOTICE: trigger(salespeople) called: action = INSERT, when = AFTER, level = ROW -SELECT tableoid::regclass, * FROM salespeople ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - tableoid | salesperson_id | salesperson_name -------------------+----------------+------------------ - salespeople01_10 | 1 | Poirot - salespeople10_20 | 10 | May - salespeople10_20 | 19 | Ivanov - salespeople20_30 | 20 | Smirnoff - salespeople30_40 | 30 | Ford -(5 rows) - -DROP TABLE salespeople CASCADE; -DROP FUNCTION after_insert_row_trigger(); --- --- Test: split partition witch identity column --- If split partition column is identity column, columns of new partitions are identity columns too. --- -CREATE TABLE salespeople(salesperson_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, salesperson_name VARCHAR(30)) PARTITION BY RANGE (salesperson_id); -CREATE TABLE salespeople1_2 PARTITION OF salespeople FOR VALUES FROM (1) TO (2); --- Create new partition with identity column: -CREATE TABLE salespeople2_5(salesperson_id INT NOT NULL, salesperson_name VARCHAR(30)); -ALTER TABLE salespeople ATTACH PARTITION salespeople2_5 FOR VALUES FROM (2) TO (5); -INSERT INTO salespeople (salesperson_name) VALUES ('Poirot'), ('Ivanov'); -ALTER TABLE salespeople SPLIT PARTITION salespeople2_5 INTO - (PARTITION salespeople2_3 FOR VALUES FROM (2) TO (3), - PARTITION salespeople3_4 FOR VALUES FROM (3) TO (4), - PARTITION salespeople4_5 FOR VALUES FROM (4) TO (5)); -INSERT INTO salespeople (salesperson_name) VALUES ('May'), ('Ford'); -SELECT tableoid::regclass, * FROM salespeople ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - tableoid | salesperson_id | salesperson_name -----------------+----------------+------------------ - salespeople1_2 | 1 | Poirot - salespeople2_3 | 2 | Ivanov - salespeople3_4 | 3 | May - salespeople4_5 | 4 | Ford -(4 rows) - --- check new partitions have identity or not after split partition -SELECT attrelid::regclass, attname, attidentity, attgenerated FROM pg_attribute -WHERE attnum > 0 -AND attrelid::regclass IN ( - 'salespeople2_3'::regclass, 'salespeople', 'salespeople2_3', - 'salespeople1_2', 'salespeople3_4', 'salespeople4_5') -ORDER BY attrelid::regclass::text COLLATE "C", attnum; - attrelid | attname | attidentity | attgenerated -----------------+------------------+-------------+-------------- - salespeople | salesperson_id | a | - salespeople | salesperson_name | | - salespeople1_2 | salesperson_id | a | - salespeople1_2 | salesperson_name | | - salespeople2_3 | salesperson_id | a | - salespeople2_3 | salesperson_name | | - salespeople3_4 | salesperson_id | a | - salespeople3_4 | salesperson_name | | - salespeople4_5 | salesperson_id | a | - salespeople4_5 | salesperson_name | | -(10 rows) - -DROP TABLE salespeople CASCADE; --- --- Test: split partition 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 new partition with some deleted columns: -CREATE TABLE salespeople10_40(d1 VARCHAR(30), salesperson_id INT PRIMARY KEY, d2 INT, d3 DATE, salesperson_name VARCHAR(30)); -INSERT INTO salespeople10_40 VALUES - ('dummy value 1', 19, 100, now(), 'Ivanov'), - ('dummy value 2', 20, 101, now(), 'Smirnoff'); -ALTER TABLE salespeople10_40 DROP COLUMN d1; -ALTER TABLE salespeople10_40 DROP COLUMN d2; -ALTER TABLE salespeople10_40 DROP COLUMN d3; -ALTER TABLE salespeople ATTACH PARTITION salespeople10_40 FOR VALUES FROM (10) TO (40); -INSERT INTO salespeople VALUES - (1, 'Poirot'), - (10, 'May'), - (30, 'Ford'); -ALTER TABLE salespeople SPLIT PARTITION salespeople10_40 INTO - (PARTITION salespeople10_20 FOR VALUES FROM (10) TO (20), - PARTITION salespeople20_30 FOR VALUES FROM (20) TO (30), - PARTITION salespeople30_40 FOR VALUES FROM (30) TO (40)); -SELECT tableoid::regclass, * FROM salespeople ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - tableoid | salesperson_id | salesperson_name -------------------+----------------+------------------ - salespeople01_10 | 1 | Poirot - salespeople10_20 | 10 | May - salespeople10_20 | 19 | Ivanov - salespeople20_30 | 20 | Smirnoff - salespeople30_40 | 30 | Ford -(5 rows) - -DROP TABLE salespeople CASCADE; --- --- Test: split sub-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 (salesperson_id INT, salesperson_name VARCHAR(30), sales_amount INT, sales_date DATE) PARTITION BY RANGE (sales_date); -CREATE TABLE sales_apr_all PARTITION OF sales_apr2022 FOR VALUES FROM ('2022-04-01') 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_range ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - tableoid | salesperson_id | salesperson_name | sales_amount | sales_date ----------------+----------------+------------------+--------------+------------ - sales_apr_all | 3 | Ford | 2000 | 04-30-2022 - sales_apr_all | 4 | Ivanov | 750 | 04-13-2022 - sales_apr_all | 5 | Deev | 250 | 04-07-2022 - sales_apr_all | 11 | Trump | 380 | 04-06-2022 - sales_feb2022 | 2 | Smirnoff | 500 | 02-10-2022 - sales_feb2022 | 6 | Poirot | 150 | 02-11-2022 - sales_feb2022 | 8 | Ericsson | 185 | 02-23-2022 - sales_jan2022 | 1 | May | 1000 | 01-31-2022 - sales_jan2022 | 10 | Halder | 350 | 01-28-2022 - sales_jan2022 | 13 | Gandi | 377 | 01-09-2022 - sales_mar2022 | 7 | Li | 175 | 03-08-2022 - sales_mar2022 | 9 | Muller | 250 | 03-11-2022 - sales_mar2022 | 12 | Plato | 350 | 03-19-2022 - sales_others | 14 | Smith | 510 | 05-04-2022 -(14 rows) - -ALTER TABLE sales_apr2022 SPLIT PARTITION sales_apr_all INTO - (PARTITION sales_apr2022_01_10 FOR VALUES FROM ('2022-04-01') TO ('2022-04-10'), - PARTITION sales_apr2022_10_20 FOR VALUES FROM ('2022-04-10') TO ('2022-04-20'), - PARTITION sales_apr2022_20_30 FOR VALUES FROM ('2022-04-20') TO ('2022-05-01')); -SELECT tableoid::regclass, * FROM sales_range ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - tableoid | salesperson_id | salesperson_name | sales_amount | sales_date ----------------------+----------------+------------------+--------------+------------ - sales_apr2022_01_10 | 5 | Deev | 250 | 04-07-2022 - sales_apr2022_01_10 | 11 | Trump | 380 | 04-06-2022 - sales_apr2022_10_20 | 4 | Ivanov | 750 | 04-13-2022 - sales_apr2022_20_30 | 3 | Ford | 2000 | 04-30-2022 - sales_feb2022 | 2 | Smirnoff | 500 | 02-10-2022 - sales_feb2022 | 6 | Poirot | 150 | 02-11-2022 - sales_feb2022 | 8 | Ericsson | 185 | 02-23-2022 - sales_jan2022 | 1 | May | 1000 | 01-31-2022 - sales_jan2022 | 10 | Halder | 350 | 01-28-2022 - sales_jan2022 | 13 | Gandi | 377 | 01-09-2022 - sales_mar2022 | 7 | Li | 175 | 03-08-2022 - sales_mar2022 | 9 | Muller | 250 | 03-11-2022 - sales_mar2022 | 12 | Plato | 350 | 03-19-2022 - sales_others | 14 | Smith | 510 | 05-04-2022 -(14 rows) - -DROP TABLE sales_range; --- --- BY LIST partitioning --- --- --- Test: specific errors for BY LIST partitioning --- -CREATE TABLE sales_list (sales_state VARCHAR(20)) PARTITION BY LIST (sales_state); -CREATE TABLE sales_nord PARTITION OF sales_list FOR VALUES IN ('Oslo', 'St. Petersburg', 'Helsinki'); -CREATE TABLE sales_all PARTITION OF sales_list FOR VALUES IN ('Warsaw', 'Lisbon', 'New York', 'Madrid', 'Beijing', 'Berlin', 'Delhi', 'Kyiv', 'Vladivostok'); -CREATE TABLE sales_others PARTITION OF sales_list DEFAULT; --- ERROR -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', 'Helsinki'), - PARTITION sales_central FOR VALUES IN ('Warsaw', 'Berlin', 'Kyiv')); -ERROR: new partition "sales_east" would overlap with another (not split) partition "sales_nord" -LINE 3: ...FOR VALUES IN ('Beijing', 'Delhi', 'Vladivostok', 'Helsinki'... - ^ --- ERROR -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', 'Lisbon', 'Kyiv')); -ERROR: new partition "sales_west" would overlap with another new partition "sales_central" -LINE 2: (PARTITION sales_west FOR VALUES IN ('Lisbon', 'New York',... - ^ --- ERROR -ALTER TABLE sales_list SPLIT PARTITION sales_all INTO - (PARTITION sales_west FOR VALUES IN ('Lisbon', 'New York', 'Madrid', NULL), - PARTITION sales_east FOR VALUES IN ('Beijing', 'Delhi', 'Vladivostok'), - PARTITION sales_central FOR VALUES IN ('Warsaw', 'Berlin', 'Kyiv')); -ERROR: new partition "sales_west" cannot have NULL value because split partition "sales_all" does not have it -LINE 2: ...s_west FOR VALUES IN ('Lisbon', 'New York', 'Madrid', NULL), - ^ --- ERROR -ALTER TABLE sales_list SPLIT PARTITION sales_all INTO - (PARTITION sales_west FOR VALUES IN ('Lisbon', 'New York', 'Madrid', 'Melbourne'), - PARTITION sales_east FOR VALUES IN ('Beijing', 'Delhi', 'Vladivostok'), - PARTITION sales_central FOR VALUES IN ('Warsaw', 'Berlin', 'Kyiv')); -ERROR: new partition "sales_west" cannot have this value because split partition "sales_all" does not have it -LINE 2: ...st FOR VALUES IN ('Lisbon', 'New York', 'Madrid', 'Melbourne... - ^ --- ERROR -ALTER TABLE sales_list SPLIT PARTITION sales_all INTO - (PARTITION sales_west FOR VALUES IN ('Lisbon', 'New York', 'Madrid', 'Melbourne'), - PARTITION sales_east FOR VALUES IN ('Beijing', 'Delhi', 'Vladivostok'), - PARTITION sales_central FOR VALUES IN ('Warsaw', 'Berlin', 'Kyiv'), - PARTITION sales_others2 DEFAULT); -ERROR: cannot split non-DEFAULT partition "sales_all" -LINE 5: PARTITION sales_others2 DEFAULT); - ^ -DETAIL: New partition cannot be DEFAULT because DEFAULT partition "sales_others" already exists. -DROP TABLE sales_list; --- Test for non-symbolic comparison of values (numeric values '0' and '0.0' are equal). -CREATE TABLE t (a numeric) PARTITION BY LIST (a); -CREATE TABLE t1 PARTITION OF t FOR VALUES in ('0', '1'); --- ERROR -ALTER TABLE t SPLIT PARTITION t1 INTO - (PARTITION x FOR VALUES IN ('0'), - PARTITION x1 FOR VALUES IN ('0.0', '1')); -ERROR: new partition "x" would overlap with another new partition "x1" -LINE 2: (PARTITION x FOR VALUES IN ('0'), - ^ -DROP TABLE t; --- --- Test: two specific errors for BY LIST partitioning: --- * new partitions do not have NULL value, which split partition has. --- * new partitions do not have a value that split partition has. --- -CREATE TABLE sales_list(sales_state VARCHAR(20)) PARTITION BY LIST (sales_state); -CREATE TABLE sales_nord PARTITION OF sales_list FOR VALUES IN ('Helsinki', 'St. Petersburg', 'Oslo'); -CREATE TABLE sales_all PARTITION OF sales_list FOR VALUES IN ('Warsaw', 'Lisbon', 'New York', 'Madrid', 'Beijing', 'Berlin', 'Delhi', 'Kyiv', 'Vladivostok', NULL); --- ERROR -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')); -ERROR: new partitions' combined partition bounds do not contain value (NULL) but split partition "sales_all" does -HINT: ALTER TABLE ... SPLIT PARTITION requires the combined bounds of the new partitions to exactly match the bound of the split partition. --- ERROR -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', NULL)); -ERROR: new partitions' combined partition bounds do not contain value ('Kyiv'::character varying(20)) but split partition "sales_all" does -HINT: ALTER TABLE ... SPLIT PARTITION requires the combined bounds of the new partitions to exactly match the bound of the split partition. --- ERROR -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'), - PARTITION sales_others DEFAULT, - PARTITION sales_others2 DEFAULT); -ERROR: cannot specify more than one DEFAULT partition -LINE 6: PARTITION sales_others2 DEFAULT); - ^ -DROP TABLE sales_list; --- --- Test: BY LIST partitioning, SPLIT PARTITION with data --- -CREATE TABLE sales_list -(salesperson_id SERIAL, - 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 ('Helsinki', 'St. Petersburg', 'Oslo'); -CREATE TABLE sales_all PARTITION OF sales_list FOR VALUES IN ('Warsaw', 'Lisbon', 'New York', 'Madrid', 'Beijing', 'Berlin', 'Delhi', 'Kyiv', 'Vladivostok'); -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', 'Oslo', 1200, '2022-03-06'), - ('Li', 'Vladivostok', 1150, '2022-03-09'), - ('May', 'Oslo', 1200, '2022-03-11'), - ('Halder', 'Helsinki', 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'); -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')); -SELECT tableoid::regclass, * FROM sales_list ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - tableoid | salesperson_id | salesperson_name | sales_state | sales_amount | sales_date ----------------+----------------+------------------+----------------+--------------+------------ - sales_central | 4 | Ivanov | Warsaw | 750 | 03-04-2022 - sales_central | 6 | Poirot | Berlin | 1000 | 03-01-2022 - sales_central | 12 | Smith | Kyiv | 350 | 03-10-2022 - sales_central | 13 | Gandi | Warsaw | 150 | 03-08-2022 - sales_east | 1 | Trump | Beijing | 1000 | 03-01-2022 - sales_east | 8 | Li | Vladivostok | 1150 | 03-09-2022 - sales_nord | 3 | Ford | St. Petersburg | 2000 | 03-05-2022 - sales_nord | 7 | May | Oslo | 1200 | 03-06-2022 - sales_nord | 9 | May | Oslo | 1200 | 03-11-2022 - sales_nord | 10 | Halder | Helsinki | 800 | 03-02-2022 - sales_west | 2 | Smirnoff | New York | 500 | 03-03-2022 - sales_west | 5 | Deev | Lisbon | 250 | 03-07-2022 - sales_west | 11 | Muller | Madrid | 650 | 03-05-2022 - sales_west | 14 | Plato | Lisbon | 950 | 03-05-2022 -(14 rows) - --- Use indexscan for testing indexes after splitting partition -SET enable_seqscan = OFF; -EXPLAIN (COSTS OFF) SELECT * FROM sales_central WHERE sales_state = 'Warsaw'; - QUERY PLAN ------------------------------------------------------------------ - Index Scan using sales_central_sales_state_idx on sales_central - Index Cond: ((sales_state)::text = 'Warsaw'::text) -(2 rows) - -SELECT * FROM sales_central WHERE sales_state = 'Warsaw'; - salesperson_id | salesperson_name | sales_state | sales_amount | sales_date -----------------+------------------+-------------+--------------+------------ - 4 | Ivanov | Warsaw | 750 | 03-04-2022 - 13 | Gandi | Warsaw | 150 | 03-08-2022 -(2 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM sales_list WHERE sales_state = 'Warsaw'; - QUERY PLAN ----------------------------------------------------------------------------- - Index Scan using sales_central_sales_state_idx on sales_central sales_list - Index Cond: ((sales_state)::text = 'Warsaw'::text) -(2 rows) - -SELECT * FROM sales_list WHERE sales_state = 'Warsaw'; - salesperson_id | salesperson_name | sales_state | sales_amount | sales_date -----------------+------------------+-------------+--------------+------------ - 4 | Ivanov | Warsaw | 750 | 03-04-2022 - 13 | Gandi | Warsaw | 150 | 03-08-2022 -(2 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM sales_list WHERE salesperson_name = 'Ivanov'; - QUERY PLAN ------------------------------------------------------------------------------------------ - Append - -> Index Scan using sales_east_salesperson_name_idx on sales_east sales_list_1 - Index Cond: ((salesperson_name)::text = 'Ivanov'::text) - -> Index Scan using sales_central_salesperson_name_idx on sales_central sales_list_2 - Index Cond: ((salesperson_name)::text = 'Ivanov'::text) - -> Bitmap Heap Scan on sales_nord sales_list_3 - Recheck Cond: ((salesperson_name)::text = 'Ivanov'::text) - -> Bitmap Index Scan on sales_nord_salesperson_name_idx - Index Cond: ((salesperson_name)::text = 'Ivanov'::text) - -> Index Scan using sales_west_salesperson_name_idx on sales_west sales_list_4 - Index Cond: ((salesperson_name)::text = 'Ivanov'::text) - -> Bitmap Heap Scan on sales_others sales_list_5 - Recheck Cond: ((salesperson_name)::text = 'Ivanov'::text) - -> Bitmap Index Scan on sales_others_salesperson_name_idx - Index Cond: ((salesperson_name)::text = 'Ivanov'::text) -(15 rows) - -SELECT * FROM sales_list WHERE salesperson_name = 'Ivanov'; - salesperson_id | salesperson_name | sales_state | sales_amount | sales_date -----------------+------------------+-------------+--------------+------------ - 4 | Ivanov | Warsaw | 750 | 03-04-2022 -(1 row) - -RESET enable_seqscan; -DROP TABLE sales_list; --- --- Test for: --- * split DEFAULT partition to partitions with spaces between bounds; --- * random order of partitions in SPLIT PARTITION command. --- -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_others PARTITION OF sales_range DEFAULT; -INSERT INTO sales_range VALUES - (1, 'May', 1000, '2022-01-31'), - (2, 'Smirnoff', 500, '2022-02-09'), - (3, 'Ford', 2000, '2022-04-30'), - (4, 'Ivanov', 750, '2022-04-13'), - (5, 'Deev', 250, '2022-04-07'), - (6, 'Poirot', 150, '2022-02-07'), - (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'); -ALTER TABLE sales_range SPLIT PARTITION sales_others INTO - (PARTITION sales_others DEFAULT, - PARTITION sales_mar2022_1decade FOR VALUES FROM ('2022-03-01') TO ('2022-03-10'), - PARTITION sales_jan2022_1decade FOR VALUES FROM ('2022-01-01') TO ('2022-01-10'), - PARTITION sales_feb2022_1decade FOR VALUES FROM ('2022-02-01') TO ('2022-02-10'), - PARTITION sales_apr2022_1decade FOR VALUES FROM ('2022-04-01') TO ('2022-04-10')); -SELECT tableoid::regclass, * FROM sales_range ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - tableoid | salesperson_id | salesperson_name | sales_amount | sales_date ------------------------+----------------+------------------+--------------+------------ - sales_apr2022_1decade | 5 | Deev | 250 | 04-07-2022 - sales_apr2022_1decade | 11 | Trump | 380 | 04-06-2022 - sales_feb2022_1decade | 2 | Smirnoff | 500 | 02-09-2022 - sales_feb2022_1decade | 6 | Poirot | 150 | 02-07-2022 - sales_jan2022_1decade | 13 | Gandi | 377 | 01-09-2022 - sales_mar2022_1decade | 7 | Li | 175 | 03-08-2022 - sales_others | 1 | May | 1000 | 01-31-2022 - sales_others | 3 | Ford | 2000 | 04-30-2022 - sales_others | 4 | Ivanov | 750 | 04-13-2022 - sales_others | 8 | Ericsson | 185 | 02-23-2022 - sales_others | 9 | Muller | 250 | 03-11-2022 - sales_others | 10 | Halder | 350 | 01-28-2022 - sales_others | 12 | Plato | 350 | 03-19-2022 - sales_others | 14 | Smith | 510 | 05-04-2022 -(14 rows) - -DROP TABLE sales_range; --- --- Test for: --- * split non-DEFAULT partition to partitions with spaces between bounds; --- * random order of partitions in SPLIT PARTITION command. --- -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_all PARTITION OF sales_range FOR VALUES FROM ('2022-01-01') TO ('2022-05-01'); -INSERT INTO sales_range VALUES - (1, 'May', 1000, '2022-01-31'), - (2, 'Smirnoff', 500, '2022-02-09'), - (3, 'Ford', 2000, '2022-04-30'), - (4, 'Ivanov', 750, '2022-04-13'), - (5, 'Deev', 250, '2022-04-07'), - (6, 'Poirot', 150, '2022-02-07'), - (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'); -ALTER TABLE sales_range SPLIT PARTITION sales_all INTO - (PARTITION sales_mar2022_1decade FOR VALUES FROM ('2022-03-01') TO ('2022-03-10'), - PARTITION sales_jan2022_1decade FOR VALUES FROM ('2022-01-01') TO ('2022-01-10'), - PARTITION sales_feb2022_1decade FOR VALUES FROM ('2022-02-01') TO ('2022-02-10'), - PARTITION sales_apr2022_1decade FOR VALUES FROM ('2022-04-01') TO ('2022-04-10'), - PARTITION sales_others DEFAULT); -SELECT tableoid::regclass, * FROM sales_range ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - tableoid | salesperson_id | salesperson_name | sales_amount | sales_date ------------------------+----------------+------------------+--------------+------------ - sales_apr2022_1decade | 5 | Deev | 250 | 04-07-2022 - sales_apr2022_1decade | 11 | Trump | 380 | 04-06-2022 - sales_feb2022_1decade | 2 | Smirnoff | 500 | 02-09-2022 - sales_feb2022_1decade | 6 | Poirot | 150 | 02-07-2022 - sales_jan2022_1decade | 13 | Gandi | 377 | 01-09-2022 - sales_mar2022_1decade | 7 | Li | 175 | 03-08-2022 - sales_others | 1 | May | 1000 | 01-31-2022 - sales_others | 3 | Ford | 2000 | 04-30-2022 - sales_others | 4 | Ivanov | 750 | 04-13-2022 - sales_others | 8 | Ericsson | 185 | 02-23-2022 - sales_others | 9 | Muller | 250 | 03-11-2022 - sales_others | 10 | Halder | 350 | 01-28-2022 - sales_others | 12 | Plato | 350 | 03-19-2022 -(13 rows) - -DROP TABLE sales_range; --- --- Test for split non-DEFAULT partition to DEFAULT partition + partitions --- with spaces between bounds. --- -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_all PARTITION OF sales_range FOR VALUES FROM ('2022-02-01') TO ('2022-05-01'); -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'); -ALTER TABLE sales_range SPLIT PARTITION sales_all INTO - (PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01'), - PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_others DEFAULT); -INSERT INTO sales_range VALUES (14, 'Smith', 510, '2022-05-04'); -SELECT tableoid::regclass, * FROM sales_range ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - tableoid | salesperson_id | salesperson_name | sales_amount | sales_date ----------------+----------------+------------------+--------------+------------ - sales_apr2022 | 3 | Ford | 2000 | 04-30-2022 - sales_apr2022 | 4 | Ivanov | 750 | 04-13-2022 - sales_apr2022 | 5 | Deev | 250 | 04-07-2022 - sales_apr2022 | 11 | Trump | 380 | 04-06-2022 - sales_feb2022 | 2 | Smirnoff | 500 | 02-10-2022 - sales_feb2022 | 6 | Poirot | 150 | 02-11-2022 - sales_feb2022 | 8 | Ericsson | 185 | 02-23-2022 - sales_jan2022 | 1 | May | 1000 | 01-31-2022 - sales_jan2022 | 10 | Halder | 350 | 01-28-2022 - sales_jan2022 | 13 | Gandi | 377 | 01-09-2022 - sales_others | 7 | Li | 175 | 03-08-2022 - sales_others | 9 | Muller | 250 | 03-11-2022 - sales_others | 12 | Plato | 350 | 03-19-2022 - sales_others | 14 | Smith | 510 | 05-04-2022 -(14 rows) - -DROP TABLE sales_range; --- --- Test that SPLIT PARTITION rejects the degenerate case where the only --- non-DEFAULT replacement partition keeps the original bound and the command --- merely adds a DEFAULT partition. --- -CREATE TABLE t (i int) PARTITION BY RANGE (i); -CREATE TABLE tp_0_50 PARTITION OF t FOR VALUES FROM (0) TO (50); -INSERT INTO t VALUES (1); --- ERROR -ALTER TABLE t SPLIT PARTITION tp_0_50 INTO - (PARTITION tp_0_50 FOR VALUES FROM (0) TO (50), - PARTITION tp_default DEFAULT); -ERROR: cannot split partition "tp_0_50" only to add a DEFAULT partition -LINE 2: (PARTITION tp_0_50 FOR VALUES FROM (0) TO (50), - ^ -DETAIL: The non-DEFAULT partition would keep the same partition bound. -HINT: Use CREATE TABLE ... PARTITION OF ... DEFAULT to add a DEFAULT partition. -DROP TABLE t; --- --- Test that a LIST split with DEFAULT is not considered degenerate when --- only NULL is removed from the explicit replacement partition. --- -CREATE TABLE t (i int) PARTITION BY LIST (i); -CREATE TABLE tp_null_1 PARTITION OF t FOR VALUES IN (NULL, 1); -ALTER TABLE t SPLIT PARTITION tp_null_1 INTO - (PARTITION tp_1 FOR VALUES IN (1), - PARTITION tp_default DEFAULT); -INSERT INTO t VALUES (NULL), (1), (2); -SELECT tableoid::regclass, i FROM t ORDER BY tableoid::regclass::text COLLATE "C", i NULLS FIRST; - tableoid | i -------------+--- - tp_1 | 1 - tp_default | - tp_default | 2 -(3 rows) - -DROP TABLE t; --- --- Test that the same-bound check for LIST partitioning uses the --- partition operator family, not byte equality. -0.0 and 0.0 have --- different bit patterns but compare equal under float8, so the --- replacement bound (-0.0, 1.0) is the same set as the original --- (0.0, 1.0) and the SPLIT is degenerate. A datumIsEqual()-based --- check would let this through; the partsupfunc-based check correctly --- rejects it. --- -CREATE TABLE t (v float8) PARTITION BY LIST (v); -CREATE TABLE tp_zero_one PARTITION OF t FOR VALUES IN (0.0, 1.0); --- ERROR -ALTER TABLE t SPLIT PARTITION tp_zero_one INTO - (PARTITION tp_zero_one FOR VALUES IN (-0.0, 1.0), - PARTITION tp_default DEFAULT); -ERROR: cannot split partition "tp_zero_one" only to add a DEFAULT partition -LINE 2: (PARTITION tp_zero_one FOR VALUES IN (-0.0, 1.0), - ^ -DETAIL: The non-DEFAULT partition would keep the same partition bound. -HINT: Use CREATE TABLE ... PARTITION OF ... DEFAULT to add a DEFAULT partition. -DROP TABLE t; --- --- Test that the explicit partition bound cannot extend outside the split --- partition's bound when a DEFAULT partition is specified. --- -CREATE TABLE t (i int) PARTITION BY RANGE (i); -CREATE TABLE tp_0_51 PARTITION OF t FOR VALUES FROM (0) TO (51); -CREATE TABLE tp_51_100 PARTITION OF t FOR VALUES FROM (51) TO (100); --- ERROR -ALTER TABLE t SPLIT PARTITION tp_0_51 INTO - (PARTITION tp_0_51 FOR VALUES FROM (0) TO (53), - PARTITION tp_default DEFAULT); -ERROR: upper bound of partition "tp_0_51" is greater than upper bound of split partition "tp_0_51" -LINE 2: (PARTITION tp_0_51 FOR VALUES FROM (0) TO (53), - ^ -HINT: Explicit partition bounds must be contained within the bounds of the split partition when a DEFAULT partition is specified. -DROP TABLE t; --- --- Try to SPLIT partition of another table. --- -CREATE TABLE t1(i int, t text) PARTITION BY LIST (t); -CREATE TABLE t1pa PARTITION OF t1 FOR VALUES IN ('A'); -CREATE TABLE t2 (i int, t text) PARTITION BY RANGE (t); --- ERROR -ALTER TABLE t2 SPLIT PARTITION t1pa INTO - (PARTITION t2a FOR VALUES FROM ('A') TO ('B'), - PARTITION t2b FOR VALUES FROM ('B') TO ('C')); -ERROR: relation "t1pa" is not a partition of relation "t2" -HINT: ALTER TABLE ... SPLIT PARTITION can only split partitions that don't have sub-partitions. -DROP TABLE t2; -DROP TABLE t1; --- --- Try to SPLIT partition of temporary table. --- -CREATE TEMP TABLE t (i int) PARTITION BY RANGE (i); -CREATE TEMP TABLE tp_0_2 PARTITION OF t FOR VALUES FROM (0) TO (2); -SELECT c.oid::pg_catalog.regclass, pg_catalog.pg_get_expr(c.relpartbound, c.oid), c.relpersistence - FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i - WHERE c.oid = i.inhrelid AND i.inhparent = 't'::regclass - ORDER BY pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT', c.oid::pg_catalog.regclass::pg_catalog.text COLLATE "C"; - oid | pg_get_expr | relpersistence ---------+----------------------------+---------------- - tp_0_2 | FOR VALUES FROM (0) TO (2) | t -(1 row) - --- ERROR -ALTER TABLE t SPLIT PARTITION tp_0_2 INTO - (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), - PARTITION tp_1_2 FOR VALUES FROM (1) TO (2)); -ERROR: cannot create a permanent relation as partition of temporary relation "t" -ALTER TABLE t SPLIT PARTITION tp_0_2 INTO - (PARTITION pg_temp.tp_0_1 FOR VALUES FROM (0) TO (1), - PARTITION pg_temp.tp_1_2 FOR VALUES FROM (1) TO (2)); --- Partitions should be temporary. -SELECT c.oid::pg_catalog.regclass, pg_catalog.pg_get_expr(c.relpartbound, c.oid), c.relpersistence - FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i - WHERE c.oid = i.inhrelid AND i.inhparent = 't'::regclass - ORDER BY pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT', c.oid::pg_catalog.regclass::pg_catalog.text COLLATE "C"; - oid | pg_get_expr | relpersistence ---------+----------------------------+---------------- - tp_0_1 | FOR VALUES FROM (0) TO (1) | t - tp_1_2 | FOR VALUES FROM (1) TO (2) | t -(2 rows) - -DROP TABLE t; --- Check the new partitions inherit parent's tablespace -CREATE TABLE t (i int PRIMARY KEY USING INDEX TABLESPACE regress_tblspace) - PARTITION BY RANGE (i) TABLESPACE regress_tblspace; -CREATE TABLE tp_0_2 PARTITION OF t FOR VALUES FROM (0) TO (2); -ALTER TABLE t SPLIT PARTITION tp_0_2 INTO - (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), - PARTITION tp_1_2 FOR VALUES FROM (1) TO (2)); -SELECT tablename, tablespace FROM pg_tables - WHERE tablename IN ('t', 'tp_0_1', 'tp_1_2') AND schemaname = 'partition_split_schema' - ORDER BY tablename COLLATE "C", tablespace COLLATE "C"; - tablename | tablespace ------------+------------------ - t | regress_tblspace - tp_0_1 | regress_tblspace - tp_1_2 | regress_tblspace -(3 rows) - -SELECT tablename, indexname, tablespace FROM pg_indexes - WHERE tablename IN ('t', 'tp_0_1', 'tp_1_2') AND schemaname = 'partition_split_schema' - ORDER BY tablename COLLATE "C", indexname COLLATE "C", tablespace COLLATE "C"; - tablename | indexname | tablespace ------------+-------------+------------------ - t | t_pkey | regress_tblspace - tp_0_1 | tp_0_1_pkey | regress_tblspace - tp_1_2 | tp_1_2_pkey | regress_tblspace -(3 rows) - -DROP TABLE t; --- Check new partitions inherits parent's table access method -CREATE ACCESS METHOD partition_split_heap TYPE TABLE HANDLER heap_tableam_handler; -CREATE TABLE t (i int) PARTITION BY RANGE (i) USING partition_split_heap; -CREATE TABLE tp_0_2 PARTITION OF t FOR VALUES FROM (0) TO (2); -ALTER TABLE t SPLIT PARTITION tp_0_2 INTO - (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), - PARTITION tp_1_2 FOR VALUES FROM (1) TO (2)); -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_1'::regclass, 'tp_1_2'::regclass) -ORDER BY c.relname COLLATE "C"; - relname | amname ----------+---------------------- - t | partition_split_heap - tp_0_1 | partition_split_heap - tp_1_2 | partition_split_heap -(3 rows) - -DROP TABLE t; -DROP ACCESS METHOD partition_split_heap; --- Split partition of a temporary table when one of the partitions after --- split has the same name as the partition being split -CREATE TEMP TABLE t (a int) PARTITION BY RANGE (a); -CREATE TEMP TABLE tp_0 PARTITION OF t FOR VALUES FROM (0) TO (2); -ALTER TABLE t SPLIT PARTITION tp_0 INTO - (PARTITION pg_temp.tp_0 FOR VALUES FROM (0) TO (1), - PARTITION pg_temp.tp_1 FOR VALUES FROM (1) TO (2)); -DROP TABLE t; --- Check defaults and constraints of new partitions -CREATE TABLE t_bigint ( - b bigint, - i int DEFAULT (3+10), - j int DEFAULT 101, - k int GENERATED ALWAYS AS (b+10) STORED -) -PARTITION BY RANGE (b); -CREATE TABLE t_bigint_default PARTITION OF t_bigint DEFAULT; --- Show defaults/constraints before SPLIT PARTITION -\d+ t_bigint - Partitioned table "partition_split_schema.t_bigint" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------+---------+-----------+----------+---------------------------------------+---------+--------------+------------- - b | bigint | | | | plain | | - i | integer | | | 3 + 10 | plain | | - j | integer | | | 101 | plain | | - k | integer | | | generated always as ((b + 10)) stored | plain | | -Partition key: RANGE (b) -Partitions: - t_bigint_default DEFAULT - -\d+ t_bigint_default - Table "partition_split_schema.t_bigint_default" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------+---------+-----------+----------+---------------------------------------+---------+--------------+------------- - b | bigint | | | | plain | | - i | integer | | | 3 + 10 | plain | | - j | integer | | | 101 | plain | | - k | integer | | | generated always as ((b + 10)) stored | plain | | -Partition of: t_bigint DEFAULT -No partition constraint - -ALTER TABLE t_bigint SPLIT PARTITION t_bigint_default INTO - (PARTITION t_bigint_01_10 FOR VALUES FROM (0) TO (10), - PARTITION t_bigint_default DEFAULT); --- Show defaults/constraints after SPLIT PARTITION -\d+ t_bigint_default - Table "partition_split_schema.t_bigint_default" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------+---------+-----------+----------+---------------------------------------+---------+--------------+------------- - b | bigint | | | | plain | | - i | integer | | | 3 + 10 | plain | | - j | integer | | | 101 | plain | | - k | integer | | | generated always as ((b + 10)) stored | plain | | -Partition of: t_bigint DEFAULT -Partition constraint: (NOT ((b IS NOT NULL) AND ((b >= '0'::bigint) AND (b < '10'::bigint)))) - -\d+ t_bigint_01_10 - Table "partition_split_schema.t_bigint_01_10" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------+---------+-----------+----------+---------------------------------------+---------+--------------+------------- - b | bigint | | | | plain | | - i | integer | | | 3 + 10 | plain | | - j | integer | | | 101 | plain | | - k | integer | | | generated always as ((b + 10)) stored | plain | | -Partition of: t_bigint FOR VALUES FROM ('0') TO ('10') -Partition constraint: ((b IS NOT NULL) AND (b >= '0'::bigint) AND (b < '10'::bigint)) - -DROP TABLE t_bigint; --- Test permission checks. The user needs to own the parent table and the --- the partition to split to do the split. -CREATE ROLE regress_partition_split_alice; -CREATE ROLE regress_partition_split_bob; -GRANT ALL ON SCHEMA partition_split_schema TO regress_partition_split_alice; -GRANT ALL ON SCHEMA partition_split_schema TO regress_partition_split_bob; -SET SESSION AUTHORIZATION regress_partition_split_alice; -CREATE TABLE t (i int) PARTITION BY RANGE (i); -CREATE TABLE tp_0_2 PARTITION OF t FOR VALUES FROM (0) TO (2); -SET SESSION AUTHORIZATION regress_partition_split_bob; -ALTER TABLE t SPLIT PARTITION tp_0_2 INTO - (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), - PARTITION tp_1_2 FOR VALUES FROM (1) TO (2)); --error -ERROR: must be owner of table t -RESET SESSION AUTHORIZATION; -ALTER TABLE t OWNER TO regress_partition_split_bob; -SET SESSION AUTHORIZATION regress_partition_split_bob; -ALTER TABLE t SPLIT PARTITION tp_0_2 INTO - (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), - PARTITION tp_1_2 FOR VALUES FROM (1) TO (2)); --error -ERROR: must be owner of table tp_0_2 -RESET SESSION AUTHORIZATION; -ALTER TABLE tp_0_2 OWNER TO regress_partition_split_bob; -SET SESSION AUTHORIZATION regress_partition_split_bob; -ALTER TABLE t SPLIT PARTITION tp_0_2 INTO - (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), - PARTITION tp_1_2 FOR VALUES FROM (1) TO (2)); --ok -RESET SESSION AUTHORIZATION; -DROP TABLE t; --- Test: owner of new partitions should be the same as owner of split partition -CREATE TABLE t (i int) PARTITION BY RANGE (i); -SET SESSION AUTHORIZATION regress_partition_split_alice; -CREATE TABLE tp_0_2(i int); -RESET SESSION AUTHORIZATION; -ALTER TABLE t ATTACH PARTITION tp_0_2 FOR VALUES FROM (0) TO (2); --- Owner is 'regress_partition_split_alice': -\dt tp_0_2 - List of tables - Schema | Name | Type | Owner -------------------------+--------+-------+------------------------------- - partition_split_schema | tp_0_2 | table | regress_partition_split_alice -(1 row) - -ALTER TABLE t SPLIT PARTITION tp_0_2 INTO - (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), - PARTITION tp_1_2 FOR VALUES FROM (1) TO (2)); --- Owner should be 'regress_partition_split_alice': -\dt tp_0_1 - List of tables - Schema | Name | Type | Owner -------------------------+--------+-------+------------------------------- - partition_split_schema | tp_0_1 | table | regress_partition_split_alice -(1 row) - -\dt tp_1_2 - List of tables - Schema | Name | Type | Owner -------------------------+--------+-------+------------------------------- - partition_split_schema | tp_1_2 | table | regress_partition_split_alice -(1 row) - -DROP TABLE t; --- Test: index of new partitions should be created with same owner as split --- partition -SET SESSION AUTHORIZATION regress_partition_split_alice; -CREATE TABLE t (i int) PARTITION BY RANGE (i); -CREATE TABLE tp_10_20 PARTITION OF t FOR VALUES FROM (10) TO (20); -INSERT INTO t VALUES (11), (16); -CREATE OR REPLACE FUNCTION run_me(integer) RETURNS integer AS $$ -BEGIN - RAISE NOTICE 'you are running me as %', CURRENT_USER; - RETURN $1; -END -$$ LANGUAGE PLPGSQL IMMUTABLE; --- Owner is 'regress_partition_split_alice': -CREATE INDEX ON t (run_me(i)); -NOTICE: you are running me as regress_partition_split_alice -NOTICE: you are running me as regress_partition_split_alice -RESET SESSION AUTHORIZATION; --- Owner should be 'regress_partition_split_alice': -ALTER TABLE t SPLIT PARTITION tp_10_20 INTO - (PARTITION tp_10_15 FOR VALUES FROM (10) TO (15), - PARTITION tp_15_20 FOR VALUES FROM (15) TO (20)); -NOTICE: you are running me as regress_partition_split_alice -NOTICE: you are running me as regress_partition_split_alice -DROP TABLE t; -DROP FUNCTION run_me(integer); -REVOKE ALL ON SCHEMA partition_split_schema FROM regress_partition_split_alice; -REVOKE ALL ON SCHEMA partition_split_schema FROM regress_partition_split_bob; -DROP ROLE regress_partition_split_alice; -DROP ROLE regress_partition_split_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 SPLIT PARTITION tp1 INTO - (PARTITION tp1_1 FOR VALUES WITH (MODULUS 4, REMAINDER 0), - PARTITION tp1_2 FOR VALUES WITH (MODULUS 4, REMAINDER 2)); -ERROR: partition of hash-partitioned table cannot be split --- ERROR -ALTER TABLE t SPLIT PARTITION tp1 INTO - (PARTITION tp1_1 FOR VALUES WITH (MODULUS 4, REMAINDER 0)); -ERROR: list of new partitions must contain at least two partitions -DROP TABLE t; --- Test for split 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 -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_x -(i int NOT NULL, - t text STORAGE MAIN DEFAULT 'default_tp_x', - b bigint, - d date GENERATED ALWAYS as ('2022-02-02') STORED); -ALTER TABLE t ATTACH PARTITION tp_x FOR VALUES FROM (0) TO (2); -COMMENT ON COLUMN tp_x.i IS 'tp_x.i'; -CREATE STATISTICS t_stat (DEPENDENCIES) on i, b from t; -CREATE STATISTICS tp_x_stat (DEPENDENCIES) on i, b from tp_x; -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_x(i, t, b) VALUES(0, DEFAULT, 1); -INSERT INTO tp_x(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_x_before_insert_row_trigger BEFORE INSERT ON tp_x FOR EACH ROW - EXECUTE PROCEDURE trigger_function('tp_x'); -\d+ tp_x - Table "partition_split_schema.tp_x" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------+---------+-----------+----------+-------------------------------------------------+---------+--------------+------------- - i | integer | | not null | | plain | | tp_x.i - t | text | | | 'default_tp_x'::text | main | | - b | bigint | | not null | | plain | | - d | date | | | generated always as ('02-02-2022'::date) stored | plain | | -Partition of: t FOR VALUES FROM (0) TO (2) -Partition constraint: ((abs(i) IS NOT NULL) AND (abs(i) >= 0) AND (abs(i) < 2)) -Check constraints: - "t_b_check" CHECK (b > 0) - "t_b_check1" CHECK (b > 0) NOT ENFORCED - "t_b_check2" CHECK (b > 0) NOT VALID -Statistics objects: - "partition_split_schema.tp_x_stat" (dependencies) ON i, b FROM tp_x -Not-null constraints: - "tp_x_i_not_null" NOT NULL "i" (inherited) - "t_b_nn" NOT NULL "b" (inherited) NOT VALID -Triggers: - t_before_insert_row_trigger BEFORE INSERT ON tp_x FOR EACH ROW EXECUTE FUNCTION trigger_function('t'), ON TABLE t - tp_x_before_insert_row_trigger BEFORE INSERT ON tp_x FOR EACH ROW EXECUTE FUNCTION trigger_function('tp_x') - -ALTER TABLE t SPLIT PARTITION tp_x INTO - (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), - PARTITION tp_x FOR VALUES FROM (1) TO (2)); -\d+ tp_x - Table "partition_split_schema.tp_x" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------+---------+-----------+----------+-------------------------------------------------+----------+--------------+------------- - i | integer | | not null | | plain | | - t | text | | | 'default_t'::text | extended | | - b | bigint | | not null | | plain | | - d | date | | | generated always as ('01-01-2022'::date) stored | plain | | -Partition of: t FOR VALUES FROM (1) TO (2) -Partition constraint: ((abs(i) IS NOT NULL) AND (abs(i) >= 1) AND (abs(i) < 2)) -Check constraints: - "t_b_check" CHECK (b > 0) - "t_b_check1" CHECK (b > 0) NOT ENFORCED - "t_b_check2" CHECK (b > 0) NOT VALID -Not-null constraints: - "t_i_not_null" NOT NULL "i" (inherited) - "t_b_nn" NOT NULL "b" (inherited) NOT VALID -Triggers: - t_before_insert_row_trigger BEFORE INSERT ON tp_x FOR EACH ROW EXECUTE FUNCTION trigger_function('t'), ON TABLE t - -INSERT INTO t(i, t, b) VALUES(1, DEFAULT, 3); -NOTICE: trigger(t) called: action = INSERT, when = BEFORE, level = ROW -SELECT tableoid::regclass, * FROM t ORDER BY tableoid::regclass::text COLLATE "C", b; - tableoid | i | t | b | d -----------+---+--------------+---+------------ - tp_0_1 | 0 | default_tp_x | 1 | 01-01-2022 - tp_x | 1 | default_tp_x | 2 | 01-01-2022 - tp_x | 1 | default_t | 3 | 01-01-2022 -(3 rows) - -DROP TABLE t; -DROP FUNCTION trigger_function(); --- 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_2 PARTITION OF t FOR VALUES FROM (0) TO (2); -ALTER TABLE t ADD CONSTRAINT cc CHECK(tableoid <> 123456789); -INSERT INTO t VALUES (0), (1); --- 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); - count -------- - 1 -(1 row) - --- "tab_id" column (stored generated column) with "tableoid" attribute requires --- recomputation here. -ALTER TABLE t SPLIT PARTITION tp_0_2 INTO - (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), - PARTITION tp_1_2 FOR VALUES FROM (1) TO (2)); --- 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); - count -------- - 0 -(1 row) - -DROP TABLE t; --- Each new partition produced by SPLIT must get its own TOAST table so --- that out-of-line varlena attributes coming from the source partition --- can 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_all PARTITION OF t FOR VALUES FROM (MINVALUE) TO (MAXVALUE); -INSERT INTO t SELECT repeat('1', 10000); -ALTER TABLE t SPLIT PARTITION tp_all INTO ( - PARTITION tp_lo FOR VALUES FROM (MINVALUE) TO ('2'), - PARTITION tp_hi FOR VALUES FROM ('2') TO (MAXVALUE) -); -SELECT relname, - reltoastrelid <> 0 AS has_toast, - pg_relation_size(reltoastrelid) > 0 AS toast_used - FROM pg_class WHERE relname IN ('tp_lo', 'tp_hi') ORDER BY relname; - relname | has_toast | toast_used ----------+-----------+------------ - tp_hi | t | f - tp_lo | t | t -(2 rows) - -SELECT length(a) FROM t; - length --------- - 10000 -(1 row) - -DROP TABLE t; --- Tablespace selection for the new partitions 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_all PARTITION OF t FOR VALUES FROM (0) TO (10); -INSERT INTO t SELECT generate_series(0, 9); -ALTER TABLE t SPLIT PARTITION tp_all INTO ( - PARTITION tp_lo FOR VALUES FROM (0) TO (5), - PARTITION tp_hi FOR VALUES FROM (5) TO (10) -); -SELECT c.relname, s.spcname FROM pg_class c LEFT JOIN pg_tablespace s - ON c.reltablespace = s.oid WHERE c.relname IN ('tp_lo', 'tp_hi') - ORDER BY c.relname; - relname | spcname ----------+------------------ - tp_hi | regress_tblspace - tp_lo | regress_tblspace -(2 rows) - -DROP TABLE t; --- Parent has no explicit tablespace, but default_tablespace is set: the --- new partitions land on default_tablespace. -CREATE TABLE t (i int) PARTITION BY RANGE(i); -CREATE TABLE tp_all PARTITION OF t FOR VALUES FROM (0) TO (10); -INSERT INTO t SELECT generate_series(0, 9); -SET default_tablespace TO regress_tblspace; -ALTER TABLE t SPLIT PARTITION tp_all INTO ( - PARTITION tp_lo FOR VALUES FROM (0) TO (5), - PARTITION tp_hi FOR VALUES FROM (5) TO (10) -); -RESET default_tablespace; -SELECT c.relname, s.spcname FROM pg_class c LEFT JOIN pg_tablespace s - ON c.reltablespace = s.oid WHERE c.relname IN ('tp_lo', 'tp_hi') - ORDER BY c.relname; - relname | spcname ----------+------------------ - tp_hi | regress_tblspace - tp_lo | regress_tblspace -(2 rows) - -DROP TABLE t; -CREATE TABLE t (i int) PARTITION BY RANGE(i); -CREATE TABLE tp_all PARTITION OF t FOR VALUES FROM (0) 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 SPLIT PARTITION tp_all INTO ( - PARTITION tp_lo FOR VALUES FROM (0) TO (5), - PARTITION tp_hi FOR VALUES FROM (5) TO (10) -); -- fails -ERROR: only shared relations can be placed in pg_global tablespace -RESET default_tablespace; --- Parent has no explicit tablespace and default_tablespace is empty: new --- partitions use the database default (reltablespace = 0). -ALTER TABLE t SPLIT PARTITION tp_all INTO ( - PARTITION tp_lo FOR VALUES FROM (0) TO (5), - PARTITION tp_hi FOR VALUES FROM (5) TO (10) -); -SELECT relname, reltablespace FROM pg_class - WHERE relname IN ('tp_lo', 'tp_hi') ORDER BY relname; - relname | reltablespace ----------+--------------- - tp_hi | 0 - tp_lo | 0 -(2 rows) - -DROP TABLE t; -RESET search_path; --- -DROP SCHEMA partition_split_schema; -DROP SCHEMA partition_split_schema2; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 8fa0a6c47fb..8356ca98ef2 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -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) diff --git a/src/test/regress/sql/partition_merge.sql b/src/test/regress/sql/partition_merge.sql deleted file mode 100644 index 80dc365b0ce..00000000000 --- a/src/test/regress/sql/partition_merge.sql +++ /dev/null @@ -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; diff --git a/src/test/regress/sql/partition_split.sql b/src/test/regress/sql/partition_split.sql deleted file mode 100644 index ffd15e7f969..00000000000 --- a/src/test/regress/sql/partition_split.sql +++ /dev/null @@ -1,1263 +0,0 @@ --- --- PARTITION_SPLIT --- Tests for "ALTER TABLE ... SPLIT PARTITION ..." command --- - -CREATE SCHEMA partition_split_schema; -CREATE SCHEMA partition_split_schema2; -SET search_path = partition_split_schema, public; - --- --- BY RANGE partitioning --- - --- --- Test for error codes --- -CREATE TABLE sales_range (salesperson_id 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_feb_mar_apr2022 PARTITION OF sales_range FOR VALUES FROM ('2022-02-01') TO ('2022-05-01'); -CREATE TABLE sales_others PARTITION OF sales_range DEFAULT; - --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_xxx INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); - --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_jan2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); - --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_jan2022 FOR VALUES IN ('2022-05-01', '2022-06-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); - --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-02-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); - --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-10-01')); - --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-01-01') TO ('2022-03-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); - --- ERROR --- (We can create partition with the same name as split partition, but can't create two partitions with the same name) -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_feb_mar_apr2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_feb_mar_apr2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); - --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_feb2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); - --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION partition_split_schema.sales_feb2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); - --- ERROR -ALTER TABLE sales_feb_mar_apr2022 SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_jan2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_feb2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); - --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-06-01')); - --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-02-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); - --- Tests for spaces between partitions, them should be executed without DEFAULT partition -ALTER TABLE sales_range DETACH PARTITION sales_others; - --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-02') TO ('2022-03-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); - --- Check the source partition not in the search path -SET search_path = partition_split_schema2, public; -ALTER TABLE partition_split_schema.sales_range -SPLIT PARTITION partition_split_schema.sales_feb_mar_apr2022 INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); -SET search_path = partition_split_schema, public; -\d+ sales_range - -DROP TABLE sales_range; -DROP TABLE sales_others; - --- Additional tests for error messages, no default partition -CREATE TABLE sales_range (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_feb_mar_apr2022 PARTITION OF sales_range FOR VALUES FROM ('2022-02-01') TO ('2022-05-01'); - --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-06-01')); - -DROP TABLE sales_range; - --- --- Add rows into partitioned table then split 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_feb_mar_apr2022 PARTITION OF sales_range FOR VALUES FROM ('2022-02-01') TO ('2022-05-01'); -CREATE TABLE sales_others PARTITION OF sales_range DEFAULT; - -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'); - -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); - -SELECT tableoid::regclass, * FROM sales_range ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; -DROP TABLE sales_range CASCADE; - --- --- Add split partition, then add rows into partitioned table --- -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_feb_mar_apr2022 PARTITION OF sales_range FOR VALUES FROM ('2022-02-01') TO ('2022-05-01'); -CREATE TABLE sales_others PARTITION OF sales_range DEFAULT; - -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'); - --- Split partition, also check schema qualification of new partitions -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION partition_split_schema.sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION partition_split_schema2.sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); -\d+ sales_range - -SELECT tableoid::regclass, * FROM sales_range ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - -DROP TABLE sales_range CASCADE; - --- --- 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_dec2021 PARTITION OF sales_date FOR VALUES FROM (2021, 12, 1) TO (2022, 1, 1); -CREATE TABLE sales_jan_feb2022 PARTITION OF sales_date FOR VALUES FROM (2022, 1, 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 ORDER BY tableoid::regclass::text COLLATE "C", sales_year, sales_month, sales_day; - -ALTER TABLE sales_date SPLIT PARTITION sales_jan_feb2022 INTO - (PARTITION sales_jan2022 FOR VALUES FROM (2022, 1, 1) TO (2022, 2, 1), - PARTITION sales_feb2022 FOR VALUES FROM (2022, 2, 1) TO (2022, 3, 1)); - -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 ORDER BY tableoid::regclass::text COLLATE "C", sales_year, sales_month, sales_day; - -DROP TABLE sales_date CASCADE; - --- --- Test: split DEFAULT partition; use an index on partition key; check index after split --- -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_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 * FROM sales_others; -SELECT * FROM pg_indexes WHERE tablename = 'sales_others' and schemaname = 'partition_split_schema' ORDER BY indexname COLLATE "C"; - -ALTER TABLE sales_range SPLIT PARTITION sales_others INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01'), - PARTITION sales_others DEFAULT); - --- Use indexscan for testing indexes -SET enable_seqscan = OFF; - -EXPLAIN (COSTS OFF) SELECT * FROM sales_feb2022 where sales_date > '2022-01-01'; -SELECT * FROM sales_feb2022 where sales_date > '2022-01-01'; -EXPLAIN (COSTS OFF) SELECT * FROM sales_mar2022 where sales_date > '2022-01-01'; -SELECT * FROM sales_mar2022 where sales_date > '2022-01-01'; -EXPLAIN (COSTS OFF) SELECT * FROM sales_apr2022 where sales_date > '2022-01-01'; -SELECT * FROM sales_apr2022 where sales_date > '2022-01-01'; -EXPLAIN (COSTS OFF) SELECT * FROM sales_others where sales_date > '2022-01-01'; -SELECT * FROM sales_others where sales_date > '2022-01-01'; - -RESET enable_seqscan; - -SELECT * FROM pg_indexes -WHERE tablename in ('sales_feb2022', 'sales_mar2022', 'sales_apr2022', 'sales_others') -AND schemaname = 'partition_split_schema' -ORDER BY indexname COLLATE "C"; - -DROP TABLE sales_range CASCADE; - --- --- Test: some cases for splitting DEFAULT partition (different bounds) --- -CREATE TABLE sales_range (salesperson_id INT, sales_date date) PARTITION BY RANGE (sales_date); -CREATE TABLE sales_others PARTITION OF sales_range DEFAULT; - --- sales_error intersects with sales_dec2021 (lower bound) --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_others INTO - (PARTITION sales_dec2021 FOR VALUES FROM ('2021-12-01') TO ('2022-01-01'), - PARTITION sales_error FOR VALUES FROM ('2021-12-30') TO ('2022-02-01'), - PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_others DEFAULT); - --- sales_error intersects with sales_feb2022 (upper bound) --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_others INTO - (PARTITION sales_dec2021 FOR VALUES FROM ('2021-12-01') TO ('2022-01-01'), - PARTITION sales_error FOR VALUES FROM ('2022-01-01') TO ('2022-02-02'), - PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_others DEFAULT); - --- sales_error intersects with sales_dec2021 (inside bound) --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_others INTO - (PARTITION sales_dec2021 FOR VALUES FROM ('2021-12-01') TO ('2022-01-01'), - PARTITION sales_error FOR VALUES FROM ('2021-12-10') TO ('2021-12-20'), - PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_others DEFAULT); - --- sales_error intersects with sales_dec2021 (exactly the same bounds) --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_others INTO - (PARTITION sales_dec2021 FOR VALUES FROM ('2021-12-01') TO ('2022-01-01'), - PARTITION sales_error FOR VALUES FROM ('2021-12-01') TO ('2022-01-01'), - PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_others DEFAULT); - --- ERROR -ALTER TABLE sales_range SPLIT PARTITION sales_others INTO - (PARTITION sales_dec2021 FOR VALUES FROM ('2021-12-01') TO ('2022-01-01'), - PARTITION sales_jan2022 FOR VALUES FROM ('2022-01-01') TO ('2022-02-01'), - PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01')); - --- no error: bounds of sales_noerror are between sales_dec2021 and sales_feb2022 -ALTER TABLE sales_range SPLIT PARTITION sales_others INTO - (PARTITION sales_dec2021 FOR VALUES FROM ('2021-12-01') TO ('2022-01-01'), - PARTITION sales_noerror FOR VALUES FROM ('2022-01-10') TO ('2022-01-20'), - PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_others DEFAULT); - -DROP TABLE sales_range; - -CREATE TABLE sales_range (sales_date date) PARTITION BY RANGE (sales_date); -CREATE TABLE sales_others PARTITION OF sales_range DEFAULT; - --- no error: bounds of sales_noerror are equal to lower and upper bounds of sales_dec2021 and sales_feb2022 -ALTER TABLE sales_range SPLIT PARTITION sales_others INTO - (PARTITION sales_dec2021 FOR VALUES FROM ('2021-12-01') TO ('2022-01-01'), - PARTITION sales_noerror FOR VALUES FROM ('2022-01-01') TO ('2022-02-01'), - PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_others DEFAULT); - -DROP TABLE sales_range; - --- --- Test: split partition with CHECK and FOREIGN KEY CONSTRAINTs on partitioned table --- -CREATE TABLE salespeople(salesperson_id INT PRIMARY KEY, salesperson_name VARCHAR(30)); -INSERT INTO salespeople VALUES (1, 'Poirot'); - -CREATE TABLE sales_range ( -salesperson_id INT REFERENCES salespeople(salesperson_id), -sales_amount INT CHECK (sales_amount > 1), -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_feb_mar_apr2022 PARTITION OF sales_range FOR VALUES FROM ('2022-02-01') TO ('2022-05-01'); -CREATE TABLE sales_others PARTITION OF sales_range DEFAULT; - -SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'sales_feb_mar_apr2022'::regclass::oid ORDER BY conname COLLATE "C"; - -ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO - (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_mar2022 FOR VALUES FROM ('2022-03-01') TO ('2022-04-01'), - PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01')); - --- We should see the same CONSTRAINTs as on sales_feb_mar_apr2022 partition -SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'sales_feb2022'::regclass::oid ORDER BY conname COLLATE "C"; -SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'sales_mar2022'::regclass::oid ORDER BY conname COLLATE "C"; -SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'sales_apr2022'::regclass::oid ORDER BY conname COLLATE "C"; - --- ERROR -INSERT INTO sales_range VALUES (1, 0, '2022-03-11'); --- ERROR -INSERT INTO sales_range VALUES (-1, 10, '2022-03-11'); --- ok -INSERT INTO sales_range VALUES (1, 10, '2022-03-11'); - -DROP TABLE sales_range CASCADE; -DROP TABLE salespeople CASCADE; - --- --- Test: split partition on partitioned table in case of existing FOREIGN KEY reference from another table --- -CREATE TABLE salespeople(salesperson_id INT PRIMARY KEY, salesperson_name VARCHAR(30)) PARTITION BY RANGE (salesperson_id); -CREATE TABLE sales (salesperson_id INT REFERENCES salespeople(salesperson_id), sales_amount INT, sales_date DATE); - -CREATE TABLE salespeople01_10 PARTITION OF salespeople FOR VALUES FROM (1) TO (10); -CREATE TABLE salespeople10_40 PARTITION OF salespeople FOR VALUES FROM (10) TO (40); - -INSERT INTO salespeople VALUES - (1, 'Poirot'), - (10, 'May'), - (19, 'Ivanov'), - (20, 'Smirnoff'), - (30, 'Ford'); - -INSERT INTO sales VALUES - (1, 100, '2022-03-01'), - (1, 110, '2022-03-02'), - (10, 150, '2022-03-01'), - (10, 90, '2022-03-03'), - (19, 200, '2022-03-04'), - (20, 50, '2022-03-12'), - (20, 170, '2022-03-02'), - (30, 30, '2022-03-04'); - -SELECT tableoid::regclass, * FROM salespeople ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - -ALTER TABLE salespeople SPLIT PARTITION salespeople10_40 INTO - (PARTITION salespeople10_20 FOR VALUES FROM (10) TO (20), - PARTITION salespeople20_30 FOR VALUES FROM (20) TO (30), - PARTITION salespeople30_40 FOR VALUES FROM (30) TO (40)); - -SELECT tableoid::regclass, * FROM salespeople ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - --- ERROR -INSERT INTO sales VALUES (40, 50, '2022-03-04'); --- ok -INSERT INTO sales VALUES (30, 50, '2022-03-04'); - -DROP TABLE sales CASCADE; -DROP TABLE salespeople CASCADE; - --- --- Test: split partition 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_40 PARTITION OF salespeople FOR VALUES FROM (10) 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_40 VALUES (19, 'Ivanov'); - -ALTER TABLE salespeople SPLIT PARTITION salespeople10_40 INTO - (PARTITION salespeople10_20 FOR VALUES FROM (10) TO (20), - PARTITION salespeople20_30 FOR VALUES FROM (20) TO (30), - PARTITION salespeople30_40 FOR VALUES FROM (30) TO (40)); - --- 2 triggers should fire here (row + statement): -INSERT INTO salespeople VALUES (20, 'Smirnoff'); --- 1 trigger should fire here (row): -INSERT INTO salespeople30_40 VALUES (30, 'Ford'); - -SELECT tableoid::regclass, * FROM salespeople ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - -DROP TABLE salespeople CASCADE; -DROP FUNCTION after_insert_row_trigger(); - --- --- Test: split partition witch identity column --- If split partition column is identity column, columns of new partitions are identity columns too. --- -CREATE TABLE salespeople(salesperson_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, salesperson_name VARCHAR(30)) PARTITION BY RANGE (salesperson_id); - -CREATE TABLE salespeople1_2 PARTITION OF salespeople FOR VALUES FROM (1) TO (2); --- Create new partition with identity column: -CREATE TABLE salespeople2_5(salesperson_id INT NOT NULL, salesperson_name VARCHAR(30)); -ALTER TABLE salespeople ATTACH PARTITION salespeople2_5 FOR VALUES FROM (2) TO (5); - -INSERT INTO salespeople (salesperson_name) VALUES ('Poirot'), ('Ivanov'); - -ALTER TABLE salespeople SPLIT PARTITION salespeople2_5 INTO - (PARTITION salespeople2_3 FOR VALUES FROM (2) TO (3), - PARTITION salespeople3_4 FOR VALUES FROM (3) TO (4), - PARTITION salespeople4_5 FOR VALUES FROM (4) TO (5)); - -INSERT INTO salespeople (salesperson_name) VALUES ('May'), ('Ford'); - -SELECT tableoid::regclass, * FROM salespeople ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - --- check new partitions have identity or not after split partition -SELECT attrelid::regclass, attname, attidentity, attgenerated FROM pg_attribute -WHERE attnum > 0 -AND attrelid::regclass IN ( - 'salespeople2_3'::regclass, 'salespeople', 'salespeople2_3', - 'salespeople1_2', 'salespeople3_4', 'salespeople4_5') -ORDER BY attrelid::regclass::text COLLATE "C", attnum; - -DROP TABLE salespeople CASCADE; - --- --- Test: split partition 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 new partition with some deleted columns: -CREATE TABLE salespeople10_40(d1 VARCHAR(30), salesperson_id INT PRIMARY KEY, d2 INT, d3 DATE, salesperson_name VARCHAR(30)); - -INSERT INTO salespeople10_40 VALUES - ('dummy value 1', 19, 100, now(), 'Ivanov'), - ('dummy value 2', 20, 101, now(), 'Smirnoff'); - -ALTER TABLE salespeople10_40 DROP COLUMN d1; -ALTER TABLE salespeople10_40 DROP COLUMN d2; -ALTER TABLE salespeople10_40 DROP COLUMN d3; - -ALTER TABLE salespeople ATTACH PARTITION salespeople10_40 FOR VALUES FROM (10) TO (40); - -INSERT INTO salespeople VALUES - (1, 'Poirot'), - (10, 'May'), - (30, 'Ford'); - -ALTER TABLE salespeople SPLIT PARTITION salespeople10_40 INTO - (PARTITION salespeople10_20 FOR VALUES FROM (10) TO (20), - PARTITION salespeople20_30 FOR VALUES FROM (20) TO (30), - PARTITION salespeople30_40 FOR VALUES FROM (30) TO (40)); - -SELECT tableoid::regclass, * FROM salespeople ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - -DROP TABLE salespeople CASCADE; - --- --- Test: split sub-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 (salesperson_id INT, salesperson_name VARCHAR(30), sales_amount INT, sales_date DATE) PARTITION BY RANGE (sales_date); -CREATE TABLE sales_apr_all PARTITION OF sales_apr2022 FOR VALUES FROM ('2022-04-01') 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_range ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - -ALTER TABLE sales_apr2022 SPLIT PARTITION sales_apr_all INTO - (PARTITION sales_apr2022_01_10 FOR VALUES FROM ('2022-04-01') TO ('2022-04-10'), - PARTITION sales_apr2022_10_20 FOR VALUES FROM ('2022-04-10') TO ('2022-04-20'), - PARTITION sales_apr2022_20_30 FOR VALUES FROM ('2022-04-20') TO ('2022-05-01')); - -SELECT tableoid::regclass, * FROM sales_range 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 (sales_state VARCHAR(20)) PARTITION BY LIST (sales_state); - -CREATE TABLE sales_nord PARTITION OF sales_list FOR VALUES IN ('Oslo', 'St. Petersburg', 'Helsinki'); -CREATE TABLE sales_all PARTITION OF sales_list FOR VALUES IN ('Warsaw', 'Lisbon', 'New York', 'Madrid', 'Beijing', 'Berlin', 'Delhi', 'Kyiv', 'Vladivostok'); -CREATE TABLE sales_others PARTITION OF sales_list DEFAULT; - --- ERROR -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', 'Helsinki'), - PARTITION sales_central FOR VALUES IN ('Warsaw', 'Berlin', 'Kyiv')); - --- ERROR -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', 'Lisbon', 'Kyiv')); - --- ERROR -ALTER TABLE sales_list SPLIT PARTITION sales_all INTO - (PARTITION sales_west FOR VALUES IN ('Lisbon', 'New York', 'Madrid', NULL), - PARTITION sales_east FOR VALUES IN ('Beijing', 'Delhi', 'Vladivostok'), - PARTITION sales_central FOR VALUES IN ('Warsaw', 'Berlin', 'Kyiv')); - --- ERROR -ALTER TABLE sales_list SPLIT PARTITION sales_all INTO - (PARTITION sales_west FOR VALUES IN ('Lisbon', 'New York', 'Madrid', 'Melbourne'), - PARTITION sales_east FOR VALUES IN ('Beijing', 'Delhi', 'Vladivostok'), - PARTITION sales_central FOR VALUES IN ('Warsaw', 'Berlin', 'Kyiv')); - --- ERROR -ALTER TABLE sales_list SPLIT PARTITION sales_all INTO - (PARTITION sales_west FOR VALUES IN ('Lisbon', 'New York', 'Madrid', 'Melbourne'), - PARTITION sales_east FOR VALUES IN ('Beijing', 'Delhi', 'Vladivostok'), - PARTITION sales_central FOR VALUES IN ('Warsaw', 'Berlin', 'Kyiv'), - PARTITION sales_others2 DEFAULT); - -DROP TABLE sales_list; - --- Test for non-symbolic comparison of values (numeric values '0' and '0.0' are equal). -CREATE TABLE t (a numeric) PARTITION BY LIST (a); -CREATE TABLE t1 PARTITION OF t FOR VALUES in ('0', '1'); --- ERROR -ALTER TABLE t SPLIT PARTITION t1 INTO - (PARTITION x FOR VALUES IN ('0'), - PARTITION x1 FOR VALUES IN ('0.0', '1')); -DROP TABLE t; - --- --- Test: two specific errors for BY LIST partitioning: --- * new partitions do not have NULL value, which split partition has. --- * new partitions do not have a value that split partition has. --- -CREATE TABLE sales_list(sales_state VARCHAR(20)) PARTITION BY LIST (sales_state); - -CREATE TABLE sales_nord PARTITION OF sales_list FOR VALUES IN ('Helsinki', 'St. Petersburg', 'Oslo'); -CREATE TABLE sales_all PARTITION OF sales_list FOR VALUES IN ('Warsaw', 'Lisbon', 'New York', 'Madrid', 'Beijing', 'Berlin', 'Delhi', 'Kyiv', 'Vladivostok', NULL); - --- ERROR -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')); - --- ERROR -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', NULL)); - --- ERROR -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'), - PARTITION sales_others DEFAULT, - PARTITION sales_others2 DEFAULT); - -DROP TABLE sales_list; - --- --- Test: BY LIST partitioning, SPLIT PARTITION with data --- -CREATE TABLE sales_list -(salesperson_id SERIAL, - 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 ('Helsinki', 'St. Petersburg', 'Oslo'); -CREATE TABLE sales_all PARTITION OF sales_list FOR VALUES IN ('Warsaw', 'Lisbon', 'New York', 'Madrid', 'Beijing', 'Berlin', 'Delhi', 'Kyiv', 'Vladivostok'); -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', 'Oslo', 1200, '2022-03-06'), - ('Li', 'Vladivostok', 1150, '2022-03-09'), - ('May', 'Oslo', 1200, '2022-03-11'), - ('Halder', 'Helsinki', 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'); - -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')); - -SELECT tableoid::regclass, * FROM sales_list ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - --- Use indexscan for testing indexes after splitting partition -SET enable_seqscan = OFF; - -EXPLAIN (COSTS OFF) SELECT * FROM sales_central WHERE sales_state = 'Warsaw'; -SELECT * FROM sales_central 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; - --- --- Test for: --- * split DEFAULT partition to partitions with spaces between bounds; --- * random order of partitions in SPLIT PARTITION command. --- -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_others PARTITION OF sales_range DEFAULT; - -INSERT INTO sales_range VALUES - (1, 'May', 1000, '2022-01-31'), - (2, 'Smirnoff', 500, '2022-02-09'), - (3, 'Ford', 2000, '2022-04-30'), - (4, 'Ivanov', 750, '2022-04-13'), - (5, 'Deev', 250, '2022-04-07'), - (6, 'Poirot', 150, '2022-02-07'), - (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'); - -ALTER TABLE sales_range SPLIT PARTITION sales_others INTO - (PARTITION sales_others DEFAULT, - PARTITION sales_mar2022_1decade FOR VALUES FROM ('2022-03-01') TO ('2022-03-10'), - PARTITION sales_jan2022_1decade FOR VALUES FROM ('2022-01-01') TO ('2022-01-10'), - PARTITION sales_feb2022_1decade FOR VALUES FROM ('2022-02-01') TO ('2022-02-10'), - PARTITION sales_apr2022_1decade FOR VALUES FROM ('2022-04-01') TO ('2022-04-10')); - -SELECT tableoid::regclass, * FROM sales_range ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - -DROP TABLE sales_range; - --- --- Test for: --- * split non-DEFAULT partition to partitions with spaces between bounds; --- * random order of partitions in SPLIT PARTITION command. --- -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_all PARTITION OF sales_range FOR VALUES FROM ('2022-01-01') TO ('2022-05-01'); - -INSERT INTO sales_range VALUES - (1, 'May', 1000, '2022-01-31'), - (2, 'Smirnoff', 500, '2022-02-09'), - (3, 'Ford', 2000, '2022-04-30'), - (4, 'Ivanov', 750, '2022-04-13'), - (5, 'Deev', 250, '2022-04-07'), - (6, 'Poirot', 150, '2022-02-07'), - (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'); - -ALTER TABLE sales_range SPLIT PARTITION sales_all INTO - (PARTITION sales_mar2022_1decade FOR VALUES FROM ('2022-03-01') TO ('2022-03-10'), - PARTITION sales_jan2022_1decade FOR VALUES FROM ('2022-01-01') TO ('2022-01-10'), - PARTITION sales_feb2022_1decade FOR VALUES FROM ('2022-02-01') TO ('2022-02-10'), - PARTITION sales_apr2022_1decade FOR VALUES FROM ('2022-04-01') TO ('2022-04-10'), - PARTITION sales_others DEFAULT); - -SELECT tableoid::regclass, * FROM sales_range ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - -DROP TABLE sales_range; - --- --- Test for split non-DEFAULT partition to DEFAULT partition + partitions --- with spaces between bounds. --- -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_all PARTITION OF sales_range FOR VALUES FROM ('2022-02-01') TO ('2022-05-01'); - -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'); - -ALTER TABLE sales_range SPLIT PARTITION sales_all INTO - (PARTITION sales_apr2022 FOR VALUES FROM ('2022-04-01') TO ('2022-05-01'), - PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), - PARTITION sales_others DEFAULT); - -INSERT INTO sales_range VALUES (14, 'Smith', 510, '2022-05-04'); - -SELECT tableoid::regclass, * FROM sales_range ORDER BY tableoid::regclass::text COLLATE "C", salesperson_id; - -DROP TABLE sales_range; - --- --- Test that SPLIT PARTITION rejects the degenerate case where the only --- non-DEFAULT replacement partition keeps the original bound and the command --- merely adds a DEFAULT partition. --- -CREATE TABLE t (i int) PARTITION BY RANGE (i); -CREATE TABLE tp_0_50 PARTITION OF t FOR VALUES FROM (0) TO (50); -INSERT INTO t VALUES (1); - --- ERROR -ALTER TABLE t SPLIT PARTITION tp_0_50 INTO - (PARTITION tp_0_50 FOR VALUES FROM (0) TO (50), - PARTITION tp_default DEFAULT); - -DROP TABLE t; - --- --- Test that a LIST split with DEFAULT is not considered degenerate when --- only NULL is removed from the explicit replacement partition. --- -CREATE TABLE t (i int) PARTITION BY LIST (i); -CREATE TABLE tp_null_1 PARTITION OF t FOR VALUES IN (NULL, 1); - -ALTER TABLE t SPLIT PARTITION tp_null_1 INTO - (PARTITION tp_1 FOR VALUES IN (1), - PARTITION tp_default DEFAULT); - -INSERT INTO t VALUES (NULL), (1), (2); -SELECT tableoid::regclass, i FROM t ORDER BY tableoid::regclass::text COLLATE "C", i NULLS FIRST; - -DROP TABLE t; - --- --- Test that the same-bound check for LIST partitioning uses the --- partition operator family, not byte equality. -0.0 and 0.0 have --- different bit patterns but compare equal under float8, so the --- replacement bound (-0.0, 1.0) is the same set as the original --- (0.0, 1.0) and the SPLIT is degenerate. A datumIsEqual()-based --- check would let this through; the partsupfunc-based check correctly --- rejects it. --- -CREATE TABLE t (v float8) PARTITION BY LIST (v); -CREATE TABLE tp_zero_one PARTITION OF t FOR VALUES IN (0.0, 1.0); - --- ERROR -ALTER TABLE t SPLIT PARTITION tp_zero_one INTO - (PARTITION tp_zero_one FOR VALUES IN (-0.0, 1.0), - PARTITION tp_default DEFAULT); - -DROP TABLE t; - --- --- Test that the explicit partition bound cannot extend outside the split --- partition's bound when a DEFAULT partition is specified. --- -CREATE TABLE t (i int) PARTITION BY RANGE (i); -CREATE TABLE tp_0_51 PARTITION OF t FOR VALUES FROM (0) TO (51); -CREATE TABLE tp_51_100 PARTITION OF t FOR VALUES FROM (51) TO (100); - --- ERROR -ALTER TABLE t SPLIT PARTITION tp_0_51 INTO - (PARTITION tp_0_51 FOR VALUES FROM (0) TO (53), - PARTITION tp_default DEFAULT); - -DROP TABLE t; - --- --- Try to SPLIT partition of another table. --- -CREATE TABLE t1(i int, t text) PARTITION BY LIST (t); -CREATE TABLE t1pa PARTITION OF t1 FOR VALUES IN ('A'); -CREATE TABLE t2 (i int, t text) PARTITION BY RANGE (t); - --- ERROR -ALTER TABLE t2 SPLIT PARTITION t1pa INTO - (PARTITION t2a FOR VALUES FROM ('A') TO ('B'), - PARTITION t2b FOR VALUES FROM ('B') TO ('C')); - -DROP TABLE t2; -DROP TABLE t1; - --- --- Try to SPLIT partition of temporary table. --- -CREATE TEMP TABLE t (i int) PARTITION BY RANGE (i); -CREATE TEMP TABLE tp_0_2 PARTITION OF t FOR VALUES FROM (0) TO (2); - -SELECT c.oid::pg_catalog.regclass, pg_catalog.pg_get_expr(c.relpartbound, c.oid), c.relpersistence - FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i - WHERE c.oid = i.inhrelid AND i.inhparent = 't'::regclass - ORDER BY pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT', c.oid::pg_catalog.regclass::pg_catalog.text COLLATE "C"; - --- ERROR -ALTER TABLE t SPLIT PARTITION tp_0_2 INTO - (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), - PARTITION tp_1_2 FOR VALUES FROM (1) TO (2)); - -ALTER TABLE t SPLIT PARTITION tp_0_2 INTO - (PARTITION pg_temp.tp_0_1 FOR VALUES FROM (0) TO (1), - PARTITION pg_temp.tp_1_2 FOR VALUES FROM (1) TO (2)); - --- Partitions should be temporary. -SELECT c.oid::pg_catalog.regclass, pg_catalog.pg_get_expr(c.relpartbound, c.oid), c.relpersistence - FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i - WHERE c.oid = i.inhrelid AND i.inhparent = 't'::regclass - ORDER BY pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT', c.oid::pg_catalog.regclass::pg_catalog.text COLLATE "C"; - -DROP TABLE t; - --- Check the new partitions inherit parent's tablespace -CREATE TABLE t (i int PRIMARY KEY USING INDEX TABLESPACE regress_tblspace) - PARTITION BY RANGE (i) TABLESPACE regress_tblspace; -CREATE TABLE tp_0_2 PARTITION OF t FOR VALUES FROM (0) TO (2); -ALTER TABLE t SPLIT PARTITION tp_0_2 INTO - (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), - PARTITION tp_1_2 FOR VALUES FROM (1) TO (2)); -SELECT tablename, tablespace FROM pg_tables - WHERE tablename IN ('t', 'tp_0_1', 'tp_1_2') AND schemaname = 'partition_split_schema' - ORDER BY tablename COLLATE "C", tablespace COLLATE "C"; -SELECT tablename, indexname, tablespace FROM pg_indexes - WHERE tablename IN ('t', 'tp_0_1', 'tp_1_2') AND schemaname = 'partition_split_schema' - ORDER BY tablename COLLATE "C", indexname COLLATE "C", tablespace COLLATE "C"; -DROP TABLE t; - --- Check new partitions inherits parent's table access method -CREATE ACCESS METHOD partition_split_heap TYPE TABLE HANDLER heap_tableam_handler; -CREATE TABLE t (i int) PARTITION BY RANGE (i) USING partition_split_heap; -CREATE TABLE tp_0_2 PARTITION OF t FOR VALUES FROM (0) TO (2); -ALTER TABLE t SPLIT PARTITION tp_0_2 INTO - (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), - PARTITION tp_1_2 FOR VALUES FROM (1) TO (2)); -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_1'::regclass, 'tp_1_2'::regclass) -ORDER BY c.relname COLLATE "C"; -DROP TABLE t; -DROP ACCESS METHOD partition_split_heap; - --- Split partition of a temporary table when one of the partitions after --- split has the same name as the partition being split -CREATE TEMP TABLE t (a int) PARTITION BY RANGE (a); -CREATE TEMP TABLE tp_0 PARTITION OF t FOR VALUES FROM (0) TO (2); -ALTER TABLE t SPLIT PARTITION tp_0 INTO - (PARTITION pg_temp.tp_0 FOR VALUES FROM (0) TO (1), - PARTITION pg_temp.tp_1 FOR VALUES FROM (1) TO (2)); -DROP TABLE t; - --- Check defaults and constraints of new partitions -CREATE TABLE t_bigint ( - b bigint, - i int DEFAULT (3+10), - j int DEFAULT 101, - k int GENERATED ALWAYS AS (b+10) STORED -) -PARTITION BY RANGE (b); -CREATE TABLE t_bigint_default PARTITION OF t_bigint DEFAULT; --- Show defaults/constraints before SPLIT PARTITION -\d+ t_bigint -\d+ t_bigint_default -ALTER TABLE t_bigint SPLIT PARTITION t_bigint_default INTO - (PARTITION t_bigint_01_10 FOR VALUES FROM (0) TO (10), - PARTITION t_bigint_default DEFAULT); --- Show defaults/constraints after SPLIT PARTITION -\d+ t_bigint_default -\d+ t_bigint_01_10 -DROP TABLE t_bigint; - --- Test permission checks. The user needs to own the parent table and the --- the partition to split to do the split. -CREATE ROLE regress_partition_split_alice; -CREATE ROLE regress_partition_split_bob; -GRANT ALL ON SCHEMA partition_split_schema TO regress_partition_split_alice; -GRANT ALL ON SCHEMA partition_split_schema TO regress_partition_split_bob; - -SET SESSION AUTHORIZATION regress_partition_split_alice; -CREATE TABLE t (i int) PARTITION BY RANGE (i); -CREATE TABLE tp_0_2 PARTITION OF t FOR VALUES FROM (0) TO (2); - -SET SESSION AUTHORIZATION regress_partition_split_bob; -ALTER TABLE t SPLIT PARTITION tp_0_2 INTO - (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), - PARTITION tp_1_2 FOR VALUES FROM (1) TO (2)); --error -RESET SESSION AUTHORIZATION; - -ALTER TABLE t OWNER TO regress_partition_split_bob; -SET SESSION AUTHORIZATION regress_partition_split_bob; -ALTER TABLE t SPLIT PARTITION tp_0_2 INTO - (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), - PARTITION tp_1_2 FOR VALUES FROM (1) TO (2)); --error -RESET SESSION AUTHORIZATION; - -ALTER TABLE tp_0_2 OWNER TO regress_partition_split_bob; -SET SESSION AUTHORIZATION regress_partition_split_bob; -ALTER TABLE t SPLIT PARTITION tp_0_2 INTO - (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), - PARTITION tp_1_2 FOR VALUES FROM (1) TO (2)); --ok -RESET SESSION AUTHORIZATION; - -DROP TABLE t; - --- Test: owner of new partitions should be the same as owner of split partition -CREATE TABLE t (i int) PARTITION BY RANGE (i); - -SET SESSION AUTHORIZATION regress_partition_split_alice; -CREATE TABLE tp_0_2(i int); -RESET SESSION AUTHORIZATION; - -ALTER TABLE t ATTACH PARTITION tp_0_2 FOR VALUES FROM (0) TO (2); - --- Owner is 'regress_partition_split_alice': -\dt tp_0_2 - -ALTER TABLE t SPLIT PARTITION tp_0_2 INTO - (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), - PARTITION tp_1_2 FOR VALUES FROM (1) TO (2)); - --- Owner should be 'regress_partition_split_alice': -\dt tp_0_1 -\dt tp_1_2 - -DROP TABLE t; - --- Test: index of new partitions should be created with same owner as split --- partition -SET SESSION AUTHORIZATION regress_partition_split_alice; -CREATE TABLE t (i int) PARTITION BY RANGE (i); -CREATE TABLE tp_10_20 PARTITION OF t FOR VALUES FROM (10) TO (20); -INSERT INTO t VALUES (11), (16); -CREATE OR REPLACE FUNCTION run_me(integer) RETURNS integer AS $$ -BEGIN - RAISE NOTICE 'you are running me as %', CURRENT_USER; - RETURN $1; -END -$$ LANGUAGE PLPGSQL IMMUTABLE; - --- Owner is 'regress_partition_split_alice': -CREATE INDEX ON t (run_me(i)); -RESET SESSION AUTHORIZATION; - --- Owner should be 'regress_partition_split_alice': -ALTER TABLE t SPLIT PARTITION tp_10_20 INTO - (PARTITION tp_10_15 FOR VALUES FROM (10) TO (15), - PARTITION tp_15_20 FOR VALUES FROM (15) TO (20)); - -DROP TABLE t; -DROP FUNCTION run_me(integer); - -REVOKE ALL ON SCHEMA partition_split_schema FROM regress_partition_split_alice; -REVOKE ALL ON SCHEMA partition_split_schema FROM regress_partition_split_bob; -DROP ROLE regress_partition_split_alice; -DROP ROLE regress_partition_split_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 SPLIT PARTITION tp1 INTO - (PARTITION tp1_1 FOR VALUES WITH (MODULUS 4, REMAINDER 0), - PARTITION tp1_2 FOR VALUES WITH (MODULUS 4, REMAINDER 2)); - --- ERROR -ALTER TABLE t SPLIT PARTITION tp1 INTO - (PARTITION tp1_1 FOR VALUES WITH (MODULUS 4, REMAINDER 0)); - -DROP TABLE t; - - --- Test for split 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 - -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_x -(i int NOT NULL, - t text STORAGE MAIN DEFAULT 'default_tp_x', - b bigint, - d date GENERATED ALWAYS as ('2022-02-02') STORED); -ALTER TABLE t ATTACH PARTITION tp_x FOR VALUES FROM (0) TO (2); -COMMENT ON COLUMN tp_x.i IS 'tp_x.i'; - -CREATE STATISTICS t_stat (DEPENDENCIES) on i, b from t; -CREATE STATISTICS tp_x_stat (DEPENDENCIES) on i, b from tp_x; - -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_x(i, t, b) VALUES(0, DEFAULT, 1); -INSERT INTO tp_x(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_x_before_insert_row_trigger BEFORE INSERT ON tp_x FOR EACH ROW - EXECUTE PROCEDURE trigger_function('tp_x'); - -\d+ tp_x -ALTER TABLE t SPLIT PARTITION tp_x INTO - (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), - PARTITION tp_x FOR VALUES FROM (1) TO (2)); -\d+ tp_x - -INSERT INTO t(i, t, b) VALUES(1, DEFAULT, 3); -SELECT tableoid::regclass, * FROM t ORDER BY tableoid::regclass::text COLLATE "C", b; -DROP TABLE t; -DROP FUNCTION trigger_function(); - - --- 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_2 PARTITION OF t FOR VALUES FROM (0) TO (2); -ALTER TABLE t ADD CONSTRAINT cc CHECK(tableoid <> 123456789); -INSERT INTO t VALUES (0), (1); - --- 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); - --- "tab_id" column (stored generated column) with "tableoid" attribute requires --- recomputation here. -ALTER TABLE t SPLIT PARTITION tp_0_2 INTO - (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), - PARTITION tp_1_2 FOR VALUES FROM (1) TO (2)); - --- 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); - -DROP TABLE t; - --- Each new partition produced by SPLIT must get its own TOAST table so --- that out-of-line varlena attributes coming from the source partition --- can 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_all PARTITION OF t FOR VALUES FROM (MINVALUE) TO (MAXVALUE); -INSERT INTO t SELECT repeat('1', 10000); -ALTER TABLE t SPLIT PARTITION tp_all INTO ( - PARTITION tp_lo FOR VALUES FROM (MINVALUE) TO ('2'), - PARTITION tp_hi FOR VALUES FROM ('2') TO (MAXVALUE) -); -SELECT relname, - reltoastrelid <> 0 AS has_toast, - pg_relation_size(reltoastrelid) > 0 AS toast_used - FROM pg_class WHERE relname IN ('tp_lo', 'tp_hi') ORDER BY relname; -SELECT length(a) FROM t; -DROP TABLE t; - --- Tablespace selection for the new partitions 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_all PARTITION OF t FOR VALUES FROM (0) TO (10); -INSERT INTO t SELECT generate_series(0, 9); -ALTER TABLE t SPLIT PARTITION tp_all INTO ( - PARTITION tp_lo FOR VALUES FROM (0) TO (5), - PARTITION tp_hi FOR VALUES FROM (5) TO (10) -); -SELECT c.relname, s.spcname FROM pg_class c LEFT JOIN pg_tablespace s - ON c.reltablespace = s.oid WHERE c.relname IN ('tp_lo', 'tp_hi') - ORDER BY c.relname; -DROP TABLE t; - --- Parent has no explicit tablespace, but default_tablespace is set: the --- new partitions land on default_tablespace. -CREATE TABLE t (i int) PARTITION BY RANGE(i); -CREATE TABLE tp_all PARTITION OF t FOR VALUES FROM (0) TO (10); -INSERT INTO t SELECT generate_series(0, 9); -SET default_tablespace TO regress_tblspace; -ALTER TABLE t SPLIT PARTITION tp_all INTO ( - PARTITION tp_lo FOR VALUES FROM (0) TO (5), - PARTITION tp_hi FOR VALUES FROM (5) TO (10) -); -RESET default_tablespace; -SELECT c.relname, s.spcname FROM pg_class c LEFT JOIN pg_tablespace s - ON c.reltablespace = s.oid WHERE c.relname IN ('tp_lo', 'tp_hi') - ORDER BY c.relname; -DROP TABLE t; - -CREATE TABLE t (i int) PARTITION BY RANGE(i); -CREATE TABLE tp_all PARTITION OF t FOR VALUES FROM (0) 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 SPLIT PARTITION tp_all INTO ( - PARTITION tp_lo FOR VALUES FROM (0) TO (5), - PARTITION tp_hi FOR VALUES FROM (5) TO (10) -); -- fails -RESET default_tablespace; --- Parent has no explicit tablespace and default_tablespace is empty: new --- partitions use the database default (reltablespace = 0). -ALTER TABLE t SPLIT PARTITION tp_all INTO ( - PARTITION tp_lo FOR VALUES FROM (0) TO (5), - PARTITION tp_hi FOR VALUES FROM (5) TO (10) -); -SELECT relname, reltablespace FROM pg_class - WHERE relname IN ('tp_lo', 'tp_hi') ORDER BY relname; -DROP TABLE t; - -RESET search_path; - --- -DROP SCHEMA partition_split_schema; -DROP SCHEMA partition_split_schema2; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 23d4e6c0651..bcf981a02a2 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -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