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 { 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