mirror of https://github.com/postgres/postgres
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 RasheedREL8_5_ALPHA1_BRANCH
parent
8504905793
commit
25d9bf2e3e
@ -0,0 +1,166 @@ |
|||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
* |
||||||
|
* constraint.c |
||||||
|
* PostgreSQL CONSTRAINT support code. |
||||||
|
* |
||||||
|
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group |
||||||
|
* Portions Copyright (c) 1994, Regents of the University of California |
||||||
|
* |
||||||
|
* IDENTIFICATION |
||||||
|
* $PostgreSQL: pgsql/src/backend/commands/constraint.c,v 1.1 2009/07/29 20:56:18 tgl Exp $ |
||||||
|
* |
||||||
|
*------------------------------------------------------------------------- |
||||||
|
*/ |
||||||
|
#include "postgres.h" |
||||||
|
|
||||||
|
#include "catalog/index.h" |
||||||
|
#include "commands/trigger.h" |
||||||
|
#include "executor/executor.h" |
||||||
|
#include "utils/builtins.h" |
||||||
|
#include "utils/tqual.h" |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* unique_key_recheck - trigger function to do a deferred uniqueness check. |
||||||
|
* |
||||||
|
* This is invoked as an AFTER ROW trigger for both INSERT and UPDATE, |
||||||
|
* for any rows recorded as potentially violating a deferrable unique |
||||||
|
* constraint. |
||||||
|
* |
||||||
|
* This may be an end-of-statement check, a commit-time check, or a |
||||||
|
* check triggered by a SET CONSTRAINTS command. |
||||||
|
*/ |
||||||
|
Datum |
||||||
|
unique_key_recheck(PG_FUNCTION_ARGS) |
||||||
|
{ |
||||||
|
TriggerData *trigdata = (TriggerData *) fcinfo->context; |
||||||
|
const char *funcname = "unique_key_recheck"; |
||||||
|
HeapTuple new_row; |
||||||
|
ItemPointerData tmptid; |
||||||
|
Relation indexRel; |
||||||
|
IndexInfo *indexInfo; |
||||||
|
EState *estate; |
||||||
|
ExprContext *econtext; |
||||||
|
TupleTableSlot *slot; |
||||||
|
Datum values[INDEX_MAX_KEYS]; |
||||||
|
bool isnull[INDEX_MAX_KEYS]; |
||||||
|
|
||||||
|
/*
|
||||||
|
* Make sure this is being called as an AFTER ROW trigger. Note: |
||||||
|
* translatable error strings are shared with ri_triggers.c, so |
||||||
|
* resist the temptation to fold the function name into them. |
||||||
|
*/ |
||||||
|
if (!CALLED_AS_TRIGGER(fcinfo)) |
||||||
|
ereport(ERROR, |
||||||
|
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), |
||||||
|
errmsg("function \"%s\" was not called by trigger manager", |
||||||
|
funcname))); |
||||||
|
|
||||||
|
if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) || |
||||||
|
!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) |
||||||
|
ereport(ERROR, |
||||||
|
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), |
||||||
|
errmsg("function \"%s\" must be fired AFTER ROW", |
||||||
|
funcname))); |
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the new data that was inserted/updated. |
||||||
|
*/ |
||||||
|
if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) |
||||||
|
new_row = trigdata->tg_trigtuple; |
||||||
|
else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) |
||||||
|
new_row = trigdata->tg_newtuple; |
||||||
|
else |
||||||
|
{ |
||||||
|
ereport(ERROR, |
||||||
|
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), |
||||||
|
errmsg("function \"%s\" must be fired for INSERT or UPDATE", |
||||||
|
funcname))); |
||||||
|
new_row = NULL; /* keep compiler quiet */ |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* If the new_row is now dead (ie, inserted and then deleted within our |
||||||
|
* transaction), we can skip the check. However, we have to be careful, |
||||||
|
* because this trigger gets queued only in response to index insertions; |
||||||
|
* which means it does not get queued for HOT updates. The row we are |
||||||
|
* called for might now be dead, but have a live HOT child, in which case |
||||||
|
* we still need to make the uniqueness check. Therefore we have to use |
||||||
|
* heap_hot_search, not just HeapTupleSatisfiesVisibility as is done in |
||||||
|
* the comparable test in RI_FKey_check. |
||||||
|
* |
||||||
|
* This might look like just an optimization, because the index AM will |
||||||
|
* make this identical test before throwing an error. But it's actually |
||||||
|
* needed for correctness, because the index AM will also throw an error |
||||||
|
* if it doesn't find the index entry for the row. If the row's dead then |
||||||
|
* it's possible the index entry has also been marked dead, and even |
||||||
|
* removed. |
||||||
|
*/ |
||||||
|
tmptid = new_row->t_self; |
||||||
|
if (!heap_hot_search(&tmptid, trigdata->tg_relation, SnapshotSelf, NULL)) |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* All rows in the HOT chain are dead, so skip the check. |
||||||
|
*/ |
||||||
|
return PointerGetDatum(NULL); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Open the index, acquiring a RowExclusiveLock, just as if we were |
||||||
|
* going to update it. (This protects against possible changes of the |
||||||
|
* index schema, not against concurrent updates.) |
||||||
|
*/ |
||||||
|
indexRel = index_open(trigdata->tg_trigger->tgconstrindid, |
||||||
|
RowExclusiveLock); |
||||||
|
indexInfo = BuildIndexInfo(indexRel); |
||||||
|
|
||||||
|
/*
|
||||||
|
* The heap tuple must be put into a slot for FormIndexDatum. |
||||||
|
*/ |
||||||
|
slot = MakeSingleTupleTableSlot(RelationGetDescr(trigdata->tg_relation)); |
||||||
|
|
||||||
|
ExecStoreTuple(new_row, slot, InvalidBuffer, false); |
||||||
|
|
||||||
|
/*
|
||||||
|
* Typically the index won't have expressions, but if it does we need |
||||||
|
* an EState to evaluate them. |
||||||
|
*/ |
||||||
|
if (indexInfo->ii_Expressions != NIL) |
||||||
|
{ |
||||||
|
estate = CreateExecutorState(); |
||||||
|
econtext = GetPerTupleExprContext(estate); |
||||||
|
econtext->ecxt_scantuple = slot; |
||||||
|
} |
||||||
|
else |
||||||
|
estate = NULL; |
||||||
|
|
||||||
|
/*
|
||||||
|
* Form the index values and isnull flags for the index entry that |
||||||
|
* we need to check. |
||||||
|
* |
||||||
|
* Note: if the index uses functions that are not as immutable as they |
||||||
|
* are supposed to be, this could produce an index tuple different from |
||||||
|
* the original. The index AM can catch such errors by verifying that |
||||||
|
* it finds a matching index entry with the tuple's TID. |
||||||
|
*/ |
||||||
|
FormIndexDatum(indexInfo, slot, estate, values, isnull); |
||||||
|
|
||||||
|
/*
|
||||||
|
* Now do the uniqueness check. This is not a real insert; it is a |
||||||
|
* check that the index entry that has already been inserted is unique. |
||||||
|
*/ |
||||||
|
index_insert(indexRel, values, isnull, &(new_row->t_self), |
||||||
|
trigdata->tg_relation, UNIQUE_CHECK_EXISTING); |
||||||
|
|
||||||
|
/*
|
||||||
|
* If that worked, then this index entry is unique, and we are done. |
||||||
|
*/ |
||||||
|
if (estate != NULL) |
||||||
|
FreeExecutorState(estate); |
||||||
|
|
||||||
|
ExecDropSingleTupleTableSlot(slot); |
||||||
|
|
||||||
|
index_close(indexRel, RowExclusiveLock); |
||||||
|
|
||||||
|
return PointerGetDatum(NULL); |
||||||
|
} |
Loading…
Reference in new issue