|
|
|
|
@ -338,9 +338,6 @@ static void validateCheckConstraint(Relation rel, HeapTuple constrtup); |
|
|
|
|
static void validateForeignKeyConstraint(char *conname, |
|
|
|
|
Relation rel, Relation pkrel, |
|
|
|
|
Oid pkindOid, Oid constraintOid); |
|
|
|
|
static void createForeignKeyTriggers(Relation rel, Oid refRelOid, |
|
|
|
|
Constraint *fkconstraint, |
|
|
|
|
Oid constraintOid, Oid indexOid); |
|
|
|
|
static void ATController(AlterTableStmt *parsetree, |
|
|
|
|
Relation rel, List *cmds, bool recurse, LOCKMODE lockmode); |
|
|
|
|
static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, |
|
|
|
|
@ -411,8 +408,10 @@ static ObjectAddress ATAddCheckConstraint(List **wqueue, |
|
|
|
|
Constraint *constr, |
|
|
|
|
bool recurse, bool recursing, bool is_readd, |
|
|
|
|
LOCKMODE lockmode); |
|
|
|
|
static ObjectAddress ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, |
|
|
|
|
Constraint *fkconstraint, LOCKMODE lockmode); |
|
|
|
|
static ObjectAddress ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, |
|
|
|
|
Relation rel, Constraint *fkconstraint, Oid parentConstr, |
|
|
|
|
bool recurse, bool recursing, |
|
|
|
|
LOCKMODE lockmode); |
|
|
|
|
static void ATExecDropConstraint(Relation rel, const char *constrName, |
|
|
|
|
DropBehavior behavior, |
|
|
|
|
bool recurse, bool recursing, |
|
|
|
|
@ -505,6 +504,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx, |
|
|
|
|
* relkind: relkind to assign to the new relation |
|
|
|
|
* ownerId: if not InvalidOid, use this as the new relation's owner. |
|
|
|
|
* typaddress: if not null, it's set to the pg_type entry's address. |
|
|
|
|
* queryString: for error reporting |
|
|
|
|
* |
|
|
|
|
* Note that permissions checks are done against current user regardless of |
|
|
|
|
* ownerId. A nonzero ownerId is used when someone is creating a relation |
|
|
|
|
@ -908,8 +908,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If we're creating a partition, create now all the indexes and triggers |
|
|
|
|
* defined in the parent. |
|
|
|
|
* If we're creating a partition, create now all the indexes, triggers, |
|
|
|
|
* FKs defined in the parent. |
|
|
|
|
* |
|
|
|
|
* We can't do it earlier, because DefineIndex wants to know the partition |
|
|
|
|
* key which we just stored. |
|
|
|
|
@ -961,6 +961,12 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, |
|
|
|
|
if (parent->trigdesc != NULL) |
|
|
|
|
CloneRowTriggersToPartition(parent, rel); |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* And foreign keys too. Note that because we're freshly creating the |
|
|
|
|
* table, there is no need to verify these new constraints. |
|
|
|
|
*/ |
|
|
|
|
CloneForeignKeyConstraints(parentId, relationId, NULL); |
|
|
|
|
|
|
|
|
|
heap_close(parent, NoLock); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -7025,7 +7031,9 @@ ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, |
|
|
|
|
RelationGetNamespace(rel), |
|
|
|
|
NIL); |
|
|
|
|
|
|
|
|
|
address = ATAddForeignKeyConstraint(tab, rel, newConstraint, |
|
|
|
|
address = ATAddForeignKeyConstraint(wqueue, tab, rel, |
|
|
|
|
newConstraint, InvalidOid, |
|
|
|
|
recurse, false, |
|
|
|
|
lockmode); |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
@ -7180,8 +7188,9 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, |
|
|
|
|
* We do permissions checks here, however. |
|
|
|
|
*/ |
|
|
|
|
static ObjectAddress |
|
|
|
|
ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, |
|
|
|
|
Constraint *fkconstraint, LOCKMODE lockmode) |
|
|
|
|
ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, |
|
|
|
|
Constraint *fkconstraint, Oid parentConstr, |
|
|
|
|
bool recurse, bool recursing, LOCKMODE lockmode) |
|
|
|
|
{ |
|
|
|
|
Relation pkrel; |
|
|
|
|
int16 pkattnum[INDEX_MAX_KEYS]; |
|
|
|
|
@ -7220,6 +7229,21 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, |
|
|
|
|
errmsg("cannot reference partitioned table \"%s\"", |
|
|
|
|
RelationGetRelationName(pkrel)))); |
|
|
|
|
|
|
|
|
|
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) |
|
|
|
|
{ |
|
|
|
|
if (!recurse) |
|
|
|
|
ereport(ERROR, |
|
|
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE), |
|
|
|
|
errmsg("foreign key referencing partitioned table \"%s\" must not be ONLY", |
|
|
|
|
RelationGetRelationName(pkrel)))); |
|
|
|
|
if (fkconstraint->skip_validation && !fkconstraint->initially_valid) |
|
|
|
|
ereport(ERROR, |
|
|
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE), |
|
|
|
|
errmsg("cannot add NOT VALID foreign key to relation \"%s\"", |
|
|
|
|
RelationGetRelationName(pkrel)), |
|
|
|
|
errdetail("This feature is not yet supported on partitioned tables."))); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (pkrel->rd_rel->relkind != RELKIND_RELATION) |
|
|
|
|
ereport(ERROR, |
|
|
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE), |
|
|
|
|
@ -7527,7 +7551,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, |
|
|
|
|
fkconstraint->deferrable, |
|
|
|
|
fkconstraint->initdeferred, |
|
|
|
|
fkconstraint->initially_valid, |
|
|
|
|
InvalidOid, /* no parent constraint */ |
|
|
|
|
parentConstr, |
|
|
|
|
RelationGetRelid(rel), |
|
|
|
|
fkattnum, |
|
|
|
|
numfks, |
|
|
|
|
@ -7553,10 +7577,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, |
|
|
|
|
ObjectAddressSet(address, ConstraintRelationId, constrOid); |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Create the triggers that will enforce the constraint. |
|
|
|
|
* Create the triggers that will enforce the constraint. We only want |
|
|
|
|
* the action triggers to appear for the parent partitioned relation, |
|
|
|
|
* even though the constraints also exist below. |
|
|
|
|
*/ |
|
|
|
|
createForeignKeyTriggers(rel, RelationGetRelid(pkrel), fkconstraint, |
|
|
|
|
constrOid, indexOid); |
|
|
|
|
constrOid, indexOid, !recursing); |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Tell Phase 3 to check that the constraint is satisfied by existing |
|
|
|
|
@ -7580,6 +7606,40 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, |
|
|
|
|
tab->constraints = lappend(tab->constraints, newcon); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* When called on a partitioned table, recurse to create the constraint on |
|
|
|
|
* the partitions also. |
|
|
|
|
*/ |
|
|
|
|
if (recurse && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) |
|
|
|
|
{ |
|
|
|
|
PartitionDesc partdesc; |
|
|
|
|
|
|
|
|
|
partdesc = RelationGetPartitionDesc(rel); |
|
|
|
|
|
|
|
|
|
for (i = 0; i < partdesc->nparts; i++) |
|
|
|
|
{ |
|
|
|
|
Oid partitionId = partdesc->oids[i]; |
|
|
|
|
Relation partition = heap_open(partitionId, lockmode); |
|
|
|
|
AlteredTableInfo *childtab; |
|
|
|
|
ObjectAddress childAddr; |
|
|
|
|
|
|
|
|
|
CheckTableNotInUse(partition, "ALTER TABLE"); |
|
|
|
|
|
|
|
|
|
/* Find or create work queue entry for this table */ |
|
|
|
|
childtab = ATGetQueueEntry(wqueue, partition); |
|
|
|
|
|
|
|
|
|
childAddr = |
|
|
|
|
ATAddForeignKeyConstraint(wqueue, childtab, partition, |
|
|
|
|
fkconstraint, constrOid, |
|
|
|
|
recurse, true, lockmode); |
|
|
|
|
|
|
|
|
|
/* Record this constraint as dependent on the parent one */ |
|
|
|
|
recordDependencyOn(&childAddr, &address, DEPENDENCY_INTERNAL_AUTO); |
|
|
|
|
|
|
|
|
|
heap_close(partition, NoLock); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Close pk table, but keep lock until we've committed. |
|
|
|
|
*/ |
|
|
|
|
@ -7842,8 +7902,8 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse, |
|
|
|
|
heap_close(refrel, NoLock); |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Foreign keys do not inherit, so we purposely ignore the |
|
|
|
|
* recursion bit here |
|
|
|
|
* We disallow creating invalid foreign keys to or from |
|
|
|
|
* partitioned tables, so ignoring the recursion bit is okay. |
|
|
|
|
*/ |
|
|
|
|
} |
|
|
|
|
else if (con->contype == CONSTRAINT_CHECK) |
|
|
|
|
@ -8489,23 +8549,16 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Create the triggers that implement an FK constraint. |
|
|
|
|
* |
|
|
|
|
* NB: if you change any trigger properties here, see also |
|
|
|
|
* ATExecAlterConstraint. |
|
|
|
|
* createForeignKeyActionTriggers |
|
|
|
|
* Create the referenced-side "action" triggers that implement a foreign |
|
|
|
|
* key. |
|
|
|
|
*/ |
|
|
|
|
static void |
|
|
|
|
createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint, |
|
|
|
|
Oid constraintOid, Oid indexOid) |
|
|
|
|
createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint, |
|
|
|
|
Oid constraintOid, Oid indexOid) |
|
|
|
|
{ |
|
|
|
|
Oid myRelOid; |
|
|
|
|
CreateTrigStmt *fk_trigger; |
|
|
|
|
|
|
|
|
|
myRelOid = RelationGetRelid(rel); |
|
|
|
|
|
|
|
|
|
/* Make changes-so-far visible */ |
|
|
|
|
CommandCounterIncrement(); |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON |
|
|
|
|
* DELETE action on the referenced table. |
|
|
|
|
@ -8555,7 +8608,8 @@ createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint, |
|
|
|
|
} |
|
|
|
|
fk_trigger->args = NIL; |
|
|
|
|
|
|
|
|
|
(void) CreateTrigger(fk_trigger, NULL, refRelOid, myRelOid, constraintOid, |
|
|
|
|
(void) CreateTrigger(fk_trigger, NULL, refRelOid, RelationGetRelid(rel), |
|
|
|
|
constraintOid, |
|
|
|
|
indexOid, InvalidOid, InvalidOid, NULL, true, false); |
|
|
|
|
|
|
|
|
|
/* Make changes-so-far visible */ |
|
|
|
|
@ -8610,22 +8664,58 @@ createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint, |
|
|
|
|
} |
|
|
|
|
fk_trigger->args = NIL; |
|
|
|
|
|
|
|
|
|
(void) CreateTrigger(fk_trigger, NULL, refRelOid, myRelOid, constraintOid, |
|
|
|
|
(void) CreateTrigger(fk_trigger, NULL, refRelOid, RelationGetRelid(rel), |
|
|
|
|
constraintOid, |
|
|
|
|
indexOid, InvalidOid, InvalidOid, NULL, true, false); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Make changes-so-far visible */ |
|
|
|
|
CommandCounterIncrement(); |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Build and execute CREATE CONSTRAINT TRIGGER statements for the CHECK |
|
|
|
|
* action for both INSERTs and UPDATEs on the referencing table. |
|
|
|
|
*/ |
|
|
|
|
/*
|
|
|
|
|
* createForeignKeyCheckTriggers |
|
|
|
|
* Create the referencing-side "check" triggers that implement a foreign |
|
|
|
|
* key. |
|
|
|
|
*/ |
|
|
|
|
static void |
|
|
|
|
createForeignKeyCheckTriggers(Oid myRelOid, Oid refRelOid, |
|
|
|
|
Constraint *fkconstraint, Oid constraintOid, |
|
|
|
|
Oid indexOid) |
|
|
|
|
{ |
|
|
|
|
CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint, constraintOid, |
|
|
|
|
indexOid, true); |
|
|
|
|
CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint, constraintOid, |
|
|
|
|
indexOid, false); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Create the triggers that implement an FK constraint. |
|
|
|
|
* |
|
|
|
|
* NB: if you change any trigger properties here, see also |
|
|
|
|
* ATExecAlterConstraint. |
|
|
|
|
*/ |
|
|
|
|
void |
|
|
|
|
createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint, |
|
|
|
|
Oid constraintOid, Oid indexOid, bool create_action) |
|
|
|
|
{ |
|
|
|
|
/*
|
|
|
|
|
* For the referenced side, create action triggers, if requested. (If the |
|
|
|
|
* referencing side is partitioned, there is still only one trigger, which |
|
|
|
|
* runs on the referenced side and points to the top of the referencing |
|
|
|
|
* hierarchy.) |
|
|
|
|
*/ |
|
|
|
|
if (create_action) |
|
|
|
|
createForeignKeyActionTriggers(rel, refRelOid, fkconstraint, constraintOid, |
|
|
|
|
indexOid); |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* For the referencing side, create the check triggers. We only need these |
|
|
|
|
* on the partitions. |
|
|
|
|
*/ |
|
|
|
|
if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) |
|
|
|
|
createForeignKeyCheckTriggers(RelationGetRelid(rel), refRelOid, |
|
|
|
|
fkconstraint, constraintOid, indexOid); |
|
|
|
|
|
|
|
|
|
CommandCounterIncrement(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ALTER TABLE DROP CONSTRAINT |
|
|
|
|
* |
|
|
|
|
@ -13889,6 +13979,8 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd) |
|
|
|
|
bool found_whole_row; |
|
|
|
|
Oid defaultPartOid; |
|
|
|
|
List *partBoundConstraint; |
|
|
|
|
List *cloned; |
|
|
|
|
ListCell *l; |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* We must lock the default partition, because attaching a new partition |
|
|
|
|
@ -14071,6 +14163,35 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd) |
|
|
|
|
/* and triggers */ |
|
|
|
|
CloneRowTriggersToPartition(rel, attachrel); |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Clone foreign key constraints, and setup for Phase 3 to verify them. |
|
|
|
|
*/ |
|
|
|
|
cloned = NIL; |
|
|
|
|
CloneForeignKeyConstraints(RelationGetRelid(rel), |
|
|
|
|
RelationGetRelid(attachrel), &cloned); |
|
|
|
|
foreach(l, cloned) |
|
|
|
|
{ |
|
|
|
|
ClonedConstraint *cloned = lfirst(l); |
|
|
|
|
NewConstraint *newcon; |
|
|
|
|
Relation clonedrel; |
|
|
|
|
AlteredTableInfo *parttab; |
|
|
|
|
|
|
|
|
|
clonedrel = relation_open(cloned->relid, NoLock); |
|
|
|
|
parttab = ATGetQueueEntry(wqueue, clonedrel); |
|
|
|
|
|
|
|
|
|
newcon = (NewConstraint *) palloc0(sizeof(NewConstraint)); |
|
|
|
|
newcon->name = cloned->constraint->conname; |
|
|
|
|
newcon->contype = CONSTR_FOREIGN; |
|
|
|
|
newcon->refrelid = cloned->refrelid; |
|
|
|
|
newcon->refindid = cloned->conindid; |
|
|
|
|
newcon->conid = cloned->conid; |
|
|
|
|
newcon->qual = (Node *) cloned->constraint; |
|
|
|
|
|
|
|
|
|
parttab->constraints = lappend(parttab->constraints, newcon); |
|
|
|
|
|
|
|
|
|
relation_close(clonedrel, NoLock); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Generate partition constraint from the partition bound specification. |
|
|
|
|
* If the parent itself is a partition, make sure to include its |
|
|
|
|
|