From 62420ae7d6675453a48e408b62030b6f0780e878 Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas Date: Fri, 24 Apr 2015 09:33:23 +0300 Subject: [PATCH] Move functions related to index maintenance to separate source file. There is enough code here to deserve a file of their own, not be buried in the middle of execUtils.c. --- src/backend/executor/Makefile | 4 +- src/backend/executor/execIndexing.c | 541 ++++++++++++++++++++++++++++ src/backend/executor/execUtils.c | 534 +-------------------------- src/include/executor/executor.h | 16 +- 4 files changed, 555 insertions(+), 540 deletions(-) create mode 100644 src/backend/executor/execIndexing.c diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile index af707b0375..bc5d373d68 100644 --- a/src/backend/executor/Makefile +++ b/src/backend/executor/Makefile @@ -12,8 +12,8 @@ subdir = src/backend/executor top_builddir = ../../.. include $(top_builddir)/src/Makefile.global -OBJS = execAmi.o execCurrent.o execGrouping.o execJunk.o execMain.o \ - execProcnode.o execQual.o execScan.o execTuples.o \ +OBJS = execAmi.o execCurrent.o execGrouping.o execIndexing.o execJunk.o \ + execMain.o execProcnode.o execQual.o execScan.o execTuples.o \ execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \ nodeBitmapAnd.o nodeBitmapOr.o \ nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeCustom.o nodeHash.o \ diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c new file mode 100644 index 0000000000..de8fcdb941 --- /dev/null +++ b/src/backend/executor/execIndexing.c @@ -0,0 +1,541 @@ +/*------------------------------------------------------------------------- + * + * execIndexing.c + * executor support for maintaining indexes + * + * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/executor/execIndexing.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/relscan.h" +#include "catalog/index.h" +#include "executor/executor.h" +#include "nodes/nodeFuncs.h" +#include "storage/lmgr.h" +#include "utils/tqual.h" + +static bool index_recheck_constraint(Relation index, Oid *constr_procs, + Datum *existing_values, bool *existing_isnull, + Datum *new_values); + +/* ---------------------------------------------------------------- + * ExecOpenIndices + * + * Find the indices associated with a result relation, open them, + * and save information about them in the result ResultRelInfo. + * + * At entry, caller has already opened and locked + * resultRelInfo->ri_RelationDesc. + * ---------------------------------------------------------------- + */ +void +ExecOpenIndices(ResultRelInfo *resultRelInfo) +{ + Relation resultRelation = resultRelInfo->ri_RelationDesc; + List *indexoidlist; + ListCell *l; + int len, + i; + RelationPtr relationDescs; + IndexInfo **indexInfoArray; + + resultRelInfo->ri_NumIndices = 0; + + /* fast path if no indexes */ + if (!RelationGetForm(resultRelation)->relhasindex) + return; + + /* + * Get cached list of index OIDs + */ + indexoidlist = RelationGetIndexList(resultRelation); + len = list_length(indexoidlist); + if (len == 0) + return; + + /* + * allocate space for result arrays + */ + relationDescs = (RelationPtr) palloc(len * sizeof(Relation)); + indexInfoArray = (IndexInfo **) palloc(len * sizeof(IndexInfo *)); + + resultRelInfo->ri_NumIndices = len; + resultRelInfo->ri_IndexRelationDescs = relationDescs; + resultRelInfo->ri_IndexRelationInfo = indexInfoArray; + + /* + * For each index, open the index relation and save pg_index info. We + * acquire RowExclusiveLock, signifying we will update the index. + * + * Note: we do this even if the index is not IndexIsReady; it's not worth + * the trouble to optimize for the case where it isn't. + */ + i = 0; + foreach(l, indexoidlist) + { + Oid indexOid = lfirst_oid(l); + Relation indexDesc; + IndexInfo *ii; + + indexDesc = index_open(indexOid, RowExclusiveLock); + + /* extract index key information from the index's pg_index info */ + ii = BuildIndexInfo(indexDesc); + + relationDescs[i] = indexDesc; + indexInfoArray[i] = ii; + i++; + } + + list_free(indexoidlist); +} + +/* ---------------------------------------------------------------- + * ExecCloseIndices + * + * Close the index relations stored in resultRelInfo + * ---------------------------------------------------------------- + */ +void +ExecCloseIndices(ResultRelInfo *resultRelInfo) +{ + int i; + int numIndices; + RelationPtr indexDescs; + + numIndices = resultRelInfo->ri_NumIndices; + indexDescs = resultRelInfo->ri_IndexRelationDescs; + + for (i = 0; i < numIndices; i++) + { + if (indexDescs[i] == NULL) + continue; /* shouldn't happen? */ + + /* Drop lock acquired by ExecOpenIndices */ + index_close(indexDescs[i], RowExclusiveLock); + } + + /* + * XXX should free indexInfo array here too? Currently we assume that + * such stuff will be cleaned up automatically in FreeExecutorState. + */ +} + +/* ---------------------------------------------------------------- + * ExecInsertIndexTuples + * + * This routine takes care of inserting index tuples + * into all the relations indexing the result relation + * when a heap tuple is inserted into the result relation. + * Much of this code should be moved into the genam + * stuff as it only exists here because the genam stuff + * doesn't provide the functionality needed by the + * executor.. -cim 9/27/89 + * + * 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. + * We can't defend against that here for lack of info. + * Should we change the API to make it safer? + * ---------------------------------------------------------------- + */ +List * +ExecInsertIndexTuples(TupleTableSlot *slot, + ItemPointer tupleid, + EState *estate) +{ + List *result = NIL; + ResultRelInfo *resultRelInfo; + int i; + int numIndices; + RelationPtr relationDescs; + Relation heapRelation; + IndexInfo **indexInfoArray; + ExprContext *econtext; + Datum values[INDEX_MAX_KEYS]; + bool isnull[INDEX_MAX_KEYS]; + + /* + * Get information from the result relation info structure. + */ + resultRelInfo = estate->es_result_relation_info; + numIndices = resultRelInfo->ri_NumIndices; + relationDescs = resultRelInfo->ri_IndexRelationDescs; + indexInfoArray = resultRelInfo->ri_IndexRelationInfo; + heapRelation = resultRelInfo->ri_RelationDesc; + + /* + * We will use the EState's per-tuple context for evaluating predicates + * and index expressions (creating it if it's not already there). + */ + econtext = GetPerTupleExprContext(estate); + + /* Arrange for econtext's scan tuple to be the tuple under test */ + econtext->ecxt_scantuple = slot; + + /* + * for each index, form and insert the index tuple + */ + for (i = 0; i < numIndices; i++) + { + Relation indexRelation = relationDescs[i]; + IndexInfo *indexInfo; + IndexUniqueCheck checkUnique; + bool satisfiesConstraint; + + if (indexRelation == NULL) + continue; + + indexInfo = indexInfoArray[i]; + + /* If the index is marked as read-only, ignore it */ + if (!indexInfo->ii_ReadyForInserts) + continue; + + /* Check for partial index */ + if (indexInfo->ii_Predicate != NIL) + { + List *predicate; + + /* + * If predicate state not set up yet, create it (in the estate's + * per-query context) + */ + predicate = indexInfo->ii_PredicateState; + if (predicate == NIL) + { + predicate = (List *) + ExecPrepareExpr((Expr *) indexInfo->ii_Predicate, + estate); + indexInfo->ii_PredicateState = predicate; + } + + /* Skip this index-update if the predicate isn't satisfied */ + if (!ExecQual(predicate, econtext, false)) + continue; + } + + /* + * FormIndexDatum fills in its values and isnull parameters with the + * appropriate values for the column(s) of the index. + */ + FormIndexDatum(indexInfo, + slot, + estate, + values, + isnull); + + /* + * 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. + * + * For a deferrable unique index, we tell the index AM to just detect + * possible non-uniqueness, and we add the index OID to the result + * list if further checking is needed. + */ + if (!indexRelation->rd_index->indisunique) + checkUnique = UNIQUE_CHECK_NO; + else if (indexRelation->rd_index->indimmediate) + checkUnique = UNIQUE_CHECK_YES; + else + checkUnique = UNIQUE_CHECK_PARTIAL; + + satisfiesConstraint = + index_insert(indexRelation, /* index relation */ + values, /* array of index Datums */ + isnull, /* null flags */ + tupleid, /* tid of heap tuple */ + heapRelation, /* heap relation */ + checkUnique); /* type of uniqueness check to do */ + + /* + * 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 or exclusion + * constraint, so make a note of the index so that we can re-check + * it later. + */ + result = lappend_oid(result, RelationGetRelid(indexRelation)); + } + } + + return result; +} + +/* + * 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; + Oid *index_collations = index->rd_indcollation; + 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; + ExprContext *econtext; + TupleTableSlot *existing_slot; + TupleTableSlot *save_scantuple; + + /* + * 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++) + { + ScanKeyEntryInitialize(&scankeys[i], + 0, + i + 1, + constr_strats[i], + InvalidOid, + index_collations[i], + constr_procs[i], + values[i]); + } + + /* + * Need a TupleTableSlot to put existing tuples in. + * + * To use FormIndexDatum, we have to make the econtext's scantuple point + * to this slot. Be sure to save and restore caller's value for + * scantuple. + */ + existing_slot = MakeSingleTupleTableSlot(RelationGetDescr(heap)); + + econtext = GetPerTupleExprContext(estate); + save_scantuple = econtext->ecxt_scantuple; + econtext->ecxt_scantuple = existing_slot; + + /* + * 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, 0); + index_rescan(index_scan, scankeys, index_natts, NULL, 0); + + while ((tup = index_getnext(index_scan, + ForwardScanDirection)) != NULL) + { + TransactionId xwait; + ItemPointerData ctid_wait; + 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)) + { + ctid_wait = tup->t_data->t_ctid; + index_endscan(index_scan); + XactLockTableWait(xwait, heap, &ctid_wait, + XLTW_RecheckExclusionConstr); + 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)), + error_new && error_existing ? + errdetail("Key %s conflicts with key %s.", + error_new, error_existing) : + errdetail("Key conflicts exist."), + errtableconstraint(heap, + RelationGetRelationName(index)))); + else + ereport(ERROR, + (errcode(ERRCODE_EXCLUSION_VIOLATION), + errmsg("conflicting key value violates exclusion constraint \"%s\"", + RelationGetRelationName(index)), + error_new && error_existing ? + errdetail("Key %s conflicts with existing key %s.", + error_new, error_existing) : + errdetail("Key conflicts with existing key."), + errtableconstraint(heap, + RelationGetRelationName(index)))); + } + + index_endscan(index_scan); + + /* + * Ordinarily, at this point the search should have found the originally + * inserted tuple, unless we exited the loop early because of conflict. + * However, it is possible to define exclusion constraints for which that + * wouldn't be true --- for instance, if the operator is <>. So we no + * longer complain if found_self is still false. + */ + + econtext->ecxt_scantuple = save_scantuple; + + 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(OidFunctionCall2Coll(constr_procs[i], + index->rd_indcollation[i], + existing_values[i], + new_values[i]))) + return false; + } + + return true; +} diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index 0736d2a310..88ba16bc6d 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -28,10 +28,6 @@ * ExecOpenScanRelation Common code for scan node init routines. * ExecCloseScanRelation * - * ExecOpenIndices \ - * ExecCloseIndices | referenced by InitPlan, EndPlan, - * ExecInsertIndexTuples / ExecInsert, ExecUpdate - * * RegisterExprContextCallback Register function shutdown callback * UnregisterExprContextCallback Deregister function shutdown callback * @@ -44,19 +40,14 @@ #include "access/relscan.h" #include "access/transam.h" -#include "catalog/index.h" -#include "executor/execdebug.h" +#include "executor/executor.h" #include "nodes/nodeFuncs.h" #include "parser/parsetree.h" -#include "storage/lmgr.h" #include "utils/memutils.h" -#include "utils/tqual.h" +#include "utils/rel.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); @@ -870,527 +861,6 @@ ExecCloseScanRelation(Relation scanrel) heap_close(scanrel, NoLock); } - -/* ---------------------------------------------------------------- - * ExecInsertIndexTuples support - * ---------------------------------------------------------------- - */ - -/* ---------------------------------------------------------------- - * ExecOpenIndices - * - * Find the indices associated with a result relation, open them, - * and save information about them in the result ResultRelInfo. - * - * At entry, caller has already opened and locked - * resultRelInfo->ri_RelationDesc. - * ---------------------------------------------------------------- - */ -void -ExecOpenIndices(ResultRelInfo *resultRelInfo) -{ - Relation resultRelation = resultRelInfo->ri_RelationDesc; - List *indexoidlist; - ListCell *l; - int len, - i; - RelationPtr relationDescs; - IndexInfo **indexInfoArray; - - resultRelInfo->ri_NumIndices = 0; - - /* fast path if no indexes */ - if (!RelationGetForm(resultRelation)->relhasindex) - return; - - /* - * Get cached list of index OIDs - */ - indexoidlist = RelationGetIndexList(resultRelation); - len = list_length(indexoidlist); - if (len == 0) - return; - - /* - * allocate space for result arrays - */ - relationDescs = (RelationPtr) palloc(len * sizeof(Relation)); - indexInfoArray = (IndexInfo **) palloc(len * sizeof(IndexInfo *)); - - resultRelInfo->ri_NumIndices = len; - resultRelInfo->ri_IndexRelationDescs = relationDescs; - resultRelInfo->ri_IndexRelationInfo = indexInfoArray; - - /* - * For each index, open the index relation and save pg_index info. We - * acquire RowExclusiveLock, signifying we will update the index. - * - * Note: we do this even if the index is not IndexIsReady; it's not worth - * the trouble to optimize for the case where it isn't. - */ - i = 0; - foreach(l, indexoidlist) - { - Oid indexOid = lfirst_oid(l); - Relation indexDesc; - IndexInfo *ii; - - indexDesc = index_open(indexOid, RowExclusiveLock); - - /* extract index key information from the index's pg_index info */ - ii = BuildIndexInfo(indexDesc); - - relationDescs[i] = indexDesc; - indexInfoArray[i] = ii; - i++; - } - - list_free(indexoidlist); -} - -/* ---------------------------------------------------------------- - * ExecCloseIndices - * - * Close the index relations stored in resultRelInfo - * ---------------------------------------------------------------- - */ -void -ExecCloseIndices(ResultRelInfo *resultRelInfo) -{ - int i; - int numIndices; - RelationPtr indexDescs; - - numIndices = resultRelInfo->ri_NumIndices; - indexDescs = resultRelInfo->ri_IndexRelationDescs; - - for (i = 0; i < numIndices; i++) - { - if (indexDescs[i] == NULL) - continue; /* shouldn't happen? */ - - /* Drop lock acquired by ExecOpenIndices */ - index_close(indexDescs[i], RowExclusiveLock); - } - - /* - * XXX should free indexInfo array here too? Currently we assume that - * such stuff will be cleaned up automatically in FreeExecutorState. - */ -} - -/* ---------------------------------------------------------------- - * ExecInsertIndexTuples - * - * This routine takes care of inserting index tuples - * into all the relations indexing the result relation - * when a heap tuple is inserted into the result relation. - * Much of this code should be moved into the genam - * stuff as it only exists here because the genam stuff - * doesn't provide the functionality needed by the - * executor.. -cim 9/27/89 - * - * 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. - * We can't defend against that here for lack of info. - * Should we change the API to make it safer? - * ---------------------------------------------------------------- - */ -List * -ExecInsertIndexTuples(TupleTableSlot *slot, - ItemPointer tupleid, - EState *estate) -{ - List *result = NIL; - ResultRelInfo *resultRelInfo; - int i; - int numIndices; - RelationPtr relationDescs; - Relation heapRelation; - IndexInfo **indexInfoArray; - ExprContext *econtext; - Datum values[INDEX_MAX_KEYS]; - bool isnull[INDEX_MAX_KEYS]; - - /* - * Get information from the result relation info structure. - */ - resultRelInfo = estate->es_result_relation_info; - numIndices = resultRelInfo->ri_NumIndices; - relationDescs = resultRelInfo->ri_IndexRelationDescs; - indexInfoArray = resultRelInfo->ri_IndexRelationInfo; - heapRelation = resultRelInfo->ri_RelationDesc; - - /* - * We will use the EState's per-tuple context for evaluating predicates - * and index expressions (creating it if it's not already there). - */ - econtext = GetPerTupleExprContext(estate); - - /* Arrange for econtext's scan tuple to be the tuple under test */ - econtext->ecxt_scantuple = slot; - - /* - * for each index, form and insert the index tuple - */ - for (i = 0; i < numIndices; i++) - { - Relation indexRelation = relationDescs[i]; - IndexInfo *indexInfo; - IndexUniqueCheck checkUnique; - bool satisfiesConstraint; - - if (indexRelation == NULL) - continue; - - indexInfo = indexInfoArray[i]; - - /* If the index is marked as read-only, ignore it */ - if (!indexInfo->ii_ReadyForInserts) - continue; - - /* Check for partial index */ - if (indexInfo->ii_Predicate != NIL) - { - List *predicate; - - /* - * If predicate state not set up yet, create it (in the estate's - * per-query context) - */ - predicate = indexInfo->ii_PredicateState; - if (predicate == NIL) - { - predicate = (List *) - ExecPrepareExpr((Expr *) indexInfo->ii_Predicate, - estate); - indexInfo->ii_PredicateState = predicate; - } - - /* Skip this index-update if the predicate isn't satisfied */ - if (!ExecQual(predicate, econtext, false)) - continue; - } - - /* - * FormIndexDatum fills in its values and isnull parameters with the - * appropriate values for the column(s) of the index. - */ - FormIndexDatum(indexInfo, - slot, - estate, - values, - isnull); - - /* - * 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. - * - * For a deferrable unique index, we tell the index AM to just detect - * possible non-uniqueness, and we add the index OID to the result - * list if further checking is needed. - */ - if (!indexRelation->rd_index->indisunique) - checkUnique = UNIQUE_CHECK_NO; - else if (indexRelation->rd_index->indimmediate) - checkUnique = UNIQUE_CHECK_YES; - else - checkUnique = UNIQUE_CHECK_PARTIAL; - - satisfiesConstraint = - index_insert(indexRelation, /* index relation */ - values, /* array of index Datums */ - isnull, /* null flags */ - tupleid, /* tid of heap tuple */ - heapRelation, /* heap relation */ - checkUnique); /* type of uniqueness check to do */ - - /* - * 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 or exclusion - * constraint, so make a note of the index so that we can re-check - * it later. - */ - result = lappend_oid(result, RelationGetRelid(indexRelation)); - } - } - - return result; -} - -/* - * 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; - Oid *index_collations = index->rd_indcollation; - 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; - ExprContext *econtext; - TupleTableSlot *existing_slot; - TupleTableSlot *save_scantuple; - - /* - * 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++) - { - ScanKeyEntryInitialize(&scankeys[i], - 0, - i + 1, - constr_strats[i], - InvalidOid, - index_collations[i], - constr_procs[i], - values[i]); - } - - /* - * Need a TupleTableSlot to put existing tuples in. - * - * To use FormIndexDatum, we have to make the econtext's scantuple point - * to this slot. Be sure to save and restore caller's value for - * scantuple. - */ - existing_slot = MakeSingleTupleTableSlot(RelationGetDescr(heap)); - - econtext = GetPerTupleExprContext(estate); - save_scantuple = econtext->ecxt_scantuple; - econtext->ecxt_scantuple = existing_slot; - - /* - * 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, 0); - index_rescan(index_scan, scankeys, index_natts, NULL, 0); - - while ((tup = index_getnext(index_scan, - ForwardScanDirection)) != NULL) - { - TransactionId xwait; - ItemPointerData ctid_wait; - 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)) - { - ctid_wait = tup->t_data->t_ctid; - index_endscan(index_scan); - XactLockTableWait(xwait, heap, &ctid_wait, - XLTW_RecheckExclusionConstr); - 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)), - error_new && error_existing ? - errdetail("Key %s conflicts with key %s.", - error_new, error_existing) : - errdetail("Key conflicts exist."), - errtableconstraint(heap, - RelationGetRelationName(index)))); - else - ereport(ERROR, - (errcode(ERRCODE_EXCLUSION_VIOLATION), - errmsg("conflicting key value violates exclusion constraint \"%s\"", - RelationGetRelationName(index)), - error_new && error_existing ? - errdetail("Key %s conflicts with existing key %s.", - error_new, error_existing) : - errdetail("Key conflicts with existing key."), - errtableconstraint(heap, - RelationGetRelationName(index)))); - } - - index_endscan(index_scan); - - /* - * Ordinarily, at this point the search should have found the originally - * inserted tuple, unless we exited the loop early because of conflict. - * However, it is possible to define exclusion constraints for which that - * wouldn't be true --- for instance, if the operator is <>. So we no - * longer complain if found_self is still false. - */ - - econtext->ecxt_scantuple = save_scantuple; - - 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(OidFunctionCall2Coll(constr_procs[i], - index->rd_indcollation[i], - existing_values[i], - new_values[i]))) - return false; - } - - return true; -} - /* * UpdateChangedParamSet * Add changed parameters to a plan node's chgParam set diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index c1e7477956..c2bf9a2940 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -351,6 +351,16 @@ extern bool ExecRelationIsTargetRelation(EState *estate, Index scanrelid); extern Relation ExecOpenScanRelation(EState *estate, Index scanrelid, int eflags); extern void ExecCloseScanRelation(Relation scanrel); +extern void RegisterExprContextCallback(ExprContext *econtext, + ExprContextCallbackFunction function, + Datum arg); +extern void UnregisterExprContextCallback(ExprContext *econtext, + ExprContextCallbackFunction function, + Datum arg); + +/* + * prototypes from functions in execIndexing.c + */ extern void ExecOpenIndices(ResultRelInfo *resultRelInfo); extern void ExecCloseIndices(ResultRelInfo *resultRelInfo); extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid, @@ -362,11 +372,5 @@ extern bool check_exclusion_constraint(Relation heap, Relation index, EState *estate, bool newIndex, bool errorOK); -extern void RegisterExprContextCallback(ExprContext *econtext, - ExprContextCallbackFunction function, - Datum arg); -extern void UnregisterExprContextCallback(ExprContext *econtext, - ExprContextCallbackFunction function, - Datum arg); #endif /* EXECUTOR_H */ -- 2.40.0