blinsert(Relation index, Datum *values, bool *isnull,
ItemPointer ht_ctid, Relation heapRel,
IndexUniqueCheck checkUnique,
+ bool indexUnchanged,
IndexInfo *indexInfo)
{
BloomState blstate;
extern bool blinsert(Relation index, Datum *values, bool *isnull,
ItemPointer ht_ctid, Relation heapRel,
IndexUniqueCheck checkUnique,
+ bool indexUnchanged,
struct IndexInfo *indexInfo);
extern IndexScanDesc blbeginscan(Relation r, int nkeys, int norderbys);
extern int64 blgetbitmap(IndexScanDesc scan, TIDBitmap *tbm);
ItemPointer heap_tid,
Relation heapRelation,
IndexUniqueCheck checkUnique,
+ bool indexUnchanged,
IndexInfo *indexInfo);
</programlisting>
Insert a new tuple into an existing index. The <literal>values</literal> and
look into the heap to verify tuple liveness).
</para>
+ <para>
+ The <literal>indexUnchanged</literal> boolean value gives a hint
+ about the nature of the tuple to be indexed. When it is true,
+ the tuple is a duplicate of some existing tuple in the index. The
+ new tuple is a logically unchanged successor MVCC tuple version. This
+ happens when an <command>UPDATE</command> takes place that does not
+ modify any columns covered by the index, but nevertheless requires a
+ new version in the index. The index AM may use this hint to decide
+ to apply bottom-up index deletion in parts of the index where many
+ versions of the same logical row accumulate. Note that updating a
+ non-key column does not affect the value of
+ <literal>indexUnchanged</literal>.
+ </para>
+
<para>
The function's Boolean result value is significant only when
<literal>checkUnique</literal> is <literal>UNIQUE_CHECK_PARTIAL</literal>.
brininsert(Relation idxRel, Datum *values, bool *nulls,
ItemPointer heaptid, Relation heapRel,
IndexUniqueCheck checkUnique,
+ bool indexUnchanged,
IndexInfo *indexInfo)
{
BlockNumber pagesPerRange;
toastrel,
toastidxs[i]->rd_index->indisunique ?
UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
- NULL);
+ false, NULL);
}
/*
gininsert(Relation index, Datum *values, bool *isnull,
ItemPointer ht_ctid, Relation heapRel,
IndexUniqueCheck checkUnique,
+ bool indexUnchanged,
IndexInfo *indexInfo)
{
GinState *ginstate = (GinState *) indexInfo->ii_AmCache;
gistinsert(Relation r, Datum *values, bool *isnull,
ItemPointer ht_ctid, Relation heapRel,
IndexUniqueCheck checkUnique,
+ bool indexUnchanged,
IndexInfo *indexInfo)
{
GISTSTATE *giststate = (GISTSTATE *) indexInfo->ii_AmCache;
hashinsert(Relation rel, Datum *values, bool *isnull,
ItemPointer ht_ctid, Relation heapRel,
IndexUniqueCheck checkUnique,
+ bool indexUnchanged,
IndexInfo *indexInfo)
{
Datum index_values[1];
heapRelation,
indexInfo->ii_Unique ?
UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
+ false,
indexInfo);
state->tups_inserted += 1;
ItemPointer heap_t_ctid,
Relation heapRelation,
IndexUniqueCheck checkUnique,
+ bool indexUnchanged,
IndexInfo *indexInfo)
{
RELATION_CHECKS;
return indexRelation->rd_indam->aminsert(indexRelation, values, isnull,
heap_t_ctid, heapRelation,
- checkUnique, indexInfo);
+ checkUnique, indexUnchanged,
+ indexInfo);
}
/*
btinsert(Relation rel, Datum *values, bool *isnull,
ItemPointer ht_ctid, Relation heapRel,
IndexUniqueCheck checkUnique,
+ bool indexUnchanged,
IndexInfo *indexInfo)
{
bool result;
spginsert(Relation index, Datum *values, bool *isnull,
ItemPointer ht_ctid, Relation heapRel,
IndexUniqueCheck checkUnique,
+ bool indexUnchanged,
IndexInfo *indexInfo)
{
SpGistState spgstate;
heapRelation,
index->rd_index->indisunique ?
UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
+ false,
indexInfo);
}
*/
index_insert(indexRel, values, isnull, &checktid,
trigdata->tg_relation, UNIQUE_CHECK_EXISTING,
- indexInfo);
+ false, indexInfo);
}
else
{
cstate->cur_lineno = buffer->linenos[i];
recheckIndexes =
ExecInsertIndexTuples(resultRelInfo,
- buffer->slots[i], estate, false, NULL,
- NIL);
+ buffer->slots[i], estate, false, false,
+ NULL, NIL);
ExecARInsertTriggers(estate, resultRelInfo,
slots[i], recheckIndexes,
cstate->transition_capture);
myslot,
estate,
false,
+ false,
NULL,
NIL);
}
static int MyTriggerDepth = 0;
/*
- * Note that similar macros also exist in executor/execMain.c. There does not
- * appear to be any good header to put them into, given the structures that
- * they use, so we let them be duplicated. Be sure to update all if one needs
- * to be changed, however.
+ * The authoritative version of this macro is in executor/execMain.c. Be sure
+ * to keep everything in sync.
*/
#define GetAllUpdatedColumns(relinfo, estate) \
(bms_union(exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->updatedCols, \
CEOUC_LIVELOCK_PREVENTING_WAIT
} CEOUC_WAIT_MODE;
+/*
+ * The authoritative version of these macro are in executor/execMain.c. Be
+ * sure to keep everything in sync.
+ */
+#define GetUpdatedColumns(relinfo, estate) \
+ (exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->updatedCols)
+#define GetExtraUpdatedColumns(relinfo, estate) \
+ (exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->extraUpdatedCols)
+
static bool check_exclusion_or_unique_constraint(Relation heap, Relation index,
IndexInfo *indexInfo,
ItemPointer tupleid,
static bool index_recheck_constraint(Relation index, Oid *constr_procs,
Datum *existing_values, bool *existing_isnull,
Datum *new_values);
+static bool index_unchanged_by_update(ResultRelInfo *resultRelInfo,
+ EState *estate, IndexInfo *indexInfo,
+ Relation indexRelation);
+static bool index_expression_changed_walker(Node *node,
+ Bitmapset *allUpdatedCols);
/* ----------------------------------------------------------------
* ExecOpenIndices
* into all the relations indexing the result relation
* when a heap tuple is inserted into the result relation.
*
+ * When 'update' is true, executor is performing an UPDATE
+ * that could not use an optimization like heapam's HOT (in
+ * more general terms a call to table_tuple_update() took
+ * place and set 'update_indexes' to true). Receiving this
+ * hint makes us consider if we should pass down the
+ * 'indexUnchanged' hint in turn. That's something that we
+ * figure out for each index_insert() call iff 'update' is
+ * true. (When 'update' is false we already know not to pass
+ * the hint to any index.)
+ *
* Unique and exclusion constraints are enforced at the same
* time. This returns a list of index OIDs for any unique or
* exclusion constraints that are deferred and that had
*
* If 'arbiterIndexes' is nonempty, noDupErr applies only to
* those indexes. NIL means noDupErr applies to all indexes.
- *
- * 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(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot,
EState *estate,
+ bool update,
bool noDupErr,
bool *specConflict,
List *arbiterIndexes)
IndexInfo *indexInfo;
bool applyNoDupErr;
IndexUniqueCheck checkUnique;
+ bool indexUnchanged;
bool satisfiesConstraint;
if (indexRelation == NULL)
else
checkUnique = UNIQUE_CHECK_PARTIAL;
+ /*
+ * There's definitely going to be an index_insert() call for this
+ * index. If we're being called as part of an UPDATE statement,
+ * consider if the 'indexUnchanged' = true hint should be passed.
+ */
+ indexUnchanged = update && index_unchanged_by_update(resultRelInfo,
+ estate,
+ indexInfo,
+ indexRelation);
+
satisfiesConstraint =
index_insert(indexRelation, /* index relation */
values, /* array of index Datums */
tupleid, /* tid of heap tuple */
heapRelation, /* heap relation */
checkUnique, /* type of uniqueness check to do */
+ indexUnchanged, /* UPDATE without logical change? */
indexInfo); /* index AM may need this */
/*
return true;
}
+
+/*
+ * Check if ExecInsertIndexTuples() should pass indexUnchanged hint.
+ *
+ * When the executor performs an UPDATE that requires a new round of index
+ * tuples, determine if we should pass 'indexUnchanged' = true hint for one
+ * single index.
+ */
+static bool
+index_unchanged_by_update(ResultRelInfo *resultRelInfo, EState *estate,
+ IndexInfo *indexInfo, Relation indexRelation)
+{
+ Bitmapset *updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+ Bitmapset *extraUpdatedCols = GetExtraUpdatedColumns(resultRelInfo, estate);
+ Bitmapset *allUpdatedCols;
+ bool hasexpression = false;
+ List *idxExprs;
+
+ /*
+ * Check for indexed attribute overlap with updated columns.
+ *
+ * Only do this for key columns. A change to a non-key column within an
+ * INCLUDE index should not be counted here. Non-key column values are
+ * opaque payload state to the index AM, a little like an extra table TID.
+ */
+ for (int attr = 0; attr < indexInfo->ii_NumIndexKeyAttrs; attr++)
+ {
+ int keycol = indexInfo->ii_IndexAttrNumbers[attr];
+
+ if (keycol <= 0)
+ {
+ /*
+ * Skip expressions for now, but remember to deal with them later
+ * on
+ */
+ hasexpression = true;
+ continue;
+ }
+
+ if (bms_is_member(keycol - FirstLowInvalidHeapAttributeNumber,
+ updatedCols) ||
+ bms_is_member(keycol - FirstLowInvalidHeapAttributeNumber,
+ extraUpdatedCols))
+ {
+ /* Changed key column -- don't hint for this index */
+ return false;
+ }
+ }
+
+ /*
+ * When we get this far and index has no expressions, return true so that
+ * index_insert() call will go on to pass 'indexUnchanged' = true hint.
+ *
+ * The _absence_ of an indexed key attribute that overlaps with updated
+ * attributes (in addition to the total absence of indexed expressions)
+ * shows that the index as a whole is logically unchanged by UPDATE.
+ */
+ if (!hasexpression)
+ return true;
+
+ /*
+ * Need to pass only one bms to expression_tree_walker helper function.
+ * Avoid allocating memory in common case where there are no extra cols.
+ */
+ if (!extraUpdatedCols)
+ allUpdatedCols = updatedCols;
+ else
+ allUpdatedCols = bms_union(updatedCols, extraUpdatedCols);
+
+ /*
+ * We have to work slightly harder in the event of indexed expressions,
+ * but the principle is the same as before: try to find columns (Vars,
+ * actually) that overlap with known-updated columns.
+ *
+ * If we find any matching Vars, don't pass hint for index. Otherwise
+ * pass hint.
+ */
+ idxExprs = RelationGetIndexExpressions(indexRelation);
+ hasexpression = index_expression_changed_walker((Node *) idxExprs,
+ allUpdatedCols);
+ list_free(idxExprs);
+ if (extraUpdatedCols)
+ bms_free(allUpdatedCols);
+
+ if (hasexpression)
+ return false;
+
+ return true;
+}
+
+/*
+ * Indexed expression helper for index_unchanged_by_update().
+ *
+ * Returns true when Var that appears within allUpdatedCols located.
+ */
+static bool
+index_expression_changed_walker(Node *node, Bitmapset *allUpdatedCols)
+{
+ if (node == NULL)
+ return false;
+
+ if (IsA(node, Var))
+ {
+ Var *var = (Var *) node;
+
+ if (bms_is_member(var->varattno - FirstLowInvalidHeapAttributeNumber,
+ allUpdatedCols))
+ {
+ /* Var was updated -- indicates that we should not hint */
+ return true;
+ }
+
+ /* Still haven't found a reason to not pass the hint */
+ return false;
+ }
+
+ return expression_tree_walker(node, index_expression_changed_walker,
+ (void *) allUpdatedCols);
+}
static void EvalPlanQualStart(EPQState *epqstate, Plan *planTree);
/*
- * Note that GetAllUpdatedColumns() also exists in commands/trigger.c. There does
- * not appear to be any good header to put it into, given the structures that
- * it uses, so we let them be duplicated. Be sure to update both if one needs
- * to be changed, however.
+ * Note that variants of these macros exists in commands/trigger.c and in
+ * execIndexing.c. There does not appear to be any good header to put it
+ * into, given the structures that it uses, so we let them be duplicated. Be
+ * sure to keep everything in sync.
*/
#define GetInsertedColumns(relinfo, estate) \
(exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->insertedCols)
if (resultRelInfo->ri_NumIndices > 0)
recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
- slot, estate, false, NULL,
- NIL);
+ slot, estate, false, false,
+ NULL, NIL);
/* AFTER ROW INSERT Triggers */
ExecARInsertTriggers(estate, resultRelInfo, slot,
if (resultRelInfo->ri_NumIndices > 0 && update_indexes)
recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
- slot, estate, false, NULL,
- NIL);
+ slot, estate, true, false,
+ NULL, NIL);
/* AFTER ROW UPDATE Triggers */
ExecARUpdateTriggers(estate, resultRelInfo,
/* insert index entries for tuple */
recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
- slot, estate, true,
+ slot, estate, false, true,
&specConflict,
arbiterIndexes);
if (resultRelInfo->ri_NumIndices > 0)
recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
slot, estate, false,
- NULL, NIL);
+ false, NULL, NIL);
}
}
/* insert index entries for tuple if necessary */
if (resultRelInfo->ri_NumIndices > 0 && update_indexes)
recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
- slot, estate, false,
+ slot, estate, true, false,
NULL, NIL);
}
InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
/*
- * Populate updatedCols so that per-column triggers can fire. This could
+ * Populate updatedCols so that per-column triggers can fire, and so
+ * executor can correctly pass down indexUnchanged hint. This could
* include more columns than were actually changed on the publisher
* because the logical replication protocol doesn't contain that
* information. But it would for example exclude columns that only exist
ItemPointer heap_tid,
Relation heapRelation,
IndexUniqueCheck checkUnique,
+ bool indexUnchanged,
struct IndexInfo *indexInfo);
/* bulk delete */
extern bool brininsert(Relation idxRel, Datum *values, bool *nulls,
ItemPointer heaptid, Relation heapRel,
IndexUniqueCheck checkUnique,
+ bool indexUnchanged,
struct IndexInfo *indexInfo);
extern IndexScanDesc brinbeginscan(Relation r, int nkeys, int norderbys);
extern int64 bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm);
ItemPointer heap_t_ctid,
Relation heapRelation,
IndexUniqueCheck checkUnique,
+ bool indexUnchanged,
struct IndexInfo *indexInfo);
extern IndexScanDesc index_beginscan(Relation heapRelation,
extern bool gininsert(Relation index, Datum *values, bool *isnull,
ItemPointer ht_ctid, Relation heapRel,
IndexUniqueCheck checkUnique,
+ bool indexUnchanged,
struct IndexInfo *indexInfo);
extern void ginEntryInsert(GinState *ginstate,
OffsetNumber attnum, Datum key, GinNullCategory category,
extern bool gistinsert(Relation r, Datum *values, bool *isnull,
ItemPointer ht_ctid, Relation heapRel,
IndexUniqueCheck checkUnique,
+ bool indexUnchanged,
struct IndexInfo *indexInfo);
extern MemoryContext createTempGistContext(void);
extern GISTSTATE *initGISTstate(Relation index);
extern bool hashinsert(Relation rel, Datum *values, bool *isnull,
ItemPointer ht_ctid, Relation heapRel,
IndexUniqueCheck checkUnique,
+ bool indexUnchanged,
struct IndexInfo *indexInfo);
extern bool hashgettuple(IndexScanDesc scan, ScanDirection dir);
extern int64 hashgetbitmap(IndexScanDesc scan, TIDBitmap *tbm);
extern bool btinsert(Relation rel, Datum *values, bool *isnull,
ItemPointer ht_ctid, Relation heapRel,
IndexUniqueCheck checkUnique,
+ bool indexUnchanged,
struct IndexInfo *indexInfo);
extern IndexScanDesc btbeginscan(Relation rel, int nkeys, int norderbys);
extern Size btestimateparallelscan(void);
extern bool spginsert(Relation index, Datum *values, bool *isnull,
ItemPointer ht_ctid, Relation heapRel,
IndexUniqueCheck checkUnique,
+ bool indexUnchanged,
struct IndexInfo *indexInfo);
/* spgscan.c */
extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
extern List *ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, EState *estate,
+ bool update,
bool noDupErr,
bool *specConflict, List *arbiterIndexes);
extern bool ExecCheckIndexConstraints(ResultRelInfo *resultRelInfo,
diinsert(Relation index, Datum *values, bool *isnull,
ItemPointer ht_ctid, Relation heapRel,
IndexUniqueCheck checkUnique,
+ bool indexUnchanged,
IndexInfo *indexInfo)
{
/* nothing to do */