diff --git a/internal/codegen/golang/imports.go b/internal/codegen/golang/imports.go index 76964248ef..84fb7abdfc 100644 --- a/internal/codegen/golang/imports.go +++ b/internal/codegen/golang/imports.go @@ -376,7 +376,24 @@ func (i *importer) queryImports(filename string) fileImports { } if !q.Arg.isEmpty() { if q.Arg.IsStruct() { + // A single named parameter referenced as both a regular + // argument (e.g. `sqlc.narg(x) IS NULL`) and as a + // `sqlc.slice('x')` produces two struct fields sharing + // the same name and type, but only the slice variant + // carries the IsSqlcSlice flag. The non-slice duplicate + // must not be counted as a `[]T` arg requiring lib/pq, + // because the generated code expands the slice in-place + // via `/*SLICE:...*/?` and never calls pq.Array on it. + sqlcSliceNames := map[string]struct{}{} for _, f := range q.Arg.Struct.Fields { + if f.HasSqlcSlice() { + sqlcSliceNames[f.Name] = struct{}{} + } + } + for _, f := range q.Arg.Struct.Fields { + if _, ok := sqlcSliceNames[f.Name]; ok { + continue + } if strings.HasPrefix(f.Type, "[]") && f.Type != "[]byte" && !f.HasSqlcSlice() { return true } diff --git a/internal/endtoend/testdata/mysql_slice_narg_no_pq_3783/mysql/go/db.go b/internal/endtoend/testdata/mysql_slice_narg_no_pq_3783/mysql/go/db.go new file mode 100644 index 0000000000..80dd6ab1f6 --- /dev/null +++ b/internal/endtoend/testdata/mysql_slice_narg_no_pq_3783/mysql/go/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/endtoend/testdata/mysql_slice_narg_no_pq_3783/mysql/go/models.go b/internal/endtoend/testdata/mysql_slice_narg_no_pq_3783/mysql/go/models.go new file mode 100644 index 0000000000..2208139112 --- /dev/null +++ b/internal/endtoend/testdata/mysql_slice_narg_no_pq_3783/mysql/go/models.go @@ -0,0 +1,15 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "database/sql" +) + +type Author struct { + ID int64 + Name string + Bio sql.NullString +} diff --git a/internal/endtoend/testdata/mysql_slice_narg_no_pq_3783/mysql/go/query.sql.go b/internal/endtoend/testdata/mysql_slice_narg_no_pq_3783/mysql/go/query.sql.go new file mode 100644 index 0000000000..239139e102 --- /dev/null +++ b/internal/endtoend/testdata/mysql_slice_narg_no_pq_3783/mysql/go/query.sql.go @@ -0,0 +1,68 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: query.sql + +package querytest + +import ( + "context" + "database/sql" + "strings" +) + +const causesPgToBeImported = `-- name: CausesPgToBeImported :many +SELECT + id AS author_id, + name AS author_name, + bio AS author_bio +FROM + authors +WHERE + (? IS NULL OR id IN (/*SLICE:author_ids*/?)) +` + +type CausesPgToBeImportedParams struct { + AuthorIds []int64 +} + +type CausesPgToBeImportedRow struct { + AuthorID int64 + AuthorName string + AuthorBio sql.NullString +} + +// https://github.com/sqlc-dev/sqlc/issues/3783 +func (q *Queries) CausesPgToBeImported(ctx context.Context, arg CausesPgToBeImportedParams) ([]CausesPgToBeImportedRow, error) { + query := causesPgToBeImported + var queryParams []interface{} + queryParams = append(queryParams, arg.AuthorIds) + if len(arg.AuthorIds) > 0 { + for _, v := range arg.AuthorIds { + queryParams = append(queryParams, v) + } + query = strings.Replace(query, "/*SLICE:author_ids*/?", strings.Repeat(",?", len(arg.AuthorIds))[1:], 1) + } else { + query = strings.Replace(query, "/*SLICE:author_ids*/?", "NULL", 1) + } + rows, err := q.db.QueryContext(ctx, query, queryParams...) + if err != nil { + return nil, err + } + defer rows.Close() + var items []CausesPgToBeImportedRow + for rows.Next() { + var i CausesPgToBeImportedRow + if err := rows.Scan(&i.AuthorID, &i.AuthorName, &i.AuthorBio); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/internal/endtoend/testdata/mysql_slice_narg_no_pq_3783/mysql/query.sql b/internal/endtoend/testdata/mysql_slice_narg_no_pq_3783/mysql/query.sql new file mode 100644 index 0000000000..1bb78072e5 --- /dev/null +++ b/internal/endtoend/testdata/mysql_slice_narg_no_pq_3783/mysql/query.sql @@ -0,0 +1,10 @@ +-- https://github.com/sqlc-dev/sqlc/issues/3783 +-- name: CausesPgToBeImported :many +SELECT + id AS author_id, + name AS author_name, + bio AS author_bio +FROM + authors +WHERE + (sqlc.narg('author_ids') IS NULL OR id IN (sqlc.slice('author_ids'))); diff --git a/internal/endtoend/testdata/mysql_slice_narg_no_pq_3783/mysql/schema.sql b/internal/endtoend/testdata/mysql_slice_narg_no_pq_3783/mysql/schema.sql new file mode 100644 index 0000000000..5333d484a6 --- /dev/null +++ b/internal/endtoend/testdata/mysql_slice_narg_no_pq_3783/mysql/schema.sql @@ -0,0 +1,5 @@ +CREATE TABLE authors ( + id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + name text NOT NULL, + bio text +); diff --git a/internal/endtoend/testdata/mysql_slice_narg_no_pq_3783/mysql/sqlc.json b/internal/endtoend/testdata/mysql_slice_narg_no_pq_3783/mysql/sqlc.json new file mode 100644 index 0000000000..974aa9ff9e --- /dev/null +++ b/internal/endtoend/testdata/mysql_slice_narg_no_pq_3783/mysql/sqlc.json @@ -0,0 +1,12 @@ +{ + "version": "1", + "packages": [ + { + "engine": "mysql", + "path": "go", + "name": "querytest", + "schema": "schema.sql", + "queries": "query.sql" + } + ] +}