From 46287b8f710ecb69af5d73d666b40b4c53282213 Mon Sep 17 00:00:00 2001 From: Clancy Date: Wed, 22 Jul 2026 16:57:22 +0300 Subject: [PATCH 1/3] Chore: update dialect support of RETURNING keyword to be per operation 1- add SupportsInsert/Update/DeleteReturning instead of SupportsReturning to be per operation, in case a dialect does not support for all (PG and Sqlite does) 2- update usage of SupportsReturning to use the new defined per-operation field 3- update model_count and model_query templates to use d.client.query instead of calling prepare and stmt.QueryRowContext, the d.client.query is already a wrapper that calls that under the hood and allows logging, for consistency --- generator/templates/model_count.gotpl | 11 +++++++-- generator/templates/model_create.gotpl | 26 +++++++++------------ generator/templates/model_query.gotpl | 23 +++++++++--------- generator/templates/relations_runtime.gotpl | 2 +- generator/templates/runtime.gotpl | 4 +++- 5 files changed, 36 insertions(+), 30 deletions(-) diff --git a/generator/templates/model_count.gotpl b/generator/templates/model_count.gotpl index 82faed1..2786ef4 100644 --- a/generator/templates/model_count.gotpl +++ b/generator/templates/model_count.gotpl @@ -60,12 +60,19 @@ func (d *{{ .Model.Name }}Delegate) runCount(ctx context.Context, params QueryPa query = sb.String() } - stmt, err := d.client.prepare(ctx, query) + rows, err := d.client.query(ctx, query, vals...) if err != nil { return 0, err } + defer rows.Close() + var count int64 - if err := stmt.QueryRowContext(ctx, vals...).Scan(&count); err != nil { + if rows.Next() { + if err := rows.Scan(&count); err != nil { + return 0, err + } + } + if err := rows.Err(); err != nil { return 0, err } return count, nil diff --git a/generator/templates/model_create.gotpl b/generator/templates/model_create.gotpl index ed73749..e7b8502 100644 --- a/generator/templates/model_create.gotpl +++ b/generator/templates/model_create.gotpl @@ -277,7 +277,6 @@ func (d *{{ .Model.Name }}Delegate) executeCreate(ctx context.Context, assignmen cols, vals := input.ToColsVals() returningCols := select{{ .Model.Name }}Cols(selects, omits) - pkCols := {{ lowercase .Model.Name }}PKCols if len(d.extensions) == 0 { hasRelations := selects.hasAnyRelation() @@ -285,7 +284,7 @@ func (d *{{ .Model.Name }}Delegate) executeCreate(ctx context.Context, assignmen 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, pkCols, conflictTarget, conflictAction) + res, err = txQ.{{ .Model.Name }}.runCreate(ctx, cols, vals, returningCols, {{ lowercase .Model.Name }}PKCols, conflictTarget, conflictAction) if err != nil { return err } @@ -293,13 +292,12 @@ func (d *{{ .Model.Name }}Delegate) executeCreate(ctx context.Context, assignmen }) return res, err } - return d.runCreate(ctx, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + return d.runCreate(ctx, cols, vals, returningCols, {{ lowercase .Model.Name }}PKCols, conflictTarget, conflictAction) } curr := func(c context.Context, args *{{ .Model.Name }}Create) (*{{ .Model.Name }}, error) { cols, vals := args.ToColsVals() returningCols := select{{ .Model.Name }}Cols(selects, omits) - pkCols := {{ lowercase .Model.Name }}PKCols hasRelations := selects.hasAnyRelation() var res *{{ .Model.Name }} @@ -307,14 +305,14 @@ func (d *{{ .Model.Name }}Delegate) executeCreate(ctx context.Context, assignmen if hasRelations { err = d.client.transaction(c, func(txQ *Queries) error { var err error - res, err = txQ.{{ .Model.Name }}.runCreate(c, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = txQ.{{ .Model.Name }}.runCreate(c, cols, vals, returningCols, {{ lowercase .Model.Name }}PKCols, conflictTarget, conflictAction) if err != nil { return err } return txQ.{{ .Model.Name }}.loadRelations(c, []*{{ .Model.Name }}{res}, selects) }) } else { - res, err = d.runCreate(c, cols, vals, returningCols, pkCols, conflictTarget, conflictAction) + res, err = d.runCreate(c, cols, vals, returningCols, {{ lowercase .Model.Name }}PKCols, conflictTarget, conflictAction) } if err != nil { return nil, err @@ -491,7 +489,7 @@ func (d *{{ .Model.Name }}Delegate) runCreate( } var res {{ .Model.Name }} - if d.client.dialect.SupportsReturning { + if d.client.dialect.SupportsInsertReturning { rows, err := d.client.query(ctx, query, vals...) if err != nil { return nil, err @@ -695,9 +693,8 @@ func (d *{{ .Model.Name }}Delegate) runCreateMany(ctx context.Context, inputs [] conflictCols = conflictTarget.UniqueColumns() } var nonConflictCols []string - pkCols := {{ lowercase .Model.Name }}PKCols if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + nonConflictCols = computeNonConflictCols(cols, conflictCols, {{ lowercase .Model.Name }}PKCols) } clause, clauseArgs := d.client.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, len(vals)+1) queryStr += clause @@ -742,15 +739,14 @@ func (d *{{ .Model.Name }}Delegate) runCreateManyAndReturn( conflictCols = conflictTarget.UniqueColumns() } var nonConflictCols []string - pkCols := {{ lowercase .Model.Name }}PKCols if conflictAction != nil && conflictAction.Type == ConflictActionUpdateNewValues { - nonConflictCols = computeNonConflictCols(cols, conflictCols, pkCols) + 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...) - if txQ.dialect.SupportsReturning && len(returningCols) > 0 { + if txQ.dialect.SupportsInsertReturning && len(returningCols) > 0 { var retSb strings.Builder retSb.Grow(12 + len(returningCols)*15) retSb.WriteString(" RETURNING ") @@ -804,11 +800,11 @@ func (d *{{ .Model.Name }}Delegate) runCreateManyAndReturn( selectSb.WriteString(" FROM ") txQ.dialect.WriteQuotedIdent(&selectSb, "{{ .Model.EffectiveTableName }}") selectSb.WriteString(" WHERE ") - txQ.dialect.WriteQuotedIdent(&selectSb, pkCols[0]) + txQ.dialect.WriteQuotedIdent(&selectSb, {{ lowercase .Model.Name }}PKCols[0]) selectSb.WriteString(" >= ") txQ.dialect.WritePlaceholder(&selectSb, 1) selectSb.WriteString(" AND ") - txQ.dialect.WriteQuotedIdent(&selectSb, pkCols[0]) + txQ.dialect.WriteQuotedIdent(&selectSb, {{ lowercase .Model.Name }}PKCols[0]) selectSb.WriteString(" < ") txQ.dialect.WritePlaceholder(&selectSb, 2) @@ -829,7 +825,7 @@ func (d *{{ .Model.Name }}Delegate) runCreateManyAndReturn( } // Always wrap in transaction if we have multiple batches OR if we need to load relations - if len(batches) > 1 || hasRelations || !d.client.dialect.SupportsReturning { + if len(batches) > 1 || hasRelations || !d.client.dialect.SupportsInsertReturning { err := d.client.transaction(ctx, func(txQ *Queries) error { for _, batch := range batches { if err := runBatch(txQ, batch); err != nil { diff --git a/generator/templates/model_query.gotpl b/generator/templates/model_query.gotpl index e4abaca..31cc95e 100644 --- a/generator/templates/model_query.gotpl +++ b/generator/templates/model_query.gotpl @@ -221,16 +221,21 @@ func (d *{{ .Model.Name }}Delegate) runFindMany( func (d *{{ .Model.Name }}Delegate) queryOne(ctx context.Context, whereClause string, whereVals []any, returningCols []string, skip *int) (*{{ .Model.Name }}, error) { limitOne := 1 query := buildSelectSQL(d.client, "{{ .Model.EffectiveTableName }}", returningCols, whereClause, &limitOne, skip) - stmt, err := d.client.prepare(ctx, query) + rows, err := d.client.query(ctx, query, whereVals...) if err != nil { return nil, err } - row := stmt.QueryRowContext(ctx, whereVals...) - var res {{ .Model.Name }} - if err := row.Scan(res.ScanFields(returningCols)...); err != nil { - if err == sql.ErrNoRows { - return nil, nil + defer rows.Close() + + if !rows.Next() { + if err := rows.Err(); err != nil { + return nil, err } + return nil, nil + } + + var res {{ .Model.Name }} + if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { return nil, err } return &res, nil @@ -238,11 +243,7 @@ func (d *{{ .Model.Name }}Delegate) queryOne(ctx context.Context, whereClause st func (d *{{ .Model.Name }}Delegate) queryMany(ctx context.Context, whereClause string, whereVals []any, returningCols []string, take *int, skip *int) ([]*{{ .Model.Name }}, error) { query := buildSelectSQL(d.client, "{{ .Model.EffectiveTableName }}", returningCols, whereClause, take, skip) - stmt, err := d.client.prepare(ctx, query) - if err != nil { - return nil, err - } - rows, err := stmt.QueryContext(ctx, whereVals...) + rows, err := d.client.query(ctx, query, whereVals...) if err != nil { return nil, err } diff --git a/generator/templates/relations_runtime.gotpl b/generator/templates/relations_runtime.gotpl index f457fe0..19f7723 100644 --- a/generator/templates/relations_runtime.gotpl +++ b/generator/templates/relations_runtime.gotpl @@ -135,7 +135,7 @@ func buildSingleInsertSQL( clause, clauseArgs := q.dialect.BuildConflictClause(conflictCols, conflictAction, nonConflictCols, valsCount+1) sb.WriteString(clause) - if q.dialect.SupportsReturning && len(returningCols) > 0 { + if q.dialect.SupportsInsertReturning && len(returningCols) > 0 { sb.WriteString(" RETURNING ") for i, col := range returningCols { if i > 0 { diff --git a/generator/templates/runtime.gotpl b/generator/templates/runtime.gotpl index f8c9ae3..4565482 100644 --- a/generator/templates/runtime.gotpl +++ b/generator/templates/runtime.gotpl @@ -70,7 +70,9 @@ func (f numericFieldUpsert[T]) Dec(val T) { type Dialect struct { QuoteChar byte PlaceholderFmt string // "" means "?" - SupportsReturning bool + SupportsInsertReturning bool + SupportsUpdateReturning bool + SupportsDeleteReturning bool SupportsLimitMinusOne bool SupportsBulkInsert bool SupportsDefaultKeyword bool From 263f1e05b7fd60e73cf617f5cc25b154458dd097 Mon Sep 17 00:00:00 2001 From: Clancy Date: Wed, 22 Jul 2026 17:01:14 +0300 Subject: [PATCH 2/3] update client dialects to use per-operation supports RETURNING instead of one unified field, run go mod tidy --- generator/templates/client.gotpl | 12 +++++++++--- go.sum | 10 ++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/generator/templates/client.gotpl b/generator/templates/client.gotpl index 5e995cc..9366782 100644 --- a/generator/templates/client.gotpl +++ b/generator/templates/client.gotpl @@ -3,7 +3,9 @@ func newDialect() Dialect { return Dialect{ QuoteChar: '"', PlaceholderFmt: "$", - SupportsReturning: true, + SupportsInsertReturning: true, + SupportsUpdateReturning: true, + SupportsDeleteReturning: true, SupportsLimitMinusOne: false, SupportsBulkInsert: true, SupportsDefaultKeyword: true, @@ -19,7 +21,9 @@ func newDialect() Dialect { return Dialect{ QuoteChar: '"', PlaceholderFmt: "", // means ? - SupportsReturning: true, + SupportsInsertReturning: true, + SupportsUpdateReturning: true, + SupportsDeleteReturning: true, SupportsLimitMinusOne: true, SupportsBulkInsert: true, SupportsDefaultKeyword: false, @@ -35,7 +39,9 @@ func newDialect() Dialect { return Dialect{ QuoteChar: '`', PlaceholderFmt: "", - SupportsReturning: false, + SupportsInsertReturning: false, + SupportsUpdateReturning: false, + SupportsDeleteReturning: false, SupportsLimitMinusOne: false, SupportsBulkInsert: false, SupportsDefaultKeyword: false, diff --git a/go.sum b/go.sum index e957085..95e0254 100644 --- a/go.sum +++ b/go.sum @@ -64,11 +64,8 @@ github.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWB github.com/zclconf/go-cty-yaml v1.1.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -76,13 +73,10 @@ golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus= golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 3a0fd7ec523b7027dafea108fb3428ba5be537dd Mon Sep 17 00:00:00 2001 From: Clancy Date: Wed, 22 Jul 2026 17:07:39 +0300 Subject: [PATCH 3/3] add sql.ErrNoRows check to return nil, nil, nil and err otherwise --- generator/templates/model_query.gotpl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/generator/templates/model_query.gotpl b/generator/templates/model_query.gotpl index 31cc95e..1f028b9 100644 --- a/generator/templates/model_query.gotpl +++ b/generator/templates/model_query.gotpl @@ -223,12 +223,18 @@ func (d *{{ .Model.Name }}Delegate) queryOne(ctx context.Context, whereClause st query := buildSelectSQL(d.client, "{{ .Model.EffectiveTableName }}", returningCols, whereClause, &limitOne, skip) rows, err := d.client.query(ctx, query, whereVals...) if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } return nil, err } defer rows.Close() if !rows.Next() { if err := rows.Err(); err != nil { + if err == sql.ErrNoRows { + return nil, nil + } return nil, err } return nil, nil @@ -236,6 +242,9 @@ func (d *{{ .Model.Name }}Delegate) queryOne(ctx context.Context, whereClause st var res {{ .Model.Name }} if err := rows.Scan(res.ScanFields(returningCols)...); err != nil { + if err == sql.ErrNoRows { + return nil, nil + } return nil, err } return &res, nil