Thread

  1. [PATCH v1 1/1] move partition code in tablecmds.c to new file

    Nathan Bossart <nathan@postgresql.org> — 2025-12-01T22:26:39Z

    ---
     src/backend/commands/Makefile         |    1 +
     src/backend/commands/meson.build      |    1 +
     src/backend/commands/partcmds.c       | 3377 ++++++++++++++++++++++++
     src/backend/commands/tablecmds.c      | 3456 +------------------------
     src/backend/partitioning/partbounds.c |    1 +
     src/include/commands/partcmds.h       |   53 +
     src/include/commands/tablecmds.h      |  134 +-
     7 files changed, 3575 insertions(+), 3448 deletions(-)
     create mode 100644 src/backend/commands/partcmds.c
     create mode 100644 src/include/commands/partcmds.h
    
    diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
    index 64cb6278409..6ffb3fed2be 100644
    --- a/src/backend/commands/Makefile
    +++ b/src/backend/commands/Makefile
    @@ -45,6 +45,7 @@ OBJS = \
     	matview.o \
     	opclasscmds.o \
     	operatorcmds.o \
    +	partcmds.o \
     	policy.o \
     	portalcmds.o \
     	prepare.o \
    diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
    index 5fc35826b1c..155e9e294dc 100644
    --- a/src/backend/commands/meson.build
    +++ b/src/backend/commands/meson.build
    @@ -33,6 +33,7 @@ backend_sources += files(
       'matview.c',
       'opclasscmds.c',
       'operatorcmds.c',
    +  'partcmds.c',
       'policy.c',
       'portalcmds.c',
       'prepare.c',
    diff --git a/src/backend/commands/partcmds.c b/src/backend/commands/partcmds.c
    new file mode 100644
    index 00000000000..0f72f698df5
    --- /dev/null
    +++ b/src/backend/commands/partcmds.c
    @@ -0,0 +1,3377 @@
    +/*-------------------------------------------------------------------------
    + *
    + * partcmds.c
    + *	  TODO
    + *
    + * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
    + * Portions Copyright (c) 1994, Regents of the University of California
    + *
    + *
    + * IDENTIFICATION
    + *	  src/backend/commands/partcmds.c
    + *
    + *-------------------------------------------------------------------------
    + */
    +#include "postgres.h"
    +
    +#include "access/genam.h"
    +#include "access/htup_details.h"
    +#include "utils/lsyscache.h"
    +#include "access/relation.h"
    +#include "access/skey.h"
    +#include "access/stratnum.h"
    +#include "access/table.h"
    +#include "catalog/heap.h"
    +#include "catalog/index.h"
    +#include "catalog/indexing.h"
    +#include "catalog/namespace.h"
    +#include "catalog/partition.h"
    +#include "catalog/pg_am_d.h"
    +#include "catalog/pg_constraint.h"
    +#include "catalog/pg_inherits.h"
    +#include "catalog/pg_trigger.h"
    +#include "commands/defrem.h"
    +#include "commands/partcmds.h"
    +#include "commands/tablecmds.h"
    +#include "commands/trigger.h"
    +#include "miscadmin.h"
    +#include "nodes/makefuncs.h"
    +#include "nodes/nodeFuncs.h"
    +#include "optimizer/optimizer.h"
    +#include "parser/parse_collate.h"
    +#include "parser/parse_expr.h"
    +#include "parser/parse_node.h"
    +#include "parser/parse_relation.h"
    +#include "parser/parse_utilcmd.h"
    +#include "partitioning/partbounds.h"
    +#include "partitioning/partdesc.h"
    +#include "storage/lmgr.h"
    +#include "tcop/utility.h"
    +#include "utils/builtins.h"
    +#include "utils/fmgroids.h"
    +#include "utils/inval.h"
    +#include "utils/memutils.h"
    +#include "utils/partcache.h"
    +#include "utils/rel.h"
    +#include "utils/syscache.h"
    +
    +/*
    + * RemoveInheritedConstraint
    + *
    + * Removes the constraint and its associated trigger from the specified
    + * relation, which inherited the given constraint.
    + */
    +static void
    +RemoveInheritedConstraint(Relation conrel, Relation trigrel, Oid conoid,
    +						  Oid conrelid)
    +{
    +	ObjectAddresses *objs;
    +	HeapTuple	consttup;
    +	ScanKeyData key;
    +	SysScanDesc scan;
    +	HeapTuple	trigtup;
    +
    +	ScanKeyInit(&key,
    +				Anum_pg_constraint_conrelid,
    +				BTEqualStrategyNumber, F_OIDEQ,
    +				ObjectIdGetDatum(conrelid));
    +
    +	scan = systable_beginscan(conrel,
    +							  ConstraintRelidTypidNameIndexId,
    +							  true, NULL, 1, &key);
    +	objs = new_object_addresses();
    +	while ((consttup = systable_getnext(scan)) != NULL)
    +	{
    +		Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(consttup);
    +
    +		if (conform->conparentid != conoid)
    +			continue;
    +		else
    +		{
    +			ObjectAddress addr;
    +			SysScanDesc scan2;
    +			ScanKeyData key2;
    +			int			n PG_USED_FOR_ASSERTS_ONLY;
    +
    +			ObjectAddressSet(addr, ConstraintRelationId, conform->oid);
    +			add_exact_object_address(&addr, objs);
    +
    +			/*
    +			 * First we must delete the dependency record that binds the
    +			 * constraint records together.
    +			 */
    +			n = deleteDependencyRecordsForSpecific(ConstraintRelationId,
    +												   conform->oid,
    +												   DEPENDENCY_INTERNAL,
    +												   ConstraintRelationId,
    +												   conoid);
    +			Assert(n == 1);		/* actually only one is expected */
    +
    +			/*
    +			 * Now search for the triggers for this constraint and set them up
    +			 * for deletion too
    +			 */
    +			ScanKeyInit(&key2,
    +						Anum_pg_trigger_tgconstraint,
    +						BTEqualStrategyNumber, F_OIDEQ,
    +						ObjectIdGetDatum(conform->oid));
    +			scan2 = systable_beginscan(trigrel, TriggerConstraintIndexId,
    +									   true, NULL, 1, &key2);
    +			while ((trigtup = systable_getnext(scan2)) != NULL)
    +			{
    +				ObjectAddressSet(addr, TriggerRelationId,
    +								 ((Form_pg_trigger) GETSTRUCT(trigtup))->oid);
    +				add_exact_object_address(&addr, objs);
    +			}
    +			systable_endscan(scan2);
    +		}
    +	}
    +	/* make the dependency deletions visible */
    +	CommandCounterIncrement();
    +	performMultipleDeletions(objs, DROP_RESTRICT,
    +							 PERFORM_DELETION_INTERNAL);
    +	systable_endscan(scan);
    +}
    +
    +/*
    + * DropForeignKeyConstraintTriggers
    + *
    + * The subroutine for tryAttachPartitionForeignKey handles the deletion of
    + * action triggers for the foreign key constraint.
    + *
    + * If valid confrelid and conrelid values are not provided, the respective
    + * trigger check will be skipped, and the trigger will be considered for
    + * removal.
    + */
    +static void
    +DropForeignKeyConstraintTriggers(Relation trigrel, Oid conoid, Oid confrelid,
    +								 Oid conrelid)
    +{
    +	ScanKeyData key;
    +	SysScanDesc scan;
    +	HeapTuple	trigtup;
    +
    +	ScanKeyInit(&key,
    +				Anum_pg_trigger_tgconstraint,
    +				BTEqualStrategyNumber, F_OIDEQ,
    +				ObjectIdGetDatum(conoid));
    +	scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
    +							  NULL, 1, &key);
    +	while ((trigtup = systable_getnext(scan)) != NULL)
    +	{
    +		Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
    +		ObjectAddress trigger;
    +
    +		/* Invalid if trigger is not for a referential integrity constraint */
    +		if (!OidIsValid(trgform->tgconstrrelid))
    +			continue;
    +		if (OidIsValid(conrelid) && trgform->tgconstrrelid != conrelid)
    +			continue;
    +		if (OidIsValid(confrelid) && trgform->tgrelid != confrelid)
    +			continue;
    +
    +		/* We should be dropping trigger related to foreign key constraint */
    +		Assert(trgform->tgfoid == F_RI_FKEY_CHECK_INS ||
    +			   trgform->tgfoid == F_RI_FKEY_CHECK_UPD ||
    +			   trgform->tgfoid == F_RI_FKEY_CASCADE_DEL ||
    +			   trgform->tgfoid == F_RI_FKEY_CASCADE_UPD ||
    +			   trgform->tgfoid == F_RI_FKEY_RESTRICT_DEL ||
    +			   trgform->tgfoid == F_RI_FKEY_RESTRICT_UPD ||
    +			   trgform->tgfoid == F_RI_FKEY_SETNULL_DEL ||
    +			   trgform->tgfoid == F_RI_FKEY_SETNULL_UPD ||
    +			   trgform->tgfoid == F_RI_FKEY_SETDEFAULT_DEL ||
    +			   trgform->tgfoid == F_RI_FKEY_SETDEFAULT_UPD ||
    +			   trgform->tgfoid == F_RI_FKEY_NOACTION_DEL ||
    +			   trgform->tgfoid == F_RI_FKEY_NOACTION_UPD);
    +
    +		/*
    +		 * The constraint is originally set up to contain this trigger as an
    +		 * implementation object, so there's a dependency record that links
    +		 * the two; however, since the trigger is no longer needed, we remove
    +		 * the dependency link in order to be able to drop the trigger while
    +		 * keeping the constraint intact.
    +		 */
    +		deleteDependencyRecordsFor(TriggerRelationId,
    +								   trgform->oid,
    +								   false);
    +		/* make dependency deletion visible to performDeletion */
    +		CommandCounterIncrement();
    +		ObjectAddressSet(trigger, TriggerRelationId,
    +						 trgform->oid);
    +		performDeletion(&trigger, DROP_RESTRICT, 0);
    +		/* make trigger drop visible, in case the loop iterates */
    +		CommandCounterIncrement();
    +	}
    +
    +	systable_endscan(scan);
    +}
    +
    +/*
    + * GetForeignKeyCheckTriggers
    + * 		Returns insert and update "check" triggers of the given relation
    + * 		belonging to the given constraint
    + */
    +static void
    +GetForeignKeyCheckTriggers(Relation trigrel,
    +						   Oid conoid, Oid confrelid, Oid conrelid,
    +						   Oid *insertTriggerOid,
    +						   Oid *updateTriggerOid)
    +{
    +	ScanKeyData key;
    +	SysScanDesc scan;
    +	HeapTuple	trigtup;
    +
    +	*insertTriggerOid = *updateTriggerOid = InvalidOid;
    +	ScanKeyInit(&key,
    +				Anum_pg_trigger_tgconstraint,
    +				BTEqualStrategyNumber, F_OIDEQ,
    +				ObjectIdGetDatum(conoid));
    +
    +	scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
    +							  NULL, 1, &key);
    +	while ((trigtup = systable_getnext(scan)) != NULL)
    +	{
    +		Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
    +
    +		if (trgform->tgconstrrelid != confrelid)
    +			continue;
    +		if (trgform->tgrelid != conrelid)
    +			continue;
    +		/* Only ever look at "check" triggers on the FK side. */
    +		if (RI_FKey_trigger_type(trgform->tgfoid) != RI_TRIGGER_FK)
    +			continue;
    +		if (TRIGGER_FOR_INSERT(trgform->tgtype))
    +		{
    +			Assert(*insertTriggerOid == InvalidOid);
    +			*insertTriggerOid = trgform->oid;
    +		}
    +		else if (TRIGGER_FOR_UPDATE(trgform->tgtype))
    +		{
    +			Assert(*updateTriggerOid == InvalidOid);
    +			*updateTriggerOid = trgform->oid;
    +		}
    +#ifndef USE_ASSERT_CHECKING
    +		/* In an assert-enabled build, continue looking to find duplicates. */
    +		if (OidIsValid(*insertTriggerOid) && OidIsValid(*updateTriggerOid))
    +			break;
    +#endif
    +	}
    +
    +	if (!OidIsValid(*insertTriggerOid))
    +		elog(ERROR, "could not find ON INSERT check triggers of foreign key constraint %u",
    +			 conoid);
    +	if (!OidIsValid(*updateTriggerOid))
    +		elog(ERROR, "could not find ON UPDATE check triggers of foreign key constraint %u",
    +			 conoid);
    +
    +	systable_endscan(scan);
    +}
    +
    +/*
    + * AttachPartitionForeignKey
    + *
    + * The subroutine for tryAttachPartitionForeignKey performs the final tasks of
    + * attaching the constraint, removing redundant triggers and entries from
    + * pg_constraint, and setting the constraint's parent.
    + */
    +static void
    +AttachPartitionForeignKey(List **wqueue,
    +						  Relation partition,
    +						  Oid partConstrOid,
    +						  Oid parentConstrOid,
    +						  Oid parentInsTrigger,
    +						  Oid parentUpdTrigger,
    +						  Relation trigrel)
    +{
    +	HeapTuple	parentConstrTup;
    +	Form_pg_constraint parentConstr;
    +	HeapTuple	partcontup;
    +	Form_pg_constraint partConstr;
    +	bool		queueValidation;
    +	Oid			partConstrFrelid;
    +	Oid			partConstrRelid;
    +	bool		parentConstrIsEnforced;
    +
    +	/* Fetch the parent constraint tuple */
    +	parentConstrTup = SearchSysCache1(CONSTROID,
    +									  ObjectIdGetDatum(parentConstrOid));
    +	if (!HeapTupleIsValid(parentConstrTup))
    +		elog(ERROR, "cache lookup failed for constraint %u", parentConstrOid);
    +	parentConstr = (Form_pg_constraint) GETSTRUCT(parentConstrTup);
    +	parentConstrIsEnforced = parentConstr->conenforced;
    +
    +	/* Fetch the child constraint tuple */
    +	partcontup = SearchSysCache1(CONSTROID,
    +								 ObjectIdGetDatum(partConstrOid));
    +	if (!HeapTupleIsValid(partcontup))
    +		elog(ERROR, "cache lookup failed for constraint %u", partConstrOid);
    +	partConstr = (Form_pg_constraint) GETSTRUCT(partcontup);
    +	partConstrFrelid = partConstr->confrelid;
    +	partConstrRelid = partConstr->conrelid;
    +
    +	/*
    +	 * If the referenced table is partitioned, then the partition we're
    +	 * attaching now has extra pg_constraint rows and action triggers that are
    +	 * no longer needed.  Remove those.
    +	 */
    +	if (get_rel_relkind(partConstrFrelid) == RELKIND_PARTITIONED_TABLE)
    +	{
    +		Relation	pg_constraint = table_open(ConstraintRelationId, RowShareLock);
    +
    +		RemoveInheritedConstraint(pg_constraint, trigrel, partConstrOid,
    +								  partConstrRelid);
    +
    +		table_close(pg_constraint, RowShareLock);
    +	}
    +
    +	/*
    +	 * Will we need to validate this constraint?   A valid parent constraint
    +	 * implies that all child constraints have been validated, so if this one
    +	 * isn't, we must trigger phase 3 validation.
    +	 */
    +	queueValidation = parentConstr->convalidated && !partConstr->convalidated;
    +
    +	ReleaseSysCache(partcontup);
    +	ReleaseSysCache(parentConstrTup);
    +
    +	/*
    +	 * The action triggers in the new partition become redundant -- the parent
    +	 * table already has equivalent ones, and those will be able to reach the
    +	 * partition.  Remove the ones in the partition.  We identify them because
    +	 * they have our constraint OID, as well as being on the referenced rel.
    +	 */
    +	DropForeignKeyConstraintTriggers(trigrel, partConstrOid, partConstrFrelid,
    +									 partConstrRelid);
    +
    +	ConstraintSetParentConstraint(partConstrOid, parentConstrOid,
    +								  RelationGetRelid(partition));
    +
    +	/*
    +	 * Like the constraint, attach partition's "check" triggers to the
    +	 * corresponding parent triggers if the constraint is ENFORCED. NOT
    +	 * ENFORCED constraints do not have these triggers.
    +	 */
    +	if (parentConstrIsEnforced)
    +	{
    +		Oid			insertTriggerOid,
    +					updateTriggerOid;
    +
    +		GetForeignKeyCheckTriggers(trigrel,
    +								   partConstrOid, partConstrFrelid, partConstrRelid,
    +								   &insertTriggerOid, &updateTriggerOid);
    +		Assert(OidIsValid(insertTriggerOid) && OidIsValid(parentInsTrigger));
    +		TriggerSetParentTrigger(trigrel, insertTriggerOid, parentInsTrigger,
    +								RelationGetRelid(partition));
    +		Assert(OidIsValid(updateTriggerOid) && OidIsValid(parentUpdTrigger));
    +		TriggerSetParentTrigger(trigrel, updateTriggerOid, parentUpdTrigger,
    +								RelationGetRelid(partition));
    +	}
    +
    +	/*
    +	 * We updated this pg_constraint row above to set its parent; validating
    +	 * it will cause its convalidated flag to change, so we need CCI here.  In
    +	 * addition, we need it unconditionally for the rare case where the parent
    +	 * table has *two* identical constraints; when reaching this function for
    +	 * the second one, we must have made our changes visible, otherwise we
    +	 * would try to attach both to this one.
    +	 */
    +	CommandCounterIncrement();
    +
    +	/* If validation is needed, put it in the queue now. */
    +	if (queueValidation)
    +	{
    +		Relation	conrel;
    +		Oid			confrelid;
    +
    +		conrel = table_open(ConstraintRelationId, RowExclusiveLock);
    +
    +		partcontup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(partConstrOid));
    +		if (!HeapTupleIsValid(partcontup))
    +			elog(ERROR, "cache lookup failed for constraint %u", partConstrOid);
    +
    +		confrelid = ((Form_pg_constraint) GETSTRUCT(partcontup))->confrelid;
    +
    +		/* Use the same lock as for AT_ValidateConstraint */
    +		QueueFKConstraintValidation(wqueue, conrel, partition, confrelid,
    +									partcontup, ShareUpdateExclusiveLock);
    +		ReleaseSysCache(partcontup);
    +		table_close(conrel, RowExclusiveLock);
    +	}
    +}
    +
    +/*
    + * When the parent of a partition receives [the referencing side of] a foreign
    + * key, we must propagate that foreign key to the partition.  However, the
    + * partition might already have an equivalent foreign key; this routine
    + * compares the given ForeignKeyCacheInfo (in the partition) to the FK defined
    + * by the other parameters.  If they are equivalent, create the link between
    + * the two constraints and return true.
    + *
    + * If the given FK does not match the one defined by rest of the params,
    + * return false.
    + */
    +bool
    +tryAttachPartitionForeignKey(List **wqueue,
    +							 ForeignKeyCacheInfo *fk,
    +							 Relation partition,
    +							 Oid parentConstrOid,
    +							 int numfks,
    +							 AttrNumber *mapped_conkey,
    +							 AttrNumber *confkey,
    +							 Oid *conpfeqop,
    +							 Oid parentInsTrigger,
    +							 Oid parentUpdTrigger,
    +							 Relation trigrel)
    +{
    +	HeapTuple	parentConstrTup;
    +	Form_pg_constraint parentConstr;
    +	HeapTuple	partcontup;
    +	Form_pg_constraint partConstr;
    +
    +	parentConstrTup = SearchSysCache1(CONSTROID,
    +									  ObjectIdGetDatum(parentConstrOid));
    +	if (!HeapTupleIsValid(parentConstrTup))
    +		elog(ERROR, "cache lookup failed for constraint %u", parentConstrOid);
    +	parentConstr = (Form_pg_constraint) GETSTRUCT(parentConstrTup);
    +
    +	/*
    +	 * Do some quick & easy initial checks.  If any of these fail, we cannot
    +	 * use this constraint.
    +	 */
    +	if (fk->confrelid != parentConstr->confrelid || fk->nkeys != numfks)
    +	{
    +		ReleaseSysCache(parentConstrTup);
    +		return false;
    +	}
    +	for (int i = 0; i < numfks; i++)
    +	{
    +		if (fk->conkey[i] != mapped_conkey[i] ||
    +			fk->confkey[i] != confkey[i] ||
    +			fk->conpfeqop[i] != conpfeqop[i])
    +		{
    +			ReleaseSysCache(parentConstrTup);
    +			return false;
    +		}
    +	}
    +
    +	/* Looks good so far; perform more extensive checks. */
    +	partcontup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
    +	if (!HeapTupleIsValid(partcontup))
    +		elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
    +	partConstr = (Form_pg_constraint) GETSTRUCT(partcontup);
    +
    +	/*
    +	 * An error should be raised if the constraint enforceability is
    +	 * different. Returning false without raising an error, as we do for other
    +	 * attributes, could lead to a duplicate constraint with the same
    +	 * enforceability as the parent. While this may be acceptable, it may not
    +	 * be ideal. Therefore, it's better to raise an error and allow the user
    +	 * to correct the enforceability before proceeding.
    +	 */
    +	if (partConstr->conenforced != parentConstr->conenforced)
    +		ereport(ERROR,
    +				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
    +				 errmsg("constraint \"%s\" enforceability conflicts with constraint \"%s\" on relation \"%s\"",
    +						NameStr(parentConstr->conname),
    +						NameStr(partConstr->conname),
    +						RelationGetRelationName(partition))));
    +
    +	if (OidIsValid(partConstr->conparentid) ||
    +		partConstr->condeferrable != parentConstr->condeferrable ||
    +		partConstr->condeferred != parentConstr->condeferred ||
    +		partConstr->confupdtype != parentConstr->confupdtype ||
    +		partConstr->confdeltype != parentConstr->confdeltype ||
    +		partConstr->confmatchtype != parentConstr->confmatchtype)
    +	{
    +		ReleaseSysCache(parentConstrTup);
    +		ReleaseSysCache(partcontup);
    +		return false;
    +	}
    +
    +	ReleaseSysCache(parentConstrTup);
    +	ReleaseSysCache(partcontup);
    +
    +	/* Looks good!  Attach this constraint. */
    +	AttachPartitionForeignKey(wqueue, partition, fk->conoid,
    +							  parentConstrOid, parentInsTrigger,
    +							  parentUpdTrigger, trigrel);
    +
    +	return true;
    +}
    +
    +/*
    + * CloneFkReferencing
    + *		Subroutine for CloneForeignKeyConstraints
    + *
    + * For each FK constraint of the parent relation in the given list, find an
    + * equivalent constraint in its partition relation that can be reparented;
    + * if one cannot be found, create a new constraint in the partition as its
    + * child.
    + *
    + * If wqueue is given, it is used to set up phase-3 verification for each
    + * cloned constraint; omit it if such verification is not needed
    + * (example: the partition is being created anew).
    + */
    +static void
    +CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
    +{
    +	AttrMap    *attmap;
    +	List	   *partFKs;
    +	List	   *clone = NIL;
    +	ListCell   *cell;
    +	Relation	trigrel;
    +
    +	/* obtain a list of constraints that we need to clone */
    +	foreach(cell, RelationGetFKeyList(parentRel))
    +	{
    +		ForeignKeyCacheInfo *fk = lfirst(cell);
    +
    +		/*
    +		 * Refuse to attach a table as partition that this partitioned table
    +		 * already has a foreign key to.  This isn't useful schema, which is
    +		 * proven by the fact that there have been no user complaints that
    +		 * it's already impossible to achieve this in the opposite direction,
    +		 * i.e., creating a foreign key that references a partition.  This
    +		 * restriction allows us to dodge some complexities around
    +		 * pg_constraint and pg_trigger row creations that would be needed
    +		 * during ATTACH/DETACH for this kind of relationship.
    +		 */
    +		if (fk->confrelid == RelationGetRelid(partRel))
    +			ereport(ERROR,
    +					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
    +					 errmsg("cannot attach table \"%s\" as a partition because it is referenced by foreign key \"%s\"",
    +							RelationGetRelationName(partRel),
    +							get_constraint_name(fk->conoid))));
    +
    +		clone = lappend_oid(clone, fk->conoid);
    +	}
    +
    +	/*
    +	 * Silently do nothing if there's nothing to do.  In particular, this
    +	 * avoids throwing a spurious error for foreign tables.
    +	 */
    +	if (clone == NIL)
    +		return;
    +
    +	if (partRel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
    +		ereport(ERROR,
    +				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    +				 errmsg("foreign key constraints are not supported on foreign tables")));
    +
    +	/*
    +	 * Triggers of the foreign keys will be manipulated a bunch of times in
    +	 * the loop below.  To avoid repeatedly opening/closing the trigger
    +	 * catalog relation, we open it here and pass it to the subroutines called
    +	 * below.
    +	 */
    +	trigrel = table_open(TriggerRelationId, RowExclusiveLock);
    +
    +	/*
    +	 * The constraint key may differ, if the columns in the partition are
    +	 * different.  This map is used to convert them.
    +	 */
    +	attmap = build_attrmap_by_name(RelationGetDescr(partRel),
    +								   RelationGetDescr(parentRel),
    +								   false);
    +
    +	partFKs = copyObject(RelationGetFKeyList(partRel));
    +
    +	foreach(cell, clone)
    +	{
    +		Oid			parentConstrOid = lfirst_oid(cell);
    +		Form_pg_constraint constrForm;
    +		Relation	pkrel;
    +		HeapTuple	tuple;
    +		int			numfks;
    +		AttrNumber	conkey[INDEX_MAX_KEYS];
    +		AttrNumber	mapped_conkey[INDEX_MAX_KEYS];
    +		AttrNumber	confkey[INDEX_MAX_KEYS];
    +		Oid			conpfeqop[INDEX_MAX_KEYS];
    +		Oid			conppeqop[INDEX_MAX_KEYS];
    +		Oid			conffeqop[INDEX_MAX_KEYS];
    +		int			numfkdelsetcols;
    +		AttrNumber	confdelsetcols[INDEX_MAX_KEYS];
    +		Constraint *fkconstraint;
    +		bool		attached;
    +		Oid			indexOid;
    +		ObjectAddress address;
    +		ListCell   *lc;
    +		Oid			insertTriggerOid = InvalidOid,
    +					updateTriggerOid = InvalidOid;
    +		bool		with_period;
    +
    +		tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(parentConstrOid));
    +		if (!HeapTupleIsValid(tuple))
    +			elog(ERROR, "cache lookup failed for constraint %u",
    +				 parentConstrOid);
    +		constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
    +
    +		/* Don't clone constraints whose parents are being cloned */
    +		if (list_member_oid(clone, constrForm->conparentid))
    +		{
    +			ReleaseSysCache(tuple);
    +			continue;
    +		}
    +
    +		/*
    +		 * Need to prevent concurrent deletions.  If pkrel is a partitioned
    +		 * relation, that means to lock all partitions.
    +		 */
    +		pkrel = table_open(constrForm->confrelid, ShareRowExclusiveLock);
    +		if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
    +			(void) find_all_inheritors(RelationGetRelid(pkrel),
    +									   ShareRowExclusiveLock, NULL);
    +
    +		DeconstructFkConstraintRow(tuple, &numfks, conkey, confkey,
    +								   conpfeqop, conppeqop, conffeqop,
    +								   &numfkdelsetcols, confdelsetcols);
    +		for (int i = 0; i < numfks; i++)
    +			mapped_conkey[i] = attmap->attnums[conkey[i] - 1];
    +
    +		/*
    +		 * Get the "check" triggers belonging to the constraint, if it is
    +		 * ENFORCED, to pass as parent OIDs for similar triggers that will be
    +		 * created on the partition in addFkRecurseReferencing().  They are
    +		 * also passed to tryAttachPartitionForeignKey() below to simply
    +		 * assign as parents to the partition's existing "check" triggers,
    +		 * that is, if the corresponding constraints is deemed attachable to
    +		 * the parent constraint.
    +		 */
    +		if (constrForm->conenforced)
    +			GetForeignKeyCheckTriggers(trigrel, constrForm->oid,
    +									   constrForm->confrelid, constrForm->conrelid,
    +									   &insertTriggerOid, &updateTriggerOid);
    +
    +		/*
    +		 * Before creating a new constraint, see whether any existing FKs are
    +		 * fit for the purpose.  If one is, attach the parent constraint to
    +		 * it, and don't clone anything.  This way we avoid the expensive
    +		 * verification step and don't end up with a duplicate FK, and we
    +		 * don't need to recurse to partitions for this constraint.
    +		 */
    +		attached = false;
    +		foreach(lc, partFKs)
    +		{
    +			ForeignKeyCacheInfo *fk = lfirst_node(ForeignKeyCacheInfo, lc);
    +
    +			if (tryAttachPartitionForeignKey(wqueue,
    +											 fk,
    +											 partRel,
    +											 parentConstrOid,
    +											 numfks,
    +											 mapped_conkey,
    +											 confkey,
    +											 conpfeqop,
    +											 insertTriggerOid,
    +											 updateTriggerOid,
    +											 trigrel))
    +			{
    +				attached = true;
    +				table_close(pkrel, NoLock);
    +				break;
    +			}
    +		}
    +		if (attached)
    +		{
    +			ReleaseSysCache(tuple);
    +			continue;
    +		}
    +
    +		/* No dice.  Set up to create our own constraint */
    +		fkconstraint = makeNode(Constraint);
    +		fkconstraint->contype = CONSTRAINT_FOREIGN;
    +		/* ->conname determined below */
    +		fkconstraint->deferrable = constrForm->condeferrable;
    +		fkconstraint->initdeferred = constrForm->condeferred;
    +		fkconstraint->location = -1;
    +		fkconstraint->pktable = NULL;
    +		/* ->fk_attrs determined below */
    +		fkconstraint->pk_attrs = NIL;
    +		fkconstraint->fk_matchtype = constrForm->confmatchtype;
    +		fkconstraint->fk_upd_action = constrForm->confupdtype;
    +		fkconstraint->fk_del_action = constrForm->confdeltype;
    +		fkconstraint->fk_del_set_cols = NIL;
    +		fkconstraint->old_conpfeqop = NIL;
    +		fkconstraint->old_pktable_oid = InvalidOid;
    +		fkconstraint->is_enforced = constrForm->conenforced;
    +		fkconstraint->skip_validation = false;
    +		fkconstraint->initially_valid = constrForm->convalidated;
    +		for (int i = 0; i < numfks; i++)
    +		{
    +			Form_pg_attribute att;
    +
    +			att = TupleDescAttr(RelationGetDescr(partRel),
    +								mapped_conkey[i] - 1);
    +			fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs,
    +											 makeString(NameStr(att->attname)));
    +		}
    +
    +		indexOid = constrForm->conindid;
    +		with_period = constrForm->conperiod;
    +
    +		/* Create the pg_constraint entry at this level */
    +		address = addFkConstraint(addFkReferencingSide,
    +								  NameStr(constrForm->conname), fkconstraint,
    +								  partRel, pkrel, indexOid, parentConstrOid,
    +								  numfks, confkey,
    +								  mapped_conkey, conpfeqop,
    +								  conppeqop, conffeqop,
    +								  numfkdelsetcols, confdelsetcols,
    +								  false, with_period);
    +
    +		/* Done with the cloned constraint's tuple */
    +		ReleaseSysCache(tuple);
    +
    +		/* Create the check triggers, and recurse to partitions, if any */
    +		addFkRecurseReferencing(wqueue,
    +								fkconstraint,
    +								partRel,
    +								pkrel,
    +								indexOid,
    +								address.objectId,
    +								numfks,
    +								confkey,
    +								mapped_conkey,
    +								conpfeqop,
    +								conppeqop,
    +								conffeqop,
    +								numfkdelsetcols,
    +								confdelsetcols,
    +								false,	/* no old check exists */
    +								AccessExclusiveLock,
    +								insertTriggerOid,
    +								updateTriggerOid,
    +								with_period);
    +		table_close(pkrel, NoLock);
    +	}
    +
    +	table_close(trigrel, RowExclusiveLock);
    +}
    +
    +/*
    + * GetForeignKeyActionTriggers
    + * 		Returns delete and update "action" triggers of the given relation
    + * 		belonging to the given constraint
    + */
    +static void
    +GetForeignKeyActionTriggers(Relation trigrel,
    +							Oid conoid, Oid confrelid, Oid conrelid,
    +							Oid *deleteTriggerOid,
    +							Oid *updateTriggerOid)
    +{
    +	ScanKeyData key;
    +	SysScanDesc scan;
    +	HeapTuple	trigtup;
    +
    +	*deleteTriggerOid = *updateTriggerOid = InvalidOid;
    +	ScanKeyInit(&key,
    +				Anum_pg_trigger_tgconstraint,
    +				BTEqualStrategyNumber, F_OIDEQ,
    +				ObjectIdGetDatum(conoid));
    +
    +	scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
    +							  NULL, 1, &key);
    +	while ((trigtup = systable_getnext(scan)) != NULL)
    +	{
    +		Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
    +
    +		if (trgform->tgconstrrelid != conrelid)
    +			continue;
    +		if (trgform->tgrelid != confrelid)
    +			continue;
    +		/* Only ever look at "action" triggers on the PK side. */
    +		if (RI_FKey_trigger_type(trgform->tgfoid) != RI_TRIGGER_PK)
    +			continue;
    +		if (TRIGGER_FOR_DELETE(trgform->tgtype))
    +		{
    +			Assert(*deleteTriggerOid == InvalidOid);
    +			*deleteTriggerOid = trgform->oid;
    +		}
    +		else if (TRIGGER_FOR_UPDATE(trgform->tgtype))
    +		{
    +			Assert(*updateTriggerOid == InvalidOid);
    +			*updateTriggerOid = trgform->oid;
    +		}
    +#ifndef USE_ASSERT_CHECKING
    +		/* In an assert-enabled build, continue looking to find duplicates */
    +		if (OidIsValid(*deleteTriggerOid) && OidIsValid(*updateTriggerOid))
    +			break;
    +#endif
    +	}
    +
    +	if (!OidIsValid(*deleteTriggerOid))
    +		elog(ERROR, "could not find ON DELETE action trigger of foreign key constraint %u",
    +			 conoid);
    +	if (!OidIsValid(*updateTriggerOid))
    +		elog(ERROR, "could not find ON UPDATE action trigger of foreign key constraint %u",
    +			 conoid);
    +
    +	systable_endscan(scan);
    +}
    +
    +/*
    + * CloneFkReferenced
    + *		Subroutine for CloneForeignKeyConstraints
    + *
    + * Find all the FKs that have the parent relation on the referenced side;
    + * clone those constraints to the given partition.  This is to be called
    + * when the partition is being created or attached.
    + *
    + * This recurses to partitions, if the relation being attached is partitioned.
    + * Recursion is done by calling addFkRecurseReferenced.
    + */
    +static void
    +CloneFkReferenced(Relation parentRel, Relation partitionRel)
    +{
    +	Relation	pg_constraint;
    +	AttrMap    *attmap;
    +	ListCell   *cell;
    +	SysScanDesc scan;
    +	ScanKeyData key[2];
    +	HeapTuple	tuple;
    +	List	   *clone = NIL;
    +	Relation	trigrel;
    +
    +	/*
    +	 * Search for any constraints where this partition's parent is in the
    +	 * referenced side.  However, we must not clone any constraint whose
    +	 * parent constraint is also going to be cloned, to avoid duplicates.  So
    +	 * do it in two steps: first construct the list of constraints to clone,
    +	 * then go over that list cloning those whose parents are not in the list.
    +	 * (We must not rely on the parent being seen first, since the catalog
    +	 * scan could return children first.)
    +	 */
    +	pg_constraint = table_open(ConstraintRelationId, RowShareLock);
    +	ScanKeyInit(&key[0],
    +				Anum_pg_constraint_confrelid, BTEqualStrategyNumber,
    +				F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(parentRel)));
    +	ScanKeyInit(&key[1],
    +				Anum_pg_constraint_contype, BTEqualStrategyNumber,
    +				F_CHAREQ, CharGetDatum(CONSTRAINT_FOREIGN));
    +	/* This is a seqscan, as we don't have a usable index ... */
    +	scan = systable_beginscan(pg_constraint, InvalidOid, true,
    +							  NULL, 2, key);
    +	while ((tuple = systable_getnext(scan)) != NULL)
    +	{
    +		Form_pg_constraint constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
    +
    +		clone = lappend_oid(clone, constrForm->oid);
    +	}
    +	systable_endscan(scan);
    +	table_close(pg_constraint, RowShareLock);
    +
    +	/*
    +	 * Triggers of the foreign keys will be manipulated a bunch of times in
    +	 * the loop below.  To avoid repeatedly opening/closing the trigger
    +	 * catalog relation, we open it here and pass it to the subroutines called
    +	 * below.
    +	 */
    +	trigrel = table_open(TriggerRelationId, RowExclusiveLock);
    +
    +	attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
    +								   RelationGetDescr(parentRel),
    +								   false);
    +	foreach(cell, clone)
    +	{
    +		Oid			constrOid = lfirst_oid(cell);
    +		Form_pg_constraint constrForm;
    +		Relation	fkRel;
    +		Oid			indexOid;
    +		Oid			partIndexId;
    +		int			numfks;
    +		AttrNumber	conkey[INDEX_MAX_KEYS];
    +		AttrNumber	mapped_confkey[INDEX_MAX_KEYS];
    +		AttrNumber	confkey[INDEX_MAX_KEYS];
    +		Oid			conpfeqop[INDEX_MAX_KEYS];
    +		Oid			conppeqop[INDEX_MAX_KEYS];
    +		Oid			conffeqop[INDEX_MAX_KEYS];
    +		int			numfkdelsetcols;
    +		AttrNumber	confdelsetcols[INDEX_MAX_KEYS];
    +		Constraint *fkconstraint;
    +		ObjectAddress address;
    +		Oid			deleteTriggerOid = InvalidOid,
    +					updateTriggerOid = InvalidOid;
    +
    +		tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid));
    +		if (!HeapTupleIsValid(tuple))
    +			elog(ERROR, "cache lookup failed for constraint %u", constrOid);
    +		constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
    +
    +		/*
    +		 * As explained above: don't try to clone a constraint for which we're
    +		 * going to clone the parent.
    +		 */
    +		if (list_member_oid(clone, constrForm->conparentid))
    +		{
    +			ReleaseSysCache(tuple);
    +			continue;
    +		}
    +
    +		/* We need the same lock level that CreateTrigger will acquire */
    +		fkRel = table_open(constrForm->conrelid, ShareRowExclusiveLock);
    +
    +		indexOid = constrForm->conindid;
    +		DeconstructFkConstraintRow(tuple,
    +								   &numfks,
    +								   conkey,
    +								   confkey,
    +								   conpfeqop,
    +								   conppeqop,
    +								   conffeqop,
    +								   &numfkdelsetcols,
    +								   confdelsetcols);
    +
    +		for (int i = 0; i < numfks; i++)
    +			mapped_confkey[i] = attmap->attnums[confkey[i] - 1];
    +
    +		fkconstraint = makeNode(Constraint);
    +		fkconstraint->contype = CONSTRAINT_FOREIGN;
    +		fkconstraint->conname = NameStr(constrForm->conname);
    +		fkconstraint->deferrable = constrForm->condeferrable;
    +		fkconstraint->initdeferred = constrForm->condeferred;
    +		fkconstraint->location = -1;
    +		fkconstraint->pktable = NULL;
    +		/* ->fk_attrs determined below */
    +		fkconstraint->pk_attrs = NIL;
    +		fkconstraint->fk_matchtype = constrForm->confmatchtype;
    +		fkconstraint->fk_upd_action = constrForm->confupdtype;
    +		fkconstraint->fk_del_action = constrForm->confdeltype;
    +		fkconstraint->fk_del_set_cols = NIL;
    +		fkconstraint->old_conpfeqop = NIL;
    +		fkconstraint->old_pktable_oid = InvalidOid;
    +		fkconstraint->is_enforced = constrForm->conenforced;
    +		fkconstraint->skip_validation = false;
    +		fkconstraint->initially_valid = constrForm->convalidated;
    +
    +		/* set up colnames that are used to generate the constraint name */
    +		for (int i = 0; i < numfks; i++)
    +		{
    +			Form_pg_attribute att;
    +
    +			att = TupleDescAttr(RelationGetDescr(fkRel),
    +								conkey[i] - 1);
    +			fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs,
    +											 makeString(NameStr(att->attname)));
    +		}
    +
    +		/*
    +		 * Add the new foreign key constraint pointing to the new partition.
    +		 * Because this new partition appears in the referenced side of the
    +		 * constraint, we don't need to set up for Phase 3 check.
    +		 */
    +		partIndexId = index_get_partition(partitionRel, indexOid);
    +		if (!OidIsValid(partIndexId))
    +			elog(ERROR, "index for %u not found in partition %s",
    +				 indexOid, RelationGetRelationName(partitionRel));
    +
    +		/*
    +		 * Get the "action" triggers belonging to the constraint to pass as
    +		 * parent OIDs for similar triggers that will be created on the
    +		 * partition in addFkRecurseReferenced().
    +		 */
    +		if (constrForm->conenforced)
    +			GetForeignKeyActionTriggers(trigrel, constrOid,
    +										constrForm->confrelid, constrForm->conrelid,
    +										&deleteTriggerOid, &updateTriggerOid);
    +
    +		/* Add this constraint ... */
    +		address = addFkConstraint(addFkReferencedSide,
    +								  fkconstraint->conname, fkconstraint, fkRel,
    +								  partitionRel, partIndexId, constrOid,
    +								  numfks, mapped_confkey,
    +								  conkey, conpfeqop, conppeqop, conffeqop,
    +								  numfkdelsetcols, confdelsetcols, false,
    +								  constrForm->conperiod);
    +		/* ... and recurse */
    +		addFkRecurseReferenced(fkconstraint,
    +							   fkRel,
    +							   partitionRel,
    +							   partIndexId,
    +							   address.objectId,
    +							   numfks,
    +							   mapped_confkey,
    +							   conkey,
    +							   conpfeqop,
    +							   conppeqop,
    +							   conffeqop,
    +							   numfkdelsetcols,
    +							   confdelsetcols,
    +							   true,
    +							   deleteTriggerOid,
    +							   updateTriggerOid,
    +							   constrForm->conperiod);
    +
    +		table_close(fkRel, NoLock);
    +		ReleaseSysCache(tuple);
    +	}
    +
    +	table_close(trigrel, RowExclusiveLock);
    +}
    +
    +/*
    + * CloneForeignKeyConstraints
    + *		Clone foreign keys from a partitioned table to a newly acquired
    + *		partition.
    + *
    + * partitionRel is a partition of parentRel, so we can be certain that it has
    + * the same columns with the same datatypes.  The columns may be in different
    + * order, though.
    + *
    + * wqueue must be passed to set up phase 3 constraint checking, unless the
    + * referencing-side partition is known to be empty (such as in CREATE TABLE /
    + * PARTITION OF).
    + */
    +void
    +CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
    +						   Relation partitionRel)
    +{
    +	/* This only works for declarative partitioning */
    +	Assert(parentRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
    +
    +	/*
    +	 * First, clone constraints where the parent is on the referencing side.
    +	 */
    +	CloneFkReferencing(wqueue, parentRel, partitionRel);
    +
    +	/*
    +	 * Clone constraints for which the parent is on the referenced side.
    +	 */
    +	CloneFkReferenced(parentRel, partitionRel);
    +}
    +
    +/*
    + * MarkInheritDetached
    + *
    + * Set inhdetachpending for a partition, for ATExecDetachPartition
    + * in concurrent mode.  While at it, verify that no other partition is
    + * already pending detach.
    + */
    +static void
    +MarkInheritDetached(Relation child_rel, Relation parent_rel)
    +{
    +	Relation	catalogRelation;
    +	SysScanDesc scan;
    +	ScanKeyData key;
    +	HeapTuple	inheritsTuple;
    +	bool		found = false;
    +
    +	Assert(parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
    +
    +	/*
    +	 * Find pg_inherits entries by inhparent.  (We need to scan them all in
    +	 * order to verify that no other partition is pending detach.)
    +	 */
    +	catalogRelation = table_open(InheritsRelationId, RowExclusiveLock);
    +	ScanKeyInit(&key,
    +				Anum_pg_inherits_inhparent,
    +				BTEqualStrategyNumber, F_OIDEQ,
    +				ObjectIdGetDatum(RelationGetRelid(parent_rel)));
    +	scan = systable_beginscan(catalogRelation, InheritsParentIndexId,
    +							  true, NULL, 1, &key);
    +
    +	while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
    +	{
    +		Form_pg_inherits inhForm;
    +
    +		inhForm = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
    +		if (inhForm->inhdetachpending)
    +			ereport(ERROR,
    +					errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    +					errmsg("partition \"%s\" already pending detach in partitioned table \"%s.%s\"",
    +						   get_rel_name(inhForm->inhrelid),
    +						   get_namespace_name(parent_rel->rd_rel->relnamespace),
    +						   RelationGetRelationName(parent_rel)),
    +					errhint("Use ALTER TABLE ... DETACH PARTITION ... FINALIZE to complete the pending detach operation."));
    +
    +		if (inhForm->inhrelid == RelationGetRelid(child_rel))
    +		{
    +			HeapTuple	newtup;
    +
    +			newtup = heap_copytuple(inheritsTuple);
    +			((Form_pg_inherits) GETSTRUCT(newtup))->inhdetachpending = true;
    +
    +			CatalogTupleUpdate(catalogRelation,
    +							   &inheritsTuple->t_self,
    +							   newtup);
    +			found = true;
    +			heap_freetuple(newtup);
    +			/* keep looking, to ensure we catch others pending detach */
    +		}
    +	}
    +
    +	/* Done */
    +	systable_endscan(scan);
    +	table_close(catalogRelation, RowExclusiveLock);
    +
    +	if (!found)
    +		ereport(ERROR,
    +				(errcode(ERRCODE_UNDEFINED_TABLE),
    +				 errmsg("relation \"%s\" is not a partition of relation \"%s\"",
    +						RelationGetRelationName(child_rel),
    +						RelationGetRelationName(parent_rel))));
    +}
    +
    +/*
    + * Transform any expressions present in the partition key
    + *
    + * Returns a transformed PartitionSpec.
    + */
    +PartitionSpec *
    +transformPartitionSpec(Relation rel, PartitionSpec *partspec)
    +{
    +	PartitionSpec *newspec;
    +	ParseState *pstate;
    +	ParseNamespaceItem *nsitem;
    +	ListCell   *l;
    +
    +	newspec = makeNode(PartitionSpec);
    +
    +	newspec->strategy = partspec->strategy;
    +	newspec->partParams = NIL;
    +	newspec->location = partspec->location;
    +
    +	/* Check valid number of columns for strategy */
    +	if (partspec->strategy == PARTITION_STRATEGY_LIST &&
    +		list_length(partspec->partParams) != 1)
    +		ereport(ERROR,
    +				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
    +				 errmsg("cannot use \"list\" partition strategy with more than one column")));
    +
    +	/*
    +	 * Create a dummy ParseState and insert the target relation as its sole
    +	 * rangetable entry.  We need a ParseState for transformExpr.
    +	 */
    +	pstate = make_parsestate(NULL);
    +	nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock,
    +										   NULL, false, true);
    +	addNSItemToQuery(pstate, nsitem, true, true, true);
    +
    +	/* take care of any partition expressions */
    +	foreach(l, partspec->partParams)
    +	{
    +		PartitionElem *pelem = lfirst_node(PartitionElem, l);
    +
    +		if (pelem->expr)
    +		{
    +			/* Copy, to avoid scribbling on the input */
    +			pelem = copyObject(pelem);
    +
    +			/* Now do parse transformation of the expression */
    +			pelem->expr = transformExpr(pstate, pelem->expr,
    +										EXPR_KIND_PARTITION_EXPRESSION);
    +
    +			/* we have to fix its collations too */
    +			assign_expr_collations(pstate, pelem->expr);
    +		}
    +
    +		newspec->partParams = lappend(newspec->partParams, pelem);
    +	}
    +
    +	return newspec;
    +}
    +
    +/*
    + * Compute per-partition-column information from a list of PartitionElems.
    + * Expressions in the PartitionElems must be parse-analyzed already.
    + */
    +void
    +ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNumber *partattrs,
    +					  List **partexprs, Oid *partopclass, Oid *partcollation,
    +					  PartitionStrategy strategy)
    +{
    +	int			attn;
    +	ListCell   *lc;
    +	Oid			am_oid;
    +
    +	attn = 0;
    +	foreach(lc, partParams)
    +	{
    +		PartitionElem *pelem = lfirst_node(PartitionElem, lc);
    +		Oid			atttype;
    +		Oid			attcollation;
    +
    +		if (pelem->name != NULL)
    +		{
    +			/* Simple attribute reference */
    +			HeapTuple	atttuple;
    +			Form_pg_attribute attform;
    +
    +			atttuple = SearchSysCacheAttName(RelationGetRelid(rel),
    +											 pelem->name);
    +			if (!HeapTupleIsValid(atttuple))
    +				ereport(ERROR,
    +						(errcode(ERRCODE_UNDEFINED_COLUMN),
    +						 errmsg("column \"%s\" named in partition key does not exist",
    +								pelem->name),
    +						 parser_errposition(pstate, pelem->location)));
    +			attform = (Form_pg_attribute) GETSTRUCT(atttuple);
    +
    +			if (attform->attnum <= 0)
    +				ereport(ERROR,
    +						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
    +						 errmsg("cannot use system column \"%s\" in partition key",
    +								pelem->name),
    +						 parser_errposition(pstate, pelem->location)));
    +
    +			/*
    +			 * Stored generated columns cannot work: They are computed after
    +			 * BEFORE triggers, but partition routing is done before all
    +			 * triggers.  Maybe virtual generated columns could be made to
    +			 * work, but then they would need to be handled as an expression
    +			 * below.
    +			 */
    +			if (attform->attgenerated)
    +				ereport(ERROR,
    +						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
    +						 errmsg("cannot use generated column in partition key"),
    +						 errdetail("Column \"%s\" is a generated column.",
    +								   pelem->name),
    +						 parser_errposition(pstate, pelem->location)));
    +
    +			partattrs[attn] = attform->attnum;
    +			atttype = attform->atttypid;
    +			attcollation = attform->attcollation;
    +			ReleaseSysCache(atttuple);
    +		}
    +		else
    +		{
    +			/* Expression */
    +			Node	   *expr = pelem->expr;
    +			char		partattname[16];
    +			Bitmapset  *expr_attrs = NULL;
    +			int			i;
    +
    +			Assert(expr != NULL);
    +			atttype = exprType(expr);
    +			attcollation = exprCollation(expr);
    +
    +			/*
    +			 * The expression must be of a storable type (e.g., not RECORD).
    +			 * The test is the same as for whether a table column is of a safe
    +			 * type (which is why we needn't check for the non-expression
    +			 * case).
    +			 */
    +			snprintf(partattname, sizeof(partattname), "%d", attn + 1);
    +			CheckAttributeType(partattname,
    +							   atttype, attcollation,
    +							   NIL, CHKATYPE_IS_PARTKEY);
    +
    +			/*
    +			 * Strip any top-level COLLATE clause.  This ensures that we treat
    +			 * "x COLLATE y" and "(x COLLATE y)" alike.
    +			 */
    +			while (IsA(expr, CollateExpr))
    +				expr = (Node *) ((CollateExpr *) expr)->arg;
    +
    +			/*
    +			 * Examine all the columns in the partition key expression. When
    +			 * the whole-row reference is present, examine all the columns of
    +			 * the partitioned table.
    +			 */
    +			pull_varattnos(expr, 1, &expr_attrs);
    +			if (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber, expr_attrs))
    +			{
    +				expr_attrs = bms_add_range(expr_attrs,
    +										   1 - FirstLowInvalidHeapAttributeNumber,
    +										   RelationGetNumberOfAttributes(rel) - FirstLowInvalidHeapAttributeNumber);
    +				expr_attrs = bms_del_member(expr_attrs, 0 - FirstLowInvalidHeapAttributeNumber);
    +			}
    +
    +			i = -1;
    +			while ((i = bms_next_member(expr_attrs, i)) >= 0)
    +			{
    +				AttrNumber	attno = i + FirstLowInvalidHeapAttributeNumber;
    +
    +				Assert(attno != 0);
    +
    +				/*
    +				 * Cannot allow system column references, since that would
    +				 * make partition routing impossible: their values won't be
    +				 * known yet when we need to do that.
    +				 */
    +				if (attno < 0)
    +					ereport(ERROR,
    +							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
    +							 errmsg("partition key expressions cannot contain system column references")));
    +
    +				/*
    +				 * Stored generated columns cannot work: They are computed
    +				 * after BEFORE triggers, but partition routing is done before
    +				 * all triggers.  Virtual generated columns could probably
    +				 * work, but it would require more work elsewhere (for example
    +				 * SET EXPRESSION would need to check whether the column is
    +				 * used in partition keys).  Seems safer to prohibit for now.
    +				 */
    +				if (TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated)
    +					ereport(ERROR,
    +							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
    +							 errmsg("cannot use generated column in partition key"),
    +							 errdetail("Column \"%s\" is a generated column.",
    +									   get_attname(RelationGetRelid(rel), attno, false)),
    +							 parser_errposition(pstate, pelem->location)));
    +			}
    +
    +			if (IsA(expr, Var) &&
    +				((Var *) expr)->varattno > 0)
    +			{
    +
    +				/*
    +				 * User wrote "(column)" or "(column COLLATE something)".
    +				 * Treat it like simple attribute anyway.
    +				 */
    +				partattrs[attn] = ((Var *) expr)->varattno;
    +			}
    +			else
    +			{
    +				partattrs[attn] = 0;	/* marks the column as expression */
    +				*partexprs = lappend(*partexprs, expr);
    +
    +				/*
    +				 * transformPartitionSpec() should have already rejected
    +				 * subqueries, aggregates, window functions, and SRFs, based
    +				 * on the EXPR_KIND_ for partition expressions.
    +				 */
    +
    +				/*
    +				 * Preprocess the expression before checking for mutability.
    +				 * This is essential for the reasons described in
    +				 * contain_mutable_functions_after_planning.  However, we call
    +				 * expression_planner for ourselves rather than using that
    +				 * function, because if constant-folding reduces the
    +				 * expression to a constant, we'd like to know that so we can
    +				 * complain below.
    +				 *
    +				 * Like contain_mutable_functions_after_planning, assume that
    +				 * expression_planner won't scribble on its input, so this
    +				 * won't affect the partexprs entry we saved above.
    +				 */
    +				expr = (Node *) expression_planner((Expr *) expr);
    +
    +				/*
    +				 * Partition expressions cannot contain mutable functions,
    +				 * because a given row must always map to the same partition
    +				 * as long as there is no change in the partition boundary
    +				 * structure.
    +				 */
    +				if (contain_mutable_functions(expr))
    +					ereport(ERROR,
    +							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
    +							 errmsg("functions in partition key expression must be marked IMMUTABLE")));
    +
    +				/*
    +				 * While it is not exactly *wrong* for a partition expression
    +				 * to be a constant, it seems better to reject such keys.
    +				 */
    +				if (IsA(expr, Const))
    +					ereport(ERROR,
    +							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
    +							 errmsg("cannot use constant expression as partition key")));
    +			}
    +		}
    +
    +		/*
    +		 * Apply collation override if any
    +		 */
    +		if (pelem->collation)
    +			attcollation = get_collation_oid(pelem->collation, false);
    +
    +		/*
    +		 * Check we have a collation iff it's a collatable type.  The only
    +		 * expected failures here are (1) COLLATE applied to a noncollatable
    +		 * type, or (2) partition expression had an unresolved collation. But
    +		 * we might as well code this to be a complete consistency check.
    +		 */
    +		if (type_is_collatable(atttype))
    +		{
    +			if (!OidIsValid(attcollation))
    +				ereport(ERROR,
    +						(errcode(ERRCODE_INDETERMINATE_COLLATION),
    +						 errmsg("could not determine which collation to use for partition expression"),
    +						 errhint("Use the COLLATE clause to set the collation explicitly.")));
    +		}
    +		else
    +		{
    +			if (OidIsValid(attcollation))
    +				ereport(ERROR,
    +						(errcode(ERRCODE_DATATYPE_MISMATCH),
    +						 errmsg("collations are not supported by type %s",
    +								format_type_be(atttype))));
    +		}
    +
    +		partcollation[attn] = attcollation;
    +
    +		/*
    +		 * Identify the appropriate operator class.  For list and range
    +		 * partitioning, we use a btree operator class; hash partitioning uses
    +		 * a hash operator class.
    +		 */
    +		if (strategy == PARTITION_STRATEGY_HASH)
    +			am_oid = HASH_AM_OID;
    +		else
    +			am_oid = BTREE_AM_OID;
    +
    +		if (!pelem->opclass)
    +		{
    +			partopclass[attn] = GetDefaultOpClass(atttype, am_oid);
    +
    +			if (!OidIsValid(partopclass[attn]))
    +			{
    +				if (strategy == PARTITION_STRATEGY_HASH)
    +					ereport(ERROR,
    +							(errcode(ERRCODE_UNDEFINED_OBJECT),
    +							 errmsg("data type %s has no default operator class for access method \"%s\"",
    +									format_type_be(atttype), "hash"),
    +							 errhint("You must specify a hash operator class or define a default hash operator class for the data type.")));
    +				else
    +					ereport(ERROR,
    +							(errcode(ERRCODE_UNDEFINED_OBJECT),
    +							 errmsg("data type %s has no default operator class for access method \"%s\"",
    +									format_type_be(atttype), "btree"),
    +							 errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
    +			}
    +		}
    +		else
    +			partopclass[attn] = ResolveOpClass(pelem->opclass,
    +											   atttype,
    +											   am_oid == HASH_AM_OID ? "hash" : "btree",
    +											   am_oid);
    +
    +		attn++;
    +	}
    +}
    +
    +/*
    + * PartConstraintImpliedByRelConstraint
    + *		Do scanrel's existing constraints imply the partition constraint?
    + *
    + * "Existing constraints" include its check constraints and column-level
    + * not-null constraints.  partConstraint describes the partition constraint,
    + * in implicit-AND form.
    + */
    +bool
    +PartConstraintImpliedByRelConstraint(Relation scanrel,
    +									 List *partConstraint)
    +{
    +	List	   *existConstraint = NIL;
    +	TupleConstr *constr = RelationGetDescr(scanrel)->constr;
    +	int			i;
    +
    +	if (constr && constr->has_not_null)
    +	{
    +		int			natts = scanrel->rd_att->natts;
    +
    +		for (i = 1; i <= natts; i++)
    +		{
    +			CompactAttribute *att = TupleDescCompactAttr(scanrel->rd_att, i - 1);
    +
    +			/* invalid not-null constraint must be ignored here */
    +			if (att->attnullability == ATTNULLABLE_VALID && !att->attisdropped)
    +			{
    +				Form_pg_attribute wholeatt = TupleDescAttr(scanrel->rd_att, i - 1);
    +				NullTest   *ntest = makeNode(NullTest);
    +
    +				ntest->arg = (Expr *) makeVar(1,
    +											  i,
    +											  wholeatt->atttypid,
    +											  wholeatt->atttypmod,
    +											  wholeatt->attcollation,
    +											  0);
    +				ntest->nulltesttype = IS_NOT_NULL;
    +
    +				/*
    +				 * argisrow=false is correct even for a composite column,
    +				 * because attnotnull does not represent a SQL-spec IS NOT
    +				 * NULL test in such a case, just IS DISTINCT FROM NULL.
    +				 */
    +				ntest->argisrow = false;
    +				ntest->location = -1;
    +				existConstraint = lappend(existConstraint, ntest);
    +			}
    +		}
    +	}
    +
    +	return ConstraintImpliedByRelConstraint(scanrel, partConstraint, existConstraint);
    +}
    +
    +/*
    + * ConstraintImpliedByRelConstraint
    + *		Do scanrel's existing constraints imply the given constraint?
    + *
    + * testConstraint is the constraint to validate. provenConstraint is a
    + * caller-provided list of conditions which this function may assume
    + * to be true. Both provenConstraint and testConstraint must be in
    + * implicit-AND form, must only contain immutable clauses, and must
    + * contain only Vars with varno = 1.
    + */
    +bool
    +ConstraintImpliedByRelConstraint(Relation scanrel, List *testConstraint, List *provenConstraint)
    +{
    +	List	   *existConstraint = list_copy(provenConstraint);
    +	TupleConstr *constr = RelationGetDescr(scanrel)->constr;
    +	int			num_check,
    +				i;
    +
    +	num_check = (constr != NULL) ? constr->num_check : 0;
    +	for (i = 0; i < num_check; i++)
    +	{
    +		Node	   *cexpr;
    +
    +		/*
    +		 * If this constraint hasn't been fully validated yet, we must ignore
    +		 * it here.
    +		 */
    +		if (!constr->check[i].ccvalid)
    +			continue;
    +
    +		/*
    +		 * NOT ENFORCED constraints are always marked as invalid, which should
    +		 * have been ignored.
    +		 */
    +		Assert(constr->check[i].ccenforced);
    +
    +		cexpr = stringToNode(constr->check[i].ccbin);
    +
    +		/*
    +		 * Run each expression through const-simplification and
    +		 * canonicalization.  It is necessary, because we will be comparing it
    +		 * to similarly-processed partition constraint expressions, and may
    +		 * fail to detect valid matches without this.
    +		 */
    +		cexpr = eval_const_expressions(NULL, cexpr);
    +		cexpr = (Node *) canonicalize_qual((Expr *) cexpr, true);
    +
    +		existConstraint = list_concat(existConstraint,
    +									  make_ands_implicit((Expr *) cexpr));
    +	}
    +
    +	/*
    +	 * Try to make the proof.  Since we are comparing CHECK constraints, we
    +	 * need to use weak implication, i.e., we assume existConstraint is
    +	 * not-false and try to prove the same for testConstraint.
    +	 *
    +	 * Note that predicate_implied_by assumes its first argument is known
    +	 * immutable.  That should always be true for both NOT NULL and partition
    +	 * constraints, so we don't test it here.
    +	 */
    +	return predicate_implied_by(testConstraint, existConstraint, true);
    +}
    +
    +/*
    + * QueuePartitionConstraintValidation
    + *
    + * Add an entry to wqueue to have the given partition constraint validated by
    + * Phase 3, for the given relation, and all its children.
    + *
    + * We first verify whether the given constraint is implied by pre-existing
    + * relation constraints; if it is, there's no need to scan the table to
    + * validate, so don't queue in that case.
    + */
    +static void
    +QueuePartitionConstraintValidation(List **wqueue, Relation scanrel,
    +								   List *partConstraint,
    +								   bool validate_default)
    +{
    +	/*
    +	 * Based on the table's existing constraints, determine whether or not we
    +	 * may skip scanning the table.
    +	 */
    +	if (PartConstraintImpliedByRelConstraint(scanrel, partConstraint))
    +	{
    +		if (!validate_default)
    +			ereport(DEBUG1,
    +					(errmsg_internal("partition constraint for table \"%s\" is implied by existing constraints",
    +									 RelationGetRelationName(scanrel))));
    +		else
    +			ereport(DEBUG1,
    +					(errmsg_internal("updated partition constraint for default partition \"%s\" is implied by existing constraints",
    +									 RelationGetRelationName(scanrel))));
    +		return;
    +	}
    +
    +	/*
    +	 * Constraints proved insufficient. For plain relations, queue a
    +	 * validation item now; for partitioned tables, recurse to process each
    +	 * partition.
    +	 */
    +	if (scanrel->rd_rel->relkind == RELKIND_RELATION)
    +	{
    +		AlteredTableInfo *tab;
    +
    +		/* Grab a work queue entry. */
    +		tab = ATGetQueueEntry(wqueue, scanrel);
    +		Assert(tab->partition_constraint == NULL);
    +		tab->partition_constraint = (Expr *) linitial(partConstraint);
    +		tab->validate_default = validate_default;
    +	}
    +	else if (scanrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
    +	{
    +		PartitionDesc partdesc = RelationGetPartitionDesc(scanrel, true);
    +		int			i;
    +
    +		for (i = 0; i < partdesc->nparts; i++)
    +		{
    +			Relation	part_rel;
    +			List	   *thisPartConstraint;
    +
    +			/*
    +			 * This is the minimum lock we need to prevent deadlocks.
    +			 */
    +			part_rel = table_open(partdesc->oids[i], AccessExclusiveLock);
    +
    +			/*
    +			 * Adjust the constraint for scanrel so that it matches this
    +			 * partition's attribute numbers.
    +			 */
    +			thisPartConstraint =
    +				map_partition_varattnos(partConstraint, 1,
    +										part_rel, scanrel);
    +
    +			QueuePartitionConstraintValidation(wqueue, part_rel,
    +											   thisPartConstraint,
    +											   validate_default);
    +			table_close(part_rel, NoLock);	/* keep lock till commit */
    +		}
    +	}
    +}
    +
    +/*
    + * AttachPartitionEnsureIndexes
    + *		subroutine for ATExecAttachPartition to create/match indexes
    + *
    + * Enforce the indexing rule for partitioned tables during ALTER TABLE / ATTACH
    + * PARTITION: every partition must have an index attached to each index on the
    + * partitioned table.
    + */
    +static void
    +AttachPartitionEnsureIndexes(List **wqueue, Relation rel, Relation attachrel)
    +{
    +	List	   *idxes;
    +	List	   *attachRelIdxs;
    +	Relation   *attachrelIdxRels;
    +	IndexInfo **attachInfos;
    +	ListCell   *cell;
    +	MemoryContext cxt;
    +	MemoryContext oldcxt;
    +
    +	cxt = AllocSetContextCreate(CurrentMemoryContext,
    +								"AttachPartitionEnsureIndexes",
    +								ALLOCSET_DEFAULT_SIZES);
    +	oldcxt = MemoryContextSwitchTo(cxt);
    +
    +	idxes = RelationGetIndexList(rel);
    +	attachRelIdxs = RelationGetIndexList(attachrel);
    +	attachrelIdxRels = palloc(sizeof(Relation) * list_length(attachRelIdxs));
    +	attachInfos = palloc(sizeof(IndexInfo *) * list_length(attachRelIdxs));
    +
    +	/* Build arrays of all existing indexes and their IndexInfos */
    +	foreach_oid(cldIdxId, attachRelIdxs)
    +	{
    +		int			i = foreach_current_index(cldIdxId);
    +
    +		attachrelIdxRels[i] = index_open(cldIdxId, AccessShareLock);
    +		attachInfos[i] = BuildIndexInfo(attachrelIdxRels[i]);
    +	}
    +
    +	/*
    +	 * If we're attaching a foreign table, we must fail if any of the indexes
    +	 * is a constraint index; otherwise, there's nothing to do here.  Do this
    +	 * before starting work, to avoid wasting the effort of building a few
    +	 * non-unique indexes before coming across a unique one.
    +	 */
    +	if (attachrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
    +	{
    +		foreach(cell, idxes)
    +		{
    +			Oid			idx = lfirst_oid(cell);
    +			Relation	idxRel = index_open(idx, AccessShareLock);
    +
    +			if (idxRel->rd_index->indisunique ||
    +				idxRel->rd_index->indisprimary)
    +				ereport(ERROR,
    +						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    +						 errmsg("cannot attach foreign table \"%s\" as partition of partitioned table \"%s\"",
    +								RelationGetRelationName(attachrel),
    +								RelationGetRelationName(rel)),
    +						 errdetail("Partitioned table \"%s\" contains unique indexes.",
    +								   RelationGetRelationName(rel))));
    +			index_close(idxRel, AccessShareLock);
    +		}
    +
    +		goto out;
    +	}
    +
    +	/*
    +	 * For each index on the partitioned table, find a matching one in the
    +	 * partition-to-be; if one is not found, create one.
    +	 */
    +	foreach(cell, idxes)
    +	{
    +		Oid			idx = lfirst_oid(cell);
    +		Relation	idxRel = index_open(idx, AccessShareLock);
    +		IndexInfo  *info;
    +		AttrMap    *attmap;
    +		bool		found = false;
    +		Oid			constraintOid;
    +
    +		/*
    +		 * Ignore indexes in the partitioned table other than partitioned
    +		 * indexes.
    +		 */
    +		if (idxRel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
    +		{
    +			index_close(idxRel, AccessShareLock);
    +			continue;
    +		}
    +
    +		/* construct an indexinfo to compare existing indexes against */
    +		info = BuildIndexInfo(idxRel);
    +		attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
    +									   RelationGetDescr(rel),
    +									   false);
    +		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
    +
    +		/*
    +		 * Scan the list of existing indexes in the partition-to-be, and mark
    +		 * the first matching, valid, unattached one we find, if any, as
    +		 * partition of the parent index.  If we find one, we're done.
    +		 */
    +		for (int i = 0; i < list_length(attachRelIdxs); i++)
    +		{
    +			Oid			cldIdxId = RelationGetRelid(attachrelIdxRels[i]);
    +			Oid			cldConstrOid = InvalidOid;
    +
    +			/* does this index have a parent?  if so, can't use it */
    +			if (attachrelIdxRels[i]->rd_rel->relispartition)
    +				continue;
    +
    +			/* If this index is invalid, can't use it */
    +			if (!attachrelIdxRels[i]->rd_index->indisvalid)
    +				continue;
    +
    +			if (CompareIndexInfo(attachInfos[i], info,
    +								 attachrelIdxRels[i]->rd_indcollation,
    +								 idxRel->rd_indcollation,
    +								 attachrelIdxRels[i]->rd_opfamily,
    +								 idxRel->rd_opfamily,
    +								 attmap))
    +			{
    +				/*
    +				 * If this index is being created in the parent because of a
    +				 * constraint, then the child needs to have a constraint also,
    +				 * so look for one.  If there is no such constraint, this
    +				 * index is no good, so keep looking.
    +				 */
    +				if (OidIsValid(constraintOid))
    +				{
    +					cldConstrOid =
    +						get_relation_idx_constraint_oid(RelationGetRelid(attachrel),
    +														cldIdxId);
    +					/* no dice */
    +					if (!OidIsValid(cldConstrOid))
    +						continue;
    +
    +					/* Ensure they're both the same type of constraint */
    +					if (get_constraint_type(constraintOid) !=
    +						get_constraint_type(cldConstrOid))
    +						continue;
    +				}
    +
    +				/* bingo. */
    +				IndexSetParentIndex(attachrelIdxRels[i], idx);
    +				if (OidIsValid(constraintOid))
    +					ConstraintSetParentConstraint(cldConstrOid, constraintOid,
    +												  RelationGetRelid(attachrel));
    +				found = true;
    +
    +				CommandCounterIncrement();
    +				break;
    +			}
    +		}
    +
    +		/*
    +		 * If no suitable index was found in the partition-to-be, create one
    +		 * now.  Note that if this is a PK, not-null constraints must already
    +		 * exist.
    +		 */
    +		if (!found)
    +		{
    +			IndexStmt  *stmt;
    +			Oid			conOid;
    +
    +			stmt = generateClonedIndexStmt(NULL,
    +										   idxRel, attmap,
    +										   &conOid);
    +			DefineIndex(RelationGetRelid(attachrel), stmt, InvalidOid,
    +						RelationGetRelid(idxRel),
    +						conOid,
    +						-1,
    +						true, false, false, false, false);
    +		}
    +
    +		index_close(idxRel, AccessShareLock);
    +	}
    +
    +out:
    +	/* Clean up. */
    +	for (int i = 0; i < list_length(attachRelIdxs); i++)
    +		index_close(attachrelIdxRels[i], AccessShareLock);
    +	MemoryContextSwitchTo(oldcxt);
    +	MemoryContextDelete(cxt);
    +}
    +
    +/*
    + * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
    + *
    + * Return the address of the newly attached partition.
    + */
    +ObjectAddress
    +ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
    +					  AlterTableUtilityContext *context)
    +{
    +	Relation	attachrel,
    +				catalog;
    +	List	   *attachrel_children;
    +	List	   *partConstraint;
    +	SysScanDesc scan;
    +	ScanKeyData skey;
    +	AttrNumber	attno;
    +	int			natts;
    +	TupleDesc	tupleDesc;
    +	ObjectAddress address;
    +	const char *trigger_name;
    +	Oid			defaultPartOid;
    +	List	   *partBoundConstraint;
    +	ParseState *pstate = make_parsestate(NULL);
    +
    +	pstate->p_sourcetext = context->queryString;
    +
    +	/*
    +	 * We must lock the default partition if one exists, because attaching a
    +	 * new partition will change its partition constraint.
    +	 */
    +	defaultPartOid =
    +		get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true));
    +	if (OidIsValid(defaultPartOid))
    +		LockRelationOid(defaultPartOid, AccessExclusiveLock);
    +
    +	attachrel = table_openrv(cmd->name, AccessExclusiveLock);
    +
    +	/*
    +	 * XXX I think it'd be a good idea to grab locks on all tables referenced
    +	 * by FKs at this point also.
    +	 */
    +
    +	/*
    +	 * Must be owner of both parent and source table -- parent was checked by
    +	 * ATSimplePermissions call in ATPrepCmd
    +	 */
    +	ATSimplePermissions(AT_AttachPartition, attachrel,
    +						ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
    +
    +	/* A partition can only have one parent */
    +	if (attachrel->rd_rel->relispartition)
    +		ereport(ERROR,
    +				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    +				 errmsg("\"%s\" is already a partition",
    +						RelationGetRelationName(attachrel))));
    +
    +	if (OidIsValid(attachrel->rd_rel->reloftype))
    +		ereport(ERROR,
    +				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    +				 errmsg("cannot attach a typed table as partition")));
    +
    +	/*
    +	 * Table being attached should not already be part of inheritance; either
    +	 * as a child table...
    +	 */
    +	catalog = table_open(InheritsRelationId, AccessShareLock);
    +	ScanKeyInit(&skey,
    +				Anum_pg_inherits_inhrelid,
    +				BTEqualStrategyNumber, F_OIDEQ,
    +				ObjectIdGetDatum(RelationGetRelid(attachrel)));
    +	scan = systable_beginscan(catalog, InheritsRelidSeqnoIndexId, true,
    +							  NULL, 1, &skey);
    +	if (HeapTupleIsValid(systable_getnext(scan)))
    +		ereport(ERROR,
    +				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    +				 errmsg("cannot attach inheritance child as partition")));
    +	systable_endscan(scan);
    +
    +	/* ...or as a parent table (except the case when it is partitioned) */
    +	ScanKeyInit(&skey,
    +				Anum_pg_inherits_inhparent,
    +				BTEqualStrategyNumber, F_OIDEQ,
    +				ObjectIdGetDatum(RelationGetRelid(attachrel)));
    +	scan = systable_beginscan(catalog, InheritsParentIndexId, true, NULL,
    +							  1, &skey);
    +	if (HeapTupleIsValid(systable_getnext(scan)) &&
    +		attachrel->rd_rel->relkind == RELKIND_RELATION)
    +		ereport(ERROR,
    +				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    +				 errmsg("cannot attach inheritance parent as partition")));
    +	systable_endscan(scan);
    +	table_close(catalog, AccessShareLock);
    +
    +	/*
    +	 * Prevent circularity by seeing if rel is a partition of attachrel. (In
    +	 * particular, this disallows making a rel a partition of itself.)
    +	 *
    +	 * We do that by checking if rel is a member of the list of attachrel's
    +	 * partitions provided the latter is partitioned at all.  We want to avoid
    +	 * having to construct this list again, so we request the strongest lock
    +	 * on all partitions.  We need the strongest lock, because we may decide
    +	 * to scan them if we find out that the table being attached (or its leaf
    +	 * partitions) may contain rows that violate the partition constraint. If
    +	 * the table has a constraint that would prevent such rows, which by
    +	 * definition is present in all the partitions, we need not scan the
    +	 * table, nor its partitions.  But we cannot risk a deadlock by taking a
    +	 * weaker lock now and the stronger one only when needed.
    +	 */
    +	attachrel_children = find_all_inheritors(RelationGetRelid(attachrel),
    +											 AccessExclusiveLock, NULL);
    +	if (list_member_oid(attachrel_children, RelationGetRelid(rel)))
    +		ereport(ERROR,
    +				(errcode(ERRCODE_DUPLICATE_TABLE),
    +				 errmsg("circular inheritance not allowed"),
    +				 errdetail("\"%s\" is already a child of \"%s\".",
    +						   RelationGetRelationName(rel),
    +						   RelationGetRelationName(attachrel))));
    +
    +	/* If the parent is permanent, so must be all of its partitions. */
    +	if (rel->rd_rel->relpersistence != RELPERSISTENCE_TEMP &&
    +		attachrel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
    +		ereport(ERROR,
    +				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    +				 errmsg("cannot attach a temporary relation as partition of permanent relation \"%s\"",
    +						RelationGetRelationName(rel))));
    +
    +	/* Temp parent cannot have a partition that is itself not a temp */
    +	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
    +		attachrel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
    +		ereport(ERROR,
    +				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    +				 errmsg("cannot attach a permanent relation as partition of temporary relation \"%s\"",
    +						RelationGetRelationName(rel))));
    +
    +	/* If the parent is temp, it must belong to this session */
    +	if (RELATION_IS_OTHER_TEMP(rel))
    +		ereport(ERROR,
    +				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    +				 errmsg("cannot attach as partition of temporary relation of another session")));
    +
    +	/* Ditto for the partition */
    +	if (RELATION_IS_OTHER_TEMP(attachrel))
    +		ereport(ERROR,
    +				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    +				 errmsg("cannot attach temporary relation of another session as partition")));
    +
    +	/*
    +	 * Check if attachrel has any identity columns or any columns that aren't
    +	 * in the parent.
    +	 */
    +	tupleDesc = RelationGetDescr(attachrel);
    +	natts = tupleDesc->natts;
    +	for (attno = 1; attno <= natts; attno++)
    +	{
    +		Form_pg_attribute attribute = TupleDescAttr(tupleDesc, attno - 1);
    +		char	   *attributeName = NameStr(attribute->attname);
    +
    +		/* Ignore dropped */
    +		if (attribute->attisdropped)
    +			continue;
    +
    +		if (attribute->attidentity)
    +			ereport(ERROR,
    +					errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    +					errmsg("table \"%s\" being attached contains an identity column \"%s\"",
    +						   RelationGetRelationName(attachrel), attributeName),
    +					errdetail("The new partition may not contain an identity column."));
    +
    +		/* Try to find the column in parent (matching on column name) */
    +		if (!SearchSysCacheExists2(ATTNAME,
    +								   ObjectIdGetDatum(RelationGetRelid(rel)),
    +								   CStringGetDatum(attributeName)))
    +			ereport(ERROR,
    +					(errcode(ERRCODE_DATATYPE_MISMATCH),
    +					 errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
    +							RelationGetRelationName(attachrel), attributeName,
    +							RelationGetRelationName(rel)),
    +					 errdetail("The new partition may contain only the columns present in parent.")));
    +	}
    +
    +	/*
    +	 * If child_rel has row-level triggers with transition tables, we
    +	 * currently don't allow it to become a partition.  See also prohibitions
    +	 * in ATExecAddInherit() and CreateTrigger().
    +	 */
    +	trigger_name = FindTriggerIncompatibleWithInheritance(attachrel->trigdesc);
    +	if (trigger_name != NULL)
    +		ereport(ERROR,
    +				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
    +				 errmsg("trigger \"%s\" prevents table \"%s\" from becoming a partition",
    +						trigger_name, RelationGetRelationName(attachrel)),
    +				 errdetail("ROW triggers with transition tables are not supported on partitions.")));
    +
    +	/*
    +	 * Check that the new partition's bound is valid and does not overlap any
    +	 * of existing partitions of the parent - note that it does not return on
    +	 * error.
    +	 */
    +	check_new_partition_bound(RelationGetRelationName(attachrel), rel,
    +							  cmd->bound, pstate);
    +
    +	/* 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.
    +	 * If the parent itself is a partition, make sure to include its
    +	 * constraint as well.
    +	 */
    +	partBoundConstraint = get_qual_from_partbound(rel, cmd->bound);
    +
    +	/*
    +	 * Use list_concat_copy() to avoid modifying partBoundConstraint in place,
    +	 * since it's needed later to construct the constraint expression for
    +	 * validating against the default partition, if any.
    +	 */
    +	partConstraint = list_concat_copy(partBoundConstraint,
    +									  RelationGetPartitionQual(rel));
    +
    +	/* Skip validation if there are no constraints to validate. */
    +	if (partConstraint)
    +	{
    +		/*
    +		 * Run the partition quals through const-simplification similar to
    +		 * check constraints.  We skip canonicalize_qual, though, because
    +		 * partition quals should be in canonical form already.
    +		 */
    +		partConstraint =
    +			(List *) eval_const_expressions(NULL,
    +											(Node *) partConstraint);
    +
    +		/* XXX this sure looks wrong */
    +		partConstraint = list_make1(make_ands_explicit(partConstraint));
    +
    +		/*
    +		 * Adjust the generated constraint to match this partition's attribute
    +		 * numbers.
    +		 */
    +		partConstraint = map_partition_varattnos(partConstraint, 1, attachrel,
    +												 rel);
    +
    +		/* Validate partition constraints against the table being attached. */
    +		QueuePartitionConstraintValidation(wqueue, attachrel, partConstraint,
    +										   false);
    +	}
    +
    +	/*
    +	 * If we're attaching a partition other than the default partition and a
    +	 * default one exists, then that partition's partition constraint changes,
    +	 * so add an entry to the work queue to validate it, too.  (We must not do
    +	 * this when the partition being attached is the default one; we already
    +	 * did it above!)
    +	 */
    +	if (OidIsValid(defaultPartOid))
    +	{
    +		Relation	defaultrel;
    +		List	   *defPartConstraint;
    +
    +		Assert(!cmd->bound->is_default);
    +
    +		/* we already hold a lock on the default partition */
    +		defaultrel = table_open(defaultPartOid, NoLock);
    +		defPartConstraint =
    +			get_proposed_default_constraint(partBoundConstraint);
    +
    +		/*
    +		 * Map the Vars in the constraint expression from rel's attnos to
    +		 * defaultrel's.
    +		 */
    +		defPartConstraint =
    +			map_partition_varattnos(defPartConstraint,
    +									1, defaultrel, rel);
    +		QueuePartitionConstraintValidation(wqueue, defaultrel,
    +										   defPartConstraint, true);
    +
    +		/* keep our lock until commit. */
    +		table_close(defaultrel, NoLock);
    +	}
    +
    +	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachrel));
    +
    +	/*
    +	 * If the partition we just attached is partitioned itself, invalidate
    +	 * relcache for all descendent partitions too to ensure that their
    +	 * rd_partcheck expression trees are rebuilt; partitions already locked at
    +	 * the beginning of this function.
    +	 */
    +	if (attachrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
    +	{
    +		ListCell   *l;
    +
    +		foreach(l, attachrel_children)
    +		{
    +			CacheInvalidateRelcacheByRelid(lfirst_oid(l));
    +		}
    +	}
    +
    +	/* keep our lock until commit */
    +	table_close(attachrel, NoLock);
    +
    +	return address;
    +}
    +
    +/*
    + * CloneRowTriggersToPartition
    + *		subroutine for ATExecAttachPartition/DefineRelation to create row
    + *		triggers on partitions
    + */
    +void
    +CloneRowTriggersToPartition(Relation parent, Relation partition)
    +{
    +	Relation	pg_trigger;
    +	ScanKeyData key;
    +	SysScanDesc scan;
    +	HeapTuple	tuple;
    +	MemoryContext perTupCxt;
    +
    +	ScanKeyInit(&key, Anum_pg_trigger_tgrelid, BTEqualStrategyNumber,
    +				F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(parent)));
    +	pg_trigger = table_open(TriggerRelationId, RowExclusiveLock);
    +	scan = systable_beginscan(pg_trigger, TriggerRelidNameIndexId,
    +							  true, NULL, 1, &key);
    +
    +	perTupCxt = AllocSetContextCreate(CurrentMemoryContext,
    +									  "clone trig", ALLOCSET_SMALL_SIZES);
    +
    +	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
    +	{
    +		Form_pg_trigger trigForm = (Form_pg_trigger) GETSTRUCT(tuple);
    +		CreateTrigStmt *trigStmt;
    +		Node	   *qual = NULL;
    +		Datum		value;
    +		bool		isnull;
    +		List	   *cols = NIL;
    +		List	   *trigargs = NIL;
    +		MemoryContext oldcxt;
    +
    +		/*
    +		 * Ignore statement-level triggers; those are not cloned.
    +		 */
    +		if (!TRIGGER_FOR_ROW(trigForm->tgtype))
    +			continue;
    +
    +		/*
    +		 * Don't clone internal triggers, because the constraint cloning code
    +		 * will.
    +		 */
    +		if (trigForm->tgisinternal)
    +			continue;
    +
    +		/*
    +		 * Complain if we find an unexpected trigger type.
    +		 */
    +		if (!TRIGGER_FOR_BEFORE(trigForm->tgtype) &&
    +			!TRIGGER_FOR_AFTER(trigForm->tgtype))
    +			elog(ERROR, "unexpected trigger \"%s\" found",
    +				 NameStr(trigForm->tgname));
    +
    +		/* Use short-lived context for CREATE TRIGGER */
    +		oldcxt = MemoryContextSwitchTo(perTupCxt);
    +
    +		/*
    +		 * If there is a WHEN clause, generate a 'cooked' version of it that's
    +		 * appropriate for the partition.
    +		 */
    +		value = heap_getattr(tuple, Anum_pg_trigger_tgqual,
    +							 RelationGetDescr(pg_trigger), &isnull);
    +		if (!isnull)
    +		{
    +			qual = stringToNode(TextDatumGetCString(value));
    +			qual = (Node *) map_partition_varattnos((List *) qual, PRS2_OLD_VARNO,
    +													partition, parent);
    +			qual = (Node *) map_partition_varattnos((List *) qual, PRS2_NEW_VARNO,
    +													partition, parent);
    +		}
    +
    +		/*
    +		 * If there is a column list, transform it to a list of column names.
    +		 * Note we don't need to map this list in any way ...
    +		 */
    +		if (trigForm->tgattr.dim1 > 0)
    +		{
    +			int			i;
    +
    +			for (i = 0; i < trigForm->tgattr.dim1; i++)
    +			{
    +				Form_pg_attribute col;
    +
    +				col = TupleDescAttr(parent->rd_att,
    +									trigForm->tgattr.values[i] - 1);
    +				cols = lappend(cols,
    +							   makeString(pstrdup(NameStr(col->attname))));
    +			}
    +		}
    +
    +		/* Reconstruct trigger arguments list. */
    +		if (trigForm->tgnargs > 0)
    +		{
    +			char	   *p;
    +
    +			value = heap_getattr(tuple, Anum_pg_trigger_tgargs,
    +								 RelationGetDescr(pg_trigger), &isnull);
    +			if (isnull)
    +				elog(ERROR, "tgargs is null for trigger \"%s\" in partition \"%s\"",
    +					 NameStr(trigForm->tgname), RelationGetRelationName(partition));
    +
    +			p = (char *) VARDATA_ANY(DatumGetByteaPP(value));
    +
    +			for (int i = 0; i < trigForm->tgnargs; i++)
    +			{
    +				trigargs = lappend(trigargs, makeString(pstrdup(p)));
    +				p += strlen(p) + 1;
    +			}
    +		}
    +
    +		trigStmt = makeNode(CreateTrigStmt);
    +		trigStmt->replace = false;
    +		trigStmt->isconstraint = OidIsValid(trigForm->tgconstraint);
    +		trigStmt->trigname = NameStr(trigForm->tgname);
    +		trigStmt->relation = NULL;
    +		trigStmt->funcname = NULL;	/* passed separately */
    +		trigStmt->args = trigargs;
    +		trigStmt->row = true;
    +		trigStmt->timing = trigForm->tgtype & TRIGGER_TYPE_TIMING_MASK;
    +		trigStmt->events = trigForm->tgtype & TRIGGER_TYPE_EVENT_MASK;
    +		trigStmt->columns = cols;
    +		trigStmt->whenClause = NULL;	/* passed separately */
    +		trigStmt->transitionRels = NIL; /* not supported at present */
    +		trigStmt->deferrable = trigForm->tgdeferrable;
    +		trigStmt->initdeferred = trigForm->tginitdeferred;
    +		trigStmt->constrrel = NULL; /* passed separately */
    +
    +		CreateTriggerFiringOn(trigStmt, NULL, RelationGetRelid(partition),
    +							  trigForm->tgconstrrelid, InvalidOid, InvalidOid,
    +							  trigForm->tgfoid, trigForm->oid, qual,
    +							  false, true, trigForm->tgenabled);
    +
    +		MemoryContextSwitchTo(oldcxt);
    +		MemoryContextReset(perTupCxt);
    +	}
    +
    +	MemoryContextDelete(perTupCxt);
    +
    +	systable_endscan(scan);
    +	table_close(pg_trigger, RowExclusiveLock);
    +}
    +
    +/*
    + * Return an OID list of constraints that reference the given relation
    + * that are marked as having a parent constraints.
    + */
    +static List *
    +GetParentedForeignKeyRefs(Relation partition)
    +{
    +	Relation	pg_constraint;
    +	HeapTuple	tuple;
    +	SysScanDesc scan;
    +	ScanKeyData key[2];
    +	List	   *constraints = NIL;
    +
    +	/*
    +	 * If no indexes, or no columns are referenceable by FKs, we can avoid the
    +	 * scan.
    +	 */
    +	if (RelationGetIndexList(partition) == NIL ||
    +		bms_is_empty(RelationGetIndexAttrBitmap(partition,
    +												INDEX_ATTR_BITMAP_KEY)))
    +		return NIL;
    +
    +	/* Search for constraints referencing this table */
    +	pg_constraint = table_open(ConstraintRelationId, AccessShareLock);
    +	ScanKeyInit(&key[0],
    +				Anum_pg_constraint_confrelid, BTEqualStrategyNumber,
    +				F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(partition)));
    +	ScanKeyInit(&key[1],
    +				Anum_pg_constraint_contype, BTEqualStrategyNumber,
    +				F_CHAREQ, CharGetDatum(CONSTRAINT_FOREIGN));
    +
    +	/* XXX This is a seqscan, as we don't have a usable index */
    +	scan = systable_beginscan(pg_constraint, InvalidOid, true, NULL, 2, key);
    +	while ((tuple = systable_getnext(scan)) != NULL)
    +	{
    +		Form_pg_constraint constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
    +
    +		/*
    +		 * We only need to process constraints that are part of larger ones.
    +		 */
    +		if (!OidIsValid(constrForm->conparentid))
    +			continue;
    +
    +		constraints = lappend_oid(constraints, constrForm->oid);
    +	}
    +
    +	systable_endscan(scan);
    +	table_close(pg_constraint, AccessShareLock);
    +
    +	return constraints;
    +}
    +
    +/*
    + * During DETACH PARTITION, verify that any foreign keys pointing to the
    + * partitioned table would not become invalid.  An error is raised if any
    + * referenced values exist.
    + */
    +static void
    +ATDetachCheckNoForeignKeyRefs(Relation partition)
    +{
    +	List	   *constraints;
    +	ListCell   *cell;
    +
    +	constraints = GetParentedForeignKeyRefs(partition);
    +
    +	foreach(cell, constraints)
    +	{
    +		Oid			constrOid = lfirst_oid(cell);
    +		HeapTuple	tuple;
    +		Form_pg_constraint constrForm;
    +		Relation	rel;
    +		Trigger		trig = {0};
    +
    +		tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid));
    +		if (!HeapTupleIsValid(tuple))
    +			elog(ERROR, "cache lookup failed for constraint %u", constrOid);
    +		constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
    +
    +		Assert(OidIsValid(constrForm->conparentid));
    +		Assert(constrForm->confrelid == RelationGetRelid(partition));
    +
    +		/* prevent data changes into the referencing table until commit */
    +		rel = table_open(constrForm->conrelid, ShareLock);
    +
    +		trig.tgoid = InvalidOid;
    +		trig.tgname = NameStr(constrForm->conname);
    +		trig.tgenabled = TRIGGER_FIRES_ON_ORIGIN;
    +		trig.tgisinternal = true;
    +		trig.tgconstrrelid = RelationGetRelid(partition);
    +		trig.tgconstrindid = constrForm->conindid;
    +		trig.tgconstraint = constrForm->oid;
    +		trig.tgdeferrable = false;
    +		trig.tginitdeferred = false;
    +		/* we needn't fill in remaining fields */
    +
    +		RI_PartitionRemove_Check(&trig, rel, partition);
    +
    +		ReleaseSysCache(tuple);
    +
    +		table_close(rel, NoLock);
    +	}
    +}
    +
    +/*
    + * DropClonedTriggersFromPartition
    + *		subroutine for ATExecDetachPartition to remove any triggers that were
    + *		cloned to the partition when it was created-as-partition or attached.
    + *		This undoes what CloneRowTriggersToPartition did.
    + */
    +static void
    +DropClonedTriggersFromPartition(Oid partitionId)
    +{
    +	ScanKeyData skey;
    +	SysScanDesc scan;
    +	HeapTuple	trigtup;
    +	Relation	tgrel;
    +	ObjectAddresses *objects;
    +
    +	objects = new_object_addresses();
    +
    +	/*
    +	 * Scan pg_trigger to search for all triggers on this rel.
    +	 */
    +	ScanKeyInit(&skey, Anum_pg_trigger_tgrelid, BTEqualStrategyNumber,
    +				F_OIDEQ, ObjectIdGetDatum(partitionId));
    +	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
    +	scan = systable_beginscan(tgrel, TriggerRelidNameIndexId,
    +							  true, NULL, 1, &skey);
    +	while (HeapTupleIsValid(trigtup = systable_getnext(scan)))
    +	{
    +		Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(trigtup);
    +		ObjectAddress trig;
    +
    +		/* Ignore triggers that weren't cloned */
    +		if (!OidIsValid(pg_trigger->tgparentid))
    +			continue;
    +
    +		/*
    +		 * Ignore internal triggers that are implementation objects of foreign
    +		 * keys, because these will be detached when the foreign keys
    +		 * themselves are.
    +		 */
    +		if (OidIsValid(pg_trigger->tgconstrrelid))
    +			continue;
    +
    +		/*
    +		 * This is ugly, but necessary: remove the dependency markings on the
    +		 * trigger so that it can be removed.
    +		 */
    +		deleteDependencyRecordsForClass(TriggerRelationId, pg_trigger->oid,
    +										TriggerRelationId,
    +										DEPENDENCY_PARTITION_PRI);
    +		deleteDependencyRecordsForClass(TriggerRelationId, pg_trigger->oid,
    +										RelationRelationId,
    +										DEPENDENCY_PARTITION_SEC);
    +
    +		/* remember this trigger to remove it below */
    +		ObjectAddressSet(trig, TriggerRelationId, pg_trigger->oid);
    +		add_exact_object_address(&trig, objects);
    +	}
    +
    +	/* make the dependency removal visible to the deletion below */
    +	CommandCounterIncrement();
    +	performMultipleDeletions(objects, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
    +
    +	/* done */
    +	free_object_addresses(objects);
    +	systable_endscan(scan);
    +	table_close(tgrel, RowExclusiveLock);
    +}
    +
    +/*
    + * Second part of ALTER TABLE .. DETACH.
    + *
    + * This is separate so that it can be run independently when the second
    + * transaction of the concurrent algorithm fails (crash or abort).
    + */
    +static void
    +DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
    +						Oid defaultPartOid)
    +{
    +	Relation	classRel;
    +	List	   *fks;
    +	ListCell   *cell;
    +	List	   *indexes;
    +	Datum		new_val[Natts_pg_class];
    +	bool		new_null[Natts_pg_class],
    +				new_repl[Natts_pg_class];
    +	HeapTuple	tuple,
    +				newtuple;
    +	Relation	trigrel = NULL;
    +	List	   *fkoids = NIL;
    +
    +	if (concurrent)
    +	{
    +		/*
    +		 * We can remove the pg_inherits row now. (In the non-concurrent case,
    +		 * this was already done).
    +		 */
    +		RemoveInheritance(partRel, rel, true);
    +	}
    +
    +	/* Drop any triggers that were cloned on creation/attach. */
    +	DropClonedTriggersFromPartition(RelationGetRelid(partRel));
    +
    +	/*
    +	 * Detach any foreign keys that are inherited.  This includes creating
    +	 * additional action triggers.
    +	 */
    +	fks = copyObject(RelationGetFKeyList(partRel));
    +	if (fks != NIL)
    +		trigrel = table_open(TriggerRelationId, RowExclusiveLock);
    +
    +	/*
    +	 * It's possible that the partition being detached has a foreign key that
    +	 * references a partitioned table.  When that happens, there are multiple
    +	 * pg_constraint rows for the partition: one points to the partitioned
    +	 * table itself, while the others point to each of its partitions.  Only
    +	 * the topmost one is to be considered here; the child constraints must be
    +	 * left alone, because conceptually those aren't coming from our parent
    +	 * partitioned table, but from this partition itself.
    +	 *
    +	 * We implement this by collecting all the constraint OIDs in a first scan
    +	 * of the FK array, and skipping in the loop below those constraints whose
    +	 * parents are listed here.
    +	 */
    +	foreach_node(ForeignKeyCacheInfo, fk, fks)
    +		fkoids = lappend_oid(fkoids, fk->conoid);
    +
    +	foreach(cell, fks)
    +	{
    +		ForeignKeyCacheInfo *fk = lfirst(cell);
    +		HeapTuple	contup;
    +		Form_pg_constraint conform;
    +
    +		contup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
    +		if (!HeapTupleIsValid(contup))
    +			elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
    +		conform = (Form_pg_constraint) GETSTRUCT(contup);
    +
    +		/*
    +		 * Consider only inherited foreign keys, and only if their parents
    +		 * aren't in the list.
    +		 */
    +		if (conform->contype != CONSTRAINT_FOREIGN ||
    +			!OidIsValid(conform->conparentid) ||
    +			list_member_oid(fkoids, conform->conparentid))
    +		{
    +			ReleaseSysCache(contup);
    +			continue;
    +		}
    +
    +		/*
    +		 * The constraint on this table must be marked no longer a child of
    +		 * the parent's constraint, as do its check triggers.
    +		 */
    +		ConstraintSetParentConstraint(fk->conoid, InvalidOid, InvalidOid);
    +
    +		/*
    +		 * Also, look up the partition's "check" triggers corresponding to the
    +		 * ENFORCED constraint being detached and detach them from the parent
    +		 * triggers. NOT ENFORCED constraints do not have these triggers;
    +		 * therefore, this step is not needed.
    +		 */
    +		if (fk->conenforced)
    +		{
    +			Oid			insertTriggerOid,
    +						updateTriggerOid;
    +
    +			GetForeignKeyCheckTriggers(trigrel,
    +									   fk->conoid, fk->confrelid, fk->conrelid,
    +									   &insertTriggerOid, &updateTriggerOid);
    +			Assert(OidIsValid(insertTriggerOid));
    +			TriggerSetParentTrigger(trigrel, insertTriggerOid, InvalidOid,
    +									RelationGetRelid(partRel));
    +			Assert(OidIsValid(updateTriggerOid));
    +			TriggerSetParentTrigger(trigrel, updateTriggerOid, InvalidOid,
    +									RelationGetRelid(partRel));
    +		}
    +
    +		/*
    +		 * Lastly, create the action triggers on the referenced table, using
    +		 * addFkRecurseReferenced, which requires some elaborate setup (so put
    +		 * it in a separate block).  While at it, if the table is partitioned,
    +		 * that function will recurse to create the pg_constraint rows and
    +		 * action triggers for each partition.
    +		 *
    +		 * Note there's no need to do addFkConstraint() here, because the
    +		 * pg_constraint row already exists.
    +		 */
    +		{
    +			Constraint *fkconstraint;
    +			int			numfks;
    +			AttrNumber	conkey[INDEX_MAX_KEYS];
    +			AttrNumber	confkey[INDEX_MAX_KEYS];
    +			Oid			conpfeqop[INDEX_MAX_KEYS];
    +			Oid			conppeqop[INDEX_MAX_KEYS];
    +			Oid			conffeqop[INDEX_MAX_KEYS];
    +			int			numfkdelsetcols;
    +			AttrNumber	confdelsetcols[INDEX_MAX_KEYS];
    +			Relation	refdRel;
    +
    +			DeconstructFkConstraintRow(contup,
    +									   &numfks,
    +									   conkey,
    +									   confkey,
    +									   conpfeqop,
    +									   conppeqop,
    +									   conffeqop,
    +									   &numfkdelsetcols,
    +									   confdelsetcols);
    +
    +			/* Create a synthetic node we'll use throughout */
    +			fkconstraint = makeNode(Constraint);
    +			fkconstraint->contype = CONSTRAINT_FOREIGN;
    +			fkconstraint->conname = pstrdup(NameStr(conform->conname));
    +			fkconstraint->deferrable = conform->condeferrable;
    +			fkconstraint->initdeferred = conform->condeferred;
    +			fkconstraint->is_enforced = conform->conenforced;
    +			fkconstraint->skip_validation = true;
    +			fkconstraint->initially_valid = conform->convalidated;
    +			/* a few irrelevant fields omitted here */
    +			fkconstraint->pktable = NULL;
    +			fkconstraint->fk_attrs = NIL;
    +			fkconstraint->pk_attrs = NIL;
    +			fkconstraint->fk_matchtype = conform->confmatchtype;
    +			fkconstraint->fk_upd_action = conform->confupdtype;
    +			fkconstraint->fk_del_action = conform->confdeltype;
    +			fkconstraint->fk_del_set_cols = NIL;
    +			fkconstraint->old_conpfeqop = NIL;
    +			fkconstraint->old_pktable_oid = InvalidOid;
    +			fkconstraint->location = -1;
    +
    +			/* set up colnames, used to generate the constraint name */
    +			for (int i = 0; i < numfks; i++)
    +			{
    +				Form_pg_attribute att;
    +
    +				att = TupleDescAttr(RelationGetDescr(partRel),
    +									conkey[i] - 1);
    +
    +				fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs,
    +												 makeString(NameStr(att->attname)));
    +			}
    +
    +			refdRel = table_open(fk->confrelid, ShareRowExclusiveLock);
    +
    +			addFkRecurseReferenced(fkconstraint, partRel,
    +								   refdRel,
    +								   conform->conindid,
    +								   fk->conoid,
    +								   numfks,
    +								   confkey,
    +								   conkey,
    +								   conpfeqop,
    +								   conppeqop,
    +								   conffeqop,
    +								   numfkdelsetcols,
    +								   confdelsetcols,
    +								   true,
    +								   InvalidOid, InvalidOid,
    +								   conform->conperiod);
    +			table_close(refdRel, NoLock);	/* keep lock till end of xact */
    +		}
    +
    +		ReleaseSysCache(contup);
    +	}
    +	list_free_deep(fks);
    +	if (trigrel)
    +		table_close(trigrel, RowExclusiveLock);
    +
    +	/*
    +	 * Any sub-constraints that are in the referenced-side of a larger
    +	 * constraint have to be removed.  This partition is no longer part of the
    +	 * key space of the constraint.
    +	 */
    +	foreach(cell, GetParentedForeignKeyRefs(partRel))
    +	{
    +		Oid			constrOid = lfirst_oid(cell);
    +		ObjectAddress constraint;
    +
    +		ConstraintSetParentConstraint(constrOid, InvalidOid, InvalidOid);
    +		deleteDependencyRecordsForClass(ConstraintRelationId,
    +										constrOid,
    +										ConstraintRelationId,
    +										DEPENDENCY_INTERNAL);
    +		CommandCounterIncrement();
    +
    +		ObjectAddressSet(constraint, ConstraintRelationId, constrOid);
    +		performDeletion(&constraint, DROP_RESTRICT, 0);
    +	}
    +
    +	/* Now we can detach indexes */
    +	indexes = RelationGetIndexList(partRel);
    +	foreach(cell, indexes)
    +	{
    +		Oid			idxid = lfirst_oid(cell);
    +		Oid			parentidx;
    +		Relation	idx;
    +		Oid			constrOid;
    +		Oid			parentConstrOid;
    +
    +		if (!has_superclass(idxid))
    +			continue;
    +
    +		parentidx = get_partition_parent(idxid, false);
    +		Assert((IndexGetRelation(parentidx, false) == RelationGetRelid(rel)));
    +
    +		idx = index_open(idxid, AccessExclusiveLock);
    +		IndexSetParentIndex(idx, InvalidOid);
    +
    +		/*
    +		 * If there's a constraint associated with the index, detach it too.
    +		 * Careful: it is possible for a constraint index in a partition to be
    +		 * the child of a non-constraint index, so verify whether the parent
    +		 * index does actually have a constraint.
    +		 */
    +		constrOid = get_relation_idx_constraint_oid(RelationGetRelid(partRel),
    +													idxid);
    +		parentConstrOid = get_relation_idx_constraint_oid(RelationGetRelid(rel),
    +														  parentidx);
    +		if (OidIsValid(parentConstrOid) && OidIsValid(constrOid))
    +			ConstraintSetParentConstraint(constrOid, InvalidOid, InvalidOid);
    +
    +		index_close(idx, NoLock);
    +	}
    +
    +	/* Update pg_class tuple */
    +	classRel = table_open(RelationRelationId, RowExclusiveLock);
    +	tuple = SearchSysCacheCopy1(RELOID,
    +								ObjectIdGetDatum(RelationGetRelid(partRel)));
    +	if (!HeapTupleIsValid(tuple))
    +		elog(ERROR, "cache lookup failed for relation %u",
    +			 RelationGetRelid(partRel));
    +	Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
    +
    +	/* Clear relpartbound and reset relispartition */
    +	memset(new_val, 0, sizeof(new_val));
    +	memset(new_null, false, sizeof(new_null));
    +	memset(new_repl, false, sizeof(new_repl));
    +	new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
    +	new_null[Anum_pg_class_relpartbound - 1] = true;
    +	new_repl[Anum_pg_class_relpartbound - 1] = true;
    +	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
    +								 new_val, new_null, new_repl);
    +
    +	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
    +	CatalogTupleUpdate(classRel, &newtuple->t_self, newtuple);
    +	heap_freetuple(newtuple);
    +	table_close(classRel, RowExclusiveLock);
    +
    +	/*
    +	 * Drop identity property from all identity columns of partition.
    +	 */
    +	for (int attno = 0; attno < RelationGetNumberOfAttributes(partRel); attno++)
    +	{
    +		Form_pg_attribute attr = TupleDescAttr(partRel->rd_att, attno);
    +
    +		if (!attr->attisdropped && attr->attidentity)
    +			ATExecDropIdentity(partRel, NameStr(attr->attname), false,
    +							   AccessExclusiveLock, true, true);
    +	}
    +
    +	if (OidIsValid(defaultPartOid))
    +	{
    +		/*
    +		 * If the relation being detached is the default partition itself,
    +		 * remove it from the parent's pg_partitioned_table entry.
    +		 *
    +		 * If not, we must invalidate default partition's relcache entry, as
    +		 * in StorePartitionBound: its partition constraint depends on every
    +		 * other partition's partition constraint.
    +		 */
    +		if (RelationGetRelid(partRel) == defaultPartOid)
    +			update_default_partition_oid(RelationGetRelid(rel), InvalidOid);
    +		else
    +			CacheInvalidateRelcacheByRelid(defaultPartOid);
    +	}
    +
    +	/*
    +	 * Invalidate the parent's relcache so that the partition is no longer
    +	 * included in its partition descriptor.
    +	 */
    +	CacheInvalidateRelcache(rel);
    +
    +	/*
    +	 * If the partition we just detached is partitioned itself, invalidate
    +	 * relcache for all descendent partitions too to ensure that their
    +	 * rd_partcheck expression trees are rebuilt; must lock partitions before
    +	 * doing so, using the same lockmode as what partRel has been locked with
    +	 * by the caller.
    +	 */
    +	if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
    +	{
    +		List	   *children;
    +
    +		children = find_all_inheritors(RelationGetRelid(partRel),
    +									   AccessExclusiveLock, NULL);
    +		foreach(cell, children)
    +		{
    +			CacheInvalidateRelcacheByRelid(lfirst_oid(cell));
    +		}
    +	}
    +}
    +
    +/*
    + * ALTER TABLE DETACH PARTITION
    + *
    + * Return the address of the relation that is no longer a partition of rel.
    + *
    + * If concurrent mode is requested, we run in two transactions.  A side-
    + * effect is that this command cannot run in a multi-part ALTER TABLE.
    + * Currently, that's enforced by the grammar.
    + *
    + * The strategy for concurrency is to first modify the partition's
    + * pg_inherit catalog row to make it visible to everyone that the
    + * partition is detached, lock the partition against writes, and commit
    + * the transaction; anyone who requests the partition descriptor from
    + * that point onwards has to ignore such a partition.  In a second
    + * transaction, we wait until all transactions that could have seen the
    + * partition as attached are gone, then we remove the rest of partition
    + * metadata (pg_inherits and pg_class.relpartbounds).
    + */
    +ObjectAddress
    +ATExecDetachPartition(List **wqueue, AlteredTableInfo *tab, Relation rel,
    +					  RangeVar *name, bool concurrent)
    +{
    +	Relation	partRel;
    +	ObjectAddress address;
    +	Oid			defaultPartOid;
    +
    +	/*
    +	 * We must lock the default partition, because detaching this partition
    +	 * will change its partition constraint.
    +	 */
    +	defaultPartOid =
    +		get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true));
    +	if (OidIsValid(defaultPartOid))
    +	{
    +		/*
    +		 * Concurrent detaching when a default partition exists is not
    +		 * supported. The main problem is that the default partition
    +		 * constraint would change.  And there's a definitional problem: what
    +		 * should happen to the tuples that are being inserted that belong to
    +		 * the partition being detached?  Putting them on the partition being
    +		 * detached would be wrong, since they'd become "lost" after the
    +		 * detaching completes but we cannot put them in the default partition
    +		 * either until we alter its partition constraint.
    +		 *
    +		 * I think we could solve this problem if we effected the constraint
    +		 * change before committing the first transaction.  But the lock would
    +		 * have to remain AEL and it would cause concurrent query planning to
    +		 * be blocked, so changing it that way would be even worse.
    +		 */
    +		if (concurrent)
    +			ereport(ERROR,
    +					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    +					 errmsg("cannot detach partitions concurrently when a default partition exists")));
    +		LockRelationOid(defaultPartOid, AccessExclusiveLock);
    +	}
    +
    +	/*
    +	 * In concurrent mode, the partition is locked with share-update-exclusive
    +	 * in the first transaction.  This allows concurrent transactions to be
    +	 * doing DML to the partition.
    +	 */
    +	partRel = table_openrv(name, concurrent ? ShareUpdateExclusiveLock :
    +						   AccessExclusiveLock);
    +
    +	/*
    +	 * Check inheritance conditions and either delete the pg_inherits row (in
    +	 * non-concurrent mode) or just set the inhdetachpending flag.
    +	 */
    +	if (!concurrent)
    +		RemoveInheritance(partRel, rel, false);
    +	else
    +		MarkInheritDetached(partRel, rel);
    +
    +	/*
    +	 * Ensure that foreign keys still hold after this detach.  This keeps
    +	 * locks on the referencing tables, which prevents concurrent transactions
    +	 * from adding rows that we wouldn't see.  For this to work in concurrent
    +	 * mode, it is critical that the partition appears as no longer attached
    +	 * for the RI queries as soon as the first transaction commits.
    +	 */
    +	ATDetachCheckNoForeignKeyRefs(partRel);
    +
    +	/*
    +	 * Concurrent mode has to work harder; first we add a new constraint to
    +	 * the partition that matches the partition constraint.  Then we close our
    +	 * existing transaction, and in a new one wait for all processes to catch
    +	 * up on the catalog updates we've done so far; at that point we can
    +	 * complete the operation.
    +	 */
    +	if (concurrent)
    +	{
    +		Oid			partrelid,
    +					parentrelid;
    +		LOCKTAG		tag;
    +		char	   *parentrelname;
    +		char	   *partrelname;
    +
    +		/*
    +		 * We're almost done now; the only traces that remain are the
    +		 * pg_inherits tuple and the partition's relpartbounds.  Before we can
    +		 * remove those, we need to wait until all transactions that know that
    +		 * this is a partition are gone.
    +		 */
    +
    +		/*
    +		 * Remember relation OIDs to re-acquire them later; and relation names
    +		 * too, for error messages if something is dropped in between.
    +		 */
    +		partrelid = RelationGetRelid(partRel);
    +		parentrelid = RelationGetRelid(rel);
    +		parentrelname = MemoryContextStrdup(PortalContext,
    +											RelationGetRelationName(rel));
    +		partrelname = MemoryContextStrdup(PortalContext,
    +										  RelationGetRelationName(partRel));
    +
    +		/* Invalidate relcache entries for the parent -- must be before close */
    +		CacheInvalidateRelcache(rel);
    +
    +		table_close(partRel, NoLock);
    +		table_close(rel, NoLock);
    +		tab->rel = NULL;
    +
    +		/* Make updated catalog entry visible */
    +		PopActiveSnapshot();
    +		CommitTransactionCommand();
    +
    +		StartTransactionCommand();
    +
    +		/*
    +		 * Now wait.  This ensures that all queries that were planned
    +		 * including the partition are finished before we remove the rest of
    +		 * catalog entries.  We don't need or indeed want to acquire this
    +		 * lock, though -- that would block later queries.
    +		 *
    +		 * We don't need to concern ourselves with waiting for a lock on the
    +		 * partition itself, since we will acquire AccessExclusiveLock below.
    +		 */
    +		SET_LOCKTAG_RELATION(tag, MyDatabaseId, parentrelid);
    +		WaitForLockersMultiple(list_make1(&tag), AccessExclusiveLock, false);
    +
    +		/*
    +		 * Now acquire locks in both relations again.  Note they may have been
    +		 * removed in the meantime, so care is required.
    +		 */
    +		rel = try_relation_open(parentrelid, ShareUpdateExclusiveLock);
    +		partRel = try_relation_open(partrelid, AccessExclusiveLock);
    +
    +		/* If the relations aren't there, something bad happened; bail out */
    +		if (rel == NULL)
    +		{
    +			if (partRel != NULL)	/* shouldn't happen */
    +				elog(WARNING, "dangling partition \"%s\" remains, can't fix",
    +					 partrelname);
    +			ereport(ERROR,
    +					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    +					 errmsg("partitioned table \"%s\" was removed concurrently",
    +							parentrelname)));
    +		}
    +		if (partRel == NULL)
    +			ereport(ERROR,
    +					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    +					 errmsg("partition \"%s\" was removed concurrently", partrelname)));
    +
    +		tab->rel = rel;
    +	}
    +
    +	/*
    +	 * Detaching the partition might involve TOAST table access, so ensure we
    +	 * have a valid snapshot.
    +	 */
    +	PushActiveSnapshot(GetTransactionSnapshot());
    +
    +	/* Do the final part of detaching */
    +	DetachPartitionFinalize(rel, partRel, concurrent, defaultPartOid);
    +
    +	PopActiveSnapshot();
    +
    +	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
    +
    +	/* keep our lock until commit */
    +	table_close(partRel, NoLock);
    +
    +	return address;
    +}
    +
    +/*
    + * ALTER TABLE ... DETACH PARTITION ... FINALIZE
    + *
    + * To use when a DETACH PARTITION command previously did not run to
    + * completion; this completes the detaching process.
    + */
    +ObjectAddress
    +ATExecDetachPartitionFinalize(Relation rel, RangeVar *name)
    +{
    +	Relation	partRel;
    +	ObjectAddress address;
    +	Snapshot	snap = GetActiveSnapshot();
    +
    +	partRel = table_openrv(name, AccessExclusiveLock);
    +
    +	/*
    +	 * Wait until existing snapshots are gone.  This is important if the
    +	 * second transaction of DETACH PARTITION CONCURRENTLY is canceled: the
    +	 * user could immediately run DETACH FINALIZE without actually waiting for
    +	 * existing transactions.  We must not complete the detach action until
    +	 * all such queries are complete (otherwise we would present them with an
    +	 * inconsistent view of catalogs).
    +	 */
    +	WaitForOlderSnapshots(snap->xmin, false);
    +
    +	DetachPartitionFinalize(rel, partRel, true, InvalidOid);
    +
    +	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
    +
    +	table_close(partRel, NoLock);
    +
    +	return address;
    +}
    +
    +/*
    + * Before acquiring lock on an index, acquire the same lock on the owning
    + * table.
    + */
    +struct AttachIndexCallbackState
    +{
    +	Oid			partitionOid;
    +	Oid			parentTblOid;
    +	bool		lockedParentTbl;
    +};
    +
    +static void
    +RangeVarCallbackForAttachIndex(const RangeVar *rv, Oid relOid, Oid oldRelOid,
    +							   void *arg)
    +{
    +	struct AttachIndexCallbackState *state;
    +	Form_pg_class classform;
    +	HeapTuple	tuple;
    +
    +	state = (struct AttachIndexCallbackState *) arg;
    +
    +	if (!state->lockedParentTbl)
    +	{
    +		LockRelationOid(state->parentTblOid, AccessShareLock);
    +		state->lockedParentTbl = true;
    +	}
    +
    +	/*
    +	 * If we previously locked some other heap, and the name we're looking up
    +	 * no longer refers to an index on that relation, release the now-useless
    +	 * lock.  XXX maybe we should do *after* we verify whether the index does
    +	 * not actually belong to the same relation ...
    +	 */
    +	if (relOid != oldRelOid && OidIsValid(state->partitionOid))
    +	{
    +		UnlockRelationOid(state->partitionOid, AccessShareLock);
    +		state->partitionOid = InvalidOid;
    +	}
    +
    +	/* Didn't find a relation, so no need for locking or permission checks. */
    +	if (!OidIsValid(relOid))
    +		return;
    +
    +	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
    +	if (!HeapTupleIsValid(tuple))
    +		return;					/* concurrently dropped, so nothing to do */
    +	classform = (Form_pg_class) GETSTRUCT(tuple);
    +	if (classform->relkind != RELKIND_PARTITIONED_INDEX &&
    +		classform->relkind != RELKIND_INDEX)
    +		ereport(ERROR,
    +				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
    +				 errmsg("\"%s\" is not an index", rv->relname)));
    +	ReleaseSysCache(tuple);
    +
    +	/*
    +	 * Since we need only examine the heap's tupledesc, an access share lock
    +	 * on it (preventing any DDL) is sufficient.
    +	 */
    +	state->partitionOid = IndexGetRelation(relOid, false);
    +	LockRelationOid(state->partitionOid, AccessShareLock);
    +}
    +
    +/*
    + * Verify whether the given partition already contains an index attached
    + * to the given partitioned index.  If so, raise an error.
    + */
    +static void
    +refuseDupeIndexAttach(Relation parentIdx, Relation partIdx, Relation partitionTbl)
    +{
    +	Oid			existingIdx;
    +
    +	existingIdx = index_get_partition(partitionTbl,
    +									  RelationGetRelid(parentIdx));
    +	if (OidIsValid(existingIdx))
    +		ereport(ERROR,
    +				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    +				 errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
    +						RelationGetRelationName(partIdx),
    +						RelationGetRelationName(parentIdx)),
    +				 errdetail("Another index \"%s\" is already attached for partition \"%s\".",
    +						   get_rel_name(existingIdx),
    +						   RelationGetRelationName(partitionTbl))));
    +}
    +
    +/*
    + * When attaching an index as a partition of a partitioned index which is a
    + * primary key, verify that all the columns in the partition are marked NOT
    + * NULL.
    + */
    +static void
    +verifyPartitionIndexNotNull(IndexInfo *iinfo, Relation partition)
    +{
    +	for (int i = 0; i < iinfo->ii_NumIndexKeyAttrs; i++)
    +	{
    +		Form_pg_attribute att = TupleDescAttr(RelationGetDescr(partition),
    +											  iinfo->ii_IndexAttrNumbers[i] - 1);
    +
    +		if (!att->attnotnull)
    +			ereport(ERROR,
    +					errcode(ERRCODE_INVALID_TABLE_DEFINITION),
    +					errmsg("invalid primary key definition"),
    +					errdetail("Column \"%s\" of relation \"%s\" is not marked NOT NULL.",
    +							  NameStr(att->attname),
    +							  RelationGetRelationName(partition)));
    +	}
    +}
    +
    +/*
    + * Verify whether the set of attached partition indexes to a parent index on
    + * a partitioned table is complete.  If it is, mark the parent index valid.
    + *
    + * This should be called each time a partition index is attached.
    + */
    +static void
    +validatePartitionedIndex(Relation partedIdx, Relation partedTbl)
    +{
    +	Relation	inheritsRel;
    +	SysScanDesc scan;
    +	ScanKeyData key;
    +	int			tuples = 0;
    +	HeapTuple	inhTup;
    +	bool		updated = false;
    +
    +	Assert(partedIdx->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
    +
    +	/*
    +	 * Scan pg_inherits for this parent index.  Count each valid index we find
    +	 * (verifying the pg_index entry for each), and if we reach the total
    +	 * amount we expect, we can mark this parent index as valid.
    +	 */
    +	inheritsRel = table_open(InheritsRelationId, AccessShareLock);
    +	ScanKeyInit(&key, Anum_pg_inherits_inhparent,
    +				BTEqualStrategyNumber, F_OIDEQ,
    +				ObjectIdGetDatum(RelationGetRelid(partedIdx)));
    +	scan = systable_beginscan(inheritsRel, InheritsParentIndexId, true,
    +							  NULL, 1, &key);
    +	while ((inhTup = systable_getnext(scan)) != NULL)
    +	{
    +		Form_pg_inherits inhForm = (Form_pg_inherits) GETSTRUCT(inhTup);
    +		HeapTuple	indTup;
    +		Form_pg_index indexForm;
    +
    +		indTup = SearchSysCache1(INDEXRELID,
    +								 ObjectIdGetDatum(inhForm->inhrelid));
    +		if (!HeapTupleIsValid(indTup))
    +			elog(ERROR, "cache lookup failed for index %u", inhForm->inhrelid);
    +		indexForm = (Form_pg_index) GETSTRUCT(indTup);
    +		if (indexForm->indisvalid)
    +			tuples += 1;
    +		ReleaseSysCache(indTup);
    +	}
    +
    +	/* Done with pg_inherits */
    +	systable_endscan(scan);
    +	table_close(inheritsRel, AccessShareLock);
    +
    +	/*
    +	 * If we found as many inherited indexes as the partitioned table has
    +	 * partitions, we're good; update pg_index to set indisvalid.
    +	 */
    +	if (tuples == RelationGetPartitionDesc(partedTbl, true)->nparts)
    +	{
    +		Relation	idxRel;
    +		HeapTuple	indTup;
    +		Form_pg_index indexForm;
    +
    +		idxRel = table_open(IndexRelationId, RowExclusiveLock);
    +		indTup = SearchSysCacheCopy1(INDEXRELID,
    +									 ObjectIdGetDatum(RelationGetRelid(partedIdx)));
    +		if (!HeapTupleIsValid(indTup))
    +			elog(ERROR, "cache lookup failed for index %u",
    +				 RelationGetRelid(partedIdx));
    +		indexForm = (Form_pg_index) GETSTRUCT(indTup);
    +
    +		indexForm->indisvalid = true;
    +		updated = true;
    +
    +		CatalogTupleUpdate(idxRel, &indTup->t_self, indTup);
    +
    +		table_close(idxRel, RowExclusiveLock);
    +		heap_freetuple(indTup);
    +	}
    +
    +	/*
    +	 * If this index is in turn a partition of a larger index, validating it
    +	 * might cause the parent to become valid also.  Try that.
    +	 */
    +	if (updated && partedIdx->rd_rel->relispartition)
    +	{
    +		Oid			parentIdxId,
    +					parentTblId;
    +		Relation	parentIdx,
    +					parentTbl;
    +
    +		/* make sure we see the validation we just did */
    +		CommandCounterIncrement();
    +
    +		parentIdxId = get_partition_parent(RelationGetRelid(partedIdx), false);
    +		parentTblId = get_partition_parent(RelationGetRelid(partedTbl), false);
    +		parentIdx = relation_open(parentIdxId, AccessExclusiveLock);
    +		parentTbl = relation_open(parentTblId, AccessExclusiveLock);
    +		Assert(!parentIdx->rd_index->indisvalid);
    +
    +		validatePartitionedIndex(parentIdx, parentTbl);
    +
    +		relation_close(parentIdx, AccessExclusiveLock);
    +		relation_close(parentTbl, AccessExclusiveLock);
    +	}
    +}
    +
    +/*
    + * ALTER INDEX i1 ATTACH PARTITION i2
    + */
    +ObjectAddress
    +ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
    +{
    +	Relation	partIdx;
    +	Relation	partTbl;
    +	Relation	parentTbl;
    +	ObjectAddress address;
    +	Oid			partIdxId;
    +	Oid			currParent;
    +	struct AttachIndexCallbackState state;
    +
    +	/*
    +	 * We need to obtain lock on the index 'name' to modify it, but we also
    +	 * need to read its owning table's tuple descriptor -- so we need to lock
    +	 * both.  To avoid deadlocks, obtain lock on the table before doing so on
    +	 * the index.  Furthermore, we need to examine the parent table of the
    +	 * partition, so lock that one too.
    +	 */
    +	state.partitionOid = InvalidOid;
    +	state.parentTblOid = parentIdx->rd_index->indrelid;
    +	state.lockedParentTbl = false;
    +	partIdxId =
    +		RangeVarGetRelidExtended(name, AccessExclusiveLock, 0,
    +								 RangeVarCallbackForAttachIndex,
    +								 &state);
    +	/* Not there? */
    +	if (!OidIsValid(partIdxId))
    +		ereport(ERROR,
    +				(errcode(ERRCODE_UNDEFINED_OBJECT),
    +				 errmsg("index \"%s\" does not exist", name->relname)));
    +
    +	/* no deadlock risk: RangeVarGetRelidExtended already acquired the lock */
    +	partIdx = relation_open(partIdxId, AccessExclusiveLock);
    +
    +	/* we already hold locks on both tables, so this is safe: */
    +	parentTbl = relation_open(parentIdx->rd_index->indrelid, AccessShareLock);
    +	partTbl = relation_open(partIdx->rd_index->indrelid, NoLock);
    +
    +	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partIdx));
    +
    +	/* Silently do nothing if already in the right state */
    +	currParent = partIdx->rd_rel->relispartition ?
    +		get_partition_parent(partIdxId, false) : InvalidOid;
    +	if (currParent != RelationGetRelid(parentIdx))
    +	{
    +		IndexInfo  *childInfo;
    +		IndexInfo  *parentInfo;
    +		AttrMap    *attmap;
    +		bool		found;
    +		int			i;
    +		PartitionDesc partDesc;
    +		Oid			constraintOid,
    +					cldConstrId = InvalidOid;
    +
    +		/*
    +		 * If this partition already has an index attached, refuse the
    +		 * operation.
    +		 */
    +		refuseDupeIndexAttach(parentIdx, partIdx, partTbl);
    +
    +		if (OidIsValid(currParent))
    +			ereport(ERROR,
    +					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    +					 errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
    +							RelationGetRelationName(partIdx),
    +							RelationGetRelationName(parentIdx)),
    +					 errdetail("Index \"%s\" is already attached to another index.",
    +							   RelationGetRelationName(partIdx))));
    +
    +		/* Make sure it indexes a partition of the other index's table */
    +		partDesc = RelationGetPartitionDesc(parentTbl, true);
    +		found = false;
    +		for (i = 0; i < partDesc->nparts; i++)
    +		{
    +			if (partDesc->oids[i] == state.partitionOid)
    +			{
    +				found = true;
    +				break;
    +			}
    +		}
    +		if (!found)
    +			ereport(ERROR,
    +					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    +					 errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
    +							RelationGetRelationName(partIdx),
    +							RelationGetRelationName(parentIdx)),
    +					 errdetail("Index \"%s\" is not an index on any partition of table \"%s\".",
    +							   RelationGetRelationName(partIdx),
    +							   RelationGetRelationName(parentTbl))));
    +
    +		/* Ensure the indexes are compatible */
    +		childInfo = BuildIndexInfo(partIdx);
    +		parentInfo = BuildIndexInfo(parentIdx);
    +		attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
    +									   RelationGetDescr(parentTbl),
    +									   false);
    +		if (!CompareIndexInfo(childInfo, parentInfo,
    +							  partIdx->rd_indcollation,
    +							  parentIdx->rd_indcollation,
    +							  partIdx->rd_opfamily,
    +							  parentIdx->rd_opfamily,
    +							  attmap))
    +			ereport(ERROR,
    +					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
    +					 errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
    +							RelationGetRelationName(partIdx),
    +							RelationGetRelationName(parentIdx)),
    +					 errdetail("The index definitions do not match.")));
    +
    +		/*
    +		 * If there is a constraint in the parent, make sure there is one in
    +		 * the child too.
    +		 */
    +		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(parentTbl),
    +														RelationGetRelid(parentIdx));
    +
    +		if (OidIsValid(constraintOid))
    +		{
    +			cldConstrId = get_relation_idx_constraint_oid(RelationGetRelid(partTbl),
    +														  partIdxId);
    +			if (!OidIsValid(cldConstrId))
    +				ereport(ERROR,
    +						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
    +						 errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
    +								RelationGetRelationName(partIdx),
    +								RelationGetRelationName(parentIdx)),
    +						 errdetail("The index \"%s\" belongs to a constraint in table \"%s\" but no constraint exists for index \"%s\".",
    +								   RelationGetRelationName(parentIdx),
    +								   RelationGetRelationName(parentTbl),
    +								   RelationGetRelationName(partIdx))));
    +		}
    +
    +		/*
    +		 * If it's a primary key, make sure the columns in the partition are
    +		 * NOT NULL.
    +		 */
    +		if (parentIdx->rd_index->indisprimary)
    +			verifyPartitionIndexNotNull(childInfo, partTbl);
    +
    +		/* All good -- do it */
    +		IndexSetParentIndex(partIdx, RelationGetRelid(parentIdx));
    +		if (OidIsValid(constraintOid))
    +			ConstraintSetParentConstraint(cldConstrId, constraintOid,
    +										  RelationGetRelid(partTbl));
    +
    +		free_attrmap(attmap);
    +
    +		validatePartitionedIndex(parentIdx, parentTbl);
    +	}
    +
    +	relation_close(parentTbl, AccessShareLock);
    +	/* keep these locks till commit */
    +	relation_close(partTbl, NoLock);
    +	relation_close(partIdx, NoLock);
    +
    +	return address;
    +}
    diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
    index 23ebaa3f230..c93d022aa6d 100644
    --- a/src/backend/commands/tablecmds.c
    +++ b/src/backend/commands/tablecmds.c
    @@ -61,6 +61,7 @@
     #include "commands/defrem.h"
     #include "commands/event_trigger.h"
     #include "commands/sequence.h"
    +#include "commands/partcmds.h"
     #include "commands/tablecmds.h"
     #include "commands/tablespace.h"
     #include "commands/trigger.h"
    @@ -132,84 +133,6 @@ typedef struct OnCommitItem
     static List *on_commits = NIL;
     
     
    -/*
    - * State information for ALTER TABLE
    - *
    - * The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo
    - * structs, one for each table modified by the operation (the named table
    - * plus any child tables that are affected).  We save lists of subcommands
    - * to apply to this table (possibly modified by parse transformation steps);
    - * these lists will be executed in Phase 2.  If a Phase 3 step is needed,
    - * necessary information is stored in the constraints and newvals lists.
    - *
    - * Phase 2 is divided into multiple passes; subcommands are executed in
    - * a pass determined by subcommand type.
    - */
    -
    -typedef enum AlterTablePass
    -{
    -	AT_PASS_UNSET = -1,			/* UNSET will cause ERROR */
    -	AT_PASS_DROP,				/* DROP (all flavors) */
    -	AT_PASS_ALTER_TYPE,			/* ALTER COLUMN TYPE */
    -	AT_PASS_ADD_COL,			/* ADD COLUMN */
    -	AT_PASS_SET_EXPRESSION,		/* ALTER SET EXPRESSION */
    -	AT_PASS_OLD_INDEX,			/* re-add existing indexes */
    -	AT_PASS_OLD_CONSTR,			/* re-add existing constraints */
    -	/* We could support a RENAME COLUMN pass here, but not currently used */
    -	AT_PASS_ADD_CONSTR,			/* ADD constraints (initial examination) */
    -	AT_PASS_COL_ATTRS,			/* set column attributes, eg NOT NULL */
    -	AT_PASS_ADD_INDEXCONSTR,	/* ADD index-based constraints */
    -	AT_PASS_ADD_INDEX,			/* ADD indexes */
    -	AT_PASS_ADD_OTHERCONSTR,	/* ADD other constraints, defaults */
    -	AT_PASS_MISC,				/* other stuff */
    -} AlterTablePass;
    -
    -#define AT_NUM_PASSES			(AT_PASS_MISC + 1)
    -
    -typedef struct AlteredTableInfo
    -{
    -	/* Information saved before any work commences: */
    -	Oid			relid;			/* Relation to work on */
    -	char		relkind;		/* Its relkind */
    -	TupleDesc	oldDesc;		/* Pre-modification tuple descriptor */
    -
    -	/*
    -	 * Transiently set during Phase 2, normally set to NULL.
    -	 *
    -	 * ATRewriteCatalogs sets this when it starts, and closes when ATExecCmd
    -	 * returns control.  This can be exploited by ATExecCmd subroutines to
    -	 * close/reopen across transaction boundaries.
    -	 */
    -	Relation	rel;
    -
    -	/* Information saved by Phase 1 for Phase 2: */
    -	List	   *subcmds[AT_NUM_PASSES]; /* Lists of AlterTableCmd */
    -	/* Information saved by Phases 1/2 for Phase 3: */
    -	List	   *constraints;	/* List of NewConstraint */
    -	List	   *newvals;		/* List of NewColumnValue */
    -	List	   *afterStmts;		/* List of utility command parsetrees */
    -	bool		verify_new_notnull; /* T if we should recheck NOT NULL */
    -	int			rewrite;		/* Reason for forced rewrite, if any */
    -	bool		chgAccessMethod;	/* T if SET ACCESS METHOD is used */
    -	Oid			newAccessMethod;	/* new access method; 0 means no change,
    -									 * if above is true */
    -	Oid			newTableSpace;	/* new tablespace; 0 means no change */
    -	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
    -	char		newrelpersistence;	/* if above is true */
    -	Expr	   *partition_constraint;	/* for attach partition validation */
    -	/* true, if validating default due to some other attach/detach */
    -	bool		validate_default;
    -	/* Objects to rebuild after completing ALTER TYPE operations */
    -	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
    -	List	   *changedConstraintDefs;	/* string definitions of same */
    -	List	   *changedIndexOids;	/* OIDs of indexes to rebuild */
    -	List	   *changedIndexDefs;	/* string definitions of same */
    -	char	   *replicaIdentityIndex;	/* index to reset as REPLICA IDENTITY */
    -	char	   *clusterOnIndex; /* index to use for CLUSTER */
    -	List	   *changedStatisticsOids;	/* OIDs of statistics to rebuild */
    -	List	   *changedStatisticsDefs;	/* string definitions of same */
    -} AlteredTableInfo;
    -
     /* Struct describing one new constraint to check in Phase 3 scan */
     /* Note: new not-null constraints are handled elsewhere */
     typedef struct NewConstraint
    @@ -325,17 +248,6 @@ struct DropRelationCallbackState
     	char		actual_relpersistence;
     };
     
    -/* Alter table target-type flags for ATSimplePermissions */
    -#define		ATT_TABLE				0x0001
    -#define		ATT_VIEW				0x0002
    -#define		ATT_MATVIEW				0x0004
    -#define		ATT_INDEX				0x0008
    -#define		ATT_COMPOSITE_TYPE		0x0010
    -#define		ATT_FOREIGN_TABLE		0x0020
    -#define		ATT_PARTITIONED_INDEX	0x0040
    -#define		ATT_SEQUENCE			0x0080
    -#define		ATT_PARTITIONED_TABLE	0x0100
    -
     /*
      * ForeignTruncateInfo
      *
    @@ -350,14 +262,6 @@ typedef struct ForeignTruncateInfo
     	List	   *rels;
     } ForeignTruncateInfo;
     
    -/* Partial or complete FK creation in addFkConstraint() */
    -typedef enum addFkConstraintSides
    -{
    -	addFkReferencedSide,
    -	addFkReferencingSide,
    -	addFkBothSides,
    -} addFkConstraintSides;
    -
     /*
      * Partition tables are expected to be dropped when the parent partitioned
      * table gets dropped. Hence for partitioning we use AUTO dependency.
    @@ -431,8 +335,6 @@ static void AlterConstrUpdateConstraintEntry(ATAlterConstraint *cmdcon, Relation
     static ObjectAddress ATExecValidateConstraint(List **wqueue,
     											  Relation rel, char *constrName,
     											  bool recurse, bool recursing, LOCKMODE lockmode);
    -static void QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation fkrel,
    -										Oid pkrelid, HeapTuple contuple, LOCKMODE lockmode);
     static void QueueCheckConstraintValidation(List **wqueue, Relation conrel, Relation rel,
     										   char *constrName, HeapTuple contuple,
     										   bool recurse, bool recursing, LOCKMODE lockmode);
    @@ -476,8 +378,6 @@ static void ATRewriteTables(AlterTableStmt *parsetree,
     							List **wqueue, LOCKMODE lockmode,
     							AlterTableUtilityContext *context);
     static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap);
    -static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel);
    -static void ATSimplePermissions(AlterTableType cmdtype, Relation rel, int allowed_targets);
     static void ATSimpleRecursion(List **wqueue, Relation rel,
     							  AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
     							  AlterTableUtilityContext *context);
    @@ -508,8 +408,6 @@ static ObjectAddress ATExecSetNotNull(List **wqueue, Relation rel,
     									  bool recurse, bool recursing,
     									  LOCKMODE lockmode);
     static bool NotNullImpliedByRelConstraints(Relation rel, Form_pg_attribute attr);
    -static bool ConstraintImpliedByRelConstraint(Relation scanrel,
    -											 List *testConstraint, List *provenConstraint);
     static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
     										 Node *newDefault, LOCKMODE lockmode);
     static ObjectAddress ATExecCookedColumnDefault(Relation rel, AttrNumber attnum,
    @@ -518,8 +416,6 @@ static ObjectAddress ATExecAddIdentity(Relation rel, const char *colName,
     									   Node *def, LOCKMODE lockmode, bool recurse, bool recursing);
     static ObjectAddress ATExecSetIdentity(Relation rel, const char *colName,
     									   Node *def, LOCKMODE lockmode, bool recurse, bool recursing);
    -static ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode,
    -										bool recurse, bool recursing);
     static ObjectAddress ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
     										 Node *newExpr, LOCKMODE lockmode);
     static void ATPrepDropExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing, LOCKMODE lockmode);
    @@ -565,37 +461,6 @@ static ObjectAddress ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *
     static int	validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
     										 int numfksetcols, int16 *fksetcolsattnums,
     										 List *fksetcols);
    -static ObjectAddress addFkConstraint(addFkConstraintSides fkside,
    -									 char *constraintname,
    -									 Constraint *fkconstraint, Relation rel,
    -									 Relation pkrel, Oid indexOid,
    -									 Oid parentConstr,
    -									 int numfks, int16 *pkattnum, int16 *fkattnum,
    -									 Oid *pfeqoperators, Oid *ppeqoperators,
    -									 Oid *ffeqoperators, int numfkdelsetcols,
    -									 int16 *fkdelsetcols, bool is_internal,
    -									 bool with_period);
    -static void addFkRecurseReferenced(Constraint *fkconstraint,
    -								   Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
    -								   int numfks, int16 *pkattnum, int16 *fkattnum,
    -								   Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
    -								   int numfkdelsetcols, int16 *fkdelsetcols,
    -								   bool old_check_ok,
    -								   Oid parentDelTrigger, Oid parentUpdTrigger,
    -								   bool with_period);
    -static void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint,
    -									Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
    -									int numfks, int16 *pkattnum, int16 *fkattnum,
    -									Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
    -									int numfkdelsetcols, int16 *fkdelsetcols,
    -									bool old_check_ok, LOCKMODE lockmode,
    -									Oid parentInsTrigger, Oid parentUpdTrigger,
    -									bool with_period);
    -static void CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
    -									   Relation partitionRel);
    -static void CloneFkReferenced(Relation parentRel, Relation partitionRel);
    -static void CloneFkReferencing(List **wqueue, Relation parentRel,
    -							   Relation partRel);
     static void createForeignKeyCheckTriggers(Oid myRelOid, Oid refRelOid,
     										  Constraint *fkconstraint, Oid constraintOid,
     										  Oid indexOid,
    @@ -606,31 +471,8 @@ static void createForeignKeyActionTriggers(Oid myRelOid, Oid refRelOid,
     										   Oid indexOid,
     										   Oid parentDelTrigger, Oid parentUpdTrigger,
     										   Oid *deleteTrigOid, Oid *updateTrigOid);
    -static bool tryAttachPartitionForeignKey(List **wqueue,
    -										 ForeignKeyCacheInfo *fk,
    -										 Relation partition,
    -										 Oid parentConstrOid, int numfks,
    -										 AttrNumber *mapped_conkey, AttrNumber *confkey,
    -										 Oid *conpfeqop,
    -										 Oid parentInsTrigger,
    -										 Oid parentUpdTrigger,
    -										 Relation trigrel);
    -static void AttachPartitionForeignKey(List **wqueue, Relation partition,
    -									  Oid partConstrOid, Oid parentConstrOid,
    -									  Oid parentInsTrigger, Oid parentUpdTrigger,
    -									  Relation trigrel);
    -static void RemoveInheritedConstraint(Relation conrel, Relation trigrel,
    -									  Oid conoid, Oid conrelid);
     static void DropForeignKeyConstraintTriggers(Relation trigrel, Oid conoid,
     											 Oid confrelid, Oid conrelid);
    -static void GetForeignKeyActionTriggers(Relation trigrel,
    -										Oid conoid, Oid confrelid, Oid conrelid,
    -										Oid *deleteTriggerOid,
    -										Oid *updateTriggerOid);
    -static void GetForeignKeyCheckTriggers(Relation trigrel,
    -									   Oid conoid, Oid confrelid, Oid conrelid,
    -									   Oid *insertTriggerOid,
    -									   Oid *updateTriggerOid);
     static void ATExecDropConstraint(Relation rel, const char *constrName,
     								 DropBehavior behavior, bool recurse,
     								 bool missing_ok, LOCKMODE lockmode);
    @@ -707,36 +549,6 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
     											Oid oldRelOid, void *arg);
     static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
     											 Oid oldrelid, void *arg);
    -static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec);
    -static void ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNumber *partattrs,
    -								  List **partexprs, Oid *partopclass, Oid *partcollation,
    -								  PartitionStrategy strategy);
    -static void CreateInheritance(Relation child_rel, Relation parent_rel, bool ispartition);
    -static void RemoveInheritance(Relation child_rel, Relation parent_rel,
    -							  bool expect_detached);
    -static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
    -										   PartitionCmd *cmd,
    -										   AlterTableUtilityContext *context);
    -static void AttachPartitionEnsureIndexes(List **wqueue, Relation rel, Relation attachrel);
    -static void QueuePartitionConstraintValidation(List **wqueue, Relation scanrel,
    -											   List *partConstraint,
    -											   bool validate_default);
    -static void CloneRowTriggersToPartition(Relation parent, Relation partition);
    -static void DropClonedTriggersFromPartition(Oid partitionId);
    -static ObjectAddress ATExecDetachPartition(List **wqueue, AlteredTableInfo *tab,
    -										   Relation rel, RangeVar *name,
    -										   bool concurrent);
    -static void DetachPartitionFinalize(Relation rel, Relation partRel,
    -									bool concurrent, Oid defaultPartOid);
    -static ObjectAddress ATExecDetachPartitionFinalize(Relation rel, RangeVar *name);
    -static ObjectAddress ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx,
    -											  RangeVar *name);
    -static void validatePartitionedIndex(Relation partedIdx, Relation partedTbl);
    -static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
    -								  Relation partitionTbl);
    -static void verifyPartitionIndexNotNull(IndexInfo *iinfo, Relation partition);
    -static List *GetParentedForeignKeyRefs(Relation partition);
    -static void ATDetachCheckNoForeignKeyRefs(Relation partition);
     static char GetAttributeCompression(Oid atttypid, const char *compression);
     static char GetAttributeStorage(Oid atttypid, const char *storagemode);
     
    @@ -6550,7 +6362,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
     /*
      * ATGetQueueEntry: find or create an entry in the ALTER TABLE work queue
      */
    -static AlteredTableInfo *
    +AlteredTableInfo *
     ATGetQueueEntry(List **wqueue, Relation rel)
     {
     	Oid			relid = RelationGetRelid(rel);
    @@ -6727,7 +6539,7 @@ alter_table_type_to_string(AlterTableType cmdtype)
      * - Ensure this user is the owner
      * - Ensure that it is not a system table
      */
    -static void
    +void
     ATSimplePermissions(AlterTableType cmdtype, Relation rel, int allowed_targets)
     {
     	int			actual_target;
    @@ -8476,7 +8288,7 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def,
      *
      * Return the address of the affected column.
      */
    -static ObjectAddress
    +ObjectAddress
     ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode,
     				   bool recurse, bool recursing)
     {
    @@ -10710,7 +10522,7 @@ validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
      *      NULL/DEFAULT clause
      * with_period: true if this is a temporal FK
      */
    -static ObjectAddress
    +ObjectAddress
     addFkConstraint(addFkConstraintSides fkside,
     				char *constraintname, Constraint *fkconstraint,
     				Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
    @@ -10888,7 +10700,7 @@ addFkConstraint(addFkConstraintSides fkside,
      *      UPDATE respectively.
      * with_period: true if this is a temporal FK
      */
    -static void
    +void
     addFkRecurseReferenced(Constraint *fkconstraint, Relation rel,
     					   Relation pkrel, Oid indexOid, Oid parentConstr,
     					   int numfks,
    @@ -11026,7 +10838,7 @@ addFkRecurseReferenced(Constraint *fkconstraint, Relation rel,
      *      UPDATE respectively.
      * with_period: true if this is a temporal FK
      */
    -static void
    +void
     addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
     						Relation pkrel, Oid indexOid, Oid parentConstr,
     						int numfks, int16 *pkattnum, int16 *fkattnum,
    @@ -11192,795 +11004,6 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
     	}
     }
     
    -/*
    - * CloneForeignKeyConstraints
    - *		Clone foreign keys from a partitioned table to a newly acquired
    - *		partition.
    - *
    - * partitionRel is a partition of parentRel, so we can be certain that it has
    - * the same columns with the same datatypes.  The columns may be in different
    - * order, though.
    - *
    - * wqueue must be passed to set up phase 3 constraint checking, unless the
    - * referencing-side partition is known to be empty (such as in CREATE TABLE /
    - * PARTITION OF).
    - */
    -static void
    -CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
    -						   Relation partitionRel)
    -{
    -	/* This only works for declarative partitioning */
    -	Assert(parentRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
    -
    -	/*
    -	 * First, clone constraints where the parent is on the referencing side.
    -	 */
    -	CloneFkReferencing(wqueue, parentRel, partitionRel);
    -
    -	/*
    -	 * Clone constraints for which the parent is on the referenced side.
    -	 */
    -	CloneFkReferenced(parentRel, partitionRel);
    -}
    -
    -/*
    - * CloneFkReferenced
    - *		Subroutine for CloneForeignKeyConstraints
    - *
    - * Find all the FKs that have the parent relation on the referenced side;
    - * clone those constraints to the given partition.  This is to be called
    - * when the partition is being created or attached.
    - *
    - * This recurses to partitions, if the relation being attached is partitioned.
    - * Recursion is done by calling addFkRecurseReferenced.
    - */
    -static void
    -CloneFkReferenced(Relation parentRel, Relation partitionRel)
    -{
    -	Relation	pg_constraint;
    -	AttrMap    *attmap;
    -	ListCell   *cell;
    -	SysScanDesc scan;
    -	ScanKeyData key[2];
    -	HeapTuple	tuple;
    -	List	   *clone = NIL;
    -	Relation	trigrel;
    -
    -	/*
    -	 * Search for any constraints where this partition's parent is in the
    -	 * referenced side.  However, we must not clone any constraint whose
    -	 * parent constraint is also going to be cloned, to avoid duplicates.  So
    -	 * do it in two steps: first construct the list of constraints to clone,
    -	 * then go over that list cloning those whose parents are not in the list.
    -	 * (We must not rely on the parent being seen first, since the catalog
    -	 * scan could return children first.)
    -	 */
    -	pg_constraint = table_open(ConstraintRelationId, RowShareLock);
    -	ScanKeyInit(&key[0],
    -				Anum_pg_constraint_confrelid, BTEqualStrategyNumber,
    -				F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(parentRel)));
    -	ScanKeyInit(&key[1],
    -				Anum_pg_constraint_contype, BTEqualStrategyNumber,
    -				F_CHAREQ, CharGetDatum(CONSTRAINT_FOREIGN));
    -	/* This is a seqscan, as we don't have a usable index ... */
    -	scan = systable_beginscan(pg_constraint, InvalidOid, true,
    -							  NULL, 2, key);
    -	while ((tuple = systable_getnext(scan)) != NULL)
    -	{
    -		Form_pg_constraint constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
    -
    -		clone = lappend_oid(clone, constrForm->oid);
    -	}
    -	systable_endscan(scan);
    -	table_close(pg_constraint, RowShareLock);
    -
    -	/*
    -	 * Triggers of the foreign keys will be manipulated a bunch of times in
    -	 * the loop below.  To avoid repeatedly opening/closing the trigger
    -	 * catalog relation, we open it here and pass it to the subroutines called
    -	 * below.
    -	 */
    -	trigrel = table_open(TriggerRelationId, RowExclusiveLock);
    -
    -	attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
    -								   RelationGetDescr(parentRel),
    -								   false);
    -	foreach(cell, clone)
    -	{
    -		Oid			constrOid = lfirst_oid(cell);
    -		Form_pg_constraint constrForm;
    -		Relation	fkRel;
    -		Oid			indexOid;
    -		Oid			partIndexId;
    -		int			numfks;
    -		AttrNumber	conkey[INDEX_MAX_KEYS];
    -		AttrNumber	mapped_confkey[INDEX_MAX_KEYS];
    -		AttrNumber	confkey[INDEX_MAX_KEYS];
    -		Oid			conpfeqop[INDEX_MAX_KEYS];
    -		Oid			conppeqop[INDEX_MAX_KEYS];
    -		Oid			conffeqop[INDEX_MAX_KEYS];
    -		int			numfkdelsetcols;
    -		AttrNumber	confdelsetcols[INDEX_MAX_KEYS];
    -		Constraint *fkconstraint;
    -		ObjectAddress address;
    -		Oid			deleteTriggerOid = InvalidOid,
    -					updateTriggerOid = InvalidOid;
    -
    -		tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid));
    -		if (!HeapTupleIsValid(tuple))
    -			elog(ERROR, "cache lookup failed for constraint %u", constrOid);
    -		constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
    -
    -		/*
    -		 * As explained above: don't try to clone a constraint for which we're
    -		 * going to clone the parent.
    -		 */
    -		if (list_member_oid(clone, constrForm->conparentid))
    -		{
    -			ReleaseSysCache(tuple);
    -			continue;
    -		}
    -
    -		/* We need the same lock level that CreateTrigger will acquire */
    -		fkRel = table_open(constrForm->conrelid, ShareRowExclusiveLock);
    -
    -		indexOid = constrForm->conindid;
    -		DeconstructFkConstraintRow(tuple,
    -								   &numfks,
    -								   conkey,
    -								   confkey,
    -								   conpfeqop,
    -								   conppeqop,
    -								   conffeqop,
    -								   &numfkdelsetcols,
    -								   confdelsetcols);
    -
    -		for (int i = 0; i < numfks; i++)
    -			mapped_confkey[i] = attmap->attnums[confkey[i] - 1];
    -
    -		fkconstraint = makeNode(Constraint);
    -		fkconstraint->contype = CONSTRAINT_FOREIGN;
    -		fkconstraint->conname = NameStr(constrForm->conname);
    -		fkconstraint->deferrable = constrForm->condeferrable;
    -		fkconstraint->initdeferred = constrForm->condeferred;
    -		fkconstraint->location = -1;
    -		fkconstraint->pktable = NULL;
    -		/* ->fk_attrs determined below */
    -		fkconstraint->pk_attrs = NIL;
    -		fkconstraint->fk_matchtype = constrForm->confmatchtype;
    -		fkconstraint->fk_upd_action = constrForm->confupdtype;
    -		fkconstraint->fk_del_action = constrForm->confdeltype;
    -		fkconstraint->fk_del_set_cols = NIL;
    -		fkconstraint->old_conpfeqop = NIL;
    -		fkconstraint->old_pktable_oid = InvalidOid;
    -		fkconstraint->is_enforced = constrForm->conenforced;
    -		fkconstraint->skip_validation = false;
    -		fkconstraint->initially_valid = constrForm->convalidated;
    -
    -		/* set up colnames that are used to generate the constraint name */
    -		for (int i = 0; i < numfks; i++)
    -		{
    -			Form_pg_attribute att;
    -
    -			att = TupleDescAttr(RelationGetDescr(fkRel),
    -								conkey[i] - 1);
    -			fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs,
    -											 makeString(NameStr(att->attname)));
    -		}
    -
    -		/*
    -		 * Add the new foreign key constraint pointing to the new partition.
    -		 * Because this new partition appears in the referenced side of the
    -		 * constraint, we don't need to set up for Phase 3 check.
    -		 */
    -		partIndexId = index_get_partition(partitionRel, indexOid);
    -		if (!OidIsValid(partIndexId))
    -			elog(ERROR, "index for %u not found in partition %s",
    -				 indexOid, RelationGetRelationName(partitionRel));
    -
    -		/*
    -		 * Get the "action" triggers belonging to the constraint to pass as
    -		 * parent OIDs for similar triggers that will be created on the
    -		 * partition in addFkRecurseReferenced().
    -		 */
    -		if (constrForm->conenforced)
    -			GetForeignKeyActionTriggers(trigrel, constrOid,
    -										constrForm->confrelid, constrForm->conrelid,
    -										&deleteTriggerOid, &updateTriggerOid);
    -
    -		/* Add this constraint ... */
    -		address = addFkConstraint(addFkReferencedSide,
    -								  fkconstraint->conname, fkconstraint, fkRel,
    -								  partitionRel, partIndexId, constrOid,
    -								  numfks, mapped_confkey,
    -								  conkey, conpfeqop, conppeqop, conffeqop,
    -								  numfkdelsetcols, confdelsetcols, false,
    -								  constrForm->conperiod);
    -		/* ... and recurse */
    -		addFkRecurseReferenced(fkconstraint,
    -							   fkRel,
    -							   partitionRel,
    -							   partIndexId,
    -							   address.objectId,
    -							   numfks,
    -							   mapped_confkey,
    -							   conkey,
    -							   conpfeqop,
    -							   conppeqop,
    -							   conffeqop,
    -							   numfkdelsetcols,
    -							   confdelsetcols,
    -							   true,
    -							   deleteTriggerOid,
    -							   updateTriggerOid,
    -							   constrForm->conperiod);
    -
    -		table_close(fkRel, NoLock);
    -		ReleaseSysCache(tuple);
    -	}
    -
    -	table_close(trigrel, RowExclusiveLock);
    -}
    -
    -/*
    - * CloneFkReferencing
    - *		Subroutine for CloneForeignKeyConstraints
    - *
    - * For each FK constraint of the parent relation in the given list, find an
    - * equivalent constraint in its partition relation that can be reparented;
    - * if one cannot be found, create a new constraint in the partition as its
    - * child.
    - *
    - * If wqueue is given, it is used to set up phase-3 verification for each
    - * cloned constraint; omit it if such verification is not needed
    - * (example: the partition is being created anew).
    - */
    -static void
    -CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
    -{
    -	AttrMap    *attmap;
    -	List	   *partFKs;
    -	List	   *clone = NIL;
    -	ListCell   *cell;
    -	Relation	trigrel;
    -
    -	/* obtain a list of constraints that we need to clone */
    -	foreach(cell, RelationGetFKeyList(parentRel))
    -	{
    -		ForeignKeyCacheInfo *fk = lfirst(cell);
    -
    -		/*
    -		 * Refuse to attach a table as partition that this partitioned table
    -		 * already has a foreign key to.  This isn't useful schema, which is
    -		 * proven by the fact that there have been no user complaints that
    -		 * it's already impossible to achieve this in the opposite direction,
    -		 * i.e., creating a foreign key that references a partition.  This
    -		 * restriction allows us to dodge some complexities around
    -		 * pg_constraint and pg_trigger row creations that would be needed
    -		 * during ATTACH/DETACH for this kind of relationship.
    -		 */
    -		if (fk->confrelid == RelationGetRelid(partRel))
    -			ereport(ERROR,
    -					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
    -					 errmsg("cannot attach table \"%s\" as a partition because it is referenced by foreign key \"%s\"",
    -							RelationGetRelationName(partRel),
    -							get_constraint_name(fk->conoid))));
    -
    -		clone = lappend_oid(clone, fk->conoid);
    -	}
    -
    -	/*
    -	 * Silently do nothing if there's nothing to do.  In particular, this
    -	 * avoids throwing a spurious error for foreign tables.
    -	 */
    -	if (clone == NIL)
    -		return;
    -
    -	if (partRel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
    -		ereport(ERROR,
    -				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    -				 errmsg("foreign key constraints are not supported on foreign tables")));
    -
    -	/*
    -	 * Triggers of the foreign keys will be manipulated a bunch of times in
    -	 * the loop below.  To avoid repeatedly opening/closing the trigger
    -	 * catalog relation, we open it here and pass it to the subroutines called
    -	 * below.
    -	 */
    -	trigrel = table_open(TriggerRelationId, RowExclusiveLock);
    -
    -	/*
    -	 * The constraint key may differ, if the columns in the partition are
    -	 * different.  This map is used to convert them.
    -	 */
    -	attmap = build_attrmap_by_name(RelationGetDescr(partRel),
    -								   RelationGetDescr(parentRel),
    -								   false);
    -
    -	partFKs = copyObject(RelationGetFKeyList(partRel));
    -
    -	foreach(cell, clone)
    -	{
    -		Oid			parentConstrOid = lfirst_oid(cell);
    -		Form_pg_constraint constrForm;
    -		Relation	pkrel;
    -		HeapTuple	tuple;
    -		int			numfks;
    -		AttrNumber	conkey[INDEX_MAX_KEYS];
    -		AttrNumber	mapped_conkey[INDEX_MAX_KEYS];
    -		AttrNumber	confkey[INDEX_MAX_KEYS];
    -		Oid			conpfeqop[INDEX_MAX_KEYS];
    -		Oid			conppeqop[INDEX_MAX_KEYS];
    -		Oid			conffeqop[INDEX_MAX_KEYS];
    -		int			numfkdelsetcols;
    -		AttrNumber	confdelsetcols[INDEX_MAX_KEYS];
    -		Constraint *fkconstraint;
    -		bool		attached;
    -		Oid			indexOid;
    -		ObjectAddress address;
    -		ListCell   *lc;
    -		Oid			insertTriggerOid = InvalidOid,
    -					updateTriggerOid = InvalidOid;
    -		bool		with_period;
    -
    -		tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(parentConstrOid));
    -		if (!HeapTupleIsValid(tuple))
    -			elog(ERROR, "cache lookup failed for constraint %u",
    -				 parentConstrOid);
    -		constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
    -
    -		/* Don't clone constraints whose parents are being cloned */
    -		if (list_member_oid(clone, constrForm->conparentid))
    -		{
    -			ReleaseSysCache(tuple);
    -			continue;
    -		}
    -
    -		/*
    -		 * Need to prevent concurrent deletions.  If pkrel is a partitioned
    -		 * relation, that means to lock all partitions.
    -		 */
    -		pkrel = table_open(constrForm->confrelid, ShareRowExclusiveLock);
    -		if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
    -			(void) find_all_inheritors(RelationGetRelid(pkrel),
    -									   ShareRowExclusiveLock, NULL);
    -
    -		DeconstructFkConstraintRow(tuple, &numfks, conkey, confkey,
    -								   conpfeqop, conppeqop, conffeqop,
    -								   &numfkdelsetcols, confdelsetcols);
    -		for (int i = 0; i < numfks; i++)
    -			mapped_conkey[i] = attmap->attnums[conkey[i] - 1];
    -
    -		/*
    -		 * Get the "check" triggers belonging to the constraint, if it is
    -		 * ENFORCED, to pass as parent OIDs for similar triggers that will be
    -		 * created on the partition in addFkRecurseReferencing().  They are
    -		 * also passed to tryAttachPartitionForeignKey() below to simply
    -		 * assign as parents to the partition's existing "check" triggers,
    -		 * that is, if the corresponding constraints is deemed attachable to
    -		 * the parent constraint.
    -		 */
    -		if (constrForm->conenforced)
    -			GetForeignKeyCheckTriggers(trigrel, constrForm->oid,
    -									   constrForm->confrelid, constrForm->conrelid,
    -									   &insertTriggerOid, &updateTriggerOid);
    -
    -		/*
    -		 * Before creating a new constraint, see whether any existing FKs are
    -		 * fit for the purpose.  If one is, attach the parent constraint to
    -		 * it, and don't clone anything.  This way we avoid the expensive
    -		 * verification step and don't end up with a duplicate FK, and we
    -		 * don't need to recurse to partitions for this constraint.
    -		 */
    -		attached = false;
    -		foreach(lc, partFKs)
    -		{
    -			ForeignKeyCacheInfo *fk = lfirst_node(ForeignKeyCacheInfo, lc);
    -
    -			if (tryAttachPartitionForeignKey(wqueue,
    -											 fk,
    -											 partRel,
    -											 parentConstrOid,
    -											 numfks,
    -											 mapped_conkey,
    -											 confkey,
    -											 conpfeqop,
    -											 insertTriggerOid,
    -											 updateTriggerOid,
    -											 trigrel))
    -			{
    -				attached = true;
    -				table_close(pkrel, NoLock);
    -				break;
    -			}
    -		}
    -		if (attached)
    -		{
    -			ReleaseSysCache(tuple);
    -			continue;
    -		}
    -
    -		/* No dice.  Set up to create our own constraint */
    -		fkconstraint = makeNode(Constraint);
    -		fkconstraint->contype = CONSTRAINT_FOREIGN;
    -		/* ->conname determined below */
    -		fkconstraint->deferrable = constrForm->condeferrable;
    -		fkconstraint->initdeferred = constrForm->condeferred;
    -		fkconstraint->location = -1;
    -		fkconstraint->pktable = NULL;
    -		/* ->fk_attrs determined below */
    -		fkconstraint->pk_attrs = NIL;
    -		fkconstraint->fk_matchtype = constrForm->confmatchtype;
    -		fkconstraint->fk_upd_action = constrForm->confupdtype;
    -		fkconstraint->fk_del_action = constrForm->confdeltype;
    -		fkconstraint->fk_del_set_cols = NIL;
    -		fkconstraint->old_conpfeqop = NIL;
    -		fkconstraint->old_pktable_oid = InvalidOid;
    -		fkconstraint->is_enforced = constrForm->conenforced;
    -		fkconstraint->skip_validation = false;
    -		fkconstraint->initially_valid = constrForm->convalidated;
    -		for (int i = 0; i < numfks; i++)
    -		{
    -			Form_pg_attribute att;
    -
    -			att = TupleDescAttr(RelationGetDescr(partRel),
    -								mapped_conkey[i] - 1);
    -			fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs,
    -											 makeString(NameStr(att->attname)));
    -		}
    -
    -		indexOid = constrForm->conindid;
    -		with_period = constrForm->conperiod;
    -
    -		/* Create the pg_constraint entry at this level */
    -		address = addFkConstraint(addFkReferencingSide,
    -								  NameStr(constrForm->conname), fkconstraint,
    -								  partRel, pkrel, indexOid, parentConstrOid,
    -								  numfks, confkey,
    -								  mapped_conkey, conpfeqop,
    -								  conppeqop, conffeqop,
    -								  numfkdelsetcols, confdelsetcols,
    -								  false, with_period);
    -
    -		/* Done with the cloned constraint's tuple */
    -		ReleaseSysCache(tuple);
    -
    -		/* Create the check triggers, and recurse to partitions, if any */
    -		addFkRecurseReferencing(wqueue,
    -								fkconstraint,
    -								partRel,
    -								pkrel,
    -								indexOid,
    -								address.objectId,
    -								numfks,
    -								confkey,
    -								mapped_conkey,
    -								conpfeqop,
    -								conppeqop,
    -								conffeqop,
    -								numfkdelsetcols,
    -								confdelsetcols,
    -								false,	/* no old check exists */
    -								AccessExclusiveLock,
    -								insertTriggerOid,
    -								updateTriggerOid,
    -								with_period);
    -		table_close(pkrel, NoLock);
    -	}
    -
    -	table_close(trigrel, RowExclusiveLock);
    -}
    -
    -/*
    - * When the parent of a partition receives [the referencing side of] a foreign
    - * key, we must propagate that foreign key to the partition.  However, the
    - * partition might already have an equivalent foreign key; this routine
    - * compares the given ForeignKeyCacheInfo (in the partition) to the FK defined
    - * by the other parameters.  If they are equivalent, create the link between
    - * the two constraints and return true.
    - *
    - * If the given FK does not match the one defined by rest of the params,
    - * return false.
    - */
    -static bool
    -tryAttachPartitionForeignKey(List **wqueue,
    -							 ForeignKeyCacheInfo *fk,
    -							 Relation partition,
    -							 Oid parentConstrOid,
    -							 int numfks,
    -							 AttrNumber *mapped_conkey,
    -							 AttrNumber *confkey,
    -							 Oid *conpfeqop,
    -							 Oid parentInsTrigger,
    -							 Oid parentUpdTrigger,
    -							 Relation trigrel)
    -{
    -	HeapTuple	parentConstrTup;
    -	Form_pg_constraint parentConstr;
    -	HeapTuple	partcontup;
    -	Form_pg_constraint partConstr;
    -
    -	parentConstrTup = SearchSysCache1(CONSTROID,
    -									  ObjectIdGetDatum(parentConstrOid));
    -	if (!HeapTupleIsValid(parentConstrTup))
    -		elog(ERROR, "cache lookup failed for constraint %u", parentConstrOid);
    -	parentConstr = (Form_pg_constraint) GETSTRUCT(parentConstrTup);
    -
    -	/*
    -	 * Do some quick & easy initial checks.  If any of these fail, we cannot
    -	 * use this constraint.
    -	 */
    -	if (fk->confrelid != parentConstr->confrelid || fk->nkeys != numfks)
    -	{
    -		ReleaseSysCache(parentConstrTup);
    -		return false;
    -	}
    -	for (int i = 0; i < numfks; i++)
    -	{
    -		if (fk->conkey[i] != mapped_conkey[i] ||
    -			fk->confkey[i] != confkey[i] ||
    -			fk->conpfeqop[i] != conpfeqop[i])
    -		{
    -			ReleaseSysCache(parentConstrTup);
    -			return false;
    -		}
    -	}
    -
    -	/* Looks good so far; perform more extensive checks. */
    -	partcontup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
    -	if (!HeapTupleIsValid(partcontup))
    -		elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
    -	partConstr = (Form_pg_constraint) GETSTRUCT(partcontup);
    -
    -	/*
    -	 * An error should be raised if the constraint enforceability is
    -	 * different. Returning false without raising an error, as we do for other
    -	 * attributes, could lead to a duplicate constraint with the same
    -	 * enforceability as the parent. While this may be acceptable, it may not
    -	 * be ideal. Therefore, it's better to raise an error and allow the user
    -	 * to correct the enforceability before proceeding.
    -	 */
    -	if (partConstr->conenforced != parentConstr->conenforced)
    -		ereport(ERROR,
    -				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
    -				 errmsg("constraint \"%s\" enforceability conflicts with constraint \"%s\" on relation \"%s\"",
    -						NameStr(parentConstr->conname),
    -						NameStr(partConstr->conname),
    -						RelationGetRelationName(partition))));
    -
    -	if (OidIsValid(partConstr->conparentid) ||
    -		partConstr->condeferrable != parentConstr->condeferrable ||
    -		partConstr->condeferred != parentConstr->condeferred ||
    -		partConstr->confupdtype != parentConstr->confupdtype ||
    -		partConstr->confdeltype != parentConstr->confdeltype ||
    -		partConstr->confmatchtype != parentConstr->confmatchtype)
    -	{
    -		ReleaseSysCache(parentConstrTup);
    -		ReleaseSysCache(partcontup);
    -		return false;
    -	}
    -
    -	ReleaseSysCache(parentConstrTup);
    -	ReleaseSysCache(partcontup);
    -
    -	/* Looks good!  Attach this constraint. */
    -	AttachPartitionForeignKey(wqueue, partition, fk->conoid,
    -							  parentConstrOid, parentInsTrigger,
    -							  parentUpdTrigger, trigrel);
    -
    -	return true;
    -}
    -
    -/*
    - * AttachPartitionForeignKey
    - *
    - * The subroutine for tryAttachPartitionForeignKey performs the final tasks of
    - * attaching the constraint, removing redundant triggers and entries from
    - * pg_constraint, and setting the constraint's parent.
    - */
    -static void
    -AttachPartitionForeignKey(List **wqueue,
    -						  Relation partition,
    -						  Oid partConstrOid,
    -						  Oid parentConstrOid,
    -						  Oid parentInsTrigger,
    -						  Oid parentUpdTrigger,
    -						  Relation trigrel)
    -{
    -	HeapTuple	parentConstrTup;
    -	Form_pg_constraint parentConstr;
    -	HeapTuple	partcontup;
    -	Form_pg_constraint partConstr;
    -	bool		queueValidation;
    -	Oid			partConstrFrelid;
    -	Oid			partConstrRelid;
    -	bool		parentConstrIsEnforced;
    -
    -	/* Fetch the parent constraint tuple */
    -	parentConstrTup = SearchSysCache1(CONSTROID,
    -									  ObjectIdGetDatum(parentConstrOid));
    -	if (!HeapTupleIsValid(parentConstrTup))
    -		elog(ERROR, "cache lookup failed for constraint %u", parentConstrOid);
    -	parentConstr = (Form_pg_constraint) GETSTRUCT(parentConstrTup);
    -	parentConstrIsEnforced = parentConstr->conenforced;
    -
    -	/* Fetch the child constraint tuple */
    -	partcontup = SearchSysCache1(CONSTROID,
    -								 ObjectIdGetDatum(partConstrOid));
    -	if (!HeapTupleIsValid(partcontup))
    -		elog(ERROR, "cache lookup failed for constraint %u", partConstrOid);
    -	partConstr = (Form_pg_constraint) GETSTRUCT(partcontup);
    -	partConstrFrelid = partConstr->confrelid;
    -	partConstrRelid = partConstr->conrelid;
    -
    -	/*
    -	 * If the referenced table is partitioned, then the partition we're
    -	 * attaching now has extra pg_constraint rows and action triggers that are
    -	 * no longer needed.  Remove those.
    -	 */
    -	if (get_rel_relkind(partConstrFrelid) == RELKIND_PARTITIONED_TABLE)
    -	{
    -		Relation	pg_constraint = table_open(ConstraintRelationId, RowShareLock);
    -
    -		RemoveInheritedConstraint(pg_constraint, trigrel, partConstrOid,
    -								  partConstrRelid);
    -
    -		table_close(pg_constraint, RowShareLock);
    -	}
    -
    -	/*
    -	 * Will we need to validate this constraint?   A valid parent constraint
    -	 * implies that all child constraints have been validated, so if this one
    -	 * isn't, we must trigger phase 3 validation.
    -	 */
    -	queueValidation = parentConstr->convalidated && !partConstr->convalidated;
    -
    -	ReleaseSysCache(partcontup);
    -	ReleaseSysCache(parentConstrTup);
    -
    -	/*
    -	 * The action triggers in the new partition become redundant -- the parent
    -	 * table already has equivalent ones, and those will be able to reach the
    -	 * partition.  Remove the ones in the partition.  We identify them because
    -	 * they have our constraint OID, as well as being on the referenced rel.
    -	 */
    -	DropForeignKeyConstraintTriggers(trigrel, partConstrOid, partConstrFrelid,
    -									 partConstrRelid);
    -
    -	ConstraintSetParentConstraint(partConstrOid, parentConstrOid,
    -								  RelationGetRelid(partition));
    -
    -	/*
    -	 * Like the constraint, attach partition's "check" triggers to the
    -	 * corresponding parent triggers if the constraint is ENFORCED. NOT
    -	 * ENFORCED constraints do not have these triggers.
    -	 */
    -	if (parentConstrIsEnforced)
    -	{
    -		Oid			insertTriggerOid,
    -					updateTriggerOid;
    -
    -		GetForeignKeyCheckTriggers(trigrel,
    -								   partConstrOid, partConstrFrelid, partConstrRelid,
    -								   &insertTriggerOid, &updateTriggerOid);
    -		Assert(OidIsValid(insertTriggerOid) && OidIsValid(parentInsTrigger));
    -		TriggerSetParentTrigger(trigrel, insertTriggerOid, parentInsTrigger,
    -								RelationGetRelid(partition));
    -		Assert(OidIsValid(updateTriggerOid) && OidIsValid(parentUpdTrigger));
    -		TriggerSetParentTrigger(trigrel, updateTriggerOid, parentUpdTrigger,
    -								RelationGetRelid(partition));
    -	}
    -
    -	/*
    -	 * We updated this pg_constraint row above to set its parent; validating
    -	 * it will cause its convalidated flag to change, so we need CCI here.  In
    -	 * addition, we need it unconditionally for the rare case where the parent
    -	 * table has *two* identical constraints; when reaching this function for
    -	 * the second one, we must have made our changes visible, otherwise we
    -	 * would try to attach both to this one.
    -	 */
    -	CommandCounterIncrement();
    -
    -	/* If validation is needed, put it in the queue now. */
    -	if (queueValidation)
    -	{
    -		Relation	conrel;
    -		Oid			confrelid;
    -
    -		conrel = table_open(ConstraintRelationId, RowExclusiveLock);
    -
    -		partcontup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(partConstrOid));
    -		if (!HeapTupleIsValid(partcontup))
    -			elog(ERROR, "cache lookup failed for constraint %u", partConstrOid);
    -
    -		confrelid = ((Form_pg_constraint) GETSTRUCT(partcontup))->confrelid;
    -
    -		/* Use the same lock as for AT_ValidateConstraint */
    -		QueueFKConstraintValidation(wqueue, conrel, partition, confrelid,
    -									partcontup, ShareUpdateExclusiveLock);
    -		ReleaseSysCache(partcontup);
    -		table_close(conrel, RowExclusiveLock);
    -	}
    -}
    -
    -/*
    - * RemoveInheritedConstraint
    - *
    - * Removes the constraint and its associated trigger from the specified
    - * relation, which inherited the given constraint.
    - */
    -static void
    -RemoveInheritedConstraint(Relation conrel, Relation trigrel, Oid conoid,
    -						  Oid conrelid)
    -{
    -	ObjectAddresses *objs;
    -	HeapTuple	consttup;
    -	ScanKeyData key;
    -	SysScanDesc scan;
    -	HeapTuple	trigtup;
    -
    -	ScanKeyInit(&key,
    -				Anum_pg_constraint_conrelid,
    -				BTEqualStrategyNumber, F_OIDEQ,
    -				ObjectIdGetDatum(conrelid));
    -
    -	scan = systable_beginscan(conrel,
    -							  ConstraintRelidTypidNameIndexId,
    -							  true, NULL, 1, &key);
    -	objs = new_object_addresses();
    -	while ((consttup = systable_getnext(scan)) != NULL)
    -	{
    -		Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(consttup);
    -
    -		if (conform->conparentid != conoid)
    -			continue;
    -		else
    -		{
    -			ObjectAddress addr;
    -			SysScanDesc scan2;
    -			ScanKeyData key2;
    -			int			n PG_USED_FOR_ASSERTS_ONLY;
    -
    -			ObjectAddressSet(addr, ConstraintRelationId, conform->oid);
    -			add_exact_object_address(&addr, objs);
    -
    -			/*
    -			 * First we must delete the dependency record that binds the
    -			 * constraint records together.
    -			 */
    -			n = deleteDependencyRecordsForSpecific(ConstraintRelationId,
    -												   conform->oid,
    -												   DEPENDENCY_INTERNAL,
    -												   ConstraintRelationId,
    -												   conoid);
    -			Assert(n == 1);		/* actually only one is expected */
    -
    -			/*
    -			 * Now search for the triggers for this constraint and set them up
    -			 * for deletion too
    -			 */
    -			ScanKeyInit(&key2,
    -						Anum_pg_trigger_tgconstraint,
    -						BTEqualStrategyNumber, F_OIDEQ,
    -						ObjectIdGetDatum(conform->oid));
    -			scan2 = systable_beginscan(trigrel, TriggerConstraintIndexId,
    -									   true, NULL, 1, &key2);
    -			while ((trigtup = systable_getnext(scan2)) != NULL)
    -			{
    -				ObjectAddressSet(addr, TriggerRelationId,
    -								 ((Form_pg_trigger) GETSTRUCT(trigtup))->oid);
    -				add_exact_object_address(&addr, objs);
    -			}
    -			systable_endscan(scan2);
    -		}
    -	}
    -	/* make the dependency deletions visible */
    -	CommandCounterIncrement();
    -	performMultipleDeletions(objs, DROP_RESTRICT,
    -							 PERFORM_DELETION_INTERNAL);
    -	systable_endscan(scan);
    -}
    -
     /*
      * DropForeignKeyConstraintTriggers
      *
    @@ -12054,128 +11077,6 @@ DropForeignKeyConstraintTriggers(Relation trigrel, Oid conoid, Oid confrelid,
     	systable_endscan(scan);
     }
     
    -/*
    - * GetForeignKeyActionTriggers
    - * 		Returns delete and update "action" triggers of the given relation
    - * 		belonging to the given constraint
    - */
    -static void
    -GetForeignKeyActionTriggers(Relation trigrel,
    -							Oid conoid, Oid confrelid, Oid conrelid,
    -							Oid *deleteTriggerOid,
    -							Oid *updateTriggerOid)
    -{
    -	ScanKeyData key;
    -	SysScanDesc scan;
    -	HeapTuple	trigtup;
    -
    -	*deleteTriggerOid = *updateTriggerOid = InvalidOid;
    -	ScanKeyInit(&key,
    -				Anum_pg_trigger_tgconstraint,
    -				BTEqualStrategyNumber, F_OIDEQ,
    -				ObjectIdGetDatum(conoid));
    -
    -	scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
    -							  NULL, 1, &key);
    -	while ((trigtup = systable_getnext(scan)) != NULL)
    -	{
    -		Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
    -
    -		if (trgform->tgconstrrelid != conrelid)
    -			continue;
    -		if (trgform->tgrelid != confrelid)
    -			continue;
    -		/* Only ever look at "action" triggers on the PK side. */
    -		if (RI_FKey_trigger_type(trgform->tgfoid) != RI_TRIGGER_PK)
    -			continue;
    -		if (TRIGGER_FOR_DELETE(trgform->tgtype))
    -		{
    -			Assert(*deleteTriggerOid == InvalidOid);
    -			*deleteTriggerOid = trgform->oid;
    -		}
    -		else if (TRIGGER_FOR_UPDATE(trgform->tgtype))
    -		{
    -			Assert(*updateTriggerOid == InvalidOid);
    -			*updateTriggerOid = trgform->oid;
    -		}
    -#ifndef USE_ASSERT_CHECKING
    -		/* In an assert-enabled build, continue looking to find duplicates */
    -		if (OidIsValid(*deleteTriggerOid) && OidIsValid(*updateTriggerOid))
    -			break;
    -#endif
    -	}
    -
    -	if (!OidIsValid(*deleteTriggerOid))
    -		elog(ERROR, "could not find ON DELETE action trigger of foreign key constraint %u",
    -			 conoid);
    -	if (!OidIsValid(*updateTriggerOid))
    -		elog(ERROR, "could not find ON UPDATE action trigger of foreign key constraint %u",
    -			 conoid);
    -
    -	systable_endscan(scan);
    -}
    -
    -/*
    - * GetForeignKeyCheckTriggers
    - * 		Returns insert and update "check" triggers of the given relation
    - * 		belonging to the given constraint
    - */
    -static void
    -GetForeignKeyCheckTriggers(Relation trigrel,
    -						   Oid conoid, Oid confrelid, Oid conrelid,
    -						   Oid *insertTriggerOid,
    -						   Oid *updateTriggerOid)
    -{
    -	ScanKeyData key;
    -	SysScanDesc scan;
    -	HeapTuple	trigtup;
    -
    -	*insertTriggerOid = *updateTriggerOid = InvalidOid;
    -	ScanKeyInit(&key,
    -				Anum_pg_trigger_tgconstraint,
    -				BTEqualStrategyNumber, F_OIDEQ,
    -				ObjectIdGetDatum(conoid));
    -
    -	scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
    -							  NULL, 1, &key);
    -	while ((trigtup = systable_getnext(scan)) != NULL)
    -	{
    -		Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
    -
    -		if (trgform->tgconstrrelid != confrelid)
    -			continue;
    -		if (trgform->tgrelid != conrelid)
    -			continue;
    -		/* Only ever look at "check" triggers on the FK side. */
    -		if (RI_FKey_trigger_type(trgform->tgfoid) != RI_TRIGGER_FK)
    -			continue;
    -		if (TRIGGER_FOR_INSERT(trgform->tgtype))
    -		{
    -			Assert(*insertTriggerOid == InvalidOid);
    -			*insertTriggerOid = trgform->oid;
    -		}
    -		else if (TRIGGER_FOR_UPDATE(trgform->tgtype))
    -		{
    -			Assert(*updateTriggerOid == InvalidOid);
    -			*updateTriggerOid = trgform->oid;
    -		}
    -#ifndef USE_ASSERT_CHECKING
    -		/* In an assert-enabled build, continue looking to find duplicates. */
    -		if (OidIsValid(*insertTriggerOid) && OidIsValid(*updateTriggerOid))
    -			break;
    -#endif
    -	}
    -
    -	if (!OidIsValid(*insertTriggerOid))
    -		elog(ERROR, "could not find ON INSERT check triggers of foreign key constraint %u",
    -			 conoid);
    -	if (!OidIsValid(*updateTriggerOid))
    -		elog(ERROR, "could not find ON UPDATE check triggers of foreign key constraint %u",
    -			 conoid);
    -
    -	systable_endscan(scan);
    -}
    -
     /*
      * ALTER TABLE ALTER CONSTRAINT
      *
    @@ -12986,7 +11887,7 @@ ATExecValidateConstraint(List **wqueue, Relation rel, char *constrName,
      * Phase 3 and update the convalidated field in the pg_constraint catalog
      * for the specified relation and all its children.
      */
    -static void
    +void
     QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation fkrel,
     							Oid pkrelid, HeapTuple contuple, LOCKMODE lockmode)
     {
    @@ -17360,7 +16261,7 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
      *
      * Common to ATExecAddInherit() and ATExecAttachPartition().
      */
    -static void
    +void
     CreateInheritance(Relation child_rel, Relation parent_rel, bool ispartition)
     {
     	Relation	catalogRelation;
    @@ -17846,78 +16747,6 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
     	return address;
     }
     
    -/*
    - * MarkInheritDetached
    - *
    - * Set inhdetachpending for a partition, for ATExecDetachPartition
    - * in concurrent mode.  While at it, verify that no other partition is
    - * already pending detach.
    - */
    -static void
    -MarkInheritDetached(Relation child_rel, Relation parent_rel)
    -{
    -	Relation	catalogRelation;
    -	SysScanDesc scan;
    -	ScanKeyData key;
    -	HeapTuple	inheritsTuple;
    -	bool		found = false;
    -
    -	Assert(parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
    -
    -	/*
    -	 * Find pg_inherits entries by inhparent.  (We need to scan them all in
    -	 * order to verify that no other partition is pending detach.)
    -	 */
    -	catalogRelation = table_open(InheritsRelationId, RowExclusiveLock);
    -	ScanKeyInit(&key,
    -				Anum_pg_inherits_inhparent,
    -				BTEqualStrategyNumber, F_OIDEQ,
    -				ObjectIdGetDatum(RelationGetRelid(parent_rel)));
    -	scan = systable_beginscan(catalogRelation, InheritsParentIndexId,
    -							  true, NULL, 1, &key);
    -
    -	while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
    -	{
    -		Form_pg_inherits inhForm;
    -
    -		inhForm = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
    -		if (inhForm->inhdetachpending)
    -			ereport(ERROR,
    -					errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    -					errmsg("partition \"%s\" already pending detach in partitioned table \"%s.%s\"",
    -						   get_rel_name(inhForm->inhrelid),
    -						   get_namespace_name(parent_rel->rd_rel->relnamespace),
    -						   RelationGetRelationName(parent_rel)),
    -					errhint("Use ALTER TABLE ... DETACH PARTITION ... FINALIZE to complete the pending detach operation."));
    -
    -		if (inhForm->inhrelid == RelationGetRelid(child_rel))
    -		{
    -			HeapTuple	newtup;
    -
    -			newtup = heap_copytuple(inheritsTuple);
    -			((Form_pg_inherits) GETSTRUCT(newtup))->inhdetachpending = true;
    -
    -			CatalogTupleUpdate(catalogRelation,
    -							   &inheritsTuple->t_self,
    -							   newtup);
    -			found = true;
    -			heap_freetuple(newtup);
    -			/* keep looking, to ensure we catch others pending detach */
    -		}
    -	}
    -
    -	/* Done */
    -	systable_endscan(scan);
    -	table_close(catalogRelation, RowExclusiveLock);
    -
    -	if (!found)
    -		ereport(ERROR,
    -				(errcode(ERRCODE_UNDEFINED_TABLE),
    -				 errmsg("relation \"%s\" is not a partition of relation \"%s\"",
    -						RelationGetRelationName(child_rel),
    -						RelationGetRelationName(parent_rel))));
    -}
    -
     /*
      * RemoveInheritance
      *
    @@ -17936,7 +16765,7 @@ MarkInheritDetached(Relation child_rel, Relation parent_rel)
      *
      * Common to ATExecDropInherit() and ATExecDetachPartition().
      */
    -static void
    +void
     RemoveInheritance(Relation child_rel, Relation parent_rel, bool expect_detached)
     {
     	Relation	catalogRelation;
    @@ -19708,2271 +18537,6 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
     	ReleaseSysCache(tuple);
     }
     
    -/*
    - * Transform any expressions present in the partition key
    - *
    - * Returns a transformed PartitionSpec.
    - */
    -static PartitionSpec *
    -transformPartitionSpec(Relation rel, PartitionSpec *partspec)
    -{
    -	PartitionSpec *newspec;
    -	ParseState *pstate;
    -	ParseNamespaceItem *nsitem;
    -	ListCell   *l;
    -
    -	newspec = makeNode(PartitionSpec);
    -
    -	newspec->strategy = partspec->strategy;
    -	newspec->partParams = NIL;
    -	newspec->location = partspec->location;
    -
    -	/* Check valid number of columns for strategy */
    -	if (partspec->strategy == PARTITION_STRATEGY_LIST &&
    -		list_length(partspec->partParams) != 1)
    -		ereport(ERROR,
    -				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
    -				 errmsg("cannot use \"list\" partition strategy with more than one column")));
    -
    -	/*
    -	 * Create a dummy ParseState and insert the target relation as its sole
    -	 * rangetable entry.  We need a ParseState for transformExpr.
    -	 */
    -	pstate = make_parsestate(NULL);
    -	nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock,
    -										   NULL, false, true);
    -	addNSItemToQuery(pstate, nsitem, true, true, true);
    -
    -	/* take care of any partition expressions */
    -	foreach(l, partspec->partParams)
    -	{
    -		PartitionElem *pelem = lfirst_node(PartitionElem, l);
    -
    -		if (pelem->expr)
    -		{
    -			/* Copy, to avoid scribbling on the input */
    -			pelem = copyObject(pelem);
    -
    -			/* Now do parse transformation of the expression */
    -			pelem->expr = transformExpr(pstate, pelem->expr,
    -										EXPR_KIND_PARTITION_EXPRESSION);
    -
    -			/* we have to fix its collations too */
    -			assign_expr_collations(pstate, pelem->expr);
    -		}
    -
    -		newspec->partParams = lappend(newspec->partParams, pelem);
    -	}
    -
    -	return newspec;
    -}
    -
    -/*
    - * Compute per-partition-column information from a list of PartitionElems.
    - * Expressions in the PartitionElems must be parse-analyzed already.
    - */
    -static void
    -ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNumber *partattrs,
    -					  List **partexprs, Oid *partopclass, Oid *partcollation,
    -					  PartitionStrategy strategy)
    -{
    -	int			attn;
    -	ListCell   *lc;
    -	Oid			am_oid;
    -
    -	attn = 0;
    -	foreach(lc, partParams)
    -	{
    -		PartitionElem *pelem = lfirst_node(PartitionElem, lc);
    -		Oid			atttype;
    -		Oid			attcollation;
    -
    -		if (pelem->name != NULL)
    -		{
    -			/* Simple attribute reference */
    -			HeapTuple	atttuple;
    -			Form_pg_attribute attform;
    -
    -			atttuple = SearchSysCacheAttName(RelationGetRelid(rel),
    -											 pelem->name);
    -			if (!HeapTupleIsValid(atttuple))
    -				ereport(ERROR,
    -						(errcode(ERRCODE_UNDEFINED_COLUMN),
    -						 errmsg("column \"%s\" named in partition key does not exist",
    -								pelem->name),
    -						 parser_errposition(pstate, pelem->location)));
    -			attform = (Form_pg_attribute) GETSTRUCT(atttuple);
    -
    -			if (attform->attnum <= 0)
    -				ereport(ERROR,
    -						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
    -						 errmsg("cannot use system column \"%s\" in partition key",
    -								pelem->name),
    -						 parser_errposition(pstate, pelem->location)));
    -
    -			/*
    -			 * Stored generated columns cannot work: They are computed after
    -			 * BEFORE triggers, but partition routing is done before all
    -			 * triggers.  Maybe virtual generated columns could be made to
    -			 * work, but then they would need to be handled as an expression
    -			 * below.
    -			 */
    -			if (attform->attgenerated)
    -				ereport(ERROR,
    -						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
    -						 errmsg("cannot use generated column in partition key"),
    -						 errdetail("Column \"%s\" is a generated column.",
    -								   pelem->name),
    -						 parser_errposition(pstate, pelem->location)));
    -
    -			partattrs[attn] = attform->attnum;
    -			atttype = attform->atttypid;
    -			attcollation = attform->attcollation;
    -			ReleaseSysCache(atttuple);
    -		}
    -		else
    -		{
    -			/* Expression */
    -			Node	   *expr = pelem->expr;
    -			char		partattname[16];
    -			Bitmapset  *expr_attrs = NULL;
    -			int			i;
    -
    -			Assert(expr != NULL);
    -			atttype = exprType(expr);
    -			attcollation = exprCollation(expr);
    -
    -			/*
    -			 * The expression must be of a storable type (e.g., not RECORD).
    -			 * The test is the same as for whether a table column is of a safe
    -			 * type (which is why we needn't check for the non-expression
    -			 * case).
    -			 */
    -			snprintf(partattname, sizeof(partattname), "%d", attn + 1);
    -			CheckAttributeType(partattname,
    -							   atttype, attcollation,
    -							   NIL, CHKATYPE_IS_PARTKEY);
    -
    -			/*
    -			 * Strip any top-level COLLATE clause.  This ensures that we treat
    -			 * "x COLLATE y" and "(x COLLATE y)" alike.
    -			 */
    -			while (IsA(expr, CollateExpr))
    -				expr = (Node *) ((CollateExpr *) expr)->arg;
    -
    -			/*
    -			 * Examine all the columns in the partition key expression. When
    -			 * the whole-row reference is present, examine all the columns of
    -			 * the partitioned table.
    -			 */
    -			pull_varattnos(expr, 1, &expr_attrs);
    -			if (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber, expr_attrs))
    -			{
    -				expr_attrs = bms_add_range(expr_attrs,
    -										   1 - FirstLowInvalidHeapAttributeNumber,
    -										   RelationGetNumberOfAttributes(rel) - FirstLowInvalidHeapAttributeNumber);
    -				expr_attrs = bms_del_member(expr_attrs, 0 - FirstLowInvalidHeapAttributeNumber);
    -			}
    -
    -			i = -1;
    -			while ((i = bms_next_member(expr_attrs, i)) >= 0)
    -			{
    -				AttrNumber	attno = i + FirstLowInvalidHeapAttributeNumber;
    -
    -				Assert(attno != 0);
    -
    -				/*
    -				 * Cannot allow system column references, since that would
    -				 * make partition routing impossible: their values won't be
    -				 * known yet when we need to do that.
    -				 */
    -				if (attno < 0)
    -					ereport(ERROR,
    -							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
    -							 errmsg("partition key expressions cannot contain system column references")));
    -
    -				/*
    -				 * Stored generated columns cannot work: They are computed
    -				 * after BEFORE triggers, but partition routing is done before
    -				 * all triggers.  Virtual generated columns could probably
    -				 * work, but it would require more work elsewhere (for example
    -				 * SET EXPRESSION would need to check whether the column is
    -				 * used in partition keys).  Seems safer to prohibit for now.
    -				 */
    -				if (TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated)
    -					ereport(ERROR,
    -							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
    -							 errmsg("cannot use generated column in partition key"),
    -							 errdetail("Column \"%s\" is a generated column.",
    -									   get_attname(RelationGetRelid(rel), attno, false)),
    -							 parser_errposition(pstate, pelem->location)));
    -			}
    -
    -			if (IsA(expr, Var) &&
    -				((Var *) expr)->varattno > 0)
    -			{
    -
    -				/*
    -				 * User wrote "(column)" or "(column COLLATE something)".
    -				 * Treat it like simple attribute anyway.
    -				 */
    -				partattrs[attn] = ((Var *) expr)->varattno;
    -			}
    -			else
    -			{
    -				partattrs[attn] = 0;	/* marks the column as expression */
    -				*partexprs = lappend(*partexprs, expr);
    -
    -				/*
    -				 * transformPartitionSpec() should have already rejected
    -				 * subqueries, aggregates, window functions, and SRFs, based
    -				 * on the EXPR_KIND_ for partition expressions.
    -				 */
    -
    -				/*
    -				 * Preprocess the expression before checking for mutability.
    -				 * This is essential for the reasons described in
    -				 * contain_mutable_functions_after_planning.  However, we call
    -				 * expression_planner for ourselves rather than using that
    -				 * function, because if constant-folding reduces the
    -				 * expression to a constant, we'd like to know that so we can
    -				 * complain below.
    -				 *
    -				 * Like contain_mutable_functions_after_planning, assume that
    -				 * expression_planner won't scribble on its input, so this
    -				 * won't affect the partexprs entry we saved above.
    -				 */
    -				expr = (Node *) expression_planner((Expr *) expr);
    -
    -				/*
    -				 * Partition expressions cannot contain mutable functions,
    -				 * because a given row must always map to the same partition
    -				 * as long as there is no change in the partition boundary
    -				 * structure.
    -				 */
    -				if (contain_mutable_functions(expr))
    -					ereport(ERROR,
    -							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
    -							 errmsg("functions in partition key expression must be marked IMMUTABLE")));
    -
    -				/*
    -				 * While it is not exactly *wrong* for a partition expression
    -				 * to be a constant, it seems better to reject such keys.
    -				 */
    -				if (IsA(expr, Const))
    -					ereport(ERROR,
    -							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
    -							 errmsg("cannot use constant expression as partition key")));
    -			}
    -		}
    -
    -		/*
    -		 * Apply collation override if any
    -		 */
    -		if (pelem->collation)
    -			attcollation = get_collation_oid(pelem->collation, false);
    -
    -		/*
    -		 * Check we have a collation iff it's a collatable type.  The only
    -		 * expected failures here are (1) COLLATE applied to a noncollatable
    -		 * type, or (2) partition expression had an unresolved collation. But
    -		 * we might as well code this to be a complete consistency check.
    -		 */
    -		if (type_is_collatable(atttype))
    -		{
    -			if (!OidIsValid(attcollation))
    -				ereport(ERROR,
    -						(errcode(ERRCODE_INDETERMINATE_COLLATION),
    -						 errmsg("could not determine which collation to use for partition expression"),
    -						 errhint("Use the COLLATE clause to set the collation explicitly.")));
    -		}
    -		else
    -		{
    -			if (OidIsValid(attcollation))
    -				ereport(ERROR,
    -						(errcode(ERRCODE_DATATYPE_MISMATCH),
    -						 errmsg("collations are not supported by type %s",
    -								format_type_be(atttype))));
    -		}
    -
    -		partcollation[attn] = attcollation;
    -
    -		/*
    -		 * Identify the appropriate operator class.  For list and range
    -		 * partitioning, we use a btree operator class; hash partitioning uses
    -		 * a hash operator class.
    -		 */
    -		if (strategy == PARTITION_STRATEGY_HASH)
    -			am_oid = HASH_AM_OID;
    -		else
    -			am_oid = BTREE_AM_OID;
    -
    -		if (!pelem->opclass)
    -		{
    -			partopclass[attn] = GetDefaultOpClass(atttype, am_oid);
    -
    -			if (!OidIsValid(partopclass[attn]))
    -			{
    -				if (strategy == PARTITION_STRATEGY_HASH)
    -					ereport(ERROR,
    -							(errcode(ERRCODE_UNDEFINED_OBJECT),
    -							 errmsg("data type %s has no default operator class for access method \"%s\"",
    -									format_type_be(atttype), "hash"),
    -							 errhint("You must specify a hash operator class or define a default hash operator class for the data type.")));
    -				else
    -					ereport(ERROR,
    -							(errcode(ERRCODE_UNDEFINED_OBJECT),
    -							 errmsg("data type %s has no default operator class for access method \"%s\"",
    -									format_type_be(atttype), "btree"),
    -							 errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
    -			}
    -		}
    -		else
    -			partopclass[attn] = ResolveOpClass(pelem->opclass,
    -											   atttype,
    -											   am_oid == HASH_AM_OID ? "hash" : "btree",
    -											   am_oid);
    -
    -		attn++;
    -	}
    -}
    -
    -/*
    - * PartConstraintImpliedByRelConstraint
    - *		Do scanrel's existing constraints imply the partition constraint?
    - *
    - * "Existing constraints" include its check constraints and column-level
    - * not-null constraints.  partConstraint describes the partition constraint,
    - * in implicit-AND form.
    - */
    -bool
    -PartConstraintImpliedByRelConstraint(Relation scanrel,
    -									 List *partConstraint)
    -{
    -	List	   *existConstraint = NIL;
    -	TupleConstr *constr = RelationGetDescr(scanrel)->constr;
    -	int			i;
    -
    -	if (constr && constr->has_not_null)
    -	{
    -		int			natts = scanrel->rd_att->natts;
    -
    -		for (i = 1; i <= natts; i++)
    -		{
    -			CompactAttribute *att = TupleDescCompactAttr(scanrel->rd_att, i - 1);
    -
    -			/* invalid not-null constraint must be ignored here */
    -			if (att->attnullability == ATTNULLABLE_VALID && !att->attisdropped)
    -			{
    -				Form_pg_attribute wholeatt = TupleDescAttr(scanrel->rd_att, i - 1);
    -				NullTest   *ntest = makeNode(NullTest);
    -
    -				ntest->arg = (Expr *) makeVar(1,
    -											  i,
    -											  wholeatt->atttypid,
    -											  wholeatt->atttypmod,
    -											  wholeatt->attcollation,
    -											  0);
    -				ntest->nulltesttype = IS_NOT_NULL;
    -
    -				/*
    -				 * argisrow=false is correct even for a composite column,
    -				 * because attnotnull does not represent a SQL-spec IS NOT
    -				 * NULL test in such a case, just IS DISTINCT FROM NULL.
    -				 */
    -				ntest->argisrow = false;
    -				ntest->location = -1;
    -				existConstraint = lappend(existConstraint, ntest);
    -			}
    -		}
    -	}
    -
    -	return ConstraintImpliedByRelConstraint(scanrel, partConstraint, existConstraint);
    -}
    -
    -/*
    - * ConstraintImpliedByRelConstraint
    - *		Do scanrel's existing constraints imply the given constraint?
    - *
    - * testConstraint is the constraint to validate. provenConstraint is a
    - * caller-provided list of conditions which this function may assume
    - * to be true. Both provenConstraint and testConstraint must be in
    - * implicit-AND form, must only contain immutable clauses, and must
    - * contain only Vars with varno = 1.
    - */
    -bool
    -ConstraintImpliedByRelConstraint(Relation scanrel, List *testConstraint, List *provenConstraint)
    -{
    -	List	   *existConstraint = list_copy(provenConstraint);
    -	TupleConstr *constr = RelationGetDescr(scanrel)->constr;
    -	int			num_check,
    -				i;
    -
    -	num_check = (constr != NULL) ? constr->num_check : 0;
    -	for (i = 0; i < num_check; i++)
    -	{
    -		Node	   *cexpr;
    -
    -		/*
    -		 * If this constraint hasn't been fully validated yet, we must ignore
    -		 * it here.
    -		 */
    -		if (!constr->check[i].ccvalid)
    -			continue;
    -
    -		/*
    -		 * NOT ENFORCED constraints are always marked as invalid, which should
    -		 * have been ignored.
    -		 */
    -		Assert(constr->check[i].ccenforced);
    -
    -		cexpr = stringToNode(constr->check[i].ccbin);
    -
    -		/*
    -		 * Run each expression through const-simplification and
    -		 * canonicalization.  It is necessary, because we will be comparing it
    -		 * to similarly-processed partition constraint expressions, and may
    -		 * fail to detect valid matches without this.
    -		 */
    -		cexpr = eval_const_expressions(NULL, cexpr);
    -		cexpr = (Node *) canonicalize_qual((Expr *) cexpr, true);
    -
    -		existConstraint = list_concat(existConstraint,
    -									  make_ands_implicit((Expr *) cexpr));
    -	}
    -
    -	/*
    -	 * Try to make the proof.  Since we are comparing CHECK constraints, we
    -	 * need to use weak implication, i.e., we assume existConstraint is
    -	 * not-false and try to prove the same for testConstraint.
    -	 *
    -	 * Note that predicate_implied_by assumes its first argument is known
    -	 * immutable.  That should always be true for both NOT NULL and partition
    -	 * constraints, so we don't test it here.
    -	 */
    -	return predicate_implied_by(testConstraint, existConstraint, true);
    -}
    -
    -/*
    - * QueuePartitionConstraintValidation
    - *
    - * Add an entry to wqueue to have the given partition constraint validated by
    - * Phase 3, for the given relation, and all its children.
    - *
    - * We first verify whether the given constraint is implied by pre-existing
    - * relation constraints; if it is, there's no need to scan the table to
    - * validate, so don't queue in that case.
    - */
    -static void
    -QueuePartitionConstraintValidation(List **wqueue, Relation scanrel,
    -								   List *partConstraint,
    -								   bool validate_default)
    -{
    -	/*
    -	 * Based on the table's existing constraints, determine whether or not we
    -	 * may skip scanning the table.
    -	 */
    -	if (PartConstraintImpliedByRelConstraint(scanrel, partConstraint))
    -	{
    -		if (!validate_default)
    -			ereport(DEBUG1,
    -					(errmsg_internal("partition constraint for table \"%s\" is implied by existing constraints",
    -									 RelationGetRelationName(scanrel))));
    -		else
    -			ereport(DEBUG1,
    -					(errmsg_internal("updated partition constraint for default partition \"%s\" is implied by existing constraints",
    -									 RelationGetRelationName(scanrel))));
    -		return;
    -	}
    -
    -	/*
    -	 * Constraints proved insufficient. For plain relations, queue a
    -	 * validation item now; for partitioned tables, recurse to process each
    -	 * partition.
    -	 */
    -	if (scanrel->rd_rel->relkind == RELKIND_RELATION)
    -	{
    -		AlteredTableInfo *tab;
    -
    -		/* Grab a work queue entry. */
    -		tab = ATGetQueueEntry(wqueue, scanrel);
    -		Assert(tab->partition_constraint == NULL);
    -		tab->partition_constraint = (Expr *) linitial(partConstraint);
    -		tab->validate_default = validate_default;
    -	}
    -	else if (scanrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
    -	{
    -		PartitionDesc partdesc = RelationGetPartitionDesc(scanrel, true);
    -		int			i;
    -
    -		for (i = 0; i < partdesc->nparts; i++)
    -		{
    -			Relation	part_rel;
    -			List	   *thisPartConstraint;
    -
    -			/*
    -			 * This is the minimum lock we need to prevent deadlocks.
    -			 */
    -			part_rel = table_open(partdesc->oids[i], AccessExclusiveLock);
    -
    -			/*
    -			 * Adjust the constraint for scanrel so that it matches this
    -			 * partition's attribute numbers.
    -			 */
    -			thisPartConstraint =
    -				map_partition_varattnos(partConstraint, 1,
    -										part_rel, scanrel);
    -
    -			QueuePartitionConstraintValidation(wqueue, part_rel,
    -											   thisPartConstraint,
    -											   validate_default);
    -			table_close(part_rel, NoLock);	/* keep lock till commit */
    -		}
    -	}
    -}
    -
    -/*
    - * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
    - *
    - * Return the address of the newly attached partition.
    - */
    -static ObjectAddress
    -ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
    -					  AlterTableUtilityContext *context)
    -{
    -	Relation	attachrel,
    -				catalog;
    -	List	   *attachrel_children;
    -	List	   *partConstraint;
    -	SysScanDesc scan;
    -	ScanKeyData skey;
    -	AttrNumber	attno;
    -	int			natts;
    -	TupleDesc	tupleDesc;
    -	ObjectAddress address;
    -	const char *trigger_name;
    -	Oid			defaultPartOid;
    -	List	   *partBoundConstraint;
    -	ParseState *pstate = make_parsestate(NULL);
    -
    -	pstate->p_sourcetext = context->queryString;
    -
    -	/*
    -	 * We must lock the default partition if one exists, because attaching a
    -	 * new partition will change its partition constraint.
    -	 */
    -	defaultPartOid =
    -		get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true));
    -	if (OidIsValid(defaultPartOid))
    -		LockRelationOid(defaultPartOid, AccessExclusiveLock);
    -
    -	attachrel = table_openrv(cmd->name, AccessExclusiveLock);
    -
    -	/*
    -	 * XXX I think it'd be a good idea to grab locks on all tables referenced
    -	 * by FKs at this point also.
    -	 */
    -
    -	/*
    -	 * Must be owner of both parent and source table -- parent was checked by
    -	 * ATSimplePermissions call in ATPrepCmd
    -	 */
    -	ATSimplePermissions(AT_AttachPartition, attachrel,
    -						ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
    -
    -	/* A partition can only have one parent */
    -	if (attachrel->rd_rel->relispartition)
    -		ereport(ERROR,
    -				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    -				 errmsg("\"%s\" is already a partition",
    -						RelationGetRelationName(attachrel))));
    -
    -	if (OidIsValid(attachrel->rd_rel->reloftype))
    -		ereport(ERROR,
    -				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    -				 errmsg("cannot attach a typed table as partition")));
    -
    -	/*
    -	 * Table being attached should not already be part of inheritance; either
    -	 * as a child table...
    -	 */
    -	catalog = table_open(InheritsRelationId, AccessShareLock);
    -	ScanKeyInit(&skey,
    -				Anum_pg_inherits_inhrelid,
    -				BTEqualStrategyNumber, F_OIDEQ,
    -				ObjectIdGetDatum(RelationGetRelid(attachrel)));
    -	scan = systable_beginscan(catalog, InheritsRelidSeqnoIndexId, true,
    -							  NULL, 1, &skey);
    -	if (HeapTupleIsValid(systable_getnext(scan)))
    -		ereport(ERROR,
    -				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    -				 errmsg("cannot attach inheritance child as partition")));
    -	systable_endscan(scan);
    -
    -	/* ...or as a parent table (except the case when it is partitioned) */
    -	ScanKeyInit(&skey,
    -				Anum_pg_inherits_inhparent,
    -				BTEqualStrategyNumber, F_OIDEQ,
    -				ObjectIdGetDatum(RelationGetRelid(attachrel)));
    -	scan = systable_beginscan(catalog, InheritsParentIndexId, true, NULL,
    -							  1, &skey);
    -	if (HeapTupleIsValid(systable_getnext(scan)) &&
    -		attachrel->rd_rel->relkind == RELKIND_RELATION)
    -		ereport(ERROR,
    -				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    -				 errmsg("cannot attach inheritance parent as partition")));
    -	systable_endscan(scan);
    -	table_close(catalog, AccessShareLock);
    -
    -	/*
    -	 * Prevent circularity by seeing if rel is a partition of attachrel. (In
    -	 * particular, this disallows making a rel a partition of itself.)
    -	 *
    -	 * We do that by checking if rel is a member of the list of attachrel's
    -	 * partitions provided the latter is partitioned at all.  We want to avoid
    -	 * having to construct this list again, so we request the strongest lock
    -	 * on all partitions.  We need the strongest lock, because we may decide
    -	 * to scan them if we find out that the table being attached (or its leaf
    -	 * partitions) may contain rows that violate the partition constraint. If
    -	 * the table has a constraint that would prevent such rows, which by
    -	 * definition is present in all the partitions, we need not scan the
    -	 * table, nor its partitions.  But we cannot risk a deadlock by taking a
    -	 * weaker lock now and the stronger one only when needed.
    -	 */
    -	attachrel_children = find_all_inheritors(RelationGetRelid(attachrel),
    -											 AccessExclusiveLock, NULL);
    -	if (list_member_oid(attachrel_children, RelationGetRelid(rel)))
    -		ereport(ERROR,
    -				(errcode(ERRCODE_DUPLICATE_TABLE),
    -				 errmsg("circular inheritance not allowed"),
    -				 errdetail("\"%s\" is already a child of \"%s\".",
    -						   RelationGetRelationName(rel),
    -						   RelationGetRelationName(attachrel))));
    -
    -	/* If the parent is permanent, so must be all of its partitions. */
    -	if (rel->rd_rel->relpersistence != RELPERSISTENCE_TEMP &&
    -		attachrel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
    -		ereport(ERROR,
    -				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    -				 errmsg("cannot attach a temporary relation as partition of permanent relation \"%s\"",
    -						RelationGetRelationName(rel))));
    -
    -	/* Temp parent cannot have a partition that is itself not a temp */
    -	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
    -		attachrel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
    -		ereport(ERROR,
    -				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    -				 errmsg("cannot attach a permanent relation as partition of temporary relation \"%s\"",
    -						RelationGetRelationName(rel))));
    -
    -	/* If the parent is temp, it must belong to this session */
    -	if (RELATION_IS_OTHER_TEMP(rel))
    -		ereport(ERROR,
    -				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    -				 errmsg("cannot attach as partition of temporary relation of another session")));
    -
    -	/* Ditto for the partition */
    -	if (RELATION_IS_OTHER_TEMP(attachrel))
    -		ereport(ERROR,
    -				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    -				 errmsg("cannot attach temporary relation of another session as partition")));
    -
    -	/*
    -	 * Check if attachrel has any identity columns or any columns that aren't
    -	 * in the parent.
    -	 */
    -	tupleDesc = RelationGetDescr(attachrel);
    -	natts = tupleDesc->natts;
    -	for (attno = 1; attno <= natts; attno++)
    -	{
    -		Form_pg_attribute attribute = TupleDescAttr(tupleDesc, attno - 1);
    -		char	   *attributeName = NameStr(attribute->attname);
    -
    -		/* Ignore dropped */
    -		if (attribute->attisdropped)
    -			continue;
    -
    -		if (attribute->attidentity)
    -			ereport(ERROR,
    -					errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    -					errmsg("table \"%s\" being attached contains an identity column \"%s\"",
    -						   RelationGetRelationName(attachrel), attributeName),
    -					errdetail("The new partition may not contain an identity column."));
    -
    -		/* Try to find the column in parent (matching on column name) */
    -		if (!SearchSysCacheExists2(ATTNAME,
    -								   ObjectIdGetDatum(RelationGetRelid(rel)),
    -								   CStringGetDatum(attributeName)))
    -			ereport(ERROR,
    -					(errcode(ERRCODE_DATATYPE_MISMATCH),
    -					 errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
    -							RelationGetRelationName(attachrel), attributeName,
    -							RelationGetRelationName(rel)),
    -					 errdetail("The new partition may contain only the columns present in parent.")));
    -	}
    -
    -	/*
    -	 * If child_rel has row-level triggers with transition tables, we
    -	 * currently don't allow it to become a partition.  See also prohibitions
    -	 * in ATExecAddInherit() and CreateTrigger().
    -	 */
    -	trigger_name = FindTriggerIncompatibleWithInheritance(attachrel->trigdesc);
    -	if (trigger_name != NULL)
    -		ereport(ERROR,
    -				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
    -				 errmsg("trigger \"%s\" prevents table \"%s\" from becoming a partition",
    -						trigger_name, RelationGetRelationName(attachrel)),
    -				 errdetail("ROW triggers with transition tables are not supported on partitions.")));
    -
    -	/*
    -	 * Check that the new partition's bound is valid and does not overlap any
    -	 * of existing partitions of the parent - note that it does not return on
    -	 * error.
    -	 */
    -	check_new_partition_bound(RelationGetRelationName(attachrel), rel,
    -							  cmd->bound, pstate);
    -
    -	/* 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.
    -	 * If the parent itself is a partition, make sure to include its
    -	 * constraint as well.
    -	 */
    -	partBoundConstraint = get_qual_from_partbound(rel, cmd->bound);
    -
    -	/*
    -	 * Use list_concat_copy() to avoid modifying partBoundConstraint in place,
    -	 * since it's needed later to construct the constraint expression for
    -	 * validating against the default partition, if any.
    -	 */
    -	partConstraint = list_concat_copy(partBoundConstraint,
    -									  RelationGetPartitionQual(rel));
    -
    -	/* Skip validation if there are no constraints to validate. */
    -	if (partConstraint)
    -	{
    -		/*
    -		 * Run the partition quals through const-simplification similar to
    -		 * check constraints.  We skip canonicalize_qual, though, because
    -		 * partition quals should be in canonical form already.
    -		 */
    -		partConstraint =
    -			(List *) eval_const_expressions(NULL,
    -											(Node *) partConstraint);
    -
    -		/* XXX this sure looks wrong */
    -		partConstraint = list_make1(make_ands_explicit(partConstraint));
    -
    -		/*
    -		 * Adjust the generated constraint to match this partition's attribute
    -		 * numbers.
    -		 */
    -		partConstraint = map_partition_varattnos(partConstraint, 1, attachrel,
    -												 rel);
    -
    -		/* Validate partition constraints against the table being attached. */
    -		QueuePartitionConstraintValidation(wqueue, attachrel, partConstraint,
    -										   false);
    -	}
    -
    -	/*
    -	 * If we're attaching a partition other than the default partition and a
    -	 * default one exists, then that partition's partition constraint changes,
    -	 * so add an entry to the work queue to validate it, too.  (We must not do
    -	 * this when the partition being attached is the default one; we already
    -	 * did it above!)
    -	 */
    -	if (OidIsValid(defaultPartOid))
    -	{
    -		Relation	defaultrel;
    -		List	   *defPartConstraint;
    -
    -		Assert(!cmd->bound->is_default);
    -
    -		/* we already hold a lock on the default partition */
    -		defaultrel = table_open(defaultPartOid, NoLock);
    -		defPartConstraint =
    -			get_proposed_default_constraint(partBoundConstraint);
    -
    -		/*
    -		 * Map the Vars in the constraint expression from rel's attnos to
    -		 * defaultrel's.
    -		 */
    -		defPartConstraint =
    -			map_partition_varattnos(defPartConstraint,
    -									1, defaultrel, rel);
    -		QueuePartitionConstraintValidation(wqueue, defaultrel,
    -										   defPartConstraint, true);
    -
    -		/* keep our lock until commit. */
    -		table_close(defaultrel, NoLock);
    -	}
    -
    -	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachrel));
    -
    -	/*
    -	 * If the partition we just attached is partitioned itself, invalidate
    -	 * relcache for all descendent partitions too to ensure that their
    -	 * rd_partcheck expression trees are rebuilt; partitions already locked at
    -	 * the beginning of this function.
    -	 */
    -	if (attachrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
    -	{
    -		ListCell   *l;
    -
    -		foreach(l, attachrel_children)
    -		{
    -			CacheInvalidateRelcacheByRelid(lfirst_oid(l));
    -		}
    -	}
    -
    -	/* keep our lock until commit */
    -	table_close(attachrel, NoLock);
    -
    -	return address;
    -}
    -
    -/*
    - * AttachPartitionEnsureIndexes
    - *		subroutine for ATExecAttachPartition to create/match indexes
    - *
    - * Enforce the indexing rule for partitioned tables during ALTER TABLE / ATTACH
    - * PARTITION: every partition must have an index attached to each index on the
    - * partitioned table.
    - */
    -static void
    -AttachPartitionEnsureIndexes(List **wqueue, Relation rel, Relation attachrel)
    -{
    -	List	   *idxes;
    -	List	   *attachRelIdxs;
    -	Relation   *attachrelIdxRels;
    -	IndexInfo **attachInfos;
    -	ListCell   *cell;
    -	MemoryContext cxt;
    -	MemoryContext oldcxt;
    -
    -	cxt = AllocSetContextCreate(CurrentMemoryContext,
    -								"AttachPartitionEnsureIndexes",
    -								ALLOCSET_DEFAULT_SIZES);
    -	oldcxt = MemoryContextSwitchTo(cxt);
    -
    -	idxes = RelationGetIndexList(rel);
    -	attachRelIdxs = RelationGetIndexList(attachrel);
    -	attachrelIdxRels = palloc(sizeof(Relation) * list_length(attachRelIdxs));
    -	attachInfos = palloc(sizeof(IndexInfo *) * list_length(attachRelIdxs));
    -
    -	/* Build arrays of all existing indexes and their IndexInfos */
    -	foreach_oid(cldIdxId, attachRelIdxs)
    -	{
    -		int			i = foreach_current_index(cldIdxId);
    -
    -		attachrelIdxRels[i] = index_open(cldIdxId, AccessShareLock);
    -		attachInfos[i] = BuildIndexInfo(attachrelIdxRels[i]);
    -	}
    -
    -	/*
    -	 * If we're attaching a foreign table, we must fail if any of the indexes
    -	 * is a constraint index; otherwise, there's nothing to do here.  Do this
    -	 * before starting work, to avoid wasting the effort of building a few
    -	 * non-unique indexes before coming across a unique one.
    -	 */
    -	if (attachrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
    -	{
    -		foreach(cell, idxes)
    -		{
    -			Oid			idx = lfirst_oid(cell);
    -			Relation	idxRel = index_open(idx, AccessShareLock);
    -
    -			if (idxRel->rd_index->indisunique ||
    -				idxRel->rd_index->indisprimary)
    -				ereport(ERROR,
    -						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    -						 errmsg("cannot attach foreign table \"%s\" as partition of partitioned table \"%s\"",
    -								RelationGetRelationName(attachrel),
    -								RelationGetRelationName(rel)),
    -						 errdetail("Partitioned table \"%s\" contains unique indexes.",
    -								   RelationGetRelationName(rel))));
    -			index_close(idxRel, AccessShareLock);
    -		}
    -
    -		goto out;
    -	}
    -
    -	/*
    -	 * For each index on the partitioned table, find a matching one in the
    -	 * partition-to-be; if one is not found, create one.
    -	 */
    -	foreach(cell, idxes)
    -	{
    -		Oid			idx = lfirst_oid(cell);
    -		Relation	idxRel = index_open(idx, AccessShareLock);
    -		IndexInfo  *info;
    -		AttrMap    *attmap;
    -		bool		found = false;
    -		Oid			constraintOid;
    -
    -		/*
    -		 * Ignore indexes in the partitioned table other than partitioned
    -		 * indexes.
    -		 */
    -		if (idxRel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
    -		{
    -			index_close(idxRel, AccessShareLock);
    -			continue;
    -		}
    -
    -		/* construct an indexinfo to compare existing indexes against */
    -		info = BuildIndexInfo(idxRel);
    -		attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
    -									   RelationGetDescr(rel),
    -									   false);
    -		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
    -
    -		/*
    -		 * Scan the list of existing indexes in the partition-to-be, and mark
    -		 * the first matching, valid, unattached one we find, if any, as
    -		 * partition of the parent index.  If we find one, we're done.
    -		 */
    -		for (int i = 0; i < list_length(attachRelIdxs); i++)
    -		{
    -			Oid			cldIdxId = RelationGetRelid(attachrelIdxRels[i]);
    -			Oid			cldConstrOid = InvalidOid;
    -
    -			/* does this index have a parent?  if so, can't use it */
    -			if (attachrelIdxRels[i]->rd_rel->relispartition)
    -				continue;
    -
    -			/* If this index is invalid, can't use it */
    -			if (!attachrelIdxRels[i]->rd_index->indisvalid)
    -				continue;
    -
    -			if (CompareIndexInfo(attachInfos[i], info,
    -								 attachrelIdxRels[i]->rd_indcollation,
    -								 idxRel->rd_indcollation,
    -								 attachrelIdxRels[i]->rd_opfamily,
    -								 idxRel->rd_opfamily,
    -								 attmap))
    -			{
    -				/*
    -				 * If this index is being created in the parent because of a
    -				 * constraint, then the child needs to have a constraint also,
    -				 * so look for one.  If there is no such constraint, this
    -				 * index is no good, so keep looking.
    -				 */
    -				if (OidIsValid(constraintOid))
    -				{
    -					cldConstrOid =
    -						get_relation_idx_constraint_oid(RelationGetRelid(attachrel),
    -														cldIdxId);
    -					/* no dice */
    -					if (!OidIsValid(cldConstrOid))
    -						continue;
    -
    -					/* Ensure they're both the same type of constraint */
    -					if (get_constraint_type(constraintOid) !=
    -						get_constraint_type(cldConstrOid))
    -						continue;
    -				}
    -
    -				/* bingo. */
    -				IndexSetParentIndex(attachrelIdxRels[i], idx);
    -				if (OidIsValid(constraintOid))
    -					ConstraintSetParentConstraint(cldConstrOid, constraintOid,
    -												  RelationGetRelid(attachrel));
    -				found = true;
    -
    -				CommandCounterIncrement();
    -				break;
    -			}
    -		}
    -
    -		/*
    -		 * If no suitable index was found in the partition-to-be, create one
    -		 * now.  Note that if this is a PK, not-null constraints must already
    -		 * exist.
    -		 */
    -		if (!found)
    -		{
    -			IndexStmt  *stmt;
    -			Oid			conOid;
    -
    -			stmt = generateClonedIndexStmt(NULL,
    -										   idxRel, attmap,
    -										   &conOid);
    -			DefineIndex(RelationGetRelid(attachrel), stmt, InvalidOid,
    -						RelationGetRelid(idxRel),
    -						conOid,
    -						-1,
    -						true, false, false, false, false);
    -		}
    -
    -		index_close(idxRel, AccessShareLock);
    -	}
    -
    -out:
    -	/* Clean up. */
    -	for (int i = 0; i < list_length(attachRelIdxs); i++)
    -		index_close(attachrelIdxRels[i], AccessShareLock);
    -	MemoryContextSwitchTo(oldcxt);
    -	MemoryContextDelete(cxt);
    -}
    -
    -/*
    - * CloneRowTriggersToPartition
    - *		subroutine for ATExecAttachPartition/DefineRelation to create row
    - *		triggers on partitions
    - */
    -static void
    -CloneRowTriggersToPartition(Relation parent, Relation partition)
    -{
    -	Relation	pg_trigger;
    -	ScanKeyData key;
    -	SysScanDesc scan;
    -	HeapTuple	tuple;
    -	MemoryContext perTupCxt;
    -
    -	ScanKeyInit(&key, Anum_pg_trigger_tgrelid, BTEqualStrategyNumber,
    -				F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(parent)));
    -	pg_trigger = table_open(TriggerRelationId, RowExclusiveLock);
    -	scan = systable_beginscan(pg_trigger, TriggerRelidNameIndexId,
    -							  true, NULL, 1, &key);
    -
    -	perTupCxt = AllocSetContextCreate(CurrentMemoryContext,
    -									  "clone trig", ALLOCSET_SMALL_SIZES);
    -
    -	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
    -	{
    -		Form_pg_trigger trigForm = (Form_pg_trigger) GETSTRUCT(tuple);
    -		CreateTrigStmt *trigStmt;
    -		Node	   *qual = NULL;
    -		Datum		value;
    -		bool		isnull;
    -		List	   *cols = NIL;
    -		List	   *trigargs = NIL;
    -		MemoryContext oldcxt;
    -
    -		/*
    -		 * Ignore statement-level triggers; those are not cloned.
    -		 */
    -		if (!TRIGGER_FOR_ROW(trigForm->tgtype))
    -			continue;
    -
    -		/*
    -		 * Don't clone internal triggers, because the constraint cloning code
    -		 * will.
    -		 */
    -		if (trigForm->tgisinternal)
    -			continue;
    -
    -		/*
    -		 * Complain if we find an unexpected trigger type.
    -		 */
    -		if (!TRIGGER_FOR_BEFORE(trigForm->tgtype) &&
    -			!TRIGGER_FOR_AFTER(trigForm->tgtype))
    -			elog(ERROR, "unexpected trigger \"%s\" found",
    -				 NameStr(trigForm->tgname));
    -
    -		/* Use short-lived context for CREATE TRIGGER */
    -		oldcxt = MemoryContextSwitchTo(perTupCxt);
    -
    -		/*
    -		 * If there is a WHEN clause, generate a 'cooked' version of it that's
    -		 * appropriate for the partition.
    -		 */
    -		value = heap_getattr(tuple, Anum_pg_trigger_tgqual,
    -							 RelationGetDescr(pg_trigger), &isnull);
    -		if (!isnull)
    -		{
    -			qual = stringToNode(TextDatumGetCString(value));
    -			qual = (Node *) map_partition_varattnos((List *) qual, PRS2_OLD_VARNO,
    -													partition, parent);
    -			qual = (Node *) map_partition_varattnos((List *) qual, PRS2_NEW_VARNO,
    -													partition, parent);
    -		}
    -
    -		/*
    -		 * If there is a column list, transform it to a list of column names.
    -		 * Note we don't need to map this list in any way ...
    -		 */
    -		if (trigForm->tgattr.dim1 > 0)
    -		{
    -			int			i;
    -
    -			for (i = 0; i < trigForm->tgattr.dim1; i++)
    -			{
    -				Form_pg_attribute col;
    -
    -				col = TupleDescAttr(parent->rd_att,
    -									trigForm->tgattr.values[i] - 1);
    -				cols = lappend(cols,
    -							   makeString(pstrdup(NameStr(col->attname))));
    -			}
    -		}
    -
    -		/* Reconstruct trigger arguments list. */
    -		if (trigForm->tgnargs > 0)
    -		{
    -			char	   *p;
    -
    -			value = heap_getattr(tuple, Anum_pg_trigger_tgargs,
    -								 RelationGetDescr(pg_trigger), &isnull);
    -			if (isnull)
    -				elog(ERROR, "tgargs is null for trigger \"%s\" in partition \"%s\"",
    -					 NameStr(trigForm->tgname), RelationGetRelationName(partition));
    -
    -			p = (char *) VARDATA_ANY(DatumGetByteaPP(value));
    -
    -			for (int i = 0; i < trigForm->tgnargs; i++)
    -			{
    -				trigargs = lappend(trigargs, makeString(pstrdup(p)));
    -				p += strlen(p) + 1;
    -			}
    -		}
    -
    -		trigStmt = makeNode(CreateTrigStmt);
    -		trigStmt->replace = false;
    -		trigStmt->isconstraint = OidIsValid(trigForm->tgconstraint);
    -		trigStmt->trigname = NameStr(trigForm->tgname);
    -		trigStmt->relation = NULL;
    -		trigStmt->funcname = NULL;	/* passed separately */
    -		trigStmt->args = trigargs;
    -		trigStmt->row = true;
    -		trigStmt->timing = trigForm->tgtype & TRIGGER_TYPE_TIMING_MASK;
    -		trigStmt->events = trigForm->tgtype & TRIGGER_TYPE_EVENT_MASK;
    -		trigStmt->columns = cols;
    -		trigStmt->whenClause = NULL;	/* passed separately */
    -		trigStmt->transitionRels = NIL; /* not supported at present */
    -		trigStmt->deferrable = trigForm->tgdeferrable;
    -		trigStmt->initdeferred = trigForm->tginitdeferred;
    -		trigStmt->constrrel = NULL; /* passed separately */
    -
    -		CreateTriggerFiringOn(trigStmt, NULL, RelationGetRelid(partition),
    -							  trigForm->tgconstrrelid, InvalidOid, InvalidOid,
    -							  trigForm->tgfoid, trigForm->oid, qual,
    -							  false, true, trigForm->tgenabled);
    -
    -		MemoryContextSwitchTo(oldcxt);
    -		MemoryContextReset(perTupCxt);
    -	}
    -
    -	MemoryContextDelete(perTupCxt);
    -
    -	systable_endscan(scan);
    -	table_close(pg_trigger, RowExclusiveLock);
    -}
    -
    -/*
    - * ALTER TABLE DETACH PARTITION
    - *
    - * Return the address of the relation that is no longer a partition of rel.
    - *
    - * If concurrent mode is requested, we run in two transactions.  A side-
    - * effect is that this command cannot run in a multi-part ALTER TABLE.
    - * Currently, that's enforced by the grammar.
    - *
    - * The strategy for concurrency is to first modify the partition's
    - * pg_inherit catalog row to make it visible to everyone that the
    - * partition is detached, lock the partition against writes, and commit
    - * the transaction; anyone who requests the partition descriptor from
    - * that point onwards has to ignore such a partition.  In a second
    - * transaction, we wait until all transactions that could have seen the
    - * partition as attached are gone, then we remove the rest of partition
    - * metadata (pg_inherits and pg_class.relpartbounds).
    - */
    -static ObjectAddress
    -ATExecDetachPartition(List **wqueue, AlteredTableInfo *tab, Relation rel,
    -					  RangeVar *name, bool concurrent)
    -{
    -	Relation	partRel;
    -	ObjectAddress address;
    -	Oid			defaultPartOid;
    -
    -	/*
    -	 * We must lock the default partition, because detaching this partition
    -	 * will change its partition constraint.
    -	 */
    -	defaultPartOid =
    -		get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true));
    -	if (OidIsValid(defaultPartOid))
    -	{
    -		/*
    -		 * Concurrent detaching when a default partition exists is not
    -		 * supported. The main problem is that the default partition
    -		 * constraint would change.  And there's a definitional problem: what
    -		 * should happen to the tuples that are being inserted that belong to
    -		 * the partition being detached?  Putting them on the partition being
    -		 * detached would be wrong, since they'd become "lost" after the
    -		 * detaching completes but we cannot put them in the default partition
    -		 * either until we alter its partition constraint.
    -		 *
    -		 * I think we could solve this problem if we effected the constraint
    -		 * change before committing the first transaction.  But the lock would
    -		 * have to remain AEL and it would cause concurrent query planning to
    -		 * be blocked, so changing it that way would be even worse.
    -		 */
    -		if (concurrent)
    -			ereport(ERROR,
    -					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    -					 errmsg("cannot detach partitions concurrently when a default partition exists")));
    -		LockRelationOid(defaultPartOid, AccessExclusiveLock);
    -	}
    -
    -	/*
    -	 * In concurrent mode, the partition is locked with share-update-exclusive
    -	 * in the first transaction.  This allows concurrent transactions to be
    -	 * doing DML to the partition.
    -	 */
    -	partRel = table_openrv(name, concurrent ? ShareUpdateExclusiveLock :
    -						   AccessExclusiveLock);
    -
    -	/*
    -	 * Check inheritance conditions and either delete the pg_inherits row (in
    -	 * non-concurrent mode) or just set the inhdetachpending flag.
    -	 */
    -	if (!concurrent)
    -		RemoveInheritance(partRel, rel, false);
    -	else
    -		MarkInheritDetached(partRel, rel);
    -
    -	/*
    -	 * Ensure that foreign keys still hold after this detach.  This keeps
    -	 * locks on the referencing tables, which prevents concurrent transactions
    -	 * from adding rows that we wouldn't see.  For this to work in concurrent
    -	 * mode, it is critical that the partition appears as no longer attached
    -	 * for the RI queries as soon as the first transaction commits.
    -	 */
    -	ATDetachCheckNoForeignKeyRefs(partRel);
    -
    -	/*
    -	 * Concurrent mode has to work harder; first we add a new constraint to
    -	 * the partition that matches the partition constraint.  Then we close our
    -	 * existing transaction, and in a new one wait for all processes to catch
    -	 * up on the catalog updates we've done so far; at that point we can
    -	 * complete the operation.
    -	 */
    -	if (concurrent)
    -	{
    -		Oid			partrelid,
    -					parentrelid;
    -		LOCKTAG		tag;
    -		char	   *parentrelname;
    -		char	   *partrelname;
    -
    -		/*
    -		 * We're almost done now; the only traces that remain are the
    -		 * pg_inherits tuple and the partition's relpartbounds.  Before we can
    -		 * remove those, we need to wait until all transactions that know that
    -		 * this is a partition are gone.
    -		 */
    -
    -		/*
    -		 * Remember relation OIDs to re-acquire them later; and relation names
    -		 * too, for error messages if something is dropped in between.
    -		 */
    -		partrelid = RelationGetRelid(partRel);
    -		parentrelid = RelationGetRelid(rel);
    -		parentrelname = MemoryContextStrdup(PortalContext,
    -											RelationGetRelationName(rel));
    -		partrelname = MemoryContextStrdup(PortalContext,
    -										  RelationGetRelationName(partRel));
    -
    -		/* Invalidate relcache entries for the parent -- must be before close */
    -		CacheInvalidateRelcache(rel);
    -
    -		table_close(partRel, NoLock);
    -		table_close(rel, NoLock);
    -		tab->rel = NULL;
    -
    -		/* Make updated catalog entry visible */
    -		PopActiveSnapshot();
    -		CommitTransactionCommand();
    -
    -		StartTransactionCommand();
    -
    -		/*
    -		 * Now wait.  This ensures that all queries that were planned
    -		 * including the partition are finished before we remove the rest of
    -		 * catalog entries.  We don't need or indeed want to acquire this
    -		 * lock, though -- that would block later queries.
    -		 *
    -		 * We don't need to concern ourselves with waiting for a lock on the
    -		 * partition itself, since we will acquire AccessExclusiveLock below.
    -		 */
    -		SET_LOCKTAG_RELATION(tag, MyDatabaseId, parentrelid);
    -		WaitForLockersMultiple(list_make1(&tag), AccessExclusiveLock, false);
    -
    -		/*
    -		 * Now acquire locks in both relations again.  Note they may have been
    -		 * removed in the meantime, so care is required.
    -		 */
    -		rel = try_relation_open(parentrelid, ShareUpdateExclusiveLock);
    -		partRel = try_relation_open(partrelid, AccessExclusiveLock);
    -
    -		/* If the relations aren't there, something bad happened; bail out */
    -		if (rel == NULL)
    -		{
    -			if (partRel != NULL)	/* shouldn't happen */
    -				elog(WARNING, "dangling partition \"%s\" remains, can't fix",
    -					 partrelname);
    -			ereport(ERROR,
    -					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    -					 errmsg("partitioned table \"%s\" was removed concurrently",
    -							parentrelname)));
    -		}
    -		if (partRel == NULL)
    -			ereport(ERROR,
    -					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    -					 errmsg("partition \"%s\" was removed concurrently", partrelname)));
    -
    -		tab->rel = rel;
    -	}
    -
    -	/*
    -	 * Detaching the partition might involve TOAST table access, so ensure we
    -	 * have a valid snapshot.
    -	 */
    -	PushActiveSnapshot(GetTransactionSnapshot());
    -
    -	/* Do the final part of detaching */
    -	DetachPartitionFinalize(rel, partRel, concurrent, defaultPartOid);
    -
    -	PopActiveSnapshot();
    -
    -	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
    -
    -	/* keep our lock until commit */
    -	table_close(partRel, NoLock);
    -
    -	return address;
    -}
    -
    -/*
    - * Second part of ALTER TABLE .. DETACH.
    - *
    - * This is separate so that it can be run independently when the second
    - * transaction of the concurrent algorithm fails (crash or abort).
    - */
    -static void
    -DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
    -						Oid defaultPartOid)
    -{
    -	Relation	classRel;
    -	List	   *fks;
    -	ListCell   *cell;
    -	List	   *indexes;
    -	Datum		new_val[Natts_pg_class];
    -	bool		new_null[Natts_pg_class],
    -				new_repl[Natts_pg_class];
    -	HeapTuple	tuple,
    -				newtuple;
    -	Relation	trigrel = NULL;
    -	List	   *fkoids = NIL;
    -
    -	if (concurrent)
    -	{
    -		/*
    -		 * We can remove the pg_inherits row now. (In the non-concurrent case,
    -		 * this was already done).
    -		 */
    -		RemoveInheritance(partRel, rel, true);
    -	}
    -
    -	/* Drop any triggers that were cloned on creation/attach. */
    -	DropClonedTriggersFromPartition(RelationGetRelid(partRel));
    -
    -	/*
    -	 * Detach any foreign keys that are inherited.  This includes creating
    -	 * additional action triggers.
    -	 */
    -	fks = copyObject(RelationGetFKeyList(partRel));
    -	if (fks != NIL)
    -		trigrel = table_open(TriggerRelationId, RowExclusiveLock);
    -
    -	/*
    -	 * It's possible that the partition being detached has a foreign key that
    -	 * references a partitioned table.  When that happens, there are multiple
    -	 * pg_constraint rows for the partition: one points to the partitioned
    -	 * table itself, while the others point to each of its partitions.  Only
    -	 * the topmost one is to be considered here; the child constraints must be
    -	 * left alone, because conceptually those aren't coming from our parent
    -	 * partitioned table, but from this partition itself.
    -	 *
    -	 * We implement this by collecting all the constraint OIDs in a first scan
    -	 * of the FK array, and skipping in the loop below those constraints whose
    -	 * parents are listed here.
    -	 */
    -	foreach_node(ForeignKeyCacheInfo, fk, fks)
    -		fkoids = lappend_oid(fkoids, fk->conoid);
    -
    -	foreach(cell, fks)
    -	{
    -		ForeignKeyCacheInfo *fk = lfirst(cell);
    -		HeapTuple	contup;
    -		Form_pg_constraint conform;
    -
    -		contup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
    -		if (!HeapTupleIsValid(contup))
    -			elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
    -		conform = (Form_pg_constraint) GETSTRUCT(contup);
    -
    -		/*
    -		 * Consider only inherited foreign keys, and only if their parents
    -		 * aren't in the list.
    -		 */
    -		if (conform->contype != CONSTRAINT_FOREIGN ||
    -			!OidIsValid(conform->conparentid) ||
    -			list_member_oid(fkoids, conform->conparentid))
    -		{
    -			ReleaseSysCache(contup);
    -			continue;
    -		}
    -
    -		/*
    -		 * The constraint on this table must be marked no longer a child of
    -		 * the parent's constraint, as do its check triggers.
    -		 */
    -		ConstraintSetParentConstraint(fk->conoid, InvalidOid, InvalidOid);
    -
    -		/*
    -		 * Also, look up the partition's "check" triggers corresponding to the
    -		 * ENFORCED constraint being detached and detach them from the parent
    -		 * triggers. NOT ENFORCED constraints do not have these triggers;
    -		 * therefore, this step is not needed.
    -		 */
    -		if (fk->conenforced)
    -		{
    -			Oid			insertTriggerOid,
    -						updateTriggerOid;
    -
    -			GetForeignKeyCheckTriggers(trigrel,
    -									   fk->conoid, fk->confrelid, fk->conrelid,
    -									   &insertTriggerOid, &updateTriggerOid);
    -			Assert(OidIsValid(insertTriggerOid));
    -			TriggerSetParentTrigger(trigrel, insertTriggerOid, InvalidOid,
    -									RelationGetRelid(partRel));
    -			Assert(OidIsValid(updateTriggerOid));
    -			TriggerSetParentTrigger(trigrel, updateTriggerOid, InvalidOid,
    -									RelationGetRelid(partRel));
    -		}
    -
    -		/*
    -		 * Lastly, create the action triggers on the referenced table, using
    -		 * addFkRecurseReferenced, which requires some elaborate setup (so put
    -		 * it in a separate block).  While at it, if the table is partitioned,
    -		 * that function will recurse to create the pg_constraint rows and
    -		 * action triggers for each partition.
    -		 *
    -		 * Note there's no need to do addFkConstraint() here, because the
    -		 * pg_constraint row already exists.
    -		 */
    -		{
    -			Constraint *fkconstraint;
    -			int			numfks;
    -			AttrNumber	conkey[INDEX_MAX_KEYS];
    -			AttrNumber	confkey[INDEX_MAX_KEYS];
    -			Oid			conpfeqop[INDEX_MAX_KEYS];
    -			Oid			conppeqop[INDEX_MAX_KEYS];
    -			Oid			conffeqop[INDEX_MAX_KEYS];
    -			int			numfkdelsetcols;
    -			AttrNumber	confdelsetcols[INDEX_MAX_KEYS];
    -			Relation	refdRel;
    -
    -			DeconstructFkConstraintRow(contup,
    -									   &numfks,
    -									   conkey,
    -									   confkey,
    -									   conpfeqop,
    -									   conppeqop,
    -									   conffeqop,
    -									   &numfkdelsetcols,
    -									   confdelsetcols);
    -
    -			/* Create a synthetic node we'll use throughout */
    -			fkconstraint = makeNode(Constraint);
    -			fkconstraint->contype = CONSTRAINT_FOREIGN;
    -			fkconstraint->conname = pstrdup(NameStr(conform->conname));
    -			fkconstraint->deferrable = conform->condeferrable;
    -			fkconstraint->initdeferred = conform->condeferred;
    -			fkconstraint->is_enforced = conform->conenforced;
    -			fkconstraint->skip_validation = true;
    -			fkconstraint->initially_valid = conform->convalidated;
    -			/* a few irrelevant fields omitted here */
    -			fkconstraint->pktable = NULL;
    -			fkconstraint->fk_attrs = NIL;
    -			fkconstraint->pk_attrs = NIL;
    -			fkconstraint->fk_matchtype = conform->confmatchtype;
    -			fkconstraint->fk_upd_action = conform->confupdtype;
    -			fkconstraint->fk_del_action = conform->confdeltype;
    -			fkconstraint->fk_del_set_cols = NIL;
    -			fkconstraint->old_conpfeqop = NIL;
    -			fkconstraint->old_pktable_oid = InvalidOid;
    -			fkconstraint->location = -1;
    -
    -			/* set up colnames, used to generate the constraint name */
    -			for (int i = 0; i < numfks; i++)
    -			{
    -				Form_pg_attribute att;
    -
    -				att = TupleDescAttr(RelationGetDescr(partRel),
    -									conkey[i] - 1);
    -
    -				fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs,
    -												 makeString(NameStr(att->attname)));
    -			}
    -
    -			refdRel = table_open(fk->confrelid, ShareRowExclusiveLock);
    -
    -			addFkRecurseReferenced(fkconstraint, partRel,
    -								   refdRel,
    -								   conform->conindid,
    -								   fk->conoid,
    -								   numfks,
    -								   confkey,
    -								   conkey,
    -								   conpfeqop,
    -								   conppeqop,
    -								   conffeqop,
    -								   numfkdelsetcols,
    -								   confdelsetcols,
    -								   true,
    -								   InvalidOid, InvalidOid,
    -								   conform->conperiod);
    -			table_close(refdRel, NoLock);	/* keep lock till end of xact */
    -		}
    -
    -		ReleaseSysCache(contup);
    -	}
    -	list_free_deep(fks);
    -	if (trigrel)
    -		table_close(trigrel, RowExclusiveLock);
    -
    -	/*
    -	 * Any sub-constraints that are in the referenced-side of a larger
    -	 * constraint have to be removed.  This partition is no longer part of the
    -	 * key space of the constraint.
    -	 */
    -	foreach(cell, GetParentedForeignKeyRefs(partRel))
    -	{
    -		Oid			constrOid = lfirst_oid(cell);
    -		ObjectAddress constraint;
    -
    -		ConstraintSetParentConstraint(constrOid, InvalidOid, InvalidOid);
    -		deleteDependencyRecordsForClass(ConstraintRelationId,
    -										constrOid,
    -										ConstraintRelationId,
    -										DEPENDENCY_INTERNAL);
    -		CommandCounterIncrement();
    -
    -		ObjectAddressSet(constraint, ConstraintRelationId, constrOid);
    -		performDeletion(&constraint, DROP_RESTRICT, 0);
    -	}
    -
    -	/* Now we can detach indexes */
    -	indexes = RelationGetIndexList(partRel);
    -	foreach(cell, indexes)
    -	{
    -		Oid			idxid = lfirst_oid(cell);
    -		Oid			parentidx;
    -		Relation	idx;
    -		Oid			constrOid;
    -		Oid			parentConstrOid;
    -
    -		if (!has_superclass(idxid))
    -			continue;
    -
    -		parentidx = get_partition_parent(idxid, false);
    -		Assert((IndexGetRelation(parentidx, false) == RelationGetRelid(rel)));
    -
    -		idx = index_open(idxid, AccessExclusiveLock);
    -		IndexSetParentIndex(idx, InvalidOid);
    -
    -		/*
    -		 * If there's a constraint associated with the index, detach it too.
    -		 * Careful: it is possible for a constraint index in a partition to be
    -		 * the child of a non-constraint index, so verify whether the parent
    -		 * index does actually have a constraint.
    -		 */
    -		constrOid = get_relation_idx_constraint_oid(RelationGetRelid(partRel),
    -													idxid);
    -		parentConstrOid = get_relation_idx_constraint_oid(RelationGetRelid(rel),
    -														  parentidx);
    -		if (OidIsValid(parentConstrOid) && OidIsValid(constrOid))
    -			ConstraintSetParentConstraint(constrOid, InvalidOid, InvalidOid);
    -
    -		index_close(idx, NoLock);
    -	}
    -
    -	/* Update pg_class tuple */
    -	classRel = table_open(RelationRelationId, RowExclusiveLock);
    -	tuple = SearchSysCacheCopy1(RELOID,
    -								ObjectIdGetDatum(RelationGetRelid(partRel)));
    -	if (!HeapTupleIsValid(tuple))
    -		elog(ERROR, "cache lookup failed for relation %u",
    -			 RelationGetRelid(partRel));
    -	Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
    -
    -	/* Clear relpartbound and reset relispartition */
    -	memset(new_val, 0, sizeof(new_val));
    -	memset(new_null, false, sizeof(new_null));
    -	memset(new_repl, false, sizeof(new_repl));
    -	new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
    -	new_null[Anum_pg_class_relpartbound - 1] = true;
    -	new_repl[Anum_pg_class_relpartbound - 1] = true;
    -	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
    -								 new_val, new_null, new_repl);
    -
    -	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
    -	CatalogTupleUpdate(classRel, &newtuple->t_self, newtuple);
    -	heap_freetuple(newtuple);
    -	table_close(classRel, RowExclusiveLock);
    -
    -	/*
    -	 * Drop identity property from all identity columns of partition.
    -	 */
    -	for (int attno = 0; attno < RelationGetNumberOfAttributes(partRel); attno++)
    -	{
    -		Form_pg_attribute attr = TupleDescAttr(partRel->rd_att, attno);
    -
    -		if (!attr->attisdropped && attr->attidentity)
    -			ATExecDropIdentity(partRel, NameStr(attr->attname), false,
    -							   AccessExclusiveLock, true, true);
    -	}
    -
    -	if (OidIsValid(defaultPartOid))
    -	{
    -		/*
    -		 * If the relation being detached is the default partition itself,
    -		 * remove it from the parent's pg_partitioned_table entry.
    -		 *
    -		 * If not, we must invalidate default partition's relcache entry, as
    -		 * in StorePartitionBound: its partition constraint depends on every
    -		 * other partition's partition constraint.
    -		 */
    -		if (RelationGetRelid(partRel) == defaultPartOid)
    -			update_default_partition_oid(RelationGetRelid(rel), InvalidOid);
    -		else
    -			CacheInvalidateRelcacheByRelid(defaultPartOid);
    -	}
    -
    -	/*
    -	 * Invalidate the parent's relcache so that the partition is no longer
    -	 * included in its partition descriptor.
    -	 */
    -	CacheInvalidateRelcache(rel);
    -
    -	/*
    -	 * If the partition we just detached is partitioned itself, invalidate
    -	 * relcache for all descendent partitions too to ensure that their
    -	 * rd_partcheck expression trees are rebuilt; must lock partitions before
    -	 * doing so, using the same lockmode as what partRel has been locked with
    -	 * by the caller.
    -	 */
    -	if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
    -	{
    -		List	   *children;
    -
    -		children = find_all_inheritors(RelationGetRelid(partRel),
    -									   AccessExclusiveLock, NULL);
    -		foreach(cell, children)
    -		{
    -			CacheInvalidateRelcacheByRelid(lfirst_oid(cell));
    -		}
    -	}
    -}
    -
    -/*
    - * ALTER TABLE ... DETACH PARTITION ... FINALIZE
    - *
    - * To use when a DETACH PARTITION command previously did not run to
    - * completion; this completes the detaching process.
    - */
    -static ObjectAddress
    -ATExecDetachPartitionFinalize(Relation rel, RangeVar *name)
    -{
    -	Relation	partRel;
    -	ObjectAddress address;
    -	Snapshot	snap = GetActiveSnapshot();
    -
    -	partRel = table_openrv(name, AccessExclusiveLock);
    -
    -	/*
    -	 * Wait until existing snapshots are gone.  This is important if the
    -	 * second transaction of DETACH PARTITION CONCURRENTLY is canceled: the
    -	 * user could immediately run DETACH FINALIZE without actually waiting for
    -	 * existing transactions.  We must not complete the detach action until
    -	 * all such queries are complete (otherwise we would present them with an
    -	 * inconsistent view of catalogs).
    -	 */
    -	WaitForOlderSnapshots(snap->xmin, false);
    -
    -	DetachPartitionFinalize(rel, partRel, true, InvalidOid);
    -
    -	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
    -
    -	table_close(partRel, NoLock);
    -
    -	return address;
    -}
    -
    -/*
    - * DropClonedTriggersFromPartition
    - *		subroutine for ATExecDetachPartition to remove any triggers that were
    - *		cloned to the partition when it was created-as-partition or attached.
    - *		This undoes what CloneRowTriggersToPartition did.
    - */
    -static void
    -DropClonedTriggersFromPartition(Oid partitionId)
    -{
    -	ScanKeyData skey;
    -	SysScanDesc scan;
    -	HeapTuple	trigtup;
    -	Relation	tgrel;
    -	ObjectAddresses *objects;
    -
    -	objects = new_object_addresses();
    -
    -	/*
    -	 * Scan pg_trigger to search for all triggers on this rel.
    -	 */
    -	ScanKeyInit(&skey, Anum_pg_trigger_tgrelid, BTEqualStrategyNumber,
    -				F_OIDEQ, ObjectIdGetDatum(partitionId));
    -	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
    -	scan = systable_beginscan(tgrel, TriggerRelidNameIndexId,
    -							  true, NULL, 1, &skey);
    -	while (HeapTupleIsValid(trigtup = systable_getnext(scan)))
    -	{
    -		Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(trigtup);
    -		ObjectAddress trig;
    -
    -		/* Ignore triggers that weren't cloned */
    -		if (!OidIsValid(pg_trigger->tgparentid))
    -			continue;
    -
    -		/*
    -		 * Ignore internal triggers that are implementation objects of foreign
    -		 * keys, because these will be detached when the foreign keys
    -		 * themselves are.
    -		 */
    -		if (OidIsValid(pg_trigger->tgconstrrelid))
    -			continue;
    -
    -		/*
    -		 * This is ugly, but necessary: remove the dependency markings on the
    -		 * trigger so that it can be removed.
    -		 */
    -		deleteDependencyRecordsForClass(TriggerRelationId, pg_trigger->oid,
    -										TriggerRelationId,
    -										DEPENDENCY_PARTITION_PRI);
    -		deleteDependencyRecordsForClass(TriggerRelationId, pg_trigger->oid,
    -										RelationRelationId,
    -										DEPENDENCY_PARTITION_SEC);
    -
    -		/* remember this trigger to remove it below */
    -		ObjectAddressSet(trig, TriggerRelationId, pg_trigger->oid);
    -		add_exact_object_address(&trig, objects);
    -	}
    -
    -	/* make the dependency removal visible to the deletion below */
    -	CommandCounterIncrement();
    -	performMultipleDeletions(objects, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
    -
    -	/* done */
    -	free_object_addresses(objects);
    -	systable_endscan(scan);
    -	table_close(tgrel, RowExclusiveLock);
    -}
    -
    -/*
    - * Before acquiring lock on an index, acquire the same lock on the owning
    - * table.
    - */
    -struct AttachIndexCallbackState
    -{
    -	Oid			partitionOid;
    -	Oid			parentTblOid;
    -	bool		lockedParentTbl;
    -};
    -
    -static void
    -RangeVarCallbackForAttachIndex(const RangeVar *rv, Oid relOid, Oid oldRelOid,
    -							   void *arg)
    -{
    -	struct AttachIndexCallbackState *state;
    -	Form_pg_class classform;
    -	HeapTuple	tuple;
    -
    -	state = (struct AttachIndexCallbackState *) arg;
    -
    -	if (!state->lockedParentTbl)
    -	{
    -		LockRelationOid(state->parentTblOid, AccessShareLock);
    -		state->lockedParentTbl = true;
    -	}
    -
    -	/*
    -	 * If we previously locked some other heap, and the name we're looking up
    -	 * no longer refers to an index on that relation, release the now-useless
    -	 * lock.  XXX maybe we should do *after* we verify whether the index does
    -	 * not actually belong to the same relation ...
    -	 */
    -	if (relOid != oldRelOid && OidIsValid(state->partitionOid))
    -	{
    -		UnlockRelationOid(state->partitionOid, AccessShareLock);
    -		state->partitionOid = InvalidOid;
    -	}
    -
    -	/* Didn't find a relation, so no need for locking or permission checks. */
    -	if (!OidIsValid(relOid))
    -		return;
    -
    -	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
    -	if (!HeapTupleIsValid(tuple))
    -		return;					/* concurrently dropped, so nothing to do */
    -	classform = (Form_pg_class) GETSTRUCT(tuple);
    -	if (classform->relkind != RELKIND_PARTITIONED_INDEX &&
    -		classform->relkind != RELKIND_INDEX)
    -		ereport(ERROR,
    -				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
    -				 errmsg("\"%s\" is not an index", rv->relname)));
    -	ReleaseSysCache(tuple);
    -
    -	/*
    -	 * Since we need only examine the heap's tupledesc, an access share lock
    -	 * on it (preventing any DDL) is sufficient.
    -	 */
    -	state->partitionOid = IndexGetRelation(relOid, false);
    -	LockRelationOid(state->partitionOid, AccessShareLock);
    -}
    -
    -/*
    - * ALTER INDEX i1 ATTACH PARTITION i2
    - */
    -static ObjectAddress
    -ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
    -{
    -	Relation	partIdx;
    -	Relation	partTbl;
    -	Relation	parentTbl;
    -	ObjectAddress address;
    -	Oid			partIdxId;
    -	Oid			currParent;
    -	struct AttachIndexCallbackState state;
    -
    -	/*
    -	 * We need to obtain lock on the index 'name' to modify it, but we also
    -	 * need to read its owning table's tuple descriptor -- so we need to lock
    -	 * both.  To avoid deadlocks, obtain lock on the table before doing so on
    -	 * the index.  Furthermore, we need to examine the parent table of the
    -	 * partition, so lock that one too.
    -	 */
    -	state.partitionOid = InvalidOid;
    -	state.parentTblOid = parentIdx->rd_index->indrelid;
    -	state.lockedParentTbl = false;
    -	partIdxId =
    -		RangeVarGetRelidExtended(name, AccessExclusiveLock, 0,
    -								 RangeVarCallbackForAttachIndex,
    -								 &state);
    -	/* Not there? */
    -	if (!OidIsValid(partIdxId))
    -		ereport(ERROR,
    -				(errcode(ERRCODE_UNDEFINED_OBJECT),
    -				 errmsg("index \"%s\" does not exist", name->relname)));
    -
    -	/* no deadlock risk: RangeVarGetRelidExtended already acquired the lock */
    -	partIdx = relation_open(partIdxId, AccessExclusiveLock);
    -
    -	/* we already hold locks on both tables, so this is safe: */
    -	parentTbl = relation_open(parentIdx->rd_index->indrelid, AccessShareLock);
    -	partTbl = relation_open(partIdx->rd_index->indrelid, NoLock);
    -
    -	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partIdx));
    -
    -	/* Silently do nothing if already in the right state */
    -	currParent = partIdx->rd_rel->relispartition ?
    -		get_partition_parent(partIdxId, false) : InvalidOid;
    -	if (currParent != RelationGetRelid(parentIdx))
    -	{
    -		IndexInfo  *childInfo;
    -		IndexInfo  *parentInfo;
    -		AttrMap    *attmap;
    -		bool		found;
    -		int			i;
    -		PartitionDesc partDesc;
    -		Oid			constraintOid,
    -					cldConstrId = InvalidOid;
    -
    -		/*
    -		 * If this partition already has an index attached, refuse the
    -		 * operation.
    -		 */
    -		refuseDupeIndexAttach(parentIdx, partIdx, partTbl);
    -
    -		if (OidIsValid(currParent))
    -			ereport(ERROR,
    -					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    -					 errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
    -							RelationGetRelationName(partIdx),
    -							RelationGetRelationName(parentIdx)),
    -					 errdetail("Index \"%s\" is already attached to another index.",
    -							   RelationGetRelationName(partIdx))));
    -
    -		/* Make sure it indexes a partition of the other index's table */
    -		partDesc = RelationGetPartitionDesc(parentTbl, true);
    -		found = false;
    -		for (i = 0; i < partDesc->nparts; i++)
    -		{
    -			if (partDesc->oids[i] == state.partitionOid)
    -			{
    -				found = true;
    -				break;
    -			}
    -		}
    -		if (!found)
    -			ereport(ERROR,
    -					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    -					 errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
    -							RelationGetRelationName(partIdx),
    -							RelationGetRelationName(parentIdx)),
    -					 errdetail("Index \"%s\" is not an index on any partition of table \"%s\".",
    -							   RelationGetRelationName(partIdx),
    -							   RelationGetRelationName(parentTbl))));
    -
    -		/* Ensure the indexes are compatible */
    -		childInfo = BuildIndexInfo(partIdx);
    -		parentInfo = BuildIndexInfo(parentIdx);
    -		attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
    -									   RelationGetDescr(parentTbl),
    -									   false);
    -		if (!CompareIndexInfo(childInfo, parentInfo,
    -							  partIdx->rd_indcollation,
    -							  parentIdx->rd_indcollation,
    -							  partIdx->rd_opfamily,
    -							  parentIdx->rd_opfamily,
    -							  attmap))
    -			ereport(ERROR,
    -					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
    -					 errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
    -							RelationGetRelationName(partIdx),
    -							RelationGetRelationName(parentIdx)),
    -					 errdetail("The index definitions do not match.")));
    -
    -		/*
    -		 * If there is a constraint in the parent, make sure there is one in
    -		 * the child too.
    -		 */
    -		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(parentTbl),
    -														RelationGetRelid(parentIdx));
    -
    -		if (OidIsValid(constraintOid))
    -		{
    -			cldConstrId = get_relation_idx_constraint_oid(RelationGetRelid(partTbl),
    -														  partIdxId);
    -			if (!OidIsValid(cldConstrId))
    -				ereport(ERROR,
    -						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
    -						 errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
    -								RelationGetRelationName(partIdx),
    -								RelationGetRelationName(parentIdx)),
    -						 errdetail("The index \"%s\" belongs to a constraint in table \"%s\" but no constraint exists for index \"%s\".",
    -								   RelationGetRelationName(parentIdx),
    -								   RelationGetRelationName(parentTbl),
    -								   RelationGetRelationName(partIdx))));
    -		}
    -
    -		/*
    -		 * If it's a primary key, make sure the columns in the partition are
    -		 * NOT NULL.
    -		 */
    -		if (parentIdx->rd_index->indisprimary)
    -			verifyPartitionIndexNotNull(childInfo, partTbl);
    -
    -		/* All good -- do it */
    -		IndexSetParentIndex(partIdx, RelationGetRelid(parentIdx));
    -		if (OidIsValid(constraintOid))
    -			ConstraintSetParentConstraint(cldConstrId, constraintOid,
    -										  RelationGetRelid(partTbl));
    -
    -		free_attrmap(attmap);
    -
    -		validatePartitionedIndex(parentIdx, parentTbl);
    -	}
    -
    -	relation_close(parentTbl, AccessShareLock);
    -	/* keep these locks till commit */
    -	relation_close(partTbl, NoLock);
    -	relation_close(partIdx, NoLock);
    -
    -	return address;
    -}
    -
    -/*
    - * Verify whether the given partition already contains an index attached
    - * to the given partitioned index.  If so, raise an error.
    - */
    -static void
    -refuseDupeIndexAttach(Relation parentIdx, Relation partIdx, Relation partitionTbl)
    -{
    -	Oid			existingIdx;
    -
    -	existingIdx = index_get_partition(partitionTbl,
    -									  RelationGetRelid(parentIdx));
    -	if (OidIsValid(existingIdx))
    -		ereport(ERROR,
    -				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    -				 errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
    -						RelationGetRelationName(partIdx),
    -						RelationGetRelationName(parentIdx)),
    -				 errdetail("Another index \"%s\" is already attached for partition \"%s\".",
    -						   get_rel_name(existingIdx),
    -						   RelationGetRelationName(partitionTbl))));
    -}
    -
    -/*
    - * Verify whether the set of attached partition indexes to a parent index on
    - * a partitioned table is complete.  If it is, mark the parent index valid.
    - *
    - * This should be called each time a partition index is attached.
    - */
    -static void
    -validatePartitionedIndex(Relation partedIdx, Relation partedTbl)
    -{
    -	Relation	inheritsRel;
    -	SysScanDesc scan;
    -	ScanKeyData key;
    -	int			tuples = 0;
    -	HeapTuple	inhTup;
    -	bool		updated = false;
    -
    -	Assert(partedIdx->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
    -
    -	/*
    -	 * Scan pg_inherits for this parent index.  Count each valid index we find
    -	 * (verifying the pg_index entry for each), and if we reach the total
    -	 * amount we expect, we can mark this parent index as valid.
    -	 */
    -	inheritsRel = table_open(InheritsRelationId, AccessShareLock);
    -	ScanKeyInit(&key, Anum_pg_inherits_inhparent,
    -				BTEqualStrategyNumber, F_OIDEQ,
    -				ObjectIdGetDatum(RelationGetRelid(partedIdx)));
    -	scan = systable_beginscan(inheritsRel, InheritsParentIndexId, true,
    -							  NULL, 1, &key);
    -	while ((inhTup = systable_getnext(scan)) != NULL)
    -	{
    -		Form_pg_inherits inhForm = (Form_pg_inherits) GETSTRUCT(inhTup);
    -		HeapTuple	indTup;
    -		Form_pg_index indexForm;
    -
    -		indTup = SearchSysCache1(INDEXRELID,
    -								 ObjectIdGetDatum(inhForm->inhrelid));
    -		if (!HeapTupleIsValid(indTup))
    -			elog(ERROR, "cache lookup failed for index %u", inhForm->inhrelid);
    -		indexForm = (Form_pg_index) GETSTRUCT(indTup);
    -		if (indexForm->indisvalid)
    -			tuples += 1;
    -		ReleaseSysCache(indTup);
    -	}
    -
    -	/* Done with pg_inherits */
    -	systable_endscan(scan);
    -	table_close(inheritsRel, AccessShareLock);
    -
    -	/*
    -	 * If we found as many inherited indexes as the partitioned table has
    -	 * partitions, we're good; update pg_index to set indisvalid.
    -	 */
    -	if (tuples == RelationGetPartitionDesc(partedTbl, true)->nparts)
    -	{
    -		Relation	idxRel;
    -		HeapTuple	indTup;
    -		Form_pg_index indexForm;
    -
    -		idxRel = table_open(IndexRelationId, RowExclusiveLock);
    -		indTup = SearchSysCacheCopy1(INDEXRELID,
    -									 ObjectIdGetDatum(RelationGetRelid(partedIdx)));
    -		if (!HeapTupleIsValid(indTup))
    -			elog(ERROR, "cache lookup failed for index %u",
    -				 RelationGetRelid(partedIdx));
    -		indexForm = (Form_pg_index) GETSTRUCT(indTup);
    -
    -		indexForm->indisvalid = true;
    -		updated = true;
    -
    -		CatalogTupleUpdate(idxRel, &indTup->t_self, indTup);
    -
    -		table_close(idxRel, RowExclusiveLock);
    -		heap_freetuple(indTup);
    -	}
    -
    -	/*
    -	 * If this index is in turn a partition of a larger index, validating it
    -	 * might cause the parent to become valid also.  Try that.
    -	 */
    -	if (updated && partedIdx->rd_rel->relispartition)
    -	{
    -		Oid			parentIdxId,
    -					parentTblId;
    -		Relation	parentIdx,
    -					parentTbl;
    -
    -		/* make sure we see the validation we just did */
    -		CommandCounterIncrement();
    -
    -		parentIdxId = get_partition_parent(RelationGetRelid(partedIdx), false);
    -		parentTblId = get_partition_parent(RelationGetRelid(partedTbl), false);
    -		parentIdx = relation_open(parentIdxId, AccessExclusiveLock);
    -		parentTbl = relation_open(parentTblId, AccessExclusiveLock);
    -		Assert(!parentIdx->rd_index->indisvalid);
    -
    -		validatePartitionedIndex(parentIdx, parentTbl);
    -
    -		relation_close(parentIdx, AccessExclusiveLock);
    -		relation_close(parentTbl, AccessExclusiveLock);
    -	}
    -}
    -
    -/*
    - * When attaching an index as a partition of a partitioned index which is a
    - * primary key, verify that all the columns in the partition are marked NOT
    - * NULL.
    - */
    -static void
    -verifyPartitionIndexNotNull(IndexInfo *iinfo, Relation partition)
    -{
    -	for (int i = 0; i < iinfo->ii_NumIndexKeyAttrs; i++)
    -	{
    -		Form_pg_attribute att = TupleDescAttr(RelationGetDescr(partition),
    -											  iinfo->ii_IndexAttrNumbers[i] - 1);
    -
    -		if (!att->attnotnull)
    -			ereport(ERROR,
    -					errcode(ERRCODE_INVALID_TABLE_DEFINITION),
    -					errmsg("invalid primary key definition"),
    -					errdetail("Column \"%s\" of relation \"%s\" is not marked NOT NULL.",
    -							  NameStr(att->attname),
    -							  RelationGetRelationName(partition)));
    -	}
    -}
    -
    -/*
    - * Return an OID list of constraints that reference the given relation
    - * that are marked as having a parent constraints.
    - */
    -static List *
    -GetParentedForeignKeyRefs(Relation partition)
    -{
    -	Relation	pg_constraint;
    -	HeapTuple	tuple;
    -	SysScanDesc scan;
    -	ScanKeyData key[2];
    -	List	   *constraints = NIL;
    -
    -	/*
    -	 * If no indexes, or no columns are referenceable by FKs, we can avoid the
    -	 * scan.
    -	 */
    -	if (RelationGetIndexList(partition) == NIL ||
    -		bms_is_empty(RelationGetIndexAttrBitmap(partition,
    -												INDEX_ATTR_BITMAP_KEY)))
    -		return NIL;
    -
    -	/* Search for constraints referencing this table */
    -	pg_constraint = table_open(ConstraintRelationId, AccessShareLock);
    -	ScanKeyInit(&key[0],
    -				Anum_pg_constraint_confrelid, BTEqualStrategyNumber,
    -				F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(partition)));
    -	ScanKeyInit(&key[1],
    -				Anum_pg_constraint_contype, BTEqualStrategyNumber,
    -				F_CHAREQ, CharGetDatum(CONSTRAINT_FOREIGN));
    -
    -	/* XXX This is a seqscan, as we don't have a usable index */
    -	scan = systable_beginscan(pg_constraint, InvalidOid, true, NULL, 2, key);
    -	while ((tuple = systable_getnext(scan)) != NULL)
    -	{
    -		Form_pg_constraint constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
    -
    -		/*
    -		 * We only need to process constraints that are part of larger ones.
    -		 */
    -		if (!OidIsValid(constrForm->conparentid))
    -			continue;
    -
    -		constraints = lappend_oid(constraints, constrForm->oid);
    -	}
    -
    -	systable_endscan(scan);
    -	table_close(pg_constraint, AccessShareLock);
    -
    -	return constraints;
    -}
    -
    -/*
    - * During DETACH PARTITION, verify that any foreign keys pointing to the
    - * partitioned table would not become invalid.  An error is raised if any
    - * referenced values exist.
    - */
    -static void
    -ATDetachCheckNoForeignKeyRefs(Relation partition)
    -{
    -	List	   *constraints;
    -	ListCell   *cell;
    -
    -	constraints = GetParentedForeignKeyRefs(partition);
    -
    -	foreach(cell, constraints)
    -	{
    -		Oid			constrOid = lfirst_oid(cell);
    -		HeapTuple	tuple;
    -		Form_pg_constraint constrForm;
    -		Relation	rel;
    -		Trigger		trig = {0};
    -
    -		tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid));
    -		if (!HeapTupleIsValid(tuple))
    -			elog(ERROR, "cache lookup failed for constraint %u", constrOid);
    -		constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
    -
    -		Assert(OidIsValid(constrForm->conparentid));
    -		Assert(constrForm->confrelid == RelationGetRelid(partition));
    -
    -		/* prevent data changes into the referencing table until commit */
    -		rel = table_open(constrForm->conrelid, ShareLock);
    -
    -		trig.tgoid = InvalidOid;
    -		trig.tgname = NameStr(constrForm->conname);
    -		trig.tgenabled = TRIGGER_FIRES_ON_ORIGIN;
    -		trig.tgisinternal = true;
    -		trig.tgconstrrelid = RelationGetRelid(partition);
    -		trig.tgconstrindid = constrForm->conindid;
    -		trig.tgconstraint = constrForm->oid;
    -		trig.tgdeferrable = false;
    -		trig.tginitdeferred = false;
    -		/* we needn't fill in remaining fields */
    -
    -		RI_PartitionRemove_Check(&trig, rel, partition);
    -
    -		ReleaseSysCache(tuple);
    -
    -		table_close(rel, NoLock);
    -	}
    -}
    -
     /*
      * resolve column compression specification to compression method.
      */
    diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
    index 8ba038c5ef4..453bbf46cef 100644
    --- a/src/backend/partitioning/partbounds.c
    +++ b/src/backend/partitioning/partbounds.c
    @@ -20,6 +20,7 @@
     #include "catalog/partition.h"
     #include "catalog/pg_inherits.h"
     #include "catalog/pg_type.h"
    +#include "commands/partcmds.h"
     #include "commands/tablecmds.h"
     #include "common/hashfn.h"
     #include "executor/executor.h"
    diff --git a/src/include/commands/partcmds.h b/src/include/commands/partcmds.h
    new file mode 100644
    index 00000000000..743431909fb
    --- /dev/null
    +++ b/src/include/commands/partcmds.h
    @@ -0,0 +1,53 @@
    +/*-------------------------------------------------------------------------
    + *
    + * partcmds.h
    + *	  prototypes for partcmds.c.
    + *
    + *
    + * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
    + * Portions Copyright (c) 1994, Regents of the University of California
    + *
    + * src/include/commands/partcmds.h
    + *
    + *-------------------------------------------------------------------------
    + */
    +#ifndef PARTCMDS_H
    +#define PARTCMDS_H
    +
    +/* to avoid including other headers */
    +typedef struct AlteredTableInfo AlteredTableInfo;
    +typedef struct ParseState ParseState;
    +typedef struct AlterTableUtilityContext AlterTableUtilityContext;
    +typedef struct ForeignKeyCacheInfo ForeignKeyCacheInfo;
    +
    +extern bool tryAttachPartitionForeignKey(List **wqueue,
    +										 ForeignKeyCacheInfo *fk,
    +										 Relation partition,
    +										 Oid parentConstrOid, int numfks,
    +										 AttrNumber *mapped_conkey, AttrNumber *confkey,
    +										 Oid *conpfeqop,
    +										 Oid parentInsTrigger,
    +										 Oid parentUpdTrigger,
    +										 Relation trigrel);
    +extern PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec);
    +extern void ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNumber *partattrs,
    +								  List **partexprs, Oid *partopclass, Oid *partcollation,
    +								  PartitionStrategy strategy);
    +extern void CloneRowTriggersToPartition(Relation parent, Relation partition);
    +extern void CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
    +									   Relation partitionRel);
    +extern bool PartConstraintImpliedByRelConstraint(Relation scanrel,
    +												 List *partConstraint);
    +extern bool ConstraintImpliedByRelConstraint(Relation scanrel,
    +											 List *testConstraint, List *provenConstraint);
    +extern ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
    +										   PartitionCmd *cmd,
    +										   AlterTableUtilityContext *context);
    +extern ObjectAddress ATExecDetachPartition(List **wqueue, AlteredTableInfo *tab,
    +										   Relation rel, RangeVar *name,
    +										   bool concurrent);
    +extern ObjectAddress ATExecDetachPartitionFinalize(Relation rel, RangeVar *name);
    +extern ObjectAddress ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx,
    +											  RangeVar *name);
    +
    +#endif							/* PARTCMDS_H */
    diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
    index e9b0fab0767..35d33fa9339 100644
    --- a/src/include/commands/tablecmds.h
    +++ b/src/include/commands/tablecmds.h
    @@ -24,6 +24,138 @@
     typedef struct AlterTableUtilityContext AlterTableUtilityContext;	/* avoid including
     																	 * tcop/utility.h here */
     
    +/*
    + * State information for ALTER TABLE
    + *
    + * The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo
    + * structs, one for each table modified by the operation (the named table
    + * plus any child tables that are affected).  We save lists of subcommands
    + * to apply to this table (possibly modified by parse transformation steps);
    + * these lists will be executed in Phase 2.  If a Phase 3 step is needed,
    + * necessary information is stored in the constraints and newvals lists.
    + *
    + * Phase 2 is divided into multiple passes; subcommands are executed in
    + * a pass determined by subcommand type.
    + */
    +
    +typedef enum AlterTablePass
    +{
    +	AT_PASS_UNSET = -1,			/* UNSET will cause ERROR */
    +	AT_PASS_DROP,				/* DROP (all flavors) */
    +	AT_PASS_ALTER_TYPE,			/* ALTER COLUMN TYPE */
    +	AT_PASS_ADD_COL,			/* ADD COLUMN */
    +	AT_PASS_SET_EXPRESSION,		/* ALTER SET EXPRESSION */
    +	AT_PASS_OLD_INDEX,			/* re-add existing indexes */
    +	AT_PASS_OLD_CONSTR,			/* re-add existing constraints */
    +	/* We could support a RENAME COLUMN pass here, but not currently used */
    +	AT_PASS_ADD_CONSTR,			/* ADD constraints (initial examination) */
    +	AT_PASS_COL_ATTRS,			/* set column attributes, eg NOT NULL */
    +	AT_PASS_ADD_INDEXCONSTR,	/* ADD index-based constraints */
    +	AT_PASS_ADD_INDEX,			/* ADD indexes */
    +	AT_PASS_ADD_OTHERCONSTR,	/* ADD other constraints, defaults */
    +	AT_PASS_MISC,				/* other stuff */
    +} AlterTablePass;
    +
    +#define AT_NUM_PASSES			(AT_PASS_MISC + 1)
    +
    +typedef struct AlteredTableInfo
    +{
    +	/* Information saved before any work commences: */
    +	Oid			relid;			/* Relation to work on */
    +	char		relkind;		/* Its relkind */
    +	TupleDesc	oldDesc;		/* Pre-modification tuple descriptor */
    +
    +	/*
    +	 * Transiently set during Phase 2, normally set to NULL.
    +	 *
    +	 * ATRewriteCatalogs sets this when it starts, and closes when ATExecCmd
    +	 * returns control.  This can be exploited by ATExecCmd subroutines to
    +	 * close/reopen across transaction boundaries.
    +	 */
    +	Relation	rel;
    +
    +	/* Information saved by Phase 1 for Phase 2: */
    +	List	   *subcmds[AT_NUM_PASSES]; /* Lists of AlterTableCmd */
    +	/* Information saved by Phases 1/2 for Phase 3: */
    +	List	   *constraints;	/* List of NewConstraint */
    +	List	   *newvals;		/* List of NewColumnValue */
    +	List	   *afterStmts;		/* List of utility command parsetrees */
    +	bool		verify_new_notnull; /* T if we should recheck NOT NULL */
    +	int			rewrite;		/* Reason for forced rewrite, if any */
    +	bool		chgAccessMethod;	/* T if SET ACCESS METHOD is used */
    +	Oid			newAccessMethod;	/* new access method; 0 means no change,
    +									 * if above is true */
    +	Oid			newTableSpace;	/* new tablespace; 0 means no change */
    +	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
    +	char		newrelpersistence;	/* if above is true */
    +	Expr	   *partition_constraint;	/* for attach partition validation */
    +	/* true, if validating default due to some other attach/detach */
    +	bool		validate_default;
    +	/* Objects to rebuild after completing ALTER TYPE operations */
    +	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
    +	List	   *changedConstraintDefs;	/* string definitions of same */
    +	List	   *changedIndexOids;	/* OIDs of indexes to rebuild */
    +	List	   *changedIndexDefs;	/* string definitions of same */
    +	char	   *replicaIdentityIndex;	/* index to reset as REPLICA IDENTITY */
    +	char	   *clusterOnIndex; /* index to use for CLUSTER */
    +	List	   *changedStatisticsOids;	/* OIDs of statistics to rebuild */
    +	List	   *changedStatisticsDefs;	/* string definitions of same */
    +} AlteredTableInfo;
    +
    +/* Alter table target-type flags for ATSimplePermissions */
    +#define		ATT_TABLE				0x0001
    +#define		ATT_VIEW				0x0002
    +#define		ATT_MATVIEW				0x0004
    +#define		ATT_INDEX				0x0008
    +#define		ATT_COMPOSITE_TYPE		0x0010
    +#define		ATT_FOREIGN_TABLE		0x0020
    +#define		ATT_PARTITIONED_INDEX	0x0040
    +#define		ATT_SEQUENCE			0x0080
    +#define		ATT_PARTITIONED_TABLE	0x0100
    +
    +/* Partial or complete FK creation in addFkConstraint() */
    +typedef enum addFkConstraintSides
    +{
    +	addFkReferencedSide,
    +	addFkReferencingSide,
    +	addFkBothSides,
    +} addFkConstraintSides;
    +
    +extern AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel);
    +extern void ATSimplePermissions(AlterTableType cmdtype, Relation rel, int allowed_targets);
    +extern void CreateInheritance(Relation child_rel, Relation parent_rel, bool ispartition);
    +extern void RemoveInheritance(Relation child_rel, Relation parent_rel,
    +							  bool expect_detached);
    +extern void addFkRecurseReferenced(Constraint *fkconstraint,
    +								   Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
    +								   int numfks, int16 *pkattnum, int16 *fkattnum,
    +								   Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
    +								   int numfkdelsetcols, int16 *fkdelsetcols,
    +								   bool old_check_ok,
    +								   Oid parentDelTrigger, Oid parentUpdTrigger,
    +								   bool with_period);
    +extern ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode,
    +										bool recurse, bool recursing);
    +extern void QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation fkrel,
    +										Oid pkrelid, HeapTuple contuple, LOCKMODE lockmode);
    +extern ObjectAddress addFkConstraint(addFkConstraintSides fkside,
    +									 char *constraintname,
    +									 Constraint *fkconstraint, Relation rel,
    +									 Relation pkrel, Oid indexOid,
    +									 Oid parentConstr,
    +									 int numfks, int16 *pkattnum, int16 *fkattnum,
    +									 Oid *pfeqoperators, Oid *ppeqoperators,
    +									 Oid *ffeqoperators, int numfkdelsetcols,
    +									 int16 *fkdelsetcols, bool is_internal,
    +									 bool with_period);
    +extern void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint,
    +									Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
    +									int numfks, int16 *pkattnum, int16 *fkattnum,
    +									Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
    +									int numfkdelsetcols, int16 *fkdelsetcols,
    +									bool old_check_ok, LOCKMODE lockmode,
    +									Oid parentInsTrigger, Oid parentUpdTrigger,
    +									bool with_period);
     
     extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
     									ObjectAddress *typaddress, const char *queryString);
    @@ -105,7 +237,5 @@ extern void RangeVarCallbackMaintainsTable(const RangeVar *relation,
     
     extern void RangeVarCallbackOwnsRelation(const RangeVar *relation,
     										 Oid relId, Oid oldRelId, void *arg);
    -extern bool PartConstraintImpliedByRelConstraint(Relation scanrel,
    -												 List *partConstraint);
     
     #endif							/* TABLECMDS_H */
    -- 
    2.39.5 (Apple Git-154)
    
    
    --qOF9Ti6sM1R7BDqI--