From 2d60910e66841d0ee273748d7094e560f8e29e5e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Apr 2026 06:25:46 +0000 Subject: [PATCH] Dedupe sqlc.arg parameters wrapped in a type cast for MySQL When a sqlc.arg() call was wrapped in a CAST (or MySQL's synthetic MATCH...AGAINST type cast), the resulting Parameter was not marked as named. Downstream struct-field generation then added numeric suffixes to disambiguate identical names, producing spurious fields like SearchTerm_3 alongside SearchTerm. Capture the isNamed result from FetchMerge so the column correctly reflects that it came from a named parameter, letting the code generator reuse a single struct field for repeated sqlc.arg(name) references. Fixes #4376 --- internal/compiler/resolve.go | 3 +- .../params_duplicate/mysql/go/models.go | 1 + .../params_duplicate/mysql/go/query.sql.go | 33 +++++++++++++++++++ .../testdata/params_duplicate/mysql/query.sql | 5 +++ .../params_duplicate/mysql/schema.sql | 3 +- 5 files changed, 43 insertions(+), 2 deletions(-) diff --git a/internal/compiler/resolve.go b/internal/compiler/resolve.go index b1fbb1990e..d926f2b1fc 100644 --- a/internal/compiler/resolve.go +++ b/internal/compiler/resolve.go @@ -513,10 +513,11 @@ func (comp *Compiler) resolveCatalogRefs(qc *QueryCatalog, rvs []*ast.RangeVar, } col := toColumn(n.TypeName) defaultP := named.NewInferredParam(col.Name, col.NotNull) - p, _ := params.FetchMerge(ref.ref.Number, defaultP) + p, isNamed := params.FetchMerge(ref.ref.Number, defaultP) col.Name = p.Name() col.NotNull = p.NotNull() + col.IsNamedParam = isNamed a = append(a, Parameter{ Number: ref.ref.Number, Column: col, diff --git a/internal/endtoend/testdata/params_duplicate/mysql/go/models.go b/internal/endtoend/testdata/params_duplicate/mysql/go/models.go index cab9171aaf..c47ceb80a6 100644 --- a/internal/endtoend/testdata/params_duplicate/mysql/go/models.go +++ b/internal/endtoend/testdata/params_duplicate/mysql/go/models.go @@ -12,4 +12,5 @@ type User struct { ID int32 FirstName sql.NullString LastName sql.NullString + Age sql.NullInt32 } diff --git a/internal/endtoend/testdata/params_duplicate/mysql/go/query.sql.go b/internal/endtoend/testdata/params_duplicate/mysql/go/query.sql.go index 332cb9b26e..33a8e53d75 100644 --- a/internal/endtoend/testdata/params_duplicate/mysql/go/query.sql.go +++ b/internal/endtoend/testdata/params_duplicate/mysql/go/query.sql.go @@ -10,6 +10,39 @@ import ( "database/sql" ) +const selectUserByAgeCast = `-- name: SelectUserByAgeCast :many +SELECT first_name FROM users +WHERE age > CAST(? AS SIGNED) + OR age < CAST(? AS SIGNED) +` + +type SelectUserByAgeCastParams struct { + Threshold int64 +} + +func (q *Queries) SelectUserByAgeCast(ctx context.Context, arg SelectUserByAgeCastParams) ([]sql.NullString, error) { + rows, err := q.db.QueryContext(ctx, selectUserByAgeCast, arg.Threshold, arg.Threshold) + if err != nil { + return nil, err + } + defer rows.Close() + var items []sql.NullString + for rows.Next() { + var first_name sql.NullString + if err := rows.Scan(&first_name); err != nil { + return nil, err + } + items = append(items, first_name) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const selectUserByID = `-- name: SelectUserByID :many SELECT first_name from users where (? = id OR ? = 0) diff --git a/internal/endtoend/testdata/params_duplicate/mysql/query.sql b/internal/endtoend/testdata/params_duplicate/mysql/query.sql index cd661993c7..3924f57ad7 100644 --- a/internal/endtoend/testdata/params_duplicate/mysql/query.sql +++ b/internal/endtoend/testdata/params_duplicate/mysql/query.sql @@ -11,3 +11,8 @@ WHERE first_name = sqlc.arg(name) /* name: SelectUserQuestion :many */ SELECT first_name from users where (? = id OR ? = 0); + +/* name: SelectUserByAgeCast :many */ +SELECT first_name FROM users +WHERE age > CAST(sqlc.arg(threshold) AS SIGNED) + OR age < CAST(sqlc.arg(threshold) AS SIGNED); diff --git a/internal/endtoend/testdata/params_duplicate/mysql/schema.sql b/internal/endtoend/testdata/params_duplicate/mysql/schema.sql index e93e087e50..497fa29b7d 100644 --- a/internal/endtoend/testdata/params_duplicate/mysql/schema.sql +++ b/internal/endtoend/testdata/params_duplicate/mysql/schema.sql @@ -1,5 +1,6 @@ CREATE TABLE users ( id integer NOT NULL AUTO_INCREMENT PRIMARY KEY, first_name varchar(255), - last_name varchar(255) + last_name varchar(255), + age int ) ENGINE=InnoDB;