fix(storage): pre-collect preferred-pool pages before staging (SQLR-1)#93
Merged
Conversation
CREATE INDEX on a wide table panicked during the post-DDL auto-save with `unknown paged-entry kind tag 0x4` because save_database walked each B-Tree's previous root *during* the staging loop. Pager::read_page shadows on-disk bytes with the in-flight `staged` buffer, so a new index extending past the old high-water would overwrite the pages of any later-staged object whose old root sat in that range. master is staged last, so its old root was the canonical victim — the master walk read the new index's freshly-staged leaf and tripped on KIND_INDEX (0x04). Snapshot every prior B-Tree's page set up front (right after read_old_rootpages, before clear_staged) and look the snapshots up in the staging loop instead of re-walking. master gets its own old_master_pages snapshot for the same reason. Also tightens PagedEntry::decode's error to name the offending kind (KIND_INDEX/KIND_INTERIOR/KIND_HNSW/KIND_FTS_POSTING) and the B-Tree the caller meant to walk, and documents the per-B-Tree decoder dispatch contract in docs/file-format.md so the bug-class doesn't recur when a future cell kind lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
CREATE INDEXon a wide table panicked during the post-DDL auto-save withInternal("unknown paged-entry kind tag 0x4 …").save_database_with_modewalked each B-Tree's previous root viacollect_pages_for_btreeduring the staging loop.Pager::read_pageshadows on-disk bytes with the in-flightstagedbuffer, so a new index extending past the old high-water would overwrite the pages of any later-staged object —sqlrite_master(always staged last) being the canonical victim. The master walk then read the new index's freshly-staged leaf and tripped onKIND_INDEX = 0x04going throughPagedEntry::decode, which only knowsKIND_LOCAL/KIND_OVERFLOW.read_old_rootpages, beforeclear_staged()), then look those snapshots up in the staging loop instead of re-walking. Master gets its ownold_master_pagessnapshot for the same reason. All five in-loopcollect_pages_for_btreecalls removed.PagedEntry::decode's error to name the offending kind and the B-Tree the caller meant to walk, and documents the per-B-Tree decoder dispatch contract indocs/file-format.mdso the bug-class doesn't recur.Test plan
secondary_index_with_interior_level_round_trips— 5000-row CREATE INDEX, reopen, lookup spot-checks, asserts root isInteriorNode(the regression's necessary condition).drop_then_recreate_wide_index_does_not_panic— exercises page recycling between two large versions of the same index name.paged_entry_decode_rejects_index_kind_with_clear_errorandpaged_entry_decode_rejects_hnsw_and_fts_kinds— wrong-decoder error message coverage.cargo build --workspace --exclude sqlrite-desktop --exclude sqlrite-python --exclude sqlrite-nodejs --all-targetscargo test --workspace --exclude sqlrite-desktop --exclude sqlrite-python --exclude sqlrite-nodejs(384 engine + 19 MCP + 12 FFI + all doctests pass)cargo fmt --all -- --checkcargo clippy --workspace --exclude sqlrite-desktop --exclude sqlrite-python --exclude sqlrite-nodejs --all-targets(no new warnings)cargo doc --workspace --exclude sqlrite-desktop --exclude sqlrite-python --exclude sqlrite-nodejs --no-deps🤖 Generated with Claude Code