5151#include "utils/builtins.h"
5252#include "utils/rel.h"
5353
54+ #include "heapam_r4_private.h"
55+
5456static void reform_and_rewrite_tuple (HeapTuple tuple ,
5557 Relation OldHeap , Relation NewHeap ,
5658 Datum * values , bool * isnull , RewriteState rwstate );
@@ -63,6 +65,114 @@ static BlockNumber heapam_scan_get_blocks_done(HeapScanDesc hscan);
6365
6466static const TableAmRoutine heapam_methods ;
6567
68+ static TableIndexFetchTupleResult heapam_store_hot_search_result (
69+ HeapHotSearchResult * result , TupleTableSlot * slot , Buffer buffer ,
70+ bool * call_again , bool * all_dead ) pg_attribute_unused ();
71+ #ifdef USE_PGRAC_CLUSTER
72+ static TableIndexFetchTupleResult heapam_index_fetch_cluster_hot (
73+ ItemPointer tid , Relation relation , Buffer buffer , Snapshot snapshot ,
74+ HeapHotSearchResult * result , TupleTableSlot * slot ,
75+ bool * call_again , bool * all_dead ) pg_attribute_unused ();
76+ #endif
77+
78+ /*
79+ * Consume the backend-private HOT result without leaking its descriptor
80+ * lifetime into the executor slot. BUFFER_BACKED remains pin-owned by the
81+ * buffer slot; OWNED_SCRATCH is copied while the scratch page is alive.
82+ */
83+ static TableIndexFetchTupleResult
84+ heapam_store_hot_search_result (HeapHotSearchResult * result ,
85+ TupleTableSlot * slot , Buffer buffer ,
86+ bool * call_again , bool * all_dead )
87+ {
88+ BufferHeapTupleTableSlot * bslot = (BufferHeapTupleTableSlot * ) slot ;
89+
90+ Assert (result != NULL );
91+ Assert (TTS_IS_BUFFERTUPLE (slot ));
92+ Assert (call_again != NULL );
93+
94+ * call_again = false;
95+
96+ switch (result -> kind )
97+ {
98+ case HEAP_HOT_SEARCH_NOT_FOUND :
99+ ExecClearTuple (slot );
100+ return TABLE_INDEX_FETCH_NOT_FOUND ;
101+
102+ case HEAP_HOT_SEARCH_BUFFER_BACKED :
103+ Assert (BufferIsValid (buffer ));
104+ bslot -> base .tupdata = result -> tuple ;
105+ ExecStoreBufferHeapTuple (& bslot -> base .tupdata , slot , buffer );
106+ break ;
107+
108+ case HEAP_HOT_SEARCH_OWNED_SCRATCH :
109+ ExecForceStoreHeapTuple (& result -> tuple , slot , false);
110+ slot -> tts_tid = result -> tuple .t_self ;
111+ slot -> tts_tableOid = result -> tuple .t_tableOid ;
112+ break ;
113+
114+ default :
115+ Assert (false);
116+ ExecClearTuple (slot );
117+ return TABLE_INDEX_FETCH_NOT_FOUND ;
118+ }
119+
120+ if (all_dead != NULL )
121+ * all_dead = false;
122+
123+ return TABLE_INDEX_FETCH_FOUND ;
124+ }
125+
126+ #ifdef USE_PGRAC_CLUSTER
127+ /*
128+ * Dormant post-SHARE-lock cluster-MVCC branch. The focused Unit37 receipt
129+ * enters this exact helper so it cannot replace the companion/FULL/scratch
130+ * walk with a test-only choreography. Live wiring waits for D13 to publish
131+ * an episode-safe TARGET admission state.
132+ */
133+ static TableIndexFetchTupleResult
134+ heapam_index_fetch_cluster_hot (ItemPointer tid , Relation relation ,
135+ Buffer buffer , Snapshot snapshot ,
136+ HeapHotSearchResult * result ,
137+ TupleTableSlot * slot ,
138+ bool * call_again , bool * all_dead )
139+ {
140+ Assert (result != NULL );
141+
142+ memset (result , 0 , sizeof (* result ));
143+ result -> kind = heap_hot_search_buffer_result (tid , relation , buffer ,
144+ snapshot , result , all_dead ,
145+ !* call_again );
146+ LockBuffer (buffer , BUFFER_LOCK_UNLOCK );
147+ return heapam_store_hot_search_result (result , slot , buffer ,
148+ call_again , all_dead );
149+ }
150+ #endif
151+
152+ #ifdef USE_CLUSTER_UNIT
153+ TableIndexFetchTupleResult
154+ cluster_heap_test_r4_store_hot_result (HeapHotSearchResult * result ,
155+ TupleTableSlot * slot , Buffer buffer ,
156+ bool * call_again , bool * all_dead )
157+ {
158+ return heapam_store_hot_search_result (result , slot , buffer ,
159+ call_again , all_dead );
160+ }
161+
162+ #ifdef USE_PGRAC_CLUSTER
163+ TableIndexFetchTupleResult
164+ cluster_heap_test_r4_index_hot_result (ItemPointer tid , Relation relation ,
165+ Buffer buffer , Snapshot snapshot ,
166+ HeapHotSearchResult * result ,
167+ TupleTableSlot * slot ,
168+ bool * call_again , bool * all_dead )
169+ {
170+ return heapam_index_fetch_cluster_hot (tid , relation , buffer , snapshot ,
171+ result , slot , call_again , all_dead );
172+ }
173+ #endif
174+ #endif
175+
66176
67177/* ------------------------------------------------------------------------
68178 * Slot related callbacks for heap AM
@@ -128,7 +238,7 @@ heapam_index_fetch_tuple_internal(struct IndexFetchTableData *scan,
128238 bool barrier_aware )
129239{
130240 IndexFetchHeapData * hscan = (IndexFetchHeapData * ) scan ;
131- BufferHeapTupleTableSlot * bslot = ( BufferHeapTupleTableSlot * ) slot ;
241+ HeapHotSearchResult hot_result ;
132242 bool got_heap_tuple ;
133243
134244 Assert (TTS_IS_BUFFERTUPLE (slot ));
@@ -164,32 +274,21 @@ heapam_index_fetch_tuple_internal(struct IndexFetchTableData *scan,
164274 }
165275 else
166276 LockBuffer (hscan -> xs_cbuf , BUFFER_LOCK_SHARE );
167- got_heap_tuple = heap_hot_search_buffer ( tid ,
168- hscan -> xs_base . rel ,
169- hscan -> xs_cbuf ,
170- snapshot ,
171- & bslot -> base . tupdata ,
172- all_dead ,
173- ! * call_again );
174- bslot -> base . tupdata . t_self = * tid ;
277+ memset ( & hot_result , 0 , sizeof ( hot_result ));
278+ hot_result . kind = heap_hot_search_buffer_result ( tid ,
279+ hscan -> xs_base . rel ,
280+ hscan -> xs_cbuf ,
281+ snapshot ,
282+ & hot_result ,
283+ all_dead ,
284+ ! * call_again ) ;
175285 LockBuffer (hscan -> xs_cbuf , BUFFER_LOCK_UNLOCK );
176-
177- if (got_heap_tuple )
178- {
179- /*
180- * Only in a non-MVCC snapshot can more than one member of the HOT
181- * chain be visible.
182- */
183- * call_again = !IsMVCCSnapshot (snapshot );
184-
185- slot -> tts_tableOid = RelationGetRelid (scan -> rel );
186- ExecStoreBufferHeapTuple (& bslot -> base .tupdata , slot , hscan -> xs_cbuf );
187- }
188- else
189- {
190- /* We've reached the end of the HOT chain. */
191- * call_again = false;
192- }
286+ got_heap_tuple = heapam_store_hot_search_result (& hot_result , slot ,
287+ hscan -> xs_cbuf , call_again , all_dead )
288+ == TABLE_INDEX_FETCH_FOUND ;
289+ if (got_heap_tuple && !IsMVCCSnapshot (snapshot )
290+ && hot_result .kind == HEAP_HOT_SEARCH_BUFFER_BACKED )
291+ * call_again = true;
193292
194293 return got_heap_tuple ? TABLE_INDEX_FETCH_FOUND : TABLE_INDEX_FETCH_NOT_FOUND ;
195294}
@@ -413,16 +512,29 @@ heapam_tuple_lock(Relation relation, ItemPointer tid, Snapshot snapshot,
413512 Buffer buffer ;
414513 HeapTuple tuple = & bslot -> base .tupdata ;
415514 bool follow_updates ;
515+ #ifdef USE_PGRAC_CLUSTER
516+ ClusterHeapSuccessorProof expected_successor ;
517+ ClusterHeapSuccessorProof next_successor ;
518+ #endif
416519
417520 follow_updates = (flags & TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS ) != 0 ;
418521 tmfd -> traversed = false;
522+ #ifdef USE_PGRAC_CLUSTER
523+ memset (& expected_successor , 0 , sizeof (expected_successor ));
524+ memset (& next_successor , 0 , sizeof (next_successor ));
525+ #endif
419526
420527 Assert (TTS_IS_BUFFERTUPLE (slot ));
421528
422529tuple_lock_retry :
423530 tuple -> t_self = * tid ;
424531 result = heap_lock_tuple (relation , tuple , cid , mode , wait_policy ,
425- follow_updates , & buffer , tmfd );
532+ follow_updates , & buffer , tmfd
533+ #ifdef USE_PGRAC_CLUSTER
534+ , expected_successor .valid ? & expected_successor : NULL
535+ , & next_successor
536+ #endif
537+ );
426538
427539 if (result == TM_Updated &&
428540 (flags & TUPLE_LOCK_FLAG_FIND_LAST_VERSION ))
@@ -437,6 +549,23 @@ heapam_tuple_lock(Relation relation, ItemPointer tid, Snapshot snapshot,
437549 SnapshotData SnapshotDirty ;
438550 TransactionId priorXmax ;
439551
552+ #ifdef USE_PGRAC_CLUSTER
553+ /*
554+ * A current-MultiXact authority result carries the exact
555+ * successor ITL/TT proof. Re-enter heap_lock_tuple directly and
556+ * consume that proof under the destination PCM-X; never reduce it
557+ * to tmfd.xmax/raw xmin across this unlocked handoff.
558+ */
559+ if (next_successor .valid )
560+ {
561+ expected_successor = next_successor ;
562+ * tid = next_successor .tid ;
563+ tmfd -> traversed = true;
564+ goto tuple_lock_retry ;
565+ }
566+ memset (& expected_successor , 0 , sizeof (expected_successor ));
567+ #endif
568+
440569 /* it was updated, so look at the updated version */
441570 * tid = tmfd -> ctid ;
442571 /* updated row should have xmin matching this xmax */
0 commit comments