Skip to content

Assemble eager belongs_to in insert returning from the inline child insert's output #1189

Description

@carllerche

Problem

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?;
  1. 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.
    
  2. 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.

Scope

Medium — touches public API or one subsystem

Additional context

  • feat(engine): add conditional execution to the exec program #1182 documents this as a known residual limitation ("Notes for reviewers").
  • 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-enginetoasty/src/engine/ — simplify, lower, plan, execC-bugA defect: the code behaves incorrectly

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions