Skip to content

Commit e607268

Browse files
author
Commitfest Bot
committed
[CF 5448] v6 - Incorrect result of bitmap heap scan.
This branch was automatically generated by a robot using patches from an email thread registered at: https://commitfest.postgresql.org/patch/5448 The branch will be overwritten each time a new patch version is posted to the thread, and also periodically to check for bitrot caused by changes on the master branch. Patch(es): https://www.postgresql.org/message-id/CAEze2Wg3gXXZTr6_rwC+s4-o2ZVFB5F985uUSgJTsECx6AmGcQ@mail.gmail.com Author(s): Matthias van de Meent
2 parents ea3f9b6 + a9276a4 commit e607268

File tree

8 files changed

+127
-132
lines changed

8 files changed

+127
-132
lines changed

src/backend/access/heap/heapam.c

+8-54
Original file line numberDiff line numberDiff line change
@@ -314,31 +314,6 @@ bitmapheap_stream_read_next(ReadStream *pgsr, void *private_data,
314314
tbmres->blockno >= hscan->rs_nblocks)
315315
continue;
316316

317-
/*
318-
* We can skip fetching the heap page if we don't need any fields from
319-
* the heap, the bitmap entries don't need rechecking, and all tuples
320-
* on the page are visible to our transaction.
321-
*/
322-
if (!(sscan->rs_flags & SO_NEED_TUPLES) &&
323-
!tbmres->recheck &&
324-
VM_ALL_VISIBLE(sscan->rs_rd, tbmres->blockno, &bscan->rs_vmbuffer))
325-
{
326-
OffsetNumber offsets[TBM_MAX_TUPLES_PER_PAGE];
327-
int noffsets;
328-
329-
/* can't be lossy in the skip_fetch case */
330-
Assert(!tbmres->lossy);
331-
Assert(bscan->rs_empty_tuples_pending >= 0);
332-
333-
/*
334-
* We throw away the offsets, but this is the easiest way to get a
335-
* count of tuples.
336-
*/
337-
noffsets = tbm_extract_page_tuple(tbmres, offsets, TBM_MAX_TUPLES_PER_PAGE);
338-
bscan->rs_empty_tuples_pending += noffsets;
339-
continue;
340-
}
341-
342317
return tbmres->blockno;
343318
}
344319

