mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-05-11 16:59:59 +08:00
Compare commits
105 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3369f8e81 | ||
|
|
c9b05ae07e | ||
|
|
32a59dbc27 | ||
|
|
ba0dff2d61 | ||
|
|
10da5e0424 | ||
|
|
4bed34090f | ||
|
|
2bfecf9354 | ||
|
|
6d129e0264 | ||
|
|
a2df1bb164 | ||
|
|
5f02e623f5 | ||
|
|
963b52fb1b | ||
|
|
02265d0bfe | ||
|
|
2e57e91826 | ||
|
|
82c642d3f4 | ||
|
|
b2571883ca | ||
|
|
00ff50c2cc | ||
|
|
4d7fa08b0b | ||
|
|
367afb544c | ||
|
|
43b8c7f641 | ||
|
|
a2dcb0079a | ||
|
|
f9619328f2 | ||
|
|
bae061a67e | ||
|
|
0b176e17ac | ||
|
|
6340e24c17 | ||
|
|
74e0676617 | ||
|
|
0defb7522f | ||
|
|
0c786ca849 | ||
|
|
26c541b9cb | ||
|
|
ade6f9ee46 | ||
|
|
f4502171ea | ||
|
|
8157e2118d | ||
|
|
e52dace416 | ||
|
|
dc260f196a | ||
|
|
559726112c | ||
|
|
a5fcf24c04 | ||
|
|
fc9b3ffdc1 | ||
|
|
e71c505e94 | ||
|
|
21c49009c0 | ||
|
|
69d355eb4b | ||
|
|
83f88d177f | ||
|
|
641ebf1667 | ||
|
|
cf435bfcc1 | ||
|
|
28f1b15b8e | ||
|
|
42413dc294 | ||
|
|
ec7ac43948 | ||
|
|
deefc1a8eb | ||
|
|
036328f1ea | ||
|
|
85057a623d | ||
|
|
1c544a26be | ||
|
|
20a61ce43e | ||
|
|
dd294e8cd6 | ||
|
|
3e9d0161bc | ||
|
|
cf6c349118 | ||
|
|
c7a0ec428c | ||
|
|
ce1c02f4f9 | ||
|
|
c3756a8f1c | ||
|
|
f4fd735aee | ||
|
|
683d793719 | ||
|
|
affbcb5698 | ||
|
|
f0d1722bbd | ||
|
|
c4f8eca459 | ||
|
|
251c071418 | ||
|
|
6652c4e445 | ||
|
|
f73613dff0 | ||
|
|
7a75dce465 | ||
|
|
801f1adf71 | ||
|
|
f76b976262 | ||
|
|
a49f9060c2 | ||
|
|
ebe28882eb | ||
|
|
fdc57d07d7 | ||
|
|
ef22042f4d | ||
|
|
944193ce25 | ||
|
|
dcfc9b79f1 | ||
|
|
b7052854bb | ||
|
|
4729a16142 | ||
|
|
3604659027 | ||
|
|
9f7f94b673 | ||
|
|
0b3629b636 | ||
|
|
a644ec7edd | ||
|
|
9941055eaa | ||
|
|
10fd9131a1 | ||
|
|
90828a0d4a | ||
|
|
b1c3c21c81 | ||
|
|
97a8b3ade5 | ||
|
|
95a5f64493 | ||
|
|
20e659749a | ||
|
|
94708cc78f | ||
|
|
06fafd2153 | ||
|
|
79de932646 | ||
|
|
b562e940e7 | ||
|
|
69068cdaf0 | ||
|
|
f25788ebea | ||
|
|
1293c4321b | ||
|
|
e3e08a7396 | ||
|
|
4b071f4c33 | ||
|
|
81831b60a9 | ||
|
|
1677a4dceb | ||
|
|
dac3600b53 | ||
|
|
3db64c7d47 | ||
|
|
7eb6aae949 | ||
|
|
07128213d6 | ||
|
|
9504d30049 | ||
|
|
ce73b9a85c | ||
|
|
4d2a146733 | ||
|
|
46e236fef7 |
@@ -1,3 +1,6 @@
|
||||
comment: false
|
||||
comment:
|
||||
layout: "flags, files"
|
||||
behavior: once
|
||||
require_changes: true
|
||||
ignore:
|
||||
- "tools"
|
||||
19
.github/workflows/go.yml
vendored
19
.github/workflows/go.yml
vendored
@@ -11,15 +11,17 @@ jobs:
|
||||
name: Linux
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ^1.16
|
||||
check-latest: true
|
||||
cache: true
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Get dependencies
|
||||
run: |
|
||||
go get -v -t -d ./...
|
||||
@@ -40,13 +42,16 @@ jobs:
|
||||
name: Windows
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ^1.16
|
||||
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v3
|
||||
# use 1.16 to guarantee Go 1.16 compatibility
|
||||
go-version: 1.16
|
||||
check-latest: true
|
||||
cache: true
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
|
||||
28
ROADMAP.md
28
ROADMAP.md
@@ -1,28 +0,0 @@
|
||||
# go-zero Roadmap
|
||||
|
||||
This document defines a high level roadmap for go-zero development and upcoming releases.
|
||||
Community and contributor involvement is vital for successfully implementing all desired items for each release.
|
||||
We hope that the items listed below will inspire further engagement from the community to keep go-zero progressing and shipping exciting and valuable features.
|
||||
|
||||
## 2021 Q2
|
||||
- [x] Support service discovery through K8S client api
|
||||
- [x] Log full sql statements for easier sql problem solving
|
||||
|
||||
## 2021 Q3
|
||||
- [x] Support `goctl model pg` to support PostgreSQL code generation
|
||||
- [x] Adapt builtin tracing mechanism to opentracing solutions
|
||||
|
||||
## 2021 Q4
|
||||
- [x] Support `username/password` authentication in ETCD
|
||||
- [x] Support `SSL/TLS` in ETCD
|
||||
- [x] Support `SSL/TLS` in `zRPC`
|
||||
- [x] Support `TLS` in redis connections
|
||||
- [x] Support `goctl bug` to report bugs conveniently
|
||||
|
||||
## 2022
|
||||
- [x] Support `context` in redis related methods for timeout and tracing
|
||||
- [x] Support `context` in sql related methods for timeout and tracing
|
||||
- [x] 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
|
||||
@@ -20,16 +20,16 @@ func (b noOpBreaker) Do(req func() error) error {
|
||||
return req()
|
||||
}
|
||||
|
||||
func (b noOpBreaker) DoWithAcceptable(req func() error, acceptable Acceptable) error {
|
||||
func (b noOpBreaker) DoWithAcceptable(req func() error, _ Acceptable) error {
|
||||
return req()
|
||||
}
|
||||
|
||||
func (b noOpBreaker) DoWithFallback(req func() error, fallback func(err error) error) error {
|
||||
func (b noOpBreaker) DoWithFallback(req func() error, _ func(err error) error) error {
|
||||
return req()
|
||||
}
|
||||
|
||||
func (b noOpBreaker) DoWithFallbackAcceptable(req func() error, fallback func(err error) error,
|
||||
acceptable Acceptable) error {
|
||||
func (b noOpBreaker) DoWithFallbackAcceptable(req func() error, _ func(err error) error,
|
||||
_ Acceptable) error {
|
||||
return req()
|
||||
}
|
||||
|
||||
@@ -38,5 +38,5 @@ type nopPromise struct{}
|
||||
func (p nopPromise) Accept() {
|
||||
}
|
||||
|
||||
func (p nopPromise) Reject(reason string) {
|
||||
func (p nopPromise) Reject(_ string) {
|
||||
}
|
||||
|
||||
@@ -32,9 +32,11 @@ func NewECBEncrypter(b cipher.Block) cipher.BlockMode {
|
||||
return (*ecbEncrypter)(newECB(b))
|
||||
}
|
||||
|
||||
// BlockSize returns the mode's block size.
|
||||
func (x *ecbEncrypter) BlockSize() int { return x.blockSize }
|
||||
|
||||
// why we don't return error is because cipher.BlockMode doesn't allow this
|
||||
// CryptBlocks encrypts a number of blocks. The length of src must be a multiple of
|
||||
// the block size. Dst and src must overlap entirely or not at all.
|
||||
func (x *ecbEncrypter) CryptBlocks(dst, src []byte) {
|
||||
if len(src)%x.blockSize != 0 {
|
||||
logx.Error("crypto/cipher: input not full blocks")
|
||||
@@ -59,11 +61,13 @@ func NewECBDecrypter(b cipher.Block) cipher.BlockMode {
|
||||
return (*ecbDecrypter)(newECB(b))
|
||||
}
|
||||
|
||||
// BlockSize returns the mode's block size.
|
||||
func (x *ecbDecrypter) BlockSize() int {
|
||||
return x.blockSize
|
||||
}
|
||||
|
||||
// why we don't return error is because cipher.BlockMode doesn't allow this
|
||||
// CryptBlocks decrypts a number of blocks. The length of src must be a multiple of
|
||||
// the block size. Dst and src must overlap entirely or not at all.
|
||||
func (x *ecbDecrypter) CryptBlocks(dst, src []byte) {
|
||||
if len(src)%x.blockSize != 0 {
|
||||
logx.Error("crypto/cipher: input not full blocks")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package codec
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
|
||||
@@ -10,7 +11,8 @@ import (
|
||||
func TestAesEcb(t *testing.T) {
|
||||
var (
|
||||
key = []byte("q4t7w!z%C*F-JaNdRgUjXn2r5u8x/A?D")
|
||||
val = []byte("hello")
|
||||
val = []byte("helloworld")
|
||||
valLong = []byte("helloworldlong..")
|
||||
badKey1 = []byte("aaaaaaaaa")
|
||||
// more than 32 chars
|
||||
badKey2 = []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
||||
@@ -31,6 +33,39 @@ func TestAesEcb(t *testing.T) {
|
||||
src, err := EcbDecrypt(key, dst)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, val, src)
|
||||
block, err := aes.NewCipher(key)
|
||||
assert.NoError(t, err)
|
||||
encrypter := NewECBEncrypter(block)
|
||||
assert.Equal(t, 16, encrypter.BlockSize())
|
||||
decrypter := NewECBDecrypter(block)
|
||||
assert.Equal(t, 16, decrypter.BlockSize())
|
||||
|
||||
dst = make([]byte, 8)
|
||||
encrypter.CryptBlocks(dst, val)
|
||||
for _, b := range dst {
|
||||
assert.Equal(t, byte(0), b)
|
||||
}
|
||||
|
||||
dst = make([]byte, 8)
|
||||
encrypter.CryptBlocks(dst, valLong)
|
||||
for _, b := range dst {
|
||||
assert.Equal(t, byte(0), b)
|
||||
}
|
||||
|
||||
dst = make([]byte, 8)
|
||||
decrypter.CryptBlocks(dst, val)
|
||||
for _, b := range dst {
|
||||
assert.Equal(t, byte(0), b)
|
||||
}
|
||||
|
||||
dst = make([]byte, 8)
|
||||
decrypter.CryptBlocks(dst, valLong)
|
||||
for _, b := range dst {
|
||||
assert.Equal(t, byte(0), b)
|
||||
}
|
||||
|
||||
_, err = EcbEncryptBase64("cTR0N3dDKkYtSmFOZFJnVWpYbjJyNXU4eC9BP0QK", "aGVsbG93b3JsZGxvbmcuLgo=")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestAesEcbBase64(t *testing.T) {
|
||||
|
||||
@@ -80,3 +80,17 @@ func TestKeyBytes(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, len(key.Bytes()) > 0)
|
||||
}
|
||||
|
||||
func TestDHOnErrors(t *testing.T) {
|
||||
key, err := GenerateKey()
|
||||
assert.Nil(t, err)
|
||||
assert.NotEmpty(t, key.Bytes())
|
||||
_, err = ComputeKey(key.PubKey, key.PriKey)
|
||||
assert.NoError(t, err)
|
||||
_, err = ComputeKey(nil, key.PriKey)
|
||||
assert.Error(t, err)
|
||||
_, err = ComputeKey(key.PubKey, nil)
|
||||
assert.Error(t, err)
|
||||
|
||||
assert.NotNil(t, NewPublicKey([]byte("")))
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import "sync"
|
||||
type Ring struct {
|
||||
elements []interface{}
|
||||
index int
|
||||
lock sync.Mutex
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// NewRing returns a Ring object with the given size n.
|
||||
@@ -31,8 +31,8 @@ func (r *Ring) Add(v interface{}) {
|
||||
|
||||
// Take takes all items from r.
|
||||
func (r *Ring) Take() []interface{} {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
var size int
|
||||
var start int
|
||||
|
||||
@@ -29,7 +29,7 @@ func NewSet() *Set {
|
||||
}
|
||||
}
|
||||
|
||||
// NewUnmanagedSet returns a unmanaged Set, which can put values with different types.
|
||||
// NewUnmanagedSet returns an unmanaged Set, which can put values with different types.
|
||||
func NewUnmanagedSet() *Set {
|
||||
return &Set{
|
||||
data: make(map[interface{}]lang.PlaceholderType),
|
||||
|
||||
@@ -69,10 +69,11 @@ func NewTimingWheel(interval time.Duration, numSlots int, execute Execute) (*Tim
|
||||
interval, numSlots, execute)
|
||||
}
|
||||
|
||||
return newTimingWheelWithClock(interval, numSlots, execute, timex.NewTicker(interval))
|
||||
return NewTimingWheelWithTicker(interval, numSlots, execute, timex.NewTicker(interval))
|
||||
}
|
||||
|
||||
func newTimingWheelWithClock(interval time.Duration, numSlots int, execute Execute,
|
||||
// NewTimingWheelWithTicker returns a TimingWheel with the given ticker.
|
||||
func NewTimingWheelWithTicker(interval time.Duration, numSlots int, execute Execute,
|
||||
ticker timex.Ticker) (*TimingWheel, error) {
|
||||
tw := &TimingWheel{
|
||||
interval: interval,
|
||||
|
||||
@@ -26,7 +26,7 @@ func TestNewTimingWheel(t *testing.T) {
|
||||
|
||||
func TestTimingWheel_Drain(t *testing.T) {
|
||||
ticker := timex.NewFakeTicker()
|
||||
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {
|
||||
tw, _ := NewTimingWheelWithTicker(testStep, 10, func(k, v interface{}) {
|
||||
}, ticker)
|
||||
tw.SetTimer("first", 3, testStep*4)
|
||||
tw.SetTimer("second", 5, testStep*7)
|
||||
@@ -62,7 +62,7 @@ func TestTimingWheel_Drain(t *testing.T) {
|
||||
func TestTimingWheel_SetTimerSoon(t *testing.T) {
|
||||
run := syncx.NewAtomicBool()
|
||||
ticker := timex.NewFakeTicker()
|
||||
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {
|
||||
tw, _ := NewTimingWheelWithTicker(testStep, 10, func(k, v interface{}) {
|
||||
assert.True(t, run.CompareAndSwap(false, true))
|
||||
assert.Equal(t, "any", k)
|
||||
assert.Equal(t, 3, v.(int))
|
||||
@@ -78,7 +78,7 @@ func TestTimingWheel_SetTimerSoon(t *testing.T) {
|
||||
func TestTimingWheel_SetTimerTwice(t *testing.T) {
|
||||
run := syncx.NewAtomicBool()
|
||||
ticker := timex.NewFakeTicker()
|
||||
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {
|
||||
tw, _ := NewTimingWheelWithTicker(testStep, 10, func(k, v interface{}) {
|
||||
assert.True(t, run.CompareAndSwap(false, true))
|
||||
assert.Equal(t, "any", k)
|
||||
assert.Equal(t, 5, v.(int))
|
||||
@@ -96,7 +96,7 @@ func TestTimingWheel_SetTimerTwice(t *testing.T) {
|
||||
|
||||
func TestTimingWheel_SetTimerWrongDelay(t *testing.T) {
|
||||
ticker := timex.NewFakeTicker()
|
||||
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {}, ticker)
|
||||
tw, _ := NewTimingWheelWithTicker(testStep, 10, func(k, v interface{}) {}, ticker)
|
||||
defer tw.Stop()
|
||||
assert.NotPanics(t, func() {
|
||||
tw.SetTimer("any", 3, -testStep)
|
||||
@@ -105,7 +105,7 @@ func TestTimingWheel_SetTimerWrongDelay(t *testing.T) {
|
||||
|
||||
func TestTimingWheel_SetTimerAfterClose(t *testing.T) {
|
||||
ticker := timex.NewFakeTicker()
|
||||
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {}, ticker)
|
||||
tw, _ := NewTimingWheelWithTicker(testStep, 10, func(k, v interface{}) {}, ticker)
|
||||
tw.Stop()
|
||||
assert.Equal(t, ErrClosed, tw.SetTimer("any", 3, testStep))
|
||||
}
|
||||
@@ -113,7 +113,7 @@ func TestTimingWheel_SetTimerAfterClose(t *testing.T) {
|
||||
func TestTimingWheel_MoveTimer(t *testing.T) {
|
||||
run := syncx.NewAtomicBool()
|
||||
ticker := timex.NewFakeTicker()
|
||||
tw, _ := newTimingWheelWithClock(testStep, 3, func(k, v interface{}) {
|
||||
tw, _ := NewTimingWheelWithTicker(testStep, 3, func(k, v interface{}) {
|
||||
assert.True(t, run.CompareAndSwap(false, true))
|
||||
assert.Equal(t, "any", k)
|
||||
assert.Equal(t, 3, v.(int))
|
||||
@@ -139,7 +139,7 @@ func TestTimingWheel_MoveTimer(t *testing.T) {
|
||||
func TestTimingWheel_MoveTimerSoon(t *testing.T) {
|
||||
run := syncx.NewAtomicBool()
|
||||
ticker := timex.NewFakeTicker()
|
||||
tw, _ := newTimingWheelWithClock(testStep, 3, func(k, v interface{}) {
|
||||
tw, _ := NewTimingWheelWithTicker(testStep, 3, func(k, v interface{}) {
|
||||
assert.True(t, run.CompareAndSwap(false, true))
|
||||
assert.Equal(t, "any", k)
|
||||
assert.Equal(t, 3, v.(int))
|
||||
@@ -155,7 +155,7 @@ func TestTimingWheel_MoveTimerSoon(t *testing.T) {
|
||||
func TestTimingWheel_MoveTimerEarlier(t *testing.T) {
|
||||
run := syncx.NewAtomicBool()
|
||||
ticker := timex.NewFakeTicker()
|
||||
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {
|
||||
tw, _ := NewTimingWheelWithTicker(testStep, 10, func(k, v interface{}) {
|
||||
assert.True(t, run.CompareAndSwap(false, true))
|
||||
assert.Equal(t, "any", k)
|
||||
assert.Equal(t, 3, v.(int))
|
||||
@@ -173,7 +173,7 @@ func TestTimingWheel_MoveTimerEarlier(t *testing.T) {
|
||||
|
||||
func TestTimingWheel_RemoveTimer(t *testing.T) {
|
||||
ticker := timex.NewFakeTicker()
|
||||
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {}, ticker)
|
||||
tw, _ := NewTimingWheelWithTicker(testStep, 10, func(k, v interface{}) {}, ticker)
|
||||
tw.SetTimer("any", 3, testStep)
|
||||
assert.NotPanics(t, func() {
|
||||
tw.RemoveTimer("any")
|
||||
@@ -236,7 +236,7 @@ func TestTimingWheel_SetTimer(t *testing.T) {
|
||||
}
|
||||
var actual int32
|
||||
done := make(chan lang.PlaceholderType)
|
||||
tw, err := newTimingWheelWithClock(testStep, test.slots, func(key, value interface{}) {
|
||||
tw, err := NewTimingWheelWithTicker(testStep, test.slots, func(key, value interface{}) {
|
||||
assert.Equal(t, 1, key.(int))
|
||||
assert.Equal(t, 2, value.(int))
|
||||
actual = atomic.LoadInt32(&count)
|
||||
@@ -317,7 +317,7 @@ func TestTimingWheel_SetAndMoveThenStart(t *testing.T) {
|
||||
}
|
||||
var actual int32
|
||||
done := make(chan lang.PlaceholderType)
|
||||
tw, err := newTimingWheelWithClock(testStep, test.slots, func(key, value interface{}) {
|
||||
tw, err := NewTimingWheelWithTicker(testStep, test.slots, func(key, value interface{}) {
|
||||
actual = atomic.LoadInt32(&count)
|
||||
close(done)
|
||||
}, ticker)
|
||||
@@ -405,7 +405,7 @@ func TestTimingWheel_SetAndMoveTwice(t *testing.T) {
|
||||
}
|
||||
var actual int32
|
||||
done := make(chan lang.PlaceholderType)
|
||||
tw, err := newTimingWheelWithClock(testStep, test.slots, func(key, value interface{}) {
|
||||
tw, err := NewTimingWheelWithTicker(testStep, test.slots, func(key, value interface{}) {
|
||||
actual = atomic.LoadInt32(&count)
|
||||
close(done)
|
||||
}, ticker)
|
||||
@@ -486,7 +486,7 @@ func TestTimingWheel_ElapsedAndSet(t *testing.T) {
|
||||
}
|
||||
var actual int32
|
||||
done := make(chan lang.PlaceholderType)
|
||||
tw, err := newTimingWheelWithClock(testStep, test.slots, func(key, value interface{}) {
|
||||
tw, err := NewTimingWheelWithTicker(testStep, test.slots, func(key, value interface{}) {
|
||||
actual = atomic.LoadInt32(&count)
|
||||
close(done)
|
||||
}, ticker)
|
||||
@@ -577,7 +577,7 @@ func TestTimingWheel_ElapsedAndSetThenMove(t *testing.T) {
|
||||
}
|
||||
var actual int32
|
||||
done := make(chan lang.PlaceholderType)
|
||||
tw, err := newTimingWheelWithClock(testStep, test.slots, func(key, value interface{}) {
|
||||
tw, err := NewTimingWheelWithTicker(testStep, test.slots, func(key, value interface{}) {
|
||||
actual = atomic.LoadInt32(&count)
|
||||
close(done)
|
||||
}, ticker)
|
||||
@@ -612,7 +612,7 @@ func TestMoveAndRemoveTask(t *testing.T) {
|
||||
}
|
||||
}
|
||||
var keys []int
|
||||
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {
|
||||
tw, _ := NewTimingWheelWithTicker(testStep, 10, func(k, v interface{}) {
|
||||
assert.Equal(t, "any", k)
|
||||
assert.Equal(t, 3, v.(int))
|
||||
keys = append(keys, v.(int))
|
||||
|
||||
@@ -5,9 +5,12 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/jsonx"
|
||||
"github.com/zeromicro/go-zero/core/mapping"
|
||||
"github.com/zeromicro/go-zero/internal/encoding"
|
||||
)
|
||||
|
||||
var loaders = map[string]func([]byte, interface{}) error{
|
||||
@@ -17,6 +20,12 @@ var loaders = map[string]func([]byte, interface{}) error{
|
||||
".yml": LoadFromYamlBytes,
|
||||
}
|
||||
|
||||
type fieldInfo struct {
|
||||
name string
|
||||
kind reflect.Kind
|
||||
children map[string]fieldInfo
|
||||
}
|
||||
|
||||
// Load loads config into v from file, .json, .yaml and .yml are acceptable.
|
||||
func Load(file string, v interface{}, opts ...Option) error {
|
||||
content, err := os.ReadFile(file)
|
||||
@@ -49,7 +58,15 @@ func LoadConfig(file string, v interface{}, opts ...Option) error {
|
||||
|
||||
// LoadFromJsonBytes loads config into v from content json bytes.
|
||||
func LoadFromJsonBytes(content []byte, v interface{}) error {
|
||||
return mapping.UnmarshalJsonBytes(content, v)
|
||||
var m map[string]interface{}
|
||||
if err := jsonx.Unmarshal(content, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
finfo := buildFieldsInfo(reflect.TypeOf(v))
|
||||
lowerCaseKeyMap := toLowerCaseKeyMap(m, finfo)
|
||||
|
||||
return mapping.UnmarshalJsonMap(lowerCaseKeyMap, v, mapping.WithCanonicalKeyFunc(toLowerCase))
|
||||
}
|
||||
|
||||
// LoadConfigFromJsonBytes loads config into v from content json bytes.
|
||||
@@ -60,12 +77,22 @@ func LoadConfigFromJsonBytes(content []byte, v interface{}) error {
|
||||
|
||||
// LoadFromTomlBytes loads config into v from content toml bytes.
|
||||
func LoadFromTomlBytes(content []byte, v interface{}) error {
|
||||
return mapping.UnmarshalTomlBytes(content, v)
|
||||
b, err := encoding.TomlToJson(content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return LoadFromJsonBytes(b, v)
|
||||
}
|
||||
|
||||
// LoadFromYamlBytes loads config into v from content yaml bytes.
|
||||
func LoadFromYamlBytes(content []byte, v interface{}) error {
|
||||
return mapping.UnmarshalYamlBytes(content, v)
|
||||
b, err := encoding.YamlToJson(content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return LoadFromJsonBytes(b, v)
|
||||
}
|
||||
|
||||
// LoadConfigFromYamlBytes loads config into v from content yaml bytes.
|
||||
@@ -80,3 +107,101 @@ func MustLoad(path string, v interface{}, opts ...Option) {
|
||||
log.Fatalf("error: config file %s, %s", path, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func buildFieldsInfo(tp reflect.Type) map[string]fieldInfo {
|
||||
tp = mapping.Deref(tp)
|
||||
|
||||
switch tp.Kind() {
|
||||
case reflect.Struct:
|
||||
return buildStructFieldsInfo(tp)
|
||||
case reflect.Array, reflect.Slice:
|
||||
return buildFieldsInfo(mapping.Deref(tp.Elem()))
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func buildStructFieldsInfo(tp reflect.Type) map[string]fieldInfo {
|
||||
info := make(map[string]fieldInfo)
|
||||
|
||||
for i := 0; i < tp.NumField(); i++ {
|
||||
field := tp.Field(i)
|
||||
name := field.Name
|
||||
lowerCaseName := toLowerCase(name)
|
||||
ft := mapping.Deref(field.Type)
|
||||
|
||||
// flatten anonymous fields
|
||||
if field.Anonymous {
|
||||
if ft.Kind() == reflect.Struct {
|
||||
fields := buildFieldsInfo(ft)
|
||||
for k, v := range fields {
|
||||
info[k] = v
|
||||
}
|
||||
} else {
|
||||
info[lowerCaseName] = fieldInfo{
|
||||
name: name,
|
||||
kind: ft.Kind(),
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
var fields map[string]fieldInfo
|
||||
switch ft.Kind() {
|
||||
case reflect.Struct:
|
||||
fields = buildFieldsInfo(ft)
|
||||
case reflect.Array, reflect.Slice:
|
||||
fields = buildFieldsInfo(ft.Elem())
|
||||
case reflect.Map:
|
||||
fields = buildFieldsInfo(ft.Elem())
|
||||
}
|
||||
|
||||
info[lowerCaseName] = fieldInfo{
|
||||
name: name,
|
||||
kind: ft.Kind(),
|
||||
children: fields,
|
||||
}
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
func toLowerCase(s string) string {
|
||||
return strings.ToLower(s)
|
||||
}
|
||||
|
||||
func toLowerCaseInterface(v interface{}, info map[string]fieldInfo) interface{} {
|
||||
switch vv := v.(type) {
|
||||
case map[string]interface{}:
|
||||
return toLowerCaseKeyMap(vv, info)
|
||||
case []interface{}:
|
||||
var arr []interface{}
|
||||
for _, vvv := range vv {
|
||||
arr = append(arr, toLowerCaseInterface(vvv, info))
|
||||
}
|
||||
return arr
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
func toLowerCaseKeyMap(m map[string]interface{}, info map[string]fieldInfo) map[string]interface{} {
|
||||
res := make(map[string]interface{})
|
||||
|
||||
for k, v := range m {
|
||||
ti, ok := info[k]
|
||||
if ok {
|
||||
res[k] = toLowerCaseInterface(v, ti.children)
|
||||
continue
|
||||
}
|
||||
|
||||
lk := toLowerCase(k)
|
||||
if ti, ok = info[lk]; ok {
|
||||
res[lk] = toLowerCaseInterface(v, ti.children)
|
||||
} else {
|
||||
res[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -56,6 +56,22 @@ func TestConfigJson(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadFromJsonBytesArray(t *testing.T) {
|
||||
input := []byte(`{"users": [{"name": "foo"}, {"Name": "bar"}]}`)
|
||||
var val struct {
|
||||
Users []struct {
|
||||
Name string
|
||||
}
|
||||
}
|
||||
|
||||
assert.NoError(t, LoadFromJsonBytes(input, &val))
|
||||
var expect []string
|
||||
for _, user := range val.Users {
|
||||
expect = append(expect, user.Name)
|
||||
}
|
||||
assert.EqualValues(t, []string{"foo", "bar"}, expect)
|
||||
}
|
||||
|
||||
func TestConfigToml(t *testing.T) {
|
||||
text := `a = "foo"
|
||||
b = 1
|
||||
@@ -81,6 +97,89 @@ d = "abcd!@#$112"
|
||||
assert.Equal(t, "abcd!@#$112", val.D)
|
||||
}
|
||||
|
||||
func TestConfigOptional(t *testing.T) {
|
||||
text := `a = "foo"
|
||||
b = 1
|
||||
c = "FOO"
|
||||
d = "abcd"
|
||||
`
|
||||
tmpfile, err := createTempFile(".toml", text)
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(tmpfile)
|
||||
|
||||
var val struct {
|
||||
A string `json:"a"`
|
||||
B int `json:"b,optional"`
|
||||
C string `json:"c,optional=B"`
|
||||
D string `json:"d,optional=b"`
|
||||
}
|
||||
if assert.NoError(t, Load(tmpfile, &val)) {
|
||||
assert.Equal(t, "foo", val.A)
|
||||
assert.Equal(t, 1, val.B)
|
||||
assert.Equal(t, "FOO", val.C)
|
||||
assert.Equal(t, "abcd", val.D)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigJsonCanonical(t *testing.T) {
|
||||
text := []byte(`{"a": "foo", "B": "bar"}`)
|
||||
|
||||
var val1 struct {
|
||||
A string `json:"a"`
|
||||
B string `json:"b"`
|
||||
}
|
||||
var val2 struct {
|
||||
A string
|
||||
B string
|
||||
}
|
||||
assert.NoError(t, LoadFromJsonBytes(text, &val1))
|
||||
assert.Equal(t, "foo", val1.A)
|
||||
assert.Equal(t, "bar", val1.B)
|
||||
assert.NoError(t, LoadFromJsonBytes(text, &val2))
|
||||
assert.Equal(t, "foo", val2.A)
|
||||
assert.Equal(t, "bar", val2.B)
|
||||
}
|
||||
|
||||
func TestConfigTomlCanonical(t *testing.T) {
|
||||
text := []byte(`a = "foo"
|
||||
B = "bar"`)
|
||||
|
||||
var val1 struct {
|
||||
A string `json:"a"`
|
||||
B string `json:"b"`
|
||||
}
|
||||
var val2 struct {
|
||||
A string
|
||||
B string
|
||||
}
|
||||
assert.NoError(t, LoadFromTomlBytes(text, &val1))
|
||||
assert.Equal(t, "foo", val1.A)
|
||||
assert.Equal(t, "bar", val1.B)
|
||||
assert.NoError(t, LoadFromTomlBytes(text, &val2))
|
||||
assert.Equal(t, "foo", val2.A)
|
||||
assert.Equal(t, "bar", val2.B)
|
||||
}
|
||||
|
||||
func TestConfigYamlCanonical(t *testing.T) {
|
||||
text := []byte(`a: foo
|
||||
B: bar`)
|
||||
|
||||
var val1 struct {
|
||||
A string `json:"a"`
|
||||
B string `json:"b"`
|
||||
}
|
||||
var val2 struct {
|
||||
A string
|
||||
B string
|
||||
}
|
||||
assert.NoError(t, LoadFromYamlBytes(text, &val1))
|
||||
assert.Equal(t, "foo", val1.A)
|
||||
assert.Equal(t, "bar", val1.B)
|
||||
assert.NoError(t, LoadFromYamlBytes(text, &val2))
|
||||
assert.Equal(t, "foo", val2.A)
|
||||
assert.Equal(t, "bar", val2.B)
|
||||
}
|
||||
|
||||
func TestConfigTomlEnv(t *testing.T) {
|
||||
text := `a = "foo"
|
||||
b = 1
|
||||
@@ -143,6 +242,214 @@ func TestConfigJsonEnv(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestToCamelCase(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
input: "",
|
||||
expect: "",
|
||||
},
|
||||
{
|
||||
input: "A",
|
||||
expect: "a",
|
||||
},
|
||||
{
|
||||
input: "a",
|
||||
expect: "a",
|
||||
},
|
||||
{
|
||||
input: "hello_world",
|
||||
expect: "hello_world",
|
||||
},
|
||||
{
|
||||
input: "Hello_world",
|
||||
expect: "hello_world",
|
||||
},
|
||||
{
|
||||
input: "hello_World",
|
||||
expect: "hello_world",
|
||||
},
|
||||
{
|
||||
input: "helloWorld",
|
||||
expect: "helloworld",
|
||||
},
|
||||
{
|
||||
input: "HelloWorld",
|
||||
expect: "helloworld",
|
||||
},
|
||||
{
|
||||
input: "hello World",
|
||||
expect: "hello world",
|
||||
},
|
||||
{
|
||||
input: "Hello World",
|
||||
expect: "hello world",
|
||||
},
|
||||
{
|
||||
input: "Hello World",
|
||||
expect: "hello world",
|
||||
},
|
||||
{
|
||||
input: "Hello World foo_bar",
|
||||
expect: "hello world foo_bar",
|
||||
},
|
||||
{
|
||||
input: "Hello World foo_Bar",
|
||||
expect: "hello world foo_bar",
|
||||
},
|
||||
{
|
||||
input: "Hello World Foo_bar",
|
||||
expect: "hello world foo_bar",
|
||||
},
|
||||
{
|
||||
input: "Hello World Foo_Bar",
|
||||
expect: "hello world foo_bar",
|
||||
},
|
||||
{
|
||||
input: "Hello.World Foo_Bar",
|
||||
expect: "hello.world foo_bar",
|
||||
},
|
||||
{
|
||||
input: "你好 World Foo_Bar",
|
||||
expect: "你好 world foo_bar",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.input, func(t *testing.T) {
|
||||
assert.Equal(t, test.expect, toLowerCase(test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadFromJsonBytesError(t *testing.T) {
|
||||
var val struct{}
|
||||
assert.Error(t, LoadFromJsonBytes([]byte(`hello`), &val))
|
||||
}
|
||||
|
||||
func TestLoadFromTomlBytesError(t *testing.T) {
|
||||
var val struct{}
|
||||
assert.Error(t, LoadFromTomlBytes([]byte(`hello`), &val))
|
||||
}
|
||||
|
||||
func TestLoadFromYamlBytesError(t *testing.T) {
|
||||
var val struct{}
|
||||
assert.Error(t, LoadFromYamlBytes([]byte(`':hello`), &val))
|
||||
}
|
||||
|
||||
func TestLoadFromYamlBytes(t *testing.T) {
|
||||
input := []byte(`layer1:
|
||||
layer2:
|
||||
layer3: foo`)
|
||||
var val struct {
|
||||
Layer1 struct {
|
||||
Layer2 struct {
|
||||
Layer3 string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert.NoError(t, LoadFromYamlBytes(input, &val))
|
||||
assert.Equal(t, "foo", val.Layer1.Layer2.Layer3)
|
||||
}
|
||||
|
||||
func TestLoadFromYamlBytesTerm(t *testing.T) {
|
||||
input := []byte(`layer1:
|
||||
layer2:
|
||||
tls_conf: foo`)
|
||||
var val struct {
|
||||
Layer1 struct {
|
||||
Layer2 struct {
|
||||
Layer3 string `json:"tls_conf"`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert.NoError(t, LoadFromYamlBytes(input, &val))
|
||||
assert.Equal(t, "foo", val.Layer1.Layer2.Layer3)
|
||||
}
|
||||
|
||||
func TestLoadFromYamlBytesLayers(t *testing.T) {
|
||||
input := []byte(`layer1:
|
||||
layer2:
|
||||
layer3: foo`)
|
||||
var val struct {
|
||||
Value string `json:"Layer1.Layer2.Layer3"`
|
||||
}
|
||||
|
||||
assert.NoError(t, LoadFromYamlBytes(input, &val))
|
||||
assert.Equal(t, "foo", val.Value)
|
||||
}
|
||||
|
||||
func TestUnmarshalJsonBytesMap(t *testing.T) {
|
||||
input := []byte(`{"foo":{"/mtproto.RPCTos": "bff.bff","bar":"baz"}}`)
|
||||
|
||||
var val struct {
|
||||
Foo map[string]string
|
||||
}
|
||||
|
||||
assert.NoError(t, LoadFromJsonBytes(input, &val))
|
||||
assert.Equal(t, "bff.bff", val.Foo["/mtproto.RPCTos"])
|
||||
assert.Equal(t, "baz", val.Foo["bar"])
|
||||
}
|
||||
|
||||
func TestUnmarshalJsonBytesMapWithSliceElements(t *testing.T) {
|
||||
input := []byte(`{"foo":{"/mtproto.RPCTos": ["bff.bff", "any"],"bar":["baz", "qux"]}}`)
|
||||
|
||||
var val struct {
|
||||
Foo map[string][]string
|
||||
}
|
||||
|
||||
assert.NoError(t, LoadFromJsonBytes(input, &val))
|
||||
assert.EqualValues(t, []string{"bff.bff", "any"}, val.Foo["/mtproto.RPCTos"])
|
||||
assert.EqualValues(t, []string{"baz", "qux"}, val.Foo["bar"])
|
||||
}
|
||||
|
||||
func TestUnmarshalJsonBytesMapWithSliceOfStructs(t *testing.T) {
|
||||
input := []byte(`{"foo":{
|
||||
"/mtproto.RPCTos": [{"bar": "any"}],
|
||||
"bar":[{"bar": "qux"}, {"bar": "ever"}]}}`)
|
||||
|
||||
var val struct {
|
||||
Foo map[string][]struct {
|
||||
Bar string
|
||||
}
|
||||
}
|
||||
|
||||
assert.NoError(t, LoadFromJsonBytes(input, &val))
|
||||
assert.Equal(t, 1, len(val.Foo["/mtproto.RPCTos"]))
|
||||
assert.Equal(t, "any", val.Foo["/mtproto.RPCTos"][0].Bar)
|
||||
assert.Equal(t, 2, len(val.Foo["bar"]))
|
||||
assert.Equal(t, "qux", val.Foo["bar"][0].Bar)
|
||||
assert.Equal(t, "ever", val.Foo["bar"][1].Bar)
|
||||
}
|
||||
|
||||
func TestUnmarshalJsonBytesWithAnonymousField(t *testing.T) {
|
||||
type (
|
||||
Int int
|
||||
|
||||
InnerConf struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
Conf struct {
|
||||
Int
|
||||
InnerConf
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
input = []byte(`{"Name": "hello", "int": 3}`)
|
||||
c Conf
|
||||
)
|
||||
assert.NoError(t, LoadFromJsonBytes(input, &c))
|
||||
assert.Equal(t, "hello", c.Name)
|
||||
assert.Equal(t, Int(3), c.Int)
|
||||
}
|
||||
|
||||
func createTempFile(ext, text string) (string, error) {
|
||||
tmpfile, err := os.CreateTemp(os.TempDir(), hash.Md5Hex([]byte(text))+"*"+ext)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package discov
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/discov/internal"
|
||||
"github.com/zeromicro/go-zero/core/lang"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
@@ -51,12 +53,7 @@ func NewPublisher(endpoints []string, key, value string, opts ...PubOption) *Pub
|
||||
|
||||
// KeepAlive keeps key:value alive.
|
||||
func (p *Publisher) KeepAlive() error {
|
||||
cli, err := internal.GetRegistry().GetConn(p.endpoints)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.lease, err = p.register(cli)
|
||||
cli, err := p.doRegister()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -83,6 +80,43 @@ func (p *Publisher) Stop() {
|
||||
p.quit.Close()
|
||||
}
|
||||
|
||||
func (p *Publisher) doKeepAlive() error {
|
||||
ticker := time.NewTicker(time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
select {
|
||||
case <-p.quit.Done():
|
||||
return nil
|
||||
default:
|
||||
cli, err := p.doRegister()
|
||||
if err != nil {
|
||||
logx.Errorf("etcd publisher doRegister: %s", err.Error())
|
||||
break
|
||||
}
|
||||
|
||||
if err := p.keepAliveAsync(cli); err != nil {
|
||||
logx.Errorf("etcd publisher keepAliveAsync: %s", err.Error())
|
||||
break
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Publisher) doRegister() (internal.EtcdClient, error) {
|
||||
cli, err := internal.GetRegistry().GetConn(p.endpoints)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.lease, err = p.register(cli)
|
||||
return cli, err
|
||||
}
|
||||
|
||||
func (p *Publisher) keepAliveAsync(cli internal.EtcdClient) error {
|
||||
ch, err := cli.KeepAlive(cli.Ctx(), p.lease)
|
||||
if err != nil {
|
||||
@@ -95,8 +129,8 @@ func (p *Publisher) keepAliveAsync(cli internal.EtcdClient) error {
|
||||
case _, ok := <-ch:
|
||||
if !ok {
|
||||
p.revoke(cli)
|
||||
if err := p.KeepAlive(); err != nil {
|
||||
logx.Errorf("KeepAlive: %s", err.Error())
|
||||
if err := p.doKeepAlive(); err != nil {
|
||||
logx.Errorf("etcd publisher KeepAlive: %s", err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -105,8 +139,8 @@ func (p *Publisher) keepAliveAsync(cli internal.EtcdClient) error {
|
||||
p.revoke(cli)
|
||||
select {
|
||||
case <-p.resumeChan:
|
||||
if err := p.KeepAlive(); err != nil {
|
||||
logx.Errorf("KeepAlive: %s", err.Error())
|
||||
if err := p.doKeepAlive(); err != nil {
|
||||
logx.Errorf("etcd publisher KeepAlive: %s", err.Error())
|
||||
}
|
||||
return
|
||||
case <-p.quit.Done():
|
||||
@@ -141,7 +175,7 @@ func (p *Publisher) register(client internal.EtcdClient) (clientv3.LeaseID, erro
|
||||
|
||||
func (p *Publisher) revoke(cli internal.EtcdClient) {
|
||||
if _, err := cli.Revoke(cli.Ctx(), p.lease); err != nil {
|
||||
logx.Error(err)
|
||||
logx.Errorf("etcd publisher revoke: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ type (
|
||||
// SubOption defines the method to customize a Subscriber.
|
||||
SubOption func(sub *Subscriber)
|
||||
|
||||
// A Subscriber is used to subscribe the given key on a etcd cluster.
|
||||
// A Subscriber is used to subscribe the given key on an etcd cluster.
|
||||
Subscriber struct {
|
||||
endpoints []string
|
||||
exclusive bool
|
||||
|
||||
15
core/fs/files_test.go
Normal file
15
core/fs/files_test.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCloseOnExec(t *testing.T) {
|
||||
file := os.NewFile(0, os.DevNull)
|
||||
assert.NotPanics(t, func() {
|
||||
CloseOnExec(file)
|
||||
})
|
||||
}
|
||||
@@ -328,7 +328,7 @@ func (s Stream) Parallel(fn ParallelFunc, opts ...Option) {
|
||||
}, opts...).Done()
|
||||
}
|
||||
|
||||
// Reduce is a utility method to let the caller deal with the underlying channel.
|
||||
// Reduce is an utility method to let the caller deal with the underlying channel.
|
||||
func (s Stream) Reduce(fn ReduceFunc) (interface{}, error) {
|
||||
return fn(s.source)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/lang"
|
||||
"github.com/zeromicro/go-zero/core/mapping"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -183,5 +182,5 @@ func innerRepr(node interface{}) string {
|
||||
}
|
||||
|
||||
func repr(node interface{}) string {
|
||||
return mapping.Repr(node)
|
||||
return lang.Repr(node)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ func (nopCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NopCloser returns a io.WriteCloser that does nothing on calling Close.
|
||||
// NopCloser returns an io.WriteCloser that does nothing on calling Close.
|
||||
func NopCloser(w io.Writer) io.WriteCloser {
|
||||
return nopCloser{w}
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package jsontype
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/globalsign/mgo/bson"
|
||||
)
|
||||
|
||||
// MilliTime represents time.Time that works better with mongodb.
|
||||
type MilliTime struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
// MarshalJSON marshals mt to json bytes.
|
||||
func (mt MilliTime) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(mt.Milli())
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals data into mt.
|
||||
func (mt *MilliTime) UnmarshalJSON(data []byte) error {
|
||||
var milli int64
|
||||
if err := json.Unmarshal(data, &milli); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mt.Time = time.Unix(0, milli*int64(time.Millisecond))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBSON returns BSON base on mt.
|
||||
func (mt MilliTime) GetBSON() (interface{}, error) {
|
||||
return mt.Time, nil
|
||||
}
|
||||
|
||||
// SetBSON sets raw into mt.
|
||||
func (mt *MilliTime) SetBSON(raw bson.Raw) error {
|
||||
return raw.Unmarshal(&mt.Time)
|
||||
}
|
||||
|
||||
// Milli returns milliseconds for mt.
|
||||
func (mt MilliTime) Milli() int64 {
|
||||
return mt.UnixNano() / int64(time.Millisecond)
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
package jsontype
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMilliTime_GetBSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tm time.Time
|
||||
}{
|
||||
{
|
||||
name: "now",
|
||||
tm: time.Now(),
|
||||
},
|
||||
{
|
||||
name: "future",
|
||||
tm: time.Now().Add(time.Hour),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got, err := MilliTime{test.tm}.GetBSON()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, test.tm, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMilliTime_MarshalJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tm time.Time
|
||||
}{
|
||||
{
|
||||
name: "now",
|
||||
tm: time.Now(),
|
||||
},
|
||||
{
|
||||
name: "future",
|
||||
tm: time.Now().Add(time.Hour),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
b, err := MilliTime{test.tm}.MarshalJSON()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, strconv.FormatInt(test.tm.UnixNano()/1e6, 10), string(b))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMilliTime_Milli(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tm time.Time
|
||||
}{
|
||||
{
|
||||
name: "now",
|
||||
tm: time.Now(),
|
||||
},
|
||||
{
|
||||
name: "future",
|
||||
tm: time.Now().Add(time.Hour),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
n := MilliTime{test.tm}.Milli()
|
||||
assert.Equal(t, test.tm.UnixNano()/1e6, n)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMilliTime_UnmarshalJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tm time.Time
|
||||
}{
|
||||
{
|
||||
name: "now",
|
||||
tm: time.Now(),
|
||||
},
|
||||
{
|
||||
name: "future",
|
||||
tm: time.Now().Add(time.Hour),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var mt MilliTime
|
||||
s := strconv.FormatInt(test.tm.UnixNano()/1e6, 10)
|
||||
err := mt.UnmarshalJSON([]byte(s))
|
||||
assert.Nil(t, err)
|
||||
s1, err := mt.MarshalJSON()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, s, string(s1))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalWithError(t *testing.T) {
|
||||
var mt MilliTime
|
||||
assert.NotNil(t, mt.UnmarshalJSON([]byte("hello")))
|
||||
}
|
||||
|
||||
func TestSetBSON(t *testing.T) {
|
||||
data, err := bson.Marshal(time.Now())
|
||||
assert.Nil(t, err)
|
||||
|
||||
var raw bson.Raw
|
||||
assert.Nil(t, bson.Unmarshal(data, &raw))
|
||||
|
||||
var mt MilliTime
|
||||
assert.Nil(t, mt.SetBSON(raw))
|
||||
assert.NotNil(t, mt.SetBSON(bson.Raw{}))
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
package lang
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Placeholder is a placeholder object that can be used globally.
|
||||
var Placeholder PlaceholderType
|
||||
|
||||
@@ -9,3 +15,64 @@ type (
|
||||
// PlaceholderType represents a placeholder type.
|
||||
PlaceholderType = struct{}
|
||||
)
|
||||
|
||||
// Repr returns the string representation of v.
|
||||
func Repr(v interface{}) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// if func (v *Type) String() string, we can't use Elem()
|
||||
switch vt := v.(type) {
|
||||
case fmt.Stringer:
|
||||
return vt.String()
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(v)
|
||||
for val.Kind() == reflect.Ptr && !val.IsNil() {
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
return reprOfValue(val)
|
||||
}
|
||||
|
||||
func reprOfValue(val reflect.Value) string {
|
||||
switch vt := val.Interface().(type) {
|
||||
case bool:
|
||||
return strconv.FormatBool(vt)
|
||||
case error:
|
||||
return vt.Error()
|
||||
case float32:
|
||||
return strconv.FormatFloat(float64(vt), 'f', -1, 32)
|
||||
case float64:
|
||||
return strconv.FormatFloat(vt, 'f', -1, 64)
|
||||
case fmt.Stringer:
|
||||
return vt.String()
|
||||
case int:
|
||||
return strconv.Itoa(vt)
|
||||
case int8:
|
||||
return strconv.Itoa(int(vt))
|
||||
case int16:
|
||||
return strconv.Itoa(int(vt))
|
||||
case int32:
|
||||
return strconv.Itoa(int(vt))
|
||||
case int64:
|
||||
return strconv.FormatInt(vt, 10)
|
||||
case string:
|
||||
return vt
|
||||
case uint:
|
||||
return strconv.FormatUint(uint64(vt), 10)
|
||||
case uint8:
|
||||
return strconv.FormatUint(uint64(vt), 10)
|
||||
case uint16:
|
||||
return strconv.FormatUint(uint64(vt), 10)
|
||||
case uint32:
|
||||
return strconv.FormatUint(uint64(vt), 10)
|
||||
case uint64:
|
||||
return strconv.FormatUint(vt, 10)
|
||||
case []byte:
|
||||
return string(vt)
|
||||
default:
|
||||
return fmt.Sprint(val.Interface())
|
||||
}
|
||||
}
|
||||
|
||||
156
core/lang/lang_test.go
Normal file
156
core/lang/lang_test.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package lang
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRepr(t *testing.T) {
|
||||
var (
|
||||
f32 float32 = 1.1
|
||||
f64 = 2.2
|
||||
i8 int8 = 1
|
||||
i16 int16 = 2
|
||||
i32 int32 = 3
|
||||
i64 int64 = 4
|
||||
u8 uint8 = 5
|
||||
u16 uint16 = 6
|
||||
u32 uint32 = 7
|
||||
u64 uint64 = 8
|
||||
)
|
||||
tests := []struct {
|
||||
v interface{}
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
nil,
|
||||
"",
|
||||
},
|
||||
{
|
||||
mockStringable{},
|
||||
"mocked",
|
||||
},
|
||||
{
|
||||
new(mockStringable),
|
||||
"mocked",
|
||||
},
|
||||
{
|
||||
newMockPtr(),
|
||||
"mockptr",
|
||||
},
|
||||
{
|
||||
&mockOpacity{
|
||||
val: 1,
|
||||
},
|
||||
"{1}",
|
||||
},
|
||||
{
|
||||
true,
|
||||
"true",
|
||||
},
|
||||
{
|
||||
false,
|
||||
"false",
|
||||
},
|
||||
{
|
||||
f32,
|
||||
"1.1",
|
||||
},
|
||||
{
|
||||
f64,
|
||||
"2.2",
|
||||
},
|
||||
{
|
||||
i8,
|
||||
"1",
|
||||
},
|
||||
{
|
||||
i16,
|
||||
"2",
|
||||
},
|
||||
{
|
||||
i32,
|
||||
"3",
|
||||
},
|
||||
{
|
||||
i64,
|
||||
"4",
|
||||
},
|
||||
{
|
||||
u8,
|
||||
"5",
|
||||
},
|
||||
{
|
||||
u16,
|
||||
"6",
|
||||
},
|
||||
{
|
||||
u32,
|
||||
"7",
|
||||
},
|
||||
{
|
||||
u64,
|
||||
"8",
|
||||
},
|
||||
{
|
||||
[]byte(`abcd`),
|
||||
"abcd",
|
||||
},
|
||||
{
|
||||
mockOpacity{val: 1},
|
||||
"{1}",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.expect, func(t *testing.T) {
|
||||
assert.Equal(t, test.expect, Repr(test.v))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReprOfValue(t *testing.T) {
|
||||
t.Run("error", func(t *testing.T) {
|
||||
assert.Equal(t, "error", reprOfValue(reflect.ValueOf(errors.New("error"))))
|
||||
})
|
||||
|
||||
t.Run("stringer", func(t *testing.T) {
|
||||
assert.Equal(t, "1.23", reprOfValue(reflect.ValueOf(json.Number("1.23"))))
|
||||
})
|
||||
|
||||
t.Run("int", func(t *testing.T) {
|
||||
assert.Equal(t, "1", reprOfValue(reflect.ValueOf(1)))
|
||||
})
|
||||
|
||||
t.Run("int", func(t *testing.T) {
|
||||
assert.Equal(t, "1", reprOfValue(reflect.ValueOf("1")))
|
||||
})
|
||||
|
||||
t.Run("int", func(t *testing.T) {
|
||||
assert.Equal(t, "1", reprOfValue(reflect.ValueOf(uint(1))))
|
||||
})
|
||||
}
|
||||
|
||||
type mockStringable struct{}
|
||||
|
||||
func (m mockStringable) String() string {
|
||||
return "mocked"
|
||||
}
|
||||
|
||||
type mockPtr struct{}
|
||||
|
||||
func newMockPtr() *mockPtr {
|
||||
return new(mockPtr)
|
||||
}
|
||||
|
||||
func (m *mockPtr) String() string {
|
||||
return "mockptr"
|
||||
}
|
||||
|
||||
type mockOpacity struct {
|
||||
val int
|
||||
}
|
||||
@@ -8,7 +8,9 @@ type LogConf struct {
|
||||
TimeFormat string `json:",optional"`
|
||||
Path string `json:",default=logs"`
|
||||
Level string `json:",default=info,options=[debug,info,error,severe]"`
|
||||
MaxContentLength uint32 `json:",optional"`
|
||||
Compress bool `json:",optional"`
|
||||
Stat bool `json:",default=true"`
|
||||
KeepDays int `json:",optional"`
|
||||
StackCooldownMillis int `json:",default=100"`
|
||||
// MaxBackups represents how many backup log files will be kept. 0 means all files will be kept forever.
|
||||
|
||||
@@ -31,7 +31,10 @@ func AddGlobalFields(fields ...LogField) {
|
||||
func ContextWithFields(ctx context.Context, fields ...LogField) context.Context {
|
||||
if val := ctx.Value(fieldsContextKey); val != nil {
|
||||
if arr, ok := val.([]LogField); ok {
|
||||
return context.WithValue(ctx, fieldsContextKey, append(arr, fields...))
|
||||
allFields := make([]LogField, 0, len(arr)+len(fields))
|
||||
allFields = append(allFields, arr...)
|
||||
allFields = append(allFields, fields...)
|
||||
return context.WithValue(ctx, fieldsContextKey, allFields)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
@@ -67,6 +68,22 @@ func TestWithFieldsAppend(t *testing.T) {
|
||||
}, fields)
|
||||
}
|
||||
|
||||
func TestWithFieldsAppendCopy(t *testing.T) {
|
||||
const count = 10
|
||||
ctx := context.Background()
|
||||
for i := 0; i < count; i++ {
|
||||
ctx = ContextWithFields(ctx, Field(strconv.Itoa(i), 1))
|
||||
}
|
||||
|
||||
af := Field("foo", 1)
|
||||
bf := Field("bar", 2)
|
||||
ctxa := ContextWithFields(ctx, af)
|
||||
ctxb := ContextWithFields(ctx, bf)
|
||||
|
||||
assert.EqualValues(t, af, ctxa.Value(fieldsContextKey).([]LogField)[count])
|
||||
assert.EqualValues(t, bf, ctxb.Value(fieldsContextKey).([]LogField)[count])
|
||||
}
|
||||
|
||||
func BenchmarkAtomicValue(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ var (
|
||||
timeFormat = "2006-01-02T15:04:05.000Z07:00"
|
||||
logLevel uint32
|
||||
encoding uint32 = jsonEncodingType
|
||||
// maxContentLength is used to truncate the log content, 0 for not truncating.
|
||||
maxContentLength uint32
|
||||
// use uint32 for atomic operations
|
||||
disableLog uint32
|
||||
disableStat uint32
|
||||
@@ -230,10 +232,16 @@ func SetUp(c LogConf) (err error) {
|
||||
setupOnce.Do(func() {
|
||||
setupLogLevel(c)
|
||||
|
||||
if !c.Stat {
|
||||
DisableStat()
|
||||
}
|
||||
|
||||
if len(c.TimeFormat) > 0 {
|
||||
timeFormat = c.TimeFormat
|
||||
}
|
||||
|
||||
atomic.StoreUint32(&maxContentLength, c.MaxContentLength)
|
||||
|
||||
switch c.Encoding {
|
||||
case plainEncoding:
|
||||
atomic.StoreUint32(&encoding, plainEncodingType)
|
||||
|
||||
@@ -529,9 +529,9 @@ func TestSetLevel(t *testing.T) {
|
||||
|
||||
func TestSetLevelTwiceWithMode(t *testing.T) {
|
||||
testModes := []string{
|
||||
"mode",
|
||||
"console",
|
||||
"volumn",
|
||||
"mode",
|
||||
}
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
@@ -791,9 +791,12 @@ func doTestStructedLogConsole(t *testing.T, w *mockWriter, write func(...interfa
|
||||
func testSetLevelTwiceWithMode(t *testing.T, mode string, w *mockWriter) {
|
||||
writer.Store(nil)
|
||||
SetUp(LogConf{
|
||||
Mode: mode,
|
||||
Level: "error",
|
||||
Path: "/dev/null",
|
||||
Mode: mode,
|
||||
Level: "debug",
|
||||
Path: "/dev/null",
|
||||
Encoding: plainEncoding,
|
||||
Stat: false,
|
||||
TimeFormat: time.RFC3339,
|
||||
})
|
||||
SetUp(LogConf{
|
||||
Mode: mode,
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/timex"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"github.com/zeromicro/go-zero/internal/trace"
|
||||
)
|
||||
|
||||
// WithCallerSkip returns a Logger with given caller skip.
|
||||
@@ -136,12 +136,12 @@ func (l *richLogger) buildFields(fields ...LogField) []LogField {
|
||||
return fields
|
||||
}
|
||||
|
||||
traceID := traceIdFromContext(l.ctx)
|
||||
traceID := trace.TraceIDFromContext(l.ctx)
|
||||
if len(traceID) > 0 {
|
||||
fields = append(fields, Field(traceKey, traceID))
|
||||
}
|
||||
|
||||
spanID := spanIdFromContext(l.ctx)
|
||||
spanID := trace.SpanIDFromContext(l.ctx)
|
||||
if len(spanID) > 0 {
|
||||
fields = append(fields, Field(spanKey, spanID))
|
||||
}
|
||||
@@ -179,21 +179,3 @@ func (l *richLogger) slow(v interface{}, fields ...LogField) {
|
||||
getWriter().Slow(v, l.buildFields(fields...)...)
|
||||
}
|
||||
}
|
||||
|
||||
func spanIdFromContext(ctx context.Context) string {
|
||||
spanCtx := trace.SpanContextFromContext(ctx)
|
||||
if spanCtx.HasSpanID() {
|
||||
return spanCtx.SpanID().String()
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func traceIdFromContext(ctx context.Context) string {
|
||||
spanCtx := trace.SpanContextFromContext(ctx)
|
||||
if spanCtx.HasTraceID() {
|
||||
return spanCtx.TraceID().String()
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -284,7 +284,7 @@ func (l *RotateLogger) getBackupFilename() string {
|
||||
func (l *RotateLogger) init() error {
|
||||
l.backup = l.rule.BackupFileName()
|
||||
|
||||
if _, err := os.Stat(l.filename); err != nil {
|
||||
if fileInfo, err := os.Stat(l.filename); err != nil {
|
||||
basePath := path.Dir(l.filename)
|
||||
if _, err = os.Stat(basePath); err != nil {
|
||||
if err = os.MkdirAll(basePath, defaultDirMode); err != nil {
|
||||
@@ -295,8 +295,11 @@ func (l *RotateLogger) init() error {
|
||||
if l.fp, err = os.Create(l.filename); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if l.fp, err = os.OpenFile(l.filename, os.O_APPEND|os.O_WRONLY, defaultFileMode); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if l.fp, err = os.OpenFile(l.filename, os.O_APPEND|os.O_WRONLY, defaultFileMode); err != nil {
|
||||
return err
|
||||
}
|
||||
l.currentSize = fileInfo.Size()
|
||||
}
|
||||
|
||||
fs.CloseOnExec(l.fp)
|
||||
|
||||
@@ -16,13 +16,13 @@ const (
|
||||
const (
|
||||
jsonEncodingType = iota
|
||||
plainEncodingType
|
||||
|
||||
plainEncoding = "plain"
|
||||
plainEncodingSep = '\t'
|
||||
sizeRotationRule = "size"
|
||||
)
|
||||
|
||||
const (
|
||||
plainEncoding = "plain"
|
||||
plainEncodingSep = '\t'
|
||||
sizeRotationRule = "size"
|
||||
|
||||
accessFilename = "access.log"
|
||||
errorFilename = "error.log"
|
||||
severeFilename = "severe.log"
|
||||
@@ -53,6 +53,7 @@ const (
|
||||
spanKey = "span"
|
||||
timestampKey = "@timestamp"
|
||||
traceKey = "trace"
|
||||
truncatedKey = "truncated"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -60,4 +61,6 @@ var (
|
||||
ErrLogPathNotSet = errors.New("log path must be set")
|
||||
// ErrLogServiceNameNotSet is an error that indicates that the service name is not set.
|
||||
ErrLogServiceNameNotSet = errors.New("log service name must be set")
|
||||
|
||||
truncatedField = Field(truncatedKey, true)
|
||||
)
|
||||
|
||||
@@ -253,11 +253,11 @@ func (n nopWriter) Stack(_ interface{}) {
|
||||
func (n nopWriter) Stat(_ interface{}, _ ...LogField) {
|
||||
}
|
||||
|
||||
func buildFields(fields ...LogField) []string {
|
||||
func buildPlainFields(fields ...LogField) []string {
|
||||
var items []string
|
||||
|
||||
for _, field := range fields {
|
||||
items = append(items, fmt.Sprintf("%s=%v", field.Key, field.Value))
|
||||
items = append(items, fmt.Sprintf("%s=%+v", field.Key, field.Value))
|
||||
}
|
||||
|
||||
return items
|
||||
@@ -269,15 +269,29 @@ func combineGlobalFields(fields []LogField) []LogField {
|
||||
return fields
|
||||
}
|
||||
|
||||
return append(globals.([]LogField), fields...)
|
||||
gf := globals.([]LogField)
|
||||
ret := make([]LogField, 0, len(gf)+len(fields))
|
||||
ret = append(ret, gf...)
|
||||
ret = append(ret, fields...)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func output(writer io.Writer, level string, val interface{}, fields ...LogField) {
|
||||
// only truncate string content, don't know how to truncate the values of other types.
|
||||
if v, ok := val.(string); ok {
|
||||
maxLen := atomic.LoadUint32(&maxContentLength)
|
||||
if maxLen > 0 && len(v) > int(maxLen) {
|
||||
val = v[:maxLen]
|
||||
fields = append(fields, truncatedField)
|
||||
}
|
||||
}
|
||||
|
||||
fields = combineGlobalFields(fields)
|
||||
|
||||
switch atomic.LoadUint32(&encoding) {
|
||||
case plainEncodingType:
|
||||
writePlainAny(writer, level, val, buildFields(fields...)...)
|
||||
writePlainAny(writer, level, val, buildPlainFields(fields...)...)
|
||||
default:
|
||||
entry := make(logEntry)
|
||||
for _, field := range fields {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -157,9 +158,40 @@ func TestWritePlainAny(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestLogWithLimitContentLength(t *testing.T) {
|
||||
maxLen := atomic.LoadUint32(&maxContentLength)
|
||||
atomic.StoreUint32(&maxContentLength, 10)
|
||||
|
||||
t.Cleanup(func() {
|
||||
atomic.StoreUint32(&maxContentLength, maxLen)
|
||||
})
|
||||
|
||||
t.Run("alert", func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
w := NewWriter(&buf)
|
||||
w.Info("1234567890")
|
||||
var v1 mockedEntry
|
||||
if err := json.Unmarshal(buf.Bytes(), &v1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, "1234567890", v1.Content)
|
||||
assert.False(t, v1.Truncated)
|
||||
|
||||
buf.Reset()
|
||||
var v2 mockedEntry
|
||||
w.Info("12345678901")
|
||||
if err := json.Unmarshal(buf.Bytes(), &v2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, "1234567890", v2.Content)
|
||||
assert.True(t, v2.Truncated)
|
||||
})
|
||||
}
|
||||
|
||||
type mockedEntry struct {
|
||||
Level string `json:"level"`
|
||||
Content string `json:"content"`
|
||||
Level string `json:"level"`
|
||||
Content string `json:"content"`
|
||||
Truncated bool `json:"truncated"`
|
||||
}
|
||||
|
||||
type easyToCloseWriter struct{}
|
||||
|
||||
@@ -8,10 +8,12 @@ type (
|
||||
// use context and OptionalDep option to determine the value of Optional
|
||||
// nothing to do with context.Context
|
||||
fieldOptionsWithContext struct {
|
||||
Inherit bool
|
||||
FromString bool
|
||||
Optional bool
|
||||
Options []string
|
||||
Default string
|
||||
EnvVar string
|
||||
Range *numberRange
|
||||
}
|
||||
|
||||
@@ -40,6 +42,10 @@ func (o *fieldOptionsWithContext) getDefault() (string, bool) {
|
||||
return o.Default, len(o.Default) > 0
|
||||
}
|
||||
|
||||
func (o *fieldOptionsWithContext) inherit() bool {
|
||||
return o != nil && o.Inherit
|
||||
}
|
||||
|
||||
func (o *fieldOptionsWithContext) optional() bool {
|
||||
return o != nil && o.Optional
|
||||
}
|
||||
@@ -101,5 +107,6 @@ func (o *fieldOptions) toOptionsWithContext(key string, m Valuer, fullName strin
|
||||
Optional: optional,
|
||||
Options: o.Options,
|
||||
Default: o.Default,
|
||||
EnvVar: o.EnvVar,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -11,22 +11,30 @@ const jsonTagKey = "json"
|
||||
var jsonUnmarshaler = NewUnmarshaler(jsonTagKey)
|
||||
|
||||
// UnmarshalJsonBytes unmarshals content into v.
|
||||
func UnmarshalJsonBytes(content []byte, v interface{}) error {
|
||||
return unmarshalJsonBytes(content, v, jsonUnmarshaler)
|
||||
func UnmarshalJsonBytes(content []byte, v interface{}, opts ...UnmarshalOption) error {
|
||||
return unmarshalJsonBytes(content, v, getJsonUnmarshaler(opts...))
|
||||
}
|
||||
|
||||
// UnmarshalJsonMap unmarshals content from m into v.
|
||||
func UnmarshalJsonMap(m map[string]interface{}, v interface{}) error {
|
||||
return jsonUnmarshaler.Unmarshal(m, v)
|
||||
func UnmarshalJsonMap(m map[string]interface{}, v interface{}, opts ...UnmarshalOption) error {
|
||||
return getJsonUnmarshaler(opts...).Unmarshal(m, v)
|
||||
}
|
||||
|
||||
// UnmarshalJsonReader unmarshals content from reader into v.
|
||||
func UnmarshalJsonReader(reader io.Reader, v interface{}) error {
|
||||
return unmarshalJsonReader(reader, v, jsonUnmarshaler)
|
||||
func UnmarshalJsonReader(reader io.Reader, v interface{}, opts ...UnmarshalOption) error {
|
||||
return unmarshalJsonReader(reader, v, getJsonUnmarshaler(opts...))
|
||||
}
|
||||
|
||||
func getJsonUnmarshaler(opts ...UnmarshalOption) *Unmarshaler {
|
||||
if len(opts) > 0 {
|
||||
return NewUnmarshaler(jsonTagKey, opts...)
|
||||
}
|
||||
|
||||
return jsonUnmarshaler
|
||||
}
|
||||
|
||||
func unmarshalJsonBytes(content []byte, v interface{}, unmarshaler *Unmarshaler) error {
|
||||
var m map[string]interface{}
|
||||
var m interface{}
|
||||
if err := jsonx.Unmarshal(content, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -35,7 +43,7 @@ func unmarshalJsonBytes(content []byte, v interface{}, unmarshaler *Unmarshaler)
|
||||
}
|
||||
|
||||
func unmarshalJsonReader(reader io.Reader, v interface{}, unmarshaler *Unmarshaler) error {
|
||||
var m map[string]interface{}
|
||||
var m interface{}
|
||||
if err := jsonx.UnmarshalFromReader(reader, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -856,8 +856,7 @@ func TestUnmarshalBytesError(t *testing.T) {
|
||||
}
|
||||
|
||||
err := UnmarshalJsonBytes([]byte(payload), &v)
|
||||
assert.NotNil(t, err)
|
||||
assert.True(t, strings.Contains(err.Error(), payload))
|
||||
assert.Equal(t, errTypeMismatch, err)
|
||||
}
|
||||
|
||||
func TestUnmarshalReaderError(t *testing.T) {
|
||||
@@ -867,9 +866,7 @@ func TestUnmarshalReaderError(t *testing.T) {
|
||||
Any string
|
||||
}
|
||||
|
||||
err := UnmarshalJsonReader(reader, &v)
|
||||
assert.NotNil(t, err)
|
||||
assert.True(t, strings.Contains(err.Error(), payload))
|
||||
assert.Equal(t, errTypeMismatch, UnmarshalJsonReader(reader, &v))
|
||||
}
|
||||
|
||||
func TestUnmarshalMap(t *testing.T) {
|
||||
@@ -900,7 +897,9 @@ func TestUnmarshalMap(t *testing.T) {
|
||||
Any string `json:",optional"`
|
||||
}
|
||||
|
||||
err := UnmarshalJsonMap(m, &v)
|
||||
err := UnmarshalJsonMap(m, &v, WithCanonicalKeyFunc(func(s string) string {
|
||||
return s
|
||||
}))
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, len(v.Any) == 0)
|
||||
})
|
||||
@@ -918,3 +917,26 @@ func TestUnmarshalMap(t *testing.T) {
|
||||
assert.Equal(t, "foo", v.Any)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshalJsonArray(t *testing.T) {
|
||||
var v []struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
}
|
||||
|
||||
body := `[{"name":"kevin", "age": 18}]`
|
||||
assert.NoError(t, UnmarshalJsonBytes([]byte(body), &v))
|
||||
assert.Equal(t, 1, len(v))
|
||||
assert.Equal(t, "kevin", v[0].Name)
|
||||
assert.Equal(t, 18, v[0].Age)
|
||||
}
|
||||
|
||||
func TestUnmarshalJsonBytesError(t *testing.T) {
|
||||
var v []struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
}
|
||||
|
||||
assert.Error(t, UnmarshalJsonBytes([]byte((``)), &v))
|
||||
assert.Error(t, UnmarshalJsonReader(strings.NewReader(``), &v))
|
||||
}
|
||||
|
||||
@@ -1,29 +1,27 @@
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"github.com/zeromicro/go-zero/internal/encoding"
|
||||
)
|
||||
|
||||
// UnmarshalTomlBytes unmarshals TOML bytes into the given v.
|
||||
func UnmarshalTomlBytes(content []byte, v interface{}) error {
|
||||
return UnmarshalTomlReader(bytes.NewReader(content), v)
|
||||
func UnmarshalTomlBytes(content []byte, v interface{}, opts ...UnmarshalOption) error {
|
||||
b, err := encoding.TomlToJson(content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return UnmarshalJsonBytes(b, v, opts...)
|
||||
}
|
||||
|
||||
// UnmarshalTomlReader unmarshals TOML from the given io.Reader into the given v.
|
||||
func UnmarshalTomlReader(r io.Reader, v interface{}) error {
|
||||
var val interface{}
|
||||
if err := toml.NewDecoder(r).Decode(&val); err != nil {
|
||||
func UnmarshalTomlReader(r io.Reader, v interface{}, opts ...UnmarshalOption) error {
|
||||
b, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := json.NewEncoder(&buf).Encode(val); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return UnmarshalJsonReader(&buf, v)
|
||||
return UnmarshalTomlBytes(b, v, opts...)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -18,7 +19,7 @@ d = "abcd!@#$112"
|
||||
C string `json:"c"`
|
||||
D string `json:"d"`
|
||||
}
|
||||
assert.Nil(t, UnmarshalTomlBytes([]byte(input), &val))
|
||||
assert.NoError(t, UnmarshalTomlReader(strings.NewReader(input), &val))
|
||||
assert.Equal(t, "foo", val.A)
|
||||
assert.Equal(t, 1, val.B)
|
||||
assert.Equal(t, "${FOO}", val.C)
|
||||
@@ -37,5 +38,12 @@ d = "abcd!@#$112"
|
||||
C string `json:"c"`
|
||||
D string `json:"d"`
|
||||
}
|
||||
assert.NotNil(t, UnmarshalTomlBytes([]byte(input), &val))
|
||||
assert.Error(t, UnmarshalTomlReader(strings.NewReader(input), &val))
|
||||
}
|
||||
|
||||
func TestUnmarshalTomlBadReader(t *testing.T) {
|
||||
var val struct {
|
||||
A string `json:"a"`
|
||||
}
|
||||
assert.Error(t, UnmarshalTomlReader(new(badReader), &val))
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -10,11 +10,14 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/lang"
|
||||
"github.com/zeromicro/go-zero/core/stringx"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultOption = "default"
|
||||
envOption = "env"
|
||||
inheritOption = "inherit"
|
||||
stringOption = "string"
|
||||
optionalOption = "optional"
|
||||
optionsOption = "options"
|
||||
@@ -53,7 +56,7 @@ type (
|
||||
|
||||
// Deref dereferences a type, if pointer type, returns its element type.
|
||||
func Deref(t reflect.Type) reflect.Type {
|
||||
if t.Kind() == reflect.Ptr {
|
||||
for t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
@@ -62,22 +65,17 @@ func Deref(t reflect.Type) reflect.Type {
|
||||
|
||||
// Repr returns the string representation of v.
|
||||
func Repr(v interface{}) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
return lang.Repr(v)
|
||||
}
|
||||
|
||||
// if func (v *Type) String() string, we can't use Elem()
|
||||
switch vt := v.(type) {
|
||||
case fmt.Stringer:
|
||||
return vt.String()
|
||||
}
|
||||
// SetValue sets target to value, pointers are processed automatically.
|
||||
func SetValue(tp reflect.Type, value, target reflect.Value) {
|
||||
value.Set(convertTypeOfPtr(tp, target))
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(v)
|
||||
if val.Kind() == reflect.Ptr && !val.IsNil() {
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
return reprOfValue(val)
|
||||
// SetMapIndexValue sets target to value at key position, pointers are processed automatically.
|
||||
func SetMapIndexValue(tp reflect.Type, value, key, target reflect.Value) {
|
||||
value.SetMapIndex(key, convertTypeOfPtr(tp, target))
|
||||
}
|
||||
|
||||
// ValidatePtr validates v if it's a valid pointer.
|
||||
@@ -91,10 +89,17 @@ func ValidatePtr(v *reflect.Value) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertType(kind reflect.Kind, str string) (interface{}, error) {
|
||||
func convertTypeFromString(kind reflect.Kind, str string) (interface{}, error) {
|
||||
switch kind {
|
||||
case reflect.Bool:
|
||||
return str == "1" || strings.ToLower(str) == "true", nil
|
||||
switch strings.ToLower(str) {
|
||||
case "1", "true":
|
||||
return true, nil
|
||||
case "0", "false":
|
||||
return false, nil
|
||||
default:
|
||||
return false, errTypeMismatch
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
intValue, err := strconv.ParseInt(str, 10, 64)
|
||||
if err != nil {
|
||||
@@ -123,6 +128,23 @@ func convertType(kind reflect.Kind, str string) (interface{}, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func convertTypeOfPtr(tp reflect.Type, target reflect.Value) reflect.Value {
|
||||
// keep the original value is a pointer
|
||||
if tp.Kind() == reflect.Ptr && target.CanAddr() {
|
||||
tp = tp.Elem()
|
||||
target = target.Addr()
|
||||
}
|
||||
|
||||
for tp.Kind() == reflect.Ptr {
|
||||
p := reflect.New(target.Type())
|
||||
p.Elem().Set(target)
|
||||
target = p
|
||||
tp = tp.Elem()
|
||||
}
|
||||
|
||||
return target
|
||||
}
|
||||
|
||||
func doParseKeyAndOptions(field reflect.StructField, value string) (string, *fieldOptions, error) {
|
||||
segments := parseSegments(value)
|
||||
key := strings.TrimSpace(segments[0])
|
||||
@@ -215,8 +237,8 @@ func isRightInclude(b byte) (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func maybeNewValue(field reflect.StructField, value reflect.Value) {
|
||||
if field.Type.Kind() == reflect.Ptr && value.IsNil() {
|
||||
func maybeNewValue(fieldType reflect.Type, value reflect.Value) {
|
||||
if fieldType.Kind() == reflect.Ptr && value.IsNil() {
|
||||
value.Set(reflect.New(value.Type().Elem()))
|
||||
}
|
||||
}
|
||||
@@ -335,6 +357,8 @@ func parseNumberRange(str string) (*numberRange, error) {
|
||||
|
||||
func parseOption(fieldOpts *fieldOptions, fieldName, option string) error {
|
||||
switch {
|
||||
case option == inheritOption:
|
||||
fieldOpts.Inherit = true
|
||||
case option == stringOption:
|
||||
fieldOpts.FromString = true
|
||||
case strings.HasPrefix(option, optionalOption):
|
||||
@@ -351,26 +375,33 @@ func parseOption(fieldOpts *fieldOptions, fieldName, option string) error {
|
||||
case option == optionalOption:
|
||||
fieldOpts.Optional = true
|
||||
case strings.HasPrefix(option, optionsOption):
|
||||
segs := strings.Split(option, equalToken)
|
||||
if len(segs) != 2 {
|
||||
return fmt.Errorf("field %s has wrong options", fieldName)
|
||||
val, err := parseProperty(fieldName, optionsOption, option)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fieldOpts.Options = parseOptions(segs[1])
|
||||
fieldOpts.Options = parseOptions(val)
|
||||
case strings.HasPrefix(option, defaultOption):
|
||||
segs := strings.Split(option, equalToken)
|
||||
if len(segs) != 2 {
|
||||
return fmt.Errorf("field %s has wrong default option", fieldName)
|
||||
val, err := parseProperty(fieldName, defaultOption, option)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fieldOpts.Default = strings.TrimSpace(segs[1])
|
||||
fieldOpts.Default = val
|
||||
case strings.HasPrefix(option, envOption):
|
||||
val, err := parseProperty(fieldName, envOption, option)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fieldOpts.EnvVar = val
|
||||
case strings.HasPrefix(option, rangeOption):
|
||||
segs := strings.Split(option, equalToken)
|
||||
if len(segs) != 2 {
|
||||
return fmt.Errorf("field %s has wrong range", fieldName)
|
||||
val, err := parseProperty(fieldName, rangeOption, option)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nr, err := parseNumberRange(segs[1])
|
||||
nr, err := parseNumberRange(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -395,6 +426,15 @@ func parseOptions(val string) []string {
|
||||
return strings.Split(val, optionSeparator)
|
||||
}
|
||||
|
||||
func parseProperty(field, tag, val string) (string, error) {
|
||||
segs := strings.Split(val, equalToken)
|
||||
if len(segs) != 2 {
|
||||
return "", fmt.Errorf("field %s has wrong %s", field, tag)
|
||||
}
|
||||
|
||||
return strings.TrimSpace(segs[1]), nil
|
||||
}
|
||||
|
||||
func parseSegments(val string) []string {
|
||||
var segments []string
|
||||
var escaped, grouped bool
|
||||
@@ -444,47 +484,6 @@ func parseSegments(val string) []string {
|
||||
return segments
|
||||
}
|
||||
|
||||
func reprOfValue(val reflect.Value) string {
|
||||
switch vt := val.Interface().(type) {
|
||||
case bool:
|
||||
return strconv.FormatBool(vt)
|
||||
case error:
|
||||
return vt.Error()
|
||||
case float32:
|
||||
return strconv.FormatFloat(float64(vt), 'f', -1, 32)
|
||||
case float64:
|
||||
return strconv.FormatFloat(vt, 'f', -1, 64)
|
||||
case fmt.Stringer:
|
||||
return vt.String()
|
||||
case int:
|
||||
return strconv.Itoa(vt)
|
||||
case int8:
|
||||
return strconv.Itoa(int(vt))
|
||||
case int16:
|
||||
return strconv.Itoa(int(vt))
|
||||
case int32:
|
||||
return strconv.Itoa(int(vt))
|
||||
case int64:
|
||||
return strconv.FormatInt(vt, 10)
|
||||
case string:
|
||||
return vt
|
||||
case uint:
|
||||
return strconv.FormatUint(uint64(vt), 10)
|
||||
case uint8:
|
||||
return strconv.FormatUint(uint64(vt), 10)
|
||||
case uint16:
|
||||
return strconv.FormatUint(uint64(vt), 10)
|
||||
case uint32:
|
||||
return strconv.FormatUint(uint64(vt), 10)
|
||||
case uint64:
|
||||
return strconv.FormatUint(vt, 10)
|
||||
case []byte:
|
||||
return string(vt)
|
||||
default:
|
||||
return fmt.Sprint(val.Interface())
|
||||
}
|
||||
}
|
||||
|
||||
func setMatchedPrimitiveValue(kind reflect.Kind, value reflect.Value, v interface{}) error {
|
||||
switch kind {
|
||||
case reflect.Bool:
|
||||
@@ -504,13 +503,13 @@ func setMatchedPrimitiveValue(kind reflect.Kind, value reflect.Value, v interfac
|
||||
return nil
|
||||
}
|
||||
|
||||
func setValue(kind reflect.Kind, value reflect.Value, str string) error {
|
||||
func setValueFromString(kind reflect.Kind, value reflect.Value, str string) error {
|
||||
if !value.CanSet() {
|
||||
return errValueNotSettable
|
||||
}
|
||||
|
||||
value = ensureValue(value)
|
||||
v, err := convertType(kind, str)
|
||||
v, err := convertTypeFromString(kind, str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -583,7 +582,7 @@ func validateAndSetValue(kind reflect.Kind, value reflect.Value, str string, opt
|
||||
return errValueNotSettable
|
||||
}
|
||||
|
||||
v, err := convertType(kind, str)
|
||||
v, err := convertTypeFromString(kind, str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ func TestValidatePtrWithZeroValue(t *testing.T) {
|
||||
|
||||
func TestSetValueNotSettable(t *testing.T) {
|
||||
var i int
|
||||
assert.NotNil(t, setValue(reflect.Int, reflect.ValueOf(i), "1"))
|
||||
assert.NotNil(t, setValueFromString(reflect.Int, reflect.ValueOf(i), "1"))
|
||||
}
|
||||
|
||||
func TestParseKeyAndOptionsErrors(t *testing.T) {
|
||||
@@ -290,133 +290,9 @@ func TestSetValueFormatErrors(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.kind.String(), func(t *testing.T) {
|
||||
err := setValue(test.kind, test.target, test.value)
|
||||
err := setValueFromString(test.kind, test.target, test.value)
|
||||
assert.NotEqual(t, errValueNotSettable, err)
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepr(t *testing.T) {
|
||||
var (
|
||||
f32 float32 = 1.1
|
||||
f64 = 2.2
|
||||
i8 int8 = 1
|
||||
i16 int16 = 2
|
||||
i32 int32 = 3
|
||||
i64 int64 = 4
|
||||
u8 uint8 = 5
|
||||
u16 uint16 = 6
|
||||
u32 uint32 = 7
|
||||
u64 uint64 = 8
|
||||
)
|
||||
tests := []struct {
|
||||
v interface{}
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
nil,
|
||||
"",
|
||||
},
|
||||
{
|
||||
mockStringable{},
|
||||
"mocked",
|
||||
},
|
||||
{
|
||||
new(mockStringable),
|
||||
"mocked",
|
||||
},
|
||||
{
|
||||
newMockPtr(),
|
||||
"mockptr",
|
||||
},
|
||||
{
|
||||
&mockOpacity{
|
||||
val: 1,
|
||||
},
|
||||
"{1}",
|
||||
},
|
||||
{
|
||||
true,
|
||||
"true",
|
||||
},
|
||||
{
|
||||
false,
|
||||
"false",
|
||||
},
|
||||
{
|
||||
f32,
|
||||
"1.1",
|
||||
},
|
||||
{
|
||||
f64,
|
||||
"2.2",
|
||||
},
|
||||
{
|
||||
i8,
|
||||
"1",
|
||||
},
|
||||
{
|
||||
i16,
|
||||
"2",
|
||||
},
|
||||
{
|
||||
i32,
|
||||
"3",
|
||||
},
|
||||
{
|
||||
i64,
|
||||
"4",
|
||||
},
|
||||
{
|
||||
u8,
|
||||
"5",
|
||||
},
|
||||
{
|
||||
u16,
|
||||
"6",
|
||||
},
|
||||
{
|
||||
u32,
|
||||
"7",
|
||||
},
|
||||
{
|
||||
u64,
|
||||
"8",
|
||||
},
|
||||
{
|
||||
[]byte(`abcd`),
|
||||
"abcd",
|
||||
},
|
||||
{
|
||||
mockOpacity{val: 1},
|
||||
"{1}",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.expect, func(t *testing.T) {
|
||||
assert.Equal(t, test.expect, Repr(test.v))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type mockStringable struct{}
|
||||
|
||||
func (m mockStringable) String() string {
|
||||
return "mocked"
|
||||
}
|
||||
|
||||
type mockPtr struct{}
|
||||
|
||||
func newMockPtr() *mockPtr {
|
||||
return new(mockPtr)
|
||||
}
|
||||
|
||||
func (m *mockPtr) String() string {
|
||||
return "mockptr"
|
||||
}
|
||||
|
||||
type mockOpacity struct {
|
||||
val int
|
||||
}
|
||||
|
||||
@@ -7,12 +7,106 @@ type (
|
||||
Value(key string) (interface{}, bool)
|
||||
}
|
||||
|
||||
// A MapValuer is a map that can use Value method to get values with given keys.
|
||||
MapValuer map[string]interface{}
|
||||
// A valuerWithParent defines a node that has a parent node.
|
||||
valuerWithParent interface {
|
||||
Valuer
|
||||
// Parent get the parent valuer for current node.
|
||||
Parent() valuerWithParent
|
||||
}
|
||||
|
||||
// A node is a map that can use Value method to get values with given keys.
|
||||
node struct {
|
||||
current Valuer
|
||||
parent valuerWithParent
|
||||
}
|
||||
|
||||
// A valueWithParent is used to wrap the value with its parent.
|
||||
valueWithParent struct {
|
||||
value interface{}
|
||||
parent valuerWithParent
|
||||
}
|
||||
|
||||
// mapValuer is a type for map to meet the Valuer interface.
|
||||
mapValuer map[string]interface{}
|
||||
// simpleValuer is a type to get value from current node.
|
||||
simpleValuer node
|
||||
// recursiveValuer is a type to get the value recursively from current and parent nodes.
|
||||
recursiveValuer node
|
||||
)
|
||||
|
||||
// Value gets the value associated with the given key from mv.
|
||||
func (mv MapValuer) Value(key string) (interface{}, bool) {
|
||||
// Value gets the value assciated with the given key from mv.
|
||||
func (mv mapValuer) Value(key string) (interface{}, bool) {
|
||||
v, ok := mv[key]
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// Value gets the value associated with the given key from sv.
|
||||
func (sv simpleValuer) Value(key string) (interface{}, bool) {
|
||||
v, ok := sv.current.Value(key)
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// Parent get the parent valuer from sv.
|
||||
func (sv simpleValuer) Parent() valuerWithParent {
|
||||
if sv.parent == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return recursiveValuer{
|
||||
current: sv.parent,
|
||||
parent: sv.parent.Parent(),
|
||||
}
|
||||
}
|
||||
|
||||
// Value gets the value associated with the given key from rv,
|
||||
// and it will inherit the value from parent nodes.
|
||||
func (rv recursiveValuer) Value(key string) (interface{}, bool) {
|
||||
val, ok := rv.current.Value(key)
|
||||
if !ok {
|
||||
if parent := rv.Parent(); parent != nil {
|
||||
return parent.Value(key)
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
vm, ok := val.(map[string]interface{})
|
||||
if !ok {
|
||||
return val, true
|
||||
}
|
||||
|
||||
parent := rv.Parent()
|
||||
if parent == nil {
|
||||
return val, true
|
||||
}
|
||||
|
||||
pv, ok := parent.Value(key)
|
||||
if !ok {
|
||||
return val, true
|
||||
}
|
||||
|
||||
pm, ok := pv.(map[string]interface{})
|
||||
if !ok {
|
||||
return val, true
|
||||
}
|
||||
|
||||
for k, v := range pm {
|
||||
if _, ok := vm[k]; !ok {
|
||||
vm[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return vm, true
|
||||
}
|
||||
|
||||
// Parent get the parent valuer from rv.
|
||||
func (rv recursiveValuer) Parent() valuerWithParent {
|
||||
if rv.parent == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return recursiveValuer{
|
||||
current: rv.parent,
|
||||
parent: rv.parent.Parent(),
|
||||
}
|
||||
}
|
||||
|
||||
57
core/mapping/valuer_test.go
Normal file
57
core/mapping/valuer_test.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMapValuerWithInherit_Value(t *testing.T) {
|
||||
input := map[string]interface{}{
|
||||
"discovery": map[string]interface{}{
|
||||
"host": "localhost",
|
||||
"port": 8080,
|
||||
},
|
||||
"component": map[string]interface{}{
|
||||
"name": "test",
|
||||
},
|
||||
}
|
||||
valuer := recursiveValuer{
|
||||
current: mapValuer(input["component"].(map[string]interface{})),
|
||||
parent: simpleValuer{
|
||||
current: mapValuer(input),
|
||||
},
|
||||
}
|
||||
|
||||
val, ok := valuer.Value("discovery")
|
||||
assert.True(t, ok)
|
||||
|
||||
m, ok := val.(map[string]interface{})
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "localhost", m["host"])
|
||||
assert.Equal(t, 8080, m["port"])
|
||||
}
|
||||
|
||||
func TestRecursiveValuer_Value(t *testing.T) {
|
||||
input := map[string]interface{}{
|
||||
"component": map[string]interface{}{
|
||||
"name": "test",
|
||||
"foo": map[string]interface{}{
|
||||
"bar": "baz",
|
||||
},
|
||||
},
|
||||
"foo": "value",
|
||||
}
|
||||
valuer := recursiveValuer{
|
||||
current: mapValuer(input["component"].(map[string]interface{})),
|
||||
parent: simpleValuer{
|
||||
current: mapValuer(input),
|
||||
},
|
||||
}
|
||||
|
||||
val, ok := valuer.Value("foo")
|
||||
assert.True(t, ok)
|
||||
assert.EqualValues(t, map[string]interface{}{
|
||||
"bar": "baz",
|
||||
}, val)
|
||||
}
|
||||
@@ -1,101 +1,27 @@
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// To make .json & .yaml consistent, we just use json as the tag key.
|
||||
const yamlTagKey = "json"
|
||||
|
||||
var (
|
||||
// ErrUnsupportedType is an error that indicates the config format is not supported.
|
||||
ErrUnsupportedType = errors.New("only map-like configs are supported")
|
||||
|
||||
yamlUnmarshaler = NewUnmarshaler(yamlTagKey)
|
||||
"github.com/zeromicro/go-zero/internal/encoding"
|
||||
)
|
||||
|
||||
// UnmarshalYamlBytes unmarshals content into v.
|
||||
func UnmarshalYamlBytes(content []byte, v interface{}) error {
|
||||
return unmarshalYamlBytes(content, v, yamlUnmarshaler)
|
||||
func UnmarshalYamlBytes(content []byte, v interface{}, opts ...UnmarshalOption) error {
|
||||
b, err := encoding.YamlToJson(content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return UnmarshalJsonBytes(b, v, opts...)
|
||||
}
|
||||
|
||||
// UnmarshalYamlReader unmarshals content from reader into v.
|
||||
func UnmarshalYamlReader(reader io.Reader, v interface{}) error {
|
||||
return unmarshalYamlReader(reader, v, yamlUnmarshaler)
|
||||
}
|
||||
|
||||
func cleanupInterfaceMap(in map[interface{}]interface{}) map[string]interface{} {
|
||||
res := make(map[string]interface{})
|
||||
for k, v := range in {
|
||||
res[Repr(k)] = cleanupMapValue(v)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func cleanupInterfaceNumber(in interface{}) json.Number {
|
||||
return json.Number(Repr(in))
|
||||
}
|
||||
|
||||
func cleanupInterfaceSlice(in []interface{}) []interface{} {
|
||||
res := make([]interface{}, len(in))
|
||||
for i, v := range in {
|
||||
res[i] = cleanupMapValue(v)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func cleanupMapValue(v interface{}) interface{} {
|
||||
switch v := v.(type) {
|
||||
case []interface{}:
|
||||
return cleanupInterfaceSlice(v)
|
||||
case map[interface{}]interface{}:
|
||||
return cleanupInterfaceMap(v)
|
||||
case bool, string:
|
||||
return v
|
||||
case int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64, float32, float64:
|
||||
return cleanupInterfaceNumber(v)
|
||||
default:
|
||||
return Repr(v)
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshal(unmarshaler *Unmarshaler, o, v interface{}) error {
|
||||
if m, ok := o.(map[string]interface{}); ok {
|
||||
return unmarshaler.Unmarshal(m, v)
|
||||
}
|
||||
|
||||
return ErrUnsupportedType
|
||||
}
|
||||
|
||||
func unmarshalYamlBytes(content []byte, v interface{}, unmarshaler *Unmarshaler) error {
|
||||
var o interface{}
|
||||
if err := yamlUnmarshal(content, &o); err != nil {
|
||||
func UnmarshalYamlReader(reader io.Reader, v interface{}, opts ...UnmarshalOption) error {
|
||||
b, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return unmarshal(unmarshaler, o, v)
|
||||
}
|
||||
|
||||
func unmarshalYamlReader(reader io.Reader, v interface{}, unmarshaler *Unmarshaler) error {
|
||||
var res interface{}
|
||||
if err := yaml.NewDecoder(reader).Decode(&res); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return unmarshal(unmarshaler, cleanupMapValue(res), v)
|
||||
}
|
||||
|
||||
// yamlUnmarshal YAML to map[string]interface{} instead of map[interface{}]interface{}.
|
||||
func yamlUnmarshal(in []byte, out interface{}) error {
|
||||
var res interface{}
|
||||
if err := yaml.Unmarshal(in, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*out.(*interface{}) = cleanupMapValue(res)
|
||||
return nil
|
||||
return UnmarshalYamlBytes(b, v, opts...)
|
||||
}
|
||||
|
||||
@@ -934,9 +934,8 @@ func TestUnmarshalYamlReaderError(t *testing.T) {
|
||||
err := UnmarshalYamlReader(reader, &v)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
reader = strings.NewReader("chenquan")
|
||||
err = UnmarshalYamlReader(reader, &v)
|
||||
assert.ErrorIs(t, err, ErrUnsupportedType)
|
||||
reader = strings.NewReader("foo")
|
||||
assert.Error(t, UnmarshalYamlReader(reader, &v))
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBadReader(t *testing.T) {
|
||||
@@ -1012,6 +1011,13 @@ func TestUnmarshalYamlMapRune(t *testing.T) {
|
||||
assert.Equal(t, rune(3), v.Machine["node3"])
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBadInput(t *testing.T) {
|
||||
var v struct {
|
||||
Any string
|
||||
}
|
||||
assert.Error(t, UnmarshalYamlBytes([]byte("':foo"), &v))
|
||||
}
|
||||
|
||||
type badReader struct{}
|
||||
|
||||
func (b *badReader) Read(_ []byte) (n int, err error) {
|
||||
|
||||
@@ -6,14 +6,14 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Unstable is used to generate random value around the mean value base on given deviation.
|
||||
// An Unstable is used to generate random value around the mean value base on given deviation.
|
||||
type Unstable struct {
|
||||
deviation float64
|
||||
r *rand.Rand
|
||||
lock *sync.Mutex
|
||||
}
|
||||
|
||||
// NewUnstable returns a Unstable.
|
||||
// NewUnstable returns an Unstable.
|
||||
func NewUnstable(deviation float64) Unstable {
|
||||
if deviation < 0 {
|
||||
deviation = 0
|
||||
|
||||
@@ -4,7 +4,8 @@ import "github.com/zeromicro/go-zero/core/logx"
|
||||
|
||||
// Recover is used with defer to do cleanup on panics.
|
||||
// Use it like:
|
||||
// defer Recover(func() {})
|
||||
//
|
||||
// defer Recover(func() {})
|
||||
func Recover(cleanups ...func()) {
|
||||
for _, cleanup := range cleanups {
|
||||
cleanup()
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/zeromicro/go-zero/core/prometheus"
|
||||
"github.com/zeromicro/go-zero/core/stat"
|
||||
"github.com/zeromicro/go-zero/core/trace"
|
||||
"github.com/zeromicro/go-zero/internal/devserver"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -28,10 +29,12 @@ const (
|
||||
type ServiceConf struct {
|
||||
Name string
|
||||
Log logx.LogConf
|
||||
Mode string `json:",default=pro,options=dev|test|rt|pre|pro"`
|
||||
MetricsUrl string `json:",optional"`
|
||||
Mode string `json:",default=pro,options=dev|test|rt|pre|pro"`
|
||||
MetricsUrl string `json:",optional"`
|
||||
// Deprecated: please use DevServer
|
||||
Prometheus prometheus.Config `json:",optional"`
|
||||
Telemetry trace.Config `json:",optional"`
|
||||
DevServer devserver.Config `json:",optional"`
|
||||
}
|
||||
|
||||
// MustSetUp sets up the service, exits on error.
|
||||
@@ -64,6 +67,7 @@ func (sc ServiceConf) SetUp() error {
|
||||
if len(sc.MetricsUrl) > 0 {
|
||||
stat.SetReportWriter(stat.NewRemoteWriter(sc.MetricsUrl))
|
||||
}
|
||||
devserver.StartAgent(sc.DevServer)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package stat
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
@@ -12,6 +13,9 @@ import (
|
||||
)
|
||||
|
||||
func TestReport(t *testing.T) {
|
||||
os.Setenv(clusterNameKey, "test-cluster")
|
||||
defer os.Unsetenv(clusterNameKey)
|
||||
|
||||
var count int32
|
||||
SetReporter(func(s string) {
|
||||
atomic.AddInt32(&count, 1)
|
||||
|
||||
@@ -258,7 +258,7 @@ func parseUints(val string) ([]uint64, error) {
|
||||
return sets, nil
|
||||
}
|
||||
|
||||
// runningInUserNS detects whether we are currently running in a user namespace.
|
||||
// runningInUserNS detects whether we are currently running in an user namespace.
|
||||
func runningInUserNS() bool {
|
||||
nsOnce.Do(func() {
|
||||
file, err := os.Open("/proc/self/uid_map")
|
||||
|
||||
@@ -33,13 +33,7 @@ func initialize() {
|
||||
}
|
||||
|
||||
cores = uint64(len(cpus))
|
||||
sets, err := cpuSets()
|
||||
if err != nil {
|
||||
logx.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
quota = float64(len(sets))
|
||||
quota = float64(len(cpus))
|
||||
cq, err := cpuQuota()
|
||||
if err == nil {
|
||||
if cq != -1 {
|
||||
|
||||
@@ -45,9 +45,13 @@ func RawFieldNames(in interface{}, postgresSql ...bool) []string {
|
||||
// `db:"id"`
|
||||
// `db:"id,type=char,length=16"`
|
||||
// `db:",type=char,length=16"`
|
||||
// `db:"-,type=char,length=16"`
|
||||
if strings.Contains(tagv, ",") {
|
||||
tagv = strings.TrimSpace(strings.Split(tagv, ",")[0])
|
||||
}
|
||||
if tagv == "-" {
|
||||
continue
|
||||
}
|
||||
if len(tagv) == 0 {
|
||||
tagv = fi.Name
|
||||
}
|
||||
|
||||
@@ -39,3 +39,33 @@ func TestFieldNamesWithTagOptions(t *testing.T) {
|
||||
assert.Equal(t, expected, out)
|
||||
})
|
||||
}
|
||||
|
||||
type mockedUserWithDashTag struct {
|
||||
ID string `db:"id" json:"id,omitempty"`
|
||||
UserName string `db:"user_name" json:"userName,omitempty"`
|
||||
Mobile string `db:"-" json:"mobile,omitempty"`
|
||||
}
|
||||
|
||||
func TestFieldNamesWithDashTag(t *testing.T) {
|
||||
t.Run("new", func(t *testing.T) {
|
||||
var u mockedUserWithDashTag
|
||||
out := RawFieldNames(&u)
|
||||
expected := []string{"`id`", "`user_name`"}
|
||||
assert.Equal(t, expected, out)
|
||||
})
|
||||
}
|
||||
|
||||
type mockedUserWithDashTagAndOptions struct {
|
||||
ID string `db:"id" json:"id,omitempty"`
|
||||
UserName string `db:"user_name,type=varchar,length=255" json:"userName,omitempty"`
|
||||
Mobile string `db:"-,type=varchar,length=255" json:"mobile,omitempty"`
|
||||
}
|
||||
|
||||
func TestFieldNamesWithDashTagAndOptions(t *testing.T) {
|
||||
t.Run("new", func(t *testing.T) {
|
||||
var u mockedUserWithDashTagAndOptions
|
||||
out := RawFieldNames(&u)
|
||||
expected := []string{"`id`", "`user_name`"}
|
||||
assert.Equal(t, expected, out)
|
||||
})
|
||||
}
|
||||
|
||||
119
core/stores/cache/cache_test.go
vendored
119
core/stores/cache/cache_test.go
vendored
@@ -10,6 +10,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/errorx"
|
||||
"github.com/zeromicro/go-zero/core/hash"
|
||||
@@ -109,51 +110,85 @@ func (mc *mockedNode) TakeWithExpireCtx(ctx context.Context, val interface{}, ke
|
||||
}
|
||||
|
||||
func TestCache_SetDel(t *testing.T) {
|
||||
const total = 1000
|
||||
r1, clean1, err := redistest.CreateRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean1()
|
||||
r2, clean2, err := redistest.CreateRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean2()
|
||||
conf := ClusterConf{
|
||||
{
|
||||
RedisConf: redis.RedisConf{
|
||||
Host: r1.Addr,
|
||||
Type: redis.NodeType,
|
||||
t.Run("test set del", func(t *testing.T) {
|
||||
const total = 1000
|
||||
r1, clean1, err := redistest.CreateRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean1()
|
||||
r2, clean2, err := redistest.CreateRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean2()
|
||||
conf := ClusterConf{
|
||||
{
|
||||
RedisConf: redis.RedisConf{
|
||||
Host: r1.Addr,
|
||||
Type: redis.NodeType,
|
||||
},
|
||||
Weight: 100,
|
||||
},
|
||||
Weight: 100,
|
||||
},
|
||||
{
|
||||
RedisConf: redis.RedisConf{
|
||||
Host: r2.Addr,
|
||||
Type: redis.NodeType,
|
||||
{
|
||||
RedisConf: redis.RedisConf{
|
||||
Host: r2.Addr,
|
||||
Type: redis.NodeType,
|
||||
},
|
||||
Weight: 100,
|
||||
},
|
||||
Weight: 100,
|
||||
},
|
||||
}
|
||||
c := New(conf, syncx.NewSingleFlight(), NewStat("mock"), errPlaceholder)
|
||||
for i := 0; i < total; i++ {
|
||||
if i%2 == 0 {
|
||||
assert.Nil(t, c.Set(fmt.Sprintf("key/%d", i), i))
|
||||
} else {
|
||||
assert.Nil(t, c.SetWithExpire(fmt.Sprintf("key/%d", i), i, 0))
|
||||
}
|
||||
}
|
||||
for i := 0; i < total; i++ {
|
||||
var val int
|
||||
assert.Nil(t, c.Get(fmt.Sprintf("key/%d", i), &val))
|
||||
assert.Equal(t, i, val)
|
||||
}
|
||||
assert.Nil(t, c.Del())
|
||||
for i := 0; i < total; i++ {
|
||||
assert.Nil(t, c.Del(fmt.Sprintf("key/%d", i)))
|
||||
}
|
||||
for i := 0; i < total; i++ {
|
||||
var val int
|
||||
assert.True(t, c.IsNotFound(c.Get(fmt.Sprintf("key/%d", i), &val)))
|
||||
assert.Equal(t, 0, val)
|
||||
}
|
||||
c := New(conf, syncx.NewSingleFlight(), NewStat("mock"), errPlaceholder)
|
||||
for i := 0; i < total; i++ {
|
||||
if i%2 == 0 {
|
||||
assert.Nil(t, c.Set(fmt.Sprintf("key/%d", i), i))
|
||||
} else {
|
||||
assert.Nil(t, c.SetWithExpire(fmt.Sprintf("key/%d", i), i, 0))
|
||||
}
|
||||
}
|
||||
for i := 0; i < total; i++ {
|
||||
var val int
|
||||
assert.Nil(t, c.Get(fmt.Sprintf("key/%d", i), &val))
|
||||
assert.Equal(t, i, val)
|
||||
}
|
||||
assert.Nil(t, c.Del())
|
||||
for i := 0; i < total; i++ {
|
||||
assert.Nil(t, c.Del(fmt.Sprintf("key/%d", i)))
|
||||
}
|
||||
assert.Nil(t, c.Del("a", "b", "c"))
|
||||
for i := 0; i < total; i++ {
|
||||
var val int
|
||||
assert.True(t, c.IsNotFound(c.Get(fmt.Sprintf("key/%d", i), &val)))
|
||||
assert.Equal(t, 0, val)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test set del error", func(t *testing.T) {
|
||||
r1, err := miniredis.Run()
|
||||
assert.NoError(t, err)
|
||||
defer r1.Close()
|
||||
r1.SetError("mock error")
|
||||
|
||||
r2, err := miniredis.Run()
|
||||
assert.NoError(t, err)
|
||||
defer r2.Close()
|
||||
r2.SetError("mock error")
|
||||
|
||||
conf := ClusterConf{
|
||||
{
|
||||
RedisConf: redis.RedisConf{
|
||||
Host: r1.Addr(),
|
||||
Type: redis.NodeType,
|
||||
},
|
||||
Weight: 100,
|
||||
},
|
||||
{
|
||||
RedisConf: redis.RedisConf{
|
||||
Host: r2.Addr(),
|
||||
Type: redis.NodeType,
|
||||
},
|
||||
Weight: 100,
|
||||
},
|
||||
}
|
||||
c := New(conf, syncx.NewSingleFlight(), NewStat("mock"), errPlaceholder)
|
||||
assert.NoError(t, c.Del("a", "b", "c"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache_OneNode(t *testing.T) {
|
||||
|
||||
85
core/stores/cache/cachenode_test.go
vendored
85
core/stores/cache/cachenode_test.go
vendored
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -11,12 +12,14 @@ import (
|
||||
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/collection"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/mathx"
|
||||
"github.com/zeromicro/go-zero/core/stat"
|
||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||
"github.com/zeromicro/go-zero/core/stores/redis/redistest"
|
||||
"github.com/zeromicro/go-zero/core/syncx"
|
||||
"github.com/zeromicro/go-zero/core/timex"
|
||||
)
|
||||
|
||||
var errTestNotFound = errors.New("not found")
|
||||
@@ -27,27 +30,54 @@ func init() {
|
||||
}
|
||||
|
||||
func TestCacheNode_DelCache(t *testing.T) {
|
||||
store, clean, err := redistest.CreateRedis()
|
||||
assert.Nil(t, err)
|
||||
store.Type = redis.ClusterType
|
||||
defer clean()
|
||||
t.Run("del cache", func(t *testing.T) {
|
||||
store, clean, err := redistest.CreateRedis()
|
||||
assert.Nil(t, err)
|
||||
store.Type = redis.ClusterType
|
||||
defer clean()
|
||||
|
||||
cn := cacheNode{
|
||||
rds: store,
|
||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
lock: new(sync.Mutex),
|
||||
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||
stat: NewStat("any"),
|
||||
errNotFound: errTestNotFound,
|
||||
}
|
||||
assert.Nil(t, cn.Del())
|
||||
assert.Nil(t, cn.Del([]string{}...))
|
||||
assert.Nil(t, cn.Del(make([]string, 0)...))
|
||||
cn.Set("first", "one")
|
||||
assert.Nil(t, cn.Del("first"))
|
||||
cn.Set("first", "one")
|
||||
cn.Set("second", "two")
|
||||
assert.Nil(t, cn.Del("first", "second"))
|
||||
cn := cacheNode{
|
||||
rds: store,
|
||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
lock: new(sync.Mutex),
|
||||
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||
stat: NewStat("any"),
|
||||
errNotFound: errTestNotFound,
|
||||
}
|
||||
assert.Nil(t, cn.Del())
|
||||
assert.Nil(t, cn.Del([]string{}...))
|
||||
assert.Nil(t, cn.Del(make([]string, 0)...))
|
||||
cn.Set("first", "one")
|
||||
assert.Nil(t, cn.Del("first"))
|
||||
cn.Set("first", "one")
|
||||
cn.Set("second", "two")
|
||||
assert.Nil(t, cn.Del("first", "second"))
|
||||
})
|
||||
|
||||
t.Run("del cache with errors", func(t *testing.T) {
|
||||
old := timingWheel
|
||||
ticker := timex.NewFakeTicker()
|
||||
var err error
|
||||
timingWheel, err = collection.NewTimingWheelWithTicker(
|
||||
time.Millisecond, timingWheelSlots, func(key, value interface{}) {
|
||||
clean(key, value)
|
||||
}, ticker)
|
||||
assert.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
timingWheel = old
|
||||
})
|
||||
|
||||
r, err := miniredis.Run()
|
||||
assert.NoError(t, err)
|
||||
defer r.Close()
|
||||
r.SetError("mock error")
|
||||
|
||||
node := NewNode(redis.New(r.Addr(), redis.Cluster()), syncx.NewSingleFlight(),
|
||||
NewStat("any"), errTestNotFound)
|
||||
assert.NoError(t, node.Del("foo", "bar"))
|
||||
ticker.Tick()
|
||||
runtime.Gosched()
|
||||
})
|
||||
}
|
||||
|
||||
func TestCacheNode_DelCacheWithErrors(t *testing.T) {
|
||||
@@ -125,6 +155,21 @@ func TestCacheNode_Take(t *testing.T) {
|
||||
assert.Equal(t, `"value"`, val)
|
||||
}
|
||||
|
||||
func TestCacheNode_TakeBadRedis(t *testing.T) {
|
||||
r, err := miniredis.Run()
|
||||
assert.NoError(t, err)
|
||||
defer r.Close()
|
||||
r.SetError("mock error")
|
||||
|
||||
cn := NewNode(redis.New(r.Addr()), syncx.NewSingleFlight(), NewStat("any"),
|
||||
errTestNotFound, WithExpiry(time.Second), WithNotFoundExpiry(time.Second))
|
||||
var str string
|
||||
assert.Error(t, cn.Take(&str, "any", func(v interface{}) error {
|
||||
*v.(*string) = "value"
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
|
||||
func TestCacheNode_TakeNotFound(t *testing.T) {
|
||||
store, clean, err := redistest.CreateRedis()
|
||||
assert.Nil(t, err)
|
||||
|
||||
4
core/stores/cache/cacheopt.go
vendored
4
core/stores/cache/cacheopt.go
vendored
@@ -34,14 +34,14 @@ func newOptions(opts ...Option) Options {
|
||||
return o
|
||||
}
|
||||
|
||||
// WithExpiry returns a func to customize a Options with given expiry.
|
||||
// WithExpiry returns a func to customize an Options with given expiry.
|
||||
func WithExpiry(expiry time.Duration) Option {
|
||||
return func(o *Options) {
|
||||
o.Expiry = expiry
|
||||
}
|
||||
}
|
||||
|
||||
// WithNotFoundExpiry returns a func to customize a Options with given not found expiry.
|
||||
// WithNotFoundExpiry returns a func to customize an Options with given not found expiry.
|
||||
func WithNotFoundExpiry(expiry time.Duration) Option {
|
||||
return func(o *Options) {
|
||||
o.NotFoundExpiry = expiry
|
||||
|
||||
16
core/stores/cache/cachestat.go
vendored
16
core/stores/cache/cachestat.go
vendored
@@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/timex"
|
||||
)
|
||||
|
||||
const statInterval = time.Minute
|
||||
@@ -25,7 +26,13 @@ func NewStat(name string) *Stat {
|
||||
ret := &Stat{
|
||||
name: name,
|
||||
}
|
||||
go ret.statLoop()
|
||||
|
||||
go func() {
|
||||
ticker := timex.NewTicker(statInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
ret.statLoop(ticker)
|
||||
}()
|
||||
|
||||
return ret
|
||||
}
|
||||
@@ -50,11 +57,8 @@ func (s *Stat) IncrementDbFails() {
|
||||
atomic.AddUint64(&s.DbFails, 1)
|
||||
}
|
||||
|
||||
func (s *Stat) statLoop() {
|
||||
ticker := time.NewTicker(statInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
func (s *Stat) statLoop(ticker timex.Ticker) {
|
||||
for range ticker.Chan() {
|
||||
total := atomic.SwapUint64(&s.Total, 0)
|
||||
if total == 0 {
|
||||
continue
|
||||
|
||||
28
core/stores/cache/cachestat_test.go
vendored
Normal file
28
core/stores/cache/cachestat_test.go
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/timex"
|
||||
)
|
||||
|
||||
func TestCacheStat_statLoop(t *testing.T) {
|
||||
t.Run("stat loop total 0", func(t *testing.T) {
|
||||
var stat Stat
|
||||
ticker := timex.NewFakeTicker()
|
||||
go stat.statLoop(ticker)
|
||||
ticker.Tick()
|
||||
ticker.Tick()
|
||||
ticker.Stop()
|
||||
})
|
||||
|
||||
t.Run("stat loop total not 0", func(t *testing.T) {
|
||||
var stat Stat
|
||||
stat.IncrementTotal()
|
||||
ticker := timex.NewFakeTicker()
|
||||
go stat.statLoop(ticker)
|
||||
ticker.Tick()
|
||||
ticker.Tick()
|
||||
ticker.Stop()
|
||||
})
|
||||
}
|
||||
@@ -26,9 +26,14 @@ type (
|
||||
)
|
||||
|
||||
// NewBulkInserter returns a BulkInserter.
|
||||
func NewBulkInserter(coll *mongo.Collection, interval ...time.Duration) *BulkInserter {
|
||||
func NewBulkInserter(coll Collection, interval ...time.Duration) (*BulkInserter, error) {
|
||||
cloneColl, err := coll.Clone()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inserter := &dbInserter{
|
||||
collection: coll,
|
||||
collection: cloneColl,
|
||||
}
|
||||
|
||||
duration := flushInterval
|
||||
@@ -39,7 +44,7 @@ func NewBulkInserter(coll *mongo.Collection, interval ...time.Duration) *BulkIns
|
||||
return &BulkInserter{
|
||||
executor: executors.NewPeriodicalExecutor(duration, inserter),
|
||||
inserter: inserter,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Flush flushes the inserter, writes all pending records.
|
||||
|
||||
@@ -15,7 +15,8 @@ func TestBulkInserter(t *testing.T) {
|
||||
|
||||
mt.Run("test", func(mt *mtest.T) {
|
||||
mt.AddMockResponses(mtest.CreateSuccessResponse(bson.D{{Key: "ok", Value: 1}}...))
|
||||
bulk := NewBulkInserter(mt.Coll)
|
||||
bulk, err := NewBulkInserter(createModel(mt).Collection)
|
||||
assert.Equal(t, err, nil)
|
||||
bulk.SetResultHandler(func(result *mongo.InsertManyResult, err error) {
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(result.InsertedIDs))
|
||||
|
||||
@@ -3,15 +3,12 @@ package mon
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/syncx"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
mopt "go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
const defaultTimeout = time.Second
|
||||
|
||||
var clientManager = syncx.NewResourceManager()
|
||||
|
||||
// ClosableClient wraps *mongo.Client and provides a Close method.
|
||||
@@ -30,9 +27,20 @@ func Inject(key string, client *mongo.Client) {
|
||||
clientManager.Inject(key, &ClosableClient{client})
|
||||
}
|
||||
|
||||
func getClient(url string) (*mongo.Client, error) {
|
||||
func getClient(url string, opts ...Option) (*mongo.Client, error) {
|
||||
val, err := clientManager.GetResource(url, func() (io.Closer, error) {
|
||||
cli, err := mongo.Connect(context.Background(), mopt.Client().ApplyURI(url))
|
||||
o := mopt.Client().ApplyURI(url)
|
||||
opts = append([]Option{defaultTimeoutOption()}, opts...)
|
||||
for _, opt := range opts {
|
||||
opt(o)
|
||||
}
|
||||
|
||||
cli, err := mongo.Connect(context.Background(), o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = cli.Ping(context.Background(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ func MustNewModel(uri, db, collection string, opts ...Option) *Model {
|
||||
|
||||
// NewModel returns a Model.
|
||||
func NewModel(uri, db, collection string, opts ...Option) (*Model, error) {
|
||||
cli, err := getClient(uri)
|
||||
cli, err := getClient(uri, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -4,14 +4,15 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/syncx"
|
||||
mopt "go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
const defaultTimeout = time.Second * 3
|
||||
|
||||
var slowThreshold = syncx.ForAtomicDuration(defaultSlowThreshold)
|
||||
|
||||
type (
|
||||
options struct {
|
||||
timeout time.Duration
|
||||
}
|
||||
options = mopt.ClientOptions
|
||||
|
||||
// Option defines the method to customize a mongo model.
|
||||
Option func(opts *options)
|
||||
@@ -22,8 +23,15 @@ func SetSlowThreshold(threshold time.Duration) {
|
||||
slowThreshold.Set(threshold)
|
||||
}
|
||||
|
||||
func defaultOptions() *options {
|
||||
return &options{
|
||||
timeout: defaultTimeout,
|
||||
func defaultTimeoutOption() Option {
|
||||
return func(opts *options) {
|
||||
opts.SetTimeout(defaultTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
// WithTimeout set the mon client operation timeout.
|
||||
func WithTimeout(timeout time.Duration) Option {
|
||||
return func(opts *options) {
|
||||
opts.SetTimeout(timeout)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
mopt "go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
func TestSetSlowThreshold(t *testing.T) {
|
||||
@@ -13,6 +14,14 @@ func TestSetSlowThreshold(t *testing.T) {
|
||||
assert.Equal(t, time.Second, slowThreshold.Load())
|
||||
}
|
||||
|
||||
func TestDefaultOptions(t *testing.T) {
|
||||
assert.Equal(t, defaultTimeout, defaultOptions().timeout)
|
||||
func Test_defaultTimeoutOption(t *testing.T) {
|
||||
opts := mopt.Client()
|
||||
defaultTimeoutOption()(opts)
|
||||
assert.Equal(t, defaultTimeout, *opts.Timeout)
|
||||
}
|
||||
|
||||
func TestWithTimeout(t *testing.T) {
|
||||
opts := mopt.Client()
|
||||
WithTimeout(time.Second)(opts)
|
||||
assert.Equal(t, time.Second, *opts.Timeout)
|
||||
}
|
||||
|
||||
@@ -14,12 +14,13 @@ import (
|
||||
var mongoCmdAttributeKey = attribute.Key("mongo.cmd")
|
||||
|
||||
func startSpan(ctx context.Context, cmd string) (context.Context, oteltrace.Span) {
|
||||
tracer := otel.GetTracerProvider().Tracer(trace.TraceName)
|
||||
tracer := otel.Tracer(trace.TraceName)
|
||||
ctx, span := tracer.Start(ctx,
|
||||
spanName,
|
||||
oteltrace.WithSpanKind(oteltrace.SpanKindClient),
|
||||
)
|
||||
span.SetAttributes(mongoCmdAttributeKey.String(cmd))
|
||||
|
||||
return ctx, span
|
||||
}
|
||||
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/zeromicro/go-zero/core/executors"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
const (
|
||||
flushInterval = time.Second
|
||||
maxBulkRows = 1000
|
||||
)
|
||||
|
||||
type (
|
||||
// ResultHandler is a handler that used to handle results.
|
||||
ResultHandler func(*mgo.BulkResult, error)
|
||||
|
||||
// A BulkInserter is used to insert bulk of mongo records.
|
||||
BulkInserter struct {
|
||||
executor *executors.PeriodicalExecutor
|
||||
inserter *dbInserter
|
||||
}
|
||||
)
|
||||
|
||||
// NewBulkInserter returns a BulkInserter.
|
||||
func NewBulkInserter(session *mgo.Session, dbName string, collectionNamer func() string) *BulkInserter {
|
||||
inserter := &dbInserter{
|
||||
session: session,
|
||||
dbName: dbName,
|
||||
collectionNamer: collectionNamer,
|
||||
}
|
||||
|
||||
return &BulkInserter{
|
||||
executor: executors.NewPeriodicalExecutor(flushInterval, inserter),
|
||||
inserter: inserter,
|
||||
}
|
||||
}
|
||||
|
||||
// Flush flushes the inserter, writes all pending records.
|
||||
func (bi *BulkInserter) Flush() {
|
||||
bi.executor.Flush()
|
||||
}
|
||||
|
||||
// Insert inserts doc.
|
||||
func (bi *BulkInserter) Insert(doc interface{}) {
|
||||
bi.executor.Add(doc)
|
||||
}
|
||||
|
||||
// SetResultHandler sets the result handler.
|
||||
func (bi *BulkInserter) SetResultHandler(handler ResultHandler) {
|
||||
bi.executor.Sync(func() {
|
||||
bi.inserter.resultHandler = handler
|
||||
})
|
||||
}
|
||||
|
||||
type dbInserter struct {
|
||||
session *mgo.Session
|
||||
dbName string
|
||||
collectionNamer func() string
|
||||
documents []interface{}
|
||||
resultHandler ResultHandler
|
||||
}
|
||||
|
||||
func (in *dbInserter) AddTask(doc interface{}) bool {
|
||||
in.documents = append(in.documents, doc)
|
||||
return len(in.documents) >= maxBulkRows
|
||||
}
|
||||
|
||||
func (in *dbInserter) Execute(objs interface{}) {
|
||||
docs := objs.([]interface{})
|
||||
if len(docs) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
bulk := in.session.DB(in.dbName).C(in.collectionNamer()).Bulk()
|
||||
bulk.Insert(docs...)
|
||||
bulk.Unordered()
|
||||
result, err := bulk.Run()
|
||||
if in.resultHandler != nil {
|
||||
in.resultHandler(result, err)
|
||||
} else if err != nil {
|
||||
logx.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (in *dbInserter) RemoveAll() interface{} {
|
||||
documents := in.documents
|
||||
in.documents = nil
|
||||
return documents
|
||||
}
|
||||
@@ -1,242 +0,0 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/zeromicro/go-zero/core/breaker"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/stores/mongo/internal"
|
||||
"github.com/zeromicro/go-zero/core/timex"
|
||||
)
|
||||
|
||||
const defaultSlowThreshold = time.Millisecond * 500
|
||||
|
||||
// ErrNotFound is an alias of mgo.ErrNotFound.
|
||||
var ErrNotFound = mgo.ErrNotFound
|
||||
|
||||
type (
|
||||
// Collection interface represents a mongo connection.
|
||||
Collection interface {
|
||||
Find(query interface{}) Query
|
||||
FindId(id interface{}) Query
|
||||
Insert(docs ...interface{}) error
|
||||
Pipe(pipeline interface{}) Pipe
|
||||
Remove(selector interface{}) error
|
||||
RemoveAll(selector interface{}) (*mgo.ChangeInfo, error)
|
||||
RemoveId(id interface{}) error
|
||||
Update(selector, update interface{}) error
|
||||
UpdateId(id, update interface{}) error
|
||||
Upsert(selector, update interface{}) (*mgo.ChangeInfo, error)
|
||||
}
|
||||
|
||||
decoratedCollection struct {
|
||||
name string
|
||||
collection internal.MgoCollection
|
||||
brk breaker.Breaker
|
||||
}
|
||||
|
||||
keepablePromise struct {
|
||||
promise breaker.Promise
|
||||
log func(error)
|
||||
}
|
||||
)
|
||||
|
||||
func newCollection(collection *mgo.Collection, brk breaker.Breaker) Collection {
|
||||
return &decoratedCollection{
|
||||
name: collection.FullName,
|
||||
collection: collection,
|
||||
brk: brk,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) Find(query interface{}) Query {
|
||||
promise, err := c.brk.Allow()
|
||||
if err != nil {
|
||||
return rejectedQuery{}
|
||||
}
|
||||
|
||||
startTime := timex.Now()
|
||||
return promisedQuery{
|
||||
Query: c.collection.Find(query),
|
||||
promise: keepablePromise{
|
||||
promise: promise,
|
||||
log: func(err error) {
|
||||
duration := timex.Since(startTime)
|
||||
c.logDuration("find", duration, err, query)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) FindId(id interface{}) Query {
|
||||
promise, err := c.brk.Allow()
|
||||
if err != nil {
|
||||
return rejectedQuery{}
|
||||
}
|
||||
|
||||
startTime := timex.Now()
|
||||
return promisedQuery{
|
||||
Query: c.collection.FindId(id),
|
||||
promise: keepablePromise{
|
||||
promise: promise,
|
||||
log: func(err error) {
|
||||
duration := timex.Since(startTime)
|
||||
c.logDuration("findId", duration, err, id)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) Insert(docs ...interface{}) (err error) {
|
||||
return c.brk.DoWithAcceptable(func() error {
|
||||
startTime := timex.Now()
|
||||
defer func() {
|
||||
duration := timex.Since(startTime)
|
||||
c.logDuration("insert", duration, err, docs...)
|
||||
}()
|
||||
|
||||
return c.collection.Insert(docs...)
|
||||
}, acceptable)
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) Pipe(pipeline interface{}) Pipe {
|
||||
promise, err := c.brk.Allow()
|
||||
if err != nil {
|
||||
return rejectedPipe{}
|
||||
}
|
||||
|
||||
startTime := timex.Now()
|
||||
return promisedPipe{
|
||||
Pipe: c.collection.Pipe(pipeline),
|
||||
promise: keepablePromise{
|
||||
promise: promise,
|
||||
log: func(err error) {
|
||||
duration := timex.Since(startTime)
|
||||
c.logDuration("pipe", duration, err, pipeline)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) Remove(selector interface{}) (err error) {
|
||||
return c.brk.DoWithAcceptable(func() error {
|
||||
startTime := timex.Now()
|
||||
defer func() {
|
||||
duration := timex.Since(startTime)
|
||||
c.logDuration("remove", duration, err, selector)
|
||||
}()
|
||||
|
||||
return c.collection.Remove(selector)
|
||||
}, acceptable)
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) RemoveAll(selector interface{}) (info *mgo.ChangeInfo, err error) {
|
||||
err = c.brk.DoWithAcceptable(func() error {
|
||||
startTime := timex.Now()
|
||||
defer func() {
|
||||
duration := timex.Since(startTime)
|
||||
c.logDuration("removeAll", duration, err, selector)
|
||||
}()
|
||||
|
||||
info, err = c.collection.RemoveAll(selector)
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) RemoveId(id interface{}) (err error) {
|
||||
return c.brk.DoWithAcceptable(func() error {
|
||||
startTime := timex.Now()
|
||||
defer func() {
|
||||
duration := timex.Since(startTime)
|
||||
c.logDuration("removeId", duration, err, id)
|
||||
}()
|
||||
|
||||
return c.collection.RemoveId(id)
|
||||
}, acceptable)
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) Update(selector, update interface{}) (err error) {
|
||||
return c.brk.DoWithAcceptable(func() error {
|
||||
startTime := timex.Now()
|
||||
defer func() {
|
||||
duration := timex.Since(startTime)
|
||||
c.logDuration("update", duration, err, selector, update)
|
||||
}()
|
||||
|
||||
return c.collection.Update(selector, update)
|
||||
}, acceptable)
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) UpdateId(id, update interface{}) (err error) {
|
||||
return c.brk.DoWithAcceptable(func() error {
|
||||
startTime := timex.Now()
|
||||
defer func() {
|
||||
duration := timex.Since(startTime)
|
||||
c.logDuration("updateId", duration, err, id, update)
|
||||
}()
|
||||
|
||||
return c.collection.UpdateId(id, update)
|
||||
}, acceptable)
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) Upsert(selector, update interface{}) (info *mgo.ChangeInfo, err error) {
|
||||
err = c.brk.DoWithAcceptable(func() error {
|
||||
startTime := timex.Now()
|
||||
defer func() {
|
||||
duration := timex.Since(startTime)
|
||||
c.logDuration("upsert", duration, err, selector, update)
|
||||
}()
|
||||
|
||||
info, err = c.collection.Upsert(selector, update)
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *decoratedCollection) logDuration(method string, duration time.Duration, err error, docs ...interface{}) {
|
||||
content, e := json.Marshal(docs)
|
||||
if e != nil {
|
||||
logx.Error(err)
|
||||
} else if err != nil {
|
||||
if duration > slowThreshold.Load() {
|
||||
logx.WithDuration(duration).Slowf("[MONGO] mongo(%s) - slowcall - %s - fail(%s) - %s",
|
||||
c.name, method, err.Error(), string(content))
|
||||
} else {
|
||||
logx.WithDuration(duration).Infof("mongo(%s) - %s - fail(%s) - %s",
|
||||
c.name, method, err.Error(), string(content))
|
||||
}
|
||||
} else {
|
||||
if duration > slowThreshold.Load() {
|
||||
logx.WithDuration(duration).Slowf("[MONGO] mongo(%s) - slowcall - %s - ok - %s",
|
||||
c.name, method, string(content))
|
||||
} else {
|
||||
logx.WithDuration(duration).Infof("mongo(%s) - %s - ok - %s", c.name, method, string(content))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p keepablePromise) accept(err error) error {
|
||||
p.promise.Accept()
|
||||
p.log(err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (p keepablePromise) keep(err error) error {
|
||||
if acceptable(err) {
|
||||
p.promise.Accept()
|
||||
} else {
|
||||
p.promise.Reject(err.Error())
|
||||
}
|
||||
|
||||
p.log(err)
|
||||
return err
|
||||
}
|
||||
|
||||
func acceptable(err error) bool {
|
||||
return err == nil || err == mgo.ErrNotFound
|
||||
}
|
||||
@@ -1,345 +0,0 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/breaker"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/stores/mongo/internal"
|
||||
"github.com/zeromicro/go-zero/core/stringx"
|
||||
)
|
||||
|
||||
var errDummy = errors.New("dummy")
|
||||
|
||||
func TestKeepPromise_accept(t *testing.T) {
|
||||
p := new(mockPromise)
|
||||
kp := keepablePromise{
|
||||
promise: p,
|
||||
log: func(error) {},
|
||||
}
|
||||
assert.Nil(t, kp.accept(nil))
|
||||
assert.Equal(t, mgo.ErrNotFound, kp.accept(mgo.ErrNotFound))
|
||||
}
|
||||
|
||||
func TestKeepPromise_keep(t *testing.T) {
|
||||
tests := []struct {
|
||||
err error
|
||||
accepted bool
|
||||
reason string
|
||||
}{
|
||||
{
|
||||
err: nil,
|
||||
accepted: true,
|
||||
reason: "",
|
||||
},
|
||||
{
|
||||
err: mgo.ErrNotFound,
|
||||
accepted: true,
|
||||
reason: "",
|
||||
},
|
||||
{
|
||||
err: errors.New("any"),
|
||||
accepted: false,
|
||||
reason: "any",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(stringx.RandId(), func(t *testing.T) {
|
||||
p := new(mockPromise)
|
||||
kp := keepablePromise{
|
||||
promise: p,
|
||||
log: func(error) {},
|
||||
}
|
||||
assert.Equal(t, test.err, kp.keep(test.err))
|
||||
assert.Equal(t, test.accepted, p.accepted)
|
||||
assert.Equal(t, test.reason, p.reason)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCollection(t *testing.T) {
|
||||
col := newCollection(&mgo.Collection{
|
||||
Database: nil,
|
||||
Name: "foo",
|
||||
FullName: "bar",
|
||||
}, breaker.GetBreaker("localhost"))
|
||||
assert.Equal(t, "bar", col.(*decoratedCollection).name)
|
||||
}
|
||||
|
||||
func TestCollectionFind(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
var query mgo.Query
|
||||
col := internal.NewMockMgoCollection(ctrl)
|
||||
col.EXPECT().Find(gomock.Any()).Return(&query)
|
||||
c := decoratedCollection{
|
||||
collection: col,
|
||||
brk: breaker.NewBreaker(),
|
||||
}
|
||||
actual := c.Find(nil)
|
||||
switch v := actual.(type) {
|
||||
case promisedQuery:
|
||||
assert.Equal(t, &query, v.Query)
|
||||
assert.Equal(t, errDummy, v.promise.keep(errDummy))
|
||||
default:
|
||||
t.Fail()
|
||||
}
|
||||
c.brk = new(dropBreaker)
|
||||
actual = c.Find(nil)
|
||||
assert.Equal(t, rejectedQuery{}, actual)
|
||||
}
|
||||
|
||||
func TestCollectionFindId(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
var query mgo.Query
|
||||
col := internal.NewMockMgoCollection(ctrl)
|
||||
col.EXPECT().FindId(gomock.Any()).Return(&query)
|
||||
c := decoratedCollection{
|
||||
collection: col,
|
||||
brk: breaker.NewBreaker(),
|
||||
}
|
||||
actual := c.FindId(nil)
|
||||
switch v := actual.(type) {
|
||||
case promisedQuery:
|
||||
assert.Equal(t, &query, v.Query)
|
||||
assert.Equal(t, errDummy, v.promise.keep(errDummy))
|
||||
default:
|
||||
t.Fail()
|
||||
}
|
||||
c.brk = new(dropBreaker)
|
||||
actual = c.FindId(nil)
|
||||
assert.Equal(t, rejectedQuery{}, actual)
|
||||
}
|
||||
|
||||
func TestCollectionInsert(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
col := internal.NewMockMgoCollection(ctrl)
|
||||
col.EXPECT().Insert(nil, nil).Return(errDummy)
|
||||
c := decoratedCollection{
|
||||
collection: col,
|
||||
brk: breaker.NewBreaker(),
|
||||
}
|
||||
err := c.Insert(nil, nil)
|
||||
assert.Equal(t, errDummy, err)
|
||||
c.brk = new(dropBreaker)
|
||||
err = c.Insert(nil, nil)
|
||||
assert.Equal(t, errDummy, err)
|
||||
}
|
||||
|
||||
func TestCollectionPipe(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
var pipe mgo.Pipe
|
||||
col := internal.NewMockMgoCollection(ctrl)
|
||||
col.EXPECT().Pipe(gomock.Any()).Return(&pipe)
|
||||
c := decoratedCollection{
|
||||
collection: col,
|
||||
brk: breaker.NewBreaker(),
|
||||
}
|
||||
actual := c.Pipe(nil)
|
||||
switch v := actual.(type) {
|
||||
case promisedPipe:
|
||||
assert.Equal(t, &pipe, v.Pipe)
|
||||
assert.Equal(t, errDummy, v.promise.keep(errDummy))
|
||||
default:
|
||||
t.Fail()
|
||||
}
|
||||
c.brk = new(dropBreaker)
|
||||
actual = c.Pipe(nil)
|
||||
assert.Equal(t, rejectedPipe{}, actual)
|
||||
}
|
||||
|
||||
func TestCollectionRemove(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
col := internal.NewMockMgoCollection(ctrl)
|
||||
col.EXPECT().Remove(gomock.Any()).Return(errDummy)
|
||||
c := decoratedCollection{
|
||||
collection: col,
|
||||
brk: breaker.NewBreaker(),
|
||||
}
|
||||
err := c.Remove(nil)
|
||||
assert.Equal(t, errDummy, err)
|
||||
c.brk = new(dropBreaker)
|
||||
err = c.Remove(nil)
|
||||
assert.Equal(t, errDummy, err)
|
||||
}
|
||||
|
||||
func TestCollectionRemoveAll(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
col := internal.NewMockMgoCollection(ctrl)
|
||||
col.EXPECT().RemoveAll(gomock.Any()).Return(nil, errDummy)
|
||||
c := decoratedCollection{
|
||||
collection: col,
|
||||
brk: breaker.NewBreaker(),
|
||||
}
|
||||
_, err := c.RemoveAll(nil)
|
||||
assert.Equal(t, errDummy, err)
|
||||
c.brk = new(dropBreaker)
|
||||
_, err = c.RemoveAll(nil)
|
||||
assert.Equal(t, errDummy, err)
|
||||
}
|
||||
|
||||
func TestCollectionRemoveId(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
col := internal.NewMockMgoCollection(ctrl)
|
||||
col.EXPECT().RemoveId(gomock.Any()).Return(errDummy)
|
||||
c := decoratedCollection{
|
||||
collection: col,
|
||||
brk: breaker.NewBreaker(),
|
||||
}
|
||||
err := c.RemoveId(nil)
|
||||
assert.Equal(t, errDummy, err)
|
||||
c.brk = new(dropBreaker)
|
||||
err = c.RemoveId(nil)
|
||||
assert.Equal(t, errDummy, err)
|
||||
}
|
||||
|
||||
func TestCollectionUpdate(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
col := internal.NewMockMgoCollection(ctrl)
|
||||
col.EXPECT().Update(gomock.Any(), gomock.Any()).Return(errDummy)
|
||||
c := decoratedCollection{
|
||||
collection: col,
|
||||
brk: breaker.NewBreaker(),
|
||||
}
|
||||
err := c.Update(nil, nil)
|
||||
assert.Equal(t, errDummy, err)
|
||||
c.brk = new(dropBreaker)
|
||||
err = c.Update(nil, nil)
|
||||
assert.Equal(t, errDummy, err)
|
||||
}
|
||||
|
||||
func TestCollectionUpdateId(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
col := internal.NewMockMgoCollection(ctrl)
|
||||
col.EXPECT().UpdateId(gomock.Any(), gomock.Any()).Return(errDummy)
|
||||
c := decoratedCollection{
|
||||
collection: col,
|
||||
brk: breaker.NewBreaker(),
|
||||
}
|
||||
err := c.UpdateId(nil, nil)
|
||||
assert.Equal(t, errDummy, err)
|
||||
c.brk = new(dropBreaker)
|
||||
err = c.UpdateId(nil, nil)
|
||||
assert.Equal(t, errDummy, err)
|
||||
}
|
||||
|
||||
func TestCollectionUpsert(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
col := internal.NewMockMgoCollection(ctrl)
|
||||
col.EXPECT().Upsert(gomock.Any(), gomock.Any()).Return(nil, errDummy)
|
||||
c := decoratedCollection{
|
||||
collection: col,
|
||||
brk: breaker.NewBreaker(),
|
||||
}
|
||||
_, err := c.Upsert(nil, nil)
|
||||
assert.Equal(t, errDummy, err)
|
||||
c.brk = new(dropBreaker)
|
||||
_, err = c.Upsert(nil, nil)
|
||||
assert.Equal(t, errDummy, err)
|
||||
}
|
||||
|
||||
func Test_logDuration(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
col := internal.NewMockMgoCollection(ctrl)
|
||||
c := decoratedCollection{
|
||||
collection: col,
|
||||
brk: breaker.NewBreaker(),
|
||||
}
|
||||
|
||||
var buf strings.Builder
|
||||
w := logx.NewWriter(&buf)
|
||||
o := logx.Reset()
|
||||
logx.SetWriter(w)
|
||||
|
||||
defer func() {
|
||||
logx.Reset()
|
||||
logx.SetWriter(o)
|
||||
}()
|
||||
|
||||
buf.Reset()
|
||||
c.logDuration("foo", time.Millisecond, nil, "bar")
|
||||
assert.Contains(t, buf.String(), "foo")
|
||||
assert.Contains(t, buf.String(), "bar")
|
||||
|
||||
buf.Reset()
|
||||
c.logDuration("foo", time.Millisecond, errors.New("bar"), make(chan int))
|
||||
assert.Contains(t, buf.String(), "bar")
|
||||
|
||||
buf.Reset()
|
||||
c.logDuration("foo", slowThreshold.Load()+time.Millisecond, errors.New("bar"))
|
||||
assert.Contains(t, buf.String(), "bar")
|
||||
assert.Contains(t, buf.String(), "slowcall")
|
||||
|
||||
buf.Reset()
|
||||
c.logDuration("foo", slowThreshold.Load()+time.Millisecond, nil)
|
||||
assert.Contains(t, buf.String(), "foo")
|
||||
assert.Contains(t, buf.String(), "slowcall")
|
||||
}
|
||||
|
||||
type mockPromise struct {
|
||||
accepted bool
|
||||
reason string
|
||||
}
|
||||
|
||||
func (p *mockPromise) Accept() {
|
||||
p.accepted = true
|
||||
}
|
||||
|
||||
func (p *mockPromise) Reject(reason string) {
|
||||
p.reason = reason
|
||||
}
|
||||
|
||||
type dropBreaker struct{}
|
||||
|
||||
func (d *dropBreaker) Name() string {
|
||||
return "dummy"
|
||||
}
|
||||
|
||||
func (d *dropBreaker) Allow() (breaker.Promise, error) {
|
||||
return nil, errDummy
|
||||
}
|
||||
|
||||
func (d *dropBreaker) Do(req func() error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dropBreaker) DoWithAcceptable(req func() error, acceptable breaker.Acceptable) error {
|
||||
return errDummy
|
||||
}
|
||||
|
||||
func (d *dropBreaker) DoWithFallback(req func() error, fallback func(err error) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dropBreaker) DoWithFallbackAcceptable(req func() error, fallback func(err error) error,
|
||||
acceptable breaker.Acceptable) error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
//go:generate mockgen -package internal -destination collection_mock.go -source collection.go
|
||||
|
||||
package internal
|
||||
|
||||
import "github.com/globalsign/mgo"
|
||||
|
||||
// MgoCollection interface represents a mgo collection.
|
||||
type MgoCollection interface {
|
||||
Find(query interface{}) *mgo.Query
|
||||
FindId(id interface{}) *mgo.Query
|
||||
Insert(docs ...interface{}) error
|
||||
Pipe(pipeline interface{}) *mgo.Pipe
|
||||
Remove(selector interface{}) error
|
||||
RemoveAll(selector interface{}) (*mgo.ChangeInfo, error)
|
||||
RemoveId(id interface{}) error
|
||||
Update(selector, update interface{}) error
|
||||
UpdateId(id, update interface{}) error
|
||||
Upsert(selector, update interface{}) (*mgo.ChangeInfo, error)
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: collection.go
|
||||
|
||||
// Package internal is a generated GoMock package.
|
||||
package internal
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
mgo "github.com/globalsign/mgo"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockMgoCollection is a mock of MgoCollection interface
|
||||
type MockMgoCollection struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockMgoCollectionMockRecorder
|
||||
}
|
||||
|
||||
// MockMgoCollectionMockRecorder is the mock recorder for MockMgoCollection
|
||||
type MockMgoCollectionMockRecorder struct {
|
||||
mock *MockMgoCollection
|
||||
}
|
||||
|
||||
// NewMockMgoCollection creates a new mock instance
|
||||
func NewMockMgoCollection(ctrl *gomock.Controller) *MockMgoCollection {
|
||||
mock := &MockMgoCollection{ctrl: ctrl}
|
||||
mock.recorder = &MockMgoCollectionMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockMgoCollection) EXPECT() *MockMgoCollectionMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Find mocks base method
|
||||
func (m *MockMgoCollection) Find(query interface{}) *mgo.Query {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Find", query)
|
||||
ret0, _ := ret[0].(*mgo.Query)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Find indicates an expected call of Find
|
||||
func (mr *MockMgoCollectionMockRecorder) Find(query interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Find", reflect.TypeOf((*MockMgoCollection)(nil).Find), query)
|
||||
}
|
||||
|
||||
// FindId mocks base method
|
||||
func (m *MockMgoCollection) FindId(id interface{}) *mgo.Query {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "FindId", id)
|
||||
ret0, _ := ret[0].(*mgo.Query)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// FindId indicates an expected call of FindId
|
||||
func (mr *MockMgoCollectionMockRecorder) FindId(id interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindId", reflect.TypeOf((*MockMgoCollection)(nil).FindId), id)
|
||||
}
|
||||
|
||||
// Insert mocks base method
|
||||
func (m *MockMgoCollection) Insert(docs ...interface{}) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{}
|
||||
for _, a := range docs {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "Insert", varargs...)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Insert indicates an expected call of Insert
|
||||
func (mr *MockMgoCollectionMockRecorder) Insert(docs ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockMgoCollection)(nil).Insert), docs...)
|
||||
}
|
||||
|
||||
// Pipe mocks base method
|
||||
func (m *MockMgoCollection) Pipe(pipeline interface{}) *mgo.Pipe {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Pipe", pipeline)
|
||||
ret0, _ := ret[0].(*mgo.Pipe)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Pipe indicates an expected call of Pipe
|
||||
func (mr *MockMgoCollectionMockRecorder) Pipe(pipeline interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Pipe", reflect.TypeOf((*MockMgoCollection)(nil).Pipe), pipeline)
|
||||
}
|
||||
|
||||
// Remove mocks base method
|
||||
func (m *MockMgoCollection) Remove(selector interface{}) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Remove", selector)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Remove indicates an expected call of Remove
|
||||
func (mr *MockMgoCollectionMockRecorder) Remove(selector interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockMgoCollection)(nil).Remove), selector)
|
||||
}
|
||||
|
||||
// RemoveAll mocks base method
|
||||
func (m *MockMgoCollection) RemoveAll(selector interface{}) (*mgo.ChangeInfo, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RemoveAll", selector)
|
||||
ret0, _ := ret[0].(*mgo.ChangeInfo)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// RemoveAll indicates an expected call of RemoveAll
|
||||
func (mr *MockMgoCollectionMockRecorder) RemoveAll(selector interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveAll", reflect.TypeOf((*MockMgoCollection)(nil).RemoveAll), selector)
|
||||
}
|
||||
|
||||
// RemoveId mocks base method
|
||||
func (m *MockMgoCollection) RemoveId(id interface{}) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RemoveId", id)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// RemoveId indicates an expected call of RemoveId
|
||||
func (mr *MockMgoCollectionMockRecorder) RemoveId(id interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveId", reflect.TypeOf((*MockMgoCollection)(nil).RemoveId), id)
|
||||
}
|
||||
|
||||
// Update mocks base method
|
||||
func (m *MockMgoCollection) Update(selector, update interface{}) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Update", selector, update)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Update indicates an expected call of Update
|
||||
func (mr *MockMgoCollectionMockRecorder) Update(selector, update interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockMgoCollection)(nil).Update), selector, update)
|
||||
}
|
||||
|
||||
// UpdateId mocks base method
|
||||
func (m *MockMgoCollection) UpdateId(id, update interface{}) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateId", id, update)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UpdateId indicates an expected call of UpdateId
|
||||
func (mr *MockMgoCollectionMockRecorder) UpdateId(id, update interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateId", reflect.TypeOf((*MockMgoCollection)(nil).UpdateId), id, update)
|
||||
}
|
||||
|
||||
// Upsert mocks base method
|
||||
func (m *MockMgoCollection) Upsert(selector, update interface{}) (*mgo.ChangeInfo, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Upsert", selector, update)
|
||||
ret0, _ := ret[0].(*mgo.ChangeInfo)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Upsert indicates an expected call of Upsert
|
||||
func (mr *MockMgoCollectionMockRecorder) Upsert(selector, update interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Upsert", reflect.TypeOf((*MockMgoCollection)(nil).Upsert), selector, update)
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
//go:generate mockgen -package mongo -destination iter_mock.go -source iter.go Iter
|
||||
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"github.com/zeromicro/go-zero/core/breaker"
|
||||
)
|
||||
|
||||
type (
|
||||
// Iter interface represents a mongo iter.
|
||||
Iter interface {
|
||||
All(result interface{}) error
|
||||
Close() error
|
||||
Done() bool
|
||||
Err() error
|
||||
For(result interface{}, f func() error) error
|
||||
Next(result interface{}) bool
|
||||
State() (int64, []bson.Raw)
|
||||
Timeout() bool
|
||||
}
|
||||
|
||||
// A ClosableIter is a closable mongo iter.
|
||||
ClosableIter struct {
|
||||
Iter
|
||||
Cleanup func()
|
||||
}
|
||||
|
||||
promisedIter struct {
|
||||
Iter
|
||||
promise keepablePromise
|
||||
}
|
||||
|
||||
rejectedIter struct{}
|
||||
)
|
||||
|
||||
func (i promisedIter) All(result interface{}) error {
|
||||
return i.promise.keep(i.Iter.All(result))
|
||||
}
|
||||
|
||||
func (i promisedIter) Close() error {
|
||||
return i.promise.keep(i.Iter.Close())
|
||||
}
|
||||
|
||||
func (i promisedIter) Err() error {
|
||||
return i.Iter.Err()
|
||||
}
|
||||
|
||||
func (i promisedIter) For(result interface{}, f func() error) error {
|
||||
var ferr error
|
||||
err := i.Iter.For(result, func() error {
|
||||
ferr = f()
|
||||
return ferr
|
||||
})
|
||||
if ferr == err {
|
||||
return i.promise.accept(err)
|
||||
}
|
||||
|
||||
return i.promise.keep(err)
|
||||
}
|
||||
|
||||
// Close closes a mongo iter.
|
||||
func (it *ClosableIter) Close() error {
|
||||
err := it.Iter.Close()
|
||||
it.Cleanup()
|
||||
return err
|
||||
}
|
||||
|
||||
func (i rejectedIter) All(result interface{}) error {
|
||||
return breaker.ErrServiceUnavailable
|
||||
}
|
||||
|
||||
func (i rejectedIter) Close() error {
|
||||
return breaker.ErrServiceUnavailable
|
||||
}
|
||||
|
||||
func (i rejectedIter) Done() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (i rejectedIter) Err() error {
|
||||
return breaker.ErrServiceUnavailable
|
||||
}
|
||||
|
||||
func (i rejectedIter) For(result interface{}, f func() error) error {
|
||||
return breaker.ErrServiceUnavailable
|
||||
}
|
||||
|
||||
func (i rejectedIter) Next(result interface{}) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (i rejectedIter) State() (int64, []bson.Raw) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (i rejectedIter) Timeout() bool {
|
||||
return false
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: iter.go
|
||||
|
||||
// Package mongo is a generated GoMock package.
|
||||
package mongo
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
bson "github.com/globalsign/mgo/bson"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockIter is a mock of Iter interface
|
||||
type MockIter struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockIterMockRecorder
|
||||
}
|
||||
|
||||
// MockIterMockRecorder is the mock recorder for MockIter
|
||||
type MockIterMockRecorder struct {
|
||||
mock *MockIter
|
||||
}
|
||||
|
||||
// NewMockIter creates a new mock instance
|
||||
func NewMockIter(ctrl *gomock.Controller) *MockIter {
|
||||
mock := &MockIter{ctrl: ctrl}
|
||||
mock.recorder = &MockIterMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockIter) EXPECT() *MockIterMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// All mocks base method
|
||||
func (m *MockIter) All(result interface{}) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "All", result)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// All indicates an expected call of All
|
||||
func (mr *MockIterMockRecorder) All(result interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "All", reflect.TypeOf((*MockIter)(nil).All), result)
|
||||
}
|
||||
|
||||
// Close mocks base method
|
||||
func (m *MockIter) Close() error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Close")
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Close indicates an expected call of Close
|
||||
func (mr *MockIterMockRecorder) Close() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockIter)(nil).Close))
|
||||
}
|
||||
|
||||
// Done mocks base method
|
||||
func (m *MockIter) Done() bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Done")
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Done indicates an expected call of Done
|
||||
func (mr *MockIterMockRecorder) Done() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Done", reflect.TypeOf((*MockIter)(nil).Done))
|
||||
}
|
||||
|
||||
// Err mocks base method
|
||||
func (m *MockIter) Err() error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Err")
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Err indicates an expected call of Err
|
||||
func (mr *MockIterMockRecorder) Err() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Err", reflect.TypeOf((*MockIter)(nil).Err))
|
||||
}
|
||||
|
||||
// For mocks base method
|
||||
func (m *MockIter) For(result interface{}, f func() error) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "For", result, f)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// For indicates an expected call of For
|
||||
func (mr *MockIterMockRecorder) For(result, f interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "For", reflect.TypeOf((*MockIter)(nil).For), result, f)
|
||||
}
|
||||
|
||||
// Next mocks base method
|
||||
func (m *MockIter) Next(result interface{}) bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Next", result)
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Next indicates an expected call of Next
|
||||
func (mr *MockIterMockRecorder) Next(result interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Next", reflect.TypeOf((*MockIter)(nil).Next), result)
|
||||
}
|
||||
|
||||
// State mocks base method
|
||||
func (m *MockIter) State() (int64, []bson.Raw) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "State")
|
||||
ret0, _ := ret[0].(int64)
|
||||
ret1, _ := ret[1].([]bson.Raw)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// State indicates an expected call of State
|
||||
func (mr *MockIterMockRecorder) State() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "State", reflect.TypeOf((*MockIter)(nil).State))
|
||||
}
|
||||
|
||||
// Timeout mocks base method
|
||||
func (m *MockIter) Timeout() bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Timeout")
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Timeout indicates an expected call of Timeout
|
||||
func (mr *MockIterMockRecorder) Timeout() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Timeout", reflect.TypeOf((*MockIter)(nil).Timeout))
|
||||
}
|
||||
@@ -1,264 +0,0 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/breaker"
|
||||
"github.com/zeromicro/go-zero/core/stringx"
|
||||
"github.com/zeromicro/go-zero/core/syncx"
|
||||
)
|
||||
|
||||
func TestClosableIter_Close(t *testing.T) {
|
||||
errs := []error{
|
||||
nil,
|
||||
mgo.ErrNotFound,
|
||||
}
|
||||
|
||||
for _, err := range errs {
|
||||
t.Run(stringx.RandId(), func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
cleaned := syncx.NewAtomicBool()
|
||||
iter := NewMockIter(ctrl)
|
||||
iter.EXPECT().Close().Return(err)
|
||||
ci := ClosableIter{
|
||||
Iter: iter,
|
||||
Cleanup: func() {
|
||||
cleaned.Set(true)
|
||||
},
|
||||
}
|
||||
assert.Equal(t, err, ci.Close())
|
||||
assert.True(t, cleaned.True())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPromisedIter_AllAndClose(t *testing.T) {
|
||||
tests := []struct {
|
||||
err error
|
||||
accepted bool
|
||||
reason string
|
||||
}{
|
||||
{
|
||||
err: nil,
|
||||
accepted: true,
|
||||
reason: "",
|
||||
},
|
||||
{
|
||||
err: mgo.ErrNotFound,
|
||||
accepted: true,
|
||||
reason: "",
|
||||
},
|
||||
{
|
||||
err: errors.New("any"),
|
||||
accepted: false,
|
||||
reason: "any",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(stringx.RandId(), func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
iter := NewMockIter(ctrl)
|
||||
iter.EXPECT().All(gomock.Any()).Return(test.err)
|
||||
promise := new(mockPromise)
|
||||
pi := promisedIter{
|
||||
Iter: iter,
|
||||
promise: keepablePromise{
|
||||
promise: promise,
|
||||
log: func(error) {},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, test.err, pi.All(nil))
|
||||
assert.Equal(t, test.accepted, promise.accepted)
|
||||
assert.Equal(t, test.reason, promise.reason)
|
||||
})
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(stringx.RandId(), func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
iter := NewMockIter(ctrl)
|
||||
iter.EXPECT().Close().Return(test.err)
|
||||
promise := new(mockPromise)
|
||||
pi := promisedIter{
|
||||
Iter: iter,
|
||||
promise: keepablePromise{
|
||||
promise: promise,
|
||||
log: func(error) {},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, test.err, pi.Close())
|
||||
assert.Equal(t, test.accepted, promise.accepted)
|
||||
assert.Equal(t, test.reason, promise.reason)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPromisedIter_Err(t *testing.T) {
|
||||
errs := []error{
|
||||
nil,
|
||||
mgo.ErrNotFound,
|
||||
}
|
||||
|
||||
for _, err := range errs {
|
||||
t.Run(stringx.RandId(), func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
iter := NewMockIter(ctrl)
|
||||
iter.EXPECT().Err().Return(err)
|
||||
promise := new(mockPromise)
|
||||
pi := promisedIter{
|
||||
Iter: iter,
|
||||
promise: keepablePromise{
|
||||
promise: promise,
|
||||
log: func(error) {},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, err, pi.Err())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPromisedIter_For(t *testing.T) {
|
||||
tests := []struct {
|
||||
err error
|
||||
accepted bool
|
||||
reason string
|
||||
}{
|
||||
{
|
||||
err: nil,
|
||||
accepted: true,
|
||||
reason: "",
|
||||
},
|
||||
{
|
||||
err: mgo.ErrNotFound,
|
||||
accepted: true,
|
||||
reason: "",
|
||||
},
|
||||
{
|
||||
err: errors.New("any"),
|
||||
accepted: false,
|
||||
reason: "any",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(stringx.RandId(), func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
iter := NewMockIter(ctrl)
|
||||
iter.EXPECT().For(gomock.Any(), gomock.Any()).Return(test.err)
|
||||
promise := new(mockPromise)
|
||||
pi := promisedIter{
|
||||
Iter: iter,
|
||||
promise: keepablePromise{
|
||||
promise: promise,
|
||||
log: func(error) {},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, test.err, pi.For(nil, nil))
|
||||
assert.Equal(t, test.accepted, promise.accepted)
|
||||
assert.Equal(t, test.reason, promise.reason)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRejectedIter_All(t *testing.T) {
|
||||
assert.Equal(t, breaker.ErrServiceUnavailable, new(rejectedIter).All(nil))
|
||||
}
|
||||
|
||||
func TestRejectedIter_Close(t *testing.T) {
|
||||
assert.Equal(t, breaker.ErrServiceUnavailable, new(rejectedIter).Close())
|
||||
}
|
||||
|
||||
func TestRejectedIter_Done(t *testing.T) {
|
||||
assert.False(t, new(rejectedIter).Done())
|
||||
}
|
||||
|
||||
func TestRejectedIter_Err(t *testing.T) {
|
||||
assert.Equal(t, breaker.ErrServiceUnavailable, new(rejectedIter).Err())
|
||||
}
|
||||
|
||||
func TestRejectedIter_For(t *testing.T) {
|
||||
assert.Equal(t, breaker.ErrServiceUnavailable, new(rejectedIter).For(nil, nil))
|
||||
}
|
||||
|
||||
func TestRejectedIter_Next(t *testing.T) {
|
||||
assert.False(t, new(rejectedIter).Next(nil))
|
||||
}
|
||||
|
||||
func TestRejectedIter_State(t *testing.T) {
|
||||
n, raw := new(rejectedIter).State()
|
||||
assert.Equal(t, int64(0), n)
|
||||
assert.Nil(t, raw)
|
||||
}
|
||||
|
||||
func TestRejectedIter_Timeout(t *testing.T) {
|
||||
assert.False(t, new(rejectedIter).Timeout())
|
||||
}
|
||||
|
||||
func TestIter_Done(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
iter := NewMockIter(ctrl)
|
||||
iter.EXPECT().Done().Return(true)
|
||||
ci := ClosableIter{
|
||||
Iter: iter,
|
||||
Cleanup: nil,
|
||||
}
|
||||
assert.True(t, ci.Done())
|
||||
}
|
||||
|
||||
func TestIter_Next(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
iter := NewMockIter(ctrl)
|
||||
iter.EXPECT().Next(gomock.Any()).Return(true)
|
||||
ci := ClosableIter{
|
||||
Iter: iter,
|
||||
Cleanup: nil,
|
||||
}
|
||||
assert.True(t, ci.Next(nil))
|
||||
}
|
||||
|
||||
func TestIter_State(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
iter := NewMockIter(ctrl)
|
||||
iter.EXPECT().State().Return(int64(1), nil)
|
||||
ci := ClosableIter{
|
||||
Iter: iter,
|
||||
Cleanup: nil,
|
||||
}
|
||||
n, raw := ci.State()
|
||||
assert.Equal(t, int64(1), n)
|
||||
assert.Nil(t, raw)
|
||||
}
|
||||
|
||||
func TestIter_Timeout(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
iter := NewMockIter(ctrl)
|
||||
iter.EXPECT().Timeout().Return(true)
|
||||
ci := ClosableIter{
|
||||
Iter: iter,
|
||||
Cleanup: nil,
|
||||
}
|
||||
assert.True(t, ci.Timeout())
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/zeromicro/go-zero/core/breaker"
|
||||
)
|
||||
|
||||
// A Model is a mongo model.
|
||||
type Model struct {
|
||||
session *concurrentSession
|
||||
db *mgo.Database
|
||||
collection string
|
||||
brk breaker.Breaker
|
||||
opts []Option
|
||||
}
|
||||
|
||||
// MustNewModel returns a Model, exits on errors.
|
||||
func MustNewModel(url, collection string, opts ...Option) *Model {
|
||||
model, err := NewModel(url, collection, opts...)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return model
|
||||
}
|
||||
|
||||
// NewModel returns a Model.
|
||||
func NewModel(url, collection string, opts ...Option) (*Model, error) {
|
||||
session, err := getConcurrentSession(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Model{
|
||||
session: session,
|
||||
// If name is empty, the database name provided in the dialed URL is used instead
|
||||
db: session.DB(""),
|
||||
collection: collection,
|
||||
brk: breaker.GetBreaker(url),
|
||||
opts: opts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Find finds a record with given query.
|
||||
func (mm *Model) Find(query interface{}) (Query, error) {
|
||||
return mm.query(func(c Collection) Query {
|
||||
return c.Find(query)
|
||||
})
|
||||
}
|
||||
|
||||
// FindId finds a record with given id.
|
||||
func (mm *Model) FindId(id interface{}) (Query, error) {
|
||||
return mm.query(func(c Collection) Query {
|
||||
return c.FindId(id)
|
||||
})
|
||||
}
|
||||
|
||||
// GetCollection returns a Collection with given session.
|
||||
func (mm *Model) GetCollection(session *mgo.Session) Collection {
|
||||
return newCollection(mm.db.C(mm.collection).With(session), mm.brk)
|
||||
}
|
||||
|
||||
// Insert inserts docs into mm.
|
||||
func (mm *Model) Insert(docs ...interface{}) error {
|
||||
return mm.execute(func(c Collection) error {
|
||||
return c.Insert(docs...)
|
||||
})
|
||||
}
|
||||
|
||||
// Pipe returns a Pipe with given pipeline.
|
||||
func (mm *Model) Pipe(pipeline interface{}) (Pipe, error) {
|
||||
return mm.pipe(func(c Collection) Pipe {
|
||||
return c.Pipe(pipeline)
|
||||
})
|
||||
}
|
||||
|
||||
// PutSession returns the given session.
|
||||
func (mm *Model) PutSession(session *mgo.Session) {
|
||||
mm.session.putSession(session)
|
||||
}
|
||||
|
||||
// Remove removes the records with given selector.
|
||||
func (mm *Model) Remove(selector interface{}) error {
|
||||
return mm.execute(func(c Collection) error {
|
||||
return c.Remove(selector)
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveAll removes all with given selector and returns a mgo.ChangeInfo.
|
||||
func (mm *Model) RemoveAll(selector interface{}) (*mgo.ChangeInfo, error) {
|
||||
return mm.change(func(c Collection) (*mgo.ChangeInfo, error) {
|
||||
return c.RemoveAll(selector)
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveId removes a record with given id.
|
||||
func (mm *Model) RemoveId(id interface{}) error {
|
||||
return mm.execute(func(c Collection) error {
|
||||
return c.RemoveId(id)
|
||||
})
|
||||
}
|
||||
|
||||
// TakeSession gets a session.
|
||||
func (mm *Model) TakeSession() (*mgo.Session, error) {
|
||||
return mm.session.takeSession(mm.opts...)
|
||||
}
|
||||
|
||||
// Update updates a record with given selector.
|
||||
func (mm *Model) Update(selector, update interface{}) error {
|
||||
return mm.execute(func(c Collection) error {
|
||||
return c.Update(selector, update)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateId updates a record with given id.
|
||||
func (mm *Model) UpdateId(id, update interface{}) error {
|
||||
return mm.execute(func(c Collection) error {
|
||||
return c.UpdateId(id, update)
|
||||
})
|
||||
}
|
||||
|
||||
// Upsert upserts a record with given selector, and returns a mgo.ChangeInfo.
|
||||
func (mm *Model) Upsert(selector, update interface{}) (*mgo.ChangeInfo, error) {
|
||||
return mm.change(func(c Collection) (*mgo.ChangeInfo, error) {
|
||||
return c.Upsert(selector, update)
|
||||
})
|
||||
}
|
||||
|
||||
func (mm *Model) change(fn func(c Collection) (*mgo.ChangeInfo, error)) (*mgo.ChangeInfo, error) {
|
||||
session, err := mm.TakeSession()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer mm.PutSession(session)
|
||||
|
||||
return fn(mm.GetCollection(session))
|
||||
}
|
||||
|
||||
func (mm *Model) execute(fn func(c Collection) error) error {
|
||||
session, err := mm.TakeSession()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer mm.PutSession(session)
|
||||
|
||||
return fn(mm.GetCollection(session))
|
||||
}
|
||||
|
||||
func (mm *Model) pipe(fn func(c Collection) Pipe) (Pipe, error) {
|
||||
session, err := mm.TakeSession()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer mm.PutSession(session)
|
||||
|
||||
return fn(mm.GetCollection(session)), nil
|
||||
}
|
||||
|
||||
func (mm *Model) query(fn func(c Collection) Query) (Query, error) {
|
||||
session, err := mm.TakeSession()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer mm.PutSession(session)
|
||||
|
||||
return fn(mm.GetCollection(session)), nil
|
||||
}
|
||||
|
||||
// WithTimeout customizes an operation with given timeout.
|
||||
func WithTimeout(timeout time.Duration) Option {
|
||||
return func(opts *options) {
|
||||
opts.timeout = timeout
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestWithTimeout(t *testing.T) {
|
||||
o := defaultOptions()
|
||||
WithTimeout(time.Second)(o)
|
||||
assert.Equal(t, time.Second, o.timeout)
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/syncx"
|
||||
)
|
||||
|
||||
var slowThreshold = syncx.ForAtomicDuration(defaultSlowThreshold)
|
||||
|
||||
type (
|
||||
options struct {
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// Option defines the method to customize a mongo model.
|
||||
Option func(opts *options)
|
||||
)
|
||||
|
||||
// SetSlowThreshold sets the slow threshold.
|
||||
func SetSlowThreshold(threshold time.Duration) {
|
||||
slowThreshold.Set(threshold)
|
||||
}
|
||||
|
||||
func defaultOptions() *options {
|
||||
return &options{
|
||||
timeout: defaultTimeout,
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSetSlowThreshold(t *testing.T) {
|
||||
assert.Equal(t, defaultSlowThreshold, slowThreshold.Load())
|
||||
SetSlowThreshold(time.Second)
|
||||
assert.Equal(t, time.Second, slowThreshold.Load())
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/zeromicro/go-zero/core/breaker"
|
||||
)
|
||||
|
||||
type (
|
||||
// Pipe interface represents a mongo pipe.
|
||||
Pipe interface {
|
||||
All(result interface{}) error
|
||||
AllowDiskUse() Pipe
|
||||
Batch(n int) Pipe
|
||||
Collation(collation *mgo.Collation) Pipe
|
||||
Explain(result interface{}) error
|
||||
Iter() Iter
|
||||
One(result interface{}) error
|
||||
SetMaxTime(d time.Duration) Pipe
|
||||
}
|
||||
|
||||
promisedPipe struct {
|
||||
*mgo.Pipe
|
||||
promise keepablePromise
|
||||
}
|
||||
|
||||
rejectedPipe struct{}
|
||||
)
|
||||
|
||||
func (p promisedPipe) All(result interface{}) error {
|
||||
return p.promise.keep(p.Pipe.All(result))
|
||||
}
|
||||
|
||||
func (p promisedPipe) AllowDiskUse() Pipe {
|
||||
p.Pipe.AllowDiskUse()
|
||||
return p
|
||||
}
|
||||
|
||||
func (p promisedPipe) Batch(n int) Pipe {
|
||||
p.Pipe.Batch(n)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p promisedPipe) Collation(collation *mgo.Collation) Pipe {
|
||||
p.Pipe.Collation(collation)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p promisedPipe) Explain(result interface{}) error {
|
||||
return p.promise.keep(p.Pipe.Explain(result))
|
||||
}
|
||||
|
||||
func (p promisedPipe) Iter() Iter {
|
||||
return promisedIter{
|
||||
Iter: p.Pipe.Iter(),
|
||||
promise: p.promise,
|
||||
}
|
||||
}
|
||||
|
||||
func (p promisedPipe) One(result interface{}) error {
|
||||
return p.promise.keep(p.Pipe.One(result))
|
||||
}
|
||||
|
||||
func (p promisedPipe) SetMaxTime(d time.Duration) Pipe {
|
||||
p.Pipe.SetMaxTime(d)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p rejectedPipe) All(result interface{}) error {
|
||||
return breaker.ErrServiceUnavailable
|
||||
}
|
||||
|
||||
func (p rejectedPipe) AllowDiskUse() Pipe {
|
||||
return p
|
||||
}
|
||||
|
||||
func (p rejectedPipe) Batch(n int) Pipe {
|
||||
return p
|
||||
}
|
||||
|
||||
func (p rejectedPipe) Collation(collation *mgo.Collation) Pipe {
|
||||
return p
|
||||
}
|
||||
|
||||
func (p rejectedPipe) Explain(result interface{}) error {
|
||||
return breaker.ErrServiceUnavailable
|
||||
}
|
||||
|
||||
func (p rejectedPipe) Iter() Iter {
|
||||
return rejectedIter{}
|
||||
}
|
||||
|
||||
func (p rejectedPipe) One(result interface{}) error {
|
||||
return breaker.ErrServiceUnavailable
|
||||
}
|
||||
|
||||
func (p rejectedPipe) SetMaxTime(d time.Duration) Pipe {
|
||||
return p
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/breaker"
|
||||
)
|
||||
|
||||
func TestRejectedPipe_All(t *testing.T) {
|
||||
assert.Equal(t, breaker.ErrServiceUnavailable, new(rejectedPipe).All(nil))
|
||||
}
|
||||
|
||||
func TestRejectedPipe_AllowDiskUse(t *testing.T) {
|
||||
var p rejectedPipe
|
||||
assert.Equal(t, p, p.AllowDiskUse())
|
||||
}
|
||||
|
||||
func TestRejectedPipe_Batch(t *testing.T) {
|
||||
var p rejectedPipe
|
||||
assert.Equal(t, p, p.Batch(1))
|
||||
}
|
||||
|
||||
func TestRejectedPipe_Collation(t *testing.T) {
|
||||
var p rejectedPipe
|
||||
assert.Equal(t, p, p.Collation(nil))
|
||||
}
|
||||
|
||||
func TestRejectedPipe_Explain(t *testing.T) {
|
||||
assert.Equal(t, breaker.ErrServiceUnavailable, new(rejectedPipe).Explain(nil))
|
||||
}
|
||||
|
||||
func TestRejectedPipe_Iter(t *testing.T) {
|
||||
assert.EqualValues(t, rejectedIter{}, new(rejectedPipe).Iter())
|
||||
}
|
||||
|
||||
func TestRejectedPipe_One(t *testing.T) {
|
||||
assert.Equal(t, breaker.ErrServiceUnavailable, new(rejectedPipe).One(nil))
|
||||
}
|
||||
|
||||
func TestRejectedPipe_SetMaxTime(t *testing.T) {
|
||||
var p rejectedPipe
|
||||
assert.Equal(t, p, p.SetMaxTime(0))
|
||||
}
|
||||
@@ -1,285 +0,0 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/zeromicro/go-zero/core/breaker"
|
||||
)
|
||||
|
||||
type (
|
||||
// Query interface represents a mongo query.
|
||||
Query interface {
|
||||
All(result interface{}) error
|
||||
Apply(change mgo.Change, result interface{}) (*mgo.ChangeInfo, error)
|
||||
Batch(n int) Query
|
||||
Collation(collation *mgo.Collation) Query
|
||||
Comment(comment string) Query
|
||||
Count() (int, error)
|
||||
Distinct(key string, result interface{}) error
|
||||
Explain(result interface{}) error
|
||||
For(result interface{}, f func() error) error
|
||||
Hint(indexKey ...string) Query
|
||||
Iter() Iter
|
||||
Limit(n int) Query
|
||||
LogReplay() Query
|
||||
MapReduce(job *mgo.MapReduce, result interface{}) (*mgo.MapReduceInfo, error)
|
||||
One(result interface{}) error
|
||||
Prefetch(p float64) Query
|
||||
Select(selector interface{}) Query
|
||||
SetMaxScan(n int) Query
|
||||
SetMaxTime(d time.Duration) Query
|
||||
Skip(n int) Query
|
||||
Snapshot() Query
|
||||
Sort(fields ...string) Query
|
||||
Tail(timeout time.Duration) Iter
|
||||
}
|
||||
|
||||
promisedQuery struct {
|
||||
*mgo.Query
|
||||
promise keepablePromise
|
||||
}
|
||||
|
||||
rejectedQuery struct{}
|
||||
)
|
||||
|
||||
func (q promisedQuery) All(result interface{}) error {
|
||||
return q.promise.keep(q.Query.All(result))
|
||||
}
|
||||
|
||||
func (q promisedQuery) Apply(change mgo.Change, result interface{}) (*mgo.ChangeInfo, error) {
|
||||
info, err := q.Query.Apply(change, result)
|
||||
return info, q.promise.keep(err)
|
||||
}
|
||||
|
||||
func (q promisedQuery) Batch(n int) Query {
|
||||
return promisedQuery{
|
||||
Query: q.Query.Batch(n),
|
||||
promise: q.promise,
|
||||
}
|
||||
}
|
||||
|
||||
func (q promisedQuery) Collation(collation *mgo.Collation) Query {
|
||||
return promisedQuery{
|
||||
Query: q.Query.Collation(collation),
|
||||
promise: q.promise,
|
||||
}
|
||||
}
|
||||
|
||||
func (q promisedQuery) Comment(comment string) Query {
|
||||
return promisedQuery{
|
||||
Query: q.Query.Comment(comment),
|
||||
promise: q.promise,
|
||||
}
|
||||
}
|
||||
|
||||
func (q promisedQuery) Count() (int, error) {
|
||||
v, err := q.Query.Count()
|
||||
return v, q.promise.keep(err)
|
||||
}
|
||||
|
||||
func (q promisedQuery) Distinct(key string, result interface{}) error {
|
||||
return q.promise.keep(q.Query.Distinct(key, result))
|
||||
}
|
||||
|
||||
func (q promisedQuery) Explain(result interface{}) error {
|
||||
return q.promise.keep(q.Query.Explain(result))
|
||||
}
|
||||
|
||||
func (q promisedQuery) For(result interface{}, f func() error) error {
|
||||
var ferr error
|
||||
err := q.Query.For(result, func() error {
|
||||
ferr = f()
|
||||
return ferr
|
||||
})
|
||||
if ferr == err {
|
||||
return q.promise.accept(err)
|
||||
}
|
||||
|
||||
return q.promise.keep(err)
|
||||
}
|
||||
|
||||
func (q promisedQuery) Hint(indexKey ...string) Query {
|
||||
return promisedQuery{
|
||||
Query: q.Query.Hint(indexKey...),
|
||||
promise: q.promise,
|
||||
}
|
||||
}
|
||||
|
||||
func (q promisedQuery) Iter() Iter {
|
||||
return promisedIter{
|
||||
Iter: q.Query.Iter(),
|
||||
promise: q.promise,
|
||||
}
|
||||
}
|
||||
|
||||
func (q promisedQuery) Limit(n int) Query {
|
||||
return promisedQuery{
|
||||
Query: q.Query.Limit(n),
|
||||
promise: q.promise,
|
||||
}
|
||||
}
|
||||
|
||||
func (q promisedQuery) LogReplay() Query {
|
||||
return promisedQuery{
|
||||
Query: q.Query.LogReplay(),
|
||||
promise: q.promise,
|
||||
}
|
||||
}
|
||||
|
||||
func (q promisedQuery) MapReduce(job *mgo.MapReduce, result interface{}) (*mgo.MapReduceInfo, error) {
|
||||
info, err := q.Query.MapReduce(job, result)
|
||||
return info, q.promise.keep(err)
|
||||
}
|
||||
|
||||
func (q promisedQuery) One(result interface{}) error {
|
||||
return q.promise.keep(q.Query.One(result))
|
||||
}
|
||||
|
||||
func (q promisedQuery) Prefetch(p float64) Query {
|
||||
return promisedQuery{
|
||||
Query: q.Query.Prefetch(p),
|
||||
promise: q.promise,
|
||||
}
|
||||
}
|
||||
|
||||
func (q promisedQuery) Select(selector interface{}) Query {
|
||||
return promisedQuery{
|
||||
Query: q.Query.Select(selector),
|
||||
promise: q.promise,
|
||||
}
|
||||
}
|
||||
|
||||
func (q promisedQuery) SetMaxScan(n int) Query {
|
||||
return promisedQuery{
|
||||
Query: q.Query.SetMaxScan(n),
|
||||
promise: q.promise,
|
||||
}
|
||||
}
|
||||
|
||||
func (q promisedQuery) SetMaxTime(d time.Duration) Query {
|
||||
return promisedQuery{
|
||||
Query: q.Query.SetMaxTime(d),
|
||||
promise: q.promise,
|
||||
}
|
||||
}
|
||||
|
||||
func (q promisedQuery) Skip(n int) Query {
|
||||
return promisedQuery{
|
||||
Query: q.Query.Skip(n),
|
||||
promise: q.promise,
|
||||
}
|
||||
}
|
||||
|
||||
func (q promisedQuery) Snapshot() Query {
|
||||
return promisedQuery{
|
||||
Query: q.Query.Snapshot(),
|
||||
promise: q.promise,
|
||||
}
|
||||
}
|
||||
|
||||
func (q promisedQuery) Sort(fields ...string) Query {
|
||||
return promisedQuery{
|
||||
Query: q.Query.Sort(fields...),
|
||||
promise: q.promise,
|
||||
}
|
||||
}
|
||||
|
||||
func (q promisedQuery) Tail(timeout time.Duration) Iter {
|
||||
return promisedIter{
|
||||
Iter: q.Query.Tail(timeout),
|
||||
promise: q.promise,
|
||||
}
|
||||
}
|
||||
|
||||
func (q rejectedQuery) All(result interface{}) error {
|
||||
return breaker.ErrServiceUnavailable
|
||||
}
|
||||
|
||||
func (q rejectedQuery) Apply(change mgo.Change, result interface{}) (*mgo.ChangeInfo, error) {
|
||||
return nil, breaker.ErrServiceUnavailable
|
||||
}
|
||||
|
||||
func (q rejectedQuery) Batch(n int) Query {
|
||||
return q
|
||||
}
|
||||
|
||||
func (q rejectedQuery) Collation(collation *mgo.Collation) Query {
|
||||
return q
|
||||
}
|
||||
|
||||
func (q rejectedQuery) Comment(comment string) Query {
|
||||
return q
|
||||
}
|
||||
|
||||
func (q rejectedQuery) Count() (int, error) {
|
||||
return 0, breaker.ErrServiceUnavailable
|
||||
}
|
||||
|
||||
func (q rejectedQuery) Distinct(key string, result interface{}) error {
|
||||
return breaker.ErrServiceUnavailable
|
||||
}
|
||||
|
||||
func (q rejectedQuery) Explain(result interface{}) error {
|
||||
return breaker.ErrServiceUnavailable
|
||||
}
|
||||
|
||||
func (q rejectedQuery) For(result interface{}, f func() error) error {
|
||||
return breaker.ErrServiceUnavailable
|
||||
}
|
||||
|
||||
func (q rejectedQuery) Hint(indexKey ...string) Query {
|
||||
return q
|
||||
}
|
||||
|
||||
func (q rejectedQuery) Iter() Iter {
|
||||
return rejectedIter{}
|
||||
}
|
||||
|
||||
func (q rejectedQuery) Limit(n int) Query {
|
||||
return q
|
||||
}
|
||||
|
||||
func (q rejectedQuery) LogReplay() Query {
|
||||
return q
|
||||
}
|
||||
|
||||
func (q rejectedQuery) MapReduce(job *mgo.MapReduce, result interface{}) (*mgo.MapReduceInfo, error) {
|
||||
return nil, breaker.ErrServiceUnavailable
|
||||
}
|
||||
|
||||
func (q rejectedQuery) One(result interface{}) error {
|
||||
return breaker.ErrServiceUnavailable
|
||||
}
|
||||
|
||||
func (q rejectedQuery) Prefetch(p float64) Query {
|
||||
return q
|
||||
}
|
||||
|
||||
func (q rejectedQuery) Select(selector interface{}) Query {
|
||||
return q
|
||||
}
|
||||
|
||||
func (q rejectedQuery) SetMaxScan(n int) Query {
|
||||
return q
|
||||
}
|
||||
|
||||
func (q rejectedQuery) SetMaxTime(d time.Duration) Query {
|
||||
return q
|
||||
}
|
||||
|
||||
func (q rejectedQuery) Skip(n int) Query {
|
||||
return q
|
||||
}
|
||||
|
||||
func (q rejectedQuery) Snapshot() Query {
|
||||
return q
|
||||
}
|
||||
|
||||
func (q rejectedQuery) Sort(fields ...string) Query {
|
||||
return q
|
||||
}
|
||||
|
||||
func (q rejectedQuery) Tail(timeout time.Duration) Iter {
|
||||
return rejectedIter{}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/breaker"
|
||||
)
|
||||
|
||||
func Test_rejectedQuery_All(t *testing.T) {
|
||||
assert.Equal(t, breaker.ErrServiceUnavailable, new(rejectedQuery).All(nil))
|
||||
}
|
||||
|
||||
func Test_rejectedQuery_Apply(t *testing.T) {
|
||||
info, err := new(rejectedQuery).Apply(mgo.Change{}, nil)
|
||||
assert.Equal(t, breaker.ErrServiceUnavailable, err)
|
||||
assert.Nil(t, info)
|
||||
}
|
||||
|
||||
func Test_rejectedQuery_Batch(t *testing.T) {
|
||||
var q rejectedQuery
|
||||
assert.Equal(t, q, q.Batch(1))
|
||||
}
|
||||
|
||||
func Test_rejectedQuery_Collation(t *testing.T) {
|
||||
var q rejectedQuery
|
||||
assert.Equal(t, q, q.Collation(nil))
|
||||
}
|
||||
|
||||
func Test_rejectedQuery_Comment(t *testing.T) {
|
||||
var q rejectedQuery
|
||||
assert.Equal(t, q, q.Comment(""))
|
||||
}
|
||||
|
||||
func Test_rejectedQuery_Count(t *testing.T) {
|
||||
n, err := new(rejectedQuery).Count()
|
||||
assert.Equal(t, breaker.ErrServiceUnavailable, err)
|
||||
assert.Equal(t, 0, n)
|
||||
}
|
||||
|
||||
func Test_rejectedQuery_Distinct(t *testing.T) {
|
||||
assert.Equal(t, breaker.ErrServiceUnavailable, new(rejectedQuery).Distinct("", nil))
|
||||
}
|
||||
|
||||
func Test_rejectedQuery_Explain(t *testing.T) {
|
||||
assert.Equal(t, breaker.ErrServiceUnavailable, new(rejectedQuery).Explain(nil))
|
||||
}
|
||||
|
||||
func Test_rejectedQuery_For(t *testing.T) {
|
||||
assert.Equal(t, breaker.ErrServiceUnavailable, new(rejectedQuery).For(nil, nil))
|
||||
}
|
||||
|
||||
func Test_rejectedQuery_Hint(t *testing.T) {
|
||||
var q rejectedQuery
|
||||
assert.Equal(t, q, q.Hint())
|
||||
}
|
||||
|
||||
func Test_rejectedQuery_Iter(t *testing.T) {
|
||||
assert.EqualValues(t, rejectedIter{}, new(rejectedQuery).Iter())
|
||||
}
|
||||
|
||||
func Test_rejectedQuery_Limit(t *testing.T) {
|
||||
var q rejectedQuery
|
||||
assert.Equal(t, q, q.Limit(1))
|
||||
}
|
||||
|
||||
func Test_rejectedQuery_LogReplay(t *testing.T) {
|
||||
var q rejectedQuery
|
||||
assert.Equal(t, q, q.LogReplay())
|
||||
}
|
||||
|
||||
func Test_rejectedQuery_MapReduce(t *testing.T) {
|
||||
info, err := new(rejectedQuery).MapReduce(nil, nil)
|
||||
assert.Equal(t, breaker.ErrServiceUnavailable, err)
|
||||
assert.Nil(t, info)
|
||||
}
|
||||
|
||||
func Test_rejectedQuery_One(t *testing.T) {
|
||||
assert.Equal(t, breaker.ErrServiceUnavailable, new(rejectedQuery).One(nil))
|
||||
}
|
||||
|
||||
func Test_rejectedQuery_Prefetch(t *testing.T) {
|
||||
var q rejectedQuery
|
||||
assert.Equal(t, q, q.Prefetch(1))
|
||||
}
|
||||
|
||||
func Test_rejectedQuery_Select(t *testing.T) {
|
||||
var q rejectedQuery
|
||||
assert.Equal(t, q, q.Select(nil))
|
||||
}
|
||||
|
||||
func Test_rejectedQuery_SetMaxScan(t *testing.T) {
|
||||
var q rejectedQuery
|
||||
assert.Equal(t, q, q.SetMaxScan(0))
|
||||
}
|
||||
|
||||
func Test_rejectedQuery_SetMaxTime(t *testing.T) {
|
||||
var q rejectedQuery
|
||||
assert.Equal(t, q, q.SetMaxTime(0))
|
||||
}
|
||||
|
||||
func Test_rejectedQuery_Skip(t *testing.T) {
|
||||
var q rejectedQuery
|
||||
assert.Equal(t, q, q.Skip(0))
|
||||
}
|
||||
|
||||
func Test_rejectedQuery_Snapshot(t *testing.T) {
|
||||
var q rejectedQuery
|
||||
assert.Equal(t, q, q.Snapshot())
|
||||
}
|
||||
|
||||
func Test_rejectedQuery_Sort(t *testing.T) {
|
||||
var q rejectedQuery
|
||||
assert.Equal(t, q, q.Sort())
|
||||
}
|
||||
|
||||
func Test_rejectedQuery_Tail(t *testing.T) {
|
||||
assert.EqualValues(t, rejectedIter{}, new(rejectedQuery).Tail(0))
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/syncx"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultConcurrency = 50
|
||||
defaultTimeout = time.Second
|
||||
)
|
||||
|
||||
var sessionManager = syncx.NewResourceManager()
|
||||
|
||||
type concurrentSession struct {
|
||||
*mgo.Session
|
||||
limit syncx.TimeoutLimit
|
||||
}
|
||||
|
||||
func (cs *concurrentSession) Close() error {
|
||||
cs.Session.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func getConcurrentSession(url string) (*concurrentSession, error) {
|
||||
val, err := sessionManager.GetResource(url, func() (io.Closer, error) {
|
||||
mgoSession, err := mgo.Dial(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
concurrentSess := &concurrentSession{
|
||||
Session: mgoSession,
|
||||
limit: syncx.NewTimeoutLimit(defaultConcurrency),
|
||||
}
|
||||
|
||||
return concurrentSess, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return val.(*concurrentSession), nil
|
||||
}
|
||||
|
||||
func (cs *concurrentSession) putSession(session *mgo.Session) {
|
||||
if err := cs.limit.Return(); err != nil {
|
||||
logx.Error(err)
|
||||
}
|
||||
|
||||
// anyway, we need to close the session
|
||||
session.Close()
|
||||
}
|
||||
|
||||
func (cs *concurrentSession) takeSession(opts ...Option) (*mgo.Session, error) {
|
||||
o := defaultOptions()
|
||||
for _, opt := range opts {
|
||||
opt(o)
|
||||
}
|
||||
|
||||
if err := cs.limit.Borrow(o.timeout); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cs.Copy(), nil
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package mongo
|
||||
|
||||
import "strings"
|
||||
|
||||
const mongoAddrSep = ","
|
||||
|
||||
// FormatAddr formats mongo hosts to a string.
|
||||
func FormatAddr(hosts []string) string {
|
||||
return strings.Join(hosts, mongoAddrSep)
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFormatAddrs(t *testing.T) {
|
||||
tests := []struct {
|
||||
addrs []string
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
addrs: []string{"a", "b"},
|
||||
expect: "a,b",
|
||||
},
|
||||
{
|
||||
addrs: []string{"a", "b", "c"},
|
||||
expect: "a,b,c",
|
||||
},
|
||||
{
|
||||
addrs: []string{},
|
||||
expect: "",
|
||||
},
|
||||
{
|
||||
addrs: nil,
|
||||
expect: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
assert.Equal(t, test.expect, FormatAddr(test.addrs))
|
||||
}
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
package mongoc
|
||||
|
||||
import (
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||
"github.com/zeromicro/go-zero/core/stores/mongo"
|
||||
"github.com/zeromicro/go-zero/core/syncx"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotFound is an alias of mgo.ErrNotFound.
|
||||
ErrNotFound = mgo.ErrNotFound
|
||||
|
||||
// can't use one SingleFlight per conn, because multiple conns may share the same cache key.
|
||||
singleFlight = syncx.NewSingleFlight()
|
||||
stats = cache.NewStat("mongoc")
|
||||
)
|
||||
|
||||
type (
|
||||
// QueryOption defines the method to customize a mongo query.
|
||||
QueryOption func(query mongo.Query) mongo.Query
|
||||
|
||||
// CachedCollection interface represents a mongo collection with cache.
|
||||
CachedCollection interface {
|
||||
Count(query interface{}) (int, error)
|
||||
DelCache(keys ...string) error
|
||||
FindAllNoCache(v, query interface{}, opts ...QueryOption) error
|
||||
FindOne(v interface{}, key string, query interface{}) error
|
||||
FindOneNoCache(v, query interface{}) error
|
||||
FindOneId(v interface{}, key string, id interface{}) error
|
||||
FindOneIdNoCache(v, id interface{}) error
|
||||
GetCache(key string, v interface{}) error
|
||||
Insert(docs ...interface{}) error
|
||||
Pipe(pipeline interface{}) mongo.Pipe
|
||||
Remove(selector interface{}, keys ...string) error
|
||||
RemoveNoCache(selector interface{}) error
|
||||
RemoveAll(selector interface{}, keys ...string) (*mgo.ChangeInfo, error)
|
||||
RemoveAllNoCache(selector interface{}) (*mgo.ChangeInfo, error)
|
||||
RemoveId(id interface{}, keys ...string) error
|
||||
RemoveIdNoCache(id interface{}) error
|
||||
SetCache(key string, v interface{}) error
|
||||
Update(selector, update interface{}, keys ...string) error
|
||||
UpdateNoCache(selector, update interface{}) error
|
||||
UpdateId(id, update interface{}, keys ...string) error
|
||||
UpdateIdNoCache(id, update interface{}) error
|
||||
Upsert(selector, update interface{}, keys ...string) (*mgo.ChangeInfo, error)
|
||||
UpsertNoCache(selector, update interface{}) (*mgo.ChangeInfo, error)
|
||||
}
|
||||
|
||||
cachedCollection struct {
|
||||
collection mongo.Collection
|
||||
cache cache.Cache
|
||||
}
|
||||
)
|
||||
|
||||
func newCollection(collection mongo.Collection, c cache.Cache) CachedCollection {
|
||||
return &cachedCollection{
|
||||
collection: collection,
|
||||
cache: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cachedCollection) Count(query interface{}) (int, error) {
|
||||
return c.collection.Find(query).Count()
|
||||
}
|
||||
|
||||
func (c *cachedCollection) DelCache(keys ...string) error {
|
||||
return c.cache.Del(keys...)
|
||||
}
|
||||
|
||||
func (c *cachedCollection) FindAllNoCache(v, query interface{}, opts ...QueryOption) error {
|
||||
q := c.collection.Find(query)
|
||||
for _, opt := range opts {
|
||||
q = opt(q)
|
||||
}
|
||||
return q.All(v)
|
||||
}
|
||||
|
||||
func (c *cachedCollection) FindOne(v interface{}, key string, query interface{}) error {
|
||||
return c.cache.Take(v, key, func(v interface{}) error {
|
||||
q := c.collection.Find(query)
|
||||
return q.One(v)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *cachedCollection) FindOneNoCache(v, query interface{}) error {
|
||||
q := c.collection.Find(query)
|
||||
return q.One(v)
|
||||
}
|
||||
|
||||
func (c *cachedCollection) FindOneId(v interface{}, key string, id interface{}) error {
|
||||
return c.cache.Take(v, key, func(v interface{}) error {
|
||||
q := c.collection.FindId(id)
|
||||
return q.One(v)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *cachedCollection) FindOneIdNoCache(v, id interface{}) error {
|
||||
q := c.collection.FindId(id)
|
||||
return q.One(v)
|
||||
}
|
||||
|
||||
func (c *cachedCollection) GetCache(key string, v interface{}) error {
|
||||
return c.cache.Get(key, v)
|
||||
}
|
||||
|
||||
func (c *cachedCollection) Insert(docs ...interface{}) error {
|
||||
return c.collection.Insert(docs...)
|
||||
}
|
||||
|
||||
func (c *cachedCollection) Pipe(pipeline interface{}) mongo.Pipe {
|
||||
return c.collection.Pipe(pipeline)
|
||||
}
|
||||
|
||||
func (c *cachedCollection) Remove(selector interface{}, keys ...string) error {
|
||||
if err := c.RemoveNoCache(selector); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.DelCache(keys...)
|
||||
}
|
||||
|
||||
func (c *cachedCollection) RemoveNoCache(selector interface{}) error {
|
||||
return c.collection.Remove(selector)
|
||||
}
|
||||
|
||||
func (c *cachedCollection) RemoveAll(selector interface{}, keys ...string) (*mgo.ChangeInfo, error) {
|
||||
info, err := c.RemoveAllNoCache(selector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.DelCache(keys...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (c *cachedCollection) RemoveAllNoCache(selector interface{}) (*mgo.ChangeInfo, error) {
|
||||
return c.collection.RemoveAll(selector)
|
||||
}
|
||||
|
||||
func (c *cachedCollection) RemoveId(id interface{}, keys ...string) error {
|
||||
if err := c.RemoveIdNoCache(id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.DelCache(keys...)
|
||||
}
|
||||
|
||||
func (c *cachedCollection) RemoveIdNoCache(id interface{}) error {
|
||||
return c.collection.RemoveId(id)
|
||||
}
|
||||
|
||||
func (c *cachedCollection) SetCache(key string, v interface{}) error {
|
||||
return c.cache.Set(key, v)
|
||||
}
|
||||
|
||||
func (c *cachedCollection) Update(selector, update interface{}, keys ...string) error {
|
||||
if err := c.UpdateNoCache(selector, update); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.DelCache(keys...)
|
||||
}
|
||||
|
||||
func (c *cachedCollection) UpdateNoCache(selector, update interface{}) error {
|
||||
return c.collection.Update(selector, update)
|
||||
}
|
||||
|
||||
func (c *cachedCollection) UpdateId(id, update interface{}, keys ...string) error {
|
||||
if err := c.UpdateIdNoCache(id, update); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.DelCache(keys...)
|
||||
}
|
||||
|
||||
func (c *cachedCollection) UpdateIdNoCache(id, update interface{}) error {
|
||||
return c.collection.UpdateId(id, update)
|
||||
}
|
||||
|
||||
func (c *cachedCollection) Upsert(selector, update interface{}, keys ...string) (*mgo.ChangeInfo, error) {
|
||||
info, err := c.UpsertNoCache(selector, update)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.DelCache(keys...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (c *cachedCollection) UpsertNoCache(selector, update interface{}) (*mgo.ChangeInfo, error) {
|
||||
return c.collection.Upsert(selector, update)
|
||||
}
|
||||
@@ -1,365 +0,0 @@
|
||||
package mongoc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/stat"
|
||||
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||
"github.com/zeromicro/go-zero/core/stores/mongo"
|
||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||
"github.com/zeromicro/go-zero/core/stores/redis/redistest"
|
||||
)
|
||||
|
||||
const dummyCount = 10
|
||||
|
||||
func init() {
|
||||
stat.SetReporter(nil)
|
||||
}
|
||||
|
||||
func TestCollection_Count(t *testing.T) {
|
||||
resetStats()
|
||||
r, clean, err := redistest.CreateRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
|
||||
cach := cache.NewNode(r, singleFlight, stats, mgo.ErrNotFound)
|
||||
c := newCollection(dummyConn{}, cach)
|
||||
val, err := c.Count("any")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, dummyCount, val)
|
||||
|
||||
var value string
|
||||
assert.Nil(t, r.Set("any", `"foo"`))
|
||||
assert.Nil(t, c.GetCache("any", &value))
|
||||
assert.Equal(t, "foo", value)
|
||||
assert.Nil(t, c.DelCache("any"))
|
||||
|
||||
assert.Nil(t, c.SetCache("any", "bar"))
|
||||
assert.Nil(t, c.FindAllNoCache(&value, "any", func(query mongo.Query) mongo.Query {
|
||||
return query
|
||||
}))
|
||||
assert.Nil(t, c.FindOne(&value, "any", "foo"))
|
||||
assert.Equal(t, "bar", value)
|
||||
assert.Nil(t, c.DelCache("any"))
|
||||
c = newCollection(dummyConn{val: `"bar"`}, cach)
|
||||
assert.Nil(t, c.FindOne(&value, "any", "foo"))
|
||||
assert.Equal(t, "bar", value)
|
||||
assert.Nil(t, c.FindOneNoCache(&value, "foo"))
|
||||
assert.Equal(t, "bar", value)
|
||||
assert.Nil(t, c.FindOneId(&value, "anyone", "foo"))
|
||||
assert.Equal(t, "bar", value)
|
||||
assert.Nil(t, c.FindOneIdNoCache(&value, "foo"))
|
||||
assert.Equal(t, "bar", value)
|
||||
assert.Nil(t, c.Insert("foo"))
|
||||
assert.Nil(t, c.Pipe("foo"))
|
||||
assert.Nil(t, c.Remove("any"))
|
||||
assert.Nil(t, c.RemoveId("any"))
|
||||
_, err = c.RemoveAll("any")
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, c.Update("foo", "bar"))
|
||||
assert.Nil(t, c.UpdateId("foo", "bar"))
|
||||
_, err = c.Upsert("foo", "bar")
|
||||
assert.Nil(t, err)
|
||||
|
||||
c = newCollection(dummyConn{
|
||||
val: `"bar"`,
|
||||
removeErr: errors.New("any"),
|
||||
}, cach)
|
||||
assert.NotNil(t, c.Remove("any"))
|
||||
_, err = c.RemoveAll("any", "bar")
|
||||
assert.NotNil(t, err)
|
||||
assert.NotNil(t, c.RemoveId("any"))
|
||||
|
||||
c = newCollection(dummyConn{
|
||||
val: `"bar"`,
|
||||
updateErr: errors.New("any"),
|
||||
}, cach)
|
||||
assert.NotNil(t, c.Update("foo", "bar"))
|
||||
assert.NotNil(t, c.UpdateId("foo", "bar"))
|
||||
_, err = c.Upsert("foo", "bar")
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestStat(t *testing.T) {
|
||||
resetStats()
|
||||
r, clean, err := redistest.CreateRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
|
||||
cach := cache.NewNode(r, singleFlight, stats, mgo.ErrNotFound)
|
||||
c := newCollection(dummyConn{}, cach).(*cachedCollection)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
var str string
|
||||
if err = c.cache.Take(&str, "name", func(v interface{}) error {
|
||||
*v.(*string) = "zero"
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
assert.Equal(t, uint64(10), atomic.LoadUint64(&stats.Total))
|
||||
assert.Equal(t, uint64(9), atomic.LoadUint64(&stats.Hit))
|
||||
}
|
||||
|
||||
func TestStatCacheFails(t *testing.T) {
|
||||
resetStats()
|
||||
log.SetOutput(io.Discard)
|
||||
defer log.SetOutput(os.Stdout)
|
||||
|
||||
r := redis.New("localhost:59999")
|
||||
cach := cache.NewNode(r, singleFlight, stats, mgo.ErrNotFound)
|
||||
c := newCollection(dummyConn{}, cach)
|
||||
|
||||
for i := 0; i < 20; i++ {
|
||||
var str string
|
||||
err := c.FindOne(&str, "name", bson.M{})
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, uint64(20), atomic.LoadUint64(&stats.Total))
|
||||
assert.Equal(t, uint64(0), atomic.LoadUint64(&stats.Hit))
|
||||
assert.Equal(t, uint64(20), atomic.LoadUint64(&stats.Miss))
|
||||
assert.Equal(t, uint64(0), atomic.LoadUint64(&stats.DbFails))
|
||||
}
|
||||
|
||||
func TestStatDbFails(t *testing.T) {
|
||||
resetStats()
|
||||
r, clean, err := redistest.CreateRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
|
||||
cach := cache.NewNode(r, singleFlight, stats, mgo.ErrNotFound)
|
||||
c := newCollection(dummyConn{}, cach).(*cachedCollection)
|
||||
|
||||
for i := 0; i < 20; i++ {
|
||||
var str string
|
||||
err := c.cache.Take(&str, "name", func(v interface{}) error {
|
||||
return errors.New("db failed")
|
||||
})
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, uint64(20), atomic.LoadUint64(&stats.Total))
|
||||
assert.Equal(t, uint64(0), atomic.LoadUint64(&stats.Hit))
|
||||
assert.Equal(t, uint64(20), atomic.LoadUint64(&stats.DbFails))
|
||||
}
|
||||
|
||||
func TestStatFromMemory(t *testing.T) {
|
||||
resetStats()
|
||||
r, clean, err := redistest.CreateRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
|
||||
cach := cache.NewNode(r, singleFlight, stats, mgo.ErrNotFound)
|
||||
c := newCollection(dummyConn{}, cach).(*cachedCollection)
|
||||
|
||||
var all sync.WaitGroup
|
||||
var wait sync.WaitGroup
|
||||
all.Add(10)
|
||||
wait.Add(4)
|
||||
go func() {
|
||||
var str string
|
||||
if err := c.cache.Take(&str, "name", func(v interface{}) error {
|
||||
*v.(*string) = "zero"
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
wait.Wait()
|
||||
runtime.Gosched()
|
||||
all.Done()
|
||||
}()
|
||||
|
||||
for i := 0; i < 4; i++ {
|
||||
go func() {
|
||||
var str string
|
||||
wait.Done()
|
||||
if err := c.cache.Take(&str, "name", func(v interface{}) error {
|
||||
*v.(*string) = "zero"
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
all.Done()
|
||||
}()
|
||||
}
|
||||
for i := 0; i < 5; i++ {
|
||||
go func() {
|
||||
var str string
|
||||
if err := c.cache.Take(&str, "name", func(v interface{}) error {
|
||||
*v.(*string) = "zero"
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
all.Done()
|
||||
}()
|
||||
}
|
||||
all.Wait()
|
||||
|
||||
assert.Equal(t, uint64(10), atomic.LoadUint64(&stats.Total))
|
||||
assert.Equal(t, uint64(9), atomic.LoadUint64(&stats.Hit))
|
||||
}
|
||||
|
||||
func resetStats() {
|
||||
atomic.StoreUint64(&stats.Total, 0)
|
||||
atomic.StoreUint64(&stats.Hit, 0)
|
||||
atomic.StoreUint64(&stats.Miss, 0)
|
||||
atomic.StoreUint64(&stats.DbFails, 0)
|
||||
}
|
||||
|
||||
type dummyConn struct {
|
||||
val string
|
||||
removeErr error
|
||||
updateErr error
|
||||
}
|
||||
|
||||
func (c dummyConn) Find(query interface{}) mongo.Query {
|
||||
return dummyQuery{val: c.val}
|
||||
}
|
||||
|
||||
func (c dummyConn) FindId(id interface{}) mongo.Query {
|
||||
return dummyQuery{val: c.val}
|
||||
}
|
||||
|
||||
func (c dummyConn) Insert(docs ...interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c dummyConn) Remove(selector interface{}) error {
|
||||
return c.removeErr
|
||||
}
|
||||
|
||||
func (dummyConn) Pipe(pipeline interface{}) mongo.Pipe {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c dummyConn) RemoveAll(selector interface{}) (*mgo.ChangeInfo, error) {
|
||||
return nil, c.removeErr
|
||||
}
|
||||
|
||||
func (c dummyConn) RemoveId(id interface{}) error {
|
||||
return c.removeErr
|
||||
}
|
||||
|
||||
func (c dummyConn) Update(selector, update interface{}) error {
|
||||
return c.updateErr
|
||||
}
|
||||
|
||||
func (c dummyConn) UpdateId(id, update interface{}) error {
|
||||
return c.updateErr
|
||||
}
|
||||
|
||||
func (c dummyConn) Upsert(selector, update interface{}) (*mgo.ChangeInfo, error) {
|
||||
return nil, c.updateErr
|
||||
}
|
||||
|
||||
type dummyQuery struct {
|
||||
val string
|
||||
}
|
||||
|
||||
func (d dummyQuery) All(result interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d dummyQuery) Apply(change mgo.Change, result interface{}) (*mgo.ChangeInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (d dummyQuery) Count() (int, error) {
|
||||
return dummyCount, nil
|
||||
}
|
||||
|
||||
func (d dummyQuery) Distinct(key string, result interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d dummyQuery) Explain(result interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d dummyQuery) For(result interface{}, f func() error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d dummyQuery) MapReduce(job *mgo.MapReduce, result interface{}) (*mgo.MapReduceInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (d dummyQuery) One(result interface{}) error {
|
||||
return json.Unmarshal([]byte(d.val), result)
|
||||
}
|
||||
|
||||
func (d dummyQuery) Batch(n int) mongo.Query {
|
||||
return d
|
||||
}
|
||||
|
||||
func (d dummyQuery) Collation(collation *mgo.Collation) mongo.Query {
|
||||
return d
|
||||
}
|
||||
|
||||
func (d dummyQuery) Comment(comment string) mongo.Query {
|
||||
return d
|
||||
}
|
||||
|
||||
func (d dummyQuery) Hint(indexKey ...string) mongo.Query {
|
||||
return d
|
||||
}
|
||||
|
||||
func (d dummyQuery) Iter() mongo.Iter {
|
||||
return &mgo.Iter{}
|
||||
}
|
||||
|
||||
func (d dummyQuery) Limit(n int) mongo.Query {
|
||||
return d
|
||||
}
|
||||
|
||||
func (d dummyQuery) LogReplay() mongo.Query {
|
||||
return d
|
||||
}
|
||||
|
||||
func (d dummyQuery) Prefetch(p float64) mongo.Query {
|
||||
return d
|
||||
}
|
||||
|
||||
func (d dummyQuery) Select(selector interface{}) mongo.Query {
|
||||
return d
|
||||
}
|
||||
|
||||
func (d dummyQuery) SetMaxScan(n int) mongo.Query {
|
||||
return d
|
||||
}
|
||||
|
||||
func (d dummyQuery) SetMaxTime(duration time.Duration) mongo.Query {
|
||||
return d
|
||||
}
|
||||
|
||||
func (d dummyQuery) Skip(n int) mongo.Query {
|
||||
return d
|
||||
}
|
||||
|
||||
func (d dummyQuery) Snapshot() mongo.Query {
|
||||
return d
|
||||
}
|
||||
|
||||
func (d dummyQuery) Sort(fields ...string) mongo.Query {
|
||||
return d
|
||||
}
|
||||
|
||||
func (d dummyQuery) Tail(timeout time.Duration) mongo.Iter {
|
||||
return &mgo.Iter{}
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
package mongoc
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||
"github.com/zeromicro/go-zero/core/stores/mongo"
|
||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||
)
|
||||
|
||||
// A Model is a mongo model that built with cache capability.
|
||||
type Model struct {
|
||||
*mongo.Model
|
||||
cache cache.Cache
|
||||
generateCollection func(*mgo.Session) CachedCollection
|
||||
}
|
||||
|
||||
// MustNewNodeModel returns a Model with a cache node, exists on errors.
|
||||
func MustNewNodeModel(url, collection string, rds *redis.Redis, opts ...cache.Option) *Model {
|
||||
model, err := NewNodeModel(url, collection, rds, opts...)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return model
|
||||
}
|
||||
|
||||
// MustNewModel returns a Model with a cache cluster, exists on errors.
|
||||
func MustNewModel(url, collection string, c cache.CacheConf, opts ...cache.Option) *Model {
|
||||
model, err := NewModel(url, collection, c, opts...)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return model
|
||||
}
|
||||
|
||||
// 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, singleFlight, stats, mgo.ErrNotFound, opts...)
|
||||
return NewModelWithCache(url, collection, c)
|
||||
}
|
||||
|
||||
// NewModelWithCache returns a Model with a custom cache.
|
||||
func NewModelWithCache(url, collection string, c cache.Cache) (*Model, error) {
|
||||
return createModel(url, collection, c, func(collection mongo.Collection) CachedCollection {
|
||||
return newCollection(collection, c)
|
||||
})
|
||||
}
|
||||
|
||||
// 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, singleFlight, stats, mgo.ErrNotFound, opts...)
|
||||
return NewModelWithCache(url, collection, c)
|
||||
}
|
||||
|
||||
// Count returns the count of given query.
|
||||
func (mm *Model) Count(query interface{}) (int, error) {
|
||||
return mm.executeInt(func(c CachedCollection) (int, error) {
|
||||
return c.Count(query)
|
||||
})
|
||||
}
|
||||
|
||||
// DelCache deletes the cache with given keys.
|
||||
func (mm *Model) DelCache(keys ...string) error {
|
||||
return mm.cache.Del(keys...)
|
||||
}
|
||||
|
||||
// GetCache unmarshal the cache into v with given key.
|
||||
func (mm *Model) GetCache(key string, v interface{}) error {
|
||||
return mm.cache.Get(key, v)
|
||||
}
|
||||
|
||||
// GetCollection returns a cache collection.
|
||||
func (mm *Model) GetCollection(session *mgo.Session) CachedCollection {
|
||||
return mm.generateCollection(session)
|
||||
}
|
||||
|
||||
// FindAllNoCache finds all records without cache.
|
||||
func (mm *Model) FindAllNoCache(v, query interface{}, opts ...QueryOption) error {
|
||||
return mm.execute(func(c CachedCollection) error {
|
||||
return c.FindAllNoCache(v, query, opts...)
|
||||
})
|
||||
}
|
||||
|
||||
// FindOne unmarshals a record into v with given key and query.
|
||||
func (mm *Model) FindOne(v interface{}, key string, query interface{}) error {
|
||||
return mm.execute(func(c CachedCollection) error {
|
||||
return c.FindOne(v, key, query)
|
||||
})
|
||||
}
|
||||
|
||||
// FindOneNoCache unmarshals a record into v with query, without cache.
|
||||
func (mm *Model) FindOneNoCache(v, query interface{}) error {
|
||||
return mm.execute(func(c CachedCollection) error {
|
||||
return c.FindOneNoCache(v, query)
|
||||
})
|
||||
}
|
||||
|
||||
// FindOneId unmarshals a record into v with query.
|
||||
func (mm *Model) FindOneId(v interface{}, key string, id interface{}) error {
|
||||
return mm.execute(func(c CachedCollection) error {
|
||||
return c.FindOneId(v, key, id)
|
||||
})
|
||||
}
|
||||
|
||||
// FindOneIdNoCache unmarshals a record into v with query, without cache.
|
||||
func (mm *Model) FindOneIdNoCache(v, id interface{}) error {
|
||||
return mm.execute(func(c CachedCollection) error {
|
||||
return c.FindOneIdNoCache(v, id)
|
||||
})
|
||||
}
|
||||
|
||||
// Insert inserts docs.
|
||||
func (mm *Model) Insert(docs ...interface{}) error {
|
||||
return mm.execute(func(c CachedCollection) error {
|
||||
return c.Insert(docs...)
|
||||
})
|
||||
}
|
||||
|
||||
// Pipe returns a mongo pipe with given pipeline.
|
||||
func (mm *Model) Pipe(pipeline interface{}) (mongo.Pipe, error) {
|
||||
return mm.pipe(func(c CachedCollection) mongo.Pipe {
|
||||
return c.Pipe(pipeline)
|
||||
})
|
||||
}
|
||||
|
||||
// Remove removes a record with given selector, and remove it from cache with given keys.
|
||||
func (mm *Model) Remove(selector interface{}, keys ...string) error {
|
||||
return mm.execute(func(c CachedCollection) error {
|
||||
return c.Remove(selector, keys...)
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveNoCache removes a record with given selector.
|
||||
func (mm *Model) RemoveNoCache(selector interface{}) error {
|
||||
return mm.execute(func(c CachedCollection) error {
|
||||
return c.RemoveNoCache(selector)
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveAll removes all records with given selector, and removes cache with given keys.
|
||||
func (mm *Model) RemoveAll(selector interface{}, keys ...string) (*mgo.ChangeInfo, error) {
|
||||
return mm.change(func(c CachedCollection) (*mgo.ChangeInfo, error) {
|
||||
return c.RemoveAll(selector, keys...)
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveAllNoCache removes all records with given selector, and returns a mgo.ChangeInfo.
|
||||
func (mm *Model) RemoveAllNoCache(selector interface{}) (*mgo.ChangeInfo, error) {
|
||||
return mm.change(func(c CachedCollection) (*mgo.ChangeInfo, error) {
|
||||
return c.RemoveAllNoCache(selector)
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveId removes a record with given id, and removes cache with given keys.
|
||||
func (mm *Model) RemoveId(id interface{}, keys ...string) error {
|
||||
return mm.execute(func(c CachedCollection) error {
|
||||
return c.RemoveId(id, keys...)
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveIdNoCache removes a record with given id.
|
||||
func (mm *Model) RemoveIdNoCache(id interface{}) error {
|
||||
return mm.execute(func(c CachedCollection) error {
|
||||
return c.RemoveIdNoCache(id)
|
||||
})
|
||||
}
|
||||
|
||||
// SetCache sets the cache with given key and value.
|
||||
func (mm *Model) SetCache(key string, v interface{}) error {
|
||||
return mm.cache.Set(key, v)
|
||||
}
|
||||
|
||||
// Update updates the record with given selector, and delete cache with given keys.
|
||||
func (mm *Model) Update(selector, update interface{}, keys ...string) error {
|
||||
return mm.execute(func(c CachedCollection) error {
|
||||
return c.Update(selector, update, keys...)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateNoCache updates the record with given selector.
|
||||
func (mm *Model) UpdateNoCache(selector, update interface{}) error {
|
||||
return mm.execute(func(c CachedCollection) error {
|
||||
return c.UpdateNoCache(selector, update)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateId updates the record with given id, and delete cache with given keys.
|
||||
func (mm *Model) UpdateId(id, update interface{}, keys ...string) error {
|
||||
return mm.execute(func(c CachedCollection) error {
|
||||
return c.UpdateId(id, update, keys...)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateIdNoCache updates the record with given id.
|
||||
func (mm *Model) UpdateIdNoCache(id, update interface{}) error {
|
||||
return mm.execute(func(c CachedCollection) error {
|
||||
return c.UpdateIdNoCache(id, update)
|
||||
})
|
||||
}
|
||||
|
||||
// Upsert upserts a record with given selector, and delete cache with given keys.
|
||||
func (mm *Model) Upsert(selector, update interface{}, keys ...string) (*mgo.ChangeInfo, error) {
|
||||
return mm.change(func(c CachedCollection) (*mgo.ChangeInfo, error) {
|
||||
return c.Upsert(selector, update, keys...)
|
||||
})
|
||||
}
|
||||
|
||||
// UpsertNoCache upserts a record with given selector.
|
||||
func (mm *Model) UpsertNoCache(selector, update interface{}) (*mgo.ChangeInfo, error) {
|
||||
return mm.change(func(c CachedCollection) (*mgo.ChangeInfo, error) {
|
||||
return c.UpsertNoCache(selector, update)
|
||||
})
|
||||
}
|
||||
|
||||
func (mm *Model) change(fn func(c CachedCollection) (*mgo.ChangeInfo, error)) (*mgo.ChangeInfo, error) {
|
||||
session, err := mm.TakeSession()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer mm.PutSession(session)
|
||||
|
||||
return fn(mm.GetCollection(session))
|
||||
}
|
||||
|
||||
func (mm *Model) execute(fn func(c CachedCollection) error) error {
|
||||
session, err := mm.TakeSession()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer mm.PutSession(session)
|
||||
|
||||
return fn(mm.GetCollection(session))
|
||||
}
|
||||
|
||||
func (mm *Model) executeInt(fn func(c CachedCollection) (int, error)) (int, error) {
|
||||
session, err := mm.TakeSession()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer mm.PutSession(session)
|
||||
|
||||
return fn(mm.GetCollection(session))
|
||||
}
|
||||
|
||||
func (mm *Model) pipe(fn func(c CachedCollection) mongo.Pipe) (mongo.Pipe, error) {
|
||||
session, err := mm.TakeSession()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer mm.PutSession(session)
|
||||
|
||||
return fn(mm.GetCollection(session)), nil
|
||||
}
|
||||
|
||||
func createModel(url, collection string, c cache.Cache,
|
||||
create func(mongo.Collection) CachedCollection) (*Model, error) {
|
||||
model, err := mongo.NewModel(url, collection)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Model{
|
||||
Model: model,
|
||||
cache: c,
|
||||
generateCollection: func(session *mgo.Session) CachedCollection {
|
||||
collection := model.GetCollection(session)
|
||||
return create(collection)
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -40,6 +40,16 @@ func TestRedisConf(t *testing.T) {
|
||||
},
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
name: "ok",
|
||||
RedisConf: RedisConf{
|
||||
Host: "localhost:6379",
|
||||
Type: ClusterType,
|
||||
Pass: "pwd",
|
||||
Tls: true,
|
||||
},
|
||||
ok: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -25,7 +25,7 @@ const spanName = "redis"
|
||||
|
||||
var (
|
||||
startTimeKey = contextKey("startTime")
|
||||
durationHook = hook{tracer: otel.GetTracerProvider().Tracer(trace.TraceName)}
|
||||
durationHook = hook{tracer: otel.Tracer(trace.TraceName)}
|
||||
redisCmdsAttributeKey = attribute.Key("redis.cmds")
|
||||
)
|
||||
|
||||
|
||||
@@ -91,13 +91,16 @@ func TestHookProcessPipelineCase1(t *testing.T) {
|
||||
log.SetOutput(&buf)
|
||||
defer log.SetOutput(writer)
|
||||
|
||||
_, err := durationHook.BeforeProcessPipeline(context.Background(), []red.Cmder{})
|
||||
assert.NoError(t, err)
|
||||
ctx, err := durationHook.BeforeProcessPipeline(context.Background(), []red.Cmder{
|
||||
red.NewCmd(context.Background()),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "redis", tracesdk.SpanFromContext(ctx).(interface{ Name() string }).Name())
|
||||
|
||||
assert.Nil(t, durationHook.AfterProcessPipeline(ctx, []red.Cmder{
|
||||
assert.NoError(t, durationHook.AfterProcessPipeline(ctx, []red.Cmder{}))
|
||||
assert.NoError(t, durationHook.AfterProcessPipeline(ctx, []red.Cmder{
|
||||
red.NewCmd(context.Background()),
|
||||
}))
|
||||
assert.False(t, strings.Contains(buf.String(), "slow"))
|
||||
|
||||
@@ -42,6 +42,12 @@ type (
|
||||
Score int64
|
||||
}
|
||||
|
||||
// A FloatPair is a key/pair for float set used in redis zet.
|
||||
FloatPair struct {
|
||||
Key string
|
||||
Score float64
|
||||
}
|
||||
|
||||
// Redis defines a redis node/cluster. It is thread-safe.
|
||||
Redis struct {
|
||||
Addr string
|
||||
@@ -786,6 +792,28 @@ func (s *Redis) HincrbyCtx(ctx context.Context, key, field string, increment int
|
||||
return
|
||||
}
|
||||
|
||||
// HincrbyFloat is the implementation of redis hincrby command.
|
||||
func (s *Redis) HincrbyFloat(key, field string, increment float64) (float64, error) {
|
||||
return s.HincrbyFloatCtx(context.Background(), key, field, increment)
|
||||
}
|
||||
|
||||
// HincrbyFloatCtx is the implementation of redis hincrby command.
|
||||
func (s *Redis) HincrbyFloatCtx(ctx context.Context, key, field string, increment float64) (val float64, err error) {
|
||||
err = s.brk.DoWithAcceptable(func() error {
|
||||
conn, err := getRedis(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
val, err = conn.HIncrByFloat(ctx, key, field, increment).Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Hkeys is the implementation of redis hkeys command.
|
||||
func (s *Redis) Hkeys(key string) ([]string, error) {
|
||||
return s.HkeysCtx(context.Background(), key)
|
||||
@@ -997,6 +1025,26 @@ func (s *Redis) IncrbyCtx(ctx context.Context, key string, increment int64) (val
|
||||
return
|
||||
}
|
||||
|
||||
// IncrbyFloat is the implementation of redis incrby command.
|
||||
func (s *Redis) IncrbyFloat(key string, increment float64) (float64, error) {
|
||||
return s.IncrbyFloatCtx(context.Background(), key, increment)
|
||||
}
|
||||
|
||||
// IncrbyFloatCtx is the implementation of redis incrby command.
|
||||
func (s *Redis) IncrbyFloatCtx(ctx context.Context, key string, increment float64) (val float64, err error) {
|
||||
err = s.brk.DoWithAcceptable(func() error {
|
||||
conn, err := getRedis(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val, err = conn.IncrByFloat(ctx, key, increment).Result()
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Keys is the implementation of redis keys command.
|
||||
func (s *Redis) Keys(pattern string) ([]string, error) {
|
||||
return s.KeysCtx(context.Background(), pattern)
|
||||
@@ -1849,17 +1897,17 @@ func (s *Redis) Zadd(key string, score int64, value string) (bool, error) {
|
||||
return s.ZaddCtx(context.Background(), key, score, value)
|
||||
}
|
||||
|
||||
// ZaddFloat is the implementation of redis zadd command.
|
||||
func (s *Redis) ZaddFloat(key string, score float64, value string) (bool, error) {
|
||||
return s.ZaddFloatCtx(context.Background(), key, score, value)
|
||||
}
|
||||
|
||||
// ZaddCtx is the implementation of redis zadd command.
|
||||
func (s *Redis) ZaddCtx(ctx context.Context, key string, score int64, value string) (
|
||||
val bool, err error) {
|
||||
return s.ZaddFloatCtx(ctx, key, float64(score), value)
|
||||
}
|
||||
|
||||
// ZaddFloat is the implementation of redis zadd command.
|
||||
func (s *Redis) ZaddFloat(key string, score float64, value string) (bool, error) {
|
||||
return s.ZaddFloatCtx(context.Background(), key, score, value)
|
||||
}
|
||||
|
||||
// ZaddFloatCtx is the implementation of redis zadd command.
|
||||
func (s *Redis) ZaddFloatCtx(ctx context.Context, key string, score float64, value string) (
|
||||
val bool, err error) {
|
||||
@@ -2017,6 +2065,47 @@ func (s *Redis) ZscoreCtx(ctx context.Context, key, value string) (val int64, er
|
||||
return
|
||||
}
|
||||
|
||||
// ZscoreByFloat is the implementation of redis zscore command score by float.
|
||||
func (s *Redis) ZscoreByFloat(key, value string) (float64, error) {
|
||||
return s.ZscoreByFloatCtx(context.Background(), key, value)
|
||||
}
|
||||
|
||||
// ZscoreByFloatCtx is the implementation of redis zscore command score by float.
|
||||
func (s *Redis) ZscoreByFloatCtx(ctx context.Context, key, value string) (val float64, err error) {
|
||||
err = s.brk.DoWithAcceptable(func() error {
|
||||
conn, err := getRedis(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
val, err = conn.ZScore(ctx, key, value).Result()
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Zscan is the implementation of redis zscan command.
|
||||
func (s *Redis) Zscan(key string, cursor uint64, match string, count int64) (
|
||||
keys []string, cur uint64, err error) {
|
||||
return s.ZscanCtx(context.Background(), key, cursor, match, count)
|
||||
}
|
||||
|
||||
// ZscanCtx is the implementation of redis zscan command.
|
||||
func (s *Redis) ZscanCtx(ctx context.Context, key string, cursor uint64, match string, count int64) (
|
||||
keys []string, cur uint64, err error) {
|
||||
err = s.brk.DoWithAcceptable(func() error {
|
||||
conn, err := getRedis(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keys, cur, err = conn.ZScan(ctx, key, cursor, match, count).Result()
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Zrank is the implementation of redis zrank command.
|
||||
func (s *Redis) Zrank(key, field string) (int64, error) {
|
||||
return s.ZrankCtx(context.Background(), key, field)
|
||||
@@ -2162,13 +2251,52 @@ func (s *Redis) ZrangeWithScoresCtx(ctx context.Context, key string, start, stop
|
||||
return
|
||||
}
|
||||
|
||||
// ZrangeWithScoresByFloat is the implementation of redis zrange command with scores by float64.
|
||||
func (s *Redis) ZrangeWithScoresByFloat(key string, start, stop int64) ([]FloatPair, error) {
|
||||
return s.ZrangeWithScoresByFloatCtx(context.Background(), key, start, stop)
|
||||
}
|
||||
|
||||
// ZrangeWithScoresByFloatCtx is the implementation of redis zrange command with scores by float64.
|
||||
func (s *Redis) ZrangeWithScoresByFloatCtx(ctx context.Context, key string, start, stop int64) (
|
||||
val []FloatPair, err error) {
|
||||
err = s.brk.DoWithAcceptable(func() error {
|
||||
conn, err := getRedis(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v, err := conn.ZRangeWithScores(ctx, key, start, stop).Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = toFloatPairs(v)
|
||||
return nil
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ZRevRangeWithScores is the implementation of redis zrevrange command with scores.
|
||||
// Deprecated: use ZrevrangeWithScores instead.
|
||||
func (s *Redis) ZRevRangeWithScores(key string, start, stop int64) ([]Pair, error) {
|
||||
return s.ZRevRangeWithScoresCtx(context.Background(), key, start, stop)
|
||||
return s.ZrevrangeWithScoresCtx(context.Background(), key, start, stop)
|
||||
}
|
||||
|
||||
// ZrevrangeWithScores is the implementation of redis zrevrange command with scores.
|
||||
func (s *Redis) ZrevrangeWithScores(key string, start, stop int64) ([]Pair, error) {
|
||||
return s.ZrevrangeWithScoresCtx(context.Background(), key, start, stop)
|
||||
}
|
||||
|
||||
// ZRevRangeWithScoresCtx is the implementation of redis zrevrange command with scores.
|
||||
// Deprecated: use ZrevrangeWithScoresCtx instead.
|
||||
func (s *Redis) ZRevRangeWithScoresCtx(ctx context.Context, key string, start, stop int64) (
|
||||
val []Pair, err error) {
|
||||
return s.ZrevrangeWithScoresCtx(ctx, key, start, stop)
|
||||
}
|
||||
|
||||
// ZrevrangeWithScoresCtx is the implementation of redis zrevrange command with scores.
|
||||
func (s *Redis) ZrevrangeWithScoresCtx(ctx context.Context, key string, start, stop int64) (
|
||||
val []Pair, err error) {
|
||||
err = s.brk.DoWithAcceptable(func() error {
|
||||
conn, err := getRedis(s)
|
||||
@@ -2188,6 +2316,45 @@ func (s *Redis) ZRevRangeWithScoresCtx(ctx context.Context, key string, start, s
|
||||
return
|
||||
}
|
||||
|
||||
// ZRevRangeWithScoresByFloat is the implementation of redis zrevrange command with scores by float.
|
||||
// Deprecated: use ZrevrangeWithScoresByFloat instead.
|
||||
func (s *Redis) ZRevRangeWithScoresByFloat(key string, start, stop int64) ([]FloatPair, error) {
|
||||
return s.ZrevrangeWithScoresByFloatCtx(context.Background(), key, start, stop)
|
||||
}
|
||||
|
||||
// ZrevrangeWithScoresByFloat is the implementation of redis zrevrange command with scores by float.
|
||||
func (s *Redis) ZrevrangeWithScoresByFloat(key string, start, stop int64) ([]FloatPair, error) {
|
||||
return s.ZrevrangeWithScoresByFloatCtx(context.Background(), key, start, stop)
|
||||
}
|
||||
|
||||
// ZRevRangeWithScoresByFloatCtx is the implementation of redis zrevrange command with scores by float.
|
||||
// Deprecated: use ZrevrangeWithScoresByFloatCtx instead.
|
||||
func (s *Redis) ZRevRangeWithScoresByFloatCtx(ctx context.Context, key string, start, stop int64) (
|
||||
val []FloatPair, err error) {
|
||||
return s.ZrevrangeWithScoresByFloatCtx(ctx, key, start, stop)
|
||||
}
|
||||
|
||||
// ZrevrangeWithScoresByFloatCtx is the implementation of redis zrevrange command with scores by float.
|
||||
func (s *Redis) ZrevrangeWithScoresByFloatCtx(ctx context.Context, key string, start, stop int64) (
|
||||
val []FloatPair, err error) {
|
||||
err = s.brk.DoWithAcceptable(func() error {
|
||||
conn, err := getRedis(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v, err := conn.ZRevRangeWithScores(ctx, key, start, stop).Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = toFloatPairs(v)
|
||||
return nil
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ZrangebyscoreWithScores is the implementation of redis zrangebyscore command with scores.
|
||||
func (s *Redis) ZrangebyscoreWithScores(key string, start, stop int64) ([]Pair, error) {
|
||||
return s.ZrangebyscoreWithScoresCtx(context.Background(), key, start, stop)
|
||||
@@ -2217,6 +2384,35 @@ func (s *Redis) ZrangebyscoreWithScoresCtx(ctx context.Context, key string, star
|
||||
return
|
||||
}
|
||||
|
||||
// ZrangebyscoreWithScoresByFloat is the implementation of redis zrangebyscore command with scores by float.
|
||||
func (s *Redis) ZrangebyscoreWithScoresByFloat(key string, start, stop float64) ([]FloatPair, error) {
|
||||
return s.ZrangebyscoreWithScoresByFloatCtx(context.Background(), key, start, stop)
|
||||
}
|
||||
|
||||
// ZrangebyscoreWithScoresByFloatCtx is the implementation of redis zrangebyscore command with scores by float.
|
||||
func (s *Redis) ZrangebyscoreWithScoresByFloatCtx(ctx context.Context, key string, start, stop float64) (
|
||||
val []FloatPair, err error) {
|
||||
err = s.brk.DoWithAcceptable(func() error {
|
||||
conn, err := getRedis(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v, err := conn.ZRangeByScoreWithScores(ctx, key, &red.ZRangeBy{
|
||||
Min: strconv.FormatFloat(start, 'f', -1, 64),
|
||||
Max: strconv.FormatFloat(stop, 'f', -1, 64),
|
||||
}).Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = toFloatPairs(v)
|
||||
return nil
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ZrangebyscoreWithScoresAndLimit is the implementation of redis zrangebyscore command
|
||||
// with scores and limit.
|
||||
func (s *Redis) ZrangebyscoreWithScoresAndLimit(key string, start, stop int64,
|
||||
@@ -2255,6 +2451,45 @@ func (s *Redis) ZrangebyscoreWithScoresAndLimitCtx(ctx context.Context, key stri
|
||||
return
|
||||
}
|
||||
|
||||
// ZrangebyscoreWithScoresByFloatAndLimit is the implementation of redis zrangebyscore command
|
||||
// with scores by float and limit.
|
||||
func (s *Redis) ZrangebyscoreWithScoresByFloatAndLimit(key string, start, stop float64,
|
||||
page, size int) ([]FloatPair, error) {
|
||||
return s.ZrangebyscoreWithScoresByFloatAndLimitCtx(context.Background(),
|
||||
key, start, stop, page, size)
|
||||
}
|
||||
|
||||
// ZrangebyscoreWithScoresByFloatAndLimitCtx is the implementation of redis zrangebyscore command
|
||||
// with scores by float and limit.
|
||||
func (s *Redis) ZrangebyscoreWithScoresByFloatAndLimitCtx(ctx context.Context, key string, start,
|
||||
stop float64, page, size int) (val []FloatPair, err error) {
|
||||
err = s.brk.DoWithAcceptable(func() error {
|
||||
if size <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
conn, err := getRedis(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v, err := conn.ZRangeByScoreWithScores(ctx, key, &red.ZRangeBy{
|
||||
Min: strconv.FormatFloat(start, 'f', -1, 64),
|
||||
Max: strconv.FormatFloat(stop, 'f', -1, 64),
|
||||
Offset: int64(page * size),
|
||||
Count: int64(size),
|
||||
}).Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = toFloatPairs(v)
|
||||
return nil
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Zrevrange is the implementation of redis zrevrange command.
|
||||
func (s *Redis) Zrevrange(key string, start, stop int64) ([]string, error) {
|
||||
return s.ZrevrangeCtx(context.Background(), key, start, stop)
|
||||
@@ -2305,11 +2540,42 @@ func (s *Redis) ZrevrangebyscoreWithScoresCtx(ctx context.Context, key string, s
|
||||
return
|
||||
}
|
||||
|
||||
// ZrevrangebyscoreWithScoresByFloat is the implementation of redis zrevrangebyscore command with scores by float.
|
||||
func (s *Redis) ZrevrangebyscoreWithScoresByFloat(key string, start, stop float64) (
|
||||
[]FloatPair, error) {
|
||||
return s.ZrevrangebyscoreWithScoresByFloatCtx(context.Background(), key, start, stop)
|
||||
}
|
||||
|
||||
// ZrevrangebyscoreWithScoresByFloatCtx is the implementation of redis zrevrangebyscore command with scores by float.
|
||||
func (s *Redis) ZrevrangebyscoreWithScoresByFloatCtx(ctx context.Context, key string,
|
||||
start, stop float64) (val []FloatPair, err error) {
|
||||
err = s.brk.DoWithAcceptable(func() error {
|
||||
conn, err := getRedis(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v, err := conn.ZRevRangeByScoreWithScores(ctx, key, &red.ZRangeBy{
|
||||
Min: strconv.FormatFloat(start, 'f', -1, 64),
|
||||
Max: strconv.FormatFloat(stop, 'f', -1, 64),
|
||||
}).Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = toFloatPairs(v)
|
||||
return nil
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ZrevrangebyscoreWithScoresAndLimit is the implementation of redis zrevrangebyscore command
|
||||
// with scores and limit.
|
||||
func (s *Redis) ZrevrangebyscoreWithScoresAndLimit(key string, start, stop int64,
|
||||
page, size int) ([]Pair, error) {
|
||||
return s.ZrevrangebyscoreWithScoresAndLimitCtx(context.Background(), key, start, stop, page, size)
|
||||
return s.ZrevrangebyscoreWithScoresAndLimitCtx(context.Background(),
|
||||
key, start, stop, page, size)
|
||||
}
|
||||
|
||||
// ZrevrangebyscoreWithScoresAndLimitCtx is the implementation of redis zrevrangebyscore command
|
||||
@@ -2343,6 +2609,45 @@ func (s *Redis) ZrevrangebyscoreWithScoresAndLimitCtx(ctx context.Context, key s
|
||||
return
|
||||
}
|
||||
|
||||
// ZrevrangebyscoreWithScoresByFloatAndLimit is the implementation of redis zrevrangebyscore command
|
||||
// with scores by float and limit.
|
||||
func (s *Redis) ZrevrangebyscoreWithScoresByFloatAndLimit(key string, start, stop float64,
|
||||
page, size int) ([]FloatPair, error) {
|
||||
return s.ZrevrangebyscoreWithScoresByFloatAndLimitCtx(context.Background(),
|
||||
key, start, stop, page, size)
|
||||
}
|
||||
|
||||
// ZrevrangebyscoreWithScoresByFloatAndLimitCtx is the implementation of redis zrevrangebyscore command
|
||||
// with scores by float and limit.
|
||||
func (s *Redis) ZrevrangebyscoreWithScoresByFloatAndLimitCtx(ctx context.Context, key string,
|
||||
start, stop float64, page, size int) (val []FloatPair, err error) {
|
||||
err = s.brk.DoWithAcceptable(func() error {
|
||||
if size <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
conn, err := getRedis(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v, err := conn.ZRevRangeByScoreWithScores(ctx, key, &red.ZRangeBy{
|
||||
Min: strconv.FormatFloat(start, 'f', -1, 64),
|
||||
Max: strconv.FormatFloat(stop, 'f', -1, 64),
|
||||
Offset: int64(page * size),
|
||||
Count: int64(size),
|
||||
}).Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = toFloatPairs(v)
|
||||
return nil
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Zrevrank is the implementation of redis zrevrank command.
|
||||
func (s *Redis) Zrevrank(key, field string) (int64, error) {
|
||||
return s.ZrevrankCtx(context.Background(), key, field)
|
||||
@@ -2444,19 +2749,43 @@ func toPairs(vals []red.Z) []Pair {
|
||||
return pairs
|
||||
}
|
||||
|
||||
func toStrings(vals []interface{}) []string {
|
||||
ret := make([]string, len(vals))
|
||||
func toFloatPairs(vals []red.Z) []FloatPair {
|
||||
pairs := make([]FloatPair, len(vals))
|
||||
|
||||
for i, val := range vals {
|
||||
if val == nil {
|
||||
ret[i] = ""
|
||||
} else {
|
||||
switch val := val.(type) {
|
||||
case string:
|
||||
ret[i] = val
|
||||
default:
|
||||
ret[i] = mapping.Repr(val)
|
||||
switch member := val.Member.(type) {
|
||||
case string:
|
||||
pairs[i] = FloatPair{
|
||||
Key: member,
|
||||
Score: val.Score,
|
||||
}
|
||||
default:
|
||||
pairs[i] = FloatPair{
|
||||
Key: mapping.Repr(val.Member),
|
||||
Score: val.Score,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pairs
|
||||
}
|
||||
|
||||
func toStrings(vals []interface{}) []string {
|
||||
ret := make([]string, len(vals))
|
||||
|
||||
for i, val := range vals {
|
||||
if val == nil {
|
||||
ret[i] = ""
|
||||
continue
|
||||
}
|
||||
|
||||
switch val := val.(type) {
|
||||
case string:
|
||||
ret[i] = val
|
||||
default:
|
||||
ret[i] = mapping.Repr(val)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,12 +8,39 @@ import (
|
||||
)
|
||||
|
||||
func TestBlockingNode(t *testing.T) {
|
||||
r, err := miniredis.Run()
|
||||
assert.Nil(t, err)
|
||||
node, err := CreateBlockingNode(New(r.Addr()))
|
||||
assert.Nil(t, err)
|
||||
node.Close()
|
||||
node, err = CreateBlockingNode(New(r.Addr(), Cluster()))
|
||||
assert.Nil(t, err)
|
||||
node.Close()
|
||||
t.Run("test blocking node", func(t *testing.T) {
|
||||
r, err := miniredis.Run()
|
||||
assert.NoError(t, err)
|
||||
defer r.Close()
|
||||
|
||||
node, err := CreateBlockingNode(New(r.Addr()))
|
||||
assert.NoError(t, err)
|
||||
node.Close()
|
||||
// close again to make sure it's safe
|
||||
assert.NotPanics(t, func() {
|
||||
node.Close()
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("test blocking node with cluster", func(t *testing.T) {
|
||||
r, err := miniredis.Run()
|
||||
assert.NoError(t, err)
|
||||
defer r.Close()
|
||||
|
||||
node, err := CreateBlockingNode(New(r.Addr(), Cluster(), WithTLS()))
|
||||
assert.NoError(t, err)
|
||||
node.Close()
|
||||
assert.NotPanics(t, func() {
|
||||
node.Close()
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("test blocking node with bad type", func(t *testing.T) {
|
||||
r, err := miniredis.Run()
|
||||
assert.NoError(t, err)
|
||||
defer r.Close()
|
||||
|
||||
_, err = CreateBlockingNode(New(r.Addr(), badType()))
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||
)
|
||||
|
||||
// CreateRedis returns a in process redis.Redis.
|
||||
// CreateRedis returns an in process redis.Redis.
|
||||
func CreateRedis() (r *redis.Redis, clean func(), err error) {
|
||||
mr, err := miniredis.Run()
|
||||
if err != nil {
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
var sqlAttributeKey = attribute.Key("sql.method")
|
||||
|
||||
func startSpan(ctx context.Context, method string) (context.Context, oteltrace.Span) {
|
||||
tracer := otel.GetTracerProvider().Tracer(trace.TraceName)
|
||||
tracer := otel.Tracer(trace.TraceName)
|
||||
start, span := tracer.Start(ctx,
|
||||
spanName,
|
||||
oteltrace.WithSpanKind(oteltrace.SpanKindClient),
|
||||
|
||||
@@ -2,6 +2,7 @@ package stringx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unicode"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/lang"
|
||||
)
|
||||
@@ -69,6 +70,33 @@ func HasEmpty(args ...string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Join joins any number of elements into a single string, separating them with given sep.
|
||||
// Empty elements are ignored. However, if the argument list is empty or all its elements are empty,
|
||||
// Join returns an empty string.
|
||||
func Join(sep byte, elem ...string) string {
|
||||
var size int
|
||||
for _, e := range elem {
|
||||
size += len(e)
|
||||
}
|
||||
if size == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
buf := make([]byte, 0, size+len(elem)-1)
|
||||
for _, e := range elem {
|
||||
if len(e) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(buf) > 0 {
|
||||
buf = append(buf, sep)
|
||||
}
|
||||
buf = append(buf, e...)
|
||||
}
|
||||
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
// NotEmpty checks if all strings are not empty in args.
|
||||
func NotEmpty(args ...string) bool {
|
||||
return !HasEmpty(args...)
|
||||
@@ -141,6 +169,15 @@ func TakeWithPriority(fns ...func() string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ToCamelCase returns the string that converts the first letter to lowercase.
|
||||
func ToCamelCase(s string) string {
|
||||
for i, v := range s {
|
||||
return string(unicode.ToLower(v)) + s[i+1:]
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// Union merges the strings in first and second.
|
||||
func Union(first, second []string) []string {
|
||||
set := make(map[string]lang.PlaceholderType)
|
||||
|
||||
@@ -147,6 +147,42 @@ func TestFirstN(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestJoin(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []string
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
name: "all blanks",
|
||||
input: []string{"", ""},
|
||||
expect: "",
|
||||
},
|
||||
{
|
||||
name: "two values",
|
||||
input: []string{"012", "abc"},
|
||||
expect: "012.abc",
|
||||
},
|
||||
{
|
||||
name: "last blank",
|
||||
input: []string{"abc", ""},
|
||||
expect: "abc",
|
||||
},
|
||||
{
|
||||
name: "first blank",
|
||||
input: []string{"", "abc"},
|
||||
expect: "abc",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expect, Join('.', test.input...))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
cases := []struct {
|
||||
input []string
|
||||
@@ -360,6 +396,61 @@ func TestTakeWithPriority(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestToCamelCase(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
input: "",
|
||||
expect: "",
|
||||
},
|
||||
{
|
||||
input: "A",
|
||||
expect: "a",
|
||||
},
|
||||
{
|
||||
input: "a",
|
||||
expect: "a",
|
||||
},
|
||||
{
|
||||
input: "hello_world",
|
||||
expect: "hello_world",
|
||||
},
|
||||
{
|
||||
input: "Hello_world",
|
||||
expect: "hello_world",
|
||||
},
|
||||
{
|
||||
input: "hello_World",
|
||||
expect: "hello_World",
|
||||
},
|
||||
{
|
||||
input: "helloWorld",
|
||||
expect: "helloWorld",
|
||||
},
|
||||
{
|
||||
input: "HelloWorld",
|
||||
expect: "helloWorld",
|
||||
},
|
||||
{
|
||||
input: "hello World",
|
||||
expect: "hello World",
|
||||
},
|
||||
{
|
||||
input: "Hello World",
|
||||
expect: "hello World",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.input, func(t *testing.T) {
|
||||
assert.Equal(t, test.expect, ToCamelCase(test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnion(t *testing.T) {
|
||||
first := []string{
|
||||
"one",
|
||||
|
||||
@@ -2,7 +2,7 @@ package syncx
|
||||
|
||||
import "sync/atomic"
|
||||
|
||||
// A OnceGuard is used to make sure a resource can be taken once.
|
||||
// An OnceGuard is used to make sure a resource can be taken once.
|
||||
type OnceGuard struct {
|
||||
done uint32
|
||||
}
|
||||
|
||||
@@ -10,17 +10,18 @@ import (
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/exporters/jaeger"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
|
||||
"go.opentelemetry.io/otel/exporters/zipkin"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
const (
|
||||
kindJaeger = "jaeger"
|
||||
kindZipkin = "zipkin"
|
||||
kindGrpc = "grpc"
|
||||
kindJaeger = "jaeger"
|
||||
kindZipkin = "zipkin"
|
||||
kindOtlpGrpc = "otlpgrpc"
|
||||
kindOtlpHttp = "otlphttp"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -29,7 +30,7 @@ var (
|
||||
tp *sdktrace.TracerProvider
|
||||
)
|
||||
|
||||
// StartAgent starts a opentelemetry agent.
|
||||
// StartAgent starts an opentelemetry agent.
|
||||
func StartAgent(c Config) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
@@ -59,12 +60,24 @@ func createExporter(c Config) (sdktrace.SpanExporter, error) {
|
||||
return jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(c.Endpoint)))
|
||||
case kindZipkin:
|
||||
return zipkin.New(c.Endpoint)
|
||||
case kindGrpc:
|
||||
return otlptracegrpc.NewUnstarted(
|
||||
case kindOtlpGrpc:
|
||||
// Always treat trace exporter as optional component, so we use nonblock here,
|
||||
// otherwise this would slow down app start up even set a dial timeout here when
|
||||
// endpoint can not reach.
|
||||
// If the connection not dial success, the global otel ErrorHandler will catch error
|
||||
// when reporting data like other exporters.
|
||||
return otlptracegrpc.New(
|
||||
context.Background(),
|
||||
otlptracegrpc.WithInsecure(),
|
||||
otlptracegrpc.WithEndpoint(c.Endpoint),
|
||||
otlptracegrpc.WithDialOption(grpc.WithBlock()),
|
||||
), nil
|
||||
)
|
||||
case kindOtlpHttp:
|
||||
// Not support flexible configuration now.
|
||||
return otlptracehttp.New(
|
||||
context.Background(),
|
||||
otlptracehttp.WithInsecure(),
|
||||
otlptracehttp.WithEndpoint(c.Endpoint),
|
||||
)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown exporter: %s", c.Batcher)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user