Skip to content

Commit 0bf107d

Browse files
committed
export mermaidjs
1 parent 5fc3392 commit 0bf107d

File tree

7 files changed

+391
-126
lines changed

7 files changed

+391
-126
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ SQLize is a powerful SQL toolkit for Golang, offering parsing, building, and mig
1616
- SQL migration generation:
1717
- Create migrations from Golang models and current SQL schema
1818
- Generate migration versions compatible with `golang-migrate/migrate`
19+
- Export ERD (MermaidJs)
20+
- Export Arvo Schema
1921

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

196+
println(newMigration.MermaidJsLive())
194197
println(newMigration.ArvoSchema())
195198
//...
196199

README_zh.md

Lines changed: 56 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -117,25 +117,25 @@ CREATE TABLE sample (
117117
package main
118118

119119
import (
120-
"time"
121-
122-
"github.com/sunary/sqlize"
120+
"time"
121+
122+
"github.com/sunary/sqlize"
123123
)
124124

125125
type user struct {
126-
ID int32 `sql:"primary_key;auto_increment"`
127-
Alias string `sql:"type:VARCHAR(64)"`
128-
Name string `sql:"type:VARCHAR(64);unique;index_columns:name,age"`
129-
Age int
130-
Bio string
131-
IgnoreMe string `sql:"-"`
132-
AcceptTncAt *time.Time `sql:"index:idx_accept_tnc_at"`
133-
CreatedAt time.Time `sql:"default:CURRENT_TIMESTAMP"`
134-
UpdatedAt time.Time `sql:"default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;index:idx_updated_at"`
126+
ID int32 `sql:"primary_key;auto_increment"`
127+
Alias string `sql:"type:VARCHAR(64)"`
128+
Name string `sql:"type:VARCHAR(64);unique;index_columns:name,age"`
129+
Age int
130+
Bio string
131+
IgnoreMe string `sql:"-"`
132+
AcceptTncAt *time.Time `sql:"index:idx_accept_tnc_at"`
133+
CreatedAt time.Time `sql:"default:CURRENT_TIMESTAMP"`
134+
UpdatedAt time.Time `sql:"default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;index:idx_updated_at"`
135135
}
136136

137137
func (user) TableName() string {
138-
return "user"
138+
return "user"
139139
}
140140

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

155155
func main() {
156-
n := time.Now()
157-
newMigration := sqlize.NewSqlize(sqlize.WithSqlTag("sql"), sqlize.WithMigrationFolder(""))
158-
_ = newMigration.FromObjects(user{AcceptTncAt: &n})
159-
160-
println(newMigration.StringUp())
161-
//CREATE TABLE `user` (
162-
// `id` int(11) AUTO_INCREMENT PRIMARY KEY,
163-
// `alias` varchar(64),
164-
// `name` varchar(64),
165-
// `age` int(11),
166-
// `bio` text,
167-
// `accept_tnc_at` datetime NULL,
168-
// `created_at` datetime DEFAULT CURRENT_TIMESTAMP(),
169-
// `updated_at` datetime DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP()
170-
//);
171-
//CREATE UNIQUE INDEX `idx_name_age` ON `user`(`name`, `age`);
172-
//CREATE INDEX `idx_accept_tnc_at` ON `user`(`accept_tnc_at`);
173-
//CREATE INDEX `idx_updated_at` ON `user`(`updated_at`);
174-
175-
println(newMigration.StringDown())
176-
//DROP TABLE IF EXISTS `user`;
177-
178-
oldMigration := sqlize.NewSqlize(sqlize.WithMigrationFolder(""))
179-
//_ = oldMigration.FromMigrationFolder()
180-
_ = oldMigration.FromString(createStm)
181-
182-
newMigration.Diff(*oldMigration)
183-
184-
println(newMigration.StringUp())
185-
//ALTER TABLE `user` ADD COLUMN `alias` varchar(64) AFTER `id`;
186-
//ALTER TABLE `user` DROP COLUMN `gender`;
187-
//CREATE INDEX `idx_accept_tnc_at` ON `user`(`accept_tnc_at`);
188-
189-
println(newMigration.StringDown())
190-
//ALTER TABLE `user` DROP COLUMN `alias`;
191-
//ALTER TABLE `user` ADD COLUMN `gender` tinyint(1) AFTER `age`;
192-
//DROP INDEX `idx_accept_tnc_at` ON `user`;
193-
194-
println(newMigration.ArvoSchema())
195-
//...
196-
197-
_ = newMigration.WriteFiles("demo migration")
156+
n := time.Now()
157+
newMigration := sqlize.NewSqlize(sqlize.WithSqlTag("sql"), sqlize.WithMigrationFolder(""))
158+
_ = newMigration.FromObjects(user{AcceptTncAt: &n})
159+
160+
println(newMigration.StringUp())
161+
//CREATE TABLE `user` (
162+
// `id` int(11) AUTO_INCREMENT PRIMARY KEY,
163+
// `alias` varchar(64),
164+
// `name` varchar(64),
165+
// `age` int(11),
166+
// `bio` text,
167+
// `accept_tnc_at` datetime NULL,
168+
// `created_at` datetime DEFAULT CURRENT_TIMESTAMP(),
169+
// `updated_at` datetime DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP()
170+
//);
171+
//CREATE UNIQUE INDEX `idx_name_age` ON `user`(`name`, `age`);
172+
//CREATE INDEX `idx_accept_tnc_at` ON `user`(`accept_tnc_at`);
173+
//CREATE INDEX `idx_updated_at` ON `user`(`updated_at`);
174+
175+
println(newMigration.StringDown())
176+
//DROP TABLE IF EXISTS `user`;
177+
178+
oldMigration := sqlize.NewSqlize(sqlize.WithMigrationFolder(""))
179+
//_ = oldMigration.FromMigrationFolder()
180+
_ = oldMigration.FromString(createStm)
181+
182+
newMigration.Diff(*oldMigration)
183+
184+
println(newMigration.StringUp())
185+
//ALTER TABLE `user` ADD COLUMN `alias` varchar(64) AFTER `id`;
186+
//ALTER TABLE `user` DROP COLUMN `gender`;
187+
//CREATE INDEX `idx_accept_tnc_at` ON `user`(`accept_tnc_at`);
188+
189+
println(newMigration.StringDown())
190+
//ALTER TABLE `user` DROP COLUMN `alias`;
191+
//ALTER TABLE `user` ADD COLUMN `gender` tinyint(1) AFTER `age`;
192+
//DROP INDEX `idx_accept_tnc_at` ON `user`;
193+
194+
println(newMigration.MermaidJsLive())
195+
println(newMigration.ArvoSchema())
196+
//...
197+
198+
_ = newMigration.WriteFiles("demo migration")
198199
}
199200
```
200201

element/column.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,25 @@ func (c Column) GetType() byte {
4747
return 0
4848
}
4949

50+
// DataType ...
51+
func (c Column) DataType() string {
52+
return c.typeDefinition(false)
53+
}
54+
55+
// Constraint ...
56+
func (c Column) Constraint() string {
57+
for _, opt := range c.CurrentAttr.Options {
58+
switch opt.Tp {
59+
case ast.ColumnOptionPrimaryKey:
60+
return "PK"
61+
case ast.ColumnOptionReference:
62+
return "FK"
63+
}
64+
}
65+
66+
return ""
67+
}
68+
5069
// HasDefaultValue ...
5170
func (c Column) HasDefaultValue() bool {
5271
for _, opt := range c.CurrentAttr.Options {
@@ -60,7 +79,7 @@ func (c Column) HasDefaultValue() bool {
6079

6180
func (c Column) hashValue() string {
6281
strHash := sql.EscapeSqlName(c.Name)
63-
strHash += c.typeDefinition(false)
82+
strHash += " " + c.typeDefinition(false)
6483
hash := md5.Sum([]byte(strHash))
6584
return hex.EncodeToString(hash[:])
6685
}
@@ -163,7 +182,7 @@ func (c Column) pkDefinition(isPrev bool) (string, bool) {
163182
if isPrev {
164183
attr = c.PreviousAttr
165184
}
166-
strSql := c.typeDefinition(isPrev)
185+
strSql := " " + c.typeDefinition(isPrev)
167186

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

207+
if opt.Tp == ast.ColumnOptionReference && opt.Refer == nil { // manual add
208+
continue
209+
}
210+
188211
_ = opt.Restore(ctx)
189212
strSql += " " + b.String()
190213
}
@@ -205,11 +228,11 @@ func (c Column) typeDefinition(isPrev bool) string {
205228

206229
switch {
207230
case sql.IsPostgres() && attr.PgType != nil:
208-
return " " + attr.PgType.SQLString()
231+
return attr.PgType.SQLString()
209232
case sql.IsSqlite() && attr.LiteType != nil:
210-
return " " + attr.LiteType.Name.Name
233+
return attr.LiteType.Name.Name
211234
case attr.MysqlType != nil:
212-
return " " + attr.MysqlType.String()
235+
return attr.MysqlType.String()
213236
}
214237

215238
return "" // column type is empty

element/table.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,10 +207,17 @@ func (t *Table) AddForeignKey(fk ForeignKey) {
207207
if id == -1 {
208208
t.ForeignKeys = append(t.ForeignKeys, fk)
209209
t.indexForeignKeys[fk.Name] = len(t.ForeignKeys) - 1
210-
return
210+
} else {
211+
t.ForeignKeys[id] = fk
211212
}
212213

213-
t.ForeignKeys[id] = fk
214+
for i := range t.Columns {
215+
if t.Columns[i].Name == fk.Column {
216+
t.Columns[i].CurrentAttr.Options = append(t.Columns[i].CurrentAttr.Options, &ast.ColumnOption{
217+
Tp: ast.ColumnOptionReference,
218+
})
219+
}
220+
}
214221
}
215222

216223
// RemoveForeignKey ...

export/mermaidjs/builder.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package mermaidjs
2+
3+
import (
4+
"encoding/base64"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/sunary/sqlize/element"
9+
)
10+
11+
const (
12+
erdTag = "erDiagram"
13+
liveUrl = "https://mermaid.ink/img/"
14+
defaultRelationType = "}o--||" // n-1
15+
)
16+
17+
type MermaidJs struct {
18+
entities []string
19+
relations []string
20+
}
21+
22+
func NewMermaidJs(tables []element.Table) *MermaidJs {
23+
mm := &MermaidJs{}
24+
for i := range tables {
25+
mm.AddTable(tables[i])
26+
}
27+
28+
return mm
29+
}
30+
31+
func (m *MermaidJs) AddTable(table element.Table) {
32+
normEntityName := func(s string) string {
33+
return strings.ToUpper(s)
34+
}
35+
36+
tab := " "
37+
ent := []string{tab + normEntityName(table.Name) + " {"}
38+
39+
tab = " "
40+
for _, c := range table.Columns {
41+
dataType := c.DataType()
42+
if strings.HasPrefix(strings.ToLower(dataType), "enum") {
43+
dataType = dataType[:4]
44+
}
45+
46+
constraint := c.Constraint()
47+
48+
cmt := c.CurrentAttr.Comment
49+
if cmt != "" {
50+
cmt = "\"" + cmt + "\""
51+
}
52+
53+
ent = append(ent, fmt.Sprintf("%s%s %s %s %s", tab, dataType, c.Name, constraint, cmt))
54+
}
55+
56+
tab = " "
57+
ent = append(ent, tab+"}")
58+
m.entities = append(m.entities, strings.Join(ent, "\n"))
59+
60+
uniRel := map[string]bool{}
61+
for _, rel := range table.ForeignKeys {
62+
if _, ok := uniRel[rel.RefTable+"-"+rel.Table]; ok {
63+
continue
64+
}
65+
66+
relType := defaultRelationType
67+
m.relations = append(m.relations, fmt.Sprintf("%s%s %s %s: %s", tab, normEntityName(rel.Table), relType, normEntityName(rel.RefTable), rel.Column))
68+
uniRel[rel.Table+"-"+rel.RefTable] = true
69+
}
70+
}
71+
72+
func (m MermaidJs) String() string {
73+
return erdTag + "\n" + strings.Join(m.entities, "\n") + "\n" + strings.Join(m.relations, "\n")
74+
}
75+
76+
func (m MermaidJs) Live() string {
77+
mmParam := base64.URLEncoding.EncodeToString([]byte(m.String()))
78+
return liveUrl + string(mmParam)
79+
}

sqlize.go

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import (
77
"path/filepath"
88

99
_ "github.com/pingcap/parser/test_driver" // driver parser
10+
"github.com/sunary/sqlize/element"
1011
"github.com/sunary/sqlize/export/avro"
12+
"github.com/sunary/sqlize/export/mermaidjs"
1113
sql_builder "github.com/sunary/sqlize/sql-builder"
1214
sql_parser "github.com/sunary/sqlize/sql-parser"
1315
sql_templates "github.com/sunary/sqlize/sql-templates"
@@ -225,19 +227,42 @@ func (s Sqlize) migrationDownVersion(ver int64) string {
225227
return fmt.Sprintf(tmp.RollbackMigrationVersion(), s.migrationTable)
226228
}
227229

230+
func (s Sqlize) selectTable(needTables ...string) []element.Table {
231+
tables := make([]element.Table, 0, len(needTables))
232+
233+
for i := range s.parser.Migration.Tables {
234+
if len(needTables) == 0 || utils.ContainStr(needTables, s.parser.Migration.Tables[i].Name) {
235+
tables = append(tables, s.parser.Migration.Tables[i])
236+
}
237+
}
238+
239+
return tables
240+
}
241+
242+
// MermaidJsErd export MermaidJs ERD
243+
func (s Sqlize) MermaidJsErd(needTables ...string) string {
244+
mm := mermaidjs.NewMermaidJs(s.selectTable(needTables...))
245+
return mm.String()
246+
}
247+
248+
// MermaidJsLive export MermaidJs Live
249+
func (s Sqlize) MermaidJsLive(needTables ...string) string {
250+
mm := mermaidjs.NewMermaidJs(s.selectTable(needTables...))
251+
return mm.Live()
252+
}
253+
228254
// ArvoSchema export arvo schema, support mysql only
229255
func (s Sqlize) ArvoSchema(needTables ...string) []string {
230256
if s.dialect != sql_templates.MysqlDialect {
231257
return nil
232258
}
233259

234-
schemas := make([]string, 0)
235-
for i := range s.parser.Migration.Tables {
236-
if len(needTables) == 0 || utils.ContainStr(needTables, s.parser.Migration.Tables[i].Name) {
237-
record := avro.NewArvoSchema(s.parser.Migration.Tables[i])
238-
jsonData, _ := json.Marshal(record)
239-
schemas = append(schemas, string(jsonData))
240-
}
260+
tables := s.selectTable(needTables...)
261+
schemas := make([]string, 0, len(tables))
262+
for i := range tables {
263+
record := avro.NewArvoSchema(tables[i])
264+
jsonData, _ := json.Marshal(record)
265+
schemas = append(schemas, string(jsonData))
241266
}
242267

243268
return schemas

0 commit comments

Comments
 (0)