diff --git a/README.md b/README.md index 959679b..a8058e1 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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()) //... diff --git a/README_zh.md b/README_zh.md index 0a7f790..faadefb 100644 --- a/README_zh.md +++ b/README_zh.md @@ -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 = ` @@ -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") } ``` diff --git a/element/column.go b/element/column.go index 6609b15..628d247 100644 --- a/element/column.go +++ b/element/column.go @@ -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 { @@ -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[:]) } @@ -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 { @@ -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() } @@ -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 diff --git a/element/table.go b/element/table.go index 314db19..385ae12 100644 --- a/element/table.go +++ b/element/table.go @@ -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 ... diff --git a/export/mermaidjs/builder.go b/export/mermaidjs/builder.go new file mode 100644 index 0000000..49c7f34 --- /dev/null +++ b/export/mermaidjs/builder.go @@ -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) +} diff --git a/sqlize.go b/sqlize.go index 5722a3c..15a0750 100644 --- a/sqlize.go +++ b/sqlize.go @@ -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" @@ -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 diff --git a/sqlize_test.go b/sqlize_test.go index adf4d5a..f176669 100644 --- a/sqlize_test.go +++ b/sqlize_test.go @@ -17,24 +17,28 @@ type Base struct { UpdatedAt time.Time } -type addsTest struct { +type person struct { ID int32 `sql:"primary_key;auto_increment"` - Name string `sql:"type:VARCHAR(64);index:name,age;unique"` + Name string `sql:"type:VARCHAR(64);unique;index:name,age"` Alias string `sql:"-"` Age int IsFemale bool CreatedAt time.Time `sql:"default:CURRENT_TIMESTAMP"` } -type person struct { +type anotherPerson struct { ID int32 `sql:"primary_key;auto_increment"` - Name string `sql:"type:VARCHAR(64);unique;index:name,age"` + Name string `sql:"type:VARCHAR(64);index:name,age;unique"` Alias string `sql:"-"` Age int IsFemale bool CreatedAt time.Time `sql:"default:CURRENT_TIMESTAMP"` } +func (anotherPerson) TableName() string { + return "another_person" +} + type hotel struct { B1 Base `sql:"embedded"` B2 Base `sql:"embedded_prefix:base_"` @@ -56,7 +60,7 @@ type movie struct { YearReleased string `sql:"column:year_released,previous:released_at"` } -type tpl struct { +type order struct { B1 Base `sql:"embedded"` ClientID string `sql:"type:varchar(255);primary_key;index_columns:client_id,country"` Country string `sql:"type:varchar(255)"` @@ -64,38 +68,39 @@ type tpl struct { User *user `sql:"foreign_key:email;references:email"` } -func (tpl) TableName() string { - return "three_pl" +func (order) TableName() string { + return "orders" } -type tpl_sqlite struct { +type order_sqlite struct { B1 Base `sql:"embedded"` ClientID string `sql:"type:TEXT;primary_key;index_columns:client_id,country"` Country string `sql:"type:TEXT"` Email string `sql:"type:TEXT;unique"` } -func (tpl_sqlite) TableName() string { - return "three_pl_sqlite" +func (order_sqlite) TableName() string { + return "orders_sqlite" } type user struct { + Email string } var ( space = regexp.MustCompile(`\s+`) - createAddsTestStm = `CREATE TABLE adds_tests ( + createAnotherPersonStm = `CREATE TABLE another_person ( id int(11) AUTO_INCREMENT PRIMARY KEY, name varchar(64), age int(11), is_female tinyint(1), created_at datetime DEFAULT CURRENT_TIMESTAMP() );` - alterAddsTestUpStm = ` - CREATE UNIQUE INDEX idx_name_age ON adds_tests(name, age);` - alterAddsTestDownStm = ` - DROP INDEX idx_name_age ON adds_tests;` + alterAnotherPersonUpStm = ` + CREATE UNIQUE INDEX idx_name_age ON another_person(name, age);` + alterAnotherPersonDownStm = ` + DROP INDEX idx_name_age ON another_person;` createPersonStm = `CREATE TABLE person ( id int(11) AUTO_INCREMENT PRIMARY KEY, @@ -149,17 +154,17 @@ ALTER TABLE movie RENAME COLUMN released_at TO year_released;` alterMovieDownStm = ` ALTER TABLE movie RENAME COLUMN year_released TO released_at;` - expectCreateAddsTestUp = ` -CREATE TABLE adds_tests ( + expectCreateAnotherPersonUp = ` +CREATE TABLE another_person ( id int(11) AUTO_INCREMENT PRIMARY KEY, name varchar(64), age int(11), is_female tinyint(1), created_at datetime DEFAULT CURRENT_TIMESTAMP() ); -CREATE UNIQUE INDEX idx_name_age ON adds_tests(name, age);` - expectCreateAddsTestDown = ` -DROP TABLE IF EXISTS adds_tests;` +CREATE UNIQUE INDEX idx_name_age ON another_person(name, age);` + expectCreateAnotherPersonDown = ` +DROP TABLE IF EXISTS another_person;` expectCreatePersonUp = ` CREATE TABLE person ( @@ -200,45 +205,94 @@ CREATE TABLE city ( );` expectCreateCityDown = ` DROP TABLE IF EXISTS city;` - expectCreateTplUp = ` -CREATE TABLE three_pl ( + expectCreateOrderUp = ` +CREATE TABLE orders ( client_id varchar(255) COMMENT 'client id', country varchar(255) COMMENT 'country', email varchar(255) COMMENT 'email', created_at datetime, updated_at datetime ); -ALTER TABLE three_pl ADD PRIMARY KEY(client_id, country); -CREATE UNIQUE INDEX idx_email ON three_pl(email); -ALTER TABLE three_pl ADD CONSTRAINT fk_user_three_pl FOREIGN KEY (email) REFERENCES user(email);` - expectCreateTplDown = ` -DROP TABLE IF EXISTS three_pl;` - expectCreateTplPostgresUp = ` -CREATE TABLE three_pl ( +ALTER TABLE orders ADD PRIMARY KEY(client_id, country); +CREATE UNIQUE INDEX idx_email ON orders(email); +ALTER TABLE orders ADD CONSTRAINT fk_user_orders FOREIGN KEY (email) REFERENCES user(email);` + expectCreateOrderDown = ` +DROP TABLE IF EXISTS orders;` + expectCreateOrderPostgresUp = ` +CREATE TABLE orders ( client_id VARCHAR(255), country VARCHAR(255), email VARCHAR(255), created_at TIMESTAMP, updated_at TIMESTAMP ); -COMMENT ON COLUMN three_pl.client_id IS 'client id'; -COMMENT ON COLUMN three_pl.country IS 'country'; -COMMENT ON COLUMN three_pl.email IS 'email'; -ALTER TABLE three_pl ADD PRIMARY KEY(client_id, country); -CREATE UNIQUE INDEX idx_email ON three_pl(email); -ALTER TABLE three_pl ADD CONSTRAINT fk_user_three_pl FOREIGN KEY (email) REFERENCES "user"(email);` - expectCreateTplPostgresDown = ` -DROP TABLE IF EXISTS three_pl;` - expectCreateTplSqliteUp = ` -CREATE TABLE three_pl_sqlite ( +COMMENT ON COLUMN orders.client_id IS 'client id'; +COMMENT ON COLUMN orders.country IS 'country'; +COMMENT ON COLUMN orders.email IS 'email'; +ALTER TABLE orders ADD PRIMARY KEY(client_id, country); +CREATE UNIQUE INDEX idx_email ON orders(email); +ALTER TABLE orders ADD CONSTRAINT fk_user_orders FOREIGN KEY (email) REFERENCES "user"(email);` + expectCreateOrderPostgresDown = ` +DROP TABLE IF EXISTS orders;` + expectCreateOrderSqliteUp = ` +CREATE TABLE orders_sqlite ( client_id TEXT, country TEXT, email TEXT, created_at TEXT, updated_at TEXT );` - expectCreateTplSqliteDown = ` -DROP TABLE IF EXISTS three_pl_sqlite;` + expectCreateOrderSqliteDown = ` +DROP TABLE IF EXISTS orders_sqlite;` + + expectPersonMermaidJsErd = `erDiagram + PERSON { + int(11) id PK + varchar(64) name + int(11) age + tinyint(1) is_female + datetime created_at + }` + expectHotelMermaidJsErd = `erDiagram + HOTEL { + int(11) id PK + text name + datetime grand_opening + datetime created_at + datetime updated_at + datetime base_created_at + datetime base_updated_at + }` + expectPersonCityMermaidJsErd = `erDiagram + PERSON { + int(11) id PK + varchar(64) name + int(11) age + tinyint(1) is_female + datetime created_at + } + CITY { + int(11) id PK + text name + enum region + }` + expectOrderUserMermaidJsErd = `erDiagram + ORDERS { + varchar(255) client_id + varchar(255) country + varchar(255) email FK + datetime created_at + datetime updated_at + } + USER { + text email + } + ORDERS }o--|| USER: email` + + expectPersonMermaidJsLive = `https://mermaid.ink/img/ZXJEaWFncmFtCiBQRVJTT04gewogIGludCgxMSkgaWQgUEsgCiAgdmFyY2hhcig2NCkgbmFtZSAgCiAgaW50KDExKSBhZ2UgIAogIHRpbnlpbnQoMSkgaXNfZmVtYWxlICAKICBkYXRldGltZSBjcmVhdGVkX2F0ICAKIH0K` + expectHotelMermaidJsLive = `https://mermaid.ink/img/ZXJEaWFncmFtCiBIT1RFTCB7CiAgaW50KDExKSBpZCBQSyAKICB0ZXh0IG5hbWUgIAogIGRhdGV0aW1lIGdyYW5kX29wZW5pbmcgIAogIGRhdGV0aW1lIGNyZWF0ZWRfYXQgIAogIGRhdGV0aW1lIHVwZGF0ZWRfYXQgIAogIGRhdGV0aW1lIGJhc2VfY3JlYXRlZF9hdCAgCiAgZGF0ZXRpbWUgYmFzZV91cGRhdGVkX2F0ICAKIH0K` + expectPersonCityMermaidJsLive = `https://mermaid.ink/img/ZXJEaWFncmFtCiBQRVJTT04gewogIGludCgxMSkgaWQgUEsgCiAgdmFyY2hhcig2NCkgbmFtZSAgCiAgaW50KDExKSBhZ2UgIAogIHRpbnlpbnQoMSkgaXNfZmVtYWxlICAKICBkYXRldGltZSBjcmVhdGVkX2F0ICAKIH0KIENJVFkgewogIGludCgxMSkgaWQgUEsgCiAgdGV4dCBuYW1lICAKICBlbnVtIHJlZ2lvbiAgCiB9Cg==` + expectOrderUserMermaidJsLive = `https://mermaid.ink/img/ZXJEaWFncmFtCiBPUkRFUlMgewogIHZhcmNoYXIoMjU1KSBjbGllbnRfaWQgIAogIHZhcmNoYXIoMjU1KSBjb3VudHJ5ICAKICB2YXJjaGFyKDI1NSkgZW1haWwgRksgCiAgZGF0ZXRpbWUgY3JlYXRlZF9hdCAgCiAgZGF0ZXRpbWUgdXBkYXRlZF9hdCAgCiB9CiBVU0VSIHsKICB0ZXh0IGVtYWlsICAKIH0KIE9SREVSUyB9by0tfHwgVVNFUjogZW1haWw=` expectPersonArvo = ` {"type":"record","name":"person","namespace":"person","fields":[{"name":"before","type":["null",{"type":"record","name":"Value","namespace":"","fields":[{"name":"id","type":"int"},{"name":"name","type":"string"},{"name":"age","type":"int"},{"name":"is_female","type":"bool"},{"name":"created_at","type":["null",{"connect.default":"1970-01-01T00:00:00Z","connect.name":"io.debezium.time.ZonedTimestamp","connect.version":1,"type":"string"}]}],"connect.name":""}]},{"name":"after","type":["null","Value"]},{"name":"op","type":"string"},{"name":"ts_ms","type":["null","long"]},{"name":"transaction","type":["null",{"type":"record","name":"ConnectDefault","namespace":"io.confluent.connect.avro","fields":[{"name":"id","type":"string"},{"name":"total_order","type":"long"},{"name":"data_collection_order","type":"long"}],"connect.name":""}]}],"connect.name":"person"}` @@ -246,6 +300,7 @@ DROP TABLE IF EXISTS three_pl_sqlite;` {"type":"record","name":"hotel","namespace":"hotel","fields":[{"name":"before","type":["null",{"type":"record","name":"Value","namespace":"","fields":[{"name":"id","type":"int"},{"name":"name","type":"string"},{"name":"grand_opening","type":{"connect.default":"1970-01-01T00:00:00Z","connect.name":"io.debezium.time.ZonedTimestamp","connect.version":1,"type":"string"}},{"name":"created_at","type":{"connect.default":"1970-01-01T00:00:00Z","connect.name":"io.debezium.time.ZonedTimestamp","connect.version":1,"type":"string"}},{"name":"updated_at","type":{"connect.default":"1970-01-01T00:00:00Z","connect.name":"io.debezium.time.ZonedTimestamp","connect.version":1,"type":"string"}},{"name":"base_created_at","type":{"connect.default":"1970-01-01T00:00:00Z","connect.name":"io.debezium.time.ZonedTimestamp","connect.version":1,"type":"string"}},{"name":"base_updated_at","type":{"connect.default":"1970-01-01T00:00:00Z","connect.name":"io.debezium.time.ZonedTimestamp","connect.version":1,"type":"string"}}],"connect.name":""}]},{"name":"after","type":["null","Value"]},{"name":"op","type":"string"},{"name":"ts_ms","type":["null","long"]},{"name":"transaction","type":["null",{"type":"record","name":"ConnectDefault","namespace":"io.confluent.connect.avro","fields":[{"name":"id","type":"string"},{"name":"total_order","type":"long"},{"name":"data_collection_order","type":"long"}],"connect.name":""}]}],"connect.name":"hotel"}` expectCityArvo = ` {"type":"record","name":"city","namespace":"city","fields":[{"name":"before","type":["null",{"type":"record","name":"Value","namespace":"","fields":[{"name":"id","type":"int"},{"name":"name","type":"string"},{"name":"region","type":["null",{"connect.default":"init","connect.name":"io.debezium.data.Enum","connect.parameters":{"allowed":"northern,southern"},"connect.version":1,"type":"string"}]}],"connect.name":""}]},{"name":"after","type":["null","Value"]},{"name":"op","type":"string"},{"name":"ts_ms","type":["null","long"]},{"name":"transaction","type":["null",{"type":"record","name":"ConnectDefault","namespace":"io.confluent.connect.avro","fields":[{"name":"id","type":"string"},{"name":"total_order","type":"long"},{"name":"data_collection_order","type":"long"}],"connect.name":""}]}],"connect.name":"city"}` + expectCreateMigrationTableUp = `CREATE TABLE IF NOT EXISTS schema_migrations ( version bigint(20) PRIMARY KEY, dirty BOOLEAN @@ -277,14 +332,14 @@ func TestSqlize_FromObjects(t *testing.T) { wantErr bool }{ { - name: "from adds_tests object", + name: "from anotherPerson object", pluralTableName: true, args: args{ - []interface{}{addsTest{}}, + []interface{}{anotherPerson{}}, "", }, - wantMigrationUp: expectCreateAddsTestUp, - wantMigrationDown: expectCreateAddsTestDown, + wantMigrationUp: expectCreateAnotherPersonUp, + wantMigrationDown: expectCreateAnotherPersonDown, wantErr: false, }, { @@ -319,14 +374,14 @@ func TestSqlize_FromObjects(t *testing.T) { wantErr: false, }, { - name: "from tpl object", + name: "from order object", generateComment: true, args: args{ - []interface{}{tpl{}}, + []interface{}{order{}}, "/", }, - wantMigrationUp: expectCreateTplUp, - wantMigrationDown: expectCreateTplDown, + wantMigrationUp: expectCreateOrderUp, + wantMigrationDown: expectCreateOrderDown, wantErr: false, }, { @@ -402,14 +457,14 @@ func TestSqlize_FromObjects(t *testing.T) { wantErr bool }{ { - name: "from tpl object", + name: "from order object", generateComment: true, args: args{ - []interface{}{tpl{}}, + []interface{}{order{}}, "/", }, - wantMigrationUp: expectCreateTplPostgresUp, - wantMigrationDown: expectCreateTplPostgresDown, + wantMigrationUp: expectCreateOrderPostgresUp, + wantMigrationDown: expectCreateOrderPostgresDown, wantErr: false, }, } @@ -440,14 +495,14 @@ func TestSqlize_FromObjects(t *testing.T) { wantErr bool }{ { - name: "from tpl sqlite object", + name: "from order sqlite object", generateComment: true, args: args{ - []interface{}{tpl_sqlite{}}, + []interface{}{order_sqlite{}}, "/", }, - wantMigrationUp: expectCreateTplSqliteUp, - wantMigrationDown: expectCreateTplSqliteDown, + wantMigrationUp: expectCreateOrderSqliteUp, + wantMigrationDown: expectCreateOrderSqliteDown, wantErr: false, }, } @@ -831,6 +886,74 @@ func TestSqlize_HashValue(t *testing.T) { } } +func TestSqlize_Mermaidjs(t *testing.T) { + now := time.Now() + + type args struct { + models []interface{} + needTables []string + } + + MermaidJsTestcases := []struct { + name string + args args + wantErd string + wantLive string + }{ + { + name: "person mermaidjs", + args: args{ + []interface{}{person{}}, + []string{"person"}, + }, + wantErd: expectPersonMermaidJsErd, + wantLive: expectPersonMermaidJsLive, + }, + { + name: "hotel mermaidjs", + args: args{ + []interface{}{hotel{GrandOpening: &now}}, + []string{"hotel"}, + }, + wantErd: expectHotelMermaidJsErd, + wantLive: expectHotelMermaidJsLive, + }, + { + name: "person city mermaidjs", + args: args{ + []interface{}{person{}, city{}}, + []string{"person", "city"}, + }, + wantErd: expectPersonCityMermaidJsErd, + wantLive: expectPersonCityMermaidJsLive, + }, + { + name: "order user mermaidjs", + args: args{ + []interface{}{order{}, user{}}, + []string{"orders", "user"}, + }, + wantErd: expectOrderUserMermaidJsErd, + wantLive: expectOrderUserMermaidJsLive, + }, + } + for _, tt := range MermaidJsTestcases { + t.Run(tt.name, func(t *testing.T) { + opts := []SqlizeOption{} + s := NewSqlize(opts...) + + s.FromObjects(tt.args.models...) + if got := s.MermaidJsErd(tt.args.needTables...); normStr(got) != normStr(tt.wantErd) { + t.Errorf("MermaidJsErd() got = \n%v,\nexpected = \n%v", got, tt.wantErd) + } + + if got := s.MermaidJsLive(tt.args.needTables...); got != tt.wantLive { + t.Errorf("MermaidJsLive() got = \n%v,\nexpected = \n%v", got, tt.wantLive) + } + }) + } +} + func TestSqlize_ArvoSchema(t *testing.T) { now := time.Now() @@ -888,6 +1011,10 @@ func normSql(s string) string { return strings.TrimSpace(space.ReplaceAllString(s, " ")) } +func normStr(s string) string { + return strings.TrimSpace(space.ReplaceAllString(s, " ")) +} + func joinSql(s ...string) string { return strings.Join(s, "\n") }