Skip to content

Commit dada970

Browse files
dvcerCommitfest Bot
authored andcommitted
amcheck: brin_index_check() - heap all indexed
This commit extends functionality of brin_index_check() with heap_all_indexed check: we validate every index range tuple against every heap tuple within the range using consistentFn. Also, we check here that fields 'has_nulls', 'all_nulls' and 'empty_range' are consistent with the range heap data. It's the most expensive part of the brin_index_check(), so it's optional.
1 parent dda830d commit dada970

File tree

6 files changed

+685
-26
lines changed

6 files changed

+685
-26
lines changed

contrib/amcheck/amcheck--1.5--1.6.sql

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88
-- brin_index_check()
99
--
1010
CREATE FUNCTION brin_index_check(index regclass,
11-
regularpagescheck boolean default false
11+
regularpagescheck boolean default false,
12+
heapallindexed boolean default false,
13+
variadic text[] default '{}'
1214
)
1315
RETURNS VOID
1416
AS 'MODULE_PATHNAME', 'brin_index_check'
1517
LANGUAGE C STRICT PARALLEL RESTRICTED;
1618

1719
-- We don't want this to be available to public
18-
REVOKE ALL ON FUNCTION brin_index_check(regclass, boolean) FROM PUBLIC;
20+
REVOKE ALL ON FUNCTION brin_index_check(regclass, boolean, boolean, text[]) FROM PUBLIC;

contrib/amcheck/expected/check_brin.out

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ $$ LANGUAGE sql;
55
-- empty table index should be valid
66
CREATE TABLE brintest (a bigint) WITH (fillfactor = 10);
77
CREATE INDEX brintest_idx ON brintest USING brin (a);
8-
SELECT brin_index_check('brintest_idx', true);
8+
SELECT brin_index_check('brintest_idx', true, true);
99
brin_index_check
1010
------------------
1111

