pub enum NestedWriteOp {
Create {
relation: &'static str,
target_table: &'static str,
foreign_key: &'static str,
payload: Vec<Vec<(String, FilterValue)>>,
},
Connect {
relation: &'static str,
target_table: &'static str,
foreign_key: &'static str,
target_pk: &'static str,
pk: FilterValue,
},
Disconnect {
relation: &'static str,
target_table: &'static str,
foreign_key: &'static str,
target_pk: &'static str,
pk: FilterValue,
},
Delete {
relation: &'static str,
target_table: &'static str,
target_pk: &'static str,
pk: FilterValue,
},
DeleteMany {
relation: &'static str,
target_table: &'static str,
foreign_key: &'static str,
filter: Filter,
},
Update {
relation: &'static str,
target_table: &'static str,
target_pk: &'static str,
pk: FilterValue,
payload: Vec<(String, WriteOp)>,
},
UpdateMany {
relation: &'static str,
target_table: &'static str,
foreign_key: &'static str,
filter: Filter,
payload: Vec<(String, WriteOp)>,
},
Upsert {
relation: &'static str,
target_table: &'static str,
foreign_key: &'static str,
target_pk: &'static str,
pk: FilterValue,
create_payload: Vec<(String, FilterValue)>,
update_payload: Vec<(String, WriteOp)>,
},
ConnectOrCreate {
relation: &'static str,
target_table: &'static str,
foreign_key: &'static str,
where_filter: Filter,
create_payload: Vec<(String, FilterValue)>,
},
Set {
relation: &'static str,
target_table: &'static str,
foreign_key: &'static str,
target_pk: &'static str,
set_pks: Vec<FilterValue>,
},
}Expand description
Model-erased nested write op used by CreateOperation::with(...).
The type-parameterized NestedWrite above is keyed on the parent
model and doesn’t compose across heterogeneous child types — a
CreateOperation<E, User>.with(posts_write) needs to carry child
writes for a different model (Post) than the parent, so User’s
NestedWrite<User> can’t encode them. This sibling enum drops the
model type parameter and carries only the runtime metadata the
execution path actually needs: the target table, the foreign-key
column on that table, and the raw child-column payload.
Emitted by the codegen’s per-relation create() / connect()
helpers on user::posts::*. Payloads are a nested
Vec<Vec<(String, FilterValue)>> rather than a strongly-typed
CreateInput because the derive path doesn’t currently emit a
CreateInput struct per model — see the task docs for the trade-off
and the upgrade path.
Variants§
Create
Create children whose FK column points at the parent’s PK.
relation is retained for diagnostics/debugging; the executor
only needs target_table, foreign_key, and payload.
Fields
payload: Vec<Vec<(String, FilterValue)>>One Vec<(column, value)> per child row. The FK column +
parent PK are appended by NestedWriteOp::execute.
Connect
Connect an existing child row by its primary-key value.
Lowers to
UPDATE <target_table> SET <foreign_key> = <parent_pk> WHERE <target_pk> = <pk>
at execute time. The identifier fields are &'static str because
they come from codegen-emitted constants on the per-relation
RelationMeta / Model types — the type itself enforces the
SQL-safety boundary (see .cursor/rules/sql-safety.mdc). Only
parent_pk and pk flow as $N-bound parameters.
Fields
pk: FilterValueDisconnect
Disconnect a child row by clearing its FK column to NULL.
Lowers to UPDATE <target_table> SET <foreign_key> = NULL WHERE <target_pk> = <pk>.
The child row persists; only the FK is cleared. Use
NestedWriteOp::Delete to remove the row entirely.
Fields
pk: FilterValueDelete
Delete a child row by its primary key.
Lowers to DELETE FROM <target_table> WHERE <target_pk> = <pk>.
Returns QueryError::not_found when the PK doesn’t match any row,
matching the Connect-batch affected-rows contract.
DeleteMany
Delete many child rows matching a scalar filter, scoped to the parent’s children only.
Lowers to DELETE FROM <target_table> WHERE <foreign_key> = <parent_pk> AND <filter>.
The AND-with-parent-FK clause is a safety bound enforced at SQL
emit time — the user-supplied filter cannot remove rows belonging
to other parents.
Update
Update a child row by its primary key.
Lowers to
UPDATE <target_table> SET <writeop-fragments> WHERE <target_pk> = $1.
Each entry in payload contributes one column assignment whose
shape is determined by the crate::inputs::WriteOp variant
(plain set, atomic increment/decrement/multiply/divide, or
null-out via Unset). Returns QueryError::not_found when the PK
doesn’t match any row, mirroring NestedWriteOp::Delete’s
affected-rows contract.
Fields
pk: FilterValueUpdateMany
Update many child rows matching a filter, scoped to the parent’s children only.
Lowers to
UPDATE <target_table> SET <writeop-fragments> WHERE <foreign_key> = $1 AND <filter>.
The AND-with-parent-FK clause is a safety bound enforced at SQL
emit time — the user-supplied filter cannot reach rows belonging
to other parents.
Fields
Upsert
Upsert: update if a row matches pk, else insert.
On dialects with native single-statement upsert (Postgres, SQLite,
DuckDB, MySQL), emits one
INSERT INTO <target_table> (<create_cols + fk>) VALUES (...) ON CONFLICT (<target_pk>) DO UPDATE SET <update_writeops>
(or ON DUPLICATE KEY UPDATE ... on MySQL). The pk field is
unused on this path — conflict detection comes from the inserted
row’s PK column, which the codegen guarantees is present in
create_payload.
On MSSQL and CQL, falls back to the two-statement form:
UPDATE <target_table> SET <update_writeops> WHERE <target_pk> = $1- If
affected_rows == 0,INSERT INTO <target_table> (<create_cols + fk>) VALUES (<...>).
Limitations of the fallback path (MSSQL/CQL only):
- The
affected_rows == 0check cannot distinguish “row absent” from “row exists but no columns changed” — a UPDATE that hits an identical row produces a spurious INSERT attempt and likely a PK unique-violation. Wrap the fallback in a transaction (or use a single-statement dialect) for strongest semantics. - There is a TOCTOU race between the UPDATE returning 0 and the subsequent INSERT; a concurrent writer can insert the same row first.
Document-store engines (NotSql dialect) are rejected at the top
of execute with QueryError::unsupported(...).
Empty payloads: an empty update_payload on a single-statement
dialect lowers to ON CONFLICT (...) DO NOTHING (PG/SQLite/DuckDB)
or INSERT IGNORE INTO (MySQL — idempotent no-op).
An empty create_payload errors with QueryError::invalid_input.
ConnectOrCreate
Connect an existing child row if a where filter matches, else
insert a new one with the parent’s FK spliced in.
Two-statement engine-agnostic lowering:
UPDATE <target_table> SET <foreign_key> = $1 WHERE <filter>(the connect path — points any matching row at the parent).- If
affected_rows == 0, emitINSERT INTO <target_table> (<create_cols + foreign_key>) VALUES (<...>)(the create path).
If the filter matches multiple rows, every match has its FK pointed
at the parent — connect_or_create is typically used with a unique
where, but this is not enforced at runtime.
As a safety measure, an empty (Filter::None) where_filter is
rejected at execute time — without this guard, the UPDATE would
lower to ... WHERE TRUE, rewriting every row in the table.
Fields
create_payload: Vec<(String, FilterValue)>Set
Replace the relation contents — after execution, exactly the
listed child rows are connected to the parent. Rows currently
connected that aren’t in set_pks get their FK cleared; rows in
set_pks that aren’t currently connected (or are connected to a
different parent) get their FK pointed at this parent.
Two-statement engine-agnostic lowering:
UPDATE <target_table> SET <foreign_key> = NULL WHERE <foreign_key> = $parent AND <target_pk> NOT IN (set_pks)UPDATE <target_table> SET <foreign_key> = $parent WHERE <target_pk> IN (set_pks)
When set_pks is empty, step 1’s NOT IN () is invalid SQL —
the executor special-cases this to UPDATE <child> SET <fk> = NULL WHERE <fk> = $parent (no NOT IN clause), then skips step 2.
set: claims rows for this parent regardless of who they belonged
to before — pre-existing FK values get overwritten. This matches
Prisma’s relation-replacement semantics.
Implementations§
Source§impl NestedWriteOp
impl NestedWriteOp
Sourcepub async fn execute<E>(
self,
engine: &E,
parent_pk: &FilterValue,
) -> QueryResult<()>where
E: QueryEngine,
pub async fn execute<E>(
self,
engine: &E,
parent_pk: &FilterValue,
) -> QueryResult<()>where
E: QueryEngine,
Execute this nested write inside engine, using parent_pk
as the foreign-key value to splice into each child row.
For Create, this emits one INSERT INTO <target_table> (...)
per child, appending the FK column + parent PK to whatever
columns/values the caller supplied.
Trait Implementations§
Source§impl Clone for NestedWriteOp
impl Clone for NestedWriteOp
Source§fn clone(&self) -> NestedWriteOp
fn clone(&self) -> NestedWriteOp
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more