Distinguish composite types of user types from built-in composites - #1298
Merged
Conversation
cberner
force-pushed
the
claude/codebase-bug-review-p9qemy
branch
from
July 9, 2026 21:15
438edb2 to
b3a1d3c
Compare
A composite type (`Option`, `Vec`, tuple, or array) built its `TypeName` from only the inner type's name string, discarding the inner type's user-defined-vs-built-in classification. So `Option<u32>` (built-in) and `Option<MyType>`, where `MyType` is a user type named "u32", produced byte-identical type names. A table created with one could then be opened as the other with no error, silently reading the stored bytes under the wrong encoding and yielding wrong results or unreachable keys. The classification byte exists precisely to keep a user type named "u32" from colliding with the built-in `u32`, but that protection was lost one nesting level down. Bubble the classification up: a composite that wraps a user-defined type is now itself classified user-defined, keeping the same name string. Composites of only built-in types are byte-for-byte unchanged, so existing databases open unchanged. Tables created by older versions stored such a composite with the non-bubbled classification. To keep them readable, TypeName carries an in-memory-only record of the classification it was bubbled from (never serialized), and check_match accepts that prior spelling in addition to the current one. For fixed-width composites the prior spelling is `Internal`, which also matches redb 2.6; for variable-width tuples it is `Internal2` only, so redb 2.6's incompatible variable-tuple encoding is still correctly rejected rather than silently misread. A flat user-defined type records no such classification, so this never lets a user type named "u32" alias the built-in `u32` at the top level. Assisted-by: Claude Code https://claude.ai/code/session_01G4cggr4Aoc9BVkEndj5Uyf
cberner
force-pushed
the
claude/codebase-bug-review-p9qemy
branch
from
July 9, 2026 21:17
b3a1d3c to
1eddc21
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1298 +/- ##
========================================
Coverage 90.21% 90.22%
========================================
Files 36 36
Lines 16330 16447 +117
========================================
+ Hits 14732 14839 +107
- Misses 1598 1608 +10
🚀 New features to boost your workflow:
|
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.
Problem
A composite type (
Option,Vec, tuple, or array) built itsTypeNamefrom only the inner type's name string, discarding the inner type's user-defined-vs-built-in classification. SoOption<u32>(built-in) andOption<MyType>, whereMyTypeis a user type named"u32", produced byte-identical type names.A table created with one could then be opened as the other with no error, silently reading the stored bytes under the wrong encoding and yielding wrong results or unreachable keys. The classification byte exists precisely to keep a user type named
"u32"from colliding with the built-inu32, but that protection was lost one nesting level down.Fix
Bubble the classification up: a composite that wraps a user-defined type is now itself classified user-defined, keeping the same name string. Composites of only built-in types are byte-for-byte unchanged, so existing databases open unchanged.
To keep tables created by older versions readable,
TypeNamecarries an in-memory-only record of the classification it was bubbled from (never serialized), andcheck_matchaccepts that prior spelling in addition to the current one:Internal, which also matches redb 2.6.Internal2only, so redb 2.6's incompatible variable-tuple encoding is still correctly rejected rather than silently misread."u32"alias the built-inu32at the top level.The
Value/Keypublic traits are untouched — the whole mechanism lives inTypeNameplus the compositetype_name()impls (which already had to change for the fix).Known limitation
An old
Option<user-type>table is byte-identical on disk to a built-inOption<u32>table, so a name-colliding user type can still open a built-in table in the reverse direction. This is inherent to preserving backwards compatibility and only reachable if a user names their type to collide with a built-in (which the docs already advise against). It is strictly better than before, where the alias happened unconditionally in both directions.Tests
src/types.rs: built-in composites keep their exact classification (unchanged on-disk bytes); a composite of a name-colliding user type no longer equals its built-in counterpart;matches_legacyaccepts the rightInternal/Internal2prior spellings; and a flat user type never legacy-matches anInternalname.tests/backward_compatibility.rsusing the realredb 2.6.0dev-dependency: anOption<user-type>table written by redb 2.6 still opens and reads back correctly, and anOption<user-type>table can no longer be opened as the built-inOption<u32>(returnsTableError::TableTypeMismatch).cargo fmt --check,cargo clippy --all-targets --all-features, and the fullcargo test --all-featuressuite all pass.🤖 Generated with Claude Code
Generated by Claude Code