@ -5409,14 +5409,282 @@ heap_inplace_update(Relation relation, HeapTuple tuple)
CacheInvalidateHeapTuple ( relation , tuple , NULL ) ;
}
# define FRM_NOOP 0x0001
# define FRM_INVALIDATE_XMAX 0x0002
# define FRM_RETURN_IS_XID 0x0004
# define FRM_RETURN_IS_MULTI 0x0008
# define FRM_MARK_COMMITTED 0x0010
/*
* heap_freeze_tuple
* FreezeMultiXactId
* Determine what to do during freezing when a tuple is marked by a
* MultiXactId .
*
* NB - - this might have the side - effect of creating a new MultiXactId !
*
* " flags " is an output value ; it ' s used to tell caller what to do on return .
* Possible flags are :
* FRM_NOOP
* don ' t do anything - - keep existing Xmax
* FRM_INVALIDATE_XMAX
* mark Xmax as InvalidTransactionId and set XMAX_INVALID flag .
* FRM_RETURN_IS_XID
* The Xid return value is a single update Xid to set as xmax .
* FRM_MARK_COMMITTED
* Xmax can be marked as HEAP_XMAX_COMMITTED
* FRM_RETURN_IS_MULTI
* The return value is a new MultiXactId to set as new Xmax .
* ( caller must obtain proper infomask bits using GetMultiXactIdHintBits )
*/
static TransactionId
FreezeMultiXactId ( MultiXactId multi , uint16 t_infomask ,
TransactionId cutoff_xid , MultiXactId cutoff_multi ,
uint16 * flags )
{
TransactionId xid = InvalidTransactionId ;
int i ;
MultiXactMember * members ;
int nmembers ;
bool need_replace ;
int nnewmembers ;
MultiXactMember * newmembers ;
bool has_lockers ;
TransactionId update_xid ;
bool update_committed ;
* flags = 0 ;
/* We should only be called in Multis */
Assert ( t_infomask & HEAP_XMAX_IS_MULTI ) ;
if ( ! MultiXactIdIsValid ( multi ) )
{
/* Ensure infomask bits are appropriately set/reset */
* flags | = FRM_INVALIDATE_XMAX ;
return InvalidTransactionId ;
}
else if ( MultiXactIdPrecedes ( multi , cutoff_multi ) )
{
/*
* This old multi cannot possibly have members still running . If it
* was a locker only , it can be removed without any further
* consideration ; but if it contained an update , we might need to
* preserve it .
*/
Assert ( ! MultiXactIdIsRunning ( multi ) ) ;
if ( HEAP_XMAX_IS_LOCKED_ONLY ( t_infomask ) )
{
* flags | = FRM_INVALIDATE_XMAX ;
xid = InvalidTransactionId ; /* not strictly necessary */
}
else
{
/* replace multi by update xid */
xid = MultiXactIdGetUpdateXid ( multi , t_infomask ) ;
/* wasn't only a lock, xid needs to be valid */
Assert ( TransactionIdIsValid ( xid ) ) ;
/*
* If the xid is older than the cutoff , it has to have aborted ,
* otherwise the tuple would have gotten pruned away .
*/
if ( TransactionIdPrecedes ( xid , cutoff_xid ) )
{
Assert ( ! TransactionIdDidCommit ( xid ) ) ;
* flags | = FRM_INVALIDATE_XMAX ;
xid = InvalidTransactionId ; /* not strictly necessary */
}
else
{
* flags | = FRM_RETURN_IS_XID ;
}
}
return xid ;
}
/*
* This multixact might have or might not have members still running , but
* we know it ' s valid and is newer than the cutoff point for multis .
* However , some member ( s ) of it may be below the cutoff for Xids , so we
* need to walk the whole members array to figure out what to do , if
* anything .
*/
nmembers = GetMultiXactIdMembers ( multi , & members , false ) ;
if ( nmembers < = 0 )
{
/* Nothing worth keeping */
* flags | = FRM_INVALIDATE_XMAX ;
return InvalidTransactionId ;
}
/* is there anything older than the cutoff? */
need_replace = false ;
for ( i = 0 ; i < nmembers ; i + + )
{
if ( TransactionIdPrecedes ( members [ i ] . xid , cutoff_xid ) )
{
need_replace = true ;
break ;
}
}
/*
* In the simplest case , there is no member older than the cutoff ; we can
* keep the existing MultiXactId as is .
*/
if ( ! need_replace )
{
* flags | = FRM_NOOP ;
pfree ( members ) ;
return InvalidTransactionId ;
}
/*
* If the multi needs to be updated , figure out which members do we need
* to keep .
*/
nnewmembers = 0 ;
newmembers = palloc ( sizeof ( MultiXactMember ) * nmembers ) ;
has_lockers = false ;
update_xid = InvalidTransactionId ;
update_committed = false ;
for ( i = 0 ; i < nmembers ; i + + )
{
/*
* Determine whether to keep this member or ignore it .
*/
if ( ISUPDATE_from_mxstatus ( members [ i ] . status ) )
{
TransactionId xid = members [ i ] . xid ;
/*
* It ' s an update ; should we keep it ? If the transaction is known
* aborted then it ' s okay to ignore it , otherwise not . However ,
* if the Xid is older than the cutoff_xid , we must remove it .
* Note that such an old updater cannot possibly be committed ,
* because HeapTupleSatisfiesVacuum would have returned
* HEAPTUPLE_DEAD and we would not be trying to freeze the tuple .
*
* Note the TransactionIdDidAbort ( ) test is just an optimization
* and not strictly necessary for correctness .
*
* As with all tuple visibility routines , it ' s critical to test
* TransactionIdIsInProgress before the transam . c routines ,
* because of race conditions explained in detail in tqual . c .
*/
if ( TransactionIdIsCurrentTransactionId ( xid ) | |
TransactionIdIsInProgress ( xid ) )
{
Assert ( ! TransactionIdIsValid ( update_xid ) ) ;
update_xid = xid ;
}
else if ( ! TransactionIdDidAbort ( xid ) )
{
/*
* Test whether to tell caller to set HEAP_XMAX_COMMITTED
* while we have the Xid still in cache . Note this can only
* be done if the transaction is known not running .
*/
if ( TransactionIdDidCommit ( xid ) )
update_committed = true ;
Assert ( ! TransactionIdIsValid ( update_xid ) ) ;
update_xid = xid ;
}
/*
* If we determined that it ' s an Xid corresponding to an update
* that must be retained , additionally add it to the list of
* members of the new Multis , in case we end up using that . ( We
* might still decide to use only an update Xid and not a multi ,
* but it ' s easier to maintain the list as we walk the old members
* list . )
*
* It is possible to end up with a very old updater Xid that
* crashed and thus did not mark itself as aborted in pg_clog .
* That would manifest as a pre - cutoff Xid . Make sure to ignore
* it .
*/
if ( TransactionIdIsValid ( update_xid ) )
{
if ( ! TransactionIdPrecedes ( update_xid , cutoff_xid ) )
{
newmembers [ nnewmembers + + ] = members [ i ] ;
}
else
{
/* cannot have committed: would be HEAPTUPLE_DEAD */
Assert ( ! TransactionIdDidCommit ( update_xid ) ) ;
update_xid = InvalidTransactionId ;
update_committed = false ;
}
}
}
else
{
/* We only keep lockers if they are still running */
if ( TransactionIdIsCurrentTransactionId ( members [ i ] . xid ) | |
TransactionIdIsInProgress ( members [ i ] . xid ) )
{
/* running locker cannot possibly be older than the cutoff */
Assert ( ! TransactionIdPrecedes ( members [ i ] . xid , cutoff_xid ) ) ;
newmembers [ nnewmembers + + ] = members [ i ] ;
has_lockers = true ;
}
}
}
pfree ( members ) ;
if ( nnewmembers = = 0 )
{
/* nothing worth keeping!? Tell caller to remove the whole thing */
* flags | = FRM_INVALIDATE_XMAX ;
xid = InvalidTransactionId ;
}
else if ( TransactionIdIsValid ( update_xid ) & & ! has_lockers )
{
/*
* If there ' s a single member and it ' s an update , pass it back alone
* without creating a new Multi . ( XXX we could do this when there ' s a
* single remaining locker , too , but that would complicate the API too
* much ; moreover , the case with the single updater is more
* interesting , because those are longer - lived . )
*/
Assert ( nnewmembers = = 1 ) ;
* flags | = FRM_RETURN_IS_XID ;
if ( update_committed )
* flags | = FRM_MARK_COMMITTED ;
xid = update_xid ;
}
else
{
/*
* Create a new multixact with the surviving members of the previous
* one , to set as new Xmax in the tuple .
*/
xid = MultiXactIdCreateFromMembers ( nnewmembers , newmembers ) ;
* flags | = FRM_RETURN_IS_MULTI ;
}
pfree ( newmembers ) ;
return xid ;
}
/*
* heap_prepare_freeze_tuple
*
* Check to see whether any of the XID fields of a tuple ( xmin , xmax , xvac )
* are older than the specified cutoff XID . If so , replace them with
* FrozenTransactionId or InvalidTransactionId as appropriate , and return
* TRUE . Return FALSE if nothing was changed .
* are older than the specified cutoff XID and cutoff MultiXactId . If so ,
* setup enough state ( in the * frz output argument ) to later execute and
* WAL - log what we would need to do , and return TRUE . Return FALSE if nothing
* is to be changed .
*
* Caller is responsible for setting the offset field , if appropriate .
*
* It is assumed that the caller has checked the tuple with
* HeapTupleSatisfiesVacuum ( ) and determined that it is not HEAPTUPLE_DEAD
@ -5425,54 +5693,44 @@ heap_inplace_update(Relation relation, HeapTuple tuple)
* NB : cutoff_xid * must * be < = the current global xmin , to ensure that any
* XID older than it could neither be running nor seen as running by any
* open transaction . This ensures that the replacement will not change
* anyone ' s idea of the tuple state . Also , since we assume the tuple is
* not HEAPTUPLE_DEAD , the fact that an XID is not still running allows us
* to assume that it is either committed good or aborted , as appropriate ;
* so we need no external state checks to decide what to do . ( This is good
* because this function is applied during WAL recovery , when we don ' t have
* access to any such state , and can ' t depend on the hint bits to be set . )
* There is an exception we make which is to assume GetMultiXactIdMembers can
* be called during recovery .
*
* anyone ' s idea of the tuple state .
* Similarly , cutoff_multi must be less than or equal to the smallest
* MultiXactId used by any transaction currently open .
*
* If the tuple is in a shared buffer , caller must hold an exclusive lock on
* that buffer .
*
* Note : it might seem we could make the changes without exclusive lock , since
* TransactionId read / write is assumed atomic anyway . However there is a race
* condition : someone who just fetched an old XID that we overwrite here could
* conceivably not finish checking the XID against pg_clog before we finish
* the VACUUM and perhaps truncate off the part of pg_clog he needs . Getting
* exclusive lock ensures no other backend is in process of checking the
* tuple status . Also , getting exclusive lock makes it safe to adjust the
* infomask bits .
*
* NB : Cannot rely on hint bits here , they might not be set after a crash or
* on a standby .
* NB : It is not enough to set hint bits to indicate something is
* committed / invalid - - they might not be set on a standby , or after crash
* recovery . We really need to remove old xids .
*/
bool
heap_freeze_tuple ( HeapTupleHeader tuple , TransactionId cutoff_xid ,
MultiXactId cutoff_multi )
heap_prepare_freeze_tuple ( HeapTupleHeader tuple , TransactionId cutoff_xid ,
TransactionId cutoff_multi ,
xl_heap_freeze_tuple * frz )
{
bool changed = false ;
bool freeze_xmax = false ;
TransactionId xid ;
frz - > frzflags = 0 ;
frz - > t_infomask2 = tuple - > t_infomask2 ;
frz - > t_infomask = tuple - > t_infomask ;
frz - > xmax = HeapTupleHeaderGetRawXmax ( tuple ) ;
/* Process xmin */
xid = HeapTupleHeaderGetXmin ( tuple ) ;
if ( TransactionIdIsNormal ( xid ) & &
TransactionIdPrecedes ( xid , cutoff_xid ) )
{
HeapTupleHeaderSetXmin ( tuple , FrozenTransactionId ) ;
frz - > frzflags | = XLH_FREEZE_XMIN ;
/*
* Might as well fix the hint bits too ; usually XMIN_COMMITTED will
* already be set here , but there ' s a small chance not .
*/
Assert ( ! ( tuple - > t_infomask & HEAP_XMIN_INVALID ) ) ;
tuple - > t_infomask | = HEAP_XMIN_COMMITTED ;
frz - > t_infomask | = HEAP_XMIN_COMMITTED ;
changed = true ;
}
@ -5489,91 +5747,53 @@ heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
if ( tuple - > t_infomask & HEAP_XMAX_IS_MULTI )
{
if ( ! MultiXactIdIsValid ( xid ) )
TransactionId newxmax ;
uint16 flags ;
newxmax = FreezeMultiXactId ( xid , tuple - > t_infomask ,
cutoff_xid , cutoff_multi , & flags ) ;
if ( flags & FRM_INVALIDATE_XMAX )
freeze_xmax = true ;
else if ( flags & FRM_RETURN_IS_XID )
{
/* no xmax set, ignore */
;
/*
* NB - - some of these transformations are only valid because
* we know the return Xid is a tuple updater ( i . e . not merely a
* locker . ) Also note that the only reason we don ' t explicitely
* worry about HEAP_KEYS_UPDATED is because it lives in t_infomask2
* rather than t_infomask .
*/
frz - > t_infomask & = ~ HEAP_XMAX_BITS ;
frz - > xmax = newxmax ;
if ( flags & FRM_MARK_COMMITTED )
frz - > t_infomask & = HEAP_XMAX_COMMITTED ;
changed = true ;
}
else if ( MultiXactIdPrecedes ( xid , cutoff_multi ) )
else if ( flags & FRM_RETURN_IS_MULTI )
{
uint16 newbits ;
uint16 newbits2 ;
/*
* This old multi cannot possibly be running . If it was a locker
* only , it can be removed without much further thought ; but if it
* contained an update , we need to preserve it .
* We can ' t use GetMultiXactIdHintBits directly on the new multi
* here ; that routine initializes the masks to all zeroes , which
* would lose other bits we need . Doing it this way ensures all
* unrelated bits remain untouched .
*/
if ( HEAP_XMAX_IS_LOCKED_ONLY ( tuple - > t_infomask ) )
freeze_xmax = true ;
else
{
TransactionId update_xid ;
frz - > t_infomask & = ~ HEAP_XMAX_BITS ;
frz - > t_infomask2 & = ~ HEAP_KEYS_UPDATED ;
GetMultiXactIdHintBits ( newxmax , & newbits , & newbits2 ) ;
frz - > t_infomask | = newbits ;
frz - > t_infomask2 | = newbits2 ;
update_xid = HeapTupleGetUpdateXid ( tuple ) ;
frz - > xmax = newxmax ;
/*
* The multixact has an update hidden within . Get rid of it .
*
* If the update_xid is below the cutoff_xid , it necessarily
* must be an aborted transaction . In a primary server , such
* an Xmax would have gotten marked invalid by
* HeapTupleSatisfiesVacuum , but in a replica that is not
* called before we are , so deal with it in the same way .
*
* If not below the cutoff_xid , then the tuple would have been
* pruned by vacuum , if the update committed long enough ago ,
* and we wouldn ' t be freezing it ; so it ' s either recently
* committed , or in - progress . Deal with this by setting the
* Xmax to the update Xid directly and remove the IS_MULTI
* bit . ( We know there cannot be running lockers in this
* multi , because it ' s below the cutoff_multi value . )
*/
if ( TransactionIdPrecedes ( update_xid , cutoff_xid ) )
{
Assert ( InRecovery | | TransactionIdDidAbort ( update_xid ) ) ;
freeze_xmax = true ;
}
else
{
Assert ( InRecovery | | ! TransactionIdIsInProgress ( update_xid ) ) ;
tuple - > t_infomask & = ~ HEAP_XMAX_BITS ;
HeapTupleHeaderSetXmax ( tuple , update_xid ) ;
changed = true ;
}
}
}
else if ( HEAP_XMAX_IS_LOCKED_ONLY ( tuple - > t_infomask ) )
{
/* newer than the cutoff, so don't touch it */
;
changed = true ;
}
else
{
TransactionId update_xid ;
/*
* This is a multixact which is not marked LOCK_ONLY , but which
* is newer than the cutoff_multi . If the update_xid is below the
* cutoff_xid point , then we can just freeze the Xmax in the
* tuple , removing it altogether . This seems simple , but there
* are several underlying assumptions :
*
* 1. A tuple marked by an multixact containing a very old
* committed update Xid would have been pruned away by vacuum ; we
* wouldn ' t be freezing this tuple at all .
*
* 2. There cannot possibly be any live locking members remaining
* in the multixact . This is because if they were alive , the
* update ' s Xid would had been considered , via the lockers '
* snapshot ' s Xmin , as part the cutoff_xid .
*
* 3. We don ' t create new MultiXacts via MultiXactIdExpand ( ) that
* include a very old aborted update Xid : in that function we only
* include update Xids corresponding to transactions that are
* committed or in - progress .
*/
update_xid = HeapTupleGetUpdateXid ( tuple ) ;
if ( TransactionIdPrecedes ( update_xid , cutoff_xid ) )
freeze_xmax = true ;
Assert ( flags & FRM_NOOP ) ;
}
}
else if ( TransactionIdIsNormal ( xid ) & &
@ -5584,17 +5804,17 @@ heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
if ( freeze_xmax )
{
HeapTupleHeaderSetXmax ( tuple , InvalidTransactionId ) ;
frz - > xmax = InvalidTransactionId ;
/*
* The tuple might be marked either XMAX_INVALID or XMAX_COMMITTED +
* LOCKED . Normalize to INVALID just to be sure no one gets confused .
* Also get rid of the HEAP_KEYS_UPDATED bit .
*/
tuple - > t_infomask & = ~ HEAP_XMAX_BITS ;
tuple - > t_infomask | = HEAP_XMAX_INVALID ;
HeapTupleHeaderClearHotUpdated ( tuple ) ;
tuple - > t_infomask2 & = ~ HEAP_KEYS_UPDATED ;
frz - > t_infomask & = ~ HEAP_XMAX_BITS ;
frz - > t_infomask | = HEAP_XMAX_INVALID ;
frz - > t_infomask2 & = ~ HEAP_HOT_UPDATED ;
frz - > t_infomask2 & = ~ HEAP_KEYS_UPDATED ;
changed = true ;
}
@ -5614,16 +5834,16 @@ heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
* xvac transaction succeeded .
*/
if ( tuple - > t_infomask & HEAP_MOVED_OFF )
HeapTupleHeaderSetXvac ( tuple , InvalidTransactionId ) ;
frz - > frzflags | = XLH_INVALID_XVAC ;
else
HeapTupleHeaderSetXvac ( tuple , FrozenTransactionId ) ;
frz - > frzflags | = XLH_FREEZE_XVAC ;
/*
* Might as well fix the hint bits too ; usually XMIN_COMMITTED
* will already be set here , but there ' s a small chance not .
*/
Assert ( ! ( tuple - > t_infomask & HEAP_XMIN_INVALID ) ) ;
tuple - > t_infomask | = HEAP_XMIN_COMMITTED ;
frz - > t_infomask | = HEAP_XMIN_COMMITTED ;
changed = true ;
}
}
@ -5631,6 +5851,70 @@ heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
return changed ;
}
/*
* heap_execute_freeze_tuple
* Execute the prepared freezing of a tuple .
*
* Caller is responsible for ensuring that no other backend can access the
* storage underlying this tuple , either by holding an exclusive lock on the
* buffer containing it ( which is what lazy VACUUM does ) , or by having it by
* in private storage ( which is what CLUSTER and friends do ) .
*
* Note : it might seem we could make the changes without exclusive lock , since
* TransactionId read / write is assumed atomic anyway . However there is a race
* condition : someone who just fetched an old XID that we overwrite here could
* conceivably not finish checking the XID against pg_clog before we finish
* the VACUUM and perhaps truncate off the part of pg_clog he needs . Getting
* exclusive lock ensures no other backend is in process of checking the
* tuple status . Also , getting exclusive lock makes it safe to adjust the
* infomask bits .
*
* NB : All code in here must be safe to execute during crash recovery !
*/
void
heap_execute_freeze_tuple ( HeapTupleHeader tuple , xl_heap_freeze_tuple * frz )
{
if ( frz - > frzflags & XLH_FREEZE_XMIN )
HeapTupleHeaderSetXmin ( tuple , FrozenTransactionId ) ;
HeapTupleHeaderSetXmax ( tuple , frz - > xmax ) ;
if ( frz - > frzflags & XLH_FREEZE_XVAC )
HeapTupleHeaderSetXvac ( tuple , FrozenTransactionId ) ;
if ( frz - > frzflags & XLH_INVALID_XVAC )
HeapTupleHeaderSetXvac ( tuple , InvalidTransactionId ) ;
tuple - > t_infomask = frz - > t_infomask ;
tuple - > t_infomask2 = frz - > t_infomask2 ;
}
/*
* heap_freeze_tuple
* Freeze tuple in place , without WAL logging .
*
* Useful for callers like CLUSTER that perform their own WAL logging .
*/
bool
heap_freeze_tuple ( HeapTupleHeader tuple , TransactionId cutoff_xid ,
TransactionId cutoff_multi )
{
xl_heap_freeze_tuple frz ;
bool do_freeze ;
do_freeze = heap_prepare_freeze_tuple ( tuple , cutoff_xid , cutoff_multi ,
& frz ) ;
/*
* Note that because this is not a WAL - logged operation , we don ' t need to
* fill in the offset in the freeze record .
*/
if ( do_freeze )
heap_execute_freeze_tuple ( tuple , & frz ) ;
return do_freeze ;
}
/*
* For a given MultiXactId , return the hint bits that should be set in the
* tuple ' s infomask .
@ -5934,16 +6218,26 @@ heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
}
else if ( MultiXactIdPrecedes ( multi , cutoff_multi ) )
return true ;
else if ( HEAP_XMAX_IS_LOCKED_ONLY ( tuple - > t_infomask ) )
{
/* only-locker multis don't need internal examination */
;
}
else
{
if ( TransactionIdPrecedes ( HeapTupleGetUpdateXid ( tuple ) ,
cutoff_xid ) )
return true ;
MultiXactMember * members ;
int nmembers ;
int i ;
/* need to check whether any member of the mxact is too old */
nmembers = GetMultiXactIdMembers ( multi , & members , false ) ;
for ( i = 0 ; i < nmembers ; i + + )
{
if ( TransactionIdPrecedes ( members [ i ] . xid , cutoff_xid ) )
{
pfree ( members ) ;
return true ;
}
}
if ( nmembers > 0 )
pfree ( members ) ;
}
}
else
@ -6193,45 +6487,44 @@ log_heap_clean(Relation reln, Buffer buffer,
}
/*
* Perform XLogInsert for a heap - freeze operation . Caller must already
* have modified the buffer and marked it dirty .
* Perform XLogInsert for a heap - freeze operation . Caller must have already
* modified the buffer and marked it dirty .
*/
XLogRecPtr
log_heap_freeze ( Relation reln , Buffer buffer ,
TransactionId cutoff_xid , MultiXactId cutoff_multi ,
OffsetNumber * offsets , int offcnt )
log_heap_freeze ( Relation reln , Buffer buffer , TransactionId cutoff_xid ,
xl_heap_freeze_tuple * tuples , int ntuples )
{
xl_heap_freeze xlrec ;
xl_heap_freeze_page xlrec ;
XLogRecPtr recptr ;
XLogRecData rdata [ 2 ] ;
/* Caller should not call me on a non-WAL-logged relation */
Assert ( RelationNeedsWAL ( reln ) ) ;
/* nor when there are no tuples to freeze */
Assert ( offc nt > 0 ) ;
Assert ( ntuples > 0 ) ;
xlrec . node = reln - > rd_node ;
xlrec . block = BufferGetBlockNumber ( buffer ) ;
xlrec . cutoff_xid = cutoff_xid ;
xlrec . cutoff_multi = cutoff_multi ;
xlrec . ntuples = ntuples ;
rdata [ 0 ] . data = ( char * ) & xlrec ;
rdata [ 0 ] . len = SizeOfHeapFreeze ;
rdata [ 0 ] . len = SizeOfHeapFreezePage ;
rdata [ 0 ] . buffer = InvalidBuffer ;
rdata [ 0 ] . next = & ( rdata [ 1 ] ) ;
/*
* The tuple - offsets array is not actually in the buffer , but pretend that
* it is . When XLogInsert stores the whole buffer , the offsets array need
* The freeze plan array is not actually in the buffer , but pretend that
* it is . When XLogInsert stores the whole buffer , the freeze plan need
* not be stored too .
*/
rdata [ 1 ] . data = ( char * ) offset s;
rdata [ 1 ] . len = offc nt * sizeof ( OffsetNumber ) ;
rdata [ 1 ] . data = ( char * ) tuple s;
rdata [ 1 ] . len = ntuples * sizeof ( xl_heap_freeze_tuple ) ;
rdata [ 1 ] . buffer = buffer ;
rdata [ 1 ] . buffer_std = true ;
rdata [ 1 ] . next = NULL ;
recptr = XLogInsert ( RM_HEAP2_ID , XLOG_HEAP2_FREEZE , rdata ) ;
recptr = XLogInsert ( RM_HEAP2_ID , XLOG_HEAP2_FREEZE_PAGE , rdata ) ;
return recptr ;
}
@ -6848,64 +7141,6 @@ heap_xlog_clean(XLogRecPtr lsn, XLogRecord *record)
XLogRecordPageWithFreeSpace ( xlrec - > node , xlrec - > block , freespace ) ;
}
static void
heap_xlog_freeze ( XLogRecPtr lsn , XLogRecord * record )
{
xl_heap_freeze * xlrec = ( xl_heap_freeze * ) XLogRecGetData ( record ) ;
TransactionId cutoff_xid = xlrec - > cutoff_xid ;
MultiXactId cutoff_multi = xlrec - > cutoff_multi ;
Buffer buffer ;
Page page ;
/*
* In Hot Standby mode , ensure that there ' s no queries running which still
* consider the frozen xids as running .
*/
if ( InHotStandby )
ResolveRecoveryConflictWithSnapshot ( cutoff_xid , xlrec - > node ) ;
/* If we have a full-page image, restore it and we're done */
if ( record - > xl_info & XLR_BKP_BLOCK ( 0 ) )
{
( void ) RestoreBackupBlock ( lsn , record , 0 , false , false ) ;
return ;
}
buffer = XLogReadBuffer ( xlrec - > node , xlrec - > block , false ) ;
if ( ! BufferIsValid ( buffer ) )
return ;
page = ( Page ) BufferGetPage ( buffer ) ;
if ( lsn < = PageGetLSN ( page ) )
{
UnlockReleaseBuffer ( buffer ) ;
return ;
}
if ( record - > xl_len > SizeOfHeapFreeze )
{
OffsetNumber * offsets ;
OffsetNumber * offsets_end ;
offsets = ( OffsetNumber * ) ( ( char * ) xlrec + SizeOfHeapFreeze ) ;
offsets_end = ( OffsetNumber * ) ( ( char * ) xlrec + record - > xl_len ) ;
while ( offsets < offsets_end )
{
/* offsets[] entries are one-based */
ItemId lp = PageGetItemId ( page , * offsets ) ;
HeapTupleHeader tuple = ( HeapTupleHeader ) PageGetItem ( page , lp ) ;
( void ) heap_freeze_tuple ( tuple , cutoff_xid , cutoff_multi ) ;
offsets + + ;
}
}
PageSetLSN ( page , lsn ) ;
MarkBufferDirty ( buffer ) ;
UnlockReleaseBuffer ( buffer ) ;
}
/*
* Replay XLOG_HEAP2_VISIBLE record .
*
@ -7020,6 +7255,63 @@ heap_xlog_visible(XLogRecPtr lsn, XLogRecord *record)
}
}
/*
* Replay XLOG_HEAP2_FREEZE_PAGE records
*/
static void
heap_xlog_freeze_page ( XLogRecPtr lsn , XLogRecord * record )
{
xl_heap_freeze_page * xlrec = ( xl_heap_freeze_page * ) XLogRecGetData ( record ) ;
TransactionId cutoff_xid = xlrec - > cutoff_xid ;
Buffer buffer ;
Page page ;
int ntup ;
/*
* In Hot Standby mode , ensure that there ' s no queries running which still
* consider the frozen xids as running .
*/
if ( InHotStandby )
ResolveRecoveryConflictWithSnapshot ( cutoff_xid , xlrec - > node ) ;
/* If we have a full-page image, restore it and we're done */
if ( record - > xl_info & XLR_BKP_BLOCK ( 0 ) )
{
( void ) RestoreBackupBlock ( lsn , record , 0 , false , false ) ;
return ;
}
buffer = XLogReadBuffer ( xlrec - > node , xlrec - > block , false ) ;
if ( ! BufferIsValid ( buffer ) )
return ;
page = ( Page ) BufferGetPage ( buffer ) ;
if ( lsn < = PageGetLSN ( page ) )
{
UnlockReleaseBuffer ( buffer ) ;
return ;
}
/* now execute freeze plan for each frozen tuple */
for ( ntup = 0 ; ntup < xlrec - > ntuples ; ntup + + )
{
xl_heap_freeze_tuple * xlrec_tp ;
ItemId lp ;
HeapTupleHeader tuple ;
xlrec_tp = & xlrec - > tuples [ ntup ] ;
lp = PageGetItemId ( page , xlrec_tp - > offset ) ; /* offsets are one-based */
tuple = ( HeapTupleHeader ) PageGetItem ( page , lp ) ;
heap_execute_freeze_tuple ( tuple , xlrec_tp ) ;
}
PageSetLSN ( page , lsn ) ;
MarkBufferDirty ( buffer ) ;
UnlockReleaseBuffer ( buffer ) ;
}
static void
heap_xlog_newpage ( XLogRecPtr lsn , XLogRecord * record )
{
@ -7883,12 +8175,12 @@ heap2_redo(XLogRecPtr lsn, XLogRecord *record)
switch ( info & XLOG_HEAP_OPMASK )
{
case XLOG_HEAP2_FREEZE :
heap_xlog_freeze ( lsn , record ) ;
break ;
case XLOG_HEAP2_CLEAN :
heap_xlog_clean ( lsn , record ) ;
break ;
case XLOG_HEAP2_FREEZE_PAGE :
heap_xlog_freeze_page ( lsn , record ) ;
break ;
case XLOG_HEAP2_CLEANUP_INFO :
heap_xlog_cleanup_info ( lsn , record ) ;
break ;