From 9463ca429c0885e2ff43adcf6c9b5239d95c868b Mon Sep 17 00:00:00 2001 From: Clancy Date: Thu, 30 Jul 2026 14:29:16 +0300 Subject: [PATCH 1/2] refactor(update pipeline & builders): standardize Update struct, hooks, and fluent builder chaining 1- Add Update struct with optional pointer fields and assignmentsToUpdate converter for type-safe validation 2- Refactor executeUpdate(), runUpdate(), executeUpdateMany(), and executeUpdateManyAndReturn() to parse assignments upfront into Update and pass cols/vals down to statement runners 3- Unify predicates into allWhere inside executeUpdate() to pass down as a single slice to extension hooks and statement execution 4- Update CreateBuilder and CreateManyAndReturnBuilder to store selects/omits on the builder struct directly and return self pointer on Select() and Omit(), removing unnecessary wrapper structs for 0-allocation method chaining 5- Add model-specific Select() and Omit() on CreateBuilder and CreateManyAndReturnBuilder for order-independent builder chaining 6- Move loadRelation and relation SQL compilers from builders_create.gotpl into relations_runtime.gotpl to modularize runtime relation helpers --- generator/templates/builders_create.gotpl | 243 +----------- generator/templates/model_create.gotpl | 398 +++++++++++--------- generator/templates/model_structs.gotpl | 211 ++++++++++- generator/templates/model_update.gotpl | 164 ++++---- generator/templates/relations_runtime.gotpl | 180 +++++++++ 5 files changed, 714 insertions(+), 482 deletions(-) diff --git a/generator/templates/builders_create.gotpl b/generator/templates/builders_create.gotpl index d40093e..fb3eb09 100644 --- a/generator/templates/builders_create.gotpl +++ b/generator/templates/builders_create.gotpl @@ -1,38 +1,24 @@ type CreateBuilder[M any, S any, O any] struct { assignments []FieldAssignment + selects *S + omits *O execFunc func(ctx context.Context, assignments []FieldAssignment, s *S, o *O, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (*M, error) conflictAction *ConflictAction conflictTarget UniqueConstraintTarget } -func (b *CreateBuilder[M, S, O]) Select(s S) *CreateSelectBuilder[M, S, O] { - return &CreateSelectBuilder[M, S, O]{builder: b, selects: s} +func (b *CreateBuilder[M, S, O]) Select(s S) *CreateBuilder[M, S, O] { + b.selects = &s + return b } -func (b *CreateBuilder[M, S, O]) Omit(o O) *CreateOmitBuilder[M, S, O] { - return &CreateOmitBuilder[M, S, O]{builder: b, omits: o} +func (b *CreateBuilder[M, S, O]) Omit(o O) *CreateBuilder[M, S, O] { + b.omits = &o + return b } func (b *CreateBuilder[M, S, O]) Exec(ctx context.Context) (*M, error) { - return b.execFunc(ctx, b.assignments, nil, nil, b.conflictTarget, b.conflictAction) -} - -type CreateSelectBuilder[M any, S any, O any] struct { - builder *CreateBuilder[M, S, O] - selects S -} - -func (b *CreateSelectBuilder[M, S, O]) Exec(ctx context.Context) (*M, error) { - return b.builder.execFunc(ctx, b.builder.assignments, &b.selects, nil, b.builder.conflictTarget, b.builder.conflictAction) -} - -type CreateOmitBuilder[M any, S any, O any] struct { - builder *CreateBuilder[M, S, O] - omits O -} - -func (b *CreateOmitBuilder[M, S, O]) Exec(ctx context.Context) (*M, error) { - return b.builder.execFunc(ctx, b.builder.assignments, nil, &b.omits, b.builder.conflictTarget, b.builder.conflictAction) + return b.execFunc(ctx, b.assignments, b.selects, b.omits, b.conflictTarget, b.conflictAction) } type CreateManyBuilder[M any] struct { @@ -53,6 +39,8 @@ func (b *CreateManyBuilder[M]) Exec(ctx context.Context) (int64, error) { type CreateManyAndReturnBuilder[M any, S any, O any] struct { records []RecordInput + selects *S + omits *O execFunc func(ctx context.Context, records []RecordInput, s *S, o *O, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) ([]*M, error) conflictAction *ConflictAction conflictTarget UniqueConstraintTarget @@ -63,212 +51,17 @@ func (b *CreateManyAndReturnBuilder[M, S, O]) SkipDuplicates() *CreateManyAndRet return b } -func (b *CreateManyAndReturnBuilder[M, S, O]) Select(s S) *CreateManyAndReturnSelectBuilder[M, S, O] { - return &CreateManyAndReturnSelectBuilder[M, S, O]{builder: b, selects: s} +func (b *CreateManyAndReturnBuilder[M, S, O]) Select(s S) *CreateManyAndReturnBuilder[M, S, O] { + b.selects = &s + return b } -func (b *CreateManyAndReturnBuilder[M, S, O]) Omit(o O) *CreateManyAndReturnOmitBuilder[M, S, O] { - return &CreateManyAndReturnOmitBuilder[M, S, O]{builder: b, omits: o} +func (b *CreateManyAndReturnBuilder[M, S, O]) Omit(o O) *CreateManyAndReturnBuilder[M, S, O] { + b.omits = &o + return b } func (b *CreateManyAndReturnBuilder[M, S, O]) Exec(ctx context.Context) ([]*M, error) { - return b.execFunc(ctx, b.records, nil, nil, b.conflictTarget, b.conflictAction) + return b.execFunc(ctx, b.records, b.selects, b.omits, b.conflictTarget, b.conflictAction) } -type CreateManyAndReturnSelectBuilder[M any, S any, O any] struct { - builder *CreateManyAndReturnBuilder[M, S, O] - selects S -} - -func (b *CreateManyAndReturnSelectBuilder[M, S, O]) Exec(ctx context.Context) ([]*M, error) { - return b.builder.execFunc(ctx, b.builder.records, &b.selects, nil, b.builder.conflictTarget, b.builder.conflictAction) -} - -type CreateManyAndReturnOmitBuilder[M any, S any, O any] struct { - builder *CreateManyAndReturnBuilder[M, S, O] - omits O -} - -func (b *CreateManyAndReturnOmitBuilder[M, S, O]) Exec(ctx context.Context) ([]*M, error) { - return b.builder.execFunc(ctx, b.builder.records, nil, &b.omits, b.builder.conflictTarget, b.builder.conflictAction) -} - -func loadRelation[P any, C any]( - ctx context.Context, - q *Queries, - parents []*P, - parentKey func(*P) (string, bool), - table string, - fkCol string, - returningCols []string, - scan func(*sql.Rows, *C) error, - childKey func(*C) (string, bool), - assign func(*P, []*C), - params QueryParams[C], -) ([]*C, error) { - var parentKeys []any - for _, p := range parents { - if p == nil { - continue - } - if key, ok := parentKey(p); ok { - parentKeys = append(parentKeys, key) - } - } - if len(parentKeys) == 0 { - return nil, nil - } - - // Prepend parent ID checks to filters using Predicate[C] - allPreds := append([]PredicateOf[C]{ - Predicate[C]{ - Data: PredicateData{ - Column: fkCol, - Operator: "IN", - Value: parentKeys, - IsLogical: false, - }, - }, - }, params.Where...) - - whereClause, vals, nextIdx := CompilePredicates(q.dialect, allPreds) - isCursorQuery := (params.Cursor.Data.Column != "" || len(params.Cursor.Data.Children) > 0) - if isCursorQuery { - cClause, cVals, err := compileCursorClause(q.dialect, params.Cursor, params.OrderBy, []string{"id"}, nil, table, nextIdx, params.Take) - if err != nil { - return nil, err - } - if cClause != "" { - if whereClause == "" { - whereClause = cClause - } else { - whereClause = "(" + whereClause + ") AND " + cClause - } - vals = append(vals, cVals...) - } - } - if whereClause != "" { - whereClause = " WHERE " + whereClause - } - - query := compileRelationSQL(q.dialect, table, fkCol, returningCols, whereClause, params) - - rows, err := q.query(ctx, query, vals...) - if err != nil { - return nil, err - } - defer rows.Close() - - childMap := make(map[string][]*C, len(parents)) - allChildren := make([]*C, 0, len(parents)) - - for rows.Next() { - var child C - if err := scan(rows, &child); err != nil { - return nil, err - } - if key, ok := childKey(&child); ok { - childMap[key] = append(childMap[key], &child) - } - allChildren = append(allChildren, &child) - } - if err := rows.Err(); err != nil { - return nil, err - } - - for _, p := range parents { - if p == nil { - continue - } - if key, ok := parentKey(p); ok { - assign(p, childMap[key]) - } - } - - return allChildren, nil -} - -func compileRelationSQL[M any](dialect Dialect, table, fkCol string, cols []string, where string, params QueryParams[M]) string { - isCursorQuery := (params.Cursor.Data.Column != "" || len(params.Cursor.Data.Children) > 0) - if params.Take != nil || params.Skip != nil || isCursorQuery { - return compilePartitionedRelationSQL(dialect, table, fkCol, cols, where, params) - } - return compileSimpleRelationSQL(dialect, table, cols, where, params) -} - -func compilePartitionedRelationSQL[M any](dialect Dialect, table, fkCol string, cols []string, where string, params QueryParams[M]) string { - var innerSb strings.Builder - innerSb.WriteString("SELECT ") - for i, col := range cols { - if i > 0 { - innerSb.WriteString(", ") - } - innerSb.WriteString(dialect.Quote(col)) - } - innerSb.WriteString(", ROW_NUMBER() OVER (PARTITION BY ") - innerSb.WriteString(dialect.Quote(fkCol)) - innerSb.WriteString(" ORDER BY ") - if len(params.OrderBy) > 0 { - for i, ord := range params.OrderBy { - if i > 0 { - innerSb.WriteString(", ") - } - innerSb.WriteString(dialect.Quote(ord.Field)) - innerSb.WriteString(" ") - innerSb.WriteString(string(ord.Direction)) - } - } else { - innerSb.WriteString(dialect.Quote("id")) - innerSb.WriteString(" ASC") - } - innerSb.WriteString(") as row_num FROM ") - innerSb.WriteString(dialect.Quote(table)) - innerSb.WriteString(where) - - var outerSb strings.Builder - outerSb.WriteString("SELECT ") - for i, col := range cols { - if i > 0 { - outerSb.WriteString(", ") - } - outerSb.WriteString(dialect.Quote(col)) - } - outerSb.WriteString(" FROM (") - outerSb.WriteString(innerSb.String()) - outerSb.WriteString(") t WHERE ") - - if params.Take != nil && params.Skip != nil { - outerSb.WriteString(fmt.Sprintf("row_num > %d AND row_num <= %d", *params.Skip, *params.Skip+*params.Take)) - } else if params.Take != nil { - outerSb.WriteString(fmt.Sprintf("row_num <= %d", *params.Take)) - } else if params.Skip != nil { - outerSb.WriteString(fmt.Sprintf("row_num > %d", *params.Skip)) - } - return outerSb.String() -} - -func compileSimpleRelationSQL[M any](dialect Dialect, table string, cols []string, where string, params QueryParams[M]) string { - var sb strings.Builder - sb.WriteString("SELECT ") - for i, col := range cols { - if i > 0 { - sb.WriteString(", ") - } - sb.WriteString(dialect.Quote(col)) - } - sb.WriteString(" FROM ") - sb.WriteString(dialect.Quote(table)) - sb.WriteString(where) - if len(params.OrderBy) > 0 { - sb.WriteString(" ORDER BY ") - for i, ord := range params.OrderBy { - if i > 0 { - sb.WriteString(", ") - } - sb.WriteString(dialect.Quote(ord.Field)) - sb.WriteString(" ") - sb.WriteString(string(ord.Direction)) - } - } - return sb.String() -} diff --git a/generator/templates/model_create.gotpl b/generator/templates/model_create.gotpl index 52265f4..dbba952 100644 --- a/generator/templates/model_create.gotpl +++ b/generator/templates/model_create.gotpl @@ -13,6 +13,16 @@ type {{ .Model.Name }}CreateBuilder struct { *CreateBuilder[{{ .Model.Name }}, {{ .Model.Name }}Select, {{ .Model.Name }}Omit] } +func (b *{{ .Model.Name }}CreateBuilder) Select(s {{ .Model.Name }}Select) *{{ .Model.Name }}CreateBuilder { + b.selects = &s + return b +} + +func (b *{{ .Model.Name }}CreateBuilder) Omit(o {{ .Model.Name }}Omit) *{{ .Model.Name }}CreateBuilder { + b.omits = &o + return b +} + func (b *{{ .Model.Name }}CreateBuilder) OnConflict(target UniqueConstraintTarget) *{{ .Model.Name }}ConflictBuilder[{{ .Model.Name }}CreateBuilder] { return &{{ .Model.Name }}ConflictBuilder[{{ .Model.Name }}CreateBuilder]{ builder: b, @@ -328,23 +338,11 @@ func (d *{{ .Model.Name }}Delegate) executeCreate(ctx context.Context, assignmen return nil, err } + cols, vals := input.ToColsVals() + returningCols := select{{ .Model.Name }}Cols(selects, omits) + if len(d.extensions) == 0 { - cols, vals := input.ToColsVals() - returningCols := select{{ .Model.Name }}Cols(selects, omits) - hasRelations := selects.hasAnyRelation() - if hasRelations { - var res *{{ .Model.Name }} - err = d.client.transaction(ctx, func(txQ *Queries) error { - var err error - res, err = txQ.{{ .Model.Name }}.runCreate(ctx, cols, vals, returningCols, {{ lowercase .Model.Name }}PKCols, conflictTarget, conflictAction) - if err != nil { - return err - } - return txQ.{{ .Model.Name }}.loadRelations(ctx, []*{{ .Model.Name }}{res}, selects) - }) - return res, err - } - return d.runCreate(ctx, cols, vals, returningCols, {{ lowercase .Model.Name }}PKCols, conflictTarget, conflictAction) + return d.runCreate(ctx, cols, vals, returningCols, selects, conflictTarget, conflictAction) } if selects == nil || !selects.hasAnySelected() { @@ -359,28 +357,9 @@ func (d *{{ .Model.Name }}Delegate) executeCreate(ctx context.Context, assignmen } curr := func(c context.Context, a *{{ .Model.Name }}CreateArgs) (*{{ .Model.Name }}, error) { - cols, vals := a.Data.ToColsVals() - returningCols := select{{ .Model.Name }}Cols(a.Select, omits) - - hasRelations := a.Select.hasAnyRelation() - var res *{{ .Model.Name }} - var err error - if hasRelations { - err = d.client.transaction(c, func(txQ *Queries) error { - var err error - res, err = txQ.{{ .Model.Name }}.runCreate(c, cols, vals, returningCols, {{ lowercase .Model.Name }}PKCols, a.ConflictTarget, a.ConflictAction) - if err != nil { - return err - } - return txQ.{{ .Model.Name }}.loadRelations(c, []*{{ .Model.Name }}{res}, a.Select) - }) - } else { - res, err = d.runCreate(c, cols, vals, returningCols, {{ lowercase .Model.Name }}PKCols, a.ConflictTarget, a.ConflictAction) - } - if err != nil { - return nil, err - } - return res, nil + cCols, cVals := a.Data.ToColsVals() + cReturningCols := select{{ .Model.Name }}Cols(a.Select, omits) + return d.runCreate(c, cCols, cVals, cReturningCols, a.Select, a.ConflictTarget, a.ConflictAction) } if len(d.extensions) == 1 { @@ -421,6 +400,16 @@ type {{ .Model.Name }}CreateManyAndReturnBuilder struct { *CreateManyAndReturnBuilder[{{ .Model.Name }}, {{ .Model.Name }}Select, {{ .Model.Name }}Omit] } +func (b *{{ .Model.Name }}CreateManyAndReturnBuilder) Select(s {{ .Model.Name }}Select) *{{ .Model.Name }}CreateManyAndReturnBuilder { + b.selects = &s + return b +} + +func (b *{{ .Model.Name }}CreateManyAndReturnBuilder) Omit(o {{ .Model.Name }}Omit) *{{ .Model.Name }}CreateManyAndReturnBuilder { + b.omits = &o + return b +} + func (b *{{ .Model.Name }}CreateManyAndReturnBuilder) OnConflict(target UniqueConstraintTarget) *{{ .Model.Name }}ConflictBuilder[{{ .Model.Name }}CreateManyAndReturnBuilder] { return &{{ .Model.Name }}ConflictBuilder[{{ .Model.Name }}CreateManyAndReturnBuilder]{ builder: b, @@ -432,43 +421,51 @@ func (b *{{ .Model.Name }}CreateManyAndReturnBuilder) OnConflict(target UniqueCo } } -func (d *{{ .Model.Name }}Delegate) CreateMany(builders ...*{{ .Model.Name }}CreateBuilder) *{{ .Model.Name }}CreateManyBuilder { +func createBuildersTo{{ .Model.Name }}RecordInputs(builders []*{{ .Model.Name }}CreateBuilder) []RecordInput { records := make([]RecordInput, len(builders)) for i, b := range builders { records[i] = RecordInput{Assignments: b.assignments} } + return records +} + +func (d *{{ .Model.Name }}Delegate) CreateMany(builders ...*{{ .Model.Name }}CreateBuilder) *{{ .Model.Name }}CreateManyBuilder { return &{{ .Model.Name }}CreateManyBuilder{ CreateManyBuilder: &CreateManyBuilder[{{ .Model.Name }}]{ - records: records, + records: createBuildersTo{{ .Model.Name }}RecordInputs(builders), execFunc: d.executeCreateMany, }, } } func (d *{{ .Model.Name }}Delegate) CreateManyAndReturn(builders ...*{{ .Model.Name }}CreateBuilder) *{{ .Model.Name }}CreateManyAndReturnBuilder { - records := make([]RecordInput, len(builders)) - for i, b := range builders { - records[i] = RecordInput{Assignments: b.assignments} - } return &{{ .Model.Name }}CreateManyAndReturnBuilder{ CreateManyAndReturnBuilder: &CreateManyAndReturnBuilder[{{ .Model.Name }}, {{ .Model.Name }}Select, {{ .Model.Name }}Omit]{ - records: records, + records: createBuildersTo{{ .Model.Name }}RecordInputs(builders), execFunc: d.executeCreateManyAndReturn, }, } } -func (d *{{ .Model.Name }}Delegate) executeCreateMany(ctx context.Context, records []RecordInput, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { +func recordsTo{{ .Model.Name }}CreateInputs(records []RecordInput) ([]*{{ .Model.Name }}Create, error) { structs := make([]{{ .Model.Name }}Create, len(records)) inputs := make([]*{{ .Model.Name }}Create, len(records)) for i, rec := range records { var err error structs[i], err = assignmentsTo{{ .Model.Name }}Create(rec.Assignments) if err != nil { - return 0, fmt.Errorf("validation failed at index %d: %w", i, err) + return nil, fmt.Errorf("validation failed at index %d: %w", i, err) } inputs[i] = &structs[i] } + return inputs, nil +} + +func (d *{{ .Model.Name }}Delegate) executeCreateMany(ctx context.Context, records []RecordInput, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { + inputs, err := recordsTo{{ .Model.Name }}CreateInputs(records) + if err != nil { + return 0, err + } if len(d.extensions) == 0 { return d.runCreateMany(ctx, inputs, conflictTarget, conflictAction) @@ -504,31 +501,12 @@ func (d *{{ .Model.Name }}Delegate) executeCreateMany(ctx context.Context, recor } func (d *{{ .Model.Name }}Delegate) executeCreateManyAndReturn(ctx context.Context, records []RecordInput, selects *{{ .Model.Name }}Select, omits *{{ .Model.Name }}Omit, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) ([]*{{ .Model.Name }}, error) { - structs := make([]{{ .Model.Name }}Create, len(records)) - inputs := make([]*{{ .Model.Name }}Create, len(records)) - for i, rec := range records { - var err error - structs[i], err = assignmentsTo{{ .Model.Name }}Create(rec.Assignments) - if err != nil { - return nil, fmt.Errorf("validation failed at index %d: %w", i, err) - } - inputs[i] = &structs[i] + inputs, err := recordsTo{{ .Model.Name }}CreateInputs(records) + if err != nil { + return nil, err } if len(d.extensions) == 0 { - hasRelations := selects != nil && selects.hasAnyRelation() - if hasRelations { - var res []*{{ .Model.Name }} - err := d.client.transaction(ctx, func(txQ *Queries) error { - var err error - res, err = txQ.{{ .Model.Name }}.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) - if err != nil { - return err - } - return txQ.{{ .Model.Name }}.loadRelations(ctx, res, selects) - }) - return res, err - } return d.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) } @@ -544,19 +522,6 @@ func (d *{{ .Model.Name }}Delegate) executeCreateManyAndReturn(ctx context.Conte } curr := func(c context.Context, a *{{ .Model.Name }}CreateManyAndReturnArgs) ([]*{{ .Model.Name }}, error) { - hasRelations := a.Select != nil && a.Select.hasAnyRelation() - if hasRelations { - var res []*{{ .Model.Name }} - err := d.client.transaction(c, func(txQ *Queries) error { - var err error - res, err = txQ.{{ .Model.Name }}.runCreateManyAndReturn(c, a.Data, a.Select, omits, a.ConflictTarget, a.ConflictAction) - if err != nil { - return err - } - return txQ.{{ .Model.Name }}.loadRelations(c, res, a.Select) - }) - return res, err - } return d.runCreateManyAndReturn(c, a.Data, a.Select, omits, a.ConflictTarget, a.ConflictAction) } @@ -584,36 +549,67 @@ func (d *{{ .Model.Name }}Delegate) runCreate( cols []string, vals []any, returningCols []string, - pkCols []string, + selects *{{ .Model.Name }}Select, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction, ) (*{{ .Model.Name }}, error) { - query, clauseArgs := buildSingleInsertSQL(d.client, "{{ .Model.Name }}", cols, returningCols, pkCols, conflictTarget, conflictAction, len(vals)) + hasRelations := selects != nil && selects.hasAnyRelation() + useTx := hasRelations && !d.client.inTx() + + if useTx { + var res *{{ .Model.Name }} + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + res, err = txQ.{{ .Model.Name }}.runCreate(ctx, cols, vals, returningCols, selects, conflictTarget, conflictAction) + if err != nil { + return err + } + return txQ.{{ .Model.Name }}.loadRelations(ctx, []*{{ .Model.Name }}{res}, selects) + }) + return res, err + } + + query, clauseArgs := buildSingleInsertSQL(d.client, "{{ .Model.Name }}", cols, returningCols, {{ lowercase .Model.Name }}PKCols, conflictTarget, conflictAction, len(vals)) if len(clauseArgs) > 0 { vals = append(vals, clauseArgs...) } - var res {{ .Model.Name }} if d.client.dialect.SupportsInsertReturning { rows, err := d.client.query(ctx, query, vals...) if err != nil { return nil, err } - defer rows.Close() - if rows.Next() { - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + if !rows.Next() { + err := rows.Err() + rows.Close() + if err != nil { return nil, err } - return &res, nil + return nil, nil + } + + var res {{ .Model.Name }} + scanErr := rows.Scan(res.ScanFields(returningCols)...) + rows.Close() + if scanErr != nil { + return nil, scanErr } - return nil, rows.Err() + + return &res, nil } - return d.runCreateFallback(ctx, query, vals, cols, returningCols, pkCols) + return d.runCreateFallback(ctx, query, vals, cols, returningCols, {{ lowercase .Model.Name }}PKCols) } -func (d *{{ .Model.Name }}Delegate) runCreateFallback(ctx context.Context, query string, vals []any, cols []string, returningCols []string, pkCols []string) (*{{ .Model.Name }}, error) { +func (d *{{ .Model.Name }}Delegate) runCreateFallback( + ctx context.Context, + query string, + vals []any, + cols []string, + returningCols []string, + pkCols []string, +) (*{{ .Model.Name }}, error) { result, err := d.client.exec(ctx, query, vals...) if err != nil { return nil, err @@ -663,16 +659,24 @@ func (d *{{ .Model.Name }}Delegate) runCreateFallback(ctx context.Context, query if err != nil { return nil, err } - defer rows.Close() - var res {{ .Model.Name }} - if rows.Next() { - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + if !rows.Next() { + err := rows.Err() + rows.Close() + if err != nil { return nil, err } - return &res, nil + return nil, nil } - return nil, rows.Err() + + var res {{ .Model.Name }} + scanErr := rows.Scan(res.ScanFields(returningCols)...) + rows.Close() + if scanErr != nil { + return nil, scanErr + } + + return &res, nil } func (d *{{ .Model.Name }}Delegate) buildBulkInsertSQL(q *Queries, batch []*{{ .Model.Name }}Create, paramStartIdx int) (cols []string, vals []any, queryStr string) { @@ -784,6 +788,41 @@ func (d *{{ .Model.Name }}Delegate) buildBulkInsertSQL(q *Queries, batch []*{{ . return cols, vals, queryStr } +func apply{{ .Model.Name }}ConflictClause(dialect Dialect, queryStr string, vals []any, cols []string, pkCols []string, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (string, []any) { + var conflictCols []string + if conflictTarget != nil { + conflictCols = conflictTarget.UniqueColumns() + } + var nonConflictCols []string + if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { + nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + } + clause, clauseArgs := dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) + queryStr += clause + if len(clauseArgs) > 0 { + vals = append(vals, clauseArgs...) + } + return queryStr, vals +} + +func scan{{ .Model.Name }}Rows(rows *sql.Rows, returningCols []string) ([]*{{ .Model.Name }}, error) { + var records []*{{ .Model.Name }} + for rows.Next() { + var res {{ .Model.Name }} + if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + rows.Close() + return nil, err + } + records = append(records, &res) + } + if err := rows.Err(); err != nil { + rows.Close() + return nil, err + } + rows.Close() + return records, nil +} + func (d *{{ .Model.Name }}Delegate) runCreateMany(ctx context.Context, inputs []*{{ .Model.Name }}Create, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { if len(inputs) == 0 { return 0, nil @@ -794,18 +833,7 @@ func (d *{{ .Model.Name }}Delegate) runCreateMany(ctx context.Context, inputs [] var count int64 for _, batch := range batches { cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) - - var conflictCols []string - if conflictTarget != nil { - conflictCols = conflictTarget.UniqueColumns() - } - var nonConflictCols []string - if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, {{ lowercase .Model.Name }}PKCols) - } - clause, clauseArgs := d.client.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) - queryStr += clause - vals = append(vals, clauseArgs...) + queryStr, vals = apply{{ .Model.Name }}ConflictClause(d.client.dialect, queryStr, vals, cols, {{ lowercase .Model.Name }}PKCols, conflictTarget, conflictAction) result, err := d.client.exec(ctx, queryStr, vals...) if err != nil { @@ -833,27 +861,37 @@ func (d *{{ .Model.Name }}Delegate) runCreateManyAndReturn( } batches := partition{{ .Model.Name }}Inputs(d.client.dialect, inputs) - returningCols := select{{ .Model.Name }}Cols(selects, omits) hasRelations := selects != nil && selects.hasAnyRelation() + useTx := (len(batches) > 1 || hasRelations || !d.client.dialect.SupportsInsertReturning) && !d.client.inTx() + if useTx { + var res []*{{ .Model.Name }} + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + if txQ.dialect.SupportsInsertReturning { + res, err = txQ.{{ .Model.Name }}.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) + } else { + res, err = txQ.{{ .Model.Name }}.runCreateManyAndReturnFallback(ctx, inputs, selects, omits, conflictTarget, conflictAction) + } + if err != nil { + return err + } + if hasRelations { + return txQ.{{ .Model.Name }}.loadRelations(ctx, res, selects) + } + return nil + }) + return res, err + } + + returningCols := select{{ .Model.Name }}Cols(selects, omits, {{ lowercase .Model.Name }}PKCols...) recordsOut := make([]*{{ .Model.Name }}, 0, len(inputs)) - - runBatch := func(txQ *Queries, batch []*{{ .Model.Name }}Create) error { - cols, vals, queryStr := d.buildBulkInsertSQL(txQ, batch, 1) - var conflictCols []string - if conflictTarget != nil { - conflictCols = conflictTarget.UniqueColumns() - } - var nonConflictCols []string - if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, {{ lowercase .Model.Name }}PKCols) - } - clause, clauseArgs := txQ.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) - queryStr += clause - vals = append(vals, clauseArgs...) + for _, batch := range batches { + cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) + queryStr, vals = apply{{ .Model.Name }}ConflictClause(d.client.dialect, queryStr, vals, cols, {{ lowercase .Model.Name }}PKCols, conflictTarget, conflictAction) - if txQ.dialect.SupportsInsertReturning && len(returningCols) > 0 { + if len(returningCols) > 0 { var retSb strings.Builder retSb.Grow(12 + len(returningCols)*15) retSb.WriteString(" RETURNING ") @@ -861,40 +899,58 @@ func (d *{{ .Model.Name }}Delegate) runCreateManyAndReturn( if i > 0 { retSb.WriteString(", ") } - txQ.dialect.WriteQuotedIdent(&retSb, col) + d.client.dialect.WriteQuotedIdent(&retSb, col) } queryStr += retSb.String() - rows, err := txQ.query(ctx, queryStr, vals...) - if err != nil { - return err - } - defer rows.Close() + } - for rows.Next() { - var res {{ .Model.Name }} - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - return err - } - recordsOut = append(recordsOut, &res) - } - return rows.Err() + rows, err := d.client.query(ctx, queryStr, vals...) + if err != nil { + return nil, err } - // Fallback for dialects without RETURNING (MySQL) - result, err := txQ.exec(ctx, queryStr, vals...) + scanned, err := scan{{ .Model.Name }}Rows(rows, returningCols) if err != nil { - return err + return nil, err + } + recordsOut = append(recordsOut, scanned...) + } + + if selects != nil && selects.hasAnyRelation() { + if err := d.loadRelations(ctx, recordsOut, selects); err != nil { + return nil, err + } + } + + return recordsOut, nil +} + +func (d *{{ .Model.Name }}Delegate) runCreateManyAndReturnFallback( + ctx context.Context, + inputs []*{{ .Model.Name }}Create, + selects *{{ .Model.Name }}Select, + omits *{{ .Model.Name }}Omit, + conflictTarget UniqueConstraintTarget, + conflictAction *ConflictAction, +) ([]*{{ .Model.Name }}, error) { + batches := partition{{ .Model.Name }}Inputs(d.client.dialect, inputs) + returningCols := select{{ .Model.Name }}Cols(selects, omits, {{ lowercase .Model.Name }}PKCols...) + recordsOut := make([]*{{ .Model.Name }}, 0, len(inputs)) + + for _, batch := range batches { + cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) + queryStr, vals = apply{{ .Model.Name }}ConflictClause(d.client.dialect, queryStr, vals, cols, {{ lowercase .Model.Name }}PKCols, conflictTarget, conflictAction) + + result, err := d.client.exec(ctx, queryStr, vals...) + if err != nil { + return nil, err } - // We need to fetch the inserted records for this batch - // Note: MySQL bulk inserts only return the ID of the FIRST inserted row lastID, err := result.LastInsertId() if err != nil { - return err + return nil, err } - // Query back the rows by IDs (assuming autoincrement ID and single PK) - // If composite PK, it's more complex, but this is a standard fallback var selectSb strings.Builder selectSb.Grow(64 + len(returningCols)*15 + len("{{ .Model.EffectiveTableName }}") + len(batch)*15) selectSb.WriteString("SELECT ") @@ -902,55 +958,29 @@ func (d *{{ .Model.Name }}Delegate) runCreateManyAndReturn( if i > 0 { selectSb.WriteString(", ") } - txQ.dialect.WriteQuotedIdent(&selectSb, col) + d.client.dialect.WriteQuotedIdent(&selectSb, col) } selectSb.WriteString(" FROM ") - txQ.dialect.WriteQuotedIdent(&selectSb, "{{ .Model.EffectiveTableName }}") + d.client.dialect.WriteQuotedIdent(&selectSb, "{{ .Model.EffectiveTableName }}") selectSb.WriteString(" WHERE ") - txQ.dialect.WriteQuotedIdent(&selectSb, {{ lowercase .Model.Name }}PKCols[0]) + d.client.dialect.WriteQuotedIdent(&selectSb, {{ lowercase .Model.Name }}PKCols[0]) selectSb.WriteString(" >= ") - txQ.dialect.WritePlaceholder(&selectSb, 1) + d.client.dialect.WritePlaceholder(&selectSb, 1) selectSb.WriteString(" AND ") - txQ.dialect.WriteQuotedIdent(&selectSb, {{ lowercase .Model.Name }}PKCols[0]) + d.client.dialect.WriteQuotedIdent(&selectSb, {{ lowercase .Model.Name }}PKCols[0]) selectSb.WriteString(" < ") - txQ.dialect.WritePlaceholder(&selectSb, 2) + d.client.dialect.WritePlaceholder(&selectSb, 2) - rows, err := txQ.query(ctx, selectSb.String(), lastID, lastID+int64(len(batch))) + rows, err := d.client.query(ctx, selectSb.String(), lastID, lastID+int64(len(batch))) if err != nil { - return err - } - defer rows.Close() - - for rows.Next() { - var res {{ .Model.Name }} - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - return err - } - recordsOut = append(recordsOut, &res) + return nil, err } - return rows.Err() - } - // Always wrap in transaction if we have multiple batches OR if we need to load relations - 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 { - return err - } - } - if hasRelations { - return txQ.{{ .Model.Name }}.loadRelations(ctx, recordsOut, selects) - } - return nil - }) + scanned, err := scan{{ .Model.Name }}Rows(rows, returningCols) if err != nil { return nil, err } - } else { - if err := runBatch(d.client, batches[0]); err != nil { - return nil, err - } + recordsOut = append(recordsOut, scanned...) } return recordsOut, nil diff --git a/generator/templates/model_structs.gotpl b/generator/templates/model_structs.gotpl index 4410a20..f436e52 100644 --- a/generator/templates/model_structs.gotpl +++ b/generator/templates/model_structs.gotpl @@ -65,6 +65,160 @@ func (s *{{ .Model.Name }}Create) colMask() uint64 { return mask } +// {{ .Model.Name }}Update contains model input fields for {{ .Model.Name }} update operations. +type {{ .Model.Name }}Update struct { + {{- range $field := .Model.ScalarFields }} + {{- $coreType := trimPrefix $field.GoType "*" }} + {{ capitalize $field.Name }} {{ if $field.EnumRef }}{{ if $field.IsArray }}[]{{ $field.EnumRef.Name }}Type{{ else }}*{{ $field.EnumRef.Name }}Type{{ end }}{{ else }}{{ if $field.IsArray }}{{ $field.GoType }}{{ else }}*{{ $coreType }}{{ end }}{{ end }} `json:"{{ $field.Name }}"` + {{- end }} +} + +func (u *{{ .Model.Name }}Update) ToColsVals() ([]string, []any) { + var cols []string + var vals []any + {{- range $field := .Model.ScalarFields }} + if u.{{ capitalize $field.Name }} != nil { + cols = append(cols, "{{ $field.EffectiveColName }}") + vals = append(vals, u.{{ capitalize $field.Name }}) + } + {{- end }} + return cols, vals +} + +func assignmentsTo{{ .Model.Name }}Update(assignments []FieldAssignment) ({{ .Model.Name }}Update, error) { + var input {{ .Model.Name }}Update + var errs ValidationError + + for _, a := range assignments { + switch a.Col { + {{- range $field := .Model.ScalarFields }} + {{- $col := $field.EffectiveColName }} + {{- $coreType := trimPrefix $field.GoType "*" }} + case "{{ $col }}": + {{- if $field.EnumRef }} + {{- $enumType := printf "%sType" $field.EnumRef.Name }} + {{- if $field.IsArray }} + if v, ok := a.Val.([]{{ $enumType }}); ok { + input.{{ capitalize $field.Name }} = v + for i, val := range v { + if !val.IsValid() { + errs.Add(fmt.Sprintf("{{ $field.Name }}[%d]", i), val, "enum", fmt.Sprintf("invalid enum value %q for field {{ $field.Name }}", val)) + } + } + } else { + errs.Add("{{ $field.Name }}", a.Val, "type", "field {{ $field.Name }} must be of type []{{ $enumType }}") + } + {{- else }} + if v, ok := a.Val.({{ $enumType }}); ok { + input.{{ capitalize $field.Name }} = &v + if !v.IsValid() { + errs.Add("{{ $field.Name }}", v, "enum", fmt.Sprintf("invalid enum value %q for field {{ $field.Name }}", v)) + } + } else if v, ok := a.Val.(*{{ $enumType }}); ok { + input.{{ capitalize $field.Name }} = v + if v != nil && !v.IsValid() { + errs.Add("{{ $field.Name }}", *v, "enum", fmt.Sprintf("invalid enum value %q for field {{ $field.Name }}", *v)) + } + } else { + errs.Add("{{ $field.Name }}", a.Val, "type", "field {{ $field.Name }} must be of type {{ $enumType }}") + } + {{- end }} + {{- else if eq $coreType "string" }} + if v, ok := a.Val.(string); ok { + input.{{ capitalize $field.Name }} = &v + {{- $maxLen := 0 }} + {{- if and $field.NativeType (or (eq $field.NativeType.Name "VarChar") (eq $field.NativeType.Name "Char")) }} + {{- $maxLen = index $field.NativeType.Args 0 }} + {{- end }} + {{- $isBit := false }} + {{- if and $field.NativeType (or (eq $field.NativeType.Name "Bit") (eq $field.NativeType.Name "VarBit")) }} + {{- $isBit = true }} + {{- end }} + {{- $isInet := false }} + {{- if and $field.NativeType (eq $field.NativeType.Name "Inet") }} + {{- $isInet = true }} + {{- end }} + errs.ValidateString("{{ $field.Name }}", v, false, {{ $maxLen }}, {{ $isBit }}, {{ $isInet }}) + {{- if and $field.NativeType (eq $field.NativeType.Name "Uuid") }} + errs.ValidateUUID("{{ $field.Name }}", v) + {{- end }} + {{- $scale := "0" }} + {{- if and $field.NativeType (or (eq $field.NativeType.Name "Decimal") (eq $field.NativeType.Name "Numeric")) (gt (len $field.NativeType.Args) 1) }} + {{- $scale = index $field.NativeType.Args 1 }} + {{- end }} + {{- if or (eq $field.Type "Decimal") (ne $scale "0") }} + errs.ValidateDecimal("{{ $field.Name }}", v, {{ $scale }}) + {{- end }} + } else if v, ok := a.Val.(*string); ok { + input.{{ capitalize $field.Name }} = v + } else { + errs.Add("{{ $field.Name }}", a.Val, "type", "field {{ $field.Name }} must be of type string") + } + {{- else if or (eq $coreType "int32") (eq $coreType "int64") (eq $coreType "int") }} + if v, ok := a.Val.({{ $coreType }}); ok { + input.{{ capitalize $field.Name }} = &v + } else if v, ok := a.Val.(*{{ $coreType }}); ok { + input.{{ capitalize $field.Name }} = v + } else { + errs.Add("{{ $field.Name }}", a.Val, "type", "field {{ $field.Name }} must be of type {{ $coreType }}") + } + {{- else if eq $coreType "bool" }} + if v, ok := a.Val.(bool); ok { + input.{{ capitalize $field.Name }} = &v + } else if v, ok := a.Val.(*bool); ok { + input.{{ capitalize $field.Name }} = v + } else { + errs.Add("{{ $field.Name }}", a.Val, "type", "field {{ $field.Name }} must be of type bool") + } + {{- else if eq $coreType "float64" }} + if v, ok := a.Val.(float64); ok { + input.{{ capitalize $field.Name }} = &v + } else if v, ok := a.Val.(*float64); ok { + input.{{ capitalize $field.Name }} = v + } else { + errs.Add("{{ $field.Name }}", a.Val, "type", "field {{ $field.Name }} must be of type float64") + } + {{- else if eq $coreType "time.Time" }} + if v, ok := a.Val.(time.Time); ok { + input.{{ capitalize $field.Name }} = &v + } else if v, ok := a.Val.(*time.Time); ok { + input.{{ capitalize $field.Name }} = v + } else { + errs.Add("{{ $field.Name }}", a.Val, "type", "field {{ $field.Name }} must be of type time.Time") + } + {{- else if eq $coreType "[]byte" }} + if v, ok := a.Val.([]byte); ok { + input.{{ capitalize $field.Name }} = &v + } else if v, ok := a.Val.(*[]byte); ok { + input.{{ capitalize $field.Name }} = v + } else { + errs.Add("{{ $field.Name }}", a.Val, "type", "field {{ $field.Name }} must be of type []byte") + } + {{- else }} + if v, ok := a.Val.({{ $coreType }}); ok { + input.{{ capitalize $field.Name }} = &v + } else if v, ok := a.Val.(*{{ $coreType }}); ok { + input.{{ capitalize $field.Name }} = v + } else if v, ok := a.Val.({{ $field.GoType }}); ok { + {{- if eq (trimPrefix $field.GoType "*") $field.GoType }} + input.{{ capitalize $field.Name }} = &v + {{- else }} + input.{{ capitalize $field.Name }} = v + {{- end }} + } else { + errs.Add("{{ $field.Name }}", a.Val, "type", "field {{ $field.Name }} must be of type {{ $field.GoType }}") + } + {{- end }} + {{- end }} + } + } + + if errs.HasErrors() { + return input, errs + } + return input, nil +} + // {{ .Model.Name }}Select specifies which scalar and relation fields to select for {{ .Model.Name }}. // @@ -424,6 +578,51 @@ func (a *{{ .Model.Name }}DeleteManyArgs) SetWhere(preds ...PredicateOf[{{ .Mode return a } +// {{ .Model.Name }}UpdateArgs is the input argument passed to {{ .Model.Name }} Update extension hooks. +type {{ .Model.Name }}UpdateArgs struct { + // Where contains all query filter predicates (merged primary unique constraint and additional predicates). + Where []PredicateOf[{{ .Model.Name }}] + // Data contains the model fields to update. + Data *{{ .Model.Name }}Update + // Select specifies which scalar and relation fields to select and return upon update. + Select *{{ .Model.Name }}Select +} + +func (a *{{ .Model.Name }}UpdateArgs) SetWhere(unique UniquePredicate[{{ .Model.Name }}], additional ...PredicateOf[{{ .Model.Name }}]) *{{ .Model.Name }}UpdateArgs { + a.Where = make([]PredicateOf[{{ .Model.Name }}], 0, 1+len(additional)) + a.Where = append(a.Where, unique) + a.Where = append(a.Where, additional...) + return a +} + +// {{ .Model.Name }}UpdateManyArgs is the input argument passed to {{ .Model.Name }} UpdateMany extension hooks. +type {{ .Model.Name }}UpdateManyArgs struct { + // Where contains all query filter predicates. + Where []PredicateOf[{{ .Model.Name }}] + // Data contains the model fields to update. + Data *{{ .Model.Name }}Update +} + +func (a *{{ .Model.Name }}UpdateManyArgs) SetWhere(preds ...PredicateOf[{{ .Model.Name }}]) *{{ .Model.Name }}UpdateManyArgs { + a.Where = preds + return a +} + +// {{ .Model.Name }}UpdateManyAndReturnArgs is the input argument passed to {{ .Model.Name }} UpdateManyAndReturn extension hooks. +type {{ .Model.Name }}UpdateManyAndReturnArgs struct { + // Where contains all query filter predicates. + Where []PredicateOf[{{ .Model.Name }}] + // Data contains the model fields to update. + Data *{{ .Model.Name }}Update + // Select specifies which scalar and relation fields to select and return upon update. + Select *{{ .Model.Name }}Select +} + +func (a *{{ .Model.Name }}UpdateManyAndReturnArgs) SetWhere(preds ...PredicateOf[{{ .Model.Name }}]) *{{ .Model.Name }}UpdateManyAndReturnArgs { + a.Where = preds + return a +} + type {{ .Model.Name }}CreateQuery = func(ctx context.Context, args *{{ .Model.Name }}CreateArgs) (*{{ .Model.Name }}, error) type {{ .Model.Name }}CreateManyQuery = func(ctx context.Context, args *{{ .Model.Name }}CreateManyArgs) (int64, error) type {{ .Model.Name }}CreateManyAndReturnQuery = func(ctx context.Context, args *{{ .Model.Name }}CreateManyAndReturnArgs) ([]*{{ .Model.Name }}, error) @@ -433,9 +632,9 @@ type {{ .Model.Name }}FindManyQuery = func(ctx context.Context, args *{{ .Model. type {{ .Model.Name }}DeleteQuery = func(ctx context.Context, args *{{ .Model.Name }}DeleteArgs) (*{{ .Model.Name }}, error) type {{ .Model.Name }}DeleteManyQuery = func(ctx context.Context, args *{{ .Model.Name }}DeleteManyArgs) (int64, error) type {{ .Model.Name }}CountQuery = func(ctx context.Context, args *{{ .Model.Name }}CountArgs) (int64, error) -type {{ .Model.Name }}UpdateQuery = func(ctx context.Context, where UniquePredicate[{{ .Model.Name }}], additional []PredicateOf[{{ .Model.Name }}], assignments []FieldAssignment, selects *{{ .Model.Name }}Select, omits *{{ .Model.Name }}Omit) (*{{ .Model.Name }}, error) -type {{ .Model.Name }}UpdateManyQuery = func(ctx context.Context, preds []PredicateOf[{{ .Model.Name }}], assignments []FieldAssignment) (int64, error) -type {{ .Model.Name }}UpdateManyAndReturnQuery = func(ctx context.Context, preds []PredicateOf[{{ .Model.Name }}], assignments []FieldAssignment, selects *{{ .Model.Name }}Select, omits *{{ .Model.Name }}Omit) ([]*{{ .Model.Name }}, error) +type {{ .Model.Name }}UpdateQuery = func(ctx context.Context, args *{{ .Model.Name }}UpdateArgs) (*{{ .Model.Name }}, error) +type {{ .Model.Name }}UpdateManyQuery = func(ctx context.Context, args *{{ .Model.Name }}UpdateManyArgs) (int64, error) +type {{ .Model.Name }}UpdateManyAndReturnQuery = func(ctx context.Context, args *{{ .Model.Name }}UpdateManyAndReturnArgs) ([]*{{ .Model.Name }}, error) type {{ .Model.Name }}Extension struct { Create func(ctx context.Context, args *{{ .Model.Name }}CreateArgs, next {{ .Model.Name }}CreateQuery) (*{{ .Model.Name }}, error) @@ -447,9 +646,9 @@ type {{ .Model.Name }}Extension struct { Delete func(ctx context.Context, args *{{ .Model.Name }}DeleteArgs, next {{ .Model.Name }}DeleteQuery) (*{{ .Model.Name }}, error) DeleteMany func(ctx context.Context, args *{{ .Model.Name }}DeleteManyArgs, next {{ .Model.Name }}DeleteManyQuery) (int64, error) Count func(ctx context.Context, args *{{ .Model.Name }}CountArgs, next {{ .Model.Name }}CountQuery) (int64, error) - Update func(ctx context.Context, where UniquePredicate[{{ .Model.Name }}], additional []PredicateOf[{{ .Model.Name }}], assignments []FieldAssignment, selects *{{ .Model.Name }}Select, omits *{{ .Model.Name }}Omit, next {{ .Model.Name }}UpdateQuery) (*{{ .Model.Name }}, error) - UpdateMany func(ctx context.Context, preds []PredicateOf[{{ .Model.Name }}], assignments []FieldAssignment, next {{ .Model.Name }}UpdateManyQuery) (int64, error) - UpdateManyAndReturn func(ctx context.Context, preds []PredicateOf[{{ .Model.Name }}], assignments []FieldAssignment, selects *{{ .Model.Name }}Select, omits *{{ .Model.Name }}Omit, next {{ .Model.Name }}UpdateManyAndReturnQuery) ([]*{{ .Model.Name }}, error) + Update func(ctx context.Context, args *{{ .Model.Name }}UpdateArgs, next {{ .Model.Name }}UpdateQuery) (*{{ .Model.Name }}, error) + UpdateMany func(ctx context.Context, args *{{ .Model.Name }}UpdateManyArgs, next {{ .Model.Name }}UpdateManyQuery) (int64, error) + UpdateManyAndReturn func(ctx context.Context, args *{{ .Model.Name }}UpdateManyAndReturnArgs, next {{ .Model.Name }}UpdateManyAndReturnQuery) ([]*{{ .Model.Name }}, error) } type {{ .Model.Name }}Delegate struct { diff --git a/generator/templates/model_update.gotpl b/generator/templates/model_update.gotpl index d913b05..5d9cdcf 100644 --- a/generator/templates/model_update.gotpl +++ b/generator/templates/model_update.gotpl @@ -84,23 +84,23 @@ func (d *{{ .Model.Name }}Delegate) UpdateManyAndReturn(preds ...PredicateOf[{{ } } -func (d *{{ .Model.Name }}Delegate) buildUpdateSQL(preds []PredicateOf[{{ .Model.Name }}], assignments []FieldAssignment, returningCols []string) (string, []any) { - whereClause, predVals, _ := CompilePredicates(d.client.dialect, preds, len(assignments)+1) +func (d *{{ .Model.Name }}Delegate) buildUpdateSQL(preds []PredicateOf[{{ .Model.Name }}], cols []string, vals []any, returningCols []string) (string, []any) { + whereClause, predVals, _ := CompilePredicates(d.client.dialect, preds, len(cols)+1) var sb strings.Builder sb.WriteString("UPDATE ") d.client.dialect.WriteQuotedIdent(&sb, "{{ .Model.EffectiveTableName }}") sb.WriteString(" SET ") - setVals := make([]any, 0, len(assignments)+len(predVals)) - for i, a := range assignments { + setVals := make([]any, 0, len(cols)+len(predVals)) + for i, col := range cols { if i > 0 { sb.WriteString(", ") } - d.client.dialect.WriteQuotedIdent(&sb, a.Col) + d.client.dialect.WriteQuotedIdent(&sb, col) sb.WriteString(" = ") d.client.dialect.WritePlaceholder(&sb, i+1) - setVals = append(setVals, a.Val) + setVals = append(setVals, vals[i]) } if whereClause != "" { @@ -127,21 +127,39 @@ func (d *{{ .Model.Name }}Delegate) buildUpdateSQL(preds []PredicateOf[{{ .Model // ----------------------------------------------------------------------------- func (d *{{ .Model.Name }}Delegate) executeUpdate(ctx context.Context, where UniquePredicate[{{ .Model.Name }}], additional []PredicateOf[{{ .Model.Name }}], assignments []FieldAssignment, selects *{{ .Model.Name }}Select, omits *{{ .Model.Name }}Omit) (*{{ .Model.Name }}, error) { + allWhere := make([]PredicateOf[{{ .Model.Name }}], 0, 1+len(additional)) + allWhere = append(allWhere, where) + allWhere = append(allWhere, additional...) + + input, err := assignmentsTo{{ .Model.Name }}Update(assignments) + if err != nil { + return nil, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.runUpdate(ctx, where, additional, assignments, selects, omits) + return d.runUpdate(ctx, allWhere, cols, vals, selects, omits) } if selects == nil || !selects.hasAnySelected() { selects = full{{ .Model.Name }}Select() } - curr := func(c context.Context, w UniquePredicate[{{ .Model.Name }}], add []PredicateOf[{{ .Model.Name }}], a []FieldAssignment, s *{{ .Model.Name }}Select, o *{{ .Model.Name }}Omit) (*{{ .Model.Name }}, error) { - return d.runUpdate(c, w, add, a, s, o) + args := &{{ .Model.Name }}UpdateArgs{ + Where: allWhere, + Data: &input, + Select: selects, + } + + curr := func(c context.Context, a *{{ .Model.Name }}UpdateArgs) (*{{ .Model.Name }}, error) { + extCols, extVals := a.Data.ToColsVals() + return d.runUpdate(c, a.Where, extCols, extVals, a.Select, omits) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.Update != nil { - return ext.Update(ctx, where, additional, assignments, selects, omits, curr) + return ext.Update(ctx, args, curr) } } @@ -149,25 +167,21 @@ func (d *{{ .Model.Name }}Delegate) executeUpdate(ctx context.Context, where Uni ext := d.extensions[i] if ext.Update != nil { next, hook := curr, ext.Update - curr = func(c context.Context, w UniquePredicate[{{ .Model.Name }}], add []PredicateOf[{{ .Model.Name }}], a []FieldAssignment, s *{{ .Model.Name }}Select, o *{{ .Model.Name }}Omit) (*{{ .Model.Name }}, error) { - return hook(c, w, add, a, s, o, next) + curr = func(c context.Context, a *{{ .Model.Name }}UpdateArgs) (*{{ .Model.Name }}, error) { + return hook(c, a, next) } } } - return curr(ctx, where, additional, assignments, selects, omits) + return curr(ctx, args) } -func (d *{{ .Model.Name }}Delegate) runUpdate(ctx context.Context, where UniquePredicate[{{ .Model.Name }}], additional []PredicateOf[{{ .Model.Name }}], assignments []FieldAssignment, selects *{{ .Model.Name }}Select, omits *{{ .Model.Name }}Omit) (*{{ .Model.Name }}, error) { - allPreds := append([]PredicateOf[{{ .Model.Name }}]{where}, additional...) - if len(assignments) == 0 { - return d.runFindUnique(ctx, allPreds, selects, omits) +func (d *{{ .Model.Name }}Delegate) runUpdate(ctx context.Context, preds []PredicateOf[{{ .Model.Name }}], cols []string, vals []any, selects *{{ .Model.Name }}Select, omits *{{ .Model.Name }}Omit) (*{{ .Model.Name }}, error) { + if len(cols) == 0 { + return d.runFindUnique(ctx, preds, selects, omits) } - if err := where.Validate(); err != nil { - return nil, err - } - for _, pr := range additional { + for _, pr := range preds { if pr != nil { if err := pr.Validate(); err != nil { return nil, err @@ -183,9 +197,9 @@ func (d *{{ .Model.Name }}Delegate) runUpdate(ctx context.Context, where UniqueP err := d.client.transaction(ctx, func(txQ *Queries) error { var err error if d.client.dialect.SupportsUpdateReturning { - res, err = txQ.{{ .Model.Name }}.runUpdate(ctx, where, additional, assignments, selects, omits) + res, err = txQ.{{ .Model.Name }}.runUpdate(ctx, preds, cols, vals, selects, omits) } else { - res, err = txQ.{{ .Model.Name }}.runUpdateFallback(ctx, where, additional, assignments, selects, omits) + res, err = txQ.{{ .Model.Name }}.runUpdateFallback(ctx, preds, cols, vals, selects, omits) } return err }) @@ -193,7 +207,7 @@ func (d *{{ .Model.Name }}Delegate) runUpdate(ctx context.Context, where UniqueP } returningCols := select{{ .Model.Name }}Cols(selects, omits, {{ lowercase .Model.Name }}PKCols...) - query, setVals := d.buildUpdateSQL(allPreds, assignments, returningCols) + query, setVals := d.buildUpdateSQL(preds, cols, vals, returningCols) rows, err := d.client.query(ctx, query, setVals...) if err != nil { @@ -225,8 +239,8 @@ func (d *{{ .Model.Name }}Delegate) runUpdate(ctx context.Context, where UniqueP return &res, nil } -func (d *{{ .Model.Name }}Delegate) execUpdateStmt(ctx context.Context, preds []PredicateOf[{{ .Model.Name }}], assignments []FieldAssignment) (int64, error) { - if len(assignments) == 0 { +func (d *{{ .Model.Name }}Delegate) execUpdateStmt(ctx context.Context, preds []PredicateOf[{{ .Model.Name }}], cols []string, vals []any) (int64, error) { + if len(cols) == 0 { return 0, nil } @@ -238,7 +252,7 @@ func (d *{{ .Model.Name }}Delegate) execUpdateStmt(ctx context.Context, preds [] } } - query, setVals := d.buildUpdateSQL(preds, assignments, nil) + query, setVals := d.buildUpdateSQL(preds, cols, vals, nil) result, err := d.client.exec(ctx, query, setVals...) if err != nil { return 0, err @@ -246,16 +260,15 @@ func (d *{{ .Model.Name }}Delegate) execUpdateStmt(ctx context.Context, preds [] return result.RowsAffected() } -func (d *{{ .Model.Name }}Delegate) runUpdateFallback(ctx context.Context, where UniquePredicate[{{ .Model.Name }}], additional []PredicateOf[{{ .Model.Name }}], assignments []FieldAssignment, selects *{{ .Model.Name }}Select, omits *{{ .Model.Name }}Omit) (*{{ .Model.Name }}, error) { - allPreds := append([]PredicateOf[{{ .Model.Name }}]{where}, additional...) - affected, err := d.execUpdateStmt(ctx, allPreds, assignments) +func (d *{{ .Model.Name }}Delegate) runUpdateFallback(ctx context.Context, preds []PredicateOf[{{ .Model.Name }}], cols []string, vals []any, selects *{{ .Model.Name }}Select, omits *{{ .Model.Name }}Omit) (*{{ .Model.Name }}, error) { + affected, err := d.execUpdateStmt(ctx, preds, cols, vals) if err != nil { return nil, err } if affected == 0 { return nil, sql.ErrNoRows } - return d.runFindUnique(ctx, allPreds, selects, omits) + return d.runFindUnique(ctx, preds, selects, omits) } // ----------------------------------------------------------------------------- @@ -263,17 +276,30 @@ func (d *{{ .Model.Name }}Delegate) runUpdateFallback(ctx context.Context, where // ----------------------------------------------------------------------------- func (d *{{ .Model.Name }}Delegate) executeUpdateMany(ctx context.Context, preds []PredicateOf[{{ .Model.Name }}], assignments []FieldAssignment) (int64, error) { + input, err := assignmentsTo{{ .Model.Name }}Update(assignments) + if err != nil { + return 0, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.execUpdateStmt(ctx, preds, assignments) + return d.execUpdateStmt(ctx, preds, cols, vals) } - curr := func(c context.Context, p []PredicateOf[{{ .Model.Name }}], a []FieldAssignment) (int64, error) { - return d.execUpdateStmt(c, p, a) + args := &{{ .Model.Name }}UpdateManyArgs{ + Where: preds, + Data: &input, + } + + curr := func(c context.Context, a *{{ .Model.Name }}UpdateManyArgs) (int64, error) { + extCols, extVals := a.Data.ToColsVals() + return d.execUpdateStmt(c, a.Where, extCols, extVals) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.UpdateMany != nil { - return ext.UpdateMany(ctx, preds, assignments, curr) + return ext.UpdateMany(ctx, args, curr) } } @@ -281,13 +307,13 @@ func (d *{{ .Model.Name }}Delegate) executeUpdateMany(ctx context.Context, preds ext := d.extensions[i] if ext.UpdateMany != nil { next, hook := curr, ext.UpdateMany - curr = func(c context.Context, p []PredicateOf[{{ .Model.Name }}], a []FieldAssignment) (int64, error) { - return hook(c, p, a, next) + curr = func(c context.Context, a *{{ .Model.Name }}UpdateManyArgs) (int64, error) { + return hook(c, a, next) } } } - return curr(ctx, preds, assignments) + return curr(ctx, args) } // ----------------------------------------------------------------------------- @@ -295,21 +321,35 @@ func (d *{{ .Model.Name }}Delegate) executeUpdateMany(ctx context.Context, preds // ----------------------------------------------------------------------------- func (d *{{ .Model.Name }}Delegate) executeUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[{{ .Model.Name }}], assignments []FieldAssignment, selects *{{ .Model.Name }}Select, omits *{{ .Model.Name }}Omit) ([]*{{ .Model.Name }}, error) { + input, err := assignmentsTo{{ .Model.Name }}Update(assignments) + if err != nil { + return nil, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.runUpdateManyAndReturn(ctx, preds, assignments, selects, omits) + return d.runUpdateManyAndReturn(ctx, preds, cols, vals, selects, omits) } if selects == nil || !selects.hasAnySelected() { selects = full{{ .Model.Name }}Select() } - curr := func(c context.Context, p []PredicateOf[{{ .Model.Name }}], a []FieldAssignment, s *{{ .Model.Name }}Select, o *{{ .Model.Name }}Omit) ([]*{{ .Model.Name }}, error) { - return d.runUpdateManyAndReturn(c, p, a, s, o) + args := &{{ .Model.Name }}UpdateManyAndReturnArgs{ + Where: preds, + Data: &input, + Select: selects, + } + + curr := func(c context.Context, a *{{ .Model.Name }}UpdateManyAndReturnArgs) ([]*{{ .Model.Name }}, error) { + extCols, extVals := a.Data.ToColsVals() + return d.runUpdateManyAndReturn(c, a.Where, extCols, extVals, a.Select, omits) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.UpdateManyAndReturn != nil { - return ext.UpdateManyAndReturn(ctx, preds, assignments, selects, omits, curr) + return ext.UpdateManyAndReturn(ctx, args, curr) } } @@ -317,17 +357,17 @@ func (d *{{ .Model.Name }}Delegate) executeUpdateManyAndReturn(ctx context.Conte ext := d.extensions[i] if ext.UpdateManyAndReturn != nil { next, hook := curr, ext.UpdateManyAndReturn - curr = func(c context.Context, p []PredicateOf[{{ .Model.Name }}], a []FieldAssignment, s *{{ .Model.Name }}Select, o *{{ .Model.Name }}Omit) ([]*{{ .Model.Name }}, error) { - return hook(c, p, a, s, o, next) + curr = func(c context.Context, a *{{ .Model.Name }}UpdateManyAndReturnArgs) ([]*{{ .Model.Name }}, error) { + return hook(c, a, next) } } } - return curr(ctx, preds, assignments, selects, omits) + return curr(ctx, args) } -func (d *{{ .Model.Name }}Delegate) runUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[{{ .Model.Name }}], assignments []FieldAssignment, selects *{{ .Model.Name }}Select, omits *{{ .Model.Name }}Omit) ([]*{{ .Model.Name }}, error) { - if len(assignments) == 0 { +func (d *{{ .Model.Name }}Delegate) runUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[{{ .Model.Name }}], cols []string, vals []any, selects *{{ .Model.Name }}Select, omits *{{ .Model.Name }}Omit) ([]*{{ .Model.Name }}, error) { + if len(cols) == 0 { return d.runFindMany(ctx, QueryParams[{{ .Model.Name }}]{Where: preds}, selects, omits) } @@ -347,9 +387,9 @@ func (d *{{ .Model.Name }}Delegate) runUpdateManyAndReturn(ctx context.Context, err := d.client.transaction(ctx, func(txQ *Queries) error { var err error if d.client.dialect.SupportsUpdateReturning { - res, err = txQ.{{ .Model.Name }}.runUpdateManyAndReturn(ctx, preds, assignments, selects, omits) + res, err = txQ.{{ .Model.Name }}.runUpdateManyAndReturn(ctx, preds, cols, vals, selects, omits) } else { - res, err = txQ.{{ .Model.Name }}.runUpdateManyAndReturnFallback(ctx, preds, assignments, selects, omits) + res, err = txQ.{{ .Model.Name }}.runUpdateManyAndReturnFallback(ctx, preds, cols, vals, selects, omits) } return err }) @@ -357,39 +397,29 @@ func (d *{{ .Model.Name }}Delegate) runUpdateManyAndReturn(ctx context.Context, } returningCols := select{{ .Model.Name }}Cols(selects, omits, {{ lowercase .Model.Name }}PKCols...) - query, setVals := d.buildUpdateSQL(preds, assignments, returningCols) + query, setVals := d.buildUpdateSQL(preds, cols, vals, returningCols) rows, err := d.client.query(ctx, query, setVals...) if err != nil { return nil, err } - results := make([]*{{ .Model.Name }}, 0) - for rows.Next() { - var res {{ .Model.Name }} - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - rows.Close() - return nil, err - } - results = append(results, &res) - } - rowsErr := rows.Err() - rows.Close() - if rowsErr != nil { - return nil, rowsErr + scanned, err := scan{{ .Model.Name }}Rows(rows, returningCols) + if err != nil { + return nil, err } if selects != nil && selects.hasAnyRelation() { - if err := d.loadRelations(ctx, results, selects); err != nil { + if err := d.loadRelations(ctx, scanned, selects); err != nil { return nil, err } } - return results, nil + return scanned, nil } -func (d *{{ .Model.Name }}Delegate) runUpdateManyAndReturnFallback(ctx context.Context, preds []PredicateOf[{{ .Model.Name }}], assignments []FieldAssignment, selects *{{ .Model.Name }}Select, omits *{{ .Model.Name }}Omit) ([]*{{ .Model.Name }}, error) { - affected, err := d.execUpdateStmt(ctx, preds, assignments) +func (d *{{ .Model.Name }}Delegate) runUpdateManyAndReturnFallback(ctx context.Context, preds []PredicateOf[{{ .Model.Name }}], cols []string, vals []any, selects *{{ .Model.Name }}Select, omits *{{ .Model.Name }}Omit) ([]*{{ .Model.Name }}, error) { + affected, err := d.execUpdateStmt(ctx, preds, cols, vals) if err != nil { return nil, err } diff --git a/generator/templates/relations_runtime.gotpl b/generator/templates/relations_runtime.gotpl index 0bdf20d..a58e9b9 100644 --- a/generator/templates/relations_runtime.gotpl +++ b/generator/templates/relations_runtime.gotpl @@ -326,4 +326,184 @@ func buildSingleInsertSQL( return sb.String(), clauseArgs } +func loadRelation[P any, C any]( + ctx context.Context, + q *Queries, + parents []*P, + parentKey func(*P) (string, bool), + table string, + fkCol string, + returningCols []string, + scan func(*sql.Rows, *C) error, + childKey func(*C) (string, bool), + assign func(*P, []*C), + params QueryParams[C], +) ([]*C, error) { + var parentKeys []any + for _, p := range parents { + if p == nil { + continue + } + if key, ok := parentKey(p); ok { + parentKeys = append(parentKeys, key) + } + } + if len(parentKeys) == 0 { + return nil, nil + } + + // Prepend parent ID checks to filters using Predicate[C] + allPreds := append([]PredicateOf[C]{ + Predicate[C]{ + Data: PredicateData{ + Column: fkCol, + Operator: "IN", + Value: parentKeys, + IsLogical: false, + }, + }, + }, params.Where...) + + whereClause, vals, nextIdx := CompilePredicates(q.dialect, allPreds) + isCursorQuery := (params.Cursor.Data.Column != "" || len(params.Cursor.Data.Children) > 0) + if isCursorQuery { + cClause, cVals, err := compileCursorClause(q.dialect, params.Cursor, params.OrderBy, []string{"id"}, nil, table, nextIdx, params.Take) + if err != nil { + return nil, err + } + if cClause != "" { + if whereClause == "" { + whereClause = cClause + } else { + whereClause = "(" + whereClause + ") AND " + cClause + } + vals = append(vals, cVals...) + } + } + if whereClause != "" { + whereClause = " WHERE " + whereClause + } + + query := compileRelationSQL(q.dialect, table, fkCol, returningCols, whereClause, params) + + rows, err := q.query(ctx, query, vals...) + if err != nil { + return nil, err + } + defer rows.Close() + + childMap := make(map[string][]*C, len(parents)) + allChildren := make([]*C, 0, len(parents)) + + for rows.Next() { + var child C + if err := scan(rows, &child); err != nil { + return nil, err + } + if key, ok := childKey(&child); ok { + childMap[key] = append(childMap[key], &child) + } + allChildren = append(allChildren, &child) + } + if err := rows.Err(); err != nil { + return nil, err + } + + for _, p := range parents { + if p == nil { + continue + } + if key, ok := parentKey(p); ok { + assign(p, childMap[key]) + } + } + + return allChildren, nil +} + +func compileRelationSQL[M any](dialect Dialect, table, fkCol string, cols []string, where string, params QueryParams[M]) string { + isCursorQuery := (params.Cursor.Data.Column != "" || len(params.Cursor.Data.Children) > 0) + if params.Take != nil || params.Skip != nil || isCursorQuery { + return compilePartitionedRelationSQL(dialect, table, fkCol, cols, where, params) + } + return compileSimpleRelationSQL(dialect, table, cols, where, params) +} + +func compilePartitionedRelationSQL[M any](dialect Dialect, table, fkCol string, cols []string, where string, params QueryParams[M]) string { + var innerSb strings.Builder + innerSb.WriteString("SELECT ") + for i, col := range cols { + if i > 0 { + innerSb.WriteString(", ") + } + innerSb.WriteString(dialect.Quote(col)) + } + innerSb.WriteString(", ROW_NUMBER() OVER (PARTITION BY ") + innerSb.WriteString(dialect.Quote(fkCol)) + innerSb.WriteString(" ORDER BY ") + if len(params.OrderBy) > 0 { + for i, ord := range params.OrderBy { + if i > 0 { + innerSb.WriteString(", ") + } + innerSb.WriteString(dialect.Quote(ord.Field)) + innerSb.WriteString(" ") + innerSb.WriteString(string(ord.Direction)) + } + } else { + innerSb.WriteString(dialect.Quote("id")) + innerSb.WriteString(" ASC") + } + innerSb.WriteString(") as row_num FROM ") + innerSb.WriteString(dialect.Quote(table)) + innerSb.WriteString(where) + + var outerSb strings.Builder + outerSb.WriteString("SELECT ") + for i, col := range cols { + if i > 0 { + outerSb.WriteString(", ") + } + outerSb.WriteString(dialect.Quote(col)) + } + outerSb.WriteString(" FROM (") + outerSb.WriteString(innerSb.String()) + outerSb.WriteString(") t WHERE ") + + if params.Take != nil && params.Skip != nil { + outerSb.WriteString(fmt.Sprintf("row_num > %d AND row_num <= %d", *params.Skip, *params.Skip+*params.Take)) + } else if params.Take != nil { + outerSb.WriteString(fmt.Sprintf("row_num <= %d", *params.Take)) + } else if params.Skip != nil { + outerSb.WriteString(fmt.Sprintf("row_num > %d", *params.Skip)) + } + return outerSb.String() +} + +func compileSimpleRelationSQL[M any](dialect Dialect, table string, cols []string, where string, params QueryParams[M]) string { + var sb strings.Builder + sb.WriteString("SELECT ") + for i, col := range cols { + if i > 0 { + sb.WriteString(", ") + } + sb.WriteString(dialect.Quote(col)) + } + sb.WriteString(" FROM ") + sb.WriteString(dialect.Quote(table)) + sb.WriteString(where) + if len(params.OrderBy) > 0 { + sb.WriteString(" ORDER BY ") + for i, ord := range params.OrderBy { + if i > 0 { + sb.WriteString(", ") + } + sb.WriteString(dialect.Quote(ord.Field)) + sb.WriteString(" ") + sb.WriteString(string(ord.Direction)) + } + } + return sb.String() +} + From a72e9bacdfb19dabba9a9d8af5ec44c50ff8b3e0 Mon Sep 17 00:00:00 2001 From: Clancy Date: Thu, 30 Jul 2026 14:30:24 +0300 Subject: [PATCH 2/2] updated generated client after standardizing Update struct, hooks, and fluent builder chaining --- integration/main.go | 52 +- integration/valk/allFieldsSoFar.go | 1385 ++++++++++++++++++++++------ integration/valk/category.go | 671 ++++++++------ integration/valk/categoryToPost.go | 671 ++++++++------ integration/valk/client.go | 442 +++++---- integration/valk/comment.go | 755 +++++++++------ integration/valk/defaultsTest.go | 769 +++++++++------ integration/valk/post.go | 713 ++++++++------ integration/valk/profile.go | 699 ++++++++------ integration/valk/user.go | 765 +++++++++------ 10 files changed, 4633 insertions(+), 2289 deletions(-) diff --git a/integration/main.go b/integration/main.go index 6a796d8..93e3d04 100644 --- a/integration/main.go +++ b/integration/main.go @@ -57,12 +57,11 @@ func main() { } db := openConn() // db := openPGConn() - defer dbReset(db) + // defer dbReset(db) defer db.Close() rawDB := db.Raw() rawDB.SetMaxOpenConns(10) ctx := context.Background() - runMigrations(db, ctx) // seed(db, ctx) @@ -71,6 +70,7 @@ func main() { // runPaginationExamples(db, ctx) // runExtensionExamples(db, ctx) runCTP(db, ctx) + db.User.FindMany().Exec(ctx) } @@ -117,6 +117,34 @@ func runExtensionExamples(db *valk.DB, ctx context.Context) { DeleteMany: func(ctx context.Context, args *valk.UserDeleteManyArgs, next valk.UserDeleteManyQuery) (int64, error) { return next(ctx, args) }, + Update: func(ctx context.Context, args *valk.UserUpdateArgs, next valk.UserUpdateQuery) (*valk.User, error) { + if args.Data.Email != nil { + lower := strings.ToLower(*args.Data.Email) + args.Data.Email = &lower + } + + for _, w := range args.Where { + if w.Column() == user.Email.Column { + + } + if w.Column() == user.EmailPhone.Column { + + } + } + return next(ctx, args) + }, + UpdateMany: func(ctx context.Context, args *valk.UserUpdateManyArgs, next valk.UserUpdateManyQuery) (int64, error) { + + return next(ctx, args) + }, + + UpdateManyAndReturn: func(ctx context.Context, + args *valk.UserUpdateManyAndReturnArgs, + next valk.UserUpdateManyAndReturnQuery) ([]*valk.User, error) { + + return next(ctx, args) + + }, }) } @@ -298,6 +326,24 @@ func inconsistency() { }) db.User.DeleteMany(user.LoginCount.EQ(0)) + + db.User.Create(). + SetEmail("x"). + SetPhoneNum("x"). + OnConflict(user.Email). + Ignore(). + Select( + user.Select{ + Email: true, + Profile: &profile.Select{ + Id: true, + Bio: true, + }, + + Posts: post.Query().Where(post.Id.EQ("xx")), + }, + ) + } // ============================================================================= @@ -453,7 +499,7 @@ func seed(db *valk.DB, ctx context.Context) *SeedData { // CONNECTIONS // ============================================================================= func openConn() *valk.DB { - db, err := valk.Open("sqlite3", "file::memory:?_pragma=foreign_keys(1)&_time_format=sqlite") + db, err := valk.Open("sqlite3", "file:memdb1?mode=memory&cache=shared&_pragma=foreign_keys(1)&_time_format=sqlite") if err != nil { log.Fatalf("failed to open db: %v", err) diff --git a/integration/valk/allFieldsSoFar.go b/integration/valk/allFieldsSoFar.go index 9852061..995bb36 100644 --- a/integration/valk/allFieldsSoFar.go +++ b/integration/valk/allFieldsSoFar.go @@ -275,6 +275,770 @@ func (s *AllFieldsSoFarCreate) colMask() uint64 { return mask } +// AllFieldsSoFarUpdate contains model input fields for AllFieldsSoFar update operations. +type AllFieldsSoFarUpdate struct { + Id *int32 `json:"id"` + StringReq *string `json:"stringReq"` + StringOpt *string `json:"stringOpt"` + StringDefault *string `json:"stringDefault"` + StringVarchar *string `json:"stringVarchar"` + StringChar *string `json:"stringChar"` + BitVal *string `json:"bitVal"` + VarBitVal *string `json:"varBitVal"` + InetVal *string `json:"inetVal"` + XmlVal *string `json:"xmlVal"` + CuidDefault *string `json:"cuidDefault"` + Cuid1Default *string `json:"cuid1Default"` + Cuid2Default *string `json:"cuid2Default"` + UuidDefault *string `json:"uuidDefault"` + Uuid4Default *string `json:"uuid4Default"` + Uuid7Default *string `json:"uuid7Default"` + UlidDefault *string `json:"ulidDefault"` + NanoidDefault *string `json:"nanoidDefault"` + UuidDb *string `json:"uuidDb"` + IntReq *int32 `json:"intReq"` + IntOpt *int32 `json:"intOpt"` + IntDefault *int32 `json:"intDefault"` + IntegerVal *int32 `json:"integerVal"` + SmallInt *int32 `json:"smallInt"` + TinyInt *int32 `json:"tinyInt"` + OidVal *int32 `json:"oidVal"` + BigIntReq *int64 `json:"bigIntReq"` + BigIntOpt *int64 `json:"bigIntOpt"` + FloatReq *float64 `json:"floatReq"` + FloatOpt *float64 `json:"floatOpt"` + RealVal *float64 `json:"realVal"` + DecimalReq *string `json:"decimalReq"` + DecimalOpt *string `json:"decimalOpt"` + DecimalPrecise *string `json:"decimalPrecise"` + MoneyVal *string `json:"moneyVal"` + BoolReq *bool `json:"boolReq"` + BoolOpt *bool `json:"boolOpt"` + BoolDefault *bool `json:"boolDefault"` + DateTimeReq *time.Time `json:"dateTimeReq"` + DateTimeOpt *time.Time `json:"dateTimeOpt"` + DateTimeDefault *time.Time `json:"dateTimeDefault"` + UpdatedAt *time.Time `json:"updatedAt"` + DateTimeTz *time.Time `json:"dateTimeTz"` + TimestampVal *time.Time `json:"timestampVal"` + TimeVal *time.Time `json:"timeVal"` + TimetzVal *time.Time `json:"timetzVal"` + JsonReq *json.RawMessage `json:"jsonReq"` + JsonOpt *json.RawMessage `json:"jsonOpt"` + JsonVal *json.RawMessage `json:"jsonVal"` + BytesReq *[]byte `json:"bytesReq"` + BytesOpt *[]byte `json:"bytesOpt"` + HstoreField *map[string]*string `json:"hstoreField"` + LtreeField *string `json:"ltreeField"` + CitextField *string `json:"citextField"` +} + +func (u *AllFieldsSoFarUpdate) ToColsVals() ([]string, []any) { + var cols []string + var vals []any + if u.Id != nil { + cols = append(cols, "id") + vals = append(vals, u.Id) + } + if u.StringReq != nil { + cols = append(cols, "stringReq") + vals = append(vals, u.StringReq) + } + if u.StringOpt != nil { + cols = append(cols, "stringOpt") + vals = append(vals, u.StringOpt) + } + if u.StringDefault != nil { + cols = append(cols, "stringDefault") + vals = append(vals, u.StringDefault) + } + if u.StringVarchar != nil { + cols = append(cols, "stringVarchar") + vals = append(vals, u.StringVarchar) + } + if u.StringChar != nil { + cols = append(cols, "stringChar") + vals = append(vals, u.StringChar) + } + if u.BitVal != nil { + cols = append(cols, "bitVal") + vals = append(vals, u.BitVal) + } + if u.VarBitVal != nil { + cols = append(cols, "varBitVal") + vals = append(vals, u.VarBitVal) + } + if u.InetVal != nil { + cols = append(cols, "inetVal") + vals = append(vals, u.InetVal) + } + if u.XmlVal != nil { + cols = append(cols, "xmlVal") + vals = append(vals, u.XmlVal) + } + if u.CuidDefault != nil { + cols = append(cols, "cuidDefault") + vals = append(vals, u.CuidDefault) + } + if u.Cuid1Default != nil { + cols = append(cols, "cuid1Default") + vals = append(vals, u.Cuid1Default) + } + if u.Cuid2Default != nil { + cols = append(cols, "cuid2Default") + vals = append(vals, u.Cuid2Default) + } + if u.UuidDefault != nil { + cols = append(cols, "uuidDefault") + vals = append(vals, u.UuidDefault) + } + if u.Uuid4Default != nil { + cols = append(cols, "uuid4Default") + vals = append(vals, u.Uuid4Default) + } + if u.Uuid7Default != nil { + cols = append(cols, "uuid7Default") + vals = append(vals, u.Uuid7Default) + } + if u.UlidDefault != nil { + cols = append(cols, "ulidDefault") + vals = append(vals, u.UlidDefault) + } + if u.NanoidDefault != nil { + cols = append(cols, "nanoidDefault") + vals = append(vals, u.NanoidDefault) + } + if u.UuidDb != nil { + cols = append(cols, "uuidDb") + vals = append(vals, u.UuidDb) + } + if u.IntReq != nil { + cols = append(cols, "intReq") + vals = append(vals, u.IntReq) + } + if u.IntOpt != nil { + cols = append(cols, "intOpt") + vals = append(vals, u.IntOpt) + } + if u.IntDefault != nil { + cols = append(cols, "intDefault") + vals = append(vals, u.IntDefault) + } + if u.IntegerVal != nil { + cols = append(cols, "integerVal") + vals = append(vals, u.IntegerVal) + } + if u.SmallInt != nil { + cols = append(cols, "smallInt") + vals = append(vals, u.SmallInt) + } + if u.TinyInt != nil { + cols = append(cols, "tinyInt") + vals = append(vals, u.TinyInt) + } + if u.OidVal != nil { + cols = append(cols, "oidVal") + vals = append(vals, u.OidVal) + } + if u.BigIntReq != nil { + cols = append(cols, "bigIntReq") + vals = append(vals, u.BigIntReq) + } + if u.BigIntOpt != nil { + cols = append(cols, "bigIntOpt") + vals = append(vals, u.BigIntOpt) + } + if u.FloatReq != nil { + cols = append(cols, "floatReq") + vals = append(vals, u.FloatReq) + } + if u.FloatOpt != nil { + cols = append(cols, "floatOpt") + vals = append(vals, u.FloatOpt) + } + if u.RealVal != nil { + cols = append(cols, "realVal") + vals = append(vals, u.RealVal) + } + if u.DecimalReq != nil { + cols = append(cols, "decimalReq") + vals = append(vals, u.DecimalReq) + } + if u.DecimalOpt != nil { + cols = append(cols, "decimalOpt") + vals = append(vals, u.DecimalOpt) + } + if u.DecimalPrecise != nil { + cols = append(cols, "decimalPrecise") + vals = append(vals, u.DecimalPrecise) + } + if u.MoneyVal != nil { + cols = append(cols, "moneyVal") + vals = append(vals, u.MoneyVal) + } + if u.BoolReq != nil { + cols = append(cols, "boolReq") + vals = append(vals, u.BoolReq) + } + if u.BoolOpt != nil { + cols = append(cols, "boolOpt") + vals = append(vals, u.BoolOpt) + } + if u.BoolDefault != nil { + cols = append(cols, "boolDefault") + vals = append(vals, u.BoolDefault) + } + if u.DateTimeReq != nil { + cols = append(cols, "dateTimeReq") + vals = append(vals, u.DateTimeReq) + } + if u.DateTimeOpt != nil { + cols = append(cols, "dateTimeOpt") + vals = append(vals, u.DateTimeOpt) + } + if u.DateTimeDefault != nil { + cols = append(cols, "dateTimeDefault") + vals = append(vals, u.DateTimeDefault) + } + if u.UpdatedAt != nil { + cols = append(cols, "updatedAt") + vals = append(vals, u.UpdatedAt) + } + if u.DateTimeTz != nil { + cols = append(cols, "dateTimeTz") + vals = append(vals, u.DateTimeTz) + } + if u.TimestampVal != nil { + cols = append(cols, "timestampVal") + vals = append(vals, u.TimestampVal) + } + if u.TimeVal != nil { + cols = append(cols, "timeVal") + vals = append(vals, u.TimeVal) + } + if u.TimetzVal != nil { + cols = append(cols, "timetzVal") + vals = append(vals, u.TimetzVal) + } + if u.JsonReq != nil { + cols = append(cols, "jsonReq") + vals = append(vals, u.JsonReq) + } + if u.JsonOpt != nil { + cols = append(cols, "jsonOpt") + vals = append(vals, u.JsonOpt) + } + if u.JsonVal != nil { + cols = append(cols, "jsonVal") + vals = append(vals, u.JsonVal) + } + if u.BytesReq != nil { + cols = append(cols, "bytesReq") + vals = append(vals, u.BytesReq) + } + if u.BytesOpt != nil { + cols = append(cols, "bytesOpt") + vals = append(vals, u.BytesOpt) + } + if u.HstoreField != nil { + cols = append(cols, "hstoreField") + vals = append(vals, u.HstoreField) + } + if u.LtreeField != nil { + cols = append(cols, "ltreeField") + vals = append(vals, u.LtreeField) + } + if u.CitextField != nil { + cols = append(cols, "citextField") + vals = append(vals, u.CitextField) + } + return cols, vals +} + +func assignmentsToAllFieldsSoFarUpdate(assignments []FieldAssignment) (AllFieldsSoFarUpdate, error) { + var input AllFieldsSoFarUpdate + var errs ValidationError + + for _, a := range assignments { + switch a.Col { + case "id": + if v, ok := a.Val.(int32); ok { + input.Id = &v + } else if v, ok := a.Val.(*int32); ok { + input.Id = v + } else { + errs.Add("id", a.Val, "type", "field id must be of type int32") + } + case "stringReq": + if v, ok := a.Val.(string); ok { + input.StringReq = &v + errs.ValidateString("stringReq", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.StringReq = v + } else { + errs.Add("stringReq", a.Val, "type", "field stringReq must be of type string") + } + case "stringOpt": + if v, ok := a.Val.(string); ok { + input.StringOpt = &v + errs.ValidateString("stringOpt", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.StringOpt = v + } else { + errs.Add("stringOpt", a.Val, "type", "field stringOpt must be of type string") + } + case "stringDefault": + if v, ok := a.Val.(string); ok { + input.StringDefault = &v + errs.ValidateString("stringDefault", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.StringDefault = v + } else { + errs.Add("stringDefault", a.Val, "type", "field stringDefault must be of type string") + } + case "stringVarchar": + if v, ok := a.Val.(string); ok { + input.StringVarchar = &v + errs.ValidateString("stringVarchar", v, false, 255, false, false) + } else if v, ok := a.Val.(*string); ok { + input.StringVarchar = v + } else { + errs.Add("stringVarchar", a.Val, "type", "field stringVarchar must be of type string") + } + case "stringChar": + if v, ok := a.Val.(string); ok { + input.StringChar = &v + errs.ValidateString("stringChar", v, false, 10, false, false) + } else if v, ok := a.Val.(*string); ok { + input.StringChar = v + } else { + errs.Add("stringChar", a.Val, "type", "field stringChar must be of type string") + } + case "bitVal": + if v, ok := a.Val.(string); ok { + input.BitVal = &v + errs.ValidateString("bitVal", v, false, 0, true, false) + } else if v, ok := a.Val.(*string); ok { + input.BitVal = v + } else { + errs.Add("bitVal", a.Val, "type", "field bitVal must be of type string") + } + case "varBitVal": + if v, ok := a.Val.(string); ok { + input.VarBitVal = &v + errs.ValidateString("varBitVal", v, false, 0, true, false) + } else if v, ok := a.Val.(*string); ok { + input.VarBitVal = v + } else { + errs.Add("varBitVal", a.Val, "type", "field varBitVal must be of type string") + } + case "inetVal": + if v, ok := a.Val.(string); ok { + input.InetVal = &v + errs.ValidateString("inetVal", v, false, 0, false, true) + } else if v, ok := a.Val.(*string); ok { + input.InetVal = v + } else { + errs.Add("inetVal", a.Val, "type", "field inetVal must be of type string") + } + case "xmlVal": + if v, ok := a.Val.(string); ok { + input.XmlVal = &v + errs.ValidateString("xmlVal", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.XmlVal = v + } else { + errs.Add("xmlVal", a.Val, "type", "field xmlVal must be of type string") + } + case "cuidDefault": + if v, ok := a.Val.(string); ok { + input.CuidDefault = &v + errs.ValidateString("cuidDefault", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.CuidDefault = v + } else { + errs.Add("cuidDefault", a.Val, "type", "field cuidDefault must be of type string") + } + case "cuid1Default": + if v, ok := a.Val.(string); ok { + input.Cuid1Default = &v + errs.ValidateString("cuid1Default", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.Cuid1Default = v + } else { + errs.Add("cuid1Default", a.Val, "type", "field cuid1Default must be of type string") + } + case "cuid2Default": + if v, ok := a.Val.(string); ok { + input.Cuid2Default = &v + errs.ValidateString("cuid2Default", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.Cuid2Default = v + } else { + errs.Add("cuid2Default", a.Val, "type", "field cuid2Default must be of type string") + } + case "uuidDefault": + if v, ok := a.Val.(string); ok { + input.UuidDefault = &v + errs.ValidateString("uuidDefault", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.UuidDefault = v + } else { + errs.Add("uuidDefault", a.Val, "type", "field uuidDefault must be of type string") + } + case "uuid4Default": + if v, ok := a.Val.(string); ok { + input.Uuid4Default = &v + errs.ValidateString("uuid4Default", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.Uuid4Default = v + } else { + errs.Add("uuid4Default", a.Val, "type", "field uuid4Default must be of type string") + } + case "uuid7Default": + if v, ok := a.Val.(string); ok { + input.Uuid7Default = &v + errs.ValidateString("uuid7Default", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.Uuid7Default = v + } else { + errs.Add("uuid7Default", a.Val, "type", "field uuid7Default must be of type string") + } + case "ulidDefault": + if v, ok := a.Val.(string); ok { + input.UlidDefault = &v + errs.ValidateString("ulidDefault", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.UlidDefault = v + } else { + errs.Add("ulidDefault", a.Val, "type", "field ulidDefault must be of type string") + } + case "nanoidDefault": + if v, ok := a.Val.(string); ok { + input.NanoidDefault = &v + errs.ValidateString("nanoidDefault", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.NanoidDefault = v + } else { + errs.Add("nanoidDefault", a.Val, "type", "field nanoidDefault must be of type string") + } + case "uuidDb": + if v, ok := a.Val.(string); ok { + input.UuidDb = &v + errs.ValidateString("uuidDb", v, false, 0, false, false) + errs.ValidateUUID("uuidDb", v) + } else if v, ok := a.Val.(*string); ok { + input.UuidDb = v + } else { + errs.Add("uuidDb", a.Val, "type", "field uuidDb must be of type string") + } + case "intReq": + if v, ok := a.Val.(int32); ok { + input.IntReq = &v + } else if v, ok := a.Val.(*int32); ok { + input.IntReq = v + } else { + errs.Add("intReq", a.Val, "type", "field intReq must be of type int32") + } + case "intOpt": + if v, ok := a.Val.(int32); ok { + input.IntOpt = &v + } else if v, ok := a.Val.(*int32); ok { + input.IntOpt = v + } else { + errs.Add("intOpt", a.Val, "type", "field intOpt must be of type int32") + } + case "intDefault": + if v, ok := a.Val.(int32); ok { + input.IntDefault = &v + } else if v, ok := a.Val.(*int32); ok { + input.IntDefault = v + } else { + errs.Add("intDefault", a.Val, "type", "field intDefault must be of type int32") + } + case "integerVal": + if v, ok := a.Val.(int32); ok { + input.IntegerVal = &v + } else if v, ok := a.Val.(*int32); ok { + input.IntegerVal = v + } else { + errs.Add("integerVal", a.Val, "type", "field integerVal must be of type int32") + } + case "smallInt": + if v, ok := a.Val.(int32); ok { + input.SmallInt = &v + } else if v, ok := a.Val.(*int32); ok { + input.SmallInt = v + } else { + errs.Add("smallInt", a.Val, "type", "field smallInt must be of type int32") + } + case "tinyInt": + if v, ok := a.Val.(int32); ok { + input.TinyInt = &v + } else if v, ok := a.Val.(*int32); ok { + input.TinyInt = v + } else { + errs.Add("tinyInt", a.Val, "type", "field tinyInt must be of type int32") + } + case "oidVal": + if v, ok := a.Val.(int32); ok { + input.OidVal = &v + } else if v, ok := a.Val.(*int32); ok { + input.OidVal = v + } else { + errs.Add("oidVal", a.Val, "type", "field oidVal must be of type int32") + } + case "bigIntReq": + if v, ok := a.Val.(int64); ok { + input.BigIntReq = &v + } else if v, ok := a.Val.(*int64); ok { + input.BigIntReq = v + } else { + errs.Add("bigIntReq", a.Val, "type", "field bigIntReq must be of type int64") + } + case "bigIntOpt": + if v, ok := a.Val.(int64); ok { + input.BigIntOpt = &v + } else if v, ok := a.Val.(*int64); ok { + input.BigIntOpt = v + } else { + errs.Add("bigIntOpt", a.Val, "type", "field bigIntOpt must be of type int64") + } + case "floatReq": + if v, ok := a.Val.(float64); ok { + input.FloatReq = &v + } else if v, ok := a.Val.(*float64); ok { + input.FloatReq = v + } else { + errs.Add("floatReq", a.Val, "type", "field floatReq must be of type float64") + } + case "floatOpt": + if v, ok := a.Val.(float64); ok { + input.FloatOpt = &v + } else if v, ok := a.Val.(*float64); ok { + input.FloatOpt = v + } else { + errs.Add("floatOpt", a.Val, "type", "field floatOpt must be of type float64") + } + case "realVal": + if v, ok := a.Val.(float64); ok { + input.RealVal = &v + } else if v, ok := a.Val.(*float64); ok { + input.RealVal = v + } else { + errs.Add("realVal", a.Val, "type", "field realVal must be of type float64") + } + case "decimalReq": + if v, ok := a.Val.(string); ok { + input.DecimalReq = &v + errs.ValidateString("decimalReq", v, false, 0, false, false) + errs.ValidateDecimal("decimalReq", v, 0) + } else if v, ok := a.Val.(*string); ok { + input.DecimalReq = v + } else { + errs.Add("decimalReq", a.Val, "type", "field decimalReq must be of type string") + } + case "decimalOpt": + if v, ok := a.Val.(string); ok { + input.DecimalOpt = &v + errs.ValidateString("decimalOpt", v, false, 0, false, false) + errs.ValidateDecimal("decimalOpt", v, 0) + } else if v, ok := a.Val.(*string); ok { + input.DecimalOpt = v + } else { + errs.Add("decimalOpt", a.Val, "type", "field decimalOpt must be of type string") + } + case "decimalPrecise": + if v, ok := a.Val.(string); ok { + input.DecimalPrecise = &v + errs.ValidateString("decimalPrecise", v, false, 0, false, false) + errs.ValidateDecimal("decimalPrecise", v, 2) + } else if v, ok := a.Val.(*string); ok { + input.DecimalPrecise = v + } else { + errs.Add("decimalPrecise", a.Val, "type", "field decimalPrecise must be of type string") + } + case "moneyVal": + if v, ok := a.Val.(string); ok { + input.MoneyVal = &v + errs.ValidateString("moneyVal", v, false, 0, false, false) + errs.ValidateDecimal("moneyVal", v, 0) + } else if v, ok := a.Val.(*string); ok { + input.MoneyVal = v + } else { + errs.Add("moneyVal", a.Val, "type", "field moneyVal must be of type string") + } + case "boolReq": + if v, ok := a.Val.(bool); ok { + input.BoolReq = &v + } else if v, ok := a.Val.(*bool); ok { + input.BoolReq = v + } else { + errs.Add("boolReq", a.Val, "type", "field boolReq must be of type bool") + } + case "boolOpt": + if v, ok := a.Val.(bool); ok { + input.BoolOpt = &v + } else if v, ok := a.Val.(*bool); ok { + input.BoolOpt = v + } else { + errs.Add("boolOpt", a.Val, "type", "field boolOpt must be of type bool") + } + case "boolDefault": + if v, ok := a.Val.(bool); ok { + input.BoolDefault = &v + } else if v, ok := a.Val.(*bool); ok { + input.BoolDefault = v + } else { + errs.Add("boolDefault", a.Val, "type", "field boolDefault must be of type bool") + } + case "dateTimeReq": + if v, ok := a.Val.(time.Time); ok { + input.DateTimeReq = &v + } else if v, ok := a.Val.(*time.Time); ok { + input.DateTimeReq = v + } else { + errs.Add("dateTimeReq", a.Val, "type", "field dateTimeReq must be of type time.Time") + } + case "dateTimeOpt": + if v, ok := a.Val.(time.Time); ok { + input.DateTimeOpt = &v + } else if v, ok := a.Val.(*time.Time); ok { + input.DateTimeOpt = v + } else { + errs.Add("dateTimeOpt", a.Val, "type", "field dateTimeOpt must be of type time.Time") + } + case "dateTimeDefault": + if v, ok := a.Val.(time.Time); ok { + input.DateTimeDefault = &v + } else if v, ok := a.Val.(*time.Time); ok { + input.DateTimeDefault = v + } else { + errs.Add("dateTimeDefault", a.Val, "type", "field dateTimeDefault must be of type time.Time") + } + case "updatedAt": + if v, ok := a.Val.(time.Time); ok { + input.UpdatedAt = &v + } else if v, ok := a.Val.(*time.Time); ok { + input.UpdatedAt = v + } else { + errs.Add("updatedAt", a.Val, "type", "field updatedAt must be of type time.Time") + } + case "dateTimeTz": + if v, ok := a.Val.(time.Time); ok { + input.DateTimeTz = &v + } else if v, ok := a.Val.(*time.Time); ok { + input.DateTimeTz = v + } else { + errs.Add("dateTimeTz", a.Val, "type", "field dateTimeTz must be of type time.Time") + } + case "timestampVal": + if v, ok := a.Val.(time.Time); ok { + input.TimestampVal = &v + } else if v, ok := a.Val.(*time.Time); ok { + input.TimestampVal = v + } else { + errs.Add("timestampVal", a.Val, "type", "field timestampVal must be of type time.Time") + } + case "timeVal": + if v, ok := a.Val.(time.Time); ok { + input.TimeVal = &v + } else if v, ok := a.Val.(*time.Time); ok { + input.TimeVal = v + } else { + errs.Add("timeVal", a.Val, "type", "field timeVal must be of type time.Time") + } + case "timetzVal": + if v, ok := a.Val.(time.Time); ok { + input.TimetzVal = &v + } else if v, ok := a.Val.(*time.Time); ok { + input.TimetzVal = v + } else { + errs.Add("timetzVal", a.Val, "type", "field timetzVal must be of type time.Time") + } + case "jsonReq": + if v, ok := a.Val.(json.RawMessage); ok { + input.JsonReq = &v + } else if v, ok := a.Val.(*json.RawMessage); ok { + input.JsonReq = v + } else if v, ok := a.Val.(json.RawMessage); ok { + input.JsonReq = &v + } else { + errs.Add("jsonReq", a.Val, "type", "field jsonReq must be of type json.RawMessage") + } + case "jsonOpt": + if v, ok := a.Val.(json.RawMessage); ok { + input.JsonOpt = &v + } else if v, ok := a.Val.(*json.RawMessage); ok { + input.JsonOpt = v + } else if v, ok := a.Val.(*json.RawMessage); ok { + input.JsonOpt = v + } else { + errs.Add("jsonOpt", a.Val, "type", "field jsonOpt must be of type *json.RawMessage") + } + case "jsonVal": + if v, ok := a.Val.(json.RawMessage); ok { + input.JsonVal = &v + } else if v, ok := a.Val.(*json.RawMessage); ok { + input.JsonVal = v + } else if v, ok := a.Val.(json.RawMessage); ok { + input.JsonVal = &v + } else { + errs.Add("jsonVal", a.Val, "type", "field jsonVal must be of type json.RawMessage") + } + case "bytesReq": + if v, ok := a.Val.([]byte); ok { + input.BytesReq = &v + } else if v, ok := a.Val.(*[]byte); ok { + input.BytesReq = v + } else { + errs.Add("bytesReq", a.Val, "type", "field bytesReq must be of type []byte") + } + case "bytesOpt": + if v, ok := a.Val.([]byte); ok { + input.BytesOpt = &v + } else if v, ok := a.Val.(*[]byte); ok { + input.BytesOpt = v + } else { + errs.Add("bytesOpt", a.Val, "type", "field bytesOpt must be of type []byte") + } + case "hstoreField": + if v, ok := a.Val.(map[string]*string); ok { + input.HstoreField = &v + } else if v, ok := a.Val.(*map[string]*string); ok { + input.HstoreField = v + } else if v, ok := a.Val.(*map[string]*string); ok { + input.HstoreField = v + } else { + errs.Add("hstoreField", a.Val, "type", "field hstoreField must be of type *map[string]*string") + } + case "ltreeField": + if v, ok := a.Val.(string); ok { + input.LtreeField = &v + errs.ValidateString("ltreeField", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.LtreeField = v + } else { + errs.Add("ltreeField", a.Val, "type", "field ltreeField must be of type string") + } + case "citextField": + if v, ok := a.Val.(string); ok { + input.CitextField = &v + errs.ValidateString("citextField", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.CitextField = v + } else { + errs.Add("citextField", a.Val, "type", "field citextField must be of type string") + } + } + } + + if errs.HasErrors() { + return input, errs + } + return input, nil +} + // AllFieldsSoFarSelect specifies which scalar and relation fields to select for AllFieldsSoFar. // // Selectable fields: @@ -964,6 +1728,51 @@ func (a *AllFieldsSoFarDeleteManyArgs) SetWhere(preds ...PredicateOf[AllFieldsSo return a } +// AllFieldsSoFarUpdateArgs is the input argument passed to AllFieldsSoFar Update extension hooks. +type AllFieldsSoFarUpdateArgs struct { + // Where contains all query filter predicates (merged primary unique constraint and additional predicates). + Where []PredicateOf[AllFieldsSoFar] + // Data contains the model fields to update. + Data *AllFieldsSoFarUpdate + // Select specifies which scalar and relation fields to select and return upon update. + Select *AllFieldsSoFarSelect +} + +func (a *AllFieldsSoFarUpdateArgs) SetWhere(unique UniquePredicate[AllFieldsSoFar], additional ...PredicateOf[AllFieldsSoFar]) *AllFieldsSoFarUpdateArgs { + a.Where = make([]PredicateOf[AllFieldsSoFar], 0, 1+len(additional)) + a.Where = append(a.Where, unique) + a.Where = append(a.Where, additional...) + return a +} + +// AllFieldsSoFarUpdateManyArgs is the input argument passed to AllFieldsSoFar UpdateMany extension hooks. +type AllFieldsSoFarUpdateManyArgs struct { + // Where contains all query filter predicates. + Where []PredicateOf[AllFieldsSoFar] + // Data contains the model fields to update. + Data *AllFieldsSoFarUpdate +} + +func (a *AllFieldsSoFarUpdateManyArgs) SetWhere(preds ...PredicateOf[AllFieldsSoFar]) *AllFieldsSoFarUpdateManyArgs { + a.Where = preds + return a +} + +// AllFieldsSoFarUpdateManyAndReturnArgs is the input argument passed to AllFieldsSoFar UpdateManyAndReturn extension hooks. +type AllFieldsSoFarUpdateManyAndReturnArgs struct { + // Where contains all query filter predicates. + Where []PredicateOf[AllFieldsSoFar] + // Data contains the model fields to update. + Data *AllFieldsSoFarUpdate + // Select specifies which scalar and relation fields to select and return upon update. + Select *AllFieldsSoFarSelect +} + +func (a *AllFieldsSoFarUpdateManyAndReturnArgs) SetWhere(preds ...PredicateOf[AllFieldsSoFar]) *AllFieldsSoFarUpdateManyAndReturnArgs { + a.Where = preds + return a +} + type AllFieldsSoFarCreateQuery = func(ctx context.Context, args *AllFieldsSoFarCreateArgs) (*AllFieldsSoFar, error) type AllFieldsSoFarCreateManyQuery = func(ctx context.Context, args *AllFieldsSoFarCreateManyArgs) (int64, error) type AllFieldsSoFarCreateManyAndReturnQuery = func(ctx context.Context, args *AllFieldsSoFarCreateManyAndReturnArgs) ([]*AllFieldsSoFar, error) @@ -973,9 +1782,9 @@ type AllFieldsSoFarFindManyQuery = func(ctx context.Context, args *AllFieldsSoFa type AllFieldsSoFarDeleteQuery = func(ctx context.Context, args *AllFieldsSoFarDeleteArgs) (*AllFieldsSoFar, error) type AllFieldsSoFarDeleteManyQuery = func(ctx context.Context, args *AllFieldsSoFarDeleteManyArgs) (int64, error) type AllFieldsSoFarCountQuery = func(ctx context.Context, args *AllFieldsSoFarCountArgs) (int64, error) -type AllFieldsSoFarUpdateQuery = func(ctx context.Context, where UniquePredicate[AllFieldsSoFar], additional []PredicateOf[AllFieldsSoFar], assignments []FieldAssignment, selects *AllFieldsSoFarSelect, omits *AllFieldsSoFarOmit) (*AllFieldsSoFar, error) -type AllFieldsSoFarUpdateManyQuery = func(ctx context.Context, preds []PredicateOf[AllFieldsSoFar], assignments []FieldAssignment) (int64, error) -type AllFieldsSoFarUpdateManyAndReturnQuery = func(ctx context.Context, preds []PredicateOf[AllFieldsSoFar], assignments []FieldAssignment, selects *AllFieldsSoFarSelect, omits *AllFieldsSoFarOmit) ([]*AllFieldsSoFar, error) +type AllFieldsSoFarUpdateQuery = func(ctx context.Context, args *AllFieldsSoFarUpdateArgs) (*AllFieldsSoFar, error) +type AllFieldsSoFarUpdateManyQuery = func(ctx context.Context, args *AllFieldsSoFarUpdateManyArgs) (int64, error) +type AllFieldsSoFarUpdateManyAndReturnQuery = func(ctx context.Context, args *AllFieldsSoFarUpdateManyAndReturnArgs) ([]*AllFieldsSoFar, error) type AllFieldsSoFarExtension struct { Create func(ctx context.Context, args *AllFieldsSoFarCreateArgs, next AllFieldsSoFarCreateQuery) (*AllFieldsSoFar, error) @@ -987,9 +1796,9 @@ type AllFieldsSoFarExtension struct { Delete func(ctx context.Context, args *AllFieldsSoFarDeleteArgs, next AllFieldsSoFarDeleteQuery) (*AllFieldsSoFar, error) DeleteMany func(ctx context.Context, args *AllFieldsSoFarDeleteManyArgs, next AllFieldsSoFarDeleteManyQuery) (int64, error) Count func(ctx context.Context, args *AllFieldsSoFarCountArgs, next AllFieldsSoFarCountQuery) (int64, error) - Update func(ctx context.Context, where UniquePredicate[AllFieldsSoFar], additional []PredicateOf[AllFieldsSoFar], assignments []FieldAssignment, selects *AllFieldsSoFarSelect, omits *AllFieldsSoFarOmit, next AllFieldsSoFarUpdateQuery) (*AllFieldsSoFar, error) - UpdateMany func(ctx context.Context, preds []PredicateOf[AllFieldsSoFar], assignments []FieldAssignment, next AllFieldsSoFarUpdateManyQuery) (int64, error) - UpdateManyAndReturn func(ctx context.Context, preds []PredicateOf[AllFieldsSoFar], assignments []FieldAssignment, selects *AllFieldsSoFarSelect, omits *AllFieldsSoFarOmit, next AllFieldsSoFarUpdateManyAndReturnQuery) ([]*AllFieldsSoFar, error) + Update func(ctx context.Context, args *AllFieldsSoFarUpdateArgs, next AllFieldsSoFarUpdateQuery) (*AllFieldsSoFar, error) + UpdateMany func(ctx context.Context, args *AllFieldsSoFarUpdateManyArgs, next AllFieldsSoFarUpdateManyQuery) (int64, error) + UpdateManyAndReturn func(ctx context.Context, args *AllFieldsSoFarUpdateManyAndReturnArgs, next AllFieldsSoFarUpdateManyAndReturnQuery) ([]*AllFieldsSoFar, error) } type AllFieldsSoFarDelegate struct { @@ -1269,6 +2078,16 @@ type AllFieldsSoFarCreateBuilder struct { *CreateBuilder[AllFieldsSoFar, AllFieldsSoFarSelect, AllFieldsSoFarOmit] } +func (b *AllFieldsSoFarCreateBuilder) Select(s AllFieldsSoFarSelect) *AllFieldsSoFarCreateBuilder { + b.selects = &s + return b +} + +func (b *AllFieldsSoFarCreateBuilder) Omit(o AllFieldsSoFarOmit) *AllFieldsSoFarCreateBuilder { + b.omits = &o + return b +} + func (b *AllFieldsSoFarCreateBuilder) OnConflict(target UniqueConstraintTarget) *AllFieldsSoFarConflictBuilder[AllFieldsSoFarCreateBuilder] { return &AllFieldsSoFarConflictBuilder[AllFieldsSoFarCreateBuilder]{ builder: b, @@ -2312,23 +3131,11 @@ func (d *AllFieldsSoFarDelegate) executeCreate(ctx context.Context, assignments return nil, err } + cols, vals := input.ToColsVals() + returningCols := selectAllFieldsSoFarCols(selects, omits) + if len(d.extensions) == 0 { - cols, vals := input.ToColsVals() - returningCols := selectAllFieldsSoFarCols(selects, omits) - hasRelations := selects.hasAnyRelation() - if hasRelations { - var res *AllFieldsSoFar - err = d.client.transaction(ctx, func(txQ *Queries) error { - var err error - res, err = txQ.AllFieldsSoFar.runCreate(ctx, cols, vals, returningCols, allFieldsSoFarPKCols, conflictTarget, conflictAction) - if err != nil { - return err - } - return txQ.AllFieldsSoFar.loadRelations(ctx, []*AllFieldsSoFar{res}, selects) - }) - return res, err - } - return d.runCreate(ctx, cols, vals, returningCols, allFieldsSoFarPKCols, conflictTarget, conflictAction) + return d.runCreate(ctx, cols, vals, returningCols, selects, conflictTarget, conflictAction) } if selects == nil || !selects.hasAnySelected() { @@ -2342,29 +3149,10 @@ func (d *AllFieldsSoFarDelegate) executeCreate(ctx context.Context, assignments ConflictAction: conflictAction, } - curr := func(c context.Context, a *AllFieldsSoFarCreateArgs) (*AllFieldsSoFar, error) { - cols, vals := a.Data.ToColsVals() - returningCols := selectAllFieldsSoFarCols(a.Select, omits) - - hasRelations := a.Select.hasAnyRelation() - var res *AllFieldsSoFar - var err error - if hasRelations { - err = d.client.transaction(c, func(txQ *Queries) error { - var err error - res, err = txQ.AllFieldsSoFar.runCreate(c, cols, vals, returningCols, allFieldsSoFarPKCols, a.ConflictTarget, a.ConflictAction) - if err != nil { - return err - } - return txQ.AllFieldsSoFar.loadRelations(c, []*AllFieldsSoFar{res}, a.Select) - }) - } else { - res, err = d.runCreate(c, cols, vals, returningCols, allFieldsSoFarPKCols, a.ConflictTarget, a.ConflictAction) - } - if err != nil { - return nil, err - } - return res, nil + curr := func(c context.Context, a *AllFieldsSoFarCreateArgs) (*AllFieldsSoFar, error) { + cCols, cVals := a.Data.ToColsVals() + cReturningCols := selectAllFieldsSoFarCols(a.Select, omits) + return d.runCreate(c, cCols, cVals, cReturningCols, a.Select, a.ConflictTarget, a.ConflictAction) } if len(d.extensions) == 1 { @@ -2405,6 +3193,16 @@ type AllFieldsSoFarCreateManyAndReturnBuilder struct { *CreateManyAndReturnBuilder[AllFieldsSoFar, AllFieldsSoFarSelect, AllFieldsSoFarOmit] } +func (b *AllFieldsSoFarCreateManyAndReturnBuilder) Select(s AllFieldsSoFarSelect) *AllFieldsSoFarCreateManyAndReturnBuilder { + b.selects = &s + return b +} + +func (b *AllFieldsSoFarCreateManyAndReturnBuilder) Omit(o AllFieldsSoFarOmit) *AllFieldsSoFarCreateManyAndReturnBuilder { + b.omits = &o + return b +} + func (b *AllFieldsSoFarCreateManyAndReturnBuilder) OnConflict(target UniqueConstraintTarget) *AllFieldsSoFarConflictBuilder[AllFieldsSoFarCreateManyAndReturnBuilder] { return &AllFieldsSoFarConflictBuilder[AllFieldsSoFarCreateManyAndReturnBuilder]{ builder: b, @@ -2416,43 +3214,51 @@ func (b *AllFieldsSoFarCreateManyAndReturnBuilder) OnConflict(target UniqueConst } } -func (d *AllFieldsSoFarDelegate) CreateMany(builders ...*AllFieldsSoFarCreateBuilder) *AllFieldsSoFarCreateManyBuilder { +func createBuildersToAllFieldsSoFarRecordInputs(builders []*AllFieldsSoFarCreateBuilder) []RecordInput { records := make([]RecordInput, len(builders)) for i, b := range builders { records[i] = RecordInput{Assignments: b.assignments} } + return records +} + +func (d *AllFieldsSoFarDelegate) CreateMany(builders ...*AllFieldsSoFarCreateBuilder) *AllFieldsSoFarCreateManyBuilder { return &AllFieldsSoFarCreateManyBuilder{ CreateManyBuilder: &CreateManyBuilder[AllFieldsSoFar]{ - records: records, + records: createBuildersToAllFieldsSoFarRecordInputs(builders), execFunc: d.executeCreateMany, }, } } func (d *AllFieldsSoFarDelegate) CreateManyAndReturn(builders ...*AllFieldsSoFarCreateBuilder) *AllFieldsSoFarCreateManyAndReturnBuilder { - records := make([]RecordInput, len(builders)) - for i, b := range builders { - records[i] = RecordInput{Assignments: b.assignments} - } return &AllFieldsSoFarCreateManyAndReturnBuilder{ CreateManyAndReturnBuilder: &CreateManyAndReturnBuilder[AllFieldsSoFar, AllFieldsSoFarSelect, AllFieldsSoFarOmit]{ - records: records, + records: createBuildersToAllFieldsSoFarRecordInputs(builders), execFunc: d.executeCreateManyAndReturn, }, } } -func (d *AllFieldsSoFarDelegate) executeCreateMany(ctx context.Context, records []RecordInput, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { +func recordsToAllFieldsSoFarCreateInputs(records []RecordInput) ([]*AllFieldsSoFarCreate, error) { structs := make([]AllFieldsSoFarCreate, len(records)) inputs := make([]*AllFieldsSoFarCreate, len(records)) for i, rec := range records { var err error structs[i], err = assignmentsToAllFieldsSoFarCreate(rec.Assignments) if err != nil { - return 0, fmt.Errorf("validation failed at index %d: %w", i, err) + return nil, fmt.Errorf("validation failed at index %d: %w", i, err) } inputs[i] = &structs[i] } + return inputs, nil +} + +func (d *AllFieldsSoFarDelegate) executeCreateMany(ctx context.Context, records []RecordInput, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { + inputs, err := recordsToAllFieldsSoFarCreateInputs(records) + if err != nil { + return 0, err + } if len(d.extensions) == 0 { return d.runCreateMany(ctx, inputs, conflictTarget, conflictAction) @@ -2488,31 +3294,12 @@ func (d *AllFieldsSoFarDelegate) executeCreateMany(ctx context.Context, records } func (d *AllFieldsSoFarDelegate) executeCreateManyAndReturn(ctx context.Context, records []RecordInput, selects *AllFieldsSoFarSelect, omits *AllFieldsSoFarOmit, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) ([]*AllFieldsSoFar, error) { - structs := make([]AllFieldsSoFarCreate, len(records)) - inputs := make([]*AllFieldsSoFarCreate, len(records)) - for i, rec := range records { - var err error - structs[i], err = assignmentsToAllFieldsSoFarCreate(rec.Assignments) - if err != nil { - return nil, fmt.Errorf("validation failed at index %d: %w", i, err) - } - inputs[i] = &structs[i] + inputs, err := recordsToAllFieldsSoFarCreateInputs(records) + if err != nil { + return nil, err } if len(d.extensions) == 0 { - hasRelations := selects != nil && selects.hasAnyRelation() - if hasRelations { - var res []*AllFieldsSoFar - err := d.client.transaction(ctx, func(txQ *Queries) error { - var err error - res, err = txQ.AllFieldsSoFar.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) - if err != nil { - return err - } - return txQ.AllFieldsSoFar.loadRelations(ctx, res, selects) - }) - return res, err - } return d.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) } @@ -2528,19 +3315,6 @@ func (d *AllFieldsSoFarDelegate) executeCreateManyAndReturn(ctx context.Context, } curr := func(c context.Context, a *AllFieldsSoFarCreateManyAndReturnArgs) ([]*AllFieldsSoFar, error) { - hasRelations := a.Select != nil && a.Select.hasAnyRelation() - if hasRelations { - var res []*AllFieldsSoFar - err := d.client.transaction(c, func(txQ *Queries) error { - var err error - res, err = txQ.AllFieldsSoFar.runCreateManyAndReturn(c, a.Data, a.Select, omits, a.ConflictTarget, a.ConflictAction) - if err != nil { - return err - } - return txQ.AllFieldsSoFar.loadRelations(c, res, a.Select) - }) - return res, err - } return d.runCreateManyAndReturn(c, a.Data, a.Select, omits, a.ConflictTarget, a.ConflictAction) } @@ -2568,36 +3342,67 @@ func (d *AllFieldsSoFarDelegate) runCreate( cols []string, vals []any, returningCols []string, - pkCols []string, + selects *AllFieldsSoFarSelect, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction, ) (*AllFieldsSoFar, error) { - query, clauseArgs := buildSingleInsertSQL(d.client, "AllFieldsSoFar", cols, returningCols, pkCols, conflictTarget, conflictAction, len(vals)) + hasRelations := selects != nil && selects.hasAnyRelation() + useTx := hasRelations && !d.client.inTx() + + if useTx { + var res *AllFieldsSoFar + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + res, err = txQ.AllFieldsSoFar.runCreate(ctx, cols, vals, returningCols, selects, conflictTarget, conflictAction) + if err != nil { + return err + } + return txQ.AllFieldsSoFar.loadRelations(ctx, []*AllFieldsSoFar{res}, selects) + }) + return res, err + } + + query, clauseArgs := buildSingleInsertSQL(d.client, "AllFieldsSoFar", cols, returningCols, allFieldsSoFarPKCols, conflictTarget, conflictAction, len(vals)) if len(clauseArgs) > 0 { vals = append(vals, clauseArgs...) } - var res AllFieldsSoFar if d.client.dialect.SupportsInsertReturning { rows, err := d.client.query(ctx, query, vals...) if err != nil { return nil, err } - defer rows.Close() - if rows.Next() { - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + if !rows.Next() { + err := rows.Err() + rows.Close() + if err != nil { return nil, err } - return &res, nil + return nil, nil + } + + var res AllFieldsSoFar + scanErr := rows.Scan(res.ScanFields(returningCols)...) + rows.Close() + if scanErr != nil { + return nil, scanErr } - return nil, rows.Err() + + return &res, nil } - return d.runCreateFallback(ctx, query, vals, cols, returningCols, pkCols) + return d.runCreateFallback(ctx, query, vals, cols, returningCols, allFieldsSoFarPKCols) } -func (d *AllFieldsSoFarDelegate) runCreateFallback(ctx context.Context, query string, vals []any, cols []string, returningCols []string, pkCols []string) (*AllFieldsSoFar, error) { +func (d *AllFieldsSoFarDelegate) runCreateFallback( + ctx context.Context, + query string, + vals []any, + cols []string, + returningCols []string, + pkCols []string, +) (*AllFieldsSoFar, error) { result, err := d.client.exec(ctx, query, vals...) if err != nil { return nil, err @@ -2647,16 +3452,24 @@ func (d *AllFieldsSoFarDelegate) runCreateFallback(ctx context.Context, query st if err != nil { return nil, err } - defer rows.Close() - var res AllFieldsSoFar - if rows.Next() { - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + if !rows.Next() { + err := rows.Err() + rows.Close() + if err != nil { return nil, err } - return &res, nil + return nil, nil + } + + var res AllFieldsSoFar + scanErr := rows.Scan(res.ScanFields(returningCols)...) + rows.Close() + if scanErr != nil { + return nil, scanErr } - return nil, rows.Err() + + return &res, nil } func (d *AllFieldsSoFarDelegate) buildBulkInsertSQL(q *Queries, batch []*AllFieldsSoFarCreate, paramStartIdx int) (cols []string, vals []any, queryStr string) { @@ -2920,6 +3733,41 @@ func (d *AllFieldsSoFarDelegate) buildBulkInsertSQL(q *Queries, batch []*AllFiel return cols, vals, queryStr } +func applyAllFieldsSoFarConflictClause(dialect Dialect, queryStr string, vals []any, cols []string, pkCols []string, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (string, []any) { + var conflictCols []string + if conflictTarget != nil { + conflictCols = conflictTarget.UniqueColumns() + } + var nonConflictCols []string + if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { + nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + } + clause, clauseArgs := dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) + queryStr += clause + if len(clauseArgs) > 0 { + vals = append(vals, clauseArgs...) + } + return queryStr, vals +} + +func scanAllFieldsSoFarRows(rows *sql.Rows, returningCols []string) ([]*AllFieldsSoFar, error) { + var records []*AllFieldsSoFar + for rows.Next() { + var res AllFieldsSoFar + if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + rows.Close() + return nil, err + } + records = append(records, &res) + } + if err := rows.Err(); err != nil { + rows.Close() + return nil, err + } + rows.Close() + return records, nil +} + func (d *AllFieldsSoFarDelegate) runCreateMany(ctx context.Context, inputs []*AllFieldsSoFarCreate, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { if len(inputs) == 0 { return 0, nil @@ -2930,18 +3778,7 @@ func (d *AllFieldsSoFarDelegate) runCreateMany(ctx context.Context, inputs []*Al var count int64 for _, batch := range batches { cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) - - var conflictCols []string - if conflictTarget != nil { - conflictCols = conflictTarget.UniqueColumns() - } - var nonConflictCols []string - if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, allFieldsSoFarPKCols) - } - clause, clauseArgs := d.client.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) - queryStr += clause - vals = append(vals, clauseArgs...) + queryStr, vals = applyAllFieldsSoFarConflictClause(d.client.dialect, queryStr, vals, cols, allFieldsSoFarPKCols, conflictTarget, conflictAction) result, err := d.client.exec(ctx, queryStr, vals...) if err != nil { @@ -2969,27 +3806,37 @@ func (d *AllFieldsSoFarDelegate) runCreateManyAndReturn( } batches := partitionAllFieldsSoFarInputs(d.client.dialect, inputs) - returningCols := selectAllFieldsSoFarCols(selects, omits) hasRelations := selects != nil && selects.hasAnyRelation() + useTx := (len(batches) > 1 || hasRelations || !d.client.dialect.SupportsInsertReturning) && !d.client.inTx() - recordsOut := make([]*AllFieldsSoFar, 0, len(inputs)) + if useTx { + var res []*AllFieldsSoFar + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + if txQ.dialect.SupportsInsertReturning { + res, err = txQ.AllFieldsSoFar.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) + } else { + res, err = txQ.AllFieldsSoFar.runCreateManyAndReturnFallback(ctx, inputs, selects, omits, conflictTarget, conflictAction) + } + if err != nil { + return err + } + if hasRelations { + return txQ.AllFieldsSoFar.loadRelations(ctx, res, selects) + } + return nil + }) + return res, err + } - runBatch := func(txQ *Queries, batch []*AllFieldsSoFarCreate) error { - cols, vals, queryStr := d.buildBulkInsertSQL(txQ, batch, 1) + returningCols := selectAllFieldsSoFarCols(selects, omits, allFieldsSoFarPKCols...) + recordsOut := make([]*AllFieldsSoFar, 0, len(inputs)) - var conflictCols []string - if conflictTarget != nil { - conflictCols = conflictTarget.UniqueColumns() - } - var nonConflictCols []string - if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, allFieldsSoFarPKCols) - } - clause, clauseArgs := txQ.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) - queryStr += clause - vals = append(vals, clauseArgs...) + for _, batch := range batches { + cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) + queryStr, vals = applyAllFieldsSoFarConflictClause(d.client.dialect, queryStr, vals, cols, allFieldsSoFarPKCols, conflictTarget, conflictAction) - if txQ.dialect.SupportsInsertReturning && len(returningCols) > 0 { + if len(returningCols) > 0 { var retSb strings.Builder retSb.Grow(12 + len(returningCols)*15) retSb.WriteString(" RETURNING ") @@ -2997,40 +3844,58 @@ func (d *AllFieldsSoFarDelegate) runCreateManyAndReturn( if i > 0 { retSb.WriteString(", ") } - txQ.dialect.WriteQuotedIdent(&retSb, col) + d.client.dialect.WriteQuotedIdent(&retSb, col) } queryStr += retSb.String() - rows, err := txQ.query(ctx, queryStr, vals...) - if err != nil { - return err - } - defer rows.Close() + } - for rows.Next() { - var res AllFieldsSoFar - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - return err - } - recordsOut = append(recordsOut, &res) - } - return rows.Err() + rows, err := d.client.query(ctx, queryStr, vals...) + if err != nil { + return nil, err } - // Fallback for dialects without RETURNING (MySQL) - result, err := txQ.exec(ctx, queryStr, vals...) + scanned, err := scanAllFieldsSoFarRows(rows, returningCols) if err != nil { - return err + return nil, err + } + recordsOut = append(recordsOut, scanned...) + } + + if selects != nil && selects.hasAnyRelation() { + if err := d.loadRelations(ctx, recordsOut, selects); err != nil { + return nil, err + } + } + + return recordsOut, nil +} + +func (d *AllFieldsSoFarDelegate) runCreateManyAndReturnFallback( + ctx context.Context, + inputs []*AllFieldsSoFarCreate, + selects *AllFieldsSoFarSelect, + omits *AllFieldsSoFarOmit, + conflictTarget UniqueConstraintTarget, + conflictAction *ConflictAction, +) ([]*AllFieldsSoFar, error) { + batches := partitionAllFieldsSoFarInputs(d.client.dialect, inputs) + returningCols := selectAllFieldsSoFarCols(selects, omits, allFieldsSoFarPKCols...) + recordsOut := make([]*AllFieldsSoFar, 0, len(inputs)) + + for _, batch := range batches { + cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) + queryStr, vals = applyAllFieldsSoFarConflictClause(d.client.dialect, queryStr, vals, cols, allFieldsSoFarPKCols, conflictTarget, conflictAction) + + result, err := d.client.exec(ctx, queryStr, vals...) + if err != nil { + return nil, err } - // We need to fetch the inserted records for this batch - // Note: MySQL bulk inserts only return the ID of the FIRST inserted row lastID, err := result.LastInsertId() if err != nil { - return err + return nil, err } - // Query back the rows by IDs (assuming autoincrement ID and single PK) - // If composite PK, it's more complex, but this is a standard fallback var selectSb strings.Builder selectSb.Grow(64 + len(returningCols)*15 + len("AllFieldsSoFar") + len(batch)*15) selectSb.WriteString("SELECT ") @@ -3038,55 +3903,29 @@ func (d *AllFieldsSoFarDelegate) runCreateManyAndReturn( if i > 0 { selectSb.WriteString(", ") } - txQ.dialect.WriteQuotedIdent(&selectSb, col) + d.client.dialect.WriteQuotedIdent(&selectSb, col) } selectSb.WriteString(" FROM ") - txQ.dialect.WriteQuotedIdent(&selectSb, "AllFieldsSoFar") + d.client.dialect.WriteQuotedIdent(&selectSb, "AllFieldsSoFar") selectSb.WriteString(" WHERE ") - txQ.dialect.WriteQuotedIdent(&selectSb, allFieldsSoFarPKCols[0]) + d.client.dialect.WriteQuotedIdent(&selectSb, allFieldsSoFarPKCols[0]) selectSb.WriteString(" >= ") - txQ.dialect.WritePlaceholder(&selectSb, 1) + d.client.dialect.WritePlaceholder(&selectSb, 1) selectSb.WriteString(" AND ") - txQ.dialect.WriteQuotedIdent(&selectSb, allFieldsSoFarPKCols[0]) + d.client.dialect.WriteQuotedIdent(&selectSb, allFieldsSoFarPKCols[0]) selectSb.WriteString(" < ") - txQ.dialect.WritePlaceholder(&selectSb, 2) + d.client.dialect.WritePlaceholder(&selectSb, 2) - rows, err := txQ.query(ctx, selectSb.String(), lastID, lastID+int64(len(batch))) + rows, err := d.client.query(ctx, selectSb.String(), lastID, lastID+int64(len(batch))) if err != nil { - return err - } - defer rows.Close() - - for rows.Next() { - var res AllFieldsSoFar - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - return err - } - recordsOut = append(recordsOut, &res) + return nil, err } - return rows.Err() - } - // Always wrap in transaction if we have multiple batches OR if we need to load relations - 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 { - return err - } - } - if hasRelations { - return txQ.AllFieldsSoFar.loadRelations(ctx, recordsOut, selects) - } - return nil - }) + scanned, err := scanAllFieldsSoFarRows(rows, returningCols) if err != nil { return nil, err } - } else { - if err := runBatch(d.client, batches[0]); err != nil { - return nil, err - } + recordsOut = append(recordsOut, scanned...) } return recordsOut, nil @@ -4110,23 +4949,23 @@ func (d *AllFieldsSoFarDelegate) UpdateManyAndReturn(preds ...PredicateOf[AllFie } } -func (d *AllFieldsSoFarDelegate) buildUpdateSQL(preds []PredicateOf[AllFieldsSoFar], assignments []FieldAssignment, returningCols []string) (string, []any) { - whereClause, predVals, _ := CompilePredicates(d.client.dialect, preds, len(assignments)+1) +func (d *AllFieldsSoFarDelegate) buildUpdateSQL(preds []PredicateOf[AllFieldsSoFar], cols []string, vals []any, returningCols []string) (string, []any) { + whereClause, predVals, _ := CompilePredicates(d.client.dialect, preds, len(cols)+1) var sb strings.Builder sb.WriteString("UPDATE ") d.client.dialect.WriteQuotedIdent(&sb, "AllFieldsSoFar") sb.WriteString(" SET ") - setVals := make([]any, 0, len(assignments)+len(predVals)) - for i, a := range assignments { + setVals := make([]any, 0, len(cols)+len(predVals)) + for i, col := range cols { if i > 0 { sb.WriteString(", ") } - d.client.dialect.WriteQuotedIdent(&sb, a.Col) + d.client.dialect.WriteQuotedIdent(&sb, col) sb.WriteString(" = ") d.client.dialect.WritePlaceholder(&sb, i+1) - setVals = append(setVals, a.Val) + setVals = append(setVals, vals[i]) } if whereClause != "" { @@ -4153,21 +4992,39 @@ func (d *AllFieldsSoFarDelegate) buildUpdateSQL(preds []PredicateOf[AllFieldsSoF // ----------------------------------------------------------------------------- func (d *AllFieldsSoFarDelegate) executeUpdate(ctx context.Context, where UniquePredicate[AllFieldsSoFar], additional []PredicateOf[AllFieldsSoFar], assignments []FieldAssignment, selects *AllFieldsSoFarSelect, omits *AllFieldsSoFarOmit) (*AllFieldsSoFar, error) { + allWhere := make([]PredicateOf[AllFieldsSoFar], 0, 1+len(additional)) + allWhere = append(allWhere, where) + allWhere = append(allWhere, additional...) + + input, err := assignmentsToAllFieldsSoFarUpdate(assignments) + if err != nil { + return nil, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.runUpdate(ctx, where, additional, assignments, selects, omits) + return d.runUpdate(ctx, allWhere, cols, vals, selects, omits) } if selects == nil || !selects.hasAnySelected() { selects = fullAllFieldsSoFarSelect() } - curr := func(c context.Context, w UniquePredicate[AllFieldsSoFar], add []PredicateOf[AllFieldsSoFar], a []FieldAssignment, s *AllFieldsSoFarSelect, o *AllFieldsSoFarOmit) (*AllFieldsSoFar, error) { - return d.runUpdate(c, w, add, a, s, o) + args := &AllFieldsSoFarUpdateArgs{ + Where: allWhere, + Data: &input, + Select: selects, + } + + curr := func(c context.Context, a *AllFieldsSoFarUpdateArgs) (*AllFieldsSoFar, error) { + extCols, extVals := a.Data.ToColsVals() + return d.runUpdate(c, a.Where, extCols, extVals, a.Select, omits) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.Update != nil { - return ext.Update(ctx, where, additional, assignments, selects, omits, curr) + return ext.Update(ctx, args, curr) } } @@ -4175,25 +5032,21 @@ func (d *AllFieldsSoFarDelegate) executeUpdate(ctx context.Context, where Unique ext := d.extensions[i] if ext.Update != nil { next, hook := curr, ext.Update - curr = func(c context.Context, w UniquePredicate[AllFieldsSoFar], add []PredicateOf[AllFieldsSoFar], a []FieldAssignment, s *AllFieldsSoFarSelect, o *AllFieldsSoFarOmit) (*AllFieldsSoFar, error) { - return hook(c, w, add, a, s, o, next) + curr = func(c context.Context, a *AllFieldsSoFarUpdateArgs) (*AllFieldsSoFar, error) { + return hook(c, a, next) } } } - return curr(ctx, where, additional, assignments, selects, omits) + return curr(ctx, args) } -func (d *AllFieldsSoFarDelegate) runUpdate(ctx context.Context, where UniquePredicate[AllFieldsSoFar], additional []PredicateOf[AllFieldsSoFar], assignments []FieldAssignment, selects *AllFieldsSoFarSelect, omits *AllFieldsSoFarOmit) (*AllFieldsSoFar, error) { - allPreds := append([]PredicateOf[AllFieldsSoFar]{where}, additional...) - if len(assignments) == 0 { - return d.runFindUnique(ctx, allPreds, selects, omits) +func (d *AllFieldsSoFarDelegate) runUpdate(ctx context.Context, preds []PredicateOf[AllFieldsSoFar], cols []string, vals []any, selects *AllFieldsSoFarSelect, omits *AllFieldsSoFarOmit) (*AllFieldsSoFar, error) { + if len(cols) == 0 { + return d.runFindUnique(ctx, preds, selects, omits) } - if err := where.Validate(); err != nil { - return nil, err - } - for _, pr := range additional { + for _, pr := range preds { if pr != nil { if err := pr.Validate(); err != nil { return nil, err @@ -4209,9 +5062,9 @@ func (d *AllFieldsSoFarDelegate) runUpdate(ctx context.Context, where UniquePred err := d.client.transaction(ctx, func(txQ *Queries) error { var err error if d.client.dialect.SupportsUpdateReturning { - res, err = txQ.AllFieldsSoFar.runUpdate(ctx, where, additional, assignments, selects, omits) + res, err = txQ.AllFieldsSoFar.runUpdate(ctx, preds, cols, vals, selects, omits) } else { - res, err = txQ.AllFieldsSoFar.runUpdateFallback(ctx, where, additional, assignments, selects, omits) + res, err = txQ.AllFieldsSoFar.runUpdateFallback(ctx, preds, cols, vals, selects, omits) } return err }) @@ -4219,7 +5072,7 @@ func (d *AllFieldsSoFarDelegate) runUpdate(ctx context.Context, where UniquePred } returningCols := selectAllFieldsSoFarCols(selects, omits, allFieldsSoFarPKCols...) - query, setVals := d.buildUpdateSQL(allPreds, assignments, returningCols) + query, setVals := d.buildUpdateSQL(preds, cols, vals, returningCols) rows, err := d.client.query(ctx, query, setVals...) if err != nil { @@ -4251,8 +5104,8 @@ func (d *AllFieldsSoFarDelegate) runUpdate(ctx context.Context, where UniquePred return &res, nil } -func (d *AllFieldsSoFarDelegate) execUpdateStmt(ctx context.Context, preds []PredicateOf[AllFieldsSoFar], assignments []FieldAssignment) (int64, error) { - if len(assignments) == 0 { +func (d *AllFieldsSoFarDelegate) execUpdateStmt(ctx context.Context, preds []PredicateOf[AllFieldsSoFar], cols []string, vals []any) (int64, error) { + if len(cols) == 0 { return 0, nil } @@ -4264,7 +5117,7 @@ func (d *AllFieldsSoFarDelegate) execUpdateStmt(ctx context.Context, preds []Pre } } - query, setVals := d.buildUpdateSQL(preds, assignments, nil) + query, setVals := d.buildUpdateSQL(preds, cols, vals, nil) result, err := d.client.exec(ctx, query, setVals...) if err != nil { return 0, err @@ -4272,16 +5125,15 @@ func (d *AllFieldsSoFarDelegate) execUpdateStmt(ctx context.Context, preds []Pre return result.RowsAffected() } -func (d *AllFieldsSoFarDelegate) runUpdateFallback(ctx context.Context, where UniquePredicate[AllFieldsSoFar], additional []PredicateOf[AllFieldsSoFar], assignments []FieldAssignment, selects *AllFieldsSoFarSelect, omits *AllFieldsSoFarOmit) (*AllFieldsSoFar, error) { - allPreds := append([]PredicateOf[AllFieldsSoFar]{where}, additional...) - affected, err := d.execUpdateStmt(ctx, allPreds, assignments) +func (d *AllFieldsSoFarDelegate) runUpdateFallback(ctx context.Context, preds []PredicateOf[AllFieldsSoFar], cols []string, vals []any, selects *AllFieldsSoFarSelect, omits *AllFieldsSoFarOmit) (*AllFieldsSoFar, error) { + affected, err := d.execUpdateStmt(ctx, preds, cols, vals) if err != nil { return nil, err } if affected == 0 { return nil, sql.ErrNoRows } - return d.runFindUnique(ctx, allPreds, selects, omits) + return d.runFindUnique(ctx, preds, selects, omits) } // ----------------------------------------------------------------------------- @@ -4289,17 +5141,30 @@ func (d *AllFieldsSoFarDelegate) runUpdateFallback(ctx context.Context, where Un // ----------------------------------------------------------------------------- func (d *AllFieldsSoFarDelegate) executeUpdateMany(ctx context.Context, preds []PredicateOf[AllFieldsSoFar], assignments []FieldAssignment) (int64, error) { + input, err := assignmentsToAllFieldsSoFarUpdate(assignments) + if err != nil { + return 0, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.execUpdateStmt(ctx, preds, assignments) + return d.execUpdateStmt(ctx, preds, cols, vals) + } + + args := &AllFieldsSoFarUpdateManyArgs{ + Where: preds, + Data: &input, } - curr := func(c context.Context, p []PredicateOf[AllFieldsSoFar], a []FieldAssignment) (int64, error) { - return d.execUpdateStmt(c, p, a) + curr := func(c context.Context, a *AllFieldsSoFarUpdateManyArgs) (int64, error) { + extCols, extVals := a.Data.ToColsVals() + return d.execUpdateStmt(c, a.Where, extCols, extVals) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.UpdateMany != nil { - return ext.UpdateMany(ctx, preds, assignments, curr) + return ext.UpdateMany(ctx, args, curr) } } @@ -4307,13 +5172,13 @@ func (d *AllFieldsSoFarDelegate) executeUpdateMany(ctx context.Context, preds [] ext := d.extensions[i] if ext.UpdateMany != nil { next, hook := curr, ext.UpdateMany - curr = func(c context.Context, p []PredicateOf[AllFieldsSoFar], a []FieldAssignment) (int64, error) { - return hook(c, p, a, next) + curr = func(c context.Context, a *AllFieldsSoFarUpdateManyArgs) (int64, error) { + return hook(c, a, next) } } } - return curr(ctx, preds, assignments) + return curr(ctx, args) } // ----------------------------------------------------------------------------- @@ -4321,21 +5186,35 @@ func (d *AllFieldsSoFarDelegate) executeUpdateMany(ctx context.Context, preds [] // ----------------------------------------------------------------------------- func (d *AllFieldsSoFarDelegate) executeUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[AllFieldsSoFar], assignments []FieldAssignment, selects *AllFieldsSoFarSelect, omits *AllFieldsSoFarOmit) ([]*AllFieldsSoFar, error) { + input, err := assignmentsToAllFieldsSoFarUpdate(assignments) + if err != nil { + return nil, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.runUpdateManyAndReturn(ctx, preds, assignments, selects, omits) + return d.runUpdateManyAndReturn(ctx, preds, cols, vals, selects, omits) } if selects == nil || !selects.hasAnySelected() { selects = fullAllFieldsSoFarSelect() } - curr := func(c context.Context, p []PredicateOf[AllFieldsSoFar], a []FieldAssignment, s *AllFieldsSoFarSelect, o *AllFieldsSoFarOmit) ([]*AllFieldsSoFar, error) { - return d.runUpdateManyAndReturn(c, p, a, s, o) + args := &AllFieldsSoFarUpdateManyAndReturnArgs{ + Where: preds, + Data: &input, + Select: selects, + } + + curr := func(c context.Context, a *AllFieldsSoFarUpdateManyAndReturnArgs) ([]*AllFieldsSoFar, error) { + extCols, extVals := a.Data.ToColsVals() + return d.runUpdateManyAndReturn(c, a.Where, extCols, extVals, a.Select, omits) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.UpdateManyAndReturn != nil { - return ext.UpdateManyAndReturn(ctx, preds, assignments, selects, omits, curr) + return ext.UpdateManyAndReturn(ctx, args, curr) } } @@ -4343,17 +5222,17 @@ func (d *AllFieldsSoFarDelegate) executeUpdateManyAndReturn(ctx context.Context, ext := d.extensions[i] if ext.UpdateManyAndReturn != nil { next, hook := curr, ext.UpdateManyAndReturn - curr = func(c context.Context, p []PredicateOf[AllFieldsSoFar], a []FieldAssignment, s *AllFieldsSoFarSelect, o *AllFieldsSoFarOmit) ([]*AllFieldsSoFar, error) { - return hook(c, p, a, s, o, next) + curr = func(c context.Context, a *AllFieldsSoFarUpdateManyAndReturnArgs) ([]*AllFieldsSoFar, error) { + return hook(c, a, next) } } } - return curr(ctx, preds, assignments, selects, omits) + return curr(ctx, args) } -func (d *AllFieldsSoFarDelegate) runUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[AllFieldsSoFar], assignments []FieldAssignment, selects *AllFieldsSoFarSelect, omits *AllFieldsSoFarOmit) ([]*AllFieldsSoFar, error) { - if len(assignments) == 0 { +func (d *AllFieldsSoFarDelegate) runUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[AllFieldsSoFar], cols []string, vals []any, selects *AllFieldsSoFarSelect, omits *AllFieldsSoFarOmit) ([]*AllFieldsSoFar, error) { + if len(cols) == 0 { return d.runFindMany(ctx, QueryParams[AllFieldsSoFar]{Where: preds}, selects, omits) } @@ -4373,9 +5252,9 @@ func (d *AllFieldsSoFarDelegate) runUpdateManyAndReturn(ctx context.Context, pre err := d.client.transaction(ctx, func(txQ *Queries) error { var err error if d.client.dialect.SupportsUpdateReturning { - res, err = txQ.AllFieldsSoFar.runUpdateManyAndReturn(ctx, preds, assignments, selects, omits) + res, err = txQ.AllFieldsSoFar.runUpdateManyAndReturn(ctx, preds, cols, vals, selects, omits) } else { - res, err = txQ.AllFieldsSoFar.runUpdateManyAndReturnFallback(ctx, preds, assignments, selects, omits) + res, err = txQ.AllFieldsSoFar.runUpdateManyAndReturnFallback(ctx, preds, cols, vals, selects, omits) } return err }) @@ -4383,39 +5262,29 @@ func (d *AllFieldsSoFarDelegate) runUpdateManyAndReturn(ctx context.Context, pre } returningCols := selectAllFieldsSoFarCols(selects, omits, allFieldsSoFarPKCols...) - query, setVals := d.buildUpdateSQL(preds, assignments, returningCols) + query, setVals := d.buildUpdateSQL(preds, cols, vals, returningCols) rows, err := d.client.query(ctx, query, setVals...) if err != nil { return nil, err } - results := make([]*AllFieldsSoFar, 0) - for rows.Next() { - var res AllFieldsSoFar - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - rows.Close() - return nil, err - } - results = append(results, &res) - } - rowsErr := rows.Err() - rows.Close() - if rowsErr != nil { - return nil, rowsErr + scanned, err := scanAllFieldsSoFarRows(rows, returningCols) + if err != nil { + return nil, err } if selects != nil && selects.hasAnyRelation() { - if err := d.loadRelations(ctx, results, selects); err != nil { + if err := d.loadRelations(ctx, scanned, selects); err != nil { return nil, err } } - return results, nil + return scanned, nil } -func (d *AllFieldsSoFarDelegate) runUpdateManyAndReturnFallback(ctx context.Context, preds []PredicateOf[AllFieldsSoFar], assignments []FieldAssignment, selects *AllFieldsSoFarSelect, omits *AllFieldsSoFarOmit) ([]*AllFieldsSoFar, error) { - affected, err := d.execUpdateStmt(ctx, preds, assignments) +func (d *AllFieldsSoFarDelegate) runUpdateManyAndReturnFallback(ctx context.Context, preds []PredicateOf[AllFieldsSoFar], cols []string, vals []any, selects *AllFieldsSoFarSelect, omits *AllFieldsSoFarOmit) ([]*AllFieldsSoFar, error) { + affected, err := d.execUpdateStmt(ctx, preds, cols, vals) if err != nil { return nil, err } diff --git a/integration/valk/category.go b/integration/valk/category.go index b84b7c4..e0072e6 100644 --- a/integration/valk/category.go +++ b/integration/valk/category.go @@ -36,6 +36,58 @@ func (s *CategoryCreate) colMask() uint64 { return mask } +// CategoryUpdate contains model input fields for Category update operations. +type CategoryUpdate struct { + Id *int32 `json:"id"` + Name *string `json:"name"` +} + +func (u *CategoryUpdate) ToColsVals() ([]string, []any) { + var cols []string + var vals []any + if u.Id != nil { + cols = append(cols, "id") + vals = append(vals, u.Id) + } + if u.Name != nil { + cols = append(cols, "name") + vals = append(vals, u.Name) + } + return cols, vals +} + +func assignmentsToCategoryUpdate(assignments []FieldAssignment) (CategoryUpdate, error) { + var input CategoryUpdate + var errs ValidationError + + for _, a := range assignments { + switch a.Col { + case "id": + if v, ok := a.Val.(int32); ok { + input.Id = &v + } else if v, ok := a.Val.(*int32); ok { + input.Id = v + } else { + errs.Add("id", a.Val, "type", "field id must be of type int32") + } + case "name": + if v, ok := a.Val.(string); ok { + input.Name = &v + errs.ValidateString("name", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.Name = v + } else { + errs.Add("name", a.Val, "type", "field name must be of type string") + } + } + } + + if errs.HasErrors() { + return input, errs + } + return input, nil +} + // CategorySelect specifies which scalar and relation fields to select for Category. // // Selectable fields: @@ -372,6 +424,51 @@ func (a *CategoryDeleteManyArgs) SetWhere(preds ...PredicateOf[Category]) *Categ return a } +// CategoryUpdateArgs is the input argument passed to Category Update extension hooks. +type CategoryUpdateArgs struct { + // Where contains all query filter predicates (merged primary unique constraint and additional predicates). + Where []PredicateOf[Category] + // Data contains the model fields to update. + Data *CategoryUpdate + // Select specifies which scalar and relation fields to select and return upon update. + Select *CategorySelect +} + +func (a *CategoryUpdateArgs) SetWhere(unique UniquePredicate[Category], additional ...PredicateOf[Category]) *CategoryUpdateArgs { + a.Where = make([]PredicateOf[Category], 0, 1+len(additional)) + a.Where = append(a.Where, unique) + a.Where = append(a.Where, additional...) + return a +} + +// CategoryUpdateManyArgs is the input argument passed to Category UpdateMany extension hooks. +type CategoryUpdateManyArgs struct { + // Where contains all query filter predicates. + Where []PredicateOf[Category] + // Data contains the model fields to update. + Data *CategoryUpdate +} + +func (a *CategoryUpdateManyArgs) SetWhere(preds ...PredicateOf[Category]) *CategoryUpdateManyArgs { + a.Where = preds + return a +} + +// CategoryUpdateManyAndReturnArgs is the input argument passed to Category UpdateManyAndReturn extension hooks. +type CategoryUpdateManyAndReturnArgs struct { + // Where contains all query filter predicates. + Where []PredicateOf[Category] + // Data contains the model fields to update. + Data *CategoryUpdate + // Select specifies which scalar and relation fields to select and return upon update. + Select *CategorySelect +} + +func (a *CategoryUpdateManyAndReturnArgs) SetWhere(preds ...PredicateOf[Category]) *CategoryUpdateManyAndReturnArgs { + a.Where = preds + return a +} + type CategoryCreateQuery = func(ctx context.Context, args *CategoryCreateArgs) (*Category, error) type CategoryCreateManyQuery = func(ctx context.Context, args *CategoryCreateManyArgs) (int64, error) type CategoryCreateManyAndReturnQuery = func(ctx context.Context, args *CategoryCreateManyAndReturnArgs) ([]*Category, error) @@ -381,9 +478,9 @@ type CategoryFindManyQuery = func(ctx context.Context, args *CategoryFindManyArg type CategoryDeleteQuery = func(ctx context.Context, args *CategoryDeleteArgs) (*Category, error) type CategoryDeleteManyQuery = func(ctx context.Context, args *CategoryDeleteManyArgs) (int64, error) type CategoryCountQuery = func(ctx context.Context, args *CategoryCountArgs) (int64, error) -type CategoryUpdateQuery = func(ctx context.Context, where UniquePredicate[Category], additional []PredicateOf[Category], assignments []FieldAssignment, selects *CategorySelect, omits *CategoryOmit) (*Category, error) -type CategoryUpdateManyQuery = func(ctx context.Context, preds []PredicateOf[Category], assignments []FieldAssignment) (int64, error) -type CategoryUpdateManyAndReturnQuery = func(ctx context.Context, preds []PredicateOf[Category], assignments []FieldAssignment, selects *CategorySelect, omits *CategoryOmit) ([]*Category, error) +type CategoryUpdateQuery = func(ctx context.Context, args *CategoryUpdateArgs) (*Category, error) +type CategoryUpdateManyQuery = func(ctx context.Context, args *CategoryUpdateManyArgs) (int64, error) +type CategoryUpdateManyAndReturnQuery = func(ctx context.Context, args *CategoryUpdateManyAndReturnArgs) ([]*Category, error) type CategoryExtension struct { Create func(ctx context.Context, args *CategoryCreateArgs, next CategoryCreateQuery) (*Category, error) @@ -395,9 +492,9 @@ type CategoryExtension struct { Delete func(ctx context.Context, args *CategoryDeleteArgs, next CategoryDeleteQuery) (*Category, error) DeleteMany func(ctx context.Context, args *CategoryDeleteManyArgs, next CategoryDeleteManyQuery) (int64, error) Count func(ctx context.Context, args *CategoryCountArgs, next CategoryCountQuery) (int64, error) - Update func(ctx context.Context, where UniquePredicate[Category], additional []PredicateOf[Category], assignments []FieldAssignment, selects *CategorySelect, omits *CategoryOmit, next CategoryUpdateQuery) (*Category, error) - UpdateMany func(ctx context.Context, preds []PredicateOf[Category], assignments []FieldAssignment, next CategoryUpdateManyQuery) (int64, error) - UpdateManyAndReturn func(ctx context.Context, preds []PredicateOf[Category], assignments []FieldAssignment, selects *CategorySelect, omits *CategoryOmit, next CategoryUpdateManyAndReturnQuery) ([]*Category, error) + Update func(ctx context.Context, args *CategoryUpdateArgs, next CategoryUpdateQuery) (*Category, error) + UpdateMany func(ctx context.Context, args *CategoryUpdateManyArgs, next CategoryUpdateManyQuery) (int64, error) + UpdateManyAndReturn func(ctx context.Context, args *CategoryUpdateManyAndReturnArgs, next CategoryUpdateManyAndReturnQuery) ([]*Category, error) } type CategoryDelegate struct { @@ -470,6 +567,16 @@ type CategoryCreateBuilder struct { *CreateBuilder[Category, CategorySelect, CategoryOmit] } +func (b *CategoryCreateBuilder) Select(s CategorySelect) *CategoryCreateBuilder { + b.selects = &s + return b +} + +func (b *CategoryCreateBuilder) Omit(o CategoryOmit) *CategoryCreateBuilder { + b.omits = &o + return b +} + func (b *CategoryCreateBuilder) OnConflict(target UniqueConstraintTarget) *CategoryConflictBuilder[CategoryCreateBuilder] { return &CategoryConflictBuilder[CategoryCreateBuilder]{ builder: b, @@ -592,23 +699,11 @@ func (d *CategoryDelegate) executeCreate(ctx context.Context, assignments []Fiel return nil, err } + cols, vals := input.ToColsVals() + returningCols := selectCategoryCols(selects, omits) + if len(d.extensions) == 0 { - cols, vals := input.ToColsVals() - returningCols := selectCategoryCols(selects, omits) - hasRelations := selects.hasAnyRelation() - if hasRelations { - var res *Category - err = d.client.transaction(ctx, func(txQ *Queries) error { - var err error - res, err = txQ.Category.runCreate(ctx, cols, vals, returningCols, categoryPKCols, conflictTarget, conflictAction) - if err != nil { - return err - } - return txQ.Category.loadRelations(ctx, []*Category{res}, selects) - }) - return res, err - } - return d.runCreate(ctx, cols, vals, returningCols, categoryPKCols, conflictTarget, conflictAction) + return d.runCreate(ctx, cols, vals, returningCols, selects, conflictTarget, conflictAction) } if selects == nil || !selects.hasAnySelected() { @@ -623,28 +718,9 @@ func (d *CategoryDelegate) executeCreate(ctx context.Context, assignments []Fiel } curr := func(c context.Context, a *CategoryCreateArgs) (*Category, error) { - cols, vals := a.Data.ToColsVals() - returningCols := selectCategoryCols(a.Select, omits) - - hasRelations := a.Select.hasAnyRelation() - var res *Category - var err error - if hasRelations { - err = d.client.transaction(c, func(txQ *Queries) error { - var err error - res, err = txQ.Category.runCreate(c, cols, vals, returningCols, categoryPKCols, a.ConflictTarget, a.ConflictAction) - if err != nil { - return err - } - return txQ.Category.loadRelations(c, []*Category{res}, a.Select) - }) - } else { - res, err = d.runCreate(c, cols, vals, returningCols, categoryPKCols, a.ConflictTarget, a.ConflictAction) - } - if err != nil { - return nil, err - } - return res, nil + cCols, cVals := a.Data.ToColsVals() + cReturningCols := selectCategoryCols(a.Select, omits) + return d.runCreate(c, cCols, cVals, cReturningCols, a.Select, a.ConflictTarget, a.ConflictAction) } if len(d.extensions) == 1 { @@ -685,6 +761,16 @@ type CategoryCreateManyAndReturnBuilder struct { *CreateManyAndReturnBuilder[Category, CategorySelect, CategoryOmit] } +func (b *CategoryCreateManyAndReturnBuilder) Select(s CategorySelect) *CategoryCreateManyAndReturnBuilder { + b.selects = &s + return b +} + +func (b *CategoryCreateManyAndReturnBuilder) Omit(o CategoryOmit) *CategoryCreateManyAndReturnBuilder { + b.omits = &o + return b +} + func (b *CategoryCreateManyAndReturnBuilder) OnConflict(target UniqueConstraintTarget) *CategoryConflictBuilder[CategoryCreateManyAndReturnBuilder] { return &CategoryConflictBuilder[CategoryCreateManyAndReturnBuilder]{ builder: b, @@ -696,43 +782,51 @@ func (b *CategoryCreateManyAndReturnBuilder) OnConflict(target UniqueConstraintT } } -func (d *CategoryDelegate) CreateMany(builders ...*CategoryCreateBuilder) *CategoryCreateManyBuilder { +func createBuildersToCategoryRecordInputs(builders []*CategoryCreateBuilder) []RecordInput { records := make([]RecordInput, len(builders)) for i, b := range builders { records[i] = RecordInput{Assignments: b.assignments} } + return records +} + +func (d *CategoryDelegate) CreateMany(builders ...*CategoryCreateBuilder) *CategoryCreateManyBuilder { return &CategoryCreateManyBuilder{ CreateManyBuilder: &CreateManyBuilder[Category]{ - records: records, + records: createBuildersToCategoryRecordInputs(builders), execFunc: d.executeCreateMany, }, } } func (d *CategoryDelegate) CreateManyAndReturn(builders ...*CategoryCreateBuilder) *CategoryCreateManyAndReturnBuilder { - records := make([]RecordInput, len(builders)) - for i, b := range builders { - records[i] = RecordInput{Assignments: b.assignments} - } return &CategoryCreateManyAndReturnBuilder{ CreateManyAndReturnBuilder: &CreateManyAndReturnBuilder[Category, CategorySelect, CategoryOmit]{ - records: records, + records: createBuildersToCategoryRecordInputs(builders), execFunc: d.executeCreateManyAndReturn, }, } } -func (d *CategoryDelegate) executeCreateMany(ctx context.Context, records []RecordInput, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { +func recordsToCategoryCreateInputs(records []RecordInput) ([]*CategoryCreate, error) { structs := make([]CategoryCreate, len(records)) inputs := make([]*CategoryCreate, len(records)) for i, rec := range records { var err error structs[i], err = assignmentsToCategoryCreate(rec.Assignments) if err != nil { - return 0, fmt.Errorf("validation failed at index %d: %w", i, err) + return nil, fmt.Errorf("validation failed at index %d: %w", i, err) } inputs[i] = &structs[i] } + return inputs, nil +} + +func (d *CategoryDelegate) executeCreateMany(ctx context.Context, records []RecordInput, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { + inputs, err := recordsToCategoryCreateInputs(records) + if err != nil { + return 0, err + } if len(d.extensions) == 0 { return d.runCreateMany(ctx, inputs, conflictTarget, conflictAction) @@ -768,31 +862,12 @@ func (d *CategoryDelegate) executeCreateMany(ctx context.Context, records []Reco } func (d *CategoryDelegate) executeCreateManyAndReturn(ctx context.Context, records []RecordInput, selects *CategorySelect, omits *CategoryOmit, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) ([]*Category, error) { - structs := make([]CategoryCreate, len(records)) - inputs := make([]*CategoryCreate, len(records)) - for i, rec := range records { - var err error - structs[i], err = assignmentsToCategoryCreate(rec.Assignments) - if err != nil { - return nil, fmt.Errorf("validation failed at index %d: %w", i, err) - } - inputs[i] = &structs[i] + inputs, err := recordsToCategoryCreateInputs(records) + if err != nil { + return nil, err } if len(d.extensions) == 0 { - hasRelations := selects != nil && selects.hasAnyRelation() - if hasRelations { - var res []*Category - err := d.client.transaction(ctx, func(txQ *Queries) error { - var err error - res, err = txQ.Category.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) - if err != nil { - return err - } - return txQ.Category.loadRelations(ctx, res, selects) - }) - return res, err - } return d.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) } @@ -808,19 +883,6 @@ func (d *CategoryDelegate) executeCreateManyAndReturn(ctx context.Context, recor } curr := func(c context.Context, a *CategoryCreateManyAndReturnArgs) ([]*Category, error) { - hasRelations := a.Select != nil && a.Select.hasAnyRelation() - if hasRelations { - var res []*Category - err := d.client.transaction(c, func(txQ *Queries) error { - var err error - res, err = txQ.Category.runCreateManyAndReturn(c, a.Data, a.Select, omits, a.ConflictTarget, a.ConflictAction) - if err != nil { - return err - } - return txQ.Category.loadRelations(c, res, a.Select) - }) - return res, err - } return d.runCreateManyAndReturn(c, a.Data, a.Select, omits, a.ConflictTarget, a.ConflictAction) } @@ -848,36 +910,67 @@ func (d *CategoryDelegate) runCreate( cols []string, vals []any, returningCols []string, - pkCols []string, + selects *CategorySelect, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction, ) (*Category, error) { - query, clauseArgs := buildSingleInsertSQL(d.client, "Category", cols, returningCols, pkCols, conflictTarget, conflictAction, len(vals)) + hasRelations := selects != nil && selects.hasAnyRelation() + useTx := hasRelations && !d.client.inTx() + + if useTx { + var res *Category + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + res, err = txQ.Category.runCreate(ctx, cols, vals, returningCols, selects, conflictTarget, conflictAction) + if err != nil { + return err + } + return txQ.Category.loadRelations(ctx, []*Category{res}, selects) + }) + return res, err + } + + query, clauseArgs := buildSingleInsertSQL(d.client, "Category", cols, returningCols, categoryPKCols, conflictTarget, conflictAction, len(vals)) if len(clauseArgs) > 0 { vals = append(vals, clauseArgs...) } - var res Category if d.client.dialect.SupportsInsertReturning { rows, err := d.client.query(ctx, query, vals...) if err != nil { return nil, err } - defer rows.Close() - if rows.Next() { - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + if !rows.Next() { + err := rows.Err() + rows.Close() + if err != nil { return nil, err } - return &res, nil + return nil, nil + } + + var res Category + scanErr := rows.Scan(res.ScanFields(returningCols)...) + rows.Close() + if scanErr != nil { + return nil, scanErr } - return nil, rows.Err() + + return &res, nil } - return d.runCreateFallback(ctx, query, vals, cols, returningCols, pkCols) + return d.runCreateFallback(ctx, query, vals, cols, returningCols, categoryPKCols) } -func (d *CategoryDelegate) runCreateFallback(ctx context.Context, query string, vals []any, cols []string, returningCols []string, pkCols []string) (*Category, error) { +func (d *CategoryDelegate) runCreateFallback( + ctx context.Context, + query string, + vals []any, + cols []string, + returningCols []string, + pkCols []string, +) (*Category, error) { result, err := d.client.exec(ctx, query, vals...) if err != nil { return nil, err @@ -927,16 +1020,24 @@ func (d *CategoryDelegate) runCreateFallback(ctx context.Context, query string, if err != nil { return nil, err } - defer rows.Close() - var res Category - if rows.Next() { - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + if !rows.Next() { + err := rows.Err() + rows.Close() + if err != nil { return nil, err } - return &res, nil + return nil, nil + } + + var res Category + scanErr := rows.Scan(res.ScanFields(returningCols)...) + rows.Close() + if scanErr != nil { + return nil, scanErr } - return nil, rows.Err() + + return &res, nil } func (d *CategoryDelegate) buildBulkInsertSQL(q *Queries, batch []*CategoryCreate, paramStartIdx int) (cols []string, vals []any, queryStr string) { @@ -1000,6 +1101,41 @@ func (d *CategoryDelegate) buildBulkInsertSQL(q *Queries, batch []*CategoryCreat return cols, vals, queryStr } +func applyCategoryConflictClause(dialect Dialect, queryStr string, vals []any, cols []string, pkCols []string, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (string, []any) { + var conflictCols []string + if conflictTarget != nil { + conflictCols = conflictTarget.UniqueColumns() + } + var nonConflictCols []string + if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { + nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + } + clause, clauseArgs := dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) + queryStr += clause + if len(clauseArgs) > 0 { + vals = append(vals, clauseArgs...) + } + return queryStr, vals +} + +func scanCategoryRows(rows *sql.Rows, returningCols []string) ([]*Category, error) { + var records []*Category + for rows.Next() { + var res Category + if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + rows.Close() + return nil, err + } + records = append(records, &res) + } + if err := rows.Err(); err != nil { + rows.Close() + return nil, err + } + rows.Close() + return records, nil +} + func (d *CategoryDelegate) runCreateMany(ctx context.Context, inputs []*CategoryCreate, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { if len(inputs) == 0 { return 0, nil @@ -1010,18 +1146,7 @@ func (d *CategoryDelegate) runCreateMany(ctx context.Context, inputs []*Category var count int64 for _, batch := range batches { cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) - - var conflictCols []string - if conflictTarget != nil { - conflictCols = conflictTarget.UniqueColumns() - } - var nonConflictCols []string - if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, categoryPKCols) - } - clause, clauseArgs := d.client.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) - queryStr += clause - vals = append(vals, clauseArgs...) + queryStr, vals = applyCategoryConflictClause(d.client.dialect, queryStr, vals, cols, categoryPKCols, conflictTarget, conflictAction) result, err := d.client.exec(ctx, queryStr, vals...) if err != nil { @@ -1049,27 +1174,37 @@ func (d *CategoryDelegate) runCreateManyAndReturn( } batches := partitionCategoryInputs(d.client.dialect, inputs) - returningCols := selectCategoryCols(selects, omits) hasRelations := selects != nil && selects.hasAnyRelation() + useTx := (len(batches) > 1 || hasRelations || !d.client.dialect.SupportsInsertReturning) && !d.client.inTx() - recordsOut := make([]*Category, 0, len(inputs)) + if useTx { + var res []*Category + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + if txQ.dialect.SupportsInsertReturning { + res, err = txQ.Category.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) + } else { + res, err = txQ.Category.runCreateManyAndReturnFallback(ctx, inputs, selects, omits, conflictTarget, conflictAction) + } + if err != nil { + return err + } + if hasRelations { + return txQ.Category.loadRelations(ctx, res, selects) + } + return nil + }) + return res, err + } - runBatch := func(txQ *Queries, batch []*CategoryCreate) error { - cols, vals, queryStr := d.buildBulkInsertSQL(txQ, batch, 1) + returningCols := selectCategoryCols(selects, omits, categoryPKCols...) + recordsOut := make([]*Category, 0, len(inputs)) - var conflictCols []string - if conflictTarget != nil { - conflictCols = conflictTarget.UniqueColumns() - } - var nonConflictCols []string - if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, categoryPKCols) - } - clause, clauseArgs := txQ.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) - queryStr += clause - vals = append(vals, clauseArgs...) + for _, batch := range batches { + cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) + queryStr, vals = applyCategoryConflictClause(d.client.dialect, queryStr, vals, cols, categoryPKCols, conflictTarget, conflictAction) - if txQ.dialect.SupportsInsertReturning && len(returningCols) > 0 { + if len(returningCols) > 0 { var retSb strings.Builder retSb.Grow(12 + len(returningCols)*15) retSb.WriteString(" RETURNING ") @@ -1077,40 +1212,58 @@ func (d *CategoryDelegate) runCreateManyAndReturn( if i > 0 { retSb.WriteString(", ") } - txQ.dialect.WriteQuotedIdent(&retSb, col) + d.client.dialect.WriteQuotedIdent(&retSb, col) } queryStr += retSb.String() - rows, err := txQ.query(ctx, queryStr, vals...) - if err != nil { - return err - } - defer rows.Close() + } - for rows.Next() { - var res Category - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - return err - } - recordsOut = append(recordsOut, &res) - } - return rows.Err() + rows, err := d.client.query(ctx, queryStr, vals...) + if err != nil { + return nil, err } - // Fallback for dialects without RETURNING (MySQL) - result, err := txQ.exec(ctx, queryStr, vals...) + scanned, err := scanCategoryRows(rows, returningCols) if err != nil { - return err + return nil, err + } + recordsOut = append(recordsOut, scanned...) + } + + if selects != nil && selects.hasAnyRelation() { + if err := d.loadRelations(ctx, recordsOut, selects); err != nil { + return nil, err + } + } + + return recordsOut, nil +} + +func (d *CategoryDelegate) runCreateManyAndReturnFallback( + ctx context.Context, + inputs []*CategoryCreate, + selects *CategorySelect, + omits *CategoryOmit, + conflictTarget UniqueConstraintTarget, + conflictAction *ConflictAction, +) ([]*Category, error) { + batches := partitionCategoryInputs(d.client.dialect, inputs) + returningCols := selectCategoryCols(selects, omits, categoryPKCols...) + recordsOut := make([]*Category, 0, len(inputs)) + + for _, batch := range batches { + cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) + queryStr, vals = applyCategoryConflictClause(d.client.dialect, queryStr, vals, cols, categoryPKCols, conflictTarget, conflictAction) + + result, err := d.client.exec(ctx, queryStr, vals...) + if err != nil { + return nil, err } - // We need to fetch the inserted records for this batch - // Note: MySQL bulk inserts only return the ID of the FIRST inserted row lastID, err := result.LastInsertId() if err != nil { - return err + return nil, err } - // Query back the rows by IDs (assuming autoincrement ID and single PK) - // If composite PK, it's more complex, but this is a standard fallback var selectSb strings.Builder selectSb.Grow(64 + len(returningCols)*15 + len("Category") + len(batch)*15) selectSb.WriteString("SELECT ") @@ -1118,55 +1271,29 @@ func (d *CategoryDelegate) runCreateManyAndReturn( if i > 0 { selectSb.WriteString(", ") } - txQ.dialect.WriteQuotedIdent(&selectSb, col) + d.client.dialect.WriteQuotedIdent(&selectSb, col) } selectSb.WriteString(" FROM ") - txQ.dialect.WriteQuotedIdent(&selectSb, "Category") + d.client.dialect.WriteQuotedIdent(&selectSb, "Category") selectSb.WriteString(" WHERE ") - txQ.dialect.WriteQuotedIdent(&selectSb, categoryPKCols[0]) + d.client.dialect.WriteQuotedIdent(&selectSb, categoryPKCols[0]) selectSb.WriteString(" >= ") - txQ.dialect.WritePlaceholder(&selectSb, 1) + d.client.dialect.WritePlaceholder(&selectSb, 1) selectSb.WriteString(" AND ") - txQ.dialect.WriteQuotedIdent(&selectSb, categoryPKCols[0]) + d.client.dialect.WriteQuotedIdent(&selectSb, categoryPKCols[0]) selectSb.WriteString(" < ") - txQ.dialect.WritePlaceholder(&selectSb, 2) + d.client.dialect.WritePlaceholder(&selectSb, 2) - rows, err := txQ.query(ctx, selectSb.String(), lastID, lastID+int64(len(batch))) + rows, err := d.client.query(ctx, selectSb.String(), lastID, lastID+int64(len(batch))) if err != nil { - return err - } - defer rows.Close() - - for rows.Next() { - var res Category - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - return err - } - recordsOut = append(recordsOut, &res) + return nil, err } - return rows.Err() - } - // Always wrap in transaction if we have multiple batches OR if we need to load relations - 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 { - return err - } - } - if hasRelations { - return txQ.Category.loadRelations(ctx, recordsOut, selects) - } - return nil - }) + scanned, err := scanCategoryRows(rows, returningCols) if err != nil { return nil, err } - } else { - if err := runBatch(d.client, batches[0]); err != nil { - return nil, err - } + recordsOut = append(recordsOut, scanned...) } return recordsOut, nil @@ -1310,23 +1437,23 @@ func (d *CategoryDelegate) UpdateManyAndReturn(preds ...PredicateOf[Category]) * } } -func (d *CategoryDelegate) buildUpdateSQL(preds []PredicateOf[Category], assignments []FieldAssignment, returningCols []string) (string, []any) { - whereClause, predVals, _ := CompilePredicates(d.client.dialect, preds, len(assignments)+1) +func (d *CategoryDelegate) buildUpdateSQL(preds []PredicateOf[Category], cols []string, vals []any, returningCols []string) (string, []any) { + whereClause, predVals, _ := CompilePredicates(d.client.dialect, preds, len(cols)+1) var sb strings.Builder sb.WriteString("UPDATE ") d.client.dialect.WriteQuotedIdent(&sb, "Category") sb.WriteString(" SET ") - setVals := make([]any, 0, len(assignments)+len(predVals)) - for i, a := range assignments { + setVals := make([]any, 0, len(cols)+len(predVals)) + for i, col := range cols { if i > 0 { sb.WriteString(", ") } - d.client.dialect.WriteQuotedIdent(&sb, a.Col) + d.client.dialect.WriteQuotedIdent(&sb, col) sb.WriteString(" = ") d.client.dialect.WritePlaceholder(&sb, i+1) - setVals = append(setVals, a.Val) + setVals = append(setVals, vals[i]) } if whereClause != "" { @@ -1353,21 +1480,39 @@ func (d *CategoryDelegate) buildUpdateSQL(preds []PredicateOf[Category], assignm // ----------------------------------------------------------------------------- func (d *CategoryDelegate) executeUpdate(ctx context.Context, where UniquePredicate[Category], additional []PredicateOf[Category], assignments []FieldAssignment, selects *CategorySelect, omits *CategoryOmit) (*Category, error) { + allWhere := make([]PredicateOf[Category], 0, 1+len(additional)) + allWhere = append(allWhere, where) + allWhere = append(allWhere, additional...) + + input, err := assignmentsToCategoryUpdate(assignments) + if err != nil { + return nil, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.runUpdate(ctx, where, additional, assignments, selects, omits) + return d.runUpdate(ctx, allWhere, cols, vals, selects, omits) } if selects == nil || !selects.hasAnySelected() { selects = fullCategorySelect() } - curr := func(c context.Context, w UniquePredicate[Category], add []PredicateOf[Category], a []FieldAssignment, s *CategorySelect, o *CategoryOmit) (*Category, error) { - return d.runUpdate(c, w, add, a, s, o) + args := &CategoryUpdateArgs{ + Where: allWhere, + Data: &input, + Select: selects, + } + + curr := func(c context.Context, a *CategoryUpdateArgs) (*Category, error) { + extCols, extVals := a.Data.ToColsVals() + return d.runUpdate(c, a.Where, extCols, extVals, a.Select, omits) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.Update != nil { - return ext.Update(ctx, where, additional, assignments, selects, omits, curr) + return ext.Update(ctx, args, curr) } } @@ -1375,25 +1520,21 @@ func (d *CategoryDelegate) executeUpdate(ctx context.Context, where UniquePredic ext := d.extensions[i] if ext.Update != nil { next, hook := curr, ext.Update - curr = func(c context.Context, w UniquePredicate[Category], add []PredicateOf[Category], a []FieldAssignment, s *CategorySelect, o *CategoryOmit) (*Category, error) { - return hook(c, w, add, a, s, o, next) + curr = func(c context.Context, a *CategoryUpdateArgs) (*Category, error) { + return hook(c, a, next) } } } - return curr(ctx, where, additional, assignments, selects, omits) + return curr(ctx, args) } -func (d *CategoryDelegate) runUpdate(ctx context.Context, where UniquePredicate[Category], additional []PredicateOf[Category], assignments []FieldAssignment, selects *CategorySelect, omits *CategoryOmit) (*Category, error) { - allPreds := append([]PredicateOf[Category]{where}, additional...) - if len(assignments) == 0 { - return d.runFindUnique(ctx, allPreds, selects, omits) +func (d *CategoryDelegate) runUpdate(ctx context.Context, preds []PredicateOf[Category], cols []string, vals []any, selects *CategorySelect, omits *CategoryOmit) (*Category, error) { + if len(cols) == 0 { + return d.runFindUnique(ctx, preds, selects, omits) } - if err := where.Validate(); err != nil { - return nil, err - } - for _, pr := range additional { + for _, pr := range preds { if pr != nil { if err := pr.Validate(); err != nil { return nil, err @@ -1409,9 +1550,9 @@ func (d *CategoryDelegate) runUpdate(ctx context.Context, where UniquePredicate[ err := d.client.transaction(ctx, func(txQ *Queries) error { var err error if d.client.dialect.SupportsUpdateReturning { - res, err = txQ.Category.runUpdate(ctx, where, additional, assignments, selects, omits) + res, err = txQ.Category.runUpdate(ctx, preds, cols, vals, selects, omits) } else { - res, err = txQ.Category.runUpdateFallback(ctx, where, additional, assignments, selects, omits) + res, err = txQ.Category.runUpdateFallback(ctx, preds, cols, vals, selects, omits) } return err }) @@ -1419,7 +1560,7 @@ func (d *CategoryDelegate) runUpdate(ctx context.Context, where UniquePredicate[ } returningCols := selectCategoryCols(selects, omits, categoryPKCols...) - query, setVals := d.buildUpdateSQL(allPreds, assignments, returningCols) + query, setVals := d.buildUpdateSQL(preds, cols, vals, returningCols) rows, err := d.client.query(ctx, query, setVals...) if err != nil { @@ -1451,8 +1592,8 @@ func (d *CategoryDelegate) runUpdate(ctx context.Context, where UniquePredicate[ return &res, nil } -func (d *CategoryDelegate) execUpdateStmt(ctx context.Context, preds []PredicateOf[Category], assignments []FieldAssignment) (int64, error) { - if len(assignments) == 0 { +func (d *CategoryDelegate) execUpdateStmt(ctx context.Context, preds []PredicateOf[Category], cols []string, vals []any) (int64, error) { + if len(cols) == 0 { return 0, nil } @@ -1464,7 +1605,7 @@ func (d *CategoryDelegate) execUpdateStmt(ctx context.Context, preds []Predicate } } - query, setVals := d.buildUpdateSQL(preds, assignments, nil) + query, setVals := d.buildUpdateSQL(preds, cols, vals, nil) result, err := d.client.exec(ctx, query, setVals...) if err != nil { return 0, err @@ -1472,16 +1613,15 @@ func (d *CategoryDelegate) execUpdateStmt(ctx context.Context, preds []Predicate return result.RowsAffected() } -func (d *CategoryDelegate) runUpdateFallback(ctx context.Context, where UniquePredicate[Category], additional []PredicateOf[Category], assignments []FieldAssignment, selects *CategorySelect, omits *CategoryOmit) (*Category, error) { - allPreds := append([]PredicateOf[Category]{where}, additional...) - affected, err := d.execUpdateStmt(ctx, allPreds, assignments) +func (d *CategoryDelegate) runUpdateFallback(ctx context.Context, preds []PredicateOf[Category], cols []string, vals []any, selects *CategorySelect, omits *CategoryOmit) (*Category, error) { + affected, err := d.execUpdateStmt(ctx, preds, cols, vals) if err != nil { return nil, err } if affected == 0 { return nil, sql.ErrNoRows } - return d.runFindUnique(ctx, allPreds, selects, omits) + return d.runFindUnique(ctx, preds, selects, omits) } // ----------------------------------------------------------------------------- @@ -1489,17 +1629,30 @@ func (d *CategoryDelegate) runUpdateFallback(ctx context.Context, where UniquePr // ----------------------------------------------------------------------------- func (d *CategoryDelegate) executeUpdateMany(ctx context.Context, preds []PredicateOf[Category], assignments []FieldAssignment) (int64, error) { + input, err := assignmentsToCategoryUpdate(assignments) + if err != nil { + return 0, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.execUpdateStmt(ctx, preds, assignments) + return d.execUpdateStmt(ctx, preds, cols, vals) + } + + args := &CategoryUpdateManyArgs{ + Where: preds, + Data: &input, } - curr := func(c context.Context, p []PredicateOf[Category], a []FieldAssignment) (int64, error) { - return d.execUpdateStmt(c, p, a) + curr := func(c context.Context, a *CategoryUpdateManyArgs) (int64, error) { + extCols, extVals := a.Data.ToColsVals() + return d.execUpdateStmt(c, a.Where, extCols, extVals) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.UpdateMany != nil { - return ext.UpdateMany(ctx, preds, assignments, curr) + return ext.UpdateMany(ctx, args, curr) } } @@ -1507,13 +1660,13 @@ func (d *CategoryDelegate) executeUpdateMany(ctx context.Context, preds []Predic ext := d.extensions[i] if ext.UpdateMany != nil { next, hook := curr, ext.UpdateMany - curr = func(c context.Context, p []PredicateOf[Category], a []FieldAssignment) (int64, error) { - return hook(c, p, a, next) + curr = func(c context.Context, a *CategoryUpdateManyArgs) (int64, error) { + return hook(c, a, next) } } } - return curr(ctx, preds, assignments) + return curr(ctx, args) } // ----------------------------------------------------------------------------- @@ -1521,21 +1674,35 @@ func (d *CategoryDelegate) executeUpdateMany(ctx context.Context, preds []Predic // ----------------------------------------------------------------------------- func (d *CategoryDelegate) executeUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[Category], assignments []FieldAssignment, selects *CategorySelect, omits *CategoryOmit) ([]*Category, error) { + input, err := assignmentsToCategoryUpdate(assignments) + if err != nil { + return nil, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.runUpdateManyAndReturn(ctx, preds, assignments, selects, omits) + return d.runUpdateManyAndReturn(ctx, preds, cols, vals, selects, omits) } if selects == nil || !selects.hasAnySelected() { selects = fullCategorySelect() } - curr := func(c context.Context, p []PredicateOf[Category], a []FieldAssignment, s *CategorySelect, o *CategoryOmit) ([]*Category, error) { - return d.runUpdateManyAndReturn(c, p, a, s, o) + args := &CategoryUpdateManyAndReturnArgs{ + Where: preds, + Data: &input, + Select: selects, + } + + curr := func(c context.Context, a *CategoryUpdateManyAndReturnArgs) ([]*Category, error) { + extCols, extVals := a.Data.ToColsVals() + return d.runUpdateManyAndReturn(c, a.Where, extCols, extVals, a.Select, omits) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.UpdateManyAndReturn != nil { - return ext.UpdateManyAndReturn(ctx, preds, assignments, selects, omits, curr) + return ext.UpdateManyAndReturn(ctx, args, curr) } } @@ -1543,17 +1710,17 @@ func (d *CategoryDelegate) executeUpdateManyAndReturn(ctx context.Context, preds ext := d.extensions[i] if ext.UpdateManyAndReturn != nil { next, hook := curr, ext.UpdateManyAndReturn - curr = func(c context.Context, p []PredicateOf[Category], a []FieldAssignment, s *CategorySelect, o *CategoryOmit) ([]*Category, error) { - return hook(c, p, a, s, o, next) + curr = func(c context.Context, a *CategoryUpdateManyAndReturnArgs) ([]*Category, error) { + return hook(c, a, next) } } } - return curr(ctx, preds, assignments, selects, omits) + return curr(ctx, args) } -func (d *CategoryDelegate) runUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[Category], assignments []FieldAssignment, selects *CategorySelect, omits *CategoryOmit) ([]*Category, error) { - if len(assignments) == 0 { +func (d *CategoryDelegate) runUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[Category], cols []string, vals []any, selects *CategorySelect, omits *CategoryOmit) ([]*Category, error) { + if len(cols) == 0 { return d.runFindMany(ctx, QueryParams[Category]{Where: preds}, selects, omits) } @@ -1573,9 +1740,9 @@ func (d *CategoryDelegate) runUpdateManyAndReturn(ctx context.Context, preds []P err := d.client.transaction(ctx, func(txQ *Queries) error { var err error if d.client.dialect.SupportsUpdateReturning { - res, err = txQ.Category.runUpdateManyAndReturn(ctx, preds, assignments, selects, omits) + res, err = txQ.Category.runUpdateManyAndReturn(ctx, preds, cols, vals, selects, omits) } else { - res, err = txQ.Category.runUpdateManyAndReturnFallback(ctx, preds, assignments, selects, omits) + res, err = txQ.Category.runUpdateManyAndReturnFallback(ctx, preds, cols, vals, selects, omits) } return err }) @@ -1583,39 +1750,29 @@ func (d *CategoryDelegate) runUpdateManyAndReturn(ctx context.Context, preds []P } returningCols := selectCategoryCols(selects, omits, categoryPKCols...) - query, setVals := d.buildUpdateSQL(preds, assignments, returningCols) + query, setVals := d.buildUpdateSQL(preds, cols, vals, returningCols) rows, err := d.client.query(ctx, query, setVals...) if err != nil { return nil, err } - results := make([]*Category, 0) - for rows.Next() { - var res Category - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - rows.Close() - return nil, err - } - results = append(results, &res) - } - rowsErr := rows.Err() - rows.Close() - if rowsErr != nil { - return nil, rowsErr + scanned, err := scanCategoryRows(rows, returningCols) + if err != nil { + return nil, err } if selects != nil && selects.hasAnyRelation() { - if err := d.loadRelations(ctx, results, selects); err != nil { + if err := d.loadRelations(ctx, scanned, selects); err != nil { return nil, err } } - return results, nil + return scanned, nil } -func (d *CategoryDelegate) runUpdateManyAndReturnFallback(ctx context.Context, preds []PredicateOf[Category], assignments []FieldAssignment, selects *CategorySelect, omits *CategoryOmit) ([]*Category, error) { - affected, err := d.execUpdateStmt(ctx, preds, assignments) +func (d *CategoryDelegate) runUpdateManyAndReturnFallback(ctx context.Context, preds []PredicateOf[Category], cols []string, vals []any, selects *CategorySelect, omits *CategoryOmit) ([]*Category, error) { + affected, err := d.execUpdateStmt(ctx, preds, cols, vals) if err != nil { return nil, err } diff --git a/integration/valk/categoryToPost.go b/integration/valk/categoryToPost.go index 338780b..313fbac 100644 --- a/integration/valk/categoryToPost.go +++ b/integration/valk/categoryToPost.go @@ -35,6 +35,58 @@ func (s *CategoryToPostCreate) colMask() uint64 { return mask } +// CategoryToPostUpdate contains model input fields for CategoryToPost update operations. +type CategoryToPostUpdate struct { + PostId *string `json:"postId"` + CategoryId *int32 `json:"categoryId"` +} + +func (u *CategoryToPostUpdate) ToColsVals() ([]string, []any) { + var cols []string + var vals []any + if u.PostId != nil { + cols = append(cols, "postId") + vals = append(vals, u.PostId) + } + if u.CategoryId != nil { + cols = append(cols, "categoryId") + vals = append(vals, u.CategoryId) + } + return cols, vals +} + +func assignmentsToCategoryToPostUpdate(assignments []FieldAssignment) (CategoryToPostUpdate, error) { + var input CategoryToPostUpdate + var errs ValidationError + + for _, a := range assignments { + switch a.Col { + case "postId": + if v, ok := a.Val.(string); ok { + input.PostId = &v + errs.ValidateString("postId", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.PostId = v + } else { + errs.Add("postId", a.Val, "type", "field postId must be of type string") + } + case "categoryId": + if v, ok := a.Val.(int32); ok { + input.CategoryId = &v + } else if v, ok := a.Val.(*int32); ok { + input.CategoryId = v + } else { + errs.Add("categoryId", a.Val, "type", "field categoryId must be of type int32") + } + } + } + + if errs.HasErrors() { + return input, errs + } + return input, nil +} + // CategoryToPostSelect specifies which scalar and relation fields to select for CategoryToPost. // // Selectable fields: @@ -375,6 +427,51 @@ func (a *CategoryToPostDeleteManyArgs) SetWhere(preds ...PredicateOf[CategoryToP return a } +// CategoryToPostUpdateArgs is the input argument passed to CategoryToPost Update extension hooks. +type CategoryToPostUpdateArgs struct { + // Where contains all query filter predicates (merged primary unique constraint and additional predicates). + Where []PredicateOf[CategoryToPost] + // Data contains the model fields to update. + Data *CategoryToPostUpdate + // Select specifies which scalar and relation fields to select and return upon update. + Select *CategoryToPostSelect +} + +func (a *CategoryToPostUpdateArgs) SetWhere(unique UniquePredicate[CategoryToPost], additional ...PredicateOf[CategoryToPost]) *CategoryToPostUpdateArgs { + a.Where = make([]PredicateOf[CategoryToPost], 0, 1+len(additional)) + a.Where = append(a.Where, unique) + a.Where = append(a.Where, additional...) + return a +} + +// CategoryToPostUpdateManyArgs is the input argument passed to CategoryToPost UpdateMany extension hooks. +type CategoryToPostUpdateManyArgs struct { + // Where contains all query filter predicates. + Where []PredicateOf[CategoryToPost] + // Data contains the model fields to update. + Data *CategoryToPostUpdate +} + +func (a *CategoryToPostUpdateManyArgs) SetWhere(preds ...PredicateOf[CategoryToPost]) *CategoryToPostUpdateManyArgs { + a.Where = preds + return a +} + +// CategoryToPostUpdateManyAndReturnArgs is the input argument passed to CategoryToPost UpdateManyAndReturn extension hooks. +type CategoryToPostUpdateManyAndReturnArgs struct { + // Where contains all query filter predicates. + Where []PredicateOf[CategoryToPost] + // Data contains the model fields to update. + Data *CategoryToPostUpdate + // Select specifies which scalar and relation fields to select and return upon update. + Select *CategoryToPostSelect +} + +func (a *CategoryToPostUpdateManyAndReturnArgs) SetWhere(preds ...PredicateOf[CategoryToPost]) *CategoryToPostUpdateManyAndReturnArgs { + a.Where = preds + return a +} + type CategoryToPostCreateQuery = func(ctx context.Context, args *CategoryToPostCreateArgs) (*CategoryToPost, error) type CategoryToPostCreateManyQuery = func(ctx context.Context, args *CategoryToPostCreateManyArgs) (int64, error) type CategoryToPostCreateManyAndReturnQuery = func(ctx context.Context, args *CategoryToPostCreateManyAndReturnArgs) ([]*CategoryToPost, error) @@ -384,9 +481,9 @@ type CategoryToPostFindManyQuery = func(ctx context.Context, args *CategoryToPos type CategoryToPostDeleteQuery = func(ctx context.Context, args *CategoryToPostDeleteArgs) (*CategoryToPost, error) type CategoryToPostDeleteManyQuery = func(ctx context.Context, args *CategoryToPostDeleteManyArgs) (int64, error) type CategoryToPostCountQuery = func(ctx context.Context, args *CategoryToPostCountArgs) (int64, error) -type CategoryToPostUpdateQuery = func(ctx context.Context, where UniquePredicate[CategoryToPost], additional []PredicateOf[CategoryToPost], assignments []FieldAssignment, selects *CategoryToPostSelect, omits *CategoryToPostOmit) (*CategoryToPost, error) -type CategoryToPostUpdateManyQuery = func(ctx context.Context, preds []PredicateOf[CategoryToPost], assignments []FieldAssignment) (int64, error) -type CategoryToPostUpdateManyAndReturnQuery = func(ctx context.Context, preds []PredicateOf[CategoryToPost], assignments []FieldAssignment, selects *CategoryToPostSelect, omits *CategoryToPostOmit) ([]*CategoryToPost, error) +type CategoryToPostUpdateQuery = func(ctx context.Context, args *CategoryToPostUpdateArgs) (*CategoryToPost, error) +type CategoryToPostUpdateManyQuery = func(ctx context.Context, args *CategoryToPostUpdateManyArgs) (int64, error) +type CategoryToPostUpdateManyAndReturnQuery = func(ctx context.Context, args *CategoryToPostUpdateManyAndReturnArgs) ([]*CategoryToPost, error) type CategoryToPostExtension struct { Create func(ctx context.Context, args *CategoryToPostCreateArgs, next CategoryToPostCreateQuery) (*CategoryToPost, error) @@ -398,9 +495,9 @@ type CategoryToPostExtension struct { Delete func(ctx context.Context, args *CategoryToPostDeleteArgs, next CategoryToPostDeleteQuery) (*CategoryToPost, error) DeleteMany func(ctx context.Context, args *CategoryToPostDeleteManyArgs, next CategoryToPostDeleteManyQuery) (int64, error) Count func(ctx context.Context, args *CategoryToPostCountArgs, next CategoryToPostCountQuery) (int64, error) - Update func(ctx context.Context, where UniquePredicate[CategoryToPost], additional []PredicateOf[CategoryToPost], assignments []FieldAssignment, selects *CategoryToPostSelect, omits *CategoryToPostOmit, next CategoryToPostUpdateQuery) (*CategoryToPost, error) - UpdateMany func(ctx context.Context, preds []PredicateOf[CategoryToPost], assignments []FieldAssignment, next CategoryToPostUpdateManyQuery) (int64, error) - UpdateManyAndReturn func(ctx context.Context, preds []PredicateOf[CategoryToPost], assignments []FieldAssignment, selects *CategoryToPostSelect, omits *CategoryToPostOmit, next CategoryToPostUpdateManyAndReturnQuery) ([]*CategoryToPost, error) + Update func(ctx context.Context, args *CategoryToPostUpdateArgs, next CategoryToPostUpdateQuery) (*CategoryToPost, error) + UpdateMany func(ctx context.Context, args *CategoryToPostUpdateManyArgs, next CategoryToPostUpdateManyQuery) (int64, error) + UpdateManyAndReturn func(ctx context.Context, args *CategoryToPostUpdateManyAndReturnArgs, next CategoryToPostUpdateManyAndReturnQuery) ([]*CategoryToPost, error) } type CategoryToPostDelegate struct { @@ -471,6 +568,16 @@ type CategoryToPostCreateBuilder struct { *CreateBuilder[CategoryToPost, CategoryToPostSelect, CategoryToPostOmit] } +func (b *CategoryToPostCreateBuilder) Select(s CategoryToPostSelect) *CategoryToPostCreateBuilder { + b.selects = &s + return b +} + +func (b *CategoryToPostCreateBuilder) Omit(o CategoryToPostOmit) *CategoryToPostCreateBuilder { + b.omits = &o + return b +} + func (b *CategoryToPostCreateBuilder) OnConflict(target UniqueConstraintTarget) *CategoryToPostConflictBuilder[CategoryToPostCreateBuilder] { return &CategoryToPostConflictBuilder[CategoryToPostCreateBuilder]{ builder: b, @@ -594,23 +701,11 @@ func (d *CategoryToPostDelegate) executeCreate(ctx context.Context, assignments return nil, err } + cols, vals := input.ToColsVals() + returningCols := selectCategoryToPostCols(selects, omits) + if len(d.extensions) == 0 { - cols, vals := input.ToColsVals() - returningCols := selectCategoryToPostCols(selects, omits) - hasRelations := selects.hasAnyRelation() - if hasRelations { - var res *CategoryToPost - err = d.client.transaction(ctx, func(txQ *Queries) error { - var err error - res, err = txQ.CategoryToPost.runCreate(ctx, cols, vals, returningCols, categoryToPostPKCols, conflictTarget, conflictAction) - if err != nil { - return err - } - return txQ.CategoryToPost.loadRelations(ctx, []*CategoryToPost{res}, selects) - }) - return res, err - } - return d.runCreate(ctx, cols, vals, returningCols, categoryToPostPKCols, conflictTarget, conflictAction) + return d.runCreate(ctx, cols, vals, returningCols, selects, conflictTarget, conflictAction) } if selects == nil || !selects.hasAnySelected() { @@ -625,28 +720,9 @@ func (d *CategoryToPostDelegate) executeCreate(ctx context.Context, assignments } curr := func(c context.Context, a *CategoryToPostCreateArgs) (*CategoryToPost, error) { - cols, vals := a.Data.ToColsVals() - returningCols := selectCategoryToPostCols(a.Select, omits) - - hasRelations := a.Select.hasAnyRelation() - var res *CategoryToPost - var err error - if hasRelations { - err = d.client.transaction(c, func(txQ *Queries) error { - var err error - res, err = txQ.CategoryToPost.runCreate(c, cols, vals, returningCols, categoryToPostPKCols, a.ConflictTarget, a.ConflictAction) - if err != nil { - return err - } - return txQ.CategoryToPost.loadRelations(c, []*CategoryToPost{res}, a.Select) - }) - } else { - res, err = d.runCreate(c, cols, vals, returningCols, categoryToPostPKCols, a.ConflictTarget, a.ConflictAction) - } - if err != nil { - return nil, err - } - return res, nil + cCols, cVals := a.Data.ToColsVals() + cReturningCols := selectCategoryToPostCols(a.Select, omits) + return d.runCreate(c, cCols, cVals, cReturningCols, a.Select, a.ConflictTarget, a.ConflictAction) } if len(d.extensions) == 1 { @@ -687,6 +763,16 @@ type CategoryToPostCreateManyAndReturnBuilder struct { *CreateManyAndReturnBuilder[CategoryToPost, CategoryToPostSelect, CategoryToPostOmit] } +func (b *CategoryToPostCreateManyAndReturnBuilder) Select(s CategoryToPostSelect) *CategoryToPostCreateManyAndReturnBuilder { + b.selects = &s + return b +} + +func (b *CategoryToPostCreateManyAndReturnBuilder) Omit(o CategoryToPostOmit) *CategoryToPostCreateManyAndReturnBuilder { + b.omits = &o + return b +} + func (b *CategoryToPostCreateManyAndReturnBuilder) OnConflict(target UniqueConstraintTarget) *CategoryToPostConflictBuilder[CategoryToPostCreateManyAndReturnBuilder] { return &CategoryToPostConflictBuilder[CategoryToPostCreateManyAndReturnBuilder]{ builder: b, @@ -698,43 +784,51 @@ func (b *CategoryToPostCreateManyAndReturnBuilder) OnConflict(target UniqueConst } } -func (d *CategoryToPostDelegate) CreateMany(builders ...*CategoryToPostCreateBuilder) *CategoryToPostCreateManyBuilder { +func createBuildersToCategoryToPostRecordInputs(builders []*CategoryToPostCreateBuilder) []RecordInput { records := make([]RecordInput, len(builders)) for i, b := range builders { records[i] = RecordInput{Assignments: b.assignments} } + return records +} + +func (d *CategoryToPostDelegate) CreateMany(builders ...*CategoryToPostCreateBuilder) *CategoryToPostCreateManyBuilder { return &CategoryToPostCreateManyBuilder{ CreateManyBuilder: &CreateManyBuilder[CategoryToPost]{ - records: records, + records: createBuildersToCategoryToPostRecordInputs(builders), execFunc: d.executeCreateMany, }, } } func (d *CategoryToPostDelegate) CreateManyAndReturn(builders ...*CategoryToPostCreateBuilder) *CategoryToPostCreateManyAndReturnBuilder { - records := make([]RecordInput, len(builders)) - for i, b := range builders { - records[i] = RecordInput{Assignments: b.assignments} - } return &CategoryToPostCreateManyAndReturnBuilder{ CreateManyAndReturnBuilder: &CreateManyAndReturnBuilder[CategoryToPost, CategoryToPostSelect, CategoryToPostOmit]{ - records: records, + records: createBuildersToCategoryToPostRecordInputs(builders), execFunc: d.executeCreateManyAndReturn, }, } } -func (d *CategoryToPostDelegate) executeCreateMany(ctx context.Context, records []RecordInput, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { +func recordsToCategoryToPostCreateInputs(records []RecordInput) ([]*CategoryToPostCreate, error) { structs := make([]CategoryToPostCreate, len(records)) inputs := make([]*CategoryToPostCreate, len(records)) for i, rec := range records { var err error structs[i], err = assignmentsToCategoryToPostCreate(rec.Assignments) if err != nil { - return 0, fmt.Errorf("validation failed at index %d: %w", i, err) + return nil, fmt.Errorf("validation failed at index %d: %w", i, err) } inputs[i] = &structs[i] } + return inputs, nil +} + +func (d *CategoryToPostDelegate) executeCreateMany(ctx context.Context, records []RecordInput, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { + inputs, err := recordsToCategoryToPostCreateInputs(records) + if err != nil { + return 0, err + } if len(d.extensions) == 0 { return d.runCreateMany(ctx, inputs, conflictTarget, conflictAction) @@ -770,31 +864,12 @@ func (d *CategoryToPostDelegate) executeCreateMany(ctx context.Context, records } func (d *CategoryToPostDelegate) executeCreateManyAndReturn(ctx context.Context, records []RecordInput, selects *CategoryToPostSelect, omits *CategoryToPostOmit, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) ([]*CategoryToPost, error) { - structs := make([]CategoryToPostCreate, len(records)) - inputs := make([]*CategoryToPostCreate, len(records)) - for i, rec := range records { - var err error - structs[i], err = assignmentsToCategoryToPostCreate(rec.Assignments) - if err != nil { - return nil, fmt.Errorf("validation failed at index %d: %w", i, err) - } - inputs[i] = &structs[i] + inputs, err := recordsToCategoryToPostCreateInputs(records) + if err != nil { + return nil, err } if len(d.extensions) == 0 { - hasRelations := selects != nil && selects.hasAnyRelation() - if hasRelations { - var res []*CategoryToPost - err := d.client.transaction(ctx, func(txQ *Queries) error { - var err error - res, err = txQ.CategoryToPost.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) - if err != nil { - return err - } - return txQ.CategoryToPost.loadRelations(ctx, res, selects) - }) - return res, err - } return d.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) } @@ -810,19 +885,6 @@ func (d *CategoryToPostDelegate) executeCreateManyAndReturn(ctx context.Context, } curr := func(c context.Context, a *CategoryToPostCreateManyAndReturnArgs) ([]*CategoryToPost, error) { - hasRelations := a.Select != nil && a.Select.hasAnyRelation() - if hasRelations { - var res []*CategoryToPost - err := d.client.transaction(c, func(txQ *Queries) error { - var err error - res, err = txQ.CategoryToPost.runCreateManyAndReturn(c, a.Data, a.Select, omits, a.ConflictTarget, a.ConflictAction) - if err != nil { - return err - } - return txQ.CategoryToPost.loadRelations(c, res, a.Select) - }) - return res, err - } return d.runCreateManyAndReturn(c, a.Data, a.Select, omits, a.ConflictTarget, a.ConflictAction) } @@ -850,36 +912,67 @@ func (d *CategoryToPostDelegate) runCreate( cols []string, vals []any, returningCols []string, - pkCols []string, + selects *CategoryToPostSelect, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction, ) (*CategoryToPost, error) { - query, clauseArgs := buildSingleInsertSQL(d.client, "CategoryToPost", cols, returningCols, pkCols, conflictTarget, conflictAction, len(vals)) + hasRelations := selects != nil && selects.hasAnyRelation() + useTx := hasRelations && !d.client.inTx() + + if useTx { + var res *CategoryToPost + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + res, err = txQ.CategoryToPost.runCreate(ctx, cols, vals, returningCols, selects, conflictTarget, conflictAction) + if err != nil { + return err + } + return txQ.CategoryToPost.loadRelations(ctx, []*CategoryToPost{res}, selects) + }) + return res, err + } + + query, clauseArgs := buildSingleInsertSQL(d.client, "CategoryToPost", cols, returningCols, categoryToPostPKCols, conflictTarget, conflictAction, len(vals)) if len(clauseArgs) > 0 { vals = append(vals, clauseArgs...) } - var res CategoryToPost if d.client.dialect.SupportsInsertReturning { rows, err := d.client.query(ctx, query, vals...) if err != nil { return nil, err } - defer rows.Close() - if rows.Next() { - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + if !rows.Next() { + err := rows.Err() + rows.Close() + if err != nil { return nil, err } - return &res, nil + return nil, nil + } + + var res CategoryToPost + scanErr := rows.Scan(res.ScanFields(returningCols)...) + rows.Close() + if scanErr != nil { + return nil, scanErr } - return nil, rows.Err() + + return &res, nil } - return d.runCreateFallback(ctx, query, vals, cols, returningCols, pkCols) + return d.runCreateFallback(ctx, query, vals, cols, returningCols, categoryToPostPKCols) } -func (d *CategoryToPostDelegate) runCreateFallback(ctx context.Context, query string, vals []any, cols []string, returningCols []string, pkCols []string) (*CategoryToPost, error) { +func (d *CategoryToPostDelegate) runCreateFallback( + ctx context.Context, + query string, + vals []any, + cols []string, + returningCols []string, + pkCols []string, +) (*CategoryToPost, error) { result, err := d.client.exec(ctx, query, vals...) if err != nil { return nil, err @@ -929,16 +1022,24 @@ func (d *CategoryToPostDelegate) runCreateFallback(ctx context.Context, query st if err != nil { return nil, err } - defer rows.Close() - var res CategoryToPost - if rows.Next() { - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + if !rows.Next() { + err := rows.Err() + rows.Close() + if err != nil { return nil, err } - return &res, nil + return nil, nil } - return nil, rows.Err() + + var res CategoryToPost + scanErr := rows.Scan(res.ScanFields(returningCols)...) + rows.Close() + if scanErr != nil { + return nil, scanErr + } + + return &res, nil } func (d *CategoryToPostDelegate) buildBulkInsertSQL(q *Queries, batch []*CategoryToPostCreate, paramStartIdx int) (cols []string, vals []any, queryStr string) { @@ -998,6 +1099,41 @@ func (d *CategoryToPostDelegate) buildBulkInsertSQL(q *Queries, batch []*Categor return cols, vals, queryStr } +func applyCategoryToPostConflictClause(dialect Dialect, queryStr string, vals []any, cols []string, pkCols []string, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (string, []any) { + var conflictCols []string + if conflictTarget != nil { + conflictCols = conflictTarget.UniqueColumns() + } + var nonConflictCols []string + if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { + nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + } + clause, clauseArgs := dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) + queryStr += clause + if len(clauseArgs) > 0 { + vals = append(vals, clauseArgs...) + } + return queryStr, vals +} + +func scanCategoryToPostRows(rows *sql.Rows, returningCols []string) ([]*CategoryToPost, error) { + var records []*CategoryToPost + for rows.Next() { + var res CategoryToPost + if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + rows.Close() + return nil, err + } + records = append(records, &res) + } + if err := rows.Err(); err != nil { + rows.Close() + return nil, err + } + rows.Close() + return records, nil +} + func (d *CategoryToPostDelegate) runCreateMany(ctx context.Context, inputs []*CategoryToPostCreate, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { if len(inputs) == 0 { return 0, nil @@ -1008,18 +1144,7 @@ func (d *CategoryToPostDelegate) runCreateMany(ctx context.Context, inputs []*Ca var count int64 for _, batch := range batches { cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) - - var conflictCols []string - if conflictTarget != nil { - conflictCols = conflictTarget.UniqueColumns() - } - var nonConflictCols []string - if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, categoryToPostPKCols) - } - clause, clauseArgs := d.client.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) - queryStr += clause - vals = append(vals, clauseArgs...) + queryStr, vals = applyCategoryToPostConflictClause(d.client.dialect, queryStr, vals, cols, categoryToPostPKCols, conflictTarget, conflictAction) result, err := d.client.exec(ctx, queryStr, vals...) if err != nil { @@ -1047,27 +1172,37 @@ func (d *CategoryToPostDelegate) runCreateManyAndReturn( } batches := partitionCategoryToPostInputs(d.client.dialect, inputs) - returningCols := selectCategoryToPostCols(selects, omits) hasRelations := selects != nil && selects.hasAnyRelation() + useTx := (len(batches) > 1 || hasRelations || !d.client.dialect.SupportsInsertReturning) && !d.client.inTx() - recordsOut := make([]*CategoryToPost, 0, len(inputs)) + if useTx { + var res []*CategoryToPost + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + if txQ.dialect.SupportsInsertReturning { + res, err = txQ.CategoryToPost.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) + } else { + res, err = txQ.CategoryToPost.runCreateManyAndReturnFallback(ctx, inputs, selects, omits, conflictTarget, conflictAction) + } + if err != nil { + return err + } + if hasRelations { + return txQ.CategoryToPost.loadRelations(ctx, res, selects) + } + return nil + }) + return res, err + } - runBatch := func(txQ *Queries, batch []*CategoryToPostCreate) error { - cols, vals, queryStr := d.buildBulkInsertSQL(txQ, batch, 1) + returningCols := selectCategoryToPostCols(selects, omits, categoryToPostPKCols...) + recordsOut := make([]*CategoryToPost, 0, len(inputs)) - var conflictCols []string - if conflictTarget != nil { - conflictCols = conflictTarget.UniqueColumns() - } - var nonConflictCols []string - if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, categoryToPostPKCols) - } - clause, clauseArgs := txQ.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) - queryStr += clause - vals = append(vals, clauseArgs...) + for _, batch := range batches { + cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) + queryStr, vals = applyCategoryToPostConflictClause(d.client.dialect, queryStr, vals, cols, categoryToPostPKCols, conflictTarget, conflictAction) - if txQ.dialect.SupportsInsertReturning && len(returningCols) > 0 { + if len(returningCols) > 0 { var retSb strings.Builder retSb.Grow(12 + len(returningCols)*15) retSb.WriteString(" RETURNING ") @@ -1075,40 +1210,58 @@ func (d *CategoryToPostDelegate) runCreateManyAndReturn( if i > 0 { retSb.WriteString(", ") } - txQ.dialect.WriteQuotedIdent(&retSb, col) + d.client.dialect.WriteQuotedIdent(&retSb, col) } queryStr += retSb.String() - rows, err := txQ.query(ctx, queryStr, vals...) - if err != nil { - return err - } - defer rows.Close() + } - for rows.Next() { - var res CategoryToPost - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - return err - } - recordsOut = append(recordsOut, &res) - } - return rows.Err() + rows, err := d.client.query(ctx, queryStr, vals...) + if err != nil { + return nil, err } - // Fallback for dialects without RETURNING (MySQL) - result, err := txQ.exec(ctx, queryStr, vals...) + scanned, err := scanCategoryToPostRows(rows, returningCols) if err != nil { - return err + return nil, err + } + recordsOut = append(recordsOut, scanned...) + } + + if selects != nil && selects.hasAnyRelation() { + if err := d.loadRelations(ctx, recordsOut, selects); err != nil { + return nil, err + } + } + + return recordsOut, nil +} + +func (d *CategoryToPostDelegate) runCreateManyAndReturnFallback( + ctx context.Context, + inputs []*CategoryToPostCreate, + selects *CategoryToPostSelect, + omits *CategoryToPostOmit, + conflictTarget UniqueConstraintTarget, + conflictAction *ConflictAction, +) ([]*CategoryToPost, error) { + batches := partitionCategoryToPostInputs(d.client.dialect, inputs) + returningCols := selectCategoryToPostCols(selects, omits, categoryToPostPKCols...) + recordsOut := make([]*CategoryToPost, 0, len(inputs)) + + for _, batch := range batches { + cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) + queryStr, vals = applyCategoryToPostConflictClause(d.client.dialect, queryStr, vals, cols, categoryToPostPKCols, conflictTarget, conflictAction) + + result, err := d.client.exec(ctx, queryStr, vals...) + if err != nil { + return nil, err } - // We need to fetch the inserted records for this batch - // Note: MySQL bulk inserts only return the ID of the FIRST inserted row lastID, err := result.LastInsertId() if err != nil { - return err + return nil, err } - // Query back the rows by IDs (assuming autoincrement ID and single PK) - // If composite PK, it's more complex, but this is a standard fallback var selectSb strings.Builder selectSb.Grow(64 + len(returningCols)*15 + len("CategoryToPost") + len(batch)*15) selectSb.WriteString("SELECT ") @@ -1116,55 +1269,29 @@ func (d *CategoryToPostDelegate) runCreateManyAndReturn( if i > 0 { selectSb.WriteString(", ") } - txQ.dialect.WriteQuotedIdent(&selectSb, col) + d.client.dialect.WriteQuotedIdent(&selectSb, col) } selectSb.WriteString(" FROM ") - txQ.dialect.WriteQuotedIdent(&selectSb, "CategoryToPost") + d.client.dialect.WriteQuotedIdent(&selectSb, "CategoryToPost") selectSb.WriteString(" WHERE ") - txQ.dialect.WriteQuotedIdent(&selectSb, categoryToPostPKCols[0]) + d.client.dialect.WriteQuotedIdent(&selectSb, categoryToPostPKCols[0]) selectSb.WriteString(" >= ") - txQ.dialect.WritePlaceholder(&selectSb, 1) + d.client.dialect.WritePlaceholder(&selectSb, 1) selectSb.WriteString(" AND ") - txQ.dialect.WriteQuotedIdent(&selectSb, categoryToPostPKCols[0]) + d.client.dialect.WriteQuotedIdent(&selectSb, categoryToPostPKCols[0]) selectSb.WriteString(" < ") - txQ.dialect.WritePlaceholder(&selectSb, 2) + d.client.dialect.WritePlaceholder(&selectSb, 2) - rows, err := txQ.query(ctx, selectSb.String(), lastID, lastID+int64(len(batch))) + rows, err := d.client.query(ctx, selectSb.String(), lastID, lastID+int64(len(batch))) if err != nil { - return err - } - defer rows.Close() - - for rows.Next() { - var res CategoryToPost - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - return err - } - recordsOut = append(recordsOut, &res) + return nil, err } - return rows.Err() - } - // Always wrap in transaction if we have multiple batches OR if we need to load relations - 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 { - return err - } - } - if hasRelations { - return txQ.CategoryToPost.loadRelations(ctx, recordsOut, selects) - } - return nil - }) + scanned, err := scanCategoryToPostRows(rows, returningCols) if err != nil { return nil, err } - } else { - if err := runBatch(d.client, batches[0]); err != nil { - return nil, err - } + recordsOut = append(recordsOut, scanned...) } return recordsOut, nil @@ -1308,23 +1435,23 @@ func (d *CategoryToPostDelegate) UpdateManyAndReturn(preds ...PredicateOf[Catego } } -func (d *CategoryToPostDelegate) buildUpdateSQL(preds []PredicateOf[CategoryToPost], assignments []FieldAssignment, returningCols []string) (string, []any) { - whereClause, predVals, _ := CompilePredicates(d.client.dialect, preds, len(assignments)+1) +func (d *CategoryToPostDelegate) buildUpdateSQL(preds []PredicateOf[CategoryToPost], cols []string, vals []any, returningCols []string) (string, []any) { + whereClause, predVals, _ := CompilePredicates(d.client.dialect, preds, len(cols)+1) var sb strings.Builder sb.WriteString("UPDATE ") d.client.dialect.WriteQuotedIdent(&sb, "CategoryToPost") sb.WriteString(" SET ") - setVals := make([]any, 0, len(assignments)+len(predVals)) - for i, a := range assignments { + setVals := make([]any, 0, len(cols)+len(predVals)) + for i, col := range cols { if i > 0 { sb.WriteString(", ") } - d.client.dialect.WriteQuotedIdent(&sb, a.Col) + d.client.dialect.WriteQuotedIdent(&sb, col) sb.WriteString(" = ") d.client.dialect.WritePlaceholder(&sb, i+1) - setVals = append(setVals, a.Val) + setVals = append(setVals, vals[i]) } if whereClause != "" { @@ -1351,21 +1478,39 @@ func (d *CategoryToPostDelegate) buildUpdateSQL(preds []PredicateOf[CategoryToPo // ----------------------------------------------------------------------------- func (d *CategoryToPostDelegate) executeUpdate(ctx context.Context, where UniquePredicate[CategoryToPost], additional []PredicateOf[CategoryToPost], assignments []FieldAssignment, selects *CategoryToPostSelect, omits *CategoryToPostOmit) (*CategoryToPost, error) { + allWhere := make([]PredicateOf[CategoryToPost], 0, 1+len(additional)) + allWhere = append(allWhere, where) + allWhere = append(allWhere, additional...) + + input, err := assignmentsToCategoryToPostUpdate(assignments) + if err != nil { + return nil, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.runUpdate(ctx, where, additional, assignments, selects, omits) + return d.runUpdate(ctx, allWhere, cols, vals, selects, omits) } if selects == nil || !selects.hasAnySelected() { selects = fullCategoryToPostSelect() } - curr := func(c context.Context, w UniquePredicate[CategoryToPost], add []PredicateOf[CategoryToPost], a []FieldAssignment, s *CategoryToPostSelect, o *CategoryToPostOmit) (*CategoryToPost, error) { - return d.runUpdate(c, w, add, a, s, o) + args := &CategoryToPostUpdateArgs{ + Where: allWhere, + Data: &input, + Select: selects, + } + + curr := func(c context.Context, a *CategoryToPostUpdateArgs) (*CategoryToPost, error) { + extCols, extVals := a.Data.ToColsVals() + return d.runUpdate(c, a.Where, extCols, extVals, a.Select, omits) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.Update != nil { - return ext.Update(ctx, where, additional, assignments, selects, omits, curr) + return ext.Update(ctx, args, curr) } } @@ -1373,25 +1518,21 @@ func (d *CategoryToPostDelegate) executeUpdate(ctx context.Context, where Unique ext := d.extensions[i] if ext.Update != nil { next, hook := curr, ext.Update - curr = func(c context.Context, w UniquePredicate[CategoryToPost], add []PredicateOf[CategoryToPost], a []FieldAssignment, s *CategoryToPostSelect, o *CategoryToPostOmit) (*CategoryToPost, error) { - return hook(c, w, add, a, s, o, next) + curr = func(c context.Context, a *CategoryToPostUpdateArgs) (*CategoryToPost, error) { + return hook(c, a, next) } } } - return curr(ctx, where, additional, assignments, selects, omits) + return curr(ctx, args) } -func (d *CategoryToPostDelegate) runUpdate(ctx context.Context, where UniquePredicate[CategoryToPost], additional []PredicateOf[CategoryToPost], assignments []FieldAssignment, selects *CategoryToPostSelect, omits *CategoryToPostOmit) (*CategoryToPost, error) { - allPreds := append([]PredicateOf[CategoryToPost]{where}, additional...) - if len(assignments) == 0 { - return d.runFindUnique(ctx, allPreds, selects, omits) +func (d *CategoryToPostDelegate) runUpdate(ctx context.Context, preds []PredicateOf[CategoryToPost], cols []string, vals []any, selects *CategoryToPostSelect, omits *CategoryToPostOmit) (*CategoryToPost, error) { + if len(cols) == 0 { + return d.runFindUnique(ctx, preds, selects, omits) } - if err := where.Validate(); err != nil { - return nil, err - } - for _, pr := range additional { + for _, pr := range preds { if pr != nil { if err := pr.Validate(); err != nil { return nil, err @@ -1407,9 +1548,9 @@ func (d *CategoryToPostDelegate) runUpdate(ctx context.Context, where UniquePred err := d.client.transaction(ctx, func(txQ *Queries) error { var err error if d.client.dialect.SupportsUpdateReturning { - res, err = txQ.CategoryToPost.runUpdate(ctx, where, additional, assignments, selects, omits) + res, err = txQ.CategoryToPost.runUpdate(ctx, preds, cols, vals, selects, omits) } else { - res, err = txQ.CategoryToPost.runUpdateFallback(ctx, where, additional, assignments, selects, omits) + res, err = txQ.CategoryToPost.runUpdateFallback(ctx, preds, cols, vals, selects, omits) } return err }) @@ -1417,7 +1558,7 @@ func (d *CategoryToPostDelegate) runUpdate(ctx context.Context, where UniquePred } returningCols := selectCategoryToPostCols(selects, omits, categoryToPostPKCols...) - query, setVals := d.buildUpdateSQL(allPreds, assignments, returningCols) + query, setVals := d.buildUpdateSQL(preds, cols, vals, returningCols) rows, err := d.client.query(ctx, query, setVals...) if err != nil { @@ -1449,8 +1590,8 @@ func (d *CategoryToPostDelegate) runUpdate(ctx context.Context, where UniquePred return &res, nil } -func (d *CategoryToPostDelegate) execUpdateStmt(ctx context.Context, preds []PredicateOf[CategoryToPost], assignments []FieldAssignment) (int64, error) { - if len(assignments) == 0 { +func (d *CategoryToPostDelegate) execUpdateStmt(ctx context.Context, preds []PredicateOf[CategoryToPost], cols []string, vals []any) (int64, error) { + if len(cols) == 0 { return 0, nil } @@ -1462,7 +1603,7 @@ func (d *CategoryToPostDelegate) execUpdateStmt(ctx context.Context, preds []Pre } } - query, setVals := d.buildUpdateSQL(preds, assignments, nil) + query, setVals := d.buildUpdateSQL(preds, cols, vals, nil) result, err := d.client.exec(ctx, query, setVals...) if err != nil { return 0, err @@ -1470,16 +1611,15 @@ func (d *CategoryToPostDelegate) execUpdateStmt(ctx context.Context, preds []Pre return result.RowsAffected() } -func (d *CategoryToPostDelegate) runUpdateFallback(ctx context.Context, where UniquePredicate[CategoryToPost], additional []PredicateOf[CategoryToPost], assignments []FieldAssignment, selects *CategoryToPostSelect, omits *CategoryToPostOmit) (*CategoryToPost, error) { - allPreds := append([]PredicateOf[CategoryToPost]{where}, additional...) - affected, err := d.execUpdateStmt(ctx, allPreds, assignments) +func (d *CategoryToPostDelegate) runUpdateFallback(ctx context.Context, preds []PredicateOf[CategoryToPost], cols []string, vals []any, selects *CategoryToPostSelect, omits *CategoryToPostOmit) (*CategoryToPost, error) { + affected, err := d.execUpdateStmt(ctx, preds, cols, vals) if err != nil { return nil, err } if affected == 0 { return nil, sql.ErrNoRows } - return d.runFindUnique(ctx, allPreds, selects, omits) + return d.runFindUnique(ctx, preds, selects, omits) } // ----------------------------------------------------------------------------- @@ -1487,17 +1627,30 @@ func (d *CategoryToPostDelegate) runUpdateFallback(ctx context.Context, where Un // ----------------------------------------------------------------------------- func (d *CategoryToPostDelegate) executeUpdateMany(ctx context.Context, preds []PredicateOf[CategoryToPost], assignments []FieldAssignment) (int64, error) { + input, err := assignmentsToCategoryToPostUpdate(assignments) + if err != nil { + return 0, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.execUpdateStmt(ctx, preds, assignments) + return d.execUpdateStmt(ctx, preds, cols, vals) + } + + args := &CategoryToPostUpdateManyArgs{ + Where: preds, + Data: &input, } - curr := func(c context.Context, p []PredicateOf[CategoryToPost], a []FieldAssignment) (int64, error) { - return d.execUpdateStmt(c, p, a) + curr := func(c context.Context, a *CategoryToPostUpdateManyArgs) (int64, error) { + extCols, extVals := a.Data.ToColsVals() + return d.execUpdateStmt(c, a.Where, extCols, extVals) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.UpdateMany != nil { - return ext.UpdateMany(ctx, preds, assignments, curr) + return ext.UpdateMany(ctx, args, curr) } } @@ -1505,13 +1658,13 @@ func (d *CategoryToPostDelegate) executeUpdateMany(ctx context.Context, preds [] ext := d.extensions[i] if ext.UpdateMany != nil { next, hook := curr, ext.UpdateMany - curr = func(c context.Context, p []PredicateOf[CategoryToPost], a []FieldAssignment) (int64, error) { - return hook(c, p, a, next) + curr = func(c context.Context, a *CategoryToPostUpdateManyArgs) (int64, error) { + return hook(c, a, next) } } } - return curr(ctx, preds, assignments) + return curr(ctx, args) } // ----------------------------------------------------------------------------- @@ -1519,21 +1672,35 @@ func (d *CategoryToPostDelegate) executeUpdateMany(ctx context.Context, preds [] // ----------------------------------------------------------------------------- func (d *CategoryToPostDelegate) executeUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[CategoryToPost], assignments []FieldAssignment, selects *CategoryToPostSelect, omits *CategoryToPostOmit) ([]*CategoryToPost, error) { + input, err := assignmentsToCategoryToPostUpdate(assignments) + if err != nil { + return nil, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.runUpdateManyAndReturn(ctx, preds, assignments, selects, omits) + return d.runUpdateManyAndReturn(ctx, preds, cols, vals, selects, omits) } if selects == nil || !selects.hasAnySelected() { selects = fullCategoryToPostSelect() } - curr := func(c context.Context, p []PredicateOf[CategoryToPost], a []FieldAssignment, s *CategoryToPostSelect, o *CategoryToPostOmit) ([]*CategoryToPost, error) { - return d.runUpdateManyAndReturn(c, p, a, s, o) + args := &CategoryToPostUpdateManyAndReturnArgs{ + Where: preds, + Data: &input, + Select: selects, + } + + curr := func(c context.Context, a *CategoryToPostUpdateManyAndReturnArgs) ([]*CategoryToPost, error) { + extCols, extVals := a.Data.ToColsVals() + return d.runUpdateManyAndReturn(c, a.Where, extCols, extVals, a.Select, omits) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.UpdateManyAndReturn != nil { - return ext.UpdateManyAndReturn(ctx, preds, assignments, selects, omits, curr) + return ext.UpdateManyAndReturn(ctx, args, curr) } } @@ -1541,17 +1708,17 @@ func (d *CategoryToPostDelegate) executeUpdateManyAndReturn(ctx context.Context, ext := d.extensions[i] if ext.UpdateManyAndReturn != nil { next, hook := curr, ext.UpdateManyAndReturn - curr = func(c context.Context, p []PredicateOf[CategoryToPost], a []FieldAssignment, s *CategoryToPostSelect, o *CategoryToPostOmit) ([]*CategoryToPost, error) { - return hook(c, p, a, s, o, next) + curr = func(c context.Context, a *CategoryToPostUpdateManyAndReturnArgs) ([]*CategoryToPost, error) { + return hook(c, a, next) } } } - return curr(ctx, preds, assignments, selects, omits) + return curr(ctx, args) } -func (d *CategoryToPostDelegate) runUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[CategoryToPost], assignments []FieldAssignment, selects *CategoryToPostSelect, omits *CategoryToPostOmit) ([]*CategoryToPost, error) { - if len(assignments) == 0 { +func (d *CategoryToPostDelegate) runUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[CategoryToPost], cols []string, vals []any, selects *CategoryToPostSelect, omits *CategoryToPostOmit) ([]*CategoryToPost, error) { + if len(cols) == 0 { return d.runFindMany(ctx, QueryParams[CategoryToPost]{Where: preds}, selects, omits) } @@ -1571,9 +1738,9 @@ func (d *CategoryToPostDelegate) runUpdateManyAndReturn(ctx context.Context, pre err := d.client.transaction(ctx, func(txQ *Queries) error { var err error if d.client.dialect.SupportsUpdateReturning { - res, err = txQ.CategoryToPost.runUpdateManyAndReturn(ctx, preds, assignments, selects, omits) + res, err = txQ.CategoryToPost.runUpdateManyAndReturn(ctx, preds, cols, vals, selects, omits) } else { - res, err = txQ.CategoryToPost.runUpdateManyAndReturnFallback(ctx, preds, assignments, selects, omits) + res, err = txQ.CategoryToPost.runUpdateManyAndReturnFallback(ctx, preds, cols, vals, selects, omits) } return err }) @@ -1581,39 +1748,29 @@ func (d *CategoryToPostDelegate) runUpdateManyAndReturn(ctx context.Context, pre } returningCols := selectCategoryToPostCols(selects, omits, categoryToPostPKCols...) - query, setVals := d.buildUpdateSQL(preds, assignments, returningCols) + query, setVals := d.buildUpdateSQL(preds, cols, vals, returningCols) rows, err := d.client.query(ctx, query, setVals...) if err != nil { return nil, err } - results := make([]*CategoryToPost, 0) - for rows.Next() { - var res CategoryToPost - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - rows.Close() - return nil, err - } - results = append(results, &res) - } - rowsErr := rows.Err() - rows.Close() - if rowsErr != nil { - return nil, rowsErr + scanned, err := scanCategoryToPostRows(rows, returningCols) + if err != nil { + return nil, err } if selects != nil && selects.hasAnyRelation() { - if err := d.loadRelations(ctx, results, selects); err != nil { + if err := d.loadRelations(ctx, scanned, selects); err != nil { return nil, err } } - return results, nil + return scanned, nil } -func (d *CategoryToPostDelegate) runUpdateManyAndReturnFallback(ctx context.Context, preds []PredicateOf[CategoryToPost], assignments []FieldAssignment, selects *CategoryToPostSelect, omits *CategoryToPostOmit) ([]*CategoryToPost, error) { - affected, err := d.execUpdateStmt(ctx, preds, assignments) +func (d *CategoryToPostDelegate) runUpdateManyAndReturnFallback(ctx context.Context, preds []PredicateOf[CategoryToPost], cols []string, vals []any, selects *CategoryToPostSelect, omits *CategoryToPostOmit) ([]*CategoryToPost, error) { + affected, err := d.execUpdateStmt(ctx, preds, cols, vals) if err != nil { return nil, err } diff --git a/integration/valk/client.go b/integration/valk/client.go index e6a6392..a76a243 100644 --- a/integration/valk/client.go +++ b/integration/valk/client.go @@ -7,9 +7,6 @@ import ( "embed" "encoding/json" "fmt" - "github.com/google/uuid" - "github.com/lib/pq/hstore" - "github.com/pressly/goose/v3" "math" "net" "regexp" @@ -19,6 +16,10 @@ import ( "sync" "time" "unicode/utf8" + + "github.com/google/uuid" + "github.com/lib/pq/hstore" + "github.com/pressly/goose/v3" ) var _ = time.Time{} @@ -1536,6 +1537,17 @@ func (f StringUniqueField[M]) In(vals []string) Predicate[M] { } } +func (f StringUniqueField[M]) NotIn(vals []string) Predicate[M] { + + return Predicate[M]{ + Data: PredicateData{ + Column: f.Column, + Operator: "NOT IN", + Value: vals, + }, + } +} + func (f StringUniqueField[M]) Like(val string) Predicate[M] { return Predicate[M]{ Data: PredicateData{ @@ -1823,39 +1835,25 @@ func (db *DB) Transaction(ctx context.Context, fn func(tx *Tx) error) error { type CreateBuilder[M any, S any, O any] struct { assignments []FieldAssignment + selects *S + omits *O execFunc func(ctx context.Context, assignments []FieldAssignment, s *S, o *O, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (*M, error) conflictAction *ConflictAction conflictTarget UniqueConstraintTarget } -func (b *CreateBuilder[M, S, O]) Select(s S) *CreateSelectBuilder[M, S, O] { - return &CreateSelectBuilder[M, S, O]{builder: b, selects: s} +func (b *CreateBuilder[M, S, O]) Select(s S) *CreateBuilder[M, S, O] { + b.selects = &s + return b } -func (b *CreateBuilder[M, S, O]) Omit(o O) *CreateOmitBuilder[M, S, O] { - return &CreateOmitBuilder[M, S, O]{builder: b, omits: o} +func (b *CreateBuilder[M, S, O]) Omit(o O) *CreateBuilder[M, S, O] { + b.omits = &o + return b } func (b *CreateBuilder[M, S, O]) Exec(ctx context.Context) (*M, error) { - return b.execFunc(ctx, b.assignments, nil, nil, b.conflictTarget, b.conflictAction) -} - -type CreateSelectBuilder[M any, S any, O any] struct { - builder *CreateBuilder[M, S, O] - selects S -} - -func (b *CreateSelectBuilder[M, S, O]) Exec(ctx context.Context) (*M, error) { - return b.builder.execFunc(ctx, b.builder.assignments, &b.selects, nil, b.builder.conflictTarget, b.builder.conflictAction) -} - -type CreateOmitBuilder[M any, S any, O any] struct { - builder *CreateBuilder[M, S, O] - omits O -} - -func (b *CreateOmitBuilder[M, S, O]) Exec(ctx context.Context) (*M, error) { - return b.builder.execFunc(ctx, b.builder.assignments, nil, &b.omits, b.builder.conflictTarget, b.builder.conflictAction) + return b.execFunc(ctx, b.assignments, b.selects, b.omits, b.conflictTarget, b.conflictAction) } type CreateManyBuilder[M any] struct { @@ -1876,6 +1874,8 @@ func (b *CreateManyBuilder[M]) Exec(ctx context.Context) (int64, error) { type CreateManyAndReturnBuilder[M any, S any, O any] struct { records []RecordInput + selects *S + omits *O execFunc func(ctx context.Context, records []RecordInput, s *S, o *O, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) ([]*M, error) conflictAction *ConflictAction conflictTarget UniqueConstraintTarget @@ -1886,214 +1886,18 @@ func (b *CreateManyAndReturnBuilder[M, S, O]) SkipDuplicates() *CreateManyAndRet return b } -func (b *CreateManyAndReturnBuilder[M, S, O]) Select(s S) *CreateManyAndReturnSelectBuilder[M, S, O] { - return &CreateManyAndReturnSelectBuilder[M, S, O]{builder: b, selects: s} +func (b *CreateManyAndReturnBuilder[M, S, O]) Select(s S) *CreateManyAndReturnBuilder[M, S, O] { + b.selects = &s + return b } -func (b *CreateManyAndReturnBuilder[M, S, O]) Omit(o O) *CreateManyAndReturnOmitBuilder[M, S, O] { - return &CreateManyAndReturnOmitBuilder[M, S, O]{builder: b, omits: o} +func (b *CreateManyAndReturnBuilder[M, S, O]) Omit(o O) *CreateManyAndReturnBuilder[M, S, O] { + b.omits = &o + return b } func (b *CreateManyAndReturnBuilder[M, S, O]) Exec(ctx context.Context) ([]*M, error) { - return b.execFunc(ctx, b.records, nil, nil, b.conflictTarget, b.conflictAction) -} - -type CreateManyAndReturnSelectBuilder[M any, S any, O any] struct { - builder *CreateManyAndReturnBuilder[M, S, O] - selects S -} - -func (b *CreateManyAndReturnSelectBuilder[M, S, O]) Exec(ctx context.Context) ([]*M, error) { - return b.builder.execFunc(ctx, b.builder.records, &b.selects, nil, b.builder.conflictTarget, b.builder.conflictAction) -} - -type CreateManyAndReturnOmitBuilder[M any, S any, O any] struct { - builder *CreateManyAndReturnBuilder[M, S, O] - omits O -} - -func (b *CreateManyAndReturnOmitBuilder[M, S, O]) Exec(ctx context.Context) ([]*M, error) { - return b.builder.execFunc(ctx, b.builder.records, nil, &b.omits, b.builder.conflictTarget, b.builder.conflictAction) -} - -func loadRelation[P any, C any]( - ctx context.Context, - q *Queries, - parents []*P, - parentKey func(*P) (string, bool), - table string, - fkCol string, - returningCols []string, - scan func(*sql.Rows, *C) error, - childKey func(*C) (string, bool), - assign func(*P, []*C), - params QueryParams[C], -) ([]*C, error) { - var parentKeys []any - for _, p := range parents { - if p == nil { - continue - } - if key, ok := parentKey(p); ok { - parentKeys = append(parentKeys, key) - } - } - if len(parentKeys) == 0 { - return nil, nil - } - - // Prepend parent ID checks to filters using Predicate[C] - allPreds := append([]PredicateOf[C]{ - Predicate[C]{ - Data: PredicateData{ - Column: fkCol, - Operator: "IN", - Value: parentKeys, - IsLogical: false, - }, - }, - }, params.Where...) - - whereClause, vals, nextIdx := CompilePredicates(q.dialect, allPreds) - isCursorQuery := (params.Cursor.Data.Column != "" || len(params.Cursor.Data.Children) > 0) - if isCursorQuery { - cClause, cVals, err := compileCursorClause(q.dialect, params.Cursor, params.OrderBy, []string{"id"}, nil, table, nextIdx, params.Take) - if err != nil { - return nil, err - } - if cClause != "" { - if whereClause == "" { - whereClause = cClause - } else { - whereClause = "(" + whereClause + ") AND " + cClause - } - vals = append(vals, cVals...) - } - } - if whereClause != "" { - whereClause = " WHERE " + whereClause - } - - query := compileRelationSQL(q.dialect, table, fkCol, returningCols, whereClause, params) - - rows, err := q.query(ctx, query, vals...) - if err != nil { - return nil, err - } - defer rows.Close() - - childMap := make(map[string][]*C, len(parents)) - allChildren := make([]*C, 0, len(parents)) - - for rows.Next() { - var child C - if err := scan(rows, &child); err != nil { - return nil, err - } - if key, ok := childKey(&child); ok { - childMap[key] = append(childMap[key], &child) - } - allChildren = append(allChildren, &child) - } - if err := rows.Err(); err != nil { - return nil, err - } - - for _, p := range parents { - if p == nil { - continue - } - if key, ok := parentKey(p); ok { - assign(p, childMap[key]) - } - } - - return allChildren, nil -} - -func compileRelationSQL[M any](dialect Dialect, table, fkCol string, cols []string, where string, params QueryParams[M]) string { - isCursorQuery := (params.Cursor.Data.Column != "" || len(params.Cursor.Data.Children) > 0) - if params.Take != nil || params.Skip != nil || isCursorQuery { - return compilePartitionedRelationSQL(dialect, table, fkCol, cols, where, params) - } - return compileSimpleRelationSQL(dialect, table, cols, where, params) -} - -func compilePartitionedRelationSQL[M any](dialect Dialect, table, fkCol string, cols []string, where string, params QueryParams[M]) string { - var innerSb strings.Builder - innerSb.WriteString("SELECT ") - for i, col := range cols { - if i > 0 { - innerSb.WriteString(", ") - } - innerSb.WriteString(dialect.Quote(col)) - } - innerSb.WriteString(", ROW_NUMBER() OVER (PARTITION BY ") - innerSb.WriteString(dialect.Quote(fkCol)) - innerSb.WriteString(" ORDER BY ") - if len(params.OrderBy) > 0 { - for i, ord := range params.OrderBy { - if i > 0 { - innerSb.WriteString(", ") - } - innerSb.WriteString(dialect.Quote(ord.Field)) - innerSb.WriteString(" ") - innerSb.WriteString(string(ord.Direction)) - } - } else { - innerSb.WriteString(dialect.Quote("id")) - innerSb.WriteString(" ASC") - } - innerSb.WriteString(") as row_num FROM ") - innerSb.WriteString(dialect.Quote(table)) - innerSb.WriteString(where) - - var outerSb strings.Builder - outerSb.WriteString("SELECT ") - for i, col := range cols { - if i > 0 { - outerSb.WriteString(", ") - } - outerSb.WriteString(dialect.Quote(col)) - } - outerSb.WriteString(" FROM (") - outerSb.WriteString(innerSb.String()) - outerSb.WriteString(") t WHERE ") - - if params.Take != nil && params.Skip != nil { - outerSb.WriteString(fmt.Sprintf("row_num > %d AND row_num <= %d", *params.Skip, *params.Skip+*params.Take)) - } else if params.Take != nil { - outerSb.WriteString(fmt.Sprintf("row_num <= %d", *params.Take)) - } else if params.Skip != nil { - outerSb.WriteString(fmt.Sprintf("row_num > %d", *params.Skip)) - } - return outerSb.String() -} - -func compileSimpleRelationSQL[M any](dialect Dialect, table string, cols []string, where string, params QueryParams[M]) string { - var sb strings.Builder - sb.WriteString("SELECT ") - for i, col := range cols { - if i > 0 { - sb.WriteString(", ") - } - sb.WriteString(dialect.Quote(col)) - } - sb.WriteString(" FROM ") - sb.WriteString(dialect.Quote(table)) - sb.WriteString(where) - if len(params.OrderBy) > 0 { - sb.WriteString(" ORDER BY ") - for i, ord := range params.OrderBy { - if i > 0 { - sb.WriteString(", ") - } - sb.WriteString(dialect.Quote(ord.Field)) - sb.WriteString(" ") - sb.WriteString(string(ord.Direction)) - } - } - return sb.String() + return b.execFunc(ctx, b.records, b.selects, b.omits, b.conflictTarget, b.conflictAction) } type UpdateBuilder[M any, S any, O any] struct { @@ -2660,3 +2464,183 @@ func buildSingleInsertSQL( return sb.String(), clauseArgs } + +func loadRelation[P any, C any]( + ctx context.Context, + q *Queries, + parents []*P, + parentKey func(*P) (string, bool), + table string, + fkCol string, + returningCols []string, + scan func(*sql.Rows, *C) error, + childKey func(*C) (string, bool), + assign func(*P, []*C), + params QueryParams[C], +) ([]*C, error) { + var parentKeys []any + for _, p := range parents { + if p == nil { + continue + } + if key, ok := parentKey(p); ok { + parentKeys = append(parentKeys, key) + } + } + if len(parentKeys) == 0 { + return nil, nil + } + + // Prepend parent ID checks to filters using Predicate[C] + allPreds := append([]PredicateOf[C]{ + Predicate[C]{ + Data: PredicateData{ + Column: fkCol, + Operator: "IN", + Value: parentKeys, + IsLogical: false, + }, + }, + }, params.Where...) + + whereClause, vals, nextIdx := CompilePredicates(q.dialect, allPreds) + isCursorQuery := (params.Cursor.Data.Column != "" || len(params.Cursor.Data.Children) > 0) + if isCursorQuery { + cClause, cVals, err := compileCursorClause(q.dialect, params.Cursor, params.OrderBy, []string{"id"}, nil, table, nextIdx, params.Take) + if err != nil { + return nil, err + } + if cClause != "" { + if whereClause == "" { + whereClause = cClause + } else { + whereClause = "(" + whereClause + ") AND " + cClause + } + vals = append(vals, cVals...) + } + } + if whereClause != "" { + whereClause = " WHERE " + whereClause + } + + query := compileRelationSQL(q.dialect, table, fkCol, returningCols, whereClause, params) + + rows, err := q.query(ctx, query, vals...) + if err != nil { + return nil, err + } + defer rows.Close() + + childMap := make(map[string][]*C, len(parents)) + allChildren := make([]*C, 0, len(parents)) + + for rows.Next() { + var child C + if err := scan(rows, &child); err != nil { + return nil, err + } + if key, ok := childKey(&child); ok { + childMap[key] = append(childMap[key], &child) + } + allChildren = append(allChildren, &child) + } + if err := rows.Err(); err != nil { + return nil, err + } + + for _, p := range parents { + if p == nil { + continue + } + if key, ok := parentKey(p); ok { + assign(p, childMap[key]) + } + } + + return allChildren, nil +} + +func compileRelationSQL[M any](dialect Dialect, table, fkCol string, cols []string, where string, params QueryParams[M]) string { + isCursorQuery := (params.Cursor.Data.Column != "" || len(params.Cursor.Data.Children) > 0) + if params.Take != nil || params.Skip != nil || isCursorQuery { + return compilePartitionedRelationSQL(dialect, table, fkCol, cols, where, params) + } + return compileSimpleRelationSQL(dialect, table, cols, where, params) +} + +func compilePartitionedRelationSQL[M any](dialect Dialect, table, fkCol string, cols []string, where string, params QueryParams[M]) string { + var innerSb strings.Builder + innerSb.WriteString("SELECT ") + for i, col := range cols { + if i > 0 { + innerSb.WriteString(", ") + } + innerSb.WriteString(dialect.Quote(col)) + } + innerSb.WriteString(", ROW_NUMBER() OVER (PARTITION BY ") + innerSb.WriteString(dialect.Quote(fkCol)) + innerSb.WriteString(" ORDER BY ") + if len(params.OrderBy) > 0 { + for i, ord := range params.OrderBy { + if i > 0 { + innerSb.WriteString(", ") + } + innerSb.WriteString(dialect.Quote(ord.Field)) + innerSb.WriteString(" ") + innerSb.WriteString(string(ord.Direction)) + } + } else { + innerSb.WriteString(dialect.Quote("id")) + innerSb.WriteString(" ASC") + } + innerSb.WriteString(") as row_num FROM ") + innerSb.WriteString(dialect.Quote(table)) + innerSb.WriteString(where) + + var outerSb strings.Builder + outerSb.WriteString("SELECT ") + for i, col := range cols { + if i > 0 { + outerSb.WriteString(", ") + } + outerSb.WriteString(dialect.Quote(col)) + } + outerSb.WriteString(" FROM (") + outerSb.WriteString(innerSb.String()) + outerSb.WriteString(") t WHERE ") + + if params.Take != nil && params.Skip != nil { + outerSb.WriteString(fmt.Sprintf("row_num > %d AND row_num <= %d", *params.Skip, *params.Skip+*params.Take)) + } else if params.Take != nil { + outerSb.WriteString(fmt.Sprintf("row_num <= %d", *params.Take)) + } else if params.Skip != nil { + outerSb.WriteString(fmt.Sprintf("row_num > %d", *params.Skip)) + } + return outerSb.String() +} + +func compileSimpleRelationSQL[M any](dialect Dialect, table string, cols []string, where string, params QueryParams[M]) string { + var sb strings.Builder + sb.WriteString("SELECT ") + for i, col := range cols { + if i > 0 { + sb.WriteString(", ") + } + sb.WriteString(dialect.Quote(col)) + } + sb.WriteString(" FROM ") + sb.WriteString(dialect.Quote(table)) + sb.WriteString(where) + if len(params.OrderBy) > 0 { + sb.WriteString(" ORDER BY ") + for i, ord := range params.OrderBy { + if i > 0 { + sb.WriteString(", ") + } + sb.WriteString(dialect.Quote(ord.Field)) + sb.WriteString(" ") + sb.WriteString(string(ord.Direction)) + } + } + return sb.String() +} diff --git a/integration/valk/comment.go b/integration/valk/comment.go index 1377708..52dc7bc 100644 --- a/integration/valk/comment.go +++ b/integration/valk/comment.go @@ -62,6 +62,142 @@ func (s *CommentCreate) colMask() uint64 { return mask } +// CommentUpdate contains model input fields for Comment update operations. +type CommentUpdate struct { + Id *string `json:"id"` + Textify *int32 `json:"textify"` + Dummy3 *string `json:"dummy3"` + Dummy1 *int32 `json:"dummy1"` + Dummy2 *string `json:"dummy2"` + PostId *string `json:"postId"` + AuthorId *string `json:"authorId"` + Meta *json.RawMessage `json:"meta"` +} + +func (u *CommentUpdate) ToColsVals() ([]string, []any) { + var cols []string + var vals []any + if u.Id != nil { + cols = append(cols, "id") + vals = append(vals, u.Id) + } + if u.Textify != nil { + cols = append(cols, "textify") + vals = append(vals, u.Textify) + } + if u.Dummy3 != nil { + cols = append(cols, "dummy3") + vals = append(vals, u.Dummy3) + } + if u.Dummy1 != nil { + cols = append(cols, "dummy1") + vals = append(vals, u.Dummy1) + } + if u.Dummy2 != nil { + cols = append(cols, "dummy2") + vals = append(vals, u.Dummy2) + } + if u.PostId != nil { + cols = append(cols, "postId") + vals = append(vals, u.PostId) + } + if u.AuthorId != nil { + cols = append(cols, "authorId") + vals = append(vals, u.AuthorId) + } + if u.Meta != nil { + cols = append(cols, "meta") + vals = append(vals, u.Meta) + } + return cols, vals +} + +func assignmentsToCommentUpdate(assignments []FieldAssignment) (CommentUpdate, error) { + var input CommentUpdate + var errs ValidationError + + for _, a := range assignments { + switch a.Col { + case "id": + if v, ok := a.Val.(string); ok { + input.Id = &v + errs.ValidateString("id", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.Id = v + } else { + errs.Add("id", a.Val, "type", "field id must be of type string") + } + case "textify": + if v, ok := a.Val.(int32); ok { + input.Textify = &v + } else if v, ok := a.Val.(*int32); ok { + input.Textify = v + } else { + errs.Add("textify", a.Val, "type", "field textify must be of type int32") + } + case "dummy3": + if v, ok := a.Val.(string); ok { + input.Dummy3 = &v + errs.ValidateString("dummy3", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.Dummy3 = v + } else { + errs.Add("dummy3", a.Val, "type", "field dummy3 must be of type string") + } + case "dummy1": + if v, ok := a.Val.(int32); ok { + input.Dummy1 = &v + } else if v, ok := a.Val.(*int32); ok { + input.Dummy1 = v + } else { + errs.Add("dummy1", a.Val, "type", "field dummy1 must be of type int32") + } + case "dummy2": + if v, ok := a.Val.(string); ok { + input.Dummy2 = &v + errs.ValidateString("dummy2", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.Dummy2 = v + } else { + errs.Add("dummy2", a.Val, "type", "field dummy2 must be of type string") + } + case "postId": + if v, ok := a.Val.(string); ok { + input.PostId = &v + errs.ValidateString("postId", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.PostId = v + } else { + errs.Add("postId", a.Val, "type", "field postId must be of type string") + } + case "authorId": + if v, ok := a.Val.(string); ok { + input.AuthorId = &v + errs.ValidateString("authorId", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.AuthorId = v + } else { + errs.Add("authorId", a.Val, "type", "field authorId must be of type string") + } + case "meta": + if v, ok := a.Val.(json.RawMessage); ok { + input.Meta = &v + } else if v, ok := a.Val.(*json.RawMessage); ok { + input.Meta = v + } else if v, ok := a.Val.(*json.RawMessage); ok { + input.Meta = v + } else { + errs.Add("meta", a.Val, "type", "field meta must be of type *json.RawMessage") + } + } + } + + if errs.HasErrors() { + return input, errs + } + return input, nil +} + // CommentSelect specifies which scalar and relation fields to select for Comment. // // Selectable fields: @@ -444,6 +580,51 @@ func (a *CommentDeleteManyArgs) SetWhere(preds ...PredicateOf[Comment]) *Comment return a } +// CommentUpdateArgs is the input argument passed to Comment Update extension hooks. +type CommentUpdateArgs struct { + // Where contains all query filter predicates (merged primary unique constraint and additional predicates). + Where []PredicateOf[Comment] + // Data contains the model fields to update. + Data *CommentUpdate + // Select specifies which scalar and relation fields to select and return upon update. + Select *CommentSelect +} + +func (a *CommentUpdateArgs) SetWhere(unique UniquePredicate[Comment], additional ...PredicateOf[Comment]) *CommentUpdateArgs { + a.Where = make([]PredicateOf[Comment], 0, 1+len(additional)) + a.Where = append(a.Where, unique) + a.Where = append(a.Where, additional...) + return a +} + +// CommentUpdateManyArgs is the input argument passed to Comment UpdateMany extension hooks. +type CommentUpdateManyArgs struct { + // Where contains all query filter predicates. + Where []PredicateOf[Comment] + // Data contains the model fields to update. + Data *CommentUpdate +} + +func (a *CommentUpdateManyArgs) SetWhere(preds ...PredicateOf[Comment]) *CommentUpdateManyArgs { + a.Where = preds + return a +} + +// CommentUpdateManyAndReturnArgs is the input argument passed to Comment UpdateManyAndReturn extension hooks. +type CommentUpdateManyAndReturnArgs struct { + // Where contains all query filter predicates. + Where []PredicateOf[Comment] + // Data contains the model fields to update. + Data *CommentUpdate + // Select specifies which scalar and relation fields to select and return upon update. + Select *CommentSelect +} + +func (a *CommentUpdateManyAndReturnArgs) SetWhere(preds ...PredicateOf[Comment]) *CommentUpdateManyAndReturnArgs { + a.Where = preds + return a +} + type CommentCreateQuery = func(ctx context.Context, args *CommentCreateArgs) (*Comment, error) type CommentCreateManyQuery = func(ctx context.Context, args *CommentCreateManyArgs) (int64, error) type CommentCreateManyAndReturnQuery = func(ctx context.Context, args *CommentCreateManyAndReturnArgs) ([]*Comment, error) @@ -453,9 +634,9 @@ type CommentFindManyQuery = func(ctx context.Context, args *CommentFindManyArgs) type CommentDeleteQuery = func(ctx context.Context, args *CommentDeleteArgs) (*Comment, error) type CommentDeleteManyQuery = func(ctx context.Context, args *CommentDeleteManyArgs) (int64, error) type CommentCountQuery = func(ctx context.Context, args *CommentCountArgs) (int64, error) -type CommentUpdateQuery = func(ctx context.Context, where UniquePredicate[Comment], additional []PredicateOf[Comment], assignments []FieldAssignment, selects *CommentSelect, omits *CommentOmit) (*Comment, error) -type CommentUpdateManyQuery = func(ctx context.Context, preds []PredicateOf[Comment], assignments []FieldAssignment) (int64, error) -type CommentUpdateManyAndReturnQuery = func(ctx context.Context, preds []PredicateOf[Comment], assignments []FieldAssignment, selects *CommentSelect, omits *CommentOmit) ([]*Comment, error) +type CommentUpdateQuery = func(ctx context.Context, args *CommentUpdateArgs) (*Comment, error) +type CommentUpdateManyQuery = func(ctx context.Context, args *CommentUpdateManyArgs) (int64, error) +type CommentUpdateManyAndReturnQuery = func(ctx context.Context, args *CommentUpdateManyAndReturnArgs) ([]*Comment, error) type CommentExtension struct { Create func(ctx context.Context, args *CommentCreateArgs, next CommentCreateQuery) (*Comment, error) @@ -467,9 +648,9 @@ type CommentExtension struct { Delete func(ctx context.Context, args *CommentDeleteArgs, next CommentDeleteQuery) (*Comment, error) DeleteMany func(ctx context.Context, args *CommentDeleteManyArgs, next CommentDeleteManyQuery) (int64, error) Count func(ctx context.Context, args *CommentCountArgs, next CommentCountQuery) (int64, error) - Update func(ctx context.Context, where UniquePredicate[Comment], additional []PredicateOf[Comment], assignments []FieldAssignment, selects *CommentSelect, omits *CommentOmit, next CommentUpdateQuery) (*Comment, error) - UpdateMany func(ctx context.Context, preds []PredicateOf[Comment], assignments []FieldAssignment, next CommentUpdateManyQuery) (int64, error) - UpdateManyAndReturn func(ctx context.Context, preds []PredicateOf[Comment], assignments []FieldAssignment, selects *CommentSelect, omits *CommentOmit, next CommentUpdateManyAndReturnQuery) ([]*Comment, error) + Update func(ctx context.Context, args *CommentUpdateArgs, next CommentUpdateQuery) (*Comment, error) + UpdateMany func(ctx context.Context, args *CommentUpdateManyArgs, next CommentUpdateManyQuery) (int64, error) + UpdateManyAndReturn func(ctx context.Context, args *CommentUpdateManyAndReturnArgs, next CommentUpdateManyAndReturnQuery) ([]*Comment, error) } type CommentDelegate struct { @@ -565,6 +746,16 @@ type CommentCreateBuilder struct { *CreateBuilder[Comment, CommentSelect, CommentOmit] } +func (b *CommentCreateBuilder) Select(s CommentSelect) *CommentCreateBuilder { + b.selects = &s + return b +} + +func (b *CommentCreateBuilder) Omit(o CommentOmit) *CommentCreateBuilder { + b.omits = &o + return b +} + func (b *CommentCreateBuilder) OnConflict(target UniqueConstraintTarget) *CommentConflictBuilder[CommentCreateBuilder] { return &CommentConflictBuilder[CommentCreateBuilder]{ builder: b, @@ -796,23 +987,11 @@ func (d *CommentDelegate) executeCreate(ctx context.Context, assignments []Field return nil, err } + cols, vals := input.ToColsVals() + returningCols := selectCommentCols(selects, omits) + if len(d.extensions) == 0 { - cols, vals := input.ToColsVals() - returningCols := selectCommentCols(selects, omits) - hasRelations := selects.hasAnyRelation() - if hasRelations { - var res *Comment - err = d.client.transaction(ctx, func(txQ *Queries) error { - var err error - res, err = txQ.Comment.runCreate(ctx, cols, vals, returningCols, commentPKCols, conflictTarget, conflictAction) - if err != nil { - return err - } - return txQ.Comment.loadRelations(ctx, []*Comment{res}, selects) - }) - return res, err - } - return d.runCreate(ctx, cols, vals, returningCols, commentPKCols, conflictTarget, conflictAction) + return d.runCreate(ctx, cols, vals, returningCols, selects, conflictTarget, conflictAction) } if selects == nil || !selects.hasAnySelected() { @@ -827,28 +1006,9 @@ func (d *CommentDelegate) executeCreate(ctx context.Context, assignments []Field } curr := func(c context.Context, a *CommentCreateArgs) (*Comment, error) { - cols, vals := a.Data.ToColsVals() - returningCols := selectCommentCols(a.Select, omits) - - hasRelations := a.Select.hasAnyRelation() - var res *Comment - var err error - if hasRelations { - err = d.client.transaction(c, func(txQ *Queries) error { - var err error - res, err = txQ.Comment.runCreate(c, cols, vals, returningCols, commentPKCols, a.ConflictTarget, a.ConflictAction) - if err != nil { - return err - } - return txQ.Comment.loadRelations(c, []*Comment{res}, a.Select) - }) - } else { - res, err = d.runCreate(c, cols, vals, returningCols, commentPKCols, a.ConflictTarget, a.ConflictAction) - } - if err != nil { - return nil, err - } - return res, nil + cCols, cVals := a.Data.ToColsVals() + cReturningCols := selectCommentCols(a.Select, omits) + return d.runCreate(c, cCols, cVals, cReturningCols, a.Select, a.ConflictTarget, a.ConflictAction) } if len(d.extensions) == 1 { @@ -889,6 +1049,16 @@ type CommentCreateManyAndReturnBuilder struct { *CreateManyAndReturnBuilder[Comment, CommentSelect, CommentOmit] } +func (b *CommentCreateManyAndReturnBuilder) Select(s CommentSelect) *CommentCreateManyAndReturnBuilder { + b.selects = &s + return b +} + +func (b *CommentCreateManyAndReturnBuilder) Omit(o CommentOmit) *CommentCreateManyAndReturnBuilder { + b.omits = &o + return b +} + func (b *CommentCreateManyAndReturnBuilder) OnConflict(target UniqueConstraintTarget) *CommentConflictBuilder[CommentCreateManyAndReturnBuilder] { return &CommentConflictBuilder[CommentCreateManyAndReturnBuilder]{ builder: b, @@ -900,43 +1070,51 @@ func (b *CommentCreateManyAndReturnBuilder) OnConflict(target UniqueConstraintTa } } -func (d *CommentDelegate) CreateMany(builders ...*CommentCreateBuilder) *CommentCreateManyBuilder { +func createBuildersToCommentRecordInputs(builders []*CommentCreateBuilder) []RecordInput { records := make([]RecordInput, len(builders)) for i, b := range builders { records[i] = RecordInput{Assignments: b.assignments} } + return records +} + +func (d *CommentDelegate) CreateMany(builders ...*CommentCreateBuilder) *CommentCreateManyBuilder { return &CommentCreateManyBuilder{ CreateManyBuilder: &CreateManyBuilder[Comment]{ - records: records, + records: createBuildersToCommentRecordInputs(builders), execFunc: d.executeCreateMany, }, } } func (d *CommentDelegate) CreateManyAndReturn(builders ...*CommentCreateBuilder) *CommentCreateManyAndReturnBuilder { - records := make([]RecordInput, len(builders)) - for i, b := range builders { - records[i] = RecordInput{Assignments: b.assignments} - } return &CommentCreateManyAndReturnBuilder{ CreateManyAndReturnBuilder: &CreateManyAndReturnBuilder[Comment, CommentSelect, CommentOmit]{ - records: records, + records: createBuildersToCommentRecordInputs(builders), execFunc: d.executeCreateManyAndReturn, }, } } -func (d *CommentDelegate) executeCreateMany(ctx context.Context, records []RecordInput, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { +func recordsToCommentCreateInputs(records []RecordInput) ([]*CommentCreate, error) { structs := make([]CommentCreate, len(records)) inputs := make([]*CommentCreate, len(records)) for i, rec := range records { var err error structs[i], err = assignmentsToCommentCreate(rec.Assignments) if err != nil { - return 0, fmt.Errorf("validation failed at index %d: %w", i, err) + return nil, fmt.Errorf("validation failed at index %d: %w", i, err) } inputs[i] = &structs[i] } + return inputs, nil +} + +func (d *CommentDelegate) executeCreateMany(ctx context.Context, records []RecordInput, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { + inputs, err := recordsToCommentCreateInputs(records) + if err != nil { + return 0, err + } if len(d.extensions) == 0 { return d.runCreateMany(ctx, inputs, conflictTarget, conflictAction) @@ -972,31 +1150,12 @@ func (d *CommentDelegate) executeCreateMany(ctx context.Context, records []Recor } func (d *CommentDelegate) executeCreateManyAndReturn(ctx context.Context, records []RecordInput, selects *CommentSelect, omits *CommentOmit, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) ([]*Comment, error) { - structs := make([]CommentCreate, len(records)) - inputs := make([]*CommentCreate, len(records)) - for i, rec := range records { - var err error - structs[i], err = assignmentsToCommentCreate(rec.Assignments) - if err != nil { - return nil, fmt.Errorf("validation failed at index %d: %w", i, err) - } - inputs[i] = &structs[i] + inputs, err := recordsToCommentCreateInputs(records) + if err != nil { + return nil, err } if len(d.extensions) == 0 { - hasRelations := selects != nil && selects.hasAnyRelation() - if hasRelations { - var res []*Comment - err := d.client.transaction(ctx, func(txQ *Queries) error { - var err error - res, err = txQ.Comment.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) - if err != nil { - return err - } - return txQ.Comment.loadRelations(ctx, res, selects) - }) - return res, err - } return d.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) } @@ -1012,19 +1171,6 @@ func (d *CommentDelegate) executeCreateManyAndReturn(ctx context.Context, record } curr := func(c context.Context, a *CommentCreateManyAndReturnArgs) ([]*Comment, error) { - hasRelations := a.Select != nil && a.Select.hasAnyRelation() - if hasRelations { - var res []*Comment - err := d.client.transaction(c, func(txQ *Queries) error { - var err error - res, err = txQ.Comment.runCreateManyAndReturn(c, a.Data, a.Select, omits, a.ConflictTarget, a.ConflictAction) - if err != nil { - return err - } - return txQ.Comment.loadRelations(c, res, a.Select) - }) - return res, err - } return d.runCreateManyAndReturn(c, a.Data, a.Select, omits, a.ConflictTarget, a.ConflictAction) } @@ -1052,36 +1198,67 @@ func (d *CommentDelegate) runCreate( cols []string, vals []any, returningCols []string, - pkCols []string, + selects *CommentSelect, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction, ) (*Comment, error) { - query, clauseArgs := buildSingleInsertSQL(d.client, "Comment", cols, returningCols, pkCols, conflictTarget, conflictAction, len(vals)) + hasRelations := selects != nil && selects.hasAnyRelation() + useTx := hasRelations && !d.client.inTx() + + if useTx { + var res *Comment + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + res, err = txQ.Comment.runCreate(ctx, cols, vals, returningCols, selects, conflictTarget, conflictAction) + if err != nil { + return err + } + return txQ.Comment.loadRelations(ctx, []*Comment{res}, selects) + }) + return res, err + } + + query, clauseArgs := buildSingleInsertSQL(d.client, "Comment", cols, returningCols, commentPKCols, conflictTarget, conflictAction, len(vals)) if len(clauseArgs) > 0 { vals = append(vals, clauseArgs...) } - var res Comment if d.client.dialect.SupportsInsertReturning { rows, err := d.client.query(ctx, query, vals...) if err != nil { return nil, err } - defer rows.Close() - if rows.Next() { - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + if !rows.Next() { + err := rows.Err() + rows.Close() + if err != nil { return nil, err } - return &res, nil + return nil, nil + } + + var res Comment + scanErr := rows.Scan(res.ScanFields(returningCols)...) + rows.Close() + if scanErr != nil { + return nil, scanErr } - return nil, rows.Err() + + return &res, nil } - return d.runCreateFallback(ctx, query, vals, cols, returningCols, pkCols) + return d.runCreateFallback(ctx, query, vals, cols, returningCols, commentPKCols) } -func (d *CommentDelegate) runCreateFallback(ctx context.Context, query string, vals []any, cols []string, returningCols []string, pkCols []string) (*Comment, error) { +func (d *CommentDelegate) runCreateFallback( + ctx context.Context, + query string, + vals []any, + cols []string, + returningCols []string, + pkCols []string, +) (*Comment, error) { result, err := d.client.exec(ctx, query, vals...) if err != nil { return nil, err @@ -1131,16 +1308,24 @@ func (d *CommentDelegate) runCreateFallback(ctx context.Context, query string, v if err != nil { return nil, err } - defer rows.Close() - var res Comment - if rows.Next() { - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + if !rows.Next() { + err := rows.Err() + rows.Close() + if err != nil { return nil, err } - return &res, nil + return nil, nil + } + + var res Comment + scanErr := rows.Scan(res.ScanFields(returningCols)...) + rows.Close() + if scanErr != nil { + return nil, scanErr } - return nil, rows.Err() + + return &res, nil } func (d *CommentDelegate) buildBulkInsertSQL(q *Queries, batch []*CommentCreate, paramStartIdx int) (cols []string, vals []any, queryStr string) { @@ -1220,6 +1405,41 @@ func (d *CommentDelegate) buildBulkInsertSQL(q *Queries, batch []*CommentCreate, return cols, vals, queryStr } +func applyCommentConflictClause(dialect Dialect, queryStr string, vals []any, cols []string, pkCols []string, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (string, []any) { + var conflictCols []string + if conflictTarget != nil { + conflictCols = conflictTarget.UniqueColumns() + } + var nonConflictCols []string + if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { + nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + } + clause, clauseArgs := dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) + queryStr += clause + if len(clauseArgs) > 0 { + vals = append(vals, clauseArgs...) + } + return queryStr, vals +} + +func scanCommentRows(rows *sql.Rows, returningCols []string) ([]*Comment, error) { + var records []*Comment + for rows.Next() { + var res Comment + if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + rows.Close() + return nil, err + } + records = append(records, &res) + } + if err := rows.Err(); err != nil { + rows.Close() + return nil, err + } + rows.Close() + return records, nil +} + func (d *CommentDelegate) runCreateMany(ctx context.Context, inputs []*CommentCreate, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { if len(inputs) == 0 { return 0, nil @@ -1230,18 +1450,7 @@ func (d *CommentDelegate) runCreateMany(ctx context.Context, inputs []*CommentCr var count int64 for _, batch := range batches { cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) - - var conflictCols []string - if conflictTarget != nil { - conflictCols = conflictTarget.UniqueColumns() - } - var nonConflictCols []string - if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, commentPKCols) - } - clause, clauseArgs := d.client.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) - queryStr += clause - vals = append(vals, clauseArgs...) + queryStr, vals = applyCommentConflictClause(d.client.dialect, queryStr, vals, cols, commentPKCols, conflictTarget, conflictAction) result, err := d.client.exec(ctx, queryStr, vals...) if err != nil { @@ -1269,27 +1478,37 @@ func (d *CommentDelegate) runCreateManyAndReturn( } batches := partitionCommentInputs(d.client.dialect, inputs) - returningCols := selectCommentCols(selects, omits) hasRelations := selects != nil && selects.hasAnyRelation() + useTx := (len(batches) > 1 || hasRelations || !d.client.dialect.SupportsInsertReturning) && !d.client.inTx() - recordsOut := make([]*Comment, 0, len(inputs)) + if useTx { + var res []*Comment + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + if txQ.dialect.SupportsInsertReturning { + res, err = txQ.Comment.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) + } else { + res, err = txQ.Comment.runCreateManyAndReturnFallback(ctx, inputs, selects, omits, conflictTarget, conflictAction) + } + if err != nil { + return err + } + if hasRelations { + return txQ.Comment.loadRelations(ctx, res, selects) + } + return nil + }) + return res, err + } - runBatch := func(txQ *Queries, batch []*CommentCreate) error { - cols, vals, queryStr := d.buildBulkInsertSQL(txQ, batch, 1) + returningCols := selectCommentCols(selects, omits, commentPKCols...) + recordsOut := make([]*Comment, 0, len(inputs)) - var conflictCols []string - if conflictTarget != nil { - conflictCols = conflictTarget.UniqueColumns() - } - var nonConflictCols []string - if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, commentPKCols) - } - clause, clauseArgs := txQ.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) - queryStr += clause - vals = append(vals, clauseArgs...) + for _, batch := range batches { + cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) + queryStr, vals = applyCommentConflictClause(d.client.dialect, queryStr, vals, cols, commentPKCols, conflictTarget, conflictAction) - if txQ.dialect.SupportsInsertReturning && len(returningCols) > 0 { + if len(returningCols) > 0 { var retSb strings.Builder retSb.Grow(12 + len(returningCols)*15) retSb.WriteString(" RETURNING ") @@ -1297,40 +1516,58 @@ func (d *CommentDelegate) runCreateManyAndReturn( if i > 0 { retSb.WriteString(", ") } - txQ.dialect.WriteQuotedIdent(&retSb, col) + d.client.dialect.WriteQuotedIdent(&retSb, col) } queryStr += retSb.String() - rows, err := txQ.query(ctx, queryStr, vals...) - if err != nil { - return err - } - defer rows.Close() + } - for rows.Next() { - var res Comment - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - return err - } - recordsOut = append(recordsOut, &res) - } - return rows.Err() + rows, err := d.client.query(ctx, queryStr, vals...) + if err != nil { + return nil, err } - // Fallback for dialects without RETURNING (MySQL) - result, err := txQ.exec(ctx, queryStr, vals...) + scanned, err := scanCommentRows(rows, returningCols) if err != nil { - return err + return nil, err + } + recordsOut = append(recordsOut, scanned...) + } + + if selects != nil && selects.hasAnyRelation() { + if err := d.loadRelations(ctx, recordsOut, selects); err != nil { + return nil, err + } + } + + return recordsOut, nil +} + +func (d *CommentDelegate) runCreateManyAndReturnFallback( + ctx context.Context, + inputs []*CommentCreate, + selects *CommentSelect, + omits *CommentOmit, + conflictTarget UniqueConstraintTarget, + conflictAction *ConflictAction, +) ([]*Comment, error) { + batches := partitionCommentInputs(d.client.dialect, inputs) + returningCols := selectCommentCols(selects, omits, commentPKCols...) + recordsOut := make([]*Comment, 0, len(inputs)) + + for _, batch := range batches { + cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) + queryStr, vals = applyCommentConflictClause(d.client.dialect, queryStr, vals, cols, commentPKCols, conflictTarget, conflictAction) + + result, err := d.client.exec(ctx, queryStr, vals...) + if err != nil { + return nil, err } - // We need to fetch the inserted records for this batch - // Note: MySQL bulk inserts only return the ID of the FIRST inserted row lastID, err := result.LastInsertId() if err != nil { - return err + return nil, err } - // Query back the rows by IDs (assuming autoincrement ID and single PK) - // If composite PK, it's more complex, but this is a standard fallback var selectSb strings.Builder selectSb.Grow(64 + len(returningCols)*15 + len("Comment") + len(batch)*15) selectSb.WriteString("SELECT ") @@ -1338,55 +1575,29 @@ func (d *CommentDelegate) runCreateManyAndReturn( if i > 0 { selectSb.WriteString(", ") } - txQ.dialect.WriteQuotedIdent(&selectSb, col) + d.client.dialect.WriteQuotedIdent(&selectSb, col) } selectSb.WriteString(" FROM ") - txQ.dialect.WriteQuotedIdent(&selectSb, "Comment") + d.client.dialect.WriteQuotedIdent(&selectSb, "Comment") selectSb.WriteString(" WHERE ") - txQ.dialect.WriteQuotedIdent(&selectSb, commentPKCols[0]) + d.client.dialect.WriteQuotedIdent(&selectSb, commentPKCols[0]) selectSb.WriteString(" >= ") - txQ.dialect.WritePlaceholder(&selectSb, 1) + d.client.dialect.WritePlaceholder(&selectSb, 1) selectSb.WriteString(" AND ") - txQ.dialect.WriteQuotedIdent(&selectSb, commentPKCols[0]) + d.client.dialect.WriteQuotedIdent(&selectSb, commentPKCols[0]) selectSb.WriteString(" < ") - txQ.dialect.WritePlaceholder(&selectSb, 2) + d.client.dialect.WritePlaceholder(&selectSb, 2) - rows, err := txQ.query(ctx, selectSb.String(), lastID, lastID+int64(len(batch))) + rows, err := d.client.query(ctx, selectSb.String(), lastID, lastID+int64(len(batch))) if err != nil { - return err - } - defer rows.Close() - - for rows.Next() { - var res Comment - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - return err - } - recordsOut = append(recordsOut, &res) + return nil, err } - return rows.Err() - } - // Always wrap in transaction if we have multiple batches OR if we need to load relations - 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 { - return err - } - } - if hasRelations { - return txQ.Comment.loadRelations(ctx, recordsOut, selects) - } - return nil - }) + scanned, err := scanCommentRows(rows, returningCols) if err != nil { return nil, err } - } else { - if err := runBatch(d.client, batches[0]); err != nil { - return nil, err - } + recordsOut = append(recordsOut, scanned...) } return recordsOut, nil @@ -1629,23 +1840,23 @@ func (d *CommentDelegate) UpdateManyAndReturn(preds ...PredicateOf[Comment]) *Co } } -func (d *CommentDelegate) buildUpdateSQL(preds []PredicateOf[Comment], assignments []FieldAssignment, returningCols []string) (string, []any) { - whereClause, predVals, _ := CompilePredicates(d.client.dialect, preds, len(assignments)+1) +func (d *CommentDelegate) buildUpdateSQL(preds []PredicateOf[Comment], cols []string, vals []any, returningCols []string) (string, []any) { + whereClause, predVals, _ := CompilePredicates(d.client.dialect, preds, len(cols)+1) var sb strings.Builder sb.WriteString("UPDATE ") d.client.dialect.WriteQuotedIdent(&sb, "Comment") sb.WriteString(" SET ") - setVals := make([]any, 0, len(assignments)+len(predVals)) - for i, a := range assignments { + setVals := make([]any, 0, len(cols)+len(predVals)) + for i, col := range cols { if i > 0 { sb.WriteString(", ") } - d.client.dialect.WriteQuotedIdent(&sb, a.Col) + d.client.dialect.WriteQuotedIdent(&sb, col) sb.WriteString(" = ") d.client.dialect.WritePlaceholder(&sb, i+1) - setVals = append(setVals, a.Val) + setVals = append(setVals, vals[i]) } if whereClause != "" { @@ -1672,21 +1883,39 @@ func (d *CommentDelegate) buildUpdateSQL(preds []PredicateOf[Comment], assignmen // ----------------------------------------------------------------------------- func (d *CommentDelegate) executeUpdate(ctx context.Context, where UniquePredicate[Comment], additional []PredicateOf[Comment], assignments []FieldAssignment, selects *CommentSelect, omits *CommentOmit) (*Comment, error) { + allWhere := make([]PredicateOf[Comment], 0, 1+len(additional)) + allWhere = append(allWhere, where) + allWhere = append(allWhere, additional...) + + input, err := assignmentsToCommentUpdate(assignments) + if err != nil { + return nil, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.runUpdate(ctx, where, additional, assignments, selects, omits) + return d.runUpdate(ctx, allWhere, cols, vals, selects, omits) } if selects == nil || !selects.hasAnySelected() { selects = fullCommentSelect() } - curr := func(c context.Context, w UniquePredicate[Comment], add []PredicateOf[Comment], a []FieldAssignment, s *CommentSelect, o *CommentOmit) (*Comment, error) { - return d.runUpdate(c, w, add, a, s, o) + args := &CommentUpdateArgs{ + Where: allWhere, + Data: &input, + Select: selects, + } + + curr := func(c context.Context, a *CommentUpdateArgs) (*Comment, error) { + extCols, extVals := a.Data.ToColsVals() + return d.runUpdate(c, a.Where, extCols, extVals, a.Select, omits) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.Update != nil { - return ext.Update(ctx, where, additional, assignments, selects, omits, curr) + return ext.Update(ctx, args, curr) } } @@ -1694,25 +1923,21 @@ func (d *CommentDelegate) executeUpdate(ctx context.Context, where UniquePredica ext := d.extensions[i] if ext.Update != nil { next, hook := curr, ext.Update - curr = func(c context.Context, w UniquePredicate[Comment], add []PredicateOf[Comment], a []FieldAssignment, s *CommentSelect, o *CommentOmit) (*Comment, error) { - return hook(c, w, add, a, s, o, next) + curr = func(c context.Context, a *CommentUpdateArgs) (*Comment, error) { + return hook(c, a, next) } } } - return curr(ctx, where, additional, assignments, selects, omits) + return curr(ctx, args) } -func (d *CommentDelegate) runUpdate(ctx context.Context, where UniquePredicate[Comment], additional []PredicateOf[Comment], assignments []FieldAssignment, selects *CommentSelect, omits *CommentOmit) (*Comment, error) { - allPreds := append([]PredicateOf[Comment]{where}, additional...) - if len(assignments) == 0 { - return d.runFindUnique(ctx, allPreds, selects, omits) +func (d *CommentDelegate) runUpdate(ctx context.Context, preds []PredicateOf[Comment], cols []string, vals []any, selects *CommentSelect, omits *CommentOmit) (*Comment, error) { + if len(cols) == 0 { + return d.runFindUnique(ctx, preds, selects, omits) } - if err := where.Validate(); err != nil { - return nil, err - } - for _, pr := range additional { + for _, pr := range preds { if pr != nil { if err := pr.Validate(); err != nil { return nil, err @@ -1728,9 +1953,9 @@ func (d *CommentDelegate) runUpdate(ctx context.Context, where UniquePredicate[C err := d.client.transaction(ctx, func(txQ *Queries) error { var err error if d.client.dialect.SupportsUpdateReturning { - res, err = txQ.Comment.runUpdate(ctx, where, additional, assignments, selects, omits) + res, err = txQ.Comment.runUpdate(ctx, preds, cols, vals, selects, omits) } else { - res, err = txQ.Comment.runUpdateFallback(ctx, where, additional, assignments, selects, omits) + res, err = txQ.Comment.runUpdateFallback(ctx, preds, cols, vals, selects, omits) } return err }) @@ -1738,7 +1963,7 @@ func (d *CommentDelegate) runUpdate(ctx context.Context, where UniquePredicate[C } returningCols := selectCommentCols(selects, omits, commentPKCols...) - query, setVals := d.buildUpdateSQL(allPreds, assignments, returningCols) + query, setVals := d.buildUpdateSQL(preds, cols, vals, returningCols) rows, err := d.client.query(ctx, query, setVals...) if err != nil { @@ -1770,8 +1995,8 @@ func (d *CommentDelegate) runUpdate(ctx context.Context, where UniquePredicate[C return &res, nil } -func (d *CommentDelegate) execUpdateStmt(ctx context.Context, preds []PredicateOf[Comment], assignments []FieldAssignment) (int64, error) { - if len(assignments) == 0 { +func (d *CommentDelegate) execUpdateStmt(ctx context.Context, preds []PredicateOf[Comment], cols []string, vals []any) (int64, error) { + if len(cols) == 0 { return 0, nil } @@ -1783,7 +2008,7 @@ func (d *CommentDelegate) execUpdateStmt(ctx context.Context, preds []PredicateO } } - query, setVals := d.buildUpdateSQL(preds, assignments, nil) + query, setVals := d.buildUpdateSQL(preds, cols, vals, nil) result, err := d.client.exec(ctx, query, setVals...) if err != nil { return 0, err @@ -1791,16 +2016,15 @@ func (d *CommentDelegate) execUpdateStmt(ctx context.Context, preds []PredicateO return result.RowsAffected() } -func (d *CommentDelegate) runUpdateFallback(ctx context.Context, where UniquePredicate[Comment], additional []PredicateOf[Comment], assignments []FieldAssignment, selects *CommentSelect, omits *CommentOmit) (*Comment, error) { - allPreds := append([]PredicateOf[Comment]{where}, additional...) - affected, err := d.execUpdateStmt(ctx, allPreds, assignments) +func (d *CommentDelegate) runUpdateFallback(ctx context.Context, preds []PredicateOf[Comment], cols []string, vals []any, selects *CommentSelect, omits *CommentOmit) (*Comment, error) { + affected, err := d.execUpdateStmt(ctx, preds, cols, vals) if err != nil { return nil, err } if affected == 0 { return nil, sql.ErrNoRows } - return d.runFindUnique(ctx, allPreds, selects, omits) + return d.runFindUnique(ctx, preds, selects, omits) } // ----------------------------------------------------------------------------- @@ -1808,17 +2032,30 @@ func (d *CommentDelegate) runUpdateFallback(ctx context.Context, where UniquePre // ----------------------------------------------------------------------------- func (d *CommentDelegate) executeUpdateMany(ctx context.Context, preds []PredicateOf[Comment], assignments []FieldAssignment) (int64, error) { + input, err := assignmentsToCommentUpdate(assignments) + if err != nil { + return 0, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.execUpdateStmt(ctx, preds, assignments) + return d.execUpdateStmt(ctx, preds, cols, vals) } - curr := func(c context.Context, p []PredicateOf[Comment], a []FieldAssignment) (int64, error) { - return d.execUpdateStmt(c, p, a) + args := &CommentUpdateManyArgs{ + Where: preds, + Data: &input, + } + + curr := func(c context.Context, a *CommentUpdateManyArgs) (int64, error) { + extCols, extVals := a.Data.ToColsVals() + return d.execUpdateStmt(c, a.Where, extCols, extVals) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.UpdateMany != nil { - return ext.UpdateMany(ctx, preds, assignments, curr) + return ext.UpdateMany(ctx, args, curr) } } @@ -1826,13 +2063,13 @@ func (d *CommentDelegate) executeUpdateMany(ctx context.Context, preds []Predica ext := d.extensions[i] if ext.UpdateMany != nil { next, hook := curr, ext.UpdateMany - curr = func(c context.Context, p []PredicateOf[Comment], a []FieldAssignment) (int64, error) { - return hook(c, p, a, next) + curr = func(c context.Context, a *CommentUpdateManyArgs) (int64, error) { + return hook(c, a, next) } } } - return curr(ctx, preds, assignments) + return curr(ctx, args) } // ----------------------------------------------------------------------------- @@ -1840,21 +2077,35 @@ func (d *CommentDelegate) executeUpdateMany(ctx context.Context, preds []Predica // ----------------------------------------------------------------------------- func (d *CommentDelegate) executeUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[Comment], assignments []FieldAssignment, selects *CommentSelect, omits *CommentOmit) ([]*Comment, error) { + input, err := assignmentsToCommentUpdate(assignments) + if err != nil { + return nil, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.runUpdateManyAndReturn(ctx, preds, assignments, selects, omits) + return d.runUpdateManyAndReturn(ctx, preds, cols, vals, selects, omits) } if selects == nil || !selects.hasAnySelected() { selects = fullCommentSelect() } - curr := func(c context.Context, p []PredicateOf[Comment], a []FieldAssignment, s *CommentSelect, o *CommentOmit) ([]*Comment, error) { - return d.runUpdateManyAndReturn(c, p, a, s, o) + args := &CommentUpdateManyAndReturnArgs{ + Where: preds, + Data: &input, + Select: selects, + } + + curr := func(c context.Context, a *CommentUpdateManyAndReturnArgs) ([]*Comment, error) { + extCols, extVals := a.Data.ToColsVals() + return d.runUpdateManyAndReturn(c, a.Where, extCols, extVals, a.Select, omits) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.UpdateManyAndReturn != nil { - return ext.UpdateManyAndReturn(ctx, preds, assignments, selects, omits, curr) + return ext.UpdateManyAndReturn(ctx, args, curr) } } @@ -1862,17 +2113,17 @@ func (d *CommentDelegate) executeUpdateManyAndReturn(ctx context.Context, preds ext := d.extensions[i] if ext.UpdateManyAndReturn != nil { next, hook := curr, ext.UpdateManyAndReturn - curr = func(c context.Context, p []PredicateOf[Comment], a []FieldAssignment, s *CommentSelect, o *CommentOmit) ([]*Comment, error) { - return hook(c, p, a, s, o, next) + curr = func(c context.Context, a *CommentUpdateManyAndReturnArgs) ([]*Comment, error) { + return hook(c, a, next) } } } - return curr(ctx, preds, assignments, selects, omits) + return curr(ctx, args) } -func (d *CommentDelegate) runUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[Comment], assignments []FieldAssignment, selects *CommentSelect, omits *CommentOmit) ([]*Comment, error) { - if len(assignments) == 0 { +func (d *CommentDelegate) runUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[Comment], cols []string, vals []any, selects *CommentSelect, omits *CommentOmit) ([]*Comment, error) { + if len(cols) == 0 { return d.runFindMany(ctx, QueryParams[Comment]{Where: preds}, selects, omits) } @@ -1892,9 +2143,9 @@ func (d *CommentDelegate) runUpdateManyAndReturn(ctx context.Context, preds []Pr err := d.client.transaction(ctx, func(txQ *Queries) error { var err error if d.client.dialect.SupportsUpdateReturning { - res, err = txQ.Comment.runUpdateManyAndReturn(ctx, preds, assignments, selects, omits) + res, err = txQ.Comment.runUpdateManyAndReturn(ctx, preds, cols, vals, selects, omits) } else { - res, err = txQ.Comment.runUpdateManyAndReturnFallback(ctx, preds, assignments, selects, omits) + res, err = txQ.Comment.runUpdateManyAndReturnFallback(ctx, preds, cols, vals, selects, omits) } return err }) @@ -1902,39 +2153,29 @@ func (d *CommentDelegate) runUpdateManyAndReturn(ctx context.Context, preds []Pr } returningCols := selectCommentCols(selects, omits, commentPKCols...) - query, setVals := d.buildUpdateSQL(preds, assignments, returningCols) + query, setVals := d.buildUpdateSQL(preds, cols, vals, returningCols) rows, err := d.client.query(ctx, query, setVals...) if err != nil { return nil, err } - results := make([]*Comment, 0) - for rows.Next() { - var res Comment - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - rows.Close() - return nil, err - } - results = append(results, &res) - } - rowsErr := rows.Err() - rows.Close() - if rowsErr != nil { - return nil, rowsErr + scanned, err := scanCommentRows(rows, returningCols) + if err != nil { + return nil, err } if selects != nil && selects.hasAnyRelation() { - if err := d.loadRelations(ctx, results, selects); err != nil { + if err := d.loadRelations(ctx, scanned, selects); err != nil { return nil, err } } - return results, nil + return scanned, nil } -func (d *CommentDelegate) runUpdateManyAndReturnFallback(ctx context.Context, preds []PredicateOf[Comment], assignments []FieldAssignment, selects *CommentSelect, omits *CommentOmit) ([]*Comment, error) { - affected, err := d.execUpdateStmt(ctx, preds, assignments) +func (d *CommentDelegate) runUpdateManyAndReturnFallback(ctx context.Context, preds []PredicateOf[Comment], cols []string, vals []any, selects *CommentSelect, omits *CommentOmit) ([]*Comment, error) { + affected, err := d.execUpdateStmt(ctx, preds, cols, vals) if err != nil { return nil, err } diff --git a/integration/valk/defaultsTest.go b/integration/valk/defaultsTest.go index 2b40a50..9a27c74 100644 --- a/integration/valk/defaultsTest.go +++ b/integration/valk/defaultsTest.go @@ -62,6 +62,156 @@ func (s *DefaultsTestCreate) colMask() uint64 { return mask } +// DefaultsTestUpdate contains model input fields for DefaultsTest update operations. +type DefaultsTestUpdate struct { + Uuid4 *string `json:"uuid4"` + Uuid7 *string `json:"uuid7"` + UuidNoArgs *string `json:"uuidNoArgs"` + Cuid1 *string `json:"cuid1"` + Cuid2 *string `json:"cuid2"` + CuidNoArgs *string `json:"cuidNoArgs"` + Ulid *string `json:"ulid"` + Nanoid *string `json:"nanoid"` + Now *time.Time `json:"now"` +} + +func (u *DefaultsTestUpdate) ToColsVals() ([]string, []any) { + var cols []string + var vals []any + if u.Uuid4 != nil { + cols = append(cols, "uuid4") + vals = append(vals, u.Uuid4) + } + if u.Uuid7 != nil { + cols = append(cols, "uuid7") + vals = append(vals, u.Uuid7) + } + if u.UuidNoArgs != nil { + cols = append(cols, "uuidNoArgs") + vals = append(vals, u.UuidNoArgs) + } + if u.Cuid1 != nil { + cols = append(cols, "cuid1") + vals = append(vals, u.Cuid1) + } + if u.Cuid2 != nil { + cols = append(cols, "cuid2") + vals = append(vals, u.Cuid2) + } + if u.CuidNoArgs != nil { + cols = append(cols, "cuidNoArgs") + vals = append(vals, u.CuidNoArgs) + } + if u.Ulid != nil { + cols = append(cols, "ulid") + vals = append(vals, u.Ulid) + } + if u.Nanoid != nil { + cols = append(cols, "nanoid") + vals = append(vals, u.Nanoid) + } + if u.Now != nil { + cols = append(cols, "now") + vals = append(vals, u.Now) + } + return cols, vals +} + +func assignmentsToDefaultsTestUpdate(assignments []FieldAssignment) (DefaultsTestUpdate, error) { + var input DefaultsTestUpdate + var errs ValidationError + + for _, a := range assignments { + switch a.Col { + case "uuid4": + if v, ok := a.Val.(string); ok { + input.Uuid4 = &v + errs.ValidateString("uuid4", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.Uuid4 = v + } else { + errs.Add("uuid4", a.Val, "type", "field uuid4 must be of type string") + } + case "uuid7": + if v, ok := a.Val.(string); ok { + input.Uuid7 = &v + errs.ValidateString("uuid7", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.Uuid7 = v + } else { + errs.Add("uuid7", a.Val, "type", "field uuid7 must be of type string") + } + case "uuidNoArgs": + if v, ok := a.Val.(string); ok { + input.UuidNoArgs = &v + errs.ValidateString("uuidNoArgs", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.UuidNoArgs = v + } else { + errs.Add("uuidNoArgs", a.Val, "type", "field uuidNoArgs must be of type string") + } + case "cuid1": + if v, ok := a.Val.(string); ok { + input.Cuid1 = &v + errs.ValidateString("cuid1", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.Cuid1 = v + } else { + errs.Add("cuid1", a.Val, "type", "field cuid1 must be of type string") + } + case "cuid2": + if v, ok := a.Val.(string); ok { + input.Cuid2 = &v + errs.ValidateString("cuid2", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.Cuid2 = v + } else { + errs.Add("cuid2", a.Val, "type", "field cuid2 must be of type string") + } + case "cuidNoArgs": + if v, ok := a.Val.(string); ok { + input.CuidNoArgs = &v + errs.ValidateString("cuidNoArgs", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.CuidNoArgs = v + } else { + errs.Add("cuidNoArgs", a.Val, "type", "field cuidNoArgs must be of type string") + } + case "ulid": + if v, ok := a.Val.(string); ok { + input.Ulid = &v + errs.ValidateString("ulid", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.Ulid = v + } else { + errs.Add("ulid", a.Val, "type", "field ulid must be of type string") + } + case "nanoid": + if v, ok := a.Val.(string); ok { + input.Nanoid = &v + errs.ValidateString("nanoid", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.Nanoid = v + } else { + errs.Add("nanoid", a.Val, "type", "field nanoid must be of type string") + } + case "now": + if v, ok := a.Val.(time.Time); ok { + input.Now = &v + } else if v, ok := a.Val.(*time.Time); ok { + input.Now = v + } else { + errs.Add("now", a.Val, "type", "field now must be of type time.Time") + } + } + } + + if errs.HasErrors() { + return input, errs + } + return input, nil +} + // DefaultsTestSelect specifies which scalar and relation fields to select for DefaultsTest. // // Selectable fields: @@ -436,6 +586,51 @@ func (a *DefaultsTestDeleteManyArgs) SetWhere(preds ...PredicateOf[DefaultsTest] return a } +// DefaultsTestUpdateArgs is the input argument passed to DefaultsTest Update extension hooks. +type DefaultsTestUpdateArgs struct { + // Where contains all query filter predicates (merged primary unique constraint and additional predicates). + Where []PredicateOf[DefaultsTest] + // Data contains the model fields to update. + Data *DefaultsTestUpdate + // Select specifies which scalar and relation fields to select and return upon update. + Select *DefaultsTestSelect +} + +func (a *DefaultsTestUpdateArgs) SetWhere(unique UniquePredicate[DefaultsTest], additional ...PredicateOf[DefaultsTest]) *DefaultsTestUpdateArgs { + a.Where = make([]PredicateOf[DefaultsTest], 0, 1+len(additional)) + a.Where = append(a.Where, unique) + a.Where = append(a.Where, additional...) + return a +} + +// DefaultsTestUpdateManyArgs is the input argument passed to DefaultsTest UpdateMany extension hooks. +type DefaultsTestUpdateManyArgs struct { + // Where contains all query filter predicates. + Where []PredicateOf[DefaultsTest] + // Data contains the model fields to update. + Data *DefaultsTestUpdate +} + +func (a *DefaultsTestUpdateManyArgs) SetWhere(preds ...PredicateOf[DefaultsTest]) *DefaultsTestUpdateManyArgs { + a.Where = preds + return a +} + +// DefaultsTestUpdateManyAndReturnArgs is the input argument passed to DefaultsTest UpdateManyAndReturn extension hooks. +type DefaultsTestUpdateManyAndReturnArgs struct { + // Where contains all query filter predicates. + Where []PredicateOf[DefaultsTest] + // Data contains the model fields to update. + Data *DefaultsTestUpdate + // Select specifies which scalar and relation fields to select and return upon update. + Select *DefaultsTestSelect +} + +func (a *DefaultsTestUpdateManyAndReturnArgs) SetWhere(preds ...PredicateOf[DefaultsTest]) *DefaultsTestUpdateManyAndReturnArgs { + a.Where = preds + return a +} + type DefaultsTestCreateQuery = func(ctx context.Context, args *DefaultsTestCreateArgs) (*DefaultsTest, error) type DefaultsTestCreateManyQuery = func(ctx context.Context, args *DefaultsTestCreateManyArgs) (int64, error) type DefaultsTestCreateManyAndReturnQuery = func(ctx context.Context, args *DefaultsTestCreateManyAndReturnArgs) ([]*DefaultsTest, error) @@ -445,9 +640,9 @@ type DefaultsTestFindManyQuery = func(ctx context.Context, args *DefaultsTestFin type DefaultsTestDeleteQuery = func(ctx context.Context, args *DefaultsTestDeleteArgs) (*DefaultsTest, error) type DefaultsTestDeleteManyQuery = func(ctx context.Context, args *DefaultsTestDeleteManyArgs) (int64, error) type DefaultsTestCountQuery = func(ctx context.Context, args *DefaultsTestCountArgs) (int64, error) -type DefaultsTestUpdateQuery = func(ctx context.Context, where UniquePredicate[DefaultsTest], additional []PredicateOf[DefaultsTest], assignments []FieldAssignment, selects *DefaultsTestSelect, omits *DefaultsTestOmit) (*DefaultsTest, error) -type DefaultsTestUpdateManyQuery = func(ctx context.Context, preds []PredicateOf[DefaultsTest], assignments []FieldAssignment) (int64, error) -type DefaultsTestUpdateManyAndReturnQuery = func(ctx context.Context, preds []PredicateOf[DefaultsTest], assignments []FieldAssignment, selects *DefaultsTestSelect, omits *DefaultsTestOmit) ([]*DefaultsTest, error) +type DefaultsTestUpdateQuery = func(ctx context.Context, args *DefaultsTestUpdateArgs) (*DefaultsTest, error) +type DefaultsTestUpdateManyQuery = func(ctx context.Context, args *DefaultsTestUpdateManyArgs) (int64, error) +type DefaultsTestUpdateManyAndReturnQuery = func(ctx context.Context, args *DefaultsTestUpdateManyAndReturnArgs) ([]*DefaultsTest, error) type DefaultsTestExtension struct { Create func(ctx context.Context, args *DefaultsTestCreateArgs, next DefaultsTestCreateQuery) (*DefaultsTest, error) @@ -459,9 +654,9 @@ type DefaultsTestExtension struct { Delete func(ctx context.Context, args *DefaultsTestDeleteArgs, next DefaultsTestDeleteQuery) (*DefaultsTest, error) DeleteMany func(ctx context.Context, args *DefaultsTestDeleteManyArgs, next DefaultsTestDeleteManyQuery) (int64, error) Count func(ctx context.Context, args *DefaultsTestCountArgs, next DefaultsTestCountQuery) (int64, error) - Update func(ctx context.Context, where UniquePredicate[DefaultsTest], additional []PredicateOf[DefaultsTest], assignments []FieldAssignment, selects *DefaultsTestSelect, omits *DefaultsTestOmit, next DefaultsTestUpdateQuery) (*DefaultsTest, error) - UpdateMany func(ctx context.Context, preds []PredicateOf[DefaultsTest], assignments []FieldAssignment, next DefaultsTestUpdateManyQuery) (int64, error) - UpdateManyAndReturn func(ctx context.Context, preds []PredicateOf[DefaultsTest], assignments []FieldAssignment, selects *DefaultsTestSelect, omits *DefaultsTestOmit, next DefaultsTestUpdateManyAndReturnQuery) ([]*DefaultsTest, error) + Update func(ctx context.Context, args *DefaultsTestUpdateArgs, next DefaultsTestUpdateQuery) (*DefaultsTest, error) + UpdateMany func(ctx context.Context, args *DefaultsTestUpdateManyArgs, next DefaultsTestUpdateManyQuery) (int64, error) + UpdateManyAndReturn func(ctx context.Context, args *DefaultsTestUpdateManyAndReturnArgs, next DefaultsTestUpdateManyAndReturnQuery) ([]*DefaultsTest, error) } type DefaultsTestDelegate struct { @@ -561,6 +756,16 @@ type DefaultsTestCreateBuilder struct { *CreateBuilder[DefaultsTest, DefaultsTestSelect, DefaultsTestOmit] } +func (b *DefaultsTestCreateBuilder) Select(s DefaultsTestSelect) *DefaultsTestCreateBuilder { + b.selects = &s + return b +} + +func (b *DefaultsTestCreateBuilder) Omit(o DefaultsTestOmit) *DefaultsTestCreateBuilder { + b.omits = &o + return b +} + func (b *DefaultsTestCreateBuilder) OnConflict(target UniqueConstraintTarget) *DefaultsTestConflictBuilder[DefaultsTestCreateBuilder] { return &DefaultsTestConflictBuilder[DefaultsTestCreateBuilder]{ builder: b, @@ -818,23 +1023,11 @@ func (d *DefaultsTestDelegate) executeCreate(ctx context.Context, assignments [] return nil, err } + cols, vals := input.ToColsVals() + returningCols := selectDefaultsTestCols(selects, omits) + if len(d.extensions) == 0 { - cols, vals := input.ToColsVals() - returningCols := selectDefaultsTestCols(selects, omits) - hasRelations := selects.hasAnyRelation() - if hasRelations { - var res *DefaultsTest - err = d.client.transaction(ctx, func(txQ *Queries) error { - var err error - res, err = txQ.DefaultsTest.runCreate(ctx, cols, vals, returningCols, defaultsTestPKCols, conflictTarget, conflictAction) - if err != nil { - return err - } - return txQ.DefaultsTest.loadRelations(ctx, []*DefaultsTest{res}, selects) - }) - return res, err - } - return d.runCreate(ctx, cols, vals, returningCols, defaultsTestPKCols, conflictTarget, conflictAction) + return d.runCreate(ctx, cols, vals, returningCols, selects, conflictTarget, conflictAction) } if selects == nil || !selects.hasAnySelected() { @@ -849,28 +1042,9 @@ func (d *DefaultsTestDelegate) executeCreate(ctx context.Context, assignments [] } curr := func(c context.Context, a *DefaultsTestCreateArgs) (*DefaultsTest, error) { - cols, vals := a.Data.ToColsVals() - returningCols := selectDefaultsTestCols(a.Select, omits) - - hasRelations := a.Select.hasAnyRelation() - var res *DefaultsTest - var err error - if hasRelations { - err = d.client.transaction(c, func(txQ *Queries) error { - var err error - res, err = txQ.DefaultsTest.runCreate(c, cols, vals, returningCols, defaultsTestPKCols, a.ConflictTarget, a.ConflictAction) - if err != nil { - return err - } - return txQ.DefaultsTest.loadRelations(c, []*DefaultsTest{res}, a.Select) - }) - } else { - res, err = d.runCreate(c, cols, vals, returningCols, defaultsTestPKCols, a.ConflictTarget, a.ConflictAction) - } - if err != nil { - return nil, err - } - return res, nil + cCols, cVals := a.Data.ToColsVals() + cReturningCols := selectDefaultsTestCols(a.Select, omits) + return d.runCreate(c, cCols, cVals, cReturningCols, a.Select, a.ConflictTarget, a.ConflictAction) } if len(d.extensions) == 1 { @@ -911,6 +1085,16 @@ type DefaultsTestCreateManyAndReturnBuilder struct { *CreateManyAndReturnBuilder[DefaultsTest, DefaultsTestSelect, DefaultsTestOmit] } +func (b *DefaultsTestCreateManyAndReturnBuilder) Select(s DefaultsTestSelect) *DefaultsTestCreateManyAndReturnBuilder { + b.selects = &s + return b +} + +func (b *DefaultsTestCreateManyAndReturnBuilder) Omit(o DefaultsTestOmit) *DefaultsTestCreateManyAndReturnBuilder { + b.omits = &o + return b +} + func (b *DefaultsTestCreateManyAndReturnBuilder) OnConflict(target UniqueConstraintTarget) *DefaultsTestConflictBuilder[DefaultsTestCreateManyAndReturnBuilder] { return &DefaultsTestConflictBuilder[DefaultsTestCreateManyAndReturnBuilder]{ builder: b, @@ -922,43 +1106,51 @@ func (b *DefaultsTestCreateManyAndReturnBuilder) OnConflict(target UniqueConstra } } -func (d *DefaultsTestDelegate) CreateMany(builders ...*DefaultsTestCreateBuilder) *DefaultsTestCreateManyBuilder { +func createBuildersToDefaultsTestRecordInputs(builders []*DefaultsTestCreateBuilder) []RecordInput { records := make([]RecordInput, len(builders)) for i, b := range builders { records[i] = RecordInput{Assignments: b.assignments} } + return records +} + +func (d *DefaultsTestDelegate) CreateMany(builders ...*DefaultsTestCreateBuilder) *DefaultsTestCreateManyBuilder { return &DefaultsTestCreateManyBuilder{ CreateManyBuilder: &CreateManyBuilder[DefaultsTest]{ - records: records, + records: createBuildersToDefaultsTestRecordInputs(builders), execFunc: d.executeCreateMany, }, } } func (d *DefaultsTestDelegate) CreateManyAndReturn(builders ...*DefaultsTestCreateBuilder) *DefaultsTestCreateManyAndReturnBuilder { - records := make([]RecordInput, len(builders)) - for i, b := range builders { - records[i] = RecordInput{Assignments: b.assignments} - } return &DefaultsTestCreateManyAndReturnBuilder{ CreateManyAndReturnBuilder: &CreateManyAndReturnBuilder[DefaultsTest, DefaultsTestSelect, DefaultsTestOmit]{ - records: records, + records: createBuildersToDefaultsTestRecordInputs(builders), execFunc: d.executeCreateManyAndReturn, }, } } -func (d *DefaultsTestDelegate) executeCreateMany(ctx context.Context, records []RecordInput, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { +func recordsToDefaultsTestCreateInputs(records []RecordInput) ([]*DefaultsTestCreate, error) { structs := make([]DefaultsTestCreate, len(records)) inputs := make([]*DefaultsTestCreate, len(records)) for i, rec := range records { var err error structs[i], err = assignmentsToDefaultsTestCreate(rec.Assignments) if err != nil { - return 0, fmt.Errorf("validation failed at index %d: %w", i, err) + return nil, fmt.Errorf("validation failed at index %d: %w", i, err) } inputs[i] = &structs[i] } + return inputs, nil +} + +func (d *DefaultsTestDelegate) executeCreateMany(ctx context.Context, records []RecordInput, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { + inputs, err := recordsToDefaultsTestCreateInputs(records) + if err != nil { + return 0, err + } if len(d.extensions) == 0 { return d.runCreateMany(ctx, inputs, conflictTarget, conflictAction) @@ -994,31 +1186,12 @@ func (d *DefaultsTestDelegate) executeCreateMany(ctx context.Context, records [] } func (d *DefaultsTestDelegate) executeCreateManyAndReturn(ctx context.Context, records []RecordInput, selects *DefaultsTestSelect, omits *DefaultsTestOmit, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) ([]*DefaultsTest, error) { - structs := make([]DefaultsTestCreate, len(records)) - inputs := make([]*DefaultsTestCreate, len(records)) - for i, rec := range records { - var err error - structs[i], err = assignmentsToDefaultsTestCreate(rec.Assignments) - if err != nil { - return nil, fmt.Errorf("validation failed at index %d: %w", i, err) - } - inputs[i] = &structs[i] + inputs, err := recordsToDefaultsTestCreateInputs(records) + if err != nil { + return nil, err } if len(d.extensions) == 0 { - hasRelations := selects != nil && selects.hasAnyRelation() - if hasRelations { - var res []*DefaultsTest - err := d.client.transaction(ctx, func(txQ *Queries) error { - var err error - res, err = txQ.DefaultsTest.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) - if err != nil { - return err - } - return txQ.DefaultsTest.loadRelations(ctx, res, selects) - }) - return res, err - } return d.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) } @@ -1034,19 +1207,6 @@ func (d *DefaultsTestDelegate) executeCreateManyAndReturn(ctx context.Context, r } curr := func(c context.Context, a *DefaultsTestCreateManyAndReturnArgs) ([]*DefaultsTest, error) { - hasRelations := a.Select != nil && a.Select.hasAnyRelation() - if hasRelations { - var res []*DefaultsTest - err := d.client.transaction(c, func(txQ *Queries) error { - var err error - res, err = txQ.DefaultsTest.runCreateManyAndReturn(c, a.Data, a.Select, omits, a.ConflictTarget, a.ConflictAction) - if err != nil { - return err - } - return txQ.DefaultsTest.loadRelations(c, res, a.Select) - }) - return res, err - } return d.runCreateManyAndReturn(c, a.Data, a.Select, omits, a.ConflictTarget, a.ConflictAction) } @@ -1074,36 +1234,67 @@ func (d *DefaultsTestDelegate) runCreate( cols []string, vals []any, returningCols []string, - pkCols []string, + selects *DefaultsTestSelect, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction, ) (*DefaultsTest, error) { - query, clauseArgs := buildSingleInsertSQL(d.client, "DefaultsTest", cols, returningCols, pkCols, conflictTarget, conflictAction, len(vals)) + hasRelations := selects != nil && selects.hasAnyRelation() + useTx := hasRelations && !d.client.inTx() + + if useTx { + var res *DefaultsTest + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + res, err = txQ.DefaultsTest.runCreate(ctx, cols, vals, returningCols, selects, conflictTarget, conflictAction) + if err != nil { + return err + } + return txQ.DefaultsTest.loadRelations(ctx, []*DefaultsTest{res}, selects) + }) + return res, err + } + + query, clauseArgs := buildSingleInsertSQL(d.client, "DefaultsTest", cols, returningCols, defaultsTestPKCols, conflictTarget, conflictAction, len(vals)) if len(clauseArgs) > 0 { vals = append(vals, clauseArgs...) } - var res DefaultsTest if d.client.dialect.SupportsInsertReturning { rows, err := d.client.query(ctx, query, vals...) if err != nil { return nil, err } - defer rows.Close() - if rows.Next() { - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + if !rows.Next() { + err := rows.Err() + rows.Close() + if err != nil { return nil, err } - return &res, nil + return nil, nil + } + + var res DefaultsTest + scanErr := rows.Scan(res.ScanFields(returningCols)...) + rows.Close() + if scanErr != nil { + return nil, scanErr } - return nil, rows.Err() + + return &res, nil } - return d.runCreateFallback(ctx, query, vals, cols, returningCols, pkCols) + return d.runCreateFallback(ctx, query, vals, cols, returningCols, defaultsTestPKCols) } -func (d *DefaultsTestDelegate) runCreateFallback(ctx context.Context, query string, vals []any, cols []string, returningCols []string, pkCols []string) (*DefaultsTest, error) { +func (d *DefaultsTestDelegate) runCreateFallback( + ctx context.Context, + query string, + vals []any, + cols []string, + returningCols []string, + pkCols []string, +) (*DefaultsTest, error) { result, err := d.client.exec(ctx, query, vals...) if err != nil { return nil, err @@ -1153,16 +1344,24 @@ func (d *DefaultsTestDelegate) runCreateFallback(ctx context.Context, query stri if err != nil { return nil, err } - defer rows.Close() - var res DefaultsTest - if rows.Next() { - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + if !rows.Next() { + err := rows.Err() + rows.Close() + if err != nil { return nil, err } - return &res, nil + return nil, nil } - return nil, rows.Err() + + var res DefaultsTest + scanErr := rows.Scan(res.ScanFields(returningCols)...) + rows.Close() + if scanErr != nil { + return nil, scanErr + } + + return &res, nil } func (d *DefaultsTestDelegate) buildBulkInsertSQL(q *Queries, batch []*DefaultsTestCreate, paramStartIdx int) (cols []string, vals []any, queryStr string) { @@ -1272,6 +1471,41 @@ func (d *DefaultsTestDelegate) buildBulkInsertSQL(q *Queries, batch []*DefaultsT return cols, vals, queryStr } +func applyDefaultsTestConflictClause(dialect Dialect, queryStr string, vals []any, cols []string, pkCols []string, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (string, []any) { + var conflictCols []string + if conflictTarget != nil { + conflictCols = conflictTarget.UniqueColumns() + } + var nonConflictCols []string + if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { + nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + } + clause, clauseArgs := dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) + queryStr += clause + if len(clauseArgs) > 0 { + vals = append(vals, clauseArgs...) + } + return queryStr, vals +} + +func scanDefaultsTestRows(rows *sql.Rows, returningCols []string) ([]*DefaultsTest, error) { + var records []*DefaultsTest + for rows.Next() { + var res DefaultsTest + if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + rows.Close() + return nil, err + } + records = append(records, &res) + } + if err := rows.Err(); err != nil { + rows.Close() + return nil, err + } + rows.Close() + return records, nil +} + func (d *DefaultsTestDelegate) runCreateMany(ctx context.Context, inputs []*DefaultsTestCreate, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { if len(inputs) == 0 { return 0, nil @@ -1282,18 +1516,7 @@ func (d *DefaultsTestDelegate) runCreateMany(ctx context.Context, inputs []*Defa var count int64 for _, batch := range batches { cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) - - var conflictCols []string - if conflictTarget != nil { - conflictCols = conflictTarget.UniqueColumns() - } - var nonConflictCols []string - if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, defaultsTestPKCols) - } - clause, clauseArgs := d.client.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) - queryStr += clause - vals = append(vals, clauseArgs...) + queryStr, vals = applyDefaultsTestConflictClause(d.client.dialect, queryStr, vals, cols, defaultsTestPKCols, conflictTarget, conflictAction) result, err := d.client.exec(ctx, queryStr, vals...) if err != nil { @@ -1321,27 +1544,37 @@ func (d *DefaultsTestDelegate) runCreateManyAndReturn( } batches := partitionDefaultsTestInputs(d.client.dialect, inputs) - returningCols := selectDefaultsTestCols(selects, omits) hasRelations := selects != nil && selects.hasAnyRelation() + useTx := (len(batches) > 1 || hasRelations || !d.client.dialect.SupportsInsertReturning) && !d.client.inTx() - recordsOut := make([]*DefaultsTest, 0, len(inputs)) + if useTx { + var res []*DefaultsTest + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + if txQ.dialect.SupportsInsertReturning { + res, err = txQ.DefaultsTest.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) + } else { + res, err = txQ.DefaultsTest.runCreateManyAndReturnFallback(ctx, inputs, selects, omits, conflictTarget, conflictAction) + } + if err != nil { + return err + } + if hasRelations { + return txQ.DefaultsTest.loadRelations(ctx, res, selects) + } + return nil + }) + return res, err + } - runBatch := func(txQ *Queries, batch []*DefaultsTestCreate) error { - cols, vals, queryStr := d.buildBulkInsertSQL(txQ, batch, 1) + returningCols := selectDefaultsTestCols(selects, omits, defaultsTestPKCols...) + recordsOut := make([]*DefaultsTest, 0, len(inputs)) - var conflictCols []string - if conflictTarget != nil { - conflictCols = conflictTarget.UniqueColumns() - } - var nonConflictCols []string - if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, defaultsTestPKCols) - } - clause, clauseArgs := txQ.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) - queryStr += clause - vals = append(vals, clauseArgs...) + for _, batch := range batches { + cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) + queryStr, vals = applyDefaultsTestConflictClause(d.client.dialect, queryStr, vals, cols, defaultsTestPKCols, conflictTarget, conflictAction) - if txQ.dialect.SupportsInsertReturning && len(returningCols) > 0 { + if len(returningCols) > 0 { var retSb strings.Builder retSb.Grow(12 + len(returningCols)*15) retSb.WriteString(" RETURNING ") @@ -1349,40 +1582,58 @@ func (d *DefaultsTestDelegate) runCreateManyAndReturn( if i > 0 { retSb.WriteString(", ") } - txQ.dialect.WriteQuotedIdent(&retSb, col) + d.client.dialect.WriteQuotedIdent(&retSb, col) } queryStr += retSb.String() - rows, err := txQ.query(ctx, queryStr, vals...) - if err != nil { - return err - } - defer rows.Close() + } - for rows.Next() { - var res DefaultsTest - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - return err - } - recordsOut = append(recordsOut, &res) - } - return rows.Err() + rows, err := d.client.query(ctx, queryStr, vals...) + if err != nil { + return nil, err } - // Fallback for dialects without RETURNING (MySQL) - result, err := txQ.exec(ctx, queryStr, vals...) + scanned, err := scanDefaultsTestRows(rows, returningCols) if err != nil { - return err + return nil, err + } + recordsOut = append(recordsOut, scanned...) + } + + if selects != nil && selects.hasAnyRelation() { + if err := d.loadRelations(ctx, recordsOut, selects); err != nil { + return nil, err + } + } + + return recordsOut, nil +} + +func (d *DefaultsTestDelegate) runCreateManyAndReturnFallback( + ctx context.Context, + inputs []*DefaultsTestCreate, + selects *DefaultsTestSelect, + omits *DefaultsTestOmit, + conflictTarget UniqueConstraintTarget, + conflictAction *ConflictAction, +) ([]*DefaultsTest, error) { + batches := partitionDefaultsTestInputs(d.client.dialect, inputs) + returningCols := selectDefaultsTestCols(selects, omits, defaultsTestPKCols...) + recordsOut := make([]*DefaultsTest, 0, len(inputs)) + + for _, batch := range batches { + cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) + queryStr, vals = applyDefaultsTestConflictClause(d.client.dialect, queryStr, vals, cols, defaultsTestPKCols, conflictTarget, conflictAction) + + result, err := d.client.exec(ctx, queryStr, vals...) + if err != nil { + return nil, err } - // We need to fetch the inserted records for this batch - // Note: MySQL bulk inserts only return the ID of the FIRST inserted row lastID, err := result.LastInsertId() if err != nil { - return err + return nil, err } - // Query back the rows by IDs (assuming autoincrement ID and single PK) - // If composite PK, it's more complex, but this is a standard fallback var selectSb strings.Builder selectSb.Grow(64 + len(returningCols)*15 + len("DefaultsTest") + len(batch)*15) selectSb.WriteString("SELECT ") @@ -1390,55 +1641,29 @@ func (d *DefaultsTestDelegate) runCreateManyAndReturn( if i > 0 { selectSb.WriteString(", ") } - txQ.dialect.WriteQuotedIdent(&selectSb, col) + d.client.dialect.WriteQuotedIdent(&selectSb, col) } selectSb.WriteString(" FROM ") - txQ.dialect.WriteQuotedIdent(&selectSb, "DefaultsTest") + d.client.dialect.WriteQuotedIdent(&selectSb, "DefaultsTest") selectSb.WriteString(" WHERE ") - txQ.dialect.WriteQuotedIdent(&selectSb, defaultsTestPKCols[0]) + d.client.dialect.WriteQuotedIdent(&selectSb, defaultsTestPKCols[0]) selectSb.WriteString(" >= ") - txQ.dialect.WritePlaceholder(&selectSb, 1) + d.client.dialect.WritePlaceholder(&selectSb, 1) selectSb.WriteString(" AND ") - txQ.dialect.WriteQuotedIdent(&selectSb, defaultsTestPKCols[0]) + d.client.dialect.WriteQuotedIdent(&selectSb, defaultsTestPKCols[0]) selectSb.WriteString(" < ") - txQ.dialect.WritePlaceholder(&selectSb, 2) + d.client.dialect.WritePlaceholder(&selectSb, 2) - rows, err := txQ.query(ctx, selectSb.String(), lastID, lastID+int64(len(batch))) + rows, err := d.client.query(ctx, selectSb.String(), lastID, lastID+int64(len(batch))) if err != nil { - return err - } - defer rows.Close() - - for rows.Next() { - var res DefaultsTest - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - return err - } - recordsOut = append(recordsOut, &res) + return nil, err } - return rows.Err() - } - // Always wrap in transaction if we have multiple batches OR if we need to load relations - 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 { - return err - } - } - if hasRelations { - return txQ.DefaultsTest.loadRelations(ctx, recordsOut, selects) - } - return nil - }) + scanned, err := scanDefaultsTestRows(rows, returningCols) if err != nil { return nil, err } - } else { - if err := runBatch(d.client, batches[0]); err != nil { - return nil, err - } + recordsOut = append(recordsOut, scanned...) } return recordsOut, nil @@ -1691,23 +1916,23 @@ func (d *DefaultsTestDelegate) UpdateManyAndReturn(preds ...PredicateOf[Defaults } } -func (d *DefaultsTestDelegate) buildUpdateSQL(preds []PredicateOf[DefaultsTest], assignments []FieldAssignment, returningCols []string) (string, []any) { - whereClause, predVals, _ := CompilePredicates(d.client.dialect, preds, len(assignments)+1) +func (d *DefaultsTestDelegate) buildUpdateSQL(preds []PredicateOf[DefaultsTest], cols []string, vals []any, returningCols []string) (string, []any) { + whereClause, predVals, _ := CompilePredicates(d.client.dialect, preds, len(cols)+1) var sb strings.Builder sb.WriteString("UPDATE ") d.client.dialect.WriteQuotedIdent(&sb, "DefaultsTest") sb.WriteString(" SET ") - setVals := make([]any, 0, len(assignments)+len(predVals)) - for i, a := range assignments { + setVals := make([]any, 0, len(cols)+len(predVals)) + for i, col := range cols { if i > 0 { sb.WriteString(", ") } - d.client.dialect.WriteQuotedIdent(&sb, a.Col) + d.client.dialect.WriteQuotedIdent(&sb, col) sb.WriteString(" = ") d.client.dialect.WritePlaceholder(&sb, i+1) - setVals = append(setVals, a.Val) + setVals = append(setVals, vals[i]) } if whereClause != "" { @@ -1734,21 +1959,39 @@ func (d *DefaultsTestDelegate) buildUpdateSQL(preds []PredicateOf[DefaultsTest], // ----------------------------------------------------------------------------- func (d *DefaultsTestDelegate) executeUpdate(ctx context.Context, where UniquePredicate[DefaultsTest], additional []PredicateOf[DefaultsTest], assignments []FieldAssignment, selects *DefaultsTestSelect, omits *DefaultsTestOmit) (*DefaultsTest, error) { + allWhere := make([]PredicateOf[DefaultsTest], 0, 1+len(additional)) + allWhere = append(allWhere, where) + allWhere = append(allWhere, additional...) + + input, err := assignmentsToDefaultsTestUpdate(assignments) + if err != nil { + return nil, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.runUpdate(ctx, where, additional, assignments, selects, omits) + return d.runUpdate(ctx, allWhere, cols, vals, selects, omits) } if selects == nil || !selects.hasAnySelected() { selects = fullDefaultsTestSelect() } - curr := func(c context.Context, w UniquePredicate[DefaultsTest], add []PredicateOf[DefaultsTest], a []FieldAssignment, s *DefaultsTestSelect, o *DefaultsTestOmit) (*DefaultsTest, error) { - return d.runUpdate(c, w, add, a, s, o) + args := &DefaultsTestUpdateArgs{ + Where: allWhere, + Data: &input, + Select: selects, + } + + curr := func(c context.Context, a *DefaultsTestUpdateArgs) (*DefaultsTest, error) { + extCols, extVals := a.Data.ToColsVals() + return d.runUpdate(c, a.Where, extCols, extVals, a.Select, omits) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.Update != nil { - return ext.Update(ctx, where, additional, assignments, selects, omits, curr) + return ext.Update(ctx, args, curr) } } @@ -1756,25 +1999,21 @@ func (d *DefaultsTestDelegate) executeUpdate(ctx context.Context, where UniquePr ext := d.extensions[i] if ext.Update != nil { next, hook := curr, ext.Update - curr = func(c context.Context, w UniquePredicate[DefaultsTest], add []PredicateOf[DefaultsTest], a []FieldAssignment, s *DefaultsTestSelect, o *DefaultsTestOmit) (*DefaultsTest, error) { - return hook(c, w, add, a, s, o, next) + curr = func(c context.Context, a *DefaultsTestUpdateArgs) (*DefaultsTest, error) { + return hook(c, a, next) } } } - return curr(ctx, where, additional, assignments, selects, omits) + return curr(ctx, args) } -func (d *DefaultsTestDelegate) runUpdate(ctx context.Context, where UniquePredicate[DefaultsTest], additional []PredicateOf[DefaultsTest], assignments []FieldAssignment, selects *DefaultsTestSelect, omits *DefaultsTestOmit) (*DefaultsTest, error) { - allPreds := append([]PredicateOf[DefaultsTest]{where}, additional...) - if len(assignments) == 0 { - return d.runFindUnique(ctx, allPreds, selects, omits) +func (d *DefaultsTestDelegate) runUpdate(ctx context.Context, preds []PredicateOf[DefaultsTest], cols []string, vals []any, selects *DefaultsTestSelect, omits *DefaultsTestOmit) (*DefaultsTest, error) { + if len(cols) == 0 { + return d.runFindUnique(ctx, preds, selects, omits) } - if err := where.Validate(); err != nil { - return nil, err - } - for _, pr := range additional { + for _, pr := range preds { if pr != nil { if err := pr.Validate(); err != nil { return nil, err @@ -1790,9 +2029,9 @@ func (d *DefaultsTestDelegate) runUpdate(ctx context.Context, where UniquePredic err := d.client.transaction(ctx, func(txQ *Queries) error { var err error if d.client.dialect.SupportsUpdateReturning { - res, err = txQ.DefaultsTest.runUpdate(ctx, where, additional, assignments, selects, omits) + res, err = txQ.DefaultsTest.runUpdate(ctx, preds, cols, vals, selects, omits) } else { - res, err = txQ.DefaultsTest.runUpdateFallback(ctx, where, additional, assignments, selects, omits) + res, err = txQ.DefaultsTest.runUpdateFallback(ctx, preds, cols, vals, selects, omits) } return err }) @@ -1800,7 +2039,7 @@ func (d *DefaultsTestDelegate) runUpdate(ctx context.Context, where UniquePredic } returningCols := selectDefaultsTestCols(selects, omits, defaultsTestPKCols...) - query, setVals := d.buildUpdateSQL(allPreds, assignments, returningCols) + query, setVals := d.buildUpdateSQL(preds, cols, vals, returningCols) rows, err := d.client.query(ctx, query, setVals...) if err != nil { @@ -1832,8 +2071,8 @@ func (d *DefaultsTestDelegate) runUpdate(ctx context.Context, where UniquePredic return &res, nil } -func (d *DefaultsTestDelegate) execUpdateStmt(ctx context.Context, preds []PredicateOf[DefaultsTest], assignments []FieldAssignment) (int64, error) { - if len(assignments) == 0 { +func (d *DefaultsTestDelegate) execUpdateStmt(ctx context.Context, preds []PredicateOf[DefaultsTest], cols []string, vals []any) (int64, error) { + if len(cols) == 0 { return 0, nil } @@ -1845,7 +2084,7 @@ func (d *DefaultsTestDelegate) execUpdateStmt(ctx context.Context, preds []Predi } } - query, setVals := d.buildUpdateSQL(preds, assignments, nil) + query, setVals := d.buildUpdateSQL(preds, cols, vals, nil) result, err := d.client.exec(ctx, query, setVals...) if err != nil { return 0, err @@ -1853,16 +2092,15 @@ func (d *DefaultsTestDelegate) execUpdateStmt(ctx context.Context, preds []Predi return result.RowsAffected() } -func (d *DefaultsTestDelegate) runUpdateFallback(ctx context.Context, where UniquePredicate[DefaultsTest], additional []PredicateOf[DefaultsTest], assignments []FieldAssignment, selects *DefaultsTestSelect, omits *DefaultsTestOmit) (*DefaultsTest, error) { - allPreds := append([]PredicateOf[DefaultsTest]{where}, additional...) - affected, err := d.execUpdateStmt(ctx, allPreds, assignments) +func (d *DefaultsTestDelegate) runUpdateFallback(ctx context.Context, preds []PredicateOf[DefaultsTest], cols []string, vals []any, selects *DefaultsTestSelect, omits *DefaultsTestOmit) (*DefaultsTest, error) { + affected, err := d.execUpdateStmt(ctx, preds, cols, vals) if err != nil { return nil, err } if affected == 0 { return nil, sql.ErrNoRows } - return d.runFindUnique(ctx, allPreds, selects, omits) + return d.runFindUnique(ctx, preds, selects, omits) } // ----------------------------------------------------------------------------- @@ -1870,17 +2108,30 @@ func (d *DefaultsTestDelegate) runUpdateFallback(ctx context.Context, where Uniq // ----------------------------------------------------------------------------- func (d *DefaultsTestDelegate) executeUpdateMany(ctx context.Context, preds []PredicateOf[DefaultsTest], assignments []FieldAssignment) (int64, error) { + input, err := assignmentsToDefaultsTestUpdate(assignments) + if err != nil { + return 0, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.execUpdateStmt(ctx, preds, assignments) + return d.execUpdateStmt(ctx, preds, cols, vals) + } + + args := &DefaultsTestUpdateManyArgs{ + Where: preds, + Data: &input, } - curr := func(c context.Context, p []PredicateOf[DefaultsTest], a []FieldAssignment) (int64, error) { - return d.execUpdateStmt(c, p, a) + curr := func(c context.Context, a *DefaultsTestUpdateManyArgs) (int64, error) { + extCols, extVals := a.Data.ToColsVals() + return d.execUpdateStmt(c, a.Where, extCols, extVals) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.UpdateMany != nil { - return ext.UpdateMany(ctx, preds, assignments, curr) + return ext.UpdateMany(ctx, args, curr) } } @@ -1888,13 +2139,13 @@ func (d *DefaultsTestDelegate) executeUpdateMany(ctx context.Context, preds []Pr ext := d.extensions[i] if ext.UpdateMany != nil { next, hook := curr, ext.UpdateMany - curr = func(c context.Context, p []PredicateOf[DefaultsTest], a []FieldAssignment) (int64, error) { - return hook(c, p, a, next) + curr = func(c context.Context, a *DefaultsTestUpdateManyArgs) (int64, error) { + return hook(c, a, next) } } } - return curr(ctx, preds, assignments) + return curr(ctx, args) } // ----------------------------------------------------------------------------- @@ -1902,21 +2153,35 @@ func (d *DefaultsTestDelegate) executeUpdateMany(ctx context.Context, preds []Pr // ----------------------------------------------------------------------------- func (d *DefaultsTestDelegate) executeUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[DefaultsTest], assignments []FieldAssignment, selects *DefaultsTestSelect, omits *DefaultsTestOmit) ([]*DefaultsTest, error) { + input, err := assignmentsToDefaultsTestUpdate(assignments) + if err != nil { + return nil, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.runUpdateManyAndReturn(ctx, preds, assignments, selects, omits) + return d.runUpdateManyAndReturn(ctx, preds, cols, vals, selects, omits) } if selects == nil || !selects.hasAnySelected() { selects = fullDefaultsTestSelect() } - curr := func(c context.Context, p []PredicateOf[DefaultsTest], a []FieldAssignment, s *DefaultsTestSelect, o *DefaultsTestOmit) ([]*DefaultsTest, error) { - return d.runUpdateManyAndReturn(c, p, a, s, o) + args := &DefaultsTestUpdateManyAndReturnArgs{ + Where: preds, + Data: &input, + Select: selects, + } + + curr := func(c context.Context, a *DefaultsTestUpdateManyAndReturnArgs) ([]*DefaultsTest, error) { + extCols, extVals := a.Data.ToColsVals() + return d.runUpdateManyAndReturn(c, a.Where, extCols, extVals, a.Select, omits) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.UpdateManyAndReturn != nil { - return ext.UpdateManyAndReturn(ctx, preds, assignments, selects, omits, curr) + return ext.UpdateManyAndReturn(ctx, args, curr) } } @@ -1924,17 +2189,17 @@ func (d *DefaultsTestDelegate) executeUpdateManyAndReturn(ctx context.Context, p ext := d.extensions[i] if ext.UpdateManyAndReturn != nil { next, hook := curr, ext.UpdateManyAndReturn - curr = func(c context.Context, p []PredicateOf[DefaultsTest], a []FieldAssignment, s *DefaultsTestSelect, o *DefaultsTestOmit) ([]*DefaultsTest, error) { - return hook(c, p, a, s, o, next) + curr = func(c context.Context, a *DefaultsTestUpdateManyAndReturnArgs) ([]*DefaultsTest, error) { + return hook(c, a, next) } } } - return curr(ctx, preds, assignments, selects, omits) + return curr(ctx, args) } -func (d *DefaultsTestDelegate) runUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[DefaultsTest], assignments []FieldAssignment, selects *DefaultsTestSelect, omits *DefaultsTestOmit) ([]*DefaultsTest, error) { - if len(assignments) == 0 { +func (d *DefaultsTestDelegate) runUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[DefaultsTest], cols []string, vals []any, selects *DefaultsTestSelect, omits *DefaultsTestOmit) ([]*DefaultsTest, error) { + if len(cols) == 0 { return d.runFindMany(ctx, QueryParams[DefaultsTest]{Where: preds}, selects, omits) } @@ -1954,9 +2219,9 @@ func (d *DefaultsTestDelegate) runUpdateManyAndReturn(ctx context.Context, preds err := d.client.transaction(ctx, func(txQ *Queries) error { var err error if d.client.dialect.SupportsUpdateReturning { - res, err = txQ.DefaultsTest.runUpdateManyAndReturn(ctx, preds, assignments, selects, omits) + res, err = txQ.DefaultsTest.runUpdateManyAndReturn(ctx, preds, cols, vals, selects, omits) } else { - res, err = txQ.DefaultsTest.runUpdateManyAndReturnFallback(ctx, preds, assignments, selects, omits) + res, err = txQ.DefaultsTest.runUpdateManyAndReturnFallback(ctx, preds, cols, vals, selects, omits) } return err }) @@ -1964,39 +2229,29 @@ func (d *DefaultsTestDelegate) runUpdateManyAndReturn(ctx context.Context, preds } returningCols := selectDefaultsTestCols(selects, omits, defaultsTestPKCols...) - query, setVals := d.buildUpdateSQL(preds, assignments, returningCols) + query, setVals := d.buildUpdateSQL(preds, cols, vals, returningCols) rows, err := d.client.query(ctx, query, setVals...) if err != nil { return nil, err } - results := make([]*DefaultsTest, 0) - for rows.Next() { - var res DefaultsTest - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - rows.Close() - return nil, err - } - results = append(results, &res) - } - rowsErr := rows.Err() - rows.Close() - if rowsErr != nil { - return nil, rowsErr + scanned, err := scanDefaultsTestRows(rows, returningCols) + if err != nil { + return nil, err } if selects != nil && selects.hasAnyRelation() { - if err := d.loadRelations(ctx, results, selects); err != nil { + if err := d.loadRelations(ctx, scanned, selects); err != nil { return nil, err } } - return results, nil + return scanned, nil } -func (d *DefaultsTestDelegate) runUpdateManyAndReturnFallback(ctx context.Context, preds []PredicateOf[DefaultsTest], assignments []FieldAssignment, selects *DefaultsTestSelect, omits *DefaultsTestOmit) ([]*DefaultsTest, error) { - affected, err := d.execUpdateStmt(ctx, preds, assignments) +func (d *DefaultsTestDelegate) runUpdateManyAndReturnFallback(ctx context.Context, preds []PredicateOf[DefaultsTest], cols []string, vals []any, selects *DefaultsTestSelect, omits *DefaultsTestOmit) ([]*DefaultsTest, error) { + affected, err := d.execUpdateStmt(ctx, preds, cols, vals) if err != nil { return nil, err } diff --git a/integration/valk/post.go b/integration/valk/post.go index 09e74f7..206eac4 100644 --- a/integration/valk/post.go +++ b/integration/valk/post.go @@ -52,6 +52,100 @@ func (s *PostCreate) colMask() uint64 { return mask } +// PostUpdate contains model input fields for Post update operations. +type PostUpdate struct { + Id *string `json:"id"` + Title *string `json:"title"` + Content *string `json:"content"` + Published *bool `json:"published"` + AuthorId *string `json:"authorId"` +} + +func (u *PostUpdate) ToColsVals() ([]string, []any) { + var cols []string + var vals []any + if u.Id != nil { + cols = append(cols, "id") + vals = append(vals, u.Id) + } + if u.Title != nil { + cols = append(cols, "title") + vals = append(vals, u.Title) + } + if u.Content != nil { + cols = append(cols, "content") + vals = append(vals, u.Content) + } + if u.Published != nil { + cols = append(cols, "published") + vals = append(vals, u.Published) + } + if u.AuthorId != nil { + cols = append(cols, "authorId") + vals = append(vals, u.AuthorId) + } + return cols, vals +} + +func assignmentsToPostUpdate(assignments []FieldAssignment) (PostUpdate, error) { + var input PostUpdate + var errs ValidationError + + for _, a := range assignments { + switch a.Col { + case "id": + if v, ok := a.Val.(string); ok { + input.Id = &v + errs.ValidateString("id", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.Id = v + } else { + errs.Add("id", a.Val, "type", "field id must be of type string") + } + case "title": + if v, ok := a.Val.(string); ok { + input.Title = &v + errs.ValidateString("title", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.Title = v + } else { + errs.Add("title", a.Val, "type", "field title must be of type string") + } + case "content": + if v, ok := a.Val.(string); ok { + input.Content = &v + errs.ValidateString("content", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.Content = v + } else { + errs.Add("content", a.Val, "type", "field content must be of type string") + } + case "published": + if v, ok := a.Val.(bool); ok { + input.Published = &v + } else if v, ok := a.Val.(*bool); ok { + input.Published = v + } else { + errs.Add("published", a.Val, "type", "field published must be of type bool") + } + case "authorId": + if v, ok := a.Val.(string); ok { + input.AuthorId = &v + errs.ValidateString("authorId", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.AuthorId = v + } else { + errs.Add("authorId", a.Val, "type", "field authorId must be of type string") + } + } + } + + if errs.HasErrors() { + return input, errs + } + return input, nil +} + // PostSelect specifies which scalar and relation fields to select for Post. // // Selectable fields: @@ -417,6 +511,51 @@ func (a *PostDeleteManyArgs) SetWhere(preds ...PredicateOf[Post]) *PostDeleteMan return a } +// PostUpdateArgs is the input argument passed to Post Update extension hooks. +type PostUpdateArgs struct { + // Where contains all query filter predicates (merged primary unique constraint and additional predicates). + Where []PredicateOf[Post] + // Data contains the model fields to update. + Data *PostUpdate + // Select specifies which scalar and relation fields to select and return upon update. + Select *PostSelect +} + +func (a *PostUpdateArgs) SetWhere(unique UniquePredicate[Post], additional ...PredicateOf[Post]) *PostUpdateArgs { + a.Where = make([]PredicateOf[Post], 0, 1+len(additional)) + a.Where = append(a.Where, unique) + a.Where = append(a.Where, additional...) + return a +} + +// PostUpdateManyArgs is the input argument passed to Post UpdateMany extension hooks. +type PostUpdateManyArgs struct { + // Where contains all query filter predicates. + Where []PredicateOf[Post] + // Data contains the model fields to update. + Data *PostUpdate +} + +func (a *PostUpdateManyArgs) SetWhere(preds ...PredicateOf[Post]) *PostUpdateManyArgs { + a.Where = preds + return a +} + +// PostUpdateManyAndReturnArgs is the input argument passed to Post UpdateManyAndReturn extension hooks. +type PostUpdateManyAndReturnArgs struct { + // Where contains all query filter predicates. + Where []PredicateOf[Post] + // Data contains the model fields to update. + Data *PostUpdate + // Select specifies which scalar and relation fields to select and return upon update. + Select *PostSelect +} + +func (a *PostUpdateManyAndReturnArgs) SetWhere(preds ...PredicateOf[Post]) *PostUpdateManyAndReturnArgs { + a.Where = preds + return a +} + type PostCreateQuery = func(ctx context.Context, args *PostCreateArgs) (*Post, error) type PostCreateManyQuery = func(ctx context.Context, args *PostCreateManyArgs) (int64, error) type PostCreateManyAndReturnQuery = func(ctx context.Context, args *PostCreateManyAndReturnArgs) ([]*Post, error) @@ -426,9 +565,9 @@ type PostFindManyQuery = func(ctx context.Context, args *PostFindManyArgs) ([]*P type PostDeleteQuery = func(ctx context.Context, args *PostDeleteArgs) (*Post, error) type PostDeleteManyQuery = func(ctx context.Context, args *PostDeleteManyArgs) (int64, error) type PostCountQuery = func(ctx context.Context, args *PostCountArgs) (int64, error) -type PostUpdateQuery = func(ctx context.Context, where UniquePredicate[Post], additional []PredicateOf[Post], assignments []FieldAssignment, selects *PostSelect, omits *PostOmit) (*Post, error) -type PostUpdateManyQuery = func(ctx context.Context, preds []PredicateOf[Post], assignments []FieldAssignment) (int64, error) -type PostUpdateManyAndReturnQuery = func(ctx context.Context, preds []PredicateOf[Post], assignments []FieldAssignment, selects *PostSelect, omits *PostOmit) ([]*Post, error) +type PostUpdateQuery = func(ctx context.Context, args *PostUpdateArgs) (*Post, error) +type PostUpdateManyQuery = func(ctx context.Context, args *PostUpdateManyArgs) (int64, error) +type PostUpdateManyAndReturnQuery = func(ctx context.Context, args *PostUpdateManyAndReturnArgs) ([]*Post, error) type PostExtension struct { Create func(ctx context.Context, args *PostCreateArgs, next PostCreateQuery) (*Post, error) @@ -440,9 +579,9 @@ type PostExtension struct { Delete func(ctx context.Context, args *PostDeleteArgs, next PostDeleteQuery) (*Post, error) DeleteMany func(ctx context.Context, args *PostDeleteManyArgs, next PostDeleteManyQuery) (int64, error) Count func(ctx context.Context, args *PostCountArgs, next PostCountQuery) (int64, error) - Update func(ctx context.Context, where UniquePredicate[Post], additional []PredicateOf[Post], assignments []FieldAssignment, selects *PostSelect, omits *PostOmit, next PostUpdateQuery) (*Post, error) - UpdateMany func(ctx context.Context, preds []PredicateOf[Post], assignments []FieldAssignment, next PostUpdateManyQuery) (int64, error) - UpdateManyAndReturn func(ctx context.Context, preds []PredicateOf[Post], assignments []FieldAssignment, selects *PostSelect, omits *PostOmit, next PostUpdateManyAndReturnQuery) ([]*Post, error) + Update func(ctx context.Context, args *PostUpdateArgs, next PostUpdateQuery) (*Post, error) + UpdateMany func(ctx context.Context, args *PostUpdateManyArgs, next PostUpdateManyQuery) (int64, error) + UpdateManyAndReturn func(ctx context.Context, args *PostUpdateManyAndReturnArgs, next PostUpdateManyAndReturnQuery) ([]*Post, error) } type PostDelegate struct { @@ -526,6 +665,16 @@ type PostCreateBuilder struct { *CreateBuilder[Post, PostSelect, PostOmit] } +func (b *PostCreateBuilder) Select(s PostSelect) *PostCreateBuilder { + b.selects = &s + return b +} + +func (b *PostCreateBuilder) Omit(o PostOmit) *PostCreateBuilder { + b.omits = &o + return b +} + func (b *PostCreateBuilder) OnConflict(target UniqueConstraintTarget) *PostConflictBuilder[PostCreateBuilder] { return &PostConflictBuilder[PostCreateBuilder]{ builder: b, @@ -701,23 +850,11 @@ func (d *PostDelegate) executeCreate(ctx context.Context, assignments []FieldAss return nil, err } + cols, vals := input.ToColsVals() + returningCols := selectPostCols(selects, omits) + if len(d.extensions) == 0 { - cols, vals := input.ToColsVals() - returningCols := selectPostCols(selects, omits) - hasRelations := selects.hasAnyRelation() - if hasRelations { - var res *Post - err = d.client.transaction(ctx, func(txQ *Queries) error { - var err error - res, err = txQ.Post.runCreate(ctx, cols, vals, returningCols, postPKCols, conflictTarget, conflictAction) - if err != nil { - return err - } - return txQ.Post.loadRelations(ctx, []*Post{res}, selects) - }) - return res, err - } - return d.runCreate(ctx, cols, vals, returningCols, postPKCols, conflictTarget, conflictAction) + return d.runCreate(ctx, cols, vals, returningCols, selects, conflictTarget, conflictAction) } if selects == nil || !selects.hasAnySelected() { @@ -732,28 +869,9 @@ func (d *PostDelegate) executeCreate(ctx context.Context, assignments []FieldAss } curr := func(c context.Context, a *PostCreateArgs) (*Post, error) { - cols, vals := a.Data.ToColsVals() - returningCols := selectPostCols(a.Select, omits) - - hasRelations := a.Select.hasAnyRelation() - var res *Post - var err error - if hasRelations { - err = d.client.transaction(c, func(txQ *Queries) error { - var err error - res, err = txQ.Post.runCreate(c, cols, vals, returningCols, postPKCols, a.ConflictTarget, a.ConflictAction) - if err != nil { - return err - } - return txQ.Post.loadRelations(c, []*Post{res}, a.Select) - }) - } else { - res, err = d.runCreate(c, cols, vals, returningCols, postPKCols, a.ConflictTarget, a.ConflictAction) - } - if err != nil { - return nil, err - } - return res, nil + cCols, cVals := a.Data.ToColsVals() + cReturningCols := selectPostCols(a.Select, omits) + return d.runCreate(c, cCols, cVals, cReturningCols, a.Select, a.ConflictTarget, a.ConflictAction) } if len(d.extensions) == 1 { @@ -794,6 +912,16 @@ type PostCreateManyAndReturnBuilder struct { *CreateManyAndReturnBuilder[Post, PostSelect, PostOmit] } +func (b *PostCreateManyAndReturnBuilder) Select(s PostSelect) *PostCreateManyAndReturnBuilder { + b.selects = &s + return b +} + +func (b *PostCreateManyAndReturnBuilder) Omit(o PostOmit) *PostCreateManyAndReturnBuilder { + b.omits = &o + return b +} + func (b *PostCreateManyAndReturnBuilder) OnConflict(target UniqueConstraintTarget) *PostConflictBuilder[PostCreateManyAndReturnBuilder] { return &PostConflictBuilder[PostCreateManyAndReturnBuilder]{ builder: b, @@ -805,43 +933,51 @@ func (b *PostCreateManyAndReturnBuilder) OnConflict(target UniqueConstraintTarge } } -func (d *PostDelegate) CreateMany(builders ...*PostCreateBuilder) *PostCreateManyBuilder { +func createBuildersToPostRecordInputs(builders []*PostCreateBuilder) []RecordInput { records := make([]RecordInput, len(builders)) for i, b := range builders { records[i] = RecordInput{Assignments: b.assignments} } + return records +} + +func (d *PostDelegate) CreateMany(builders ...*PostCreateBuilder) *PostCreateManyBuilder { return &PostCreateManyBuilder{ CreateManyBuilder: &CreateManyBuilder[Post]{ - records: records, + records: createBuildersToPostRecordInputs(builders), execFunc: d.executeCreateMany, }, } } func (d *PostDelegate) CreateManyAndReturn(builders ...*PostCreateBuilder) *PostCreateManyAndReturnBuilder { - records := make([]RecordInput, len(builders)) - for i, b := range builders { - records[i] = RecordInput{Assignments: b.assignments} - } return &PostCreateManyAndReturnBuilder{ CreateManyAndReturnBuilder: &CreateManyAndReturnBuilder[Post, PostSelect, PostOmit]{ - records: records, + records: createBuildersToPostRecordInputs(builders), execFunc: d.executeCreateManyAndReturn, }, } } -func (d *PostDelegate) executeCreateMany(ctx context.Context, records []RecordInput, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { +func recordsToPostCreateInputs(records []RecordInput) ([]*PostCreate, error) { structs := make([]PostCreate, len(records)) inputs := make([]*PostCreate, len(records)) for i, rec := range records { var err error structs[i], err = assignmentsToPostCreate(rec.Assignments) if err != nil { - return 0, fmt.Errorf("validation failed at index %d: %w", i, err) + return nil, fmt.Errorf("validation failed at index %d: %w", i, err) } inputs[i] = &structs[i] } + return inputs, nil +} + +func (d *PostDelegate) executeCreateMany(ctx context.Context, records []RecordInput, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { + inputs, err := recordsToPostCreateInputs(records) + if err != nil { + return 0, err + } if len(d.extensions) == 0 { return d.runCreateMany(ctx, inputs, conflictTarget, conflictAction) @@ -877,31 +1013,12 @@ func (d *PostDelegate) executeCreateMany(ctx context.Context, records []RecordIn } func (d *PostDelegate) executeCreateManyAndReturn(ctx context.Context, records []RecordInput, selects *PostSelect, omits *PostOmit, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) ([]*Post, error) { - structs := make([]PostCreate, len(records)) - inputs := make([]*PostCreate, len(records)) - for i, rec := range records { - var err error - structs[i], err = assignmentsToPostCreate(rec.Assignments) - if err != nil { - return nil, fmt.Errorf("validation failed at index %d: %w", i, err) - } - inputs[i] = &structs[i] + inputs, err := recordsToPostCreateInputs(records) + if err != nil { + return nil, err } if len(d.extensions) == 0 { - hasRelations := selects != nil && selects.hasAnyRelation() - if hasRelations { - var res []*Post - err := d.client.transaction(ctx, func(txQ *Queries) error { - var err error - res, err = txQ.Post.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) - if err != nil { - return err - } - return txQ.Post.loadRelations(ctx, res, selects) - }) - return res, err - } return d.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) } @@ -917,19 +1034,6 @@ func (d *PostDelegate) executeCreateManyAndReturn(ctx context.Context, records [ } curr := func(c context.Context, a *PostCreateManyAndReturnArgs) ([]*Post, error) { - hasRelations := a.Select != nil && a.Select.hasAnyRelation() - if hasRelations { - var res []*Post - err := d.client.transaction(c, func(txQ *Queries) error { - var err error - res, err = txQ.Post.runCreateManyAndReturn(c, a.Data, a.Select, omits, a.ConflictTarget, a.ConflictAction) - if err != nil { - return err - } - return txQ.Post.loadRelations(c, res, a.Select) - }) - return res, err - } return d.runCreateManyAndReturn(c, a.Data, a.Select, omits, a.ConflictTarget, a.ConflictAction) } @@ -957,36 +1061,67 @@ func (d *PostDelegate) runCreate( cols []string, vals []any, returningCols []string, - pkCols []string, + selects *PostSelect, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction, ) (*Post, error) { - query, clauseArgs := buildSingleInsertSQL(d.client, "Post", cols, returningCols, pkCols, conflictTarget, conflictAction, len(vals)) + hasRelations := selects != nil && selects.hasAnyRelation() + useTx := hasRelations && !d.client.inTx() + + if useTx { + var res *Post + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + res, err = txQ.Post.runCreate(ctx, cols, vals, returningCols, selects, conflictTarget, conflictAction) + if err != nil { + return err + } + return txQ.Post.loadRelations(ctx, []*Post{res}, selects) + }) + return res, err + } + + query, clauseArgs := buildSingleInsertSQL(d.client, "Post", cols, returningCols, postPKCols, conflictTarget, conflictAction, len(vals)) if len(clauseArgs) > 0 { vals = append(vals, clauseArgs...) } - var res Post if d.client.dialect.SupportsInsertReturning { rows, err := d.client.query(ctx, query, vals...) if err != nil { return nil, err } - defer rows.Close() - if rows.Next() { - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + if !rows.Next() { + err := rows.Err() + rows.Close() + if err != nil { return nil, err } - return &res, nil + return nil, nil } - return nil, rows.Err() + + var res Post + scanErr := rows.Scan(res.ScanFields(returningCols)...) + rows.Close() + if scanErr != nil { + return nil, scanErr + } + + return &res, nil } - return d.runCreateFallback(ctx, query, vals, cols, returningCols, pkCols) + return d.runCreateFallback(ctx, query, vals, cols, returningCols, postPKCols) } -func (d *PostDelegate) runCreateFallback(ctx context.Context, query string, vals []any, cols []string, returningCols []string, pkCols []string) (*Post, error) { +func (d *PostDelegate) runCreateFallback( + ctx context.Context, + query string, + vals []any, + cols []string, + returningCols []string, + pkCols []string, +) (*Post, error) { result, err := d.client.exec(ctx, query, vals...) if err != nil { return nil, err @@ -1036,16 +1171,24 @@ func (d *PostDelegate) runCreateFallback(ctx context.Context, query string, vals if err != nil { return nil, err } - defer rows.Close() - var res Post - if rows.Next() { - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + if !rows.Next() { + err := rows.Err() + rows.Close() + if err != nil { return nil, err } - return &res, nil + return nil, nil } - return nil, rows.Err() + + var res Post + scanErr := rows.Scan(res.ScanFields(returningCols)...) + rows.Close() + if scanErr != nil { + return nil, scanErr + } + + return &res, nil } func (d *PostDelegate) buildBulkInsertSQL(q *Queries, batch []*PostCreate, paramStartIdx int) (cols []string, vals []any, queryStr string) { @@ -1123,6 +1266,41 @@ func (d *PostDelegate) buildBulkInsertSQL(q *Queries, batch []*PostCreate, param return cols, vals, queryStr } +func applyPostConflictClause(dialect Dialect, queryStr string, vals []any, cols []string, pkCols []string, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (string, []any) { + var conflictCols []string + if conflictTarget != nil { + conflictCols = conflictTarget.UniqueColumns() + } + var nonConflictCols []string + if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { + nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + } + clause, clauseArgs := dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) + queryStr += clause + if len(clauseArgs) > 0 { + vals = append(vals, clauseArgs...) + } + return queryStr, vals +} + +func scanPostRows(rows *sql.Rows, returningCols []string) ([]*Post, error) { + var records []*Post + for rows.Next() { + var res Post + if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + rows.Close() + return nil, err + } + records = append(records, &res) + } + if err := rows.Err(); err != nil { + rows.Close() + return nil, err + } + rows.Close() + return records, nil +} + func (d *PostDelegate) runCreateMany(ctx context.Context, inputs []*PostCreate, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { if len(inputs) == 0 { return 0, nil @@ -1133,18 +1311,7 @@ func (d *PostDelegate) runCreateMany(ctx context.Context, inputs []*PostCreate, var count int64 for _, batch := range batches { cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) - - var conflictCols []string - if conflictTarget != nil { - conflictCols = conflictTarget.UniqueColumns() - } - var nonConflictCols []string - if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, postPKCols) - } - clause, clauseArgs := d.client.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) - queryStr += clause - vals = append(vals, clauseArgs...) + queryStr, vals = applyPostConflictClause(d.client.dialect, queryStr, vals, cols, postPKCols, conflictTarget, conflictAction) result, err := d.client.exec(ctx, queryStr, vals...) if err != nil { @@ -1172,27 +1339,37 @@ func (d *PostDelegate) runCreateManyAndReturn( } batches := partitionPostInputs(d.client.dialect, inputs) - returningCols := selectPostCols(selects, omits) hasRelations := selects != nil && selects.hasAnyRelation() + useTx := (len(batches) > 1 || hasRelations || !d.client.dialect.SupportsInsertReturning) && !d.client.inTx() - recordsOut := make([]*Post, 0, len(inputs)) + if useTx { + var res []*Post + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + if txQ.dialect.SupportsInsertReturning { + res, err = txQ.Post.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) + } else { + res, err = txQ.Post.runCreateManyAndReturnFallback(ctx, inputs, selects, omits, conflictTarget, conflictAction) + } + if err != nil { + return err + } + if hasRelations { + return txQ.Post.loadRelations(ctx, res, selects) + } + return nil + }) + return res, err + } - runBatch := func(txQ *Queries, batch []*PostCreate) error { - cols, vals, queryStr := d.buildBulkInsertSQL(txQ, batch, 1) + returningCols := selectPostCols(selects, omits, postPKCols...) + recordsOut := make([]*Post, 0, len(inputs)) - var conflictCols []string - if conflictTarget != nil { - conflictCols = conflictTarget.UniqueColumns() - } - var nonConflictCols []string - if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, postPKCols) - } - clause, clauseArgs := txQ.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) - queryStr += clause - vals = append(vals, clauseArgs...) + for _, batch := range batches { + cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) + queryStr, vals = applyPostConflictClause(d.client.dialect, queryStr, vals, cols, postPKCols, conflictTarget, conflictAction) - if txQ.dialect.SupportsInsertReturning && len(returningCols) > 0 { + if len(returningCols) > 0 { var retSb strings.Builder retSb.Grow(12 + len(returningCols)*15) retSb.WriteString(" RETURNING ") @@ -1200,40 +1377,58 @@ func (d *PostDelegate) runCreateManyAndReturn( if i > 0 { retSb.WriteString(", ") } - txQ.dialect.WriteQuotedIdent(&retSb, col) + d.client.dialect.WriteQuotedIdent(&retSb, col) } queryStr += retSb.String() - rows, err := txQ.query(ctx, queryStr, vals...) - if err != nil { - return err - } - defer rows.Close() + } - for rows.Next() { - var res Post - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - return err - } - recordsOut = append(recordsOut, &res) - } - return rows.Err() + rows, err := d.client.query(ctx, queryStr, vals...) + if err != nil { + return nil, err } - // Fallback for dialects without RETURNING (MySQL) - result, err := txQ.exec(ctx, queryStr, vals...) + scanned, err := scanPostRows(rows, returningCols) if err != nil { - return err + return nil, err + } + recordsOut = append(recordsOut, scanned...) + } + + if selects != nil && selects.hasAnyRelation() { + if err := d.loadRelations(ctx, recordsOut, selects); err != nil { + return nil, err + } + } + + return recordsOut, nil +} + +func (d *PostDelegate) runCreateManyAndReturnFallback( + ctx context.Context, + inputs []*PostCreate, + selects *PostSelect, + omits *PostOmit, + conflictTarget UniqueConstraintTarget, + conflictAction *ConflictAction, +) ([]*Post, error) { + batches := partitionPostInputs(d.client.dialect, inputs) + returningCols := selectPostCols(selects, omits, postPKCols...) + recordsOut := make([]*Post, 0, len(inputs)) + + for _, batch := range batches { + cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) + queryStr, vals = applyPostConflictClause(d.client.dialect, queryStr, vals, cols, postPKCols, conflictTarget, conflictAction) + + result, err := d.client.exec(ctx, queryStr, vals...) + if err != nil { + return nil, err } - // We need to fetch the inserted records for this batch - // Note: MySQL bulk inserts only return the ID of the FIRST inserted row lastID, err := result.LastInsertId() if err != nil { - return err + return nil, err } - // Query back the rows by IDs (assuming autoincrement ID and single PK) - // If composite PK, it's more complex, but this is a standard fallback var selectSb strings.Builder selectSb.Grow(64 + len(returningCols)*15 + len("Post") + len(batch)*15) selectSb.WriteString("SELECT ") @@ -1241,55 +1436,29 @@ func (d *PostDelegate) runCreateManyAndReturn( if i > 0 { selectSb.WriteString(", ") } - txQ.dialect.WriteQuotedIdent(&selectSb, col) + d.client.dialect.WriteQuotedIdent(&selectSb, col) } selectSb.WriteString(" FROM ") - txQ.dialect.WriteQuotedIdent(&selectSb, "Post") + d.client.dialect.WriteQuotedIdent(&selectSb, "Post") selectSb.WriteString(" WHERE ") - txQ.dialect.WriteQuotedIdent(&selectSb, postPKCols[0]) + d.client.dialect.WriteQuotedIdent(&selectSb, postPKCols[0]) selectSb.WriteString(" >= ") - txQ.dialect.WritePlaceholder(&selectSb, 1) + d.client.dialect.WritePlaceholder(&selectSb, 1) selectSb.WriteString(" AND ") - txQ.dialect.WriteQuotedIdent(&selectSb, postPKCols[0]) + d.client.dialect.WriteQuotedIdent(&selectSb, postPKCols[0]) selectSb.WriteString(" < ") - txQ.dialect.WritePlaceholder(&selectSb, 2) + d.client.dialect.WritePlaceholder(&selectSb, 2) - rows, err := txQ.query(ctx, selectSb.String(), lastID, lastID+int64(len(batch))) + rows, err := d.client.query(ctx, selectSb.String(), lastID, lastID+int64(len(batch))) if err != nil { - return err - } - defer rows.Close() - - for rows.Next() { - var res Post - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - return err - } - recordsOut = append(recordsOut, &res) + return nil, err } - return rows.Err() - } - // Always wrap in transaction if we have multiple batches OR if we need to load relations - 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 { - return err - } - } - if hasRelations { - return txQ.Post.loadRelations(ctx, recordsOut, selects) - } - return nil - }) + scanned, err := scanPostRows(rows, returningCols) if err != nil { return nil, err } - } else { - if err := runBatch(d.client, batches[0]); err != nil { - return nil, err - } + recordsOut = append(recordsOut, scanned...) } return recordsOut, nil @@ -1478,23 +1647,23 @@ func (d *PostDelegate) UpdateManyAndReturn(preds ...PredicateOf[Post]) *PostUpda } } -func (d *PostDelegate) buildUpdateSQL(preds []PredicateOf[Post], assignments []FieldAssignment, returningCols []string) (string, []any) { - whereClause, predVals, _ := CompilePredicates(d.client.dialect, preds, len(assignments)+1) +func (d *PostDelegate) buildUpdateSQL(preds []PredicateOf[Post], cols []string, vals []any, returningCols []string) (string, []any) { + whereClause, predVals, _ := CompilePredicates(d.client.dialect, preds, len(cols)+1) var sb strings.Builder sb.WriteString("UPDATE ") d.client.dialect.WriteQuotedIdent(&sb, "Post") sb.WriteString(" SET ") - setVals := make([]any, 0, len(assignments)+len(predVals)) - for i, a := range assignments { + setVals := make([]any, 0, len(cols)+len(predVals)) + for i, col := range cols { if i > 0 { sb.WriteString(", ") } - d.client.dialect.WriteQuotedIdent(&sb, a.Col) + d.client.dialect.WriteQuotedIdent(&sb, col) sb.WriteString(" = ") d.client.dialect.WritePlaceholder(&sb, i+1) - setVals = append(setVals, a.Val) + setVals = append(setVals, vals[i]) } if whereClause != "" { @@ -1521,21 +1690,39 @@ func (d *PostDelegate) buildUpdateSQL(preds []PredicateOf[Post], assignments []F // ----------------------------------------------------------------------------- func (d *PostDelegate) executeUpdate(ctx context.Context, where UniquePredicate[Post], additional []PredicateOf[Post], assignments []FieldAssignment, selects *PostSelect, omits *PostOmit) (*Post, error) { + allWhere := make([]PredicateOf[Post], 0, 1+len(additional)) + allWhere = append(allWhere, where) + allWhere = append(allWhere, additional...) + + input, err := assignmentsToPostUpdate(assignments) + if err != nil { + return nil, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.runUpdate(ctx, where, additional, assignments, selects, omits) + return d.runUpdate(ctx, allWhere, cols, vals, selects, omits) } if selects == nil || !selects.hasAnySelected() { selects = fullPostSelect() } - curr := func(c context.Context, w UniquePredicate[Post], add []PredicateOf[Post], a []FieldAssignment, s *PostSelect, o *PostOmit) (*Post, error) { - return d.runUpdate(c, w, add, a, s, o) + args := &PostUpdateArgs{ + Where: allWhere, + Data: &input, + Select: selects, + } + + curr := func(c context.Context, a *PostUpdateArgs) (*Post, error) { + extCols, extVals := a.Data.ToColsVals() + return d.runUpdate(c, a.Where, extCols, extVals, a.Select, omits) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.Update != nil { - return ext.Update(ctx, where, additional, assignments, selects, omits, curr) + return ext.Update(ctx, args, curr) } } @@ -1543,25 +1730,21 @@ func (d *PostDelegate) executeUpdate(ctx context.Context, where UniquePredicate[ ext := d.extensions[i] if ext.Update != nil { next, hook := curr, ext.Update - curr = func(c context.Context, w UniquePredicate[Post], add []PredicateOf[Post], a []FieldAssignment, s *PostSelect, o *PostOmit) (*Post, error) { - return hook(c, w, add, a, s, o, next) + curr = func(c context.Context, a *PostUpdateArgs) (*Post, error) { + return hook(c, a, next) } } } - return curr(ctx, where, additional, assignments, selects, omits) + return curr(ctx, args) } -func (d *PostDelegate) runUpdate(ctx context.Context, where UniquePredicate[Post], additional []PredicateOf[Post], assignments []FieldAssignment, selects *PostSelect, omits *PostOmit) (*Post, error) { - allPreds := append([]PredicateOf[Post]{where}, additional...) - if len(assignments) == 0 { - return d.runFindUnique(ctx, allPreds, selects, omits) +func (d *PostDelegate) runUpdate(ctx context.Context, preds []PredicateOf[Post], cols []string, vals []any, selects *PostSelect, omits *PostOmit) (*Post, error) { + if len(cols) == 0 { + return d.runFindUnique(ctx, preds, selects, omits) } - if err := where.Validate(); err != nil { - return nil, err - } - for _, pr := range additional { + for _, pr := range preds { if pr != nil { if err := pr.Validate(); err != nil { return nil, err @@ -1577,9 +1760,9 @@ func (d *PostDelegate) runUpdate(ctx context.Context, where UniquePredicate[Post err := d.client.transaction(ctx, func(txQ *Queries) error { var err error if d.client.dialect.SupportsUpdateReturning { - res, err = txQ.Post.runUpdate(ctx, where, additional, assignments, selects, omits) + res, err = txQ.Post.runUpdate(ctx, preds, cols, vals, selects, omits) } else { - res, err = txQ.Post.runUpdateFallback(ctx, where, additional, assignments, selects, omits) + res, err = txQ.Post.runUpdateFallback(ctx, preds, cols, vals, selects, omits) } return err }) @@ -1587,7 +1770,7 @@ func (d *PostDelegate) runUpdate(ctx context.Context, where UniquePredicate[Post } returningCols := selectPostCols(selects, omits, postPKCols...) - query, setVals := d.buildUpdateSQL(allPreds, assignments, returningCols) + query, setVals := d.buildUpdateSQL(preds, cols, vals, returningCols) rows, err := d.client.query(ctx, query, setVals...) if err != nil { @@ -1619,8 +1802,8 @@ func (d *PostDelegate) runUpdate(ctx context.Context, where UniquePredicate[Post return &res, nil } -func (d *PostDelegate) execUpdateStmt(ctx context.Context, preds []PredicateOf[Post], assignments []FieldAssignment) (int64, error) { - if len(assignments) == 0 { +func (d *PostDelegate) execUpdateStmt(ctx context.Context, preds []PredicateOf[Post], cols []string, vals []any) (int64, error) { + if len(cols) == 0 { return 0, nil } @@ -1632,7 +1815,7 @@ func (d *PostDelegate) execUpdateStmt(ctx context.Context, preds []PredicateOf[P } } - query, setVals := d.buildUpdateSQL(preds, assignments, nil) + query, setVals := d.buildUpdateSQL(preds, cols, vals, nil) result, err := d.client.exec(ctx, query, setVals...) if err != nil { return 0, err @@ -1640,16 +1823,15 @@ func (d *PostDelegate) execUpdateStmt(ctx context.Context, preds []PredicateOf[P return result.RowsAffected() } -func (d *PostDelegate) runUpdateFallback(ctx context.Context, where UniquePredicate[Post], additional []PredicateOf[Post], assignments []FieldAssignment, selects *PostSelect, omits *PostOmit) (*Post, error) { - allPreds := append([]PredicateOf[Post]{where}, additional...) - affected, err := d.execUpdateStmt(ctx, allPreds, assignments) +func (d *PostDelegate) runUpdateFallback(ctx context.Context, preds []PredicateOf[Post], cols []string, vals []any, selects *PostSelect, omits *PostOmit) (*Post, error) { + affected, err := d.execUpdateStmt(ctx, preds, cols, vals) if err != nil { return nil, err } if affected == 0 { return nil, sql.ErrNoRows } - return d.runFindUnique(ctx, allPreds, selects, omits) + return d.runFindUnique(ctx, preds, selects, omits) } // ----------------------------------------------------------------------------- @@ -1657,17 +1839,30 @@ func (d *PostDelegate) runUpdateFallback(ctx context.Context, where UniquePredic // ----------------------------------------------------------------------------- func (d *PostDelegate) executeUpdateMany(ctx context.Context, preds []PredicateOf[Post], assignments []FieldAssignment) (int64, error) { + input, err := assignmentsToPostUpdate(assignments) + if err != nil { + return 0, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.execUpdateStmt(ctx, preds, assignments) + return d.execUpdateStmt(ctx, preds, cols, vals) } - curr := func(c context.Context, p []PredicateOf[Post], a []FieldAssignment) (int64, error) { - return d.execUpdateStmt(c, p, a) + args := &PostUpdateManyArgs{ + Where: preds, + Data: &input, + } + + curr := func(c context.Context, a *PostUpdateManyArgs) (int64, error) { + extCols, extVals := a.Data.ToColsVals() + return d.execUpdateStmt(c, a.Where, extCols, extVals) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.UpdateMany != nil { - return ext.UpdateMany(ctx, preds, assignments, curr) + return ext.UpdateMany(ctx, args, curr) } } @@ -1675,13 +1870,13 @@ func (d *PostDelegate) executeUpdateMany(ctx context.Context, preds []PredicateO ext := d.extensions[i] if ext.UpdateMany != nil { next, hook := curr, ext.UpdateMany - curr = func(c context.Context, p []PredicateOf[Post], a []FieldAssignment) (int64, error) { - return hook(c, p, a, next) + curr = func(c context.Context, a *PostUpdateManyArgs) (int64, error) { + return hook(c, a, next) } } } - return curr(ctx, preds, assignments) + return curr(ctx, args) } // ----------------------------------------------------------------------------- @@ -1689,21 +1884,35 @@ func (d *PostDelegate) executeUpdateMany(ctx context.Context, preds []PredicateO // ----------------------------------------------------------------------------- func (d *PostDelegate) executeUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[Post], assignments []FieldAssignment, selects *PostSelect, omits *PostOmit) ([]*Post, error) { + input, err := assignmentsToPostUpdate(assignments) + if err != nil { + return nil, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.runUpdateManyAndReturn(ctx, preds, assignments, selects, omits) + return d.runUpdateManyAndReturn(ctx, preds, cols, vals, selects, omits) } if selects == nil || !selects.hasAnySelected() { selects = fullPostSelect() } - curr := func(c context.Context, p []PredicateOf[Post], a []FieldAssignment, s *PostSelect, o *PostOmit) ([]*Post, error) { - return d.runUpdateManyAndReturn(c, p, a, s, o) + args := &PostUpdateManyAndReturnArgs{ + Where: preds, + Data: &input, + Select: selects, + } + + curr := func(c context.Context, a *PostUpdateManyAndReturnArgs) ([]*Post, error) { + extCols, extVals := a.Data.ToColsVals() + return d.runUpdateManyAndReturn(c, a.Where, extCols, extVals, a.Select, omits) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.UpdateManyAndReturn != nil { - return ext.UpdateManyAndReturn(ctx, preds, assignments, selects, omits, curr) + return ext.UpdateManyAndReturn(ctx, args, curr) } } @@ -1711,17 +1920,17 @@ func (d *PostDelegate) executeUpdateManyAndReturn(ctx context.Context, preds []P ext := d.extensions[i] if ext.UpdateManyAndReturn != nil { next, hook := curr, ext.UpdateManyAndReturn - curr = func(c context.Context, p []PredicateOf[Post], a []FieldAssignment, s *PostSelect, o *PostOmit) ([]*Post, error) { - return hook(c, p, a, s, o, next) + curr = func(c context.Context, a *PostUpdateManyAndReturnArgs) ([]*Post, error) { + return hook(c, a, next) } } } - return curr(ctx, preds, assignments, selects, omits) + return curr(ctx, args) } -func (d *PostDelegate) runUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[Post], assignments []FieldAssignment, selects *PostSelect, omits *PostOmit) ([]*Post, error) { - if len(assignments) == 0 { +func (d *PostDelegate) runUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[Post], cols []string, vals []any, selects *PostSelect, omits *PostOmit) ([]*Post, error) { + if len(cols) == 0 { return d.runFindMany(ctx, QueryParams[Post]{Where: preds}, selects, omits) } @@ -1741,9 +1950,9 @@ func (d *PostDelegate) runUpdateManyAndReturn(ctx context.Context, preds []Predi err := d.client.transaction(ctx, func(txQ *Queries) error { var err error if d.client.dialect.SupportsUpdateReturning { - res, err = txQ.Post.runUpdateManyAndReturn(ctx, preds, assignments, selects, omits) + res, err = txQ.Post.runUpdateManyAndReturn(ctx, preds, cols, vals, selects, omits) } else { - res, err = txQ.Post.runUpdateManyAndReturnFallback(ctx, preds, assignments, selects, omits) + res, err = txQ.Post.runUpdateManyAndReturnFallback(ctx, preds, cols, vals, selects, omits) } return err }) @@ -1751,39 +1960,29 @@ func (d *PostDelegate) runUpdateManyAndReturn(ctx context.Context, preds []Predi } returningCols := selectPostCols(selects, omits, postPKCols...) - query, setVals := d.buildUpdateSQL(preds, assignments, returningCols) + query, setVals := d.buildUpdateSQL(preds, cols, vals, returningCols) rows, err := d.client.query(ctx, query, setVals...) if err != nil { return nil, err } - results := make([]*Post, 0) - for rows.Next() { - var res Post - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - rows.Close() - return nil, err - } - results = append(results, &res) - } - rowsErr := rows.Err() - rows.Close() - if rowsErr != nil { - return nil, rowsErr + scanned, err := scanPostRows(rows, returningCols) + if err != nil { + return nil, err } if selects != nil && selects.hasAnyRelation() { - if err := d.loadRelations(ctx, results, selects); err != nil { + if err := d.loadRelations(ctx, scanned, selects); err != nil { return nil, err } } - return results, nil + return scanned, nil } -func (d *PostDelegate) runUpdateManyAndReturnFallback(ctx context.Context, preds []PredicateOf[Post], assignments []FieldAssignment, selects *PostSelect, omits *PostOmit) ([]*Post, error) { - affected, err := d.execUpdateStmt(ctx, preds, assignments) +func (d *PostDelegate) runUpdateManyAndReturnFallback(ctx context.Context, preds []PredicateOf[Post], cols []string, vals []any, selects *PostSelect, omits *PostOmit) ([]*Post, error) { + affected, err := d.execUpdateStmt(ctx, preds, cols, vals) if err != nil { return nil, err } diff --git a/integration/valk/profile.go b/integration/valk/profile.go index b12325f..3885953 100644 --- a/integration/valk/profile.go +++ b/integration/valk/profile.go @@ -45,6 +45,86 @@ func (s *ProfileCreate) colMask() uint64 { return mask } +// ProfileUpdate contains model input fields for Profile update operations. +type ProfileUpdate struct { + Id *string `json:"id"` + Bio *string `json:"bio"` + UserId *string `json:"userId"` + CreatedAt *time.Time `json:"createdAt"` +} + +func (u *ProfileUpdate) ToColsVals() ([]string, []any) { + var cols []string + var vals []any + if u.Id != nil { + cols = append(cols, "id") + vals = append(vals, u.Id) + } + if u.Bio != nil { + cols = append(cols, "bio") + vals = append(vals, u.Bio) + } + if u.UserId != nil { + cols = append(cols, "userId") + vals = append(vals, u.UserId) + } + if u.CreatedAt != nil { + cols = append(cols, "createdAt") + vals = append(vals, u.CreatedAt) + } + return cols, vals +} + +func assignmentsToProfileUpdate(assignments []FieldAssignment) (ProfileUpdate, error) { + var input ProfileUpdate + var errs ValidationError + + for _, a := range assignments { + switch a.Col { + case "id": + if v, ok := a.Val.(string); ok { + input.Id = &v + errs.ValidateString("id", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.Id = v + } else { + errs.Add("id", a.Val, "type", "field id must be of type string") + } + case "bio": + if v, ok := a.Val.(string); ok { + input.Bio = &v + errs.ValidateString("bio", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.Bio = v + } else { + errs.Add("bio", a.Val, "type", "field bio must be of type string") + } + case "userId": + if v, ok := a.Val.(string); ok { + input.UserId = &v + errs.ValidateString("userId", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.UserId = v + } else { + errs.Add("userId", a.Val, "type", "field userId must be of type string") + } + case "createdAt": + if v, ok := a.Val.(time.Time); ok { + input.CreatedAt = &v + } else if v, ok := a.Val.(*time.Time); ok { + input.CreatedAt = v + } else { + errs.Add("createdAt", a.Val, "type", "field createdAt must be of type time.Time") + } + } + } + + if errs.HasErrors() { + return input, errs + } + return input, nil +} + // ProfileSelect specifies which scalar and relation fields to select for Profile. // // Selectable fields: @@ -395,6 +475,51 @@ func (a *ProfileDeleteManyArgs) SetWhere(preds ...PredicateOf[Profile]) *Profile return a } +// ProfileUpdateArgs is the input argument passed to Profile Update extension hooks. +type ProfileUpdateArgs struct { + // Where contains all query filter predicates (merged primary unique constraint and additional predicates). + Where []PredicateOf[Profile] + // Data contains the model fields to update. + Data *ProfileUpdate + // Select specifies which scalar and relation fields to select and return upon update. + Select *ProfileSelect +} + +func (a *ProfileUpdateArgs) SetWhere(unique UniquePredicate[Profile], additional ...PredicateOf[Profile]) *ProfileUpdateArgs { + a.Where = make([]PredicateOf[Profile], 0, 1+len(additional)) + a.Where = append(a.Where, unique) + a.Where = append(a.Where, additional...) + return a +} + +// ProfileUpdateManyArgs is the input argument passed to Profile UpdateMany extension hooks. +type ProfileUpdateManyArgs struct { + // Where contains all query filter predicates. + Where []PredicateOf[Profile] + // Data contains the model fields to update. + Data *ProfileUpdate +} + +func (a *ProfileUpdateManyArgs) SetWhere(preds ...PredicateOf[Profile]) *ProfileUpdateManyArgs { + a.Where = preds + return a +} + +// ProfileUpdateManyAndReturnArgs is the input argument passed to Profile UpdateManyAndReturn extension hooks. +type ProfileUpdateManyAndReturnArgs struct { + // Where contains all query filter predicates. + Where []PredicateOf[Profile] + // Data contains the model fields to update. + Data *ProfileUpdate + // Select specifies which scalar and relation fields to select and return upon update. + Select *ProfileSelect +} + +func (a *ProfileUpdateManyAndReturnArgs) SetWhere(preds ...PredicateOf[Profile]) *ProfileUpdateManyAndReturnArgs { + a.Where = preds + return a +} + type ProfileCreateQuery = func(ctx context.Context, args *ProfileCreateArgs) (*Profile, error) type ProfileCreateManyQuery = func(ctx context.Context, args *ProfileCreateManyArgs) (int64, error) type ProfileCreateManyAndReturnQuery = func(ctx context.Context, args *ProfileCreateManyAndReturnArgs) ([]*Profile, error) @@ -404,9 +529,9 @@ type ProfileFindManyQuery = func(ctx context.Context, args *ProfileFindManyArgs) type ProfileDeleteQuery = func(ctx context.Context, args *ProfileDeleteArgs) (*Profile, error) type ProfileDeleteManyQuery = func(ctx context.Context, args *ProfileDeleteManyArgs) (int64, error) type ProfileCountQuery = func(ctx context.Context, args *ProfileCountArgs) (int64, error) -type ProfileUpdateQuery = func(ctx context.Context, where UniquePredicate[Profile], additional []PredicateOf[Profile], assignments []FieldAssignment, selects *ProfileSelect, omits *ProfileOmit) (*Profile, error) -type ProfileUpdateManyQuery = func(ctx context.Context, preds []PredicateOf[Profile], assignments []FieldAssignment) (int64, error) -type ProfileUpdateManyAndReturnQuery = func(ctx context.Context, preds []PredicateOf[Profile], assignments []FieldAssignment, selects *ProfileSelect, omits *ProfileOmit) ([]*Profile, error) +type ProfileUpdateQuery = func(ctx context.Context, args *ProfileUpdateArgs) (*Profile, error) +type ProfileUpdateManyQuery = func(ctx context.Context, args *ProfileUpdateManyArgs) (int64, error) +type ProfileUpdateManyAndReturnQuery = func(ctx context.Context, args *ProfileUpdateManyAndReturnArgs) ([]*Profile, error) type ProfileExtension struct { Create func(ctx context.Context, args *ProfileCreateArgs, next ProfileCreateQuery) (*Profile, error) @@ -418,9 +543,9 @@ type ProfileExtension struct { Delete func(ctx context.Context, args *ProfileDeleteArgs, next ProfileDeleteQuery) (*Profile, error) DeleteMany func(ctx context.Context, args *ProfileDeleteManyArgs, next ProfileDeleteManyQuery) (int64, error) Count func(ctx context.Context, args *ProfileCountArgs, next ProfileCountQuery) (int64, error) - Update func(ctx context.Context, where UniquePredicate[Profile], additional []PredicateOf[Profile], assignments []FieldAssignment, selects *ProfileSelect, omits *ProfileOmit, next ProfileUpdateQuery) (*Profile, error) - UpdateMany func(ctx context.Context, preds []PredicateOf[Profile], assignments []FieldAssignment, next ProfileUpdateManyQuery) (int64, error) - UpdateManyAndReturn func(ctx context.Context, preds []PredicateOf[Profile], assignments []FieldAssignment, selects *ProfileSelect, omits *ProfileOmit, next ProfileUpdateManyAndReturnQuery) ([]*Profile, error) + Update func(ctx context.Context, args *ProfileUpdateArgs, next ProfileUpdateQuery) (*Profile, error) + UpdateMany func(ctx context.Context, args *ProfileUpdateManyArgs, next ProfileUpdateManyQuery) (int64, error) + UpdateManyAndReturn func(ctx context.Context, args *ProfileUpdateManyAndReturnArgs, next ProfileUpdateManyAndReturnQuery) ([]*Profile, error) } type ProfileDelegate struct { @@ -501,6 +626,16 @@ type ProfileCreateBuilder struct { *CreateBuilder[Profile, ProfileSelect, ProfileOmit] } +func (b *ProfileCreateBuilder) Select(s ProfileSelect) *ProfileCreateBuilder { + b.selects = &s + return b +} + +func (b *ProfileCreateBuilder) Omit(o ProfileOmit) *ProfileCreateBuilder { + b.omits = &o + return b +} + func (b *ProfileCreateBuilder) OnConflict(target UniqueConstraintTarget) *ProfileConflictBuilder[ProfileCreateBuilder] { return &ProfileConflictBuilder[ProfileCreateBuilder]{ builder: b, @@ -660,23 +795,11 @@ func (d *ProfileDelegate) executeCreate(ctx context.Context, assignments []Field return nil, err } + cols, vals := input.ToColsVals() + returningCols := selectProfileCols(selects, omits) + if len(d.extensions) == 0 { - cols, vals := input.ToColsVals() - returningCols := selectProfileCols(selects, omits) - hasRelations := selects.hasAnyRelation() - if hasRelations { - var res *Profile - err = d.client.transaction(ctx, func(txQ *Queries) error { - var err error - res, err = txQ.Profile.runCreate(ctx, cols, vals, returningCols, profilePKCols, conflictTarget, conflictAction) - if err != nil { - return err - } - return txQ.Profile.loadRelations(ctx, []*Profile{res}, selects) - }) - return res, err - } - return d.runCreate(ctx, cols, vals, returningCols, profilePKCols, conflictTarget, conflictAction) + return d.runCreate(ctx, cols, vals, returningCols, selects, conflictTarget, conflictAction) } if selects == nil || !selects.hasAnySelected() { @@ -691,28 +814,9 @@ func (d *ProfileDelegate) executeCreate(ctx context.Context, assignments []Field } curr := func(c context.Context, a *ProfileCreateArgs) (*Profile, error) { - cols, vals := a.Data.ToColsVals() - returningCols := selectProfileCols(a.Select, omits) - - hasRelations := a.Select.hasAnyRelation() - var res *Profile - var err error - if hasRelations { - err = d.client.transaction(c, func(txQ *Queries) error { - var err error - res, err = txQ.Profile.runCreate(c, cols, vals, returningCols, profilePKCols, a.ConflictTarget, a.ConflictAction) - if err != nil { - return err - } - return txQ.Profile.loadRelations(c, []*Profile{res}, a.Select) - }) - } else { - res, err = d.runCreate(c, cols, vals, returningCols, profilePKCols, a.ConflictTarget, a.ConflictAction) - } - if err != nil { - return nil, err - } - return res, nil + cCols, cVals := a.Data.ToColsVals() + cReturningCols := selectProfileCols(a.Select, omits) + return d.runCreate(c, cCols, cVals, cReturningCols, a.Select, a.ConflictTarget, a.ConflictAction) } if len(d.extensions) == 1 { @@ -753,6 +857,16 @@ type ProfileCreateManyAndReturnBuilder struct { *CreateManyAndReturnBuilder[Profile, ProfileSelect, ProfileOmit] } +func (b *ProfileCreateManyAndReturnBuilder) Select(s ProfileSelect) *ProfileCreateManyAndReturnBuilder { + b.selects = &s + return b +} + +func (b *ProfileCreateManyAndReturnBuilder) Omit(o ProfileOmit) *ProfileCreateManyAndReturnBuilder { + b.omits = &o + return b +} + func (b *ProfileCreateManyAndReturnBuilder) OnConflict(target UniqueConstraintTarget) *ProfileConflictBuilder[ProfileCreateManyAndReturnBuilder] { return &ProfileConflictBuilder[ProfileCreateManyAndReturnBuilder]{ builder: b, @@ -764,43 +878,51 @@ func (b *ProfileCreateManyAndReturnBuilder) OnConflict(target UniqueConstraintTa } } -func (d *ProfileDelegate) CreateMany(builders ...*ProfileCreateBuilder) *ProfileCreateManyBuilder { +func createBuildersToProfileRecordInputs(builders []*ProfileCreateBuilder) []RecordInput { records := make([]RecordInput, len(builders)) for i, b := range builders { records[i] = RecordInput{Assignments: b.assignments} } + return records +} + +func (d *ProfileDelegate) CreateMany(builders ...*ProfileCreateBuilder) *ProfileCreateManyBuilder { return &ProfileCreateManyBuilder{ CreateManyBuilder: &CreateManyBuilder[Profile]{ - records: records, + records: createBuildersToProfileRecordInputs(builders), execFunc: d.executeCreateMany, }, } } func (d *ProfileDelegate) CreateManyAndReturn(builders ...*ProfileCreateBuilder) *ProfileCreateManyAndReturnBuilder { - records := make([]RecordInput, len(builders)) - for i, b := range builders { - records[i] = RecordInput{Assignments: b.assignments} - } return &ProfileCreateManyAndReturnBuilder{ CreateManyAndReturnBuilder: &CreateManyAndReturnBuilder[Profile, ProfileSelect, ProfileOmit]{ - records: records, + records: createBuildersToProfileRecordInputs(builders), execFunc: d.executeCreateManyAndReturn, }, } } -func (d *ProfileDelegate) executeCreateMany(ctx context.Context, records []RecordInput, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { +func recordsToProfileCreateInputs(records []RecordInput) ([]*ProfileCreate, error) { structs := make([]ProfileCreate, len(records)) inputs := make([]*ProfileCreate, len(records)) for i, rec := range records { var err error structs[i], err = assignmentsToProfileCreate(rec.Assignments) if err != nil { - return 0, fmt.Errorf("validation failed at index %d: %w", i, err) + return nil, fmt.Errorf("validation failed at index %d: %w", i, err) } inputs[i] = &structs[i] } + return inputs, nil +} + +func (d *ProfileDelegate) executeCreateMany(ctx context.Context, records []RecordInput, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { + inputs, err := recordsToProfileCreateInputs(records) + if err != nil { + return 0, err + } if len(d.extensions) == 0 { return d.runCreateMany(ctx, inputs, conflictTarget, conflictAction) @@ -836,31 +958,12 @@ func (d *ProfileDelegate) executeCreateMany(ctx context.Context, records []Recor } func (d *ProfileDelegate) executeCreateManyAndReturn(ctx context.Context, records []RecordInput, selects *ProfileSelect, omits *ProfileOmit, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) ([]*Profile, error) { - structs := make([]ProfileCreate, len(records)) - inputs := make([]*ProfileCreate, len(records)) - for i, rec := range records { - var err error - structs[i], err = assignmentsToProfileCreate(rec.Assignments) - if err != nil { - return nil, fmt.Errorf("validation failed at index %d: %w", i, err) - } - inputs[i] = &structs[i] + inputs, err := recordsToProfileCreateInputs(records) + if err != nil { + return nil, err } if len(d.extensions) == 0 { - hasRelations := selects != nil && selects.hasAnyRelation() - if hasRelations { - var res []*Profile - err := d.client.transaction(ctx, func(txQ *Queries) error { - var err error - res, err = txQ.Profile.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) - if err != nil { - return err - } - return txQ.Profile.loadRelations(ctx, res, selects) - }) - return res, err - } return d.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) } @@ -876,19 +979,6 @@ func (d *ProfileDelegate) executeCreateManyAndReturn(ctx context.Context, record } curr := func(c context.Context, a *ProfileCreateManyAndReturnArgs) ([]*Profile, error) { - hasRelations := a.Select != nil && a.Select.hasAnyRelation() - if hasRelations { - var res []*Profile - err := d.client.transaction(c, func(txQ *Queries) error { - var err error - res, err = txQ.Profile.runCreateManyAndReturn(c, a.Data, a.Select, omits, a.ConflictTarget, a.ConflictAction) - if err != nil { - return err - } - return txQ.Profile.loadRelations(c, res, a.Select) - }) - return res, err - } return d.runCreateManyAndReturn(c, a.Data, a.Select, omits, a.ConflictTarget, a.ConflictAction) } @@ -916,36 +1006,67 @@ func (d *ProfileDelegate) runCreate( cols []string, vals []any, returningCols []string, - pkCols []string, + selects *ProfileSelect, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction, ) (*Profile, error) { - query, clauseArgs := buildSingleInsertSQL(d.client, "Profile", cols, returningCols, pkCols, conflictTarget, conflictAction, len(vals)) + hasRelations := selects != nil && selects.hasAnyRelation() + useTx := hasRelations && !d.client.inTx() + + if useTx { + var res *Profile + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + res, err = txQ.Profile.runCreate(ctx, cols, vals, returningCols, selects, conflictTarget, conflictAction) + if err != nil { + return err + } + return txQ.Profile.loadRelations(ctx, []*Profile{res}, selects) + }) + return res, err + } + + query, clauseArgs := buildSingleInsertSQL(d.client, "Profile", cols, returningCols, profilePKCols, conflictTarget, conflictAction, len(vals)) if len(clauseArgs) > 0 { vals = append(vals, clauseArgs...) } - var res Profile if d.client.dialect.SupportsInsertReturning { rows, err := d.client.query(ctx, query, vals...) if err != nil { return nil, err } - defer rows.Close() - if rows.Next() { - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + if !rows.Next() { + err := rows.Err() + rows.Close() + if err != nil { return nil, err } - return &res, nil + return nil, nil + } + + var res Profile + scanErr := rows.Scan(res.ScanFields(returningCols)...) + rows.Close() + if scanErr != nil { + return nil, scanErr } - return nil, rows.Err() + + return &res, nil } - return d.runCreateFallback(ctx, query, vals, cols, returningCols, pkCols) + return d.runCreateFallback(ctx, query, vals, cols, returningCols, profilePKCols) } -func (d *ProfileDelegate) runCreateFallback(ctx context.Context, query string, vals []any, cols []string, returningCols []string, pkCols []string) (*Profile, error) { +func (d *ProfileDelegate) runCreateFallback( + ctx context.Context, + query string, + vals []any, + cols []string, + returningCols []string, + pkCols []string, +) (*Profile, error) { result, err := d.client.exec(ctx, query, vals...) if err != nil { return nil, err @@ -995,16 +1116,24 @@ func (d *ProfileDelegate) runCreateFallback(ctx context.Context, query string, v if err != nil { return nil, err } - defer rows.Close() - var res Profile - if rows.Next() { - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + if !rows.Next() { + err := rows.Err() + rows.Close() + if err != nil { return nil, err } - return &res, nil + return nil, nil } - return nil, rows.Err() + + var res Profile + scanErr := rows.Scan(res.ScanFields(returningCols)...) + rows.Close() + if scanErr != nil { + return nil, scanErr + } + + return &res, nil } func (d *ProfileDelegate) buildBulkInsertSQL(q *Queries, batch []*ProfileCreate, paramStartIdx int) (cols []string, vals []any, queryStr string) { @@ -1080,6 +1209,41 @@ func (d *ProfileDelegate) buildBulkInsertSQL(q *Queries, batch []*ProfileCreate, return cols, vals, queryStr } +func applyProfileConflictClause(dialect Dialect, queryStr string, vals []any, cols []string, pkCols []string, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (string, []any) { + var conflictCols []string + if conflictTarget != nil { + conflictCols = conflictTarget.UniqueColumns() + } + var nonConflictCols []string + if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { + nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + } + clause, clauseArgs := dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) + queryStr += clause + if len(clauseArgs) > 0 { + vals = append(vals, clauseArgs...) + } + return queryStr, vals +} + +func scanProfileRows(rows *sql.Rows, returningCols []string) ([]*Profile, error) { + var records []*Profile + for rows.Next() { + var res Profile + if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + rows.Close() + return nil, err + } + records = append(records, &res) + } + if err := rows.Err(); err != nil { + rows.Close() + return nil, err + } + rows.Close() + return records, nil +} + func (d *ProfileDelegate) runCreateMany(ctx context.Context, inputs []*ProfileCreate, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { if len(inputs) == 0 { return 0, nil @@ -1090,18 +1254,7 @@ func (d *ProfileDelegate) runCreateMany(ctx context.Context, inputs []*ProfileCr var count int64 for _, batch := range batches { cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) - - var conflictCols []string - if conflictTarget != nil { - conflictCols = conflictTarget.UniqueColumns() - } - var nonConflictCols []string - if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, profilePKCols) - } - clause, clauseArgs := d.client.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) - queryStr += clause - vals = append(vals, clauseArgs...) + queryStr, vals = applyProfileConflictClause(d.client.dialect, queryStr, vals, cols, profilePKCols, conflictTarget, conflictAction) result, err := d.client.exec(ctx, queryStr, vals...) if err != nil { @@ -1129,27 +1282,37 @@ func (d *ProfileDelegate) runCreateManyAndReturn( } batches := partitionProfileInputs(d.client.dialect, inputs) - returningCols := selectProfileCols(selects, omits) hasRelations := selects != nil && selects.hasAnyRelation() + useTx := (len(batches) > 1 || hasRelations || !d.client.dialect.SupportsInsertReturning) && !d.client.inTx() - recordsOut := make([]*Profile, 0, len(inputs)) + if useTx { + var res []*Profile + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + if txQ.dialect.SupportsInsertReturning { + res, err = txQ.Profile.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) + } else { + res, err = txQ.Profile.runCreateManyAndReturnFallback(ctx, inputs, selects, omits, conflictTarget, conflictAction) + } + if err != nil { + return err + } + if hasRelations { + return txQ.Profile.loadRelations(ctx, res, selects) + } + return nil + }) + return res, err + } - runBatch := func(txQ *Queries, batch []*ProfileCreate) error { - cols, vals, queryStr := d.buildBulkInsertSQL(txQ, batch, 1) + returningCols := selectProfileCols(selects, omits, profilePKCols...) + recordsOut := make([]*Profile, 0, len(inputs)) - var conflictCols []string - if conflictTarget != nil { - conflictCols = conflictTarget.UniqueColumns() - } - var nonConflictCols []string - if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, profilePKCols) - } - clause, clauseArgs := txQ.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) - queryStr += clause - vals = append(vals, clauseArgs...) + for _, batch := range batches { + cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) + queryStr, vals = applyProfileConflictClause(d.client.dialect, queryStr, vals, cols, profilePKCols, conflictTarget, conflictAction) - if txQ.dialect.SupportsInsertReturning && len(returningCols) > 0 { + if len(returningCols) > 0 { var retSb strings.Builder retSb.Grow(12 + len(returningCols)*15) retSb.WriteString(" RETURNING ") @@ -1157,40 +1320,58 @@ func (d *ProfileDelegate) runCreateManyAndReturn( if i > 0 { retSb.WriteString(", ") } - txQ.dialect.WriteQuotedIdent(&retSb, col) + d.client.dialect.WriteQuotedIdent(&retSb, col) } queryStr += retSb.String() - rows, err := txQ.query(ctx, queryStr, vals...) - if err != nil { - return err - } - defer rows.Close() + } - for rows.Next() { - var res Profile - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - return err - } - recordsOut = append(recordsOut, &res) - } - return rows.Err() + rows, err := d.client.query(ctx, queryStr, vals...) + if err != nil { + return nil, err } - // Fallback for dialects without RETURNING (MySQL) - result, err := txQ.exec(ctx, queryStr, vals...) + scanned, err := scanProfileRows(rows, returningCols) if err != nil { - return err + return nil, err + } + recordsOut = append(recordsOut, scanned...) + } + + if selects != nil && selects.hasAnyRelation() { + if err := d.loadRelations(ctx, recordsOut, selects); err != nil { + return nil, err + } + } + + return recordsOut, nil +} + +func (d *ProfileDelegate) runCreateManyAndReturnFallback( + ctx context.Context, + inputs []*ProfileCreate, + selects *ProfileSelect, + omits *ProfileOmit, + conflictTarget UniqueConstraintTarget, + conflictAction *ConflictAction, +) ([]*Profile, error) { + batches := partitionProfileInputs(d.client.dialect, inputs) + returningCols := selectProfileCols(selects, omits, profilePKCols...) + recordsOut := make([]*Profile, 0, len(inputs)) + + for _, batch := range batches { + cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) + queryStr, vals = applyProfileConflictClause(d.client.dialect, queryStr, vals, cols, profilePKCols, conflictTarget, conflictAction) + + result, err := d.client.exec(ctx, queryStr, vals...) + if err != nil { + return nil, err } - // We need to fetch the inserted records for this batch - // Note: MySQL bulk inserts only return the ID of the FIRST inserted row lastID, err := result.LastInsertId() if err != nil { - return err + return nil, err } - // Query back the rows by IDs (assuming autoincrement ID and single PK) - // If composite PK, it's more complex, but this is a standard fallback var selectSb strings.Builder selectSb.Grow(64 + len(returningCols)*15 + len("Profile") + len(batch)*15) selectSb.WriteString("SELECT ") @@ -1198,55 +1379,29 @@ func (d *ProfileDelegate) runCreateManyAndReturn( if i > 0 { selectSb.WriteString(", ") } - txQ.dialect.WriteQuotedIdent(&selectSb, col) + d.client.dialect.WriteQuotedIdent(&selectSb, col) } selectSb.WriteString(" FROM ") - txQ.dialect.WriteQuotedIdent(&selectSb, "Profile") + d.client.dialect.WriteQuotedIdent(&selectSb, "Profile") selectSb.WriteString(" WHERE ") - txQ.dialect.WriteQuotedIdent(&selectSb, profilePKCols[0]) + d.client.dialect.WriteQuotedIdent(&selectSb, profilePKCols[0]) selectSb.WriteString(" >= ") - txQ.dialect.WritePlaceholder(&selectSb, 1) + d.client.dialect.WritePlaceholder(&selectSb, 1) selectSb.WriteString(" AND ") - txQ.dialect.WriteQuotedIdent(&selectSb, profilePKCols[0]) + d.client.dialect.WriteQuotedIdent(&selectSb, profilePKCols[0]) selectSb.WriteString(" < ") - txQ.dialect.WritePlaceholder(&selectSb, 2) + d.client.dialect.WritePlaceholder(&selectSb, 2) - rows, err := txQ.query(ctx, selectSb.String(), lastID, lastID+int64(len(batch))) + rows, err := d.client.query(ctx, selectSb.String(), lastID, lastID+int64(len(batch))) if err != nil { - return err - } - defer rows.Close() - - for rows.Next() { - var res Profile - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - return err - } - recordsOut = append(recordsOut, &res) + return nil, err } - return rows.Err() - } - // Always wrap in transaction if we have multiple batches OR if we need to load relations - 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 { - return err - } - } - if hasRelations { - return txQ.Profile.loadRelations(ctx, recordsOut, selects) - } - return nil - }) + scanned, err := scanProfileRows(rows, returningCols) if err != nil { return nil, err } - } else { - if err := runBatch(d.client, batches[0]); err != nil { - return nil, err - } + recordsOut = append(recordsOut, scanned...) } return recordsOut, nil @@ -1419,23 +1574,23 @@ func (d *ProfileDelegate) UpdateManyAndReturn(preds ...PredicateOf[Profile]) *Pr } } -func (d *ProfileDelegate) buildUpdateSQL(preds []PredicateOf[Profile], assignments []FieldAssignment, returningCols []string) (string, []any) { - whereClause, predVals, _ := CompilePredicates(d.client.dialect, preds, len(assignments)+1) +func (d *ProfileDelegate) buildUpdateSQL(preds []PredicateOf[Profile], cols []string, vals []any, returningCols []string) (string, []any) { + whereClause, predVals, _ := CompilePredicates(d.client.dialect, preds, len(cols)+1) var sb strings.Builder sb.WriteString("UPDATE ") d.client.dialect.WriteQuotedIdent(&sb, "Profile") sb.WriteString(" SET ") - setVals := make([]any, 0, len(assignments)+len(predVals)) - for i, a := range assignments { + setVals := make([]any, 0, len(cols)+len(predVals)) + for i, col := range cols { if i > 0 { sb.WriteString(", ") } - d.client.dialect.WriteQuotedIdent(&sb, a.Col) + d.client.dialect.WriteQuotedIdent(&sb, col) sb.WriteString(" = ") d.client.dialect.WritePlaceholder(&sb, i+1) - setVals = append(setVals, a.Val) + setVals = append(setVals, vals[i]) } if whereClause != "" { @@ -1462,21 +1617,39 @@ func (d *ProfileDelegate) buildUpdateSQL(preds []PredicateOf[Profile], assignmen // ----------------------------------------------------------------------------- func (d *ProfileDelegate) executeUpdate(ctx context.Context, where UniquePredicate[Profile], additional []PredicateOf[Profile], assignments []FieldAssignment, selects *ProfileSelect, omits *ProfileOmit) (*Profile, error) { + allWhere := make([]PredicateOf[Profile], 0, 1+len(additional)) + allWhere = append(allWhere, where) + allWhere = append(allWhere, additional...) + + input, err := assignmentsToProfileUpdate(assignments) + if err != nil { + return nil, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.runUpdate(ctx, where, additional, assignments, selects, omits) + return d.runUpdate(ctx, allWhere, cols, vals, selects, omits) } if selects == nil || !selects.hasAnySelected() { selects = fullProfileSelect() } - curr := func(c context.Context, w UniquePredicate[Profile], add []PredicateOf[Profile], a []FieldAssignment, s *ProfileSelect, o *ProfileOmit) (*Profile, error) { - return d.runUpdate(c, w, add, a, s, o) + args := &ProfileUpdateArgs{ + Where: allWhere, + Data: &input, + Select: selects, + } + + curr := func(c context.Context, a *ProfileUpdateArgs) (*Profile, error) { + extCols, extVals := a.Data.ToColsVals() + return d.runUpdate(c, a.Where, extCols, extVals, a.Select, omits) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.Update != nil { - return ext.Update(ctx, where, additional, assignments, selects, omits, curr) + return ext.Update(ctx, args, curr) } } @@ -1484,25 +1657,21 @@ func (d *ProfileDelegate) executeUpdate(ctx context.Context, where UniquePredica ext := d.extensions[i] if ext.Update != nil { next, hook := curr, ext.Update - curr = func(c context.Context, w UniquePredicate[Profile], add []PredicateOf[Profile], a []FieldAssignment, s *ProfileSelect, o *ProfileOmit) (*Profile, error) { - return hook(c, w, add, a, s, o, next) + curr = func(c context.Context, a *ProfileUpdateArgs) (*Profile, error) { + return hook(c, a, next) } } } - return curr(ctx, where, additional, assignments, selects, omits) + return curr(ctx, args) } -func (d *ProfileDelegate) runUpdate(ctx context.Context, where UniquePredicate[Profile], additional []PredicateOf[Profile], assignments []FieldAssignment, selects *ProfileSelect, omits *ProfileOmit) (*Profile, error) { - allPreds := append([]PredicateOf[Profile]{where}, additional...) - if len(assignments) == 0 { - return d.runFindUnique(ctx, allPreds, selects, omits) +func (d *ProfileDelegate) runUpdate(ctx context.Context, preds []PredicateOf[Profile], cols []string, vals []any, selects *ProfileSelect, omits *ProfileOmit) (*Profile, error) { + if len(cols) == 0 { + return d.runFindUnique(ctx, preds, selects, omits) } - if err := where.Validate(); err != nil { - return nil, err - } - for _, pr := range additional { + for _, pr := range preds { if pr != nil { if err := pr.Validate(); err != nil { return nil, err @@ -1518,9 +1687,9 @@ func (d *ProfileDelegate) runUpdate(ctx context.Context, where UniquePredicate[P err := d.client.transaction(ctx, func(txQ *Queries) error { var err error if d.client.dialect.SupportsUpdateReturning { - res, err = txQ.Profile.runUpdate(ctx, where, additional, assignments, selects, omits) + res, err = txQ.Profile.runUpdate(ctx, preds, cols, vals, selects, omits) } else { - res, err = txQ.Profile.runUpdateFallback(ctx, where, additional, assignments, selects, omits) + res, err = txQ.Profile.runUpdateFallback(ctx, preds, cols, vals, selects, omits) } return err }) @@ -1528,7 +1697,7 @@ func (d *ProfileDelegate) runUpdate(ctx context.Context, where UniquePredicate[P } returningCols := selectProfileCols(selects, omits, profilePKCols...) - query, setVals := d.buildUpdateSQL(allPreds, assignments, returningCols) + query, setVals := d.buildUpdateSQL(preds, cols, vals, returningCols) rows, err := d.client.query(ctx, query, setVals...) if err != nil { @@ -1560,8 +1729,8 @@ func (d *ProfileDelegate) runUpdate(ctx context.Context, where UniquePredicate[P return &res, nil } -func (d *ProfileDelegate) execUpdateStmt(ctx context.Context, preds []PredicateOf[Profile], assignments []FieldAssignment) (int64, error) { - if len(assignments) == 0 { +func (d *ProfileDelegate) execUpdateStmt(ctx context.Context, preds []PredicateOf[Profile], cols []string, vals []any) (int64, error) { + if len(cols) == 0 { return 0, nil } @@ -1573,7 +1742,7 @@ func (d *ProfileDelegate) execUpdateStmt(ctx context.Context, preds []PredicateO } } - query, setVals := d.buildUpdateSQL(preds, assignments, nil) + query, setVals := d.buildUpdateSQL(preds, cols, vals, nil) result, err := d.client.exec(ctx, query, setVals...) if err != nil { return 0, err @@ -1581,16 +1750,15 @@ func (d *ProfileDelegate) execUpdateStmt(ctx context.Context, preds []PredicateO return result.RowsAffected() } -func (d *ProfileDelegate) runUpdateFallback(ctx context.Context, where UniquePredicate[Profile], additional []PredicateOf[Profile], assignments []FieldAssignment, selects *ProfileSelect, omits *ProfileOmit) (*Profile, error) { - allPreds := append([]PredicateOf[Profile]{where}, additional...) - affected, err := d.execUpdateStmt(ctx, allPreds, assignments) +func (d *ProfileDelegate) runUpdateFallback(ctx context.Context, preds []PredicateOf[Profile], cols []string, vals []any, selects *ProfileSelect, omits *ProfileOmit) (*Profile, error) { + affected, err := d.execUpdateStmt(ctx, preds, cols, vals) if err != nil { return nil, err } if affected == 0 { return nil, sql.ErrNoRows } - return d.runFindUnique(ctx, allPreds, selects, omits) + return d.runFindUnique(ctx, preds, selects, omits) } // ----------------------------------------------------------------------------- @@ -1598,17 +1766,30 @@ func (d *ProfileDelegate) runUpdateFallback(ctx context.Context, where UniquePre // ----------------------------------------------------------------------------- func (d *ProfileDelegate) executeUpdateMany(ctx context.Context, preds []PredicateOf[Profile], assignments []FieldAssignment) (int64, error) { + input, err := assignmentsToProfileUpdate(assignments) + if err != nil { + return 0, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.execUpdateStmt(ctx, preds, assignments) + return d.execUpdateStmt(ctx, preds, cols, vals) } - curr := func(c context.Context, p []PredicateOf[Profile], a []FieldAssignment) (int64, error) { - return d.execUpdateStmt(c, p, a) + args := &ProfileUpdateManyArgs{ + Where: preds, + Data: &input, + } + + curr := func(c context.Context, a *ProfileUpdateManyArgs) (int64, error) { + extCols, extVals := a.Data.ToColsVals() + return d.execUpdateStmt(c, a.Where, extCols, extVals) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.UpdateMany != nil { - return ext.UpdateMany(ctx, preds, assignments, curr) + return ext.UpdateMany(ctx, args, curr) } } @@ -1616,13 +1797,13 @@ func (d *ProfileDelegate) executeUpdateMany(ctx context.Context, preds []Predica ext := d.extensions[i] if ext.UpdateMany != nil { next, hook := curr, ext.UpdateMany - curr = func(c context.Context, p []PredicateOf[Profile], a []FieldAssignment) (int64, error) { - return hook(c, p, a, next) + curr = func(c context.Context, a *ProfileUpdateManyArgs) (int64, error) { + return hook(c, a, next) } } } - return curr(ctx, preds, assignments) + return curr(ctx, args) } // ----------------------------------------------------------------------------- @@ -1630,21 +1811,35 @@ func (d *ProfileDelegate) executeUpdateMany(ctx context.Context, preds []Predica // ----------------------------------------------------------------------------- func (d *ProfileDelegate) executeUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[Profile], assignments []FieldAssignment, selects *ProfileSelect, omits *ProfileOmit) ([]*Profile, error) { + input, err := assignmentsToProfileUpdate(assignments) + if err != nil { + return nil, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.runUpdateManyAndReturn(ctx, preds, assignments, selects, omits) + return d.runUpdateManyAndReturn(ctx, preds, cols, vals, selects, omits) } if selects == nil || !selects.hasAnySelected() { selects = fullProfileSelect() } - curr := func(c context.Context, p []PredicateOf[Profile], a []FieldAssignment, s *ProfileSelect, o *ProfileOmit) ([]*Profile, error) { - return d.runUpdateManyAndReturn(c, p, a, s, o) + args := &ProfileUpdateManyAndReturnArgs{ + Where: preds, + Data: &input, + Select: selects, + } + + curr := func(c context.Context, a *ProfileUpdateManyAndReturnArgs) ([]*Profile, error) { + extCols, extVals := a.Data.ToColsVals() + return d.runUpdateManyAndReturn(c, a.Where, extCols, extVals, a.Select, omits) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.UpdateManyAndReturn != nil { - return ext.UpdateManyAndReturn(ctx, preds, assignments, selects, omits, curr) + return ext.UpdateManyAndReturn(ctx, args, curr) } } @@ -1652,17 +1847,17 @@ func (d *ProfileDelegate) executeUpdateManyAndReturn(ctx context.Context, preds ext := d.extensions[i] if ext.UpdateManyAndReturn != nil { next, hook := curr, ext.UpdateManyAndReturn - curr = func(c context.Context, p []PredicateOf[Profile], a []FieldAssignment, s *ProfileSelect, o *ProfileOmit) ([]*Profile, error) { - return hook(c, p, a, s, o, next) + curr = func(c context.Context, a *ProfileUpdateManyAndReturnArgs) ([]*Profile, error) { + return hook(c, a, next) } } } - return curr(ctx, preds, assignments, selects, omits) + return curr(ctx, args) } -func (d *ProfileDelegate) runUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[Profile], assignments []FieldAssignment, selects *ProfileSelect, omits *ProfileOmit) ([]*Profile, error) { - if len(assignments) == 0 { +func (d *ProfileDelegate) runUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[Profile], cols []string, vals []any, selects *ProfileSelect, omits *ProfileOmit) ([]*Profile, error) { + if len(cols) == 0 { return d.runFindMany(ctx, QueryParams[Profile]{Where: preds}, selects, omits) } @@ -1682,9 +1877,9 @@ func (d *ProfileDelegate) runUpdateManyAndReturn(ctx context.Context, preds []Pr err := d.client.transaction(ctx, func(txQ *Queries) error { var err error if d.client.dialect.SupportsUpdateReturning { - res, err = txQ.Profile.runUpdateManyAndReturn(ctx, preds, assignments, selects, omits) + res, err = txQ.Profile.runUpdateManyAndReturn(ctx, preds, cols, vals, selects, omits) } else { - res, err = txQ.Profile.runUpdateManyAndReturnFallback(ctx, preds, assignments, selects, omits) + res, err = txQ.Profile.runUpdateManyAndReturnFallback(ctx, preds, cols, vals, selects, omits) } return err }) @@ -1692,39 +1887,29 @@ func (d *ProfileDelegate) runUpdateManyAndReturn(ctx context.Context, preds []Pr } returningCols := selectProfileCols(selects, omits, profilePKCols...) - query, setVals := d.buildUpdateSQL(preds, assignments, returningCols) + query, setVals := d.buildUpdateSQL(preds, cols, vals, returningCols) rows, err := d.client.query(ctx, query, setVals...) if err != nil { return nil, err } - results := make([]*Profile, 0) - for rows.Next() { - var res Profile - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - rows.Close() - return nil, err - } - results = append(results, &res) - } - rowsErr := rows.Err() - rows.Close() - if rowsErr != nil { - return nil, rowsErr + scanned, err := scanProfileRows(rows, returningCols) + if err != nil { + return nil, err } if selects != nil && selects.hasAnyRelation() { - if err := d.loadRelations(ctx, results, selects); err != nil { + if err := d.loadRelations(ctx, scanned, selects); err != nil { return nil, err } } - return results, nil + return scanned, nil } -func (d *ProfileDelegate) runUpdateManyAndReturnFallback(ctx context.Context, preds []PredicateOf[Profile], assignments []FieldAssignment, selects *ProfileSelect, omits *ProfileOmit) ([]*Profile, error) { - affected, err := d.execUpdateStmt(ctx, preds, assignments) +func (d *ProfileDelegate) runUpdateManyAndReturnFallback(ctx context.Context, preds []PredicateOf[Profile], cols []string, vals []any, selects *ProfileSelect, omits *ProfileOmit) ([]*Profile, error) { + affected, err := d.execUpdateStmt(ctx, preds, cols, vals) if err != nil { return nil, err } diff --git a/integration/valk/user.go b/integration/valk/user.go index afb75aa..7a598bd 100644 --- a/integration/valk/user.go +++ b/integration/valk/user.go @@ -72,6 +72,152 @@ func (s *UserCreate) colMask() uint64 { return mask } +// UserUpdate contains model input fields for User update operations. +type UserUpdate struct { + Id *string `json:"id"` + Email *string `json:"email"` + PhoneNum *string `json:"phoneNum"` + Password *string `json:"password"` + Role *UserRoleType `json:"role"` + RoleOptional *UserRoleType `json:"roleOptional"` + LoginCount *int32 `json:"loginCount"` + ReferredById *string `json:"referredById"` +} + +func (u *UserUpdate) ToColsVals() ([]string, []any) { + var cols []string + var vals []any + if u.Id != nil { + cols = append(cols, "id") + vals = append(vals, u.Id) + } + if u.Email != nil { + cols = append(cols, "email") + vals = append(vals, u.Email) + } + if u.PhoneNum != nil { + cols = append(cols, "phoneNum") + vals = append(vals, u.PhoneNum) + } + if u.Password != nil { + cols = append(cols, "password") + vals = append(vals, u.Password) + } + if u.Role != nil { + cols = append(cols, "role") + vals = append(vals, u.Role) + } + if u.RoleOptional != nil { + cols = append(cols, "roleOptional") + vals = append(vals, u.RoleOptional) + } + if u.LoginCount != nil { + cols = append(cols, "loginCount") + vals = append(vals, u.LoginCount) + } + if u.ReferredById != nil { + cols = append(cols, "referredById") + vals = append(vals, u.ReferredById) + } + return cols, vals +} + +func assignmentsToUserUpdate(assignments []FieldAssignment) (UserUpdate, error) { + var input UserUpdate + var errs ValidationError + + for _, a := range assignments { + switch a.Col { + case "id": + if v, ok := a.Val.(string); ok { + input.Id = &v + errs.ValidateString("id", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.Id = v + } else { + errs.Add("id", a.Val, "type", "field id must be of type string") + } + case "email": + if v, ok := a.Val.(string); ok { + input.Email = &v + errs.ValidateString("email", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.Email = v + } else { + errs.Add("email", a.Val, "type", "field email must be of type string") + } + case "phoneNum": + if v, ok := a.Val.(string); ok { + input.PhoneNum = &v + errs.ValidateString("phoneNum", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.PhoneNum = v + } else { + errs.Add("phoneNum", a.Val, "type", "field phoneNum must be of type string") + } + case "password": + if v, ok := a.Val.(string); ok { + input.Password = &v + errs.ValidateString("password", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.Password = v + } else { + errs.Add("password", a.Val, "type", "field password must be of type string") + } + case "role": + if v, ok := a.Val.(UserRoleType); ok { + input.Role = &v + if !v.IsValid() { + errs.Add("role", v, "enum", fmt.Sprintf("invalid enum value %q for field role", v)) + } + } else if v, ok := a.Val.(*UserRoleType); ok { + input.Role = v + if v != nil && !v.IsValid() { + errs.Add("role", *v, "enum", fmt.Sprintf("invalid enum value %q for field role", *v)) + } + } else { + errs.Add("role", a.Val, "type", "field role must be of type UserRoleType") + } + case "roleOptional": + if v, ok := a.Val.(UserRoleType); ok { + input.RoleOptional = &v + if !v.IsValid() { + errs.Add("roleOptional", v, "enum", fmt.Sprintf("invalid enum value %q for field roleOptional", v)) + } + } else if v, ok := a.Val.(*UserRoleType); ok { + input.RoleOptional = v + if v != nil && !v.IsValid() { + errs.Add("roleOptional", *v, "enum", fmt.Sprintf("invalid enum value %q for field roleOptional", *v)) + } + } else { + errs.Add("roleOptional", a.Val, "type", "field roleOptional must be of type UserRoleType") + } + case "loginCount": + if v, ok := a.Val.(int32); ok { + input.LoginCount = &v + } else if v, ok := a.Val.(*int32); ok { + input.LoginCount = v + } else { + errs.Add("loginCount", a.Val, "type", "field loginCount must be of type int32") + } + case "referredById": + if v, ok := a.Val.(string); ok { + input.ReferredById = &v + errs.ValidateString("referredById", v, false, 0, false, false) + } else if v, ok := a.Val.(*string); ok { + input.ReferredById = v + } else { + errs.Add("referredById", a.Val, "type", "field referredById must be of type string") + } + } + } + + if errs.HasErrors() { + return input, errs + } + return input, nil +} + // UserSelect specifies which scalar and relation fields to select for User. // // Selectable fields: @@ -466,6 +612,51 @@ func (a *UserDeleteManyArgs) SetWhere(preds ...PredicateOf[User]) *UserDeleteMan return a } +// UserUpdateArgs is the input argument passed to User Update extension hooks. +type UserUpdateArgs struct { + // Where contains all query filter predicates (merged primary unique constraint and additional predicates). + Where []PredicateOf[User] + // Data contains the model fields to update. + Data *UserUpdate + // Select specifies which scalar and relation fields to select and return upon update. + Select *UserSelect +} + +func (a *UserUpdateArgs) SetWhere(unique UniquePredicate[User], additional ...PredicateOf[User]) *UserUpdateArgs { + a.Where = make([]PredicateOf[User], 0, 1+len(additional)) + a.Where = append(a.Where, unique) + a.Where = append(a.Where, additional...) + return a +} + +// UserUpdateManyArgs is the input argument passed to User UpdateMany extension hooks. +type UserUpdateManyArgs struct { + // Where contains all query filter predicates. + Where []PredicateOf[User] + // Data contains the model fields to update. + Data *UserUpdate +} + +func (a *UserUpdateManyArgs) SetWhere(preds ...PredicateOf[User]) *UserUpdateManyArgs { + a.Where = preds + return a +} + +// UserUpdateManyAndReturnArgs is the input argument passed to User UpdateManyAndReturn extension hooks. +type UserUpdateManyAndReturnArgs struct { + // Where contains all query filter predicates. + Where []PredicateOf[User] + // Data contains the model fields to update. + Data *UserUpdate + // Select specifies which scalar and relation fields to select and return upon update. + Select *UserSelect +} + +func (a *UserUpdateManyAndReturnArgs) SetWhere(preds ...PredicateOf[User]) *UserUpdateManyAndReturnArgs { + a.Where = preds + return a +} + type UserCreateQuery = func(ctx context.Context, args *UserCreateArgs) (*User, error) type UserCreateManyQuery = func(ctx context.Context, args *UserCreateManyArgs) (int64, error) type UserCreateManyAndReturnQuery = func(ctx context.Context, args *UserCreateManyAndReturnArgs) ([]*User, error) @@ -475,9 +666,9 @@ type UserFindManyQuery = func(ctx context.Context, args *UserFindManyArgs) ([]*U type UserDeleteQuery = func(ctx context.Context, args *UserDeleteArgs) (*User, error) type UserDeleteManyQuery = func(ctx context.Context, args *UserDeleteManyArgs) (int64, error) type UserCountQuery = func(ctx context.Context, args *UserCountArgs) (int64, error) -type UserUpdateQuery = func(ctx context.Context, where UniquePredicate[User], additional []PredicateOf[User], assignments []FieldAssignment, selects *UserSelect, omits *UserOmit) (*User, error) -type UserUpdateManyQuery = func(ctx context.Context, preds []PredicateOf[User], assignments []FieldAssignment) (int64, error) -type UserUpdateManyAndReturnQuery = func(ctx context.Context, preds []PredicateOf[User], assignments []FieldAssignment, selects *UserSelect, omits *UserOmit) ([]*User, error) +type UserUpdateQuery = func(ctx context.Context, args *UserUpdateArgs) (*User, error) +type UserUpdateManyQuery = func(ctx context.Context, args *UserUpdateManyArgs) (int64, error) +type UserUpdateManyAndReturnQuery = func(ctx context.Context, args *UserUpdateManyAndReturnArgs) ([]*User, error) type UserExtension struct { Create func(ctx context.Context, args *UserCreateArgs, next UserCreateQuery) (*User, error) @@ -489,9 +680,9 @@ type UserExtension struct { Delete func(ctx context.Context, args *UserDeleteArgs, next UserDeleteQuery) (*User, error) DeleteMany func(ctx context.Context, args *UserDeleteManyArgs, next UserDeleteManyQuery) (int64, error) Count func(ctx context.Context, args *UserCountArgs, next UserCountQuery) (int64, error) - Update func(ctx context.Context, where UniquePredicate[User], additional []PredicateOf[User], assignments []FieldAssignment, selects *UserSelect, omits *UserOmit, next UserUpdateQuery) (*User, error) - UpdateMany func(ctx context.Context, preds []PredicateOf[User], assignments []FieldAssignment, next UserUpdateManyQuery) (int64, error) - UpdateManyAndReturn func(ctx context.Context, preds []PredicateOf[User], assignments []FieldAssignment, selects *UserSelect, omits *UserOmit, next UserUpdateManyAndReturnQuery) ([]*User, error) + Update func(ctx context.Context, args *UserUpdateArgs, next UserUpdateQuery) (*User, error) + UpdateMany func(ctx context.Context, args *UserUpdateManyArgs, next UserUpdateManyQuery) (int64, error) + UpdateManyAndReturn func(ctx context.Context, args *UserUpdateManyAndReturnArgs, next UserUpdateManyAndReturnQuery) ([]*User, error) } type UserDelegate struct { @@ -589,6 +780,16 @@ type UserCreateBuilder struct { *CreateBuilder[User, UserSelect, UserOmit] } +func (b *UserCreateBuilder) Select(s UserSelect) *UserCreateBuilder { + b.selects = &s + return b +} + +func (b *UserCreateBuilder) Omit(o UserOmit) *UserCreateBuilder { + b.omits = &o + return b +} + func (b *UserCreateBuilder) OnConflict(target UniqueConstraintTarget) *UserConflictBuilder[UserCreateBuilder] { return &UserConflictBuilder[UserCreateBuilder]{ builder: b, @@ -820,23 +1021,11 @@ func (d *UserDelegate) executeCreate(ctx context.Context, assignments []FieldAss return nil, err } + cols, vals := input.ToColsVals() + returningCols := selectUserCols(selects, omits) + if len(d.extensions) == 0 { - cols, vals := input.ToColsVals() - returningCols := selectUserCols(selects, omits) - hasRelations := selects.hasAnyRelation() - if hasRelations { - var res *User - err = d.client.transaction(ctx, func(txQ *Queries) error { - var err error - res, err = txQ.User.runCreate(ctx, cols, vals, returningCols, userPKCols, conflictTarget, conflictAction) - if err != nil { - return err - } - return txQ.User.loadRelations(ctx, []*User{res}, selects) - }) - return res, err - } - return d.runCreate(ctx, cols, vals, returningCols, userPKCols, conflictTarget, conflictAction) + return d.runCreate(ctx, cols, vals, returningCols, selects, conflictTarget, conflictAction) } if selects == nil || !selects.hasAnySelected() { @@ -851,28 +1040,9 @@ func (d *UserDelegate) executeCreate(ctx context.Context, assignments []FieldAss } curr := func(c context.Context, a *UserCreateArgs) (*User, error) { - cols, vals := a.Data.ToColsVals() - returningCols := selectUserCols(a.Select, omits) - - hasRelations := a.Select.hasAnyRelation() - var res *User - var err error - if hasRelations { - err = d.client.transaction(c, func(txQ *Queries) error { - var err error - res, err = txQ.User.runCreate(c, cols, vals, returningCols, userPKCols, a.ConflictTarget, a.ConflictAction) - if err != nil { - return err - } - return txQ.User.loadRelations(c, []*User{res}, a.Select) - }) - } else { - res, err = d.runCreate(c, cols, vals, returningCols, userPKCols, a.ConflictTarget, a.ConflictAction) - } - if err != nil { - return nil, err - } - return res, nil + cCols, cVals := a.Data.ToColsVals() + cReturningCols := selectUserCols(a.Select, omits) + return d.runCreate(c, cCols, cVals, cReturningCols, a.Select, a.ConflictTarget, a.ConflictAction) } if len(d.extensions) == 1 { @@ -913,6 +1083,16 @@ type UserCreateManyAndReturnBuilder struct { *CreateManyAndReturnBuilder[User, UserSelect, UserOmit] } +func (b *UserCreateManyAndReturnBuilder) Select(s UserSelect) *UserCreateManyAndReturnBuilder { + b.selects = &s + return b +} + +func (b *UserCreateManyAndReturnBuilder) Omit(o UserOmit) *UserCreateManyAndReturnBuilder { + b.omits = &o + return b +} + func (b *UserCreateManyAndReturnBuilder) OnConflict(target UniqueConstraintTarget) *UserConflictBuilder[UserCreateManyAndReturnBuilder] { return &UserConflictBuilder[UserCreateManyAndReturnBuilder]{ builder: b, @@ -924,43 +1104,51 @@ func (b *UserCreateManyAndReturnBuilder) OnConflict(target UniqueConstraintTarge } } -func (d *UserDelegate) CreateMany(builders ...*UserCreateBuilder) *UserCreateManyBuilder { +func createBuildersToUserRecordInputs(builders []*UserCreateBuilder) []RecordInput { records := make([]RecordInput, len(builders)) for i, b := range builders { records[i] = RecordInput{Assignments: b.assignments} } + return records +} + +func (d *UserDelegate) CreateMany(builders ...*UserCreateBuilder) *UserCreateManyBuilder { return &UserCreateManyBuilder{ CreateManyBuilder: &CreateManyBuilder[User]{ - records: records, + records: createBuildersToUserRecordInputs(builders), execFunc: d.executeCreateMany, }, } } func (d *UserDelegate) CreateManyAndReturn(builders ...*UserCreateBuilder) *UserCreateManyAndReturnBuilder { - records := make([]RecordInput, len(builders)) - for i, b := range builders { - records[i] = RecordInput{Assignments: b.assignments} - } return &UserCreateManyAndReturnBuilder{ CreateManyAndReturnBuilder: &CreateManyAndReturnBuilder[User, UserSelect, UserOmit]{ - records: records, + records: createBuildersToUserRecordInputs(builders), execFunc: d.executeCreateManyAndReturn, }, } } -func (d *UserDelegate) executeCreateMany(ctx context.Context, records []RecordInput, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { +func recordsToUserCreateInputs(records []RecordInput) ([]*UserCreate, error) { structs := make([]UserCreate, len(records)) inputs := make([]*UserCreate, len(records)) for i, rec := range records { var err error structs[i], err = assignmentsToUserCreate(rec.Assignments) if err != nil { - return 0, fmt.Errorf("validation failed at index %d: %w", i, err) + return nil, fmt.Errorf("validation failed at index %d: %w", i, err) } inputs[i] = &structs[i] } + return inputs, nil +} + +func (d *UserDelegate) executeCreateMany(ctx context.Context, records []RecordInput, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { + inputs, err := recordsToUserCreateInputs(records) + if err != nil { + return 0, err + } if len(d.extensions) == 0 { return d.runCreateMany(ctx, inputs, conflictTarget, conflictAction) @@ -996,31 +1184,12 @@ func (d *UserDelegate) executeCreateMany(ctx context.Context, records []RecordIn } func (d *UserDelegate) executeCreateManyAndReturn(ctx context.Context, records []RecordInput, selects *UserSelect, omits *UserOmit, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) ([]*User, error) { - structs := make([]UserCreate, len(records)) - inputs := make([]*UserCreate, len(records)) - for i, rec := range records { - var err error - structs[i], err = assignmentsToUserCreate(rec.Assignments) - if err != nil { - return nil, fmt.Errorf("validation failed at index %d: %w", i, err) - } - inputs[i] = &structs[i] + inputs, err := recordsToUserCreateInputs(records) + if err != nil { + return nil, err } if len(d.extensions) == 0 { - hasRelations := selects != nil && selects.hasAnyRelation() - if hasRelations { - var res []*User - err := d.client.transaction(ctx, func(txQ *Queries) error { - var err error - res, err = txQ.User.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) - if err != nil { - return err - } - return txQ.User.loadRelations(ctx, res, selects) - }) - return res, err - } return d.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) } @@ -1036,19 +1205,6 @@ func (d *UserDelegate) executeCreateManyAndReturn(ctx context.Context, records [ } curr := func(c context.Context, a *UserCreateManyAndReturnArgs) ([]*User, error) { - hasRelations := a.Select != nil && a.Select.hasAnyRelation() - if hasRelations { - var res []*User - err := d.client.transaction(c, func(txQ *Queries) error { - var err error - res, err = txQ.User.runCreateManyAndReturn(c, a.Data, a.Select, omits, a.ConflictTarget, a.ConflictAction) - if err != nil { - return err - } - return txQ.User.loadRelations(c, res, a.Select) - }) - return res, err - } return d.runCreateManyAndReturn(c, a.Data, a.Select, omits, a.ConflictTarget, a.ConflictAction) } @@ -1076,36 +1232,67 @@ func (d *UserDelegate) runCreate( cols []string, vals []any, returningCols []string, - pkCols []string, + selects *UserSelect, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction, ) (*User, error) { - query, clauseArgs := buildSingleInsertSQL(d.client, "User", cols, returningCols, pkCols, conflictTarget, conflictAction, len(vals)) + hasRelations := selects != nil && selects.hasAnyRelation() + useTx := hasRelations && !d.client.inTx() + + if useTx { + var res *User + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + res, err = txQ.User.runCreate(ctx, cols, vals, returningCols, selects, conflictTarget, conflictAction) + if err != nil { + return err + } + return txQ.User.loadRelations(ctx, []*User{res}, selects) + }) + return res, err + } + + query, clauseArgs := buildSingleInsertSQL(d.client, "User", cols, returningCols, userPKCols, conflictTarget, conflictAction, len(vals)) if len(clauseArgs) > 0 { vals = append(vals, clauseArgs...) } - var res User if d.client.dialect.SupportsInsertReturning { rows, err := d.client.query(ctx, query, vals...) if err != nil { return nil, err } - defer rows.Close() - if rows.Next() { - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + if !rows.Next() { + err := rows.Err() + rows.Close() + if err != nil { return nil, err } - return &res, nil + return nil, nil } - return nil, rows.Err() + + var res User + scanErr := rows.Scan(res.ScanFields(returningCols)...) + rows.Close() + if scanErr != nil { + return nil, scanErr + } + + return &res, nil } - return d.runCreateFallback(ctx, query, vals, cols, returningCols, pkCols) + return d.runCreateFallback(ctx, query, vals, cols, returningCols, userPKCols) } -func (d *UserDelegate) runCreateFallback(ctx context.Context, query string, vals []any, cols []string, returningCols []string, pkCols []string) (*User, error) { +func (d *UserDelegate) runCreateFallback( + ctx context.Context, + query string, + vals []any, + cols []string, + returningCols []string, + pkCols []string, +) (*User, error) { result, err := d.client.exec(ctx, query, vals...) if err != nil { return nil, err @@ -1155,16 +1342,24 @@ func (d *UserDelegate) runCreateFallback(ctx context.Context, query string, vals if err != nil { return nil, err } - defer rows.Close() - var res User - if rows.Next() { - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + if !rows.Next() { + err := rows.Err() + rows.Close() + if err != nil { return nil, err } - return &res, nil + return nil, nil } - return nil, rows.Err() + + var res User + scanErr := rows.Scan(res.ScanFields(returningCols)...) + rows.Close() + if scanErr != nil { + return nil, scanErr + } + + return &res, nil } func (d *UserDelegate) buildBulkInsertSQL(q *Queries, batch []*UserCreate, paramStartIdx int) (cols []string, vals []any, queryStr string) { @@ -1260,6 +1455,41 @@ func (d *UserDelegate) buildBulkInsertSQL(q *Queries, batch []*UserCreate, param return cols, vals, queryStr } +func applyUserConflictClause(dialect Dialect, queryStr string, vals []any, cols []string, pkCols []string, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (string, []any) { + var conflictCols []string + if conflictTarget != nil { + conflictCols = conflictTarget.UniqueColumns() + } + var nonConflictCols []string + if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { + nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + } + clause, clauseArgs := dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) + queryStr += clause + if len(clauseArgs) > 0 { + vals = append(vals, clauseArgs...) + } + return queryStr, vals +} + +func scanUserRows(rows *sql.Rows, returningCols []string) ([]*User, error) { + var records []*User + for rows.Next() { + var res User + if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + rows.Close() + return nil, err + } + records = append(records, &res) + } + if err := rows.Err(); err != nil { + rows.Close() + return nil, err + } + rows.Close() + return records, nil +} + func (d *UserDelegate) runCreateMany(ctx context.Context, inputs []*UserCreate, conflictTarget UniqueConstraintTarget, conflictAction *ConflictAction) (int64, error) { if len(inputs) == 0 { return 0, nil @@ -1270,18 +1500,7 @@ func (d *UserDelegate) runCreateMany(ctx context.Context, inputs []*UserCreate, var count int64 for _, batch := range batches { cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) - - var conflictCols []string - if conflictTarget != nil { - conflictCols = conflictTarget.UniqueColumns() - } - var nonConflictCols []string - if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, userPKCols) - } - clause, clauseArgs := d.client.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) - queryStr += clause - vals = append(vals, clauseArgs...) + queryStr, vals = applyUserConflictClause(d.client.dialect, queryStr, vals, cols, userPKCols, conflictTarget, conflictAction) result, err := d.client.exec(ctx, queryStr, vals...) if err != nil { @@ -1309,27 +1528,37 @@ func (d *UserDelegate) runCreateManyAndReturn( } batches := partitionUserInputs(d.client.dialect, inputs) - returningCols := selectUserCols(selects, omits) hasRelations := selects != nil && selects.hasAnyRelation() + useTx := (len(batches) > 1 || hasRelations || !d.client.dialect.SupportsInsertReturning) && !d.client.inTx() - recordsOut := make([]*User, 0, len(inputs)) + if useTx { + var res []*User + err := d.client.transaction(ctx, func(txQ *Queries) error { + var err error + if txQ.dialect.SupportsInsertReturning { + res, err = txQ.User.runCreateManyAndReturn(ctx, inputs, selects, omits, conflictTarget, conflictAction) + } else { + res, err = txQ.User.runCreateManyAndReturnFallback(ctx, inputs, selects, omits, conflictTarget, conflictAction) + } + if err != nil { + return err + } + if hasRelations { + return txQ.User.loadRelations(ctx, res, selects) + } + return nil + }) + return res, err + } - runBatch := func(txQ *Queries, batch []*UserCreate) error { - cols, vals, queryStr := d.buildBulkInsertSQL(txQ, batch, 1) + returningCols := selectUserCols(selects, omits, userPKCols...) + recordsOut := make([]*User, 0, len(inputs)) - var conflictCols []string - if conflictTarget != nil { - conflictCols = conflictTarget.UniqueColumns() - } - var nonConflictCols []string - if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, userPKCols) - } - clause, clauseArgs := txQ.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) - queryStr += clause - vals = append(vals, clauseArgs...) + for _, batch := range batches { + cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) + queryStr, vals = applyUserConflictClause(d.client.dialect, queryStr, vals, cols, userPKCols, conflictTarget, conflictAction) - if txQ.dialect.SupportsInsertReturning && len(returningCols) > 0 { + if len(returningCols) > 0 { var retSb strings.Builder retSb.Grow(12 + len(returningCols)*15) retSb.WriteString(" RETURNING ") @@ -1337,40 +1566,58 @@ func (d *UserDelegate) runCreateManyAndReturn( if i > 0 { retSb.WriteString(", ") } - txQ.dialect.WriteQuotedIdent(&retSb, col) + d.client.dialect.WriteQuotedIdent(&retSb, col) } queryStr += retSb.String() - rows, err := txQ.query(ctx, queryStr, vals...) - if err != nil { - return err - } - defer rows.Close() + } - for rows.Next() { - var res User - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - return err - } - recordsOut = append(recordsOut, &res) - } - return rows.Err() + rows, err := d.client.query(ctx, queryStr, vals...) + if err != nil { + return nil, err } - // Fallback for dialects without RETURNING (MySQL) - result, err := txQ.exec(ctx, queryStr, vals...) + scanned, err := scanUserRows(rows, returningCols) if err != nil { - return err + return nil, err + } + recordsOut = append(recordsOut, scanned...) + } + + if selects != nil && selects.hasAnyRelation() { + if err := d.loadRelations(ctx, recordsOut, selects); err != nil { + return nil, err + } + } + + return recordsOut, nil +} + +func (d *UserDelegate) runCreateManyAndReturnFallback( + ctx context.Context, + inputs []*UserCreate, + selects *UserSelect, + omits *UserOmit, + conflictTarget UniqueConstraintTarget, + conflictAction *ConflictAction, +) ([]*User, error) { + batches := partitionUserInputs(d.client.dialect, inputs) + returningCols := selectUserCols(selects, omits, userPKCols...) + recordsOut := make([]*User, 0, len(inputs)) + + for _, batch := range batches { + cols, vals, queryStr := d.buildBulkInsertSQL(d.client, batch, 1) + queryStr, vals = applyUserConflictClause(d.client.dialect, queryStr, vals, cols, userPKCols, conflictTarget, conflictAction) + + result, err := d.client.exec(ctx, queryStr, vals...) + if err != nil { + return nil, err } - // We need to fetch the inserted records for this batch - // Note: MySQL bulk inserts only return the ID of the FIRST inserted row lastID, err := result.LastInsertId() if err != nil { - return err + return nil, err } - // Query back the rows by IDs (assuming autoincrement ID and single PK) - // If composite PK, it's more complex, but this is a standard fallback var selectSb strings.Builder selectSb.Grow(64 + len(returningCols)*15 + len("User") + len(batch)*15) selectSb.WriteString("SELECT ") @@ -1378,55 +1625,29 @@ func (d *UserDelegate) runCreateManyAndReturn( if i > 0 { selectSb.WriteString(", ") } - txQ.dialect.WriteQuotedIdent(&selectSb, col) + d.client.dialect.WriteQuotedIdent(&selectSb, col) } selectSb.WriteString(" FROM ") - txQ.dialect.WriteQuotedIdent(&selectSb, "User") + d.client.dialect.WriteQuotedIdent(&selectSb, "User") selectSb.WriteString(" WHERE ") - txQ.dialect.WriteQuotedIdent(&selectSb, userPKCols[0]) + d.client.dialect.WriteQuotedIdent(&selectSb, userPKCols[0]) selectSb.WriteString(" >= ") - txQ.dialect.WritePlaceholder(&selectSb, 1) + d.client.dialect.WritePlaceholder(&selectSb, 1) selectSb.WriteString(" AND ") - txQ.dialect.WriteQuotedIdent(&selectSb, userPKCols[0]) + d.client.dialect.WriteQuotedIdent(&selectSb, userPKCols[0]) selectSb.WriteString(" < ") - txQ.dialect.WritePlaceholder(&selectSb, 2) + d.client.dialect.WritePlaceholder(&selectSb, 2) - rows, err := txQ.query(ctx, selectSb.String(), lastID, lastID+int64(len(batch))) + rows, err := d.client.query(ctx, selectSb.String(), lastID, lastID+int64(len(batch))) if err != nil { - return err - } - defer rows.Close() - - for rows.Next() { - var res User - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - return err - } - recordsOut = append(recordsOut, &res) + return nil, err } - return rows.Err() - } - // Always wrap in transaction if we have multiple batches OR if we need to load relations - 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 { - return err - } - } - if hasRelations { - return txQ.User.loadRelations(ctx, recordsOut, selects) - } - return nil - }) + scanned, err := scanUserRows(rows, returningCols) if err != nil { return nil, err } - } else { - if err := runBatch(d.client, batches[0]); err != nil { - return nil, err - } + recordsOut = append(recordsOut, scanned...) } return recordsOut, nil @@ -1666,23 +1887,23 @@ func (d *UserDelegate) UpdateManyAndReturn(preds ...PredicateOf[User]) *UserUpda } } -func (d *UserDelegate) buildUpdateSQL(preds []PredicateOf[User], assignments []FieldAssignment, returningCols []string) (string, []any) { - whereClause, predVals, _ := CompilePredicates(d.client.dialect, preds, len(assignments)+1) +func (d *UserDelegate) buildUpdateSQL(preds []PredicateOf[User], cols []string, vals []any, returningCols []string) (string, []any) { + whereClause, predVals, _ := CompilePredicates(d.client.dialect, preds, len(cols)+1) var sb strings.Builder sb.WriteString("UPDATE ") d.client.dialect.WriteQuotedIdent(&sb, "User") sb.WriteString(" SET ") - setVals := make([]any, 0, len(assignments)+len(predVals)) - for i, a := range assignments { + setVals := make([]any, 0, len(cols)+len(predVals)) + for i, col := range cols { if i > 0 { sb.WriteString(", ") } - d.client.dialect.WriteQuotedIdent(&sb, a.Col) + d.client.dialect.WriteQuotedIdent(&sb, col) sb.WriteString(" = ") d.client.dialect.WritePlaceholder(&sb, i+1) - setVals = append(setVals, a.Val) + setVals = append(setVals, vals[i]) } if whereClause != "" { @@ -1709,21 +1930,39 @@ func (d *UserDelegate) buildUpdateSQL(preds []PredicateOf[User], assignments []F // ----------------------------------------------------------------------------- func (d *UserDelegate) executeUpdate(ctx context.Context, where UniquePredicate[User], additional []PredicateOf[User], assignments []FieldAssignment, selects *UserSelect, omits *UserOmit) (*User, error) { + allWhere := make([]PredicateOf[User], 0, 1+len(additional)) + allWhere = append(allWhere, where) + allWhere = append(allWhere, additional...) + + input, err := assignmentsToUserUpdate(assignments) + if err != nil { + return nil, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.runUpdate(ctx, where, additional, assignments, selects, omits) + return d.runUpdate(ctx, allWhere, cols, vals, selects, omits) } if selects == nil || !selects.hasAnySelected() { selects = fullUserSelect() } - curr := func(c context.Context, w UniquePredicate[User], add []PredicateOf[User], a []FieldAssignment, s *UserSelect, o *UserOmit) (*User, error) { - return d.runUpdate(c, w, add, a, s, o) + args := &UserUpdateArgs{ + Where: allWhere, + Data: &input, + Select: selects, + } + + curr := func(c context.Context, a *UserUpdateArgs) (*User, error) { + extCols, extVals := a.Data.ToColsVals() + return d.runUpdate(c, a.Where, extCols, extVals, a.Select, omits) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.Update != nil { - return ext.Update(ctx, where, additional, assignments, selects, omits, curr) + return ext.Update(ctx, args, curr) } } @@ -1731,25 +1970,21 @@ func (d *UserDelegate) executeUpdate(ctx context.Context, where UniquePredicate[ ext := d.extensions[i] if ext.Update != nil { next, hook := curr, ext.Update - curr = func(c context.Context, w UniquePredicate[User], add []PredicateOf[User], a []FieldAssignment, s *UserSelect, o *UserOmit) (*User, error) { - return hook(c, w, add, a, s, o, next) + curr = func(c context.Context, a *UserUpdateArgs) (*User, error) { + return hook(c, a, next) } } } - return curr(ctx, where, additional, assignments, selects, omits) + return curr(ctx, args) } -func (d *UserDelegate) runUpdate(ctx context.Context, where UniquePredicate[User], additional []PredicateOf[User], assignments []FieldAssignment, selects *UserSelect, omits *UserOmit) (*User, error) { - allPreds := append([]PredicateOf[User]{where}, additional...) - if len(assignments) == 0 { - return d.runFindUnique(ctx, allPreds, selects, omits) +func (d *UserDelegate) runUpdate(ctx context.Context, preds []PredicateOf[User], cols []string, vals []any, selects *UserSelect, omits *UserOmit) (*User, error) { + if len(cols) == 0 { + return d.runFindUnique(ctx, preds, selects, omits) } - if err := where.Validate(); err != nil { - return nil, err - } - for _, pr := range additional { + for _, pr := range preds { if pr != nil { if err := pr.Validate(); err != nil { return nil, err @@ -1765,9 +2000,9 @@ func (d *UserDelegate) runUpdate(ctx context.Context, where UniquePredicate[User err := d.client.transaction(ctx, func(txQ *Queries) error { var err error if d.client.dialect.SupportsUpdateReturning { - res, err = txQ.User.runUpdate(ctx, where, additional, assignments, selects, omits) + res, err = txQ.User.runUpdate(ctx, preds, cols, vals, selects, omits) } else { - res, err = txQ.User.runUpdateFallback(ctx, where, additional, assignments, selects, omits) + res, err = txQ.User.runUpdateFallback(ctx, preds, cols, vals, selects, omits) } return err }) @@ -1775,7 +2010,7 @@ func (d *UserDelegate) runUpdate(ctx context.Context, where UniquePredicate[User } returningCols := selectUserCols(selects, omits, userPKCols...) - query, setVals := d.buildUpdateSQL(allPreds, assignments, returningCols) + query, setVals := d.buildUpdateSQL(preds, cols, vals, returningCols) rows, err := d.client.query(ctx, query, setVals...) if err != nil { @@ -1807,8 +2042,8 @@ func (d *UserDelegate) runUpdate(ctx context.Context, where UniquePredicate[User return &res, nil } -func (d *UserDelegate) execUpdateStmt(ctx context.Context, preds []PredicateOf[User], assignments []FieldAssignment) (int64, error) { - if len(assignments) == 0 { +func (d *UserDelegate) execUpdateStmt(ctx context.Context, preds []PredicateOf[User], cols []string, vals []any) (int64, error) { + if len(cols) == 0 { return 0, nil } @@ -1820,7 +2055,7 @@ func (d *UserDelegate) execUpdateStmt(ctx context.Context, preds []PredicateOf[U } } - query, setVals := d.buildUpdateSQL(preds, assignments, nil) + query, setVals := d.buildUpdateSQL(preds, cols, vals, nil) result, err := d.client.exec(ctx, query, setVals...) if err != nil { return 0, err @@ -1828,16 +2063,15 @@ func (d *UserDelegate) execUpdateStmt(ctx context.Context, preds []PredicateOf[U return result.RowsAffected() } -func (d *UserDelegate) runUpdateFallback(ctx context.Context, where UniquePredicate[User], additional []PredicateOf[User], assignments []FieldAssignment, selects *UserSelect, omits *UserOmit) (*User, error) { - allPreds := append([]PredicateOf[User]{where}, additional...) - affected, err := d.execUpdateStmt(ctx, allPreds, assignments) +func (d *UserDelegate) runUpdateFallback(ctx context.Context, preds []PredicateOf[User], cols []string, vals []any, selects *UserSelect, omits *UserOmit) (*User, error) { + affected, err := d.execUpdateStmt(ctx, preds, cols, vals) if err != nil { return nil, err } if affected == 0 { return nil, sql.ErrNoRows } - return d.runFindUnique(ctx, allPreds, selects, omits) + return d.runFindUnique(ctx, preds, selects, omits) } // ----------------------------------------------------------------------------- @@ -1845,17 +2079,30 @@ func (d *UserDelegate) runUpdateFallback(ctx context.Context, where UniquePredic // ----------------------------------------------------------------------------- func (d *UserDelegate) executeUpdateMany(ctx context.Context, preds []PredicateOf[User], assignments []FieldAssignment) (int64, error) { + input, err := assignmentsToUserUpdate(assignments) + if err != nil { + return 0, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.execUpdateStmt(ctx, preds, assignments) + return d.execUpdateStmt(ctx, preds, cols, vals) + } + + args := &UserUpdateManyArgs{ + Where: preds, + Data: &input, } - curr := func(c context.Context, p []PredicateOf[User], a []FieldAssignment) (int64, error) { - return d.execUpdateStmt(c, p, a) + curr := func(c context.Context, a *UserUpdateManyArgs) (int64, error) { + extCols, extVals := a.Data.ToColsVals() + return d.execUpdateStmt(c, a.Where, extCols, extVals) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.UpdateMany != nil { - return ext.UpdateMany(ctx, preds, assignments, curr) + return ext.UpdateMany(ctx, args, curr) } } @@ -1863,13 +2110,13 @@ func (d *UserDelegate) executeUpdateMany(ctx context.Context, preds []PredicateO ext := d.extensions[i] if ext.UpdateMany != nil { next, hook := curr, ext.UpdateMany - curr = func(c context.Context, p []PredicateOf[User], a []FieldAssignment) (int64, error) { - return hook(c, p, a, next) + curr = func(c context.Context, a *UserUpdateManyArgs) (int64, error) { + return hook(c, a, next) } } } - return curr(ctx, preds, assignments) + return curr(ctx, args) } // ----------------------------------------------------------------------------- @@ -1877,21 +2124,35 @@ func (d *UserDelegate) executeUpdateMany(ctx context.Context, preds []PredicateO // ----------------------------------------------------------------------------- func (d *UserDelegate) executeUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[User], assignments []FieldAssignment, selects *UserSelect, omits *UserOmit) ([]*User, error) { + input, err := assignmentsToUserUpdate(assignments) + if err != nil { + return nil, err + } + + cols, vals := input.ToColsVals() + if len(d.extensions) == 0 { - return d.runUpdateManyAndReturn(ctx, preds, assignments, selects, omits) + return d.runUpdateManyAndReturn(ctx, preds, cols, vals, selects, omits) } if selects == nil || !selects.hasAnySelected() { selects = fullUserSelect() } - curr := func(c context.Context, p []PredicateOf[User], a []FieldAssignment, s *UserSelect, o *UserOmit) ([]*User, error) { - return d.runUpdateManyAndReturn(c, p, a, s, o) + args := &UserUpdateManyAndReturnArgs{ + Where: preds, + Data: &input, + Select: selects, + } + + curr := func(c context.Context, a *UserUpdateManyAndReturnArgs) ([]*User, error) { + extCols, extVals := a.Data.ToColsVals() + return d.runUpdateManyAndReturn(c, a.Where, extCols, extVals, a.Select, omits) } if len(d.extensions) == 1 { if ext := d.extensions[0]; ext.UpdateManyAndReturn != nil { - return ext.UpdateManyAndReturn(ctx, preds, assignments, selects, omits, curr) + return ext.UpdateManyAndReturn(ctx, args, curr) } } @@ -1899,17 +2160,17 @@ func (d *UserDelegate) executeUpdateManyAndReturn(ctx context.Context, preds []P ext := d.extensions[i] if ext.UpdateManyAndReturn != nil { next, hook := curr, ext.UpdateManyAndReturn - curr = func(c context.Context, p []PredicateOf[User], a []FieldAssignment, s *UserSelect, o *UserOmit) ([]*User, error) { - return hook(c, p, a, s, o, next) + curr = func(c context.Context, a *UserUpdateManyAndReturnArgs) ([]*User, error) { + return hook(c, a, next) } } } - return curr(ctx, preds, assignments, selects, omits) + return curr(ctx, args) } -func (d *UserDelegate) runUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[User], assignments []FieldAssignment, selects *UserSelect, omits *UserOmit) ([]*User, error) { - if len(assignments) == 0 { +func (d *UserDelegate) runUpdateManyAndReturn(ctx context.Context, preds []PredicateOf[User], cols []string, vals []any, selects *UserSelect, omits *UserOmit) ([]*User, error) { + if len(cols) == 0 { return d.runFindMany(ctx, QueryParams[User]{Where: preds}, selects, omits) } @@ -1929,9 +2190,9 @@ func (d *UserDelegate) runUpdateManyAndReturn(ctx context.Context, preds []Predi err := d.client.transaction(ctx, func(txQ *Queries) error { var err error if d.client.dialect.SupportsUpdateReturning { - res, err = txQ.User.runUpdateManyAndReturn(ctx, preds, assignments, selects, omits) + res, err = txQ.User.runUpdateManyAndReturn(ctx, preds, cols, vals, selects, omits) } else { - res, err = txQ.User.runUpdateManyAndReturnFallback(ctx, preds, assignments, selects, omits) + res, err = txQ.User.runUpdateManyAndReturnFallback(ctx, preds, cols, vals, selects, omits) } return err }) @@ -1939,39 +2200,29 @@ func (d *UserDelegate) runUpdateManyAndReturn(ctx context.Context, preds []Predi } returningCols := selectUserCols(selects, omits, userPKCols...) - query, setVals := d.buildUpdateSQL(preds, assignments, returningCols) + query, setVals := d.buildUpdateSQL(preds, cols, vals, returningCols) rows, err := d.client.query(ctx, query, setVals...) if err != nil { return nil, err } - results := make([]*User, 0) - for rows.Next() { - var res User - if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { - rows.Close() - return nil, err - } - results = append(results, &res) - } - rowsErr := rows.Err() - rows.Close() - if rowsErr != nil { - return nil, rowsErr + scanned, err := scanUserRows(rows, returningCols) + if err != nil { + return nil, err } if selects != nil && selects.hasAnyRelation() { - if err := d.loadRelations(ctx, results, selects); err != nil { + if err := d.loadRelations(ctx, scanned, selects); err != nil { return nil, err } } - return results, nil + return scanned, nil } -func (d *UserDelegate) runUpdateManyAndReturnFallback(ctx context.Context, preds []PredicateOf[User], assignments []FieldAssignment, selects *UserSelect, omits *UserOmit) ([]*User, error) { - affected, err := d.execUpdateStmt(ctx, preds, assignments) +func (d *UserDelegate) runUpdateManyAndReturnFallback(ctx context.Context, preds []PredicateOf[User], cols []string, vals []any, selects *UserSelect, omits *UserOmit) ([]*User, error) { + affected, err := d.execUpdateStmt(ctx, preds, cols, vals) if err != nil { return nil, err }