├── go.mod ├── .gitignore ├── LICENSE ├── sql2pb.go ├── README.md ├── tools └── stringx │ └── stringx.go ├── go.sum └── core └── core.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Mikaelemmmm/sql2pb 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/chuckpreslar/inflect v0.0.0-20150228233301-423e3ac59c61 7 | github.com/go-sql-driver/mysql v1.6.0 8 | github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e 9 | ) 10 | 11 | require ( 12 | github.com/onsi/ginkgo v1.16.5 // indirect 13 | github.com/onsi/gomega v1.19.0 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | .idea 27 | .idea/* 28 | .idea/** 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Mikael 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /sql2pb.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "flag" 6 | "fmt" 7 | "github.com/Mikaelemmmm/sql2pb/core" 8 | "log" 9 | "strings" 10 | 11 | _ "github.com/go-sql-driver/mysql" 12 | ) 13 | 14 | func main() { 15 | dbType := flag.String("db", "mysql", "the database type") 16 | host := flag.String("host", "localhost", "the database host") 17 | port := flag.Int("port", 3306, "the database port") 18 | user := flag.String("user", "root", "the database user") 19 | password := flag.String("password", "", "the database password") 20 | schema := flag.String("schema", "", "the database schema") 21 | table := flag.String("table", "*", "the table schema,multiple tables ',' split. ") 22 | serviceName := flag.String("service_name", *schema, "the protobuf service name , defaults to the database schema.") 23 | packageName := flag.String("package", *schema, "the protocol buffer package. defaults to the database schema.") 24 | goPackageName := flag.String("go_package", "", "the protocol buffer go_package. defaults to the database schema.") 25 | ignoreTableStr := flag.String("ignore_tables", "", "a comma spaced list of tables to ignore") 26 | ignoreColumnStr := flag.String("ignore_columns", "", "a comma spaced list of mysql columns to ignore") 27 | fieldStyle := flag.String("field_style", "sqlPb", "gen protobuf field style, sql_pb | sqlPb") 28 | 29 | flag.Parse() 30 | 31 | if *schema == "" { 32 | fmt.Println(" - please input the database schema ") 33 | return 34 | } 35 | 36 | connStr := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", *user, *password, *host, *port, *schema) 37 | db, err := sql.Open(*dbType, connStr) 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | 42 | defer db.Close() 43 | 44 | ignoreTables := strings.Split(*ignoreTableStr, ",") 45 | ignoreColumns := strings.Split(*ignoreColumnStr, ",") 46 | 47 | s, err := core.GenerateSchema(db, *table, ignoreTables, ignoreColumns, *serviceName, *goPackageName, *packageName, *fieldStyle) 48 | 49 | if nil != err { 50 | log.Fatal(err) 51 | } 52 | 53 | if nil != s { 54 | fmt.Println(s) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #### Give a star before you see it. Ha ha ha ~ ~ 2 | 3 | Generates a protobuf file from your mysql database. 4 | 5 | ### Uses 6 | 7 | ##### Tips: If your operating system is windows, the default encoding of windows command line is "GBK", you need to change it to "UTF-8", otherwise the generated file will be messed up. 8 | 9 | 10 | 11 | #### Use from the command line: 12 | 13 | `go install github.com/Mikaelemmmm/sql2pb@latest` 14 | 15 | ``` 16 | $ sql2pb -h 17 | 18 | Usage of sql2pb: 19 | -db string 20 | the database type (default "mysql") 21 | -field_style string 22 | gen protobuf field style, sql_pb | sqlPb (default "sqlPb") 23 | -go_package string 24 | the protocol buffer go_package. defaults to the database schema. 25 | -host string 26 | the database host (default "localhost") 27 | -ignore_columns string 28 | a comma spaced list of mysql columns to ignore 29 | -ignore_tables string 30 | a comma spaced list of tables to ignore 31 | -package string 32 | the protocol buffer package. defaults to the database schema. 33 | -password string 34 | the database password (default "root") 35 | -port int 36 | the database port (default 3306) 37 | -schema string 38 | the database schema 39 | -service_name string 40 | the protobuf service name , defaults to the database schema. 41 | -table string 42 | the table schema,multiple tables ',' split. (default "*") 43 | -user string 44 | the database user (default "root") 45 | 46 | ``` 47 | 48 | ``` 49 | $ sql2pb -go_package ./pb -host localhost -package pb -password root -port 3306 -schema usercenter -service_name usersrv -user root > usersrv.proto 50 | ``` 51 | 52 | 53 | 54 | #### Use as an imported library 55 | 56 | ```sh 57 | $ go get -u github.com/Mikaelemmmm/sql2pb@latest 58 | ``` 59 | 60 | ```go 61 | package main 62 | 63 | import ( 64 | "database/sql" 65 | "fmt" 66 | "github.com/Mikaelemmmm/sql2pb/core" 67 | _ "github.com/go-sql-driver/mysql" 68 | "log" 69 | ) 70 | 71 | func main() { 72 | 73 | dbType:= "mysql" 74 | connStr := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", "root", "root", "127.0.0.1", 3306, "zero-demo") 75 | pkg := "my_package" 76 | goPkg := "./my_package" 77 | table:= "*" 78 | serviceName:="usersrv" 79 | fieldStyle := "sqlPb" 80 | 81 | db, err := sql.Open(dbType, connStr) 82 | if err != nil { 83 | log.Fatal(err) 84 | } 85 | defer db.Close() 86 | s, err := core.GenerateSchema(db, table,nil,nil,serviceName, goPkg, pkg,fieldStyle) 87 | 88 | if nil != err { 89 | log.Fatal(err) 90 | } 91 | 92 | if nil != s { 93 | fmt.Println(s) 94 | } 95 | } 96 | ``` 97 | 98 | #### Thanks for schemabuf 99 | schemabuf : https://github.com/mcos/schemabuf 100 | -------------------------------------------------------------------------------- /tools/stringx/stringx.go: -------------------------------------------------------------------------------- 1 | package stringx 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | "unicode" 7 | ) 8 | 9 | var WhiteSpace = []rune{'\n', '\t', '\f', '\v', ' '} 10 | 11 | // String provides for converting the source text into other spell case,like lower,snake,camel 12 | type String struct { 13 | source string 14 | } 15 | 16 | // From converts the input text to String and returns it 17 | func From(data string) String { 18 | return String{source: data} 19 | } 20 | 21 | // IsEmptyOrSpace returns true if the length of the string value is 0 after call strings.TrimSpace, or else returns false 22 | func (s String) IsEmptyOrSpace() bool { 23 | if len(s.source) == 0 { 24 | return true 25 | } 26 | if strings.TrimSpace(s.source) == "" { 27 | return true 28 | } 29 | return false 30 | } 31 | 32 | // Lower calls the strings.ToLower 33 | func (s String) Lower() string { 34 | return strings.ToLower(s.source) 35 | } 36 | 37 | // Upper calls the strings.ToUpper 38 | func (s String) Upper() string { 39 | return strings.ToUpper(s.source) 40 | } 41 | 42 | // ReplaceAll calls the strings.ReplaceAll 43 | func (s String) ReplaceAll(old, new string) string { 44 | return strings.Replace(s.source, old, new, -1) 45 | } 46 | 47 | // Source returns the source string value 48 | func (s String) Source() string { 49 | return s.source 50 | } 51 | 52 | // Title calls the strings.Title 53 | func (s String) Title() string { 54 | if s.IsEmptyOrSpace() { 55 | return s.source 56 | } 57 | return strings.Title(s.source) 58 | } 59 | 60 | // ToCamel converts the input text into camel case 61 | func (s String) ToCamel() string { 62 | list := s.splitBy(func(r rune) bool { 63 | return r == '_' 64 | }, true) 65 | var target []string 66 | for _, item := range list { 67 | target = append(target, From(item).Title()) 68 | } 69 | return strings.Join(target, "") 70 | } 71 | 72 | // ToCamelWithStartLower converts the input text into camel case 73 | func (s String) ToCamelWithStartLower() string { 74 | list := s.splitBy(func(r rune) bool { 75 | return r == '_' 76 | }, true) 77 | var target []string 78 | for i, item := range list { 79 | if i !=0{ 80 | target = append(target, From(item).Title()) 81 | }else{ 82 | target = append(target, item) 83 | } 84 | 85 | } 86 | return strings.Join(target, "") 87 | } 88 | 89 | // ToSnake converts the input text into snake case 90 | func (s String) ToSnake() string { 91 | list := s.splitBy(unicode.IsUpper, false) 92 | var target []string 93 | for _, item := range list { 94 | target = append(target, From(item).Lower()) 95 | } 96 | return strings.Join(target, "_") 97 | } 98 | 99 | // Untitle return the original string if rune is not letter at index 0 100 | func (s String) Untitle() string { 101 | if s.IsEmptyOrSpace() { 102 | return s.source 103 | } 104 | r := rune(s.source[0]) 105 | if !unicode.IsUpper(r) && !unicode.IsLower(r) { 106 | return s.source 107 | } 108 | return string(unicode.ToLower(r)) + s.source[1:] 109 | } 110 | 111 | // it will not ignore spaces 112 | func (s String) splitBy(fn func(r rune) bool, remove bool) []string { 113 | if s.IsEmptyOrSpace() { 114 | return nil 115 | } 116 | var list []string 117 | buffer := new(bytes.Buffer) 118 | for _, r := range s.source { 119 | if fn(r) { 120 | if buffer.Len() != 0 { 121 | list = append(list, buffer.String()) 122 | buffer.Reset() 123 | } 124 | if !remove { 125 | buffer.WriteRune(r) 126 | } 127 | continue 128 | } 129 | buffer.WriteRune(r) 130 | } 131 | if buffer.Len() != 0 { 132 | list = append(list, buffer.String()) 133 | } 134 | return list 135 | } 136 | 137 | func ContainsAny(s string, runes ...rune) bool { 138 | if len(runes) == 0 { 139 | return true 140 | } 141 | tmp := make(map[rune]struct{}, len(runes)) 142 | for _, r := range runes { 143 | tmp[r] = struct{}{} 144 | } 145 | 146 | for _, r := range s { 147 | if _, ok := tmp[r]; ok { 148 | return true 149 | } 150 | } 151 | return false 152 | } 153 | 154 | func ContainsWhiteSpace(s string) bool { 155 | return ContainsAny(s, WhiteSpace...) 156 | } 157 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/chuckpreslar/inflect v0.0.0-20150228233301-423e3ac59c61 h1:p3YW8skKpechCIYMN6D26pCy+7hedHyAzpjqNQcuWFo= 2 | github.com/chuckpreslar/inflect v0.0.0-20150228233301-423e3ac59c61/go.mod h1:EvGA6uaxT1pYJoxnnvkW+17PQ4wf02iLbBomS0vTkVU= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 6 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 7 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 8 | github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= 9 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 10 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 11 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 12 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 13 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 14 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 15 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 16 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 17 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 18 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 19 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 20 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 21 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 22 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 23 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 24 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 25 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 26 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 27 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= 28 | github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= 29 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 30 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 31 | github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= 32 | github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= 33 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 34 | github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e h1:zWKUYT07mGmVBH+9UgnHXd/ekCK99C8EbDSAt5qsjXE= 35 | github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= 36 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 37 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 38 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 39 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 40 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 41 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 42 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 43 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 44 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 45 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 46 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 47 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 48 | golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= 49 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 50 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 51 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 52 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 53 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 54 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 55 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 56 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 57 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 58 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 59 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 60 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 61 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= 62 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 63 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 64 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 65 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 66 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 67 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 68 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 69 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 70 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 71 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 72 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 73 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 74 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 75 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 76 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 77 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 78 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 79 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 80 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 81 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 82 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 83 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 84 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 85 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 86 | -------------------------------------------------------------------------------- /core/core.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "database/sql" 6 | "fmt" 7 | "github.com/Mikaelemmmm/sql2pb/tools/stringx" 8 | "log" 9 | "regexp" 10 | "sort" 11 | "strings" 12 | 13 | "github.com/chuckpreslar/inflect" 14 | "github.com/serenize/snaker" 15 | ) 16 | 17 | const ( 18 | // proto3 is a describing the proto3 syntax type. 19 | proto3 = "proto3" 20 | 21 | // indent represents the indentation amount for fields. the style guide suggests 22 | // two spaces 23 | indent = " " 24 | 25 | // gen protobuf field style 26 | fieldStyleToCamelWithStartLower = "sqlPb" 27 | fieldStyleToSnake = "sql_pb" 28 | ) 29 | 30 | // GenerateSchema generates a protobuf schema from a database connection and a package name. 31 | // A list of tables to ignore may also be supplied. 32 | // The returned schema implements the `fmt.Stringer` interface, in order to generate a string 33 | // representation of a protobuf schema. 34 | // Do not rely on the structure of the Generated schema to provide any context about 35 | // the protobuf types. The schema reflects the layout of a protobuf file and should be used 36 | // to pipe the output of the `Schema.String()` to a file. 37 | func GenerateSchema(db *sql.DB, table string, ignoreTables, ignoreColumns []string, serviceName, goPkg, pkg, fieldStyle string) (*Schema, error) { 38 | s := &Schema{} 39 | 40 | dbs, err := dbSchema(db) 41 | if nil != err { 42 | return nil, err 43 | } 44 | 45 | s.Syntax = proto3 46 | s.ServiceName = serviceName 47 | if "" != pkg { 48 | s.Package = pkg 49 | } 50 | if "" != goPkg { 51 | s.GoPackage = goPkg 52 | } else { 53 | s.GoPackage = "./" + s.Package 54 | } 55 | 56 | cols, err := dbColumns(db, dbs, table) 57 | if nil != err { 58 | return nil, err 59 | } 60 | 61 | err = typesFromColumns(s, cols, ignoreTables, ignoreColumns, fieldStyle) 62 | if nil != err { 63 | return nil, err 64 | } 65 | 66 | sort.Sort(s.Imports) 67 | sort.Sort(s.Messages) 68 | sort.Sort(s.Enums) 69 | 70 | return s, nil 71 | } 72 | 73 | // typesFromColumns creates the appropriate schema properties from a collection of column types. 74 | func typesFromColumns(s *Schema, cols []Column, ignoreTables, ignoreColumns []string, fieldStyle string) error { 75 | messageMap := map[string]*Message{} 76 | ignoreMap := map[string]bool{} 77 | ignoreColumnMap := map[string]bool{} 78 | for _, ig := range ignoreTables { 79 | ignoreMap[ig] = true 80 | } 81 | for _, ic := range ignoreColumns { 82 | ignoreColumnMap[ic] = true 83 | } 84 | 85 | for _, c := range cols { 86 | if _, ok := ignoreMap[c.TableName]; ok { 87 | continue 88 | } 89 | if _, ok := ignoreColumnMap[c.ColumnName]; ok { 90 | continue 91 | } 92 | 93 | messageName := snaker.SnakeToCamel(c.TableName) 94 | //messageName = inflect.Singularize(messageName) 95 | 96 | msg, ok := messageMap[messageName] 97 | if !ok { 98 | messageMap[messageName] = &Message{Name: messageName, Comment: c.TableComment, Style: fieldStyle} 99 | msg = messageMap[messageName] 100 | } 101 | 102 | err := parseColumn(s, msg, c) 103 | if nil != err { 104 | return err 105 | } 106 | } 107 | 108 | for _, v := range messageMap { 109 | s.Messages = append(s.Messages, v) 110 | } 111 | 112 | return nil 113 | } 114 | 115 | func dbSchema(db *sql.DB) (string, error) { 116 | var schema string 117 | 118 | err := db.QueryRow("SELECT SCHEMA()").Scan(&schema) 119 | 120 | return schema, err 121 | } 122 | 123 | func dbColumns(db *sql.DB, schema, table string) ([]Column, error) { 124 | 125 | tableArr := strings.Split(table, ",") 126 | 127 | q := "SELECT c.TABLE_NAME, c.COLUMN_NAME, c.IS_NULLABLE, c.DATA_TYPE, " + 128 | "c.CHARACTER_MAXIMUM_LENGTH, c.NUMERIC_PRECISION, c.NUMERIC_SCALE, c.COLUMN_TYPE ,c.COLUMN_COMMENT,t.TABLE_COMMENT " + 129 | "FROM INFORMATION_SCHEMA.COLUMNS as c LEFT JOIN INFORMATION_SCHEMA.TABLES as t on c.TABLE_NAME = t.TABLE_NAME and c.TABLE_SCHEMA = t.TABLE_SCHEMA" + 130 | " WHERE c.TABLE_SCHEMA = ?" 131 | 132 | if table != "" && table != "*" { 133 | q += " AND c.TABLE_NAME IN('" + strings.TrimRight(strings.Join(tableArr, "' ,'"), ",") + "')" 134 | } 135 | 136 | q += " ORDER BY c.TABLE_NAME, c.ORDINAL_POSITION" 137 | 138 | rows, err := db.Query(q, schema) 139 | defer rows.Close() 140 | if nil != err { 141 | return nil, err 142 | } 143 | 144 | cols := []Column{} 145 | 146 | for rows.Next() { 147 | cs := Column{} 148 | err := rows.Scan(&cs.TableName, &cs.ColumnName, &cs.IsNullable, &cs.DataType, 149 | &cs.CharacterMaximumLength, &cs.NumericPrecision, &cs.NumericScale, &cs.ColumnType, &cs.ColumnComment, &cs.TableComment) 150 | if err != nil { 151 | log.Fatal(err) 152 | } 153 | 154 | if cs.TableComment == "" { 155 | cs.TableComment = stringx.From(cs.TableName).ToCamelWithStartLower() 156 | } 157 | 158 | cols = append(cols, cs) 159 | } 160 | if err := rows.Err(); nil != err { 161 | return nil, err 162 | } 163 | 164 | return cols, nil 165 | } 166 | 167 | // Schema is a representation of a protobuf schema. 168 | type Schema struct { 169 | Syntax string 170 | ServiceName string 171 | GoPackage string 172 | Package string 173 | Imports sort.StringSlice 174 | Messages MessageCollection 175 | Enums EnumCollection 176 | } 177 | 178 | // MessageCollection represents a sortable collection of messages. 179 | type MessageCollection []*Message 180 | 181 | func (mc MessageCollection) Len() int { 182 | return len(mc) 183 | } 184 | 185 | func (mc MessageCollection) Less(i, j int) bool { 186 | return mc[i].Name < mc[j].Name 187 | } 188 | 189 | func (mc MessageCollection) Swap(i, j int) { 190 | mc[i], mc[j] = mc[j], mc[i] 191 | } 192 | 193 | // EnumCollection represents a sortable collection of enums. 194 | type EnumCollection []*Enum 195 | 196 | func (ec EnumCollection) Len() int { 197 | return len(ec) 198 | } 199 | 200 | func (ec EnumCollection) Less(i, j int) bool { 201 | return ec[i].Name < ec[j].Name 202 | } 203 | 204 | func (ec EnumCollection) Swap(i, j int) { 205 | ec[i], ec[j] = ec[j], ec[i] 206 | } 207 | 208 | // AppendImport adds an import to a schema if the specific import does not already exist in the schema. 209 | func (s *Schema) AppendImport(imports string) { 210 | shouldAdd := true 211 | for _, si := range s.Imports { 212 | if si == imports { 213 | shouldAdd = false 214 | break 215 | } 216 | } 217 | 218 | if shouldAdd { 219 | s.Imports = append(s.Imports, imports) 220 | } 221 | 222 | } 223 | 224 | // String returns a string representation of a Schema. 225 | func (s *Schema) String() string { 226 | buf := new(bytes.Buffer) 227 | buf.WriteString(fmt.Sprintf("syntax = \"%s\";\n", s.Syntax)) 228 | buf.WriteString("\n") 229 | buf.WriteString(fmt.Sprintf("option go_package =\"%s\";\n", s.GoPackage)) 230 | buf.WriteString("\n") 231 | buf.WriteString(fmt.Sprintf("package %s;\n", s.Package)) 232 | 233 | buf.WriteString("\n") 234 | buf.WriteString("// ------------------------------------ \n") 235 | buf.WriteString("// Messages\n") 236 | buf.WriteString("// ------------------------------------ \n\n") 237 | 238 | for _, m := range s.Messages { 239 | buf.WriteString("//--------------------------------" + m.Comment + "--------------------------------") 240 | buf.WriteString("\n") 241 | m.GenDefaultMessage(buf) 242 | m.GenRpcAddReqRespMessage(buf) 243 | m.GenRpcUpdateReqMessage(buf) 244 | m.GenRpcDelReqMessage(buf) 245 | m.GenRpcGetByIdReqMessage(buf) 246 | m.GenRpcSearchReqMessage(buf) 247 | } 248 | 249 | buf.WriteString("\n") 250 | 251 | if len(s.Enums) > 0 { 252 | buf.WriteString("// ------------------------------------ \n") 253 | buf.WriteString("// Enums\n") 254 | buf.WriteString("// ------------------------------------ \n\n") 255 | 256 | for _, e := range s.Enums { 257 | buf.WriteString(fmt.Sprintf("%s\n", e)) 258 | } 259 | } 260 | 261 | buf.WriteString("\n") 262 | buf.WriteString("// ------------------------------------ \n") 263 | buf.WriteString("// Rpc Func\n") 264 | buf.WriteString("// ------------------------------------ \n\n") 265 | 266 | funcTpl := "service " + s.ServiceName + "{ \n\n" 267 | for _, m := range s.Messages { 268 | funcTpl += "\t //-----------------------" + m.Comment + "----------------------- \n" 269 | funcTpl += "\t rpc Add" + m.Name + "(Add" + m.Name + "Req) returns (Add" + m.Name + "Resp); \n" 270 | funcTpl += "\t rpc Update" + m.Name + "(Update" + m.Name + "Req) returns (Update" + m.Name + "Resp); \n" 271 | funcTpl += "\t rpc Del" + m.Name + "(Del" + m.Name + "Req) returns (Del" + m.Name + "Resp); \n" 272 | funcTpl += "\t rpc Get" + m.Name + "ById(Get" + m.Name + "ByIdReq) returns (Get" + m.Name + "ByIdResp); \n" 273 | funcTpl += "\t rpc Search" + m.Name + "(Search" + m.Name + "Req) returns (Search" + m.Name + "Resp); \n" 274 | } 275 | funcTpl = funcTpl + "\n}" 276 | buf.WriteString(funcTpl) 277 | 278 | return buf.String() 279 | } 280 | 281 | // Enum represents a protocol buffer enumerated type. 282 | type Enum struct { 283 | Name string 284 | Comment string 285 | Fields []EnumField 286 | } 287 | 288 | // String returns a string representation of an Enum. 289 | func (e *Enum) String() string { 290 | buf := new(bytes.Buffer) 291 | 292 | buf.WriteString(fmt.Sprintf("// %s \n", e.Comment)) 293 | buf.WriteString(fmt.Sprintf("enum %s {\n", e.Name)) 294 | 295 | for _, f := range e.Fields { 296 | buf.WriteString(fmt.Sprintf("%s%s;\n", indent, f)) 297 | } 298 | 299 | buf.WriteString("}\n") 300 | 301 | return buf.String() 302 | } 303 | 304 | // AppendField appends an EnumField to an Enum. 305 | func (e *Enum) AppendField(ef EnumField) error { 306 | for _, f := range e.Fields { 307 | if f.Tag() == ef.Tag() { 308 | return fmt.Errorf("tag `%d` is already in use by field `%s`", ef.Tag(), f.Name()) 309 | } 310 | } 311 | 312 | e.Fields = append(e.Fields, ef) 313 | 314 | return nil 315 | } 316 | 317 | // EnumField represents a field in an enumerated type. 318 | type EnumField struct { 319 | name string 320 | tag int 321 | } 322 | 323 | // NewEnumField constructs an EnumField type. 324 | func NewEnumField(name string, tag int) EnumField { 325 | name = strings.ToUpper(name) 326 | 327 | re := regexp.MustCompile(`([^\w]+)`) 328 | name = re.ReplaceAllString(name, "_") 329 | 330 | return EnumField{name, tag} 331 | } 332 | 333 | // String returns a string representation of an Enum. 334 | func (ef EnumField) String() string { 335 | return fmt.Sprintf("%s = %d", ef.name, ef.tag) 336 | } 337 | 338 | // Name returns the name of the enum field. 339 | func (ef EnumField) Name() string { 340 | return ef.name 341 | } 342 | 343 | // Tag returns the identifier tag of the enum field. 344 | func (ef EnumField) Tag() int { 345 | return ef.tag 346 | } 347 | 348 | // newEnumFromStrings creates an enum from a name and a slice of strings that represent the names of each field. 349 | func newEnumFromStrings(name, comment string, ss []string) (*Enum, error) { 350 | enum := &Enum{} 351 | enum.Name = name 352 | enum.Comment = comment 353 | 354 | for i, s := range ss { 355 | err := enum.AppendField(NewEnumField(s, i)) 356 | if nil != err { 357 | return nil, err 358 | } 359 | } 360 | 361 | return enum, nil 362 | } 363 | 364 | // Service represents a protocol buffer service. 365 | // TODO: Implement this in a schema. 366 | type Service struct{} 367 | 368 | // Message represents a protocol buffer message. 369 | type Message struct { 370 | Name string 371 | Comment string 372 | Fields []MessageField 373 | Style string 374 | } 375 | 376 | // GenDefaultMessage gen default message 377 | func (m Message) GenDefaultMessage(buf *bytes.Buffer) { 378 | mOrginName := m.Name 379 | mOrginFields := m.Fields 380 | 381 | curFields := []MessageField{} 382 | var filedTag int 383 | for _, field := range m.Fields { 384 | if isInSlice([]string{"version", "del_state", "delete_time"}, field.Name) { 385 | continue 386 | } 387 | filedTag++ 388 | field.tag = filedTag 389 | field.Name = stringx.From(field.Name).ToCamelWithStartLower() 390 | if m.Style == fieldStyleToSnake { 391 | field.Name = stringx.From(field.Name).ToSnake() 392 | } 393 | 394 | if field.Comment == "" { 395 | field.Comment = field.Name 396 | } 397 | curFields = append(curFields, field) 398 | } 399 | m.Fields = curFields 400 | buf.WriteString(fmt.Sprintf("%s\n", m)) 401 | 402 | //reset 403 | m.Name = mOrginName 404 | m.Fields = mOrginFields 405 | } 406 | 407 | // GenRpcAddReqRespMessage gen add req message 408 | func (m Message) GenRpcAddReqRespMessage(buf *bytes.Buffer) { 409 | mOrginName := m.Name 410 | mOrginFields := m.Fields 411 | 412 | //req 413 | m.Name = "Add" + mOrginName + "Req" 414 | curFields := []MessageField{} 415 | var filedTag int 416 | for _, field := range m.Fields { 417 | if isInSlice([]string{"id", "create_time", "update_time", "version", "del_state", "delete_time"}, field.Name) { 418 | continue 419 | } 420 | filedTag++ 421 | field.tag = filedTag 422 | field.Name = stringx.From(field.Name).ToCamelWithStartLower() 423 | if m.Style == fieldStyleToSnake { 424 | field.Name = stringx.From(field.Name).ToSnake() 425 | } 426 | if field.Comment == "" { 427 | field.Comment = field.Name 428 | } 429 | curFields = append(curFields, field) 430 | } 431 | m.Fields = curFields 432 | buf.WriteString(fmt.Sprintf("%s\n", m)) 433 | 434 | //reset 435 | m.Name = mOrginName 436 | m.Fields = mOrginFields 437 | 438 | //resp 439 | m.Name = "Add" + mOrginName + "Resp" 440 | m.Fields = []MessageField{} 441 | buf.WriteString(fmt.Sprintf("%s\n", m)) 442 | 443 | //reset 444 | m.Name = mOrginName 445 | m.Fields = mOrginFields 446 | 447 | } 448 | 449 | // GenRpcUpdateReqMessage gen add resp message 450 | func (m Message) GenRpcUpdateReqMessage(buf *bytes.Buffer) { 451 | mOrginName := m.Name 452 | mOrginFields := m.Fields 453 | 454 | m.Name = "Update" + mOrginName + "Req" 455 | curFields := []MessageField{} 456 | var filedTag int 457 | for _, field := range m.Fields { 458 | if isInSlice([]string{"create_time", "update_time", "version", "del_state", "delete_time"}, field.Name) { 459 | continue 460 | } 461 | filedTag++ 462 | field.tag = filedTag 463 | field.Name = stringx.From(field.Name).ToCamelWithStartLower() 464 | if m.Style == fieldStyleToSnake { 465 | field.Name = stringx.From(field.Name).ToSnake() 466 | } 467 | if field.Comment == "" { 468 | field.Comment = field.Name 469 | } 470 | curFields = append(curFields, field) 471 | } 472 | m.Fields = curFields 473 | buf.WriteString(fmt.Sprintf("%s\n", m)) 474 | 475 | //reset 476 | m.Name = mOrginName 477 | m.Fields = mOrginFields 478 | 479 | //resp 480 | m.Name = "Update" + mOrginName + "Resp" 481 | m.Fields = []MessageField{} 482 | buf.WriteString(fmt.Sprintf("%s\n", m)) 483 | 484 | //reset 485 | m.Name = mOrginName 486 | m.Fields = mOrginFields 487 | } 488 | 489 | // GenRpcDelReqMessage gen add resp message 490 | func (m Message) GenRpcDelReqMessage(buf *bytes.Buffer) { 491 | mOrginName := m.Name 492 | mOrginFields := m.Fields 493 | 494 | m.Name = "Del" + mOrginName + "Req" 495 | m.Fields = []MessageField{ 496 | {Name: "id", Typ: "int64", tag: 1, Comment: "id"}, 497 | } 498 | buf.WriteString(fmt.Sprintf("%s\n", m)) 499 | 500 | //reset 501 | m.Name = mOrginName 502 | m.Fields = mOrginFields 503 | 504 | //resp 505 | m.Name = "Del" + mOrginName + "Resp" 506 | m.Fields = []MessageField{} 507 | buf.WriteString(fmt.Sprintf("%s\n", m)) 508 | 509 | //reset 510 | m.Name = mOrginName 511 | m.Fields = mOrginFields 512 | } 513 | 514 | // GenRpcGetByIdReqMessage gen add resp message 515 | func (m Message) GenRpcGetByIdReqMessage(buf *bytes.Buffer) { 516 | mOrginName := m.Name 517 | mOrginFields := m.Fields 518 | 519 | m.Name = "Get" + mOrginName + "ByIdReq" 520 | m.Fields = []MessageField{ 521 | {Name: "id", Typ: "int64", tag: 1, Comment: "id"}, 522 | } 523 | buf.WriteString(fmt.Sprintf("%s\n", m)) 524 | 525 | //reset 526 | m.Name = mOrginName 527 | m.Fields = mOrginFields 528 | 529 | //resp 530 | firstWord := strings.ToLower(string(m.Name[0])) 531 | m.Name = "Get" + mOrginName + "ByIdResp" 532 | 533 | name := stringx.From(firstWord + mOrginName[1:]).ToCamelWithStartLower() 534 | comment := stringx.From(firstWord + mOrginName[1:]).ToCamelWithStartLower() 535 | if m.Style == fieldStyleToSnake { 536 | name = stringx.From(firstWord + mOrginName[1:]).ToSnake() 537 | comment = stringx.From(firstWord + mOrginName[1:]).ToSnake() 538 | } 539 | m.Fields = []MessageField{ 540 | {Typ: mOrginName, Name: name, tag: 1, Comment: comment}, 541 | } 542 | buf.WriteString(fmt.Sprintf("%s\n", m)) 543 | 544 | //reset 545 | m.Name = mOrginName 546 | m.Fields = mOrginFields 547 | } 548 | 549 | // GenRpcSearchReqMessage gen add resp message 550 | func (m Message) GenRpcSearchReqMessage(buf *bytes.Buffer) { 551 | mOrginName := m.Name 552 | mOrginFields := m.Fields 553 | 554 | m.Name = "Search" + mOrginName + "Req" 555 | curFields := []MessageField{ 556 | {Typ: "int64", Name: "page", tag: 1, Comment: "page"}, 557 | {Typ: "int64", Name: "limit", tag: 2, Comment: "limit"}, 558 | } 559 | var filedTag = len(curFields) 560 | for _, field := range m.Fields { 561 | if isInSlice([]string{"version", "del_state", "delete_time"}, field.Name) { 562 | continue 563 | } 564 | filedTag++ 565 | field.tag = filedTag 566 | 567 | field.Name = stringx.From(field.Name).ToCamelWithStartLower() 568 | if m.Style == fieldStyleToSnake { 569 | field.Name = stringx.From(field.Name).ToSnake() 570 | } 571 | if field.Comment == "" { 572 | field.Comment = field.Name 573 | } 574 | curFields = append(curFields, field) 575 | } 576 | m.Fields = curFields 577 | buf.WriteString(fmt.Sprintf("%s\n", m)) 578 | 579 | //reset 580 | m.Name = mOrginName 581 | m.Fields = mOrginFields 582 | 583 | //resp 584 | firstWord := strings.ToLower(string(m.Name[0])) 585 | m.Name = "Search" + mOrginName + "Resp" 586 | 587 | name := stringx.From(firstWord + mOrginName[1:]).ToCamelWithStartLower() 588 | comment := stringx.From(firstWord + mOrginName[1:]).ToCamelWithStartLower() 589 | if m.Style == fieldStyleToSnake { 590 | name = stringx.From(firstWord + mOrginName[1:]).ToSnake() 591 | comment = stringx.From(firstWord + mOrginName[1:]).ToSnake() 592 | } 593 | 594 | m.Fields = []MessageField{ 595 | {Typ: "repeated " + mOrginName, Name: name, tag: 1, Comment: comment}, 596 | } 597 | buf.WriteString(fmt.Sprintf("%s\n", m)) 598 | 599 | //reset 600 | m.Name = mOrginName 601 | m.Fields = mOrginFields 602 | } 603 | 604 | // String returns a string representation of a Message. 605 | func (m Message) String() string { 606 | var buf bytes.Buffer 607 | 608 | buf.WriteString(fmt.Sprintf("message %s {\n", m.Name)) 609 | for _, f := range m.Fields { 610 | buf.WriteString(fmt.Sprintf("%s%s; //%s\n", indent, f, f.Comment)) 611 | } 612 | buf.WriteString("}\n") 613 | 614 | return buf.String() 615 | } 616 | 617 | // AppendField appends a message field to a message. If the tag of the message field is in use, an error will be returned. 618 | func (m *Message) AppendField(mf MessageField) error { 619 | for _, f := range m.Fields { 620 | if f.Tag() == mf.Tag() { 621 | return fmt.Errorf("tag `%d` is already in use by field `%s`", mf.Tag(), f.Name) 622 | } 623 | } 624 | 625 | m.Fields = append(m.Fields, mf) 626 | 627 | return nil 628 | } 629 | 630 | // MessageField represents the field of a message. 631 | type MessageField struct { 632 | Typ string 633 | Name string 634 | tag int 635 | Comment string 636 | } 637 | 638 | // NewMessageField creates a new message field. 639 | func NewMessageField(typ, name string, tag int, comment string) MessageField { 640 | return MessageField{typ, name, tag, comment} 641 | } 642 | 643 | // Tag returns the unique numbered tag of the message field. 644 | func (f MessageField) Tag() int { 645 | return f.tag 646 | } 647 | 648 | // String returns a string representation of a message field. 649 | func (f MessageField) String() string { 650 | return fmt.Sprintf("%s %s = %d", f.Typ, f.Name, f.tag) 651 | } 652 | 653 | // Column represents a database column. 654 | type Column struct { 655 | Style string 656 | TableName string 657 | TableComment string 658 | ColumnName string 659 | IsNullable string 660 | DataType string 661 | CharacterMaximumLength sql.NullInt64 662 | NumericPrecision sql.NullInt64 663 | NumericScale sql.NullInt64 664 | ColumnType string 665 | ColumnComment string 666 | } 667 | 668 | // Table represents a database table. 669 | type Table struct { 670 | TableName string 671 | ColumnName string 672 | } 673 | 674 | // parseColumn parses a column and inserts the relevant fields in the Message. If an enumerated type is encountered, an Enum will 675 | // be added to the Schema. Returns an error if an incompatible protobuf data type cannot be found for the database column type. 676 | func parseColumn(s *Schema, msg *Message, col Column) error { 677 | typ := strings.ToLower(col.DataType) 678 | var fieldType string 679 | 680 | switch typ { 681 | case "char", "varchar", "text", "longtext", "mediumtext", "tinytext": 682 | fieldType = "string" 683 | case "enum", "set": 684 | // Parse c.ColumnType to get the enum list 685 | enumList := regexp.MustCompile(`[enum|set]\((.+?)\)`).FindStringSubmatch(col.ColumnType) 686 | enums := strings.FieldsFunc(enumList[1], func(c rune) bool { 687 | cs := string(c) 688 | return "," == cs || "'" == cs 689 | }) 690 | 691 | enumName := inflect.Singularize(snaker.SnakeToCamel(col.TableName)) + snaker.SnakeToCamel(col.ColumnName) 692 | enum, err := newEnumFromStrings(enumName, col.ColumnComment, enums) 693 | if nil != err { 694 | return err 695 | } 696 | 697 | s.Enums = append(s.Enums, enum) 698 | 699 | fieldType = enumName 700 | case "blob", "mediumblob", "longblob", "varbinary", "binary": 701 | fieldType = "bytes" 702 | case "date", "time", "datetime", "timestamp": 703 | //s.AppendImport("google/protobuf/timestamp.proto") 704 | fieldType = "int64" 705 | case "bool", "bit": 706 | fieldType = "bool" 707 | case "tinyint", "smallint", "int", "mediumint", "bigint": 708 | fieldType = "int64" 709 | case "float", "decimal", "double": 710 | fieldType = "double" 711 | case "json": 712 | fieldType = "string" 713 | } 714 | 715 | if "" == fieldType { 716 | return fmt.Errorf("no compatible protobuf type found for `%s`. column: `%s`.`%s`", col.DataType, col.TableName, col.ColumnName) 717 | } 718 | 719 | field := NewMessageField(fieldType, col.ColumnName, len(msg.Fields)+1, col.ColumnComment) 720 | 721 | err := msg.AppendField(field) 722 | if nil != err { 723 | return err 724 | } 725 | 726 | return nil 727 | } 728 | 729 | func isInSlice(slice []string, s string) bool { 730 | for i, _ := range slice { 731 | if slice[i] == s { 732 | return true 733 | } 734 | } 735 | return false 736 | } 737 | --------------------------------------------------------------------------------