├── .gitignore ├── LICENSE ├── README.md ├── demo-wasm ├── .gitignore ├── callback │ ├── Makefile │ ├── index.html │ └── main.go ├── hello-world │ ├── Makefile │ ├── index.html │ └── main.go ├── manipulate-dom │ ├── Makefile │ ├── index.html │ └── main.go └── register-functions │ ├── Makefile │ ├── index.html │ └── main.go ├── gee-bolt ├── day1-pages │ ├── go.mod │ ├── meta.go │ └── page.go ├── day2-mmap │ ├── db.go │ └── go.mod └── day3-tree │ ├── go.mod │ ├── meta.go │ ├── node.go │ └── page.go ├── gee-cache ├── day1-lru │ └── geecache │ │ ├── go.mod │ │ └── lru │ │ ├── lru.go │ │ └── lru_test.go ├── day2-single-node │ └── geecache │ │ ├── byteview.go │ │ ├── cache.go │ │ ├── geecache.go │ │ ├── geecache_test.go │ │ ├── go.mod │ │ └── lru │ │ ├── lru.go │ │ └── lru_test.go ├── day3-http-server │ ├── geecache │ │ ├── byteview.go │ │ ├── cache.go │ │ ├── geecache.go │ │ ├── geecache_test.go │ │ ├── go.mod │ │ ├── http.go │ │ └── lru │ │ │ ├── lru.go │ │ │ └── lru_test.go │ ├── go.mod │ └── main.go ├── day4-consistent-hash │ ├── geecache │ │ ├── byteview.go │ │ ├── cache.go │ │ ├── consistenthash │ │ │ ├── consistenthash.go │ │ │ └── consistenthash_test.go │ │ ├── geecache.go │ │ ├── geecache_test.go │ │ ├── go.mod │ │ ├── http.go │ │ └── lru │ │ │ ├── lru.go │ │ │ └── lru_test.go │ ├── go.mod │ └── main.go ├── day5-multi-nodes │ ├── geecache │ │ ├── byteview.go │ │ ├── cache.go │ │ ├── consistenthash │ │ │ ├── consistenthash.go │ │ │ └── consistenthash_test.go │ │ ├── geecache.go │ │ ├── geecache_test.go │ │ ├── go.mod │ │ ├── http.go │ │ ├── lru │ │ │ ├── lru.go │ │ │ └── lru_test.go │ │ └── peers.go │ ├── go.mod │ ├── main.go │ └── run.sh ├── day6-single-flight │ ├── geecache │ │ ├── byteview.go │ │ ├── cache.go │ │ ├── consistenthash │ │ │ ├── consistenthash.go │ │ │ └── consistenthash_test.go │ │ ├── geecache.go │ │ ├── geecache_test.go │ │ ├── go.mod │ │ ├── http.go │ │ ├── lru │ │ │ ├── lru.go │ │ │ └── lru_test.go │ │ ├── peers.go │ │ └── singleflight │ │ │ ├── singleflight.go │ │ │ └── singleflight_test.go │ ├── go.mod │ ├── main.go │ └── run.sh ├── day7-proto-buf │ ├── geecache │ │ ├── byteview.go │ │ ├── cache.go │ │ ├── consistenthash │ │ │ ├── consistenthash.go │ │ │ └── consistenthash_test.go │ │ ├── geecache.go │ │ ├── geecache_test.go │ │ ├── geecachepb │ │ │ ├── geecachepb.pb.go │ │ │ └── geecachepb.proto │ │ ├── go.mod │ │ ├── go.sum │ │ ├── http.go │ │ ├── lru │ │ │ ├── lru.go │ │ │ └── lru_test.go │ │ ├── peers.go │ │ └── singleflight │ │ │ ├── singleflight.go │ │ │ └── singleflight_test.go │ ├── go.mod │ ├── go.sum │ ├── main.go │ └── run.sh └── doc │ ├── geecache-day1.md │ ├── geecache-day1 │ ├── lru.jpg │ └── lru_logo.jpg │ ├── geecache-day2.md │ ├── geecache-day2 │ ├── concurrent_cache.jpg │ └── concurrent_cache_logo.jpg │ ├── geecache-day3.md │ ├── geecache-day3 │ ├── http.jpg │ └── http_logo.jpg │ ├── geecache-day4.md │ ├── geecache-day4 │ ├── add_peer.jpg │ ├── hash.jpg │ ├── hash_logo.jpg │ └── hash_select.jpg │ ├── geecache-day5.md │ ├── geecache-day5 │ ├── dist_nodes.jpg │ └── dist_nodes_logo.jpg │ ├── geecache-day6.md │ ├── geecache-day6 │ ├── singleflight.jpg │ └── singleflight_logo.jpg │ ├── geecache-day7.md │ ├── geecache-day7 │ ├── protobuf.jpg │ └── protobuf_logo.jpg │ ├── geecache.md │ └── geecache │ ├── geecache.jpg │ └── geecache_sm.jpg ├── gee-orm ├── day1-database-sql │ ├── cmd_test │ │ └── main.go │ ├── geeorm.go │ ├── geeorm_test.go │ ├── go.mod │ ├── log │ │ ├── log.go │ │ └── log_test.go │ └── session │ │ ├── raw.go │ │ └── raw_test.go ├── day2-reflect-schema │ ├── dialect │ │ ├── dialect.go │ │ ├── sqlite3.go │ │ └── sqlite3_test.go │ ├── geeorm.go │ ├── geeorm_test.go │ ├── go.mod │ ├── log │ │ ├── log.go │ │ └── log_test.go │ ├── schema │ │ ├── schema.go │ │ └── schema_test.go │ └── session │ │ ├── raw.go │ │ ├── raw_test.go │ │ ├── table.go │ │ └── table_test.go ├── day3-save-query │ ├── clause │ │ ├── clause.go │ │ ├── clause_test.go │ │ └── generator.go │ ├── dialect │ │ ├── dialect.go │ │ ├── sqlite3.go │ │ └── sqlite3_test.go │ ├── geeorm.go │ ├── geeorm_test.go │ ├── go.mod │ ├── log │ │ ├── log.go │ │ └── log_test.go │ ├── schema │ │ ├── schema.go │ │ └── schema_test.go │ └── session │ │ ├── raw.go │ │ ├── raw_test.go │ │ ├── record.go │ │ ├── record_test.go │ │ ├── table.go │ │ └── table_test.go ├── day4-chain-operation │ ├── clause │ │ ├── clause.go │ │ ├── clause_test.go │ │ └── generator.go │ ├── dialect │ │ ├── dialect.go │ │ ├── sqlite3.go │ │ └── sqlite3_test.go │ ├── geeorm.go │ ├── geeorm_test.go │ ├── go.mod │ ├── log │ │ ├── log.go │ │ └── log_test.go │ ├── schema │ │ ├── schema.go │ │ └── schema_test.go │ └── session │ │ ├── raw.go │ │ ├── raw_test.go │ │ ├── record.go │ │ ├── record_test.go │ │ ├── table.go │ │ └── table_test.go ├── day5-hooks │ ├── clause │ │ ├── clause.go │ │ ├── clause_test.go │ │ └── generator.go │ ├── dialect │ │ ├── dialect.go │ │ ├── sqlite3.go │ │ └── sqlite3_test.go │ ├── geeorm.go │ ├── geeorm_test.go │ ├── go.mod │ ├── log │ │ ├── log.go │ │ └── log_test.go │ ├── schema │ │ ├── schema.go │ │ └── schema_test.go │ └── session │ │ ├── hooks.go │ │ ├── hooks_test.go │ │ ├── raw.go │ │ ├── raw_test.go │ │ ├── record.go │ │ ├── record_test.go │ │ ├── table.go │ │ └── table_test.go ├── day6-transaction │ ├── clause │ │ ├── clause.go │ │ ├── clause_test.go │ │ └── generator.go │ ├── dialect │ │ ├── dialect.go │ │ ├── sqlite3.go │ │ └── sqlite3_test.go │ ├── geeorm.go │ ├── geeorm_test.go │ ├── go.mod │ ├── log │ │ ├── log.go │ │ └── log_test.go │ ├── schema │ │ ├── schema.go │ │ └── schema_test.go │ └── session │ │ ├── hooks.go │ │ ├── hooks_test.go │ │ ├── raw.go │ │ ├── raw_test.go │ │ ├── record.go │ │ ├── record_test.go │ │ ├── table.go │ │ ├── table_test.go │ │ └── transaction.go ├── day7-migrate │ ├── clause │ │ ├── clause.go │ │ ├── clause_test.go │ │ └── generator.go │ ├── dialect │ │ ├── dialect.go │ │ ├── sqlite3.go │ │ └── sqlite3_test.go │ ├── geeorm.go │ ├── geeorm_test.go │ ├── go.mod │ ├── log │ │ ├── log.go │ │ └── log_test.go │ ├── schema │ │ ├── schema.go │ │ └── schema_test.go │ └── session │ │ ├── hooks.go │ │ ├── hooks_test.go │ │ ├── raw.go │ │ ├── raw_test.go │ │ ├── record.go │ │ ├── record_test.go │ │ ├── table.go │ │ ├── table_test.go │ │ └── transaction.go ├── doc │ ├── geeorm-day1.md │ ├── geeorm-day1 │ │ └── geeorm_log.png │ ├── geeorm-day2.md │ ├── geeorm-day3.md │ ├── geeorm-day4.md │ ├── geeorm-day5.md │ ├── geeorm-day6.md │ ├── geeorm-day7.md │ ├── geeorm.md │ └── geeorm │ │ ├── geeorm.jpg │ │ └── geeorm_sm.jpg └── run_test.sh ├── gee-rpc ├── day1-codec │ ├── codec │ │ ├── codec.go │ │ └── gob.go │ ├── go.mod │ ├── main │ │ └── main.go │ └── server.go ├── day2-client │ ├── client.go │ ├── codec │ │ ├── codec.go │ │ └── gob.go │ ├── go.mod │ ├── main │ │ └── main.go │ └── server.go ├── day3-service │ ├── client.go │ ├── codec │ │ ├── codec.go │ │ └── gob.go │ ├── go.mod │ ├── main │ │ └── main.go │ ├── server.go │ ├── service.go │ └── service_test.go ├── day4-timeout │ ├── client.go │ ├── client_test.go │ ├── codec │ │ ├── codec.go │ │ └── gob.go │ ├── go.mod │ ├── main │ │ └── main.go │ ├── server.go │ ├── service.go │ └── service_test.go ├── day5-http-debug │ ├── client.go │ ├── client_test.go │ ├── codec │ │ ├── codec.go │ │ └── gob.go │ ├── debug.go │ ├── go.mod │ ├── main │ │ └── main.go │ ├── server.go │ ├── service.go │ └── service_test.go ├── day6-load-balance │ ├── client.go │ ├── client_test.go │ ├── codec │ │ ├── codec.go │ │ └── gob.go │ ├── debug.go │ ├── go.mod │ ├── main │ │ └── main.go │ ├── server.go │ ├── service.go │ ├── service_test.go │ └── xclient │ │ ├── discovery.go │ │ └── xclient.go ├── day7-registry │ ├── client.go │ ├── client_test.go │ ├── codec │ │ ├── codec.go │ │ └── gob.go │ ├── debug.go │ ├── go.mod │ ├── main │ │ └── main.go │ ├── registry │ │ └── registry.go │ ├── server.go │ ├── service.go │ ├── service_test.go │ └── xclient │ │ ├── discovery.go │ │ ├── discovery_gee.go │ │ └── xclient.go └── doc │ ├── geerpc-day1.md │ ├── geerpc-day2.md │ ├── geerpc-day3.md │ ├── geerpc-day4.md │ ├── geerpc-day5.md │ ├── geerpc-day5 │ └── geerpc_debug.png │ ├── geerpc-day6.md │ ├── geerpc-day7.md │ ├── geerpc-day7 │ └── registry.jpg │ ├── geerpc.md │ └── geerpc │ └── geerpc.jpg ├── gee-web ├── README.md ├── day1-http-base │ ├── base1 │ │ ├── go.mod │ │ └── main.go │ ├── base2 │ │ ├── go.mod │ │ └── main.go │ └── base3 │ │ ├── gee │ │ ├── gee.go │ │ └── go.mod │ │ ├── go.mod │ │ └── main.go ├── day2-context │ ├── gee │ │ ├── context.go │ │ ├── gee.go │ │ ├── go.mod │ │ └── router.go │ ├── go.mod │ └── main.go ├── day3-router │ ├── gee │ │ ├── context.go │ │ ├── gee.go │ │ ├── go.mod │ │ ├── router.go │ │ ├── router_test.go │ │ └── trie.go │ ├── go.mod │ └── main.go ├── day4-group │ ├── gee │ │ ├── context.go │ │ ├── gee.go │ │ ├── gee_test.go │ │ ├── go.mod │ │ ├── router.go │ │ ├── router_test.go │ │ └── trie.go │ ├── go.mod │ └── main.go ├── day5-middleware │ ├── gee │ │ ├── context.go │ │ ├── gee.go │ │ ├── gee_test.go │ │ ├── go.mod │ │ ├── logger.go │ │ ├── router.go │ │ ├── router_test.go │ │ └── trie.go │ ├── go.mod │ └── main.go ├── day6-template │ ├── gee │ │ ├── context.go │ │ ├── gee.go │ │ ├── gee_test.go │ │ ├── go.mod │ │ ├── logger.go │ │ ├── router.go │ │ ├── router_test.go │ │ └── trie.go │ ├── go.mod │ ├── main.go │ ├── static │ │ ├── css │ │ │ └── geektutu.css │ │ └── file1.txt │ └── templates │ │ ├── arr.tmpl │ │ ├── css.tmpl │ │ └── custom_func.tmpl ├── day7-panic-recover │ ├── gee │ │ ├── context.go │ │ ├── gee.go │ │ ├── gee_test.go │ │ ├── go.mod │ │ ├── logger.go │ │ ├── recovery.go │ │ ├── router.go │ │ ├── router_test.go │ │ └── trie.go │ ├── go.mod │ └── main.go └── doc │ ├── gee-day1.md │ ├── gee-day2.md │ ├── gee-day3.md │ ├── gee-day3 │ ├── trie_eg.jpg │ └── trie_router.jpg │ ├── gee-day4.md │ ├── gee-day4 │ └── group.jpg │ ├── gee-day5.md │ ├── gee-day5 │ └── middleware.jpg │ ├── gee-day6.md │ ├── gee-day6 │ ├── html.png │ └── static.jpg │ ├── gee-day7.md │ ├── gee-day7 │ └── go-panic.png │ ├── gee.md │ └── gee │ └── gee.jpg └── questions ├── 7days-golang-q1.md └── 7days-golang-q1 └── 7days-golang-qa.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | .vscode 4 | tmp 5 | *.db 6 | *.sum -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Dai Jie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /demo-wasm/.gitignore: -------------------------------------------------------------------------------- 1 | *.wasm 2 | static -------------------------------------------------------------------------------- /demo-wasm/callback/Makefile: -------------------------------------------------------------------------------- 1 | all: static/main.wasm static/wasm_exec.js 2 | ifeq (, $(shell which goexec)) 3 | go get -u github.com/shurcooL/goexec 4 | endif 5 | goexec 'http.ListenAndServe(`:9999`, http.FileServer(http.Dir(`.`)))' 6 | 7 | static/wasm_exec.js: 8 | cp "$(shell go env GOROOT)/misc/wasm/wasm_exec.js" static 9 | 10 | static/main.wasm: main.go 11 | GO111MODULE=auto GOOS=js GOARCH=wasm go build -o static/main.wasm . -------------------------------------------------------------------------------- /demo-wasm/callback/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | 14 | 15 |

16 | 17 | 18 | -------------------------------------------------------------------------------- /demo-wasm/callback/main.go: -------------------------------------------------------------------------------- 1 | // main.go 2 | package main 3 | 4 | import ( 5 | "syscall/js" 6 | "time" 7 | ) 8 | 9 | func fib(i int) int { 10 | if i == 0 || i == 1 { 11 | return 1 12 | } 13 | return fib(i-1) + fib(i-2) 14 | } 15 | 16 | func fibFunc(this js.Value, args []js.Value) interface{} { 17 | callback := args[len(args)-1] 18 | go func() { 19 | time.Sleep(3 * time.Second) 20 | v := fib(args[0].Int()) 21 | callback.Invoke(v) 22 | }() 23 | 24 | js.Global().Get("ans").Set("innerHTML", "Waiting 3s...") 25 | return nil 26 | } 27 | 28 | func main() { 29 | done := make(chan int, 0) 30 | js.Global().Set("fibFunc", js.FuncOf(fibFunc)) 31 | <-done 32 | } 33 | -------------------------------------------------------------------------------- /demo-wasm/hello-world/Makefile: -------------------------------------------------------------------------------- 1 | all: static/main.wasm static/wasm_exec.js 2 | ifeq (, $(shell which goexec)) 3 | go get -u github.com/shurcooL/goexec 4 | endif 5 | goexec 'http.ListenAndServe(`:9999`, http.FileServer(http.Dir(`.`)))' 6 | 7 | static/wasm_exec.js: 8 | cp "$(shell go env GOROOT)/misc/wasm/wasm_exec.js" static 9 | 10 | static/main.wasm: main.go 11 | GO111MODULE=auto GOOS=js GOARCH=wasm go build -o static/main.wasm . -------------------------------------------------------------------------------- /demo-wasm/hello-world/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /demo-wasm/hello-world/main.go: -------------------------------------------------------------------------------- 1 | // main.go 2 | package main 3 | 4 | import "syscall/js" 5 | 6 | func main() { 7 | alert := js.Global().Get("alert") 8 | alert.Invoke("Hello World!") 9 | } -------------------------------------------------------------------------------- /demo-wasm/manipulate-dom/Makefile: -------------------------------------------------------------------------------- 1 | all: static/main.wasm static/wasm_exec.js 2 | ifeq (, $(shell which goexec)) 3 | go get -u github.com/shurcooL/goexec 4 | endif 5 | goexec 'http.ListenAndServe(`:9999`, http.FileServer(http.Dir(`.`)))' 6 | 7 | static/wasm_exec.js: 8 | cp "$(shell go env GOROOT)/misc/wasm/wasm_exec.js" static 9 | 10 | static/main.wasm: main.go 11 | GO111MODULE=auto GOOS=js GOARCH=wasm go build -o static/main.wasm . -------------------------------------------------------------------------------- /demo-wasm/manipulate-dom/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | 14 | 15 |

1

16 | 17 | 18 | -------------------------------------------------------------------------------- /demo-wasm/manipulate-dom/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strconv" 5 | "syscall/js" 6 | ) 7 | 8 | func fib(i int) int { 9 | if i == 0 || i == 1 { 10 | return 1 11 | } 12 | return fib(i-1) + fib(i-2) 13 | } 14 | 15 | var ( 16 | document = js.Global().Get("document") 17 | numEle = document.Call("getElementById", "num") 18 | ansEle = document.Call("getElementById", "ans") 19 | btnEle = js.Global().Get("btn") 20 | ) 21 | 22 | func fibFunc(this js.Value, args []js.Value) interface{} { 23 | v := numEle.Get("value") 24 | if num, err := strconv.Atoi(v.String()); err == nil { 25 | ansEle.Set("innerHTML", js.ValueOf(fib(num))) 26 | } 27 | return nil 28 | } 29 | 30 | func main() { 31 | done := make(chan int, 0) 32 | btnEle.Call("addEventListener", "click", js.FuncOf(fibFunc)) 33 | <-done 34 | } 35 | -------------------------------------------------------------------------------- /demo-wasm/register-functions/Makefile: -------------------------------------------------------------------------------- 1 | all: static/main.wasm static/wasm_exec.js 2 | ifeq (, $(shell which goexec)) 3 | go get -u github.com/shurcooL/goexec 4 | endif 5 | goexec 'http.ListenAndServe(`:9999`, http.FileServer(http.Dir(`.`)))' 6 | 7 | static/wasm_exec.js: 8 | cp "$(shell go env GOROOT)/misc/wasm/wasm_exec.js" static 9 | 10 | static/main.wasm: main.go 11 | GO111MODULE=auto GOOS=js GOARCH=wasm go build -o static/main.wasm . -------------------------------------------------------------------------------- /demo-wasm/register-functions/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | 14 | 15 |

1

