mirror of
				https://github.com/postgres/postgres.git
				synced 2025-11-03 09:13:20 +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