Merge branch 'develop' into frt/supports_for_4144

# Conflicts:
#	client/orm/orm_queryset.go
This commit is contained in:
Anker Jam
2021-01-09 21:16:27 +08:00
317 changed files with 3113 additions and 1662 deletions

View File

@@ -1,37 +1,34 @@
## cache
cache is a Go cache manager. It can use many cache adapters. The repo is inspired by `database/sql` .
cache is a Go cache manager. It can use many cache adapters. The repo is inspired by `database/sql` .
## How to install?
go get github.com/astaxie/beego/cache
go get github.com/beego/beego/v2/cache
## What adapters are supported?
As of now this cache support memory, Memcache and Redis.
## How to use it?
First you must import it
import (
"github.com/astaxie/beego/cache"
"github.com/beego/beego/v2/cache"
)
Then init a Cache (example with memory adapter)
bm, err := cache.NewCache("memory", `{"interval":60}`)
Use it like this:
Use it like this:
bm.Put("astaxie", 1, 10 * time.Second)
bm.Get("astaxie")
bm.IsExist("astaxie")
bm.Delete("astaxie")
## Memory adapter
Configure memory adapter like this:
@@ -40,7 +37,6 @@ Configure memory adapter like this:
interval means the gc time. The cache will check at each time interval, whether item has expired.
## Memcache adapter
Memcache adapter use the [gomemcache](http://github.com/bradfitz/gomemcache) client.
@@ -49,7 +45,6 @@ Configure like this:
{"conn":"127.0.0.1:11211"}
## Redis adapter
Redis adapter use the [redigo](http://github.com/gomodule/redigo) client.

View File

@@ -16,7 +16,7 @@
// Usage:
//
// import(
// "github.com/astaxie/beego/cache"
// "github.com/beego/beego/v2/cache"
// )
//
// bm, err := cache.NewCache("memory", `{"interval":60}`)

View File

@@ -16,6 +16,7 @@ package cache
import (
"context"
"math"
"os"
"sync"
"testing"
@@ -46,11 +47,11 @@ func TestCacheIncr(t *testing.T) {
}
func TestCache(t *testing.T) {
bm, err := NewCache("memory", `{"interval":20}`)
bm, err := NewCache("memory", `{"interval":1}`)
if err != nil {
t.Error("init err")
}
timeoutDuration := 10 * time.Second
timeoutDuration := 5 * time.Second
if err = bm.Put(context.Background(), "astaxie", 1, timeoutDuration); err != nil {
t.Error("set Error", err)
}
@@ -62,7 +63,7 @@ func TestCache(t *testing.T) {
t.Error("get err")
}
time.Sleep(30 * time.Second)
time.Sleep(7 * time.Second)
if res, _ := bm.IsExist(context.Background(), "astaxie"); res {
t.Error("check err")
@@ -72,21 +73,13 @@ func TestCache(t *testing.T) {
t.Error("set Error", err)
}
if err = bm.Incr(context.Background(), "astaxie"); err != nil {
t.Error("Incr Error", err)
}
// test different integer type for incr & decr
testMultiTypeIncrDecr(t, bm, timeoutDuration)
if v, _ := bm.Get(context.Background(), "astaxie"); v.(int) != 2 {
t.Error("get err")
}
// test overflow of incr&decr
testIncrOverFlow(t, bm, timeoutDuration)
testDecrOverFlow(t, bm, timeoutDuration)
if err = bm.Decr(context.Background(), "astaxie"); err != nil {
t.Error("Decr Error", err)
}
if v, _ := bm.Get(context.Background(), "astaxie"); v.(int) != 1 {
t.Error("get err")
}
bm.Delete(context.Background(), "astaxie")
if res, _ := bm.IsExist(context.Background(), "astaxie"); res {
t.Error("delete err")
@@ -120,6 +113,20 @@ func TestCache(t *testing.T) {
if vv[1].(string) != "author1" {
t.Error("GetMulti ERROR")
}
vv, err = bm.GetMulti(context.Background(), []string{"astaxie0", "astaxie1"})
if len(vv) != 2 {
t.Error("GetMulti ERROR")
}
if vv[0] != nil {
t.Error("GetMulti ERROR")
}
if vv[1].(string) != "author1" {
t.Error("GetMulti ERROR")
}
if err != nil && err.Error() != "key [astaxie0] error: the key isn't exist" {
t.Error("GetMulti ERROR")
}
}
func TestFileCache(t *testing.T) {
@@ -139,21 +146,13 @@ func TestFileCache(t *testing.T) {
t.Error("get err")
}
if err = bm.Incr(context.Background(), "astaxie"); err != nil {
t.Error("Incr Error", err)
}
// test different integer type for incr & decr
testMultiTypeIncrDecr(t, bm, timeoutDuration)
if v, _ := bm.Get(context.Background(), "astaxie"); v.(int) != 2 {
t.Error("get err")
}
// test overflow of incr&decr
testIncrOverFlow(t, bm, timeoutDuration)
testDecrOverFlow(t, bm, timeoutDuration)
if err = bm.Decr(context.Background(), "astaxie"); err != nil {
t.Error("Decr Error", err)
}
if v, _ := bm.Get(context.Background(), "astaxie"); v.(int) != 1 {
t.Error("get err")
}
bm.Delete(context.Background(), "astaxie")
if res, _ := bm.IsExist(context.Background(), "astaxie"); res {
t.Error("delete err")
@@ -189,5 +188,99 @@ func TestFileCache(t *testing.T) {
t.Error("GetMulti ERROR")
}
vv, err = bm.GetMulti(context.Background(), []string{"astaxie0", "astaxie1"})
if len(vv) != 2 {
t.Error("GetMulti ERROR")
}
if vv[0] != nil {
t.Error("GetMulti ERROR")
}
if vv[1].(string) != "author1" {
t.Error("GetMulti ERROR")
}
if err == nil {
t.Error("GetMulti ERROR")
}
os.RemoveAll("cache")
}
func testMultiTypeIncrDecr(t *testing.T, c Cache, timeout time.Duration) {
testIncrDecr(t, c, 1, 2, timeout)
testIncrDecr(t, c, int32(1), int32(2), timeout)
testIncrDecr(t, c, int64(1), int64(2), timeout)
testIncrDecr(t, c, uint(1), uint(2), timeout)
testIncrDecr(t, c, uint32(1), uint32(2), timeout)
testIncrDecr(t, c, uint64(1), uint64(2), timeout)
}
func testIncrDecr(t *testing.T, c Cache, beforeIncr interface{}, afterIncr interface{}, timeout time.Duration) {
var err error
ctx := context.Background()
key := "incDecKey"
if err = c.Put(ctx, key, beforeIncr, timeout); err != nil {
t.Error("Get Error", err)
}
if err = c.Incr(ctx, key); err != nil {
t.Error("Incr Error", err)
}
if v, _ := c.Get(ctx, key); v != afterIncr {
t.Error("Get Error")
}
if err = c.Decr(ctx, key); err != nil {
t.Error("Decr Error", err)
}
if v, _ := c.Get(ctx, key); v != beforeIncr {
t.Error("Get Error")
}
if err := c.Delete(ctx, key); err != nil {
t.Error("Delete Error")
}
}
func testIncrOverFlow(t *testing.T, c Cache, timeout time.Duration) {
var err error
ctx := context.Background()
key := "incKey"
// int64
if err = c.Put(ctx, key, int64(math.MaxInt64), timeout); err != nil {
t.Error("Put Error: ", err.Error())
return
}
defer func() {
if err = c.Delete(ctx, key); err != nil {
t.Errorf("Delete error: %s", err.Error())
}
}()
if err = c.Incr(ctx, key); err == nil {
t.Error("Incr error")
return
}
}
func testDecrOverFlow(t *testing.T, c Cache, timeout time.Duration) {
var err error
ctx := context.Background()
key := "decKey"
// int64
if err = c.Put(ctx, key, int64(math.MinInt64), timeout); err != nil {
t.Error("Put Error: ", err.Error())
return
}
defer func() {
if err = c.Delete(ctx, key); err != nil {
t.Errorf("Delete error: %s", err.Error())
}
}()
if err = c.Decr(ctx, key); err == nil {
t.Error("Decr error")
return
}
}

83
client/cache/calc_utils.go vendored Normal file
View File

@@ -0,0 +1,83 @@
package cache
import (
"fmt"
"math"
)
func incr(originVal interface{}) (interface{}, error) {
switch val := originVal.(type) {
case int:
tmp := val + 1
if val > 0 && tmp < 0 {
return nil, fmt.Errorf("increment would overflow")
}
return tmp, nil
case int32:
if val == math.MaxInt32 {
return nil, fmt.Errorf("increment would overflow")
}
return val + 1, nil
case int64:
if val == math.MaxInt64 {
return nil, fmt.Errorf("increment would overflow")
}
return val + 1, nil
case uint:
tmp := val + 1
if tmp < val {
return nil, fmt.Errorf("increment would overflow")
}
return tmp, nil
case uint32:
if val == math.MaxUint32 {
return nil, fmt.Errorf("increment would overflow")
}
return val + 1, nil
case uint64:
if val == math.MaxUint64 {
return nil, fmt.Errorf("increment would overflow")
}
return val + 1, nil
default:
return nil, fmt.Errorf("item val is not (u)int (u)int32 (u)int64")
}
}
func decr(originVal interface{}) (interface{}, error) {
switch val := originVal.(type) {
case int:
tmp := val - 1
if val < 0 && tmp > 0 {
return nil, fmt.Errorf("decrement would overflow")
}
return tmp, nil
case int32:
if val == math.MinInt32 {
return nil, fmt.Errorf("decrement would overflow")
}
return val - 1, nil
case int64:
if val == math.MinInt64 {
return nil, fmt.Errorf("decrement would overflow")
}
return val - 1, nil
case uint:
if val == 0 {
return nil, fmt.Errorf("decrement would overflow")
}
return val - 1, nil
case uint32:
if val == 0 {
return nil, fmt.Errorf("increment would overflow")
}
return val - 1, nil
case uint64:
if val == 0 {
return nil, fmt.Errorf("increment would overflow")
}
return val - 1, nil
default:
return nil, fmt.Errorf("item val is not (u)int (u)int32 (u)int64")
}
}

241
client/cache/calc_utils_test.go vendored Normal file
View File

@@ -0,0 +1,241 @@
package cache
import (
"math"
"strconv"
"testing"
)
func TestIncr(t *testing.T) {
// int
var originVal interface{} = int(1)
var updateVal interface{} = int(2)
val, err := incr(originVal)
if err != nil {
t.Errorf("incr failed, err: %s", err.Error())
return
}
if val != updateVal {
t.Errorf("incr failed, expect %v, but %v actually", updateVal, val)
return
}
_, err = incr(int(1 << (strconv.IntSize - 1) - 1))
if err == nil {
t.Error("incr failed")
return
}
// int32
originVal = int32(1)
updateVal = int32(2)
val, err = incr(originVal)
if err != nil {
t.Errorf("incr failed, err: %s", err.Error())
return
}
if val != updateVal {
t.Errorf("incr failed, expect %v, but %v actually", updateVal, val)
return
}
_, err = incr(int32(math.MaxInt32))
if err == nil {
t.Error("incr failed")
return
}
// int64
originVal = int64(1)
updateVal = int64(2)
val, err = incr(originVal)
if err != nil {
t.Errorf("incr failed, err: %s", err.Error())
return
}
if val != updateVal {
t.Errorf("incr failed, expect %v, but %v actually", updateVal, val)
return
}
_, err = incr(int64(math.MaxInt64))
if err == nil {
t.Error("incr failed")
return
}
// uint
originVal = uint(1)
updateVal = uint(2)
val, err = incr(originVal)
if err != nil {
t.Errorf("incr failed, err: %s", err.Error())
return
}
if val != updateVal {
t.Errorf("incr failed, expect %v, but %v actually", updateVal, val)
return
}
_, err = incr(uint(1 << (strconv.IntSize) - 1))
if err == nil {
t.Error("incr failed")
return
}
// uint32
originVal = uint32(1)
updateVal = uint32(2)
val, err = incr(originVal)
if err != nil {
t.Errorf("incr failed, err: %s", err.Error())
return
}
if val != updateVal {
t.Errorf("incr failed, expect %v, but %v actually", updateVal, val)
return
}
_, err = incr(uint32(math.MaxUint32))
if err == nil {
t.Error("incr failed")
return
}
// uint64
originVal = uint64(1)
updateVal = uint64(2)
val, err = incr(originVal)
if err != nil {
t.Errorf("incr failed, err: %s", err.Error())
return
}
if val != updateVal {
t.Errorf("incr failed, expect %v, but %v actually", updateVal, val)
return
}
_, err = incr(uint64(math.MaxUint64))
if err == nil {
t.Error("incr failed")
return
}
// other type
_, err = incr("string")
if err == nil {
t.Error("incr failed")
return
}
}
func TestDecr(t *testing.T) {
// int
var originVal interface{} = int(2)
var updateVal interface{} = int(1)
val, err := decr(originVal)
if err != nil {
t.Errorf("decr failed, err: %s", err.Error())
return
}
if val != updateVal {
t.Errorf("decr failed, expect %v, but %v actually", updateVal, val)
return
}
_, err = decr(int(-1 << (strconv.IntSize - 1)))
if err == nil {
t.Error("decr failed")
return
}
// int32
originVal = int32(2)
updateVal = int32(1)
val, err = decr(originVal)
if err != nil {
t.Errorf("decr failed, err: %s", err.Error())
return
}
if val != updateVal {
t.Errorf("decr failed, expect %v, but %v actually", updateVal, val)
return
}
_, err = decr(int32(math.MinInt32))
if err == nil {
t.Error("decr failed")
return
}
// int64
originVal = int64(2)
updateVal = int64(1)
val, err = decr(originVal)
if err != nil {
t.Errorf("decr failed, err: %s", err.Error())
return
}
if val != updateVal {
t.Errorf("decr failed, expect %v, but %v actually", updateVal, val)
return
}
_, err = decr(int64(math.MinInt64))
if err == nil {
t.Error("decr failed")
return
}
// uint
originVal = uint(2)
updateVal = uint(1)
val, err = decr(originVal)
if err != nil {
t.Errorf("decr failed, err: %s", err.Error())
return
}
if val != updateVal {
t.Errorf("decr failed, expect %v, but %v actually", updateVal, val)
return
}
_, err = decr(uint(0))
if err == nil {
t.Error("decr failed")
return
}
// uint32
originVal = uint32(2)
updateVal = uint32(1)
val, err = decr(originVal)
if err != nil {
t.Errorf("decr failed, err: %s", err.Error())
return
}
if val != updateVal {
t.Errorf("decr failed, expect %v, but %v actually", updateVal, val)
return
}
_, err = decr(uint32(0))
if err == nil {
t.Error("decr failed")
return
}
// uint64
originVal = uint64(2)
updateVal = uint64(1)
val, err = decr(originVal)
if err != nil {
t.Errorf("decr failed, err: %s", err.Error())
return
}
if val != updateVal {
t.Errorf("decr failed, expect %v, but %v actually", updateVal, val)
return
}
_, err = decr(uint64(0))
if err == nil {
t.Error("decr failed")
return
}
// other type
_, err = decr("string")
if err == nil {
t.Error("decr failed")
return
}
}

61
client/cache/file.go vendored
View File

@@ -26,8 +26,8 @@ import (
"io/ioutil"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"time"
"github.com/pkg/errors"
@@ -144,17 +144,22 @@ func (fc *FileCache) Get(ctx context.Context, key string) (interface{}, error) {
// GetMulti gets values from file cache.
// if nonexistent or expired return an empty string.
func (fc *FileCache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) {
var rc []interface{}
for _, key := range keys {
val, err := fc.Get(context.Background(), key)
if err != nil {
rc = append(rc, err)
} else {
rc = append(rc, val)
}
rc := make([]interface{}, len(keys))
keysErr := make([]string, 0)
for i, ki := range keys {
val, err := fc.Get(context.Background(), ki)
if err != nil {
keysErr = append(keysErr, fmt.Sprintf("key [%s] error: %s", ki, err.Error()))
continue
}
rc[i] = val
}
return rc, nil
if len(keysErr) == 0 {
return rc, nil
}
return rc, errors.New(strings.Join(keysErr, "; "))
}
// Put value into file cache.
@@ -189,28 +194,32 @@ func (fc *FileCache) Delete(ctx context.Context, key string) error {
// Incr increases cached int value.
// fc value is saved forever unless deleted.
func (fc *FileCache) Incr(ctx context.Context, key string) error {
data, _ := fc.Get(context.Background(), key)
var incr int
if reflect.TypeOf(data).Name() != "int" {
incr = 0
} else {
incr = data.(int) + 1
data, err := fc.Get(context.Background(), key)
if err != nil {
return err
}
fc.Put(context.Background(), key, incr, time.Duration(fc.EmbedExpiry))
return nil
val, err := incr(data)
if err != nil {
return err
}
return fc.Put(context.Background(), key, val, time.Duration(fc.EmbedExpiry))
}
// Decr decreases cached int value.
func (fc *FileCache) Decr(ctx context.Context, key string) error {
data, _ := fc.Get(context.Background(), key)
var decr int
if reflect.TypeOf(data).Name() != "int" || data.(int)-1 <= 0 {
decr = 0
} else {
decr = data.(int) - 1
data, err := fc.Get(context.Background(), key)
if err != nil {
return err
}
fc.Put(context.Background(), key, decr, time.Duration(fc.EmbedExpiry))
return nil
val, err := decr(data)
if err != nil {
return err
}
return fc.Put(context.Background(), key, val, time.Duration(fc.EmbedExpiry))
}
// IsExist checks if value exists.

View File

@@ -20,8 +20,8 @@
//
// Usage:
// import(
// _ "github.com/astaxie/beego/cache/memcache"
// "github.com/astaxie/beego/cache"
// _ "github.com/beego/beego/v2/cache/memcache"
// "github.com/beego/beego/v2/cache"
// )
//
// bm, err := cache.NewCache("memcache", `{"conn":"127.0.0.1:11211"}`)
@@ -33,12 +33,13 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"github.com/bradfitz/gomemcache/memcache"
"github.com/astaxie/beego/client/cache"
"github.com/beego/beego/v2/client/cache"
)
// Cache Memcache adapter.
@@ -68,19 +69,31 @@ func (rc *Cache) Get(ctx context.Context, key string) (interface{}, error) {
// GetMulti gets a value from a key in memcache.
func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) {
var rv []interface{}
rv := make([]interface{}, len(keys))
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return rv, err
}
}
mv, err := rc.conn.GetMulti(keys)
if err == nil {
for _, v := range mv {
rv = append(rv, v.Value)
}
if err != nil {
return rv, err
}
return rv, err
keysErr := make([]string, 0)
for i, ki := range keys {
if _, ok := mv[ki]; !ok {
keysErr = append(keysErr, fmt.Sprintf("key [%s] error: %s", ki, "the key isn't exist"))
continue
}
rv[i] = mv[ki].Value
}
if len(keysErr) == 0 {
return rv, nil
}
return rv, fmt.Errorf(strings.Join(keysErr, "; "))
}
// Put puts a value into memcache.

View File

@@ -24,11 +24,10 @@ import (
_ "github.com/bradfitz/gomemcache/memcache"
"github.com/astaxie/beego/client/cache"
"github.com/beego/beego/v2/client/cache"
)
func TestMemcacheCache(t *testing.T) {
addr := os.Getenv("MEMCACHE_ADDR")
if addr == "" {
addr = "127.0.0.1:11211"
@@ -114,6 +113,20 @@ func TestMemcacheCache(t *testing.T) {
t.Error("GetMulti ERROR")
}
vv, err = bm.GetMulti(context.Background(), []string{"astaxie0", "astaxie1"})
if len(vv) != 2 {
t.Error("GetMulti ERROR")
}
if vv[0] != nil {
t.Error("GetMulti ERROR")
}
if string(vv[1].([]byte)) != "author1" {
t.Error("GetMulti ERROR")
}
if err != nil && err.Error() == "key [astaxie0] error: key isn't exist" {
t.Error("GetMulti ERROR")
}
// test clear all
if err = bm.ClearAll(context.Background()); err != nil {
t.Error("clear all err")

View File

@@ -18,6 +18,8 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"sync"
"time"
)
@@ -68,22 +70,28 @@ func (bc *MemoryCache) Get(ctx context.Context, key string) (interface{}, error)
}
return itm.val, nil
}
return nil, nil
return nil, errors.New("the key isn't exist")
}
// GetMulti gets caches from memory.
// If non-existent or expired, return nil.
func (bc *MemoryCache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) {
var rc []interface{}
for _, name := range keys {
val, err := bc.Get(context.Background(), name)
rc := make([]interface{}, len(keys))
keysErr := make([]string, 0)
for i, ki := range keys {
val, err := bc.Get(context.Background(), ki)
if err != nil {
rc = append(rc, err)
} else {
rc = append(rc, val)
keysErr = append(keysErr, fmt.Sprintf("key [%s] error: %s", ki, err.Error()))
continue
}
rc[i] = val
}
return rc, nil
if len(keysErr) == 0 {
return rc, nil
}
return rc, errors.New(strings.Join(keysErr, "; "))
}
// Put puts cache into memory.
@@ -122,22 +130,12 @@ func (bc *MemoryCache) Incr(ctx context.Context, key string) error {
if !ok {
return errors.New("key not exist")
}
switch val := itm.val.(type) {
case int:
itm.val = val + 1
case int32:
itm.val = val + 1
case int64:
itm.val = val + 1
case uint:
itm.val = val + 1
case uint32:
itm.val = val + 1
case uint64:
itm.val = val + 1
default:
return errors.New("item val is not (u)int (u)int32 (u)int64")
val, err := incr(itm.val)
if err != nil {
return err
}
itm.val = val
return nil
}
@@ -149,34 +147,12 @@ func (bc *MemoryCache) Decr(ctx context.Context, key string) error {
if !ok {
return errors.New("key not exist")
}
switch val := itm.val.(type) {
case int:
itm.val = val - 1
case int64:
itm.val = val - 1
case int32:
itm.val = val - 1
case uint:
if val > 0 {
itm.val = val - 1
} else {
return errors.New("item val is less than 0")
}
case uint32:
if val > 0 {
itm.val = val - 1
} else {
return errors.New("item val is less than 0")
}
case uint64:
if val > 0 {
itm.val = val - 1
} else {
return errors.New("item val is less than 0")
}
default:
return errors.New("item val is not int int64 int32")
val, err := decr(itm.val)
if err != nil {
return err
}
itm.val = val
return nil
}

View File

@@ -20,8 +20,8 @@
//
// Usage:
// import(
// _ "github.com/astaxie/beego/cache/redis"
// "github.com/astaxie/beego/cache"
// _ "github.com/beego/beego/v2/cache/redis"
// "github.com/beego/beego/v2/cache"
// )
//
// bm, err := cache.NewCache("redis", `{"conn":"127.0.0.1:11211"}`)
@@ -40,7 +40,7 @@ import (
"github.com/gomodule/redigo/redis"
"github.com/astaxie/beego/client/cache"
"github.com/beego/beego/v2/client/cache"
)
var (

View File

@@ -24,7 +24,7 @@ import (
"github.com/gomodule/redigo/redis"
"github.com/stretchr/testify/assert"
"github.com/astaxie/beego/client/cache"
"github.com/beego/beego/v2/client/cache"
)
func TestRedisCache(t *testing.T) {
@@ -113,6 +113,14 @@ func TestRedisCache(t *testing.T) {
t.Error("GetMulti ERROR")
}
vv, _ = bm.GetMulti(context.Background(), []string{"astaxie0", "astaxie1"})
if vv[0] != nil {
t.Error("GetMulti ERROR")
}
if v, _ := redis.String(vv[1], nil); v != "author1" {
t.Error("GetMulti ERROR")
}
// test clear all
if err = bm.ClearAll(context.Background()); err != nil {
t.Error("clear all err")

View File

@@ -4,13 +4,14 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
"time"
"github.com/ssdb/gossdb/ssdb"
"github.com/astaxie/beego/client/cache"
"github.com/beego/beego/v2/client/cache"
)
// Cache SSDB adapter
@@ -19,7 +20,7 @@ type Cache struct {
conninfo []string
}
//NewSsdbCache creates new ssdb adapter.
// NewSsdbCache creates new ssdb adapter.
func NewSsdbCache() cache.Cache {
return &Cache{}
}
@@ -28,36 +29,50 @@ func NewSsdbCache() cache.Cache {
func (rc *Cache) Get(ctx context.Context, key string) (interface{}, error) {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return nil, nil
return nil, err
}
}
value, err := rc.conn.Get(key)
if err == nil {
return value, nil
}
return nil, nil
return nil, err
}
// GetMulti gets one or keys values from ssdb.
func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) {
size := len(keys)
var values []interface{}
values := make([]interface{}, size)
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return values, err
}
}
res, err := rc.conn.Do("multi_get", keys)
if err != nil {
return values, err
}
resSize := len(res)
if err == nil {
for i := 1; i < resSize; i += 2 {
values = append(values, res[i+1])
keyIdx := make(map[string]int)
for i := 1; i < resSize; i += 2 {
keyIdx[res[i]] = i
}
keysErr := make([]string, 0)
for i, ki := range keys {
if _, ok := keyIdx[ki]; !ok {
keysErr = append(keysErr, fmt.Sprintf("key [%s] error: %s", ki, "the key isn't exist"))
continue
}
return values, nil
values[i] = res[keyIdx[ki]+1]
}
for i := 0; i < size; i++ {
values = append(values, err)
if len(keysErr) != 0 {
return values, fmt.Errorf(strings.Join(keysErr, "; "))
}
return values, nil
}

View File

@@ -8,7 +8,7 @@ import (
"testing"
"time"
"github.com/astaxie/beego/client/cache"
"github.com/beego/beego/v2/client/cache"
)
func TestSsdbcacheCache(t *testing.T) {
@@ -106,6 +106,20 @@ func TestSsdbcacheCache(t *testing.T) {
t.Error("getmulti error")
}
vv, err = ssdb.GetMulti(context.Background(), []string{"ssdb", "ssdb11"})
if len(vv) != 2 {
t.Error("getmulti error")
}
if vv[0].(string) != "ssdb" {
t.Error("getmulti error")
}
if vv[1] != nil {
t.Error("getmulti error")
}
if err != nil && err.Error() != "key [ssdb11] error: the key isn't exist" {
t.Error("getmulti error")
}
// test clear all done
if err = ssdb.ClearAll(context.Background()); err != nil {
t.Error("clear all err")

View File

@@ -1,20 +1,23 @@
# httplib
httplib is an libs help you to curl remote url.
# How to use?
## GET
you can use Get to crawl data.
import "github.com/astaxie/beego/httplib"
import "github.com/beego/beego/v2/httplib"
str, err := httplib.Get("http://beego.me/").String()
if err != nil {
// error
}
fmt.Println(str)
## POST
POST data to remote url
req := httplib.Post("http://beego.me/")
@@ -40,13 +43,12 @@ Example:
// POST
httplib.Post("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second)
## Debug
If you want to debug the request info, set the debug on
httplib.Get("http://beego.me/").Debug(true)
## Set HTTP Basic Auth
str, err := Get("http://beego.me/").SetBasicAuth("user", "passwd").String()
@@ -54,21 +56,21 @@ If you want to debug the request info, set the debug on
// error
}
fmt.Println(str)
## Set HTTPS
If request url is https, You can set the client support TSL:
httplib.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
More info about the `tls.Config` please visit http://golang.org/pkg/crypto/tls/#Config
More info about the `tls.Config` please visit http://golang.org/pkg/crypto/tls/#Config
## Set HTTP Version
some servers need to specify the protocol version of HTTP
httplib.Get("http://beego.me/").SetProtocolVersion("HTTP/1.1")
## Set Cookie
some http request need setcookie. So set it like this:
@@ -91,7 +93,6 @@ httplib support mutil file upload, use `req.PostFile()`
}
fmt.Println(str)
See godoc for further documentation and examples.
* [godoc.org/github.com/astaxie/beego/httplib](https://godoc.org/github.com/astaxie/beego/httplib)
* [godoc.org/github.com/beego/beego/v2/httplib](https://godoc.org/github.com/beego/beego/v2/httplib)

View File

@@ -18,13 +18,17 @@ import (
"context"
"net/http"
"github.com/astaxie/beego/client/httplib"
logKit "github.com/go-kit/kit/log"
opentracingKit "github.com/go-kit/kit/tracing/opentracing"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/log"
"github.com/beego/beego/v2/client/httplib"
)
type FilterChainBuilder struct {
// TagURL true will tag span with url
TagURL bool
// CustomSpanFunc users are able to custom their span
CustomSpanFunc func(span opentracing.Span, ctx context.Context,
req *httplib.BeegoHTTPRequest, resp *http.Response, err error)
@@ -49,13 +53,19 @@ func (builder *FilterChainBuilder) FilterChain(next httplib.Filter) httplib.Filt
}
span.SetTag("http.method", method)
span.SetTag("peer.hostname", req.GetRequest().URL.Host)
span.SetTag("http.url", req.GetRequest().URL.String())
span.SetTag("http.scheme", req.GetRequest().URL.Scheme)
span.SetTag("span.kind", "client")
span.SetTag("component", "beego")
if builder.TagURL {
span.SetTag("http.url", req.GetRequest().URL.String())
}
span.LogFields(log.String("http.url", req.GetRequest().URL.String()))
if err != nil {
span.SetTag("error", true)
span.SetTag("message", err.Error())
span.LogFields(log.String("message", err.Error()))
} else if resp != nil && !(resp.StatusCode < 300 && resp.StatusCode >= 200) {
span.SetTag("error", true)
}

View File

@@ -23,7 +23,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/astaxie/beego/client/httplib"
"github.com/beego/beego/v2/client/httplib"
)
func TestFilterChainBuilder_FilterChain(t *testing.T) {
@@ -33,7 +33,9 @@ func TestFilterChainBuilder_FilterChain(t *testing.T) {
StatusCode: 404,
}, errors.New("hello")
}
builder := &FilterChainBuilder{}
builder := &FilterChainBuilder{
TagURL: true,
}
filter := builder.FilterChain(next)
req := httplib.Get("https://github.com/notifications?query=repo%3Aastaxie%2Fbeego")
resp, err := filter(context.Background(), req)

View File

@@ -22,7 +22,7 @@ import (
"github.com/prometheus/client_golang/prometheus"
"github.com/astaxie/beego/client/httplib"
"github.com/beego/beego/v2/client/httplib"
)
type FilterChainBuilder struct {
@@ -43,7 +43,7 @@ func (builder *FilterChainBuilder) FilterChain(next httplib.Filter) httplib.Filt
"appname": builder.AppName,
},
Help: "The statics info for remote http requests",
}, []string{"proto", "scheme", "method", "host", "path", "status", "duration", "isError"})
}, []string{"proto", "scheme", "method", "host", "path", "status", "isError"})
return func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) {
startTime := time.Now()
@@ -73,5 +73,5 @@ func (builder *FilterChainBuilder) report(startTime time.Time, endTime time.Time
dur := int(endTime.Sub(startTime) / time.Millisecond)
builder.summaryVec.WithLabelValues(proto, scheme, method, host, path,
strconv.Itoa(status), strconv.Itoa(dur), strconv.FormatBool(err == nil))
strconv.Itoa(status), strconv.FormatBool(err != nil)).Observe(float64(dur))
}

View File

@@ -22,7 +22,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/astaxie/beego/client/httplib"
"github.com/beego/beego/v2/client/httplib"
)
func TestFilterChainBuilder_FilterChain(t *testing.T) {

View File

@@ -0,0 +1,39 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httplib
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
)
// NewHttpResponseWithJsonBody will try to convert the data to json format
// usually you only use this when you want to mock http Response
func NewHttpResponseWithJsonBody(data interface{}) *http.Response {
var body []byte
if str, ok := data.(string); ok {
body = []byte(str)
} else if bts, ok := data.([]byte); ok {
body = bts
} else {
body, _ = json.Marshal(data)
}
return &http.Response{
ContentLength: int64(len(body)),
Body: ioutil.NopCloser(bytes.NewReader(body)),
}
}

View File

@@ -0,0 +1,36 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httplib
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewHttpResponseWithJsonBody(t *testing.T) {
// string
resp := NewHttpResponseWithJsonBody("{}")
assert.Equal(t, int64(2), resp.ContentLength)
resp = NewHttpResponseWithJsonBody([]byte("{}"))
assert.Equal(t, int64(2), resp.ContentLength)
resp = NewHttpResponseWithJsonBody(&user{
Name: "Tom",
})
assert.True(t, resp.ContentLength > 0)
}

View File

@@ -15,7 +15,7 @@
// Package httplib is used as http.Client
// Usage:
//
// import "github.com/astaxie/beego/httplib"
// import "github.com/beego/beego/v2/httplib"
//
// b := httplib.Post("http://beego.me/")
// b.Param("username","astaxie")
@@ -44,48 +44,22 @@ import (
"mime/multipart"
"net"
"net/http"
"net/http/cookiejar"
"net/http/httputil"
"net/url"
"os"
"path"
"strings"
"sync"
"time"
"gopkg.in/yaml.v2"
)
var defaultSetting = BeegoHTTPSettings{
UserAgent: "beegoServer",
ConnectTimeout: 60 * time.Second,
ReadWriteTimeout: 60 * time.Second,
Gzip: true,
DumpBody: true,
}
var defaultCookieJar http.CookieJar
var settingMutex sync.Mutex
// it will be the last filter and execute request.Do
var doRequestFilter = func(ctx context.Context, req *BeegoHTTPRequest) (*http.Response, error) {
return req.doRequest(ctx)
}
// createDefaultCookie creates a global cookiejar to store cookies.
func createDefaultCookie() {
settingMutex.Lock()
defer settingMutex.Unlock()
defaultCookieJar, _ = cookiejar.New(nil)
}
// SetDefaultSetting overwrites default settings
func SetDefaultSetting(setting BeegoHTTPSettings) {
settingMutex.Lock()
defer settingMutex.Unlock()
defaultSetting = setting
}
// NewBeegoRequest returns *BeegoHttpRequest with specific method
func NewBeegoRequest(rawurl, method string) *BeegoHTTPRequest {
var resp http.Response
@@ -136,23 +110,7 @@ func Head(url string) *BeegoHTTPRequest {
return NewBeegoRequest(url, "HEAD")
}
// BeegoHTTPSettings is the http.Client setting
type BeegoHTTPSettings struct {
ShowDebug bool
UserAgent string
ConnectTimeout time.Duration
ReadWriteTimeout time.Duration
TLSClientConfig *tls.Config
Proxy func(*http.Request) (*url.URL, error)
Transport http.RoundTripper
CheckRedirect func(req *http.Request, via []*http.Request) error
EnableCookie bool
Gzip bool
DumpBody bool
Retries int // if set to -1 means will retry forever
RetryDelay time.Duration
FilterChains []FilterChain
}
// BeegoHTTPRequest provides more useful methods than http.Request for requesting a url.
type BeegoHTTPRequest struct {
@@ -338,10 +296,16 @@ func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest {
case string:
bf := bytes.NewBufferString(t)
b.req.Body = ioutil.NopCloser(bf)
b.req.GetBody = func() (io.ReadCloser, error) {
return ioutil.NopCloser(bf), nil
}
b.req.ContentLength = int64(len(t))
case []byte:
bf := bytes.NewBuffer(t)
b.req.Body = ioutil.NopCloser(bf)
b.req.GetBody = func() (io.ReadCloser, error) {
return ioutil.NopCloser(bf), nil
}
b.req.ContentLength = int64(len(t))
}
return b
@@ -355,6 +319,9 @@ func (b *BeegoHTTPRequest) XMLBody(obj interface{}) (*BeegoHTTPRequest, error) {
return b, err
}
b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
b.req.GetBody = func() (io.ReadCloser, error) {
return ioutil.NopCloser(bytes.NewReader(byts)), nil
}
b.req.ContentLength = int64(len(byts))
b.req.Header.Set("Content-Type", "application/xml")
}
@@ -553,7 +520,7 @@ func (b *BeegoHTTPRequest) doRequest(ctx context.Context) (resp *http.Response,
// retries default value is 0, it will run once.
// retries equal to -1, it will run forever until success
// retries is setted, it will retries fixed times.
// Sleeps for a 400ms inbetween calls to reduce spam
// Sleeps for a 400ms between calls to reduce spam
for i := 0; b.setting.Retries == -1 || i <= b.setting.Retries; i++ {
resp, err = client.Do(b.req)
if err == nil {

View File

@@ -15,6 +15,7 @@
package httplib
import (
"context"
"errors"
"io/ioutil"
"net"
@@ -23,6 +24,8 @@ import (
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestResponse(t *testing.T) {
@@ -98,7 +101,7 @@ func TestSimplePost(t *testing.T) {
}
}
//func TestPostFile(t *testing.T) {
// func TestPostFile(t *testing.T) {
// v := "smallfish"
// req := Post("http://httpbin.org/post")
// req.Debug(true)
@@ -115,7 +118,7 @@ func TestSimplePost(t *testing.T) {
// if n == -1 {
// t.Fatal(v + " not found in post")
// }
//}
// }
func TestSimplePut(t *testing.T) {
str, err := Put("http://httpbin.org/put").String()
@@ -284,3 +287,94 @@ func TestHeader(t *testing.T) {
}
t.Log(str)
}
// TestAddFilter make sure that AddFilters only work for the specific request
func TestAddFilter(t *testing.T) {
req := Get("http://beego.me")
req.AddFilters(func(next Filter) Filter {
return func(ctx context.Context, req *BeegoHTTPRequest) (*http.Response, error) {
return next(ctx, req)
}
})
r := Get("http://beego.me")
assert.Equal(t, 1, len(req.setting.FilterChains)-len(r.setting.FilterChains))
}
func TestHead(t *testing.T) {
req := Head("http://beego.me")
assert.NotNil(t, req)
assert.Equal(t, "HEAD", req.req.Method)
}
func TestDelete(t *testing.T) {
req := Delete("http://beego.me")
assert.NotNil(t, req)
assert.Equal(t, "DELETE", req.req.Method)
}
func TestPost(t *testing.T) {
req := Post("http://beego.me")
assert.NotNil(t, req)
assert.Equal(t, "POST", req.req.Method)
}
func TestNewBeegoRequest(t *testing.T) {
req := NewBeegoRequest("http://beego.me", "GET")
assert.NotNil(t, req)
assert.Equal(t, "GET", req.req.Method)
}
func TestPut(t *testing.T) {
req := Put("http://beego.me")
assert.NotNil(t, req)
assert.Equal(t, "PUT", req.req.Method)
}
func TestBeegoHTTPRequest_Header(t *testing.T) {
req := Post("http://beego.me")
key, value := "test-header", "test-header-value"
req.Header(key, value)
assert.Equal(t, value, req.req.Header.Get(key))
}
func TestBeegoHTTPRequest_SetHost(t *testing.T) {
req := Post("http://beego.me")
host := "test-hose"
req.SetHost(host)
assert.Equal(t, host, req.req.Host)
}
func TestBeegoHTTPRequest_Param(t *testing.T) {
req := Post("http://beego.me")
key, value := "test-param", "test-param-value"
req.Param(key, value)
assert.Equal(t, value, req.params[key][0])
value1 := "test-param-value-1"
req.Param(key, value1)
assert.Equal(t, value1, req.params[key][1])
}
func TestBeegoHTTPRequest_Body(t *testing.T) {
req := Post("http://beego.me")
body := `hello, world`
req.Body([]byte(body))
assert.Equal(t, int64(len(body)), req.req.ContentLength)
assert.NotNil(t, req.req.GetBody)
}
type user struct {
Name string `xml:"name"`
}
func TestBeegoHTTPRequest_XMLBody(t *testing.T) {
req := Post("http://beego.me")
body := &user{
Name: "Tom",
}
_, err := req.XMLBody(body)
assert.True(t, req.req.ContentLength > 0)
assert.Nil(t, err)
assert.NotNil(t, req.req.GetBody)
}

View File

@@ -0,0 +1,80 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mock
import (
"context"
"net/http"
"github.com/beego/beego/v2/client/httplib"
"github.com/beego/beego/v2/core/logs"
)
const mockCtxKey = "beego-httplib-mock"
func init() {
InitMockSetting()
}
type Stub interface {
Mock(cond RequestCondition, resp *http.Response, err error)
Clear()
MockByPath(path string, resp *http.Response, err error)
}
var mockFilter = &MockResponseFilter{}
func InitMockSetting() {
httplib.AddDefaultFilter(mockFilter.FilterChain)
}
func StartMock() Stub {
return mockFilter
}
func CtxWithMock(ctx context.Context, mock... *Mock) context.Context {
return context.WithValue(ctx, mockCtxKey, mock)
}
func mockFromCtx(ctx context.Context) []*Mock {
ms := ctx.Value(mockCtxKey)
if ms != nil {
if res, ok := ms.([]*Mock); ok {
return res
}
logs.Error("mockCtxKey found in context, but value is not type []*Mock")
}
return nil
}
type Mock struct {
cond RequestCondition
resp *http.Response
err error
}
func NewMockByPath(path string, resp *http.Response, err error) *Mock {
return NewMock(NewSimpleCondition(path), resp, err)
}
func NewMock(con RequestCondition, resp *http.Response, err error) *Mock {
return &Mock{
cond: con,
resp: resp,
err: err,
}
}

View File

@@ -0,0 +1,177 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mock
import (
"context"
"encoding/json"
"net/textproto"
"regexp"
"github.com/beego/beego/v2/client/httplib"
)
type RequestCondition interface {
Match(ctx context.Context, req *httplib.BeegoHTTPRequest) bool
}
// reqCondition create condition
// - path: same path
// - pathReg: request path match pathReg
// - method: same method
// - Query parameters (key, value)
// - header (key, value)
// - Body json format, contains specific (key, value).
type SimpleCondition struct {
pathReg string
path string
method string
query map[string]string
header map[string]string
body map[string]interface{}
}
func NewSimpleCondition(path string, opts ...simpleConditionOption) *SimpleCondition {
sc := &SimpleCondition{
path: path,
query: make(map[string]string),
header: make(map[string]string),
body: map[string]interface{}{},
}
for _, o := range opts {
o(sc)
}
return sc
}
func (sc *SimpleCondition) Match(ctx context.Context, req *httplib.BeegoHTTPRequest) bool {
res := true
if len(sc.path) > 0 {
res = sc.matchPath(ctx, req)
} else if len(sc.pathReg) > 0 {
res = sc.matchPathReg(ctx, req)
} else {
return false
}
return res &&
sc.matchMethod(ctx, req) &&
sc.matchQuery(ctx, req) &&
sc.matchHeader(ctx, req) &&
sc.matchBodyFields(ctx, req)
}
func (sc *SimpleCondition) matchPath(ctx context.Context, req *httplib.BeegoHTTPRequest) bool {
path := req.GetRequest().URL.Path
return path == sc.path
}
func (sc *SimpleCondition) matchPathReg(ctx context.Context, req *httplib.BeegoHTTPRequest) bool {
path := req.GetRequest().URL.Path
if b, err := regexp.Match(sc.pathReg, []byte(path)); err == nil {
return b
}
return false
}
func (sc *SimpleCondition) matchQuery(ctx context.Context, req *httplib.BeegoHTTPRequest) bool {
qs := req.GetRequest().URL.Query()
for k, v := range sc.query {
if uv, ok := qs[k]; !ok || uv[0] != v {
return false
}
}
return true
}
func (sc *SimpleCondition) matchHeader(ctx context.Context, req *httplib.BeegoHTTPRequest) bool {
headers := req.GetRequest().Header
for k, v := range sc.header {
if uv, ok := headers[k]; !ok || uv[0] != v {
return false
}
}
return true
}
func (sc *SimpleCondition) matchBodyFields(ctx context.Context, req *httplib.BeegoHTTPRequest) bool {
if len(sc.body) == 0 {
return true
}
getBody := req.GetRequest().GetBody
body, err := getBody()
if err != nil {
return false
}
bytes := make([]byte, req.GetRequest().ContentLength)
_, err = body.Read(bytes)
if err != nil {
return false
}
m := make(map[string]interface{})
err = json.Unmarshal(bytes, &m)
if err != nil {
return false
}
for k, v := range sc.body {
if uv, ok := m[k]; !ok || uv != v {
return false
}
}
return true
}
func (sc *SimpleCondition) matchMethod(ctx context.Context, req *httplib.BeegoHTTPRequest) bool {
if len(sc.method) > 0 {
return sc.method == req.GetRequest().Method
}
return true
}
type simpleConditionOption func(sc *SimpleCondition)
func WithPathReg(pathReg string) simpleConditionOption {
return func(sc *SimpleCondition) {
sc.pathReg = pathReg
}
}
func WithQuery(key, value string) simpleConditionOption {
return func(sc *SimpleCondition) {
sc.query[key] = value
}
}
func WithHeader(key, value string) simpleConditionOption {
return func(sc *SimpleCondition) {
sc.header[textproto.CanonicalMIMEHeaderKey(key)] = value
}
}
func WithJsonBodyFields(field string, value interface{}) simpleConditionOption {
return func(sc *SimpleCondition) {
sc.body[field] = value
}
}
func WithMethod(method string) simpleConditionOption {
return func(sc *SimpleCondition) {
sc.method = method
}
}

View File

@@ -0,0 +1,124 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mock
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/beego/beego/v2/client/httplib"
)
func init() {
}
func TestSimpleCondition_MatchPath(t *testing.T) {
sc := NewSimpleCondition("/abc/s")
res := sc.Match(context.Background(), httplib.Get("http://localhost:8080/abc/s"))
assert.True(t, res)
}
func TestSimpleCondition_MatchQuery(t *testing.T) {
k, v := "my-key", "my-value"
sc := NewSimpleCondition("/abc/s")
res := sc.Match(context.Background(), httplib.Get("http://localhost:8080/abc/s?my-key=my-value"))
assert.True(t, res)
sc = NewSimpleCondition("/abc/s", WithQuery(k, v))
res = sc.Match(context.Background(), httplib.Get("http://localhost:8080/abc/s?my-key=my-value"))
assert.True(t, res)
res = sc.Match(context.Background(), httplib.Get("http://localhost:8080/abc/s?my-key=my-valuesss"))
assert.False(t, res)
res = sc.Match(context.Background(), httplib.Get("http://localhost:8080/abc/s?my-key-a=my-value"))
assert.False(t, res)
res = sc.Match(context.Background(), httplib.Get("http://localhost:8080/abc/s?my-key=my-value&abc=hello"))
assert.True(t, res)
}
func TestSimpleCondition_MatchHeader(t *testing.T) {
k, v := "my-header", "my-header-value"
sc := NewSimpleCondition("/abc/s")
req := httplib.Get("http://localhost:8080/abc/s")
assert.True(t, sc.Match(context.Background(), req))
req = httplib.Get("http://localhost:8080/abc/s")
req.Header(k, v)
assert.True(t, sc.Match(context.Background(), req))
sc = NewSimpleCondition("/abc/s", WithHeader(k, v))
req.Header(k, v)
assert.True(t, sc.Match(context.Background(), req))
req.Header(k, "invalid")
assert.False(t, sc.Match(context.Background(), req))
}
func TestSimpleCondition_MatchBodyField(t *testing.T) {
sc := NewSimpleCondition("/abc/s")
req := httplib.Post("http://localhost:8080/abc/s")
assert.True(t, sc.Match(context.Background(), req))
req.Body(`{
"body-field": 123
}`)
assert.True(t, sc.Match(context.Background(), req))
k := "body-field"
v := float64(123)
sc = NewSimpleCondition("/abc/s", WithJsonBodyFields(k, v))
assert.True(t, sc.Match(context.Background(), req))
sc = NewSimpleCondition("/abc/s", WithJsonBodyFields(k, v))
req.Body(`{
"body-field": abc
}`)
assert.False(t, sc.Match(context.Background(), req))
sc = NewSimpleCondition("/abc/s", WithJsonBodyFields("body-field", "abc"))
req.Body(`{
"body-field": "abc"
}`)
assert.True(t, sc.Match(context.Background(), req))
}
func TestSimpleCondition_Match(t *testing.T) {
sc := NewSimpleCondition("/abc/s")
req := httplib.Post("http://localhost:8080/abc/s")
assert.True(t, sc.Match(context.Background(), req))
sc = NewSimpleCondition("/abc/s", WithMethod("POST"))
assert.True(t, sc.Match(context.Background(), req))
sc = NewSimpleCondition("/abc/s", WithMethod("GET"))
assert.False(t, sc.Match(context.Background(), req))
}
func TestSimpleCondition_MatchPathReg(t *testing.T) {
sc := NewSimpleCondition("", WithPathReg(`\/abc\/.*`))
req := httplib.Post("http://localhost:8080/abc/s")
assert.True(t, sc.Match(context.Background(), req))
req = httplib.Post("http://localhost:8080/abcd/s")
assert.False(t, sc.Match(context.Background(), req))
}

View File

@@ -0,0 +1,61 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mock
import (
"context"
"net/http"
"github.com/beego/beego/v2/client/httplib"
)
// MockResponse will return mock response if find any suitable mock data
// if you want to test your code using httplib, you need this.
type MockResponseFilter struct {
ms []*Mock
}
func NewMockResponseFilter() *MockResponseFilter {
return &MockResponseFilter{
ms: make([]*Mock, 0, 1),
}
}
func (m *MockResponseFilter) FilterChain(next httplib.Filter) httplib.Filter {
return func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) {
ms := mockFromCtx(ctx)
ms = append(ms, m.ms...)
for _, mock := range ms {
if mock.cond.Match(ctx, req) {
return mock.resp, mock.err
}
}
return next(ctx, req)
}
}
func (m *MockResponseFilter) MockByPath(path string, resp *http.Response, err error) {
m.Mock(NewSimpleCondition(path), resp, err)
}
func (m *MockResponseFilter) Clear() {
m.ms = make([]*Mock, 0, 1)
}
// Mock add mock data
// If the cond.Match(...) = true, the resp and err will be returned
func (m *MockResponseFilter) Mock(cond RequestCondition, resp *http.Response, err error) {
m.ms = append(m.ms, NewMock(cond, resp, err))
}

View File

@@ -0,0 +1,63 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mock
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/beego/beego/v2/client/httplib"
)
func TestMockResponseFilter_FilterChain(t *testing.T) {
req := httplib.Get("http://localhost:8080/abc/s")
ft := NewMockResponseFilter()
expectedResp := httplib.NewHttpResponseWithJsonBody(`{}`)
expectedErr := errors.New("expected error")
ft.Mock(NewSimpleCondition("/abc/s"), expectedResp, expectedErr)
req.AddFilters(ft.FilterChain)
resp, err := req.DoRequest()
assert.Equal(t, expectedErr, err)
assert.Equal(t, expectedResp, resp)
req = httplib.Get("http://localhost:8080/abcd/s")
req.AddFilters(ft.FilterChain)
resp, err = req.DoRequest()
assert.NotEqual(t, expectedErr, err)
assert.NotEqual(t, expectedResp, resp)
req = httplib.Get("http://localhost:8080/abc/s")
req.AddFilters(ft.FilterChain)
expectedResp1 := httplib.NewHttpResponseWithJsonBody(map[string]string{})
expectedErr1 := errors.New("expected error")
ft.Mock(NewSimpleCondition("/abc/abs/bbc"), expectedResp1, expectedErr1)
resp, err = req.DoRequest()
assert.Equal(t, expectedErr, err)
assert.Equal(t, expectedResp, resp)
req = httplib.Get("http://localhost:8080/abc/abs/bbc")
req.AddFilters(ft.FilterChain)
ft.Mock(NewSimpleCondition("/abc/abs/bbc"), expectedResp1, expectedErr1)
resp, err = req.DoRequest()
assert.Equal(t, expectedErr1, err)
assert.Equal(t, expectedResp1, resp)
}

View File

@@ -0,0 +1,77 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mock
import (
"context"
"errors"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/beego/beego/v2/client/httplib"
)
func TestStartMock(t *testing.T) {
// httplib.defaultSetting.FilterChains = []httplib.FilterChain{mockFilter.FilterChain}
stub := StartMock()
// defer stub.Clear()
expectedResp := httplib.NewHttpResponseWithJsonBody([]byte(`{}`))
expectedErr := errors.New("expected err")
stub.Mock(NewSimpleCondition("/abc"), expectedResp, expectedErr)
resp, err := OriginalCodeUsingHttplib()
assert.Equal(t, expectedErr, err)
assert.Equal(t, expectedResp, resp)
}
// TestStartMock_Isolation Test StartMock that
// mock only work for this request
func TestStartMock_Isolation(t *testing.T) {
// httplib.defaultSetting.FilterChains = []httplib.FilterChain{mockFilter.FilterChain}
// setup global stub
stub := StartMock()
globalMockResp := httplib.NewHttpResponseWithJsonBody([]byte(`{}`))
globalMockErr := errors.New("expected err")
stub.Mock(NewSimpleCondition("/abc"), globalMockResp, globalMockErr)
expectedResp := httplib.NewHttpResponseWithJsonBody(struct {
A string `json:"a"`
}{
A: "aaa",
})
expectedErr := errors.New("expected err aa")
m := NewMockByPath("/abc", expectedResp, expectedErr)
ctx := CtxWithMock(context.Background(), m)
resp, err := OriginnalCodeUsingHttplibPassCtx(ctx)
assert.Equal(t, expectedErr, err)
assert.Equal(t, expectedResp, resp)
}
func OriginnalCodeUsingHttplibPassCtx(ctx context.Context) (*http.Response, error) {
return httplib.Get("http://localhost:7777/abc").DoRequestWithCtx(ctx)
}
func OriginalCodeUsingHttplib() (*http.Response, error){
return httplib.Get("http://localhost:7777/abc").DoRequest()
}

81
client/httplib/setting.go Normal file
View File

@@ -0,0 +1,81 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httplib
import (
"crypto/tls"
"net/http"
"net/http/cookiejar"
"net/url"
"sync"
"time"
)
// BeegoHTTPSettings is the http.Client setting
type BeegoHTTPSettings struct {
ShowDebug bool
UserAgent string
ConnectTimeout time.Duration
ReadWriteTimeout time.Duration
TLSClientConfig *tls.Config
Proxy func(*http.Request) (*url.URL, error)
Transport http.RoundTripper
CheckRedirect func(req *http.Request, via []*http.Request) error
EnableCookie bool
Gzip bool
DumpBody bool
Retries int // if set to -1 means will retry forever
RetryDelay time.Duration
FilterChains []FilterChain
}
// createDefaultCookie creates a global cookiejar to store cookies.
func createDefaultCookie() {
settingMutex.Lock()
defer settingMutex.Unlock()
defaultCookieJar, _ = cookiejar.New(nil)
}
// SetDefaultSetting overwrites default settings
// Keep in mind that when you invoke the SetDefaultSetting
// some methods invoked before SetDefaultSetting
func SetDefaultSetting(setting BeegoHTTPSettings) {
settingMutex.Lock()
defer settingMutex.Unlock()
defaultSetting = setting
}
var defaultSetting = BeegoHTTPSettings{
UserAgent: "beegoServer",
ConnectTimeout: 60 * time.Second,
ReadWriteTimeout: 60 * time.Second,
Gzip: true,
DumpBody: true,
FilterChains: make([]FilterChain, 0, 4),
}
var defaultCookieJar http.CookieJar
var settingMutex sync.Mutex
// AddDefaultFilter add a new filter into defaultSetting
// Be careful about using this method if you invoke SetDefaultSetting somewhere
func AddDefaultFilter(fc FilterChain) {
settingMutex.Lock()
defer settingMutex.Unlock()
if defaultSetting.FilterChains == nil {
defaultSetting.FilterChains = make([]FilterChain, 0, 4)
}
defaultSetting.FilterChains = append(defaultSetting.FilterChains, fc)
}

View File

@@ -15,7 +15,7 @@
package testing
import (
"github.com/astaxie/beego/client/httplib"
"github.com/beego/beego/v2/client/httplib"
)
var port = ""

View File

@@ -1,6 +1,6 @@
# beego orm
[![Build Status](https://drone.io/github.com/astaxie/beego/status.png)](https://drone.io/github.com/astaxie/beego/latest)
[![Build Status](https://drone.io/github.com/beego/beego/v2/status.png)](https://drone.io/github.com/beego/beego/v2/latest)
A powerful orm framework for go.
@@ -27,7 +27,7 @@ more features please read the docs
**Install:**
go get github.com/astaxie/beego/orm
go get github.com/beego/beego/v2/orm
## Changelog
@@ -45,7 +45,7 @@ package main
import (
"fmt"
"github.com/astaxie/beego/orm"
"github.com/beego/beego/v2/orm"
_ "github.com/go-sql-driver/mysql" // import your used driver
)

View File

@@ -22,7 +22,7 @@ import (
"strings"
"time"
"github.com/astaxie/beego/client/orm/hints"
"github.com/beego/beego/v2/client/orm/hints"
)
const (
@@ -524,7 +524,7 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a
return 0, fmt.Errorf("`%s` nonsupport InsertOrUpdate in beego", a.DriverName)
}
//Get on the key-value pairs
// Get on the key-value pairs
for _, v := range args {
kv := strings.Split(v, "=")
if len(kv) == 2 {
@@ -559,7 +559,7 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a
updates[i] = v + "=" + valueStr
case DRPostgres:
if conflitValue != nil {
//postgres ON CONFLICT DO UPDATE SET can`t use colu=colu+values
// postgres ON CONFLICT DO UPDATE SET can`t use colu=colu+values
updates[i] = fmt.Sprintf("%s=(select %s from %s where %s = ? )", v, valueStr, mi.table, args0)
updateValues = append(updateValues, conflitValue)
} else {
@@ -584,7 +584,7 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a
if isMulti {
qmarks = strings.Repeat(qmarks+"), (", multi-1) + qmarks
}
//conflitValue maybe is a int,can`t use fmt.Sprintf
// conflitValue maybe is a int,can`t use fmt.Sprintf
query := fmt.Sprintf("INSERT INTO %s%s%s (%s%s%s) VALUES (%s) %s "+qupdates, Q, mi.table, Q, Q, columns, Q, qmarks, iouStr)
d.ins.ReplaceMarks(&query)

View File

@@ -111,7 +111,7 @@ func (d *dbBaseMysql) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Val
iouStr = "ON DUPLICATE KEY UPDATE"
//Get on the key-value pairs
// Get on the key-value pairs
for _, v := range args {
kv := strings.Split(v, "=")
if len(kv) == 2 {
@@ -155,7 +155,7 @@ func (d *dbBaseMysql) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Val
if isMulti {
qmarks = strings.Repeat(qmarks+"), (", multi-1) + qmarks
}
//conflitValue maybe is a int,can`t use fmt.Sprintf
// conflitValue maybe is a int,can`t use fmt.Sprintf
query := fmt.Sprintf("INSERT INTO %s%s%s (%s%s%s) VALUES (%s) %s "+qupdates, Q, mi.table, Q, Q, columns, Q, qmarks, iouStr)
d.ins.ReplaceMarks(&query)

View File

@@ -18,7 +18,7 @@ import (
"fmt"
"strings"
"github.com/astaxie/beego/client/orm/hints"
"github.com/beego/beego/v2/client/orm/hints"
)
// oracle operators.
@@ -77,7 +77,7 @@ func (d *dbBaseOracle) DbTypes() map[string]string {
return oracleTypes
}
//ShowTablesQuery show all the tables in database
// ShowTablesQuery show all the tables in database
func (d *dbBaseOracle) ShowTablesQuery() string {
return "SELECT TABLE_NAME FROM USER_TABLES"
}

View File

@@ -21,7 +21,7 @@ import (
"strings"
"time"
"github.com/astaxie/beego/client/orm/hints"
"github.com/beego/beego/v2/client/orm/hints"
)
// sqlite operators.

View File

@@ -18,7 +18,7 @@ import (
"context"
"database/sql"
"github.com/astaxie/beego/core/utils"
"github.com/beego/beego/v2/core/utils"
)
// DoNothingOrm won't do anything, usually you use this to custom your mock Ormer implementation

View File

@@ -19,10 +19,10 @@ import (
"reflect"
"strings"
"github.com/astaxie/beego/core/logs"
"github.com/beego/beego/v2/core/logs"
"github.com/astaxie/beego/client/orm"
"github.com/astaxie/beego/core/bean"
"github.com/beego/beego/v2/client/orm"
"github.com/beego/beego/v2/core/bean"
)
// DefaultValueFilterChainBuilder only works for InsertXXX method,

View File

@@ -19,7 +19,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/astaxie/beego/client/orm"
"github.com/beego/beego/v2/client/orm"
)
func TestDefaultValueFilterChainBuilder_FilterChain(t *testing.T) {

View File

@@ -20,7 +20,7 @@ import (
"github.com/opentracing/opentracing-go"
"github.com/astaxie/beego/client/orm"
"github.com/beego/beego/v2/client/orm"
)
// FilterChainBuilder provides an extension point

View File

@@ -21,7 +21,7 @@ import (
"github.com/opentracing/opentracing-go"
"github.com/astaxie/beego/client/orm"
"github.com/beego/beego/v2/client/orm"
)
func TestFilterChainBuilder_FilterChain(t *testing.T) {

View File

@@ -22,7 +22,7 @@ import (
"github.com/prometheus/client_golang/prometheus"
"github.com/astaxie/beego/client/orm"
"github.com/beego/beego/v2/client/orm"
)
// FilterChainBuilder is an extension point,
@@ -50,7 +50,7 @@ func (builder *FilterChainBuilder) FilterChain(next orm.Filter) orm.Filter {
"appname": builder.AppName,
},
Help: "The statics info for orm operation",
}, []string{"method", "name", "duration", "insideTx", "txName"})
}, []string{"method", "name", "insideTx", "txName"})
return func(ctx context.Context, inv *orm.Invocation) []interface{} {
startTime := time.Now()
@@ -74,12 +74,12 @@ func (builder *FilterChainBuilder) report(ctx context.Context, inv *orm.Invocati
builder.reportTxn(ctx, inv)
return
}
builder.summaryVec.WithLabelValues(inv.Method, inv.GetTableName(), strconv.Itoa(int(dur)),
strconv.FormatBool(inv.InsideTx), inv.TxName)
builder.summaryVec.WithLabelValues(inv.Method, inv.GetTableName(),
strconv.FormatBool(inv.InsideTx), inv.TxName).Observe(float64(dur))
}
func (builder *FilterChainBuilder) reportTxn(ctx context.Context, inv *orm.Invocation) {
dur := time.Now().Sub(inv.TxStartTime) / time.Millisecond
builder.summaryVec.WithLabelValues(inv.Method, inv.TxName, strconv.Itoa(int(dur)),
strconv.FormatBool(inv.InsideTx), inv.TxName)
builder.summaryVec.WithLabelValues(inv.Method, inv.TxName,
strconv.FormatBool(inv.InsideTx), inv.TxName).Observe(float64(dur))
}

View File

@@ -21,7 +21,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/astaxie/beego/client/orm"
"github.com/beego/beego/v2/client/orm"
)
func TestFilterChainBuilder_FilterChain1(t *testing.T) {

View File

@@ -20,7 +20,7 @@ import (
"reflect"
"time"
"github.com/astaxie/beego/core/utils"
"github.com/beego/beego/v2/core/utils"
)
const (

View File

@@ -21,7 +21,7 @@ import (
"sync"
"testing"
"github.com/astaxie/beego/core/utils"
"github.com/beego/beego/v2/core/utils"
"github.com/stretchr/testify/assert"
)
@@ -222,7 +222,7 @@ func TestFilterOrmDecorator_InsertMulti(t *testing.T) {
}
})
bulk := []*FilterTestEntity{&FilterTestEntity{}, &FilterTestEntity{}}
bulk := []*FilterTestEntity{{}, {}}
i, err := od.InsertMulti(2, bulk)
assert.NotNil(t, err)
assert.Equal(t, "insert multi error", err.Error())

View File

@@ -15,11 +15,11 @@
package hints
import (
"github.com/astaxie/beego/core/utils"
"github.com/beego/beego/v2/core/utils"
)
const (
//query level
// query level
KeyForceIndex = iota
KeyUseIndex
KeyIgnoreIndex

View File

@@ -17,7 +17,7 @@ package migration
import (
"fmt"
"github.com/astaxie/beego/core/logs"
"github.com/beego/beego/v2/core/logs"
)
// Index struct defines the structure of Index Columns

View File

@@ -33,8 +33,8 @@ import (
"strings"
"time"
"github.com/astaxie/beego/client/orm"
"github.com/astaxie/beego/core/logs"
"github.com/beego/beego/v2/client/orm"
"github.com/beego/beego/v2/core/logs"
)
// const the data format for the bee generate migration datatype
@@ -52,7 +52,7 @@ type Migrationer interface {
GetCreated() int64
}
//Migration defines the migrations by either SQL or DDL
// Migration defines the migrations by either SQL or DDL
type Migration struct {
sqls []string
Created string
@@ -104,7 +104,7 @@ func (m *Migration) Down() {
m.sqls = append(m.sqls, m.GetSQL())
}
//Migrate adds the SQL to the execution list
// Migrate adds the SQL to the execution list
func (m *Migration) Migrate(migrationType string) {
m.ModifyType = migrationType
m.sqls = append(m.sqls, m.GetSQL())

View File

@@ -45,7 +45,7 @@ type _modelCache struct {
done bool
}
//NewModelCacheHandler generator of _modelCache
// NewModelCacheHandler generator of _modelCache
func NewModelCacheHandler() *_modelCache {
return &_modelCache{
cache: make(map[string]*modelInfo),
@@ -113,7 +113,7 @@ func (mc *_modelCache) clean() {
mc.done = false
}
//bootstrap bootstrap for models
// bootstrap bootstrap for models
func (mc *_modelCache) bootstrap() {
mc.Lock()
defer mc.Unlock()
@@ -407,7 +407,7 @@ func (mc *_modelCache) register(prefixOrSuffixStr string, prefixOrSuffix bool, m
return
}
//getDbDropSQL get database scheme drop sql queries
// getDbDropSQL get database scheme drop sql queries
func (mc *_modelCache) getDbDropSQL(al *alias) (queries []string, err error) {
if len(mc.cache) == 0 {
err = errors.New("no Model found, need register your model")
@@ -422,7 +422,7 @@ func (mc *_modelCache) getDbDropSQL(al *alias) (queries []string, err error) {
return queries, nil
}
//getDbCreateSQL get database scheme creation sql queries
// getDbCreateSQL get database scheme creation sql queries
func (mc *_modelCache) getDbCreateSQL(al *alias) (queries []string, tableIndexes map[string][]dbIndex, err error) {
if len(mc.cache) == 0 {
err = errors.New("no Model found, need register your model")
@@ -467,9 +467,9 @@ func (mc *_modelCache) getDbCreateSQL(al *alias) (queries []string, tableIndexes
column += " " + "NOT NULL"
}
//if fi.initial.String() != "" {
// if fi.initial.String() != "" {
// column += " DEFAULT " + fi.initial.String()
//}
// }
// Append attribute DEFAULT
column += getColumnDefault(fi)

View File

@@ -194,7 +194,7 @@ checkType:
}
fieldType = f.FieldType()
if fieldType&IsRelField > 0 {
err = fmt.Errorf("unsupport type custom field, please refer to https://github.com/astaxie/beego/blob/master/orm/models_fields.go#L24-L42")
err = fmt.Errorf("unsupport type custom field, please refer to https://github.com/beego/beego/v2/blob/master/orm/models_fields.go#L24-L42")
goto end
}
default:

View File

@@ -74,7 +74,7 @@ func addModelFields(mi *modelInfo, ind reflect.Value, mName string, index []int)
} else if err != nil {
break
}
//record current field index
// record current field index
fi.fieldIndex = append(fi.fieldIndex, index...)
fi.fieldIndex = append(fi.fieldIndex, i)
fi.mi = mi

View File

@@ -318,7 +318,7 @@ type Post struct {
Created time.Time `orm:"auto_now_add"`
Updated time.Time `orm:"auto_now"`
UpdatedPrecision time.Time `orm:"auto_now;type(datetime);precision(4)"`
Tags []*Tag `orm:"rel(m2m);rel_through(github.com/astaxie/beego/client/orm.PostTags)"`
Tags []*Tag `orm:"rel(m2m);rel_through(github.com/beego/beego/v2/client/orm.PostTags)"`
}
func (u *Post) TableIndex() [][]string {
@@ -376,7 +376,7 @@ type Group struct {
type Permission struct {
ID int `orm:"column(id)"`
Name string
Groups []*Group `orm:"rel(m2m);rel_through(github.com/astaxie/beego/client/orm.GroupPermissions)"`
Groups []*Group `orm:"rel(m2m);rel_through(github.com/beego/beego/v2/client/orm.GroupPermissions)"`
}
type GroupPermissions struct {
@@ -485,7 +485,7 @@ var (
usage:
go get -u github.com/astaxie/beego/client/orm
go get -u github.com/beego/beego/v2/client/orm
go get -u github.com/go-sql-driver/mysql
go get -u github.com/mattn/go-sqlite3
go get -u github.com/lib/pq
@@ -495,25 +495,25 @@ var (
mysql -u root -e 'create database orm_test;'
export ORM_DRIVER=mysql
export ORM_SOURCE="root:@/orm_test?charset=utf8"
go test -v github.com/astaxie/beego/client/orm
go test -v github.com/beego/beego/v2/client/orm
#### Sqlite3
export ORM_DRIVER=sqlite3
export ORM_SOURCE='file:memory_test?mode=memory'
go test -v github.com/astaxie/beego/client/orm
go test -v github.com/beego/beego/v2/client/orm
#### PostgreSQL
psql -c 'create database orm_test;' -U postgres
export ORM_DRIVER=postgres
export ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
go test -v github.com/astaxie/beego/client/orm
go test -v github.com/beego/beego/v2/client/orm
#### TiDB
export ORM_DRIVER=tidb
export ORM_SOURCE='memory://test/test'
go test -v github.com/astaxie/beego/pgk/orm
go test -v github.com/beego/beego/v2/pgk/orm
`
)

View File

@@ -21,7 +21,7 @@
//
// import (
// "fmt"
// "github.com/astaxie/beego/client/orm"
// "github.com/beego/beego/v2/client/orm"
// _ "github.com/go-sql-driver/mysql" // import your used driver
// )
//
@@ -63,10 +63,10 @@ import (
"reflect"
"time"
"github.com/astaxie/beego/client/orm/hints"
"github.com/astaxie/beego/core/utils"
"github.com/beego/beego/v2/client/orm/hints"
"github.com/beego/beego/v2/core/utils"
"github.com/astaxie/beego/core/logs"
"github.com/beego/beego/v2/core/logs"
)
// DebugQueries define the debug

View File

@@ -77,10 +77,13 @@ func (c Condition) AndNot(expr string, args ...interface{}) *Condition {
// AndCond combine a condition to current condition
func (c *Condition) AndCond(cond *Condition) *Condition {
c = c.clone()
if c == cond {
panic(fmt.Errorf("<Condition.AndCond> cannot use self as sub cond"))
}
c = c.clone()
if cond != nil {
c.params = append(c.params, condValue{cond: cond, isCond: true})
}
@@ -150,5 +153,8 @@ func (c *Condition) IsEmpty() bool {
// clone clone a condition
func (c Condition) clone() *Condition {
params := make([]condValue, len(c.params))
copy(params, c.params)
c.params = params
return &c
}

View File

@@ -29,7 +29,7 @@ type Log struct {
*log.Logger
}
//costomer log func
// costomer log func
var LogFunc func(query map[string]interface{})
// NewLog set io.Writer to create a Logger.

View File

@@ -17,8 +17,8 @@ package orm
import (
"context"
"fmt"
"github.com/astaxie/beego/client/orm/hints"
"github.com/astaxie/beego/client/orm/clauses/order_clause"
"github.com/astaxie/beego/v2/client/orm/hints"
"github.com/astaxie/beego/v2/client/orm/clauses/order_clause"
)
type colValue struct {

View File

@@ -181,6 +181,12 @@ func (o *rawSet) setFieldValue(ind reflect.Value, value interface{}) {
if err == nil {
ind.Set(reflect.ValueOf(t))
}
} else if len(str) >= 8 {
str = str[:8]
t, err := time.ParseInLocation(formatTime, str, DefaultTimeLoc)
if err == nil {
ind.Set(reflect.ValueOf(t))
}
}
}
case sql.NullString, sql.NullInt64, sql.NullFloat64, sql.NullBool:

View File

@@ -32,7 +32,7 @@ import (
"testing"
"time"
"github.com/astaxie/beego/client/orm/hints"
"github.com/beego/beego/v2/client/orm/hints"
"github.com/stretchr/testify/assert"
)
@@ -1780,6 +1780,10 @@ func TestRawQueryRow(t *testing.T) {
throwFail(t, AssertIs(id, 1))
break
case "time":
v = v.(time.Time).In(DefaultTimeLoc)
value := dataValues[col].(time.Time).In(DefaultTimeLoc)
assert.True(t, v.(time.Time).Sub(value) <= time.Second)
break
case "date":
case "datetime":
v = v.(time.Time).In(DefaultTimeLoc)
@@ -1807,12 +1811,12 @@ func TestRawQueryRow(t *testing.T) {
throwFail(t, AssertIs(*status, 3))
throwFail(t, AssertIs(pid, nil))
type Embeded struct {
type Embedded struct {
Email string
}
type queryRowNoModelTest struct {
Id int
EmbedField Embeded
EmbedField Embedded
}
cols = []string{
@@ -2702,3 +2706,48 @@ func TestPSQueryBuilder(t *testing.T) {
throwFailNow(t, AssertIs(l[0].UserName, "astaxie"))
throwFailNow(t, AssertIs(l[0].Age, 30))
}
func TestCondition(t *testing.T) {
// test Condition whether to include yourself
cond := NewCondition()
cond = cond.AndCond(cond.Or("ID", 1))
cond = cond.AndCond(cond.Or("ID", 2))
cond = cond.AndCond(cond.Or("ID", 3))
cond = cond.AndCond(cond.Or("ID", 4))
cycleFlag := false
var hasCycle func(*Condition)
hasCycle = func(c *Condition) {
if nil == c || cycleFlag {
return
}
condPointMap := make(map[string]bool)
condPointMap[fmt.Sprintf("%p", c)] = true
for _, p := range c.params {
if p.isCond {
adr := fmt.Sprintf("%p", p.cond)
if condPointMap[adr] {
// self as sub cond was cycle
cycleFlag = true
break
}
condPointMap[adr] = true
}
}
if cycleFlag {
return
}
for _, p := range c.params {
if p.isCond {
// check next cond
hasCycle(p.cond)
}
}
return
}
hasCycle(cond)
// cycleFlag was true,meaning use self as sub cond
throwFail(t, AssertIs(!cycleFlag, true))
return
}

View File

@@ -21,7 +21,7 @@ import (
"reflect"
"time"
"github.com/astaxie/beego/core/utils"
"github.com/beego/beego/v2/core/utils"
)
// TableNaming is usually used by model
@@ -96,13 +96,13 @@ type Fielder interface {
}
type TxBeginner interface {
//self control transaction
// self control transaction
Begin() (TxOrmer, error)
BeginWithCtx(ctx context.Context) (TxOrmer, error)
BeginWithOpts(opts *sql.TxOptions) (TxOrmer, error)
BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error)
//closure control transaction
// closure control transaction
DoTx(task func(ctx context.Context, txOrm TxOrmer) error) error
DoTxWithCtx(ctx context.Context, task func(ctx context.Context, txOrm TxOrmer) error) error
DoTxWithOpts(opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error
@@ -114,7 +114,7 @@ type TxCommitter interface {
Rollback() error
}
//Data Manipulation Language
// Data Manipulation Language
type DML interface {
// insert model data to database
// for example:

View File

@@ -49,12 +49,12 @@ func (f *StrTo) Set(v string) {
// Clear string
func (f *StrTo) Clear() {
*f = StrTo(0x1E)
*f = StrTo(rune(0x1E))
}
// Exist check string exist
func (f StrTo) Exist() bool {
return string(f) != string(0x1E)
return string(f) != string(rune(0x1E))
}
// Bool string to bool