├── .gitignore ├── .travis.yml ├── Godeps ├── Godeps.json ├── Readme └── _workspace │ ├── .gitignore │ └── src │ └── github.com │ ├── BurntSushi │ └── toml │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── COMPATIBLE │ │ ├── COPYING │ │ ├── Makefile │ │ ├── README.md │ │ ├── cmd │ │ ├── toml-test-decoder │ │ │ ├── COPYING │ │ │ ├── README.md │ │ │ └── main.go │ │ ├── toml-test-encoder │ │ │ ├── COPYING │ │ │ ├── README.md │ │ │ └── main.go │ │ └── tomlv │ │ │ ├── COPYING │ │ │ ├── README.md │ │ │ └── main.go │ │ ├── decode.go │ │ ├── decode_meta.go │ │ ├── decode_test.go │ │ ├── doc.go │ │ ├── encode.go │ │ ├── encode_test.go │ │ ├── encoding_types.go │ │ ├── encoding_types_1.1.go │ │ ├── lex.go │ │ ├── parse.go │ │ ├── session.vim │ │ ├── type_check.go │ │ └── type_fields.go │ ├── flike │ └── golog │ │ ├── LICENSE.md │ │ ├── README.md │ │ ├── filehandler.go │ │ ├── handler.go │ │ ├── log.go │ │ └── log_test.go │ └── go-sql-driver │ └── mysql │ ├── .gitignore │ ├── .travis.yml │ ├── AUTHORS │ ├── CHANGELOG.md │ ├── CONTRIBUTING.md │ ├── LICENSE │ ├── README.md │ ├── appengine.go │ ├── benchmark_test.go │ ├── buffer.go │ ├── collations.go │ ├── connection.go │ ├── const.go │ ├── driver.go │ ├── driver_test.go │ ├── errors.go │ ├── errors_test.go │ ├── infile.go │ ├── packets.go │ ├── result.go │ ├── rows.go │ ├── statement.go │ ├── transaction.go │ ├── utils.go │ └── utils_test.go ├── Makefile ├── Readme.md ├── Readme_zh.md ├── cmd └── idgo │ └── main.go ├── config └── config.go ├── dev.sh ├── etc └── idgo.toml ├── go.mod ├── go.sum └── server ├── command.go ├── idgo.go ├── idgo_test.go ├── proto.go └── server.go /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | pkg 3 | .DS_Store 4 | y.output 5 | .idea 6 | *.iml 7 | build_config.mk 8 | 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 1.6 3 | before_install: 4 | - go get github.com/tools/godep 5 | script: 6 | - source dev.sh 7 | - make 8 | -------------------------------------------------------------------------------- /Godeps/Godeps.json: -------------------------------------------------------------------------------- 1 | { 2 | "ImportPath": "github.com/flike/idgo", 3 | "GoVersion": "go1.6", 4 | "Packages": [ 5 | "./..." 6 | ], 7 | "Deps": [ 8 | { 9 | "ImportPath": "github.com/BurntSushi/toml", 10 | "Comment": "v0.1.0-23-g5c4df71", 11 | "Rev": "5c4df71dfe9ac89ef6287afc05e4c1b16ae65a1e" 12 | }, 13 | { 14 | "ImportPath": "github.com/flike/golog", 15 | "Rev": "d59ac6dad9f09769d00642a7431b8f32823fb9f6" 16 | }, 17 | { 18 | "ImportPath": "github.com/go-sql-driver/mysql", 19 | "Comment": "v1.2-26-g9543750", 20 | "Rev": "9543750295406ef070f7de8ae9c43ccddd44e15e" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /Godeps/Readme: -------------------------------------------------------------------------------- 1 | This directory tree is generated automatically by godep. 2 | 3 | Please do not edit. 4 | 5 | See https://github.com/tools/godep for more information. 6 | -------------------------------------------------------------------------------- /Godeps/_workspace/.gitignore: -------------------------------------------------------------------------------- 1 | /pkg 2 | /bin 3 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/BurntSushi/toml/.gitignore: -------------------------------------------------------------------------------- 1 | TAGS 2 | tags 3 | .*.swp 4 | tomlcheck/tomlcheck 5 | toml.test 6 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/BurntSushi/toml/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.1 4 | - 1.2 5 | - tip 6 | install: 7 | - go install ./... 8 | - go get github.com/BurntSushi/toml-test 9 | script: 10 | - export PATH="$PATH:$HOME/gopath/bin" 11 | - make test 12 | 13 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/BurntSushi/toml/COMPATIBLE: -------------------------------------------------------------------------------- 1 | Compatible with TOML version 2 | [v0.2.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.2.0.md) 3 | 4 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/BurntSushi/toml/COPYING: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | 15 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/BurntSushi/toml/Makefile: -------------------------------------------------------------------------------- 1 | install: 2 | go install ./... 3 | 4 | test: install 5 | go test -v 6 | toml-test toml-test-decoder 7 | toml-test -encoder toml-test-encoder 8 | 9 | fmt: 10 | gofmt -w *.go */*.go 11 | colcheck *.go */*.go 12 | 13 | tags: 14 | find ./ -name '*.go' -print0 | xargs -0 gotags > TAGS 15 | 16 | push: 17 | git push origin master 18 | git push github master 19 | 20 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/BurntSushi/toml/README.md: -------------------------------------------------------------------------------- 1 | ## TOML parser and encoder for Go with reflection 2 | 3 | TOML stands for Tom's Obvious, Minimal Language. This Go package provides a 4 | reflection interface similar to Go's standard library `json` and `xml` 5 | packages. This package also supports the `encoding.TextUnmarshaler` and 6 | `encoding.TextMarshaler` interfaces so that you can define custom data 7 | representations. (There is an example of this below.) 8 | 9 | Spec: https://github.com/mojombo/toml 10 | 11 | Compatible with TOML version 12 | [v0.2.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.2.0.md) 13 | 14 | Documentation: http://godoc.org/github.com/BurntSushi/toml 15 | 16 | Installation: 17 | 18 | ```bash 19 | go get github.com/BurntSushi/toml 20 | ``` 21 | 22 | Try the toml validator: 23 | 24 | ```bash 25 | go get github.com/BurntSushi/toml/cmd/tomlv 26 | tomlv some-toml-file.toml 27 | ``` 28 | 29 | [![Build status](https://api.travis-ci.org/BurntSushi/toml.png)](https://travis-ci.org/BurntSushi/toml) 30 | 31 | 32 | ### Testing 33 | 34 | This package passes all tests in 35 | [toml-test](https://github.com/BurntSushi/toml-test) for both the decoder 36 | and the encoder. 37 | 38 | ### Examples 39 | 40 | This package works similarly to how the Go standard library handles `XML` 41 | and `JSON`. Namely, data is loaded into Go values via reflection. 42 | 43 | For the simplest example, consider some TOML file as just a list of keys 44 | and values: 45 | 46 | ```toml 47 | Age = 25 48 | Cats = [ "Cauchy", "Plato" ] 49 | Pi = 3.14 50 | Perfection = [ 6, 28, 496, 8128 ] 51 | DOB = 1987-07-05T05:45:00Z 52 | ``` 53 | 54 | Which could be defined in Go as: 55 | 56 | ```go 57 | type Config struct { 58 | Age int 59 | Cats []string 60 | Pi float64 61 | Perfection []int 62 | DOB time.Time // requires `import time` 63 | } 64 | ``` 65 | 66 | And then decoded with: 67 | 68 | ```go 69 | var conf Config 70 | if _, err := toml.Decode(tomlData, &conf); err != nil { 71 | // handle error 72 | } 73 | ``` 74 | 75 | You can also use struct tags if your struct field name doesn't map to a TOML 76 | key value directly: 77 | 78 | ```toml 79 | some_key_NAME = "wat" 80 | ``` 81 | 82 | ```go 83 | type TOML struct { 84 | ObscureKey string `toml:"some_key_NAME"` 85 | } 86 | ``` 87 | 88 | ### Using the `encoding.TextUnmarshaler` interface 89 | 90 | Here's an example that automatically parses duration strings into 91 | `time.Duration` values: 92 | 93 | ```toml 94 | [[song]] 95 | name = "Thunder Road" 96 | duration = "4m49s" 97 | 98 | [[song]] 99 | name = "Stairway to Heaven" 100 | duration = "8m03s" 101 | ``` 102 | 103 | Which can be decoded with: 104 | 105 | ```go 106 | type song struct { 107 | Name string 108 | Duration duration 109 | } 110 | type songs struct { 111 | Song []song 112 | } 113 | var favorites songs 114 | if _, err := toml.Decode(blob, &favorites); err != nil { 115 | log.Fatal(err) 116 | } 117 | 118 | for _, s := range favorites.Song { 119 | fmt.Printf("%s (%s)\n", s.Name, s.Duration) 120 | } 121 | ``` 122 | 123 | And you'll also need a `duration` type that satisfies the 124 | `encoding.TextUnmarshaler` interface: 125 | 126 | ```go 127 | type duration struct { 128 | time.Duration 129 | } 130 | 131 | func (d *duration) UnmarshalText(text []byte) error { 132 | var err error 133 | d.Duration, err = time.ParseDuration(string(text)) 134 | return err 135 | } 136 | ``` 137 | 138 | ### More complex usage 139 | 140 | Here's an example of how to load the example from the official spec page: 141 | 142 | ```toml 143 | # This is a TOML document. Boom. 144 | 145 | title = "TOML Example" 146 | 147 | [owner] 148 | name = "Tom Preston-Werner" 149 | organization = "GitHub" 150 | bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." 151 | dob = 1979-05-27T07:32:00Z # First class dates? Why not? 152 | 153 | [database] 154 | server = "192.168.1.1" 155 | ports = [ 8001, 8001, 8002 ] 156 | connection_max = 5000 157 | enabled = true 158 | 159 | [servers] 160 | 161 | # You can indent as you please. Tabs or spaces. TOML don't care. 162 | [servers.alpha] 163 | ip = "10.0.0.1" 164 | dc = "eqdc10" 165 | 166 | [servers.beta] 167 | ip = "10.0.0.2" 168 | dc = "eqdc10" 169 | 170 | [clients] 171 | data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it 172 | 173 | # Line breaks are OK when inside arrays 174 | hosts = [ 175 | "alpha", 176 | "omega" 177 | ] 178 | ``` 179 | 180 | And the corresponding Go types are: 181 | 182 | ```go 183 | type tomlConfig struct { 184 | Title string 185 | Owner ownerInfo 186 | DB database `toml:"database"` 187 | Servers map[string]server 188 | Clients clients 189 | } 190 | 191 | type ownerInfo struct { 192 | Name string 193 | Org string `toml:"organization"` 194 | Bio string 195 | DOB time.Time 196 | } 197 | 198 | type database struct { 199 | Server string 200 | Ports []int 201 | ConnMax int `toml:"connection_max"` 202 | Enabled bool 203 | } 204 | 205 | type server struct { 206 | IP string 207 | DC string 208 | } 209 | 210 | type clients struct { 211 | Data [][]interface{} 212 | Hosts []string 213 | } 214 | ``` 215 | 216 | Note that a case insensitive match will be tried if an exact match can't be 217 | found. 218 | 219 | A working example of the above can be found in `_examples/example.{go,toml}`. 220 | 221 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/toml-test-decoder/COPYING: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | 15 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/toml-test-decoder/README.md: -------------------------------------------------------------------------------- 1 | # Implements the TOML test suite interface 2 | 3 | This is an implementation of the interface expected by 4 | [toml-test](https://github.com/BurntSushi/toml-test) for my 5 | [toml parser written in Go](https://github.com/BurntSushi/toml). 6 | In particular, it maps TOML data on `stdin` to a JSON format on `stdout`. 7 | 8 | 9 | Compatible with TOML version 10 | [v0.2.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.2.0.md) 11 | 12 | Compatible with `toml-test` version 13 | [v0.2.0](https://github.com/BurntSushi/toml-test/tree/v0.2.0) 14 | 15 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/toml-test-decoder/main.go: -------------------------------------------------------------------------------- 1 | // Command toml-test-decoder satisfies the toml-test interface for testing 2 | // TOML decoders. Namely, it accepts TOML on stdin and outputs JSON on stdout. 3 | package main 4 | 5 | import ( 6 | "encoding/json" 7 | "flag" 8 | "fmt" 9 | "log" 10 | "os" 11 | "path" 12 | "time" 13 | 14 | "github.com/BurntSushi/toml" 15 | ) 16 | 17 | func init() { 18 | log.SetFlags(0) 19 | 20 | flag.Usage = usage 21 | flag.Parse() 22 | } 23 | 24 | func usage() { 25 | log.Printf("Usage: %s < toml-file\n", path.Base(os.Args[0])) 26 | flag.PrintDefaults() 27 | 28 | os.Exit(1) 29 | } 30 | 31 | func main() { 32 | if flag.NArg() != 0 { 33 | flag.Usage() 34 | } 35 | 36 | var tmp interface{} 37 | if _, err := toml.DecodeReader(os.Stdin, &tmp); err != nil { 38 | log.Fatalf("Error decoding TOML: %s", err) 39 | } 40 | 41 | typedTmp := translate(tmp) 42 | if err := json.NewEncoder(os.Stdout).Encode(typedTmp); err != nil { 43 | log.Fatalf("Error encoding JSON: %s", err) 44 | } 45 | } 46 | 47 | func translate(tomlData interface{}) interface{} { 48 | switch orig := tomlData.(type) { 49 | case map[string]interface{}: 50 | typed := make(map[string]interface{}, len(orig)) 51 | for k, v := range orig { 52 | typed[k] = translate(v) 53 | } 54 | return typed 55 | case []map[string]interface{}: 56 | typed := make([]map[string]interface{}, len(orig)) 57 | for i, v := range orig { 58 | typed[i] = translate(v).(map[string]interface{}) 59 | } 60 | return typed 61 | case []interface{}: 62 | typed := make([]interface{}, len(orig)) 63 | for i, v := range orig { 64 | typed[i] = translate(v) 65 | } 66 | 67 | // We don't really need to tag arrays, but let's be future proof. 68 | // (If TOML ever supports tuples, we'll need this.) 69 | return tag("array", typed) 70 | case time.Time: 71 | return tag("datetime", orig.Format("2006-01-02T15:04:05Z")) 72 | case bool: 73 | return tag("bool", fmt.Sprintf("%v", orig)) 74 | case int64: 75 | return tag("integer", fmt.Sprintf("%d", orig)) 76 | case float64: 77 | return tag("float", fmt.Sprintf("%v", orig)) 78 | case string: 79 | return tag("string", orig) 80 | } 81 | 82 | panic(fmt.Sprintf("Unknown type: %T", tomlData)) 83 | } 84 | 85 | func tag(typeName string, data interface{}) map[string]interface{} { 86 | return map[string]interface{}{ 87 | "type": typeName, 88 | "value": data, 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/toml-test-encoder/COPYING: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | 15 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/toml-test-encoder/README.md: -------------------------------------------------------------------------------- 1 | # Implements the TOML test suite interface for TOML encoders 2 | 3 | This is an implementation of the interface expected by 4 | [toml-test](https://github.com/BurntSushi/toml-test) for the 5 | [TOML encoder](https://github.com/BurntSushi/toml). 6 | In particular, it maps JSON data on `stdin` to a TOML format on `stdout`. 7 | 8 | 9 | Compatible with TOML version 10 | [v0.2.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.2.0.md) 11 | 12 | Compatible with `toml-test` version 13 | [v0.2.0](https://github.com/BurntSushi/toml-test/tree/v0.2.0) 14 | 15 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/toml-test-encoder/main.go: -------------------------------------------------------------------------------- 1 | // Command toml-test-encoder satisfies the toml-test interface for testing 2 | // TOML encoders. Namely, it accepts JSON on stdin and outputs TOML on stdout. 3 | package main 4 | 5 | import ( 6 | "encoding/json" 7 | "flag" 8 | "log" 9 | "os" 10 | "path" 11 | "strconv" 12 | "time" 13 | 14 | "github.com/BurntSushi/toml" 15 | ) 16 | 17 | func init() { 18 | log.SetFlags(0) 19 | 20 | flag.Usage = usage 21 | flag.Parse() 22 | } 23 | 24 | func usage() { 25 | log.Printf("Usage: %s < json-file\n", path.Base(os.Args[0])) 26 | flag.PrintDefaults() 27 | 28 | os.Exit(1) 29 | } 30 | 31 | func main() { 32 | if flag.NArg() != 0 { 33 | flag.Usage() 34 | } 35 | 36 | var tmp interface{} 37 | if err := json.NewDecoder(os.Stdin).Decode(&tmp); err != nil { 38 | log.Fatalf("Error decoding JSON: %s", err) 39 | } 40 | 41 | tomlData := translate(tmp) 42 | if err := toml.NewEncoder(os.Stdout).Encode(tomlData); err != nil { 43 | log.Fatalf("Error encoding TOML: %s", err) 44 | } 45 | } 46 | 47 | func translate(typedJson interface{}) interface{} { 48 | switch v := typedJson.(type) { 49 | case map[string]interface{}: 50 | if len(v) == 2 && in("type", v) && in("value", v) { 51 | return untag(v) 52 | } 53 | m := make(map[string]interface{}, len(v)) 54 | for k, v2 := range v { 55 | m[k] = translate(v2) 56 | } 57 | return m 58 | case []interface{}: 59 | tabArray := make([]map[string]interface{}, len(v)) 60 | for i := range v { 61 | if m, ok := translate(v[i]).(map[string]interface{}); ok { 62 | tabArray[i] = m 63 | } else { 64 | log.Fatalf("JSON arrays may only contain objects. This " + 65 | "corresponds to only tables being allowed in " + 66 | "TOML table arrays.") 67 | } 68 | } 69 | return tabArray 70 | } 71 | log.Fatalf("Unrecognized JSON format '%T'.", typedJson) 72 | panic("unreachable") 73 | } 74 | 75 | func untag(typed map[string]interface{}) interface{} { 76 | t := typed["type"].(string) 77 | v := typed["value"] 78 | switch t { 79 | case "string": 80 | return v.(string) 81 | case "integer": 82 | v := v.(string) 83 | n, err := strconv.Atoi(v) 84 | if err != nil { 85 | log.Fatalf("Could not parse '%s' as integer: %s", v, err) 86 | } 87 | return n 88 | case "float": 89 | v := v.(string) 90 | f, err := strconv.ParseFloat(v, 64) 91 | if err != nil { 92 | log.Fatalf("Could not parse '%s' as float64: %s", v, err) 93 | } 94 | return f 95 | case "datetime": 96 | v := v.(string) 97 | t, err := time.Parse("2006-01-02T15:04:05Z", v) 98 | if err != nil { 99 | log.Fatalf("Could not parse '%s' as a datetime: %s", v, err) 100 | } 101 | return t 102 | case "bool": 103 | v := v.(string) 104 | switch v { 105 | case "true": 106 | return true 107 | case "false": 108 | return false 109 | } 110 | log.Fatalf("Could not parse '%s' as a boolean.", v) 111 | case "array": 112 | v := v.([]interface{}) 113 | array := make([]interface{}, len(v)) 114 | for i := range v { 115 | if m, ok := v[i].(map[string]interface{}); ok { 116 | array[i] = untag(m) 117 | } else { 118 | log.Fatalf("Arrays may only contain other arrays or "+ 119 | "primitive values, but found a '%T'.", m) 120 | } 121 | } 122 | return array 123 | } 124 | log.Fatalf("Unrecognized tag type '%s'.", t) 125 | panic("unreachable") 126 | } 127 | 128 | func in(key string, m map[string]interface{}) bool { 129 | _, ok := m[key] 130 | return ok 131 | } 132 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/tomlv/COPYING: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | 15 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/tomlv/README.md: -------------------------------------------------------------------------------- 1 | # TOML Validator 2 | 3 | If Go is installed, it's simple to try it out: 4 | 5 | ```bash 6 | go get github.com/BurntSushi/toml/cmd/tomlv 7 | tomlv some-toml-file.toml 8 | ``` 9 | 10 | You can see the types of every key in a TOML file with: 11 | 12 | ```bash 13 | tomlv -types some-toml-file.toml 14 | ``` 15 | 16 | At the moment, only one error message is reported at a time. Error messages 17 | include line numbers. No output means that the files given are valid TOML, or 18 | there is a bug in `tomlv`. 19 | 20 | Compatible with TOML version 21 | [v0.1.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.1.0.md) 22 | 23 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/tomlv/main.go: -------------------------------------------------------------------------------- 1 | // Command tomlv validates TOML documents and prints each key's type. 2 | package main 3 | 4 | import ( 5 | "flag" 6 | "fmt" 7 | "log" 8 | "os" 9 | "path" 10 | "strings" 11 | "text/tabwriter" 12 | 13 | "github.com/BurntSushi/toml" 14 | ) 15 | 16 | var ( 17 | flagTypes = false 18 | ) 19 | 20 | func init() { 21 | log.SetFlags(0) 22 | 23 | flag.BoolVar(&flagTypes, "types", flagTypes, 24 | "When set, the types of every defined key will be shown.") 25 | 26 | flag.Usage = usage 27 | flag.Parse() 28 | } 29 | 30 | func usage() { 31 | log.Printf("Usage: %s toml-file [ toml-file ... ]\n", 32 | path.Base(os.Args[0])) 33 | flag.PrintDefaults() 34 | 35 | os.Exit(1) 36 | } 37 | 38 | func main() { 39 | if flag.NArg() < 1 { 40 | flag.Usage() 41 | } 42 | for _, f := range flag.Args() { 43 | var tmp interface{} 44 | md, err := toml.DecodeFile(f, &tmp) 45 | if err != nil { 46 | log.Fatalf("Error in '%s': %s", f, err) 47 | } 48 | if flagTypes { 49 | printTypes(md) 50 | } 51 | } 52 | } 53 | 54 | func printTypes(md toml.MetaData) { 55 | tabw := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 56 | for _, key := range md.Keys() { 57 | fmt.Fprintf(tabw, "%s%s\t%s\n", 58 | strings.Repeat(" ", len(key)-1), key, md.Type(key...)) 59 | } 60 | tabw.Flush() 61 | } 62 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/BurntSushi/toml/decode.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "math" 8 | "reflect" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | var e = fmt.Errorf 14 | 15 | // Unmarshaler is the interface implemented by objects that can unmarshal a 16 | // TOML description of themselves. 17 | type Unmarshaler interface { 18 | UnmarshalTOML(interface{}) error 19 | } 20 | 21 | // Unmarshal decodes the contents of `p` in TOML format into a pointer `v`. 22 | func Unmarshal(p []byte, v interface{}) error { 23 | _, err := Decode(string(p), v) 24 | return err 25 | } 26 | 27 | // Primitive is a TOML value that hasn't been decoded into a Go value. 28 | // When using the various `Decode*` functions, the type `Primitive` may 29 | // be given to any value, and its decoding will be delayed. 30 | // 31 | // A `Primitive` value can be decoded using the `PrimitiveDecode` function. 32 | // 33 | // The underlying representation of a `Primitive` value is subject to change. 34 | // Do not rely on it. 35 | // 36 | // N.B. Primitive values are still parsed, so using them will only avoid 37 | // the overhead of reflection. They can be useful when you don't know the 38 | // exact type of TOML data until run time. 39 | type Primitive struct { 40 | undecoded interface{} 41 | context Key 42 | } 43 | 44 | // DEPRECATED! 45 | // 46 | // Use MetaData.PrimitiveDecode instead. 47 | func PrimitiveDecode(primValue Primitive, v interface{}) error { 48 | md := MetaData{decoded: make(map[string]bool)} 49 | return md.unify(primValue.undecoded, rvalue(v)) 50 | } 51 | 52 | // PrimitiveDecode is just like the other `Decode*` functions, except it 53 | // decodes a TOML value that has already been parsed. Valid primitive values 54 | // can *only* be obtained from values filled by the decoder functions, 55 | // including this method. (i.e., `v` may contain more `Primitive` 56 | // values.) 57 | // 58 | // Meta data for primitive values is included in the meta data returned by 59 | // the `Decode*` functions with one exception: keys returned by the Undecoded 60 | // method will only reflect keys that were decoded. Namely, any keys hidden 61 | // behind a Primitive will be considered undecoded. Executing this method will 62 | // update the undecoded keys in the meta data. (See the example.) 63 | func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error { 64 | md.context = primValue.context 65 | defer func() { md.context = nil }() 66 | return md.unify(primValue.undecoded, rvalue(v)) 67 | } 68 | 69 | // Decode will decode the contents of `data` in TOML format into a pointer 70 | // `v`. 71 | // 72 | // TOML hashes correspond to Go structs or maps. (Dealer's choice. They can be 73 | // used interchangeably.) 74 | // 75 | // TOML arrays of tables correspond to either a slice of structs or a slice 76 | // of maps. 77 | // 78 | // TOML datetimes correspond to Go `time.Time` values. 79 | // 80 | // All other TOML types (float, string, int, bool and array) correspond 81 | // to the obvious Go types. 82 | // 83 | // An exception to the above rules is if a type implements the 84 | // encoding.TextUnmarshaler interface. In this case, any primitive TOML value 85 | // (floats, strings, integers, booleans and datetimes) will be converted to 86 | // a byte string and given to the value's UnmarshalText method. See the 87 | // Unmarshaler example for a demonstration with time duration strings. 88 | // 89 | // Key mapping 90 | // 91 | // TOML keys can map to either keys in a Go map or field names in a Go 92 | // struct. The special `toml` struct tag may be used to map TOML keys to 93 | // struct fields that don't match the key name exactly. (See the example.) 94 | // A case insensitive match to struct names will be tried if an exact match 95 | // can't be found. 96 | // 97 | // The mapping between TOML values and Go values is loose. That is, there 98 | // may exist TOML values that cannot be placed into your representation, and 99 | // there may be parts of your representation that do not correspond to 100 | // TOML values. This loose mapping can be made stricter by using the IsDefined 101 | // and/or Undecoded methods on the MetaData returned. 102 | // 103 | // This decoder will not handle cyclic types. If a cyclic type is passed, 104 | // `Decode` will not terminate. 105 | func Decode(data string, v interface{}) (MetaData, error) { 106 | p, err := parse(data) 107 | if err != nil { 108 | return MetaData{}, err 109 | } 110 | md := MetaData{ 111 | p.mapping, p.types, p.ordered, 112 | make(map[string]bool, len(p.ordered)), nil, 113 | } 114 | return md, md.unify(p.mapping, rvalue(v)) 115 | } 116 | 117 | // DecodeFile is just like Decode, except it will automatically read the 118 | // contents of the file at `fpath` and decode it for you. 119 | func DecodeFile(fpath string, v interface{}) (MetaData, error) { 120 | bs, err := ioutil.ReadFile(fpath) 121 | if err != nil { 122 | return MetaData{}, err 123 | } 124 | return Decode(string(bs), v) 125 | } 126 | 127 | // DecodeReader is just like Decode, except it will consume all bytes 128 | // from the reader and decode it for you. 129 | func DecodeReader(r io.Reader, v interface{}) (MetaData, error) { 130 | bs, err := ioutil.ReadAll(r) 131 | if err != nil { 132 | return MetaData{}, err 133 | } 134 | return Decode(string(bs), v) 135 | } 136 | 137 | // unify performs a sort of type unification based on the structure of `rv`, 138 | // which is the client representation. 139 | // 140 | // Any type mismatch produces an error. Finding a type that we don't know 141 | // how to handle produces an unsupported type error. 142 | func (md *MetaData) unify(data interface{}, rv reflect.Value) error { 143 | 144 | // Special case. Look for a `Primitive` value. 145 | if rv.Type() == reflect.TypeOf((*Primitive)(nil)).Elem() { 146 | // Save the undecoded data and the key context into the primitive 147 | // value. 148 | context := make(Key, len(md.context)) 149 | copy(context, md.context) 150 | rv.Set(reflect.ValueOf(Primitive{ 151 | undecoded: data, 152 | context: context, 153 | })) 154 | return nil 155 | } 156 | 157 | // Special case. Unmarshaler Interface support. 158 | if rv.CanAddr() { 159 | if v, ok := rv.Addr().Interface().(Unmarshaler); ok { 160 | return v.UnmarshalTOML(data) 161 | } 162 | } 163 | 164 | // Special case. Handle time.Time values specifically. 165 | // TODO: Remove this code when we decide to drop support for Go 1.1. 166 | // This isn't necessary in Go 1.2 because time.Time satisfies the encoding 167 | // interfaces. 168 | if rv.Type().AssignableTo(rvalue(time.Time{}).Type()) { 169 | return md.unifyDatetime(data, rv) 170 | } 171 | 172 | // Special case. Look for a value satisfying the TextUnmarshaler interface. 173 | if v, ok := rv.Interface().(TextUnmarshaler); ok { 174 | return md.unifyText(data, v) 175 | } 176 | // BUG(burntsushi) 177 | // The behavior here is incorrect whenever a Go type satisfies the 178 | // encoding.TextUnmarshaler interface but also corresponds to a TOML 179 | // hash or array. In particular, the unmarshaler should only be applied 180 | // to primitive TOML values. But at this point, it will be applied to 181 | // all kinds of values and produce an incorrect error whenever those values 182 | // are hashes or arrays (including arrays of tables). 183 | 184 | k := rv.Kind() 185 | 186 | // laziness 187 | if k >= reflect.Int && k <= reflect.Uint64 { 188 | return md.unifyInt(data, rv) 189 | } 190 | switch k { 191 | case reflect.Ptr: 192 | elem := reflect.New(rv.Type().Elem()) 193 | err := md.unify(data, reflect.Indirect(elem)) 194 | if err != nil { 195 | return err 196 | } 197 | rv.Set(elem) 198 | return nil 199 | case reflect.Struct: 200 | return md.unifyStruct(data, rv) 201 | case reflect.Map: 202 | return md.unifyMap(data, rv) 203 | case reflect.Array: 204 | return md.unifyArray(data, rv) 205 | case reflect.Slice: 206 | return md.unifySlice(data, rv) 207 | case reflect.String: 208 | return md.unifyString(data, rv) 209 | case reflect.Bool: 210 | return md.unifyBool(data, rv) 211 | case reflect.Interface: 212 | // we only support empty interfaces. 213 | if rv.NumMethod() > 0 { 214 | return e("Unsupported type '%s'.", rv.Kind()) 215 | } 216 | return md.unifyAnything(data, rv) 217 | case reflect.Float32: 218 | fallthrough 219 | case reflect.Float64: 220 | return md.unifyFloat64(data, rv) 221 | } 222 | return e("Unsupported type '%s'.", rv.Kind()) 223 | } 224 | 225 | func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error { 226 | tmap, ok := mapping.(map[string]interface{}) 227 | if !ok { 228 | return mismatch(rv, "map", mapping) 229 | } 230 | 231 | for key, datum := range tmap { 232 | var f *field 233 | fields := cachedTypeFields(rv.Type()) 234 | for i := range fields { 235 | ff := &fields[i] 236 | if ff.name == key { 237 | f = ff 238 | break 239 | } 240 | if f == nil && strings.EqualFold(ff.name, key) { 241 | f = ff 242 | } 243 | } 244 | if f != nil { 245 | subv := rv 246 | for _, i := range f.index { 247 | subv = indirect(subv.Field(i)) 248 | } 249 | if isUnifiable(subv) { 250 | md.decoded[md.context.add(key).String()] = true 251 | md.context = append(md.context, key) 252 | if err := md.unify(datum, subv); err != nil { 253 | return e("Type mismatch for '%s.%s': %s", 254 | rv.Type().String(), f.name, err) 255 | } 256 | md.context = md.context[0 : len(md.context)-1] 257 | } else if f.name != "" { 258 | // Bad user! No soup for you! 259 | return e("Field '%s.%s' is unexported, and therefore cannot "+ 260 | "be loaded with reflection.", rv.Type().String(), f.name) 261 | } 262 | } 263 | } 264 | return nil 265 | } 266 | 267 | func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error { 268 | tmap, ok := mapping.(map[string]interface{}) 269 | if !ok { 270 | return badtype("map", mapping) 271 | } 272 | if rv.IsNil() { 273 | rv.Set(reflect.MakeMap(rv.Type())) 274 | } 275 | for k, v := range tmap { 276 | md.decoded[md.context.add(k).String()] = true 277 | md.context = append(md.context, k) 278 | 279 | rvkey := indirect(reflect.New(rv.Type().Key())) 280 | rvval := reflect.Indirect(reflect.New(rv.Type().Elem())) 281 | if err := md.unify(v, rvval); err != nil { 282 | return err 283 | } 284 | md.context = md.context[0 : len(md.context)-1] 285 | 286 | rvkey.SetString(k) 287 | rv.SetMapIndex(rvkey, rvval) 288 | } 289 | return nil 290 | } 291 | 292 | func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error { 293 | datav := reflect.ValueOf(data) 294 | if datav.Kind() != reflect.Slice { 295 | return badtype("slice", data) 296 | } 297 | sliceLen := datav.Len() 298 | if sliceLen != rv.Len() { 299 | return e("expected array length %d; got TOML array of length %d", 300 | rv.Len(), sliceLen) 301 | } 302 | return md.unifySliceArray(datav, rv) 303 | } 304 | 305 | func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error { 306 | datav := reflect.ValueOf(data) 307 | if datav.Kind() != reflect.Slice { 308 | return badtype("slice", data) 309 | } 310 | sliceLen := datav.Len() 311 | if rv.IsNil() { 312 | rv.Set(reflect.MakeSlice(rv.Type(), sliceLen, sliceLen)) 313 | } 314 | return md.unifySliceArray(datav, rv) 315 | } 316 | 317 | func (md *MetaData) unifySliceArray(data, rv reflect.Value) error { 318 | sliceLen := data.Len() 319 | for i := 0; i < sliceLen; i++ { 320 | v := data.Index(i).Interface() 321 | sliceval := indirect(rv.Index(i)) 322 | if err := md.unify(v, sliceval); err != nil { 323 | return err 324 | } 325 | } 326 | return nil 327 | } 328 | 329 | func (md *MetaData) unifyDatetime(data interface{}, rv reflect.Value) error { 330 | if _, ok := data.(time.Time); ok { 331 | rv.Set(reflect.ValueOf(data)) 332 | return nil 333 | } 334 | return badtype("time.Time", data) 335 | } 336 | 337 | func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error { 338 | if s, ok := data.(string); ok { 339 | rv.SetString(s) 340 | return nil 341 | } 342 | return badtype("string", data) 343 | } 344 | 345 | func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error { 346 | if num, ok := data.(float64); ok { 347 | switch rv.Kind() { 348 | case reflect.Float32: 349 | fallthrough 350 | case reflect.Float64: 351 | rv.SetFloat(num) 352 | default: 353 | panic("bug") 354 | } 355 | return nil 356 | } 357 | return badtype("float", data) 358 | } 359 | 360 | func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error { 361 | if num, ok := data.(int64); ok { 362 | if rv.Kind() >= reflect.Int && rv.Kind() <= reflect.Int64 { 363 | switch rv.Kind() { 364 | case reflect.Int, reflect.Int64: 365 | // No bounds checking necessary. 366 | case reflect.Int8: 367 | if num < math.MinInt8 || num > math.MaxInt8 { 368 | return e("Value '%d' is out of range for int8.", num) 369 | } 370 | case reflect.Int16: 371 | if num < math.MinInt16 || num > math.MaxInt16 { 372 | return e("Value '%d' is out of range for int16.", num) 373 | } 374 | case reflect.Int32: 375 | if num < math.MinInt32 || num > math.MaxInt32 { 376 | return e("Value '%d' is out of range for int32.", num) 377 | } 378 | } 379 | rv.SetInt(num) 380 | } else if rv.Kind() >= reflect.Uint && rv.Kind() <= reflect.Uint64 { 381 | unum := uint64(num) 382 | switch rv.Kind() { 383 | case reflect.Uint, reflect.Uint64: 384 | // No bounds checking necessary. 385 | case reflect.Uint8: 386 | if num < 0 || unum > math.MaxUint8 { 387 | return e("Value '%d' is out of range for uint8.", num) 388 | } 389 | case reflect.Uint16: 390 | if num < 0 || unum > math.MaxUint16 { 391 | return e("Value '%d' is out of range for uint16.", num) 392 | } 393 | case reflect.Uint32: 394 | if num < 0 || unum > math.MaxUint32 { 395 | return e("Value '%d' is out of range for uint32.", num) 396 | } 397 | } 398 | rv.SetUint(unum) 399 | } else { 400 | panic("unreachable") 401 | } 402 | return nil 403 | } 404 | return badtype("integer", data) 405 | } 406 | 407 | func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error { 408 | if b, ok := data.(bool); ok { 409 | rv.SetBool(b) 410 | return nil 411 | } 412 | return badtype("boolean", data) 413 | } 414 | 415 | func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error { 416 | rv.Set(reflect.ValueOf(data)) 417 | return nil 418 | } 419 | 420 | func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error { 421 | var s string 422 | switch sdata := data.(type) { 423 | case TextMarshaler: 424 | text, err := sdata.MarshalText() 425 | if err != nil { 426 | return err 427 | } 428 | s = string(text) 429 | case fmt.Stringer: 430 | s = sdata.String() 431 | case string: 432 | s = sdata 433 | case bool: 434 | s = fmt.Sprintf("%v", sdata) 435 | case int64: 436 | s = fmt.Sprintf("%d", sdata) 437 | case float64: 438 | s = fmt.Sprintf("%f", sdata) 439 | default: 440 | return badtype("primitive (string-like)", data) 441 | } 442 | if err := v.UnmarshalText([]byte(s)); err != nil { 443 | return err 444 | } 445 | return nil 446 | } 447 | 448 | // rvalue returns a reflect.Value of `v`. All pointers are resolved. 449 | func rvalue(v interface{}) reflect.Value { 450 | return indirect(reflect.ValueOf(v)) 451 | } 452 | 453 | // indirect returns the value pointed to by a pointer. 454 | // Pointers are followed until the value is not a pointer. 455 | // New values are allocated for each nil pointer. 456 | // 457 | // An exception to this rule is if the value satisfies an interface of 458 | // interest to us (like encoding.TextUnmarshaler). 459 | func indirect(v reflect.Value) reflect.Value { 460 | if v.Kind() != reflect.Ptr { 461 | if v.CanAddr() { 462 | pv := v.Addr() 463 | if _, ok := pv.Interface().(TextUnmarshaler); ok { 464 | return pv 465 | } 466 | } 467 | return v 468 | } 469 | if v.IsNil() { 470 | v.Set(reflect.New(v.Type().Elem())) 471 | } 472 | return indirect(reflect.Indirect(v)) 473 | } 474 | 475 | func isUnifiable(rv reflect.Value) bool { 476 | if rv.CanSet() { 477 | return true 478 | } 479 | if _, ok := rv.Interface().(TextUnmarshaler); ok { 480 | return true 481 | } 482 | return false 483 | } 484 | 485 | func badtype(expected string, data interface{}) error { 486 | return e("Expected %s but found '%T'.", expected, data) 487 | } 488 | 489 | func mismatch(user reflect.Value, expected string, data interface{}) error { 490 | return e("Type mismatch for %s. Expected %s but found '%T'.", 491 | user.Type().String(), expected, data) 492 | } 493 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/BurntSushi/toml/decode_meta.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import "strings" 4 | 5 | // MetaData allows access to meta information about TOML data that may not 6 | // be inferrable via reflection. In particular, whether a key has been defined 7 | // and the TOML type of a key. 8 | type MetaData struct { 9 | mapping map[string]interface{} 10 | types map[string]tomlType 11 | keys []Key 12 | decoded map[string]bool 13 | context Key // Used only during decoding. 14 | } 15 | 16 | // IsDefined returns true if the key given exists in the TOML data. The key 17 | // should be specified hierarchially. e.g., 18 | // 19 | // // access the TOML key 'a.b.c' 20 | // IsDefined("a", "b", "c") 21 | // 22 | // IsDefined will return false if an empty key given. Keys are case sensitive. 23 | func (md *MetaData) IsDefined(key ...string) bool { 24 | if len(key) == 0 { 25 | return false 26 | } 27 | 28 | var hash map[string]interface{} 29 | var ok bool 30 | var hashOrVal interface{} = md.mapping 31 | for _, k := range key { 32 | if hash, ok = hashOrVal.(map[string]interface{}); !ok { 33 | return false 34 | } 35 | if hashOrVal, ok = hash[k]; !ok { 36 | return false 37 | } 38 | } 39 | return true 40 | } 41 | 42 | // Type returns a string representation of the type of the key specified. 43 | // 44 | // Type will return the empty string if given an empty key or a key that 45 | // does not exist. Keys are case sensitive. 46 | func (md *MetaData) Type(key ...string) string { 47 | fullkey := strings.Join(key, ".") 48 | if typ, ok := md.types[fullkey]; ok { 49 | return typ.typeString() 50 | } 51 | return "" 52 | } 53 | 54 | // Key is the type of any TOML key, including key groups. Use (MetaData).Keys 55 | // to get values of this type. 56 | type Key []string 57 | 58 | func (k Key) String() string { 59 | return strings.Join(k, ".") 60 | } 61 | 62 | func (k Key) maybeQuotedAll() string { 63 | var ss []string 64 | for i := range k { 65 | ss = append(ss, k.maybeQuoted(i)) 66 | } 67 | return strings.Join(ss, ".") 68 | } 69 | 70 | func (k Key) maybeQuoted(i int) string { 71 | quote := false 72 | for _, c := range k[i] { 73 | if !isBareKeyChar(c) { 74 | quote = true 75 | break 76 | } 77 | } 78 | if quote { 79 | return "\"" + strings.Replace(k[i], "\"", "\\\"", -1) + "\"" 80 | } else { 81 | return k[i] 82 | } 83 | } 84 | 85 | func (k Key) add(piece string) Key { 86 | newKey := make(Key, len(k)+1) 87 | copy(newKey, k) 88 | newKey[len(k)] = piece 89 | return newKey 90 | } 91 | 92 | // Keys returns a slice of every key in the TOML data, including key groups. 93 | // Each key is itself a slice, where the first element is the top of the 94 | // hierarchy and the last is the most specific. 95 | // 96 | // The list will have the same order as the keys appeared in the TOML data. 97 | // 98 | // All keys returned are non-empty. 99 | func (md *MetaData) Keys() []Key { 100 | return md.keys 101 | } 102 | 103 | // Undecoded returns all keys that have not been decoded in the order in which 104 | // they appear in the original TOML document. 105 | // 106 | // This includes keys that haven't been decoded because of a Primitive value. 107 | // Once the Primitive value is decoded, the keys will be considered decoded. 108 | // 109 | // Also note that decoding into an empty interface will result in no decoding, 110 | // and so no keys will be considered decoded. 111 | // 112 | // In this sense, the Undecoded keys correspond to keys in the TOML document 113 | // that do not have a concrete type in your representation. 114 | func (md *MetaData) Undecoded() []Key { 115 | undecoded := make([]Key, 0, len(md.keys)) 116 | for _, key := range md.keys { 117 | if !md.decoded[key.String()] { 118 | undecoded = append(undecoded, key) 119 | } 120 | } 121 | return undecoded 122 | } 123 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/BurntSushi/toml/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package toml provides facilities for decoding and encoding TOML configuration 3 | files via reflection. There is also support for delaying decoding with 4 | the Primitive type, and querying the set of keys in a TOML document with the 5 | MetaData type. 6 | 7 | The specification implemented: https://github.com/mojombo/toml 8 | 9 | The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify 10 | whether a file is a valid TOML document. It can also be used to print the 11 | type of each key in a TOML document. 12 | 13 | Testing 14 | 15 | There are two important types of tests used for this package. The first is 16 | contained inside '*_test.go' files and uses the standard Go unit testing 17 | framework. These tests are primarily devoted to holistically testing the 18 | decoder and encoder. 19 | 20 | The second type of testing is used to verify the implementation's adherence 21 | to the TOML specification. These tests have been factored into their own 22 | project: https://github.com/BurntSushi/toml-test 23 | 24 | The reason the tests are in a separate project is so that they can be used by 25 | any implementation of TOML. Namely, it is language agnostic. 26 | */ 27 | package toml 28 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/BurntSushi/toml/encode_test.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "net" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func TestEncodeRoundTrip(t *testing.T) { 13 | type Config struct { 14 | Age int 15 | Cats []string 16 | Pi float64 17 | Perfection []int 18 | DOB time.Time 19 | Ipaddress net.IP 20 | } 21 | 22 | var inputs = Config{ 23 | 13, 24 | []string{"one", "two", "three"}, 25 | 3.145, 26 | []int{11, 2, 3, 4}, 27 | time.Now(), 28 | net.ParseIP("192.168.59.254"), 29 | } 30 | 31 | var firstBuffer bytes.Buffer 32 | e := NewEncoder(&firstBuffer) 33 | err := e.Encode(inputs) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | var outputs Config 38 | if _, err := Decode(firstBuffer.String(), &outputs); err != nil { 39 | log.Printf("Could not decode:\n-----\n%s\n-----\n", 40 | firstBuffer.String()) 41 | t.Fatal(err) 42 | } 43 | 44 | // could test each value individually, but I'm lazy 45 | var secondBuffer bytes.Buffer 46 | e2 := NewEncoder(&secondBuffer) 47 | err = e2.Encode(outputs) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | if firstBuffer.String() != secondBuffer.String() { 52 | t.Error( 53 | firstBuffer.String(), 54 | "\n\n is not identical to\n\n", 55 | secondBuffer.String()) 56 | } 57 | } 58 | 59 | // XXX(burntsushi) 60 | // I think these tests probably should be removed. They are good, but they 61 | // ought to be obsolete by toml-test. 62 | func TestEncode(t *testing.T) { 63 | type Embedded struct { 64 | Int int `toml:"_int"` 65 | } 66 | type NonStruct int 67 | 68 | date := time.Date(2014, 5, 11, 20, 30, 40, 0, time.FixedZone("IST", 3600)) 69 | dateStr := "2014-05-11T19:30:40Z" 70 | 71 | tests := map[string]struct { 72 | input interface{} 73 | wantOutput string 74 | wantError error 75 | }{ 76 | "bool field": { 77 | input: struct { 78 | BoolTrue bool 79 | BoolFalse bool 80 | }{true, false}, 81 | wantOutput: "BoolTrue = true\nBoolFalse = false\n", 82 | }, 83 | "int fields": { 84 | input: struct { 85 | Int int 86 | Int8 int8 87 | Int16 int16 88 | Int32 int32 89 | Int64 int64 90 | }{1, 2, 3, 4, 5}, 91 | wantOutput: "Int = 1\nInt8 = 2\nInt16 = 3\nInt32 = 4\nInt64 = 5\n", 92 | }, 93 | "uint fields": { 94 | input: struct { 95 | Uint uint 96 | Uint8 uint8 97 | Uint16 uint16 98 | Uint32 uint32 99 | Uint64 uint64 100 | }{1, 2, 3, 4, 5}, 101 | wantOutput: "Uint = 1\nUint8 = 2\nUint16 = 3\nUint32 = 4" + 102 | "\nUint64 = 5\n", 103 | }, 104 | "float fields": { 105 | input: struct { 106 | Float32 float32 107 | Float64 float64 108 | }{1.5, 2.5}, 109 | wantOutput: "Float32 = 1.5\nFloat64 = 2.5\n", 110 | }, 111 | "string field": { 112 | input: struct{ String string }{"foo"}, 113 | wantOutput: "String = \"foo\"\n", 114 | }, 115 | "string field and unexported field": { 116 | input: struct { 117 | String string 118 | unexported int 119 | }{"foo", 0}, 120 | wantOutput: "String = \"foo\"\n", 121 | }, 122 | "datetime field in UTC": { 123 | input: struct{ Date time.Time }{date}, 124 | wantOutput: fmt.Sprintf("Date = %s\n", dateStr), 125 | }, 126 | "datetime field as primitive": { 127 | // Using a map here to fail if isStructOrMap() returns true for 128 | // time.Time. 129 | input: map[string]interface{}{ 130 | "Date": date, 131 | "Int": 1, 132 | }, 133 | wantOutput: fmt.Sprintf("Date = %s\nInt = 1\n", dateStr), 134 | }, 135 | "array fields": { 136 | input: struct { 137 | IntArray0 [0]int 138 | IntArray3 [3]int 139 | }{[0]int{}, [3]int{1, 2, 3}}, 140 | wantOutput: "IntArray0 = []\nIntArray3 = [1, 2, 3]\n", 141 | }, 142 | "slice fields": { 143 | input: struct{ IntSliceNil, IntSlice0, IntSlice3 []int }{ 144 | nil, []int{}, []int{1, 2, 3}, 145 | }, 146 | wantOutput: "IntSlice0 = []\nIntSlice3 = [1, 2, 3]\n", 147 | }, 148 | "datetime slices": { 149 | input: struct{ DatetimeSlice []time.Time }{ 150 | []time.Time{date, date}, 151 | }, 152 | wantOutput: fmt.Sprintf("DatetimeSlice = [%s, %s]\n", 153 | dateStr, dateStr), 154 | }, 155 | "nested arrays and slices": { 156 | input: struct { 157 | SliceOfArrays [][2]int 158 | ArrayOfSlices [2][]int 159 | SliceOfArraysOfSlices [][2][]int 160 | ArrayOfSlicesOfArrays [2][][2]int 161 | SliceOfMixedArrays [][2]interface{} 162 | ArrayOfMixedSlices [2][]interface{} 163 | }{ 164 | [][2]int{{1, 2}, {3, 4}}, 165 | [2][]int{{1, 2}, {3, 4}}, 166 | [][2][]int{ 167 | { 168 | {1, 2}, {3, 4}, 169 | }, 170 | { 171 | {5, 6}, {7, 8}, 172 | }, 173 | }, 174 | [2][][2]int{ 175 | { 176 | {1, 2}, {3, 4}, 177 | }, 178 | { 179 | {5, 6}, {7, 8}, 180 | }, 181 | }, 182 | [][2]interface{}{ 183 | {1, 2}, {"a", "b"}, 184 | }, 185 | [2][]interface{}{ 186 | {1, 2}, {"a", "b"}, 187 | }, 188 | }, 189 | wantOutput: `SliceOfArrays = [[1, 2], [3, 4]] 190 | ArrayOfSlices = [[1, 2], [3, 4]] 191 | SliceOfArraysOfSlices = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] 192 | ArrayOfSlicesOfArrays = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] 193 | SliceOfMixedArrays = [[1, 2], ["a", "b"]] 194 | ArrayOfMixedSlices = [[1, 2], ["a", "b"]] 195 | `, 196 | }, 197 | "empty slice": { 198 | input: struct{ Empty []interface{} }{[]interface{}{}}, 199 | wantOutput: "Empty = []\n", 200 | }, 201 | "(error) slice with element type mismatch (string and integer)": { 202 | input: struct{ Mixed []interface{} }{[]interface{}{1, "a"}}, 203 | wantError: errArrayMixedElementTypes, 204 | }, 205 | "(error) slice with element type mismatch (integer and float)": { 206 | input: struct{ Mixed []interface{} }{[]interface{}{1, 2.5}}, 207 | wantError: errArrayMixedElementTypes, 208 | }, 209 | "slice with elems of differing Go types, same TOML types": { 210 | input: struct { 211 | MixedInts []interface{} 212 | MixedFloats []interface{} 213 | }{ 214 | []interface{}{ 215 | int(1), int8(2), int16(3), int32(4), int64(5), 216 | uint(1), uint8(2), uint16(3), uint32(4), uint64(5), 217 | }, 218 | []interface{}{float32(1.5), float64(2.5)}, 219 | }, 220 | wantOutput: "MixedInts = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]\n" + 221 | "MixedFloats = [1.5, 2.5]\n", 222 | }, 223 | "(error) slice w/ element type mismatch (one is nested array)": { 224 | input: struct{ Mixed []interface{} }{ 225 | []interface{}{1, []interface{}{2}}, 226 | }, 227 | wantError: errArrayMixedElementTypes, 228 | }, 229 | "(error) slice with 1 nil element": { 230 | input: struct{ NilElement1 []interface{} }{[]interface{}{nil}}, 231 | wantError: errArrayNilElement, 232 | }, 233 | "(error) slice with 1 nil element (and other non-nil elements)": { 234 | input: struct{ NilElement []interface{} }{ 235 | []interface{}{1, nil}, 236 | }, 237 | wantError: errArrayNilElement, 238 | }, 239 | "simple map": { 240 | input: map[string]int{"a": 1, "b": 2}, 241 | wantOutput: "a = 1\nb = 2\n", 242 | }, 243 | "map with interface{} value type": { 244 | input: map[string]interface{}{"a": 1, "b": "c"}, 245 | wantOutput: "a = 1\nb = \"c\"\n", 246 | }, 247 | "map with interface{} value type, some of which are structs": { 248 | input: map[string]interface{}{ 249 | "a": struct{ Int int }{2}, 250 | "b": 1, 251 | }, 252 | wantOutput: "b = 1\n\n[a]\n Int = 2\n", 253 | }, 254 | "nested map": { 255 | input: map[string]map[string]int{ 256 | "a": {"b": 1}, 257 | "c": {"d": 2}, 258 | }, 259 | wantOutput: "[a]\n b = 1\n\n[c]\n d = 2\n", 260 | }, 261 | "nested struct": { 262 | input: struct{ Struct struct{ Int int } }{ 263 | struct{ Int int }{1}, 264 | }, 265 | wantOutput: "[Struct]\n Int = 1\n", 266 | }, 267 | "nested struct and non-struct field": { 268 | input: struct { 269 | Struct struct{ Int int } 270 | Bool bool 271 | }{struct{ Int int }{1}, true}, 272 | wantOutput: "Bool = true\n\n[Struct]\n Int = 1\n", 273 | }, 274 | "2 nested structs": { 275 | input: struct{ Struct1, Struct2 struct{ Int int } }{ 276 | struct{ Int int }{1}, struct{ Int int }{2}, 277 | }, 278 | wantOutput: "[Struct1]\n Int = 1\n\n[Struct2]\n Int = 2\n", 279 | }, 280 | "deeply nested structs": { 281 | input: struct { 282 | Struct1, Struct2 struct{ Struct3 *struct{ Int int } } 283 | }{ 284 | struct{ Struct3 *struct{ Int int } }{&struct{ Int int }{1}}, 285 | struct{ Struct3 *struct{ Int int } }{nil}, 286 | }, 287 | wantOutput: "[Struct1]\n [Struct1.Struct3]\n Int = 1" + 288 | "\n\n[Struct2]\n", 289 | }, 290 | "nested struct with nil struct elem": { 291 | input: struct { 292 | Struct struct{ Inner *struct{ Int int } } 293 | }{ 294 | struct{ Inner *struct{ Int int } }{nil}, 295 | }, 296 | wantOutput: "[Struct]\n", 297 | }, 298 | "nested struct with no fields": { 299 | input: struct { 300 | Struct struct{ Inner struct{} } 301 | }{ 302 | struct{ Inner struct{} }{struct{}{}}, 303 | }, 304 | wantOutput: "[Struct]\n [Struct.Inner]\n", 305 | }, 306 | "struct with tags": { 307 | input: struct { 308 | Struct struct { 309 | Int int `toml:"_int"` 310 | } `toml:"_struct"` 311 | Bool bool `toml:"_bool"` 312 | }{ 313 | struct { 314 | Int int `toml:"_int"` 315 | }{1}, true, 316 | }, 317 | wantOutput: "_bool = true\n\n[_struct]\n _int = 1\n", 318 | }, 319 | "embedded struct": { 320 | input: struct{ Embedded }{Embedded{1}}, 321 | wantOutput: "_int = 1\n", 322 | }, 323 | "embedded *struct": { 324 | input: struct{ *Embedded }{&Embedded{1}}, 325 | wantOutput: "_int = 1\n", 326 | }, 327 | "nested embedded struct": { 328 | input: struct { 329 | Struct struct{ Embedded } `toml:"_struct"` 330 | }{struct{ Embedded }{Embedded{1}}}, 331 | wantOutput: "[_struct]\n _int = 1\n", 332 | }, 333 | "nested embedded *struct": { 334 | input: struct { 335 | Struct struct{ *Embedded } `toml:"_struct"` 336 | }{struct{ *Embedded }{&Embedded{1}}}, 337 | wantOutput: "[_struct]\n _int = 1\n", 338 | }, 339 | "array of tables": { 340 | input: struct { 341 | Structs []*struct{ Int int } `toml:"struct"` 342 | }{ 343 | []*struct{ Int int }{{1}, {3}}, 344 | }, 345 | wantOutput: "[[struct]]\n Int = 1\n\n[[struct]]\n Int = 3\n", 346 | }, 347 | "array of tables order": { 348 | input: map[string]interface{}{ 349 | "map": map[string]interface{}{ 350 | "zero": 5, 351 | "arr": []map[string]int{ 352 | map[string]int{ 353 | "friend": 5, 354 | }, 355 | }, 356 | }, 357 | }, 358 | wantOutput: "[map]\n zero = 5\n\n [[map.arr]]\n friend = 5\n", 359 | }, 360 | "(error) top-level slice": { 361 | input: []struct{ Int int }{{1}, {2}, {3}}, 362 | wantError: errNoKey, 363 | }, 364 | "(error) slice of slice": { 365 | input: struct { 366 | Slices [][]struct{ Int int } 367 | }{ 368 | [][]struct{ Int int }{{{1}}, {{2}}, {{3}}}, 369 | }, 370 | wantError: errArrayNoTable, 371 | }, 372 | "(error) map no string key": { 373 | input: map[int]string{1: ""}, 374 | wantError: errNonString, 375 | }, 376 | "(error) anonymous non-struct": { 377 | input: struct{ NonStruct }{5}, 378 | wantError: errAnonNonStruct, 379 | }, 380 | "(error) empty key name": { 381 | input: map[string]int{"": 1}, 382 | wantError: errAnything, 383 | }, 384 | "(error) empty map name": { 385 | input: map[string]interface{}{ 386 | "": map[string]int{"v": 1}, 387 | }, 388 | wantError: errAnything, 389 | }, 390 | } 391 | for label, test := range tests { 392 | encodeExpected(t, label, test.input, test.wantOutput, test.wantError) 393 | } 394 | } 395 | 396 | func TestEncodeNestedTableArrays(t *testing.T) { 397 | type song struct { 398 | Name string `toml:"name"` 399 | } 400 | type album struct { 401 | Name string `toml:"name"` 402 | Songs []song `toml:"songs"` 403 | } 404 | type springsteen struct { 405 | Albums []album `toml:"albums"` 406 | } 407 | value := springsteen{ 408 | []album{ 409 | {"Born to Run", 410 | []song{{"Jungleland"}, {"Meeting Across the River"}}}, 411 | {"Born in the USA", 412 | []song{{"Glory Days"}, {"Dancing in the Dark"}}}, 413 | }, 414 | } 415 | expected := `[[albums]] 416 | name = "Born to Run" 417 | 418 | [[albums.songs]] 419 | name = "Jungleland" 420 | 421 | [[albums.songs]] 422 | name = "Meeting Across the River" 423 | 424 | [[albums]] 425 | name = "Born in the USA" 426 | 427 | [[albums.songs]] 428 | name = "Glory Days" 429 | 430 | [[albums.songs]] 431 | name = "Dancing in the Dark" 432 | ` 433 | encodeExpected(t, "nested table arrays", value, expected, nil) 434 | } 435 | 436 | func TestEncodeArrayHashWithNormalHashOrder(t *testing.T) { 437 | type Alpha struct { 438 | V int 439 | } 440 | type Beta struct { 441 | V int 442 | } 443 | type Conf struct { 444 | V int 445 | A Alpha 446 | B []Beta 447 | } 448 | 449 | val := Conf{ 450 | V: 1, 451 | A: Alpha{2}, 452 | B: []Beta{{3}}, 453 | } 454 | expected := "V = 1\n\n[A]\n V = 2\n\n[[B]]\n V = 3\n" 455 | encodeExpected(t, "array hash with normal hash order", val, expected, nil) 456 | } 457 | 458 | func TestEncodeWithOmitEmpty(t *testing.T) { 459 | type simple struct { 460 | User string `toml:"user"` 461 | Pass string `toml:"password,omitempty"` 462 | } 463 | 464 | value := simple{"Testing", ""} 465 | expected := fmt.Sprintf("user = %q\n", value.User) 466 | encodeExpected(t, "simple with omitempty, is empty", value, expected, nil) 467 | value.Pass = "some password" 468 | expected = fmt.Sprintf("user = %q\npassword = %q\n", value.User, value.Pass) 469 | encodeExpected(t, "simple with omitempty, not empty", value, expected, nil) 470 | } 471 | 472 | func TestEncodeWithOmitZero(t *testing.T) { 473 | type simple struct { 474 | Number int `toml:"number,omitzero"` 475 | Real float64 `toml:"real,omitzero"` 476 | Unsigned uint `toml:"unsigned,omitzero"` 477 | } 478 | 479 | value := simple{0, 0.0, uint(0)} 480 | expected := "" 481 | 482 | encodeExpected(t, "simple with omitzero, all zero", value, expected, nil) 483 | 484 | value.Number = 10 485 | value.Real = 20 486 | value.Unsigned = 5 487 | expected = `number = 10 488 | real = 20.0 489 | unsigned = 5 490 | ` 491 | encodeExpected(t, "simple with omitzero, non-zero", value, expected, nil) 492 | } 493 | 494 | func encodeExpected( 495 | t *testing.T, label string, val interface{}, wantStr string, wantErr error, 496 | ) { 497 | var buf bytes.Buffer 498 | enc := NewEncoder(&buf) 499 | err := enc.Encode(val) 500 | if err != wantErr { 501 | if wantErr != nil { 502 | if wantErr == errAnything && err != nil { 503 | return 504 | } 505 | t.Errorf("%s: want Encode error %v, got %v", label, wantErr, err) 506 | } else { 507 | t.Errorf("%s: Encode failed: %s", label, err) 508 | } 509 | } 510 | if err != nil { 511 | return 512 | } 513 | if got := buf.String(); wantStr != got { 514 | t.Errorf("%s: want\n-----\n%q\n-----\nbut got\n-----\n%q\n-----\n", 515 | label, wantStr, got) 516 | } 517 | } 518 | 519 | func ExampleEncoder_Encode() { 520 | date, _ := time.Parse(time.RFC822, "14 Mar 10 18:00 UTC") 521 | var config = map[string]interface{}{ 522 | "date": date, 523 | "counts": []int{1, 1, 2, 3, 5, 8}, 524 | "hash": map[string]string{ 525 | "key1": "val1", 526 | "key2": "val2", 527 | }, 528 | } 529 | buf := new(bytes.Buffer) 530 | if err := NewEncoder(buf).Encode(config); err != nil { 531 | log.Fatal(err) 532 | } 533 | fmt.Println(buf.String()) 534 | 535 | // Output: 536 | // counts = [1, 1, 2, 3, 5, 8] 537 | // date = 2010-03-14T18:00:00Z 538 | // 539 | // [hash] 540 | // key1 = "val1" 541 | // key2 = "val2" 542 | } 543 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/BurntSushi/toml/encoding_types.go: -------------------------------------------------------------------------------- 1 | // +build go1.2 2 | 3 | package toml 4 | 5 | // In order to support Go 1.1, we define our own TextMarshaler and 6 | // TextUnmarshaler types. For Go 1.2+, we just alias them with the 7 | // standard library interfaces. 8 | 9 | import ( 10 | "encoding" 11 | ) 12 | 13 | // TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here 14 | // so that Go 1.1 can be supported. 15 | type TextMarshaler encoding.TextMarshaler 16 | 17 | // TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined 18 | // here so that Go 1.1 can be supported. 19 | type TextUnmarshaler encoding.TextUnmarshaler 20 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/BurntSushi/toml/encoding_types_1.1.go: -------------------------------------------------------------------------------- 1 | // +build !go1.2 2 | 3 | package toml 4 | 5 | // These interfaces were introduced in Go 1.2, so we add them manually when 6 | // compiling for Go 1.1. 7 | 8 | // TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here 9 | // so that Go 1.1 can be supported. 10 | type TextMarshaler interface { 11 | MarshalText() (text []byte, err error) 12 | } 13 | 14 | // TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined 15 | // here so that Go 1.1 can be supported. 16 | type TextUnmarshaler interface { 17 | UnmarshalText(text []byte) error 18 | } 19 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/BurntSushi/toml/parse.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strconv" 7 | "strings" 8 | "time" 9 | "unicode" 10 | "unicode/utf8" 11 | ) 12 | 13 | type parser struct { 14 | mapping map[string]interface{} 15 | types map[string]tomlType 16 | lx *lexer 17 | 18 | // A list of keys in the order that they appear in the TOML data. 19 | ordered []Key 20 | 21 | // the full key for the current hash in scope 22 | context Key 23 | 24 | // the base key name for everything except hashes 25 | currentKey string 26 | 27 | // rough approximation of line number 28 | approxLine int 29 | 30 | // A map of 'key.group.names' to whether they were created implicitly. 31 | implicits map[string]bool 32 | } 33 | 34 | type parseError string 35 | 36 | func (pe parseError) Error() string { 37 | return string(pe) 38 | } 39 | 40 | func parse(data string) (p *parser, err error) { 41 | defer func() { 42 | if r := recover(); r != nil { 43 | var ok bool 44 | if err, ok = r.(parseError); ok { 45 | return 46 | } 47 | panic(r) 48 | } 49 | }() 50 | 51 | p = &parser{ 52 | mapping: make(map[string]interface{}), 53 | types: make(map[string]tomlType), 54 | lx: lex(data), 55 | ordered: make([]Key, 0), 56 | implicits: make(map[string]bool), 57 | } 58 | for { 59 | item := p.next() 60 | if item.typ == itemEOF { 61 | break 62 | } 63 | p.topLevel(item) 64 | } 65 | 66 | return p, nil 67 | } 68 | 69 | func (p *parser) panicf(format string, v ...interface{}) { 70 | msg := fmt.Sprintf("Near line %d (last key parsed '%s'): %s", 71 | p.approxLine, p.current(), fmt.Sprintf(format, v...)) 72 | panic(parseError(msg)) 73 | } 74 | 75 | func (p *parser) next() item { 76 | it := p.lx.nextItem() 77 | if it.typ == itemError { 78 | p.panicf("%s", it.val) 79 | } 80 | return it 81 | } 82 | 83 | func (p *parser) bug(format string, v ...interface{}) { 84 | log.Fatalf("BUG: %s\n\n", fmt.Sprintf(format, v...)) 85 | } 86 | 87 | func (p *parser) expect(typ itemType) item { 88 | it := p.next() 89 | p.assertEqual(typ, it.typ) 90 | return it 91 | } 92 | 93 | func (p *parser) assertEqual(expected, got itemType) { 94 | if expected != got { 95 | p.bug("Expected '%s' but got '%s'.", expected, got) 96 | } 97 | } 98 | 99 | func (p *parser) topLevel(item item) { 100 | switch item.typ { 101 | case itemCommentStart: 102 | p.approxLine = item.line 103 | p.expect(itemText) 104 | case itemTableStart: 105 | kg := p.next() 106 | p.approxLine = kg.line 107 | 108 | var key Key 109 | for ; kg.typ != itemTableEnd && kg.typ != itemEOF; kg = p.next() { 110 | key = append(key, p.keyString(kg)) 111 | } 112 | p.assertEqual(itemTableEnd, kg.typ) 113 | 114 | p.establishContext(key, false) 115 | p.setType("", tomlHash) 116 | p.ordered = append(p.ordered, key) 117 | case itemArrayTableStart: 118 | kg := p.next() 119 | p.approxLine = kg.line 120 | 121 | var key Key 122 | for ; kg.typ != itemArrayTableEnd && kg.typ != itemEOF; kg = p.next() { 123 | key = append(key, p.keyString(kg)) 124 | } 125 | p.assertEqual(itemArrayTableEnd, kg.typ) 126 | 127 | p.establishContext(key, true) 128 | p.setType("", tomlArrayHash) 129 | p.ordered = append(p.ordered, key) 130 | case itemKeyStart: 131 | kname := p.next() 132 | p.approxLine = kname.line 133 | p.currentKey = p.keyString(kname) 134 | 135 | val, typ := p.value(p.next()) 136 | p.setValue(p.currentKey, val) 137 | p.setType(p.currentKey, typ) 138 | p.ordered = append(p.ordered, p.context.add(p.currentKey)) 139 | p.currentKey = "" 140 | default: 141 | p.bug("Unexpected type at top level: %s", item.typ) 142 | } 143 | } 144 | 145 | // Gets a string for a key (or part of a key in a table name). 146 | func (p *parser) keyString(it item) string { 147 | switch it.typ { 148 | case itemText: 149 | return it.val 150 | case itemString, itemMultilineString, 151 | itemRawString, itemRawMultilineString: 152 | s, _ := p.value(it) 153 | return s.(string) 154 | default: 155 | p.bug("Unexpected key type: %s", it.typ) 156 | panic("unreachable") 157 | } 158 | } 159 | 160 | // value translates an expected value from the lexer into a Go value wrapped 161 | // as an empty interface. 162 | func (p *parser) value(it item) (interface{}, tomlType) { 163 | switch it.typ { 164 | case itemString: 165 | return p.replaceEscapes(it.val), p.typeOfPrimitive(it) 166 | case itemMultilineString: 167 | trimmed := stripFirstNewline(stripEscapedWhitespace(it.val)) 168 | return p.replaceEscapes(trimmed), p.typeOfPrimitive(it) 169 | case itemRawString: 170 | return it.val, p.typeOfPrimitive(it) 171 | case itemRawMultilineString: 172 | return stripFirstNewline(it.val), p.typeOfPrimitive(it) 173 | case itemBool: 174 | switch it.val { 175 | case "true": 176 | return true, p.typeOfPrimitive(it) 177 | case "false": 178 | return false, p.typeOfPrimitive(it) 179 | } 180 | p.bug("Expected boolean value, but got '%s'.", it.val) 181 | case itemInteger: 182 | num, err := strconv.ParseInt(it.val, 10, 64) 183 | if err != nil { 184 | // See comment below for floats describing why we make a 185 | // distinction between a bug and a user error. 186 | if e, ok := err.(*strconv.NumError); ok && 187 | e.Err == strconv.ErrRange { 188 | 189 | p.panicf("Integer '%s' is out of the range of 64-bit "+ 190 | "signed integers.", it.val) 191 | } else { 192 | p.bug("Expected integer value, but got '%s'.", it.val) 193 | } 194 | } 195 | return num, p.typeOfPrimitive(it) 196 | case itemFloat: 197 | num, err := strconv.ParseFloat(it.val, 64) 198 | if err != nil { 199 | // Distinguish float values. Normally, it'd be a bug if the lexer 200 | // provides an invalid float, but it's possible that the float is 201 | // out of range of valid values (which the lexer cannot determine). 202 | // So mark the former as a bug but the latter as a legitimate user 203 | // error. 204 | // 205 | // This is also true for integers. 206 | if e, ok := err.(*strconv.NumError); ok && 207 | e.Err == strconv.ErrRange { 208 | 209 | p.panicf("Float '%s' is out of the range of 64-bit "+ 210 | "IEEE-754 floating-point numbers.", it.val) 211 | } else { 212 | p.bug("Expected float value, but got '%s'.", it.val) 213 | } 214 | } 215 | return num, p.typeOfPrimitive(it) 216 | case itemDatetime: 217 | t, err := time.Parse("2006-01-02T15:04:05Z", it.val) 218 | if err != nil { 219 | p.bug("Expected Zulu formatted DateTime, but got '%s'.", it.val) 220 | } 221 | return t, p.typeOfPrimitive(it) 222 | case itemArray: 223 | array := make([]interface{}, 0) 224 | types := make([]tomlType, 0) 225 | 226 | for it = p.next(); it.typ != itemArrayEnd; it = p.next() { 227 | if it.typ == itemCommentStart { 228 | p.expect(itemText) 229 | continue 230 | } 231 | 232 | val, typ := p.value(it) 233 | array = append(array, val) 234 | types = append(types, typ) 235 | } 236 | return array, p.typeOfArray(types) 237 | } 238 | p.bug("Unexpected value type: %s", it.typ) 239 | panic("unreachable") 240 | } 241 | 242 | // establishContext sets the current context of the parser, 243 | // where the context is either a hash or an array of hashes. Which one is 244 | // set depends on the value of the `array` parameter. 245 | // 246 | // Establishing the context also makes sure that the key isn't a duplicate, and 247 | // will create implicit hashes automatically. 248 | func (p *parser) establishContext(key Key, array bool) { 249 | var ok bool 250 | 251 | // Always start at the top level and drill down for our context. 252 | hashContext := p.mapping 253 | keyContext := make(Key, 0) 254 | 255 | // We only need implicit hashes for key[0:-1] 256 | for _, k := range key[0 : len(key)-1] { 257 | _, ok = hashContext[k] 258 | keyContext = append(keyContext, k) 259 | 260 | // No key? Make an implicit hash and move on. 261 | if !ok { 262 | p.addImplicit(keyContext) 263 | hashContext[k] = make(map[string]interface{}) 264 | } 265 | 266 | // If the hash context is actually an array of tables, then set 267 | // the hash context to the last element in that array. 268 | // 269 | // Otherwise, it better be a table, since this MUST be a key group (by 270 | // virtue of it not being the last element in a key). 271 | switch t := hashContext[k].(type) { 272 | case []map[string]interface{}: 273 | hashContext = t[len(t)-1] 274 | case map[string]interface{}: 275 | hashContext = t 276 | default: 277 | p.panicf("Key '%s' was already created as a hash.", keyContext) 278 | } 279 | } 280 | 281 | p.context = keyContext 282 | if array { 283 | // If this is the first element for this array, then allocate a new 284 | // list of tables for it. 285 | k := key[len(key)-1] 286 | if _, ok := hashContext[k]; !ok { 287 | hashContext[k] = make([]map[string]interface{}, 0, 5) 288 | } 289 | 290 | // Add a new table. But make sure the key hasn't already been used 291 | // for something else. 292 | if hash, ok := hashContext[k].([]map[string]interface{}); ok { 293 | hashContext[k] = append(hash, make(map[string]interface{})) 294 | } else { 295 | p.panicf("Key '%s' was already created and cannot be used as "+ 296 | "an array.", keyContext) 297 | } 298 | } else { 299 | p.setValue(key[len(key)-1], make(map[string]interface{})) 300 | } 301 | p.context = append(p.context, key[len(key)-1]) 302 | } 303 | 304 | // setValue sets the given key to the given value in the current context. 305 | // It will make sure that the key hasn't already been defined, account for 306 | // implicit key groups. 307 | func (p *parser) setValue(key string, value interface{}) { 308 | var tmpHash interface{} 309 | var ok bool 310 | 311 | hash := p.mapping 312 | keyContext := make(Key, 0) 313 | for _, k := range p.context { 314 | keyContext = append(keyContext, k) 315 | if tmpHash, ok = hash[k]; !ok { 316 | p.bug("Context for key '%s' has not been established.", keyContext) 317 | } 318 | switch t := tmpHash.(type) { 319 | case []map[string]interface{}: 320 | // The context is a table of hashes. Pick the most recent table 321 | // defined as the current hash. 322 | hash = t[len(t)-1] 323 | case map[string]interface{}: 324 | hash = t 325 | default: 326 | p.bug("Expected hash to have type 'map[string]interface{}', but "+ 327 | "it has '%T' instead.", tmpHash) 328 | } 329 | } 330 | keyContext = append(keyContext, key) 331 | 332 | if _, ok := hash[key]; ok { 333 | // Typically, if the given key has already been set, then we have 334 | // to raise an error since duplicate keys are disallowed. However, 335 | // it's possible that a key was previously defined implicitly. In this 336 | // case, it is allowed to be redefined concretely. (See the 337 | // `tests/valid/implicit-and-explicit-after.toml` test in `toml-test`.) 338 | // 339 | // But we have to make sure to stop marking it as an implicit. (So that 340 | // another redefinition provokes an error.) 341 | // 342 | // Note that since it has already been defined (as a hash), we don't 343 | // want to overwrite it. So our business is done. 344 | if p.isImplicit(keyContext) { 345 | p.removeImplicit(keyContext) 346 | return 347 | } 348 | 349 | // Otherwise, we have a concrete key trying to override a previous 350 | // key, which is *always* wrong. 351 | p.panicf("Key '%s' has already been defined.", keyContext) 352 | } 353 | hash[key] = value 354 | } 355 | 356 | // setType sets the type of a particular value at a given key. 357 | // It should be called immediately AFTER setValue. 358 | // 359 | // Note that if `key` is empty, then the type given will be applied to the 360 | // current context (which is either a table or an array of tables). 361 | func (p *parser) setType(key string, typ tomlType) { 362 | keyContext := make(Key, 0, len(p.context)+1) 363 | for _, k := range p.context { 364 | keyContext = append(keyContext, k) 365 | } 366 | if len(key) > 0 { // allow type setting for hashes 367 | keyContext = append(keyContext, key) 368 | } 369 | p.types[keyContext.String()] = typ 370 | } 371 | 372 | // addImplicit sets the given Key as having been created implicitly. 373 | func (p *parser) addImplicit(key Key) { 374 | p.implicits[key.String()] = true 375 | } 376 | 377 | // removeImplicit stops tagging the given key as having been implicitly 378 | // created. 379 | func (p *parser) removeImplicit(key Key) { 380 | p.implicits[key.String()] = false 381 | } 382 | 383 | // isImplicit returns true if the key group pointed to by the key was created 384 | // implicitly. 385 | func (p *parser) isImplicit(key Key) bool { 386 | return p.implicits[key.String()] 387 | } 388 | 389 | // current returns the full key name of the current context. 390 | func (p *parser) current() string { 391 | if len(p.currentKey) == 0 { 392 | return p.context.String() 393 | } 394 | if len(p.context) == 0 { 395 | return p.currentKey 396 | } 397 | return fmt.Sprintf("%s.%s", p.context, p.currentKey) 398 | } 399 | 400 | func stripFirstNewline(s string) string { 401 | if len(s) == 0 || s[0] != '\n' { 402 | return s 403 | } 404 | return s[1:len(s)] 405 | } 406 | 407 | func stripEscapedWhitespace(s string) string { 408 | esc := strings.Split(s, "\\\n") 409 | if len(esc) > 1 { 410 | for i := 1; i < len(esc); i++ { 411 | esc[i] = strings.TrimLeftFunc(esc[i], unicode.IsSpace) 412 | } 413 | } 414 | return strings.Join(esc, "") 415 | } 416 | 417 | func (p *parser) replaceEscapes(str string) string { 418 | var replaced []rune 419 | s := []byte(str) 420 | r := 0 421 | for r < len(s) { 422 | if s[r] != '\\' { 423 | c, size := utf8.DecodeRune(s[r:]) 424 | r += size 425 | replaced = append(replaced, c) 426 | continue 427 | } 428 | r += 1 429 | if r >= len(s) { 430 | p.bug("Escape sequence at end of string.") 431 | return "" 432 | } 433 | switch s[r] { 434 | default: 435 | p.bug("Expected valid escape code after \\, but got %q.", s[r]) 436 | return "" 437 | case 'b': 438 | replaced = append(replaced, rune(0x0008)) 439 | r += 1 440 | case 't': 441 | replaced = append(replaced, rune(0x0009)) 442 | r += 1 443 | case 'n': 444 | replaced = append(replaced, rune(0x000A)) 445 | r += 1 446 | case 'f': 447 | replaced = append(replaced, rune(0x000C)) 448 | r += 1 449 | case 'r': 450 | replaced = append(replaced, rune(0x000D)) 451 | r += 1 452 | case '"': 453 | replaced = append(replaced, rune(0x0022)) 454 | r += 1 455 | case '\\': 456 | replaced = append(replaced, rune(0x005C)) 457 | r += 1 458 | case 'u': 459 | // At this point, we know we have a Unicode escape of the form 460 | // `uXXXX` at [r, r+5). (Because the lexer guarantees this 461 | // for us.) 462 | escaped := p.asciiEscapeToUnicode(s[r+1 : r+5]) 463 | replaced = append(replaced, escaped) 464 | r += 5 465 | case 'U': 466 | // At this point, we know we have a Unicode escape of the form 467 | // `uXXXX` at [r, r+9). (Because the lexer guarantees this 468 | // for us.) 469 | escaped := p.asciiEscapeToUnicode(s[r+1 : r+9]) 470 | replaced = append(replaced, escaped) 471 | r += 9 472 | } 473 | } 474 | return string(replaced) 475 | } 476 | 477 | func (p *parser) asciiEscapeToUnicode(bs []byte) rune { 478 | s := string(bs) 479 | hex, err := strconv.ParseUint(strings.ToLower(s), 16, 32) 480 | if err != nil { 481 | p.bug("Could not parse '%s' as a hexadecimal number, but the "+ 482 | "lexer claims it's OK: %s", s, err) 483 | } 484 | 485 | // BUG(burntsushi) 486 | // I honestly don't understand how this works. I can't seem 487 | // to find a way to make this fail. I figured this would fail on invalid 488 | // UTF-8 characters like U+DCFF, but it doesn't. 489 | if !utf8.ValidString(string(rune(hex))) { 490 | p.panicf("Escaped character '\\u%s' is not valid UTF-8.", s) 491 | } 492 | return rune(hex) 493 | } 494 | 495 | func isStringType(ty itemType) bool { 496 | return ty == itemString || ty == itemMultilineString || 497 | ty == itemRawString || ty == itemRawMultilineString 498 | } 499 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/BurntSushi/toml/session.vim: -------------------------------------------------------------------------------- 1 | au BufWritePost *.go silent!make tags > /dev/null 2>&1 2 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/BurntSushi/toml/type_check.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | // tomlType represents any Go type that corresponds to a TOML type. 4 | // While the first draft of the TOML spec has a simplistic type system that 5 | // probably doesn't need this level of sophistication, we seem to be militating 6 | // toward adding real composite types. 7 | type tomlType interface { 8 | typeString() string 9 | } 10 | 11 | // typeEqual accepts any two types and returns true if they are equal. 12 | func typeEqual(t1, t2 tomlType) bool { 13 | if t1 == nil || t2 == nil { 14 | return false 15 | } 16 | return t1.typeString() == t2.typeString() 17 | } 18 | 19 | func typeIsHash(t tomlType) bool { 20 | return typeEqual(t, tomlHash) || typeEqual(t, tomlArrayHash) 21 | } 22 | 23 | type tomlBaseType string 24 | 25 | func (btype tomlBaseType) typeString() string { 26 | return string(btype) 27 | } 28 | 29 | func (btype tomlBaseType) String() string { 30 | return btype.typeString() 31 | } 32 | 33 | var ( 34 | tomlInteger tomlBaseType = "Integer" 35 | tomlFloat tomlBaseType = "Float" 36 | tomlDatetime tomlBaseType = "Datetime" 37 | tomlString tomlBaseType = "String" 38 | tomlBool tomlBaseType = "Bool" 39 | tomlArray tomlBaseType = "Array" 40 | tomlHash tomlBaseType = "Hash" 41 | tomlArrayHash tomlBaseType = "ArrayHash" 42 | ) 43 | 44 | // typeOfPrimitive returns a tomlType of any primitive value in TOML. 45 | // Primitive values are: Integer, Float, Datetime, String and Bool. 46 | // 47 | // Passing a lexer item other than the following will cause a BUG message 48 | // to occur: itemString, itemBool, itemInteger, itemFloat, itemDatetime. 49 | func (p *parser) typeOfPrimitive(lexItem item) tomlType { 50 | switch lexItem.typ { 51 | case itemInteger: 52 | return tomlInteger 53 | case itemFloat: 54 | return tomlFloat 55 | case itemDatetime: 56 | return tomlDatetime 57 | case itemString: 58 | return tomlString 59 | case itemMultilineString: 60 | return tomlString 61 | case itemRawString: 62 | return tomlString 63 | case itemRawMultilineString: 64 | return tomlString 65 | case itemBool: 66 | return tomlBool 67 | } 68 | p.bug("Cannot infer primitive type of lex item '%s'.", lexItem) 69 | panic("unreachable") 70 | } 71 | 72 | // typeOfArray returns a tomlType for an array given a list of types of its 73 | // values. 74 | // 75 | // In the current spec, if an array is homogeneous, then its type is always 76 | // "Array". If the array is not homogeneous, an error is generated. 77 | func (p *parser) typeOfArray(types []tomlType) tomlType { 78 | // Empty arrays are cool. 79 | if len(types) == 0 { 80 | return tomlArray 81 | } 82 | 83 | theType := types[0] 84 | for _, t := range types[1:] { 85 | if !typeEqual(theType, t) { 86 | p.panicf("Array contains values of type '%s' and '%s', but "+ 87 | "arrays must be homogeneous.", theType, t) 88 | } 89 | } 90 | return tomlArray 91 | } 92 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/BurntSushi/toml/type_fields.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | // Struct field handling is adapted from code in encoding/json: 4 | // 5 | // Copyright 2010 The Go Authors. All rights reserved. 6 | // Use of this source code is governed by a BSD-style 7 | // license that can be found in the Go distribution. 8 | 9 | import ( 10 | "reflect" 11 | "sort" 12 | "sync" 13 | ) 14 | 15 | // A field represents a single field found in a struct. 16 | type field struct { 17 | name string // the name of the field (`toml` tag included) 18 | tag bool // whether field has a `toml` tag 19 | index []int // represents the depth of an anonymous field 20 | typ reflect.Type // the type of the field 21 | } 22 | 23 | // byName sorts field by name, breaking ties with depth, 24 | // then breaking ties with "name came from toml tag", then 25 | // breaking ties with index sequence. 26 | type byName []field 27 | 28 | func (x byName) Len() int { return len(x) } 29 | 30 | func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 31 | 32 | func (x byName) Less(i, j int) bool { 33 | if x[i].name != x[j].name { 34 | return x[i].name < x[j].name 35 | } 36 | if len(x[i].index) != len(x[j].index) { 37 | return len(x[i].index) < len(x[j].index) 38 | } 39 | if x[i].tag != x[j].tag { 40 | return x[i].tag 41 | } 42 | return byIndex(x).Less(i, j) 43 | } 44 | 45 | // byIndex sorts field by index sequence. 46 | type byIndex []field 47 | 48 | func (x byIndex) Len() int { return len(x) } 49 | 50 | func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 51 | 52 | func (x byIndex) Less(i, j int) bool { 53 | for k, xik := range x[i].index { 54 | if k >= len(x[j].index) { 55 | return false 56 | } 57 | if xik != x[j].index[k] { 58 | return xik < x[j].index[k] 59 | } 60 | } 61 | return len(x[i].index) < len(x[j].index) 62 | } 63 | 64 | // typeFields returns a list of fields that TOML should recognize for the given 65 | // type. The algorithm is breadth-first search over the set of structs to 66 | // include - the top struct and then any reachable anonymous structs. 67 | func typeFields(t reflect.Type) []field { 68 | // Anonymous fields to explore at the current level and the next. 69 | current := []field{} 70 | next := []field{{typ: t}} 71 | 72 | // Count of queued names for current level and the next. 73 | count := map[reflect.Type]int{} 74 | nextCount := map[reflect.Type]int{} 75 | 76 | // Types already visited at an earlier level. 77 | visited := map[reflect.Type]bool{} 78 | 79 | // Fields found. 80 | var fields []field 81 | 82 | for len(next) > 0 { 83 | current, next = next, current[:0] 84 | count, nextCount = nextCount, map[reflect.Type]int{} 85 | 86 | for _, f := range current { 87 | if visited[f.typ] { 88 | continue 89 | } 90 | visited[f.typ] = true 91 | 92 | // Scan f.typ for fields to include. 93 | for i := 0; i < f.typ.NumField(); i++ { 94 | sf := f.typ.Field(i) 95 | if sf.PkgPath != "" { // unexported 96 | continue 97 | } 98 | name := sf.Tag.Get("toml") 99 | if name == "-" { 100 | continue 101 | } 102 | index := make([]int, len(f.index)+1) 103 | copy(index, f.index) 104 | index[len(f.index)] = i 105 | 106 | ft := sf.Type 107 | if ft.Name() == "" && ft.Kind() == reflect.Ptr { 108 | // Follow pointer. 109 | ft = ft.Elem() 110 | } 111 | 112 | // Record found field and index sequence. 113 | if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct { 114 | tagged := name != "" 115 | if name == "" { 116 | name = sf.Name 117 | } 118 | fields = append(fields, field{name, tagged, index, ft}) 119 | if count[f.typ] > 1 { 120 | // If there were multiple instances, add a second, 121 | // so that the annihilation code will see a duplicate. 122 | // It only cares about the distinction between 1 or 2, 123 | // so don't bother generating any more copies. 124 | fields = append(fields, fields[len(fields)-1]) 125 | } 126 | continue 127 | } 128 | 129 | // Record new anonymous struct to explore in next round. 130 | nextCount[ft]++ 131 | if nextCount[ft] == 1 { 132 | f := field{name: ft.Name(), index: index, typ: ft} 133 | next = append(next, f) 134 | } 135 | } 136 | } 137 | } 138 | 139 | sort.Sort(byName(fields)) 140 | 141 | // Delete all fields that are hidden by the Go rules for embedded fields, 142 | // except that fields with TOML tags are promoted. 143 | 144 | // The fields are sorted in primary order of name, secondary order 145 | // of field index length. Loop over names; for each name, delete 146 | // hidden fields by choosing the one dominant field that survives. 147 | out := fields[:0] 148 | for advance, i := 0, 0; i < len(fields); i += advance { 149 | // One iteration per name. 150 | // Find the sequence of fields with the name of this first field. 151 | fi := fields[i] 152 | name := fi.name 153 | for advance = 1; i+advance < len(fields); advance++ { 154 | fj := fields[i+advance] 155 | if fj.name != name { 156 | break 157 | } 158 | } 159 | if advance == 1 { // Only one field with this name 160 | out = append(out, fi) 161 | continue 162 | } 163 | dominant, ok := dominantField(fields[i : i+advance]) 164 | if ok { 165 | out = append(out, dominant) 166 | } 167 | } 168 | 169 | fields = out 170 | sort.Sort(byIndex(fields)) 171 | 172 | return fields 173 | } 174 | 175 | // dominantField looks through the fields, all of which are known to 176 | // have the same name, to find the single field that dominates the 177 | // others using Go's embedding rules, modified by the presence of 178 | // TOML tags. If there are multiple top-level fields, the boolean 179 | // will be false: This condition is an error in Go and we skip all 180 | // the fields. 181 | func dominantField(fields []field) (field, bool) { 182 | // The fields are sorted in increasing index-length order. The winner 183 | // must therefore be one with the shortest index length. Drop all 184 | // longer entries, which is easy: just truncate the slice. 185 | length := len(fields[0].index) 186 | tagged := -1 // Index of first tagged field. 187 | for i, f := range fields { 188 | if len(f.index) > length { 189 | fields = fields[:i] 190 | break 191 | } 192 | if f.tag { 193 | if tagged >= 0 { 194 | // Multiple tagged fields at the same level: conflict. 195 | // Return no field. 196 | return field{}, false 197 | } 198 | tagged = i 199 | } 200 | } 201 | if tagged >= 0 { 202 | return fields[tagged], true 203 | } 204 | // All remaining fields have the same length. If there's more than one, 205 | // we have a conflict (two fields named "X" at the same level) and we 206 | // return no field. 207 | if len(fields) > 1 { 208 | return field{}, false 209 | } 210 | return fields[0], true 211 | } 212 | 213 | var fieldCache struct { 214 | sync.RWMutex 215 | m map[reflect.Type][]field 216 | } 217 | 218 | // cachedTypeFields is like typeFields but uses a cache to avoid repeated work. 219 | func cachedTypeFields(t reflect.Type) []field { 220 | fieldCache.RLock() 221 | f := fieldCache.m[t] 222 | fieldCache.RUnlock() 223 | if f != nil { 224 | return f 225 | } 226 | 227 | // Compute fields without lock. 228 | // Might duplicate effort but won't hold other computations back. 229 | f = typeFields(t) 230 | if f == nil { 231 | f = []field{} 232 | } 233 | 234 | fieldCache.Lock() 235 | if fieldCache.m == nil { 236 | fieldCache.m = map[reflect.Type][]field{} 237 | } 238 | fieldCache.m[t] = f 239 | fieldCache.Unlock() 240 | return f 241 | } 242 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/flike/golog/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 chenfei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/flike/golog/README.md: -------------------------------------------------------------------------------- 1 | # golog 2 | 3 | simple log library for Golang 4 | 5 | ## case 6 | 7 | ``` 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | . "github.com/flike/golog" 13 | "os" 14 | ) 15 | 16 | func main() { 17 | path := "/tmp/test_log" 18 | os.RemoveAll(path) 19 | os.Mkdir(path, 0777) 20 | fileName := path + "/test.log" 21 | 22 | h, err := NewRotatingFileHandler(fileName, 1024*1024, 2) 23 | if err != nil { 24 | fmt.Println(err.Error()) 25 | } 26 | //GlobalLogger is a global variable 27 | GlobalLogger = New(h, Lfile|Ltime|Llevel) 28 | GlobalLogger.SetLevel(LevelTrace) 29 | args1 := "go" 30 | args2 := "log" 31 | args3 := "golog" 32 | //the log will record into the file(/tmp/test_log/test.log) 33 | Debug("Mode", "main", "OK", 0, "args1", args1, "args2", args2, "args3", args3) 34 | GlobalLogger.Close() 35 | } 36 | 37 | ``` 38 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/flike/golog/filehandler.go: -------------------------------------------------------------------------------- 1 | package golog 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | "time" 8 | ) 9 | 10 | //FileHandler writes log to a file. 11 | type FileHandler struct { 12 | fd *os.File 13 | } 14 | 15 | func NewFileHandler(fileName string, flag int) (*FileHandler, error) { 16 | dir := path.Dir(fileName) 17 | os.Mkdir(dir, 0777) 18 | 19 | f, err := os.OpenFile(fileName, flag, 0) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | h := new(FileHandler) 25 | 26 | h.fd = f 27 | 28 | return h, nil 29 | } 30 | 31 | func (h *FileHandler) Write(b []byte) (n int, err error) { 32 | return h.fd.Write(b) 33 | } 34 | 35 | func (h *FileHandler) Close() error { 36 | return h.fd.Close() 37 | } 38 | 39 | //RotatingFileHandler writes log a file, if file size exceeds maxBytes, 40 | //it will backup current file and open a new one. 41 | // 42 | //max backup file number is set by backupCount, it will delete oldest if backups too many. 43 | type RotatingFileHandler struct { 44 | fd *os.File 45 | 46 | fileName string 47 | maxBytes int 48 | backupCount int 49 | } 50 | 51 | func NewRotatingFileHandler(fileName string, maxBytes int, backupCount int) (*RotatingFileHandler, error) { 52 | dir := path.Dir(fileName) 53 | os.Mkdir(dir, 0777) 54 | 55 | h := new(RotatingFileHandler) 56 | 57 | if maxBytes <= 0 { 58 | return nil, fmt.Errorf("invalid max bytes") 59 | } 60 | 61 | h.fileName = fileName 62 | h.maxBytes = maxBytes 63 | h.backupCount = backupCount 64 | 65 | var err error 66 | h.fd, err = os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) 67 | if err != nil { 68 | return nil, err 69 | } 70 | 71 | return h, nil 72 | } 73 | 74 | func (h *RotatingFileHandler) Write(p []byte) (n int, err error) { 75 | h.doRollover() 76 | return h.fd.Write(p) 77 | } 78 | 79 | func (h *RotatingFileHandler) Close() error { 80 | if h.fd != nil { 81 | return h.fd.Close() 82 | } 83 | return nil 84 | } 85 | 86 | func (h *RotatingFileHandler) doRollover() { 87 | f, err := h.fd.Stat() 88 | if err != nil { 89 | return 90 | } 91 | 92 | if h.maxBytes <= 0 { 93 | return 94 | } else if f.Size() < int64(h.maxBytes) { 95 | return 96 | } 97 | 98 | if h.backupCount > 0 { 99 | h.fd.Close() 100 | 101 | for i := h.backupCount - 1; i > 0; i-- { 102 | sfn := fmt.Sprintf("%s.%d", h.fileName, i) 103 | dfn := fmt.Sprintf("%s.%d", h.fileName, i+1) 104 | 105 | os.Rename(sfn, dfn) 106 | } 107 | 108 | dfn := fmt.Sprintf("%s.1", h.fileName) 109 | os.Rename(h.fileName, dfn) 110 | 111 | h.fd, _ = os.OpenFile(h.fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) 112 | } 113 | } 114 | 115 | //TimeRotatingFileHandler writes log to a file, 116 | //it will backup current and open a new one, with a period time you sepecified. 117 | // 118 | //refer: http://docs.python.org/2/library/logging.handlers.html. 119 | //same like python TimedRotatingFileHandler. 120 | type TimeRotatingFileHandler struct { 121 | fd *os.File 122 | 123 | baseName string 124 | interval int64 125 | suffix string 126 | rolloverAt int64 127 | } 128 | 129 | const ( 130 | WhenSecond = iota 131 | WhenMinute 132 | WhenHour 133 | WhenDay 134 | ) 135 | 136 | func NewTimeRotatingFileHandler(baseName string, when int8, interval int) (*TimeRotatingFileHandler, error) { 137 | dir := path.Dir(baseName) 138 | os.Mkdir(dir, 0777) 139 | 140 | h := new(TimeRotatingFileHandler) 141 | 142 | h.baseName = baseName 143 | 144 | switch when { 145 | case WhenSecond: 146 | h.interval = 1 147 | h.suffix = "2006-01-02_15-04-05" 148 | case WhenMinute: 149 | h.interval = 60 150 | h.suffix = "2006-01-02_15-04" 151 | case WhenHour: 152 | h.interval = 3600 153 | h.suffix = "2006-01-02_15" 154 | case WhenDay: 155 | h.interval = 3600 * 24 156 | h.suffix = "2006-01-02" 157 | default: 158 | return nil, fmt.Errorf("invalid when_rotate: %d", when) 159 | } 160 | 161 | h.interval = h.interval * int64(interval) 162 | 163 | var err error 164 | h.fd, err = os.OpenFile(h.baseName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) 165 | if err != nil { 166 | return nil, err 167 | } 168 | 169 | fInfo, _ := h.fd.Stat() 170 | h.rolloverAt = fInfo.ModTime().Unix() + h.interval 171 | 172 | return h, nil 173 | } 174 | 175 | func (h *TimeRotatingFileHandler) doRollover() { 176 | //refer http://hg.python.org/cpython/file/2.7/Lib/logging/handlers.py 177 | now := time.Now() 178 | 179 | if h.rolloverAt <= now.Unix() { 180 | fName := h.baseName + now.Format(h.suffix) 181 | h.fd.Close() 182 | e := os.Rename(h.baseName, fName) 183 | if e != nil { 184 | panic(e) 185 | } 186 | 187 | h.fd, _ = os.OpenFile(h.baseName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) 188 | 189 | h.rolloverAt = time.Now().Unix() + h.interval 190 | } 191 | } 192 | 193 | func (h *TimeRotatingFileHandler) Write(b []byte) (n int, err error) { 194 | h.doRollover() 195 | return h.fd.Write(b) 196 | } 197 | 198 | func (h *TimeRotatingFileHandler) Close() error { 199 | return h.fd.Close() 200 | } 201 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/flike/golog/handler.go: -------------------------------------------------------------------------------- 1 | package golog 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | //Handler writes logs to somewhere 8 | type Handler interface { 9 | Write(p []byte) (n int, err error) 10 | Close() error 11 | } 12 | 13 | //StreamHandler writes logs to a specified io Writer, maybe stdout, stderr, etc... 14 | type StreamHandler struct { 15 | w io.Writer 16 | } 17 | 18 | func NewStreamHandler(w io.Writer) (*StreamHandler, error) { 19 | h := new(StreamHandler) 20 | 21 | h.w = w 22 | 23 | return h, nil 24 | } 25 | 26 | func (h *StreamHandler) Write(b []byte) (n int, err error) { 27 | return h.w.Write(b) 28 | } 29 | 30 | func (h *StreamHandler) Close() error { 31 | return nil 32 | } 33 | 34 | //NullHandler does nothing, it discards anything. 35 | type NullHandler struct { 36 | } 37 | 38 | func NewNullHandler() (*NullHandler, error) { 39 | return new(NullHandler), nil 40 | } 41 | 42 | func (h *NullHandler) Write(b []byte) (n int, err error) { 43 | return len(b), nil 44 | } 45 | 46 | func (h *NullHandler) Close() error { 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/flike/golog/log.go: -------------------------------------------------------------------------------- 1 | package golog 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "runtime" 8 | "strconv" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | //log level, from low to high, more high means more serious 14 | const ( 15 | LevelTrace = iota 16 | LevelDebug 17 | LevelInfo 18 | LevelWarn 19 | LevelError 20 | LevelFatal 21 | ) 22 | 23 | const ( 24 | Ltime = 1 << iota //time format "2006/01/02 15:04:05" 25 | Lfile //file.go:123 26 | Llevel //[Trace|Debug|Info...] 27 | ) 28 | 29 | var LevelName [6]string = [6]string{"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"} 30 | 31 | const TimeFormat = "2006/01/02 15:04:05" 32 | 33 | const maxBufPoolSize = 16 34 | 35 | type Logger struct { 36 | sync.Mutex 37 | 38 | level int 39 | flag int 40 | 41 | handler Handler 42 | 43 | quit chan struct{} 44 | msg chan []byte 45 | 46 | bufs [][]byte 47 | 48 | wg sync.WaitGroup 49 | 50 | closed bool 51 | } 52 | 53 | //new a logger with specified handler and flag 54 | func New(handler Handler, flag int) *Logger { 55 | var l = new(Logger) 56 | 57 | l.level = LevelInfo 58 | l.handler = handler 59 | 60 | l.flag = flag 61 | 62 | l.quit = make(chan struct{}) 63 | l.closed = false 64 | 65 | l.msg = make(chan []byte, 1024) 66 | 67 | l.bufs = make([][]byte, 0, 16) 68 | 69 | l.wg.Add(1) 70 | go l.run() 71 | 72 | return l 73 | } 74 | 75 | //new a default logger with specified handler and flag: Ltime|Lfile|Llevel 76 | func NewDefault(handler Handler) *Logger { 77 | return New(handler, Ltime|Lfile|Llevel) 78 | } 79 | 80 | func newStdHandler() *StreamHandler { 81 | h, _ := NewStreamHandler(os.Stdout) 82 | return h 83 | } 84 | 85 | var std = NewDefault(newStdHandler()) 86 | 87 | func Close() { 88 | std.Close() 89 | } 90 | 91 | func (l *Logger) run() { 92 | defer l.wg.Done() 93 | for { 94 | select { 95 | case msg := <-l.msg: 96 | l.handler.Write(msg) 97 | l.putBuf(msg) 98 | case <-l.quit: 99 | if len(l.msg) == 0 { 100 | return 101 | } 102 | } 103 | } 104 | } 105 | 106 | func (l *Logger) popBuf() []byte { 107 | l.Lock() 108 | var buf []byte 109 | if len(l.bufs) == 0 { 110 | buf = make([]byte, 0, 1024) 111 | } else { 112 | buf = l.bufs[len(l.bufs)-1] 113 | l.bufs = l.bufs[0 : len(l.bufs)-1] 114 | } 115 | l.Unlock() 116 | 117 | return buf 118 | } 119 | 120 | func (l *Logger) putBuf(buf []byte) { 121 | l.Lock() 122 | if len(l.bufs) < maxBufPoolSize { 123 | buf = buf[0:0] 124 | l.bufs = append(l.bufs, buf) 125 | } 126 | l.Unlock() 127 | } 128 | 129 | func (l *Logger) Close() { 130 | if l.closed { 131 | return 132 | } 133 | l.closed = true 134 | 135 | close(l.quit) 136 | l.wg.Wait() 137 | l.quit = nil 138 | 139 | l.handler.Close() 140 | } 141 | 142 | //set log level, any log level less than it will not log 143 | func (l *Logger) SetLevel(level int) { 144 | l.level = level 145 | } 146 | 147 | func (l *Logger) Level() int { 148 | return l.level 149 | } 150 | 151 | //a low interface, maybe you can use it for your special log format 152 | //but it may be not exported later...... 153 | func (l *Logger) Output(callDepth int, level int, format string, v ...interface{}) { 154 | if l.level > level { 155 | return 156 | } 157 | 158 | buf := l.popBuf() 159 | 160 | if l.flag&Ltime > 0 { 161 | now := time.Now().Format(TimeFormat) 162 | buf = append(buf, now...) 163 | buf = append(buf, " - "...) 164 | } 165 | 166 | if l.flag&Llevel > 0 { 167 | buf = append(buf, LevelName[level]...) 168 | buf = append(buf, " - "...) 169 | } 170 | 171 | if l.flag&Lfile > 0 { 172 | _, file, line, ok := runtime.Caller(callDepth) 173 | if !ok { 174 | file = "???" 175 | line = 0 176 | } else { 177 | for i := len(file) - 1; i > 0; i-- { 178 | if file[i] == '/' { 179 | file = file[i+1:] 180 | break 181 | } 182 | } 183 | } 184 | 185 | buf = append(buf, file...) 186 | buf = append(buf, ":["...) 187 | 188 | buf = strconv.AppendInt(buf, int64(line), 10) 189 | buf = append(buf, "] - "...) 190 | } 191 | 192 | s := fmt.Sprintf(format, v...) 193 | 194 | buf = append(buf, s...) 195 | 196 | if s[len(s)-1] != '\n' { 197 | buf = append(buf, '\n') 198 | } 199 | 200 | l.msg <- buf 201 | } 202 | 203 | func SetLevel(level int) { 204 | std.SetLevel(level) 205 | } 206 | 207 | func StdLogger() *Logger { 208 | return std 209 | } 210 | 211 | func GetLevel() int { 212 | return std.level 213 | } 214 | 215 | //全局变量 216 | var GlobalLogger *Logger = StdLogger() 217 | 218 | func escape(s string, filterEqual bool) string { 219 | dest := make([]byte, 0, 2*len(s)) 220 | for i := 0; i < len(s); i++ { 221 | r := s[i] 222 | switch r { 223 | case '|': 224 | continue 225 | case '%': 226 | dest = append(dest, '%', '%') 227 | case '=': 228 | if !filterEqual { 229 | dest = append(dest, '=') 230 | } 231 | default: 232 | dest = append(dest, r) 233 | } 234 | } 235 | 236 | return string(dest) 237 | } 238 | 239 | func output(level int, module string, method string, msg string, reqId int64, args ...interface{}) { 240 | if level < GlobalLogger.Level() { 241 | return 242 | } 243 | 244 | num := len(args) / 2 245 | var argsBuff bytes.Buffer 246 | for i := 0; i < num; i++ { 247 | argsBuff.WriteString(escape(fmt.Sprintf("%v=%v", args[i*2], args[i*2+1]), false)) 248 | if (i+1)*2 != len(args) { 249 | argsBuff.WriteString("|") 250 | } 251 | } 252 | if len(args)%2 == 1 { 253 | argsBuff.WriteString(escape(fmt.Sprintf("%v", args[len(args)-1]), false)) 254 | } 255 | 256 | content := fmt.Sprintf(`[%s] "%s" "%s" "%s" req_id=%d`, 257 | module, method, msg, argsBuff.String(), reqId) 258 | 259 | GlobalLogger.Output(3, level, content) 260 | } 261 | 262 | //func Init(strLevel string, path string) error { 263 | // if path != "" { 264 | // handler, err := NewTimeRotatingFileHandler(path, WhenDay, 1) 265 | // if err != nil { 266 | // return err 267 | // } 268 | // logger = NewDefault(handler) 269 | // } 270 | // var level int 271 | // switch strLevel { 272 | // case "trace": 273 | // level = LevelTrace 274 | // case "debug": 275 | // level = LevelDebug 276 | // case "info": 277 | // level = LevelInfo 278 | // case "warn": 279 | // level = LevelWarn 280 | // case "error": 281 | // level = LevelError 282 | // case "fatal": 283 | // level = LevelFatal 284 | // default: 285 | // return fmt.Errorf("invalid log level:%s", strLevel) 286 | // } 287 | // logger.SetLevel(level) 288 | // return nil 289 | //} 290 | 291 | func Trace(module string, method string, msg string, reqId int64, args ...interface{}) { 292 | output(LevelTrace, module, method, msg, reqId, args...) 293 | } 294 | func Debug(module string, method string, msg string, reqId int64, args ...interface{}) { 295 | output(LevelDebug, module, method, msg, reqId, args...) 296 | } 297 | func Info(module string, method string, msg string, reqId int64, args ...interface{}) { 298 | output(LevelInfo, module, method, msg, reqId, args...) 299 | } 300 | func Warn(module string, method string, msg string, reqId int64, args ...interface{}) { 301 | output(LevelWarn, module, method, msg, reqId, args...) 302 | } 303 | func Error(module string, method string, msg string, reqId int64, args ...interface{}) { 304 | output(LevelError, module, method, msg, reqId, args...) 305 | } 306 | func Fatal(module string, method string, msg string, reqId int64, args ...interface{}) { 307 | output(LevelFatal, module, method, msg, reqId, args...) 308 | } 309 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/flike/golog/log_test.go: -------------------------------------------------------------------------------- 1 | package golog 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | const ( 9 | ModMySQL = "mysql" 10 | ) 11 | 12 | func TestLog(t *testing.T) { 13 | SetLevel(LevelTrace) 14 | Error(ModMySQL, "sqlx.DB.Select", "test", 0, "select * from qing_user") 15 | Info(ModMySQL, "sqlx.DB.Select", "test", 0, "select * from qing_user") 16 | Trace(ModMySQL, "sqlx.DB.Select", "test", 0, "select * from qing_user") 17 | Debug(ModMySQL, "sqlx.DB.Select", "test", 0, "select * from qing_user") 18 | Warn(ModMySQL, "sqlx.DB.Select", "test", 0, "select * from qing_user") 19 | Fatal(ModMySQL, "sqlx.DB.Select", "test", 0, "a", "b", "c", "d", "e") 20 | 21 | Fatal(ModMySQL, "AA", "test", 0, "%3d", 123) 22 | } 23 | 24 | func TestEscape(t *testing.T) { 25 | r := escape("abc= %|", false) 26 | if r != "abc= %%" { 27 | t.Fatal("invalid result ", r) 28 | } 29 | 30 | r = escape("abc= %|", true) 31 | if r != "abc %%" { 32 | t.Fatal("invalid result ", r) 33 | } 34 | 35 | if r := escape("%3d", false); r != "%%3d" { 36 | t.Fatal("invalid result ", r) 37 | } 38 | } 39 | 40 | func TestRotatingFileLog(t *testing.T) { 41 | path := "/tmp/test_log" 42 | os.RemoveAll(path) 43 | 44 | os.Mkdir(path, 0777) 45 | fileName := path + "/test" 46 | 47 | h, err := NewRotatingFileHandler(fileName, 1024*1024, 2) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | 52 | GlobalLogger = New(h, Lfile|Ltime|Llevel) 53 | GlobalLogger.SetLevel(LevelTrace) 54 | Debug("log", "hello,world", "OK", 0, "fileName", fileName, "fileName2", fileName, "fileName3", fileName) 55 | 56 | GlobalLogger.Close() 57 | 58 | //os.RemoveAll(path) 59 | } 60 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/go-sql-driver/mysql/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .DS_Store? 3 | ._* 4 | .Spotlight-V100 5 | .Trashes 6 | Icon? 7 | ehthumbs.db 8 | Thumbs.db 9 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/go-sql-driver/mysql/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.1 4 | - 1.2 5 | - 1.3 6 | - tip 7 | 8 | before_script: 9 | - mysql -e 'create database gotest;' 10 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/go-sql-driver/mysql/AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of Go-MySQL-Driver authors for copyright purposes. 2 | 3 | # If you are submitting a patch, please add your name or the name of the 4 | # organization which holds the copyright to this list in alphabetical order. 5 | 6 | # Names should be added to this file as 7 | # Name 8 | # The email address is not required for organizations. 9 | # Please keep the list sorted. 10 | 11 | 12 | # Individual Persons 13 | 14 | Aaron Hopkins 15 | Arne Hormann 16 | Carlos Nieto 17 | DisposaBoy 18 | Frederick Mayle 19 | Gustavo Kristic 20 | Hanno Braun 21 | Henri Yandell 22 | James Harr 23 | Jian Zhen 24 | Julien Schmidt 25 | Leonardo YongUk Kim 26 | Lucas Liu 27 | Luke Scott 28 | Michael Woolnough 29 | Nicola Peduzzi 30 | Xiaobing Jiang 31 | Xiuming Chen 32 | 33 | # Organizations 34 | 35 | Barracuda Networks, Inc. 36 | Google Inc. 37 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/go-sql-driver/mysql/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## HEAD 2 | 3 | Changes: 4 | 5 | - Use decimals field from MySQL to format time types 6 | 7 | Bugfixes: 8 | 9 | - Enable microsecond resolution on TIME, DATETIME and TIMESTAMP 10 | 11 | 12 | ## Version 1.2 (2014-06-03) 13 | 14 | Changes: 15 | 16 | - We switched back to a "rolling release". `go get` installs the current master branch again 17 | - Version v1 of the driver will not be maintained anymore. Go 1.0 is no longer supported by this driver 18 | - Exported errors to allow easy checking from application code 19 | - Enabled TCP Keepalives on TCP connections 20 | - Optimized INFILE handling (better buffer size calculation, lazy init, ...) 21 | - The DSN parser also checks for a missing separating slash 22 | - Faster binary date / datetime to string formatting 23 | - Also exported the MySQLWarning type 24 | - mysqlConn.Close returns the first error encountered instead of ignoring all errors 25 | - writePacket() automatically writes the packet size to the header 26 | - readPacket() uses an iterative approach instead of the recursive approach to merge splitted packets 27 | 28 | New Features: 29 | 30 | - `RegisterDial` allows the usage of a custom dial function to establish the network connection 31 | - Setting the connection collation is possible with the `collation` DSN parameter. This parameter should be preferred over the `charset` parameter 32 | - Logging of critical errors is configurable with `SetLogger` 33 | - Google CloudSQL support 34 | 35 | Bugfixes: 36 | 37 | - Allow more than 32 parameters in prepared statements 38 | - Various old_password fixes 39 | - Fixed TestConcurrent test to pass Go's race detection 40 | - Fixed appendLengthEncodedInteger for large numbers 41 | - Renamed readLengthEnodedString to readLengthEncodedString and skipLengthEnodedString to skipLengthEncodedString (fixed typo) 42 | 43 | 44 | ## Version 1.1 (2013-11-02) 45 | 46 | Changes: 47 | 48 | - Go-MySQL-Driver now requires Go 1.1 49 | - Connections now use the collation `utf8_general_ci` by default. Adding `&charset=UTF8` to the DSN should not be necessary anymore 50 | - Made closing rows and connections error tolerant. This allows for example deferring rows.Close() without checking for errors 51 | - `[]byte(nil)` is now treated as a NULL value. Before, it was treated like an empty string / `[]byte("")` 52 | - DSN parameter values must now be url.QueryEscape'ed. This allows text values to contain special characters, such as '&'. 53 | - Use the IO buffer also for writing. This results in zero allocations (by the driver) for most queries 54 | - Optimized the buffer for reading 55 | - stmt.Query now caches column metadata 56 | - New Logo 57 | - Changed the copyright header to include all contributors 58 | - Improved the LOAD INFILE documentation 59 | - The driver struct is now exported to make the driver directly accessible 60 | - Refactored the driver tests 61 | - Added more benchmarks and moved all to a separate file 62 | - Other small refactoring 63 | 64 | New Features: 65 | 66 | - Added *old_passwords* support: Required in some cases, but must be enabled by adding `allowOldPasswords=true` to the DSN since it is insecure 67 | - Added a `clientFoundRows` parameter: Return the number of matching rows instead of the number of rows changed on UPDATEs 68 | - Added TLS/SSL support: Use a TLS/SSL encrypted connection to the server. Custom TLS configs can be registered and used 69 | 70 | Bugfixes: 71 | 72 | - Fixed MySQL 4.1 support: MySQL 4.1 sends packets with lengths which differ from the specification 73 | - Convert to DB timezone when inserting `time.Time` 74 | - Splitted packets (more than 16MB) are now merged correctly 75 | - Fixed false positive `io.EOF` errors when the data was fully read 76 | - Avoid panics on reuse of closed connections 77 | - Fixed empty string producing false nil values 78 | - Fixed sign byte for positive TIME fields 79 | 80 | 81 | ## Version 1.0 (2013-05-14) 82 | 83 | Initial Release 84 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/go-sql-driver/mysql/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | ## Reporting Issues 4 | 5 | Before creating a new Issue, please check first if a similar Issue [already exists](https://github.com/go-sql-driver/mysql/issues?state=open) or was [recently closed](https://github.com/go-sql-driver/mysql/issues?direction=desc&page=1&sort=updated&state=closed). 6 | 7 | Please provide the following minimum information: 8 | * Your Go-MySQL-Driver version (or git SHA) 9 | * Your Go version (run `go version` in your console) 10 | * A detailed issue description 11 | * Error Log if present 12 | * If possible, a short example 13 | 14 | 15 | ## Contributing Code 16 | 17 | By contributing to this project, you share your code under the Mozilla Public License 2, as specified in the LICENSE file. 18 | Don't forget to add yourself to the AUTHORS file. 19 | 20 | ### Pull Requests Checklist 21 | 22 | Please check the following points before submitting your pull request: 23 | - [x] Code compiles correctly 24 | - [x] Created tests, if possible 25 | - [x] All tests pass 26 | - [x] Extended the README / documentation, if necessary 27 | - [x] Added yourself to the AUTHORS file 28 | 29 | ### Code Review 30 | 31 | Everyone is invited to review and comment on pull requests. 32 | If it looks fine to you, comment with "LGTM" (Looks good to me). 33 | 34 | If changes are required, notice the reviewers with "PTAL" (Please take another look) after committing the fixes. 35 | 36 | Before merging the Pull Request, at least one [team member](https://github.com/go-sql-driver?tab=members) must have commented with "LGTM". 37 | 38 | ## Development Ideas 39 | 40 | If you are looking for ideas for code contributions, please check our [Development Ideas](https://github.com/go-sql-driver/mysql/wiki/Development-Ideas) Wiki page. 41 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/go-sql-driver/mysql/README.md: -------------------------------------------------------------------------------- 1 | # Go-MySQL-Driver 2 | 3 | A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) package 4 | 5 | ![Go-MySQL-Driver logo](https://raw.github.com/wiki/go-sql-driver/mysql/gomysql_m.png "Golang Gopher holding the MySQL Dolphin") 6 | 7 | **Latest stable Release:** [Version 1.2 (June 03, 2014)](https://github.com/go-sql-driver/mysql/releases) 8 | 9 | [![Build Status](https://travis-ci.org/go-sql-driver/mysql.png?branch=master)](https://travis-ci.org/go-sql-driver/mysql) 10 | 11 | --------------------------------------- 12 | * [Features](#features) 13 | * [Requirements](#requirements) 14 | * [Installation](#installation) 15 | * [Usage](#usage) 16 | * [DSN (Data Source Name)](#dsn-data-source-name) 17 | * [Password](#password) 18 | * [Protocol](#protocol) 19 | * [Address](#address) 20 | * [Parameters](#parameters) 21 | * [Examples](#examples) 22 | * [LOAD DATA LOCAL INFILE support](#load-data-local-infile-support) 23 | * [time.Time support](#timetime-support) 24 | * [Unicode support](#unicode-support) 25 | * [Testing / Development](#testing--development) 26 | * [License](#license) 27 | 28 | --------------------------------------- 29 | 30 | ## Features 31 | * Lightweight and [fast](https://github.com/go-sql-driver/sql-benchmark "golang MySQL-Driver performance") 32 | * Native Go implementation. No C-bindings, just pure Go 33 | * Connections over TCP/IPv4, TCP/IPv6 or Unix domain sockets 34 | * Automatic handling of broken connections 35 | * Automatic Connection Pooling *(by database/sql package)* 36 | * Supports queries larger than 16MB 37 | * Full [`sql.RawBytes`](http://golang.org/pkg/database/sql/#RawBytes) support. 38 | * Intelligent `LONG DATA` handling in prepared statements 39 | * Secure `LOAD DATA LOCAL INFILE` support with file Whitelisting and `io.Reader` support 40 | * Optional `time.Time` parsing 41 | 42 | ## Requirements 43 | * Go 1.1 or higher 44 | * MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+) 45 | 46 | --------------------------------------- 47 | 48 | ## Installation 49 | Simple install the package to your [$GOPATH](http://code.google.com/p/go-wiki/wiki/GOPATH "GOPATH") with the [go tool](http://golang.org/cmd/go/ "go command") from shell: 50 | ```bash 51 | $ go get github.com/go-sql-driver/mysql 52 | ``` 53 | Make sure [Git is installed](http://git-scm.com/downloads) on your machine and in your system's `PATH`. 54 | 55 | ## Usage 56 | _Go MySQL Driver_ is an implementation of Go's `database/sql/driver` interface. You only need to import the driver and can use the full [`database/sql`](http://golang.org/pkg/database/sql) API then. 57 | 58 | Use `mysql` as `driverName` and a valid [DSN](#dsn-data-source-name) as `dataSourceName`: 59 | ```go 60 | import "database/sql" 61 | import _ "github.com/go-sql-driver/mysql" 62 | 63 | db, err := sql.Open("mysql", "user:password@/dbname") 64 | ``` 65 | 66 | [Examples are available in our Wiki](https://github.com/go-sql-driver/mysql/wiki/Examples "Go-MySQL-Driver Examples"). 67 | 68 | 69 | ### DSN (Data Source Name) 70 | 71 | The Data Source Name has a common format, like e.g. [PEAR DB](http://pear.php.net/manual/en/package.database.db.intro-dsn.php) uses it, but without type-prefix (optional parts marked by squared brackets): 72 | ``` 73 | [username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN] 74 | ``` 75 | 76 | A DSN in its fullest form: 77 | ``` 78 | username:password@protocol(address)/dbname?param=value 79 | ``` 80 | 81 | Except for the databasename, all values are optional. So the minimal DSN is: 82 | ``` 83 | /dbname 84 | ``` 85 | 86 | If you do not want to preselect a database, leave `dbname` empty: 87 | ``` 88 | / 89 | ``` 90 | This has the same effect as an empty DSN string: 91 | ``` 92 | 93 | ``` 94 | 95 | #### Password 96 | Passwords can consist of any character. Escaping is **not** necessary. 97 | 98 | #### Protocol 99 | See [net.Dial](http://golang.org/pkg/net/#Dial) for more information which networks are available. 100 | In general you should use an Unix domain socket if available and TCP otherwise for best performance. 101 | 102 | #### Address 103 | For TCP and UDP networks, addresses have the form `host:port`. 104 | If `host` is a literal IPv6 address, it must be enclosed in square brackets. 105 | The functions [net.JoinHostPort](http://golang.org/pkg/net/#JoinHostPort) and [net.SplitHostPort](http://golang.org/pkg/net/#SplitHostPort) manipulate addresses in this form. 106 | 107 | For Unix domain sockets the address is the absolute path to the MySQL-Server-socket, e.g. `/var/run/mysqld/mysqld.sock` or `/tmp/mysql.sock`. 108 | 109 | #### Parameters 110 | *Parameters are case-sensitive!* 111 | 112 | Notice that any of `true`, `TRUE`, `True` or `1` is accepted to stand for a true boolean value. Not surprisingly, false can be specified as any of: `false`, `FALSE`, `False` or `0`. 113 | 114 | ##### `allowAllFiles` 115 | 116 | ``` 117 | Type: bool 118 | Valid Values: true, false 119 | Default: false 120 | ``` 121 | 122 | `allowAllFiles=true` disables the file Whitelist for `LOAD DATA LOCAL INFILE` and allows *all* files. 123 | [*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html) 124 | 125 | ##### `allowOldPasswords` 126 | 127 | ``` 128 | Type: bool 129 | Valid Values: true, false 130 | Default: false 131 | ``` 132 | `allowOldPasswords=true` allows the usage of the insecure old password method. This should be avoided, but is necessary in some cases. See also [the old_passwords wiki page](https://github.com/go-sql-driver/mysql/wiki/old_passwords). 133 | 134 | ##### `charset` 135 | 136 | ``` 137 | Type: string 138 | Valid Values: 139 | Default: none 140 | ``` 141 | 142 | Sets the charset used for client-server interaction (`"SET NAMES "`). If multiple charsets are set (separated by a comma), the following charset is used if setting the charset failes. This enables for example support for `utf8mb4` ([introduced in MySQL 5.5.3](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html)) with fallback to `utf8` for older servers (`charset=utf8mb4,utf8`). 143 | 144 | Usage of the `charset` parameter is discouraged because it issues additional queries to the server. 145 | Unless you need the fallback behavior, please use `collation` instead. 146 | 147 | ##### `collation` 148 | 149 | ``` 150 | Type: string 151 | Valid Values: 152 | Default: utf8_general_ci 153 | ``` 154 | 155 | Sets the collation used for client-server interaction on connection. In contrast to `charset`, `collation` does not issue additional queries. If the specified collation is unavailable on the target server, the connection will fail. 156 | 157 | A list of valid charsets for a server is retrievable with `SHOW COLLATION`. 158 | 159 | ##### `clientFoundRows` 160 | 161 | ``` 162 | Type: bool 163 | Valid Values: true, false 164 | Default: false 165 | ``` 166 | 167 | `clientFoundRows=true` causes an UPDATE to return the number of matching rows instead of the number of rows changed. 168 | 169 | 170 | ##### `loc` 171 | 172 | ``` 173 | Type: string 174 | Valid Values: 175 | Default: UTC 176 | ``` 177 | 178 | Sets the location for time.Time values (when using `parseTime=true`). *"Local"* sets the system's location. See [time.LoadLocation](http://golang.org/pkg/time/#LoadLocation) for details. 179 | 180 | Please keep in mind, that param values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed. Alternatively you can manually replace the `/` with `%2F`. For example `US/Pacific` would be `loc=US%2FPacific`. 181 | 182 | 183 | ##### `parseTime` 184 | 185 | ``` 186 | Type: bool 187 | Valid Values: true, false 188 | Default: false 189 | ``` 190 | 191 | `parseTime=true` changes the output type of `DATE` and `DATETIME` values to `time.Time` instead of `[]byte` / `string` 192 | 193 | 194 | ##### `strict` 195 | 196 | ``` 197 | Type: bool 198 | Valid Values: true, false 199 | Default: false 200 | ``` 201 | 202 | `strict=true` enables the strict mode in which MySQL warnings are treated as errors. 203 | 204 | By default MySQL also treats notes as warnings. Use [`sql_notes=false`](http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_sql_notes) to ignore notes. See the [examples](#examples) for an DSN example. 205 | 206 | 207 | ##### `timeout` 208 | 209 | ``` 210 | Type: decimal number 211 | Default: OS default 212 | ``` 213 | 214 | *Driver* side connection timeout. The value must be a string of decimal numbers, each with optional fraction and a unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*. To set a server side timeout, use the parameter [`wait_timeout`](http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_wait_timeout). 215 | 216 | 217 | ##### `tls` 218 | 219 | ``` 220 | Type: bool / string 221 | Valid Values: true, false, skip-verify, 222 | Default: false 223 | ``` 224 | 225 | `tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side). Use a custom value registered with [`mysql.RegisterTLSConfig`](http://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig). 226 | 227 | 228 | ##### System Variables 229 | 230 | All other parameters are interpreted as system variables: 231 | * `autocommit`: `"SET autocommit="` 232 | * `time_zone`: `"SET time_zone="` 233 | * [`tx_isolation`](https://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_tx_isolation): `"SET tx_isolation="` 234 | * `param`: `"SET ="` 235 | 236 | *The values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed!* 237 | 238 | #### Examples 239 | ``` 240 | user@unix(/path/to/socket)/dbname 241 | ``` 242 | 243 | ``` 244 | root:pw@unix(/tmp/mysql.sock)/myDatabase?loc=Local 245 | ``` 246 | 247 | ``` 248 | user:password@tcp(localhost:5555)/dbname?tls=skip-verify&autocommit=true 249 | ``` 250 | 251 | Use the [strict mode](#strict) but ignore notes: 252 | ``` 253 | user:password@/dbname?strict=true&sql_notes=false 254 | ``` 255 | 256 | TCP via IPv6: 257 | ``` 258 | user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname?timeout=90s&collation=utf8mb4_unicode_ci 259 | ``` 260 | 261 | TCP on a remote host, e.g. Amazon RDS: 262 | ``` 263 | id:password@tcp(your-amazonaws-uri.com:3306)/dbname 264 | ``` 265 | 266 | Google Cloud SQL on App Engine: 267 | ``` 268 | user@cloudsql(project-id:instance-name)/dbname 269 | ``` 270 | 271 | TCP using default port (3306) on localhost: 272 | ``` 273 | user:password@tcp/dbname?charset=utf8mb4,utf8&sys_var=esc%40ped 274 | ``` 275 | 276 | Use the default protocol (tcp) and host (localhost:3306): 277 | ``` 278 | user:password@/dbname 279 | ``` 280 | 281 | No Database preselected: 282 | ``` 283 | user:password@/ 284 | ``` 285 | 286 | ### `LOAD DATA LOCAL INFILE` support 287 | For this feature you need direct access to the package. Therefore you must change the import path (no `_`): 288 | ```go 289 | import "github.com/go-sql-driver/mysql" 290 | ``` 291 | 292 | Files must be whitelisted by registering them with `mysql.RegisterLocalFile(filepath)` (recommended) or the Whitelist check must be deactivated by using the DSN parameter `allowAllFiles=true` ([*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)). 293 | 294 | To use a `io.Reader` a handler function must be registered with `mysql.RegisterReaderHandler(name, handler)` which returns a `io.Reader` or `io.ReadCloser`. The Reader is available with the filepath `Reader::` then. 295 | 296 | See the [godoc of Go-MySQL-Driver](http://godoc.org/github.com/go-sql-driver/mysql "golang mysql driver documentation") for details. 297 | 298 | 299 | ### `time.Time` support 300 | The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your programm. 301 | 302 | However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` variables, which is the logical opposite in Go to `DATE` and `DATETIME` in MySQL. You can do that by changing the internal output type from `[]byte` to `time.Time` with the DSN parameter `parseTime=true`. You can set the default [`time.Time` location](http://golang.org/pkg/time/#Location) with the `loc` DSN parameter. 303 | 304 | **Caution:** As of Go 1.1, this makes `time.Time` the only variable type you can scan `DATE` and `DATETIME` values into. This breaks for example [`sql.RawBytes` support](https://github.com/go-sql-driver/mysql/wiki/Examples#rawbytes). 305 | 306 | Alternatively you can use the [`NullTime`](http://godoc.org/github.com/go-sql-driver/mysql#NullTime) type as the scan destination, which works with both `time.Time` and `string` / `[]byte`. 307 | 308 | 309 | ### Unicode support 310 | Since version 1.1 Go-MySQL-Driver automatically uses the collation `utf8_general_ci` by default. 311 | 312 | Other collations / charsets can be set using the [`collation`](#collation) DSN parameter. 313 | 314 | Version 1.0 of the driver recommended adding `&charset=utf8` (alias for `SET NAMES utf8`) to the DSN to enable proper UTF-8 support. This is not necessary anymore. The [`collation`](#collation) parameter should be preferred to set another collation / charset than the default. 315 | 316 | See http://dev.mysql.com/doc/refman/5.7/en/charset-unicode.html for more details on MySQL's Unicode support. 317 | 318 | 319 | ## Testing / Development 320 | To run the driver tests you may need to adjust the configuration. See the [Testing Wiki-Page](https://github.com/go-sql-driver/mysql/wiki/Testing "Testing") for details. 321 | 322 | Go-MySQL-Driver is not feature-complete yet. Your help is very appreciated. 323 | If you want to contribute, you can work on an [open issue](https://github.com/go-sql-driver/mysql/issues?state=open) or review a [pull request](https://github.com/go-sql-driver/mysql/pulls). 324 | 325 | See the [Contribution Guidelines](https://github.com/go-sql-driver/mysql/blob/master/CONTRIBUTING.md) for details. 326 | 327 | --------------------------------------- 328 | 329 | ## License 330 | Go-MySQL-Driver is licensed under the [Mozilla Public License Version 2.0](https://raw.github.com/go-sql-driver/mysql/master/LICENSE) 331 | 332 | Mozilla summarizes the license scope as follows: 333 | > MPL: The copyleft applies to any files containing MPLed code. 334 | 335 | 336 | That means: 337 | * You can **use** the **unchanged** source code both in private and commercially 338 | * When distributing, you **must publish** the source code of any **changed files** licensed under the MPL 2.0 under a) the MPL 2.0 itself or b) a compatible license (e.g. GPL 3.0 or Apache License 2.0) 339 | * You **needn't publish** the source code of your library as long as the files licensed under the MPL 2.0 are **unchanged** 340 | 341 | Please read the [MPL 2.0 FAQ](http://www.mozilla.org/MPL/2.0/FAQ.html) if you have further questions regarding the license. 342 | 343 | You can read the full terms here: [LICENSE](https://raw.github.com/go-sql-driver/mysql/master/LICENSE) 344 | 345 | ![Go Gopher and MySQL Dolphin](https://raw.github.com/wiki/go-sql-driver/mysql/go-mysql-driver_m.jpg "Golang Gopher transporting the MySQL Dolphin in a wheelbarrow") 346 | 347 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/go-sql-driver/mysql/appengine.go: -------------------------------------------------------------------------------- 1 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 2 | // 3 | // Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 | // You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | // +build appengine 10 | 11 | package mysql 12 | 13 | import ( 14 | "appengine/cloudsql" 15 | ) 16 | 17 | func init() { 18 | RegisterDial("cloudsql", cloudsql.Dial) 19 | } 20 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/go-sql-driver/mysql/benchmark_test.go: -------------------------------------------------------------------------------- 1 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 2 | // 3 | // Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 | // You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | package mysql 10 | 11 | import ( 12 | "bytes" 13 | "database/sql" 14 | "strings" 15 | "sync" 16 | "sync/atomic" 17 | "testing" 18 | ) 19 | 20 | type TB testing.B 21 | 22 | func (tb *TB) check(err error) { 23 | if err != nil { 24 | tb.Fatal(err) 25 | } 26 | } 27 | 28 | func (tb *TB) checkDB(db *sql.DB, err error) *sql.DB { 29 | tb.check(err) 30 | return db 31 | } 32 | 33 | func (tb *TB) checkRows(rows *sql.Rows, err error) *sql.Rows { 34 | tb.check(err) 35 | return rows 36 | } 37 | 38 | func (tb *TB) checkStmt(stmt *sql.Stmt, err error) *sql.Stmt { 39 | tb.check(err) 40 | return stmt 41 | } 42 | 43 | func initDB(b *testing.B, queries ...string) *sql.DB { 44 | tb := (*TB)(b) 45 | db := tb.checkDB(sql.Open("mysql", dsn)) 46 | for _, query := range queries { 47 | if _, err := db.Exec(query); err != nil { 48 | b.Fatalf("Error on %q: %v", query, err) 49 | } 50 | } 51 | return db 52 | } 53 | 54 | const concurrencyLevel = 10 55 | 56 | func BenchmarkQuery(b *testing.B) { 57 | tb := (*TB)(b) 58 | b.StopTimer() 59 | b.ReportAllocs() 60 | db := initDB(b, 61 | "DROP TABLE IF EXISTS foo", 62 | "CREATE TABLE foo (id INT PRIMARY KEY, val CHAR(50))", 63 | `INSERT INTO foo VALUES (1, "one")`, 64 | `INSERT INTO foo VALUES (2, "two")`, 65 | ) 66 | db.SetMaxIdleConns(concurrencyLevel) 67 | defer db.Close() 68 | 69 | stmt := tb.checkStmt(db.Prepare("SELECT val FROM foo WHERE id=?")) 70 | defer stmt.Close() 71 | 72 | remain := int64(b.N) 73 | var wg sync.WaitGroup 74 | wg.Add(concurrencyLevel) 75 | defer wg.Wait() 76 | b.StartTimer() 77 | 78 | for i := 0; i < concurrencyLevel; i++ { 79 | go func() { 80 | for { 81 | if atomic.AddInt64(&remain, -1) < 0 { 82 | wg.Done() 83 | return 84 | } 85 | 86 | var got string 87 | tb.check(stmt.QueryRow(1).Scan(&got)) 88 | if got != "one" { 89 | b.Errorf("query = %q; want one", got) 90 | wg.Done() 91 | return 92 | } 93 | } 94 | }() 95 | } 96 | } 97 | 98 | func BenchmarkExec(b *testing.B) { 99 | tb := (*TB)(b) 100 | b.StopTimer() 101 | b.ReportAllocs() 102 | db := tb.checkDB(sql.Open("mysql", dsn)) 103 | db.SetMaxIdleConns(concurrencyLevel) 104 | defer db.Close() 105 | 106 | stmt := tb.checkStmt(db.Prepare("DO 1")) 107 | defer stmt.Close() 108 | 109 | remain := int64(b.N) 110 | var wg sync.WaitGroup 111 | wg.Add(concurrencyLevel) 112 | defer wg.Wait() 113 | b.StartTimer() 114 | 115 | for i := 0; i < concurrencyLevel; i++ { 116 | go func() { 117 | for { 118 | if atomic.AddInt64(&remain, -1) < 0 { 119 | wg.Done() 120 | return 121 | } 122 | 123 | if _, err := stmt.Exec(); err != nil { 124 | b.Fatal(err.Error()) 125 | } 126 | } 127 | }() 128 | } 129 | } 130 | 131 | // data, but no db writes 132 | var roundtripSample []byte 133 | 134 | func initRoundtripBenchmarks() ([]byte, int, int) { 135 | if roundtripSample == nil { 136 | roundtripSample = []byte(strings.Repeat("0123456789abcdef", 1024*1024)) 137 | } 138 | return roundtripSample, 16, len(roundtripSample) 139 | } 140 | 141 | func BenchmarkRoundtripTxt(b *testing.B) { 142 | b.StopTimer() 143 | sample, min, max := initRoundtripBenchmarks() 144 | sampleString := string(sample) 145 | b.ReportAllocs() 146 | tb := (*TB)(b) 147 | db := tb.checkDB(sql.Open("mysql", dsn)) 148 | defer db.Close() 149 | b.StartTimer() 150 | var result string 151 | for i := 0; i < b.N; i++ { 152 | length := min + i 153 | if length > max { 154 | length = max 155 | } 156 | test := sampleString[0:length] 157 | rows := tb.checkRows(db.Query(`SELECT "` + test + `"`)) 158 | if !rows.Next() { 159 | rows.Close() 160 | b.Fatalf("crashed") 161 | } 162 | err := rows.Scan(&result) 163 | if err != nil { 164 | rows.Close() 165 | b.Fatalf("crashed") 166 | } 167 | if result != test { 168 | rows.Close() 169 | b.Errorf("mismatch") 170 | } 171 | rows.Close() 172 | } 173 | } 174 | 175 | func BenchmarkRoundtripBin(b *testing.B) { 176 | b.StopTimer() 177 | sample, min, max := initRoundtripBenchmarks() 178 | b.ReportAllocs() 179 | tb := (*TB)(b) 180 | db := tb.checkDB(sql.Open("mysql", dsn)) 181 | defer db.Close() 182 | stmt := tb.checkStmt(db.Prepare("SELECT ?")) 183 | defer stmt.Close() 184 | b.StartTimer() 185 | var result sql.RawBytes 186 | for i := 0; i < b.N; i++ { 187 | length := min + i 188 | if length > max { 189 | length = max 190 | } 191 | test := sample[0:length] 192 | rows := tb.checkRows(stmt.Query(test)) 193 | if !rows.Next() { 194 | rows.Close() 195 | b.Fatalf("crashed") 196 | } 197 | err := rows.Scan(&result) 198 | if err != nil { 199 | rows.Close() 200 | b.Fatalf("crashed") 201 | } 202 | if !bytes.Equal(result, test) { 203 | rows.Close() 204 | b.Errorf("mismatch") 205 | } 206 | rows.Close() 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/go-sql-driver/mysql/buffer.go: -------------------------------------------------------------------------------- 1 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 2 | // 3 | // Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 | // You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | package mysql 10 | 11 | import "io" 12 | 13 | const defaultBufSize = 4096 14 | 15 | // A buffer which is used for both reading and writing. 16 | // This is possible since communication on each connection is synchronous. 17 | // In other words, we can't write and read simultaneously on the same connection. 18 | // The buffer is similar to bufio.Reader / Writer but zero-copy-ish 19 | // Also highly optimized for this particular use case. 20 | type buffer struct { 21 | buf []byte 22 | rd io.Reader 23 | idx int 24 | length int 25 | } 26 | 27 | func newBuffer(rd io.Reader) buffer { 28 | var b [defaultBufSize]byte 29 | return buffer{ 30 | buf: b[:], 31 | rd: rd, 32 | } 33 | } 34 | 35 | // fill reads into the buffer until at least _need_ bytes are in it 36 | func (b *buffer) fill(need int) error { 37 | n := b.length 38 | 39 | // move existing data to the beginning 40 | if n > 0 && b.idx > 0 { 41 | copy(b.buf[0:n], b.buf[b.idx:]) 42 | } 43 | 44 | // grow buffer if necessary 45 | // TODO: let the buffer shrink again at some point 46 | // Maybe keep the org buf slice and swap back? 47 | if need > len(b.buf) { 48 | // Round up to the next multiple of the default size 49 | newBuf := make([]byte, ((need/defaultBufSize)+1)*defaultBufSize) 50 | copy(newBuf, b.buf) 51 | b.buf = newBuf 52 | } 53 | 54 | b.idx = 0 55 | 56 | for { 57 | nn, err := b.rd.Read(b.buf[n:]) 58 | n += nn 59 | 60 | switch err { 61 | case nil: 62 | if n < need { 63 | continue 64 | } 65 | b.length = n 66 | return nil 67 | 68 | case io.EOF: 69 | if n >= need { 70 | b.length = n 71 | return nil 72 | } 73 | return io.ErrUnexpectedEOF 74 | 75 | default: 76 | return err 77 | } 78 | } 79 | } 80 | 81 | // returns next N bytes from buffer. 82 | // The returned slice is only guaranteed to be valid until the next read 83 | func (b *buffer) readNext(need int) ([]byte, error) { 84 | if b.length < need { 85 | // refill 86 | if err := b.fill(need); err != nil { 87 | return nil, err 88 | } 89 | } 90 | 91 | offset := b.idx 92 | b.idx += need 93 | b.length -= need 94 | return b.buf[offset:b.idx], nil 95 | } 96 | 97 | // returns a buffer with the requested size. 98 | // If possible, a slice from the existing buffer is returned. 99 | // Otherwise a bigger buffer is made. 100 | // Only one buffer (total) can be used at a time. 101 | func (b *buffer) takeBuffer(length int) []byte { 102 | if b.length > 0 { 103 | return nil 104 | } 105 | 106 | // test (cheap) general case first 107 | if length <= defaultBufSize || length <= cap(b.buf) { 108 | return b.buf[:length] 109 | } 110 | 111 | if length < maxPacketSize { 112 | b.buf = make([]byte, length) 113 | return b.buf 114 | } 115 | return make([]byte, length) 116 | } 117 | 118 | // shortcut which can be used if the requested buffer is guaranteed to be 119 | // smaller than defaultBufSize 120 | // Only one buffer (total) can be used at a time. 121 | func (b *buffer) takeSmallBuffer(length int) []byte { 122 | if b.length == 0 { 123 | return b.buf[:length] 124 | } 125 | return nil 126 | } 127 | 128 | // takeCompleteBuffer returns the complete existing buffer. 129 | // This can be used if the necessary buffer size is unknown. 130 | // Only one buffer (total) can be used at a time. 131 | func (b *buffer) takeCompleteBuffer() []byte { 132 | if b.length == 0 { 133 | return b.buf 134 | } 135 | return nil 136 | } 137 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/go-sql-driver/mysql/collations.go: -------------------------------------------------------------------------------- 1 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 2 | // 3 | // Copyright 2014 The Go-MySQL-Driver Authors. All rights reserved. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 | // You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | package mysql 10 | 11 | const defaultCollation byte = 33 // utf8_general_ci 12 | 13 | // A list of available collations mapped to the internal ID. 14 | // To update this map use the following MySQL query: 15 | // SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS 16 | var collations = map[string]byte{ 17 | "big5_chinese_ci": 1, 18 | "latin2_czech_cs": 2, 19 | "dec8_swedish_ci": 3, 20 | "cp850_general_ci": 4, 21 | "latin1_german1_ci": 5, 22 | "hp8_english_ci": 6, 23 | "koi8r_general_ci": 7, 24 | "latin1_swedish_ci": 8, 25 | "latin2_general_ci": 9, 26 | "swe7_swedish_ci": 10, 27 | "ascii_general_ci": 11, 28 | "ujis_japanese_ci": 12, 29 | "sjis_japanese_ci": 13, 30 | "cp1251_bulgarian_ci": 14, 31 | "latin1_danish_ci": 15, 32 | "hebrew_general_ci": 16, 33 | "tis620_thai_ci": 18, 34 | "euckr_korean_ci": 19, 35 | "latin7_estonian_cs": 20, 36 | "latin2_hungarian_ci": 21, 37 | "koi8u_general_ci": 22, 38 | "cp1251_ukrainian_ci": 23, 39 | "gb2312_chinese_ci": 24, 40 | "greek_general_ci": 25, 41 | "cp1250_general_ci": 26, 42 | "latin2_croatian_ci": 27, 43 | "gbk_chinese_ci": 28, 44 | "cp1257_lithuanian_ci": 29, 45 | "latin5_turkish_ci": 30, 46 | "latin1_german2_ci": 31, 47 | "armscii8_general_ci": 32, 48 | "utf8_general_ci": 33, 49 | "cp1250_czech_cs": 34, 50 | "ucs2_general_ci": 35, 51 | "cp866_general_ci": 36, 52 | "keybcs2_general_ci": 37, 53 | "macce_general_ci": 38, 54 | "macroman_general_ci": 39, 55 | "cp852_general_ci": 40, 56 | "latin7_general_ci": 41, 57 | "latin7_general_cs": 42, 58 | "macce_bin": 43, 59 | "cp1250_croatian_ci": 44, 60 | "utf8mb4_general_ci": 45, 61 | "utf8mb4_bin": 46, 62 | "latin1_bin": 47, 63 | "latin1_general_ci": 48, 64 | "latin1_general_cs": 49, 65 | "cp1251_bin": 50, 66 | "cp1251_general_ci": 51, 67 | "cp1251_general_cs": 52, 68 | "macroman_bin": 53, 69 | "utf16_general_ci": 54, 70 | "utf16_bin": 55, 71 | "utf16le_general_ci": 56, 72 | "cp1256_general_ci": 57, 73 | "cp1257_bin": 58, 74 | "cp1257_general_ci": 59, 75 | "utf32_general_ci": 60, 76 | "utf32_bin": 61, 77 | "utf16le_bin": 62, 78 | "binary": 63, 79 | "armscii8_bin": 64, 80 | "ascii_bin": 65, 81 | "cp1250_bin": 66, 82 | "cp1256_bin": 67, 83 | "cp866_bin": 68, 84 | "dec8_bin": 69, 85 | "greek_bin": 70, 86 | "hebrew_bin": 71, 87 | "hp8_bin": 72, 88 | "keybcs2_bin": 73, 89 | "koi8r_bin": 74, 90 | "koi8u_bin": 75, 91 | "latin2_bin": 77, 92 | "latin5_bin": 78, 93 | "latin7_bin": 79, 94 | "cp850_bin": 80, 95 | "cp852_bin": 81, 96 | "swe7_bin": 82, 97 | "utf8_bin": 83, 98 | "big5_bin": 84, 99 | "euckr_bin": 85, 100 | "gb2312_bin": 86, 101 | "gbk_bin": 87, 102 | "sjis_bin": 88, 103 | "tis620_bin": 89, 104 | "ucs2_bin": 90, 105 | "ujis_bin": 91, 106 | "geostd8_general_ci": 92, 107 | "geostd8_bin": 93, 108 | "latin1_spanish_ci": 94, 109 | "cp932_japanese_ci": 95, 110 | "cp932_bin": 96, 111 | "eucjpms_japanese_ci": 97, 112 | "eucjpms_bin": 98, 113 | "cp1250_polish_ci": 99, 114 | "utf16_unicode_ci": 101, 115 | "utf16_icelandic_ci": 102, 116 | "utf16_latvian_ci": 103, 117 | "utf16_romanian_ci": 104, 118 | "utf16_slovenian_ci": 105, 119 | "utf16_polish_ci": 106, 120 | "utf16_estonian_ci": 107, 121 | "utf16_spanish_ci": 108, 122 | "utf16_swedish_ci": 109, 123 | "utf16_turkish_ci": 110, 124 | "utf16_czech_ci": 111, 125 | "utf16_danish_ci": 112, 126 | "utf16_lithuanian_ci": 113, 127 | "utf16_slovak_ci": 114, 128 | "utf16_spanish2_ci": 115, 129 | "utf16_roman_ci": 116, 130 | "utf16_persian_ci": 117, 131 | "utf16_esperanto_ci": 118, 132 | "utf16_hungarian_ci": 119, 133 | "utf16_sinhala_ci": 120, 134 | "utf16_german2_ci": 121, 135 | "utf16_croatian_ci": 122, 136 | "utf16_unicode_520_ci": 123, 137 | "utf16_vietnamese_ci": 124, 138 | "ucs2_unicode_ci": 128, 139 | "ucs2_icelandic_ci": 129, 140 | "ucs2_latvian_ci": 130, 141 | "ucs2_romanian_ci": 131, 142 | "ucs2_slovenian_ci": 132, 143 | "ucs2_polish_ci": 133, 144 | "ucs2_estonian_ci": 134, 145 | "ucs2_spanish_ci": 135, 146 | "ucs2_swedish_ci": 136, 147 | "ucs2_turkish_ci": 137, 148 | "ucs2_czech_ci": 138, 149 | "ucs2_danish_ci": 139, 150 | "ucs2_lithuanian_ci": 140, 151 | "ucs2_slovak_ci": 141, 152 | "ucs2_spanish2_ci": 142, 153 | "ucs2_roman_ci": 143, 154 | "ucs2_persian_ci": 144, 155 | "ucs2_esperanto_ci": 145, 156 | "ucs2_hungarian_ci": 146, 157 | "ucs2_sinhala_ci": 147, 158 | "ucs2_german2_ci": 148, 159 | "ucs2_croatian_ci": 149, 160 | "ucs2_unicode_520_ci": 150, 161 | "ucs2_vietnamese_ci": 151, 162 | "ucs2_general_mysql500_ci": 159, 163 | "utf32_unicode_ci": 160, 164 | "utf32_icelandic_ci": 161, 165 | "utf32_latvian_ci": 162, 166 | "utf32_romanian_ci": 163, 167 | "utf32_slovenian_ci": 164, 168 | "utf32_polish_ci": 165, 169 | "utf32_estonian_ci": 166, 170 | "utf32_spanish_ci": 167, 171 | "utf32_swedish_ci": 168, 172 | "utf32_turkish_ci": 169, 173 | "utf32_czech_ci": 170, 174 | "utf32_danish_ci": 171, 175 | "utf32_lithuanian_ci": 172, 176 | "utf32_slovak_ci": 173, 177 | "utf32_spanish2_ci": 174, 178 | "utf32_roman_ci": 175, 179 | "utf32_persian_ci": 176, 180 | "utf32_esperanto_ci": 177, 181 | "utf32_hungarian_ci": 178, 182 | "utf32_sinhala_ci": 179, 183 | "utf32_german2_ci": 180, 184 | "utf32_croatian_ci": 181, 185 | "utf32_unicode_520_ci": 182, 186 | "utf32_vietnamese_ci": 183, 187 | "utf8_unicode_ci": 192, 188 | "utf8_icelandic_ci": 193, 189 | "utf8_latvian_ci": 194, 190 | "utf8_romanian_ci": 195, 191 | "utf8_slovenian_ci": 196, 192 | "utf8_polish_ci": 197, 193 | "utf8_estonian_ci": 198, 194 | "utf8_spanish_ci": 199, 195 | "utf8_swedish_ci": 200, 196 | "utf8_turkish_ci": 201, 197 | "utf8_czech_ci": 202, 198 | "utf8_danish_ci": 203, 199 | "utf8_lithuanian_ci": 204, 200 | "utf8_slovak_ci": 205, 201 | "utf8_spanish2_ci": 206, 202 | "utf8_roman_ci": 207, 203 | "utf8_persian_ci": 208, 204 | "utf8_esperanto_ci": 209, 205 | "utf8_hungarian_ci": 210, 206 | "utf8_sinhala_ci": 211, 207 | "utf8_german2_ci": 212, 208 | "utf8_croatian_ci": 213, 209 | "utf8_unicode_520_ci": 214, 210 | "utf8_vietnamese_ci": 215, 211 | "utf8_general_mysql500_ci": 223, 212 | "utf8mb4_unicode_ci": 224, 213 | "utf8mb4_icelandic_ci": 225, 214 | "utf8mb4_latvian_ci": 226, 215 | "utf8mb4_romanian_ci": 227, 216 | "utf8mb4_slovenian_ci": 228, 217 | "utf8mb4_polish_ci": 229, 218 | "utf8mb4_estonian_ci": 230, 219 | "utf8mb4_spanish_ci": 231, 220 | "utf8mb4_swedish_ci": 232, 221 | "utf8mb4_turkish_ci": 233, 222 | "utf8mb4_czech_ci": 234, 223 | "utf8mb4_danish_ci": 235, 224 | "utf8mb4_lithuanian_ci": 236, 225 | "utf8mb4_slovak_ci": 237, 226 | "utf8mb4_spanish2_ci": 238, 227 | "utf8mb4_roman_ci": 239, 228 | "utf8mb4_persian_ci": 240, 229 | "utf8mb4_esperanto_ci": 241, 230 | "utf8mb4_hungarian_ci": 242, 231 | "utf8mb4_sinhala_ci": 243, 232 | "utf8mb4_german2_ci": 244, 233 | "utf8mb4_croatian_ci": 245, 234 | "utf8mb4_unicode_520_ci": 246, 235 | "utf8mb4_vietnamese_ci": 247, 236 | } 237 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/go-sql-driver/mysql/connection.go: -------------------------------------------------------------------------------- 1 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 2 | // 3 | // Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 | // You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | package mysql 10 | 11 | import ( 12 | "crypto/tls" 13 | "database/sql/driver" 14 | "errors" 15 | "net" 16 | "strings" 17 | "time" 18 | ) 19 | 20 | type mysqlConn struct { 21 | buf buffer 22 | netConn net.Conn 23 | affectedRows uint64 24 | insertId uint64 25 | cfg *config 26 | maxPacketAllowed int 27 | maxWriteSize int 28 | flags clientFlag 29 | sequence uint8 30 | parseTime bool 31 | strict bool 32 | } 33 | 34 | type config struct { 35 | user string 36 | passwd string 37 | net string 38 | addr string 39 | dbname string 40 | params map[string]string 41 | loc *time.Location 42 | tls *tls.Config 43 | timeout time.Duration 44 | collation uint8 45 | allowAllFiles bool 46 | allowOldPasswords bool 47 | clientFoundRows bool 48 | } 49 | 50 | // Handles parameters set in DSN after the connection is established 51 | func (mc *mysqlConn) handleParams() (err error) { 52 | for param, val := range mc.cfg.params { 53 | switch param { 54 | // Charset 55 | case "charset": 56 | charsets := strings.Split(val, ",") 57 | for i := range charsets { 58 | // ignore errors here - a charset may not exist 59 | err = mc.exec("SET NAMES " + charsets[i]) 60 | if err == nil { 61 | break 62 | } 63 | } 64 | if err != nil { 65 | return 66 | } 67 | 68 | // time.Time parsing 69 | case "parseTime": 70 | var isBool bool 71 | mc.parseTime, isBool = readBool(val) 72 | if !isBool { 73 | return errors.New("Invalid Bool value: " + val) 74 | } 75 | 76 | // Strict mode 77 | case "strict": 78 | var isBool bool 79 | mc.strict, isBool = readBool(val) 80 | if !isBool { 81 | return errors.New("Invalid Bool value: " + val) 82 | } 83 | 84 | // Compression 85 | case "compress": 86 | err = errors.New("Compression not implemented yet") 87 | return 88 | 89 | // System Vars 90 | default: 91 | err = mc.exec("SET " + param + "=" + val + "") 92 | if err != nil { 93 | return 94 | } 95 | } 96 | } 97 | 98 | return 99 | } 100 | 101 | func (mc *mysqlConn) Begin() (driver.Tx, error) { 102 | if mc.netConn == nil { 103 | errLog.Print(ErrInvalidConn) 104 | return nil, driver.ErrBadConn 105 | } 106 | err := mc.exec("START TRANSACTION") 107 | if err == nil { 108 | return &mysqlTx{mc}, err 109 | } 110 | 111 | return nil, err 112 | } 113 | 114 | func (mc *mysqlConn) Close() (err error) { 115 | // Makes Close idempotent 116 | if mc.netConn != nil { 117 | err = mc.writeCommandPacket(comQuit) 118 | if err == nil { 119 | err = mc.netConn.Close() 120 | } else { 121 | mc.netConn.Close() 122 | } 123 | mc.netConn = nil 124 | } 125 | 126 | mc.cfg = nil 127 | mc.buf.rd = nil 128 | 129 | return 130 | } 131 | 132 | func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) { 133 | if mc.netConn == nil { 134 | errLog.Print(ErrInvalidConn) 135 | return nil, driver.ErrBadConn 136 | } 137 | // Send command 138 | err := mc.writeCommandPacketStr(comStmtPrepare, query) 139 | if err != nil { 140 | return nil, err 141 | } 142 | 143 | stmt := &mysqlStmt{ 144 | mc: mc, 145 | } 146 | 147 | // Read Result 148 | columnCount, err := stmt.readPrepareResultPacket() 149 | if err == nil { 150 | if stmt.paramCount > 0 { 151 | if err = mc.readUntilEOF(); err != nil { 152 | return nil, err 153 | } 154 | } 155 | 156 | if columnCount > 0 { 157 | err = mc.readUntilEOF() 158 | } 159 | } 160 | 161 | return stmt, err 162 | } 163 | 164 | func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) { 165 | if mc.netConn == nil { 166 | errLog.Print(ErrInvalidConn) 167 | return nil, driver.ErrBadConn 168 | } 169 | if len(args) == 0 { // no args, fastpath 170 | mc.affectedRows = 0 171 | mc.insertId = 0 172 | 173 | err := mc.exec(query) 174 | if err == nil { 175 | return &mysqlResult{ 176 | affectedRows: int64(mc.affectedRows), 177 | insertId: int64(mc.insertId), 178 | }, err 179 | } 180 | return nil, err 181 | } 182 | 183 | // with args, must use prepared stmt 184 | return nil, driver.ErrSkip 185 | 186 | } 187 | 188 | // Internal function to execute commands 189 | func (mc *mysqlConn) exec(query string) error { 190 | // Send command 191 | err := mc.writeCommandPacketStr(comQuery, query) 192 | if err != nil { 193 | return err 194 | } 195 | 196 | // Read Result 197 | resLen, err := mc.readResultSetHeaderPacket() 198 | if err == nil && resLen > 0 { 199 | if err = mc.readUntilEOF(); err != nil { 200 | return err 201 | } 202 | 203 | err = mc.readUntilEOF() 204 | } 205 | 206 | return err 207 | } 208 | 209 | func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) { 210 | if mc.netConn == nil { 211 | errLog.Print(ErrInvalidConn) 212 | return nil, driver.ErrBadConn 213 | } 214 | if len(args) == 0 { // no args, fastpath 215 | // Send command 216 | err := mc.writeCommandPacketStr(comQuery, query) 217 | if err == nil { 218 | // Read Result 219 | var resLen int 220 | resLen, err = mc.readResultSetHeaderPacket() 221 | if err == nil { 222 | rows := new(textRows) 223 | rows.mc = mc 224 | 225 | if resLen == 0 { 226 | // no columns, no more data 227 | return emptyRows{}, nil 228 | } 229 | // Columns 230 | rows.columns, err = mc.readColumns(resLen) 231 | return rows, err 232 | } 233 | } 234 | return nil, err 235 | } 236 | 237 | // with args, must use prepared stmt 238 | return nil, driver.ErrSkip 239 | } 240 | 241 | // Gets the value of the given MySQL System Variable 242 | // The returned byte slice is only valid until the next read 243 | func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) { 244 | // Send command 245 | if err := mc.writeCommandPacketStr(comQuery, "SELECT @@"+name); err != nil { 246 | return nil, err 247 | } 248 | 249 | // Read Result 250 | resLen, err := mc.readResultSetHeaderPacket() 251 | if err == nil { 252 | rows := new(textRows) 253 | rows.mc = mc 254 | 255 | if resLen > 0 { 256 | // Columns 257 | if err := mc.readUntilEOF(); err != nil { 258 | return nil, err 259 | } 260 | } 261 | 262 | dest := make([]driver.Value, resLen) 263 | if err = rows.readRow(dest); err == nil { 264 | return dest[0].([]byte), mc.readUntilEOF() 265 | } 266 | } 267 | return nil, err 268 | } 269 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/go-sql-driver/mysql/const.go: -------------------------------------------------------------------------------- 1 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 2 | // 3 | // Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 | // You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | package mysql 10 | 11 | const ( 12 | minProtocolVersion byte = 10 13 | maxPacketSize = 1<<24 - 1 14 | timeFormat = "2006-01-02 15:04:05.999999" 15 | ) 16 | 17 | // MySQL constants documentation: 18 | // http://dev.mysql.com/doc/internals/en/client-server-protocol.html 19 | 20 | const ( 21 | iOK byte = 0x00 22 | iLocalInFile byte = 0xfb 23 | iEOF byte = 0xfe 24 | iERR byte = 0xff 25 | ) 26 | 27 | type clientFlag uint32 28 | 29 | const ( 30 | clientLongPassword clientFlag = 1 << iota 31 | clientFoundRows 32 | clientLongFlag 33 | clientConnectWithDB 34 | clientNoSchema 35 | clientCompress 36 | clientODBC 37 | clientLocalFiles 38 | clientIgnoreSpace 39 | clientProtocol41 40 | clientInteractive 41 | clientSSL 42 | clientIgnoreSIGPIPE 43 | clientTransactions 44 | clientReserved 45 | clientSecureConn 46 | clientMultiStatements 47 | clientMultiResults 48 | ) 49 | 50 | const ( 51 | comQuit byte = iota + 1 52 | comInitDB 53 | comQuery 54 | comFieldList 55 | comCreateDB 56 | comDropDB 57 | comRefresh 58 | comShutdown 59 | comStatistics 60 | comProcessInfo 61 | comConnect 62 | comProcessKill 63 | comDebug 64 | comPing 65 | comTime 66 | comDelayedInsert 67 | comChangeUser 68 | comBinlogDump 69 | comTableDump 70 | comConnectOut 71 | comRegiserSlave 72 | comStmtPrepare 73 | comStmtExecute 74 | comStmtSendLongData 75 | comStmtClose 76 | comStmtReset 77 | comSetOption 78 | comStmtFetch 79 | ) 80 | 81 | const ( 82 | fieldTypeDecimal byte = iota 83 | fieldTypeTiny 84 | fieldTypeShort 85 | fieldTypeLong 86 | fieldTypeFloat 87 | fieldTypeDouble 88 | fieldTypeNULL 89 | fieldTypeTimestamp 90 | fieldTypeLongLong 91 | fieldTypeInt24 92 | fieldTypeDate 93 | fieldTypeTime 94 | fieldTypeDateTime 95 | fieldTypeYear 96 | fieldTypeNewDate 97 | fieldTypeVarChar 98 | fieldTypeBit 99 | ) 100 | const ( 101 | fieldTypeNewDecimal byte = iota + 0xf6 102 | fieldTypeEnum 103 | fieldTypeSet 104 | fieldTypeTinyBLOB 105 | fieldTypeMediumBLOB 106 | fieldTypeLongBLOB 107 | fieldTypeBLOB 108 | fieldTypeVarString 109 | fieldTypeString 110 | fieldTypeGeometry 111 | ) 112 | 113 | type fieldFlag uint16 114 | 115 | const ( 116 | flagNotNULL fieldFlag = 1 << iota 117 | flagPriKey 118 | flagUniqueKey 119 | flagMultipleKey 120 | flagBLOB 121 | flagUnsigned 122 | flagZeroFill 123 | flagBinary 124 | flagEnum 125 | flagAutoIncrement 126 | flagTimestamp 127 | flagSet 128 | flagUnknown1 129 | flagUnknown2 130 | flagUnknown3 131 | flagUnknown4 132 | ) 133 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/go-sql-driver/mysql/driver.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 | // You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 8 | // 9 | // The driver should be used via the database/sql package: 10 | // 11 | // import "database/sql" 12 | // import _ "github.com/go-sql-driver/mysql" 13 | // 14 | // db, err := sql.Open("mysql", "user:password@/dbname") 15 | // 16 | // See https://github.com/go-sql-driver/mysql#usage for details 17 | package mysql 18 | 19 | import ( 20 | "database/sql" 21 | "database/sql/driver" 22 | "net" 23 | ) 24 | 25 | // This struct is exported to make the driver directly accessible. 26 | // In general the driver is used via the database/sql package. 27 | type MySQLDriver struct{} 28 | 29 | // DialFunc is a function which can be used to establish the network connection. 30 | // Custom dial functions must be registered with RegisterDial 31 | type DialFunc func(addr string) (net.Conn, error) 32 | 33 | var dials map[string]DialFunc 34 | 35 | // RegisterDial registers a custom dial function. It can then be used by the 36 | // network address mynet(addr), where mynet is the registered new network. 37 | // addr is passed as a parameter to the dial function. 38 | func RegisterDial(net string, dial DialFunc) { 39 | if dials == nil { 40 | dials = make(map[string]DialFunc) 41 | } 42 | dials[net] = dial 43 | } 44 | 45 | // Open new Connection. 46 | // See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how 47 | // the DSN string is formated 48 | func (d MySQLDriver) Open(dsn string) (driver.Conn, error) { 49 | var err error 50 | 51 | // New mysqlConn 52 | mc := &mysqlConn{ 53 | maxPacketAllowed: maxPacketSize, 54 | maxWriteSize: maxPacketSize - 1, 55 | } 56 | mc.cfg, err = parseDSN(dsn) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | // Connect to Server 62 | if dial, ok := dials[mc.cfg.net]; ok { 63 | mc.netConn, err = dial(mc.cfg.addr) 64 | } else { 65 | nd := net.Dialer{Timeout: mc.cfg.timeout} 66 | mc.netConn, err = nd.Dial(mc.cfg.net, mc.cfg.addr) 67 | } 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | // Enable TCP Keepalives on TCP connections 73 | if tc, ok := mc.netConn.(*net.TCPConn); ok { 74 | if err := tc.SetKeepAlive(true); err != nil { 75 | mc.Close() 76 | return nil, err 77 | } 78 | } 79 | 80 | mc.buf = newBuffer(mc.netConn) 81 | 82 | // Reading Handshake Initialization Packet 83 | cipher, err := mc.readInitPacket() 84 | if err != nil { 85 | mc.Close() 86 | return nil, err 87 | } 88 | 89 | // Send Client Authentication Packet 90 | if err = mc.writeAuthPacket(cipher); err != nil { 91 | mc.Close() 92 | return nil, err 93 | } 94 | 95 | // Read Result Packet 96 | err = mc.readResultOK() 97 | if err != nil { 98 | // Retry with old authentication method, if allowed 99 | if mc.cfg != nil && mc.cfg.allowOldPasswords && err == ErrOldPassword { 100 | if err = mc.writeOldAuthPacket(cipher); err != nil { 101 | mc.Close() 102 | return nil, err 103 | } 104 | if err = mc.readResultOK(); err != nil { 105 | mc.Close() 106 | return nil, err 107 | } 108 | } else { 109 | mc.Close() 110 | return nil, err 111 | } 112 | 113 | } 114 | 115 | // Get max allowed packet size 116 | maxap, err := mc.getSystemVar("max_allowed_packet") 117 | if err != nil { 118 | mc.Close() 119 | return nil, err 120 | } 121 | mc.maxPacketAllowed = stringToInt(maxap) - 1 122 | if mc.maxPacketAllowed < maxPacketSize { 123 | mc.maxWriteSize = mc.maxPacketAllowed 124 | } 125 | 126 | // Handle DSN Params 127 | err = mc.handleParams() 128 | if err != nil { 129 | mc.Close() 130 | return nil, err 131 | } 132 | 133 | return mc, nil 134 | } 135 | 136 | func init() { 137 | sql.Register("mysql", &MySQLDriver{}) 138 | } 139 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/go-sql-driver/mysql/errors.go: -------------------------------------------------------------------------------- 1 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 2 | // 3 | // Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 | // You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | package mysql 10 | 11 | import ( 12 | "database/sql/driver" 13 | "errors" 14 | "fmt" 15 | "io" 16 | "log" 17 | "os" 18 | ) 19 | 20 | // Various errors the driver might return. Can change between driver versions. 21 | var ( 22 | ErrInvalidConn = errors.New("Invalid Connection") 23 | ErrMalformPkt = errors.New("Malformed Packet") 24 | ErrNoTLS = errors.New("TLS encryption requested but server does not support TLS") 25 | ErrOldPassword = errors.New("This server only supports the insecure old password authentication. If you still want to use it, please add 'allowOldPasswords=1' to your DSN. See also https://github.com/go-sql-driver/mysql/wiki/old_passwords") 26 | ErrOldProtocol = errors.New("MySQL-Server does not support required Protocol 41+") 27 | ErrPktSync = errors.New("Commands out of sync. You can't run this command now") 28 | ErrPktSyncMul = errors.New("Commands out of sync. Did you run multiple statements at once?") 29 | ErrPktTooLarge = errors.New("Packet for query is too large. You can change this value on the server by adjusting the 'max_allowed_packet' variable.") 30 | ErrBusyBuffer = errors.New("Busy buffer") 31 | ) 32 | 33 | var errLog Logger = log.New(os.Stderr, "[MySQL] ", log.Ldate|log.Ltime|log.Lshortfile) 34 | 35 | // Logger is used to log critical error messages. 36 | type Logger interface { 37 | Print(v ...interface{}) 38 | } 39 | 40 | // SetLogger is used to set the logger for critical errors. 41 | // The initial logger is os.Stderr. 42 | func SetLogger(logger Logger) error { 43 | if logger == nil { 44 | return errors.New("logger is nil") 45 | } 46 | errLog = logger 47 | return nil 48 | } 49 | 50 | // MySQLError is an error type which represents a single MySQL error 51 | type MySQLError struct { 52 | Number uint16 53 | Message string 54 | } 55 | 56 | func (me *MySQLError) Error() string { 57 | return fmt.Sprintf("Error %d: %s", me.Number, me.Message) 58 | } 59 | 60 | // MySQLWarnings is an error type which represents a group of one or more MySQL 61 | // warnings 62 | type MySQLWarnings []MySQLWarning 63 | 64 | func (mws MySQLWarnings) Error() string { 65 | var msg string 66 | for i, warning := range mws { 67 | if i > 0 { 68 | msg += "\r\n" 69 | } 70 | msg += fmt.Sprintf( 71 | "%s %s: %s", 72 | warning.Level, 73 | warning.Code, 74 | warning.Message, 75 | ) 76 | } 77 | return msg 78 | } 79 | 80 | // MySQLWarning is an error type which represents a single MySQL warning. 81 | // Warnings are returned in groups only. See MySQLWarnings 82 | type MySQLWarning struct { 83 | Level string 84 | Code string 85 | Message string 86 | } 87 | 88 | func (mc *mysqlConn) getWarnings() (err error) { 89 | rows, err := mc.Query("SHOW WARNINGS", nil) 90 | if err != nil { 91 | return 92 | } 93 | 94 | var warnings = MySQLWarnings{} 95 | var values = make([]driver.Value, 3) 96 | 97 | for { 98 | err = rows.Next(values) 99 | switch err { 100 | case nil: 101 | warning := MySQLWarning{} 102 | 103 | if raw, ok := values[0].([]byte); ok { 104 | warning.Level = string(raw) 105 | } else { 106 | warning.Level = fmt.Sprintf("%s", values[0]) 107 | } 108 | if raw, ok := values[1].([]byte); ok { 109 | warning.Code = string(raw) 110 | } else { 111 | warning.Code = fmt.Sprintf("%s", values[1]) 112 | } 113 | if raw, ok := values[2].([]byte); ok { 114 | warning.Message = string(raw) 115 | } else { 116 | warning.Message = fmt.Sprintf("%s", values[0]) 117 | } 118 | 119 | warnings = append(warnings, warning) 120 | 121 | case io.EOF: 122 | return warnings 123 | 124 | default: 125 | rows.Close() 126 | return 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/go-sql-driver/mysql/errors_test.go: -------------------------------------------------------------------------------- 1 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 2 | // 3 | // Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 | // You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | package mysql 10 | 11 | import ( 12 | "bytes" 13 | "log" 14 | "testing" 15 | ) 16 | 17 | func TestErrorsSetLogger(t *testing.T) { 18 | previous := errLog 19 | defer func() { 20 | errLog = previous 21 | }() 22 | 23 | // set up logger 24 | const expected = "prefix: test\n" 25 | buffer := bytes.NewBuffer(make([]byte, 0, 64)) 26 | logger := log.New(buffer, "prefix: ", 0) 27 | 28 | // print 29 | SetLogger(logger) 30 | errLog.Print("test") 31 | 32 | // check result 33 | if actual := buffer.String(); actual != expected { 34 | t.Errorf("expected %q, got %q", expected, actual) 35 | } 36 | } 37 | 38 | func TestErrorsStrictIgnoreNotes(t *testing.T) { 39 | runTests(t, dsn+"&sql_notes=false", func(dbt *DBTest) { 40 | dbt.mustExec("DROP TABLE IF EXISTS does_not_exist") 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/go-sql-driver/mysql/infile.go: -------------------------------------------------------------------------------- 1 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 2 | // 3 | // Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 | // You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | package mysql 10 | 11 | import ( 12 | "fmt" 13 | "io" 14 | "os" 15 | "strings" 16 | ) 17 | 18 | var ( 19 | fileRegister map[string]bool 20 | readerRegister map[string]func() io.Reader 21 | ) 22 | 23 | // RegisterLocalFile adds the given file to the file whitelist, 24 | // so that it can be used by "LOAD DATA LOCAL INFILE ". 25 | // Alternatively you can allow the use of all local files with 26 | // the DSN parameter 'allowAllFiles=true' 27 | // 28 | // filePath := "/home/gopher/data.csv" 29 | // mysql.RegisterLocalFile(filePath) 30 | // err := db.Exec("LOAD DATA LOCAL INFILE '" + filePath + "' INTO TABLE foo") 31 | // if err != nil { 32 | // ... 33 | // 34 | func RegisterLocalFile(filePath string) { 35 | // lazy map init 36 | if fileRegister == nil { 37 | fileRegister = make(map[string]bool) 38 | } 39 | 40 | fileRegister[strings.Trim(filePath, `"`)] = true 41 | } 42 | 43 | // DeregisterLocalFile removes the given filepath from the whitelist. 44 | func DeregisterLocalFile(filePath string) { 45 | delete(fileRegister, strings.Trim(filePath, `"`)) 46 | } 47 | 48 | // RegisterReaderHandler registers a handler function which is used 49 | // to receive a io.Reader. 50 | // The Reader can be used by "LOAD DATA LOCAL INFILE Reader::". 51 | // If the handler returns a io.ReadCloser Close() is called when the 52 | // request is finished. 53 | // 54 | // mysql.RegisterReaderHandler("data", func() io.Reader { 55 | // var csvReader io.Reader // Some Reader that returns CSV data 56 | // ... // Open Reader here 57 | // return csvReader 58 | // }) 59 | // err := db.Exec("LOAD DATA LOCAL INFILE 'Reader::data' INTO TABLE foo") 60 | // if err != nil { 61 | // ... 62 | // 63 | func RegisterReaderHandler(name string, handler func() io.Reader) { 64 | // lazy map init 65 | if readerRegister == nil { 66 | readerRegister = make(map[string]func() io.Reader) 67 | } 68 | 69 | readerRegister[name] = handler 70 | } 71 | 72 | // DeregisterReaderHandler removes the ReaderHandler function with 73 | // the given name from the registry. 74 | func DeregisterReaderHandler(name string) { 75 | delete(readerRegister, name) 76 | } 77 | 78 | func deferredClose(err *error, closer io.Closer) { 79 | closeErr := closer.Close() 80 | if *err == nil { 81 | *err = closeErr 82 | } 83 | } 84 | 85 | func (mc *mysqlConn) handleInFileRequest(name string) (err error) { 86 | var rdr io.Reader 87 | var data []byte 88 | 89 | if strings.HasPrefix(name, "Reader::") { // io.Reader 90 | name = name[8:] 91 | if handler, inMap := readerRegister[name]; inMap { 92 | rdr = handler() 93 | if rdr != nil { 94 | data = make([]byte, 4+mc.maxWriteSize) 95 | 96 | if cl, ok := rdr.(io.Closer); ok { 97 | defer deferredClose(&err, cl) 98 | } 99 | } else { 100 | err = fmt.Errorf("Reader '%s' is ", name) 101 | } 102 | } else { 103 | err = fmt.Errorf("Reader '%s' is not registered", name) 104 | } 105 | } else { // File 106 | name = strings.Trim(name, `"`) 107 | if mc.cfg.allowAllFiles || fileRegister[name] { 108 | var file *os.File 109 | var fi os.FileInfo 110 | 111 | if file, err = os.Open(name); err == nil { 112 | defer deferredClose(&err, file) 113 | 114 | // get file size 115 | if fi, err = file.Stat(); err == nil { 116 | rdr = file 117 | if fileSize := int(fi.Size()); fileSize <= mc.maxWriteSize { 118 | data = make([]byte, 4+fileSize) 119 | } else if fileSize <= mc.maxPacketAllowed { 120 | data = make([]byte, 4+mc.maxWriteSize) 121 | } else { 122 | err = fmt.Errorf("Local File '%s' too large: Size: %d, Max: %d", name, fileSize, mc.maxPacketAllowed) 123 | } 124 | } 125 | } 126 | } else { 127 | err = fmt.Errorf("Local File '%s' is not registered. Use the DSN parameter 'allowAllFiles=true' to allow all files", name) 128 | } 129 | } 130 | 131 | // send content packets 132 | if err == nil { 133 | var n int 134 | for err == nil { 135 | n, err = rdr.Read(data[4:]) 136 | if n > 0 { 137 | if ioErr := mc.writePacket(data[:4+n]); ioErr != nil { 138 | return ioErr 139 | } 140 | } 141 | } 142 | if err == io.EOF { 143 | err = nil 144 | } 145 | } 146 | 147 | // send empty packet (termination) 148 | if data == nil { 149 | data = make([]byte, 4) 150 | } 151 | if ioErr := mc.writePacket(data[:4]); ioErr != nil { 152 | return ioErr 153 | } 154 | 155 | // read OK packet 156 | if err == nil { 157 | return mc.readResultOK() 158 | } else { 159 | mc.readPacket() 160 | } 161 | return err 162 | } 163 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/go-sql-driver/mysql/result.go: -------------------------------------------------------------------------------- 1 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 2 | // 3 | // Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 | // You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | package mysql 10 | 11 | type mysqlResult struct { 12 | affectedRows int64 13 | insertId int64 14 | } 15 | 16 | func (res *mysqlResult) LastInsertId() (int64, error) { 17 | return res.insertId, nil 18 | } 19 | 20 | func (res *mysqlResult) RowsAffected() (int64, error) { 21 | return res.affectedRows, nil 22 | } 23 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/go-sql-driver/mysql/rows.go: -------------------------------------------------------------------------------- 1 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 2 | // 3 | // Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 | // You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | package mysql 10 | 11 | import ( 12 | "database/sql/driver" 13 | "io" 14 | ) 15 | 16 | type mysqlField struct { 17 | name string 18 | flags fieldFlag 19 | fieldType byte 20 | decimals byte 21 | } 22 | 23 | type mysqlRows struct { 24 | mc *mysqlConn 25 | columns []mysqlField 26 | } 27 | 28 | type binaryRows struct { 29 | mysqlRows 30 | } 31 | 32 | type textRows struct { 33 | mysqlRows 34 | } 35 | 36 | type emptyRows struct{} 37 | 38 | func (rows *mysqlRows) Columns() []string { 39 | columns := make([]string, len(rows.columns)) 40 | for i := range columns { 41 | columns[i] = rows.columns[i].name 42 | } 43 | return columns 44 | } 45 | 46 | func (rows *mysqlRows) Close() error { 47 | mc := rows.mc 48 | if mc == nil { 49 | return nil 50 | } 51 | if mc.netConn == nil { 52 | return ErrInvalidConn 53 | } 54 | 55 | // Remove unread packets from stream 56 | err := mc.readUntilEOF() 57 | rows.mc = nil 58 | return err 59 | } 60 | 61 | func (rows *binaryRows) Next(dest []driver.Value) error { 62 | if mc := rows.mc; mc != nil { 63 | if mc.netConn == nil { 64 | return ErrInvalidConn 65 | } 66 | 67 | // Fetch next row from stream 68 | if err := rows.readRow(dest); err != io.EOF { 69 | return err 70 | } 71 | rows.mc = nil 72 | } 73 | return io.EOF 74 | } 75 | 76 | func (rows *textRows) Next(dest []driver.Value) error { 77 | if mc := rows.mc; mc != nil { 78 | if mc.netConn == nil { 79 | return ErrInvalidConn 80 | } 81 | 82 | // Fetch next row from stream 83 | if err := rows.readRow(dest); err != io.EOF { 84 | return err 85 | } 86 | rows.mc = nil 87 | } 88 | return io.EOF 89 | } 90 | 91 | func (rows emptyRows) Columns() []string { 92 | return nil 93 | } 94 | 95 | func (rows emptyRows) Close() error { 96 | return nil 97 | } 98 | 99 | func (rows emptyRows) Next(dest []driver.Value) error { 100 | return io.EOF 101 | } 102 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/go-sql-driver/mysql/statement.go: -------------------------------------------------------------------------------- 1 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 2 | // 3 | // Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 | // You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | package mysql 10 | 11 | import ( 12 | "database/sql/driver" 13 | ) 14 | 15 | type mysqlStmt struct { 16 | mc *mysqlConn 17 | id uint32 18 | paramCount int 19 | columns []mysqlField // cached from the first query 20 | } 21 | 22 | func (stmt *mysqlStmt) Close() error { 23 | if stmt.mc == nil || stmt.mc.netConn == nil { 24 | errLog.Print(ErrInvalidConn) 25 | return driver.ErrBadConn 26 | } 27 | 28 | err := stmt.mc.writeCommandPacketUint32(comStmtClose, stmt.id) 29 | stmt.mc = nil 30 | return err 31 | } 32 | 33 | func (stmt *mysqlStmt) NumInput() int { 34 | return stmt.paramCount 35 | } 36 | 37 | func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) { 38 | if stmt.mc.netConn == nil { 39 | errLog.Print(ErrInvalidConn) 40 | return nil, driver.ErrBadConn 41 | } 42 | // Send command 43 | err := stmt.writeExecutePacket(args) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | mc := stmt.mc 49 | 50 | mc.affectedRows = 0 51 | mc.insertId = 0 52 | 53 | // Read Result 54 | resLen, err := mc.readResultSetHeaderPacket() 55 | if err == nil { 56 | if resLen > 0 { 57 | // Columns 58 | err = mc.readUntilEOF() 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | // Rows 64 | err = mc.readUntilEOF() 65 | } 66 | if err == nil { 67 | return &mysqlResult{ 68 | affectedRows: int64(mc.affectedRows), 69 | insertId: int64(mc.insertId), 70 | }, nil 71 | } 72 | } 73 | 74 | return nil, err 75 | } 76 | 77 | func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) { 78 | if stmt.mc.netConn == nil { 79 | errLog.Print(ErrInvalidConn) 80 | return nil, driver.ErrBadConn 81 | } 82 | // Send command 83 | err := stmt.writeExecutePacket(args) 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | mc := stmt.mc 89 | 90 | // Read Result 91 | resLen, err := mc.readResultSetHeaderPacket() 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | rows := new(binaryRows) 97 | rows.mc = mc 98 | 99 | if resLen > 0 { 100 | // Columns 101 | // If not cached, read them and cache them 102 | if stmt.columns == nil { 103 | rows.columns, err = mc.readColumns(resLen) 104 | stmt.columns = rows.columns 105 | } else { 106 | rows.columns = stmt.columns 107 | err = mc.readUntilEOF() 108 | } 109 | } 110 | 111 | return rows, err 112 | } 113 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/go-sql-driver/mysql/transaction.go: -------------------------------------------------------------------------------- 1 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 2 | // 3 | // Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 | // You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | package mysql 10 | 11 | type mysqlTx struct { 12 | mc *mysqlConn 13 | } 14 | 15 | func (tx *mysqlTx) Commit() (err error) { 16 | if tx.mc == nil || tx.mc.netConn == nil { 17 | return ErrInvalidConn 18 | } 19 | err = tx.mc.exec("COMMIT") 20 | tx.mc = nil 21 | return 22 | } 23 | 24 | func (tx *mysqlTx) Rollback() (err error) { 25 | if tx.mc == nil || tx.mc.netConn == nil { 26 | return ErrInvalidConn 27 | } 28 | err = tx.mc.exec("ROLLBACK") 29 | tx.mc = nil 30 | return 31 | } 32 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/go-sql-driver/mysql/utils_test.go: -------------------------------------------------------------------------------- 1 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 2 | // 3 | // Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 | // You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | package mysql 10 | 11 | import ( 12 | "bytes" 13 | "encoding/binary" 14 | "fmt" 15 | "testing" 16 | "time" 17 | ) 18 | 19 | var testDSNs = []struct { 20 | in string 21 | out string 22 | loc *time.Location 23 | }{ 24 | {"username:password@protocol(address)/dbname?param=value", "&{user:username passwd:password net:protocol addr:address dbname:dbname params:map[param:value] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC}, 25 | {"user@unix(/path/to/socket)/dbname?charset=utf8", "&{user:user passwd: net:unix addr:/path/to/socket dbname:dbname params:map[charset:utf8] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC}, 26 | {"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true", "&{user:user passwd:password net:tcp addr:localhost:5555 dbname:dbname params:map[charset:utf8] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC}, 27 | {"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify", "&{user:user passwd:password net:tcp addr:localhost:5555 dbname:dbname params:map[charset:utf8mb4,utf8] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC}, 28 | {"user:password@/dbname?loc=UTC&timeout=30s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci", "&{user:user passwd:password net:tcp addr:127.0.0.1:3306 dbname:dbname params:map[] loc:%p tls: timeout:30000000000 collation:224 allowAllFiles:true allowOldPasswords:true clientFoundRows:true}", time.UTC}, 29 | {"user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local", "&{user:user passwd:p@ss(word) net:tcp addr:[de:ad:be:ef::ca:fe]:80 dbname:dbname params:map[] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.Local}, 30 | {"/dbname", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname:dbname params:map[] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC}, 31 | {"@/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC}, 32 | {"/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC}, 33 | {"", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC}, 34 | {"user:p@/ssword@/", "&{user:user passwd:p@/ssword net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC}, 35 | {"unix/?arg=%2Fsome%2Fpath.ext", "&{user: passwd: net:unix addr:/tmp/mysql.sock dbname: params:map[arg:/some/path.ext] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC}, 36 | } 37 | 38 | func TestDSNParser(t *testing.T) { 39 | var cfg *config 40 | var err error 41 | var res string 42 | 43 | for i, tst := range testDSNs { 44 | cfg, err = parseDSN(tst.in) 45 | if err != nil { 46 | t.Error(err.Error()) 47 | } 48 | 49 | // pointer not static 50 | cfg.tls = nil 51 | 52 | res = fmt.Sprintf("%+v", cfg) 53 | if res != fmt.Sprintf(tst.out, tst.loc) { 54 | t.Errorf("%d. parseDSN(%q) => %q, want %q", i, tst.in, res, fmt.Sprintf(tst.out, tst.loc)) 55 | } 56 | } 57 | } 58 | 59 | func TestDSNParserInvalid(t *testing.T) { 60 | var invalidDSNs = []string{ 61 | "@net(addr/", // no closing brace 62 | "@tcp(/", // no closing brace 63 | "tcp(/", // no closing brace 64 | "(/", // no closing brace 65 | "net(addr)//", // unescaped 66 | "user:pass@tcp(1.2.3.4:3306)", // no trailing slash 67 | //"/dbname?arg=/some/unescaped/path", 68 | } 69 | 70 | for i, tst := range invalidDSNs { 71 | if _, err := parseDSN(tst); err == nil { 72 | t.Errorf("invalid DSN #%d. (%s) didn't error!", i, tst) 73 | } 74 | } 75 | } 76 | 77 | func BenchmarkParseDSN(b *testing.B) { 78 | b.ReportAllocs() 79 | 80 | for i := 0; i < b.N; i++ { 81 | for _, tst := range testDSNs { 82 | if _, err := parseDSN(tst.in); err != nil { 83 | b.Error(err.Error()) 84 | } 85 | } 86 | } 87 | } 88 | 89 | func TestScanNullTime(t *testing.T) { 90 | var scanTests = []struct { 91 | in interface{} 92 | error bool 93 | valid bool 94 | time time.Time 95 | }{ 96 | {tDate, false, true, tDate}, 97 | {sDate, false, true, tDate}, 98 | {[]byte(sDate), false, true, tDate}, 99 | {tDateTime, false, true, tDateTime}, 100 | {sDateTime, false, true, tDateTime}, 101 | {[]byte(sDateTime), false, true, tDateTime}, 102 | {tDate0, false, true, tDate0}, 103 | {sDate0, false, true, tDate0}, 104 | {[]byte(sDate0), false, true, tDate0}, 105 | {sDateTime0, false, true, tDate0}, 106 | {[]byte(sDateTime0), false, true, tDate0}, 107 | {"", true, false, tDate0}, 108 | {"1234", true, false, tDate0}, 109 | {0, true, false, tDate0}, 110 | } 111 | 112 | var nt = NullTime{} 113 | var err error 114 | 115 | for _, tst := range scanTests { 116 | err = nt.Scan(tst.in) 117 | if (err != nil) != tst.error { 118 | t.Errorf("%v: expected error status %t, got %t", tst.in, tst.error, (err != nil)) 119 | } 120 | if nt.Valid != tst.valid { 121 | t.Errorf("%v: expected valid status %t, got %t", tst.in, tst.valid, nt.Valid) 122 | } 123 | if nt.Time != tst.time { 124 | t.Errorf("%v: expected time %v, got %v", tst.in, tst.time, nt.Time) 125 | } 126 | } 127 | } 128 | 129 | func TestLengthEncodedInteger(t *testing.T) { 130 | var integerTests = []struct { 131 | num uint64 132 | encoded []byte 133 | }{ 134 | {0x0000000000000000, []byte{0x00}}, 135 | {0x0000000000000012, []byte{0x12}}, 136 | {0x00000000000000fa, []byte{0xfa}}, 137 | {0x0000000000000100, []byte{0xfc, 0x00, 0x01}}, 138 | {0x0000000000001234, []byte{0xfc, 0x34, 0x12}}, 139 | {0x000000000000ffff, []byte{0xfc, 0xff, 0xff}}, 140 | {0x0000000000010000, []byte{0xfd, 0x00, 0x00, 0x01}}, 141 | {0x0000000000123456, []byte{0xfd, 0x56, 0x34, 0x12}}, 142 | {0x0000000000ffffff, []byte{0xfd, 0xff, 0xff, 0xff}}, 143 | {0x0000000001000000, []byte{0xfe, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}}, 144 | {0x123456789abcdef0, []byte{0xfe, 0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12}}, 145 | {0xffffffffffffffff, []byte{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, 146 | } 147 | 148 | for _, tst := range integerTests { 149 | num, isNull, numLen := readLengthEncodedInteger(tst.encoded) 150 | if isNull { 151 | t.Errorf("%x: expected %d, got NULL", tst.encoded, tst.num) 152 | } 153 | if num != tst.num { 154 | t.Errorf("%x: expected %d, got %d", tst.encoded, tst.num, num) 155 | } 156 | if numLen != len(tst.encoded) { 157 | t.Errorf("%x: expected size %d, got %d", tst.encoded, len(tst.encoded), numLen) 158 | } 159 | encoded := appendLengthEncodedInteger(nil, num) 160 | if !bytes.Equal(encoded, tst.encoded) { 161 | t.Errorf("%v: expected %x, got %x", num, tst.encoded, encoded) 162 | } 163 | } 164 | } 165 | 166 | func TestOldPass(t *testing.T) { 167 | scramble := []byte{9, 8, 7, 6, 5, 4, 3, 2} 168 | vectors := []struct { 169 | pass string 170 | out string 171 | }{ 172 | {" pass", "47575c5a435b4251"}, 173 | {"pass ", "47575c5a435b4251"}, 174 | {"123\t456", "575c47505b5b5559"}, 175 | {"C0mpl!ca ted#PASS123", "5d5d554849584a45"}, 176 | } 177 | for _, tuple := range vectors { 178 | ours := scrambleOldPassword(scramble, []byte(tuple.pass)) 179 | if tuple.out != fmt.Sprintf("%x", ours) { 180 | t.Errorf("Failed old password %q", tuple.pass) 181 | } 182 | } 183 | } 184 | 185 | func TestFormatBinaryDateTime(t *testing.T) { 186 | rawDate := [11]byte{} 187 | binary.LittleEndian.PutUint16(rawDate[:2], 1978) // years 188 | rawDate[2] = 12 // months 189 | rawDate[3] = 30 // days 190 | rawDate[4] = 15 // hours 191 | rawDate[5] = 46 // minutes 192 | rawDate[6] = 23 // seconds 193 | binary.LittleEndian.PutUint32(rawDate[7:], 987654) // microseconds 194 | expect := func(expected string, inlen, outlen uint8) { 195 | actual, _ := formatBinaryDateTime(rawDate[:inlen], outlen, false) 196 | bytes, ok := actual.([]byte) 197 | if !ok { 198 | t.Errorf("formatBinaryDateTime must return []byte, was %T", actual) 199 | } 200 | if string(bytes) != expected { 201 | t.Errorf( 202 | "expected %q, got %q for length in %d, out %d", 203 | bytes, actual, inlen, outlen, 204 | ) 205 | } 206 | } 207 | expect("0000-00-00", 0, 10) 208 | expect("0000-00-00 00:00:00", 0, 19) 209 | expect("1978-12-30", 4, 10) 210 | expect("1978-12-30 15:46:23", 7, 19) 211 | expect("1978-12-30 15:46:23.987654", 11, 26) 212 | } 213 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include build_config.mk 2 | 3 | all: build 4 | 5 | build: 6 | $(GO) install ./... 7 | 8 | clean: 9 | @rm -rf bin ./build_config.mk 10 | 11 | test: 12 | $(GO) test ./... -race -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | ## 1. Overview [中文主页](Readme_zh.md) 2 | [![Build Status](https://travis-ci.org/flike/idgo.svg?branch=master)](https://travis-ci.org/flike/idgo) 3 | 4 | Idgo is a sequential id generator which can generate batch ids through MySQL transcation way. Its features as follows: 5 | 6 | - The id generated by idgo is by order. 7 | - generate batch ids through MySQL transcation way, dose not increase the load of MySQL. 8 | - When idgo process crashed, admin can restart idgo, does not worry about generating repetitive ids. 9 | - Idgo talks with clients using redis protocol, developer can connect idgo using redis sdk. 10 | 11 | Someone knows the resolution of generating id with MySQL: 12 | 13 | ``` 14 | REPLACE INTO Tickets64 (stub) VALUES ('a'); 15 | SELECT LAST_INSERT_ID(); 16 | 17 | ``` 18 | 19 | The disadvantage of this resolution is that generates one id need query MySQL once. When generating id too quickly, leading MySQL overload. This is why I build this project to generating ids. 20 | 21 | ## 2. The architecture of idgo 22 | 23 | Idgo talks with clients using redis protocol, developer can connect to idgo using redis sdk. Every Key will mapped to a table storing in MySQL, and the table only has one row data to record the id. 24 | 25 | ![idgo_arch](http://ww2.sinaimg.cn/large/6e5705a5gw1f2nz3bot3tj20qo0k0mxe.jpg) 26 | 27 | Idgo only supports four commands of redis as follows: 28 | 29 | - `SET key value`, set the initial value of id generator in idgo. 30 | - `GET key`, get the value of key. 31 | - `EXISTS key`, check the key if exist. 32 | - `DEL key`, delete the key in idgo. 33 | - `SELECT index`, just a mock select command, prevent the select command error. 34 | 35 | ## 3. Install and use idgo 36 | 37 | Install idgo following these steps: 38 | 39 | ``` 40 | 1. Install Go environment, the version of Go 41 | is greater than 1.3. 42 | 2. Install godep. `go get github.com/tools/godep`. 43 | 3. git clone https://github.com/flike/idgo src/github.com/flike/idgo 44 | 4. cd src/github.com/flike/idgo 45 | 5. source ./dev.sh 46 | 6. make 47 | 7. set the config file. 48 | 8. run idgo. `./bin/idgo -config=etc/idgo.toml` 49 | 50 | ``` 51 | Set the config file(etc/idgo.toml): 52 | 53 | ``` 54 | #the address of idgo 55 | addr="127.0.0.1:6389" 56 | #log_path: /Users/flike/src 57 | log_level="debug" 58 | 59 | [storage_db] 60 | mysql_host="127.0.0.1" 61 | mysql_port=3306 62 | db_name="idgo_test" 63 | user="root" 64 | password="" 65 | max_idle_conns=64 66 | 67 | ``` 68 | 69 | Examples: 70 | 71 | ``` 72 | 73 | #start idgo 74 | ➜ idgo git:(master) ✗ ./bin/idgo -config=etc/idgo.toml 75 | 2016/04/07 11:51:20 - INFO - server.go:[62] - [server] "NewServer" "Server running" "netProto=tcp|address=127.0.0.1:6389" req_id=0 76 | 2016/04/07 11:51:20 - INFO - main.go:[80] - [main] "main" "Idgo start!" "" req_id=0 77 | 78 | #start a redis client to connecting idgo, and set/get the key. 79 | ➜ ~ redis-cli -p 6389 80 | redis 127.0.0.1:6389> get abc 81 | (integer) 0 82 | redis 127.0.0.1:6389> set abc 100 83 | OK 84 | redis 127.0.0.1:6389> get abc 85 | (integer) 101 86 | redis 127.0.0.1:6389> get abc 87 | (integer) 102 88 | redis 127.0.0.1:6389> get abc 89 | (integer) 103 90 | redis 127.0.0.1:6389> get abc 91 | (integer) 104 92 | 93 | ``` 94 | 95 | ## 4. HA 96 | 97 | When the idgo crashed, you can restart idgo and reset the key by increasing a fixed offset. 98 | 99 | ## 5. License 100 | 101 | MIT 102 | -------------------------------------------------------------------------------- /Readme_zh.md: -------------------------------------------------------------------------------- 1 | # idgo 简介 2 | [![Build Status](https://travis-ci.org/flike/idgo.svg?branch=master)](https://travis-ci.org/flike/idgo) 3 | ## 1. idgo特点 4 | 5 | idgo是一个利用MySQL批量生成ID的ID生成器, 主要有以下特点: 6 | 7 | - 生成的ID是顺序递增的。 8 | - 每次通过事务批量取ID,性能较高,且不会对MySQL造成压力。 9 | - 当ID生成器服务崩溃后,可以继续生成有效ID,避免了ID回绕的风险。 10 | - 服务端模拟Redis协议,通过`GET`和`SET`获取和设置key。不必开发专门的获取ID的SDK,直接使用Reids的SDK就可。 11 | 12 | 业界已经有利于MySQL生成ID的方案,都是通过: 13 | 14 | ``` 15 | REPLACE INTO Tickets64 (stub) VALUES ('a'); 16 | SELECT LAST_INSERT_ID(); 17 | ``` 18 | 这种方式生成ID的弊端就是每生成一个ID都需要查询一下MySQL,当ID生成过快时会对MySQL造成很大的压力。这正是我开发这个项目的原因。服务端兼容Redis协议是为了避免单独开发和idgo通信的SDK。 19 | 20 | ## 2. idgo架构 21 | idgo和前端应用是采用redis协议通信的,然后每个`id_key`是存储在MySQL数据库中,每个key会在MySQL中生成一张表,表中只有一条记录。这样做的目的是保证当idgo由于意外崩溃后,`id_key`对应的值不会丢失,这样就避免产生了id回绕的风险。 22 | ![idgo_arch](http://ww2.sinaimg.cn/large/6e5705a5gw1f2nz3bot3tj20qo0k0mxe.jpg) 23 | 24 | idgo目前只支持四个redis命令: 25 | 26 | ``` 27 | 1. SET key value,通过这个操作设置id生成器的初始值。 28 | 例如:SET abc 123 29 | 2. GET key,通过该命令获取id。 30 | 3. EXISTS key,查看一个key是否存在。 31 | 4. DEL key,删除一个key。 32 | 5. SELECT index,选择一个db,目前是一个假方法,没实现任何功能,只是为了避免初始化客户端时调用SELECT出错。 33 | ``` 34 | 35 | 36 | ## 3. 安装和使用idgo 37 | 38 | 1. 安装idgo 39 | 40 | ``` 41 | 1. 安装Go语言环境(Go版本1.3以上),具体步骤请Google。 42 | 2. 安装godep工具, go get github.com/tools/godep 。 43 | 2. git clone https://github.com/flike/idgo src/github.com/flike/idgo 44 | 3. cd src/github.com/flike/idgo 45 | 4. source ./dev.sh 46 | 5. make 47 | 6. 设置配置文件 48 | 7. 运行idgo。./bin/idgo -config=etc/idgo.toml 49 | ``` 50 | 51 | 52 | 设置配置文件(`etc/idgo.toml`): 53 | 54 | ``` 55 | #idgo的IP和port 56 | addr="127.0.0.1:6389" 57 | #log_path: /Users/flike/src 58 | #日志级别 59 | log_level="debug" 60 | 61 | [storage_db] 62 | mysql_host="127.0.0.1" 63 | mysql_port=3306 64 | db_name="idgo_test" 65 | user="root" 66 | password="" 67 | max_idle_conns=64 68 | ``` 69 | 70 | 操作演示: 71 | 72 | ``` 73 | #启动idgo 74 | ➜ idgo git:(master) ✗ ./bin/idgo -config=etc/idgo.toml 75 | 2016/04/07 11:51:20 - INFO - server.go:[62] - [server] "NewServer" "Server running" "netProto=tcp|address=127.0.0.1:6389" req_id=0 76 | 2016/04/07 11:51:20 - INFO - main.go:[80] - [main] "main" "Idgo start!" "" req_id=0 77 | 78 | #启动一个客户端连接idgo 79 | ➜ ~ redis-cli -p 6389 80 | redis 127.0.0.1:6389> get abc 81 | (integer) 0 82 | redis 127.0.0.1:6389> set abc 100 83 | OK 84 | redis 127.0.0.1:6389> get abc 85 | (integer) 101 86 | redis 127.0.0.1:6389> get abc 87 | (integer) 102 88 | redis 127.0.0.1:6389> get abc 89 | (integer) 103 90 | redis 127.0.0.1:6389> get abc 91 | (integer) 104 92 | redis 127.0.0.1:6389> 93 | 94 | ``` 95 | 96 | ## 4. 压力测试 97 | 压测环境 98 | 99 | |类别|名称| 100 | |---|---| 101 | |OS |CentOS release 6.4| 102 | |CPU |Common KVM CPU @ 2.13GHz| 103 | |RAM |2GB| 104 | |DISK |50GB| 105 | |Mysql |5.1.73| 106 | 107 | 本地mac连接远程该MySQL实例压测ID生成服务。 108 | 每秒钟可以生成20多万个ID。性能方面完全不会有瓶颈。 109 | 110 | ## 5.ID生成服务宕机后的恢复方案 111 | 112 | 当idgo服务意外宕机后,可以切从库,然后将idgo对应的key加上适当的偏移量。 113 | 114 | # License 115 | 116 | MIT 117 | -------------------------------------------------------------------------------- /cmd/idgo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "os/signal" 8 | "path" 9 | "runtime" 10 | "strings" 11 | "syscall" 12 | 13 | "github.com/flike/golog" 14 | "github.com/flike/idgo/config" 15 | "github.com/flike/idgo/server" 16 | ) 17 | 18 | var configFile *string = flag.String("config", "etc/idgo.toml", "idgo config file") 19 | var logLevel *string = flag.String("log-level", "", "log level [debug|info|warn|error], default error") 20 | 21 | const ( 22 | sysLogName = "sys.log" 23 | MaxLogSize = 1024 * 1024 * 1024 24 | ) 25 | 26 | func main() { 27 | runtime.GOMAXPROCS(runtime.NumCPU()) 28 | flag.Parse() 29 | 30 | if len(*configFile) == 0 { 31 | fmt.Println("must use a config file") 32 | return 33 | } 34 | 35 | cfg, err := config.ParseConfigFile(*configFile) 36 | if err != nil { 37 | fmt.Printf("parse config file error:%v\n", err.Error()) 38 | return 39 | } 40 | 41 | //when the log file size greater than 1GB, kingtask will generate a new file 42 | if len(cfg.LogPath) != 0 { 43 | sysFilePath := path.Join(cfg.LogPath, sysLogName) 44 | sysFile, err := golog.NewRotatingFileHandler(sysFilePath, MaxLogSize, 1) 45 | if err != nil { 46 | fmt.Printf("new log file error:%v\n", err.Error()) 47 | return 48 | } 49 | golog.GlobalLogger = golog.New(sysFile, golog.Lfile|golog.Ltime|golog.Llevel) 50 | } 51 | 52 | if *logLevel != "" { 53 | setLogLevel(*logLevel) 54 | } else { 55 | setLogLevel(cfg.LogLevel) 56 | } 57 | 58 | var s *server.Server 59 | s, err = server.NewServer(cfg) 60 | if err != nil { 61 | golog.Error("main", "main", err.Error(), 0) 62 | golog.GlobalLogger.Close() 63 | s.Close() 64 | return 65 | } 66 | 67 | err = s.Init() 68 | if err != nil { 69 | golog.Error("main", "main", err.Error(), 0) 70 | golog.GlobalLogger.Close() 71 | s.Close() 72 | return 73 | } 74 | 75 | sc := make(chan os.Signal, 1) 76 | signal.Notify(sc, 77 | syscall.SIGHUP, 78 | syscall.SIGINT, 79 | syscall.SIGTERM, 80 | syscall.SIGQUIT) 81 | 82 | go func() { 83 | sig := <-sc 84 | golog.Info("main", "main", "Got signal", 0, "signal", sig) 85 | golog.GlobalLogger.Close() 86 | s.Close() 87 | }() 88 | golog.Info("main", "main", "Idgo start!", 0) 89 | s.Serve() 90 | } 91 | 92 | func setLogLevel(level string) { 93 | switch strings.ToLower(level) { 94 | case "debug": 95 | golog.GlobalLogger.SetLevel(golog.LevelDebug) 96 | case "info": 97 | golog.GlobalLogger.SetLevel(golog.LevelInfo) 98 | case "warn": 99 | golog.GlobalLogger.SetLevel(golog.LevelWarn) 100 | case "error": 101 | golog.GlobalLogger.SetLevel(golog.LevelError) 102 | default: 103 | golog.GlobalLogger.SetLevel(golog.LevelError) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "io/ioutil" 5 | 6 | "github.com/BurntSushi/toml" 7 | ) 8 | 9 | type Config struct { 10 | Addr string `toml:"addr"` 11 | LogPath string `toml:"log_path"` 12 | LogLevel string `toml:"log_level"` 13 | DatabaseConfig *DBConfig `toml:"storage_db"` 14 | } 15 | 16 | type DBConfig struct { 17 | Host string `toml:"mysql_host"` 18 | Port int `toml:"mysql_port"` 19 | User string `toml:"user"` 20 | Password string `toml:"password"` 21 | DBName string `toml:"db_name"` 22 | MaxIdleConns int `toml:"max_idle_conns"` 23 | } 24 | 25 | func ParseConfigFile(fileName string) (*Config, error) { 26 | var cfg Config 27 | 28 | data, err := ioutil.ReadFile(fileName) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | _, err = toml.Decode(string(data), &cfg) 34 | if err != nil { 35 | return nil, err 36 | } 37 | return &cfg, nil 38 | } 39 | -------------------------------------------------------------------------------- /dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export QINGTOP=$(pwd) 4 | export QINGROOT="${QINGROOT:-${QINGTOP/\/src\/github.com\/flike\/idgo/}}" 5 | # QINGTOP sanity check 6 | if [[ "$QINGTOP" == "${QINGTOP/\/src\/github.com\/flike\/idgo/}" ]]; then 7 | echo "WARNING: QINGTOP($QINGTOP) does not contain src/github.com/flike/idgo" 8 | exit 1 9 | fi 10 | 11 | function add_path() 12 | { 13 | # $1 path variable 14 | # $2 path to add 15 | if [ -d "$2" ] && [[ ":$1:" != *":$2:"* ]]; then 16 | echo "$1:$2" 17 | else 18 | echo "$1" 19 | fi 20 | } 21 | 22 | export GOBIN=$QINGTOP/bin 23 | 24 | godep path > /dev/null 2>&1 25 | if [ "$?" = 0 ]; then 26 | echo "GO=godep go" > build_config.mk 27 | export GOPATH=`godep path` 28 | # godep restore 29 | else 30 | echo "GO=go" > build_config.mk 31 | fi 32 | 33 | export GOPATH="$QINGROOT" -------------------------------------------------------------------------------- /etc/idgo.toml: -------------------------------------------------------------------------------- 1 | addr="127.0.0.1:6389" 2 | #log_path: /Users/flike/src 3 | #日志级别 4 | log_level="debug" 5 | 6 | [storage_db] 7 | mysql_host="127.0.0.1" 8 | mysql_port=3306 9 | db_name="dump_test" 10 | user="root" 11 | password="" 12 | max_idle_conns=64 -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/flike/idgo 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/BurntSushi/toml v1.0.0 7 | github.com/flike/golog v0.0.0-20150625093146-d59ac6dad9f0 8 | github.com/go-sql-driver/mysql v1.6.0 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= 2 | github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 3 | github.com/flike/golog v0.0.0-20150625093146-d59ac6dad9f0 h1:arbHc2l82wtD1t8yO2qcj8zRkw9A870PlwftSm53REs= 4 | github.com/flike/golog v0.0.0-20150625093146-d59ac6dad9f0/go.mod h1:nLPGt1XMR4Ol1YGuf2UheT7FcT2I4soUaEXamF9PMFk= 5 | github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= 6 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 7 | -------------------------------------------------------------------------------- /server/command.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import "strconv" 4 | 5 | func (s *Server) handleGet(r *Request) Reply { 6 | var idgen *MySQLIdGenerator 7 | var ok bool 8 | var id int64 9 | var err error 10 | 11 | if r.HasArgument(0) == false { 12 | return ErrNotEnoughArgs 13 | } 14 | 15 | idGenKey := string(r.Arguments[0]) 16 | if len(idGenKey) == 0 { 17 | return ErrNoKey 18 | } 19 | s.Lock() 20 | idgen, ok = s.keyGeneratorMap[idGenKey] 21 | 22 | if ok == false { 23 | s.Unlock() 24 | return &BulkReply{ 25 | value: nil, 26 | } 27 | } 28 | 29 | s.Unlock() 30 | id, err = idgen.Next() 31 | if err != nil { 32 | return &ErrorReply{ 33 | message: err.Error(), 34 | } 35 | } 36 | 37 | idStr := strconv.FormatInt(id, 10) 38 | return &BulkReply{ 39 | value: []byte(idStr), 40 | } 41 | } 42 | 43 | // redis command(set abc 12) 44 | func (s *Server) handleSet(r *Request) Reply { 45 | var idgen *MySQLIdGenerator 46 | var ok bool 47 | var err error 48 | 49 | if r.HasArgument(0) == false { 50 | return ErrNotEnoughArgs 51 | } 52 | 53 | idGenKey := string(r.Arguments[0]) 54 | if len(idGenKey) == 0 { 55 | return ErrNoKey 56 | } 57 | idValue, errReply := r.GetInt(1) 58 | if errReply != nil { 59 | return errReply 60 | } 61 | s.Lock() 62 | idgen, ok = s.keyGeneratorMap[idGenKey] 63 | if ok == false { 64 | idgen, err = NewMySQLIdGenerator(s.db, idGenKey, BatchCount) 65 | if err != nil { 66 | s.Unlock() 67 | return &ErrorReply{ 68 | message: err.Error(), 69 | } 70 | } 71 | s.keyGeneratorMap[idGenKey] = idgen 72 | } 73 | 74 | s.Unlock() 75 | err = s.SetKey(idGenKey) 76 | if err != nil { 77 | return &ErrorReply{ 78 | message: err.Error(), 79 | } 80 | } 81 | 82 | err = idgen.Reset(idValue, false) 83 | if err != nil { 84 | return &ErrorReply{ 85 | message: err.Error(), 86 | } 87 | } 88 | 89 | return &StatusReply{ 90 | code: "OK", 91 | } 92 | } 93 | 94 | func (s *Server) handleExists(r *Request) Reply { 95 | var ok bool 96 | var id int64 97 | 98 | if r.HasArgument(0) == false { 99 | return ErrNotEnoughArgs 100 | } 101 | 102 | idGenKey := string(r.Arguments[0]) 103 | if len(idGenKey) == 0 { 104 | return ErrNoKey 105 | } 106 | s.Lock() 107 | _, ok = s.keyGeneratorMap[idGenKey] 108 | s.Unlock() 109 | if ok { 110 | id = 1 111 | } 112 | 113 | return &IntReply{ 114 | number: id, 115 | } 116 | } 117 | 118 | func (s *Server) handleDel(r *Request) Reply { 119 | var idgen *MySQLIdGenerator 120 | var ok bool 121 | var id int64 = 0 122 | 123 | if r.HasArgument(0) == false { 124 | return ErrNotEnoughArgs 125 | } 126 | 127 | idGenKey := string(r.Arguments[0]) 128 | if len(idGenKey) == 0 { 129 | return ErrNoKey 130 | } 131 | s.Lock() 132 | idgen, ok = s.keyGeneratorMap[idGenKey] 133 | if ok { 134 | delete(s.keyGeneratorMap, idGenKey) 135 | } 136 | s.Unlock() 137 | if ok { 138 | err := idgen.DelKeyTable(idGenKey) 139 | if err != nil { 140 | return &ErrorReply{ 141 | message: err.Error(), 142 | } 143 | } 144 | err = s.DelKey(idGenKey) 145 | if err != nil { 146 | return &ErrorReply{ 147 | message: err.Error(), 148 | } 149 | } 150 | id = 1 151 | } 152 | 153 | return &IntReply{ 154 | number: id, 155 | } 156 | } 157 | 158 | func (s *Server) handleSelect(r *Request) Reply { 159 | if r.HasArgument(0) == false { 160 | return ErrNotEnoughArgs 161 | } 162 | 163 | num := string(r.Arguments[0]) 164 | if len(num) == 0 { 165 | return ErrNotEnoughArgs 166 | } 167 | 168 | return &StatusReply{ 169 | code: "OK", 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /server/idgo.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "sync" 7 | 8 | _ "github.com/go-sql-driver/mysql" 9 | ) 10 | 11 | const ( 12 | // create key table 13 | CreateTableSQLFormat = ` 14 | CREATE TABLE %s ( 15 | id bigint(20) unsigned NOT NULL auto_increment, 16 | PRIMARY KEY (id) 17 | ) ENGINE=Innodb DEFAULT CHARSET=utf8 ` 18 | 19 | // create key table if not exist 20 | CreateTableNTSQLFormat = ` 21 | CREATE TABLE IF NOT EXISTS %s ( 22 | id bigint(20) unsigned NOT NULL auto_increment, 23 | PRIMARY KEY (id) 24 | ) ENGINE=Innodb DEFAULT CHARSET=utf8 ` 25 | 26 | DropTableSQLFormat = `DROP TABLE IF EXISTS %s` 27 | InsertIdSQLFormat = "INSERT INTO %s(id) VALUES(%d)" 28 | SelectForUpdate = "SELECT id FROM %s FOR UPDATE" 29 | UpdateIdSQLFormat = "UPDATE %s SET id = id + %d" 30 | GetRowCountSQLFormat = "SELECT count(*) FROM %s" 31 | GetKeySQLFormat = "show tables like '%s'" 32 | 33 | BatchCount = 2000 34 | ) 35 | 36 | type MySQLIdGenerator struct { 37 | db *sql.DB 38 | key string // id generator key name 39 | cur int64 // current id 40 | batchMax int64 // max id till get from mysql 41 | batch int64 // get batch count ids from mysql once 42 | 43 | lock sync.Mutex 44 | } 45 | 46 | func NewMySQLIdGenerator(db *sql.DB, section string, batchCount int64) (*MySQLIdGenerator, error) { 47 | idGenerator := new(MySQLIdGenerator) 48 | idGenerator.db = db 49 | if len(section) == 0 { 50 | return nil, fmt.Errorf("section is nil") 51 | } 52 | err := idGenerator.SetSection(section) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | if batchCount != 0 { 58 | idGenerator.batch = batchCount 59 | } else { 60 | idGenerator.batch = BatchCount 61 | } 62 | 63 | idGenerator.cur = 0 64 | idGenerator.batchMax = idGenerator.cur 65 | return idGenerator, nil 66 | } 67 | 68 | func (m *MySQLIdGenerator) SetSection(key string) error { 69 | m.key = key 70 | return nil 71 | } 72 | 73 | // get id from key table 74 | func (m *MySQLIdGenerator) getIdFromMySQL() (int64, error) { 75 | var id int64 76 | selectForUpdate := fmt.Sprintf(SelectForUpdate, m.key) 77 | tx, err := m.db.Begin() 78 | if err != nil { 79 | return 0, err 80 | } 81 | 82 | rows, err := tx.Query(selectForUpdate) 83 | if err != nil { 84 | tx.Rollback() 85 | return 0, err 86 | } 87 | defer rows.Close() 88 | for rows.Next() { 89 | err := rows.Scan(&id) 90 | if err != nil { 91 | tx.Rollback() 92 | return 0, err 93 | } 94 | } 95 | tx.Commit() 96 | 97 | return id, nil 98 | } 99 | 100 | // get current id 101 | func (m *MySQLIdGenerator) Current() (int64, error) { 102 | m.lock.Lock() 103 | defer m.lock.Unlock() 104 | 105 | return m.cur, nil 106 | } 107 | 108 | func (m *MySQLIdGenerator) Next() (int64, error) { 109 | var id int64 110 | var haveValue bool 111 | selectForUpdate := fmt.Sprintf(SelectForUpdate, m.key) 112 | updateIdSql := fmt.Sprintf(UpdateIdSQLFormat, m.key, m.batch) 113 | m.lock.Lock() 114 | defer m.lock.Unlock() 115 | if m.batchMax < m.cur+1 { 116 | tx, err := m.db.Begin() 117 | if err != nil { 118 | return 0, err 119 | } 120 | 121 | rows, err := tx.Query(selectForUpdate) 122 | if err != nil { 123 | tx.Rollback() 124 | return 0, err 125 | } 126 | defer rows.Close() 127 | for rows.Next() { 128 | err := rows.Scan(&id) 129 | if err != nil { 130 | tx.Rollback() 131 | return 0, err 132 | } 133 | haveValue = true 134 | } 135 | // When the idgo table has no id key 136 | if haveValue == false { 137 | return 0, fmt.Errorf("%s:have no id key", m.key) 138 | } 139 | _, err = tx.Exec(updateIdSql) 140 | if err != nil { 141 | tx.Rollback() 142 | return 0, err 143 | } 144 | tx.Commit() 145 | 146 | // batchMax is larger than cur BatchCount 147 | m.batchMax = id + BatchCount 148 | m.cur = id 149 | } 150 | m.cur++ 151 | return m.cur, nil 152 | } 153 | 154 | func (m *MySQLIdGenerator) Init() error { 155 | var err error 156 | 157 | m.lock.Lock() 158 | defer m.lock.Unlock() 159 | 160 | m.cur, err = m.getIdFromMySQL() 161 | if err != nil { 162 | return err 163 | } 164 | m.batchMax = m.cur 165 | return nil 166 | } 167 | 168 | // if force is true, create table directly 169 | // if force is false, create table use CreateTableNTSQLFormat 170 | func (m *MySQLIdGenerator) Reset(idOffset int64, force bool) error { 171 | var err error 172 | createTableSQL := fmt.Sprintf(CreateTableSQLFormat, m.key) 173 | createTableNtSQL := fmt.Sprintf(CreateTableNTSQLFormat, m.key) 174 | dropTableSQL := fmt.Sprintf(DropTableSQLFormat, m.key) 175 | 176 | m.lock.Lock() 177 | defer m.lock.Unlock() 178 | 179 | if force == true { 180 | _, err = m.db.Exec(dropTableSQL) 181 | if err != nil { 182 | return err 183 | } 184 | _, err = m.db.Exec(createTableSQL) 185 | if err != nil { 186 | return err 187 | } 188 | } else { 189 | var rowCount int64 190 | _, err = m.db.Exec(createTableNtSQL) 191 | if err != nil { 192 | return err 193 | } 194 | // check the idgo value if exist 195 | getRowCountSQL := fmt.Sprintf(GetRowCountSQLFormat, m.key) 196 | rows, err := m.db.Query(getRowCountSQL) 197 | if err != nil { 198 | return err 199 | } 200 | defer rows.Close() 201 | for rows.Next() { 202 | err := rows.Scan(&rowCount) 203 | if err != nil { 204 | return err 205 | } 206 | } 207 | 208 | if rowCount == int64(1) { 209 | m.cur, err = m.getIdFromMySQL() 210 | if err != nil { 211 | return err 212 | } 213 | m.batchMax = m.cur 214 | return nil 215 | } 216 | } 217 | 218 | insertIdSQL := fmt.Sprintf(InsertIdSQLFormat, m.key, idOffset) 219 | _, err = m.db.Exec(insertIdSQL) 220 | if err != nil { 221 | m.db.Exec(dropTableSQL) 222 | return err 223 | } 224 | m.cur = idOffset 225 | m.batchMax = m.cur 226 | return nil 227 | } 228 | 229 | func (m *MySQLIdGenerator) DelKeyTable(key string) error { 230 | dropTableSQL := fmt.Sprintf(DropTableSQLFormat, key) 231 | 232 | m.lock.Lock() 233 | defer m.lock.Unlock() 234 | 235 | _, err := m.db.Exec(dropTableSQL) 236 | if err != nil { 237 | return err 238 | } 239 | return nil 240 | } 241 | -------------------------------------------------------------------------------- /server/idgo_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "sync" 7 | "testing" 8 | 9 | _ "github.com/go-sql-driver/mysql" 10 | ) 11 | 12 | var wg sync.WaitGroup 13 | var db *sql.DB 14 | 15 | func init() { 16 | var err error 17 | 18 | db, err = sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/test?charset=utf8") 19 | if err != nil { 20 | fmt.Println(err.Error()) 21 | } 22 | } 23 | 24 | func GetId(idGenerator *MySQLIdGenerator) { 25 | defer wg.Done() 26 | for i := 0; i < 100; i++ { 27 | _, err := idGenerator.Next() 28 | if err != nil { 29 | fmt.Println(err.Error()) 30 | } 31 | } 32 | } 33 | 34 | func TestMySQLIdgen(t *testing.T) { 35 | idGenerator, err := NewMySQLIdGenerator(db, "mysql_victory", BatchCount) 36 | if err != nil { 37 | t.Fatal(err.Error()) 38 | } 39 | err = idGenerator.Reset(1, false) 40 | if err != nil { 41 | t.Fatal(err.Error()) 42 | } 43 | // 10 goroutine 44 | wg.Add(10) 45 | for i := 0; i < 10; i++ { 46 | go GetId(idGenerator) 47 | } 48 | wg.Wait() 49 | id, err := idGenerator.Next() 50 | if err != nil { 51 | t.Fatal(err.Error()) 52 | } 53 | t.Log(id) 54 | } 55 | 56 | func BenchmarkMySQLIdgen(b *testing.B) { 57 | idGenerator, err := NewMySQLIdGenerator(db, "mysql_file", BatchCount) 58 | if err != nil { 59 | b.Fatal(err.Error()) 60 | } 61 | err = idGenerator.Reset(1, false) 62 | if err != nil { 63 | b.Fatal(err.Error()) 64 | } 65 | 66 | b.StartTimer() 67 | for i := 0; i < 1000; i++ { 68 | _, err = idGenerator.Next() 69 | if err != nil { 70 | b.Fatal(err.Error()) 71 | } 72 | } 73 | 74 | b.StopTimer() 75 | } 76 | -------------------------------------------------------------------------------- /server/proto.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | type Request struct { 13 | Command string 14 | Arguments [][]byte 15 | RemoteAddress string 16 | Connection io.ReadCloser 17 | } 18 | 19 | func (r *Request) HasArgument(index int) bool { 20 | return index >= 0 && index < len(r.Arguments) 21 | } 22 | 23 | func (r *Request) ExpectArgument(index int) *ErrorReply { 24 | if !r.HasArgument(index) { 25 | return ErrNotEnoughArgs 26 | } 27 | return nil 28 | } 29 | 30 | func (r *Request) GetInt(index int) (int64, *ErrorReply) { 31 | if errReply := r.ExpectArgument(index); errReply != nil { 32 | return -1, errReply 33 | } 34 | if n, err := strconv.ParseInt(string(r.Arguments[index]), 10, 64); err != nil { 35 | return -1, ErrExpectInteger 36 | } else { 37 | return n, nil 38 | } 39 | } 40 | 41 | func NewRequest(conn io.ReadCloser) (*Request, error) { 42 | reader := bufio.NewReader(conn) 43 | 44 | // *CRLF 45 | line, err := reader.ReadString('\n') 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | var argCount int 51 | if line[0] == '*' { 52 | if _, err := fmt.Sscanf(line, "*%d\r\n", &argCount); err != nil { 53 | return nil, Malformed("*<#Arguments>", line) 54 | } 55 | 56 | // $CRLF 57 | // CRLF 58 | command, err := readArgument(reader) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | arguments := make([][]byte, argCount-1) 64 | for i := 0; i < argCount-1; i++ { 65 | if arguments[i], err = readArgument(reader); err != nil { 66 | return nil, err 67 | } 68 | } 69 | 70 | return &Request{ 71 | Command: strings.ToUpper(string(command)), 72 | Arguments: arguments, 73 | Connection: conn, 74 | }, nil 75 | } 76 | 77 | return nil, fmt.Errorf("new request error") 78 | } 79 | 80 | func readArgument(reader *bufio.Reader) ([]byte, error) { 81 | line, err := reader.ReadString('\n') 82 | if err != nil { 83 | return nil, Malformed("$", line) 84 | } 85 | 86 | var argLength int 87 | if _, err := fmt.Sscanf(line, "$%d\r\n", &argLength); err != nil { 88 | return nil, Malformed("$", line) 89 | } 90 | 91 | data, err := ioutil.ReadAll(io.LimitReader(reader, int64(argLength))) 92 | if err != nil { 93 | return nil, err 94 | } 95 | if len(data) != argLength { 96 | return nil, MalformedLength(argLength, len(data)) 97 | } 98 | if b, err := reader.ReadByte(); err != nil || b != '\r' { 99 | return nil, MalformedMissingCRLF() 100 | } 101 | if b, err := reader.ReadByte(); err != nil || b != '\n' { 102 | return nil, MalformedMissingCRLF() 103 | } 104 | 105 | return data, nil 106 | } 107 | 108 | func Malformed(expected string, got string) error { 109 | return fmt.Errorf("Mailformed request: %s does not match %s", got, expected) 110 | } 111 | 112 | func MalformedLength(expected int, got int) error { 113 | return fmt.Errorf("Mailformed request: argument length %d does not match %d", got, expected) 114 | } 115 | 116 | func MalformedMissingCRLF() error { 117 | return fmt.Errorf("Mailformed request: line should end with CRLF") 118 | } 119 | 120 | type Reply io.WriterTo 121 | 122 | var ( 123 | ErrMethodNotSupported = &ErrorReply{"Method is not supported"} 124 | ErrNotEnoughArgs = &ErrorReply{"Not enough arguments for the command"} 125 | ErrTooMuchArgs = &ErrorReply{"Too many arguments for the command"} 126 | ErrWrongArgsNumber = &ErrorReply{"Wrong number of arguments"} 127 | ErrExpectInteger = &ErrorReply{"Expected integer"} 128 | ErrExpectPositivInteger = &ErrorReply{"Expected positive integer"} 129 | ErrExpectMorePair = &ErrorReply{"Expected at least one key val pair"} 130 | ErrExpectEvenPair = &ErrorReply{"Got uneven number of key val pairs"} 131 | 132 | ErrNoKey = &ErrorReply{"no key for set"} 133 | ) 134 | 135 | type ErrorReply struct { 136 | message string 137 | } 138 | 139 | func (er *ErrorReply) WriteTo(w io.Writer) (int64, error) { 140 | n, err := w.Write([]byte("-ERROR " + er.message + "\r\n")) 141 | return int64(n), err 142 | } 143 | 144 | func (er *ErrorReply) Error() string { 145 | return er.message 146 | } 147 | 148 | type StatusReply struct { 149 | code string 150 | } 151 | 152 | func (r *StatusReply) WriteTo(w io.Writer) (int64, error) { 153 | n, err := w.Write([]byte("+" + r.code + "\r\n")) 154 | return int64(n), err 155 | } 156 | 157 | type IntReply struct { 158 | number int64 159 | } 160 | 161 | func (r *IntReply) WriteTo(w io.Writer) (int64, error) { 162 | n, err := w.Write([]byte(":" + strconv.FormatInt(r.number, 10) + "\r\n")) 163 | return int64(n), err 164 | } 165 | 166 | type BulkReply struct { 167 | value []byte 168 | } 169 | 170 | func (r *BulkReply) WriteTo(w io.Writer) (int64, error) { 171 | return writeBytes(r.value, w) 172 | } 173 | 174 | type MultiBulkReply struct { 175 | values [][]byte 176 | } 177 | 178 | func (r *MultiBulkReply) WriteTo(w io.Writer) (int64, error) { 179 | if r.values == nil { 180 | return 0, fmt.Errorf("Multi bulk reply found a nil values") 181 | } 182 | if wrote, err := w.Write([]byte("*" + strconv.Itoa(len(r.values)) + "\r\n")); err != nil { 183 | return int64(wrote), err 184 | } else { 185 | total := int64(wrote) 186 | for _, value := range r.values { 187 | wroteData, err := writeBytes(value, w) 188 | total += wroteData 189 | if err != nil { 190 | return total, err 191 | } 192 | } 193 | return total, nil 194 | } 195 | } 196 | 197 | func writeNullBytes(w io.Writer) (int64, error) { 198 | n, err := w.Write([]byte("$-1\r\n")) 199 | return int64(n), err 200 | } 201 | 202 | func writeBytes(value interface{}, w io.Writer) (int64, error) { 203 | if value == nil { 204 | return writeNullBytes(w) 205 | } 206 | switch v := value.(type) { 207 | case []byte: 208 | if len(v) == 0 { 209 | return writeNullBytes(w) 210 | } 211 | buf := []byte("$" + strconv.Itoa(len(v)) + "\r\n") 212 | buf = append(buf, v...) 213 | buf = append(buf, []byte("\r\n")...) 214 | n, err := w.Write(buf) 215 | if err != nil { 216 | return 0, err 217 | } 218 | return int64(n), nil 219 | case int: 220 | wrote, err := w.Write([]byte(":" + strconv.Itoa(v) + "\r\n")) 221 | return int64(wrote), err 222 | } 223 | return 0, fmt.Errorf("Invalid type sent to WriteBytes") 224 | } 225 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "net" 7 | "runtime" 8 | "sync" 9 | 10 | "github.com/flike/golog" 11 | 12 | "github.com/flike/idgo/config" 13 | ) 14 | 15 | const ( 16 | KeyRecordTableName = "__idgo__" 17 | CreateRecordTableSQLFormat = ` 18 | CREATE TABLE %s ( 19 | k VARCHAR(255) NOT NULL, 20 | PRIMARY KEY (k) 21 | ) ENGINE=Innodb DEFAULT CHARSET=utf8 ` 22 | 23 | // create key table if not exist 24 | CreateRecordTableNTSQLFormat = ` 25 | CREATE TABLE IF NOT EXISTS %s ( 26 | k VARCHAR(255) NOT NULL, 27 | PRIMARY KEY (k) 28 | ) ENGINE=Innodb DEFAULT CHARSET=utf8 ` 29 | 30 | InsertKeySQLFormat = "INSERT INTO %s (k) VALUES ('%s')" 31 | SelectKeySQLFormat = "SELECT k FROM %s WHERE k = '%s'" 32 | SelectKeysSQLFormat = "SELECT k FROM %s" 33 | DeleteKeySQLFormat = "DELETE FROM %s WHERE k = '%s'" 34 | ) 35 | 36 | type Server struct { 37 | cfg *config.Config 38 | 39 | listener net.Listener 40 | db *sql.DB 41 | keyGeneratorMap map[string]*MySQLIdGenerator 42 | sync.RWMutex 43 | running bool 44 | } 45 | 46 | func NewServer(c *config.Config) (*Server, error) { 47 | s := new(Server) 48 | s.cfg = c 49 | 50 | var err error 51 | // init db 52 | proto := "mysql" 53 | charset := "utf8" 54 | // root:@tcp(127.0.0.1:3306)/test?charset=utf8 55 | url := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?%s", 56 | c.DatabaseConfig.User, 57 | c.DatabaseConfig.Password, 58 | c.DatabaseConfig.Host, 59 | c.DatabaseConfig.Port, 60 | c.DatabaseConfig.DBName, 61 | charset, 62 | ) 63 | 64 | s.db, err = sql.Open(proto, url) 65 | if err != nil { 66 | golog.Error("main", "NewServer", "open database error", 0, 67 | "err", err.Error(), 68 | ) 69 | return nil, err 70 | } 71 | 72 | netProto := "tcp" 73 | s.listener, err = net.Listen(netProto, s.cfg.Addr) 74 | if err != nil { 75 | return nil, err 76 | } 77 | s.keyGeneratorMap = make(map[string]*MySQLIdGenerator) 78 | 79 | golog.Info("server", "NewServer", "Server running", 0, 80 | "netProto", 81 | netProto, 82 | "address", 83 | s.cfg.Addr, 84 | ) 85 | 86 | return s, nil 87 | } 88 | 89 | func (s *Server) Init() error { 90 | createTableNtSQL := fmt.Sprintf(CreateRecordTableNTSQLFormat, KeyRecordTableName) 91 | selectKeysSQL := fmt.Sprintf(SelectKeysSQLFormat, KeyRecordTableName) 92 | _, err := s.db.Exec(createTableNtSQL) 93 | if err != nil { 94 | return err 95 | } 96 | rows, err := s.db.Query(selectKeysSQL) 97 | if err != nil { 98 | return err 99 | } 100 | defer rows.Close() 101 | for rows.Next() { 102 | idGenKey := "" 103 | err := rows.Scan(&idGenKey) 104 | if err != nil { 105 | return err 106 | } 107 | if idGenKey != "" { 108 | idgen, ok := s.keyGeneratorMap[idGenKey] 109 | if ok == false { 110 | isExist, err := s.IsKeyExist(idGenKey) 111 | if err != nil { 112 | return err 113 | } 114 | if isExist { 115 | idgen, err = NewMySQLIdGenerator(s.db, idGenKey, BatchCount) 116 | if err != nil { 117 | return err 118 | } 119 | s.keyGeneratorMap[idGenKey] = idgen 120 | } 121 | } 122 | } 123 | } 124 | return nil 125 | } 126 | 127 | func (s *Server) Serve() error { 128 | s.running = true 129 | for s.running { 130 | conn, err := s.listener.Accept() 131 | if err != nil { 132 | golog.Error("server", "Run", err.Error(), 0) 133 | continue 134 | } 135 | 136 | go s.onConn(conn) 137 | } 138 | return nil 139 | } 140 | 141 | func (s *Server) onConn(conn net.Conn) error { 142 | defer func() { 143 | clientAddr := conn.RemoteAddr().String() 144 | r := recover() 145 | if err, ok := r.(error); ok { 146 | const size = 4096 147 | buf := make([]byte, size) 148 | buf = buf[:runtime.Stack(buf, false)] // 获得当前goroutine的stacktrace 149 | golog.Error("server", "onConn", "error", 0, 150 | "remoteAddr", clientAddr, 151 | "stack", string(buf), 152 | "err", err.Error(), 153 | ) 154 | reply := &ErrorReply{ 155 | message: err.Error(), 156 | } 157 | reply.WriteTo(conn) 158 | } 159 | conn.Close() 160 | }() 161 | 162 | for { 163 | request, err := NewRequest(conn) 164 | if err != nil { 165 | return err 166 | } 167 | 168 | reply := s.ServeRequest(request) 169 | if _, err := reply.WriteTo(conn); err != nil { 170 | golog.Error("server", "onConn", "reply write error", 0, 171 | "err", err.Error()) 172 | return err 173 | } 174 | 175 | } 176 | return nil 177 | } 178 | 179 | func (s *Server) ServeRequest(request *Request) Reply { 180 | switch request.Command { 181 | case "GET": 182 | return s.handleGet(request) 183 | case "SET": 184 | return s.handleSet(request) 185 | case "EXISTS": 186 | return s.handleExists(request) 187 | case "DEL": 188 | return s.handleDel(request) 189 | case "SELECT": 190 | return s.handleSelect(request) 191 | default: 192 | return ErrMethodNotSupported 193 | } 194 | 195 | return nil 196 | } 197 | 198 | func (s *Server) Close() { 199 | s.running = false 200 | if s.listener != nil { 201 | s.listener.Close() 202 | } 203 | golog.Info("server", "close", "server closed!", 0) 204 | } 205 | 206 | func (s *Server) IsKeyExist(key string) (bool, error) { 207 | var tableName string 208 | var haveValue bool 209 | if len(key) == 0 { 210 | return false, nil 211 | } 212 | getKeySQL := fmt.Sprintf(GetKeySQLFormat, key) 213 | rows, err := s.db.Query(getKeySQL) 214 | if err != nil { 215 | return false, err 216 | } 217 | defer rows.Close() 218 | for rows.Next() { 219 | err := rows.Scan(&tableName) 220 | if err != nil { 221 | return false, err 222 | } 223 | haveValue = true 224 | } 225 | if haveValue == false { 226 | return false, nil 227 | } 228 | return true, nil 229 | } 230 | 231 | func (s *Server) GetKey(key string) (string, error) { 232 | keyName := "" 233 | selectKeySQL := fmt.Sprintf(SelectKeySQLFormat, KeyRecordTableName, key) 234 | rows, err := s.db.Query(selectKeySQL) 235 | if err != nil { 236 | return keyName, err 237 | } 238 | defer rows.Close() 239 | for rows.Next() { 240 | err := rows.Scan(&keyName) 241 | if err != nil { 242 | return keyName, err 243 | } 244 | } 245 | if keyName == "" { 246 | return keyName, fmt.Errorf("%s:not exists key", key) 247 | } 248 | return keyName, nil 249 | } 250 | 251 | func (s *Server) SetKey(key string) error { 252 | if len(key) == 0 { 253 | return fmt.Errorf("%s:invalid key", key) 254 | } 255 | _, err := s.GetKey(key) 256 | if err == nil { 257 | return nil 258 | } else { 259 | insertKeySQL := fmt.Sprintf(InsertKeySQLFormat, KeyRecordTableName, key) 260 | _, err = s.db.Exec(insertKeySQL) 261 | if err != nil { 262 | return err 263 | } 264 | return nil 265 | } 266 | } 267 | 268 | func (s *Server) DelKey(key string) error { 269 | if len(key) == 0 { 270 | return fmt.Errorf("%s:invalid key", key) 271 | } 272 | _, err := s.GetKey(key) 273 | if err == nil { 274 | deletetKeySQL := fmt.Sprintf(DeleteKeySQLFormat, KeyRecordTableName, key) 275 | _, err = s.db.Exec(deletetKeySQL) 276 | if err != nil { 277 | return err 278 | } 279 | return nil 280 | } else { 281 | return nil 282 | } 283 | } 284 | --------------------------------------------------------------------------------