Compare commits

..

22 Commits

Author SHA1 Message Date
5bc9acdab3 version v2.5.3 (#2945) 2023-09-11 10:19:51 +08:00
ab1970e7d6 add new function g.Go (#2943) 2023-09-11 10:18:44 +08:00
1582714325 improve signal listening for package grpcx/ghttp/gproc (#2942) 2023-09-11 10:15:08 +08:00
7391a4d45a improve trace span generating for package gctx and http tracing content for package ghttp (#2937) 2023-09-11 10:14:00 +08:00
7e16d9b63e fix codes due to static codes analysis (#2935) 2023-09-07 20:22:20 +08:00
d49dccb147 test: add unit tests regarding issue 2901 (#2930) 2023-09-05 19:30:54 +08:00
6cddfdb313 improve join feature for package gdb (#2929) 2023-09-05 19:29:28 +08:00
912316d765 add cluster mode and tls configuration support for package gredis (#2936) 2023-09-05 19:23:17 +08:00
b9e2b05f04 change interface ConvertDataForRecord to ConvertValueForField for package gdb (#2916) 2023-09-04 21:23:54 +08:00
887803e495 add structure logging handler for package glog (#2919) 2023-09-04 21:23:46 +08:00
eb11061bd2 fix: gjson encode to string expect inconformity(issue 2520) (#2928) 2023-09-04 21:19:22 +08:00
000c7a92ed ORM add function: LeftJoinOnFields,RightJoinOnFields,InnerJoinOnFields (#2921) 2023-09-04 20:33:53 +08:00
097b26f318 fix: multiple interfaces cause the original type to be inaccessible (#2915) 2023-09-04 20:11:14 +08:00
3da5e5e865 fix: gutil.IsSlice judgment logic error (#2910) 2023-09-04 20:09:55 +08:00
e60262fec9 docs: fix code comment err in Model Join case (#2884) 2023-09-04 20:05:52 +08:00
74bf1b4bc3 fix(cmd): Fix gf build examples (#2917) 2023-08-31 15:32:49 +08:00
3f69e0db36 improve error stack configuration for package gerror, add framework error stack filter for package glog (#2918) 2023-08-31 15:31:55 +08:00
7d4c59ac5a fix: OpenAPI cannot correctly identify the file type under the canoni… (#2898) 2023-08-28 21:52:22 +08:00
3841f05e02 add AdapterContent implements for gcfg.Adapter (#2892) 2023-08-28 21:49:30 +08:00
bcd409ab1c fix typo "Upadte" -> "Update" (#2906) 2023-08-28 21:48:56 +08:00
4dd43aa018 improve packed project template for command init (#2885) 2023-08-23 19:28:09 +08:00
aed695313a rewrite gmutex with sync.RWMutex (#2883) 2023-08-23 19:27:57 +08:00
120 changed files with 2394 additions and 1273 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@
package ghttp
// registerSignalHandler does nothing on a window platform.
// registerSignalHandler does nothing on window platform.
func handleProcessSignal() {
}

View File

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

View File

@ -159,6 +159,8 @@ func (s *Server) doSetHandler(
switch item.Type {
case HandlerTypeHandler, HandlerTypeObject:
duplicatedHandler = items[i]
}
if duplicatedHandler != nil {
break
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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
}

View File

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

View File

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

View File

@ -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, `\/`)

View 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,
},
})
})
}

View File

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

View File

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

View File

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

View 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
}

View File

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

View 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
}

View 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