mirror of
https://github.com/postgres/postgres.git
synced 2025-06-14 18:42:34 +03:00
Support deferrable uniqueness constraints.
The current implementation fires an AFTER ROW trigger for each tuple that looks like it might be non-unique according to the index contents at the time of insertion. This works well as long as there aren't many conflicts, but won't scale to massive unique-key reassignments. Improving that case is a TODO item. Dean Rasheed
This commit is contained in:
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.249 2009/07/28 02:56:30 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.250 2009/07/29 20:56:18 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -63,7 +63,8 @@ static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
|
||||
Instrumentation *instr,
|
||||
MemoryContext per_tuple_context);
|
||||
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
|
||||
bool row_trigger, HeapTuple oldtup, HeapTuple newtup);
|
||||
bool row_trigger, HeapTuple oldtup, HeapTuple newtup,
|
||||
List *recheckIndexes);
|
||||
|
||||
|
||||
/*
|
||||
@ -77,6 +78,10 @@ static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
|
||||
* indexOid, if nonzero, is the OID of an index associated with the constraint.
|
||||
* We do nothing with this except store it into pg_trigger.tgconstrindid.
|
||||
*
|
||||
* prefix is NULL for user-created triggers. For internally generated
|
||||
* constraint triggers, it is a prefix string to use in building the
|
||||
* trigger name. (stmt->trigname is the constraint name in such cases.)
|
||||
*
|
||||
* If checkPermissions is true we require ACL_TRIGGER permissions on the
|
||||
* relation. If not, the caller already checked permissions. (This is
|
||||
* currently redundant with constraintOid being zero, but it's clearer to
|
||||
@ -87,7 +92,7 @@ static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
|
||||
*/
|
||||
Oid
|
||||
CreateTrigger(CreateTrigStmt *stmt,
|
||||
Oid constraintOid, Oid indexOid,
|
||||
Oid constraintOid, Oid indexOid, const char *prefix,
|
||||
bool checkPermissions)
|
||||
{
|
||||
int16 tgtype;
|
||||
@ -216,20 +221,20 @@ CreateTrigger(CreateTrigStmt *stmt,
|
||||
trigoid = GetNewOid(tgrel);
|
||||
|
||||
/*
|
||||
* If trigger is for an RI constraint, the passed-in name is the
|
||||
* constraint name; save that and build a unique trigger name to avoid
|
||||
* collisions with user-selected trigger names.
|
||||
* If trigger is for a constraint, stmt->trigname is the constraint
|
||||
* name; save that and build a unique trigger name based on the supplied
|
||||
* prefix, to avoid collisions with user-selected trigger names.
|
||||
*/
|
||||
if (OidIsValid(constraintOid))
|
||||
if (prefix != NULL)
|
||||
{
|
||||
snprintf(constrtrigname, sizeof(constrtrigname),
|
||||
"RI_ConstraintTrigger_%u", trigoid);
|
||||
"%s_%u", prefix, trigoid);
|
||||
trigname = constrtrigname;
|
||||
constrname = stmt->trigname;
|
||||
}
|
||||
else if (stmt->isconstraint)
|
||||
{
|
||||
/* constraint trigger: trigger name is also constraint name */
|
||||
/* user constraint trigger: trigger name is also constraint name */
|
||||
trigname = stmt->trigname;
|
||||
constrname = stmt->trigname;
|
||||
}
|
||||
@ -1650,7 +1655,7 @@ ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo)
|
||||
|
||||
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
|
||||
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
|
||||
false, NULL, NULL);
|
||||
false, NULL, NULL, NIL);
|
||||
}
|
||||
|
||||
HeapTuple
|
||||
@ -1706,13 +1711,13 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
|
||||
|
||||
void
|
||||
ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
|
||||
HeapTuple trigtuple)
|
||||
HeapTuple trigtuple, List *recheckIndexes)
|
||||
{
|
||||
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
|
||||
|
||||
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
|
||||
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
|
||||
true, NULL, trigtuple);
|
||||
true, NULL, trigtuple, recheckIndexes);
|
||||
}
|
||||
|
||||
void
|
||||
@ -1781,7 +1786,7 @@ ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
|
||||
|
||||
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
|
||||
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
|
||||
false, NULL, NULL);
|
||||
false, NULL, NULL, NIL);
|
||||
}
|
||||
|
||||
bool
|
||||
@ -1858,7 +1863,7 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
|
||||
tupleid, NULL);
|
||||
|
||||
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
|
||||
true, trigtuple, NULL);
|
||||
true, trigtuple, NULL, NIL);
|
||||
heap_freetuple(trigtuple);
|
||||
}
|
||||
}
|
||||
@ -1929,7 +1934,7 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
|
||||
|
||||
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
|
||||
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
|
||||
false, NULL, NULL);
|
||||
false, NULL, NULL, NIL);
|
||||
}
|
||||
|
||||
HeapTuple
|
||||
@ -1999,7 +2004,8 @@ ExecBRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
|
||||
|
||||
void
|
||||
ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
|
||||
ItemPointer tupleid, HeapTuple newtuple)
|
||||
ItemPointer tupleid, HeapTuple newtuple,
|
||||
List *recheckIndexes)
|
||||
{
|
||||
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
|
||||
|
||||
@ -2009,7 +2015,7 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
|
||||
tupleid, NULL);
|
||||
|
||||
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
|
||||
true, trigtuple, newtuple);
|
||||
true, trigtuple, newtuple, recheckIndexes);
|
||||
heap_freetuple(trigtuple);
|
||||
}
|
||||
}
|
||||
@ -2080,7 +2086,7 @@ ExecASTruncateTriggers(EState *estate, ResultRelInfo *relinfo)
|
||||
|
||||
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
|
||||
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
|
||||
false, NULL, NULL);
|
||||
false, NULL, NULL, NIL);
|
||||
}
|
||||
|
||||
|
||||
@ -3793,15 +3799,18 @@ AfterTriggerPendingOnRel(Oid relid)
|
||||
/* ----------
|
||||
* AfterTriggerSaveEvent()
|
||||
*
|
||||
* Called by ExecA[RS]...Triggers() to add the event to the queue.
|
||||
* Called by ExecA[RS]...Triggers() to queue up the triggers that should
|
||||
* be fired for an event.
|
||||
*
|
||||
* NOTE: should be called only if we've determined that an event must
|
||||
* be added to the queue.
|
||||
* NOTE: this is called whenever there are any triggers associated with
|
||||
* the event (even if they are disabled). This function decides which
|
||||
* triggers actually need to be queued.
|
||||
* ----------
|
||||
*/
|
||||
static void
|
||||
AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
|
||||
HeapTuple oldtup, HeapTuple newtup)
|
||||
HeapTuple oldtup, HeapTuple newtup,
|
||||
List *recheckIndexes)
|
||||
{
|
||||
Relation rel = relinfo->ri_RelationDesc;
|
||||
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
|
||||
@ -3961,6 +3970,17 @@ AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If the trigger is a deferred unique constraint check trigger,
|
||||
* only queue it if the unique constraint was potentially violated,
|
||||
* which we know from index insertion time.
|
||||
*/
|
||||
if (trigger->tgfoid == F_UNIQUE_KEY_RECHECK)
|
||||
{
|
||||
if (!list_member_oid(recheckIndexes, trigger->tgconstrindid))
|
||||
continue; /* Uniqueness definitely not violated */
|
||||
}
|
||||
|
||||
/*
|
||||
* Fill in event structure and add it to the current query's queue.
|
||||
*/
|
||||
|
Reference in New Issue
Block a user