16 | 17 | 18 | -------------------------------------------------------------------------------- /demo-wasm/register-functions/main.go: -------------------------------------------------------------------------------- 1 | // main.go 2 | package main 3 | 4 | import "syscall/js" 5 | 6 | func fib(i int) int { 7 | if i == 0 || i == 1 { 8 | return 1 9 | } 10 | return fib(i-1) + fib(i-2) 11 | } 12 | 13 | func fibFunc(this js.Value, args []js.Value) interface{} { 14 | return js.ValueOf(fib(args[0].Int())) 15 | } 16 | 17 | func main() { 18 | done := make(chan int, 0) 19 | js.Global().Set("fibFunc", js.FuncOf(fibFunc)) 20 | <-done 21 | } 22 | -------------------------------------------------------------------------------- /gee-bolt/day1-pages/go.mod: -------------------------------------------------------------------------------- 1 | module geebolt 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gee-bolt/day1-pages/meta.go: -------------------------------------------------------------------------------- 1 | package geebolt 2 | 3 | import ( 4 | "errors" 5 | "hash/fnv" 6 | "unsafe" 7 | ) 8 | 9 | // Represent a marker value to indicate that a file is a gee-bolt DB 10 | const magic uint32 = 0xED0CDAED 11 | 12 | type meta struct { 13 | magic uint32 14 | pageSize uint32 15 | pgid uint64 16 | checksum uint64 17 | } 18 | 19 | func (m *meta) sum64() uint64 { 20 | var h = fnv.New64a() 21 | _, _ = h.Write((*[unsafe.Offsetof(meta{}.checksum)]byte)(unsafe.Pointer(m))[:]) 22 | return h.Sum64() 23 | } 24 | 25 | func (m *meta) validate() error { 26 | if m.magic != magic { 27 | return errors.New("invalid magic number") 28 | } 29 | if m.checksum != m.sum64() { 30 | return errors.New("invalid checksum") 31 | } 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /gee-bolt/day2-mmap/db.go: -------------------------------------------------------------------------------- 1 | package geebolt 2 | 3 | import "os" 4 | 5 | type DB struct { 6 | data []byte 7 | file *os.File 8 | } 9 | 10 | const maxMapSize = 1 << 31 11 | 12 | func (db *DB) mmap(sz int) error { 13 | b, err := syscall.Mmap() 14 | } 15 | 16 | func Open(path string) { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /gee-bolt/day2-mmap/go.mod: -------------------------------------------------------------------------------- 1 | module geebolt 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gee-bolt/day3-tree/go.mod: -------------------------------------------------------------------------------- 1 | module geebolt 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gee-bolt/day3-tree/meta.go: -------------------------------------------------------------------------------- 1 | package geebolt 2 | 3 | import ( 4 | "errors" 5 | "hash/fnv" 6 | "unsafe" 7 | ) 8 | 9 | // Represent a marker value to indicate that a file is a gee-bolt DB 10 | const magic uint32 = 0xED0CDAED 11 | 12 | type meta struct { 13 | magic uint32 14 | pageSize uint32 15 | pgid uint64 16 | checksum uint64 17 | } 18 | 19 | func (m *meta) sum64() uint64 { 20 | var h = fnv.New64a() 21 | _, _ = h.Write((*[unsafe.Offsetof(meta{}.checksum)]byte)(unsafe.Pointer(m))[:]) 22 | return h.Sum64() 23 | } 24 | 25 | func (m *meta) validate() error { 26 | if m.magic != magic { 27 | return errors.New("invalid magic number") 28 | } 29 | if m.checksum != m.sum64() { 30 | return errors.New("invalid checksum") 31 | } 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /gee-bolt/day3-tree/node.go: -------------------------------------------------------------------------------- 1 | package geebolt 2 | 3 | import ( 4 | "bytes" 5 | "sort" 6 | ) 7 | 8 | type kv struct { 9 | key []byte 10 | value []byte 11 | } 12 | 13 | type node struct { 14 | isLeaf bool 15 | key []byte 16 | parent *node 17 | children []*node 18 | kvs []kv 19 | } 20 | 21 | func (n *node) root() *node { 22 | if n.parent == nil { 23 | return n 24 | } 25 | return n.parent.root() 26 | } 27 | 28 | func (n *node) index(key []byte) (index int, exact bool) { 29 | index = sort.Search(len(n.kvs), func(i int) bool { 30 | return bytes.Compare(n.kvs[i].key, key) != -1 31 | }) 32 | exact = len(n.kvs) > 0 && index < len(n.kvs) && bytes.Equal(n.kvs[index].key, key) 33 | return 34 | } 35 | 36 | func (n *node) put(oldKey, newKey, value []byte) { 37 | index, exact := n.index(oldKey) 38 | if !exact { 39 | n.kvs = append(n.kvs, kv{}) 40 | copy(n.kvs[index+1:], n.kvs[index:]) 41 | } 42 | kv := &n.kvs[index] 43 | kv.key = newKey 44 | kv.value = value 45 | } 46 | 47 | func (n *node) del(key []byte) { 48 | index, exact := n.index(key) 49 | if exact { 50 | n.kvs = append(n.kvs[:index], n.kvs[index+1:]...) 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /gee-cache/day1-lru/geecache/go.mod: -------------------------------------------------------------------------------- 1 | module geecache 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gee-cache/day1-lru/geecache/lru/lru_test.go: -------------------------------------------------------------------------------- 1 | package lru 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | type String string 9 | 10 | func (d String) Len() int { 11 | return len(d) 12 | } 13 | 14 | func TestGet(t *testing.T) { 15 | lru := New(int64(0), nil) 16 | lru.Add("key1", String("1234")) 17 | if v, ok := lru.Get("key1"); !ok || string(v.(String)) != "1234" { 18 | t.Fatalf("cache hit key1=1234 failed") 19 | } 20 | if _, ok := lru.Get("key2"); ok { 21 | t.Fatalf("cache miss key2 failed") 22 | } 23 | } 24 | 25 | func TestRemoveoldest(t *testing.T) { 26 | k1, k2, k3 := "key1", "key2", "k3" 27 | v1, v2, v3 := "value1", "value2", "v3" 28 | cap := len(k1 + k2 + v1 + v2) 29 | lru := New(int64(cap), nil) 30 | lru.Add(k1, String(v1)) 31 | lru.Add(k2, String(v2)) 32 | lru.Add(k3, String(v3)) 33 | 34 | if _, ok := lru.Get("key1"); ok || lru.Len() != 2 { 35 | t.Fatalf("Removeoldest key1 failed") 36 | } 37 | } 38 | 39 | func TestOnEvicted(t *testing.T) { 40 | keys := make([]string, 0) 41 | callback := func(key string, value Value) { 42 | keys = append(keys, key) 43 | } 44 | lru := New(int64(10), callback) 45 | lru.Add("key1", String("123456")) 46 | lru.Add("k2", String("k2")) 47 | lru.Add("k3", String("k3")) 48 | lru.Add("k4", String("k4")) 49 | 50 | expect := []string{"key1", "k2"} 51 | 52 | if !reflect.DeepEqual(expect, keys) { 53 | t.Fatalf("Call OnEvicted failed, expect keys equals to %s", expect) 54 | } 55 | } 56 | 57 | func TestAdd(t *testing.T) { 58 | lru := New(int64(0), nil) 59 | lru.Add("key", String("1")) 60 | lru.Add("key", String("111")) 61 | 62 | if lru.nbytes != int64(len("key")+len("111")) { 63 | t.Fatal("expected 6 but got", lru.nbytes) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /gee-cache/day2-single-node/geecache/byteview.go: -------------------------------------------------------------------------------- 1 | package geecache 2 | 3 | // A ByteView holds an immutable view of bytes. 4 | type ByteView struct { 5 | b []byte 6 | } 7 | 8 | // Len returns the view's length 9 | func (v ByteView) Len() int { 10 | return len(v.b) 11 | } 12 | 13 | // ByteSlice returns a copy of the data as a byte slice. 14 | func (v ByteView) ByteSlice() []byte { 15 | return cloneBytes(v.b) 16 | } 17 | 18 | // String returns the data as a string, making a copy if necessary. 19 | func (v ByteView) String() string { 20 | return string(v.b) 21 | } 22 | 23 | func cloneBytes(b []byte) []byte { 24 | c := make([]byte, len(b)) 25 | copy(c, b) 26 | return c 27 | } 28 | -------------------------------------------------------------------------------- /gee-cache/day2-single-node/geecache/cache.go: -------------------------------------------------------------------------------- 1 | package geecache 2 | 3 | import ( 4 | "geecache/lru" 5 | "sync" 6 | ) 7 | 8 | type cache struct { 9 | mu sync.Mutex 10 | lru *lru.Cache 11 | cacheBytes int64 12 | } 13 | 14 | func (c *cache) add(key string, value ByteView) { 15 | c.mu.Lock() 16 | defer c.mu.Unlock() 17 | if c.lru == nil { 18 | c.lru = lru.New(c.cacheBytes, nil) 19 | } 20 | c.lru.Add(key, value) 21 | } 22 | 23 | func (c *cache) get(key string) (value ByteView, ok bool) { 24 | c.mu.Lock() 25 | defer c.mu.Unlock() 26 | if c.lru == nil { 27 | return 28 | } 29 | 30 | if v, ok := c.lru.Get(key); ok { 31 | return v.(ByteView), ok 32 | } 33 | 34 | return 35 | } 36 | -------------------------------------------------------------------------------- /gee-cache/day2-single-node/geecache/go.mod: -------------------------------------------------------------------------------- 1 | module geecache 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gee-cache/day2-single-node/geecache/lru/lru_test.go: -------------------------------------------------------------------------------- 1 | package lru 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | type String string 9 | 10 | func (d String) Len() int { 11 | return len(d) 12 | } 13 | 14 | func TestGet(t *testing.T) { 15 | lru := New(int64(0), nil) 16 | lru.Add("key1", String("1234")) 17 | if v, ok := lru.Get("key1"); !ok || string(v.(String)) != "1234" { 18 | t.Fatalf("cache hit key1=1234 failed") 19 | } 20 | if _, ok := lru.Get("key2"); ok { 21 | t.Fatalf("cache miss key2 failed") 22 | } 23 | } 24 | 25 | func TestRemoveoldest(t *testing.T) { 26 | k1, k2, k3 := "key1", "key2", "k3" 27 | v1, v2, v3 := "value1", "value2", "v3" 28 | cap := len(k1 + k2 + v1 + v2) 29 | lru := New(int64(cap), nil) 30 | lru.Add(k1, String(v1)) 31 | lru.Add(k2, String(v2)) 32 | lru.Add(k3, String(v3)) 33 | 34 | if _, ok := lru.Get("key1"); ok || lru.Len() != 2 { 35 | t.Fatalf("Removeoldest key1 failed") 36 | } 37 | } 38 | 39 | func TestOnEvicted(t *testing.T) { 40 | keys := make([]string, 0) 41 | callback := func(key string, value Value) { 42 | keys = append(keys, key) 43 | } 44 | lru := New(int64(10), callback) 45 | lru.Add("key1", String("123456")) 46 | lru.Add("k2", String("k2")) 47 | lru.Add("k3", String("k3")) 48 | lru.Add("k4", String("k4")) 49 | 50 | expect := []string{"key1", "k2"} 51 | 52 | if !reflect.DeepEqual(expect, keys) { 53 | t.Fatalf("Call OnEvicted failed, expect keys equals to %s", expect) 54 | } 55 | } 56 | 57 | func TestAdd(t *testing.T) { 58 | lru := New(int64(0), nil) 59 | lru.Add("key", String("1")) 60 | lru.Add("key", String("111")) 61 | 62 | if lru.nbytes != int64(len("key")+len("111")) { 63 | t.Fatal("expected 6 but got", lru.nbytes) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /gee-cache/day3-http-server/geecache/byteview.go: -------------------------------------------------------------------------------- 1 | package geecache 2 | 3 | // A ByteView holds an immutable view of bytes. 4 | type ByteView struct { 5 | b []byte 6 | } 7 | 8 | // Len returns the view's length 9 | func (v ByteView) Len() int { 10 | return len(v.b) 11 | } 12 | 13 | // ByteSlice returns a copy of the data as a byte slice. 14 | func (v ByteView) ByteSlice() []byte { 15 | return cloneBytes(v.b) 16 | } 17 | 18 | // String returns the data as a string, making a copy if necessary. 19 | func (v ByteView) String() string { 20 | return string(v.b) 21 | } 22 | 23 | func cloneBytes(b []byte) []byte { 24 | c := make([]byte, len(b)) 25 | copy(c, b) 26 | return c 27 | } 28 | -------------------------------------------------------------------------------- /gee-cache/day3-http-server/geecache/cache.go: -------------------------------------------------------------------------------- 1 | package geecache 2 | 3 | import ( 4 | "geecache/lru" 5 | "sync" 6 | ) 7 | 8 | type cache struct { 9 | mu sync.Mutex 10 | lru *lru.Cache 11 | cacheBytes int64 12 | } 13 | 14 | func (c *cache) add(key string, value ByteView) { 15 | c.mu.Lock() 16 | defer c.mu.Unlock() 17 | if c.lru == nil { 18 | c.lru = lru.New(c.cacheBytes, nil) 19 | } 20 | c.lru.Add(key, value) 21 | } 22 | 23 | func (c *cache) get(key string) (value ByteView, ok bool) { 24 | c.mu.Lock() 25 | defer c.mu.Unlock() 26 | if c.lru == nil { 27 | return 28 | } 29 | 30 | if v, ok := c.lru.Get(key); ok { 31 | return v.(ByteView), ok 32 | } 33 | 34 | return 35 | } 36 | -------------------------------------------------------------------------------- /gee-cache/day3-http-server/geecache/go.mod: -------------------------------------------------------------------------------- 1 | module geecache 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gee-cache/day3-http-server/geecache/http.go: -------------------------------------------------------------------------------- 1 | package geecache 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | const defaultBasePath = "/_geecache/" 11 | 12 | // HTTPPool implements PeerPicker for a pool of HTTP peers. 13 | type HTTPPool struct { 14 | // this peer's base URL, e.g. "https://example.net:8000" 15 | self string 16 | basePath string 17 | } 18 | 19 | // NewHTTPPool initializes an HTTP pool of peers. 20 | func NewHTTPPool(self string) *HTTPPool { 21 | return &HTTPPool{ 22 | self: self, 23 | basePath: defaultBasePath, 24 | } 25 | } 26 | 27 | // Log info with server name 28 | func (p *HTTPPool) Log(format string, v ...interface{}) { 29 | log.Printf("[Server %s] %s", p.self, fmt.Sprintf(format, v...)) 30 | } 31 | 32 | // ServeHTTP handle all http requests 33 | func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) { 34 | if !strings.HasPrefix(r.URL.Path, p.basePath) { 35 | panic("HTTPPool serving unexpected path: " + r.URL.Path) 36 | } 37 | p.Log("%s %s", r.Method, r.URL.Path) 38 | // /// required 39 | parts := strings.SplitN(r.URL.Path[len(p.basePath):], "/", 2) 40 | if len(parts) != 2 { 41 | http.Error(w, "bad request", http.StatusBadRequest) 42 | return 43 | } 44 | 45 | groupName := parts[0] 46 | key := parts[1] 47 | 48 | group := GetGroup(groupName) 49 | if group == nil { 50 | http.Error(w, "no such group: "+groupName, http.StatusNotFound) 51 | return 52 | } 53 | 54 | view, err := group.Get(key) 55 | if err != nil { 56 | http.Error(w, err.Error(), http.StatusInternalServerError) 57 | return 58 | } 59 | 60 | w.Header().Set("Content-Type", "application/octet-stream") 61 | w.Write(view.ByteSlice()) 62 | } 63 | -------------------------------------------------------------------------------- /gee-cache/day3-http-server/geecache/lru/lru_test.go: -------------------------------------------------------------------------------- 1 | package lru 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | type String string 9 | 10 | func (d String) Len() int { 11 | return len(d) 12 | } 13 | 14 | func TestGet(t *testing.T) { 15 | lru := New(int64(0), nil) 16 | lru.Add("key1", String("1234")) 17 | if v, ok := lru.Get("key1"); !ok || string(v.(String)) != "1234" { 18 | t.Fatalf("cache hit key1=1234 failed") 19 | } 20 | if _, ok := lru.Get("key2"); ok { 21 | t.Fatalf("cache miss key2 failed") 22 | } 23 | } 24 | 25 | func TestRemoveoldest(t *testing.T) { 26 | k1, k2, k3 := "key1", "key2", "k3" 27 | v1, v2, v3 := "value1", "value2", "v3" 28 | cap := len(k1 + k2 + v1 + v2) 29 | lru := New(int64(cap), nil) 30 | lru.Add(k1, String(v1)) 31 | lru.Add(k2, String(v2)) 32 | lru.Add(k3, String(v3)) 33 | 34 | if _, ok := lru.Get("key1"); ok || lru.Len() != 2 { 35 | t.Fatalf("Removeoldest key1 failed") 36 | } 37 | } 38 | 39 | func TestOnEvicted(t *testing.T) { 40 | keys := make([]string, 0) 41 | callback := func(key string, value Value) { 42 | keys = append(keys, key) 43 | } 44 | lru := New(int64(10), callback) 45 | lru.Add("key1", String("123456")) 46 | lru.Add("k2", String("k2")) 47 | lru.Add("k3", String("k3")) 48 | lru.Add("k4", String("k4")) 49 | 50 | expect := []string{"key1", "k2"} 51 | 52 | if !reflect.DeepEqual(expect, keys) { 53 | t.Fatalf("Call OnEvicted failed, expect keys equals to %s", expect) 54 | } 55 | } 56 | 57 | func TestAdd(t *testing.T) { 58 | lru := New(int64(0), nil) 59 | lru.Add("key", String("1")) 60 | lru.Add("key", String("111")) 61 | 62 | if lru.nbytes != int64(len("key")+len("111")) { 63 | t.Fatal("expected 6 but got", lru.nbytes) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /gee-cache/day3-http-server/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.13 4 | 5 | require geecache v0.0.0 6 | 7 | replace geecache => ./geecache 8 | -------------------------------------------------------------------------------- /gee-cache/day3-http-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | $ curl http://localhost:9999/_geecache/scores/Tom 5 | 630 6 | 7 | $ curl http://localhost:9999/_geecache/scores/kkk 8 | kkk not exist 9 | */ 10 | 11 | import ( 12 | "fmt" 13 | "geecache" 14 | "log" 15 | "net/http" 16 | ) 17 | 18 | var db = map[string]string{ 19 | "Tom": "630", 20 | "Jack": "589", 21 | "Sam": "567", 22 | } 23 | 24 | func main() { 25 | geecache.NewGroup("scores", 2<<10, geecache.GetterFunc( 26 | func(key string) ([]byte, error) { 27 | log.Println("[SlowDB] search key", key) 28 | if v, ok := db[key]; ok { 29 | return []byte(v), nil 30 | } 31 | return nil, fmt.Errorf("%s not exist", key) 32 | })) 33 | 34 | addr := "localhost:9999" 35 | peers := geecache.NewHTTPPool(addr) 36 | log.Println("geecache is running at", addr) 37 | log.Fatal(http.ListenAndServe(addr, peers)) 38 | } 39 | -------------------------------------------------------------------------------- /gee-cache/day4-consistent-hash/geecache/byteview.go: -------------------------------------------------------------------------------- 1 | package geecache 2 | 3 | // A ByteView holds an immutable view of bytes. 4 | type ByteView struct { 5 | b []byte 6 | } 7 | 8 | // Len returns the view's length 9 | func (v ByteView) Len() int { 10 | return len(v.b) 11 | } 12 | 13 | // ByteSlice returns a copy of the data as a byte slice. 14 | func (v ByteView) ByteSlice() []byte { 15 | return cloneBytes(v.b) 16 | } 17 | 18 | // String returns the data as a string, making a copy if necessary. 19 | func (v ByteView) String() string { 20 | return string(v.b) 21 | } 22 | 23 | func cloneBytes(b []byte) []byte { 24 | c := make([]byte, len(b)) 25 | copy(c, b) 26 | return c 27 | } 28 | -------------------------------------------------------------------------------- /gee-cache/day4-consistent-hash/geecache/cache.go: -------------------------------------------------------------------------------- 1 | package geecache 2 | 3 | import ( 4 | "geecache/lru" 5 | "sync" 6 | ) 7 | 8 | type cache struct { 9 | mu sync.Mutex 10 | lru *lru.Cache 11 | cacheBytes int64 12 | } 13 | 14 | func (c *cache) add(key string, value ByteView) { 15 | c.mu.Lock() 16 | defer c.mu.Unlock() 17 | if c.lru == nil { 18 | c.lru = lru.New(c.cacheBytes, nil) 19 | } 20 | c.lru.Add(key, value) 21 | } 22 | 23 | func (c *cache) get(key string) (value ByteView, ok bool) { 24 | c.mu.Lock() 25 | defer c.mu.Unlock() 26 | if c.lru == nil { 27 | return 28 | } 29 | 30 | if v, ok := c.lru.Get(key); ok { 31 | return v.(ByteView), ok 32 | } 33 | 34 | return 35 | } 36 | -------------------------------------------------------------------------------- /gee-cache/day4-consistent-hash/geecache/consistenthash/consistenthash.go: -------------------------------------------------------------------------------- 1 | package consistenthash 2 | 3 | import ( 4 | "hash/crc32" 5 | "sort" 6 | "strconv" 7 | ) 8 | 9 | // Hash maps bytes to uint32 10 | type Hash func(data []byte) uint32 11 | 12 | // Map constains all hashed keys 13 | type Map struct { 14 | hash Hash 15 | replicas int 16 | keys []int // Sorted 17 | hashMap map[int]string 18 | } 19 | 20 | // New creates a Map instance 21 | func New(replicas int, fn Hash) *Map { 22 | m := &Map{ 23 | replicas: replicas, 24 | hash: fn, 25 | hashMap: make(map[int]string), 26 | } 27 | if m.hash == nil { 28 | m.hash = crc32.ChecksumIEEE 29 | } 30 | return m 31 | } 32 | 33 | // Add adds some keys to the hash. 34 | func (m *Map) Add(keys ...string) { 35 | for _, key := range keys { 36 | for i := 0; i < m.replicas; i++ { 37 | hash := int(m.hash([]byte(strconv.Itoa(i) + key))) 38 | m.keys = append(m.keys, hash) 39 | m.hashMap[hash] = key 40 | } 41 | } 42 | sort.Ints(m.keys) 43 | } 44 | 45 | // Get gets the closest item in the hash to the provided key. 46 | func (m *Map) Get(key string) string { 47 | if len(m.keys) == 0 { 48 | return "" 49 | } 50 | 51 | hash := int(m.hash([]byte(key))) 52 | // Binary search for appropriate replica. 53 | idx := sort.Search(len(m.keys), func(i int) bool { 54 | return m.keys[i] >= hash 55 | }) 56 | 57 | return m.hashMap[m.keys[idx%len(m.keys)]] 58 | } 59 | -------------------------------------------------------------------------------- /gee-cache/day4-consistent-hash/geecache/consistenthash/consistenthash_test.go: -------------------------------------------------------------------------------- 1 | package consistenthash 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | ) 7 | 8 | func TestHashing(t *testing.T) { 9 | hash := New(3, func(key []byte) uint32 { 10 | i, _ := strconv.Atoi(string(key)) 11 | return uint32(i) 12 | }) 13 | 14 | // Given the above hash function, this will give replicas with "hashes": 15 | // 2, 4, 6, 12, 14, 16, 22, 24, 26 16 | hash.Add("6", "4", "2") 17 | 18 | testCases := map[string]string{ 19 | "2": "2", 20 | "11": "2", 21 | "23": "4", 22 | "27": "2", 23 | } 24 | 25 | for k, v := range testCases { 26 | if hash.Get(k) != v { 27 | t.Errorf("Asking for %s, should have yielded %s", k, v) 28 | } 29 | } 30 | 31 | // Adds 8, 18, 28 32 | hash.Add("8") 33 | 34 | // 27 should now map to 8. 35 | testCases["27"] = "8" 36 | 37 | for k, v := range testCases { 38 | if hash.Get(k) != v { 39 | t.Errorf("Asking for %s, should have yielded %s", k, v) 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /gee-cache/day4-consistent-hash/geecache/go.mod: -------------------------------------------------------------------------------- 1 | module geecache 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gee-cache/day4-consistent-hash/geecache/http.go: -------------------------------------------------------------------------------- 1 | package geecache 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | const defaultBasePath = "/_geecache/" 11 | 12 | // HTTPPool implements PeerPicker for a pool of HTTP peers. 13 | type HTTPPool struct { 14 | // this peer's base URL, e.g. "https://example.net:8000" 15 | self string 16 | basePath string 17 | } 18 | 19 | // NewHTTPPool initializes an HTTP pool of peers. 20 | func NewHTTPPool(self string) *HTTPPool { 21 | return &HTTPPool{ 22 | self: self, 23 | basePath: defaultBasePath, 24 | } 25 | } 26 | 27 | // Log info with server name 28 | func (p *HTTPPool) Log(format string, v ...interface{}) { 29 | log.Printf("[Server %s] %s", p.self, fmt.Sprintf(format, v...)) 30 | } 31 | 32 | // ServeHTTP handle all http requests 33 | func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) { 34 | if !strings.HasPrefix(r.URL.Path, p.basePath) { 35 | panic("HTTPPool serving unexpected path: " + r.URL.Path) 36 | } 37 | p.Log("%s %s", r.Method, r.URL.Path) 38 | // /// required 39 | parts := strings.SplitN(r.URL.Path[len(p.basePath):], "/", 2) 40 | if len(parts) != 2 { 41 | http.Error(w, "bad request", http.StatusBadRequest) 42 | return 43 | } 44 | 45 | groupName := parts[0] 46 | key := parts[1] 47 | 48 | group := GetGroup(groupName) 49 | if group == nil { 50 | http.Error(w, "no such group: "+groupName, http.StatusNotFound) 51 | return 52 | } 53 | 54 | view, err := group.Get(key) 55 | if err != nil { 56 | http.Error(w, err.Error(), http.StatusInternalServerError) 57 | return 58 | } 59 | 60 | w.Header().Set("Content-Type", "application/octet-stream") 61 | w.Write(view.ByteSlice()) 62 | } 63 | -------------------------------------------------------------------------------- /gee-cache/day4-consistent-hash/geecache/lru/lru_test.go: -------------------------------------------------------------------------------- 1 | package lru 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | type String string 9 | 10 | func (d String) Len() int { 11 | return len(d) 12 | } 13 | 14 | func TestGet(t *testing.T) { 15 | lru := New(int64(0), nil) 16 | lru.Add("key1", String("1234")) 17 | if v, ok := lru.Get("key1"); !ok || string(v.(String)) != "1234" { 18 | t.Fatalf("cache hit key1=1234 failed") 19 | } 20 | if _, ok := lru.Get("key2"); ok { 21 | t.Fatalf("cache miss key2 failed") 22 | } 23 | } 24 | 25 | func TestRemoveoldest(t *testing.T) { 26 | k1, k2, k3 := "key1", "key2", "k3" 27 | v1, v2, v3 := "value1", "value2", "v3" 28 | cap := len(k1 + k2 + v1 + v2) 29 | lru := New(int64(cap), nil) 30 | lru.Add(k1, String(v1)) 31 | lru.Add(k2, String(v2)) 32 | lru.Add(k3, String(v3)) 33 | 34 | if _, ok := lru.Get("key1"); ok || lru.Len() != 2 { 35 | t.Fatalf("Removeoldest key1 failed") 36 | } 37 | } 38 | 39 | func TestOnEvicted(t *testing.T) { 40 | keys := make([]string, 0) 41 | callback := func(key string, value Value) { 42 | keys = append(keys, key) 43 | } 44 | lru := New(int64(10), callback) 45 | lru.Add("key1", String("123456")) 46 | lru.Add("k2", String("k2")) 47 | lru.Add("k3", String("k3")) 48 | lru.Add("k4", String("k4")) 49 | 50 | expect := []string{"key1", "k2"} 51 | 52 | if !reflect.DeepEqual(expect, keys) { 53 | t.Fatalf("Call OnEvicted failed, expect keys equals to %s", expect) 54 | } 55 | } 56 | 57 | func TestAdd(t *testing.T) { 58 | lru := New(int64(0), nil) 59 | lru.Add("key", String("1")) 60 | lru.Add("key", String("111")) 61 | 62 | if lru.nbytes != int64(len("key")+len("111")) { 63 | t.Fatal("expected 6 but got", lru.nbytes) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /gee-cache/day4-consistent-hash/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.13 4 | 5 | require geecache v0.0.0 6 | 7 | replace geecache => ./geecache 8 | -------------------------------------------------------------------------------- /gee-cache/day4-consistent-hash/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | $ curl http://localhost:9999/_geecache/scores/Tom 5 | 630 6 | 7 | $ curl http://localhost:9999/_geecache/scores/kkk 8 | kkk not exist 9 | */ 10 | 11 | import ( 12 | "fmt" 13 | "geecache" 14 | "log" 15 | "net/http" 16 | ) 17 | 18 | var db = map[string]string{ 19 | "Tom": "630", 20 | "Jack": "589", 21 | "Sam": "567", 22 | } 23 | 24 | func main() { 25 | geecache.NewGroup("scores", 2<<10, geecache.GetterFunc( 26 | func(key string) ([]byte, error) { 27 | log.Println("[SlowDB] search key", key) 28 | if v, ok := db[key]; ok { 29 | return []byte(v), nil 30 | } 31 | return nil, fmt.Errorf("%s not exist", key) 32 | })) 33 | 34 | addr := "localhost:9999" 35 | peers := geecache.NewHTTPPool(addr) 36 | log.Println("geecache is running at", addr) 37 | log.Fatal(http.ListenAndServe(addr, peers)) 38 | } 39 | -------------------------------------------------------------------------------- /gee-cache/day5-multi-nodes/geecache/byteview.go: -------------------------------------------------------------------------------- 1 | package geecache 2 | 3 | // A ByteView holds an immutable view of bytes. 4 | type ByteView struct { 5 | b []byte 6 | } 7 | 8 | // Len returns the view's length 9 | func (v ByteView) Len() int { 10 | return len(v.b) 11 | } 12 | 13 | // ByteSlice returns a copy of the data as a byte slice. 14 | func (v ByteView) ByteSlice() []byte { 15 | return cloneBytes(v.b) 16 | } 17 | 18 | // String returns the data as a string, making a copy if necessary. 19 | func (v ByteView) String() string { 20 | return string(v.b) 21 | } 22 | 23 | func cloneBytes(b []byte) []byte { 24 | c := make([]byte, len(b)) 25 | copy(c, b) 26 | return c 27 | } 28 | -------------------------------------------------------------------------------- /gee-cache/day5-multi-nodes/geecache/cache.go: -------------------------------------------------------------------------------- 1 | package geecache 2 | 3 | import ( 4 | "geecache/lru" 5 | "sync" 6 | ) 7 | 8 | type cache struct { 9 | mu sync.Mutex 10 | lru *lru.Cache 11 | cacheBytes int64 12 | } 13 | 14 | func (c *cache) add(key string, value ByteView) { 15 | c.mu.Lock() 16 | defer c.mu.Unlock() 17 | if c.lru == nil { 18 | c.lru = lru.New(c.cacheBytes, nil) 19 | } 20 | c.lru.Add(key, value) 21 | } 22 | 23 | func (c *cache) get(key string) (value ByteView, ok bool) { 24 | c.mu.Lock() 25 | defer c.mu.Unlock() 26 | if c.lru == nil { 27 | return 28 | } 29 | 30 | if v, ok := c.lru.Get(key); ok { 31 | return v.(ByteView), ok 32 | } 33 | 34 | return 35 | } 36 | -------------------------------------------------------------------------------- /gee-cache/day5-multi-nodes/geecache/consistenthash/consistenthash.go: -------------------------------------------------------------------------------- 1 | package consistenthash 2 | 3 | import ( 4 | "hash/crc32" 5 | "sort" 6 | "strconv" 7 | ) 8 | 9 | // Hash maps bytes to uint32 10 | type Hash func(data []byte) uint32 11 | 12 | // Map constains all hashed keys 13 | type Map struct { 14 | hash Hash 15 | replicas int 16 | keys []int // Sorted 17 | hashMap map[int]string 18 | } 19 | 20 | // New creates a Map instance 21 | func New(replicas int, fn Hash) *Map { 22 | m := &Map{ 23 | replicas: replicas, 24 | hash: fn, 25 | hashMap: make(map[int]string), 26 | } 27 | if m.hash == nil { 28 | m.hash = crc32.ChecksumIEEE 29 | } 30 | return m 31 | } 32 | 33 | // Add adds some keys to the hash. 34 | func (m *Map) Add(keys ...string) { 35 | for _, key := range keys { 36 | for i := 0; i < m.replicas; i++ { 37 | hash := int(m.hash([]byte(strconv.Itoa(i) + key))) 38 | m.keys = append(m.keys, hash) 39 | m.hashMap[hash] = key 40 | } 41 | } 42 | sort.Ints(m.keys) 43 | } 44 | 45 | // Get gets the closest item in the hash to the provided key. 46 | func (m *Map) Get(key string) string { 47 | if len(m.keys) == 0 { 48 | return "" 49 | } 50 | 51 | hash := int(m.hash([]byte(key))) 52 | // Binary search for appropriate replica. 53 | idx := sort.Search(len(m.keys), func(i int) bool { 54 | return m.keys[i] >= hash 55 | }) 56 | 57 | return m.hashMap[m.keys[idx%len(m.keys)]] 58 | } 59 | -------------------------------------------------------------------------------- /gee-cache/day5-multi-nodes/geecache/consistenthash/consistenthash_test.go: -------------------------------------------------------------------------------- 1 | package consistenthash 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | ) 7 | 8 | func TestHashing(t *testing.T) { 9 | hash := New(3, func(key []byte) uint32 { 10 | i, _ := strconv.Atoi(string(key)) 11 | return uint32(i) 12 | }) 13 | 14 | // Given the above hash function, this will give replicas with "hashes": 15 | // 2, 4, 6, 12, 14, 16, 22, 24, 26 16 | hash.Add("6", "4", "2") 17 | 18 | testCases := map[string]string{ 19 | "2": "2", 20 | "11": "2", 21 | "23": "4", 22 | "27": "2", 23 | } 24 | 25 | for k, v := range testCases { 26 | if hash.Get(k) != v { 27 | t.Errorf("Asking for %s, should have yielded %s", k, v) 28 | } 29 | } 30 | 31 | // Adds 8, 18, 28 32 | hash.Add("8") 33 | 34 | // 27 should now map to 8. 35 | testCases["27"] = "8" 36 | 37 | for k, v := range testCases { 38 | if hash.Get(k) != v { 39 | t.Errorf("Asking for %s, should have yielded %s", k, v) 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /gee-cache/day5-multi-nodes/geecache/go.mod: -------------------------------------------------------------------------------- 1 | module geecache 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gee-cache/day5-multi-nodes/geecache/lru/lru_test.go: -------------------------------------------------------------------------------- 1 | package lru 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | type String string 9 | 10 | func (d String) Len() int { 11 | return len(d) 12 | } 13 | 14 | func TestGet(t *testing.T) { 15 | lru := New(int64(0), nil) 16 | lru.Add("key1", String("1234")) 17 | if v, ok := lru.Get("key1"); !ok || string(v.(String)) != "1234" { 18 | t.Fatalf("cache hit key1=1234 failed") 19 | } 20 | if _, ok := lru.Get("key2"); ok { 21 | t.Fatalf("cache miss key2 failed") 22 | } 23 | } 24 | 25 | func TestRemoveoldest(t *testing.T) { 26 | k1, k2, k3 := "key1", "key2", "k3" 27 | v1, v2, v3 := "value1", "value2", "v3" 28 | cap := len(k1 + k2 + v1 + v2) 29 | lru := New(int64(cap), nil) 30 | lru.Add(k1, String(v1)) 31 | lru.Add(k2, String(v2)) 32 | lru.Add(k3, String(v3)) 33 | 34 | if _, ok := lru.Get("key1"); ok || lru.Len() != 2 { 35 | t.Fatalf("Removeoldest key1 failed") 36 | } 37 | } 38 | 39 | func TestOnEvicted(t *testing.T) { 40 | keys := make([]string, 0) 41 | callback := func(key string, value Value) { 42 | keys = append(keys, key) 43 | } 44 | lru := New(int64(10), callback) 45 | lru.Add("key1", String("123456")) 46 | lru.Add("k2", String("k2")) 47 | lru.Add("k3", String("k3")) 48 | lru.Add("k4", String("k4")) 49 | 50 | expect := []string{"key1", "k2"} 51 | 52 | if !reflect.DeepEqual(expect, keys) { 53 | t.Fatalf("Call OnEvicted failed, expect keys equals to %s", expect) 54 | } 55 | } 56 | 57 | func TestAdd(t *testing.T) { 58 | lru := New(int64(0), nil) 59 | lru.Add("key", String("1")) 60 | lru.Add("key", String("111")) 61 | 62 | if lru.nbytes != int64(len("key")+len("111")) { 63 | t.Fatal("expected 6 but got", lru.nbytes) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /gee-cache/day5-multi-nodes/geecache/peers.go: -------------------------------------------------------------------------------- 1 | package geecache 2 | 3 | // PeerPicker is the interface that must be implemented to locate 4 | // the peer that owns a specific key. 5 | type PeerPicker interface { 6 | PickPeer(key string) (peer PeerGetter, ok bool) 7 | } 8 | 9 | // PeerGetter is the interface that must be implemented by a peer. 10 | type PeerGetter interface { 11 | Get(group string, key string) ([]byte, error) 12 | } 13 | -------------------------------------------------------------------------------- /gee-cache/day5-multi-nodes/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.13 4 | 5 | require geecache v0.0.0 6 | 7 | replace geecache => ./geecache 8 | -------------------------------------------------------------------------------- /gee-cache/day5-multi-nodes/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | trap "rm server;kill 0" EXIT 3 | 4 | go build -o server 5 | ./server -port=8001 & 6 | ./server -port=8002 & 7 | ./server -port=8003 -api=1 & 8 | 9 | sleep 2 10 | echo ">>> start test" 11 | curl "http://localhost:9999/api?key=Tom" & 12 | curl "http://localhost:9999/api?key=Tom" & 13 | curl "http://localhost:9999/api?key=Tom" & 14 | 15 | wait -------------------------------------------------------------------------------- /gee-cache/day6-single-flight/geecache/byteview.go: -------------------------------------------------------------------------------- 1 | package geecache 2 | 3 | // A ByteView holds an immutable view of bytes. 4 | type ByteView struct { 5 | b []byte 6 | } 7 | 8 | // Len returns the view's length 9 | func (v ByteView) Len() int { 10 | return len(v.b) 11 | } 12 | 13 | // ByteSlice returns a copy of the data as a byte slice. 14 | func (v ByteView) ByteSlice() []byte { 15 | return cloneBytes(v.b) 16 | } 17 | 18 | // String returns the data as a string, making a copy if necessary. 19 | func (v ByteView) String() string { 20 | return string(v.b) 21 | } 22 | 23 | func cloneBytes(b []byte) []byte { 24 | c := make([]byte, len(b)) 25 | copy(c, b) 26 | return c 27 | } 28 | -------------------------------------------------------------------------------- /gee-cache/day6-single-flight/geecache/cache.go: -------------------------------------------------------------------------------- 1 | package geecache 2 | 3 | import ( 4 | "geecache/lru" 5 | "sync" 6 | ) 7 | 8 | type cache struct { 9 | mu sync.Mutex 10 | lru *lru.Cache 11 | cacheBytes int64 12 | } 13 | 14 | func (c *cache) add(key string, value ByteView) { 15 | c.mu.Lock() 16 | defer c.mu.Unlock() 17 | if c.lru == nil { 18 | c.lru = lru.New(c.cacheBytes, nil) 19 | } 20 | c.lru.Add(key, value) 21 | } 22 | 23 | func (c *cache) get(key string) (value ByteView, ok bool) { 24 | c.mu.Lock() 25 | defer c.mu.Unlock() 26 | if c.lru == nil { 27 | return 28 | } 29 | 30 | if v, ok := c.lru.Get(key); ok { 31 | return v.(ByteView), ok 32 | } 33 | 34 | return 35 | } 36 | -------------------------------------------------------------------------------- /gee-cache/day6-single-flight/geecache/consistenthash/consistenthash.go: -------------------------------------------------------------------------------- 1 | package consistenthash 2 | 3 | import ( 4 | "hash/crc32" 5 | "sort" 6 | "strconv" 7 | ) 8 | 9 | // Hash maps bytes to uint32 10 | type Hash func(data []byte) uint32 11 | 12 | // Map constains all hashed keys 13 | type Map struct { 14 | hash Hash 15 | replicas int 16 | keys []int // Sorted 17 | hashMap map[int]string 18 | } 19 | 20 | // New creates a Map instance 21 | func New(replicas int, fn Hash) *Map { 22 | m := &Map{ 23 | replicas: replicas, 24 | hash: fn, 25 | hashMap: make(map[int]string), 26 | } 27 | if m.hash == nil { 28 | m.hash = crc32.ChecksumIEEE 29 | } 30 | return m 31 | } 32 | 33 | // Add adds some keys to the hash. 34 | func (m *Map) Add(keys ...string) { 35 | for _, key := range keys { 36 | for i := 0; i < m.replicas; i++ { 37 | hash := int(m.hash([]byte(strconv.Itoa(i) + key))) 38 | m.keys = append(m.keys, hash) 39 | m.hashMap[hash] = key 40 | } 41 | } 42 | sort.Ints(m.keys) 43 | } 44 | 45 | // Get gets the closest item in the hash to the provided key. 46 | func (m *Map) Get(key string) string { 47 | if len(m.keys) == 0 { 48 | return "" 49 | } 50 | 51 | hash := int(m.hash([]byte(key))) 52 | // Binary search for appropriate replica. 53 | idx := sort.Search(len(m.keys), func(i int) bool { 54 | return m.keys[i] >= hash 55 | }) 56 | 57 | return m.hashMap[m.keys[idx%len(m.keys)]] 58 | } 59 | -------------------------------------------------------------------------------- /gee-cache/day6-single-flight/geecache/consistenthash/consistenthash_test.go: -------------------------------------------------------------------------------- 1 | package consistenthash 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | ) 7 | 8 | func TestHashing(t *testing.T) { 9 | hash := New(3, func(key []byte) uint32 { 10 | i, _ := strconv.Atoi(string(key)) 11 | return uint32(i) 12 | }) 13 | 14 | // Given the above hash function, this will give replicas with "hashes": 15 | // 2, 4, 6, 12, 14, 16, 22, 24, 26 16 | hash.Add("6", "4", "2") 17 | 18 | testCases := map[string]string{ 19 | "2": "2", 20 | "11": "2", 21 | "23": "4", 22 | "27": "2", 23 | } 24 | 25 | for k, v := range testCases { 26 | if hash.Get(k) != v { 27 | t.Errorf("Asking for %s, should have yielded %s", k, v) 28 | } 29 | } 30 | 31 | // Adds 8, 18, 28 32 | hash.Add("8") 33 | 34 | // 27 should now map to 8. 35 | testCases["27"] = "8" 36 | 37 | for k, v := range testCases { 38 | if hash.Get(k) != v { 39 | t.Errorf("Asking for %s, should have yielded %s", k, v) 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /gee-cache/day6-single-flight/geecache/go.mod: -------------------------------------------------------------------------------- 1 | module geecache 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gee-cache/day6-single-flight/geecache/lru/lru_test.go: -------------------------------------------------------------------------------- 1 | package lru 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | type String string 9 | 10 | func (d String) Len() int { 11 | return len(d) 12 | } 13 | 14 | func TestGet(t *testing.T) { 15 | lru := New(int64(0), nil) 16 | lru.Add("key1", String("1234")) 17 | if v, ok := lru.Get("key1"); !ok || string(v.(String)) != "1234" { 18 | t.Fatalf("cache hit key1=1234 failed") 19 | } 20 | if _, ok := lru.Get("key2"); ok { 21 | t.Fatalf("cache miss key2 failed") 22 | } 23 | } 24 | 25 | func TestRemoveoldest(t *testing.T) { 26 | k1, k2, k3 := "key1", "key2", "k3" 27 | v1, v2, v3 := "value1", "value2", "v3" 28 | cap := len(k1 + k2 + v1 + v2) 29 | lru := New(int64(cap), nil) 30 | lru.Add(k1, String(v1)) 31 | lru.Add(k2, String(v2)) 32 | lru.Add(k3, String(v3)) 33 | 34 | if _, ok := lru.Get("key1"); ok || lru.Len() != 2 { 35 | t.Fatalf("Removeoldest key1 failed") 36 | } 37 | } 38 | 39 | func TestOnEvicted(t *testing.T) { 40 | keys := make([]string, 0) 41 | callback := func(key string, value Value) { 42 | keys = append(keys, key) 43 | } 44 | lru := New(int64(10), callback) 45 | lru.Add("key1", String("123456")) 46 | lru.Add("k2", String("k2")) 47 | lru.Add("k3", String("k3")) 48 | lru.Add("k4", String("k4")) 49 | 50 | expect := []string{"key1", "k2"} 51 | 52 | if !reflect.DeepEqual(expect, keys) { 53 | t.Fatalf("Call OnEvicted failed, expect keys equals to %s", expect) 54 | } 55 | } 56 | 57 | func TestAdd(t *testing.T) { 58 | lru := New(int64(0), nil) 59 | lru.Add("key", String("1")) 60 | lru.Add("key", String("111")) 61 | 62 | if lru.nbytes != int64(len("key")+len("111")) { 63 | t.Fatal("expected 6 but got", lru.nbytes) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /gee-cache/day6-single-flight/geecache/peers.go: -------------------------------------------------------------------------------- 1 | package geecache 2 | 3 | // PeerPicker is the interface that must be implemented to locate 4 | // the peer that owns a specific key. 5 | type PeerPicker interface { 6 | PickPeer(key string) (peer PeerGetter, ok bool) 7 | } 8 | 9 | // PeerGetter is the interface that must be implemented by a peer. 10 | type PeerGetter interface { 11 | Get(group string, key string) ([]byte, error) 12 | } 13 | -------------------------------------------------------------------------------- /gee-cache/day6-single-flight/geecache/singleflight/singleflight.go: -------------------------------------------------------------------------------- 1 | package singleflight 2 | 3 | import "sync" 4 | 5 | // call is an in-flight or completed Do call 6 | type call struct { 7 | wg sync.WaitGroup 8 | val interface{} 9 | err error 10 | } 11 | 12 | // Group represents a class of work and forms a namespace in which 13 | // units of work can be executed with duplicate suppression. 14 | type Group struct { 15 | mu sync.Mutex // protects m 16 | m map[string]*call // lazily initialized 17 | } 18 | 19 | // Do executes and returns the results of the given function, making 20 | // sure that only one execution is in-flight for a given key at a 21 | // time. If a duplicate comes in, the duplicate caller waits for the 22 | // original to complete and receives the same results. 23 | func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) { 24 | g.mu.Lock() 25 | if g.m == nil { 26 | g.m = make(map[string]*call) 27 | } 28 | if c, ok := g.m[key]; ok { 29 | g.mu.Unlock() 30 | c.wg.Wait() 31 | return c.val, c.err 32 | } 33 | c := new(call) 34 | c.wg.Add(1) 35 | g.m[key] = c 36 | g.mu.Unlock() 37 | 38 | c.val, c.err = fn() 39 | c.wg.Done() 40 | 41 | g.mu.Lock() 42 | delete(g.m, key) 43 | g.mu.Unlock() 44 | 45 | return c.val, c.err 46 | } 47 | -------------------------------------------------------------------------------- /gee-cache/day6-single-flight/geecache/singleflight/singleflight_test.go: -------------------------------------------------------------------------------- 1 | package singleflight 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDo(t *testing.T) { 8 | var g Group 9 | v, err := g.Do("key", func() (interface{}, error) { 10 | return "bar", nil 11 | }) 12 | 13 | if v != "bar" || err != nil { 14 | t.Errorf("Do v = %v, error = %v", v, err) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gee-cache/day6-single-flight/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.13 4 | 5 | require geecache v0.0.0 6 | 7 | replace geecache => ./geecache 8 | -------------------------------------------------------------------------------- /gee-cache/day6-single-flight/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | trap "rm server;kill 0" EXIT 3 | 4 | go build -o server 5 | ./server -port=8001 & 6 | ./server -port=8002 & 7 | ./server -port=8003 -api=1 & 8 | 9 | sleep 2 10 | echo ">>> start test" 11 | curl "http://localhost:9999/api?key=Tom" & 12 | curl "http://localhost:9999/api?key=Tom" & 13 | curl "http://localhost:9999/api?key=Tom" & 14 | curl "http://localhost:9999/api?key=Tom" & 15 | curl "http://localhost:9999/api?key=Tom" & 16 | curl "http://localhost:9999/api?key=Tom" & 17 | curl "http://localhost:9999/api?key=Tom" & 18 | 19 | wait -------------------------------------------------------------------------------- /gee-cache/day7-proto-buf/geecache/byteview.go: -------------------------------------------------------------------------------- 1 | package geecache 2 | 3 | // A ByteView holds an immutable view of bytes. 4 | type ByteView struct { 5 | b []byte 6 | } 7 | 8 | // Len returns the view's length 9 | func (v ByteView) Len() int { 10 | return len(v.b) 11 | } 12 | 13 | // ByteSlice returns a copy of the data as a byte slice. 14 | func (v ByteView) ByteSlice() []byte { 15 | return cloneBytes(v.b) 16 | } 17 | 18 | // String returns the data as a string, making a copy if necessary. 19 | func (v ByteView) String() string { 20 | return string(v.b) 21 | } 22 | 23 | func cloneBytes(b []byte) []byte { 24 | c := make([]byte, len(b)) 25 | copy(c, b) 26 | return c 27 | } 28 | -------------------------------------------------------------------------------- /gee-cache/day7-proto-buf/geecache/cache.go: -------------------------------------------------------------------------------- 1 | package geecache 2 | 3 | import ( 4 | "geecache/lru" 5 | "sync" 6 | ) 7 | 8 | type cache struct { 9 | mu sync.Mutex 10 | lru *lru.Cache 11 | cacheBytes int64 12 | } 13 | 14 | func (c *cache) add(key string, value ByteView) { 15 | c.mu.Lock() 16 | defer c.mu.Unlock() 17 | if c.lru == nil { 18 | c.lru = lru.New(c.cacheBytes, nil) 19 | } 20 | c.lru.Add(key, value) 21 | } 22 | 23 | func (c *cache) get(key string) (value ByteView, ok bool) { 24 | c.mu.Lock() 25 | defer c.mu.Unlock() 26 | if c.lru == nil { 27 | return 28 | } 29 | 30 | if v, ok := c.lru.Get(key); ok { 31 | return v.(ByteView), ok 32 | } 33 | 34 | return 35 | } 36 | -------------------------------------------------------------------------------- /gee-cache/day7-proto-buf/geecache/consistenthash/consistenthash.go: -------------------------------------------------------------------------------- 1 | package consistenthash 2 | 3 | import ( 4 | "hash/crc32" 5 | "sort" 6 | "strconv" 7 | ) 8 | 9 | // Hash maps bytes to uint32 10 | type Hash func(data []byte) uint32 11 | 12 | // Map constains all hashed keys 13 | type Map struct { 14 | hash Hash 15 | replicas int 16 | keys []int // Sorted 17 | hashMap map[int]string 18 | } 19 | 20 | // New creates a Map instance 21 | func New(replicas int, fn Hash) *Map { 22 | m := &Map{ 23 | replicas: replicas, 24 | hash: fn, 25 | hashMap: make(map[int]string), 26 | } 27 | if m.hash == nil { 28 | m.hash = crc32.ChecksumIEEE 29 | } 30 | return m 31 | } 32 | 33 | // Add adds some keys to the hash. 34 | func (m *Map) Add(keys ...string) { 35 | for _, key := range keys { 36 | for i := 0; i < m.replicas; i++ { 37 | hash := int(m.hash([]byte(strconv.Itoa(i) + key))) 38 | m.keys = append(m.keys, hash) 39 | m.hashMap[hash] = key 40 | } 41 | } 42 | sort.Ints(m.keys) 43 | } 44 | 45 | // Get gets the closest item in the hash to the provided key. 46 | func (m *Map) Get(key string) string { 47 | if len(m.keys) == 0 { 48 | return "" 49 | } 50 | 51 | hash := int(m.hash([]byte(key))) 52 | // Binary search for appropriate replica. 53 | idx := sort.Search(len(m.keys), func(i int) bool { 54 | return m.keys[i] >= hash 55 | }) 56 | 57 | return m.hashMap[m.keys[idx%len(m.keys)]] 58 | } 59 | -------------------------------------------------------------------------------- /gee-cache/day7-proto-buf/geecache/consistenthash/consistenthash_test.go: -------------------------------------------------------------------------------- 1 | package consistenthash 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | ) 7 | 8 | func TestHashing(t *testing.T) { 9 | hash := New(3, func(key []byte) uint32 { 10 | i, _ := strconv.Atoi(string(key)) 11 | return uint32(i) 12 | }) 13 | 14 | // Given the above hash function, this will give replicas with "hashes": 15 | // 2, 4, 6, 12, 14, 16, 22, 24, 26 16 | hash.Add("6", "4", "2") 17 | 18 | testCases := map[string]string{ 19 | "2": "2", 20 | "11": "2", 21 | "23": "4", 22 | "27": "2", 23 | } 24 | 25 | for k, v := range testCases { 26 | if hash.Get(k) != v { 27 | t.Errorf("Asking for %s, should have yielded %s", k, v) 28 | } 29 | } 30 | 31 | // Adds 8, 18, 28 32 | hash.Add("8") 33 | 34 | // 27 should now map to 8. 35 | testCases["27"] = "8" 36 | 37 | for k, v := range testCases { 38 | if hash.Get(k) != v { 39 | t.Errorf("Asking for %s, should have yielded %s", k, v) 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /gee-cache/day7-proto-buf/geecache/geecachepb/geecachepb.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package geecachepb; 4 | 5 | message Request { 6 | string group = 1; 7 | string key = 2; 8 | } 9 | 10 | message Response { 11 | bytes value = 1; 12 | } 13 | 14 | service GroupCache { 15 | rpc Get(Request) returns (Response); 16 | } 17 | -------------------------------------------------------------------------------- /gee-cache/day7-proto-buf/geecache/go.mod: -------------------------------------------------------------------------------- 1 | module geecache 2 | 3 | go 1.13 4 | 5 | require github.com/golang/protobuf v1.3.3 6 | -------------------------------------------------------------------------------- /gee-cache/day7-proto-buf/geecache/go.sum: -------------------------------------------------------------------------------- 1 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= 2 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 3 | -------------------------------------------------------------------------------- /gee-cache/day7-proto-buf/geecache/lru/lru_test.go: -------------------------------------------------------------------------------- 1 | package lru 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | type String string 9 | 10 | func (d String) Len() int { 11 | return len(d) 12 | } 13 | 14 | func TestGet(t *testing.T) { 15 | lru := New(int64(0), nil) 16 | lru.Add("key1", String("1234")) 17 | if v, ok := lru.Get("key1"); !ok || string(v.(String)) != "1234" { 18 | t.Fatalf("cache hit key1=1234 failed") 19 | } 20 | if _, ok := lru.Get("key2"); ok { 21 | t.Fatalf("cache miss key2 failed") 22 | } 23 | } 24 | 25 | func TestRemoveoldest(t *testing.T) { 26 | k1, k2, k3 := "key1", "key2", "k3" 27 | v1, v2, v3 := "value1", "value2", "v3" 28 | cap := len(k1 + k2 + v1 + v2) 29 | lru := New(int64(cap), nil) 30 | lru.Add(k1, String(v1)) 31 | lru.Add(k2, String(v2)) 32 | lru.Add(k3, String(v3)) 33 | 34 | if _, ok := lru.Get("key1"); ok || lru.Len() != 2 { 35 | t.Fatalf("Removeoldest key1 failed") 36 | } 37 | } 38 | 39 | func TestOnEvicted(t *testing.T) { 40 | keys := make([]string, 0) 41 | callback := func(key string, value Value) { 42 | keys = append(keys, key) 43 | } 44 | lru := New(int64(10), callback) 45 | lru.Add("key1", String("123456")) 46 | lru.Add("k2", String("k2")) 47 | lru.Add("k3", String("k3")) 48 | lru.Add("k4", String("k4")) 49 | 50 | expect := []string{"key1", "k2"} 51 | 52 | if !reflect.DeepEqual(expect, keys) { 53 | t.Fatalf("Call OnEvicted failed, expect keys equals to %s", expect) 54 | } 55 | } 56 | 57 | func TestAdd(t *testing.T) { 58 | lru := New(int64(0), nil) 59 | lru.Add("key", String("1")) 60 | lru.Add("key", String("111")) 61 | 62 | if lru.nbytes != int64(len("key")+len("111")) { 63 | t.Fatal("expected 6 but got", lru.nbytes) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /gee-cache/day7-proto-buf/geecache/peers.go: -------------------------------------------------------------------------------- 1 | package geecache 2 | 3 | import pb "geecache/geecachepb" 4 | 5 | // PeerPicker is the interface that must be implemented to locate 6 | // the peer that owns a specific key. 7 | type PeerPicker interface { 8 | PickPeer(key string) (peer PeerGetter, ok bool) 9 | } 10 | 11 | // PeerGetter is the interface that must be implemented by a peer. 12 | type PeerGetter interface { 13 | Get(in *pb.Request, out *pb.Response) error 14 | } 15 | -------------------------------------------------------------------------------- /gee-cache/day7-proto-buf/geecache/singleflight/singleflight.go: -------------------------------------------------------------------------------- 1 | package singleflight 2 | 3 | import "sync" 4 | 5 | // call is an in-flight or completed Do call 6 | type call struct { 7 | wg sync.WaitGroup 8 | val interface{} 9 | err error 10 | } 11 | 12 | // Group represents a class of work and forms a namespace in which 13 | // units of work can be executed with duplicate suppression. 14 | type Group struct { 15 | mu sync.Mutex // protects m 16 | m map[string]*call // lazily initialized 17 | } 18 | 19 | // Do executes and returns the results of the given function, making 20 | // sure that only one execution is in-flight for a given key at a 21 | // time. If a duplicate comes in, the duplicate caller waits for the 22 | // original to complete and receives the same results. 23 | func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) { 24 | g.mu.Lock() 25 | if g.m == nil { 26 | g.m = make(map[string]*call) 27 | } 28 | if c, ok := g.m[key]; ok { 29 | g.mu.Unlock() 30 | c.wg.Wait() 31 | return c.val, c.err 32 | } 33 | c := new(call) 34 | c.wg.Add(1) 35 | g.m[key] = c 36 | g.mu.Unlock() 37 | 38 | c.val, c.err = fn() 39 | c.wg.Done() 40 | 41 | g.mu.Lock() 42 | delete(g.m, key) 43 | g.mu.Unlock() 44 | 45 | return c.val, c.err 46 | } 47 | -------------------------------------------------------------------------------- /gee-cache/day7-proto-buf/geecache/singleflight/singleflight_test.go: -------------------------------------------------------------------------------- 1 | package singleflight 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDo(t *testing.T) { 8 | var g Group 9 | v, err := g.Do("key", func() (interface{}, error) { 10 | return "bar", nil 11 | }) 12 | 13 | if v != "bar" || err != nil { 14 | t.Errorf("Do v = %v, error = %v", v, err) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gee-cache/day7-proto-buf/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.13 4 | 5 | require geecache v0.0.0 6 | 7 | replace geecache => ./geecache 8 | -------------------------------------------------------------------------------- /gee-cache/day7-proto-buf/go.sum: -------------------------------------------------------------------------------- 1 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= 2 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 3 | -------------------------------------------------------------------------------- /gee-cache/day7-proto-buf/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | trap "rm server;kill 0" EXIT 3 | 4 | go build -o server 5 | ./server -port=8001 & 6 | ./server -port=8002 & 7 | ./server -port=8003 -api=1 & 8 | 9 | sleep 2 10 | echo ">>> start test" 11 | curl "http://localhost:9999/api?key=Tom" & 12 | curl "http://localhost:9999/api?key=Tom" & 13 | curl "http://localhost:9999/api?key=Tom" & 14 | 15 | wait -------------------------------------------------------------------------------- /gee-cache/doc/geecache-day1/lru.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-cache/doc/geecache-day1/lru.jpg -------------------------------------------------------------------------------- /gee-cache/doc/geecache-day1/lru_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-cache/doc/geecache-day1/lru_logo.jpg -------------------------------------------------------------------------------- /gee-cache/doc/geecache-day2/concurrent_cache.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-cache/doc/geecache-day2/concurrent_cache.jpg -------------------------------------------------------------------------------- /gee-cache/doc/geecache-day2/concurrent_cache_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-cache/doc/geecache-day2/concurrent_cache_logo.jpg -------------------------------------------------------------------------------- /gee-cache/doc/geecache-day3/http.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-cache/doc/geecache-day3/http.jpg -------------------------------------------------------------------------------- /gee-cache/doc/geecache-day3/http_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-cache/doc/geecache-day3/http_logo.jpg -------------------------------------------------------------------------------- /gee-cache/doc/geecache-day4/add_peer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-cache/doc/geecache-day4/add_peer.jpg -------------------------------------------------------------------------------- /gee-cache/doc/geecache-day4/hash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-cache/doc/geecache-day4/hash.jpg -------------------------------------------------------------------------------- /gee-cache/doc/geecache-day4/hash_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-cache/doc/geecache-day4/hash_logo.jpg -------------------------------------------------------------------------------- /gee-cache/doc/geecache-day4/hash_select.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-cache/doc/geecache-day4/hash_select.jpg -------------------------------------------------------------------------------- /gee-cache/doc/geecache-day5/dist_nodes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-cache/doc/geecache-day5/dist_nodes.jpg -------------------------------------------------------------------------------- /gee-cache/doc/geecache-day5/dist_nodes_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-cache/doc/geecache-day5/dist_nodes_logo.jpg -------------------------------------------------------------------------------- /gee-cache/doc/geecache-day6/singleflight.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-cache/doc/geecache-day6/singleflight.jpg -------------------------------------------------------------------------------- /gee-cache/doc/geecache-day6/singleflight_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-cache/doc/geecache-day6/singleflight_logo.jpg -------------------------------------------------------------------------------- /gee-cache/doc/geecache-day7/protobuf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-cache/doc/geecache-day7/protobuf.jpg -------------------------------------------------------------------------------- /gee-cache/doc/geecache-day7/protobuf_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-cache/doc/geecache-day7/protobuf_logo.jpg -------------------------------------------------------------------------------- /gee-cache/doc/geecache/geecache.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-cache/doc/geecache/geecache.jpg -------------------------------------------------------------------------------- /gee-cache/doc/geecache/geecache_sm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-cache/doc/geecache/geecache_sm.jpg -------------------------------------------------------------------------------- /gee-orm/day1-database-sql/cmd_test/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "geeorm" 6 | _ "github.com/mattn/go-sqlite3" 7 | ) 8 | 9 | func main() { 10 | engine, _ := geeorm.NewEngine("sqlite3", "gee.db") 11 | defer engine.Close() 12 | s := engine.NewSession() 13 | _, _ = s.Raw("DROP TABLE IF EXISTS User;").Exec() 14 | _, _ = s.Raw("CREATE TABLE User(Name text);").Exec() 15 | _, _ = s.Raw("CREATE TABLE User(Name text);").Exec() 16 | result, _ := s.Raw("INSERT INTO User(`Name`) values (?), (?)", "Tom", "Sam").Exec() 17 | count, _ := result.RowsAffected() 18 | fmt.Printf("Exec success, %d affected\n", count) 19 | 20 | } 21 | -------------------------------------------------------------------------------- /gee-orm/day1-database-sql/geeorm.go: -------------------------------------------------------------------------------- 1 | package geeorm 2 | 3 | import ( 4 | "database/sql" 5 | 6 | "geeorm/log" 7 | "geeorm/session" 8 | ) 9 | 10 | // Engine is the main struct of geeorm, manages all db sessions and transactions. 11 | type Engine struct { 12 | db *sql.DB 13 | } 14 | 15 | // NewEngine create a instance of Engine 16 | // connect database and ping it to test whether it's alive 17 | func NewEngine(driver, source string) (e *Engine, err error) { 18 | db, err := sql.Open(driver, source) 19 | if err != nil { 20 | log.Error(err) 21 | return 22 | } 23 | // Send a ping to make sure the database connection is alive. 24 | if err = db.Ping(); err != nil { 25 | log.Error(err) 26 | return 27 | } 28 | e = &Engine{db: db} 29 | log.Info("Connect database success") 30 | return 31 | } 32 | 33 | // Close database connection 34 | func (engine *Engine) Close() { 35 | if err := engine.db.Close(); err != nil { 36 | log.Error("Failed to close database") 37 | } 38 | log.Info("Close database success") 39 | } 40 | 41 | // NewSession creates a new session for next operations 42 | func (engine *Engine) NewSession() *session.Session { 43 | return session.New(engine.db) 44 | } 45 | -------------------------------------------------------------------------------- /gee-orm/day1-database-sql/geeorm_test.go: -------------------------------------------------------------------------------- 1 | package geeorm 2 | 3 | import ( 4 | _ "github.com/mattn/go-sqlite3" 5 | "testing" 6 | ) 7 | 8 | func OpenDB(t *testing.T) *Engine { 9 | t.Helper() 10 | engine, err := NewEngine("sqlite3", "gee.db") 11 | if err != nil { 12 | t.Fatal("failed to connect", err) 13 | } 14 | return engine 15 | } 16 | 17 | func TestNewEngine(t *testing.T) { 18 | engine := OpenDB(t) 19 | defer engine.Close() 20 | } 21 | -------------------------------------------------------------------------------- /gee-orm/day1-database-sql/go.mod: -------------------------------------------------------------------------------- 1 | module geeorm 2 | 3 | go 1.13 4 | 5 | require github.com/mattn/go-sqlite3 v2.0.3+incompatible 6 | -------------------------------------------------------------------------------- /gee-orm/day1-database-sql/log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | var ( 11 | errorLog = log.New(os.Stdout, "\033[31m[error]\033[0m ", log.LstdFlags|log.Lshortfile) 12 | infoLog = log.New(os.Stdout, "\033[34m[info ]\033[0m ", log.LstdFlags|log.Lshortfile) 13 | loggers = []*log.Logger{errorLog, infoLog} 14 | mu sync.Mutex 15 | ) 16 | 17 | // log methods 18 | var ( 19 | Error = errorLog.Println 20 | Errorf = errorLog.Printf 21 | Info = infoLog.Println 22 | Infof = infoLog.Printf 23 | ) 24 | 25 | // log levels 26 | const ( 27 | InfoLevel = iota 28 | ErrorLevel 29 | Disabled 30 | ) 31 | 32 | // SetLevel controls log level 33 | func SetLevel(level int) { 34 | mu.Lock() 35 | defer mu.Unlock() 36 | 37 | for _, logger := range loggers { 38 | logger.SetOutput(os.Stdout) 39 | } 40 | 41 | if ErrorLevel < level { 42 | errorLog.SetOutput(ioutil.Discard) 43 | } 44 | if InfoLevel < level { 45 | infoLog.SetOutput(ioutil.Discard) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /gee-orm/day1-database-sql/log/log_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestSetLevel(t *testing.T) { 9 | SetLevel(ErrorLevel) 10 | if infoLog.Writer() == os.Stdout || errorLog.Writer() != os.Stdout { 11 | t.Fatal("failed to set log level") 12 | } 13 | SetLevel(Disabled) 14 | if infoLog.Writer() == os.Stdout || errorLog.Writer() == os.Stdout { 15 | t.Fatal("failed to set log level") 16 | } 17 | } -------------------------------------------------------------------------------- /gee-orm/day1-database-sql/session/raw_test.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "database/sql" 5 | "os" 6 | "testing" 7 | 8 | _ "github.com/mattn/go-sqlite3" 9 | ) 10 | 11 | var TestDB *sql.DB 12 | 13 | func TestMain(m *testing.M) { 14 | TestDB, _ = sql.Open("sqlite3", "../gee.db") 15 | code := m.Run() 16 | _ = TestDB.Close() 17 | os.Exit(code) 18 | } 19 | 20 | func NewSession() *Session { 21 | return New(TestDB) 22 | } 23 | 24 | func TestSession_Exec(t *testing.T) { 25 | s := NewSession() 26 | _, _ = s.Raw("DROP TABLE IF EXISTS User;").Exec() 27 | _, _ = s.Raw("CREATE TABLE User(Name text);").Exec() 28 | result, _ := s.Raw("INSERT INTO User(`Name`) values (?), (?)", "Tom", "Sam").Exec() 29 | if count, err := result.RowsAffected(); err != nil || count != 2 { 30 | t.Fatal("expect 2, but got", count) 31 | } 32 | } 33 | 34 | func TestSession_QueryRows(t *testing.T) { 35 | s := NewSession() 36 | _, _ = s.Raw("DROP TABLE IF EXISTS User;").Exec() 37 | _, _ = s.Raw("CREATE TABLE User(Name text);").Exec() 38 | row := s.Raw("SELECT count(*) FROM User").QueryRow() 39 | var count int 40 | if err := row.Scan(&count); err != nil || count != 0 { 41 | t.Fatal("failed to query db", err) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /gee-orm/day2-reflect-schema/dialect/dialect.go: -------------------------------------------------------------------------------- 1 | package dialect 2 | 3 | import "reflect" 4 | 5 | var dialectsMap = map[string]Dialect{} 6 | 7 | // Dialect is an interface contains methods that a dialect has to implement 8 | type Dialect interface { 9 | DataTypeOf(typ reflect.Value) string 10 | TableExistSQL(tableName string) (string, []interface{}) 11 | } 12 | 13 | // RegisterDialect register a dialect to the global variable 14 | func RegisterDialect(name string, dialect Dialect) { 15 | dialectsMap[name] = dialect 16 | } 17 | 18 | // Get the dialect from global variable if it exists 19 | func GetDialect(name string) (dialect Dialect, ok bool) { 20 | dialect, ok = dialectsMap[name] 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /gee-orm/day2-reflect-schema/dialect/sqlite3.go: -------------------------------------------------------------------------------- 1 | package dialect 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "time" 7 | ) 8 | 9 | type sqlite3 struct{} 10 | 11 | var _ Dialect = (*sqlite3)(nil) 12 | 13 | func init() { 14 | RegisterDialect("sqlite3", &sqlite3{}) 15 | } 16 | 17 | // Get Data Type for sqlite3 Dialect 18 | func (s *sqlite3) DataTypeOf(typ reflect.Value) string { 19 | switch typ.Kind() { 20 | case reflect.Bool: 21 | return "bool" 22 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, 23 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr: 24 | return "integer" 25 | case reflect.Int64, reflect.Uint64: 26 | return "bigint" 27 | case reflect.Float32, reflect.Float64: 28 | return "real" 29 | case reflect.String: 30 | return "text" 31 | case reflect.Array, reflect.Slice: 32 | return "blob" 33 | case reflect.Struct: 34 | if _, ok := typ.Interface().(time.Time); ok { 35 | return "datetime" 36 | } 37 | } 38 | panic(fmt.Sprintf("invalid sql type %s (%s)", typ.Type().Name(), typ.Kind())) 39 | } 40 | 41 | // TableExistSQL returns SQL that judge whether the table exists in database 42 | func (s *sqlite3) TableExistSQL(tableName string) (string, []interface{}) { 43 | args := []interface{}{tableName} 44 | return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args 45 | } 46 | -------------------------------------------------------------------------------- /gee-orm/day2-reflect-schema/dialect/sqlite3_test.go: -------------------------------------------------------------------------------- 1 | package dialect 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestDataTypeOf(t *testing.T) { 9 | dial := &sqlite3{} 10 | cases := []struct { 11 | Value interface{} 12 | Type string 13 | }{ 14 | {"Tom", "text"}, 15 | {123, "integer"}, 16 | {1.2, "real"}, 17 | {[]int{1, 2, 3}, "blob"}, 18 | } 19 | 20 | for _, c := range cases { 21 | if typ := dial.DataTypeOf(reflect.ValueOf(c.Value)); typ != c.Type { 22 | t.Fatalf("expect %s, but got %s", c.Type, typ) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /gee-orm/day2-reflect-schema/geeorm.go: -------------------------------------------------------------------------------- 1 | package geeorm 2 | 3 | import ( 4 | "database/sql" 5 | "geeorm/dialect" 6 | "geeorm/log" 7 | "geeorm/session" 8 | ) 9 | 10 | // Engine is the main struct of geeorm, manages all db sessions and transactions. 11 | type Engine struct { 12 | db *sql.DB 13 | dialect dialect.Dialect 14 | } 15 | 16 | // NewEngine create a instance of Engine 17 | // connect database and ping it to test whether it's alive 18 | func NewEngine(driver, source string) (e *Engine, err error) { 19 | db, err := sql.Open(driver, source) 20 | if err != nil { 21 | log.Error(err) 22 | return 23 | } 24 | // Send a ping to make sure the database connection is alive. 25 | if err = db.Ping(); err != nil { 26 | log.Error(err) 27 | return 28 | } 29 | // make sure the specific dialect exists 30 | dial, ok := dialect.GetDialect(driver) 31 | if !ok { 32 | log.Errorf("dialect %s Not Found", driver) 33 | return 34 | } 35 | e = &Engine{db: db, dialect: dial} 36 | log.Info("Connect database success") 37 | return 38 | } 39 | 40 | // Close database connection 41 | func (engine *Engine) Close() { 42 | if err := engine.db.Close(); err != nil { 43 | log.Error("Failed to close database") 44 | } 45 | log.Info("Close database success") 46 | } 47 | 48 | // NewSession creates a new session for next operations 49 | func (engine *Engine) NewSession() *session.Session { 50 | return session.New(engine.db, engine.dialect) 51 | } 52 | -------------------------------------------------------------------------------- /gee-orm/day2-reflect-schema/geeorm_test.go: -------------------------------------------------------------------------------- 1 | package geeorm 2 | 3 | import ( 4 | _ "github.com/mattn/go-sqlite3" 5 | "testing" 6 | ) 7 | 8 | func OpenDB(t *testing.T) *Engine { 9 | t.Helper() 10 | engine, err := NewEngine("sqlite3", "gee.db") 11 | if err != nil { 12 | t.Fatal("failed to connect", err) 13 | } 14 | return engine 15 | } 16 | 17 | func TestNewEngine(t *testing.T) { 18 | engine := OpenDB(t) 19 | defer engine.Close() 20 | } 21 | -------------------------------------------------------------------------------- /gee-orm/day2-reflect-schema/go.mod: -------------------------------------------------------------------------------- 1 | module geeorm 2 | 3 | go 1.13 4 | 5 | require github.com/mattn/go-sqlite3 v2.0.3+incompatible 6 | -------------------------------------------------------------------------------- /gee-orm/day2-reflect-schema/log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | var ( 11 | errorLog = log.New(os.Stdout, "\033[31m[error]\033[0m ", log.LstdFlags|log.Lshortfile) 12 | infoLog = log.New(os.Stdout, "\033[34m[info ]\033[0m ", log.LstdFlags|log.Lshortfile) 13 | loggers = []*log.Logger{errorLog, infoLog} 14 | mu sync.Mutex 15 | ) 16 | 17 | // log methods 18 | var ( 19 | Error = errorLog.Println 20 | Errorf = errorLog.Printf 21 | Info = infoLog.Println 22 | Infof = infoLog.Printf 23 | ) 24 | 25 | // log levels 26 | const ( 27 | InfoLevel = iota 28 | ErrorLevel 29 | Disabled 30 | ) 31 | 32 | // SetLevel controls log level 33 | func SetLevel(level int) { 34 | mu.Lock() 35 | defer mu.Unlock() 36 | 37 | for _, logger := range loggers { 38 | logger.SetOutput(os.Stdout) 39 | } 40 | 41 | if ErrorLevel < level { 42 | errorLog.SetOutput(ioutil.Discard) 43 | } 44 | if InfoLevel < level { 45 | infoLog.SetOutput(ioutil.Discard) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /gee-orm/day2-reflect-schema/log/log_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestSetLevel(t *testing.T) { 9 | SetLevel(ErrorLevel) 10 | if infoLog.Writer() == os.Stdout || errorLog.Writer() != os.Stdout { 11 | t.Fatal("failed to set log level") 12 | } 13 | SetLevel(Disabled) 14 | if infoLog.Writer() == os.Stdout || errorLog.Writer() == os.Stdout { 15 | t.Fatal("failed to set log level") 16 | } 17 | } -------------------------------------------------------------------------------- /gee-orm/day2-reflect-schema/schema/schema_test.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "geeorm/dialect" 5 | "testing" 6 | ) 7 | 8 | type User struct { 9 | Name string `geeorm:"PRIMARY KEY"` 10 | Age int 11 | } 12 | 13 | var TestDial, _ = dialect.GetDialect("sqlite3") 14 | 15 | func TestParse(t *testing.T) { 16 | schema := Parse(&User{}, TestDial) 17 | if schema.Name != "User" || len(schema.Fields) != 2 { 18 | t.Fatal("failed to parse User struct") 19 | } 20 | if schema.GetField("Name").Tag != "PRIMARY KEY" { 21 | t.Fatal("failed to parse primary key") 22 | } 23 | } 24 | 25 | func TestSchema_RecordValues(t *testing.T) { 26 | schema := Parse(&User{}, TestDial) 27 | values := schema.RecordValues(&User{"Tom", 18}) 28 | 29 | name := values[0].(string) 30 | age := values[1].(int) 31 | 32 | if name != "Tom" || age != 18 { 33 | t.Fatal("failed to get values") 34 | } 35 | } 36 | 37 | type UserTest struct { 38 | Name string `geeorm:"PRIMARY KEY"` 39 | Age int 40 | } 41 | 42 | func (u *UserTest) TableName() string { 43 | return "ns_user_test" 44 | } 45 | 46 | func TestSchema_TableName(t *testing.T) { 47 | schema := Parse(&UserTest{}, TestDial) 48 | if schema.Name != "ns_user_test" || len(schema.Fields) != 2 { 49 | t.Fatal("failed to parse User struct") 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /gee-orm/day2-reflect-schema/session/raw_test.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "database/sql" 5 | "os" 6 | "testing" 7 | 8 | "geeorm/dialect" 9 | 10 | _ "github.com/mattn/go-sqlite3" 11 | ) 12 | 13 | var ( 14 | TestDB *sql.DB 15 | TestDial, _ = dialect.GetDialect("sqlite3") 16 | ) 17 | 18 | func TestMain(m *testing.M) { 19 | TestDB, _ = sql.Open("sqlite3", "../gee.db") 20 | code := m.Run() 21 | _ = TestDB.Close() 22 | os.Exit(code) 23 | } 24 | 25 | func NewSession() *Session { 26 | return New(TestDB, TestDial) 27 | } 28 | 29 | func TestSession_Exec(t *testing.T) { 30 | s := NewSession() 31 | _, _ = s.Raw("DROP TABLE IF EXISTS User;").Exec() 32 | _, _ = s.Raw("CREATE TABLE User(Name text);").Exec() 33 | result, _ := s.Raw("INSERT INTO User(`Name`) values (?), (?)", "Tom", "Sam").Exec() 34 | if count, err := result.RowsAffected(); err != nil || count != 2 { 35 | t.Fatal("expect 2, but got", count) 36 | } 37 | } 38 | 39 | func TestSession_QueryRows(t *testing.T) { 40 | s := NewSession() 41 | _, _ = s.Raw("DROP TABLE IF EXISTS User;").Exec() 42 | _, _ = s.Raw("CREATE TABLE User(Name text);").Exec() 43 | row := s.Raw("SELECT count(*) FROM User").QueryRow() 44 | var count int 45 | if err := row.Scan(&count); err != nil || count != 0 { 46 | t.Fatal("failed to query db", err) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /gee-orm/day2-reflect-schema/session/table.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "fmt" 5 | "geeorm/log" 6 | "reflect" 7 | "strings" 8 | 9 | "geeorm/schema" 10 | ) 11 | 12 | // Model assigns refTable 13 | func (s *Session) Model(value interface{}) *Session { 14 | // nil or different model, update refTable 15 | if s.refTable == nil || reflect.TypeOf(value) != reflect.TypeOf(s.refTable.Model) { 16 | s.refTable = schema.Parse(value, s.dialect) 17 | } 18 | return s 19 | } 20 | 21 | // RefTable returns a Schema instance that contains all parsed fields 22 | func (s *Session) RefTable() *schema.Schema { 23 | if s.refTable == nil { 24 | log.Error("Model is not set") 25 | } 26 | return s.refTable 27 | } 28 | 29 | // CreateTable create a table in database with a model 30 | func (s *Session) CreateTable() error { 31 | table := s.RefTable() 32 | var columns []string 33 | for _, field := range table.Fields { 34 | columns = append(columns, fmt.Sprintf("%s %s %s", field.Name, field.Type, field.Tag)) 35 | } 36 | desc := strings.Join(columns, ",") 37 | _, err := s.Raw(fmt.Sprintf("CREATE TABLE %s (%s);", table.Name, desc)).Exec() 38 | return err 39 | } 40 | 41 | // DropTable drops a table with the name of model 42 | func (s *Session) DropTable() error { 43 | _, err := s.Raw(fmt.Sprintf("DROP TABLE IF EXISTS %s", s.RefTable().Name)).Exec() 44 | return err 45 | } 46 | 47 | // HasTable returns true of the table exists 48 | func (s *Session) HasTable() bool { 49 | sql, values := s.dialect.TableExistSQL(s.RefTable().Name) 50 | row := s.Raw(sql, values...).QueryRow() 51 | var tmp string 52 | _ = row.Scan(&tmp) 53 | return tmp == s.RefTable().Name 54 | } 55 | -------------------------------------------------------------------------------- /gee-orm/day2-reflect-schema/session/table_test.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | type User struct { 8 | Name string `geeorm:"PRIMARY KEY"` 9 | Age int 10 | } 11 | func TestSession_CreateTable(t *testing.T) { 12 | s := NewSession().Model(&User{}) 13 | _ = s.DropTable() 14 | _ = s.CreateTable() 15 | if !s.HasTable() { 16 | t.Fatal("Failed to create table User") 17 | } 18 | } 19 | 20 | func TestSession_Model(t *testing.T) { 21 | s := NewSession().Model(&User{}) 22 | table := s.RefTable() 23 | s.Model(&Session{}) 24 | if table.Name != "User" || s.RefTable().Name != "Session" { 25 | t.Fatal("Failed to change model") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /gee-orm/day3-save-query/clause/clause.go: -------------------------------------------------------------------------------- 1 | package clause 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Clause contains SQL conditions 8 | type Clause struct { 9 | sql map[Type]string 10 | sqlVars map[Type][]interface{} 11 | } 12 | 13 | // Type is the type of Clause 14 | type Type int 15 | 16 | // Support types for Clause 17 | const ( 18 | INSERT Type = iota 19 | VALUES 20 | SELECT 21 | LIMIT 22 | WHERE 23 | ORDERBY 24 | ) 25 | 26 | // Set adds a sub clause of specific type 27 | func (c *Clause) Set(name Type, vars ...interface{}) { 28 | if c.sql == nil { 29 | c.sql = make(map[Type]string) 30 | c.sqlVars = make(map[Type][]interface{}) 31 | } 32 | sql, vars := generators[name](vars...) 33 | c.sql[name] = sql 34 | c.sqlVars[name] = vars 35 | } 36 | 37 | // Build generate the final SQL and SQLVars 38 | func (c *Clause) Build(orders ...Type) (string, []interface{}) { 39 | var sqls []string 40 | var vars []interface{} 41 | for _, order := range orders { 42 | if sql, ok := c.sql[order]; ok { 43 | sqls = append(sqls, sql) 44 | vars = append(vars, c.sqlVars[order]...) 45 | } 46 | } 47 | return strings.Join(sqls, " "), vars 48 | } 49 | -------------------------------------------------------------------------------- /gee-orm/day3-save-query/clause/clause_test.go: -------------------------------------------------------------------------------- 1 | package clause 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestClause_Set(t *testing.T) { 9 | var clause Clause 10 | clause.Set(INSERT, "User", []string{"Name", "Age"}) 11 | sql := clause.sql[INSERT] 12 | vars := clause.sqlVars[INSERT] 13 | t.Log(sql, vars) 14 | if sql != "INSERT INTO User (Name,Age)" || len(vars) != 0 { 15 | t.Fatal("failed to get clause") 16 | } 17 | } 18 | 19 | func testSelect(t *testing.T) { 20 | var clause Clause 21 | clause.Set(LIMIT, 3) 22 | clause.Set(SELECT, "User", []string{"*"}) 23 | clause.Set(WHERE, "Name = ?", "Tom") 24 | clause.Set(ORDERBY, "Age ASC") 25 | sql, vars := clause.Build(SELECT, WHERE, ORDERBY, LIMIT) 26 | t.Log(sql, vars) 27 | if sql != "SELECT * FROM User WHERE Name = ? ORDER BY Age ASC LIMIT ?" { 28 | t.Fatal("failed to build SQL") 29 | } 30 | if !reflect.DeepEqual(vars, []interface{}{"Tom", 3}) { 31 | t.Fatal("failed to build SQLVars") 32 | } 33 | } 34 | 35 | func TestClause_Build(t *testing.T) { 36 | t.Run("select", func(t *testing.T) { 37 | testSelect(t) 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /gee-orm/day3-save-query/dialect/dialect.go: -------------------------------------------------------------------------------- 1 | package dialect 2 | 3 | import "reflect" 4 | 5 | var dialectsMap = map[string]Dialect{} 6 | 7 | // Dialect is an interface contains methods that a dialect has to implement 8 | type Dialect interface { 9 | DataTypeOf(typ reflect.Value) string 10 | TableExistSQL(tableName string) (string, []interface{}) 11 | } 12 | 13 | // RegisterDialect register a dialect to the global variable 14 | func RegisterDialect(name string, dialect Dialect) { 15 | dialectsMap[name] = dialect 16 | } 17 | 18 | // Get the dialect from global variable if it exists 19 | func GetDialect(name string) (dialect Dialect, ok bool) { 20 | dialect, ok = dialectsMap[name] 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /gee-orm/day3-save-query/dialect/sqlite3.go: -------------------------------------------------------------------------------- 1 | package dialect 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "time" 7 | ) 8 | 9 | type sqlite3 struct{} 10 | 11 | var _ Dialect = (*sqlite3)(nil) 12 | 13 | func init() { 14 | RegisterDialect("sqlite3", &sqlite3{}) 15 | } 16 | 17 | // Get Data Type for sqlite3 Dialect 18 | func (s *sqlite3) DataTypeOf(typ reflect.Value) string { 19 | switch typ.Kind() { 20 | case reflect.Bool: 21 | return "bool" 22 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, 23 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr: 24 | return "integer" 25 | case reflect.Int64, reflect.Uint64: 26 | return "bigint" 27 | case reflect.Float32, reflect.Float64: 28 | return "real" 29 | case reflect.String: 30 | return "text" 31 | case reflect.Array, reflect.Slice: 32 | return "blob" 33 | case reflect.Struct: 34 | if _, ok := typ.Interface().(time.Time); ok { 35 | return "datetime" 36 | } 37 | } 38 | panic(fmt.Sprintf("invalid sql type %s (%s)", typ.Type().Name(), typ.Kind())) 39 | } 40 | 41 | // TableExistSQL returns SQL that judge whether the table exists in database 42 | func (s *sqlite3) TableExistSQL(tableName string) (string, []interface{}) { 43 | args := []interface{}{tableName} 44 | return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args 45 | } 46 | -------------------------------------------------------------------------------- /gee-orm/day3-save-query/dialect/sqlite3_test.go: -------------------------------------------------------------------------------- 1 | package dialect 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestDataTypeOf(t *testing.T) { 9 | dial := &sqlite3{} 10 | cases := []struct { 11 | Value interface{} 12 | Type string 13 | }{ 14 | {"Tom", "text"}, 15 | {123, "integer"}, 16 | {1.2, "real"}, 17 | {[]int{1, 2, 3}, "blob"}, 18 | } 19 | 20 | for _, c := range cases { 21 | if typ := dial.DataTypeOf(reflect.ValueOf(c.Value)); typ != c.Type { 22 | t.Fatalf("expect %s, but got %s", c.Type, typ) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /gee-orm/day3-save-query/geeorm.go: -------------------------------------------------------------------------------- 1 | package geeorm 2 | 3 | import ( 4 | "database/sql" 5 | "geeorm/dialect" 6 | "geeorm/log" 7 | "geeorm/session" 8 | ) 9 | 10 | // Engine is the main struct of geeorm, manages all db sessions and transactions. 11 | type Engine struct { 12 | db *sql.DB 13 | dialect dialect.Dialect 14 | } 15 | 16 | // NewEngine create a instance of Engine 17 | // connect database and ping it to test whether it's alive 18 | func NewEngine(driver, source string) (e *Engine, err error) { 19 | db, err := sql.Open(driver, source) 20 | if err != nil { 21 | log.Error(err) 22 | return 23 | } 24 | // Send a ping to make sure the database connection is alive. 25 | if err = db.Ping(); err != nil { 26 | log.Error(err) 27 | return 28 | } 29 | // make sure the specific dialect exists 30 | dial, ok := dialect.GetDialect(driver) 31 | if !ok { 32 | log.Errorf("dialect %s Not Found", driver) 33 | return 34 | } 35 | e = &Engine{db: db, dialect: dial} 36 | log.Info("Connect database success") 37 | return 38 | } 39 | 40 | // Close database connection 41 | func (engine *Engine) Close() { 42 | if err := engine.db.Close(); err != nil { 43 | log.Error("Failed to close database") 44 | } 45 | log.Info("Close database success") 46 | } 47 | 48 | // NewSession creates a new session for next operations 49 | func (engine *Engine) NewSession() *session.Session { 50 | return session.New(engine.db, engine.dialect) 51 | } 52 | -------------------------------------------------------------------------------- /gee-orm/day3-save-query/geeorm_test.go: -------------------------------------------------------------------------------- 1 | package geeorm 2 | 3 | import ( 4 | _ "github.com/mattn/go-sqlite3" 5 | "testing" 6 | ) 7 | 8 | func OpenDB(t *testing.T) *Engine { 9 | t.Helper() 10 | engine, err := NewEngine("sqlite3", "gee.db") 11 | if err != nil { 12 | t.Fatal("failed to connect", err) 13 | } 14 | return engine 15 | } 16 | 17 | func TestNewEngine(t *testing.T) { 18 | engine := OpenDB(t) 19 | defer engine.Close() 20 | } 21 | -------------------------------------------------------------------------------- /gee-orm/day3-save-query/go.mod: -------------------------------------------------------------------------------- 1 | module geeorm 2 | 3 | go 1.13 4 | 5 | require github.com/mattn/go-sqlite3 v2.0.3+incompatible 6 | -------------------------------------------------------------------------------- /gee-orm/day3-save-query/log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | var ( 11 | errorLog = log.New(os.Stdout, "\033[31m[error]\033[0m ", log.LstdFlags|log.Lshortfile) 12 | infoLog = log.New(os.Stdout, "\033[34m[info ]\033[0m ", log.LstdFlags|log.Lshortfile) 13 | loggers = []*log.Logger{errorLog, infoLog} 14 | mu sync.Mutex 15 | ) 16 | 17 | // log methods 18 | var ( 19 | Error = errorLog.Println 20 | Errorf = errorLog.Printf 21 | Info = infoLog.Println 22 | Infof = infoLog.Printf 23 | ) 24 | 25 | // log levels 26 | const ( 27 | InfoLevel = iota 28 | ErrorLevel 29 | Disabled 30 | ) 31 | 32 | // SetLevel controls log level 33 | func SetLevel(level int) { 34 | mu.Lock() 35 | defer mu.Unlock() 36 | 37 | for _, logger := range loggers { 38 | logger.SetOutput(os.Stdout) 39 | } 40 | 41 | if ErrorLevel < level { 42 | errorLog.SetOutput(ioutil.Discard) 43 | } 44 | if InfoLevel < level { 45 | infoLog.SetOutput(ioutil.Discard) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /gee-orm/day3-save-query/log/log_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestSetLevel(t *testing.T) { 9 | SetLevel(ErrorLevel) 10 | if infoLog.Writer() == os.Stdout || errorLog.Writer() != os.Stdout { 11 | t.Fatal("failed to set log level") 12 | } 13 | SetLevel(Disabled) 14 | if infoLog.Writer() == os.Stdout || errorLog.Writer() == os.Stdout { 15 | t.Fatal("failed to set log level") 16 | } 17 | } -------------------------------------------------------------------------------- /gee-orm/day3-save-query/schema/schema_test.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "geeorm/dialect" 5 | "testing" 6 | ) 7 | 8 | type User struct { 9 | Name string `geeorm:"PRIMARY KEY"` 10 | Age int 11 | } 12 | 13 | var TestDial, _ = dialect.GetDialect("sqlite3") 14 | 15 | func TestParse(t *testing.T) { 16 | schema := Parse(&User{}, TestDial) 17 | if schema.Name != "User" || len(schema.Fields) != 2 { 18 | t.Fatal("failed to parse User struct") 19 | } 20 | if schema.GetField("Name").Tag != "PRIMARY KEY" { 21 | t.Fatal("failed to parse primary key") 22 | } 23 | } 24 | 25 | func TestSchema_RecordValues(t *testing.T) { 26 | schema := Parse(&User{}, TestDial) 27 | values := schema.RecordValues(&User{"Tom", 18}) 28 | 29 | name := values[0].(string) 30 | age := values[1].(int) 31 | 32 | if name != "Tom" || age != 18 { 33 | t.Fatal("failed to get values") 34 | } 35 | } 36 | 37 | type UserTest struct { 38 | Name string `geeorm:"PRIMARY KEY"` 39 | Age int 40 | } 41 | 42 | func (u *UserTest) TableName() string { 43 | return "ns_user_test" 44 | } 45 | 46 | func TestSchema_TableName(t *testing.T) { 47 | schema := Parse(&UserTest{}, TestDial) 48 | if schema.Name != "ns_user_test" || len(schema.Fields) != 2 { 49 | t.Fatal("failed to parse User struct") 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /gee-orm/day3-save-query/session/raw_test.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "database/sql" 5 | "os" 6 | "testing" 7 | 8 | "geeorm/dialect" 9 | 10 | _ "github.com/mattn/go-sqlite3" 11 | ) 12 | 13 | var ( 14 | TestDB *sql.DB 15 | TestDial, _ = dialect.GetDialect("sqlite3") 16 | ) 17 | 18 | func TestMain(m *testing.M) { 19 | TestDB, _ = sql.Open("sqlite3", "../gee.db") 20 | code := m.Run() 21 | _ = TestDB.Close() 22 | os.Exit(code) 23 | } 24 | 25 | func NewSession() *Session { 26 | return New(TestDB, TestDial) 27 | } 28 | func TestSession_Exec(t *testing.T) { 29 | s := NewSession() 30 | _, _ = s.Raw("DROP TABLE IF EXISTS User;").Exec() 31 | _, _ = s.Raw("CREATE TABLE User(Name text);").Exec() 32 | result, _ := s.Raw("INSERT INTO User(`Name`) values (?), (?)", "Tom", "Sam").Exec() 33 | if count, err := result.RowsAffected(); err != nil || count != 2 { 34 | t.Fatal("expect 2, but got", count) 35 | } 36 | } 37 | 38 | func TestSession_QueryRows(t *testing.T) { 39 | s := NewSession() 40 | _, _ = s.Raw("DROP TABLE IF EXISTS User;").Exec() 41 | _, _ = s.Raw("CREATE TABLE User(Name text);").Exec() 42 | row := s.Raw("SELECT count(*) FROM User").QueryRow() 43 | var count int 44 | if err := row.Scan(&count); err != nil || count != 0 { 45 | t.Fatal("failed to query db", err) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /gee-orm/day3-save-query/session/record.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "geeorm/clause" 5 | "reflect" 6 | ) 7 | 8 | // Insert one or more records in database 9 | func (s *Session) Insert(values ...interface{}) (int64, error) { 10 | recordValues := make([]interface{}, 0) 11 | for _, value := range values { 12 | table := s.Model(value).RefTable() 13 | s.clause.Set(clause.INSERT, table.Name, table.FieldNames) 14 | recordValues = append(recordValues, table.RecordValues(value)) 15 | } 16 | 17 | s.clause.Set(clause.VALUES, recordValues...) 18 | sql, vars := s.clause.Build(clause.INSERT, clause.VALUES) 19 | result, err := s.Raw(sql, vars...).Exec() 20 | if err != nil { 21 | return 0, err 22 | } 23 | 24 | return result.RowsAffected() 25 | } 26 | 27 | // Find gets all eligible records 28 | func (s *Session) Find(values interface{}) error { 29 | destSlice := reflect.Indirect(reflect.ValueOf(values)) 30 | destType := destSlice.Type().Elem() 31 | table := s.Model(reflect.New(destType).Elem().Interface()).RefTable() 32 | 33 | s.clause.Set(clause.SELECT, table.Name, table.FieldNames) 34 | sql, vars := s.clause.Build(clause.SELECT, clause.WHERE, clause.ORDERBY, clause.LIMIT) 35 | rows, err := s.Raw(sql, vars...).QueryRows() 36 | if err != nil { 37 | return err 38 | } 39 | 40 | for rows.Next() { 41 | dest := reflect.New(destType).Elem() 42 | var values []interface{} 43 | for _, name := range table.FieldNames { 44 | values = append(values, dest.FieldByName(name).Addr().Interface()) 45 | } 46 | if err := rows.Scan(values...); err != nil { 47 | return err 48 | } 49 | destSlice.Set(reflect.Append(destSlice, dest)) 50 | } 51 | return rows.Close() 52 | } 53 | -------------------------------------------------------------------------------- /gee-orm/day3-save-query/session/record_test.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import "testing" 4 | 5 | var ( 6 | user1 = &User{"Tom", 18} 7 | user2 = &User{"Sam", 25} 8 | user3 = &User{"Jack", 25} 9 | ) 10 | 11 | func testRecordInit(t *testing.T) *Session { 12 | t.Helper() 13 | s := NewSession().Model(&User{}) 14 | err1 := s.DropTable() 15 | err2 := s.CreateTable() 16 | _, err3 := s.Insert(user1, user2) 17 | if err1 != nil || err2 != nil || err3 != nil { 18 | t.Fatal("failed init test records") 19 | } 20 | return s 21 | } 22 | 23 | func TestSession_Insert(t *testing.T) { 24 | s := testRecordInit(t) 25 | affected, err := s.Insert(user3) 26 | if err != nil || affected != 1 { 27 | t.Fatal("failed to create record") 28 | } 29 | } 30 | 31 | func TestSession_Find(t *testing.T) { 32 | s := testRecordInit(t) 33 | var users []User 34 | if err := s.Find(&users); err != nil || len(users) != 2 { 35 | t.Fatal("failed to query all") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /gee-orm/day3-save-query/session/table.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "fmt" 5 | "geeorm/log" 6 | "reflect" 7 | "strings" 8 | 9 | "geeorm/schema" 10 | ) 11 | 12 | // Model assigns refTable 13 | func (s *Session) Model(value interface{}) *Session { 14 | // nil or different model, update refTable 15 | if s.refTable == nil || reflect.TypeOf(value) != reflect.TypeOf(s.refTable.Model) { 16 | s.refTable = schema.Parse(value, s.dialect) 17 | } 18 | return s 19 | } 20 | 21 | // RefTable returns a Schema instance that contains all parsed fields 22 | func (s *Session) RefTable() *schema.Schema { 23 | if s.refTable == nil { 24 | log.Error("Model is not set") 25 | } 26 | return s.refTable 27 | } 28 | 29 | // CreateTable create a table in database with a model 30 | func (s *Session) CreateTable() error { 31 | table := s.RefTable() 32 | var columns []string 33 | for _, field := range table.Fields { 34 | columns = append(columns, fmt.Sprintf("%s %s %s", field.Name, field.Type, field.Tag)) 35 | } 36 | desc := strings.Join(columns, ",") 37 | _, err := s.Raw(fmt.Sprintf("CREATE TABLE %s (%s);", table.Name, desc)).Exec() 38 | return err 39 | } 40 | 41 | // DropTable drops a table with the name of model 42 | func (s *Session) DropTable() error { 43 | _, err := s.Raw(fmt.Sprintf("DROP TABLE IF EXISTS %s", s.RefTable().Name)).Exec() 44 | return err 45 | } 46 | 47 | // HasTable returns true of the table exists 48 | func (s *Session) HasTable() bool { 49 | sql, values := s.dialect.TableExistSQL(s.RefTable().Name) 50 | row := s.Raw(sql, values...).QueryRow() 51 | var tmp string 52 | _ = row.Scan(&tmp) 53 | return tmp == s.RefTable().Name 54 | } 55 | -------------------------------------------------------------------------------- /gee-orm/day3-save-query/session/table_test.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | type User struct { 8 | Name string `geeorm:"PRIMARY KEY"` 9 | Age int 10 | } 11 | 12 | func TestSession_CreateTable(t *testing.T) { 13 | s := NewSession().Model(&User{}) 14 | _ = s.DropTable() 15 | _ = s.CreateTable() 16 | if !s.HasTable() { 17 | t.Fatal("Failed to create table User") 18 | } 19 | } 20 | 21 | func TestSession_Model(t *testing.T) { 22 | s := NewSession().Model(&User{}) 23 | table := s.RefTable() 24 | s.Model(&Session{}) 25 | if table.Name != "User" || s.RefTable().Name != "Session" { 26 | t.Fatal("Failed to change model") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gee-orm/day4-chain-operation/clause/clause.go: -------------------------------------------------------------------------------- 1 | package clause 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Clause contains SQL conditions 8 | type Clause struct { 9 | sql map[Type]string 10 | sqlVars map[Type][]interface{} 11 | } 12 | 13 | // Type is the type of Clause 14 | type Type int 15 | 16 | // Support types for Clause 17 | const ( 18 | INSERT Type = iota 19 | VALUES 20 | SELECT 21 | LIMIT 22 | WHERE 23 | ORDERBY 24 | UPDATE 25 | DELETE 26 | COUNT 27 | ) 28 | 29 | // Set adds a sub clause of specific type 30 | func (c *Clause) Set(name Type, vars ...interface{}) { 31 | if c.sql == nil { 32 | c.sql = make(map[Type]string) 33 | c.sqlVars = make(map[Type][]interface{}) 34 | } 35 | sql, vars := generators[name](vars...) 36 | c.sql[name] = sql 37 | c.sqlVars[name] = vars 38 | } 39 | 40 | // Build generate the final SQL and SQLVars 41 | func (c *Clause) Build(orders ...Type) (string, []interface{}) { 42 | var sqls []string 43 | var vars []interface{} 44 | for _, order := range orders { 45 | if sql, ok := c.sql[order]; ok { 46 | sqls = append(sqls, sql) 47 | vars = append(vars, c.sqlVars[order]...) 48 | } 49 | } 50 | return strings.Join(sqls, " "), vars 51 | } 52 | -------------------------------------------------------------------------------- /gee-orm/day4-chain-operation/dialect/dialect.go: -------------------------------------------------------------------------------- 1 | package dialect 2 | 3 | import "reflect" 4 | 5 | var dialectsMap = map[string]Dialect{} 6 | 7 | // Dialect is an interface contains methods that a dialect has to implement 8 | type Dialect interface { 9 | DataTypeOf(typ reflect.Value) string 10 | TableExistSQL(tableName string) (string, []interface{}) 11 | } 12 | 13 | // RegisterDialect register a dialect to the global variable 14 | func RegisterDialect(name string, dialect Dialect) { 15 | dialectsMap[name] = dialect 16 | } 17 | 18 | // Get the dialect from global variable if it exists 19 | func GetDialect(name string) (dialect Dialect, ok bool) { 20 | dialect, ok = dialectsMap[name] 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /gee-orm/day4-chain-operation/dialect/sqlite3.go: -------------------------------------------------------------------------------- 1 | package dialect 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "time" 7 | ) 8 | 9 | type sqlite3 struct{} 10 | 11 | var _ Dialect = (*sqlite3)(nil) 12 | 13 | func init() { 14 | RegisterDialect("sqlite3", &sqlite3{}) 15 | } 16 | 17 | // Get Data Type for sqlite3 Dialect 18 | func (s *sqlite3) DataTypeOf(typ reflect.Value) string { 19 | switch typ.Kind() { 20 | case reflect.Bool: 21 | return "bool" 22 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, 23 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr: 24 | return "integer" 25 | case reflect.Int64, reflect.Uint64: 26 | return "bigint" 27 | case reflect.Float32, reflect.Float64: 28 | return "real" 29 | case reflect.String: 30 | return "text" 31 | case reflect.Array, reflect.Slice: 32 | return "blob" 33 | case reflect.Struct: 34 | if _, ok := typ.Interface().(time.Time); ok { 35 | return "datetime" 36 | } 37 | } 38 | panic(fmt.Sprintf("invalid sql type %s (%s)", typ.Type().Name(), typ.Kind())) 39 | } 40 | 41 | // TableExistSQL returns SQL that judge whether the table exists in database 42 | func (s *sqlite3) TableExistSQL(tableName string) (string, []interface{}) { 43 | args := []interface{}{tableName} 44 | return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args 45 | } 46 | -------------------------------------------------------------------------------- /gee-orm/day4-chain-operation/dialect/sqlite3_test.go: -------------------------------------------------------------------------------- 1 | package dialect 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestDataTypeOf(t *testing.T) { 9 | dial := &sqlite3{} 10 | cases := []struct { 11 | Value interface{} 12 | Type string 13 | }{ 14 | {"Tom", "text"}, 15 | {123, "integer"}, 16 | {1.2, "real"}, 17 | {[]int{1, 2, 3}, "blob"}, 18 | } 19 | 20 | for _, c := range cases { 21 | if typ := dial.DataTypeOf(reflect.ValueOf(c.Value)); typ != c.Type { 22 | t.Fatalf("expect %s, but got %s", c.Type, typ) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /gee-orm/day4-chain-operation/geeorm.go: -------------------------------------------------------------------------------- 1 | package geeorm 2 | 3 | import ( 4 | "database/sql" 5 | "geeorm/dialect" 6 | "geeorm/log" 7 | "geeorm/session" 8 | ) 9 | 10 | // Engine is the main struct of geeorm, manages all db sessions and transactions. 11 | type Engine struct { 12 | db *sql.DB 13 | dialect dialect.Dialect 14 | } 15 | 16 | // NewEngine create a instance of Engine 17 | // connect database and ping it to test whether it's alive 18 | func NewEngine(driver, source string) (e *Engine, err error) { 19 | db, err := sql.Open(driver, source) 20 | if err != nil { 21 | log.Error(err) 22 | return 23 | } 24 | // Send a ping to make sure the database connection is alive. 25 | if err = db.Ping(); err != nil { 26 | log.Error(err) 27 | return 28 | } 29 | // make sure the specific dialect exists 30 | dial, ok := dialect.GetDialect(driver) 31 | if !ok { 32 | log.Errorf("dialect %s Not Found", driver) 33 | return 34 | } 35 | e = &Engine{db: db, dialect: dial} 36 | log.Info("Connect database success") 37 | return 38 | } 39 | 40 | // Close database connection 41 | func (engine *Engine) Close() { 42 | if err := engine.db.Close(); err != nil { 43 | log.Error("Failed to close database") 44 | } 45 | log.Info("Close database success") 46 | } 47 | 48 | // NewSession creates a new session for next operations 49 | func (engine *Engine) NewSession() *session.Session { 50 | return session.New(engine.db, engine.dialect) 51 | } 52 | -------------------------------------------------------------------------------- /gee-orm/day4-chain-operation/geeorm_test.go: -------------------------------------------------------------------------------- 1 | package geeorm 2 | 3 | import ( 4 | _ "github.com/mattn/go-sqlite3" 5 | "testing" 6 | ) 7 | 8 | func OpenDB(t *testing.T) *Engine { 9 | t.Helper() 10 | engine, err := NewEngine("sqlite3", "gee.db") 11 | if err != nil { 12 | t.Fatal("failed to connect", err) 13 | } 14 | return engine 15 | } 16 | 17 | func TestNewEngine(t *testing.T) { 18 | engine := OpenDB(t) 19 | defer engine.Close() 20 | } 21 | -------------------------------------------------------------------------------- /gee-orm/day4-chain-operation/go.mod: -------------------------------------------------------------------------------- 1 | module geeorm 2 | 3 | go 1.13 4 | 5 | require github.com/mattn/go-sqlite3 v2.0.3+incompatible 6 | -------------------------------------------------------------------------------- /gee-orm/day4-chain-operation/log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | var ( 11 | errorLog = log.New(os.Stdout, "\033[31m[error]\033[0m ", log.LstdFlags|log.Lshortfile) 12 | infoLog = log.New(os.Stdout, "\033[34m[info ]\033[0m ", log.LstdFlags|log.Lshortfile) 13 | loggers = []*log.Logger{errorLog, infoLog} 14 | mu sync.Mutex 15 | ) 16 | 17 | // log methods 18 | var ( 19 | Error = errorLog.Println 20 | Errorf = errorLog.Printf 21 | Info = infoLog.Println 22 | Infof = infoLog.Printf 23 | ) 24 | 25 | // log levels 26 | const ( 27 | InfoLevel = iota 28 | ErrorLevel 29 | Disabled 30 | ) 31 | 32 | // SetLevel controls log level 33 | func SetLevel(level int) { 34 | mu.Lock() 35 | defer mu.Unlock() 36 | 37 | for _, logger := range loggers { 38 | logger.SetOutput(os.Stdout) 39 | } 40 | 41 | if ErrorLevel < level { 42 | errorLog.SetOutput(ioutil.Discard) 43 | } 44 | if InfoLevel < level { 45 | infoLog.SetOutput(ioutil.Discard) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /gee-orm/day4-chain-operation/log/log_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestSetLevel(t *testing.T) { 9 | SetLevel(ErrorLevel) 10 | if infoLog.Writer() == os.Stdout || errorLog.Writer() != os.Stdout { 11 | t.Fatal("failed to set log level") 12 | } 13 | SetLevel(Disabled) 14 | if infoLog.Writer() == os.Stdout || errorLog.Writer() == os.Stdout { 15 | t.Fatal("failed to set log level") 16 | } 17 | } -------------------------------------------------------------------------------- /gee-orm/day4-chain-operation/schema/schema_test.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "geeorm/dialect" 5 | "testing" 6 | ) 7 | 8 | type User struct { 9 | Name string `geeorm:"PRIMARY KEY"` 10 | Age int 11 | } 12 | 13 | var TestDial, _ = dialect.GetDialect("sqlite3") 14 | 15 | func TestParse(t *testing.T) { 16 | schema := Parse(&User{}, TestDial) 17 | if schema.Name != "User" || len(schema.Fields) != 2 { 18 | t.Fatal("failed to parse User struct") 19 | } 20 | if schema.GetField("Name").Tag != "PRIMARY KEY" { 21 | t.Fatal("failed to parse primary key") 22 | } 23 | } 24 | 25 | func TestSchema_RecordValues(t *testing.T) { 26 | schema := Parse(&User{}, TestDial) 27 | values := schema.RecordValues(&User{"Tom", 18}) 28 | 29 | name := values[0].(string) 30 | age := values[1].(int) 31 | 32 | if name != "Tom" || age != 18 { 33 | t.Fatal("failed to get values") 34 | } 35 | } 36 | 37 | type UserTest struct { 38 | Name string `geeorm:"PRIMARY KEY"` 39 | Age int 40 | } 41 | 42 | func (u *UserTest) TableName() string { 43 | return "ns_user_test" 44 | } 45 | 46 | func TestSchema_TableName(t *testing.T) { 47 | schema := Parse(&UserTest{}, TestDial) 48 | if schema.Name != "ns_user_test" || len(schema.Fields) != 2 { 49 | t.Fatal("failed to parse User struct") 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /gee-orm/day4-chain-operation/session/raw_test.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "database/sql" 5 | "os" 6 | "testing" 7 | 8 | "geeorm/dialect" 9 | 10 | _ "github.com/mattn/go-sqlite3" 11 | ) 12 | 13 | var ( 14 | TestDB *sql.DB 15 | TestDial, _ = dialect.GetDialect("sqlite3") 16 | ) 17 | 18 | func TestMain(m *testing.M) { 19 | TestDB, _ = sql.Open("sqlite3", "../gee.db") 20 | code := m.Run() 21 | _ = TestDB.Close() 22 | os.Exit(code) 23 | } 24 | 25 | func NewSession() *Session { 26 | return New(TestDB, TestDial) 27 | } 28 | 29 | func TestSession_Exec(t *testing.T) { 30 | s := NewSession() 31 | _, _ = s.Raw("DROP TABLE IF EXISTS User;").Exec() 32 | _, _ = s.Raw("CREATE TABLE User(Name text);").Exec() 33 | result, _ := s.Raw("INSERT INTO User(`Name`) values (?), (?)", "Tom", "Sam").Exec() 34 | if count, err := result.RowsAffected(); err != nil || count != 2 { 35 | t.Fatal("expect 2, but got", count) 36 | } 37 | } 38 | 39 | func TestSession_QueryRows(t *testing.T) { 40 | s := NewSession() 41 | _, _ = s.Raw("DROP TABLE IF EXISTS User;").Exec() 42 | _, _ = s.Raw("CREATE TABLE User(Name text);").Exec() 43 | row := s.Raw("SELECT count(*) FROM User").QueryRow() 44 | var count int 45 | if err := row.Scan(&count); err != nil || count != 0 { 46 | t.Fatal("failed to query db", err) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /gee-orm/day4-chain-operation/session/table.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "fmt" 5 | "geeorm/log" 6 | "reflect" 7 | "strings" 8 | 9 | "geeorm/schema" 10 | ) 11 | 12 | // Model assigns refTable 13 | func (s *Session) Model(value interface{}) *Session { 14 | // nil or different model, update refTable 15 | if s.refTable == nil || reflect.TypeOf(value) != reflect.TypeOf(s.refTable.Model) { 16 | s.refTable = schema.Parse(value, s.dialect) 17 | } 18 | return s 19 | } 20 | 21 | // RefTable returns a Schema instance that contains all parsed fields 22 | func (s *Session) RefTable() *schema.Schema { 23 | if s.refTable == nil { 24 | log.Error("Model is not set") 25 | } 26 | return s.refTable 27 | } 28 | 29 | // CreateTable create a table in database with a model 30 | func (s *Session) CreateTable() error { 31 | table := s.RefTable() 32 | var columns []string 33 | for _, field := range table.Fields { 34 | columns = append(columns, fmt.Sprintf("%s %s %s", field.Name, field.Type, field.Tag)) 35 | } 36 | desc := strings.Join(columns, ",") 37 | _, err := s.Raw(fmt.Sprintf("CREATE TABLE %s (%s);", table.Name, desc)).Exec() 38 | return err 39 | } 40 | 41 | // DropTable drops a table with the name of model 42 | func (s *Session) DropTable() error { 43 | _, err := s.Raw(fmt.Sprintf("DROP TABLE IF EXISTS %s", s.RefTable().Name)).Exec() 44 | return err 45 | } 46 | 47 | // HasTable returns true of the table exists 48 | func (s *Session) HasTable() bool { 49 | sql, values := s.dialect.TableExistSQL(s.RefTable().Name) 50 | row := s.Raw(sql, values...).QueryRow() 51 | var tmp string 52 | _ = row.Scan(&tmp) 53 | return tmp == s.RefTable().Name 54 | } 55 | -------------------------------------------------------------------------------- /gee-orm/day4-chain-operation/session/table_test.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | type User struct { 8 | Name string `geeorm:"PRIMARY KEY"` 9 | Age int 10 | } 11 | 12 | func TestSession_CreateTable(t *testing.T) { 13 | s := NewSession().Model(&User{}) 14 | _ = s.DropTable() 15 | _ = s.CreateTable() 16 | if !s.HasTable() { 17 | t.Fatal("Failed to create table User") 18 | } 19 | } 20 | 21 | func TestSession_Model(t *testing.T) { 22 | s := NewSession().Model(&User{}) 23 | table := s.RefTable() 24 | s.Model(&Session{}) 25 | if table.Name != "User" || s.RefTable().Name != "Session" { 26 | t.Fatal("Failed to change model") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gee-orm/day5-hooks/clause/clause.go: -------------------------------------------------------------------------------- 1 | package clause 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Clause contains SQL conditions 8 | type Clause struct { 9 | sql map[Type]string 10 | sqlVars map[Type][]interface{} 11 | } 12 | 13 | // Type is the type of Clause 14 | type Type int 15 | 16 | // Support types for Clause 17 | const ( 18 | INSERT Type = iota 19 | VALUES 20 | SELECT 21 | LIMIT 22 | WHERE 23 | ORDERBY 24 | UPDATE 25 | DELETE 26 | COUNT 27 | ) 28 | 29 | // Set adds a sub clause of specific type 30 | func (c *Clause) Set(name Type, vars ...interface{}) { 31 | if c.sql == nil { 32 | c.sql = make(map[Type]string) 33 | c.sqlVars = make(map[Type][]interface{}) 34 | } 35 | sql, vars := generators[name](vars...) 36 | c.sql[name] = sql 37 | c.sqlVars[name] = vars 38 | } 39 | 40 | // Build generate the final SQL and SQLVars 41 | func (c *Clause) Build(orders ...Type) (string, []interface{}) { 42 | var sqls []string 43 | var vars []interface{} 44 | for _, order := range orders { 45 | if sql, ok := c.sql[order]; ok { 46 | sqls = append(sqls, sql) 47 | vars = append(vars, c.sqlVars[order]...) 48 | } 49 | } 50 | return strings.Join(sqls, " "), vars 51 | } 52 | -------------------------------------------------------------------------------- /gee-orm/day5-hooks/dialect/dialect.go: -------------------------------------------------------------------------------- 1 | package dialect 2 | 3 | import "reflect" 4 | 5 | var dialectsMap = map[string]Dialect{} 6 | 7 | // Dialect is an interface contains methods that a dialect has to implement 8 | type Dialect interface { 9 | DataTypeOf(typ reflect.Value) string 10 | TableExistSQL(tableName string) (string, []interface{}) 11 | } 12 | 13 | // RegisterDialect register a dialect to the global variable 14 | func RegisterDialect(name string, dialect Dialect) { 15 | dialectsMap[name] = dialect 16 | } 17 | 18 | // Get the dialect from global variable if it exists 19 | func GetDialect(name string) (dialect Dialect, ok bool) { 20 | dialect, ok = dialectsMap[name] 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /gee-orm/day5-hooks/dialect/sqlite3.go: -------------------------------------------------------------------------------- 1 | package dialect 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "time" 7 | ) 8 | 9 | type sqlite3 struct{} 10 | 11 | var _ Dialect = (*sqlite3)(nil) 12 | 13 | func init() { 14 | RegisterDialect("sqlite3", &sqlite3{}) 15 | } 16 | 17 | // Get Data Type for sqlite3 Dialect 18 | func (s *sqlite3) DataTypeOf(typ reflect.Value) string { 19 | switch typ.Kind() { 20 | case reflect.Bool: 21 | return "bool" 22 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, 23 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr: 24 | return "integer" 25 | case reflect.Int64, reflect.Uint64: 26 | return "bigint" 27 | case reflect.Float32, reflect.Float64: 28 | return "real" 29 | case reflect.String: 30 | return "text" 31 | case reflect.Array, reflect.Slice: 32 | return "blob" 33 | case reflect.Struct: 34 | if _, ok := typ.Interface().(time.Time); ok { 35 | return "datetime" 36 | } 37 | } 38 | panic(fmt.Sprintf("invalid sql type %s (%s)", typ.Type().Name(), typ.Kind())) 39 | } 40 | 41 | // TableExistSQL returns SQL that judge whether the table exists in database 42 | func (s *sqlite3) TableExistSQL(tableName string) (string, []interface{}) { 43 | args := []interface{}{tableName} 44 | return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args 45 | } 46 | -------------------------------------------------------------------------------- /gee-orm/day5-hooks/dialect/sqlite3_test.go: -------------------------------------------------------------------------------- 1 | package dialect 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestDataTypeOf(t *testing.T) { 9 | dial := &sqlite3{} 10 | cases := []struct { 11 | Value interface{} 12 | Type string 13 | }{ 14 | {"Tom", "text"}, 15 | {123, "integer"}, 16 | {1.2, "real"}, 17 | {[]int{1, 2, 3}, "blob"}, 18 | } 19 | 20 | for _, c := range cases { 21 | if typ := dial.DataTypeOf(reflect.ValueOf(c.Value)); typ != c.Type { 22 | t.Fatalf("expect %s, but got %s", c.Type, typ) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /gee-orm/day5-hooks/geeorm.go: -------------------------------------------------------------------------------- 1 | package geeorm 2 | 3 | import ( 4 | "database/sql" 5 | "geeorm/dialect" 6 | "geeorm/log" 7 | "geeorm/session" 8 | ) 9 | 10 | // Engine is the main struct of geeorm, manages all db sessions and transactions. 11 | type Engine struct { 12 | db *sql.DB 13 | dialect dialect.Dialect 14 | } 15 | 16 | // NewEngine create a instance of Engine 17 | // connect database and ping it to test whether it's alive 18 | func NewEngine(driver, source string) (e *Engine, err error) { 19 | db, err := sql.Open(driver, source) 20 | if err != nil { 21 | log.Error(err) 22 | return 23 | } 24 | // Send a ping to make sure the database connection is alive. 25 | if err = db.Ping(); err != nil { 26 | log.Error(err) 27 | return 28 | } 29 | // make sure the specific dialect exists 30 | dial, ok := dialect.GetDialect(driver) 31 | if !ok { 32 | log.Errorf("dialect %s Not Found", driver) 33 | return 34 | } 35 | e = &Engine{db: db, dialect: dial} 36 | log.Info("Connect database success") 37 | return 38 | } 39 | 40 | // Close database connection 41 | func (engine *Engine) Close() { 42 | if err := engine.db.Close(); err != nil { 43 | log.Error("Failed to close database") 44 | } 45 | log.Info("Close database success") 46 | } 47 | 48 | // NewSession creates a new session for next operations 49 | func (engine *Engine) NewSession() *session.Session { 50 | return session.New(engine.db, engine.dialect) 51 | } 52 | -------------------------------------------------------------------------------- /gee-orm/day5-hooks/geeorm_test.go: -------------------------------------------------------------------------------- 1 | package geeorm 2 | 3 | import ( 4 | _ "github.com/mattn/go-sqlite3" 5 | "testing" 6 | ) 7 | 8 | func OpenDB(t *testing.T) *Engine { 9 | t.Helper() 10 | engine, err := NewEngine("sqlite3", "gee.db") 11 | if err != nil { 12 | t.Fatal("failed to connect", err) 13 | } 14 | return engine 15 | } 16 | 17 | func TestNewEngine(t *testing.T) { 18 | engine := OpenDB(t) 19 | defer engine.Close() 20 | } 21 | -------------------------------------------------------------------------------- /gee-orm/day5-hooks/go.mod: -------------------------------------------------------------------------------- 1 | module geeorm 2 | 3 | go 1.13 4 | 5 | require github.com/mattn/go-sqlite3 v2.0.3+incompatible 6 | -------------------------------------------------------------------------------- /gee-orm/day5-hooks/log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | var ( 11 | errorLog = log.New(os.Stdout, "\033[31m[error]\033[0m ", log.LstdFlags|log.Lshortfile) 12 | infoLog = log.New(os.Stdout, "\033[34m[info ]\033[0m ", log.LstdFlags|log.Lshortfile) 13 | loggers = []*log.Logger{errorLog, infoLog} 14 | mu sync.Mutex 15 | ) 16 | 17 | // log methods 18 | var ( 19 | Error = errorLog.Println 20 | Errorf = errorLog.Printf 21 | Info = infoLog.Println 22 | Infof = infoLog.Printf 23 | ) 24 | 25 | // log levels 26 | const ( 27 | InfoLevel = iota 28 | ErrorLevel 29 | Disabled 30 | ) 31 | 32 | // SetLevel controls log level 33 | func SetLevel(level int) { 34 | mu.Lock() 35 | defer mu.Unlock() 36 | 37 | for _, logger := range loggers { 38 | logger.SetOutput(os.Stdout) 39 | } 40 | 41 | if ErrorLevel < level { 42 | errorLog.SetOutput(ioutil.Discard) 43 | } 44 | if InfoLevel < level { 45 | infoLog.SetOutput(ioutil.Discard) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /gee-orm/day5-hooks/log/log_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestSetLevel(t *testing.T) { 9 | SetLevel(ErrorLevel) 10 | if infoLog.Writer() == os.Stdout || errorLog.Writer() != os.Stdout { 11 | t.Fatal("failed to set log level") 12 | } 13 | SetLevel(Disabled) 14 | if infoLog.Writer() == os.Stdout || errorLog.Writer() == os.Stdout { 15 | t.Fatal("failed to set log level") 16 | } 17 | } -------------------------------------------------------------------------------- /gee-orm/day5-hooks/schema/schema_test.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "geeorm/dialect" 5 | "testing" 6 | ) 7 | 8 | type User struct { 9 | Name string `geeorm:"PRIMARY KEY"` 10 | Age int 11 | } 12 | 13 | var TestDial, _ = dialect.GetDialect("sqlite3") 14 | 15 | func TestParse(t *testing.T) { 16 | schema := Parse(&User{}, TestDial) 17 | if schema.Name != "User" || len(schema.Fields) != 2 { 18 | t.Fatal("failed to parse User struct") 19 | } 20 | if schema.GetField("Name").Tag != "PRIMARY KEY" { 21 | t.Fatal("failed to parse primary key") 22 | } 23 | } 24 | 25 | func TestSchema_RecordValues(t *testing.T) { 26 | schema := Parse(&User{}, TestDial) 27 | values := schema.RecordValues(&User{"Tom", 18}) 28 | 29 | name := values[0].(string) 30 | age := values[1].(int) 31 | 32 | if name != "Tom" || age != 18 { 33 | t.Fatal("failed to get values") 34 | } 35 | } 36 | 37 | type UserTest struct { 38 | Name string `geeorm:"PRIMARY KEY"` 39 | Age int 40 | } 41 | 42 | func (u *UserTest) TableName() string { 43 | return "ns_user_test" 44 | } 45 | 46 | func TestSchema_TableName(t *testing.T) { 47 | schema := Parse(&UserTest{}, TestDial) 48 | if schema.Name != "ns_user_test" || len(schema.Fields) != 2 { 49 | t.Fatal("failed to parse User struct") 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /gee-orm/day5-hooks/session/hooks.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "geeorm/log" 5 | "reflect" 6 | ) 7 | 8 | // Hooks constants 9 | const ( 10 | BeforeQuery = "BeforeQuery" 11 | AfterQuery = "AfterQuery" 12 | BeforeUpdate = "BeforeUpdate" 13 | AfterUpdate = "AfterUpdate" 14 | BeforeDelete = "BeforeDelete" 15 | AfterDelete = "AfterDelete" 16 | BeforeInsert = "BeforeInsert" 17 | AfterInsert = "AfterInsert" 18 | ) 19 | 20 | // CallMethod calls the registered hooks 21 | func (s *Session) CallMethod(method string, value interface{}) { 22 | fm := reflect.ValueOf(s.RefTable().Model).MethodByName(method) 23 | if value != nil { 24 | fm = reflect.ValueOf(value).MethodByName(method) 25 | } 26 | param := []reflect.Value{reflect.ValueOf(s)} 27 | if fm.IsValid() { 28 | if v := fm.Call(param); len(v) > 0 { 29 | if err, ok := v[0].Interface().(error); ok { 30 | log.Error(err) 31 | } 32 | } 33 | } 34 | return 35 | } 36 | -------------------------------------------------------------------------------- /gee-orm/day5-hooks/session/hooks_test.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "geeorm/log" 5 | "testing" 6 | ) 7 | 8 | type Account struct { 9 | ID int `geeorm:"PRIMARY KEY"` 10 | Password string 11 | } 12 | 13 | func (account *Account) BeforeInsert(s *Session) error { 14 | log.Info("before inert", account) 15 | account.ID += 1000 16 | return nil 17 | } 18 | 19 | func (account *Account) AfterQuery(s *Session) error { 20 | log.Info("after query", account) 21 | account.Password = "******" 22 | return nil 23 | } 24 | 25 | func TestSession_CallMethod(t *testing.T) { 26 | s := NewSession().Model(&Account{}) 27 | _ = s.DropTable() 28 | _ = s.CreateTable() 29 | _, _ = s.Insert(&Account{1, "123456"}, &Account{2, "qwerty"}) 30 | 31 | u := &Account{} 32 | 33 | err := s.First(u) 34 | if err != nil || u.ID != 1001 || u.Password != "******" { 35 | t.Fatal("Failed to call hooks after query, got", u) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /gee-orm/day5-hooks/session/raw_test.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "database/sql" 5 | "os" 6 | "testing" 7 | 8 | "geeorm/dialect" 9 | 10 | _ "github.com/mattn/go-sqlite3" 11 | ) 12 | 13 | var ( 14 | TestDB *sql.DB 15 | TestDial, _ = dialect.GetDialect("sqlite3") 16 | ) 17 | 18 | func TestMain(m *testing.M) { 19 | TestDB, _ = sql.Open("sqlite3", "../gee.db") 20 | code := m.Run() 21 | _ = TestDB.Close() 22 | os.Exit(code) 23 | } 24 | 25 | func NewSession() *Session { 26 | return New(TestDB, TestDial) 27 | } 28 | 29 | func TestSession_Exec(t *testing.T) { 30 | s := NewSession() 31 | _, _ = s.Raw("DROP TABLE IF EXISTS User;").Exec() 32 | _, _ = s.Raw("CREATE TABLE User(Name text);").Exec() 33 | result, _ := s.Raw("INSERT INTO User(`Name`) values (?), (?)", "Tom", "Sam").Exec() 34 | if count, err := result.RowsAffected(); err != nil || count != 2 { 35 | t.Fatal("expect 2, but got", count) 36 | } 37 | } 38 | 39 | func TestSession_QueryRows(t *testing.T) { 40 | s := NewSession() 41 | _, _ = s.Raw("DROP TABLE IF EXISTS User;").Exec() 42 | _, _ = s.Raw("CREATE TABLE User(Name text);").Exec() 43 | row := s.Raw("SELECT count(*) FROM User").QueryRow() 44 | var count int 45 | if err := row.Scan(&count); err != nil || count != 0 { 46 | t.Fatal("failed to query db", err) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /gee-orm/day5-hooks/session/table.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "fmt" 5 | "geeorm/log" 6 | "reflect" 7 | "strings" 8 | 9 | "geeorm/schema" 10 | ) 11 | 12 | // Model assigns refTable 13 | func (s *Session) Model(value interface{}) *Session { 14 | // nil or different model, update refTable 15 | if s.refTable == nil || reflect.TypeOf(value) != reflect.TypeOf(s.refTable.Model) { 16 | s.refTable = schema.Parse(value, s.dialect) 17 | } 18 | return s 19 | } 20 | 21 | // RefTable returns a Schema instance that contains all parsed fields 22 | func (s *Session) RefTable() *schema.Schema { 23 | if s.refTable == nil { 24 | log.Error("Model is not set") 25 | } 26 | return s.refTable 27 | } 28 | 29 | // CreateTable create a table in database with a model 30 | func (s *Session) CreateTable() error { 31 | table := s.RefTable() 32 | var columns []string 33 | for _, field := range table.Fields { 34 | columns = append(columns, fmt.Sprintf("%s %s %s", field.Name, field.Type, field.Tag)) 35 | } 36 | desc := strings.Join(columns, ",") 37 | _, err := s.Raw(fmt.Sprintf("CREATE TABLE %s (%s);", table.Name, desc)).Exec() 38 | return err 39 | } 40 | 41 | // DropTable drops a table with the name of model 42 | func (s *Session) DropTable() error { 43 | _, err := s.Raw(fmt.Sprintf("DROP TABLE IF EXISTS %s", s.RefTable().Name)).Exec() 44 | return err 45 | } 46 | 47 | // HasTable returns true of the table exists 48 | func (s *Session) HasTable() bool { 49 | sql, values := s.dialect.TableExistSQL(s.RefTable().Name) 50 | row := s.Raw(sql, values...).QueryRow() 51 | var tmp string 52 | _ = row.Scan(&tmp) 53 | return tmp == s.RefTable().Name 54 | } 55 | -------------------------------------------------------------------------------- /gee-orm/day5-hooks/session/table_test.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | type User struct { 8 | Name string `geeorm:"PRIMARY KEY"` 9 | Age int 10 | } 11 | 12 | func TestSession_CreateTable(t *testing.T) { 13 | s := NewSession().Model(&User{}) 14 | _ = s.DropTable() 15 | _ = s.CreateTable() 16 | if !s.HasTable() { 17 | t.Fatal("Failed to create table User") 18 | } 19 | } 20 | 21 | func TestSession_Model(t *testing.T) { 22 | s := NewSession().Model(&User{}) 23 | table := s.RefTable() 24 | s.Model(&Session{}) 25 | if table.Name != "User" || s.RefTable().Name != "Session" { 26 | t.Fatal("Failed to change model") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gee-orm/day6-transaction/clause/clause.go: -------------------------------------------------------------------------------- 1 | package clause 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Clause contains SQL conditions 8 | type Clause struct { 9 | sql map[Type]string 10 | sqlVars map[Type][]interface{} 11 | } 12 | 13 | // Type is the type of Clause 14 | type Type int 15 | 16 | // Support types for Clause 17 | const ( 18 | INSERT Type = iota 19 | VALUES 20 | SELECT 21 | LIMIT 22 | WHERE 23 | ORDERBY 24 | UPDATE 25 | DELETE 26 | COUNT 27 | ) 28 | 29 | // Set adds a sub clause of specific type 30 | func (c *Clause) Set(name Type, vars ...interface{}) { 31 | if c.sql == nil { 32 | c.sql = make(map[Type]string) 33 | c.sqlVars = make(map[Type][]interface{}) 34 | } 35 | sql, vars := generators[name](vars...) 36 | c.sql[name] = sql 37 | c.sqlVars[name] = vars 38 | } 39 | 40 | // Build generate the final SQL and SQLVars 41 | func (c *Clause) Build(orders ...Type) (string, []interface{}) { 42 | var sqls []string 43 | var vars []interface{} 44 | for _, order := range orders { 45 | if sql, ok := c.sql[order]; ok { 46 | sqls = append(sqls, sql) 47 | vars = append(vars, c.sqlVars[order]...) 48 | } 49 | } 50 | return strings.Join(sqls, " "), vars 51 | } 52 | -------------------------------------------------------------------------------- /gee-orm/day6-transaction/dialect/dialect.go: -------------------------------------------------------------------------------- 1 | package dialect 2 | 3 | import "reflect" 4 | 5 | var dialectsMap = map[string]Dialect{} 6 | 7 | // Dialect is an interface contains methods that a dialect has to implement 8 | type Dialect interface { 9 | DataTypeOf(typ reflect.Value) string 10 | TableExistSQL(tableName string) (string, []interface{}) 11 | } 12 | 13 | // RegisterDialect register a dialect to the global variable 14 | func RegisterDialect(name string, dialect Dialect) { 15 | dialectsMap[name] = dialect 16 | } 17 | 18 | // Get the dialect from global variable if it exists 19 | func GetDialect(name string) (dialect Dialect, ok bool) { 20 | dialect, ok = dialectsMap[name] 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /gee-orm/day6-transaction/dialect/sqlite3.go: -------------------------------------------------------------------------------- 1 | package dialect 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "time" 7 | ) 8 | 9 | type sqlite3 struct{} 10 | 11 | var _ Dialect = (*sqlite3)(nil) 12 | 13 | func init() { 14 | RegisterDialect("sqlite3", &sqlite3{}) 15 | } 16 | 17 | // Get Data Type for sqlite3 Dialect 18 | func (s *sqlite3) DataTypeOf(typ reflect.Value) string { 19 | switch typ.Kind() { 20 | case reflect.Bool: 21 | return "bool" 22 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, 23 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr: 24 | return "integer" 25 | case reflect.Int64, reflect.Uint64: 26 | return "bigint" 27 | case reflect.Float32, reflect.Float64: 28 | return "real" 29 | case reflect.String: 30 | return "text" 31 | case reflect.Array, reflect.Slice: 32 | return "blob" 33 | case reflect.Struct: 34 | if _, ok := typ.Interface().(time.Time); ok { 35 | return "datetime" 36 | } 37 | } 38 | panic(fmt.Sprintf("invalid sql type %s (%s)", typ.Type().Name(), typ.Kind())) 39 | } 40 | 41 | // TableExistSQL returns SQL that judge whether the table exists in database 42 | func (s *sqlite3) TableExistSQL(tableName string) (string, []interface{}) { 43 | args := []interface{}{tableName} 44 | return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args 45 | } 46 | -------------------------------------------------------------------------------- /gee-orm/day6-transaction/dialect/sqlite3_test.go: -------------------------------------------------------------------------------- 1 | package dialect 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestDataTypeOf(t *testing.T) { 9 | dial := &sqlite3{} 10 | cases := []struct { 11 | Value interface{} 12 | Type string 13 | }{ 14 | {"Tom", "text"}, 15 | {123, "integer"}, 16 | {1.2, "real"}, 17 | {[]int{1, 2, 3}, "blob"}, 18 | } 19 | 20 | for _, c := range cases { 21 | if typ := dial.DataTypeOf(reflect.ValueOf(c.Value)); typ != c.Type { 22 | t.Fatalf("expect %s, but got %s", c.Type, typ) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /gee-orm/day6-transaction/go.mod: -------------------------------------------------------------------------------- 1 | module geeorm 2 | 3 | go 1.13 4 | 5 | require github.com/mattn/go-sqlite3 v2.0.3+incompatible 6 | -------------------------------------------------------------------------------- /gee-orm/day6-transaction/log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | var ( 11 | errorLog = log.New(os.Stdout, "\033[31m[error]\033[0m ", log.LstdFlags|log.Lshortfile) 12 | infoLog = log.New(os.Stdout, "\033[34m[info ]\033[0m ", log.LstdFlags|log.Lshortfile) 13 | loggers = []*log.Logger{errorLog, infoLog} 14 | mu sync.Mutex 15 | ) 16 | 17 | // log methods 18 | var ( 19 | Error = errorLog.Println 20 | Errorf = errorLog.Printf 21 | Info = infoLog.Println 22 | Infof = infoLog.Printf 23 | ) 24 | 25 | // log levels 26 | const ( 27 | InfoLevel = iota 28 | ErrorLevel 29 | Disabled 30 | ) 31 | 32 | // SetLevel controls log level 33 | func SetLevel(level int) { 34 | mu.Lock() 35 | defer mu.Unlock() 36 | 37 | for _, logger := range loggers { 38 | logger.SetOutput(os.Stdout) 39 | } 40 | 41 | if ErrorLevel < level { 42 | errorLog.SetOutput(ioutil.Discard) 43 | } 44 | if InfoLevel < level { 45 | infoLog.SetOutput(ioutil.Discard) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /gee-orm/day6-transaction/log/log_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestSetLevel(t *testing.T) { 9 | SetLevel(ErrorLevel) 10 | if infoLog.Writer() == os.Stdout || errorLog.Writer() != os.Stdout { 11 | t.Fatal("failed to set log level") 12 | } 13 | SetLevel(Disabled) 14 | if infoLog.Writer() == os.Stdout || errorLog.Writer() == os.Stdout { 15 | t.Fatal("failed to set log level") 16 | } 17 | } -------------------------------------------------------------------------------- /gee-orm/day6-transaction/schema/schema_test.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "geeorm/dialect" 5 | "testing" 6 | ) 7 | 8 | type User struct { 9 | Name string `geeorm:"PRIMARY KEY"` 10 | Age int 11 | } 12 | 13 | var TestDial, _ = dialect.GetDialect("sqlite3") 14 | 15 | func TestParse(t *testing.T) { 16 | schema := Parse(&User{}, TestDial) 17 | if schema.Name != "User" || len(schema.Fields) != 2 { 18 | t.Fatal("failed to parse User struct") 19 | } 20 | if schema.GetField("Name").Tag != "PRIMARY KEY" { 21 | t.Fatal("failed to parse primary key") 22 | } 23 | } 24 | 25 | func TestSchema_RecordValues(t *testing.T) { 26 | schema := Parse(&User{}, TestDial) 27 | values := schema.RecordValues(&User{"Tom", 18}) 28 | 29 | name := values[0].(string) 30 | age := values[1].(int) 31 | 32 | if name != "Tom" || age != 18 { 33 | t.Fatal("failed to get values") 34 | } 35 | } 36 | 37 | type UserTest struct { 38 | Name string `geeorm:"PRIMARY KEY"` 39 | Age int 40 | } 41 | 42 | func (u *UserTest) TableName() string { 43 | return "ns_user_test" 44 | } 45 | 46 | func TestSchema_TableName(t *testing.T) { 47 | schema := Parse(&UserTest{}, TestDial) 48 | if schema.Name != "ns_user_test" || len(schema.Fields) != 2 { 49 | t.Fatal("failed to parse User struct") 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /gee-orm/day6-transaction/session/hooks.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "geeorm/log" 5 | "reflect" 6 | ) 7 | 8 | // Hooks constants 9 | const ( 10 | BeforeQuery = "BeforeQuery" 11 | AfterQuery = "AfterQuery" 12 | BeforeUpdate = "BeforeUpdate" 13 | AfterUpdate = "AfterUpdate" 14 | BeforeDelete = "BeforeDelete" 15 | AfterDelete = "AfterDelete" 16 | BeforeInsert = "BeforeInsert" 17 | AfterInsert = "AfterInsert" 18 | ) 19 | 20 | // CallMethod calls the registered hooks 21 | func (s *Session) CallMethod(method string, value interface{}) { 22 | fm := reflect.ValueOf(s.RefTable().Model).MethodByName(method) 23 | if value != nil { 24 | fm = reflect.ValueOf(value).MethodByName(method) 25 | } 26 | param := []reflect.Value{reflect.ValueOf(s)} 27 | if fm.IsValid() { 28 | if v := fm.Call(param); len(v) > 0 { 29 | if err, ok := v[0].Interface().(error); ok { 30 | log.Error(err) 31 | } 32 | } 33 | } 34 | return 35 | } 36 | -------------------------------------------------------------------------------- /gee-orm/day6-transaction/session/hooks_test.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "geeorm/log" 5 | "testing" 6 | ) 7 | 8 | type Account struct { 9 | ID int `geeorm:"PRIMARY KEY"` 10 | Password string 11 | } 12 | 13 | func (account *Account) BeforeInsert(s *Session) error { 14 | log.Info("before inert", account) 15 | account.ID += 1000 16 | return nil 17 | } 18 | 19 | func (account *Account) AfterQuery(s *Session) error { 20 | log.Info("after query", account) 21 | account.Password = "******" 22 | return nil 23 | } 24 | 25 | func TestSession_CallMethod(t *testing.T) { 26 | s := NewSession().Model(&Account{}) 27 | _ = s.DropTable() 28 | _ = s.CreateTable() 29 | _, _ = s.Insert(&Account{1, "123456"}, &Account{2, "qwerty"}) 30 | 31 | u := &Account{} 32 | 33 | err := s.First(u) 34 | if err != nil || u.ID != 1001 || u.Password != "******" { 35 | t.Fatal("Failed to call hooks after query, got", u) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /gee-orm/day6-transaction/session/raw_test.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "database/sql" 5 | "os" 6 | "testing" 7 | 8 | "geeorm/dialect" 9 | 10 | _ "github.com/mattn/go-sqlite3" 11 | ) 12 | 13 | var ( 14 | TestDB *sql.DB 15 | TestDial, _ = dialect.GetDialect("sqlite3") 16 | ) 17 | 18 | func TestMain(m *testing.M) { 19 | TestDB, _ = sql.Open("sqlite3", "../gee.db") 20 | code := m.Run() 21 | _ = TestDB.Close() 22 | os.Exit(code) 23 | } 24 | 25 | func NewSession() *Session { 26 | return New(TestDB, TestDial) 27 | } 28 | 29 | func TestSession_Exec(t *testing.T) { 30 | s := NewSession() 31 | _, _ = s.Raw("DROP TABLE IF EXISTS User;").Exec() 32 | _, _ = s.Raw("CREATE TABLE User(Name text);").Exec() 33 | result, _ := s.Raw("INSERT INTO User(`Name`) values (?), (?)", "Tom", "Sam").Exec() 34 | if count, err := result.RowsAffected(); err != nil || count != 2 { 35 | t.Fatal("expect 2, but got", count) 36 | } 37 | } 38 | 39 | func TestSession_QueryRows(t *testing.T) { 40 | s := NewSession() 41 | _, _ = s.Raw("DROP TABLE IF EXISTS User;").Exec() 42 | _, _ = s.Raw("CREATE TABLE User(Name text);").Exec() 43 | row := s.Raw("SELECT count(*) FROM User").QueryRow() 44 | var count int 45 | if err := row.Scan(&count); err != nil || count != 0 { 46 | t.Fatal("failed to query db", err) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /gee-orm/day6-transaction/session/table.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "fmt" 5 | "geeorm/log" 6 | "reflect" 7 | "strings" 8 | 9 | "geeorm/schema" 10 | ) 11 | 12 | // Model assigns refTable 13 | func (s *Session) Model(value interface{}) *Session { 14 | // nil or different model, update refTable 15 | if s.refTable == nil || reflect.TypeOf(value) != reflect.TypeOf(s.refTable.Model) { 16 | s.refTable = schema.Parse(value, s.dialect) 17 | } 18 | return s 19 | } 20 | 21 | // RefTable returns a Schema instance that contains all parsed fields 22 | func (s *Session) RefTable() *schema.Schema { 23 | if s.refTable == nil { 24 | log.Error("Model is not set") 25 | } 26 | return s.refTable 27 | } 28 | 29 | // CreateTable create a table in database with a model 30 | func (s *Session) CreateTable() error { 31 | table := s.RefTable() 32 | var columns []string 33 | for _, field := range table.Fields { 34 | columns = append(columns, fmt.Sprintf("%s %s %s", field.Name, field.Type, field.Tag)) 35 | } 36 | desc := strings.Join(columns, ",") 37 | _, err := s.Raw(fmt.Sprintf("CREATE TABLE %s (%s);", table.Name, desc)).Exec() 38 | return err 39 | } 40 | 41 | // DropTable drops a table with the name of model 42 | func (s *Session) DropTable() error { 43 | _, err := s.Raw(fmt.Sprintf("DROP TABLE IF EXISTS %s", s.RefTable().Name)).Exec() 44 | return err 45 | } 46 | 47 | // HasTable returns true of the table exists 48 | func (s *Session) HasTable() bool { 49 | sql, values := s.dialect.TableExistSQL(s.RefTable().Name) 50 | row := s.Raw(sql, values...).QueryRow() 51 | var tmp string 52 | _ = row.Scan(&tmp) 53 | return tmp == s.RefTable().Name 54 | } 55 | -------------------------------------------------------------------------------- /gee-orm/day6-transaction/session/table_test.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | type User struct { 8 | Name string `geeorm:"PRIMARY KEY"` 9 | Age int 10 | } 11 | 12 | func TestSession_CreateTable(t *testing.T) { 13 | s := NewSession().Model(&User{}) 14 | _ = s.DropTable() 15 | _ = s.CreateTable() 16 | if !s.HasTable() { 17 | t.Fatal("Failed to create table User") 18 | } 19 | } 20 | 21 | func TestSession_Model(t *testing.T) { 22 | s := NewSession().Model(&User{}) 23 | table := s.RefTable() 24 | s.Model(&Session{}) 25 | if table.Name != "User" || s.RefTable().Name != "Session" { 26 | t.Fatal("Failed to change model") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gee-orm/day6-transaction/session/transaction.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import "geeorm/log" 4 | 5 | // Begin a transaction 6 | func (s *Session) Begin() (err error) { 7 | log.Info("transaction begin") 8 | if s.tx, err = s.db.Begin(); err != nil { 9 | log.Error(err) 10 | return 11 | } 12 | return 13 | } 14 | 15 | // Commit a transaction 16 | func (s *Session) Commit() (err error) { 17 | log.Info("transaction commit") 18 | if err = s.tx.Commit(); err != nil { 19 | log.Error(err) 20 | } 21 | return 22 | } 23 | 24 | // Rollback a transaction 25 | func (s *Session) Rollback() (err error) { 26 | log.Info("transaction rollback") 27 | if err = s.tx.Rollback(); err != nil { 28 | log.Error(err) 29 | } 30 | return 31 | } 32 | -------------------------------------------------------------------------------- /gee-orm/day7-migrate/clause/clause.go: -------------------------------------------------------------------------------- 1 | package clause 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Clause contains SQL conditions 8 | type Clause struct { 9 | sql map[Type]string 10 | sqlVars map[Type][]interface{} 11 | } 12 | 13 | // Type is the type of Clause 14 | type Type int 15 | 16 | // Support types for Clause 17 | const ( 18 | INSERT Type = iota 19 | VALUES 20 | SELECT 21 | LIMIT 22 | WHERE 23 | ORDERBY 24 | UPDATE 25 | DELETE 26 | COUNT 27 | ) 28 | 29 | // Set adds a sub clause of specific type 30 | func (c *Clause) Set(name Type, vars ...interface{}) { 31 | if c.sql == nil { 32 | c.sql = make(map[Type]string) 33 | c.sqlVars = make(map[Type][]interface{}) 34 | } 35 | sql, vars := generators[name](vars...) 36 | c.sql[name] = sql 37 | c.sqlVars[name] = vars 38 | } 39 | 40 | // Build generate the final SQL and SQLVars 41 | func (c *Clause) Build(orders ...Type) (string, []interface{}) { 42 | var sqls []string 43 | var vars []interface{} 44 | for _, order := range orders { 45 | if sql, ok := c.sql[order]; ok { 46 | sqls = append(sqls, sql) 47 | vars = append(vars, c.sqlVars[order]...) 48 | } 49 | } 50 | return strings.Join(sqls, " "), vars 51 | } 52 | -------------------------------------------------------------------------------- /gee-orm/day7-migrate/dialect/dialect.go: -------------------------------------------------------------------------------- 1 | package dialect 2 | 3 | import "reflect" 4 | 5 | var dialectsMap = map[string]Dialect{} 6 | 7 | // Dialect is an interface contains methods that a dialect has to implement 8 | type Dialect interface { 9 | DataTypeOf(typ reflect.Value) string 10 | TableExistSQL(tableName string) (string, []interface{}) 11 | } 12 | 13 | // RegisterDialect register a dialect to the global variable 14 | func RegisterDialect(name string, dialect Dialect) { 15 | dialectsMap[name] = dialect 16 | } 17 | 18 | // Get the dialect from global variable if it exists 19 | func GetDialect(name string) (dialect Dialect, ok bool) { 20 | dialect, ok = dialectsMap[name] 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /gee-orm/day7-migrate/dialect/sqlite3.go: -------------------------------------------------------------------------------- 1 | package dialect 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "time" 7 | ) 8 | 9 | type sqlite3 struct{} 10 | 11 | var _ Dialect = (*sqlite3)(nil) 12 | 13 | func init() { 14 | RegisterDialect("sqlite3", &sqlite3{}) 15 | } 16 | 17 | // Get Data Type for sqlite3 Dialect 18 | func (s *sqlite3) DataTypeOf(typ reflect.Value) string { 19 | switch typ.Kind() { 20 | case reflect.Bool: 21 | return "bool" 22 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, 23 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr: 24 | return "integer" 25 | case reflect.Int64, reflect.Uint64: 26 | return "bigint" 27 | case reflect.Float32, reflect.Float64: 28 | return "real" 29 | case reflect.String: 30 | return "text" 31 | case reflect.Array, reflect.Slice: 32 | return "blob" 33 | case reflect.Struct: 34 | if _, ok := typ.Interface().(time.Time); ok { 35 | return "datetime" 36 | } 37 | } 38 | panic(fmt.Sprintf("invalid sql type %s (%s)", typ.Type().Name(), typ.Kind())) 39 | } 40 | 41 | // TableExistSQL returns SQL that judge whether the table exists in database 42 | func (s *sqlite3) TableExistSQL(tableName string) (string, []interface{}) { 43 | args := []interface{}{tableName} 44 | return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args 45 | } 46 | -------------------------------------------------------------------------------- /gee-orm/day7-migrate/dialect/sqlite3_test.go: -------------------------------------------------------------------------------- 1 | package dialect 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestDataTypeOf(t *testing.T) { 9 | dial := &sqlite3{} 10 | cases := []struct { 11 | Value interface{} 12 | Type string 13 | }{ 14 | {"Tom", "text"}, 15 | {123, "integer"}, 16 | {1.2, "real"}, 17 | {[]int{1, 2, 3}, "blob"}, 18 | } 19 | 20 | for _, c := range cases { 21 | if typ := dial.DataTypeOf(reflect.ValueOf(c.Value)); typ != c.Type { 22 | t.Fatalf("expect %s, but got %s", c.Type, typ) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /gee-orm/day7-migrate/go.mod: -------------------------------------------------------------------------------- 1 | module geeorm 2 | 3 | go 1.13 4 | 5 | require github.com/mattn/go-sqlite3 v2.0.3+incompatible 6 | -------------------------------------------------------------------------------- /gee-orm/day7-migrate/log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | var ( 11 | errorLog = log.New(os.Stdout, "\033[31m[error]\033[0m ", log.LstdFlags|log.Lshortfile) 12 | infoLog = log.New(os.Stdout, "\033[34m[info ]\033[0m ", log.LstdFlags|log.Lshortfile) 13 | loggers = []*log.Logger{errorLog, infoLog} 14 | mu sync.Mutex 15 | ) 16 | 17 | // log methods 18 | var ( 19 | Error = errorLog.Println 20 | Errorf = errorLog.Printf 21 | Info = infoLog.Println 22 | Infof = infoLog.Printf 23 | ) 24 | 25 | // log levels 26 | const ( 27 | InfoLevel = iota 28 | ErrorLevel 29 | Disabled 30 | ) 31 | 32 | // SetLevel controls log level 33 | func SetLevel(level int) { 34 | mu.Lock() 35 | defer mu.Unlock() 36 | 37 | for _, logger := range loggers { 38 | logger.SetOutput(os.Stdout) 39 | } 40 | 41 | if ErrorLevel < level { 42 | errorLog.SetOutput(ioutil.Discard) 43 | } 44 | if InfoLevel < level { 45 | infoLog.SetOutput(ioutil.Discard) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /gee-orm/day7-migrate/log/log_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestSetLevel(t *testing.T) { 9 | SetLevel(ErrorLevel) 10 | if infoLog.Writer() == os.Stdout || errorLog.Writer() != os.Stdout { 11 | t.Fatal("failed to set log level") 12 | } 13 | SetLevel(Disabled) 14 | if infoLog.Writer() == os.Stdout || errorLog.Writer() == os.Stdout { 15 | t.Fatal("failed to set log level") 16 | } 17 | } -------------------------------------------------------------------------------- /gee-orm/day7-migrate/schema/schema_test.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "geeorm/dialect" 5 | "testing" 6 | ) 7 | 8 | type User struct { 9 | Name string `geeorm:"PRIMARY KEY"` 10 | Age int 11 | } 12 | 13 | var TestDial, _ = dialect.GetDialect("sqlite3") 14 | 15 | func TestParse(t *testing.T) { 16 | schema := Parse(&User{}, TestDial) 17 | if schema.Name != "User" || len(schema.Fields) != 2 { 18 | t.Fatal("failed to parse User struct") 19 | } 20 | if schema.GetField("Name").Tag != "PRIMARY KEY" { 21 | t.Fatal("failed to parse primary key") 22 | } 23 | } 24 | 25 | func TestSchema_RecordValues(t *testing.T) { 26 | schema := Parse(&User{}, TestDial) 27 | values := schema.RecordValues(&User{"Tom", 18}) 28 | 29 | name := values[0].(string) 30 | age := values[1].(int) 31 | 32 | if name != "Tom" || age != 18 { 33 | t.Fatal("failed to get values") 34 | } 35 | } 36 | 37 | type UserTest struct { 38 | Name string `geeorm:"PRIMARY KEY"` 39 | Age int 40 | } 41 | 42 | func (u *UserTest) TableName() string { 43 | return "ns_user_test" 44 | } 45 | 46 | func TestSchema_TableName(t *testing.T) { 47 | schema := Parse(&UserTest{}, TestDial) 48 | if schema.Name != "ns_user_test" || len(schema.Fields) != 2 { 49 | t.Fatal("failed to parse User struct") 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /gee-orm/day7-migrate/session/hooks.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "geeorm/log" 5 | "reflect" 6 | ) 7 | 8 | // Hooks constants 9 | const ( 10 | BeforeQuery = "BeforeQuery" 11 | AfterQuery = "AfterQuery" 12 | BeforeUpdate = "BeforeUpdate" 13 | AfterUpdate = "AfterUpdate" 14 | BeforeDelete = "BeforeDelete" 15 | AfterDelete = "AfterDelete" 16 | BeforeInsert = "BeforeInsert" 17 | AfterInsert = "AfterInsert" 18 | ) 19 | 20 | // CallMethod calls the registered hooks 21 | func (s *Session) CallMethod(method string, value interface{}) { 22 | fm := reflect.ValueOf(s.RefTable().Model).MethodByName(method) 23 | if value != nil { 24 | fm = reflect.ValueOf(value).MethodByName(method) 25 | } 26 | param := []reflect.Value{reflect.ValueOf(s)} 27 | if fm.IsValid() { 28 | if v := fm.Call(param); len(v) > 0 { 29 | if err, ok := v[0].Interface().(error); ok { 30 | log.Error(err) 31 | } 32 | } 33 | } 34 | return 35 | } 36 | -------------------------------------------------------------------------------- /gee-orm/day7-migrate/session/hooks_test.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "geeorm/log" 5 | "testing" 6 | ) 7 | 8 | type Account struct { 9 | ID int `geeorm:"PRIMARY KEY"` 10 | Password string 11 | } 12 | 13 | func (account *Account) BeforeInsert(s *Session) error { 14 | log.Info("before inert", account) 15 | account.ID += 1000 16 | return nil 17 | } 18 | 19 | func (account *Account) AfterQuery(s *Session) error { 20 | log.Info("after query", account) 21 | account.Password = "******" 22 | return nil 23 | } 24 | 25 | func TestSession_CallMethod(t *testing.T) { 26 | s := NewSession().Model(&Account{}) 27 | _ = s.DropTable() 28 | _ = s.CreateTable() 29 | _, _ = s.Insert(&Account{1, "123456"}, &Account{2, "qwerty"}) 30 | 31 | u := &Account{} 32 | 33 | err := s.First(u) 34 | if err != nil || u.ID != 1001 || u.Password != "******" { 35 | t.Fatal("Failed to call hooks after query, got", u) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /gee-orm/day7-migrate/session/raw_test.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "database/sql" 5 | "os" 6 | "testing" 7 | 8 | "geeorm/dialect" 9 | 10 | _ "github.com/mattn/go-sqlite3" 11 | ) 12 | 13 | var ( 14 | TestDB *sql.DB 15 | TestDial, _ = dialect.GetDialect("sqlite3") 16 | ) 17 | 18 | func TestMain(m *testing.M) { 19 | TestDB, _ = sql.Open("sqlite3", "../gee.db") 20 | code := m.Run() 21 | _ = TestDB.Close() 22 | os.Exit(code) 23 | } 24 | 25 | func NewSession() *Session { 26 | return New(TestDB, TestDial) 27 | } 28 | 29 | func TestSession_Exec(t *testing.T) { 30 | s := NewSession() 31 | _, _ = s.Raw("DROP TABLE IF EXISTS User;").Exec() 32 | _, _ = s.Raw("CREATE TABLE User(Name text);").Exec() 33 | result, _ := s.Raw("INSERT INTO User(`Name`) values (?), (?)", "Tom", "Sam").Exec() 34 | if count, err := result.RowsAffected(); err != nil || count != 2 { 35 | t.Fatal("expect 2, but got", count) 36 | } 37 | } 38 | 39 | func TestSession_QueryRows(t *testing.T) { 40 | s := NewSession() 41 | _, _ = s.Raw("DROP TABLE IF EXISTS User;").Exec() 42 | _, _ = s.Raw("CREATE TABLE User(Name text);").Exec() 43 | row := s.Raw("SELECT count(*) FROM User").QueryRow() 44 | var count int 45 | if err := row.Scan(&count); err != nil || count != 0 { 46 | t.Fatal("failed to query db", err) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /gee-orm/day7-migrate/session/table.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "fmt" 5 | "geeorm/log" 6 | "reflect" 7 | "strings" 8 | 9 | "geeorm/schema" 10 | ) 11 | 12 | // Model assigns refTable 13 | func (s *Session) Model(value interface{}) *Session { 14 | // nil or different model, update refTable 15 | if s.refTable == nil || reflect.TypeOf(value) != reflect.TypeOf(s.refTable.Model) { 16 | s.refTable = schema.Parse(value, s.dialect) 17 | } 18 | return s 19 | } 20 | 21 | // RefTable returns a Schema instance that contains all parsed fields 22 | func (s *Session) RefTable() *schema.Schema { 23 | if s.refTable == nil { 24 | log.Error("Model is not set") 25 | } 26 | return s.refTable 27 | } 28 | 29 | // CreateTable create a table in database with a model 30 | func (s *Session) CreateTable() error { 31 | table := s.RefTable() 32 | var columns []string 33 | for _, field := range table.Fields { 34 | columns = append(columns, fmt.Sprintf("%s %s %s", field.Name, field.Type, field.Tag)) 35 | } 36 | desc := strings.Join(columns, ",") 37 | _, err := s.Raw(fmt.Sprintf("CREATE TABLE %s (%s);", table.Name, desc)).Exec() 38 | return err 39 | } 40 | 41 | // DropTable drops a table with the name of model 42 | func (s *Session) DropTable() error { 43 | _, err := s.Raw(fmt.Sprintf("DROP TABLE IF EXISTS %s", s.RefTable().Name)).Exec() 44 | return err 45 | } 46 | 47 | // HasTable returns true of the table exists 48 | func (s *Session) HasTable() bool { 49 | sql, values := s.dialect.TableExistSQL(s.RefTable().Name) 50 | row := s.Raw(sql, values...).QueryRow() 51 | var tmp string 52 | _ = row.Scan(&tmp) 53 | return tmp == s.RefTable().Name 54 | } 55 | -------------------------------------------------------------------------------- /gee-orm/day7-migrate/session/table_test.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | type User struct { 8 | Name string `geeorm:"PRIMARY KEY"` 9 | Age int 10 | } 11 | 12 | func TestSession_CreateTable(t *testing.T) { 13 | s := NewSession().Model(&User{}) 14 | _ = s.DropTable() 15 | _ = s.CreateTable() 16 | if !s.HasTable() { 17 | t.Fatal("Failed to create table User") 18 | } 19 | } 20 | 21 | func TestSession_Model(t *testing.T) { 22 | s := NewSession().Model(&User{}) 23 | table := s.RefTable() 24 | s.Model(&Session{}) 25 | if table.Name != "User" || s.RefTable().Name != "Session" { 26 | t.Fatal("Failed to change model") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gee-orm/day7-migrate/session/transaction.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import "geeorm/log" 4 | 5 | // Begin a transaction 6 | func (s *Session) Begin() (err error) { 7 | log.Info("transaction begin") 8 | if s.tx, err = s.db.Begin(); err != nil { 9 | log.Error(err) 10 | return 11 | } 12 | return 13 | } 14 | 15 | // Commit a transaction 16 | func (s *Session) Commit() (err error) { 17 | log.Info("transaction commit") 18 | if err = s.tx.Commit(); err != nil { 19 | log.Error(err) 20 | } 21 | return 22 | } 23 | 24 | // Rollback a transaction 25 | func (s *Session) Rollback() (err error) { 26 | log.Info("transaction rollback") 27 | if err = s.tx.Rollback(); err != nil { 28 | log.Error(err) 29 | } 30 | return 31 | } 32 | -------------------------------------------------------------------------------- /gee-orm/doc/geeorm-day1/geeorm_log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-orm/doc/geeorm-day1/geeorm_log.png -------------------------------------------------------------------------------- /gee-orm/doc/geeorm/geeorm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-orm/doc/geeorm/geeorm.jpg -------------------------------------------------------------------------------- /gee-orm/doc/geeorm/geeorm_sm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-orm/doc/geeorm/geeorm_sm.jpg -------------------------------------------------------------------------------- /gee-orm/run_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eou pipefail 3 | 4 | cur=$PWD 5 | for item in "$cur"/day*/ 6 | do 7 | echo "$item" 8 | cd "$item" 9 | go test geeorm/... 2>&1 | grep -v warning 10 | done -------------------------------------------------------------------------------- /gee-rpc/day1-codec/codec/codec.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | type Header struct { 8 | ServiceMethod string // format "Service.Method" 9 | Seq uint64 // sequence number chosen by client 10 | Error string 11 | } 12 | 13 | type Codec interface { 14 | io.Closer 15 | ReadHeader(*Header) error 16 | ReadBody(interface{}) error 17 | Write(*Header, interface{}) error 18 | } 19 | 20 | type NewCodecFunc func(io.ReadWriteCloser) Codec 21 | 22 | type Type string 23 | 24 | const ( 25 | GobType Type = "application/gob" 26 | JsonType Type = "application/json" // not implemented 27 | ) 28 | 29 | var NewCodecFuncMap map[Type]NewCodecFunc 30 | 31 | func init() { 32 | NewCodecFuncMap = make(map[Type]NewCodecFunc) 33 | NewCodecFuncMap[GobType] = NewGobCodec 34 | } 35 | -------------------------------------------------------------------------------- /gee-rpc/day1-codec/codec/gob.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "bufio" 5 | "encoding/gob" 6 | "io" 7 | "log" 8 | ) 9 | 10 | type GobCodec struct { 11 | conn io.ReadWriteCloser 12 | buf *bufio.Writer 13 | dec *gob.Decoder 14 | enc *gob.Encoder 15 | } 16 | 17 | var _ Codec = (*GobCodec)(nil) 18 | 19 | func NewGobCodec(conn io.ReadWriteCloser) Codec { 20 | buf := bufio.NewWriter(conn) 21 | return &GobCodec{ 22 | conn: conn, 23 | buf: buf, 24 | dec: gob.NewDecoder(conn), 25 | enc: gob.NewEncoder(buf), 26 | } 27 | } 28 | 29 | func (c *GobCodec) ReadHeader(h *Header) error { 30 | return c.dec.Decode(h) 31 | } 32 | 33 | func (c *GobCodec) ReadBody(body interface{}) error { 34 | return c.dec.Decode(body) 35 | } 36 | 37 | func (c *GobCodec) Write(h *Header, body interface{}) (err error) { 38 | defer func() { 39 | _ = c.buf.Flush() 40 | if err != nil { 41 | _ = c.Close() 42 | } 43 | }() 44 | if err = c.enc.Encode(h); err != nil { 45 | log.Println("rpc: gob error encoding header:", err) 46 | return 47 | } 48 | if err = c.enc.Encode(body); err != nil { 49 | log.Println("rpc: gob error encoding body:", err) 50 | return 51 | } 52 | return 53 | } 54 | 55 | func (c *GobCodec) Close() error { 56 | return c.conn.Close() 57 | } 58 | -------------------------------------------------------------------------------- /gee-rpc/day1-codec/go.mod: -------------------------------------------------------------------------------- 1 | module geerpc 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gee-rpc/day1-codec/main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "geerpc" 7 | "geerpc/codec" 8 | "log" 9 | "net" 10 | "time" 11 | ) 12 | 13 | func startServer(addr chan string) { 14 | // pick a free port 15 | l, err := net.Listen("tcp", ":0") 16 | if err != nil { 17 | log.Fatal("network error:", err) 18 | } 19 | log.Println("start rpc server on", l.Addr()) 20 | addr <- l.Addr().String() 21 | geerpc.Accept(l) 22 | } 23 | 24 | func main() { 25 | log.SetFlags(0) 26 | addr := make(chan string) 27 | go startServer(addr) 28 | 29 | // in fact, following code is like a simple geerpc client 30 | conn, _ := net.Dial("tcp", <-addr) 31 | defer func() { _ = conn.Close() }() 32 | 33 | time.Sleep(time.Second) 34 | // send options 35 | _ = json.NewEncoder(conn).Encode(geerpc.DefaultOption) 36 | cc := codec.NewGobCodec(conn) 37 | // send request & receive response 38 | for i := 0; i < 5; i++ { 39 | h := &codec.Header{ 40 | ServiceMethod: "Foo.Sum", 41 | Seq: uint64(i), 42 | } 43 | _ = cc.Write(h, fmt.Sprintf("geerpc req %d", h.Seq)) 44 | _ = cc.ReadHeader(h) 45 | var reply string 46 | _ = cc.ReadBody(&reply) 47 | log.Println("reply:", reply) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /gee-rpc/day2-client/codec/codec.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | type Header struct { 8 | ServiceMethod string // format "Service.Method" 9 | Seq uint64 // sequence number chosen by client 10 | Error string 11 | } 12 | 13 | type Codec interface { 14 | io.Closer 15 | ReadHeader(*Header) error 16 | ReadBody(interface{}) error 17 | Write(*Header, interface{}) error 18 | } 19 | 20 | type NewCodecFunc func(io.ReadWriteCloser) Codec 21 | 22 | type Type string 23 | 24 | const ( 25 | GobType Type = "application/gob" 26 | JsonType Type = "application/json" // not implemented 27 | ) 28 | 29 | var NewCodecFuncMap map[Type]NewCodecFunc 30 | 31 | func init() { 32 | NewCodecFuncMap = make(map[Type]NewCodecFunc) 33 | NewCodecFuncMap[GobType] = NewGobCodec 34 | } 35 | -------------------------------------------------------------------------------- /gee-rpc/day2-client/codec/gob.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "bufio" 5 | "encoding/gob" 6 | "io" 7 | "log" 8 | ) 9 | 10 | type GobCodec struct { 11 | conn io.ReadWriteCloser 12 | buf *bufio.Writer 13 | dec *gob.Decoder 14 | enc *gob.Encoder 15 | } 16 | 17 | var _ Codec = (*GobCodec)(nil) 18 | 19 | func NewGobCodec(conn io.ReadWriteCloser) Codec { 20 | buf := bufio.NewWriter(conn) 21 | return &GobCodec{ 22 | conn: conn, 23 | buf: buf, 24 | dec: gob.NewDecoder(conn), 25 | enc: gob.NewEncoder(buf), 26 | } 27 | } 28 | 29 | func (c *GobCodec) ReadHeader(h *Header) error { 30 | return c.dec.Decode(h) 31 | } 32 | 33 | func (c *GobCodec) ReadBody(body interface{}) error { 34 | return c.dec.Decode(body) 35 | } 36 | 37 | func (c *GobCodec) Write(h *Header, body interface{}) (err error) { 38 | defer func() { 39 | _ = c.buf.Flush() 40 | if err != nil { 41 | _ = c.Close() 42 | } 43 | }() 44 | if err = c.enc.Encode(h); err != nil { 45 | log.Println("rpc: gob error encoding header:", err) 46 | return 47 | } 48 | if err = c.enc.Encode(body); err != nil { 49 | log.Println("rpc: gob error encoding body:", err) 50 | return 51 | } 52 | return 53 | } 54 | 55 | func (c *GobCodec) Close() error { 56 | return c.conn.Close() 57 | } 58 | -------------------------------------------------------------------------------- /gee-rpc/day2-client/go.mod: -------------------------------------------------------------------------------- 1 | module geerpc 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gee-rpc/day2-client/main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "geerpc" 6 | "log" 7 | "net" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | func startServer(addr chan string) { 13 | // pick a free port 14 | l, err := net.Listen("tcp", ":0") 15 | if err != nil { 16 | log.Fatal("network error:", err) 17 | } 18 | log.Println("start rpc server on", l.Addr()) 19 | addr <- l.Addr().String() 20 | geerpc.Accept(l) 21 | } 22 | 23 | func main() { 24 | log.SetFlags(0) 25 | addr := make(chan string) 26 | go startServer(addr) 27 | client, _ := geerpc.Dial("tcp", <-addr) 28 | defer func() { _ = client.Close() }() 29 | 30 | time.Sleep(time.Second) 31 | // send request & receive response 32 | var wg sync.WaitGroup 33 | for i := 0; i < 5; i++ { 34 | wg.Add(1) 35 | go func(i int) { 36 | defer wg.Done() 37 | args := fmt.Sprintf("geerpc req %d", i) 38 | var reply string 39 | if err := client.Call("Foo.Sum", args, &reply); err != nil { 40 | log.Fatal("call Foo.Sum error:", err) 41 | } 42 | log.Println("reply:", reply) 43 | }(i) 44 | } 45 | wg.Wait() 46 | } 47 | -------------------------------------------------------------------------------- /gee-rpc/day3-service/codec/codec.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | type Header struct { 8 | ServiceMethod string // format "Service.Method" 9 | Seq uint64 // sequence number chosen by client 10 | Error string 11 | } 12 | 13 | type Codec interface { 14 | io.Closer 15 | ReadHeader(*Header) error 16 | ReadBody(interface{}) error 17 | Write(*Header, interface{}) error 18 | } 19 | 20 | type NewCodecFunc func(io.ReadWriteCloser) Codec 21 | 22 | type Type string 23 | 24 | const ( 25 | GobType Type = "application/gob" 26 | JsonType Type = "application/json" // not implemented 27 | ) 28 | 29 | var NewCodecFuncMap map[Type]NewCodecFunc 30 | 31 | func init() { 32 | NewCodecFuncMap = make(map[Type]NewCodecFunc) 33 | NewCodecFuncMap[GobType] = NewGobCodec 34 | } 35 | -------------------------------------------------------------------------------- /gee-rpc/day3-service/codec/gob.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "bufio" 5 | "encoding/gob" 6 | "io" 7 | "log" 8 | ) 9 | 10 | type GobCodec struct { 11 | conn io.ReadWriteCloser 12 | buf *bufio.Writer 13 | dec *gob.Decoder 14 | enc *gob.Encoder 15 | } 16 | 17 | var _ Codec = (*GobCodec)(nil) 18 | 19 | func NewGobCodec(conn io.ReadWriteCloser) Codec { 20 | buf := bufio.NewWriter(conn) 21 | return &GobCodec{ 22 | conn: conn, 23 | buf: buf, 24 | dec: gob.NewDecoder(conn), 25 | enc: gob.NewEncoder(buf), 26 | } 27 | } 28 | 29 | func (c *GobCodec) ReadHeader(h *Header) error { 30 | return c.dec.Decode(h) 31 | } 32 | 33 | func (c *GobCodec) ReadBody(body interface{}) error { 34 | return c.dec.Decode(body) 35 | } 36 | 37 | func (c *GobCodec) Write(h *Header, body interface{}) (err error) { 38 | defer func() { 39 | _ = c.buf.Flush() 40 | if err != nil { 41 | _ = c.Close() 42 | } 43 | }() 44 | if err = c.enc.Encode(h); err != nil { 45 | log.Println("rpc: gob error encoding header:", err) 46 | return 47 | } 48 | if err = c.enc.Encode(body); err != nil { 49 | log.Println("rpc: gob error encoding body:", err) 50 | return 51 | } 52 | return 53 | } 54 | 55 | func (c *GobCodec) Close() error { 56 | return c.conn.Close() 57 | } 58 | -------------------------------------------------------------------------------- /gee-rpc/day3-service/go.mod: -------------------------------------------------------------------------------- 1 | module geerpc 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gee-rpc/day3-service/main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "geerpc" 5 | "log" 6 | "net" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | type Foo int 12 | 13 | type Args struct{ Num1, Num2 int } 14 | 15 | func (f Foo) Sum(args Args, reply *int) error { 16 | *reply = args.Num1 + args.Num2 17 | return nil 18 | } 19 | 20 | func startServer(addr chan string) { 21 | var foo Foo 22 | if err := geerpc.Register(&foo); err != nil { 23 | log.Fatal("register error:", err) 24 | } 25 | // pick a free port 26 | l, err := net.Listen("tcp", ":0") 27 | if err != nil { 28 | log.Fatal("network error:", err) 29 | } 30 | log.Println("start rpc server on", l.Addr()) 31 | addr <- l.Addr().String() 32 | geerpc.Accept(l) 33 | } 34 | 35 | func main() { 36 | log.SetFlags(0) 37 | addr := make(chan string) 38 | go startServer(addr) 39 | client, _ := geerpc.Dial("tcp", <-addr) 40 | defer func() { _ = client.Close() }() 41 | 42 | time.Sleep(time.Second) 43 | // send request & receive response 44 | var wg sync.WaitGroup 45 | for i := 0; i < 5; i++ { 46 | wg.Add(1) 47 | go func(i int) { 48 | defer wg.Done() 49 | args := &Args{Num1: i, Num2: i * i} 50 | var reply int 51 | if err := client.Call("Foo.Sum", args, &reply); err != nil { 52 | log.Fatal("call Foo.Sum error:", err) 53 | } 54 | log.Printf("%d + %d = %d", args.Num1, args.Num2, reply) 55 | }(i) 56 | } 57 | wg.Wait() 58 | } 59 | -------------------------------------------------------------------------------- /gee-rpc/day3-service/service_test.go: -------------------------------------------------------------------------------- 1 | package geerpc 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | type Foo int 10 | 11 | type Args struct{ Num1, Num2 int } 12 | 13 | func (f Foo) Sum(args Args, reply *int) error { 14 | *reply = args.Num1 + args.Num2 15 | return nil 16 | } 17 | 18 | // it's not a exported Method 19 | func (f Foo) sum(args Args, reply *int) error { 20 | *reply = args.Num1 + args.Num2 21 | return nil 22 | } 23 | 24 | func _assert(condition bool, msg string, v ...interface{}) { 25 | if !condition { 26 | panic(fmt.Sprintf("assertion failed: "+msg, v...)) 27 | } 28 | } 29 | 30 | func TestNewService(t *testing.T) { 31 | var foo Foo 32 | s := newService(&foo) 33 | _assert(len(s.method) == 1, "wrong service Method, expect 1, but got %d", len(s.method)) 34 | mType := s.method["Sum"] 35 | _assert(mType != nil, "wrong Method, Sum shouldn't nil") 36 | } 37 | 38 | func TestMethodType_Call(t *testing.T) { 39 | var foo Foo 40 | s := newService(&foo) 41 | mType := s.method["Sum"] 42 | 43 | argv := mType.newArgv() 44 | replyv := mType.newReplyv() 45 | argv.Set(reflect.ValueOf(Args{Num1: 1, Num2: 3})) 46 | err := s.call(mType, argv, replyv) 47 | _assert(err == nil && *replyv.Interface().(*int) == 4 && mType.NumCalls() == 1, "failed to call Foo.Sum") 48 | } 49 | -------------------------------------------------------------------------------- /gee-rpc/day4-timeout/codec/codec.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | type Header struct { 8 | ServiceMethod string // format "Service.Method" 9 | Seq uint64 // sequence number chosen by client 10 | Error string 11 | } 12 | 13 | type Codec interface { 14 | io.Closer 15 | ReadHeader(*Header) error 16 | ReadBody(interface{}) error 17 | Write(*Header, interface{}) error 18 | } 19 | 20 | type NewCodecFunc func(io.ReadWriteCloser) Codec 21 | 22 | type Type string 23 | 24 | const ( 25 | GobType Type = "application/gob" 26 | JsonType Type = "application/json" // not implemented 27 | ) 28 | 29 | var NewCodecFuncMap map[Type]NewCodecFunc 30 | 31 | func init() { 32 | NewCodecFuncMap = make(map[Type]NewCodecFunc) 33 | NewCodecFuncMap[GobType] = NewGobCodec 34 | } 35 | -------------------------------------------------------------------------------- /gee-rpc/day4-timeout/codec/gob.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "bufio" 5 | "encoding/gob" 6 | "io" 7 | "log" 8 | ) 9 | 10 | type GobCodec struct { 11 | conn io.ReadWriteCloser 12 | buf *bufio.Writer 13 | dec *gob.Decoder 14 | enc *gob.Encoder 15 | } 16 | 17 | var _ Codec = (*GobCodec)(nil) 18 | 19 | func NewGobCodec(conn io.ReadWriteCloser) Codec { 20 | buf := bufio.NewWriter(conn) 21 | return &GobCodec{ 22 | conn: conn, 23 | buf: buf, 24 | dec: gob.NewDecoder(conn), 25 | enc: gob.NewEncoder(buf), 26 | } 27 | } 28 | 29 | func (c *GobCodec) ReadHeader(h *Header) error { 30 | return c.dec.Decode(h) 31 | } 32 | 33 | func (c *GobCodec) ReadBody(body interface{}) error { 34 | return c.dec.Decode(body) 35 | } 36 | 37 | func (c *GobCodec) Write(h *Header, body interface{}) (err error) { 38 | defer func() { 39 | _ = c.buf.Flush() 40 | if err != nil { 41 | _ = c.Close() 42 | } 43 | }() 44 | if err = c.enc.Encode(h); err != nil { 45 | log.Println("rpc: gob error encoding header:", err) 46 | return 47 | } 48 | if err = c.enc.Encode(body); err != nil { 49 | log.Println("rpc: gob error encoding body:", err) 50 | return 51 | } 52 | return 53 | } 54 | 55 | func (c *GobCodec) Close() error { 56 | return c.conn.Close() 57 | } 58 | -------------------------------------------------------------------------------- /gee-rpc/day4-timeout/go.mod: -------------------------------------------------------------------------------- 1 | module geerpc 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gee-rpc/day4-timeout/main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "geerpc" 6 | "log" 7 | "net" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | type Foo int 13 | 14 | type Args struct{ Num1, Num2 int } 15 | 16 | func (f Foo) Sum(args Args, reply *int) error { 17 | *reply = args.Num1 + args.Num2 18 | return nil 19 | } 20 | 21 | func startServer(addr chan string) { 22 | var foo Foo 23 | if err := geerpc.Register(&foo); err != nil { 24 | log.Fatal("register error:", err) 25 | } 26 | // pick a free port 27 | l, err := net.Listen("tcp", ":0") 28 | if err != nil { 29 | log.Fatal("network error:", err) 30 | } 31 | log.Println("start rpc server on", l.Addr()) 32 | addr <- l.Addr().String() 33 | geerpc.Accept(l) 34 | } 35 | 36 | func main() { 37 | log.SetFlags(0) 38 | addr := make(chan string) 39 | go startServer(addr) 40 | client, _ := geerpc.Dial("tcp", <-addr) 41 | defer func() { _ = client.Close() }() 42 | 43 | time.Sleep(time.Second) 44 | // send request & receive response 45 | var wg sync.WaitGroup 46 | for i := 0; i < 5; i++ { 47 | wg.Add(1) 48 | go func(i int) { 49 | defer wg.Done() 50 | args := &Args{Num1: i, Num2: i * i} 51 | var reply int 52 | if err := client.Call(context.Background(), "Foo.Sum", args, &reply); err != nil { 53 | log.Fatal("call Foo.Sum error:", err) 54 | } 55 | log.Printf("%d + %d = %d", args.Num1, args.Num2, reply) 56 | }(i) 57 | } 58 | wg.Wait() 59 | } 60 | -------------------------------------------------------------------------------- /gee-rpc/day4-timeout/service_test.go: -------------------------------------------------------------------------------- 1 | package geerpc 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | type Foo int 10 | 11 | type Args struct{ Num1, Num2 int } 12 | 13 | func (f Foo) Sum(args Args, reply *int) error { 14 | *reply = args.Num1 + args.Num2 15 | return nil 16 | } 17 | 18 | // it's not a exported Method 19 | func (f Foo) sum(args Args, reply *int) error { 20 | *reply = args.Num1 + args.Num2 21 | return nil 22 | } 23 | 24 | func _assert(condition bool, msg string, v ...interface{}) { 25 | if !condition { 26 | panic(fmt.Sprintf("assertion failed: "+msg, v...)) 27 | } 28 | } 29 | 30 | func TestNewService(t *testing.T) { 31 | var foo Foo 32 | s := newService(&foo) 33 | _assert(len(s.method) == 1, "wrong service Method, expect 1, but got %d", len(s.method)) 34 | mType := s.method["Sum"] 35 | _assert(mType != nil, "wrong Method, Sum shouldn't nil") 36 | } 37 | 38 | func TestMethodType_Call(t *testing.T) { 39 | var foo Foo 40 | s := newService(&foo) 41 | mType := s.method["Sum"] 42 | 43 | argv := mType.newArgv() 44 | replyv := mType.newReplyv() 45 | argv.Set(reflect.ValueOf(Args{Num1: 1, Num2: 3})) 46 | err := s.call(mType, argv, replyv) 47 | _assert(err == nil && *replyv.Interface().(*int) == 4 && mType.NumCalls() == 1, "failed to call Foo.Sum") 48 | } 49 | -------------------------------------------------------------------------------- /gee-rpc/day5-http-debug/codec/codec.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | type Header struct { 8 | ServiceMethod string // format "Service.Method" 9 | Seq uint64 // sequence number chosen by client 10 | Error string 11 | } 12 | 13 | type Codec interface { 14 | io.Closer 15 | ReadHeader(*Header) error 16 | ReadBody(interface{}) error 17 | Write(*Header, interface{}) error 18 | } 19 | 20 | type NewCodecFunc func(io.ReadWriteCloser) Codec 21 | 22 | type Type string 23 | 24 | const ( 25 | GobType Type = "application/gob" 26 | JsonType Type = "application/json" // not implemented 27 | ) 28 | 29 | var NewCodecFuncMap map[Type]NewCodecFunc 30 | 31 | func init() { 32 | NewCodecFuncMap = make(map[Type]NewCodecFunc) 33 | NewCodecFuncMap[GobType] = NewGobCodec 34 | } 35 | -------------------------------------------------------------------------------- /gee-rpc/day5-http-debug/codec/gob.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "bufio" 5 | "encoding/gob" 6 | "io" 7 | "log" 8 | ) 9 | 10 | type GobCodec struct { 11 | conn io.ReadWriteCloser 12 | buf *bufio.Writer 13 | dec *gob.Decoder 14 | enc *gob.Encoder 15 | } 16 | 17 | var _ Codec = (*GobCodec)(nil) 18 | 19 | func NewGobCodec(conn io.ReadWriteCloser) Codec { 20 | buf := bufio.NewWriter(conn) 21 | return &GobCodec{ 22 | conn: conn, 23 | buf: buf, 24 | dec: gob.NewDecoder(conn), 25 | enc: gob.NewEncoder(buf), 26 | } 27 | } 28 | 29 | func (c *GobCodec) ReadHeader(h *Header) error { 30 | return c.dec.Decode(h) 31 | } 32 | 33 | func (c *GobCodec) ReadBody(body interface{}) error { 34 | return c.dec.Decode(body) 35 | } 36 | 37 | func (c *GobCodec) Write(h *Header, body interface{}) (err error) { 38 | defer func() { 39 | _ = c.buf.Flush() 40 | if err != nil { 41 | _ = c.Close() 42 | } 43 | }() 44 | if err = c.enc.Encode(h); err != nil { 45 | log.Println("rpc: gob error encoding header:", err) 46 | return 47 | } 48 | if err = c.enc.Encode(body); err != nil { 49 | log.Println("rpc: gob error encoding body:", err) 50 | return 51 | } 52 | return 53 | } 54 | 55 | func (c *GobCodec) Close() error { 56 | return c.conn.Close() 57 | } 58 | -------------------------------------------------------------------------------- /gee-rpc/day5-http-debug/debug.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package geerpc 6 | 7 | import ( 8 | "fmt" 9 | "html/template" 10 | "net/http" 11 | ) 12 | 13 | const debugText = ` 14 | 15 | GeeRPC Services 16 | {{range .}} 17 |
18 | Service {{.Name}} 19 |
20 | 21 | 22 | {{range $name, $mtype := .Method}} 23 | 24 | 25 | 26 | 27 | {{end}} 28 |
MethodCalls
{{$name}}({{$mtype.ArgType}}, {{$mtype.ReplyType}}) error{{$mtype.NumCalls}}
29 | {{end}} 30 | 31 | ` 32 | 33 | var debug = template.Must(template.New("RPC debug").Parse(debugText)) 34 | 35 | type debugHTTP struct { 36 | *Server 37 | } 38 | 39 | type debugService struct { 40 | Name string 41 | Method map[string]*methodType 42 | } 43 | 44 | // Runs at /debug/geerpc 45 | func (server debugHTTP) ServeHTTP(w http.ResponseWriter, req *http.Request) { 46 | // Build a sorted version of the data. 47 | var services []debugService 48 | server.serviceMap.Range(func(namei, svci interface{}) bool { 49 | svc := svci.(*service) 50 | services = append(services, debugService{ 51 | Name: namei.(string), 52 | Method: svc.method, 53 | }) 54 | return true 55 | }) 56 | err := debug.Execute(w, services) 57 | if err != nil { 58 | _, _ = fmt.Fprintln(w, "rpc: error executing template:", err.Error()) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /gee-rpc/day5-http-debug/go.mod: -------------------------------------------------------------------------------- 1 | module geerpc 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gee-rpc/day5-http-debug/main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "geerpc" 6 | "log" 7 | "net" 8 | "net/http" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | type Foo int 14 | 15 | type Args struct{ Num1, Num2 int } 16 | 17 | func (f Foo) Sum(args Args, reply *int) error { 18 | *reply = args.Num1 + args.Num2 19 | return nil 20 | } 21 | 22 | func startServer(addrCh chan string) { 23 | var foo Foo 24 | l, _ := net.Listen("tcp", ":9999") 25 | _ = geerpc.Register(&foo) 26 | geerpc.HandleHTTP() 27 | addrCh <- l.Addr().String() 28 | _ = http.Serve(l, nil) 29 | } 30 | 31 | func call(addrCh chan string) { 32 | client, _ := geerpc.DialHTTP("tcp", <-addrCh) 33 | defer func() { _ = client.Close() }() 34 | 35 | time.Sleep(time.Second) 36 | // send request & receive response 37 | var wg sync.WaitGroup 38 | for i := 0; i < 5; i++ { 39 | wg.Add(1) 40 | go func(i int) { 41 | defer wg.Done() 42 | args := &Args{Num1: i, Num2: i * i} 43 | var reply int 44 | if err := client.Call(context.Background(), "Foo.Sum", args, &reply); err != nil { 45 | log.Fatal("call Foo.Sum error:", err) 46 | } 47 | log.Printf("%d + %d = %d", args.Num1, args.Num2, reply) 48 | }(i) 49 | } 50 | wg.Wait() 51 | } 52 | 53 | func main() { 54 | log.SetFlags(0) 55 | ch := make(chan string) 56 | go call(ch) 57 | startServer(ch) 58 | } 59 | -------------------------------------------------------------------------------- /gee-rpc/day5-http-debug/service_test.go: -------------------------------------------------------------------------------- 1 | package geerpc 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | type Foo int 10 | 11 | type Args struct{ Num1, Num2 int } 12 | 13 | func (f Foo) Sum(args Args, reply *int) error { 14 | *reply = args.Num1 + args.Num2 15 | return nil 16 | } 17 | 18 | // it's not a exported Method 19 | func (f Foo) sum(args Args, reply *int) error { 20 | *reply = args.Num1 + args.Num2 21 | return nil 22 | } 23 | 24 | func _assert(condition bool, msg string, v ...interface{}) { 25 | if !condition { 26 | panic(fmt.Sprintf("assertion failed: "+msg, v...)) 27 | } 28 | } 29 | 30 | func TestNewService(t *testing.T) { 31 | var foo Foo 32 | s := newService(&foo) 33 | _assert(len(s.method) == 1, "wrong service Method, expect 1, but got %d", len(s.method)) 34 | mType := s.method["Sum"] 35 | _assert(mType != nil, "wrong Method, Sum shouldn't nil") 36 | } 37 | 38 | func TestMethodType_Call(t *testing.T) { 39 | var foo Foo 40 | s := newService(&foo) 41 | mType := s.method["Sum"] 42 | 43 | argv := mType.newArgv() 44 | replyv := mType.newReplyv() 45 | argv.Set(reflect.ValueOf(Args{Num1: 1, Num2: 3})) 46 | err := s.call(mType, argv, replyv) 47 | _assert(err == nil && *replyv.Interface().(*int) == 4 && mType.NumCalls() == 1, "failed to call Foo.Sum") 48 | } 49 | -------------------------------------------------------------------------------- /gee-rpc/day6-load-balance/codec/codec.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | type Header struct { 8 | ServiceMethod string // format "Service.Method" 9 | Seq uint64 // sequence number chosen by client 10 | Error string 11 | } 12 | 13 | type Codec interface { 14 | io.Closer 15 | ReadHeader(*Header) error 16 | ReadBody(interface{}) error 17 | Write(*Header, interface{}) error 18 | } 19 | 20 | type NewCodecFunc func(io.ReadWriteCloser) Codec 21 | 22 | type Type string 23 | 24 | const ( 25 | GobType Type = "application/gob" 26 | JsonType Type = "application/json" // not implemented 27 | ) 28 | 29 | var NewCodecFuncMap map[Type]NewCodecFunc 30 | 31 | func init() { 32 | NewCodecFuncMap = make(map[Type]NewCodecFunc) 33 | NewCodecFuncMap[GobType] = NewGobCodec 34 | } 35 | -------------------------------------------------------------------------------- /gee-rpc/day6-load-balance/codec/gob.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "bufio" 5 | "encoding/gob" 6 | "io" 7 | "log" 8 | ) 9 | 10 | type GobCodec struct { 11 | conn io.ReadWriteCloser 12 | buf *bufio.Writer 13 | dec *gob.Decoder 14 | enc *gob.Encoder 15 | } 16 | 17 | var _ Codec = (*GobCodec)(nil) 18 | 19 | func NewGobCodec(conn io.ReadWriteCloser) Codec { 20 | buf := bufio.NewWriter(conn) 21 | return &GobCodec{ 22 | conn: conn, 23 | buf: buf, 24 | dec: gob.NewDecoder(conn), 25 | enc: gob.NewEncoder(buf), 26 | } 27 | } 28 | 29 | func (c *GobCodec) ReadHeader(h *Header) error { 30 | return c.dec.Decode(h) 31 | } 32 | 33 | func (c *GobCodec) ReadBody(body interface{}) error { 34 | return c.dec.Decode(body) 35 | } 36 | 37 | func (c *GobCodec) Write(h *Header, body interface{}) (err error) { 38 | defer func() { 39 | _ = c.buf.Flush() 40 | if err != nil { 41 | _ = c.Close() 42 | } 43 | }() 44 | if err = c.enc.Encode(h); err != nil { 45 | log.Println("rpc: gob error encoding header:", err) 46 | return 47 | } 48 | if err = c.enc.Encode(body); err != nil { 49 | log.Println("rpc: gob error encoding body:", err) 50 | return 51 | } 52 | return 53 | } 54 | 55 | func (c *GobCodec) Close() error { 56 | return c.conn.Close() 57 | } 58 | -------------------------------------------------------------------------------- /gee-rpc/day6-load-balance/debug.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package geerpc 6 | 7 | import ( 8 | "fmt" 9 | "html/template" 10 | "net/http" 11 | ) 12 | 13 | const debugText = ` 14 | 15 | GeeRPC Services 16 | {{range .}} 17 |
18 | Service {{.Name}} 19 |
20 | 21 | 22 | {{range $name, $mtype := .Method}} 23 | 24 | 25 | 26 | 27 | {{end}} 28 |
MethodCalls
{{$name}}({{$mtype.ArgType}}, {{$mtype.ReplyType}}) error{{$mtype.NumCalls}}
29 | {{end}} 30 | 31 | ` 32 | 33 | var debug = template.Must(template.New("RPC debug").Parse(debugText)) 34 | 35 | type debugHTTP struct { 36 | *Server 37 | } 38 | 39 | type debugService struct { 40 | Name string 41 | Method map[string]*methodType 42 | } 43 | 44 | // Runs at /debug/geerpc 45 | func (server debugHTTP) ServeHTTP(w http.ResponseWriter, req *http.Request) { 46 | // Build a sorted version of the data. 47 | var services []debugService 48 | server.serviceMap.Range(func(namei, svci interface{}) bool { 49 | svc := svci.(*service) 50 | services = append(services, debugService{ 51 | Name: namei.(string), 52 | Method: svc.method, 53 | }) 54 | return true 55 | }) 56 | err := debug.Execute(w, services) 57 | if err != nil { 58 | _, _ = fmt.Fprintln(w, "rpc: error executing template:", err.Error()) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /gee-rpc/day6-load-balance/go.mod: -------------------------------------------------------------------------------- 1 | module geerpc 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gee-rpc/day6-load-balance/service_test.go: -------------------------------------------------------------------------------- 1 | package geerpc 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | type Foo int 10 | 11 | type Args struct{ Num1, Num2 int } 12 | 13 | func (f Foo) Sum(args Args, reply *int) error { 14 | *reply = args.Num1 + args.Num2 15 | return nil 16 | } 17 | 18 | // it's not a exported Method 19 | func (f Foo) sum(args Args, reply *int) error { 20 | *reply = args.Num1 + args.Num2 21 | return nil 22 | } 23 | 24 | func _assert(condition bool, msg string, v ...interface{}) { 25 | if !condition { 26 | panic(fmt.Sprintf("assertion failed: "+msg, v...)) 27 | } 28 | } 29 | 30 | func TestNewService(t *testing.T) { 31 | var foo Foo 32 | s := newService(&foo) 33 | _assert(len(s.method) == 1, "wrong service Method, expect 1, but got %d", len(s.method)) 34 | mType := s.method["Sum"] 35 | _assert(mType != nil, "wrong Method, Sum shouldn't nil") 36 | } 37 | 38 | func TestMethodType_Call(t *testing.T) { 39 | var foo Foo 40 | s := newService(&foo) 41 | mType := s.method["Sum"] 42 | 43 | argv := mType.newArgv() 44 | replyv := mType.newReplyv() 45 | argv.Set(reflect.ValueOf(Args{Num1: 1, Num2: 3})) 46 | err := s.call(mType, argv, replyv) 47 | _assert(err == nil && *replyv.Interface().(*int) == 4 && mType.NumCalls() == 1, "failed to call Foo.Sum") 48 | } 49 | -------------------------------------------------------------------------------- /gee-rpc/day7-registry/codec/codec.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | type Header struct { 8 | ServiceMethod string // format "Service.Method" 9 | Seq uint64 // sequence number chosen by client 10 | Error string 11 | } 12 | 13 | type Codec interface { 14 | io.Closer 15 | ReadHeader(*Header) error 16 | ReadBody(interface{}) error 17 | Write(*Header, interface{}) error 18 | } 19 | 20 | type NewCodecFunc func(io.ReadWriteCloser) Codec 21 | 22 | type Type string 23 | 24 | const ( 25 | GobType Type = "application/gob" 26 | JsonType Type = "application/json" // not implemented 27 | ) 28 | 29 | var NewCodecFuncMap map[Type]NewCodecFunc 30 | 31 | func init() { 32 | NewCodecFuncMap = make(map[Type]NewCodecFunc) 33 | NewCodecFuncMap[GobType] = NewGobCodec 34 | } 35 | -------------------------------------------------------------------------------- /gee-rpc/day7-registry/codec/gob.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "bufio" 5 | "encoding/gob" 6 | "io" 7 | "log" 8 | ) 9 | 10 | type GobCodec struct { 11 | conn io.ReadWriteCloser 12 | buf *bufio.Writer 13 | dec *gob.Decoder 14 | enc *gob.Encoder 15 | } 16 | 17 | var _ Codec = (*GobCodec)(nil) 18 | 19 | func NewGobCodec(conn io.ReadWriteCloser) Codec { 20 | buf := bufio.NewWriter(conn) 21 | return &GobCodec{ 22 | conn: conn, 23 | buf: buf, 24 | dec: gob.NewDecoder(conn), 25 | enc: gob.NewEncoder(buf), 26 | } 27 | } 28 | 29 | func (c *GobCodec) ReadHeader(h *Header) error { 30 | return c.dec.Decode(h) 31 | } 32 | 33 | func (c *GobCodec) ReadBody(body interface{}) error { 34 | return c.dec.Decode(body) 35 | } 36 | 37 | func (c *GobCodec) Write(h *Header, body interface{}) (err error) { 38 | defer func() { 39 | _ = c.buf.Flush() 40 | if err != nil { 41 | _ = c.Close() 42 | } 43 | }() 44 | if err = c.enc.Encode(h); err != nil { 45 | log.Println("rpc: gob error encoding header:", err) 46 | return 47 | } 48 | if err = c.enc.Encode(body); err != nil { 49 | log.Println("rpc: gob error encoding body:", err) 50 | return 51 | } 52 | return 53 | } 54 | 55 | func (c *GobCodec) Close() error { 56 | return c.conn.Close() 57 | } 58 | -------------------------------------------------------------------------------- /gee-rpc/day7-registry/debug.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package geerpc 6 | 7 | import ( 8 | "fmt" 9 | "html/template" 10 | "net/http" 11 | ) 12 | 13 | const debugText = ` 14 | 15 | GeeRPC Services 16 | {{range .}} 17 |
18 | Service {{.Name}} 19 |
20 | 21 | 22 | {{range $name, $mtype := .Method}} 23 | 24 | 25 | 26 | 27 | {{end}} 28 |
MethodCalls
{{$name}}({{$mtype.ArgType}}, {{$mtype.ReplyType}}) error{{$mtype.NumCalls}}
29 | {{end}} 30 | 31 | ` 32 | 33 | var debug = template.Must(template.New("RPC debug").Parse(debugText)) 34 | 35 | type debugHTTP struct { 36 | *Server 37 | } 38 | 39 | type debugService struct { 40 | Name string 41 | Method map[string]*methodType 42 | } 43 | 44 | // Runs at /debug/geerpc 45 | func (server debugHTTP) ServeHTTP(w http.ResponseWriter, req *http.Request) { 46 | // Build a sorted version of the data. 47 | var services []debugService 48 | server.serviceMap.Range(func(namei, svci interface{}) bool { 49 | svc := svci.(*service) 50 | services = append(services, debugService{ 51 | Name: namei.(string), 52 | Method: svc.method, 53 | }) 54 | return true 55 | }) 56 | err := debug.Execute(w, services) 57 | if err != nil { 58 | _, _ = fmt.Fprintln(w, "rpc: error executing template:", err.Error()) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /gee-rpc/day7-registry/go.mod: -------------------------------------------------------------------------------- 1 | module geerpc 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gee-rpc/day7-registry/service_test.go: -------------------------------------------------------------------------------- 1 | package geerpc 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | type Foo int 10 | 11 | type Args struct{ Num1, Num2 int } 12 | 13 | func (f Foo) Sum(args Args, reply *int) error { 14 | *reply = args.Num1 + args.Num2 15 | return nil 16 | } 17 | 18 | // it's not a exported Method 19 | func (f Foo) sum(args Args, reply *int) error { 20 | *reply = args.Num1 + args.Num2 21 | return nil 22 | } 23 | 24 | func _assert(condition bool, msg string, v ...interface{}) { 25 | if !condition { 26 | panic(fmt.Sprintf("assertion failed: "+msg, v...)) 27 | } 28 | } 29 | 30 | func TestNewService(t *testing.T) { 31 | var foo Foo 32 | s := newService(&foo) 33 | _assert(len(s.method) == 1, "wrong service Method, expect 1, but got %d", len(s.method)) 34 | mType := s.method["Sum"] 35 | _assert(mType != nil, "wrong Method, Sum shouldn't nil") 36 | } 37 | 38 | func TestMethodType_Call(t *testing.T) { 39 | var foo Foo 40 | s := newService(&foo) 41 | mType := s.method["Sum"] 42 | 43 | argv := mType.newArgv() 44 | replyv := mType.newReplyv() 45 | argv.Set(reflect.ValueOf(Args{Num1: 1, Num2: 3})) 46 | err := s.call(mType, argv, replyv) 47 | _assert(err == nil && *replyv.Interface().(*int) == 4 && mType.NumCalls() == 1, "failed to call Foo.Sum") 48 | } 49 | -------------------------------------------------------------------------------- /gee-rpc/doc/geerpc-day5/geerpc_debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-rpc/doc/geerpc-day5/geerpc_debug.png -------------------------------------------------------------------------------- /gee-rpc/doc/geerpc-day7/registry.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-rpc/doc/geerpc-day7/registry.jpg -------------------------------------------------------------------------------- /gee-rpc/doc/geerpc/geerpc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-rpc/doc/geerpc/geerpc.jpg -------------------------------------------------------------------------------- /gee-web/day1-http-base/base1/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gee-web/day1-http-base/base1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // $ curl http://localhost:9999/ 4 | // URL.Path = "/" 5 | // $ curl http://localhost:9999/hello 6 | // Header["Accept"] = ["*/*"] 7 | // Header["User-Agent"] = ["curl/7.54.0"] 8 | 9 | import ( 10 | "fmt" 11 | "log" 12 | "net/http" 13 | ) 14 | 15 | func main() { 16 | http.HandleFunc("/", indexHandler) 17 | http.HandleFunc("/hello", helloHandler) 18 | log.Fatal(http.ListenAndServe(":9999", nil)) 19 | } 20 | 21 | // handler echoes r.URL.Path 22 | func indexHandler(w http.ResponseWriter, req *http.Request) { 23 | fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path) 24 | } 25 | 26 | // handler echoes r.URL.Header 27 | func helloHandler(w http.ResponseWriter, req *http.Request) { 28 | for k, v := range req.Header { 29 | fmt.Fprintf(w, "Header[%q] = %q\n", k, v) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /gee-web/day1-http-base/base2/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gee-web/day1-http-base/base2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // $ curl http://localhost:9999/ 4 | // URL.Path = "/" 5 | // $ curl http://localhost:9999/hello 6 | // Header["Accept"] = ["*/*"] 7 | // Header["User-Agent"] = ["curl/7.54.0"] 8 | // curl http://localhost:9999/world 9 | // 404 NOT FOUND: /world 10 | 11 | import ( 12 | "fmt" 13 | "log" 14 | "net/http" 15 | ) 16 | 17 | // Engine is the uni handler for all requests 18 | type Engine struct{} 19 | 20 | func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { 21 | switch req.URL.Path { 22 | case "/": 23 | fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path) 24 | case "/hello": 25 | for k, v := range req.Header { 26 | fmt.Fprintf(w, "Header[%q] = %q\n", k, v) 27 | } 28 | default: 29 | fmt.Fprintf(w, "404 NOT FOUND: %s\n", req.URL) 30 | } 31 | } 32 | 33 | func main() { 34 | engine := new(Engine) 35 | log.Fatal(http.ListenAndServe(":9999", engine)) 36 | } 37 | -------------------------------------------------------------------------------- /gee-web/day1-http-base/base3/gee/gee.go: -------------------------------------------------------------------------------- 1 | package gee 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | // HandlerFunc defines the request handler used by gee 10 | type HandlerFunc func(http.ResponseWriter, *http.Request) 11 | 12 | // Engine implement the interface of ServeHTTP 13 | type Engine struct { 14 | router map[string]HandlerFunc 15 | } 16 | 17 | // New is the constructor of gee.Engine 18 | func New() *Engine { 19 | return &Engine{router: make(map[string]HandlerFunc)} 20 | } 21 | 22 | func (engine *Engine) addRoute(method string, pattern string, handler HandlerFunc) { 23 | key := method + "-" + pattern 24 | log.Printf("Route %4s - %s", method, pattern) 25 | engine.router[key] = handler 26 | } 27 | 28 | // GET defines the method to add GET request 29 | func (engine *Engine) GET(pattern string, handler HandlerFunc) { 30 | engine.addRoute("GET", pattern, handler) 31 | } 32 | 33 | // POST defines the method to add POST request 34 | func (engine *Engine) POST(pattern string, handler HandlerFunc) { 35 | engine.addRoute("POST", pattern, handler) 36 | } 37 | 38 | // Run defines the method to start a http server 39 | func (engine *Engine) Run(addr string) (err error) { 40 | return http.ListenAndServe(addr, engine) 41 | } 42 | 43 | func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { 44 | key := req.Method + "-" + req.URL.Path 45 | if handler, ok := engine.router[key]; ok { 46 | handler(w, req) 47 | } else { 48 | fmt.Fprintf(w, "404 NOT FOUND: %s\n", req.URL) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /gee-web/day1-http-base/base3/gee/go.mod: -------------------------------------------------------------------------------- 1 | module gee 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gee-web/day1-http-base/base3/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.13 4 | 5 | require gee v0.0.0 6 | 7 | replace gee => ./gee 8 | -------------------------------------------------------------------------------- /gee-web/day1-http-base/base3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // $ curl http://localhost:9999/ 4 | // URL.Path = "/" 5 | // $ curl http://localhost:9999/hello 6 | // Header["Accept"] = ["*/*"] 7 | // Header["User-Agent"] = ["curl/7.54.0"] 8 | // curl http://localhost:9999/world 9 | // 404 NOT FOUND: /world 10 | 11 | import ( 12 | "fmt" 13 | "net/http" 14 | 15 | "gee" 16 | ) 17 | 18 | func main() { 19 | r := gee.New() 20 | r.GET("/", func(w http.ResponseWriter, req *http.Request) { 21 | fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path) 22 | }) 23 | 24 | r.GET("/hello", func(w http.ResponseWriter, req *http.Request) { 25 | for k, v := range req.Header { 26 | fmt.Fprintf(w, "Header[%q] = %q\n", k, v) 27 | } 28 | }) 29 | 30 | r.Run(":9999") 31 | } 32 | -------------------------------------------------------------------------------- /gee-web/day2-context/gee/gee.go: -------------------------------------------------------------------------------- 1 | package gee 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | ) 7 | 8 | // HandlerFunc defines the request handler used by gee 9 | type HandlerFunc func(*Context) 10 | 11 | // Engine implement the interface of ServeHTTP 12 | type Engine struct { 13 | router *router 14 | } 15 | 16 | // New is the constructor of gee.Engine 17 | func New() *Engine { 18 | return &Engine{router: newRouter()} 19 | } 20 | 21 | func (engine *Engine) addRoute(method string, pattern string, handler HandlerFunc) { 22 | log.Printf("Route %4s - %s", method, pattern) 23 | engine.router.addRoute(method, pattern, handler) 24 | } 25 | 26 | // GET defines the method to add GET request 27 | func (engine *Engine) GET(pattern string, handler HandlerFunc) { 28 | engine.addRoute("GET", pattern, handler) 29 | } 30 | 31 | // POST defines the method to add POST request 32 | func (engine *Engine) POST(pattern string, handler HandlerFunc) { 33 | engine.addRoute("POST", pattern, handler) 34 | } 35 | 36 | // Run defines the method to start a http server 37 | func (engine *Engine) Run(addr string) (err error) { 38 | return http.ListenAndServe(addr, engine) 39 | } 40 | 41 | func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { 42 | c := newContext(w, req) 43 | engine.router.handle(c) 44 | } 45 | -------------------------------------------------------------------------------- /gee-web/day2-context/gee/go.mod: -------------------------------------------------------------------------------- 1 | module gee 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gee-web/day2-context/gee/router.go: -------------------------------------------------------------------------------- 1 | package gee 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | type router struct { 8 | handlers map[string]HandlerFunc 9 | } 10 | 11 | func newRouter() *router { 12 | return &router{handlers: make(map[string]HandlerFunc)} 13 | } 14 | 15 | func (r *router) addRoute(method string, pattern string, handler HandlerFunc) { 16 | key := method + "-" + pattern 17 | r.handlers[key] = handler 18 | } 19 | 20 | func (r *router) handle(c *Context) { 21 | key := c.Method + "-" + c.Path 22 | if handler, ok := r.handlers[key]; ok { 23 | handler(c) 24 | } else { 25 | c.String(http.StatusNotFound, "404 NOT FOUND: %s\n", c.Path) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /gee-web/day2-context/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.13 4 | 5 | require gee v0.0.0 6 | 7 | replace gee => ./gee 8 | -------------------------------------------------------------------------------- /gee-web/day2-context/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | (1) 5 | $ curl -i http://localhost:9999/ 6 | HTTP/1.1 200 OK 7 | Date: Mon, 12 Aug 2019 16:52:52 GMT 8 | Content-Length: 18 9 | Content-Type: text/html; charset=utf-8 10 |

Hello Gee

11 | 12 | (2) 13 | $ curl "http://localhost:9999/hello?name=geektutu" 14 | hello geektutu, you're at /hello 15 | 16 | (3) 17 | $ curl "http://localhost:9999/login" -X POST -d 'username=geektutu&password=1234' 18 | {"password":"1234","username":"geektutu"} 19 | 20 | (4) 21 | $ curl "http://localhost:9999/xxx" 22 | 404 NOT FOUND: /xxx 23 | */ 24 | 25 | import ( 26 | "net/http" 27 | 28 | "gee" 29 | ) 30 | 31 | func main() { 32 | r := gee.New() 33 | r.GET("/", func(c *gee.Context) { 34 | c.HTML(http.StatusOK, "

Hello Gee

") 35 | }) 36 | r.GET("/hello", func(c *gee.Context) { 37 | // expect /hello?name=geektutu 38 | c.String(http.StatusOK, "hello %s, you're at %s\n", c.Query("name"), c.Path) 39 | }) 40 | 41 | r.POST("/login", func(c *gee.Context) { 42 | c.JSON(http.StatusOK, gee.H{ 43 | "username": c.PostForm("username"), 44 | "password": c.PostForm("password"), 45 | }) 46 | }) 47 | 48 | r.Run(":9999") 49 | } 50 | -------------------------------------------------------------------------------- /gee-web/day3-router/gee/gee.go: -------------------------------------------------------------------------------- 1 | package gee 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | ) 7 | 8 | // HandlerFunc defines the request handler used by gee 9 | type HandlerFunc func(*Context) 10 | 11 | // Engine implement the interface of ServeHTTP 12 | type Engine struct { 13 | router *router 14 | } 15 | 16 | // New is the constructor of gee.Engine 17 | func New() *Engine { 18 | return &Engine{router: newRouter()} 19 | } 20 | 21 | func (engine *Engine) addRoute(method string, pattern string, handler HandlerFunc) { 22 | log.Printf("Route %4s - %s", method, pattern) 23 | engine.router.addRoute(method, pattern, handler) 24 | } 25 | 26 | // GET defines the method to add GET request 27 | func (engine *Engine) GET(pattern string, handler HandlerFunc) { 28 | engine.addRoute("GET", pattern, handler) 29 | } 30 | 31 | // POST defines the method to add POST request 32 | func (engine *Engine) POST(pattern string, handler HandlerFunc) { 33 | engine.addRoute("POST", pattern, handler) 34 | } 35 | 36 | // Run defines the method to start a http server 37 | func (engine *Engine) Run(addr string) (err error) { 38 | return http.ListenAndServe(addr, engine) 39 | } 40 | 41 | func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { 42 | c := newContext(w, req) 43 | engine.router.handle(c) 44 | } 45 | -------------------------------------------------------------------------------- /gee-web/day3-router/gee/go.mod: -------------------------------------------------------------------------------- 1 | module gee 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gee-web/day3-router/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.13 4 | 5 | require gee v0.0.0 6 | 7 | replace gee => ./gee 8 | -------------------------------------------------------------------------------- /gee-web/day3-router/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | (1) 5 | $ curl -i http://localhost:9999/ 6 | HTTP/1.1 200 OK 7 | Date: Mon, 12 Aug 2019 16:52:52 GMT 8 | Content-Length: 18 9 | Content-Type: text/html; charset=utf-8 10 |

Hello Gee

11 | 12 | (2) 13 | $ curl "http://localhost:9999/hello?name=geektutu" 14 | hello geektutu, you're at /hello 15 | 16 | (3) 17 | $ curl "http://localhost:9999/hello/geektutu" 18 | hello geektutu, you're at /hello/geektutu 19 | 20 | (4) 21 | $ curl "http://localhost:9999/assets/css/geektutu.css" 22 | {"filepath":"css/geektutu.css"} 23 | 24 | (5) 25 | $ curl "http://localhost:9999/xxx" 26 | 404 NOT FOUND: /xxx 27 | */ 28 | 29 | import ( 30 | "net/http" 31 | 32 | "gee" 33 | ) 34 | 35 | func main() { 36 | r := gee.New() 37 | r.GET("/", func(c *gee.Context) { 38 | c.HTML(http.StatusOK, "

Hello Gee

") 39 | }) 40 | 41 | r.GET("/hello", func(c *gee.Context) { 42 | // expect /hello?name=geektutu 43 | c.String(http.StatusOK, "hello %s, you're at %s\n", c.Query("name"), c.Path) 44 | }) 45 | 46 | r.GET("/hello/:name", func(c *gee.Context) { 47 | // expect /hello/geektutu 48 | c.String(http.StatusOK, "hello %s, you're at %s\n", c.Param("name"), c.Path) 49 | }) 50 | 51 | r.GET("/assets/*filepath", func(c *gee.Context) { 52 | c.JSON(http.StatusOK, gee.H{"filepath": c.Param("filepath")}) 53 | }) 54 | 55 | r.Run(":9999") 56 | } 57 | -------------------------------------------------------------------------------- /gee-web/day4-group/gee/gee_test.go: -------------------------------------------------------------------------------- 1 | package gee 2 | 3 | import "testing" 4 | 5 | func TestNestedGroup(t *testing.T) { 6 | r := New() 7 | v1 := r.Group("/v1") 8 | v2 := v1.Group("/v2") 9 | v3 := v2.Group("/v3") 10 | if v2.prefix != "/v1/v2" { 11 | t.Fatal("v2 prefix should be /v1/v2") 12 | } 13 | if v3.prefix != "/v1/v2/v3" { 14 | t.Fatal("v2 prefix should be /v1/v2") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gee-web/day4-group/gee/go.mod: -------------------------------------------------------------------------------- 1 | module gee 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gee-web/day4-group/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.13 4 | 5 | require gee v0.0.0 6 | 7 | replace gee => ./gee 8 | -------------------------------------------------------------------------------- /gee-web/day5-middleware/gee/gee_test.go: -------------------------------------------------------------------------------- 1 | package gee 2 | 3 | import "testing" 4 | 5 | func TestNestedGroup(t *testing.T) { 6 | r := New() 7 | v1 := r.Group("/v1") 8 | v2 := v1.Group("/v2") 9 | v3 := v2.Group("/v3") 10 | if v2.prefix != "/v1/v2" { 11 | t.Fatal("v2 prefix should be /v1/v2") 12 | } 13 | if v3.prefix != "/v1/v2/v3" { 14 | t.Fatal("v2 prefix should be /v1/v2") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gee-web/day5-middleware/gee/go.mod: -------------------------------------------------------------------------------- 1 | module gee 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gee-web/day5-middleware/gee/logger.go: -------------------------------------------------------------------------------- 1 | package gee 2 | 3 | import ( 4 | "log" 5 | "time" 6 | ) 7 | 8 | func Logger() HandlerFunc { 9 | return func(c *Context) { 10 | // Start timer 11 | t := time.Now() 12 | // Process request 13 | c.Next() 14 | // Calculate resolution time 15 | log.Printf("[%d] %s in %v", c.StatusCode, c.Req.RequestURI, time.Since(t)) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /gee-web/day5-middleware/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.13 4 | 5 | require gee v0.0.0 6 | 7 | replace gee => ./gee 8 | -------------------------------------------------------------------------------- /gee-web/day5-middleware/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | (1) global middleware Logger 5 | $ curl http://localhost:9999/ 6 |

Hello Gee

7 | 8 | >>> log 9 | 2019/08/17 01:37:38 [200] / in 3.14µs 10 | */ 11 | 12 | /* 13 | (2) global + group middleware 14 | $ curl http://localhost:9999/v2/hello/geektutu 15 | {"message":"Internal Server Error"} 16 | 17 | >>> log 18 | 2019/08/17 01:38:48 [200] /v2/hello/geektutu in 61.467µs for group v2 19 | 2019/08/17 01:38:48 [200] /v2/hello/geektutu in 281µs 20 | */ 21 | 22 | import ( 23 | "log" 24 | "net/http" 25 | "time" 26 | 27 | "gee" 28 | ) 29 | 30 | func onlyForV2() gee.HandlerFunc { 31 | return func(c *gee.Context) { 32 | // Start timer 33 | t := time.Now() 34 | // if a server error occurred 35 | c.Fail(500, "Internal Server Error") 36 | // Calculate resolution time 37 | log.Printf("[%d] %s in %v for group v2", c.StatusCode, c.Req.RequestURI, time.Since(t)) 38 | } 39 | } 40 | 41 | func main() { 42 | r := gee.New() 43 | r.Use(gee.Logger()) // global midlleware 44 | r.GET("/", func(c *gee.Context) { 45 | c.HTML(http.StatusOK, "

Hello Gee

") 46 | }) 47 | 48 | v2 := r.Group("/v2") 49 | v2.Use(onlyForV2()) // v2 group middleware 50 | { 51 | v2.GET("/hello/:name", func(c *gee.Context) { 52 | // expect /hello/geektutu 53 | c.String(http.StatusOK, "hello %s, you're at %s\n", c.Param("name"), c.Path) 54 | }) 55 | } 56 | 57 | r.Run(":9999") 58 | } 59 | -------------------------------------------------------------------------------- /gee-web/day6-template/gee/gee_test.go: -------------------------------------------------------------------------------- 1 | package gee 2 | 3 | import "testing" 4 | 5 | func TestNestedGroup(t *testing.T) { 6 | r := New() 7 | v1 := r.Group("/v1") 8 | v2 := v1.Group("/v2") 9 | v3 := v2.Group("/v3") 10 | if v2.prefix != "/v1/v2" { 11 | t.Fatal("v2 prefix should be /v1/v2") 12 | } 13 | if v3.prefix != "/v1/v2/v3" { 14 | t.Fatal("v2 prefix should be /v1/v2") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gee-web/day6-template/gee/go.mod: -------------------------------------------------------------------------------- 1 | module gee 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gee-web/day6-template/gee/logger.go: -------------------------------------------------------------------------------- 1 | package gee 2 | 3 | import ( 4 | "log" 5 | "time" 6 | ) 7 | 8 | func Logger() HandlerFunc { 9 | return func(c *Context) { 10 | // Start timer 11 | t := time.Now() 12 | // Process request 13 | c.Next() 14 | // Calculate resolution time 15 | log.Printf("[%d] %s in %v", c.StatusCode, c.Req.RequestURI, time.Since(t)) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /gee-web/day6-template/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.13 4 | 5 | require gee v0.0.0 6 | 7 | replace gee => ./gee 8 | -------------------------------------------------------------------------------- /gee-web/day6-template/static/css/geektutu.css: -------------------------------------------------------------------------------- 1 | p { 2 | color: orange; 3 | font-weight: 700; 4 | font-size: 20px; 5 | } 6 | -------------------------------------------------------------------------------- /gee-web/day6-template/static/file1.txt: -------------------------------------------------------------------------------- 1 | I'm file1 2 | I'm file1 3 | I'm file1 4 | -------------------------------------------------------------------------------- /gee-web/day6-template/templates/arr.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

hello, {{.title}}

5 | {{range $index, $ele := .stuArr }} 6 |

{{ $index }}: {{ $ele.Name }} is {{ $ele.Age }} years old

7 | {{ end }} 8 | 9 | -------------------------------------------------------------------------------- /gee-web/day6-template/templates/css.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 |

geektutu.css is loaded

4 | -------------------------------------------------------------------------------- /gee-web/day6-template/templates/custom_func.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

hello, {{.title}}

5 |

Date: {{.now | FormatAsDate}}

6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /gee-web/day7-panic-recover/gee/gee_test.go: -------------------------------------------------------------------------------- 1 | package gee 2 | 3 | import "testing" 4 | 5 | func TestNestedGroup(t *testing.T) { 6 | r := New() 7 | v1 := r.Group("/v1") 8 | v2 := v1.Group("/v2") 9 | v3 := v2.Group("/v3") 10 | if v2.prefix != "/v1/v2" { 11 | t.Fatal("v2 prefix should be /v1/v2") 12 | } 13 | if v3.prefix != "/v1/v2/v3" { 14 | t.Fatal("v2 prefix should be /v1/v2") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gee-web/day7-panic-recover/gee/go.mod: -------------------------------------------------------------------------------- 1 | module gee 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gee-web/day7-panic-recover/gee/logger.go: -------------------------------------------------------------------------------- 1 | package gee 2 | 3 | import ( 4 | "log" 5 | "time" 6 | ) 7 | 8 | func Logger() HandlerFunc { 9 | return func(c *Context) { 10 | // Start timer 11 | t := time.Now() 12 | // Process request 13 | c.Next() 14 | // Calculate resolution time 15 | log.Printf("[%d] %s in %v", c.StatusCode, c.Req.RequestURI, time.Since(t)) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /gee-web/day7-panic-recover/gee/recovery.go: -------------------------------------------------------------------------------- 1 | package gee 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "runtime" 8 | "strings" 9 | ) 10 | 11 | // print stack trace for debug 12 | func trace(message string) string { 13 | var pcs [32]uintptr 14 | n := runtime.Callers(3, pcs[:]) // skip first 3 caller 15 | 16 | var str strings.Builder 17 | str.WriteString(message + "\nTraceback:") 18 | for _, pc := range pcs[:n] { 19 | fn := runtime.FuncForPC(pc) 20 | file, line := fn.FileLine(pc) 21 | str.WriteString(fmt.Sprintf("\n\t%s:%d", file, line)) 22 | } 23 | return str.String() 24 | } 25 | 26 | func Recovery() HandlerFunc { 27 | return func(c *Context) { 28 | defer func() { 29 | if err := recover(); err != nil { 30 | message := fmt.Sprintf("%s", err) 31 | log.Printf("%s\n\n", trace(message)) 32 | c.Fail(http.StatusInternalServerError, "Internal Server Error") 33 | } 34 | }() 35 | 36 | c.Next() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /gee-web/day7-panic-recover/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.13 4 | 5 | require gee v0.0.0 6 | 7 | replace gee => ./gee 8 | -------------------------------------------------------------------------------- /gee-web/doc/gee-day3/trie_eg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-web/doc/gee-day3/trie_eg.jpg -------------------------------------------------------------------------------- /gee-web/doc/gee-day3/trie_router.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-web/doc/gee-day3/trie_router.jpg -------------------------------------------------------------------------------- /gee-web/doc/gee-day4/group.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-web/doc/gee-day4/group.jpg -------------------------------------------------------------------------------- /gee-web/doc/gee-day5/middleware.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-web/doc/gee-day5/middleware.jpg -------------------------------------------------------------------------------- /gee-web/doc/gee-day6/html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-web/doc/gee-day6/html.png -------------------------------------------------------------------------------- /gee-web/doc/gee-day6/static.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-web/doc/gee-day6/static.jpg -------------------------------------------------------------------------------- /gee-web/doc/gee-day7/go-panic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-web/doc/gee-day7/go-panic.png -------------------------------------------------------------------------------- /gee-web/doc/gee/gee.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-web/doc/gee/gee.jpg -------------------------------------------------------------------------------- /questions/7days-golang-q1/7days-golang-qa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/7days-golang/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/questions/7days-golang-q1/7days-golang-qa.jpg --------------------------------------------------------------------------------