Skip to content

Commit 850e049

Browse files
committed
feat: rename accurate mode to analyzer.database: only with analyzerv2 experiment
This change refactors the "accurate analyzer mode" feature: 1. Rename config option from `analyzer.accurate: true` to `analyzer.database: only` - a third option in addition to true/false 2. Gate the feature behind the `analyzerv2` experiment flag. The feature is only enabled when: - `analyzer.database: only` is set in the config - `SQLCEXPERIMENT=analyzerv2` environment variable is set 3. Update JSON schemas to support boolean or "only" for analyzer.database 4. Add experiment tests for analyzerv2 flag 5. Update end-to-end test configs and expected outputs The database-only mode skips building the internal catalog from schema files and instead relies entirely on the database for type resolution and star expansion. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 6a12272 commit 850e049

File tree

27 files changed

+219
-238
lines changed

27 files changed

+219
-238
lines changed

internal/cmd/generate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ func remoteGenerate(ctx context.Context, configPath string, conf *config.Config,
295295

296296
func parse(ctx context.Context, name, dir string, sql config.SQL, combo config.CombinedSettings, parserOpts opts.Parser, stderr io.Writer) (*compiler.Result, bool) {
297297
defer trace.StartRegion(ctx, "parse").End()
298-
c, err := compiler.NewCompiler(sql, combo)
298+
c, err := compiler.NewCompiler(sql, combo, parserOpts)
299299
defer func() {
300300
if c != nil {
301301
c.Close(ctx)

internal/compiler/compile.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ func (c *Compiler) parseCatalog(schemas []string) error {
4141
contents := migrations.RemoveRollbackStatements(string(blob))
4242
c.schema = append(c.schema, contents)
4343

44-
// In accurate mode, we only need to collect schema files for migrations
44+
// In database-only mode, we only need to collect schema files for migrations
4545
// but don't build the internal catalog from them
46-
if c.accurateMode {
46+
if c.databaseOnlyMode {
4747
continue
4848
}
4949

@@ -68,8 +68,8 @@ func (c *Compiler) parseCatalog(schemas []string) error {
6868
func (c *Compiler) parseQueries(o opts.Parser) (*Result, error) {
6969
ctx := context.Background()
7070

71-
// In accurate mode, initialize the database connection pool before parsing queries
72-
if c.accurateMode && c.pgAnalyzer != nil {
71+
// In database-only mode, initialize the database connection pool before parsing queries
72+
if c.databaseOnlyMode && c.pgAnalyzer != nil {
7373
if err := c.pgAnalyzer.EnsurePool(ctx, c.schema); err != nil {
7474
return nil, fmt.Errorf("failed to initialize database connection: %w", err)
7575
}
@@ -131,8 +131,8 @@ func (c *Compiler) parseQueries(o opts.Parser) (*Result, error) {
131131
return nil, fmt.Errorf("no queries contained in paths %s", strings.Join(c.conf.Queries, ","))
132132
}
133133

134-
// In accurate mode, build the catalog from the database after parsing all queries
135-
if c.accurateMode && c.pgAnalyzer != nil {
134+
// In database-only mode, build the catalog from the database after parsing all queries
135+
if c.databaseOnlyMode && c.pgAnalyzer != nil {
136136
// Default to "public" schema if no specific schemas are specified
137137
schemas := []string{"public"}
138138
cat, err := c.pgAnalyzer.IntrospectSchema(ctx, schemas)

internal/compiler/engine.go

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,34 +29,35 @@ type Compiler struct {
2929

3030
schema []string
3131

32-
// accurateMode indicates that the compiler should use database-only analysis
33-
// and skip building the internal catalog from schema files
34-
accurateMode bool
35-
// pgAnalyzer is the PostgreSQL-specific analyzer used in accurate mode
32+
// databaseOnlyMode indicates that the compiler should use database-only analysis
33+
// and skip building the internal catalog from schema files (analyzer.database: only)
34+
databaseOnlyMode bool
35+
// pgAnalyzer is the PostgreSQL-specific analyzer used in database-only mode
3636
// for schema introspection
3737
pgAnalyzer *pganalyze.Analyzer
38-
// expander is used to expand SELECT * and RETURNING * in accurate mode
38+
// expander is used to expand SELECT * and RETURNING * in database-only mode
3939
expander *expander.Expander
4040
}
4141

42-
func NewCompiler(conf config.SQL, combo config.CombinedSettings) (*Compiler, error) {
42+
func NewCompiler(conf config.SQL, combo config.CombinedSettings, parserOpts opts.Parser) (*Compiler, error) {
4343
c := &Compiler{conf: conf, combo: combo}
4444

4545
if conf.Database != nil && conf.Database.Managed {
4646
client := dbmanager.NewClient(combo.Global.Servers)
4747
c.client = client
4848
}
4949

50-
// Check for accurate mode
51-
accurateMode := conf.Analyzer.Accurate != nil && *conf.Analyzer.Accurate
50+
// Check for database-only mode (analyzer.database: only)
51+
// This feature requires the analyzerv2 experiment to be enabled
52+
databaseOnlyMode := conf.Analyzer.Database.IsOnly() && parserOpts.Experiment.AnalyzerV2
5253

5354
switch conf.Engine {
5455
case config.EngineSQLite:
5556
c.parser = sqlite.NewParser()
5657
c.catalog = sqlite.NewCatalog()
5758
c.selector = newSQLiteSelector()
5859
if conf.Database != nil {
59-
if conf.Analyzer.Database == nil || *conf.Analyzer.Database {
60+
if conf.Analyzer.Database.IsEnabled() {
6061
c.analyzer = analyzer.Cached(
6162
sqliteanalyze.New(*conf.Database),
6263
combo.Global,
@@ -74,15 +75,15 @@ func NewCompiler(conf config.SQL, combo config.CombinedSettings) (*Compiler, err
7475
c.catalog = postgresql.NewCatalog()
7576
c.selector = newDefaultSelector()
7677

77-
if accurateMode {
78-
// Accurate mode requires a database connection
78+
if databaseOnlyMode {
79+
// Database-only mode requires a database connection
7980
if conf.Database == nil {
80-
return nil, fmt.Errorf("accurate mode requires database configuration")
81+
return nil, fmt.Errorf("analyzer.database: only requires database configuration")
8182
}
8283
if conf.Database.URI == "" && !conf.Database.Managed {
83-
return nil, fmt.Errorf("accurate mode requires database.uri or database.managed")
84+
return nil, fmt.Errorf("analyzer.database: only requires database.uri or database.managed")
8485
}
85-
c.accurateMode = true
86+
c.databaseOnlyMode = true
8687
// Create the PostgreSQL analyzer for schema introspection
8788
c.pgAnalyzer = pganalyze.New(c.client, *conf.Database)
8889
// Use the analyzer wrapped with cache for query analysis
@@ -95,7 +96,7 @@ func NewCompiler(conf config.SQL, combo config.CombinedSettings) (*Compiler, err
9596
// The parser implements both Parser and format.Dialect interfaces
9697
c.expander = expander.New(c.pgAnalyzer, parser, parser)
9798
} else if conf.Database != nil {
98-
if conf.Analyzer.Database == nil || *conf.Analyzer.Database {
99+
if conf.Analyzer.Database.IsEnabled() {
99100
c.analyzer = analyzer.Cached(
100101
pganalyze.New(c.client, *conf.Database),
101102
combo.Global,

internal/compiler/parse.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ func (c *Compiler) parseQuery(stmt ast.Node, src string, o opts.Parser) (*Query,
7171
}
7272

7373
var anlys *analysis
74-
if c.accurateMode && c.expander != nil {
75-
// In accurate mode, use the expander for star expansion
74+
if c.databaseOnlyMode && c.expander != nil {
75+
// In database-only mode, use the expander for star expansion
7676
// and rely entirely on the database analyzer for type resolution
7777
expandedQuery, err := c.expander.Expand(ctx, rawSQL)
7878
if err != nil {

internal/config/config.go

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,75 @@ type SQL struct {
122122
Analyzer Analyzer `json:"analyzer" yaml:"analyzer"`
123123
}
124124

125+
// AnalyzerDatabase represents the database analyzer setting.
126+
// It can be a boolean (true/false) or the string "only" for database-only mode.
127+
type AnalyzerDatabase struct {
128+
value *bool // nil means not set, true/false for boolean values
129+
isOnly bool // true when set to "only"
130+
}
131+
132+
// IsEnabled returns true if the database analyzer should be used.
133+
// Returns true for both `true` and `"only"` settings.
134+
func (a AnalyzerDatabase) IsEnabled() bool {
135+
if a.isOnly {
136+
return true
137+
}
138+
return a.value == nil || *a.value
139+
}
140+
141+
// IsOnly returns true if the analyzer is set to "only" mode.
142+
func (a AnalyzerDatabase) IsOnly() bool {
143+
return a.isOnly
144+
}
145+
146+
func (a *AnalyzerDatabase) UnmarshalJSON(data []byte) error {
147+
// Try to unmarshal as boolean first
148+
var b bool
149+
if err := json.Unmarshal(data, &b); err == nil {
150+
a.value = &b
151+
a.isOnly = false
152+
return nil
153+
}
154+
155+
// Try to unmarshal as string
156+
var s string
157+
if err := json.Unmarshal(data, &s); err == nil {
158+
if s == "only" {
159+
a.isOnly = true
160+
a.value = nil
161+
return nil
162+
}
163+
return errors.New("analyzer.database must be true, false, or \"only\"")
164+
}
165+
166+
return errors.New("analyzer.database must be true, false, or \"only\"")
167+
}
168+
169+
func (a *AnalyzerDatabase) UnmarshalYAML(unmarshal func(interface{}) error) error {
170+
// Try to unmarshal as boolean first
171+
var b bool
172+
if err := unmarshal(&b); err == nil {
173+
a.value = &b
174+
a.isOnly = false
175+
return nil
176+
}
177+
178+
// Try to unmarshal as string
179+
var s string
180+
if err := unmarshal(&s); err == nil {
181+
if s == "only" {
182+
a.isOnly = true
183+
a.value = nil
184+
return nil
185+
}
186+
return errors.New("analyzer.database must be true, false, or \"only\"")
187+
}
188+
189+
return errors.New("analyzer.database must be true, false, or \"only\"")
190+
}
191+
125192
type Analyzer struct {
126-
Database *bool `json:"database" yaml:"database"`
127-
Accurate *bool `json:"accurate" yaml:"accurate"`
193+
Database AnalyzerDatabase `json:"database" yaml:"database"`
128194
}
129195

130196
// TODO: Figure out a better name for this

internal/config/v_one.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@
7979
"type": "object",
8080
"properties": {
8181
"database": {
82-
"type": "boolean"
83-
},
84-
"accurate": {
85-
"type": "boolean"
82+
"oneOf": [
83+
{"type": "boolean"},
84+
{"const": "only"}
85+
]
8686
}
8787
}
8888
},

internal/config/v_two.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,10 @@
8282
"type": "object",
8383
"properties": {
8484
"database": {
85-
"type": "boolean"
86-
},
87-
"accurate": {
88-
"type": "boolean"
85+
"oneOf": [
86+
{"type": "boolean"},
87+
{"const": "only"}
88+
]
8989
}
9090
}
9191
},

internal/endtoend/endtoend_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,8 +263,9 @@ func TestReplay(t *testing.T) {
263263

264264
opts := cmd.Options{
265265
Env: cmd.Env{
266-
Debug: opts.DebugFromString(args.Env["SQLCDEBUG"]),
267-
NoRemote: true,
266+
Debug: opts.DebugFromString(args.Env["SQLCDEBUG"]),
267+
Experiment: opts.ExperimentFromString(args.Env["SQLCEXPERIMENT"]),
268+
NoRemote: true,
268269
},
269270
Stderr: &stderr,
270271
MutateConfig: testctx.Mutate(t, path),
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
{
2-
"contexts": ["managed-db"]
2+
"contexts": ["managed-db"],
3+
"env": {
4+
"SQLCEXPERIMENT": "analyzerv2"
5+
}
36
}

internal/endtoend/testdata/accurate_cte/postgresql/stdlib/go/models.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)