snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
This commit is contained in:
150
internal/goldeneye/mysql/mysql.go
Normal file
150
internal/goldeneye/mysql/mysql.go
Normal file
@@ -0,0 +1,150 @@
|
||||
// Package mysql generates the MySQL dialect seed under
|
||||
// internal/engine/dolphin/dialect from a live server, and verifies the
|
||||
// MySQL analyze cases under internal/endtoend/testdata against the same
|
||||
// server.
|
||||
//
|
||||
// MySQL keeps no catalog of its types, functions or operators — the server
|
||||
// describes a built-in function no further than its name, in the help
|
||||
// tables — so types.jsonl, functions.jsonl and operators.jsonl are written
|
||||
// by hand and are not this package's business. What it does describe is
|
||||
// its data dictionary: relations.jsonl is every view of information_schema,
|
||||
// read from information_schema itself.
|
||||
//
|
||||
// The package also verifies the MySQL analyze cases under
|
||||
// internal/endtoend/testdata against the same server: each case's schema
|
||||
// and fixture are loaded into a database of their own and its queries run
|
||||
// there. Result columns come from the result set's metadata as the driver
|
||||
// reports it, provenance and parameters from the optimizer trace's
|
||||
// expanded_query and the note EXPLAIN leaves, since MySQL reports nothing
|
||||
// about a parameter but its position. The answer is printed in the JSON
|
||||
// shape sqlc analyze prints and compared with the case's committed
|
||||
// stdout.json byte for byte.
|
||||
//
|
||||
// The server is named by MYSQL_SERVER_URI, in the form the go-sql-driver
|
||||
// DSN takes, and has to be the major release in Major, since each release
|
||||
// adds to information_schema.
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
driver "github.com/go-sql-driver/mysql"
|
||||
|
||||
"github.com/sqlc-dev/sqlc/internal/goldeneye/dialect"
|
||||
)
|
||||
|
||||
// Engine is the name sqlc knows the dialect by, and the name of the analyze
|
||||
// case directories under internal/endtoend/testdata.
|
||||
const Engine = "mysql"
|
||||
|
||||
// Dir is the name of the engine directory the dialect lives under, which
|
||||
// is named after the parser sqlc reads MySQL with rather than the engine.
|
||||
const Dir = "dolphin"
|
||||
|
||||
// Major is the MySQL major release the dialect is generated from. Bumping
|
||||
// it is a deliberate change: every release adds tables and columns to the
|
||||
// system schemas, so regenerate and review the dialect after changing it.
|
||||
const Major = 26
|
||||
|
||||
// Locate returns the server to generate from, named by MYSQL_SERVER_URI.
|
||||
func Locate() (string, error) {
|
||||
dsn := os.Getenv("MYSQL_SERVER_URI")
|
||||
if dsn == "" {
|
||||
return "", errors.New("MYSQL_SERVER_URI is not set")
|
||||
}
|
||||
if _, err := driver.ParseDSN(dsn); err != nil {
|
||||
return "", fmt.Errorf("MYSQL_SERVER_URI: %w", err)
|
||||
}
|
||||
return dsn, nil
|
||||
}
|
||||
|
||||
// open connects to the server. The connection runs several statements at
|
||||
// once, so that a case's schema loads as one script, and holds one
|
||||
// session, since the optimizer trace and the warnings a statement leaves
|
||||
// are both per session.
|
||||
func open(ctx context.Context, dsn string) (*sql.Conn, error) {
|
||||
cfg, err := driver.ParseDSN(dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.MultiStatements = true
|
||||
db, err := sql.Open("mysql", cfg.FormatDSN())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := db.Conn(ctx)
|
||||
if err != nil {
|
||||
db.Close()
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// Version reports the release a server is.
|
||||
func Version(ctx context.Context, dsn string) (string, error) {
|
||||
conn, err := open(ctx, dsn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer conn.Close()
|
||||
return version(ctx, conn)
|
||||
}
|
||||
|
||||
func version(ctx context.Context, conn *sql.Conn) (string, error) {
|
||||
var v string
|
||||
if err := conn.QueryRowContext(ctx, "SELECT version()").Scan(&v); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "MySQL " + v, nil
|
||||
}
|
||||
|
||||
// checkVersion refuses a server of another major release than the dialect
|
||||
// is generated from, whose system schemas would differ from the committed
|
||||
// ones without anything being wrong.
|
||||
func checkVersion(ctx context.Context, conn *sql.Conn) error {
|
||||
var v string
|
||||
if err := conn.QueryRowContext(ctx, "SELECT version()").Scan(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
head, _, _ := strings.Cut(v, ".")
|
||||
major, err := strconv.Atoi(head)
|
||||
if err != nil {
|
||||
return fmt.Errorf("version %q: %w", v, err)
|
||||
}
|
||||
if major != Major {
|
||||
return fmt.Errorf("the dialect is generated from MySQL %d, but the server is MySQL %d", Major, major)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Generate reads the dialect from the server: relations.jsonl, the views of
|
||||
// information_schema.
|
||||
func Generate(ctx context.Context, dsn string) (dialect.Files, error) {
|
||||
conn, err := open(ctx, dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
if err := checkVersion(ctx, conn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var relations []dialect.Relation
|
||||
for _, schema := range systemSchemas {
|
||||
rels, err := readRelations(ctx, conn, schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
relations = append(relations, rels...)
|
||||
}
|
||||
blob, err := dialect.JSONL(relations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dialect.Files{dialect.RelationsFile: blob}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user