mirror of https://github.com/postgres/postgres
To increase our test coverage in general, and because I will add onto this in the next commit to also test amcheck with incomplete splits. This is copied from the similar test we had for GIN indexes. B-tree's incomplete splits work similarly to GIN's, so with small changes, the same test works for B-tree too. Reviewed-by: Peter Geoghegan <pg@bowt.ie> Discussion: https://www.postgresql.org/message-id/abd65090-5336-42cc-b768-2bdd66738404@iki.fipull/255/head
parent
f894acb24a
commit
1e4e5783e7
@ -0,0 +1,28 @@ |
|||||||
|
# src/test/modules/nbtree/Makefile
|
||||||
|
|
||||||
|
EXTRA_INSTALL = src/test/modules/injection_points
|
||||||
|
|
||||||
|
REGRESS = nbtree_incomplete_splits
|
||||||
|
|
||||||
|
ifdef USE_PGXS |
||||||
|
PG_CONFIG = pg_config
|
||||||
|
PGXS := $(shell $(PG_CONFIG) --pgxs)
|
||||||
|
include $(PGXS) |
||||||
|
else |
||||||
|
subdir = src/test/modules/nbtree
|
||||||
|
top_builddir = ../../../..
|
||||||
|
include $(top_builddir)/src/Makefile.global |
||||||
|
|
||||||
|
# XXX: This test is conditional on enable_injection_points in the
|
||||||
|
# parent Makefile, so we should never get here in the first place if
|
||||||
|
# injection points are not enabled. But the buildfarm 'misc-check'
|
||||||
|
# step doesn't pay attention to the if-condition in the parent
|
||||||
|
# Makefile. To work around that, disable running the test here too.
|
||||||
|
ifeq ($(enable_injection_points),yes) |
||||||
|
include $(top_srcdir)/contrib/contrib-global.mk |
||||||
|
else |
||||||
|
check: |
||||||
|
@echo "injection points are disabled in this build"
|
||||||
|
endif |
||||||
|
|
||||||
|
endif |
||||||
@ -0,0 +1,179 @@ |
|||||||
|
-- |
||||||
|
-- Test incomplete splits in B-tree indexes. |
||||||
|
-- |
||||||
|
-- We use a test table with integers from 1 to :next_i. Each integer |
||||||
|
-- occurs exactly once, no gaps or duplicates, although the index does |
||||||
|
-- contain some duplicates because some of the inserting transactions |
||||||
|
-- are rolled back during the test. The exact contents of the table |
||||||
|
-- depend on the physical layout of the index, which in turn depends |
||||||
|
-- at least on the block size, so instead of checking the exact |
||||||
|
-- contents, we check those invariants. :next_i psql variable is |
||||||
|
-- maintained at all times to hold the last inserted integer + 1. |
||||||
|
-- |
||||||
|
-- This uses injection points to cause errors that leave some page |
||||||
|
-- splits in "incomplete" state |
||||||
|
set client_min_messages TO 'warning'; |
||||||
|
create extension if not exists injection_points; |
||||||
|
reset client_min_messages; |
||||||
|
-- Make all injection points local to this process, for concurrency. |
||||||
|
SELECT injection_points_set_local(); |
||||||
|
injection_points_set_local |
||||||
|
---------------------------- |
||||||
|
|
||||||
|
(1 row) |
||||||
|
|
||||||
|
-- Use the index for all the queries |
||||||
|
set enable_seqscan=off; |
||||||
|
-- Print a NOTICE whenever an incomplete split gets fixed |
||||||
|
SELECT injection_points_attach('nbtree-finish-incomplete-split', 'notice'); |
||||||
|
injection_points_attach |
||||||
|
------------------------- |
||||||
|
|
||||||
|
(1 row) |
||||||
|
|
||||||
|
-- |
||||||
|
-- First create the test table and some helper functions |
||||||
|
-- |
||||||
|
create table nbtree_incomplete_splits(i int4) with (autovacuum_enabled = off); |
||||||
|
create index nbtree_incomplete_splits_i_idx on nbtree_incomplete_splits using btree (i); |
||||||
|
-- Inserts 'n' rows to the test table. Pass :next_i as the first |
||||||
|
-- argument, returns the new value for :next_i. |
||||||
|
create function insert_n(first_i int, n int) returns int language plpgsql as $$ |
||||||
|
begin |
||||||
|
insert into nbtree_incomplete_splits select g from generate_series(first_i, first_i + n - 1) as g; |
||||||
|
return first_i + n; |
||||||
|
end; |
||||||
|
$$; |
||||||
|
-- Inserts to the table until an insert fails. Like insert_n(), returns the |
||||||
|
-- new value for :next_i. |
||||||
|
create function insert_until_fail(next_i int, step int default 1) returns int language plpgsql as $$ |
||||||
|
declare |
||||||
|
i integer; |
||||||
|
begin |
||||||
|
-- Insert rows in batches of 'step' rows each, until an error occurs. |
||||||
|
i := 0; |
||||||
|
loop |
||||||
|
begin |
||||||
|
select insert_n(next_i, step) into next_i; |
||||||
|
exception when others then |
||||||
|
raise notice 'failed with: %', sqlerrm; |
||||||
|
exit; |
||||||
|
end; |
||||||
|
|
||||||
|
-- The caller is expected to set an injection point that eventually |
||||||
|
-- causes an error. But bail out if still no error after 10000 |
||||||
|
-- attempts, so that we don't get stuck in an infinite loop. |
||||||
|
i := i + 1; |
||||||
|
if i = 10000 then |
||||||
|
raise 'no error on inserts after % iterations', i; |
||||||
|
end if; |
||||||
|
end loop; |
||||||
|
|
||||||
|
return next_i; |
||||||
|
end; |
||||||
|
$$; |
||||||
|
-- Check the invariants. |
||||||
|
create function verify(next_i int) returns bool language plpgsql as $$ |
||||||
|
declare |
||||||
|
c integer; |
||||||
|
begin |
||||||
|
-- Perform a scan over the trailing part of the index, where the |
||||||
|
-- possible incomplete splits are. (We don't check the whole table, |
||||||
|
-- because that'd be pretty slow.) |
||||||
|
-- |
||||||
|
-- Find all rows that overlap with the last 200 inserted integers. Or |
||||||
|
-- the next 100, which shouldn't exist. |
||||||
|
select count(*) into c from nbtree_incomplete_splits where i between next_i - 200 and next_i + 100; |
||||||
|
if c <> 200 then |
||||||
|
raise 'unexpected count % ', c; |
||||||
|
end if; |
||||||
|
return true; |
||||||
|
end; |
||||||
|
$$; |
||||||
|
-- Insert one array to get started. |
||||||
|
select insert_n(1, 1000) as next_i |
||||||
|
\gset |
||||||
|
select verify(:next_i); |
||||||
|
verify |
||||||
|
-------- |
||||||
|
t |
||||||
|
(1 row) |
||||||
|
|
||||||
|
-- |
||||||
|
-- Test incomplete leaf split |
||||||
|
-- |
||||||
|
SELECT injection_points_attach('nbtree-leave-leaf-split-incomplete', 'error'); |
||||||
|
injection_points_attach |
||||||
|
------------------------- |
||||||
|
|
||||||
|
(1 row) |
||||||
|
|
||||||
|
select insert_until_fail(:next_i) as next_i |
||||||
|
\gset |
||||||
|
NOTICE: failed with: error triggered for injection point nbtree-leave-leaf-split-incomplete |
||||||
|
SELECT injection_points_detach('nbtree-leave-leaf-split-incomplete'); |
||||||
|
injection_points_detach |
||||||
|
------------------------- |
||||||
|
|
||||||
|
(1 row) |
||||||
|
|
||||||
|
-- Verify that a scan works even though there's an incomplete split |
||||||
|
select verify(:next_i); |
||||||
|
verify |
||||||
|
-------- |
||||||
|
t |
||||||
|
(1 row) |
||||||
|
|
||||||
|
-- Insert some more rows, finishing the split |
||||||
|
select insert_n(:next_i, 10) as next_i |
||||||
|
\gset |
||||||
|
NOTICE: notice triggered for injection point nbtree-finish-incomplete-split |
||||||
|
-- Verify that a scan still works |
||||||
|
select verify(:next_i); |
||||||
|
verify |
||||||
|
-------- |
||||||
|
t |
||||||
|
(1 row) |
||||||
|
|
||||||
|
-- |
||||||
|
-- Test incomplete internal page split |
||||||
|
-- |
||||||
|
SELECT injection_points_attach('nbtree-leave-internal-split-incomplete', 'error'); |
||||||
|
injection_points_attach |
||||||
|
------------------------- |
||||||
|
|
||||||
|
(1 row) |
||||||
|
|
||||||
|
select insert_until_fail(:next_i, 100) as next_i |
||||||
|
\gset |
||||||
|
NOTICE: failed with: error triggered for injection point nbtree-leave-internal-split-incomplete |
||||||
|
SELECT injection_points_detach('nbtree-leave-internal-split-incomplete'); |
||||||
|
injection_points_detach |
||||||
|
------------------------- |
||||||
|
|
||||||
|
(1 row) |
||||||
|
|
||||||
|
-- Verify that a scan works even though there's an incomplete split |
||||||
|
select verify(:next_i); |
||||||
|
verify |
||||||
|
-------- |
||||||
|
t |
||||||
|
(1 row) |
||||||
|
|
||||||
|
-- Insert some more rows, finishing the split |
||||||
|
select insert_n(:next_i, 10) as next_i |
||||||
|
\gset |
||||||
|
NOTICE: notice triggered for injection point nbtree-finish-incomplete-split |
||||||
|
-- Verify that a scan still works |
||||||
|
select verify(:next_i); |
||||||
|
verify |
||||||
|
-------- |
||||||
|
t |
||||||
|
(1 row) |
||||||
|
|
||||||
|
SELECT injection_points_detach('nbtree-finish-incomplete-split'); |
||||||
|
injection_points_detach |
||||||
|
------------------------- |
||||||
|
|
||||||
|
(1 row) |
||||||
|
|
||||||
@ -0,0 +1,16 @@ |
|||||||
|
# Copyright (c) 2022-2025, PostgreSQL Global Development Group |
||||||
|
|
||||||
|
if not get_option('injection_points') |
||||||
|
subdir_done() |
||||||
|
endif |
||||||
|
|
||||||
|
tests += { |
||||||
|
'name': 'nbtree', |
||||||
|
'sd': meson.current_source_dir(), |
||||||
|
'bd': meson.current_build_dir(), |
||||||
|
'regress': { |
||||||
|
'sql': [ |
||||||
|
'nbtree_incomplete_splits', |
||||||
|
], |
||||||
|
}, |
||||||
|
} |
||||||
@ -0,0 +1,134 @@ |
|||||||
|
-- |
||||||
|
-- Test incomplete splits in B-tree indexes. |
||||||
|
-- |
||||||
|
-- We use a test table with integers from 1 to :next_i. Each integer |
||||||
|
-- occurs exactly once, no gaps or duplicates, although the index does |
||||||
|
-- contain some duplicates because some of the inserting transactions |
||||||
|
-- are rolled back during the test. The exact contents of the table |
||||||
|
-- depend on the physical layout of the index, which in turn depends |
||||||
|
-- at least on the block size, so instead of checking the exact |
||||||
|
-- contents, we check those invariants. :next_i psql variable is |
||||||
|
-- maintained at all times to hold the last inserted integer + 1. |
||||||
|
-- |
||||||
|
|
||||||
|
-- This uses injection points to cause errors that leave some page |
||||||
|
-- splits in "incomplete" state |
||||||
|
set client_min_messages TO 'warning'; |
||||||
|
create extension if not exists injection_points; |
||||||
|
reset client_min_messages; |
||||||
|
|
||||||
|
-- Make all injection points local to this process, for concurrency. |
||||||
|
SELECT injection_points_set_local(); |
||||||
|
|
||||||
|
-- Use the index for all the queries |
||||||
|
set enable_seqscan=off; |
||||||
|
|
||||||
|
-- Print a NOTICE whenever an incomplete split gets fixed |
||||||
|
SELECT injection_points_attach('nbtree-finish-incomplete-split', 'notice'); |
||||||
|
|
||||||
|
-- |
||||||
|
-- First create the test table and some helper functions |
||||||
|
-- |
||||||
|
create table nbtree_incomplete_splits(i int4) with (autovacuum_enabled = off); |
||||||
|
|
||||||
|
create index nbtree_incomplete_splits_i_idx on nbtree_incomplete_splits using btree (i); |
||||||
|
|
||||||
|
-- Inserts 'n' rows to the test table. Pass :next_i as the first |
||||||
|
-- argument, returns the new value for :next_i. |
||||||
|
create function insert_n(first_i int, n int) returns int language plpgsql as $$ |
||||||
|
begin |
||||||
|
insert into nbtree_incomplete_splits select g from generate_series(first_i, first_i + n - 1) as g; |
||||||
|
return first_i + n; |
||||||
|
end; |
||||||
|
$$; |
||||||
|
|
||||||
|
-- Inserts to the table until an insert fails. Like insert_n(), returns the |
||||||
|
-- new value for :next_i. |
||||||
|
create function insert_until_fail(next_i int, step int default 1) returns int language plpgsql as $$ |
||||||
|
declare |
||||||
|
i integer; |
||||||
|
begin |
||||||
|
-- Insert rows in batches of 'step' rows each, until an error occurs. |
||||||
|
i := 0; |
||||||
|
loop |
||||||
|
begin |
||||||
|
select insert_n(next_i, step) into next_i; |
||||||
|
exception when others then |
||||||
|
raise notice 'failed with: %', sqlerrm; |
||||||
|
exit; |
||||||
|
end; |
||||||
|
|
||||||
|
-- The caller is expected to set an injection point that eventually |
||||||
|
-- causes an error. But bail out if still no error after 10000 |
||||||
|
-- attempts, so that we don't get stuck in an infinite loop. |
||||||
|
i := i + 1; |
||||||
|
if i = 10000 then |
||||||
|
raise 'no error on inserts after % iterations', i; |
||||||
|
end if; |
||||||
|
end loop; |
||||||
|
|
||||||
|
return next_i; |
||||||
|
end; |
||||||
|
$$; |
||||||
|
|
||||||
|
-- Check the invariants. |
||||||
|
create function verify(next_i int) returns bool language plpgsql as $$ |
||||||
|
declare |
||||||
|
c integer; |
||||||
|
begin |
||||||
|
-- Perform a scan over the trailing part of the index, where the |
||||||
|
-- possible incomplete splits are. (We don't check the whole table, |
||||||
|
-- because that'd be pretty slow.) |
||||||
|
-- |
||||||
|
-- Find all rows that overlap with the last 200 inserted integers. Or |
||||||
|
-- the next 100, which shouldn't exist. |
||||||
|
select count(*) into c from nbtree_incomplete_splits where i between next_i - 200 and next_i + 100; |
||||||
|
if c <> 200 then |
||||||
|
raise 'unexpected count % ', c; |
||||||
|
end if; |
||||||
|
return true; |
||||||
|
end; |
||||||
|
$$; |
||||||
|
|
||||||
|
-- Insert one array to get started. |
||||||
|
select insert_n(1, 1000) as next_i |
||||||
|
\gset |
||||||
|
select verify(:next_i); |
||||||
|
|
||||||
|
|
||||||
|
-- |
||||||
|
-- Test incomplete leaf split |
||||||
|
-- |
||||||
|
SELECT injection_points_attach('nbtree-leave-leaf-split-incomplete', 'error'); |
||||||
|
select insert_until_fail(:next_i) as next_i |
||||||
|
\gset |
||||||
|
SELECT injection_points_detach('nbtree-leave-leaf-split-incomplete'); |
||||||
|
|
||||||
|
-- Verify that a scan works even though there's an incomplete split |
||||||
|
select verify(:next_i); |
||||||
|
|
||||||
|
-- Insert some more rows, finishing the split |
||||||
|
select insert_n(:next_i, 10) as next_i |
||||||
|
\gset |
||||||
|
-- Verify that a scan still works |
||||||
|
select verify(:next_i); |
||||||
|
|
||||||
|
|
||||||
|
-- |
||||||
|
-- Test incomplete internal page split |
||||||
|
-- |
||||||
|
SELECT injection_points_attach('nbtree-leave-internal-split-incomplete', 'error'); |
||||||
|
select insert_until_fail(:next_i, 100) as next_i |
||||||
|
\gset |
||||||
|
SELECT injection_points_detach('nbtree-leave-internal-split-incomplete'); |
||||||
|
|
||||||
|
-- Verify that a scan works even though there's an incomplete split |
||||||
|
select verify(:next_i); |
||||||
|
|
||||||
|
-- Insert some more rows, finishing the split |
||||||
|
select insert_n(:next_i, 10) as next_i |
||||||
|
\gset |
||||||
|
-- Verify that a scan still works |
||||||
|
select verify(:next_i); |
||||||
|
|
||||||
|
SELECT injection_points_detach('nbtree-finish-incomplete-split'); |
||||||
Loading…
Reference in new issue