@@ -1123,9 +1098,10 @@ heap_beginscan(Relation relation, Snapshot snapshot,
11231098
if (flags & SO_TYPE_BITMAPSCAN)
11241099
{
11251100
BitmapHeapScanDesc bscan = palloc(sizeof(BitmapHeapScanDescData));
1126-
1127-
bscan->rs_vmbuffer = InvalidBuffer;
1128-
bscan->rs_empty_tuples_pending = 0;
1101+
/*
1102+
* Bitmap Heap scans do not have any new fields that a normal Heap
1103+
* Scan does not have, so no special initializations required here.
1104+
*/
11291105
scan = (HeapScanDesc) bscan;
11301106
}
11311107
else
@@ -1280,23 +1256,10 @@ heap_rescan(TableScanDesc sscan, ScanKey key, bool set_params,
12801256
scan->rs_cbuf = InvalidBuffer;
12811257
}
12821258

1283-
if (scan->rs_base.rs_flags & SO_TYPE_BITMAPSCAN)
1284-
{
1285-
BitmapHeapScanDesc bscan = (BitmapHeapScanDesc) scan;
1286-
1287-
/*
1288-
* Reset empty_tuples_pending, a field only used by bitmap heap scan,
1289-
* to avoid incorrectly emitting NULL-filled tuples from a previous
1290-
* scan on rescan.
1291-
*/
1292-
bscan->rs_empty_tuples_pending = 0;
1293-
1294-
if (BufferIsValid(bscan->rs_vmbuffer))
1295-
{
1296-
ReleaseBuffer(bscan->rs_vmbuffer);
1297-
bscan->rs_vmbuffer = InvalidBuffer;
1298-
}
1299-
}
1259+
/*
1260+
* SO_TYPE_BITMAPSCAN would be cleaned up here, but it does not hold any
1261+
* additional data vs a normal HeapScan
1262+
*/
13001263

13011264
/*
13021265
* The read stream is reset on rescan. This must be done before
@@ -1325,15 +1288,6 @@ heap_endscan(TableScanDesc sscan)
13251288
if (BufferIsValid(scan->rs_cbuf))
13261289
ReleaseBuffer(scan->rs_cbuf);
13271290

1328-
if (scan->rs_base.rs_flags & SO_TYPE_BITMAPSCAN)
1329-
{
1330-
BitmapHeapScanDesc bscan = (BitmapHeapScanDesc) sscan;
1331-
1332-
bscan->rs_empty_tuples_pending = 0;
1333-
if (BufferIsValid(bscan->rs_vmbuffer))
1334-
ReleaseBuffer(bscan->rs_vmbuffer);
1335-
}
1336-
13371291
/*
13381292
* Must free the read stream before freeing the BufferAccessStrategy.
13391293
*/

src/backend/access/heap/heapam_handler.c

+2-42
Original file line numberDiff line numberDiff line change
@@ -2137,32 +2137,6 @@ heapam_scan_bitmap_next_tuple(TableScanDesc scan,
21372137
*/
21382138
while (hscan->rs_cindex >= hscan->rs_ntuples)
21392139
{
2140-
/*
2141-
* Emit empty tuples before advancing to the next block
2142-
*/
2143-
if (bscan->rs_empty_tuples_pending > 0)
2144-
{
2145-
/*
2146-
* If we don't have to fetch the tuple, just return nulls.
2147-
*/
2148-
ExecStoreAllNullTuple(slot);
2149-
bscan->rs_empty_tuples_pending--;
2150-
2151-
/*
2152-
* We do not recheck all NULL tuples. Because the streaming read
2153-
* API only yields TBMIterateResults for blocks actually fetched
2154-
* from the heap, we must unset `recheck` ourselves here to ensure
2155-
* correct results.
2156-
*
2157-
* Our read stream callback accrues a count of empty tuples to
2158-
* emit and then emits them after emitting tuples from the next
2159-
* fetched block. If no blocks need fetching, we'll emit the
2160-
* accrued count at the end of the scan.
2161-
*/
2162-
*recheck = false;
2163-
return true;
2164-
}
2165-
21662140
/*
21672141
* Returns false if the bitmap is exhausted and there are no further
21682142
* blocks we need to scan.
@@ -2516,24 +2490,10 @@ BitmapHeapScanNextBlock(TableScanDesc scan,
25162490

25172491
if (BufferIsInvalid(hscan->rs_cbuf))
25182492
{
2519-
if (BufferIsValid(bscan->rs_vmbuffer))
2520-
{
2521-
ReleaseBuffer(bscan->rs_vmbuffer);
2522-
bscan->rs_vmbuffer = InvalidBuffer;
2523-
}
2524-
25252493
/*
2526-
* The bitmap is exhausted. Now emit any remaining empty tuples. The
2527-
* read stream API only returns TBMIterateResults for blocks actually
2528-
* fetched from the heap. Our callback will accrue a count of empty
2529-
* tuples to emit for all blocks we skipped fetching. So, if we skip
2530-
* fetching heap blocks at the end of the relation (or no heap blocks
2531-
* are fetched) we need to ensure we emit empty tuples before ending
2532-
* the scan. We don't recheck empty tuples so ensure `recheck` is
2533-
* unset.
2494+
* The bitmap is exhausted.
25342495
*/
2535-
*recheck = false;
2536-
return bscan->rs_empty_tuples_pending > 0;
2496+
return false;
25372497
}
25382498

25392499
Assert(per_buffer_data);

src/backend/executor/nodeBitmapHeapscan.c

+1-14
Original file line numberDiff line numberDiff line change
@@ -105,24 +105,11 @@ BitmapTableScanSetup(BitmapHeapScanState *node)
105105
*/
106106
if (!node->ss.ss_currentScanDesc)
107107
{
108-
bool need_tuples = false;
109-
110-
/*
111-
* We can potentially skip fetching heap pages if we do not need any
112-
* columns of the table, either for checking non-indexable quals or
113-
* for returning data. This test is a bit simplistic, as it checks
114-
* the stronger condition that there's no qual or return tlist at all.
115-
* But in most cases it's probably not worth working harder than that.
116-
*/
117-
need_tuples = (node->ss.ps.plan->qual != NIL ||
118-
node->ss.ps.plan->targetlist != NIL);
119-
120108
node->ss.ss_currentScanDesc =
121109
table_beginscan_bm(node->ss.ss_currentRelation,
122110
node->ss.ps.state->es_snapshot,
123111
0,
124-
NULL,
125-
need_tuples);
112+
NULL);
126113
}
127114

128115
node->ss.ss_currentScanDesc->st.rs_tbmiterator = tbmiterator;

src/include/access/heapam.h

+1-11
Original file line numberDiff line numberDiff line change
@@ -103,17 +103,7 @@ typedef struct BitmapHeapScanDescData
103103
{
104104
HeapScanDescData rs_heap_base;
105105

106-
/*
107-
* These fields are only used for bitmap scans for the "skip fetch"
108-
* optimization. Bitmap scans needing no fields from the heap may skip
109-
* fetching an all visible block, instead using the number of tuples per
110-
* block reported by the bitmap to determine how many NULL-filled tuples
111-
* to return. They are common to parallel and serial BitmapHeapScans
112-
*/
113-
114-
/* page of VM containing info for current block */
115-
Buffer rs_vmbuffer;
116-
int rs_empty_tuples_pending;
106+
/* Holds no data */
117107
} BitmapHeapScanDescData;
118108
typedef struct BitmapHeapScanDescData *BitmapHeapScanDesc;
119109

src/include/access/tableam.h

+1-11
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,6 @@ typedef enum ScanOptions
6262

6363
/* unregister snapshot at scan end? */
6464
SO_TEMP_SNAPSHOT = 1 << 9,
65-
66-
/*
67-
* At the discretion of the table AM, bitmap table scans may be able to
68-
* skip fetching a block from the table if none of the table data is
69-
* needed. If table data may be needed, set SO_NEED_TUPLES.
70-
*/
71-
SO_NEED_TUPLES = 1 << 10,
7265
} ScanOptions;
7366

