Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Created sqlc-dolphin-gen tool to dump MySQL catalog info
  • Loading branch information
terricain committed Jun 13, 2024
commit 627a099a75376eee9672f3451ac38ddddd0148cf
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ sqlc-dev:
sqlc-pg-gen:
go build -o ~/bin/sqlc-pg-gen ./internal/tools/sqlc-pg-gen

sqlc-dolphin-gen:
go build -o ~/bin/sqlc-dolphin-gen ./internal/tools/sqlc-dolphin-gen

sqlc-gen-json:
go build -o ~/bin/sqlc-gen-json ./cmd/sqlc-gen-json

Expand Down
25 changes: 23 additions & 2 deletions internal/codegen/golang/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package golang
import (
"bufio"
"fmt"
"log"
"slices"
"sort"
"strings"

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

// getSchemasToSkip Returns a list of schemas which should not be included in the generated output
func getSchemasToSkip(req *plugin.GenerateRequest) []string {
switch req.Settings.Engine {
case "postgresql":
return []string{"pg_catalog", "information_schema"}
case "mysql":
return []string{"information_schema", "performance_schema", "sys", "mysql"}
case "sqlite":
return []string{}
default:
log.Printf("WARN internal/codegen/golang/result.go:getSchemasToSkip unhandled engine: %s\n", req.Settings.Engine)
return []string{}
}
}

func buildEnums(req *plugin.GenerateRequest, options *opts.Options) []Enum {
var enums []Enum
schemasToSkip := getSchemasToSkip(req)

for _, schema := range req.Catalog.Schemas {
if schema.Name == "pg_catalog" || schema.Name == "information_schema" {
if slices.Contains(schemasToSkip, schema.Name) {
continue
}
for _, enum := range schema.Enums {
Expand Down Expand Up @@ -62,8 +81,10 @@ func buildEnums(req *plugin.GenerateRequest, options *opts.Options) []Enum {

func buildStructs(req *plugin.GenerateRequest, options *opts.Options) []Struct {
var structs []Struct
schemasToSkip := getSchemasToSkip(req)

for _, schema := range req.Catalog.Schemas {
if schema.Name == "pg_catalog" || schema.Name == "information_schema" {
if slices.Contains(schemasToSkip, schema.Name) {
continue
}
for _, table := range schema.Tables {
Expand Down
23 changes: 16 additions & 7 deletions internal/engine/dolphin/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,22 @@ import (
"github.com/sqlc-dev/sqlc/internal/sql/catalog"
)

// toPointer converts an int to a pointer without a temporary
// variable at the call-site, and is used by the generated schemas
func toPointer(x int) *int {
return &x
}

func NewCatalog() *catalog.Catalog {
def := "public" // TODO: What is the default database for MySQL?
return &catalog.Catalog{
DefaultSchema: def,
Schemas: []*catalog.Schema{
defaultSchema(def),
},
Extensions: map[string]struct{}{},
}

c := catalog.New(def)
// New() creates an empty schema which we'll replace with MySQL stdlib functions
c.Schemas[0] = defaultSchema(def)
c.Schemas = append(c.Schemas, genInformationSchema())
c.Schemas = append(c.Schemas, genPerformanceSchema())
c.Schemas = append(c.Schemas, genSysSchema())
c.Schemas = append(c.Schemas, genMysqlCatalog())

return c
}
218 changes: 218 additions & 0 deletions internal/tools/sqlc-dolphin-gen/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
package main

import (
"bytes"
"context"
"database/sql"
"flag"
"fmt"
"github.com/go-sql-driver/mysql"
"go/format"
"log"
"os"
"path/filepath"
"text/template"

_ "github.com/go-sql-driver/mysql"
)

const catalogTmpl = `// Code generated by sqlc-dolphin-gen. DO NOT EDIT.

package {{.Pkg}}

import (
"github.com/sqlc-dev/sqlc/internal/sql/ast"
"github.com/sqlc-dev/sqlc/internal/sql/catalog"
)

var funcs{{.GenFnName}} = []*catalog.Function {
{{- range .Procs}}
{
Name: "{{.Name}}",
Args: []*catalog.Argument{
{{range .Args}}{
Name: "{{.Name}}",
Type: &ast.TypeName{Name: "{{.Type}}"},
{{- if ne .Mode "IN" }}
Mode: {{ .GoMode }},
{{- end}}
},
{{end}}
},
{{- if ne .ReturnType "" }}
ReturnType: &ast.TypeName{Name: "{{.ReturnType}}"},
{{end}}
},
{{- end}}
}

func {{.GenFnName}}() *catalog.Schema {
s := &catalog.Schema{Name: "{{ .SchemaName }}"}
s.Funcs = funcs{{.GenFnName}}
{{- if .Relations }}
s.Tables = []*catalog.Table {
{{- range .Relations }}
{
Rel: &ast.TableName{
Catalog: "public",
Schema: "{{.SchemaName}}",
Name: "{{.Name}}",
},
Columns: []*catalog.Column{
{{- range .Columns}}
{
Name: "{{.Name}}",
Type: ast.TypeName{Name: "{{.Type}}"},
{{- if .IsNotNull}}
IsNotNull: true,
{{- end}}
{{- if .Length }}
Length: toPointer({{ .Length }}),
{{- end}}
},
{{- end}}
},
},
{{- end}}
}
{{- end }}
return s
}
`

type tmplCtx struct {
Pkg string
GenFnName string
SchemaName string
Procs []Proc
Relations []Relation
}

func main() {
if err := run(context.Background()); err != nil {
log.Fatal(err)
}
}

func getEnvOrDefault(env, defaultValue string) string {
result := os.Getenv(env)
if result == "" {
return defaultValue
}
return result
}

func databaseURL() string {
dburl := os.Getenv("DATABASE_URL")
if dburl != "" {
return dburl
}
mysqlHost := getEnvOrDefault("MYSQL_HOST", "127.0.0.1")
mysqlPort := getEnvOrDefault("MYSQL_PORT", "3306")

config := mysql.Config{
User: getEnvOrDefault("MYSQL_USER", "root"),
Passwd: getEnvOrDefault("MYSQL_PASSWORD", "mysecretpassword"),
Addr: fmt.Sprintf("%s:%s", mysqlHost, mysqlPort),
DBName: getEnvOrDefault("MYSQL_DATABASE", ""),
}

return config.FormatDSN()
}

func run(ctx context.Context) error {
flag.Parse()

dir := flag.Arg(0)
if dir == "" {
dir = filepath.Join("internal", "engine", "dolphin")
}

tmpl, err := template.New("").Parse(catalogTmpl)
if err != nil {
return err
}
conn, err := sql.Open("mysql", databaseURL())
if err != nil {
return err
}
defer conn.Close()

schemas := []schemaToLoad{
{
Name: "mysql", // Is also mysql when running in MariaDB
GenFnName: "genMysqlCatalog",
DestPath: filepath.Join(dir, "mysql_catalog.go"),
},
{
Name: "information_schema",
GenFnName: "genInformationSchema",
DestPath: filepath.Join(dir, "information_schema.go"),
},
{
Name: "performance_schema",
GenFnName: "genPerformanceSchema",
DestPath: filepath.Join(dir, "performance_schema.go"),
},
{
Name: "sys",
GenFnName: "genSysSchema",
DestPath: filepath.Join(dir, "sys_schema.go"),
},
}

for _, schema := range schemas {
procs, err := readProcs(ctx, conn, schema.Name)
if err != nil {
return err
}

relations, err := readRelations(ctx, conn, schema.Name)
if err != nil {
return err
}

err = writeFormattedGo(tmpl, tmplCtx{
Pkg: "dolphin",
SchemaName: schema.Name,
GenFnName: schema.GenFnName,
Procs: procs,
Relations: relations,
}, schema.DestPath)

if err != nil {
return err
}
}

return nil
}

// writeFormattedGo executes `tmpl` with `data` as its context to the file `destPath`
func writeFormattedGo(tmpl *template.Template, data any, destPath string) error {
out := bytes.NewBuffer([]byte{})
err := tmpl.Execute(out, data)
if err != nil {
return err
}
code, err := format.Source(out.Bytes())
if err != nil {
return err
}

err = os.WriteFile(destPath, code, 0644)
if err != nil {
return err
}

return nil
}

type schemaToLoad struct {
// name is the name of a schema to load
Name string
// DestPath is the destination for the generate file
DestPath string
// The name of the function to generate for loading this schema
GenFnName string
}
Loading