From 9ade28743ef557b4727d39388194cc6c56952503 Mon Sep 17 00:00:00 2001 From: Greg Burd Date: Sun, 2 Nov 2025 11:36:20 -0500 Subject: [PATCH v20260226 1/2] Idenfity modified indexed attributes in the executor on UPDATE Refactor executor update logic to determine which indexed columns have actually changed during an UPDATE operation rather than leaving this up to HeapDetermineColumnsInfo() in heap_update(). ExecCheckIndexedAttrsForChanges() replaces HeapDeterminesColumnsInfo() and is called before table_tuple_update() crucially without the need for an exclusive buffer lock on the page that holds the tuple being updated. This reduces the time the lock is held later within heapam_tuple_update() and heap_update(). ExecCheckIndexedAttrsForChanges() in turn uses ExecCompareSlotAttrs() to identify which attributes have changed and then intersects that with the set of indexed attributes to identify the modified indexed set. Besides identifying the set of modified indexed attributes HeapDetermineColumnsInfo() was also responsible for part of the logic involed in the decision to include the replica identity key or not. This now happens in heap_update() when modified_attrs_valid is false. Catalog tuple updates use simple_heap_update() and don't pass a modified_attrs Bitmapset into heap_update() indicated by the modified_attrs_valid bool set to false. Updates stemming from logical replication also use the new ExecCheckIndexedAttrsForChanges() in ExecSimpleRelationUpdate(). Before row triggers on UPDATE may use heap_modify_tuple() to update attributes not identified by ExecGetAllUpdatedCols() as is the case in tsvector_update_trigger(). ExecBRUpdateTriggers() now identifies changes to indexed columns not found by ExecGetAllUpdateCols() and adds their attributes to ri_extraUpdatedCols. See tsearch.sql tests for an example of this. --- src/backend/access/heap/heapam.c | 80 +++++++++++++++++++++--- src/backend/access/heap/heapam_handler.c | 7 +-- src/backend/access/table/tableam.c | 5 +- src/backend/commands/trigger.c | 20 +++++- src/backend/executor/execReplication.c | 7 ++- src/backend/executor/execTuples.c | 78 +++++++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 78 ++++++++++++++++++++--- src/backend/replication/logical/worker.c | 10 +-- src/backend/utils/cache/relcache.c | 15 +++++ src/include/access/heapam.h | 1 + src/include/access/tableam.h | 8 ++- src/include/executor/executor.h | 8 +++ src/include/utils/rel.h | 1 + src/include/utils/relcache.h | 1 + 14 files changed, 283 insertions(+), 36 deletions(-) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 98d53caeea8..ab8b6ddb8de 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -3311,6 +3311,7 @@ TM_Result heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode, + const Bitmapset *modified_attrs, bool modified_attrs_valid, TU_UpdateIndexes *update_indexes) { TM_Result result; @@ -3320,7 +3321,6 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup, Bitmapset *key_attrs; Bitmapset *id_attrs; Bitmapset *interesting_attrs; - Bitmapset *modified_attrs; ItemId lp; HeapTupleData oldtup; HeapTuple heaptup; @@ -3345,7 +3345,7 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup, bool all_visible_cleared_new = false; bool checked_lockers; bool locker_remains; - bool id_has_external = false; + bool rep_id_key_required = false; TransactionId xmax_new_tuple, xmax_old_tuple; uint16 infomask_old_tuple, @@ -3487,9 +3487,69 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup, * new tuple so we must include it as part of the old_key_tuple. See * ExtractReplicaIdentity. */ - modified_attrs = HeapDetermineColumnsInfo(relation, interesting_attrs, - id_attrs, &oldtup, - newtup, &id_has_external); + if (!modified_attrs_valid) + { + bool id_has_external = false; + + modified_attrs = HeapDetermineColumnsInfo(relation, interesting_attrs, + id_attrs, &oldtup, + newtup, &id_has_external); + rep_id_key_required = id_has_external || + bms_overlap(modified_attrs, id_attrs); + } + else + { + /* + * ExtractReplicatIdentity() needs to know if a modified attrbute is + * used as a replica indentity or if any of the unmodified indexed + * attributes in the old tuple are stored externally and used as a + * replica identity. + */ + rep_id_key_required = bms_overlap(modified_attrs, id_attrs); + if (!rep_id_key_required) + { + Bitmapset *attrs; + TupleDesc tupdesc = RelationGetDescr(relation); + int attidx = -1; + + /* Check all unmodified indexed replica identity key attributes */ + attrs = bms_difference(interesting_attrs, modified_attrs); + attrs = bms_int_members(attrs, id_attrs); + + while ((attidx = bms_next_member(attrs, attidx)) >= 0) + { + /* + * attidx is zero-based, attrnum is the normal attribute + * number + */ + AttrNumber attrnum = attidx + FirstLowInvalidHeapAttributeNumber; + Datum value; + bool isnull; + + /* + * System attributes are not added into interesting_attrs in + * relcache. + */ + Assert(attrnum > 0); + + value = heap_getattr(&oldtup, attrnum, tupdesc, &isnull); + + /* No need to check attributes that can't be stored externally */ + if (isnull || + TupleDescCompactAttr(tupdesc, attrnum - 1)->attlen != -1) + continue; + + /* Check if the old tuple's attribute is stored externally */ + if (VARATT_IS_EXTERNAL((struct varlena *) DatumGetPointer(value))) + { + rep_id_key_required = true; + break; + } + } + + bms_free(attrs); + } + } /* * If we're not updating any "key" column, we can grab a weaker lock type. @@ -3763,7 +3823,7 @@ l2: bms_free(sum_attrs); bms_free(key_attrs); bms_free(id_attrs); - bms_free(modified_attrs); + /* modified attrs is passed in and free'd by the caller, or NULL */ bms_free(interesting_attrs); return result; } @@ -4111,8 +4171,7 @@ l2: * columns are modified or it has external data. */ old_key_tuple = ExtractReplicaIdentity(relation, &oldtup, - bms_overlap(modified_attrs, id_attrs) || - id_has_external, + rep_id_key_required, &old_key_copied); /* NO EREPORT(ERROR) from here till changes are logged */ @@ -4278,7 +4337,7 @@ l2: bms_free(sum_attrs); bms_free(key_attrs); bms_free(id_attrs); - bms_free(modified_attrs); + /* modified attrs is passed in and free'd by the caller, or NULL */ bms_free(interesting_attrs); return TM_Ok; @@ -4562,7 +4621,8 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup result = heap_update(relation, otid, tup, GetCurrentCommandId(true), InvalidSnapshot, true /* wait for commit */ , - &tmfd, &lockmode, update_indexes); + &tmfd, &lockmode, + NULL, false, update_indexes); switch (result) { case TM_SelfModified: diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index b83e2013d50..2690593fe4c 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -312,12 +312,11 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid, return heap_delete(relation, tid, cid, crosscheck, wait, tmfd, changingPart); } - static TM_Result heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot, CommandId cid, Snapshot snapshot, Snapshot crosscheck, - bool wait, TM_FailureData *tmfd, - LockTupleMode *lockmode, TU_UpdateIndexes *update_indexes) + bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode, + const Bitmapset *modified_attrs, TU_UpdateIndexes *update_indexes) { bool shouldFree = true; HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree); @@ -328,7 +327,7 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot, tuple->t_tableOid = slot->tts_tableOid; result = heap_update(relation, otid, tuple, cid, crosscheck, wait, - tmfd, lockmode, update_indexes); + tmfd, lockmode, modified_attrs, true, update_indexes); ItemPointerCopy(&tuple->t_self, &slot->tts_tid); /* diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c index dfda1af412e..42acd5b17a9 100644 --- a/src/backend/access/table/tableam.c +++ b/src/backend/access/table/tableam.c @@ -359,6 +359,7 @@ void simple_table_tuple_update(Relation rel, ItemPointer otid, TupleTableSlot *slot, Snapshot snapshot, + const Bitmapset *mix_attrs, TU_UpdateIndexes *update_indexes) { TM_Result result; @@ -369,7 +370,9 @@ simple_table_tuple_update(Relation rel, ItemPointer otid, GetCurrentCommandId(true), snapshot, InvalidSnapshot, true /* wait for commit */ , - &tmfd, &lockmode, update_indexes); + &tmfd, &lockmode, + mix_attrs, + update_indexes); switch (result) { diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 98d402c0a3b..64efa55dfe3 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -2978,6 +2978,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, bool is_merge_update) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; + TupleDesc tupdesc = RelationGetDescr(relinfo->ri_RelationDesc); TupleTableSlot *oldslot = ExecGetTriggerOldSlot(estate, relinfo); HeapTuple newtuple = NULL; HeapTuple trigtuple; @@ -2985,7 +2986,9 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, bool should_free_new = false; TriggerData LocTriggerData = {0}; int i; - Bitmapset *updatedCols; + Bitmapset *updatedCols = NULL; + Bitmapset *remainingCols = NULL; + Bitmapset *modifiedCols; LockTupleMode lockmode; /* Determine lock mode to use */ @@ -3127,6 +3130,21 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, if (should_free_trig) heap_freetuple(trigtuple); + /* + * Before UPDATE triggers may have updated attributes not known to + * ExecGetAllUpdatedColumns() using heap_modify_tuple() or + * heap_modifiy_tuple_by_cols(). Find and record those now. + */ + remainingCols = bms_add_range(NULL, 1 - FirstLowInvalidHeapAttributeNumber, + tupdesc->natts - FirstLowInvalidHeapAttributeNumber); + remainingCols = bms_del_members(remainingCols, updatedCols); + modifiedCols = ExecCompareSlotAttrs(tupdesc, remainingCols, oldslot, newslot); + relinfo->ri_extraUpdatedCols = + bms_add_members(relinfo->ri_extraUpdatedCols, modifiedCols); + + bms_free(remainingCols); + bms_free(modifiedCols); + return true; } diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c index 2497ee7edc5..910f3db37cf 100644 --- a/src/backend/executor/execReplication.c +++ b/src/backend/executor/execReplication.c @@ -33,6 +33,7 @@ #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/rel.h" +#include "utils/relcache.h" #include "utils/snapmgr.h" #include "utils/syscache.h" #include "utils/typcache.h" @@ -906,6 +907,7 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo, bool skip_tuple = false; Relation rel = resultRelInfo->ri_RelationDesc; ItemPointer tid = &(searchslot->tts_tid); + Bitmapset *mix_attrs; /* * We support only non-system tables, with @@ -944,8 +946,11 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo, if (rel->rd_rel->relispartition) ExecPartitionCheck(resultRelInfo, slot, estate, true); + mix_attrs = ExecCheckIndexedAttrsForChanges(resultRelInfo, + estate, searchslot, slot); + simple_table_tuple_update(rel, tid, slot, estate->es_snapshot, - &update_indexes); + mix_attrs, &update_indexes); conflictindexes = resultRelInfo->ri_onConflictArbiterIndexes; diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c index b768eae9e53..1064ebe845b 100644 --- a/src/backend/executor/execTuples.c +++ b/src/backend/executor/execTuples.c @@ -66,6 +66,7 @@ #include "nodes/nodeFuncs.h" #include "storage/bufmgr.h" #include "utils/builtins.h" +#include "utils/datum.h" #include "utils/expandeddatum.h" #include "utils/lsyscache.h" #include "utils/typcache.h" @@ -1929,6 +1930,83 @@ ExecFetchSlotHeapTupleDatum(TupleTableSlot *slot) return ret; } +/* + * ExecCompareSlotAttrs + * + * Compare the subset of attributes in attrs bewtween TupleTableSlots to detect + * which attributes have changed. + * + * Returns a Bitmapset of attribute indices (using + * FirstLowInvalidHeapAttributeNumber convention) that differ between the two + * slots. + */ +Bitmapset * +ExecCompareSlotAttrs(TupleDesc tupdesc, const Bitmapset *attrs, + TupleTableSlot *s1, TupleTableSlot *s2) +{ + int attidx = -1; + Bitmapset *modified = NULL; + + /* XXX what if slots don't share the same tupleDescriptor... */ + /* Assert(s1->tts_tupleDescriptor == s2->tts_tupleDescriptor); */ + + while ((attidx = bms_next_member(attrs, attidx)) >= 0) + { + /* attidx is zero-based, attrnum is the normal attribute number */ + AttrNumber attrnum = attidx + FirstLowInvalidHeapAttributeNumber; + Datum value1, + value2; + bool null1, + null2; + CompactAttribute *att; + + /* + * If it's a whole-tuple reference, say "not equal". It's not really + * worth supporting this case, since it could only succeed after a + * no-op update, which is hardly a case worth optimizing for. + */ + if (attrnum == 0) + { + modified = bms_add_member(modified, attidx); + continue; + } + + /* + * Likewise, automatically say "not equal" for any system attribute + * other than tableOID; we cannot expect these to be consistent in a + * HOT chain, or even to be set correctly yet in the new tuple. + */ + if (attrnum < 0) + { + if (attrnum != TableOidAttributeNumber) + { + modified = bms_add_member(modified, attidx); + continue; + } + } + + att = TupleDescCompactAttr(tupdesc, attrnum - 1); + value1 = slot_getattr(s1, attrnum, &null1); + value2 = slot_getattr(s2, attrnum, &null2); + + /* A change to/from NULL, so not equal */ + if (null1 != null2) + { + modified = bms_add_member(modified, attidx); + continue; + } + + /* Both NULL, no change/unmodified */ + if (null2) + continue; + + if (!datum_image_eq(value1, value2, att->attbyval, att->attlen)) + modified = bms_add_member(modified, attidx); + } + + return modified; +} + /* ---------------------------------------------------------------- * convenience initialization routines * ---------------------------------------------------------------- diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 793c76d4f82..18796baed28 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -17,6 +17,7 @@ * ExecModifyTable - retrieve the next tuple from the node * ExecEndModifyTable - shut down the ModifyTable node * ExecReScanModifyTable - rescan the ModifyTable node + * ExecCheckIndexedAttrsForChanges - find set of updated indexed columns * * NOTES * The ModifyTable node receives input from its outerPlan, which is @@ -54,6 +55,7 @@ #include "access/htup_details.h" #include "access/tableam.h" +#include "access/tupdesc.h" #include "access/xact.h" #include "commands/trigger.h" #include "executor/execPartition.h" @@ -188,6 +190,50 @@ static TupleTableSlot *ExecMergeNotMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo, bool canSetTag); +/* + * ExecCheckIndexedAttrsForChanges + * + * Determine which indexes need updating by finding the set of modified indexed + * attributes. + * + * The goal is for the executor to know, ahead of calling into the table AM to + * process the update and before calling into the index AM for inserting new + * index tuples, which attributes in the new TupleTableSlot, if any, truely + * necessitate a new index tuple. + * + * Returns a Bitmapset of attributes that intersects with indexes which require + * a new index tuple. + */ +Bitmapset * +ExecCheckIndexedAttrsForChanges(ResultRelInfo *resultRelInfo, + EState *estate, + TupleTableSlot *old_tts, + TupleTableSlot *new_tts) +{ + Relation relation = resultRelInfo->ri_RelationDesc; + TupleDesc tupdesc = RelationGetDescr(relation); + Bitmapset *attrs, + *mix_attrs; + + /* If no indexes, we're done */ + if (resultRelInfo->ri_NumIndices == 0) + return NULL; + + /* + * Fetch the set of attributes explicity SET in the UPDATE statement or + * set by a before row trigger (even if not mentioned in the SQL) and get + * the subset that are also indexed. + */ + attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_INDEXED); + attrs = bms_int_members(attrs, ExecGetAllUpdatedCols(resultRelInfo, estate)); + + /* Find out which, if any, modified indexed attributes changed */ + mix_attrs = ExecCompareSlotAttrs(tupdesc, attrs, old_tts, new_tts); + + bms_free(attrs); + + return mix_attrs; +} /* * Verify that the tuples to be produced by INSERT match the @@ -2195,14 +2241,17 @@ ExecUpdatePrepareSlot(ResultRelInfo *resultRelInfo, */ static TM_Result ExecUpdateAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo, - ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot, - bool canSetTag, UpdateContext *updateCxt) + ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *oldSlot, + TupleTableSlot *slot, bool canSetTag, UpdateContext *updateCxt) { EState *estate = context->estate; Relation resultRelationDesc = resultRelInfo->ri_RelationDesc; bool partition_constraint_failed; TM_Result result; + /* The set of modified indexed attributes that trigger new index entries */ + Bitmapset *mix_attrs = NULL; + updateCxt->crossPartUpdate = false; /* @@ -2319,7 +2368,16 @@ lreplace: ExecConstraints(resultRelInfo, slot, estate); /* - * replace the heap tuple + * Next up we need to find out the set of indexed attributes that have + * changed in value and should trigger a new index tuple. We could start + * with the set of updated columns via ExecGetUpdatedCols(), but if we do + * we will overlook attributes directly modified by heap_modify_tuple() + * which are not known to ExecGetUpdatedCols(). + */ + mix_attrs = ExecCheckIndexedAttrsForChanges(resultRelInfo, estate, oldSlot, slot); + + /* + * Call into the table AM to update the heap tuple. * * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that * the row to be updated is visible to that snapshot, and throw a @@ -2333,6 +2391,7 @@ lreplace: estate->es_crosscheck_snapshot, true /* wait for commit */ , &context->tmfd, &updateCxt->lockmode, + mix_attrs, &updateCxt->updateIndexes); return result; @@ -2555,8 +2614,9 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo, */ redo_act: lockedtid = *tupleid; - result = ExecUpdateAct(context, resultRelInfo, tupleid, oldtuple, slot, - canSetTag, &updateCxt); + + result = ExecUpdateAct(context, resultRelInfo, tupleid, oldtuple, oldSlot, + slot, canSetTag, &updateCxt); /* * If ExecUpdateAct reports that a cross-partition update was done, @@ -3406,8 +3466,8 @@ lmerge_matched: Assert(oldtuple == NULL); result = ExecUpdateAct(context, resultRelInfo, tupleid, - NULL, newslot, canSetTag, - &updateCxt); + NULL, resultRelInfo->ri_oldTupleSlot, + newslot, canSetTag, &updateCxt); /* * As in ExecUpdate(), if ExecUpdateAct() reports that a @@ -3432,6 +3492,7 @@ lmerge_matched: tupleid, NULL, newslot); mtstate->mt_merge_updated += 1; } + break; case CMD_DELETE: @@ -4539,7 +4600,7 @@ ExecModifyTable(PlanState *pstate) * For UPDATE/DELETE/MERGE, fetch the row identity info for the tuple * to be updated/deleted/merged. For a heap relation, that's a TID; * otherwise we may have a wholerow junk attr that carries the old - * tuple in toto. Keep this in step with the part of + * tuple in total. Keep this in step with the part of * ExecInitModifyTable that sets up ri_RowIdAttNo. */ if (operation == CMD_UPDATE || operation == CMD_DELETE || @@ -4719,6 +4780,7 @@ ExecModifyTable(PlanState *pstate) /* Now apply the update. */ slot = ExecUpdate(&context, resultRelInfo, tupleid, oldtuple, oldSlot, slot, node->canSetTag); + if (tuplock) UnlockTuple(resultRelInfo->ri_RelationDesc, tupleid, InplaceUpdateTupleLock); diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c index bae8c011390..8a9b78e2e28 100644 --- a/src/backend/replication/logical/worker.c +++ b/src/backend/replication/logical/worker.c @@ -2919,7 +2919,6 @@ apply_handle_update_internal(ApplyExecutionData *edata, TupleTableSlot *localslot = NULL; ConflictTupleInfo conflicttuple = {0}; bool found; - MemoryContext oldctx; EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1, NIL); ExecOpenIndices(relinfo, false); @@ -2958,15 +2957,13 @@ apply_handle_update_internal(ApplyExecutionData *edata, } /* Process and store remote tuple in the slot */ - oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); slot_modify_data(remoteslot, localslot, relmapentry, newtup); - MemoryContextSwitchTo(oldctx); EvalPlanQualSetSlot(&epqstate, remoteslot); InitConflictIndexes(relinfo); - /* Do the actual update. */ + /* First check privileges */ TargetPrivilegesCheck(relinfo->ri_RelationDesc, ACL_UPDATE); ExecSimpleRelationUpdate(relinfo, estate, &epqstate, localslot, remoteslot); @@ -3524,10 +3521,7 @@ apply_handle_tuple_routing(ApplyExecutionData *edata, * Apply the update to the local tuple, putting the result in * remoteslot_part. */ - oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); - slot_modify_data(remoteslot_part, localslot, part_entry, - newtup); - MemoryContextSwitchTo(oldctx); + slot_modify_data(remoteslot_part, localslot, part_entry, newtup); EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1, NIL); diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 6b634c9fff1..547cf1d054d 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -2477,6 +2477,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc) bms_free(relation->rd_idattr); bms_free(relation->rd_hotblockingattr); bms_free(relation->rd_summarizedattr); + bms_free(relation->rd_indexedattr); if (relation->rd_pubdesc) pfree(relation->rd_pubdesc); if (relation->rd_options) @@ -5278,6 +5279,7 @@ RelationGetIndexPredicate(Relation relation) * index (empty if FULL) * INDEX_ATTR_BITMAP_HOT_BLOCKING Columns that block updates from being HOT * INDEX_ATTR_BITMAP_SUMMARIZED Columns included in summarizing indexes + * INDEX_ATTR_BITMAP_INDEXED Columns referenced by indexes * * Attribute numbers are offset by FirstLowInvalidHeapAttributeNumber so that * we can include system attributes (e.g., OID) in the bitmap representation. @@ -5301,6 +5303,7 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind) Bitmapset *pkindexattrs; /* columns in the primary index */ Bitmapset *idindexattrs; /* columns in the replica identity */ Bitmapset *hotblockingattrs; /* columns with HOT blocking indexes */ + Bitmapset *indexedattrs; /* columns referenced by indexes */ Bitmapset *summarizedattrs; /* columns with summarizing indexes */ List *indexoidlist; List *newindexoidlist; @@ -5324,6 +5327,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind) return bms_copy(relation->rd_hotblockingattr); case INDEX_ATTR_BITMAP_SUMMARIZED: return bms_copy(relation->rd_summarizedattr); + case INDEX_ATTR_BITMAP_INDEXED: + return bms_copy(relation->rd_indexedattr); default: elog(ERROR, "unknown attrKind %u", attrKind); } @@ -5368,6 +5373,7 @@ restart: idindexattrs = NULL; hotblockingattrs = NULL; summarizedattrs = NULL; + indexedattrs = NULL; foreach(l, indexoidlist) { Oid indexOid = lfirst_oid(l); @@ -5500,10 +5506,14 @@ restart: bms_free(idindexattrs); bms_free(hotblockingattrs); bms_free(summarizedattrs); + /* indexedattrs not yet initialized */ goto restart; } + /* Set indexed attributes to track all referenced attributes */ + indexedattrs = bms_union(hotblockingattrs, summarizedattrs); + /* Don't leak the old values of these bitmaps, if any */ relation->rd_attrsvalid = false; bms_free(relation->rd_keyattr); @@ -5516,6 +5526,8 @@ restart: relation->rd_hotblockingattr = NULL; bms_free(relation->rd_summarizedattr); relation->rd_summarizedattr = NULL; + bms_free(relation->rd_indexedattr); + relation->rd_indexedattr = NULL; /* * Now save copies of the bitmaps in the relcache entry. We intentionally @@ -5530,6 +5542,7 @@ restart: relation->rd_idattr = bms_copy(idindexattrs); relation->rd_hotblockingattr = bms_copy(hotblockingattrs); relation->rd_summarizedattr = bms_copy(summarizedattrs); + relation->rd_indexedattr = bms_copy(indexedattrs); relation->rd_attrsvalid = true; MemoryContextSwitchTo(oldcxt); @@ -5546,6 +5559,8 @@ restart: return hotblockingattrs; case INDEX_ATTR_BITMAP_SUMMARIZED: return summarizedattrs; + case INDEX_ATTR_BITMAP_INDEXED: + return indexedattrs; default: elog(ERROR, "unknown attrKind %u", attrKind); return NULL; diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 3c0961ab36b..a56f3d1f378 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -368,6 +368,7 @@ extern TM_Result heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode, + const Bitmapset *mix_attrs, bool mix_attrs_valid, TU_UpdateIndexes *update_indexes); extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple, CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy, diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index 119593b7b46..bc360a9b529 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -549,6 +549,7 @@ typedef struct TableAmRoutine bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode, + const Bitmapset *updated_cols, TU_UpdateIndexes *update_indexes); /* see table_tuple_lock() for reference about parameters */ @@ -1524,12 +1525,12 @@ static inline TM_Result table_tuple_update(Relation rel, ItemPointer otid, TupleTableSlot *slot, CommandId cid, Snapshot snapshot, Snapshot crosscheck, bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode, - TU_UpdateIndexes *update_indexes) + const Bitmapset *mix_cols, TU_UpdateIndexes *update_indexes) { return rel->rd_tableam->tuple_update(rel, otid, slot, cid, snapshot, crosscheck, - wait, tmfd, - lockmode, update_indexes); + wait, tmfd, lockmode, + mix_cols, update_indexes); } /* @@ -2010,6 +2011,7 @@ extern void simple_table_tuple_delete(Relation rel, ItemPointer tid, Snapshot snapshot); extern void simple_table_tuple_update(Relation rel, ItemPointer otid, TupleTableSlot *slot, Snapshot snapshot, + const Bitmapset *mix_attrs, TU_UpdateIndexes *update_indexes); diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index d46ba59895d..7b0019fe15b 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -606,6 +606,10 @@ extern TupleDesc ExecCleanTypeFromTL(List *targetList); extern TupleDesc ExecTypeFromExprList(List *exprList); extern void ExecTypeSetColNames(TupleDesc typeInfo, List *namesList); extern void UpdateChangedParamSet(PlanState *node, Bitmapset *newchg); +extern Bitmapset *ExecCompareSlotAttrs(TupleDesc tupdesc, + const Bitmapset *attrs, + TupleTableSlot *old_tts, + TupleTableSlot *new_tts); typedef struct TupOutputState { @@ -803,5 +807,9 @@ extern ResultRelInfo *ExecLookupResultRelByOid(ModifyTableState *node, Oid resultoid, bool missing_ok, bool update_cache); +extern Bitmapset *ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo, + EState *estate, + TupleTableSlot *old_tts, + TupleTableSlot *new_tts); #endif /* EXECUTOR_H */ diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 236830f6b93..df5426fd7fb 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -164,6 +164,7 @@ typedef struct RelationData Bitmapset *rd_idattr; /* included in replica identity index */ Bitmapset *rd_hotblockingattr; /* cols blocking HOT update */ Bitmapset *rd_summarizedattr; /* cols indexed by summarizing indexes */ + Bitmapset *rd_indexedattr; /* all cols referenced by indexes */ PublicationDesc *rd_pubdesc; /* publication descriptor, or NULL */ diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h index 2700224939a..5834ab7b903 100644 --- a/src/include/utils/relcache.h +++ b/src/include/utils/relcache.h @@ -71,6 +71,7 @@ typedef enum IndexAttrBitmapKind INDEX_ATTR_BITMAP_IDENTITY_KEY, INDEX_ATTR_BITMAP_HOT_BLOCKING, INDEX_ATTR_BITMAP_SUMMARIZED, + INDEX_ATTR_BITMAP_INDEXED, } IndexAttrBitmapKind; extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation, -- 2.51.2