diff options
Diffstat (limited to 'src/backend/executor/execUtils.c')
-rw-r--r-- | src/backend/executor/execUtils.c | 264 |
1 files changed, 255 insertions, 9 deletions
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index e1aa8ce8ac9..ac993465a51 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.166 2009/11/20 20:38:10 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.167 2009/12/07 05:22:22 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -44,16 +44,22 @@ #include "access/genam.h" #include "access/heapam.h" +#include "access/relscan.h" +#include "access/transam.h" #include "catalog/index.h" #include "executor/execdebug.h" #include "nodes/nodeFuncs.h" #include "parser/parsetree.h" +#include "storage/lmgr.h" #include "utils/memutils.h" #include "utils/relcache.h" #include "utils/tqual.h" static bool get_last_attnums(Node *node, ProjectionInfo *projInfo); +static bool index_recheck_constraint(Relation index, Oid *constr_procs, + Datum *existing_values, bool *existing_isnull, + Datum *new_values); static void ShutdownExprContext(ExprContext *econtext, bool isCommit); @@ -959,8 +965,8 @@ ExecCloseIndices(ResultRelInfo *resultRelInfo) * doesn't provide the functionality needed by the * executor.. -cim 9/27/89 * - * This returns a list of OIDs for any unique indexes - * whose constraint check was deferred and which had + * This returns a list of index OIDs for any unique or exclusion + * constraints that are deferred and that had * potential (unconfirmed) conflicts. * * CAUTION: this must not be called for a HOT update. @@ -1011,7 +1017,7 @@ ExecInsertIndexTuples(TupleTableSlot *slot, Relation indexRelation = relationDescs[i]; IndexInfo *indexInfo; IndexUniqueCheck checkUnique; - bool isUnique; + bool satisfiesConstraint; if (indexRelation == NULL) continue; @@ -1056,7 +1062,7 @@ ExecInsertIndexTuples(TupleTableSlot *slot, isnull); /* - * The index AM does the rest, including uniqueness checking. + * The index AM does the actual insertion, plus uniqueness checking. * * For an immediate-mode unique index, we just tell the index AM to * throw error if not unique. @@ -1076,7 +1082,7 @@ ExecInsertIndexTuples(TupleTableSlot *slot, else checkUnique = UNIQUE_CHECK_PARTIAL; - isUnique = + satisfiesConstraint = index_insert(indexRelation, /* index relation */ values, /* array of index Datums */ isnull, /* null flags */ @@ -1084,11 +1090,36 @@ ExecInsertIndexTuples(TupleTableSlot *slot, heapRelation, /* heap relation */ checkUnique); /* type of uniqueness check to do */ - if (checkUnique == UNIQUE_CHECK_PARTIAL && !isUnique) + /* + * If the index has an associated exclusion constraint, check that. + * This is simpler than the process for uniqueness checks since we + * always insert first and then check. If the constraint is deferred, + * we check now anyway, but don't throw error on violation; instead + * we'll queue a recheck event. + * + * An index for an exclusion constraint can't also be UNIQUE (not an + * essential property, we just don't allow it in the grammar), so no + * need to preserve the prior state of satisfiesConstraint. + */ + if (indexInfo->ii_ExclusionOps != NULL) + { + bool errorOK = !indexRelation->rd_index->indimmediate; + + satisfiesConstraint = + check_exclusion_constraint(heapRelation, + indexRelation, indexInfo, + tupleid, values, isnull, + estate, false, errorOK); + } + + if ((checkUnique == UNIQUE_CHECK_PARTIAL || + indexInfo->ii_ExclusionOps != NULL) && + !satisfiesConstraint) { /* - * The tuple potentially violates the uniqueness constraint, - * so make a note of the index so that we can re-check it later. + * The tuple potentially violates the uniqueness or exclusion + * constraint, so make a note of the index so that we can re-check + * it later. */ result = lappend_oid(result, RelationGetRelid(indexRelation)); } @@ -1098,6 +1129,221 @@ ExecInsertIndexTuples(TupleTableSlot *slot, } /* + * Check for violation of an exclusion constraint + * + * heap: the table containing the new tuple + * index: the index supporting the exclusion constraint + * indexInfo: info about the index, including the exclusion properties + * tupleid: heap TID of the new tuple we have just inserted + * values, isnull: the *index* column values computed for the new tuple + * estate: an EState we can do evaluation in + * newIndex: if true, we are trying to build a new index (this affects + * only the wording of error messages) + * errorOK: if true, don't throw error for violation + * + * Returns true if OK, false if actual or potential violation + * + * When errorOK is true, we report violation without waiting to see if any + * concurrent transaction has committed or not; so the violation is only + * potential, and the caller must recheck sometime later. This behavior + * is convenient for deferred exclusion checks; we need not bother queuing + * a deferred event if there is definitely no conflict at insertion time. + * + * When errorOK is false, we'll throw error on violation, so a false result + * is impossible. + */ +bool +check_exclusion_constraint(Relation heap, Relation index, IndexInfo *indexInfo, + ItemPointer tupleid, Datum *values, bool *isnull, + EState *estate, bool newIndex, bool errorOK) +{ + Oid *constr_procs = indexInfo->ii_ExclusionProcs; + uint16 *constr_strats = indexInfo->ii_ExclusionStrats; + int index_natts = index->rd_index->indnatts; + IndexScanDesc index_scan; + HeapTuple tup; + ScanKeyData scankeys[INDEX_MAX_KEYS]; + SnapshotData DirtySnapshot; + int i; + bool conflict; + bool found_self; + TupleTableSlot *existing_slot; + + /* + * If any of the input values are NULL, the constraint check is assumed + * to pass (i.e., we assume the operators are strict). + */ + for (i = 0; i < index_natts; i++) + { + if (isnull[i]) + return true; + } + + /* + * Search the tuples that are in the index for any violations, + * including tuples that aren't visible yet. + */ + InitDirtySnapshot(DirtySnapshot); + + for (i = 0; i < index_natts; i++) + { + ScanKeyInit(&scankeys[i], + i + 1, + constr_strats[i], + constr_procs[i], + values[i]); + } + + /* Need a TupleTableSlot to put existing tuples in */ + existing_slot = MakeSingleTupleTableSlot(RelationGetDescr(heap)); + + /* + * May have to restart scan from this point if a potential + * conflict is found. + */ +retry: + conflict = false; + found_self = false; + index_scan = index_beginscan(heap, index, &DirtySnapshot, + index_natts, scankeys); + + while ((tup = index_getnext(index_scan, + ForwardScanDirection)) != NULL) + { + TransactionId xwait; + Datum existing_values[INDEX_MAX_KEYS]; + bool existing_isnull[INDEX_MAX_KEYS]; + char *error_new; + char *error_existing; + + /* + * Ignore the entry for the tuple we're trying to check. + */ + if (ItemPointerEquals(tupleid, &tup->t_self)) + { + if (found_self) /* should not happen */ + elog(ERROR, "found self tuple multiple times in index \"%s\"", + RelationGetRelationName(index)); + found_self = true; + continue; + } + + /* + * Extract the index column values and isnull flags from the existing + * tuple. + */ + ExecStoreTuple(tup, existing_slot, InvalidBuffer, false); + FormIndexDatum(indexInfo, existing_slot, estate, + existing_values, existing_isnull); + + /* If lossy indexscan, must recheck the condition */ + if (index_scan->xs_recheck) + { + if (!index_recheck_constraint(index, + constr_procs, + existing_values, + existing_isnull, + values)) + continue; /* tuple doesn't actually match, so no conflict */ + } + + /* + * At this point we have either a conflict or a potential conflict. + * If we're not supposed to raise error, just return the fact of the + * potential conflict without waiting to see if it's real. + */ + if (errorOK) + { + conflict = true; + break; + } + + /* + * If an in-progress transaction is affecting the visibility of this + * tuple, we need to wait for it to complete and then recheck. For + * simplicity we do rechecking by just restarting the whole scan --- + * this case probably doesn't happen often enough to be worth trying + * harder, and anyway we don't want to hold any index internal locks + * while waiting. + */ + xwait = TransactionIdIsValid(DirtySnapshot.xmin) ? + DirtySnapshot.xmin : DirtySnapshot.xmax; + + if (TransactionIdIsValid(xwait)) + { + index_endscan(index_scan); + XactLockTableWait(xwait); + goto retry; + } + + /* + * We have a definite conflict. Report it. + */ + error_new = BuildIndexValueDescription(index, values, isnull); + error_existing = BuildIndexValueDescription(index, existing_values, + existing_isnull); + if (newIndex) + ereport(ERROR, + (errcode(ERRCODE_EXCLUSION_VIOLATION), + errmsg("could not create exclusion constraint \"%s\"", + RelationGetRelationName(index)), + errdetail("Key %s conflicts with key %s.", + error_new, error_existing))); + else + ereport(ERROR, + (errcode(ERRCODE_EXCLUSION_VIOLATION), + errmsg("conflicting key value violates exclusion constraint \"%s\"", + RelationGetRelationName(index)), + errdetail("Key %s conflicts with existing key %s.", + error_new, error_existing))); + } + + index_endscan(index_scan); + + /* + * We should have found our tuple in the index, unless we exited the + * loop early because of conflict. Complain if not. + */ + if (!found_self && !conflict) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("failed to re-find tuple within index \"%s\"", + RelationGetRelationName(index)), + errhint("This may be because of a non-immutable index expression."))); + + ExecDropSingleTupleTableSlot(existing_slot); + + return !conflict; +} + +/* + * Check existing tuple's index values to see if it really matches the + * exclusion condition against the new_values. Returns true if conflict. + */ +static bool +index_recheck_constraint(Relation index, Oid *constr_procs, + Datum *existing_values, bool *existing_isnull, + Datum *new_values) +{ + int index_natts = index->rd_index->indnatts; + int i; + + for (i = 0; i < index_natts; i++) + { + /* Assume the exclusion operators are strict */ + if (existing_isnull[i]) + return false; + + if (!DatumGetBool(OidFunctionCall2(constr_procs[i], + existing_values[i], + new_values[i]))) + return false; + } + + return true; +} + +/* * UpdateChangedParamSet * Add changed parameters to a plan node's chgParam set */ |