mirror of
https://gitee.com/johng/gf
synced 2026-06-08 02:27:42 +08:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5bc9acdab3 | |||
| ab1970e7d6 | |||
| 1582714325 | |||
| 7391a4d45a | |||
| 7e16d9b63e | |||
| d49dccb147 | |||
| 6cddfdb313 | |||
| 912316d765 | |||
| b9e2b05f04 | |||
| 887803e495 | |||
| eb11061bd2 | |||
| 000c7a92ed | |||
| 097b26f318 | |||
| 3da5e5e865 | |||
| e60262fec9 | |||
| 74bf1b4bc3 | |||
| 3f69e0db36 | |||
| 7d4c59ac5a | |||
| 3841f05e02 | |||
| bcd409ab1c | |||
| 4dd43aa018 | |||
| aed695313a |
4
.github/workflows/gf.yml
vendored
4
.github/workflows/gf.yml
vendored
@ -111,7 +111,9 @@ jobs:
|
||||
--health-retries 10
|
||||
|
||||
# ClickHouse backend server.
|
||||
# docker run -d --name clickhouse -p 9000:9000 -p 8123:8123 -p 9001:9001 loads/clickhouse-server:22.1.3.7
|
||||
# docker run -d --name clickhouse \
|
||||
# -p 9000:9000 -p 8123:8123 -p 9001:9001 \
|
||||
# loads/clickhouse-server:22.1.3.7
|
||||
clickhouse-server:
|
||||
image: loads/clickhouse-server:22.1.3.7
|
||||
ports:
|
||||
|
||||
@ -3,13 +3,13 @@ module github.com/gogf/gf/cmd/gf/v2
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.5.2
|
||||
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.5.2
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.5.2
|
||||
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.5.2
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.5.2
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.5.2
|
||||
github.com/gogf/gf/v2 v2.5.2
|
||||
github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.5.3
|
||||
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.5.3
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.5.3
|
||||
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.5.3
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.5.3
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.5.3
|
||||
github.com/gogf/gf/v2 v2.5.3
|
||||
github.com/minio/selfupdate v0.6.0
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
golang.org/x/mod v0.9.0
|
||||
|
||||
@ -47,7 +47,7 @@ const (
|
||||
cBuildBrief = `cross-building go project for lots of platforms`
|
||||
cBuildEg = `
|
||||
gf build main.go
|
||||
gf build main.go --pack public,template
|
||||
gf build main.go --ps public,template
|
||||
gf build main.go --cgo
|
||||
gf build main.go -m none
|
||||
gf build main.go -n my-app -a all -s all
|
||||
|
||||
@ -67,9 +67,10 @@ func generateStructFieldDefinition(
|
||||
ctx context.Context, field *gdb.TableField, in generateStructDefinitionInput,
|
||||
) (attrLines []string, appendImport string) {
|
||||
var (
|
||||
err error
|
||||
typeName string
|
||||
jsonTag = getJsonTagFromCase(field.Name, in.JsonCase)
|
||||
err error
|
||||
localTypeName gdb.LocalType
|
||||
localTypeNameStr string
|
||||
jsonTag = getJsonTagFromCase(field.Name, in.JsonCase)
|
||||
)
|
||||
|
||||
if in.TypeMapping != nil && len(in.TypeMapping) > 0 {
|
||||
@ -84,38 +85,39 @@ func generateStructFieldDefinition(
|
||||
}
|
||||
if tryTypeName != "" {
|
||||
if typeMapping, ok := in.TypeMapping[strings.ToLower(tryTypeName)]; ok {
|
||||
typeName = typeMapping.Type
|
||||
localTypeNameStr = typeMapping.Type
|
||||
appendImport = typeMapping.Import
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if typeName == "" {
|
||||
typeName, err = in.DB.CheckLocalTypeForField(ctx, field.Type, nil)
|
||||
if localTypeNameStr == "" {
|
||||
localTypeName, err = in.DB.CheckLocalTypeForField(ctx, field.Type, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
switch typeName {
|
||||
case gdb.LocalTypeDate, gdb.LocalTypeDatetime:
|
||||
if in.StdTime {
|
||||
typeName = "time.Time"
|
||||
} else {
|
||||
typeName = "*gtime.Time"
|
||||
}
|
||||
|
||||
case gdb.LocalTypeInt64Bytes:
|
||||
typeName = "int64"
|
||||
switch localTypeName {
|
||||
case gdb.LocalTypeDate, gdb.LocalTypeDatetime:
|
||||
if in.StdTime {
|
||||
localTypeNameStr = "time.Time"
|
||||
} else {
|
||||
localTypeNameStr = "*gtime.Time"
|
||||
}
|
||||
|
||||
case gdb.LocalTypeUint64Bytes:
|
||||
typeName = "uint64"
|
||||
case gdb.LocalTypeInt64Bytes:
|
||||
localTypeNameStr = "int64"
|
||||
|
||||
// Special type handle.
|
||||
case gdb.LocalTypeJson, gdb.LocalTypeJsonb:
|
||||
if in.GJsonSupport {
|
||||
typeName = "*gjson.Json"
|
||||
} else {
|
||||
typeName = "string"
|
||||
case gdb.LocalTypeUint64Bytes:
|
||||
localTypeNameStr = "uint64"
|
||||
|
||||
// Special type handle.
|
||||
case gdb.LocalTypeJson, gdb.LocalTypeJsonb:
|
||||
if in.GJsonSupport {
|
||||
localTypeNameStr = "*gjson.Json"
|
||||
} else {
|
||||
localTypeNameStr = "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,7 +127,7 @@ func generateStructFieldDefinition(
|
||||
)
|
||||
attrLines = []string{
|
||||
" #" + gstr.CaseCamel(field.Name),
|
||||
" #" + typeName,
|
||||
" #" + localTypeNameStr,
|
||||
}
|
||||
attrLines = append(attrLines, " #"+fmt.Sprintf(tagKey+`json:"%s"`, jsonTag))
|
||||
attrLines = append(attrLines, " #"+fmt.Sprintf(`description:"%s"`+tagKey, descriptionTag))
|
||||
|
||||
@ -12,6 +12,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/olekukonko/tablewriter"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/consts"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
@ -23,7 +25,6 @@ import (
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gtag"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
type (
|
||||
@ -294,17 +295,17 @@ func generateEntityMessageDefinition(entityName string, fieldMap map[string]*gdb
|
||||
// generateMessageFieldForPbEntity generates and returns the message definition for specified field.
|
||||
func generateMessageFieldForPbEntity(index int, field *gdb.TableField, in CGenPbEntityInternalInput) []string {
|
||||
var (
|
||||
typeName string
|
||||
comment string
|
||||
jsonTagStr string
|
||||
err error
|
||||
ctx = gctx.GetInitCtx()
|
||||
localTypeName gdb.LocalType
|
||||
comment string
|
||||
jsonTagStr string
|
||||
err error
|
||||
ctx = gctx.GetInitCtx()
|
||||
)
|
||||
typeName, err = in.DB.CheckLocalTypeForField(ctx, field.Type, nil)
|
||||
localTypeName, err = in.DB.CheckLocalTypeForField(ctx, field.Type, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var typeMapping = map[string]string{
|
||||
var typeMapping = map[gdb.LocalType]string{
|
||||
gdb.LocalTypeString: "string",
|
||||
gdb.LocalTypeDate: "google.protobuf.Timestamp",
|
||||
gdb.LocalTypeDatetime: "google.protobuf.Timestamp",
|
||||
@ -324,9 +325,9 @@ func generateMessageFieldForPbEntity(index int, field *gdb.TableField, in CGenPb
|
||||
gdb.LocalTypeJson: "string",
|
||||
gdb.LocalTypeJsonb: "string",
|
||||
}
|
||||
typeName = typeMapping[typeName]
|
||||
if typeName == "" {
|
||||
typeName = "string"
|
||||
localTypeNameStr := typeMapping[localTypeName]
|
||||
if localTypeNameStr == "" {
|
||||
localTypeNameStr = "string"
|
||||
}
|
||||
|
||||
comment = gstr.ReplaceByArray(field.Comment, g.SliceStr{
|
||||
@ -350,7 +351,7 @@ func generateMessageFieldForPbEntity(index int, field *gdb.TableField, in CGenPb
|
||||
}
|
||||
}
|
||||
return []string{
|
||||
" #" + typeName,
|
||||
" #" + localTypeNameStr,
|
||||
" #" + formatCase(field.Name, in.NameCase),
|
||||
" #= " + gconv.String(index) + jsonTagStr + ";",
|
||||
" #" + fmt.Sprintf(`// %s`, comment),
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -8,13 +8,14 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/internal/deepcopy"
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// StrAnyMap implements map[string]interface{} with RWMutex that has switch.
|
||||
|
||||
@ -184,7 +184,7 @@ func (set *Set) Clear() {
|
||||
set.mu.Unlock()
|
||||
}
|
||||
|
||||
// Slice returns the an of items of the set as slice.
|
||||
// Slice returns all items of the set as slice.
|
||||
func (set *Set) Slice() []interface{} {
|
||||
set.mu.RLock()
|
||||
var (
|
||||
|
||||
@ -4,7 +4,7 @@ go 1.18
|
||||
|
||||
require (
|
||||
github.com/apolloconfig/agollo/v4 v4.1.1
|
||||
github.com/gogf/gf/v2 v2.5.2
|
||||
github.com/gogf/gf/v2 v2.5.3
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/config/kubecm/v2
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.5.2
|
||||
github.com/gogf/gf/v2 v2.5.3
|
||||
k8s.io/api v0.27.4
|
||||
k8s.io/apimachinery v0.27.4
|
||||
k8s.io/client-go v0.27.4
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/config/nacos/v2
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.5.2
|
||||
github.com/gogf/gf/v2 v2.5.3
|
||||
github.com/nacos-group/nacos-sdk-go v1.1.4
|
||||
)
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/config/polaris/v2
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.5.2
|
||||
github.com/gogf/gf/v2 v2.5.3
|
||||
github.com/polarismesh/polaris-go v1.5.1
|
||||
)
|
||||
|
||||
|
||||
@ -27,8 +27,6 @@ import (
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gtag"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
@ -139,9 +137,7 @@ func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string,
|
||||
|
||||
// TableFields retrieves and returns the fields' information of specified table of current schema.
|
||||
// Also see DriverMysql.TableFields.
|
||||
func (d *Driver) TableFields(
|
||||
ctx context.Context, table string, schema ...string,
|
||||
) (fields map[string]*gdb.TableField, err error) {
|
||||
func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
@ -224,9 +220,8 @@ func (d *Driver) DoFilter(
|
||||
if len(args) == 0 {
|
||||
return originSql, args, nil
|
||||
}
|
||||
|
||||
var index int
|
||||
// Convert placeholder char '?' to string "$x".
|
||||
var index int
|
||||
originSql, _ = gregex.ReplaceStringFunc(`\?`, originSql, func(s string) string {
|
||||
index++
|
||||
return fmt.Sprintf(`$%d`, index)
|
||||
@ -251,9 +246,12 @@ func (d *Driver) DoFilter(
|
||||
case "UPDATE":
|
||||
// MySQL eg: UPDATE table_name SET field1=new-value1, field2=new-value2 [WHERE Clause]
|
||||
// Clickhouse eg: ALTER TABLE [db.]table UPDATE column1 = expr1 [, ...] WHERE filter_expr
|
||||
newSql, err = gregex.ReplaceStringFuncMatch(updateFilterPattern, originSql, func(s []string) string {
|
||||
return fmt.Sprintf("ALTER TABLE %s UPDATE", s[1])
|
||||
})
|
||||
newSql, err = gregex.ReplaceStringFuncMatch(
|
||||
updateFilterPattern, originSql,
|
||||
func(s []string) string {
|
||||
return fmt.Sprintf("ALTER TABLE %s UPDATE", s[1])
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
@ -262,9 +260,12 @@ func (d *Driver) DoFilter(
|
||||
case "DELETE":
|
||||
// MySQL eg: DELETE FROM table_name [WHERE Clause]
|
||||
// Clickhouse eg: ALTER TABLE [db.]table [ON CLUSTER cluster] DELETE WHERE filter_expr
|
||||
newSql, err = gregex.ReplaceStringFuncMatch(deleteFilterPattern, originSql, func(s []string) string {
|
||||
return fmt.Sprintf("ALTER TABLE %s DELETE", s[1])
|
||||
})
|
||||
newSql, err = gregex.ReplaceStringFuncMatch(
|
||||
deleteFilterPattern, originSql,
|
||||
func(s []string) string {
|
||||
return fmt.Sprintf("ALTER TABLE %s DELETE", s[1])
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
@ -280,6 +281,7 @@ func (d *Driver) DoCommit(ctx context.Context, in gdb.DoCommitInput) (out gdb.Do
|
||||
return d.Core.DoCommit(ctx, in)
|
||||
}
|
||||
|
||||
// DoInsert inserts or updates data forF given table.
|
||||
func (d *Driver) DoInsert(
|
||||
ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
|
||||
) (result sql.Result, err error) {
|
||||
@ -335,86 +337,80 @@ func (d *Driver) DoInsert(
|
||||
return
|
||||
}
|
||||
|
||||
// ConvertDataForRecord converting for any data that will be inserted into table/collection as a record.
|
||||
func (d *Driver) ConvertDataForRecord(ctx context.Context, value interface{}) (map[string]interface{}, error) {
|
||||
m := gconv.Map(value, gtag.ORM)
|
||||
|
||||
// transforms a value of a particular type
|
||||
for k, v := range m {
|
||||
switch itemValue := v.(type) {
|
||||
|
||||
case time.Time:
|
||||
m[k] = itemValue
|
||||
// If the time is zero, it then updates it to nil,
|
||||
// which will insert/update the value to database as "null".
|
||||
if itemValue.IsZero() {
|
||||
m[k] = nil
|
||||
}
|
||||
|
||||
case uuid.UUID:
|
||||
m[k] = itemValue
|
||||
|
||||
case *time.Time:
|
||||
m[k] = itemValue
|
||||
// If the time is zero, it then updates it to nil,
|
||||
// which will insert/update the value to database as "null".
|
||||
if itemValue == nil || itemValue.IsZero() {
|
||||
m[k] = nil
|
||||
}
|
||||
|
||||
case gtime.Time:
|
||||
// for gtime type, needs to get time.Time
|
||||
m[k] = itemValue.Time
|
||||
// If the time is zero, it then updates it to nil,
|
||||
// which will insert/update the value to database as "null".
|
||||
if itemValue.IsZero() {
|
||||
m[k] = nil
|
||||
}
|
||||
|
||||
case *gtime.Time:
|
||||
// for gtime type, needs to get time.Time
|
||||
if itemValue != nil {
|
||||
m[k] = itemValue.Time
|
||||
}
|
||||
// If the time is zero, it then updates it to nil,
|
||||
// which will insert/update the value to database as "null".
|
||||
if itemValue == nil || itemValue.IsZero() {
|
||||
m[k] = nil
|
||||
}
|
||||
|
||||
case decimal.Decimal:
|
||||
m[k] = itemValue
|
||||
|
||||
case *decimal.Decimal:
|
||||
m[k] = nil
|
||||
if itemValue != nil {
|
||||
m[k] = *itemValue
|
||||
}
|
||||
|
||||
default:
|
||||
// if the other type implements valuer for the driver package
|
||||
// the converted result is used
|
||||
// otherwise the interface data is committed
|
||||
valuer, ok := itemValue.(driver.Valuer)
|
||||
if !ok {
|
||||
m[k] = itemValue
|
||||
continue
|
||||
}
|
||||
convertedValue, err := valuer.Value()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m[k] = convertedValue
|
||||
// ConvertValueForField converts value to the type of the record field.
|
||||
func (d *Driver) ConvertValueForField(ctx context.Context, fieldType string, fieldValue interface{}) (interface{}, error) {
|
||||
switch itemValue := fieldValue.(type) {
|
||||
case time.Time:
|
||||
// If the time is zero, it then updates it to nil,
|
||||
// which will insert/update the value to database as "null".
|
||||
if itemValue.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
case uuid.UUID:
|
||||
return itemValue, nil
|
||||
|
||||
case *time.Time:
|
||||
// If the time is zero, it then updates it to nil,
|
||||
// which will insert/update the value to database as "null".
|
||||
if itemValue == nil || itemValue.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
return itemValue, nil
|
||||
|
||||
case gtime.Time:
|
||||
// If the time is zero, it then updates it to nil,
|
||||
// which will insert/update the value to database as "null".
|
||||
if itemValue.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
// for gtime type, needs to get time.Time
|
||||
return itemValue.Time, nil
|
||||
|
||||
case *gtime.Time:
|
||||
// for gtime type, needs to get time.Time
|
||||
if itemValue != nil {
|
||||
return itemValue.Time, nil
|
||||
}
|
||||
// If the time is zero, it then updates it to nil,
|
||||
// which will insert/update the value to database as "null".
|
||||
if itemValue == nil || itemValue.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
case decimal.Decimal:
|
||||
return itemValue, nil
|
||||
|
||||
case *decimal.Decimal:
|
||||
if itemValue != nil {
|
||||
return *itemValue, nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
default:
|
||||
// if the other type implements valuer for the driver package
|
||||
// the converted result is used
|
||||
// otherwise the interface data is committed
|
||||
valuer, ok := itemValue.(driver.Valuer)
|
||||
if !ok {
|
||||
return itemValue, nil
|
||||
}
|
||||
convertedValue, err := valuer.Value()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return convertedValue, nil
|
||||
}
|
||||
return m, nil
|
||||
return fieldValue, nil
|
||||
}
|
||||
|
||||
// DoDelete does "DELETE FROM ... " statement for the table.
|
||||
func (d *Driver) DoDelete(ctx context.Context, link gdb.Link, table string, condition string, args ...interface{}) (result sql.Result, err error) {
|
||||
ctx = d.injectNeedParsedSql(ctx)
|
||||
return d.Core.DoDelete(ctx, link, table, condition, args...)
|
||||
}
|
||||
|
||||
// DoUpdate does "UPDATE ... " statement for the table.
|
||||
func (d *Driver) DoUpdate(ctx context.Context, link gdb.Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) {
|
||||
ctx = d.injectNeedParsedSql(ctx)
|
||||
return d.Core.DoUpdate(ctx, link, table, data, condition, args...)
|
||||
@ -435,10 +431,12 @@ func (d *Driver) Replace(ctx context.Context, table string, data interface{}, ba
|
||||
return nil, errUnsupportedReplace
|
||||
}
|
||||
|
||||
// Begin starts and returns the transaction object.
|
||||
func (d *Driver) Begin(ctx context.Context) (tx gdb.TX, err error) {
|
||||
return nil, errUnsupportedBegin
|
||||
}
|
||||
|
||||
// Transaction wraps the transaction logic using function `f`.
|
||||
func (d *Driver) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) error {
|
||||
return errUnsupportedTransaction
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ go 1.18
|
||||
|
||||
require (
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.0.15
|
||||
github.com/gogf/gf/v2 v2.5.2
|
||||
github.com/gogf/gf/v2 v2.5.3
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/shopspring/decimal v1.3.1
|
||||
)
|
||||
|
||||
@ -27,7 +27,6 @@ import (
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gtag"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
@ -52,16 +51,19 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// New create and returns a driver that implements gdb.Driver, which supports operations for dm.
|
||||
func New() gdb.Driver {
|
||||
return &Driver{}
|
||||
}
|
||||
|
||||
// New creates and returns a database object for dm.
|
||||
func (d *Driver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) {
|
||||
return &Driver{
|
||||
Core: core,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Open creates and returns an underlying sql.DB object for pgsql.
|
||||
func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
||||
var (
|
||||
source string
|
||||
@ -100,10 +102,13 @@ func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// GetChars returns the security char for this type of database.
|
||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||
return quoteChar, quoteChar
|
||||
}
|
||||
|
||||
// Tables retrieves and returns the tables of current schema.
|
||||
// It's mainly used in cli tool chain for automatically generating the models.
|
||||
func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string, err error) {
|
||||
var result gdb.Result
|
||||
// When schema is empty, return the default link
|
||||
@ -126,9 +131,8 @@ func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string,
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Driver) TableFields(
|
||||
ctx context.Context, table string, schema ...string,
|
||||
) (fields map[string]*gdb.TableField, err error) {
|
||||
// TableFields retrieves and returns the fields' information of specified table of current schema.
|
||||
func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
@ -173,49 +177,44 @@ func (d *Driver) TableFields(
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
// ConvertDataForRecord converting for any data that will be inserted into table/collection as a record.
|
||||
func (d *Driver) ConvertDataForRecord(ctx context.Context, value interface{}) (map[string]interface{}, error) {
|
||||
m := gconv.Map(value, gtag.ORM)
|
||||
|
||||
// transforms a value of a particular type
|
||||
for k, v := range m {
|
||||
switch itemValue := v.(type) {
|
||||
// dm does not support time.Time, it so here converts it to time string that it supports.
|
||||
case time.Time:
|
||||
m[k] = gtime.New(itemValue).String()
|
||||
// If the time is zero, it then updates it to nil,
|
||||
// which will insert/update the value to database as "null".
|
||||
if itemValue.IsZero() {
|
||||
m[k] = nil
|
||||
}
|
||||
|
||||
// dm does not support time.Time, it so here converts it to time string that it supports.
|
||||
case *time.Time:
|
||||
m[k] = gtime.New(itemValue).String()
|
||||
// If the time is zero, it then updates it to nil,
|
||||
// which will insert/update the value to database as "null".
|
||||
if itemValue == nil || itemValue.IsZero() {
|
||||
m[k] = nil
|
||||
}
|
||||
// ConvertValueForField converts value to the type of the record field.
|
||||
func (d *Driver) ConvertValueForField(ctx context.Context, fieldType string, fieldValue interface{}) (interface{}, error) {
|
||||
switch itemValue := fieldValue.(type) {
|
||||
// dm does not support time.Time, it so here converts it to time string that it supports.
|
||||
case time.Time:
|
||||
// If the time is zero, it then updates it to nil,
|
||||
// which will insert/update the value to database as "null".
|
||||
if itemValue.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
return gtime.New(itemValue).String(), nil
|
||||
|
||||
// dm does not support time.Time, it so here converts it to time string that it supports.
|
||||
case *time.Time:
|
||||
// If the time is zero, it then updates it to nil,
|
||||
// which will insert/update the value to database as "null".
|
||||
if itemValue == nil || itemValue.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
return gtime.New(itemValue).String(), nil
|
||||
}
|
||||
return m, nil
|
||||
|
||||
return fieldValue, nil
|
||||
}
|
||||
|
||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
|
||||
defer func() {
|
||||
newSql, newArgs, err = d.Core.DoFilter(ctx, link, newSql, newArgs)
|
||||
}()
|
||||
// There should be no need to capitalize, because it has been done from field processing before
|
||||
newSql, err = gregex.ReplaceString(`["\n\t]`, "", sql)
|
||||
newSql = gstr.ReplaceI(newSql, "GROUP_CONCAT", "WM_CONCAT")
|
||||
// gutil.Dump("Driver.DoFilter()::newSql", newSql)
|
||||
newArgs = args
|
||||
// gutil.Dump("Driver.DoFilter()::newArgs", newArgs)
|
||||
return
|
||||
newSql, _ = gregex.ReplaceString(`["\n\t]`, "", sql)
|
||||
return d.Core.DoFilter(
|
||||
ctx,
|
||||
link,
|
||||
gstr.ReplaceI(newSql, "GROUP_CONCAT", "WM_CONCAT"),
|
||||
args,
|
||||
)
|
||||
}
|
||||
|
||||
// DoInsert inserts or updates data forF given table.
|
||||
func (d *Driver) DoInsert(
|
||||
ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
|
||||
) (result sql.Result, err error) {
|
||||
|
||||
@ -6,7 +6,7 @@ replace github.com/gogf/gf/v2 => ../../../
|
||||
|
||||
require (
|
||||
gitee.com/chunanyong/dm v1.8.10
|
||||
github.com/gogf/gf/v2 v2.5.2
|
||||
github.com/gogf/gf/v2 v2.5.3
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@ -4,7 +4,7 @@ go 1.18
|
||||
|
||||
require (
|
||||
github.com/denisenkom/go-mssqldb v0.12.3
|
||||
github.com/gogf/gf/v2 v2.5.2
|
||||
github.com/gogf/gf/v2 v2.5.3
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@ -104,17 +104,14 @@ func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||
|
||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
|
||||
defer func() {
|
||||
newSql, newArgs, err = d.Core.DoFilter(ctx, link, newSql, newArgs)
|
||||
}()
|
||||
var index int
|
||||
// Convert placeholder char '?' to string "@px".
|
||||
str, _ := gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
|
||||
newSql, _ = gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
|
||||
index++
|
||||
return fmt.Sprintf("@p%d", index)
|
||||
})
|
||||
str, _ = gregex.ReplaceString("\"", "", str)
|
||||
return d.parseSql(str), args, nil
|
||||
newSql, _ = gregex.ReplaceString("\"", "", newSql)
|
||||
return d.Core.DoFilter(ctx, link, d.parseSql(newSql), args)
|
||||
}
|
||||
|
||||
// parseSql does some replacement of the sql before commits it to underlying driver,
|
||||
@ -299,14 +296,20 @@ ORDER BY a.id,a.colorder`,
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
// DoInsert is not supported in mssql.
|
||||
// DoInsert inserts or updates data forF given table.
|
||||
func (d *Driver) DoInsert(ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption) (result sql.Result, err error) {
|
||||
switch option.InsertOption {
|
||||
case gdb.InsertOptionSave:
|
||||
return nil, gerror.NewCode(gcode.CodeNotSupported, `Save operation is not supported by mssql driver`)
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeNotSupported,
|
||||
`Save operation is not supported by mssql driver`,
|
||||
)
|
||||
|
||||
case gdb.InsertOptionReplace:
|
||||
return nil, gerror.NewCode(gcode.CodeNotSupported, `Replace operation is not supported by mssql driver`)
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeNotSupported,
|
||||
`Replace operation is not supported by mssql driver`,
|
||||
)
|
||||
|
||||
default:
|
||||
return d.Core.DoInsert(ctx, link, table, list, option)
|
||||
|
||||
@ -319,7 +319,7 @@ func Test_DB_Insert_KeyFieldNameMapping(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DB_Upadte_KeyFieldNameMapping(t *testing.T) {
|
||||
func Test_DB_Update_KeyFieldNameMapping(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ go 1.18
|
||||
|
||||
require (
|
||||
github.com/go-sql-driver/mysql v1.7.1
|
||||
github.com/gogf/gf/v2 v2.5.2
|
||||
github.com/gogf/gf/v2 v2.5.3
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@ -15,6 +15,7 @@ import (
|
||||
"strings"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
@ -143,9 +144,7 @@ func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string,
|
||||
//
|
||||
// It's using cache feature to enhance the performance, which is never expired util the
|
||||
// process restarts.
|
||||
func (d *Driver) TableFields(
|
||||
ctx context.Context, table string, schema ...string,
|
||||
) (fields map[string]*gdb.TableField, err error) {
|
||||
func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
|
||||
@ -10,10 +10,7 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/go-sql-driver/mysql"
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
@ -32,61 +29,6 @@ func Test_Instance(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// Fix issue: https://github.com/gogf/gf/issues/819
|
||||
func Test_Func_ConvertDataForRecord(t *testing.T) {
|
||||
type Test struct {
|
||||
ResetPasswordTokenAt mysql.NullTime `orm:"reset_password_token_at"`
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
c := &gdb.Core{}
|
||||
m, err := c.ConvertDataForRecord(nil, new(Test))
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(m), 1)
|
||||
t.Assert(m["reset_password_token_at"], nil)
|
||||
})
|
||||
|
||||
type TestNil struct {
|
||||
JsonEmptyString *gjson.Json `orm:"json_empty_string"`
|
||||
JsonNil *gjson.Json `orm:"json_nil"`
|
||||
JsonNull *gjson.Json `orm:"json_null"`
|
||||
VarEmptyString *gvar.Var `orm:"var_empty_string"`
|
||||
VarNil *gvar.Var `orm:"var_nil"`
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
c := &gdb.Core{}
|
||||
m, err := c.ConvertDataForRecord(nil, TestNil{
|
||||
JsonEmptyString: gjson.New(""),
|
||||
JsonNil: gjson.New(nil),
|
||||
JsonNull: gjson.New(struct{}{}),
|
||||
VarEmptyString: gvar.New(""),
|
||||
VarNil: gvar.New(nil),
|
||||
})
|
||||
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(m), 5)
|
||||
|
||||
valueEmptyString, exist := m["json_empty_string"]
|
||||
t.Assert(exist, true)
|
||||
t.Assert(valueEmptyString, nil)
|
||||
|
||||
valueNil, exist := m["json_nil"]
|
||||
t.Assert(exist, true)
|
||||
t.Assert(valueNil, nil)
|
||||
|
||||
valueNull, exist := m["json_null"]
|
||||
t.Assert(exist, true)
|
||||
t.Assert(valueNull, "null")
|
||||
|
||||
valueEmptyString, exist = m["var_empty_string"]
|
||||
t.Assert(exist, true)
|
||||
t.Assert(valueEmptyString, "")
|
||||
|
||||
valueNil, exist = m["var_nil"]
|
||||
t.Assert(exist, true)
|
||||
t.Assert(valueNil, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Func_FormatSqlWithArgs(t *testing.T) {
|
||||
// mysql
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
|
||||
@ -304,7 +304,7 @@ func Test_DB_Insert_NilGjson(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DB_Upadte_KeyFieldNameMapping(t *testing.T) {
|
||||
func Test_DB_Update_KeyFieldNameMapping(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
@ -1404,29 +1404,18 @@ func Test_Model_LeftJoin(t *testing.T) {
|
||||
defer dropTable(table2)
|
||||
|
||||
res, err := db.Model(table2).Where("id > ?", 3).Delete()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.AssertNil(err)
|
||||
|
||||
n, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
t.Assert(n, 7)
|
||||
}
|
||||
t.AssertNil(err)
|
||||
t.Assert(n, 7)
|
||||
|
||||
result, err := db.Model(table1+" u1").LeftJoin(table2+" u2", "u1.id = u2.id").All()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 10)
|
||||
|
||||
result, err = db.Model(table1+" u1").LeftJoin(table2+" u2", "u1.id = u2.id").Where("u1.id > ? ", 2).All()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 8)
|
||||
})
|
||||
}
|
||||
|
||||
@ -83,6 +83,75 @@ func Test_Model_InnerJoinOnField(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_LeftJoinOnFields(t *testing.T) {
|
||||
var (
|
||||
table1 = gtime.TimestampNanoStr() + "_table1"
|
||||
table2 = gtime.TimestampNanoStr() + "_table2"
|
||||
)
|
||||
createInitTable(table1)
|
||||
defer dropTable(table1)
|
||||
createInitTable(table2)
|
||||
defer dropTable(table2)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Model(table1).
|
||||
FieldsPrefix(table1, "*").
|
||||
LeftJoinOnFields(table2, "id", "=", "id").
|
||||
WhereIn("id", g.Slice{1, 2}).
|
||||
Order("id asc").All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(r), 2)
|
||||
t.Assert(r[0]["id"], "1")
|
||||
t.Assert(r[1]["id"], "2")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_RightJoinOnFields(t *testing.T) {
|
||||
var (
|
||||
table1 = gtime.TimestampNanoStr() + "_table1"
|
||||
table2 = gtime.TimestampNanoStr() + "_table2"
|
||||
)
|
||||
createInitTable(table1)
|
||||
defer dropTable(table1)
|
||||
createInitTable(table2)
|
||||
defer dropTable(table2)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Model(table1).
|
||||
FieldsPrefix(table1, "*").
|
||||
RightJoinOnFields(table2, "id", "=", "id").
|
||||
WhereIn("id", g.Slice{1, 2}).
|
||||
Order("id asc").All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(r), 2)
|
||||
t.Assert(r[0]["id"], "1")
|
||||
t.Assert(r[1]["id"], "2")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_InnerJoinOnFields(t *testing.T) {
|
||||
var (
|
||||
table1 = gtime.TimestampNanoStr() + "_table1"
|
||||
table2 = gtime.TimestampNanoStr() + "_table2"
|
||||
)
|
||||
createInitTable(table1)
|
||||
defer dropTable(table1)
|
||||
createInitTable(table2)
|
||||
defer dropTable(table2)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Model(table1).
|
||||
FieldsPrefix(table1, "*").
|
||||
InnerJoinOnFields(table2, "id", "=", "id").
|
||||
WhereIn("id", g.Slice{1, 2}).
|
||||
Order("id asc").All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(r), 2)
|
||||
t.Assert(r[0]["id"], "1")
|
||||
t.Assert(r[1]["id"], "2")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_FieldsPrefix(t *testing.T) {
|
||||
var (
|
||||
table1 = gtime.TimestampNanoStr() + "_table1"
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/drivers/oracle/v2
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.5.2
|
||||
github.com/gogf/gf/v2 v2.5.3
|
||||
github.com/sijms/go-ora/v2 v2.7.10
|
||||
)
|
||||
|
||||
|
||||
@ -114,10 +114,6 @@ func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||
|
||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
|
||||
defer func() {
|
||||
newSql, newArgs, err = d.Core.DoFilter(ctx, link, newSql, newArgs)
|
||||
}()
|
||||
|
||||
var index int
|
||||
// Convert placeholder char '?' to string ":vx".
|
||||
newSql, _ = gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
|
||||
@ -125,10 +121,7 @@ func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args [
|
||||
return fmt.Sprintf(":v%d", index)
|
||||
})
|
||||
newSql, _ = gregex.ReplaceString("\"", "", newSql)
|
||||
|
||||
newSql = d.parseSql(newSql)
|
||||
newArgs = args
|
||||
return
|
||||
return d.Core.DoFilter(ctx, link, d.parseSql(newSql), args)
|
||||
}
|
||||
|
||||
// parseSql does some replacement of the sql before commits it to underlying driver,
|
||||
@ -214,9 +207,7 @@ func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string,
|
||||
// TableFields retrieves and returns the fields' information of specified table of current schema.
|
||||
//
|
||||
// Also see DriverMysql.TableFields.
|
||||
func (d *Driver) TableFields(
|
||||
ctx context.Context, table string, schema ...string,
|
||||
) (fields map[string]*gdb.TableField, err error) {
|
||||
func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
@ -259,17 +250,6 @@ FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`,
|
||||
}
|
||||
|
||||
// DoInsert inserts or updates data for given table.
|
||||
// This function is usually used for custom interface definition, you do not need call it manually.
|
||||
// The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc.
|
||||
// Eg:
|
||||
// Data(g.Map{"uid": 10000, "name":"john"})
|
||||
// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
|
||||
//
|
||||
// The parameter `option` values are as follows:
|
||||
// 0: insert: just insert, if there's unique/primary key in the data, it returns error;
|
||||
// 1: replace: if there's unique/primary key in the data, it deletes it from table and inserts a new one;
|
||||
// 2: save: if there's unique/primary key in the data, it updates it or else inserts a new one;
|
||||
// 3: ignore: if there's unique/primary key in the data, it ignores the inserting;
|
||||
func (d *Driver) DoInsert(
|
||||
ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
|
||||
) (result sql.Result, err error) {
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/drivers/pgsql/v2
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.5.2
|
||||
github.com/gogf/gf/v2 v2.5.3
|
||||
github.com/lib/pq v1.10.9
|
||||
)
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
//
|
||||
// Note:
|
||||
// 1. It does not support Save/Replace features.
|
||||
// 2. It does not support Insert Ignore features.
|
||||
package pgsql
|
||||
|
||||
import (
|
||||
@ -122,7 +123,7 @@ func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||
}
|
||||
|
||||
// CheckLocalTypeForField checks and returns corresponding local golang type for given db type.
|
||||
func (d *Driver) CheckLocalTypeForField(ctx context.Context, fieldType string, fieldValue interface{}) (string, error) {
|
||||
func (d *Driver) CheckLocalTypeForField(ctx context.Context, fieldType string, fieldValue interface{}) (gdb.LocalType, error) {
|
||||
var typeName string
|
||||
match, _ := gregex.MatchString(`(.+?)\((.+)\)`, fieldType)
|
||||
if len(match) == 3 {
|
||||
@ -204,24 +205,21 @@ func (d *Driver) ConvertValueForLocal(ctx context.Context, fieldType string, fie
|
||||
|
||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
|
||||
defer func() {
|
||||
newSql, newArgs, err = d.Core.DoFilter(ctx, link, newSql, newArgs)
|
||||
}()
|
||||
var index int
|
||||
// Convert placeholder char '?' to string "$x".
|
||||
sql, _ = gregex.ReplaceStringFunc(`\?`, sql, func(s string) string {
|
||||
newSql, _ = gregex.ReplaceStringFunc(`\?`, sql, func(s string) string {
|
||||
index++
|
||||
return fmt.Sprintf(`$%d`, index)
|
||||
})
|
||||
// Handle pgsql jsonb feature support, which contains place holder char '?'.
|
||||
// Handle pgsql jsonb feature support, which contains place-holder char '?'.
|
||||
// Refer:
|
||||
// https://github.com/gogf/gf/issues/1537
|
||||
// https://www.postgresql.org/docs/12/functions-json.html
|
||||
sql, _ = gregex.ReplaceStringFuncMatch(`(::jsonb([^\w\d]*)\$\d)`, sql, func(match []string) string {
|
||||
newSql, _ = gregex.ReplaceStringFuncMatch(`(::jsonb([^\w\d]*)\$\d)`, newSql, func(match []string) string {
|
||||
return fmt.Sprintf(`::jsonb%s?`, match[2])
|
||||
})
|
||||
newSql, _ = gregex.ReplaceString(` LIMIT (\d+),\s*(\d+)`, ` LIMIT $2 OFFSET $1`, sql)
|
||||
return newSql, args, nil
|
||||
newSql, _ = gregex.ReplaceString(` LIMIT (\d+),\s*(\d+)`, ` LIMIT $2 OFFSET $1`, newSql)
|
||||
return d.Core.DoFilter(ctx, link, newSql, args)
|
||||
}
|
||||
|
||||
// Tables retrieves and returns the tables of current schema.
|
||||
@ -269,8 +267,6 @@ ORDER BY
|
||||
}
|
||||
|
||||
// TableFields retrieves and returns the fields' information of specified table of current schema.
|
||||
//
|
||||
// Also see DriverMysql.TableFields.
|
||||
func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
@ -328,7 +324,7 @@ ORDER BY a.attnum`,
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
// DoInsert is not supported in pgsql.
|
||||
// DoInsert inserts or updates data forF given table.
|
||||
func (d *Driver) DoInsert(ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption) (result sql.Result, err error) {
|
||||
switch option.InsertOption {
|
||||
case gdb.InsertOptionSave:
|
||||
@ -364,6 +360,8 @@ func (d *Driver) DoInsert(ctx context.Context, link gdb.Link, table string, list
|
||||
return d.Core.DoInsert(ctx, link, table, list, option)
|
||||
}
|
||||
|
||||
// DoExec commits the sql string and its arguments to underlying driver
|
||||
// through given link object and returns the execution result.
|
||||
func (d *Driver) DoExec(ctx context.Context, link gdb.Link, sql string, args ...interface{}) (result sql.Result, err error) {
|
||||
var (
|
||||
isUseCoreDoExec bool = false // Check whether the default method needs to be used
|
||||
|
||||
@ -4,7 +4,7 @@ go 1.18
|
||||
|
||||
require (
|
||||
github.com/glebarez/go-sqlite v1.21.2
|
||||
github.com/gogf/gf/v2 v2.5.2
|
||||
github.com/gogf/gf/v2 v2.5.3
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@ -7,14 +7,13 @@
|
||||
// Package sqlite implements gdb.Driver, which supports operations for database SQLite.
|
||||
//
|
||||
// Note:
|
||||
// 1. It does not support Save/Replace features.
|
||||
// 1. It does not support Save features.
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
_ "github.com/glebarez/go-sqlite"
|
||||
|
||||
@ -114,6 +113,22 @@ func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||
|
||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
|
||||
// Special insert/ignore operation for sqlite.
|
||||
switch {
|
||||
case gstr.HasPrefix(sql, gdb.InsertOperationIgnore):
|
||||
sql = "INSERT OR IGNORE" + sql[len(gdb.InsertOperationIgnore):]
|
||||
|
||||
case gstr.HasPrefix(sql, gdb.InsertOperationReplace):
|
||||
sql = "INSERT OR REPLACE" + sql[len(gdb.InsertOperationReplace):]
|
||||
|
||||
default:
|
||||
if gstr.Contains(sql, gdb.InsertOnDuplicateKeyUpdate) {
|
||||
return sql, args, gerror.NewCode(
|
||||
gcode.CodeNotSupported,
|
||||
`Save operation is not supported by sqlite driver`,
|
||||
)
|
||||
}
|
||||
}
|
||||
return d.Core.DoFilter(ctx, link, sql, args)
|
||||
}
|
||||
|
||||
@ -126,7 +141,11 @@ func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err = d.DoSelect(ctx, link, `SELECT NAME FROM SQLITE_MASTER WHERE TYPE='table' ORDER BY NAME`)
|
||||
result, err = d.DoSelect(
|
||||
ctx,
|
||||
link,
|
||||
`SELECT NAME FROM SQLITE_MASTER WHERE TYPE='table' ORDER BY NAME`,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -141,9 +160,7 @@ func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string,
|
||||
// TableFields retrieves and returns the fields' information of specified table of current schema.
|
||||
//
|
||||
// Also see DriverMysql.TableFields.
|
||||
func (d *Driver) TableFields(
|
||||
ctx context.Context, table string, schema ...string,
|
||||
) (fields map[string]*gdb.TableField, err error) {
|
||||
func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
@ -173,83 +190,3 @@ func (d *Driver) TableFields(
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
// DoInsert is not supported in sqlite.
|
||||
func (d *Driver) DoInsert(
|
||||
ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
|
||||
) (result sql.Result, err error) {
|
||||
switch option.InsertOption {
|
||||
case gdb.InsertOptionSave:
|
||||
return nil, gerror.NewCode(gcode.CodeNotSupported, `Save operation is not supported by sqlite driver`)
|
||||
|
||||
case gdb.InsertOptionIgnore, gdb.InsertOptionReplace:
|
||||
var (
|
||||
keys []string // Field names.
|
||||
values []string // Value holder string array, like: (?,?,?)
|
||||
params []interface{} // Values that will be committed to underlying database driver.
|
||||
onDuplicateStr string // onDuplicateStr is used in "ON DUPLICATE KEY UPDATE" statement.
|
||||
)
|
||||
// Handle the field names and placeholders.
|
||||
for k := range list[0] {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
// Prepare the batch result pointer.
|
||||
var (
|
||||
charL, charR = d.GetChars()
|
||||
batchResult = new(gdb.SqlResult)
|
||||
keysStr = charL + strings.Join(keys, charR+","+charL) + charR
|
||||
operation = "INSERT OR IGNORE"
|
||||
)
|
||||
|
||||
if option.InsertOption == gdb.InsertOptionReplace {
|
||||
operation = "INSERT OR REPLACE"
|
||||
}
|
||||
var (
|
||||
listLength = len(list)
|
||||
valueHolder = make([]string, 0)
|
||||
)
|
||||
for i := 0; i < listLength; i++ {
|
||||
values = values[:0]
|
||||
// Note that the map type is unordered,
|
||||
// so it should use slice+key to retrieve the value.
|
||||
for _, k := range keys {
|
||||
if s, ok := list[i][k].(gdb.Raw); ok {
|
||||
values = append(values, gconv.String(s))
|
||||
} else {
|
||||
values = append(values, "?")
|
||||
params = append(params, list[i][k])
|
||||
}
|
||||
}
|
||||
valueHolder = append(valueHolder, "("+gstr.Join(values, ",")+")")
|
||||
// Batch package checks: It meets the batch number, or it is the last element.
|
||||
if len(valueHolder) == option.BatchCount || (i == listLength-1 && len(valueHolder) > 0) {
|
||||
var (
|
||||
stdSqlResult sql.Result
|
||||
affectedRows int64
|
||||
)
|
||||
stdSqlResult, err = d.DoExec(ctx, link, fmt.Sprintf(
|
||||
"%s INTO %s(%s) VALUES%s %s",
|
||||
operation, d.QuotePrefixTableName(table), keysStr,
|
||||
gstr.Join(valueHolder, ","),
|
||||
onDuplicateStr,
|
||||
), params...)
|
||||
if err != nil {
|
||||
return stdSqlResult, err
|
||||
}
|
||||
if affectedRows, err = stdSqlResult.RowsAffected(); err != nil {
|
||||
err = gerror.WrapCode(gcode.CodeDbOperationError, err, `sql.Result.RowsAffected failed`)
|
||||
return stdSqlResult, err
|
||||
} else {
|
||||
batchResult.Result = stdSqlResult
|
||||
batchResult.Affected += affectedRows
|
||||
}
|
||||
params = params[:0]
|
||||
valueHolder = valueHolder[:0]
|
||||
}
|
||||
}
|
||||
return batchResult, nil
|
||||
|
||||
default:
|
||||
return d.Core.DoInsert(ctx, link, table, list, option)
|
||||
}
|
||||
}
|
||||
|
||||
@ -272,7 +272,7 @@ func Test_DB_Insert_KeyFieldNameMapping(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DB_Upadte_KeyFieldNameMapping(t *testing.T) {
|
||||
func Test_DB_Update_KeyFieldNameMapping(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
|
||||
@ -2787,34 +2787,6 @@ func Test_Model_Fields_Struct(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// func Test_Model_NullField(t *testing.T) {
|
||||
// table := createTable()
|
||||
// defer dropTable(table)
|
||||
|
||||
// gtest.C(t, func(t *gtest.T) {
|
||||
// type User struct {
|
||||
// Id int
|
||||
// Passport *string
|
||||
// }
|
||||
// data := g.Map{
|
||||
// "id": 1,
|
||||
// "passport": nil,
|
||||
// }
|
||||
// result, err := db.Model(table).Data(data).Insert()
|
||||
// t.AssertNil(err)
|
||||
// n, _ := result.RowsAffected()
|
||||
// t.Assert(n, 1)
|
||||
// one, err := db.Model(table).WherePri(1).One()
|
||||
// t.AssertNil(err)
|
||||
|
||||
// var user *User
|
||||
// err = one.Struct(&user)
|
||||
// t.AssertNil(err)
|
||||
// t.Assert(user.Id, data["id"])
|
||||
// t.Assert(user.Passport, data["passport"])
|
||||
// })
|
||||
// }
|
||||
|
||||
func Test_Model_Empty_Slice_Argument(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
@ -3222,17 +3194,6 @@ func Test_Model_WhereNotNull(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// func Test_Model_WhereOrNull(t *testing.T) {
|
||||
// table := createInitTable()
|
||||
// defer dropTable(table)
|
||||
|
||||
// gtest.C(t, func(t *gtest.T) {
|
||||
// result, err := db.Model(table).WhereOrNull("nickname").WhereOrNull("passport").OrderAsc("id").OrderRandom().All()
|
||||
// t.AssertNil(err)
|
||||
// t.Assert(len(result), 0)
|
||||
// })
|
||||
// }
|
||||
|
||||
func Test_Model_WhereOrNotNull(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
@ -3950,56 +3911,6 @@ func Test_Model_WherePrefixLike(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TODO
|
||||
// https://github.com/gogf/gf/issues/1700
|
||||
// func Test_Model_Issue1700(t *testing.T) {
|
||||
// table := "user_" + gtime.Now().TimestampNanoStr()
|
||||
// if _, err := db.Exec(ctx, fmt.Sprintf(`
|
||||
// CREATE TABLE IF NOT EXISTS %s (
|
||||
// id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
// UNIQUE
|
||||
// NOT NULL,
|
||||
// user_id int(10) NOT NULL,
|
||||
// UserId int(10) NOT NULL
|
||||
// );
|
||||
// `, table,
|
||||
// )); err != nil {
|
||||
// gtest.AssertNil(err)
|
||||
// }
|
||||
// defer dropTable(table)
|
||||
|
||||
// gtest.C(t, func(t *gtest.T) {
|
||||
// type User struct {
|
||||
// Id int `orm:"id"`
|
||||
// Userid int `orm:"user_id"`
|
||||
// UserId int `orm:"UserId"`
|
||||
// }
|
||||
// _, err := db.Model(table).Data(User{
|
||||
// Id: 1,
|
||||
// Userid: 2,
|
||||
// UserId: 3,
|
||||
// }).Insert()
|
||||
// t.AssertNil(err)
|
||||
|
||||
// one, err := db.Model(table).One()
|
||||
// t.AssertNil(err)
|
||||
// t.Assert(one, g.Map{
|
||||
// "id": 1,
|
||||
// "user_id": 2,
|
||||
// "UserId": 3,
|
||||
// })
|
||||
|
||||
// for i := 0; i < 1000; i++ {
|
||||
// var user *User
|
||||
// err = db.Model(table).Scan(&user)
|
||||
// t.AssertNil(err)
|
||||
// t.Assert(user.Id, 1)
|
||||
// t.Assert(user.Userid, 2)
|
||||
// t.Assert(user.UserId, 3)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
// https://github.com/gogf/gf/issues/1159
|
||||
func Test_ScanList_NoRecreate_PtrAttribute(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/drivers/sqlitecgo/v2
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.5.2
|
||||
github.com/gogf/gf/v2 v2.5.3
|
||||
github.com/mattn/go-sqlite3 v1.14.17
|
||||
)
|
||||
|
||||
|
||||
@ -7,15 +7,15 @@
|
||||
// Package sqlitecgo implements gdb.Driver, which supports operations for database SQLite.
|
||||
//
|
||||
// Note:
|
||||
// 1. Using sqlitecgo is for building a 32-bit Windows operating system
|
||||
// 2. You need to set the environment variable CGO_ENABLED=1 and make sure that GCC is installed on your path. windows gcc: https://jmeubank.github.io/tdm-gcc/
|
||||
// 1. Using sqlitecgo is for building a 32-bit Windows operating system
|
||||
// 2. You need to set the environment variable CGO_ENABLED=1 and make sure that GCC is installed
|
||||
// on your path. windows gcc: https://jmeubank.github.io/tdm-gcc/
|
||||
package sqlitecgo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
|
||||
@ -115,6 +115,22 @@ func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||
|
||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
|
||||
// Special insert/ignore operation for sqlite.
|
||||
switch {
|
||||
case gstr.HasPrefix(sql, gdb.InsertOperationIgnore):
|
||||
sql = "INSERT OR IGNORE" + sql[len(gdb.InsertOperationIgnore):]
|
||||
|
||||
case gstr.HasPrefix(sql, gdb.InsertOperationReplace):
|
||||
sql = "INSERT OR REPLACE" + sql[len(gdb.InsertOperationReplace):]
|
||||
|
||||
default:
|
||||
if gstr.Contains(sql, gdb.InsertOnDuplicateKeyUpdate) {
|
||||
return sql, args, gerror.NewCode(
|
||||
gcode.CodeNotSupported,
|
||||
`Save operation is not supported by sqlite driver`,
|
||||
)
|
||||
}
|
||||
}
|
||||
return d.Core.DoFilter(ctx, link, sql, args)
|
||||
}
|
||||
|
||||
@ -127,7 +143,11 @@ func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err = d.DoSelect(ctx, link, `SELECT NAME FROM SQLITE_MASTER WHERE TYPE='table' ORDER BY NAME`)
|
||||
result, err = d.DoSelect(
|
||||
ctx,
|
||||
link,
|
||||
`SELECT NAME FROM SQLITE_MASTER WHERE TYPE='table' ORDER BY NAME`,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -142,9 +162,7 @@ func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string,
|
||||
// TableFields retrieves and returns the fields' information of specified table of current schema.
|
||||
//
|
||||
// Also see DriverMysql.TableFields.
|
||||
func (d *Driver) TableFields(
|
||||
ctx context.Context, table string, schema ...string,
|
||||
) (fields map[string]*gdb.TableField, err error) {
|
||||
func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
@ -174,83 +192,3 @@ func (d *Driver) TableFields(
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
// DoInsert is not supported in sqlite.
|
||||
func (d *Driver) DoInsert(
|
||||
ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
|
||||
) (result sql.Result, err error) {
|
||||
switch option.InsertOption {
|
||||
case gdb.InsertOptionSave:
|
||||
return nil, gerror.NewCode(gcode.CodeNotSupported, `Save operation is not supported by sqlite driver`)
|
||||
|
||||
case gdb.InsertOptionIgnore, gdb.InsertOptionReplace:
|
||||
var (
|
||||
keys []string // Field names.
|
||||
values []string // Value holder string array, like: (?,?,?)
|
||||
params []interface{} // Values that will be committed to underlying database driver.
|
||||
onDuplicateStr string // onDuplicateStr is used in "ON DUPLICATE KEY UPDATE" statement.
|
||||
)
|
||||
// Handle the field names and placeholders.
|
||||
for k := range list[0] {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
// Prepare the batch result pointer.
|
||||
var (
|
||||
charL, charR = d.GetChars()
|
||||
batchResult = new(gdb.SqlResult)
|
||||
keysStr = charL + strings.Join(keys, charR+","+charL) + charR
|
||||
operation = "INSERT OR IGNORE"
|
||||
)
|
||||
|
||||
if option.InsertOption == gdb.InsertOptionReplace {
|
||||
operation = "INSERT OR REPLACE"
|
||||
}
|
||||
var (
|
||||
listLength = len(list)
|
||||
valueHolder = make([]string, 0)
|
||||
)
|
||||
for i := 0; i < listLength; i++ {
|
||||
values = values[:0]
|
||||
// Note that the map type is unordered,
|
||||
// so it should use slice+key to retrieve the value.
|
||||
for _, k := range keys {
|
||||
if s, ok := list[i][k].(gdb.Raw); ok {
|
||||
values = append(values, gconv.String(s))
|
||||
} else {
|
||||
values = append(values, "?")
|
||||
params = append(params, list[i][k])
|
||||
}
|
||||
}
|
||||
valueHolder = append(valueHolder, "("+gstr.Join(values, ",")+")")
|
||||
// Batch package checks: It meets the batch number, or it is the last element.
|
||||
if len(valueHolder) == option.BatchCount || (i == listLength-1 && len(valueHolder) > 0) {
|
||||
var (
|
||||
stdSqlResult sql.Result
|
||||
affectedRows int64
|
||||
)
|
||||
stdSqlResult, err = d.DoExec(ctx, link, fmt.Sprintf(
|
||||
"%s INTO %s(%s) VALUES%s %s",
|
||||
operation, d.QuotePrefixTableName(table), keysStr,
|
||||
gstr.Join(valueHolder, ","),
|
||||
onDuplicateStr,
|
||||
), params...)
|
||||
if err != nil {
|
||||
return stdSqlResult, err
|
||||
}
|
||||
if affectedRows, err = stdSqlResult.RowsAffected(); err != nil {
|
||||
err = gerror.WrapCode(gcode.CodeDbOperationError, err, `sql.Result.RowsAffected failed`)
|
||||
return stdSqlResult, err
|
||||
} else {
|
||||
batchResult.Result = stdSqlResult
|
||||
batchResult.Affected += affectedRows
|
||||
}
|
||||
params = params[:0]
|
||||
valueHolder = valueHolder[:0]
|
||||
}
|
||||
}
|
||||
return batchResult, nil
|
||||
|
||||
default:
|
||||
return d.Core.DoInsert(ctx, link, table, list, option)
|
||||
}
|
||||
}
|
||||
|
||||
@ -272,7 +272,7 @@ func Test_DB_Insert_KeyFieldNameMapping(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DB_Upadte_KeyFieldNameMapping(t *testing.T) {
|
||||
func Test_DB_Update_KeyFieldNameMapping(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/nosql/redis/v2
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.5.2
|
||||
github.com/gogf/gf/v2 v2.5.3
|
||||
github.com/redis/go-redis/v9 v9.0.5
|
||||
go.opentelemetry.io/otel v1.14.0
|
||||
go.opentelemetry.io/otel/trace v1.14.0
|
||||
|
||||
@ -9,6 +9,7 @@ package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
@ -67,7 +68,7 @@ func New(config *gredis.Config) *Redis {
|
||||
redisSentinel := opts.Failover()
|
||||
redisSentinel.ReplicaOnly = config.SlaveOnly
|
||||
client = redis.NewFailoverClient(redisSentinel)
|
||||
} else if len(opts.Addrs) > 1 {
|
||||
} else if len(opts.Addrs) > 1 || config.Cluster {
|
||||
client = redis.NewClusterClient(opts.Cluster())
|
||||
} else {
|
||||
client = redis.NewClient(opts.Simple())
|
||||
@ -135,4 +136,9 @@ func fillWithDefaultConfiguration(config *gredis.Config) {
|
||||
if config.ReadTimeout == 0 {
|
||||
config.ReadTimeout = -1
|
||||
}
|
||||
if config.TLSConfig == nil && config.TLS {
|
||||
config.TLSConfig = &tls.Config{
|
||||
InsecureSkipVerify: config.TLSSkipVerify,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/registry/etcd/v2
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.5.2
|
||||
github.com/gogf/gf/v2 v2.5.3
|
||||
go.etcd.io/etcd/client/v3 v3.5.7
|
||||
)
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ module github.com/gogf/gf/contrib/registry/file/v2
|
||||
|
||||
go 1.18
|
||||
|
||||
require github.com/gogf/gf/v2 v2.5.2
|
||||
require github.com/gogf/gf/v2 v2.5.3
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.2.0 // indirect
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/registry/polaris/v2
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.5.2
|
||||
github.com/gogf/gf/v2 v2.5.3
|
||||
github.com/polarismesh/polaris-go v1.5.1
|
||||
)
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ go 1.18
|
||||
|
||||
require (
|
||||
github.com/go-zookeeper/zk v1.0.3
|
||||
github.com/gogf/gf/v2 v2.5.2
|
||||
github.com/gogf/gf/v2 v2.5.3
|
||||
golang.org/x/sync v0.3.0
|
||||
)
|
||||
|
||||
|
||||
@ -3,8 +3,8 @@ module github.com/gogf/gf/contrib/rpc/grpcx/v2
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/contrib/registry/file/v2 v2.5.2
|
||||
github.com/gogf/gf/v2 v2.5.2
|
||||
github.com/gogf/gf/contrib/registry/file/v2 v2.5.3
|
||||
github.com/gogf/gf/v2 v2.5.3
|
||||
go.opentelemetry.io/otel v1.14.0
|
||||
go.opentelemetry.io/otel/trace v1.14.0
|
||||
google.golang.org/grpc v1.57.0
|
||||
|
||||
@ -11,9 +11,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
@ -130,34 +128,14 @@ func (s *GrpcServer) Run() {
|
||||
|
||||
// doSignalListen does signal listening and handling for gracefully shutdown.
|
||||
func (s *GrpcServer) doSignalListen() {
|
||||
var (
|
||||
ctx = gctx.GetInitCtx()
|
||||
sigChan = make(chan os.Signal, 1)
|
||||
)
|
||||
signal.Notify(
|
||||
sigChan,
|
||||
syscall.SIGINT,
|
||||
syscall.SIGQUIT,
|
||||
syscall.SIGKILL,
|
||||
syscall.SIGTERM,
|
||||
syscall.SIGABRT,
|
||||
)
|
||||
for {
|
||||
sig := <-sigChan
|
||||
switch sig {
|
||||
case
|
||||
syscall.SIGINT,
|
||||
syscall.SIGQUIT,
|
||||
syscall.SIGKILL,
|
||||
syscall.SIGTERM,
|
||||
syscall.SIGABRT:
|
||||
s.Logger().Infof(ctx, "signal received: %s, gracefully shutting down", sig.String())
|
||||
s.doServiceDeregister()
|
||||
time.Sleep(time.Second)
|
||||
s.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
var ctx = context.Background()
|
||||
gproc.AddSigHandlerShutdown(func(sig os.Signal) {
|
||||
s.Logger().Infof(ctx, "signal received: %s, gracefully shutting down", sig.String())
|
||||
s.doServiceDeregister()
|
||||
time.Sleep(time.Second)
|
||||
s.Stop()
|
||||
})
|
||||
gproc.Listen()
|
||||
}
|
||||
|
||||
// Logger is alias of GetLogger.
|
||||
|
||||
@ -2,7 +2,7 @@ module github.com/gogf/gf/contrib/sdk/httpclient/v2
|
||||
|
||||
go 1.18
|
||||
|
||||
require github.com/gogf/gf/v2 v2.5.2
|
||||
require github.com/gogf/gf/v2 v2.5.3
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.2.0 // indirect
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/trace/jaeger/v2
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.5.2
|
||||
github.com/gogf/gf/v2 v2.5.3
|
||||
go.opentelemetry.io/otel v1.14.0
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.14.0
|
||||
go.opentelemetry.io/otel/sdk v1.14.0
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/trace/otlpgrpc/v2
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.5.2
|
||||
github.com/gogf/gf/v2 v2.5.3
|
||||
go.opentelemetry.io/otel v1.16.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/trace/otlphttp/v2
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.5.2
|
||||
github.com/gogf/gf/v2 v2.5.3
|
||||
go.opentelemetry.io/otel v1.16.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.16.0
|
||||
|
||||
@ -172,9 +172,9 @@ type DB interface {
|
||||
GetChars() (charLeft string, charRight string) // See Core.GetChars.
|
||||
Tables(ctx context.Context, schema ...string) (tables []string, err error) // See Core.Tables. The driver must implement this function.
|
||||
TableFields(ctx context.Context, table string, schema ...string) (map[string]*TableField, error) // See Core.TableFields. The driver must implement this function.
|
||||
ConvertDataForRecord(ctx context.Context, data interface{}) (map[string]interface{}, error) // See Core.ConvertDataForRecord
|
||||
ConvertValueForField(ctx context.Context, fieldType string, fieldValue interface{}) (interface{}, error) // See Core.ConvertValueForField
|
||||
ConvertValueForLocal(ctx context.Context, fieldType string, fieldValue interface{}) (interface{}, error) // See Core.ConvertValueForLocal
|
||||
CheckLocalTypeForField(ctx context.Context, fieldType string, fieldValue interface{}) (string, error) // See Core.CheckLocalTypeForField
|
||||
CheckLocalTypeForField(ctx context.Context, fieldType string, fieldValue interface{}) (LocalType, error) // See Core.CheckLocalTypeForField
|
||||
}
|
||||
|
||||
// TX defines the interfaces for ORM transaction operations.
|
||||
@ -328,11 +328,11 @@ type DoInsertOption struct {
|
||||
type TableField struct {
|
||||
Index int // For ordering purpose as map is unordered.
|
||||
Name string // Field name.
|
||||
Type string // Field type.
|
||||
Type string // Field type. Eg: 'int(10) unsigned', 'varchar(64)'.
|
||||
Null bool // Field can be null or not.
|
||||
Key string // The index information(empty if it's not an index).
|
||||
Key string // The index information(empty if it's not an index). Eg: PRI, MUL.
|
||||
Default interface{} // Default value for the field.
|
||||
Extra string // Extra information.
|
||||
Extra string // Extra information. Eg: auto_increment.
|
||||
Comment string // Field comment.
|
||||
}
|
||||
|
||||
@ -356,15 +356,10 @@ type CatchSQLManager struct {
|
||||
DoCommit bool
|
||||
}
|
||||
|
||||
type queryType int
|
||||
|
||||
const (
|
||||
defaultModelSafe = false
|
||||
defaultCharset = `utf8`
|
||||
defaultProtocol = `tcp`
|
||||
queryTypeNormal queryType = 0
|
||||
queryTypeCount queryType = 1
|
||||
queryTypeValue queryType = 2
|
||||
unionTypeNormal = 0
|
||||
unionTypeAll = 1
|
||||
defaultMaxIdleConnCount = 10 // Max idle connection count in pool.
|
||||
@ -386,13 +381,33 @@ const (
|
||||
linkPattern = `(\w+):([\w\-]*):(.*?)@(\w+?)\((.+?)\)/{0,1}([^\?]*)\?{0,1}(.*)`
|
||||
)
|
||||
|
||||
type queryType int
|
||||
|
||||
const (
|
||||
queryTypeNormal queryType = 0
|
||||
queryTypeCount queryType = 1
|
||||
queryTypeValue queryType = 2
|
||||
)
|
||||
|
||||
type joinOperator string
|
||||
|
||||
const (
|
||||
joinOperatorLeft joinOperator = "LEFT"
|
||||
joinOperatorRight joinOperator = "RIGHT"
|
||||
joinOperatorInner joinOperator = "INNER"
|
||||
)
|
||||
|
||||
type InsertOption int
|
||||
|
||||
const (
|
||||
InsertOptionDefault InsertOption = 0
|
||||
InsertOptionReplace InsertOption = 1
|
||||
InsertOptionSave InsertOption = 2
|
||||
InsertOptionIgnore InsertOption = 3
|
||||
InsertOptionDefault InsertOption = 0
|
||||
InsertOptionReplace InsertOption = 1
|
||||
InsertOptionSave InsertOption = 2
|
||||
InsertOptionIgnore InsertOption = 3
|
||||
InsertOperationInsert = "INSERT"
|
||||
InsertOperationReplace = "REPLACE"
|
||||
InsertOperationIgnore = "INSERT IGNORE"
|
||||
InsertOnDuplicateKeyUpdate = "ON DUPLICATE KEY UPDATE"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -407,25 +422,27 @@ const (
|
||||
SqlTypeStmtQueryRowContext = "DB.Statement.QueryRowContext"
|
||||
)
|
||||
|
||||
type LocalType string
|
||||
|
||||
const (
|
||||
LocalTypeString = "string"
|
||||
LocalTypeDate = "date"
|
||||
LocalTypeDatetime = "datetime"
|
||||
LocalTypeInt = "int"
|
||||
LocalTypeUint = "uint"
|
||||
LocalTypeInt64 = "int64"
|
||||
LocalTypeUint64 = "uint64"
|
||||
LocalTypeIntSlice = "[]int"
|
||||
LocalTypeInt64Slice = "[]int64"
|
||||
LocalTypeUint64Slice = "[]uint64"
|
||||
LocalTypeInt64Bytes = "int64-bytes"
|
||||
LocalTypeUint64Bytes = "uint64-bytes"
|
||||
LocalTypeFloat32 = "float32"
|
||||
LocalTypeFloat64 = "float64"
|
||||
LocalTypeBytes = "[]byte"
|
||||
LocalTypeBool = "bool"
|
||||
LocalTypeJson = "json"
|
||||
LocalTypeJsonb = "jsonb"
|
||||
LocalTypeString LocalType = "string"
|
||||
LocalTypeDate LocalType = "date"
|
||||
LocalTypeDatetime LocalType = "datetime"
|
||||
LocalTypeInt LocalType = "int"
|
||||
LocalTypeUint LocalType = "uint"
|
||||
LocalTypeInt64 LocalType = "int64"
|
||||
LocalTypeUint64 LocalType = "uint64"
|
||||
LocalTypeIntSlice LocalType = "[]int"
|
||||
LocalTypeInt64Slice LocalType = "[]int64"
|
||||
LocalTypeUint64Slice LocalType = "[]uint64"
|
||||
LocalTypeInt64Bytes LocalType = "int64-bytes"
|
||||
LocalTypeUint64Bytes LocalType = "uint64-bytes"
|
||||
LocalTypeFloat32 LocalType = "float32"
|
||||
LocalTypeFloat64 LocalType = "float64"
|
||||
LocalTypeBytes LocalType = "[]byte"
|
||||
LocalTypeBool LocalType = "bool"
|
||||
LocalTypeJson LocalType = "json"
|
||||
LocalTypeJsonb LocalType = "jsonb"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@ -422,10 +422,10 @@ func (c *Core) fieldsToSequence(ctx context.Context, table string, fields []stri
|
||||
// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
|
||||
//
|
||||
// The parameter `option` values are as follows:
|
||||
// 0: insert: just insert, if there's unique/primary key in the data, it returns error;
|
||||
// 1: replace: if there's unique/primary key in the data, it deletes it from table and inserts a new one;
|
||||
// 2: save: if there's unique/primary key in the data, it updates it or else inserts a new one;
|
||||
// 3: ignore: if there's unique/primary key in the data, it ignores the inserting;
|
||||
// InsertOptionDefault: just insert, if there's unique/primary key in the data, it returns error;
|
||||
// InsertOptionReplace: if there's unique/primary key in the data, it deletes it from table and inserts a new one;
|
||||
// InsertOptionSave: if there's unique/primary key in the data, it updates it or else inserts a new one;
|
||||
// InsertOptionIgnore: if there's unique/primary key in the data, it ignores the inserting;
|
||||
func (c *Core) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) {
|
||||
var (
|
||||
keys []string // Field names.
|
||||
@ -433,8 +433,10 @@ func (c *Core) DoInsert(ctx context.Context, link Link, table string, list List,
|
||||
params []interface{} // Values that will be committed to underlying database driver.
|
||||
onDuplicateStr string // onDuplicateStr is used in "ON DUPLICATE KEY UPDATE" statement.
|
||||
)
|
||||
// ============================================================================================
|
||||
// Group the list by fields. Different fields to different list.
|
||||
// It here uses ListMap to keep sequence for data inserting.
|
||||
// ============================================================================================
|
||||
var keyListMap = gmap.NewListMap()
|
||||
for _, item := range list {
|
||||
var (
|
||||
@ -574,7 +576,7 @@ func (c *Core) formatOnDuplicate(columns []string, option DoInsertOption) string
|
||||
)
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", onDuplicateStr)
|
||||
return InsertOnDuplicateKeyUpdate + " " + onDuplicateStr
|
||||
}
|
||||
|
||||
// Update does "UPDATE ... " statement for the table.
|
||||
@ -633,7 +635,7 @@ func (c *Core) DoUpdate(ctx context.Context, link Link, table string, data inter
|
||||
}
|
||||
}
|
||||
)
|
||||
dataMap, err = c.db.ConvertDataForRecord(ctx, data)
|
||||
dataMap, err = c.ConvertDataForRecord(ctx, data, table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ import (
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gbinary"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
@ -23,32 +24,67 @@ import (
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
// GetFieldTypeStr retrieves and returns the field type string for certain field by name.
|
||||
func (c *Core) GetFieldTypeStr(ctx context.Context, fieldName, table, schema string) string {
|
||||
field := c.GetFieldType(ctx, fieldName, table, schema)
|
||||
if field != nil {
|
||||
return field.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetFieldType retrieves and returns the field type object for certain field by name.
|
||||
func (c *Core) GetFieldType(ctx context.Context, fieldName, table, schema string) *TableField {
|
||||
fieldsMap, err := c.db.TableFields(ctx, table, schema)
|
||||
if err != nil {
|
||||
intlog.Errorf(
|
||||
ctx,
|
||||
`TableFields failed for table "%s", schema "%s": %+v`,
|
||||
table, schema, err,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
for tableFieldName, tableField := range fieldsMap {
|
||||
if tableFieldName == fieldName {
|
||||
return tableField
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConvertDataForRecord is a very important function, which does converting for any data that
|
||||
// will be inserted into table/collection as a record.
|
||||
//
|
||||
// The parameter `value` should be type of *map/map/*struct/struct.
|
||||
// It supports embedded struct definition for struct.
|
||||
func (c *Core) ConvertDataForRecord(ctx context.Context, value interface{}) (map[string]interface{}, error) {
|
||||
func (c *Core) ConvertDataForRecord(ctx context.Context, value interface{}, table string) (map[string]interface{}, error) {
|
||||
var (
|
||||
err error
|
||||
data = DataToMapDeep(value)
|
||||
)
|
||||
for k, v := range data {
|
||||
data[k], err = c.ConvertDataForRecordValue(ctx, v)
|
||||
for fieldName, fieldValue := range data {
|
||||
data[fieldName], err = c.db.ConvertValueForField(
|
||||
ctx,
|
||||
c.GetFieldTypeStr(ctx, fieldName, table, c.GetSchema()),
|
||||
fieldValue,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, gerror.Wrapf(err, `ConvertDataForRecordValue failed for value: %#v`, v)
|
||||
return nil, gerror.Wrapf(err, `ConvertDataForRecord failed for value: %#v`, fieldValue)
|
||||
}
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (c *Core) ConvertDataForRecordValue(ctx context.Context, value interface{}) (interface{}, error) {
|
||||
// ConvertValueForField converts value to the type of the record field.
|
||||
// The parameter `fieldType` is the target record field.
|
||||
// The parameter `fieldValue` is the value that to be committed to record field.
|
||||
func (c *Core) ConvertValueForField(ctx context.Context, fieldType string, fieldValue interface{}) (interface{}, error) {
|
||||
var (
|
||||
err error
|
||||
convertedValue = value
|
||||
convertedValue = fieldValue
|
||||
)
|
||||
// If `value` implements interface `driver.Valuer`, it then uses the interface for value converting.
|
||||
if valuer, ok := value.(driver.Valuer); ok {
|
||||
if valuer, ok := fieldValue.(driver.Valuer); ok {
|
||||
if convertedValue, err = valuer.Value(); err != nil {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -58,7 +94,7 @@ func (c *Core) ConvertDataForRecordValue(ctx context.Context, value interface{})
|
||||
}
|
||||
// Default value converting.
|
||||
var (
|
||||
rvValue = reflect.ValueOf(value)
|
||||
rvValue = reflect.ValueOf(fieldValue)
|
||||
rvKind = rvValue.Kind()
|
||||
)
|
||||
for rvKind == reflect.Ptr {
|
||||
@ -68,16 +104,16 @@ func (c *Core) ConvertDataForRecordValue(ctx context.Context, value interface{})
|
||||
switch rvKind {
|
||||
case reflect.Slice, reflect.Array, reflect.Map:
|
||||
// It should ignore the bytes type.
|
||||
if _, ok := value.([]byte); !ok {
|
||||
if _, ok := fieldValue.([]byte); !ok {
|
||||
// Convert the value to JSON.
|
||||
convertedValue, err = json.Marshal(value)
|
||||
convertedValue, err = json.Marshal(fieldValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Struct:
|
||||
switch r := value.(type) {
|
||||
switch r := fieldValue.(type) {
|
||||
// If the time is zero, it then updates it to nil,
|
||||
// which will insert/update the value to database as "null".
|
||||
case time.Time:
|
||||
@ -109,14 +145,14 @@ func (c *Core) ConvertDataForRecordValue(ctx context.Context, value interface{})
|
||||
// If `value` implements interface iNil,
|
||||
// check its IsNil() function, if got ture,
|
||||
// which will insert/update the value to database as "null".
|
||||
if v, ok := value.(iNil); ok && v.IsNil() {
|
||||
if v, ok := fieldValue.(iNil); ok && v.IsNil() {
|
||||
convertedValue = nil
|
||||
} else if s, ok := value.(iString); ok {
|
||||
} else if s, ok := fieldValue.(iString); ok {
|
||||
// Use string conversion in default.
|
||||
convertedValue = s.String()
|
||||
} else {
|
||||
// Convert the value to JSON.
|
||||
convertedValue, err = json.Marshal(value)
|
||||
convertedValue, err = json.Marshal(fieldValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -127,7 +163,7 @@ func (c *Core) ConvertDataForRecordValue(ctx context.Context, value interface{})
|
||||
}
|
||||
|
||||
// CheckLocalTypeForField checks and returns corresponding type for given db type.
|
||||
func (c *Core) CheckLocalTypeForField(ctx context.Context, fieldType string, fieldValue interface{}) (string, error) {
|
||||
func (c *Core) CheckLocalTypeForField(ctx context.Context, fieldType string, fieldValue interface{}) (LocalType, error) {
|
||||
var (
|
||||
typeName string
|
||||
typePattern string
|
||||
@ -275,7 +311,8 @@ func (c *Core) ConvertValueForLocal(ctx context.Context, fieldType string, field
|
||||
}
|
||||
switch typeName {
|
||||
case LocalTypeBytes:
|
||||
if strings.Contains(typeName, "binary") || strings.Contains(typeName, "blob") {
|
||||
var typeNameStr = string(typeName)
|
||||
if strings.Contains(typeNameStr, "binary") || strings.Contains(typeNameStr, "blob") {
|
||||
return fieldValue, nil
|
||||
}
|
||||
return gconv.Bytes(fieldValue), nil
|
||||
|
||||
@ -98,14 +98,14 @@ func (d *DriverWrapperDB) TableFields(
|
||||
// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
|
||||
//
|
||||
// The parameter `option` values are as follows:
|
||||
// 0: insert: just insert, if there's unique/primary key in the data, it returns error;
|
||||
// 1: replace: if there's unique/primary key in the data, it deletes it from table and inserts a new one;
|
||||
// 2: save: if there's unique/primary key in the data, it updates it or else inserts a new one;
|
||||
// 3: ignore: if there's unique/primary key in the data, it ignores the inserting;
|
||||
// InsertOptionDefault: just insert, if there's unique/primary key in the data, it returns error;
|
||||
// InsertOptionReplace: if there's unique/primary key in the data, it deletes it from table and inserts a new one;
|
||||
// InsertOptionSave: if there's unique/primary key in the data, it updates it or else inserts a new one;
|
||||
// InsertOptionIgnore: if there's unique/primary key in the data, it ignores the inserting;
|
||||
func (d *DriverWrapperDB) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) {
|
||||
// Convert data type before commit it to underlying db driver.
|
||||
for i, item := range list {
|
||||
list[i], err = d.DB.ConvertDataForRecord(ctx, item)
|
||||
list[i], err = d.GetCore().ConvertDataForRecord(ctx, item, table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -200,11 +200,11 @@ func GetInsertOperationByOption(option InsertOption) string {
|
||||
var operator string
|
||||
switch option {
|
||||
case InsertOptionReplace:
|
||||
operator = "REPLACE"
|
||||
operator = InsertOperationReplace
|
||||
case InsertOptionIgnore:
|
||||
operator = "INSERT IGNORE"
|
||||
operator = InsertOperationIgnore
|
||||
default:
|
||||
operator = "INSERT"
|
||||
operator = InsertOperationInsert
|
||||
}
|
||||
return operator
|
||||
}
|
||||
@ -222,14 +222,6 @@ func DataToMapDeep(value interface{}) map[string]interface{} {
|
||||
switch v.(type) {
|
||||
case time.Time, *time.Time, gtime.Time, *gtime.Time, gjson.Json, *gjson.Json:
|
||||
m[k] = v
|
||||
|
||||
default:
|
||||
// Use string conversion in default.
|
||||
if s, ok := v.(iString); ok {
|
||||
m[k] = s.String()
|
||||
} else {
|
||||
m[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return m
|
||||
|
||||
@ -12,6 +12,188 @@ import (
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
|
||||
// The parameter `table` can be joined table and its joined condition,
|
||||
// and also with its alias name.
|
||||
//
|
||||
// Eg:
|
||||
// Model("user").LeftJoin("user_detail", "user_detail.uid=user.uid")
|
||||
// Model("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid")
|
||||
// Model("user", "u").LeftJoin("SELECT xxx FROM xxx","a", "a.uid=u.uid").
|
||||
func (m *Model) LeftJoin(tableOrSubQueryAndJoinConditions ...string) *Model {
|
||||
return m.doJoin(joinOperatorLeft, tableOrSubQueryAndJoinConditions...)
|
||||
}
|
||||
|
||||
// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
|
||||
// The parameter `table` can be joined table and its joined condition,
|
||||
// and also with its alias name.
|
||||
//
|
||||
// Eg:
|
||||
// Model("user").RightJoin("user_detail", "user_detail.uid=user.uid")
|
||||
// Model("user", "u").RightJoin("user_detail", "ud", "ud.uid=u.uid")
|
||||
// Model("user", "u").RightJoin("SELECT xxx FROM xxx","a", "a.uid=u.uid").
|
||||
func (m *Model) RightJoin(tableOrSubQueryAndJoinConditions ...string) *Model {
|
||||
return m.doJoin(joinOperatorRight, tableOrSubQueryAndJoinConditions...)
|
||||
}
|
||||
|
||||
// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
|
||||
// The parameter `table` can be joined table and its joined condition,
|
||||
// and also with its alias name。
|
||||
//
|
||||
// Eg:
|
||||
// Model("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
|
||||
// Model("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
|
||||
// Model("user", "u").InnerJoin("SELECT xxx FROM xxx","a", "a.uid=u.uid").
|
||||
func (m *Model) InnerJoin(tableOrSubQueryAndJoinConditions ...string) *Model {
|
||||
return m.doJoin(joinOperatorInner, tableOrSubQueryAndJoinConditions...)
|
||||
}
|
||||
|
||||
// LeftJoinOnField performs as LeftJoin, but it joins both tables with the `same field name`.
|
||||
//
|
||||
// Eg:
|
||||
// Model("order").LeftJoinOnField("user", "user_id")
|
||||
// Model("order").LeftJoinOnField("product", "product_id").
|
||||
func (m *Model) LeftJoinOnField(table, field string) *Model {
|
||||
return m.doJoin(joinOperatorLeft, table, fmt.Sprintf(
|
||||
`%s.%s=%s.%s`,
|
||||
m.tablesInit,
|
||||
m.db.GetCore().QuoteWord(field),
|
||||
m.db.GetCore().QuoteWord(table),
|
||||
m.db.GetCore().QuoteWord(field),
|
||||
))
|
||||
}
|
||||
|
||||
// RightJoinOnField performs as RightJoin, but it joins both tables with the `same field name`.
|
||||
//
|
||||
// Eg:
|
||||
// Model("order").InnerJoinOnField("user", "user_id")
|
||||
// Model("order").InnerJoinOnField("product", "product_id").
|
||||
func (m *Model) RightJoinOnField(table, field string) *Model {
|
||||
return m.doJoin(joinOperatorRight, table, fmt.Sprintf(
|
||||
`%s.%s=%s.%s`,
|
||||
m.tablesInit,
|
||||
m.db.GetCore().QuoteWord(field),
|
||||
m.db.GetCore().QuoteWord(table),
|
||||
m.db.GetCore().QuoteWord(field),
|
||||
))
|
||||
}
|
||||
|
||||
// InnerJoinOnField performs as InnerJoin, but it joins both tables with the `same field name`.
|
||||
//
|
||||
// Eg:
|
||||
// Model("order").InnerJoinOnField("user", "user_id")
|
||||
// Model("order").InnerJoinOnField("product", "product_id").
|
||||
func (m *Model) InnerJoinOnField(table, field string) *Model {
|
||||
return m.doJoin(joinOperatorInner, table, fmt.Sprintf(
|
||||
`%s.%s=%s.%s`,
|
||||
m.tablesInit,
|
||||
m.db.GetCore().QuoteWord(field),
|
||||
m.db.GetCore().QuoteWord(table),
|
||||
m.db.GetCore().QuoteWord(field),
|
||||
))
|
||||
}
|
||||
|
||||
// LeftJoinOnFields performs as LeftJoin. It specifies different fields and comparison operator.
|
||||
//
|
||||
// Eg:
|
||||
// Model("user").LeftJoinOnFields("order", "id", "=", "user_id")
|
||||
// Model("user").LeftJoinOnFields("order", "id", ">", "user_id")
|
||||
// Model("user").LeftJoinOnFields("order", "id", "<", "user_id")
|
||||
func (m *Model) LeftJoinOnFields(table, firstField, operator, secondField string) *Model {
|
||||
return m.doJoin(joinOperatorLeft, table, fmt.Sprintf(
|
||||
`%s.%s %s %s.%s`,
|
||||
m.tablesInit,
|
||||
m.db.GetCore().QuoteWord(firstField),
|
||||
operator,
|
||||
m.db.GetCore().QuoteWord(table),
|
||||
m.db.GetCore().QuoteWord(secondField),
|
||||
))
|
||||
}
|
||||
|
||||
// RightJoinOnFields performs as RightJoin. It specifies different fields and comparison operator.
|
||||
//
|
||||
// Eg:
|
||||
// Model("user").RightJoinOnFields("order", "id", "=", "user_id")
|
||||
// Model("user").RightJoinOnFields("order", "id", ">", "user_id")
|
||||
// Model("user").RightJoinOnFields("order", "id", "<", "user_id")
|
||||
func (m *Model) RightJoinOnFields(table, firstField, operator, secondField string) *Model {
|
||||
return m.doJoin(joinOperatorRight, table, fmt.Sprintf(
|
||||
`%s.%s %s %s.%s`,
|
||||
m.tablesInit,
|
||||
m.db.GetCore().QuoteWord(firstField),
|
||||
operator,
|
||||
m.db.GetCore().QuoteWord(table),
|
||||
m.db.GetCore().QuoteWord(secondField),
|
||||
))
|
||||
}
|
||||
|
||||
// InnerJoinOnFields performs as InnerJoin. It specifies different fields and comparison operator.
|
||||
//
|
||||
// Eg:
|
||||
// Model("user").InnerJoinOnFields("order", "id", "=", "user_id")
|
||||
// Model("user").InnerJoinOnFields("order", "id", ">", "user_id")
|
||||
// Model("user").InnerJoinOnFields("order", "id", "<", "user_id")
|
||||
func (m *Model) InnerJoinOnFields(table, firstField, operator, secondField string) *Model {
|
||||
return m.doJoin(joinOperatorInner, table, fmt.Sprintf(
|
||||
`%s.%s %s %s.%s`,
|
||||
m.tablesInit,
|
||||
m.db.GetCore().QuoteWord(firstField),
|
||||
operator,
|
||||
m.db.GetCore().QuoteWord(table),
|
||||
m.db.GetCore().QuoteWord(secondField),
|
||||
))
|
||||
}
|
||||
|
||||
// doJoin does "LEFT/RIGHT/INNER JOIN ... ON ..." statement on the model.
|
||||
// The parameter `tableOrSubQueryAndJoinConditions` can be joined table and its joined condition,
|
||||
// and also with its alias name.
|
||||
//
|
||||
// Eg:
|
||||
// Model("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
|
||||
// Model("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
|
||||
// Model("user", "u").InnerJoin("user_detail", "ud", "ud.uid>u.uid")
|
||||
// Model("user", "u").InnerJoin("SELECT xxx FROM xxx","a", "a.uid=u.uid")
|
||||
// Related issues:
|
||||
// https://github.com/gogf/gf/issues/1024
|
||||
func (m *Model) doJoin(operator joinOperator, tableOrSubQueryAndJoinConditions ...string) *Model {
|
||||
var (
|
||||
model = m.getModel()
|
||||
joinStr = ""
|
||||
)
|
||||
// Check the first parameter table or sub-query.
|
||||
if len(tableOrSubQueryAndJoinConditions) > 0 {
|
||||
if isSubQuery(tableOrSubQueryAndJoinConditions[0]) {
|
||||
joinStr = gstr.Trim(tableOrSubQueryAndJoinConditions[0])
|
||||
if joinStr[0] != '(' {
|
||||
joinStr = "(" + joinStr + ")"
|
||||
}
|
||||
} else {
|
||||
joinStr = m.db.GetCore().QuotePrefixTableName(tableOrSubQueryAndJoinConditions[0])
|
||||
}
|
||||
}
|
||||
// Generate join condition statement string.
|
||||
conditionLength := len(tableOrSubQueryAndJoinConditions)
|
||||
switch {
|
||||
case conditionLength > 2:
|
||||
model.tables += fmt.Sprintf(
|
||||
" %s JOIN %s AS %s ON (%s)",
|
||||
operator, joinStr,
|
||||
m.db.GetCore().QuoteWord(tableOrSubQueryAndJoinConditions[1]),
|
||||
tableOrSubQueryAndJoinConditions[2],
|
||||
)
|
||||
case conditionLength == 2:
|
||||
model.tables += fmt.Sprintf(
|
||||
" %s JOIN %s ON (%s)",
|
||||
operator, joinStr, tableOrSubQueryAndJoinConditions[1],
|
||||
)
|
||||
case conditionLength == 1:
|
||||
model.tables += fmt.Sprintf(
|
||||
" %s JOIN %s", operator, joinStr,
|
||||
)
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
// isSubQuery checks and returns whether given string a sub-query sql string.
|
||||
func isSubQuery(s string) bool {
|
||||
s = gstr.TrimLeft(s, "()")
|
||||
@ -22,127 +204,3 @@ func isSubQuery(s string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
|
||||
// The parameter `table` can be joined table and its joined condition,
|
||||
// and also with its alias name.
|
||||
//
|
||||
// Eg:
|
||||
// Model("user").LeftJoin("user_detail", "user_detail.uid=user.uid")
|
||||
// Model("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid")
|
||||
// Model("user", "u").LeftJoin("SELECT xxx FROM xxx AS a", "a.uid=u.uid").
|
||||
func (m *Model) LeftJoin(table ...string) *Model {
|
||||
return m.doJoin("LEFT", table...)
|
||||
}
|
||||
|
||||
// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
|
||||
// The parameter `table` can be joined table and its joined condition,
|
||||
// and also with its alias name.
|
||||
//
|
||||
// Eg:
|
||||
// Model("user").RightJoin("user_detail", "user_detail.uid=user.uid")
|
||||
// Model("user", "u").RightJoin("user_detail", "ud", "ud.uid=u.uid")
|
||||
// Model("user", "u").RightJoin("SELECT xxx FROM xxx AS a", "a.uid=u.uid").
|
||||
func (m *Model) RightJoin(table ...string) *Model {
|
||||
return m.doJoin("RIGHT", table...)
|
||||
}
|
||||
|
||||
// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
|
||||
// The parameter `table` can be joined table and its joined condition,
|
||||
// and also with its alias name。
|
||||
//
|
||||
// Eg:
|
||||
// Model("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
|
||||
// Model("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
|
||||
// Model("user", "u").InnerJoin("SELECT xxx FROM xxx AS a", "a.uid=u.uid").
|
||||
func (m *Model) InnerJoin(table ...string) *Model {
|
||||
return m.doJoin("INNER", table...)
|
||||
}
|
||||
|
||||
// LeftJoinOnField performs as LeftJoin, but it joins both tables with the same field name.
|
||||
//
|
||||
// Eg:
|
||||
// Model("order").LeftJoinOnField("user", "user_id")
|
||||
// Model("order").LeftJoinOnField("product", "product_id").
|
||||
func (m *Model) LeftJoinOnField(table, field string) *Model {
|
||||
return m.doJoin("LEFT", table, fmt.Sprintf(
|
||||
`%s.%s=%s.%s`,
|
||||
m.tablesInit,
|
||||
m.db.GetCore().QuoteWord(field),
|
||||
m.db.GetCore().QuoteWord(table),
|
||||
m.db.GetCore().QuoteWord(field),
|
||||
))
|
||||
}
|
||||
|
||||
// RightJoinOnField performs as RightJoin, but it joins both tables with the same field name.
|
||||
//
|
||||
// Eg:
|
||||
// Model("order").InnerJoinOnField("user", "user_id")
|
||||
// Model("order").InnerJoinOnField("product", "product_id").
|
||||
func (m *Model) RightJoinOnField(table, field string) *Model {
|
||||
return m.doJoin("RIGHT", table, fmt.Sprintf(
|
||||
`%s.%s=%s.%s`,
|
||||
m.tablesInit,
|
||||
m.db.GetCore().QuoteWord(field),
|
||||
m.db.GetCore().QuoteWord(table),
|
||||
m.db.GetCore().QuoteWord(field),
|
||||
))
|
||||
}
|
||||
|
||||
// InnerJoinOnField performs as InnerJoin, but it joins both tables with the same field name.
|
||||
//
|
||||
// Eg:
|
||||
// Model("order").InnerJoinOnField("user", "user_id")
|
||||
// Model("order").InnerJoinOnField("product", "product_id").
|
||||
func (m *Model) InnerJoinOnField(table, field string) *Model {
|
||||
return m.doJoin("INNER", table, fmt.Sprintf(
|
||||
`%s.%s=%s.%s`,
|
||||
m.tablesInit,
|
||||
m.db.GetCore().QuoteWord(field),
|
||||
m.db.GetCore().QuoteWord(table),
|
||||
m.db.GetCore().QuoteWord(field),
|
||||
))
|
||||
}
|
||||
|
||||
// doJoin does "LEFT/RIGHT/INNER JOIN ... ON ..." statement on the model.
|
||||
// The parameter `table` can be joined table and its joined condition,
|
||||
// and also with its alias name.
|
||||
//
|
||||
// Eg:
|
||||
// Model("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
|
||||
// Model("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
|
||||
// Model("user", "u").InnerJoin("SELECT xxx FROM xxx AS a", "a.uid=u.uid")
|
||||
// Related issues:
|
||||
// https://github.com/gogf/gf/issues/1024
|
||||
func (m *Model) doJoin(operator string, table ...string) *Model {
|
||||
var (
|
||||
model = m.getModel()
|
||||
joinStr = ""
|
||||
)
|
||||
if len(table) > 0 {
|
||||
if isSubQuery(table[0]) {
|
||||
joinStr = gstr.Trim(table[0])
|
||||
if joinStr[0] != '(' {
|
||||
joinStr = "(" + joinStr + ")"
|
||||
}
|
||||
} else {
|
||||
joinStr = m.db.GetCore().QuotePrefixTableName(table[0])
|
||||
}
|
||||
}
|
||||
if len(table) > 2 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" %s JOIN %s AS %s ON (%s)",
|
||||
operator, joinStr, m.db.GetCore().QuoteWord(table[1]), table[2],
|
||||
)
|
||||
} else if len(table) == 2 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" %s JOIN %s ON (%s)",
|
||||
operator, joinStr, table[1],
|
||||
)
|
||||
} else if len(table) == 1 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" %s JOIN %s", operator, joinStr,
|
||||
)
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
@ -44,9 +44,6 @@ func (r *SqlResult) MustGetInsertId() int64 {
|
||||
// driver may support this.
|
||||
// Also, See sql.Result.
|
||||
func (r *SqlResult) RowsAffected() (int64, error) {
|
||||
if r.Result == nil {
|
||||
return 0, nil
|
||||
}
|
||||
if r.Affected > 0 {
|
||||
return r.Affected, nil
|
||||
}
|
||||
|
||||
@ -39,6 +39,7 @@ type Config struct {
|
||||
TLSSkipVerify bool `json:"tlsSkipVerify"` // Disables server name verification when connecting over TLS.
|
||||
TLSConfig *tls.Config `json:"-"` // TLS Config to use. When set TLS will be negotiated.
|
||||
SlaveOnly bool `json:"slaveOnly"` // Route all commands to slave read-only nodes.
|
||||
Cluster bool `json:"cluster"` // Specifies whether cluster mode be used.
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
@ -14,6 +14,7 @@ import (
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
@ -587,3 +588,15 @@ func Test_Issue1747(t *testing.T) {
|
||||
t.Assert(j.Get("2"), `336371793314971759`)
|
||||
})
|
||||
}
|
||||
|
||||
// https://github.com/gogf/gf/issues/2520
|
||||
func Test_Issue2520(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type test struct {
|
||||
Unique *gvar.Var `json:"unique"`
|
||||
}
|
||||
|
||||
t2 := test{Unique: gvar.New(gtime.Date())}
|
||||
t.Assert(gjson.MustEncodeString(t2), gjson.New(t2).MustToJsonString())
|
||||
})
|
||||
}
|
||||
|
||||
@ -13,7 +13,6 @@ package gerror
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/internal/command"
|
||||
)
|
||||
|
||||
// IIs is the interface for Is feature.
|
||||
@ -59,21 +58,6 @@ type IUnwrap interface {
|
||||
}
|
||||
|
||||
const (
|
||||
// commandEnvKeyForBrief is the command environment name for switch key for brief error stack.
|
||||
commandEnvKeyForBrief = "gf.gerror.brief"
|
||||
|
||||
// commaSeparatorSpace is the comma separator with space.
|
||||
commaSeparatorSpace = ", "
|
||||
)
|
||||
|
||||
var (
|
||||
// isUsingBriefStack is the switch key for brief error stack.
|
||||
isUsingBriefStack bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
value := command.GetOptWithEnv(commandEnvKeyForBrief)
|
||||
if value == "1" || value == "true" {
|
||||
isUsingBriefStack = true
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/consts"
|
||||
"github.com/gogf/gf/v2/internal/errors"
|
||||
)
|
||||
|
||||
// stackInfo manages stack info of certain error.
|
||||
@ -35,9 +36,10 @@ func (err *Error) Stack() string {
|
||||
return ""
|
||||
}
|
||||
var (
|
||||
loop = err
|
||||
index = 1
|
||||
infos []*stackInfo
|
||||
loop = err
|
||||
index = 1
|
||||
infos []*stackInfo
|
||||
isStackModeBrief = errors.IsStackModeBrief()
|
||||
)
|
||||
for loop != nil {
|
||||
info := &stackInfo{
|
||||
@ -46,7 +48,7 @@ func (err *Error) Stack() string {
|
||||
}
|
||||
index++
|
||||
infos = append(infos, info)
|
||||
loopLinesOfStackInfo(loop.stack, info)
|
||||
loopLinesOfStackInfo(loop.stack, info, isStackModeBrief)
|
||||
if loop.error != nil {
|
||||
if e, ok := loop.error.(*Error); ok {
|
||||
loop = e
|
||||
@ -131,14 +133,14 @@ func formatStackLines(buffer *bytes.Buffer, lines *list.List) string {
|
||||
}
|
||||
|
||||
// loopLinesOfStackInfo iterates the stack info lines and produces the stack line info.
|
||||
func loopLinesOfStackInfo(st stack, info *stackInfo) {
|
||||
func loopLinesOfStackInfo(st stack, info *stackInfo, isStackModeBrief bool) {
|
||||
if st == nil {
|
||||
return
|
||||
}
|
||||
for _, p := range st {
|
||||
if fn := runtime.FuncForPC(p - 1); fn != nil {
|
||||
file, line := fn.FileLine(p - 1)
|
||||
if isUsingBriefStack {
|
||||
if isStackModeBrief {
|
||||
// filter whole GoFrame packages stack paths.
|
||||
if strings.Contains(file, consts.StackFilterKeyForGoFrame) {
|
||||
continue
|
||||
|
||||
@ -3,20 +3,20 @@ module github.com/gogf/gf/example
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/contrib/config/apollo/v2 v2.5.2
|
||||
github.com/gogf/gf/contrib/config/kubecm/v2 v2.5.2
|
||||
github.com/gogf/gf/contrib/config/nacos/v2 v2.5.2
|
||||
github.com/gogf/gf/contrib/config/polaris/v2 v2.5.2
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.5.2
|
||||
github.com/gogf/gf/contrib/nosql/redis/v2 v2.5.2
|
||||
github.com/gogf/gf/contrib/registry/etcd/v2 v2.5.2
|
||||
github.com/gogf/gf/contrib/registry/file/v2 v2.5.2
|
||||
github.com/gogf/gf/contrib/registry/polaris/v2 v2.5.2
|
||||
github.com/gogf/gf/contrib/rpc/grpcx/v2 v2.5.2
|
||||
github.com/gogf/gf/contrib/trace/jaeger/v2 v2.5.2
|
||||
github.com/gogf/gf/contrib/trace/otlpgrpc/v2 v2.5.2
|
||||
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.5.2
|
||||
github.com/gogf/gf/v2 v2.5.2
|
||||
github.com/gogf/gf/contrib/config/apollo/v2 v2.5.3
|
||||
github.com/gogf/gf/contrib/config/kubecm/v2 v2.5.3
|
||||
github.com/gogf/gf/contrib/config/nacos/v2 v2.5.3
|
||||
github.com/gogf/gf/contrib/config/polaris/v2 v2.5.3
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.5.3
|
||||
github.com/gogf/gf/contrib/nosql/redis/v2 v2.5.3
|
||||
github.com/gogf/gf/contrib/registry/etcd/v2 v2.5.3
|
||||
github.com/gogf/gf/contrib/registry/file/v2 v2.5.3
|
||||
github.com/gogf/gf/contrib/registry/polaris/v2 v2.5.3
|
||||
github.com/gogf/gf/contrib/rpc/grpcx/v2 v2.5.3
|
||||
github.com/gogf/gf/contrib/trace/jaeger/v2 v2.5.3
|
||||
github.com/gogf/gf/contrib/trace/otlpgrpc/v2 v2.5.3
|
||||
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.5.3
|
||||
github.com/gogf/gf/v2 v2.5.3
|
||||
github.com/nacos-group/nacos-sdk-go v1.1.4
|
||||
github.com/polarismesh/polaris-go v1.5.1
|
||||
google.golang.org/grpc v1.57.0
|
||||
|
||||
@ -17,6 +17,20 @@ import (
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
type (
|
||||
Func = gutil.Func // Func is the function which contains context parameter.
|
||||
RecoverFunc = gutil.RecoverFunc // RecoverFunc is the panic recover function which contains context parameter.
|
||||
)
|
||||
|
||||
// Go creates a new asynchronous goroutine function with specified recover function.
|
||||
//
|
||||
// The parameter `recoverFunc` is called when any panic during executing of `goroutineFunc`.
|
||||
// If `recoverFunc` is given nil, it ignores the panic from `goroutineFunc` and no panic will
|
||||
// throw to parent goroutine.
|
||||
func Go(ctx context.Context, goroutineFunc Func, recoverFunc RecoverFunc) {
|
||||
gutil.Go(ctx, goroutineFunc, recoverFunc)
|
||||
}
|
||||
|
||||
// NewVar returns a gvar.Var.
|
||||
func NewVar(i interface{}, safe ...bool) *Var {
|
||||
return gvar.New(i, safe...)
|
||||
|
||||
@ -9,8 +9,10 @@ package g_test
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/container/garray"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
@ -114,3 +116,19 @@ func Test_Object(t *testing.T) {
|
||||
t.AssertNE(g.Validator(), nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Go(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
wg = sync.WaitGroup{}
|
||||
array = garray.NewArray(true)
|
||||
)
|
||||
wg.Add(1)
|
||||
g.Go(context.Background(), func(ctx context.Context) {
|
||||
defer wg.Done()
|
||||
array.Append(1)
|
||||
}, nil)
|
||||
wg.Wait()
|
||||
t.Assert(array.Len(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
61
internal/errors/errors.go
Normal file
61
internal/errors/errors.go
Normal file
@ -0,0 +1,61 @@
|
||||
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package errors provides functionalities to manipulate errors for internal usage purpose.
|
||||
package errors
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/internal/command"
|
||||
)
|
||||
|
||||
// StackMode is the mode that printing stack information in StackModeBrief or StackModeDetail mode.
|
||||
type StackMode string
|
||||
|
||||
const (
|
||||
// commandEnvKeyForBrief is the command environment name for switch key for brief error stack.
|
||||
// Deprecated: use commandEnvKeyForStackMode instead.
|
||||
commandEnvKeyForBrief = "gf.gerror.brief"
|
||||
|
||||
// commandEnvKeyForStackMode is the command environment name for switch key for brief error stack.
|
||||
commandEnvKeyForStackMode = "gf.gerror.stack.mode"
|
||||
)
|
||||
|
||||
const (
|
||||
// StackModeBrief specifies all error stacks printing no framework error stacks.
|
||||
StackModeBrief StackMode = "brief"
|
||||
|
||||
// StackModeDetail specifies all error stacks printing detailed error stacks including framework stacks.
|
||||
StackModeDetail StackMode = "detail"
|
||||
)
|
||||
|
||||
var (
|
||||
// stackModeConfigured is the configured error stack mode variable.
|
||||
// It is brief stack mode in default.
|
||||
stackModeConfigured = StackModeBrief
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Deprecated.
|
||||
briefSetting := command.GetOptWithEnv(commandEnvKeyForBrief)
|
||||
if briefSetting == "1" || briefSetting == "true" {
|
||||
stackModeConfigured = StackModeBrief
|
||||
}
|
||||
|
||||
// The error stack mode is configured using command line arguments or environments.
|
||||
stackModeSetting := command.GetOptWithEnv(commandEnvKeyForStackMode)
|
||||
if stackModeSetting != "" {
|
||||
stackModeSettingMode := StackMode(stackModeSetting)
|
||||
switch stackModeSettingMode {
|
||||
case StackModeBrief, StackModeDetail:
|
||||
stackModeConfigured = stackModeSettingMode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IsStackModeBrief returns whether current error stack mode is in brief mode.
|
||||
func IsStackModeBrief() bool {
|
||||
return stackModeConfigured == StackModeBrief
|
||||
}
|
||||
20
internal/errors/errors_test.go
Normal file
20
internal/errors/errors_test.go
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package errors_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/errors"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
func Test_IsStackModeBrief(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.Assert(errors.IsStackModeBrief(), true)
|
||||
})
|
||||
}
|
||||
@ -5,6 +5,8 @@
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package instance provides instances management.
|
||||
//
|
||||
// Note that this package is not used for cache, as it has no cache expiration.
|
||||
package instance
|
||||
|
||||
import (
|
||||
|
||||
@ -57,6 +57,7 @@ func IsSlice(value interface{}) bool {
|
||||
)
|
||||
for reflectKind == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
switch reflectKind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
|
||||
@ -9,8 +9,10 @@ package utils_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/internal/utils"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
@ -124,6 +126,9 @@ func TestVar_IsSlice(t *testing.T) {
|
||||
t.Assert(utils.IsSlice(int8(1)), false)
|
||||
t.Assert(utils.IsSlice(uint8(1)), false)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.Assert(utils.IsSlice(gvar.New(gtime.Now()).IsSlice()), false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestVar_IsMap(t *testing.T) {
|
||||
|
||||
@ -36,6 +36,7 @@ const (
|
||||
tracingEventHttpResponse = "http.response"
|
||||
tracingEventHttpResponseHeaders = "http.response.headers"
|
||||
tracingEventHttpResponseBody = "http.response.body"
|
||||
tracingEventHttpRequestUrl = "http.request.url"
|
||||
tracingMiddlewareHandled gctx.StrKey = `MiddlewareServerTracingHandled`
|
||||
)
|
||||
|
||||
@ -64,7 +65,7 @@ func internalMiddlewareServerTracing(r *Request) {
|
||||
ctx,
|
||||
propagation.HeaderCarrier(r.Header),
|
||||
),
|
||||
r.URL.String(),
|
||||
r.URL.Path,
|
||||
trace.WithSpanKind(trace.SpanKindServer),
|
||||
)
|
||||
defer span.End()
|
||||
@ -90,6 +91,7 @@ func internalMiddlewareServerTracing(r *Request) {
|
||||
r.Body = utils.NewReadCloser(reqBodyContentBytes, false)
|
||||
|
||||
span.AddEvent(tracingEventHttpRequest, trace.WithAttributes(
|
||||
attribute.String(tracingEventHttpRequestUrl, r.URL.String()),
|
||||
attribute.String(tracingEventHttpRequestHeaders, gconv.String(httputil.HeaderToMap(r.Header))),
|
||||
attribute.String(tracingEventHttpRequestBaggage, gtrace.GetBaggageMap(ctx).String()),
|
||||
attribute.String(tracingEventHttpRequestBody, gstr.StrLimit(
|
||||
|
||||
@ -63,11 +63,6 @@ type Request struct {
|
||||
originUrlPath string // Original URL path that passed from client.
|
||||
}
|
||||
|
||||
type handlerResponse struct {
|
||||
Object interface{}
|
||||
Error error
|
||||
}
|
||||
|
||||
// staticFile is the file struct for static file service.
|
||||
type staticFile struct {
|
||||
File *gres.File // Resource file object.
|
||||
|
||||
@ -101,9 +101,9 @@ func (m *middleware) Next() {
|
||||
loop = false
|
||||
}
|
||||
}, func(ctx context.Context, exception error) {
|
||||
if v, ok := exception.(error); ok && gerror.HasStack(v) {
|
||||
if gerror.HasStack(exception) {
|
||||
// It's already an error that has stack info.
|
||||
m.request.error = v
|
||||
m.request.error = exception
|
||||
} else {
|
||||
// Create a new error with stack info.
|
||||
// Note that there's a skip pointing the start stacktrace
|
||||
|
||||
@ -232,7 +232,7 @@ func (r *Request) mergeInTagStructValue(data map[string]interface{}, pointer int
|
||||
)
|
||||
|
||||
for k, v := range r.Header {
|
||||
if v != nil && len(v) > 0 {
|
||||
if len(v) > 0 {
|
||||
headerMap[k] = v[0]
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ package ghttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -52,14 +53,9 @@ func (p *utilAdmin) Restart(r *Request) {
|
||||
// Custom start binary path when this process exits.
|
||||
path := r.GetQuery("newExeFilePath").String()
|
||||
if path == "" {
|
||||
path = gfile.SelfPath()
|
||||
path = os.Args[0]
|
||||
}
|
||||
if len(path) > 0 {
|
||||
err = RestartAllServer(ctx, path)
|
||||
} else {
|
||||
err = RestartAllServer(ctx)
|
||||
}
|
||||
if err == nil {
|
||||
if err = RestartAllServer(ctx, path); err == nil {
|
||||
r.Response.WriteExit("server restarted")
|
||||
} else {
|
||||
r.Response.WriteExit(err.Error())
|
||||
|
||||
@ -51,9 +51,9 @@ var (
|
||||
serverProcessStatus = gtype.NewInt()
|
||||
)
|
||||
|
||||
// RestartAllServer restarts all the servers of the process.
|
||||
// RestartAllServer restarts all the servers of the process gracefully.
|
||||
// The optional parameter `newExeFilePath` specifies the new binary file for creating process.
|
||||
func RestartAllServer(ctx context.Context, newExeFilePath ...string) error {
|
||||
func RestartAllServer(ctx context.Context, newExeFilePath string) error {
|
||||
if !gracefulEnabled {
|
||||
return gerror.NewCode(gcode.CodeInvalidOperation, "graceful reload feature is disabled")
|
||||
}
|
||||
@ -65,10 +65,10 @@ func RestartAllServer(ctx context.Context, newExeFilePath ...string) error {
|
||||
if err := checkActionFrequency(); err != nil {
|
||||
return err
|
||||
}
|
||||
return restartWebServers(ctx, "", newExeFilePath...)
|
||||
return restartWebServers(ctx, nil, newExeFilePath)
|
||||
}
|
||||
|
||||
// ShutdownAllServer shuts down all servers of current process.
|
||||
// ShutdownAllServer shuts down all servers of current process gracefully.
|
||||
func ShutdownAllServer(ctx context.Context) error {
|
||||
serverActionLocker.Lock()
|
||||
defer serverActionLocker.Unlock()
|
||||
@ -78,7 +78,7 @@ func ShutdownAllServer(ctx context.Context) error {
|
||||
if err := checkActionFrequency(); err != nil {
|
||||
return err
|
||||
}
|
||||
shutdownWebServers(ctx)
|
||||
shutdownWebServersGracefully(ctx, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -156,12 +156,12 @@ func forkReloadProcess(ctx context.Context, newExeFilePath ...string) error {
|
||||
}
|
||||
|
||||
// forkRestartProcess creates a new server process.
|
||||
func forkRestartProcess(ctx context.Context, newExeFilePath ...string) error {
|
||||
func forkRestartProcess(ctx context.Context, newExeFilePath string) error {
|
||||
var (
|
||||
path = os.Args[0]
|
||||
)
|
||||
if len(newExeFilePath) > 0 {
|
||||
path = newExeFilePath[0]
|
||||
if newExeFilePath != "" {
|
||||
path = newExeFilePath
|
||||
}
|
||||
if err := os.Unsetenv(adminActionReloadEnvKey); err != nil {
|
||||
intlog.Errorf(ctx, `%+v`, err)
|
||||
@ -198,8 +198,8 @@ func bufferToServerFdMap(buffer []byte) map[string]listenerFdMap {
|
||||
j, _ := gjson.LoadContent(buffer)
|
||||
for k := range j.Var().Map() {
|
||||
m := make(map[string]string)
|
||||
for k, v := range j.Get(k).Map() {
|
||||
m[k] = gconv.String(v)
|
||||
for mapKey, mapValue := range j.Get(k).MapStrStr() {
|
||||
m[mapKey] = mapValue
|
||||
}
|
||||
sfm[k] = m
|
||||
}
|
||||
@ -208,61 +208,51 @@ func bufferToServerFdMap(buffer []byte) map[string]listenerFdMap {
|
||||
}
|
||||
|
||||
// restartWebServers restarts all servers.
|
||||
func restartWebServers(ctx context.Context, signal string, newExeFilePath ...string) error {
|
||||
func restartWebServers(ctx context.Context, signal os.Signal, newExeFilePath string) error {
|
||||
serverProcessStatus.Set(adminActionRestarting)
|
||||
if runtime.GOOS == "windows" {
|
||||
if len(signal) > 0 {
|
||||
if signal != nil {
|
||||
// Controlled by signal.
|
||||
forceCloseWebServers(ctx)
|
||||
if err := forkRestartProcess(ctx, newExeFilePath...); err != nil {
|
||||
if err := forkRestartProcess(ctx, newExeFilePath); err != nil {
|
||||
intlog.Errorf(ctx, `%+v`, err)
|
||||
}
|
||||
} else {
|
||||
// Controlled by web page.
|
||||
// It should ensure the response wrote to client and then close all servers gracefully.
|
||||
gtimer.SetTimeout(ctx, time.Second, func(ctx context.Context) {
|
||||
forceCloseWebServers(ctx)
|
||||
if err := forkRestartProcess(ctx, newExeFilePath...); err != nil {
|
||||
intlog.Errorf(ctx, `%+v`, err)
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
if err := forkReloadProcess(ctx, newExeFilePath...); err != nil {
|
||||
glog.Printf(ctx, "%d: server restarts failed", gproc.Pid())
|
||||
serverProcessStatus.Set(adminActionNone)
|
||||
return err
|
||||
} else {
|
||||
if len(signal) > 0 {
|
||||
glog.Printf(ctx, "%d: server restarting by signal: %s", gproc.Pid(), signal)
|
||||
} else {
|
||||
glog.Printf(ctx, "%d: server restarting by web admin", gproc.Pid())
|
||||
// Controlled by web page.
|
||||
// It should ensure the response wrote to client and then close all servers gracefully.
|
||||
gtimer.SetTimeout(ctx, time.Second, func(ctx context.Context) {
|
||||
forceCloseWebServers(ctx)
|
||||
if err := forkRestartProcess(ctx, newExeFilePath); err != nil {
|
||||
intlog.Errorf(ctx, `%+v`, err)
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
if err := forkReloadProcess(ctx, newExeFilePath); err != nil {
|
||||
glog.Printf(ctx, "%d: server restarts failed", gproc.Pid())
|
||||
serverProcessStatus.Set(adminActionNone)
|
||||
return err
|
||||
} else {
|
||||
if signal != nil {
|
||||
glog.Printf(ctx, "%d: server restarting by signal: %s", gproc.Pid(), signal)
|
||||
} else {
|
||||
glog.Printf(ctx, "%d: server restarting by web admin", gproc.Pid())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// shutdownWebServers shuts down all servers.
|
||||
func shutdownWebServers(ctx context.Context, signal ...string) {
|
||||
serverProcessStatus.Set(adminActionShuttingDown)
|
||||
if len(signal) > 0 {
|
||||
glog.Printf(ctx, "%d: server shutting down by signal: %s", gproc.Pid(), signal[0])
|
||||
forceCloseWebServers(ctx)
|
||||
allShutdownChan <- struct{}{}
|
||||
} else {
|
||||
glog.Printf(ctx, "%d: server shutting down by api", gproc.Pid())
|
||||
gtimer.SetTimeout(ctx, time.Second, func(ctx context.Context) {
|
||||
forceCloseWebServers(ctx)
|
||||
allShutdownChan <- struct{}{}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// shutdownWebServersGracefully gracefully shuts down all servers.
|
||||
func shutdownWebServersGracefully(ctx context.Context, signal ...string) {
|
||||
if len(signal) > 0 {
|
||||
glog.Printf(ctx, "%d: server gracefully shutting down by signal: %s", gproc.Pid(), signal[0])
|
||||
func shutdownWebServersGracefully(ctx context.Context, signal os.Signal) {
|
||||
serverProcessStatus.Set(adminActionShuttingDown)
|
||||
if signal != nil {
|
||||
glog.Printf(
|
||||
ctx,
|
||||
"%d: server gracefully shutting down by signal: %s",
|
||||
gproc.Pid(), signal.String(),
|
||||
)
|
||||
} else {
|
||||
glog.Printf(ctx, "%d: server gracefully shutting down by api", gproc.Pid())
|
||||
}
|
||||
@ -296,7 +286,7 @@ func handleProcessMessage() {
|
||||
if msg := gproc.Receive(adminGProcCommGroup); msg != nil {
|
||||
if bytes.EqualFold(msg.Data, []byte("exit")) {
|
||||
intlog.Printf(ctx, "%d: process message: exit", gproc.Pid())
|
||||
shutdownWebServersGracefully(ctx)
|
||||
shutdownWebServersGracefully(ctx, nil)
|
||||
allShutdownChan <- struct{}{}
|
||||
intlog.Printf(ctx, "%d: process message: exit done", gproc.Pid())
|
||||
return
|
||||
|
||||
@ -12,62 +12,30 @@ package ghttp
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
"github.com/gogf/gf/v2/os/gproc"
|
||||
)
|
||||
|
||||
// procSignalChan is the channel for listening to the signal.
|
||||
var procSignalChan = make(chan os.Signal)
|
||||
|
||||
// handleProcessSignal handles all signals from system in blocking way.
|
||||
func handleProcessSignal() {
|
||||
var (
|
||||
ctx = context.TODO()
|
||||
sig os.Signal
|
||||
)
|
||||
signal.Notify(
|
||||
procSignalChan,
|
||||
syscall.SIGINT,
|
||||
syscall.SIGQUIT,
|
||||
syscall.SIGKILL,
|
||||
syscall.SIGTERM,
|
||||
syscall.SIGABRT,
|
||||
syscall.SIGUSR1,
|
||||
syscall.SIGUSR2,
|
||||
)
|
||||
for {
|
||||
sig = <-procSignalChan
|
||||
intlog.Printf(ctx, `signal received: %s`, sig.String())
|
||||
switch sig {
|
||||
// Shutdown the servers.
|
||||
case syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGABRT:
|
||||
shutdownWebServers(ctx, sig.String())
|
||||
var ctx = context.TODO()
|
||||
gproc.AddSigHandlerShutdown(func(sig os.Signal) {
|
||||
shutdownWebServersGracefully(ctx, sig)
|
||||
})
|
||||
gproc.AddSigHandler(func(sig os.Signal) {
|
||||
// If the graceful restart feature is not enabled,
|
||||
// it does nothing except printing a warning log.
|
||||
if !gracefulEnabled {
|
||||
glog.Warning(ctx, "graceful reload feature is disabled")
|
||||
return
|
||||
|
||||
// Shutdown the servers gracefully.
|
||||
// Especially from K8S when running server in POD.
|
||||
case syscall.SIGTERM:
|
||||
shutdownWebServersGracefully(ctx, sig.String())
|
||||
return
|
||||
|
||||
// Restart the servers.
|
||||
case syscall.SIGUSR1:
|
||||
// If the graceful restart feature is not enabled,
|
||||
// it does nothing except printing a warning log.
|
||||
if !gracefulEnabled {
|
||||
glog.Warning(ctx, "graceful reload feature is disabled")
|
||||
continue
|
||||
}
|
||||
|
||||
if err := restartWebServers(ctx, sig.String()); err != nil {
|
||||
intlog.Errorf(ctx, `%+v`, err)
|
||||
}
|
||||
return
|
||||
|
||||
default:
|
||||
}
|
||||
}
|
||||
if err := restartWebServers(ctx, sig, ""); err != nil {
|
||||
intlog.Errorf(ctx, `%+v`, err)
|
||||
}
|
||||
}, syscall.SIGUSR1)
|
||||
|
||||
gproc.Listen()
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
|
||||
package ghttp
|
||||
|
||||
// registerSignalHandler does nothing on a window platform.
|
||||
// registerSignalHandler does nothing on window platform.
|
||||
func handleProcessSignal() {
|
||||
|
||||
}
|
||||
|
||||
@ -97,11 +97,6 @@ func (s *gracefulServer) Fd() uintptr {
|
||||
return 0
|
||||
}
|
||||
|
||||
// setFd sets the file descriptor for current server.
|
||||
func (s *gracefulServer) setFd(fd int) {
|
||||
s.fd = uintptr(fd)
|
||||
}
|
||||
|
||||
// CreateListener creates listener on configured address.
|
||||
func (s *gracefulServer) CreateListener() error {
|
||||
ln, err := s.getNetListener()
|
||||
|
||||
@ -159,6 +159,8 @@ func (s *Server) doSetHandler(
|
||||
switch item.Type {
|
||||
case HandlerTypeHandler, HandlerTypeObject:
|
||||
duplicatedHandler = items[i]
|
||||
}
|
||||
if duplicatedHandler != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ import (
|
||||
func Benchmark_TrimRightCharWithStrings(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
path := "//////////"
|
||||
path = strings.TrimRight(path, "/")
|
||||
strings.TrimRight(path, "/")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -205,8 +205,8 @@ func Test_Middleware_Status(t *testing.T) {
|
||||
t.Assert(client.GetContent(ctx, "/user/list"), "200")
|
||||
|
||||
resp, err := client.Get(ctx, "/")
|
||||
defer resp.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp.Close()
|
||||
t.Assert(resp.StatusCode, 404)
|
||||
})
|
||||
}
|
||||
@ -664,7 +664,7 @@ func Test_Middleware_Panic(t *testing.T) {
|
||||
group.Middleware(func(r *ghttp.Request) {
|
||||
i++
|
||||
panic("error")
|
||||
r.Middleware.Next()
|
||||
// r.Middleware.Next()
|
||||
}, func(r *ghttp.Request) {
|
||||
i++
|
||||
r.Middleware.Next()
|
||||
|
||||
@ -9,11 +9,11 @@ package ghttp_test
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/encoding/gbase64"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gbase64"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
@ -213,15 +213,17 @@ func Test_Request_BasicAuth(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_Request_SetCtx(t *testing.T) {
|
||||
type ctxKey string
|
||||
const testkey ctxKey = "test"
|
||||
s := g.Server(guid.S())
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.Middleware(func(r *ghttp.Request) {
|
||||
ctx := context.WithValue(r.Context(), "test", 1)
|
||||
ctx := context.WithValue(r.Context(), testkey, 1)
|
||||
r.SetCtx(ctx)
|
||||
r.Middleware.Next()
|
||||
})
|
||||
group.ALL("/", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.Context().Value("test"))
|
||||
r.Response.Write(r.Context().Value(testkey))
|
||||
})
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
@ -238,15 +240,17 @@ func Test_Request_SetCtx(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_Request_GetCtx(t *testing.T) {
|
||||
type ctxKey string
|
||||
const testkey ctxKey = "test"
|
||||
s := g.Server(guid.S())
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.Middleware(func(r *ghttp.Request) {
|
||||
ctx := context.WithValue(r.GetCtx(), "test", 1)
|
||||
ctx := context.WithValue(r.GetCtx(), testkey, 1)
|
||||
r.SetCtx(ctx)
|
||||
r.Middleware.Next()
|
||||
})
|
||||
group.ALL("/", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.Context().Value("test"))
|
||||
r.Response.Write(r.Context().Value(testkey))
|
||||
})
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
@ -290,9 +294,6 @@ func Test_Request_Form(t *testing.T) {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
type Default struct {
|
||||
D string
|
||||
}
|
||||
s := g.Server(guid.S())
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.ALL("/", func(r *ghttp.Request) {
|
||||
|
||||
@ -122,23 +122,23 @@ func Test_Router_Method(t *testing.T) {
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
|
||||
resp1, err := client.Get(ctx, "/get")
|
||||
defer resp1.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp1.Close()
|
||||
t.Assert(resp1.StatusCode, 200)
|
||||
|
||||
resp2, err := client.Post(ctx, "/get")
|
||||
defer resp2.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp2.Close()
|
||||
t.Assert(resp2.StatusCode, 404)
|
||||
|
||||
resp3, err := client.Get(ctx, "/post")
|
||||
defer resp3.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp3.Close()
|
||||
t.Assert(resp3.StatusCode, 404)
|
||||
|
||||
resp4, err := client.Post(ctx, "/post")
|
||||
defer resp4.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp4.Close()
|
||||
t.Assert(resp4.StatusCode, 200)
|
||||
})
|
||||
}
|
||||
@ -194,28 +194,28 @@ func Test_Router_Status(t *testing.T) {
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
|
||||
resp1, err := client.Get(ctx, "/200")
|
||||
defer resp1.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp1.Close()
|
||||
t.Assert(resp1.StatusCode, 200)
|
||||
|
||||
resp2, err := client.Get(ctx, "/300")
|
||||
defer resp2.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp2.Close()
|
||||
t.Assert(resp2.StatusCode, 300)
|
||||
|
||||
resp3, err := client.Get(ctx, "/400")
|
||||
defer resp3.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp3.Close()
|
||||
t.Assert(resp3.StatusCode, 400)
|
||||
|
||||
resp4, err := client.Get(ctx, "/500")
|
||||
defer resp4.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp4.Close()
|
||||
t.Assert(resp4.StatusCode, 500)
|
||||
|
||||
resp5, err := client.Get(ctx, "/404")
|
||||
defer resp5.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp5.Close()
|
||||
t.Assert(resp5.StatusCode, 404)
|
||||
})
|
||||
}
|
||||
@ -239,8 +239,8 @@ func Test_Router_CustomStatusHandler(t *testing.T) {
|
||||
|
||||
t.Assert(client.GetContent(ctx, "/"), "hello")
|
||||
resp, err := client.Get(ctx, "/ThisDoesNotExist")
|
||||
defer resp.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp.Close()
|
||||
t.Assert(resp.StatusCode, 404)
|
||||
t.Assert(resp.ReadAllString(), "404 page")
|
||||
})
|
||||
@ -263,8 +263,8 @@ func Test_Router_404(t *testing.T) {
|
||||
|
||||
t.Assert(client.GetContent(ctx, "/"), "hello")
|
||||
resp, err := client.Get(ctx, "/ThisDoesNotExist")
|
||||
defer resp.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp.Close()
|
||||
t.Assert(resp.StatusCode, 404)
|
||||
})
|
||||
}
|
||||
|
||||
@ -86,23 +86,23 @@ func Test_Router_DomainMethod(t *testing.T) {
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
|
||||
resp1, err := client.Get(ctx, "/get")
|
||||
defer resp1.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp1.Close()
|
||||
t.Assert(resp1.StatusCode, 404)
|
||||
|
||||
resp2, err := client.Post(ctx, "/get")
|
||||
defer resp2.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp2.Close()
|
||||
t.Assert(resp2.StatusCode, 404)
|
||||
|
||||
resp3, err := client.Get(ctx, "/post")
|
||||
defer resp3.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp3.Close()
|
||||
t.Assert(resp3.StatusCode, 404)
|
||||
|
||||
resp4, err := client.Post(ctx, "/post")
|
||||
defer resp4.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp4.Close()
|
||||
t.Assert(resp4.StatusCode, 404)
|
||||
})
|
||||
|
||||
@ -111,23 +111,23 @@ func Test_Router_DomainMethod(t *testing.T) {
|
||||
client.SetPrefix(fmt.Sprintf("http://localhost:%d", s.GetListenedPort()))
|
||||
|
||||
resp1, err := client.Get(ctx, "/get")
|
||||
defer resp1.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp1.Close()
|
||||
t.Assert(resp1.StatusCode, 200)
|
||||
|
||||
resp2, err := client.Post(ctx, "/get")
|
||||
defer resp2.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp2.Close()
|
||||
t.Assert(resp2.StatusCode, 404)
|
||||
|
||||
resp3, err := client.Get(ctx, "/post")
|
||||
defer resp3.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp3.Close()
|
||||
t.Assert(resp3.StatusCode, 404)
|
||||
|
||||
resp4, err := client.Post(ctx, "/post")
|
||||
defer resp4.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp4.Close()
|
||||
t.Assert(resp4.StatusCode, 200)
|
||||
})
|
||||
|
||||
@ -136,23 +136,23 @@ func Test_Router_DomainMethod(t *testing.T) {
|
||||
client.SetPrefix(fmt.Sprintf("http://local:%d", s.GetListenedPort()))
|
||||
|
||||
resp1, err := client.Get(ctx, "/get")
|
||||
defer resp1.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp1.Close()
|
||||
t.Assert(resp1.StatusCode, 200)
|
||||
|
||||
resp2, err := client.Post(ctx, "/get")
|
||||
defer resp2.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp2.Close()
|
||||
t.Assert(resp2.StatusCode, 404)
|
||||
|
||||
resp3, err := client.Get(ctx, "/post")
|
||||
defer resp3.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp3.Close()
|
||||
t.Assert(resp3.StatusCode, 404)
|
||||
|
||||
resp4, err := client.Post(ctx, "/post")
|
||||
defer resp4.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp4.Close()
|
||||
t.Assert(resp4.StatusCode, 200)
|
||||
})
|
||||
}
|
||||
@ -182,23 +182,23 @@ func Test_Router_DomainStatus(t *testing.T) {
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
|
||||
resp1, err := client.Get(ctx, "/200")
|
||||
defer resp1.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp1.Close()
|
||||
t.Assert(resp1.StatusCode, 404)
|
||||
|
||||
resp2, err := client.Get(ctx, "/300")
|
||||
defer resp2.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp2.Close()
|
||||
t.Assert(resp2.StatusCode, 404)
|
||||
|
||||
resp3, err := client.Get(ctx, "/400")
|
||||
defer resp3.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp3.Close()
|
||||
t.Assert(resp3.StatusCode, 404)
|
||||
|
||||
resp4, err := client.Get(ctx, "/500")
|
||||
defer resp4.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp4.Close()
|
||||
t.Assert(resp4.StatusCode, 404)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
@ -206,23 +206,23 @@ func Test_Router_DomainStatus(t *testing.T) {
|
||||
client.SetPrefix(fmt.Sprintf("http://localhost:%d", s.GetListenedPort()))
|
||||
|
||||
resp1, err := client.Get(ctx, "/200")
|
||||
defer resp1.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp1.Close()
|
||||
t.Assert(resp1.StatusCode, 200)
|
||||
|
||||
resp2, err := client.Get(ctx, "/300")
|
||||
defer resp2.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp2.Close()
|
||||
t.Assert(resp2.StatusCode, 300)
|
||||
|
||||
resp3, err := client.Get(ctx, "/400")
|
||||
defer resp3.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp3.Close()
|
||||
t.Assert(resp3.StatusCode, 400)
|
||||
|
||||
resp4, err := client.Get(ctx, "/500")
|
||||
defer resp4.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp4.Close()
|
||||
t.Assert(resp4.StatusCode, 500)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
@ -230,23 +230,23 @@ func Test_Router_DomainStatus(t *testing.T) {
|
||||
client.SetPrefix(fmt.Sprintf("http://local:%d", s.GetListenedPort()))
|
||||
|
||||
resp1, err := client.Get(ctx, "/200")
|
||||
defer resp1.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp1.Close()
|
||||
t.Assert(resp1.StatusCode, 200)
|
||||
|
||||
resp2, err := client.Get(ctx, "/300")
|
||||
defer resp2.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp2.Close()
|
||||
t.Assert(resp2.StatusCode, 300)
|
||||
|
||||
resp3, err := client.Get(ctx, "/400")
|
||||
defer resp3.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp3.Close()
|
||||
t.Assert(resp3.StatusCode, 400)
|
||||
|
||||
resp4, err := client.Get(ctx, "/500")
|
||||
defer resp4.Close()
|
||||
t.AssertNil(err)
|
||||
defer resp4.Close()
|
||||
t.Assert(resp4.StatusCode, 500)
|
||||
})
|
||||
}
|
||||
|
||||
@ -54,15 +54,15 @@ func Test_Server_Wrap_Handler(t *testing.T) {
|
||||
client := g.Client()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d/api", s.GetListenedPort()))
|
||||
|
||||
response, er1 := client.Get(ctx, "/wrapf")
|
||||
response, err := client.Get(ctx, "/wrapf")
|
||||
t.AssertNil(err)
|
||||
defer response.Close()
|
||||
t.Assert(er1, nil)
|
||||
t.Assert(response.StatusCode, http.StatusBadRequest)
|
||||
t.Assert(response.ReadAllString(), str1)
|
||||
|
||||
response2, er2 := client.Post(ctx, "/wraph")
|
||||
response2, err := client.Post(ctx, "/wraph")
|
||||
t.AssertNil(err)
|
||||
defer response2.Close()
|
||||
t.Assert(er2, nil)
|
||||
t.Assert(response2.StatusCode, http.StatusInternalServerError)
|
||||
t.Assert(response2.ReadAllString(), str2)
|
||||
})
|
||||
|
||||
@ -43,6 +43,7 @@ const (
|
||||
TypeBoolean = `boolean`
|
||||
TypeArray = `array`
|
||||
TypeString = `string`
|
||||
TypeFile = `file`
|
||||
TypeObject = `object`
|
||||
FormatInt32 = `int32`
|
||||
FormatInt64 = `int64`
|
||||
@ -145,6 +146,8 @@ func (oai *OpenApiV3) golangTypeToOAIType(t reflect.Type) string {
|
||||
switch t.String() {
|
||||
case `time.Time`, `gtime.Time`:
|
||||
return TypeString
|
||||
case `ghttp.UploadFile`:
|
||||
return TypeFile
|
||||
}
|
||||
return TypeObject
|
||||
|
||||
|
||||
@ -76,7 +76,7 @@ func (oai *OpenApiV3) newSchemaRefWithGolangType(golangType reflect.Type, tagMap
|
||||
}
|
||||
schemaRef.Value = schema
|
||||
switch oaiType {
|
||||
case TypeString:
|
||||
case TypeString, TypeFile:
|
||||
// Nothing to do.
|
||||
case TypeInteger:
|
||||
if schemaRef.Value.Default != nil {
|
||||
|
||||
@ -19,6 +19,10 @@ import (
|
||||
// Func is the cache function that calculates and returns the value.
|
||||
type Func func(ctx context.Context) (value interface{}, err error)
|
||||
|
||||
const (
|
||||
DurationNoExpire = 0 // Expire duration that never expires.
|
||||
)
|
||||
|
||||
// Default cache object.
|
||||
var defaultCache = New()
|
||||
|
||||
|
||||
79
os/gcfg/gcfg_adapter_content.go
Normal file
79
os/gcfg/gcfg_adapter_content.go
Normal file
@ -0,0 +1,79 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gcfg
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
// AdapterContent implements interface Adapter using content.
|
||||
// The configuration content supports the coding types as package `gjson`.
|
||||
type AdapterContent struct {
|
||||
jsonVar *gvar.Var // The pared JSON object for configuration content, type: *gjson.Json.
|
||||
}
|
||||
|
||||
// NewAdapterContent returns a new configuration management object using custom content.
|
||||
// The parameter `content` specifies the default configuration content for reading.
|
||||
func NewAdapterContent(content ...string) (*AdapterContent, error) {
|
||||
a := &AdapterContent{
|
||||
jsonVar: gvar.New(nil, true),
|
||||
}
|
||||
if len(content) > 0 {
|
||||
if err := a.SetContent(content[0]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// SetContent sets customized configuration content for specified `file`.
|
||||
// The `file` is unnecessary param, default is DefaultConfigFile.
|
||||
func (a *AdapterContent) SetContent(content string) error {
|
||||
j, err := gjson.LoadContent(content, true)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, `load configuration content failed`)
|
||||
}
|
||||
a.jsonVar.Set(j)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Available checks and returns the backend configuration service is available.
|
||||
// The optional parameter `resource` specifies certain configuration resource.
|
||||
//
|
||||
// Note that this function does not return error as it just does simply check for
|
||||
// backend configuration service.
|
||||
func (a *AdapterContent) Available(ctx context.Context, resource ...string) (ok bool) {
|
||||
if a.jsonVar.IsNil() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Get retrieves and returns value by specified `pattern` in current resource.
|
||||
// Pattern like:
|
||||
// "x.y.z" for map item.
|
||||
// "x.0.y" for slice item.
|
||||
func (a *AdapterContent) Get(ctx context.Context, pattern string) (value interface{}, err error) {
|
||||
if a.jsonVar.IsNil() {
|
||||
return nil, nil
|
||||
}
|
||||
return a.jsonVar.Val().(*gjson.Json).Get(pattern).Val(), nil
|
||||
}
|
||||
|
||||
// Data retrieves and returns all configuration data in current resource as map.
|
||||
// Note that this function may lead lots of memory usage if configuration data is too large,
|
||||
// you can implement this function if necessary.
|
||||
func (a *AdapterContent) Data(ctx context.Context) (data map[string]interface{}, err error) {
|
||||
if a.jsonVar.IsNil() {
|
||||
return nil, nil
|
||||
}
|
||||
return a.jsonVar.Val().(*gjson.Json).Var().Map(), nil
|
||||
}
|
||||
@ -23,6 +23,7 @@ import (
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
// AdapterFile implements interface Adapter using file.
|
||||
type AdapterFile struct {
|
||||
defaultName string // Default configuration file name.
|
||||
searchPaths *garray.StrArray // Searching path array.
|
||||
@ -113,19 +114,19 @@ func NewAdapterFile(file ...string) (*AdapterFile, error) {
|
||||
//
|
||||
// Note that, turning on this feature is quite expensive, and it is not recommended
|
||||
// allowing separators in the key names. It is best to avoid this on the application side.
|
||||
func (c *AdapterFile) SetViolenceCheck(check bool) {
|
||||
c.violenceCheck = check
|
||||
c.Clear()
|
||||
func (a *AdapterFile) SetViolenceCheck(check bool) {
|
||||
a.violenceCheck = check
|
||||
a.Clear()
|
||||
}
|
||||
|
||||
// SetFileName sets the default configuration file name.
|
||||
func (c *AdapterFile) SetFileName(name string) {
|
||||
c.defaultName = name
|
||||
func (a *AdapterFile) SetFileName(name string) {
|
||||
a.defaultName = name
|
||||
}
|
||||
|
||||
// GetFileName returns the default configuration file name.
|
||||
func (c *AdapterFile) GetFileName() string {
|
||||
return c.defaultName
|
||||
func (a *AdapterFile) GetFileName() string {
|
||||
return a.defaultName
|
||||
}
|
||||
|
||||
// Get retrieves and returns value by specified `pattern`.
|
||||
@ -136,8 +137,8 @@ func (c *AdapterFile) GetFileName() string {
|
||||
// "list.10", "array.0.name", "array.0.1.id".
|
||||
//
|
||||
// It returns a default value specified by `def` if value for `pattern` is not found.
|
||||
func (c *AdapterFile) Get(ctx context.Context, pattern string) (value interface{}, err error) {
|
||||
j, err := c.getJson()
|
||||
func (a *AdapterFile) Get(ctx context.Context, pattern string) (value interface{}, err error) {
|
||||
j, err := a.getJson()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -152,8 +153,8 @@ func (c *AdapterFile) Get(ctx context.Context, pattern string) (value interface{
|
||||
// It is commonly used for updates certain configuration value in runtime.
|
||||
// Note that, it is not recommended using `Set` configuration at runtime as the configuration would be
|
||||
// automatically refreshed if underlying configuration file changed.
|
||||
func (c *AdapterFile) Set(pattern string, value interface{}) error {
|
||||
j, err := c.getJson()
|
||||
func (a *AdapterFile) Set(pattern string, value interface{}) error {
|
||||
j, err := a.getJson()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -164,8 +165,8 @@ func (c *AdapterFile) Set(pattern string, value interface{}) error {
|
||||
}
|
||||
|
||||
// Data retrieves and returns all configuration data as map type.
|
||||
func (c *AdapterFile) Data(ctx context.Context) (data map[string]interface{}, err error) {
|
||||
j, err := c.getJson()
|
||||
func (a *AdapterFile) Data(ctx context.Context) (data map[string]interface{}, err error) {
|
||||
j, err := a.getJson()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -176,8 +177,8 @@ func (c *AdapterFile) Data(ctx context.Context) (data map[string]interface{}, er
|
||||
}
|
||||
|
||||
// MustGet acts as function Get, but it panics if error occurs.
|
||||
func (c *AdapterFile) MustGet(ctx context.Context, pattern string) *gvar.Var {
|
||||
v, err := c.Get(ctx, pattern)
|
||||
func (a *AdapterFile) MustGet(ctx context.Context, pattern string) *gvar.Var {
|
||||
v, err := a.Get(ctx, pattern)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -186,26 +187,26 @@ func (c *AdapterFile) MustGet(ctx context.Context, pattern string) *gvar.Var {
|
||||
|
||||
// Clear removes all parsed configuration files content cache,
|
||||
// which will force reload configuration content from file.
|
||||
func (c *AdapterFile) Clear() {
|
||||
c.jsonMap.Clear()
|
||||
func (a *AdapterFile) Clear() {
|
||||
a.jsonMap.Clear()
|
||||
}
|
||||
|
||||
// Dump prints current Json object with more manually readable.
|
||||
func (c *AdapterFile) Dump() {
|
||||
if j, _ := c.getJson(); j != nil {
|
||||
func (a *AdapterFile) Dump() {
|
||||
if j, _ := a.getJson(); j != nil {
|
||||
j.Dump()
|
||||
}
|
||||
}
|
||||
|
||||
// Available checks and returns whether configuration of given `file` is available.
|
||||
func (c *AdapterFile) Available(ctx context.Context, fileName ...string) bool {
|
||||
checkFileName := gutil.GetOrDefaultStr(c.defaultName, fileName...)
|
||||
func (a *AdapterFile) Available(ctx context.Context, fileName ...string) bool {
|
||||
checkFileName := gutil.GetOrDefaultStr(a.defaultName, fileName...)
|
||||
// Custom configuration content exists.
|
||||
if c.GetContent(checkFileName) != "" {
|
||||
if a.GetContent(checkFileName) != "" {
|
||||
return true
|
||||
}
|
||||
// Configuration file exists in system path.
|
||||
if path, _ := c.GetFilePath(checkFileName); path != "" {
|
||||
if path, _ := a.GetFilePath(checkFileName); path != "" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@ -213,12 +214,12 @@ func (c *AdapterFile) Available(ctx context.Context, fileName ...string) bool {
|
||||
|
||||
// autoCheckAndAddMainPkgPathToSearchPaths automatically checks and adds directory path of package main
|
||||
// to the searching path list if it's currently in development environment.
|
||||
func (c *AdapterFile) autoCheckAndAddMainPkgPathToSearchPaths() {
|
||||
func (a *AdapterFile) autoCheckAndAddMainPkgPathToSearchPaths() {
|
||||
if gmode.IsDevelop() {
|
||||
mainPkgPath := gfile.MainPkgPath()
|
||||
if mainPkgPath != "" {
|
||||
if !c.searchPaths.Contains(mainPkgPath) {
|
||||
c.searchPaths.Append(mainPkgPath)
|
||||
if !a.searchPaths.Contains(mainPkgPath) {
|
||||
a.searchPaths.Append(mainPkgPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -226,26 +227,26 @@ func (c *AdapterFile) autoCheckAndAddMainPkgPathToSearchPaths() {
|
||||
|
||||
// getJson returns a *gjson.Json object for the specified `file` content.
|
||||
// It would print error if file reading fails. It returns nil if any error occurs.
|
||||
func (c *AdapterFile) getJson(fileName ...string) (configJson *gjson.Json, err error) {
|
||||
func (a *AdapterFile) getJson(fileName ...string) (configJson *gjson.Json, err error) {
|
||||
var (
|
||||
usedFileName = c.defaultName
|
||||
usedFileName = a.defaultName
|
||||
)
|
||||
if len(fileName) > 0 && fileName[0] != "" {
|
||||
usedFileName = fileName[0]
|
||||
} else {
|
||||
usedFileName = c.defaultName
|
||||
usedFileName = a.defaultName
|
||||
}
|
||||
// It uses json map to cache specified configuration file content.
|
||||
result := c.jsonMap.GetOrSetFuncLock(usedFileName, func() interface{} {
|
||||
result := a.jsonMap.GetOrSetFuncLock(usedFileName, func() interface{} {
|
||||
var (
|
||||
content string
|
||||
filePath string
|
||||
)
|
||||
// The configured content can be any kind of data type different from its file type.
|
||||
isFromConfigContent := true
|
||||
if content = c.GetContent(usedFileName); content == "" {
|
||||
if content = a.GetContent(usedFileName); content == "" {
|
||||
isFromConfigContent = false
|
||||
filePath, err = c.GetFilePath(usedFileName)
|
||||
filePath, err = a.GetFilePath(usedFileName)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
@ -273,12 +274,12 @@ func (c *AdapterFile) getJson(fileName ...string) (configJson *gjson.Json, err e
|
||||
}
|
||||
return nil
|
||||
}
|
||||
configJson.SetViolenceCheck(c.violenceCheck)
|
||||
configJson.SetViolenceCheck(a.violenceCheck)
|
||||
// Add monitor for this configuration file,
|
||||
// any changes of this file will refresh its cache in Config object.
|
||||
if filePath != "" && !gres.Contains(filePath) {
|
||||
_, err = gfsnotify.Add(filePath, func(event *gfsnotify.Event) {
|
||||
c.jsonMap.Remove(usedFileName)
|
||||
a.jsonMap.Remove(usedFileName)
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
|
||||
@ -14,7 +14,7 @@ import (
|
||||
|
||||
// SetContent sets customized configuration content for specified `file`.
|
||||
// The `file` is unnecessary param, default is DefaultConfigFile.
|
||||
func (c *AdapterFile) SetContent(content string, file ...string) {
|
||||
func (a *AdapterFile) SetContent(content string, file ...string) {
|
||||
name := DefaultConfigFileName
|
||||
if len(file) > 0 {
|
||||
name = file[0]
|
||||
@ -36,7 +36,7 @@ func (c *AdapterFile) SetContent(content string, file ...string) {
|
||||
|
||||
// GetContent returns customized configuration content for specified `file`.
|
||||
// The `file` is unnecessary param, default is DefaultConfigFile.
|
||||
func (c *AdapterFile) GetContent(file ...string) string {
|
||||
func (a *AdapterFile) GetContent(file ...string) string {
|
||||
name := DefaultConfigFileName
|
||||
if len(file) > 0 {
|
||||
name = file[0]
|
||||
@ -46,7 +46,7 @@ func (c *AdapterFile) GetContent(file ...string) string {
|
||||
|
||||
// RemoveContent removes the global configuration with specified `file`.
|
||||
// If `name` is not passed, it removes configuration of the default group name.
|
||||
func (c *AdapterFile) RemoveContent(file ...string) {
|
||||
func (a *AdapterFile) RemoveContent(file ...string) {
|
||||
name := DefaultConfigFileName
|
||||
if len(file) > 0 {
|
||||
name = file[0]
|
||||
@ -69,7 +69,7 @@ func (c *AdapterFile) RemoveContent(file ...string) {
|
||||
}
|
||||
|
||||
// ClearContent removes all global configuration contents.
|
||||
func (c *AdapterFile) ClearContent() {
|
||||
func (a *AdapterFile) ClearContent() {
|
||||
customConfigContentMap.Clear()
|
||||
// Clear cache for all instances.
|
||||
localInstances.LockFunc(func(m map[string]interface{}) {
|
||||
|
||||
@ -24,7 +24,7 @@ import (
|
||||
// SetPath sets the configuration directory path for file search.
|
||||
// The parameter `path` can be absolute or relative path,
|
||||
// but absolute path is strongly recommended.
|
||||
func (c *AdapterFile) SetPath(path string) (err error) {
|
||||
func (a *AdapterFile) SetPath(path string) (err error) {
|
||||
var (
|
||||
isDir = false
|
||||
realPath = ""
|
||||
@ -37,7 +37,7 @@ func (c *AdapterFile) SetPath(path string) (err error) {
|
||||
realPath = gfile.RealPath(path)
|
||||
if realPath == "" {
|
||||
// Relative path.
|
||||
c.searchPaths.RLockFunc(func(array []string) {
|
||||
a.searchPaths.RLockFunc(func(array []string) {
|
||||
for _, v := range array {
|
||||
if searchedPath, _ := gspath.Search(v, path); searchedPath != "" {
|
||||
realPath = searchedPath
|
||||
@ -53,9 +53,9 @@ func (c *AdapterFile) SetPath(path string) (err error) {
|
||||
// Path not exist.
|
||||
if realPath == "" {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if c.searchPaths.Len() > 0 {
|
||||
if a.searchPaths.Len() > 0 {
|
||||
buffer.WriteString(fmt.Sprintf(`SetPath failed: cannot find directory "%s" in following paths:`, path))
|
||||
c.searchPaths.RLockFunc(func(array []string) {
|
||||
a.searchPaths.RLockFunc(func(array []string) {
|
||||
for k, v := range array {
|
||||
buffer.WriteString(fmt.Sprintf("\n%d. %s", k+1, v))
|
||||
}
|
||||
@ -74,20 +74,20 @@ func (c *AdapterFile) SetPath(path string) (err error) {
|
||||
)
|
||||
}
|
||||
// Repeated path check.
|
||||
if c.searchPaths.Search(realPath) != -1 {
|
||||
if a.searchPaths.Search(realPath) != -1 {
|
||||
return nil
|
||||
}
|
||||
c.jsonMap.Clear()
|
||||
c.searchPaths.Clear()
|
||||
c.searchPaths.Append(realPath)
|
||||
a.jsonMap.Clear()
|
||||
a.searchPaths.Clear()
|
||||
a.searchPaths.Append(realPath)
|
||||
intlog.Print(context.TODO(), "SetPath:", realPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddPath adds an absolute or relative path to the search paths.
|
||||
func (c *AdapterFile) AddPath(paths ...string) (err error) {
|
||||
func (a *AdapterFile) AddPath(paths ...string) (err error) {
|
||||
for _, path := range paths {
|
||||
if err = c.doAddPath(path); err != nil {
|
||||
if err = a.doAddPath(path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -95,7 +95,7 @@ func (c *AdapterFile) AddPath(paths ...string) (err error) {
|
||||
}
|
||||
|
||||
// doAddPath adds an absolute or relative path to the search paths.
|
||||
func (c *AdapterFile) doAddPath(path string) (err error) {
|
||||
func (a *AdapterFile) doAddPath(path string) (err error) {
|
||||
var (
|
||||
isDir = false
|
||||
realPath = ""
|
||||
@ -110,7 +110,7 @@ func (c *AdapterFile) doAddPath(path string) (err error) {
|
||||
realPath = gfile.RealPath(path)
|
||||
if realPath == "" {
|
||||
// Relative path.
|
||||
c.searchPaths.RLockFunc(func(array []string) {
|
||||
a.searchPaths.RLockFunc(func(array []string) {
|
||||
for _, v := range array {
|
||||
if searchedPath, _ := gspath.Search(v, path); searchedPath != "" {
|
||||
realPath = searchedPath
|
||||
@ -125,9 +125,9 @@ func (c *AdapterFile) doAddPath(path string) (err error) {
|
||||
}
|
||||
if realPath == "" {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if c.searchPaths.Len() > 0 {
|
||||
if a.searchPaths.Len() > 0 {
|
||||
buffer.WriteString(fmt.Sprintf(`AddPath failed: cannot find directory "%s" in following paths:`, path))
|
||||
c.searchPaths.RLockFunc(func(array []string) {
|
||||
a.searchPaths.RLockFunc(func(array []string) {
|
||||
for k, v := range array {
|
||||
buffer.WriteString(fmt.Sprintf("\n%d. %s", k+1, v))
|
||||
}
|
||||
@ -141,23 +141,23 @@ func (c *AdapterFile) doAddPath(path string) (err error) {
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `AddPath failed: path "%s" should be directory type`, path)
|
||||
}
|
||||
// Repeated path check.
|
||||
if c.searchPaths.Search(realPath) != -1 {
|
||||
if a.searchPaths.Search(realPath) != -1 {
|
||||
return nil
|
||||
}
|
||||
c.searchPaths.Append(realPath)
|
||||
a.searchPaths.Append(realPath)
|
||||
intlog.Print(context.TODO(), "AddPath:", realPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPaths returns the searching path array of current configuration manager.
|
||||
func (c *AdapterFile) GetPaths() []string {
|
||||
return c.searchPaths.Slice()
|
||||
func (a *AdapterFile) GetPaths() []string {
|
||||
return a.searchPaths.Slice()
|
||||
}
|
||||
|
||||
// doGetFilePath returns the absolute configuration file path for the given filename by `file`.
|
||||
// If `file` is not passed, it returns the configuration file path of the default name.
|
||||
// It returns an empty `path` string and an error if the given `file` does not exist.
|
||||
func (c *AdapterFile) doGetFilePath(fileName string) (path string) {
|
||||
func (a *AdapterFile) doGetFilePath(fileName string) (path string) {
|
||||
var (
|
||||
tempPath string
|
||||
resFile *gres.File
|
||||
@ -175,7 +175,7 @@ func (c *AdapterFile) doGetFilePath(fileName string) (path string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
c.searchPaths.RLockFunc(func(array []string) {
|
||||
a.searchPaths.RLockFunc(func(array []string) {
|
||||
for _, searchPath := range array {
|
||||
for _, tryFolder := range resourceTryFolders {
|
||||
tempPath = searchPath + tryFolder + fileName
|
||||
@ -191,7 +191,7 @@ func (c *AdapterFile) doGetFilePath(fileName string) (path string) {
|
||||
})
|
||||
}
|
||||
|
||||
c.autoCheckAndAddMainPkgPathToSearchPaths()
|
||||
a.autoCheckAndAddMainPkgPathToSearchPaths()
|
||||
|
||||
// Searching local file system.
|
||||
if path == "" {
|
||||
@ -199,7 +199,7 @@ func (c *AdapterFile) doGetFilePath(fileName string) (path string) {
|
||||
if path = gfile.RealPath(fileName); path != "" && !gfile.IsDir(path) {
|
||||
return
|
||||
}
|
||||
c.searchPaths.RLockFunc(func(array []string) {
|
||||
a.searchPaths.RLockFunc(func(array []string) {
|
||||
for _, searchPath := range array {
|
||||
searchPath = gstr.TrimRight(searchPath, `\/`)
|
||||
for _, tryFolder := range localSystemTryFolders {
|
||||
@ -220,23 +220,23 @@ func (c *AdapterFile) doGetFilePath(fileName string) (path string) {
|
||||
// GetFilePath returns the absolute configuration file path for the given filename by `file`.
|
||||
// If `file` is not passed, it returns the configuration file path of the default name.
|
||||
// It returns an empty `path` string and an error if the given `file` does not exist.
|
||||
func (c *AdapterFile) GetFilePath(fileName ...string) (path string, err error) {
|
||||
func (a *AdapterFile) GetFilePath(fileName ...string) (path string, err error) {
|
||||
var (
|
||||
fileExtName string
|
||||
tempFileName string
|
||||
usedFileName = c.defaultName
|
||||
usedFileName = a.defaultName
|
||||
)
|
||||
if len(fileName) > 0 {
|
||||
usedFileName = fileName[0]
|
||||
}
|
||||
fileExtName = gfile.ExtName(usedFileName)
|
||||
if path = c.doGetFilePath(usedFileName); (path == "" || gfile.IsDir(path)) && !gstr.InArray(supportedFileTypes, fileExtName) {
|
||||
if path = a.doGetFilePath(usedFileName); (path == "" || gfile.IsDir(path)) && !gstr.InArray(supportedFileTypes, fileExtName) {
|
||||
// If it's not using default configuration or its configuration file is not available,
|
||||
// it searches the possible configuration file according to the name and all supported
|
||||
// file types.
|
||||
for _, fileType := range supportedFileTypes {
|
||||
tempFileName = fmt.Sprintf(`%s.%s`, usedFileName, fileType)
|
||||
if path = c.doGetFilePath(tempFileName); path != "" {
|
||||
if path = a.doGetFilePath(tempFileName); path != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -244,7 +244,7 @@ func (c *AdapterFile) GetFilePath(fileName ...string) (path string, err error) {
|
||||
// If it cannot find the path of `file`, it formats and returns a detailed error.
|
||||
if path == "" {
|
||||
var buffer = bytes.NewBuffer(nil)
|
||||
if c.searchPaths.Len() > 0 {
|
||||
if a.searchPaths.Len() > 0 {
|
||||
if !gstr.InArray(supportedFileTypes, fileExtName) {
|
||||
buffer.WriteString(fmt.Sprintf(
|
||||
`possible config files "%s" or "%s" not found in resource manager or following system searching paths:`,
|
||||
@ -256,7 +256,7 @@ func (c *AdapterFile) GetFilePath(fileName ...string) (path string, err error) {
|
||||
usedFileName,
|
||||
))
|
||||
}
|
||||
c.searchPaths.RLockFunc(func(array []string) {
|
||||
a.searchPaths.RLockFunc(func(array []string) {
|
||||
index := 1
|
||||
for _, searchPath := range array {
|
||||
searchPath = gstr.TrimRight(searchPath, `\/`)
|
||||
|
||||
70
os/gcfg/gcfg_z_unit_adapter_content_test.go
Normal file
70
os/gcfg/gcfg_z_unit_adapter_content_test.go
Normal file
@ -0,0 +1,70 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// go test *.go -bench=".*" -benchmem
|
||||
|
||||
package gcfg_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gcfg"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
func TestAdapterContent_Available_Get_Data(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
adapter, err := gcfg.NewAdapterContent()
|
||||
t.AssertNil(err)
|
||||
t.Assert(adapter.Available(ctx), false)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
content := `{"a": 1, "b": 2, "c": {"d": 3}}`
|
||||
adapter, err := gcfg.NewAdapterContent(content)
|
||||
t.AssertNil(err)
|
||||
|
||||
c := gcfg.NewWithAdapter(adapter)
|
||||
t.Assert(c.Available(ctx), true)
|
||||
t.Assert(c.MustGet(ctx, "a"), 1)
|
||||
t.Assert(c.MustGet(ctx, "b"), 2)
|
||||
t.Assert(c.MustGet(ctx, "c.d"), 3)
|
||||
t.Assert(c.MustGet(ctx, "d"), nil)
|
||||
t.Assert(c.MustData(ctx), g.Map{
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
"c": g.Map{
|
||||
"d": 3,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestAdapterContent_SetContent(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
adapter, err := gcfg.NewAdapterContent()
|
||||
t.AssertNil(err)
|
||||
t.Assert(adapter.Available(ctx), false)
|
||||
|
||||
content := `{"a": 1, "b": 2, "c": {"d": 3}}`
|
||||
err = adapter.SetContent(content)
|
||||
t.AssertNil(err)
|
||||
c := gcfg.NewWithAdapter(adapter)
|
||||
t.Assert(c.Available(ctx), true)
|
||||
t.Assert(c.MustGet(ctx, "a"), 1)
|
||||
t.Assert(c.MustGet(ctx, "b"), 2)
|
||||
t.Assert(c.MustGet(ctx, "c.d"), 3)
|
||||
t.Assert(c.MustGet(ctx, "d"), nil)
|
||||
t.Assert(c.MustData(ctx), g.Map{
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
"c": g.Map{
|
||||
"d": 3,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
@ -21,6 +21,7 @@ import (
|
||||
|
||||
"github.com/gogf/gf/v2/debug/gdebug"
|
||||
"github.com/gogf/gf/v2/internal/consts"
|
||||
"github.com/gogf/gf/v2/internal/errors"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
@ -92,7 +93,7 @@ func (l *Logger) getFilePath(now time.Time) string {
|
||||
}
|
||||
|
||||
// print prints `s` to defined writer, logging file or passed `std`.
|
||||
func (l *Logger) print(ctx context.Context, level int, stack string, values ...interface{}) {
|
||||
func (l *Logger) print(ctx context.Context, level int, stack string, values ...any) {
|
||||
// Lazy initialize for rotation feature.
|
||||
// It uses atomic reading operation to enhance the performance checking.
|
||||
// It here uses CAP for performance and concurrent safety.
|
||||
@ -116,6 +117,7 @@ func (l *Logger) print(ctx context.Context, level int, stack string, values ...i
|
||||
Color: defaultLevelColor[level],
|
||||
Level: level,
|
||||
Stack: stack,
|
||||
Values: values,
|
||||
}
|
||||
)
|
||||
|
||||
@ -125,7 +127,7 @@ func (l *Logger) print(ctx context.Context, level int, stack string, values ...i
|
||||
} else if defaultHandler != nil {
|
||||
input.handlers = []Handler{defaultHandler}
|
||||
}
|
||||
input.handlers = append(input.handlers, defaultPrintHandler)
|
||||
input.handlers = append(input.handlers, doFinalPrint)
|
||||
|
||||
// Time.
|
||||
timeFormat := ""
|
||||
@ -204,24 +206,6 @@ func (l *Logger) print(ctx context.Context, level int, stack string, values ...i
|
||||
}
|
||||
}
|
||||
}
|
||||
var tempStr string
|
||||
for _, v := range values {
|
||||
tempStr = gconv.String(v)
|
||||
if len(input.Content) > 0 {
|
||||
if input.Content[len(input.Content)-1] == '\n' {
|
||||
// Remove one blank line(\n\n).
|
||||
if len(tempStr) > 0 && tempStr[0] == '\n' {
|
||||
input.Content += tempStr[1:]
|
||||
} else {
|
||||
input.Content += tempStr
|
||||
}
|
||||
} else {
|
||||
input.Content += " " + tempStr
|
||||
}
|
||||
} else {
|
||||
input.Content = tempStr
|
||||
}
|
||||
}
|
||||
if l.config.Flags&F_ASYNC > 0 {
|
||||
input.IsAsync = true
|
||||
err := asyncPool.Add(ctx, func(ctx context.Context) {
|
||||
@ -264,9 +248,7 @@ func (l *Logger) doDefaultPrint(ctx context.Context, input *HandlerInput) *bytes
|
||||
// printToWriter writes buffer to writer.
|
||||
func (l *Logger) printToWriter(ctx context.Context, input *HandlerInput) *bytes.Buffer {
|
||||
if l.config.Writer != nil {
|
||||
var (
|
||||
buffer = input.getRealBuffer(l.config.WriterColorEnable)
|
||||
)
|
||||
var buffer = input.getRealBuffer(l.config.WriterColorEnable)
|
||||
if _, err := l.config.Writer.Write(buffer.Bytes()); err != nil {
|
||||
intlog.Errorf(ctx, `%+v`, err)
|
||||
}
|
||||
@ -282,7 +264,7 @@ func (l *Logger) printToStdout(ctx context.Context, input *HandlerInput) *bytes.
|
||||
err error
|
||||
buffer = input.getRealBuffer(!l.config.StdoutColorDisabled)
|
||||
)
|
||||
// This will lose color in Windows os system.
|
||||
// This will lose color in Windows os system. DO NOT USE.
|
||||
// if _, err := os.Stdout.Write(input.getRealBuffer(true).Bytes()); err != nil {
|
||||
|
||||
// This will print color in Windows os system.
|
||||
@ -371,23 +353,23 @@ func (l *Logger) getOpenedFilePointer(ctx context.Context, path string) *gfpool.
|
||||
}
|
||||
|
||||
// printStd prints content `s` without stack.
|
||||
func (l *Logger) printStd(ctx context.Context, level int, value ...interface{}) {
|
||||
l.print(ctx, level, "", value...)
|
||||
func (l *Logger) printStd(ctx context.Context, level int, values ...interface{}) {
|
||||
l.print(ctx, level, "", values...)
|
||||
}
|
||||
|
||||
// printStd prints content `s` with stack check.
|
||||
func (l *Logger) printErr(ctx context.Context, level int, value ...interface{}) {
|
||||
func (l *Logger) printErr(ctx context.Context, level int, values ...interface{}) {
|
||||
var stack string
|
||||
if l.config.StStatus == 1 {
|
||||
stack = l.GetStack()
|
||||
}
|
||||
// In matter of sequence, do not use stderr here, but use the same stdout.
|
||||
l.print(ctx, level, stack, value...)
|
||||
l.print(ctx, level, stack, values...)
|
||||
}
|
||||
|
||||
// format formats `values` using fmt.Sprintf.
|
||||
func (l *Logger) format(format string, value ...interface{}) string {
|
||||
return fmt.Sprintf(format, value...)
|
||||
func (l *Logger) format(format string, values ...interface{}) string {
|
||||
return fmt.Sprintf(format, values...)
|
||||
}
|
||||
|
||||
// PrintStack prints the caller stack,
|
||||
@ -411,5 +393,9 @@ func (l *Logger) GetStack(skip ...int) string {
|
||||
if l.config.StFilter != "" {
|
||||
filters = append(filters, l.config.StFilter)
|
||||
}
|
||||
// Whether filter framework error stacks.
|
||||
if errors.IsStackModeBrief() {
|
||||
filters = append(filters, consts.StackFilterKeyForGoFrame)
|
||||
}
|
||||
return gdebug.StackWithFilters(filters, stackSkip)
|
||||
}
|
||||
|
||||
@ -10,6 +10,8 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// Handler is function handler for custom logging content outputs.
|
||||
@ -31,6 +33,7 @@ type HandlerInput struct {
|
||||
TraceId string // Trace id, only available if OpenTelemetry is enabled.
|
||||
Prefix string // Custom prefix string for logging content.
|
||||
Content string // Content is the main logging content without error stack string produced by logger.
|
||||
Values []any // The passed un-formatted values array to logger.
|
||||
Stack string // Stack string produced by logger, only available if Config.StStatus configured.
|
||||
IsAsync bool // IsAsync marks it is in asynchronous logging.
|
||||
}
|
||||
@ -43,9 +46,9 @@ type internalHandlerInfo struct {
|
||||
// defaultHandler is the default handler for package.
|
||||
var defaultHandler Handler
|
||||
|
||||
// defaultPrintHandler is a handler for logging content printing.
|
||||
// doFinalPrint is a handler for logging content printing.
|
||||
// This handler outputs logging content to file/stdout/write if any of them configured.
|
||||
func defaultPrintHandler(ctx context.Context, in *HandlerInput) {
|
||||
func doFinalPrint(ctx context.Context, in *HandlerInput) {
|
||||
buffer := in.Logger.doDefaultPrint(ctx, in)
|
||||
if in.Buffer.Len() == 0 {
|
||||
in.Buffer = buffer
|
||||
@ -113,12 +116,35 @@ func (in *HandlerInput) getDefaultBuffer(withColor bool) *bytes.Buffer {
|
||||
in.addStringToBuffer(buffer, in.CallerPath)
|
||||
}
|
||||
}
|
||||
|
||||
if in.Content != "" {
|
||||
if in.Stack != "" {
|
||||
in.addStringToBuffer(buffer, in.Content+"\nStack:\n"+in.Stack)
|
||||
} else {
|
||||
in.addStringToBuffer(buffer, in.Content)
|
||||
in.addStringToBuffer(buffer, in.Content)
|
||||
}
|
||||
|
||||
// Convert values string content.
|
||||
var valueContent string
|
||||
for _, v := range in.Values {
|
||||
valueContent = gconv.String(v)
|
||||
if len(valueContent) == 0 {
|
||||
continue
|
||||
}
|
||||
if buffer.Len() > 0 {
|
||||
if buffer.Bytes()[buffer.Len()-1] == '\n' {
|
||||
// Remove one blank line(\n\n).
|
||||
if valueContent[0] == '\n' {
|
||||
valueContent = valueContent[1:]
|
||||
}
|
||||
buffer.WriteString(valueContent)
|
||||
} else {
|
||||
buffer.WriteString(" " + valueContent)
|
||||
}
|
||||
} else {
|
||||
buffer.WriteString(valueContent)
|
||||
}
|
||||
}
|
||||
|
||||
if in.Stack != "" {
|
||||
in.addStringToBuffer(buffer, "\nStack:\n"+in.Stack)
|
||||
}
|
||||
// avoid a single space at the end of a line.
|
||||
buffer.WriteString("\n")
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// HandlerOutputJson is the structure outputting logging content as single json.
|
||||
@ -18,8 +19,8 @@ type HandlerOutputJson struct {
|
||||
TraceId string `json:",omitempty"` // Trace id, only available if tracing is enabled.
|
||||
CtxStr string `json:",omitempty"` // The retrieved context value string from context, only available if Config.CtxKeys configured.
|
||||
Level string `json:""` // Formatted level string, like "DEBU", "ERRO", etc. Eg: ERRO
|
||||
CallerFunc string `json:",omitempty"` // The source function name that calls logging, only available if F_CALLER_FN set.
|
||||
CallerPath string `json:",omitempty"` // The source file path and its line number that calls logging, only available if F_FILE_SHORT or F_FILE_LONG set.
|
||||
CallerFunc string `json:",omitempty"` // The source function name that calls logging, only available if F_CALLER_FN set.
|
||||
Prefix string `json:",omitempty"` // Custom prefix string for logging content.
|
||||
Content string `json:""` // Content is the main logging content, containing error stack string produced by logger.
|
||||
Stack string `json:",omitempty"` // Stack string produced by logger, only available if Config.StStatus configured.
|
||||
@ -38,6 +39,28 @@ func HandlerJson(ctx context.Context, in *HandlerInput) {
|
||||
Content: in.Content,
|
||||
Stack: in.Stack,
|
||||
}
|
||||
// Convert values string content.
|
||||
var valueContent string
|
||||
for _, v := range in.Values {
|
||||
valueContent = gconv.String(v)
|
||||
if len(valueContent) == 0 {
|
||||
continue
|
||||
}
|
||||
if len(output.Content) > 0 {
|
||||
if output.Content[len(output.Content)-1] == '\n' {
|
||||
// Remove one blank line(\n\n).
|
||||
if valueContent[0] == '\n' {
|
||||
valueContent = valueContent[1:]
|
||||
}
|
||||
output.Content += valueContent
|
||||
} else {
|
||||
output.Content += " " + valueContent
|
||||
}
|
||||
} else {
|
||||
output.Content += valueContent
|
||||
}
|
||||
}
|
||||
// Output json content.
|
||||
jsonBytes, err := json.Marshal(output)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
245
os/glog/glog_logger_handler_structure.go
Normal file
245
os/glog/glog_logger_handler_structure.go
Normal file
@ -0,0 +1,245 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package glog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"strconv"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type structuredBuffer struct {
|
||||
in *HandlerInput
|
||||
buffer *bytes.Buffer
|
||||
}
|
||||
|
||||
const (
|
||||
structureKeyTime = "Time"
|
||||
structureKeyLevel = "Level"
|
||||
structureKeyPrefix = "Prefix"
|
||||
structureKeyContent = "Content"
|
||||
structureKeyTraceId = "TraceId"
|
||||
structureKeyCallerFunc = "CallerFunc"
|
||||
structureKeyCallerPath = "CallerPath"
|
||||
structureKeyCtxStr = "CtxStr"
|
||||
structureKeyStack = "Stack"
|
||||
)
|
||||
|
||||
// Copied from encoding/json/tables.go.
|
||||
//
|
||||
// safeSet holds the value true if the ASCII character with the given array
|
||||
// position can be represented inside a JSON string without any further
|
||||
// escaping.
|
||||
//
|
||||
// All values are true except for the ASCII control characters (0-31), the
|
||||
// double quote ("), and the backslash character ("\").
|
||||
var safeSet = [utf8.RuneSelf]bool{
|
||||
' ': true,
|
||||
'!': true,
|
||||
'"': false,
|
||||
'#': true,
|
||||
'$': true,
|
||||
'%': true,
|
||||
'&': true,
|
||||
'\'': true,
|
||||
'(': true,
|
||||
')': true,
|
||||
'*': true,
|
||||
'+': true,
|
||||
',': true,
|
||||
'-': true,
|
||||
'.': true,
|
||||
'/': true,
|
||||
'0': true,
|
||||
'1': true,
|
||||
'2': true,
|
||||
'3': true,
|
||||
'4': true,
|
||||
'5': true,
|
||||
'6': true,
|
||||
'7': true,
|
||||
'8': true,
|
||||
'9': true,
|
||||
':': true,
|
||||
';': true,
|
||||
'<': true,
|
||||
'=': true,
|
||||
'>': true,
|
||||
'?': true,
|
||||
'@': true,
|
||||
'A': true,
|
||||
'B': true,
|
||||
'C': true,
|
||||
'D': true,
|
||||
'E': true,
|
||||
'F': true,
|
||||
'G': true,
|
||||
'H': true,
|
||||
'I': true,
|
||||
'J': true,
|
||||
'K': true,
|
||||
'L': true,
|
||||
'M': true,
|
||||
'N': true,
|
||||
'O': true,
|
||||
'P': true,
|
||||
'Q': true,
|
||||
'R': true,
|
||||
'S': true,
|
||||
'T': true,
|
||||
'U': true,
|
||||
'V': true,
|
||||
'W': true,
|
||||
'X': true,
|
||||
'Y': true,
|
||||
'Z': true,
|
||||
'[': true,
|
||||
'\\': false,
|
||||
']': true,
|
||||
'^': true,
|
||||
'_': true,
|
||||
'`': true,
|
||||
'a': true,
|
||||
'b': true,
|
||||
'c': true,
|
||||
'd': true,
|
||||
'e': true,
|
||||
'f': true,
|
||||
'g': true,
|
||||
'h': true,
|
||||
'i': true,
|
||||
'j': true,
|
||||
'k': true,
|
||||
'l': true,
|
||||
'm': true,
|
||||
'n': true,
|
||||
'o': true,
|
||||
'p': true,
|
||||
'q': true,
|
||||
'r': true,
|
||||
's': true,
|
||||
't': true,
|
||||
'u': true,
|
||||
'v': true,
|
||||
'w': true,
|
||||
'x': true,
|
||||
'y': true,
|
||||
'z': true,
|
||||
'{': true,
|
||||
'|': true,
|
||||
'}': true,
|
||||
'~': true,
|
||||
'\u007f': true,
|
||||
}
|
||||
|
||||
// HandlerStructure is a handler for output logging content as a structured string.
|
||||
func HandlerStructure(ctx context.Context, in *HandlerInput) {
|
||||
s := newStructuredBuffer(in)
|
||||
in.Buffer.Write(s.Bytes())
|
||||
in.Buffer.Write([]byte("\n"))
|
||||
in.Next(ctx)
|
||||
}
|
||||
|
||||
func newStructuredBuffer(in *HandlerInput) *structuredBuffer {
|
||||
return &structuredBuffer{
|
||||
in: in,
|
||||
buffer: bytes.NewBuffer(nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (buf *structuredBuffer) Bytes() []byte {
|
||||
buf.addValue(structureKeyTime, buf.in.TimeFormat)
|
||||
if buf.in.TraceId != "" {
|
||||
buf.addValue(structureKeyTraceId, buf.in.TraceId)
|
||||
}
|
||||
if buf.in.CtxStr != "" {
|
||||
buf.addValue(structureKeyCtxStr, buf.in.CtxStr)
|
||||
}
|
||||
if buf.in.LevelFormat != "" {
|
||||
buf.addValue(structureKeyLevel, buf.in.LevelFormat)
|
||||
}
|
||||
if buf.in.CallerPath != "" {
|
||||
buf.addValue(structureKeyCallerPath, buf.in.CallerPath)
|
||||
}
|
||||
if buf.in.CallerFunc != "" {
|
||||
buf.addValue(structureKeyCallerFunc, buf.in.CallerFunc)
|
||||
}
|
||||
if buf.in.Prefix != "" {
|
||||
buf.addValue(structureKeyPrefix, buf.in.Prefix)
|
||||
}
|
||||
// If the values cannot be the pair, move the first one to content.
|
||||
values := buf.in.Values
|
||||
if len(values)%2 != 0 {
|
||||
if buf.in.Content != "" {
|
||||
buf.in.Content += " "
|
||||
}
|
||||
buf.in.Content += gconv.String(values[0])
|
||||
values = values[1:]
|
||||
}
|
||||
if buf.in.Content != "" {
|
||||
buf.addValue(structureKeyContent, buf.in.Content)
|
||||
}
|
||||
// Values pairs.
|
||||
for i := 0; i < len(values); i += 2 {
|
||||
buf.addValue(values[i], values[i+1])
|
||||
}
|
||||
if buf.in.Stack != "" {
|
||||
buf.addValue(structureKeyStack, buf.in.Stack)
|
||||
}
|
||||
contentBytes := buf.buffer.Bytes()
|
||||
buf.buffer.Reset()
|
||||
contentBytes = bytes.ReplaceAll(contentBytes, []byte{'\n'}, []byte{' '})
|
||||
return contentBytes
|
||||
}
|
||||
|
||||
func (buf *structuredBuffer) addValue(k, v any) {
|
||||
var (
|
||||
ks = gconv.String(k)
|
||||
vs = gconv.String(v)
|
||||
)
|
||||
if buf.buffer.Len() > 0 {
|
||||
buf.buffer.WriteByte(' ')
|
||||
}
|
||||
buf.appendString(ks)
|
||||
buf.buffer.WriteByte('=')
|
||||
buf.appendString(vs)
|
||||
}
|
||||
|
||||
func (buf *structuredBuffer) appendString(s string) {
|
||||
if buf.needsQuoting(s) {
|
||||
s = strconv.Quote(s)
|
||||
}
|
||||
buf.buffer.WriteString(s)
|
||||
}
|
||||
|
||||
func (buf *structuredBuffer) needsQuoting(s string) bool {
|
||||
if len(s) == 0 {
|
||||
return true
|
||||
}
|
||||
for i := 0; i < len(s); {
|
||||
b := s[i]
|
||||
if b < utf8.RuneSelf {
|
||||
// Quote anything except a backslash that would need quoting in a
|
||||
// JSON string, as well as space and '='
|
||||
if b != '\\' && (b == ' ' || b == '=' || !safeSet[b]) {
|
||||
return true
|
||||
}
|
||||
i++
|
||||
continue
|
||||
}
|
||||
r, size := utf8.DecodeRuneInString(s[i:])
|
||||
if r == utf8.RuneError || unicode.IsSpace(r) || !unicode.IsPrint(r) {
|
||||
return true
|
||||
}
|
||||
i += size
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -89,6 +89,25 @@ func TestLogger_SetHandlers_HandlerJson(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestLogger_SetHandlers_HandlerStructure(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
w := bytes.NewBuffer(nil)
|
||||
l := glog.NewWithWriter(w)
|
||||
l.SetHandlers(glog.HandlerStructure)
|
||||
l.SetCtxKeys("Trace-Id", "Span-Id", "Test")
|
||||
ctx := context.WithValue(context.Background(), "Trace-Id", "1234567890")
|
||||
ctx = context.WithValue(ctx, "Span-Id", "abcdefg")
|
||||
|
||||
l.Debug(ctx, "debug", "uid", 1000)
|
||||
l.Info(ctx, "info", "' '", `"\n`)
|
||||
|
||||
t.Assert(gstr.Count(w.String(), "uid=1000"), 1)
|
||||
t.Assert(gstr.Count(w.String(), "Content=debug"), 1)
|
||||
t.Assert(gstr.Count(w.String(), `"' '"="\"\\n"`), 1)
|
||||
t.Assert(gstr.Count(w.String(), `CtxStr="1234567890, abcdefg"`), 2)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_SetDefaultHandler(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
oldHandler := glog.GetDefaultHandler()
|
||||
|
||||
16
os/gmutex/gmutex.go
Normal file
16
os/gmutex/gmutex.go
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gmutex inherits and extends sync.Mutex and sync.RWMutex with more futures.
|
||||
//
|
||||
// Note that, it is refracted using stdlib mutex of package sync from GoFrame version v2.5.2.
|
||||
package gmutex
|
||||
|
||||
// New creates and returns a new mutex.
|
||||
// Deprecated: use Mutex or RWMutex instead.
|
||||
func New() *RWMutex {
|
||||
return &RWMutex{}
|
||||
}
|
||||
38
os/gmutex/gmutex_mutex.go
Normal file
38
os/gmutex/gmutex_mutex.go
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmutex
|
||||
|
||||
import "sync"
|
||||
|
||||
// Mutex is a high level Mutex, which implements more rich features for mutex.
|
||||
type Mutex struct {
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// LockFunc locks the mutex for writing with given callback function `f`.
|
||||
// If there's a write/reading lock the mutex, it will block until the lock is released.
|
||||
//
|
||||
// It releases the lock after `f` is executed.
|
||||
func (m *Mutex) LockFunc(f func()) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
f()
|
||||
}
|
||||
|
||||
// TryLockFunc tries locking the mutex for writing with given callback function `f`.
|
||||
// it returns true immediately if success, or if there's a write/reading lock on the mutex,
|
||||
// it returns false immediately.
|
||||
//
|
||||
// It releases the lock after `f` is executed.
|
||||
func (m *Mutex) TryLockFunc(f func()) (result bool) {
|
||||
if m.TryLock() {
|
||||
result = true
|
||||
defer m.Unlock()
|
||||
f()
|
||||
}
|
||||
return
|
||||
}
|
||||
62
os/gmutex/gmutex_rwmutex.go
Normal file
62
os/gmutex/gmutex_rwmutex.go
Normal file
@ -0,0 +1,62 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmutex
|
||||
|
||||
import "sync"
|
||||
|
||||
// RWMutex is a high level RWMutex, which implements more rich features for mutex.
|
||||
type RWMutex struct {
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// LockFunc locks the mutex for writing with given callback function `f`.
|
||||
// If there's a write/reading lock the mutex, it will block until the lock is released.
|
||||
//
|
||||
// It releases the lock after `f` is executed.
|
||||
func (m *RWMutex) LockFunc(f func()) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
f()
|
||||
}
|
||||
|
||||
// RLockFunc locks the mutex for reading with given callback function `f`.
|
||||
// If there's a writing lock the mutex, it will block until the lock is released.
|
||||
//
|
||||
// It releases the lock after `f` is executed.
|
||||
func (m *RWMutex) RLockFunc(f func()) {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
f()
|
||||
}
|
||||
|
||||
// TryLockFunc tries locking the mutex for writing with given callback function `f`.
|
||||
// it returns true immediately if success, or if there's a write/reading lock on the mutex,
|
||||
// it returns false immediately.
|
||||
//
|
||||
// It releases the lock after `f` is executed.
|
||||
func (m *RWMutex) TryLockFunc(f func()) (result bool) {
|
||||
if m.TryLock() {
|
||||
result = true
|
||||
defer m.Unlock()
|
||||
f()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TryRLockFunc tries locking the mutex for reading with given callback function `f`.
|
||||
// It returns true immediately if success, or if there's a writing lock on the mutex,
|
||||
// it returns false immediately.
|
||||
//
|
||||
// It releases the lock after `f` is executed.
|
||||
func (m *RWMutex) TryRLockFunc(f func()) (result bool) {
|
||||
if m.TryRLock() {
|
||||
result = true
|
||||
defer m.RUnlock()
|
||||
f()
|
||||
}
|
||||
return
|
||||
}
|
||||
85
os/gmutex/gmutex_z_bench_test.go
Normal file
85
os/gmutex/gmutex_z_bench_test.go
Normal file
@ -0,0 +1,85 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmutex_test
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gmutex"
|
||||
)
|
||||
|
||||
var (
|
||||
mu = sync.Mutex{}
|
||||
rwmu = sync.RWMutex{}
|
||||
gmu = gmutex.New()
|
||||
)
|
||||
|
||||
func Benchmark_Mutex_LockUnlock(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
mu.Lock()
|
||||
mu.Unlock()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Benchmark_RWMutex_LockUnlock(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
rwmu.Lock()
|
||||
rwmu.Unlock()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Benchmark_RWMutex_RLockRUnlock(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
rwmu.RLock()
|
||||
rwmu.RUnlock()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Benchmark_GMutex_LockUnlock(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
gmu.Lock()
|
||||
gmu.Unlock()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Benchmark_GMutex_TryLock(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if gmu.TryLock() {
|
||||
gmu.Unlock()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Benchmark_GMutex_RLockRUnlock(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
gmu.RLock()
|
||||
gmu.RUnlock()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Benchmark_GMutex_TryRLock(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if gmu.TryRLock() {
|
||||
gmu.RUnlock()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user