amcheck: fix bug of missing corruption in allequalimage validation
Hi,
While poking around the code in contrib/amcheck/verify_nbtree.c, I noticed the following block:
```
if (allequalimage && !_bt_allequalimage(indrel, false))
{
bool has_interval_ops = false;
for (int i = 0; i < IndexRelationGetNumberOfKeyAttributes(indrel); i++)
if (indrel->rd_opfamily[i] == INTERVAL_BTREE_FAM_OID)
{
has_interval_ops = true;
ereport(ERROR,
(errcode(ERRCODE_INDEX_CORRUPTED),
errmsg("index \"%s\" metapage incorrectly indicates that deduplication is safe",
RelationGetRelationName(indrel)),
has_interval_ops
? errhint("This is known of \"interval\" indexes last built on a version predating 2023-11.")
: 0));
}
}
```
My initial impression was that has_interval_ops was unneeded and could be removed, as it is always true at the point of use. I originally thought this would just be a tiny refactoring.
However, on second thought, I realized that having the ereport inside the for loop is actually a bug. If allequalimage is set in the metapage but _bt_allequalimage says it’s unsafe, we should report corruption regardless of the column types. In the current code, if the index does not contain an interval opfamily, the loop finishes without reaching the ereport, thus silencing the corruption.
This patch moves the ereport out of the for loop. This ensures that corruption is reported unconditionally, while keeping the interval-specific hint optional.
Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/
Attachments:
v1-0001-amcheck-fix-missing-corruption-error-in-allequali.patchapplication/octet-stream; name=v1-0001-amcheck-fix-missing-corruption-error-in-allequali.patch; x-unix-mode=0644Download+9-8
On Wed, 25 Feb 2026 at 08:12, Chao Li <li.evan.chao@gmail.com> wrote:
Hi,
While poking around the code in contrib/amcheck/verify_nbtree.c, I noticed the following block:
```
if (allequalimage && !_bt_allequalimage(indrel, false))
{
bool has_interval_ops = false;for (int i = 0; i < IndexRelationGetNumberOfKeyAttributes(indrel); i++)
if (indrel->rd_opfamily[i] == INTERVAL_BTREE_FAM_OID)
{
has_interval_ops = true;
ereport(ERROR,
(errcode(ERRCODE_INDEX_CORRUPTED),
errmsg("index \"%s\" metapage incorrectly indicates that deduplication is safe",
RelationGetRelationName(indrel)),
has_interval_ops
? errhint("This is known of \"interval\" indexes last built on a version predating 2023-11.")
: 0));
}
}
```My initial impression was that has_interval_ops was unneeded and could be removed, as it is always true at the point of use. I originally thought this would just be a tiny refactoring.
However, on second thought, I realized that having the ereport inside the for loop is actually a bug. If allequalimage is set in the metapage but _bt_allequalimage says it’s unsafe, we should report corruption regardless of the column types. In the current code, if the index does not contain an interval opfamily, the loop finishes without reaching the ereport, thus silencing the corruption.
This patch moves the ereport out of the for loop. This ensures that corruption is reported unconditionally, while keeping the interval-specific hint optional.
Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/
uff, this looks like a clear oversight of d70b176.
commit d70b17636ddf1ea2c71d1c7bc477372b36ccb66b
Author: Tomas Vondra <tomas.vondra@postgresql.org>
Date: Sat Mar 29 15:14:47 2025 +0100
amcheck: Move common routines into a separate module
...
Reviewed-By: Kirill Reshke <reshkekirill@gmail.com>
Discussion: /messages/by-id/45AC9B0A-2B45-40EE-B08F-BDCF5739D1E1@yandex-team.ru
Oops.
Before d70b176 it was like this:
--
Best regards,
Kirill Reshke
On Feb 25, 2026, at 14:43, Kirill Reshke <reshkekirill@gmail.com> wrote:
On Wed, 25 Feb 2026 at 08:12, Chao Li <li.evan.chao@gmail.com> wrote:
Hi,
While poking around the code in contrib/amcheck/verify_nbtree.c, I noticed the following block:
```
if (allequalimage && !_bt_allequalimage(indrel, false))
{
bool has_interval_ops = false;for (int i = 0; i < IndexRelationGetNumberOfKeyAttributes(indrel); i++)
if (indrel->rd_opfamily[i] == INTERVAL_BTREE_FAM_OID)
{
has_interval_ops = true;
ereport(ERROR,
(errcode(ERRCODE_INDEX_CORRUPTED),
errmsg("index \"%s\" metapage incorrectly indicates that deduplication is safe",
RelationGetRelationName(indrel)),
has_interval_ops
? errhint("This is known of \"interval\" indexes last built on a version predating 2023-11.")
: 0));
}
}
```My initial impression was that has_interval_ops was unneeded and could be removed, as it is always true at the point of use. I originally thought this would just be a tiny refactoring.
However, on second thought, I realized that having the ereport inside the for loop is actually a bug. If allequalimage is set in the metapage but _bt_allequalimage says it’s unsafe, we should report corruption regardless of the column types. In the current code, if the index does not contain an interval opfamily, the loop finishes without reaching the ereport, thus silencing the corruption.
This patch moves the ereport out of the for loop. This ensures that corruption is reported unconditionally, while keeping the interval-specific hint optional.
Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/uff, this looks like a clear oversight of d70b176.
Before d70b176 it was like this:
Thanks for pointing out the origin code that seems to prove my fix is correct. But my patch adds “break” in the “for” loop, which makes it slightly better than the original version.
Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/
On Wed, 25 Feb 2026 at 11:59, Chao Li <li.evan.chao@gmail.com> wrote:
On Feb 25, 2026, at 14:43, Kirill Reshke <reshkekirill@gmail.com> wrote:
On Wed, 25 Feb 2026 at 08:12, Chao Li <li.evan.chao@gmail.com> wrote:
Hi,
While poking around the code in contrib/amcheck/verify_nbtree.c, I noticed the following block:
```
if (allequalimage && !_bt_allequalimage(indrel, false))
{
bool has_interval_ops = false;for (int i = 0; i < IndexRelationGetNumberOfKeyAttributes(indrel); i++)
if (indrel->rd_opfamily[i] == INTERVAL_BTREE_FAM_OID)
{
has_interval_ops = true;
ereport(ERROR,
(errcode(ERRCODE_INDEX_CORRUPTED),
errmsg("index \"%s\" metapage incorrectly indicates that deduplication is safe",
RelationGetRelationName(indrel)),
has_interval_ops
? errhint("This is known of \"interval\" indexes last built on a version predating 2023-11.")
: 0));
}
}
```My initial impression was that has_interval_ops was unneeded and could be removed, as it is always true at the point of use. I originally thought this would just be a tiny refactoring.
However, on second thought, I realized that having the ereport inside the for loop is actually a bug. If allequalimage is set in the metapage but _bt_allequalimage says it’s unsafe, we should report corruption regardless of the column types. In the current code, if the index does not contain an interval opfamily, the loop finishes without reaching the ereport, thus silencing the corruption.
This patch moves the ereport out of the for loop. This ensures that corruption is reported unconditionally, while keeping the interval-specific hint optional.
Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/uff, this looks like a clear oversight of d70b176.
Before d70b176 it was like this:
Thanks for pointing out the origin code that seems to prove my fix is correct. But my patch adds “break” in the “for” loop, which makes it slightly better than the original version.
OK LGTM then
--
Best regards,
Kirill Reshke
On Feb 25, 2026, at 18:13, Kirill Reshke <reshkekirill@gmail.com> wrote:
On Wed, 25 Feb 2026 at 11:59, Chao Li <li.evan.chao@gmail.com> wrote:
On Feb 25, 2026, at 14:43, Kirill Reshke <reshkekirill@gmail.com> wrote:
On Wed, 25 Feb 2026 at 08:12, Chao Li <li.evan.chao@gmail.com> wrote:
Hi,
While poking around the code in contrib/amcheck/verify_nbtree.c, I noticed the following block:
```
if (allequalimage && !_bt_allequalimage(indrel, false))
{
bool has_interval_ops = false;for (int i = 0; i < IndexRelationGetNumberOfKeyAttributes(indrel); i++)
if (indrel->rd_opfamily[i] == INTERVAL_BTREE_FAM_OID)
{
has_interval_ops = true;
ereport(ERROR,
(errcode(ERRCODE_INDEX_CORRUPTED),
errmsg("index \"%s\" metapage incorrectly indicates that deduplication is safe",
RelationGetRelationName(indrel)),
has_interval_ops
? errhint("This is known of \"interval\" indexes last built on a version predating 2023-11.")
: 0));
}
}
```My initial impression was that has_interval_ops was unneeded and could be removed, as it is always true at the point of use. I originally thought this would just be a tiny refactoring.
However, on second thought, I realized that having the ereport inside the for loop is actually a bug. If allequalimage is set in the metapage but _bt_allequalimage says it’s unsafe, we should report corruption regardless of the column types. In the current code, if the index does not contain an interval opfamily, the loop finishes without reaching the ereport, thus silencing the corruption.
This patch moves the ereport out of the for loop. This ensures that corruption is reported unconditionally, while keeping the interval-specific hint optional.
Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/uff, this looks like a clear oversight of d70b176.
Before d70b176 it was like this:
Thanks for pointing out the origin code that seems to prove my fix is correct. But my patch adds “break” in the “for” loop, which makes it slightly better than the original version.
OK LGTM then
--
Best regards,
Kirill Reshke
Hi Kirill,
Thanks for your review. I added the patch to CF: https://commitfest.postgresql.org/patch/6534/ for tracking purpose. Would you please make yourself a reviewer there.
Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/
Hi,
On Thu, Feb 26, 2026 at 9:13 AM Chao Li <li.evan.chao@gmail.com> wrote:
On Feb 25, 2026, at 18:13, Kirill Reshke <reshkekirill@gmail.com> wrote:
On Wed, 25 Feb 2026 at 11:59, Chao Li <li.evan.chao@gmail.com> wrote:
On Feb 25, 2026, at 14:43, Kirill Reshke <reshkekirill@gmail.com> wrote:
On Wed, 25 Feb 2026 at 08:12, Chao Li <li.evan.chao@gmail.com> wrote:
Hi,
While poking around the code in contrib/amcheck/verify_nbtree.c, I noticed the following block:
```
if (allequalimage && !_bt_allequalimage(indrel, false))
{
bool has_interval_ops = false;for (int i = 0; i < IndexRelationGetNumberOfKeyAttributes(indrel); i++)
if (indrel->rd_opfamily[i] == INTERVAL_BTREE_FAM_OID)
{
has_interval_ops = true;
ereport(ERROR,
(errcode(ERRCODE_INDEX_CORRUPTED),
errmsg("index \"%s\" metapage incorrectly indicates that deduplication is safe",
RelationGetRelationName(indrel)),
has_interval_ops
? errhint("This is known of \"interval\" indexes last built on a version predating 2023-11.")
: 0));
}
}
```My initial impression was that has_interval_ops was unneeded and could be removed, as it is always true at the point of use. I originally thought this would just be a tiny refactoring.
However, on second thought, I realized that having the ereport inside the for loop is actually a bug. If allequalimage is set in the metapage but _bt_allequalimage says it’s unsafe, we should report corruption regardless of the column types. In the current code, if the index does not contain an interval opfamily, the loop finishes without reaching the ereport, thus silencing the corruption.
This patch moves the ereport out of the for loop. This ensures that corruption is reported unconditionally, while keeping the interval-specific hint optional.
Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/uff, this looks like a clear oversight of d70b176.
Before d70b176 it was like this:
Thanks for pointing out the origin code that seems to prove my fix is correct. But my patch adds “break” in the “for” loop, which makes it slightly better than the original version.
I think that the break is a small improvement.
Once has_interval_ops becomes true, the loop has already learned
everything it needs for the optional hint, so scanning the remaining
key attributes does not change behavior. In that sense, your version
is slightly better than the pre-d70b176 code.
AFAICS, the bug is that the corruption report was incorrectly gated on
finding INTERVAL_BTREE_FAM_OID; moving the ereport(ERROR) out of the
loop would fix it.
LGTM from my side.
--
Best,
Xuneng