mirror of
https://github.com/postgres/postgres.git
synced 2025-04-25 21:42:33 +03:00
Add support for NOT ENFORCED in foreign key constraints
This expands the NOT ENFORCED constraint flag, previously only supported for CHECK constraints (commit ca87c415e2f), to foreign key constraints. Normally, when a foreign key 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 foreign key constraint, triggers will not be created, and the constraint will be marked as NOT VALID. Similarly, if an existing foreign key 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 foreign key constraint is changed to ENFORCED, the necessary triggers will be created, and the will be changed to VALID by performing necessary validation. Since not-enforced foreign key constraints have no triggers, the shortcut used for example in psql and pg_dump to skip looking for foreign keys if the relation is known not to have triggers no longer applies. (It already didn't work for partitioned tables.) Author: Amul Sul <sulamul@gmail.com> Reviewed-by: Joel Jacobson <joel@compiler.org> Reviewed-by: Andrew Dunstan <andrew@dunslane.net> Reviewed-by: Peter Eisentraut <peter@eisentraut.org> Reviewed-by: jian he <jian.universality@gmail.com> Reviewed-by: Alvaro Herrera <alvherre@alvh.no-ip.org> Reviewed-by: Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> Reviewed-by: Isaac Morland <isaac.morland@gmail.com> Reviewed-by: Alexandra Wang <alexandra.wang.oss@gmail.com> Tested-by: Triveni N <triveni.n@enterprisedb.com> Discussion: https://www.postgresql.org/message-id/flat/CAAJ_b962c5AcYW9KUt_R_ER5qs3fUGbe4az-SP-vuwPS-w-AGA@mail.gmail.com
This commit is contained in:
parent
327d987df1
commit
eec0040c4b
@ -2620,7 +2620,6 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
|
|||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
Is the constraint enforced?
|
Is the constraint enforced?
|
||||||
Currently, can be false only for CHECK constraints
|
|
||||||
</para></entry>
|
</para></entry>
|
||||||
</row>
|
</row>
|
||||||
|
|
||||||
|
@ -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>
|
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</replaceable> [ NOT VALID ]
|
||||||
ADD <replaceable class="parameter">table_constraint_using_index</replaceable>
|
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 ]
|
||||||
ALTER CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> [ INHERIT | NO INHERIT ]
|
ALTER CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> [ INHERIT | NO INHERIT ]
|
||||||
VALIDATE CONSTRAINT <replaceable class="parameter">constraint_name</replaceable>
|
VALIDATE CONSTRAINT <replaceable class="parameter">constraint_name</replaceable>
|
||||||
DROP CONSTRAINT [ IF EXISTS ] <replaceable class="parameter">constraint_name</replaceable> [ RESTRICT | CASCADE ]
|
DROP CONSTRAINT [ IF EXISTS ] <replaceable class="parameter">constraint_name</replaceable> [ RESTRICT | CASCADE ]
|
||||||
@ -589,7 +589,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
|
|||||||
This form validates a foreign key or check constraint that was
|
This form validates a foreign key or check constraint that was
|
||||||
previously created as <literal>NOT VALID</literal>, by scanning the
|
previously created as <literal>NOT VALID</literal>, by scanning the
|
||||||
table to ensure there are no rows for which the constraint is not
|
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
|
(See <xref linkend="sql-altertable-notes"/> below for an explanation
|
||||||
of the usefulness of this command.)
|
of the usefulness of this command.)
|
||||||
</para>
|
</para>
|
||||||
|
@ -1409,7 +1409,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
|
|||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
This is currently only supported for <literal>CHECK</literal>
|
This is currently only supported for foreign key and <literal>CHECK</literal>
|
||||||
constraints.
|
constraints.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
@ -100,8 +100,9 @@ CreateConstraintEntry(const char *constraintName,
|
|||||||
ObjectAddresses *addrs_auto;
|
ObjectAddresses *addrs_auto;
|
||||||
ObjectAddresses *addrs_normal;
|
ObjectAddresses *addrs_normal;
|
||||||
|
|
||||||
/* Only CHECK constraint can be not enforced */
|
/* Only CHECK or FOREIGN KEY constraint can be not enforced */
|
||||||
Assert(isEnforced || constraintType == CONSTRAINT_CHECK);
|
Assert(isEnforced || constraintType == CONSTRAINT_CHECK ||
|
||||||
|
constraintType == CONSTRAINT_FOREIGN);
|
||||||
/* NOT ENFORCED constraint must be NOT VALID */
|
/* NOT ENFORCED constraint must be NOT VALID */
|
||||||
Assert(isEnforced || !isValidated);
|
Assert(isEnforced || !isValidated);
|
||||||
|
|
||||||
|
@ -281,7 +281,7 @@ F461 Named character sets NO
|
|||||||
F471 Scalar subquery values YES
|
F471 Scalar subquery values YES
|
||||||
F481 Expanded NULL predicate YES
|
F481 Expanded NULL predicate YES
|
||||||
F491 Constraint management 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 YES
|
||||||
F501 Features and conformance views 01 SQL_FEATURES view YES
|
F501 Features and conformance views 01 SQL_FEATURES view YES
|
||||||
F501 Features and conformance views 02 SQL_SIZING view YES
|
F501 Features and conformance views 02 SQL_SIZING view YES
|
||||||
|
@ -395,6 +395,14 @@ static ObjectAddress ATExecAlterConstraint(List **wqueue, Relation rel,
|
|||||||
static bool ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon, Relation conrel,
|
static bool ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon, Relation conrel,
|
||||||
Relation tgrel, Relation rel, HeapTuple contuple,
|
Relation tgrel, Relation rel, HeapTuple contuple,
|
||||||
bool recurse, LOCKMODE lockmode);
|
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,
|
static bool ATExecAlterConstrDeferrability(List **wqueue, ATAlterConstraint *cmdcon,
|
||||||
Relation conrel, Relation tgrel, Relation rel,
|
Relation conrel, Relation tgrel, Relation rel,
|
||||||
HeapTuple contuple, bool recurse,
|
HeapTuple contuple, bool recurse,
|
||||||
@ -405,6 +413,14 @@ static bool ATExecAlterConstrInheritability(List **wqueue, ATAlterConstraint *cm
|
|||||||
static void AlterConstrTriggerDeferrability(Oid conoid, Relation tgrel, Relation rel,
|
static void AlterConstrTriggerDeferrability(Oid conoid, Relation tgrel, Relation rel,
|
||||||
bool deferrable, bool initdeferred,
|
bool deferrable, bool initdeferred,
|
||||||
List **otherrelids);
|
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,
|
static void AlterConstrDeferrabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
|
||||||
Relation conrel, Relation tgrel, Relation rel,
|
Relation conrel, Relation tgrel, Relation rel,
|
||||||
HeapTuple contuple, bool recurse,
|
HeapTuple contuple, bool recurse,
|
||||||
@ -10610,7 +10626,7 @@ addFkConstraint(addFkConstraintSides fkside,
|
|||||||
CONSTRAINT_FOREIGN,
|
CONSTRAINT_FOREIGN,
|
||||||
fkconstraint->deferrable,
|
fkconstraint->deferrable,
|
||||||
fkconstraint->initdeferred,
|
fkconstraint->initdeferred,
|
||||||
true, /* Is Enforced */
|
fkconstraint->is_enforced,
|
||||||
fkconstraint->initially_valid,
|
fkconstraint->initially_valid,
|
||||||
parentConstr,
|
parentConstr,
|
||||||
RelationGetRelid(rel),
|
RelationGetRelid(rel),
|
||||||
@ -10728,21 +10744,23 @@ addFkRecurseReferenced(Constraint *fkconstraint, Relation rel,
|
|||||||
Oid parentDelTrigger, Oid parentUpdTrigger,
|
Oid parentDelTrigger, Oid parentUpdTrigger,
|
||||||
bool with_period)
|
bool with_period)
|
||||||
{
|
{
|
||||||
Oid deleteTriggerOid,
|
Oid deleteTriggerOid = InvalidOid,
|
||||||
updateTriggerOid;
|
updateTriggerOid = InvalidOid;
|
||||||
|
|
||||||
Assert(CheckRelationLockedByMe(pkrel, ShareRowExclusiveLock, true));
|
Assert(CheckRelationLockedByMe(pkrel, ShareRowExclusiveLock, true));
|
||||||
Assert(CheckRelationLockedByMe(rel, 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),
|
if (fkconstraint->is_enforced)
|
||||||
RelationGetRelid(pkrel),
|
createForeignKeyActionTriggers(RelationGetRelid(rel),
|
||||||
fkconstraint,
|
RelationGetRelid(pkrel),
|
||||||
parentConstr, indexOid,
|
fkconstraint,
|
||||||
parentDelTrigger, parentUpdTrigger,
|
parentConstr, indexOid,
|
||||||
&deleteTriggerOid, &updateTriggerOid);
|
parentDelTrigger, parentUpdTrigger,
|
||||||
|
&deleteTriggerOid, &updateTriggerOid);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the referenced table is partitioned, recurse on ourselves to handle
|
* 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,
|
Oid parentInsTrigger, Oid parentUpdTrigger,
|
||||||
bool with_period)
|
bool with_period)
|
||||||
{
|
{
|
||||||
Oid insertTriggerOid,
|
Oid insertTriggerOid = InvalidOid,
|
||||||
updateTriggerOid;
|
updateTriggerOid = InvalidOid;
|
||||||
|
|
||||||
Assert(OidIsValid(parentConstr));
|
Assert(OidIsValid(parentConstr));
|
||||||
Assert(CheckRelationLockedByMe(rel, ShareRowExclusiveLock, true));
|
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")));
|
errmsg("foreign key constraints are not supported on foreign tables")));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add the check triggers to it and, if necessary, schedule it to be
|
* Add check triggers if the constraint is ENFORCED, and if needed,
|
||||||
* checked in Phase 3.
|
* schedule them to be checked in Phase 3.
|
||||||
*
|
*
|
||||||
* If the relation is partitioned, drill down to do it to its partitions.
|
* If the relation is partitioned, drill down to do it to its partitions.
|
||||||
*/
|
*/
|
||||||
createForeignKeyCheckTriggers(RelationGetRelid(rel),
|
if (fkconstraint->is_enforced)
|
||||||
RelationGetRelid(pkrel),
|
createForeignKeyCheckTriggers(RelationGetRelid(rel),
|
||||||
fkconstraint,
|
RelationGetRelid(pkrel),
|
||||||
parentConstr,
|
fkconstraint,
|
||||||
indexOid,
|
parentConstr,
|
||||||
parentInsTrigger, parentUpdTrigger,
|
indexOid,
|
||||||
&insertTriggerOid, &updateTriggerOid);
|
parentInsTrigger, parentUpdTrigger,
|
||||||
|
&insertTriggerOid, &updateTriggerOid);
|
||||||
|
|
||||||
if (rel->rd_rel->relkind == RELKIND_RELATION)
|
if (rel->rd_rel->relkind == RELKIND_RELATION)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Tell Phase 3 to check that the constraint is satisfied by existing
|
* Tell Phase 3 to check that the constraint is satisfied by existing
|
||||||
* rows. We can skip this during table creation, when requested
|
* rows. We can skip this during table creation, when constraint is
|
||||||
* explicitly by specifying NOT VALID in an ADD FOREIGN KEY command,
|
* specified as NOT ENFORCED, or when requested explicitly by
|
||||||
* and when we're recreating a constraint following a SET DATA TYPE
|
* specifying NOT VALID in an ADD FOREIGN KEY command, and when we're
|
||||||
* operation that did not impugn its validity.
|
* 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;
|
NewConstraint *newcon;
|
||||||
AlteredTableInfo *tab;
|
AlteredTableInfo *tab;
|
||||||
@ -11129,8 +11150,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
|
|||||||
AttrNumber confdelsetcols[INDEX_MAX_KEYS];
|
AttrNumber confdelsetcols[INDEX_MAX_KEYS];
|
||||||
Constraint *fkconstraint;
|
Constraint *fkconstraint;
|
||||||
ObjectAddress address;
|
ObjectAddress address;
|
||||||
Oid deleteTriggerOid,
|
Oid deleteTriggerOid = InvalidOid,
|
||||||
updateTriggerOid;
|
updateTriggerOid = InvalidOid;
|
||||||
|
|
||||||
tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid));
|
tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid));
|
||||||
if (!HeapTupleIsValid(tuple))
|
if (!HeapTupleIsValid(tuple))
|
||||||
@ -11190,8 +11211,9 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
|
|||||||
fkconstraint->fk_del_set_cols = NIL;
|
fkconstraint->fk_del_set_cols = NIL;
|
||||||
fkconstraint->old_conpfeqop = NIL;
|
fkconstraint->old_conpfeqop = NIL;
|
||||||
fkconstraint->old_pktable_oid = InvalidOid;
|
fkconstraint->old_pktable_oid = InvalidOid;
|
||||||
|
fkconstraint->is_enforced = constrForm->conenforced;
|
||||||
fkconstraint->skip_validation = false;
|
fkconstraint->skip_validation = false;
|
||||||
fkconstraint->initially_valid = true;
|
fkconstraint->initially_valid = constrForm->convalidated;
|
||||||
|
|
||||||
/* set up colnames that are used to generate the constraint name */
|
/* set up colnames that are used to generate the constraint name */
|
||||||
for (int i = 0; i < numfks; i++)
|
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
|
* parent OIDs for similar triggers that will be created on the
|
||||||
* partition in addFkRecurseReferenced().
|
* partition in addFkRecurseReferenced().
|
||||||
*/
|
*/
|
||||||
GetForeignKeyActionTriggers(trigrel, constrOid,
|
if (constrForm->conenforced)
|
||||||
constrForm->confrelid, constrForm->conrelid,
|
GetForeignKeyActionTriggers(trigrel, constrOid,
|
||||||
&deleteTriggerOid, &updateTriggerOid);
|
constrForm->confrelid, constrForm->conrelid,
|
||||||
|
&deleteTriggerOid, &updateTriggerOid);
|
||||||
|
|
||||||
/* Add this constraint ... */
|
/* Add this constraint ... */
|
||||||
address = addFkConstraint(addFkReferencedSide,
|
address = addFkConstraint(addFkReferencedSide,
|
||||||
@ -11354,8 +11377,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
|
|||||||
Oid indexOid;
|
Oid indexOid;
|
||||||
ObjectAddress address;
|
ObjectAddress address;
|
||||||
ListCell *lc;
|
ListCell *lc;
|
||||||
Oid insertTriggerOid,
|
Oid insertTriggerOid = InvalidOid,
|
||||||
updateTriggerOid;
|
updateTriggerOid = InvalidOid;
|
||||||
bool with_period;
|
bool with_period;
|
||||||
|
|
||||||
tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(parentConstrOid));
|
tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(parentConstrOid));
|
||||||
@ -11387,17 +11410,18 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
|
|||||||
mapped_conkey[i] = attmap->attnums[conkey[i] - 1];
|
mapped_conkey[i] = attmap->attnums[conkey[i] - 1];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get the "check" triggers belonging to the constraint to pass as
|
* Get the "check" triggers belonging to the constraint, if it is
|
||||||
* parent OIDs for similar triggers that will be created on the
|
* ENFORCED, to pass as parent OIDs for similar triggers that will be
|
||||||
* partition in addFkRecurseReferencing(). They are also passed to
|
* created on the partition in addFkRecurseReferencing(). They are
|
||||||
* tryAttachPartitionForeignKey() below to simply assign as parents to
|
* also passed to tryAttachPartitionForeignKey() below to simply
|
||||||
* the partition's existing "check" triggers, that is, if the
|
* assign as parents to the partition's existing "check" triggers,
|
||||||
* corresponding constraints is deemed attachable to the parent
|
* that is, if the corresponding constraints is deemed attachable to
|
||||||
* constraint.
|
* the parent constraint.
|
||||||
*/
|
*/
|
||||||
GetForeignKeyCheckTriggers(trigrel, constrForm->oid,
|
if (constrForm->conenforced)
|
||||||
constrForm->confrelid, constrForm->conrelid,
|
GetForeignKeyCheckTriggers(trigrel, constrForm->oid,
|
||||||
&insertTriggerOid, &updateTriggerOid);
|
constrForm->confrelid, constrForm->conrelid,
|
||||||
|
&insertTriggerOid, &updateTriggerOid);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Before creating a new constraint, see whether any existing FKs are
|
* 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->fk_del_set_cols = NIL;
|
||||||
fkconstraint->old_conpfeqop = NIL;
|
fkconstraint->old_conpfeqop = NIL;
|
||||||
fkconstraint->old_pktable_oid = InvalidOid;
|
fkconstraint->old_pktable_oid = InvalidOid;
|
||||||
|
fkconstraint->is_enforced = constrForm->conenforced;
|
||||||
fkconstraint->skip_validation = false;
|
fkconstraint->skip_validation = false;
|
||||||
fkconstraint->initially_valid = constrForm->convalidated;
|
fkconstraint->initially_valid = constrForm->convalidated;
|
||||||
for (int i = 0; i < numfks; i++)
|
for (int i = 0; i < numfks; i++)
|
||||||
@ -11564,6 +11589,23 @@ tryAttachPartitionForeignKey(List **wqueue,
|
|||||||
if (!HeapTupleIsValid(partcontup))
|
if (!HeapTupleIsValid(partcontup))
|
||||||
elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
|
elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
|
||||||
partConstr = (Form_pg_constraint) GETSTRUCT(partcontup);
|
partConstr = (Form_pg_constraint) GETSTRUCT(partcontup);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* An error should be raised if the constraint enforceability is
|
||||||
|
* different. Returning false without raising an error, as we do for other
|
||||||
|
* attributes, could lead to a duplicate constraint with the same
|
||||||
|
* enforceability as the parent. While this may be acceptable, it may not
|
||||||
|
* be ideal. Therefore, it's better to raise an error and allow the user
|
||||||
|
* to correct the enforceability before proceeding.
|
||||||
|
*/
|
||||||
|
if (partConstr->conenforced != parentConstr->conenforced)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||||
|
errmsg("constraint \"%s\" enforceability conflicts with constraint \"%s\" on relation \"%s\"",
|
||||||
|
NameStr(parentConstr->conname),
|
||||||
|
NameStr(partConstr->conname),
|
||||||
|
RelationGetRelationName(partition))));
|
||||||
|
|
||||||
if (OidIsValid(partConstr->conparentid) ||
|
if (OidIsValid(partConstr->conparentid) ||
|
||||||
partConstr->condeferrable != parentConstr->condeferrable ||
|
partConstr->condeferrable != parentConstr->condeferrable ||
|
||||||
partConstr->condeferred != parentConstr->condeferred ||
|
partConstr->condeferred != parentConstr->condeferred ||
|
||||||
@ -11610,8 +11652,7 @@ AttachPartitionForeignKey(List **wqueue,
|
|||||||
bool queueValidation;
|
bool queueValidation;
|
||||||
Oid partConstrFrelid;
|
Oid partConstrFrelid;
|
||||||
Oid partConstrRelid;
|
Oid partConstrRelid;
|
||||||
Oid insertTriggerOid,
|
bool parentConstrIsEnforced;
|
||||||
updateTriggerOid;
|
|
||||||
|
|
||||||
/* Fetch the parent constraint tuple */
|
/* Fetch the parent constraint tuple */
|
||||||
parentConstrTup = SearchSysCache1(CONSTROID,
|
parentConstrTup = SearchSysCache1(CONSTROID,
|
||||||
@ -11619,6 +11660,7 @@ AttachPartitionForeignKey(List **wqueue,
|
|||||||
if (!HeapTupleIsValid(parentConstrTup))
|
if (!HeapTupleIsValid(parentConstrTup))
|
||||||
elog(ERROR, "cache lookup failed for constraint %u", parentConstrOid);
|
elog(ERROR, "cache lookup failed for constraint %u", parentConstrOid);
|
||||||
parentConstr = (Form_pg_constraint) GETSTRUCT(parentConstrTup);
|
parentConstr = (Form_pg_constraint) GETSTRUCT(parentConstrTup);
|
||||||
|
parentConstrIsEnforced = parentConstr->conenforced;
|
||||||
|
|
||||||
/* Fetch the child constraint tuple */
|
/* Fetch the child constraint tuple */
|
||||||
partcontup = SearchSysCache1(CONSTROID,
|
partcontup = SearchSysCache1(CONSTROID,
|
||||||
@ -11668,17 +11710,24 @@ AttachPartitionForeignKey(List **wqueue,
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Like the constraint, attach partition's "check" triggers to the
|
* 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,
|
if (parentConstrIsEnforced)
|
||||||
partConstrOid, partConstrFrelid, partConstrRelid,
|
{
|
||||||
&insertTriggerOid, &updateTriggerOid);
|
Oid insertTriggerOid,
|
||||||
Assert(OidIsValid(insertTriggerOid) && OidIsValid(parentInsTrigger));
|
updateTriggerOid;
|
||||||
TriggerSetParentTrigger(trigrel, insertTriggerOid, parentInsTrigger,
|
|
||||||
RelationGetRelid(partition));
|
GetForeignKeyCheckTriggers(trigrel,
|
||||||
Assert(OidIsValid(updateTriggerOid) && OidIsValid(parentUpdTrigger));
|
partConstrOid, partConstrFrelid, partConstrRelid,
|
||||||
TriggerSetParentTrigger(trigrel, updateTriggerOid, parentUpdTrigger,
|
&insertTriggerOid, &updateTriggerOid);
|
||||||
RelationGetRelid(partition));
|
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
|
* We updated this pg_constraint row above to set its parent; validating
|
||||||
@ -11792,6 +11841,10 @@ RemoveInheritedConstraint(Relation conrel, Relation trigrel, Oid conoid,
|
|||||||
*
|
*
|
||||||
* The subroutine for tryAttachPartitionForeignKey handles the deletion of
|
* The subroutine for tryAttachPartitionForeignKey handles the deletion of
|
||||||
* action triggers for the foreign key constraint.
|
* 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
|
static void
|
||||||
DropForeignKeyConstraintTriggers(Relation trigrel, Oid conoid, Oid confrelid,
|
DropForeignKeyConstraintTriggers(Relation trigrel, Oid conoid, Oid confrelid,
|
||||||
@ -11812,10 +11865,27 @@ DropForeignKeyConstraintTriggers(Relation trigrel, Oid conoid, Oid confrelid,
|
|||||||
Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
|
Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
|
||||||
ObjectAddress trigger;
|
ObjectAddress trigger;
|
||||||
|
|
||||||
if (trgform->tgconstrrelid != conrelid)
|
/* Invalid if trigger is not for a referential integrity constraint */
|
||||||
|
if (!OidIsValid(trgform->tgconstrrelid))
|
||||||
continue;
|
continue;
|
||||||
if (trgform->tgrelid != confrelid)
|
if (OidIsValid(conrelid) && trgform->tgconstrrelid != conrelid)
|
||||||
continue;
|
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
|
* The constraint is originally set up to contain this trigger as an
|
||||||
@ -12028,6 +12098,11 @@ ATExecAlterConstraint(List **wqueue, Relation rel, ATAlterConstraint *cmdcon,
|
|||||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||||
errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key constraint",
|
errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key constraint",
|
||||||
cmdcon->conname, RelationGetRelationName(rel))));
|
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 &&
|
if (cmdcon->alterInheritability &&
|
||||||
currcon->contype != CONSTRAINT_NOTNULL)
|
currcon->contype != CONSTRAINT_NOTNULL)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
@ -12107,7 +12182,7 @@ ATExecAlterConstraint(List **wqueue, Relation rel, ATAlterConstraint *cmdcon,
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* A subroutine of ATExecAlterConstraint that calls the respective routines for
|
* A subroutine of ATExecAlterConstraint that calls the respective routines for
|
||||||
* altering constraint attributes.
|
* altering constraint's enforceability, deferrability or inheritability.
|
||||||
*/
|
*/
|
||||||
static bool
|
static bool
|
||||||
ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon,
|
ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon,
|
||||||
@ -12115,16 +12190,35 @@ ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon,
|
|||||||
HeapTuple contuple, bool recurse,
|
HeapTuple contuple, bool recurse,
|
||||||
LOCKMODE lockmode)
|
LOCKMODE lockmode)
|
||||||
{
|
{
|
||||||
|
Form_pg_constraint currcon;
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
List *otherrelids = NIL;
|
List *otherrelids = NIL;
|
||||||
|
|
||||||
|
currcon = (Form_pg_constraint) GETSTRUCT(contuple);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Do the catalog work for the deferrability change, recurse if necessary.
|
* 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->alterDeferrability &&
|
if (cmdcon->alterEnforceability &&
|
||||||
ATExecAlterConstrDeferrability(wqueue, cmdcon, conrel, tgrel, rel,
|
ATExecAlterConstrEnforceability(wqueue, cmdcon, conrel, tgrel,
|
||||||
contuple, recurse, &otherrelids,
|
currcon->conrelid, currcon->confrelid,
|
||||||
lockmode))
|
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
|
* AlterConstrUpdateConstraintEntry already invalidated relcache for
|
||||||
@ -12149,6 +12243,151 @@ ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon,
|
|||||||
return changed;
|
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);
|
||||||
|
}
|
||||||
|
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.
|
* Returns true if the constraint's deferrability is altered.
|
||||||
*
|
*
|
||||||
@ -12353,6 +12592,55 @@ AlterConstrTriggerDeferrability(Oid conoid, Relation tgrel, Relation rel,
|
|||||||
systable_endscan(tgscan);
|
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)))
|
||||||
|
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
|
* Invokes ATExecAlterConstrDeferrability for each constraint that is a child of
|
||||||
* the specified constraint.
|
* the specified constraint.
|
||||||
@ -12413,11 +12701,25 @@ AlterConstrUpdateConstraintEntry(ATAlterConstraint *cmdcon, Relation conrel,
|
|||||||
HeapTuple copyTuple;
|
HeapTuple copyTuple;
|
||||||
Form_pg_constraint copy_con;
|
Form_pg_constraint copy_con;
|
||||||
|
|
||||||
Assert(cmdcon->alterDeferrability || cmdcon->alterInheritability);
|
Assert(cmdcon->alterEnforceability || cmdcon->alterDeferrability ||
|
||||||
|
cmdcon->alterInheritability);
|
||||||
|
|
||||||
copyTuple = heap_copytuple(contuple);
|
copyTuple = heap_copytuple(contuple);
|
||||||
copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
|
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)
|
if (cmdcon->alterDeferrability)
|
||||||
{
|
{
|
||||||
copy_con->condeferrable = cmdcon->deferrable;
|
copy_con->condeferrable = cmdcon->deferrable;
|
||||||
@ -17137,9 +17439,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
|
|||||||
NameStr(child_con->conname), RelationGetRelationName(child_rel))));
|
NameStr(child_con->conname), RelationGetRelationName(child_rel))));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A non-enforced child constraint cannot be merged with an
|
* A NOT ENFORCED child constraint cannot be merged with an
|
||||||
* enforced parent constraint. However, the reverse is allowed,
|
* ENFORCED parent constraint. However, the reverse is allowed,
|
||||||
* where the child constraint is enforced.
|
* where the child constraint is ENFORCED.
|
||||||
*/
|
*/
|
||||||
if (parent_con->conenforced && !child_con->conenforced)
|
if (parent_con->conenforced && !child_con->conenforced)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
@ -20510,8 +20812,6 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
|
|||||||
ForeignKeyCacheInfo *fk = lfirst(cell);
|
ForeignKeyCacheInfo *fk = lfirst(cell);
|
||||||
HeapTuple contup;
|
HeapTuple contup;
|
||||||
Form_pg_constraint conform;
|
Form_pg_constraint conform;
|
||||||
Oid insertTriggerOid,
|
|
||||||
updateTriggerOid;
|
|
||||||
|
|
||||||
contup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
|
contup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
|
||||||
if (!HeapTupleIsValid(contup))
|
if (!HeapTupleIsValid(contup))
|
||||||
@ -20538,17 +20838,25 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Also, look up the partition's "check" triggers corresponding to the
|
* 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,
|
if (fk->conenforced)
|
||||||
fk->conoid, fk->confrelid, fk->conrelid,
|
{
|
||||||
&insertTriggerOid, &updateTriggerOid);
|
Oid insertTriggerOid,
|
||||||
Assert(OidIsValid(insertTriggerOid));
|
updateTriggerOid;
|
||||||
TriggerSetParentTrigger(trigrel, insertTriggerOid, InvalidOid,
|
|
||||||
RelationGetRelid(partRel));
|
GetForeignKeyCheckTriggers(trigrel,
|
||||||
Assert(OidIsValid(updateTriggerOid));
|
fk->conoid, fk->confrelid, fk->conrelid,
|
||||||
TriggerSetParentTrigger(trigrel, updateTriggerOid, InvalidOid,
|
&insertTriggerOid, &updateTriggerOid);
|
||||||
RelationGetRelid(partRel));
|
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
|
* Lastly, create the action triggers on the referenced table, using
|
||||||
@ -20588,8 +20896,9 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
|
|||||||
fkconstraint->conname = pstrdup(NameStr(conform->conname));
|
fkconstraint->conname = pstrdup(NameStr(conform->conname));
|
||||||
fkconstraint->deferrable = conform->condeferrable;
|
fkconstraint->deferrable = conform->condeferrable;
|
||||||
fkconstraint->initdeferred = conform->condeferred;
|
fkconstraint->initdeferred = conform->condeferred;
|
||||||
|
fkconstraint->is_enforced = conform->conenforced;
|
||||||
fkconstraint->skip_validation = true;
|
fkconstraint->skip_validation = true;
|
||||||
fkconstraint->initially_valid = true;
|
fkconstraint->initially_valid = conform->convalidated;
|
||||||
/* a few irrelevant fields omitted here */
|
/* a few irrelevant fields omitted here */
|
||||||
fkconstraint->pktable = NULL;
|
fkconstraint->pktable = NULL;
|
||||||
fkconstraint->fk_attrs = NIL;
|
fkconstraint->fk_attrs = NIL;
|
||||||
|
@ -640,6 +640,10 @@ get_relation_foreign_keys(PlannerInfo *root, RelOptInfo *rel,
|
|||||||
/* conrelid should always be that of the table we're considering */
|
/* conrelid should always be that of the table we're considering */
|
||||||
Assert(cachedfk->conrelid == RelationGetRelid(relation));
|
Assert(cachedfk->conrelid == RelationGetRelid(relation));
|
||||||
|
|
||||||
|
/* skip constraints currently not enforced */
|
||||||
|
if (!cachedfk->conenforced)
|
||||||
|
continue;
|
||||||
|
|
||||||
/* Scan to find other RTEs matching confrelid */
|
/* Scan to find other RTEs matching confrelid */
|
||||||
rti = 0;
|
rti = 0;
|
||||||
foreach(lc2, rtable)
|
foreach(lc2, rtable)
|
||||||
|
@ -2662,6 +2662,8 @@ alter_table_cmd:
|
|||||||
n->subtype = AT_AlterConstraint;
|
n->subtype = AT_AlterConstraint;
|
||||||
n->def = (Node *) c;
|
n->def = (Node *) c;
|
||||||
c->conname = $3;
|
c->conname = $3;
|
||||||
|
if ($4 & (CAS_NOT_ENFORCED | CAS_ENFORCED))
|
||||||
|
c->alterEnforceability = true;
|
||||||
if ($4 & (CAS_DEFERRABLE | CAS_NOT_DEFERRABLE |
|
if ($4 & (CAS_DEFERRABLE | CAS_NOT_DEFERRABLE |
|
||||||
CAS_INITIALLY_DEFERRED | CAS_INITIALLY_IMMEDIATE))
|
CAS_INITIALLY_DEFERRED | CAS_INITIALLY_IMMEDIATE))
|
||||||
c->alterDeferrability = true;
|
c->alterDeferrability = true;
|
||||||
@ -2670,7 +2672,10 @@ alter_table_cmd:
|
|||||||
processCASbits($4, @4, "FOREIGN KEY",
|
processCASbits($4, @4, "FOREIGN KEY",
|
||||||
&c->deferrable,
|
&c->deferrable,
|
||||||
&c->initdeferred,
|
&c->initdeferred,
|
||||||
NULL, NULL, &c->noinherit, yyscanner);
|
&c->is_enforced,
|
||||||
|
NULL,
|
||||||
|
&c->noinherit,
|
||||||
|
yyscanner);
|
||||||
$$ = (Node *) n;
|
$$ = (Node *) n;
|
||||||
}
|
}
|
||||||
/* ALTER TABLE <name> ALTER CONSTRAINT INHERIT */
|
/* ALTER TABLE <name> ALTER CONSTRAINT INHERIT */
|
||||||
@ -4334,7 +4339,7 @@ ConstraintElem:
|
|||||||
n->fk_del_set_cols = ($11)->deleteAction->cols;
|
n->fk_del_set_cols = ($11)->deleteAction->cols;
|
||||||
processCASbits($12, @12, "FOREIGN KEY",
|
processCASbits($12, @12, "FOREIGN KEY",
|
||||||
&n->deferrable, &n->initdeferred,
|
&n->deferrable, &n->initdeferred,
|
||||||
NULL, &n->skip_validation, NULL,
|
&n->is_enforced, &n->skip_validation, NULL,
|
||||||
yyscanner);
|
yyscanner);
|
||||||
n->initially_valid = !n->skip_validation;
|
n->initially_valid = !n->skip_validation;
|
||||||
$$ = (Node *) n;
|
$$ = (Node *) n;
|
||||||
|
@ -2962,8 +2962,10 @@ transformFKConstraints(CreateStmtContext *cxt,
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* If CREATE TABLE or adding a column with NULL default, we can safely
|
* If CREATE TABLE or adding a column with NULL default, we can safely
|
||||||
* skip validation of FK constraints, and nonetheless mark them valid.
|
* skip validation of FK constraints, and mark them as valid based on the
|
||||||
* (This will override any user-supplied NOT VALID flag.)
|
* 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)
|
if (skipValidation)
|
||||||
{
|
{
|
||||||
@ -2972,7 +2974,7 @@ transformFKConstraints(CreateStmtContext *cxt,
|
|||||||
Constraint *constraint = (Constraint *) lfirst(fkclist);
|
Constraint *constraint = (Constraint *) lfirst(fkclist);
|
||||||
|
|
||||||
constraint->skip_validation = true;
|
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:
|
case CONSTR_ATTR_ENFORCED:
|
||||||
if (lastprimarycon == NULL ||
|
if (lastprimarycon == NULL ||
|
||||||
lastprimarycon->contype != CONSTR_CHECK)
|
(lastprimarycon->contype != CONSTR_CHECK &&
|
||||||
|
lastprimarycon->contype != CONSTR_FOREIGN))
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
errmsg("misplaced ENFORCED clause"),
|
errmsg("misplaced ENFORCED clause"),
|
||||||
@ -3983,7 +3986,8 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
|
|||||||
|
|
||||||
case CONSTR_ATTR_NOT_ENFORCED:
|
case CONSTR_ATTR_NOT_ENFORCED:
|
||||||
if (lastprimarycon == NULL ||
|
if (lastprimarycon == NULL ||
|
||||||
lastprimarycon->contype != CONSTR_CHECK)
|
(lastprimarycon->contype != CONSTR_CHECK &&
|
||||||
|
lastprimarycon->contype != CONSTR_FOREIGN))
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
errmsg("misplaced NOT ENFORCED clause"),
|
errmsg("misplaced NOT ENFORCED clause"),
|
||||||
|
6
src/backend/utils/cache/relcache.c
vendored
6
src/backend/utils/cache/relcache.c
vendored
@ -4666,11 +4666,6 @@ RelationGetFKeyList(Relation relation)
|
|||||||
if (relation->rd_fkeyvalid)
|
if (relation->rd_fkeyvalid)
|
||||||
return relation->rd_fkeylist;
|
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
|
* 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
|
* doing the scan. After successfully completing the scan, we copy that
|
||||||
@ -4702,6 +4697,7 @@ RelationGetFKeyList(Relation relation)
|
|||||||
info->conoid = constraint->oid;
|
info->conoid = constraint->oid;
|
||||||
info->conrelid = constraint->conrelid;
|
info->conrelid = constraint->conrelid;
|
||||||
info->confrelid = constraint->confrelid;
|
info->confrelid = constraint->confrelid;
|
||||||
|
info->conenforced = constraint->conenforced;
|
||||||
|
|
||||||
DeconstructFkConstraintRow(htup, &info->nkeys,
|
DeconstructFkConstraintRow(htup, &info->nkeys,
|
||||||
info->conkey,
|
info->conkey,
|
||||||
|
@ -8084,13 +8084,7 @@ getConstraints(Archive *fout, TableInfo tblinfo[], int numTables)
|
|||||||
{
|
{
|
||||||
TableInfo *tinfo = &tblinfo[i];
|
TableInfo *tinfo = &tblinfo[i];
|
||||||
|
|
||||||
/*
|
if (!(tinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
|
||||||
* 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))
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* OK, we need info for this table */
|
/* OK, we need info for this table */
|
||||||
|
@ -2550,136 +2550,124 @@ describeOneTableDetails(const char *schemaname,
|
|||||||
PQclear(result);
|
PQclear(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/* Print foreign-key constraints */
|
||||||
* Print foreign-key constraints (there are none if no triggers,
|
if (pset.sversion >= 120000 &&
|
||||||
* except if the table is partitioned, in which case the triggers
|
(tableinfo.ispartition || tableinfo.relkind == RELKIND_PARTITIONED_TABLE))
|
||||||
* appear in the partitions)
|
|
||||||
*/
|
|
||||||
if (tableinfo.hastriggers ||
|
|
||||||
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.
|
||||||
/*
|
*/
|
||||||
* Put the constraints defined in this table first, followed
|
printfPQExpBuffer(&buf,
|
||||||
* by the constraints defined in ancestor partitioned tables.
|
"SELECT conrelid = '%s'::pg_catalog.regclass AS sametable,\n"
|
||||||
*/
|
" conname,\n"
|
||||||
printfPQExpBuffer(&buf,
|
" pg_catalog.pg_get_constraintdef(oid, true) AS condef,\n"
|
||||||
"SELECT conrelid = '%s'::pg_catalog.regclass AS sametable,\n"
|
" conrelid::pg_catalog.regclass AS ontable\n"
|
||||||
" conname,\n"
|
" FROM pg_catalog.pg_constraint,\n"
|
||||||
" pg_catalog.pg_get_constraintdef(oid, true) AS condef,\n"
|
" pg_catalog.pg_partition_ancestors('%s')\n"
|
||||||
" conrelid::pg_catalog.regclass AS ontable\n"
|
" WHERE conrelid = relid AND contype = " CppAsString2(CONSTRAINT_FOREIGN) " AND conparentid = 0\n"
|
||||||
" FROM pg_catalog.pg_constraint,\n"
|
"ORDER BY sametable DESC, conname;",
|
||||||
" pg_catalog.pg_partition_ancestors('%s')\n"
|
oid, oid);
|
||||||
" WHERE conrelid = relid AND contype = " CppAsString2(CONSTRAINT_FOREIGN) " AND conparentid = 0\n"
|
}
|
||||||
"ORDER BY sametable DESC, conname;",
|
else
|
||||||
oid, oid);
|
{
|
||||||
}
|
printfPQExpBuffer(&buf,
|
||||||
else
|
"SELECT true as sametable, conname,\n"
|
||||||
{
|
" pg_catalog.pg_get_constraintdef(r.oid, true) as condef,\n"
|
||||||
printfPQExpBuffer(&buf,
|
" conrelid::pg_catalog.regclass AS ontable\n"
|
||||||
"SELECT true as sametable, conname,\n"
|
"FROM pg_catalog.pg_constraint r\n"
|
||||||
" pg_catalog.pg_get_constraintdef(r.oid, true) as condef,\n"
|
"WHERE r.conrelid = '%s' AND r.contype = " CppAsString2(CONSTRAINT_FOREIGN) "\n",
|
||||||
" conrelid::pg_catalog.regclass AS ontable\n"
|
oid);
|
||||||
"FROM pg_catalog.pg_constraint r\n"
|
|
||||||
"WHERE r.conrelid = '%s' AND r.contype = " CppAsString2(CONSTRAINT_FOREIGN) "\n",
|
|
||||||
oid);
|
|
||||||
|
|
||||||
if (pset.sversion >= 120000)
|
if (pset.sversion >= 120000)
|
||||||
appendPQExpBufferStr(&buf, " AND conparentid = 0\n");
|
appendPQExpBufferStr(&buf, " AND conparentid = 0\n");
|
||||||
appendPQExpBufferStr(&buf, "ORDER BY conname");
|
appendPQExpBufferStr(&buf, "ORDER BY conname");
|
||||||
}
|
|
||||||
|
|
||||||
result = PSQLexec(buf.data);
|
|
||||||
if (!result)
|
|
||||||
goto error_return;
|
|
||||||
else
|
|
||||||
tuples = PQntuples(result);
|
|
||||||
|
|
||||||
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, _("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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* print incoming foreign-key references */
|
result = PSQLexec(buf.data);
|
||||||
if (tableinfo.hastriggers ||
|
if (!result)
|
||||||
tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
|
goto error_return;
|
||||||
|
else
|
||||||
|
tuples = PQntuples(result);
|
||||||
|
|
||||||
|
if (tuples > 0)
|
||||||
{
|
{
|
||||||
if (pset.sversion >= 120000)
|
int i_sametable = PQfnumber(result, "sametable"),
|
||||||
{
|
i_conname = PQfnumber(result, "conname"),
|
||||||
printfPQExpBuffer(&buf,
|
i_condef = PQfnumber(result, "condef"),
|
||||||
"SELECT conname, conrelid::pg_catalog.regclass AS ontable,\n"
|
i_ontable = PQfnumber(result, "ontable");
|
||||||
" 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);
|
printTableAddFooter(&cont, _("Foreign-key constraints:"));
|
||||||
if (!result)
|
for (i = 0; i < tuples; i++)
|
||||||
goto error_return;
|
|
||||||
else
|
|
||||||
tuples = PQntuples(result);
|
|
||||||
|
|
||||||
if (tuples > 0)
|
|
||||||
{
|
{
|
||||||
int i_conname = PQfnumber(result, "conname"),
|
/*
|
||||||
i_ontable = PQfnumber(result, "ontable"),
|
* Print untranslated constraint name and definition. Use a
|
||||||
i_condef = PQfnumber(result, "condef");
|
* "TABLE tab" prefix when the constraint is defined in a
|
||||||
|
* parent partitioned table.
|
||||||
printTableAddFooter(&cont, _("Referenced by:"));
|
*/
|
||||||
for (i = 0; i < tuples; i++)
|
if (strcmp(PQgetvalue(result, i, i_sametable), "f") == 0)
|
||||||
{
|
|
||||||
printfPQExpBuffer(&buf, " TABLE \"%s\" CONSTRAINT \"%s\" %s",
|
printfPQExpBuffer(&buf, " TABLE \"%s\" CONSTRAINT \"%s\" %s",
|
||||||
PQgetvalue(result, i, i_ontable),
|
PQgetvalue(result, i, i_ontable),
|
||||||
PQgetvalue(result, i, i_conname),
|
PQgetvalue(result, i, i_conname),
|
||||||
PQgetvalue(result, i, i_condef));
|
PQgetvalue(result, i, i_condef));
|
||||||
|
else
|
||||||
|
printfPQExpBuffer(&buf, " \"%s\" %s",
|
||||||
|
PQgetvalue(result, i, i_conname),
|
||||||
|
PQgetvalue(result, i, i_condef));
|
||||||
|
|
||||||
printTableAddFooter(&cont, buf.data);
|
printTableAddFooter(&cont, buf.data);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
PQclear(result);
|
|
||||||
}
|
}
|
||||||
|
PQclear(result);
|
||||||
|
|
||||||
|
/* print incoming foreign-key references */
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PQclear(result);
|
||||||
|
|
||||||
/* print any row-level policies */
|
/* print any row-level policies */
|
||||||
if (pset.sversion >= 90500)
|
if (pset.sversion >= 90500)
|
||||||
|
@ -2495,6 +2495,8 @@ typedef struct ATAlterConstraint
|
|||||||
{
|
{
|
||||||
NodeTag type;
|
NodeTag type;
|
||||||
char *conname; /* Constraint name */
|
char *conname; /* Constraint name */
|
||||||
|
bool alterEnforceability; /* changing enforceability properties? */
|
||||||
|
bool is_enforced; /* ENFORCED? */
|
||||||
bool alterDeferrability; /* changing deferrability properties? */
|
bool alterDeferrability; /* changing deferrability properties? */
|
||||||
bool deferrable; /* DEFERRABLE? */
|
bool deferrable; /* DEFERRABLE? */
|
||||||
bool initdeferred; /* INITIALLY DEFERRED? */
|
bool initdeferred; /* INITIALLY DEFERRED? */
|
||||||
|
@ -284,6 +284,9 @@ typedef struct ForeignKeyCacheInfo
|
|||||||
/* number of columns in the foreign key */
|
/* number of columns in the foreign key */
|
||||||
int nkeys;
|
int nkeys;
|
||||||
|
|
||||||
|
/* Is enforced ? */
|
||||||
|
bool conenforced;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* these arrays each have nkeys valid entries:
|
* these arrays each have nkeys valid entries:
|
||||||
*/
|
*/
|
||||||
|
@ -745,13 +745,9 @@ ERROR: misplaced NOT ENFORCED clause
|
|||||||
LINE 1: CREATE TABLE UNIQUE_NOTEN_TBL(i int UNIQUE NOT ENFORCED);
|
LINE 1: CREATE TABLE UNIQUE_NOTEN_TBL(i int UNIQUE NOT ENFORCED);
|
||||||
^
|
^
|
||||||
ALTER TABLE unique_tbl ALTER CONSTRAINT unique_tbl_i_key ENFORCED;
|
ALTER TABLE unique_tbl ALTER CONSTRAINT unique_tbl_i_key ENFORCED;
|
||||||
ERROR: FOREIGN KEY constraints cannot be marked ENFORCED
|
ERROR: cannot alter enforceability of constraint "unique_tbl_i_key" of relation "unique_tbl"
|
||||||
LINE 1: ...TABLE unique_tbl ALTER CONSTRAINT unique_tbl_i_key ENFORCED;
|
|
||||||
^
|
|
||||||
ALTER TABLE unique_tbl ALTER CONSTRAINT unique_tbl_i_key NOT ENFORCED;
|
ALTER TABLE unique_tbl ALTER CONSTRAINT unique_tbl_i_key NOT ENFORCED;
|
||||||
ERROR: FOREIGN KEY constraints cannot be marked NOT ENFORCED
|
ERROR: cannot alter enforceability of constraint "unique_tbl_i_key" of relation "unique_tbl"
|
||||||
LINE 1: ...ABLE unique_tbl ALTER CONSTRAINT unique_tbl_i_key NOT ENFORC...
|
|
||||||
^
|
|
||||||
DROP TABLE unique_tbl;
|
DROP TABLE unique_tbl;
|
||||||
--
|
--
|
||||||
-- EXCLUDE constraints
|
-- EXCLUDE constraints
|
||||||
|
@ -1,21 +1,49 @@
|
|||||||
--
|
--
|
||||||
-- FOREIGN KEY
|
-- FOREIGN KEY
|
||||||
--
|
--
|
||||||
|
-- NOT ENFORCED
|
||||||
|
--
|
||||||
|
-- First test, check and cascade
|
||||||
|
--
|
||||||
|
CREATE TABLE PKTABLE ( ptest1 int PRIMARY KEY, ptest2 text );
|
||||||
|
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 attempt 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
|
-- MATCH FULL
|
||||||
--
|
--
|
||||||
-- First test, check and cascade
|
-- 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
|
-- Insert test data into PKTABLE
|
||||||
INSERT INTO PKTABLE VALUES (1, 'Test1');
|
|
||||||
INSERT INTO PKTABLE VALUES (2, 'Test2');
|
|
||||||
INSERT INTO PKTABLE VALUES (3, 'Test3');
|
INSERT INTO PKTABLE VALUES (3, 'Test3');
|
||||||
INSERT INTO PKTABLE VALUES (4, 'Test4');
|
INSERT INTO PKTABLE VALUES (4, 'Test4');
|
||||||
INSERT INTO PKTABLE VALUES (5, 'Test5');
|
INSERT INTO PKTABLE VALUES (5, 'Test5');
|
||||||
-- Insert successful rows into FK TABLE
|
-- 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 (3, 4);
|
||||||
INSERT INTO FKTABLE VALUES (NULL, 1);
|
INSERT INTO FKTABLE VALUES (NULL, 1);
|
||||||
-- Insert a failed row into FK TABLE
|
-- 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;
|
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"
|
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.
|
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 FKTABLE;
|
||||||
DROP TABLE PKTABLE;
|
DROP TABLE PKTABLE;
|
||||||
-- MATCH SIMPLE
|
-- 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"
|
ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey"
|
||||||
DETAIL: Key (fk)=(20) is not present in table "pktable".
|
DETAIL: Key (fk)=(20) is not present in table "pktable".
|
||||||
COMMIT;
|
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
|
-- try additional syntax
|
||||||
ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE;
|
ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE;
|
||||||
-- illegal options
|
-- 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
|
ERROR: FOREIGN KEY constraints cannot be marked NOT VALID
|
||||||
LINE 1: ...ER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey 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
|
-- 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
|
-- be made to the same row. This was broken by subtransaction-related
|
||||||
-- changes in 8.0.
|
-- changes in 8.0.
|
||||||
@ -1586,10 +1666,14 @@ ALTER TABLE fk_partitioned_fk DROP COLUMN fdrop1;
|
|||||||
CREATE TABLE fk_partitioned_fk_1 (fdrop1 int, fdrop2 int, a int, fdrop3 int, b int);
|
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_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 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);
|
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 DROP COLUMN fdrop1, DROP COLUMN fdrop2;
|
||||||
|
ALTER TABLE fk_partitioned_fk_2 ADD CONSTRAINT fk_partitioned_fk_a_b_fkey 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 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)
|
CREATE TABLE fk_partitioned_fk_3 (fdrop1 int, fdrop2 int, fdrop3 int, fdrop4 int, b int, a int)
|
||||||
PARTITION BY HASH (a);
|
PARTITION BY HASH (a);
|
||||||
ALTER TABLE fk_partitioned_fk_3 DROP COLUMN fdrop1, DROP COLUMN fdrop2,
|
ALTER TABLE fk_partitioned_fk_3 DROP COLUMN fdrop1, DROP COLUMN fdrop2,
|
||||||
@ -1665,6 +1749,67 @@ Indexes:
|
|||||||
Referenced by:
|
Referenced by:
|
||||||
TABLE "fk_partitioned_fk" CONSTRAINT "fk_partitioned_fk_a_b_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b)
|
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_a_b_fkey | fk_partitioned_fk_3 | RI_ConstraintTrigger_c_N | 5
|
||||||
|
fk_partitioned_fk_a_b_fkey | fk_partitioned_fk_3 | RI_ConstraintTrigger_c_N | 17
|
||||||
|
fk_partitioned_fk_a_b_fkey | fk_partitioned_fk_3_0 | RI_ConstraintTrigger_c_N | 5
|
||||||
|
fk_partitioned_fk_a_b_fkey | fk_partitioned_fk_3_0 | RI_ConstraintTrigger_c_N | 17
|
||||||
|
fk_partitioned_fk_a_b_fkey | fk_partitioned_fk_3_1 | RI_ConstraintTrigger_c_N | 5
|
||||||
|
fk_partitioned_fk_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_a_b_fkey | fk_partitioned_fk_3 | RI_ConstraintTrigger_c_N | 5
|
||||||
|
fk_partitioned_fk_a_b_fkey | fk_partitioned_fk_3 | RI_ConstraintTrigger_c_N | 17
|
||||||
|
fk_partitioned_fk_a_b_fkey | fk_partitioned_fk_3_0 | RI_ConstraintTrigger_c_N | 5
|
||||||
|
fk_partitioned_fk_a_b_fkey | fk_partitioned_fk_3_0 | RI_ConstraintTrigger_c_N | 17
|
||||||
|
fk_partitioned_fk_a_b_fkey | fk_partitioned_fk_3_1 | RI_ConstraintTrigger_c_N | 5
|
||||||
|
fk_partitioned_fk_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;
|
ALTER TABLE fk_partitioned_fk DROP CONSTRAINT fk_partitioned_fk_a_b_fkey;
|
||||||
-- done.
|
-- done.
|
||||||
DROP TABLE fk_notpartitioned_pk, fk_partitioned_fk;
|
DROP TABLE fk_notpartitioned_pk, fk_partitioned_fk;
|
||||||
@ -1962,6 +2107,43 @@ Partition of: fk_partitioned_fk FOR VALUES IN (1500, 1502)
|
|||||||
Foreign-key constraints:
|
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
|
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,
|
||||||
|
CONSTRAINT fk_part_con FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk ON UPDATE CASCADE ON DELETE CASCADE NOT ENFORCED);
|
||||||
|
-- fail -- cannot merge constraints with different enforceability.
|
||||||
|
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES IN (1500,1502);
|
||||||
|
ERROR: constraint "fk_partitioned_fk_a_b_fkey" enforceability conflicts with constraint "fk_part_con" on relation "fk_partitioned_fk_2"
|
||||||
|
-- If the constraint is modified to match the enforceability of the parent, it will work.
|
||||||
|
BEGIN;
|
||||||
|
-- change child constraint
|
||||||
|
ALTER TABLE fk_partitioned_fk_2 ALTER CONSTRAINT fk_part_con ENFORCED;
|
||||||
|
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES IN (1500,1502);
|
||||||
|
\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
|
||||||
|
|
||||||
|
ROLLBACK;
|
||||||
|
BEGIN;
|
||||||
|
-- or change parent constraint
|
||||||
|
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);
|
||||||
|
\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;
|
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 (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_1 PARTITION OF fk_partitioned_fk_4 FOR VALUES FROM (1,1) TO (100,100);
|
||||||
|
@ -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 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_constraint6 check (f1 < 10) enforced;
|
||||||
NOTICE: merging constraint "inh_check_constraint6" with inherited definition
|
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 not-valid 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);
|
create table p1_c2(f1 int constraint inh_check_constraint4 check (f1 < 10)) inherits(p1);
|
||||||
NOTICE: merging column "f1" with inherited definition
|
NOTICE: merging column "f1" with inherited definition
|
||||||
NOTICE: merging constraint "inh_check_constraint4" 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
|
select conrelid::regclass::text as relname, conname, conislocal, coninhcount, conenforced, convalidated
|
||||||
from pg_constraint where conname like 'inh\_check\_constraint%'
|
from pg_constraint where conname like 'inh\_check\_constraint%'
|
||||||
order by 1, 2;
|
order by 1, 2;
|
||||||
relname | conname | conislocal | coninhcount | conenforced | convalidated
|
relname | conname | conislocal | coninhcount | conenforced | convalidated
|
||||||
---------+-----------------------+------------+-------------+-------------+--------------
|
---------+------------------------+------------+-------------+-------------+--------------
|
||||||
p1 | inh_check_constraint1 | t | 0 | t | t
|
p1 | inh_check_constraint1 | t | 0 | t | t
|
||||||
p1 | inh_check_constraint2 | t | 0 | t | t
|
p1 | inh_check_constraint10 | t | 0 | f | f
|
||||||
p1 | inh_check_constraint3 | t | 0 | f | f
|
p1 | inh_check_constraint2 | t | 0 | t | t
|
||||||
p1 | inh_check_constraint4 | t | 0 | f | f
|
p1 | inh_check_constraint3 | t | 0 | f | f
|
||||||
p1 | inh_check_constraint5 | t | 0 | f | f
|
p1 | inh_check_constraint4 | t | 0 | f | f
|
||||||
p1 | inh_check_constraint6 | t | 0 | f | f
|
p1 | inh_check_constraint5 | t | 0 | f | f
|
||||||
p1 | inh_check_constraint8 | t | 0 | t | t
|
p1 | inh_check_constraint6 | t | 0 | f | f
|
||||||
p1_c1 | inh_check_constraint1 | t | 1 | t | t
|
p1 | inh_check_constraint8 | t | 0 | t | t
|
||||||
p1_c1 | inh_check_constraint2 | t | 1 | t | t
|
p1 | inh_check_constraint9 | t | 0 | f | f
|
||||||
p1_c1 | inh_check_constraint3 | t | 1 | f | f
|
p1_c1 | inh_check_constraint1 | t | 1 | t | t
|
||||||
p1_c1 | inh_check_constraint4 | t | 1 | f | f
|
p1_c1 | inh_check_constraint10 | t | 1 | t | t
|
||||||
p1_c1 | inh_check_constraint5 | t | 1 | t | t
|
p1_c1 | inh_check_constraint2 | t | 1 | t | t
|
||||||
p1_c1 | inh_check_constraint6 | t | 1 | t | t
|
p1_c1 | inh_check_constraint3 | t | 1 | f | f
|
||||||
p1_c1 | inh_check_constraint7 | t | 0 | f | f
|
p1_c1 | inh_check_constraint4 | t | 1 | f | f
|
||||||
p1_c1 | inh_check_constraint8 | f | 1 | t | t
|
p1_c1 | inh_check_constraint5 | t | 1 | t | t
|
||||||
p1_c2 | inh_check_constraint1 | f | 1 | t | t
|
p1_c1 | inh_check_constraint6 | t | 1 | t | t
|
||||||
p1_c2 | inh_check_constraint2 | f | 1 | t | t
|
p1_c1 | inh_check_constraint7 | t | 0 | f | f
|
||||||
p1_c2 | inh_check_constraint3 | f | 1 | f | f
|
p1_c1 | inh_check_constraint8 | f | 1 | t | t
|
||||||
p1_c2 | inh_check_constraint4 | t | 1 | t | t
|
p1_c1 | inh_check_constraint9 | t | 1 | t | f
|
||||||
p1_c2 | inh_check_constraint5 | f | 1 | f | f
|
p1_c2 | inh_check_constraint1 | f | 1 | t | t
|
||||||
p1_c2 | inh_check_constraint6 | f | 1 | f | f
|
p1_c2 | inh_check_constraint10 | f | 1 | f | f
|
||||||
p1_c2 | inh_check_constraint8 | f | 1 | t | t
|
p1_c2 | inh_check_constraint2 | f | 1 | t | t
|
||||||
p1_c3 | inh_check_constraint1 | f | 2 | t | t
|
p1_c2 | inh_check_constraint3 | f | 1 | f | f
|
||||||
p1_c3 | inh_check_constraint2 | f | 2 | t | t
|
p1_c2 | inh_check_constraint4 | t | 1 | t | t
|
||||||
p1_c3 | inh_check_constraint3 | f | 2 | f | f
|
p1_c2 | inh_check_constraint5 | f | 1 | f | f
|
||||||
p1_c3 | inh_check_constraint4 | f | 2 | f | f
|
p1_c2 | inh_check_constraint6 | f | 1 | f | f
|
||||||
p1_c3 | inh_check_constraint5 | f | 2 | t | t
|
p1_c2 | inh_check_constraint8 | f | 1 | t | t
|
||||||
p1_c3 | inh_check_constraint6 | f | 2 | t | t
|
p1_c2 | inh_check_constraint9 | f | 1 | f | f
|
||||||
p1_c3 | inh_check_constraint7 | f | 1 | f | f
|
p1_c3 | inh_check_constraint1 | f | 2 | t | t
|
||||||
p1_c3 | inh_check_constraint8 | f | 2 | t | t
|
p1_c3 | inh_check_constraint10 | f | 2 | t | t
|
||||||
(30 rows)
|
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;
|
drop table p1 cascade;
|
||||||
NOTICE: drop cascades to 3 other objects
|
NOTICE: drop cascades to 3 other objects
|
||||||
|
@ -2,23 +2,46 @@
|
|||||||
-- FOREIGN KEY
|
-- FOREIGN KEY
|
||||||
--
|
--
|
||||||
|
|
||||||
-- MATCH FULL
|
-- NOT ENFORCED
|
||||||
--
|
--
|
||||||
-- First test, check and cascade
|
-- First test, check and cascade
|
||||||
--
|
--
|
||||||
CREATE TABLE PKTABLE ( ptest1 int PRIMARY KEY, ptest2 text );
|
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 attempt to
|
||||||
|
-- change it.
|
||||||
INSERT INTO PKTABLE VALUES (1, 'Test1');
|
INSERT INTO PKTABLE VALUES (1, 'Test1');
|
||||||
INSERT INTO PKTABLE VALUES (2, 'Test2');
|
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 (3, 'Test3');
|
||||||
INSERT INTO PKTABLE VALUES (4, 'Test4');
|
INSERT INTO PKTABLE VALUES (4, 'Test4');
|
||||||
INSERT INTO PKTABLE VALUES (5, 'Test5');
|
INSERT INTO PKTABLE VALUES (5, 'Test5');
|
||||||
|
|
||||||
-- Insert successful rows into FK TABLE
|
-- 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 (3, 4);
|
||||||
INSERT INTO FKTABLE VALUES (NULL, 1);
|
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;
|
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 FKTABLE;
|
||||||
DROP TABLE PKTABLE;
|
DROP TABLE PKTABLE;
|
||||||
|
|
||||||
@ -968,12 +1012,25 @@ INSERT INTO fktable VALUES (0, 20);
|
|||||||
|
|
||||||
COMMIT;
|
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
|
-- try additional syntax
|
||||||
ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE;
|
ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE;
|
||||||
-- illegal options
|
-- illegal options
|
||||||
ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE INITIALLY DEFERRED;
|
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 NO INHERIT;
|
||||||
ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT VALID;
|
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
|
-- 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
|
-- be made to the same row. This was broken by subtransaction-related
|
||||||
@ -1184,11 +1241,14 @@ ALTER TABLE fk_partitioned_fk DROP COLUMN fdrop1;
|
|||||||
CREATE TABLE fk_partitioned_fk_1 (fdrop1 int, fdrop2 int, a int, fdrop3 int, b int);
|
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_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 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);
|
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 DROP COLUMN fdrop1, DROP COLUMN fdrop2;
|
||||||
|
ALTER TABLE fk_partitioned_fk_2 ADD CONSTRAINT fk_partitioned_fk_a_b_fkey 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 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)
|
CREATE TABLE fk_partitioned_fk_3 (fdrop1 int, fdrop2 int, fdrop3 int, fdrop4 int, b int, a int)
|
||||||
PARTITION BY HASH (a);
|
PARTITION BY HASH (a);
|
||||||
ALTER TABLE fk_partitioned_fk_3 DROP COLUMN fdrop1, DROP COLUMN fdrop2,
|
ALTER TABLE fk_partitioned_fk_3 DROP COLUMN fdrop1, DROP COLUMN fdrop2,
|
||||||
@ -1234,6 +1294,32 @@ UPDATE fk_notpartitioned_pk SET b = 1502 WHERE a = 1500;
|
|||||||
UPDATE fk_notpartitioned_pk SET b = 2504 WHERE a = 2500;
|
UPDATE fk_notpartitioned_pk SET b = 2504 WHERE a = 2500;
|
||||||
-- check psql behavior
|
-- check psql behavior
|
||||||
\d fk_notpartitioned_pk
|
\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;
|
ALTER TABLE fk_partitioned_fk DROP CONSTRAINT fk_partitioned_fk_a_b_fkey;
|
||||||
-- done.
|
-- done.
|
||||||
DROP TABLE fk_notpartitioned_pk, fk_partitioned_fk;
|
DROP TABLE fk_notpartitioned_pk, fk_partitioned_fk;
|
||||||
@ -1441,6 +1527,25 @@ ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES IN
|
|||||||
\d fk_partitioned_fk_2
|
\d fk_partitioned_fk_2
|
||||||
DROP TABLE fk_partitioned_fk_2;
|
DROP TABLE fk_partitioned_fk_2;
|
||||||
|
|
||||||
|
CREATE TABLE fk_partitioned_fk_2 (b int, a int,
|
||||||
|
CONSTRAINT fk_part_con FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk ON UPDATE CASCADE ON DELETE CASCADE NOT ENFORCED);
|
||||||
|
-- fail -- cannot merge constraints with different enforceability.
|
||||||
|
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES IN (1500,1502);
|
||||||
|
-- If the constraint is modified to match the enforceability of the parent, it will work.
|
||||||
|
BEGIN;
|
||||||
|
-- change child constraint
|
||||||
|
ALTER TABLE fk_partitioned_fk_2 ALTER CONSTRAINT fk_part_con ENFORCED;
|
||||||
|
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES IN (1500,1502);
|
||||||
|
\d fk_partitioned_fk_2
|
||||||
|
ROLLBACK;
|
||||||
|
BEGIN;
|
||||||
|
-- or change parent constraint
|
||||||
|
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);
|
||||||
|
\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 (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_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);
|
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);
|
||||||
|
@ -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 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_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 not-valid 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);
|
create table p1_c2(f1 int constraint inh_check_constraint4 check (f1 < 10)) inherits(p1);
|
||||||
|
|
||||||
-- but reverse is not allowed
|
-- but reverse is not allowed
|
||||||
|
Loading…
x
Reference in New Issue
Block a user