Lightweight is the only modern C++ library that models associations at all — BelongsTo, HasMany, HasOneThrough, HasManyThrough, CompositeForeignKey, with ddl2cpp inferring them from a live schema. What is missing is the mechanism every other ecosystem pairs with them: a way to load a relation for a set of records in one query.
Current behaviour
Query<Record>().All() calls ConfigureRelationAutoLoading per record in a loop (DataMapper.hpp:2458-2463), installing a lazy loader on each.
- Touching the relation on N rows issues N queries —
LoadHasMany runs one Query<Other>(...) per owner (DataMapper.hpp:2653-2666).
LoadRelations(record) is eager but still per record, and still one query per relation member (DataMapper.hpp:2825-2884).
HasMany::Each streams rather than materializing (HasMany.hpp:249-259), which bounds memory but not query count.
There is no .With() / .Include() / preload anywhere in QueryBuilders.hpp or DataMapper.hpp. The only mitigation is manual — WhereIn plus explicit joins, which is what the shipped Chinook example does (src/examples/test_chinook/main.cpp:154-161).
Prior art
- Django:
select_related (join) / prefetch_related (second query + in-memory stitch); 6.1 added FETCH_RAISE to turn an unintended lazy load into an exception
- EF Core:
.Include() / .ThenInclude()
- Hibernate: fetch joins +
@BatchSize
- ActiveRecord:
.includes()
- SeaORM 2.0:
find_with_related
Suggested shape
auto albums = dm.Query<Album>()
.With<&Album::Track_1>() // one extra SELECT ... WHERE album_id IN (...)
.All();
Implementation sketch: after materializing the outer result set, collect the primary keys, issue one WhereIn query per requested relation, and distribute the rows into the per-record loaders instead of installing lazy ones. The WhereIn machinery and the per-relation select builders (BuildHasManySelectQuery, BuildHasManyThroughSelectQuery) already exist.
A cheaper interim step: a debug-mode counter on SqlLogger that warns when more than N lazy loads fire for the same relation within one scope — the N+1 detector before the N+1 fix. That composes well with #555 (metrics collector).
Lightweight is the only modern C++ library that models associations at all —
BelongsTo,HasMany,HasOneThrough,HasManyThrough,CompositeForeignKey, withddl2cppinferring them from a live schema. What is missing is the mechanism every other ecosystem pairs with them: a way to load a relation for a set of records in one query.Current behaviour
Query<Record>().All()callsConfigureRelationAutoLoadingper record in a loop (DataMapper.hpp:2458-2463), installing a lazy loader on each.LoadHasManyruns oneQuery<Other>(...)per owner (DataMapper.hpp:2653-2666).LoadRelations(record)is eager but still per record, and still one query per relation member (DataMapper.hpp:2825-2884).HasMany::Eachstreams rather than materializing (HasMany.hpp:249-259), which bounds memory but not query count.There is no
.With()/.Include()/ preload anywhere inQueryBuilders.hpporDataMapper.hpp. The only mitigation is manual —WhereInplus explicit joins, which is what the shipped Chinook example does (src/examples/test_chinook/main.cpp:154-161).Prior art
select_related(join) /prefetch_related(second query + in-memory stitch); 6.1 addedFETCH_RAISEto turn an unintended lazy load into an exception.Include()/.ThenInclude()@BatchSize.includes()find_with_relatedSuggested shape
Implementation sketch: after materializing the outer result set, collect the primary keys, issue one
WhereInquery per requested relation, and distribute the rows into the per-record loaders instead of installing lazy ones. TheWhereInmachinery and the per-relation select builders (BuildHasManySelectQuery,BuildHasManyThroughSelectQuery) already exist.A cheaper interim step: a debug-mode counter on
SqlLoggerthat warns when more than N lazy loads fire for the same relation within one scope — the N+1 detector before the N+1 fix. That composes well with #555 (metrics collector).