Skip to content

export mermaidJs #60

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ SQLize is a powerful SQL toolkit for Golang, offering parsing, building, and mig
- SQL migration generation:
- Create migrations from Golang models and current SQL schema
- Generate migration versions compatible with `golang-migrate/migrate`
- Export ERD (MermaidJs)
- Export Arvo Schema

- Advanced functionalities:
- Support for embedded structs
Expand Down Expand Up @@ -191,6 +193,7 @@ func main() {
//ALTER TABLE `user` ADD COLUMN `gender` tinyint(1) AFTER `age`;
//DROP INDEX `idx_accept_tnc_at` ON `user`;

println(newMigration.MermaidJsLive())
println(newMigration.ArvoSchema())
//...

Expand Down
111 changes: 56 additions & 55 deletions README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,25 +117,25 @@ CREATE TABLE sample (
package main

import (
"time"
"github.com/sunary/sqlize"
"time"
"github.com/sunary/sqlize"
)

type user struct {
ID int32 `sql:"primary_key;auto_increment"`
Alias string `sql:"type:VARCHAR(64)"`
Name string `sql:"type:VARCHAR(64);unique;index_columns:name,age"`
Age int
Bio string
IgnoreMe string `sql:"-"`
AcceptTncAt *time.Time `sql:"index:idx_accept_tnc_at"`
CreatedAt time.Time `sql:"default:CURRENT_TIMESTAMP"`
UpdatedAt time.Time `sql:"default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;index:idx_updated_at"`
ID int32 `sql:"primary_key;auto_increment"`
Alias string `sql:"type:VARCHAR(64)"`
Name string `sql:"type:VARCHAR(64);unique;index_columns:name,age"`
Age int
Bio string
IgnoreMe string `sql:"-"`
AcceptTncAt *time.Time `sql:"index:idx_accept_tnc_at"`
CreatedAt time.Time `sql:"default:CURRENT_TIMESTAMP"`
UpdatedAt time.Time `sql:"default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;index:idx_updated_at"`
}

func (user) TableName() string {
return "user"
return "user"
}

var createStm = `
Expand All @@ -153,48 +153,49 @@ CREATE UNIQUE INDEX idx_name_age ON user(name, age);
CREATE INDEX idx_updated_at ON user(updated_at);`

func main() {
n := time.Now()
newMigration := sqlize.NewSqlize(sqlize.WithSqlTag("sql"), sqlize.WithMigrationFolder(""))
_ = newMigration.FromObjects(user{AcceptTncAt: &n})

println(newMigration.StringUp())
//CREATE TABLE `user` (
// `id` int(11) AUTO_INCREMENT PRIMARY KEY,
// `alias` varchar(64),
// `name` varchar(64),
// `age` int(11),
// `bio` text,
// `accept_tnc_at` datetime NULL,
// `created_at` datetime DEFAULT CURRENT_TIMESTAMP(),
// `updated_at` datetime DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP()
//);
//CREATE UNIQUE INDEX `idx_name_age` ON `user`(`name`, `age`);
//CREATE INDEX `idx_accept_tnc_at` ON `user`(`accept_tnc_at`);
//CREATE INDEX `idx_updated_at` ON `user`(`updated_at`);

println(newMigration.StringDown())
//DROP TABLE IF EXISTS `user`;

oldMigration := sqlize.NewSqlize(sqlize.WithMigrationFolder(""))
//_ = oldMigration.FromMigrationFolder()
_ = oldMigration.FromString(createStm)

newMigration.Diff(*oldMigration)

println(newMigration.StringUp())
//ALTER TABLE `user` ADD COLUMN `alias` varchar(64) AFTER `id`;
//ALTER TABLE `user` DROP COLUMN `gender`;
//CREATE INDEX `idx_accept_tnc_at` ON `user`(`accept_tnc_at`);

println(newMigration.StringDown())
//ALTER TABLE `user` DROP COLUMN `alias`;
//ALTER TABLE `user` ADD COLUMN `gender` tinyint(1) AFTER `age`;
//DROP INDEX `idx_accept_tnc_at` ON `user`;

println(newMigration.ArvoSchema())
//...

_ = newMigration.WriteFiles("demo migration")
n := time.Now()
newMigration := sqlize.NewSqlize(sqlize.WithSqlTag("sql"), sqlize.WithMigrationFolder(""))
_ = newMigration.FromObjects(user{AcceptTncAt: &n})

println(newMigration.StringUp())
//CREATE TABLE `user` (
// `id` int(11) AUTO_INCREMENT PRIMARY KEY,
// `alias` varchar(64),
// `name` varchar(64),
// `age` int(11),
// `bio` text,
// `accept_tnc_at` datetime NULL,
// `created_at` datetime DEFAULT CURRENT_TIMESTAMP(),
// `updated_at` datetime DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP()
//);
//CREATE UNIQUE INDEX `idx_name_age` ON `user`(`name`, `age`);
//CREATE INDEX `idx_accept_tnc_at` ON `user`(`accept_tnc_at`);
//CREATE INDEX `idx_updated_at` ON `user`(`updated_at`);

println(newMigration.StringDown())
//DROP TABLE IF EXISTS `user`;

oldMigration := sqlize.NewSqlize(sqlize.WithMigrationFolder(""))
//_ = oldMigration.FromMigrationFolder()
_ = oldMigration.FromString(createStm)

newMigration.Diff(*oldMigration)

println(newMigration.StringUp())
//ALTER TABLE `user` ADD COLUMN `alias` varchar(64) AFTER `id`;
//ALTER TABLE `user` DROP COLUMN `gender`;
//CREATE INDEX `idx_accept_tnc_at` ON `user`(`accept_tnc_at`);

println(newMigration.StringDown())
//ALTER TABLE `user` DROP COLUMN `alias`;
//ALTER TABLE `user` ADD COLUMN `gender` tinyint(1) AFTER `age`;
//DROP INDEX `idx_accept_tnc_at` ON `user`;

println(newMigration.MermaidJsLive())
println(newMigration.ArvoSchema())
//...

_ = newMigration.WriteFiles("demo migration")
}
```

Expand Down
33 changes: 28 additions & 5 deletions element/column.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,25 @@ func (c Column) GetType() byte {
return 0
}

// DataType ...
func (c Column) DataType() string {
return c.typeDefinition(false)
}

// Constraint ...
func (c Column) Constraint() string {
for _, opt := range c.CurrentAttr.Options {
switch opt.Tp {
case ast.ColumnOptionPrimaryKey:
return "PK"
case ast.ColumnOptionReference:
return "FK"
}
}

return ""
}

// HasDefaultValue ...
func (c Column) HasDefaultValue() bool {
for _, opt := range c.CurrentAttr.Options {
Expand All @@ -60,7 +79,7 @@ func (c Column) HasDefaultValue() bool {

func (c Column) hashValue() string {
strHash := sql.EscapeSqlName(c.Name)
strHash += c.typeDefinition(false)
strHash += " " + c.typeDefinition(false)
hash := md5.Sum([]byte(strHash))
return hex.EncodeToString(hash[:])
}
Expand Down Expand Up @@ -163,7 +182,7 @@ func (c Column) pkDefinition(isPrev bool) (string, bool) {
if isPrev {
attr = c.PreviousAttr
}
strSql := c.typeDefinition(isPrev)
strSql := " " + c.typeDefinition(isPrev)

isPrimaryKey := false
for _, opt := range attr.Options {
Expand All @@ -185,6 +204,10 @@ func (c Column) pkDefinition(isPrev bool) (string, bool) {
continue
}

if opt.Tp == ast.ColumnOptionReference && opt.Refer == nil { // manual add
continue
}

_ = opt.Restore(ctx)
strSql += " " + b.String()
}
Expand All @@ -205,11 +228,11 @@ func (c Column) typeDefinition(isPrev bool) string {

switch {
case sql.IsPostgres() && attr.PgType != nil:
return " " + attr.PgType.SQLString()
return attr.PgType.SQLString()
case sql.IsSqlite() && attr.LiteType != nil:
return " " + attr.LiteType.Name.Name
return attr.LiteType.Name.Name
case attr.MysqlType != nil:
return " " + attr.MysqlType.String()
return attr.MysqlType.String()
}

return "" // column type is empty
Expand Down
11 changes: 9 additions & 2 deletions element/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,17 @@ func (t *Table) AddForeignKey(fk ForeignKey) {
if id == -1 {
t.ForeignKeys = append(t.ForeignKeys, fk)
t.indexForeignKeys[fk.Name] = len(t.ForeignKeys) - 1
return
} else {
t.ForeignKeys[id] = fk
}

t.ForeignKeys[id] = fk
for i := range t.Columns {
if t.Columns[i].Name == fk.Column {
t.Columns[i].CurrentAttr.Options = append(t.Columns[i].CurrentAttr.Options, &ast.ColumnOption{
Tp: ast.ColumnOptionReference,
})
}
}
}

// RemoveForeignKey ...
Expand Down
79 changes: 79 additions & 0 deletions export/mermaidjs/builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package mermaidjs

import (
"encoding/base64"
"fmt"
"strings"

"github.com/sunary/sqlize/element"
)

const (
erdTag = "erDiagram"
liveUrl = "https://mermaid.ink/img/"
defaultRelationType = "}o--||" // n-1
)

type MermaidJs struct {
entities []string
relations []string
}

func NewMermaidJs(tables []element.Table) *MermaidJs {
mm := &MermaidJs{}
for i := range tables {
mm.AddTable(tables[i])
}

return mm
}

func (m *MermaidJs) AddTable(table element.Table) {
normEntityName := func(s string) string {
return strings.ToUpper(s)
}

tab := " "
ent := []string{tab + normEntityName(table.Name) + " {"}

tab = " "
for _, c := range table.Columns {
dataType := c.DataType()
if strings.HasPrefix(strings.ToLower(dataType), "enum") {
dataType = dataType[:4]
}

constraint := c.Constraint()

cmt := c.CurrentAttr.Comment
if cmt != "" {
cmt = "\"" + cmt + "\""
}

ent = append(ent, fmt.Sprintf("%s%s %s %s %s", tab, dataType, c.Name, constraint, cmt))
}

tab = " "
ent = append(ent, tab+"}")
m.entities = append(m.entities, strings.Join(ent, "\n"))

uniRel := map[string]bool{}
for _, rel := range table.ForeignKeys {
if _, ok := uniRel[rel.RefTable+"-"+rel.Table]; ok {
continue
}

relType := defaultRelationType
m.relations = append(m.relations, fmt.Sprintf("%s%s %s %s: %s", tab, normEntityName(rel.Table), relType, normEntityName(rel.RefTable), rel.Column))
uniRel[rel.Table+"-"+rel.RefTable] = true
}
}

func (m MermaidJs) String() string {
return erdTag + "\n" + strings.Join(m.entities, "\n") + "\n" + strings.Join(m.relations, "\n")
}

func (m MermaidJs) Live() string {
mmParam := base64.URLEncoding.EncodeToString([]byte(m.String()))
return liveUrl + string(mmParam)
}
39 changes: 32 additions & 7 deletions sqlize.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import (
"path/filepath"

_ "github.com/pingcap/parser/test_driver" // driver parser
"github.com/sunary/sqlize/element"
"github.com/sunary/sqlize/export/avro"
"github.com/sunary/sqlize/export/mermaidjs"
sql_builder "github.com/sunary/sqlize/sql-builder"
sql_parser "github.com/sunary/sqlize/sql-parser"
sql_templates "github.com/sunary/sqlize/sql-templates"
Expand Down Expand Up @@ -225,19 +227,42 @@ func (s Sqlize) migrationDownVersion(ver int64) string {
return fmt.Sprintf(tmp.RollbackMigrationVersion(), s.migrationTable)
}

func (s Sqlize) selectTable(needTables ...string) []element.Table {
tables := make([]element.Table, 0, len(needTables))

for i := range s.parser.Migration.Tables {
if len(needTables) == 0 || utils.ContainStr(needTables, s.parser.Migration.Tables[i].Name) {
tables = append(tables, s.parser.Migration.Tables[i])
}
}

return tables
}

// MermaidJsErd export MermaidJs ERD
func (s Sqlize) MermaidJsErd(needTables ...string) string {
mm := mermaidjs.NewMermaidJs(s.selectTable(needTables...))
return mm.String()
}

// MermaidJsLive export MermaidJs Live
func (s Sqlize) MermaidJsLive(needTables ...string) string {
mm := mermaidjs.NewMermaidJs(s.selectTable(needTables...))
return mm.Live()
}

// ArvoSchema export arvo schema, support mysql only
func (s Sqlize) ArvoSchema(needTables ...string) []string {
if s.dialect != sql_templates.MysqlDialect {
return nil
}

schemas := make([]string, 0)
for i := range s.parser.Migration.Tables {
if len(needTables) == 0 || utils.ContainStr(needTables, s.parser.Migration.Tables[i].Name) {
record := avro.NewArvoSchema(s.parser.Migration.Tables[i])
jsonData, _ := json.Marshal(record)
schemas = append(schemas, string(jsonData))
}
tables := s.selectTable(needTables...)
schemas := make([]string, 0, len(tables))
for i := range tables {
record := avro.NewArvoSchema(tables[i])
jsonData, _ := json.Marshal(record)
schemas = append(schemas, string(jsonData))
}

return schemas
Expand Down
Loading