v13-0008-Add-support-for-NOT-ENFORCED-in-foreign-key-cons.patch
application/x-patch
Filename: v13-0008-Add-support-for-NOT-ENFORCED-in-foreign-key-cons.patch
Type: application/x-patch
Part: 7
Message:
Re: NOT ENFORCED constraint feature
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v13-0008
Subject: Add support for NOT ENFORCED in foreign key constraints.
| File | + | − |
|---|---|---|
| doc/src/sgml/advanced.sgml | 13 | 0 |
| doc/src/sgml/catalogs.sgml | 1 | 1 |
| doc/src/sgml/ddl.sgml | 7 | 5 |
| doc/src/sgml/ref/alter_table.sgml | 3 | 2 |
| doc/src/sgml/ref/create_table.sgml | 1 | 1 |
| src/backend/catalog/pg_constraint.c | 3 | 2 |
| src/backend/catalog/sql_features.txt | 1 | 1 |
| src/backend/commands/tablecmds.c | 371 | 118 |
| src/backend/parser/gram.y | 8 | 3 |
| src/backend/parser/parse_utilcmd.c | 4 | 2 |
| src/backend/utils/cache/relcache.c | 1 | 0 |
| src/include/nodes/parsenodes.h | 6 | 0 |
| src/include/utils/rel.h | 3 | 0 |
| src/test/regress/expected/constraints.out | 4 | 7 |
| src/test/regress/expected/foreign_key.out | 153 | 5 |
| src/test/regress/sql/constraints.sql | 0 | 1 |
| src/test/regress/sql/foreign_key.sql | 100 | 4 |
From b3adc8c243ab524830e91965d90a963df85cf65d Mon Sep 17 00:00:00 2001
From: Amul Sul <amul.sul@enterprisedb.com>
Date: Mon, 20 Jan 2025 21:45:07 +0530
Subject: [PATCH v13 8/9] Add support for NOT ENFORCED in foreign key
constraints.
Typically, when a foreign key (FK) constraint is created on a table,
action and check triggers are added to maintain data integrity. With
this patch, if a constraint is marked as NOT ENFORCED, integrity
checks are no longer required, making these triggers unnecessary.
Consequently, when creating a NOT ENFORCED FK constraint, triggers
will not be created, and the constraint will be marked as NOT VALID.
Similarly, if an existing FK constraint is changed to NOT ENFORCED,
the associated triggers will be dropped, and the constraint will also
be marked as NOT VALID. Conversely, if a NOT ENFORCED FK constraint is
changed to ENFORCED, the necessary triggers will be created, and the
constraint will remain in the NOT VALID state.
----
NOTE: In this patch, the tryAttachPartitionForeignKey() function will
not merge the constraint if the enforcibility differs. This will be
addressed in the next patch.
----
---
doc/src/sgml/advanced.sgml | 13 +
doc/src/sgml/catalogs.sgml | 2 +-
doc/src/sgml/ddl.sgml | 12 +-
doc/src/sgml/ref/alter_table.sgml | 5 +-
doc/src/sgml/ref/create_table.sgml | 2 +-
src/backend/catalog/pg_constraint.c | 5 +-
src/backend/catalog/sql_features.txt | 2 +-
src/backend/commands/tablecmds.c | 489 ++++++++++++++++------
src/backend/parser/gram.y | 11 +-
src/backend/parser/parse_utilcmd.c | 6 +-
src/backend/utils/cache/relcache.c | 1 +
src/include/nodes/parsenodes.h | 6 +
src/include/utils/rel.h | 3 +
src/test/regress/expected/constraints.out | 11 +-
src/test/regress/expected/foreign_key.out | 158 ++++++-
src/test/regress/sql/constraints.sql | 1 -
src/test/regress/sql/foreign_key.sql | 104 ++++-
17 files changed, 679 insertions(+), 152 deletions(-)
diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index e15a3323dfb..14ed11e1f4d 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -125,6 +125,19 @@ ERROR: insert or update on table "weather" violates foreign key constraint "wea
DETAIL: Key (city)=(Berkeley) is not present in table "cities".
</screen>
</para>
+ <para>
+ If, for some reason, you do not want to enforce this constraint and wish to
+ avoid the error, you can modify the foreign key constraint to <literal>NOT ENFORCED</literal>,
+ or specify it in the <link linkend="sql-createtable"><command>CREATE TABLE</command></link>.
+ </para>
+ <para>
+ Let's change the constraint to <literal>NOT ENFORCED</literal>, and when you
+ attempt to reinsert the same record, it will not result in any error.
+<programlisting>
+ALTER TABLE weather ALTER CONSTRAINT weather_city_fkey NOT ENFORCED;
+INSERT INTO weather VALUES ('Berkeley', 45, 53, 0.0, '1994-11-28');
+</programlisting>
+ </para>
<para>
The behavior of foreign keys can be finely tuned to your
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ee59a7e15d0..3831c68eeb1 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2599,7 +2599,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</para>
<para>
Is the constraint enforced?
- Currently, can be false only for CHECK constraints
+ Currently, can be false only for foreign keys and CHECK constraints
</para></entry>
</row>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index ae156b6b1cd..90f4264f6d7 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -1081,11 +1081,13 @@ CREATE TABLE example (
</indexterm>
<para>
- A foreign key constraint specifies that the values in a column (or
- a group of columns) must match the values appearing in some row
- of another table.
- We say this maintains the <firstterm>referential
- integrity</firstterm> between two related tables.
+ A foreign key constraint with <literal>ENFORCED</literal>, the default
+ setting, specifies that the values in a column (or a group of columns)
+ must match the values appearing in some row of another table. We say this
+ maintains the <firstterm>referential integrity</firstterm> between two
+ related tables. If a foreign key constraint is created with <literal>NOT ENFORCED</literal>,
+ the check for aforementioned <firstterm>referential integrity</firstterm>
+ will not be performed.
</para>
<para>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 8e56b8e59b0..3d7cd0842de 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 ] <replaceable class="parameter">name</replaceable>
ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> SET COMPRESSION <replaceable class="parameter">compression_method</replaceable>
ADD <replaceable class="parameter">table_constraint</replaceable> [ NOT VALID ]
ADD <replaceable class="parameter">table_constraint_using_index</replaceable>
- ALTER CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+ ALTER CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] [ ENFORCED | NOT ENFORCED ]
VALIDATE CONSTRAINT <replaceable class="parameter">constraint_name</replaceable>
DROP CONSTRAINT [ IF EXISTS ] <replaceable class="parameter">constraint_name</replaceable> [ RESTRICT | CASCADE ]
DISABLE TRIGGER [ <replaceable class="parameter">trigger_name</replaceable> | ALL | USER ]
@@ -568,7 +568,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
This form validates a foreign key or check constraint that was
previously created as <literal>NOT VALID</literal>, 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 <xref linkend="sql-altertable-notes"/> below for an explanation
of the usefulness of this command.)
</para>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 9acbc4dd34d..3ec63ff4344 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -1405,7 +1405,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</para>
<para>
- This is currently only supported for <literal>CHECK</literal>
+ This is currently only supported for foreign key and <literal>CHECK</literal>
constraints.
</para>
</listitem>
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 60bc54a96b6..aed9c0c8d9f 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);
conDesc = table_open(ConstraintRelationId, RowExclusiveLock);
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index 2f250d2c57b..d49471bbdf2 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 NO foreign key and check constraints only
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 820a837e18b..74b8ce82a18 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -389,17 +389,33 @@ static void AlterIndexNamespaces(Relation classRel, Relation rel,
static void AlterSeqNamespaces(Relation classRel, Relation rel,
Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved,
LOCKMODE lockmode);
-static ObjectAddress ATExecAlterConstraint(Relation rel, AlterConstraintStmt *cmdcon,
- bool recurse, bool recursing, LOCKMODE lockmode);
-static bool ATExecAlterConstrRecurse(AlterConstraintStmt *cmdcon, Relation conrel,
- Relation tgrel, const Oid fkrelid, const Oid pkrelid,
+static ObjectAddress ATExecAlterConstraint(List **wqueue, Relation rel,
+ AlterConstraintStmt *cmdcon,
+ bool recurse, bool recursing,
+ LOCKMODE lockmode);
+static bool ATExecAlterConstrRecurse(List **wqueue, AlterConstraintStmt *cmdcon,
+ Relation conrel, Relation tgrel,
+ const Oid fkrelid, const Oid pkrelid,
HeapTuple contuple, List **otherrelids,
- LOCKMODE lockmode);
-static void AlterConstrTriggerDeferrability(Oid conoid, Relation tgrel, Relation rel,
+ LOCKMODE lockmode, Oid ReferencedParentDelTrigger,
+ Oid ReferencedParentUpdTrigger,
+ Oid ReferencingParentInsTrigger,
+ Oid ReferencingParentUpdTrigger);
+static void ATExecAlterConstrEnforceability(List **wqueue, AlterConstraintStmt *cmdcon,
+ Relation conrel, Relation tgrel,
+ const Oid fkrelid, const Oid pkrelid,
+ HeapTuple contuple, List **otherrelids,
+ LOCKMODE lockmode,
+ Oid ReferencedParentDelTrigger,
+ Oid ReferencedParentUpdTrigger,
+ Oid ReferencingParentInsTrigger,
+ Oid ReferencingParentUpdTrigger);
+static void AlterConstrTriggerDeferrability(Oid conoid, Relation tgrel, Oid fkrel,
bool deferrable, bool initdeferred,
List **otherrelids);
-static void ATExecAlterChildConstr(AlterConstraintStmt *cmdcon, Relation conrel,
- Relation tgrel, const Oid fkrelid, const Oid pkrelid,
+static void ATExecAlterChildConstr(List **wqueue, AlterConstraintStmt *cmdcon,
+ Relation conrel, Relation tgrel,
+ const Oid fkrelid, const Oid pkrelid,
HeapTuple contuple, List **otherrelids,
LOCKMODE lockmode);
static ObjectAddress ATExecValidateConstraint(List **wqueue,
@@ -5457,7 +5473,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
break;
case AT_AlterConstraint: /* ALTER CONSTRAINT */
address =
- ATExecAlterConstraint(rel,
+ ATExecAlterConstraint(wqueue, rel,
castNode(AlterConstraintStmt, cmd->def),
false, false, lockmode);
break;
@@ -10530,7 +10546,7 @@ addFkConstraint(addFkConstraintSides fkside,
CONSTRAINT_FOREIGN,
fkconstraint->deferrable,
fkconstraint->initdeferred,
- true, /* Is Enforced */
+ fkconstraint->is_enforced,
fkconstraint->initially_valid,
parentConstr,
RelationGetRelid(rel),
@@ -10648,21 +10664,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
@@ -10783,8 +10801,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));
@@ -10796,29 +10814,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;
@@ -11049,8 +11070,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))
@@ -11110,6 +11131,7 @@ 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;
@@ -11139,9 +11161,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,
@@ -11274,8 +11297,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));
@@ -11307,17 +11330,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
@@ -11370,6 +11394,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++)
@@ -11487,6 +11512,7 @@ tryAttachPartitionForeignKey(List **wqueue,
if (OidIsValid(partConstr->conparentid) ||
partConstr->condeferrable != parentConstr->condeferrable ||
partConstr->condeferred != parentConstr->condeferred ||
+ partConstr->conenforced != parentConstr->conenforced ||
partConstr->confupdtype != parentConstr->confupdtype ||
partConstr->confdeltype != parentConstr->confdeltype ||
partConstr->confmatchtype != parentConstr->confmatchtype)
@@ -11530,8 +11556,7 @@ AttachPartitionForeignKey(List **wqueue,
bool queueValidation;
Oid partConstrFrelid;
Oid partConstrRelid;
- Oid insertTriggerOid,
- updateTriggerOid;
+ bool parentConstrIsEnforced;
/* Fetch the parent constraint tuple */
parentConstrTup = SearchSysCache1(CONSTROID,
@@ -11539,6 +11564,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,
@@ -11588,17 +11614,24 @@ AttachPartitionForeignKey(List **wqueue,
/*
* Like the constraint, attach partition's "check" triggers to the
- * corresponding parent triggers.
+ * corresponding parent triggers if the constraint is enforced.
+ * Non-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
@@ -11712,6 +11745,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,
@@ -11732,10 +11769,27 @@ 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 ke 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
@@ -11892,8 +11946,8 @@ GetForeignKeyCheckTriggers(Relation trigrel,
* InvalidObjectAddress.
*/
static ObjectAddress
-ATExecAlterConstraint(Relation rel, AlterConstraintStmt *cmdcon, bool recurse,
- bool recursing, LOCKMODE lockmode)
+ATExecAlterConstraint(List **wqueue, Relation rel, AlterConstraintStmt *cmdcon,
+ bool recurse, bool recursing, LOCKMODE lockmode)
{
Relation conrel;
Relation tgrel;
@@ -11935,10 +11989,19 @@ ATExecAlterConstraint(Relation rel, AlterConstraintStmt *cmdcon, bool recurse,
currcon = (Form_pg_constraint) GETSTRUCT(contuple);
if (currcon->contype != CONSTRAINT_FOREIGN)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key constraint",
- cmdcon->conname, RelationGetRelationName(rel))));
+ {
+ if (cmdcon->alterEnforceability)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter enforceability of constraint \"%s\" of relation \"%s\"",
+ cmdcon->conname, RelationGetRelationName(rel)),
+ errdetail("Enforceability can only be altered for foreign key constraints.")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key constraint",
+ cmdcon->conname, RelationGetRelationName(rel))));
+ }
/*
* If it's not the topmost constraint, raise an error.
@@ -11990,13 +12053,14 @@ ATExecAlterConstraint(Relation rel, AlterConstraintStmt *cmdcon, bool recurse,
* processed regardless, in case they had the constraint locally changed.
*/
address = InvalidObjectAddress;
- if (currcon->condeferrable != cmdcon->deferrable ||
- currcon->condeferred != cmdcon->initdeferred ||
+ if (cmdcon->alterDeferrability || cmdcon->alterEnforceability ||
rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
- if (ATExecAlterConstrRecurse(cmdcon, conrel, tgrel, currcon->conrelid,
- currcon->confrelid, contuple, &otherrelids,
- lockmode))
+ if (ATExecAlterConstrRecurse(wqueue, cmdcon, conrel, tgrel,
+ currcon->conrelid, currcon->confrelid,
+ contuple, &otherrelids, lockmode,
+ InvalidOid, InvalidOid, InvalidOid,
+ InvalidOid))
ObjectAddressSet(address, ConstraintRelationId, currcon->oid);
}
@@ -12028,10 +12092,14 @@ ATExecAlterConstraint(Relation rel, AlterConstraintStmt *cmdcon, bool recurse,
* but existing releases don't do that.)
*/
static bool
-ATExecAlterConstrRecurse(AlterConstraintStmt *cmdcon, Relation conrel,
- Relation tgrel, const Oid fkrelid, const Oid pkrelid,
- HeapTuple contuple, List **otherrelids,
- LOCKMODE lockmode)
+ATExecAlterConstrRecurse(List **wqueue, AlterConstraintStmt *cmdcon,
+ Relation conrel, Relation tgrel, const Oid fkrelid,
+ const Oid pkrelid, HeapTuple contuple,
+ List **otherrelids, LOCKMODE lockmode,
+ Oid ReferencedParentDelTrigger,
+ Oid ReferencedParentUpdTrigger,
+ Oid ReferencingParentInsTrigger,
+ Oid ReferencingParentUpdTrigger)
{
Form_pg_constraint currcon;
Oid conoid;
@@ -12055,15 +12123,24 @@ ATExecAlterConstrRecurse(AlterConstraintStmt *cmdcon, Relation conrel,
* silently do nothing.
*/
if (currcon->condeferrable != cmdcon->deferrable ||
- currcon->condeferred != cmdcon->initdeferred)
+ currcon->condeferred != cmdcon->initdeferred ||
+ currcon->conenforced != cmdcon->is_enforced)
{
HeapTuple copyTuple;
Form_pg_constraint copy_con;
copyTuple = heap_copytuple(contuple);
copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
- copy_con->condeferrable = cmdcon->deferrable;
- copy_con->condeferred = cmdcon->initdeferred;
+
+ if (cmdcon->alterDeferrability)
+ {
+ copy_con->condeferrable = cmdcon->deferrable;
+ copy_con->condeferred = cmdcon->initdeferred;
+ }
+
+ if (cmdcon->alterEnforceability)
+ copy_con->conenforced = cmdcon->is_enforced;
+
CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);
InvokeObjectPostAlterHook(ConstraintRelationId,
@@ -12074,32 +12151,200 @@ ATExecAlterConstrRecurse(AlterConstraintStmt *cmdcon, Relation conrel,
/* Make new constraint flags visible to others */
CacheInvalidateRelcache(rel);
+ }
+ /*
+ * Note that even if deferrability is requested to be altered along with
+ * enforceability, we don't need to call AlterConstrTriggerDeferrability()
+ * explicitly. Changing enforceability involves either creating or
+ * dropping the trigger, during which the deferrability will be set
+ * accordingly.
+ */
+ if (cmdcon->alterEnforceability &&
+ currcon->conenforced != cmdcon->is_enforced)
+ {
+ ATExecAlterConstrEnforceability(wqueue, cmdcon, conrel, tgrel, fkrelid,
+ pkrelid, contuple, otherrelids, lockmode,
+ ReferencedParentDelTrigger,
+ ReferencedParentUpdTrigger,
+ ReferencingParentInsTrigger,
+ ReferencingParentUpdTrigger);
+ /*
+ * Tell Phase 3 to check that the constraint is satisfied by existing
+ * rows, but skip this step if the constraint is NOT VALID.
+ */
+ if (cmdcon->is_enforced && currcon->convalidated &&
+ rel->rd_rel->relkind == RELKIND_RELATION)
+ {
+ AlteredTableInfo *tab;
+ NewConstraint *newcon;
+ Constraint *fkconstraint;
+
+ /* Queue validation for phase 3 */
+ fkconstraint = makeNode(Constraint);
+ /* for now this is all we need */
+ fkconstraint->conname = pstrdup(NameStr(currcon->conname));
+
+ newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
+ newcon->name = fkconstraint->conname;
+ newcon->contype = CONSTR_FOREIGN;
+ newcon->refrelid = currcon->confrelid;
+ newcon->refindid = currcon->conindid;
+ newcon->conid = conoid;
+ newcon->qual = (Node *) fkconstraint;
+
+ /* Find or create work queue entry for this table */
+ tab = ATGetQueueEntry(wqueue, rel);
+ tab->constraints = lappend(tab->constraints, newcon);
+ }
+ }
+ else
+ {
/*
* Now we need to update the multiple entries in pg_trigger that
* implement the constraint.
*/
- AlterConstrTriggerDeferrability(conoid, tgrel, rel, cmdcon->deferrable,
- cmdcon->initdeferred, otherrelids);
+ if (cmdcon->alterDeferrability &&
+ (currcon->condeferrable != cmdcon->deferrable ||
+ currcon->condeferred != cmdcon->initdeferred))
+ AlterConstrTriggerDeferrability(conoid, tgrel,
+ RelationGetRelid(rel),
+ cmdcon->deferrable,
+ cmdcon->initdeferred,
+ otherrelids);
+
+ /*
+ * If the table at either end of the constraint is partitioned, we
+ * need to recurse and handle every constraint that is a child of this
+ * one.
+ *
+ * (This assumes that the recurse flag is forcibly set for partitioned
+ * tables, and not set for legacy inheritance, though we don't check
+ * for that here.)
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+ get_rel_relkind(refrelid) == RELKIND_PARTITIONED_TABLE)
+ ATExecAlterChildConstr(wqueue, cmdcon, conrel, tgrel, fkrelid,
+ pkrelid, contuple, otherrelids, lockmode);
}
- /*
- * If the table at either end of the constraint is partitioned, we need to
- * recurse and handle every constraint that is a child of this one.
- *
- * (This assumes that the recurse flag is forcibly set for partitioned
- * tables, and not set for legacy inheritance, though we don't check for
- * that here.)
- */
- if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
- get_rel_relkind(refrelid) == RELKIND_PARTITIONED_TABLE)
- ATExecAlterChildConstr(cmdcon, conrel, tgrel, fkrelid, pkrelid,
- contuple, otherrelids, lockmode);
table_close(rel, NoLock);
return changed;
}
+/*
+ * A subroutine of ATExecAlterConstrRecurse that updates the enforceability of
+ * a foreign key constraint. Depending on whether the constraint is being set
+ * to enforced or not enforced, it creates or drops the trigger accordingly.
+ *
+ * The arguments to this function have the same meaning as the arguments to
+ * ATExecAlterConstrRecurse.
+ */
+static void
+ATExecAlterConstrEnforceability(List **wqueue, AlterConstraintStmt *cmdcon,
+ Relation conrel, Relation tgrel,
+ const Oid fkrelid, const Oid pkrelid,
+ HeapTuple contuple, List **otherrelids,
+ LOCKMODE lockmode,
+ Oid ReferencedParentDelTrigger,
+ Oid ReferencedParentUpdTrigger,
+ Oid ReferencingParentInsTrigger,
+ Oid ReferencingParentUpdTrigger)
+{
+ Form_pg_constraint currcon;
+ Oid conoid;
+
+ currcon = (Form_pg_constraint) GETSTRUCT(contuple);
+ conoid = currcon->oid;
+
+ /* 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 (get_rel_relkind(currcon->conrelid) == RELKIND_PARTITIONED_TABLE ||
+ get_rel_relkind(currcon->confrelid) == RELKIND_PARTITIONED_TABLE)
+ ATExecAlterChildConstr(wqueue, cmdcon, conrel, tgrel, fkrelid,
+ pkrelid, contuple, otherrelids, lockmode);
+
+ /* Drop all the triggers */
+ DropForeignKeyConstraintTriggers(tgrel, conoid, InvalidOid, InvalidOid);
+ }
+ else /* Create triggers */
+ {
+ Oid ReferencedDelTriggerOid = InvalidOid,
+ ReferencedUpdTriggerOid = InvalidOid,
+ ReferencingInsTriggerOid = InvalidOid,
+ ReferencingUpdTriggerOid = InvalidOid;
+
+ /* Prepare the minimal information required for trigger creation. */
+ Constraint *fkconstraint = makeNode(Constraint);
+
+ 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);
+
+ /*
+ * 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 (get_rel_relkind(currcon->conrelid) == RELKIND_PARTITIONED_TABLE ||
+ get_rel_relkind(currcon->confrelid) == RELKIND_PARTITIONED_TABLE)
+ {
+ ScanKeyData pkey;
+ SysScanDesc pscan;
+ HeapTuple childtup;
+
+ 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)))
+ ATExecAlterConstrRecurse(wqueue, cmdcon, conrel, tgrel, fkrelid,
+ pkrelid, childtup, otherrelids,
+ lockmode, ReferencedDelTriggerOid,
+ ReferencedUpdTriggerOid,
+ ReferencingInsTriggerOid,
+ ReferencingUpdTriggerOid);
+
+ systable_endscan(pscan);
+ }
+ }
+}
+
/*
* A subroutine of ATExecAlterConstrRecurse that updated constraint trigger's
* deferrability.
@@ -12108,7 +12353,7 @@ ATExecAlterConstrRecurse(AlterConstraintStmt *cmdcon, Relation conrel,
* ATExecAlterConstrRecurse.
*/
static void
-AlterConstrTriggerDeferrability(Oid conoid, Relation tgrel, Relation rel,
+AlterConstrTriggerDeferrability(Oid conoid, Relation tgrel, Oid fkrel,
bool deferrable, bool initdeferred,
List **otherrelids)
{
@@ -12134,7 +12379,7 @@ AlterConstrTriggerDeferrability(Oid conoid, Relation tgrel, Relation rel,
* other rels that don't have a trigger whose properties change, but
* let's be conservative.)
*/
- if (tgform->tgrelid != RelationGetRelid(rel))
+ if (tgform->tgrelid != fkrel)
*otherrelids = list_append_unique_oid(*otherrelids,
tgform->tgrelid);
@@ -12173,10 +12418,10 @@ AlterConstrTriggerDeferrability(Oid conoid, Relation tgrel, Relation rel,
* ATExecAlterConstrRecurse.
*/
static void
-ATExecAlterChildConstr(AlterConstraintStmt *cmdcon, Relation conrel,
- Relation tgrel, const Oid fkrelid, const Oid pkrelid,
- HeapTuple contuple, List **otherrelids,
- LOCKMODE lockmode)
+ATExecAlterChildConstr(List **wqueue, AlterConstraintStmt *cmdcon,
+ Relation conrel, Relation tgrel, const Oid fkrelid,
+ const Oid pkrelid, HeapTuple contuple,
+ List **otherrelids, LOCKMODE lockmode)
{
Form_pg_constraint currcon;
Oid conoid;
@@ -12196,8 +12441,9 @@ ATExecAlterChildConstr(AlterConstraintStmt *cmdcon, Relation conrel,
true, NULL, 1, &pkey);
while (HeapTupleIsValid(childtup = systable_getnext(pscan)))
- ATExecAlterConstrRecurse(cmdcon, conrel, tgrel, fkrelid, pkrelid,
- childtup, otherrelids, lockmode);
+ ATExecAlterConstrRecurse(wqueue, cmdcon, conrel, tgrel, fkrelid,
+ pkrelid, childtup, otherrelids, lockmode,
+ InvalidOid, InvalidOid, InvalidOid, InvalidOid);
systable_endscan(pscan);
}
@@ -20267,8 +20513,6 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
ForeignKeyCacheInfo *fk = lfirst(cell);
HeapTuple contup;
Form_pg_constraint conform;
- Oid insertTriggerOid,
- updateTriggerOid;
contup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
if (!HeapTupleIsValid(contup))
@@ -20295,17 +20539,25 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
/*
* 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. Non-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
@@ -20345,6 +20597,7 @@ 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;
/* a few irrelevant fields omitted here */
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a36e322c78e..4cab07f989a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2665,7 +2665,12 @@ alter_table_cmd:
processCASbits($4, @4, "ALTER CONSTRAINT statement",
&c->deferrable,
&c->initdeferred,
- NULL, NULL, NULL, yyscanner);
+ &c->is_enforced, NULL, NULL, yyscanner);
+ c->alterDeferrability =
+ ($4 & (CAS_NOT_DEFERRABLE | CAS_DEFERRABLE |
+ CAS_INITIALLY_IMMEDIATE | CAS_INITIALLY_IMMEDIATE)) != 0;
+ c->alterEnforceability =
+ ($4 & (CAS_NOT_ENFORCED | CAS_ENFORCED)) != 0;
$$ = (Node *) n;
}
/* ALTER TABLE <name> VALIDATE CONSTRAINT ... */
@@ -4315,8 +4320,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 9053c898e06..d568eba5cac 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3977,7 +3977,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"),
@@ -3993,7 +3994,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 c010de4aeca..dd956b0bf4a 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4692,6 +4692,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/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 909bf71e601..edab71e68c1 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2513,8 +2513,14 @@ typedef struct AlterConstraintStmt
char *conname; /* Constraint name */
+ bool alterDeferrability; /* True when any of the following flags
+ * are specifically set for modification */
bool deferrable; /* DEFERRABLE? */
bool initdeferred; /* INITIALLY DEFERRED? */
+
+ bool alterEnforceability; /* True when following flag is
+ * specifically set for modification */
+ bool is_enforced; /* enforced constraint? */
} AlterConstraintStmt;
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 48b95f211f3..fcba0efbd7c 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 692a69fe457..75d40d06b6e 100644
--- a/src/test/regress/expected/constraints.out
+++ b/src/test/regress/expected/constraints.out
@@ -744,15 +744,12 @@ CREATE TABLE UNIQUE_NOTEN_TBL(i int UNIQUE NOT ENFORCED);
ERROR: misplaced NOT ENFORCED clause
LINE 1: CREATE TABLE UNIQUE_NOTEN_TBL(i int UNIQUE NOT ENFORCED);
^
--- XXX: error message is misleading here
ALTER TABLE unique_tbl ALTER CONSTRAINT unique_tbl_i_key ENFORCED;
-ERROR: ALTER CONSTRAINT statement 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"
+DETAIL: Enforceability can only be altered for foreign key constraints.
ALTER TABLE unique_tbl ALTER CONSTRAINT unique_tbl_i_key NOT ENFORCED;
-ERROR: ALTER CONSTRAINT statement 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"
+DETAIL: Enforceability can only be altered for foreign key constraints.
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 374dcb266e7..3183b1cbe8f 100644
--- a/src/test/regress/expected/foreign_key.out
+++ b/src/test/regress/expected/foreign_key.out
@@ -1,12 +1,43 @@
--
-- 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 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".
+-- Remove any existing rows that violate the constraint, then attempt to change
+-- it.
+TRUNCATE FKTABLE;
+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 (1, 'Test1');
INSERT INTO PKTABLE VALUES (2, 'Test2');
@@ -351,6 +382,41 @@ 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 FOREIGN KEY(ftest1, ftest2) REFERENCES PKTABLE NOT VALID NOT ENFORCED;
+ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_ftest1_ftest2_fkey DEFERRABLE INITIALLY DEFERRED;
+SELECT condeferrable, condeferred, conenforced, convalidated
+FROM pg_constraint WHERE conname = 'fktable_ftest1_ftest2_fkey';
+ condeferrable | condeferred | conenforced | convalidated
+---------------+-------------+-------------+--------------
+ t | t | f | f
+(1 row)
+
+ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_ftest1_ftest2_fkey NOT ENFORCED;
+SELECT condeferrable, condeferred, conenforced, convalidated
+FROM pg_constraint WHERE conname = 'fktable_ftest1_ftest2_fkey';
+ condeferrable | condeferred | conenforced | convalidated
+---------------+-------------+-------------+--------------
+ t | t | f | f
+(1 row)
+
+ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_ftest1_ftest2_fkey ENFORCED;
+SELECT condeferrable, condeferred, conenforced, convalidated
+FROM pg_constraint WHERE conname = 'fktable_ftest1_ftest2_fkey';
+ condeferrable | condeferred | conenforced | convalidated
+---------------+-------------+-------------+--------------
+ t | t | t | f
+(1 row)
+
+-- Can change enforceability and deferrability together
+ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_ftest1_ftest2_fkey NOT ENFORCED NOT DEFERRABLE;
+SELECT condeferrable, condeferred, conenforced, convalidated
+FROM pg_constraint WHERE conname = 'fktable_ftest1_ftest2_fkey';
+ condeferrable | condeferred | conenforced | convalidated
+---------------+-------------+-------------+--------------
+ f | f | f | f
+(1 row)
+
DROP TABLE FKTABLE;
DROP TABLE PKTABLE;
-- MATCH SIMPLE
@@ -1276,6 +1342,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, no error.
+UPDATE pktable SET id = 10 WHERE id = 5;
+-- doesn't match PK, no error.
+INSERT INTO fktable VALUES (0, 20);
+ROLLBACK;
-- try additional syntax
ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE;
-- illegal option
@@ -1283,6 +1356,14 @@ ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE INITIALLY DE
ERROR: constraint declared INITIALLY DEFERRED must be DEFERRABLE
LINE 1: ...e ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE INITIALLY ...
^
+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.
@@ -1580,10 +1661,12 @@ 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 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_2 ADD FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk NOT ENFORCED;
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,
@@ -1606,10 +1689,10 @@ INSERT INTO fk_partitioned_fk_1 (a,b) VALUES (500, 501);
ERROR: insert or update on table "fk_partitioned_fk_1" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
DETAIL: Key (a, b)=(500, 501) is not present in table "fk_notpartitioned_pk".
INSERT INTO fk_partitioned_fk (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"
+ERROR: insert or update on table "fk_partitioned_fk_2" violates foreign key constraint "fk_partitioned_fk_2_a_b_fkey"
DETAIL: Key (a, b)=(1500, 1501) is not present in table "fk_notpartitioned_pk".
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"
+ERROR: insert or update on table "fk_partitioned_fk_2" violates foreign key constraint "fk_partitioned_fk_2_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"
@@ -1659,6 +1742,37 @@ 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
+CREATE TEMP TABLE tmp_trg_info AS SELECT tgtype, tgrelid::regclass, tgconstraint
+FROM pg_trigger
+WHERE tgrelid IN (SELECT relid FROM pg_partition_tree('fk_partitioned_fk'::regclass)
+ UNION ALL SELECT 'fk_notpartitioned_pk'::regclass);
+SELECT count(1) FROM tmp_trg_info;
+ count
+-------
+ 14
+(1 row)
+
+ALTER TABLE fk_partitioned_fk ALTER CONSTRAINT fk_partitioned_fk_a_b_fkey NOT ENFORCED;
+-- No triggers
+SELECT count(1) FROM pg_trigger
+WHERE tgrelid IN (SELECT relid FROM pg_partition_tree('fk_partitioned_fk'::regclass)
+ UNION ALL SELECT 'fk_notpartitioned_pk'::regclass);
+ count
+-------
+ 0
+(1 row)
+
+-- 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 COUNT(1) FROM pg_trigger pgt JOIN tmp_trg_info tt
+ON (pgt.tgtype = tt.tgtype AND pgt.tgrelid = tt.tgrelid AND pgt.tgconstraint = tt.tgconstraint);
+ count
+-------
+ 14
+(1 row)
+
ALTER TABLE fk_partitioned_fk DROP CONSTRAINT fk_partitioned_fk_a_b_fkey;
-- done.
DROP TABLE fk_notpartitioned_pk, fk_partitioned_fk;
@@ -1956,6 +2070,40 @@ Partition of: fk_partitioned_fk FOR VALUES IN (1500, 1502)
Foreign-key constraints:
TABLE "fk_partitioned_fk" CONSTRAINT "fk_partitioned_fk_a_b_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
+DROP TABLE fk_partitioned_fk_2;
+CREATE TABLE fk_partitioned_fk_2 (b int, a int,
+ FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk ON UPDATE CASCADE ON DELETE CASCADE NOT ENFORCED);
+BEGIN;
+ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES IN (1500,1502);
+-- should be having two constraints
+\d fk_partitioned_fk_2
+ Table "public.fk_partitioned_fk_2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ b | integer | | |
+ a | integer | | |
+Partition of: fk_partitioned_fk FOR VALUES IN (1500, 1502)
+Foreign-key constraints:
+ "fk_partitioned_fk_2_a_b_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE NOT ENFORCED
+ TABLE "fk_partitioned_fk" CONSTRAINT "fk_partitioned_fk_a_b_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
+
+DROP TABLE fk_partitioned_fk_2;
+ROLLBACK;
+BEGIN;
+ALTER TABLE fk_partitioned_fk ALTER CONSTRAINT fk_partitioned_fk_a_b_fkey NOT ENFORCED;
+ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES IN (1500,1502);
+-- should have only one constraint
+\d fk_partitioned_fk_2
+ Table "public.fk_partitioned_fk_2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ b | integer | | |
+ a | integer | | |
+Partition of: fk_partitioned_fk FOR VALUES IN (1500, 1502)
+Foreign-key constraints:
+ TABLE "fk_partitioned_fk" CONSTRAINT "fk_partitioned_fk_a_b_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE NOT ENFORCED
+
+ROLLBACK;
DROP TABLE fk_partitioned_fk_2;
CREATE TABLE fk_partitioned_fk_4 (a int, b int, FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE) PARTITION BY RANGE (b, a);
CREATE TABLE fk_partitioned_fk_4_1 PARTITION OF fk_partitioned_fk_4 FOR VALUES FROM (1,1) TO (100,100);
diff --git a/src/test/regress/sql/constraints.sql b/src/test/regress/sql/constraints.sql
index d6742f83fb9..e61e7324768 100644
--- a/src/test/regress/sql/constraints.sql
+++ b/src/test/regress/sql/constraints.sql
@@ -534,7 +534,6 @@ SELECT * FROM unique_tbl;
-- enforcibility cannot be specified or set for unique constrain
CREATE TABLE UNIQUE_EN_TBL(i int UNIQUE ENFORCED);
CREATE TABLE UNIQUE_NOTEN_TBL(i int UNIQUE NOT ENFORCED);
--- XXX: error message is misleading here
ALTER TABLE unique_tbl ALTER CONSTRAINT unique_tbl_i_key ENFORCED;
ALTER TABLE unique_tbl ALTER CONSTRAINT unique_tbl_i_key NOT ENFORCED;
diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql
index bc0adb8cfe9..408a01e377b 100644
--- a/src/test/regress/sql/foreign_key.sql
+++ b/src/test/regress/sql/foreign_key.sql
@@ -2,13 +2,39 @@
-- 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 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;
+
+-- 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;
+
+-- Remove any existing rows that violate the constraint, then attempt to change
+-- it.
+TRUNCATE FKTABLE;
+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 (1, 'Test1');
INSERT INTO PKTABLE VALUES (2, 'Test2');
@@ -230,6 +256,25 @@ 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 FOREIGN KEY(ftest1, ftest2) REFERENCES PKTABLE NOT VALID NOT ENFORCED;
+ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_ftest1_ftest2_fkey DEFERRABLE INITIALLY DEFERRED;
+SELECT condeferrable, condeferred, conenforced, convalidated
+FROM pg_constraint WHERE conname = 'fktable_ftest1_ftest2_fkey';
+
+ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_ftest1_ftest2_fkey NOT ENFORCED;
+SELECT condeferrable, condeferred, conenforced, convalidated
+FROM pg_constraint WHERE conname = 'fktable_ftest1_ftest2_fkey';
+
+ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_ftest1_ftest2_fkey ENFORCED;
+SELECT condeferrable, condeferred, conenforced, convalidated
+FROM pg_constraint WHERE conname = 'fktable_ftest1_ftest2_fkey';
+
+-- Can change enforceability and deferrability together
+ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_ftest1_ftest2_fkey NOT ENFORCED NOT DEFERRABLE;
+SELECT condeferrable, condeferred, conenforced, convalidated
+FROM pg_constraint WHERE conname = 'fktable_ftest1_ftest2_fkey';
+
DROP TABLE FKTABLE;
DROP TABLE PKTABLE;
@@ -968,10 +1013,23 @@ INSERT INTO fktable VALUES (0, 20);
COMMIT;
+ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT ENFORCED;
+
+BEGIN;
+
+-- doesn't match FK, no error.
+UPDATE pktable SET id = 10 WHERE id = 5;
+-- doesn't match PK, no error.
+INSERT INTO fktable VALUES (0, 20);
+
+ROLLBACK;
+
-- try additional syntax
ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE;
-- illegal option
ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE INITIALLY DEFERRED;
+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
@@ -1182,11 +1240,12 @@ 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 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_2 ADD FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk NOT ENFORCED;
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,
@@ -1232,6 +1291,27 @@ 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
+CREATE TEMP TABLE tmp_trg_info AS SELECT tgtype, tgrelid::regclass, tgconstraint
+FROM pg_trigger
+WHERE tgrelid IN (SELECT relid FROM pg_partition_tree('fk_partitioned_fk'::regclass)
+ UNION ALL SELECT 'fk_notpartitioned_pk'::regclass);
+SELECT count(1) FROM tmp_trg_info;
+
+ALTER TABLE fk_partitioned_fk ALTER CONSTRAINT fk_partitioned_fk_a_b_fkey NOT ENFORCED;
+-- No triggers
+SELECT count(1) FROM pg_trigger
+WHERE tgrelid IN (SELECT relid FROM pg_partition_tree('fk_partitioned_fk'::regclass)
+ UNION ALL SELECT 'fk_notpartitioned_pk'::regclass);
+
+-- 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 COUNT(1) FROM pg_trigger pgt JOIN tmp_trg_info tt
+ON (pgt.tgtype = tt.tgtype AND pgt.tgrelid = tt.tgrelid AND pgt.tgconstraint = tt.tgconstraint);
+
ALTER TABLE fk_partitioned_fk DROP CONSTRAINT fk_partitioned_fk_a_b_fkey;
-- done.
DROP TABLE fk_notpartitioned_pk, fk_partitioned_fk;
@@ -1439,6 +1519,22 @@ ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES IN
\d fk_partitioned_fk_2
DROP TABLE fk_partitioned_fk_2;
+CREATE TABLE fk_partitioned_fk_2 (b int, a int,
+ FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk ON UPDATE CASCADE ON DELETE CASCADE NOT ENFORCED);
+BEGIN;
+ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES IN (1500,1502);
+-- should be having two constraints
+\d fk_partitioned_fk_2
+DROP TABLE fk_partitioned_fk_2;
+ROLLBACK;
+BEGIN;
+ALTER TABLE fk_partitioned_fk ALTER CONSTRAINT fk_partitioned_fk_a_b_fkey NOT ENFORCED;
+ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES IN (1500,1502);
+-- should have only one constraint
+\d fk_partitioned_fk_2
+ROLLBACK;
+DROP TABLE fk_partitioned_fk_2;
+
CREATE TABLE fk_partitioned_fk_4 (a int, b int, FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE) PARTITION BY RANGE (b, a);
CREATE TABLE fk_partitioned_fk_4_1 PARTITION OF fk_partitioned_fk_4 FOR VALUES FROM (1,1) TO (100,100);
CREATE TABLE fk_partitioned_fk_4_2 (a int, b int, FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE SET NULL);
--
2.43.5