mirror of
https://gitee.com/johng/gf
synced 2026-06-09 02:57:43 +08:00
Compare commits
25 Commits
v2.7.3
...
contrib/dr
| Author | SHA1 | Date | |
|---|---|---|---|
| 79451e4624 | |||
| a716c6bfab | |||
| 5aa321dbde | |||
| 3f2b1cb329 | |||
| ab2c3b02ac | |||
| 76783fd72b | |||
| a1ce97ec9b | |||
| c13004e230 | |||
| 9af8393758 | |||
| e15b543a5b | |||
| 8a1c97f518 | |||
| d8e3e9d713 | |||
| 777c2e7117 | |||
| c4327f62e7 | |||
| fd33dcb97b | |||
| d8b06d056e | |||
| e186eab1a5 | |||
| 6a99931798 | |||
| 4ee5bf5c45 | |||
| e4669387b5 | |||
| 0e471eab38 | |||
| 3d63ebfe81 | |||
| 9b318bb57f | |||
| 6b3fb607cf | |||
| bb9a3b83eb |
13
.github/workflows/ci-main.sh
vendored
13
.github/workflows/ci-main.sh
vendored
@ -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
|
||||
|
||||
41
.github/workflows/ci-main.yml
vendored
41
.github/workflows/ci-main.yml
vendored
@ -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:
|
||||
|
||||
2
.github/workflows/ci-sub.yml
vendored
2
.github/workflows/ci-sub.yml
vendored
@ -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:
|
||||
|
||||
4
.github/workflows/golangci-lint.yml
vendored
4
.github/workflows/golangci-lint.yml
vendored
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.")
|
||||
|
||||
@ -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]))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -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{}
|
||||
|
||||
|
||||
@ -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),
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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,
|
||||
)
|
||||
|
||||
@ -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{
|
||||
|
||||
85
cmd/gf/internal/cmd/testdata/issue/3749/dao/internal/table_user.go
vendored
Normal file
85
cmd/gf/internal/cmd/testdata/issue/3749/dao/internal/table_user.go
vendored
Normal 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)
|
||||
}
|
||||
27
cmd/gf/internal/cmd/testdata/issue/3749/dao/table_user.go
vendored
Normal file
27
cmd/gf/internal/cmd/testdata/issue/3749/dao/table_user.go
vendored
Normal 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.
|
||||
22
cmd/gf/internal/cmd/testdata/issue/3749/model/do/table_user.go
vendored
Normal file
22
cmd/gf/internal/cmd/testdata/issue/3749/model/do/table_user.go
vendored
Normal 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
|
||||
}
|
||||
20
cmd/gf/internal/cmd/testdata/issue/3749/model/entity/table_user.go
vendored
Normal file
20
cmd/gf/internal/cmd/testdata/issue/3749/model/entity/table_user.go
vendored
Normal 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
|
||||
}
|
||||
10
cmd/gf/internal/cmd/testdata/issue/3749/user.tpl.sql
vendored
Normal file
10
cmd/gf/internal/cmd/testdata/issue/3749/user.tpl.sql
vendored
Normal 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;
|
||||
@ -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)
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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(), "")
|
||||
})
|
||||
}
|
||||
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
9
contrib/drivers/mysql/testdata/date_time_example.sql
vendored
Normal file
9
contrib/drivers/mysql/testdata/date_time_example.sql
vendored
Normal 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;
|
||||
8
contrib/drivers/mysql/testdata/issue3754.sql
vendored
Normal file
8
contrib/drivers/mysql/testdata/issue3754.sql
vendored
Normal 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;
|
||||
@ -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
|
||||
)
|
||||
|
||||
|
||||
12
contrib/drivers/oracle/oracle_order.go
Normal file
12
contrib/drivers/oracle/oracle_order.go
Normal 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()"
|
||||
}
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
|
||||
12
contrib/drivers/pgsql/pgsql_order.go
Normal file
12
contrib/drivers/pgsql/pgsql_order.go
Normal 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()"
|
||||
}
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
|
||||
12
contrib/drivers/sqlite/sqlite_order.go
Normal file
12
contrib/drivers/sqlite/sqlite_order.go
Normal 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()"
|
||||
}
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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`))
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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 = ""
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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.
|
||||
|
||||
26
debug/gdebug/gdebug_z_unit_internal_test.go
Normal file
26
debug/gdebug/gdebug_z_unit_internal_test.go
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 (
|
||||
|
||||
@ -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 '&' will be re-escaped as '&amp;'.
|
||||
//
|
||||
/*
|
||||
The values are:
|
||||
" "
|
||||
' '
|
||||
< <
|
||||
> >
|
||||
& &
|
||||
*/
|
||||
//
|
||||
// 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) {
|
||||
|
||||
@ -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 & I love coding.</Bio><Email>john.doe@example.com</Email><Name><>&'"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)
|
||||
})
|
||||
}
|
||||
|
||||
@ -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.
|
||||
)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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{
|
||||
|
||||
@ -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] == '.' {
|
||||
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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),
|
||||
))
|
||||
}
|
||||
|
||||
82
net/gclient/gclient_z_unit_issue_test.go
Normal file
82
net/gclient/gclient_z_unit_issue_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
@ -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()
|
||||
|
||||
@ -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())),
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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.
|
||||
|
||||
23
net/ghttp/ghttp_server_config_api.go
Normal file
23
net/ghttp/ghttp_server_config_api.go
Normal 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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@ -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
Reference in New Issue
Block a user