Compare commits

...

25 Commits

Author SHA1 Message Date
79451e4624 fix(net/ghttp&gclient,contrib/rpc/grpcx): remove request and response contents in opentelemetry tracing attributes (#3810) 2024-09-26 09:50:07 +08:00
a716c6bfab feat: new version v2.7.4 (#3809) 2024-09-25 19:53:23 +08:00
5aa321dbde fix(util/gconv): unstable converting when there is an external attribute with the same name as the internal structure (#3799) 2024-09-25 19:22:15 +08:00
3f2b1cb329 feat(database/gdb): add year field type support for ORM operations (#3805) 2024-09-25 16:33:52 +08:00
ab2c3b02ac fix(cmd/gf): table and field names converted to its lower case before CamelCase converting in command gen dao (#3801) 2024-09-25 10:59:00 +08:00
76783fd72b fix(net/gclient): panic when containing @file: parameter value in json post request (#3775) 2024-09-25 10:37:32 +08:00
fsl
a1ce97ec9b feat(contrib/registry/etcd/): add DialTimeout and AutoSyncInterval option (#3698) 2024-09-24 17:01:10 +08:00
c13004e230 fix(database/gdb): support OrderRandom feature in different databases (#3794) 2024-09-24 11:58:34 +08:00
9af8393758 fix(net/goai): change default value of RequestBody.Required from true to false, add required tag support for RequestBody (#3796) 2024-09-24 11:51:53 +08:00
e15b543a5b fix(util/gvalid): retrive empty slice parameter in custom validation rule function failed (#3795) 2024-09-23 19:59:48 +08:00
8a1c97f518 fix(util/gconv): cached field indexes append issue caused incorrect field converting (#3790) 2024-09-23 19:05:32 +08:00
d8e3e9d713 fix(utils/utils_str): recognize '+' as a valid numeric sign (#3778) 2024-09-23 16:45:55 +08:00
777c2e7117 ci: fix mssql docker service failed in ci (#3792) 2024-09-23 11:50:48 +08:00
c4327f62e7 fix(net/ghttp): server shutdown not graceful using admin api /debug/admin/shutdown (#3777) 2024-09-19 14:10:16 +08:00
fd33dcb97b ci: add go version 1.23 support (#3733) 2024-09-19 10:38:20 +08:00
d8b06d056e fix(os/gcache): a little memory leak for removed timestamp key (#3779) 2024-09-14 11:05:47 +08:00
e186eab1a5 fix(debug/gdebug): incorrect package name handling in function CallerPackage (#3771) 2024-09-13 16:51:39 +08:00
6a99931798 fix(database/gdb): #3755 error parsing database link without port number (#3772) 2024-09-13 16:50:59 +08:00
4ee5bf5c45 fix(database/gdb): #3754 FieldsEx feature conflicts with soft time feature in soft time fields updating (#3773) 2024-09-13 16:50:38 +08:00
e4669387b5 fix(encoding/gxml): XML special character encoding error (#3740) 2024-09-13 10:37:15 +08:00
0e471eab38 fix(util/gconv): #3764 fix bool converting issue (#3765) 2024-09-12 21:59:38 +08:00
3d63ebfe81 fix(net/ghttp): skip common response body in common response handler for streaming content types (#3762) 2024-09-12 17:50:43 +08:00
9b318bb57f perf(database/gdb): performance improvement for struct scanning when with feature disabled (#3677) 2024-09-12 15:38:18 +08:00
6b3fb607cf fix(net/goai): fix openapi miss required tag of BizRequest when set CommonRequest (#3724) 2024-09-10 18:00:21 +08:00
bb9a3b83eb feat(database/gdb): add time field type for value converting for/from field (#3712) 2024-09-10 17:51:22 +08:00
134 changed files with 2528 additions and 597 deletions

View File

@ -7,23 +7,24 @@ for file in `find . -name go.mod`; do
dirpath=$(dirname $file)
echo $dirpath
# ignore mssql tests as its docker service failed
# TODO remove this ignoring codes after the mssql docker service OK
if [ "mssql" = $(basename $dirpath) ]; then
continue 1
fi
if [[ $file =~ "/testdata/" ]]; then
echo "ignore testdata path $file"
continue 1
fi
# package kuhecm needs golang >= v1.19
# package kuhecm was moved to sub ci procedure.
if [ "kubecm" = $(basename $dirpath) ]; then
continue 1
if ! go version|grep -qE "go1.19|go1.[2-9][0-9]"; then
echo "ignore kubecm as go version: $(go version)"
continue 1
fi
fi
# package consul needs golang >= v1.19
if [ "consul" = $(basename $dirpath) ]; then
continue 1
if ! go version|grep -qE "go1.19|go1.[2-9][0-9]"; then
echo "ignore consul as go version: $(go version)"
continue 1

View File

@ -58,6 +58,11 @@ jobs:
- 6379:6379
# MySQL backend server.
# docker run -d --name mysql \
# -p 3306:3306 \
# -e MYSQL_DATABASE=test \
# -e MYSQL_ROOT_PASSWORD=12345678 \
# loads/mysql:5.7
mysql:
image: loads/mysql:5.7
env:
@ -108,22 +113,24 @@ jobs:
# -e MSSQL_USER=root \
# -e MSSQL_PASSWORD=LoremIpsum86 \
# loads/mssqldocker:14.0.3391.2
mssql:
image: loads/mssqldocker:14.0.3391.2
env:
ACCEPT_EULA: Y
SA_PASSWORD: LoremIpsum86
MSSQL_DB: test
MSSQL_USER: root
MSSQL_PASSWORD: LoremIpsum86
ports:
- 1433:1433
options: >-
--health-cmd="/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P LoremIpsum86 -l 30 -Q \"SELECT 1\" || exit 1"
--health-start-period 10s
--health-interval 10s
--health-timeout 5s
--health-retries 10
# TODO mssql docker failed, will be enabled later after it is OK in github action.
# mssql:
# image: loads/mssqldocker:14.0.3391.2
# env:
# ACCEPT_EULA: Y
# SA_PASSWORD: LoremIpsum86
# MSSQL_DB: test
# MSSQL_USER: root
# MSSQL_PASSWORD: LoremIpsum86
# ports:
# - 1433:1433
# options: >-
# --health-cmd="/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P LoremIpsum86 -l 30 -Q \"SELECT 1\" || exit 1"
# --health-start-period 10s
# --health-interval 10s
# --health-timeout 5s
# --health-retries 10
# ClickHouse backend server.
# docker run -d --name clickhouse \
@ -185,7 +192,7 @@ jobs:
strategy:
matrix:
go-version: [ "1.18", "1.19", "1.20", "1.21", "1.22" ]
go-version: [ "1.18", "1.19", "1.20", "1.21", "1.22", "1.23" ]
goarch: [ "386", "amd64" ]
steps:

View File

@ -37,7 +37,7 @@ jobs:
strategy:
matrix:
go-version: [ "1.18", "1.19", "1.20", "1.22" ]
go-version: [ "1.18", "1.19", "1.20", "1.22", "1.23" ]
goarch: [ "386", "amd64" ]
steps:

View File

@ -36,7 +36,7 @@ jobs:
golangci:
strategy:
matrix:
go-version: [ '1.18','1.19','1.20','1.21.4','1.22' ]
go-version: [ '1.18','1.19','1.20','1.21.4','1.22','1.23' ]
name: golangci-lint
runs-on: ubuntu-latest
steps:
@ -50,5 +50,5 @@ jobs:
uses: golangci/golangci-lint-action@v6
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.58.2
version: v1.60.1
args: --timeout 3m0s

View File

@ -125,7 +125,7 @@ linters-settings:
# Checks the number of lines in a function.
# If lower than 0, disable the check.
# Default: 60
lines: 330
lines: 340
# Checks the number of statements in a function.
# If lower than 0, disable the check.
# Default: 40

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.7.3
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.7.3
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.7.3
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.7.3
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.7.3
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.7.3
github.com/gogf/gf/v2 v2.7.3
github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.7.4
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.7.4
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.7.4
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.7.4
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.7.4
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.7.4
github.com/gogf/gf/v2 v2.7.4
github.com/gogf/selfupdate v0.0.0-20231215043001-5c48c528462f
github.com/olekukonko/tablewriter v0.0.5
golang.org/x/mod v0.17.0

View File

@ -59,7 +59,7 @@ func (c cGF) Index(ctx context.Context, in cGFInput) (out *cGFOutput, err error)
answer := "n"
// No argument or option, do installation checks.
if data, isInstalled := service.Install.IsInstalled(); !isInstalled {
mlog.Print("hi, it seams it's the first time you installing gf cli.")
mlog.Print("hi, it seems it's the first time you installing gf cli.")
answer = gcmd.Scanf("do you want to install gf(%s) binary to your system? [y/n]: ", gf.VERSION)
} else if !data.IsSelf {
mlog.Print("hi, you have installed gf cli.")

View File

@ -691,3 +691,74 @@ func Test_Gen_Dao_Issue3459(t *testing.T) {
}
})
}
// https://github.com/gogf/gf/issues/3749
func Test_Gen_Dao_Issue3749(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
err error
db = testDB
table = "table_user"
sqlContent = fmt.Sprintf(
gtest.DataContent(`issue`, `3749`, `user.tpl.sql`),
table,
)
)
dropTableWithDb(db, table)
array := gstr.SplitAndTrim(sqlContent, ";")
for _, v := range array {
if _, err = db.Exec(ctx, v); err != nil {
t.AssertNil(err)
}
}
defer dropTableWithDb(db, table)
var (
path = gfile.Temp(guid.S())
group = "test"
in = gendao.CGenDaoInput{
Path: path,
Link: link,
Group: group,
}
)
err = gutil.FillStructWithDefault(&in)
t.AssertNil(err)
err = gfile.Mkdir(path)
t.AssertNil(err)
// for go mod import path auto retrieve.
err = gfile.Copy(
gtest.DataPath("gendao", "go.mod.txt"),
gfile.Join(path, "go.mod"),
)
t.AssertNil(err)
_, err = gendao.CGenDao{}.Dao(ctx, in)
t.AssertNil(err)
defer gfile.Remove(path)
// files
files, err := gfile.ScanDir(path, "*.go", true)
t.AssertNil(err)
t.Assert(files, []string{
filepath.FromSlash(path + "/dao/internal/table_user.go"),
filepath.FromSlash(path + "/dao/table_user.go"),
filepath.FromSlash(path + "/model/do/table_user.go"),
filepath.FromSlash(path + "/model/entity/table_user.go"),
})
// content
testPath := gtest.DataPath(`issue`, `3749`)
expectFiles := []string{
filepath.FromSlash(testPath + "/dao/internal/table_user.go"),
filepath.FromSlash(testPath + "/dao/table_user.go"),
filepath.FromSlash(testPath + "/model/do/table_user.go"),
filepath.FromSlash(testPath + "/model/entity/table_user.go"),
}
for i, _ := range files {
t.Assert(gfile.GetContents(files[i]), gfile.GetContents(expectFiles[i]))
}
})
}

View File

@ -208,9 +208,11 @@ type (
NoModelComment bool `name:"noModelComment" short:"m" brief:"{CGenDaoBriefNoModelComment}" orphan:"true"`
Clear bool `name:"clear" short:"a" brief:"{CGenDaoBriefClear}" orphan:"true"`
TypeMapping map[DBFieldTypeName]CustomAttributeType `name:"typeMapping" short:"y" brief:"{CGenDaoBriefTypeMapping}" orphan:"true"`
FieldMapping map[DBTableFieldName]CustomAttributeType `name:"fieldMapping" short:"fm" brief:"{CGenDaoBriefFieldMapping}" orphan:"true"`
genItems *CGenDaoInternalGenItems
TypeMapping map[DBFieldTypeName]CustomAttributeType `name:"typeMapping" short:"y" brief:"{CGenDaoBriefTypeMapping}" orphan:"true"`
FieldMapping map[DBTableFieldName]CustomAttributeType `name:"fieldMapping" short:"fm" brief:"{CGenDaoBriefFieldMapping}" orphan:"true"`
// internal usage purpose.
genItems *CGenDaoInternalGenItems
}
CGenDaoOutput struct{}

View File

@ -58,8 +58,8 @@ func generateDaoSingle(ctx context.Context, in generateDaoSingleInput) {
mlog.Fatalf(`fetching tables fields failed for table "%s": %+v`, in.TableName, err)
}
var (
tableNameCamelCase = gstr.CaseCamel(strings.ToLower(in.NewTableName))
tableNameCamelLowerCase = gstr.CaseCamelLower(strings.ToLower(in.NewTableName))
tableNameCamelCase = formatFieldName(in.NewTableName, FieldNameCaseCamel)
tableNameCamelLowerCase = formatFieldName(in.NewTableName, FieldNameCaseCamelLower)
tableNameSnakeCase = gstr.CaseSnake(in.NewTableName)
importPrefix = in.ImportPrefix
)
@ -179,7 +179,7 @@ func generateColumnNamesForDao(fieldMap map[string]*gdb.TableField, removeFieldP
}
array[index] = []string{
" #" + gstr.CaseCamel(strings.ToLower(newFiledName)) + ":",
" #" + formatFieldName(newFiledName, FieldNameCaseCamel) + ":",
fmt.Sprintf(` #"%s",`, field.Name),
}
}
@ -219,7 +219,7 @@ func generateColumnDefinitionForDao(fieldMap map[string]*gdb.TableField, removeF
newFiledName = gstr.TrimLeftStr(newFiledName, v, 1)
}
array[index] = []string{
" #" + gstr.CaseCamel(strings.ToLower(newFiledName)),
" #" + formatFieldName(newFiledName, FieldNameCaseCamel),
" # " + "string",
" #" + fmt.Sprintf(`// %s`, comment),
}

View File

@ -40,7 +40,7 @@ func generateDo(ctx context.Context, in CGenDaoInternalInput) {
structDefinition, _ = generateStructDefinition(ctx, generateStructDefinitionInput{
CGenDaoInternalInput: in,
TableName: tableName,
StructName: gstr.CaseCamel(strings.ToLower(newTableName)),
StructName: formatFieldName(newTableName, FieldNameCaseCamel),
FieldMap: fieldMap,
IsDo: true,
})
@ -61,7 +61,7 @@ func generateDo(ctx context.Context, in CGenDaoInternalInput) {
ctx,
in,
tableName,
gstr.CaseCamel(strings.ToLower(newTableName)),
formatFieldName(newTableName, FieldNameCaseCamel),
structDefinition,
)
in.genItems.AppendGeneratedFilePath(doFilePath)

View File

@ -36,7 +36,7 @@ func generateEntity(ctx context.Context, in CGenDaoInternalInput) {
structDefinition, appendImports = generateStructDefinition(ctx, generateStructDefinitionInput{
CGenDaoInternalInput: in,
TableName: tableName,
StructName: gstr.CaseCamel(strings.ToLower(newTableName)),
StructName: formatFieldName(newTableName, FieldNameCaseCamel),
FieldMap: fieldMap,
IsDo: false,
})
@ -44,7 +44,7 @@ func generateEntity(ctx context.Context, in CGenDaoInternalInput) {
ctx,
in,
newTableName,
gstr.CaseCamel(strings.ToLower(newTableName)),
formatFieldName(newTableName, FieldNameCaseCamel),
structDefinition,
appendImports,
)

View File

@ -99,7 +99,7 @@ func generateStructFieldDefinition(
}
localTypeNameStr = string(localTypeName)
switch localTypeName {
case gdb.LocalTypeDate, gdb.LocalTypeDatetime:
case gdb.LocalTypeDate, gdb.LocalTypeTime, gdb.LocalTypeDatetime:
if in.StdTime {
localTypeNameStr = "time.Time"
} else {
@ -140,7 +140,7 @@ func generateStructFieldDefinition(
}
attrLines = []string{
" #" + gstr.CaseCamel(strings.ToLower(newFiledName)),
" #" + formatFieldName(newFiledName, FieldNameCaseCamel),
" #" + localTypeNameStr,
}
attrLines = append(attrLines, fmt.Sprintf(` #%sjson:"%s"`, tagKey, jsonTag))
@ -167,6 +167,43 @@ func generateStructFieldDefinition(
return attrLines, appendImport
}
type FieldNameCase string
const (
FieldNameCaseCamel FieldNameCase = "CaseCamel"
FieldNameCaseCamelLower FieldNameCase = "CaseCamelLower"
)
// formatFieldName formats and returns a new field name that is used for golang codes generating.
func formatFieldName(fieldName string, nameCase FieldNameCase) string {
// For normal databases like mysql, pgsql, sqlite,
// field/table names of that are in normal case.
var newFieldName = fieldName
if isAllUpper(fieldName) {
// For special databases like dm, oracle,
// field/table names of that are in upper case.
newFieldName = strings.ToLower(fieldName)
}
switch nameCase {
case FieldNameCaseCamel:
return gstr.CaseCamel(newFieldName)
case FieldNameCaseCamelLower:
return gstr.CaseCamelLower(newFieldName)
default:
return ""
}
}
// isAllUpper checks and returns whether given `fieldName` all letters are upper case.
func isAllUpper(fieldName string) bool {
for _, b := range fieldName {
if b >= 'a' && b <= 'z' {
return false
}
}
return true
}
// formatComment formats the comment string to fit the golang code without any lines.
func formatComment(comment string) string {
comment = gstr.ReplaceByArray(comment, g.SliceStr{

View File

@ -0,0 +1,85 @@
// ==========================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// ==========================================================================
package internal
import (
"context"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
// TableUserDao is the data access object for table table_user.
type TableUserDao struct {
table string // table is the underlying table name of the DAO.
group string // group is the database configuration group name of current DAO.
columns TableUserColumns // columns contains all the column names of Table for convenient usage.
}
// TableUserColumns defines and stores column names for table table_user.
type TableUserColumns struct {
Id string // User ID
ParentId string //
Passport string // User Passport
PassWord string // User Password
Nickname2 string // User Nickname
CreateAt string // Created Time
UpdateAt string // Updated Time
}
// tableUserColumns holds the columns for table table_user.
var tableUserColumns = TableUserColumns{
Id: "Id",
ParentId: "parentId",
Passport: "PASSPORT",
PassWord: "PASS_WORD",
Nickname2: "NICKNAME2",
CreateAt: "create_at",
UpdateAt: "update_at",
}
// NewTableUserDao creates and returns a new DAO object for table data access.
func NewTableUserDao() *TableUserDao {
return &TableUserDao{
group: "test",
table: "table_user",
columns: tableUserColumns,
}
}
// DB retrieves and returns the underlying raw database management object of current DAO.
func (dao *TableUserDao) DB() gdb.DB {
return g.DB(dao.group)
}
// Table returns the table name of current dao.
func (dao *TableUserDao) Table() string {
return dao.table
}
// Columns returns all column names of current dao.
func (dao *TableUserDao) Columns() TableUserColumns {
return dao.columns
}
// Group returns the configuration group name of database of current dao.
func (dao *TableUserDao) Group() string {
return dao.group
}
// Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation.
func (dao *TableUserDao) Ctx(ctx context.Context) *gdb.Model {
return dao.DB().Model(dao.table).Safe().Ctx(ctx)
}
// Transaction wraps the transaction logic using function f.
// It rollbacks the transaction and returns the error from function f if it returns non-nil error.
// It commits the transaction and returns nil if function f returns nil.
//
// Note that, you should not Commit or Rollback the transaction in function f
// as it is automatically handled by this function.
func (dao *TableUserDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
return dao.Ctx(ctx).Transaction(ctx, f)
}

View File

@ -0,0 +1,27 @@
// =================================================================================
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
// =================================================================================
package dao
import (
"for-gendao-test/pkg/dao/internal"
)
// internalTableUserDao is internal type for wrapping internal DAO implements.
type internalTableUserDao = *internal.TableUserDao
// tableUserDao is the data access object for table table_user.
// You can define custom methods on it to extend its functionality as you wish.
type tableUserDao struct {
internalTableUserDao
}
var (
// TableUser is globally public accessible object for table table_user operations.
TableUser = tableUserDao{
internal.NewTableUserDao(),
}
)
// Fill with you ideas below.

View File

@ -0,0 +1,22 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package do
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// TableUser is the golang structure of table table_user for DAO operations like Where/Data.
type TableUser struct {
g.Meta `orm:"table:table_user, do:true"`
Id interface{} // User ID
ParentId interface{} //
Passport interface{} // User Passport
PassWord interface{} // User Password
Nickname2 interface{} // User Nickname
CreateAt *gtime.Time // Created Time
UpdateAt *gtime.Time // Updated Time
}

View File

@ -0,0 +1,20 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package entity
import (
"github.com/gogf/gf/v2/os/gtime"
)
// TableUser is the golang structure for table table_user.
type TableUser struct {
Id uint `json:"id" orm:"Id" ` // User ID
ParentId string `json:"parentId" orm:"parentId" ` //
Passport string `json:"pASSPORT" orm:"PASSPORT" ` // User Passport
PassWord string `json:"pASSWORD" orm:"PASS_WORD" ` // User Password
Nickname2 string `json:"nICKNAME2" orm:"NICKNAME2" ` // User Nickname
CreateAt *gtime.Time `json:"createAt" orm:"create_at" ` // Created Time
UpdateAt *gtime.Time `json:"updateAt" orm:"update_at" ` // Updated Time
}

View File

@ -0,0 +1,10 @@
CREATE TABLE `%s` (
`Id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'User ID',
`parentId` varchar(45) NOT NULL COMMENT '',
`PASSPORT` varchar(45) NOT NULL COMMENT 'User Passport',
`PASS_WORD` varchar(45) NOT NULL COMMENT 'User Password',
`NICKNAME2` varchar(45) NOT NULL COMMENT 'User Nickname',
`create_at` datetime DEFAULT NULL COMMENT 'Created Time',
`update_at` datetime DEFAULT NULL COMMENT 'Updated Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

View File

@ -291,7 +291,7 @@ func TestRemove(t *testing.T) {
})
}
func TestIssue4103(t *testing.T) {
func Test_Issue4103(t *testing.T) {
l1 := New()
l1.PushBack(1)
l1.PushBack(2)
@ -312,7 +312,7 @@ func TestIssue4103(t *testing.T) {
}
}
func TestIssue6349(t *testing.T) {
func Test_Issue6349(t *testing.T) {
l := New()
l.PushBack(1)
l.PushBack(2)

View File

@ -4,7 +4,7 @@ go 1.18
require (
github.com/apolloconfig/agollo/v4 v4.3.1
github.com/gogf/gf/v2 v2.7.3
github.com/gogf/gf/v2 v2.7.4
)
require (

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/config/consul/v2
go 1.18
require (
github.com/gogf/gf/v2 v2.7.3
github.com/gogf/gf/v2 v2.7.4
github.com/hashicorp/consul/api v1.24.0
github.com/hashicorp/go-cleanhttp v0.5.2
)

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/config/kubecm/v2
go 1.18
require (
github.com/gogf/gf/v2 v2.7.3
github.com/gogf/gf/v2 v2.7.4
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.7.3
github.com/gogf/gf/v2 v2.7.4
github.com/nacos-group/nacos-sdk-go/v2 v2.2.5
)

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.7.3
github.com/gogf/gf/v2 v2.7.4
github.com/polarismesh/polaris-go v1.5.5
)

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.7.3
github.com/gogf/gf/v2 v2.7.4
github.com/google/uuid v1.3.0
github.com/shopspring/decimal v1.3.1
)

View File

@ -6,7 +6,7 @@ replace github.com/gogf/gf/v2 => ../../../
require (
gitee.com/chunanyong/dm v1.8.12
github.com/gogf/gf/v2 v2.7.3
github.com/gogf/gf/v2 v2.7.4
)
require (

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/drivers/mssql/v2
go 1.18
require (
github.com/gogf/gf/v2 v2.7.3
github.com/gogf/gf/v2 v2.7.4
github.com/microsoft/go-mssqldb v1.7.1
)

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.7.3
github.com/gogf/gf/v2 v2.7.4
)
require (

View File

@ -104,6 +104,7 @@ func Test_DB_Insert(t *testing.T) {
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "T1",
"create_time": gtime.Now().String(),
"create_date": gtime.Date(),
})
t.AssertNil(err)
@ -114,6 +115,7 @@ func Test_DB_Insert(t *testing.T) {
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "name_2",
"create_time": gtime.Now().String(),
"create_date": gtime.Date(),
})
t.AssertNil(err)
n, _ := result.RowsAffected()
@ -121,19 +123,22 @@ func Test_DB_Insert(t *testing.T) {
// struct
type User struct {
Id int `gconv:"id"`
Passport string `json:"passport"`
Password string `gconv:"password"`
Nickname string `gconv:"nickname"`
CreateTime string `json:"create_time"`
Id int `gconv:"id"`
Passport string `json:"passport"`
Password string `gconv:"password"`
Nickname string `gconv:"nickname"`
CreateTime string `json:"create_time"`
CreateDate *gtime.Time `json:"create_date"`
}
timeStr := gtime.New("2024-10-01 12:01:01").String()
gTime := gtime.New("2024-10-01 12:01:01")
timeStr, dateStr := gTime.String(), "2024-10-01 00:00:00"
result, err = db.Insert(ctx, table, User{
Id: 3,
Passport: "user_3",
Password: "25d55ad283aa400af464c76d713c07ad",
Nickname: "name_3",
CreateTime: timeStr,
CreateDate: gTime,
})
t.AssertNil(err)
n, _ = result.RowsAffected()
@ -147,15 +152,18 @@ func Test_DB_Insert(t *testing.T) {
t.Assert(one["password"].String(), "25d55ad283aa400af464c76d713c07ad")
t.Assert(one["nickname"].String(), "name_3")
t.Assert(one["create_time"].GTime().String(), timeStr)
t.Assert(one["create_date"].GTime().String(), dateStr)
// *struct
timeStr = gtime.New("2024-10-01 12:01:01").String()
gTime = gtime.New("2024-10-01 12:01:01")
timeStr, dateStr = gTime.String(), "2024-10-01 00:00:00"
result, err = db.Insert(ctx, table, &User{
Id: 4,
Passport: "t4",
Password: "25d55ad283aa400af464c76d713c07ad",
Nickname: "name_4",
CreateTime: timeStr,
CreateDate: gTime,
})
t.AssertNil(err)
n, _ = result.RowsAffected()
@ -168,9 +176,11 @@ func Test_DB_Insert(t *testing.T) {
t.Assert(one["password"].String(), "25d55ad283aa400af464c76d713c07ad")
t.Assert(one["nickname"].String(), "name_4")
t.Assert(one["create_time"].GTime().String(), timeStr)
t.Assert(one["create_date"].GTime().String(), dateStr)
// batch with Insert
timeStr = gtime.New("2024-10-01 12:01:01").String()
gTime = gtime.New("2024-10-01 12:01:01")
timeStr, dateStr = gTime.String(), "2024-10-01 00:00:00"
r, err := db.Insert(ctx, table, g.Slice{
g.Map{
"id": 200,
@ -178,6 +188,7 @@ func Test_DB_Insert(t *testing.T) {
"password": "25d55ad283aa400af464c76d71qw07ad",
"nickname": "T200",
"create_time": timeStr,
"create_date": gTime,
},
g.Map{
"id": 300,
@ -185,6 +196,7 @@ func Test_DB_Insert(t *testing.T) {
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "T300",
"create_time": timeStr,
"create_date": gTime,
},
})
t.AssertNil(err)
@ -198,6 +210,7 @@ func Test_DB_Insert(t *testing.T) {
t.Assert(one["password"].String(), "25d55ad283aa400af464c76d71qw07ad")
t.Assert(one["nickname"].String(), "T200")
t.Assert(one["create_time"].GTime().String(), timeStr)
t.Assert(one["create_date"].GTime().String(), dateStr)
})
}
@ -1644,7 +1657,7 @@ func Test_Core_ClearTableFields(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
fields, err := db.TableFields(ctx, table)
t.AssertNil(err)
t.Assert(len(fields), 5)
t.Assert(len(fields), 6)
})
gtest.C(t, func(t *gtest.T) {
err := db.GetCore().ClearTableFields(ctx, table)

View File

@ -129,6 +129,7 @@ func createTableWithDb(db gdb.DB, table ...string) (name string) {
password char(32) NULL,
nickname varchar(45) NULL,
create_time timestamp(6) NULL,
create_date date NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, name,

View File

@ -1063,7 +1063,7 @@ func Test_Issue2552_ClearTableFieldsAll(t *testing.T) {
ctx = context.Background()
sqlArray, err = gdb.CatchSQL(ctx, func(ctx context.Context) error {
one, err := db.Model(table).Ctx(ctx).One()
t.Assert(len(one), 5)
t.Assert(len(one), 6)
return err
})
t.AssertNil(err)
@ -1078,7 +1078,7 @@ func Test_Issue2552_ClearTableFieldsAll(t *testing.T) {
ctx = context.Background()
sqlArray, err = gdb.CatchSQL(ctx, func(ctx context.Context) error {
one, err := db.Model(table).Ctx(ctx).One()
t.Assert(len(one), 4)
t.Assert(len(one), 5)
return err
})
t.AssertNil(err)
@ -1109,7 +1109,7 @@ func Test_Issue2552_ClearTableFields(t *testing.T) {
ctx = context.Background()
sqlArray, err = gdb.CatchSQL(ctx, func(ctx context.Context) error {
one, err := db.Model(table).Ctx(ctx).One()
t.Assert(len(one), 5)
t.Assert(len(one), 6)
return err
})
t.AssertNil(err)
@ -1124,7 +1124,7 @@ func Test_Issue2552_ClearTableFields(t *testing.T) {
ctx = context.Background()
sqlArray, err = gdb.CatchSQL(ctx, func(ctx context.Context) error {
one, err := db.Model(table).Ctx(ctx).One()
t.Assert(len(one), 4)
t.Assert(len(one), 5)
return err
})
t.AssertNil(err)
@ -1216,3 +1216,65 @@ func Test_Issue3649(t *testing.T) {
t.Assert(sql[0], sqlStr)
})
}
// https://github.com/gogf/gf/issues/3754
func Test_Issue3754(t *testing.T) {
table := "issue3754"
array := gstr.SplitAndTrim(gtest.DataContent(`issue3754.sql`), ";")
for _, v := range array {
if _, err := db.Exec(ctx, v); err != nil {
gtest.Error(err)
}
}
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
fieldsEx := []string{"delete_at", "create_at", "update_at"}
// Insert.
dataInsert := g.Map{
"id": 1,
"name": "name_1",
}
r, err := db.Model(table).Data(dataInsert).FieldsEx(fieldsEx).Insert()
t.AssertNil(err)
n, _ := r.RowsAffected()
t.Assert(n, 1)
oneInsert, err := db.Model(table).WherePri(1).One()
t.AssertNil(err)
t.Assert(oneInsert["id"].Int(), 1)
t.Assert(oneInsert["name"].String(), "name_1")
t.Assert(oneInsert["delete_at"].String(), "")
t.Assert(oneInsert["create_at"].String(), "")
t.Assert(oneInsert["update_at"].String(), "")
// Update.
dataUpdate := g.Map{
"name": "name_1000",
}
r, err = db.Model(table).Data(dataUpdate).FieldsEx(fieldsEx).WherePri(1).Update()
t.AssertNil(err)
n, _ = r.RowsAffected()
t.Assert(n, 1)
oneUpdate, err := db.Model(table).WherePri(1).One()
t.AssertNil(err)
t.Assert(oneUpdate["id"].Int(), 1)
t.Assert(oneUpdate["name"].String(), "name_1000")
t.Assert(oneUpdate["delete_at"].String(), "")
t.Assert(oneUpdate["create_at"].String(), "")
t.Assert(oneUpdate["update_at"].String(), "")
// FieldsEx does not affect Delete operation.
r, err = db.Model(table).FieldsEx(fieldsEx).WherePri(1).Delete()
n, _ = r.RowsAffected()
t.Assert(n, 1)
oneDeleteUnscoped, err := db.Model(table).Unscoped().WherePri(1).One()
t.AssertNil(err)
t.Assert(oneDeleteUnscoped["id"].Int(), 1)
t.Assert(oneDeleteUnscoped["name"].String(), "name_1000")
t.AssertNE(oneDeleteUnscoped["delete_at"].String(), "")
t.Assert(oneDeleteUnscoped["create_at"].String(), "")
t.Assert(oneDeleteUnscoped["update_at"].String(), "")
})
}

View File

@ -2335,7 +2335,7 @@ func Test_Model_FieldsEx(t *testing.T) {
r, err := db.Model(table).FieldsEx("create_time, id").Where("id in (?)", g.Slice{1, 2}).Order("id asc").All()
t.AssertNil(err)
t.Assert(len(r), 2)
t.Assert(len(r[0]), 3)
t.Assert(len(r[0]), 4)
t.Assert(r[0]["id"], "")
t.Assert(r[0]["passport"], "user_1")
t.Assert(r[0]["password"], "pass_1")
@ -2982,7 +2982,7 @@ func Test_Model_FieldsEx_AutoMapping(t *testing.T) {
"CreateTime": 1,
}).Where("id", 2).One()
t.AssertNil(err)
t.Assert(len(one), 2)
t.Assert(len(one), 3)
t.Assert(one["id"], 2)
t.Assert(one["nickname"], "name_2")
})
@ -2999,7 +2999,7 @@ func Test_Model_FieldsEx_AutoMapping(t *testing.T) {
CreateTime: 0,
}).Where("id", 2).One()
t.AssertNil(err)
t.Assert(len(one), 2)
t.Assert(len(one), 3)
t.Assert(one["id"], 2)
t.Assert(one["nickname"], "name_2")
})
@ -3157,8 +3157,8 @@ func Test_TimeZoneInsert(t *testing.T) {
gtest.AssertNil(err)
CreateTime := "2020-11-22 12:23:45"
UpdateTime := "2020-11-22 13:23:45"
DeleteTime := "2020-11-22 14:23:45"
UpdateTime := "2020-11-22 13:23:46"
DeleteTime := "2020-11-22 14:23:47"
type User struct {
Id int `json:"id"`
CreatedAt *gtime.Time `json:"created_at"`
@ -3176,13 +3176,14 @@ func Test_TimeZoneInsert(t *testing.T) {
}
gtest.C(t, func(t *gtest.T) {
_, _ = db.Model(tableName).Unscoped().Insert(u)
_, err = db.Model(tableName).Unscoped().Insert(u)
t.AssertNil(err)
userEntity := &User{}
err := db.Model(tableName).Where("id", 1).Unscoped().Scan(&userEntity)
err = db.Model(tableName).Where("id", 1).Unscoped().Scan(&userEntity)
t.AssertNil(err)
t.Assert(userEntity.CreatedAt.String(), "2020-11-22 11:23:45")
t.Assert(userEntity.UpdatedAt.String(), "2020-11-22 12:23:45")
t.Assert(gtime.NewFromTime(userEntity.DeletedAt).String(), "2020-11-22 13:23:45")
t.Assert(userEntity.UpdatedAt.String(), "2020-11-22 12:23:46")
t.Assert(gtime.NewFromTime(userEntity.DeletedAt).String(), "2020-11-22 13:23:47")
})
}
@ -4785,3 +4786,35 @@ func Test_Model_FixGdbJoin(t *testing.T) {
t.Assert(gtest.DataContent(`fix_gdb_join_expect.sql`), sqlSlice[len(sqlSlice)-1])
})
}
func Test_Model_Year_Date_Time_DateTime_Timestamp(t *testing.T) {
table := "date_time_example"
array := gstr.SplitAndTrim(gtest.DataContent(`date_time_example.sql`), ";")
for _, v := range array {
if _, err := db.Exec(ctx, v); err != nil {
gtest.Error(err)
}
}
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
// insert.
var now = gtime.Now()
_, err := db.Model("date_time_example").Insert(g.Map{
"year": now,
"date": now,
"time": now,
"datetime": now,
"timestamp": now,
})
t.AssertNil(err)
// select.
one, err := db.Model("date_time_example").One()
t.AssertNil(err)
t.Assert(one["year"].String(), now.Format("Y"))
t.Assert(one["date"].String(), now.Format("Y-m-d"))
t.Assert(one["time"].String(), now.Format("H:i:s"))
t.AssertLT(one["datetime"].GTime().Sub(now).Seconds(), 5)
t.AssertLT(one["timestamp"].GTime().Sub(now).Seconds(), 5)
})
}

View File

@ -0,0 +1,9 @@
CREATE TABLE `date_time_example` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`year` year DEFAULT NULL COMMENT 'year',
`date` date DEFAULT NULL COMMENT 'Date',
`time` time DEFAULT NULL COMMENT 'time',
`datetime` datetime DEFAULT NULL COMMENT 'datetime',
`timestamp` timestamp NULL DEFAULT NULL COMMENT 'Timestamp',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -0,0 +1,8 @@
CREATE TABLE `issue3754` (
id int(11) NOT NULL,
name varchar(45) DEFAULT NULL,
create_at datetime(0) DEFAULT NULL,
update_at datetime(0) DEFAULT NULL,
delete_at datetime(0) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

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.7.3
github.com/gogf/gf/v2 v2.7.4
github.com/sijms/go-ora/v2 v2.7.10
)

View File

@ -0,0 +1,12 @@
// 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 oracle
// OrderRandomFunction returns the SQL function for random ordering.
func (d *Driver) OrderRandomFunction() string {
return "DBMS_RANDOM.VALUE()"
}

View File

@ -1177,6 +1177,17 @@ func Test_Model_Replace(t *testing.T) {
})
}
func Test_OrderRandom(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).OrderRandom().All()
t.AssertNil(err)
t.Assert(len(result), TableSize)
})
}
/* not support the "AS"
func Test_Model_Raw(t *testing.T) {
table := createInitTable()

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.7.3
github.com/gogf/gf/v2 v2.7.4
github.com/lib/pq v1.10.9
)

View File

@ -0,0 +1,12 @@
// 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 pgsql
// OrderRandomFunction returns the SQL function for random ordering.
func (d *Driver) OrderRandomFunction() string {
return "RANDOM()"
}

View File

@ -587,3 +587,14 @@ func Test_Model_OnDuplicateEx(t *testing.T) {
t.Assert(one["nickname"], "name_1")
})
}
func Test_OrderRandom(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).OrderRandom().All()
t.AssertNil(err)
t.Assert(len(result), TableSize)
})
}

View File

@ -4,7 +4,7 @@ go 1.18
require (
github.com/glebarez/go-sqlite v1.21.2
github.com/gogf/gf/v2 v2.7.3
github.com/gogf/gf/v2 v2.7.4
)
require (

View File

@ -0,0 +1,12 @@
// 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 sqlite
// OrderRandomFunction returns the SQL function for random ordering.
func (d *Driver) OrderRandomFunction() string {
return "RANDOM()"
}

View File

@ -4299,3 +4299,14 @@ func TestResult_Structs1(t *testing.T) {
t.Assert(array[1].Name, "smith")
})
}
func Test_OrderRandom(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).OrderRandom().All()
t.AssertNil(err)
t.Assert(len(result), TableSize)
})
}

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.7.3
github.com/gogf/gf/v2 v2.7.4
github.com/mattn/go-sqlite3 v1.14.17
)

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/metric/otelmetric/v2
go 1.18
require (
github.com/gogf/gf/v2 v2.7.3
github.com/gogf/gf/v2 v2.7.4
github.com/prometheus/client_golang v1.19.0
go.opentelemetry.io/contrib/instrumentation/runtime v0.49.0
go.opentelemetry.io/otel v1.24.0

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.7.3
github.com/gogf/gf/v2 v2.7.4
github.com/redis/go-redis/v9 v9.2.1
go.opentelemetry.io/otel v1.14.0
go.opentelemetry.io/otel/trace v1.14.0

View File

@ -12,6 +12,7 @@ import (
"time"
etcd3 "go.etcd.io/etcd/client/v3"
"google.golang.org/grpc"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
@ -38,11 +39,26 @@ type Registry struct {
type Option struct {
Logger glog.ILogger
KeepaliveTTL time.Duration
// DialTimeout is the timeout for failing to establish a connection.
DialTimeout time.Duration
// AutoSyncInterval is the interval to update endpoints with its latest members.
AutoSyncInterval time.Duration
DialOptions []grpc.DialOption
}
const (
// DefaultKeepAliveTTL is the default keepalive TTL.
DefaultKeepAliveTTL = 10 * time.Second
// DefaultDialTimeout is the timeout for failing to establish a connection.
DefaultDialTimeout = time.Second * 5
// DefaultAutoSyncInterval is the interval to update endpoints with its latest members.
// 0 disables auto-sync. By default auto-sync is disabled.
DefaultAutoSyncInterval = time.Second
)
// New creates and returns a new etcd registry.
@ -80,6 +96,21 @@ func New(address string, option ...Option) gsvc.Registry {
if password != "" {
cfg.Password = password
}
cfg.DialTimeout = DefaultDialTimeout
cfg.AutoSyncInterval = DefaultAutoSyncInterval
var usedOption Option
if len(option) > 0 {
usedOption = option[0]
}
if usedOption.DialTimeout > 0 {
cfg.DialTimeout = usedOption.DialTimeout
}
if usedOption.AutoSyncInterval > 0 {
cfg.AutoSyncInterval = usedOption.AutoSyncInterval
}
client, err := etcd3.New(cfg)
if err != nil {
panic(gerror.Wrap(err, `create etcd client failed`))

View File

@ -3,8 +3,9 @@ module github.com/gogf/gf/contrib/registry/etcd/v2
go 1.18
require (
github.com/gogf/gf/v2 v2.7.3
github.com/gogf/gf/v2 v2.7.4
go.etcd.io/etcd/client/v3 v3.5.7
google.golang.org/grpc v1.59.0
)
require (
@ -41,7 +42,6 @@ require (
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
google.golang.org/grpc v1.59.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

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.7.3
require github.com/gogf/gf/v2 v2.7.4
require (
github.com/BurntSushi/toml v1.3.2 // indirect

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/registry/nacos/v2
go 1.18
require (
github.com/gogf/gf/v2 v2.7.3
github.com/gogf/gf/v2 v2.7.4
github.com/nacos-group/nacos-sdk-go/v2 v2.2.7
)

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.7.3
github.com/gogf/gf/v2 v2.7.4
github.com/polarismesh/polaris-go v1.5.5
)

View File

@ -4,7 +4,7 @@ go 1.18
require (
github.com/go-zookeeper/zk v1.0.3
github.com/gogf/gf/v2 v2.7.3
github.com/gogf/gf/v2 v2.7.4
golang.org/x/sync v0.4.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.7.3
github.com/gogf/gf/v2 v2.7.3
github.com/gogf/gf/contrib/registry/file/v2 v2.7.4
github.com/gogf/gf/v2 v2.7.4
go.opentelemetry.io/otel v1.14.0
go.opentelemetry.io/otel/trace v1.14.0
google.golang.org/grpc v1.57.2

View File

@ -29,12 +29,10 @@ const (
tracingInstrumentGrpcClient = "github.com/gogf/gf/contrib/rpc/grpcx/v2/krpc.GrpcClient"
tracingInstrumentGrpcServer = "github.com/gogf/gf/contrib/rpc/grpcx/v2/krpc.GrpcServer"
tracingEventGrpcRequest = "grpc.request"
tracingEventGrpcRequestMessage = "grpc.request.message"
tracingEventGrpcRequestBaggage = "grpc.request.baggage"
tracingEventGrpcMetadataOutgoing = "grpc.metadata.outgoing"
tracingEventGrpcMetadataIncoming = "grpc.metadata.incoming"
tracingEventGrpcResponse = "grpc.response"
tracingEventGrpcResponseMessage = "grpc.response.message"
)
type metadataSupplier struct {

View File

@ -19,7 +19,6 @@ import (
"google.golang.org/grpc/status"
"github.com/gogf/gf/contrib/rpc/grpcx/v2/internal/grpcctx"
"github.com/gogf/gf/contrib/rpc/grpcx/v2/internal/utils"
"github.com/gogf/gf/v2"
"github.com/gogf/gf/v2/net/gtrace"
"github.com/gogf/gf/v2/util/gconv"
@ -59,25 +58,10 @@ func UnaryClientInterceptor(ctx context.Context, method string, req, reply inter
span.AddEvent(tracingEventGrpcRequest, trace.WithAttributes(
attribute.String(tracingEventGrpcRequestBaggage, gconv.String(gtrace.GetBaggageMap(ctx))),
attribute.String(tracingEventGrpcMetadataOutgoing, gconv.String(grpcctx.Ctx{}.OutgoingMap(ctx))),
attribute.String(
tracingEventGrpcRequestMessage,
utils.MarshalMessageToJsonStringForTracing(
req, "Request", tracingMaxContentLogSize,
),
),
))
err := invoker(ctx, method, req, reply, cc, callOpts...)
span.AddEvent(tracingEventGrpcResponse, trace.WithAttributes(
attribute.String(
tracingEventGrpcResponseMessage,
utils.MarshalMessageToJsonStringForTracing(
reply, "Response", tracingMaxContentLogSize,
),
),
))
if err != nil {
s, _ := status.FromError(err)
span.SetStatus(codes.Error, s.Message())

View File

@ -20,7 +20,6 @@ import (
"google.golang.org/grpc/status"
"github.com/gogf/gf/contrib/rpc/grpcx/v2/internal/grpcctx"
"github.com/gogf/gf/contrib/rpc/grpcx/v2/internal/utils"
"github.com/gogf/gf/v2"
"github.com/gogf/gf/v2/net/gtrace"
"github.com/gogf/gf/v2/util/gconv"
@ -58,25 +57,10 @@ func UnaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.Una
span.AddEvent(tracingEventGrpcRequest, trace.WithAttributes(
attribute.String(tracingEventGrpcRequestBaggage, gconv.String(gtrace.GetBaggageMap(ctx))),
attribute.String(tracingEventGrpcMetadataIncoming, gconv.String(grpcctx.Ctx{}.IncomingMap(ctx))),
attribute.String(
tracingEventGrpcRequestMessage,
utils.MarshalMessageToJsonStringForTracing(
req, "Request", tracingMaxContentLogSize,
),
),
))
res, err := handler(ctx, req)
span.AddEvent(tracingEventGrpcResponse, trace.WithAttributes(
attribute.String(
tracingEventGrpcResponseMessage,
utils.MarshalMessageToJsonStringForTracing(
res, "Response", tracingMaxContentLogSize,
),
),
))
if err != nil {
s, _ := status.FromError(err)
span.SetStatus(codes.Error, s.Message())

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.7.3
require github.com/gogf/gf/v2 v2.7.4
require (
github.com/BurntSushi/toml v1.3.2 // 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.7.3
github.com/gogf/gf/v2 v2.7.4
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.18
require (
github.com/gogf/gf/v2 v2.7.3
github.com/gogf/gf/v2 v2.7.4
go.opentelemetry.io/otel v1.22.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/trace/otlphttp/v2
go 1.18
require (
github.com/gogf/gf/v2 v2.7.3
github.com/gogf/gf/v2 v2.7.4
go.opentelemetry.io/otel v1.22.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0

View File

@ -178,6 +178,7 @@ type DB interface {
ConvertValueForLocal(ctx context.Context, fieldType string, fieldValue interface{}) (interface{}, error) // See Core.ConvertValueForLocal
CheckLocalTypeForField(ctx context.Context, fieldType string, fieldValue interface{}) (LocalType, error) // See Core.CheckLocalTypeForField
FormatUpsert(columns []string, list List, option DoInsertOption) (string, error) // See Core.DoFormatUpsert
OrderRandomFunction() string // See Core.OrderRandomFunction
}
// TX defines the interfaces for ORM transaction operations.
@ -446,6 +447,7 @@ type LocalType string
const (
LocalTypeUndefined LocalType = ""
LocalTypeString LocalType = "string"
LocalTypeTime LocalType = "time"
LocalTypeDate LocalType = "date"
LocalTypeDatetime LocalType = "datetime"
LocalTypeInt LocalType = "int"
@ -491,9 +493,11 @@ const (
fieldTypeSmallmoney = "smallmoney"
fieldTypeBool = "bool"
fieldTypeBit = "bit"
fieldTypeDate = "date"
fieldTypeDatetime = "datetime"
fieldTypeTimestamp = "timestamp"
fieldTypeYear = "year" // YYYY
fieldTypeDate = "date" // YYYY-MM-DD
fieldTypeTime = "time" // HH:MM:SS
fieldTypeDatetime = "datetime" // YYYY-MM-DD HH:MM:SS
fieldTypeTimestamp = "timestamp" // YYYYMMDD HHMMSS
fieldTypeTimestampz = "timestamptz"
fieldTypeJson = "json"
fieldTypeJsonb = "jsonb"

View File

@ -276,12 +276,18 @@ func parseConfigNodeLink(node *ConfigNode) *ConfigNode {
node.Pass = match[3]
node.Protocol = match[4]
array := gstr.Split(match[5], ":")
if len(array) == 2 && node.Protocol != "file" {
node.Host = array[0]
node.Port = array[1]
node.Name = match[6]
} else {
if node.Protocol == "file" {
node.Name = match[5]
} else {
if len(array) == 2 {
// link with port.
node.Host = array[0]
node.Port = array[1]
} else {
// link without port.
node.Host = array[0]
}
node.Name = match[6]
}
if len(match) > 6 && match[7] != "" {
node.Extra = match[7]

View File

@ -28,7 +28,19 @@ import (
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
// Kinds of data type examples:
// year(4)
// datetime
// varchar(64)
// bigint(20)
// int(10) unsigned
typeName := gstr.StrTillEx(field.Type, "(") // int(10) unsigned -> int
if typeName != "" {
typeName = gstr.Trim(typeName)
} else {
typeName = field.Type
}
return typeName
}
return ""
}
@ -63,9 +75,10 @@ func (c *Core) ConvertDataForRecord(ctx context.Context, value interface{}, tabl
data = MapOrStructToMapDeep(value, true)
)
for fieldName, fieldValue := range data {
var fieldType = c.GetFieldTypeStr(ctx, fieldName, table, c.GetSchema())
data[fieldName], err = c.db.ConvertValueForField(
ctx,
c.GetFieldTypeStr(ctx, fieldName, table, c.GetSchema()),
fieldType,
fieldValue,
)
if err != nil {
@ -83,6 +96,10 @@ func (c *Core) ConvertValueForField(ctx context.Context, fieldType string, field
err error
convertedValue = fieldValue
)
switch fieldValue.(type) {
case time.Time, *time.Time, gtime.Time, *gtime.Time:
goto Default
}
// If `value` implements interface `driver.Valuer`, it then uses the interface for value converting.
if valuer, ok := fieldValue.(driver.Valuer); ok {
if convertedValue, err = valuer.Value(); err != nil {
@ -90,6 +107,7 @@ func (c *Core) ConvertValueForField(ctx context.Context, fieldType string, field
}
return convertedValue, nil
}
Default:
// Default value converting.
var (
rvValue = reflect.ValueOf(fieldValue)
@ -100,6 +118,9 @@ func (c *Core) ConvertValueForField(ctx context.Context, fieldType string, field
rvKind = rvValue.Kind()
}
switch rvKind {
case reflect.Invalid:
convertedValue = nil
case reflect.Slice, reflect.Array, reflect.Map:
// It should ignore the bytes type.
if _, ok := fieldValue.([]byte); !ok {
@ -109,7 +130,6 @@ func (c *Core) ConvertValueForField(ctx context.Context, fieldType string, field
return nil, err
}
}
case reflect.Struct:
switch r := fieldValue.(type) {
// If the time is zero, it then updates it to nil,
@ -117,25 +137,65 @@ func (c *Core) ConvertValueForField(ctx context.Context, fieldType string, field
case time.Time:
if r.IsZero() {
convertedValue = nil
} else {
switch fieldType {
case fieldTypeYear:
convertedValue = r.Format("2006")
case fieldTypeDate:
convertedValue = r.Format("2006-01-02")
case fieldTypeTime:
convertedValue = r.Format("15:04:05")
default:
}
}
case *time.Time:
if r == nil {
// Nothing to do.
} else {
switch fieldType {
case fieldTypeYear:
convertedValue = r.Format("2006")
case fieldTypeDate:
convertedValue = r.Format("2006-01-02")
case fieldTypeTime:
convertedValue = r.Format("15:04:05")
default:
}
}
case gtime.Time:
if r.IsZero() {
convertedValue = nil
} else {
convertedValue = r.Time
switch fieldType {
case fieldTypeYear:
convertedValue = r.Layout("2006")
case fieldTypeDate:
convertedValue = r.Layout("2006-01-02")
case fieldTypeTime:
convertedValue = r.Layout("15:04:05")
default:
convertedValue = r.Time
}
}
case *gtime.Time:
if r.IsZero() {
convertedValue = nil
} else {
convertedValue = r.Time
switch fieldType {
case fieldTypeYear:
convertedValue = r.Layout("2006")
case fieldTypeDate:
convertedValue = r.Layout("2006-01-02")
case fieldTypeTime:
convertedValue = r.Layout("15:04:05")
default:
convertedValue = r.Time
}
}
case *time.Time:
// Nothing to do.
case Counter, *Counter:
// Nothing to do.
@ -156,7 +216,9 @@ func (c *Core) ConvertValueForField(ctx context.Context, fieldType string, field
}
}
}
default:
}
return convertedValue, nil
}
@ -247,6 +309,10 @@ func (c *Core) CheckLocalTypeForField(ctx context.Context, fieldType string, fie
fieldTypeDate:
return LocalTypeDate, nil
case
fieldTypeTime:
return LocalTypeTime, nil
case
fieldTypeDatetime,
fieldTypeTimestamp,
@ -353,13 +419,19 @@ func (c *Core) ConvertValueForLocal(
return gconv.Bool(fieldValue), nil
case LocalTypeDate:
// Date without time.
if t, ok := fieldValue.(time.Time); ok {
return gtime.NewFromTime(t).Format("Y-m-d"), nil
}
t, _ := gtime.StrToTime(gconv.String(fieldValue))
return t.Format("Y-m-d"), nil
case LocalTypeTime:
if t, ok := fieldValue.(time.Time); ok {
return gtime.NewFromTime(t).Format("H:i:s"), nil
}
t, _ := gtime.StrToTime(gconv.String(fieldValue))
return t.Format("H:i:s"), nil
case LocalTypeDatetime:
if t, ok := fieldValue.(time.Time); ok {
return gtime.NewFromTime(t), nil

View File

@ -455,6 +455,11 @@ func (c *Core) RowsToResult(ctx context.Context, rows *sql.Rows) (Result, error)
return result, nil
}
// OrderRandomFunction returns the SQL function for random ordering.
func (c *Core) OrderRandomFunction() string {
return "RAND()"
}
func (c *Core) columnValueToLocalValue(ctx context.Context, value interface{}, columnType *sql.ColumnType) (interface{}, error) {
var scanType = columnType.ScanType()
if scanType != nil {

View File

@ -25,7 +25,7 @@ type Model struct {
tablesInit string // Table names when model initialization.
tables string // Operation table names, which can be more than one table names and aliases, like: "user", "user u", "user u, user_detail ud".
fields string // Operation fields, multiple fields joined using char ','.
fieldsEx string // Excluded operation fields, multiple fields joined using char ','.
fieldsEx []string // Excluded operation fields, it here uses slice instead of string type for quick filtering.
withArray []interface{} // Arguments for With feature.
withAll bool // Enable model association operations on all objects that have "with" tag in the struct.
extraArgs []interface{} // Extra custom arguments for sql, which are prepended to the arguments before sql committed to underlying driver.
@ -281,6 +281,10 @@ func (m *Model) Clone() *Model {
newModel.whereBuilder = m.whereBuilder.Clone()
newModel.whereBuilder.model = newModel
// Shallow copy slice attributes.
if n := len(m.fieldsEx); n > 0 {
newModel.fieldsEx = make([]string, n)
copy(newModel.fieldsEx, m.fieldsEx)
}
if n := len(m.extraArgs); n > 0 {
newModel.extraArgs = make([]interface{}, n)
copy(newModel.extraArgs, m.extraArgs)
@ -289,6 +293,10 @@ func (m *Model) Clone() *Model {
newModel.withArray = make([]interface{}, n)
copy(newModel.withArray, m.withArray)
}
if n := len(m.having); n > 0 {
newModel.having = make([]interface{}, n)
copy(newModel.having, m.having)
}
return newModel
}

View File

@ -10,6 +10,7 @@ import (
"fmt"
"github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)
@ -17,31 +18,36 @@ import (
// Fields appends `fieldNamesOrMapStruct` to the operation fields of the model, multiple fields joined using char ','.
// The parameter `fieldNamesOrMapStruct` can be type of string/map/*map/struct/*struct.
//
// Eg:
// Example:
// Fields("id", "name", "age")
// Fields([]string{"id", "name", "age"})
// Fields(map[string]interface{}{"id":1, "name":"john", "age":18})
// Fields(User{ Id: 1, Name: "john", Age: 18}).
// Fields(User{Id: 1, Name: "john", Age: 18}).
func (m *Model) Fields(fieldNamesOrMapStruct ...interface{}) *Model {
length := len(fieldNamesOrMapStruct)
if length == 0 {
return m
}
fields := m.getFieldsFrom(m.tablesInit, fieldNamesOrMapStruct...)
fields := m.filterFieldsFrom(m.tablesInit, fieldNamesOrMapStruct...)
if len(fields) == 0 {
return m
}
return m.appendFieldsByStr(gstr.Join(fields, ","))
model := m.getModel()
return model.appendFieldsByStr(gstr.Join(fields, ","))
}
// FieldsPrefix performs as function Fields but add extra prefix for each field.
func (m *Model) FieldsPrefix(prefixOrAlias string, fieldNamesOrMapStruct ...interface{}) *Model {
fields := m.getFieldsFrom(m.getTableNameByPrefixOrAlias(prefixOrAlias), fieldNamesOrMapStruct...)
fields := m.filterFieldsFrom(
m.getTableNameByPrefixOrAlias(prefixOrAlias),
fieldNamesOrMapStruct...,
)
if len(fields) == 0 {
return m
}
gstr.PrefixArray(fields, prefixOrAlias+".")
return m.appendFieldsByStr(gstr.Join(fields, ","))
model := m.getModel()
return model.appendFieldsByStr(gstr.Join(fields, ","))
}
// FieldsEx appends `fieldNamesOrMapStruct` to the excluded operation fields of the model,
@ -49,28 +55,36 @@ func (m *Model) FieldsPrefix(prefixOrAlias string, fieldNamesOrMapStruct ...inte
// Note that this function supports only single table operations.
// The parameter `fieldNamesOrMapStruct` can be type of string/map/*map/struct/*struct.
//
// Also see Fields.
// Example:
// FieldsEx("id", "name", "age")
// FieldsEx([]string{"id", "name", "age"})
// FieldsEx(map[string]interface{}{"id":1, "name":"john", "age":18})
// FieldsEx(User{Id: 1, Name: "john", Age: 18}).
func (m *Model) FieldsEx(fieldNamesOrMapStruct ...interface{}) *Model {
return m.doFieldsEx(m.tablesInit, fieldNamesOrMapStruct...)
}
func (m *Model) doFieldsEx(table string, fieldNamesOrMapStruct ...interface{}) *Model {
length := len(fieldNamesOrMapStruct)
if length == 0 {
return m
}
fields := m.getFieldsFrom(table, fieldNamesOrMapStruct...)
fields := m.filterFieldsFrom(table, fieldNamesOrMapStruct...)
if len(fields) == 0 {
return m
}
return m.appendFieldsExByStr(gstr.Join(fields, ","))
model := m.getModel()
model.fieldsEx = append(model.fieldsEx, fields...)
return model
}
// FieldsExPrefix performs as function FieldsEx but add extra prefix for each field.
func (m *Model) FieldsExPrefix(prefixOrAlias string, fieldNamesOrMapStruct ...interface{}) *Model {
model := m.doFieldsEx(m.getTableNameByPrefixOrAlias(prefixOrAlias), fieldNamesOrMapStruct...)
array := gstr.SplitAndTrim(model.fieldsEx, ",")
gstr.PrefixArray(array, prefixOrAlias+".")
model.fieldsEx = gstr.Join(array, ",")
model := m.doFieldsEx(
m.getTableNameByPrefixOrAlias(prefixOrAlias),
fieldNamesOrMapStruct...,
)
gstr.PrefixArray(model.fieldsEx, prefixOrAlias+".")
return model
}
@ -80,7 +94,10 @@ func (m *Model) FieldCount(column string, as ...string) *Model {
if len(as) > 0 && as[0] != "" {
asStr = fmt.Sprintf(` AS %s`, m.db.GetCore().QuoteWord(as[0]))
}
return m.appendFieldsByStr(fmt.Sprintf(`COUNT(%s)%s`, m.QuoteWord(column), asStr))
model := m.getModel()
return model.appendFieldsByStr(
fmt.Sprintf(`COUNT(%s)%s`, m.QuoteWord(column), asStr),
)
}
// FieldSum formats and appends commonly used field `SUM(column)` to the select fields of model.
@ -89,7 +106,10 @@ func (m *Model) FieldSum(column string, as ...string) *Model {
if len(as) > 0 && as[0] != "" {
asStr = fmt.Sprintf(` AS %s`, m.db.GetCore().QuoteWord(as[0]))
}
return m.appendFieldsByStr(fmt.Sprintf(`SUM(%s)%s`, m.QuoteWord(column), asStr))
model := m.getModel()
return model.appendFieldsByStr(
fmt.Sprintf(`SUM(%s)%s`, m.QuoteWord(column), asStr),
)
}
// FieldMin formats and appends commonly used field `MIN(column)` to the select fields of model.
@ -98,7 +118,10 @@ func (m *Model) FieldMin(column string, as ...string) *Model {
if len(as) > 0 && as[0] != "" {
asStr = fmt.Sprintf(` AS %s`, m.db.GetCore().QuoteWord(as[0]))
}
return m.appendFieldsByStr(fmt.Sprintf(`MIN(%s)%s`, m.QuoteWord(column), asStr))
model := m.getModel()
return model.appendFieldsByStr(
fmt.Sprintf(`MIN(%s)%s`, m.QuoteWord(column), asStr),
)
}
// FieldMax formats and appends commonly used field `MAX(column)` to the select fields of model.
@ -107,7 +130,10 @@ func (m *Model) FieldMax(column string, as ...string) *Model {
if len(as) > 0 && as[0] != "" {
asStr = fmt.Sprintf(` AS %s`, m.db.GetCore().QuoteWord(as[0]))
}
return m.appendFieldsByStr(fmt.Sprintf(`MAX(%s)%s`, m.QuoteWord(column), asStr))
model := m.getModel()
return model.appendFieldsByStr(
fmt.Sprintf(`MAX(%s)%s`, m.QuoteWord(column), asStr),
)
}
// FieldAvg formats and appends commonly used field `AVG(column)` to the select fields of model.
@ -116,7 +142,10 @@ func (m *Model) FieldAvg(column string, as ...string) *Model {
if len(as) > 0 && as[0] != "" {
asStr = fmt.Sprintf(` AS %s`, m.db.GetCore().QuoteWord(as[0]))
}
return m.appendFieldsByStr(fmt.Sprintf(`AVG(%s)%s`, m.QuoteWord(column), asStr))
model := m.getModel()
return model.appendFieldsByStr(
fmt.Sprintf(`AVG(%s)%s`, m.QuoteWord(column), asStr),
)
}
// GetFieldsStr retrieves and returns all fields from the table, joined with char ','.
@ -152,17 +181,17 @@ func (m *Model) GetFieldsStr(prefix ...string) string {
// joined with char ','.
// The parameter `fields` specifies the fields that are excluded.
// The optional parameter `prefix` specifies the prefix for each field, eg: FieldsExStr("id", "u.").
func (m *Model) GetFieldsExStr(fields string, prefix ...string) string {
func (m *Model) GetFieldsExStr(fields string, prefix ...string) (string, error) {
prefixStr := ""
if len(prefix) > 0 {
prefixStr = prefix[0]
}
tableFields, err := m.TableFields(m.tablesInit)
if err != nil {
panic(err)
return "", err
}
if len(tableFields) == 0 {
panic(fmt.Sprintf(`empty table fields for table "%s"`, m.tables))
return "", gerror.Newf(`empty table fields for table "%s"`, m.tables)
}
fieldsExSet := gset.NewStrSetFrom(gstr.SplitAndTrim(fields, ","))
fieldsArray := make([]string, len(tableFields))
@ -180,7 +209,7 @@ func (m *Model) GetFieldsExStr(fields string, prefix ...string) string {
newFields += prefixStr + k
}
newFields = m.db.GetCore().QuoteString(newFields)
return newFields
return newFields, nil
}
// HasField determine whether the field exists in the table.
@ -189,7 +218,7 @@ func (m *Model) HasField(field string) (bool, error) {
}
// getFieldsFrom retrieves, filters and returns fields name from table `table`.
func (m *Model) getFieldsFrom(table string, fieldNamesOrMapStruct ...interface{}) []string {
func (m *Model) filterFieldsFrom(table string, fieldNamesOrMapStruct ...interface{}) []string {
length := len(fieldNamesOrMapStruct)
if length == 0 {
return nil
@ -238,14 +267,11 @@ func (m *Model) appendFieldsByStr(fields string) *Model {
return m
}
func (m *Model) appendFieldsExByStr(fieldsEx string) *Model {
if fieldsEx != "" {
model := m.getModel()
if model.fieldsEx != "" {
model.fieldsEx += ","
func (m *Model) isFieldInFieldsEx(field string) bool {
for _, v := range m.fieldsEx {
if v == field {
return true
}
model.fieldsEx += fieldsEx
return model
}
return m
return false
}

View File

@ -285,7 +285,14 @@ func (m *Model) doInsertWithOption(ctx context.Context, insertOption InsertOptio
}
// Automatic handling for creating/updating time.
if !m.unscoped && (fieldNameCreate != "" || fieldNameUpdate != "") {
if fieldNameCreate != "" && m.isFieldInFieldsEx(fieldNameCreate) {
fieldNameCreate = ""
}
if fieldNameUpdate != "" && m.isFieldInFieldsEx(fieldNameUpdate) {
fieldNameUpdate = ""
}
var isSoftTimeFeatureEnabled = fieldNameCreate != "" || fieldNameUpdate != ""
if !m.unscoped && isSoftTimeFeatureEnabled {
for k, v := range list {
if fieldNameCreate != "" && empty.IsNil(v[fieldNameCreate]) {
fieldCreateValue := stm.GetValueByFieldTypeForCreateOrUpdate(ctx, fieldTypeCreate, false)
@ -299,6 +306,7 @@ func (m *Model) doInsertWithOption(ctx context.Context, insertOption InsertOptio
v[fieldNameUpdate] = fieldUpdateValue
}
}
// for timestamp field that should initialize the delete_at field with value, for example 0.
if fieldNameDelete != "" && empty.IsNil(v[fieldNameDelete]) {
fieldDeleteValue := stm.GetValueByFieldTypeForCreateOrUpdate(ctx, fieldTypeDelete, true)
if fieldDeleteValue != nil {

View File

@ -59,7 +59,7 @@ func (m *Model) OrderDesc(column string) *Model {
// OrderRandom sets the "ORDER BY RANDOM()" statement for the model.
func (m *Model) OrderRandom() *Model {
model := m.getModel()
model.orderBy = "RAND()"
model.orderBy = m.db.OrderRandomFunction()
return model
}

View File

@ -176,7 +176,7 @@ func (m *Model) Array(fieldsAndWhere ...interface{}) ([]Value, error) {
func (m *Model) doStruct(pointer interface{}, where ...interface{}) error {
model := m
// Auto selecting fields by struct attributes.
if model.fieldsEx == "" && (model.fields == "" || model.fields == "*") {
if len(model.fieldsEx) == 0 && (model.fields == "" || model.fields == "*") {
if v, ok := pointer.(reflect.Value); ok {
model = m.Fields(v.Interface())
} else {
@ -212,7 +212,7 @@ func (m *Model) doStruct(pointer interface{}, where ...interface{}) error {
func (m *Model) doStructs(pointer interface{}, where ...interface{}) error {
model := m
// Auto selecting fields by struct attributes.
if model.fieldsEx == "" && (model.fields == "" || model.fields == "*") {
if len(model.fieldsEx) == 0 && (model.fields == "" || model.fields == "*") {
if v, ok := pointer.(reflect.Value); ok {
model = m.Fields(
reflect.New(
@ -341,7 +341,7 @@ func (m *Model) ScanList(structSlicePointer interface{}, bindToAttrName string,
if err != nil {
return err
}
if m.fields != defaultFields || m.fieldsEx != "" {
if m.fields != defaultFields || len(m.fieldsEx) != 0 {
// There are custom fields.
result, err = m.All()
} else {
@ -675,7 +675,7 @@ func (m *Model) getAutoPrefix() string {
// getFieldsFiltered checks the fields and fieldsEx attributes, filters and returns the fields that will
// really be committed to underlying database driver.
func (m *Model) getFieldsFiltered() string {
if m.fieldsEx == "" {
if len(m.fieldsEx) == 0 {
// No filtering, containing special chars.
if gstr.ContainsAny(m.fields, "()") {
return m.fields
@ -688,7 +688,7 @@ func (m *Model) getFieldsFiltered() string {
}
var (
fieldsArray []string
fieldsExSet = gset.NewStrSetFrom(gstr.SplitAndTrim(m.fieldsEx, ","))
fieldsExSet = gset.NewStrSetFrom(m.fieldsEx)
)
if m.fields != "*" {
// Filter custom fields with fieldEx.

View File

@ -15,12 +15,11 @@ import (
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/intlog"
"github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/os/gcache"
"github.com/gogf/gf/v2/os/gtime"
"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/gutil"
)
// SoftTimeType custom defines the soft time field type.
@ -206,9 +205,7 @@ func (m *softTimeMaintainer) getSoftFieldNameAndType(
return nil, nil
}
for _, checkFiledName := range checkFiledNames {
fieldName, _ = gutil.MapPossibleItemByKey(
gconv.Map(fieldsMap), checkFiledName,
)
fieldName = searchFieldNameFromMap(fieldsMap, checkFiledName)
if fieldName != "" {
fieldType, _ = m.db.CheckLocalTypeForField(
ctx, fieldsMap[fieldName].Type, nil,
@ -238,6 +235,23 @@ func (m *softTimeMaintainer) getSoftFieldNameAndType(
return
}
func searchFieldNameFromMap(fieldsMap map[string]*TableField, key string) string {
if len(fieldsMap) == 0 {
return ""
}
_, ok := fieldsMap[key]
if ok {
return key
}
key = utils.RemoveSymbols(key)
for k := range fieldsMap {
if strings.EqualFold(utils.RemoveSymbols(k), key) {
return k
}
}
return ""
}
// GetWhereConditionForDelete retrieves and returns the condition string for soft deleting.
// It supports multiple tables string like:
// "user u, user_detail ud"
@ -339,7 +353,7 @@ func (m *softTimeMaintainer) getConditionByFieldNameAndTypeForSoftDeleting(
switch m.softTimeOption.SoftTimeType {
case SoftTimeTypeAuto:
switch fieldType {
case LocalTypeDate, LocalTypeDatetime:
case LocalTypeDate, LocalTypeTime, LocalTypeDatetime:
return fmt.Sprintf(`%s IS NULL`, quotedFieldName)
case LocalTypeInt, LocalTypeUint, LocalTypeInt64, LocalTypeUint64, LocalTypeBool:
return fmt.Sprintf(`%s=0`, quotedFieldName)
@ -368,7 +382,7 @@ func (m *softTimeMaintainer) GetValueByFieldTypeForCreateOrUpdate(
var value any
if isDeletedField {
switch fieldType {
case LocalTypeDate, LocalTypeDatetime:
case LocalTypeDate, LocalTypeTime, LocalTypeDatetime:
value = nil
default:
value = 0
@ -378,7 +392,7 @@ func (m *softTimeMaintainer) GetValueByFieldTypeForCreateOrUpdate(
switch m.softTimeOption.SoftTimeType {
case SoftTimeTypeAuto:
switch fieldType {
case LocalTypeDate, LocalTypeDatetime:
case LocalTypeDate, LocalTypeTime, LocalTypeDatetime:
value = gtime.Now()
case LocalTypeInt, LocalTypeUint, LocalTypeInt64, LocalTypeUint64:
value = gtime.Timestamp()

View File

@ -54,7 +54,7 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
ctx, "", m.tablesInit,
)
)
if m.unscoped {
if fieldNameUpdate != "" && (m.unscoped || m.isFieldInFieldsEx(fieldNameUpdate)) {
fieldNameUpdate = ""
}

View File

@ -204,7 +204,7 @@ func (m *Model) doMappingAndFilterForInsertOrUpdateDataMap(data Map, allowOmitEm
}
} else if len(m.fieldsEx) > 0 {
// Filter specified fields.
for _, v := range gstr.SplitAndTrim(m.fieldsEx, ",") {
for _, v := range m.fieldsEx {
delete(data, v)
}
}

View File

@ -67,6 +67,9 @@ func (m *Model) WithAll() *Model {
// doWithScanStruct handles model association operations feature for single struct.
func (m *Model) doWithScanStruct(pointer interface{}) error {
if len(m.withArray) == 0 && m.withAll == false {
return nil
}
var (
err error
allowedTypeStrArray = make([]string, 0)
@ -183,6 +186,9 @@ func (m *Model) doWithScanStruct(pointer interface{}) error {
// doWithScanStructs handles model association operations feature for struct slice.
// Also see doWithScanStruct.
func (m *Model) doWithScanStructs(pointer interface{}) error {
if len(m.withArray) == 0 && m.withAll == false {
return nil
}
if v, ok := pointer.(reflect.Value); ok {
pointer = v.Interface()
}

View File

@ -232,6 +232,22 @@ func Test_parseConfigNodeLink_WithType(t *testing.T) {
t.Assert(newNode.Charset, defaultCharset)
t.Assert(newNode.Protocol, `tcp`)
})
// #3755
gtest.C(t, func(t *gtest.T) {
node := &ConfigNode{
Link: "mysql:user:pwd@tcp(rdsid.mysql.rds.aliyuncs.com)/dbname?charset=utf8&loc=Local",
}
newNode := parseConfigNodeLink(node)
t.Assert(newNode.Type, `mysql`)
t.Assert(newNode.User, `user`)
t.Assert(newNode.Pass, `pwd`)
t.Assert(newNode.Host, `rdsid.mysql.rds.aliyuncs.com`)
t.Assert(newNode.Port, ``)
t.Assert(newNode.Name, `dbname`)
t.Assert(newNode.Extra, `charset=utf8&loc=Local`)
t.Assert(newNode.Charset, `utf8`)
t.Assert(newNode.Protocol, `tcp`)
})
}
func Test_Func_doQuoteWord(t *testing.T) {

View File

@ -12,7 +12,6 @@ import (
"github.com/gogf/gf/v2/container/gvar"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/text/gstr"
)
// Redis client.
@ -48,12 +47,7 @@ const (
errorNilRedis = `the Redis object is nil`
)
var (
errorNilAdapter = gstr.Trim(gstr.Replace(`
redis adapter is not set, missing configuration or adapter register?
possible reference: https://github.com/gogf/gf/tree/master/contrib/nosql/redis
`, "\n", ""))
)
const errorNilAdapter = `redis adapter is not set, missing configuration or adapter register? possible reference: https://github.com/gogf/gf/tree/master/contrib/nosql/redis`
// initGroup initializes the group object of redis.
func (r *Redis) initGroup() *Redis {

View File

@ -133,16 +133,25 @@ func filterFileByFilters(file string, filters []string) (filtered bool) {
// CallerPackage returns the package name of the caller.
func CallerPackage() string {
function, _, _ := Caller()
// it defines a new internal function to retrieve the package name from caller function name,
// which is for unit testing purpose for core logic of this function.
return getPackageFromCallerFunction(function)
}
func getPackageFromCallerFunction(function string) string {
indexSplit := strings.LastIndexByte(function, '/')
if indexSplit == -1 {
return function[:strings.IndexByte(function, '.')]
} else {
leftPart := function[:indexSplit+1]
rightPart := function[indexSplit+1:]
indexDot := strings.IndexByte(function, '.')
rightPart = rightPart[:indexDot-1]
return leftPart + rightPart
}
var (
leftPart = function[:indexSplit+1]
rightPart = function[indexSplit+1:]
indexDot = strings.IndexByte(rightPart, '.')
)
if indexDot >= 0 {
rightPart = rightPart[:indexDot]
}
return leftPart + rightPart
}
// CallerFunction returns the function name of the caller.

View File

@ -0,0 +1,26 @@
// 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 gdebug
import (
"testing"
)
func Test_getPackageFromCallerFunction(t *testing.T) {
dataMap := map[string]string{
"github.com/gogf/gf/v2/test/a": "github.com/gogf/gf/v2/test/a",
"github.com/gogf/gf/v2/test/a.C": "github.com/gogf/gf/v2/test/a",
"github.com/gogf/gf/v2/test/aa.C": "github.com/gogf/gf/v2/test/aa",
"github.com/gogf/gf/v2/test/gtest.C": "github.com/gogf/gf/v2/test/gtest",
}
for functionName, packageName := range dataMap {
if result := getPackageFromCallerFunction(functionName); result != packageName {
t.Logf(`%s != %s`, result, packageName)
t.Fail()
}
}
}

View File

@ -1,3 +1,9 @@
// 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 gdebug_test
import (

View File

@ -49,6 +49,27 @@ func DecodeWithoutRoot(content []byte) (map[string]interface{}, error) {
return m, nil
}
// XMLEscapeChars forces escaping invalid characters in attribute and element values.
// NOTE: this is brute force with NO interrogation of '&' being escaped already; if it is
// then '&amp;' will be re-escaped as '&amp;amp;'.
//
/*
The values are:
" &quot;
' &apos;
< &lt;
> &gt;
& &amp;
*/
//
// Note: if XMLEscapeCharsDecoder(true) has been called - or the default, 'false,' value
// has been toggled to 'true' - then XMLEscapeChars(true) is ignored. If XMLEscapeChars(true)
// has already been called before XMLEscapeCharsDecoder(true), XMLEscapeChars(false) is called
// to turn escape encoding on mv.Xml, etc., to prevent double escaping ampersands, '&'.
func XMLEscapeChars(b ...bool) {
mxj.XMLEscapeChars(b...)
}
// Encode encodes map `m` to an XML format content as bytes.
// The optional parameter `rootTag` is used to specify the XML root tag.
func Encode(m map[string]interface{}, rootTag ...string) ([]byte, error) {

View File

@ -206,3 +206,28 @@ func TestErrCase(t *testing.T) {
}
})
}
func Test_Issue3716(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
xml = `<Person><Bio>I am a software developer &amp; I love coding.</Bio><Email>john.doe@example.com</Email><Name>&lt;&gt;&amp;&apos;&quot;AAA</Name></Person>`
m = map[string]interface{}{
"Person": map[string]interface{}{
"Name": "<>&'\"AAA",
"Email": "john.doe@example.com",
"Bio": "I am a software developer & I love coding.",
},
}
)
gxml.XMLEscapeChars(true)
defer gxml.XMLEscapeChars(false)
xb, err := gxml.Encode(m)
t.AssertNil(err)
t.Assert(string(xb), xml)
dm, err := gxml.Decode(xb)
t.AssertNil(err)
t.Assert(dm, m)
})
}

View File

@ -46,7 +46,7 @@ var (
CodeNotFound = localCode{65, "Not Found", nil} // Resource does not exist.
CodeInvalidRequest = localCode{66, "Invalid Request", nil} // Invalid request.
CodeNecessaryPackageNotImport = localCode{67, "Necessary Package Not Import", nil} // It needs necessary package import.
CodeInternalPanic = localCode{68, "Internal Panic", nil} // An panic occurred internally.
CodeInternalPanic = localCode{68, "Internal Panic", nil} // A panic occurred internally.
CodeBusinessValidationFailed = localCode{300, "Business Validation Failed", nil} // Business validation failed.
)

View File

@ -3,22 +3,22 @@ module github.com/gogf/gf/example
go 1.21
require (
github.com/gogf/gf/contrib/config/apollo/v2 v2.7.3
github.com/gogf/gf/contrib/config/consul/v2 v2.7.3
github.com/gogf/gf/contrib/config/kubecm/v2 v2.7.3
github.com/gogf/gf/contrib/config/nacos/v2 v2.7.3
github.com/gogf/gf/contrib/config/polaris/v2 v2.7.3
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.7.3
github.com/gogf/gf/contrib/metric/otelmetric/v2 v2.7.3
github.com/gogf/gf/contrib/nosql/redis/v2 v2.7.3
github.com/gogf/gf/contrib/registry/etcd/v2 v2.7.3
github.com/gogf/gf/contrib/registry/file/v2 v2.7.3
github.com/gogf/gf/contrib/registry/nacos/v2 v2.7.3
github.com/gogf/gf/contrib/registry/polaris/v2 v2.7.3
github.com/gogf/gf/contrib/rpc/grpcx/v2 v2.7.3
github.com/gogf/gf/contrib/trace/otlpgrpc/v2 v2.7.3
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.7.3
github.com/gogf/gf/v2 v2.7.3
github.com/gogf/gf/contrib/config/apollo/v2 v2.7.4
github.com/gogf/gf/contrib/config/consul/v2 v2.7.4
github.com/gogf/gf/contrib/config/kubecm/v2 v2.7.4
github.com/gogf/gf/contrib/config/nacos/v2 v2.7.4
github.com/gogf/gf/contrib/config/polaris/v2 v2.7.4
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.7.4
github.com/gogf/gf/contrib/metric/otelmetric/v2 v2.7.4
github.com/gogf/gf/contrib/nosql/redis/v2 v2.7.4
github.com/gogf/gf/contrib/registry/etcd/v2 v2.7.4
github.com/gogf/gf/contrib/registry/file/v2 v2.7.4
github.com/gogf/gf/contrib/registry/nacos/v2 v2.7.4
github.com/gogf/gf/contrib/registry/polaris/v2 v2.7.4
github.com/gogf/gf/contrib/rpc/grpcx/v2 v2.7.4
github.com/gogf/gf/contrib/trace/otlpgrpc/v2 v2.7.4
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.7.4
github.com/gogf/gf/v2 v2.7.4
github.com/hashicorp/consul/api v1.24.0
github.com/hashicorp/go-cleanhttp v0.5.2
github.com/nacos-group/nacos-sdk-go/v2 v2.2.7

View File

@ -908,7 +908,7 @@ func TestPointerToStruct(t *testing.T) {
}
}
func TestIssue9(t *testing.T) {
func Test_Issue9(t *testing.T) {
// simple pointer copy
x := 42
testA := map[string]*int{

View File

@ -58,7 +58,10 @@ func IsNumeric(s string) bool {
return false
}
for i := 0; i < length; i++ {
if s[i] == '-' && i == 0 {
if (s[i] == '-' || s[i] == '+') && i == 0 {
if length == 1 {
return false
}
continue
}
if s[i] == '.' {

View File

@ -95,3 +95,34 @@ func Test_CanCallIsNil(t *testing.T) {
t.Assert(utils.CanCallIsNil(reflect.ValueOf(iUnsafePointer)), true)
})
}
func Test_IsNumeric(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
t.Assert(utils.IsNumeric("12345"), true)
t.Assert(utils.IsNumeric("-12345"), true)
t.Assert(utils.IsNumeric("+12345"), true)
t.Assert(utils.IsNumeric("123.45"), true)
t.Assert(utils.IsNumeric("-123.45"), true)
t.Assert(utils.IsNumeric("+123.45"), true)
t.Assert(utils.IsNumeric("1+23"), false)
t.Assert(utils.IsNumeric("123a45"), false)
t.Assert(utils.IsNumeric("123.45.67"), false)
t.Assert(utils.IsNumeric(""), false)
t.Assert(utils.IsNumeric("1e10"), false)
t.Assert(utils.IsNumeric("123 45"), false)
t.Assert(utils.IsNumeric("!!!"), false)
t.Assert(utils.IsNumeric("-a23"), false)
t.Assert(utils.IsNumeric("+a23"), false)
t.Assert(utils.IsNumeric("1+23"), false)
t.Assert(utils.IsNumeric("1-23"), false)
t.Assert(utils.IsNumeric("123."), false)
t.Assert(utils.IsNumeric(".123"), false)
t.Assert(utils.IsNumeric("123.a"), false)
t.Assert(utils.IsNumeric("a.123"), false)
t.Assert(utils.IsNumeric("+"), false)
t.Assert(utils.IsNumeric("-"), false)
t.Assert(utils.IsNumeric("."), false)
t.Assert(utils.IsNumeric("-."), false)
t.Assert(utils.IsNumeric("+."), false)
})
}

View File

@ -9,7 +9,6 @@ package gclient
import (
"context"
"fmt"
"io"
"net/http"
"net/http/httptrace"
@ -21,7 +20,6 @@ import (
"github.com/gogf/gf/v2"
"github.com/gogf/gf/v2/internal/httputil"
"github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/net/gtrace"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/gmetric"
@ -39,10 +37,8 @@ const (
tracingEventHttpRequest = "http.request"
tracingEventHttpRequestHeaders = "http.request.headers"
tracingEventHttpRequestBaggage = "http.request.baggage"
tracingEventHttpRequestBody = "http.request.body"
tracingEventHttpResponse = "http.response"
tracingEventHttpResponseHeaders = "http.response.headers"
tracingEventHttpResponseBody = "http.response.body"
tracingMiddlewareHandled gctx.StrKey = `MiddlewareClientTracingHandled`
)
@ -102,17 +98,11 @@ func internalMiddlewareObservability(c *Client, r *http.Request) (response *Resp
return
}
reqBodyContentBytes, _ := io.ReadAll(response.Body)
response.Body = utils.NewReadCloser(reqBodyContentBytes, false)
resBodyContent, err := gtrace.SafeContentForHttp(reqBodyContentBytes, response.Header)
if err != nil {
span.SetStatus(codes.Error, fmt.Sprintf(`converting safe content failed: %s`, err.Error()))
}
span.AddEvent(tracingEventHttpResponse, trace.WithAttributes(
attribute.String(tracingEventHttpResponseHeaders, gconv.String(httputil.HeaderToMap(response.Header))),
attribute.String(tracingEventHttpResponseBody, resBodyContent),
attribute.String(
tracingEventHttpResponseHeaders,
gconv.String(httputil.HeaderToMap(response.Header)),
),
))
return
}

View File

@ -167,7 +167,10 @@ func (c *Client) prepareRequest(ctx context.Context, method, url string, data ..
if !gstr.ContainsI(url, httpProtocolName) {
url = httpProtocolName + `://` + url
}
var params string
var (
params string
allowFileUploading = true
)
if len(data) > 0 {
switch c.header[httpHeaderContentType] {
case httpHeaderContentTypeJson:
@ -181,6 +184,7 @@ func (c *Client) prepareRequest(ctx context.Context, method, url string, data ..
params = string(b)
}
}
allowFileUploading = false
case httpHeaderContentTypeXml:
switch data[0].(type) {
@ -193,6 +197,8 @@ func (c *Client) prepareRequest(ctx context.Context, method, url string, data ..
params = string(b)
}
}
allowFileUploading = false
default:
params = httputil.BuildParams(data[0], c.noUrlEncode)
}
@ -223,14 +229,18 @@ func (c *Client) prepareRequest(ctx context.Context, method, url string, data ..
return nil, err
}
} else {
if strings.Contains(params, httpParamFileHolder) {
if allowFileUploading && strings.Contains(params, httpParamFileHolder) {
// File uploading request.
var (
buffer = bytes.NewBuffer(nil)
writer = multipart.NewWriter(buffer)
buffer = bytes.NewBuffer(nil)
writer = multipart.NewWriter(buffer)
isFileUploading = false
)
for _, item := range strings.Split(params, "&") {
array := strings.Split(item, "=")
if len(array) < 2 {
continue
}
if len(array[1]) > 6 && strings.Compare(array[1][0:6], httpParamFileHolder) == 0 {
path := array[1][6:]
if !gfile.Exists(path) {
@ -241,43 +251,50 @@ func (c *Client) prepareRequest(ctx context.Context, method, url string, data ..
formFileName = gfile.Basename(path)
formFieldName = array[0]
)
// it sets post content type as `application/octet-stream`
if file, err = writer.CreateFormFile(formFieldName, formFileName); err != nil {
err = gerror.Wrapf(err, `CreateFormFile failed with "%s", "%s"`, formFieldName, formFileName)
return nil, err
} else {
var f *os.File
if f, err = gfile.Open(path); err != nil {
return nil, err
}
if _, err = io.Copy(file, f); err != nil {
err = gerror.Wrapf(err, `io.Copy failed from "%s" to form "%s"`, path, formFieldName)
_ = f.Close()
return nil, err
}
_ = f.Close()
return nil, gerror.Wrapf(
err, `CreateFormFile failed with "%s", "%s"`, formFieldName, formFileName,
)
}
var f *os.File
if f, err = gfile.Open(path); err != nil {
return nil, err
}
if _, err = io.Copy(file, f); err != nil {
_ = f.Close()
return nil, gerror.Wrapf(
err, `io.Copy failed from "%s" to form "%s"`, path, formFieldName,
)
}
if err = f.Close(); err != nil {
return nil, gerror.Wrapf(err, `close file descriptor failed for "%s"`, path)
}
isFileUploading = true
} else {
var (
fieldName = array[0]
fieldValue = array[1]
)
if err = writer.WriteField(fieldName, fieldValue); err != nil {
err = gerror.Wrapf(err, `write form field failed with "%s", "%s"`, fieldName, fieldValue)
return nil, err
return nil, gerror.Wrapf(
err, `write form field failed with "%s", "%s"`, fieldName, fieldValue,
)
}
}
}
// Close finishes the multipart message and writes the trailing
// boundary end line to the output.
if err = writer.Close(); err != nil {
err = gerror.Wrapf(err, `form writer close failed`)
return nil, err
return nil, gerror.Wrapf(err, `form writer close failed`)
}
if req, err = http.NewRequest(method, url, buffer); err != nil {
err = gerror.Wrapf(err, `http.NewRequest failed for method "%s" and URL "%s"`, method, url)
return nil, err
} else {
return nil, gerror.Wrapf(
err, `http.NewRequest failed for method "%s" and URL "%s"`, method, url,
)
}
if isFileUploading {
req.Header.Set(httpHeaderContentType, writer.FormDataContentType())
}
} else {
@ -286,18 +303,17 @@ func (c *Client) prepareRequest(ctx context.Context, method, url string, data ..
if req, err = http.NewRequest(method, url, bytes.NewReader(paramBytes)); err != nil {
err = gerror.Wrapf(err, `http.NewRequest failed for method "%s" and URL "%s"`, method, url)
return nil, err
} else {
if v, ok := c.header[httpHeaderContentType]; ok {
// Custom Content-Type.
req.Header.Set(httpHeaderContentType, v)
} else if len(paramBytes) > 0 {
if (paramBytes[0] == '[' || paramBytes[0] == '{') && json.Valid(paramBytes) {
// Auto-detecting and setting the post content format: JSON.
req.Header.Set(httpHeaderContentType, httpHeaderContentTypeJson)
} else if gregex.IsMatchString(httpRegexParamJson, params) {
// If the parameters passed like "name=value", it then uses form type.
req.Header.Set(httpHeaderContentType, httpHeaderContentTypeForm)
}
}
if v, ok := c.header[httpHeaderContentType]; ok {
// Custom Content-Type.
req.Header.Set(httpHeaderContentType, v)
} else if len(paramBytes) > 0 {
if (paramBytes[0] == '[' || paramBytes[0] == '{') && json.Valid(paramBytes) {
// Auto-detecting and setting the post content format: JSON.
req.Header.Set(httpHeaderContentType, httpHeaderContentTypeJson)
} else if gregex.IsMatchString(httpRegexParamJson, params) {
// If the parameters passed like "name=value", it then uses form type.
req.Header.Set(httpHeaderContentType, httpHeaderContentTypeForm)
}
}
}

View File

@ -218,14 +218,8 @@ func (ct *clientTracerTracing) WroteRequest(info httptrace.WroteRequestInfo) {
ct.span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, info.Err))
}
reqBodyContent, err := gtrace.SafeContentForHttp(ct.requestBody, ct.request.Header)
if err != nil {
ct.span.SetStatus(codes.Error, fmt.Sprintf(`converting safe content failed: %s`, err.Error()))
}
ct.span.AddEvent(tracingEventHttpRequest, trace.WithAttributes(
attribute.String(tracingEventHttpRequestHeaders, gconv.String(ct.headers)),
attribute.String(tracingEventHttpRequestBaggage, gtrace.GetBaggageMap(ct.Context).String()),
attribute.String(tracingEventHttpRequestBody, reqBodyContent),
))
}

View File

@ -0,0 +1,82 @@
// 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 gclient_test
import (
"fmt"
"strings"
"testing"
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/gclient"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/util/guid"
)
func Test_Issue3748(t *testing.T) {
s := g.Server(guid.S())
s.BindHandler("/", func(r *ghttp.Request) {
r.Response.Write(
r.GetBody(),
)
})
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
clientHost := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
client := gclient.New()
client.SetHeader("Content-Type", "application/json")
data := map[string]interface{}{
"name": "@file:",
"value": "json",
}
client.SetPrefix(clientHost)
content := client.PostContent(ctx, "/", data)
t.Assert(content, `{"name":"@file:","value":"json"}`)
})
gtest.C(t, func(t *gtest.T) {
client := gclient.New()
client.SetHeader("Content-Type", "application/xml")
data := map[string]interface{}{
"name": "@file:",
"value": "xml",
}
client.SetPrefix(clientHost)
content := client.PostContent(ctx, "/", data)
t.Assert(content, `<doc><name>@file:</name><value>xml</value></doc>`)
})
gtest.C(t, func(t *gtest.T) {
client := gclient.New()
client.SetHeader("Content-Type", "application/x-www-form-urlencoded")
data := map[string]interface{}{
"name": "@file:",
"value": "x-www-form-urlencoded",
}
client.SetPrefix(clientHost)
content := client.PostContent(ctx, "/", data)
t.Assert(strings.Contains(content, `Content-Disposition: form-data; name="value"`), true)
t.Assert(strings.Contains(content, `Content-Disposition: form-data; name="name"`), true)
t.Assert(strings.Contains(content, "\r\n@file:"), true)
t.Assert(strings.Contains(content, "\r\nx-www-form-urlencoded"), true)
})
gtest.C(t, func(t *gtest.T) {
client := gclient.New()
data := "@file:"
client.SetPrefix(clientHost)
_, err := client.Post(ctx, "/", data)
t.AssertNil(err)
})
}

View File

@ -7,6 +7,7 @@
package ghttp
import (
"mime"
"net/http"
"github.com/gogf/gf/v2/errors/gcode"
@ -20,6 +21,17 @@ type DefaultHandlerResponse struct {
Data interface{} `json:"data" dc:"Result data for certain request according API definition"`
}
const (
contentTypeEventStream = "text/event-stream"
contentTypeOctetStream = "application/octet-stream"
contentTypeMixedReplace = "multipart/x-mixed-replace"
)
var (
// streamContentType is the content types for stream response.
streamContentType = []string{contentTypeEventStream, contentTypeOctetStream, contentTypeMixedReplace}
)
// MiddlewareHandlerResponse is the default middleware handling handler response object and its error.
func MiddlewareHandlerResponse(r *Request) {
r.Middleware.Next()
@ -29,6 +41,14 @@ func MiddlewareHandlerResponse(r *Request) {
return
}
// It does not output common response content if it is stream response.
mediaType, _, _ := mime.ParseMediaType(r.Response.Header().Get("Content-Type"))
for _, ct := range streamContentType {
if mediaType == ct {
return
}
}
var (
msg string
err = r.GetError()

View File

@ -9,7 +9,6 @@ package ghttp
import (
"context"
"fmt"
"io"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
@ -18,9 +17,7 @@ import (
"go.opentelemetry.io/otel/trace"
"github.com/gogf/gf/v2"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/httputil"
"github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/net/gtrace"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/util/gconv"
@ -31,10 +28,8 @@ const (
tracingEventHttpRequest = "http.request"
tracingEventHttpRequestHeaders = "http.request.headers"
tracingEventHttpRequestBaggage = "http.request.baggage"
tracingEventHttpRequestBody = "http.request.body"
tracingEventHttpResponse = "http.response"
tracingEventHttpResponseHeaders = "http.response.headers"
tracingEventHttpResponseBody = "http.response.body"
tracingEventHttpRequestUrl = "http.request.url"
tracingMiddlewareHandled gctx.StrKey = `MiddlewareServerTracingHandled`
)
@ -80,24 +75,10 @@ func internalMiddlewareServerTracing(r *Request) {
return
}
// Request content logging.
reqBodyContentBytes, err := io.ReadAll(r.Body)
if err != nil {
r.SetError(gerror.Wrap(err, `read request body failed`))
span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, err))
return
}
r.Body = utils.NewReadCloser(reqBodyContentBytes, false)
reqBodyContent, err := gtrace.SafeContentForHttp(reqBodyContentBytes, r.Header)
if err != nil {
span.SetStatus(codes.Error, fmt.Sprintf(`converting safe content failed: %s`, err.Error()))
}
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, reqBodyContent),
))
// Continue executing.
@ -109,18 +90,14 @@ func internalMiddlewareServerTracing(r *Request) {
}
// Error logging.
if err = r.GetError(); err != nil {
if err := r.GetError(); err != nil {
span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, err))
}
// Response content logging.
resBodyContent, err := gtrace.SafeContentForHttp(r.Response.Buffer(), r.Response.Header())
if err != nil {
span.SetStatus(codes.Error, fmt.Sprintf(`converting safe content failed: %s`, err.Error()))
}
span.AddEvent(tracingEventHttpResponse, trace.WithAttributes(
attribute.String(tracingEventHttpResponseHeaders, gconv.String(httputil.HeaderToMap(r.Response.Header()))),
attribute.String(tracingEventHttpResponseBody, resBodyContent),
attribute.String(
tracingEventHttpResponseHeaders,
gconv.String(httputil.HeaderToMap(r.Response.Header())),
),
))
}

View File

@ -247,7 +247,13 @@ func (s *Server) Start() error {
// If this is a child process, it then notifies its parent exit.
if gproc.IsChild() {
gtimer.SetTimeout(ctx, time.Duration(s.config.GracefulTimeout)*time.Second, func(ctx context.Context) {
var gracefulTimeout = time.Duration(s.config.GracefulTimeout) * time.Second
gtimer.SetTimeout(ctx, gracefulTimeout, func(ctx context.Context) {
intlog.Printf(
ctx,
`pid[%d]: notice parent server graceful shuttingdown, ppid: %d`,
gproc.Pid(), gproc.PPid(),
)
if err := gproc.Send(gproc.PPid(), []byte("exit"), adminGProcCommGroup); err != nil {
intlog.Errorf(ctx, `server error in process communication: %+v`, err)
}

View File

@ -36,8 +36,14 @@ func (p *utilAdmin) Index(r *Request) {
<body>
<p>Pid: {{.pid}}</p>
<p>File Path: {{.path}}</p>
<p><a href="{{$.uri}}/restart">Restart</a></p>
<p><a href="{{$.uri}}/shutdown">Shutdown</a></p>
<p>
<a href="{{$.uri}}/restart">Restart</a>
please make sure it is running using standalone binary not from IDE or "go run"
</p>
<p>
<a href="{{$.uri}}/shutdown">Shutdown</a>
graceful shutdown the server
</p>
</body>
</html>
`, data)
@ -89,7 +95,7 @@ func (s *Server) Shutdown() error {
// Only shut down current servers.
// It may have multiple underlying http servers.
for _, v := range s.servers {
v.close(ctx)
v.shutdown(ctx)
}
return nil
}

View File

@ -21,6 +21,7 @@ import (
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/intlog"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/glog"
"github.com/gogf/gf/v2/os/gproc"
"github.com/gogf/gf/v2/os/gtime"
@ -55,7 +56,10 @@ var (
// The optional parameter `newExeFilePath` specifies the new binary file for creating process.
func RestartAllServer(ctx context.Context, newExeFilePath string) error {
if !gracefulEnabled {
return gerror.NewCode(gcode.CodeInvalidOperation, "graceful reload feature is disabled")
return gerror.NewCode(
gcode.CodeInvalidOperation,
"graceful reload feature is disabled",
)
}
serverActionLocker.Lock()
defer serverActionLocker.Unlock()
@ -115,13 +119,16 @@ func checkActionFrequency() error {
// forkReloadProcess creates a new child process and copies the fd to child process.
func forkReloadProcess(ctx context.Context, newExeFilePath ...string) error {
var (
path = os.Args[0]
binaryPath = os.Args[0]
)
if len(newExeFilePath) > 0 && newExeFilePath[0] != "" {
path = newExeFilePath[0]
binaryPath = newExeFilePath[0]
}
if !gfile.Exists(binaryPath) {
return gerror.Newf(`binary file path "%s" does not exist`, binaryPath)
}
var (
p = gproc.NewProcess(path, os.Args[1:], os.Environ())
p = gproc.NewProcess(binaryPath, os.Args[1:], os.Environ())
sfm = getServerFdMap()
)
for name, m := range sfm {
@ -145,9 +152,9 @@ func forkReloadProcess(ctx context.Context, newExeFilePath ...string) error {
buffer, _ := gjson.Encode(sfm)
p.Env = append(p.Env, adminActionReloadEnvKey+"="+string(buffer))
if _, err := p.Start(ctx); err != nil {
glog.Errorf(
intlog.Errorf(
ctx,
"%d: fork process failed, error:%s, %s",
"%d: fork process failed, error: %s, %s",
gproc.Pid(), err.Error(), string(buffer),
)
return err
@ -254,7 +261,7 @@ func shutdownWebServersGracefully(ctx context.Context, signal os.Signal) {
gproc.Pid(), signal.String(),
)
} else {
glog.Printf(ctx, "%d: server gracefully shutting down by api", gproc.Pid())
glog.Printf(ctx, "pid[%d]: server gracefully shutting down by api", gproc.Pid())
}
serverMapping.RLockFunc(func(m map[string]interface{}) {
for _, v := range m {

View File

@ -230,6 +230,19 @@ type ServerConfig struct {
SwaggerPath string `json:"swaggerPath"` // SwaggerPath specifies the swagger UI path for route registering.
SwaggerUITemplate string `json:"swaggerUITemplate"` // SwaggerUITemplate specifies the swagger UI custom template
// ======================================================================================================
// Graceful reload & shutdown.
// ======================================================================================================
// Graceful enables graceful reload feature for all servers of the process.
Graceful bool `json:"graceful"`
// GracefulTimeout set the maximum survival time (seconds) of the parent process.
GracefulTimeout int `json:"gracefulTimeout"`
// GracefulShutdownTimeout set the maximum survival time (seconds) before stopping the server.
GracefulShutdownTimeout int `json:"gracefulShutdownTimeout"`
// ======================================================================================================
// Other.
// ======================================================================================================
@ -254,15 +267,6 @@ type ServerConfig struct {
// DumpRouterMap specifies whether automatically dumps router map when server starts.
DumpRouterMap bool `json:"dumpRouterMap"`
// Graceful enables graceful reload feature for all servers of the process.
Graceful bool `json:"graceful"`
// GracefulTimeout set the maximum survival time (seconds) of the parent process.
GracefulTimeout uint8 `json:"gracefulTimeout"`
// GracefulShutdownTimeout set the maximum survival time (seconds) before stopping the server.
GracefulShutdownTimeout uint8 `json:"gracefulShutdownTimeout"`
}
// NewConfig creates and returns a ServerConfig object with default configurations.

View File

@ -0,0 +1,23 @@
// 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 ghttp
// SetSwaggerPath sets the SwaggerPath for server.
func (s *Server) SetSwaggerPath(path string) {
s.config.SwaggerPath = path
}
// SetSwaggerUITemplate sets the Swagger template for server.
func (s *Server) SetSwaggerUITemplate(swaggerUITemplate string) {
s.config.SwaggerUITemplate = swaggerUITemplate
}
// SetOpenApiPath sets the OpenApiPath for server.
// For example: SetOpenApiPath("/api.json")
func (s *Server) SetOpenApiPath(path string) {
s.config.OpenApiPath = path
}

View File

@ -27,17 +27,34 @@ func (s *Server) SetFormParsingMemory(maxMemory int64) {
s.config.FormParsingMemory = maxMemory
}
// SetSwaggerPath sets the SwaggerPath for server.
func (s *Server) SetSwaggerPath(path string) {
s.config.SwaggerPath = path
// SetGraceful sets the Graceful for server.
func (s *Server) SetGraceful(graceful bool) {
s.config.Graceful = graceful
// note: global setting.
gracefulEnabled = graceful
}
// SetSwaggerUITemplate sets the Swagger template for server.
func (s *Server) SetSwaggerUITemplate(swaggerUITemplate string) {
s.config.SwaggerUITemplate = swaggerUITemplate
// GetGraceful returns the Graceful for server.
func (s *Server) GetGraceful() bool {
return s.config.Graceful
}
// SetOpenApiPath sets the OpenApiPath for server.
func (s *Server) SetOpenApiPath(path string) {
s.config.OpenApiPath = path
// SetGracefulTimeout sets the GracefulTimeout for server.
func (s *Server) SetGracefulTimeout(gracefulTimeout int) {
s.config.GracefulTimeout = gracefulTimeout
}
// GetGracefulTimeout returns the GracefulTimeout for server.
func (s *Server) GetGracefulTimeout() int {
return s.config.GracefulTimeout
}
// SetGracefulShutdownTimeout sets the GracefulShutdownTimeout for server.
func (s *Server) SetGracefulShutdownTimeout(gracefulShutdownTimeout int) {
s.config.GracefulShutdownTimeout = gracefulShutdownTimeout
}
// GetGracefulShutdownTimeout returns the GracefulShutdownTimeout for server.
func (s *Server) GetGracefulShutdownTimeout() int {
return s.config.GracefulShutdownTimeout
}

View File

@ -259,6 +259,7 @@ func (s *gracefulServer) getRawListener() net.Listener {
}
// close shuts down the server forcibly.
// for graceful shutdown, please use gracefulServer.shutdown.
func (s *gracefulServer) close(ctx context.Context) {
if s.status.Val() == ServerStatusStopped {
return

View File

@ -163,3 +163,42 @@ func Test_ClientMaxBodySize_File(t *testing.T) {
)
})
}
func Test_Config_Graceful(t *testing.T) {
var (
defaultConfig = ghttp.NewConfig()
expect = true
)
gtest.C(t, func(t *gtest.T) {
s := g.Server(guid.S())
t.Assert(s.GetGraceful(), defaultConfig.Graceful)
s.SetGraceful(expect)
t.Assert(s.GetGraceful(), expect)
})
}
func Test_Config_GracefulTimeout(t *testing.T) {
var (
defaultConfig = ghttp.NewConfig()
expect = 3
)
gtest.C(t, func(t *gtest.T) {
s := g.Server(guid.S())
t.Assert(s.GetGracefulTimeout(), defaultConfig.GracefulTimeout)
s.SetGracefulTimeout(expect)
t.Assert(s.GetGracefulTimeout(), expect)
})
}
func Test_Config_GracefulShutdownTimeout(t *testing.T) {
var (
defaultConfig = ghttp.NewConfig()
expect = 10
)
gtest.C(t, func(t *gtest.T) {
s := g.Server(guid.S())
t.Assert(s.GetGracefulShutdownTimeout(), defaultConfig.GracefulShutdownTimeout)
s.SetGracefulShutdownTimeout(expect)
t.Assert(s.GetGracefulShutdownTimeout(), expect)
})
}

View File

@ -772,6 +772,49 @@ func Test_MiddlewareHandlerGzipResponse(t *testing.T) {
})
}
func Test_MiddlewareHandlerStreamResponse(t *testing.T) {
s := g.Server(guid.S())
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse)
group.GET("/stream/event", func(r *ghttp.Request) {
r.Response.Header().Set("Content-Type", "text/event-stream")
})
group.GET("/stream/octet", func(r *ghttp.Request) {
r.Response.Header().Set("Content-Type", "application/octet-stream")
})
group.GET("/stream/mixed", func(r *ghttp.Request) {
r.Response.Header().Set("Content-Type", "multipart/x-mixed-replace")
})
})
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
client := g.Client()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
rsp, err := client.Get(ctx, "/stream/event")
t.AssertNil(err)
t.Assert(rsp.StatusCode, http.StatusOK)
t.Assert(rsp.ReadAllString(), "")
rsp, err = client.Get(ctx, "/stream/octet")
t.AssertNil(err)
t.Assert(rsp.StatusCode, http.StatusOK)
t.Assert(rsp.ReadAllString(), "")
rsp, err = client.Get(ctx, "/stream/mixed")
t.AssertNil(err)
t.Assert(rsp.StatusCode, http.StatusOK)
t.Assert(rsp.ReadAllString(), "")
})
}
type testTracerProvider struct{}
var _ trace.TracerProvider = &testTracerProvider{}

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