From b9a30a19b4cd8413d190378005015048bcb6c869 Mon Sep 17 00:00:00 2001 From: Clancy Date: Sun, 2 Aug 2026 18:54:46 +0300 Subject: [PATCH 1/2] refactor(create): transition builder type on OnConflict to prevent nested conflict clauses in bulk operations Prevent calling OnConflict on individual child items inside CreateMany / CreateManyAndReturn at compile time. OnConflict on CreateBuilder now transitions the return type to UpsertBuilder, while CreateMany continues to strictly require CreateBuilder items. --- generator/templates/model_create.gotpl | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/generator/templates/model_create.gotpl b/generator/templates/model_create.gotpl index bac9b0f..f34ac19 100644 --- a/generator/templates/model_create.gotpl +++ b/generator/templates/model_create.gotpl @@ -9,6 +9,20 @@ func (s *{{ .Model.Name }}Select) hasAnyRelation() bool { {{- end }} } +type {{ .Model.Name }}UpsertBuilder struct { + *CreateBuilder[{{ .Model.Name }}, {{ .Model.Name }}Select, {{ .Model.Name }}Omit] +} + +func (b *{{ .Model.Name }}UpsertBuilder) Select(s {{ .Model.Name }}Select) *{{ .Model.Name }}UpsertBuilder { + b.selects = &s + return b +} + +func (b *{{ .Model.Name }}UpsertBuilder) Omit(o {{ .Model.Name }}Omit) *{{ .Model.Name }}UpsertBuilder { + b.omits = &o + return b +} + type {{ .Model.Name }}CreateBuilder struct { *CreateBuilder[{{ .Model.Name }}, {{ .Model.Name }}Select, {{ .Model.Name }}Omit] } @@ -23,9 +37,10 @@ func (b *{{ .Model.Name }}CreateBuilder) Omit(o {{ .Model.Name }}Omit) *{{ .Mode return b } -func (b *{{ .Model.Name }}CreateBuilder) OnConflict(target UniqueConstraintTarget) *{{ .Model.Name }}ConflictBuilder[{{ .Model.Name }}CreateBuilder] { - return &{{ .Model.Name }}ConflictBuilder[{{ .Model.Name }}CreateBuilder]{ - builder: b, +func (b *{{ .Model.Name }}CreateBuilder) OnConflict(target UniqueConstraintTarget) *{{ .Model.Name }}ConflictBuilder[{{ .Model.Name }}UpsertBuilder] { + upsertBuilder := &{{ .Model.Name }}UpsertBuilder{CreateBuilder: b.CreateBuilder} + return &{{ .Model.Name }}ConflictBuilder[{{ .Model.Name }}UpsertBuilder]{ + builder: upsertBuilder, conflictTarget: target, setAction: func(action ConflictAction, target UniqueConstraintTarget) { b.conflictAction = &action From fedeedeae9db8d82f9b0ce9de0c3a1bd8b3c93cf Mon Sep 17 00:00:00 2001 From: Clancy Date: Sun, 2 Aug 2026 18:55:21 +0300 Subject: [PATCH 2/2] updated generated client, after updating create builders to prevent conflict on bulk-create's inner builders --- integration/phi/allFieldsSoFar.go | 21 ++++++++++++++++++--- integration/phi/category.go | 21 ++++++++++++++++++--- integration/phi/categoryToPost.go | 21 ++++++++++++++++++--- integration/phi/comment.go | 21 ++++++++++++++++++--- integration/phi/defaultsTest.go | 21 ++++++++++++++++++--- integration/phi/post.go | 21 ++++++++++++++++++--- integration/phi/profile.go | 21 ++++++++++++++++++--- integration/phi/user.go | 21 ++++++++++++++++++--- 8 files changed, 144 insertions(+), 24 deletions(-) diff --git a/integration/phi/allFieldsSoFar.go b/integration/phi/allFieldsSoFar.go index 9b46aff..3e15edb 100644 --- a/integration/phi/allFieldsSoFar.go +++ b/integration/phi/allFieldsSoFar.go @@ -2071,6 +2071,20 @@ func (s *AllFieldsSoFarSelect) hasAnyRelation() bool { return false } +type AllFieldsSoFarUpsertBuilder struct { + *CreateBuilder[AllFieldsSoFar, AllFieldsSoFarSelect, AllFieldsSoFarOmit] +} + +func (b *AllFieldsSoFarUpsertBuilder) Select(s AllFieldsSoFarSelect) *AllFieldsSoFarUpsertBuilder { + b.selects = &s + return b +} + +func (b *AllFieldsSoFarUpsertBuilder) Omit(o AllFieldsSoFarOmit) *AllFieldsSoFarUpsertBuilder { + b.omits = &o + return b +} + type AllFieldsSoFarCreateBuilder struct { *CreateBuilder[AllFieldsSoFar, AllFieldsSoFarSelect, AllFieldsSoFarOmit] } @@ -2085,9 +2099,10 @@ func (b *AllFieldsSoFarCreateBuilder) Omit(o AllFieldsSoFarOmit) *AllFieldsSoFar return b } -func (b *AllFieldsSoFarCreateBuilder) OnConflict(target UniqueConstraintTarget) *AllFieldsSoFarConflictBuilder[AllFieldsSoFarCreateBuilder] { - return &AllFieldsSoFarConflictBuilder[AllFieldsSoFarCreateBuilder]{ - builder: b, +func (b *AllFieldsSoFarCreateBuilder) OnConflict(target UniqueConstraintTarget) *AllFieldsSoFarConflictBuilder[AllFieldsSoFarUpsertBuilder] { + upsertBuilder := &AllFieldsSoFarUpsertBuilder{CreateBuilder: b.CreateBuilder} + return &AllFieldsSoFarConflictBuilder[AllFieldsSoFarUpsertBuilder]{ + builder: upsertBuilder, conflictTarget: target, setAction: func(action ConflictAction, target UniqueConstraintTarget) { b.conflictAction = &action diff --git a/integration/phi/category.go b/integration/phi/category.go index 9863916..9da7dcd 100644 --- a/integration/phi/category.go +++ b/integration/phi/category.go @@ -564,6 +564,20 @@ func (s *CategorySelect) hasAnyRelation() bool { return s.Posts != nil } +type CategoryUpsertBuilder struct { + *CreateBuilder[Category, CategorySelect, CategoryOmit] +} + +func (b *CategoryUpsertBuilder) Select(s CategorySelect) *CategoryUpsertBuilder { + b.selects = &s + return b +} + +func (b *CategoryUpsertBuilder) Omit(o CategoryOmit) *CategoryUpsertBuilder { + b.omits = &o + return b +} + type CategoryCreateBuilder struct { *CreateBuilder[Category, CategorySelect, CategoryOmit] } @@ -578,9 +592,10 @@ func (b *CategoryCreateBuilder) Omit(o CategoryOmit) *CategoryCreateBuilder { return b } -func (b *CategoryCreateBuilder) OnConflict(target UniqueConstraintTarget) *CategoryConflictBuilder[CategoryCreateBuilder] { - return &CategoryConflictBuilder[CategoryCreateBuilder]{ - builder: b, +func (b *CategoryCreateBuilder) OnConflict(target UniqueConstraintTarget) *CategoryConflictBuilder[CategoryUpsertBuilder] { + upsertBuilder := &CategoryUpsertBuilder{CreateBuilder: b.CreateBuilder} + return &CategoryConflictBuilder[CategoryUpsertBuilder]{ + builder: upsertBuilder, conflictTarget: target, setAction: func(action ConflictAction, target UniqueConstraintTarget) { b.conflictAction = &action diff --git a/integration/phi/categoryToPost.go b/integration/phi/categoryToPost.go index 576fac4..7c11863 100644 --- a/integration/phi/categoryToPost.go +++ b/integration/phi/categoryToPost.go @@ -565,6 +565,20 @@ func (s *CategoryToPostSelect) hasAnyRelation() bool { return s.Post != nil || s.Category != nil } +type CategoryToPostUpsertBuilder struct { + *CreateBuilder[CategoryToPost, CategoryToPostSelect, CategoryToPostOmit] +} + +func (b *CategoryToPostUpsertBuilder) Select(s CategoryToPostSelect) *CategoryToPostUpsertBuilder { + b.selects = &s + return b +} + +func (b *CategoryToPostUpsertBuilder) Omit(o CategoryToPostOmit) *CategoryToPostUpsertBuilder { + b.omits = &o + return b +} + type CategoryToPostCreateBuilder struct { *CreateBuilder[CategoryToPost, CategoryToPostSelect, CategoryToPostOmit] } @@ -579,9 +593,10 @@ func (b *CategoryToPostCreateBuilder) Omit(o CategoryToPostOmit) *CategoryToPost return b } -func (b *CategoryToPostCreateBuilder) OnConflict(target UniqueConstraintTarget) *CategoryToPostConflictBuilder[CategoryToPostCreateBuilder] { - return &CategoryToPostConflictBuilder[CategoryToPostCreateBuilder]{ - builder: b, +func (b *CategoryToPostCreateBuilder) OnConflict(target UniqueConstraintTarget) *CategoryToPostConflictBuilder[CategoryToPostUpsertBuilder] { + upsertBuilder := &CategoryToPostUpsertBuilder{CreateBuilder: b.CreateBuilder} + return &CategoryToPostConflictBuilder[CategoryToPostUpsertBuilder]{ + builder: upsertBuilder, conflictTarget: target, setAction: func(action ConflictAction, target UniqueConstraintTarget) { b.conflictAction = &action diff --git a/integration/phi/comment.go b/integration/phi/comment.go index 9f86636..10c5ce8 100644 --- a/integration/phi/comment.go +++ b/integration/phi/comment.go @@ -741,6 +741,20 @@ func (s *CommentSelect) hasAnyRelation() bool { return s.Post != nil || s.Author != nil } +type CommentUpsertBuilder struct { + *CreateBuilder[Comment, CommentSelect, CommentOmit] +} + +func (b *CommentUpsertBuilder) Select(s CommentSelect) *CommentUpsertBuilder { + b.selects = &s + return b +} + +func (b *CommentUpsertBuilder) Omit(o CommentOmit) *CommentUpsertBuilder { + b.omits = &o + return b +} + type CommentCreateBuilder struct { *CreateBuilder[Comment, CommentSelect, CommentOmit] } @@ -755,9 +769,10 @@ func (b *CommentCreateBuilder) Omit(o CommentOmit) *CommentCreateBuilder { return b } -func (b *CommentCreateBuilder) OnConflict(target UniqueConstraintTarget) *CommentConflictBuilder[CommentCreateBuilder] { - return &CommentConflictBuilder[CommentCreateBuilder]{ - builder: b, +func (b *CommentCreateBuilder) OnConflict(target UniqueConstraintTarget) *CommentConflictBuilder[CommentUpsertBuilder] { + upsertBuilder := &CommentUpsertBuilder{CreateBuilder: b.CreateBuilder} + return &CommentConflictBuilder[CommentUpsertBuilder]{ + builder: upsertBuilder, conflictTarget: target, setAction: func(action ConflictAction, target UniqueConstraintTarget) { b.conflictAction = &action diff --git a/integration/phi/defaultsTest.go b/integration/phi/defaultsTest.go index 6498c63..5181374 100644 --- a/integration/phi/defaultsTest.go +++ b/integration/phi/defaultsTest.go @@ -753,6 +753,20 @@ func (s *DefaultsTestSelect) hasAnyRelation() bool { return false } +type DefaultsTestUpsertBuilder struct { + *CreateBuilder[DefaultsTest, DefaultsTestSelect, DefaultsTestOmit] +} + +func (b *DefaultsTestUpsertBuilder) Select(s DefaultsTestSelect) *DefaultsTestUpsertBuilder { + b.selects = &s + return b +} + +func (b *DefaultsTestUpsertBuilder) Omit(o DefaultsTestOmit) *DefaultsTestUpsertBuilder { + b.omits = &o + return b +} + type DefaultsTestCreateBuilder struct { *CreateBuilder[DefaultsTest, DefaultsTestSelect, DefaultsTestOmit] } @@ -767,9 +781,10 @@ func (b *DefaultsTestCreateBuilder) Omit(o DefaultsTestOmit) *DefaultsTestCreate return b } -func (b *DefaultsTestCreateBuilder) OnConflict(target UniqueConstraintTarget) *DefaultsTestConflictBuilder[DefaultsTestCreateBuilder] { - return &DefaultsTestConflictBuilder[DefaultsTestCreateBuilder]{ - builder: b, +func (b *DefaultsTestCreateBuilder) OnConflict(target UniqueConstraintTarget) *DefaultsTestConflictBuilder[DefaultsTestUpsertBuilder] { + upsertBuilder := &DefaultsTestUpsertBuilder{CreateBuilder: b.CreateBuilder} + return &DefaultsTestConflictBuilder[DefaultsTestUpsertBuilder]{ + builder: upsertBuilder, conflictTarget: target, setAction: func(action ConflictAction, target UniqueConstraintTarget) { b.conflictAction = &action diff --git a/integration/phi/post.go b/integration/phi/post.go index 9a807f0..e16b07a 100644 --- a/integration/phi/post.go +++ b/integration/phi/post.go @@ -697,6 +697,20 @@ func (s *PostSelect) hasAnyRelation() bool { return s.Author != nil || s.Comments != nil || s.Categories != nil } +type PostUpsertBuilder struct { + *CreateBuilder[Post, PostSelect, PostOmit] +} + +func (b *PostUpsertBuilder) Select(s PostSelect) *PostUpsertBuilder { + b.selects = &s + return b +} + +func (b *PostUpsertBuilder) Omit(o PostOmit) *PostUpsertBuilder { + b.omits = &o + return b +} + type PostCreateBuilder struct { *CreateBuilder[Post, PostSelect, PostOmit] } @@ -711,9 +725,10 @@ func (b *PostCreateBuilder) Omit(o PostOmit) *PostCreateBuilder { return b } -func (b *PostCreateBuilder) OnConflict(target UniqueConstraintTarget) *PostConflictBuilder[PostCreateBuilder] { - return &PostConflictBuilder[PostCreateBuilder]{ - builder: b, +func (b *PostCreateBuilder) OnConflict(target UniqueConstraintTarget) *PostConflictBuilder[PostUpsertBuilder] { + upsertBuilder := &PostUpsertBuilder{CreateBuilder: b.CreateBuilder} + return &PostConflictBuilder[PostUpsertBuilder]{ + builder: upsertBuilder, conflictTarget: target, setAction: func(action ConflictAction, target UniqueConstraintTarget) { b.conflictAction = &action diff --git a/integration/phi/profile.go b/integration/phi/profile.go index fcbeeb3..aeab89d 100644 --- a/integration/phi/profile.go +++ b/integration/phi/profile.go @@ -623,6 +623,20 @@ func (s *ProfileSelect) hasAnyRelation() bool { return s.User != nil } +type ProfileUpsertBuilder struct { + *CreateBuilder[Profile, ProfileSelect, ProfileOmit] +} + +func (b *ProfileUpsertBuilder) Select(s ProfileSelect) *ProfileUpsertBuilder { + b.selects = &s + return b +} + +func (b *ProfileUpsertBuilder) Omit(o ProfileOmit) *ProfileUpsertBuilder { + b.omits = &o + return b +} + type ProfileCreateBuilder struct { *CreateBuilder[Profile, ProfileSelect, ProfileOmit] } @@ -637,9 +651,10 @@ func (b *ProfileCreateBuilder) Omit(o ProfileOmit) *ProfileCreateBuilder { return b } -func (b *ProfileCreateBuilder) OnConflict(target UniqueConstraintTarget) *ProfileConflictBuilder[ProfileCreateBuilder] { - return &ProfileConflictBuilder[ProfileCreateBuilder]{ - builder: b, +func (b *ProfileCreateBuilder) OnConflict(target UniqueConstraintTarget) *ProfileConflictBuilder[ProfileUpsertBuilder] { + upsertBuilder := &ProfileUpsertBuilder{CreateBuilder: b.CreateBuilder} + return &ProfileConflictBuilder[ProfileUpsertBuilder]{ + builder: upsertBuilder, conflictTarget: target, setAction: func(action ConflictAction, target UniqueConstraintTarget) { b.conflictAction = &action diff --git a/integration/phi/user.go b/integration/phi/user.go index 399674a..3316053 100644 --- a/integration/phi/user.go +++ b/integration/phi/user.go @@ -924,6 +924,20 @@ func (s *UserSelect) hasAnyRelation() bool { return s.Profile != nil || s.Posts != nil || s.Comments != nil || s.ReferredBy != nil || s.Referrals != nil } +type UserUpsertBuilder struct { + *CreateBuilder[User, UserSelect, UserOmit] +} + +func (b *UserUpsertBuilder) Select(s UserSelect) *UserUpsertBuilder { + b.selects = &s + return b +} + +func (b *UserUpsertBuilder) Omit(o UserOmit) *UserUpsertBuilder { + b.omits = &o + return b +} + type UserCreateBuilder struct { *CreateBuilder[User, UserSelect, UserOmit] } @@ -938,9 +952,10 @@ func (b *UserCreateBuilder) Omit(o UserOmit) *UserCreateBuilder { return b } -func (b *UserCreateBuilder) OnConflict(target UniqueConstraintTarget) *UserConflictBuilder[UserCreateBuilder] { - return &UserConflictBuilder[UserCreateBuilder]{ - builder: b, +func (b *UserCreateBuilder) OnConflict(target UniqueConstraintTarget) *UserConflictBuilder[UserUpsertBuilder] { + upsertBuilder := &UserUpsertBuilder{CreateBuilder: b.CreateBuilder} + return &UserConflictBuilder[UserUpsertBuilder]{ + builder: upsertBuilder, conflictTarget: target, setAction: func(action ConflictAction, target UniqueConstraintTarget) { b.conflictAction = &action