Skip to content

Commit 2a8278c

Browse files
committed
compiler: fix what the star dedupe claims, and prove it is needed
Review of the previous commit turned up three things. The dedupe in expandCore was justified by a CTE analyzed twice, which does not happen — a CTE is analyzed once and cached. Instrumenting it to panic on a duplicate showed the corpus never hits it at all. The real source is an expression typed a second time when a later clause refers to the output name it was given: "SELECT (SELECT * FROM baz LIMIT 1) AS x FROM foo GROUP BY x" reports the star in the subquery once for GROUP BY and once for the target list, and dropping the dedupe turns that query into an overlapping edit. The comment now says so and the query is part of the star_expansion_core case on every engine, where it generates what the legacy path generates. expandCore returned an error it never produced, so it returns only edits. newParser was set on the core path alone, leaving a nil func field on a Compiler built the other way. Nothing calls it there today, but a field that is only sometimes valid is one to get wrong later, so the legacy path sets it too and takes its own parser from it.
1 parent d9b243b commit 2a8278c

15 files changed

Lines changed: 123 additions & 18 deletions

File tree

‎internal/compiler/engine.go‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,11 @@ type Compiler struct {
3838
coreAnalysis bool
3939
coreDialect core.Option
4040

41-
// newParser builds a parser for the configured engine. The core path
42-
// analyzes statements concurrently, and a parser holds enough state that
43-
// two goroutines cannot share one, so a goroutine that has to parse
44-
// something of its own — the query text it just rewrote — builds its own.
41+
// newParser builds a parser for the configured engine, and is set for
42+
// every engine either path supports. The core path analyzes statements
43+
// concurrently, and a parser holds enough state that two goroutines cannot
44+
// share one, so a goroutine that has to parse something of its own — the
45+
// query text it just rewrote — builds its own rather than taking c.parser.
4546
newParser func() Parser
4647

4748
schema []string
@@ -82,7 +83,7 @@ func NewCompiler(conf config.SQL, combo config.CombinedSettings, parserOpts opts
8283

8384
switch conf.Engine {
8485
case config.EngineSQLite:
85-
c.parser = sqlite.NewParser()
86+
c.newParser = func() Parser { return sqlite.NewParser() }
8687
c.catalog = sqlite.NewCatalog()
8788
c.selector = newSQLiteSelector()
8889

@@ -96,11 +97,11 @@ func NewCompiler(conf config.SQL, combo config.CombinedSettings, parserOpts opts
9697
}
9798
}
9899
case config.EngineMySQL:
99-
c.parser = dolphin.NewParser()
100+
c.newParser = func() Parser { return dolphin.NewParser() }
100101
c.catalog = dolphin.NewCatalog()
101102
c.selector = newDefaultSelector()
102103
case config.EnginePostgreSQL:
103-
c.parser = postgresql.NewParser()
104+
c.newParser = func() Parser { return postgresql.NewParser() }
104105
c.catalog = postgresql.NewCatalog()
105106
c.selector = newDefaultSelector()
106107

@@ -116,6 +117,7 @@ func NewCompiler(conf config.SQL, combo config.CombinedSettings, parserOpts opts
116117
default:
117118
return nil, fmt.Errorf("unknown engine: %s", conf.Engine)
118119
}
120+
c.parser = c.newParser()
119121
return c, nil
120122
}
121123

‎internal/compiler/expand_core.go‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,18 @@ import (
1313
// its subqueries, so there is nothing left to look up here: what remains is
1414
// deciding how each name is written, which is the engine's business and not
1515
// the core's.
16-
func (c *Compiler) expandCore(raw *ast.RawStmt, stars []core.StarExpansion) ([]source.Edit, error) {
16+
func (c *Compiler) expandCore(raw *ast.RawStmt, stars []core.StarExpansion) []source.Edit {
1717
if len(stars) == 0 {
18-
return nil, nil
18+
return nil
1919
}
2020
edits := make([]source.Edit, 0, len(stars))
2121
seen := make(map[int]bool, len(stars))
2222
for _, star := range stars {
23-
// A statement analyzed more than once — the same CTE referenced twice,
24-
// say — reports its stars once per pass. Editing one twice would
25-
// overlap, so only the first is kept.
23+
// The analyzer types an expression again when a later clause refers to
24+
// the output name it was given, so "SELECT (SELECT * FROM t) AS x ...
25+
// GROUP BY x" reports the star in the subquery once per pass. The
26+
// passes agree on what the star covers, and editing the same reference
27+
// twice would overlap, so only the first is kept.
2628
if seen[star.Location] {
2729
continue
2830
}
@@ -70,5 +72,5 @@ func (c *Compiler) expandCore(raw *ast.RawStmt, stars []core.StarExpansion) ([]s
7072
New: strings.Join(cols, ", "),
7173
})
7274
}
73-
return edits, nil
75+
return edits
7476
}

‎internal/compiler/parse_core.go‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,7 @@ func (c *Compiler) parseQueryCore(raw *ast.RawStmt, src string, pre *preprocess.
6565
for _, p := range res.Parameters {
6666
params = append(params, Parameter{Number: p.Number, Column: coreParamColumn(p, namedParams)})
6767
}
68-
edits, err := c.expandCore(raw, res.Stars)
69-
if err != nil {
70-
return nil, err
71-
}
72-
expanded, err = source.Mutate(rawSQL, edits)
68+
expanded, err = source.Mutate(rawSQL, c.expandCore(raw, res.Stars))
7369
if err != nil {
7470
return nil, err
7571
}

‎internal/endtoend/testdata/star_expansion_core/mysql/go/models.go‎

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎internal/endtoend/testdata/star_expansion_core/mysql/go/query.sql.go‎

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎internal/endtoend/testdata/star_expansion_core/mysql/query.sql‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,6 @@ SELECT * FROM (SELECT * FROM bar) sub;
1212

1313
-- name: StarExpansionCTE :many
1414
WITH t AS (SELECT * FROM bar) SELECT * FROM t;
15+
16+
-- name: StarExpansionAliasedSubquery :many
17+
SELECT (SELECT * FROM baz LIMIT 1) AS x FROM foo GROUP BY x;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
CREATE TABLE foo (a text, b text, `group` text);
22
CREATE TABLE bar (a text, c text);
3+
CREATE TABLE baz (z text);

‎internal/endtoend/testdata/star_expansion_core/postgresql/go/models.go‎

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎internal/endtoend/testdata/star_expansion_core/postgresql/go/query.sql.go‎

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎internal/endtoend/testdata/star_expansion_core/postgresql/query.sql‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ WITH t AS (SELECT * FROM bar) SELECT * FROM t;
1515

1616
-- name: StarExpansionReturning :one
1717
INSERT INTO bar (a, c) VALUES ($1, $2) RETURNING *;
18+
19+
-- name: StarExpansionAliasedSubquery :many
20+
SELECT (SELECT * FROM baz LIMIT 1) AS x FROM foo GROUP BY x;

0 commit comments

Comments
 (0)