]> granicus.if.org Git - postgresql/blob - src/backend/commands/constraint.c
Add exclusion constraints, which generalize the concept of uniqueness to
[postgresql] / src / backend / commands / constraint.c
1 /*-------------------------------------------------------------------------
2  *
3  * constraint.c
4  *        PostgreSQL CONSTRAINT support code.
5  *
6  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  * IDENTIFICATION
10  *        $PostgreSQL: pgsql/src/backend/commands/constraint.c,v 1.2 2009/12/07 05:22:21 tgl Exp $
11  *
12  *-------------------------------------------------------------------------
13  */
14 #include "postgres.h"
15
16 #include "catalog/index.h"
17 #include "commands/trigger.h"
18 #include "executor/executor.h"
19 #include "utils/builtins.h"
20 #include "utils/tqual.h"
21
22
23 /*
24  * unique_key_recheck - trigger function to do a deferred uniqueness check.
25  *
26  * This now also does deferred exclusion-constraint checks, so the name is
27  * somewhat historical.
28  *
29  * This is invoked as an AFTER ROW trigger for both INSERT and UPDATE,
30  * for any rows recorded as potentially violating a deferrable unique
31  * or exclusion constraint.
32  *
33  * This may be an end-of-statement check, a commit-time check, or a
34  * check triggered by a SET CONSTRAINTS command.
35  */
36 Datum
37 unique_key_recheck(PG_FUNCTION_ARGS)
38 {
39         TriggerData *trigdata = (TriggerData *) fcinfo->context;
40         const char *funcname = "unique_key_recheck";
41         HeapTuple       new_row;
42         ItemPointerData tmptid;
43         Relation        indexRel;
44         IndexInfo  *indexInfo;
45         EState     *estate;
46         ExprContext *econtext;
47         TupleTableSlot *slot;
48         Datum           values[INDEX_MAX_KEYS];
49         bool            isnull[INDEX_MAX_KEYS];
50
51         /*
52          * Make sure this is being called as an AFTER ROW trigger.  Note:
53          * translatable error strings are shared with ri_triggers.c, so
54          * resist the temptation to fold the function name into them.
55          */
56         if (!CALLED_AS_TRIGGER(fcinfo))
57                 ereport(ERROR,
58                                 (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
59                                  errmsg("function \"%s\" was not called by trigger manager",
60                                                 funcname)));
61
62         if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
63                 !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
64                 ereport(ERROR,
65                                 (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
66                                  errmsg("function \"%s\" must be fired AFTER ROW",
67                                                 funcname)));
68
69         /*
70          * Get the new data that was inserted/updated.
71          */
72         if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
73                 new_row = trigdata->tg_trigtuple;
74         else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
75                 new_row = trigdata->tg_newtuple;
76         else
77         {
78                 ereport(ERROR,
79                                 (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
80                                  errmsg("function \"%s\" must be fired for INSERT or UPDATE",
81                                                 funcname)));
82                 new_row = NULL;                 /* keep compiler quiet */
83         }
84
85         /*
86          * If the new_row is now dead (ie, inserted and then deleted within our
87          * transaction), we can skip the check.  However, we have to be careful,
88          * because this trigger gets queued only in response to index insertions;
89          * which means it does not get queued for HOT updates.  The row we are
90          * called for might now be dead, but have a live HOT child, in which case
91          * we still need to make the check.  Therefore we have to use
92          * heap_hot_search, not just HeapTupleSatisfiesVisibility as is done in
93          * the comparable test in RI_FKey_check.
94          *
95          * This might look like just an optimization, because the index AM will
96          * make this identical test before throwing an error.  But it's actually
97          * needed for correctness, because the index AM will also throw an error
98          * if it doesn't find the index entry for the row.  If the row's dead then
99          * it's possible the index entry has also been marked dead, and even
100          * removed.
101          */
102         tmptid = new_row->t_self;
103         if (!heap_hot_search(&tmptid, trigdata->tg_relation, SnapshotSelf, NULL))
104         {
105                 /*
106                  * All rows in the HOT chain are dead, so skip the check.
107                  */
108                 return PointerGetDatum(NULL);
109         }
110
111         /*
112          * Open the index, acquiring a RowExclusiveLock, just as if we were
113          * going to update it.  (This protects against possible changes of the
114          * index schema, not against concurrent updates.)
115          */
116         indexRel = index_open(trigdata->tg_trigger->tgconstrindid,
117                                                   RowExclusiveLock);
118         indexInfo = BuildIndexInfo(indexRel);
119
120         /*
121          * The heap tuple must be put into a slot for FormIndexDatum.
122          */
123         slot = MakeSingleTupleTableSlot(RelationGetDescr(trigdata->tg_relation));
124
125         ExecStoreTuple(new_row, slot, InvalidBuffer, false);
126
127         /*
128          * Typically the index won't have expressions, but if it does we need
129          * an EState to evaluate them.  We need it for exclusion constraints
130          * too, even if they are just on simple columns.
131          */
132         if (indexInfo->ii_Expressions != NIL ||
133                 indexInfo->ii_ExclusionOps != NULL)
134         {
135                 estate = CreateExecutorState();
136                 econtext = GetPerTupleExprContext(estate);
137                 econtext->ecxt_scantuple = slot;
138         }
139         else
140                 estate = NULL;
141
142         /*
143          * Form the index values and isnull flags for the index entry that
144          * we need to check.
145          *
146          * Note: if the index uses functions that are not as immutable as they
147          * are supposed to be, this could produce an index tuple different from
148          * the original.  The index AM can catch such errors by verifying that
149          * it finds a matching index entry with the tuple's TID.  For exclusion
150          * constraints we check this in check_exclusion_constraint().
151          */
152         FormIndexDatum(indexInfo, slot, estate, values, isnull);
153
154         /*
155          * Now do the appropriate check.
156          */
157         if (indexInfo->ii_ExclusionOps == NULL)
158         {
159                 /*
160                  * Note: this is not a real insert; it is a check that the index entry
161                  * that has already been inserted is unique.
162                  */
163                 index_insert(indexRel, values, isnull, &(new_row->t_self),
164                                          trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
165         }
166         else
167         {
168                 /*
169                  * For exclusion constraints we just do the normal check, but now
170                  * it's okay to throw error.
171                  */
172                 check_exclusion_constraint(trigdata->tg_relation, indexRel, indexInfo,
173                                                                    &(new_row->t_self), values, isnull,
174                                                                    estate, false, false);
175         }
176
177         /*
178          * If that worked, then this index entry is unique or non-excluded,
179          * and we are done.
180          */
181         if (estate != NULL)
182                 FreeExecutorState(estate);
183
184         ExecDropSingleTupleTableSlot(slot);
185
186         index_close(indexRel, RowExclusiveLock);
187
188         return PointerGetDatum(NULL);
189 }