@@ -19,7 +19,7 @@ CREATE INDEX brintest_idx ON brintest USING brin (a int8_minmax_ops) WITH (pages
1919
INSERT INTO brintest (a) SELECT x FROM generate_series(1,100000) x;
2020
-- create some empty ranges
2121
DELETE FROM brintest WHERE a > 20000 AND a < 40000;
22-
SELECT brin_index_check('brintest_idx', true);
22+
SELECT brin_index_check('brintest_idx', true, true);
2323
brin_index_check
2424
------------------
2525

@@ -28,7 +28,7 @@ SELECT brin_index_check('brintest_idx', true);
2828
-- rebuild index
2929
DROP INDEX brintest_idx;
3030
CREATE INDEX brintest_idx ON brintest USING brin (a int8_minmax_ops) WITH (pages_per_range = 2);
31-
SELECT brin_index_check('brintest_idx', true);
31+
SELECT brin_index_check('brintest_idx', true, true);
3232
brin_index_check
3333
------------------
3434

@@ -42,7 +42,7 @@ CREATE INDEX brintest_idx ON brintest USING brin (a int8_minmax_multi_ops) WITH
4242
INSERT INTO brintest (a) SELECT x FROM generate_series(1,100000) x;
4343
-- create some empty ranges
4444
DELETE FROM brintest WHERE a > 20000 AND a < 40000;
45-
SELECT brin_index_check('brintest_idx', true);
45+
SELECT brin_index_check('brintest_idx', true, true);
4646
brin_index_check
4747
------------------
4848

@@ -51,7 +51,7 @@ SELECT brin_index_check('brintest_idx', true);
5151
-- rebuild index
5252
DROP INDEX brintest_idx;
5353
CREATE INDEX brintest_idx ON brintest USING brin (a int8_minmax_multi_ops) WITH (pages_per_range = 2);
54-
SELECT brin_index_check('brintest_idx', true);
54+
SELECT brin_index_check('brintest_idx', true, true);
5555
brin_index_check
5656
------------------
5757

@@ -65,7 +65,7 @@ CREATE INDEX brintest_idx ON brintest USING brin (a int8_bloom_ops) WITH (pages_
6565
INSERT INTO brintest (a) SELECT x FROM generate_series(1,100000) x;
6666
-- create some empty ranges
6767
DELETE FROM brintest WHERE a > 20000 AND a < 40000;
68-
SELECT brin_index_check('brintest_idx', true);
68+
SELECT brin_index_check('brintest_idx', true, true);
6969
brin_index_check
7070
------------------
7171

@@ -74,7 +74,7 @@ SELECT brin_index_check('brintest_idx', true);
7474
-- rebuild index
7575
DROP INDEX brintest_idx;
7676
CREATE INDEX brintest_idx ON brintest USING brin (a int8_bloom_ops) WITH (pages_per_range = 2);
77-
SELECT brin_index_check('brintest_idx', true);
77+
SELECT brin_index_check('brintest_idx', true, true);
7878
brin_index_check
7979
------------------
8080

@@ -90,7 +90,7 @@ SELECT box(point(random() * 1000, random() * 1000), point(random() * 1000, rando
9090
FROM generate_series(1, 10000);
9191
-- create some empty ranges
9292
DELETE FROM brintest WHERE id > 2000 AND id < 4000;
93-
SELECT brin_index_check('brintest_idx', true);
93+
SELECT brin_index_check('brintest_idx', true, true, '@>');
9494
brin_index_check
9595
------------------
9696

@@ -99,7 +99,7 @@ SELECT brin_index_check('brintest_idx', true);
9999
-- rebuild index
100100
DROP INDEX brintest_idx;
101101
CREATE INDEX brintest_idx ON brintest USING brin (a box_inclusion_ops) WITH (pages_per_range = 2);
102-
SELECT brin_index_check('brintest_idx', true);
102+
SELECT brin_index_check('brintest_idx', true, true, '@>');
103103
brin_index_check
104104
------------------
105105

@@ -113,7 +113,7 @@ CREATE INDEX brintest_idx ON brintest USING brin (id int8_minmax_ops, a text_min
113113
INSERT INTO brintest (a) SELECT random_string((x % 100)) FROM generate_series(1,3000) x;
114114
-- create some empty ranges
115115
DELETE FROM brintest WHERE id > 1500 AND id < 2500;
116-
SELECT brin_index_check('brintest_idx', true);
116+
SELECT brin_index_check('brintest_idx', true, true);
117117
brin_index_check
118118
------------------
119119

@@ -122,12 +122,50 @@ SELECT brin_index_check('brintest_idx', true);
122122
-- rebuild index
123123
DROP INDEX brintest_idx;
124124
CREATE INDEX brintest_idx ON brintest USING brin (id int8_minmax_ops, a text_minmax_ops) WITH (pages_per_range = 2);
125-
SELECT brin_index_check('brintest_idx', true);
125+
SELECT brin_index_check('brintest_idx', true, true);
126126
brin_index_check
127127
------------------
128128

129129
(1 row)
130130

131+
-- cleanup
132+
DROP TABLE brintest;
133+
-- multiple attributes test with custom operators
134+
CREATE TABLE brintest (id bigserial, a text, b box) WITH (fillfactor = 10);
135+
CREATE INDEX brintest_idx ON brintest USING brin (id int8_minmax_ops, a text_minmax_ops, b box_inclusion_ops) WITH (pages_per_range = 2);
136+
INSERT INTO brintest (a, b) SELECT
137+
random_string((x % 100)),
138+
box(point(random() * 1000, random() * 1000), point(random() * 1000, random() * 1000))
139+
FROM generate_series(1, 3000) x;
140+
-- create some empty ranges
141+
DELETE FROM brintest WHERE id > 1500 AND id < 2500;
142+
SELECT brin_index_check('brintest_idx', true, true, '=', '=', '@>');
143+
brin_index_check
144+
------------------
145+
146+
(1 row)
147+
148+
-- rebuild index
149+
DROP INDEX brintest_idx;
150+
CREATE INDEX brintest_idx ON brintest USING brin (id int8_minmax_ops, a text_minmax_ops, b box_inclusion_ops) WITH (pages_per_range = 2);
151+
SELECT brin_index_check('brintest_idx', true, true, '=', '=', '@>');
152+
brin_index_check
153+
------------------
154+
155+
(1 row)
156+
157+
-- error if it's impossible to use default operator for all index attributes
158+
SELECT brin_index_check('brintest_idx', true, true);
159+
ERROR: Operator = is not a member of operator family "box_inclusion_ops"
160+
-- error if number of operators in input doesn't match index attributes number
161+
SELECT brin_index_check('brintest_idx', true, true, '=');
162+
ERROR: Number of operator names in input (1) doesn't match index attributes number (3)
163+
-- error if operator name is NULL
164+
SELECT brin_index_check('brintest_idx', true, true, '=', '=', NULL);
165+
ERROR: Operator name must not be NULL
166+
-- error if there is no operator for attribute type
167+
SELECT brin_index_check('brintest_idx', true, true, '=', '=', '@@');
168+
ERROR: There is no operator @@ for type "box"
131169
-- cleanup
132170
DROP TABLE brintest;
133171
-- cleanup

contrib/amcheck/sql/check_brin.sql

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ $$ LANGUAGE sql;
77
-- empty table index should be valid
88
CREATE TABLE brintest (a bigint) WITH (fillfactor = 10);
99
CREATE INDEX brintest_idx ON brintest USING brin (a);
10-
SELECT brin_index_check('brintest_idx', true);
10+
SELECT brin_index_check('brintest_idx', true, true);
1111
-- cleanup
1212
DROP TABLE brintest;
1313

@@ -17,12 +17,12 @@ CREATE INDEX brintest_idx ON brintest USING brin (a int8_minmax_ops) WITH (pages
1717
INSERT INTO brintest (a) SELECT x FROM generate_series(1,100000) x;
1818
-- create some empty ranges
1919
DELETE FROM brintest WHERE a > 20000 AND a < 40000;
20-
SELECT brin_index_check('brintest_idx', true);
20+
SELECT brin_index_check('brintest_idx', true, true);
2121

2222
-- rebuild index
2323
DROP INDEX brintest_idx;
2424
CREATE INDEX brintest_idx ON brintest USING brin (a int8_minmax_ops) WITH (pages_per_range = 2);
25-
SELECT brin_index_check('brintest_idx', true);
25+
SELECT brin_index_check('brintest_idx', true, true);
2626
-- cleanup
2727
DROP TABLE brintest;
2828

@@ -34,12 +34,12 @@ CREATE INDEX brintest_idx ON brintest USING brin (a int8_minmax_multi_ops) WITH
3434
INSERT INTO brintest (a) SELECT x FROM generate_series(1,100000) x;
3535
-- create some empty ranges
3636
DELETE FROM brintest WHERE a > 20000 AND a < 40000;
37-
SELECT brin_index_check('brintest_idx', true);
37+
SELECT brin_index_check('brintest_idx', true, true);
3838

3939
-- rebuild index
4040
DROP INDEX brintest_idx;
4141
CREATE INDEX brintest_idx ON brintest USING brin (a int8_minmax_multi_ops) WITH (pages_per_range = 2);
42-
SELECT brin_index_check('brintest_idx', true);
42+
SELECT brin_index_check('brintest_idx', true, true);
4343
-- cleanup
4444
DROP TABLE brintest;
4545

@@ -51,12 +51,12 @@ CREATE INDEX brintest_idx ON brintest USING brin (a int8_bloom_ops) WITH (pages_
5151
INSERT INTO brintest (a) SELECT x FROM generate_series(1,100000) x;
5252
-- create some empty ranges
5353
DELETE FROM brintest WHERE a > 20000 AND a < 40000;
54-
SELECT brin_index_check('brintest_idx', true);
54+
SELECT brin_index_check('brintest_idx', true, true);
5555

5656
-- rebuild index
5757
DROP INDEX brintest_idx;
5858
CREATE INDEX brintest_idx ON brintest USING brin (a int8_bloom_ops) WITH (pages_per_range = 2);
59-
SELECT brin_index_check('brintest_idx', true);
59+
SELECT brin_index_check('brintest_idx', true, true);
6060
-- cleanup
6161
DROP TABLE brintest;
6262

@@ -70,12 +70,12 @@ FROM generate_series(1, 10000);
7070
-- create some empty ranges
7171
DELETE FROM brintest WHERE id > 2000 AND id < 4000;
7272

73-
SELECT brin_index_check('brintest_idx', true);
73+
SELECT brin_index_check('brintest_idx', true, true, '@>');
7474

7575
-- rebuild index
7676
DROP INDEX brintest_idx;
7777
CREATE INDEX brintest_idx ON brintest USING brin (a box_inclusion_ops) WITH (pages_per_range = 2);
78-
SELECT brin_index_check('brintest_idx', true);
78+
SELECT brin_index_check('brintest_idx', true, true, '@>');
7979
-- cleanup
8080
DROP TABLE brintest;
8181

@@ -86,12 +86,44 @@ CREATE INDEX brintest_idx ON brintest USING brin (id int8_minmax_ops, a text_min
8686
INSERT INTO brintest (a) SELECT random_string((x % 100)) FROM generate_series(1,3000) x;
8787
-- create some empty ranges
8888
DELETE FROM brintest WHERE id > 1500 AND id < 2500;
89-
SELECT brin_index_check('brintest_idx', true);
89+
SELECT brin_index_check('brintest_idx', true, true);
9090

9191
-- rebuild index
9292
DROP INDEX brintest_idx;
9393
CREATE INDEX brintest_idx ON brintest USING brin (id int8_minmax_ops, a text_minmax_ops) WITH (pages_per_range = 2);
94-
SELECT brin_index_check('brintest_idx', true);
94+
SELECT brin_index_check('brintest_idx', true, true);
95+
-- cleanup
96+
DROP TABLE brintest;
97+
98+
99+
-- multiple attributes test with custom operators
100+
CREATE TABLE brintest (id bigserial, a text, b box) WITH (fillfactor = 10);
101+
CREATE INDEX brintest_idx ON brintest USING brin (id int8_minmax_ops, a text_minmax_ops, b box_inclusion_ops) WITH (pages_per_range = 2);
102+
INSERT INTO brintest (a, b) SELECT
103+
random_string((x % 100)),
104+
box(point(random() * 1000, random() * 1000), point(random() * 1000, random() * 1000))
105+
FROM generate_series(1, 3000) x;
106+
-- create some empty ranges
107+
DELETE FROM brintest WHERE id > 1500 AND id < 2500;
108+
SELECT brin_index_check('brintest_idx', true, true, '=', '=', '@>');
109+
110+
-- rebuild index
111+
DROP INDEX brintest_idx;
112+
CREATE INDEX brintest_idx ON brintest USING brin (id int8_minmax_ops, a text_minmax_ops, b box_inclusion_ops) WITH (pages_per_range = 2);
113+
SELECT brin_index_check('brintest_idx', true, true, '=', '=', '@>');
114+
115+
-- error if it's impossible to use default operator for all index attributes
116+
SELECT brin_index_check('brintest_idx', true, true);
117+
118+
-- error if number of operators in input doesn't match index attributes number
119+
SELECT brin_index_check('brintest_idx', true, true, '=');
120+
121+
-- error if operator name is NULL
122+
SELECT brin_index_check('brintest_idx', true, true, '=', '=', NULL);
123+
124+
-- error if there is no operator for attribute type
125+
SELECT brin_index_check('brintest_idx', true, true, '=', '=', '@@');
126+
95127
-- cleanup
96128
DROP TABLE brintest;
97129

contrib/amcheck/t/007_verify_brin.pl

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,55 @@
210210
return qq(INSERT INTO $test_struct->{table_name} (a) VALUES ('aaaaa'););
211211
},
212212
expected => wrap("revmap doesn't point to index tuple. Range blkno: 0, revmap item: (1,0), index tuple: (2,1)")
213+
},
214+
{
215+
# range is marked as empty_range, but heap has some data for the range
216+
217+
find => pack('LCC', 0, 0x88, 0x03),
218+
replace => pack('LCC', 0, 0xA8, 0x01),
219+
blkno => 2, # regular page
220+
table_data => sub {
221+
my ($test_struct) = @_;
222+
return qq(INSERT INTO $test_struct->{table_name} (a) VALUES (null););
223+
},
224+
expected => wrap('range is marked as empty but contains qualified live tuples. Range blkno: 0, heap tid (0,1)')
225+
},
226+
{
227+
# range hasnulls & allnulls are false, but heap contains null values for the range
228+
229+
find => pack('LCC', 0, 0x88, 0x02),
230+
replace => pack('LCC', 0, 0x88, 0x00),
231+
blkno => 2, # regular page
232+
table_data => sub {
233+
my ($test_struct) = @_;
234+
return qq(INSERT INTO $test_struct->{table_name} (a) VALUES (null), ('aaaaa'););
235+
},
236+
expected => wrap('range hasnulls and allnulls are false, but contains a null value. Range blkno: 0, heap tid (0,1)')
237+
},
238+
{
239+
# range allnulls is true, but heap contains non-null values for the range
240+
241+
find => pack('LCC', 0, 0x88, 0x02),
242+
replace => pack('LCC', 0, 0x88, 0x01),
243+
blkno => 2, # regular page
244+
table_data => sub {
245+
my ($test_struct) = @_;
246+
return qq(INSERT INTO $test_struct->{table_name} (a) VALUES (null), ('aaaaa'););
247+
},
248+
expected => wrap('range allnulls is true, but contains nonnull value. Range blkno: 0, heap tid (0,2)')
249+
},
250+
{
251+
# consistent function return FALSE for the valid heap value
252+
# replace "ccccc" with "bbbbb" so that min_max index was too narrow
253+
254+
find => 'ccccc',
255+
replace => 'bbbbb',
256+
blkno => 2, # regular page
257+
table_data => sub {
258+
my ($test_struct) = @_;
259+
return qq(INSERT INTO $test_struct->{table_name} (a) VALUES ('aaaaa'), ('ccccc'););
260+
},
261+
expected => wrap('heap tuple inconsistent with index. Range blkno: 0, heap tid (0,2)')
213262
}
214263
);
215264

@@ -251,7 +300,7 @@
251300
$node->start;
252301

253302
foreach my $test_struct (@tests) {
254-
my ($result, $stdout, $stderr) = $node->psql('postgres', qq(SELECT brin_index_check('$test_struct->{index_name}', true)));
303+
my ($result, $stdout, $stderr) = $node->psql('postgres', qq(SELECT brin_index_check('$test_struct->{index_name}', true, true)));
255304
like($stderr, $test_struct->{expected});
256305
}
257306

0 commit comments

Comments
 (0)