StructMapper: schedule scans by column index (O(C²) → O(C)) - #8
Merged
stephenafamo merged 2 commits intoJul 7, 2026
Merged
Conversation
sandonemaki
added a commit
to sandonemaki/bob
that referenced
this pull request
Jul 7, 2026
…pper Generate a per-table NULL-tolerant typed mapper (<table>ScanMapperNullable) for every table that is the target of a to-one relationship, and pass it to orm.Preload so the joined child columns are scanned without reflection while preserving the LEFT JOIN semantics of the previous scan.StructMapper path: an all-NULL row still yields no child object, and NULL values scanned into non-nullable fields still leave the zero value. Builds on stephenafamo#715 and benefits from stephenafamo/scan#8. BREAKING CHANGE: orm.Preload (and the psql/mysql/sqlite Preload wrappers) gain a new PreloadMapper[T] parameter. Only hand-written callers of orm.Preload are affected; generated code is regenerated as part of this change. Pass nil to keep the previous reflection-based behaviour.
jay-babu
pushed a commit
to jay-babu/bob
that referenced
this pull request
Jul 8, 2026
…pper Generate a per-table NULL-tolerant typed mapper (<table>ScanMapperNullable) for every table that is the target of a to-one relationship, and pass it to orm.Preload so the joined child columns are scanned without reflection while preserving the LEFT JOIN semantics of the previous scan.StructMapper path: an all-NULL row still yields no child object, and NULL values scanned into non-nullable fields still leave the zero value. Builds on stephenafamo#715 and benefits from stephenafamo/scan#8. BREAKING CHANGE: orm.Preload (and the psql/mysql/sqlite Preload wrappers) gain a new PreloadMapper[T] parameter. Only hand-written callers of orm.Preload are affected; generated code is regenerated as part of this change. Pass nil to keep the previous reflection-based behaviour.
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
This PR switches StructMapper’s scan reservation from a per-row linear lookup by column name to a direct reservation by column index already known in
filterColumns, reducing the per-row matching cost fromO(C²)toO(C).All changes are internal to StructMapper’s implementation. The public API remains unchanged, and normal scan results stay behaviourally identical.
Context
On the bob side, stephenafamo/bob#715 already removes reflection via generated code, but StructMapper-based paths such as Preload still pay the remaining column-name matching cost in scan.
The goal of this PR is to remove that cost for StructMapper users as well. After merge/tagging, the plan is to bump
scanin bob and re-measure real-world Preload workloads to decide whether an additional typed-Preload follow-up is still necessary.Note
The only behavioural difference is the pathological case where a result set contains duplicate column names (for example,
SELECT id, id FROM ...).Today, the second occurrence ends up as an unknown destination and returns an error. With this change, each occurrence is scheduled by its own column index, so both are scanned into the same field and the later value wins.