├── .gitignore ├── LICENSE ├── README.MD ├── cmd ├── dbml-gen-go-model │ └── main.go ├── lexer │ └── main.go └── parse-to-go │ └── main.go ├── core └── core.go ├── go.mod ├── go.sum ├── internal └── gen-go-model │ ├── gen │ ├── dbml.go │ ├── generate.go │ └── generator.go │ └── genutil │ ├── normalize.go │ ├── normalize_test.go │ ├── strcase.go │ └── strcase_test.go ├── parser ├── parser.go └── parser_test.go ├── scanner ├── check.go ├── scanner.go └── scanner_test.go ├── test.dbml └── token ├── token.go └── token_string.go /.gitignore: -------------------------------------------------------------------------------- 1 | model/* 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Thinh Tran 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 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # DBML parser for Go 2 | 3 | DBML-go is a Go parser for [DBML](https://www.dbml.org) syntax. 4 | 5 | ## Installation 6 | 7 | Go get 8 | 9 | ```bash 10 | go get github.com/duythinht/dbml-go/... 11 | ``` 12 | 13 | ## Quick start 14 | 15 | ```go 16 | package main 17 | 18 | import ( 19 | "fmt" 20 | "os" 21 | 22 | "github.com/duythinht/dbml-go/parser" 23 | "github.com/duythinht/dbml-go/scanner" 24 | ) 25 | 26 | func main() { 27 | f, _ := os.Open("test.dbml") 28 | s := scanner.NewScanner(f) 29 | parser := parser.NewParser(s) 30 | dbml, err := parser.Parse() 31 | if err != nil { 32 | fmt.Printf("%s\n", err) 33 | os.Exit(1) 34 | } 35 | 36 | // process dbml here 37 | } 38 | ``` 39 | 40 | ## Go model generate 41 | 42 | ```bash 43 | go get github.com/duythinht/dbml-go/cmd/dbml-go-gen-model 44 | ``` 45 | 46 | ``` 47 | Usage: 48 | dbml-gen-go-model [flags] 49 | 50 | Flags: 51 | -E, --exclude string regex for exclude "from" files. Only applied if "from" is a directory 52 | -t, --fieldtags stringArray go field tags (default [db,json,mapstructure]) 53 | -f, --from string source of dbml, can be https://dbdiagram.io/... | fire_name.dbml (default "database.dbml") 54 | --gen-table-name should generate "TableName" function 55 | -h, --help help for dbml-gen-go-model 56 | -o, --out string output folder (default "model") 57 | -p, --package string single for multiple files (default "model") 58 | --recursive recursive search directory. Only applied if "from" is a directory 59 | --remember-alias should remember table alias. Only applied if "from" is a directory 60 | ``` 61 | 62 | #### Example: 63 | 64 | input: 65 | 66 | ```dbml 67 | // database.dbml 68 | Table users as U { 69 | id int [pk, unique, increment] // auto-increment 70 | full_name varchar [not null, unique, default: 1] 71 | created_at timestamp 72 | country_code int 73 | Note: 'This is simple note' 74 | } 75 | ``` 76 | 77 | Run: 78 | 79 | ```bash 80 | mkdir -p model 81 | dbml-gen-go-model -f database.dbml -o model -p model 82 | ``` 83 | 84 | output: model/users.table.go 85 | 86 | ```go 87 | // Code generated by dbml-gen-go-model. DO NOT EDIT. 88 | // Supported by duythinht@2020 89 | package model 90 | 91 | // User is generated type for table 'users' 92 | type User struct { 93 | ID int `db:"id" json:"id" mapstructure:"id"` 94 | FullName string `db:"full_name" json:"full_name" mapstructure:"full_name"` 95 | CreatedAt int `db:"created_at" json:"created_at" mapstructure:"created_at"` 96 | CountryCode int `db:"country_code" json:"country_code" mapstructure:"country_code"` 97 | } 98 | 99 | // table 'users' columns list struct 100 | type __tbl_users_columns struct { 101 | ID string 102 | FullName string 103 | CreatedAt string 104 | CountryCode string 105 | } 106 | 107 | // table 'users' metadata struct 108 | type __tbl_users struct { 109 | Name string 110 | Columns __tbl_users_columns 111 | } 112 | 113 | // table 'users' metadata info 114 | var _tbl_users = __tbl_users{ 115 | Columns: __tbl_users_columns{ 116 | CountryCode: "country_code", 117 | CreatedAt: "created_at", 118 | FullName: "full_name", 119 | ID: "id", 120 | }, 121 | Name: "users", 122 | } 123 | 124 | // GetColumns return list columns name for table 'users' 125 | func (*__tbl_users) GetColumns() []string { 126 | return []string{"id", "full_name", "created_at", "country_code"} 127 | } 128 | 129 | // T return metadata info for table 'users' 130 | func (*User) T() *__tbl_users { 131 | return &_tbl_users 132 | } 133 | 134 | ``` 135 | -------------------------------------------------------------------------------- /cmd/dbml-gen-go-model/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/duythinht/dbml-go/internal/gen-go-model/gen" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func main() { 11 | 12 | var ( 13 | from = "database.dbml" 14 | out = "model" 15 | gopackage = "model" 16 | fieldtags = []string{"db", "json", "mapstructure"} 17 | shouldGenTblName = false 18 | rememberAlias = false 19 | recursive = false 20 | exclude = "" 21 | ) 22 | 23 | cmd := &cobra.Command{ 24 | Use: "dbml-gen-go-model", 25 | RunE: func(cmd *cobra.Command, args []string) error { 26 | gen.Generate(gen.Opts{ 27 | From: from, 28 | Out: out, 29 | Package: gopackage, 30 | FieldTags: fieldtags, 31 | ShouldGenTblName: shouldGenTblName, 32 | RememberAlias: rememberAlias, 33 | Recursive: recursive, 34 | Exclude: exclude, 35 | }) 36 | return nil 37 | }, 38 | PreRunE: func(cmd *cobra.Command, args []string) error { 39 | return nil 40 | }, 41 | } 42 | flags := cmd.Flags() 43 | flags.StringVarP(&from, "from", "f", from, "source of dbml, can be https://dbdiagram.io/... | fire_name.dbml") 44 | flags.StringVarP(&out, "out", "o", out, "output folder") 45 | flags.StringVarP(&gopackage, "package", "p", gopackage, "single for multiple files") 46 | flags.StringArrayVarP(&fieldtags, "fieldtags", "t", fieldtags, "go field tags") 47 | flags.BoolVarP(&shouldGenTblName, "gen-table-name", "", shouldGenTblName, "should generate \"TableName\" function") 48 | flags.BoolVarP(&rememberAlias, "remember-alias", "", rememberAlias, "should remember table alias. Only applied if \"from\" is a directory") 49 | flags.BoolVarP(&recursive, "recursive", "", recursive, "recursive search directory. Only applied if \"from\" is a directory") 50 | flags.StringVarP(&exclude, "exclude", "E", exclude, "regex for exclude \"from\" files. Only applied if \"from\" is a directory") 51 | if err := cmd.Execute(); err != nil { 52 | log.Fatalf("Error %s", err) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /cmd/lexer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "os" 7 | 8 | "github.com/duythinht/dbml-go/token" 9 | 10 | "github.com/duythinht/dbml-go/scanner" 11 | ) 12 | 13 | func main() { 14 | f2, _ := os.Open("test.dbml") 15 | s := scanner.NewScanner(f2) 16 | for { 17 | l, c := s.LineInfo() 18 | tok, lit := s.Read() 19 | if tok == token.EOF { 20 | break 21 | } 22 | fmt.Printf("[%d:%d]\tToken: %s\tlit: %s\n", l, c, tok, lit) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /cmd/parse-to-go/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/duythinht/dbml-go/parser" 8 | "github.com/duythinht/dbml-go/scanner" 9 | ) 10 | 11 | func main() { 12 | f, _ := os.Open("test.dbml") 13 | s := scanner.NewScanner(f) 14 | parser := parser.NewParser(s) 15 | dbml, err := parser.Parse() 16 | fmt.Printf("%#v, %v", dbml, err) 17 | } 18 | -------------------------------------------------------------------------------- /core/core.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import "github.com/duythinht/dbml-go/token" 4 | 5 | // DBML structure 6 | type DBML struct { 7 | Project Project 8 | Tables []Table 9 | Enums []Enum 10 | Refs []Ref 11 | TableGroups []TableGroup 12 | } 13 | 14 | // Project ... 15 | type Project struct { 16 | Name string 17 | Note string 18 | DatabaseType string 19 | } 20 | 21 | // Table ... 22 | type Table struct { 23 | Name string 24 | As string 25 | Note string 26 | Columns []Column 27 | Indexes []Index 28 | } 29 | 30 | // Column ... 31 | type Column struct { 32 | Name string 33 | Type string 34 | Settings ColumnSetting 35 | } 36 | 37 | // ColumnSetting ... 38 | type ColumnSetting struct { 39 | Note string 40 | PK bool 41 | Unique bool 42 | Default string 43 | Null bool 44 | Increment bool 45 | Ref struct { 46 | Type RelationshipType 47 | To string 48 | } 49 | } 50 | 51 | // Index ... 52 | type Index struct { 53 | Fields []string 54 | Settings IndexSetting 55 | } 56 | 57 | // IndexSetting ... 58 | type IndexSetting struct { 59 | Type string 60 | Name string 61 | Unique bool 62 | PK bool 63 | Note string 64 | } 65 | 66 | // RelationshipType ... 67 | type RelationshipType int 68 | 69 | const ( 70 | //None relationship 71 | None = iota 72 | //OneToOne 1 - 1 73 | OneToOne 74 | //OneToMany 1 - n 75 | OneToMany 76 | // ManyToOne n - 1 77 | ManyToOne 78 | ) 79 | 80 | // Relationship ... 81 | type Relationship struct { 82 | From string 83 | To string 84 | Type RelationshipType 85 | } 86 | 87 | //RelationshipMap ... 88 | var RelationshipMap = map[token.Token]RelationshipType{ 89 | token.GTR: ManyToOne, 90 | token.LSS: OneToMany, 91 | token.SUB: OneToOne, 92 | } 93 | 94 | // Ref ... 95 | type Ref struct { 96 | // TODO: 97 | // - handle Ref 98 | Name string // optional 99 | Relationships []Relationship 100 | } 101 | 102 | // Enum ... 103 | type Enum struct { 104 | Name string 105 | Values []EnumValue 106 | } 107 | 108 | // EnumValue ... 109 | type EnumValue struct { 110 | Name string 111 | Note string 112 | } 113 | 114 | // TableGroup ... 115 | type TableGroup struct { 116 | // TODO: 117 | // -- handle for table group 118 | Name string 119 | Members []string 120 | } 121 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/duythinht/dbml-go 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/dave/jennifer v1.4.0 7 | github.com/spf13/cobra v1.0.0 8 | github.com/spf13/pflag v1.0.5 // indirect 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 4 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 5 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 6 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 7 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 8 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 9 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 10 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 11 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 12 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 13 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 14 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 15 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 16 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 17 | github.com/dave/jennifer v1.4.0 h1:tNJFJmLDVTLu+v05mVZ88RINa3vQqnyyWkTKWYz0CwE= 18 | github.com/dave/jennifer v1.4.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= 19 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 20 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 21 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 22 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 23 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 24 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 25 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 26 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 27 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 28 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 29 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 30 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 31 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 32 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 33 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 34 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 35 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 36 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 37 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 38 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 39 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 40 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 41 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 42 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 43 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 44 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 45 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 46 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 47 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 48 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 49 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 50 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 51 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 52 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 53 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 54 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 55 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 56 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 57 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 58 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 59 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 60 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 61 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 62 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 63 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 64 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 65 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 66 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 67 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 68 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 69 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 70 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 71 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 72 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 73 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 74 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 75 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 76 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 77 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 78 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 79 | github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= 80 | github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= 81 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 82 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 83 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 84 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 85 | github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= 86 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 87 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 88 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 89 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= 90 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 91 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 92 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 93 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 94 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 95 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 96 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 97 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 98 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 99 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 100 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 101 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 102 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 103 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 104 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 105 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 106 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 107 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 108 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 109 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 110 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 111 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 112 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 113 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 114 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 115 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 116 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 117 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 118 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 119 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 120 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 121 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 122 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 123 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 124 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 125 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 126 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 127 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 128 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 129 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 130 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 131 | -------------------------------------------------------------------------------- /internal/gen-go-model/gen/dbml.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "net/http" 9 | "os" 10 | "path/filepath" 11 | "regexp" 12 | "strings" 13 | 14 | "github.com/duythinht/dbml-go/core" 15 | "github.com/duythinht/dbml-go/parser" 16 | "github.com/duythinht/dbml-go/scanner" 17 | ) 18 | 19 | const ( 20 | dbdiagramURLPattern = `https://dbdiagram.io/d/(\w+)` 21 | dbdiagramAPIURLPattern = `https://api.dbdiagram.io/query/%s` 22 | ) 23 | 24 | var re = regexp.MustCompile(dbdiagramURLPattern) 25 | 26 | func parseDBML(from string, recursive bool, exclude *regexp.Regexp) (ret []*core.DBML) { 27 | files := collectFiles(from, recursive, exclude) 28 | for _, f := range files { 29 | r, err := dbmlReader(f) 30 | if err != nil { 31 | fmt.Printf("Error read file %s: %s", f, err) 32 | continue 33 | } 34 | 35 | p := parser.NewParser(scanner.NewScanner(r)) 36 | dbml, err := p.Parse() 37 | if err != nil { 38 | fmt.Printf("Error parse file %s: %s", f, err) 39 | continue 40 | } 41 | ret = append(ret, dbml) 42 | } 43 | return 44 | } 45 | 46 | func collectFiles(from string, recursive bool, exclude *regexp.Regexp) []string { 47 | stat, err := os.Stat(from) 48 | if err != nil { 49 | fmt.Printf("Invalid from parameter %s", err) 50 | return []string{} 51 | } 52 | 53 | // single file 54 | if !stat.IsDir() { 55 | return []string{from} 56 | } 57 | 58 | // directory 59 | files := []string{} 60 | filepath.Walk(from, func(path string, info os.FileInfo, err error) error { 61 | if path != from && info.IsDir() && !recursive { 62 | return filepath.SkipDir 63 | } 64 | if exclude != nil && exclude.MatchString(path) { 65 | fmt.Println("Match exclude pattern: ", path) 66 | return nil 67 | } 68 | files = append(files, path) 69 | return nil 70 | }) 71 | return files 72 | } 73 | 74 | func dbmlReader(from string) (io.Reader, error) { 75 | m := re.FindStringSubmatch(from) 76 | 77 | if len(m) < 1 { 78 | return os.Open(from) 79 | } 80 | 81 | dbdURL := fmt.Sprintf(dbdiagramAPIURLPattern, m[1]) 82 | resp, err := http.Get(dbdURL) 83 | if err != nil { 84 | return nil, err 85 | } 86 | defer resp.Body.Close() 87 | if resp.StatusCode != 200 { 88 | return nil, fmt.Errorf("%s NOT FOUND", dbdURL) 89 | } 90 | bodyJSON, err := ioutil.ReadAll(resp.Body) 91 | if err != nil { 92 | return nil, err 93 | } 94 | body := new(struct { 95 | ID string `json:"_id"` 96 | Content string `json:"content"` 97 | }) 98 | err = json.Unmarshal(bodyJSON, body) 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | return strings.NewReader(body.Content), nil 104 | } 105 | -------------------------------------------------------------------------------- /internal/gen-go-model/gen/generate.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strings" 7 | ) 8 | 9 | type Opts struct { 10 | From string 11 | Out string 12 | Package string 13 | FieldTags []string 14 | ShouldGenTblName bool 15 | RememberAlias bool 16 | Recursive bool 17 | Exclude string 18 | } 19 | 20 | // Generate go model 21 | func Generate(opts Opts) { 22 | var pattern *regexp.Regexp 23 | if strings.TrimSpace(opts.Exclude) != "" { 24 | pattern, _ = regexp.Compile(opts.Exclude) 25 | } 26 | 27 | dbmls := parseDBML(opts.From, opts.Recursive, pattern) 28 | 29 | g := newgen() 30 | g.out = opts.Out 31 | g.gopackage = opts.Package 32 | g.fieldtags = opts.FieldTags 33 | g.shouldGenTblName = opts.ShouldGenTblName 34 | 35 | for _, dbml := range dbmls { 36 | g.reset(opts.RememberAlias) 37 | g.dbml = dbml 38 | if err := g.generate(); err != nil { 39 | fmt.Printf("Error generate file %s", err) 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /internal/gen-go-model/gen/generator.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strings" 7 | 8 | "github.com/dave/jennifer/jen" 9 | "github.com/duythinht/dbml-go/core" 10 | "github.com/duythinht/dbml-go/internal/gen-go-model/genutil" 11 | ) 12 | 13 | type generator struct { 14 | dbml *core.DBML 15 | out string 16 | gopackage string 17 | fieldtags []string 18 | types map[string]jen.Code 19 | shouldGenTblName bool 20 | } 21 | 22 | func newgen() *generator { 23 | return &generator{ 24 | types: make(map[string]jen.Code), 25 | } 26 | } 27 | 28 | func (g *generator) reset(rememberAlias bool) { 29 | g.dbml = nil 30 | if !rememberAlias { 31 | g.types = make(map[string]jen.Code) 32 | } 33 | } 34 | 35 | func (g *generator) file() *jen.File { 36 | return jen.NewFilePathName(g.out, g.gopackage) 37 | } 38 | 39 | func (g *generator) generate() error { 40 | if err := g.genEnums(); err != nil { 41 | return err 42 | } 43 | return nil 44 | } 45 | 46 | func (g *generator) genEnums() error { 47 | for _, enum := range g.dbml.Enums { 48 | if err := g.genEnum(enum); err != nil { 49 | return err 50 | } 51 | } 52 | for _, table := range g.dbml.Tables { 53 | if err := g.genTable(table); err != nil { 54 | return err 55 | } 56 | } 57 | 58 | return nil 59 | } 60 | 61 | func (g *generator) genEnum(enum core.Enum) error { 62 | f := jen.NewFilePathName(g.out, g.gopackage) 63 | 64 | enumOriginName := genutil.NormalizeTypeName(enum.Name) 65 | enumGoTypeName := genutil.NormalizeGoTypeName(enum.Name) 66 | 67 | f.PackageComment("Code generated by dbml-gen-go-model. DO NOT EDIT.") 68 | f.PackageComment("Supported by duythinht@2020") 69 | f.Commentf("%s is generated type for enum '%s'", enumGoTypeName, enumOriginName) 70 | f.Type().Id(enumGoTypeName).Int() 71 | 72 | f.Const().DefsFunc(func(group *jen.Group) { 73 | group.Id("_").Id(enumGoTypeName).Op("=").Iota() 74 | for _, value := range enum.Values { 75 | v := group.Id(genutil.NormalLizeGoName(value.Name)) 76 | if value.Note != "" { 77 | v.Comment(value.Note) 78 | } 79 | } 80 | }) 81 | 82 | g.types[enum.Name] = jen.Id(enumGoTypeName) 83 | 84 | return f.Save(fmt.Sprintf("%s/%s.enum.go", g.out, genutil.Normalize(enum.Name))) 85 | } 86 | 87 | func (g *generator) genTable(table core.Table) error { 88 | f := jen.NewFilePathName(g.out, g.gopackage) 89 | 90 | tableOriginName := genutil.Normalize(table.Name) 91 | tableGoTypeName := genutil.NormalizeGoTypeName(table.Name) 92 | 93 | f.PackageComment("Code generated by dbml-gen-go-model. DO NOT EDIT.") 94 | f.PackageComment("Supported by duythinht@2020") 95 | f.Commentf("%s is generated type for table '%s'", tableGoTypeName, tableOriginName) 96 | 97 | var genColumnErr error 98 | 99 | cols := make([]string, 0) 100 | 101 | f.Type().Id(tableGoTypeName).StructFunc(func(group *jen.Group) { 102 | for _, column := range table.Columns { 103 | columnName := genutil.NormalLizeGoName(column.Name) 104 | columnOriginName := genutil.Normalize(column.Name) 105 | t, ok := g.getJenType(column.Type) 106 | if !ok { 107 | genColumnErr = fmt.Errorf("type '%s' is not support", column.Type) 108 | } 109 | if column.Settings.Note != "" { 110 | group.Comment(column.Settings.Note) 111 | } 112 | 113 | gotags := make(map[string]string) 114 | for _, t := range g.fieldtags { 115 | gotags[strings.TrimSpace(t)] = columnOriginName 116 | } 117 | group.Id(columnName).Add(t).Tag(gotags) 118 | cols = append(cols, columnOriginName) 119 | } 120 | }) 121 | 122 | if genColumnErr != nil { 123 | return genColumnErr 124 | } 125 | 126 | tableMetadataType := "__tbl_" + tableOriginName 127 | tableMetadataColumnsType := tableMetadataType + "_columns" 128 | 129 | f.Commentf("// table '%s' columns list struct", tableOriginName) 130 | f.Type().Id(tableMetadataColumnsType).StructFunc(func(group *jen.Group) { 131 | for _, column := range table.Columns { 132 | group.Id(genutil.NormalLizeGoName(column.Name)).String() 133 | } 134 | }) 135 | 136 | f.Commentf("// table '%s' metadata struct", tableOriginName) 137 | f.Type().Id("__tbl_"+tableOriginName).Struct( 138 | jen.Id("Name").String(), 139 | jen.Id("Columns").Id(tableMetadataColumnsType), 140 | ) 141 | 142 | tableMetadataVar := "_tbl_" + tableOriginName 143 | 144 | f.Commentf("// table '%s' metadata info", tableOriginName) 145 | f.Var().Id(tableMetadataVar).Op("=").Id(tableMetadataType).Values(jen.DictFunc(func(d jen.Dict) { 146 | d[jen.Id("Name")] = jen.Lit(tableOriginName) 147 | d[jen.Id("Columns")] = jen.Id(tableMetadataColumnsType).Values(jen.DictFunc(func(d jen.Dict) { 148 | for _, column := range table.Columns { 149 | columnName := genutil.NormalLizeGoName(column.Name) 150 | columnOriginName := genutil.Normalize(column.Name) 151 | d[jen.Id(columnName)] = jen.Lit(columnOriginName) 152 | } 153 | })) 154 | })) 155 | 156 | f.Commentf("GetColumns return list columns name for table '%s'", tableOriginName) 157 | f.Func().Params( 158 | jen.Op("*").Id(tableMetadataType), 159 | ).Id("GetColumns").Params().Index().String().Block( 160 | jen.Return(jen.Index().String().ValuesFunc(func(g *jen.Group) { 161 | for _, col := range cols { 162 | g.Lit(col) 163 | } 164 | })), 165 | ) 166 | 167 | f.Commentf("T return metadata info for table '%s'", tableOriginName) 168 | f.Func().Params( 169 | jen.Op("*").Id(tableGoTypeName), 170 | ).Id("T").Params().Op("*").Id(tableMetadataType).Block( 171 | jen.Return().Op("&").Id(tableMetadataVar), 172 | ) 173 | 174 | if g.shouldGenTblName { 175 | f.Commentf("TableName return table name") 176 | f.Func().Params( 177 | jen.Id(tableGoTypeName), 178 | ).Id("TableName").Params().Id("string").Block( 179 | jen.Return(jen.Lit(tableOriginName)), 180 | ) 181 | } 182 | 183 | return f.Save(fmt.Sprintf("%s/%s.table.go", g.out, genutil.Normalize(table.Name))) 184 | } 185 | 186 | const primeTypePattern = `^(\w+)(\(d+\))?` 187 | 188 | var ( 189 | regexType = regexp.MustCompile(primeTypePattern) 190 | builtinTypes = map[string]jen.Code{ 191 | "int": jen.Int(), 192 | "int8": jen.Int8(), 193 | "int16": jen.Int16(), 194 | "int32": jen.Int32(), 195 | "int64": jen.Int64(), 196 | "bigint": jen.Int64(), 197 | "uint": jen.Uint(), 198 | "uint8": jen.Uint8(), 199 | "uint16": jen.Uint16(), 200 | "uint32": jen.Uint32(), 201 | "uint64": jen.Uint64(), 202 | "float": jen.Float64(), 203 | "float32": jen.Float32(), 204 | "float64": jen.Float64(), 205 | "bool": jen.Bool(), 206 | "text": jen.String(), 207 | "varchar": jen.String(), 208 | "char": jen.String(), 209 | "byte": jen.Byte(), 210 | "rune": jen.Rune(), 211 | "timestamp": jen.Int(), 212 | "datetime": jen.Qual("time", "Time"), 213 | } 214 | ) 215 | 216 | func (g *generator) getJenType(s string) (jen.Code, bool) { 217 | m := regexType.FindStringSubmatch(s) 218 | if len(m) >= 2 { 219 | // lookup for builtin type 220 | if t, ok := builtinTypes[m[1]]; ok { 221 | return t, ok 222 | } 223 | } 224 | t, ok := g.types[s] 225 | return t, ok 226 | } 227 | -------------------------------------------------------------------------------- /internal/gen-go-model/genutil/normalize.go: -------------------------------------------------------------------------------- 1 | package genutil 2 | 3 | import "strings" 4 | 5 | // NormalizeTypeName return name of model type, which normalize 6 | // eg: 7 | // users => User 8 | // categories => category 9 | func NormalizeTypeName(s string) string { 10 | s1 := strings.ReplaceAll(s, " ", "_") 11 | if strings.HasSuffix(s1, "us") { 12 | return s1 13 | } 14 | 15 | if strings.HasSuffix(s1, "ies") { 16 | return s1[:len(s1)-3] + "y" 17 | } 18 | 19 | for _, suffix := range []string{"oes", "ses", "zes", "xes", "shes", "ches"} { 20 | if strings.HasSuffix(s1, suffix) { 21 | return s1[:len(s1)-2] 22 | } 23 | } 24 | if strings.HasSuffix(s1, "s") { 25 | return s1[:len(s1)-1] 26 | } 27 | 28 | return s1 29 | } 30 | 31 | // Normalize return name in underscore 32 | func Normalize(s string) string { 33 | return strings.ReplaceAll(s, " ", "_") 34 | } 35 | 36 | // NormalLizeGoName return normalize GoName 37 | func NormalLizeGoName(s string) string { 38 | return GoInitialismCamelCase(Normalize(s)) 39 | } 40 | 41 | //NormalizeGoTypeName return Normalize for Go Type Name 42 | func NormalizeGoTypeName(s string) string { 43 | return GoInitialismCamelCase(NormalizeTypeName(s)) 44 | } 45 | -------------------------------------------------------------------------------- /internal/gen-go-model/genutil/normalize_test.go: -------------------------------------------------------------------------------- 1 | package genutil 2 | 3 | import "testing" 4 | 5 | func TestNormalizeType(t *testing.T) { 6 | for s, s1 := range map[string]string{ 7 | "heroes": "hero", 8 | "countries": "country", 9 | "watches": "watch", 10 | "the_heroes": "the_hero", 11 | } { 12 | if out := NormalizeTypeName(s); out != s1 { 13 | t.Fatalf("in: %s, out: %s, expected: %s", s, out, s1) 14 | } 15 | } 16 | } 17 | 18 | func TestNormalizeGoType(t *testing.T) { 19 | for s, s1 := range map[string]string{ 20 | "heroes": "Hero", 21 | "countries": "Country", 22 | "watches": "Watch", 23 | "the_heroes": "TheHero", 24 | } { 25 | if out := NormalizeGoTypeName(s); out != s1 { 26 | t.Fatalf("in: %s, out: %s, expected: %s", s, out, s1) 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /internal/gen-go-model/genutil/strcase.go: -------------------------------------------------------------------------------- 1 | // Package genutil was reference from https://github.com/protocolbuffers/protobuf-go/internal/strs/strings.go 2 | package genutil 3 | 4 | import ( 5 | "bytes" 6 | "strings" 7 | "unicode" 8 | ) 9 | 10 | var commonInitialisms = map[string]bool{ 11 | "ACL": true, 12 | "API": true, 13 | "ASCII": true, 14 | "CPU": true, 15 | "CSS": true, 16 | "DNS": true, 17 | "EOF": true, 18 | "GUID": true, 19 | "HTML": true, 20 | "HTTP": true, 21 | "HTTPS": true, 22 | "ID": true, 23 | "IP": true, 24 | "JSON": true, 25 | "LHS": true, 26 | "QPS": true, 27 | "RAM": true, 28 | "RHS": true, 29 | "RPC": true, 30 | "SLA": true, 31 | "SMTP": true, 32 | "SQL": true, 33 | "SSH": true, 34 | "TCP": true, 35 | "TLS": true, 36 | "TTL": true, 37 | "UDP": true, 38 | "UI": true, 39 | "UID": true, 40 | "UUID": true, 41 | "URI": true, 42 | "URL": true, 43 | "UTF8": true, 44 | "VM": true, 45 | "XML": true, 46 | "XMPP": true, 47 | "XSRF": true, 48 | "XSS": true, 49 | } 50 | 51 | // GoInitialismCamelCase return initialism 52 | func GoInitialismCamelCase(s string) string { 53 | s1 := Initialism(goCamelCase(s)) 54 | return s1 55 | } 56 | 57 | // Initialism return initialsm string 58 | func Initialism(s string) string { 59 | if s == "_" { 60 | return s 61 | } 62 | var buf bytes.Buffer 63 | runes := []rune(s) 64 | 65 | w, i := 0, 0 //start scan 66 | for i+1 < len(runes) { 67 | // if the word start 68 | i++ 69 | if runes[i] == '_' { 70 | w = i + 1 71 | buf.WriteRune('_') 72 | } 73 | if unicode.IsLower(runes[i]) && (i == len(runes)-1 || runes[i+1] == '_' || unicode.IsUpper(runes[i+1])) { 74 | word := string(runes[w : i+1]) 75 | if u := strings.ToUpper(word); commonInitialisms[u] && unicode.IsUpper(runes[w]) { 76 | buf.WriteString(u) 77 | } else { 78 | buf.WriteString(word) 79 | } 80 | w = i + 1 81 | } 82 | } 83 | return buf.String() 84 | } 85 | 86 | func goCamelCase(s string) string { 87 | // Invariant: if the next letter is lower case, it must be converted 88 | // to upper case. 89 | // That is, we process a word at a time, where words are marked by _ or 90 | // upper case letter. Digits are treated as words. 91 | var b []byte 92 | for i := 0; i < len(s); i++ { 93 | c := s[i] 94 | switch { 95 | case c == '.' && i+1 < len(s) && isASCIILower(s[i+1]): 96 | // Skip over '.' in ".{{lowercase}}". 97 | case c == '.': 98 | b = append(b, '_') // convert '.' to '_' 99 | case c == '_' && (i == 0 || s[i-1] == '.'): 100 | // Convert initial '_' to ensure we start with a capital letter. 101 | // Do the same for '_' after '.' to match historic behavior. 102 | b = append(b, 'X') // convert '_' to 'X' 103 | case c == '_' && i+1 < len(s) && isASCIILower(s[i+1]): 104 | // Skip over '_' in "_{{lowercase}}". 105 | case isASCIIDigit(c): 106 | b = append(b, c) 107 | default: 108 | // Assume we have a letter now - if not, it's a bogus identifier. 109 | // The next word is a sequence of characters that must start upper case. 110 | if isASCIILower(c) { 111 | c -= 'a' - 'A' // convert lowercase to uppercase 112 | } 113 | b = append(b, c) 114 | 115 | // Accept lower case sequence that follows. 116 | for ; i+1 < len(s) && isASCIILower(s[i+1]); i++ { 117 | b = append(b, s[i+1]) 118 | } 119 | } 120 | } 121 | return string(b) 122 | } 123 | 124 | // JSONCamelCase converts a snake_case identifier to a camelCase identifier, 125 | // according to the protobuf JSON specification. 126 | func JSONCamelCase(s string) string { 127 | var b []byte 128 | var wasUnderscore bool 129 | for i := 0; i < len(s); i++ { // proto identifiers are always ASCII 130 | c := s[i] 131 | if c != '_' { 132 | if wasUnderscore && isASCIILower(c) { 133 | c -= 'a' - 'A' // convert to uppercase 134 | } 135 | b = append(b, c) 136 | } 137 | wasUnderscore = c == '_' 138 | } 139 | return string(b) 140 | } 141 | 142 | // JSONSnakeCase converts a camelCase identifier to a snake_case identifier, 143 | // according to the protobuf JSON specification. 144 | func JSONSnakeCase(s string) string { 145 | var b []byte 146 | for i := 0; i < len(s); i++ { // proto identifiers are always ASCII 147 | c := s[i] 148 | if isASCIIUpper(c) { 149 | b = append(b, '_') 150 | c += 'a' - 'A' // convert to lowercase 151 | } 152 | b = append(b, c) 153 | } 154 | return string(b) 155 | } 156 | 157 | func isASCIILower(c byte) bool { 158 | return 'a' <= c && c <= 'z' 159 | } 160 | func isASCIIUpper(c byte) bool { 161 | return 'A' <= c && c <= 'Z' 162 | } 163 | func isASCIIDigit(c byte) bool { 164 | return '0' <= c && c <= '9' 165 | } 166 | -------------------------------------------------------------------------------- /internal/gen-go-model/genutil/strcase_test.go: -------------------------------------------------------------------------------- 1 | package genutil 2 | 3 | import "testing" 4 | 5 | func TestIntialismCase(t *testing.T) { 6 | for in, expect := range map[string]string{ 7 | "get__url": "get__url", 8 | "GetUrlOk": "GetURLOk", 9 | "chaosUrl": "chaosURL", 10 | "Id": "ID", 11 | "serve_http": "serve_http", 12 | "url": "url", 13 | } { 14 | if out := Initialism(in); out != expect { 15 | t.Fatalf("in: %s, out: %s, expect %s\n", in, out, expect) 16 | } 17 | } 18 | } 19 | 20 | func TestGetInitialismCamelCase(t *testing.T) { 21 | for in, expect := range map[string]string{ 22 | "get__url": "Get_URL", 23 | "GetUrlOk": "GetURLOk", 24 | "chaosUrl": "ChaosURL", 25 | "id": "ID", 26 | "serve_http": "ServeHTTP", 27 | } { 28 | if out := GoInitialismCamelCase(in); out != expect { 29 | t.Fatalf("in: %s, out: %s, expect %s\n", in, out, expect) 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /parser/parser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "regexp" 7 | "strings" 8 | 9 | "github.com/duythinht/dbml-go/core" 10 | "github.com/duythinht/dbml-go/scanner" 11 | "github.com/duythinht/dbml-go/token" 12 | ) 13 | 14 | // Parser declaration 15 | type Parser struct { 16 | s *scanner.Scanner 17 | 18 | // current token & literal 19 | token token.Token 20 | lit string 21 | 22 | Debug bool 23 | } 24 | 25 | // NewParser ... 26 | func NewParser(s *scanner.Scanner) *Parser { 27 | return &Parser{ 28 | s: s, 29 | token: token.ILLEGAL, 30 | lit: "", 31 | Debug: os.Getenv("DBML_PARSER_DEBUG") == "true", 32 | } 33 | } 34 | 35 | // Parse ... 36 | func (p *Parser) Parse() (*core.DBML, error) { 37 | dbml := &core.DBML{} 38 | for { 39 | p.next() 40 | switch p.token { 41 | case token.PROJECT: 42 | project, err := p.parseProject() 43 | if err != nil { 44 | return nil, err 45 | } 46 | p.debug("project", project) 47 | dbml.Project = *project 48 | case token.TABLE: 49 | table, err := p.parseTable() 50 | if err != nil { 51 | return nil, err 52 | } 53 | p.debug("table", table) 54 | 55 | // TODO: 56 | // * register table to tables map, for check ref 57 | dbml.Tables = append(dbml.Tables, *table) 58 | 59 | case token.REF: 60 | ref, err := p.parseRefs() 61 | if err != nil { 62 | return nil, err 63 | } 64 | p.debug("Refs", ref) 65 | 66 | // TODO: 67 | // * Check refs is valid or not (by tables map) 68 | dbml.Refs = append(dbml.Refs, *ref) 69 | 70 | case token.ENUM: 71 | enum, err := p.parseEnum() 72 | if err != nil { 73 | return nil, err 74 | } 75 | p.debug("Enum", enum) 76 | dbml.Enums = append(dbml.Enums, *enum) 77 | 78 | case token.TABLEGROUP: 79 | tableGroup, err := p.parseTableGroup() 80 | if err != nil { 81 | return nil, err 82 | } 83 | p.debug("TableGroup", tableGroup) 84 | dbml.TableGroups = append(dbml.TableGroups, *tableGroup) 85 | case token.EOF: 86 | return dbml, nil 87 | default: 88 | p.debug("token", p.token.String(), "lit", p.lit) 89 | return nil, p.expect("Project, Ref, Table, Enum, TableGroup") 90 | } 91 | } 92 | } 93 | 94 | func (p *Parser) parseTableGroup() (*core.TableGroup, error) { 95 | tableGroup := &core.TableGroup{} 96 | p.next() 97 | if p.token != token.IDENT && p.token != token.DSTRING { 98 | return nil, fmt.Errorf("TableGroup name is invalid: %s", p.lit) 99 | } 100 | tableGroup.Name = p.lit 101 | p.next() 102 | if p.token != token.LBRACE { 103 | return nil, p.expect("{") 104 | } 105 | p.next() 106 | 107 | for p.token == token.IDENT || p.token == token.DSTRING { 108 | tableGroup.Members = append(tableGroup.Members, p.lit) 109 | p.next() 110 | } 111 | if p.token != token.RBRACE { 112 | return nil, p.expect("}") 113 | } 114 | return tableGroup, nil 115 | } 116 | 117 | func (p *Parser) parseEnum() (*core.Enum, error) { 118 | enum := &core.Enum{} 119 | p.next() 120 | if !token.IsIdent(p.token) && p.token != token.DSTRING { 121 | return nil, fmt.Errorf("enum name is invalid: %s", p.lit) 122 | } 123 | enum.Name = p.lit 124 | p.next() 125 | if p.token != token.LBRACE { 126 | return nil, p.expect("{") 127 | } 128 | p.next() 129 | 130 | for token.IsIdent(p.token) { 131 | enumValue := core.EnumValue{ 132 | Name: p.lit, 133 | } 134 | p.next() 135 | if p.token == token.LBRACK { 136 | // handle [Note: ...] 137 | p.next() 138 | if p.token == token.NOTE { 139 | note, err := p.parseDescription() 140 | if err != nil { 141 | return nil, p.expect("note: 'string'") 142 | } 143 | enumValue.Note = note 144 | p.next() 145 | } 146 | if p.token != token.RBRACK { 147 | return nil, p.expect("]") 148 | } 149 | p.next() 150 | } 151 | enum.Values = append(enum.Values, enumValue) 152 | } 153 | if p.token != token.RBRACE { 154 | return nil, p.expect("}") 155 | } 156 | return enum, nil 157 | } 158 | 159 | func (p *Parser) parseRefs() (*core.Ref, error) { 160 | ref := &core.Ref{} 161 | p.next() 162 | 163 | // Handle for Ref ... 164 | if p.token == token.IDENT { 165 | ref.Name = p.lit 166 | p.next() 167 | } 168 | 169 | // Ref: from > to 170 | if p.token == token.COLON { 171 | p.next() 172 | rel, err := p.parseRelationship() 173 | if err != nil { 174 | return nil, err 175 | } 176 | ref.Relationships = append(ref.Relationships, *rel) 177 | return ref, nil 178 | } 179 | 180 | if p.token == token.LBRACE { 181 | p.next() 182 | 183 | for { 184 | if p.token == token.RBRACE { 185 | return ref, nil 186 | } else if p.token == token.IDENT || p.token == token.DSTRING { 187 | rel, err := p.parseRelationship() 188 | if err != nil { 189 | return nil, err 190 | } 191 | ref.Relationships = append(ref.Relationships, *rel) 192 | } else { 193 | return nil, p.expect("Ref: { from > to }") 194 | } 195 | p.next() 196 | } 197 | } 198 | 199 | return nil, p.expect("Ref: | Refs {}") 200 | } 201 | 202 | func (p *Parser) parseRelationship() (*core.Relationship, error) { 203 | rel := &core.Relationship{} 204 | if p.token != token.IDENT && p.token != token.DSTRING { 205 | return nil, p.expect("(rel from) table.column_name") 206 | } 207 | 208 | rel.From = p.lit 209 | 210 | p.next() 211 | if reltype, ok := core.RelationshipMap[p.token]; ok { 212 | rel.Type = reltype 213 | } else { 214 | return nil, p.expect("> | < | -") 215 | } 216 | 217 | p.next() 218 | if p.token != token.IDENT { 219 | return nil, p.expect("(rel to) table.column_name") 220 | } 221 | rel.To = p.lit 222 | return rel, nil 223 | } 224 | 225 | func (p *Parser) parseTable() (*core.Table, error) { 226 | table := &core.Table{} 227 | p.next() 228 | switch p.token { 229 | case token.IDENT, token.DSTRING: 230 | //pass 231 | default: 232 | if m, _ := regexp.MatchString("^[a-zA-Z1-9]+$", p.lit); !m { 233 | return nil, fmt.Errorf("table name is invalid: %s", p.lit) 234 | } 235 | } 236 | table.Name = p.lit 237 | 238 | p.next() 239 | 240 | switch p.token { 241 | case token.AS: 242 | // handle as 243 | p.next() 244 | switch p.token { 245 | case token.STRING, token.IDENT: 246 | table.As = p.lit 247 | default: 248 | return nil, p.expect("as NAME") 249 | } 250 | p.next() 251 | fallthrough 252 | case token.LBRACE: 253 | p.next() 254 | for { 255 | switch p.token { 256 | case token.INDEXES: 257 | indexes, err := p.parseIndexes() 258 | if err != nil { 259 | return nil, err 260 | } 261 | table.Indexes = indexes 262 | case token.RBRACE: 263 | return table, nil 264 | default: 265 | columnName := p.lit 266 | currentToken := p.token 267 | p.next() 268 | if currentToken == token.NOTE && p.token == token.COLON { 269 | note, err := p.parseString() 270 | if err != nil { 271 | return nil, err 272 | } 273 | table.Note = note 274 | p.next() 275 | } else { 276 | column, err := p.parseColumn(columnName) 277 | if err != nil { 278 | return nil, err 279 | } 280 | table.Columns = append(table.Columns, *column) 281 | } 282 | } 283 | } 284 | default: 285 | return nil, p.expect("{") 286 | } 287 | } 288 | 289 | func (p *Parser) parseIndexes() ([]core.Index, error) { 290 | indexes := []core.Index{} 291 | 292 | p.next() 293 | if p.token != token.LBRACE { 294 | return nil, p.expect("{") 295 | } 296 | 297 | p.next() 298 | for { 299 | if p.token == token.RBRACE { 300 | p.next() // pop } 301 | return indexes, nil 302 | } 303 | // parse an Index 304 | index, err := p.parseIndex() 305 | if err != nil { 306 | return nil, err 307 | } 308 | p.debug("index", index) 309 | indexes = append(indexes, *index) 310 | } 311 | } 312 | 313 | func (p *Parser) parseIndex() (*core.Index, error) { 314 | index := &core.Index{} 315 | 316 | if p.token == token.LPAREN { 317 | p.next() 318 | for token.IsIdent(p.token) { 319 | index.Fields = append(index.Fields, p.lit) 320 | p.next() 321 | if p.token == token.COMMA { 322 | p.next() 323 | } 324 | } 325 | if p.token != token.RPAREN { 326 | return nil, p.expect(")") 327 | } 328 | } else if token.IsIdent(p.token) { 329 | index.Fields = append(index.Fields, p.lit) 330 | } else { 331 | return nil, p.expect("field_name") 332 | } 333 | 334 | p.next() 335 | 336 | if p.token == token.LBRACK { 337 | // Handle index setting [settings...] 338 | commaAllowed := false 339 | 340 | for { 341 | p.next() 342 | switch { 343 | case p.token == token.IDENT && strings.ToLower(p.lit) == "name": 344 | name, err := p.parseDescription() 345 | if err != nil { 346 | return nil, p.expect("name: 'index_name'") 347 | } 348 | index.Settings.Name = name 349 | case p.token == token.NOTE: 350 | note, err := p.parseDescription() 351 | if err != nil { 352 | return nil, p.expect("note: 'index note'") 353 | } 354 | index.Settings.Note = note 355 | case p.token == token.PK: 356 | index.Settings.PK = true 357 | case p.token == token.UNIQUE: 358 | index.Settings.Unique = true 359 | case p.token == token.TYPE: 360 | p.next() 361 | if p.token != token.COLON { 362 | return nil, p.expect(":") 363 | } 364 | p.next() 365 | if p.token != token.IDENT || (p.lit != "hash" && p.lit != "btree") { 366 | return nil, p.expect("hash|btree") 367 | } 368 | index.Settings.Type = p.lit 369 | case p.token == token.COMMA: 370 | if !commaAllowed { 371 | return nil, p.expect("[index settings...]") 372 | } 373 | case p.token == token.RBRACK: 374 | p.next() 375 | return index, nil 376 | default: 377 | return nil, p.expect("note|name|type|pk|unique") 378 | } 379 | commaAllowed = !commaAllowed 380 | } 381 | } 382 | 383 | return index, nil 384 | } 385 | 386 | func (p *Parser) parseColumn(name string) (*core.Column, error) { 387 | column := &core.Column{ 388 | Name: name, 389 | } 390 | if p.token != token.IDENT { 391 | return nil, p.expect("int, varchar,...") 392 | } 393 | column.Type = p.lit 394 | p.next() 395 | 396 | // parse for type 397 | switch p.token { 398 | case token.LPAREN: 399 | p.next() 400 | if p.token != token.INT { 401 | return nil, p.expect("int") 402 | } 403 | column.Type = fmt.Sprintf("%s(%s)", column.Type, p.lit) 404 | p.next() 405 | if p.token != token.RPAREN { 406 | return nil, p.expect(token.RPAREN.String()) 407 | } 408 | p.next() 409 | if p.token != token.LBRACK { 410 | break 411 | } 412 | fallthrough 413 | case token.LBRACK: 414 | //handle parseColumn 415 | columnSetting, err := p.parseColumnSettings() 416 | if err != nil { 417 | return nil, fmt.Errorf("parse column settings: %w", err) 418 | } 419 | p.next() // remove ']' 420 | column.Settings = *columnSetting 421 | } 422 | 423 | p.debug("column", column) 424 | return column, nil 425 | } 426 | 427 | func (p *Parser) parseColumnSettings() (*core.ColumnSetting, error) { 428 | columnSetting := &core.ColumnSetting{Null: true} 429 | commaAllowed := false 430 | 431 | for { 432 | p.next() 433 | switch p.token { 434 | case token.PK: 435 | columnSetting.PK = true 436 | case token.PRIMARY: 437 | p.next() 438 | if p.token != token.KEY { 439 | return nil, p.expect("KEY") 440 | } 441 | columnSetting.PK = true 442 | case token.REF: 443 | p.next() 444 | if p.token != token.COLON { 445 | return nil, p.expect(":") 446 | } 447 | p.next() 448 | if p.token != token.LSS && p.token != token.GTR && p.token != token.SUB { 449 | return nil, p.expect("< | > | -") 450 | } 451 | columnSetting.Ref.Type = core.RelationshipMap[p.token] 452 | p.next() 453 | if p.token != token.IDENT { 454 | return nil, p.expect("table.column_id") 455 | } 456 | columnSetting.Ref.To = p.lit 457 | case token.NOT: 458 | p.next() 459 | if p.token != token.NULL { 460 | return nil, p.expect("null") 461 | } 462 | columnSetting.Null = false 463 | case token.UNIQUE: 464 | columnSetting.Unique = true 465 | case token.INCREMENT: 466 | columnSetting.Increment = true 467 | case token.DEFAULT: 468 | p.next() 469 | if p.token != token.COLON { 470 | return nil, p.expect(":") 471 | } 472 | p.next() 473 | switch p.token { 474 | case token.STRING, token.DSTRING, token.TSTRING, token.INT, token.FLOAT, token.EXPR: 475 | //TODO: 476 | // * handle default value by expr 477 | // * validate default value by type 478 | columnSetting.Default = p.lit 479 | default: 480 | return nil, p.expect("default value") 481 | } 482 | case token.NOTE: 483 | str, err := p.parseDescription() 484 | if err != nil { 485 | return nil, err 486 | } 487 | columnSetting.Note = str 488 | case token.COMMA: 489 | if !commaAllowed { 490 | return nil, p.expect("pk | primary key | unique") 491 | } 492 | case token.RBRACK: 493 | return columnSetting, nil 494 | default: 495 | return nil, p.expect("pk, primary key, unique") 496 | } 497 | commaAllowed = !commaAllowed 498 | } 499 | } 500 | 501 | func (p *Parser) parseProject() (*core.Project, error) { 502 | project := &core.Project{} 503 | p.next() 504 | if p.token != token.IDENT && p.token != token.DSTRING { 505 | return nil, p.expect("project_name") 506 | } 507 | 508 | project.Name = p.lit 509 | p.next() 510 | 511 | if p.token != token.LBRACE { 512 | return nil, p.expect("{") 513 | } 514 | for { 515 | p.next() 516 | switch p.token { 517 | case token.IDENT: 518 | switch p.lit { 519 | case "database_type": 520 | str, err := p.parseDescription() 521 | if err != nil { 522 | return nil, err 523 | } 524 | project.DatabaseType = str 525 | default: 526 | return nil, p.expect("database_type") 527 | } 528 | case token.NOTE: 529 | note, err := p.parseDescription() 530 | if err != nil { 531 | return nil, err 532 | } 533 | project.Note = note 534 | case token.RBRACE: 535 | return project, nil 536 | default: 537 | return nil, fmt.Errorf("invalid token %s", p.lit) 538 | } 539 | } 540 | } 541 | 542 | func (p *Parser) parseString() (string, error) { 543 | p.next() 544 | switch p.token { 545 | case token.STRING, token.DSTRING, token.TSTRING: 546 | return p.lit, nil 547 | default: 548 | return "", p.expect("string, double quote string, triple string") 549 | } 550 | } 551 | 552 | func (p *Parser) parseDescription() (string, error) { 553 | p.next() 554 | if p.token != token.COLON { 555 | return "", p.expect(":") 556 | } 557 | return p.parseString() 558 | } 559 | 560 | func (p *Parser) next() { 561 | for { 562 | p.token, p.lit = p.s.Read() 563 | //p.debug("token:", p.token.String(), "lit:", p.lit) 564 | if p.token != token.COMMENT { 565 | break 566 | } 567 | } 568 | } 569 | 570 | func (p *Parser) expect(expected string) error { 571 | l, c := p.s.LineInfo() 572 | return fmt.Errorf("[%d:%d] invalid token '%s', expected: '%s'", l, c, p.lit, expected) 573 | } 574 | 575 | func (p *Parser) debug(args ...interface{}) { 576 | if p.Debug { 577 | for _, arg := range args { 578 | fmt.Printf("%#v\t", arg) 579 | } 580 | fmt.Println() 581 | } 582 | } 583 | -------------------------------------------------------------------------------- /parser/parser_test.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/duythinht/dbml-go/scanner" 8 | ) 9 | 10 | func p(str string) *Parser { 11 | r := strings.NewReader(str) 12 | s := scanner.NewScanner(r) 13 | parser := NewParser(s) 14 | return parser 15 | } 16 | 17 | func TestIllegalSyntax(t *testing.T) { 18 | parser := p(`Project test { abc , xyz`) 19 | _, err := parser.Parse() 20 | if err == nil { 21 | t.Fail() 22 | } 23 | } 24 | 25 | func TestParseSimple(t *testing.T) { 26 | parser := p(` 27 | Project test { 28 | note: 'just test note' 29 | } 30 | table users { 31 | id int [pk, note: 'just test column note'] 32 | } 33 | table float_number { 34 | 35 | } 36 | `) 37 | dbml, err := parser.Parse() 38 | if err != nil { 39 | t.Fail() 40 | } 41 | if dbml.Project.Name != "test" { 42 | t.Fail() 43 | } 44 | 45 | if dbml.Project.Note != "just test note" { 46 | t.Fail() 47 | } 48 | 49 | usersTable := dbml.Tables[0] 50 | if usersTable.Name != "users" { 51 | t.Fail() 52 | } 53 | idColumn := usersTable.Columns[0] 54 | if idColumn.Name != "id" { 55 | t.Fail() 56 | } 57 | if !idColumn.Settings.PK { 58 | t.Fail() 59 | } 60 | if idColumn.Settings.Note != "just test column note" { 61 | t.Fail() 62 | } 63 | } 64 | 65 | func TestParseTableName(t *testing.T) { 66 | parser := p(` 67 | Table int { 68 | id int 69 | } 70 | `) 71 | dbml, err := parser.Parse() 72 | if err != nil { 73 | t.Fail() 74 | } 75 | table := dbml.Tables[0] 76 | if table.Name != "int" { 77 | t.Fatalf("table name should be 'int'") 78 | } 79 | } 80 | 81 | func TestParseTableWithType(t *testing.T) { 82 | parser := p(` 83 | Table int { 84 | type int 85 | } 86 | `) 87 | dbml, err := parser.Parse() 88 | if err != nil { 89 | t.Fail() 90 | } 91 | table := dbml.Tables[0] 92 | if table.Columns[0].Name != "type" { 93 | t.Fatalf("column name should be 'type'") 94 | } 95 | } 96 | 97 | func TestParseTableWithNoteColumn(t *testing.T) { 98 | parser := p(` 99 | Table int { 100 | note int 101 | } 102 | `) 103 | dbml, err := parser.Parse() 104 | 105 | //t.Log(err) 106 | if err != nil { 107 | t.Fatalf("%v", err) 108 | } 109 | 110 | table := dbml.Tables[0] 111 | if table.Columns[0].Name != "note" { 112 | t.Fatalf("column name should be 'note'") 113 | } 114 | } 115 | 116 | func TestAllowKeywordsAsTable(t *testing.T) { 117 | parser := p(` 118 | Table project { 119 | note int 120 | } 121 | `) 122 | dbml, err := parser.Parse() 123 | 124 | //t.Log(err) 125 | if err != nil { 126 | t.Fatalf("%v", err) 127 | } 128 | 129 | table := dbml.Tables[0] 130 | if table.Name != "project" { 131 | t.Fatalf("table name should be 'project'") 132 | } 133 | } 134 | 135 | func TestAllowKeywordsAsEnum(t *testing.T) { 136 | parser := p(` 137 | Enum project { 138 | key 139 | } 140 | `) 141 | dbml, err := parser.Parse() 142 | 143 | //t.Log(err) 144 | if err != nil { 145 | t.Fatalf("%v", err) 146 | } 147 | 148 | enum := dbml.Enums[0] 149 | if enum.Name != "project" { 150 | t.Fatalf("enum name should be 'project'") 151 | } 152 | 153 | if enum.Values[0].Name != "key" { 154 | t.Fatalf("enum value should be 'key'") 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /scanner/check.go: -------------------------------------------------------------------------------- 1 | package scanner 2 | 3 | func isWhitespace(ch rune) bool { return ch == ' ' || ch == '\t' || ch == '\n' } 4 | func isLetter(ch rune) bool { return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') } 5 | func isDigit(ch rune) bool { return (ch >= '0' && ch <= '9') } 6 | -------------------------------------------------------------------------------- /scanner/scanner.go: -------------------------------------------------------------------------------- 1 | package scanner 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "io" 7 | 8 | "github.com/duythinht/dbml-go/token" 9 | ) 10 | 11 | const eof = rune(0) 12 | 13 | // Scanner represents a lexical scanner. 14 | type Scanner struct { 15 | r *bufio.Reader 16 | ch rune // for peek 17 | l uint 18 | c uint 19 | } 20 | 21 | // NewScanner returns a new instance of Scanner. 22 | func NewScanner(r io.Reader) *Scanner { 23 | s := &Scanner{r: bufio.NewReader(r), l: 1, c: 0} 24 | s.next() 25 | return s 26 | } 27 | 28 | // Next return next token and literal value 29 | func (s *Scanner) Read() (tok token.Token, lit string) { 30 | for isWhitespace(s.ch) { 31 | s.next() 32 | } 33 | 34 | // Otherwise read the individual character. 35 | switch { 36 | case isLetter(s.ch): 37 | return s.scanIdent() 38 | case isDigit(s.ch): 39 | return s.scanNumber() 40 | default: 41 | ch := s.ch 42 | lit := string(ch) 43 | s.next() 44 | switch ch { 45 | case eof: 46 | return token.EOF, "" 47 | case '-': 48 | return token.SUB, lit 49 | case '<': 50 | return token.LSS, lit 51 | case '>': 52 | return token.GTR, lit 53 | case '(': 54 | return token.LPAREN, lit 55 | case '[': 56 | return token.LBRACK, lit 57 | case '{': 58 | return token.LBRACE, lit 59 | case ')': 60 | return token.RPAREN, lit 61 | case ']': 62 | return token.RBRACK, lit 63 | case '}': 64 | return token.RBRACE, lit 65 | case ';': 66 | return token.SEMICOLON, lit 67 | case ':': 68 | return token.COLON, lit 69 | case ',': 70 | return token.COMMA, lit 71 | case '.': 72 | return token.PERIOD, lit 73 | case '`': 74 | return s.scanExpression() 75 | case '\'', '"': 76 | return s.scanString(ch) 77 | case '/': 78 | if s.ch == '/' { 79 | return token.COMMENT, s.scanComment() 80 | } 81 | return token.ILLEGAL, string(ch) 82 | } 83 | return token.ILLEGAL, string(ch) 84 | } 85 | } 86 | 87 | func (s *Scanner) scanComment() string { 88 | var buf bytes.Buffer 89 | buf.WriteString("/") 90 | for s.ch != '\n' && s.ch != eof { 91 | buf.WriteRune(s.ch) 92 | s.next() 93 | } 94 | return buf.String() 95 | } 96 | 97 | func (s *Scanner) scanNumber() (token.Token, string) { 98 | var buf bytes.Buffer 99 | countDot := 0 100 | for isDigit(s.ch) || (s.ch == '.' && countDot < 2) { 101 | if s.ch == '.' { 102 | countDot++ 103 | } 104 | buf.WriteRune(s.ch) 105 | s.next() 106 | } 107 | if countDot < 1 { 108 | return token.INT, buf.String() 109 | } else if countDot > 1 { 110 | return token.ILLEGAL, buf.String() 111 | } 112 | return token.FLOAT, buf.String() 113 | } 114 | 115 | func (s *Scanner) scanString(quo rune) (token.Token, string) { 116 | switch quo { 117 | case '"': 118 | lit, ok := s.scanTo(quo) 119 | if ok { 120 | return token.DSTRING, lit 121 | } 122 | return token.ILLEGAL, lit 123 | case '\'': 124 | if s.ch != '\'' { 125 | lit, ok := s.scanTo(quo) 126 | if ok { 127 | return token.STRING, lit 128 | } 129 | return token.ILLEGAL, lit 130 | } 131 | // Handle Triple quote string 132 | var buf bytes.Buffer 133 | s.next() 134 | if s.ch == '\'' { // triple quote string 135 | s.next() 136 | count := 0 137 | for count < 3 { 138 | switch s.ch { 139 | case '\'': 140 | count++ 141 | case eof: 142 | return token.ILLEGAL, buf.String() 143 | } 144 | buf.WriteRune(s.ch) 145 | s.next() 146 | } 147 | return token.TSTRING, buf.String()[:buf.Len()-count] 148 | } 149 | return token.ILLEGAL, buf.String() 150 | default: 151 | return token.ILLEGAL, string(eof) 152 | } 153 | } 154 | 155 | func (s *Scanner) scanExpression() (token.Token, string) { 156 | lit, ok := s.scanTo('`') 157 | if ok { 158 | return token.EXPR, lit 159 | } 160 | return token.ILLEGAL, lit 161 | } 162 | 163 | func (s *Scanner) scanTo(stop rune) (string, bool) { 164 | var buf bytes.Buffer 165 | for { 166 | switch s.ch { 167 | case stop: 168 | s.next() 169 | return buf.String(), true 170 | case '\n', eof: 171 | return buf.String(), false 172 | default: 173 | buf.WriteRune(s.ch) 174 | s.next() 175 | } 176 | } 177 | } 178 | 179 | func (s *Scanner) scanIdent() (tok token.Token, lit string) { 180 | var buf bytes.Buffer 181 | for { 182 | buf.WriteRune(s.ch) 183 | s.next() 184 | if !isLetter(s.ch) && !isDigit(s.ch) && s.ch != '_' && s.ch != '.' { 185 | break 186 | } 187 | } 188 | return token.Lookup(buf.String()), buf.String() 189 | } 190 | 191 | func (s *Scanner) next() { 192 | ch, _, err := s.r.ReadRune() 193 | if err != nil { 194 | s.ch = eof 195 | return 196 | } 197 | if ch == '\n' { 198 | s.l++ 199 | s.c = 0 200 | } 201 | s.c++ 202 | s.ch = ch 203 | } 204 | 205 | // LineInfo return line info 206 | func (s *Scanner) LineInfo() (uint, uint) { 207 | return s.l, s.c 208 | } 209 | -------------------------------------------------------------------------------- /scanner/scanner_test.go: -------------------------------------------------------------------------------- 1 | package scanner 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/duythinht/dbml-go/token" 8 | ) 9 | 10 | func sc(str string) *Scanner { 11 | return NewScanner(strings.NewReader(str)) 12 | } 13 | 14 | func TestScanForNumber(t *testing.T) { 15 | s := sc("123.456") 16 | if tok, lit := s.Read(); tok != token.FLOAT { 17 | t.Fatalf("token %s, should be %s, lit %s", tok, token.FLOAT, lit) 18 | } 19 | 20 | s = sc("123.456i") 21 | if tok, lit := s.Read(); tok != token.FLOAT { 22 | t.Fatalf("token %s, should be token.FLOAT, lit %s", tok, lit) 23 | if tok, lit := s.Read(); tok != token.IDENT { 24 | t.Fatalf("token %s, should be %s, lit %s", tok, token.IDENT, lit) 25 | } 26 | } 27 | 28 | s = sc("123") 29 | if tok, lit := s.Read(); tok != token.INT { 30 | t.Fatalf("token %s, should be %s, lit %s", tok, token.INT, lit) 31 | } 32 | 33 | s = sc("123.2.3") 34 | if tok, lit := s.Read(); tok != token.ILLEGAL { 35 | t.Fatalf("token %s, should be %s, lit %s", tok, token.ILLEGAL, lit) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test.dbml: -------------------------------------------------------------------------------- 1 | //// -- LEVEL 1 2 | //// -- Tables and References 3 | 4 | Project test { 5 | database_type: 'PostgreSQL' 6 | Note: 'Description of the project' 7 | } 8 | 9 | // Creating tables 10 | Table users as U { 11 | id int [pk, unique, increment] // auto-increment 12 | full_name varchar [not null, unique, default: 1] 13 | created_at timestamp 14 | country_code int 15 | type int 16 | note int 17 | Note: 'khong hieu duoc' 18 | } 19 | 20 | Table merchants { 21 | id int [pk] 22 | merchant_name varchar 23 | country_code int [not null] 24 | 'created at' varchar 25 | admin_id int [ref: > U.id] // inline relationship (many-to-one) 26 | } 27 | 28 | Table countries { 29 | code int [pk] 30 | name varchar 31 | continent_name varchar 32 | } 33 | 34 | // Creating references 35 | // You can also define relaionship separately 36 | // > many-to-one; < one-to-many; - one-to-one 37 | Ref{ 38 | U.country_code > countries.code 39 | merchants.country_code > countries.code 40 | } 41 | 42 | //----------------------------------------------// 43 | 44 | //// -- LEVEL 2 45 | //// -- Adding column settings 46 | 47 | Table order_items { 48 | order_id int [ref: > orders.id] 49 | product_id int 50 | quantity int [default: 1] // default value 51 | } 52 | 53 | Ref: order_items.product_id > products.id 54 | 55 | Table orders { 56 | id int [pk] // primary key 57 | user_id int [not null, unique] 58 | status varchar 59 | created_at varchar [note: '''When order created'''] // add column note 60 | } 61 | 62 | Table int { 63 | id int 64 | } 65 | 66 | //----------------------------------------------// 67 | 68 | //// -- Level 3 69 | //// -- Enum, Indexes 70 | 71 | // Enum for 'products' table below 72 | Enum products_status { 73 | out_of_stock 74 | in_stock 75 | running_low [note: 'less than 20'] // add column note 76 | } 77 | 78 | // Indexes: You can define a single or multi-column index 79 | Table products { 80 | id int [pk] 81 | name varchar 82 | merchant_id int [not null] 83 | price int 84 | status products_status 85 | created_at datetime [default: `now()`] 86 | 87 | Indexes { 88 | (merchant_id, status) [name:'product_status', type: hash] 89 | id [unique] 90 | } 91 | } 92 | 93 | Ref: products.merchant_id > merchants.id // many-to-one 94 | 95 | TableGroup hello_world { 96 | just_test 97 | just_a_test 98 | } -------------------------------------------------------------------------------- /token/token.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | import "strings" 4 | 5 | //Token ... 6 | type Token int 7 | 8 | //go:generate stringer -type=Token 9 | const ( 10 | // Special tokens 11 | ILLEGAL Token = iota 12 | EOF 13 | COMMENT 14 | 15 | _literalBeg 16 | // Identifiers and basic type literals 17 | // (these tokens stand for classes of literals) 18 | IDENT // main 19 | INT // 12345 20 | FLOAT // 123.45 21 | IMAG // 123.45i 22 | STRING // 'abc' 23 | DSTRING // "abc" 24 | TSTRING // '''abc''' 25 | 26 | EXPR // `now()` 27 | 28 | _literalEnd 29 | 30 | _operatorBeg 31 | 32 | SUB // - 33 | LSS // < 34 | GTR // > 35 | 36 | LPAREN // ( 37 | LBRACK // [ 38 | LBRACE // { 39 | COMMA // , 40 | PERIOD // . 41 | 42 | RPAREN // ) 43 | RBRACK // ] 44 | RBRACE // } 45 | SEMICOLON // ; 46 | COLON // : 47 | 48 | _operatorEnd 49 | 50 | _keywordBeg 51 | 52 | PROJECT 53 | TABLE 54 | ENUM 55 | REF 56 | AS 57 | TABLEGROUP 58 | 59 | _keywordEnd 60 | 61 | _miscBeg 62 | 63 | PRIMARY 64 | KEY 65 | PK 66 | NOTE 67 | UNIQUE 68 | NOT 69 | NULL 70 | INCREMENT 71 | DEFAULT 72 | 73 | INDEXES 74 | TYPE 75 | DELETE 76 | UPDATE 77 | NO 78 | ACTION 79 | RESTRICT 80 | SET 81 | 82 | _miscEnd 83 | ) 84 | 85 | // Tokens map to string 86 | var Tokens = [...]string{ 87 | ILLEGAL: "ILLEGAL", 88 | 89 | EOF: "EOF", 90 | COMMENT: "COMMENT", 91 | 92 | IDENT: "IDENT", 93 | INT: "INT", 94 | FLOAT: "FLOAT", 95 | IMAG: "IMAG", 96 | STRING: "STRING", 97 | DSTRING: "DSTRING", 98 | TSTRING: "TSTRING", 99 | EXPR: "EXPR", 100 | 101 | SUB: "-", 102 | LSS: "<", 103 | GTR: ">", 104 | 105 | LPAREN: "(", 106 | LBRACK: "[", 107 | LBRACE: "{", 108 | 109 | RPAREN: ")", 110 | RBRACK: "]", 111 | RBRACE: "}", 112 | 113 | SEMICOLON: ";", 114 | COLON: ":", 115 | COMMA: ",", 116 | PERIOD: ".", 117 | 118 | PROJECT: "PROJECT", 119 | TABLE: "TABLE", 120 | ENUM: "ENUM", 121 | REF: "REF", 122 | AS: "AS", 123 | TABLEGROUP: "TABLEGROUP", 124 | 125 | PRIMARY: "PRIMARY", 126 | KEY: "KEY", 127 | PK: "PK", 128 | NOTE: "NOTE", 129 | UNIQUE: "UNIQUE", 130 | NOT: "NOT", 131 | NULL: "NULL", 132 | INCREMENT: "INCREMENT", 133 | DEFAULT: "DEFAULT", 134 | 135 | INDEXES: "INDEXES", 136 | TYPE: "TYPE", 137 | DELETE: "DELETE", 138 | UPDATE: "UPDATE", 139 | NO: "NO", 140 | ACTION: "ACTION", 141 | RESTRICT: "RESTRICT", 142 | SET: "SET", 143 | } 144 | 145 | var keywords map[string]Token 146 | 147 | func init() { 148 | keywords = make(map[string]Token) 149 | for i := _keywordBeg + 1; i < _miscEnd; i++ { 150 | keywords[Tokens[i]] = i 151 | } 152 | } 153 | 154 | // Lookup find token with a name 155 | func Lookup(ident string) Token { 156 | if tok, ok := keywords[strings.ToUpper(ident)]; ok { 157 | return tok 158 | } 159 | return IDENT 160 | } 161 | 162 | func IsIdent(t Token) bool { 163 | switch t { 164 | case IDENT: 165 | return true 166 | default: 167 | return _keywordBeg < t && t < _miscEnd 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /token/token_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=Token"; DO NOT EDIT. 2 | 3 | package token 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[ILLEGAL-0] 12 | _ = x[EOF-1] 13 | _ = x[COMMENT-2] 14 | _ = x[_literalBeg-3] 15 | _ = x[IDENT-4] 16 | _ = x[INT-5] 17 | _ = x[FLOAT-6] 18 | _ = x[IMAG-7] 19 | _ = x[STRING-8] 20 | _ = x[DSTRING-9] 21 | _ = x[TSTRING-10] 22 | _ = x[EXPR-11] 23 | _ = x[_literalEnd-12] 24 | _ = x[_operatorBeg-13] 25 | _ = x[SUB-14] 26 | _ = x[LSS-15] 27 | _ = x[GTR-16] 28 | _ = x[LPAREN-17] 29 | _ = x[LBRACK-18] 30 | _ = x[LBRACE-19] 31 | _ = x[COMMA-20] 32 | _ = x[PERIOD-21] 33 | _ = x[RPAREN-22] 34 | _ = x[RBRACK-23] 35 | _ = x[RBRACE-24] 36 | _ = x[SEMICOLON-25] 37 | _ = x[COLON-26] 38 | _ = x[_operatorEnd-27] 39 | _ = x[_keywordBeg-28] 40 | _ = x[PROJECT-29] 41 | _ = x[TABLE-30] 42 | _ = x[ENUM-31] 43 | _ = x[REF-32] 44 | _ = x[AS-33] 45 | _ = x[TABLEGROUP-34] 46 | _ = x[_keywordEnd-35] 47 | _ = x[_miscBeg-36] 48 | _ = x[PRIMARY-37] 49 | _ = x[KEY-38] 50 | _ = x[PK-39] 51 | _ = x[NOTE-40] 52 | _ = x[UNIQUE-41] 53 | _ = x[NOT-42] 54 | _ = x[NULL-43] 55 | _ = x[INCREMENT-44] 56 | _ = x[DEFAULT-45] 57 | _ = x[INDEXES-46] 58 | _ = x[TYPE-47] 59 | _ = x[DELETE-48] 60 | _ = x[UPDATE-49] 61 | _ = x[NO-50] 62 | _ = x[ACTION-51] 63 | _ = x[RESTRICT-52] 64 | _ = x[SET-53] 65 | _ = x[_miscEnd-54] 66 | } 67 | 68 | const _Token_name = "ILLEGALEOFCOMMENT_literalBegIDENTINTFLOATIMAGSTRINGDSTRINGTSTRINGEXPR_literalEnd_operatorBegSUBLSSGTRLPARENLBRACKLBRACECOMMAPERIODRPARENRBRACKRBRACESEMICOLONCOLON_operatorEnd_keywordBegPROJECTTABLEENUMREFASTABLEGROUP_keywordEnd_miscBegPRIMARYKEYPKNOTEUNIQUENOTNULLINCREMENTDEFAULTINDEXESTYPEDELETEUPDATENOACTIONRESTRICTSET_miscEnd" 69 | 70 | var _Token_index = [...]uint16{0, 7, 10, 17, 28, 33, 36, 41, 45, 51, 58, 65, 69, 80, 92, 95, 98, 101, 107, 113, 119, 124, 130, 136, 142, 148, 157, 162, 174, 185, 192, 197, 201, 204, 206, 216, 227, 235, 242, 245, 247, 251, 257, 260, 264, 273, 280, 287, 291, 297, 303, 305, 311, 319, 322, 330} 71 | 72 | func (i Token) String() string { 73 | if i < 0 || i >= Token(len(_Token_index)-1) { 74 | return "Token(" + strconv.FormatInt(int64(i), 10) + ")" 75 | } 76 | return _Token_name[_Token_index[i]:_Token_index[i+1]] 77 | } 78 | --------------------------------------------------------------------------------