Selecting an entity expands its eager @FK graph into JOINs (a non-null @FK produces an INNER JOIN, a @Nullable @FK a LEFT JOIN; Ref fields are not joined and load lazily). Deep or wide entity graphs can therefore produce large join counts. Ref is exactly the lever to cut a join subtree, but nothing surfaces when a graph is getting expensive or which field would help most — new users often don't discover Ref as that lever until they hit a wall.
Non-goal (deliberate)
No pre-emptive boot/validation warning on a subjective "too many joins" threshold. A large join over indexed keys is frequently intentional and fine; a boot-time warning on an arbitrary number is noise and false positives, against the fail-fast / no-boot-warnings stance. Any advice must be objective at query time or explicitly opt-in.
Proposal — two parts
1. Query-time fail-fast on the dialect join ceiling (framework).
When a select would exceed the database's hard join limit (e.g. SQLite ~64 tables, MySQL 61), fail fast at query build with a message clearer than the raw driver error: name the entity, the join count, the limit, and the highest-win Ref field to apply. This is objective, query-time, and fail-fast — consistent with how ambiguity is already handled. Likely adds a maxJoins()-style capability to SqlDialect, defaulting to unknown/unbounded.
2. Opt-in join analysis (CLI / on-demand).
A storm analyze (or doctor) command over the compile-time metamodel index reports, per entity: the eager-select join count and a ranked list of Ref candidates by how many joins each cut would save. Zero boot noise, run on demand. Ties into the CLI work in #213. A DEBUG-level log when a real query crosses a configurable threshold is a possible third tier, off by default.
The "win" computation
Eager @FK edges form an acyclic graph rooted at the entity (eager cycles are impossible — they must already be broken with a Ref), which Storm expands into join aliases. The win of Ref-ing field f = the number of join nodes made unreachable by cutting edge f (the size of f's dominated subtree). A table reached via multiple paths is joined once per path, so path count is what is actually cut, which makes Ref-ing a shared hub especially valuable. Rank fields by win.
Notes
- Advisory framing only ("here is the shape of your graph"), never prescriptive.
- Prototypable locally first: pure static analysis over
ModelImpl / the join builder, no database required.
Tasks
Selecting an entity expands its eager
@FKgraph into JOINs (a non-null@FKproduces anINNER JOIN, a@Nullable @FKaLEFT JOIN;Reffields are not joined and load lazily). Deep or wide entity graphs can therefore produce large join counts.Refis exactly the lever to cut a join subtree, but nothing surfaces when a graph is getting expensive or which field would help most — new users often don't discoverRefas that lever until they hit a wall.Non-goal (deliberate)
No pre-emptive boot/validation warning on a subjective "too many joins" threshold. A large join over indexed keys is frequently intentional and fine; a boot-time warning on an arbitrary number is noise and false positives, against the fail-fast / no-boot-warnings stance. Any advice must be objective at query time or explicitly opt-in.
Proposal — two parts
1. Query-time fail-fast on the dialect join ceiling (framework).
When a select would exceed the database's hard join limit (e.g. SQLite ~64 tables, MySQL 61), fail fast at query build with a message clearer than the raw driver error: name the entity, the join count, the limit, and the highest-win
Reffield to apply. This is objective, query-time, and fail-fast — consistent with how ambiguity is already handled. Likely adds amaxJoins()-style capability toSqlDialect, defaulting to unknown/unbounded.2. Opt-in join analysis (CLI / on-demand).
A
storm analyze(ordoctor) command over the compile-time metamodel index reports, per entity: the eager-select join count and a ranked list ofRefcandidates by how many joins each cut would save. Zero boot noise, run on demand. Ties into the CLI work in #213. A DEBUG-level log when a real query crosses a configurable threshold is a possible third tier, off by default.The "win" computation
Eager
@FKedges form an acyclic graph rooted at the entity (eager cycles are impossible — they must already be broken with aRef), which Storm expands into join aliases. The win ofRef-ing fieldf= the number of join nodes made unreachable by cutting edgef(the size off's dominated subtree). A table reached via multiple paths is joined once per path, so path count is what is actually cut, which makesRef-ing a shared hub especially valuable. Rank fields by win.Notes
ModelImpl/ the join builder, no database required.Tasks
SqlDialectjoin-ceiling capability + query-time fail-fast that names the highest-winReffieldRefcandidates by win)stormCLI (opt-in), reusing the metamodel indexRefas the join-cutting lever and when to reach for it