From ba5d8fd885894562e4a077aa5b6c04948ff83a91 Mon Sep 17 00:00:00 2001 From: Hari Babu Date: Thu, 29 Mar 2018 16:59:20 +1100 Subject: [PATCH 11/16] Improve tuple locking interface Currently, executor code have to traverse heap update chains. That's doesn't seems to be acceptable if we're going to have pluggable table access methods whose could provide alternative implementations for our MVCC model. New locking function is responsible for finding latest tuple version (if required). EvalPlanQual() is now only responsible for re-evaluating quals, but not for locking tuple. In addition, we've distinguish HeapTupleUpdated and HeapTupleDeleted HTSU_Result's, because in alternative MVCC implementations multiple tuple versions may have same TID, and immutability of TID after update isn't sign of tuple deletion anymore. For the same reason, TID is not pointer to particular tuple version anymore. And in order to point particular tuple version we're going to lock, we've to provide snapshot as well. In heap storage access method, this snapshot is used for assert checking only, but it might be vital for other table access methods. Similar changes are upcoming to tuple_update() and tuple_delete() interface methods. --- src/backend/access/heap/heapam.c | 27 +- src/backend/access/heap/heapam_handler.c | 179 ++++++++++++- src/backend/access/heap/heapam_visibility.c | 20 +- src/backend/access/table/tableam.c | 11 +- src/backend/commands/trigger.c | 71 ++--- src/backend/executor/execMain.c | 293 +-------------------- src/backend/executor/execReplication.c | 36 ++- src/backend/executor/nodeLockRows.c | 70 ++--- src/backend/executor/nodeModifyTable.c | 154 +++++++---- src/include/access/heapam.h | 1 + src/include/access/tableam.h | 8 +- src/include/access/tableamapi.h | 4 +- src/include/executor/executor.h | 6 +- src/include/nodes/lockoptions.h | 5 + src/include/utils/snapshot.h | 1 + .../isolation/expected/partition-key-update-1.out | 2 +- 16 files changed, 416 insertions(+), 472 deletions(-) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 4caa52c20f..5cd1d1b2de 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -3210,6 +3210,7 @@ l1: { Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated || + result == HeapTupleDeleted || result == HeapTupleBeingUpdated); Assert(!(tp.t_data->t_infomask & HEAP_XMAX_INVALID)); hufd->ctid = tp.t_data->t_ctid; @@ -3223,6 +3224,8 @@ l1: UnlockTupleTuplock(relation, &(tp.t_self), LockTupleExclusive); if (vmbuffer != InvalidBuffer) ReleaseBuffer(vmbuffer); + if (result == HeapTupleUpdated && ItemPointerEquals(tid, &hufd->ctid)) + result = HeapTupleDeleted; return result; } @@ -3440,6 +3443,10 @@ simple_heap_delete(Relation relation, ItemPointer tid) elog(ERROR, "tuple concurrently updated"); break; + case HeapTupleDeleted: + elog(ERROR, "tuple concurrently deleted"); + break; + default: elog(ERROR, "unrecognized heap_delete status: %u", result); break; @@ -3860,6 +3867,7 @@ l2: { Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated || + result == HeapTupleDeleted || result == HeapTupleBeingUpdated); Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID)); hufd->ctid = oldtup.t_data->t_ctid; @@ -3879,6 +3887,8 @@ l2: bms_free(id_attrs); bms_free(modified_attrs); bms_free(interesting_attrs); + if (result == HeapTupleUpdated && ItemPointerEquals(otid, &hufd->ctid)) + result = HeapTupleDeleted; return result; } @@ -4585,6 +4595,10 @@ simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup) elog(ERROR, "tuple concurrently updated"); break; + case HeapTupleDeleted: + elog(ERROR, "tuple concurrently deleted"); + break; + default: elog(ERROR, "unrecognized heap_update status: %u", result); break; @@ -4637,6 +4651,7 @@ get_mxact_status_for_lock(LockTupleMode mode, bool is_update) * HeapTupleInvisible: lock failed because tuple was never visible to us * HeapTupleSelfUpdated: lock failed because tuple updated by self * HeapTupleUpdated: lock failed because tuple updated by other xact + * HeapTupleDeleted: lock failed because tuple deleted by other xact * HeapTupleWouldBlock: lock couldn't be acquired and wait_policy is skip * * In the failure cases other than HeapTupleInvisible, the routine fills @@ -4705,7 +4720,7 @@ l3: result = HeapTupleInvisible; goto out_locked; } - else if (result == HeapTupleBeingUpdated || result == HeapTupleUpdated) + else if (result == HeapTupleBeingUpdated || result == HeapTupleUpdated || result == HeapTupleDeleted) { TransactionId xwait; uint16 infomask; @@ -4985,7 +5000,7 @@ l3: * or we must wait for the locking transaction or multixact; so below * we ensure that we grab buffer lock after the sleep. */ - if (require_sleep && (result == HeapTupleUpdated)) + if (require_sleep && (result == HeapTupleUpdated || result == HeapTupleDeleted)) { LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE); goto failed; @@ -5145,6 +5160,8 @@ l3: HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask) || HeapTupleHeaderIsOnlyLocked(tuple->t_data)) result = HeapTupleMayBeUpdated; + else if (ItemPointerEquals(&tuple->t_self, &tuple->t_data->t_ctid)) + result = HeapTupleDeleted; else result = HeapTupleUpdated; } @@ -5153,7 +5170,7 @@ failed: if (result != HeapTupleMayBeUpdated) { Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated || - result == HeapTupleWouldBlock); + result == HeapTupleWouldBlock || result == HeapTupleDeleted); Assert(!(tuple->t_data->t_infomask & HEAP_XMAX_INVALID)); hufd->ctid = tuple->t_data->t_ctid; hufd->xmax = HeapTupleHeaderGetUpdateXid(tuple->t_data); @@ -6041,6 +6058,10 @@ next: result = HeapTupleMayBeUpdated; out_locked: + + if (result == HeapTupleUpdated && ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid)) + result = HeapTupleDeleted; + UnlockReleaseBuffer(buf); out_unlocked: diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index f2de024fdb..062e184095 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -174,6 +174,7 @@ heapam_heap_delete(Relation relation, ItemPointer tid, CommandId cid, * HeapTupleInvisible: lock failed because tuple was never visible to us * HeapTupleSelfUpdated: lock failed because tuple updated by self * HeapTupleUpdated: lock failed because tuple updated by other xact + * HeapTupleDeleted: lock failed because tuple deleted by other xact * HeapTupleWouldBlock: lock couldn't be acquired and wait_policy is skip * * In the failure cases other than HeapTupleInvisible, the routine fills @@ -184,21 +185,191 @@ heapam_heap_delete(Relation relation, ItemPointer tid, CommandId cid, * See comments for struct HeapUpdateFailureData for additional info. */ static HTSU_Result -heapam_lock_tuple(Relation relation, ItemPointer tid, TableTuple *stuple, - CommandId cid, LockTupleMode mode, - LockWaitPolicy wait_policy, bool follow_updates, Buffer *buffer, +heapam_lock_tuple(Relation relation, ItemPointer tid, Snapshot snapshot, + TableTuple *stuple, CommandId cid, LockTupleMode mode, + LockWaitPolicy wait_policy, uint8 flags, HeapUpdateFailureData *hufd) { HTSU_Result result; HeapTupleData tuple; + Buffer buffer; Assert(stuple != NULL); *stuple = NULL; + hufd->traversed = false; + +retry: tuple.t_self = *tid; - result = heap_lock_tuple(relation, &tuple, cid, mode, wait_policy, follow_updates, buffer, hufd); + result = heap_lock_tuple(relation, &tuple, cid, mode, wait_policy, + (flags & TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS) ? true : false, + &buffer, hufd); + + if (result == HeapTupleUpdated && + (flags & TUPLE_LOCK_FLAG_FIND_LAST_VERSION)) + { + ReleaseBuffer(buffer); + /* Should not encounter speculative tuple on recheck */ + Assert(!HeapTupleHeaderIsSpeculative(tuple.t_data)); + + if (!ItemPointerEquals(&hufd->ctid, &tuple.t_self)) + { + SnapshotData SnapshotDirty; + TransactionId priorXmax; + + /* it was updated, so look at the updated version */ + *tid = hufd->ctid; + /* updated row should have xmin matching this xmax */ + priorXmax = hufd->xmax; + + /* + * fetch target tuple + * + * Loop here to deal with updated or busy tuples + */ + InitDirtySnapshot(SnapshotDirty); + for (;;) + { + if (ItemPointerIndicatesMovedPartitions(tid)) + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("tuple to be locked was already moved to another partition due to concurrent update"))); + + + if (heap_fetch(relation, tid, &SnapshotDirty, &tuple, &buffer, true, NULL)) + { + /* + * If xmin isn't what we're expecting, the slot must have been + * recycled and reused for an unrelated tuple. This implies that + * the latest version of the row was deleted, so we need do + * nothing. (Should be safe to examine xmin without getting + * buffer's content lock. We assume reading a TransactionId to be + * atomic, and Xmin never changes in an existing tuple, except to + * invalid or frozen, and neither of those can match priorXmax.) + */ + if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple.t_data), + priorXmax)) + { + ReleaseBuffer(buffer); + return HeapTupleDeleted; + } + + /* otherwise xmin should not be dirty... */ + if (TransactionIdIsValid(SnapshotDirty.xmin)) + elog(ERROR, "t_xmin is uncommitted in tuple to be updated"); + + /* + * If tuple is being updated by other transaction then we have to + * wait for its commit/abort, or die trying. + */ + if (TransactionIdIsValid(SnapshotDirty.xmax)) + { + ReleaseBuffer(buffer); + switch (wait_policy) + { + case LockWaitBlock: + XactLockTableWait(SnapshotDirty.xmax, + relation, &tuple.t_self, + XLTW_FetchUpdated); + break; + case LockWaitSkip: + if (!ConditionalXactLockTableWait(SnapshotDirty.xmax)) + return result; /* skip instead of waiting */ + break; + case LockWaitError: + if (!ConditionalXactLockTableWait(SnapshotDirty.xmax)) + ereport(ERROR, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("could not obtain lock on row in relation \"%s\"", + RelationGetRelationName(relation)))); + break; + } + continue; /* loop back to repeat heap_fetch */ + } + + /* + * If tuple was inserted by our own transaction, we have to check + * cmin against es_output_cid: cmin >= current CID means our + * command cannot see the tuple, so we should ignore it. Otherwise + * heap_lock_tuple() will throw an error, and so would any later + * attempt to update or delete the tuple. (We need not check cmax + * because HeapTupleSatisfiesDirty will consider a tuple deleted + * by our transaction dead, regardless of cmax.) We just checked + * that priorXmax == xmin, so we can test that variable instead of + * doing HeapTupleHeaderGetXmin again. + */ + if (TransactionIdIsCurrentTransactionId(priorXmax) && + HeapTupleHeaderGetCmin(tuple.t_data) >= cid) + { + ReleaseBuffer(buffer); + return result; + } + + hufd->traversed = true; + *tid = tuple.t_data->t_ctid; + ReleaseBuffer(buffer); + goto retry; + } + + /* + * If the referenced slot was actually empty, the latest version of + * the row must have been deleted, so we need do nothing. + */ + if (tuple.t_data == NULL) + { + ReleaseBuffer(buffer); + return HeapTupleDeleted; + } + + /* + * As above, if xmin isn't what we're expecting, do nothing. + */ + if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple.t_data), + priorXmax)) + { + ReleaseBuffer(buffer); + return HeapTupleDeleted; + } + + /* + * If we get here, the tuple was found but failed SnapshotDirty. + * Assuming the xmin is either a committed xact or our own xact (as it + * certainly should be if we're trying to modify the tuple), this must + * mean that the row was updated or deleted by either a committed xact + * or our own xact. If it was deleted, we can ignore it; if it was + * updated then chain up to the next version and repeat the whole + * process. + * + * As above, it should be safe to examine xmax and t_ctid without the + * buffer content lock, because they can't be changing. + */ + if (ItemPointerEquals(&tuple.t_self, &tuple.t_data->t_ctid)) + { + /* deleted, so forget about it */ + ReleaseBuffer(buffer); + return HeapTupleDeleted; + } + + /* updated, so look at the updated row */ + *tid = tuple.t_data->t_ctid; + /* updated row should have xmin matching this xmax */ + priorXmax = HeapTupleHeaderGetUpdateXid(tuple.t_data); + ReleaseBuffer(buffer); + /* loop back to fetch next in chain */ + } + } + else + { + /* tuple was deleted, so give up */ + return HeapTupleDeleted; + } + } + + Assert((flags & TUPLE_LOCK_FLAG_FIND_LAST_VERSION) || + HeapTupleSatisfies((TableTuple) &tuple, snapshot, InvalidBuffer)); *stuple = heap_copytuple(&tuple); + ReleaseBuffer(buffer); return result; } diff --git a/src/backend/access/heap/heapam_visibility.c b/src/backend/access/heap/heapam_visibility.c index 9051d4be88..1d45f98a2e 100644 --- a/src/backend/access/heap/heapam_visibility.c +++ b/src/backend/access/heap/heapam_visibility.c @@ -616,7 +616,11 @@ HeapTupleSatisfiesUpdate(TableTuple stup, CommandId curcid, { if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) return HeapTupleMayBeUpdated; - return HeapTupleUpdated; /* updated by other */ + /* updated by other */ + if (ItemPointerEquals(&htup->t_self, &tuple->t_ctid)) + return HeapTupleDeleted; + else + return HeapTupleUpdated; } if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) @@ -657,7 +661,12 @@ HeapTupleSatisfiesUpdate(TableTuple stup, CommandId curcid, return HeapTupleBeingUpdated; if (TransactionIdDidCommit(xmax)) - return HeapTupleUpdated; + { + if (ItemPointerEquals(&htup->t_self, &tuple->t_ctid)) + return HeapTupleDeleted; + else + return HeapTupleUpdated; + } /* * By here, the update in the Xmax is either aborted or crashed, but @@ -713,7 +722,12 @@ HeapTupleSatisfiesUpdate(TableTuple stup, CommandId curcid, SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, HeapTupleHeaderGetRawXmax(tuple)); - return HeapTupleUpdated; /* updated by other */ + + /* updated by other */ + if (ItemPointerEquals(&htup->t_self, &tuple->t_ctid)) + return HeapTupleDeleted; + else + return HeapTupleUpdated; } /* diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c index 576afe5de2..39b0f990c4 100644 --- a/src/backend/access/table/tableam.c +++ b/src/backend/access/table/tableam.c @@ -41,13 +41,14 @@ table_fetch(Relation relation, * table_lock_tuple - lock a tuple in shared or exclusive mode */ HTSU_Result -table_lock_tuple(Relation relation, ItemPointer tid, TableTuple * stuple, - CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy, - bool follow_updates, Buffer *buffer, HeapUpdateFailureData *hufd) +table_lock_tuple(Relation relation, ItemPointer tid, Snapshot snapshot, + TableTuple *stuple, CommandId cid, LockTupleMode mode, + LockWaitPolicy wait_policy, uint8 flags, + HeapUpdateFailureData *hufd) { - return relation->rd_tableamroutine->tuple_lock(relation, tid, stuple, + return relation->rd_tableamroutine->tuple_lock(relation, tid, snapshot, stuple, cid, mode, wait_policy, - follow_updates, buffer, hufd); + flags, hufd); } /* ---------------- diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index a4ea981442..75deabe0fe 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3251,8 +3251,6 @@ GetTupleForTrigger(EState *estate, Relation relation = relinfo->ri_RelationDesc; TableTuple tuple; HeapTuple result; - Buffer buffer; - tuple_data t_data; if (newSlot != NULL) { @@ -3267,11 +3265,11 @@ GetTupleForTrigger(EState *estate, /* * lock tuple for update */ -ltrmark:; - test = table_lock_tuple(relation, tid, &tuple, + test = table_lock_tuple(relation, tid, estate->es_snapshot, &tuple, estate->es_output_cid, lockmode, LockWaitBlock, - false, &buffer, &hufd); + IsolationUsesXactSnapshot() ? 0 : TUPLE_LOCK_FLAG_FIND_LAST_VERSION, + &hufd); result = tuple; switch (test) { @@ -3292,55 +3290,42 @@ ltrmark:; errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows."))); /* treat it as deleted; do not process */ - ReleaseBuffer(buffer); return NULL; case HeapTupleMayBeUpdated: - break; - - case HeapTupleUpdated: - ReleaseBuffer(buffer); - if (IsolationUsesXactSnapshot()) - ereport(ERROR, - (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), - errmsg("could not serialize access due to concurrent update"))); - if (ItemPointerIndicatesMovedPartitions(&hufd.ctid)) - ereport(ERROR, - (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), - errmsg("tuple to be locked was already moved to another partition due to concurrent update"))); - t_data = relation->rd_tableamroutine->get_tuple_data(tuple, TID); - if (!ItemPointerEquals(&hufd.ctid, &(t_data.tid))) + if (hufd.traversed) { - /* it was updated, so look at the updated version */ TupleTableSlot *epqslot; epqslot = EvalPlanQual(estate, epqstate, relation, relinfo->ri_RangeTableIndex, - lockmode, - &hufd.ctid, - hufd.xmax); - if (!TupIsNull(epqslot)) - { - *tid = hufd.ctid; - *newSlot = epqslot; - - /* - * EvalPlanQual already locked the tuple, but we - * re-call heap_lock_tuple anyway as an easy way of - * re-fetching the correct tuple. Speed is hardly a - * criterion in this path anyhow. - */ - goto ltrmark; - } + tuple); + + /* If PlanQual failed for updated tuple - we must not process this tuple!*/ + if (TupIsNull(epqslot)) + return NULL; + + *newSlot = epqslot; } + break; - /* - * if tuple was deleted or PlanQual failed for updated tuple - - * we must not process this tuple! - */ + case HeapTupleUpdated: + if (IsolationUsesXactSnapshot()) + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to concurrent update"))); + elog(ERROR, "wrong heap_lock_tuple status: %u", test); + break; + + case HeapTupleDeleted: + if (IsolationUsesXactSnapshot()) + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to concurrent update"))); + /* tuple was deleted */ return NULL; case HeapTupleInvisible: @@ -3348,13 +3333,13 @@ ltrmark:; break; default: - ReleaseBuffer(buffer); elog(ERROR, "unrecognized heap_lock_tuple status: %u", test); return NULL; /* keep compiler quiet */ } } else { + Buffer buffer; Page page; ItemId lp; HeapTupleData tupledata; @@ -3384,9 +3369,9 @@ ltrmark:; LockBuffer(buffer, BUFFER_LOCK_UNLOCK); result = heap_copytuple(&tupledata); + ReleaseBuffer(buffer); } - ReleaseBuffer(buffer); return result; } diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index c677e4b19a..745a9fa491 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -2488,9 +2488,7 @@ ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist) * epqstate - state for EvalPlanQual rechecking * relation - table containing tuple * rti - rangetable index of table containing tuple - * lockmode - requested tuple lock mode - * *tid - t_ctid from the outdated tuple (ie, next updated version) - * priorXmax - t_xmax from the outdated tuple + * tuple - tuple for processing * * *tid is also an output parameter: it's modified to hold the TID of the * latest version of the tuple (note this may be changed even on failure) @@ -2503,32 +2501,12 @@ ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist) */ TupleTableSlot * EvalPlanQual(EState *estate, EPQState *epqstate, - Relation relation, Index rti, int lockmode, - ItemPointer tid, TransactionId priorXmax) + Relation relation, Index rti, TableTuple tuple) { TupleTableSlot *slot; - TableTuple copyTuple; - tuple_data t_data; Assert(rti > 0); - /* - * Get and lock the updated version of the row; if fail, return NULL. - */ - copyTuple = EvalPlanQualFetch(estate, relation, lockmode, LockWaitBlock, - tid, priorXmax); - - if (copyTuple == NULL) - return NULL; - - /* - * For UPDATE/DELETE we have to return tid of actual row we're executing - * PQ for. - */ - - t_data = table_tuple_get_data(relation, copyTuple, TID); - *tid = t_data.tid; - /* * Need to run a recheck subquery. Initialize or reinitialize EPQ state. */ @@ -2538,7 +2516,7 @@ EvalPlanQual(EState *estate, EPQState *epqstate, * Free old test tuple, if any, and store new tuple where relation's scan * node will see it */ - EvalPlanQualSetTuple(epqstate, rti, copyTuple); + EvalPlanQualSetTuple(epqstate, rti, tuple); /* * Fetch any non-locked source rows @@ -2570,271 +2548,6 @@ EvalPlanQual(EState *estate, EPQState *epqstate, return slot; } -/* - * Fetch a copy of the newest version of an outdated tuple - * - * estate - executor state data - * relation - table containing tuple - * lockmode - requested tuple lock mode - * wait_policy - requested lock wait policy - * *tid - t_ctid from the outdated tuple (ie, next updated version) - * priorXmax - t_xmax from the outdated tuple - * - * Returns a palloc'd copy of the newest tuple version, or NULL if we find - * that there is no newest version (ie, the row was deleted not updated). - * We also return NULL if the tuple is locked and the wait policy is to skip - * such tuples. - * - * If successful, we have locked the newest tuple version, so caller does not - * need to worry about it changing anymore. - * - * Note: properly, lockmode should be declared as enum LockTupleMode, - * but we use "int" to avoid having to include heapam.h in executor.h. - */ -TableTuple -EvalPlanQualFetch(EState *estate, Relation relation, int lockmode, - LockWaitPolicy wait_policy, - ItemPointer tid, TransactionId priorXmax) -{ - TableTuple tuple = NULL; - SnapshotData SnapshotDirty; - tuple_data t_data; - - /* - * fetch target tuple - * - * Loop here to deal with updated or busy tuples - */ - InitDirtySnapshot(SnapshotDirty); - for (;;) - { - Buffer buffer; - ItemPointerData ctid; - - if (table_fetch(relation, tid, &SnapshotDirty, &tuple, &buffer, true, NULL)) - { - HTSU_Result test; - HeapUpdateFailureData hufd; - - /* - * If xmin isn't what we're expecting, the slot must have been - * recycled and reused for an unrelated tuple. This implies that - * the latest version of the row was deleted, so we need do - * nothing. (Should be safe to examine xmin without getting - * buffer's content lock. We assume reading a TransactionId to be - * atomic, and Xmin never changes in an existing tuple, except to - * invalid or frozen, and neither of those can match priorXmax.) - */ - if (!TransactionIdEquals(HeapTupleHeaderGetXmin(((HeapTuple) tuple)->t_data), - priorXmax)) - { - ReleaseBuffer(buffer); - return NULL; - } - - /* otherwise xmin should not be dirty... */ - if (TransactionIdIsValid(SnapshotDirty.xmin)) - elog(ERROR, "t_xmin is uncommitted in tuple to be updated"); - - /* - * If tuple is being updated by other transaction then we have to - * wait for its commit/abort, or die trying. - */ - if (TransactionIdIsValid(SnapshotDirty.xmax)) - { - ReleaseBuffer(buffer); - switch (wait_policy) - { - case LockWaitBlock: - XactLockTableWait(SnapshotDirty.xmax, - relation, - tid, - XLTW_FetchUpdated); - break; - case LockWaitSkip: - if (!ConditionalXactLockTableWait(SnapshotDirty.xmax)) - return NULL; /* skip instead of waiting */ - break; - case LockWaitError: - if (!ConditionalXactLockTableWait(SnapshotDirty.xmax)) - ereport(ERROR, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("could not obtain lock on row in relation \"%s\"", - RelationGetRelationName(relation)))); - break; - } - continue; /* loop back to repeat heap_fetch */ - } - - /* - * If tuple was inserted by our own transaction, we have to check - * cmin against es_output_cid: cmin >= current CID means our - * command cannot see the tuple, so we should ignore it. Otherwise - * heap_lock_tuple() will throw an error, and so would any later - * attempt to update or delete the tuple. (We need not check cmax - * because HeapTupleSatisfiesDirty will consider a tuple deleted - * by our transaction dead, regardless of cmax.) We just checked - * that priorXmax == xmin, so we can test that variable instead of - * doing HeapTupleHeaderGetXmin again. - */ - if (TransactionIdIsCurrentTransactionId(priorXmax)) - { - t_data = table_tuple_get_data(relation, tuple, CMIN); - if (t_data.cid >= estate->es_output_cid) - { - ReleaseBuffer(buffer); - return NULL; - } - } - - /* - * This is a live tuple, so now try to lock it. - */ - test = table_lock_tuple(relation, tid, &tuple, - estate->es_output_cid, - lockmode, wait_policy, - false, &buffer, &hufd); - /* We now have two pins on the buffer, get rid of one */ - ReleaseBuffer(buffer); - - switch (test) - { - case HeapTupleSelfUpdated: - - /* - * The target tuple was already updated or deleted by the - * current command, or by a later command in the current - * transaction. We *must* ignore the tuple in the former - * case, so as to avoid the "Halloween problem" of - * repeated update attempts. In the latter case it might - * be sensible to fetch the updated tuple instead, but - * doing so would require changing heap_update and - * heap_delete to not complain about updating "invisible" - * tuples, which seems pretty scary (heap_lock_tuple will - * not complain, but few callers expect - * HeapTupleInvisible, and we're not one of them). So for - * now, treat the tuple as deleted and do not process. - */ - ReleaseBuffer(buffer); - return NULL; - - case HeapTupleMayBeUpdated: - /* successfully locked */ - break; - - case HeapTupleUpdated: - ReleaseBuffer(buffer); - if (IsolationUsesXactSnapshot()) - ereport(ERROR, - (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), - errmsg("could not serialize access due to concurrent update"))); - if (ItemPointerIndicatesMovedPartitions(&hufd.ctid)) - ereport(ERROR, - (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), - errmsg("tuple to be locked was already moved to another partition due to concurrent update"))); - -#if 0 //hari - /* Should not encounter speculative tuple on recheck */ - Assert(!HeapTupleHeaderIsSpeculative(tuple.t_data)); -#endif - t_data = table_tuple_get_data(relation, tuple, TID); - if (!ItemPointerEquals(&hufd.ctid, &t_data.tid)) - { - /* it was updated, so look at the updated version */ - *tid = hufd.ctid; - /* updated row should have xmin matching this xmax */ - priorXmax = hufd.xmax; - continue; - } - /* tuple was deleted, so give up */ - return NULL; - - case HeapTupleWouldBlock: - ReleaseBuffer(buffer); - return NULL; - - case HeapTupleInvisible: - elog(ERROR, "attempted to lock invisible tuple"); - break; - - default: - ReleaseBuffer(buffer); - elog(ERROR, "unrecognized heap_lock_tuple status: %u", - test); - return NULL; /* keep compiler quiet */ - } - - ReleaseBuffer(buffer); - break; - } - - /* - * If the referenced slot was actually empty, the latest version of - * the row must have been deleted, so we need do nothing. - */ - if (tuple == NULL) - { - ReleaseBuffer(buffer); - return NULL; - } - - /* - * As above, if xmin isn't what we're expecting, do nothing. - */ - if (!TransactionIdEquals(HeapTupleHeaderGetXmin(((HeapTuple) tuple)->t_data), - priorXmax)) - { - ReleaseBuffer(buffer); - return NULL; - } - - /* - * If we get here, the tuple was found but failed SnapshotDirty. - * Assuming the xmin is either a committed xact or our own xact (as it - * certainly should be if we're trying to modify the tuple), this must - * mean that the row was updated or deleted by either a committed xact - * or our own xact. If it was deleted, we can ignore it; if it was - * updated then chain up to the next version and repeat the whole - * process. - * - * As above, it should be safe to examine xmax and t_ctid without the - * buffer content lock, because they can't be changing. - */ - - /* check whether next version would be in a different partition */ - /* hari: FIXME: use a new table API */ - if (HeapTupleHeaderIndicatesMovedPartitions(((HeapTuple) tuple)->t_data)) - ereport(ERROR, - (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), - errmsg("tuple to be locked was already moved to another partition due to concurrent update"))); - - t_data = table_tuple_get_data(relation, tuple, CTID); - ctid = t_data.tid; - - /* check whether tuple has been deleted */ - if (ItemPointerEquals(tid, &ctid)) - { - /* deleted, so forget about it */ - ReleaseBuffer(buffer); - return NULL; - } - - /* updated, so look at the updated row */ - *tid = ctid; - - /* updated row should have xmin matching this xmax */ - t_data = table_tuple_get_data(relation, tuple, UPDATED_XID); - priorXmax = t_data.xid; - ReleaseBuffer(buffer); - /* loop back to fetch next in chain */ - } - - /* - * Return the tuple - */ - return tuple; -} - /* * EvalPlanQualInit -- initialize during creation of a plan state node * that might need to invoke EPQ processing. diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c index b4fa287933..3297fd7392 100644 --- a/src/backend/executor/execReplication.c +++ b/src/backend/executor/execReplication.c @@ -167,21 +167,19 @@ retry: /* Found tuple, try to lock it in the lockmode. */ if (found) { - Buffer buf; HeapUpdateFailureData hufd; HTSU_Result res; TableTuple locktup; PushActiveSnapshot(GetLatestSnapshot()); - res = table_lock_tuple(rel, &(outslot->tts_tid), &locktup, GetCurrentCommandId(false), + res = table_lock_tuple(rel, &(outslot->tts_tid), GetLatestSnapshot(), + &locktup, + GetCurrentCommandId(false), lockmode, LockWaitBlock, - false /* don't follow updates */ , - &buf, &hufd); - /* the tuple slot already has the buffer pinned */ - if (BufferIsValid(buf)) - ReleaseBuffer(buf); + 0 /* don't follow updates */ , + &hufd); pfree(locktup); PopActiveSnapshot(); @@ -201,6 +199,12 @@ retry: (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("concurrent update, retrying"))); goto retry; + case HeapTupleDeleted: + /* XXX: Improve handling here */ + ereport(LOG, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("concurrent delete, retrying"))); + goto retry; case HeapTupleInvisible: elog(ERROR, "attempted to lock invisible tuple"); break; @@ -280,21 +284,19 @@ retry: /* Found tuple, try to lock it in the lockmode. */ if (found) { - Buffer buf; HeapUpdateFailureData hufd; HTSU_Result res; TableTuple locktup; PushActiveSnapshot(GetLatestSnapshot()); - res = table_lock_tuple(rel, &(outslot->tts_tid), &locktup, GetCurrentCommandId(false), + res = table_lock_tuple(rel, &(outslot->tts_tid), GetLatestSnapshot(), + &locktup, + GetCurrentCommandId(false), lockmode, LockWaitBlock, - false /* don't follow updates */ , - &buf, &hufd); - /* the tuple slot already has the buffer pinned */ - if (BufferIsValid(buf)) - ReleaseBuffer(buf); + 0 /* don't follow updates */ , + &hufd); pfree(locktup); @@ -315,6 +317,12 @@ retry: (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("concurrent update, retrying"))); goto retry; + case HeapTupleDeleted: + /* XXX: Improve handling here */ + ereport(LOG, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("concurrent delete, retrying"))); + goto retry; case HeapTupleInvisible: elog(ERROR, "attempted to lock invisible tuple"); break; diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c index 5c75e46547..26296f3355 100644 --- a/src/backend/executor/nodeLockRows.c +++ b/src/backend/executor/nodeLockRows.c @@ -79,13 +79,11 @@ lnext: Datum datum; bool isNull; TableTuple tuple; - Buffer buffer; HeapUpdateFailureData hufd; LockTupleMode lockmode; HTSU_Result test; TableTuple copyTuple; ItemPointerData tid; - tuple_data t_data; /* clear any leftover test tuple for this rel */ testTuple = (TableTuple) (&(node->lr_curtuples[erm->rti - 1])); @@ -183,12 +181,12 @@ lnext: break; } - test = table_lock_tuple(erm->relation, &tid, &tuple, - estate->es_output_cid, - lockmode, erm->waitPolicy, true, - &buffer, &hufd); - if (BufferIsValid(buffer)) - ReleaseBuffer(buffer); + test = table_lock_tuple(erm->relation, &tid, estate->es_snapshot, + &tuple, estate->es_output_cid, + lockmode, erm->waitPolicy, + (IsolationUsesXactSnapshot() ? 0 : TUPLE_LOCK_FLAG_FIND_LAST_VERSION) + | TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS, + &hufd); switch (test) { @@ -216,6 +214,16 @@ lnext: case HeapTupleMayBeUpdated: /* got the lock successfully */ + if (hufd.traversed) + { + /* Save locked tuple for EvalPlanQual testing below */ + *testTuple = tuple; + + /* Remember we need to do EPQ testing */ + epq_needed = true; + + /* Continue loop until we have all target tuples */ + } break; case HeapTupleUpdated: @@ -223,43 +231,19 @@ lnext: ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("could not serialize access due to concurrent update"))); - if (ItemPointerIndicatesMovedPartitions(&hufd.ctid)) + /* skip lock */ + goto lnext; + + case HeapTupleDeleted: + if (IsolationUsesXactSnapshot()) ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), - errmsg("tuple to be locked was already moved to another partition due to concurrent update"))); - - t_data = erm->relation->rd_tableamroutine->get_tuple_data(tuple, TID); - if (ItemPointerEquals(&hufd.ctid, &(t_data.tid))) - { - /* Tuple was deleted, so don't return it */ - goto lnext; - } - - /* updated, so fetch and lock the updated version */ - copyTuple = EvalPlanQualFetch(estate, erm->relation, - lockmode, erm->waitPolicy, - &hufd.ctid, hufd.xmax); - - if (copyTuple == NULL) - { - /* - * Tuple was deleted; or it's locked and we're under SKIP - * LOCKED policy, so don't return it - */ - goto lnext; - } - /* remember the actually locked tuple's TID */ - t_data = erm->relation->rd_tableamroutine->get_tuple_data(copyTuple, TID); - tid = t_data.tid; - - /* Save locked tuple for EvalPlanQual testing below */ - *testTuple = copyTuple; - - /* Remember we need to do EPQ testing */ - epq_needed = true; - - /* Continue loop until we have all target tuples */ - break; + errmsg("could not serialize access due to concurrent update"))); + /* + * Tuple was deleted; or it's locked and we're under SKIP + * LOCKED policy, so don't return it + */ + goto lnext; case HeapTupleInvisible: elog(ERROR, "attempted to lock invisible tuple"); diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 99347845da..9560a2fa51 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -211,7 +211,8 @@ ExecCheckHeapTupleVisible(EState *estate, * We need buffer pin and lock to call HeapTupleSatisfiesVisibility. * Caller should be holding pin, but not lock. */ - LockBuffer(buffer, BUFFER_LOCK_SHARE); + if (BufferIsValid(buffer)) + LockBuffer(buffer, BUFFER_LOCK_SHARE); if (!HeapTupleSatisfiesVisibility(rel->rd_tableamroutine, tuple, estate->es_snapshot, buffer)) { tuple_data t_data = table_tuple_get_data(rel, tuple, XMIN); @@ -227,7 +228,8 @@ ExecCheckHeapTupleVisible(EState *estate, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("could not serialize access due to concurrent update"))); } - LockBuffer(buffer, BUFFER_LOCK_UNLOCK); + if (BufferIsValid(buffer)) + LockBuffer(buffer, BUFFER_LOCK_UNLOCK); } /* @@ -617,6 +619,7 @@ ExecDelete(ModifyTableState *mtstate, HeapUpdateFailureData hufd; TupleTableSlot *slot = NULL; TransitionCaptureState *ar_delete_trig_tcs; + TableTuple tuple; if (tupleDeleted) *tupleDeleted = false; @@ -703,6 +706,35 @@ ldelete:; NULL, &hufd, changingPart); + + if (result == HeapTupleUpdated && !IsolationUsesXactSnapshot()) + { + result = table_lock_tuple(resultRelationDesc, tupleid, + estate->es_snapshot, + &tuple, estate->es_output_cid, + LockTupleExclusive, LockWaitBlock, + TUPLE_LOCK_FLAG_FIND_LAST_VERSION, + &hufd); + /*hari FIXME*/ + /*Assert(result != HeapTupleUpdated && hufd.traversed);*/ + if (result == HeapTupleMayBeUpdated) + { + TupleTableSlot *epqslot; + + epqslot = EvalPlanQual(estate, + epqstate, + resultRelationDesc, + resultRelInfo->ri_RangeTableIndex, + tuple); + if (TupIsNull(epqslot)) + { + /* Tuple no more passing quals, exiting... */ + return NULL; + } + goto ldelete; + } + } + switch (result) { case HeapTupleSelfUpdated: @@ -748,28 +780,16 @@ ldelete:; ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("could not serialize access due to concurrent update"))); - if (ItemPointerIndicatesMovedPartitions(&hufd.ctid)) + else + /* shouldn't get there */ + elog(ERROR, "wrong heap_delete status: %u", result); + break; + + case HeapTupleDeleted: + if (IsolationUsesXactSnapshot()) ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), - errmsg("tuple to be deleted was already moved to another partition due to concurrent update"))); - - if (!ItemPointerEquals(tupleid, &hufd.ctid)) - { - TupleTableSlot *epqslot; - - epqslot = EvalPlanQual(estate, - epqstate, - resultRelationDesc, - resultRelInfo->ri_RangeTableIndex, - LockTupleExclusive, - &hufd.ctid, - hufd.xmax); - if (!TupIsNull(epqslot)) - { - *tupleid = hufd.ctid; - goto ldelete; - } - } + errmsg("could not serialize access due to concurrent delete"))); /* tuple already deleted; nothing to do */ return NULL; @@ -1166,6 +1186,37 @@ lreplace:; &hufd, &lockmode, ExecInsertIndexTuples, &recheckIndexes); + + if (result == HeapTupleUpdated && !IsolationUsesXactSnapshot()) + { + result = table_lock_tuple(resultRelationDesc, tupleid, + estate->es_snapshot, + &tuple, estate->es_output_cid, + lockmode, LockWaitBlock, + TUPLE_LOCK_FLAG_FIND_LAST_VERSION, + &hufd); + /* hari FIXME*/ + /*Assert(result != HeapTupleUpdated && hufd.traversed);*/ + if (result == HeapTupleMayBeUpdated) + { + TupleTableSlot *epqslot; + + epqslot = EvalPlanQual(estate, + epqstate, + resultRelationDesc, + resultRelInfo->ri_RangeTableIndex, + tuple); + if (TupIsNull(epqslot)) + { + /* Tuple no more passing quals, exiting... */ + return NULL; + } + slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot); + tuple = ExecHeapifySlot(slot); + goto lreplace; + } + } + switch (result) { case HeapTupleSelfUpdated: @@ -1210,30 +1261,16 @@ lreplace:; ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("could not serialize access due to concurrent update"))); - if (ItemPointerIndicatesMovedPartitions(&hufd.ctid)) + else + /* shouldn't get there */ + elog(ERROR, "wrong heap_delete status: %u", result); + break; + + case HeapTupleDeleted: + if (IsolationUsesXactSnapshot()) ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), - errmsg("tuple to be updated was already moved to another partition due to concurrent update"))); - - if (!ItemPointerEquals(tupleid, &hufd.ctid)) - { - TupleTableSlot *epqslot; - - epqslot = EvalPlanQual(estate, - epqstate, - resultRelationDesc, - resultRelInfo->ri_RangeTableIndex, - lockmode, - &hufd.ctid, - hufd.xmax); - if (!TupIsNull(epqslot)) - { - *tupleid = hufd.ctid; - slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot); - tuple = ExecHeapifySlot(slot); - goto lreplace; - } - } + errmsg("could not serialize access due to concurrent delete"))); /* tuple already deleted; nothing to do */ return NULL; @@ -1302,8 +1339,8 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, HeapUpdateFailureData hufd; LockTupleMode lockmode; HTSU_Result test; - Buffer buffer; tuple_data t_data; + SnapshotData snapshot; /* Determine lock mode to use */ lockmode = ExecUpdateLockMode(estate, resultRelInfo); @@ -1314,8 +1351,12 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, * previous conclusion that the tuple is conclusively committed is not * true anymore. */ - test = table_lock_tuple(relation, conflictTid, &tuple, estate->es_output_cid, - lockmode, LockWaitBlock, false, &buffer, &hufd); + InitDirtySnapshot(snapshot); + test = table_lock_tuple(relation, conflictTid, + &snapshot, + /*estate->es_snapshot,*/ + &tuple, estate->es_output_cid, + lockmode, LockWaitBlock, 0, &hufd); switch (test) { case HeapTupleMayBeUpdated: @@ -1382,8 +1423,15 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, * loop here, as the new version of the row might not conflict * anymore, or the conflicting tuple has actually been deleted. */ - if (BufferIsValid(buffer)) - ReleaseBuffer(buffer); + pfree(tuple); + return false; + + case HeapTupleDeleted: + if (IsolationUsesXactSnapshot()) + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to concurrent delete"))); + pfree(tuple); return false; @@ -1412,10 +1460,10 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, * snapshot. This is in line with the way UPDATE deals with newer tuple * versions. */ - ExecCheckHeapTupleVisible(estate, relation, tuple, buffer); + ExecCheckHeapTupleVisible(estate, relation, tuple, InvalidBuffer); /* Store target's existing tuple in the state's dedicated slot */ - ExecStoreTuple(tuple, mtstate->mt_existing, buffer, false); + ExecStoreTuple(tuple, mtstate->mt_existing, InvalidBuffer, false); /* * Make tuple and any needed join variables available to ExecQual and @@ -1430,8 +1478,6 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, if (!ExecQual(onConflictSetWhere, econtext)) { - if (BufferIsValid(buffer)) - ReleaseBuffer(buffer); pfree(tuple); InstrCountFiltered1(&mtstate->ps, 1); return true; /* done with the tuple */ @@ -1477,8 +1523,6 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, &mtstate->mt_epqstate, mtstate->ps.state, canSetTag); - if (BufferIsValid(buffer)) - ReleaseBuffer(buffer); pfree(tuple); return true; } diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 13658f9e93..26d882eb00 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -69,6 +69,7 @@ typedef struct HeapUpdateFailureData ItemPointerData ctid; TransactionId xmax; CommandId cmax; + bool traversed; } HeapUpdateFailureData; diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index 77d08eae14..7227f834a5 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -87,10 +87,10 @@ extern bool table_hot_search_buffer(ItemPointer tid, Relation relation, Buffer b extern bool table_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot, bool *all_dead); -extern HTSU_Result table_lock_tuple(Relation relation, ItemPointer tid, TableTuple * stuple, - CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy, - bool follow_updates, - Buffer *buffer, HeapUpdateFailureData *hufd); +extern HTSU_Result table_lock_tuple(Relation relation, ItemPointer tid, Snapshot snapshot, + TableTuple *stuple, CommandId cid, LockTupleMode mode, + LockWaitPolicy wait_policy, uint8 flags, + HeapUpdateFailureData *hufd); extern Oid table_insert(Relation relation, TupleTableSlot *slot, CommandId cid, int options, BulkInsertState bistate, InsertIndexTuples IndexFunc, diff --git a/src/include/access/tableamapi.h b/src/include/access/tableamapi.h index f6fae31449..4897f1407e 100644 --- a/src/include/access/tableamapi.h +++ b/src/include/access/tableamapi.h @@ -61,12 +61,12 @@ typedef bool (*TupleFetch_function) (Relation relation, typedef HTSU_Result (*TupleLock_function) (Relation relation, ItemPointer tid, + Snapshot snapshot, TableTuple * tuple, CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy, - bool follow_update, - Buffer *buffer, + uint8 flags, HeapUpdateFailureData *hufd); typedef void (*MultiInsert_function) (Relation relation, HeapTuple *tuples, int ntuples, diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 406572771b..964f3e8fd8 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -191,11 +191,7 @@ extern LockTupleMode ExecUpdateLockMode(EState *estate, ResultRelInfo *relinfo); extern ExecRowMark *ExecFindRowMark(EState *estate, Index rti, bool missing_ok); extern ExecAuxRowMark *ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist); extern TupleTableSlot *EvalPlanQual(EState *estate, EPQState *epqstate, - Relation relation, Index rti, int lockmode, - ItemPointer tid, TransactionId priorXmax); -extern TableTuple EvalPlanQualFetch(EState *estate, Relation relation, - int lockmode, LockWaitPolicy wait_policy, ItemPointer tid, - TransactionId priorXmax); + Relation relation, Index rti, TableTuple tuple); extern void EvalPlanQualInit(EPQState *epqstate, EState *estate, Plan *subplan, List *auxrowmarks, int epqParam); extern void EvalPlanQualSetPlan(EPQState *epqstate, diff --git a/src/include/nodes/lockoptions.h b/src/include/nodes/lockoptions.h index 24afd6efd4..bcde234614 100644 --- a/src/include/nodes/lockoptions.h +++ b/src/include/nodes/lockoptions.h @@ -43,4 +43,9 @@ typedef enum LockWaitPolicy LockWaitError } LockWaitPolicy; +/* Follow tuples whose update is in progress if lock modes don't conflict */ +#define TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS 0x01 +/* Follow update chain and lock lastest version of tuple */ +#define TUPLE_LOCK_FLAG_FIND_LAST_VERSION 0x02 + #endif /* LOCKOPTIONS_H */ diff --git a/src/include/utils/snapshot.h b/src/include/utils/snapshot.h index ca96fd00fa..95a91db03c 100644 --- a/src/include/utils/snapshot.h +++ b/src/include/utils/snapshot.h @@ -136,6 +136,7 @@ typedef enum HeapTupleInvisible, HeapTupleSelfUpdated, HeapTupleUpdated, + HeapTupleDeleted, HeapTupleBeingUpdated, HeapTupleWouldBlock /* can be returned by heap_tuple_lock */ } HTSU_Result; diff --git a/src/test/isolation/expected/partition-key-update-1.out b/src/test/isolation/expected/partition-key-update-1.out index 37fe6a7b27..a632d7f7ba 100644 --- a/src/test/isolation/expected/partition-key-update-1.out +++ b/src/test/isolation/expected/partition-key-update-1.out @@ -15,7 +15,7 @@ step s1u: UPDATE foo SET a=2 WHERE a=1; step s2d: DELETE FROM foo WHERE a=1; step s1c: COMMIT; step s2d: <... completed> -error in steps s1c s2d: ERROR: tuple to be deleted was already moved to another partition due to concurrent update +error in steps s1c s2d: ERROR: tuple to be locked was already moved to another partition due to concurrent update step s2c: COMMIT; starting permutation: s1b s2b s2d s1u s2c s1c -- 2.16.1.windows.4