/* ---------------- * index_rescan - (re)start a scan of an index * * During a restart, the caller may specify a new set of scankeys and/or * orderbykeys; but the number of keys cannot differ from what index_beginscan * was told. (Later we might relax that to "must not exceed", but currently * the index AMs tend to assume that scan->numberOfKeys is what to believe.) * To restart the scan without changing keys, pass NULL for the key arrays. * (Of course, keys *must* be passed on the first call, unless * scan->numberOfKeys is zero.) * ---------------- */ void index_rescan( struct index_scan *scan, struct scankey *keys, int nkeys, struct scankey *orderbys, int norderbys) { struct fmgr_info *procedure; SCAN_CHECKS; GET_SCAN_PROCEDURE(amrescan); ASSERT(nkeys == scan->numberOfKeys); ASSERT(norderbys == scan->numberOfOrderBys); /* Release any held pin on a heap page */ if (BUF_VALID(scan->xs_cbuf)) { release_buf(scan->xs_cbuf); scan->xs_cbuf = INVALID_BUF; } scan->xs_next_hot = INVALID_ITEM_ID; scan->kill_prior_tuple = false; /* for safety */ FC5(procedure, PTR_TO_D(scan), PTR_TO_D(keys), INT32_TO_D(nkeys), PTR_TO_D(orderbys), INT32_TO_D(norderbys)); }
/* ---------------- * index_rescan - (re)start a scan of an index * * During a restart, the caller may specify a new set of scankeys and/or * orderbykeys; but the number of keys cannot differ from what index_beginscan * was told. (Later we might relax that to "must not exceed", but currently * the index AMs tend to assume that scan->numberOfKeys is what to believe.) * To restart the scan without changing keys, pass NULL for the key arrays. * (Of course, keys *must* be passed on the first call, unless * scan->numberOfKeys is zero.) * ---------------- */ void index_rescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int norderbys) { FmgrInfo *procedure; SCAN_CHECKS; GET_SCAN_PROCEDURE(amrescan); Assert(nkeys == scan->numberOfKeys); Assert(norderbys == scan->numberOfOrderBys); /* Release any held pin on a heap page */ if (BufferIsValid(scan->xs_cbuf)) { ReleaseBuffer(scan->xs_cbuf); scan->xs_cbuf = InvalidBuffer; } scan->xs_continue_hot = false; scan->kill_prior_tuple = false; /* for safety */ FunctionCall5(procedure, PointerGetDatum(scan), PointerGetDatum(keys), Int32GetDatum(nkeys), PointerGetDatum(orderbys), Int32GetDatum(norderbys)); }
/* ---------------- * index_endscan - end a scan * ---------------- */ void index_endscan(IndexScanDesc scan) { RegProcedure procedure; SCAN_CHECKS; GET_SCAN_PROCEDURE(endscan, amendscan); /* Release any held pin on a heap page */ if (BufferIsValid(scan->xs_cbuf)) { ReleaseBuffer(scan->xs_cbuf); scan->xs_cbuf = InvalidBuffer; } /* End the AM's scan */ OidFunctionCall1(procedure, PointerGetDatum(scan)); /* Release index lock and refcount acquired by index_beginscan */ UnlockRelation(scan->indexRelation, AccessShareLock); RelationDecrementReferenceCount(scan->indexRelation); /* Release the scan data structure itself */ IndexScanEnd(scan); }
/* ---------------- * index_getbitmap - get all tuples at once from an index scan * * Adds the TIDs of all heap tuples satisfying the scan keys to a bitmap. * Since there's no interlock between the index scan and the eventual heap * access, this is only safe to use with MVCC-based snapshots: the heap * item slot could have been replaced by a newer tuple by the time we get * to it. * * Returns the number of matching tuples found. (Note: this might be only * approximate, so it should only be used for statistical purposes.) * ---------------- */ int64 index_getbitmap(struct index_scan* scan, tbm_n* bitmap) { struct fmgr_info *procedure; int64 ntids; datum_t d; SCAN_CHECKS; GET_SCAN_PROCEDURE(amgetbitmap); /* just make sure this is false... */ scan->kill_prior_tuple = false; /* * have the am's getbitmap proc do all the work. */ d = FC2(procedure, PTR_TO_D(scan), PTR_TO_D(bitmap)); ntids = D_TO_INT64(d); /* If int8 is pass-by-ref, must free the result to avoid memory leak */ #ifndef USE_FLOAT8_BYVAL pfree(D_TO_PTR(d)); #endif stat_index_tuples(scan->indexRelation, ntids); return ntids; }
/* ---------------- * index_getbitmap - get all tuples at once from an index scan * * Adds the TIDs of all heap tuples satisfying the scan keys to a bitmap. * Since there's no interlock between the index scan and the eventual heap * access, this is only safe to use with MVCC-based snapshots: the heap * item slot could have been replaced by a newer tuple by the time we get * to it. * * Returns the number of matching tuples found. (Note: this might be only * approximate, so it should only be used for statistical purposes.) * ---------------- */ int64 index_getbitmap(IndexScanDesc scan, TIDBitmap *bitmap) { FmgrInfo *procedure; int64 ntids; Datum d; SCAN_CHECKS; GET_SCAN_PROCEDURE(amgetbitmap); /* just make sure this is false... */ scan->kill_prior_tuple = false; /* * have the am's getbitmap proc do all the work. */ d = FunctionCall2(procedure, PointerGetDatum(scan), PointerGetDatum(bitmap)); ntids = DatumGetInt64(d); /* If int8 is pass-by-ref, must free the result to avoid memory leak */ #ifndef USE_FLOAT8_BYVAL pfree(DatumGetPointer(d)); #endif pgstat_count_index_tuples(scan->indexRelation, ntids); return ntids; }
/* ---------------- * index_markpos - mark a scan position * ---------------- */ void index_markpos(struct index_scan *scan) { struct fmgr_info *procedure; SCAN_CHECKS; GET_SCAN_PROCEDURE(ammarkpos); FC1(procedure, PTR_TO_D(scan)); }
/* ---------------- * index_markpos - mark a scan position * ---------------- */ void index_markpos(IndexScanDesc scan) { FmgrInfo *procedure; SCAN_CHECKS; GET_SCAN_PROCEDURE(ammarkpos); FunctionCall1(procedure, PointerGetDatum(scan)); }
/* ---------------- * index_restrpos - restore a scan position * * NOTE: this only restores the internal scan state of the index AM. * The current result tuple (scan->xs_ctup) doesn't change. See comments * for ExecRestrPos(). * ---------------- */ void index_restrpos(IndexScanDesc scan) { FmgrInfo *procedure; SCAN_CHECKS; GET_SCAN_PROCEDURE(amrestrpos); scan->kill_prior_tuple = false; /* for safety */ FunctionCall1(procedure, PointerGetDatum(scan)); }
/* ---------------- * index_markpos - mark a scan position * ---------------- */ void index_markpos(IndexScanDesc scan) { RegProcedure procedure; SCAN_CHECKS; GET_SCAN_PROCEDURE(markpos, ammarkpos); scan->unique_tuple_mark = scan->unique_tuple_pos; OidFunctionCall1(procedure, PointerGetDatum(scan)); }
/* ---------------- * index_restrpos - restore a scan position * * NOTE: this only restores the internal scan state of the index AM. * The current result tuple (scan->xs_ctup) doesn't change. See comments * for ExecRestrPos(). * * NOTE: in the presence of HOT chains, mark/restore only works correctly * if the scan's snapshot is MVCC-safe; that ensures that there's at most one * returnable tuple in each HOT chain, and so restoring the prior state at the * granularity of the index AM is sufficient. Since the only current user * of mark/restore functionality is nodeMergejoin.c, this effectively means * that merge-join plans only work for MVCC snapshots. This could be fixed * if necessary, but for now it seems unimportant. * ---------------- */ void index_restrpos(struct index_scan *scan) { struct fmgr_info *procedure; ASSERT(IS_MVCC_SNAP(scan->xs_snapshot)); SCAN_CHECKS; GET_SCAN_PROCEDURE(amrestrpos); scan->xs_next_hot = INVALID_ITEM_ID; scan->kill_prior_tuple = false; /* for safety */ FC1(procedure, PTR_TO_D(scan)); }
/* ---------------- * index_beginscan - start a scan of an index * * Note: heapRelation may be NULL if there is no intention of calling * index_getnext on this scan; index_getnext_indexitem will not use the * heapRelation link (nor the snapshot). However, the caller had better * be holding some kind of lock on the heap relation in any case, to ensure * no one deletes it (or the index) out from under us. * ---------------- */ IndexScanDesc index_beginscan(Relation heapRelation, Relation indexRelation, Snapshot snapshot, int nkeys, ScanKey key) { IndexScanDesc scan; RegProcedure procedure; RELATION_CHECKS; GET_REL_PROCEDURE(beginscan, ambeginscan); RelationIncrementReferenceCount(indexRelation); /* * Acquire AccessShareLock for the duration of the scan * * Note: we could get an SI inval message here and consequently have to * rebuild the relcache entry. The refcount increment above ensures * that we will rebuild it and not just flush it... */ LockRelation(indexRelation, AccessShareLock); /* * Tell the AM to open a scan. */ scan = (IndexScanDesc) DatumGetPointer(OidFunctionCall3(procedure, PointerGetDatum(indexRelation), Int32GetDatum(nkeys), PointerGetDatum(key))); /* * Save additional parameters into the scandesc. Everything else was * set up by RelationGetIndexScan. */ scan->heapRelation = heapRelation; scan->xs_snapshot = snapshot; /* * We want to look up the amgettuple procedure just once per scan, not * once per index_getnext call. So do it here and save the fmgr info * result in the scan descriptor. */ GET_SCAN_PROCEDURE(beginscan, amgettuple); fmgr_info(procedure, &scan->fn_getnext); return scan; }
/* ---------------- * index_restrpos - restore a scan position * * NOTE: this only restores the internal scan state of the index AM. * The current result tuple (scan->xs_ctup) doesn't change. See comments * for ExecRestrPos(). * * NOTE: in the presence of HOT chains, mark/restore only works correctly * if the scan's snapshot is MVCC-safe; that ensures that there's at most one * returnable tuple in each HOT chain, and so restoring the prior state at the * granularity of the index AM is sufficient. Since the only current user * of mark/restore functionality is nodeMergejoin.c, this effectively means * that merge-join plans only work for MVCC snapshots. This could be fixed * if necessary, but for now it seems unimportant. * ---------------- */ void index_restrpos(IndexScanDesc scan) { FmgrInfo *procedure; Assert(IsMVCCSnapshot(scan->xs_snapshot)); SCAN_CHECKS; GET_SCAN_PROCEDURE(amrestrpos); scan->xs_continue_hot = false; scan->kill_prior_tuple = false; /* for safety */ FunctionCall1(procedure, PointerGetDatum(scan)); }
/* ---------------- * index_restrpos - restore a scan position * ---------------- */ void index_restrpos(IndexScanDesc scan) { RegProcedure procedure; SCAN_CHECKS; GET_SCAN_PROCEDURE(restrpos, amrestrpos); scan->kill_prior_tuple = false; /* for safety */ /* * We do not reset got_tuple; so if the scan is actually being * short-circuited by index_getnext, the effective position * restoration is done by restoring unique_tuple_pos. */ scan->unique_tuple_pos = scan->unique_tuple_mark; OidFunctionCall1(procedure, PointerGetDatum(scan)); }
/* ---------------- * index_rescan - (re)start a scan of an index * * The caller may specify a new set of scankeys (but the number of keys * cannot change). To restart the scan without changing keys, pass NULL * for the key array. * * Note that this is also called when first starting an indexscan; * see RelationGetIndexScan. Keys *must* be passed in that case, * unless scan->numberOfKeys is zero. * ---------------- */ void index_rescan(IndexScanDesc scan, ScanKey key) { RegProcedure procedure; SCAN_CHECKS; GET_SCAN_PROCEDURE(rescan, amrescan); scan->kill_prior_tuple = false; /* for safety */ scan->keys_are_unique = false; /* may be set by index AM */ scan->got_tuple = false; scan->unique_tuple_pos = 0; scan->unique_tuple_mark = 0; OidFunctionCall2(procedure, PointerGetDatum(scan), PointerGetDatum(key)); pgstat_reset_index_scan(&scan->xs_pgstat_info); }
/* ---------------- * index_getnext_tid - get the next TID from a scan * * The result is the next TID satisfying the scan keys, * or NULL if no more matching tuples exist. * ---------------- */ ItemPointer index_getnext_tid(IndexScanDesc scan, ScanDirection direction) { FmgrInfo *procedure; bool found; SCAN_CHECKS; GET_SCAN_PROCEDURE(amgettuple); Assert(TransactionIdIsValid(RecentGlobalXmin)); /* * The AM's amgettuple proc finds the next index entry matching the scan * keys, and puts the TID into scan->xs_ctup.t_self. It should also set * scan->xs_recheck and possibly scan->xs_itup, though we pay no attention * to those fields 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, we're done */ if (!found) { /* ... but first, release any held pin on a heap page */ if (BufferIsValid(scan->xs_cbuf)) { ReleaseBuffer(scan->xs_cbuf); scan->xs_cbuf = InvalidBuffer; } return NULL; } pgstat_count_index_tuples(scan->indexRelation, 1); /* Return the TID of the tuple we found. */ return &scan->xs_ctup.t_self; }
/* ---------------- * index_rescan - (re)start a scan of an index * * The caller may specify a new set of scankeys (but the number of keys * cannot change). To restart the scan without changing keys, pass NULL * for the key array. * * Note that this is also called when first starting an indexscan; * see RelationGetIndexScan. Keys *must* be passed in that case, * unless scan->numberOfKeys is zero. * ---------------- */ void index_rescan(IndexScanDesc scan, ScanKey key) { FmgrInfo *procedure; SCAN_CHECKS; GET_SCAN_PROCEDURE(amrescan); /* Release any held pin on a heap page */ if (BufferIsValid(scan->xs_cbuf)) { ReleaseBuffer(scan->xs_cbuf); scan->xs_cbuf = InvalidBuffer; } scan->kill_prior_tuple = false; /* for safety */ FunctionCall2(procedure, PointerGetDatum(scan), PointerGetDatum(key)); }
/* ---------------- * index_getbitmap - get all tuples at once from an index scan * * it invokes am's getmulti function to get a bitmap. If am is an on-disk * bitmap index access method (see bitmap.h), then a StreamBitmap is * returned; a TIDBitmap otherwise. Note that an index am's getmulti * function can assume that the bitmap that it's given as argument is of * the same type as what the function constructs itself. * ---------------- */ Node * index_getbitmap(IndexScanDesc scan, Node *bitmap) { FmgrInfo *procedure; Node *bm; SCAN_CHECKS; GET_SCAN_PROCEDURE(amgetbitmap); /* just make sure this is false... */ scan->kill_prior_tuple = false; /* * have the am's getbitmap proc do all the work. */ bm = (Node *) DatumGetPointer(FunctionCall2(procedure, PointerGetDatum(scan), PointerGetDatum(bitmap))); return bm; }
/* ---------------- * index_endscan - end a scan * ---------------- */ void index_endscan(struct index_scan *scan) { struct fmgr_info *procedure; SCAN_CHECKS; GET_SCAN_PROCEDURE(amendscan); /* Release any held pin on a heap page */ if (BUF_VALID(scan->xs_cbuf)) { release_buf(scan->xs_cbuf); scan->xs_cbuf = INVALID_BUF; } /* End the AM's scan */ FC1(procedure, PTR_TO_D(scan)); /* Release index refcount acquired by index_beginscan */ rel_decr_refcnt(scan->indexRelation); /* Release the scan data structure itself */ index_scan_end(scan); }
/* ---------------- * index_getnext_indexitem - get the next index tuple from a scan * * Finds the next index tuple satisfying the scan keys. Note that the * corresponding heap tuple is not accessed, and thus no time qual (snapshot) * check is done, other than the index AM's internal check for killed tuples * (which most callers of this routine will probably want to suppress by * setting scan->ignore_killed_tuples = false). * * On success (TRUE return), the heap TID of the found index entry is in * scan->xs_ctup.t_self. scan->xs_cbuf is untouched. * ---------------- */ bool index_getnext_indexitem(IndexScanDesc scan, ScanDirection direction) { FmgrInfo *procedure; bool found; SCAN_CHECKS; GET_SCAN_PROCEDURE(amgettuple); /* just make sure this is false... */ scan->kill_prior_tuple = false; /* * have the am's gettuple proc do all the work. */ found = DatumGetBool(FunctionCall2(procedure, PointerGetDatum(scan), Int32GetDatum(direction))); pgstat_count_index_tuples(scan->indexRelation, 1 /*ntids*/); return found; }
/* ---------------- * 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); 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. We do not do this * when in recovery because it may violate MVCC to do so. see * comments in RelationGetIndexScan(). */ if (!scan->xactStartedInRecovery) 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; bool valid; /* 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 */ valid = HeapTupleSatisfiesVisibility(heapTuple, scan->xs_snapshot, scan->xs_cbuf); CheckForSerializableConflictOut(valid, scan->heapRelation, heapTuple, scan->xs_cbuf); if (valid) { /* * 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) && !IsolationIsSerializable()) 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; PredicateLockTuple(scan->heapRelation, heapTuple); 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 */ }
/* ---------------- * 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; bool all_dead = false; SCAN_CHECKS; GET_SCAN_PROCEDURE(amgettuple); Assert(TransactionIdIsValid(RecentGlobalXmin)); for (;;) { bool got_heap_tuple; if (scan->xs_continue_hot) { /* * 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)); } 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. We do not do this * when in recovery because it may violate MVCC to do so. see * comments in RelationGetIndexScan(). */ if (!scan->xactStartedInRecovery) scan->kill_prior_tuple = all_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); } /* Obtain share-lock on the buffer so we can examine visibility */ LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE); got_heap_tuple = heap_hot_search_buffer(tid, scan->heapRelation, scan->xs_cbuf, scan->xs_snapshot, &scan->xs_ctup, &all_dead, !scan->xs_continue_hot); LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK); if (got_heap_tuple) { /* * Only in a non-MVCC snapshot can more than one member of the * HOT chain be visible. */ scan->xs_continue_hot = !IsMVCCSnapshot(scan->xs_snapshot); pgstat_count_heap_fetch(scan->indexRelation); return heapTuple; } /* Loop around to ask index AM for another TID */ scan->xs_continue_hot = false; } /* 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 */ }
/* ---------------- * 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). * ---------------- */ HeapTuple index_getnext(IndexScanDesc scan, ScanDirection direction) { MIRROREDLOCK_BUFMGR_DECLARE; HeapTuple heapTuple = &scan->xs_ctup; FmgrInfo *procedure; SCAN_CHECKS; GET_SCAN_PROCEDURE(amgettuple); /* just make sure this is false... */ scan->kill_prior_tuple = false; for (;;) { bool found; /* * The AM's gettuple proc finds the next tuple matching the scan keys. */ found = DatumGetBool(FunctionCall2(procedure, PointerGetDatum(scan), Int32GetDatum(direction))); /* Reset kill flag immediately for safety */ scan->kill_prior_tuple = false; if (!found) { /* 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 */ } pgstat_count_index_tuples(scan->indexRelation, 1); /* * Fetch the heap tuple and see if it matches the snapshot. */ if (heap_release_fetch(scan->heapRelation, scan->xs_snapshot, heapTuple, &scan->xs_cbuf, true, scan->indexRelation)) break; /* Skip if no undeleted tuple at this location */ if (heapTuple->t_data == NULL) continue; /* * If we can't see it, maybe no one else can either. Check to see if * the tuple is dead to all transactions. If so, signal the index AM * to not return it on future indexscans. * * We told heap_release_fetch to keep a pin on the buffer, so we can * re-access the tuple here. But we must re-lock the buffer first. */ // -------- MirroredLock ---------- MIRROREDLOCK_BUFMGR_LOCK; LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE); if (HeapTupleSatisfiesVacuum(heapTuple->t_data, RecentGlobalXmin, scan->xs_cbuf, true) == HEAPTUPLE_DEAD) scan->kill_prior_tuple = true; LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK); MIRROREDLOCK_BUFMGR_UNLOCK; // -------- MirroredLock ---------- } /* Success exit */ return heapTuple; }
/* ---------------- * 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. * ---------------- */ struct heap_tuple *index_getnext(struct index_scan *scan, enum scandir direction) { struct heap_tuple *heapTuple = &scan->xs_ctup; struct item_ptr *tid = &heapTuple->t_self; struct fmgr_info *procedure; SCAN_CHECKS; GET_SCAN_PROCEDURE(amgettuple); ASSERT(XID_VALID(recent_global_xmin)); /* * 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 (;;) { item_id_t offnum; bool at_chain_start; page_p dp; if (scan->xs_next_hot != INVALID_ITEM_ID) { /* * We are resuming scan of a HOT chain after having i * returned an earlier member. Must still hold pin on * current heap page. */ ASSERT(BUF_VALID(scan->xs_cbuf)); ASSERT(ITEM_PTR_BLK(tid) == buf_block_nr(scan->xs_cbuf)); ASSERT(XID_VALID(scan->xs_prev_xmax)); offnum = scan->xs_next_hot; at_chain_start = false; scan->xs_next_hot = INVALID_ITEM_ID; } else { bool found; buf_id_t prev_buf; /* * If we scanned a whole HOT chain and found only dead tuples, * tell index AM to kill its entry for that TID. We do not do this * when in recovery because it may violate MVCC to do so. see * comments in rel_get_index_scan(). */ if (!scan->xact_started_in_reco) 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 = D_TO_BOOL(FC2(procedure, PTR_TO_D(scan), INT32_TO_D(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; stat_index_tuples(scan->indexRelation, 1); /* Switch to correct buffer if we don't have it already */ prev_buf = scan->xs_cbuf; scan->xs_cbuf = release_read_buf( scan->xs_cbuf, scan->heapRelation, ITEM_PTR_BLK(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, recent_global_xmin); /* Prepare to scan HOT chain starting at index-referenced offnum */ offnum = ITEM_PTR_OFFSET(tid); at_chain_start = true; /* We don't know what the first tuple's xmin should be */ scan->xs_prev_xmax = INVALID_XID; /* 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 */ lock_buf(scan->xs_cbuf, BUF_LOCK_SHARE); dp = (page_p) BUF_PAGE(scan->xs_cbuf); /* scan_pl through possible multiple members of HOT-chain */ for (;;) { struct item_id *lp; struct item_ptr *ctid; bool valid; /* check for bogus TID */ if (offnum < FIRST_ITEM_ID || offnum > PAGE_MAX_ITEM_ID(dp)) break; lp = PAGE_ITEM_ID(dp, offnum); /* check for unused, dead, or redirected items */ if (!ITEMID_NORMAL(lp)) { /* We should only see a redirect at start of chain */ if (ITEMID_REDIRECTED(lp) && at_chain_start) { /* Follow the redirect */ offnum = ITEMID_REDIRECT(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 = (struct htup_header *)PAGE_GET_ITEM(dp, lp); heapTuple->t_len = ITEMID_LENGTH(lp); ITEM_PTR_SET_OFFSET(tid, offnum); heapTuple->t_tableOid = REL_ID(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 && HT_IS_HEAP_ONLY(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 (XID_VALID(scan->xs_prev_xmax) && !XID_EQ(scan->xs_prev_xmax, HTH_GET_XMIN(heapTuple->t_data))) break; /* If it's visible per the snapshot, we must return it */ valid = HEAPTUP_VISIBILITY(heapTuple, scan->xs_snapshot, scan->xs_cbuf); check_ser_conflict_out( valid, scan->heapRelation, heapTuple, scan->xs_cbuf, scan->xs_snapshot); if (valid) { /* * 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 (IS_MVCC_SNAP(scan->xs_snapshot)) { scan->xs_next_hot = INVALID_ITEM_ID; } else if (HT_IS_HOT_UPDATED(heapTuple)) { ASSERT(ITEM_PTR_BLK(ctid) == ITEM_PTR_BLK(tid)); scan->xs_next_hot = ITEM_PTR_OFFSET(ctid); scan->xs_prev_xmax = HTH_GET_XMAX(heapTuple->t_data); } else { scan->xs_next_hot = INVALID_ITEM_ID; } predlock_tuple( scan->heapRelation, heapTuple, scan->xs_snapshot); lock_buf(scan->xs_cbuf, BUF_LOCK_UNLOCK); stat_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 && ht_satisfy_vacuum( heapTuple->t_data, recent_global_xmin, scan->xs_cbuf) != HTV_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 (HT_IS_HOT_UPDATED(heapTuple)) { ASSERT(ITEM_PTR_BLK(ctid) == ITEM_PTR_BLK(tid)); offnum = ITEM_PTR_OFFSET(ctid); at_chain_start = false; scan->xs_prev_xmax = HTH_GET_XMAX(heapTuple->t_data); } else { break; /* end of chain */ } } /* loop over a single HOT chain */ lock_buf(scan->xs_cbuf, BUF_LOCK_UNLOCK); /* Loop around to ask index AM for another TID */ scan->xs_next_hot = INVALID_ITEM_ID; } /* Release any held pin on a heap page */ if (BUF_VALID(scan->xs_cbuf)) { release_buf(scan->xs_cbuf); scan->xs_cbuf = INVALID_BUF; } return NULL; /* failure exit */ }