Skip to content

Commit e52cd4e

Browse files
kyleconroyclaude
andcommitted
feat(mysql): Add database analyzer for MySQL
Adds a MySQL database analyzer that validates queries against a real MySQL database during code generation. This is similar to the existing PostgreSQL and SQLite analyzers. Key features: - Creates managed databases (sqlc_managed_{hash}) based on migration content - Validates query syntax using PrepareContext against real MySQL - Detects parameter count from ? placeholders - Gracefully handles missing database connections Also fixes several MySQL test cases to use correct MySQL syntax: - Changed $1/$2 placeholders to ? (MySQL syntax) - Added AS aliases to count(*) expressions in CTEs - Fixed column references and table aliases - Removed PostgreSQL-specific public. schema prefix Tests requiring MySQL-specific features (HeatWave VECTOR functions, SHOW WARNINGS in prepared statements, etc.) are restricted to base context. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 2e44b62 commit e52cd4e

File tree

28 files changed

+436
-57
lines changed

28 files changed

+436
-57
lines changed

examples/authors/sqlc.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ sql:
2424
engine: mysql
2525
database:
2626
uri: "${VET_TEST_EXAMPLES_MYSQL_AUTHORS}"
27+
analyzer:
28+
database: false
2729
rules:
2830
- sqlc/db-prepare
2931
# - mysql-query-too-costly

examples/booktest/sqlc.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
"database": {
3131
"uri": "${VET_TEST_EXAMPLES_MYSQL_BOOKTEST}"
3232
},
33+
"analyzer": {
34+
"database": false
35+
},
3336
"rules": [
3437
"sqlc/db-prepare"
3538
]

examples/ondeck/sqlc.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333
"database": {
3434
"uri": "${VET_TEST_EXAMPLES_MYSQL_ONDECK}"
3535
},
36+
"analyzer": {
37+
"database": false
38+
},
3639
"rules": [
3740
"sqlc/db-prepare"
3841
],

internal/compiler/analyze.go

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -77,25 +77,30 @@ func combineAnalysis(prev *analysis, a *analyzer.Analysis) *analysis {
7777
Column: convertColumn(p.Column),
7878
})
7979
}
80-
if len(prev.Columns) == len(cols) {
81-
for i := range prev.Columns {
82-
// Only override column types if the analyzer provides a specific type
83-
// (not "any"), since the catalog-based inference may have better info
84-
if cols[i].DataType != "any" {
85-
prev.Columns[i].DataType = cols[i].DataType
86-
prev.Columns[i].IsArray = cols[i].IsArray
87-
prev.Columns[i].ArrayDims = cols[i].ArrayDims
80+
// Only update columns if the analyzer returned column info
81+
// An empty slice from the analyzer means it couldn't determine columns,
82+
// so we should keep the catalog-inferred columns
83+
if len(cols) > 0 {
84+
if len(prev.Columns) == len(cols) {
85+
for i := range prev.Columns {
86+
// Only override column types if the analyzer provides a specific type
87+
// (not "any"), since the catalog-based inference may have better info
88+
if cols[i].DataType != "any" {
89+
prev.Columns[i].DataType = cols[i].DataType
90+
prev.Columns[i].IsArray = cols[i].IsArray
91+
prev.Columns[i].ArrayDims = cols[i].ArrayDims
92+
}
8893
}
89-
}
90-
} else {
91-
embedding := false
92-
for i := range prev.Columns {
93-
if prev.Columns[i].EmbedTable != nil {
94-
embedding = true
94+
} else {
95+
embedding := false
96+
for i := range prev.Columns {
97+
if prev.Columns[i].EmbedTable != nil {
98+
embedding = true
99+
}
100+
}
101+
if !embedding {
102+
prev.Columns = cols
95103
}
96-
}
97-
if !embedding {
98-
prev.Columns = cols
99104
}
100105
}
101106
if len(prev.Parameters) == len(params) {

internal/compiler/engine.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/sqlc-dev/sqlc/internal/config"
99
"github.com/sqlc-dev/sqlc/internal/dbmanager"
1010
"github.com/sqlc-dev/sqlc/internal/engine/dolphin"
11+
mysqlanalyze "github.com/sqlc-dev/sqlc/internal/engine/dolphin/analyzer"
1112
"github.com/sqlc-dev/sqlc/internal/engine/postgresql"
1213
pganalyze "github.com/sqlc-dev/sqlc/internal/engine/postgresql/analyzer"
1314
"github.com/sqlc-dev/sqlc/internal/engine/sqlite"
@@ -55,6 +56,15 @@ func NewCompiler(conf config.SQL, combo config.CombinedSettings) (*Compiler, err
5556
c.parser = dolphin.NewParser()
5657
c.catalog = dolphin.NewCatalog()
5758
c.selector = newDefaultSelector()
59+
if conf.Database != nil {
60+
if conf.Analyzer.Database == nil || *conf.Analyzer.Database {
61+
c.analyzer = analyzer.Cached(
62+
mysqlanalyze.New(combo.Global.Servers, *conf.Database),
63+
combo.Global,
64+
*conf.Database,
65+
)
66+
}
67+
}
5868
case config.EnginePostgreSQL:
5969
c.parser = postgresql.NewParser()
6070
c.catalog = postgresql.NewCatalog()

internal/endtoend/testdata/create_view/mysql/go/query.sql.go

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

internal/endtoend/testdata/create_view/mysql/query.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
SELECT * FROM first_view;
33

44
-- name: GetSecond :many
5-
SELECT * FROM second_view WHERE val2 = $1;
5+
SELECT * FROM second_view WHERE val2 = ?;

internal/endtoend/testdata/cte_count/mysql/go/query.sql.go

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
-- name: CTECount :many
22
WITH all_count AS (
3-
SELECT count(*) FROM bar
3+
SELECT count(*) AS count FROM bar
44
), ready_count AS (
5-
SELECT count(*) FROM bar WHERE ready = true
5+
SELECT count(*) AS count FROM bar WHERE ready = true
66
)
77
SELECT all_count.count, ready_count.count
88
FROM all_count, ready_count;

internal/endtoend/testdata/cte_filter/mysql/go/query.sql.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)