From: Andres Freund Date: Thu, 7 Mar 2019 16:23:34 -0800 Subject: [PATCH v20 2/2] tableam: Add insert, delete, update, lock_tuple. Author: Reviewed-By: Discussion: https://postgr.es/m/ Backpatch: --- contrib/pgrowlocks/pgrowlocks.c | 6 +- src/backend/access/heap/heapam.c | 395 +++++++--------- src/backend/access/heap/heapam_handler.c | 324 +++++++++++++ src/backend/access/heap/heapam_visibility.c | 105 +++-- src/backend/access/heap/tuptoaster.c | 2 +- src/backend/access/table/tableam.c | 113 +++++ src/backend/access/table/tableamapi.c | 13 + src/backend/commands/copy.c | 3 +- src/backend/commands/trigger.c | 109 ++--- src/backend/executor/execIndexing.c | 4 +- src/backend/executor/execMain.c | 289 +----------- src/backend/executor/execReplication.c | 137 +++--- src/backend/executor/nodeLockRows.c | 137 ++---- src/backend/executor/nodeModifyTable.c | 424 ++++++++++-------- src/backend/executor/nodeTidscan.c | 2 +- src/include/access/heapam.h | 58 +-- src/include/access/tableam.h | 347 ++++++++++++++ src/include/executor/executor.h | 12 +- src/include/nodes/lockoptions.h | 5 + src/include/utils/snapshot.h | 13 - .../expected/partition-key-update-1.out | 2 +- src/tools/pgindent/typedefs.list | 4 +- 22 files changed, 1465 insertions(+), 1039 deletions(-) diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c index 2d2a6cf1533..54514309091 100644 --- a/contrib/pgrowlocks/pgrowlocks.c +++ b/contrib/pgrowlocks/pgrowlocks.c @@ -146,7 +146,7 @@ pgrowlocks(PG_FUNCTION_ARGS) /* scan the relation */ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) { - HTSU_Result htsu; + TM_Result htsu; TransactionId xmax; uint16 infomask; @@ -160,9 +160,9 @@ pgrowlocks(PG_FUNCTION_ARGS) infomask = tuple->t_data->t_infomask; /* - * A tuple is locked if HTSU returns BeingUpdated. + * A tuple is locked if HTSU returns BeingModified. */ - if (htsu == HeapTupleBeingUpdated) + if (htsu == TableTupleBeingModified) { char **values; diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 3c8a5da0bc8..698faa4a83e 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -86,7 +86,7 @@ static void compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask, LockTupleMode mode, bool is_update, TransactionId *result_xmax, uint16 *result_infomask, uint16 *result_infomask2); -static HTSU_Result heap_lock_updated_tuple(Relation rel, HeapTuple tuple, +static TM_Result heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid, TransactionId xid, LockTupleMode mode); static void GetMultiXactIdHintBits(MultiXactId multi, uint16 *new_infomask, @@ -1389,7 +1389,6 @@ heap_fetch(Relation relation, Snapshot snapshot, HeapTuple tuple, Buffer *userbuf, - bool keep_buf, Relation stats_relation) { ItemPointer tid = &(tuple->t_self); @@ -1419,13 +1418,8 @@ heap_fetch(Relation relation, if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page)) { LockBuffer(buffer, BUFFER_LOCK_UNLOCK); - if (keep_buf) - *userbuf = buffer; - else - { - ReleaseBuffer(buffer); - *userbuf = InvalidBuffer; - } + ReleaseBuffer(buffer); + *userbuf = InvalidBuffer; tuple->t_data = NULL; return false; } @@ -1441,13 +1435,8 @@ heap_fetch(Relation relation, if (!ItemIdIsNormal(lp)) { LockBuffer(buffer, BUFFER_LOCK_UNLOCK); - if (keep_buf) - *userbuf = buffer; - else - { - ReleaseBuffer(buffer); - *userbuf = InvalidBuffer; - } + ReleaseBuffer(buffer); + *userbuf = InvalidBuffer; tuple->t_data = NULL; return false; } @@ -1486,14 +1475,9 @@ heap_fetch(Relation relation, return true; } - /* Tuple failed time qual, but maybe caller wants to see it anyway. */ - if (keep_buf) - *userbuf = buffer; - else - { - ReleaseBuffer(buffer); - *userbuf = InvalidBuffer; - } + /* Tuple failed time qual */ + ReleaseBuffer(buffer); + *userbuf = InvalidBuffer; return false; } @@ -1886,40 +1870,12 @@ ReleaseBulkInsertStatePin(BulkInsertState bistate) * The new tuple is stamped with current transaction ID and the specified * command ID. * - * If the HEAP_INSERT_SKIP_WAL option is specified, the new tuple is not - * logged in WAL, even for a non-temp relation. Safe usage of this behavior - * requires that we arrange that all new tuples go into new pages not - * containing any tuples from other transactions, and that the relation gets - * fsync'd before commit. (See also heap_sync() comments) + * See table_insert for comments about most of the input flags, except that + * this routine directly takes a tuple rather than a slot. * - * The HEAP_INSERT_SKIP_FSM option is passed directly to - * RelationGetBufferForTuple, which see for more info. - * - * HEAP_INSERT_FROZEN should only be specified for inserts into - * relfilenodes created during the current subtransaction and when - * there are no prior snapshots or pre-existing portals open. - * This causes rows to be frozen, which is an MVCC violation and - * requires explicit options chosen by user. - * - * HEAP_INSERT_SPECULATIVE is used on so-called "speculative insertions", - * which can be backed out afterwards without aborting the whole transaction. - * Other sessions can wait for the speculative insertion to be confirmed, - * turning it into a regular tuple, or aborted, as if it never existed. - * Speculatively inserted tuples behave as "value locks" of short duration, - * used to implement INSERT .. ON CONFLICT. - * - * HEAP_INSERT_NO_LOGICAL force-disables the emitting of logical decoding - * information for the tuple. This should solely be used during table rewrites - * where RelationIsLogicallyLogged(relation) is not yet accurate for the new - * relation. - * - * Note that most of these options will be applied when inserting into the - * heap's TOAST table, too, if the tuple requires any out-of-line data. Only - * HEAP_INSERT_SPECULATIVE is explicitly ignored, as the toast data does not - * partake in speculative insertion. - * - * The BulkInsertState object (if any; bistate can be NULL for default - * behavior) is also just passed through to RelationGetBufferForTuple. + * There's corresponding HEAP_INSERT_ options to all the TABLE_INSERT_ + * options, and there additionally is HEAP_INSERT_SPECULATIVE which is used to + * implement table_insert_speculative(). * * On return the header fields of *tup are updated to match the stored tuple; * in particular tup->t_self receives the actual TID where the tuple was @@ -2489,36 +2445,20 @@ xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask) /* * heap_delete - delete a tuple * - * NB: do not call this directly unless you are prepared to deal with - * concurrent-update conditions. Use simple_heap_delete instead. + * See table_delete() for an explanation of the parameters. * - * relation - table to be modified (caller must hold suitable lock) - * tid - TID of tuple to be deleted - * cid - delete command ID (used for visibility test, and stored into - * cmax if successful) - * crosscheck - if not InvalidSnapshot, also check tuple against this - * wait - true if should wait for any conflicting update to commit/abort - * hufd - output parameter, filled in failure cases (see below) - * changingPart - true iff the tuple is being moved to another partition - * table due to an update of the partition key. Otherwise, false. * - * Normal, successful return value is HeapTupleMayBeUpdated, which - * actually means we did delete it. Failure return codes are - * HeapTupleSelfUpdated, HeapTupleUpdated, or HeapTupleBeingUpdated - * (the last only possible if wait == false). - * - * In the failure cases, the routine fills *hufd with the tuple's t_ctid, + * In the failure cases, the routine fills *tmfd with the tuple's t_ctid, * t_xmax (resolving a possible MultiXact, if necessary), and t_cmax - * (the last only for HeapTupleSelfUpdated, since we + * (the last only for TableTupleSelfModified, since we * cannot obtain cmax from a combocid generated by another transaction). - * See comments for struct HeapUpdateFailureData for additional info. */ -HTSU_Result +TM_Result heap_delete(Relation relation, ItemPointer tid, CommandId cid, Snapshot crosscheck, bool wait, - HeapUpdateFailureData *hufd, bool changingPart) + TM_FailureData *tmfd, bool changingPart) { - HTSU_Result result; + TM_Result result; TransactionId xid = GetCurrentTransactionId(); ItemId lp; HeapTupleData tp; @@ -2586,14 +2526,14 @@ heap_delete(Relation relation, ItemPointer tid, l1: result = HeapTupleSatisfiesUpdate(&tp, cid, buffer); - if (result == HeapTupleInvisible) + if (result == TableTupleInvisible) { UnlockReleaseBuffer(buffer); ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("attempted to delete invisible tuple"))); } - else if (result == HeapTupleBeingUpdated && wait) + else if (result == TableTupleBeingModified && wait) { TransactionId xwait; uint16 infomask; @@ -2687,35 +2627,38 @@ l1: if ((tp.t_data->t_infomask & HEAP_XMAX_INVALID) || HEAP_XMAX_IS_LOCKED_ONLY(tp.t_data->t_infomask) || HeapTupleHeaderIsOnlyLocked(tp.t_data)) - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; else - result = HeapTupleUpdated; + result = TableTupleUpdated; } - if (crosscheck != InvalidSnapshot && result == HeapTupleMayBeUpdated) + if (crosscheck != InvalidSnapshot && result == TableTupleMayBeModified) { /* Perform additional check for transaction-snapshot mode RI updates */ if (!HeapTupleSatisfiesVisibility(&tp, crosscheck, buffer)) - result = HeapTupleUpdated; + result = TableTupleUpdated; } - if (result != HeapTupleMayBeUpdated) + if (result != TableTupleMayBeModified) { - Assert(result == HeapTupleSelfUpdated || - result == HeapTupleUpdated || - result == HeapTupleBeingUpdated); + Assert(result == TableTupleSelfModified || + result == TableTupleUpdated || + result == TableTupleDeleted || + result == TableTupleBeingModified); Assert(!(tp.t_data->t_infomask & HEAP_XMAX_INVALID)); - hufd->ctid = tp.t_data->t_ctid; - hufd->xmax = HeapTupleHeaderGetUpdateXid(tp.t_data); - if (result == HeapTupleSelfUpdated) - hufd->cmax = HeapTupleHeaderGetCmax(tp.t_data); + tmfd->ctid = tp.t_data->t_ctid; + tmfd->xmax = HeapTupleHeaderGetUpdateXid(tp.t_data); + if (result == TableTupleSelfModified) + tmfd->cmax = HeapTupleHeaderGetCmax(tp.t_data); else - hufd->cmax = InvalidCommandId; + tmfd->cmax = InvalidCommandId; UnlockReleaseBuffer(buffer); if (have_tuple_lock) UnlockTupleTuplock(relation, &(tp.t_self), LockTupleExclusive); if (vmbuffer != InvalidBuffer) ReleaseBuffer(vmbuffer); + if (result == TableTupleUpdated && ItemPointerEquals(tid, &tmfd->ctid)) + result = TableTupleDeleted; return result; } @@ -2896,7 +2839,7 @@ l1: if (old_key_tuple != NULL && old_key_copied) heap_freetuple(old_key_tuple); - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; } /* @@ -2910,28 +2853,32 @@ l1: void simple_heap_delete(Relation relation, ItemPointer tid) { - HTSU_Result result; - HeapUpdateFailureData hufd; + TM_Result result; + TM_FailureData tmfd; result = heap_delete(relation, tid, GetCurrentCommandId(true), InvalidSnapshot, true /* wait for commit */ , - &hufd, false /* changingPart */ ); + &tmfd, false /* changingPart */ ); switch (result) { - case HeapTupleSelfUpdated: + case TableTupleSelfModified: /* Tuple was already updated in current command? */ elog(ERROR, "tuple already updated by self"); break; - case HeapTupleMayBeUpdated: + case TableTupleMayBeModified: /* done successfully */ break; - case HeapTupleUpdated: + case TableTupleUpdated: elog(ERROR, "tuple concurrently updated"); break; + case TableTupleDeleted: + elog(ERROR, "tuple concurrently deleted"); + break; + default: elog(ERROR, "unrecognized heap_delete status: %u", result); break; @@ -2941,42 +2888,19 @@ simple_heap_delete(Relation relation, ItemPointer tid) /* * heap_update - replace a tuple * - * NB: do not call this directly unless you are prepared to deal with - * concurrent-update conditions. Use simple_heap_update instead. + * See table_update() for an explanation of the parameters. * - * relation - table to be modified (caller must hold suitable lock) - * otid - TID of old tuple to be replaced - * newtup - newly constructed tuple data to store - * cid - update command ID (used for visibility test, and stored into - * cmax/cmin if successful) - * crosscheck - if not InvalidSnapshot, also check old tuple against this - * wait - true if should wait for any conflicting update to commit/abort - * hufd - output parameter, filled in failure cases (see below) - * lockmode - output parameter, filled with lock mode acquired on tuple - * - * Normal, successful return value is HeapTupleMayBeUpdated, which - * actually means we *did* update it. Failure return codes are - * HeapTupleSelfUpdated, HeapTupleUpdated, or HeapTupleBeingUpdated - * (the last only possible if wait == false). - * - * On success, the header fields of *newtup are updated to match the new - * stored tuple; in particular, newtup->t_self is set to the TID where the - * new tuple was inserted, and its HEAP_ONLY_TUPLE flag is set iff a HOT - * update was done. However, any TOAST changes in the new tuple's - * data are not reflected into *newtup. - * - * In the failure cases, the routine fills *hufd with the tuple's t_ctid, - * t_xmax (resolving a possible MultiXact, if necessary), and t_cmax - * (the last only for HeapTupleSelfUpdated, since we - * cannot obtain cmax from a combocid generated by another transaction). - * See comments for struct HeapUpdateFailureData for additional info. + * In the failure cases, the routine fills *tmfd with the tuple's t_ctid, + * t_xmax (resolving a possible MultiXact, if necessary), and t_cmax (the last + * only for TableTupleSelfModified, since we cannot obtain cmax from a + * combocid generated by another transaction). */ -HTSU_Result +TM_Result heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait, - HeapUpdateFailureData *hufd, LockTupleMode *lockmode) + TM_FailureData *tmfd, LockTupleMode *lockmode) { - HTSU_Result result; + TM_Result result; TransactionId xid = GetCurrentTransactionId(); Bitmapset *hot_attrs; Bitmapset *key_attrs; @@ -3150,16 +3074,16 @@ l2: result = HeapTupleSatisfiesUpdate(&oldtup, cid, buffer); /* see below about the "no wait" case */ - Assert(result != HeapTupleBeingUpdated || wait); + Assert(result != TableTupleBeingModified || wait); - if (result == HeapTupleInvisible) + if (result == TableTupleInvisible) { UnlockReleaseBuffer(buffer); ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("attempted to update invisible tuple"))); } - else if (result == HeapTupleBeingUpdated && wait) + else if (result == TableTupleBeingModified && wait) { TransactionId xwait; uint16 infomask; @@ -3250,7 +3174,7 @@ l2: * MultiXact. In that case, we need to check whether it committed * or aborted. If it aborted we are safe to update it again; * otherwise there is an update conflict, and we have to return - * HeapTupleUpdated below. + * TableTupleUpdated below. * * In the LockTupleExclusive case, we still need to preserve the * surviving members: those would include the tuple locks we had @@ -3322,28 +3246,29 @@ l2: can_continue = true; } - result = can_continue ? HeapTupleMayBeUpdated : HeapTupleUpdated; + result = can_continue ? TableTupleMayBeModified : TableTupleUpdated; } - if (crosscheck != InvalidSnapshot && result == HeapTupleMayBeUpdated) + if (crosscheck != InvalidSnapshot && result == TableTupleMayBeModified) { /* Perform additional check for transaction-snapshot mode RI updates */ if (!HeapTupleSatisfiesVisibility(&oldtup, crosscheck, buffer)) - result = HeapTupleUpdated; + result = TableTupleUpdated; } - if (result != HeapTupleMayBeUpdated) + if (result != TableTupleMayBeModified) { - Assert(result == HeapTupleSelfUpdated || - result == HeapTupleUpdated || - result == HeapTupleBeingUpdated); + Assert(result == TableTupleSelfModified || + result == TableTupleUpdated || + result == TableTupleDeleted || + result == TableTupleBeingModified); Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID)); - hufd->ctid = oldtup.t_data->t_ctid; - hufd->xmax = HeapTupleHeaderGetUpdateXid(oldtup.t_data); - if (result == HeapTupleSelfUpdated) - hufd->cmax = HeapTupleHeaderGetCmax(oldtup.t_data); + tmfd->ctid = oldtup.t_data->t_ctid; + tmfd->xmax = HeapTupleHeaderGetUpdateXid(oldtup.t_data); + if (result == TableTupleSelfModified) + tmfd->cmax = HeapTupleHeaderGetCmax(oldtup.t_data); else - hufd->cmax = InvalidCommandId; + tmfd->cmax = InvalidCommandId; UnlockReleaseBuffer(buffer); if (have_tuple_lock) UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode); @@ -3354,6 +3279,9 @@ l2: bms_free(id_attrs); bms_free(modified_attrs); bms_free(interesting_attrs); + // FIXME, this needs to be implemented above + if (result == TableTupleUpdated && ItemPointerEquals(otid, &tmfd->ctid)) + result = TableTupleDeleted; return result; } @@ -3828,7 +3756,7 @@ l2: bms_free(modified_attrs); bms_free(interesting_attrs); - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; } /* @@ -3948,29 +3876,33 @@ HeapDetermineModifiedColumns(Relation relation, Bitmapset *interesting_cols, void simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup) { - HTSU_Result result; - HeapUpdateFailureData hufd; + TM_Result result; + TM_FailureData tmfd; LockTupleMode lockmode; result = heap_update(relation, otid, tup, GetCurrentCommandId(true), InvalidSnapshot, true /* wait for commit */ , - &hufd, &lockmode); + &tmfd, &lockmode); switch (result) { - case HeapTupleSelfUpdated: + case TableTupleSelfModified: /* Tuple was already updated in current command? */ elog(ERROR, "tuple already updated by self"); break; - case HeapTupleMayBeUpdated: + case TableTupleMayBeModified: /* done successfully */ break; - case HeapTupleUpdated: + case TableTupleUpdated: elog(ERROR, "tuple concurrently updated"); break; + case TableTupleDeleted: + elog(ERROR, "tuple concurrently deleted"); + break; + default: elog(ERROR, "unrecognized heap_update status: %u", result); break; @@ -4005,7 +3937,7 @@ get_mxact_status_for_lock(LockTupleMode mode, bool is_update) * * Input parameters: * relation: relation containing tuple (caller must hold suitable lock) - * tuple->t_self: TID of tuple to lock (rest of struct need not be valid) + * tid: TID of tuple to lock * cid: current command ID (used for visibility test, and stored into * tuple's cmax if lock is successful) * mode: indicates if shared or exclusive tuple lock is desired @@ -4016,32 +3948,26 @@ get_mxact_status_for_lock(LockTupleMode mode, bool is_update) * Output parameters: * *tuple: all fields filled in * *buffer: set to buffer holding tuple (pinned but not locked at exit) - * *hufd: filled in failure cases (see below) + * *tmfd: filled in failure cases (see below) * - * Function result may be: - * HeapTupleMayBeUpdated: lock was successfully acquired - * 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 - * HeapTupleWouldBlock: lock couldn't be acquired and wait_policy is skip + * Function results are the same as table_lock_tuple(). * - * In the failure cases other than HeapTupleInvisible, the routine fills - * *hufd with the tuple's t_ctid, t_xmax (resolving a possible MultiXact, - * if necessary), and t_cmax (the last only for HeapTupleSelfUpdated, + * In the failure cases other than TableTupleInvisible, the routine fills + * *tmfd with the tuple's t_ctid, t_xmax (resolving a possible MultiXact, + * if necessary), and t_cmax (the last only for TableTupleSelfModified, * since we cannot obtain cmax from a combocid generated by another * transaction). - * See comments for struct HeapUpdateFailureData for additional info. + * See comments for struct TM_FailureData for additional info. * * See README.tuplock for a thorough explanation of this mechanism. */ -HTSU_Result -heap_lock_tuple(Relation relation, HeapTuple tuple, +TM_Result +heap_lock_tuple(Relation relation, ItemPointer tid, CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy, bool follow_updates, - Buffer *buffer, HeapUpdateFailureData *hufd) + HeapTuple tuple, Buffer *buffer, TM_FailureData *tmfd) { - HTSU_Result result; - ItemPointer tid = &(tuple->t_self); + TM_Result result; ItemId lp; Page page; Buffer vmbuffer = InvalidBuffer; @@ -4076,11 +4002,12 @@ heap_lock_tuple(Relation relation, HeapTuple tuple, tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); tuple->t_len = ItemIdGetLength(lp); tuple->t_tableOid = RelationGetRelid(relation); + tuple->t_self = *tid; l3: result = HeapTupleSatisfiesUpdate(tuple, cid, *buffer); - if (result == HeapTupleInvisible) + if (result == TableTupleInvisible) { /* * This is possible, but only when locking a tuple for ON CONFLICT @@ -4088,10 +4015,10 @@ l3: * order to give that case the opportunity to throw a more specific * error. */ - result = HeapTupleInvisible; + result = TableTupleInvisible; goto out_locked; } - else if (result == HeapTupleBeingUpdated || result == HeapTupleUpdated) + else if (result == TableTupleBeingModified || result == TableTupleUpdated || result == TableTupleDeleted) { TransactionId xwait; uint16 infomask; @@ -4147,7 +4074,7 @@ l3: if (TUPLOCK_from_mxstatus(members[i].status) >= mode) { pfree(members); - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; goto out_unlocked; } } @@ -4163,20 +4090,20 @@ l3: Assert(HEAP_XMAX_IS_KEYSHR_LOCKED(infomask) || HEAP_XMAX_IS_SHR_LOCKED(infomask) || HEAP_XMAX_IS_EXCL_LOCKED(infomask)); - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; goto out_unlocked; case LockTupleShare: if (HEAP_XMAX_IS_SHR_LOCKED(infomask) || HEAP_XMAX_IS_EXCL_LOCKED(infomask)) { - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; goto out_unlocked; } break; case LockTupleNoKeyExclusive: if (HEAP_XMAX_IS_EXCL_LOCKED(infomask)) { - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; goto out_unlocked; } break; @@ -4184,7 +4111,7 @@ l3: if (HEAP_XMAX_IS_EXCL_LOCKED(infomask) && infomask2 & HEAP_KEYS_UPDATED) { - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; goto out_unlocked; } break; @@ -4233,12 +4160,12 @@ l3: */ if (follow_updates && updated) { - HTSU_Result res; + TM_Result res; res = heap_lock_updated_tuple(relation, tuple, &t_ctid, GetCurrentTransactionId(), mode); - if (res != HeapTupleMayBeUpdated) + if (res != TableTupleMayBeModified) { result = res; /* recovery code expects to have buffer lock held */ @@ -4371,7 +4298,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 == TableTupleUpdated || result == TableTupleDeleted)) { LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE); goto failed; @@ -4394,7 +4321,7 @@ l3: * This can only happen if wait_policy is Skip and the lock * couldn't be obtained. */ - result = HeapTupleWouldBlock; + result = TableTupleWouldBlock; /* recovery code expects to have buffer lock held */ LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE); goto failed; @@ -4420,7 +4347,7 @@ l3: status, infomask, relation, NULL)) { - result = HeapTupleWouldBlock; + result = TableTupleWouldBlock; /* recovery code expects to have buffer lock held */ LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE); goto failed; @@ -4460,7 +4387,7 @@ l3: case LockWaitSkip: if (!ConditionalXactLockTableWait(xwait)) { - result = HeapTupleWouldBlock; + result = TableTupleWouldBlock; /* recovery code expects to have buffer lock held */ LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE); goto failed; @@ -4479,12 +4406,12 @@ l3: /* if there are updates, follow the update chain */ if (follow_updates && !HEAP_XMAX_IS_LOCKED_ONLY(infomask)) { - HTSU_Result res; + TM_Result res; res = heap_lock_updated_tuple(relation, tuple, &t_ctid, GetCurrentTransactionId(), mode); - if (res != HeapTupleMayBeUpdated) + if (res != TableTupleMayBeModified) { result = res; /* recovery code expects to have buffer lock held */ @@ -4530,23 +4457,25 @@ l3: (tuple->t_data->t_infomask & HEAP_XMAX_INVALID) || HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask) || HeapTupleHeaderIsOnlyLocked(tuple->t_data)) - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; + else if (ItemPointerEquals(&tuple->t_self, &tuple->t_data->t_ctid)) + result = TableTupleDeleted; else - result = HeapTupleUpdated; + result = TableTupleUpdated; } failed: - if (result != HeapTupleMayBeUpdated) + if (result != TableTupleMayBeModified) { - Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated || - result == HeapTupleWouldBlock); + Assert(result == TableTupleSelfModified || result == TableTupleUpdated || + result == TableTupleWouldBlock || result == TableTupleDeleted); Assert(!(tuple->t_data->t_infomask & HEAP_XMAX_INVALID)); - hufd->ctid = tuple->t_data->t_ctid; - hufd->xmax = HeapTupleHeaderGetUpdateXid(tuple->t_data); - if (result == HeapTupleSelfUpdated) - hufd->cmax = HeapTupleHeaderGetCmax(tuple->t_data); + tmfd->ctid = tuple->t_data->t_ctid; + tmfd->xmax = HeapTupleHeaderGetUpdateXid(tuple->t_data); + if (result == TableTupleSelfModified) + tmfd->cmax = HeapTupleHeaderGetCmax(tuple->t_data); else - hufd->cmax = InvalidCommandId; + tmfd->cmax = InvalidCommandId; goto out_locked; } @@ -4664,7 +4593,7 @@ failed: END_CRIT_SECTION(); - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; out_locked: LockBuffer(*buffer, BUFFER_LOCK_UNLOCK); @@ -5022,8 +4951,8 @@ l5: * with the given xid, does the current transaction need to wait, fail, or can * it continue if it wanted to acquire a lock of the given mode? "needwait" * is set to true if waiting is necessary; if it can continue, then - * HeapTupleMayBeUpdated is returned. If the lock is already held by the - * current transaction, return HeapTupleSelfUpdated. In case of a conflict + * TableTupleMayBeModified is returned. If the lock is already held by the + * current transaction, return TableTupleSelfModified. In case of a conflict * with another transaction, a different HeapTupleSatisfiesUpdate return code * is returned. * @@ -5031,7 +4960,7 @@ l5: * lock held by a single Xid, i.e. not a real MultiXactId; we express it this * way for simplicity of API. */ -static HTSU_Result +static TM_Result test_lockmode_for_conflict(MultiXactStatus status, TransactionId xid, LockTupleMode mode, bool *needwait) { @@ -5052,7 +4981,7 @@ test_lockmode_for_conflict(MultiXactStatus status, TransactionId xid, * very rare but can happen if multiple transactions are trying to * lock an ancient version of the same tuple. */ - return HeapTupleSelfUpdated; + return TableTupleSelfModified; } else if (TransactionIdIsInProgress(xid)) { @@ -5072,10 +5001,10 @@ test_lockmode_for_conflict(MultiXactStatus status, TransactionId xid, * If we set needwait above, then this value doesn't matter; * otherwise, this value signals to caller that it's okay to proceed. */ - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; } else if (TransactionIdDidAbort(xid)) - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; else if (TransactionIdDidCommit(xid)) { /* @@ -5094,18 +5023,18 @@ test_lockmode_for_conflict(MultiXactStatus status, TransactionId xid, * always be checked. */ if (!ISUPDATE_from_mxstatus(status)) - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; if (DoLockModesConflict(LOCKMODE_from_mxstatus(status), LOCKMODE_from_mxstatus(wantedstatus))) /* bummer */ - return HeapTupleUpdated; + return TableTupleUpdated; - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; } /* Not in progress, not aborted, not committed -- must have crashed */ - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; } @@ -5116,11 +5045,11 @@ test_lockmode_for_conflict(MultiXactStatus status, TransactionId xid, * xid with the given mode; if this tuple is updated, recurse to lock the new * version as well. */ -static HTSU_Result +static TM_Result heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid, LockTupleMode mode) { - HTSU_Result result; + TM_Result result; ItemPointerData tupid; HeapTupleData mytup; Buffer buf; @@ -5145,7 +5074,7 @@ heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid, block = ItemPointerGetBlockNumber(&tupid); ItemPointerCopy(&tupid, &(mytup.t_self)); - if (!heap_fetch(rel, SnapshotAny, &mytup, &buf, false, NULL)) + if (!heap_fetch(rel, SnapshotAny, &mytup, &buf, NULL)) { /* * if we fail to find the updated version of the tuple, it's @@ -5154,7 +5083,7 @@ heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid, * chain, and there's no further tuple to lock: return success to * caller. */ - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; goto out_unlocked; } @@ -5203,7 +5132,7 @@ l4: !TransactionIdEquals(HeapTupleHeaderGetXmin(mytup.t_data), priorXmax)) { - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; goto out_locked; } @@ -5214,7 +5143,7 @@ l4: */ if (TransactionIdDidAbort(HeapTupleHeaderGetXmin(mytup.t_data))) { - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; goto out_locked; } @@ -5269,7 +5198,7 @@ l4: * this tuple and continue locking the next version in the * update chain. */ - if (result == HeapTupleSelfUpdated) + if (result == TableTupleSelfModified) { pfree(members); goto next; @@ -5284,7 +5213,7 @@ l4: pfree(members); goto l4; } - if (result != HeapTupleMayBeUpdated) + if (result != TableTupleMayBeModified) { pfree(members); goto out_locked; @@ -5345,7 +5274,7 @@ l4: * either. We just need to skip this tuple and continue * locking the next version in the update chain. */ - if (result == HeapTupleSelfUpdated) + if (result == TableTupleSelfModified) goto next; if (needwait) @@ -5355,7 +5284,7 @@ l4: XLTW_LockUpdated); goto l4; } - if (result != HeapTupleMayBeUpdated) + if (result != TableTupleMayBeModified) { goto out_locked; } @@ -5415,7 +5344,7 @@ next: ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid) || HeapTupleHeaderIsOnlyLocked(mytup.t_data)) { - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; goto out_locked; } @@ -5425,9 +5354,14 @@ next: UnlockReleaseBuffer(buf); } - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; out_locked: + + // FIXME + if (result == TableTupleUpdated && ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid)) + result = TableTupleDeleted; + UnlockReleaseBuffer(buf); out_unlocked: @@ -5459,7 +5393,7 @@ out_unlocked: * transaction cannot be using repeatable read or serializable isolation * levels, because that would lead to a serializability failure. */ -static HTSU_Result +static TM_Result heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid, TransactionId xid, LockTupleMode mode) { @@ -5485,7 +5419,7 @@ heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid, } /* nothing to lock */ - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; } /* @@ -5505,7 +5439,7 @@ heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid, * An explicit confirmation WAL record also makes logical decoding simpler. */ void -heap_finish_speculative(Relation relation, HeapTuple tuple) +heap_finish_speculative(Relation relation, ItemPointer tid) { Buffer buffer; Page page; @@ -5513,11 +5447,11 @@ heap_finish_speculative(Relation relation, HeapTuple tuple) ItemId lp = NULL; HeapTupleHeader htup; - buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&(tuple->t_self))); + buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid)); LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); page = (Page) BufferGetPage(buffer); - offnum = ItemPointerGetOffsetNumber(&(tuple->t_self)); + offnum = ItemPointerGetOffsetNumber(tid); if (PageGetMaxOffsetNumber(page) >= offnum) lp = PageGetItemId(page, offnum); @@ -5533,7 +5467,7 @@ heap_finish_speculative(Relation relation, HeapTuple tuple) /* NO EREPORT(ERROR) from here till changes are logged */ START_CRIT_SECTION(); - Assert(HeapTupleHeaderIsSpeculative(tuple->t_data)); + Assert(HeapTupleHeaderIsSpeculative(htup)); MarkBufferDirty(buffer); @@ -5541,7 +5475,7 @@ heap_finish_speculative(Relation relation, HeapTuple tuple) * Replace the speculative insertion token with a real t_ctid, pointing to * itself like it does on regular tuples. */ - htup->t_ctid = tuple->t_self; + htup->t_ctid = *tid; /* XLOG stuff */ if (RelationNeedsWAL(relation)) @@ -5549,7 +5483,7 @@ heap_finish_speculative(Relation relation, HeapTuple tuple) xl_heap_confirm xlrec; XLogRecPtr recptr; - xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self); + xlrec.offnum = ItemPointerGetOffsetNumber(tid); XLogBeginInsert(); @@ -5596,10 +5530,9 @@ heap_finish_speculative(Relation relation, HeapTuple tuple) * confirmation records. */ void -heap_abort_speculative(Relation relation, HeapTuple tuple) +heap_abort_speculative(Relation relation, ItemPointer tid) { TransactionId xid = GetCurrentTransactionId(); - ItemPointer tid = &(tuple->t_self); ItemId lp; HeapTupleData tp; Page page; diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 6a26fcef94c..0ec2f69b20e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -21,7 +21,9 @@ #include "access/heapam.h" #include "access/tableam.h" +#include "access/xact.h" #include "storage/bufmgr.h" +#include "storage/lmgr.h" #include "utils/builtins.h" @@ -169,6 +171,321 @@ heapam_tuple_satisfies_snapshot(Relation rel, TupleTableSlot *slot, } +/* ---------------------------------------------------------------------------- + * Functions for manipulations of physical tuples for heap AM. + * ---------------------------------------------------------------------------- + */ + +static void +heapam_tuple_insert(Relation relation, TupleTableSlot *slot, CommandId cid, + int options, BulkInsertState bistate) +{ + bool shouldFree = true; + HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree); + + /* Update the tuple with table oid */ + slot->tts_tableOid = RelationGetRelid(relation); + if (slot->tts_tableOid != InvalidOid) + tuple->t_tableOid = slot->tts_tableOid; + + /* Perform the insertion, and copy the resulting ItemPointer */ + heap_insert(relation, tuple, cid, options, bistate); + ItemPointerCopy(&tuple->t_self, &slot->tts_tid); + + if (shouldFree) + pfree(tuple); +} + +static void +heapam_tuple_insert_speculative(Relation relation, TupleTableSlot *slot, CommandId cid, + int options, BulkInsertState bistate, uint32 specToken) +{ + bool shouldFree = true; + HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree); + + /* Update the tuple with table oid */ + slot->tts_tableOid = RelationGetRelid(relation); + if (slot->tts_tableOid != InvalidOid) + tuple->t_tableOid = slot->tts_tableOid; + + HeapTupleHeaderSetSpeculativeToken(tuple->t_data, specToken); + options |= HEAP_INSERT_SPECULATIVE; + + /* Perform the insertion, and copy the resulting ItemPointer */ + heap_insert(relation, tuple, cid, options, bistate); + ItemPointerCopy(&tuple->t_self, &slot->tts_tid); + + if (shouldFree) + pfree(tuple); +} + +static void +heapam_tuple_complete_speculative(Relation relation, TupleTableSlot *slot, uint32 spekToken, + bool succeeded) +{ + bool shouldFree = true; + HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree); + + /* adjust the tuple's state accordingly */ + if (!succeeded) + heap_finish_speculative(relation, &slot->tts_tid); + else + heap_abort_speculative(relation, &slot->tts_tid); + + if (shouldFree) + pfree(tuple); +} + +static TM_Result +heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid, + Snapshot snapshot, Snapshot crosscheck, bool wait, + TM_FailureData *tmfd, bool changingPart) +{ + /* + * Currently Deleting of index tuples are handled at vacuum, in case if + * the storage itself is cleaning the dead tuples by itself, it is the + * time to call the index tuple deletion also. + */ + 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, bool *update_indexes) +{ + bool shouldFree = true; + HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree); + TM_Result result; + + /* Update the tuple with table oid */ + slot->tts_tableOid = RelationGetRelid(relation); + if (slot->tts_tableOid != InvalidOid) + tuple->t_tableOid = slot->tts_tableOid; + + result = heap_update(relation, otid, tuple, cid, crosscheck, wait, + tmfd, lockmode); + ItemPointerCopy(&tuple->t_self, &slot->tts_tid); + + /* + * Decide whether new index entries are needed for the tuple + * + * Note: heap_update returns the tid (location) of the new tuple in the + * t_self field. + * + * If it's a HOT update, we mustn't insert new index entries. + */ + *update_indexes = result == TableTupleMayBeModified && + !HeapTupleIsHeapOnly(tuple); + + if (shouldFree) + pfree(tuple); + + return result; +} + +static TM_Result +heapam_tuple_lock(Relation relation, ItemPointer tid, Snapshot snapshot, + TupleTableSlot *slot, CommandId cid, LockTupleMode mode, + LockWaitPolicy wait_policy, uint8 flags, + TM_FailureData *tmfd) +{ + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; + TM_Result result; + Buffer buffer; + HeapTuple tuple = &bslot->base.tupdata; + bool follow_updates; + + follow_updates = (flags & TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS) != 0; + tmfd->traversed = false; + + Assert(TTS_IS_BUFFERTUPLE(slot)); + +retry: + result = heap_lock_tuple(relation, tid, cid, mode, wait_policy, + follow_updates, tuple, &buffer, tmfd); + + if (result == TableTupleUpdated && + (flags & TUPLE_LOCK_FLAG_FIND_LAST_VERSION)) + { + ReleaseBuffer(buffer); + /* Should not encounter speculative tuple on recheck */ + Assert(!HeapTupleHeaderIsSpeculative(tuple->t_data)); + + if (!ItemPointerEquals(&tmfd->ctid, &tuple->t_self)) + { + SnapshotData SnapshotDirty; + TransactionId priorXmax; + + /* it was updated, so look at the updated version */ + *tid = tmfd->ctid; + /* updated row should have xmin matching this xmax */ + priorXmax = tmfd->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"))); + + tuple->t_self = *tid; + if (heap_fetch(relation, &SnapshotDirty, tuple, &buffer, 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 TableTupleDeleted; + } + + /* 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)) + /* skip instead of waiting */ + return TableTupleWouldBlock; + 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; + } + + tmfd->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) + { + return TableTupleDeleted; + } + + /* + * As above, if xmin isn't what we're expecting, do nothing. + */ + if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple->t_data), + priorXmax)) + { + if (BufferIsValid(buffer)) + ReleaseBuffer(buffer); + return TableTupleDeleted; + } + + /* + * 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 */ + if (BufferIsValid(buffer)) + ReleaseBuffer(buffer); + return TableTupleDeleted; + } + + /* 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); + if (BufferIsValid(buffer)) + ReleaseBuffer(buffer); + /* loop back to fetch next in chain */ + } + } + else + { + /* tuple was deleted, so give up */ + return TableTupleDeleted; + } + } + + slot->tts_tableOid = RelationGetRelid(relation); + if (slot->tts_tableOid != InvalidOid) + tuple->t_tableOid = slot->tts_tableOid; + /* store in slot, transferring existing pin */ + ExecStorePinnedBufferHeapTuple(tuple, slot, buffer); + + return result; +} + + /* ------------------------------------------------------------------------ * Definition of the heap table access method. * ------------------------------------------------------------------------ @@ -193,6 +510,13 @@ static const TableAmRoutine heapam_methods = { .index_fetch_end = heapam_index_fetch_end, .index_fetch_tuple = heapam_index_fetch_tuple, + .tuple_insert = heapam_tuple_insert, + .tuple_insert_speculative = heapam_tuple_insert_speculative, + .tuple_complete_speculative = heapam_tuple_complete_speculative, + .tuple_delete = heapam_tuple_delete, + .tuple_update = heapam_tuple_update, + .tuple_lock = heapam_tuple_lock, + .tuple_satisfies_snapshot = heapam_tuple_satisfies_snapshot, }; diff --git a/src/backend/access/heap/heapam_visibility.c b/src/backend/access/heap/heapam_visibility.c index 6cb38f80c68..b9d0475cde1 100644 --- a/src/backend/access/heap/heapam_visibility.c +++ b/src/backend/access/heap/heapam_visibility.c @@ -67,6 +67,7 @@ #include "access/htup_details.h" #include "access/multixact.h" #include "access/subtrans.h" +#include "access/tableam.h" #include "access/transam.h" #include "access/xact.h" #include "access/xlog.h" @@ -433,24 +434,24 @@ HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot, * * The possible return codes are: * - * HeapTupleInvisible: the tuple didn't exist at all when the scan started, + * TableTupleInvisible: the tuple didn't exist at all when the scan started, * e.g. it was created by a later CommandId. * - * HeapTupleMayBeUpdated: The tuple is valid and visible, so it may be + * TableTupleMayBeModified: The tuple is valid and visible, so it may be * updated. * - * HeapTupleSelfUpdated: The tuple was updated by the current transaction, + * TableTupleSelfModified: The tuple was updated by the current transaction, * after the current scan started. * - * HeapTupleUpdated: The tuple was updated by a committed transaction. + * TableTupleUpdated: The tuple was updated by a committed transaction. * - * HeapTupleBeingUpdated: The tuple is being updated by an in-progress + * TableTupleBeingModified: The tuple is being updated by an in-progress * transaction other than the current transaction. (Note: this includes * the case where the tuple is share-locked by a MultiXact, even if the * MultiXact includes the current transaction. Callers that want to * distinguish that case must test for it themselves.) */ -HTSU_Result +TM_Result HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, Buffer buffer) { @@ -462,7 +463,7 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, if (!HeapTupleHeaderXminCommitted(tuple)) { if (HeapTupleHeaderXminInvalid(tuple)) - return HeapTupleInvisible; + return TableTupleInvisible; /* Used by pre-9.0 binary upgrades */ if (tuple->t_infomask & HEAP_MOVED_OFF) @@ -470,14 +471,14 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, TransactionId xvac = HeapTupleHeaderGetXvac(tuple); if (TransactionIdIsCurrentTransactionId(xvac)) - return HeapTupleInvisible; + return TableTupleInvisible; if (!TransactionIdIsInProgress(xvac)) { if (TransactionIdDidCommit(xvac)) { SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, InvalidTransactionId); - return HeapTupleInvisible; + return TableTupleInvisible; } SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, InvalidTransactionId); @@ -491,7 +492,7 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, if (!TransactionIdIsCurrentTransactionId(xvac)) { if (TransactionIdIsInProgress(xvac)) - return HeapTupleInvisible; + return TableTupleInvisible; if (TransactionIdDidCommit(xvac)) SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, InvalidTransactionId); @@ -499,17 +500,17 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, { SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, InvalidTransactionId); - return HeapTupleInvisible; + return TableTupleInvisible; } } } else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple))) { if (HeapTupleHeaderGetCmin(tuple) >= curcid) - return HeapTupleInvisible; /* inserted after scan started */ + return TableTupleInvisible; /* inserted after scan started */ if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */ - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) { @@ -527,9 +528,9 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) { if (MultiXactIdIsRunning(xmax, true)) - return HeapTupleBeingUpdated; + return TableTupleBeingModified; else - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; } /* @@ -538,8 +539,8 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, * locked/updated. */ if (!TransactionIdIsInProgress(xmax)) - return HeapTupleMayBeUpdated; - return HeapTupleBeingUpdated; + return TableTupleMayBeModified; + return TableTupleBeingModified; } if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) @@ -556,16 +557,16 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, { if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false)) - return HeapTupleBeingUpdated; - return HeapTupleMayBeUpdated; + return TableTupleBeingModified; + return TableTupleMayBeModified; } else { if (HeapTupleHeaderGetCmax(tuple) >= curcid) - return HeapTupleSelfUpdated; /* updated after scan + return TableTupleSelfModified; /* updated after scan * started */ else - return HeapTupleInvisible; /* updated before scan + return TableTupleInvisible; /* updated before scan * started */ } } @@ -575,16 +576,16 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, /* deleting subtransaction must have aborted */ SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId); - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; } if (HeapTupleHeaderGetCmax(tuple) >= curcid) - return HeapTupleSelfUpdated; /* updated after scan started */ + return TableTupleSelfModified; /* updated after scan started */ else - return HeapTupleInvisible; /* updated before scan started */ + return TableTupleInvisible; /* updated before scan started */ } else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple))) - return HeapTupleInvisible; + return TableTupleInvisible; else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple))) SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, HeapTupleHeaderGetRawXmin(tuple)); @@ -593,20 +594,23 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, /* it must have aborted or crashed */ SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, InvalidTransactionId); - return HeapTupleInvisible; + return TableTupleInvisible; } } /* by here, the inserting transaction has committed */ if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid or aborted */ - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; if (tuple->t_infomask & HEAP_XMAX_COMMITTED) { if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) - return HeapTupleMayBeUpdated; - return HeapTupleUpdated; /* updated by other */ + return TableTupleMayBeModified; + if (ItemPointerEquals(&htup->t_self, &tuple->t_ctid)) + return TableTupleDeleted; /* deleted by other */ + else + return TableTupleUpdated; /* updated by other */ } if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) @@ -614,22 +618,22 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, TransactionId xmax; if (HEAP_LOCKED_UPGRADED(tuple->t_infomask)) - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) { if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), true)) - return HeapTupleBeingUpdated; + return TableTupleBeingModified; SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId); - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; } xmax = HeapTupleGetUpdateXid(tuple); if (!TransactionIdIsValid(xmax)) { if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false)) - return HeapTupleBeingUpdated; + return TableTupleBeingModified; } /* not LOCKED_ONLY, so it has to have an xmax */ @@ -638,16 +642,21 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, if (TransactionIdIsCurrentTransactionId(xmax)) { if (HeapTupleHeaderGetCmax(tuple) >= curcid) - return HeapTupleSelfUpdated; /* updated after scan started */ + return TableTupleSelfModified; /* updated after scan started */ else - return HeapTupleInvisible; /* updated before scan started */ + return TableTupleInvisible; /* updated before scan started */ } if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false)) - return HeapTupleBeingUpdated; + return TableTupleBeingModified; if (TransactionIdDidCommit(xmax)) - return HeapTupleUpdated; + { + if (ItemPointerEquals(&htup->t_self, &tuple->t_ctid)) + return TableTupleDeleted; + else + return TableTupleUpdated; + } /* * By here, the update in the Xmax is either aborted or crashed, but @@ -662,34 +671,34 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, */ SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId); - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; } else { /* There are lockers running */ - return HeapTupleBeingUpdated; + return TableTupleBeingModified; } } if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple))) { if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) - return HeapTupleBeingUpdated; + return TableTupleBeingModified; if (HeapTupleHeaderGetCmax(tuple) >= curcid) - return HeapTupleSelfUpdated; /* updated after scan started */ + return TableTupleSelfModified; /* updated after scan started */ else - return HeapTupleInvisible; /* updated before scan started */ + return TableTupleInvisible; /* updated before scan started */ } if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple))) - return HeapTupleBeingUpdated; + return TableTupleBeingModified; if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple))) { /* it must have aborted or crashed */ SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId); - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; } /* xmax transaction committed */ @@ -698,12 +707,16 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, { SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId); - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; } SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, HeapTupleHeaderGetRawXmax(tuple)); - return HeapTupleUpdated; /* updated by other */ + + if (ItemPointerEquals(&htup->t_self, &tuple->t_ctid)) + return TableTupleDeleted; /* deleted by other */ + else + return TableTupleUpdated; /* updated by other */ } /* diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c index cd921a46005..a40cfcf1954 100644 --- a/src/backend/access/heap/tuptoaster.c +++ b/src/backend/access/heap/tuptoaster.c @@ -1763,7 +1763,7 @@ toast_delete_datum(Relation rel, Datum value, bool is_speculative) * Have a chunk, delete it */ if (is_speculative) - heap_abort_speculative(toastrel, toasttup); + heap_abort_speculative(toastrel, &toasttup->t_self); else simple_heap_delete(toastrel, &toasttup->t_self); } diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c index 628d930c130..540c1f99766 100644 --- a/src/backend/access/table/tableam.c +++ b/src/backend/access/table/tableam.c @@ -176,6 +176,119 @@ table_beginscan_parallel(Relation relation, ParallelTableScanDesc parallel_scan) } +/* ---------------------------------------------------------------------------- + * Functions to make modifications a bit simpler. + * ---------------------------------------------------------------------------- + */ + +/* + * simple_table_insert - insert a tuple + * + * Currently, this routine differs from table_insert only in supplying a + * default command ID and not allowing access to the speedup options. + */ +void +simple_table_insert(Relation rel, TupleTableSlot *slot) +{ + table_insert(rel, slot, GetCurrentCommandId(true), 0, NULL); +} + +/* + * simple_table_delete - delete a tuple + * + * This routine may be used to delete a tuple when concurrent updates of + * the target tuple are not expected (for example, because we have a lock + * on the relation associated with the tuple). Any failure is reported + * via ereport(). + */ +void +simple_table_delete(Relation rel, ItemPointer tid, Snapshot snapshot) +{ + TM_Result result; + TM_FailureData tmfd; + + result = table_delete(rel, tid, + GetCurrentCommandId(true), + snapshot, InvalidSnapshot, + true /* wait for commit */ , + &tmfd, false /* changingPart */ ); + + switch (result) + { + case TableTupleSelfModified: + /* Tuple was already updated in current command? */ + elog(ERROR, "tuple already updated by self"); + break; + + case TableTupleMayBeModified: + /* done successfully */ + break; + + case TableTupleUpdated: + elog(ERROR, "tuple concurrently updated"); + break; + + case TableTupleDeleted: + elog(ERROR, "tuple concurrently deleted"); + break; + + default: + elog(ERROR, "unrecognized table_delete status: %u", result); + break; + } +} + +/* + * simple_table_update - replace a tuple + * + * This routine may be used to update a tuple when concurrent updates of + * the target tuple are not expected (for example, because we have a lock + * on the relation associated with the tuple). Any failure is reported + * via ereport(). + */ +void +simple_table_update(Relation rel, ItemPointer otid, + TupleTableSlot *slot, + Snapshot snapshot, + bool *update_indexes) +{ + TM_Result result; + TM_FailureData tmfd; + LockTupleMode lockmode; + + result = table_update(rel, otid, slot, + GetCurrentCommandId(true), + snapshot, InvalidSnapshot, + true /* wait for commit */ , + &tmfd, &lockmode, update_indexes); + + switch (result) + { + case TableTupleSelfModified: + /* Tuple was already updated in current command? */ + elog(ERROR, "tuple already updated by self"); + break; + + case TableTupleMayBeModified: + /* done successfully */ + break; + + case TableTupleUpdated: + elog(ERROR, "tuple concurrently updated"); + break; + + case TableTupleDeleted: + elog(ERROR, "tuple concurrently deleted"); + break; + + default: + elog(ERROR, "unrecognized table_update status: %u", result); + break; + } + +} + + /* ---------------------------------------------------------------------------- * Helper functions to implement parallel scans for block oriented AMs. * ---------------------------------------------------------------------------- diff --git a/src/backend/access/table/tableamapi.c b/src/backend/access/table/tableamapi.c index 3d3b82e1e58..c8592060112 100644 --- a/src/backend/access/table/tableamapi.c +++ b/src/backend/access/table/tableamapi.c @@ -64,6 +64,19 @@ GetTableAmRoutine(Oid amhandler) Assert(routine->tuple_satisfies_snapshot != NULL); + Assert(routine->tuple_insert != NULL); + + /* + * Could be made optional, but would require throwing error during + * parse-analysis. + */ + Assert(routine->tuple_insert_speculative != NULL); + Assert(routine->tuple_complete_speculative != NULL); + + Assert(routine->tuple_delete != NULL); + Assert(routine->tuple_update != NULL); + Assert(routine->tuple_lock != NULL); + return routine; } diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 218a6e01cbb..705df8900ba 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -3007,7 +3007,6 @@ CopyFrom(CopyState cstate) /* And create index entries for it */ if (resultRelInfo->ri_NumIndices > 0) recheckIndexes = ExecInsertIndexTuples(slot, - &(tuple->t_self), estate, false, NULL, @@ -3151,7 +3150,7 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid, cstate->cur_lineno = firstBufferedLineNo + i; ExecStoreHeapTuple(bufferedTuples[i], myslot, false); recheckIndexes = - ExecInsertIndexTuples(myslot, &(bufferedTuples[i]->t_self), + ExecInsertIndexTuples(myslot, estate, false, NULL, NIL); ExecARInsertTriggers(estate, resultRelInfo, myslot, diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 71098896947..2221188ea47 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -15,6 +15,7 @@ #include "access/genam.h" #include "access/heapam.h" +#include "access/tableam.h" #include "access/sysattr.h" #include "access/htup_details.h" #include "access/xact.h" @@ -3285,19 +3286,11 @@ GetTupleForTrigger(EState *estate, TupleTableSlot **newSlot) { Relation relation = relinfo->ri_RelationDesc; - HeapTuple tuple; - Buffer buffer; - BufferHeapTupleTableSlot *boldslot; - - Assert(TTS_IS_BUFFERTUPLE(oldslot)); - ExecClearTuple(oldslot); - boldslot = (BufferHeapTupleTableSlot *) oldslot; - tuple = &boldslot->base.tupdata; if (newSlot != NULL) { - HTSU_Result test; - HeapUpdateFailureData hufd; + TM_Result test; + TM_FailureData tmfd; *newSlot = NULL; @@ -3307,15 +3300,15 @@ GetTupleForTrigger(EState *estate, /* * lock tuple for update */ -ltrmark:; - tuple->t_self = *tid; - test = heap_lock_tuple(relation, tuple, - estate->es_output_cid, - lockmode, LockWaitBlock, - false, &buffer, &hufd); + test = table_lock_tuple(relation, tid, estate->es_snapshot, oldslot, + estate->es_output_cid, + lockmode, LockWaitBlock, + IsolationUsesXactSnapshot() ? 0 : TUPLE_LOCK_FLAG_FIND_LAST_VERSION, + &tmfd); + switch (test) { - case HeapTupleSelfUpdated: + case TableTupleSelfModified: /* * The target tuple was already updated or deleted by the @@ -3325,73 +3318,59 @@ ltrmark:; * enumerated in ExecUpdate and ExecDelete in * nodeModifyTable.c. */ - if (hufd.cmax != estate->es_output_cid) + if (tmfd.cmax != estate->es_output_cid) ereport(ERROR, (errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION), errmsg("tuple to be updated was already modified by an operation triggered by the current command"), 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 false; - case HeapTupleMayBeUpdated: - ExecStorePinnedBufferHeapTuple(tuple, oldslot, buffer); - - 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 (!ItemPointerEquals(&hufd.ctid, &tuple->t_self)) + case TableTupleMayBeModified: + if (tmfd.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; + oldslot); - *newSlot = epqslot; + /* + * If PlanQual failed for updated tuple - we must not + * process this tuple! + */ + if (TupIsNull(epqslot)) + return false; - /* - * 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; - } + *newSlot = epqslot; } + break; - /* - * if tuple was deleted or PlanQual failed for updated tuple - - * we must not process this tuple! - */ + case TableTupleUpdated: + if (IsolationUsesXactSnapshot()) + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to concurrent update"))); + elog(ERROR, "unexpected table_lock_tuple status: %u", test); + break; + + case TableTupleDeleted: + if (IsolationUsesXactSnapshot()) + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to concurrent delete"))); + /* tuple was deleted */ return false; - case HeapTupleInvisible: + case TableTupleInvisible: elog(ERROR, "attempted to lock invisible tuple"); break; default: - ReleaseBuffer(buffer); - elog(ERROR, "unrecognized heap_lock_tuple status: %u", test); + elog(ERROR, "unrecognized table_lock_tuple status: %u", test); return false; /* keep compiler quiet */ } } @@ -3399,6 +3378,14 @@ ltrmark:; { Page page; ItemId lp; + Buffer buffer; + BufferHeapTupleTableSlot *boldslot; + HeapTuple tuple; + + Assert(TTS_IS_BUFFERTUPLE(oldslot)); + ExecClearTuple(oldslot); + boldslot = (BufferHeapTupleTableSlot *) oldslot; + tuple = &boldslot->base.tupdata; buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid)); @@ -4286,7 +4273,7 @@ AfterTriggerExecute(EState *estate, LocTriggerData.tg_trigslot = ExecGetTriggerOldSlot(estate, relInfo); ItemPointerCopy(&(event->ate_ctid1), &(tuple1.t_self)); - if (!heap_fetch(rel, SnapshotAny, &tuple1, &buffer, false, NULL)) + if (!heap_fetch(rel, SnapshotAny, &tuple1, &buffer, NULL)) elog(ERROR, "failed to fetch tuple1 for AFTER trigger"); ExecStorePinnedBufferHeapTuple(&tuple1, LocTriggerData.tg_trigslot, @@ -4310,7 +4297,7 @@ AfterTriggerExecute(EState *estate, LocTriggerData.tg_newslot = ExecGetTriggerNewSlot(estate, relInfo); ItemPointerCopy(&(event->ate_ctid2), &(tuple2.t_self)); - if (!heap_fetch(rel, SnapshotAny, &tuple2, &buffer, false, NULL)) + if (!heap_fetch(rel, SnapshotAny, &tuple2, &buffer, NULL)) elog(ERROR, "failed to fetch tuple2 for AFTER trigger"); ExecStorePinnedBufferHeapTuple(&tuple2, LocTriggerData.tg_newslot, diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c index e67dd6750c6..3b602bb8baf 100644 --- a/src/backend/executor/execIndexing.c +++ b/src/backend/executor/execIndexing.c @@ -271,12 +271,12 @@ ExecCloseIndices(ResultRelInfo *resultRelInfo) */ List * ExecInsertIndexTuples(TupleTableSlot *slot, - ItemPointer tupleid, EState *estate, bool noDupErr, bool *specConflict, List *arbiterIndexes) { + ItemPointer tupleid = &slot->tts_tid; List *result = NIL; ResultRelInfo *resultRelInfo; int i; @@ -288,6 +288,8 @@ ExecInsertIndexTuples(TupleTableSlot *slot, Datum values[INDEX_MAX_KEYS]; bool isnull[INDEX_MAX_KEYS]; + Assert(ItemPointerIsValid(tupleid)); + /* * Get information from the result relation info structure. */ diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 63a34760eec..3a8e852b49d 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -2417,27 +2417,29 @@ ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist) /* - * Check a modified tuple to see if we want to process its updated version - * under READ COMMITTED rules. + * Check the updated version of a tuple to see if we want to process it under + * READ COMMITTED rules. * * estate - outer executor state data * 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 + * inputslot - tuple for processing - this can be the slot from + * EvalPlanQualSlot(), for the increased efficiency. * - * *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) + * This tests whether the tuple in inputslot still matches the relvant + * quals. For that result to be useful, typically the input tuple has to be + * last row version (otherwise the result isn't particularly useful) and + * locked (otherwise the result might be out of date). That's typically + * achieved by using table_lock_tuple() with the + * TUPLE_LOCK_FLAG_FIND_LAST_VERSION flag.. * * Returns a slot containing the new candidate update/delete tuple, or * NULL if we determine we shouldn't process the row. */ TupleTableSlot * EvalPlanQual(EState *estate, EPQState *epqstate, - Relation relation, Index rti, LockTupleMode lockmode, - ItemPointer tid, TransactionId priorXmax) + Relation relation, Index rti, TupleTableSlot *inputslot) { TupleTableSlot *slot; TupleTableSlot *testslot; @@ -2450,19 +2452,12 @@ EvalPlanQual(EState *estate, EPQState *epqstate, EvalPlanQualBegin(epqstate, estate); /* - * Get and lock the updated version of the row; if fail, return NULL. + * Callers will often use the EvalPlanQualSlot to store the tuple to avoid + * an unnecessary copy. */ testslot = EvalPlanQualSlot(epqstate, relation, rti); - if (!EvalPlanQualFetch(estate, relation, lockmode, LockWaitBlock, - tid, priorXmax, - testslot)) - return NULL; - - /* - * For UPDATE/DELETE we have to return tid of actual row we're executing - * PQ for. - */ - *tid = testslot->tts_tid; + if (testslot != inputslot) + ExecCopySlot(testslot, inputslot); /* * Fetch any non-locked source rows @@ -2494,258 +2489,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 - * slot - slot to store newest tuple version - * - * Returns true, with slot containing the newest tuple version, or false if we - * find that there is no newest version (ie, the row was deleted not updated). - * We also return false 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. - */ -bool -EvalPlanQualFetch(EState *estate, Relation relation, LockTupleMode lockmode, - LockWaitPolicy wait_policy, - ItemPointer tid, TransactionId priorXmax, - TupleTableSlot *slot) -{ - HeapTupleData tuple; - SnapshotData SnapshotDirty; - - /* - * fetch target tuple - * - * Loop here to deal with updated or busy tuples - */ - InitDirtySnapshot(SnapshotDirty); - tuple.t_self = *tid; - for (;;) - { - Buffer buffer; - - if (heap_fetch(relation, &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(tuple.t_data), - priorXmax)) - { - ReleaseBuffer(buffer); - return false; - } - - /* 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 false; /* 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) >= estate->es_output_cid) - { - ReleaseBuffer(buffer); - return false; - } - - /* - * This is a live tuple, so now try to lock it. - */ - test = heap_lock_tuple(relation, &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 false; - - 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"))); - - /* Should not encounter speculative tuple on recheck */ - Assert(!HeapTupleHeaderIsSpeculative(tuple.t_data)); - if (!ItemPointerEquals(&hufd.ctid, &tuple.t_self)) - { - /* it was updated, so look at the updated version */ - tuple.t_self = hufd.ctid; - /* updated row should have xmin matching this xmax */ - priorXmax = hufd.xmax; - continue; - } - /* tuple was deleted, so give up */ - return false; - - case HeapTupleWouldBlock: - ReleaseBuffer(buffer); - return false; - - case HeapTupleInvisible: - elog(ERROR, "attempted to lock invisible tuple"); - break; - - default: - ReleaseBuffer(buffer); - elog(ERROR, "unrecognized heap_lock_tuple status: %u", - test); - return false; /* keep compiler quiet */ - } - - /* - * We got tuple - store it for use by the recheck query. - */ - ExecStorePinnedBufferHeapTuple(&tuple, slot, buffer); - ExecMaterializeSlot(slot); - 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.t_data == NULL) - { - ReleaseBuffer(buffer); - return false; - } - - /* - * As above, if xmin isn't what we're expecting, do nothing. - */ - if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple.t_data), - priorXmax)) - { - ReleaseBuffer(buffer); - return false; - } - - /* - * 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 */ - if (HeapTupleHeaderIndicatesMovedPartitions(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"))); - - /* check whether tuple has been deleted */ - if (ItemPointerEquals(&tuple.t_self, &tuple.t_data->t_ctid)) - { - /* deleted, so forget about it */ - ReleaseBuffer(buffer); - return false; - } - - /* updated, so look at the updated row */ - tuple.t_self = 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 */ - } - - /* signal success */ - return true; -} - /* * EvalPlanQualInit -- initialize during creation of a plan state node * that might need to invoke EPQ processing. @@ -2911,7 +2654,7 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate) tuple.t_self = *((ItemPointer) DatumGetPointer(datum)); if (!heap_fetch(erm->relation, SnapshotAny, &tuple, &buffer, - false, NULL)) + NULL)) elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck"); /* successful, store tuple */ diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c index 95dfc4987de..c8bdc224803 100644 --- a/src/backend/executor/execReplication.c +++ b/src/backend/executor/execReplication.c @@ -15,7 +15,6 @@ #include "postgres.h" #include "access/genam.h" -#include "access/heapam.h" #include "access/relscan.h" #include "access/tableam.h" #include "access/transam.h" @@ -166,35 +165,28 @@ retry: /* Found tuple, try to lock it in the lockmode. */ if (found) { - Buffer buf; - HeapUpdateFailureData hufd; - HTSU_Result res; - HeapTupleData locktup; - HeapTupleTableSlot *hslot = (HeapTupleTableSlot *)outslot; - - /* Only a heap tuple has item pointers. */ - Assert(TTS_IS_HEAPTUPLE(outslot) || TTS_IS_BUFFERTUPLE(outslot)); - ItemPointerCopy(&hslot->tuple->t_self, &locktup.t_self); + TM_FailureData tmfd; + TM_Result res; PushActiveSnapshot(GetLatestSnapshot()); - res = heap_lock_tuple(rel, &locktup, GetCurrentCommandId(false), - lockmode, - LockWaitBlock, - false /* don't follow updates */ , - &buf, &hufd); - /* the tuple slot already has the buffer pinned */ - ReleaseBuffer(buf); + res = table_lock_tuple(rel, &(outslot->tts_tid), GetLatestSnapshot(), + outslot, + GetCurrentCommandId(false), + lockmode, + LockWaitBlock, + 0 /* don't follow updates */ , + &tmfd); PopActiveSnapshot(); switch (res) { - case HeapTupleMayBeUpdated: + case TableTupleMayBeModified: break; - case HeapTupleUpdated: + case TableTupleUpdated: /* XXX: Improve handling here */ - if (ItemPointerIndicatesMovedPartitions(&hufd.ctid)) + if (ItemPointerIndicatesMovedPartitions(&tmfd.ctid)) ereport(LOG, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("tuple to be locked was already moved to another partition due to concurrent update, retrying"))); @@ -203,11 +195,17 @@ retry: (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("concurrent update, retrying"))); goto retry; - case HeapTupleInvisible: + case TableTupleDeleted: + /* XXX: Improve handling here */ + ereport(LOG, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("concurrent delete, retrying"))); + goto retry; + case TableTupleInvisible: elog(ERROR, "attempted to lock invisible tuple"); break; default: - elog(ERROR, "unexpected heap_lock_tuple status: %u", res); + elog(ERROR, "unexpected table_lock_tuple status: %u", res); break; } } @@ -330,35 +328,28 @@ retry: /* Found tuple, try to lock it in the lockmode. */ if (found) { - Buffer buf; - HeapUpdateFailureData hufd; - HTSU_Result res; - HeapTupleData locktup; - HeapTupleTableSlot *hslot = (HeapTupleTableSlot *)outslot; - - /* Only a heap tuple has item pointers. */ - Assert(TTS_IS_HEAPTUPLE(outslot) || TTS_IS_BUFFERTUPLE(outslot)); - ItemPointerCopy(&hslot->tuple->t_self, &locktup.t_self); + TM_FailureData tmfd; + TM_Result res; PushActiveSnapshot(GetLatestSnapshot()); - res = heap_lock_tuple(rel, &locktup, GetCurrentCommandId(false), - lockmode, - LockWaitBlock, - false /* don't follow updates */ , - &buf, &hufd); - /* the tuple slot already has the buffer pinned */ - ReleaseBuffer(buf); + res = table_lock_tuple(rel, &(outslot->tts_tid), GetLatestSnapshot(), + outslot, + GetCurrentCommandId(false), + lockmode, + LockWaitBlock, + 0 /* don't follow updates */ , + &tmfd); PopActiveSnapshot(); switch (res) { - case HeapTupleMayBeUpdated: + case TableTupleMayBeModified: break; - case HeapTupleUpdated: + case TableTupleUpdated: /* XXX: Improve handling here */ - if (ItemPointerIndicatesMovedPartitions(&hufd.ctid)) + if (ItemPointerIndicatesMovedPartitions(&tmfd.ctid)) ereport(LOG, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("tuple to be locked was already moved to another partition due to concurrent update, retrying"))); @@ -367,11 +358,17 @@ retry: (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("concurrent update, retrying"))); goto retry; - case HeapTupleInvisible: + case TableTupleDeleted: + /* XXX: Improve handling here */ + ereport(LOG, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("concurrent delete, retrying"))); + goto retry; + case TableTupleInvisible: elog(ERROR, "attempted to lock invisible tuple"); break; default: - elog(ERROR, "unexpected heap_lock_tuple status: %u", res); + elog(ERROR, "unexpected table_lock_tuple status: %u", res); break; } } @@ -392,7 +389,6 @@ void ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot) { bool skip_tuple = false; - HeapTuple tuple; ResultRelInfo *resultRelInfo = estate->es_result_relation_info; Relation rel = resultRelInfo->ri_RelationDesc; @@ -419,16 +415,11 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot) if (resultRelInfo->ri_PartitionCheck) ExecPartitionCheck(resultRelInfo, slot, estate, true); - /* Materialize slot into a tuple that we can scribble upon. */ - tuple = ExecFetchSlotHeapTuple(slot, true, NULL); - /* OK, store the tuple and create index entries for it */ - simple_heap_insert(rel, tuple); - ItemPointerCopy(&tuple->t_self, &slot->tts_tid); + simple_table_insert(resultRelInfo->ri_RelationDesc, slot); if (resultRelInfo->ri_NumIndices > 0) - recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self), - estate, false, NULL, + recheckIndexes = ExecInsertIndexTuples(slot, estate, false, NULL, NIL); /* AFTER ROW INSERT Triggers */ @@ -456,13 +447,9 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, TupleTableSlot *searchslot, TupleTableSlot *slot) { bool skip_tuple = false; - HeapTuple tuple; ResultRelInfo *resultRelInfo = estate->es_result_relation_info; Relation rel = resultRelInfo->ri_RelationDesc; - HeapTupleTableSlot *hsearchslot = (HeapTupleTableSlot *)searchslot; - - /* We expect the searchslot to contain a heap tuple. */ - Assert(TTS_IS_HEAPTUPLE(searchslot) || TTS_IS_BUFFERTUPLE(searchslot)); + ItemPointer tid = &(searchslot->tts_tid); /* For now we support only tables. */ Assert(rel->rd_rel->relkind == RELKIND_RELATION); @@ -474,14 +461,14 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, resultRelInfo->ri_TrigDesc->trig_update_before_row) { if (!ExecBRUpdateTriggers(estate, epqstate, resultRelInfo, - &hsearchslot->tuple->t_self, - NULL, slot)) + tid, NULL, slot)) skip_tuple = true; /* "do nothing" */ } if (!skip_tuple) { List *recheckIndexes = NIL; + bool update_indexes; /* Check the constraints of the tuple */ if (rel->rd_att->constr) @@ -489,23 +476,16 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, if (resultRelInfo->ri_PartitionCheck) ExecPartitionCheck(resultRelInfo, slot, estate, true); - /* Materialize slot into a tuple that we can scribble upon. */ - tuple = ExecFetchSlotHeapTuple(slot, true, NULL); + simple_table_update(rel, tid, slot, + estate->es_snapshot, &update_indexes); - /* OK, update the tuple and index entries for it */ - simple_heap_update(rel, &hsearchslot->tuple->t_self, tuple); - ItemPointerCopy(&tuple->t_self, &slot->tts_tid); - - if (resultRelInfo->ri_NumIndices > 0 && - !HeapTupleIsHeapOnly(tuple)) - recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self), - estate, false, NULL, + if (resultRelInfo->ri_NumIndices > 0 && update_indexes) + recheckIndexes = ExecInsertIndexTuples(slot, estate, false, NULL, NIL); /* AFTER ROW UPDATE Triggers */ ExecARUpdateTriggers(estate, resultRelInfo, - &(tuple->t_self), - NULL, slot, + tid, NULL, slot, recheckIndexes, NULL); list_free(recheckIndexes); @@ -525,11 +505,7 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate, bool skip_tuple = false; ResultRelInfo *resultRelInfo = estate->es_result_relation_info; Relation rel = resultRelInfo->ri_RelationDesc; - HeapTupleTableSlot *hsearchslot = (HeapTupleTableSlot *)searchslot; - - /* For now we support only tables and heap tuples. */ - Assert(rel->rd_rel->relkind == RELKIND_RELATION); - Assert(TTS_IS_HEAPTUPLE(searchslot) || TTS_IS_BUFFERTUPLE(searchslot)); + ItemPointer tid = &searchslot->tts_tid; CheckCmdReplicaIdentity(rel, CMD_DELETE); @@ -538,23 +514,18 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate, resultRelInfo->ri_TrigDesc->trig_delete_before_row) { skip_tuple = !ExecBRDeleteTriggers(estate, epqstate, resultRelInfo, - &hsearchslot->tuple->t_self, - NULL, NULL); + tid, NULL, NULL); } if (!skip_tuple) { - List *recheckIndexes = NIL; - /* OK, delete the tuple */ - simple_heap_delete(rel, &hsearchslot->tuple->t_self); + simple_table_delete(rel, tid, estate->es_snapshot); /* AFTER ROW DELETE Triggers */ ExecARDeleteTriggers(estate, resultRelInfo, - &hsearchslot->tuple->t_self, NULL, NULL); - - list_free(recheckIndexes); + tid, NULL, NULL); } } diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c index 76f0f9d66e5..cfa258fed96 100644 --- a/src/backend/executor/nodeLockRows.c +++ b/src/backend/executor/nodeLockRows.c @@ -23,6 +23,7 @@ #include "access/heapam.h" #include "access/htup_details.h" +#include "access/tableam.h" #include "access/xact.h" #include "executor/executor.h" #include "executor/nodeLockRows.h" @@ -82,11 +83,11 @@ lnext: ExecRowMark *erm = aerm->rowmark; Datum datum; bool isNull; - HeapTupleData tuple; - Buffer buffer; - HeapUpdateFailureData hufd; + ItemPointerData tid; + TM_FailureData tmfd; LockTupleMode lockmode; - HTSU_Result test; + int lockflags = 0; + TM_Result test; TupleTableSlot *markSlot; /* clear any leftover test tuple for this rel */ @@ -112,6 +113,7 @@ lnext: /* this child is inactive right now */ erm->ermActive = false; ItemPointerSetInvalid(&(erm->curCtid)); + ExecClearTuple(markSlot); continue; } } @@ -160,8 +162,8 @@ lnext: continue; } - /* okay, try to lock the tuple */ - tuple.t_self = *((ItemPointer) DatumGetPointer(datum)); + /* okay, try to lock (and fetch) the tuple */ + tid = *((ItemPointer) DatumGetPointer(datum)); switch (erm->markType) { case ROW_MARK_EXCLUSIVE: @@ -182,18 +184,23 @@ lnext: break; } - test = heap_lock_tuple(erm->relation, &tuple, - estate->es_output_cid, - lockmode, erm->waitPolicy, true, - &buffer, &hufd); - ReleaseBuffer(buffer); + lockflags = TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS; + if (!IsolationUsesXactSnapshot()) + lockflags |= TUPLE_LOCK_FLAG_FIND_LAST_VERSION; + + test = table_lock_tuple(erm->relation, &tid, estate->es_snapshot, + markSlot, estate->es_output_cid, + lockmode, erm->waitPolicy, + lockflags, + &tmfd); + switch (test) { - case HeapTupleWouldBlock: + case TableTupleWouldBlock: /* couldn't lock tuple in SKIP LOCKED mode */ goto lnext; - case HeapTupleSelfUpdated: + case TableTupleSelfModified: /* * The target tuple was already updated or deleted by the @@ -204,65 +211,48 @@ lnext: * 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 + * pretty scary (table_lock_tuple will not complain, but few + * callers expect TableTupleInvisible, and we're not one of * them). So for now, treat the tuple as deleted and do not * process. */ goto lnext; - case HeapTupleMayBeUpdated: - /* got the lock successfully */ + case TableTupleMayBeModified: + /* + * Got the lock successfully, the locked tuple saved in + * markSlot for, if needed, EvalPlanQual testing below. + */ + if (tmfd.traversed) + epq_needed = true; break; - case HeapTupleUpdated: + case TableTupleUpdated: 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 (ItemPointerEquals(&hufd.ctid, &tuple.t_self)) - { - /* Tuple was deleted, so don't return it */ - goto lnext; - } - - /* updated, so fetch and lock the updated version */ - if (!EvalPlanQualFetch(estate, erm->relation, - lockmode, erm->waitPolicy, - &hufd.ctid, hufd.xmax, - markSlot)) - { - /* - * 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 */ - tuple.t_self = markSlot->tts_tid; - - /* Remember we need to do EPQ testing */ - epq_needed = true; - - /* Continue loop until we have all target tuples */ + elog(ERROR, "unexpected table_lock_tuple status: %u", test); break; - case HeapTupleInvisible: + case TableTupleDeleted: + if (IsolationUsesXactSnapshot()) + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to concurrent update"))); + /* tuple was deleted so don't return it */ + goto lnext; + + case TableTupleInvisible: elog(ERROR, "attempted to lock invisible tuple"); break; default: - elog(ERROR, "unrecognized heap_lock_tuple status: %u", - test); + elog(ERROR, "unrecognized table_lock_tuple status: %u", test); } /* Remember locked tuple's TID for EPQ testing and WHERE CURRENT OF */ - erm->curCtid = tuple.t_self; + erm->curCtid = tid; } /* @@ -270,49 +260,6 @@ lnext: */ if (epq_needed) { - /* - * Fetch a copy of any rows that were successfully locked without any - * update having occurred. (We do this in a separate pass so as to - * avoid overhead in the common case where there are no concurrent - * updates.) Make sure any inactive child rels have NULL test tuples - * in EPQ. - */ - foreach(lc, node->lr_arowMarks) - { - ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc); - ExecRowMark *erm = aerm->rowmark; - TupleTableSlot *markSlot; - HeapTupleData tuple; - Buffer buffer; - - markSlot = EvalPlanQualSlot(&node->lr_epqstate, erm->relation, erm->rti); - - /* skip non-active child tables, but clear their test tuples */ - if (!erm->ermActive) - { - Assert(erm->rti != erm->prti); /* check it's child table */ - ExecClearTuple(markSlot); - continue; - } - - /* was tuple updated and fetched above? */ - if (!TupIsNull(markSlot)) - continue; - - /* foreign tables should have been fetched above */ - Assert(erm->relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE); - Assert(ItemPointerIsValid(&(erm->curCtid))); - - /* okay, fetch the tuple */ - tuple.t_self = erm->curCtid; - if (!heap_fetch(erm->relation, SnapshotAny, &tuple, &buffer, - false, NULL)) - elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck"); - ExecStorePinnedBufferHeapTuple(&tuple, markSlot, buffer); - ExecMaterializeSlot(markSlot); - /* successful, use tuple in slot */ - } - /* * Now fetch any non-locked source rows --- the EPQ logic knows how to * do that. diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index fa92db130bb..c106d437a7b 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -190,31 +190,33 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo, */ static void ExecCheckHeapTupleVisible(EState *estate, - HeapTuple tuple, - Buffer buffer) + Relation rel, + TupleTableSlot *slot) { if (!IsolationUsesXactSnapshot()) return; - /* - * We need buffer pin and lock to call HeapTupleSatisfiesVisibility. - * Caller should be holding pin, but not lock. - */ - LockBuffer(buffer, BUFFER_LOCK_SHARE); - if (!HeapTupleSatisfiesVisibility(tuple, estate->es_snapshot, buffer)) + if (!table_tuple_satisfies_snapshot(rel, slot, estate->es_snapshot)) { + Datum xminDatum; + TransactionId xmin; + bool isnull; + + xminDatum = slot_getsysattr(slot, MinTransactionIdAttributeNumber, &isnull); + Assert(!isnull); + xmin = DatumGetTransactionId(xminDatum); + /* * We should not raise a serialization failure if the conflict is * against a tuple inserted by our own transaction, even if it's not * visible to our snapshot. (This would happen, for example, if * conflicting keys are proposed for insertion in a single command.) */ - if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple->t_data))) + if (!TransactionIdIsCurrentTransactionId(xmin)) ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("could not serialize access due to concurrent update"))); } - LockBuffer(buffer, BUFFER_LOCK_UNLOCK); } /* @@ -223,7 +225,8 @@ ExecCheckHeapTupleVisible(EState *estate, static void ExecCheckTIDVisible(EState *estate, ResultRelInfo *relinfo, - ItemPointer tid) + ItemPointer tid, + TupleTableSlot *tempSlot) { Relation rel = relinfo->ri_RelationDesc; Buffer buffer; @@ -234,10 +237,10 @@ ExecCheckTIDVisible(EState *estate, return; tuple.t_self = *tid; - if (!heap_fetch(rel, SnapshotAny, &tuple, &buffer, false, NULL)) + if (!heap_fetch(rel, SnapshotAny, &tuple, &buffer, NULL)) elog(ERROR, "failed to fetch conflicting tuple for ON CONFLICT"); - ExecCheckHeapTupleVisible(estate, &tuple, buffer); - ReleaseBuffer(buffer); + ExecStorePinnedBufferHeapTuple(&tuple, tempSlot, buffer); + ExecCheckHeapTupleVisible(estate, rel, tempSlot); } /* ---------------------------------------------------------------- @@ -319,7 +322,6 @@ ExecInsert(ModifyTableState *mtstate, else { WCOKind wco_kind; - HeapTuple inserttuple; /* * Constraints might reference the tableoid column, so (re-)initialize @@ -417,16 +419,19 @@ ExecInsert(ModifyTableState *mtstate, * In case of ON CONFLICT DO NOTHING, do nothing. However, * verify that the tuple is visible to the executor's MVCC * snapshot at higher isolation levels. + * + * FIXME: Either comment or replace usage of + * ExecGetReturningSlot(). Need a slot that's compatible + * with the resultRelInfo table. */ Assert(onconflict == ONCONFLICT_NOTHING); - ExecCheckTIDVisible(estate, resultRelInfo, &conflictTid); + ExecCheckTIDVisible(estate, resultRelInfo, &conflictTid, + ExecGetReturningSlot(estate, resultRelInfo)); InstrCountTuples2(&mtstate->ps, 1); return NULL; } } - inserttuple = ExecFetchSlotHeapTuple(slot, true, NULL); - /* * Before we start insertion proper, acquire our "speculative * insertion lock". Others can use that to wait for us to decide @@ -434,26 +439,22 @@ ExecInsert(ModifyTableState *mtstate, * waiting for the whole transaction to complete. */ specToken = SpeculativeInsertionLockAcquire(GetCurrentTransactionId()); - HeapTupleHeaderSetSpeculativeToken(inserttuple->t_data, specToken); /* insert the tuple, with the speculative token */ - heap_insert(resultRelationDesc, inserttuple, - estate->es_output_cid, - HEAP_INSERT_SPECULATIVE, - NULL); - slot->tts_tableOid = RelationGetRelid(resultRelationDesc); - ItemPointerCopy(&inserttuple->t_self, &slot->tts_tid); + table_insert_speculative(resultRelationDesc, slot, + estate->es_output_cid, + 0, + NULL, + specToken); /* insert index entries for tuple */ - recheckIndexes = ExecInsertIndexTuples(slot, &(inserttuple->t_self), + recheckIndexes = ExecInsertIndexTuples(slot, estate, true, &specConflict, arbiterIndexes); /* adjust the tuple's state accordingly */ - if (!specConflict) - heap_finish_speculative(resultRelationDesc, inserttuple); - else - heap_abort_speculative(resultRelationDesc, inserttuple); + table_complete_speculative(resultRelationDesc, slot, + specToken, specConflict); /* * Wake up anyone waiting for our decision. They will re-check @@ -479,23 +480,14 @@ ExecInsert(ModifyTableState *mtstate, } else { - /* - * insert the tuple normally. - * - * Note: heap_insert returns the tid (location) of the new tuple - * in the t_self field. - */ - inserttuple = ExecFetchSlotHeapTuple(slot, true, NULL); - heap_insert(resultRelationDesc, inserttuple, - estate->es_output_cid, - 0, NULL); - slot->tts_tableOid = RelationGetRelid(resultRelationDesc); - ItemPointerCopy(&inserttuple->t_self, &slot->tts_tid); + /* insert the tuple normally */ + table_insert(resultRelationDesc, slot, + estate->es_output_cid, + 0, NULL); /* insert index entries for tuple */ if (resultRelInfo->ri_NumIndices > 0) - recheckIndexes = ExecInsertIndexTuples(slot, &(inserttuple->t_self), - estate, false, NULL, + recheckIndexes = ExecInsertIndexTuples(slot, estate, false, NULL, NIL); } } @@ -594,8 +586,8 @@ ExecDelete(ModifyTableState *mtstate, { ResultRelInfo *resultRelInfo; Relation resultRelationDesc; - HTSU_Result result; - HeapUpdateFailureData hufd; + TM_Result result; + TM_FailureData tmfd; TupleTableSlot *slot = NULL; TransitionCaptureState *ar_delete_trig_tcs; @@ -671,15 +663,17 @@ ExecDelete(ModifyTableState *mtstate, * mode transactions. */ ldelete:; - result = heap_delete(resultRelationDesc, tupleid, - estate->es_output_cid, - estate->es_crosscheck_snapshot, - true /* wait for commit */ , - &hufd, - changingPart); + result = table_delete(resultRelationDesc, tupleid, + estate->es_output_cid, + estate->es_snapshot, + estate->es_crosscheck_snapshot, + true /* wait for commit */ , + &tmfd, + changingPart); + switch (result) { - case HeapTupleSelfUpdated: + case TableTupleSelfModified: /* * The target tuple was already updated or deleted by the @@ -705,7 +699,7 @@ ldelete:; * can re-execute the DELETE and then return NULL to cancel * the outer delete. */ - if (hufd.cmax != estate->es_output_cid) + if (tmfd.cmax != estate->es_output_cid) ereport(ERROR, (errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION), errmsg("tuple to be updated was already modified by an operation triggered by the current command"), @@ -714,52 +708,97 @@ ldelete:; /* Else, already deleted by self; nothing to do */ return NULL; - case HeapTupleMayBeUpdated: + case TableTupleMayBeModified: break; - case HeapTupleUpdated: + case TableTupleUpdated: + { + TupleTableSlot *inputslot; + TupleTableSlot *epqslot; + + if (IsolationUsesXactSnapshot()) + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to concurrent update"))); + + /* + * Already know that we're going to need to do EPQ, so + * fetch tuple directly into the right slot. + */ + EvalPlanQualBegin(epqstate, estate); + inputslot = EvalPlanQualSlot(epqstate, resultRelationDesc, + resultRelInfo->ri_RangeTableIndex); + + result = table_lock_tuple(resultRelationDesc, tupleid, + estate->es_snapshot, + inputslot, estate->es_output_cid, + LockTupleExclusive, LockWaitBlock, + TUPLE_LOCK_FLAG_FIND_LAST_VERSION, + &tmfd); + + switch (result) + { + case TableTupleMayBeModified: + Assert(tmfd.traversed); + epqslot = EvalPlanQual(estate, + epqstate, + resultRelationDesc, + resultRelInfo->ri_RangeTableIndex, + inputslot); + if (TupIsNull(epqslot)) + /* Tuple not passing quals anymore, exiting... */ + return NULL; + + /* + * If requested, skip delete and pass back the updated + * row. + */ + if (epqreturnslot) + { + *epqreturnslot = epqslot; + return NULL; + } + else + goto ldelete; + + case TableTupleDeleted: + /* tuple already deleted; nothing to do */ + return NULL; + + default: + /* + * TableTupleInvisible should be impossible + * because we're waiting for updated row versions, + * and would already have errored out if the first + * version is invisible. + * + * TableTupleSelfModified should be impossible, as + * we'd otherwise should have hit the + * TableTupleSelfModified case in response to + * table_delete above. + * + * TableTupleUpdated should be impossible, because + * we're locking the latest version via + * TUPLE_LOCK_FLAG_FIND_LAST_VERSION. + */ + elog(ERROR, "unexpected table_lock_tuple status: %u", result); + return NULL; + } + + Assert(false); + break; + } + + case TableTupleDeleted: 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 deleted was already moved to another partition due to concurrent update"))); - - if (!ItemPointerEquals(tupleid, &hufd.ctid)) - { - TupleTableSlot *my_epqslot; - - my_epqslot = EvalPlanQual(estate, - epqstate, - resultRelationDesc, - resultRelInfo->ri_RangeTableIndex, - LockTupleExclusive, - &hufd.ctid, - hufd.xmax); - if (!TupIsNull(my_epqslot)) - { - *tupleid = hufd.ctid; - - /* - * If requested, skip delete and pass back the updated - * row. - */ - if (epqreturnslot) - { - *epqreturnslot = my_epqslot; - return NULL; - } - else - goto ldelete; - } - } + errmsg("could not serialize access due to concurrent delete"))); /* tuple already deleted; nothing to do */ return NULL; default: - elog(ERROR, "unrecognized heap_delete status: %u", result); + elog(ERROR, "unrecognized table_delete status: %u", result); return NULL; } @@ -842,7 +881,7 @@ ldelete:; deltuple->t_self = *tupleid; if (!heap_fetch(resultRelationDesc, SnapshotAny, - deltuple, &buffer, false, NULL)) + deltuple, &buffer, NULL)) elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING"); ExecStorePinnedBufferHeapTuple(deltuple, slot, buffer); @@ -897,11 +936,10 @@ ExecUpdate(ModifyTableState *mtstate, EState *estate, bool canSetTag) { - HeapTuple updatetuple; ResultRelInfo *resultRelInfo; Relation resultRelationDesc; - HTSU_Result result; - HeapUpdateFailureData hufd; + TM_Result result; + TM_FailureData tmfd; List *recheckIndexes = NIL; TupleConversionMap *saved_tcs_map = NULL; @@ -925,7 +963,7 @@ ExecUpdate(ModifyTableState *mtstate, { if (!ExecBRUpdateTriggers(estate, epqstate, resultRelInfo, tupleid, oldtuple, slot)) - return NULL; /* "do nothing" */ + return NULL; /* "do nothing" */ } /* INSTEAD OF ROW UPDATE Triggers */ @@ -934,7 +972,7 @@ ExecUpdate(ModifyTableState *mtstate, { if (!ExecIRUpdateTriggers(estate, resultRelInfo, oldtuple, slot)) - return NULL; /* "do nothing" */ + return NULL; /* "do nothing" */ } else if (resultRelInfo->ri_FdwRoutine) { @@ -960,6 +998,7 @@ ExecUpdate(ModifyTableState *mtstate, { LockTupleMode lockmode; bool partition_constraint_failed; + bool update_indexes; /* * Constraints might reference the tableoid column, so (re-)initialize @@ -973,11 +1012,14 @@ ExecUpdate(ModifyTableState *mtstate, * If we generate a new candidate tuple after EvalPlanQual testing, we * must loop back here and recheck any RLS policies and constraints. * (We don't need to redo triggers, however. If there are any BEFORE - * triggers then trigger.c will have done heap_lock_tuple to lock the + * triggers then trigger.c will have done table_lock_tuple to lock the * correct tuple, so there's no need to do them again.) */ lreplace:; + /* ensure slot is independent, consider e.g. EPQ */ + ExecMaterializeSlot(slot); + /* * If partition constraint fails, this row might get moved to another * partition, in which case we should check the RLS CHECK policy just @@ -1145,18 +1187,16 @@ lreplace:; * needed for referential integrity updates in transaction-snapshot * mode transactions. */ - updatetuple = ExecFetchSlotHeapTuple(slot, true, NULL); - result = heap_update(resultRelationDesc, tupleid, - updatetuple, - estate->es_output_cid, - estate->es_crosscheck_snapshot, - true /* wait for commit */ , - &hufd, &lockmode); - ItemPointerCopy(&updatetuple->t_self, &slot->tts_tid); + result = table_update(resultRelationDesc, tupleid, slot, + estate->es_output_cid, + estate->es_snapshot, + estate->es_crosscheck_snapshot, + true /* wait for commit */ , + &tmfd, &lockmode, &update_indexes); switch (result) { - case HeapTupleSelfUpdated: + case TableTupleSelfModified: /* * The target tuple was already updated or deleted by the @@ -1181,7 +1221,7 @@ lreplace:; * can re-execute the UPDATE (assuming it can figure out how) * and then return NULL to cancel the outer update. */ - if (hufd.cmax != estate->es_output_cid) + if (tmfd.cmax != estate->es_output_cid) ereport(ERROR, (errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION), errmsg("tuple to be updated was already modified by an operation triggered by the current command"), @@ -1190,64 +1230,80 @@ lreplace:; /* Else, already updated by self; nothing to do */ return NULL; - case HeapTupleMayBeUpdated: + case TableTupleMayBeModified: break; - case HeapTupleUpdated: + case TableTupleUpdated: + { + TupleTableSlot *inputslot; + TupleTableSlot *epqslot; + + if (IsolationUsesXactSnapshot()) + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to concurrent update"))); + + /* + * Already know that we're going to need to do EPQ, so + * fetch tuple directly into the right slot. + */ + EvalPlanQualBegin(epqstate, estate); + inputslot = EvalPlanQualSlot(epqstate, resultRelationDesc, + resultRelInfo->ri_RangeTableIndex); + + result = table_lock_tuple(resultRelationDesc, tupleid, + estate->es_snapshot, + inputslot, estate->es_output_cid, + lockmode, LockWaitBlock, + TUPLE_LOCK_FLAG_FIND_LAST_VERSION, + &tmfd); + + switch (result) + { + case TableTupleMayBeModified: + Assert(tmfd.traversed); + + epqslot = EvalPlanQual(estate, + epqstate, + resultRelationDesc, + resultRelInfo->ri_RangeTableIndex, + inputslot); + if (TupIsNull(epqslot)) + /* Tuple not passing quals anymore, exiting... */ + return NULL; + + slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot); + goto lreplace; + + case TableTupleDeleted: + /* tuple already deleted; nothing to do */ + return NULL; + + default: + /* see table_lock_tuple call in ExecDelete() */ + elog(ERROR, "unexpected table_lock_tuple status: %u", result); + return NULL; + } + } + + break; + + case TableTupleDeleted: 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 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); - goto lreplace; - } - } + errmsg("could not serialize access due to concurrent delete"))); /* tuple already deleted; nothing to do */ return NULL; default: - elog(ERROR, "unrecognized heap_update status: %u", result); + elog(ERROR, "unrecognized table_update status: %u", result); return NULL; } - /* - * Note: instead of having to update the old index tuples associated - * with the heap tuple, all we do is form and insert new index tuples. - * This is because UPDATEs are actually DELETEs and INSERTs, and index - * tuple deletion is done later by VACUUM (see notes in ExecDelete). - * All we do here is insert new index tuples. -cim 9/27/89 - */ - - /* - * insert index entries for tuple - * - * Note: heap_update returns the tid (location) of the new tuple in - * the t_self field. - * - * If it's a HOT update, we mustn't insert new index entries. - */ - if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(updatetuple)) - recheckIndexes = ExecInsertIndexTuples(slot, &(updatetuple->t_self), - estate, false, NULL, NIL); + /* insert index entries for tuple if necessary */ + if (resultRelInfo->ri_NumIndices > 0 && update_indexes) + recheckIndexes = ExecInsertIndexTuples(slot, estate, false, NULL, NIL); } if (canSetTag) @@ -1306,11 +1362,12 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, Relation relation = resultRelInfo->ri_RelationDesc; ExprState *onConflictSetWhere = resultRelInfo->ri_onConflict->oc_WhereClause; TupleTableSlot *existing = resultRelInfo->ri_onConflict->oc_Existing; - HeapTupleData tuple; - HeapUpdateFailureData hufd; + TM_FailureData tmfd; LockTupleMode lockmode; - HTSU_Result test; - Buffer buffer; + TM_Result test; + Datum xminDatum; + TransactionId xmin; + bool isnull; /* Determine lock mode to use */ lockmode = ExecUpdateLockMode(estate, resultRelInfo); @@ -1321,17 +1378,18 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, * previous conclusion that the tuple is conclusively committed is not * true anymore. */ - tuple.t_self = *conflictTid; - test = heap_lock_tuple(relation, &tuple, estate->es_output_cid, - lockmode, LockWaitBlock, false, &buffer, - &hufd); + test = table_lock_tuple(relation, conflictTid, + estate->es_snapshot, + existing, estate->es_output_cid, + lockmode, LockWaitBlock, 0, + &tmfd); switch (test) { - case HeapTupleMayBeUpdated: + case TableTupleMayBeModified: /* success! */ break; - case HeapTupleInvisible: + case TableTupleInvisible: /* * This can occur when a just inserted tuple is updated again in @@ -1339,7 +1397,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, * conflicting key values are inserted. * * This is somewhat similar to the ExecUpdate() - * HeapTupleSelfUpdated case. We do not want to proceed because + * TableTupleSelfModified case. We do not want to proceed because * it would lead to the same row being updated a second time in * some unspecified order, and in contrast to plain UPDATEs * there's no historical behavior to break. @@ -1349,7 +1407,13 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, * that for SQL MERGE, an exception must be raised in the event of * an attempt to update the same row twice. */ - if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple.t_data))) + xminDatum = slot_getsysattr(existing, + MinTransactionIdAttributeNumber, + &isnull); + Assert(!isnull); + xmin = DatumGetTransactionId(xminDatum); + + if (TransactionIdIsCurrentTransactionId(xmin)) ereport(ERROR, (errcode(ERRCODE_CARDINALITY_VIOLATION), errmsg("ON CONFLICT DO UPDATE command cannot affect row a second time"), @@ -1359,7 +1423,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, elog(ERROR, "attempted to lock invisible tuple"); break; - case HeapTupleSelfUpdated: + case TableTupleSelfModified: /* * This state should never be reached. As a dirty snapshot is used @@ -1369,7 +1433,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, elog(ERROR, "unexpected self-updated tuple"); break; - case HeapTupleUpdated: + case TableTupleUpdated: if (IsolationUsesXactSnapshot()) ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), @@ -1381,7 +1445,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, * be lock is moved to another partition due to concurrent update * of the partition key. */ - Assert(!ItemPointerIndicatesMovedPartitions(&hufd.ctid)); + Assert(!ItemPointerIndicatesMovedPartitions(&tmfd.ctid)); /* * Tell caller to try again from the very start. @@ -1390,11 +1454,20 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, * loop here, as the new version of the row might not conflict * anymore, or the conflicting tuple has actually been deleted. */ - ReleaseBuffer(buffer); + ExecClearTuple(existing); + return false; + + case TableTupleDeleted: + if (IsolationUsesXactSnapshot()) + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to concurrent delete"))); + + ExecClearTuple(existing); return false; default: - elog(ERROR, "unrecognized heap_lock_tuple status: %u", test); + elog(ERROR, "unrecognized table_lock_tuple status: %u", test); } /* Success, the tuple is locked. */ @@ -1412,10 +1485,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, * snapshot. This is in line with the way UPDATE deals with newer tuple * versions. */ - ExecCheckHeapTupleVisible(estate, &tuple, buffer); - - /* Store target's existing tuple in the state's dedicated slot */ - ExecStorePinnedBufferHeapTuple(&tuple, existing, buffer); + ExecCheckHeapTupleVisible(estate, relation, existing); /* * Make tuple and any needed join variables available to ExecQual and @@ -1462,7 +1532,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, /* * Note that it is possible that the target tuple has been modified in - * this session, after the above heap_lock_tuple. We choose to not error + * this session, after the above table_lock_tuple. We choose to not error * out in that case, in line with ExecUpdate's treatment of similar cases. * This can happen if an UPDATE is triggered from within ExecQual(), * ExecWithCheckOptions() or ExecProject() above, e.g. by selecting from a @@ -1470,7 +1540,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, */ /* Execute UPDATE with projection */ - *returning = ExecUpdate(mtstate, &tuple.t_self, NULL, + *returning = ExecUpdate(mtstate, conflictTid, NULL, resultRelInfo->ri_onConflict->oc_ProjSlot, planSlot, &mtstate->mt_epqstate, mtstate->ps.state, diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c index 08872ef9b4f..0e6a0748c8c 100644 --- a/src/backend/executor/nodeTidscan.c +++ b/src/backend/executor/nodeTidscan.c @@ -376,7 +376,7 @@ TidNext(TidScanState *node) if (node->tss_isCurrentOf) heap_get_latest_tid(heapRelation, snapshot, &tuple->t_self); - if (heap_fetch(heapRelation, snapshot, tuple, &buffer, false, NULL)) + if (heap_fetch(heapRelation, snapshot, tuple, &buffer, NULL)) { /* * Store the scanned tuple in the scan tuple slot of the scan diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index eb9e160bfd9..505fce96b0e 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -19,6 +19,7 @@ #include "access/sdir.h" #include "access/skey.h" #include "access/table.h" /* for backward compatibility */ +#include "access/tableam.h" #include "nodes/lockoptions.h" #include "nodes/primnodes.h" #include "storage/bufpage.h" @@ -28,39 +29,16 @@ /* "options" flag bits for heap_insert */ -#define HEAP_INSERT_SKIP_WAL 0x0001 -#define HEAP_INSERT_SKIP_FSM 0x0002 -#define HEAP_INSERT_FROZEN 0x0004 -#define HEAP_INSERT_SPECULATIVE 0x0008 -#define HEAP_INSERT_NO_LOGICAL 0x0010 +#define HEAP_INSERT_SKIP_WAL TABLE_INSERT_SKIP_WAL +#define HEAP_INSERT_SKIP_FSM TABLE_INSERT_SKIP_FSM +#define HEAP_INSERT_FROZEN TABLE_INSERT_FROZEN +#define HEAP_INSERT_NO_LOGICAL TABLE_INSERT_NO_LOGICAL +#define HEAP_INSERT_SPECULATIVE 0x0010 typedef struct BulkInsertStateData *BulkInsertState; #define MaxLockTupleMode LockTupleExclusive -/* - * When heap_update, heap_delete, or heap_lock_tuple fail because the target - * tuple is already outdated, they fill in this struct to provide information - * to the caller about what happened. - * ctid is the target's ctid link: it is the same as the target's TID if the - * target was deleted, or the location of the replacement tuple if the target - * was updated. - * xmax is the outdating transaction's XID. If the caller wants to visit the - * replacement tuple, it must check that this matches before believing the - * replacement is really a match. - * cmax is the outdating command's CID, but only when the failure code is - * HeapTupleSelfUpdated (i.e., something in the current transaction outdated - * the tuple); otherwise cmax is zero. (We make this restriction because - * HeapTupleHeaderGetCmax doesn't work for tuples outdated in other - * transactions.) - */ -typedef struct HeapUpdateFailureData -{ - ItemPointerData ctid; - TransactionId xmax; - CommandId cmax; -} HeapUpdateFailureData; - /* * Descriptor for heap table scans. */ @@ -150,8 +128,7 @@ extern bool heap_getnextslot(TableScanDesc sscan, ScanDirection direction, struct TupleTableSlot *slot); extern bool heap_fetch(Relation relation, Snapshot snapshot, - HeapTuple tuple, Buffer *userbuf, bool keep_buf, - Relation stats_relation); + HeapTuple tuple, Buffer *userbuf, Relation stats_relation); extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer, Snapshot snapshot, HeapTuple heapTuple, bool *all_dead, bool first_call); @@ -170,19 +147,20 @@ extern void heap_insert(Relation relation, HeapTuple tup, CommandId cid, int options, BulkInsertState bistate); extern void heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, CommandId cid, int options, BulkInsertState bistate); -extern HTSU_Result heap_delete(Relation relation, ItemPointer tid, +extern TM_Result heap_delete(Relation relation, ItemPointer tid, CommandId cid, Snapshot crosscheck, bool wait, - HeapUpdateFailureData *hufd, bool changingPart); -extern void heap_finish_speculative(Relation relation, HeapTuple tuple); -extern void heap_abort_speculative(Relation relation, HeapTuple tuple); -extern HTSU_Result heap_update(Relation relation, ItemPointer otid, + struct TM_FailureData *tmfd, bool changingPart); +extern void heap_finish_speculative(Relation relation, ItemPointer tid); +extern void heap_abort_speculative(Relation relation, ItemPointer tid); +extern TM_Result heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait, - HeapUpdateFailureData *hufd, LockTupleMode *lockmode); -extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple, + struct TM_FailureData *tmfd, LockTupleMode *lockmode); +extern TM_Result heap_lock_tuple(Relation relation, ItemPointer tid, CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy, - bool follow_update, - Buffer *buffer, HeapUpdateFailureData *hufd); + bool follow_update, HeapTuple tuple, + Buffer *buffer, struct TM_FailureData *tmfd); + extern void heap_inplace_update(Relation relation, HeapTuple tuple); extern bool heap_freeze_tuple(HeapTupleHeader tuple, TransactionId relfrozenxid, TransactionId relminmxid, @@ -223,7 +201,7 @@ extern void heap_vacuum_rel(Relation onerel, /* in heap/heapam_visibility.c */ extern bool HeapTupleSatisfiesVisibility(HeapTuple stup, Snapshot snapshot, Buffer buffer); -extern HTSU_Result HeapTupleSatisfiesUpdate(HeapTuple stup, CommandId curcid, +extern TM_Result HeapTupleSatisfiesUpdate(HeapTuple stup, CommandId curcid, Buffer buffer); extern HTSV_Result HeapTupleSatisfiesVacuum(HeapTuple stup, TransactionId OldestXmin, Buffer buffer); diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index 50b8ab93539..b257e9a2aa5 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -27,6 +27,73 @@ extern char *default_table_access_method; extern bool synchronize_seqscans; +struct BulkInsertStateData; + + +/* + * Result codes for table_{update,delete,lock}_tuple, and for visibility + * routines inside table AMs. + */ +typedef enum TM_Result +{ + /* Signals that the action succeeded (i.e. update/delete performed) */ + TableTupleMayBeModified, + + /* The affected tuple wasn't visible to the relevant snapshot */ + TableTupleInvisible, + + /* The affected tuple was already modified by the calling backend */ + TableTupleSelfModified, + + /* The affected tuple was updated by another transaction */ + TableTupleUpdated, + + /* The affected tuple was deleted by another transaction */ + TableTupleDeleted, + + /* + * The affected tuple is currently being modified by another session. This + * will only be returned if (update/delete/lock)_tuple are instructed not + * to wait. + */ + TableTupleBeingModified, + + /* lock couldn't be acquired, action skipped. Only used by lock_tuple */ + TableTupleWouldBlock +} TM_Result; + + +/* + * When table_update, table_delete, or table_lock_tuple fail because the target + * tuple is already outdated, they fill in this struct to provide information + * to the caller about what happened. + * ctid is the target's ctid link: it is the same as the target's TID if the + * target was deleted, or the location of the replacement tuple if the target + * was updated. + * xmax is the outdating transaction's XID. If the caller wants to visit the + * replacement tuple, it must check that this matches before believing the + * replacement is really a match. + * cmax is the outdating command's CID, but only when the failure code is + * TableTupleSelfModified (i.e., something in the current transaction outdated + * the tuple); otherwise cmax is zero. (We make this restriction because + * HeapTupleHeaderGetCmax doesn't work for tuples outdated in other + * transactions.) + */ +typedef struct TM_FailureData +{ + ItemPointerData ctid; + TransactionId xmax; + CommandId cmax; + bool traversed; +} TM_FailureData; + +/* "options" flag bits for heap_insert */ +#define TABLE_INSERT_SKIP_WAL 0x0001 +#define TABLE_INSERT_SKIP_FSM 0x0002 +#define TABLE_INSERT_FROZEN 0x0004 +#define TABLE_INSERT_NO_LOGICAL 0x0008 + + /* * API struct for a table AM. Note this must be allocated in a * server-lifetime manner, typically as a static const struct, which then gets @@ -200,6 +267,62 @@ typedef struct TableAmRoutine TupleTableSlot *slot, Snapshot snapshot); + /* ------------------------------------------------------------------------ + * Manipulations of physical tuples. + * ------------------------------------------------------------------------ + */ + + /* see table_insert() for reference about parameters */ + void (*tuple_insert) (Relation rel, TupleTableSlot *slot, CommandId cid, + int options, struct BulkInsertStateData *bistate); + + /* see table_insert() for reference about parameters */ + void (*tuple_insert_speculative) (Relation rel, + TupleTableSlot *slot, + CommandId cid, + int options, + struct BulkInsertStateData *bistate, + uint32 specToken); + + /* see table_insert() for reference about parameters */ + void (*tuple_complete_speculative) (Relation rel, + TupleTableSlot *slot, + uint32 specToken, + bool succeeded); + + /* see table_insert() for reference about parameters */ + TM_Result (*tuple_delete) (Relation rel, + ItemPointer tid, + CommandId cid, + Snapshot snapshot, + Snapshot crosscheck, + bool wait, + TM_FailureData *tmfd, + bool changingPart); + + /* see table_insert() for reference about parameters */ + TM_Result (*tuple_update) (Relation rel, + ItemPointer otid, + TupleTableSlot *slot, + CommandId cid, + Snapshot snapshot, + Snapshot crosscheck, + bool wait, + TM_FailureData *tmfd, + LockTupleMode *lockmode, + bool *update_indexes); + + /* see table_insert() for reference about parameters */ + TM_Result (*tuple_lock) (Relation rel, + ItemPointer tid, + Snapshot snapshot, + TupleTableSlot *slot, + CommandId cid, + LockTupleMode mode, + LockWaitPolicy wait_policy, + uint8 flags, + TM_FailureData *tmfd); + } TableAmRoutine; @@ -487,6 +610,230 @@ table_tuple_satisfies_snapshot(Relation rel, TupleTableSlot *slot, Snapshot snap } +/* ---------------------------------------------------------------------------- + * Functions for manipulations of physical tuples. + * ---------------------------------------------------------------------------- + */ + +/* + * Insert a tuple from a slot into table AM routine. + * + * The options bitmask allows to specify options that allow to change the + * behaviour of the AM. Several options might be ignored by AMs not supporting + * them. + * + * If the TABLE_INSERT_SKIP_WAL option is specified, the new tuple will not + * necessarily logged to WAL, even for a non-temp relation. It is the AMs + * choice whether this optimization is supported. + * + * If the TABLE_INSERT_SKIP_FSM option is specified, AMs are free to not reuse + * free space in the relation. This can save some cycles when we know the + * relation is new and doesn't contain useful amounts of free space. It's + * commonly passed directly to RelationGetBufferForTuple, see for more info. + * + * TABLE_INSERT_FROZEN should only be specified for inserts into + * relfilenodes created during the current subtransaction and when + * there are no prior snapshots or pre-existing portals open. + * This causes rows to be frozen, which is an MVCC violation and + * requires explicit options chosen by user. + * + * TABLE_INSERT_NO_LOGICAL force-disables the emitting of logical decoding + * information for the tuple. This should solely be used during table rewrites + * where RelationIsLogicallyLogged(relation) is not yet accurate for the new + * relation. + * + * Note that most of these options will be applied when inserting into the + * heap's TOAST table, too, if the tuple requires any out-of-line data + * + * + * The BulkInsertState object (if any; bistate can be NULL for default + * behavior) is also just passed through to RelationGetBufferForTuple. + * + * On return the slot's tts_tid and tts_tableOid are updated to reflect the + * insertion. But note that any toasting of fields within the slot is NOT + * reflected in the slots contents. + */ +static inline void +table_insert(Relation rel, TupleTableSlot *slot, CommandId cid, + int options, struct BulkInsertStateData *bistate) +{ + rel->rd_tableam->tuple_insert(rel, slot, cid, options, + bistate); +} + +/* + * Perform a "speculative insertion". These can be backed out afterwards + * without aborting the whole transaction. Other sessions can wait for the + * speculative insertion to be confirmed, turning it into a regular tuple, or + * aborted, as if it never existed. Speculatively inserted tuples behave as + * "value locks" of short duration, used to implement INSERT .. ON CONFLICT. + * + * A transaction having performed a speculative insertion has to either abort, + * or finish the speculative insertion with + * table_complete_speculative(succeeded = ...). + */ +static inline void +table_insert_speculative(Relation rel, TupleTableSlot *slot, CommandId cid, + int options, struct BulkInsertStateData *bistate, uint32 specToken) +{ + rel->rd_tableam->tuple_insert_speculative(rel, slot, cid, options, + bistate, specToken); +} + +/* + * Complete "speculative insertion" started in the same transaction. If + * succeeded is true, the tuple is fully inserted, if false, it's removed. + */ +static inline void +table_complete_speculative(Relation rel, TupleTableSlot *slot, uint32 specToken, + bool succeeded) +{ + return rel->rd_tableam->tuple_complete_speculative(rel, slot, specToken, + succeeded); +} + +/* + * Delete a tuple. + * + * NB: do not call this directly unless prepared to deal with + * concurrent-update conditions. Use simple_table_delete instead. + * + * Input parameters: + * relation - table to be modified (caller must hold suitable lock) + * tid - TID of tuple to be deleted + * cid - delete command ID (used for visibility test, and stored into + * cmax if successful) + * crosscheck - if not InvalidSnapshot, also check tuple against this + * wait - true if should wait for any conflicting update to commit/abort + * Output parameters: + * tmfd - filled in failure cases (see below) + * changingPart - true iff the tuple is being moved to another partition + * table due to an update of the partition key. Otherwise, false. + * + * Normal, successful return value is TableTupleMayBeModified, which + * actually means we did delete it. Failure return codes are + * TableTupleSelfModified, TableTupleUpdated, or TableTupleBeingModified + * (the last only possible if wait == false). + * + * In the failure cases, the routine fills *tmfd with the tuple's t_ctid, + * t_xmax, and, if possible, and, if possible, t_cmax. See comments for + * struct TM_FailureData for additional info. + */ +static inline TM_Result +table_delete(Relation rel, ItemPointer tid, CommandId cid, + Snapshot snapshot, Snapshot crosscheck, bool wait, + TM_FailureData *tmfd, bool changingPart) +{ + return rel->rd_tableam->tuple_delete(rel, tid, cid, + snapshot, crosscheck, + wait, tmfd, changingPart); +} + +/* + * Update a tuple. + * + * NB: do not call this directly unless you are prepared to deal with + * concurrent-update conditions. Use simple_table_update instead. + * + * Input parameters: + * relation - table to be modified (caller must hold suitable lock) + * otid - TID of old tuple to be replaced + * newtup - newly constructed tuple data to store + * cid - update command ID (used for visibility test, and stored into + * cmax/cmin if successful) + * crosscheck - if not InvalidSnapshot, also check old tuple against this + * wait - true if should wait for any conflicting update to commit/abort + * Output parameters: + * tmfd - filled in failure cases (see below) + * lockmode - filled with lock mode acquired on tuple + * update_indexes - in success cases this is set to true if new index entries + * are required for this tuple + * + * Normal, successful return value is TableTupleMayBeModified, which + * actually means we *did* update it. Failure return codes are + * TableTupleSelfModified, TableTupleUpdated, or TableTupleBeingModified + * (the last only possible if wait == false). + * + * On success, the header fields of *newtup are updated to match the new + * stored tuple; in particular, newtup->t_self is set to the TID where the + * new tuple was inserted, and its HEAP_ONLY_TUPLE flag is set iff a HOT + * update was done. However, any TOAST changes in the new tuple's + * data are not reflected into *newtup. + * + * In the failure cases, the routine fills *tmfd with the tuple's t_ctid, + * t_xmax, and, if possible, t_cmax. See comments for struct TM_FailureData + * for additional info. + */ +static inline TM_Result +table_update(Relation rel, ItemPointer otid, TupleTableSlot *slot, + CommandId cid, Snapshot snapshot, Snapshot crosscheck, bool wait, + TM_FailureData *tmfd, LockTupleMode *lockmode, + bool *update_indexes) +{ + return rel->rd_tableam->tuple_update(rel, otid, slot, + cid, snapshot, crosscheck, + wait, tmfd, + lockmode, update_indexes); +} + +/* + * Lock a tuple in the specified mode. + * + * Input parameters: + * relation: relation containing tuple (caller must hold suitable lock) + * tid: TID of tuple to lock + * snapshot: snapshot to use for visibility determinations + * cid: current command ID (used for visibility test, and stored into + * tuple's cmax if lock is successful) + * mode: lock mode desired + * wait_policy: what to do if tuple lock is not available + * flags: + * If TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS, follow the update chain to + * also lock descendant tuples if lock modes don't conflict. + * If TUPLE_LOCK_FLAG_FIND_LAST_VERSION, update chain and lock lastest + * version. + * + * Output parameters: + * *slot: contains the target tuple + * *tmfd: filled in failure cases (see below) + * + * Function result may be: + * TableTupleMayBeModified: lock was successfully acquired + * TableTupleInvisible: lock failed because tuple was never visible to us + * TableTupleSelfModified: lock failed because tuple updated by self + * TableTupleUpdated: lock failed because tuple updated by other xact + * TableTupleDeleted: lock failed because tuple deleted by other xact + * TableTupleWouldBlock: lock couldn't be acquired and wait_policy is skip + * + * In the failure cases other than TableTupleInvisible, the routine fills + * *tmfd with the tuple's t_ctid, t_xmax, and, if possible, t_cmax. See + * comments for struct TM_FailureData for additional info. + */ +static inline TM_Result +table_lock_tuple(Relation rel, ItemPointer tid, Snapshot snapshot, + TupleTableSlot *slot, CommandId cid, LockTupleMode mode, + LockWaitPolicy wait_policy, uint8 flags, + TM_FailureData *tmfd) +{ + return rel->rd_tableam->tuple_lock(rel, tid, snapshot, slot, + cid, mode, wait_policy, + flags, tmfd); +} + + +/* ---------------------------------------------------------------------------- + * Functions to make modifications a bit simpler. + * ---------------------------------------------------------------------------- + */ + +extern void simple_table_insert(Relation rel, TupleTableSlot *slot); +extern void simple_table_delete(Relation rel, ItemPointer tid, + Snapshot snapshot); +extern void simple_table_update(Relation rel, ItemPointer otid, + TupleTableSlot *slot, Snapshot snapshot, + bool *update_indexes); + + /* ---------------------------------------------------------------------------- * Helper functions to implement parallel scans for block oriented AMs. * ---------------------------------------------------------------------------- diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 9003f2ce583..ceacd1c6370 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -195,12 +195,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, LockTupleMode lockmode, - ItemPointer tid, TransactionId priorXmax); -extern bool EvalPlanQualFetch(EState *estate, Relation relation, - LockTupleMode lockmode, LockWaitPolicy wait_policy, - ItemPointer tid, TransactionId priorXmax, - TupleTableSlot *slot); + Relation relation, Index rti, TupleTableSlot *testslot); extern void EvalPlanQualInit(EPQState *epqstate, EState *estate, Plan *subplan, List *auxrowmarks, int epqParam); extern void EvalPlanQualSetPlan(EPQState *epqstate, @@ -569,9 +564,8 @@ extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relIn */ extern void ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative); extern void ExecCloseIndices(ResultRelInfo *resultRelInfo); -extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid, - EState *estate, bool noDupErr, bool *specConflict, - List *arbiterIndexes); +extern List *ExecInsertIndexTuples(TupleTableSlot *slot, EState *estate, bool noDupErr, + bool *specConflict, List *arbiterIndexes); extern bool ExecCheckIndexConstraints(TupleTableSlot *slot, EState *estate, ItemPointer conflictTid, List *arbiterIndexes); extern void check_exclusion_constraint(Relation heap, Relation index, diff --git a/src/include/nodes/lockoptions.h b/src/include/nodes/lockoptions.h index 8e8ccff43ca..d6b1160ab4b 100644 --- a/src/include/nodes/lockoptions.h +++ b/src/include/nodes/lockoptions.h @@ -58,4 +58,9 @@ typedef enum LockTupleMode LockTupleExclusive } LockTupleMode; +/* Follow tuples whose update is in progress if lock modes don't conflict */ +#define TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS (1 << 0) +/* Follow update chain and lock lastest version of tuple */ +#define TUPLE_LOCK_FLAG_FIND_LAST_VERSION (1 << 1) + #endif /* LOCKOPTIONS_H */ diff --git a/src/include/utils/snapshot.h b/src/include/utils/snapshot.h index e7ea5cf7b56..7bf7cad5727 100644 --- a/src/include/utils/snapshot.h +++ b/src/include/utils/snapshot.h @@ -184,17 +184,4 @@ typedef struct SnapshotData XLogRecPtr lsn; /* position in the WAL stream when taken */ } SnapshotData; -/* - * Result codes for HeapTupleSatisfiesUpdate. - */ -typedef enum -{ - HeapTupleMayBeUpdated, - HeapTupleInvisible, - HeapTupleSelfUpdated, - HeapTupleUpdated, - HeapTupleBeingUpdated, - HeapTupleWouldBlock /* can be returned by heap_tuple_lock */ -} HTSU_Result; - #endif /* SNAPSHOT_H */ diff --git a/src/test/isolation/expected/partition-key-update-1.out b/src/test/isolation/expected/partition-key-update-1.out index 37fe6a7b277..a632d7f7bad 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 diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index b301bce4b1b..0015fc0ead9 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -943,7 +943,6 @@ HSParser HSpool HStore HTAB -HTSU_Result HTSV_Result HV Hash @@ -982,7 +981,6 @@ HeapTupleData HeapTupleFields HeapTupleHeader HeapTupleHeaderData -HeapUpdateFailureData HistControl HotStandbyState I32 @@ -2282,6 +2280,8 @@ TBMSharedIteratorState TBMStatus TBlockState TIDBitmap +TM_FailureData +TM_Result TOKEN_DEFAULT_DACL TOKEN_INFORMATION_CLASS TOKEN_PRIVILEGES -- 2.21.0.dirty --qw3ipai4e5xhdytn--