Skip to content

fix(adi): keep every tag when the bag grows past page 255 - #144

Merged
FiveTechSoft merged 1 commit into
FiveTechSoft:mainfrom
russimicro:fix/adi-tagdir-and-keypos
Jul 29, 2026
Merged

fix(adi): keep every tag when the bag grows past page 255#144
FiveTechSoft merged 1 commit into
FiveTechSoft:mainfrom
russimicro:fix/adi-tagdir-and-keypos

Conversation

@russimicro

Copy link
Copy Markdown
Collaborator

Problem

A .adi bag keeps every tag but the first when it grows past page 255.

Reported by RusSoft, whose ERP indexes ARTICULO with 22 tags. After a
reindex the bag held all 22 on disk — page 2 says count=22 and the 22
per-tag header pages are there — yet reopening it exposed exactly one
order. The browse then asked to sort by a tag the engine claimed not to
exist, and the application fell back to scanning 34,595 records.

Verified by reading the tag directory of the real file:

tagdir count = 22
entry  0  raw 03 00 00 00 00 43  -> page 3
entry  1  raw 81 03 00 00 00 43  -> page 897     <- 0x81 alone = 129: wrong page
entry  2  raw a2 14 00 00 00 43  -> page 5282    <- 0xa2 alone = 162: wrong page

Root cause

The tag-directory entry stored the per-tag header PAGE NUMBER in a single
byte:

pg2[ADI_TAGDIR_ENTRY_START] = static_cast<std::uint8_t>(hdr_pg);

The first tag always lands on page 3, so it survived. Every later
add_tag appends its pages at the end of a bag that is already megabytes
long, so the real page was truncated to its low byte. On reopen
scan_tagdir jumped to the wrong page, found no F-marker there and
dropped the tag without an error.

Fix

The page is written and read as u32 LE over the entry's first four
bytes. Those bytes were unused zeros in the old layout, so a legacy entry
reads back exactly the value it did before — existing bags stay readable.

Bags already written with a truncated page cannot be recovered (the high
bits are simply gone) and need a reindex; the commit message says so.

Test

tests/unit/abi_adi_tagdir_wide_page_test.cpp loads 4,000 rows before
creating the extra tags, which pushes their headers past page 255. That is
the only condition the old code failed under — which is why the existing
two-tag test on a tiny table kept passing. Verified on the generated bag
that the scenario really exercises it:

entry 0 -> page 3
entry 1 -> page 70
entry 2 -> page 309     (raw 35 01 ... ; the old code read 0x35 = 53)

The test asserts the tags are listed, opened, and navigable — it
activates each one by handle and checks that the tag ordering by a
descending-valued column starts on the last-appended row. It avoids tag
ordinals and tag-name lookup on purpose, so it does not depend on
directory ordering (prepend vs append) or on name-based tag identity.

Validation

Built on MSVC 2022 x64 against main @ a253ff0:

Result
New test passes
Full suite: main vs main + this fix identical set of pre-existing failures, none new

Failures were compared by NAME on both builds, not by count — several of
the pre-existing ones (triggers, openads_serverd) are flaky.

A tag-directory entry stored the per-tag header page number in a single
byte. The first tag always lands on page 3, so it survived; every later
add_tag appends its pages at the end of a bag that is already megabytes
long, so the real page (897, 5282, 13519, ...) was truncated to its low
byte. On reopen the reader jumped to the wrong page, found no F-marker
and dropped the tag silently.

Reported by RusSoft, whose ERP indexes ARTICULO with 22 tags: the .adi
held all 22 on disk (page 2 says count=22, the 22 per-tag headers are
there) yet exposed exactly one order, so the browse asked to sort by a
tag the engine claimed not to exist.

The page is now written and read as u32 LE over the entry's first four
bytes. Those bytes were unused zeros, so a legacy entry reads back
exactly the value it did before.

Bags already written with a truncated page stay broken -- the high bits
are gone, not recoverable -- and need a reindex.

Test: 4000 rows loaded BEFORE the extra tags are created, which pushes
their headers past page 255. That is the only condition the old code
failed under, which is why the existing two-tag test on a tiny table
kept passing.
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@FiveTechSoft
FiveTechSoft merged commit b8eecbf into FiveTechSoft:main Jul 29, 2026
6 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants