Compare commits

..

100 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
200 changed files with 5012 additions and 1460 deletions

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

@@ -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

@@ -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

@@ -65,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

@@ -75,7 +75,7 @@ var (
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

@@ -937,7 +937,6 @@ func TestUnmarshalYamlReaderError(t *testing.T) {
reader = strings.NewReader("chenquan")
err = UnmarshalYamlReader(reader, &v)
assert.ErrorIs(t, err, ErrUnsupportedType)
}
func TestUnmarshalYamlBadReader(t *testing.T) {

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

@@ -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)
}

View File

@@ -640,6 +640,29 @@ func (s *Redis) GetBitCtx(ctx context.Context, key string, offset int64) (val in
return
}
// GetSet is the implementation of redis getset command.
func (s *Redis) GetSet(key, value string) (string, error) {
return s.GetSetCtx(context.Background(), key, value)
}
// GetSetCtx is the implementation of redis getset command.
func (s *Redis) GetSetCtx(ctx context.Context, key, value string) (val string, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
if val, err = conn.GetSet(ctx, key, value).Result(); err == red.Nil {
return nil
}
return err
}, acceptable)
return
}
// Hdel is the implementation of redis hdel command.
func (s *Redis) Hdel(key string, fields ...string) (bool, error) {
return s.HdelCtx(context.Background(), key, fields...)
@@ -1381,21 +1404,28 @@ func (s *Redis) ScanCtx(ctx context.Context, cursor uint64, match string, count
}
// SetBit is the implementation of redis setbit command.
func (s *Redis) SetBit(key string, offset int64, value int) error {
func (s *Redis) SetBit(key string, offset int64, value int) (int, error) {
return s.SetBitCtx(context.Background(), key, offset, value)
}
// SetBitCtx is the implementation of redis setbit command.
func (s *Redis) SetBitCtx(ctx context.Context, key string, offset int64, value int) error {
return s.brk.DoWithAcceptable(func() error {
func (s *Redis) SetBitCtx(ctx context.Context, key string, offset int64, value int) (val int, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
_, err = conn.SetBit(ctx, key, offset, value).Result()
return err
v, err := conn.SetBit(ctx, key, offset, value).Result()
if err != nil {
return err
}
val = int(v)
return nil
}, acceptable)
return
}
// Sscan is the implementation of redis sscan command.
@@ -2358,7 +2388,7 @@ func WithTLS() Option {
}
func acceptable(err error) bool {
return err == nil || err == red.Nil
return err == nil || err == red.Nil || err == context.Canceled
}
func getRedis(r *Redis) (RedisNode, error) {

View File

@@ -387,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)
@@ -701,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")

View File

@@ -2,6 +2,7 @@ package redis
import (
"math/rand"
"strconv"
"sync/atomic"
"time"
@@ -11,19 +12,26 @@ import (
)
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

@@ -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

@@ -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"),
},
},
}

20
go.mod
View File

@@ -17,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
@@ -42,10 +42,20 @@ 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-20220127200216-cd36cc0744dd // indirect
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
google.golang.org/genproto v0.0.0-20220211171837-173942840c17 // 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
)

130
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=
@@ -52,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=
@@ -163,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=
@@ -170,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=
@@ -188,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=
@@ -237,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=
@@ -273,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=
@@ -331,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=
@@ -371,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=
@@ -397,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=
@@ -465,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=
@@ -474,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-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
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=
@@ -531,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=
@@ -546,8 +596,8 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
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-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/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=
@@ -596,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=
@@ -618,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=
@@ -642,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-20220211171837-173942840c17 h1:2X+CNIheCutWRyKRte8szGxrE5ggtV4U+NKAbh/oLhg=
google.golang.org/genproto v0.0.0-20220211171837-173942840c17/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
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=
@@ -656,6 +734,10 @@ 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=
@@ -670,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=
@@ -709,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 介绍
@@ -242,6 +248,10 @@ go-zero 已被许多公司用于生产部署,接入场景如在线教育、电
>54. 一犀科技成都有限公司
>55. 北京术杰科技有限公司
>56. 时代脉搏网络科技(云浮市)有限公司
>57. 店有帮
>58. 七牛云
>59. 费芮网络
>60. 51CTO
如果贵公司也已使用 go-zero欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。
@@ -271,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)
}
}

View File

@@ -56,22 +56,22 @@ Port: 54321
}
for _, test := range tests {
var srv *Server
var svr *Server
var err error
if test.fail {
_, err = NewServer(test.c, test.opts...)
assert.NotNil(t, err)
continue
} else {
srv = MustNewServer(test.c, test.opts...)
svr = MustNewServer(test.c, test.opts...)
}
srv.Use(ToMiddleware(func(next http.Handler) http.Handler {
svr.Use(ToMiddleware(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
})
}))
srv.AddRoute(Route{
svr.AddRoute(Route{
Method: http.MethodGet,
Path: "/",
Handler: nil,
@@ -89,12 +89,19 @@ Port: 54321
}
}()
srv.Start()
srv.Stop()
svr.Start()
svr.Stop()
}()
}
}
func TestWithMaxBytes(t *testing.T) {
const maxBytes = 1000
var fr featuredRoutes
WithMaxBytes(maxBytes)(&fr)
assert.Equal(t, int64(maxBytes), fr.maxBytes)
}
func TestWithMiddleware(t *testing.T) {
m := make(map[string]string)
rt := router.NewRouter()
@@ -290,9 +297,9 @@ Port: 54321
}
for _, testCase := range testCases {
srv, err := NewServer(testCase.c, testCase.opts...)
svr, err := NewServer(testCase.c, testCase.opts...)
assert.Nil(t, err)
assert.Equal(t, srv.ngin.tlsConfig, testCase.res)
assert.Equal(t, svr.ngin.tlsConfig, testCase.res)
}
}
@@ -304,11 +311,11 @@ Port: 54321
var cnf RestConf
assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(configYaml), &cnf))
rt := router.NewRouter()
srv, err := NewServer(cnf, WithRouter(rt))
svr, err := NewServer(cnf, WithRouter(rt))
assert.Nil(t, err)
opt := WithCors("local")
opt(srv)
opt(svr)
}
func TestWithCustomCors(t *testing.T) {
@@ -319,7 +326,7 @@ Port: 54321
var cnf RestConf
assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(configYaml), &cnf))
rt := router.NewRouter()
srv, err := NewServer(cnf, WithRouter(rt))
svr, err := NewServer(cnf, WithRouter(rt))
assert.Nil(t, err)
opt := WithCustomCors(func(header http.Header) {
@@ -327,5 +334,5 @@ Port: 54321
}, func(w http.ResponseWriter) {
w.WriteHeader(http.StatusOK)
}, "local")
opt(srv)
opt(svr)
}

View File

@@ -36,5 +36,6 @@ type (
jwt jwtSetting
signature signatureSetting
routes []Route
maxBytes int64
}
)

1
tools/goctl/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.vscode

34
tools/goctl/Dockerfile Normal file
View File

@@ -0,0 +1,34 @@
FROM golang:alpine AS builder
LABEL stage=gobuilder
ENV CGO_ENABLED 0
ENV GOPROXY https://goproxy.cn,direct
RUN apk update --no-cache && apk add --no-cache tzdata
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
WORKDIR /build
ADD go.mod .
ADD go.sum .
RUN go mod download
COPY . .
RUN go build -ldflags="-s -w" -o /app/goctl ./goctl.go
FROM alpine
RUN apk update --no-cache && apk add --no-cache protoc
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai
COPY --from=builder /go/bin/protoc-gen-go /usr/bin/protoc-gen-go
COPY --from=builder /go/bin/protoc-gen-go-grpc /usr/bin/protoc-gen-go-grpc
ENV TZ Asia/Shanghai
WORKDIR /app
COPY --from=builder /app/goctl /app/goctl
CMD ["./goctl"]

View File

@@ -55,8 +55,9 @@ func ApiCommand(c *cli.Context) error {
home := c.String("home")
remote := c.String("remote")
branch := c.String("branch")
if len(remote) > 0 {
repo, _ := util.CloneIntoGitHome(remote)
repo, _ := util.CloneIntoGitHome(remote, branch)
if len(repo) > 0 {
home = repo
}

View File

@@ -0,0 +1,40 @@
package dartgen
import (
"fmt"
"os"
"os/exec"
)
const dartExec = "dart"
func formatDir(dir string) error {
ok, err := dirctoryExists(dir)
if err != nil {
return err
}
if !ok {
return fmt.Errorf("format failed, directory %q does not exist", dir)
}
_, err = exec.LookPath(dartExec)
if err != nil {
return err
}
cmd := exec.Command(dartExec, "format", dir)
cmd.Env = os.Environ()
cmd.Stderr = os.Stderr
return cmd.Run()
}
func dirctoryExists(dir string) (bool, error) {
_, err := os.Stat(dir)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}

View File

@@ -2,6 +2,7 @@ package dartgen
import (
"errors"
"fmt"
"strings"
"github.com/urfave/cli"
@@ -13,12 +14,18 @@ import (
func DartCommand(c *cli.Context) error {
apiFile := c.String("api")
dir := c.String("dir")
isLegacy := c.Bool("legacy")
hostname := c.String("hostname")
if len(apiFile) == 0 {
return errors.New("missing -api")
}
if len(dir) == 0 {
return errors.New("missing -dir")
}
if len(hostname) == 0 {
fmt.Println("you could use '-hostname' flag to specify your server hostname")
hostname = "go-zero.dev"
}
api, err := parser.Parse(apiFile)
if err != nil {
@@ -30,8 +37,11 @@ func DartCommand(c *cli.Context) error {
dir = dir + "/"
}
api.Info.Title = strings.Replace(apiFile, ".api", "", -1)
logx.Must(genData(dir+"data/", api))
logx.Must(genApi(dir+"api/", api))
logx.Must(genVars(dir + "vars/"))
logx.Must(genData(dir+"data/", api, isLegacy))
logx.Must(genApi(dir+"api/", api, isLegacy))
logx.Must(genVars(dir+"vars/", isLegacy, hostname))
if err := formatDir(dir); err != nil {
logx.Errorf("failed to format, %v", err)
}
return nil
}

View File

@@ -2,13 +2,14 @@ package dartgen
import (
"os"
"strings"
"text/template"
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
)
const apiTemplate = `import 'api.dart';
import '../data/{{with .Info}}{{.Title}}{{end}}.dart';
import '../data/{{with .Info}}{{getBaseName .Title}}{{end}}.dart';
{{with .Service}}
/// {{.Name}}
{{range .Routes}}
@@ -22,24 +23,45 @@ Future {{pathToFuncName .Path}}( {{if ne .Method "get"}}{{with .RequestType}}{{.
Function eventually}) async {
await api{{if eq .Method "get"}}Get{{else}}Post{{end}}('{{.Path}}',{{if ne .Method "get"}}request,{{end}}
ok: (data) {
if (ok != null) ok({{with .ResponseType}}{{.Name}}{{end}}.fromJson(data));
if (ok != null) ok({{with .ResponseType}}{{.Name}}.fromJson(data){{end}});
}, fail: fail, eventually: eventually);
}
{{end}}
{{end}}`
func genApi(dir string, api *spec.ApiSpec) error {
const apiTemplateV2 = `import 'api.dart';
import '../data/{{with .Info}}{{getBaseName .Title}}{{end}}.dart';
{{with .Service}}
/// {{.Name}}
{{range .Routes}}
/// --{{.Path}}--
///
/// request: {{with .RequestType}}{{.Name}}{{end}}
/// response: {{with .ResponseType}}{{.Name}}{{end}}
Future {{pathToFuncName .Path}}( {{if ne .Method "get"}}{{with .RequestType}}{{.Name}} request,{{end}}{{end}}
{Function({{with .ResponseType}}{{.Name}}{{end}})? ok,
Function(String)? fail,
Function? eventually}) async {
await api{{if eq .Method "get"}}Get{{else}}Post{{end}}('{{.Path}}',{{if ne .Method "get"}}request,{{end}}
ok: (data) {
if (ok != null) ok({{with .ResponseType}}{{.Name}}.fromJson(data){{end}});
}, fail: fail, eventually: eventually);
}
{{end}}
{{end}}`
func genApi(dir string, api *spec.ApiSpec, isLegacy bool) error {
err := os.MkdirAll(dir, 0o755)
if err != nil {
return err
}
err = genApiFile(dir)
err = genApiFile(dir, isLegacy)
if err != nil {
return err
}
file, err := os.OpenFile(dir+api.Service.Name+".dart", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
file, err := os.OpenFile(dir+strings.ToLower(api.Service.Name+".dart"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil {
return err
}
@@ -47,7 +69,11 @@ func genApi(dir string, api *spec.ApiSpec) error {
defer file.Close()
t := template.New("apiTemplate")
t = t.Funcs(funcMap)
t, err = t.Parse(apiTemplate)
tpl := apiTemplateV2
if isLegacy {
tpl = apiTemplate
}
t, err = t.Parse(tpl)
if err != nil {
return err
}
@@ -55,7 +81,7 @@ func genApi(dir string, api *spec.ApiSpec) error {
return t.Execute(file, api)
}
func genApiFile(dir string) error {
func genApiFile(dir string, isLegacy bool) error {
path := dir + "api.dart"
if fileExists(path) {
return nil
@@ -66,6 +92,10 @@ func genApiFile(dir string) error {
}
defer apiFile.Close()
_, err = apiFile.WriteString(apiFileContent)
tpl := apiFileContentV2
if isLegacy {
tpl = apiFileContent
}
_, err = apiFile.WriteString(tpl)
return err
}

View File

@@ -2,6 +2,7 @@ package dartgen
import (
"os"
"strings"
"text/template"
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
@@ -19,30 +20,54 @@ class {{.Name}}{
});
factory {{.Name}}.fromJson(Map<String,dynamic> m) {
return {{.Name}}({{range .Members}}
{{lowCamelCase .Name}}: {{if isDirectType .Type.Name}}m['{{tagGet .Tag "json"}}']{{else if isClassListType .Type.Name}}(m['{{tagGet .Tag "json"}}'] as List<dynamic>).map((i) => {{getCoreType .Type.Name}}.fromJson(i)){{else}}{{.Type.Name}}.fromJson(m['{{tagGet .Tag "json"}}']){{end}},{{end}}
{{lowCamelCase .Name}}: {{if isDirectType .Type.Name}}m['{{getPropertyFromMember .}}']{{else if isClassListType .Type.Name}}(m['{{getPropertyFromMember .}}'] as List<dynamic>).map((i) => {{getCoreType .Type.Name}}.fromJson(i)){{else}}{{.Type.Name}}.fromJson(m['{{getPropertyFromMember .}}']){{end}},{{end}}
);
}
Map<String,dynamic> toJson() {
return { {{range .Members}}
'{{tagGet .Tag "json"}}': {{if isDirectType .Type.Name}}{{lowCamelCase .Name}}{{else if isClassListType .Type.Name}}{{lowCamelCase .Name}}.map((i) => i.toJson()){{else}}{{lowCamelCase .Name}}.toJson(){{end}},{{end}}
'{{getPropertyFromMember .}}': {{if isDirectType .Type.Name}}{{lowCamelCase .Name}}{{else if isClassListType .Type.Name}}{{lowCamelCase .Name}}.map((i) => i.toJson()){{else}}{{lowCamelCase .Name}}.toJson(){{end}},{{end}}
};
}
}
{{end}}
`
func genData(dir string, api *spec.ApiSpec) error {
const dataTemplateV2 = `// --{{with .Info}}{{.Title}}{{end}}--
{{ range .Types}}
class {{.Name}} {
{{range .Members}}
{{if .Comment}}{{.Comment}}{{end}}
final {{.Type.Name}} {{lowCamelCase .Name}};
{{end}}{{.Name}}({{if .Members}}{
{{range .Members}} required this.{{lowCamelCase .Name}},
{{end}}}{{end}});
factory {{.Name}}.fromJson(Map<String,dynamic> m) {
return {{.Name}}({{range .Members}}
{{lowCamelCase .Name}}: {{if isDirectType .Type.Name}}m['{{getPropertyFromMember .}}']
{{else if isClassListType .Type.Name}}(m['{{getPropertyFromMember .}}'] as List<dynamic>).map((i) => {{getCoreType .Type.Name}}.fromJson(i)).toList()
{{else}}{{.Type.Name}}.fromJson(m['{{getPropertyFromMember .}}']){{end}},{{end}}
);
}
Map<String,dynamic> toJson() {
return { {{range .Members}}
'{{getPropertyFromMember .}}': {{if isDirectType .Type.Name}}{{lowCamelCase .Name}}{{else if isClassListType .Type.Name}}{{lowCamelCase .Name}}.map((i) => i.toJson()){{else}}{{lowCamelCase .Name}}.toJson(){{end}},{{end}}
};
}
}
{{end}}`
func genData(dir string, api *spec.ApiSpec, isLegacy bool) error {
err := os.MkdirAll(dir, 0o755)
if err != nil {
return err
}
err = genTokens(dir)
err = genTokens(dir, isLegacy)
if err != nil {
return err
}
file, err := os.OpenFile(dir+api.Service.Name+".dart", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
file, err := os.OpenFile(dir+strings.ToLower(api.Service.Name+".dart"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil {
return err
}
@@ -50,7 +75,11 @@ func genData(dir string, api *spec.ApiSpec) error {
t := template.New("dataTemplate")
t = t.Funcs(funcMap)
t, err = t.Parse(dataTemplate)
tpl := dataTemplateV2
if isLegacy {
tpl = dataTemplate
}
t, err = t.Parse(tpl)
if err != nil {
return err
}
@@ -63,7 +92,7 @@ func genData(dir string, api *spec.ApiSpec) error {
return t.Execute(file, api)
}
func genTokens(dir string) error {
func genTokens(dir string, isLeagcy bool) error {
path := dir + "tokens.dart"
if fileExists(path) {
return nil
@@ -75,7 +104,11 @@ func genTokens(dir string) error {
}
defer tokensFile.Close()
_, err = tokensFile.WriteString(tokensFileContent)
tpl := tokensFileContentV2
if isLeagcy {
tpl = tokensFileContent
}
_, err = tokensFile.WriteString(tpl)
return err
}

View File

@@ -1,11 +1,13 @@
package dartgen
import (
"fmt"
"io/ioutil"
"os"
)
const varTemplate = `import 'dart:convert';
const (
varTemplate = `import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import '../data/tokens.dart';
@@ -40,21 +42,59 @@ Future<Tokens> getTokens() async {
}
`
func genVars(dir string) error {
varTemplateV2 = `import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import '../data/tokens.dart';
const String _tokenKey = 'tokens';
/// Saves tokens
Future<bool> setTokens(Tokens tokens) async {
var sp = await SharedPreferences.getInstance();
return await sp.setString(_tokenKey, jsonEncode(tokens.toJson()));
}
/// remove tokens
Future<bool> removeTokens() async {
var sp = await SharedPreferences.getInstance();
return sp.remove(_tokenKey);
}
/// Reads tokens
Future<Tokens?> getTokens() async {
try {
var sp = await SharedPreferences.getInstance();
var str = sp.getString('tokens');
if (str.isEmpty) {
return null;
}
return Tokens.fromJson(jsonDecode(str));
} catch (e) {
print(e);
return null;
}
}`
)
func genVars(dir string, isLegacy bool, hostname string) error {
err := os.MkdirAll(dir, 0o755)
if err != nil {
return err
}
if !fileExists(dir + "vars.dart") {
err = ioutil.WriteFile(dir+"vars.dart", []byte(`const serverHost='demo-crm.xiaoheiban.cn';`), 0o644)
err = ioutil.WriteFile(dir+"vars.dart", []byte(fmt.Sprintf(`const serverHost='%s';`, hostname)), 0o644)
if err != nil {
return err
}
}
if !fileExists(dir + "kv.dart") {
err = ioutil.WriteFile(dir+"kv.dart", []byte(varTemplate), 0o644)
tpl := varTemplateV2
if isLegacy {
tpl = varTemplate
}
err = ioutil.WriteFile(dir+"kv.dart", []byte(tpl), 0o644)
if err != nil {
return err
}

View File

@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"os"
"path"
"strings"
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
@@ -34,18 +35,16 @@ func pathToFuncName(path string) string {
return util.ToLower(camel[:1]) + camel[1:]
}
func tagGet(tag, k string) string {
tags, err := spec.Parse(tag)
if err != nil {
panic(k + " not exist")
}
func getBaseName(str string) string {
return path.Base(str)
}
v, err := tags.Get(k)
func getPropertyFromMember(member spec.Member) string {
name, err := member.GetPropertyName()
if err != nil {
panic(k + " value not exist")
panic(fmt.Sprintf("cannot get property name of %q", member.Name))
}
return v.Name
return name
}
func isDirectType(s string) bool {
@@ -129,11 +128,10 @@ func specTypeToDart(tp spec.Type) (string, error) {
}
s := getBaseType(valueType)
if len(s) == 0 {
return s, errors.New("unsupported primitive type " + tp.Name())
if len(s) != 0 {
return s, nil
}
return s, nil
return fmt.Sprintf("List<%s>", valueType), nil
case spec.InterfaceType:
return "Object", nil
case spec.PointerType:

View File

@@ -0,0 +1,71 @@
package dartgen
import (
"testing"
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
)
func Test_getPropertyFromMember(t *testing.T) {
tests := []struct {
name string
member spec.Member
want string
}{
{
name: "json tag should be ok",
member: spec.Member{
Tag: "`json:\"foo\"`",
Name: "Foo",
},
want: "foo",
},
{
name: "form tag should be ok",
member: spec.Member{
Tag: "`form:\"bar\"`",
Name: "Bar",
},
want: "bar",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := getPropertyFromMember(tt.member); got != tt.want {
t.Errorf("getPropertyFromMember() = %v, want %v", got, tt.want)
}
})
}
}
func Test_specTypeToDart(t *testing.T) {
tests := []struct {
name string
specType spec.Type
want string
wantErr bool
}{
{
name: "[]string should return List<String>",
specType: spec.ArrayType{RawName: "[]string", Value: spec.PrimitiveType{RawName: "string"}},
want: "List<String>",
},
{
name: "[]Foo should return List<Foo>",
specType: spec.ArrayType{RawName: "[]Foo", Value: spec.DefineStruct{RawName: "Foo"}},
want: "List<Foo>",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := specTypeToDart(tt.specType)
if (err != nil) != tt.wantErr {
t.Errorf("specTypeToDart() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("specTypeToDart() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -3,12 +3,13 @@ package dartgen
import "text/template"
var funcMap = template.FuncMap{
"tagGet": tagGet,
"isDirectType": isDirectType,
"isClassListType": isClassListType,
"getCoreType": getCoreType,
"pathToFuncName": pathToFuncName,
"lowCamelCase": lowCamelCase,
"getBaseName": getBaseName,
"getPropertyFromMember": getPropertyFromMember,
"isDirectType": isDirectType,
"isClassListType": isClassListType,
"getCoreType": getCoreType,
"pathToFuncName": pathToFuncName,
"lowCamelCase": lowCamelCase,
}
const (
@@ -99,6 +100,96 @@ Future _apiRequest(String method, String path, dynamic data,
}
`
apiFileContentV2 = `import 'dart:io';
import 'dart:convert';
import '../vars/kv.dart';
import '../vars/vars.dart';
/// send request with post method
///
/// data: any request class that will be converted to json automatically
/// ok: is called when request succeeds
/// fail: is called when request fails
/// eventually: is always called until the nearby functions returns
Future apiPost(String path, dynamic data,
{Map<String, String>? header,
Function(Map<String, dynamic>)? ok,
Function(String)? fail,
Function? eventually}) async {
await _apiRequest('POST', path, data,
header: header, ok: ok, fail: fail, eventually: eventually);
}
/// send request with get method
///
/// ok: is called when request succeeds
/// fail: is called when request fails
/// eventually: is always called until the nearby functions returns
Future apiGet(String path,
{Map<String, String>? header,
Function(Map<String, dynamic>)? ok,
Function(String)? fail,
Function? eventually}) async {
await _apiRequest('GET', path, null,
header: header, ok: ok, fail: fail, eventually: eventually);
}
Future _apiRequest(String method, String path, dynamic data,
{Map<String, String>? header,
Function(Map<String, dynamic>)? ok,
Function(String)? fail,
Function? eventually}) async {
var tokens = await getTokens();
try {
var client = HttpClient();
HttpClientRequest r;
if (method == 'POST') {
r = await client.postUrl(Uri.parse('https://' + serverHost + path));
} else {
r = await client.getUrl(Uri.parse('https://' + serverHost + path));
}
r.headers.set('Content-Type', 'application/json');
if (tokens != null) {
r.headers.set('Authorization', tokens.accessToken);
}
if (header != null) {
header.forEach((k, v) {
r.headers.set(k, v);
});
}
var strData = '';
if (data != null) {
strData = jsonEncode(data);
}
r.write(strData);
var rp = await r.close();
var body = await rp.transform(utf8.decoder).join();
print('${rp.statusCode} - $path');
print('-- request --');
print(strData);
print('-- response --');
print('$body \n');
if (rp.statusCode == 404) {
if (fail != null) fail('404 not found');
} else {
Map<String, dynamic> base = jsonDecode(body);
if (rp.statusCode == 200) {
if (base['code'] != 0) {
if (fail != null) fail(base['desc']);
} else {
if (ok != null) ok(base['data']);
}
} else if (base['code'] != 0) {
if (fail != null) fail(base['desc']);
}
}
} catch (e) {
if (fail != null) fail(e.toString());
}
if (eventually != null) eventually();
}`
tokensFileContent = `class Tokens {
/// 用于访问的token, 每次请求都必须带在Header里面
final String accessToken;
@@ -132,5 +223,41 @@ Future _apiRequest(String method, String path, dynamic data,
};
}
}
`
tokensFileContentV2 = `class Tokens {
/// 用于访问的token, 每次请求都必须带在Header里面
final String accessToken;
final int accessExpire;
/// 用于刷新token
final String refreshToken;
final int refreshExpire;
final int refreshAfter;
Tokens({
required this.accessToken,
required this.accessExpire,
required this.refreshToken,
required this.refreshExpire,
required this.refreshAfter
});
factory Tokens.fromJson(Map<String, dynamic> m) {
return Tokens(
accessToken: m['access_token'],
accessExpire: m['access_expire'],
refreshToken: m['refresh_token'],
refreshExpire: m['refresh_expire'],
refreshAfter: m['refresh_after']);
}
Map<String, dynamic> toJson() {
return {
'access_token': accessToken,
'access_expire': accessExpire,
'refresh_token': refreshToken,
'refresh_expire': refreshExpire,
'refresh_after': refreshAfter,
};
}
}
`
)

View File

@@ -12,7 +12,7 @@ import (
"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
)
// DocCommand generate markdown doc file
// DocCommand generate Markdown doc file
func DocCommand(c *cli.Context) error {
dir := c.String("dir")
if len(dir) == 0 {
@@ -45,7 +45,7 @@ func DocCommand(c *cli.Context) error {
for _, p := range files {
api, err := parser.Parse(p)
if err != nil {
return fmt.Errorf("parse file: %s, err: %s", p, err.Error())
return fmt.Errorf("parse file: %s, err: %w", p, err)
}
api.Service = api.Service.JoinPrefix()

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"go/format"
"go/scanner"
"io"
"io/ioutil"
"os"
"path/filepath"
@@ -28,14 +29,15 @@ const (
// GoFormatApi format api file
func GoFormatApi(c *cli.Context) error {
useStdin := c.Bool("stdin")
skipCheckDeclare := c.Bool("declare")
dir := c.String("dir")
var be errorx.BatchError
if useStdin {
if err := apiFormatByStdin(); err != nil {
if err := apiFormatReader(os.Stdin, dir, skipCheckDeclare); err != nil {
be.Add(err)
}
} else {
dir := c.String("dir")
if len(dir) == 0 {
return errors.New("missing -dir")
}
@@ -47,7 +49,7 @@ func GoFormatApi(c *cli.Context) error {
err = filepath.Walk(dir, func(path string, fi os.FileInfo, errBack error) (err error) {
if strings.HasSuffix(path, ".api") {
if err := ApiFormatByPath(path); err != nil {
if err := ApiFormatByPath(path, skipCheckDeclare); err != nil {
be.Add(util.WrapErr(err, fi.Name()))
}
}
@@ -64,13 +66,14 @@ func GoFormatApi(c *cli.Context) error {
return be.Err()
}
func apiFormatByStdin() error {
data, err := ioutil.ReadAll(os.Stdin)
// apiFormatReader
// filename is needed when there are `import` literals.
func apiFormatReader(reader io.Reader, filename string, skipCheckDeclare bool) error {
data, err := ioutil.ReadAll(reader)
if err != nil {
return err
}
result, err := apiFormat(string(data))
result, err := apiFormat(string(data), skipCheckDeclare, filename)
if err != nil {
return err
}
@@ -80,7 +83,7 @@ func apiFormatByStdin() error {
}
// ApiFormatByPath format api from file path
func ApiFormatByPath(apiFilePath string) error {
func ApiFormatByPath(apiFilePath string, skipCheckDeclare bool) error {
data, err := ioutil.ReadFile(apiFilePath)
if err != nil {
return err
@@ -91,12 +94,12 @@ func ApiFormatByPath(apiFilePath string) error {
return err
}
result, err := apiFormat(string(data), abs)
result, err := apiFormat(string(data), skipCheckDeclare, abs)
if err != nil {
return err
}
_, err = parser.ParseContent(result, abs)
_, err = parser.ParseContentWithParserSkipCheckTypeDeclaration(result, abs)
if err != nil {
return err
}
@@ -104,8 +107,13 @@ func ApiFormatByPath(apiFilePath string) error {
return ioutil.WriteFile(apiFilePath, []byte(result), os.ModePerm)
}
func apiFormat(data string, filename ...string) (string, error) {
_, err := parser.ParseContent(data, filename...)
func apiFormat(data string, skipCheckDeclare bool, filename ...string) (string, error) {
var err error
if skipCheckDeclare {
_, err = parser.ParseContentWithParserSkipCheckTypeDeclaration(data, filename...)
} else {
_, err = parser.ParseContent(data, filename...)
}
if err != nil {
return "", err
}

View File

@@ -1,18 +1,25 @@
package format
import (
"fmt"
"io/fs"
"io/ioutil"
"os"
"path"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
notFormattedStr = `
type Request struct {
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
}
type Response struct {
Message string ` + "`" + `json:"message"` + "`" + `
Students []Student ` + "`" + `json:"students"` + "`" + `
}
service A-api {
@server(
@@ -26,7 +33,8 @@ handler: GreetHandler
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
}
type Response {
Message string ` + "`" + `json:"message"` + "`" + `
Message string ` + "`" + `json:"message"` + "`" + `
Students []Student ` + "`" + `json:"students"` + "`" + `
}
service A-api {
@server(
@@ -37,7 +45,32 @@ service A-api {
)
func TestFormat(t *testing.T) {
r, err := apiFormat(notFormattedStr)
r, err := apiFormat(notFormattedStr, true)
assert.Nil(t, err)
assert.Equal(t, formattedStr, r)
_, err = apiFormat(notFormattedStr, false)
assert.Errorf(t, err, " line 7:13 can not found declaration 'Student' in context")
}
func Test_apiFormatReader_issue1721(t *testing.T) {
dir, err := os.MkdirTemp("", "goctl-api-format")
require.NoError(t, err)
defer os.RemoveAll(dir)
subDir := path.Join(dir, "sub")
err = os.MkdirAll(subDir, fs.ModePerm)
require.NoError(t, err)
importedFilename := path.Join(dir, "foo.api")
err = ioutil.WriteFile(importedFilename, []byte{}, fs.ModePerm)
require.NoError(t, err)
filename := path.Join(subDir, "bar.api")
err = ioutil.WriteFile(filename, []byte(fmt.Sprintf(`import "%s"`, importedFilename)), 0644)
require.NoError(t, err)
f, err := os.Open(filename)
require.NoError(t, err)
err = apiFormatReader(f, filename, false)
assert.NoError(t, err)
}

View File

@@ -33,8 +33,9 @@ func GoCommand(c *cli.Context) error {
namingStyle := c.String("style")
home := c.String("home")
remote := c.String("remote")
branch := c.String("branch")
if len(remote) > 0 {
repo, _ := util.CloneIntoGitHome(remote)
repo, _ := util.CloneIntoGitHome(remote, branch)
if len(repo) > 0 {
home = repo
}
@@ -85,7 +86,7 @@ func DoGenProject(apiFile, dir, style string) error {
return err
}
if err := apiformat.ApiFormatByPath(apiFile); err != nil {
if err := apiformat.ApiFormatByPath(apiFile, false); err != nil {
return err
}

View File

@@ -34,7 +34,7 @@ func {{.HandlerName}}(svcCtx *svc.ServiceContext) http.HandlerFunc {
}
{{end}}l := {{.LogicName}}.New{{.LogicType}}(r.Context(), svcCtx)
{{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}req{{end}})
{{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}&req{{end}})
if err != nil {
httpx.Error(w, err)
} else {

View File

@@ -26,8 +26,8 @@ type {{.logic}} struct {
svcCtx *svc.ServiceContext
}
func New{{.logic}}(ctx context.Context, svcCtx *svc.ServiceContext) {{.logic}} {
return {{.logic}}{
func New{{.logic}}(ctx context.Context, svcCtx *svc.ServiceContext) *{{.logic}} {
return &{{.logic}}{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
@@ -73,7 +73,7 @@ func genLogicByRoute(dir, rootPkg string, cfg *config.Config, group spec.Group,
returnString = "return nil"
}
if len(route.RequestTypeName()) > 0 {
requestString = "req " + requestGoTypeName(route, typesPacket)
requestString = "req *" + requestGoTypeName(route, typesPacket)
}
subDir := getLogicFolderPath(group, route)

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