PRAGMA integrity_check cannot detect extra index entries: aCnt is hardcoded to zero
- Dominant language
- C
- Stars
- 268
- Forks
- 18
- Avg merge
- 2h 26m
- Merged PRs (30d)
- 454
Description
Found in a full-repo review at `0ba280f06f`.
`PRAGMA integrity_check`'s "wrong # of entries in index" arm cannot fire on a DoltLite-format database. It compares two VDBE registers that are both always zero, so an index holding entries with no corresponding table row is reported as `ok`.
## Cause
`pragma.c` fills registers 8.. from `OP_IntegrityCk` and then compares the index count against the table count (`src/pragma.c:1806-1834`):
```c
sqlite3VdbeLoadString(v, 2, "wrong # of entries in index ");
...
addr = sqlite3VdbeAddOp3(v, OP_Eq, 8+cnt, 0, 8+iTab);
```
Stock's `sqlite3BtreeIntegrityCheck` writes a real per-tree row count into `aCnt` (`src/btree.c:11389`, `sqlite3MemSetArrayInt64(aCnt, i, sCheck.nRow)`). The prolly implementation writes zero into every slot (`src/prolly_btree.c`, in `sqlite3BtreeIntegrityCheck`):
```c
if( aCnt ){
sqlite3VdbeMemSetInt64(&aCnt[i], 0);
}
```
so every comparison is `0 == 0`.
The stub dates to `293ab76455` (2026-03-16), whose message says it populated `aCnt` "to prevent crash when pragma reads uninitialized row counts". It was a crash fix that was never completed, and it has been silently disabling this check for the life of the project.
The rest of `integrity_check` is unaffected — the row-by-row block is plain VDBE over cursors, so missing index entries, non-unique entries, NULL-in-NOT-NULL, CHECK and STRICT violations are all still detected. Only the reverse direction, extra/orphan index entries, is invisible.
## Proof
Patched a scratch build to fill `aCnt` from the existing `countTreeEntries`, plus an env-gated `+1` on one tree to simulate one orphan entry:
```
root 0 : patched => wrong # of entries in index wb | master => ok
root 1 : patched => wrong # of entries in index wb | master => ok
root 2 : patched => wrong # of entries in index ub | master => ok
root 3 : patched => wrong # of entries in index ub | master => ok
root 4 : patched => wrong # of entries in index iv | master => ok
root 5 : patched => wrong # of entries in index iv | master => ok
```
For contrast, stock on a deliberately corrupted file reports the same class correctly: `wrong # of entries in index i1`.
No false positives on healthy databases across INTEGER PK, TEXT PK, WITHOUT ROWID composite PK, and unique + non-unique secondary indexes.
## Cost of the fix: none
`countTreeEntries` (`src/prolly_btree_cursor_count.c:5`) already exists and delegates to `prollySubtreeCount`, which reads the count from the root node header — O(1). Measured `PRAGMA integrity_check` on 500k rows plus an index: **0.31 s on master, 0.31 s patched.**
## Why it matters
`integrity_check` is the structural oracle this project leans on hardest: **1,460 call sites across 330 test files** (271 `.test`, 40 `.sh`, 17 `.c`, 2 `.py`), including the invariant check in the stateful VC fuzzer. Orphan index entries are exactly the defect shape of #2864 (`CREATE INDEX` indexing past the end of the mutmap), #2489, #2632 and #2644 — four index-corruption bugs in three weeks, every one of them found by a human rather than by this check.
## Fix
Fill `aCnt[i]` from `countTreeEntries(p, aRoot[i], &nRow)` instead of zero.
Contributor guide
No contributing guide indexed for this repository
Research direction
Read the sqlite3BtreeIntegrityCheck implementation in src/prolly_btree.c, the countTreeEntries helper in src/prolly_btree_cursor_count.c, and the aCnt consumer in src/pragma.c:1806-1834. Run existing PRAGMA integrity_check coverage and verify that healthy databases remain clean while an orphan index entry reports “wrong # of entries in index” instead of “ok”.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c, sqlite
- Domain
- database
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100