From 4698c52d2d630f745a68abb062fa7dbddbdd6aec Mon Sep 17 00:00:00 2001 From: Clancy Date: Wed, 22 Jul 2026 17:14:52 +0300 Subject: [PATCH 1/2] Feat: Implement Delete 1- Update builders_delete template, following the same builder pattern, supporting Select and Omit 2- Update model_delete template declaring the Delete method on model delegates, the underlying executeDelete hook chain router, and runDelete database runner: - Simple delete with no select (defaults to returning all scalar fields) - Delete with scalar fields selection (returns requested scalar fields) - Delete with nested relations selection - Dialect native returning support via SupportsDeleteReturning, pre-querying both scalar fields and relations inside a transaction before deletion when relations are selected or when the dialect lacks RETURNING support using executeFindUnique under the hood as the full implementation is already in place, handling selection with relations and everything, separated from FindUnique to prevent hooks mixing up 3- Add DeleteQuery type and Delete extension hook signatures to the model_structs template 4- Add singular delete integration tests to delete_test.go covering basic deletion, scalar selection/omission, relation loading, 0-row sql.ErrNoRows error handling, and middleware hooks --- generator/templates/builders_delete.gotpl | 21 ++++ generator/templates/model_delete.gotpl | 136 ++++++++++++++++++++ generator/templates/model_structs.gotpl | 2 + integration/delete_test.go | 147 ++++++++++++++++++++++ integration/main.go | 79 ++++++++++++ 5 files changed, 385 insertions(+) diff --git a/generator/templates/builders_delete.gotpl b/generator/templates/builders_delete.gotpl index 8b84bad..135d768 100644 --- a/generator/templates/builders_delete.gotpl +++ b/generator/templates/builders_delete.gotpl @@ -6,3 +6,24 @@ type DeleteManyBuilder[M any] struct { func (b *DeleteManyBuilder[M]) Exec(ctx context.Context) (int64, error) { return b.execFunc(ctx, b.where) } + +type DeleteBuilder[M any, S any, O any] struct { + where UniquePredicate[M] + selects *S + omits *O + execFunc func(ctx context.Context, where UniquePredicate[M], selects *S, omits *O) (*M, error) +} + +func (b *DeleteBuilder[M, S, O]) Select(selects S) *DeleteBuilder[M, S, O] { + b.selects = &selects + return b +} + +func (b *DeleteBuilder[M, S, O]) Omit(omits O) *DeleteBuilder[M, S, O] { + b.omits = &omits + return b +} + +func (b *DeleteBuilder[M, S, O]) Exec(ctx context.Context) (*M, error) { + return b.execFunc(ctx, b.where, b.selects, b.omits) +} diff --git a/generator/templates/model_delete.gotpl b/generator/templates/model_delete.gotpl index 9be68b1..cd9ddf9 100644 --- a/generator/templates/model_delete.gotpl +++ b/generator/templates/model_delete.gotpl @@ -51,3 +51,139 @@ func (d *{{ .Model.Name }}Delegate) runDeleteMany(ctx context.Context, preds []P } return result.RowsAffected() } + +func (d *{{ .Model.Name }}Delegate) Delete(where UniquePredicate[{{ .Model.Name }}]) *DeleteBuilder[{{ .Model.Name }}, {{ .Model.Name }}Select, {{ .Model.Name }}Omit] { + return &DeleteBuilder[{{ .Model.Name }}, {{ .Model.Name }}Select, {{ .Model.Name }}Omit]{ + where: where, + execFunc: d.executeDelete, + } +} + +func (d *{{ .Model.Name }}Delegate) executeDelete(ctx context.Context, where UniquePredicate[{{ .Model.Name }}], selects *{{ .Model.Name }}Select, omits *{{ .Model.Name }}Omit) (*{{ .Model.Name }}, error) { + if len(d.extensions) == 0 { + return d.runDelete(ctx, where, selects, omits) + } + + curr := func(c context.Context, w UniquePredicate[{{ .Model.Name }}], s *{{ .Model.Name }}Select, o *{{ .Model.Name }}Omit) (*{{ .Model.Name }}, error) { + return d.runDelete(c, w, s, o) + } + + for _, ext := range slices.Backward(d.extensions) { + if ext.Delete != nil { + next, hook := curr, ext.Delete + curr = func(c context.Context, w UniquePredicate[{{ .Model.Name }}], s *{{ .Model.Name }}Select, o *{{ .Model.Name }}Omit) (*{{ .Model.Name }}, error) { + return hook(c, w, s, o, next) + } + } + } + + return curr(ctx, where, selects, omits) +} + +func (d *{{ .Model.Name }}Delegate) runDelete(ctx context.Context, where UniquePredicate[{{ .Model.Name }}], selects *{{ .Model.Name }}Select, omits *{{ .Model.Name }}Omit) (*{{ .Model.Name }}, error) { + if err := where.Validate(); err != nil { + return nil, err + } + + returningCols := select{{ .Model.Name }}Cols(selects, omits, {{ lowercase .Model.Name }}PKCols...) + + hasRelations := selects != nil && selects.hasAnyRelation() + useTx := !d.client.dialect.SupportsDeleteReturning || hasRelations + + if useTx { + var res *{{ .Model.Name }} + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + res, err = txQ.{{ .Model.Name }}.executeFindUnique(ctx, where, nil, selects, omits) + if err != nil { + return err + } + if res == nil { + return sql.ErrNoRows + } + + // Build DELETE statement by PK + var deleteSb strings.Builder + deleteSb.WriteString("DELETE FROM ") + txQ.dialect.WriteQuotedIdent(&deleteSb, "{{ .Model.EffectiveTableName }}") + deleteSb.WriteString(" WHERE ") + + var pkPreds []PredicateOf[{{ .Model.Name }}] + {{- if .Model.CompositePK }} + {{- range $fName := .Model.CompositePK }} + {{- $field := $.Model.GetField $fName }} + pkPreds = append(pkPreds, Predicate[{{ $.Model.Name }}]{ + Data: PredicateData{ + Column: "{{ $field.EffectiveColName }}", + Operator: "=", + Value: res.{{ capitalize $field.Name }}, + }, + }) + {{- end }} + {{- else }} + {{- range $field := .Model.ScalarFields }} + {{- if $field.IsID }} + pkPreds = append(pkPreds, Predicate[{{ $.Model.Name }}]{ + Data: PredicateData{ + Column: "{{ $field.EffectiveColName }}", + Operator: "=", + Value: res.{{ capitalize $field.Name }}, + }, + }) + {{- end }} + {{- end }} + {{- end }} + + whereClause, vals := CompilePredicates(txQ.dialect, pkPreds) + deleteSb.WriteString(whereClause) + + _, err = txQ.exec(ctx, deleteSb.String(), vals...) + if err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return res, nil + } + + // Dialect supports RETURNING, and no relations need loading: run direct DELETE ... RETURNING + var sb strings.Builder + sb.WriteString("DELETE FROM ") + d.client.dialect.WriteQuotedIdent(&sb, "{{ .Model.EffectiveTableName }}") + + whereClause, vals := CompilePredicates(d.client.dialect, []PredicateOf[{{ .Model.Name }}]{where}) + if whereClause != "" { + sb.WriteString(" WHERE ") + sb.WriteString(whereClause) + } + + sb.WriteString(" RETURNING ") + for i, col := range returningCols { + if i > 0 { + sb.WriteString(", ") + } + d.client.dialect.WriteQuotedIdent(&sb, col) + } + + rows, err := d.client.query(ctx, sb.String(), vals...) + if err != nil { + return nil, err + } + defer rows.Close() + + if !rows.Next() { + if err := rows.Err(); err != nil { + return nil, err + } + return nil, sql.ErrNoRows + } + + var row {{ .Model.Name }} + if err := rows.Scan(row.ScanFields(returningCols)...); err != nil { + return nil, err + } + return &row, nil +} diff --git a/generator/templates/model_structs.gotpl b/generator/templates/model_structs.gotpl index 1503bde..77cdccd 100644 --- a/generator/templates/model_structs.gotpl +++ b/generator/templates/model_structs.gotpl @@ -128,6 +128,7 @@ type {{ .Model.Name }}FindUniqueQuery = func(ctx context.Context, where UniquePr type {{ .Model.Name }}FindFirstQuery = func(ctx context.Context, params QueryParams[{{ .Model.Name }}], selects *{{ .Model.Name }}Select, omits *{{ .Model.Name }}Omit) (*{{ .Model.Name }}, error) type {{ .Model.Name }}FindManyQuery = func(ctx context.Context, params QueryParams[{{ .Model.Name }}], selects *{{ .Model.Name }}Select, omits *{{ .Model.Name }}Omit) ([]*{{ .Model.Name }}, error) type {{ .Model.Name }}DeleteManyQuery = func(ctx context.Context, preds []PredicateOf[{{ .Model.Name }}]) (int64, error) +type {{ .Model.Name }}DeleteQuery = func(ctx context.Context, where UniquePredicate[{{ .Model.Name }}], selects *{{ .Model.Name }}Select, omits *{{ .Model.Name }}Omit) (*{{ .Model.Name }}, error) type {{ .Model.Name }}CountQuery = func(ctx context.Context, params QueryParams[{{ .Model.Name }}]) (int64, error) type {{ .Model.Name }}Extension struct { @@ -138,6 +139,7 @@ type {{ .Model.Name }}Extension struct { FindFirst func(ctx context.Context, params QueryParams[{{ .Model.Name }}], selects *{{ .Model.Name }}Select, omits *{{ .Model.Name }}Omit, next {{ .Model.Name }}FindFirstQuery) (*{{ .Model.Name }}, error) FindMany func(ctx context.Context, params QueryParams[{{ .Model.Name }}], selects *{{ .Model.Name }}Select, omits *{{ .Model.Name }}Omit, next {{ .Model.Name }}FindManyQuery) ([]*{{ .Model.Name }}, error) DeleteMany func(ctx context.Context, preds []PredicateOf[{{ .Model.Name }}], next {{ .Model.Name }}DeleteManyQuery) (int64, error) + Delete func(ctx context.Context, where UniquePredicate[{{ .Model.Name }}], selects *{{ .Model.Name }}Select, omits *{{ .Model.Name }}Omit, next {{ .Model.Name }}DeleteQuery) (*{{ .Model.Name }}, error) Count func(ctx context.Context, params QueryParams[{{ .Model.Name }}], next {{ .Model.Name }}CountQuery) (int64, error) } diff --git a/integration/delete_test.go b/integration/delete_test.go index 38683ef..fd4d7b6 100644 --- a/integration/delete_test.go +++ b/integration/delete_test.go @@ -78,3 +78,150 @@ func TestDeleteMany_NoMatches(t *testing.T) { t.Errorf("expected 0 deleted users, got %d", count) } } + +func TestDeleteBasic(t *testing.T) { + db, cleanup := setupTestDB(t) + defer cleanup() + ctx := context.Background() + + u, err := db.User.Create(). + SetEmail("todelete@example.com"). + SetPhoneNum("+123456"). + SetRole(valk.UserRole.Student). + Exec(ctx) + if err != nil { + t.Fatalf("failed to create user: %v", err) + } + + deleted, err := db.User.Delete(user.Email.EQ("todelete@example.com")).Exec(ctx) + if err != nil { + t.Fatalf("failed to delete user: %v", err) + } + + if deleted.Email != "todelete@example.com" { + t.Errorf("expected deleted user email to be todelete@example.com, got %s", deleted.Email) + } + if deleted.Id != u.Id { + t.Errorf("expected deleted user id to be %s, got %s", u.Id, deleted.Id) + } + + var count int + err = db.Raw().QueryRowContext(ctx, `SELECT COUNT(*) FROM "User" WHERE email = 'todelete@example.com'`).Scan(&count) + if err != nil { + t.Fatalf("failed to query count: %v", err) + } + if count != 0 { + t.Errorf("expected user to be completely deleted from DB, got count %d", count) + } +} + +func TestDeleteSelectOmit(t *testing.T) { + db, cleanup := setupTestDB(t) + defer cleanup() + ctx := context.Background() + + _, err := db.User.Create(). + SetEmail("selectdelete@example.com"). + SetPhoneNum("+55555"). + SetRole(valk.UserRole.Student). + Exec(ctx) + if err != nil { + t.Fatalf("failed to create user: %v", err) + } + + deleted, err := db.User.Delete(user.Email.EQ("selectdelete@example.com")). + Select(valk.UserSelect{Email: true}). + Exec(ctx) + if err != nil { + t.Fatalf("failed to delete: %v", err) + } + + if deleted.Email != "selectdelete@example.com" { + t.Errorf("expected email to be populated, got %s", deleted.Email) + } + if deleted.PhoneNum != "" { + t.Errorf("expected phoneNum to be omitted (empty), got %s", deleted.PhoneNum) + } +} + +func TestDeleteWithRelations(t *testing.T) { + db, cleanup := setupTestDB(t) + defer cleanup() + ctx := context.Background() + + u, err := db.User.Create(). + SetEmail("reldelete@example.com"). + SetPhoneNum("+777"). + Exec(ctx) + if err != nil { + t.Fatalf("failed to create user: %v", err) + } + + _, err = db.Profile.Create(). + SetBio("My bio"). + SetUserId(u.Id). + Exec(ctx) + if err != nil { + t.Fatalf("failed to create profile: %v", err) + } + + deleted, err := db.User.Delete(user.Id.EQ(u.Id)). + Select(valk.UserSelect{ + Email: true, + Profile: &valk.ProfileSelect{ + Bio: true, + }, + }). + Exec(ctx) + if err != nil { + t.Fatalf("failed to delete user: %v", err) + } + + if deleted.Profile == nil { + t.Errorf("expected related profile to be loaded, got nil") + } else if deleted.Profile.Bio == nil || *deleted.Profile.Bio != "My bio" { + t.Errorf("expected loaded profile bio to be 'My bio', got %v", deleted.Profile.Bio) + } +} + +func TestDeleteNotFound(t *testing.T) { + db, cleanup := setupTestDB(t) + defer cleanup() + ctx := context.Background() + + _, err := db.User.Delete(user.Email.EQ("notfound@example.com")).Exec(ctx) + if err == nil { + t.Fatal("expected error when deleting non-existent row, got nil") + } +} + +func TestDeleteHooks(t *testing.T) { + db, cleanup := setupTestDB(t) + defer cleanup() + ctx := context.Background() + + hookCalled := false + db.User.Use(user.Extension{ + Delete: func(ctx context.Context, where valk.UniquePredicate[valk.User], selects *valk.UserSelect, omits *valk.UserOmit, next valk.UserDeleteQuery) (*valk.User, error) { + hookCalled = true + return next(ctx, where, selects, omits) + }, + }) + + _, err := db.User.Create(). + SetEmail("hookdelete@example.com"). + SetPhoneNum("+999"). + Exec(ctx) + if err != nil { + t.Fatalf("failed to create: %v", err) + } + + _, err = db.User.Delete(user.Email.EQ("hookdelete@example.com")).Exec(ctx) + if err != nil { + t.Fatalf("failed to delete: %v", err) + } + + if !hookCalled { + t.Errorf("expected delete hook to be called, but it wasn't") + } +} diff --git a/integration/main.go b/integration/main.go index ad81c0f..abaf37e 100644 --- a/integration/main.go +++ b/integration/main.go @@ -6,6 +6,7 @@ import ( "fmt" "integration/valk" "integration/valk/post" + "integration/valk/profile" "integration/valk/user" "os" @@ -174,6 +175,84 @@ func main() { Exec(ctx) fmt.Printf("COUNT %d USERS \n", usersCnt) + // --- DELETE SCENARIO 1: Simple Delete (No Select -> Returns All Scalar Fields) --- + delUser1, err := db.User.Create(). + SetEmail("del1@example.com"). + SetPhoneNum("+10001"). + Exec(ctx) + if err != nil { + log.Fatalf("failed to create user 1: %v", err) + } + deleted1, err := db.User.Delete(user.Id.EQ(delUser1.Id)).Exec(ctx) + if err != nil { + log.Fatalf("failed to delete user 1: %v", err) + } + fmt.Println("DELETE SCENARIO 1 (Simple Delete - All Scalars):") + printJSON(deleted1) + + // --- DELETE SCENARIO 2: Delete with Scalar Field Selection --- + delUser2, err := db.User.Create(). + SetEmail("del2@example.com"). + SetPhoneNum("+10002"). + Exec(ctx) + if err != nil { + log.Fatalf("failed to create user 2: %v", err) + } + + deleted2, err := db.User.Delete(user.Id.EQ(delUser2.Id)). + Select(valk.UserSelect{ + Email: true, + Role: true, + }). + Exec(ctx) + if err != nil { + log.Fatalf("failed to delete user 2: %v", err) + } + fmt.Println("DELETE SCENARIO 2 (Scalar Field Selection):") + printJSON(deleted2) + + // --- DELETE SCENARIO 3: Delete with Nested Relations Selection --- + delUser3, err := db.User.Create(). + SetEmail("del3@example.com"). + SetPhoneNum("+10003"). + Exec(ctx) + if err != nil { + log.Fatalf("failed to create user 3: %v", err) + } + + _, err = db.Profile.Create(). + SetBio("Bio of del3"). + SetUserId(delUser3.Id). + Exec(ctx) + if err != nil { + log.Fatalf("failed to create profile for user 3: %v", err) + } + + _, err = db.Post.Create(). + SetTitle("First Post of del3"). + SetAuthorId(delUser3.Id). + Exec(ctx) + if err != nil { + log.Fatalf("failed to create post for user 3: %v", err) + } + + deleted3, err := db.User.Delete(user.Id.EQ(delUser3.Id)). + Select(valk.UserSelect{ + Email: true, + Profile: &profile.Select{ + Bio: true, + }, + Posts: post.Query().Select(post.Select{ + Title: true, + }), + }). + Exec(ctx) + if err != nil { + log.Fatalf("failed to delete user 3: %v", err) + } + fmt.Println("DELETE SCENARIO 3 (Nested Relations Selection):") + printJSON(deleted3) + } // func seed(db *valk.DB, ctx context.Context) *SeedData { From 57a27887f94e684763b50338d956e48ee36b3f9c Mon Sep 17 00:00:00 2001 From: Clancy Date: Wed, 22 Jul 2026 17:17:51 +0300 Subject: [PATCH 2/2] updated generated client after implementing delete --- integration/valk/allFieldsSoFar.go | 184 +++++++++++++++++++++++---- integration/valk/category.go | 184 +++++++++++++++++++++++---- integration/valk/categoryToPost.go | 191 +++++++++++++++++++++++++---- integration/valk/client.go | 71 +++++++---- integration/valk/comment.go | 184 +++++++++++++++++++++++---- integration/valk/defaultsTest.go | 184 +++++++++++++++++++++++---- integration/valk/post.go | 184 +++++++++++++++++++++++---- integration/valk/profile.go | 184 +++++++++++++++++++++++---- integration/valk/user.go | 184 +++++++++++++++++++++++---- 9 files changed, 1327 insertions(+), 223 deletions(-) diff --git a/integration/valk/allFieldsSoFar.go b/integration/valk/allFieldsSoFar.go index a8d881a..d185a79 100644 --- a/integration/valk/allFieldsSoFar.go +++ b/integration/valk/allFieldsSoFar.go @@ -396,6 +396,7 @@ type AllFieldsSoFarFindUniqueQuery = func(ctx context.Context, where UniquePredi type AllFieldsSoFarFindFirstQuery = func(ctx context.Context, params QueryParams[AllFieldsSoFar], selects *AllFieldsSoFarSelect, omits *AllFieldsSoFarOmit) (*AllFieldsSoFar, error) type AllFieldsSoFarFindManyQuery = func(ctx context.Context, params QueryParams[AllFieldsSoFar], selects *AllFieldsSoFarSelect, omits *AllFieldsSoFarOmit) ([]*AllFieldsSoFar, error) type AllFieldsSoFarDeleteManyQuery = func(ctx context.Context, preds []PredicateOf[AllFieldsSoFar]) (int64, error) +type AllFieldsSoFarDeleteQuery = func(ctx context.Context, where UniquePredicate[AllFieldsSoFar], selects *AllFieldsSoFarSelect, omits *AllFieldsSoFarOmit) (*AllFieldsSoFar, error) type AllFieldsSoFarCountQuery = func(ctx context.Context, params QueryParams[AllFieldsSoFar]) (int64, error) type AllFieldsSoFarExtension struct { @@ -406,6 +407,7 @@ type AllFieldsSoFarExtension struct { FindFirst func(ctx context.Context, params QueryParams[AllFieldsSoFar], selects *AllFieldsSoFarSelect, omits *AllFieldsSoFarOmit, next AllFieldsSoFarFindFirstQuery) (*AllFieldsSoFar, error) FindMany func(ctx context.Context, params QueryParams[AllFieldsSoFar], selects *AllFieldsSoFarSelect, omits *AllFieldsSoFarOmit, next AllFieldsSoFarFindManyQuery) ([]*AllFieldsSoFar, error) DeleteMany func(ctx context.Context, preds []PredicateOf[AllFieldsSoFar], next AllFieldsSoFarDeleteManyQuery) (int64, error) + Delete func(ctx context.Context, where UniquePredicate[AllFieldsSoFar], selects *AllFieldsSoFarSelect, omits *AllFieldsSoFarOmit, next AllFieldsSoFarDeleteQuery) (*AllFieldsSoFar, error) Count func(ctx context.Context, params QueryParams[AllFieldsSoFar], next AllFieldsSoFarCountQuery) (int64, error) } @@ -1711,7 +1713,6 @@ func (d *AllFieldsSoFarDelegate) executeCreate(ctx context.Context, assignments cols, vals := input.ToColsVals() returningCols := selectAllFieldsSoFarCols(selects, omits) - pkCols := allFieldsSoFarPKCols if len(d.extensions) == 0 { hasRelations := selects.hasAnyRelation() @@ -1719,7 +1720,7 @@ func (d *AllFieldsSoFarDelegate) executeCreate(ctx context.Context, assignments var res *AllFieldsSoFar err = d.client.transaction(ctx, func(txQ *Queries) error { var err error - res, err = txQ.AllFieldsSoFar.runCreate(ctx, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = txQ.AllFieldsSoFar.runCreate(ctx, cols, vals, returningCols, allFieldsSoFarPKCols, conflictTarget, conflictAction) if err != nil { return err } @@ -1727,13 +1728,12 @@ func (d *AllFieldsSoFarDelegate) executeCreate(ctx context.Context, assignments }) return res, err } - return d.runCreate(ctx, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + return d.runCreate(ctx, cols, vals, returningCols, allFieldsSoFarPKCols, conflictTarget, conflictAction) } curr := func(c context.Context, args *AllFieldsSoFarCreate) (*AllFieldsSoFar, error) { cols, vals := args.ToColsVals() returningCols := selectAllFieldsSoFarCols(selects, omits) - pkCols := allFieldsSoFarPKCols hasRelations := selects.hasAnyRelation() var res *AllFieldsSoFar @@ -1741,14 +1741,14 @@ func (d *AllFieldsSoFarDelegate) executeCreate(ctx context.Context, assignments if hasRelations { err = d.client.transaction(c, func(txQ *Queries) error { var err error - res, err = txQ.AllFieldsSoFar.runCreate(c, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = txQ.AllFieldsSoFar.runCreate(c, cols, vals, returningCols, allFieldsSoFarPKCols, conflictTarget, conflictAction) if err != nil { return err } return txQ.AllFieldsSoFar.loadRelations(c, []*AllFieldsSoFar{res}, selects) }) } else { - res, err = d.runCreate(c, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = d.runCreate(c, cols, vals, returningCols, allFieldsSoFarPKCols, conflictTarget, conflictAction) } if err != nil { return nil, err @@ -1925,7 +1925,7 @@ func (d *AllFieldsSoFarDelegate) runCreate( } var res AllFieldsSoFar - if d.client.dialect.SupportsReturning { + if d.client.dialect.SupportsInsertReturning { rows, err := d.client.query(ctx, query, vals...) if err != nil { return nil, err @@ -2279,9 +2279,8 @@ func (d *AllFieldsSoFarDelegate) runCreateMany(ctx context.Context, inputs []*Al conflictCols = conflictTarget.UniqueColumns() } var nonConflictCols []string - pkCols := allFieldsSoFarPKCols if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + nonConflictCols = computeNonConflictCols(cols, conflictCols, allFieldsSoFarPKCols) } clause, clauseArgs := d.client.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) queryStr += clause @@ -2326,15 +2325,14 @@ func (d *AllFieldsSoFarDelegate) runCreateManyAndReturn( conflictCols = conflictTarget.UniqueColumns() } var nonConflictCols []string - pkCols := allFieldsSoFarPKCols if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + nonConflictCols = computeNonConflictCols(cols, conflictCols, allFieldsSoFarPKCols) } clause, clauseArgs := txQ.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) queryStr += clause vals = append(vals, clauseArgs...) - if txQ.dialect.SupportsReturning && len(returningCols) > 0 { + if txQ.dialect.SupportsInsertReturning && len(returningCols) > 0 { var retSb strings.Builder retSb.Grow(12 + len(returningCols)*15) retSb.WriteString(" RETURNING ") @@ -2388,11 +2386,11 @@ func (d *AllFieldsSoFarDelegate) runCreateManyAndReturn( selectSb.WriteString(" FROM ") txQ.dialect.WriteQuotedIdent(&selectSb, "AllFieldsSoFar") selectSb.WriteString(" WHERE ") - txQ.dialect.WriteQuotedIdent(&selectSb, pkCols[0]) + txQ.dialect.WriteQuotedIdent(&selectSb, allFieldsSoFarPKCols[0]) selectSb.WriteString(" >= ") txQ.dialect.WritePlaceholder(&selectSb, 1) selectSb.WriteString(" AND ") - txQ.dialect.WriteQuotedIdent(&selectSb, pkCols[0]) + txQ.dialect.WriteQuotedIdent(&selectSb, allFieldsSoFarPKCols[0]) selectSb.WriteString(" < ") txQ.dialect.WritePlaceholder(&selectSb, 2) @@ -2413,7 +2411,7 @@ func (d *AllFieldsSoFarDelegate) runCreateManyAndReturn( } // Always wrap in transaction if we have multiple batches OR if we need to load relations - if len(batches) > 1 || hasRelations || !d.client.dialect.SupportsReturning { + if len(batches) > 1 || hasRelations || !d.client.dialect.SupportsInsertReturning { err := d.client.transaction(ctx, func(txQ *Queries) error { for _, batch := range batches { if err := runBatch(txQ, batch); err != nil { @@ -2854,13 +2852,27 @@ func (d *AllFieldsSoFarDelegate) runFindMany( func (d *AllFieldsSoFarDelegate) queryOne(ctx context.Context, whereClause string, whereVals []any, returningCols []string, skip *int) (*AllFieldsSoFar, error) { limitOne := 1 query := buildSelectSQL(d.client, "AllFieldsSoFar", returningCols, whereClause, &limitOne, skip) - stmt, err := d.client.prepare(ctx, query) + rows, err := d.client.query(ctx, query, whereVals...) if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } return nil, err } - row := stmt.QueryRowContext(ctx, whereVals...) + defer rows.Close() + + if !rows.Next() { + if err := rows.Err(); err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + return nil, nil + } + var res AllFieldsSoFar - if err := row.Scan(res.ScanFields(returningCols)...); err != nil { + if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { if err == sql.ErrNoRows { return nil, nil } @@ -2871,11 +2883,7 @@ func (d *AllFieldsSoFarDelegate) queryOne(ctx context.Context, whereClause strin func (d *AllFieldsSoFarDelegate) queryMany(ctx context.Context, whereClause string, whereVals []any, returningCols []string, take *int, skip *int) ([]*AllFieldsSoFar, error) { query := buildSelectSQL(d.client, "AllFieldsSoFar", returningCols, whereClause, take, skip) - stmt, err := d.client.prepare(ctx, query) - if err != nil { - return nil, err - } - rows, err := stmt.QueryContext(ctx, whereVals...) + rows, err := d.client.query(ctx, query, whereVals...) if err != nil { return nil, err } @@ -2947,6 +2955,125 @@ func (d *AllFieldsSoFarDelegate) runDeleteMany(ctx context.Context, preds []Pred } return result.RowsAffected() } + +func (d *AllFieldsSoFarDelegate) Delete(where UniquePredicate[AllFieldsSoFar]) *DeleteBuilder[AllFieldsSoFar, AllFieldsSoFarSelect, AllFieldsSoFarOmit] { + return &DeleteBuilder[AllFieldsSoFar, AllFieldsSoFarSelect, AllFieldsSoFarOmit]{ + where: where, + execFunc: d.executeDelete, + } +} + +func (d *AllFieldsSoFarDelegate) executeDelete(ctx context.Context, where UniquePredicate[AllFieldsSoFar], selects *AllFieldsSoFarSelect, omits *AllFieldsSoFarOmit) (*AllFieldsSoFar, error) { + if len(d.extensions) == 0 { + return d.runDelete(ctx, where, selects, omits) + } + + curr := func(c context.Context, w UniquePredicate[AllFieldsSoFar], s *AllFieldsSoFarSelect, o *AllFieldsSoFarOmit) (*AllFieldsSoFar, error) { + return d.runDelete(c, w, s, o) + } + + for _, ext := range slices.Backward(d.extensions) { + if ext.Delete != nil { + next, hook := curr, ext.Delete + curr = func(c context.Context, w UniquePredicate[AllFieldsSoFar], s *AllFieldsSoFarSelect, o *AllFieldsSoFarOmit) (*AllFieldsSoFar, error) { + return hook(c, w, s, o, next) + } + } + } + + return curr(ctx, where, selects, omits) +} + +func (d *AllFieldsSoFarDelegate) runDelete(ctx context.Context, where UniquePredicate[AllFieldsSoFar], selects *AllFieldsSoFarSelect, omits *AllFieldsSoFarOmit) (*AllFieldsSoFar, error) { + if err := where.Validate(); err != nil { + return nil, err + } + + returningCols := selectAllFieldsSoFarCols(selects, omits, allFieldsSoFarPKCols...) + + hasRelations := selects != nil && selects.hasAnyRelation() + useTx := !d.client.dialect.SupportsDeleteReturning || hasRelations + + if useTx { + var res *AllFieldsSoFar + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + res, err = txQ.AllFieldsSoFar.executeFindUnique(ctx, where, nil, selects, omits) + if err != nil { + return err + } + if res == nil { + return sql.ErrNoRows + } + + // Build DELETE statement by PK + var deleteSb strings.Builder + deleteSb.WriteString("DELETE FROM ") + txQ.dialect.WriteQuotedIdent(&deleteSb, "AllFieldsSoFar") + deleteSb.WriteString(" WHERE ") + + var pkPreds []PredicateOf[AllFieldsSoFar] + pkPreds = append(pkPreds, Predicate[AllFieldsSoFar]{ + Data: PredicateData{ + Column: "id", + Operator: "=", + Value: res.Id, + }, + }) + + whereClause, vals := CompilePredicates(txQ.dialect, pkPreds) + deleteSb.WriteString(whereClause) + + _, err = txQ.exec(ctx, deleteSb.String(), vals...) + if err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return res, nil + } + + // Dialect supports RETURNING, and no relations need loading: run direct DELETE ... RETURNING + var sb strings.Builder + sb.WriteString("DELETE FROM ") + d.client.dialect.WriteQuotedIdent(&sb, "AllFieldsSoFar") + + whereClause, vals := CompilePredicates(d.client.dialect, []PredicateOf[AllFieldsSoFar]{where}) + if whereClause != "" { + sb.WriteString(" WHERE ") + sb.WriteString(whereClause) + } + + sb.WriteString(" RETURNING ") + for i, col := range returningCols { + if i > 0 { + sb.WriteString(", ") + } + d.client.dialect.WriteQuotedIdent(&sb, col) + } + + rows, err := d.client.query(ctx, sb.String(), vals...) + if err != nil { + return nil, err + } + defer rows.Close() + + if !rows.Next() { + if err := rows.Err(); err != nil { + return nil, err + } + return nil, sql.ErrNoRows + } + + var row AllFieldsSoFar + if err := rows.Scan(row.ScanFields(returningCols)...); err != nil { + return nil, err + } + return &row, nil +} func (d *AllFieldsSoFarDelegate) Count(preds ...PredicateOf[AllFieldsSoFar]) *CountBuilder[AllFieldsSoFar] { return &CountBuilder[AllFieldsSoFar]{ where: preds, @@ -3009,12 +3136,19 @@ func (d *AllFieldsSoFarDelegate) runCount(ctx context.Context, params QueryParam query = sb.String() } - stmt, err := d.client.prepare(ctx, query) + rows, err := d.client.query(ctx, query, vals...) if err != nil { return 0, err } + defer rows.Close() + var count int64 - if err := stmt.QueryRowContext(ctx, vals...).Scan(&count); err != nil { + if rows.Next() { + if err := rows.Scan(&count); err != nil { + return 0, err + } + } + if err := rows.Err(); err != nil { return 0, err } return count, nil diff --git a/integration/valk/category.go b/integration/valk/category.go index d8d615a..9998086 100644 --- a/integration/valk/category.go +++ b/integration/valk/category.go @@ -108,6 +108,7 @@ type CategoryFindUniqueQuery = func(ctx context.Context, where UniquePredicate[C type CategoryFindFirstQuery = func(ctx context.Context, params QueryParams[Category], selects *CategorySelect, omits *CategoryOmit) (*Category, error) type CategoryFindManyQuery = func(ctx context.Context, params QueryParams[Category], selects *CategorySelect, omits *CategoryOmit) ([]*Category, error) type CategoryDeleteManyQuery = func(ctx context.Context, preds []PredicateOf[Category]) (int64, error) +type CategoryDeleteQuery = func(ctx context.Context, where UniquePredicate[Category], selects *CategorySelect, omits *CategoryOmit) (*Category, error) type CategoryCountQuery = func(ctx context.Context, params QueryParams[Category]) (int64, error) type CategoryExtension struct { @@ -118,6 +119,7 @@ type CategoryExtension struct { FindFirst func(ctx context.Context, params QueryParams[Category], selects *CategorySelect, omits *CategoryOmit, next CategoryFindFirstQuery) (*Category, error) FindMany func(ctx context.Context, params QueryParams[Category], selects *CategorySelect, omits *CategoryOmit, next CategoryFindManyQuery) ([]*Category, error) DeleteMany func(ctx context.Context, preds []PredicateOf[Category], next CategoryDeleteManyQuery) (int64, error) + Delete func(ctx context.Context, where UniquePredicate[Category], selects *CategorySelect, omits *CategoryOmit, next CategoryDeleteQuery) (*Category, error) Count func(ctx context.Context, params QueryParams[Category], next CategoryCountQuery) (int64, error) } @@ -304,7 +306,6 @@ func (d *CategoryDelegate) executeCreate(ctx context.Context, assignments []Fiel cols, vals := input.ToColsVals() returningCols := selectCategoryCols(selects, omits) - pkCols := categoryPKCols if len(d.extensions) == 0 { hasRelations := selects.hasAnyRelation() @@ -312,7 +313,7 @@ func (d *CategoryDelegate) executeCreate(ctx context.Context, assignments []Fiel var res *Category err = d.client.transaction(ctx, func(txQ *Queries) error { var err error - res, err = txQ.Category.runCreate(ctx, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = txQ.Category.runCreate(ctx, cols, vals, returningCols, categoryPKCols, conflictTarget, conflictAction) if err != nil { return err } @@ -320,13 +321,12 @@ func (d *CategoryDelegate) executeCreate(ctx context.Context, assignments []Fiel }) return res, err } - return d.runCreate(ctx, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + return d.runCreate(ctx, cols, vals, returningCols, categoryPKCols, conflictTarget, conflictAction) } curr := func(c context.Context, args *CategoryCreate) (*Category, error) { cols, vals := args.ToColsVals() returningCols := selectCategoryCols(selects, omits) - pkCols := categoryPKCols hasRelations := selects.hasAnyRelation() var res *Category @@ -334,14 +334,14 @@ func (d *CategoryDelegate) executeCreate(ctx context.Context, assignments []Fiel if hasRelations { err = d.client.transaction(c, func(txQ *Queries) error { var err error - res, err = txQ.Category.runCreate(c, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = txQ.Category.runCreate(c, cols, vals, returningCols, categoryPKCols, conflictTarget, conflictAction) if err != nil { return err } return txQ.Category.loadRelations(c, []*Category{res}, selects) }) } else { - res, err = d.runCreate(c, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = d.runCreate(c, cols, vals, returningCols, categoryPKCols, conflictTarget, conflictAction) } if err != nil { return nil, err @@ -518,7 +518,7 @@ func (d *CategoryDelegate) runCreate( } var res Category - if d.client.dialect.SupportsReturning { + if d.client.dialect.SupportsInsertReturning { rows, err := d.client.query(ctx, query, vals...) if err != nil { return nil, err @@ -676,9 +676,8 @@ func (d *CategoryDelegate) runCreateMany(ctx context.Context, inputs []*Category conflictCols = conflictTarget.UniqueColumns() } var nonConflictCols []string - pkCols := categoryPKCols if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + nonConflictCols = computeNonConflictCols(cols, conflictCols, categoryPKCols) } clause, clauseArgs := d.client.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) queryStr += clause @@ -723,15 +722,14 @@ func (d *CategoryDelegate) runCreateManyAndReturn( conflictCols = conflictTarget.UniqueColumns() } var nonConflictCols []string - pkCols := categoryPKCols if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + nonConflictCols = computeNonConflictCols(cols, conflictCols, categoryPKCols) } clause, clauseArgs := txQ.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) queryStr += clause vals = append(vals, clauseArgs...) - if txQ.dialect.SupportsReturning && len(returningCols) > 0 { + if txQ.dialect.SupportsInsertReturning && len(returningCols) > 0 { var retSb strings.Builder retSb.Grow(12 + len(returningCols)*15) retSb.WriteString(" RETURNING ") @@ -785,11 +783,11 @@ func (d *CategoryDelegate) runCreateManyAndReturn( selectSb.WriteString(" FROM ") txQ.dialect.WriteQuotedIdent(&selectSb, "Category") selectSb.WriteString(" WHERE ") - txQ.dialect.WriteQuotedIdent(&selectSb, pkCols[0]) + txQ.dialect.WriteQuotedIdent(&selectSb, categoryPKCols[0]) selectSb.WriteString(" >= ") txQ.dialect.WritePlaceholder(&selectSb, 1) selectSb.WriteString(" AND ") - txQ.dialect.WriteQuotedIdent(&selectSb, pkCols[0]) + txQ.dialect.WriteQuotedIdent(&selectSb, categoryPKCols[0]) selectSb.WriteString(" < ") txQ.dialect.WritePlaceholder(&selectSb, 2) @@ -810,7 +808,7 @@ func (d *CategoryDelegate) runCreateManyAndReturn( } // Always wrap in transaction if we have multiple batches OR if we need to load relations - if len(batches) > 1 || hasRelations || !d.client.dialect.SupportsReturning { + if len(batches) > 1 || hasRelations || !d.client.dialect.SupportsInsertReturning { err := d.client.transaction(ctx, func(txQ *Queries) error { for _, batch := range batches { if err := runBatch(txQ, batch); err != nil { @@ -1099,13 +1097,27 @@ func (d *CategoryDelegate) runFindMany( func (d *CategoryDelegate) queryOne(ctx context.Context, whereClause string, whereVals []any, returningCols []string, skip *int) (*Category, error) { limitOne := 1 query := buildSelectSQL(d.client, "Category", returningCols, whereClause, &limitOne, skip) - stmt, err := d.client.prepare(ctx, query) + rows, err := d.client.query(ctx, query, whereVals...) if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } return nil, err } - row := stmt.QueryRowContext(ctx, whereVals...) + defer rows.Close() + + if !rows.Next() { + if err := rows.Err(); err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + return nil, nil + } + var res Category - if err := row.Scan(res.ScanFields(returningCols)...); err != nil { + if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { if err == sql.ErrNoRows { return nil, nil } @@ -1116,11 +1128,7 @@ func (d *CategoryDelegate) queryOne(ctx context.Context, whereClause string, whe func (d *CategoryDelegate) queryMany(ctx context.Context, whereClause string, whereVals []any, returningCols []string, take *int, skip *int) ([]*Category, error) { query := buildSelectSQL(d.client, "Category", returningCols, whereClause, take, skip) - stmt, err := d.client.prepare(ctx, query) - if err != nil { - return nil, err - } - rows, err := stmt.QueryContext(ctx, whereVals...) + rows, err := d.client.query(ctx, query, whereVals...) if err != nil { return nil, err } @@ -1192,6 +1200,125 @@ func (d *CategoryDelegate) runDeleteMany(ctx context.Context, preds []PredicateO } return result.RowsAffected() } + +func (d *CategoryDelegate) Delete(where UniquePredicate[Category]) *DeleteBuilder[Category, CategorySelect, CategoryOmit] { + return &DeleteBuilder[Category, CategorySelect, CategoryOmit]{ + where: where, + execFunc: d.executeDelete, + } +} + +func (d *CategoryDelegate) executeDelete(ctx context.Context, where UniquePredicate[Category], selects *CategorySelect, omits *CategoryOmit) (*Category, error) { + if len(d.extensions) == 0 { + return d.runDelete(ctx, where, selects, omits) + } + + curr := func(c context.Context, w UniquePredicate[Category], s *CategorySelect, o *CategoryOmit) (*Category, error) { + return d.runDelete(c, w, s, o) + } + + for _, ext := range slices.Backward(d.extensions) { + if ext.Delete != nil { + next, hook := curr, ext.Delete + curr = func(c context.Context, w UniquePredicate[Category], s *CategorySelect, o *CategoryOmit) (*Category, error) { + return hook(c, w, s, o, next) + } + } + } + + return curr(ctx, where, selects, omits) +} + +func (d *CategoryDelegate) runDelete(ctx context.Context, where UniquePredicate[Category], selects *CategorySelect, omits *CategoryOmit) (*Category, error) { + if err := where.Validate(); err != nil { + return nil, err + } + + returningCols := selectCategoryCols(selects, omits, categoryPKCols...) + + hasRelations := selects != nil && selects.hasAnyRelation() + useTx := !d.client.dialect.SupportsDeleteReturning || hasRelations + + if useTx { + var res *Category + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + res, err = txQ.Category.executeFindUnique(ctx, where, nil, selects, omits) + if err != nil { + return err + } + if res == nil { + return sql.ErrNoRows + } + + // Build DELETE statement by PK + var deleteSb strings.Builder + deleteSb.WriteString("DELETE FROM ") + txQ.dialect.WriteQuotedIdent(&deleteSb, "Category") + deleteSb.WriteString(" WHERE ") + + var pkPreds []PredicateOf[Category] + pkPreds = append(pkPreds, Predicate[Category]{ + Data: PredicateData{ + Column: "id", + Operator: "=", + Value: res.Id, + }, + }) + + whereClause, vals := CompilePredicates(txQ.dialect, pkPreds) + deleteSb.WriteString(whereClause) + + _, err = txQ.exec(ctx, deleteSb.String(), vals...) + if err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return res, nil + } + + // Dialect supports RETURNING, and no relations need loading: run direct DELETE ... RETURNING + var sb strings.Builder + sb.WriteString("DELETE FROM ") + d.client.dialect.WriteQuotedIdent(&sb, "Category") + + whereClause, vals := CompilePredicates(d.client.dialect, []PredicateOf[Category]{where}) + if whereClause != "" { + sb.WriteString(" WHERE ") + sb.WriteString(whereClause) + } + + sb.WriteString(" RETURNING ") + for i, col := range returningCols { + if i > 0 { + sb.WriteString(", ") + } + d.client.dialect.WriteQuotedIdent(&sb, col) + } + + rows, err := d.client.query(ctx, sb.String(), vals...) + if err != nil { + return nil, err + } + defer rows.Close() + + if !rows.Next() { + if err := rows.Err(); err != nil { + return nil, err + } + return nil, sql.ErrNoRows + } + + var row Category + if err := rows.Scan(row.ScanFields(returningCols)...); err != nil { + return nil, err + } + return &row, nil +} func (d *CategoryDelegate) Count(preds ...PredicateOf[Category]) *CountBuilder[Category] { return &CountBuilder[Category]{ where: preds, @@ -1254,12 +1381,19 @@ func (d *CategoryDelegate) runCount(ctx context.Context, params QueryParams[Cate query = sb.String() } - stmt, err := d.client.prepare(ctx, query) + rows, err := d.client.query(ctx, query, vals...) if err != nil { return 0, err } + defer rows.Close() + var count int64 - if err := stmt.QueryRowContext(ctx, vals...).Scan(&count); err != nil { + if rows.Next() { + if err := rows.Scan(&count); err != nil { + return 0, err + } + } + if err := rows.Err(); err != nil { return 0, err } return count, nil diff --git a/integration/valk/categoryToPost.go b/integration/valk/categoryToPost.go index c793506..c5be9a1 100644 --- a/integration/valk/categoryToPost.go +++ b/integration/valk/categoryToPost.go @@ -108,6 +108,7 @@ type CategoryToPostFindUniqueQuery = func(ctx context.Context, where UniquePredi type CategoryToPostFindFirstQuery = func(ctx context.Context, params QueryParams[CategoryToPost], selects *CategoryToPostSelect, omits *CategoryToPostOmit) (*CategoryToPost, error) type CategoryToPostFindManyQuery = func(ctx context.Context, params QueryParams[CategoryToPost], selects *CategoryToPostSelect, omits *CategoryToPostOmit) ([]*CategoryToPost, error) type CategoryToPostDeleteManyQuery = func(ctx context.Context, preds []PredicateOf[CategoryToPost]) (int64, error) +type CategoryToPostDeleteQuery = func(ctx context.Context, where UniquePredicate[CategoryToPost], selects *CategoryToPostSelect, omits *CategoryToPostOmit) (*CategoryToPost, error) type CategoryToPostCountQuery = func(ctx context.Context, params QueryParams[CategoryToPost]) (int64, error) type CategoryToPostExtension struct { @@ -118,6 +119,7 @@ type CategoryToPostExtension struct { FindFirst func(ctx context.Context, params QueryParams[CategoryToPost], selects *CategoryToPostSelect, omits *CategoryToPostOmit, next CategoryToPostFindFirstQuery) (*CategoryToPost, error) FindMany func(ctx context.Context, params QueryParams[CategoryToPost], selects *CategoryToPostSelect, omits *CategoryToPostOmit, next CategoryToPostFindManyQuery) ([]*CategoryToPost, error) DeleteMany func(ctx context.Context, preds []PredicateOf[CategoryToPost], next CategoryToPostDeleteManyQuery) (int64, error) + Delete func(ctx context.Context, where UniquePredicate[CategoryToPost], selects *CategoryToPostSelect, omits *CategoryToPostOmit, next CategoryToPostDeleteQuery) (*CategoryToPost, error) Count func(ctx context.Context, params QueryParams[CategoryToPost], next CategoryToPostCountQuery) (int64, error) } @@ -306,7 +308,6 @@ func (d *CategoryToPostDelegate) executeCreate(ctx context.Context, assignments cols, vals := input.ToColsVals() returningCols := selectCategoryToPostCols(selects, omits) - pkCols := categoryToPostPKCols if len(d.extensions) == 0 { hasRelations := selects.hasAnyRelation() @@ -314,7 +315,7 @@ func (d *CategoryToPostDelegate) executeCreate(ctx context.Context, assignments var res *CategoryToPost err = d.client.transaction(ctx, func(txQ *Queries) error { var err error - res, err = txQ.CategoryToPost.runCreate(ctx, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = txQ.CategoryToPost.runCreate(ctx, cols, vals, returningCols, categoryToPostPKCols, conflictTarget, conflictAction) if err != nil { return err } @@ -322,13 +323,12 @@ func (d *CategoryToPostDelegate) executeCreate(ctx context.Context, assignments }) return res, err } - return d.runCreate(ctx, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + return d.runCreate(ctx, cols, vals, returningCols, categoryToPostPKCols, conflictTarget, conflictAction) } curr := func(c context.Context, args *CategoryToPostCreate) (*CategoryToPost, error) { cols, vals := args.ToColsVals() returningCols := selectCategoryToPostCols(selects, omits) - pkCols := categoryToPostPKCols hasRelations := selects.hasAnyRelation() var res *CategoryToPost @@ -336,14 +336,14 @@ func (d *CategoryToPostDelegate) executeCreate(ctx context.Context, assignments if hasRelations { err = d.client.transaction(c, func(txQ *Queries) error { var err error - res, err = txQ.CategoryToPost.runCreate(c, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = txQ.CategoryToPost.runCreate(c, cols, vals, returningCols, categoryToPostPKCols, conflictTarget, conflictAction) if err != nil { return err } return txQ.CategoryToPost.loadRelations(c, []*CategoryToPost{res}, selects) }) } else { - res, err = d.runCreate(c, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = d.runCreate(c, cols, vals, returningCols, categoryToPostPKCols, conflictTarget, conflictAction) } if err != nil { return nil, err @@ -520,7 +520,7 @@ func (d *CategoryToPostDelegate) runCreate( } var res CategoryToPost - if d.client.dialect.SupportsReturning { + if d.client.dialect.SupportsInsertReturning { rows, err := d.client.query(ctx, query, vals...) if err != nil { return nil, err @@ -674,9 +674,8 @@ func (d *CategoryToPostDelegate) runCreateMany(ctx context.Context, inputs []*Ca conflictCols = conflictTarget.UniqueColumns() } var nonConflictCols []string - pkCols := categoryToPostPKCols if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + nonConflictCols = computeNonConflictCols(cols, conflictCols, categoryToPostPKCols) } clause, clauseArgs := d.client.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) queryStr += clause @@ -721,15 +720,14 @@ func (d *CategoryToPostDelegate) runCreateManyAndReturn( conflictCols = conflictTarget.UniqueColumns() } var nonConflictCols []string - pkCols := categoryToPostPKCols if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + nonConflictCols = computeNonConflictCols(cols, conflictCols, categoryToPostPKCols) } clause, clauseArgs := txQ.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) queryStr += clause vals = append(vals, clauseArgs...) - if txQ.dialect.SupportsReturning && len(returningCols) > 0 { + if txQ.dialect.SupportsInsertReturning && len(returningCols) > 0 { var retSb strings.Builder retSb.Grow(12 + len(returningCols)*15) retSb.WriteString(" RETURNING ") @@ -783,11 +781,11 @@ func (d *CategoryToPostDelegate) runCreateManyAndReturn( selectSb.WriteString(" FROM ") txQ.dialect.WriteQuotedIdent(&selectSb, "CategoryToPost") selectSb.WriteString(" WHERE ") - txQ.dialect.WriteQuotedIdent(&selectSb, pkCols[0]) + txQ.dialect.WriteQuotedIdent(&selectSb, categoryToPostPKCols[0]) selectSb.WriteString(" >= ") txQ.dialect.WritePlaceholder(&selectSb, 1) selectSb.WriteString(" AND ") - txQ.dialect.WriteQuotedIdent(&selectSb, pkCols[0]) + txQ.dialect.WriteQuotedIdent(&selectSb, categoryToPostPKCols[0]) selectSb.WriteString(" < ") txQ.dialect.WritePlaceholder(&selectSb, 2) @@ -808,7 +806,7 @@ func (d *CategoryToPostDelegate) runCreateManyAndReturn( } // Always wrap in transaction if we have multiple batches OR if we need to load relations - if len(batches) > 1 || hasRelations || !d.client.dialect.SupportsReturning { + if len(batches) > 1 || hasRelations || !d.client.dialect.SupportsInsertReturning { err := d.client.transaction(ctx, func(txQ *Queries) error { for _, batch := range batches { if err := runBatch(txQ, batch); err != nil { @@ -1097,13 +1095,27 @@ func (d *CategoryToPostDelegate) runFindMany( func (d *CategoryToPostDelegate) queryOne(ctx context.Context, whereClause string, whereVals []any, returningCols []string, skip *int) (*CategoryToPost, error) { limitOne := 1 query := buildSelectSQL(d.client, "CategoryToPost", returningCols, whereClause, &limitOne, skip) - stmt, err := d.client.prepare(ctx, query) + rows, err := d.client.query(ctx, query, whereVals...) if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } return nil, err } - row := stmt.QueryRowContext(ctx, whereVals...) + defer rows.Close() + + if !rows.Next() { + if err := rows.Err(); err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + return nil, nil + } + var res CategoryToPost - if err := row.Scan(res.ScanFields(returningCols)...); err != nil { + if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { if err == sql.ErrNoRows { return nil, nil } @@ -1114,11 +1126,7 @@ func (d *CategoryToPostDelegate) queryOne(ctx context.Context, whereClause strin func (d *CategoryToPostDelegate) queryMany(ctx context.Context, whereClause string, whereVals []any, returningCols []string, take *int, skip *int) ([]*CategoryToPost, error) { query := buildSelectSQL(d.client, "CategoryToPost", returningCols, whereClause, take, skip) - stmt, err := d.client.prepare(ctx, query) - if err != nil { - return nil, err - } - rows, err := stmt.QueryContext(ctx, whereVals...) + rows, err := d.client.query(ctx, query, whereVals...) if err != nil { return nil, err } @@ -1190,6 +1198,132 @@ func (d *CategoryToPostDelegate) runDeleteMany(ctx context.Context, preds []Pred } return result.RowsAffected() } + +func (d *CategoryToPostDelegate) Delete(where UniquePredicate[CategoryToPost]) *DeleteBuilder[CategoryToPost, CategoryToPostSelect, CategoryToPostOmit] { + return &DeleteBuilder[CategoryToPost, CategoryToPostSelect, CategoryToPostOmit]{ + where: where, + execFunc: d.executeDelete, + } +} + +func (d *CategoryToPostDelegate) executeDelete(ctx context.Context, where UniquePredicate[CategoryToPost], selects *CategoryToPostSelect, omits *CategoryToPostOmit) (*CategoryToPost, error) { + if len(d.extensions) == 0 { + return d.runDelete(ctx, where, selects, omits) + } + + curr := func(c context.Context, w UniquePredicate[CategoryToPost], s *CategoryToPostSelect, o *CategoryToPostOmit) (*CategoryToPost, error) { + return d.runDelete(c, w, s, o) + } + + for _, ext := range slices.Backward(d.extensions) { + if ext.Delete != nil { + next, hook := curr, ext.Delete + curr = func(c context.Context, w UniquePredicate[CategoryToPost], s *CategoryToPostSelect, o *CategoryToPostOmit) (*CategoryToPost, error) { + return hook(c, w, s, o, next) + } + } + } + + return curr(ctx, where, selects, omits) +} + +func (d *CategoryToPostDelegate) runDelete(ctx context.Context, where UniquePredicate[CategoryToPost], selects *CategoryToPostSelect, omits *CategoryToPostOmit) (*CategoryToPost, error) { + if err := where.Validate(); err != nil { + return nil, err + } + + returningCols := selectCategoryToPostCols(selects, omits, categoryToPostPKCols...) + + hasRelations := selects != nil && selects.hasAnyRelation() + useTx := !d.client.dialect.SupportsDeleteReturning || hasRelations + + if useTx { + var res *CategoryToPost + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + res, err = txQ.CategoryToPost.executeFindUnique(ctx, where, nil, selects, omits) + if err != nil { + return err + } + if res == nil { + return sql.ErrNoRows + } + + // Build DELETE statement by PK + var deleteSb strings.Builder + deleteSb.WriteString("DELETE FROM ") + txQ.dialect.WriteQuotedIdent(&deleteSb, "CategoryToPost") + deleteSb.WriteString(" WHERE ") + + var pkPreds []PredicateOf[CategoryToPost] + pkPreds = append(pkPreds, Predicate[CategoryToPost]{ + Data: PredicateData{ + Column: "postId", + Operator: "=", + Value: res.PostId, + }, + }) + pkPreds = append(pkPreds, Predicate[CategoryToPost]{ + Data: PredicateData{ + Column: "categoryId", + Operator: "=", + Value: res.CategoryId, + }, + }) + + whereClause, vals := CompilePredicates(txQ.dialect, pkPreds) + deleteSb.WriteString(whereClause) + + _, err = txQ.exec(ctx, deleteSb.String(), vals...) + if err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return res, nil + } + + // Dialect supports RETURNING, and no relations need loading: run direct DELETE ... RETURNING + var sb strings.Builder + sb.WriteString("DELETE FROM ") + d.client.dialect.WriteQuotedIdent(&sb, "CategoryToPost") + + whereClause, vals := CompilePredicates(d.client.dialect, []PredicateOf[CategoryToPost]{where}) + if whereClause != "" { + sb.WriteString(" WHERE ") + sb.WriteString(whereClause) + } + + sb.WriteString(" RETURNING ") + for i, col := range returningCols { + if i > 0 { + sb.WriteString(", ") + } + d.client.dialect.WriteQuotedIdent(&sb, col) + } + + rows, err := d.client.query(ctx, sb.String(), vals...) + if err != nil { + return nil, err + } + defer rows.Close() + + if !rows.Next() { + if err := rows.Err(); err != nil { + return nil, err + } + return nil, sql.ErrNoRows + } + + var row CategoryToPost + if err := rows.Scan(row.ScanFields(returningCols)...); err != nil { + return nil, err + } + return &row, nil +} func (d *CategoryToPostDelegate) Count(preds ...PredicateOf[CategoryToPost]) *CountBuilder[CategoryToPost] { return &CountBuilder[CategoryToPost]{ where: preds, @@ -1252,12 +1386,19 @@ func (d *CategoryToPostDelegate) runCount(ctx context.Context, params QueryParam query = sb.String() } - stmt, err := d.client.prepare(ctx, query) + rows, err := d.client.query(ctx, query, vals...) if err != nil { return 0, err } + defer rows.Close() + var count int64 - if err := stmt.QueryRowContext(ctx, vals...).Scan(&count); err != nil { + if rows.Next() { + if err := rows.Scan(&count); err != nil { + return 0, err + } + } + if err := rows.Err(); err != nil { return 0, err } return count, nil diff --git a/integration/valk/client.go b/integration/valk/client.go index 9e4ac50..a3b5608 100644 --- a/integration/valk/client.go +++ b/integration/valk/client.go @@ -215,17 +215,19 @@ func (f numericFieldUpsert[T]) Dec(val T) { } type Dialect struct { - QuoteChar byte - PlaceholderFmt string // "" means "?" - SupportsReturning bool - SupportsLimitMinusOne bool - SupportsBulkInsert bool - SupportsDefaultKeyword bool - ConflictKeyword string // "ON CONFLICT" (Pg/SQLite) or "ON DUPLICATE KEY" (MySQL) - ConflictIgnore string // "DO NOTHING" or "" - ConflictUpdate string // "DO UPDATE SET" or "UPDATE" - ConflictExcluded string // "EXCLUDED." or "VALUES(" - ConflictExcludedEnd string // "" or ")" + QuoteChar byte + PlaceholderFmt string // "" means "?" + SupportsInsertReturning bool + SupportsUpdateReturning bool + SupportsDeleteReturning bool + SupportsLimitMinusOne bool + SupportsBulkInsert bool + SupportsDefaultKeyword bool + ConflictKeyword string // "ON CONFLICT" (Pg/SQLite) or "ON DUPLICATE KEY" (MySQL) + ConflictIgnore string // "DO NOTHING" or "" + ConflictUpdate string // "DO UPDATE SET" or "UPDATE" + ConflictExcluded string // "EXCLUDED." or "VALUES(" + ConflictExcludedEnd string // "" or ")" } func (d Dialect) WriteQuotedIdent(sb *strings.Builder, ident string) { @@ -548,17 +550,19 @@ func (d Dialect) BuildConflictClause(conflictCols []string, action *ConflictActi func newDialect() Dialect { return Dialect{ - QuoteChar: '"', - PlaceholderFmt: "$", - SupportsReturning: true, - SupportsLimitMinusOne: false, - SupportsBulkInsert: true, - SupportsDefaultKeyword: true, - ConflictKeyword: "ON CONFLICT", - ConflictIgnore: "DO NOTHING", - ConflictUpdate: "DO UPDATE SET", - ConflictExcluded: "EXCLUDED.", - ConflictExcludedEnd: "", + QuoteChar: '"', + PlaceholderFmt: "$", + SupportsInsertReturning: true, + SupportsUpdateReturning: true, + SupportsDeleteReturning: true, + SupportsLimitMinusOne: false, + SupportsBulkInsert: true, + SupportsDefaultKeyword: true, + ConflictKeyword: "ON CONFLICT", + ConflictIgnore: "DO NOTHING", + ConflictUpdate: "DO UPDATE SET", + ConflictExcluded: "EXCLUDED.", + ConflictExcludedEnd: "", } } @@ -2112,6 +2116,27 @@ func (b *DeleteManyBuilder[M]) Exec(ctx context.Context) (int64, error) { return b.execFunc(ctx, b.where) } +type DeleteBuilder[M any, S any, O any] struct { + where UniquePredicate[M] + selects *S + omits *O + execFunc func(ctx context.Context, where UniquePredicate[M], selects *S, omits *O) (*M, error) +} + +func (b *DeleteBuilder[M, S, O]) Select(selects S) *DeleteBuilder[M, S, O] { + b.selects = &selects + return b +} + +func (b *DeleteBuilder[M, S, O]) Omit(omits O) *DeleteBuilder[M, S, O] { + b.omits = &omits + return b +} + +func (b *DeleteBuilder[M, S, O]) Exec(ctx context.Context) (*M, error) { + return b.execFunc(ctx, b.where, b.selects, b.omits) +} + type CountBuilder[M any] struct { where []PredicateOf[M] take *int @@ -2274,7 +2299,7 @@ func buildSingleInsertSQL( clause, clauseArgs := q.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, valsCount+1) sb.WriteString(clause) - if q.dialect.SupportsReturning && len(returningCols) > 0 { + if q.dialect.SupportsInsertReturning && len(returningCols) > 0 { sb.WriteString(" RETURNING ") for i, col := range returningCols { if i > 0 { diff --git a/integration/valk/comment.go b/integration/valk/comment.go index 105b600..0bb4356 100644 --- a/integration/valk/comment.go +++ b/integration/valk/comment.go @@ -141,6 +141,7 @@ type CommentFindUniqueQuery = func(ctx context.Context, where UniquePredicate[Co type CommentFindFirstQuery = func(ctx context.Context, params QueryParams[Comment], selects *CommentSelect, omits *CommentOmit) (*Comment, error) type CommentFindManyQuery = func(ctx context.Context, params QueryParams[Comment], selects *CommentSelect, omits *CommentOmit) ([]*Comment, error) type CommentDeleteManyQuery = func(ctx context.Context, preds []PredicateOf[Comment]) (int64, error) +type CommentDeleteQuery = func(ctx context.Context, where UniquePredicate[Comment], selects *CommentSelect, omits *CommentOmit) (*Comment, error) type CommentCountQuery = func(ctx context.Context, params QueryParams[Comment]) (int64, error) type CommentExtension struct { @@ -151,6 +152,7 @@ type CommentExtension struct { FindFirst func(ctx context.Context, params QueryParams[Comment], selects *CommentSelect, omits *CommentOmit, next CommentFindFirstQuery) (*Comment, error) FindMany func(ctx context.Context, params QueryParams[Comment], selects *CommentSelect, omits *CommentOmit, next CommentFindManyQuery) ([]*Comment, error) DeleteMany func(ctx context.Context, preds []PredicateOf[Comment], next CommentDeleteManyQuery) (int64, error) + Delete func(ctx context.Context, where UniquePredicate[Comment], selects *CommentSelect, omits *CommentOmit, next CommentDeleteQuery) (*Comment, error) Count func(ctx context.Context, params QueryParams[Comment], next CommentCountQuery) (int64, error) } @@ -469,7 +471,6 @@ func (d *CommentDelegate) executeCreate(ctx context.Context, assignments []Field cols, vals := input.ToColsVals() returningCols := selectCommentCols(selects, omits) - pkCols := commentPKCols if len(d.extensions) == 0 { hasRelations := selects.hasAnyRelation() @@ -477,7 +478,7 @@ func (d *CommentDelegate) executeCreate(ctx context.Context, assignments []Field var res *Comment err = d.client.transaction(ctx, func(txQ *Queries) error { var err error - res, err = txQ.Comment.runCreate(ctx, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = txQ.Comment.runCreate(ctx, cols, vals, returningCols, commentPKCols, conflictTarget, conflictAction) if err != nil { return err } @@ -485,13 +486,12 @@ func (d *CommentDelegate) executeCreate(ctx context.Context, assignments []Field }) return res, err } - return d.runCreate(ctx, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + return d.runCreate(ctx, cols, vals, returningCols, commentPKCols, conflictTarget, conflictAction) } curr := func(c context.Context, args *CommentCreate) (*Comment, error) { cols, vals := args.ToColsVals() returningCols := selectCommentCols(selects, omits) - pkCols := commentPKCols hasRelations := selects.hasAnyRelation() var res *Comment @@ -499,14 +499,14 @@ func (d *CommentDelegate) executeCreate(ctx context.Context, assignments []Field if hasRelations { err = d.client.transaction(c, func(txQ *Queries) error { var err error - res, err = txQ.Comment.runCreate(c, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = txQ.Comment.runCreate(c, cols, vals, returningCols, commentPKCols, conflictTarget, conflictAction) if err != nil { return err } return txQ.Comment.loadRelations(c, []*Comment{res}, selects) }) } else { - res, err = d.runCreate(c, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = d.runCreate(c, cols, vals, returningCols, commentPKCols, conflictTarget, conflictAction) } if err != nil { return nil, err @@ -683,7 +683,7 @@ func (d *CommentDelegate) runCreate( } var res Comment - if d.client.dialect.SupportsReturning { + if d.client.dialect.SupportsInsertReturning { rows, err := d.client.query(ctx, query, vals...) if err != nil { return nil, err @@ -857,9 +857,8 @@ func (d *CommentDelegate) runCreateMany(ctx context.Context, inputs []*CommentCr conflictCols = conflictTarget.UniqueColumns() } var nonConflictCols []string - pkCols := commentPKCols if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + nonConflictCols = computeNonConflictCols(cols, conflictCols, commentPKCols) } clause, clauseArgs := d.client.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) queryStr += clause @@ -904,15 +903,14 @@ func (d *CommentDelegate) runCreateManyAndReturn( conflictCols = conflictTarget.UniqueColumns() } var nonConflictCols []string - pkCols := commentPKCols if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + nonConflictCols = computeNonConflictCols(cols, conflictCols, commentPKCols) } clause, clauseArgs := txQ.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) queryStr += clause vals = append(vals, clauseArgs...) - if txQ.dialect.SupportsReturning && len(returningCols) > 0 { + if txQ.dialect.SupportsInsertReturning && len(returningCols) > 0 { var retSb strings.Builder retSb.Grow(12 + len(returningCols)*15) retSb.WriteString(" RETURNING ") @@ -966,11 +964,11 @@ func (d *CommentDelegate) runCreateManyAndReturn( selectSb.WriteString(" FROM ") txQ.dialect.WriteQuotedIdent(&selectSb, "Comment") selectSb.WriteString(" WHERE ") - txQ.dialect.WriteQuotedIdent(&selectSb, pkCols[0]) + txQ.dialect.WriteQuotedIdent(&selectSb, commentPKCols[0]) selectSb.WriteString(" >= ") txQ.dialect.WritePlaceholder(&selectSb, 1) selectSb.WriteString(" AND ") - txQ.dialect.WriteQuotedIdent(&selectSb, pkCols[0]) + txQ.dialect.WriteQuotedIdent(&selectSb, commentPKCols[0]) selectSb.WriteString(" < ") txQ.dialect.WritePlaceholder(&selectSb, 2) @@ -991,7 +989,7 @@ func (d *CommentDelegate) runCreateManyAndReturn( } // Always wrap in transaction if we have multiple batches OR if we need to load relations - if len(batches) > 1 || hasRelations || !d.client.dialect.SupportsReturning { + if len(batches) > 1 || hasRelations || !d.client.dialect.SupportsInsertReturning { err := d.client.transaction(ctx, func(txQ *Queries) error { for _, batch := range batches { if err := runBatch(txQ, batch); err != nil { @@ -1295,13 +1293,27 @@ func (d *CommentDelegate) runFindMany( func (d *CommentDelegate) queryOne(ctx context.Context, whereClause string, whereVals []any, returningCols []string, skip *int) (*Comment, error) { limitOne := 1 query := buildSelectSQL(d.client, "Comment", returningCols, whereClause, &limitOne, skip) - stmt, err := d.client.prepare(ctx, query) + rows, err := d.client.query(ctx, query, whereVals...) if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } return nil, err } - row := stmt.QueryRowContext(ctx, whereVals...) + defer rows.Close() + + if !rows.Next() { + if err := rows.Err(); err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + return nil, nil + } + var res Comment - if err := row.Scan(res.ScanFields(returningCols)...); err != nil { + if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { if err == sql.ErrNoRows { return nil, nil } @@ -1312,11 +1324,7 @@ func (d *CommentDelegate) queryOne(ctx context.Context, whereClause string, wher func (d *CommentDelegate) queryMany(ctx context.Context, whereClause string, whereVals []any, returningCols []string, take *int, skip *int) ([]*Comment, error) { query := buildSelectSQL(d.client, "Comment", returningCols, whereClause, take, skip) - stmt, err := d.client.prepare(ctx, query) - if err != nil { - return nil, err - } - rows, err := stmt.QueryContext(ctx, whereVals...) + rows, err := d.client.query(ctx, query, whereVals...) if err != nil { return nil, err } @@ -1388,6 +1396,125 @@ func (d *CommentDelegate) runDeleteMany(ctx context.Context, preds []PredicateOf } return result.RowsAffected() } + +func (d *CommentDelegate) Delete(where UniquePredicate[Comment]) *DeleteBuilder[Comment, CommentSelect, CommentOmit] { + return &DeleteBuilder[Comment, CommentSelect, CommentOmit]{ + where: where, + execFunc: d.executeDelete, + } +} + +func (d *CommentDelegate) executeDelete(ctx context.Context, where UniquePredicate[Comment], selects *CommentSelect, omits *CommentOmit) (*Comment, error) { + if len(d.extensions) == 0 { + return d.runDelete(ctx, where, selects, omits) + } + + curr := func(c context.Context, w UniquePredicate[Comment], s *CommentSelect, o *CommentOmit) (*Comment, error) { + return d.runDelete(c, w, s, o) + } + + for _, ext := range slices.Backward(d.extensions) { + if ext.Delete != nil { + next, hook := curr, ext.Delete + curr = func(c context.Context, w UniquePredicate[Comment], s *CommentSelect, o *CommentOmit) (*Comment, error) { + return hook(c, w, s, o, next) + } + } + } + + return curr(ctx, where, selects, omits) +} + +func (d *CommentDelegate) runDelete(ctx context.Context, where UniquePredicate[Comment], selects *CommentSelect, omits *CommentOmit) (*Comment, error) { + if err := where.Validate(); err != nil { + return nil, err + } + + returningCols := selectCommentCols(selects, omits, commentPKCols...) + + hasRelations := selects != nil && selects.hasAnyRelation() + useTx := !d.client.dialect.SupportsDeleteReturning || hasRelations + + if useTx { + var res *Comment + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + res, err = txQ.Comment.executeFindUnique(ctx, where, nil, selects, omits) + if err != nil { + return err + } + if res == nil { + return sql.ErrNoRows + } + + // Build DELETE statement by PK + var deleteSb strings.Builder + deleteSb.WriteString("DELETE FROM ") + txQ.dialect.WriteQuotedIdent(&deleteSb, "Comment") + deleteSb.WriteString(" WHERE ") + + var pkPreds []PredicateOf[Comment] + pkPreds = append(pkPreds, Predicate[Comment]{ + Data: PredicateData{ + Column: "id", + Operator: "=", + Value: res.Id, + }, + }) + + whereClause, vals := CompilePredicates(txQ.dialect, pkPreds) + deleteSb.WriteString(whereClause) + + _, err = txQ.exec(ctx, deleteSb.String(), vals...) + if err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return res, nil + } + + // Dialect supports RETURNING, and no relations need loading: run direct DELETE ... RETURNING + var sb strings.Builder + sb.WriteString("DELETE FROM ") + d.client.dialect.WriteQuotedIdent(&sb, "Comment") + + whereClause, vals := CompilePredicates(d.client.dialect, []PredicateOf[Comment]{where}) + if whereClause != "" { + sb.WriteString(" WHERE ") + sb.WriteString(whereClause) + } + + sb.WriteString(" RETURNING ") + for i, col := range returningCols { + if i > 0 { + sb.WriteString(", ") + } + d.client.dialect.WriteQuotedIdent(&sb, col) + } + + rows, err := d.client.query(ctx, sb.String(), vals...) + if err != nil { + return nil, err + } + defer rows.Close() + + if !rows.Next() { + if err := rows.Err(); err != nil { + return nil, err + } + return nil, sql.ErrNoRows + } + + var row Comment + if err := rows.Scan(row.ScanFields(returningCols)...); err != nil { + return nil, err + } + return &row, nil +} func (d *CommentDelegate) Count(preds ...PredicateOf[Comment]) *CountBuilder[Comment] { return &CountBuilder[Comment]{ where: preds, @@ -1450,12 +1577,19 @@ func (d *CommentDelegate) runCount(ctx context.Context, params QueryParams[Comme query = sb.String() } - stmt, err := d.client.prepare(ctx, query) + rows, err := d.client.query(ctx, query, vals...) if err != nil { return 0, err } + defer rows.Close() + var count int64 - if err := stmt.QueryRowContext(ctx, vals...).Scan(&count); err != nil { + if rows.Next() { + if err := rows.Scan(&count); err != nil { + return 0, err + } + } + if err := rows.Err(); err != nil { return 0, err } return count, nil diff --git a/integration/valk/defaultsTest.go b/integration/valk/defaultsTest.go index 6ed1b90..8f65f36 100644 --- a/integration/valk/defaultsTest.go +++ b/integration/valk/defaultsTest.go @@ -140,6 +140,7 @@ type DefaultsTestFindUniqueQuery = func(ctx context.Context, where UniquePredica type DefaultsTestFindFirstQuery = func(ctx context.Context, params QueryParams[DefaultsTest], selects *DefaultsTestSelect, omits *DefaultsTestOmit) (*DefaultsTest, error) type DefaultsTestFindManyQuery = func(ctx context.Context, params QueryParams[DefaultsTest], selects *DefaultsTestSelect, omits *DefaultsTestOmit) ([]*DefaultsTest, error) type DefaultsTestDeleteManyQuery = func(ctx context.Context, preds []PredicateOf[DefaultsTest]) (int64, error) +type DefaultsTestDeleteQuery = func(ctx context.Context, where UniquePredicate[DefaultsTest], selects *DefaultsTestSelect, omits *DefaultsTestOmit) (*DefaultsTest, error) type DefaultsTestCountQuery = func(ctx context.Context, params QueryParams[DefaultsTest]) (int64, error) type DefaultsTestExtension struct { @@ -150,6 +151,7 @@ type DefaultsTestExtension struct { FindFirst func(ctx context.Context, params QueryParams[DefaultsTest], selects *DefaultsTestSelect, omits *DefaultsTestOmit, next DefaultsTestFindFirstQuery) (*DefaultsTest, error) FindMany func(ctx context.Context, params QueryParams[DefaultsTest], selects *DefaultsTestSelect, omits *DefaultsTestOmit, next DefaultsTestFindManyQuery) ([]*DefaultsTest, error) DeleteMany func(ctx context.Context, preds []PredicateOf[DefaultsTest], next DefaultsTestDeleteManyQuery) (int64, error) + Delete func(ctx context.Context, where UniquePredicate[DefaultsTest], selects *DefaultsTestSelect, omits *DefaultsTestOmit, next DefaultsTestDeleteQuery) (*DefaultsTest, error) Count func(ctx context.Context, params QueryParams[DefaultsTest], next DefaultsTestCountQuery) (int64, error) } @@ -499,7 +501,6 @@ func (d *DefaultsTestDelegate) executeCreate(ctx context.Context, assignments [] cols, vals := input.ToColsVals() returningCols := selectDefaultsTestCols(selects, omits) - pkCols := defaultsTestPKCols if len(d.extensions) == 0 { hasRelations := selects.hasAnyRelation() @@ -507,7 +508,7 @@ func (d *DefaultsTestDelegate) executeCreate(ctx context.Context, assignments [] var res *DefaultsTest err = d.client.transaction(ctx, func(txQ *Queries) error { var err error - res, err = txQ.DefaultsTest.runCreate(ctx, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = txQ.DefaultsTest.runCreate(ctx, cols, vals, returningCols, defaultsTestPKCols, conflictTarget, conflictAction) if err != nil { return err } @@ -515,13 +516,12 @@ func (d *DefaultsTestDelegate) executeCreate(ctx context.Context, assignments [] }) return res, err } - return d.runCreate(ctx, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + return d.runCreate(ctx, cols, vals, returningCols, defaultsTestPKCols, conflictTarget, conflictAction) } curr := func(c context.Context, args *DefaultsTestCreate) (*DefaultsTest, error) { cols, vals := args.ToColsVals() returningCols := selectDefaultsTestCols(selects, omits) - pkCols := defaultsTestPKCols hasRelations := selects.hasAnyRelation() var res *DefaultsTest @@ -529,14 +529,14 @@ func (d *DefaultsTestDelegate) executeCreate(ctx context.Context, assignments [] if hasRelations { err = d.client.transaction(c, func(txQ *Queries) error { var err error - res, err = txQ.DefaultsTest.runCreate(c, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = txQ.DefaultsTest.runCreate(c, cols, vals, returningCols, defaultsTestPKCols, conflictTarget, conflictAction) if err != nil { return err } return txQ.DefaultsTest.loadRelations(c, []*DefaultsTest{res}, selects) }) } else { - res, err = d.runCreate(c, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = d.runCreate(c, cols, vals, returningCols, defaultsTestPKCols, conflictTarget, conflictAction) } if err != nil { return nil, err @@ -713,7 +713,7 @@ func (d *DefaultsTestDelegate) runCreate( } var res DefaultsTest - if d.client.dialect.SupportsReturning { + if d.client.dialect.SupportsInsertReturning { rows, err := d.client.query(ctx, query, vals...) if err != nil { return nil, err @@ -917,9 +917,8 @@ func (d *DefaultsTestDelegate) runCreateMany(ctx context.Context, inputs []*Defa conflictCols = conflictTarget.UniqueColumns() } var nonConflictCols []string - pkCols := defaultsTestPKCols if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + nonConflictCols = computeNonConflictCols(cols, conflictCols, defaultsTestPKCols) } clause, clauseArgs := d.client.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) queryStr += clause @@ -964,15 +963,14 @@ func (d *DefaultsTestDelegate) runCreateManyAndReturn( conflictCols = conflictTarget.UniqueColumns() } var nonConflictCols []string - pkCols := defaultsTestPKCols if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + nonConflictCols = computeNonConflictCols(cols, conflictCols, defaultsTestPKCols) } clause, clauseArgs := txQ.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) queryStr += clause vals = append(vals, clauseArgs...) - if txQ.dialect.SupportsReturning && len(returningCols) > 0 { + if txQ.dialect.SupportsInsertReturning && len(returningCols) > 0 { var retSb strings.Builder retSb.Grow(12 + len(returningCols)*15) retSb.WriteString(" RETURNING ") @@ -1026,11 +1024,11 @@ func (d *DefaultsTestDelegate) runCreateManyAndReturn( selectSb.WriteString(" FROM ") txQ.dialect.WriteQuotedIdent(&selectSb, "DefaultsTest") selectSb.WriteString(" WHERE ") - txQ.dialect.WriteQuotedIdent(&selectSb, pkCols[0]) + txQ.dialect.WriteQuotedIdent(&selectSb, defaultsTestPKCols[0]) selectSb.WriteString(" >= ") txQ.dialect.WritePlaceholder(&selectSb, 1) selectSb.WriteString(" AND ") - txQ.dialect.WriteQuotedIdent(&selectSb, pkCols[0]) + txQ.dialect.WriteQuotedIdent(&selectSb, defaultsTestPKCols[0]) selectSb.WriteString(" < ") txQ.dialect.WritePlaceholder(&selectSb, 2) @@ -1051,7 +1049,7 @@ func (d *DefaultsTestDelegate) runCreateManyAndReturn( } // Always wrap in transaction if we have multiple batches OR if we need to load relations - if len(batches) > 1 || hasRelations || !d.client.dialect.SupportsReturning { + if len(batches) > 1 || hasRelations || !d.client.dialect.SupportsInsertReturning { err := d.client.transaction(ctx, func(txQ *Queries) error { for _, batch := range batches { if err := runBatch(txQ, batch); err != nil { @@ -1351,13 +1349,27 @@ func (d *DefaultsTestDelegate) runFindMany( func (d *DefaultsTestDelegate) queryOne(ctx context.Context, whereClause string, whereVals []any, returningCols []string, skip *int) (*DefaultsTest, error) { limitOne := 1 query := buildSelectSQL(d.client, "DefaultsTest", returningCols, whereClause, &limitOne, skip) - stmt, err := d.client.prepare(ctx, query) + rows, err := d.client.query(ctx, query, whereVals...) if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } return nil, err } - row := stmt.QueryRowContext(ctx, whereVals...) + defer rows.Close() + + if !rows.Next() { + if err := rows.Err(); err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + return nil, nil + } + var res DefaultsTest - if err := row.Scan(res.ScanFields(returningCols)...); err != nil { + if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { if err == sql.ErrNoRows { return nil, nil } @@ -1368,11 +1380,7 @@ func (d *DefaultsTestDelegate) queryOne(ctx context.Context, whereClause string, func (d *DefaultsTestDelegate) queryMany(ctx context.Context, whereClause string, whereVals []any, returningCols []string, take *int, skip *int) ([]*DefaultsTest, error) { query := buildSelectSQL(d.client, "DefaultsTest", returningCols, whereClause, take, skip) - stmt, err := d.client.prepare(ctx, query) - if err != nil { - return nil, err - } - rows, err := stmt.QueryContext(ctx, whereVals...) + rows, err := d.client.query(ctx, query, whereVals...) if err != nil { return nil, err } @@ -1444,6 +1452,125 @@ func (d *DefaultsTestDelegate) runDeleteMany(ctx context.Context, preds []Predic } return result.RowsAffected() } + +func (d *DefaultsTestDelegate) Delete(where UniquePredicate[DefaultsTest]) *DeleteBuilder[DefaultsTest, DefaultsTestSelect, DefaultsTestOmit] { + return &DeleteBuilder[DefaultsTest, DefaultsTestSelect, DefaultsTestOmit]{ + where: where, + execFunc: d.executeDelete, + } +} + +func (d *DefaultsTestDelegate) executeDelete(ctx context.Context, where UniquePredicate[DefaultsTest], selects *DefaultsTestSelect, omits *DefaultsTestOmit) (*DefaultsTest, error) { + if len(d.extensions) == 0 { + return d.runDelete(ctx, where, selects, omits) + } + + curr := func(c context.Context, w UniquePredicate[DefaultsTest], s *DefaultsTestSelect, o *DefaultsTestOmit) (*DefaultsTest, error) { + return d.runDelete(c, w, s, o) + } + + for _, ext := range slices.Backward(d.extensions) { + if ext.Delete != nil { + next, hook := curr, ext.Delete + curr = func(c context.Context, w UniquePredicate[DefaultsTest], s *DefaultsTestSelect, o *DefaultsTestOmit) (*DefaultsTest, error) { + return hook(c, w, s, o, next) + } + } + } + + return curr(ctx, where, selects, omits) +} + +func (d *DefaultsTestDelegate) runDelete(ctx context.Context, where UniquePredicate[DefaultsTest], selects *DefaultsTestSelect, omits *DefaultsTestOmit) (*DefaultsTest, error) { + if err := where.Validate(); err != nil { + return nil, err + } + + returningCols := selectDefaultsTestCols(selects, omits, defaultsTestPKCols...) + + hasRelations := selects != nil && selects.hasAnyRelation() + useTx := !d.client.dialect.SupportsDeleteReturning || hasRelations + + if useTx { + var res *DefaultsTest + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + res, err = txQ.DefaultsTest.executeFindUnique(ctx, where, nil, selects, omits) + if err != nil { + return err + } + if res == nil { + return sql.ErrNoRows + } + + // Build DELETE statement by PK + var deleteSb strings.Builder + deleteSb.WriteString("DELETE FROM ") + txQ.dialect.WriteQuotedIdent(&deleteSb, "DefaultsTest") + deleteSb.WriteString(" WHERE ") + + var pkPreds []PredicateOf[DefaultsTest] + pkPreds = append(pkPreds, Predicate[DefaultsTest]{ + Data: PredicateData{ + Column: "uuid4", + Operator: "=", + Value: res.Uuid4, + }, + }) + + whereClause, vals := CompilePredicates(txQ.dialect, pkPreds) + deleteSb.WriteString(whereClause) + + _, err = txQ.exec(ctx, deleteSb.String(), vals...) + if err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return res, nil + } + + // Dialect supports RETURNING, and no relations need loading: run direct DELETE ... RETURNING + var sb strings.Builder + sb.WriteString("DELETE FROM ") + d.client.dialect.WriteQuotedIdent(&sb, "DefaultsTest") + + whereClause, vals := CompilePredicates(d.client.dialect, []PredicateOf[DefaultsTest]{where}) + if whereClause != "" { + sb.WriteString(" WHERE ") + sb.WriteString(whereClause) + } + + sb.WriteString(" RETURNING ") + for i, col := range returningCols { + if i > 0 { + sb.WriteString(", ") + } + d.client.dialect.WriteQuotedIdent(&sb, col) + } + + rows, err := d.client.query(ctx, sb.String(), vals...) + if err != nil { + return nil, err + } + defer rows.Close() + + if !rows.Next() { + if err := rows.Err(); err != nil { + return nil, err + } + return nil, sql.ErrNoRows + } + + var row DefaultsTest + if err := rows.Scan(row.ScanFields(returningCols)...); err != nil { + return nil, err + } + return &row, nil +} func (d *DefaultsTestDelegate) Count(preds ...PredicateOf[DefaultsTest]) *CountBuilder[DefaultsTest] { return &CountBuilder[DefaultsTest]{ where: preds, @@ -1506,12 +1633,19 @@ func (d *DefaultsTestDelegate) runCount(ctx context.Context, params QueryParams[ query = sb.String() } - stmt, err := d.client.prepare(ctx, query) + rows, err := d.client.query(ctx, query, vals...) if err != nil { return 0, err } + defer rows.Close() + var count int64 - if err := stmt.QueryRowContext(ctx, vals...).Scan(&count); err != nil { + if rows.Next() { + if err := rows.Scan(&count); err != nil { + return 0, err + } + } + if err := rows.Err(); err != nil { return 0, err } return count, nil diff --git a/integration/valk/post.go b/integration/valk/post.go index 6a2f1cf..c8a5e79 100644 --- a/integration/valk/post.go +++ b/integration/valk/post.go @@ -129,6 +129,7 @@ type PostFindUniqueQuery = func(ctx context.Context, where UniquePredicate[Post] type PostFindFirstQuery = func(ctx context.Context, params QueryParams[Post], selects *PostSelect, omits *PostOmit) (*Post, error) type PostFindManyQuery = func(ctx context.Context, params QueryParams[Post], selects *PostSelect, omits *PostOmit) ([]*Post, error) type PostDeleteManyQuery = func(ctx context.Context, preds []PredicateOf[Post]) (int64, error) +type PostDeleteQuery = func(ctx context.Context, where UniquePredicate[Post], selects *PostSelect, omits *PostOmit) (*Post, error) type PostCountQuery = func(ctx context.Context, params QueryParams[Post]) (int64, error) type PostExtension struct { @@ -139,6 +140,7 @@ type PostExtension struct { FindFirst func(ctx context.Context, params QueryParams[Post], selects *PostSelect, omits *PostOmit, next PostFindFirstQuery) (*Post, error) FindMany func(ctx context.Context, params QueryParams[Post], selects *PostSelect, omits *PostOmit, next PostFindManyQuery) ([]*Post, error) DeleteMany func(ctx context.Context, preds []PredicateOf[Post], next PostDeleteManyQuery) (int64, error) + Delete func(ctx context.Context, where UniquePredicate[Post], selects *PostSelect, omits *PostOmit, next PostDeleteQuery) (*Post, error) Count func(ctx context.Context, params QueryParams[Post], next PostCountQuery) (int64, error) } @@ -390,7 +392,6 @@ func (d *PostDelegate) executeCreate(ctx context.Context, assignments []FieldAss cols, vals := input.ToColsVals() returningCols := selectPostCols(selects, omits) - pkCols := postPKCols if len(d.extensions) == 0 { hasRelations := selects.hasAnyRelation() @@ -398,7 +399,7 @@ func (d *PostDelegate) executeCreate(ctx context.Context, assignments []FieldAss var res *Post err = d.client.transaction(ctx, func(txQ *Queries) error { var err error - res, err = txQ.Post.runCreate(ctx, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = txQ.Post.runCreate(ctx, cols, vals, returningCols, postPKCols, conflictTarget, conflictAction) if err != nil { return err } @@ -406,13 +407,12 @@ func (d *PostDelegate) executeCreate(ctx context.Context, assignments []FieldAss }) return res, err } - return d.runCreate(ctx, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + return d.runCreate(ctx, cols, vals, returningCols, postPKCols, conflictTarget, conflictAction) } curr := func(c context.Context, args *PostCreate) (*Post, error) { cols, vals := args.ToColsVals() returningCols := selectPostCols(selects, omits) - pkCols := postPKCols hasRelations := selects.hasAnyRelation() var res *Post @@ -420,14 +420,14 @@ func (d *PostDelegate) executeCreate(ctx context.Context, assignments []FieldAss if hasRelations { err = d.client.transaction(c, func(txQ *Queries) error { var err error - res, err = txQ.Post.runCreate(c, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = txQ.Post.runCreate(c, cols, vals, returningCols, postPKCols, conflictTarget, conflictAction) if err != nil { return err } return txQ.Post.loadRelations(c, []*Post{res}, selects) }) } else { - res, err = d.runCreate(c, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = d.runCreate(c, cols, vals, returningCols, postPKCols, conflictTarget, conflictAction) } if err != nil { return nil, err @@ -604,7 +604,7 @@ func (d *PostDelegate) runCreate( } var res Post - if d.client.dialect.SupportsReturning { + if d.client.dialect.SupportsInsertReturning { rows, err := d.client.query(ctx, query, vals...) if err != nil { return nil, err @@ -776,9 +776,8 @@ func (d *PostDelegate) runCreateMany(ctx context.Context, inputs []*PostCreate, conflictCols = conflictTarget.UniqueColumns() } var nonConflictCols []string - pkCols := postPKCols if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + nonConflictCols = computeNonConflictCols(cols, conflictCols, postPKCols) } clause, clauseArgs := d.client.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) queryStr += clause @@ -823,15 +822,14 @@ func (d *PostDelegate) runCreateManyAndReturn( conflictCols = conflictTarget.UniqueColumns() } var nonConflictCols []string - pkCols := postPKCols if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + nonConflictCols = computeNonConflictCols(cols, conflictCols, postPKCols) } clause, clauseArgs := txQ.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) queryStr += clause vals = append(vals, clauseArgs...) - if txQ.dialect.SupportsReturning && len(returningCols) > 0 { + if txQ.dialect.SupportsInsertReturning && len(returningCols) > 0 { var retSb strings.Builder retSb.Grow(12 + len(returningCols)*15) retSb.WriteString(" RETURNING ") @@ -885,11 +883,11 @@ func (d *PostDelegate) runCreateManyAndReturn( selectSb.WriteString(" FROM ") txQ.dialect.WriteQuotedIdent(&selectSb, "Post") selectSb.WriteString(" WHERE ") - txQ.dialect.WriteQuotedIdent(&selectSb, pkCols[0]) + txQ.dialect.WriteQuotedIdent(&selectSb, postPKCols[0]) selectSb.WriteString(" >= ") txQ.dialect.WritePlaceholder(&selectSb, 1) selectSb.WriteString(" AND ") - txQ.dialect.WriteQuotedIdent(&selectSb, pkCols[0]) + txQ.dialect.WriteQuotedIdent(&selectSb, postPKCols[0]) selectSb.WriteString(" < ") txQ.dialect.WritePlaceholder(&selectSb, 2) @@ -910,7 +908,7 @@ func (d *PostDelegate) runCreateManyAndReturn( } // Always wrap in transaction if we have multiple batches OR if we need to load relations - if len(batches) > 1 || hasRelations || !d.client.dialect.SupportsReturning { + if len(batches) > 1 || hasRelations || !d.client.dialect.SupportsInsertReturning { err := d.client.transaction(ctx, func(txQ *Queries) error { for _, batch := range batches { if err := runBatch(txQ, batch); err != nil { @@ -1202,13 +1200,27 @@ func (d *PostDelegate) runFindMany( func (d *PostDelegate) queryOne(ctx context.Context, whereClause string, whereVals []any, returningCols []string, skip *int) (*Post, error) { limitOne := 1 query := buildSelectSQL(d.client, "Post", returningCols, whereClause, &limitOne, skip) - stmt, err := d.client.prepare(ctx, query) + rows, err := d.client.query(ctx, query, whereVals...) if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } return nil, err } - row := stmt.QueryRowContext(ctx, whereVals...) + defer rows.Close() + + if !rows.Next() { + if err := rows.Err(); err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + return nil, nil + } + var res Post - if err := row.Scan(res.ScanFields(returningCols)...); err != nil { + if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { if err == sql.ErrNoRows { return nil, nil } @@ -1219,11 +1231,7 @@ func (d *PostDelegate) queryOne(ctx context.Context, whereClause string, whereVa func (d *PostDelegate) queryMany(ctx context.Context, whereClause string, whereVals []any, returningCols []string, take *int, skip *int) ([]*Post, error) { query := buildSelectSQL(d.client, "Post", returningCols, whereClause, take, skip) - stmt, err := d.client.prepare(ctx, query) - if err != nil { - return nil, err - } - rows, err := stmt.QueryContext(ctx, whereVals...) + rows, err := d.client.query(ctx, query, whereVals...) if err != nil { return nil, err } @@ -1295,6 +1303,125 @@ func (d *PostDelegate) runDeleteMany(ctx context.Context, preds []PredicateOf[Po } return result.RowsAffected() } + +func (d *PostDelegate) Delete(where UniquePredicate[Post]) *DeleteBuilder[Post, PostSelect, PostOmit] { + return &DeleteBuilder[Post, PostSelect, PostOmit]{ + where: where, + execFunc: d.executeDelete, + } +} + +func (d *PostDelegate) executeDelete(ctx context.Context, where UniquePredicate[Post], selects *PostSelect, omits *PostOmit) (*Post, error) { + if len(d.extensions) == 0 { + return d.runDelete(ctx, where, selects, omits) + } + + curr := func(c context.Context, w UniquePredicate[Post], s *PostSelect, o *PostOmit) (*Post, error) { + return d.runDelete(c, w, s, o) + } + + for _, ext := range slices.Backward(d.extensions) { + if ext.Delete != nil { + next, hook := curr, ext.Delete + curr = func(c context.Context, w UniquePredicate[Post], s *PostSelect, o *PostOmit) (*Post, error) { + return hook(c, w, s, o, next) + } + } + } + + return curr(ctx, where, selects, omits) +} + +func (d *PostDelegate) runDelete(ctx context.Context, where UniquePredicate[Post], selects *PostSelect, omits *PostOmit) (*Post, error) { + if err := where.Validate(); err != nil { + return nil, err + } + + returningCols := selectPostCols(selects, omits, postPKCols...) + + hasRelations := selects != nil && selects.hasAnyRelation() + useTx := !d.client.dialect.SupportsDeleteReturning || hasRelations + + if useTx { + var res *Post + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + res, err = txQ.Post.executeFindUnique(ctx, where, nil, selects, omits) + if err != nil { + return err + } + if res == nil { + return sql.ErrNoRows + } + + // Build DELETE statement by PK + var deleteSb strings.Builder + deleteSb.WriteString("DELETE FROM ") + txQ.dialect.WriteQuotedIdent(&deleteSb, "Post") + deleteSb.WriteString(" WHERE ") + + var pkPreds []PredicateOf[Post] + pkPreds = append(pkPreds, Predicate[Post]{ + Data: PredicateData{ + Column: "id", + Operator: "=", + Value: res.Id, + }, + }) + + whereClause, vals := CompilePredicates(txQ.dialect, pkPreds) + deleteSb.WriteString(whereClause) + + _, err = txQ.exec(ctx, deleteSb.String(), vals...) + if err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return res, nil + } + + // Dialect supports RETURNING, and no relations need loading: run direct DELETE ... RETURNING + var sb strings.Builder + sb.WriteString("DELETE FROM ") + d.client.dialect.WriteQuotedIdent(&sb, "Post") + + whereClause, vals := CompilePredicates(d.client.dialect, []PredicateOf[Post]{where}) + if whereClause != "" { + sb.WriteString(" WHERE ") + sb.WriteString(whereClause) + } + + sb.WriteString(" RETURNING ") + for i, col := range returningCols { + if i > 0 { + sb.WriteString(", ") + } + d.client.dialect.WriteQuotedIdent(&sb, col) + } + + rows, err := d.client.query(ctx, sb.String(), vals...) + if err != nil { + return nil, err + } + defer rows.Close() + + if !rows.Next() { + if err := rows.Err(); err != nil { + return nil, err + } + return nil, sql.ErrNoRows + } + + var row Post + if err := rows.Scan(row.ScanFields(returningCols)...); err != nil { + return nil, err + } + return &row, nil +} func (d *PostDelegate) Count(preds ...PredicateOf[Post]) *CountBuilder[Post] { return &CountBuilder[Post]{ where: preds, @@ -1357,12 +1484,19 @@ func (d *PostDelegate) runCount(ctx context.Context, params QueryParams[Post]) ( query = sb.String() } - stmt, err := d.client.prepare(ctx, query) + rows, err := d.client.query(ctx, query, vals...) if err != nil { return 0, err } + defer rows.Close() + var count int64 - if err := stmt.QueryRowContext(ctx, vals...).Scan(&count); err != nil { + if rows.Next() { + if err := rows.Scan(&count); err != nil { + return 0, err + } + } + if err := rows.Err(); err != nil { return 0, err } return count, nil diff --git a/integration/valk/profile.go b/integration/valk/profile.go index 4cfebde..f1565c1 100644 --- a/integration/valk/profile.go +++ b/integration/valk/profile.go @@ -119,6 +119,7 @@ type ProfileFindUniqueQuery = func(ctx context.Context, where UniquePredicate[Pr type ProfileFindFirstQuery = func(ctx context.Context, params QueryParams[Profile], selects *ProfileSelect, omits *ProfileOmit) (*Profile, error) type ProfileFindManyQuery = func(ctx context.Context, params QueryParams[Profile], selects *ProfileSelect, omits *ProfileOmit) ([]*Profile, error) type ProfileDeleteManyQuery = func(ctx context.Context, preds []PredicateOf[Profile]) (int64, error) +type ProfileDeleteQuery = func(ctx context.Context, where UniquePredicate[Profile], selects *ProfileSelect, omits *ProfileOmit) (*Profile, error) type ProfileCountQuery = func(ctx context.Context, params QueryParams[Profile]) (int64, error) type ProfileExtension struct { @@ -129,6 +130,7 @@ type ProfileExtension struct { FindFirst func(ctx context.Context, params QueryParams[Profile], selects *ProfileSelect, omits *ProfileOmit, next ProfileFindFirstQuery) (*Profile, error) FindMany func(ctx context.Context, params QueryParams[Profile], selects *ProfileSelect, omits *ProfileOmit, next ProfileFindManyQuery) ([]*Profile, error) DeleteMany func(ctx context.Context, preds []PredicateOf[Profile], next ProfileDeleteManyQuery) (int64, error) + Delete func(ctx context.Context, where UniquePredicate[Profile], selects *ProfileSelect, omits *ProfileOmit, next ProfileDeleteQuery) (*Profile, error) Count func(ctx context.Context, params QueryParams[Profile], next ProfileCountQuery) (int64, error) } @@ -360,7 +362,6 @@ func (d *ProfileDelegate) executeCreate(ctx context.Context, assignments []Field cols, vals := input.ToColsVals() returningCols := selectProfileCols(selects, omits) - pkCols := profilePKCols if len(d.extensions) == 0 { hasRelations := selects.hasAnyRelation() @@ -368,7 +369,7 @@ func (d *ProfileDelegate) executeCreate(ctx context.Context, assignments []Field var res *Profile err = d.client.transaction(ctx, func(txQ *Queries) error { var err error - res, err = txQ.Profile.runCreate(ctx, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = txQ.Profile.runCreate(ctx, cols, vals, returningCols, profilePKCols, conflictTarget, conflictAction) if err != nil { return err } @@ -376,13 +377,12 @@ func (d *ProfileDelegate) executeCreate(ctx context.Context, assignments []Field }) return res, err } - return d.runCreate(ctx, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + return d.runCreate(ctx, cols, vals, returningCols, profilePKCols, conflictTarget, conflictAction) } curr := func(c context.Context, args *ProfileCreate) (*Profile, error) { cols, vals := args.ToColsVals() returningCols := selectProfileCols(selects, omits) - pkCols := profilePKCols hasRelations := selects.hasAnyRelation() var res *Profile @@ -390,14 +390,14 @@ func (d *ProfileDelegate) executeCreate(ctx context.Context, assignments []Field if hasRelations { err = d.client.transaction(c, func(txQ *Queries) error { var err error - res, err = txQ.Profile.runCreate(c, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = txQ.Profile.runCreate(c, cols, vals, returningCols, profilePKCols, conflictTarget, conflictAction) if err != nil { return err } return txQ.Profile.loadRelations(c, []*Profile{res}, selects) }) } else { - res, err = d.runCreate(c, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = d.runCreate(c, cols, vals, returningCols, profilePKCols, conflictTarget, conflictAction) } if err != nil { return nil, err @@ -574,7 +574,7 @@ func (d *ProfileDelegate) runCreate( } var res Profile - if d.client.dialect.SupportsReturning { + if d.client.dialect.SupportsInsertReturning { rows, err := d.client.query(ctx, query, vals...) if err != nil { return nil, err @@ -744,9 +744,8 @@ func (d *ProfileDelegate) runCreateMany(ctx context.Context, inputs []*ProfileCr conflictCols = conflictTarget.UniqueColumns() } var nonConflictCols []string - pkCols := profilePKCols if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + nonConflictCols = computeNonConflictCols(cols, conflictCols, profilePKCols) } clause, clauseArgs := d.client.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) queryStr += clause @@ -791,15 +790,14 @@ func (d *ProfileDelegate) runCreateManyAndReturn( conflictCols = conflictTarget.UniqueColumns() } var nonConflictCols []string - pkCols := profilePKCols if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + nonConflictCols = computeNonConflictCols(cols, conflictCols, profilePKCols) } clause, clauseArgs := txQ.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) queryStr += clause vals = append(vals, clauseArgs...) - if txQ.dialect.SupportsReturning && len(returningCols) > 0 { + if txQ.dialect.SupportsInsertReturning && len(returningCols) > 0 { var retSb strings.Builder retSb.Grow(12 + len(returningCols)*15) retSb.WriteString(" RETURNING ") @@ -853,11 +851,11 @@ func (d *ProfileDelegate) runCreateManyAndReturn( selectSb.WriteString(" FROM ") txQ.dialect.WriteQuotedIdent(&selectSb, "Profile") selectSb.WriteString(" WHERE ") - txQ.dialect.WriteQuotedIdent(&selectSb, pkCols[0]) + txQ.dialect.WriteQuotedIdent(&selectSb, profilePKCols[0]) selectSb.WriteString(" >= ") txQ.dialect.WritePlaceholder(&selectSb, 1) selectSb.WriteString(" AND ") - txQ.dialect.WriteQuotedIdent(&selectSb, pkCols[0]) + txQ.dialect.WriteQuotedIdent(&selectSb, profilePKCols[0]) selectSb.WriteString(" < ") txQ.dialect.WritePlaceholder(&selectSb, 2) @@ -878,7 +876,7 @@ func (d *ProfileDelegate) runCreateManyAndReturn( } // Always wrap in transaction if we have multiple batches OR if we need to load relations - if len(batches) > 1 || hasRelations || !d.client.dialect.SupportsReturning { + if len(batches) > 1 || hasRelations || !d.client.dialect.SupportsInsertReturning { err := d.client.transaction(ctx, func(txQ *Queries) error { for _, batch := range batches { if err := runBatch(txQ, batch); err != nil { @@ -1168,13 +1166,27 @@ func (d *ProfileDelegate) runFindMany( func (d *ProfileDelegate) queryOne(ctx context.Context, whereClause string, whereVals []any, returningCols []string, skip *int) (*Profile, error) { limitOne := 1 query := buildSelectSQL(d.client, "Profile", returningCols, whereClause, &limitOne, skip) - stmt, err := d.client.prepare(ctx, query) + rows, err := d.client.query(ctx, query, whereVals...) if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } return nil, err } - row := stmt.QueryRowContext(ctx, whereVals...) + defer rows.Close() + + if !rows.Next() { + if err := rows.Err(); err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + return nil, nil + } + var res Profile - if err := row.Scan(res.ScanFields(returningCols)...); err != nil { + if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { if err == sql.ErrNoRows { return nil, nil } @@ -1185,11 +1197,7 @@ func (d *ProfileDelegate) queryOne(ctx context.Context, whereClause string, wher func (d *ProfileDelegate) queryMany(ctx context.Context, whereClause string, whereVals []any, returningCols []string, take *int, skip *int) ([]*Profile, error) { query := buildSelectSQL(d.client, "Profile", returningCols, whereClause, take, skip) - stmt, err := d.client.prepare(ctx, query) - if err != nil { - return nil, err - } - rows, err := stmt.QueryContext(ctx, whereVals...) + rows, err := d.client.query(ctx, query, whereVals...) if err != nil { return nil, err } @@ -1261,6 +1269,125 @@ func (d *ProfileDelegate) runDeleteMany(ctx context.Context, preds []PredicateOf } return result.RowsAffected() } + +func (d *ProfileDelegate) Delete(where UniquePredicate[Profile]) *DeleteBuilder[Profile, ProfileSelect, ProfileOmit] { + return &DeleteBuilder[Profile, ProfileSelect, ProfileOmit]{ + where: where, + execFunc: d.executeDelete, + } +} + +func (d *ProfileDelegate) executeDelete(ctx context.Context, where UniquePredicate[Profile], selects *ProfileSelect, omits *ProfileOmit) (*Profile, error) { + if len(d.extensions) == 0 { + return d.runDelete(ctx, where, selects, omits) + } + + curr := func(c context.Context, w UniquePredicate[Profile], s *ProfileSelect, o *ProfileOmit) (*Profile, error) { + return d.runDelete(c, w, s, o) + } + + for _, ext := range slices.Backward(d.extensions) { + if ext.Delete != nil { + next, hook := curr, ext.Delete + curr = func(c context.Context, w UniquePredicate[Profile], s *ProfileSelect, o *ProfileOmit) (*Profile, error) { + return hook(c, w, s, o, next) + } + } + } + + return curr(ctx, where, selects, omits) +} + +func (d *ProfileDelegate) runDelete(ctx context.Context, where UniquePredicate[Profile], selects *ProfileSelect, omits *ProfileOmit) (*Profile, error) { + if err := where.Validate(); err != nil { + return nil, err + } + + returningCols := selectProfileCols(selects, omits, profilePKCols...) + + hasRelations := selects != nil && selects.hasAnyRelation() + useTx := !d.client.dialect.SupportsDeleteReturning || hasRelations + + if useTx { + var res *Profile + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + res, err = txQ.Profile.executeFindUnique(ctx, where, nil, selects, omits) + if err != nil { + return err + } + if res == nil { + return sql.ErrNoRows + } + + // Build DELETE statement by PK + var deleteSb strings.Builder + deleteSb.WriteString("DELETE FROM ") + txQ.dialect.WriteQuotedIdent(&deleteSb, "Profile") + deleteSb.WriteString(" WHERE ") + + var pkPreds []PredicateOf[Profile] + pkPreds = append(pkPreds, Predicate[Profile]{ + Data: PredicateData{ + Column: "id", + Operator: "=", + Value: res.Id, + }, + }) + + whereClause, vals := CompilePredicates(txQ.dialect, pkPreds) + deleteSb.WriteString(whereClause) + + _, err = txQ.exec(ctx, deleteSb.String(), vals...) + if err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return res, nil + } + + // Dialect supports RETURNING, and no relations need loading: run direct DELETE ... RETURNING + var sb strings.Builder + sb.WriteString("DELETE FROM ") + d.client.dialect.WriteQuotedIdent(&sb, "Profile") + + whereClause, vals := CompilePredicates(d.client.dialect, []PredicateOf[Profile]{where}) + if whereClause != "" { + sb.WriteString(" WHERE ") + sb.WriteString(whereClause) + } + + sb.WriteString(" RETURNING ") + for i, col := range returningCols { + if i > 0 { + sb.WriteString(", ") + } + d.client.dialect.WriteQuotedIdent(&sb, col) + } + + rows, err := d.client.query(ctx, sb.String(), vals...) + if err != nil { + return nil, err + } + defer rows.Close() + + if !rows.Next() { + if err := rows.Err(); err != nil { + return nil, err + } + return nil, sql.ErrNoRows + } + + var row Profile + if err := rows.Scan(row.ScanFields(returningCols)...); err != nil { + return nil, err + } + return &row, nil +} func (d *ProfileDelegate) Count(preds ...PredicateOf[Profile]) *CountBuilder[Profile] { return &CountBuilder[Profile]{ where: preds, @@ -1323,12 +1450,19 @@ func (d *ProfileDelegate) runCount(ctx context.Context, params QueryParams[Profi query = sb.String() } - stmt, err := d.client.prepare(ctx, query) + rows, err := d.client.query(ctx, query, vals...) if err != nil { return 0, err } + defer rows.Close() + var count int64 - if err := stmt.QueryRowContext(ctx, vals...).Scan(&count); err != nil { + if rows.Next() { + if err := rows.Scan(&count); err != nil { + return 0, err + } + } + if err := rows.Err(); err != nil { return 0, err } return count, nil diff --git a/integration/valk/user.go b/integration/valk/user.go index 4b77f52..53ca7b3 100644 --- a/integration/valk/user.go +++ b/integration/valk/user.go @@ -154,6 +154,7 @@ type UserFindUniqueQuery = func(ctx context.Context, where UniquePredicate[User] type UserFindFirstQuery = func(ctx context.Context, params QueryParams[User], selects *UserSelect, omits *UserOmit) (*User, error) type UserFindManyQuery = func(ctx context.Context, params QueryParams[User], selects *UserSelect, omits *UserOmit) ([]*User, error) type UserDeleteManyQuery = func(ctx context.Context, preds []PredicateOf[User]) (int64, error) +type UserDeleteQuery = func(ctx context.Context, where UniquePredicate[User], selects *UserSelect, omits *UserOmit) (*User, error) type UserCountQuery = func(ctx context.Context, params QueryParams[User]) (int64, error) type UserExtension struct { @@ -164,6 +165,7 @@ type UserExtension struct { FindFirst func(ctx context.Context, params QueryParams[User], selects *UserSelect, omits *UserOmit, next UserFindFirstQuery) (*User, error) FindMany func(ctx context.Context, params QueryParams[User], selects *UserSelect, omits *UserOmit, next UserFindManyQuery) ([]*User, error) DeleteMany func(ctx context.Context, preds []PredicateOf[User], next UserDeleteManyQuery) (int64, error) + Delete func(ctx context.Context, where UniquePredicate[User], selects *UserSelect, omits *UserOmit, next UserDeleteQuery) (*User, error) Count func(ctx context.Context, params QueryParams[User], next UserCountQuery) (int64, error) } @@ -483,7 +485,6 @@ func (d *UserDelegate) executeCreate(ctx context.Context, assignments []FieldAss cols, vals := input.ToColsVals() returningCols := selectUserCols(selects, omits) - pkCols := userPKCols if len(d.extensions) == 0 { hasRelations := selects.hasAnyRelation() @@ -491,7 +492,7 @@ func (d *UserDelegate) executeCreate(ctx context.Context, assignments []FieldAss var res *User err = d.client.transaction(ctx, func(txQ *Queries) error { var err error - res, err = txQ.User.runCreate(ctx, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = txQ.User.runCreate(ctx, cols, vals, returningCols, userPKCols, conflictTarget, conflictAction) if err != nil { return err } @@ -499,13 +500,12 @@ func (d *UserDelegate) executeCreate(ctx context.Context, assignments []FieldAss }) return res, err } - return d.runCreate(ctx, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + return d.runCreate(ctx, cols, vals, returningCols, userPKCols, conflictTarget, conflictAction) } curr := func(c context.Context, args *UserCreate) (*User, error) { cols, vals := args.ToColsVals() returningCols := selectUserCols(selects, omits) - pkCols := userPKCols hasRelations := selects.hasAnyRelation() var res *User @@ -513,14 +513,14 @@ func (d *UserDelegate) executeCreate(ctx context.Context, assignments []FieldAss if hasRelations { err = d.client.transaction(c, func(txQ *Queries) error { var err error - res, err = txQ.User.runCreate(c, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = txQ.User.runCreate(c, cols, vals, returningCols, userPKCols, conflictTarget, conflictAction) if err != nil { return err } return txQ.User.loadRelations(c, []*User{res}, selects) }) } else { - res, err = d.runCreate(c, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = d.runCreate(c, cols, vals, returningCols, userPKCols, conflictTarget, conflictAction) } if err != nil { return nil, err @@ -697,7 +697,7 @@ func (d *UserDelegate) runCreate( } var res User - if d.client.dialect.SupportsReturning { + if d.client.dialect.SupportsInsertReturning { rows, err := d.client.query(ctx, query, vals...) if err != nil { return nil, err @@ -887,9 +887,8 @@ func (d *UserDelegate) runCreateMany(ctx context.Context, inputs []*UserCreate, conflictCols = conflictTarget.UniqueColumns() } var nonConflictCols []string - pkCols := userPKCols if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + nonConflictCols = computeNonConflictCols(cols, conflictCols, userPKCols) } clause, clauseArgs := d.client.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) queryStr += clause @@ -934,15 +933,14 @@ func (d *UserDelegate) runCreateManyAndReturn( conflictCols = conflictTarget.UniqueColumns() } var nonConflictCols []string - pkCols := userPKCols if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + nonConflictCols = computeNonConflictCols(cols, conflictCols, userPKCols) } clause, clauseArgs := txQ.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) queryStr += clause vals = append(vals, clauseArgs...) - if txQ.dialect.SupportsReturning && len(returningCols) > 0 { + if txQ.dialect.SupportsInsertReturning && len(returningCols) > 0 { var retSb strings.Builder retSb.Grow(12 + len(returningCols)*15) retSb.WriteString(" RETURNING ") @@ -996,11 +994,11 @@ func (d *UserDelegate) runCreateManyAndReturn( selectSb.WriteString(" FROM ") txQ.dialect.WriteQuotedIdent(&selectSb, "User") selectSb.WriteString(" WHERE ") - txQ.dialect.WriteQuotedIdent(&selectSb, pkCols[0]) + txQ.dialect.WriteQuotedIdent(&selectSb, userPKCols[0]) selectSb.WriteString(" >= ") txQ.dialect.WritePlaceholder(&selectSb, 1) selectSb.WriteString(" AND ") - txQ.dialect.WriteQuotedIdent(&selectSb, pkCols[0]) + txQ.dialect.WriteQuotedIdent(&selectSb, userPKCols[0]) selectSb.WriteString(" < ") txQ.dialect.WritePlaceholder(&selectSb, 2) @@ -1021,7 +1019,7 @@ func (d *UserDelegate) runCreateManyAndReturn( } // Always wrap in transaction if we have multiple batches OR if we need to load relations - if len(batches) > 1 || hasRelations || !d.client.dialect.SupportsReturning { + if len(batches) > 1 || hasRelations || !d.client.dialect.SupportsInsertReturning { err := d.client.transaction(ctx, func(txQ *Queries) error { for _, batch := range batches { if err := runBatch(txQ, batch); err != nil { @@ -1322,13 +1320,27 @@ func (d *UserDelegate) runFindMany( func (d *UserDelegate) queryOne(ctx context.Context, whereClause string, whereVals []any, returningCols []string, skip *int) (*User, error) { limitOne := 1 query := buildSelectSQL(d.client, "User", returningCols, whereClause, &limitOne, skip) - stmt, err := d.client.prepare(ctx, query) + rows, err := d.client.query(ctx, query, whereVals...) if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } return nil, err } - row := stmt.QueryRowContext(ctx, whereVals...) + defer rows.Close() + + if !rows.Next() { + if err := rows.Err(); err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + return nil, nil + } + var res User - if err := row.Scan(res.ScanFields(returningCols)...); err != nil { + if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { if err == sql.ErrNoRows { return nil, nil } @@ -1339,11 +1351,7 @@ func (d *UserDelegate) queryOne(ctx context.Context, whereClause string, whereVa func (d *UserDelegate) queryMany(ctx context.Context, whereClause string, whereVals []any, returningCols []string, take *int, skip *int) ([]*User, error) { query := buildSelectSQL(d.client, "User", returningCols, whereClause, take, skip) - stmt, err := d.client.prepare(ctx, query) - if err != nil { - return nil, err - } - rows, err := stmt.QueryContext(ctx, whereVals...) + rows, err := d.client.query(ctx, query, whereVals...) if err != nil { return nil, err } @@ -1415,6 +1423,125 @@ func (d *UserDelegate) runDeleteMany(ctx context.Context, preds []PredicateOf[Us } return result.RowsAffected() } + +func (d *UserDelegate) Delete(where UniquePredicate[User]) *DeleteBuilder[User, UserSelect, UserOmit] { + return &DeleteBuilder[User, UserSelect, UserOmit]{ + where: where, + execFunc: d.executeDelete, + } +} + +func (d *UserDelegate) executeDelete(ctx context.Context, where UniquePredicate[User], selects *UserSelect, omits *UserOmit) (*User, error) { + if len(d.extensions) == 0 { + return d.runDelete(ctx, where, selects, omits) + } + + curr := func(c context.Context, w UniquePredicate[User], s *UserSelect, o *UserOmit) (*User, error) { + return d.runDelete(c, w, s, o) + } + + for _, ext := range slices.Backward(d.extensions) { + if ext.Delete != nil { + next, hook := curr, ext.Delete + curr = func(c context.Context, w UniquePredicate[User], s *UserSelect, o *UserOmit) (*User, error) { + return hook(c, w, s, o, next) + } + } + } + + return curr(ctx, where, selects, omits) +} + +func (d *UserDelegate) runDelete(ctx context.Context, where UniquePredicate[User], selects *UserSelect, omits *UserOmit) (*User, error) { + if err := where.Validate(); err != nil { + return nil, err + } + + returningCols := selectUserCols(selects, omits, userPKCols...) + + hasRelations := selects != nil && selects.hasAnyRelation() + useTx := !d.client.dialect.SupportsDeleteReturning || hasRelations + + if useTx { + var res *User + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + res, err = txQ.User.executeFindUnique(ctx, where, nil, selects, omits) + if err != nil { + return err + } + if res == nil { + return sql.ErrNoRows + } + + // Build DELETE statement by PK + var deleteSb strings.Builder + deleteSb.WriteString("DELETE FROM ") + txQ.dialect.WriteQuotedIdent(&deleteSb, "User") + deleteSb.WriteString(" WHERE ") + + var pkPreds []PredicateOf[User] + pkPreds = append(pkPreds, Predicate[User]{ + Data: PredicateData{ + Column: "id", + Operator: "=", + Value: res.Id, + }, + }) + + whereClause, vals := CompilePredicates(txQ.dialect, pkPreds) + deleteSb.WriteString(whereClause) + + _, err = txQ.exec(ctx, deleteSb.String(), vals...) + if err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return res, nil + } + + // Dialect supports RETURNING, and no relations need loading: run direct DELETE ... RETURNING + var sb strings.Builder + sb.WriteString("DELETE FROM ") + d.client.dialect.WriteQuotedIdent(&sb, "User") + + whereClause, vals := CompilePredicates(d.client.dialect, []PredicateOf[User]{where}) + if whereClause != "" { + sb.WriteString(" WHERE ") + sb.WriteString(whereClause) + } + + sb.WriteString(" RETURNING ") + for i, col := range returningCols { + if i > 0 { + sb.WriteString(", ") + } + d.client.dialect.WriteQuotedIdent(&sb, col) + } + + rows, err := d.client.query(ctx, sb.String(), vals...) + if err != nil { + return nil, err + } + defer rows.Close() + + if !rows.Next() { + if err := rows.Err(); err != nil { + return nil, err + } + return nil, sql.ErrNoRows + } + + var row User + if err := rows.Scan(row.ScanFields(returningCols)...); err != nil { + return nil, err + } + return &row, nil +} func (d *UserDelegate) Count(preds ...PredicateOf[User]) *CountBuilder[User] { return &CountBuilder[User]{ where: preds, @@ -1477,12 +1604,19 @@ func (d *UserDelegate) runCount(ctx context.Context, params QueryParams[User]) ( query = sb.String() } - stmt, err := d.client.prepare(ctx, query) + rows, err := d.client.query(ctx, query, vals...) if err != nil { return 0, err } + defer rows.Close() + var count int64 - if err := stmt.QueryRowContext(ctx, vals...).Scan(&count); err != nil { + if rows.Next() { + if err := rows.Scan(&count); err != nil { + return 0, err + } + } + if err := rows.Err(); err != nil { return 0, err } return count, nil