Skip to content

Commit 627a099

Browse files
committed
Created sqlc-dolphin-gen tool to dump MySQL catalog info
1 parent 78a3600 commit 627a099

File tree

6 files changed

+505
-9
lines changed

6 files changed

+505
-9
lines changed

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ sqlc-dev:
2929
sqlc-pg-gen:
3030
go build -o ~/bin/sqlc-pg-gen ./internal/tools/sqlc-pg-gen
3131

32+
sqlc-dolphin-gen:
33+
go build -o ~/bin/sqlc-dolphin-gen ./internal/tools/sqlc-dolphin-gen
34+
3235
sqlc-gen-json:
3336
go build -o ~/bin/sqlc-gen-json ./cmd/sqlc-gen-json
3437

internal/codegen/golang/result.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package golang
33
import (
44
"bufio"
55
"fmt"
6+
"log"
7+
"slices"
68
"sort"
79
"strings"
810

@@ -13,10 +15,27 @@ import (
1315
"github.com/sqlc-dev/sqlc/internal/plugin"
1416
)
1517

18+
// getSchemasToSkip Returns a list of schemas which should not be included in the generated output
19+
func getSchemasToSkip(req *plugin.GenerateRequest) []string {
20+
switch req.Settings.Engine {
21+
case "postgresql":
22+
return []string{"pg_catalog", "information_schema"}
23+
case "mysql":
24+
return []string{"information_schema", "performance_schema", "sys", "mysql"}
25+
case "sqlite":
26+
return []string{}
27+
default:
28+
log.Printf("WARN internal/codegen/golang/result.go:getSchemasToSkip unhandled engine: %s\n", req.Settings.Engine)
29+
return []string{}
30+
}
31+
}
32+
1633
func buildEnums(req *plugin.GenerateRequest, options *opts.Options) []Enum {
1734
var enums []Enum
35+
schemasToSkip := getSchemasToSkip(req)
36+
1837
for _, schema := range req.Catalog.Schemas {
19-
if schema.Name == "pg_catalog" || schema.Name == "information_schema" {
38+
if slices.Contains(schemasToSkip, schema.Name) {
2039
continue
2140
}
2241
for _, enum := range schema.Enums {
@@ -62,8 +81,10 @@ func buildEnums(req *plugin.GenerateRequest, options *opts.Options) []Enum {
6281

6382
func buildStructs(req *plugin.GenerateRequest, options *opts.Options) []Struct {
6483
var structs []Struct
84+
schemasToSkip := getSchemasToSkip(req)
85+
6586
for _, schema := range req.Catalog.Schemas {
66-
if schema.Name == "pg_catalog" || schema.Name == "information_schema" {
87+
if slices.Contains(schemasToSkip, schema.Name) {
6788
continue
6889
}
6990
for _, table := range schema.Tables {

internal/engine/dolphin/catalog.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,22 @@ import (
44
"github.com/sqlc-dev/sqlc/internal/sql/catalog"
55
)
66

7+
// toPointer converts an int to a pointer without a temporary
8+
// variable at the call-site, and is used by the generated schemas
9+
func toPointer(x int) *int {
10+
return &x
11+
}
12+
713
func NewCatalog() *catalog.Catalog {
814
def := "public" // TODO: What is the default database for MySQL?
9-
return &catalog.Catalog{
10-
DefaultSchema: def,
11-
Schemas: []*catalog.Schema{
12-
defaultSchema(def),
13-
},
14-
Extensions: map[string]struct{}{},
15-
}
15+
16+
c := catalog.New(def)
17+
// New() creates an empty schema which we'll replace with MySQL stdlib functions
18+
c.Schemas[0] = defaultSchema(def)
19+
c.Schemas = append(c.Schemas, genInformationSchema())
20+
c.Schemas = append(c.Schemas, genPerformanceSchema())
21+
c.Schemas = append(c.Schemas, genSysSchema())
22+
c.Schemas = append(c.Schemas, genMysqlCatalog())
23+
24+
return c
1625
}
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"database/sql"
7+
"flag"
8+
"fmt"
9+
"github.com/go-sql-driver/mysql"
10+
"go/format"
11+
"log"
12+
"os"
13+
"path/filepath"
14+
"text/template"
15+
16+
_ "github.com/go-sql-driver/mysql"
17+
)
18+
19+
const catalogTmpl = `// Code generated by sqlc-dolphin-gen. DO NOT EDIT.
20+
21+
package {{.Pkg}}
22+
23+
import (
24+
"github.com/sqlc-dev/sqlc/internal/sql/ast"
25+
"github.com/sqlc-dev/sqlc/internal/sql/catalog"
26+
)
27+
28+
var funcs{{.GenFnName}} = []*catalog.Function {
29+
{{- range .Procs}}
30+
{
31+
Name: "{{.Name}}",
32+
Args: []*catalog.Argument{
33+
{{range .Args}}{
34+
Name: "{{.Name}}",
35+
Type: &ast.TypeName{Name: "{{.Type}}"},
36+
{{- if ne .Mode "IN" }}
37+
Mode: {{ .GoMode }},
38+
{{- end}}
39+
},
40+
{{end}}
41+
},
42+
{{- if ne .ReturnType "" }}
43+
ReturnType: &ast.TypeName{Name: "{{.ReturnType}}"},
44+
{{end}}
45+
},
46+
{{- end}}
47+
}
48+
49+
func {{.GenFnName}}() *catalog.Schema {
50+
s := &catalog.Schema{Name: "{{ .SchemaName }}"}
51+
s.Funcs = funcs{{.GenFnName}}
52+
{{- if .Relations }}
53+
s.Tables = []*catalog.Table {
54+
{{- range .Relations }}
55+
{
56+
Rel: &ast.TableName{
57+
Catalog: "public",
58+
Schema: "{{.SchemaName}}",
59+
Name: "{{.Name}}",
60+
},
61+
Columns: []*catalog.Column{
62+
{{- range .Columns}}
63+
{
64+
Name: "{{.Name}}",
65+
Type: ast.TypeName{Name: "{{.Type}}"},
66+
{{- if .IsNotNull}}
67+
IsNotNull: true,
68+
{{- end}}
69+
{{- if .Length }}
70+
Length: toPointer({{ .Length }}),
71+
{{- end}}
72+
},
73+
{{- end}}
74+
},
75+
},
76+
{{- end}}
77+
}
78+
{{- end }}
79+
return s
80+
}
81+
`
82+
83+
type tmplCtx struct {
84+
Pkg string
85+
GenFnName string
86+
SchemaName string
87+
Procs []Proc
88+
Relations []Relation
89+
}
90+
91+
func main() {
92+
if err := run(context.Background()); err != nil {
93+
log.Fatal(err)
94+
}
95+
}
96+
97+
func getEnvOrDefault(env, defaultValue string) string {
98+
result := os.Getenv(env)
99+
if result == "" {
100+
return defaultValue
101+
}
102+
return result
103+
}
104+
105+
func databaseURL() string {
106+
dburl := os.Getenv("DATABASE_URL")
107+
if dburl != "" {
108+
return dburl
109+
}
110+
mysqlHost := getEnvOrDefault("MYSQL_HOST", "127.0.0.1")
111+
mysqlPort := getEnvOrDefault("MYSQL_PORT", "3306")
112+
113+
config := mysql.Config{
114+
User: getEnvOrDefault("MYSQL_USER", "root"),
115+
Passwd: getEnvOrDefault("MYSQL_PASSWORD", "mysecretpassword"),
116+
Addr: fmt.Sprintf("%s:%s", mysqlHost, mysqlPort),
117+
DBName: getEnvOrDefault("MYSQL_DATABASE", ""),
118+
}
119+
120+
return config.FormatDSN()
121+
}
122+
123+
func run(ctx context.Context) error {
124+
flag.Parse()
125+
126+
dir := flag.Arg(0)
127+
if dir == "" {
128+
dir = filepath.Join("internal", "engine", "dolphin")
129+
}
130+
131+
tmpl, err := template.New("").Parse(catalogTmpl)
132+
if err != nil {
133+
return err
134+
}
135+
conn, err := sql.Open("mysql", databaseURL())
136+
if err != nil {
137+
return err
138+
}
139+
defer conn.Close()
140+
141+
schemas := []schemaToLoad{
142+
{
143+
Name: "mysql", // Is also mysql when running in MariaDB
144+
GenFnName: "genMysqlCatalog",
145+
DestPath: filepath.Join(dir, "mysql_catalog.go"),
146+
},
147+
{
148+
Name: "information_schema",
149+
GenFnName: "genInformationSchema",
150+
DestPath: filepath.Join(dir, "information_schema.go"),
151+
},
152+
{
153+
Name: "performance_schema",
154+
GenFnName: "genPerformanceSchema",
155+
DestPath: filepath.Join(dir, "performance_schema.go"),
156+
},
157+
{
158+
Name: "sys",
159+
GenFnName: "genSysSchema",
160+
DestPath: filepath.Join(dir, "sys_schema.go"),
161+
},
162+
}
163+
164+
for _, schema := range schemas {
165+
procs, err := readProcs(ctx, conn, schema.Name)
166+
if err != nil {
167+
return err
168+
}
169+
170+
relations, err := readRelations(ctx, conn, schema.Name)
171+
if err != nil {
172+
return err
173+
}
174+
175+
err = writeFormattedGo(tmpl, tmplCtx{
176+
Pkg: "dolphin",
177+
SchemaName: schema.Name,
178+
GenFnName: schema.GenFnName,
179+
Procs: procs,
180+
Relations: relations,
181+
}, schema.DestPath)
182+
183+
if err != nil {
184+
return err
185+
}
186+
}
187+
188+
return nil
189+
}
190+
191+
// writeFormattedGo executes `tmpl` with `data` as its context to the file `destPath`
192+
func writeFormattedGo(tmpl *template.Template, data any, destPath string) error {
193+
out := bytes.NewBuffer([]byte{})
194+
err := tmpl.Execute(out, data)
195+
if err != nil {
196+
return err
197+
}
198+
code, err := format.Source(out.Bytes())
199+
if err != nil {
200+
return err
201+
}
202+
203+
err = os.WriteFile(destPath, code, 0644)
204+
if err != nil {
205+
return err
206+
}
207+
208+
return nil
209+
}
210+
211+
type schemaToLoad struct {
212+
// name is the name of a schema to load
213+
Name string
214+
// DestPath is the destination for the generate file
215+
DestPath string
216+
// The name of the function to generate for loading this schema
217+
GenFnName string
218+
}

0 commit comments

Comments
 (0)