diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2009-07-29 20:56:21 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2009-07-29 20:56:21 +0000 |
commit | 25d9bf2e3e66ee2e546c5c523d148ecab6ee1dcc (patch) | |
tree | b0dee0f1d6111fd6658d432ec30e5ddb88adc02f /src/backend/commands/constraint.c | |
parent | 850490579318ff52097eec92ce535357dd0c7a3a (diff) | |
download | postgresql-25d9bf2e3e66ee2e546c5c523d148ecab6ee1dcc.tar.gz postgresql-25d9bf2e3e66ee2e546c5c523d148ecab6ee1dcc.zip |
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
Diffstat (limited to 'src/backend/commands/constraint.c')
-rw-r--r-- | src/backend/commands/constraint.c | 166 |
1 files changed, 166 insertions, 0 deletions
diff --git a/src/backend/commands/constraint.c b/src/backend/commands/constraint.c new file mode 100644 index 00000000000..42d4d4e1f9c --- /dev/null +++ b/src/backend/commands/constraint.c @@ -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); +} |