Compare commits

...

120 Commits

Author SHA1 Message Date
Kevin Wan
78ea0769fd feat: simplify httpc (#1748)
* feat: simplify httpc

* chore: fix lint errors

* chore: fix log url issue

* chore: fix log url issue

* refactor: handle resp & err in ResponseHandler

* chore: remove unnecessary var names in return clause
2022-04-03 14:32:27 +08:00
Kevin Wan
e0fa8d820d feat: return original value of setbit in redis (#1746) 2022-04-02 20:25:51 +08:00
Kevin Wan
dfd58c213c fix: model generation bug on with cache (#1743)
* fix: model generation bug on with cache

* chore: refine template

* chore: fix test failure
2022-04-02 15:36:06 +08:00
Kevin Wan
83cacf51b7 chore: update goctl version to 1.3.4 (#1742) 2022-04-02 14:19:34 +08:00
Kevin Wan
6dccfa29fd feat: let model customizable (#1738) 2022-04-01 22:19:52 +08:00
anqiansong
7e0b0ab0b1 Fix zrpc code generation error with --remote (#1739)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-04-01 22:19:33 +08:00
Kevin Wan
ac18cc470d chore: refactor to use const instead of var (#1731) 2022-04-01 15:23:45 +08:00
Fyn
f4471846ff feat(goctl): supports model code 'DO NOT EDIT' (#1728)
Resolves: #1710
2022-04-01 14:48:45 +08:00
anqiansong
9c2d526a11 Fix unit test (#1730)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-04-01 14:46:12 +08:00
Kevin Wan
2b9fc26c38 refactor: guard timeout on API files (#1726) 2022-03-31 21:39:02 +08:00
Xiaoju Jiang
321dc2d410 Added support for setting the parameter size accepted by the interface and custom timeout and maxbytes in API file (#1713)
* Added support for setting the parameter size accepted by the interface

* support custom timeout and maxbytes in API file

* support timeout used unit

* remove goctl maxBytes
2022-03-31 20:20:00 +08:00
Fyn
500bd87c85 fix(goctl): api format with reader input (#1722)
resolves #1721
2022-03-31 00:20:51 +08:00
Kevin Wan
e9620c8c05 chore: refactor code (#1708) 2022-03-24 22:10:15 +08:00
aimuz
70e51bb352 fix: empty slice are set to nil (#1702)
support for empty slce, Same behavior as json.Unmarshal
2022-03-24 21:41:38 +08:00
Kevin Wan
278cd123c8 feat: remove reentrance in redislock, timeout bug (#1704) 2022-03-24 16:17:01 +08:00
Kevin Wan
3febb1a5d0 chore: refactor code (#1700) 2022-03-23 19:09:45 +08:00
Mikael
d8054d8def fix -cache=true insert no clean cache (#1672)
* fix -cache=true insert no clean cache

* fix -cache=true insert no clean cache
2022-03-23 18:55:16 +08:00
Kevin Wan
ec271db7a0 chore: refactor code (#1699) 2022-03-23 18:24:44 +08:00
benqi
bbac994c8a feat: add getset command in redis and kv (#1693) 2022-03-23 18:02:56 +08:00
Kevin Wan
c1d9e6a00b feat: add httpc.Parse (#1698) 2022-03-23 17:58:21 +08:00
anqiansong
0aeb49a6b0 Add verbose flag (#1696)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-03-22 21:00:26 +08:00
Kevin Wan
fe262766b4 chore: fix lint issue (#1694) 2022-03-22 13:31:05 +08:00
Kevin Wan
7181505c8a Update LICENSE 2022-03-21 10:32:41 +08:00
Kevin Wan
f060a226bc refactor: simplify the code (#1670) 2022-03-20 17:26:12 +08:00
Mervin.Wong
93d524b797 fix: the new RawFieldNames considers the tag with options. (#1663)
Co-authored-by: JinfaWang <wangjinfa@iie.ac.cn>
2022-03-20 16:59:19 +08:00
anqiansong
5c169f4f49 Remove debug log (#1669)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-03-20 16:28:36 +08:00
Kevin Wan
d29dfa12e3 feat: support -base to specify base image for goctl docker (#1668)
* feat: support -base to specify base image for goctl docker

* chore: update usage
2022-03-20 11:17:55 +08:00
anqiansong
194f55e08e Remove unused code (#1667)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-03-19 23:15:11 +08:00
Kevin Wan
c0f9892fe3 feat: add Dockerfile for goctl (#1666) 2022-03-19 23:07:17 +08:00
anqiansong
227104d7d7 feat: Remove command goctl rpc proto (#1665)
* Fix goctl completion expression

* Fix code generation error if the pkg of pb/grpc is same to zrpc call client pkg

* Remove deprecated comment on action goctl rpc new

* Remove zrpc code generation on action goctl rpc proto

* Remove zrpc code generation on action goctl rpc proto

* Remove Generator interface

Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-03-19 22:50:22 +08:00
anqiansong
448029aa4b Mkdir if not exists (#1659)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-03-17 21:44:22 +08:00
Fyn
17e0afeac0 fix(goctl): model method FindOneCtx should be FindOne (#1656) 2022-03-17 17:16:53 +08:00
ronething-bot
18916b5189 [fix] typo (#1655) 2022-03-17 10:00:29 +08:00
Kevin Wan
c11a09be23 chore: remove unnecessary env (#1654) 2022-03-16 17:31:10 +08:00
ronething-bot
56e1ecf2f3 fix: typo (#1646) 2022-03-15 17:46:13 +08:00
Kevin Wan
f9e6013a6c refactor: httpc package for easy to use (#1645) 2022-03-15 14:18:46 +08:00
Kevin Wan
b5d1d8b0d1 refactor: httpc package for easy to use (#1643) 2022-03-14 20:15:14 +08:00
xybingbing
09e6d94f9e FindOneBy 漏 Context (#1642) 2022-03-14 18:56:26 +08:00
Kevin Wan
2a5717d7fb feat: add httpc/Service for convinience (#1641) 2022-03-14 15:36:06 +08:00
Kevin Wan
85cf662c6f feat: add httpc/Get httpc/Post (#1640) 2022-03-13 14:49:14 +08:00
Kevin Wan
3279a7ef0f feat: add rest/httpc to make http requests governacible (#1638)
* feat: change x-trace-id to traceparent to follow opentelemetry

* feat: add rest/httpc to make http requests governacible

* chore: remove blank lines
2022-03-13 14:11:14 +08:00
Kevin Wan
fec908a19b Update ROADMAP.md
update roadmap.
2022-03-13 14:09:11 +08:00
Kevin Wan
f5ed0cda58 Update ROADMAP.md
update roadmap.
2022-03-13 14:08:28 +08:00
anqiansong
cc9d16f505 fix: Update unix-like path regex (#1637)
* Revert import value regex

* Update linux path regex

Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-03-12 22:21:17 +08:00
Kevin Wan
c05d74b44c feat: support cpu stat on cgroups v2 (#1636)
* feat: cpu stat

* feat: add cpu stat for cgroup2

* feat: add cpu stat for cgroup2, tidy mod

* feat: support cpu stat in cgroup v2
2022-03-12 21:00:04 +08:00
mlr3000
32c88b6352 feat: support oracle :N dynamic parameters (#1552)
* chore:use struct pointer

* feat: support oracle :N dynamic parameters

* Update utils.go

* Update utils.go

* Update utils.go

pg argIndex will not always go up

* Update utils_test.go

* Keep the original

* Update utils_test.go
2022-03-12 18:49:07 +08:00
Kevin Wan
7dabec260f Update readme-cn.md
update readme.
2022-03-12 16:09:18 +08:00
Kevin Wan
4feb88f9b5 Update readme-cn.md
update readme.
2022-03-12 15:23:52 +08:00
Kevin Wan
2776caed0e Update readme.md
update readme.
2022-03-12 15:19:51 +08:00
chensy
c55694d957 Support for referencing types in different API files using format (#1630) 2022-03-12 15:17:31 +08:00
Ziyi Zhang
209ffb934b fix(goctl): kotlin code generation (#1632)
Signed-off-by: Ziyi Zhang <soasurs@gmail.com>
2022-03-11 13:44:18 +08:00
Kevin Wan
26a33932cd feat: support scratch as the base docker image (#1634) 2022-03-11 12:15:38 +08:00
Kevin Wan
d6a692971f chore: reduce the docker image size (#1633)
* chore: reduce the docker image size

* chore: format dockerfile
2022-03-11 11:30:21 +08:00
anqiansong
4624390e54 Fix #1585 #1547 (#1624) 2022-03-09 19:26:35 +08:00
Kevin Wan
63b7d292c1 chore: update goctl version to 1.3.3, change docker build temp dir (#1621) 2022-03-07 14:44:12 +08:00
Fyn
365c569d7c fix(goctl): dart gen user defined struct array (#1620) 2022-03-07 14:11:47 +08:00
anqiansong
68a81fea8a Fix #1609 (#1617) 2022-03-05 22:52:32 +08:00
anqiansong
08a8bd7ef7 Fix #1614 (#1616) 2022-03-05 21:40:41 +08:00
Kevin Wan
b939ce75ba chore: refactor code (#1613) 2022-03-04 17:55:13 +08:00
Kevin Wan
3b7ca86e4f chore: add unit tests (#1615)
* test: add more tests

* test: add more tests
2022-03-04 17:54:09 +08:00
Javen
60760b52ab model中db标签增加'-'符号以支持数据库查询时忽略对应字段. (#1612) 2022-03-04 17:00:46 +08:00
qi
96c128c58a fix: HitQuota should be returned instead of Allowed when limit is equal to 1. (#1581) 2022-03-04 16:14:45 +08:00
Fyn
0c35f39a7d fix: fix(gctl): apiparser_parser auto format (#1607) 2022-03-04 15:36:20 +08:00
Fyn
6a66dde0a1 feat(goctl): api dart support flutter v2 (#1603)
0. support null-safety code gen
1. supports -legacy flag for legacy code gen
2. supports -hostname flag for server hostname
3. use dart official format
4. fix some some bugs

Resolves: #1602
2022-03-04 15:34:13 +08:00
Kevin Wan
36b9fcba44 Update readme-cn.md 2022-03-03 14:35:48 +08:00
Kevin Wan
bf99dda620 Update readme-cn.md 2022-03-03 14:35:10 +08:00
Kevin Wan
511dfcb409 Update readme.md 2022-03-03 14:34:34 +08:00
Kevin Wan
900bc96420 test: add more tests (#1604) 2022-03-02 21:19:04 +08:00
Kevin Wan
be277a7376 Update readme-cn.md
add go-zero users.
2022-03-02 21:18:31 +08:00
Kevin Wan
f15a4f9188 chore: update go-zero to v1.3.1 in goctl (#1599) 2022-03-01 20:56:57 +08:00
Kevin Wan
e31128650e Revert "🐞 fix(gen): pg gen of insert (#1591)" (#1598)
This reverts commit cc4c4928e0.
2022-03-01 20:27:59 +08:00
Kevin Wan
168740b64d chore: upgrade etcd (#1597) 2022-03-01 20:16:44 +08:00
toutou_o
cc4c4928e0 🐞 fix(gen): pg gen of insert (#1591)
Co-authored-by: kurimi1 <d0n41df@gmail.com>
2022-03-01 19:53:23 +08:00
Fyn
fba6543b23 fix: goctl api dart support form tag (#1596) 2022-03-01 16:17:37 +08:00
Kevin Wan
877eb6ac56 Update readme.md
add producthunt.
2022-03-01 16:11:17 +08:00
Kevin Wan
259a5a13e7 chore: fix data race (#1593) 2022-02-28 23:17:51 +08:00
Fyn
cf7c7cb392 build: update goctl dependency ddl-parser to v1.0.3 (#1586)
* build: update goctl dependency ddl-parser to v1.0.3

* fix: race condition when testing logx

Resolves: #1587
2022-02-28 17:31:59 +08:00
ccx
86d01e2e99 test: add testcase for FIFO Queue in collection module (#1589)
cover the case of non-zero value for q.Header when q.Elements expands
2022-02-28 17:15:11 +08:00
Kevin Wan
7a28e19a27 Update readme-cn.md 2022-02-27 23:30:14 +08:00
Kevin Wan
900ea63d68 Update readme-cn.md
add migration notice.
2022-02-27 23:29:45 +08:00
Kevin Wan
87ab86cdd0 Update readme.md 2022-02-27 23:26:03 +08:00
Kevin Wan
0697494ffd Update readme.md
Add migrate steps.
2022-02-27 23:25:22 +08:00
anqiansong
ffd69a2f5e Fix bug int overflow while build goctl on arch 386 (#1582)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-02-27 10:51:57 +08:00
Kevin Wan
66f10bb5e6 chore: add goctl command help (#1578) 2022-02-26 17:02:04 +08:00
Kevin Wan
8131a0e777 Update readme.md
update chat link.
2022-02-25 23:02:09 +08:00
Kevin Wan
32a557dff6 Update readme.md
add discord.
2022-02-25 23:01:15 +08:00
Fyn
db949e40f1 feat: supports importValue for more path formats (#1569)
`importValueRegex` now can match more path formats

Resolves: #1568
2022-02-25 11:16:57 +08:00
Kevin Wan
e0454138e0 update goctl to go 1.16 for io/fs usage (#1571)
* update goctl to go 1.16 for io/fs usage

* feat: support pg serial type for auto_increment (#1563)

* add correct example for pg's url

* 🐞 fix: merge

* 🐞 fix: pg default port

*  feat: support serial type

Co-authored-by: kurimi1 <d0n41df@gmail.com>

* chore: format code

Co-authored-by: toutou_o <33993460+kurimi1@users.noreply.github.com>
Co-authored-by: kurimi1 <d0n41df@gmail.com>
2022-02-24 13:58:53 +08:00
toutou_o
3b07ed1b97 feat: support pg serial type for auto_increment (#1563)
* add correct example for pg's url

* 🐞 fix: merge

* 🐞 fix: pg default port

*  feat: support serial type

Co-authored-by: kurimi1 <d0n41df@gmail.com>
2022-02-24 13:39:31 +08:00
anqiansong
daa98f5a27 Feature: Add goctl env (#1557) 2022-02-21 10:19:33 +08:00
Kevin Wan
842656aa90 feat: log 404 requests with traceid (#1554) 2022-02-19 20:50:33 +08:00
Kevin Wan
aa29036cb3 feat: support ctx in sql model generation (#1551) 2022-02-17 10:28:55 +08:00
Kevin Wan
607bae27fa feat: support ctx in sqlx/sqlc, listed in ROADMAP (#1535)
* feat: support ctx in sqlx/sqlc

* chore: update roadmap

* fix: context.Canceled should be acceptable

* use %w to wrap errors

* chore: remove unused vars
2022-02-16 19:31:43 +08:00
Kevin Wan
7c63676be4 docs: add go-zero users (#1546) 2022-02-16 12:02:52 +08:00
Kevin Wan
9e113909b3 ignore context.Canceled for redis breaker (#1545) 2022-02-15 21:31:30 +08:00
Kevin Wan
bd105474ca chore: update help message (#1544) 2022-02-15 21:19:40 +08:00
Mikael
a078f5d764 add the serviceAccount of deployment (#1543)
Co-authored-by: 977231903@qq.com <>
2022-02-15 20:57:14 +08:00
Kevin Wan
b215fa3ee6 fix #1541 (#1542) 2022-02-15 18:40:26 +08:00
mlr3000
50b1928502 chore:use struct pointer (#1538) 2022-02-15 11:34:48 +08:00
Kevin Wan
493e3bcf4b docs: update roadmap (#1537) 2022-02-15 08:37:03 +08:00
Kevin Wan
6deb80625d fix issue of default migrate version (#1536)
* fix issue of default migrate version

* chore: update console colors
2022-02-14 23:09:32 +08:00
Kevin Wan
6ab051568c Update readme-cn.md
add go-zero users
2022-02-14 16:57:48 +08:00
Kevin Wan
2732d3cdae chore: refactor cache (#1532) 2022-02-13 18:04:31 +08:00
chenquan
e8c307e4dc feat: support ctx in Cache (#1518)
* feature: support ctx in `Cache`

Signed-off-by: chenquan <chenquan.dev@foxmail.com>

* fix: `errors.Is` instead of `=`

Signed-off-by: chenquan <chenquan.dev@foxmail.com>
2022-02-13 17:28:14 +08:00
Kevin Wan
84ddc660c4 chore: goctl format issue (#1531) 2022-02-13 13:17:19 +08:00
Kevin Wan
e60e707955 upgrade grpc version (#1530) 2022-02-12 23:58:41 +08:00
Kevin Wan
cf4321b2d0 fix #1525 (#1527) 2022-02-11 23:04:57 +08:00
chenquan
1993faf2f8 fix: fix a typo (#1522)
Signed-off-by: chenquan <chenquan.dev@foxmail.com>
2022-02-11 21:15:45 +08:00
Kevin Wan
0ce85376bf chore: update goctl version to 1.3.2 (#1524) 2022-02-11 21:02:50 +08:00
Kevin Wan
a40254156f refactor: refactor yaml unmarshaler (#1517) 2022-02-09 17:22:52 +08:00
chenquan
05cc62f5ff chore: optimize yaml unmarshaler (#1513) 2022-02-09 16:57:00 +08:00
chenquan
9c2c90e533 chore: make error clearer (#1514) 2022-02-09 14:40:05 +08:00
Kevin Wan
822ee2e1c5 feat: update go-redis to v8, support ctx in redis methods (#1507)
* feat: update go-redis to v8, support ctx in redis methods

* fix compile errors

* chore: remove unused const

* chore: add tracing log on redis
2022-02-09 11:06:06 +08:00
anqiansong
77482c8946 fixes typo (#1511)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-02-08 22:16:38 +08:00
Kevin Wan
7ef0ab3119 Update readme-cn.md 2022-02-08 11:47:33 +08:00
anqiansong
8bd89a297a feature: Add goctl completion (#1505)
* feature: Add `goctl completion`

* Update const

Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-02-08 10:50:21 +08:00
Kevin Wan
bb75cc796e test: change fuzz tests (#1504) 2022-02-05 09:44:01 +08:00
Kevin Wan
0fdd8f54eb ci: add test for win (#1503)
* ci: add test for win

* ci: update check names

* ci: use go build instead of go test to verify win test

* fix: windows test failure

* chore: disable logs in tests
2022-02-05 00:06:23 +08:00
anqiansong
b1ffc464cd fix typo: goctl protoc usage (#1502)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-02-03 22:13:02 +08:00
Kevin Wan
50174960e4 chore: update command comment (#1501) 2022-02-02 22:02:08 +08:00
238 changed files with 6554 additions and 1943 deletions

View File

@@ -7,32 +7,50 @@ on:
branches: [ master ]
jobs:
build:
name: Build
test-linux:
name: Linux
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: ^1.15
id: go
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: ^1.15
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Get dependencies
run: |
go get -v -t -d ./...
- name: Get dependencies
run: |
go get -v -t -d ./...
- name: Lint
run: |
go vet -stdmethods=false $(go list ./...)
go install mvdan.cc/gofumpt@latest
test -z "$(gofumpt -s -l -extra .)" || echo "Please run 'gofumpt -l -w -extra .'"
- name: Lint
run: |
go vet -stdmethods=false $(go list ./...)
go install mvdan.cc/gofumpt@latest
test -z "$(gofumpt -s -l -extra .)" || echo "Please run 'gofumpt -l -w -extra .'"
- name: Test
run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
- name: Test
run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
- name: Codecov
uses: codecov/codecov-action@v2
- name: Codecov
uses: codecov/codecov-action@v2
test-win:
name: Windows
runs-on: windows-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: ^1.15
- name: Checkout codebase
uses: actions/checkout@v2
- name: Test
run: |
go mod verify
go mod download
go test -v -race ./...
cd tools/goctl && go build -v goctl.go

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020 xiaoheiban_server_go
Copyright (c) 2022 zeromicro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -20,9 +20,9 @@ We hope that the items listed below will inspire further engagement from the com
- [x] Support `goctl bug` to report bugs conveniently
## 2022
- [ ] Support `goctl mock` command to start a mocking server with given `.api` file
- [ ] Add `httpx.Client` with governance, like circuit breaker etc.
- [ ] Support `goctl doctor` command to report potential issues for given service
- [ ] Support `context` in redis related methods for timeout and tracing
- [ ] Support `context` in sql related methods for timeout and tracing
- [x] Support `context` in redis related methods for timeout and tracing
- [x] Support `context` in sql related methods for timeout and tracing
- [ ] Support `context` in mongodb related methods for timeout and tracing
- [x] Add `httpc.Do` with HTTP call governance, like circuit breaker etc.
- [ ] Support `goctl doctor` command to report potential issues for given service
- [ ] Support `goctl mock` command to start a mocking server with given `.api` file

View File

@@ -171,7 +171,7 @@ func (lt loggedThrottle) allow() (Promise, error) {
func (lt loggedThrottle) doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error {
return lt.logError(lt.internalThrottle.doReq(req, fallback, func(err error) bool {
accept := acceptable(err)
if !accept {
if !accept && err != nil {
lt.errWin.add(err.Error())
}
return accept

View File

@@ -61,3 +61,41 @@ func TestPutMore(t *testing.T) {
assert.Equal(t, string(element), string(body.([]byte)))
}
}
func TestPutMoreWithHeaderNotZero(t *testing.T) {
elements := [][]byte{
[]byte("hello"),
[]byte("world"),
[]byte("again"),
}
queue := NewQueue(4)
for i := range elements {
queue.Put(elements[i])
}
// take 1
body, ok := queue.Take()
assert.True(t, ok)
element, ok := body.([]byte)
assert.True(t, ok)
assert.Equal(t, element, []byte("hello"))
// put more
queue.Put([]byte("b4"))
queue.Put([]byte("b5")) // will store in elements[0]
queue.Put([]byte("b6")) // cause expansion
results := [][]byte{
[]byte("world"),
[]byte("again"),
[]byte("b4"),
[]byte("b5"),
[]byte("b6"),
}
for _, element := range results {
body, ok := queue.Take()
assert.True(t, ok)
assert.Equal(t, string(element), string(body.([]byte)))
}
}

View File

@@ -2,6 +2,13 @@ package discov
import "errors"
var (
// errEmptyEtcdHosts indicates that etcd hosts are empty.
errEmptyEtcdHosts = errors.New("empty etcd hosts")
// errEmptyEtcdKey indicates that etcd key is empty.
errEmptyEtcdKey = errors.New("empty etcd key")
)
// EtcdConf is the config item with the given key on etcd.
type EtcdConf struct {
Hosts []string
@@ -27,9 +34,9 @@ func (c EtcdConf) HasTLS() bool {
// Validate validates c.
func (c EtcdConf) Validate() error {
if len(c.Hosts) == 0 {
return errors.New("empty etcd hosts")
return errEmptyEtcdHosts
} else if len(c.Key) == 0 {
return errors.New("empty etcd key")
return errEmptyEtcdKey
} else {
return nil
}

View File

@@ -11,10 +11,12 @@ type (
errorArray []error
)
// Add adds err to be.
func (be *BatchError) Add(err error) {
if err != nil {
be.errs = append(be.errs, err)
// Add adds errs to be, nil errors are ignored.
func (be *BatchError) Add(errs ...error) {
for _, err := range errs {
if err != nil {
be.errs = append(be.errs, err)
}
}
}

View File

@@ -5,6 +5,9 @@ import (
"os"
)
// errExceedFileSize indicates that the file size is exceeded.
var errExceedFileSize = errors.New("exceed file size")
// A RangeReader is used to read a range of content from a file.
type RangeReader struct {
file *os.File
@@ -29,7 +32,7 @@ func (rr *RangeReader) Read(p []byte) (n int, err error) {
}
if rr.stop < rr.start || rr.start >= stat.Size() {
return 0, errors.New("exceed file size")
return 0, errExceedFileSize
}
if rr.stop-rr.start < int64(len(p)) {

View File

@@ -51,5 +51,5 @@ func unmarshalUseNumber(decoder *json.Decoder, v interface{}) error {
}
func formatError(v string, err error) error {
return fmt.Errorf("string: `%s`, error: `%s`", v, err.Error())
return fmt.Errorf("string: `%s`, error: `%w`", v, err)
}

View File

@@ -14,8 +14,8 @@ local window = tonumber(ARGV[2])
local current = redis.call("INCRBY", KEYS[1], 1)
if current == 1 then
redis.call("expire", KEYS[1], window)
return 1
elseif current < limit then
end
if current < limit then
return 1
elseif current == limit then
return 2

View File

@@ -23,10 +23,9 @@ func TestPeriodLimit_RedisUnavailable(t *testing.T) {
const (
seconds = 1
total = 100
quota = 5
)
l := NewPeriodLimit(seconds, quota, redis.NewRedis(s.Addr(), redis.NodeType), "periodlimit")
l := NewPeriodLimit(seconds, quota, redis.New(s.Addr()), "periodlimit")
s.Close()
val, err := l.Take("first")
assert.NotNil(t, err)
@@ -66,3 +65,13 @@ func testPeriodLimit(t *testing.T, opts ...PeriodOption) {
assert.Equal(t, 1, hitQuota)
assert.Equal(t, total-quota, overQuota)
}
func TestQuotaFull(t *testing.T) {
s, err := miniredis.Run()
assert.Nil(t, err)
l := NewPeriodLimit(1, 1, redis.New(s.Addr()), "periodlimit")
val, err := l.Take("first")
assert.Nil(t, err)
assert.Equal(t, HitQuota, val)
}

View File

@@ -3,6 +3,7 @@ package logx
import (
"fmt"
"io"
"sync/atomic"
"time"
"github.com/zeromicro/go-zero/core/timex"
@@ -79,7 +80,7 @@ func (l *durationLogger) WithDuration(duration time.Duration) Logger {
}
func (l *durationLogger) write(writer io.Writer, level string, val interface{}) {
switch encoding {
switch atomic.LoadUint32(&encoding) {
case plainEncodingType:
writePlainAny(writer, level, val, l.Duration)
default:

View File

@@ -3,6 +3,7 @@ package logx
import (
"log"
"strings"
"sync/atomic"
"testing"
"time"
@@ -38,10 +39,10 @@ func TestWithDurationInfo(t *testing.T) {
}
func TestWithDurationInfoConsole(t *testing.T) {
old := encoding
encoding = plainEncodingType
old := atomic.LoadUint32(&encoding)
atomic.StoreUint32(&encoding, plainEncodingType)
defer func() {
encoding = old
atomic.StoreUint32(&encoding, old)
}()
var builder strings.Builder

View File

@@ -72,10 +72,10 @@ var (
// ErrLogServiceNameNotSet is an error that indicates that the service name is not set.
ErrLogServiceNameNotSet = errors.New("log service name must be set")
timeFormat = "2006-01-02T15:04:05.000Z07"
timeFormat = "2006-01-02T15:04:05.000Z07:00"
writeConsole bool
logLevel uint32
encoding = jsonEncodingType
encoding uint32 = jsonEncodingType
// use uint32 for atomic operations
disableStat uint32
infoLog io.WriteCloser
@@ -137,9 +137,9 @@ func SetUp(c LogConf) error {
}
switch c.Encoding {
case plainEncoding:
encoding = plainEncodingType
atomic.StoreUint32(&encoding, plainEncodingType)
default:
encoding = jsonEncodingType
atomic.StoreUint32(&encoding, jsonEncodingType)
}
switch c.Mode {
@@ -424,7 +424,7 @@ func infoTextSync(msg string) {
}
func outputAny(writer io.Writer, level string, val interface{}) {
switch encoding {
switch atomic.LoadUint32(&encoding) {
case plainEncodingType:
writePlainAny(writer, level, val)
default:
@@ -438,7 +438,7 @@ func outputAny(writer io.Writer, level string, val interface{}) {
}
func outputText(writer io.Writer, level, msg string) {
switch encoding {
switch atomic.LoadUint32(&encoding) {
case plainEncodingType:
writePlainText(writer, level, msg)
default:

View File

@@ -145,10 +145,10 @@ func TestStructedLogInfoConsoleAny(t *testing.T) {
doTestStructedLogConsole(t, func(writer io.WriteCloser) {
infoLog = writer
}, func(v ...interface{}) {
old := encoding
encoding = plainEncodingType
old := atomic.LoadUint32(&encoding)
atomic.StoreUint32(&encoding, plainEncodingType)
defer func() {
encoding = old
atomic.StoreUint32(&encoding, old)
}()
Infov(v)
@@ -159,10 +159,10 @@ func TestStructedLogInfoConsoleAnyString(t *testing.T) {
doTestStructedLogConsole(t, func(writer io.WriteCloser) {
infoLog = writer
}, func(v ...interface{}) {
old := encoding
encoding = plainEncodingType
old := atomic.LoadUint32(&encoding)
atomic.StoreUint32(&encoding, plainEncodingType)
defer func() {
encoding = old
atomic.StoreUint32(&encoding, old)
}()
Infov(fmt.Sprint(v...))
@@ -173,10 +173,10 @@ func TestStructedLogInfoConsoleAnyError(t *testing.T) {
doTestStructedLogConsole(t, func(writer io.WriteCloser) {
infoLog = writer
}, func(v ...interface{}) {
old := encoding
encoding = plainEncodingType
old := atomic.LoadUint32(&encoding)
atomic.StoreUint32(&encoding, plainEncodingType)
defer func() {
encoding = old
atomic.StoreUint32(&encoding, old)
}()
Infov(errors.New(fmt.Sprint(v...)))
@@ -187,10 +187,10 @@ func TestStructedLogInfoConsoleAnyStringer(t *testing.T) {
doTestStructedLogConsole(t, func(writer io.WriteCloser) {
infoLog = writer
}, func(v ...interface{}) {
old := encoding
encoding = plainEncodingType
old := atomic.LoadUint32(&encoding)
atomic.StoreUint32(&encoding, plainEncodingType)
defer func() {
encoding = old
atomic.StoreUint32(&encoding, old)
}()
Infov(ValStringer{
@@ -203,10 +203,10 @@ func TestStructedLogInfoConsoleText(t *testing.T) {
doTestStructedLogConsole(t, func(writer io.WriteCloser) {
infoLog = writer
}, func(v ...interface{}) {
old := encoding
encoding = plainEncodingType
old := atomic.LoadUint32(&encoding)
atomic.StoreUint32(&encoding, plainEncodingType)
defer func() {
encoding = old
atomic.StoreUint32(&encoding, old)
}()
Info(fmt.Sprint(v...))

View File

@@ -29,9 +29,9 @@ func TestRedirector(t *testing.T) {
}
func captureOutput(f func()) string {
atomic.StoreUint32(&initialized, 1)
writer := new(mockWriter)
infoLog = writer
atomic.StoreUint32(&initialized, 1)
prevLevel := atomic.LoadUint32(&logLevel)
SetLevel(InfoLevel)
@@ -44,5 +44,9 @@ func captureOutput(f func()) string {
func getContent(jsonStr string) string {
var entry logEntry
json.Unmarshal([]byte(jsonStr), &entry)
return entry.Content.(string)
val, ok := entry.Content.(string)
if ok {
return val
}
return ""
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"io"
"sync/atomic"
"time"
"github.com/zeromicro/go-zero/core/timex"
@@ -80,7 +81,7 @@ func (l *traceLogger) write(writer io.Writer, level string, val interface{}) {
traceID := traceIdFromContext(l.ctx)
spanID := spanIdFromContext(l.ctx)
switch encoding {
switch atomic.LoadUint32(&encoding) {
case plainEncodingType:
writePlainAny(writer, level, val, l.Duration, traceID, spanID)
default:

View File

@@ -83,10 +83,10 @@ func TestTraceInfo(t *testing.T) {
}
func TestTraceInfoConsole(t *testing.T) {
old := encoding
encoding = plainEncodingType
old := atomic.LoadUint32(&encoding)
atomic.StoreUint32(&encoding, jsonEncodingType)
defer func() {
encoding = old
atomic.StoreUint32(&encoding, old)
}()
var buf mockWriter

View File

@@ -448,7 +448,15 @@ func (u *Unmarshaler) fillSlice(fieldType reflect.Type, value reflect.Value, map
dereffedBaseType := Deref(baseType)
dereffedBaseKind := dereffedBaseType.Kind()
refValue := reflect.ValueOf(mapValue)
if refValue.IsNil() {
return nil
}
conv := reflect.MakeSlice(reflect.SliceOf(baseType), refValue.Len(), refValue.Cap())
if refValue.Len() == 0 {
value.Set(conv)
return nil
}
var valid bool
for i := 0; i < refValue.Len(); i++ {

View File

@@ -198,6 +198,49 @@ func TestUnmarshalIntWithDefault(t *testing.T) {
assert.Equal(t, 1, in.Int)
}
func TestUnmarshalBoolSliceRequired(t *testing.T) {
type inner struct {
Bools []bool `key:"bools"`
}
var in inner
assert.NotNil(t, UnmarshalKey(map[string]interface{}{}, &in))
}
func TestUnmarshalBoolSliceNil(t *testing.T) {
type inner struct {
Bools []bool `key:"bools,optional"`
}
var in inner
assert.Nil(t, UnmarshalKey(map[string]interface{}{}, &in))
assert.Nil(t, in.Bools)
}
func TestUnmarshalBoolSliceNilExplicit(t *testing.T) {
type inner struct {
Bools []bool `key:"bools,optional"`
}
var in inner
assert.Nil(t, UnmarshalKey(map[string]interface{}{
"bools": nil,
}, &in))
assert.Nil(t, in.Bools)
}
func TestUnmarshalBoolSliceEmpty(t *testing.T) {
type inner struct {
Bools []bool `key:"bools,optional"`
}
var in inner
assert.Nil(t, UnmarshalKey(map[string]interface{}{
"bools": []bool{},
}, &in))
assert.Empty(t, in.Bools)
}
func TestUnmarshalBoolSliceWithDefault(t *testing.T) {
type inner struct {
Bools []bool `key:"bools,default=[true,false]"`
@@ -330,28 +373,34 @@ func TestUnmarshalFloat(t *testing.T) {
func TestUnmarshalInt64Slice(t *testing.T) {
var v struct {
Ages []int64 `key:"ages"`
Ages []int64 `key:"ages"`
Slice []int64 `key:"slice"`
}
m := map[string]interface{}{
"ages": []int64{1, 2},
"ages": []int64{1, 2},
"slice": []interface{}{},
}
ast := assert.New(t)
ast.Nil(UnmarshalKey(m, &v))
ast.ElementsMatch([]int64{1, 2}, v.Ages)
ast.Equal([]int64{}, v.Slice)
}
func TestUnmarshalIntSlice(t *testing.T) {
var v struct {
Ages []int `key:"ages"`
Ages []int `key:"ages"`
Slice []int `key:"slice"`
}
m := map[string]interface{}{
"ages": []int{1, 2},
"ages": []int{1, 2},
"slice": []interface{}{},
}
ast := assert.New(t)
ast.Nil(UnmarshalKey(m, &v))
ast.ElementsMatch([]int{1, 2}, v.Ages)
ast.Equal([]int{}, v.Slice)
}
func TestUnmarshalString(t *testing.T) {

View File

@@ -4,7 +4,6 @@ import (
"encoding/json"
"errors"
"io"
"io/ioutil"
"gopkg.in/yaml.v2"
)
@@ -14,7 +13,7 @@ const yamlTagKey = "json"
var (
// ErrUnsupportedType is an error that indicates the config format is not supported.
ErrUnsupportedType = errors.New("only map-like configs are suported")
ErrUnsupportedType = errors.New("only map-like configs are supported")
yamlUnmarshaler = NewUnmarshaler(yamlTagKey)
)
@@ -29,39 +28,6 @@ func UnmarshalYamlReader(reader io.Reader, v interface{}) error {
return unmarshalYamlReader(reader, v, yamlUnmarshaler)
}
func unmarshalYamlBytes(content []byte, v interface{}, unmarshaler *Unmarshaler) error {
var o interface{}
if err := yamlUnmarshal(content, &o); err != nil {
return err
}
if m, ok := o.(map[string]interface{}); ok {
return unmarshaler.Unmarshal(m, v)
}
return ErrUnsupportedType
}
func unmarshalYamlReader(reader io.Reader, v interface{}, unmarshaler *Unmarshaler) error {
content, err := ioutil.ReadAll(reader)
if err != nil {
return err
}
return unmarshalYamlBytes(content, v, unmarshaler)
}
// yamlUnmarshal YAML to map[string]interface{} instead of map[interface{}]interface{}.
func yamlUnmarshal(in []byte, out interface{}) error {
var res interface{}
if err := yaml.Unmarshal(in, &res); err != nil {
return err
}
*out.(*interface{}) = cleanupMapValue(res)
return nil
}
func cleanupInterfaceMap(in map[interface{}]interface{}) map[string]interface{} {
res := make(map[string]interface{})
for k, v := range in {
@@ -96,3 +62,40 @@ func cleanupMapValue(v interface{}) interface{} {
return Repr(v)
}
}
func unmarshal(unmarshaler *Unmarshaler, o interface{}, v interface{}) error {
if m, ok := o.(map[string]interface{}); ok {
return unmarshaler.Unmarshal(m, v)
}
return ErrUnsupportedType
}
func unmarshalYamlBytes(content []byte, v interface{}, unmarshaler *Unmarshaler) error {
var o interface{}
if err := yamlUnmarshal(content, &o); err != nil {
return err
}
return unmarshal(unmarshaler, o, v)
}
func unmarshalYamlReader(reader io.Reader, v interface{}, unmarshaler *Unmarshaler) error {
var res interface{}
if err := yaml.NewDecoder(reader).Decode(&res); err != nil {
return err
}
return unmarshal(unmarshaler, cleanupMapValue(res), v)
}
// yamlUnmarshal YAML to map[string]interface{} instead of map[interface{}]interface{}.
func yamlUnmarshal(in []byte, out interface{}) error {
var res interface{}
if err := yaml.Unmarshal(in, &res); err != nil {
return err
}
*out.(*interface{}) = cleanupMapValue(res)
return nil
}

View File

@@ -926,14 +926,17 @@ func TestUnmarshalYamlBytesError(t *testing.T) {
}
func TestUnmarshalYamlReaderError(t *testing.T) {
payload := `abcd: cdef`
reader := strings.NewReader(payload)
var v struct {
Any string
}
reader := strings.NewReader(`abcd: cdef`)
err := UnmarshalYamlReader(reader, &v)
assert.NotNil(t, err)
reader = strings.NewReader("chenquan")
err = UnmarshalYamlReader(reader, &v)
assert.ErrorIs(t, err, ErrUnsupportedType)
}
func TestUnmarshalYamlBadReader(t *testing.T) {
@@ -1011,6 +1014,6 @@ func TestUnmarshalYamlMapRune(t *testing.T) {
type badReader struct{}
func (b *badReader) Read(p []byte) (n int, err error) {
func (b *badReader) Read(_ []byte) (n int, err error) {
return 0, io.ErrLimitReached
}

View File

@@ -1,3 +1,6 @@
//go:build linux || darwin
// +build linux darwin
package proc
import (

View File

@@ -11,6 +11,7 @@ const (
mega = 1024 * 1024
)
// DisplayStats prints the goroutine, memory, GC stats with given interval, default to 5 seconds.
func DisplayStats(interval ...time.Duration) {
duration := defaultInterval
for _, val := range interval {

View File

@@ -1,78 +1,129 @@
package internal
import (
"bufio"
"fmt"
"os"
"path"
"strconv"
"strings"
"sync"
"time"
"github.com/zeromicro/go-zero/core/iox"
"github.com/zeromicro/go-zero/core/lang"
"golang.org/x/sys/unix"
)
const cgroupDir = "/sys/fs/cgroup"
const (
cgroupDir = "/sys/fs/cgroup"
cpuStatFile = cgroupDir + "/cpu.stat"
cpusetFile = cgroupDir + "/cpuset.cpus.effective"
)
type cgroup struct {
var (
isUnifiedOnce sync.Once
isUnified bool
inUserNS bool
nsOnce sync.Once
)
type cgroup interface {
cpuQuotaUs() (int64, error)
cpuPeriodUs() (uint64, error)
cpus() ([]uint64, error)
usageAllCpus() (uint64, error)
}
func currentCgroup() (cgroup, error) {
if isCgroup2UnifiedMode() {
return currentCgroupV2()
}
return currentCgroupV1()
}
type cgroupV1 struct {
cgroups map[string]string
}
func (c *cgroup) acctUsageAllCpus() (uint64, error) {
data, err := iox.ReadText(path.Join(c.cgroups["cpuacct"], "cpuacct.usage"))
if err != nil {
return 0, err
}
return parseUint(string(data))
}
func (c *cgroup) acctUsagePerCpu() ([]uint64, error) {
data, err := iox.ReadText(path.Join(c.cgroups["cpuacct"], "cpuacct.usage_percpu"))
if err != nil {
return nil, err
}
var usage []uint64
for _, v := range strings.Fields(string(data)) {
u, err := parseUint(v)
if err != nil {
return nil, err
}
usage = append(usage, u)
}
return usage, nil
}
func (c *cgroup) cpuQuotaUs() (int64, error) {
func (c *cgroupV1) cpuQuotaUs() (int64, error) {
data, err := iox.ReadText(path.Join(c.cgroups["cpu"], "cpu.cfs_quota_us"))
if err != nil {
return 0, err
}
return strconv.ParseInt(string(data), 10, 64)
return strconv.ParseInt(data, 10, 64)
}
func (c *cgroup) cpuPeriodUs() (uint64, error) {
func (c *cgroupV1) cpuPeriodUs() (uint64, error) {
data, err := iox.ReadText(path.Join(c.cgroups["cpu"], "cpu.cfs_period_us"))
if err != nil {
return 0, err
}
return parseUint(string(data))
return parseUint(data)
}
func (c *cgroup) cpus() ([]uint64, error) {
func (c *cgroupV1) cpus() ([]uint64, error) {
data, err := iox.ReadText(path.Join(c.cgroups["cpuset"], "cpuset.cpus"))
if err != nil {
return nil, err
}
return parseUints(string(data))
return parseUints(data)
}
func currentCgroup() (*cgroup, error) {
func (c *cgroupV1) usageAllCpus() (uint64, error) {
data, err := iox.ReadText(path.Join(c.cgroups["cpuacct"], "cpuacct.usage"))
if err != nil {
return 0, err
}
return parseUint(data)
}
type cgroupV2 struct {
cgroups map[string]string
}
func (c *cgroupV2) cpuQuotaUs() (int64, error) {
data, err := iox.ReadText(path.Join(cgroupDir, "cpu.cfs_quota_us"))
if err != nil {
return 0, err
}
return strconv.ParseInt(data, 10, 64)
}
func (c *cgroupV2) cpuPeriodUs() (uint64, error) {
data, err := iox.ReadText(path.Join(cgroupDir, "cpu.cfs_period_us"))
if err != nil {
return 0, err
}
return parseUint(data)
}
func (c *cgroupV2) cpus() ([]uint64, error) {
data, err := iox.ReadText(cpusetFile)
if err != nil {
return nil, err
}
return parseUints(data)
}
func (c *cgroupV2) usageAllCpus() (uint64, error) {
usec, err := parseUint(c.cgroups["usage_usec"])
if err != nil {
return 0, err
}
return usec * uint64(time.Microsecond), nil
}
func currentCgroupV1() (cgroup, error) {
cgroupFile := fmt.Sprintf("/proc/%d/cgroup", os.Getpid())
lines, err := iox.ReadTextLines(cgroupFile, iox.WithoutBlank())
if err != nil {
@@ -100,11 +151,51 @@ func currentCgroup() (*cgroup, error) {
}
}
return &cgroup{
return &cgroupV1{
cgroups: cgroups,
}, nil
}
func currentCgroupV2() (cgroup, error) {
lines, err := iox.ReadTextLines(cpuStatFile, iox.WithoutBlank())
if err != nil {
return nil, err
}
cgroups := make(map[string]string)
for _, line := range lines {
cols := strings.Fields(line)
if len(cols) != 2 {
return nil, fmt.Errorf("invalid cgroupV2 line: %s", line)
}
cgroups[cols[0]] = cols[1]
}
return &cgroupV2{
cgroups: cgroups,
}, nil
}
// isCgroup2UnifiedMode returns whether we are running in cgroup v2 unified mode.
func isCgroup2UnifiedMode() bool {
isUnifiedOnce.Do(func() {
var st unix.Statfs_t
err := unix.Statfs(cgroupDir, &st)
if err != nil {
if os.IsNotExist(err) && runningInUserNS() {
// ignore the "not found" error if running in userns
isUnified = false
return
}
panic(fmt.Sprintf("cannot statfs cgroup root: %s", err))
}
isUnified = st.Type == unix.CGROUP2_SUPER_MAGIC
})
return isUnified
}
func parseUint(s string) (uint64, error) {
v, err := strconv.ParseInt(s, 10, 64)
if err != nil {
@@ -166,3 +257,36 @@ func parseUints(val string) ([]uint64, error) {
return sets, nil
}
// runningInUserNS detects whether we are currently running in a user namespace.
func runningInUserNS() bool {
nsOnce.Do(func() {
file, err := os.Open("/proc/self/uid_map")
if err != nil {
// This kernel-provided file only exists if user namespaces are supported
return
}
defer file.Close()
buf := bufio.NewReader(file)
l, _, err := buf.ReadLine()
if err != nil {
return
}
line := string(l)
var a, b, c int64
fmt.Sscanf(line, "%d %d %d", &a, &b, &c)
/*
* We assume we are in the initial user namespace if we have a full
* range - 4294967295 uids starting at uid 0.
*/
if a == 0 && b == 0 && c == 4294967295 {
return
}
inUserNS = true
})
return inUserNS
}

View File

@@ -24,7 +24,7 @@ var (
// if /proc not present, ignore the cpu calculation, like wsl linux
func init() {
cpus, err := perCpuUsage()
cpus, err := cpuSets()
if err != nil {
logx.Error(err)
return
@@ -117,15 +117,6 @@ func cpuSets() ([]uint64, error) {
return cg.cpus()
}
func perCpuUsage() ([]uint64, error) {
cg, err := currentCgroup()
if err != nil {
return nil, err
}
return cg.acctUsagePerCpu()
}
func systemCpuUsage() (uint64, error) {
lines, err := iox.ReadTextLines("/proc/stat", iox.WithoutBlank())
if err != nil {
@@ -157,10 +148,10 @@ func systemCpuUsage() (uint64, error) {
}
func totalCpuUsage() (usage uint64, err error) {
var cg *cgroup
var cg cgroup
if cg, err = currentCgroup(); err != nil {
return
}
return cg.acctUsageAllCpus()
return cg.usageAllCpus()
}

View File

@@ -30,18 +30,32 @@ func RawFieldNames(in interface{}, postgresSql ...bool) []string {
for i := 0; i < v.NumField(); i++ {
// gets us a StructField
fi := typ.Field(i)
if tagv := fi.Tag.Get(dbTag); tagv != "" {
if pg {
out = append(out, tagv)
} else {
out = append(out, fmt.Sprintf("`%s`", tagv))
}
} else {
tagv := fi.Tag.Get(dbTag)
switch tagv {
case "-":
continue
case "":
if pg {
out = append(out, fi.Name)
} else {
out = append(out, fmt.Sprintf("`%s`", fi.Name))
}
default:
// get tag name with the tag opton, e.g.:
// `db:"id"`
// `db:"id,type=char,length=16"`
// `db:",type=char,length=16"`
if strings.Contains(tagv, ",") {
tagv = strings.TrimSpace(strings.Split(tagv, ",")[0])
}
if len(tagv) == 0 {
tagv = fi.Name
}
if pg {
out = append(out, tagv)
} else {
out = append(out, fmt.Sprintf("`%s`", tagv))
}
}
}

View File

@@ -22,3 +22,20 @@ func TestFieldNames(t *testing.T) {
assert.Equal(t, expected, out)
})
}
type mockedUserWithOptions struct {
ID string `db:"id" json:"id,omitempty"`
UserName string `db:"user_name,type=varchar,length=255" json:"userName,omitempty"`
Sex int `db:"sex" json:"sex,omitempty"`
UUID string `db:",type=varchar,length=16" uuid:"uuid,omitempty"`
Age int `db:"age" json:"age"`
}
func TestFieldNamesWithTagOptions(t *testing.T) {
t.Run("new", func(t *testing.T) {
var u mockedUserWithOptions
out := RawFieldNames(&u)
expected := []string{"`id`", "`user_name`", "`sex`", "`UUID`", "`age`"}
assert.Equal(t, expected, out)
})
}

View File

@@ -1,6 +1,8 @@
package cache
import (
"context"
"errors"
"fmt"
"log"
"time"
@@ -13,13 +15,37 @@ import (
type (
// Cache interface is used to define the cache implementation.
Cache interface {
// Del deletes cached values with keys.
Del(keys ...string) error
Get(key string, v interface{}) error
// DelCtx deletes cached values with keys.
DelCtx(ctx context.Context, keys ...string) error
// Get gets the cache with key and fills into v.
Get(key string, val interface{}) error
// GetCtx gets the cache with key and fills into v.
GetCtx(ctx context.Context, key string, val interface{}) error
// IsNotFound checks if the given error is the defined errNotFound.
IsNotFound(err error) bool
Set(key string, v interface{}) error
SetWithExpire(key string, v interface{}, expire time.Duration) error
Take(v interface{}, key string, query func(v interface{}) error) error
TakeWithExpire(v interface{}, key string, query func(v interface{}, expire time.Duration) error) error
// Set sets the cache with key and v, using c.expiry.
Set(key string, val interface{}) error
// SetCtx sets the cache with key and v, using c.expiry.
SetCtx(ctx context.Context, key string, val interface{}) error
// SetWithExpire sets the cache with key and v, using given expire.
SetWithExpire(key string, val interface{}, expire time.Duration) error
// SetWithExpireCtx sets the cache with key and v, using given expire.
SetWithExpireCtx(ctx context.Context, key string, val interface{}, expire time.Duration) error
// Take takes the result from cache first, if not found,
// query from DB and set cache using c.expiry, then return the result.
Take(val interface{}, key string, query func(val interface{}) error) error
// TakeCtx takes the result from cache first, if not found,
// query from DB and set cache using c.expiry, then return the result.
TakeCtx(ctx context.Context, val interface{}, key string, query func(val interface{}) error) error
// TakeWithExpire takes the result from cache first, if not found,
// query from DB and set cache using given expire, then return the result.
TakeWithExpire(val interface{}, key string, query func(val interface{}, expire time.Duration) error) error
// TakeWithExpireCtx takes the result from cache first, if not found,
// query from DB and set cache using given expire, then return the result.
TakeWithExpireCtx(ctx context.Context, val interface{}, key string,
query func(val interface{}, expire time.Duration) error) error
}
cacheCluster struct {
@@ -51,7 +77,13 @@ func New(c ClusterConf, barrier syncx.SingleFlight, st *Stat, errNotFound error,
}
}
// Del deletes cached values with keys.
func (cc cacheCluster) Del(keys ...string) error {
return cc.DelCtx(context.Background(), keys...)
}
// DelCtx deletes cached values with keys.
func (cc cacheCluster) DelCtx(ctx context.Context, keys ...string) error {
switch len(keys) {
case 0:
return nil
@@ -62,7 +94,7 @@ func (cc cacheCluster) Del(keys ...string) error {
return cc.errNotFound
}
return c.(Cache).Del(key)
return c.(Cache).DelCtx(ctx, key)
default:
var be errorx.BatchError
nodes := make(map[interface{}][]string)
@@ -76,7 +108,7 @@ func (cc cacheCluster) Del(keys ...string) error {
nodes[c] = append(nodes[c], key)
}
for c, ks := range nodes {
if err := c.(Cache).Del(ks...); err != nil {
if err := c.(Cache).DelCtx(ctx, ks...); err != nil {
be.Add(err)
}
}
@@ -85,52 +117,86 @@ func (cc cacheCluster) Del(keys ...string) error {
}
}
func (cc cacheCluster) Get(key string, v interface{}) error {
// Get gets the cache with key and fills into v.
func (cc cacheCluster) Get(key string, val interface{}) error {
return cc.GetCtx(context.Background(), key, val)
}
// GetCtx gets the cache with key and fills into v.
func (cc cacheCluster) GetCtx(ctx context.Context, key string, val interface{}) error {
c, ok := cc.dispatcher.Get(key)
if !ok {
return cc.errNotFound
}
return c.(Cache).Get(key, v)
return c.(Cache).GetCtx(ctx, key, val)
}
// IsNotFound checks if the given error is the defined errNotFound.
func (cc cacheCluster) IsNotFound(err error) bool {
return err == cc.errNotFound
return errors.Is(err, cc.errNotFound)
}
func (cc cacheCluster) Set(key string, v interface{}) error {
// Set sets the cache with key and v, using c.expiry.
func (cc cacheCluster) Set(key string, val interface{}) error {
return cc.SetCtx(context.Background(), key, val)
}
// SetCtx sets the cache with key and v, using c.expiry.
func (cc cacheCluster) SetCtx(ctx context.Context, key string, val interface{}) error {
c, ok := cc.dispatcher.Get(key)
if !ok {
return cc.errNotFound
}
return c.(Cache).Set(key, v)
return c.(Cache).SetCtx(ctx, key, val)
}
func (cc cacheCluster) SetWithExpire(key string, v interface{}, expire time.Duration) error {
// SetWithExpire sets the cache with key and v, using given expire.
func (cc cacheCluster) SetWithExpire(key string, val interface{}, expire time.Duration) error {
return cc.SetWithExpireCtx(context.Background(), key, val, expire)
}
// SetWithExpireCtx sets the cache with key and v, using given expire.
func (cc cacheCluster) SetWithExpireCtx(ctx context.Context, key string, val interface{}, expire time.Duration) error {
c, ok := cc.dispatcher.Get(key)
if !ok {
return cc.errNotFound
}
return c.(Cache).SetWithExpire(key, v, expire)
return c.(Cache).SetWithExpireCtx(ctx, key, val, expire)
}
func (cc cacheCluster) Take(v interface{}, key string, query func(v interface{}) error) error {
// Take takes the result from cache first, if not found,
// query from DB and set cache using c.expiry, then return the result.
func (cc cacheCluster) Take(val interface{}, key string, query func(val interface{}) error) error {
return cc.TakeCtx(context.Background(), val, key, query)
}
// TakeCtx takes the result from cache first, if not found,
// query from DB and set cache using c.expiry, then return the result.
func (cc cacheCluster) TakeCtx(ctx context.Context, val interface{}, key string, query func(val interface{}) error) error {
c, ok := cc.dispatcher.Get(key)
if !ok {
return cc.errNotFound
}
return c.(Cache).Take(v, key, query)
return c.(Cache).TakeCtx(ctx, val, key, query)
}
func (cc cacheCluster) TakeWithExpire(v interface{}, key string,
query func(v interface{}, expire time.Duration) error) error {
// TakeWithExpire takes the result from cache first, if not found,
// query from DB and set cache using given expire, then return the result.
func (cc cacheCluster) TakeWithExpire(val interface{}, key string, query func(val interface{}, expire time.Duration) error) error {
return cc.TakeWithExpireCtx(context.Background(), val, key, query)
}
// TakeWithExpireCtx takes the result from cache first, if not found,
// query from DB and set cache using given expire, then return the result.
func (cc cacheCluster) TakeWithExpireCtx(ctx context.Context, val interface{}, key string, query func(val interface{}, expire time.Duration) error) error {
c, ok := cc.dispatcher.Get(key)
if !ok {
return cc.errNotFound
}
return c.(Cache).TakeWithExpire(v, key, query)
return c.(Cache).TakeWithExpireCtx(ctx, val, key, query)
}

View File

@@ -1,7 +1,9 @@
package cache
import (
"context"
"encoding/json"
"errors"
"fmt"
"math"
"strconv"
@@ -16,12 +18,18 @@ import (
"github.com/zeromicro/go-zero/core/syncx"
)
var _ Cache = (*mockedNode)(nil)
type mockedNode struct {
vals map[string][]byte
errNotFound error
}
func (mc *mockedNode) Del(keys ...string) error {
return mc.DelCtx(context.Background(), keys...)
}
func (mc *mockedNode) DelCtx(_ context.Context, keys ...string) error {
var be errorx.BatchError
for _, key := range keys {
@@ -35,21 +43,29 @@ func (mc *mockedNode) Del(keys ...string) error {
return be.Err()
}
func (mc *mockedNode) Get(key string, v interface{}) error {
func (mc *mockedNode) Get(key string, val interface{}) error {
return mc.GetCtx(context.Background(), key, val)
}
func (mc *mockedNode) GetCtx(ctx context.Context, key string, val interface{}) error {
bs, ok := mc.vals[key]
if ok {
return json.Unmarshal(bs, v)
return json.Unmarshal(bs, val)
}
return mc.errNotFound
}
func (mc *mockedNode) IsNotFound(err error) bool {
return err == mc.errNotFound
return errors.Is(err, mc.errNotFound)
}
func (mc *mockedNode) Set(key string, v interface{}) error {
data, err := json.Marshal(v)
func (mc *mockedNode) Set(key string, val interface{}) error {
return mc.SetCtx(context.Background(), key, val)
}
func (mc *mockedNode) SetCtx(ctx context.Context, key string, val interface{}) error {
data, err := json.Marshal(val)
if err != nil {
return err
}
@@ -58,25 +74,37 @@ func (mc *mockedNode) Set(key string, v interface{}) error {
return nil
}
func (mc *mockedNode) SetWithExpire(key string, v interface{}, expire time.Duration) error {
return mc.Set(key, v)
func (mc *mockedNode) SetWithExpire(key string, val interface{}, expire time.Duration) error {
return mc.SetWithExpireCtx(context.Background(), key, val, expire)
}
func (mc *mockedNode) Take(v interface{}, key string, query func(v interface{}) error) error {
func (mc *mockedNode) SetWithExpireCtx(ctx context.Context, key string, val interface{}, expire time.Duration) error {
return mc.Set(key, val)
}
func (mc *mockedNode) Take(val interface{}, key string, query func(val interface{}) error) error {
return mc.TakeCtx(context.Background(), val, key, query)
}
func (mc *mockedNode) TakeCtx(ctx context.Context, val interface{}, key string, query func(val interface{}) error) error {
if _, ok := mc.vals[key]; ok {
return mc.Get(key, v)
return mc.GetCtx(ctx, key, val)
}
if err := query(v); err != nil {
if err := query(val); err != nil {
return err
}
return mc.Set(key, v)
return mc.SetCtx(ctx, key, val)
}
func (mc *mockedNode) TakeWithExpire(v interface{}, key string, query func(v interface{}, expire time.Duration) error) error {
return mc.Take(v, key, func(v interface{}) error {
return query(v, 0)
func (mc *mockedNode) TakeWithExpire(val interface{}, key string, query func(val interface{}, expire time.Duration) error) error {
return mc.TakeWithExpireCtx(context.Background(), val, key, query)
}
func (mc *mockedNode) TakeWithExpireCtx(ctx context.Context, val interface{}, key string, query func(val interface{}, expire time.Duration) error) error {
return mc.Take(val, key, func(val interface{}) error {
return query(val, 0)
})
}
@@ -113,18 +141,18 @@ func TestCache_SetDel(t *testing.T) {
}
}
for i := 0; i < total; i++ {
var v int
assert.Nil(t, c.Get(fmt.Sprintf("key/%d", i), &v))
assert.Equal(t, i, v)
var val int
assert.Nil(t, c.Get(fmt.Sprintf("key/%d", i), &val))
assert.Equal(t, i, val)
}
assert.Nil(t, c.Del())
for i := 0; i < total; i++ {
assert.Nil(t, c.Del(fmt.Sprintf("key/%d", i)))
}
for i := 0; i < total; i++ {
var v int
assert.True(t, c.IsNotFound(c.Get(fmt.Sprintf("key/%d", i), &v)))
assert.Equal(t, 0, v)
var val int
assert.True(t, c.IsNotFound(c.Get(fmt.Sprintf("key/%d", i), &val)))
assert.Equal(t, 0, val)
}
}
@@ -151,18 +179,18 @@ func TestCache_OneNode(t *testing.T) {
}
}
for i := 0; i < total; i++ {
var v int
assert.Nil(t, c.Get(fmt.Sprintf("key/%d", i), &v))
assert.Equal(t, i, v)
var val int
assert.Nil(t, c.Get(fmt.Sprintf("key/%d", i), &val))
assert.Equal(t, i, val)
}
assert.Nil(t, c.Del())
for i := 0; i < total; i++ {
assert.Nil(t, c.Del(fmt.Sprintf("key/%d", i)))
}
for i := 0; i < total; i++ {
var v int
assert.True(t, c.IsNotFound(c.Get(fmt.Sprintf("key/%d", i), &v)))
assert.Equal(t, 0, v)
var val int
assert.True(t, c.IsNotFound(c.Get(fmt.Sprintf("key/%d", i), &val)))
assert.Equal(t, 0, val)
}
}
@@ -202,9 +230,9 @@ func TestCache_Balance(t *testing.T) {
assert.True(t, entropy > .95, fmt.Sprintf("entropy should be greater than 0.95, but got %.2f", entropy))
for i := 0; i < total; i++ {
var v int
assert.Nil(t, c.Get(strconv.Itoa(i), &v))
assert.Equal(t, i, v)
var val int
assert.Nil(t, c.Get(strconv.Itoa(i), &val))
assert.Equal(t, i, val)
}
for i := 0; i < total/10; i++ {
@@ -216,14 +244,14 @@ func TestCache_Balance(t *testing.T) {
for i := 0; i < total/10; i++ {
var val int
if i%2 == 0 {
assert.Nil(t, c.Take(&val, strconv.Itoa(i*10), func(v interface{}) error {
*v.(*int) = i
assert.Nil(t, c.Take(&val, strconv.Itoa(i*10), func(val interface{}) error {
*val.(*int) = i
count++
return nil
}))
} else {
assert.Nil(t, c.TakeWithExpire(&val, strconv.Itoa(i*10), func(v interface{}, expire time.Duration) error {
*v.(*int) = i
assert.Nil(t, c.TakeWithExpire(&val, strconv.Itoa(i*10), func(val interface{}, expire time.Duration) error {
*val.(*int) = i
count++
return nil
}))
@@ -244,10 +272,10 @@ func TestCacheNoNode(t *testing.T) {
assert.NotNil(t, c.Get("foo", nil))
assert.NotNil(t, c.Set("foo", nil))
assert.NotNil(t, c.SetWithExpire("foo", nil, time.Second))
assert.NotNil(t, c.Take(nil, "foo", func(v interface{}) error {
assert.NotNil(t, c.Take(nil, "foo", func(val interface{}) error {
return nil
}))
assert.NotNil(t, c.TakeWithExpire(nil, "foo", func(v interface{}, duration time.Duration) error {
assert.NotNil(t, c.TakeWithExpire(nil, "foo", func(val interface{}, duration time.Duration) error {
return nil
}))
}
@@ -255,8 +283,8 @@ func TestCacheNoNode(t *testing.T) {
func calcEntropy(m map[int]int, total int) float64 {
var entropy float64
for _, v := range m {
proba := float64(v) / float64(total)
for _, val := range m {
proba := float64(val) / float64(total)
entropy -= proba * math.Log2(proba)
}

View File

@@ -1,6 +1,7 @@
package cache
import (
"context"
"errors"
"fmt"
"math/rand"
@@ -61,30 +62,39 @@ func NewNode(rds *redis.Redis, barrier syncx.SingleFlight, st *Stat,
// Del deletes cached values with keys.
func (c cacheNode) Del(keys ...string) error {
return c.DelCtx(context.Background(), keys...)
}
// DelCtx deletes cached values with keys.
func (c cacheNode) DelCtx(ctx context.Context, keys ...string) error {
if len(keys) == 0 {
return nil
}
logger := logx.WithContext(ctx)
if len(keys) > 1 && c.rds.Type == redis.ClusterType {
for _, key := range keys {
if _, err := c.rds.Del(key); err != nil {
logx.Errorf("failed to clear cache with key: %q, error: %v", key, err)
if _, err := c.rds.DelCtx(ctx, key); err != nil {
logger.Errorf("failed to clear cache with key: %q, error: %v", key, err)
c.asyncRetryDelCache(key)
}
}
} else {
if _, err := c.rds.Del(keys...); err != nil {
logx.Errorf("failed to clear cache with keys: %q, error: %v", formatKeys(keys), err)
c.asyncRetryDelCache(keys...)
}
} else if _, err := c.rds.DelCtx(ctx, keys...); err != nil {
logger.Errorf("failed to clear cache with keys: %q, error: %v", formatKeys(keys), err)
c.asyncRetryDelCache(keys...)
}
return nil
}
// Get gets the cache with key and fills into v.
func (c cacheNode) Get(key string, v interface{}) error {
err := c.doGetCache(key, v)
func (c cacheNode) Get(key string, val interface{}) error {
return c.GetCtx(context.Background(), key, val)
}
// GetCtx gets the cache with key and fills into v.
func (c cacheNode) GetCtx(ctx context.Context, key string, val interface{}) error {
err := c.doGetCache(ctx, key, val)
if err == errPlaceholder {
return c.errNotFound
}
@@ -94,22 +104,32 @@ func (c cacheNode) Get(key string, v interface{}) error {
// IsNotFound checks if the given error is the defined errNotFound.
func (c cacheNode) IsNotFound(err error) bool {
return err == c.errNotFound
return errors.Is(err, c.errNotFound)
}
// Set sets the cache with key and v, using c.expiry.
func (c cacheNode) Set(key string, v interface{}) error {
return c.SetWithExpire(key, v, c.aroundDuration(c.expiry))
func (c cacheNode) Set(key string, val interface{}) error {
return c.SetCtx(context.Background(), key, val)
}
// SetCtx sets the cache with key and v, using c.expiry.
func (c cacheNode) SetCtx(ctx context.Context, key string, val interface{}) error {
return c.SetWithExpireCtx(ctx, key, val, c.aroundDuration(c.expiry))
}
// SetWithExpire sets the cache with key and v, using given expire.
func (c cacheNode) SetWithExpire(key string, v interface{}, expire time.Duration) error {
data, err := jsonx.Marshal(v)
func (c cacheNode) SetWithExpire(key string, val interface{}, expire time.Duration) error {
return c.SetWithExpireCtx(context.Background(), key, val, expire)
}
// SetWithExpireCtx sets the cache with key and v, using given expire.
func (c cacheNode) SetWithExpireCtx(ctx context.Context, key string, val interface{}, expire time.Duration) error {
data, err := jsonx.Marshal(val)
if err != nil {
return err
}
return c.rds.Setex(key, string(data), int(expire.Seconds()))
return c.rds.SetexCtx(ctx, key, string(data), int(expire.Seconds()))
}
// String returns a string that represents the cacheNode.
@@ -119,21 +139,32 @@ func (c cacheNode) String() string {
// Take takes the result from cache first, if not found,
// query from DB and set cache using c.expiry, then return the result.
func (c cacheNode) Take(v interface{}, key string, query func(v interface{}) error) error {
return c.doTake(v, key, query, func(v interface{}) error {
return c.Set(key, v)
func (c cacheNode) Take(val interface{}, key string, query func(val interface{}) error) error {
return c.TakeCtx(context.Background(), val, key, query)
}
// TakeCtx takes the result from cache first, if not found,
// query from DB and set cache using c.expiry, then return the result.
func (c cacheNode) TakeCtx(ctx context.Context, val interface{}, key string, query func(val interface{}) error) error {
return c.doTake(ctx, val, key, query, func(v interface{}) error {
return c.SetCtx(ctx, key, v)
})
}
// TakeWithExpire takes the result from cache first, if not found,
// query from DB and set cache using given expire, then return the result.
func (c cacheNode) TakeWithExpire(v interface{}, key string, query func(v interface{},
expire time.Duration) error) error {
func (c cacheNode) TakeWithExpire(val interface{}, key string, query func(val interface{}, expire time.Duration) error) error {
return c.TakeWithExpireCtx(context.Background(), val, key, query)
}
// TakeWithExpireCtx takes the result from cache first, if not found,
// query from DB and set cache using given expire, then return the result.
func (c cacheNode) TakeWithExpireCtx(ctx context.Context, val interface{}, key string, query func(val interface{}, expire time.Duration) error) error {
expire := c.aroundDuration(c.expiry)
return c.doTake(v, key, func(v interface{}) error {
return c.doTake(ctx, val, key, func(v interface{}) error {
return query(v, expire)
}, func(v interface{}) error {
return c.SetWithExpire(key, v, expire)
return c.SetWithExpireCtx(ctx, key, v, expire)
})
}
@@ -148,9 +179,9 @@ func (c cacheNode) asyncRetryDelCache(keys ...string) {
}, keys...)
}
func (c cacheNode) doGetCache(key string, v interface{}) error {
func (c cacheNode) doGetCache(ctx context.Context, key string, v interface{}) error {
c.stat.IncrementTotal()
data, err := c.rds.Get(key)
data, err := c.rds.GetCtx(ctx, key)
if err != nil {
c.stat.IncrementMiss()
return err
@@ -166,13 +197,14 @@ func (c cacheNode) doGetCache(key string, v interface{}) error {
return errPlaceholder
}
return c.processCache(key, data, v)
return c.processCache(ctx, key, data, v)
}
func (c cacheNode) doTake(v interface{}, key string, query func(v interface{}) error,
cacheVal func(v interface{}) error) error {
func (c cacheNode) doTake(ctx context.Context, v interface{}, key string,
query func(v interface{}) error, cacheVal func(v interface{}) error) error {
logger := logx.WithContext(ctx)
val, fresh, err := c.barrier.DoEx(key, func() (interface{}, error) {
if err := c.doGetCache(key, v); err != nil {
if err := c.doGetCache(ctx, key, v); err != nil {
if err == errPlaceholder {
return nil, c.errNotFound
} else if err != c.errNotFound {
@@ -183,8 +215,8 @@ func (c cacheNode) doTake(v interface{}, key string, query func(v interface{}) e
}
if err = query(v); err == c.errNotFound {
if err = c.setCacheWithNotFound(key); err != nil {
logx.Error(err)
if err = c.setCacheWithNotFound(ctx, key); err != nil {
logger.Error(err)
}
return nil, c.errNotFound
@@ -194,7 +226,7 @@ func (c cacheNode) doTake(v interface{}, key string, query func(v interface{}) e
}
if err = cacheVal(v); err != nil {
logx.Error(err)
logger.Error(err)
}
}
@@ -214,7 +246,7 @@ func (c cacheNode) doTake(v interface{}, key string, query func(v interface{}) e
return jsonx.Unmarshal(val.([]byte), v)
}
func (c cacheNode) processCache(key, data string, v interface{}) error {
func (c cacheNode) processCache(ctx context.Context, key, data string, v interface{}) error {
err := jsonx.Unmarshal([]byte(data), v)
if err == nil {
return nil
@@ -222,10 +254,11 @@ func (c cacheNode) processCache(key, data string, v interface{}) error {
report := fmt.Sprintf("unmarshal cache, node: %s, key: %s, value: %s, error: %v",
c.rds.Addr, key, data, err)
logx.Error(report)
logger := logx.WithContext(ctx)
logger.Error(report)
stat.Report(report)
if _, e := c.rds.Del(key); e != nil {
logx.Errorf("delete invalid cache, node: %s, key: %s, value: %s, error: %v",
if _, e := c.rds.DelCtx(ctx, key); e != nil {
logger.Errorf("delete invalid cache, node: %s, key: %s, value: %s, error: %v",
c.rds.Addr, key, data, e)
}
@@ -233,6 +266,6 @@ func (c cacheNode) processCache(key, data string, v interface{}) error {
return c.errNotFound
}
func (c cacheNode) setCacheWithNotFound(key string) error {
return c.rds.Setex(key, notFoundPlaceholder, int(c.aroundDuration(c.notFoundExpiry).Seconds()))
func (c cacheNode) setCacheWithNotFound(ctx context.Context, key string) error {
return c.rds.SetexCtx(ctx, key, notFoundPlaceholder, int(c.aroundDuration(c.notFoundExpiry).Seconds()))
}

View File

@@ -24,6 +24,7 @@ type (
Expire(key string, seconds int) error
Expireat(key string, expireTime int64) error
Get(key string) (string, error)
GetSet(key, value string) (string, error)
Hdel(key, field string) (bool, error)
Hexists(key, field string) (bool, error)
Hget(key, field string) (string, error)
@@ -459,6 +460,15 @@ func (cs clusterStore) SetnxEx(key, value string, seconds int) (bool, error) {
return node.SetnxEx(key, value, seconds)
}
func (cs clusterStore) GetSet(key, value string) (string, error) {
node, err := cs.getRedis(key)
if err != nil {
return "", err
}
return node.GetSet(key, value)
}
func (cs clusterStore) Sismember(key string, value interface{}) (bool, error) {
node, err := cs.getRedis(key)
if err != nil {

View File

@@ -490,6 +490,29 @@ func TestRedis_SetExNx(t *testing.T) {
})
}
func TestRedis_Getset(t *testing.T) {
store := clusterStore{dispatcher: hash.NewConsistentHash()}
_, err := store.GetSet("hello", "world")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
val, err := client.GetSet("hello", "world")
assert.Nil(t, err)
assert.Equal(t, "", val)
val, err = client.Get("hello")
assert.Nil(t, err)
assert.Equal(t, "world", val)
val, err = client.GetSet("hello", "newworld")
assert.Nil(t, err)
assert.Equal(t, "world", val)
val, err = client.Get("hello")
assert.Nil(t, err)
assert.Equal(t, "newworld", val)
_, err = client.Del("hello")
assert.Nil(t, err)
})
}
func TestRedis_SetGetDelHashField(t *testing.T) {
store := clusterStore{dispatcher: hash.NewConsistentHash()}
err := store.Hset("key", "field", "value")

View File

@@ -12,8 +12,8 @@ var (
ErrNotFound = mgo.ErrNotFound
// can't use one SingleFlight per conn, because multiple conns may share the same cache key.
sharedCalls = syncx.NewSingleFlight()
stats = cache.NewStat("mongoc")
singleFlight = syncx.NewSingleFlight()
stats = cache.NewStat("mongoc")
)
type (

View File

@@ -34,7 +34,7 @@ func TestCollection_Count(t *testing.T) {
assert.Nil(t, err)
defer clean()
cach := cache.NewNode(r, sharedCalls, stats, mgo.ErrNotFound)
cach := cache.NewNode(r, singleFlight, stats, mgo.ErrNotFound)
c := newCollection(dummyConn{}, cach)
val, err := c.Count("any")
assert.Nil(t, err)
@@ -98,7 +98,7 @@ func TestStat(t *testing.T) {
assert.Nil(t, err)
defer clean()
cach := cache.NewNode(r, sharedCalls, stats, mgo.ErrNotFound)
cach := cache.NewNode(r, singleFlight, stats, mgo.ErrNotFound)
c := newCollection(dummyConn{}, cach).(*cachedCollection)
for i := 0; i < 10; i++ {
@@ -121,7 +121,7 @@ func TestStatCacheFails(t *testing.T) {
defer log.SetOutput(os.Stdout)
r := redis.New("localhost:59999")
cach := cache.NewNode(r, sharedCalls, stats, mgo.ErrNotFound)
cach := cache.NewNode(r, singleFlight, stats, mgo.ErrNotFound)
c := newCollection(dummyConn{}, cach)
for i := 0; i < 20; i++ {
@@ -142,7 +142,7 @@ func TestStatDbFails(t *testing.T) {
assert.Nil(t, err)
defer clean()
cach := cache.NewNode(r, sharedCalls, stats, mgo.ErrNotFound)
cach := cache.NewNode(r, singleFlight, stats, mgo.ErrNotFound)
c := newCollection(dummyConn{}, cach).(*cachedCollection)
for i := 0; i < 20; i++ {
@@ -164,7 +164,7 @@ func TestStatFromMemory(t *testing.T) {
assert.Nil(t, err)
defer clean()
cach := cache.NewNode(r, sharedCalls, stats, mgo.ErrNotFound)
cach := cache.NewNode(r, singleFlight, stats, mgo.ErrNotFound)
c := newCollection(dummyConn{}, cach).(*cachedCollection)
var all sync.WaitGroup

View File

@@ -38,7 +38,7 @@ func MustNewModel(url, collection string, c cache.CacheConf, opts ...cache.Optio
// NewModel returns a Model with a cache cluster.
func NewModel(url, collection string, conf cache.CacheConf, opts ...cache.Option) (*Model, error) {
c := cache.New(conf, sharedCalls, stats, mgo.ErrNotFound, opts...)
c := cache.New(conf, singleFlight, stats, mgo.ErrNotFound, opts...)
return NewModelWithCache(url, collection, c)
}
@@ -51,7 +51,7 @@ func NewModelWithCache(url, collection string, c cache.Cache) (*Model, error) {
// NewNodeModel returns a Model with a cache node.
func NewNodeModel(url, collection string, rds *redis.Redis, opts ...cache.Option) (*Model, error) {
c := cache.NewNode(rds, sharedCalls, stats, mgo.ErrNotFound, opts...)
c := cache.NewNode(rds, singleFlight, stats, mgo.ErrNotFound, opts...)
return NewModelWithCache(url, collection, c)
}

83
core/stores/redis/hook.go Normal file
View File

@@ -0,0 +1,83 @@
package redis
import (
"context"
"strings"
"time"
red "github.com/go-redis/redis/v8"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/mapping"
"github.com/zeromicro/go-zero/core/timex"
)
var (
startTimeKey = contextKey("startTime")
durationHook = hook{}
)
type (
contextKey string
hook struct{}
)
func (h hook) BeforeProcess(ctx context.Context, _ red.Cmder) (context.Context, error) {
return context.WithValue(ctx, startTimeKey, timex.Now()), nil
}
func (h hook) AfterProcess(ctx context.Context, cmd red.Cmder) error {
val := ctx.Value(startTimeKey)
if val == nil {
return nil
}
start, ok := val.(time.Duration)
if !ok {
return nil
}
duration := timex.Since(start)
if duration > slowThreshold.Load() {
logDuration(ctx, cmd, duration)
}
return nil
}
func (h hook) BeforeProcessPipeline(ctx context.Context, _ []red.Cmder) (context.Context, error) {
return context.WithValue(ctx, startTimeKey, timex.Now()), nil
}
func (h hook) AfterProcessPipeline(ctx context.Context, cmds []red.Cmder) error {
if len(cmds) == 0 {
return nil
}
val := ctx.Value(startTimeKey)
if val == nil {
return nil
}
start, ok := val.(time.Duration)
if !ok {
return nil
}
duration := timex.Since(start)
if duration > slowThreshold.Load()*time.Duration(len(cmds)) {
logDuration(ctx, cmds[0], duration)
}
return nil
}
func logDuration(ctx context.Context, cmd red.Cmder, duration time.Duration) {
var buf strings.Builder
for i, arg := range cmd.Args() {
if i > 0 {
buf.WriteByte(' ')
}
buf.WriteString(mapping.Repr(arg))
}
logx.WithContext(ctx).WithDuration(duration).Slowf("[REDIS] slowcall on executing: %s", buf.String())
}

View File

@@ -0,0 +1,137 @@
package redis
import (
"context"
"log"
"strings"
"testing"
"time"
red "github.com/go-redis/redis/v8"
"github.com/stretchr/testify/assert"
)
func TestHookProcessCase1(t *testing.T) {
writer := log.Writer()
var buf strings.Builder
log.SetOutput(&buf)
defer log.SetOutput(writer)
ctx, err := durationHook.BeforeProcess(context.Background(), nil)
if err != nil {
t.Fatal(err)
}
assert.Nil(t, durationHook.AfterProcess(ctx, red.NewCmd(context.Background())))
assert.False(t, strings.Contains(buf.String(), "slow"))
}
func TestHookProcessCase2(t *testing.T) {
writer := log.Writer()
var buf strings.Builder
log.SetOutput(&buf)
defer log.SetOutput(writer)
ctx, err := durationHook.BeforeProcess(context.Background(), nil)
if err != nil {
t.Fatal(err)
}
time.Sleep(slowThreshold.Load() + time.Millisecond)
assert.Nil(t, durationHook.AfterProcess(ctx, red.NewCmd(context.Background(), "foo", "bar")))
assert.True(t, strings.Contains(buf.String(), "slow"))
}
func TestHookProcessCase3(t *testing.T) {
writer := log.Writer()
var buf strings.Builder
log.SetOutput(&buf)
defer log.SetOutput(writer)
assert.Nil(t, durationHook.AfterProcess(context.Background(), red.NewCmd(context.Background())))
assert.True(t, buf.Len() == 0)
}
func TestHookProcessCase4(t *testing.T) {
writer := log.Writer()
var buf strings.Builder
log.SetOutput(&buf)
defer log.SetOutput(writer)
ctx := context.WithValue(context.Background(), startTimeKey, "foo")
assert.Nil(t, durationHook.AfterProcess(ctx, red.NewCmd(context.Background())))
assert.True(t, buf.Len() == 0)
}
func TestHookProcessPipelineCase1(t *testing.T) {
writer := log.Writer()
var buf strings.Builder
log.SetOutput(&buf)
defer log.SetOutput(writer)
ctx, err := durationHook.BeforeProcessPipeline(context.Background(), nil)
if err != nil {
t.Fatal(err)
}
assert.Nil(t, durationHook.AfterProcessPipeline(ctx, []red.Cmder{
red.NewCmd(context.Background()),
}))
assert.False(t, strings.Contains(buf.String(), "slow"))
}
func TestHookProcessPipelineCase2(t *testing.T) {
writer := log.Writer()
var buf strings.Builder
log.SetOutput(&buf)
defer log.SetOutput(writer)
ctx, err := durationHook.BeforeProcessPipeline(context.Background(), nil)
if err != nil {
t.Fatal(err)
}
time.Sleep(slowThreshold.Load() + time.Millisecond)
assert.Nil(t, durationHook.AfterProcessPipeline(ctx, []red.Cmder{
red.NewCmd(context.Background(), "foo", "bar"),
}))
assert.True(t, strings.Contains(buf.String(), "slow"))
}
func TestHookProcessPipelineCase3(t *testing.T) {
writer := log.Writer()
var buf strings.Builder
log.SetOutput(&buf)
defer log.SetOutput(writer)
assert.Nil(t, durationHook.AfterProcessPipeline(context.Background(), []red.Cmder{
red.NewCmd(context.Background()),
}))
assert.True(t, buf.Len() == 0)
}
func TestHookProcessPipelineCase4(t *testing.T) {
writer := log.Writer()
var buf strings.Builder
log.SetOutput(&buf)
defer log.SetOutput(writer)
ctx := context.WithValue(context.Background(), startTimeKey, "foo")
assert.Nil(t, durationHook.AfterProcessPipeline(ctx, []red.Cmder{
red.NewCmd(context.Background()),
}))
assert.True(t, buf.Len() == 0)
}
func TestHookProcessPipelineCase5(t *testing.T) {
writer := log.Writer()
var buf strings.Builder
log.SetOutput(&buf)
defer log.SetOutput(writer)
ctx := context.WithValue(context.Background(), startTimeKey, "foo")
assert.Nil(t, durationHook.AfterProcessPipeline(ctx, nil))
assert.True(t, buf.Len() == 0)
}

View File

@@ -1,32 +0,0 @@
package redis
import (
"strings"
red "github.com/go-redis/redis"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/mapping"
"github.com/zeromicro/go-zero/core/timex"
)
func checkDuration(proc func(red.Cmder) error) func(red.Cmder) error {
return func(cmd red.Cmder) error {
start := timex.Now()
defer func() {
duration := timex.Since(start)
if duration > slowThreshold.Load() {
var buf strings.Builder
for i, arg := range cmd.Args() {
if i > 0 {
buf.WriteByte(' ')
}
buf.WriteString(mapping.Repr(arg))
}
logx.WithDuration(duration).Slowf("[REDIS] slowcall on executing: %s", buf.String())
}
}()
return proc(cmd)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
package redis
import (
"context"
"crypto/tls"
"errors"
"io"
@@ -9,8 +10,9 @@ import (
"time"
"github.com/alicebob/miniredis/v2"
red "github.com/go-redis/redis"
red "github.com/go-redis/redis/v8"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stringx"
)
@@ -385,30 +387,33 @@ func TestRedis_Mget(t *testing.T) {
func TestRedis_SetBit(t *testing.T) {
runOnRedis(t, func(client *Redis) {
err := New(client.Addr, badType()).SetBit("key", 1, 1)
_, err := New(client.Addr, badType()).SetBit("key", 1, 1)
assert.NotNil(t, err)
err = client.SetBit("key", 1, 1)
val, err := client.SetBit("key", 1, 1)
assert.Nil(t, err)
assert.Equal(t, 0, val)
})
}
func TestRedis_GetBit(t *testing.T) {
runOnRedis(t, func(client *Redis) {
err := client.SetBit("key", 2, 1)
val, err := client.SetBit("key", 2, 1)
assert.Nil(t, err)
assert.Equal(t, 0, val)
_, err = New(client.Addr, badType()).GetBit("key", 2)
assert.NotNil(t, err)
val, err := client.GetBit("key", 2)
v, err := client.GetBit("key", 2)
assert.Nil(t, err)
assert.Equal(t, 1, val)
assert.Equal(t, 1, v)
})
}
func TestRedis_BitCount(t *testing.T) {
runOnRedis(t, func(client *Redis) {
for i := 0; i < 11; i++ {
err := client.SetBit("key", int64(i), 1)
val, err := client.SetBit("key", int64(i), 1)
assert.Nil(t, err)
assert.Equal(t, 0, val)
}
_, err := New(client.Addr, badType()).BitCount("key", 0, -1)
@@ -699,6 +704,28 @@ func TestRedis_Set(t *testing.T) {
})
}
func TestRedis_GetSet(t *testing.T) {
runOnRedis(t, func(client *Redis) {
_, err := New(client.Addr, badType()).GetSet("hello", "world")
assert.NotNil(t, err)
val, err := client.GetSet("hello", "world")
assert.Nil(t, err)
assert.Equal(t, "", val)
val, err = client.Get("hello")
assert.Nil(t, err)
assert.Equal(t, "world", val)
val, err = client.GetSet("hello", "newworld")
assert.Nil(t, err)
assert.Equal(t, "world", val)
val, err = client.Get("hello")
assert.Nil(t, err)
assert.Equal(t, "newworld", val)
ret, err := client.Del("hello")
assert.Nil(t, err)
assert.Equal(t, 1, ret)
})
}
func TestRedis_SetGetDel(t *testing.T) {
runOnRedis(t, func(client *Redis) {
err := New(client.Addr, badType()).Set("hello", "world")
@@ -963,13 +990,14 @@ func TestRedis_SortedSet(t *testing.T) {
assert.NotNil(t, err)
client.Zadd("second", 2, "aa")
client.Zadd("third", 3, "bbb")
val, err = client.Zunionstore("union", ZStore{
val, err = client.Zunionstore("union", &ZStore{
Keys: []string{"second", "third"},
Weights: []float64{1, 2},
Aggregate: "SUM",
}, "second", "third")
})
assert.Nil(t, err)
assert.Equal(t, int64(2), val)
_, err = New(client.Addr, badType()).Zunionstore("union", ZStore{})
_, err = New(client.Addr, badType()).Zunionstore("union", &ZStore{})
assert.NotNil(t, err)
vals, err = client.Zrange("union", 0, 10000)
assert.Nil(t, err)
@@ -987,9 +1015,9 @@ func TestRedis_Pipelined(t *testing.T) {
}))
err := client.Pipelined(
func(pipe Pipeliner) error {
pipe.Incr("pipelined_counter")
pipe.Expire("pipelined_counter", time.Hour)
pipe.ZAdd("zadd", Z{Score: 12, Member: "zadd"})
pipe.Incr(context.Background(), "pipelined_counter")
pipe.Expire(context.Background(), "pipelined_counter", time.Hour)
pipe.ZAdd(context.Background(), "zadd", &Z{Score: 12, Member: "zadd"})
return nil
},
)
@@ -1135,6 +1163,8 @@ func TestRedis_WithPass(t *testing.T) {
}
func runOnRedis(t *testing.T, fn func(client *Redis)) {
logx.Disable()
s, err := miniredis.Run()
assert.Nil(t, err)
defer func() {
@@ -1153,6 +1183,8 @@ func runOnRedis(t *testing.T, fn func(client *Redis)) {
}
func runOnRedisTLS(t *testing.T, fn func(client *Redis)) {
logx.Disable()
s, err := miniredis.RunTLS(&tls.Config{
Certificates: make([]tls.Certificate, 1),
InsecureSkipVerify: true,
@@ -1182,6 +1214,6 @@ type mockedNode struct {
RedisNode
}
func (n mockedNode) BLPop(timeout time.Duration, keys ...string) *red.StringSliceCmd {
return red.NewStringSliceCmd("foo", "bar")
func (n mockedNode) BLPop(ctx context.Context, timeout time.Duration, keys ...string) *red.StringSliceCmd {
return red.NewStringSliceCmd(context.Background(), "foo", "bar")
}

View File

@@ -3,7 +3,7 @@ package redis
import (
"fmt"
red "github.com/go-redis/redis"
red "github.com/go-redis/redis/v8"
"github.com/zeromicro/go-zero/core/logx"
)

View File

@@ -4,7 +4,7 @@ import (
"crypto/tls"
"io"
red "github.com/go-redis/redis"
red "github.com/go-redis/redis/v8"
"github.com/zeromicro/go-zero/core/syncx"
)
@@ -32,7 +32,8 @@ func getClient(r *Redis) (*red.Client, error) {
MinIdleConns: idleConns,
TLSConfig: tlsConfig,
})
store.WrapProcess(checkDuration)
store.AddHook(durationHook)
return store, nil
})
if err != nil {

View File

@@ -4,7 +4,7 @@ import (
"crypto/tls"
"io"
red "github.com/go-redis/redis"
red "github.com/go-redis/redis/v8"
"github.com/zeromicro/go-zero/core/syncx"
)
@@ -25,7 +25,7 @@ func getCluster(r *Redis) (*red.ClusterClient, error) {
MinIdleConns: idleConns,
TLSConfig: tlsConfig,
})
store.WrapProcess(checkDuration)
store.AddHook(durationHook)
return store, nil
})

View File

@@ -2,28 +2,36 @@ package redis
import (
"math/rand"
"strconv"
"sync/atomic"
"time"
red "github.com/go-redis/redis"
red "github.com/go-redis/redis/v8"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stringx"
)
const (
randomLen = 16
tolerance = 500 // milliseconds
millisPerSecond = 1000
lockCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2])
return "OK"
else
return redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])
end`
delCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end`
randomLen = 16
)
// A RedisLock is a redis lock.
type RedisLock struct {
store *Redis
seconds uint32
count int32
key string
id string
}
@@ -43,35 +51,30 @@ func NewRedisLock(store *Redis, key string) *RedisLock {
// Acquire acquires the lock.
func (rl *RedisLock) Acquire() (bool, error) {
newCount := atomic.AddInt32(&rl.count, 1)
if newCount > 1 {
seconds := atomic.LoadUint32(&rl.seconds)
resp, err := rl.store.Eval(lockCommand, []string{rl.key}, []string{
rl.id, strconv.Itoa(int(seconds)*millisPerSecond + tolerance),
})
if err == red.Nil {
return false, nil
} else if err != nil {
logx.Errorf("Error on acquiring lock for %s, %s", rl.key, err.Error())
return false, err
} else if resp == nil {
return false, nil
}
reply, ok := resp.(string)
if ok && reply == "OK" {
return true, nil
}
seconds := atomic.LoadUint32(&rl.seconds)
ok, err := rl.store.SetnxEx(rl.key, rl.id, int(seconds+1)) // +1s for tolerance
if err == red.Nil {
atomic.AddInt32(&rl.count, -1)
return false, nil
} else if err != nil {
atomic.AddInt32(&rl.count, -1)
logx.Errorf("Error on acquiring lock for %s, %s", rl.key, err.Error())
return false, err
} else if !ok {
atomic.AddInt32(&rl.count, -1)
return false, nil
}
return true, nil
logx.Errorf("Unknown reply when acquiring lock for %s: %v", rl.key, resp)
return false, nil
}
// Release releases the lock.
func (rl *RedisLock) Release() (bool, error) {
newCount := atomic.AddInt32(&rl.count, -1)
if newCount > 0 {
return true, nil
}
resp, err := rl.store.Eval(delCommand, []string{rl.key}, []string{rl.id})
if err != nil {
return false, err

View File

@@ -29,25 +29,5 @@ func TestRedisLock(t *testing.T) {
endAcquire, err := secondLock.Acquire()
assert.Nil(t, err)
assert.True(t, endAcquire)
endAcquire, err = secondLock.Acquire()
assert.Nil(t, err)
assert.True(t, endAcquire)
release, err = secondLock.Release()
assert.Nil(t, err)
assert.True(t, release)
againAcquire, err = firstLock.Acquire()
assert.Nil(t, err)
assert.False(t, againAcquire)
release, err = secondLock.Release()
assert.Nil(t, err)
assert.True(t, release)
firstAcquire, err = firstLock.Acquire()
assert.Nil(t, err)
assert.True(t, firstAcquire)
})
}

View File

@@ -17,10 +17,12 @@ func CreateRedis() (r *redis.Redis, clean func(), err error) {
return redis.New(mr.Addr()), func() {
ch := make(chan lang.PlaceholderType)
go func() {
mr.Close()
close(ch)
}()
select {
case <-ch:
case <-time.After(time.Second):

View File

@@ -4,9 +4,12 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/logx"
)
func TestScriptCache(t *testing.T) {
logx.Disable()
cache := GetScriptCache()
cache.SetSha("foo", "bar")
cache.SetSha("bla", "blabla")

View File

@@ -1,6 +1,7 @@
package sqlc
import (
"context"
"database/sql"
"time"
@@ -18,19 +19,27 @@ var (
ErrNotFound = sqlx.ErrNotFound
// can't use one SingleFlight per conn, because multiple conns may share the same cache key.
exclusiveCalls = syncx.NewSingleFlight()
stats = cache.NewStat("sqlc")
singleFlights = syncx.NewSingleFlight()
stats = cache.NewStat("sqlc")
)
type (
// ExecFn defines the sql exec method.
ExecFn func(conn sqlx.SqlConn) (sql.Result, error)
// ExecCtxFn defines the sql exec method.
ExecCtxFn func(ctx context.Context, conn sqlx.SqlConn) (sql.Result, error)
// IndexQueryFn defines the query method that based on unique indexes.
IndexQueryFn func(conn sqlx.SqlConn, v interface{}) (interface{}, error)
// IndexQueryCtxFn defines the query method that based on unique indexes.
IndexQueryCtxFn func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (interface{}, error)
// PrimaryQueryFn defines the query method that based on primary keys.
PrimaryQueryFn func(conn sqlx.SqlConn, v, primary interface{}) error
// PrimaryQueryCtxFn defines the query method that based on primary keys.
PrimaryQueryCtxFn func(ctx context.Context, conn sqlx.SqlConn, v, primary interface{}) error
// QueryFn defines the query method.
QueryFn func(conn sqlx.SqlConn, v interface{}) error
// QueryCtxFn defines the query method.
QueryCtxFn func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error
// A CachedConn is a DB connection with cache capability.
CachedConn struct {
@@ -41,7 +50,7 @@ type (
// NewConn returns a CachedConn with a redis cluster cache.
func NewConn(db sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) CachedConn {
cc := cache.New(c, exclusiveCalls, stats, sql.ErrNoRows, opts...)
cc := cache.New(c, singleFlights, stats, sql.ErrNoRows, opts...)
return NewConnWithCache(db, cc)
}
@@ -55,28 +64,46 @@ func NewConnWithCache(db sqlx.SqlConn, c cache.Cache) CachedConn {
// NewNodeConn returns a CachedConn with a redis node cache.
func NewNodeConn(db sqlx.SqlConn, rds *redis.Redis, opts ...cache.Option) CachedConn {
c := cache.NewNode(rds, exclusiveCalls, stats, sql.ErrNoRows, opts...)
c := cache.NewNode(rds, singleFlights, stats, sql.ErrNoRows, opts...)
return NewConnWithCache(db, c)
}
// DelCache deletes cache with keys.
func (cc CachedConn) DelCache(keys ...string) error {
return cc.cache.Del(keys...)
return cc.DelCacheCtx(context.Background(), keys...)
}
// DelCacheCtx deletes cache with keys.
func (cc CachedConn) DelCacheCtx(ctx context.Context, keys ...string) error {
return cc.cache.DelCtx(ctx, keys...)
}
// GetCache unmarshals cache with given key into v.
func (cc CachedConn) GetCache(key string, v interface{}) error {
return cc.cache.Get(key, v)
return cc.GetCacheCtx(context.Background(), key, v)
}
// GetCacheCtx unmarshals cache with given key into v.
func (cc CachedConn) GetCacheCtx(ctx context.Context, key string, v interface{}) error {
return cc.cache.GetCtx(ctx, key, v)
}
// Exec runs given exec on given keys, and returns execution result.
func (cc CachedConn) Exec(exec ExecFn, keys ...string) (sql.Result, error) {
res, err := exec(cc.db)
execCtx := func(_ context.Context, conn sqlx.SqlConn) (sql.Result, error) {
return exec(conn)
}
return cc.ExecCtx(context.Background(), execCtx, keys...)
}
// ExecCtx runs given exec on given keys, and returns execution result.
func (cc CachedConn) ExecCtx(ctx context.Context, exec ExecCtxFn, keys ...string) (sql.Result, error) {
res, err := exec(ctx, cc.db)
if err != nil {
return nil, err
}
if err := cc.DelCache(keys...); err != nil {
if err := cc.DelCacheCtx(ctx, keys...); err != nil {
return nil, err
}
@@ -85,31 +112,61 @@ func (cc CachedConn) Exec(exec ExecFn, keys ...string) (sql.Result, error) {
// ExecNoCache runs exec with given sql statement, without affecting cache.
func (cc CachedConn) ExecNoCache(q string, args ...interface{}) (sql.Result, error) {
return cc.db.Exec(q, args...)
return cc.ExecNoCacheCtx(context.Background(), q, args...)
}
// ExecNoCacheCtx runs exec with given sql statement, without affecting cache.
func (cc CachedConn) ExecNoCacheCtx(ctx context.Context, q string, args ...interface{}) (
sql.Result, error) {
return cc.db.ExecCtx(ctx, q, args...)
}
// QueryRow unmarshals into v with given key and query func.
func (cc CachedConn) QueryRow(v interface{}, key string, query QueryFn) error {
return cc.cache.Take(v, key, func(v interface{}) error {
return query(cc.db, v)
queryCtx := func(_ context.Context, conn sqlx.SqlConn, v interface{}) error {
return query(conn, v)
}
return cc.QueryRowCtx(context.Background(), v, key, queryCtx)
}
// QueryRowCtx unmarshals into v with given key and query func.
func (cc CachedConn) QueryRowCtx(ctx context.Context, v interface{}, key string, query QueryCtxFn) error {
return cc.cache.TakeCtx(ctx, v, key, func(v interface{}) error {
return query(ctx, cc.db, v)
})
}
// QueryRowIndex unmarshals into v with given key.
func (cc CachedConn) QueryRowIndex(v interface{}, key string, keyer func(primary interface{}) string,
indexQuery IndexQueryFn, primaryQuery PrimaryQueryFn) error {
indexQueryCtx := func(_ context.Context, conn sqlx.SqlConn, v interface{}) (interface{}, error) {
return indexQuery(conn, v)
}
primaryQueryCtx := func(_ context.Context, conn sqlx.SqlConn, v, primary interface{}) error {
return primaryQuery(conn, v, primary)
}
return cc.QueryRowIndexCtx(context.Background(), v, key, keyer, indexQueryCtx, primaryQueryCtx)
}
// QueryRowIndexCtx unmarshals into v with given key.
func (cc CachedConn) QueryRowIndexCtx(ctx context.Context, v interface{}, key string,
keyer func(primary interface{}) string, indexQuery IndexQueryCtxFn,
primaryQuery PrimaryQueryCtxFn) error {
var primaryKey interface{}
var found bool
if err := cc.cache.TakeWithExpire(&primaryKey, key, func(val interface{}, expire time.Duration) (err error) {
primaryKey, err = indexQuery(cc.db, v)
if err != nil {
return
}
if err := cc.cache.TakeWithExpireCtx(ctx, &primaryKey, key,
func(val interface{}, expire time.Duration) (err error) {
primaryKey, err = indexQuery(ctx, cc.db, v)
if err != nil {
return
}
found = true
return cc.cache.SetWithExpire(keyer(primaryKey), v, expire+cacheSafeGapBetweenIndexAndPrimary)
}); err != nil {
found = true
return cc.cache.SetWithExpireCtx(ctx, keyer(primaryKey), v,
expire+cacheSafeGapBetweenIndexAndPrimary)
}); err != nil {
return err
}
@@ -117,28 +174,54 @@ func (cc CachedConn) QueryRowIndex(v interface{}, key string, keyer func(primary
return nil
}
return cc.cache.Take(v, keyer(primaryKey), func(v interface{}) error {
return primaryQuery(cc.db, v, primaryKey)
return cc.cache.TakeCtx(ctx, v, keyer(primaryKey), func(v interface{}) error {
return primaryQuery(ctx, cc.db, v, primaryKey)
})
}
// QueryRowNoCache unmarshals into v with given statement.
func (cc CachedConn) QueryRowNoCache(v interface{}, q string, args ...interface{}) error {
return cc.db.QueryRow(v, q, args...)
return cc.QueryRowNoCacheCtx(context.Background(), v, q, args...)
}
// QueryRowNoCacheCtx unmarshals into v with given statement.
func (cc CachedConn) QueryRowNoCacheCtx(ctx context.Context, v interface{}, q string,
args ...interface{}) error {
return cc.db.QueryRowCtx(ctx, v, q, args...)
}
// QueryRowsNoCache unmarshals into v with given statement.
// It doesn't use cache, because it might cause consistency problem.
func (cc CachedConn) QueryRowsNoCache(v interface{}, q string, args ...interface{}) error {
return cc.db.QueryRows(v, q, args...)
return cc.QueryRowsNoCacheCtx(context.Background(), v, q, args...)
}
// QueryRowsNoCacheCtx unmarshals into v with given statement.
// It doesn't use cache, because it might cause consistency problem.
func (cc CachedConn) QueryRowsNoCacheCtx(ctx context.Context, v interface{}, q string,
args ...interface{}) error {
return cc.db.QueryRowsCtx(ctx, v, q, args...)
}
// SetCache sets v into cache with given key.
func (cc CachedConn) SetCache(key string, v interface{}) error {
return cc.cache.Set(key, v)
func (cc CachedConn) SetCache(key string, val interface{}) error {
return cc.SetCacheCtx(context.Background(), key, val)
}
// SetCacheCtx sets v into cache with given key.
func (cc CachedConn) SetCacheCtx(ctx context.Context, key string, val interface{}) error {
return cc.cache.SetCtx(ctx, key, val)
}
// Transact runs given fn in transaction mode.
func (cc CachedConn) Transact(fn func(sqlx.Session) error) error {
return cc.db.Transact(fn)
fnCtx := func(_ context.Context, session sqlx.Session) error {
return fn(session)
}
return cc.TransactCtx(context.Background(), fnCtx)
}
// TransactCtx runs given fn in transaction mode.
func (cc CachedConn) TransactCtx(ctx context.Context, fn func(context.Context, sqlx.Session) error) error {
return cc.db.TransactCtx(ctx, fn)
}

View File

@@ -1,6 +1,7 @@
package sqlc
import (
"context"
"database/sql"
"encoding/json"
"errors"
@@ -568,7 +569,7 @@ func TestNewConnWithCache(t *testing.T) {
defer clean()
var conn trackedConn
c := NewConnWithCache(&conn, cache.NewNode(r, exclusiveCalls, stats, sql.ErrNoRows))
c := NewConnWithCache(&conn, cache.NewNode(r, singleFlights, stats, sql.ErrNoRows))
_, err = c.ExecNoCache("delete from user_table where id='kevin'")
assert.Nil(t, err)
assert.True(t, conn.execValue)
@@ -585,6 +586,30 @@ type dummySqlConn struct {
queryRow func(interface{}, string, ...interface{}) error
}
func (d dummySqlConn) ExecCtx(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
return nil, nil
}
func (d dummySqlConn) PrepareCtx(ctx context.Context, query string) (sqlx.StmtSession, error) {
return nil, nil
}
func (d dummySqlConn) QueryRowPartialCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error {
return nil
}
func (d dummySqlConn) QueryRowsCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error {
return nil
}
func (d dummySqlConn) QueryRowsPartialCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error {
return nil
}
func (d dummySqlConn) TransactCtx(ctx context.Context, fn func(context.Context, sqlx.Session) error) error {
return nil
}
func (d dummySqlConn) Exec(query string, args ...interface{}) (sql.Result, error) {
return nil, nil
}
@@ -594,6 +619,10 @@ func (d dummySqlConn) Prepare(query string) (sqlx.StmtSession, error) {
}
func (d dummySqlConn) QueryRow(v interface{}, query string, args ...interface{}) error {
return d.QueryRowCtx(context.Background(), v, query, args...)
}
func (d dummySqlConn) QueryRowCtx(_ context.Context, v interface{}, query string, args ...interface{}) error {
if d.queryRow != nil {
return d.queryRow(v, query, args...)
}
@@ -628,13 +657,21 @@ type trackedConn struct {
}
func (c *trackedConn) Exec(query string, args ...interface{}) (sql.Result, error) {
return c.ExecCtx(context.Background(), query, args...)
}
func (c *trackedConn) ExecCtx(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
c.execValue = true
return c.dummySqlConn.Exec(query, args...)
return c.dummySqlConn.ExecCtx(ctx, query, args...)
}
func (c *trackedConn) QueryRows(v interface{}, query string, args ...interface{}) error {
return c.QueryRowsCtx(context.Background(), v, query, args...)
}
func (c *trackedConn) QueryRowsCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error {
c.queryRowsValue = true
return c.dummySqlConn.QueryRows(v, query, args...)
return c.dummySqlConn.QueryRowsCtx(ctx, v, query, args...)
}
func (c *trackedConn) RawDB() (*sql.DB, error) {
@@ -642,6 +679,12 @@ func (c *trackedConn) RawDB() (*sql.DB, error) {
}
func (c *trackedConn) Transact(fn func(session sqlx.Session) error) error {
c.transactValue = true
return c.dummySqlConn.Transact(fn)
return c.TransactCtx(context.Background(), func(_ context.Context, session sqlx.Session) error {
return fn(session)
})
}
func (c *trackedConn) TransactCtx(ctx context.Context, fn func(context.Context, sqlx.Session) error) error {
c.transactValue = true
return c.dummySqlConn.TransactCtx(ctx, fn)
}

View File

@@ -25,6 +25,7 @@ type (
// A BulkInserter is used to batch insert records.
// Postgresql is not supported yet, because of the sql is formated with symbol `$`.
// Oracle is not supported yet, because of the sql is formated with symbol `:`.
BulkInserter struct {
executor *executors.PeriodicalExecutor
inserter *dbInserter

View File

@@ -1,6 +1,7 @@
package sqlx
import (
"context"
"database/sql"
"errors"
"strconv"
@@ -17,12 +18,40 @@ type mockedConn struct {
execErr error
}
func (c *mockedConn) Exec(query string, args ...interface{}) (sql.Result, error) {
func (c *mockedConn) ExecCtx(_ context.Context, query string, args ...interface{}) (sql.Result, error) {
c.query = query
c.args = args
return nil, c.execErr
}
func (c *mockedConn) PrepareCtx(ctx context.Context, query string) (StmtSession, error) {
panic("implement me")
}
func (c *mockedConn) QueryRowCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error {
panic("implement me")
}
func (c *mockedConn) QueryRowPartialCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error {
panic("implement me")
}
func (c *mockedConn) QueryRowsCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error {
panic("implement me")
}
func (c *mockedConn) QueryRowsPartialCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error {
panic("implement me")
}
func (c *mockedConn) TransactCtx(ctx context.Context, fn func(context.Context, Session) error) error {
panic("should not called")
}
func (c *mockedConn) Exec(query string, args ...interface{}) (sql.Result, error) {
return c.ExecCtx(context.Background(), query, args...)
}
func (c *mockedConn) Prepare(query string) (StmtSession, error) {
panic("should not called")
}

View File

@@ -1,6 +1,7 @@
package sqlx
import (
"context"
"database/sql"
"errors"
"testing"
@@ -16,7 +17,7 @@ func TestUnmarshalRowBool(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value bool
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.True(t, value)
@@ -29,7 +30,7 @@ func TestUnmarshalRowBoolNotSettable(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value bool
assert.NotNil(t, query(db, func(rows *sql.Rows) error {
assert.NotNil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(value, rows, true)
}, "select value from users where user=?", "anyone"))
})
@@ -41,7 +42,7 @@ func TestUnmarshalRowInt(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value int
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, 2, value)
@@ -54,7 +55,7 @@ func TestUnmarshalRowInt8(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value int8
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, int8(3), value)
@@ -67,7 +68,7 @@ func TestUnmarshalRowInt16(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value int16
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.Equal(t, int16(4), value)
@@ -80,7 +81,7 @@ func TestUnmarshalRowInt32(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value int32
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.Equal(t, int32(5), value)
@@ -93,7 +94,7 @@ func TestUnmarshalRowInt64(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value int64
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, int64(6), value)
@@ -106,7 +107,7 @@ func TestUnmarshalRowUint(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value uint
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, uint(2), value)
@@ -119,7 +120,7 @@ func TestUnmarshalRowUint8(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value uint8
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, uint8(3), value)
@@ -132,7 +133,7 @@ func TestUnmarshalRowUint16(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value uint16
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, uint16(4), value)
@@ -145,7 +146,7 @@ func TestUnmarshalRowUint32(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value uint32
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, uint32(5), value)
@@ -158,7 +159,7 @@ func TestUnmarshalRowUint64(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value uint64
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, uint16(6), value)
@@ -171,7 +172,7 @@ func TestUnmarshalRowFloat32(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value float32
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, float32(7), value)
@@ -184,7 +185,7 @@ func TestUnmarshalRowFloat64(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value float64
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, float64(8), value)
@@ -198,7 +199,7 @@ func TestUnmarshalRowString(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value string
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -215,7 +216,7 @@ func TestUnmarshalRowStruct(t *testing.T) {
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("liao,5")
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(value, rows, true)
}, "select name, age from users where user=?", "anyone"))
assert.Equal(t, "liao", value.Name)
@@ -233,7 +234,7 @@ func TestUnmarshalRowStructWithTags(t *testing.T) {
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("liao,5")
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(value, rows, true)
}, "select name, age from users where user=?", "anyone"))
assert.Equal(t, "liao", value.Name)
@@ -251,7 +252,7 @@ func TestUnmarshalRowStructWithTagsWrongColumns(t *testing.T) {
rs := sqlmock.NewRows([]string{"name"}).FromCSVString("liao")
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
assert.NotNil(t, query(db, func(rows *sql.Rows) error {
assert.NotNil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(value, rows, true)
}, "select name, age from users where user=?", "anyone"))
})
@@ -264,7 +265,7 @@ func TestUnmarshalRowsBool(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []bool
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -278,7 +279,7 @@ func TestUnmarshalRowsInt(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []int
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -292,7 +293,7 @@ func TestUnmarshalRowsInt8(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []int8
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -306,7 +307,7 @@ func TestUnmarshalRowsInt16(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []int16
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -320,7 +321,7 @@ func TestUnmarshalRowsInt32(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []int32
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -334,7 +335,7 @@ func TestUnmarshalRowsInt64(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []int64
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -348,7 +349,7 @@ func TestUnmarshalRowsUint(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []uint
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -362,7 +363,7 @@ func TestUnmarshalRowsUint8(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []uint8
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -376,7 +377,7 @@ func TestUnmarshalRowsUint16(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []uint16
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -390,7 +391,7 @@ func TestUnmarshalRowsUint32(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []uint32
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -404,7 +405,7 @@ func TestUnmarshalRowsUint64(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []uint64
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -418,7 +419,7 @@ func TestUnmarshalRowsFloat32(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []float32
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -432,7 +433,7 @@ func TestUnmarshalRowsFloat64(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []float64
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -446,7 +447,7 @@ func TestUnmarshalRowsString(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []string
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -462,7 +463,7 @@ func TestUnmarshalRowsBoolPtr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*bool
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -478,7 +479,7 @@ func TestUnmarshalRowsIntPtr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*int
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -494,7 +495,7 @@ func TestUnmarshalRowsInt8Ptr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*int8
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -510,7 +511,7 @@ func TestUnmarshalRowsInt16Ptr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*int16
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -526,7 +527,7 @@ func TestUnmarshalRowsInt32Ptr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*int32
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -542,7 +543,7 @@ func TestUnmarshalRowsInt64Ptr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*int64
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -558,7 +559,7 @@ func TestUnmarshalRowsUintPtr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*uint
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -574,7 +575,7 @@ func TestUnmarshalRowsUint8Ptr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*uint8
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -590,7 +591,7 @@ func TestUnmarshalRowsUint16Ptr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*uint16
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -606,7 +607,7 @@ func TestUnmarshalRowsUint32Ptr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*uint32
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -622,7 +623,7 @@ func TestUnmarshalRowsUint64Ptr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*uint64
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -638,7 +639,7 @@ func TestUnmarshalRowsFloat32Ptr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*float32
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -654,7 +655,7 @@ func TestUnmarshalRowsFloat64Ptr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*float64
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -670,7 +671,7 @@ func TestUnmarshalRowsStringPtr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*string
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -699,7 +700,7 @@ func TestUnmarshalRowsStruct(t *testing.T) {
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("first,2\nsecond,3")
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select name, age from users where user=?", "anyone"))
@@ -739,7 +740,7 @@ func TestUnmarshalRowsStructWithNullStringType(t *testing.T) {
rs := sqlmock.NewRows([]string{"name", "value"}).AddRow(
"first", "firstnullstring").AddRow("second", nil)
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select name, age from users where user=?", "anyone"))
@@ -773,7 +774,7 @@ func TestUnmarshalRowsStructWithTags(t *testing.T) {
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("first,2\nsecond,3")
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select name, age from users where user=?", "anyone"))
@@ -814,7 +815,7 @@ func TestUnmarshalRowsStructAndEmbeddedAnonymousStructWithTags(t *testing.T) {
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
rs := sqlmock.NewRows([]string{"name", "age", "value"}).FromCSVString("first,2,3\nsecond,3,4")
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select name, age, value from users where user=?", "anyone"))
@@ -856,7 +857,7 @@ func TestUnmarshalRowsStructAndEmbeddedStructPtrAnonymousWithTags(t *testing.T)
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
rs := sqlmock.NewRows([]string{"name", "age", "value"}).FromCSVString("first,2,3\nsecond,3,4")
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select name, age, value from users where user=?", "anyone"))
@@ -890,7 +891,7 @@ func TestUnmarshalRowsStructPtr(t *testing.T) {
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("first,2\nsecond,3")
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select name, age from users where user=?", "anyone"))
@@ -923,7 +924,7 @@ func TestUnmarshalRowsStructWithTagsPtr(t *testing.T) {
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("first,2\nsecond,3")
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select name, age from users where user=?", "anyone"))
@@ -956,7 +957,7 @@ func TestUnmarshalRowsStructWithTagsPtrWithInnerPtr(t *testing.T) {
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("first,2\nsecond,3")
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select name, age from users where user=?", "anyone"))
@@ -976,7 +977,7 @@ func TestCommonSqlConn_QueryRowOptional(t *testing.T) {
User string `db:"user"`
Age int `db:"age"`
}
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&r, rows, false)
}, "select age from users where user=?", "anyone"))
assert.Empty(t, r.User)
@@ -1027,7 +1028,7 @@ func TestUnmarshalRowError(t *testing.T) {
User string `db:"user"`
Age int `db:"age"`
}
test.validate(query(db, func(rows *sql.Rows) error {
test.validate(query(context.Background(), db, func(rows *sql.Rows) error {
scanner := mockedScanner{
colErr: test.colErr,
scanErr: test.scanErr,

View File

@@ -1,6 +1,7 @@
package sqlx
import (
"context"
"database/sql"
"github.com/zeromicro/go-zero/core/breaker"
@@ -14,11 +15,17 @@ type (
// Session stands for raw connections or transaction sessions
Session interface {
Exec(query string, args ...interface{}) (sql.Result, error)
ExecCtx(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
Prepare(query string) (StmtSession, error)
PrepareCtx(ctx context.Context, query string) (StmtSession, error)
QueryRow(v interface{}, query string, args ...interface{}) error
QueryRowCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error
QueryRowPartial(v interface{}, query string, args ...interface{}) error
QueryRowPartialCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error
QueryRows(v interface{}, query string, args ...interface{}) error
QueryRowsCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error
QueryRowsPartial(v interface{}, query string, args ...interface{}) error
QueryRowsPartialCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error
}
// SqlConn only stands for raw connections, so Transact method can be called.
@@ -27,7 +34,8 @@ type (
// RawDB is for other ORM to operate with, use it with caution.
// Notice: don't close it.
RawDB() (*sql.DB, error)
Transact(func(session Session) error) error
Transact(fn func(Session) error) error
TransactCtx(ctx context.Context, fn func(context.Context, Session) error) error
}
// SqlOption defines the method to customize a sql connection.
@@ -37,10 +45,15 @@ type (
StmtSession interface {
Close() error
Exec(args ...interface{}) (sql.Result, error)
ExecCtx(ctx context.Context, args ...interface{}) (sql.Result, error)
QueryRow(v interface{}, args ...interface{}) error
QueryRowCtx(ctx context.Context, v interface{}, args ...interface{}) error
QueryRowPartial(v interface{}, args ...interface{}) error
QueryRowPartialCtx(ctx context.Context, v interface{}, args ...interface{}) error
QueryRows(v interface{}, args ...interface{}) error
QueryRowsCtx(ctx context.Context, v interface{}, args ...interface{}) error
QueryRowsPartial(v interface{}, args ...interface{}) error
QueryRowsPartialCtx(ctx context.Context, v interface{}, args ...interface{}) error
}
// thread-safe
@@ -58,7 +71,9 @@ type (
sessionConn interface {
Exec(query string, args ...interface{}) (sql.Result, error)
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
Query(query string, args ...interface{}) (*sql.Rows, error)
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
}
statement struct {
@@ -68,7 +83,9 @@ type (
stmtConn interface {
Exec(args ...interface{}) (sql.Result, error)
ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error)
Query(args ...interface{}) (*sql.Rows, error)
QueryContext(ctx context.Context, args ...interface{}) (*sql.Rows, error)
}
)
@@ -112,6 +129,11 @@ func NewSqlConnFromDB(db *sql.DB, opts ...SqlOption) SqlConn {
}
func (db *commonSqlConn) Exec(q string, args ...interface{}) (result sql.Result, err error) {
return db.ExecCtx(context.Background(), q, args...)
}
func (db *commonSqlConn) ExecCtx(ctx context.Context, q string, args ...interface{}) (
result sql.Result, err error) {
err = db.brk.DoWithAcceptable(func() error {
var conn *sql.DB
conn, err = db.connProv()
@@ -120,7 +142,7 @@ func (db *commonSqlConn) Exec(q string, args ...interface{}) (result sql.Result,
return err
}
result, err = exec(conn, q, args...)
result, err = exec(ctx, conn, q, args...)
return err
}, db.acceptable)
@@ -128,6 +150,10 @@ func (db *commonSqlConn) Exec(q string, args ...interface{}) (result sql.Result,
}
func (db *commonSqlConn) Prepare(query string) (stmt StmtSession, err error) {
return db.PrepareCtx(context.Background(), query)
}
func (db *commonSqlConn) PrepareCtx(ctx context.Context, query string) (stmt StmtSession, err error) {
err = db.brk.DoWithAcceptable(func() error {
var conn *sql.DB
conn, err = db.connProv()
@@ -136,7 +162,7 @@ func (db *commonSqlConn) Prepare(query string) (stmt StmtSession, err error) {
return err
}
st, err := conn.Prepare(query)
st, err := conn.PrepareContext(ctx, query)
if err != nil {
return err
}
@@ -152,25 +178,45 @@ func (db *commonSqlConn) Prepare(query string) (stmt StmtSession, err error) {
}
func (db *commonSqlConn) QueryRow(v interface{}, q string, args ...interface{}) error {
return db.queryRows(func(rows *sql.Rows) error {
return db.QueryRowCtx(context.Background(), v, q, args...)
}
func (db *commonSqlConn) QueryRowCtx(ctx context.Context, v interface{}, q string,
args ...interface{}) error {
return db.queryRows(ctx, func(rows *sql.Rows) error {
return unmarshalRow(v, rows, true)
}, q, args...)
}
func (db *commonSqlConn) QueryRowPartial(v interface{}, q string, args ...interface{}) error {
return db.queryRows(func(rows *sql.Rows) error {
return db.QueryRowPartialCtx(context.Background(), v, q, args...)
}
func (db *commonSqlConn) QueryRowPartialCtx(ctx context.Context, v interface{},
q string, args ...interface{}) error {
return db.queryRows(ctx, func(rows *sql.Rows) error {
return unmarshalRow(v, rows, false)
}, q, args...)
}
func (db *commonSqlConn) QueryRows(v interface{}, q string, args ...interface{}) error {
return db.queryRows(func(rows *sql.Rows) error {
return db.QueryRowsCtx(context.Background(), v, q, args...)
}
func (db *commonSqlConn) QueryRowsCtx(ctx context.Context, v interface{}, q string,
args ...interface{}) error {
return db.queryRows(ctx, func(rows *sql.Rows) error {
return unmarshalRows(v, rows, true)
}, q, args...)
}
func (db *commonSqlConn) QueryRowsPartial(v interface{}, q string, args ...interface{}) error {
return db.queryRows(func(rows *sql.Rows) error {
return db.QueryRowsPartialCtx(context.Background(), v, q, args...)
}
func (db *commonSqlConn) QueryRowsPartialCtx(ctx context.Context, v interface{},
q string, args ...interface{}) error {
return db.queryRows(ctx, func(rows *sql.Rows) error {
return unmarshalRows(v, rows, false)
}, q, args...)
}
@@ -180,13 +226,19 @@ func (db *commonSqlConn) RawDB() (*sql.DB, error) {
}
func (db *commonSqlConn) Transact(fn func(Session) error) error {
return db.TransactCtx(context.Background(), func(_ context.Context, session Session) error {
return fn(session)
})
}
func (db *commonSqlConn) TransactCtx(ctx context.Context, fn func(context.Context, Session) error) error {
return db.brk.DoWithAcceptable(func() error {
return transact(db, db.beginTx, fn)
return transact(ctx, db, db.beginTx, fn)
}, db.acceptable)
}
func (db *commonSqlConn) acceptable(err error) bool {
ok := err == nil || err == sql.ErrNoRows || err == sql.ErrTxDone
ok := err == nil || err == sql.ErrNoRows || err == sql.ErrTxDone || err == context.Canceled
if db.accept == nil {
return ok
}
@@ -194,7 +246,8 @@ func (db *commonSqlConn) acceptable(err error) bool {
return ok || db.accept(err)
}
func (db *commonSqlConn) queryRows(scanner func(*sql.Rows) error, q string, args ...interface{}) error {
func (db *commonSqlConn) queryRows(ctx context.Context, scanner func(*sql.Rows) error,
q string, args ...interface{}) error {
var qerr error
return db.brk.DoWithAcceptable(func() error {
conn, err := db.connProv()
@@ -203,7 +256,7 @@ func (db *commonSqlConn) queryRows(scanner func(*sql.Rows) error, q string, args
return err
}
return query(conn, func(rows *sql.Rows) error {
return query(ctx, conn, func(rows *sql.Rows) error {
qerr = scanner(rows)
return qerr
}, q, args...)
@@ -217,29 +270,49 @@ func (s statement) Close() error {
}
func (s statement) Exec(args ...interface{}) (sql.Result, error) {
return execStmt(s.stmt, s.query, args...)
return s.ExecCtx(context.Background(), args...)
}
func (s statement) ExecCtx(ctx context.Context, args ...interface{}) (sql.Result, error) {
return execStmt(ctx, s.stmt, s.query, args...)
}
func (s statement) QueryRow(v interface{}, args ...interface{}) error {
return queryStmt(s.stmt, func(rows *sql.Rows) error {
return s.QueryRowCtx(context.Background(), v, args...)
}
func (s statement) QueryRowCtx(ctx context.Context, v interface{}, args ...interface{}) error {
return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
return unmarshalRow(v, rows, true)
}, s.query, args...)
}
func (s statement) QueryRowPartial(v interface{}, args ...interface{}) error {
return queryStmt(s.stmt, func(rows *sql.Rows) error {
return s.QueryRowPartialCtx(context.Background(), v, args...)
}
func (s statement) QueryRowPartialCtx(ctx context.Context, v interface{}, args ...interface{}) error {
return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
return unmarshalRow(v, rows, false)
}, s.query, args...)
}
func (s statement) QueryRows(v interface{}, args ...interface{}) error {
return queryStmt(s.stmt, func(rows *sql.Rows) error {
return s.QueryRowsCtx(context.Background(), v, args...)
}
func (s statement) QueryRowsCtx(ctx context.Context, v interface{}, args ...interface{}) error {
return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
return unmarshalRows(v, rows, true)
}, s.query, args...)
}
func (s statement) QueryRowsPartial(v interface{}, args ...interface{}) error {
return queryStmt(s.stmt, func(rows *sql.Rows) error {
return s.QueryRowsPartialCtx(context.Background(), v, args...)
}
func (s statement) QueryRowsPartialCtx(ctx context.Context, v interface{}, args ...interface{}) error {
return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
return unmarshalRows(v, rows, false)
}, s.query, args...)
}

View File

@@ -1,6 +1,7 @@
package sqlx
import (
"context"
"database/sql"
"time"
@@ -18,64 +19,65 @@ func SetSlowThreshold(threshold time.Duration) {
slowThreshold.Set(threshold)
}
func exec(conn sessionConn, q string, args ...interface{}) (sql.Result, error) {
func exec(ctx context.Context, conn sessionConn, q string, args ...interface{}) (sql.Result, error) {
stmt, err := format(q, args...)
if err != nil {
return nil, err
}
startTime := timex.Now()
result, err := conn.Exec(q, args...)
result, err := conn.ExecContext(ctx, q, args...)
duration := timex.Since(startTime)
if duration > slowThreshold.Load() {
logx.WithDuration(duration).Slowf("[SQL] exec: slowcall - %s", stmt)
logx.WithContext(ctx).WithDuration(duration).Slowf("[SQL] exec: slowcall - %s", stmt)
} else {
logx.WithDuration(duration).Infof("sql exec: %s", stmt)
logx.WithContext(ctx).WithDuration(duration).Infof("sql exec: %s", stmt)
}
if err != nil {
logSqlError(stmt, err)
logSqlError(ctx, stmt, err)
}
return result, err
}
func execStmt(conn stmtConn, q string, args ...interface{}) (sql.Result, error) {
func execStmt(ctx context.Context, conn stmtConn, q string, args ...interface{}) (sql.Result, error) {
stmt, err := format(q, args...)
if err != nil {
return nil, err
}
startTime := timex.Now()
result, err := conn.Exec(args...)
result, err := conn.ExecContext(ctx, args...)
duration := timex.Since(startTime)
if duration > slowThreshold.Load() {
logx.WithDuration(duration).Slowf("[SQL] execStmt: slowcall - %s", stmt)
logx.WithContext(ctx).WithDuration(duration).Slowf("[SQL] execStmt: slowcall - %s", stmt)
} else {
logx.WithDuration(duration).Infof("sql execStmt: %s", stmt)
logx.WithContext(ctx).WithDuration(duration).Infof("sql execStmt: %s", stmt)
}
if err != nil {
logSqlError(stmt, err)
logSqlError(ctx, stmt, err)
}
return result, err
}
func query(conn sessionConn, scanner func(*sql.Rows) error, q string, args ...interface{}) error {
func query(ctx context.Context, conn sessionConn, scanner func(*sql.Rows) error,
q string, args ...interface{}) error {
stmt, err := format(q, args...)
if err != nil {
return err
}
startTime := timex.Now()
rows, err := conn.Query(q, args...)
rows, err := conn.QueryContext(ctx, q, args...)
duration := timex.Since(startTime)
if duration > slowThreshold.Load() {
logx.WithDuration(duration).Slowf("[SQL] query: slowcall - %s", stmt)
logx.WithContext(ctx).WithDuration(duration).Slowf("[SQL] query: slowcall - %s", stmt)
} else {
logx.WithDuration(duration).Infof("sql query: %s", stmt)
logx.WithContext(ctx).WithDuration(duration).Infof("sql query: %s", stmt)
}
if err != nil {
logSqlError(stmt, err)
logSqlError(ctx, stmt, err)
return err
}
defer rows.Close()
@@ -83,22 +85,23 @@ func query(conn sessionConn, scanner func(*sql.Rows) error, q string, args ...in
return scanner(rows)
}
func queryStmt(conn stmtConn, scanner func(*sql.Rows) error, q string, args ...interface{}) error {
func queryStmt(ctx context.Context, conn stmtConn, scanner func(*sql.Rows) error,
q string, args ...interface{}) error {
stmt, err := format(q, args...)
if err != nil {
return err
}
startTime := timex.Now()
rows, err := conn.Query(args...)
rows, err := conn.QueryContext(ctx, args...)
duration := timex.Since(startTime)
if duration > slowThreshold.Load() {
logx.WithDuration(duration).Slowf("[SQL] queryStmt: slowcall - %s", stmt)
logx.WithContext(ctx).WithDuration(duration).Slowf("[SQL] queryStmt: slowcall - %s", stmt)
} else {
logx.WithDuration(duration).Infof("sql queryStmt: %s", stmt)
logx.WithContext(ctx).WithDuration(duration).Infof("sql queryStmt: %s", stmt)
}
if err != nil {
logSqlError(stmt, err)
logSqlError(ctx, stmt, err)
return err
}
defer rows.Close()

View File

@@ -1,6 +1,7 @@
package sqlx
import (
"context"
"database/sql"
"errors"
"testing"
@@ -57,7 +58,7 @@ func TestStmt_exec(t *testing.T) {
test := test
fns := []func(args ...interface{}) (sql.Result, error){
func(args ...interface{}) (sql.Result, error) {
return exec(&mockedSessionConn{
return exec(context.Background(), &mockedSessionConn{
lastInsertId: test.lastInsertId,
rowsAffected: test.rowsAffected,
err: test.err,
@@ -65,7 +66,7 @@ func TestStmt_exec(t *testing.T) {
}, test.query, args...)
},
func(args ...interface{}) (sql.Result, error) {
return execStmt(&mockedStmtConn{
return execStmt(context.Background(), &mockedStmtConn{
lastInsertId: test.lastInsertId,
rowsAffected: test.rowsAffected,
err: test.err,
@@ -137,7 +138,7 @@ func TestStmt_query(t *testing.T) {
test := test
fns := []func(args ...interface{}) error{
func(args ...interface{}) error {
return query(&mockedSessionConn{
return query(context.Background(), &mockedSessionConn{
err: test.err,
delay: test.delay,
}, func(rows *sql.Rows) error {
@@ -145,7 +146,7 @@ func TestStmt_query(t *testing.T) {
}, test.query, args...)
},
func(args ...interface{}) error {
return queryStmt(&mockedStmtConn{
return queryStmt(context.Background(), &mockedStmtConn{
err: test.err,
delay: test.delay,
}, func(rows *sql.Rows) error {
@@ -185,6 +186,10 @@ type mockedSessionConn struct {
}
func (m *mockedSessionConn) Exec(query string, args ...interface{}) (sql.Result, error) {
return m.ExecContext(context.Background(), query, args...)
}
func (m *mockedSessionConn) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
if m.delay {
time.Sleep(defaultSlowThreshold + time.Millisecond)
}
@@ -195,6 +200,10 @@ func (m *mockedSessionConn) Exec(query string, args ...interface{}) (sql.Result,
}
func (m *mockedSessionConn) Query(query string, args ...interface{}) (*sql.Rows, error) {
return m.QueryContext(context.Background(), query, args...)
}
func (m *mockedSessionConn) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
if m.delay {
time.Sleep(defaultSlowThreshold + time.Millisecond)
}
@@ -214,6 +223,10 @@ type mockedStmtConn struct {
}
func (m *mockedStmtConn) Exec(args ...interface{}) (sql.Result, error) {
return m.ExecContext(context.Background(), args...)
}
func (m *mockedStmtConn) ExecContext(_ context.Context, _ ...interface{}) (sql.Result, error) {
if m.delay {
time.Sleep(defaultSlowThreshold + time.Millisecond)
}
@@ -224,6 +237,10 @@ func (m *mockedStmtConn) Exec(args ...interface{}) (sql.Result, error) {
}
func (m *mockedStmtConn) Query(args ...interface{}) (*sql.Rows, error) {
return m.QueryContext(context.Background(), args...)
}
func (m *mockedStmtConn) QueryContext(_ context.Context, _ ...interface{}) (*sql.Rows, error) {
if m.delay {
time.Sleep(defaultSlowThreshold + time.Millisecond)
}

View File

@@ -1,6 +1,7 @@
package sqlx
import (
"context"
"database/sql"
"fmt"
)
@@ -26,11 +27,19 @@ func NewSessionFromTx(tx *sql.Tx) Session {
}
func (t txSession) Exec(q string, args ...interface{}) (sql.Result, error) {
return exec(t.Tx, q, args...)
return t.ExecCtx(context.Background(), q, args...)
}
func (t txSession) ExecCtx(ctx context.Context, q string, args ...interface{}) (sql.Result, error) {
return exec(ctx, t.Tx, q, args...)
}
func (t txSession) Prepare(q string) (StmtSession, error) {
stmt, err := t.Tx.Prepare(q)
return t.PrepareCtx(context.Background(), q)
}
func (t txSession) PrepareCtx(ctx context.Context, q string) (StmtSession, error) {
stmt, err := t.Tx.PrepareContext(ctx, q)
if err != nil {
return nil, err
}
@@ -42,25 +51,43 @@ func (t txSession) Prepare(q string) (StmtSession, error) {
}
func (t txSession) QueryRow(v interface{}, q string, args ...interface{}) error {
return query(t.Tx, func(rows *sql.Rows) error {
return t.QueryRowCtx(context.Background(), v, q, args...)
}
func (t txSession) QueryRowCtx(ctx context.Context, v interface{}, q string, args ...interface{}) error {
return query(ctx, t.Tx, func(rows *sql.Rows) error {
return unmarshalRow(v, rows, true)
}, q, args...)
}
func (t txSession) QueryRowPartial(v interface{}, q string, args ...interface{}) error {
return query(t.Tx, func(rows *sql.Rows) error {
return t.QueryRowPartialCtx(context.Background(), v, q, args...)
}
func (t txSession) QueryRowPartialCtx(ctx context.Context, v interface{}, q string,
args ...interface{}) error {
return query(ctx, t.Tx, func(rows *sql.Rows) error {
return unmarshalRow(v, rows, false)
}, q, args...)
}
func (t txSession) QueryRows(v interface{}, q string, args ...interface{}) error {
return query(t.Tx, func(rows *sql.Rows) error {
return t.QueryRowsCtx(context.Background(), v, q, args...)
}
func (t txSession) QueryRowsCtx(ctx context.Context, v interface{}, q string, args ...interface{}) error {
return query(ctx, t.Tx, func(rows *sql.Rows) error {
return unmarshalRows(v, rows, true)
}, q, args...)
}
func (t txSession) QueryRowsPartial(v interface{}, q string, args ...interface{}) error {
return query(t.Tx, func(rows *sql.Rows) error {
return t.QueryRowsPartialCtx(context.Background(), v, q, args...)
}
func (t txSession) QueryRowsPartialCtx(ctx context.Context, v interface{}, q string,
args ...interface{}) error {
return query(ctx, t.Tx, func(rows *sql.Rows) error {
return unmarshalRows(v, rows, false)
}, q, args...)
}
@@ -76,17 +103,19 @@ func begin(db *sql.DB) (trans, error) {
}, nil
}
func transact(db *commonSqlConn, b beginnable, fn func(Session) error) (err error) {
func transact(ctx context.Context, db *commonSqlConn, b beginnable,
fn func(context.Context, Session) error) (err error) {
conn, err := db.connProv()
if err != nil {
db.onError(err)
return err
}
return transactOnConn(conn, b, fn)
return transactOnConn(ctx, conn, b, fn)
}
func transactOnConn(conn *sql.DB, b beginnable, fn func(Session) error) (err error) {
func transactOnConn(ctx context.Context, conn *sql.DB, b beginnable,
fn func(context.Context, Session) error) (err error) {
var tx trans
tx, err = b(conn)
if err != nil {
@@ -96,18 +125,18 @@ func transactOnConn(conn *sql.DB, b beginnable, fn func(Session) error) (err err
defer func() {
if p := recover(); p != nil {
if e := tx.Rollback(); e != nil {
err = fmt.Errorf("recover from %#v, rollback failed: %s", p, e)
err = fmt.Errorf("recover from %#v, rollback failed: %w", p, e)
} else {
err = fmt.Errorf("recoveer from %#v", p)
}
} else if err != nil {
if e := tx.Rollback(); e != nil {
err = fmt.Errorf("transaction failed: %s, rollback failed: %s", err, e)
err = fmt.Errorf("transaction failed: %s, rollback failed: %w", err, e)
}
} else {
err = tx.Commit()
}
}()
return fn(tx)
return fn(ctx, tx)
}

View File

@@ -1,6 +1,7 @@
package sqlx
import (
"context"
"database/sql"
"errors"
"testing"
@@ -26,26 +27,50 @@ func (mt *mockTx) Exec(q string, args ...interface{}) (sql.Result, error) {
return nil, nil
}
func (mt *mockTx) ExecCtx(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
return nil, nil
}
func (mt *mockTx) Prepare(query string) (StmtSession, error) {
return nil, nil
}
func (mt *mockTx) PrepareCtx(ctx context.Context, query string) (StmtSession, error) {
return nil, nil
}
func (mt *mockTx) QueryRow(v interface{}, q string, args ...interface{}) error {
return nil
}
func (mt *mockTx) QueryRowCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error {
return nil
}
func (mt *mockTx) QueryRowPartial(v interface{}, q string, args ...interface{}) error {
return nil
}
func (mt *mockTx) QueryRowPartialCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error {
return nil
}
func (mt *mockTx) QueryRows(v interface{}, q string, args ...interface{}) error {
return nil
}
func (mt *mockTx) QueryRowsCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error {
return nil
}
func (mt *mockTx) QueryRowsPartial(v interface{}, q string, args ...interface{}) error {
return nil
}
func (mt *mockTx) QueryRowsPartialCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error {
return nil
}
func (mt *mockTx) Rollback() error {
mt.status |= mockRollback
return nil
@@ -59,18 +84,20 @@ func beginMock(mock *mockTx) beginnable {
func TestTransactCommit(t *testing.T) {
mock := &mockTx{}
err := transactOnConn(nil, beginMock(mock), func(Session) error {
return nil
})
err := transactOnConn(context.Background(), nil, beginMock(mock),
func(context.Context, Session) error {
return nil
})
assert.Equal(t, mockCommit, mock.status)
assert.Nil(t, err)
}
func TestTransactRollback(t *testing.T) {
mock := &mockTx{}
err := transactOnConn(nil, beginMock(mock), func(Session) error {
return errors.New("rollback")
})
err := transactOnConn(context.Background(), nil, beginMock(mock),
func(context.Context, Session) error {
return errors.New("rollback")
})
assert.Equal(t, mockRollback, mock.status)
assert.NotNil(t, err)
}

View File

@@ -1,6 +1,7 @@
package sqlx
import (
"context"
"fmt"
"strconv"
"strings"
@@ -66,7 +67,7 @@ func format(query string, args ...interface{}) (string, error) {
writeValue(&b, args[argIndex])
argIndex++
case '$':
case ':', '$':
var j int
for j = i + 1; j < bytes; j++ {
char := query[j]
@@ -74,16 +75,18 @@ func format(query string, args ...interface{}) (string, error) {
break
}
}
if j > i+1 {
index, err := strconv.Atoi(query[i+1 : j])
if err != nil {
return "", err
}
// index starts from 1 for pg
// index starts from 1 for pg or oracle
if index > argIndex {
argIndex = index
}
index--
if index < 0 || numArgs <= index {
return "", fmt.Errorf("error: wrong index %d in sql", index)
@@ -109,9 +112,9 @@ func logInstanceError(datasource string, err error) {
logx.Errorf("Error on getting sql instance of %s: %v", datasource, err)
}
func logSqlError(stmt string, err error) {
func logSqlError(ctx context.Context, stmt string, err error) {
if err != nil && err != ErrNotFound {
logx.Errorf("stmt: %s, error: %s", stmt, err.Error())
logx.WithContext(ctx).Errorf("stmt: %s, error: %s", stmt, err.Error())
}
}

View File

@@ -73,6 +73,30 @@ func TestFormat(t *testing.T) {
args: []interface{}{"133", false},
hasErr: true,
},
{
name: "oracle normal",
query: "select name, age from users where bool=:1 and phone=:2",
args: []interface{}{true, "133"},
expect: "select name, age from users where bool=1 and phone='133'",
},
{
name: "oracle normal reverse",
query: "select name, age from users where bool=:2 and phone=:1",
args: []interface{}{"133", false},
expect: "select name, age from users where bool=0 and phone='133'",
},
{
name: "oracle error not number",
query: "select name, age from users where bool=:a and phone=:1",
args: []interface{}{"133", false},
hasErr: true,
},
{
name: "oracle error more args",
query: "select name, age from users where bool=:2 and phone=:1 and nickname=:3",
args: []interface{}{"133", false},
hasErr: true,
},
}
for _, test := range tests {

View File

@@ -7,6 +7,9 @@ import (
"github.com/zeromicro/go-zero/core/lang"
)
// errTimeout indicates a timeout.
var errTimeout = errors.New("timeout")
type (
// Ticker interface wraps the Chan and Stop methods.
Ticker interface {
@@ -70,7 +73,7 @@ func (ft *fakeTicker) Tick() {
func (ft *fakeTicker) Wait(d time.Duration) error {
select {
case <-time.After(d):
return errors.New("timeout")
return errTimeout
case <-ft.done:
return nil
}

View File

@@ -90,14 +90,14 @@ func TestParseFullMethod(t *testing.T) {
semconv.RPCMethodKey.String("theMethod"),
},
}, {
fullMethod: "/pkg.srv",
name: "pkg.srv",
fullMethod: "/pkg.svr",
name: "pkg.svr",
attr: []attribute.KeyValue(nil),
}, {
fullMethod: "/pkg.srv/",
name: "pkg.srv/",
fullMethod: "/pkg.svr/",
name: "pkg.svr/",
attr: []attribute.KeyValue{
semconv.RPCServiceKey.String("pkg.srv"),
semconv.RPCServiceKey.String("pkg.svr"),
},
},
}

24
go.mod
View File

@@ -7,7 +7,6 @@ require (
github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/alicebob/miniredis/v2 v2.17.0
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8
github.com/go-redis/redis v6.15.9+incompatible
github.com/go-sql-driver/mysql v1.6.0
github.com/golang-jwt/jwt/v4 v4.2.0
github.com/golang/mock v1.6.0
@@ -18,8 +17,8 @@ require (
github.com/prometheus/client_golang v1.11.0
github.com/spaolacci/murmur3 v1.1.0
github.com/stretchr/testify v1.7.0
go.etcd.io/etcd/api/v3 v3.5.1
go.etcd.io/etcd/client/v3 v3.5.1
go.etcd.io/etcd/api/v3 v3.5.2
go.etcd.io/etcd/client/v3 v3.5.2
go.opentelemetry.io/otel v1.3.0
go.opentelemetry.io/otel/exporters/jaeger v1.3.0
go.opentelemetry.io/otel/exporters/zipkin v1.3.0
@@ -28,7 +27,7 @@ require (
go.uber.org/automaxprocs v1.4.0
go.uber.org/goleak v1.1.12
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11
google.golang.org/grpc v1.43.0
google.golang.org/grpc v1.44.0
google.golang.org/protobuf v1.27.1
gopkg.in/cheggaaa/pb.v1 v1.0.28
gopkg.in/h2non/gock.v1 v1.1.2
@@ -42,10 +41,21 @@ require (
require (
github.com/fatih/color v1.10.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-redis/redis/v8 v8.11.4
github.com/google/gofuzz v1.2.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/openzipkin/zipkin-go v0.4.0 // indirect
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d // indirect
golang.org/x/sys v0.0.0-20220111092808-5a964db01320 // indirect
google.golang.org/genproto v0.0.0-20220112215332-a9c7c0acf9f2 // indirect
github.com/prometheus/common v0.30.0 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.21.0 // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7 // indirect
k8s.io/klog/v2 v2.40.1 // indirect
)

154
go.sum
View File

@@ -9,17 +9,27 @@ cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6T
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
@@ -37,7 +47,6 @@ github.com/ClickHouse/clickhouse-go v1.5.1/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHg
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/Shopify/sarama v1.30.0/go.mod h1:zujlQQx1kzHsh4jfV1USnptCQrHAEZ2Hk8fTKCulPVs=
@@ -53,6 +62,8 @@ github.com/alicebob/miniredis/v2 v2.17.0 h1:EwLdrIS50uczw71Jc7iVSxZluTKj5nfSP8n7
github.com/alicebob/miniredis/v2 v2.17.0/go.mod h1:gquAfGbzn92jvtrSC69+6zZnwSODVXVpYDRaGhWaL6I=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -60,10 +71,9 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk=
github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -73,7 +83,6 @@ github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
@@ -87,6 +96,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@@ -100,7 +111,6 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
@@ -142,8 +152,8 @@ github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL9
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg=
github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
@@ -165,6 +175,8 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -172,6 +184,7 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
@@ -190,21 +203,27 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -239,8 +258,9 @@ github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhB
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
@@ -275,15 +295,17 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@@ -333,13 +355,15 @@ github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu/Ug=
github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rabbitmq/amqp091-go v1.1.0/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
@@ -352,7 +376,6 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
@@ -374,21 +397,24 @@ github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/X
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da h1:NimzV1aGyq29m5ukMK0AMWEhFaL/lrEOaephfuoiARg=
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
go.etcd.io/etcd/api/v3 v3.5.1 h1:v28cktvBq+7vGyJXF8G+rWJmj+1XUmMtqcLnH8hDocM=
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.1 h1:XIQcHCFSG53bJETYeRJtIxdLv2EWRGxcfzR8lSnTH4E=
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v3 v3.5.1 h1:oImGuV5LGKjCqXdjkMHCyWa5OO1gYKCnC/1sgdfj1Uk=
go.etcd.io/etcd/client/v3 v3.5.1/go.mod h1:OnjH4M8OnAotwaB2l9bVgZzRFKru7/ZMoS46OtKyd3Q=
go.etcd.io/etcd/api/v3 v3.5.2 h1:tXok5yLlKyuQ/SXSjtqHc4uzNaMqZi2XsoSPr/LlJXI=
go.etcd.io/etcd/api/v3 v3.5.2/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
go.etcd.io/etcd/client/pkg/v3 v3.5.2 h1:4hzqQ6hIb3blLyQ8usCU4h3NghkqcsohEQ3o3VetYxE=
go.etcd.io/etcd/client/pkg/v3 v3.5.2/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v3 v3.5.2 h1:WdnejrUtQC4nCxK0/dLTMqKOB+U5TP/2Ya0BJL+1otA=
go.etcd.io/etcd/client/v3 v3.5.2/go.mod h1:kOOaWFFgHygyT0WlSmL8TJiXmMysO/nNUlEsSsN6W4o=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/otel v1.3.0 h1:APxLf0eiBwLl+SOXiJJCVYzA1OOJNyAoV8C5RNRyy7Y=
go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs=
go.opentelemetry.io/otel/exporters/jaeger v1.3.0 h1:HfydzioALdtcB26H5WHc4K47iTETJCdloL7VN579/L0=
@@ -400,16 +426,20 @@ go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1t
go.opentelemetry.io/otel/trace v1.3.0 h1:doy8Hzb1RJ+I3yFhtDmwNc7tIyw1tNMOIsyPzp1NOGY=
go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0=
go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -468,6 +498,7 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -477,29 +508,39 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d h1:1n1fc535VhN8SYtD4cDUyNlfpAF2ROMM9+11equK3hs=
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -534,8 +575,14 @@ golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -548,10 +595,12 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220111092808-5a964db01320 h1:0jf+tOCoZ3LyutmCOWpVni1chK4VfFLhRsDK7MhqGRY=
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -597,8 +646,18 @@ golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
@@ -619,13 +678,21 @@ google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -643,12 +710,22 @@ google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvx
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20220112215332-a9c7c0acf9f2 h1:z+R4M/SuyaRsj1zu3WC+nIQyfSrSIpuDcY01/R3uCtg=
google.golang.org/genproto v0.0.0-20220112215332-a9c7c0acf9f2/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7 h1:ntPPoHzFW6Xp09ueznmahONZufyoSakK/piXnr2BU3I=
google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -657,13 +734,16 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM=
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.44.0 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg=
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -672,6 +752,7 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
@@ -711,6 +792,7 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.20.12 h1:LfRpmRkJLwPP8eaYehsVVmIIfg1yCBIIUHaSsdqCgHA=
k8s.io/api v0.20.12/go.mod h1:A2brwyEkVLM3wQGNnzoAa5JsQRzHK0uoOQ+bsnv7V68=
k8s.io/apimachinery v0.20.12 h1:2c0LIVNMvB8k2Ozstmhl2zGeCEcPazznuLYEwxFdNjM=

View File

@@ -2,6 +2,8 @@
# go-zero
***缩短从需求到上线的距离***
[English](readme.md) | 简体中文
[![Go](https://github.com/zeromicro/go-zero/workflows/Go/badge.svg?branch=master)](https://github.com/zeromicro/go-zero/actions)
@@ -11,9 +13,13 @@
[![Release](https://img.shields.io/github/v/release/zeromicro/go-zero.svg?style=flat-square)](https://github.com/zeromicro/go-zero)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
> ***缩短从需求到上线的距离***
**注意为了满足开源基金会要求go-zero 从好未来tal-tech组织下迁移至中立的 GitHub 组织zeromicro**
> ***注意:***
>
> 从 v1.3.0 之前版本升级请执行以下命令:
>
> `GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest`
>
> `goctl migrate —verbose —version v1.3.1`
## 0. go-zero 介绍
@@ -234,12 +240,18 @@ go-zero 已被许多公司用于生产部署,接入场景如在线教育、电
>46. 上海游族网络
>47. 深信服
>48. 中免日上科技互联有限公司
>48. ECLOUDVALLEY TECHNOLOGY (HK) LIMITED
>48. 馨科智(深圳)科技有限公司
>48. 成都松珀科技有限公司
>48. 亿景智联
>48. 上海扩博智能技术有限公司
>48. 一犀科技成都有限公司
>49. ECLOUDVALLEY TECHNOLOGY (HK) LIMITED
>50. 馨科智(深圳)科技有限公司
>51. 成都松珀科技有限公司
>52. 亿景智联
>53. 上海扩博智能技术有限公司
>54. 一犀科技成都有限公司
>55. 北京术杰科技有限公司
>56. 时代脉搏网络科技(云浮市)有限公司
>57. 店有帮
>58. 七牛云
>59. 费芮网络
>60. 51CTO
如果贵公司也已使用 go-zero欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。
@@ -269,3 +281,9 @@ go-zero 收录在 [CNCF Cloud Native 云原生技术全景图](https://landscape
加群之前有劳点一下 ***star***,一个小小的 ***star*** 是作者们回答海量问题的动力!🤝
<img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/wechat.jpg" alt="wechat" width="300" />
## 12. 赞助一下👍
如果觉得项目有帮助,可以请作者喝杯咖啡 🍹
<img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/sponsor.png" alt="wechat" width="300" />

View File

@@ -9,8 +9,16 @@ English | [简体中文](readme-cn.md)
[![Go Report Card](https://goreportcard.com/badge/github.com/zeromicro/go-zero)](https://goreportcard.com/report/github.com/zeromicro/go-zero)
[![Release](https://img.shields.io/github/v/release/zeromicro/go-zero.svg?style=flat-square)](https://github.com/zeromicro/go-zero)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Discord](https://img.shields.io/discord/794530774463414292?label=chat&logo=discord)](https://discord.gg/4JQvC5A4Fe)
<a href="https://www.producthunt.com/posts/go-zero?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-go&#0045;zero" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=334030&theme=light" alt="go&#0045;zero - A&#0032;web&#0032;&#0038;&#0032;rpc&#0032;framework&#0032;written&#0032;in&#0032;Go&#0046; | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
**Note: To meet the requirements of Open Source Foundation, we moved go-zero from tal-tech to zeromicro (a neutral GitHub organization).**
> ***Important!***
>
> To upgrade from versions eariler than v1.3.0, run the following commands.
>
> `go install github.com/zeromicro/go-zero/tools/goctl@latest`
>
> `goctl migrate —verbose —version v1.3.1`
## 0. what is go-zero
@@ -221,7 +229,7 @@ go get -u github.com/zeromicro/go-zero
## 9. Chat group
Join the chat via https://join.slack.com/t/go-zero/shared_invite/zt-10ruju779-BE4y6lQNB_R21samtyKTgA
Join the chat via https://discord.gg/4JQvC5A4Fe
## 10. Cloud Native Landscape

View File

@@ -14,6 +14,7 @@ import (
"github.com/zeromicro/go-zero/rest/handler"
"github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/internal"
"github.com/zeromicro/go-zero/rest/internal/response"
)
// use 1000m to represent 100%
@@ -34,16 +35,16 @@ type engine struct {
}
func newEngine(c RestConf) *engine {
srv := &engine{
svr := &engine{
conf: c,
}
if c.CpuThreshold > 0 {
srv.shedder = load.NewAdaptiveShedder(load.WithCpuThreshold(c.CpuThreshold))
srv.priorityShedder = load.NewAdaptiveShedder(load.WithCpuThreshold(
svr.shedder = load.NewAdaptiveShedder(load.WithCpuThreshold(c.CpuThreshold))
svr.priorityShedder = load.NewAdaptiveShedder(load.WithCpuThreshold(
(c.CpuThreshold + topCpuUsage) >> 1))
}
return srv
return svr
}
func (ng *engine) addRoutes(r featuredRoutes) {
@@ -93,7 +94,7 @@ func (ng *engine) bindRoute(fr featuredRoutes, router httpx.Router, metrics *sta
handler.TimeoutHandler(ng.checkedTimeout(fr.timeout)),
handler.RecoverHandler,
handler.MetricHandler(metrics),
handler.MaxBytesHandler(ng.conf.MaxBytes),
handler.MaxBytesHandler(ng.checkedMaxBytes(fr.maxBytes)),
handler.GunzipHandler,
)
chain = ng.appendAuthHandler(fr, chain, verifier)
@@ -118,6 +119,14 @@ func (ng *engine) bindRoutes(router httpx.Router) error {
return nil
}
func (ng *engine) checkedMaxBytes(bytes int64) int64 {
if bytes > 0 {
return bytes
}
return ng.conf.MaxBytes
}
func (ng *engine) checkedTimeout(timeout time.Duration) time.Duration {
if timeout > 0 {
return timeout
@@ -154,6 +163,27 @@ func (ng *engine) getShedder(priority bool) load.Shedder {
return ng.shedder
}
// notFoundHandler returns a middleware that handles 404 not found requests.
func (ng *engine) notFoundHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
chain := alice.New(
handler.TracingHandler(ng.conf.Name, ""),
ng.getLogHandler(),
)
var h http.Handler
if next != nil {
h = chain.Then(next)
} else {
h = chain.Then(http.NotFoundHandler())
}
cw := response.NewHeaderOnceResponseWriter(w)
h.ServeHTTP(cw, r)
cw.WriteHeader(http.StatusNotFound)
})
}
func (ng *engine) setTlsConfig(cfg *tls.Config) {
ng.tlsConfig = cfg
}
@@ -216,9 +246,9 @@ func (ng *engine) start(router httpx.Router) error {
}
return internal.StartHttps(ng.conf.Host, ng.conf.Port, ng.conf.CertFile,
ng.conf.KeyFile, router, func(srv *http.Server) {
ng.conf.KeyFile, router, func(svr *http.Server) {
if ng.tlsConfig != nil {
srv.TLSConfig = ng.tlsConfig
svr.TLSConfig = ng.tlsConfig
}
})
}

View File

@@ -1,13 +1,17 @@
package rest
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/core/logx"
)
func TestNewEngine(t *testing.T) {
@@ -190,6 +194,110 @@ func TestEngine_checkedTimeout(t *testing.T) {
}
}
func TestEngine_checkedMaxBytes(t *testing.T) {
tests := []struct {
name string
maxBytes int64
expect int64
}{
{
name: "not set",
expect: 1000,
},
{
name: "less",
maxBytes: 500,
expect: 500,
},
{
name: "equal",
maxBytes: 1000,
expect: 1000,
},
{
name: "more",
maxBytes: 1500,
expect: 1500,
},
}
ng := newEngine(RestConf{
MaxBytes: 1000,
})
for _, test := range tests {
assert.Equal(t, test.expect, ng.checkedMaxBytes(test.maxBytes))
}
}
func TestEngine_notFoundHandler(t *testing.T) {
logx.Disable()
ng := newEngine(RestConf{})
ts := httptest.NewServer(ng.notFoundHandler(nil))
defer ts.Close()
client := ts.Client()
err := func(ctx context.Context) error {
req, err := http.NewRequest("GET", ts.URL+"/bad", nil)
assert.Nil(t, err)
res, err := client.Do(req)
assert.Nil(t, err)
assert.Equal(t, http.StatusNotFound, res.StatusCode)
return res.Body.Close()
}(context.Background())
assert.Nil(t, err)
}
func TestEngine_notFoundHandlerNotNil(t *testing.T) {
logx.Disable()
ng := newEngine(RestConf{})
var called int32
ts := httptest.NewServer(ng.notFoundHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
atomic.AddInt32(&called, 1)
})))
defer ts.Close()
client := ts.Client()
err := func(ctx context.Context) error {
req, err := http.NewRequest("GET", ts.URL+"/bad", nil)
assert.Nil(t, err)
res, err := client.Do(req)
assert.Nil(t, err)
assert.Equal(t, http.StatusNotFound, res.StatusCode)
return res.Body.Close()
}(context.Background())
assert.Nil(t, err)
assert.Equal(t, int32(1), atomic.LoadInt32(&called))
}
func TestEngine_notFoundHandlerNotNilWriteHeader(t *testing.T) {
logx.Disable()
ng := newEngine(RestConf{})
var called int32
ts := httptest.NewServer(ng.notFoundHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
atomic.AddInt32(&called, 1)
w.WriteHeader(http.StatusExpectationFailed)
})))
defer ts.Close()
client := ts.Client()
err := func(ctx context.Context) error {
req, err := http.NewRequest("GET", ts.URL+"/bad", nil)
assert.Nil(t, err)
res, err := client.Do(req)
assert.Nil(t, err)
assert.Equal(t, http.StatusExpectationFailed, res.StatusCode)
return res.Body.Close()
}(context.Background())
assert.Nil(t, err)
assert.Equal(t, int32(1), atomic.LoadInt32(&called))
}
type mockedRouter struct{}
func (m mockedRouter) ServeHTTP(writer http.ResponseWriter, request *http.Request) {

View File

@@ -1,15 +1,14 @@
package handler
import (
"bufio"
"context"
"errors"
"net"
"net/http"
"net/http/httputil"
"github.com/golang-jwt/jwt/v4"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/rest/internal/response"
"github.com/zeromicro/go-zero/rest/token"
)
@@ -105,7 +104,7 @@ func detailAuthLog(r *http.Request, reason string) {
}
func unauthorized(w http.ResponseWriter, r *http.Request, err error, callback UnauthorizedCallback) {
writer := newGuardedResponseWriter(w)
writer := response.NewHeaderOnceResponseWriter(w)
if err != nil {
detailAuthLog(r, err.Error())
@@ -121,47 +120,3 @@ func unauthorized(w http.ResponseWriter, r *http.Request, err error, callback Un
// if user not setting HTTP header, we set header with 401
writer.WriteHeader(http.StatusUnauthorized)
}
type guardedResponseWriter struct {
writer http.ResponseWriter
wroteHeader bool
}
func newGuardedResponseWriter(w http.ResponseWriter) *guardedResponseWriter {
return &guardedResponseWriter{
writer: w,
}
}
func (grw *guardedResponseWriter) Flush() {
if flusher, ok := grw.writer.(http.Flusher); ok {
flusher.Flush()
}
}
func (grw *guardedResponseWriter) Header() http.Header {
return grw.writer.Header()
}
// Hijack implements the http.Hijacker interface.
// This expands the Response to fulfill http.Hijacker if the underlying http.ResponseWriter supports it.
func (grw *guardedResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if hijacked, ok := grw.writer.(http.Hijacker); ok {
return hijacked.Hijack()
}
return nil, nil, errors.New("server doesn't support hijacking")
}
func (grw *guardedResponseWriter) Write(body []byte) (int, error) {
return grw.writer.Write(body)
}
func (grw *guardedResponseWriter) WriteHeader(statusCode int) {
if grw.wroteHeader {
return
}
grw.wroteHeader = true
grw.writer.WriteHeader(statusCode)
}

View File

@@ -90,26 +90,6 @@ func TestAuthHandler_NilError(t *testing.T) {
})
}
func TestAuthHandler_Flush(t *testing.T) {
resp := httptest.NewRecorder()
handler := newGuardedResponseWriter(resp)
handler.Flush()
assert.True(t, resp.Flushed)
}
func TestAuthHandler_Hijack(t *testing.T) {
resp := httptest.NewRecorder()
writer := newGuardedResponseWriter(resp)
assert.NotPanics(t, func() {
writer.Hijack()
})
writer = newGuardedResponseWriter(mockedHijackable{resp})
assert.NotPanics(t, func() {
writer.Hijack()
})
}
func buildToken(secretKey string, payloads map[string]interface{}, seconds int64) (string, error) {
now := time.Now().Unix()
claims := make(jwt.MapClaims)

View File

@@ -9,7 +9,7 @@ import (
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stat"
"github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/internal/security"
"github.com/zeromicro/go-zero/rest/internal/response"
)
const breakerSeparator = "://"
@@ -28,7 +28,7 @@ func BreakerHandler(method, path string, metrics *stat.Metrics) func(http.Handle
return
}
cw := &security.WithCodeResponseWriter{Writer: w}
cw := &response.WithCodeResponseWriter{Writer: w}
defer func() {
if cw.Code < http.StatusInternalServerError {
promise.Accept()

View File

@@ -8,7 +8,7 @@ import (
"github.com/zeromicro/go-zero/core/metric"
"github.com/zeromicro/go-zero/core/prometheus"
"github.com/zeromicro/go-zero/core/timex"
"github.com/zeromicro/go-zero/rest/internal/security"
"github.com/zeromicro/go-zero/rest/internal/response"
)
const serverNamespace = "http_server"
@@ -41,7 +41,7 @@ func PrometheusHandler(path string) func(http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
startTime := timex.Now()
cw := &security.WithCodeResponseWriter{Writer: w}
cw := &response.WithCodeResponseWriter{Writer: w}
defer func() {
metricServerReqDur.Observe(int64(timex.Since(startTime)/time.Millisecond), path)
metricServerReqCodeTotal.Inc(path, strconv.Itoa(cw.Code))

View File

@@ -8,7 +8,7 @@ import (
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stat"
"github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/internal/security"
"github.com/zeromicro/go-zero/rest/internal/response"
)
const serviceType = "api"
@@ -41,7 +41,7 @@ func SheddingHandler(shedder load.Shedder, metrics *stat.Metrics) func(http.Hand
return
}
cw := &security.WithCodeResponseWriter{Writer: w}
cw := &response.WithCodeResponseWriter{Writer: w}
defer func() {
if cw.Code == http.StatusServiceUnavailable {
promise.Fail()

View File

@@ -18,21 +18,21 @@ func TracingHandler(serviceName, path string) func(http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := propagator.Extract(r.Context(), propagation.HeaderCarrier(r.Header))
spanName := path
if len(spanName) == 0 {
spanName = r.URL.Path
}
spanCtx, span := tracer.Start(
ctx,
path,
spanName,
oteltrace.WithSpanKind(oteltrace.SpanKindServer),
oteltrace.WithAttributes(semconv.HTTPServerAttributesFromHTTPRequest(
serviceName, path, r)...),
serviceName, spanName, r)...),
)
defer span.End()
// convenient for tracking error messages
sc := span.SpanContext()
if sc.HasTraceID() {
w.Header().Set(trace.TraceIdKey, sc.TraceID().String())
}
propagator.Inject(spanCtx, propagation.HeaderCarrier(w.Header()))
next.ServeHTTP(w, r.WithContext(spanCtx))
})
}

View File

@@ -6,6 +6,7 @@ import (
"net/http/httptest"
"testing"
"github.com/justinas/alice"
"github.com/stretchr/testify/assert"
ztrace "github.com/zeromicro/go-zero/core/trace"
"go.opentelemetry.io/otel"
@@ -21,28 +22,31 @@ func TestOtelHandler(t *testing.T) {
Sampler: 1.0,
})
ts := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header))
spanCtx := trace.SpanContextFromContext(ctx)
assert.Equal(t, true, spanCtx.IsValid())
}),
)
defer ts.Close()
for _, test := range []string{"", "bar"} {
t.Run(test, func(t *testing.T) {
h := alice.New(TracingHandler("foo", test)).Then(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header))
spanCtx := trace.SpanContextFromContext(ctx)
assert.True(t, spanCtx.IsValid())
}))
ts := httptest.NewServer(h)
defer ts.Close()
client := ts.Client()
err := func(ctx context.Context) error {
ctx, span := otel.Tracer("httptrace/client").Start(ctx, "test")
defer span.End()
client := ts.Client()
err := func(ctx context.Context) error {
ctx, span := otel.Tracer("httptrace/client").Start(ctx, "test")
defer span.End()
req, _ := http.NewRequest("GET", ts.URL, nil)
otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))
req, _ := http.NewRequest("GET", ts.URL, nil)
otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))
res, err := client.Do(req)
assert.Equal(t, err, nil)
_ = res.Body.Close()
return nil
}(context.Background())
res, err := client.Do(req)
assert.Nil(t, err)
return res.Body.Close()
}(context.Background())
assert.Equal(t, err, nil)
assert.Nil(t, err)
})
}
}

View File

@@ -0,0 +1,8 @@
package internal
import "net/http"
type (
Interceptor func(r *http.Request) (*http.Request, ResponseHandler)
ResponseHandler func(resp *http.Response, err error)
)

View File

@@ -0,0 +1,34 @@
package internal
import (
"net/http"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/timex"
"go.opentelemetry.io/otel/propagation"
)
func LogInterceptor(r *http.Request) (*http.Request, ResponseHandler) {
start := timex.Now()
return r, func(resp *http.Response, err error) {
duration := timex.Since(start)
if err != nil {
logger := logx.WithContext(r.Context()).WithDuration(duration)
logger.Errorf("[HTTP] %s %s - %v", r.Method, r.URL, err)
return
}
var tc propagation.TraceContext
ctx := tc.Extract(r.Context(), propagation.HeaderCarrier(resp.Header))
logger := logx.WithContext(ctx).WithDuration(duration)
if isOkResponse(resp.StatusCode) {
logger.Infof("[HTTP] %d - %s %s", resp.StatusCode, r.Method, r.URL)
} else {
logger.Errorf("[HTTP] %d - %s %s", resp.StatusCode, r.Method, r.URL)
}
}
}
func isOkResponse(code int) bool {
return code < http.StatusBadRequest
}

View File

@@ -0,0 +1,51 @@
package internal
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestLogInterceptor(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
}))
defer svr.Close()
req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err)
req, handler := LogInterceptor(req)
resp, err := http.DefaultClient.Do(req)
handler(resp, err)
assert.Nil(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
}
func TestLogInterceptorServerError(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}))
defer svr.Close()
req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err)
req, handler := LogInterceptor(req)
resp, err := http.DefaultClient.Do(req)
handler(resp, err)
assert.Nil(t, err)
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
}
func TestLogInterceptorServerClosed(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}))
defer svr.Close()
req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err)
svr.Close()
req, handler := LogInterceptor(req)
resp, err := http.DefaultClient.Do(req)
handler(resp, err)
assert.NotNil(t, err)
assert.Nil(t, resp)
}

44
rest/httpc/requests.go Normal file
View File

@@ -0,0 +1,44 @@
package httpc
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpc/internal"
)
var interceptors = []internal.Interceptor{
internal.LogInterceptor,
}
// DoRequest sends an HTTP request and returns an HTTP response.
func DoRequest(r *http.Request) (*http.Response, error) {
return request(r, defaultClient{})
}
type (
client interface {
do(r *http.Request) (*http.Response, error)
}
defaultClient struct{}
)
func (c defaultClient) do(r *http.Request) (*http.Response, error) {
return http.DefaultClient.Do(r)
}
func request(r *http.Request, cli client) (*http.Response, error) {
var respHandlers []internal.ResponseHandler
for _, interceptor := range interceptors {
var h internal.ResponseHandler
r, h = interceptor(r)
respHandlers = append(respHandlers, h)
}
resp, err := cli.do(r)
for i := len(respHandlers) - 1; i >= 0; i-- {
respHandlers[i](resp, err)
}
return resp, err
}

View File

@@ -0,0 +1,41 @@
package httpc
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestDo(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
}))
defer svr.Close()
req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err)
resp, err := DoRequest(req)
assert.Nil(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
}
func TestDoNotFound(t *testing.T) {
svr := httptest.NewServer(http.NotFoundHandler())
defer svr.Close()
req, err := http.NewRequest(http.MethodPost, svr.URL, nil)
assert.Nil(t, err)
req.Header.Set("Content-Type", "application/json")
resp, err := DoRequest(req)
assert.Nil(t, err)
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
}
func TestDoMoved(t *testing.T) {
svr := httptest.NewServer(http.RedirectHandler("/foo", http.StatusMovedPermanently))
defer svr.Close()
req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err)
_, err = DoRequest(req)
// too many redirects
assert.NotNil(t, err)
}

36
rest/httpc/responses.go Normal file
View File

@@ -0,0 +1,36 @@
package httpc
import (
"net/http"
"strings"
"github.com/zeromicro/go-zero/core/mapping"
"github.com/zeromicro/go-zero/rest/internal/encoding"
)
// Parse parses the response.
func Parse(resp *http.Response, val interface{}) error {
if err := ParseHeaders(resp, val); err != nil {
return err
}
return ParseJsonBody(resp, val)
}
// ParseHeaders parses the rsponse headers.
func ParseHeaders(resp *http.Response, val interface{}) error {
return encoding.ParseHeaders(resp.Header, val)
}
// ParseJsonBody parses the rsponse body, which should be in json content type.
func ParseJsonBody(resp *http.Response, val interface{}) error {
if withJsonBody(resp) {
return mapping.UnmarshalJsonReader(resp.Body, val)
}
return mapping.UnmarshalJsonMap(nil, val)
}
func withJsonBody(r *http.Response) bool {
return r.ContentLength > 0 && strings.Contains(r.Header.Get(contentType), applicationJson)
}

View File

@@ -0,0 +1,64 @@
package httpc
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestParse(t *testing.T) {
var val struct {
Foo string `header:"foo"`
Name string `json:"name"`
Value int `json:"value"`
}
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("foo", "bar")
w.Header().Set(contentType, applicationJson)
w.Write([]byte(`{"name":"kevin","value":100}`))
}))
defer svr.Close()
req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err)
resp, err := DoRequest(req)
assert.Nil(t, err)
assert.Nil(t, Parse(resp, &val))
assert.Equal(t, "bar", val.Foo)
assert.Equal(t, "kevin", val.Name)
assert.Equal(t, 100, val.Value)
}
func TestParseHeaderError(t *testing.T) {
var val struct {
Foo int `header:"foo"`
}
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("foo", "bar")
w.Header().Set(contentType, applicationJson)
}))
defer svr.Close()
req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err)
resp, err := DoRequest(req)
assert.Nil(t, err)
assert.NotNil(t, Parse(resp, &val))
}
func TestParseNoBody(t *testing.T) {
var val struct {
Foo string `header:"foo"`
}
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("foo", "bar")
w.Header().Set(contentType, applicationJson)
}))
defer svr.Close()
req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err)
resp, err := DoRequest(req)
assert.Nil(t, err)
assert.Nil(t, Parse(resp, &val))
assert.Equal(t, "bar", val.Foo)
}

61
rest/httpc/service.go Normal file
View File

@@ -0,0 +1,61 @@
package httpc
import (
"net/http"
"github.com/zeromicro/go-zero/core/breaker"
)
type (
// Option is used to customize the *http.Client.
Option func(r *http.Request) *http.Request
// Service represents a remote HTTP service.
Service interface {
// DoRequest sends a HTTP request to the service.
DoRequest(r *http.Request) (*http.Response, error)
}
namedService struct {
name string
cli *http.Client
opts []Option
}
)
// NewService returns a remote service with the given name.
// opts are used to customize the *http.Client.
func NewService(name string, opts ...Option) Service {
return NewServiceWithClient(name, http.DefaultClient, opts...)
}
// NewServiceWithClient returns a remote service with the given name.
// opts are used to customize the *http.Client.
func NewServiceWithClient(name string, cli *http.Client, opts ...Option) Service {
return namedService{
name: name,
cli: cli,
opts: opts,
}
}
// DoRequest sends an HTTP request to the service.
func (s namedService) DoRequest(r *http.Request) (*http.Response, error) {
return request(r, s)
}
func (s namedService) do(r *http.Request) (resp *http.Response, err error) {
for _, opt := range s.opts {
r = opt(r)
}
brk := breaker.GetBreaker(s.name)
err = brk.DoWithAcceptable(func() error {
resp, err = s.cli.Do(r)
return err
}, func(err error) bool {
return err == nil && resp.StatusCode < http.StatusInternalServerError
})
return
}

View File

@@ -0,0 +1,49 @@
package httpc
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNamedService_Do(t *testing.T) {
svr := httptest.NewServer(http.RedirectHandler("/foo", http.StatusMovedPermanently))
defer svr.Close()
req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err)
service := NewService("foo")
_, err = service.DoRequest(req)
// too many redirects
assert.NotNil(t, err)
}
func TestNamedService_Get(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("foo", r.Header.Get("foo"))
}))
defer svr.Close()
service := NewService("foo", func(r *http.Request) *http.Request {
r.Header.Set("foo", "bar")
return r
})
req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err)
resp, err := service.DoRequest(req)
assert.Nil(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "bar", resp.Header.Get("foo"))
}
func TestNamedService_Post(t *testing.T) {
svr := httptest.NewServer(http.NotFoundHandler())
defer svr.Close()
service := NewService("foo")
req, err := http.NewRequest(http.MethodPost, svr.URL, nil)
assert.Nil(t, err)
req.Header.Set("Content-Type", "application/json")
resp, err := service.DoRequest(req)
assert.Nil(t, err)
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
}

6
rest/httpc/vars.go Normal file
View File

@@ -0,0 +1,6 @@
package httpc
const (
contentType = "Content-Type"
applicationJson = "application/json"
)

View File

@@ -3,17 +3,16 @@ package httpx
import (
"io"
"net/http"
"net/textproto"
"strings"
"github.com/zeromicro/go-zero/core/mapping"
"github.com/zeromicro/go-zero/rest/internal/encoding"
"github.com/zeromicro/go-zero/rest/pathvar"
)
const (
formKey = "form"
pathKey = "path"
headerKey = "header"
maxMemory = 32 << 20 // 32MB
maxBodyLen = 8 << 20 // 8MB
separator = ";"
@@ -21,10 +20,8 @@ const (
)
var (
formUnmarshaler = mapping.NewUnmarshaler(formKey, mapping.WithStringValues())
pathUnmarshaler = mapping.NewUnmarshaler(pathKey, mapping.WithStringValues())
headerUnmarshaler = mapping.NewUnmarshaler(headerKey, mapping.WithStringValues(),
mapping.WithCanonicalKeyFunc(textproto.CanonicalMIMEHeaderKey))
formUnmarshaler = mapping.NewUnmarshaler(formKey, mapping.WithStringValues())
pathUnmarshaler = mapping.NewUnmarshaler(pathKey, mapping.WithStringValues())
)
// Parse parses the request.
@@ -46,16 +43,7 @@ func Parse(r *http.Request, v interface{}) error {
// ParseHeaders parses the headers request.
func ParseHeaders(r *http.Request, v interface{}) error {
m := map[string]interface{}{}
for k, v := range r.Header {
if len(v) == 1 {
m[k] = v[0]
} else {
m[k] = v
}
}
return headerUnmarshaler.Unmarshal(m, v)
return encoding.ParseHeaders(r.Header, v)
}
// ParseForm parses the form request.

View File

@@ -1,10 +1,9 @@
package cors
import (
"bufio"
"errors"
"net"
"net/http"
"github.com/zeromicro/go-zero/rest/internal/response"
)
const (
@@ -30,7 +29,7 @@ const (
// At most one origin can be specified, other origins are ignored if given, default to be *.
func NotAllowedHandler(fn func(w http.ResponseWriter), origins ...string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gw := &guardedResponseWriter{w: w}
gw := response.NewHeaderOnceResponseWriter(w)
checkAndSetHeaders(gw, r, origins)
if fn != nil {
fn(gw)
@@ -62,44 +61,6 @@ func Middleware(fn func(w http.Header), origins ...string) func(http.HandlerFunc
}
}
type guardedResponseWriter struct {
w http.ResponseWriter
wroteHeader bool
}
func (w *guardedResponseWriter) Flush() {
if flusher, ok := w.w.(http.Flusher); ok {
flusher.Flush()
}
}
func (w *guardedResponseWriter) Header() http.Header {
return w.w.Header()
}
// Hijack implements the http.Hijacker interface.
// This expands the Response to fulfill http.Hijacker if the underlying http.ResponseWriter supports it.
func (w *guardedResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if hijacked, ok := w.w.(http.Hijacker); ok {
return hijacked.Hijack()
}
return nil, nil, errors.New("server doesn't support hijacking")
}
func (w *guardedResponseWriter) Write(bytes []byte) (int, error) {
return w.w.Write(bytes)
}
func (w *guardedResponseWriter) WriteHeader(code int) {
if w.wroteHeader {
return
}
w.w.WriteHeader(code)
w.wroteHeader = true
}
func checkAndSetHeaders(w http.ResponseWriter, r *http.Request, origins []string) {
setVaryHeaders(w, r)

View File

@@ -1,8 +1,6 @@
package cors
import (
"bufio"
"net"
"net/http"
"net/http/httptest"
"testing"
@@ -131,48 +129,3 @@ func TestCorsHandlerWithOrigins(t *testing.T) {
}
}
}
func TestGuardedResponseWriter_Flush(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "http://localhost", nil)
handler := NotAllowedHandler(func(w http.ResponseWriter) {
w.Header().Set("X-Test", "test")
w.WriteHeader(http.StatusServiceUnavailable)
_, err := w.Write([]byte("content"))
assert.Nil(t, err)
flusher, ok := w.(http.Flusher)
assert.True(t, ok)
flusher.Flush()
}, "foo.com")
resp := httptest.NewRecorder()
handler.ServeHTTP(resp, req)
assert.Equal(t, http.StatusServiceUnavailable, resp.Code)
assert.Equal(t, "test", resp.Header().Get("X-Test"))
assert.Equal(t, "content", resp.Body.String())
}
func TestGuardedResponseWriter_Hijack(t *testing.T) {
resp := httptest.NewRecorder()
writer := &guardedResponseWriter{
w: resp,
}
assert.NotPanics(t, func() {
writer.Hijack()
})
writer = &guardedResponseWriter{
w: mockedHijackable{resp},
}
assert.NotPanics(t, func() {
writer.Hijack()
})
}
type mockedHijackable struct {
*httptest.ResponseRecorder
}
func (m mockedHijackable) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return nil, nil, nil
}

View File

@@ -0,0 +1,27 @@
package encoding
import (
"net/http"
"net/textproto"
"github.com/zeromicro/go-zero/core/mapping"
)
const headerKey = "header"
var headerUnmarshaler = mapping.NewUnmarshaler(headerKey, mapping.WithStringValues(),
mapping.WithCanonicalKeyFunc(textproto.CanonicalMIMEHeaderKey))
// ParseHeaders parses the headers request.
func ParseHeaders(header http.Header, v interface{}) error {
m := map[string]interface{}{}
for k, v := range header {
if len(v) == 1 {
m[k] = v[0]
} else {
m[k] = v
}
}
return headerUnmarshaler.Unmarshal(m, v)
}

View File

@@ -0,0 +1,40 @@
package encoding
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseHeaders(t *testing.T) {
var val struct {
Foo string `header:"foo"`
Baz int `header:"baz"`
Qux bool `header:"qux,default=true"`
}
r := httptest.NewRequest(http.MethodGet, "/any", nil)
r.Header.Set("foo", "bar")
r.Header.Set("baz", "1")
assert.Nil(t, ParseHeaders(r.Header, &val))
assert.Equal(t, "bar", val.Foo)
assert.Equal(t, 1, val.Baz)
assert.True(t, val.Qux)
}
func TestParseHeadersMulti(t *testing.T) {
var val struct {
Foo []string `header:"foo"`
Baz int `header:"baz"`
Qux bool `header:"qux,default=true"`
}
r := httptest.NewRequest(http.MethodGet, "/any", nil)
r.Header.Set("foo", "bar")
r.Header.Add("foo", "bar1")
r.Header.Set("baz", "1")
assert.Nil(t, ParseHeaders(r.Header, &val))
assert.Equal(t, []string{"bar", "bar1"}, val.Foo)
assert.Equal(t, 1, val.Baz)
assert.True(t, val.Qux)
}

View File

@@ -36,3 +36,8 @@ func TestError(t *testing.T) {
assert.True(t, strings.Contains(val, "third"))
assert.True(t, strings.Contains(val, "\n"))
}
func TestContextKey_String(t *testing.T) {
val := contextKey("foo")
assert.True(t, strings.Contains(val.String(), "foo"))
}

View File

@@ -0,0 +1,57 @@
package response
import (
"bufio"
"errors"
"net"
"net/http"
)
// HeaderOnceResponseWriter is a http.ResponseWriter implementation
// that only the first WriterHeader takes effect.
type HeaderOnceResponseWriter struct {
w http.ResponseWriter
wroteHeader bool
}
// NewHeaderOnceResponseWriter returns a HeaderOnceResponseWriter.
func NewHeaderOnceResponseWriter(w http.ResponseWriter) http.ResponseWriter {
return &HeaderOnceResponseWriter{w: w}
}
// Flush flushes the response writer.
func (w *HeaderOnceResponseWriter) Flush() {
if flusher, ok := w.w.(http.Flusher); ok {
flusher.Flush()
}
}
// Header returns the http header.
func (w *HeaderOnceResponseWriter) Header() http.Header {
return w.w.Header()
}
// Hijack implements the http.Hijacker interface.
// This expands the Response to fulfill http.Hijacker if the underlying http.ResponseWriter supports it.
func (w *HeaderOnceResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if hijacked, ok := w.w.(http.Hijacker); ok {
return hijacked.Hijack()
}
return nil, nil, errors.New("server doesn't support hijacking")
}
// Write writes bytes into w.
func (w *HeaderOnceResponseWriter) Write(bytes []byte) (int, error) {
return w.w.Write(bytes)
}
// WriteHeader writes code into w, and not sealing the writer.
func (w *HeaderOnceResponseWriter) WriteHeader(code int) {
if w.wroteHeader {
return
}
w.w.WriteHeader(code)
w.wroteHeader = true
}

View File

@@ -0,0 +1,58 @@
package response
import (
"bufio"
"net"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestHeaderOnceResponseWriter_Flush(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "http://localhost", nil)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cw := NewHeaderOnceResponseWriter(w)
cw.Header().Set("X-Test", "test")
cw.WriteHeader(http.StatusServiceUnavailable)
cw.WriteHeader(http.StatusExpectationFailed)
_, err := cw.Write([]byte("content"))
assert.Nil(t, err)
flusher, ok := cw.(http.Flusher)
assert.True(t, ok)
flusher.Flush()
})
resp := httptest.NewRecorder()
handler.ServeHTTP(resp, req)
assert.Equal(t, http.StatusServiceUnavailable, resp.Code)
assert.Equal(t, "test", resp.Header().Get("X-Test"))
assert.Equal(t, "content", resp.Body.String())
}
func TestHeaderOnceResponseWriter_Hijack(t *testing.T) {
resp := httptest.NewRecorder()
writer := &HeaderOnceResponseWriter{
w: resp,
}
assert.NotPanics(t, func() {
writer.Hijack()
})
writer = &HeaderOnceResponseWriter{
w: mockedHijackable{resp},
}
assert.NotPanics(t, func() {
writer.Hijack()
})
}
type mockedHijackable struct {
*httptest.ResponseRecorder
}
func (m mockedHijackable) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return nil, nil, nil
}

View File

@@ -1,7 +1,8 @@
package security
package response
import (
"bufio"
"errors"
"net"
"net/http"
)
@@ -27,7 +28,11 @@ func (w *WithCodeResponseWriter) Header() http.Header {
// Hijack implements the http.Hijacker interface.
// This expands the Response to fulfill http.Hijacker if the underlying http.ResponseWriter supports it.
func (w *WithCodeResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return w.Writer.(http.Hijacker).Hijack()
if hijacked, ok := w.Writer.(http.Hijacker); ok {
return hijacked.Hijack()
}
return nil, nil, errors.New("server doesn't support hijacking")
}
// Write writes bytes into w.

View File

@@ -1,4 +1,4 @@
package security
package response
import (
"net/http"
@@ -31,3 +31,20 @@ func TestWithCodeResponseWriter(t *testing.T) {
assert.Equal(t, "test", resp.Header().Get("X-Test"))
assert.Equal(t, "content", resp.Body.String())
}
func TestWithCodeResponseWriter_Hijack(t *testing.T) {
resp := httptest.NewRecorder()
writer := &WithCodeResponseWriter{
Writer: resp,
}
assert.NotPanics(t, func() {
writer.Hijack()
})
writer = &WithCodeResponseWriter{
Writer: mockedHijackable{resp},
}
assert.NotPanics(t, func() {
writer.Hijack()
})
}

View File

@@ -151,6 +151,8 @@ func TestContentSecurity(t *testing.T) {
return
}
encrypted := test.mode != "0"
assert.Equal(t, encrypted, header.Encrypted())
assert.Equal(t, test.code, VerifySignature(r, header, time.Minute))
})
}

View File

@@ -10,25 +10,25 @@ import (
)
// StartOption defines the method to customize http.Server.
type StartOption func(srv *http.Server)
type StartOption func(svr *http.Server)
// StartHttp starts a http server.
func StartHttp(host string, port int, handler http.Handler, opts ...StartOption) error {
return start(host, port, handler, func(srv *http.Server) error {
return srv.ListenAndServe()
return start(host, port, handler, func(svr *http.Server) error {
return svr.ListenAndServe()
}, opts...)
}
// StartHttps starts a https server.
func StartHttps(host string, port int, certFile, keyFile string, handler http.Handler,
opts ...StartOption) error {
return start(host, port, handler, func(srv *http.Server) error {
return start(host, port, handler, func(svr *http.Server) error {
// certFile and keyFile are set in buildHttpsServer
return srv.ListenAndServeTLS(certFile, keyFile)
return svr.ListenAndServeTLS(certFile, keyFile)
}, opts...)
}
func start(host string, port int, handler http.Handler, run func(srv *http.Server) error,
func start(host string, port int, handler http.Handler, run func(svr *http.Server) error,
opts ...StartOption) (err error) {
server := &http.Server{
Addr: fmt.Sprintf("%s:%d", host, port),

View File

@@ -0,0 +1,33 @@
package internal
import (
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestStartHttp(t *testing.T) {
svr := httptest.NewUnstartedServer(http.NotFoundHandler())
fields := strings.Split(svr.Listener.Addr().String(), ":")
port, err := strconv.Atoi(fields[1])
assert.Nil(t, err)
err = StartHttp(fields[0], port, http.NotFoundHandler(), func(svr *http.Server) {
svr.IdleTimeout = 0
})
assert.NotNil(t, err)
}
func TestStartHttps(t *testing.T) {
svr := httptest.NewTLSServer(http.NotFoundHandler())
fields := strings.Split(svr.Listener.Addr().String(), ":")
port, err := strconv.Atoi(fields[1])
assert.Nil(t, err)
err = StartHttps(fields[0], port, "", "", http.NotFoundHandler(), func(svr *http.Server) {
svr.IdleTimeout = 0
})
assert.NotNil(t, err)
}

View File

@@ -49,6 +49,7 @@ func NewServer(c RestConf, opts ...RunOption) (*Server, error) {
router: router.NewRouter(),
}
opts = append([]RunOption{WithNotFoundHandler(nil)}, opts...)
for _, opt := range opts {
opt(server)
}
@@ -136,6 +137,13 @@ func WithJwtTransition(secret, prevSecret string) RouteOption {
}
}
// WithMaxBytes returns a RouteOption to set maxBytes with the given value.
func WithMaxBytes(maxBytes int64) RouteOption {
return func(r *featuredRoutes) {
r.maxBytes = maxBytes
}
}
// WithMiddlewares adds given middlewares to given routes.
func WithMiddlewares(ms []Middleware, rs ...Route) []Route {
for i := len(ms) - 1; i >= 0; i-- {
@@ -163,7 +171,8 @@ func WithMiddleware(middleware Middleware, rs ...Route) []Route {
// WithNotFoundHandler returns a RunOption with not found handler set to given handler.
func WithNotFoundHandler(handler http.Handler) RunOption {
return func(server *Server) {
server.router.SetNotFoundHandler(handler)
notFoundHandler := server.ngin.notFoundHandler(handler)
server.router.SetNotFoundHandler(notFoundHandler)
}
}
@@ -223,22 +232,22 @@ func WithTimeout(timeout time.Duration) RouteOption {
// WithTLSConfig returns a RunOption that with given tls config.
func WithTLSConfig(cfg *tls.Config) RunOption {
return func(srv *Server) {
srv.ngin.setTlsConfig(cfg)
return func(svr *Server) {
svr.ngin.setTlsConfig(cfg)
}
}
// WithUnauthorizedCallback returns a RunOption that with given unauthorized callback set.
func WithUnauthorizedCallback(callback handler.UnauthorizedCallback) RunOption {
return func(srv *Server) {
srv.ngin.setUnauthorizedCallback(callback)
return func(svr *Server) {
svr.ngin.setUnauthorizedCallback(callback)
}
}
// WithUnsignedCallback returns a RunOption that with given unsigned callback set.
func WithUnsignedCallback(callback handler.UnsignedCallback) RunOption {
return func(srv *Server) {
srv.ngin.setUnsignedCallback(callback)
return func(svr *Server) {
svr.ngin.setUnsignedCallback(callback)
}
}

Some files were not shown because too many files have changed in this diff Show More