From 8d86317cf30cdd15044f7834e5141216c5f1bb1e Mon Sep 17 00:00:00 2001 From: snppg Date: Wed, 15 Jul 2026 18:58:47 +0700 Subject: [PATCH] amcheck: Detect duplicate PostingItem keys in GIN posting trees gin_index_check() used a strict < comparison for adjacent separator keys, so equal keys were silently accepted. GIN posting-tree separators must be strictly ascending (each key is a child right bound), matching the entry-tree checks that already reject equals. --- contrib/amcheck/t/006_verify_gin.pl | 30 +++++++++++++++++++++++++++++ contrib/amcheck/verify_gin.c | 3 ++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/contrib/amcheck/t/006_verify_gin.pl b/contrib/amcheck/t/006_verify_gin.pl index 93571f1d2ab..d720a3b3f13 100644 --- a/contrib/amcheck/t/006_verify_gin.pl +++ b/contrib/amcheck/t/006_verify_gin.pl @@ -37,6 +37,7 @@ invalid_entry_columns_order_test(); inconsistent_with_parent_key__parent_key_corrupted_test(); inconsistent_with_parent_key__child_key_corrupted_test(); inconsistent_with_parent_key__parent_key_corrupted_posting_tree_test(); +equal_posting_tree_internal_keys_test(); sub invalid_entry_order_leaf_page_test { @@ -252,6 +253,35 @@ sub inconsistent_with_parent_key__parent_key_corrupted_posting_tree_test like($stderr, qr/$expected/); } +# Equal adjacent PostingItem keys on posting-tree root (block 2). +sub equal_posting_tree_internal_keys_test +{ + my $indexname = "test_gin_idx"; + + $node->safe_psql( + 'postgres', qq( + DROP TABLE IF EXISTS test; + CREATE TABLE test (a text[]); + INSERT INTO test (a) SELECT ('{aaaaa}') FROM generate_series(1, 30000); + CREATE INDEX $indexname ON test USING gin (a); + )); + my $relpath = relation_filepath($indexname); + + $node->stop; + + # PostingItems start at offset 32; set first key equal to second. + my $find = qr/\A(.{32})(.{4})(.{6})(.{4})(.{6})/s; + my $replace = '$1$2$5$4$5'; + string_replace_block($relpath, $find, $replace, 2); + + $node->start; + + my ($result, $stdout, $stderr) = + $node->psql('postgres', qq(SELECT gin_index_check('$indexname'))); + like($stderr, + qr/index "$indexname" has wrong tuple order in posting tree, block 2, offset 2/); +} + # Returns the filesystem path for the named relation. sub relation_filepath diff --git a/contrib/amcheck/verify_gin.c b/contrib/amcheck/verify_gin.c index fa06689ed5b..9b01b215b5c 100644 --- a/contrib/amcheck/verify_gin.c +++ b/contrib/amcheck/verify_gin.c @@ -334,7 +334,8 @@ gin_check_posting_tree_parent_keys_consistency(Relation rel, BlockNumber posting { PostingItem *previous_posting_item = GinDataPageGetPostingItem(page, i - 1); - if (ItemPointerCompare(&posting_item->key, &previous_posting_item->key) < 0) + /* Reject out-of-order and duplicate separator keys. */ + if (ItemPointerCompare(&posting_item->key, &previous_posting_item->key) <= 0) ereport(ERROR, (errcode(ERRCODE_INDEX_CORRUPTED), errmsg("index \"%s\" has wrong tuple order in posting tree, block %u, offset %u", -- 2.55.0