mirror of
https://github.com/postgres/postgres.git
synced 2025-05-17 06:41:24 +03:00
Back-patch fixes for problems with VACUUM destroying t_ctid chains too soon,
and with insufficient paranoia in code that follows t_ctid links. This patch covers the 7.4 branch.
This commit is contained in:
parent
c9e69d71bd
commit
2ba0540024
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/access/heap/heapam.c,v 1.157.2.1 2004/10/13 22:22:02 tgl Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/access/heap/heapam.c,v 1.157.2.2 2005/08/25 22:07:11 tgl Exp $
|
||||
*
|
||||
*
|
||||
* INTERFACE ROUTINES
|
||||
@ -961,90 +961,136 @@ heap_fetch(Relation relation,
|
||||
|
||||
/*
|
||||
* heap_get_latest_tid - get the latest tid of a specified tuple
|
||||
*
|
||||
* Actually, this gets the latest version that is visible according to
|
||||
* the passed snapshot. You can pass SnapshotDirty to get the very latest,
|
||||
* possibly uncommitted version.
|
||||
*
|
||||
* *tid is both an input and an output parameter: it is updated to
|
||||
* show the latest version of the row. Note that it will not be changed
|
||||
* if no version of the row passes the snapshot test.
|
||||
*/
|
||||
ItemPointer
|
||||
void
|
||||
heap_get_latest_tid(Relation relation,
|
||||
Snapshot snapshot,
|
||||
ItemPointer tid)
|
||||
{
|
||||
ItemId lp = NULL;
|
||||
Buffer buffer;
|
||||
PageHeader dp;
|
||||
OffsetNumber offnum;
|
||||
HeapTupleData tp;
|
||||
HeapTupleHeader t_data;
|
||||
BlockNumber blk;
|
||||
ItemPointerData ctid;
|
||||
bool invalidBlock,
|
||||
linkend,
|
||||
valid;
|
||||
TransactionId priorXmax;
|
||||
|
||||
/* this is to avoid Assert failures on bad input */
|
||||
if (!ItemPointerIsValid(tid))
|
||||
return;
|
||||
|
||||
/*
|
||||
* get the buffer from the relation descriptor Note that this does a
|
||||
* buffer pin.
|
||||
* Since this can be called with user-supplied TID, don't trust the
|
||||
* input too much. (RelationGetNumberOfBlocks is an expensive check,
|
||||
* so we don't check t_ctid links again this way. Note that it would
|
||||
* not do to call it just once and save the result, either.)
|
||||
*/
|
||||
|
||||
buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
|
||||
|
||||
if (!BufferIsValid(buffer))
|
||||
elog(ERROR, "ReadBuffer(\"%s\", %lu) failed",
|
||||
RelationGetRelationName(relation),
|
||||
(unsigned long) ItemPointerGetBlockNumber(tid));
|
||||
|
||||
LockBuffer(buffer, BUFFER_LOCK_SHARE);
|
||||
blk = ItemPointerGetBlockNumber(tid);
|
||||
if (blk >= RelationGetNumberOfBlocks(relation))
|
||||
elog(ERROR, "block number %u is out of range for relation \"%s\"",
|
||||
blk, RelationGetRelationName(relation));
|
||||
|
||||
/*
|
||||
* get the item line pointer corresponding to the requested tid
|
||||
* Loop to chase down t_ctid links. At top of loop, ctid is the
|
||||
* tuple we need to examine, and *tid is the TID we will return if
|
||||
* ctid turns out to be bogus.
|
||||
*
|
||||
* Note that we will loop until we reach the end of the t_ctid chain.
|
||||
* Depending on the snapshot passed, there might be at most one visible
|
||||
* version of the row, but we don't try to optimize for that.
|
||||
*/
|
||||
dp = (PageHeader) BufferGetPage(buffer);
|
||||
offnum = ItemPointerGetOffsetNumber(tid);
|
||||
invalidBlock = true;
|
||||
if (!PageIsNew(dp))
|
||||
ctid = *tid;
|
||||
priorXmax = InvalidTransactionId; /* cannot check first XMIN */
|
||||
for (;;)
|
||||
{
|
||||
Buffer buffer;
|
||||
PageHeader dp;
|
||||
OffsetNumber offnum;
|
||||
ItemId lp;
|
||||
HeapTupleData tp;
|
||||
bool valid;
|
||||
|
||||
/*
|
||||
* Read, pin, and lock the page.
|
||||
*/
|
||||
buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&ctid));
|
||||
|
||||
if (!BufferIsValid(buffer))
|
||||
elog(ERROR, "ReadBuffer(\"%s\", %lu) failed",
|
||||
RelationGetRelationName(relation),
|
||||
(unsigned long) ItemPointerGetBlockNumber(&ctid));
|
||||
|
||||
LockBuffer(buffer, BUFFER_LOCK_SHARE);
|
||||
dp = (PageHeader) BufferGetPage(buffer);
|
||||
|
||||
/*
|
||||
* Check for bogus item number. This is not treated as an error
|
||||
* condition because it can happen while following a t_ctid link.
|
||||
* We just assume that the prior tid is OK and return it unchanged.
|
||||
*/
|
||||
offnum = ItemPointerGetOffsetNumber(&ctid);
|
||||
if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(dp))
|
||||
{
|
||||
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
|
||||
ReleaseBuffer(buffer);
|
||||
break;
|
||||
}
|
||||
lp = PageGetItemId(dp, offnum);
|
||||
if (ItemIdIsUsed(lp))
|
||||
invalidBlock = false;
|
||||
}
|
||||
if (invalidBlock)
|
||||
{
|
||||
if (!ItemIdIsUsed(lp))
|
||||
{
|
||||
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
|
||||
ReleaseBuffer(buffer);
|
||||
break;
|
||||
}
|
||||
|
||||
/* OK to access the tuple */
|
||||
tp.t_self = ctid;
|
||||
tp.t_datamcxt = NULL;
|
||||
tp.t_data = (HeapTupleHeader) PageGetItem(dp, lp);
|
||||
tp.t_len = ItemIdGetLength(lp);
|
||||
|
||||
/*
|
||||
* After following a t_ctid link, we might arrive at an unrelated
|
||||
* tuple. Check for XMIN match.
|
||||
*/
|
||||
if (TransactionIdIsValid(priorXmax) &&
|
||||
!TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(tp.t_data)))
|
||||
{
|
||||
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
|
||||
ReleaseBuffer(buffer);
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check time qualification of tuple; if visible, set it as the new
|
||||
* result candidate.
|
||||
*/
|
||||
HeapTupleSatisfies(&tp, relation, buffer, dp,
|
||||
snapshot, 0, NULL, valid);
|
||||
if (valid)
|
||||
*tid = ctid;
|
||||
|
||||
/*
|
||||
* If there's a valid t_ctid link, follow it, else we're done.
|
||||
*/
|
||||
if ((tp.t_data->t_infomask & (HEAP_XMAX_INVALID |
|
||||
HEAP_MARKED_FOR_UPDATE)) ||
|
||||
ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid))
|
||||
{
|
||||
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
|
||||
ReleaseBuffer(buffer);
|
||||
break;
|
||||
}
|
||||
|
||||
ctid = tp.t_data->t_ctid;
|
||||
priorXmax = HeapTupleHeaderGetXmax(tp.t_data);
|
||||
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
|
||||
ReleaseBuffer(buffer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* more sanity checks
|
||||
*/
|
||||
|
||||
tp.t_datamcxt = NULL;
|
||||
t_data = tp.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lp);
|
||||
tp.t_len = ItemIdGetLength(lp);
|
||||
tp.t_self = *tid;
|
||||
ctid = tp.t_data->t_ctid;
|
||||
|
||||
/*
|
||||
* check time qualification of tid
|
||||
*/
|
||||
|
||||
HeapTupleSatisfies(&tp, relation, buffer, dp,
|
||||
snapshot, 0, (ScanKey) NULL, valid);
|
||||
|
||||
linkend = true;
|
||||
if ((t_data->t_infomask & HEAP_XMIN_COMMITTED) != 0 &&
|
||||
!ItemPointerEquals(tid, &ctid))
|
||||
linkend = false;
|
||||
|
||||
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
|
||||
ReleaseBuffer(buffer);
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
if (linkend)
|
||||
return NULL;
|
||||
heap_get_latest_tid(relation, snapshot, &ctid);
|
||||
*tid = ctid;
|
||||
}
|
||||
|
||||
return tid;
|
||||
} /* end of loop */
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1210,6 +1256,7 @@ simple_heap_insert(Relation relation, HeapTuple tup)
|
||||
* relation - table to be modified
|
||||
* tid - TID of tuple to be deleted
|
||||
* ctid - output parameter, used only for failure case (see below)
|
||||
* update_xmax - output parameter, used only for failure case (see below)
|
||||
* cid - delete command ID to use in verifying tuple visibility
|
||||
* crosscheck - if not SnapshotAny, also check tuple against this
|
||||
* wait - true if should wait for any conflicting update to commit/abort
|
||||
@ -1217,13 +1264,17 @@ simple_heap_insert(Relation relation, HeapTuple tup)
|
||||
* 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). On a failure return,
|
||||
* *ctid is set to the ctid link of the target tuple (possibly a later
|
||||
* version of the row).
|
||||
* (the last only possible if wait == false).
|
||||
*
|
||||
* In the failure cases, the routine returns the tuple's t_ctid and t_xmax.
|
||||
* If t_ctid is the same as tid, the tuple was deleted; if different, the
|
||||
* tuple was updated, and t_ctid is the location of the replacement tuple.
|
||||
* (t_xmax is needed to verify that the replacement tuple matches.)
|
||||
*/
|
||||
int
|
||||
heap_delete(Relation relation, ItemPointer tid,
|
||||
ItemPointer ctid, CommandId cid, Snapshot crosscheck, bool wait)
|
||||
ItemPointer ctid, TransactionId *update_xmax,
|
||||
CommandId cid, Snapshot crosscheck, bool wait)
|
||||
{
|
||||
ItemId lp;
|
||||
HeapTupleData tp;
|
||||
@ -1243,11 +1294,11 @@ heap_delete(Relation relation, ItemPointer tid,
|
||||
|
||||
dp = (PageHeader) BufferGetPage(buffer);
|
||||
lp = PageGetItemId(dp, ItemPointerGetOffsetNumber(tid));
|
||||
|
||||
tp.t_datamcxt = NULL;
|
||||
tp.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lp);
|
||||
tp.t_data = (HeapTupleHeader) PageGetItem(dp, lp);
|
||||
tp.t_len = ItemIdGetLength(lp);
|
||||
tp.t_self = *tid;
|
||||
tp.t_tableOid = relation->rd_id;
|
||||
|
||||
l1:
|
||||
sv_infomask = tp.t_data->t_infomask;
|
||||
@ -1307,7 +1358,9 @@ l1:
|
||||
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);
|
||||
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
|
||||
ReleaseBuffer(buffer);
|
||||
return result;
|
||||
@ -1397,11 +1450,12 @@ l1:
|
||||
void
|
||||
simple_heap_delete(Relation relation, ItemPointer tid)
|
||||
{
|
||||
ItemPointerData ctid;
|
||||
int result;
|
||||
ItemPointerData update_ctid;
|
||||
TransactionId update_xmax;
|
||||
|
||||
result = heap_delete(relation, tid,
|
||||
&ctid,
|
||||
&update_ctid, &update_xmax,
|
||||
GetCurrentCommandId(), SnapshotAny,
|
||||
true /* wait for commit */);
|
||||
switch (result)
|
||||
@ -1435,6 +1489,7 @@ simple_heap_delete(Relation relation, ItemPointer tid)
|
||||
* otid - TID of old tuple to be replaced
|
||||
* newtup - newly constructed tuple data to store
|
||||
* ctid - output parameter, used only for failure case (see below)
|
||||
* update_xmax - output parameter, used only for failure case (see below)
|
||||
* cid - update command ID to use in verifying old tuple visibility
|
||||
* crosscheck - if not SnapshotAny, also check old tuple against this
|
||||
* wait - true if should wait for any conflicting update to commit/abort
|
||||
@ -1442,15 +1497,20 @@ simple_heap_delete(Relation relation, ItemPointer tid)
|
||||
* 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 a failure return,
|
||||
* *ctid is set to the ctid link of the old tuple (possibly a later
|
||||
* version of the row).
|
||||
* (the last only possible if wait == false).
|
||||
*
|
||||
* On success, newtup->t_self is set to the TID where the new tuple
|
||||
* was inserted.
|
||||
*
|
||||
* In the failure cases, the routine returns the tuple's t_ctid and t_xmax.
|
||||
* If t_ctid is the same as otid, the tuple was deleted; if different, the
|
||||
* tuple was updated, and t_ctid is the location of the replacement tuple.
|
||||
* (t_xmax is needed to verify that the replacement tuple matches.)
|
||||
*/
|
||||
int
|
||||
heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
|
||||
ItemPointer ctid, CommandId cid, Snapshot crosscheck, bool wait)
|
||||
ItemPointer ctid, TransactionId *update_xmax,
|
||||
CommandId cid, Snapshot crosscheck, bool wait)
|
||||
{
|
||||
ItemId lp;
|
||||
HeapTupleData oldtup;
|
||||
@ -1544,7 +1604,9 @@ l2:
|
||||
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);
|
||||
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
|
||||
ReleaseBuffer(buffer);
|
||||
return result;
|
||||
@ -1764,11 +1826,12 @@ l2:
|
||||
void
|
||||
simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup)
|
||||
{
|
||||
ItemPointerData ctid;
|
||||
int result;
|
||||
ItemPointerData update_ctid;
|
||||
TransactionId update_xmax;
|
||||
|
||||
result = heap_update(relation, otid, tup,
|
||||
&ctid,
|
||||
&update_ctid, &update_xmax,
|
||||
GetCurrentCommandId(), SnapshotAny,
|
||||
true /* wait for commit */);
|
||||
switch (result)
|
||||
@ -1794,9 +1857,34 @@ simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup)
|
||||
|
||||
/*
|
||||
* heap_mark4update - mark a tuple for update
|
||||
*
|
||||
* Note that this acquires a buffer pin, which the caller must release.
|
||||
*
|
||||
* 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)
|
||||
* cid: current command ID (used for visibility test, and stored into
|
||||
* tuple's cmax if lock is successful)
|
||||
*
|
||||
* Output parameters:
|
||||
* *tuple: all fields filled in
|
||||
* *buffer: set to buffer holding tuple (pinned but not locked at exit)
|
||||
* *ctid: set to tuple's t_ctid, but only in failure cases
|
||||
* *update_xmax: set to tuple's xmax, but only in failure cases
|
||||
*
|
||||
* Function result may be:
|
||||
* HeapTupleMayBeUpdated: lock was successfully acquired
|
||||
* HeapTupleSelfUpdated: lock failed because tuple updated by self
|
||||
* HeapTupleUpdated: lock failed because tuple updated by other xact
|
||||
*
|
||||
* In the failure cases, the routine returns the tuple's t_ctid and t_xmax.
|
||||
* If t_ctid is the same as t_self, the tuple was deleted; if different, the
|
||||
* tuple was updated, and t_ctid is the location of the replacement tuple.
|
||||
* (t_xmax is needed to verify that the replacement tuple matches.)
|
||||
*/
|
||||
int
|
||||
heap_mark4update(Relation relation, HeapTuple tuple, Buffer *buffer,
|
||||
ItemPointer ctid, TransactionId *update_xmax,
|
||||
CommandId cid)
|
||||
{
|
||||
ItemPointer tid = &(tuple->t_self);
|
||||
@ -1814,9 +1902,12 @@ heap_mark4update(Relation relation, HeapTuple tuple, Buffer *buffer,
|
||||
|
||||
dp = (PageHeader) BufferGetPage(*buffer);
|
||||
lp = PageGetItemId(dp, ItemPointerGetOffsetNumber(tid));
|
||||
Assert(ItemIdIsUsed(lp));
|
||||
|
||||
tuple->t_datamcxt = NULL;
|
||||
tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lp);
|
||||
tuple->t_len = ItemIdGetLength(lp);
|
||||
tuple->t_tableOid = RelationGetRelid(relation);
|
||||
|
||||
l3:
|
||||
sv_infomask = tuple->t_data->t_infomask;
|
||||
@ -1863,7 +1954,9 @@ l3:
|
||||
if (result != HeapTupleMayBeUpdated)
|
||||
{
|
||||
Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated);
|
||||
tuple->t_self = tuple->t_data->t_ctid;
|
||||
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);
|
||||
return result;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/commands/async.c,v 1.102.2.1 2004/05/22 21:58:41 tgl Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/commands/async.c,v 1.102.2.2 2005/08/25 22:07:15 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -512,8 +512,9 @@ AtCommit_Notify(void)
|
||||
}
|
||||
else if (listener->notification == 0)
|
||||
{
|
||||
ItemPointerData ctid;
|
||||
int result;
|
||||
ItemPointerData update_ctid;
|
||||
TransactionId update_xmax;
|
||||
|
||||
rTuple = heap_modifytuple(lTuple, lRel,
|
||||
value, nulls, repl);
|
||||
@ -533,7 +534,7 @@ AtCommit_Notify(void)
|
||||
* heap_getnext and heap_update calls.
|
||||
*/
|
||||
result = heap_update(lRel, &lTuple->t_self, rTuple,
|
||||
&ctid,
|
||||
&update_ctid, &update_xmax,
|
||||
GetCurrentCommandId(), SnapshotAny,
|
||||
false /* no wait for commit */);
|
||||
switch (result)
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.159 2003/10/02 06:34:03 petere Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.159.2.1 2005/08/25 22:07:15 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -1556,14 +1556,18 @@ GetTupleForTrigger(EState *estate, ResultRelInfo *relinfo,
|
||||
if (newSlot != NULL)
|
||||
{
|
||||
int test;
|
||||
ItemPointerData update_ctid;
|
||||
TransactionId update_xmax;
|
||||
|
||||
*newSlot = NULL;
|
||||
|
||||
/*
|
||||
* mark tuple for update
|
||||
*/
|
||||
*newSlot = NULL;
|
||||
tuple.t_self = *tid;
|
||||
ltrmark:;
|
||||
test = heap_mark4update(relation, &tuple, &buffer, cid);
|
||||
tuple.t_self = *tid;
|
||||
test = heap_mark4update(relation, &tuple, &buffer,
|
||||
&update_ctid, &update_xmax, cid);
|
||||
switch (test)
|
||||
{
|
||||
case HeapTupleSelfUpdated:
|
||||
@ -1580,15 +1584,18 @@ ltrmark:;
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
|
||||
errmsg("could not serialize access due to concurrent update")));
|
||||
else if (!(ItemPointerEquals(&(tuple.t_self), tid)))
|
||||
else if (!ItemPointerEquals(&update_ctid, &tuple.t_self))
|
||||
{
|
||||
TupleTableSlot *epqslot = EvalPlanQual(estate,
|
||||
relinfo->ri_RangeTableIndex,
|
||||
&(tuple.t_self));
|
||||
/* it was updated, so look at the updated version */
|
||||
TupleTableSlot *epqslot;
|
||||
|
||||
if (!(TupIsNull(epqslot)))
|
||||
epqslot = EvalPlanQual(estate,
|
||||
relinfo->ri_RangeTableIndex,
|
||||
&update_ctid,
|
||||
update_xmax);
|
||||
if (!TupIsNull(epqslot))
|
||||
{
|
||||
*tid = tuple.t_self;
|
||||
*tid = update_ctid;
|
||||
*newSlot = epqslot;
|
||||
goto ltrmark;
|
||||
}
|
||||
@ -1626,6 +1633,7 @@ ltrmark:;
|
||||
tuple.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lp);
|
||||
tuple.t_len = ItemIdGetLength(lp);
|
||||
tuple.t_self = *tid;
|
||||
tuple.t_tableOid = RelationGetRelid(relation);
|
||||
}
|
||||
|
||||
result = heap_copytuple(&tuple);
|
||||
|
@ -13,7 +13,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.263.2.1 2004/12/23 22:42:32 tgl Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.263.2.2 2005/08/25 22:07:16 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -1662,8 +1662,6 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
|
||||
Buffer Cbuf = buf;
|
||||
bool freeCbuf = false;
|
||||
bool chain_move_failed = false;
|
||||
Page Cpage;
|
||||
ItemId Citemid;
|
||||
ItemPointerData Ctid;
|
||||
HeapTupleData tp = tuple;
|
||||
Size tlen = tuple_len;
|
||||
@ -1687,68 +1685,85 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
|
||||
break; /* out of walk-along-page loop */
|
||||
}
|
||||
|
||||
vtmove = (VTupleMove) palloc(100 * sizeof(VTupleMoveData));
|
||||
num_vtmove = 0;
|
||||
free_vtmove = 100;
|
||||
|
||||
/*
|
||||
* If this tuple is in the begin/middle of the chain then
|
||||
* we have to move to the end of chain.
|
||||
* we have to move to the end of chain. As with any
|
||||
* t_ctid chase, we have to verify that each new tuple
|
||||
* is really the descendant of the tuple we came from.
|
||||
*/
|
||||
while (!(tp.t_data->t_infomask & (HEAP_XMAX_INVALID |
|
||||
HEAP_MARKED_FOR_UPDATE)) &&
|
||||
!(ItemPointerEquals(&(tp.t_self),
|
||||
&(tp.t_data->t_ctid))))
|
||||
{
|
||||
Ctid = tp.t_data->t_ctid;
|
||||
if (freeCbuf)
|
||||
ReleaseBuffer(Cbuf);
|
||||
freeCbuf = true;
|
||||
Cbuf = ReadBuffer(onerel,
|
||||
ItemPointerGetBlockNumber(&Ctid));
|
||||
Cpage = BufferGetPage(Cbuf);
|
||||
Citemid = PageGetItemId(Cpage,
|
||||
ItemPointerGetOffsetNumber(&Ctid));
|
||||
if (!ItemIdIsUsed(Citemid))
|
||||
ItemPointerData nextTid;
|
||||
TransactionId priorXmax;
|
||||
Buffer nextBuf;
|
||||
Page nextPage;
|
||||
OffsetNumber nextOffnum;
|
||||
ItemId nextItemid;
|
||||
HeapTupleHeader nextTdata;
|
||||
|
||||
nextTid = tp.t_data->t_ctid;
|
||||
priorXmax = HeapTupleHeaderGetXmax(tp.t_data);
|
||||
/* assume block# is OK (see heap_fetch comments) */
|
||||
nextBuf = ReadBuffer(onerel,
|
||||
ItemPointerGetBlockNumber(&nextTid));
|
||||
nextPage = BufferGetPage(nextBuf);
|
||||
/* If bogus or unused slot, assume tp is end of chain */
|
||||
nextOffnum = ItemPointerGetOffsetNumber(&nextTid);
|
||||
if (nextOffnum < FirstOffsetNumber ||
|
||||
nextOffnum > PageGetMaxOffsetNumber(nextPage))
|
||||
{
|
||||
/*
|
||||
* This means that in the middle of chain there
|
||||
* was tuple updated by older (than OldestXmin)
|
||||
* xaction and this tuple is already deleted by
|
||||
* me. Actually, upper part of chain should be
|
||||
* removed and seems that this should be handled
|
||||
* in scan_heap(), but it's not implemented at the
|
||||
* moment and so we just stop shrinking here.
|
||||
*/
|
||||
elog(DEBUG2, "child itemid in update-chain marked as unused --- can't continue repair_frag");
|
||||
chain_move_failed = true;
|
||||
break; /* out of loop to move to chain end */
|
||||
ReleaseBuffer(nextBuf);
|
||||
break;
|
||||
}
|
||||
nextItemid = PageGetItemId(nextPage, nextOffnum);
|
||||
if (!ItemIdIsUsed(nextItemid))
|
||||
{
|
||||
ReleaseBuffer(nextBuf);
|
||||
break;
|
||||
}
|
||||
/* if not matching XMIN, assume tp is end of chain */
|
||||
nextTdata = (HeapTupleHeader) PageGetItem(nextPage,
|
||||
nextItemid);
|
||||
if (!TransactionIdEquals(HeapTupleHeaderGetXmin(nextTdata),
|
||||
priorXmax))
|
||||
{
|
||||
ReleaseBuffer(nextBuf);
|
||||
break;
|
||||
}
|
||||
/* OK, switch our attention to the next tuple in chain */
|
||||
tp.t_datamcxt = NULL;
|
||||
tp.t_data = (HeapTupleHeader) PageGetItem(Cpage, Citemid);
|
||||
tp.t_self = Ctid;
|
||||
tlen = tp.t_len = ItemIdGetLength(Citemid);
|
||||
}
|
||||
if (chain_move_failed)
|
||||
{
|
||||
tp.t_data = nextTdata;
|
||||
tp.t_self = nextTid;
|
||||
tlen = tp.t_len = ItemIdGetLength(nextItemid);
|
||||
if (freeCbuf)
|
||||
ReleaseBuffer(Cbuf);
|
||||
pfree(vtmove);
|
||||
break; /* out of walk-along-page loop */
|
||||
Cbuf = nextBuf;
|
||||
freeCbuf = true;
|
||||
}
|
||||
|
||||
/* Set up workspace for planning the chain move */
|
||||
vtmove = (VTupleMove) palloc(100 * sizeof(VTupleMoveData));
|
||||
num_vtmove = 0;
|
||||
free_vtmove = 100;
|
||||
|
||||
/*
|
||||
* Check if all items in chain can be moved
|
||||
* Now, walk backwards up the chain (towards older tuples)
|
||||
* and check if all items in chain can be moved. We record
|
||||
* all the moves that need to be made in the vtmove array.
|
||||
*/
|
||||
for (;;)
|
||||
{
|
||||
Buffer Pbuf;
|
||||
Page Ppage;
|
||||
ItemId Pitemid;
|
||||
HeapTupleData Ptp;
|
||||
HeapTupleHeader PTdata;
|
||||
VTupleLinkData vtld,
|
||||
*vtlp;
|
||||
|
||||
/* Identify a target page to move this tuple to */
|
||||
if (to_vacpage == NULL ||
|
||||
!enough_space(to_vacpage, tlen))
|
||||
{
|
||||
@ -1818,18 +1833,17 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
|
||||
/* this can't happen since we saw tuple earlier: */
|
||||
if (!ItemIdIsUsed(Pitemid))
|
||||
elog(ERROR, "parent itemid marked as unused");
|
||||
Ptp.t_datamcxt = NULL;
|
||||
Ptp.t_data = (HeapTupleHeader) PageGetItem(Ppage, Pitemid);
|
||||
PTdata = (HeapTupleHeader) PageGetItem(Ppage, Pitemid);
|
||||
|
||||
/* ctid should not have changed since we saved it */
|
||||
Assert(ItemPointerEquals(&(vtld.new_tid),
|
||||
&(Ptp.t_data->t_ctid)));
|
||||
&(PTdata->t_ctid)));
|
||||
|
||||
/*
|
||||
* Read above about cases when !ItemIdIsUsed(Citemid)
|
||||
* Read above about cases when !ItemIdIsUsed(nextItemid)
|
||||
* (child item is removed)... Due to the fact that at
|
||||
* the moment we don't remove unuseful part of
|
||||
* update-chain, it's possible to get too old parent
|
||||
* update-chain, it's possible to get non-matching parent
|
||||
* row here. Like as in the case which caused this
|
||||
* problem, we stop shrinking here. I could try to
|
||||
* find real parent row but want not to do it because
|
||||
@ -1837,7 +1851,7 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
|
||||
* and we are too close to 6.5 release. - vadim
|
||||
* 06/11/99
|
||||
*/
|
||||
if (!(TransactionIdEquals(HeapTupleHeaderGetXmax(Ptp.t_data),
|
||||
if (!(TransactionIdEquals(HeapTupleHeaderGetXmax(PTdata),
|
||||
HeapTupleHeaderGetXmin(tp.t_data))))
|
||||
{
|
||||
ReleaseBuffer(Pbuf);
|
||||
@ -1845,8 +1859,8 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
|
||||
chain_move_failed = true;
|
||||
break; /* out of check-all-items loop */
|
||||
}
|
||||
tp.t_datamcxt = Ptp.t_datamcxt;
|
||||
tp.t_data = Ptp.t_data;
|
||||
tp.t_datamcxt = NULL;
|
||||
tp.t_data = PTdata;
|
||||
tlen = tp.t_len = ItemIdGetLength(Pitemid);
|
||||
if (freeCbuf)
|
||||
ReleaseBuffer(Cbuf);
|
||||
@ -1881,6 +1895,8 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
|
||||
for (ti = 0; ti < num_vtmove; ti++)
|
||||
{
|
||||
VacPage destvacpage = vtmove[ti].vacpage;
|
||||
Page Cpage;
|
||||
ItemId Citemid;
|
||||
|
||||
/* Get page to move from */
|
||||
tuple.t_self = vtmove[ti].tid;
|
||||
@ -1969,16 +1985,27 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
|
||||
InvalidOffsetNumber,
|
||||
LP_USED);
|
||||
if (newoff == InvalidOffsetNumber)
|
||||
{
|
||||
elog(PANIC, "failed to add item with len = %lu to page %u while moving tuple chain",
|
||||
(unsigned long) tuple_len, destvacpage->blkno);
|
||||
}
|
||||
newitemid = PageGetItemId(ToPage, newoff);
|
||||
/* drop temporary copy, and point to the version on the dest page */
|
||||
pfree(newtup.t_data);
|
||||
newtup.t_datamcxt = NULL;
|
||||
newtup.t_data = (HeapTupleHeader) PageGetItem(ToPage, newitemid);
|
||||
|
||||
ItemPointerSet(&(newtup.t_self), destvacpage->blkno, newoff);
|
||||
|
||||
/*
|
||||
* Set new tuple's t_ctid pointing to itself for last
|
||||
* tuple in chain, and to next tuple in chain
|
||||
* otherwise.
|
||||
*/
|
||||
if (!ItemPointerIsValid(&Ctid))
|
||||
newtup.t_data->t_ctid = newtup.t_self;
|
||||
else
|
||||
newtup.t_data->t_ctid = Ctid;
|
||||
Ctid = newtup.t_self;
|
||||
|
||||
/* XLOG stuff */
|
||||
if (!onerel->rd_istemp)
|
||||
{
|
||||
@ -2008,17 +2035,6 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
|
||||
if (destvacpage->blkno > last_move_dest_block)
|
||||
last_move_dest_block = destvacpage->blkno;
|
||||
|
||||
/*
|
||||
* Set new tuple's t_ctid pointing to itself for last
|
||||
* tuple in chain, and to next tuple in chain
|
||||
* otherwise.
|
||||
*/
|
||||
if (!ItemPointerIsValid(&Ctid))
|
||||
newtup.t_data->t_ctid = newtup.t_self;
|
||||
else
|
||||
newtup.t_data->t_ctid = Ctid;
|
||||
Ctid = newtup.t_self;
|
||||
|
||||
num_moved++;
|
||||
|
||||
/*
|
||||
|
@ -26,7 +26,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.220.2.2 2004/03/02 18:56:28 tgl Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.220.2.3 2005/08/25 22:07:18 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -1157,8 +1157,10 @@ lnext: ;
|
||||
foreach(l, estate->es_rowMark)
|
||||
{
|
||||
execRowMark *erm = lfirst(l);
|
||||
Buffer buffer;
|
||||
HeapTupleData tuple;
|
||||
Buffer buffer;
|
||||
ItemPointerData update_ctid;
|
||||
TransactionId update_xmax;
|
||||
TupleTableSlot *newSlot;
|
||||
int test;
|
||||
|
||||
@ -1176,6 +1178,7 @@ lnext: ;
|
||||
|
||||
tuple.t_self = *((ItemPointer) DatumGetPointer(datum));
|
||||
test = heap_mark4update(erm->relation, &tuple, &buffer,
|
||||
&update_ctid, &update_xmax,
|
||||
estate->es_snapshot->curcid);
|
||||
ReleaseBuffer(buffer);
|
||||
switch (test)
|
||||
@ -1192,11 +1195,15 @@ lnext: ;
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
|
||||
errmsg("could not serialize access due to concurrent update")));
|
||||
if (!(ItemPointerEquals(&(tuple.t_self),
|
||||
(ItemPointer) DatumGetPointer(datum))))
|
||||
if (!ItemPointerEquals(&update_ctid,
|
||||
&tuple.t_self))
|
||||
{
|
||||
newSlot = EvalPlanQual(estate, erm->rti, &(tuple.t_self));
|
||||
if (!(TupIsNull(newSlot)))
|
||||
/* updated, so look at updated version */
|
||||
newSlot = EvalPlanQual(estate,
|
||||
erm->rti,
|
||||
&update_ctid,
|
||||
update_xmax);
|
||||
if (!TupIsNull(newSlot))
|
||||
{
|
||||
slot = newSlot;
|
||||
estate->es_useEvalPlan = true;
|
||||
@ -1448,8 +1455,9 @@ ExecDelete(TupleTableSlot *slot,
|
||||
{
|
||||
ResultRelInfo *resultRelInfo;
|
||||
Relation resultRelationDesc;
|
||||
ItemPointerData ctid;
|
||||
int result;
|
||||
ItemPointerData update_ctid;
|
||||
TransactionId update_xmax;
|
||||
|
||||
/*
|
||||
* get information on the (current) result relation
|
||||
@ -1475,7 +1483,7 @@ ExecDelete(TupleTableSlot *slot,
|
||||
*/
|
||||
ldelete:;
|
||||
result = heap_delete(resultRelationDesc, tupleid,
|
||||
&ctid,
|
||||
&update_ctid, &update_xmax,
|
||||
estate->es_snapshot->curcid,
|
||||
estate->es_crosscheck_snapshot,
|
||||
true /* wait for commit */);
|
||||
@ -1493,14 +1501,17 @@ ldelete:;
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
|
||||
errmsg("could not serialize access due to concurrent update")));
|
||||
else if (!(ItemPointerEquals(tupleid, &ctid)))
|
||||
else if (!ItemPointerEquals(tupleid, &update_ctid))
|
||||
{
|
||||
TupleTableSlot *epqslot = EvalPlanQual(estate,
|
||||
resultRelInfo->ri_RangeTableIndex, &ctid);
|
||||
TupleTableSlot *epqslot;
|
||||
|
||||
epqslot = EvalPlanQual(estate,
|
||||
resultRelInfo->ri_RangeTableIndex,
|
||||
&update_ctid,
|
||||
update_xmax);
|
||||
if (!TupIsNull(epqslot))
|
||||
{
|
||||
*tupleid = ctid;
|
||||
*tupleid = update_ctid;
|
||||
goto ldelete;
|
||||
}
|
||||
}
|
||||
@ -1547,8 +1558,9 @@ ExecUpdate(TupleTableSlot *slot,
|
||||
HeapTuple tuple;
|
||||
ResultRelInfo *resultRelInfo;
|
||||
Relation resultRelationDesc;
|
||||
ItemPointerData ctid;
|
||||
int result;
|
||||
ItemPointerData update_ctid;
|
||||
TransactionId update_xmax;
|
||||
int numIndices;
|
||||
|
||||
/*
|
||||
@ -1611,7 +1623,7 @@ lreplace:;
|
||||
* replace the heap tuple
|
||||
*/
|
||||
result = heap_update(resultRelationDesc, tupleid, tuple,
|
||||
&ctid,
|
||||
&update_ctid, &update_xmax,
|
||||
estate->es_snapshot->curcid,
|
||||
estate->es_crosscheck_snapshot,
|
||||
true /* wait for commit */);
|
||||
@ -1629,14 +1641,17 @@ lreplace:;
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
|
||||
errmsg("could not serialize access due to concurrent update")));
|
||||
else if (!(ItemPointerEquals(tupleid, &ctid)))
|
||||
else if (!(ItemPointerEquals(tupleid, &update_ctid)))
|
||||
{
|
||||
TupleTableSlot *epqslot = EvalPlanQual(estate,
|
||||
resultRelInfo->ri_RangeTableIndex, &ctid);
|
||||
TupleTableSlot *epqslot;
|
||||
|
||||
epqslot = EvalPlanQual(estate,
|
||||
resultRelInfo->ri_RangeTableIndex,
|
||||
&update_ctid,
|
||||
update_xmax);
|
||||
if (!TupIsNull(epqslot))
|
||||
{
|
||||
*tupleid = ctid;
|
||||
*tupleid = update_ctid;
|
||||
tuple = ExecRemoveJunk(estate->es_junkFilter, epqslot);
|
||||
slot = ExecStoreTuple(tuple,
|
||||
estate->es_junkFilter->jf_resultSlot,
|
||||
@ -1782,9 +1797,21 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
|
||||
* under READ COMMITTED rules.
|
||||
*
|
||||
* See backend/executor/README for some info about how this works.
|
||||
*
|
||||
* estate - executor state data
|
||||
* rti - rangetable index of table containing tuple
|
||||
* *tid - t_ctid from the outdated tuple (ie, next updated version)
|
||||
* priorXmax - t_xmax from the outdated tuple
|
||||
*
|
||||
* *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)
|
||||
*
|
||||
* 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, Index rti, ItemPointer tid)
|
||||
EvalPlanQual(EState *estate, Index rti,
|
||||
ItemPointer tid, TransactionId priorXmax)
|
||||
{
|
||||
evalPlanQual *epq;
|
||||
EState *epqstate;
|
||||
@ -1828,11 +1855,24 @@ EvalPlanQual(EState *estate, Index rti, ItemPointer tid)
|
||||
{
|
||||
Buffer buffer;
|
||||
|
||||
if (heap_fetch(relation, SnapshotDirty, &tuple, &buffer, false, NULL))
|
||||
if (heap_fetch(relation, SnapshotDirty, &tuple, &buffer, true, NULL))
|
||||
{
|
||||
TransactionId xwait = SnapshotDirty->xmax;
|
||||
/*
|
||||
* 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, since xmin never changes in an existing
|
||||
* tuple.)
|
||||
*/
|
||||
if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple.t_data),
|
||||
priorXmax))
|
||||
{
|
||||
ReleaseBuffer(buffer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* xmin should not be dirty... */
|
||||
/* otherwise xmin should not be dirty... */
|
||||
if (TransactionIdIsValid(SnapshotDirty->xmin))
|
||||
elog(ERROR, "t_xmin is uncommitted in tuple to be updated");
|
||||
|
||||
@ -1840,11 +1880,11 @@ EvalPlanQual(EState *estate, Index rti, ItemPointer tid)
|
||||
* If tuple is being updated by other transaction then we have
|
||||
* to wait for its commit/abort.
|
||||
*/
|
||||
if (TransactionIdIsValid(xwait))
|
||||
if (TransactionIdIsValid(SnapshotDirty->xmax))
|
||||
{
|
||||
ReleaseBuffer(buffer);
|
||||
XactLockTableWait(xwait);
|
||||
continue;
|
||||
XactLockTableWait(SnapshotDirty->xmax);
|
||||
continue; /* loop back to repeat heap_fetch */
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1856,22 +1896,50 @@ EvalPlanQual(EState *estate, Index rti, ItemPointer tid)
|
||||
}
|
||||
|
||||
/*
|
||||
* Oops! Invalid tuple. Have to check is it updated or deleted.
|
||||
* Note that it's possible to get invalid SnapshotDirty->tid if
|
||||
* tuple updated by this transaction. Have we to check this ?
|
||||
* If the referenced slot was actually empty, the latest version
|
||||
* of the row must have been deleted, so we need do nothing.
|
||||
*/
|
||||
if (ItemPointerIsValid(&(SnapshotDirty->tid)) &&
|
||||
!(ItemPointerEquals(&(tuple.t_self), &(SnapshotDirty->tid))))
|
||||
if (tuple.t_data == NULL)
|
||||
{
|
||||
/* updated, so look at the updated copy */
|
||||
tuple.t_self = SnapshotDirty->tid;
|
||||
continue;
|
||||
ReleaseBuffer(buffer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Deleted or updated by this transaction; forget it.
|
||||
* As above, if xmin isn't what we're expecting, do nothing.
|
||||
*/
|
||||
return NULL;
|
||||
if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple.t_data),
|
||||
priorXmax))
|
||||
{
|
||||
ReleaseBuffer(buffer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we get here, the tuple was found but failed SnapshotDirty.
|
||||
* Assuming the xmin is either a committed xact or our own xact
|
||||
* (as it certainly should be if we're trying to modify the tuple),
|
||||
* this must mean that the row was updated or deleted by either
|
||||
* a committed xact or our own xact. If it was deleted, we can
|
||||
* ignore it; if it was updated then chain up to the next version
|
||||
* and repeat the whole test.
|
||||
*
|
||||
* As above, it should be safe to examine xmax and t_ctid without
|
||||
* the buffer content lock, because they can't be changing.
|
||||
*/
|
||||
if (ItemPointerEquals(&tuple.t_self, &tuple.t_data->t_ctid))
|
||||
{
|
||||
/* deleted, so forget about it */
|
||||
ReleaseBuffer(buffer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* updated, so look at the updated row */
|
||||
tuple.t_self = tuple.t_data->t_ctid;
|
||||
/* updated row should have xmin matching this xmax */
|
||||
priorXmax = HeapTupleHeaderGetXmax(tuple.t_data);
|
||||
ReleaseBuffer(buffer);
|
||||
/* loop back to fetch next in chain */
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -30,7 +30,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/utils/time/tqual.c,v 1.70.2.1 2005/05/07 21:23:02 tgl Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/utils/time/tqual.c,v 1.70.2.2 2005/08/25 22:07:19 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -972,10 +972,13 @@ HeapTupleSatisfiesVacuum(HeapTupleHeader tuple, TransactionId OldestXmin)
|
||||
HeapTupleHeaderGetXmax(tuple)))
|
||||
{
|
||||
/*
|
||||
* inserter also deleted it, so it was never visible to anyone
|
||||
* else
|
||||
* Inserter also deleted it, so it was never visible to anyone
|
||||
* else. However, we can only remove it early if it's not an
|
||||
* updated tuple; else its parent tuple is linking to it via t_ctid,
|
||||
* and this tuple mustn't go away before the parent does.
|
||||
*/
|
||||
return HEAPTUPLE_DEAD;
|
||||
if (!(tuple->t_infomask & HEAP_UPDATED))
|
||||
return HEAPTUPLE_DEAD;
|
||||
}
|
||||
|
||||
if (!TransactionIdPrecedes(HeapTupleHeaderGetXmax(tuple), OldestXmin))
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $Id: heapam.h,v 1.85.2.1 2005/02/06 20:15:29 tgl Exp $
|
||||
* $Id: heapam.h,v 1.85.2.2 2005/08/25 22:07:20 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -149,17 +149,21 @@ extern bool heap_fetch(Relation relation, Snapshot snapshot,
|
||||
HeapTuple tuple, Buffer *userbuf, bool keep_buf,
|
||||
PgStat_Info *pgstat_info);
|
||||
|
||||
extern ItemPointer heap_get_latest_tid(Relation relation, Snapshot snapshot,
|
||||
extern void heap_get_latest_tid(Relation relation, Snapshot snapshot,
|
||||
ItemPointer tid);
|
||||
extern void setLastTid(const ItemPointer tid);
|
||||
|
||||
extern Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid);
|
||||
extern int heap_delete(Relation relation, ItemPointer tid, ItemPointer ctid,
|
||||
CommandId cid, Snapshot crosscheck, bool wait);
|
||||
extern int heap_update(Relation relation, ItemPointer otid, HeapTuple tup,
|
||||
ItemPointer ctid, CommandId cid, Snapshot crosscheck, bool wait);
|
||||
extern int heap_mark4update(Relation relation, HeapTuple tup,
|
||||
Buffer *userbuf, CommandId cid);
|
||||
extern int heap_delete(Relation relation, ItemPointer tid,
|
||||
ItemPointer ctid, TransactionId *update_xmax,
|
||||
CommandId cid, Snapshot crosscheck, bool wait);
|
||||
extern int heap_update(Relation relation, ItemPointer otid,
|
||||
HeapTuple newtup,
|
||||
ItemPointer ctid, TransactionId *update_xmax,
|
||||
CommandId cid, Snapshot crosscheck, bool wait);
|
||||
extern int heap_mark4update(Relation relation, HeapTuple tuple,
|
||||
Buffer *buffer, ItemPointer ctid,
|
||||
TransactionId *update_xmax, CommandId cid);
|
||||
|
||||
extern Oid simple_heap_insert(Relation relation, HeapTuple tup);
|
||||
extern void simple_heap_delete(Relation relation, ItemPointer tid);
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $Id: executor.h,v 1.102.2.3 2004/03/02 18:56:28 tgl Exp $
|
||||
* $Id: executor.h,v 1.102.2.4 2005/08/25 22:07:21 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -98,7 +98,7 @@ extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
|
||||
extern void ExecConstraints(ResultRelInfo *resultRelInfo,
|
||||
TupleTableSlot *slot, EState *estate);
|
||||
extern TupleTableSlot *EvalPlanQual(EState *estate, Index rti,
|
||||
ItemPointer tid);
|
||||
ItemPointer tid, TransactionId priorXmax);
|
||||
|
||||
/*
|
||||
* prototypes from functions in execProcnode.c
|
||||
|
Loading…
x
Reference in New Issue
Block a user