serializable_lock_consistency.patch
application/octet-stream
Filename: serializable_lock_consistency.patch
Type: application/octet-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
| File | + | − |
|---|---|---|
| src/backend/access/heap/heapam.c | 0 | 0 |
| src/backend/access/transam/multixact.c | 0 | 0 |
| src/backend/commands/trigger.c | 0 | 0 |
| src/backend/executor/execMain.c | 0 | 0 |
| src/backend/executor/nodeLockRows.c | 0 | 0 |
| src/backend/executor/nodeModifyTable.c | 0 | 0 |
| src/backend/utils/time/snapmgr.c | 0 | 0 |
| src/backend/utils/time/tqual.c | 0 | 0 |
| src/include/access/heapam.h | 0 | 0 |
| src/include/access/multixact.h | 0 | 0 |
| src/include/utils/tqual.h | 0 | 0 |
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 8975191..175c656 100644
*** a/src/backend/access/heap/heapam.c
--- b/src/backend/access/heap/heapam.c
*************** static XLogRecPtr log_heap_update(Relati
*** 83,88 ****
--- 83,89 ----
bool all_visible_cleared, bool new_all_visible_cleared);
static bool HeapSatisfiesHOTUpdate(Relation relation, Bitmapset *hot_attrs,
HeapTuple oldtup, HeapTuple newtup);
+ static bool HeapSatisfiesLockersVisible(HeapTupleHeader tuple, Snapshot snapshot);
/* ----------------------------------------------------------------
*************** simple_heap_insert(Relation relation, He
*** 2033,2040 ****
* update_xmax - output parameter, used only for failure case (see below)
* 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
*
* Normal, successful return value is HeapTupleMayBeUpdated, which
* actually means we did delete it. Failure return codes are
--- 2034,2042 ----
* update_xmax - output parameter, used only for failure case (see below)
* cid - delete command ID (used for visibility test, and stored into
* cmax if successful)
* wait - true if should wait for any conflicting update to commit/abort
+ * lockcheck_snapshot - if not NULL, report the tuple as updated if it
+ * was locked by a transaction not visible under this snapshot
*
* Normal, successful return value is HeapTupleMayBeUpdated, which
* actually means we did delete it. Failure return codes are
*************** simple_heap_insert(Relation relation, He
*** 2049,2055 ****
HTSU_Result
heap_delete(Relation relation, ItemPointer tid,
ItemPointer ctid, TransactionId *update_xmax,
! CommandId cid, Snapshot crosscheck, bool wait)
{
HTSU_Result result;
TransactionId xid = GetCurrentTransactionId();
--- 2051,2057 ----
HTSU_Result
heap_delete(Relation relation, ItemPointer tid,
ItemPointer ctid, TransactionId *update_xmax,
! CommandId cid, bool wait, Snapshot lockcheck_snapshot)
{
HTSU_Result result;
TransactionId xid = GetCurrentTransactionId();
*************** l1:
*** 2170,2181 ****
else
result = HeapTupleUpdated;
}
!
! if (crosscheck != InvalidSnapshot && result == HeapTupleMayBeUpdated)
{
! /* Perform additional check for serializable RI updates */
! if (!HeapTupleSatisfiesVisibility(&tp, crosscheck, buffer))
! result = HeapTupleUpdated;
}
if (result != HeapTupleMayBeUpdated)
--- 2172,2183 ----
else
result = HeapTupleUpdated;
}
!
! /* Verify visibility of locking transactions. */
! if ((result == HeapTupleMayBeUpdated) &&
! !HeapSatisfiesLockersVisible(tp.t_data, lockcheck_snapshot))
{
! result = HeapTupleUpdated;
}
if (result != HeapTupleMayBeUpdated)
*************** l1:
*** 2183,2189 ****
Assert(result == HeapTupleSelfUpdated ||
result == HeapTupleUpdated ||
result == HeapTupleBeingUpdated);
! Assert(!(tp.t_data->t_infomask & HEAP_XMAX_INVALID));
*ctid = tp.t_data->t_ctid;
*update_xmax = HeapTupleHeaderGetXmax(tp.t_data);
UnlockReleaseBuffer(buffer);
--- 2185,2192 ----
Assert(result == HeapTupleSelfUpdated ||
result == HeapTupleUpdated ||
result == HeapTupleBeingUpdated);
! Assert(!(tp.t_data->t_infomask & HEAP_XMAX_INVALID) ||
! (tp.t_data->t_infomask & HEAP_IS_LOCKED));
*ctid = tp.t_data->t_ctid;
*update_xmax = HeapTupleHeaderGetXmax(tp.t_data);
UnlockReleaseBuffer(buffer);
*************** simple_heap_delete(Relation relation, It
*** 2313,2320 ****
result = heap_delete(relation, tid,
&update_ctid, &update_xmax,
! GetCurrentCommandId(true), InvalidSnapshot,
! true /* wait for commit */ );
switch (result)
{
case HeapTupleSelfUpdated:
--- 2316,2324 ----
result = heap_delete(relation, tid,
&update_ctid, &update_xmax,
! GetCurrentCommandId(true),
! true /* wait for commit */ ,
! InvalidSnapshot);
switch (result)
{
case HeapTupleSelfUpdated:
*************** simple_heap_delete(Relation relation, It
*** 2349,2355 ****
* update_xmax - output parameter, used only for failure case (see below)
* 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
*
* Normal, successful return value is HeapTupleMayBeUpdated, which
--- 2353,2360 ----
* update_xmax - output parameter, used only for failure case (see below)
* cid - update command ID (used for visibility test, and stored into
* cmax/cmin if successful)
! * lockcheck_snapshot - if not NULL, report the tuple as updated if it
! * was locked by a transaction not visible under this snapshot
* wait - true if should wait for any conflicting update to commit/abort
*
* Normal, successful return value is HeapTupleMayBeUpdated, which
*************** simple_heap_delete(Relation relation, It
*** 2371,2377 ****
HTSU_Result
heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
ItemPointer ctid, TransactionId *update_xmax,
! CommandId cid, Snapshot crosscheck, bool wait)
{
HTSU_Result result;
TransactionId xid = GetCurrentTransactionId();
--- 2376,2382 ----
HTSU_Result
heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
ItemPointer ctid, TransactionId *update_xmax,
! CommandId cid, bool wait, Snapshot lockcheck_snapshot)
{
HTSU_Result result;
TransactionId xid = GetCurrentTransactionId();
*************** l2:
*** 2523,2541 ****
result = HeapTupleUpdated;
}
! if (crosscheck != InvalidSnapshot && result == HeapTupleMayBeUpdated)
{
! /* Perform additional check for serializable RI updates */
! if (!HeapTupleSatisfiesVisibility(&oldtup, crosscheck, buffer))
! result = HeapTupleUpdated;
}
if (result != HeapTupleMayBeUpdated)
{
Assert(result == HeapTupleSelfUpdated ||
result == HeapTupleUpdated ||
result == HeapTupleBeingUpdated);
! Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID));
*ctid = oldtup.t_data->t_ctid;
*update_xmax = HeapTupleHeaderGetXmax(oldtup.t_data);
UnlockReleaseBuffer(buffer);
--- 2528,2548 ----
result = HeapTupleUpdated;
}
! /* Verify visibility of locking transactions. */
! if ((result == HeapTupleMayBeUpdated) &&
! !HeapSatisfiesLockersVisible(oldtup.t_data, lockcheck_snapshot))
{
! result = HeapTupleUpdated;
}
+
if (result != HeapTupleMayBeUpdated)
{
Assert(result == HeapTupleSelfUpdated ||
result == HeapTupleUpdated ||
result == HeapTupleBeingUpdated);
! Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID) ||
! (oldtup.t_data->t_infomask & HEAP_IS_LOCKED));
*ctid = oldtup.t_data->t_ctid;
*update_xmax = HeapTupleHeaderGetXmax(oldtup.t_data);
UnlockReleaseBuffer(buffer);
*************** HeapSatisfiesHOTUpdate(Relation relation
*** 2961,2966 ****
--- 2968,3057 ----
return true;
}
+
+ /*
+ * Returns false if one of the tuple's lockers committed but
+ * aren't visible according to lockcheck_snapshot, exclusing subtransactions
+ * of the current transaction.
+ * Assumes that all locking transaction either committed or aborted,
+ * but aren't still in progress.
+ */
+ static bool
+ HeapSatisfiesLockersVisible(HeapTupleHeader tuple, Snapshot lockcheck_snapshot)
+ {
+ if (lockcheck_snapshot == InvalidSnapshot)
+ return true;
+
+ if (tuple->t_infomask & HEAP_IS_LOCKED)
+ {
+ /* If the tuple was locked, we now check whether the locking
+ * transaction(s) are visible under lockcheck_snapshot. If
+ * they aren't, we pretent the tuple was updated.
+ */
+
+ if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+ {
+ TransactionId* xids;
+ int xids_l = GetMultiXactIdMembers(HeapTupleHeaderGetXmax(tuple), &xids);
+
+ if (xids_l < 1) {
+ /* The multi xact either is too old to be inspected or doesn't contain members.
+ * The second case is probably impossible, but even if not it doesn't pose
+ * any problem.
+ * In the first case, we have to trust that all xids that were contained in
+ * the xact are in fact visible under lockcheck_snapshot. Currently this
+ * is always the case, since lockcheck_snapshot is always the transaction's
+ * serializable snapshot, and we call MultiXactIdSetOldestVisible() before
+ * acquireing that snapshot.
+ */
+ return true;
+ }
+ else
+ {
+ int i;
+ for (i = 0; i < xids_l; ++i)
+ {
+ /* Ignore our own subtransactions */
+ if (TransactionIdIsCurrentTransactionId(xids[i]))
+ continue;
+
+ /* We expect to be called after the locking transactions' fates have been decided */
+ Assert(!TransactionIdIsInProgress(xids[i]));
+
+ if (!TransactionIdDidAbort(xids[i]) &&
+ XidInMVCCSnapshot(xids[i], lockcheck_snapshot))
+ {
+ /* Ups. Non-aborted, invisible locker */
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ else
+ {
+ TransactionId xid = HeapTupleHeaderGetXmax(tuple);
+
+ /* Ignore our own subtransactions */
+ if (TransactionIdIsCurrentTransactionId(xid))
+ return true;
+
+ /* We expect to be called after the locking transaction's fate has been decided, excluding
+ ourself. */
+ Assert(!TransactionIdIsInProgress(xid));
+
+ /* Locker must either be visible or have aborted */
+ return TransactionIdDidAbort(xid) ||
+ !XidInMVCCSnapshot(xid, lockcheck_snapshot);
+ }
+ }
+ else
+ {
+ /* Tuple wasn't locked */
+ return true;
+ }
+ }
+
/*
* simple_heap_update - replace a tuple
*
*************** simple_heap_update(Relation relation, It
*** 2978,2985 ****
result = heap_update(relation, otid, tup,
&update_ctid, &update_xmax,
! GetCurrentCommandId(true), InvalidSnapshot,
! true /* wait for commit */ );
switch (result)
{
case HeapTupleSelfUpdated:
--- 3069,3076 ----
result = heap_update(relation, otid, tup,
&update_ctid, &update_xmax,
! GetCurrentCommandId(true),
! true /* wait for commit */, InvalidSnapshot);
switch (result)
{
case HeapTupleSelfUpdated:
*************** simple_heap_update(Relation relation, It
*** 3013,3018 ****
--- 3104,3112 ----
* tuple's cmax if lock is successful)
* mode: indicates if shared or exclusive tuple lock is desired
* nowait: if true, ereport rather than blocking if lock not available
+ * lockcheck_snapshot: if not NULL, report the tuple as updated if it
+ * was locked by a transaction not visible under
+ * this snapshot
*
* Output parameters:
* *tuple: all fields filled in
*************** simple_heap_update(Relation relation, It
*** 3066,3072 ****
HTSU_Result
heap_lock_tuple(Relation relation, HeapTuple tuple, Buffer *buffer,
ItemPointer ctid, TransactionId *update_xmax,
! CommandId cid, LockTupleMode mode, bool nowait)
{
HTSU_Result result;
ItemPointer tid = &(tuple->t_self);
--- 3160,3167 ----
HTSU_Result
heap_lock_tuple(Relation relation, HeapTuple tuple, Buffer *buffer,
ItemPointer ctid, TransactionId *update_xmax,
! CommandId cid, LockTupleMode mode, bool nowait,
! Snapshot lockcheck_snapshot)
{
HTSU_Result result;
ItemPointer tid = &(tuple->t_self);
*************** l3:
*** 3246,3256 ****
else
result = HeapTupleUpdated;
}
if (result != HeapTupleMayBeUpdated)
{
Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated);
! Assert(!(tuple->t_data->t_infomask & HEAP_XMAX_INVALID));
*ctid = tuple->t_data->t_ctid;
*update_xmax = HeapTupleHeaderGetXmax(tuple->t_data);
LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
--- 3341,3359 ----
else
result = HeapTupleUpdated;
}
+
+ /* Verify visibility of locking transactions */
+ if ((result == HeapTupleMayBeUpdated) &&
+ !HeapSatisfiesLockersVisible(tuple->t_data, lockcheck_snapshot))
+ {
+ result = HeapTupleUpdated;
+ }
if (result != HeapTupleMayBeUpdated)
{
Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated);
! Assert(!(tuple->t_data->t_infomask & HEAP_XMAX_INVALID) ||
! (tuple->t_data->t_infomask & HEAP_IS_LOCKED));
*ctid = tuple->t_data->t_ctid;
*update_xmax = HeapTupleHeaderGetXmax(tuple->t_data);
LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index b5bb311..47ff5de 100644
*** a/src/backend/access/transam/multixact.c
--- b/src/backend/access/transam/multixact.c
*************** static MemoryContext MXactContext = NULL
*** 211,217 ****
#endif
/* internal MultiXactId management */
- static void MultiXactIdSetOldestVisible(void);
static MultiXactId CreateMultiXactId(int nxids, TransactionId *xids);
static void RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
int nxids, TransactionId *xids);
--- 211,216 ----
*************** MultiXactIdSetOldestMember(void)
*** 531,537 ****
* there is no live transaction, now or later, that can be a member of any
* MultiXactId older than the OldestVisibleMXactId we compute here.
*/
! static void
MultiXactIdSetOldestVisible(void)
{
if (!MultiXactIdIsValid(OldestVisibleMXactId[MyBackendId]))
--- 530,536 ----
* there is no live transaction, now or later, that can be a member of any
* MultiXactId older than the OldestVisibleMXactId we compute here.
*/
! void
MultiXactIdSetOldestVisible(void)
{
if (!MultiXactIdIsValid(OldestVisibleMXactId[MyBackendId]))
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 2cbc192..120ea60 100644
*** a/src/backend/commands/trigger.c
--- b/src/backend/commands/trigger.c
*************** ltrmark:;
*** 2347,2353 ****
test = heap_lock_tuple(relation, &tuple, &buffer,
&update_ctid, &update_xmax,
estate->es_output_cid,
! LockTupleExclusive, false);
switch (test)
{
case HeapTupleSelfUpdated:
--- 2347,2355 ----
test = heap_lock_tuple(relation, &tuple, &buffer,
&update_ctid, &update_xmax,
estate->es_output_cid,
! LockTupleExclusive, false,
! IsXactIsoLevelSerializable ? estate->es_snapshot :
! InvalidSnapshot);
switch (test)
{
case HeapTupleSelfUpdated:
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d299310..fd5ed3c 100644
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
*************** EvalPlanQualFetch(EState *estate, Relati
*** 1518,1527 ****
/*
* This is a live tuple, so now try to lock it.
*/
test = heap_lock_tuple(relation, &tuple, &buffer,
&update_ctid, &update_xmax,
estate->es_output_cid,
! lockmode, false);
/* We now have two pins on the buffer, get rid of one */
ReleaseBuffer(buffer);
--- 1518,1530 ----
/*
* This is a live tuple, so now try to lock it.
*/
+ Assert(!IsXactIsoLevelSerializable || (estate->es_snapshot != InvalidSnapshot));
test = heap_lock_tuple(relation, &tuple, &buffer,
&update_ctid, &update_xmax,
estate->es_output_cid,
! lockmode, false,
! IsXactIsoLevelSerializable ? estate->es_snapshot :
! InvalidSnapshot);
/* We now have two pins on the buffer, get rid of one */
ReleaseBuffer(buffer);
diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c
index 00f0562..b771b19 100644
*** a/src/backend/executor/nodeLockRows.c
--- b/src/backend/executor/nodeLockRows.c
*************** lnext:
*** 71,76 ****
--- 71,77 ----
ItemPointerData update_ctid;
TransactionId update_xmax;
LockTupleMode lockmode;
+ Snapshot lockcheck_snapshot = InvalidSnapshot;
HTSU_Result test;
HeapTuple copyTuple;
*************** lnext:
*** 110,123 ****
/* okay, try to lock the tuple */
if (erm->markType == ROW_MARK_EXCLUSIVE)
lockmode = LockTupleExclusive;
else
lockmode = LockTupleShared;
test = heap_lock_tuple(erm->relation, &tuple, &buffer,
&update_ctid, &update_xmax,
estate->es_output_cid,
! lockmode, erm->noWait);
ReleaseBuffer(buffer);
switch (test)
{
--- 111,134 ----
/* okay, try to lock the tuple */
if (erm->markType == ROW_MARK_EXCLUSIVE)
+ {
lockmode = LockTupleExclusive;
+ /* In serializable mode, acquireing an exclusive lock
+ * requires seeing all other lockers.
+ */
+ if (IsXactIsoLevelSerializable)
+ lockcheck_snapshot = estate->es_snapshot;
+ }
else
+ {
lockmode = LockTupleShared;
+ }
test = heap_lock_tuple(erm->relation, &tuple, &buffer,
&update_ctid, &update_xmax,
estate->es_output_cid,
! lockmode, erm->noWait,
! lockcheck_snapshot);
ReleaseBuffer(buffer);
switch (test)
{
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 7856b66..ecc75f9 100644
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
*************** ldelete:;
*** 316,323 ****
result = heap_delete(resultRelationDesc, tupleid,
&update_ctid, &update_xmax,
estate->es_output_cid,
! estate->es_crosscheck_snapshot,
! true /* wait for commit */ );
switch (result)
{
case HeapTupleSelfUpdated:
--- 316,324 ----
result = heap_delete(resultRelationDesc, tupleid,
&update_ctid, &update_xmax,
estate->es_output_cid,
! true, /* wait for commit */
! IsXactIsoLevelSerializable ? estate->es_snapshot :
! InvalidSnapshot);
switch (result)
{
case HeapTupleSelfUpdated:
*************** lreplace:;
*** 504,511 ****
result = heap_update(resultRelationDesc, tupleid, tuple,
&update_ctid, &update_xmax,
estate->es_output_cid,
! estate->es_crosscheck_snapshot,
! true /* wait for commit */ );
switch (result)
{
case HeapTupleSelfUpdated:
--- 505,513 ----
result = heap_update(resultRelationDesc, tupleid, tuple,
&update_ctid, &update_xmax,
estate->es_output_cid,
! true, /* wait for commit */
! IsXactIsoLevelSerializable ? estate->es_snapshot :
! InvalidSnapshot);
switch (result)
{
case HeapTupleSelfUpdated:
diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c
index 59a27de..d4dc5ca 100644
*** a/src/backend/utils/time/snapmgr.c
--- b/src/backend/utils/time/snapmgr.c
***************
*** 27,32 ****
--- 27,33 ----
#include "access/transam.h"
#include "access/xact.h"
+ #include "access/multixact.h"
#include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/memutils.h"
*************** GetTransactionSnapshot(void)
*** 125,130 ****
--- 126,139 ----
if (!FirstSnapshotSet)
{
Assert(RegisteredSnapshots == 0);
+
+ /* Must store the oldest visible multi xact *before* taking the
+ * serializable snapshot. Otherwise HeapSatisfiesLockersVisible in
+ * heapam.c will fail, since it'll be unable to inspect a multi xact
+ * that might contains xids invisible to the serializable snapshot.
+ */
+ if (IsXactIsoLevelSerializable)
+ MultiXactIdSetOldestVisible();
CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData);
FirstSnapshotSet = true;
diff --git a/src/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c
index 4f3630c..302a571 100644
*** a/src/backend/utils/time/tqual.c
--- b/src/backend/utils/time/tqual.c
*************** SnapshotData SnapshotSelfData = {HeapTup
*** 72,80 ****
SnapshotData SnapshotAnyData = {HeapTupleSatisfiesAny};
SnapshotData SnapshotToastData = {HeapTupleSatisfiesToast};
- /* local functions */
- static bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
-
/*
* SetHintBits()
--- 72,77 ----
*************** HeapTupleSatisfiesVacuum(HeapTupleHeader
*** 1253,1259 ****
* by this function. This is OK for current uses, because we actually only
* apply this for known-committed XIDs.
*/
! static bool
XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot)
{
uint32 i;
--- 1250,1256 ----
* by this function. This is OK for current uses, because we actually only
* apply this for known-committed XIDs.
*/
! bool
XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot)
{
uint32 i;
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index f963146..a17d62c 100644
*** a/src/include/access/heapam.h
--- b/src/include/access/heapam.h
*************** extern Oid heap_insert(Relation relation
*** 98,112 ****
int options, BulkInsertState bistate);
extern HTSU_Result heap_delete(Relation relation, ItemPointer tid,
ItemPointer ctid, TransactionId *update_xmax,
! CommandId cid, Snapshot crosscheck, bool wait);
extern HTSU_Result heap_update(Relation relation, ItemPointer otid,
HeapTuple newtup,
ItemPointer ctid, TransactionId *update_xmax,
! CommandId cid, Snapshot crosscheck, bool wait);
extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
Buffer *buffer, ItemPointer ctid,
TransactionId *update_xmax, CommandId cid,
! LockTupleMode mode, bool nowait);
extern void heap_inplace_update(Relation relation, HeapTuple tuple);
extern bool heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
Buffer buf);
--- 98,113 ----
int options, BulkInsertState bistate);
extern HTSU_Result heap_delete(Relation relation, ItemPointer tid,
ItemPointer ctid, TransactionId *update_xmax,
! CommandId cid, bool wait, Snapshot crosscheck);
extern HTSU_Result heap_update(Relation relation, ItemPointer otid,
HeapTuple newtup,
ItemPointer ctid, TransactionId *update_xmax,
! CommandId cid, bool wait, Snapshot crosscheck);
extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
Buffer *buffer, ItemPointer ctid,
TransactionId *update_xmax, CommandId cid,
! LockTupleMode mode, bool nowait,
! Snapshot lockcheck_snapshot);
extern void heap_inplace_update(Relation relation, HeapTuple tuple);
extern bool heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
Buffer buf);
diff --git a/src/include/access/multixact.h b/src/include/access/multixact.h
index 5910465..4769bca 100644
*** a/src/include/access/multixact.h
--- b/src/include/access/multixact.h
*************** extern bool MultiXactIdIsCurrent(MultiXa
*** 49,54 ****
--- 49,55 ----
extern void MultiXactIdWait(MultiXactId multi);
extern bool ConditionalMultiXactIdWait(MultiXactId multi);
extern void MultiXactIdSetOldestMember(void);
+ extern void MultiXactIdSetOldestVisible(void);
extern int GetMultiXactIdMembers(MultiXactId multi, TransactionId **xids);
extern void AtEOXact_MultiXact(void);
diff --git a/src/include/utils/tqual.h b/src/include/utils/tqual.h
index e85e820..fa386e7 100644
*** a/src/include/utils/tqual.h
--- b/src/include/utils/tqual.h
*************** extern PGDLLIMPORT SnapshotData Snapshot
*** 41,46 ****
--- 41,48 ----
#define IsMVCCSnapshot(snapshot) \
((snapshot)->satisfies == HeapTupleSatisfiesMVCC)
+ bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
+
/*
* HeapTupleSatisfiesVisibility
* True iff heap tuple satisfies a time qual.