7467
/*
@@ -920,13 +913,10 @@ table_beginscan_strat(Relation rel, Snapshot snapshot,
920913
*/
921914
static inline TableScanDesc
922915
table_beginscan_bm(Relation rel, Snapshot snapshot,
923-
int nkeys, struct ScanKeyData *key, bool need_tuple)
916+
int nkeys, struct ScanKeyData *key)
924917
{
925918
uint32 flags = SO_TYPE_BITMAPSCAN | SO_ALLOW_PAGEMODE;
926919

927-
if (need_tuple)
928-
flags |= SO_NEED_TUPLES;
929-
930920
return rel->rd_tableam->scan_begin(rel, snapshot, nkeys, key,
931921
NULL, flags);
932922
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
Parsed test spec with 2 sessions
2+
3+
starting permutation: s2_vacuum s2_mod s1_begin s1_prepare s1_fetch_1 s2_vacuum s1_fetch_all s1_commit
4+
step s2_vacuum: VACUUM (TRUNCATE false) ios_bitmap;
5+
step s2_mod:
6+
DELETE FROM ios_bitmap WHERE a > 1;
7+
8+
step s1_begin: BEGIN;
9+
step s1_prepare:
10+
DECLARE foo NO SCROLL CURSOR FOR SELECT row_number() OVER () FROM ios_bitmap WHERE a > 0 or b > 0;
11+
12+
step s1_fetch_1:
13+
FETCH FROM foo;
14+
15+
row_number
16+
----------
17+
1
18+
(1 row)
19+
20+
step s2_vacuum: VACUUM (TRUNCATE false) ios_bitmap;
21+
step s1_fetch_all:
22+
FETCH ALL FROM foo;
23+
24+
row_number
25+
----------
26+
(0 rows)
27+
28+
step s1_commit: COMMIT;

src/test/isolation/isolation_schedule

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ test: partial-index
1717
test: two-ids
1818
test: multiple-row-versions
1919
test: index-only-scan
20+
test: index-only-bitmapscan
2021
test: predicate-lock-hot-tuple
2122
test: update-conflict-out
2223
test: deadlock-simple
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# index-only-bitmapscan test showing wrong results
2+
#
3+
setup
4+
{
5+
-- by using a low fillfactor and a wide tuple we can get multiple blocks
6+
-- with just few rows
7+
CREATE TABLE ios_bitmap (a int NOT NULL, b int not null, pad char(1024) default '')
8+
WITH (AUTOVACUUM_ENABLED = false, FILLFACTOR = 10);
9+
10+
INSERT INTO ios_bitmap SELECT g.i, g.i FROM generate_series(1, 10) g(i);
11+
12+
CREATE INDEX ios_bitmap_a ON ios_bitmap(a);
13+
CREATE INDEX ios_bitmap_b ON ios_bitmap(b);
14+
}
15+
16+
teardown
17+
{
18+
DROP TABLE ios_bitmap;
19+
}
20+
21+
22+
session s1
23+
24+
setup {
25+
SET enable_seqscan = false;
26+
}
27+
28+
step s1_begin { BEGIN; }
29+
step s1_commit { COMMIT; }
30+
31+
32+
# The test query uses an or between two indexes to ensure make it more likely
33+
# to use a bitmap index scan
34+
#
35+
# The row_number() hack is a way to have something returned (isolationtester
36+
# doesn't display empty rows) while still allowing for the index-only scan
37+
# optimization in bitmap heap scans, which requires an empty targetlist.
38+
step s1_prepare {
39+
DECLARE foo NO SCROLL CURSOR FOR SELECT row_number() OVER () FROM ios_bitmap WHERE a > 0 or b > 0;
40+
}
41+
42+
step s1_fetch_1 {
43+
FETCH FROM foo;
44+
}
45+
46+
step s1_fetch_all {
47+
FETCH ALL FROM foo;
48+
}
49+
50+
51+
session s2
52+
53+
# Don't delete row 1 so we have a row for the cursor to "rest" on.
54+
step s2_mod
55+
{
56+
DELETE FROM ios_bitmap WHERE a > 1;
57+
}
58+
59+
# Disable truncation, as otherwise we'll just wait for a timeout while trying
60+
# to acquire the lock
61+
step s2_vacuum { VACUUM (TRUNCATE false) ios_bitmap; }
62+
63+
permutation
64+
# Vacuum first, to ensure VM exists, otherwise the bitmapscan will consider
65+
# VM to be size 0, due to caching. Can't do that in setup because
66+
s2_vacuum
67+
68+
# delete nearly all rows, to make issue visible
69+
s2_mod
70+
# create a cursor
71+
s1_begin
72+
s1_prepare
73+
74+
# fetch one row from the cursor, that ensures the index scan portion is done
75+
# before the vacuum in the next step
76+
s1_fetch_1
77+
78+
# with the bug this vacuum will mark pages as all-visible that the scan in
79+
# the next step then considers all-visible, despite all rows from those
80+
# pages having been removed.
81+
s2_vacuum
82+
# if this returns any rows, we're busted
83+
s1_fetch_all
84+
85+
s1_commit

0 commit comments

Comments
 (0)