diff --git a/internal/compiler/engine.go b/internal/compiler/engine.go index b830faaa19..c1b7f7e5ec 100644 --- a/internal/compiler/engine.go +++ b/internal/compiler/engine.go @@ -38,6 +38,13 @@ type Compiler struct { coreAnalysis bool coreDialect core.Option + // newParser builds a parser for the configured engine, and is set for + // every engine either path supports. The core path analyzes statements + // concurrently, and a parser holds enough state that two goroutines cannot + // share one, so a goroutine that has to parse something of its own — the + // query text it just rewrote — builds its own rather than taking c.parser. + newParser func() Parser + schema []string } @@ -76,7 +83,7 @@ func NewCompiler(conf config.SQL, combo config.CombinedSettings, parserOpts opts switch conf.Engine { case config.EngineSQLite: - c.parser = sqlite.NewParser() + c.newParser = func() Parser { return sqlite.NewParser() } c.catalog = sqlite.NewCatalog() c.selector = newSQLiteSelector() @@ -90,11 +97,11 @@ func NewCompiler(conf config.SQL, combo config.CombinedSettings, parserOpts opts } } case config.EngineMySQL: - c.parser = dolphin.NewParser() + c.newParser = func() Parser { return dolphin.NewParser() } c.catalog = dolphin.NewCatalog() c.selector = newDefaultSelector() case config.EnginePostgreSQL: - c.parser = postgresql.NewParser() + c.newParser = func() Parser { return postgresql.NewParser() } c.catalog = postgresql.NewCatalog() c.selector = newDefaultSelector() @@ -110,6 +117,7 @@ func NewCompiler(conf config.SQL, combo config.CombinedSettings, parserOpts opts default: return nil, fmt.Errorf("unknown engine: %s", conf.Engine) } + c.parser = c.newParser() return c, nil } @@ -119,28 +127,29 @@ func (c *Compiler) initCore() error { var dialect core.Option switch c.conf.Engine { case config.EngineSQLite: - c.parser = sqlite.NewParser() + c.newParser = func() Parser { return sqlite.NewParser() } c.selector = newSQLiteSelector() dialect = sqlite.Dialect() case config.EngineMySQL: - c.parser = dolphin.NewParser() + c.newParser = func() Parser { return dolphin.NewParser() } c.selector = newDefaultSelector() dialect = dolphin.Dialect() case config.EnginePostgreSQL: - c.parser = postgresql.NewParser() + c.newParser = func() Parser { return postgresql.NewParser() } c.selector = newDefaultSelector() dialect = postgresql.Dialect() case config.EngineClickHouse: - c.parser = clickhouse.NewParser() + c.newParser = func() Parser { return clickhouse.NewParser() } c.selector = newDefaultSelector() dialect = clickhouse.Dialect() case config.EngineGoogleSQL: - c.parser = googlesql.NewParser() + c.newParser = func() Parser { return googlesql.NewParser() } c.selector = newDefaultSelector() dialect = googlesql.Dialect() default: return fmt.Errorf("unknown engine: %s", c.conf.Engine) } + c.parser = c.newParser() c.coreDialect = dialect return nil } diff --git a/internal/compiler/expand.go b/internal/compiler/expand.go index 98dd82cbdc..e76853c5a3 100644 --- a/internal/compiler/expand.go +++ b/internal/compiler/expand.go @@ -78,6 +78,38 @@ func (c *Compiler) quote(x string) string { } } +// starOldFunc measures how much of the query text a star reference occupies, +// so an edit replaces the reference and nothing else. Each part is measured +// both bare and quoted: an embed was rewritten to "table.*" in the query text, +// preserving the way the user quoted the table, so it is measured the same way +// as a star reference the user wrote. +func (c *Compiler) starOldFunc(parts []string) func(string) int { + old := make([]string, 0, len(parts)) + for _, p := range parts { + if p == "*" { + old = append(old, p) + } else { + old = append(old, c.quoteIdent(p)) + } + } + return func(s string) int { + length := 0 + for i, o := range old { + if hasSeparator := i > 0; hasSeparator { + length++ + } + if strings.HasPrefix(s[length:], o) { + length += len(o) + } else if quoted := c.quote(o); strings.HasPrefix(s[length:], quoted) { + length += len(quoted) + } else { + length += len(o) + } + } + return length + } +} + func (c *Compiler) expandStmt(qc *QueryCatalog, raw *ast.RawStmt, node ast.Node) ([]source.Edit, error) { tables, err := c.sourceTables(qc, node) if err != nil { @@ -157,37 +189,9 @@ func (c *Compiler) expandStmt(qc *QueryCatalog, raw *ast.RawStmt, node ast.Node) cols = append(cols, cname) } } - var old []string - for _, p := range parts { - if p == "*" { - old = append(old, p) - } else { - old = append(old, c.quoteIdent(p)) - } - } - - // An embed was rewritten to "table.*" in the query text, so it is - // measured the same way as a star reference the user wrote. - oldFunc := func(s string) int { - length := 0 - for i, o := range old { - if hasSeparator := i > 0; hasSeparator { - length++ - } - if strings.HasPrefix(s[length:], o) { - length += len(o) - } else if quoted := c.quote(o); strings.HasPrefix(s[length:], quoted) { - length += len(quoted) - } else { - length += len(o) - } - } - return length - } - edits = append(edits, source.Edit{ Location: res.Location - raw.StmtLocation, - OldFunc: oldFunc, + OldFunc: c.starOldFunc(parts), New: strings.Join(cols, ", "), }) } diff --git a/internal/compiler/expand_core.go b/internal/compiler/expand_core.go new file mode 100644 index 0000000000..ab818c38e1 --- /dev/null +++ b/internal/compiler/expand_core.go @@ -0,0 +1,76 @@ +package compiler + +import ( + "strings" + + "github.com/sqlc-dev/sqlc/internal/core" + "github.com/sqlc-dev/sqlc/internal/source" + "github.com/sqlc-dev/sqlc/internal/sql/ast" +) + +// expandCore rewrites the stars in a query's text with the columns the core +// analyzer resolved them to. The analyzer has already walked the statement and +// its subqueries, so there is nothing left to look up here: what remains is +// deciding how each name is written, which is the engine's business and not +// the core's. +func (c *Compiler) expandCore(raw *ast.RawStmt, stars []core.StarExpansion) []source.Edit { + if len(stars) == 0 { + return nil + } + edits := make([]source.Edit, 0, len(stars)) + seen := make(map[int]bool, len(stars)) + for _, star := range stars { + // The analyzer types an expression again when a later clause refers to + // the output name it was given, so "SELECT (SELECT * FROM t) AS x ... + // GROUP BY x" reports the star in the subquery once per pass. The + // passes agree on what the star covers, and editing the same reference + // twice would overlap, so only the first is kept. + if seen[star.Location] { + continue + } + seen[star.Location] = true + + // Everything before the star qualifies it: "foo.*" is scoped to foo, + // while a bare "*" covers every relation in the FROM clause. + scope := strings.Join(star.Fields[:len(star.Fields)-1], ".") + + // An unqualified star that covers more than one relation may name the + // same column twice, so those are written with their relation. + counts := map[string]int{} + if scope == "" { + for _, col := range star.Columns { + counts[col.Name]++ + } + } + + cols := make([]string, 0, len(star.Columns)) + for _, col := range star.Columns { + cname := col.Name + if star.Alias != "" { + cname = star.Alias + } + cname = c.quoteIdent(cname) + if scope != "" { + cname = c.quoteIdent(scope) + "." + cname + } + if counts[cname] > 1 { + cname = c.quoteIdent(col.Relation) + "." + cname + } + + // This is important for SQLite in particular which needs to wrap + // jsonb column values with `json(colname)` so they're in a publicly + // usable format (i.e. not jsonb). + cols = append(cols, c.selector.ColumnExpr(cname, &Column{ + Name: col.Name, + DataType: col.DataType, + })) + } + + edits = append(edits, source.Edit{ + Location: star.Location - raw.StmtLocation, + OldFunc: c.starOldFunc(star.Fields), + New: strings.Join(cols, ", "), + }) + } + return edits +} diff --git a/internal/compiler/parse_core.go b/internal/compiler/parse_core.go index 920d3140fa..6cae7586c3 100644 --- a/internal/compiler/parse_core.go +++ b/internal/compiler/parse_core.go @@ -2,6 +2,7 @@ package compiler import ( "errors" + "fmt" "strings" "github.com/sqlc-dev/sqlc/internal/core" @@ -64,6 +65,17 @@ func (c *Compiler) parseQueryCore(raw *ast.RawStmt, src string, pre *preprocess. for _, p := range res.Parameters { params = append(params, Parameter{Number: p.Number, Column: coreParamColumn(p, namedParams)}) } + expanded, err = source.Mutate(rawSQL, c.expandCore(raw, res.Stars)) + if err != nil { + return nil, err + } + } + + // If the query string was edited, make sure the syntax is valid + if expanded != rawSQL { + if _, err := c.newParser().Parse(strings.NewReader(expanded)); err != nil { + return nil, fmt.Errorf("edited query syntax is invalid: %w", err) + } } trimmed, comments, err := source.StripComments(expanded) diff --git a/internal/core/analysis.go b/internal/core/analysis.go index e8e2edf851..822c12c411 100644 --- a/internal/core/analysis.go +++ b/internal/core/analysis.go @@ -10,9 +10,39 @@ const ( ) type PrepareResult struct { - Command Command `json:"command,omitempty"` - Columns []Column `json:"columns"` - Parameters []Parameter `json:"parameters"` + Command Command `json:"command,omitempty"` + Columns []Column `json:"columns"` + Parameters []Parameter `json:"parameters"` + Stars []StarExpansion `json:"stars,omitempty"` +} + +// StarExpansion is what a star in a target list stands for. The analyzer +// resolves the reference against the query's scope and reports the columns it +// covers; rewriting the query text with them is the caller's to do, since only +// it knows how the engine quotes an identifier. +type StarExpansion struct { + // Location is where the target the star belongs to starts, measured the + // way the AST measures a node: from the beginning of the file the + // statement was parsed from. + Location int `json:"location"` + + // Fields is the reference as it was written, with the star as its last + // element: ["*"] for a bare star and ["foo", "*"] for a qualified one. + Fields []string `json:"fields"` + + // Alias is the output name the target was given, if any. + Alias string `json:"alias,omitempty"` + + Columns []StarColumn `json:"columns"` +} + +// StarColumn is a single column a star expanded to. +type StarColumn struct { + // Relation is the name the column's relation goes by in the query, which + // is its alias when it was given one. + Relation string `json:"relation,omitempty"` + Name string `json:"name"` + DataType string `json:"data_type,omitempty"` } type ColumnSource struct { diff --git a/internal/core/analyzer/analyzer.go b/internal/core/analyzer/analyzer.go index 7096a70bb7..4608ff1bdc 100644 --- a/internal/core/analyzer/analyzer.go +++ b/internal/core/analyzer/analyzer.go @@ -14,6 +14,7 @@ func Prepare(cat *core.Catalog, stmt ast.Node) (core.PrepareResult, error) { a := &analyzer{ cat: cat, params: map[int]core.Parameter{}, + stars: &[]core.StarExpansion{}, } switch s := stmt.(type) { case *ast.SelectStmt: @@ -63,6 +64,18 @@ type analyzer struct { // resolving guards against an alias that refers to itself. resolving map[string]bool + + // stars are the expansions every star in the statement asked for, shared + // with the analyzers of the queries nested in it so one statement reports + // all of them. + stars *[]core.StarExpansion +} + +func (a *analyzer) recordStar(s core.StarExpansion) { + if a.stars == nil { + return + } + *a.stars = append(*a.stars, s) } // subquery analyzes a nested SELECT. It shares the parameter set, so a @@ -74,6 +87,7 @@ func (a *analyzer) subquery(s *ast.SelectStmt) (*analyzer, error) { params: a.params, outer: a.scope, ctes: a.ctes, + stars: a.stars, } if err := sub.analyzeSelect(s); err != nil { return nil, err @@ -105,11 +119,15 @@ func derivedRel(alias string, cols []core.Column) scopeRel { } func (a *analyzer) result() core.PrepareResult { - return core.PrepareResult{ + res := core.PrepareResult{ Command: a.command, Columns: a.columns, Parameters: orderedParams(a.params), } + if a.stars != nil { + res.Stars = *a.stars + } + return res } func orderedParams(m map[int]core.Parameter) []core.Parameter { diff --git a/internal/core/analyzer/projection.go b/internal/core/analyzer/projection.go index 9547eed833..29f1f7c548 100644 --- a/internal/core/analyzer/projection.go +++ b/internal/core/analyzer/projection.go @@ -14,7 +14,7 @@ func (a *analyzer) projectTarget(rt *ast.ResTarget) error { if cr, ok := rt.Val.(*ast.ColumnRef); ok { fields = flattenFields(cr.Fields) if isStar(fields) { - a.emitStar(fields) + a.emitStar(rt, fields) return nil } } @@ -79,16 +79,23 @@ func isStar(fields []string) bool { return len(fields) > 0 && fields[len(fields)-1] == "*" } -func (a *analyzer) emitStar(fields []string) { +func (a *analyzer) emitStar(rt *ast.ResTarget, fields []string) { relName := "" if len(fields) > 1 { relName = fields[0] } + // The star is reported along with the columns it covers, so the query text + // can be rewritten to name them. + star := core.StarExpansion{Location: rt.Location, Fields: fields} + if rt.Name != nil { + star.Alias = *rt.Name + } for _, rel := range a.scope.rels { if relName != "" && rel.alias != relName { continue } a.columns = slices.Grow(a.columns, len(rel.cols)) + star.Columns = slices.Grow(star.Columns, len(rel.cols)) for _, c := range rel.cols { col := core.Column{ Name: c.Name, @@ -100,6 +107,12 @@ func (a *analyzer) emitStar(fields []string) { col.DataType, col.IsArray = a.typeNameOf(exprType{typeOID: c.TypeOID}) a.decorateSource(&col, c.AttOID, rel.alias) a.columns = append(a.columns, col) + star.Columns = append(star.Columns, core.StarColumn{ + Relation: rel.alias, + Name: c.Name, + DataType: col.DataType, + }) } } + a.recordStar(star) } diff --git a/internal/endtoend/testdata/experiment_coreanalyzer/mysql/go/query.sql.go b/internal/endtoend/testdata/experiment_coreanalyzer/mysql/go/query.sql.go index ea37ba27da..69c870213b 100644 --- a/internal/endtoend/testdata/experiment_coreanalyzer/mysql/go/query.sql.go +++ b/internal/endtoend/testdata/experiment_coreanalyzer/mysql/go/query.sql.go @@ -36,7 +36,7 @@ func (q *Queries) DeleteAuthor(ctx context.Context, id int64) error { } const getAuthor = `-- name: GetAuthor :one -SELECT * FROM authors +SELECT id, name, bio FROM authors WHERE id = ? ` diff --git a/internal/endtoend/testdata/experiment_coreanalyzer/postgresql/stdlib/go/query.sql.go b/internal/endtoend/testdata/experiment_coreanalyzer/postgresql/stdlib/go/query.sql.go index 706ce0e529..2e2ced077f 100644 --- a/internal/endtoend/testdata/experiment_coreanalyzer/postgresql/stdlib/go/query.sql.go +++ b/internal/endtoend/testdata/experiment_coreanalyzer/postgresql/stdlib/go/query.sql.go @@ -15,7 +15,7 @@ import ( const createAuthor = `-- name: CreateAuthor :one INSERT INTO authors (id, name, bio) VALUES ($1, $2, $3) -RETURNING * +RETURNING id, name, bio, tags ` type CreateAuthorParams struct { @@ -47,7 +47,7 @@ func (q *Queries) DeleteAuthor(ctx context.Context, id int64) error { } const getAuthor = `-- name: GetAuthor :one -SELECT * FROM authors +SELECT id, name, bio, tags FROM authors WHERE id = $1 ` diff --git a/internal/endtoend/testdata/experiment_coreanalyzer/sqlite/go/query.sql.go b/internal/endtoend/testdata/experiment_coreanalyzer/sqlite/go/query.sql.go index db27b7ced6..cc56c0a7d5 100644 --- a/internal/endtoend/testdata/experiment_coreanalyzer/sqlite/go/query.sql.go +++ b/internal/endtoend/testdata/experiment_coreanalyzer/sqlite/go/query.sql.go @@ -13,7 +13,7 @@ import ( const createAuthor = `-- name: CreateAuthor :one INSERT INTO authors (id, name, bio) VALUES (?, ?, ?) -RETURNING * +RETURNING id, name, bio ` type CreateAuthorParams struct { @@ -40,7 +40,7 @@ func (q *Queries) DeleteAuthor(ctx context.Context, id int64) error { } const getAuthor = `-- name: GetAuthor :one -SELECT * FROM authors +SELECT id, name, bio FROM authors WHERE id = ? ` diff --git a/internal/endtoend/testdata/star_expansion_core/mysql/exec.json b/internal/endtoend/testdata/star_expansion_core/mysql/exec.json new file mode 100644 index 0000000000..8da46d61b5 --- /dev/null +++ b/internal/endtoend/testdata/star_expansion_core/mysql/exec.json @@ -0,0 +1,6 @@ +{ + "command": "generate", + "env": { + "SQLCEXPERIMENT": "coreanalyzer" + } +} diff --git a/internal/endtoend/testdata/star_expansion_core/mysql/go/db.go b/internal/endtoend/testdata/star_expansion_core/mysql/go/db.go new file mode 100644 index 0000000000..80dd6ab1f6 --- /dev/null +++ b/internal/endtoend/testdata/star_expansion_core/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/star_expansion_core/mysql/go/models.go b/internal/endtoend/testdata/star_expansion_core/mysql/go/models.go new file mode 100644 index 0000000000..3e874b1005 --- /dev/null +++ b/internal/endtoend/testdata/star_expansion_core/mysql/go/models.go @@ -0,0 +1,24 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "database/sql" +) + +type Bar struct { + A sql.NullString + C sql.NullString +} + +type Baz struct { + Z sql.NullString +} + +type Foo struct { + A sql.NullString + B sql.NullString + Group sql.NullString +} diff --git a/internal/endtoend/testdata/star_expansion_core/mysql/go/query.sql.go b/internal/endtoend/testdata/star_expansion_core/mysql/go/query.sql.go new file mode 100644 index 0000000000..10c1e7be16 --- /dev/null +++ b/internal/endtoend/testdata/star_expansion_core/mysql/go/query.sql.go @@ -0,0 +1,209 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: query.sql + +package querytest + +import ( + "context" + "database/sql" +) + +const starExpansion = `-- name: StarExpansion :many +SELECT a, b, ` + "`" + `group` + "`" + `, a, b, ` + "`" + `group` + "`" + `, foo.a, foo.b, foo.` + "`" + `group` + "`" + ` FROM foo +` + +type StarExpansionRow struct { + A sql.NullString + B sql.NullString + Group sql.NullString + A_2 sql.NullString + B_2 sql.NullString + Group_2 sql.NullString + A_3 sql.NullString + B_3 sql.NullString + Group_3 sql.NullString +} + +func (q *Queries) StarExpansion(ctx context.Context) ([]StarExpansionRow, error) { + rows, err := q.db.QueryContext(ctx, starExpansion) + if err != nil { + return nil, err + } + defer rows.Close() + var items []StarExpansionRow + for rows.Next() { + var i StarExpansionRow + if err := rows.Scan( + &i.A, + &i.B, + &i.Group, + &i.A_2, + &i.B_2, + &i.Group_2, + &i.A_3, + &i.B_3, + &i.Group_3, + ); 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 +} + +const starExpansionAliasedSubquery = `-- name: StarExpansionAliasedSubquery :many +SELECT (SELECT z FROM baz LIMIT 1) AS x FROM foo GROUP BY x +` + +func (q *Queries) StarExpansionAliasedSubquery(ctx context.Context) ([]sql.NullString, error) { + rows, err := q.db.QueryContext(ctx, starExpansionAliasedSubquery) + if err != nil { + return nil, err + } + defer rows.Close() + var items []sql.NullString + for rows.Next() { + var x sql.NullString + if err := rows.Scan(&x); err != nil { + return nil, err + } + items = append(items, x) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const starExpansionCTE = `-- name: StarExpansionCTE :many +WITH t AS (SELECT a, c FROM bar) SELECT a, c FROM t +` + +func (q *Queries) StarExpansionCTE(ctx context.Context) ([]Bar, error) { + rows, err := q.db.QueryContext(ctx, starExpansionCTE) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Bar + for rows.Next() { + var i Bar + if err := rows.Scan(&i.A, &i.C); 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 +} + +const starExpansionJoin = `-- name: StarExpansionJoin :many +SELECT foo.a, b, ` + "`" + `group` + "`" + `, bar.a, c FROM foo, bar +` + +type StarExpansionJoinRow struct { + A sql.NullString + B sql.NullString + Group sql.NullString + A_2 sql.NullString + C sql.NullString +} + +func (q *Queries) StarExpansionJoin(ctx context.Context) ([]StarExpansionJoinRow, error) { + rows, err := q.db.QueryContext(ctx, starExpansionJoin) + if err != nil { + return nil, err + } + defer rows.Close() + var items []StarExpansionJoinRow + for rows.Next() { + var i StarExpansionJoinRow + if err := rows.Scan( + &i.A, + &i.B, + &i.Group, + &i.A_2, + &i.C, + ); 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 +} + +const starExpansionSubquery = `-- name: StarExpansionSubquery :many +SELECT a, c FROM (SELECT a, c FROM bar) sub +` + +func (q *Queries) StarExpansionSubquery(ctx context.Context) ([]Bar, error) { + rows, err := q.db.QueryContext(ctx, starExpansionSubquery) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Bar + for rows.Next() { + var i Bar + if err := rows.Scan(&i.A, &i.C); 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 +} + +const starQuotedExpansion = `-- name: StarQuotedExpansion :many +SELECT t.a, t.b, t.` + "`" + `group` + "`" + ` FROM foo ` + "`" + `t` + "`" + ` +` + +func (q *Queries) StarQuotedExpansion(ctx context.Context) ([]Foo, error) { + rows, err := q.db.QueryContext(ctx, starQuotedExpansion) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Foo + for rows.Next() { + var i Foo + if err := rows.Scan(&i.A, &i.B, &i.Group); 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/star_expansion_core/mysql/query.sql b/internal/endtoend/testdata/star_expansion_core/mysql/query.sql new file mode 100644 index 0000000000..89c1b257b0 --- /dev/null +++ b/internal/endtoend/testdata/star_expansion_core/mysql/query.sql @@ -0,0 +1,17 @@ +-- name: StarExpansion :many +SELECT *, *, foo.* FROM foo; + +-- name: StarQuotedExpansion :many +SELECT `t`.* FROM foo `t`; + +-- name: StarExpansionJoin :many +SELECT * FROM foo, bar; + +-- name: StarExpansionSubquery :many +SELECT * FROM (SELECT * FROM bar) sub; + +-- name: StarExpansionCTE :many +WITH t AS (SELECT * FROM bar) SELECT * FROM t; + +-- name: StarExpansionAliasedSubquery :many +SELECT (SELECT * FROM baz LIMIT 1) AS x FROM foo GROUP BY x; diff --git a/internal/endtoend/testdata/star_expansion_core/mysql/schema.sql b/internal/endtoend/testdata/star_expansion_core/mysql/schema.sql new file mode 100644 index 0000000000..1a6957d981 --- /dev/null +++ b/internal/endtoend/testdata/star_expansion_core/mysql/schema.sql @@ -0,0 +1,3 @@ +CREATE TABLE foo (a text, b text, `group` text); +CREATE TABLE bar (a text, c text); +CREATE TABLE baz (z text); diff --git a/internal/endtoend/testdata/star_expansion_core/mysql/sqlc.json b/internal/endtoend/testdata/star_expansion_core/mysql/sqlc.json new file mode 100644 index 0000000000..974aa9ff9e --- /dev/null +++ b/internal/endtoend/testdata/star_expansion_core/mysql/sqlc.json @@ -0,0 +1,12 @@ +{ + "version": "1", + "packages": [ + { + "engine": "mysql", + "path": "go", + "name": "querytest", + "schema": "schema.sql", + "queries": "query.sql" + } + ] +} diff --git a/internal/endtoend/testdata/star_expansion_core/postgresql/exec.json b/internal/endtoend/testdata/star_expansion_core/postgresql/exec.json new file mode 100644 index 0000000000..8da46d61b5 --- /dev/null +++ b/internal/endtoend/testdata/star_expansion_core/postgresql/exec.json @@ -0,0 +1,6 @@ +{ + "command": "generate", + "env": { + "SQLCEXPERIMENT": "coreanalyzer" + } +} diff --git a/internal/endtoend/testdata/star_expansion_core/postgresql/go/db.go b/internal/endtoend/testdata/star_expansion_core/postgresql/go/db.go new file mode 100644 index 0000000000..80dd6ab1f6 --- /dev/null +++ b/internal/endtoend/testdata/star_expansion_core/postgresql/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/star_expansion_core/postgresql/go/models.go b/internal/endtoend/testdata/star_expansion_core/postgresql/go/models.go new file mode 100644 index 0000000000..3e874b1005 --- /dev/null +++ b/internal/endtoend/testdata/star_expansion_core/postgresql/go/models.go @@ -0,0 +1,24 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "database/sql" +) + +type Bar struct { + A sql.NullString + C sql.NullString +} + +type Baz struct { + Z sql.NullString +} + +type Foo struct { + A sql.NullString + B sql.NullString + Group sql.NullString +} diff --git a/internal/endtoend/testdata/star_expansion_core/postgresql/go/query.sql.go b/internal/endtoend/testdata/star_expansion_core/postgresql/go/query.sql.go new file mode 100644 index 0000000000..3a688f9404 --- /dev/null +++ b/internal/endtoend/testdata/star_expansion_core/postgresql/go/query.sql.go @@ -0,0 +1,225 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: query.sql + +package querytest + +import ( + "context" + "database/sql" +) + +const starExpansion = `-- name: StarExpansion :many +SELECT a, b, "group", a, b, "group", foo.a, foo.b, foo."group" FROM foo +` + +type StarExpansionRow struct { + A sql.NullString + B sql.NullString + Group sql.NullString + A_2 sql.NullString + B_2 sql.NullString + Group_2 sql.NullString + A_3 sql.NullString + B_3 sql.NullString + Group_3 sql.NullString +} + +func (q *Queries) StarExpansion(ctx context.Context) ([]StarExpansionRow, error) { + rows, err := q.db.QueryContext(ctx, starExpansion) + if err != nil { + return nil, err + } + defer rows.Close() + var items []StarExpansionRow + for rows.Next() { + var i StarExpansionRow + if err := rows.Scan( + &i.A, + &i.B, + &i.Group, + &i.A_2, + &i.B_2, + &i.Group_2, + &i.A_3, + &i.B_3, + &i.Group_3, + ); 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 +} + +const starExpansionAliasedSubquery = `-- name: StarExpansionAliasedSubquery :many +SELECT (SELECT z FROM baz LIMIT 1) AS x FROM foo GROUP BY x +` + +func (q *Queries) StarExpansionAliasedSubquery(ctx context.Context) ([]sql.NullString, error) { + rows, err := q.db.QueryContext(ctx, starExpansionAliasedSubquery) + if err != nil { + return nil, err + } + defer rows.Close() + var items []sql.NullString + for rows.Next() { + var x sql.NullString + if err := rows.Scan(&x); err != nil { + return nil, err + } + items = append(items, x) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const starExpansionCTE = `-- name: StarExpansionCTE :many +WITH t AS (SELECT a, c FROM bar) SELECT a, c FROM t +` + +func (q *Queries) StarExpansionCTE(ctx context.Context) ([]Bar, error) { + rows, err := q.db.QueryContext(ctx, starExpansionCTE) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Bar + for rows.Next() { + var i Bar + if err := rows.Scan(&i.A, &i.C); 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 +} + +const starExpansionJoin = `-- name: StarExpansionJoin :many +SELECT foo.a, b, "group", bar.a, c FROM foo, bar +` + +type StarExpansionJoinRow struct { + A sql.NullString + B sql.NullString + Group sql.NullString + A_2 sql.NullString + C sql.NullString +} + +func (q *Queries) StarExpansionJoin(ctx context.Context) ([]StarExpansionJoinRow, error) { + rows, err := q.db.QueryContext(ctx, starExpansionJoin) + if err != nil { + return nil, err + } + defer rows.Close() + var items []StarExpansionJoinRow + for rows.Next() { + var i StarExpansionJoinRow + if err := rows.Scan( + &i.A, + &i.B, + &i.Group, + &i.A_2, + &i.C, + ); 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 +} + +const starExpansionReturning = `-- name: StarExpansionReturning :one +INSERT INTO bar (a, c) VALUES ($1, $2) RETURNING a, c +` + +type StarExpansionReturningParams struct { + A sql.NullString + C sql.NullString +} + +func (q *Queries) StarExpansionReturning(ctx context.Context, arg StarExpansionReturningParams) (Bar, error) { + row := q.db.QueryRowContext(ctx, starExpansionReturning, arg.A, arg.C) + var i Bar + err := row.Scan(&i.A, &i.C) + return i, err +} + +const starExpansionSubquery = `-- name: StarExpansionSubquery :many +SELECT a, c FROM (SELECT a, c FROM bar) sub +` + +func (q *Queries) StarExpansionSubquery(ctx context.Context) ([]Bar, error) { + rows, err := q.db.QueryContext(ctx, starExpansionSubquery) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Bar + for rows.Next() { + var i Bar + if err := rows.Scan(&i.A, &i.C); 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 +} + +const starQuotedExpansion = `-- name: StarQuotedExpansion :many +SELECT t.a, t.b, t."group" FROM foo "t" +` + +func (q *Queries) StarQuotedExpansion(ctx context.Context) ([]Foo, error) { + rows, err := q.db.QueryContext(ctx, starQuotedExpansion) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Foo + for rows.Next() { + var i Foo + if err := rows.Scan(&i.A, &i.B, &i.Group); 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/star_expansion_core/postgresql/query.sql b/internal/endtoend/testdata/star_expansion_core/postgresql/query.sql new file mode 100644 index 0000000000..099f54dc6d --- /dev/null +++ b/internal/endtoend/testdata/star_expansion_core/postgresql/query.sql @@ -0,0 +1,20 @@ +-- name: StarExpansion :many +SELECT *, *, foo.* FROM foo; + +-- name: StarQuotedExpansion :many +SELECT "t".* FROM foo "t"; + +-- name: StarExpansionJoin :many +SELECT * FROM foo, bar; + +-- name: StarExpansionSubquery :many +SELECT * FROM (SELECT * FROM bar) sub; + +-- name: StarExpansionCTE :many +WITH t AS (SELECT * FROM bar) SELECT * FROM t; + +-- name: StarExpansionReturning :one +INSERT INTO bar (a, c) VALUES ($1, $2) RETURNING *; + +-- name: StarExpansionAliasedSubquery :many +SELECT (SELECT * FROM baz LIMIT 1) AS x FROM foo GROUP BY x; diff --git a/internal/endtoend/testdata/star_expansion_core/postgresql/schema.sql b/internal/endtoend/testdata/star_expansion_core/postgresql/schema.sql new file mode 100644 index 0000000000..126783157b --- /dev/null +++ b/internal/endtoend/testdata/star_expansion_core/postgresql/schema.sql @@ -0,0 +1,3 @@ +CREATE TABLE foo (a text, b text, "group" text); +CREATE TABLE bar (a text, c text); +CREATE TABLE baz (z text); diff --git a/internal/endtoend/testdata/star_expansion_core/postgresql/sqlc.json b/internal/endtoend/testdata/star_expansion_core/postgresql/sqlc.json new file mode 100644 index 0000000000..cd518671ac --- /dev/null +++ b/internal/endtoend/testdata/star_expansion_core/postgresql/sqlc.json @@ -0,0 +1,12 @@ +{ + "version": "1", + "packages": [ + { + "engine": "postgresql", + "path": "go", + "name": "querytest", + "schema": "schema.sql", + "queries": "query.sql" + } + ] +} diff --git a/internal/endtoend/testdata/star_expansion_core/sqlite/exec.json b/internal/endtoend/testdata/star_expansion_core/sqlite/exec.json new file mode 100644 index 0000000000..8da46d61b5 --- /dev/null +++ b/internal/endtoend/testdata/star_expansion_core/sqlite/exec.json @@ -0,0 +1,6 @@ +{ + "command": "generate", + "env": { + "SQLCEXPERIMENT": "coreanalyzer" + } +} diff --git a/internal/endtoend/testdata/star_expansion_core/sqlite/go/db.go b/internal/endtoend/testdata/star_expansion_core/sqlite/go/db.go new file mode 100644 index 0000000000..80dd6ab1f6 --- /dev/null +++ b/internal/endtoend/testdata/star_expansion_core/sqlite/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/star_expansion_core/sqlite/go/models.go b/internal/endtoend/testdata/star_expansion_core/sqlite/go/models.go new file mode 100644 index 0000000000..3e874b1005 --- /dev/null +++ b/internal/endtoend/testdata/star_expansion_core/sqlite/go/models.go @@ -0,0 +1,24 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "database/sql" +) + +type Bar struct { + A sql.NullString + C sql.NullString +} + +type Baz struct { + Z sql.NullString +} + +type Foo struct { + A sql.NullString + B sql.NullString + Group sql.NullString +} diff --git a/internal/endtoend/testdata/star_expansion_core/sqlite/go/query.sql.go b/internal/endtoend/testdata/star_expansion_core/sqlite/go/query.sql.go new file mode 100644 index 0000000000..1ad5fa7633 --- /dev/null +++ b/internal/endtoend/testdata/star_expansion_core/sqlite/go/query.sql.go @@ -0,0 +1,225 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: query.sql + +package querytest + +import ( + "context" + "database/sql" +) + +const starExpansion = `-- name: StarExpansion :many +SELECT a, b, "group", a, b, "group", foo.a, foo.b, foo."group" FROM foo +` + +type StarExpansionRow struct { + A sql.NullString + B sql.NullString + Group sql.NullString + A_2 sql.NullString + B_2 sql.NullString + Group_2 sql.NullString + A_3 sql.NullString + B_3 sql.NullString + Group_3 sql.NullString +} + +func (q *Queries) StarExpansion(ctx context.Context) ([]StarExpansionRow, error) { + rows, err := q.db.QueryContext(ctx, starExpansion) + if err != nil { + return nil, err + } + defer rows.Close() + var items []StarExpansionRow + for rows.Next() { + var i StarExpansionRow + if err := rows.Scan( + &i.A, + &i.B, + &i.Group, + &i.A_2, + &i.B_2, + &i.Group_2, + &i.A_3, + &i.B_3, + &i.Group_3, + ); 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 +} + +const starExpansionAliasedSubquery = `-- name: StarExpansionAliasedSubquery :many +SELECT (SELECT z FROM baz LIMIT 1) AS x FROM foo GROUP BY x +` + +func (q *Queries) StarExpansionAliasedSubquery(ctx context.Context) ([]sql.NullString, error) { + rows, err := q.db.QueryContext(ctx, starExpansionAliasedSubquery) + if err != nil { + return nil, err + } + defer rows.Close() + var items []sql.NullString + for rows.Next() { + var x sql.NullString + if err := rows.Scan(&x); err != nil { + return nil, err + } + items = append(items, x) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const starExpansionCTE = `-- name: StarExpansionCTE :many +WITH t AS (SELECT a, c FROM bar) SELECT a, c FROM t +` + +func (q *Queries) StarExpansionCTE(ctx context.Context) ([]Bar, error) { + rows, err := q.db.QueryContext(ctx, starExpansionCTE) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Bar + for rows.Next() { + var i Bar + if err := rows.Scan(&i.A, &i.C); 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 +} + +const starExpansionJoin = `-- name: StarExpansionJoin :many +SELECT foo.a, b, "group", bar.a, c FROM foo, bar +` + +type StarExpansionJoinRow struct { + A sql.NullString + B sql.NullString + Group sql.NullString + A_2 sql.NullString + C sql.NullString +} + +func (q *Queries) StarExpansionJoin(ctx context.Context) ([]StarExpansionJoinRow, error) { + rows, err := q.db.QueryContext(ctx, starExpansionJoin) + if err != nil { + return nil, err + } + defer rows.Close() + var items []StarExpansionJoinRow + for rows.Next() { + var i StarExpansionJoinRow + if err := rows.Scan( + &i.A, + &i.B, + &i.Group, + &i.A_2, + &i.C, + ); 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 +} + +const starExpansionReturning = `-- name: StarExpansionReturning :one +INSERT INTO bar (a, c) VALUES (?, ?) RETURNING a, c +` + +type StarExpansionReturningParams struct { + A sql.NullString + C sql.NullString +} + +func (q *Queries) StarExpansionReturning(ctx context.Context, arg StarExpansionReturningParams) (Bar, error) { + row := q.db.QueryRowContext(ctx, starExpansionReturning, arg.A, arg.C) + var i Bar + err := row.Scan(&i.A, &i.C) + return i, err +} + +const starExpansionSubquery = `-- name: StarExpansionSubquery :many +SELECT a, c FROM (SELECT a, c FROM bar) sub +` + +func (q *Queries) StarExpansionSubquery(ctx context.Context) ([]Bar, error) { + rows, err := q.db.QueryContext(ctx, starExpansionSubquery) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Bar + for rows.Next() { + var i Bar + if err := rows.Scan(&i.A, &i.C); 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 +} + +const starQuotedExpansion = `-- name: StarQuotedExpansion :many +SELECT t.a, t.b, t."group" FROM foo "t" +` + +func (q *Queries) StarQuotedExpansion(ctx context.Context) ([]Foo, error) { + rows, err := q.db.QueryContext(ctx, starQuotedExpansion) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Foo + for rows.Next() { + var i Foo + if err := rows.Scan(&i.A, &i.B, &i.Group); 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/star_expansion_core/sqlite/query.sql b/internal/endtoend/testdata/star_expansion_core/sqlite/query.sql new file mode 100644 index 0000000000..90351e9b99 --- /dev/null +++ b/internal/endtoend/testdata/star_expansion_core/sqlite/query.sql @@ -0,0 +1,20 @@ +-- name: StarExpansion :many +SELECT *, *, foo.* FROM foo; + +-- name: StarQuotedExpansion :many +SELECT "t".* FROM foo "t"; + +-- name: StarExpansionJoin :many +SELECT * FROM foo, bar; + +-- name: StarExpansionSubquery :many +SELECT * FROM (SELECT * FROM bar) sub; + +-- name: StarExpansionCTE :many +WITH t AS (SELECT * FROM bar) SELECT * FROM t; + +-- name: StarExpansionReturning :one +INSERT INTO bar (a, c) VALUES (?, ?) RETURNING *; + +-- name: StarExpansionAliasedSubquery :many +SELECT (SELECT * FROM baz LIMIT 1) AS x FROM foo GROUP BY x; diff --git a/internal/endtoend/testdata/star_expansion_core/sqlite/schema.sql b/internal/endtoend/testdata/star_expansion_core/sqlite/schema.sql new file mode 100644 index 0000000000..126783157b --- /dev/null +++ b/internal/endtoend/testdata/star_expansion_core/sqlite/schema.sql @@ -0,0 +1,3 @@ +CREATE TABLE foo (a text, b text, "group" text); +CREATE TABLE bar (a text, c text); +CREATE TABLE baz (z text); diff --git a/internal/endtoend/testdata/star_expansion_core/sqlite/sqlc.json b/internal/endtoend/testdata/star_expansion_core/sqlite/sqlc.json new file mode 100644 index 0000000000..1f9d43df5d --- /dev/null +++ b/internal/endtoend/testdata/star_expansion_core/sqlite/sqlc.json @@ -0,0 +1,12 @@ +{ + "version": "1", + "packages": [ + { + "engine": "sqlite", + "path": "go", + "name": "querytest", + "schema": "schema.sql", + "queries": "query.sql" + } + ] +}