You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Since #1182, an eager (non-deferred) belongs_to in an INSERT's model returning is loaded with a per-row SELECT correlated on the row's FK. Two problems with that SELECT when the related row is created inline through the relation:
let post = Post::create().title("hello").user(User::create().name("Alice"))// Post.user is eager: `user: User`.exec(&db).await?;
MySQL fails when the target key is DB-generated. With an auto-increment key on User, the post's user_id is not a constant at plan time, so the post INSERT needs RETURNING user_id to feed the load SELECT. The MySQL driver only emulates RETURNING for auto-increment columns, so execution fails:
MySQL does not support RETURNING clause for non-auto-increment columns.
Column 'user_id' in table 'posts' is not auto-increment.
The SELECT is redundant on every backend. The plan already holds the entire user row: the non-key columns are client-known constants in the child insert's VALUES (Toasty applies field defaults and generates non-auto values client-side), and the generated key arrives through the child insert's existing returning channel (last_insert_id emulation on MySQL) — the same channel that fills the post's FK today.
Affects Toasty users on MySQL (failure) and all SQL/NoSQL backends (an unnecessary statement per created row). No driver-facing changes.
Proposed solution
When a belongs_to is associated from an inline child INSERT (plan_mut_belongs_to_associate_stmt), assemble the parent's eager relation slot from the child insert's own output instead of planning a load SELECT.
Today plan_mut_belongs_to_associate_stmt replaces the child insert's returning with a projection of just the referenced key fields. Instead, keep the full model returning and reuse it twice: project the key fields out of it for the FK assignment (as today), and splice the full record into the parent's relation slot. The existing partial-constantization machinery (constantize_insert_returning) already produces the needed split: constant columns inline, the auto-increment key stays a runtime value from the driver's existing insert-returning channel.
The per-row load SELECT from #1182 remains for the other shapes (FK given as a value or an existing model reference), where the related row is not in the plan.
Constraints that make this sound:
The associate path asserts single-row child inserts, so MySQL's last_insert_id (first-id-of-batch) ambiguity does not arise.
Toasty has no DB-computed columns other than auto-increment keys (defaults are applied client-side), so the child VALUES plus the generated key fully reconstruct the row. If server-side defaults or generated columns are ever added, these shapes would need a read-back instead.
Alternatives considered
Redirect the load SELECT's correlation to the child insert's key (instead of the parent's FK column). The back-ref then lands on the auto-increment key, which MySQL's RETURNING emulation handles. Fixes the failure and is more contained, but keeps a SELECT for data the plan already has.
General MySQL RETURNING emulation — after an INSERT, issue SELECT <cols> WHERE pk = last_insert_id() inside the plan's transaction, mirroring the follow-up-SELECT strategy the update-returning path already uses on MySQL. Lifts the auto-increment-only restriction for every case, not just this one, but is driver/exec-level work with a larger blast radius. Worth considering independently.
Today the failing shape surfaces as a panic in exec_statement.rs rather than an error; if this proposal is deferred, converting that panic to an unsupported_feature error is a cheap interim improvement.
Problem
Since #1182, an eager (non-deferred)
belongs_toin an INSERT's model returning is loaded with a per-row SELECT correlated on the row's FK. Two problems with that SELECT when the related row is created inline through the relation:MySQL fails when the target key is DB-generated. With an auto-increment key on
User, the post'suser_idis not a constant at plan time, so the post INSERT needsRETURNING user_idto feed the load SELECT. The MySQL driver only emulatesRETURNINGfor auto-increment columns, so execution fails:The SELECT is redundant on every backend. The plan already holds the entire user row: the non-key columns are client-known constants in the child insert's VALUES (Toasty applies field defaults and generates non-auto values client-side), and the generated key arrives through the child insert's existing returning channel (
last_insert_idemulation on MySQL) — the same channel that fills the post's FK today.Affects Toasty users on MySQL (failure) and all SQL/NoSQL backends (an unnecessary statement per created row). No driver-facing changes.
Proposed solution
When a
belongs_tois associated from an inline child INSERT (plan_mut_belongs_to_associate_stmt), assemble the parent's eager relation slot from the child insert's own output instead of planning a load SELECT.Today
plan_mut_belongs_to_associate_stmtreplaces the child insert's returning with a projection of just the referenced key fields. Instead, keep the full model returning and reuse it twice: project the key fields out of it for the FK assignment (as today), and splice the full record into the parent's relation slot. The existing partial-constantization machinery (constantize_insert_returning) already produces the needed split: constant columns inline, the auto-increment key stays a runtime value from the driver's existing insert-returning channel.The per-row load SELECT from #1182 remains for the other shapes (FK given as a value or an existing model reference), where the related row is not in the plan.
Constraints that make this sound:
last_insert_id(first-id-of-batch) ambiguity does not arise.Alternatives considered
RETURNINGemulation handles. Fixes the failure and is more contained, but keeps a SELECT for data the plan already has.RETURNINGemulation — after an INSERT, issueSELECT <cols> WHERE pk = last_insert_id()inside the plan's transaction, mirroring the follow-up-SELECT strategy the update-returning path already uses on MySQL. Lifts the auto-increment-only restriction for every case, not just this one, but is driver/exec-level work with a larger blast radius. Worth considering independently.Scope
Medium — touches public API or one subsystem
Additional context
exec_statement.rsrather than an error; if this proposal is deferred, converting that panic to anunsupported_featureerror is a cheap interim improvement.