├── .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 | Method | Calls |
22 | {{range $name, $mtype := .Method}}
23 |
24 | {{$name}}({{$mtype.ArgType}}, {{$mtype.ReplyType}}) error |
25 | {{$mtype.NumCalls}} |
26 |
27 | {{end}}
28 |
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 | Method | Calls |
22 | {{range $name, $mtype := .Method}}
23 |
24 | {{$name}}({{$mtype.ArgType}}, {{$mtype.ReplyType}}) error |
25 | {{$mtype.NumCalls}} |
26 |
27 | {{end}}
28 |
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 | Method | Calls |
22 | {{range $name, $mtype := .Method}}
23 |
24 | {{$name}}({{$mtype.ArgType}}, {{$mtype.ReplyType}}) error |
25 | {{$mtype.NumCalls}} |
26 |
27 | {{end}}
28 |
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
--------------------------------------------------------------------------------