Remove TryInsertResult::Empty#3103
Conversation
There was a problem hiding this comment.
The concern
To delete Empty, the PR severs the InsertMany -> TryInsert bridge, so batch inserts no longer produce a TryInsert. But on_conflict_do_nothing on a batch still needs a way to say "ran, nothing inserted." The only remaining return type is InsertManyResult { last_insert_id: Option<_> }, which has no conflicted state — so the PR maps it away:
Err(DbErr::RecordNotInserted) => Ok(InsertManyResult { last_insert_id: None }),Two problems with that:
-
Conflictedcollapses intoNone, indistinguishable from empty input. For a batch, "empty input (no SQL run)", "all rows conflicted (SQL ran, 0 inserted)", and "inserted (got a last id)" are three genuinely distinct outcomes. Folding them intoOption<id>silently loses the middle one. The "effectively equivalent toOption" observation only holds afterConflictedhas already been discarded — it's a bit circular. -
The swallow is on
InsertMany::execitself, not gated behindon_conflict_do_nothing. So every batch insert now turnsRecordNotInsertedintoOk(None), including a plaininsert_many(models).exec()that previously returnedErr(RecordNotInserted). That masks errors broadly — and it's the path we use internally to insert M2M junction rows, so an after-save that silently inserts nothing would no longer surface.
Why the rationale only covers half
The justification — empty input is already handled by InsertMany::exec returning None, so Empty is redundant — is fair, but it only speaks to the empty case (and only on the single-row path, where empty can't occur anyway). It says nothing about Conflicted. Effectively two changes are bundled here: (a) drop Empty, which is defensible, and (b) drop the batch's Conflicted signal, which isn't justified.
|
I'd rather preserve explicit Conflicted |
|
Why don't we just return DbErr::RecordNotInserted? |
This fixes a legacy semantic issue. In practice, the
Emptybranch is usually unreachable because empty inputs are already handled byInsertMany, so removing it makes the semantics clearer.At this point,
TryInsertResultis effectively equivalent toOption, so it could be further simplified to useOptioninstead of a custom type. Users can still reproduce the previousTryInsert::Conflictsemantics with their owninput.is_empty()andlast_insert_id.is_none()checks when needed.The changelog needs to be updated to explain the corresponding behavioral changes.
Breaking Changes
TryInsertResult::Empty