1
0
mirror of https://github.com/postgres/postgres.git synced 2026-10-23 13:29:09 +03:00

Fix incorrect block accounting in TID Range Scans

heapgettup_advance_block() (or heapgettup() and heapgettup_pagemode() in
versions before v16) would incorrectly decrement rs_numblocks when
advancing to the next block.  Ordinarily this was fine, but if the TID
Range scan plan was used by a scrollable cursor and the cursor was fetched
backwards enough to result in another block being required, then
rs_numblocks may have reached 0 prematurely resulting in the scan ending
prematurely.

Reported-by: Jeff Davis <pgsql@j-davis.com>
Author: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/6b50233fcc0f7d6f4845f083777fa9702d9612e4.camel@j-davis.com
Backpatch-through: 14
This commit is contained in:
David Rowley
2026-09-24 01:21:45 +12:00
parent 851a5869cd
commit c84dd31bd9
3 changed files with 78 additions and 8 deletions

View File

@@ -767,8 +767,14 @@ heapgettup(HeapScanDesc scan,
*/
if (backward)
{
finished = (page == scan->rs_startblock) ||
(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
/*
* We're done if the last block is the start position. No need to
* check if rs_numblocks was set by heap_setscanlimits() as that
* only changes the end block. The start block is the same with
* or without scan limits.
*/
finished = (page == scan->rs_startblock);
if (page == 0)
page = scan->rs_nblocks;
page--;
@@ -789,8 +795,19 @@ heapgettup(HeapScanDesc scan,
page++;
if (page >= scan->rs_nblocks)
page = 0;
finished = (page == scan->rs_startblock) ||
(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
if (page == scan->rs_startblock)
finished = true;
else if (scan->rs_numblocks != InvalidBlockNumber)
{
BlockNumber endblock;
endblock = (scan->rs_startblock + scan->rs_numblocks) %
scan->rs_nblocks;
finished = (page == endblock);
}
else
finished = false;
/*
* Report our new scan position for synchronization purposes. We
@@ -1076,8 +1093,13 @@ heapgettup_pagemode(HeapScanDesc scan,
*/
if (backward)
{
finished = (page == scan->rs_startblock) ||
(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
/*
* We're done if the last block is the start position. No need to
* check if rs_numblocks was set by heap_setscanlimits() as that
* only changes the end block. The start block is the same with
* or without scan limits.
*/
finished = (page == scan->rs_startblock);
if (page == 0)
page = scan->rs_nblocks;
page--;
@@ -1098,8 +1120,18 @@ heapgettup_pagemode(HeapScanDesc scan,
page++;
if (page >= scan->rs_nblocks)
page = 0;
finished = (page == scan->rs_startblock) ||
(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
if (page == scan->rs_startblock)
finished = true;
else if (scan->rs_numblocks != InvalidBlockNumber)
{
BlockNumber endblock;
endblock = (scan->rs_startblock + scan->rs_numblocks) %
scan->rs_nblocks;
finished = (page == endblock);
}
else
finished = false;
/*
* Report our new scan position for synchronization purposes. We

View File

@@ -295,6 +295,32 @@ FETCH LAST c;
(0,10)
(1 row)
COMMIT;
-- Ensure TID Range Scans with scrollable cursors count blocks correctly.
BEGIN;
DECLARE c SCROLL CURSOR FOR SELECT ctid FROM tidrangescan WHERE ctid >= '(0,1)' AND ctid <= '(2,10)';
MOVE FORWARD 25 c;
MOVE BACKWARD 4 c;
FETCH BACKWARD 1 c;
ctid
--------
(1,10)
(1 row)
MOVE BACKWARD ALL c;
FETCH FORWARD 1 c;
ctid
-------
(0,1)
(1 row)
MOVE FORWARD ALL c;
FETCH BACKWARD 1 c;
ctid
--------
(2,10)
(1 row)
COMMIT;
DROP TABLE tidrangescan;
RESET enable_seqscan;

View File

@@ -96,6 +96,18 @@ FETCH FIRST c;
FETCH LAST c;
COMMIT;
-- Ensure TID Range Scans with scrollable cursors count blocks correctly.
BEGIN;
DECLARE c SCROLL CURSOR FOR SELECT ctid FROM tidrangescan WHERE ctid >= '(0,1)' AND ctid <= '(2,10)';
MOVE FORWARD 25 c;
MOVE BACKWARD 4 c;
FETCH BACKWARD 1 c;
MOVE BACKWARD ALL c;
FETCH FORWARD 1 c;
MOVE FORWARD ALL c;
FETCH BACKWARD 1 c;
COMMIT;
DROP TABLE tidrangescan;
RESET enable_seqscan;