Skip to content

Fix ON_CONFLICT_NOTHING cascade causing FK violation (#3712)#3818

Merged
rbygrave merged 1 commit into
masterfrom
feature/3712-cascade-ocdn-insert
Jul 1, 2026
Merged

Fix ON_CONFLICT_NOTHING cascade causing FK violation (#3712)#3818
rbygrave merged 1 commit into
masterfrom
feature/3712-cascade-ocdn-insert

Conversation

@rbygrave

@rbygrave rbygrave commented Jul 1, 2026

Copy link
Copy Markdown
Member

When inserting a bean with InsertOptions.ON_CONFLICT_NOTHING that has cascade children (OneToMany / exported OneToOne), a unique constraint conflict caused the parent insert to be silently skipped (0 rows), but Ebean still cascaded and attempted to insert the children — resulting in a FK violation.

Changes

• BeanDescriptor.hasCascadeChildren() — returns true when the bean has save-cascade children (OneToMany or exported OneToOne) that hold a FK back to this bean. • PersistRequestBean.setInsertOptions() — when ON_CONFLICT_NOTHING is used and the bean has cascade children, sets skipBatchForTopLevel = true so the parent INSERT executes immediately (non-batched). This ensures the row count is known before any cascade runs. Beans without cascade children are unaffected and continue to batch normally. • PersistRequestBean.checkRowCount() — when the parent INSERT returns 0 rows under ON_CONFLICT_NOTHING, sets insertConflictSkipped = true and returns early, leaving the bean unmarked as loaded/persisted. • DmlHandler.checkRowCount() — skips postExecute() when the insert was conflict-skipped. • DefaultPersister.insert() — guards saveAssocMany() with !request.isInsertConflictSkipped(), preventing cascade saves when the parent was not actually inserted.

When inserting a bean with InsertOptions.ON_CONFLICT_NOTHING that has cascade children (OneToMany / exported OneToOne), a unique constraint conflict caused the parent insert to be silently skipped (0 rows), but Ebean still cascaded and attempted to insert the children — resulting in a FK violation.

Changes

• BeanDescriptor.hasCascadeChildren() — returns true when the bean has save-cascade children (OneToMany or exported OneToOne) that hold a FK back to this bean.
• PersistRequestBean.setInsertOptions() — when ON_CONFLICT_NOTHING is used and the bean has cascade children, sets skipBatchForTopLevel = true so the parent INSERT executes immediately (non-batched). This ensures the row count is known before any cascade runs. Beans without cascade children are unaffected and continue to batch normally.
• PersistRequestBean.checkRowCount() — when the parent INSERT returns 0 rows under ON_CONFLICT_NOTHING, sets insertConflictSkipped = true and returns early, leaving the bean unmarked as loaded/persisted.
• DmlHandler.checkRowCount() — skips postExecute() when the insert was conflict-skipped.
• DefaultPersister.insert() — guards saveAssocMany() with !request.isInsertConflictSkipped(), preventing cascade saves when the parent was not actually inserted.
@rbygrave rbygrave self-assigned this Jul 1, 2026
@rbygrave rbygrave linked an issue Jul 1, 2026 that may be closed by this pull request
@rbygrave rbygrave merged commit 985e3b1 into master Jul 1, 2026
1 check passed
@rbygrave rbygrave added this to the 18.2.0 milestone Jul 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

InsertOptions.ON_CONFLICT_NOTHING with cascade

2 participants