diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index fb0506355515..014bb8156654 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2620,7 +2620,7 @@ SCRAM-SHA-256$<iteration count>:&l
Is the constraint enforced?
- Currently, can be false only for CHECK constraints
+ Currently, can be false only for foreign keys and CHECK constraints
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 11d1bc7dbe19..ece438f0075f 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -58,7 +58,7 @@ ALTER TABLE [ IF EXISTS ] name
ALTER [ COLUMN ] column_name SET COMPRESSION compression_method
ADD table_constraint [ NOT VALID ]
ADD table_constraint_using_index
- ALTER CONSTRAINT constraint_name [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+ ALTER CONSTRAINT constraint_name [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] [ ENFORCED | NOT ENFORCED ]
ALTER CONSTRAINT constraint_name [ INHERIT | NO INHERIT ]
VALIDATE CONSTRAINT constraint_name
DROP CONSTRAINT [ IF EXISTS ] constraint_name [ RESTRICT | CASCADE ]
@@ -589,7 +589,8 @@ WITH ( MODULUS numeric_literal, REM
This form validates a foreign key or check constraint that was
previously created as NOT VALID, by scanning the
table to ensure there are no rows for which the constraint is not
- satisfied. Nothing happens if the constraint is already marked valid.
+ satisfied. If the constraint is not enforced, an error is thrown.
+ Nothing happens if the constraint is already marked valid.
(See below for an explanation
of the usefulness of this command.)
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index e5c034d724e4..4a41b2f55300 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -1409,7 +1409,7 @@ WITH ( MODULUS numeric_literal, REM
- This is currently only supported for CHECK
+ This is currently only supported for foreign key and CHECK
constraints.
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index ac80652baf25..0467e7442ffd 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -100,8 +100,9 @@ CreateConstraintEntry(const char *constraintName,
ObjectAddresses *addrs_auto;
ObjectAddresses *addrs_normal;
- /* Only CHECK constraint can be not enforced */
- Assert(isEnforced || constraintType == CONSTRAINT_CHECK);
+ /* Only CHECK or FOREIGN KEY constraint can be not enforced */
+ Assert(isEnforced || constraintType == CONSTRAINT_CHECK ||
+ constraintType == CONSTRAINT_FOREIGN);
/* NOT ENFORCED constraint must be NOT VALID */
Assert(isEnforced || !isValidated);
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index 2f250d2c57bf..ebe85337c287 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -281,7 +281,7 @@ F461 Named character sets NO
F471 Scalar subquery values YES
F481 Expanded NULL predicate YES
F491 Constraint management YES
-F492 Optional table constraint enforcement NO check constraints only
+F492 Optional table constraint enforcement YES except not-null constraints
F501 Features and conformance views YES
F501 Features and conformance views 01 SQL_FEATURES view YES
F501 Features and conformance views 02 SQL_SIZING view YES
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 10624353b0a0..17d632daf54c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -395,6 +395,14 @@ static ObjectAddress ATExecAlterConstraint(List **wqueue, Relation rel,
static bool ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon, Relation conrel,
Relation tgrel, Relation rel, HeapTuple contuple,
bool recurse, LOCKMODE lockmode);
+static bool ATExecAlterConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon,
+ Relation conrel, Relation tgrel,
+ const Oid fkrelid, const Oid pkrelid,
+ HeapTuple contuple, LOCKMODE lockmode,
+ Oid ReferencedParentDelTrigger,
+ Oid ReferencedParentUpdTrigger,
+ Oid ReferencingParentInsTrigger,
+ Oid ReferencingParentUpdTrigger);
static bool ATExecAlterConstrDeferrability(List **wqueue, ATAlterConstraint *cmdcon,
Relation conrel, Relation tgrel, Relation rel,
HeapTuple contuple, bool recurse,
@@ -405,6 +413,14 @@ static bool ATExecAlterConstrInheritability(List **wqueue, ATAlterConstraint *cm
static void AlterConstrTriggerDeferrability(Oid conoid, Relation tgrel, Relation rel,
bool deferrable, bool initdeferred,
List **otherrelids);
+static void AlterConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
+ Relation conrel, Relation tgrel,
+ const Oid fkrelid, const Oid pkrelid,
+ HeapTuple contuple, LOCKMODE lockmode,
+ Oid ReferencedParentDelTrigger,
+ Oid ReferencedParentUpdTrigger,
+ Oid ReferencingParentInsTrigger,
+ Oid ReferencingParentUpdTrigger);
static void AlterConstrDeferrabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
Relation conrel, Relation tgrel, Relation rel,
HeapTuple contuple, bool recurse,
@@ -10610,7 +10626,7 @@ addFkConstraint(addFkConstraintSides fkside,
CONSTRAINT_FOREIGN,
fkconstraint->deferrable,
fkconstraint->initdeferred,
- true, /* Is Enforced */
+ fkconstraint->is_enforced,
fkconstraint->initially_valid,
parentConstr,
RelationGetRelid(rel),
@@ -10728,21 +10744,23 @@ addFkRecurseReferenced(Constraint *fkconstraint, Relation rel,
Oid parentDelTrigger, Oid parentUpdTrigger,
bool with_period)
{
- Oid deleteTriggerOid,
- updateTriggerOid;
+ Oid deleteTriggerOid = InvalidOid,
+ updateTriggerOid = InvalidOid;
Assert(CheckRelationLockedByMe(pkrel, ShareRowExclusiveLock, true));
Assert(CheckRelationLockedByMe(rel, ShareRowExclusiveLock, true));
/*
- * Create the action triggers that enforce the constraint.
+ * Create action triggers to enforce the constraint, or skip them if the
+ * constraint is NOT ENFORCED.
*/
- createForeignKeyActionTriggers(RelationGetRelid(rel),
- RelationGetRelid(pkrel),
- fkconstraint,
- parentConstr, indexOid,
- parentDelTrigger, parentUpdTrigger,
- &deleteTriggerOid, &updateTriggerOid);
+ if (fkconstraint->is_enforced)
+ createForeignKeyActionTriggers(RelationGetRelid(rel),
+ RelationGetRelid(pkrel),
+ fkconstraint,
+ parentConstr, indexOid,
+ parentDelTrigger, parentUpdTrigger,
+ &deleteTriggerOid, &updateTriggerOid);
/*
* If the referenced table is partitioned, recurse on ourselves to handle
@@ -10863,8 +10881,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
Oid parentInsTrigger, Oid parentUpdTrigger,
bool with_period)
{
- Oid insertTriggerOid,
- updateTriggerOid;
+ Oid insertTriggerOid = InvalidOid,
+ updateTriggerOid = InvalidOid;
Assert(OidIsValid(parentConstr));
Assert(CheckRelationLockedByMe(rel, ShareRowExclusiveLock, true));
@@ -10876,29 +10894,32 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
errmsg("foreign key constraints are not supported on foreign tables")));
/*
- * Add the check triggers to it and, if necessary, schedule it to be
- * checked in Phase 3.
+ * Add check triggers if the constraint is ENFORCED, and if needed,
+ * schedule them to be checked in Phase 3.
*
* If the relation is partitioned, drill down to do it to its partitions.
*/
- createForeignKeyCheckTriggers(RelationGetRelid(rel),
- RelationGetRelid(pkrel),
- fkconstraint,
- parentConstr,
- indexOid,
- parentInsTrigger, parentUpdTrigger,
- &insertTriggerOid, &updateTriggerOid);
+ if (fkconstraint->is_enforced)
+ createForeignKeyCheckTriggers(RelationGetRelid(rel),
+ RelationGetRelid(pkrel),
+ fkconstraint,
+ parentConstr,
+ indexOid,
+ parentInsTrigger, parentUpdTrigger,
+ &insertTriggerOid, &updateTriggerOid);
if (rel->rd_rel->relkind == RELKIND_RELATION)
{
/*
* Tell Phase 3 to check that the constraint is satisfied by existing
- * rows. We can skip this during table creation, when requested
- * explicitly by specifying NOT VALID in an ADD FOREIGN KEY command,
- * and when we're recreating a constraint following a SET DATA TYPE
- * operation that did not impugn its validity.
+ * rows. We can skip this during table creation, when constraint is
+ * specified as NOT ENFORCED, or when requested explicitly by
+ * specifying NOT VALID in an ADD FOREIGN KEY command, and when we're
+ * recreating a constraint following a SET DATA TYPE operation that
+ * did not impugn its validity.
*/
- if (wqueue && !old_check_ok && !fkconstraint->skip_validation)
+ if (wqueue && !old_check_ok && !fkconstraint->skip_validation &&
+ fkconstraint->is_enforced)
{
NewConstraint *newcon;
AlteredTableInfo *tab;
@@ -11129,8 +11150,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
AttrNumber confdelsetcols[INDEX_MAX_KEYS];
Constraint *fkconstraint;
ObjectAddress address;
- Oid deleteTriggerOid,
- updateTriggerOid;
+ Oid deleteTriggerOid = InvalidOid,
+ updateTriggerOid = InvalidOid;
tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid));
if (!HeapTupleIsValid(tuple))
@@ -11190,8 +11211,9 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
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 = true;
+ fkconstraint->initially_valid = constrForm->convalidated;
/* set up colnames that are used to generate the constraint name */
for (int i = 0; i < numfks; i++)
@@ -11219,9 +11241,10 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
* parent OIDs for similar triggers that will be created on the
* partition in addFkRecurseReferenced().
*/
- GetForeignKeyActionTriggers(trigrel, constrOid,
- constrForm->confrelid, constrForm->conrelid,
- &deleteTriggerOid, &updateTriggerOid);
+ if (constrForm->conenforced)
+ GetForeignKeyActionTriggers(trigrel, constrOid,
+ constrForm->confrelid, constrForm->conrelid,
+ &deleteTriggerOid, &updateTriggerOid);
/* Add this constraint ... */
address = addFkConstraint(addFkReferencedSide,
@@ -11354,8 +11377,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
Oid indexOid;
ObjectAddress address;
ListCell *lc;
- Oid insertTriggerOid,
- updateTriggerOid;
+ Oid insertTriggerOid = InvalidOid,
+ updateTriggerOid = InvalidOid;
bool with_period;
tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(parentConstrOid));
@@ -11387,17 +11410,18 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
mapped_conkey[i] = attmap->attnums[conkey[i] - 1];
/*
- * Get the "check" triggers belonging to the constraint 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.
+ * 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.
*/
- GetForeignKeyCheckTriggers(trigrel, constrForm->oid,
- constrForm->confrelid, constrForm->conrelid,
- &insertTriggerOid, &updateTriggerOid);
+ if (constrForm->conenforced)
+ GetForeignKeyCheckTriggers(trigrel, constrForm->oid,
+ constrForm->confrelid, constrForm->conrelid,
+ &insertTriggerOid, &updateTriggerOid);
/*
* Before creating a new constraint, see whether any existing FKs are
@@ -11450,6 +11474,7 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
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++)
@@ -11610,8 +11635,9 @@ AttachPartitionForeignKey(List **wqueue,
bool queueValidation;
Oid partConstrFrelid;
Oid partConstrRelid;
- Oid insertTriggerOid,
- updateTriggerOid;
+ bool parentConstrIsEnforced;
+ bool partConstrIsEnforced;
+ bool partConstrParentIsSet;
/* Fetch the parent constraint tuple */
parentConstrTup = SearchSysCache1(CONSTROID,
@@ -11619,6 +11645,7 @@ AttachPartitionForeignKey(List **wqueue,
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,
@@ -11626,13 +11653,47 @@ AttachPartitionForeignKey(List **wqueue,
if (!HeapTupleIsValid(partcontup))
elog(ERROR, "cache lookup failed for constraint %u", partConstrOid);
partConstr = (Form_pg_constraint) GETSTRUCT(partcontup);
+ partConstrIsEnforced = partConstr->conenforced;
partConstrFrelid = partConstr->confrelid;
partConstrRelid = partConstr->conrelid;
+ /*
+ * The case where the parent constraint is NOT ENFORCED and the child
+ * constraint is ENFORCED is acceptable because the not enforced parent
+ * constraint lacks triggers, eliminating any redundancy issues with the
+ * enforced child constraint. In this scenario, the child constraint
+ * remains enforced, and its trigger is retained, ensuring that
+ * referential integrity checks for the child continue as before, even
+ * with the parent constraint not enforced. The relationship between the
+ * two constraints is preserved by setting the parent constraint, which
+ * allows us to locate the child constraint. This becomes important if the
+ * parent constraint is later changed to enforced, at which point the
+ * necessary trigger will be created for the parent, and any redundancy
+ * from these triggers will be appropriately handled.
+ */
+ if (!parentConstrIsEnforced && partConstrIsEnforced)
+ {
+ ReleaseSysCache(partcontup);
+ ReleaseSysCache(parentConstrTup);
+
+ ConstraintSetParentConstraint(partConstrOid, parentConstrOid,
+ RelationGetRelid(partition));
+ CommandCounterIncrement();
+
+ return;
+ }
+
/*
* 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.
+ *
+ * Note that this must be done beforehand, particularly in situations
+ * where we might decide to change the constraint to an ENFORCED state
+ * which will create the required triggers and add the child constraint to
+ * the validation queue. To avoid generating unnecessary triggers and
+ * adding them to the validation queue, it is crucial to eliminate any
+ * redundant constraints beforehand.
*/
if (get_rel_relkind(partConstrFrelid) == RELKIND_PARTITIONED_TABLE)
{
@@ -11651,6 +11712,53 @@ AttachPartitionForeignKey(List **wqueue,
*/
queueValidation = parentConstr->convalidated && !partConstr->convalidated;
+ /*
+ * The case where the parent constraint is ENFORCED and the child
+ * constraint is NOT ENFORCED is not acceptable, as it would violate
+ * referential integrity. In such cases, the child constraint will first
+ * be enforced before merging it with the enforced parent constraint.
+ * Subsequently, removing action triggers, setting up constraint triggers,
+ * and handling check triggers for the parent will be managed in the usual
+ * manner, similar to how two enforced constraints are merged.
+ */
+ if (parentConstrIsEnforced && !partConstrIsEnforced)
+ {
+ ATAlterConstraint *cmdcon = makeNode(ATAlterConstraint);
+ Relation conrel;
+
+ cmdcon->conname = NameStr(partConstr->conname);
+ cmdcon->deferrable = partConstr->condeferrable;
+ cmdcon->initdeferred = partConstr->condeferred;
+ cmdcon->alterEnforceability = true;
+ cmdcon->is_enforced = true;
+
+ conrel = table_open(ConstraintRelationId, RowExclusiveLock);
+
+ ATExecAlterConstrEnforceability(wqueue, cmdcon, conrel, trigrel,
+ partConstr->conrelid,
+ partConstr->confrelid,
+ partcontup, AccessExclusiveLock,
+ InvalidOid, InvalidOid, InvalidOid,
+ InvalidOid);
+
+ table_close(conrel, RowExclusiveLock);
+
+ CommandCounterIncrement();
+
+ /*
+ * No further validation is needed, as changing the constraint to
+ * enforced will implicitly trigger the same validation.
+ */
+ queueValidation = false;
+ }
+
+ /*
+ * The constraint parent shouldn't be set beforehand, or if it's already
+ * set, it should be the specified parent.
+ */
+ partConstrParentIsSet = OidIsValid(partConstr->conparentid);
+ Assert(!partConstrParentIsSet || partConstr->conparentid == parentConstrOid);
+
ReleaseSysCache(partcontup);
ReleaseSysCache(parentConstrTup);
@@ -11663,22 +11771,31 @@ AttachPartitionForeignKey(List **wqueue,
DropForeignKeyConstraintTriggers(trigrel, partConstrOid, partConstrFrelid,
partConstrRelid);
- ConstraintSetParentConstraint(partConstrOid, parentConstrOid,
- RelationGetRelid(partition));
+ /* Skip if the parent is already set */
+ if (!partConstrParentIsSet)
+ ConstraintSetParentConstraint(partConstrOid, parentConstrOid,
+ RelationGetRelid(partition));
/*
* Like the constraint, attach partition's "check" triggers to the
- * corresponding parent triggers.
+ * corresponding parent triggers if the constraint is ENFORCED. NOT
+ * ENFORCED constraints do not have these triggers.
*/
- 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));
+ 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
@@ -11792,6 +11909,10 @@ RemoveInheritedConstraint(Relation conrel, Relation trigrel, Oid conoid,
*
* 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,
@@ -11812,11 +11933,28 @@ DropForeignKeyConstraintTriggers(Relation trigrel, Oid conoid, Oid confrelid,
Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
ObjectAddress trigger;
- if (trgform->tgconstrrelid != conrelid)
+ /* Invalid if trigger is not for a referential integrity constraint */
+ if (!OidIsValid(trgform->tgconstrrelid))
continue;
- if (trgform->tgrelid != confrelid)
+ if (OidIsValid(conrelid) && trgform->tgconstrrelid != conrelid)
+ continue;
+ if (OidIsValid(confrelid) && trgform->tgrelid != confrelid)
continue;
+ /* We should be droping 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
@@ -12028,6 +12166,11 @@ ATExecAlterConstraint(List **wqueue, Relation rel, ATAlterConstraint *cmdcon,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key constraint",
cmdcon->conname, RelationGetRelationName(rel))));
+ if (cmdcon->alterEnforceability && currcon->contype != CONSTRAINT_FOREIGN)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter enforceability of constraint \"%s\" of relation \"%s\"",
+ cmdcon->conname, RelationGetRelationName(rel))));
if (cmdcon->alterInheritability &&
currcon->contype != CONSTRAINT_NOTNULL)
ereport(ERROR,
@@ -12107,7 +12250,7 @@ ATExecAlterConstraint(List **wqueue, Relation rel, ATAlterConstraint *cmdcon,
/*
* A subroutine of ATExecAlterConstraint that calls the respective routines for
- * altering constraint attributes.
+ * altering constraint's enforceability, deferrability or inheritability.
*/
static bool
ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon,
@@ -12115,16 +12258,35 @@ ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon,
HeapTuple contuple, bool recurse,
LOCKMODE lockmode)
{
+ Form_pg_constraint currcon;
bool changed = false;
List *otherrelids = NIL;
+ currcon = (Form_pg_constraint) GETSTRUCT(contuple);
+
/*
- * Do the catalog work for the deferrability change, recurse if necessary.
- */
- if (cmdcon->alterDeferrability &&
- ATExecAlterConstrDeferrability(wqueue, cmdcon, conrel, tgrel, rel,
- contuple, recurse, &otherrelids,
- lockmode))
+ * Do the catalog work for the enforceability or deferrability change,
+ * recurse if necessary.
+ *
+ * Note that even if deferrability is requested to be altered along with
+ * enforceability, we don't need to explicitly update multiple entries in
+ * pg_trigger related to deferrability.
+ *
+ * Modifying enforceability involves either creating or dropping the
+ * trigger, during which the deferrability setting will be adjusted
+ * automatically.
+ */
+ if (cmdcon->alterEnforceability &&
+ ATExecAlterConstrEnforceability(wqueue, cmdcon, conrel, tgrel,
+ currcon->conrelid, currcon->confrelid,
+ contuple, lockmode, InvalidOid,
+ InvalidOid, InvalidOid, InvalidOid))
+ changed = true;
+
+ else if (cmdcon->alterDeferrability &&
+ ATExecAlterConstrDeferrability(wqueue, cmdcon, conrel, tgrel, rel,
+ contuple, recurse, &otherrelids,
+ lockmode))
{
/*
* AlterConstrUpdateConstraintEntry already invalidated relcache for
@@ -12149,6 +12311,162 @@ ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon,
return changed;
}
+/*
+ * Returns true if the constraint's enforceability is altered.
+ *
+ * Depending on whether the constraint is being set to ENFORCED or NOT
+ * ENFORCED, it creates or drops the trigger accordingly.
+ *
+ * Note that we must recurse even when trying to change a constraint to not
+ * enforced if it is already not enforced, in case descendant constraints
+ * might be enforced and need to be changed to not enforced. Conversely, we
+ * should do nothing if a constraint is being set to enforced and is already
+ * enforced, as descendant constraints cannot be different in that case.
+ */
+static bool
+ATExecAlterConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon,
+ Relation conrel, Relation tgrel,
+ const Oid fkrelid, const Oid pkrelid,
+ HeapTuple contuple, LOCKMODE lockmode,
+ Oid ReferencedParentDelTrigger,
+ Oid ReferencedParentUpdTrigger,
+ Oid ReferencingParentInsTrigger,
+ Oid ReferencingParentUpdTrigger)
+{
+ Form_pg_constraint currcon;
+ Oid conoid;
+ Relation rel;
+ bool changed = false;
+
+ /* Since this function recurses, it could be driven to stack overflow */
+ check_stack_depth();
+
+ Assert(cmdcon->alterEnforceability);
+
+ currcon = (Form_pg_constraint) GETSTRUCT(contuple);
+ conoid = currcon->oid;
+
+ /* Should be foreign key constraint */
+ Assert(currcon->contype == CONSTRAINT_FOREIGN);
+
+ rel = table_open(currcon->conrelid, lockmode);
+
+ if (currcon->conenforced != cmdcon->is_enforced)
+ {
+ AlterConstrUpdateConstraintEntry(cmdcon, conrel, contuple);
+ changed = true;
+ }
+
+ /* Drop triggers */
+ if (!cmdcon->is_enforced)
+ {
+ /*
+ * When setting a constraint to NOT ENFORCED, the constraint triggers
+ * need to be dropped. Therefore, we must process the child relations
+ * first, followed by the parent, to account for dependencies.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+ get_rel_relkind(currcon->confrelid) == RELKIND_PARTITIONED_TABLE)
+ AlterConstrEnforceabilityRecurse(wqueue, cmdcon, conrel, tgrel,
+ fkrelid, pkrelid, contuple,
+ lockmode, InvalidOid, InvalidOid,
+ InvalidOid, InvalidOid);
+
+ /* Drop all the triggers */
+ DropForeignKeyConstraintTriggers(tgrel, conoid, InvalidOid, InvalidOid);
+
+ /*
+ * If the referenced table is partitioned, the child constraint we're
+ * changing to NOT ENFORCED may have additional pg_constraint rows and
+ * action triggers that remain untouched while this child constraint
+ * is attached to the NOT ENFORCED parent. These must now be removed.
+ * For more details, see AttachPartitionForeignKey().
+ */
+ if (OidIsValid(currcon->conparentid) &&
+ get_rel_relkind(currcon->confrelid) == RELKIND_PARTITIONED_TABLE)
+ RemoveInheritedConstraint(conrel, tgrel, currcon->conrelid, conoid);
+ }
+ else if (changed) /* Create triggers */
+ {
+ Oid ReferencedDelTriggerOid = InvalidOid,
+ ReferencedUpdTriggerOid = InvalidOid,
+ ReferencingInsTriggerOid = InvalidOid,
+ ReferencingUpdTriggerOid = InvalidOid;
+
+ /* Prepare the minimal information required for trigger creation. */
+ Constraint *fkconstraint = makeNode(Constraint);
+
+ fkconstraint->conname = pstrdup(NameStr(currcon->conname));
+ fkconstraint->fk_matchtype = currcon->confmatchtype;
+ fkconstraint->fk_upd_action = currcon->confupdtype;
+ fkconstraint->fk_del_action = currcon->confdeltype;
+
+ /* Create referenced triggers */
+ if (currcon->conrelid == fkrelid)
+ createForeignKeyActionTriggers(currcon->conrelid,
+ currcon->confrelid,
+ fkconstraint,
+ conoid,
+ currcon->conindid,
+ ReferencedParentDelTrigger,
+ ReferencedParentUpdTrigger,
+ &ReferencedDelTriggerOid,
+ &ReferencedUpdTriggerOid);
+
+ /* Create referencing triggers */
+ if (currcon->confrelid == pkrelid)
+ createForeignKeyCheckTriggers(currcon->conrelid,
+ pkrelid,
+ fkconstraint,
+ conoid,
+ currcon->conindid,
+ ReferencingParentInsTrigger,
+ ReferencingParentUpdTrigger,
+ &ReferencingInsTriggerOid,
+ &ReferencingUpdTriggerOid);
+
+ /*
+ * Tell Phase 3 to check that the constraint is satisfied by existing
+ * rows.
+ */
+ if (rel->rd_rel->relkind == RELKIND_RELATION)
+ {
+ AlteredTableInfo *tab;
+ NewConstraint *newcon;
+
+ newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
+ newcon->name = fkconstraint->conname;
+ newcon->contype = CONSTR_FOREIGN;
+ newcon->refrelid = currcon->confrelid;
+ newcon->refindid = currcon->conindid;
+ newcon->conid = currcon->oid;
+ newcon->qual = (Node *) fkconstraint;
+
+ /* Find or create work queue entry for this table */
+ tab = ATGetQueueEntry(wqueue, rel);
+ tab->constraints = lappend(tab->constraints, newcon);
+ }
+
+ /*
+ * If the table at either end of the constraint is partitioned, we
+ * need to recurse and create triggers for each constraint that is a
+ * child of this one.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+ get_rel_relkind(currcon->confrelid) == RELKIND_PARTITIONED_TABLE)
+ AlterConstrEnforceabilityRecurse(wqueue, cmdcon, conrel, tgrel,
+ fkrelid, pkrelid, contuple,
+ lockmode, ReferencedDelTriggerOid,
+ ReferencedUpdTriggerOid,
+ ReferencingInsTriggerOid,
+ ReferencingUpdTriggerOid);
+ }
+
+ table_close(rel, NoLock);
+
+ return changed;
+}
+
/*
* Returns true if the constraint's deferrability is altered.
*
@@ -12353,6 +12671,82 @@ AlterConstrTriggerDeferrability(Oid conoid, Relation tgrel, Relation rel,
systable_endscan(tgscan);
}
+/*
+ * Invokes ATExecAlterConstrEnforceability for each constraint that is a child of
+ * the specified constraint.
+ *
+ * Note that this doesn't handle recursion the normal way, viz. by scanning the
+ * list of child relations and recursing; instead it uses the conparentid
+ * relationships. This may need to be reconsidered.
+ *
+ * The arguments to this function have the same meaning as the arguments to
+ * ATExecAlterConstrEnforceability.
+ */
+static void
+AlterConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
+ Relation conrel, Relation tgrel,
+ const Oid fkrelid, const Oid pkrelid,
+ HeapTuple contuple, LOCKMODE lockmode,
+ Oid ReferencedParentDelTrigger,
+ Oid ReferencedParentUpdTrigger,
+ Oid ReferencingParentInsTrigger,
+ Oid ReferencingParentUpdTrigger)
+{
+ Form_pg_constraint currcon;
+ Oid conoid;
+ ScanKeyData pkey;
+ SysScanDesc pscan;
+ HeapTuple childtup;
+
+ currcon = (Form_pg_constraint) GETSTRUCT(contuple);
+ conoid = currcon->oid;
+
+ ScanKeyInit(&pkey,
+ Anum_pg_constraint_conparentid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(conoid));
+
+ pscan = systable_beginscan(conrel, ConstraintParentIndexId,
+ true, NULL, 1, &pkey);
+
+ while (HeapTupleIsValid(childtup = systable_getnext(pscan)))
+ {
+ Form_pg_constraint childcon;
+
+ childcon = (Form_pg_constraint) GETSTRUCT(childtup);
+
+ /*
+ * When the parent constraint is modified to be ENFORCED, and the
+ * child constraint is attached to the parent constraint (which is
+ * already ENFORCED), some constraints and action triggers on the
+ * child table may become redundant and need to be removed.
+ */
+ if (cmdcon->is_enforced && childcon->conenforced)
+ {
+ if (currcon->confrelid == pkrelid)
+ {
+ Relation rel = table_open(childcon->conrelid, lockmode);
+
+ AttachPartitionForeignKey(wqueue, rel, childcon->oid,
+ conoid,
+ ReferencingParentInsTrigger,
+ ReferencingParentUpdTrigger,
+ tgrel);
+
+ table_close(rel, NoLock);
+ }
+ }
+ else
+ ATExecAlterConstrEnforceability(wqueue, cmdcon, conrel, tgrel, fkrelid,
+ pkrelid, childtup, lockmode,
+ ReferencedParentDelTrigger,
+ ReferencedParentUpdTrigger,
+ ReferencingParentInsTrigger,
+ ReferencingParentUpdTrigger);
+ }
+ systable_endscan(pscan);
+}
+
/*
* Invokes ATExecAlterConstrDeferrability for each constraint that is a child of
* the specified constraint.
@@ -12413,11 +12807,25 @@ AlterConstrUpdateConstraintEntry(ATAlterConstraint *cmdcon, Relation conrel,
HeapTuple copyTuple;
Form_pg_constraint copy_con;
- Assert(cmdcon->alterDeferrability || cmdcon->alterInheritability);
+ Assert(cmdcon->alterEnforceability || cmdcon->alterDeferrability ||
+ cmdcon->alterInheritability);
copyTuple = heap_copytuple(contuple);
copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
+ if (cmdcon->alterEnforceability)
+ {
+ copy_con->conenforced = cmdcon->is_enforced;
+
+ /*
+ * NB: The convalidated status is irrelevant when the constraint is
+ * set to NOT ENFORCED, but for consistency, it should still be set
+ * appropriately. Similarly, if the constraint is later changed to
+ * ENFORCED, validation will be performed during phase 3, so it makes
+ * sense to mark it as valid in that case.
+ */
+ copy_con->convalidated = cmdcon->is_enforced;
+ }
if (cmdcon->alterDeferrability)
{
copy_con->condeferrable = cmdcon->deferrable;
@@ -17137,9 +17545,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
NameStr(child_con->conname), RelationGetRelationName(child_rel))));
/*
- * A non-enforced child constraint cannot be merged with an
- * enforced parent constraint. However, the reverse is allowed,
- * where the child constraint is enforced.
+ * A NOT ENFORCED child constraint cannot be merged with an
+ * ENFORCED parent constraint. However, the reverse is allowed,
+ * where the child constraint is ENFORCED.
*/
if (parent_con->conenforced && !child_con->conenforced)
ereport(ERROR,
@@ -20509,9 +20917,9 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
{
ForeignKeyCacheInfo *fk = lfirst(cell);
HeapTuple contup;
+ HeapTuple parentContup;
Form_pg_constraint conform;
- Oid insertTriggerOid,
- updateTriggerOid;
+ Oid parentConstrIsEnforced;
contup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
if (!HeapTupleIsValid(contup))
@@ -20530,25 +20938,55 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
continue;
}
+ /* Get the enforcibility of the parent constraint */
+ parentContup = SearchSysCache1(CONSTROID,
+ ObjectIdGetDatum(conform->conparentid));
+ if (!HeapTupleIsValid(parentContup))
+ elog(ERROR, "cache lookup failed for constraint %u",
+ conform->conparentid);
+ parentConstrIsEnforced =
+ ((Form_pg_constraint) GETSTRUCT(parentContup))->conenforced;
+ ReleaseSysCache(parentContup);
+
/*
* 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);
+ /*
+ * Unsetting the parent is sufficient when the parent constraint is
+ * NOT ENFORCED and the child constraint is ENFORCED, as we link them
+ * by setting the constraint parent, while leaving the rest unchanged.
+ * For more details, see AttachPartitionForeignKey().
+ */
+ if (!parentConstrIsEnforced && fk->conenforced)
+ {
+ ReleaseSysCache(contup);
+ continue;
+ }
+
/*
* Also, look up the partition's "check" triggers corresponding to the
- * constraint being detached and detach them from the parent triggers.
+ * 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.
*/
- 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));
+ 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
@@ -20588,8 +21026,9 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
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 = true;
+ fkconstraint->initially_valid = conform->convalidated;
/* a few irrelevant fields omitted here */
fkconstraint->pktable = NULL;
fkconstraint->fk_attrs = NIL;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0fc502a3a406..27257ec5dc11 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2662,6 +2662,8 @@ alter_table_cmd:
n->subtype = AT_AlterConstraint;
n->def = (Node *) c;
c->conname = $3;
+ if ($4 & (CAS_NOT_ENFORCED | CAS_ENFORCED))
+ c->alterEnforceability = true;
if ($4 & (CAS_DEFERRABLE | CAS_NOT_DEFERRABLE |
CAS_INITIALLY_DEFERRED | CAS_INITIALLY_IMMEDIATE))
c->alterDeferrability = true;
@@ -2670,7 +2672,10 @@ alter_table_cmd:
processCASbits($4, @4, "FOREIGN KEY",
&c->deferrable,
&c->initdeferred,
- NULL, NULL, &c->noinherit, yyscanner);
+ &c->is_enforced,
+ NULL,
+ &c->noinherit,
+ yyscanner);
$$ = (Node *) n;
}
/* ALTER TABLE ALTER CONSTRAINT INHERIT */
@@ -4334,8 +4339,8 @@ ConstraintElem:
n->fk_del_set_cols = ($11)->deleteAction->cols;
processCASbits($12, @12, "FOREIGN KEY",
&n->deferrable, &n->initdeferred,
- NULL, &n->skip_validation, NULL,
- yyscanner);
+ &n->is_enforced, &n->skip_validation,
+ NULL, yyscanner);
n->initially_valid = !n->skip_validation;
$$ = (Node *) n;
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 9c1541e1fea9..62015431fdf1 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -2962,8 +2962,10 @@ transformFKConstraints(CreateStmtContext *cxt,
/*
* If CREATE TABLE or adding a column with NULL default, we can safely
- * skip validation of FK constraints, and nonetheless mark them valid.
- * (This will override any user-supplied NOT VALID flag.)
+ * skip validation of FK constraints, and mark them as valid based on the
+ * constraint enforcement flag, since NOT ENFORCED constraints must always
+ * be marked as NOT VALID. (This will override any user-supplied NOT VALID
+ * flag.)
*/
if (skipValidation)
{
@@ -2972,7 +2974,7 @@ transformFKConstraints(CreateStmtContext *cxt,
Constraint *constraint = (Constraint *) lfirst(fkclist);
constraint->skip_validation = true;
- constraint->initially_valid = true;
+ constraint->initially_valid = constraint->is_enforced;
}
}
@@ -3967,7 +3969,8 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
case CONSTR_ATTR_ENFORCED:
if (lastprimarycon == NULL ||
- lastprimarycon->contype != CONSTR_CHECK)
+ (lastprimarycon->contype != CONSTR_CHECK &&
+ lastprimarycon->contype != CONSTR_FOREIGN))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced ENFORCED clause"),
@@ -3983,7 +3986,8 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
case CONSTR_ATTR_NOT_ENFORCED:
if (lastprimarycon == NULL ||
- lastprimarycon->contype != CONSTR_CHECK)
+ (lastprimarycon->contype != CONSTR_CHECK &&
+ lastprimarycon->contype != CONSTR_FOREIGN))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced NOT ENFORCED clause"),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 9f54a9e72b73..18a14ae186ef 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4666,11 +4666,6 @@ RelationGetFKeyList(Relation relation)
if (relation->rd_fkeyvalid)
return relation->rd_fkeylist;
- /* Fast path: non-partitioned tables without triggers can't have FKs */
- if (!relation->rd_rel->relhastriggers &&
- relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
- return NIL;
-
/*
* We build the list we intend to return (in the caller's context) while
* doing the scan. After successfully completing the scan, we copy that
@@ -4702,6 +4697,7 @@ RelationGetFKeyList(Relation relation)
info->conoid = constraint->oid;
info->conrelid = constraint->conrelid;
info->confrelid = constraint->confrelid;
+ info->conenforced = constraint->conenforced;
DeconstructFkConstraintRow(htup, &info->nkeys,
info->conkey,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 84a78625820c..f6666e1c986c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -8062,13 +8062,7 @@ getConstraints(Archive *fout, TableInfo tblinfo[], int numTables)
{
TableInfo *tinfo = &tblinfo[i];
- /*
- * For partitioned tables, foreign keys have no triggers so they must
- * be included anyway in case some foreign keys are defined.
- */
- if ((!tinfo->hastriggers &&
- tinfo->relkind != RELKIND_PARTITIONED_TABLE) ||
- !(tinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ if (!(tinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
continue;
/* OK, we need info for this table */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index bf565afcc4ef..e4a4c5071ddc 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2550,136 +2550,124 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /*
- * Print foreign-key constraints (there are none if no triggers,
- * except if the table is partitioned, in which case the triggers
- * appear in the partitions)
- */
- if (tableinfo.hastriggers ||
- tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
+ /* Print foreign-key constraints */
+ if (pset.sversion >= 120000 &&
+ (tableinfo.ispartition || tableinfo.relkind == RELKIND_PARTITIONED_TABLE))
{
- if (pset.sversion >= 120000 &&
- (tableinfo.ispartition || tableinfo.relkind == RELKIND_PARTITIONED_TABLE))
- {
- /*
- * Put the constraints defined in this table first, followed
- * by the constraints defined in ancestor partitioned tables.
- */
- printfPQExpBuffer(&buf,
- "SELECT conrelid = '%s'::pg_catalog.regclass AS sametable,\n"
- " conname,\n"
- " pg_catalog.pg_get_constraintdef(oid, true) AS condef,\n"
- " conrelid::pg_catalog.regclass AS ontable\n"
- " FROM pg_catalog.pg_constraint,\n"
- " pg_catalog.pg_partition_ancestors('%s')\n"
- " WHERE conrelid = relid AND contype = " CppAsString2(CONSTRAINT_FOREIGN) " AND conparentid = 0\n"
- "ORDER BY sametable DESC, conname;",
- oid, oid);
- }
- else
- {
- printfPQExpBuffer(&buf,
- "SELECT true as sametable, conname,\n"
- " pg_catalog.pg_get_constraintdef(r.oid, true) as condef,\n"
- " conrelid::pg_catalog.regclass AS ontable\n"
- "FROM pg_catalog.pg_constraint r\n"
- "WHERE r.conrelid = '%s' AND r.contype = " CppAsString2(CONSTRAINT_FOREIGN) "\n",
- oid);
-
- if (pset.sversion >= 120000)
- appendPQExpBufferStr(&buf, " AND conparentid = 0\n");
- appendPQExpBufferStr(&buf, "ORDER BY conname");
- }
+ /*
+ * Put the constraints defined in this table first, followed
+ * by the constraints defined in ancestor partitioned tables.
+ */
+ printfPQExpBuffer(&buf,
+ "SELECT conrelid = '%s'::pg_catalog.regclass AS sametable,\n"
+ " conname,\n"
+ " pg_catalog.pg_get_constraintdef(oid, true) AS condef,\n"
+ " conrelid::pg_catalog.regclass AS ontable\n"
+ " FROM pg_catalog.pg_constraint,\n"
+ " pg_catalog.pg_partition_ancestors('%s')\n"
+ " WHERE conrelid = relid AND contype = " CppAsString2(CONSTRAINT_FOREIGN) " AND conparentid = 0\n"
+ "ORDER BY sametable DESC, conname;",
+ oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT true as sametable, conname,\n"
+ " pg_catalog.pg_get_constraintdef(r.oid, true) as condef,\n"
+ " conrelid::pg_catalog.regclass AS ontable\n"
+ "FROM pg_catalog.pg_constraint r\n"
+ "WHERE r.conrelid = '%s' AND r.contype = " CppAsString2(CONSTRAINT_FOREIGN) "\n",
+ oid);
- result = PSQLexec(buf.data);
- if (!result)
- goto error_return;
- else
- tuples = PQntuples(result);
+ if (pset.sversion >= 120000)
+ appendPQExpBufferStr(&buf, " AND conparentid = 0\n");
+ appendPQExpBufferStr(&buf, "ORDER BY conname");
+ }
- if (tuples > 0)
- {
- int i_sametable = PQfnumber(result, "sametable"),
- i_conname = PQfnumber(result, "conname"),
- i_condef = PQfnumber(result, "condef"),
- i_ontable = PQfnumber(result, "ontable");
+ result = PSQLexec(buf.data);
+ if (!result)
+ goto error_return;
+ else
+ tuples = PQntuples(result);
- printTableAddFooter(&cont, _("Foreign-key constraints:"));
- for (i = 0; i < tuples; i++)
- {
- /*
- * Print untranslated constraint name and definition. Use
- * a "TABLE tab" prefix when the constraint is defined in
- * a parent partitioned table.
- */
- if (strcmp(PQgetvalue(result, i, i_sametable), "f") == 0)
- printfPQExpBuffer(&buf, " TABLE \"%s\" CONSTRAINT \"%s\" %s",
- PQgetvalue(result, i, i_ontable),
- PQgetvalue(result, i, i_conname),
- PQgetvalue(result, i, i_condef));
- else
- printfPQExpBuffer(&buf, " \"%s\" %s",
- PQgetvalue(result, i, i_conname),
- PQgetvalue(result, i, i_condef));
+ if (tuples > 0)
+ {
+ int i_sametable = PQfnumber(result, "sametable"),
+ i_conname = PQfnumber(result, "conname"),
+ i_condef = PQfnumber(result, "condef"),
+ i_ontable = PQfnumber(result, "ontable");
- printTableAddFooter(&cont, buf.data);
- }
+ printTableAddFooter(&cont, _("Foreign-key constraints:"));
+ for (i = 0; i < tuples; i++)
+ {
+ /*
+ * Print untranslated constraint name and definition. Use
+ * a "TABLE tab" prefix when the constraint is defined in
+ * a parent partitioned table.
+ */
+ if (strcmp(PQgetvalue(result, i, i_sametable), "f") == 0)
+ printfPQExpBuffer(&buf, " TABLE \"%s\" CONSTRAINT \"%s\" %s",
+ PQgetvalue(result, i, i_ontable),
+ PQgetvalue(result, i, i_conname),
+ PQgetvalue(result, i, i_condef));
+ else
+ printfPQExpBuffer(&buf, " \"%s\" %s",
+ PQgetvalue(result, i, i_conname),
+ PQgetvalue(result, i, i_condef));
+
+ printTableAddFooter(&cont, buf.data);
}
- PQclear(result);
}
+ PQclear(result);
/* print incoming foreign-key references */
- if (tableinfo.hastriggers ||
- tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
+ if (pset.sversion >= 120000)
{
- if (pset.sversion >= 120000)
- {
- printfPQExpBuffer(&buf,
- "SELECT conname, conrelid::pg_catalog.regclass AS ontable,\n"
- " pg_catalog.pg_get_constraintdef(oid, true) AS condef\n"
- " FROM pg_catalog.pg_constraint c\n"
- " WHERE confrelid IN (SELECT pg_catalog.pg_partition_ancestors('%s')\n"
- " UNION ALL VALUES ('%s'::pg_catalog.regclass))\n"
- " AND contype = " CppAsString2(CONSTRAINT_FOREIGN) " AND conparentid = 0\n"
- "ORDER BY conname;",
- oid, oid);
- }
- else
- {
- printfPQExpBuffer(&buf,
- "SELECT conname, conrelid::pg_catalog.regclass AS ontable,\n"
- " pg_catalog.pg_get_constraintdef(oid, true) AS condef\n"
- " FROM pg_catalog.pg_constraint\n"
- " WHERE confrelid = %s AND contype = " CppAsString2(CONSTRAINT_FOREIGN) "\n"
- "ORDER BY conname;",
- oid);
- }
+ printfPQExpBuffer(&buf,
+ "SELECT conname, conrelid::pg_catalog.regclass AS ontable,\n"
+ " pg_catalog.pg_get_constraintdef(oid, true) AS condef\n"
+ " FROM pg_catalog.pg_constraint c\n"
+ " WHERE confrelid IN (SELECT pg_catalog.pg_partition_ancestors('%s')\n"
+ " UNION ALL VALUES ('%s'::pg_catalog.regclass))\n"
+ " AND contype = " CppAsString2(CONSTRAINT_FOREIGN) " AND conparentid = 0\n"
+ "ORDER BY conname;",
+ oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT conname, conrelid::pg_catalog.regclass AS ontable,\n"
+ " pg_catalog.pg_get_constraintdef(oid, true) AS condef\n"
+ " FROM pg_catalog.pg_constraint\n"
+ " WHERE confrelid = %s AND contype = " CppAsString2(CONSTRAINT_FOREIGN) "\n"
+ "ORDER BY conname;",
+ oid);
+ }
- result = PSQLexec(buf.data);
- if (!result)
- goto error_return;
- else
- tuples = PQntuples(result);
+ result = PSQLexec(buf.data);
+ if (!result)
+ goto error_return;
+ else
+ tuples = PQntuples(result);
- if (tuples > 0)
- {
- int i_conname = PQfnumber(result, "conname"),
- i_ontable = PQfnumber(result, "ontable"),
- i_condef = PQfnumber(result, "condef");
+ if (tuples > 0)
+ {
+ int i_conname = PQfnumber(result, "conname"),
+ i_ontable = PQfnumber(result, "ontable"),
+ i_condef = PQfnumber(result, "condef");
- printTableAddFooter(&cont, _("Referenced by:"));
- for (i = 0; i < tuples; i++)
- {
- printfPQExpBuffer(&buf, " TABLE \"%s\" CONSTRAINT \"%s\" %s",
- PQgetvalue(result, i, i_ontable),
- PQgetvalue(result, i, i_conname),
- PQgetvalue(result, i, i_condef));
+ printTableAddFooter(&cont, _("Referenced by:"));
+ for (i = 0; i < tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " TABLE \"%s\" CONSTRAINT \"%s\" %s",
+ PQgetvalue(result, i, i_ontable),
+ PQgetvalue(result, i, i_conname),
+ PQgetvalue(result, i, i_condef));
- printTableAddFooter(&cont, buf.data);
- }
+ printTableAddFooter(&cont, buf.data);
}
- PQclear(result);
}
+ PQclear(result);
/* print any row-level policies */
if (pset.sversion >= 90500)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index df331b1c0d99..00fefa9483a7 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2495,6 +2495,8 @@ typedef struct ATAlterConstraint
{
NodeTag type;
char *conname; /* Constraint name */
+ bool alterEnforceability; /* changing enforceability properties? */
+ bool is_enforced; /* ENFORCED? */
bool alterDeferrability; /* changing deferrability properties? */
bool deferrable; /* DEFERRABLE? */
bool initdeferred; /* INITIALLY DEFERRED? */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d94fddd7cef7..b552359915f1 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -284,6 +284,9 @@ typedef struct ForeignKeyCacheInfo
/* number of columns in the foreign key */
int nkeys;
+ /* Is enforced ? */
+ bool conenforced;
+
/*
* these arrays each have nkeys valid entries:
*/
diff --git a/src/test/regress/expected/constraints.out b/src/test/regress/expected/constraints.out
index 4f39100fcdf9..a719d2f74e9d 100644
--- a/src/test/regress/expected/constraints.out
+++ b/src/test/regress/expected/constraints.out
@@ -745,13 +745,9 @@ ERROR: misplaced NOT ENFORCED clause
LINE 1: CREATE TABLE UNIQUE_NOTEN_TBL(i int UNIQUE NOT ENFORCED);
^
ALTER TABLE unique_tbl ALTER CONSTRAINT unique_tbl_i_key ENFORCED;
-ERROR: FOREIGN KEY constraints cannot be marked ENFORCED
-LINE 1: ...TABLE unique_tbl ALTER CONSTRAINT unique_tbl_i_key ENFORCED;
- ^
+ERROR: cannot alter enforceability of constraint "unique_tbl_i_key" of relation "unique_tbl"
ALTER TABLE unique_tbl ALTER CONSTRAINT unique_tbl_i_key NOT ENFORCED;
-ERROR: FOREIGN KEY constraints cannot be marked NOT ENFORCED
-LINE 1: ...ABLE unique_tbl ALTER CONSTRAINT unique_tbl_i_key NOT ENFORC...
- ^
+ERROR: cannot alter enforceability of constraint "unique_tbl_i_key" of relation "unique_tbl"
DROP TABLE unique_tbl;
--
-- EXCLUDE constraints
diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out
index 7f678349a8e9..404c9c66bcd5 100644
--- a/src/test/regress/expected/foreign_key.out
+++ b/src/test/regress/expected/foreign_key.out
@@ -1,21 +1,49 @@
--
-- FOREIGN KEY
--
--- MATCH FULL
+-- NOT ENFORCED
--
-- First test, check and cascade
--
CREATE TABLE PKTABLE ( ptest1 int PRIMARY KEY, ptest2 text );
-CREATE TABLE FKTABLE ( ftest1 int REFERENCES PKTABLE MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, ftest2 int );
--- Insert test data into PKTABLE
+CREATE TABLE FKTABLE ( ftest1 int CONSTRAINT fktable_ftest1_fkey REFERENCES PKTABLE MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE NOT ENFORCED,
+ ftest2 int );
+-- Inserting into the foreign key table will not result in an error, even if
+-- there is no matching key in the referenced table.
+INSERT INTO FKTABLE VALUES (1, 2);
+INSERT INTO FKTABLE VALUES (2, 3);
+-- Check FKTABLE
+SELECT * FROM FKTABLE;
+ ftest1 | ftest2
+--------+--------
+ 1 | 2
+ 2 | 3
+(2 rows)
+
+-- Reverting it back to ENFORCED will result in failure because constraint validation will be triggered,
+-- as it was previously in a valid state.
+ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_ftest1_fkey ENFORCED;
+ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL: Key (ftest1)=(1) is not present in table "pktable".
+-- Insert referenced data that satisfies the constraint, then attempted to
+-- change it.
INSERT INTO PKTABLE VALUES (1, 'Test1');
INSERT INTO PKTABLE VALUES (2, 'Test2');
+ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_ftest1_fkey ENFORCED;
+-- Any further inserts will fail due to the enforcement.
+INSERT INTO FKTABLE VALUES (3, 4);
+ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL: Key (ftest1)=(3) is not present in table "pktable".
+--
+-- MATCH FULL
+--
+-- First test, check and cascade
+--
+-- Insert test data into PKTABLE
INSERT INTO PKTABLE VALUES (3, 'Test3');
INSERT INTO PKTABLE VALUES (4, 'Test4');
INSERT INTO PKTABLE VALUES (5, 'Test5');
-- Insert successful rows into FK TABLE
-INSERT INTO FKTABLE VALUES (1, 2);
-INSERT INTO FKTABLE VALUES (2, 3);
INSERT INTO FKTABLE VALUES (3, 4);
INSERT INTO FKTABLE VALUES (NULL, 1);
-- Insert a failed row into FK TABLE
@@ -351,6 +379,43 @@ INSERT INTO FKTABLE VALUES (1, NULL);
ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1, ftest2) REFERENCES PKTABLE MATCH FULL;
ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_ftest2_fkey"
DETAIL: MATCH FULL does not allow mixing of null and nonnull key values.
+-- Modifying other attributes of a constraint should not affect its enforceability, and vice versa
+ALTER TABLE FKTABLE ADD CONSTRAINT fk_con FOREIGN KEY(ftest1, ftest2) REFERENCES PKTABLE NOT VALID NOT ENFORCED;
+ALTER TABLE FKTABLE ALTER CONSTRAINT fk_con DEFERRABLE INITIALLY DEFERRED;
+SELECT condeferrable, condeferred, conenforced, convalidated
+FROM pg_constraint WHERE conname = 'fk_con';
+ condeferrable | condeferred | conenforced | convalidated
+---------------+-------------+-------------+--------------
+ t | t | f | f
+(1 row)
+
+ALTER TABLE FKTABLE ALTER CONSTRAINT fk_con NOT ENFORCED;
+SELECT condeferrable, condeferred, conenforced, convalidated
+FROM pg_constraint WHERE conname = 'fk_con';
+ condeferrable | condeferred | conenforced | convalidated
+---------------+-------------+-------------+--------------
+ t | t | f | f
+(1 row)
+
+-- Enforceability also changes the validate state, as data validation will be
+-- performed during this transformation.
+ALTER TABLE FKTABLE ALTER CONSTRAINT fk_con ENFORCED;
+SELECT condeferrable, condeferred, conenforced, convalidated
+FROM pg_constraint WHERE conname = 'fk_con';
+ condeferrable | condeferred | conenforced | convalidated
+---------------+-------------+-------------+--------------
+ t | t | t | t
+(1 row)
+
+-- Can change enforceability and deferrability together
+ALTER TABLE FKTABLE ALTER CONSTRAINT fk_con NOT ENFORCED NOT DEFERRABLE;
+SELECT condeferrable, condeferred, conenforced, convalidated
+FROM pg_constraint WHERE conname = 'fk_con';
+ condeferrable | condeferred | conenforced | convalidated
+---------------+-------------+-------------+--------------
+ f | f | f | f
+(1 row)
+
DROP TABLE FKTABLE;
DROP TABLE PKTABLE;
-- MATCH SIMPLE
@@ -1276,6 +1341,13 @@ INSERT INTO fktable VALUES (0, 20);
ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey"
DETAIL: Key (fk)=(20) is not present in table "pktable".
COMMIT;
+ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT ENFORCED;
+BEGIN;
+-- doesn't match FK, but no error.
+UPDATE pktable SET id = 10 WHERE id = 5;
+-- doesn't match PK, but no error.
+INSERT INTO fktable VALUES (0, 20);
+ROLLBACK;
-- try additional syntax
ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE;
-- illegal options
@@ -1289,6 +1361,14 @@ ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT VALID;
ERROR: FOREIGN KEY constraints cannot be marked NOT VALID
LINE 1: ...ER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT VALID;
^
+ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey ENFORCED NOT ENFORCED;
+ERROR: conflicting constraint properties
+LINE 1: ...fktable ALTER CONSTRAINT fktable_fk_fkey ENFORCED NOT ENFORC...
+ ^
+CREATE TEMP TABLE fktable2 (fk int references pktable ENFORCED NOT ENFORCED);
+ERROR: multiple ENFORCED/NOT ENFORCED clauses not allowed
+LINE 1: ...ABLE fktable2 (fk int references pktable ENFORCED NOT ENFORC...
+ ^
-- test order of firing of FK triggers when several RI-induced changes need to
-- be made to the same row. This was broken by subtransaction-related
-- changes in 8.0.
@@ -1586,18 +1666,77 @@ ALTER TABLE fk_partitioned_fk DROP COLUMN fdrop1;
CREATE TABLE fk_partitioned_fk_1 (fdrop1 int, fdrop2 int, a int, fdrop3 int, b int);
ALTER TABLE fk_partitioned_fk_1 DROP COLUMN fdrop1, DROP COLUMN fdrop2, DROP COLUMN fdrop3;
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_1 FOR VALUES FROM (0,0) TO (1000,1000);
-ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk;
+ALTER TABLE fk_partitioned_fk ADD CONSTRAINT fk_partitioned_fk_a_b_fkey FOREIGN KEY (a, b)
+ REFERENCES fk_notpartitioned_pk NOT ENFORCED;
CREATE TABLE fk_partitioned_fk_2 (b int, fdrop1 int, fdrop2 int, a int);
ALTER TABLE fk_partitioned_fk_2 DROP COLUMN fdrop1, DROP COLUMN fdrop2;
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES FROM (1000,1000) TO (2000,2000);
+ALTER TABLE fk_partitioned_fk ALTER CONSTRAINT fk_partitioned_fk_a_b_fkey ENFORCED;
CREATE TABLE fk_partitioned_fk_3 (fdrop1 int, fdrop2 int, fdrop3 int, fdrop4 int, b int, a int)
PARTITION BY HASH (a);
ALTER TABLE fk_partitioned_fk_3 DROP COLUMN fdrop1, DROP COLUMN fdrop2,
DROP COLUMN fdrop3, DROP COLUMN fdrop4;
CREATE TABLE fk_partitioned_fk_3_0 PARTITION OF fk_partitioned_fk_3 FOR VALUES WITH (MODULUS 5, REMAINDER 0);
CREATE TABLE fk_partitioned_fk_3_1 PARTITION OF fk_partitioned_fk_3 FOR VALUES WITH (MODULUS 5, REMAINDER 1);
+-- Merge the not-enforced parent constraint with the enforced and not-enforced child constraints
+ALTER TABLE fk_partitioned_fk_3_0 ADD CONSTRAINT fk_partitioned_fk_3_0_a_b_fkey FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk ENFORCED;
+ALTER TABLE fk_partitioned_fk_3_1 ADD CONSTRAINT fk_partitioned_fk_3_1_a_b_fkey FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk NOT ENFORCED;
+ALTER TABLE fk_partitioned_fk_3 ADD CONSTRAINT fk_partitioned_fk_3_a_b_fkey FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk NOT ENFORCED;
+-- Merge the non-enforced parent constraint with both the enforced and
+-- non-enforced child constraints, where the referenced table is partitioned.
+CREATE TABLE fk_partitioned_pk (a int, b int, PRIMARY KEY (a, b)) PARTITION BY RANGE (a, b);
+CREATE TABLE fk_partitioned_pk_1 PARTITION OF fk_partitioned_pk FOR VALUES FROM (0,0) TO (1000,1000);
+-- Merge the enforced parent constraint with the enforced and not-enforced child constraints.
+ALTER TABLE fk_partitioned_fk_3_0 ADD CONSTRAINT fk_partitioned_fk_3_0_a_b_fkey1 FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk ENFORCED;
+ALTER TABLE fk_partitioned_fk_3_1 ADD CONSTRAINT fk_partitioned_fk_3_1_a_b_fkey1 FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk NOT ENFORCED;
+ALTER TABLE fk_partitioned_fk_3 ADD CONSTRAINT fk_partitioned_fk_3_a_b_fkey1 FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk ENFORCED;
+SELECT conname, conenforced, convalidated, conrelid::regclass, confrelid::regclass
+FROM pg_constraint WHERE conrelid::regclass::text like 'fk_partitioned_fk_3%' ORDER BY oid;
+ conname | conenforced | convalidated | conrelid | confrelid
+---------------------------------+-------------+--------------+-----------------------+----------------------
+ fk_partitioned_fk_3_0_a_b_fkey | t | t | fk_partitioned_fk_3_0 | fk_notpartitioned_pk
+ fk_partitioned_fk_3_1_a_b_fkey | f | f | fk_partitioned_fk_3_1 | fk_notpartitioned_pk
+ fk_partitioned_fk_3_a_b_fkey | f | f | fk_partitioned_fk_3 | fk_notpartitioned_pk
+ fk_partitioned_fk_3_0_a_b_fkey1 | t | t | fk_partitioned_fk_3_0 | fk_partitioned_pk
+ fk_partitioned_fk_3_1_a_b_fkey1 | t | t | fk_partitioned_fk_3_1 | fk_partitioned_pk
+ fk_partitioned_fk_3_a_b_fkey1 | t | t | fk_partitioned_fk_3 | fk_partitioned_pk
+ fk_partitioned_fk_3_a_b_fkey2 | t | t | fk_partitioned_fk_3 | fk_partitioned_pk_1
+(7 rows)
+
+ALTER TABLE fk_partitioned_fk_3 DETACH PARTITION fk_partitioned_fk_3_0;
+ALTER TABLE fk_partitioned_fk_3 ATTACH PARTITION fk_partitioned_fk_3_0 FOR VALUES WITH (MODULUS 5, REMAINDER 0);
+SELECT conname, conenforced, convalidated, conrelid::regclass, confrelid::regclass
+FROM pg_constraint WHERE conrelid::regclass::text like 'fk_partitioned_fk_3%' ORDER BY oid;
+ conname | conenforced | convalidated | conrelid | confrelid
+---------------------------------+-------------+--------------+-----------------------+----------------------
+ fk_partitioned_fk_3_0_a_b_fkey | t | t | fk_partitioned_fk_3_0 | fk_notpartitioned_pk
+ fk_partitioned_fk_3_1_a_b_fkey | f | f | fk_partitioned_fk_3_1 | fk_notpartitioned_pk
+ fk_partitioned_fk_3_a_b_fkey | f | f | fk_partitioned_fk_3 | fk_notpartitioned_pk
+ fk_partitioned_fk_3_0_a_b_fkey1 | t | t | fk_partitioned_fk_3_0 | fk_partitioned_pk
+ fk_partitioned_fk_3_1_a_b_fkey1 | t | t | fk_partitioned_fk_3_1 | fk_partitioned_pk
+ fk_partitioned_fk_3_a_b_fkey1 | t | t | fk_partitioned_fk_3 | fk_partitioned_pk
+ fk_partitioned_fk_3_a_b_fkey2 | t | t | fk_partitioned_fk_3 | fk_partitioned_pk_1
+(7 rows)
+
+ALTER TABLE fk_partitioned_fk_3 ALTER CONSTRAINT fk_partitioned_fk_3_a_b_fkey1 NOT ENFORCED;
+-- Merging an enforced parent constraint (validated) with a not-enforced child
+-- constraint will implicitly change the child constraint to enforced and apply
+-- the validation as well.
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_3
FOR VALUES FROM (2000,2000) TO (3000,3000);
+SELECT conname, conenforced, convalidated, conrelid::regclass, confrelid::regclass
+FROM pg_constraint WHERE conrelid::regclass::text like 'fk_partitioned_fk_3%' ORDER BY oid;
+ conname | conenforced | convalidated | conrelid | confrelid
+---------------------------------+-------------+--------------+-----------------------+----------------------
+ fk_partitioned_fk_3_0_a_b_fkey | t | t | fk_partitioned_fk_3_0 | fk_notpartitioned_pk
+ fk_partitioned_fk_3_1_a_b_fkey | t | t | fk_partitioned_fk_3_1 | fk_notpartitioned_pk
+ fk_partitioned_fk_3_a_b_fkey | t | t | fk_partitioned_fk_3 | fk_notpartitioned_pk
+ fk_partitioned_fk_3_0_a_b_fkey1 | f | f | fk_partitioned_fk_3_0 | fk_partitioned_pk
+ fk_partitioned_fk_3_1_a_b_fkey1 | f | f | fk_partitioned_fk_3_1 | fk_partitioned_pk
+ fk_partitioned_fk_3_a_b_fkey1 | f | f | fk_partitioned_fk_3 | fk_partitioned_pk
+ fk_partitioned_fk_3_a_b_fkey2 | f | f | fk_partitioned_fk_3 | fk_partitioned_pk_1
+(7 rows)
+
-- Creating a foreign key with ONLY on a partitioned table referencing
-- a non-partitioned table fails.
ALTER TABLE ONLY fk_partitioned_fk ADD FOREIGN KEY (a, b)
@@ -1618,16 +1757,16 @@ INSERT INTO fk_partitioned_fk_2 (a,b) VALUES (1500, 1501);
ERROR: insert or update on table "fk_partitioned_fk_2" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
DETAIL: Key (a, b)=(1500, 1501) is not present in table "fk_notpartitioned_pk".
INSERT INTO fk_partitioned_fk (a,b) VALUES (2500, 2502);
-ERROR: insert or update on table "fk_partitioned_fk_3_1" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
+ERROR: insert or update on table "fk_partitioned_fk_3_1" violates foreign key constraint "fk_partitioned_fk_3_1_a_b_fkey"
DETAIL: Key (a, b)=(2500, 2502) is not present in table "fk_notpartitioned_pk".
INSERT INTO fk_partitioned_fk_3 (a,b) VALUES (2500, 2502);
-ERROR: insert or update on table "fk_partitioned_fk_3_1" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
+ERROR: insert or update on table "fk_partitioned_fk_3_1" violates foreign key constraint "fk_partitioned_fk_3_1_a_b_fkey"
DETAIL: Key (a, b)=(2500, 2502) is not present in table "fk_notpartitioned_pk".
INSERT INTO fk_partitioned_fk (a,b) VALUES (2501, 2503);
-ERROR: insert or update on table "fk_partitioned_fk_3_0" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
+ERROR: insert or update on table "fk_partitioned_fk_3_0" violates foreign key constraint "fk_partitioned_fk_3_0_a_b_fkey"
DETAIL: Key (a, b)=(2501, 2503) is not present in table "fk_notpartitioned_pk".
INSERT INTO fk_partitioned_fk_3 (a,b) VALUES (2501, 2503);
-ERROR: insert or update on table "fk_partitioned_fk_3_0" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
+ERROR: insert or update on table "fk_partitioned_fk_3_0" violates foreign key constraint "fk_partitioned_fk_3_0_a_b_fkey"
DETAIL: Key (a, b)=(2501, 2503) is not present in table "fk_notpartitioned_pk".
-- but if we insert the values that make them valid, then they work
INSERT INTO fk_notpartitioned_pk VALUES (500, 501), (1500, 1501),
@@ -1638,7 +1777,7 @@ INSERT INTO fk_partitioned_fk (a,b) VALUES (2500, 2502);
INSERT INTO fk_partitioned_fk (a,b) VALUES (2501, 2503);
-- this update fails because there is no referenced row
UPDATE fk_partitioned_fk SET a = a + 1 WHERE a = 2501;
-ERROR: insert or update on table "fk_partitioned_fk_3_1" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
+ERROR: insert or update on table "fk_partitioned_fk_3_1" violates foreign key constraint "fk_partitioned_fk_3_1_a_b_fkey"
DETAIL: Key (a, b)=(2502, 2503) is not present in table "fk_notpartitioned_pk".
-- but we can fix it thusly:
INSERT INTO fk_notpartitioned_pk (a,b) VALUES (2502, 2503);
@@ -1665,6 +1804,67 @@ Indexes:
Referenced by:
TABLE "fk_partitioned_fk" CONSTRAINT "fk_partitioned_fk_a_b_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b)
+-- Check the exsting FK trigger
+SELECT conname, tgrelid::regclass as tgrel, regexp_replace(tgname, '[0-9]+', 'N') as tgname, tgtype
+FROM pg_trigger t JOIN pg_constraint c ON (t.tgconstraint = c.oid)
+WHERE tgrelid IN (SELECT relid FROM pg_partition_tree('fk_partitioned_fk'::regclass)
+ UNION ALL SELECT 'fk_notpartitioned_pk'::regclass)
+ORDER BY tgrelid, tgtype;
+ conname | tgrel | tgname | tgtype
+--------------------------------+-----------------------+--------------------------+--------
+ fk_partitioned_fk_a_b_fkey | fk_notpartitioned_pk | RI_ConstraintTrigger_a_N | 9
+ fk_partitioned_fk_a_b_fkey | fk_notpartitioned_pk | RI_ConstraintTrigger_a_N | 17
+ fk_partitioned_fk_a_b_fkey | fk_partitioned_fk | RI_ConstraintTrigger_c_N | 5
+ fk_partitioned_fk_a_b_fkey | fk_partitioned_fk | RI_ConstraintTrigger_c_N | 17
+ fk_partitioned_fk_a_b_fkey | fk_partitioned_fk_1 | RI_ConstraintTrigger_c_N | 5
+ fk_partitioned_fk_a_b_fkey | fk_partitioned_fk_1 | RI_ConstraintTrigger_c_N | 17
+ fk_partitioned_fk_a_b_fkey | fk_partitioned_fk_2 | RI_ConstraintTrigger_c_N | 5
+ fk_partitioned_fk_a_b_fkey | fk_partitioned_fk_2 | RI_ConstraintTrigger_c_N | 17
+ fk_partitioned_fk_3_a_b_fkey | fk_partitioned_fk_3 | RI_ConstraintTrigger_c_N | 5
+ fk_partitioned_fk_3_a_b_fkey | fk_partitioned_fk_3 | RI_ConstraintTrigger_c_N | 17
+ fk_partitioned_fk_3_0_a_b_fkey | fk_partitioned_fk_3_0 | RI_ConstraintTrigger_c_N | 5
+ fk_partitioned_fk_3_0_a_b_fkey | fk_partitioned_fk_3_0 | RI_ConstraintTrigger_c_N | 17
+ fk_partitioned_fk_3_1_a_b_fkey | fk_partitioned_fk_3_1 | RI_ConstraintTrigger_c_N | 5
+ fk_partitioned_fk_3_1_a_b_fkey | fk_partitioned_fk_3_1 | RI_ConstraintTrigger_c_N | 17
+(14 rows)
+
+ALTER TABLE fk_partitioned_fk ALTER CONSTRAINT fk_partitioned_fk_a_b_fkey NOT ENFORCED;
+-- No triggers
+SELECT conname, tgrelid::regclass as tgrel, regexp_replace(tgname, '[0-9]+', 'N') as tgname, tgtype
+FROM pg_trigger t JOIN pg_constraint c ON (t.tgconstraint = c.oid)
+WHERE tgrelid IN (SELECT relid FROM pg_partition_tree('fk_partitioned_fk'::regclass)
+ UNION ALL SELECT 'fk_notpartitioned_pk'::regclass)
+ORDER BY tgrelid, tgtype;
+ conname | tgrel | tgname | tgtype
+---------+-------+--------+--------
+(0 rows)
+
+-- Changing it back to ENFORCED will recreate the necessary triggers.
+ALTER TABLE fk_partitioned_fk ALTER CONSTRAINT fk_partitioned_fk_a_b_fkey ENFORCED;
+-- Should be exactly the same number of triggers found as before
+SELECT conname, tgrelid::regclass as tgrel, regexp_replace(tgname, '[0-9]+', 'N') as tgname, tgtype
+FROM pg_trigger t JOIN pg_constraint c ON (t.tgconstraint = c.oid)
+WHERE tgrelid IN (SELECT relid FROM pg_partition_tree('fk_partitioned_fk'::regclass)
+ UNION ALL SELECT 'fk_notpartitioned_pk'::regclass)
+ORDER BY tgrelid, tgtype;
+ conname | tgrel | tgname | tgtype
+--------------------------------+-----------------------+--------------------------+--------
+ fk_partitioned_fk_a_b_fkey | fk_notpartitioned_pk | RI_ConstraintTrigger_a_N | 9
+ fk_partitioned_fk_a_b_fkey | fk_notpartitioned_pk | RI_ConstraintTrigger_a_N | 17
+ fk_partitioned_fk_a_b_fkey | fk_partitioned_fk | RI_ConstraintTrigger_c_N | 5
+ fk_partitioned_fk_a_b_fkey | fk_partitioned_fk | RI_ConstraintTrigger_c_N | 17
+ fk_partitioned_fk_a_b_fkey | fk_partitioned_fk_1 | RI_ConstraintTrigger_c_N | 5
+ fk_partitioned_fk_a_b_fkey | fk_partitioned_fk_1 | RI_ConstraintTrigger_c_N | 17
+ fk_partitioned_fk_a_b_fkey | fk_partitioned_fk_2 | RI_ConstraintTrigger_c_N | 5
+ fk_partitioned_fk_a_b_fkey | fk_partitioned_fk_2 | RI_ConstraintTrigger_c_N | 17
+ fk_partitioned_fk_3_a_b_fkey | fk_partitioned_fk_3 | RI_ConstraintTrigger_c_N | 5
+ fk_partitioned_fk_3_a_b_fkey | fk_partitioned_fk_3 | RI_ConstraintTrigger_c_N | 17
+ fk_partitioned_fk_3_0_a_b_fkey | fk_partitioned_fk_3_0 | RI_ConstraintTrigger_c_N | 5
+ fk_partitioned_fk_3_0_a_b_fkey | fk_partitioned_fk_3_0 | RI_ConstraintTrigger_c_N | 17
+ fk_partitioned_fk_3_1_a_b_fkey | fk_partitioned_fk_3_1 | RI_ConstraintTrigger_c_N | 5
+ fk_partitioned_fk_3_1_a_b_fkey | fk_partitioned_fk_3_1 | RI_ConstraintTrigger_c_N | 17
+(14 rows)
+
ALTER TABLE fk_partitioned_fk DROP CONSTRAINT fk_partitioned_fk_a_b_fkey;
-- done.
DROP TABLE fk_notpartitioned_pk, fk_partitioned_fk;
@@ -1750,8 +1950,6 @@ WHERE conrelid::regclass::text like 'fk_partitioned_fk%' ORDER BY oid::regclass:
DROP TABLE fk_partitioned_fk, fk_notpartitioned_pk;
-- NOT VALID foreign key on a non-partitioned table referencing a partitioned table
-CREATE TABLE fk_partitioned_pk (a int, b int, PRIMARY KEY (a, b)) PARTITION BY RANGE (a, b);
-CREATE TABLE fk_partitioned_pk_1 PARTITION OF fk_partitioned_pk FOR VALUES FROM (0,0) TO (1000,1000);
CREATE TABLE fk_notpartitioned_fk (b int, a int);
ALTER TABLE fk_notpartitioned_fk ADD FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk NOT VALID;
-- Constraint will be invalid.
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 4d07d0bd79bb..caab1164fdb9 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1332,6 +1332,13 @@ NOTICE: merging constraint "inh_check_constraint5" with inherited definition
alter table p1 add constraint inh_check_constraint6 check (f1 < 10) not enforced;
alter table p1_c1 add constraint inh_check_constraint6 check (f1 < 10) enforced;
NOTICE: merging constraint "inh_check_constraint6" with inherited definition
+alter table p1_c1 add constraint inh_check_constraint9 check (f1 < 10) not valid enforced;
+alter table p1 add constraint inh_check_constraint9 check (f1 < 10) not enforced;
+NOTICE: merging constraint "inh_check_constraint9" with inherited definition
+-- the invalid state of the child constraint will be ignored here.
+alter table p1 add constraint inh_check_constraint10 check (f1 < 10) not enforced;
+alter table p1_c1 add constraint inh_check_constraint10 check (f1 < 10) not valid enforced;
+NOTICE: merging constraint "inh_check_constraint10" with inherited definition
create table p1_c2(f1 int constraint inh_check_constraint4 check (f1 < 10)) inherits(p1);
NOTICE: merging column "f1" with inherited definition
NOTICE: merging constraint "inh_check_constraint4" with inherited definition
@@ -1356,39 +1363,47 @@ ERROR: constraint "inh_check_constraint6" conflicts with NOT ENFORCED constrain
select conrelid::regclass::text as relname, conname, conislocal, coninhcount, conenforced, convalidated
from pg_constraint where conname like 'inh\_check\_constraint%'
order by 1, 2;
- relname | conname | conislocal | coninhcount | conenforced | convalidated
----------+-----------------------+------------+-------------+-------------+--------------
- p1 | inh_check_constraint1 | t | 0 | t | t
- p1 | inh_check_constraint2 | t | 0 | t | t
- p1 | inh_check_constraint3 | t | 0 | f | f
- p1 | inh_check_constraint4 | t | 0 | f | f
- p1 | inh_check_constraint5 | t | 0 | f | f
- p1 | inh_check_constraint6 | t | 0 | f | f
- p1 | inh_check_constraint8 | t | 0 | t | t
- p1_c1 | inh_check_constraint1 | t | 1 | t | t
- p1_c1 | inh_check_constraint2 | t | 1 | t | t
- p1_c1 | inh_check_constraint3 | t | 1 | f | f
- p1_c1 | inh_check_constraint4 | t | 1 | f | f
- p1_c1 | inh_check_constraint5 | t | 1 | t | t
- p1_c1 | inh_check_constraint6 | t | 1 | t | t
- p1_c1 | inh_check_constraint7 | t | 0 | f | f
- p1_c1 | inh_check_constraint8 | f | 1 | t | t
- p1_c2 | inh_check_constraint1 | f | 1 | t | t
- p1_c2 | inh_check_constraint2 | f | 1 | t | t
- p1_c2 | inh_check_constraint3 | f | 1 | f | f
- p1_c2 | inh_check_constraint4 | t | 1 | t | t
- p1_c2 | inh_check_constraint5 | f | 1 | f | f
- p1_c2 | inh_check_constraint6 | f | 1 | f | f
- p1_c2 | inh_check_constraint8 | f | 1 | t | t
- p1_c3 | inh_check_constraint1 | f | 2 | t | t
- p1_c3 | inh_check_constraint2 | f | 2 | t | t
- p1_c3 | inh_check_constraint3 | f | 2 | f | f
- p1_c3 | inh_check_constraint4 | f | 2 | f | f
- p1_c3 | inh_check_constraint5 | f | 2 | t | t
- p1_c3 | inh_check_constraint6 | f | 2 | t | t
- p1_c3 | inh_check_constraint7 | f | 1 | f | f
- p1_c3 | inh_check_constraint8 | f | 2 | t | t
-(30 rows)
+ relname | conname | conislocal | coninhcount | conenforced | convalidated
+---------+------------------------+------------+-------------+-------------+--------------
+ p1 | inh_check_constraint1 | t | 0 | t | t
+ p1 | inh_check_constraint10 | t | 0 | f | f
+ p1 | inh_check_constraint2 | t | 0 | t | t
+ p1 | inh_check_constraint3 | t | 0 | f | f
+ p1 | inh_check_constraint4 | t | 0 | f | f
+ p1 | inh_check_constraint5 | t | 0 | f | f
+ p1 | inh_check_constraint6 | t | 0 | f | f
+ p1 | inh_check_constraint8 | t | 0 | t | t
+ p1 | inh_check_constraint9 | t | 0 | f | f
+ p1_c1 | inh_check_constraint1 | t | 1 | t | t
+ p1_c1 | inh_check_constraint10 | t | 1 | t | t
+ p1_c1 | inh_check_constraint2 | t | 1 | t | t
+ p1_c1 | inh_check_constraint3 | t | 1 | f | f
+ p1_c1 | inh_check_constraint4 | t | 1 | f | f
+ p1_c1 | inh_check_constraint5 | t | 1 | t | t
+ p1_c1 | inh_check_constraint6 | t | 1 | t | t
+ p1_c1 | inh_check_constraint7 | t | 0 | f | f
+ p1_c1 | inh_check_constraint8 | f | 1 | t | t
+ p1_c1 | inh_check_constraint9 | t | 1 | t | f
+ p1_c2 | inh_check_constraint1 | f | 1 | t | t
+ p1_c2 | inh_check_constraint10 | f | 1 | f | f
+ p1_c2 | inh_check_constraint2 | f | 1 | t | t
+ p1_c2 | inh_check_constraint3 | f | 1 | f | f
+ p1_c2 | inh_check_constraint4 | t | 1 | t | t
+ p1_c2 | inh_check_constraint5 | f | 1 | f | f
+ p1_c2 | inh_check_constraint6 | f | 1 | f | f
+ p1_c2 | inh_check_constraint8 | f | 1 | t | t
+ p1_c2 | inh_check_constraint9 | f | 1 | f | f
+ p1_c3 | inh_check_constraint1 | f | 2 | t | t
+ p1_c3 | inh_check_constraint10 | f | 2 | t | t
+ p1_c3 | inh_check_constraint2 | f | 2 | t | t
+ p1_c3 | inh_check_constraint3 | f | 2 | f | f
+ p1_c3 | inh_check_constraint4 | f | 2 | f | f
+ p1_c3 | inh_check_constraint5 | f | 2 | t | t
+ p1_c3 | inh_check_constraint6 | f | 2 | t | t
+ p1_c3 | inh_check_constraint7 | f | 1 | f | f
+ p1_c3 | inh_check_constraint8 | f | 2 | t | t
+ p1_c3 | inh_check_constraint9 | f | 2 | t | t
+(38 rows)
drop table p1 cascade;
NOTICE: drop cascades to 3 other objects
diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql
index 44945b0453af..91b25799a832 100644
--- a/src/test/regress/sql/foreign_key.sql
+++ b/src/test/regress/sql/foreign_key.sql
@@ -2,23 +2,46 @@
-- FOREIGN KEY
--
--- MATCH FULL
+-- NOT ENFORCED
--
-- First test, check and cascade
--
CREATE TABLE PKTABLE ( ptest1 int PRIMARY KEY, ptest2 text );
-CREATE TABLE FKTABLE ( ftest1 int REFERENCES PKTABLE MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, ftest2 int );
+CREATE TABLE FKTABLE ( ftest1 int CONSTRAINT fktable_ftest1_fkey REFERENCES PKTABLE MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE NOT ENFORCED,
+ ftest2 int );
--- Insert test data into PKTABLE
+-- Inserting into the foreign key table will not result in an error, even if
+-- there is no matching key in the referenced table.
+INSERT INTO FKTABLE VALUES (1, 2);
+INSERT INTO FKTABLE VALUES (2, 3);
+
+-- Check FKTABLE
+SELECT * FROM FKTABLE;
+
+-- Reverting it back to ENFORCED will result in failure because constraint validation will be triggered,
+-- as it was previously in a valid state.
+ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_ftest1_fkey ENFORCED;
+
+-- Insert referenced data that satisfies the constraint, then attempted to
+-- change it.
INSERT INTO PKTABLE VALUES (1, 'Test1');
INSERT INTO PKTABLE VALUES (2, 'Test2');
+ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_ftest1_fkey ENFORCED;
+
+-- Any further inserts will fail due to the enforcement.
+INSERT INTO FKTABLE VALUES (3, 4);
+
+--
+-- MATCH FULL
+--
+-- First test, check and cascade
+--
+-- Insert test data into PKTABLE
INSERT INTO PKTABLE VALUES (3, 'Test3');
INSERT INTO PKTABLE VALUES (4, 'Test4');
INSERT INTO PKTABLE VALUES (5, 'Test5');
-- Insert successful rows into FK TABLE
-INSERT INTO FKTABLE VALUES (1, 2);
-INSERT INTO FKTABLE VALUES (2, 3);
INSERT INTO FKTABLE VALUES (3, 4);
INSERT INTO FKTABLE VALUES (NULL, 1);
@@ -230,6 +253,27 @@ INSERT INTO FKTABLE VALUES (1, NULL);
ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1, ftest2) REFERENCES PKTABLE MATCH FULL;
+-- Modifying other attributes of a constraint should not affect its enforceability, and vice versa
+ALTER TABLE FKTABLE ADD CONSTRAINT fk_con FOREIGN KEY(ftest1, ftest2) REFERENCES PKTABLE NOT VALID NOT ENFORCED;
+ALTER TABLE FKTABLE ALTER CONSTRAINT fk_con DEFERRABLE INITIALLY DEFERRED;
+SELECT condeferrable, condeferred, conenforced, convalidated
+FROM pg_constraint WHERE conname = 'fk_con';
+
+ALTER TABLE FKTABLE ALTER CONSTRAINT fk_con NOT ENFORCED;
+SELECT condeferrable, condeferred, conenforced, convalidated
+FROM pg_constraint WHERE conname = 'fk_con';
+
+-- Enforceability also changes the validate state, as data validation will be
+-- performed during this transformation.
+ALTER TABLE FKTABLE ALTER CONSTRAINT fk_con ENFORCED;
+SELECT condeferrable, condeferred, conenforced, convalidated
+FROM pg_constraint WHERE conname = 'fk_con';
+
+-- Can change enforceability and deferrability together
+ALTER TABLE FKTABLE ALTER CONSTRAINT fk_con NOT ENFORCED NOT DEFERRABLE;
+SELECT condeferrable, condeferred, conenforced, convalidated
+FROM pg_constraint WHERE conname = 'fk_con';
+
DROP TABLE FKTABLE;
DROP TABLE PKTABLE;
@@ -968,12 +1012,25 @@ INSERT INTO fktable VALUES (0, 20);
COMMIT;
+ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT ENFORCED;
+
+BEGIN;
+
+-- doesn't match FK, but no error.
+UPDATE pktable SET id = 10 WHERE id = 5;
+-- doesn't match PK, but no error.
+INSERT INTO fktable VALUES (0, 20);
+
+ROLLBACK;
+
-- try additional syntax
ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE;
-- illegal options
ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NO INHERIT;
ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT VALID;
+ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey ENFORCED NOT ENFORCED;
+CREATE TEMP TABLE fktable2 (fk int references pktable ENFORCED NOT ENFORCED);
-- test order of firing of FK triggers when several RI-induced changes need to
-- be made to the same row. This was broken by subtransaction-related
@@ -1184,20 +1241,52 @@ ALTER TABLE fk_partitioned_fk DROP COLUMN fdrop1;
CREATE TABLE fk_partitioned_fk_1 (fdrop1 int, fdrop2 int, a int, fdrop3 int, b int);
ALTER TABLE fk_partitioned_fk_1 DROP COLUMN fdrop1, DROP COLUMN fdrop2, DROP COLUMN fdrop3;
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_1 FOR VALUES FROM (0,0) TO (1000,1000);
-ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk;
+ALTER TABLE fk_partitioned_fk ADD CONSTRAINT fk_partitioned_fk_a_b_fkey FOREIGN KEY (a, b)
+ REFERENCES fk_notpartitioned_pk NOT ENFORCED;
CREATE TABLE fk_partitioned_fk_2 (b int, fdrop1 int, fdrop2 int, a int);
ALTER TABLE fk_partitioned_fk_2 DROP COLUMN fdrop1, DROP COLUMN fdrop2;
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES FROM (1000,1000) TO (2000,2000);
-
+ALTER TABLE fk_partitioned_fk ALTER CONSTRAINT fk_partitioned_fk_a_b_fkey ENFORCED;
CREATE TABLE fk_partitioned_fk_3 (fdrop1 int, fdrop2 int, fdrop3 int, fdrop4 int, b int, a int)
PARTITION BY HASH (a);
ALTER TABLE fk_partitioned_fk_3 DROP COLUMN fdrop1, DROP COLUMN fdrop2,
DROP COLUMN fdrop3, DROP COLUMN fdrop4;
CREATE TABLE fk_partitioned_fk_3_0 PARTITION OF fk_partitioned_fk_3 FOR VALUES WITH (MODULUS 5, REMAINDER 0);
CREATE TABLE fk_partitioned_fk_3_1 PARTITION OF fk_partitioned_fk_3 FOR VALUES WITH (MODULUS 5, REMAINDER 1);
+-- Merge the not-enforced parent constraint with the enforced and not-enforced child constraints
+ALTER TABLE fk_partitioned_fk_3_0 ADD CONSTRAINT fk_partitioned_fk_3_0_a_b_fkey FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk ENFORCED;
+ALTER TABLE fk_partitioned_fk_3_1 ADD CONSTRAINT fk_partitioned_fk_3_1_a_b_fkey FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk NOT ENFORCED;
+ALTER TABLE fk_partitioned_fk_3 ADD CONSTRAINT fk_partitioned_fk_3_a_b_fkey FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk NOT ENFORCED;
+
+-- Merge the non-enforced parent constraint with both the enforced and
+-- non-enforced child constraints, where the referenced table is partitioned.
+CREATE TABLE fk_partitioned_pk (a int, b int, PRIMARY KEY (a, b)) PARTITION BY RANGE (a, b);
+CREATE TABLE fk_partitioned_pk_1 PARTITION OF fk_partitioned_pk FOR VALUES FROM (0,0) TO (1000,1000);
+-- Merge the enforced parent constraint with the enforced and not-enforced child constraints.
+ALTER TABLE fk_partitioned_fk_3_0 ADD CONSTRAINT fk_partitioned_fk_3_0_a_b_fkey1 FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk ENFORCED;
+ALTER TABLE fk_partitioned_fk_3_1 ADD CONSTRAINT fk_partitioned_fk_3_1_a_b_fkey1 FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk NOT ENFORCED;
+ALTER TABLE fk_partitioned_fk_3 ADD CONSTRAINT fk_partitioned_fk_3_a_b_fkey1 FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk ENFORCED;
+
+SELECT conname, conenforced, convalidated, conrelid::regclass, confrelid::regclass
+FROM pg_constraint WHERE conrelid::regclass::text like 'fk_partitioned_fk_3%' ORDER BY oid;
+
+ALTER TABLE fk_partitioned_fk_3 DETACH PARTITION fk_partitioned_fk_3_0;
+ALTER TABLE fk_partitioned_fk_3 ATTACH PARTITION fk_partitioned_fk_3_0 FOR VALUES WITH (MODULUS 5, REMAINDER 0);
+
+SELECT conname, conenforced, convalidated, conrelid::regclass, confrelid::regclass
+FROM pg_constraint WHERE conrelid::regclass::text like 'fk_partitioned_fk_3%' ORDER BY oid;
+
+ALTER TABLE fk_partitioned_fk_3 ALTER CONSTRAINT fk_partitioned_fk_3_a_b_fkey1 NOT ENFORCED;
+
+-- Merging an enforced parent constraint (validated) with a not-enforced child
+-- constraint will implicitly change the child constraint to enforced and apply
+-- the validation as well.
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_3
FOR VALUES FROM (2000,2000) TO (3000,3000);
+SELECT conname, conenforced, convalidated, conrelid::regclass, confrelid::regclass
+FROM pg_constraint WHERE conrelid::regclass::text like 'fk_partitioned_fk_3%' ORDER BY oid;
+
-- Creating a foreign key with ONLY on a partitioned table referencing
-- a non-partitioned table fails.
ALTER TABLE ONLY fk_partitioned_fk ADD FOREIGN KEY (a, b)
@@ -1234,6 +1323,32 @@ UPDATE fk_notpartitioned_pk SET b = 1502 WHERE a = 1500;
UPDATE fk_notpartitioned_pk SET b = 2504 WHERE a = 2500;
-- check psql behavior
\d fk_notpartitioned_pk
+
+-- Check the exsting FK trigger
+SELECT conname, tgrelid::regclass as tgrel, regexp_replace(tgname, '[0-9]+', 'N') as tgname, tgtype
+FROM pg_trigger t JOIN pg_constraint c ON (t.tgconstraint = c.oid)
+WHERE tgrelid IN (SELECT relid FROM pg_partition_tree('fk_partitioned_fk'::regclass)
+ UNION ALL SELECT 'fk_notpartitioned_pk'::regclass)
+ORDER BY tgrelid, tgtype;
+
+ALTER TABLE fk_partitioned_fk ALTER CONSTRAINT fk_partitioned_fk_a_b_fkey NOT ENFORCED;
+-- No triggers
+SELECT conname, tgrelid::regclass as tgrel, regexp_replace(tgname, '[0-9]+', 'N') as tgname, tgtype
+FROM pg_trigger t JOIN pg_constraint c ON (t.tgconstraint = c.oid)
+WHERE tgrelid IN (SELECT relid FROM pg_partition_tree('fk_partitioned_fk'::regclass)
+ UNION ALL SELECT 'fk_notpartitioned_pk'::regclass)
+ORDER BY tgrelid, tgtype;
+
+-- Changing it back to ENFORCED will recreate the necessary triggers.
+ALTER TABLE fk_partitioned_fk ALTER CONSTRAINT fk_partitioned_fk_a_b_fkey ENFORCED;
+
+-- Should be exactly the same number of triggers found as before
+SELECT conname, tgrelid::regclass as tgrel, regexp_replace(tgname, '[0-9]+', 'N') as tgname, tgtype
+FROM pg_trigger t JOIN pg_constraint c ON (t.tgconstraint = c.oid)
+WHERE tgrelid IN (SELECT relid FROM pg_partition_tree('fk_partitioned_fk'::regclass)
+ UNION ALL SELECT 'fk_notpartitioned_pk'::regclass)
+ORDER BY tgrelid, tgtype;
+
ALTER TABLE fk_partitioned_fk DROP CONSTRAINT fk_partitioned_fk_a_b_fkey;
-- done.
DROP TABLE fk_notpartitioned_pk, fk_partitioned_fk;
@@ -1303,8 +1418,6 @@ WHERE conrelid::regclass::text like 'fk_partitioned_fk%' ORDER BY oid::regclass:
DROP TABLE fk_partitioned_fk, fk_notpartitioned_pk;
-- NOT VALID foreign key on a non-partitioned table referencing a partitioned table
-CREATE TABLE fk_partitioned_pk (a int, b int, PRIMARY KEY (a, b)) PARTITION BY RANGE (a, b);
-CREATE TABLE fk_partitioned_pk_1 PARTITION OF fk_partitioned_pk FOR VALUES FROM (0,0) TO (1000,1000);
CREATE TABLE fk_notpartitioned_fk (b int, a int);
ALTER TABLE fk_notpartitioned_fk ADD FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk NOT VALID;
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index 941189761fdb..5f0b2617464e 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -481,6 +481,13 @@ alter table p1 add constraint inh_check_constraint5 check (f1 < 10) not enforced
alter table p1 add constraint inh_check_constraint6 check (f1 < 10) not enforced;
alter table p1_c1 add constraint inh_check_constraint6 check (f1 < 10) enforced;
+alter table p1_c1 add constraint inh_check_constraint9 check (f1 < 10) not valid enforced;
+alter table p1 add constraint inh_check_constraint9 check (f1 < 10) not enforced;
+
+-- the invalid state of the child constraint will be ignored here.
+alter table p1 add constraint inh_check_constraint10 check (f1 < 10) not enforced;
+alter table p1_c1 add constraint inh_check_constraint10 check (f1 < 10) not valid enforced;
+
create table p1_c2(f1 int constraint inh_check_constraint4 check (f1 < 10)) inherits(p1);
-- but reverse is not allowed