Index-only-scans, indexam API changes - Mailing list pgsql-hackers
From | Heikki Linnakangas |
---|---|
Subject | Index-only-scans, indexam API changes |
Date | |
Msg-id | 4A5ADFE6.6060507@enterprisedb.com Whole thread Raw |
Responses |
Re: Index-only-scans, indexam API changes
Re: Index-only-scans, indexam API changes |
List | pgsql-hackers |
At the moment, amgettuple only returns pointers to heap tuples. There is no way to return data from the index tuples. That needs to be changed to support index-only scans. I propose that we split index_getnext into two steps: fetching the next match from the index (index_next()), and fetching the corresponding heap tuple (index_fetch()). Patch attached. There is still a shorthand index_getnext() that is compatible with the old function, but it now just calls index_next()+index_fetch(). The new index_fetch() function can only fetch one match from a HOT chain. That greatly simplifies the code in indexam.c. The only caller needing to fetch more than one tuple from a HOT chain (= using SnapshotAny) is CLUSTER, so I moved the HOT-chain following logic into cluster.c, with small changes to heap_hot_search_buffer(). -- Heikki Linnakangas EnterpriseDB http://www.enterprisedb.com *** a/src/backend/access/heap/heapam.c --- b/src/backend/access/heap/heapam.c *************** *** 1490,1497 **** heap_fetch(Relation relation, * On entry, *tid is the TID of a tuple (either a simple tuple, or the root * of a HOT chain), and buffer is the buffer holding this tuple. We search * for the first chain member satisfying the given snapshot. If one is ! * found, we update *tid to reference that tuple's offset number, and ! * return TRUE. If no match, return FALSE without modifying *tid. * * If all_dead is not NULL, we check non-visible tuples to see if they are * globally dead; *all_dead is set TRUE if all members of the HOT chain --- 1490,1503 ---- * On entry, *tid is the TID of a tuple (either a simple tuple, or the root * of a HOT chain), and buffer is the buffer holding this tuple. We search * for the first chain member satisfying the given snapshot. If one is ! * found, we update *tid and *heapTuple to reference that tuple, and ! * return TRUE. If no match, return FALSE without modifying *tid (*heapTuple ! * is undefined in that case). ! * ! * To fetch the next matching tuple in the chain, call again with 'firstCall' ! * set to FALSE and *tid still pointing to the tuple returned by previous call. ! * (normally only one tuple in a chain can be visible at a time, but that does ! * not hold for special snapshots like SnapshotAny) * * If all_dead is not NULL, we check non-visible tuples to see if they are * globally dead; *all_dead is set TRUE if all members of the HOT chain *************** *** 1503,1529 **** heap_fetch(Relation relation, */ bool heap_hot_search_buffer(ItemPointer tid, Buffer buffer, Snapshot snapshot, ! bool *all_dead) { Page dp = (Page) BufferGetPage(buffer); TransactionId prev_xmax = InvalidTransactionId; OffsetNumber offnum; bool at_chain_start; ! if (all_dead) *all_dead = true; Assert(TransactionIdIsValid(RecentGlobalXmin)); Assert(ItemPointerGetBlockNumber(tid) == BufferGetBlockNumber(buffer)); offnum = ItemPointerGetOffsetNumber(tid); ! at_chain_start = true; /* Scan through possible multiple members of HOT-chain */ for (;;) { ItemId lp; - HeapTupleData heapTuple; /* check for bogus TID */ if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(dp)) --- 1509,1537 ---- */ bool heap_hot_search_buffer(ItemPointer tid, Buffer buffer, Snapshot snapshot, ! HeapTuple heapTuple, bool *all_dead, bool firstCall) { Page dp = (Page) BufferGetPage(buffer); TransactionId prev_xmax = InvalidTransactionId; OffsetNumber offnum; bool at_chain_start; + bool skip; ! if (all_dead && firstCall) *all_dead = true; Assert(TransactionIdIsValid(RecentGlobalXmin)); Assert(ItemPointerGetBlockNumber(tid) == BufferGetBlockNumber(buffer)); offnum = ItemPointerGetOffsetNumber(tid); ! ! at_chain_start = firstCall; ! skip = !firstCall; /* Scan through possible multiple members of HOT-chain */ for (;;) { ItemId lp; /* check for bogus TID */ if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(dp)) *************** *** 1546,1558 **** heap_hot_search_buffer(ItemPointer tid, Buffer buffer, Snapshot snapshot, break; } ! heapTuple.t_data = (HeapTupleHeader) PageGetItem(dp, lp); ! heapTuple.t_len = ItemIdGetLength(lp); /* * Shouldn't see a HEAP_ONLY tuple at chain start. */ ! if (at_chain_start && HeapTupleIsHeapOnly(&heapTuple)) break; /* --- 1554,1566 ---- break; } ! heapTuple->t_data = (HeapTupleHeader) PageGetItem(dp, lp); ! heapTuple->t_len = ItemIdGetLength(lp); /* * Shouldn't see a HEAP_ONLY tuple at chain start. */ ! if (at_chain_start && HeapTupleIsHeapOnly(heapTuple)) break; /* *************** *** 1561,1577 **** heap_hot_search_buffer(ItemPointer tid, Buffer buffer, Snapshot snapshot, */ if (TransactionIdIsValid(prev_xmax) && !TransactionIdEquals(prev_xmax, ! HeapTupleHeaderGetXmin(heapTuple.t_data))) break; /* If it's visible per the snapshot, we must return it */ ! if (HeapTupleSatisfiesVisibility(&heapTuple, snapshot, buffer)) { ItemPointerSetOffsetNumber(tid, offnum); if (all_dead) *all_dead = false; return true; } /* * If we can't see it, maybe no one else can either. At caller --- 1569,1586 ---- */ if (TransactionIdIsValid(prev_xmax) && !TransactionIdEquals(prev_xmax, ! HeapTupleHeaderGetXmin(heapTuple->t_data))) break; /* If it's visible per the snapshot, we must return it */ ! if (!skip && HeapTupleSatisfiesVisibility(heapTuple, snapshot, buffer)) { ItemPointerSetOffsetNumber(tid, offnum); if (all_dead) *all_dead = false; return true; } + skip = false; /* * If we can't see it, maybe no one else can either. At caller *************** *** 1579,1585 **** heap_hot_search_buffer(ItemPointer tid, Buffer buffer, Snapshot snapshot, * transactions. */ if (all_dead && *all_dead && ! HeapTupleSatisfiesVacuum(heapTuple.t_data, RecentGlobalXmin, buffer) != HEAPTUPLE_DEAD) *all_dead = false; --- 1588,1594 ---- * transactions. */ if (all_dead && *all_dead && ! HeapTupleSatisfiesVacuum(heapTuple->t_data, RecentGlobalXmin, buffer) != HEAPTUPLE_DEAD) *all_dead = false; *************** *** 1587,1599 **** heap_hot_search_buffer(ItemPointer tid, Buffer buffer, Snapshot snapshot, * Check to see if HOT chain continues past this tuple; if so fetch * the next offnum and loop around. */ ! if (HeapTupleIsHotUpdated(&heapTuple)) { ! Assert(ItemPointerGetBlockNumber(&heapTuple.t_data->t_ctid) == ItemPointerGetBlockNumber(tid)); ! offnum = ItemPointerGetOffsetNumber(&heapTuple.t_data->t_ctid); at_chain_start = false; ! prev_xmax = HeapTupleHeaderGetXmax(heapTuple.t_data); } else break; /* end of chain */ --- 1596,1608 ---- * Check to see if HOT chain continues past this tuple; if so fetch * the next offnum and loop around. */ ! if (HeapTupleIsHotUpdated(heapTuple)) { ! Assert(ItemPointerGetBlockNumber(&heapTuple->t_data->t_ctid) == ItemPointerGetBlockNumber(tid)); ! offnum = ItemPointerGetOffsetNumber(&heapTuple->t_data->t_ctid); at_chain_start = false; ! prev_xmax = HeapTupleHeaderGetXmax(heapTuple->t_data); } else break; /* end of chain */ *************** *** 1615,1624 **** heap_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot, { bool result; Buffer buffer; buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid)); LockBuffer(buffer, BUFFER_LOCK_SHARE); ! result = heap_hot_search_buffer(tid, buffer, snapshot, all_dead); LockBuffer(buffer, BUFFER_LOCK_UNLOCK); ReleaseBuffer(buffer); return result; --- 1624,1635 ---- { bool result; Buffer buffer; + HeapTupleData heapTuple; buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid)); LockBuffer(buffer, BUFFER_LOCK_SHARE); ! result = heap_hot_search_buffer(tid, buffer, snapshot, &heapTuple, ! all_dead, true); LockBuffer(buffer, BUFFER_LOCK_UNLOCK); ReleaseBuffer(buffer); return result; *** a/src/backend/access/index/genam.c --- b/src/backend/access/index/genam.c *************** *** 97,105 **** RelationGetIndexScan(Relation indexRelation, ItemPointerSetInvalid(&scan->xs_ctup.t_self); scan->xs_ctup.t_data = NULL; scan->xs_cbuf = InvalidBuffer; - scan->xs_hot_dead = false; - scan->xs_next_hot = InvalidOffsetNumber; - scan->xs_prev_xmax = InvalidTransactionId; /* * Let the AM fill in the key and any opaque data it wants. --- 97,102 ---- *** a/src/backend/access/index/indexam.c --- b/src/backend/access/index/indexam.c *************** *** 20,26 **** * index_insert - insert an index tuple into a relation * index_markpos - mark a scan position * index_restrpos - restore a scan position ! * index_getnext - get the next tuple from a scan * index_getbitmap - get all tuples from a scan * index_bulk_delete - bulk deletion of index tuples * index_vacuum_cleanup - post-deletion cleanup of an index --- 20,27 ---- * index_insert - insert an index tuple into a relation * index_markpos - mark a scan position * index_restrpos - restore a scan position ! * index_next - advance index scan to next match ! * index_fetch - fetch heap tuple for current index scan position * index_getbitmap - get all tuples from a scan * index_bulk_delete - bulk deletion of index tuples * index_vacuum_cleanup - post-deletion cleanup of an index *************** *** 310,317 **** index_rescan(IndexScanDesc scan, ScanKey key) scan->xs_cbuf = InvalidBuffer; } - scan->xs_next_hot = InvalidOffsetNumber; - scan->kill_prior_tuple = false; /* for safety */ FunctionCall2(procedure, --- 311,316 ---- *************** *** 389,420 **** index_restrpos(IndexScanDesc scan) SCAN_CHECKS; GET_SCAN_PROCEDURE(amrestrpos); - scan->xs_next_hot = InvalidOffsetNumber; - scan->kill_prior_tuple = false; /* for safety */ FunctionCall1(procedure, PointerGetDatum(scan)); } /* ---------------- ! * index_getnext - get the next heap tuple from a scan * ! * The result is the next heap tuple satisfying the scan keys and the ! * snapshot, or NULL if no more matching tuples exist. On success, ! * the buffer containing the heap tuple is pinned (the pin will be dropped ! * at the next index_getnext or index_endscan). * * Note: caller must check scan->xs_recheck, and perform rechecking of the * scan keys if required. We do not do that here because we don't have * enough information to do it efficiently in the general case. * ---------------- */ ! HeapTuple ! index_getnext(IndexScanDesc scan, ScanDirection direction) { - HeapTuple heapTuple = &scan->xs_ctup; - ItemPointer tid = &heapTuple->t_self; FmgrInfo *procedure; SCAN_CHECKS; GET_SCAN_PROCEDURE(amgettuple); --- 388,415 ---- SCAN_CHECKS; GET_SCAN_PROCEDURE(amrestrpos); scan->kill_prior_tuple = false; /* for safety */ FunctionCall1(procedure, PointerGetDatum(scan)); } /* ---------------- ! * index_next - get the next index tuple from a scan * ! * Advances to the next index tuple satisfying the scan keys, returning TRUE ! * if there was one, FALSE otherwise. The heap TID pointer of the next match ! * is stored in scan->xs_ctup.self. * * Note: caller must check scan->xs_recheck, and perform rechecking of the * scan keys if required. We do not do that here because we don't have * enough information to do it efficiently in the general case. * ---------------- */ ! bool ! index_next(IndexScanDesc scan, ScanDirection direction) { FmgrInfo *procedure; + bool found; SCAN_CHECKS; GET_SCAN_PROCEDURE(amgettuple); *************** *** 422,641 **** index_getnext(IndexScanDesc scan, ScanDirection direction) Assert(TransactionIdIsValid(RecentGlobalXmin)); /* ! * We always reset xs_hot_dead; if we are here then either we are just ! * starting the scan, or we previously returned a visible tuple, and in ! * either case it's inappropriate to kill the prior index entry. */ ! scan->xs_hot_dead = false; ! for (;;) ! { ! OffsetNumber offnum; ! bool at_chain_start; ! Page dp; ! if (scan->xs_next_hot != InvalidOffsetNumber) ! { ! /* ! * We are resuming scan of a HOT chain after having returned an ! * earlier member. Must still hold pin on current heap page. ! */ ! Assert(BufferIsValid(scan->xs_cbuf)); ! Assert(ItemPointerGetBlockNumber(tid) == ! BufferGetBlockNumber(scan->xs_cbuf)); ! Assert(TransactionIdIsValid(scan->xs_prev_xmax)); ! offnum = scan->xs_next_hot; ! at_chain_start = false; ! scan->xs_next_hot = InvalidOffsetNumber; ! } ! else { ! bool found; ! Buffer prev_buf; ! ! /* ! * If we scanned a whole HOT chain and found only dead tuples, ! * tell index AM to kill its entry for that TID. ! */ ! scan->kill_prior_tuple = scan->xs_hot_dead; ! ! /* ! * The AM's gettuple proc finds the next index entry matching the ! * scan keys, and puts the TID in xs_ctup.t_self (ie, *tid). It ! * should also set scan->xs_recheck, though we pay no attention to ! * that here. ! */ ! found = DatumGetBool(FunctionCall2(procedure, ! PointerGetDatum(scan), ! Int32GetDatum(direction))); ! ! /* Reset kill flag immediately for safety */ ! scan->kill_prior_tuple = false; ! ! /* If we're out of index entries, break out of outer loop */ ! if (!found) ! break; ! ! pgstat_count_index_tuples(scan->indexRelation, 1); ! ! /* Switch to correct buffer if we don't have it already */ ! prev_buf = scan->xs_cbuf; ! scan->xs_cbuf = ReleaseAndReadBuffer(scan->xs_cbuf, ! scan->heapRelation, ! ItemPointerGetBlockNumber(tid)); ! ! /* ! * Prune page, but only if we weren't already on this page ! */ ! if (prev_buf != scan->xs_cbuf) ! heap_page_prune_opt(scan->heapRelation, scan->xs_cbuf, ! RecentGlobalXmin); ! ! /* Prepare to scan HOT chain starting at index-referenced offnum */ ! offnum = ItemPointerGetOffsetNumber(tid); ! at_chain_start = true; ! ! /* We don't know what the first tuple's xmin should be */ ! scan->xs_prev_xmax = InvalidTransactionId; ! ! /* Initialize flag to detect if all entries are dead */ ! scan->xs_hot_dead = true; } ! /* Obtain share-lock on the buffer so we can examine visibility */ ! LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE); ! dp = (Page) BufferGetPage(scan->xs_cbuf); - /* Scan through possible multiple members of HOT-chain */ - for (;;) - { - ItemId lp; - ItemPointer ctid; - - /* check for bogus TID */ - if (offnum < FirstOffsetNumber || - offnum > PageGetMaxOffsetNumber(dp)) - break; - - lp = PageGetItemId(dp, offnum); - - /* check for unused, dead, or redirected items */ - if (!ItemIdIsNormal(lp)) - { - /* We should only see a redirect at start of chain */ - if (ItemIdIsRedirected(lp) && at_chain_start) - { - /* Follow the redirect */ - offnum = ItemIdGetRedirect(lp); - at_chain_start = false; - continue; - } - /* else must be end of chain */ - break; - } - - /* - * We must initialize all of *heapTuple (ie, scan->xs_ctup) since - * it is returned to the executor on success. - */ - heapTuple->t_data = (HeapTupleHeader) PageGetItem(dp, lp); - heapTuple->t_len = ItemIdGetLength(lp); - ItemPointerSetOffsetNumber(tid, offnum); - heapTuple->t_tableOid = RelationGetRelid(scan->heapRelation); - ctid = &heapTuple->t_data->t_ctid; - - /* - * Shouldn't see a HEAP_ONLY tuple at chain start. (This test - * should be unnecessary, since the chain root can't be removed - * while we have pin on the index entry, but let's make it - * anyway.) - */ - if (at_chain_start && HeapTupleIsHeapOnly(heapTuple)) - break; - - /* - * The xmin should match the previous xmax value, else chain is - * broken. (Note: this test is not optional because it protects - * us against the case where the prior chain member's xmax aborted - * since we looked at it.) - */ - if (TransactionIdIsValid(scan->xs_prev_xmax) && - !TransactionIdEquals(scan->xs_prev_xmax, - HeapTupleHeaderGetXmin(heapTuple->t_data))) - break; - - /* If it's visible per the snapshot, we must return it */ - if (HeapTupleSatisfiesVisibility(heapTuple, scan->xs_snapshot, - scan->xs_cbuf)) - { - /* - * If the snapshot is MVCC, we know that it could accept at - * most one member of the HOT chain, so we can skip examining - * any more members. Otherwise, check for continuation of the - * HOT-chain, and set state for next time. - */ - if (IsMVCCSnapshot(scan->xs_snapshot)) - scan->xs_next_hot = InvalidOffsetNumber; - else if (HeapTupleIsHotUpdated(heapTuple)) - { - Assert(ItemPointerGetBlockNumber(ctid) == - ItemPointerGetBlockNumber(tid)); - scan->xs_next_hot = ItemPointerGetOffsetNumber(ctid); - scan->xs_prev_xmax = HeapTupleHeaderGetXmax(heapTuple->t_data); - } - else - scan->xs_next_hot = InvalidOffsetNumber; - - LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK); - - pgstat_count_heap_fetch(scan->indexRelation); - - return heapTuple; - } - - /* - * If we can't see it, maybe no one else can either. Check to see - * if the tuple is dead to all transactions. If we find that all - * the tuples in the HOT chain are dead, we'll signal the index AM - * to not return that TID on future indexscans. - */ - if (scan->xs_hot_dead && - HeapTupleSatisfiesVacuum(heapTuple->t_data, RecentGlobalXmin, - scan->xs_cbuf) != HEAPTUPLE_DEAD) - scan->xs_hot_dead = false; - - /* - * Check to see if HOT chain continues past this tuple; if so - * fetch the next offnum (we don't bother storing it into - * xs_next_hot, but must store xs_prev_xmax), and loop around. - */ - if (HeapTupleIsHotUpdated(heapTuple)) - { - Assert(ItemPointerGetBlockNumber(ctid) == - ItemPointerGetBlockNumber(tid)); - offnum = ItemPointerGetOffsetNumber(ctid); - at_chain_start = false; - scan->xs_prev_xmax = HeapTupleHeaderGetXmax(heapTuple->t_data); - } - else - break; /* end of chain */ - } /* loop over a single HOT chain */ - - LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK); - - /* Loop around to ask index AM for another TID */ - scan->xs_next_hot = InvalidOffsetNumber; - } ! /* Release any held pin on a heap page */ ! if (BufferIsValid(scan->xs_cbuf)) { ! ReleaseBuffer(scan->xs_cbuf); ! scan->xs_cbuf = InvalidBuffer; } ! return NULL; /* failure exit */ } /* ---------------- --- 417,541 ---- Assert(TransactionIdIsValid(RecentGlobalXmin)); /* ! * The AM's gettuple proc finds the next index entry matching the scan ! * keys, and puts the TID in xs_ctup.t_self. It should also set ! * scan->xs_recheck, though we pay no attention to that here. */ ! found = DatumGetBool(FunctionCall2(procedure, ! PointerGetDatum(scan), ! Int32GetDatum(direction))); ! /* Reset kill flag immediately for safety */ ! scan->kill_prior_tuple = false; ! /* If we're out of index entries, release any held pin on a heap page */ ! if (!found) ! { ! if (BufferIsValid(scan->xs_cbuf)) { ! ReleaseBuffer(scan->xs_cbuf); ! scan->xs_cbuf = InvalidBuffer; } + return false; + } ! pgstat_count_index_tuples(scan->indexRelation, 1); ! return true; ! } ! /* ---------------- ! * index_fetch - fetch the heap tuple the current index pointer points to ! * ! * The result is the heap tuple the current index tuple points to if it ! * matches the snapshot, or NULL if it doesn't. On success, the buffer ! * containing the heap tuple is pinned (the pin will be dropped at the next ! * index_fetch or index_endscan). ! * ---------------- ! */ ! HeapTuple ! index_fetch(IndexScanDesc scan) ! { ! ItemPointer tid = &scan->xs_ctup.t_self; ! bool found; ! Buffer prev_buf; ! ! /* ! * We only fetch the first matching tuple in a chain, so we can't support ! * SnapshotAny. All other snapshots only consider one tuple in a HOT chain ! * as visible at any given moment. ! */ ! Assert(scan->xs_snapshot != SnapshotAny); ! ! /* Switch to correct buffer if we don't have it already */ ! prev_buf = scan->xs_cbuf; ! scan->xs_cbuf = ReleaseAndReadBuffer(scan->xs_cbuf, ! scan->heapRelation, ! ItemPointerGetBlockNumber(tid)); ! ! /* ! * Prune page, but only if we weren't already on this page ! */ ! if (prev_buf != scan->xs_cbuf) ! heap_page_prune_opt(scan->heapRelation, scan->xs_cbuf, ! RecentGlobalXmin); ! ! /* Obtain share-lock on the buffer so we can examine visibility */ ! LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE); ! ! found = heap_hot_search_buffer(tid, scan->xs_cbuf, ! scan->xs_snapshot, &scan->xs_ctup, ! &scan->kill_prior_tuple, true); ! LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK); ! ! if (found) { ! Assert(!scan->kill_prior_tuple); ! ! /* ! * We must initialize all of *heapTuple (ie, scan->xs_ctup) since ! * it is returned to the executor on success. ! */ ! scan->xs_ctup.t_tableOid = RelationGetRelid(scan->heapRelation); ! ! pgstat_count_heap_fetch(scan->indexRelation); ! ! return &scan->xs_ctup; } + else + return NULL; + } ! /* ---------------- ! * index_getnext - get the next heap tuple from a scan ! * ! * This is a shorthand for index_next() + index_fetch(). ! * ! * The result is the next heap tuple satisfying the scan keys and the ! * snapshot, or NULL if no more matching tuples exist. On success, ! * the buffer containing the heap tuple is pinned (the pin will be dropped ! * at the next index_getnext or index_endscan). ! * ! * Note: caller must check scan->xs_recheck, and perform rechecking of the ! * scan keys if required. We do not do that here because we don't have ! * enough information to do it efficiently in the general case. ! * ---------------- ! */ ! HeapTuple ! index_getnext(IndexScanDesc scan, ScanDirection direction) ! { ! for(;;) ! { ! HeapTuple tup; ! ! if (!index_next(scan, direction)) ! return NULL; ! ! tup = index_fetch(scan); ! if (tup != NULL) ! return tup; ! } } /* ---------------- *** a/src/backend/commands/cluster.c --- b/src/backend/commands/cluster.c *************** *** 772,777 **** copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex) --- 772,778 ---- TransactionId OldestXmin; TransactionId FreezeXid; RewriteState rwstate; + ItemPointerData tid; /* * Open the relations we need. *************** *** 829,847 **** copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex) scan = index_beginscan(OldHeap, OldIndex, SnapshotAny, 0, (ScanKey) NULL); ! while ((tuple = index_getnext(scan, ForwardScanDirection)) != NULL) { HeapTuple copiedTuple; bool isdead; int i; CHECK_FOR_INTERRUPTS(); ! /* Since we used no scan keys, should never need to recheck */ ! if (scan->xs_recheck) ! elog(ERROR, "CLUSTER does not support lossy index conditions"); ! LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE); switch (HeapTupleSatisfiesVacuum(tuple->t_data, OldestXmin, scan->xs_cbuf)) --- 830,885 ---- scan = index_beginscan(OldHeap, OldIndex, SnapshotAny, 0, (ScanKey) NULL); ! tuple = NULL; ! for(;;) { HeapTuple copiedTuple; bool isdead; int i; + bool found; CHECK_FOR_INTERRUPTS(); ! /* Advance to next index tuple if we're done with current HOT chain */ ! if (tuple == NULL) ! { ! if (!index_next(scan, ForwardScanDirection)) ! break; ! ! /* Since we used no scan keys, should never need to recheck */ ! if (scan->xs_recheck) ! elog(ERROR, "CLUSTER does not support lossy index conditions"); ! ! tid = scan->xs_ctup.t_self; ! scan->xs_cbuf = ReleaseAndReadBuffer(scan->xs_cbuf, OldHeap, ! ItemPointerGetBlockNumber(&tid)); ! /* Obtain share-lock on the buffer so we can examine visibility */ ! LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE); ! found = heap_hot_search_buffer(&tid, scan->xs_cbuf, ! scan->xs_snapshot, &scan->xs_ctup, ! &scan->kill_prior_tuple, true); ! if (!found) ! { ! LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK); ! break; ! } ! } ! else ! { ! /* Fetch next tuple from current HOT chain */ ! LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE); ! found = heap_hot_search_buffer(&tid, scan->xs_cbuf, ! scan->xs_snapshot, &scan->xs_ctup, ! &scan->kill_prior_tuple, false); ! if (!found) ! { ! LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK); ! tuple = NULL; ! continue; ! } ! } ! tuple = &scan->xs_ctup; switch (HeapTupleSatisfiesVacuum(tuple->t_data, OldestXmin, scan->xs_cbuf)) *** a/src/backend/executor/nodeBitmapHeapscan.c --- b/src/backend/executor/nodeBitmapHeapscan.c *************** *** 382,390 **** bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres) { OffsetNumber offnum = tbmres->offsets[curslot]; ItemPointerData tid; ItemPointerSet(&tid, page, offnum); ! if (heap_hot_search_buffer(&tid, buffer, snapshot, NULL)) scan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid); } } --- 382,392 ---- { OffsetNumber offnum = tbmres->offsets[curslot]; ItemPointerData tid; + HeapTupleData heapTuple; ItemPointerSet(&tid, page, offnum); ! if (heap_hot_search_buffer(&tid, buffer, snapshot, &heapTuple, ! NULL, true)) scan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid); } } *** a/src/include/access/genam.h --- b/src/include/access/genam.h *************** *** 117,122 **** extern void index_endscan(IndexScanDesc scan); --- 117,125 ---- extern void index_markpos(IndexScanDesc scan); extern void index_restrpos(IndexScanDesc scan); extern HeapTuple index_getnext(IndexScanDesc scan, ScanDirection direction); + extern bool index_next(IndexScanDesc scan, ScanDirection direction); + extern HeapTuple index_fetch(IndexScanDesc scan); + extern int64 index_getbitmap(IndexScanDesc scan, TIDBitmap *bitmap); extern IndexBulkDeleteResult *index_bulk_delete(IndexVacuumInfo *info, *** a/src/include/access/heapam.h --- b/src/include/access/heapam.h *************** *** 83,89 **** extern bool heap_fetch(Relation relation, Snapshot snapshot, HeapTuple tuple, Buffer *userbuf, bool keep_buf, Relation stats_relation); extern bool heap_hot_search_buffer(ItemPointer tid, Buffer buffer, ! Snapshot snapshot, bool *all_dead); extern bool heap_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot, bool *all_dead); --- 83,90 ---- HeapTuple tuple, Buffer *userbuf, bool keep_buf, Relation stats_relation); extern bool heap_hot_search_buffer(ItemPointer tid, Buffer buffer, ! Snapshot snapshot, HeapTuple tuple, ! bool *all_dead, bool firstCall); extern bool heap_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot, bool *all_dead); *** a/src/include/access/relscan.h --- b/src/include/access/relscan.h *************** *** 77,87 **** typedef struct IndexScanDescData Buffer xs_cbuf; /* current heap buffer in scan, if any */ /* NB: if xs_cbuf is not InvalidBuffer, we hold a pin on that buffer */ bool xs_recheck; /* T means scan keys must be rechecked */ - - /* state data for traversing HOT chains in index_getnext */ - bool xs_hot_dead; /* T if all members of HOT chain are dead */ - OffsetNumber xs_next_hot; /* next member of HOT chain, if any */ - TransactionId xs_prev_xmax; /* previous HOT chain member's XMAX, if any */ } IndexScanDescData; /* Struct for heap-or-index scans of system tables */ --- 77,82 ----
pgsql-hackers by date: