[PATCH v1 1/1] move partition code in tablecmds.c to new file
Nathan Bossart <nathan@postgresql.org>
From: Nathan Bossart <nathan@postgresql.org>
To:
Date: 2025-12-01T22:26:39Z
Lists: pgsql-hackers
---
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--