mirror of
https://github.com/postgres/postgres.git
synced 2025-06-26 12:21:12 +03:00
amcheck: Fix parent key check in gin_index_check()
The checks introduced by commit 14ffaece0f
did not get the parent key
checks quite right, missing some data corruption cases. In particular:
* The "rightlink" check was not working as intended, because rightlink
is a BlockNumber, and InvalidBlockNumber is 0xFFFFFFFF, so
!GinPageGetOpaque(page)->rightlink
almost always evaluates to false (except for rightlink=0). So in most
cases parenttup was left NULL, preventing any checks against parent.
* Use GinGetDownlink() to retrieve child blkno to avoid triggering
Assert, same as the core GIN code.
Issues reported by Arseniy Mukhin, along with a proposed patch. Review
by Andrey M. Borodin, cleanup and improvements by me.
Author: Arseniy Mukhin <arseniy.mukhin.dev@gmail.com>
Reviewed-by: Andrey M. Borodin <x4mmm@yandex-team.ru>
Discussion: https://postgr.es/m/CAE7r3MJ611B9TE=YqBBncewp7-k64VWs+sjk7XF6fJUX77uFBA@mail.gmail.com
This commit is contained in:
@ -34,6 +34,8 @@ $node->safe_psql(
|
|||||||
invalid_entry_order_leaf_page_test();
|
invalid_entry_order_leaf_page_test();
|
||||||
invalid_entry_order_inner_page_test();
|
invalid_entry_order_inner_page_test();
|
||||||
invalid_entry_columns_order_test();
|
invalid_entry_columns_order_test();
|
||||||
|
inconsistent_with_parent_key__parent_key_corrupted_test();
|
||||||
|
inconsistent_with_parent_key__child_key_corrupted_test();
|
||||||
|
|
||||||
sub invalid_entry_order_leaf_page_test
|
sub invalid_entry_order_leaf_page_test
|
||||||
{
|
{
|
||||||
@ -159,6 +161,82 @@ sub invalid_entry_columns_order_test
|
|||||||
like($stderr, qr/$expected/);
|
like($stderr, qr/$expected/);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub inconsistent_with_parent_key__parent_key_corrupted_test
|
||||||
|
{
|
||||||
|
my $relname = "test";
|
||||||
|
my $indexname = "test_gin_idx";
|
||||||
|
|
||||||
|
# fill the table until we have a split
|
||||||
|
$node->safe_psql(
|
||||||
|
'postgres', qq(
|
||||||
|
DROP TABLE IF EXISTS $relname;
|
||||||
|
CREATE TABLE $relname (a text[]);
|
||||||
|
INSERT INTO $relname (a) VALUES (('{' || 'llllllllll' || random_string($filler_size) ||'}')::text[]);
|
||||||
|
INSERT INTO $relname (a) VALUES (('{' || 'mmmmmmmmmm' || random_string($filler_size) ||'}')::text[]);
|
||||||
|
INSERT INTO $relname (a) VALUES (('{' || 'nnnnnnnnnn' || random_string($filler_size) ||'}')::text[]);
|
||||||
|
INSERT INTO $relname (a) VALUES (('{' || 'xxxxxxxxxx' || random_string($filler_size) ||'}')::text[]);
|
||||||
|
INSERT INTO $relname (a) VALUES (('{' || 'yyyyyyyyyy' || random_string($filler_size) ||'}')::text[]);
|
||||||
|
CREATE INDEX $indexname ON $relname USING gin (a);
|
||||||
|
));
|
||||||
|
my $relpath = relation_filepath($indexname);
|
||||||
|
|
||||||
|
$node->stop;
|
||||||
|
|
||||||
|
my $blkno = 1; # root
|
||||||
|
|
||||||
|
# we have nnnnnnnnnn... as parent key in the root, so replace it with something smaller then child's keys
|
||||||
|
string_replace_block(
|
||||||
|
$relpath,
|
||||||
|
'nnnnnnnnnn',
|
||||||
|
'aaaaaaaaaa',
|
||||||
|
$blkno
|
||||||
|
);
|
||||||
|
|
||||||
|
$node->start;
|
||||||
|
|
||||||
|
my ($result, $stdout, $stderr) = $node->psql('postgres', qq(SELECT gin_index_check('$indexname')));
|
||||||
|
my $expected = "index \"$indexname\" has inconsistent records on page 3 offset 3";
|
||||||
|
like($stderr, qr/$expected/);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub inconsistent_with_parent_key__child_key_corrupted_test
|
||||||
|
{
|
||||||
|
my $relname = "test";
|
||||||
|
my $indexname = "test_gin_idx";
|
||||||
|
|
||||||
|
# fill the table until we have a split
|
||||||
|
$node->safe_psql(
|
||||||
|
'postgres', qq(
|
||||||
|
DROP TABLE IF EXISTS $relname;
|
||||||
|
CREATE TABLE $relname (a text[]);
|
||||||
|
INSERT INTO $relname (a) VALUES (('{' || 'llllllllll' || random_string($filler_size) ||'}')::text[]);
|
||||||
|
INSERT INTO $relname (a) VALUES (('{' || 'mmmmmmmmmm' || random_string($filler_size) ||'}')::text[]);
|
||||||
|
INSERT INTO $relname (a) VALUES (('{' || 'nnnnnnnnnn' || random_string($filler_size) ||'}')::text[]);
|
||||||
|
INSERT INTO $relname (a) VALUES (('{' || 'xxxxxxxxxx' || random_string($filler_size) ||'}')::text[]);
|
||||||
|
INSERT INTO $relname (a) VALUES (('{' || 'yyyyyyyyyy' || random_string($filler_size) ||'}')::text[]);
|
||||||
|
CREATE INDEX $indexname ON $relname USING gin (a);
|
||||||
|
));
|
||||||
|
my $relpath = relation_filepath($indexname);
|
||||||
|
|
||||||
|
$node->stop;
|
||||||
|
|
||||||
|
my $blkno = 3; # leaf
|
||||||
|
|
||||||
|
# we have nnnnnnnnnn... as parent key in the root, so replace child key with something bigger
|
||||||
|
string_replace_block(
|
||||||
|
$relpath,
|
||||||
|
'nnnnnnnnnn',
|
||||||
|
'pppppppppp',
|
||||||
|
$blkno
|
||||||
|
);
|
||||||
|
|
||||||
|
$node->start;
|
||||||
|
|
||||||
|
my ($result, $stdout, $stderr) = $node->psql('postgres', qq(SELECT gin_index_check('$indexname')));
|
||||||
|
my $expected = "index \"$indexname\" has inconsistent records on page 3 offset 3";
|
||||||
|
like($stderr, qr/$expected/);
|
||||||
|
}
|
||||||
|
|
||||||
# Returns the filesystem path for the named relation.
|
# Returns the filesystem path for the named relation.
|
||||||
sub relation_filepath
|
sub relation_filepath
|
||||||
{
|
{
|
||||||
|
@ -608,10 +608,10 @@ gin_check_parent_keys_consistency(Relation rel,
|
|||||||
ptr = (GinScanItem *) palloc(sizeof(GinScanItem));
|
ptr = (GinScanItem *) palloc(sizeof(GinScanItem));
|
||||||
ptr->depth = stack->depth + 1;
|
ptr->depth = stack->depth + 1;
|
||||||
/* last tuple in layer has no high key */
|
/* last tuple in layer has no high key */
|
||||||
if (i != maxoff && !GinPageGetOpaque(page)->rightlink)
|
if (i == maxoff && rightlink == InvalidBlockNumber)
|
||||||
ptr->parenttup = CopyIndexTuple(idxtuple);
|
|
||||||
else
|
|
||||||
ptr->parenttup = NULL;
|
ptr->parenttup = NULL;
|
||||||
|
else
|
||||||
|
ptr->parenttup = CopyIndexTuple(idxtuple);
|
||||||
ptr->parentblk = stack->blkno;
|
ptr->parentblk = stack->blkno;
|
||||||
ptr->blkno = GinGetDownlink(idxtuple);
|
ptr->blkno = GinGetDownlink(idxtuple);
|
||||||
ptr->next = stack->next;
|
ptr->next = stack->next;
|
||||||
@ -748,7 +748,7 @@ gin_refind_parent(Relation rel, BlockNumber parentblkno,
|
|||||||
ItemId p_iid = PageGetItemIdCareful(rel, parentblkno, parentpage, o);
|
ItemId p_iid = PageGetItemIdCareful(rel, parentblkno, parentpage, o);
|
||||||
IndexTuple itup = (IndexTuple) PageGetItem(parentpage, p_iid);
|
IndexTuple itup = (IndexTuple) PageGetItem(parentpage, p_iid);
|
||||||
|
|
||||||
if (ItemPointerGetBlockNumber(&(itup->t_tid)) == childblkno)
|
if (GinGetDownlink(itup) == childblkno)
|
||||||
{
|
{
|
||||||
/* Found it! Make copy and return it */
|
/* Found it! Make copy and return it */
|
||||||
result = CopyIndexTuple(itup);
|
result = CopyIndexTuple(itup);
|
||||||
|
Reference in New Issue
Block a user