Skip to content

Commit 46e9f15

Browse files
committed
Cover every supported engine for parse and analyze; fix flag reuse
Convert the parse and analyze commands to constructor functions (newParseCmd/newAnalyzeCmd), matching the existing NewCmdVet pattern, so each Do invocation gets fresh flag state. Previously the shared package-level command vars leaked flag values (e.g. --ast) between calls, which surfaced when running multiple analyze cases in one test process. Add replay cases pinning the output format for each supported engine: parse for postgresql, mysql, sqlite, and clickhouse; analyze for postgresql, mysql, and sqlite; plus an analyze case exercising --ast.
1 parent b6f323e commit 46e9f15

24 files changed

Lines changed: 586 additions & 189 deletions

File tree

internal/cmd/analyze.go

Lines changed: 107 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ import (
1616
"github.com/sqlc-dev/sqlc/internal/sql/ast"
1717
)
1818

19-
var analyzeCmd = &cobra.Command{
20-
Use: "analyze [query-file]",
21-
Short: "Analyze a query against a schema and output the result columns and parameters",
22-
Long: `Analyze a query file against a schema file and output the inferred result
19+
func newAnalyzeCmd() *cobra.Command {
20+
cmd := &cobra.Command{
21+
Use: "analyze [query-file]",
22+
Short: "Analyze a query against a schema and output the result columns and parameters",
23+
Long: `Analyze a query file against a schema file and output the inferred result
2324
columns and parameters as JSON.
2425
2526
Unlike "sqlc generate", this command does not require a configuration file and
@@ -42,112 +43,117 @@ Examples:
4243
4344
# Include the statement AST in the output
4445
sqlc analyze --dialect postgresql --schema schema.sql --ast query.sql`,
45-
Args: cobra.MaximumNArgs(1),
46-
RunE: func(cmd *cobra.Command, args []string) error {
47-
dialect, err := cmd.Flags().GetString("dialect")
48-
if err != nil {
49-
return err
50-
}
51-
if dialect == "" {
52-
return fmt.Errorf("--dialect flag is required (postgresql, mysql, or sqlite)")
53-
}
54-
55-
schemaPath, err := cmd.Flags().GetString("schema")
56-
if err != nil {
57-
return err
58-
}
59-
if schemaPath == "" {
60-
return fmt.Errorf("--schema flag is required")
61-
}
62-
63-
includeAST, err := cmd.Flags().GetBool("ast")
64-
if err != nil {
65-
return err
66-
}
67-
68-
// The query comes from a file argument or, when none is given, from
69-
// stdin. The compiler reads queries from files, so stdin is written to
70-
// a temporary file.
71-
var queryPath string
72-
if len(args) == 1 {
73-
queryPath = args[0]
74-
} else {
75-
stat, err := os.Stdin.Stat()
46+
Args: cobra.MaximumNArgs(1),
47+
RunE: func(cmd *cobra.Command, args []string) error {
48+
dialect, err := cmd.Flags().GetString("dialect")
7649
if err != nil {
77-
return fmt.Errorf("failed to stat stdin: %w", err)
50+
return err
7851
}
79-
if (stat.Mode() & os.ModeCharDevice) != 0 {
80-
return fmt.Errorf("no query provided. Specify a query file or pipe SQL via stdin")
52+
if dialect == "" {
53+
return fmt.Errorf("--dialect flag is required (postgresql, mysql, or sqlite)")
8154
}
82-
data, err := io.ReadAll(cmd.InOrStdin())
55+
56+
schemaPath, err := cmd.Flags().GetString("schema")
57+
if err != nil {
58+
return err
59+
}
60+
if schemaPath == "" {
61+
return fmt.Errorf("--schema flag is required")
62+
}
63+
64+
includeAST, err := cmd.Flags().GetBool("ast")
8365
if err != nil {
84-
return fmt.Errorf("failed to read stdin: %w", err)
66+
return err
67+
}
68+
69+
// The query comes from a file argument or, when none is given, from
70+
// stdin. The compiler reads queries from files, so stdin is written to
71+
// a temporary file.
72+
var queryPath string
73+
if len(args) == 1 {
74+
queryPath = args[0]
75+
} else {
76+
stat, err := os.Stdin.Stat()
77+
if err != nil {
78+
return fmt.Errorf("failed to stat stdin: %w", err)
79+
}
80+
if (stat.Mode() & os.ModeCharDevice) != 0 {
81+
return fmt.Errorf("no query provided. Specify a query file or pipe SQL via stdin")
82+
}
83+
data, err := io.ReadAll(cmd.InOrStdin())
84+
if err != nil {
85+
return fmt.Errorf("failed to read stdin: %w", err)
86+
}
87+
tmp, err := os.CreateTemp("", "sqlc-analyze-*.sql")
88+
if err != nil {
89+
return fmt.Errorf("failed to create temp file: %w", err)
90+
}
91+
defer os.Remove(tmp.Name())
92+
if _, err := tmp.Write(data); err != nil {
93+
tmp.Close()
94+
return fmt.Errorf("failed to write temp file: %w", err)
95+
}
96+
if err := tmp.Close(); err != nil {
97+
return fmt.Errorf("failed to close temp file: %w", err)
98+
}
99+
queryPath = tmp.Name()
100+
}
101+
102+
var engine config.Engine
103+
switch dialect {
104+
case "postgresql", "postgres", "pg":
105+
engine = config.EnginePostgreSQL
106+
case "mysql":
107+
engine = config.EngineMySQL
108+
case "sqlite":
109+
engine = config.EngineSQLite
110+
default:
111+
return fmt.Errorf("unsupported dialect: %s (use postgresql, mysql, or sqlite)", dialect)
112+
}
113+
114+
sql := config.SQL{
115+
Engine: engine,
116+
Schema: config.Paths{schemaPath},
117+
Queries: config.Paths{queryPath},
85118
}
86-
tmp, err := os.CreateTemp("", "sqlc-analyze-*.sql")
119+
combo := config.Combine(config.Config{}, sql)
120+
parserOpts := opts.Parser{}
121+
122+
ctx := cmd.Context()
123+
c, err := compiler.NewCompiler(sql, combo, parserOpts)
87124
if err != nil {
88-
return fmt.Errorf("failed to create temp file: %w", err)
125+
return fmt.Errorf("error creating compiler: %w", err)
126+
}
127+
defer c.Close(ctx)
128+
129+
if err := c.ParseCatalog(sql.Schema); err != nil {
130+
return fmt.Errorf("error parsing schema: %w", formatParseError(err))
131+
}
132+
if err := c.ParseQueries(sql.Queries, parserOpts); err != nil {
133+
return fmt.Errorf("error parsing queries: %w", formatParseError(err))
89134
}
90-
defer os.Remove(tmp.Name())
91-
if _, err := tmp.Write(data); err != nil {
92-
tmp.Close()
93-
return fmt.Errorf("failed to write temp file: %w", err)
135+
136+
result := c.Result()
137+
138+
out := make([]analyzedQuery, 0, len(result.Queries))
139+
for _, q := range result.Queries {
140+
out = append(out, newAnalyzedQuery(q, includeAST))
94141
}
95-
if err := tmp.Close(); err != nil {
96-
return fmt.Errorf("failed to close temp file: %w", err)
142+
143+
stdout := cmd.OutOrStdout()
144+
encoder := json.NewEncoder(stdout)
145+
encoder.SetIndent("", " ")
146+
if err := encoder.Encode(out); err != nil {
147+
return fmt.Errorf("failed to encode analysis: %w", err)
97148
}
98-
queryPath = tmp.Name()
99-
}
100-
101-
var engine config.Engine
102-
switch dialect {
103-
case "postgresql", "postgres", "pg":
104-
engine = config.EnginePostgreSQL
105-
case "mysql":
106-
engine = config.EngineMySQL
107-
case "sqlite":
108-
engine = config.EngineSQLite
109-
default:
110-
return fmt.Errorf("unsupported dialect: %s (use postgresql, mysql, or sqlite)", dialect)
111-
}
112-
113-
sql := config.SQL{
114-
Engine: engine,
115-
Schema: config.Paths{schemaPath},
116-
Queries: config.Paths{queryPath},
117-
}
118-
combo := config.Combine(config.Config{}, sql)
119-
parserOpts := opts.Parser{}
120-
121-
ctx := cmd.Context()
122-
c, err := compiler.NewCompiler(sql, combo, parserOpts)
123-
if err != nil {
124-
return fmt.Errorf("error creating compiler: %w", err)
125-
}
126-
defer c.Close(ctx)
127-
128-
if err := c.ParseCatalog(sql.Schema); err != nil {
129-
return fmt.Errorf("error parsing schema: %w", formatParseError(err))
130-
}
131-
if err := c.ParseQueries(sql.Queries, parserOpts); err != nil {
132-
return fmt.Errorf("error parsing queries: %w", formatParseError(err))
133-
}
134-
135-
result := c.Result()
136-
137-
out := make([]analyzedQuery, 0, len(result.Queries))
138-
for _, q := range result.Queries {
139-
out = append(out, newAnalyzedQuery(q, includeAST))
140-
}
141-
142-
stdout := cmd.OutOrStdout()
143-
encoder := json.NewEncoder(stdout)
144-
encoder.SetIndent("", " ")
145-
if err := encoder.Encode(out); err != nil {
146-
return fmt.Errorf("failed to encode analysis: %w", err)
147-
}
148-
149-
return nil
150-
},
149+
150+
return nil
151+
},
152+
}
153+
cmd.Flags().StringP("dialect", "d", "", "SQL dialect to use (postgresql, mysql, or sqlite)")
154+
cmd.Flags().StringP("schema", "s", "", "path to the schema file")
155+
cmd.Flags().BoolP("ast", "", false, "include the statement AST in the output")
156+
return cmd
151157
}
152158

153159
// formatParseError unwraps a multierr.Error into a single error containing all

internal/cmd/cmd.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,6 @@ func init() {
3232
initCmd.Flags().BoolP("v1", "", false, "generate v1 config yaml file")
3333
initCmd.Flags().BoolP("v2", "", true, "generate v2 config yaml file")
3434
initCmd.MarkFlagsMutuallyExclusive("v1", "v2")
35-
parseCmd.Flags().StringP("dialect", "d", "", "SQL dialect to use (postgresql, mysql, or sqlite)")
36-
analyzeCmd.Flags().StringP("dialect", "d", "", "SQL dialect to use (postgresql, mysql, or sqlite)")
37-
analyzeCmd.Flags().StringP("schema", "s", "", "path to the schema file")
38-
analyzeCmd.Flags().BoolP("ast", "", false, "include the statement AST in the output")
3935
}
4036

4137
// Do runs the command logic.
@@ -48,8 +44,8 @@ func Do(args []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) int
4844
rootCmd.AddCommand(diffCmd)
4945
rootCmd.AddCommand(genCmd)
5046
rootCmd.AddCommand(initCmd)
51-
rootCmd.AddCommand(parseCmd)
52-
rootCmd.AddCommand(analyzeCmd)
47+
rootCmd.AddCommand(newParseCmd())
48+
rootCmd.AddCommand(newAnalyzeCmd())
5349
rootCmd.AddCommand(versionCmd)
5450
rootCmd.AddCommand(verifyCmd)
5551
rootCmd.AddCommand(pushCmd)

0 commit comments

Comments
 (0)