mirror of
https://github.com/postgres/postgres.git
synced 2025-10-27 00:12:01 +03:00
Revert support for ALTER TABLE ... MERGE/SPLIT PARTITION(S) commands
This commit reverts1adf16b8fb,87c21bb941, and subsequent fixes and improvements includingdf64c81ca9,c99ef1811a,9dfcac8e15,885742b9f8,842c9b2705,fcf80c5d5f,96c7381c4c,f4fc7cb54b,60ae37a8bc,259c96fa8f,449cdcd486,3ca43dbbb6,2a679ae94e,3a82c689fd,fbd4321fd5,d53a4286d7,c086896625,4e5d6c4091,04158e7fa3. The reason for reverting is security issues related to repeatable name lookups (CVE-2014-0062). Even though04158e7fa3solved part of the problem, there are still remaining issues, which aren't feasible to even carefully analyze before the RC deadline. Reported-by: Noah Misch, Robert Haas Discussion: https://postgr.es/m/20240808171351.a9.nmisch%40google.com Backpatch-through: 17
This commit is contained in:
@@ -657,11 +657,6 @@ static void ATDetachCheckNoForeignKeyRefs(Relation partition);
|
||||
static char GetAttributeCompression(Oid atttypid, const char *compression);
|
||||
static char GetAttributeStorage(Oid atttypid, const char *storagemode);
|
||||
|
||||
static void ATExecSplitPartition(List **wqueue, AlteredTableInfo *tab,
|
||||
Relation rel, PartitionCmd *cmd,
|
||||
AlterTableUtilityContext *context);
|
||||
static void ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
|
||||
PartitionCmd *cmd, AlterTableUtilityContext *context);
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* DefineRelation
|
||||
@@ -4672,14 +4667,6 @@ AlterTableGetLockLevel(List *cmds)
|
||||
cmd_lockmode = ShareUpdateExclusiveLock;
|
||||
break;
|
||||
|
||||
case AT_SplitPartition:
|
||||
cmd_lockmode = AccessExclusiveLock;
|
||||
break;
|
||||
|
||||
case AT_MergePartitions:
|
||||
cmd_lockmode = AccessExclusiveLock;
|
||||
break;
|
||||
|
||||
case AT_CheckNotNull:
|
||||
|
||||
/*
|
||||
@@ -5106,16 +5093,6 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
|
||||
/* No command-specific prep needed */
|
||||
pass = AT_PASS_MISC;
|
||||
break;
|
||||
case AT_SplitPartition:
|
||||
ATSimplePermissions(cmd->subtype, rel, ATT_TABLE);
|
||||
/* No command-specific prep needed */
|
||||
pass = AT_PASS_MISC;
|
||||
break;
|
||||
case AT_MergePartitions:
|
||||
ATSimplePermissions(cmd->subtype, rel, ATT_TABLE);
|
||||
/* No command-specific prep needed */
|
||||
pass = AT_PASS_MISC;
|
||||
break;
|
||||
default: /* oops */
|
||||
elog(ERROR, "unrecognized alter table type: %d",
|
||||
(int) cmd->subtype);
|
||||
@@ -5512,22 +5489,6 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
|
||||
case AT_DetachPartitionFinalize:
|
||||
address = ATExecDetachPartitionFinalize(rel, ((PartitionCmd *) cmd->def)->name);
|
||||
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;
|
||||
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;
|
||||
default: /* oops */
|
||||
elog(ERROR, "unrecognized alter table type: %d",
|
||||
(int) cmd->subtype);
|
||||
@@ -6516,10 +6477,6 @@ alter_table_type_to_string(AlterTableType cmdtype)
|
||||
return "DETACH PARTITION";
|
||||
case AT_DetachPartitionFinalize:
|
||||
return "DETACH PARTITION ... FINALIZE";
|
||||
case AT_SplitPartition:
|
||||
return "SPLIT PARTITION";
|
||||
case AT_MergePartitions:
|
||||
return "MERGE PARTITIONS";
|
||||
case AT_AddIdentity:
|
||||
return "ALTER COLUMN ... ADD IDENTITY";
|
||||
case AT_SetIdentity:
|
||||
@@ -18328,37 +18285,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)
|
||||
{
|
||||
/* OK to create inheritance. Rest of the checks performed there */
|
||||
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 <name> ATTACH PARTITION <partition-name> FOR VALUES
|
||||
*
|
||||
@@ -18561,8 +18487,23 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
|
||||
check_new_partition_bound(RelationGetRelationName(attachrel), rel,
|
||||
cmd->bound, pstate);
|
||||
|
||||
/* Attach a new partition to the partitioned table. */
|
||||
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);
|
||||
|
||||
/*
|
||||
* 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.
|
||||
@@ -20077,729 +20018,3 @@ GetAttributeStorage(Oid atttypid, const char *storagemode)
|
||||
|
||||
return cstorage;
|
||||
}
|
||||
|
||||
/*
|
||||
* Struct with context of new partition for inserting rows from split partition
|
||||
*/
|
||||
typedef struct SplitPartitionContext
|
||||
{
|
||||
ExprState *partqualstate; /* expression for checking slot for partition
|
||||
* (NULL for DEFAULT partition) */
|
||||
BulkInsertState bistate; /* state of bulk inserts for partition */
|
||||
TupleTableSlot *dstslot; /* slot for inserting row into partition */
|
||||
Relation partRel; /* relation for partition */
|
||||
} SplitPartitionContext;
|
||||
|
||||
|
||||
/*
|
||||
* createSplitPartitionContext: create context for partition and fill it
|
||||
*/
|
||||
static SplitPartitionContext *
|
||||
createSplitPartitionContext(Relation partRel)
|
||||
{
|
||||
SplitPartitionContext *pc;
|
||||
|
||||
pc = (SplitPartitionContext *) palloc0(sizeof(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 tuple slot for new partition. */
|
||||
pc->dstslot = MakeSingleTupleTableSlot(RelationGetDescr(pc->partRel),
|
||||
table_slot_callbacks(pc->partRel));
|
||||
ExecStoreAllNullTuple(pc->dstslot);
|
||||
|
||||
return pc;
|
||||
}
|
||||
|
||||
/*
|
||||
* deleteSplitPartitionContext: delete context for partition
|
||||
*/
|
||||
static void
|
||||
deleteSplitPartitionContext(SplitPartitionContext *pc, int ti_options)
|
||||
{
|
||||
ExecDropSingleTupleTableSlot(pc->dstslot);
|
||||
FreeBulkInsertState(pc->bistate);
|
||||
|
||||
table_finish_bulk_insert(pc->partRel, ti_options);
|
||||
|
||||
pfree(pc);
|
||||
}
|
||||
|
||||
/*
|
||||
* moveSplitTableRows: scan split partition (splitRel) of partitioned table
|
||||
* (rel) and move rows into new partitions.
|
||||
*
|
||||
* New partitions description:
|
||||
* partlist: list of pointers to SinglePartitionSpec structures.
|
||||
* newPartRels: list of Relations.
|
||||
* defaultPartOid: oid of DEFAULT partition, for table rel.
|
||||
*/
|
||||
static void
|
||||
moveSplitTableRows(Relation rel, Relation splitRel, List *partlist, List *newPartRels, Oid defaultPartOid)
|
||||
{
|
||||
/* The FSM is empty, so don't bother using it. */
|
||||
int 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;
|
||||
bool isOldDefaultPart = false;
|
||||
|
||||
mycid = GetCurrentCommandId(true);
|
||||
|
||||
estate = CreateExecutorState();
|
||||
|
||||
forboth(listptr, partlist, listptr2, newPartRels)
|
||||
{
|
||||
SinglePartitionSpec *sps = (SinglePartitionSpec *) lfirst(listptr);
|
||||
|
||||
pc = createSplitPartitionContext((Relation) lfirst(listptr2));
|
||||
|
||||
if (sps->bound->is_default)
|
||||
{
|
||||
/* We should not create constraint for detached 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 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 list. */
|
||||
partContexts = lappend(partContexts, pc);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create partition context for DEFAULT partition. We can insert values
|
||||
* into this partition in case spaces with values between new partitions.
|
||||
*/
|
||||
if (!defaultPartCtx && OidIsValid(defaultPartOid))
|
||||
{
|
||||
/* Indicate that we allocate context for old DEFAULT partition */
|
||||
isOldDefaultPart = true;
|
||||
defaultPartCtx = createSplitPartitionContext(table_open(defaultPartOid, AccessExclusiveLock));
|
||||
}
|
||||
|
||||
econtext = GetPerTupleExprContext(estate);
|
||||
|
||||
/* Create necessary tuple slot. */
|
||||
srcslot = MakeSingleTupleTableSlot(RelationGetDescr(splitRel),
|
||||
table_slot_callbacks(splitRel));
|
||||
|
||||
/*
|
||||
* Map computing for moving attributes of split partition to new partition
|
||||
* (for 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);
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/* Extract data from old tuple. */
|
||||
slot_getallattrs(srcslot);
|
||||
|
||||
econtext->ecxt_scantuple = srcslot;
|
||||
|
||||
/* Search partition for current slot srcslot. */
|
||||
foreach(listptr, partContexts)
|
||||
{
|
||||
pc = (SplitPartitionContext *) lfirst(listptr);
|
||||
|
||||
if (pc->partqualstate /* skip DEFAULT partition */ &&
|
||||
ExecCheck(pc->partqualstate, econtext))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
ResetExprContext(econtext);
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
/* Use DEFAULT partition if it exists. */
|
||||
if (defaultPartCtx)
|
||||
pc = defaultPartCtx;
|
||||
else
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_CHECK_VIOLATION),
|
||||
errmsg("can not find partition for split partition row"),
|
||||
errtable(splitRel)));
|
||||
}
|
||||
|
||||
if (tuple_map)
|
||||
{
|
||||
/* Need to use map to copy attributes. */
|
||||
insertslot = execute_attr_map_slot(tuple_map->attrMap, srcslot, pc->dstslot);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* 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);
|
||||
}
|
||||
|
||||
/* Write the tuple out to the new relation. */
|
||||
table_tuple_insert(pc->partRel, insertslot, mycid,
|
||||
ti_options, pc->bistate);
|
||||
|
||||
ResetExprContext(econtext);
|
||||
|
||||
CHECK_FOR_INTERRUPTS();
|
||||
}
|
||||
|
||||
MemoryContextSwitchTo(oldCxt);
|
||||
|
||||
table_endscan(scan);
|
||||
UnregisterSnapshot(snapshot);
|
||||
|
||||
if (tuple_map)
|
||||
free_conversion_map(tuple_map);
|
||||
|
||||
ExecDropSingleTupleTableSlot(srcslot);
|
||||
|
||||
FreeExecutorState(estate);
|
||||
|
||||
foreach(listptr, partContexts)
|
||||
deleteSplitPartitionContext((SplitPartitionContext *) lfirst(listptr), ti_options);
|
||||
|
||||
/* Need to close table and free buffers for DEFAULT partition. */
|
||||
if (isOldDefaultPart)
|
||||
{
|
||||
Relation defaultPartRel = defaultPartCtx->partRel;
|
||||
|
||||
deleteSplitPartitionContext(defaultPartCtx, ti_options);
|
||||
/* Keep the lock until commit. */
|
||||
table_close(defaultPartRel, NoLock);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* createPartitionTable: create table for a new partition with given name
|
||||
* (newPartName) like table (modelRel)
|
||||
*
|
||||
* Emulates command: CREATE [TEMP] TABLE <newPartName> (LIKE <modelRel's name>
|
||||
* INCLUDING ALL EXCLUDING INDEXES EXCLUDING IDENTITY EXCLUDING STATISTICS)
|
||||
*
|
||||
* Also, this function sets the new partition access method same as parent
|
||||
* table access methods (similarly to CREATE TABLE ... PARTITION OF). It
|
||||
* checks that parent and child tables have compatible persistence.
|
||||
*
|
||||
* Function returns the created relation (locked in AccessExclusiveLock mode).
|
||||
*/
|
||||
static Relation
|
||||
createPartitionTable(RangeVar *newPartName, Relation modelRel,
|
||||
AlterTableUtilityContext *context)
|
||||
{
|
||||
CreateStmt *createStmt;
|
||||
TableLikeClause *tlc;
|
||||
PlannedStmt *wrapper;
|
||||
Relation newRel;
|
||||
|
||||
/* If existing rel is temp, it must belong to this session */
|
||||
if (modelRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
|
||||
!modelRel->rd_islocaltemp)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("cannot create as partition of temporary relation of another session")));
|
||||
|
||||
/* New partition should have the same persistence as modelRel */
|
||||
newPartName->relpersistence = modelRel->rd_rel->relpersistence;
|
||||
|
||||
createStmt = makeNode(CreateStmt);
|
||||
createStmt->relation = newPartName;
|
||||
createStmt->tableElts = NIL;
|
||||
createStmt->inhRelations = NIL;
|
||||
createStmt->constraints = NIL;
|
||||
createStmt->options = NIL;
|
||||
createStmt->oncommit = ONCOMMIT_NOOP;
|
||||
createStmt->tablespacename = get_tablespace_name(modelRel->rd_rel->reltablespace);
|
||||
createStmt->if_not_exists = false;
|
||||
createStmt->accessMethod = get_am_name(modelRel->rd_rel->relam);
|
||||
|
||||
tlc = makeNode(TableLikeClause);
|
||||
tlc->relation = makeRangeVar(get_namespace_name(RelationGetNamespace(modelRel)),
|
||||
RelationGetRelationName(modelRel), -1);
|
||||
|
||||
/*
|
||||
* Indexes will be inherited on "attach new partitions" stage, after data
|
||||
* moving. We also don't copy the extended statistics for consistency
|
||||
* with CREATE TABLE PARTITION OF.
|
||||
*/
|
||||
tlc->options = CREATE_TABLE_LIKE_ALL &
|
||||
~(CREATE_TABLE_LIKE_INDEXES | CREATE_TABLE_LIKE_IDENTITY | CREATE_TABLE_LIKE_STATISTICS);
|
||||
tlc->relationOid = InvalidOid;
|
||||
tlc->newRelationOid = InvalidOid;
|
||||
createStmt->tableElts = lappend(createStmt->tableElts, tlc);
|
||||
|
||||
/* Need to make a wrapper PlannedStmt. */
|
||||
wrapper = makeNode(PlannedStmt);
|
||||
wrapper->commandType = CMD_UTILITY;
|
||||
wrapper->canSetTag = false;
|
||||
wrapper->utilityStmt = (Node *) createStmt;
|
||||
wrapper->stmt_location = context->pstmt->stmt_location;
|
||||
wrapper->stmt_len = context->pstmt->stmt_len;
|
||||
|
||||
ProcessUtility(wrapper,
|
||||
context->queryString,
|
||||
false,
|
||||
PROCESS_UTILITY_SUBCOMMAND,
|
||||
NULL,
|
||||
NULL,
|
||||
None_Receiver,
|
||||
NULL);
|
||||
|
||||
/*
|
||||
* Open the new partition with no lock, because we already have
|
||||
* AccessExclusiveLock placed there after creation.
|
||||
*/
|
||||
newRel = table_open(tlc->newRelationOid, NoLock);
|
||||
|
||||
/*
|
||||
* 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 (modelRel->rd_rel->relpersistence != RELPERSISTENCE_TEMP &&
|
||||
newRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("cannot create a temporary relation as partition of permanent relation \"%s\"",
|
||||
RelationGetRelationName(modelRel))));
|
||||
|
||||
/* Permanent rels cannot be partitions belonging to temporary parent */
|
||||
if (newRel->rd_rel->relpersistence != RELPERSISTENCE_TEMP &&
|
||||
modelRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("cannot create a permanent relation as partition of temporary relation \"%s\"",
|
||||
RelationGetRelationName(modelRel))));
|
||||
|
||||
return newRel;
|
||||
}
|
||||
|
||||
/*
|
||||
* ALTER TABLE <name> SPLIT PARTITION <partition-name> INTO <partition-list>
|
||||
*/
|
||||
static void
|
||||
ATExecSplitPartition(List **wqueue, AlteredTableInfo *tab, Relation rel,
|
||||
PartitionCmd *cmd, AlterTableUtilityContext *context)
|
||||
{
|
||||
Relation splitRel;
|
||||
Oid splitRelOid;
|
||||
char relname[NAMEDATALEN];
|
||||
Oid namespaceId;
|
||||
ListCell *listptr,
|
||||
*listptr2;
|
||||
bool isSameName = false;
|
||||
char tmpRelName[NAMEDATALEN];
|
||||
List *newPartRels = NIL;
|
||||
ObjectAddress object;
|
||||
Oid defaultPartOid;
|
||||
|
||||
defaultPartOid = get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true));
|
||||
|
||||
/*
|
||||
* We are going to detach and remove this partition: need to use exclusive
|
||||
* lock for preventing DML-queries to the partition.
|
||||
*/
|
||||
splitRel = table_openrv(cmd->name, AccessExclusiveLock);
|
||||
|
||||
splitRelOid = RelationGetRelid(splitRel);
|
||||
|
||||
/* Check descriptions of new partitions. */
|
||||
foreach(listptr, cmd->partlist)
|
||||
{
|
||||
Oid existing_relid;
|
||||
SinglePartitionSpec *sps = (SinglePartitionSpec *) lfirst(listptr);
|
||||
|
||||
strlcpy(relname, sps->name->relname, NAMEDATALEN);
|
||||
|
||||
/*
|
||||
* Look up the namespace in which we are supposed to create the
|
||||
* partition, check we have permission to create there, lock it
|
||||
* against concurrent drop, and mark stmt->relation as
|
||||
* RELPERSISTENCE_TEMP if a temporary namespace is selected.
|
||||
*/
|
||||
sps->name->relpersistence = rel->rd_rel->relpersistence;
|
||||
namespaceId =
|
||||
RangeVarGetAndCheckCreationNamespace(sps->name, NoLock, NULL);
|
||||
|
||||
/*
|
||||
* This would fail later on anyway if the relation already exists. But
|
||||
* by catching it here we can emit a nicer error message.
|
||||
*/
|
||||
existing_relid = get_relname_relid(relname, namespaceId);
|
||||
if (existing_relid == splitRelOid && !isSameName)
|
||||
/* One new partition can have the same name as split partition. */
|
||||
isSameName = true;
|
||||
else if (existing_relid != InvalidOid)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DUPLICATE_TABLE),
|
||||
errmsg("relation \"%s\" already exists", relname)));
|
||||
}
|
||||
|
||||
/* Detach split partition. */
|
||||
RemoveInheritance(splitRel, rel, false);
|
||||
/* Do the final part of detaching. */
|
||||
DetachPartitionFinalize(rel, splitRel, false, defaultPartOid);
|
||||
|
||||
/*
|
||||
* If new partition has the same name as split partition then we should
|
||||
* rename split partition for reusing 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, false, false);
|
||||
|
||||
/*
|
||||
* We must bump the command counter to make the split partition tuple
|
||||
* visible after renaming.
|
||||
*/
|
||||
CommandCounterIncrement();
|
||||
}
|
||||
|
||||
/* Create new partitions (like split partition), without indexes. */
|
||||
foreach(listptr, cmd->partlist)
|
||||
{
|
||||
SinglePartitionSpec *sps = (SinglePartitionSpec *) lfirst(listptr);
|
||||
Relation newPartRel;
|
||||
|
||||
newPartRel = createPartitionTable(sps->name, rel, context);
|
||||
newPartRels = lappend(newPartRels, newPartRel);
|
||||
}
|
||||
|
||||
/* Copy data from split partition to new partitions. */
|
||||
moveSplitTableRows(rel, splitRel, cmd->partlist, newPartRels, defaultPartOid);
|
||||
/* Keep the lock until commit. */
|
||||
table_close(splitRel, NoLock);
|
||||
|
||||
/* Attach new partitions to 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);
|
||||
/* Keep the lock until commit. */
|
||||
table_close(newPartRel, NoLock);
|
||||
}
|
||||
|
||||
/* Drop split partition. */
|
||||
object.classId = RelationRelationId;
|
||||
object.objectId = splitRelOid;
|
||||
object.objectSubId = 0;
|
||||
/* Probably DROP_CASCADE is not needed. */
|
||||
performDeletion(&object, DROP_RESTRICT, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* moveMergedTablesRows: scan partitions to be merged (mergingPartitionsList)
|
||||
* of the partitioned table (rel) and move rows into the new partition
|
||||
* (newPartRel).
|
||||
*/
|
||||
static void
|
||||
moveMergedTablesRows(Relation rel, List *mergingPartitionsList,
|
||||
Relation newPartRel)
|
||||
{
|
||||
CommandId mycid;
|
||||
|
||||
/* The FSM is empty, so don't bother using it. */
|
||||
int ti_options = TABLE_INSERT_SKIP_FSM;
|
||||
ListCell *listptr;
|
||||
BulkInsertState bistate; /* state of bulk inserts for partition */
|
||||
TupleTableSlot *dstslot;
|
||||
|
||||
mycid = GetCurrentCommandId(true);
|
||||
|
||||
/* Prepare a BulkInsertState for table_tuple_insert. */
|
||||
bistate = GetBulkInsertState();
|
||||
|
||||
/* Create necessary tuple slot. */
|
||||
dstslot = MakeSingleTupleTableSlot(RelationGetDescr(newPartRel),
|
||||
table_slot_callbacks(newPartRel));
|
||||
ExecStoreAllNullTuple(dstslot);
|
||||
|
||||
foreach(listptr, mergingPartitionsList)
|
||||
{
|
||||
Relation mergingPartition = (Relation) lfirst(listptr);
|
||||
TupleTableSlot *srcslot;
|
||||
TupleConversionMap *tuple_map;
|
||||
TableScanDesc scan;
|
||||
Snapshot snapshot;
|
||||
|
||||
/* Create tuple slot for new partition. */
|
||||
srcslot = MakeSingleTupleTableSlot(RelationGetDescr(mergingPartition),
|
||||
table_slot_callbacks(mergingPartition));
|
||||
|
||||
/*
|
||||
* Map computing for moving attributes of merged partition to 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);
|
||||
|
||||
while (table_scan_getnextslot(scan, ForwardScanDirection, srcslot))
|
||||
{
|
||||
TupleTableSlot *insertslot;
|
||||
|
||||
/* Extract data from old tuple. */
|
||||
slot_getallattrs(srcslot);
|
||||
|
||||
if (tuple_map)
|
||||
{
|
||||
/* Need to use map to copy attributes. */
|
||||
insertslot = execute_attr_map_slot(tuple_map->attrMap, srcslot, dstslot);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* 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);
|
||||
}
|
||||
|
||||
/* Write the tuple out to the new relation. */
|
||||
table_tuple_insert(newPartRel, insertslot, mycid,
|
||||
ti_options, bistate);
|
||||
|
||||
CHECK_FOR_INTERRUPTS();
|
||||
}
|
||||
|
||||
table_endscan(scan);
|
||||
UnregisterSnapshot(snapshot);
|
||||
|
||||
if (tuple_map)
|
||||
free_conversion_map(tuple_map);
|
||||
|
||||
ExecDropSingleTupleTableSlot(srcslot);
|
||||
}
|
||||
|
||||
ExecDropSingleTupleTableSlot(dstslot);
|
||||
FreeBulkInsertState(bistate);
|
||||
|
||||
table_finish_bulk_insert(newPartRel, ti_options);
|
||||
}
|
||||
|
||||
/*
|
||||
* ALTER TABLE <name> MERGE PARTITIONS <partition-list> INTO <partition-name>
|
||||
*/
|
||||
static void
|
||||
ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
|
||||
PartitionCmd *cmd, AlterTableUtilityContext *context)
|
||||
{
|
||||
Relation newPartRel;
|
||||
ListCell *listptr;
|
||||
List *mergingPartitionsList = NIL;
|
||||
Oid defaultPartOid;
|
||||
Oid namespaceId;
|
||||
Oid existingRelid;
|
||||
|
||||
/*
|
||||
* Lock all merged partitions, check them and create list with partitions
|
||||
* contexts.
|
||||
*/
|
||||
foreach(listptr, cmd->partlist)
|
||||
{
|
||||
RangeVar *name = (RangeVar *) lfirst(listptr);
|
||||
Relation mergingPartition;
|
||||
|
||||
/*
|
||||
* We are going to detach and remove this partition: need to use
|
||||
* exclusive lock for preventing DML-queries to the partition.
|
||||
*/
|
||||
mergingPartition = table_openrv(name, AccessExclusiveLock);
|
||||
|
||||
/* Store a next merging partition into the list. */
|
||||
mergingPartitionsList = lappend(mergingPartitionsList,
|
||||
mergingPartition);
|
||||
}
|
||||
|
||||
/*
|
||||
* Look up the namespace in which we are supposed to create the partition,
|
||||
* check we have permission to create there, lock it against concurrent
|
||||
* drop, and mark stmt->relation as RELPERSISTENCE_TEMP if a temporary
|
||||
* namespace is selected.
|
||||
*/
|
||||
cmd->name->relpersistence = rel->rd_rel->relpersistence;
|
||||
namespaceId =
|
||||
RangeVarGetAndCheckCreationNamespace(cmd->name, NoLock, NULL);
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
existingRelid = get_relname_relid(cmd->name->relname, namespaceId);
|
||||
|
||||
if (OidIsValid(existingRelid))
|
||||
{
|
||||
Relation sameNamePartition = NULL;
|
||||
|
||||
foreach_ptr(RelationData, mergingPartition, mergingPartitionsList)
|
||||
{
|
||||
if (RelationGetRelid(mergingPartition) == existingRelid)
|
||||
{
|
||||
sameNamePartition = mergingPartition;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (sameNamePartition)
|
||||
{
|
||||
/*
|
||||
* The new partition has the same name as one of merging
|
||||
* partitions.
|
||||
*/
|
||||
char tmpRelName[NAMEDATALEN];
|
||||
|
||||
/* Generate 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(RelationGetRelid(sameNamePartition),
|
||||
tmpRelName, false, 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)));
|
||||
}
|
||||
}
|
||||
|
||||
/* Detach all merged partitions. */
|
||||
defaultPartOid =
|
||||
get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true));
|
||||
foreach(listptr, mergingPartitionsList)
|
||||
{
|
||||
Relation mergingPartition = (Relation) lfirst(listptr);
|
||||
|
||||
/* Remove the pg_inherits row first. */
|
||||
RemoveInheritance(mergingPartition, rel, false);
|
||||
/* Do the final part of detaching. */
|
||||
DetachPartitionFinalize(rel, mergingPartition, false, defaultPartOid);
|
||||
}
|
||||
|
||||
/* Create table for new partition, use partitioned table as model. */
|
||||
newPartRel = createPartitionTable(cmd->name, rel, context);
|
||||
|
||||
/* Copy data from merged partitions to new partition. */
|
||||
moveMergedTablesRows(rel, mergingPartitionsList, newPartRel);
|
||||
|
||||
/* Drop the current partitions before attaching the new one. */
|
||||
foreach(listptr, mergingPartitionsList)
|
||||
{
|
||||
ObjectAddress object;
|
||||
Relation mergingPartition = (Relation) lfirst(listptr);
|
||||
|
||||
/* Get relation id before table_close() call. */
|
||||
object.objectId = RelationGetRelid(mergingPartition);
|
||||
object.classId = RelationRelationId;
|
||||
object.objectSubId = 0;
|
||||
|
||||
/* Keep the lock until commit. */
|
||||
table_close(mergingPartition, NoLock);
|
||||
|
||||
performDeletion(&object, DROP_RESTRICT, 0);
|
||||
}
|
||||
list_free(mergingPartitionsList);
|
||||
|
||||
/*
|
||||
* Attach a new partition to the partitioned table. wqueue = NULL:
|
||||
* verification for each cloned constraint is not needed.
|
||||
*/
|
||||
attachPartitionTable(NULL, rel, newPartRel, cmd->bound);
|
||||
|
||||
/* Keep the lock until commit. */
|
||||
table_close(newPartRel, NoLock);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user