Skip to content

StructMapper: schedule scans by column index (O(C²) → O(C)) - #8

Merged
stephenafamo merged 2 commits into
stephenafamo:mainfrom
sandonemaki:perf/schedule-scan-by-index
Jul 7, 2026
Merged

StructMapper: schedule scans by column index (O(C²) → O(C))#8
stephenafamo merged 2 commits into
stephenafamo:mainfrom
sandonemaki:perf/schedule-scan-by-index

Conversation

@sandonemaki

@sandonemaki sandonemaki commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

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 from O(C²) to O(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 scan in 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.

@sandonemaki sandonemaki changed the title Perf/schedule scan by index StructMapper: schedule scans by column index (O(C²) → O(C)) Jul 7, 2026
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.
@stephenafamo
stephenafamo merged commit cbbcabc into stephenafamo:main Jul 7, 2026
3 checks passed
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.
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