mirror of
https://gitee.com/johng/gf
synced 2026-06-09 02:57:43 +08:00
Compare commits
19 Commits
contrib/dr
...
v2.3.2
| Author | SHA1 | Date | |
|---|---|---|---|
| ad737ded3c | |||
| ac6b0c0980 | |||
| b69e0ff9f7 | |||
| 0361f9f7de | |||
| 005668aca8 | |||
| 013f8b216a | |||
| 8ecfa91e5d | |||
| 117fc6eda2 | |||
| d66af122c7 | |||
| a7467945ca | |||
| 81d8aa55cd | |||
| 4a6630138d | |||
| 3adae3a9aa | |||
| 21ebf48072 | |||
| 2b90bcfab6 | |||
| 5f0641f348 | |||
| 38c9cac578 | |||
| 9ba49fa454 | |||
| 39fede66e6 |
1
.github/workflows/gf.yml
vendored
1
.github/workflows/gf.yml
vendored
@ -122,6 +122,7 @@ jobs:
|
||||
- 1521:1521
|
||||
|
||||
# dm8 server
|
||||
# docker run -d --name dm -p 5236:5236 loads/dm:v8.1.2.128_ent_x86_64_ctm_pack4
|
||||
dm-server:
|
||||
image: loads/dm:v8.1.2.128_ent_x86_64_ctm_pack4
|
||||
ports:
|
||||
|
||||
2
.github/workflows/issue-check-inactive.yml
vendored
2
.github/workflows/issue-check-inactive.yml
vendored
@ -25,4 +25,4 @@ jobs:
|
||||
inactive-label: 'inactive'
|
||||
inactive-day: 7
|
||||
issue-state: open
|
||||
exclude-labels: 'bug,$exclude-empty'
|
||||
exclude-labels: 'bug,planned,$exclude-empty'
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -7,11 +7,8 @@
|
||||
.settings/
|
||||
.vscode/
|
||||
vendor/
|
||||
composer.lock
|
||||
gitpush.sh
|
||||
pkg/
|
||||
bin/
|
||||
cbuild
|
||||
**/.DS_Store
|
||||
.test/
|
||||
cmd/gf/main
|
||||
|
||||
@ -33,6 +33,7 @@ gf docker main.go
|
||||
gf docker main.go -t hub.docker.com/john/image:tag
|
||||
gf docker main.go -t hub.docker.com/john/image:tag
|
||||
gf docker main.go -p -t hub.docker.com/john/image:tag
|
||||
gf docker main.go -p -tp ["hub.docker.com/john","hub.docker.com/smith"] -tn image:tag
|
||||
`
|
||||
cDockerDc = `
|
||||
The "docker" command builds the GF project to a docker images.
|
||||
@ -45,6 +46,7 @@ You should have docker installed, and there must be a Dockerfile in the root of
|
||||
cDockerFileBrief = `file path of the Dockerfile. it's "manifest/docker/Dockerfile" in default`
|
||||
cDockerShellBrief = `path of the shell file which is executed before docker build`
|
||||
cDockerPushBrief = `auto push the docker image to docker registry if "-t" option passed`
|
||||
cDockerTagBrief = `full tag for this docker, pattern like "xxx.xxx.xxx/image:tag"`
|
||||
cDockerTagNameBrief = `tag name for this docker, pattern like "image:tag". this option is required with TagPrefixes`
|
||||
cDockerTagPrefixesBrief = `tag prefixes for this docker, which are used for docker push. this option is required with TagName`
|
||||
cDockerExtraBrief = `extra build options passed to "docker image"`
|
||||
@ -61,6 +63,7 @@ func init() {
|
||||
`cDockerShellBrief`: cDockerShellBrief,
|
||||
`cDockerBuildBrief`: cDockerBuildBrief,
|
||||
`cDockerPushBrief`: cDockerPushBrief,
|
||||
`cDockerTagBrief`: cDockerTagBrief,
|
||||
`cDockerTagNameBrief`: cDockerTagNameBrief,
|
||||
`cDockerTagPrefixesBrief`: cDockerTagPrefixesBrief,
|
||||
`cDockerExtraBrief`: cDockerExtraBrief,
|
||||
@ -73,6 +76,7 @@ type cDockerInput struct {
|
||||
File string `name:"file" short:"f" brief:"{cDockerFileBrief}" d:"manifest/docker/Dockerfile"`
|
||||
Shell string `name:"shell" short:"s" brief:"{cDockerShellBrief}" d:"manifest/docker/docker.sh"`
|
||||
Build string `name:"build" short:"b" brief:"{cDockerBuildBrief}" d:"-a amd64 -s linux"`
|
||||
Tag string `name:"tag" short:"t" brief:"{cDockerTagBrief}"`
|
||||
TagName string `name:"tagName" short:"tn" brief:"{cDockerTagNameBrief}" v:"required-with:TagPrefixes"`
|
||||
TagPrefixes []string `name:"tagPrefixes" short:"tp" brief:"{cDockerTagPrefixesBrief}" v:"required-with:TagName"`
|
||||
Push bool `name:"push" short:"p" brief:"{cDockerPushBrief}" orphan:"true"`
|
||||
@ -114,7 +118,7 @@ func (c cDocker) Index(ctx context.Context, in cDockerInput) (out *cDockerOutput
|
||||
}
|
||||
}
|
||||
if len(dockerTags) == 0 {
|
||||
dockerTags = []string{""}
|
||||
dockerTags = []string{in.Tag}
|
||||
}
|
||||
for i, dockerTag := range dockerTags {
|
||||
if i > 0 {
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gtype"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
@ -154,7 +155,7 @@ func (app *cRunApp) Run(ctx context.Context) {
|
||||
if runtime.GOOS == "windows" {
|
||||
// Special handling for windows platform.
|
||||
// DO NOT USE "cmd /c" command.
|
||||
process = gproc.NewProcess(runCommand, nil)
|
||||
process = gproc.NewProcess(outputPath, strings.Fields(app.Args))
|
||||
} else {
|
||||
process = gproc.NewProcessCmd(runCommand, nil)
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -173,28 +173,28 @@ func (a *Array) SortFunc(less func(v1, v2 interface{}) bool) *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// InsertBefore inserts the `value` to the front of `index`.
|
||||
func (a *Array) InsertBefore(index int, value interface{}) error {
|
||||
// InsertBefore inserts the `values` to the front of `index`.
|
||||
func (a *Array) InsertBefore(index int, values ...interface{}) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if index < 0 || index >= len(a.array) {
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
||||
}
|
||||
rear := append([]interface{}{}, a.array[index:]...)
|
||||
a.array = append(a.array[0:index], value)
|
||||
a.array = append(a.array[0:index], values...)
|
||||
a.array = append(a.array, rear...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertAfter inserts the `value` to the back of `index`.
|
||||
func (a *Array) InsertAfter(index int, value interface{}) error {
|
||||
// InsertAfter inserts the `values` to the back of `index`.
|
||||
func (a *Array) InsertAfter(index int, values ...interface{}) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if index < 0 || index >= len(a.array) {
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
||||
}
|
||||
rear := append([]interface{}{}, a.array[index+1:]...)
|
||||
a.array = append(a.array[0:index+1], value)
|
||||
a.array = append(a.array[0:index+1], values...)
|
||||
a.array = append(a.array, rear...)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -168,28 +168,28 @@ func (a *IntArray) SortFunc(less func(v1, v2 int) bool) *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// InsertBefore inserts the `value` to the front of `index`.
|
||||
func (a *IntArray) InsertBefore(index int, value int) error {
|
||||
// InsertBefore inserts the `values` to the front of `index`.
|
||||
func (a *IntArray) InsertBefore(index int, values ...int) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if index < 0 || index >= len(a.array) {
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
||||
}
|
||||
rear := append([]int{}, a.array[index:]...)
|
||||
a.array = append(a.array[0:index], value)
|
||||
a.array = append(a.array[0:index], values...)
|
||||
a.array = append(a.array, rear...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertAfter inserts the `value` to the back of `index`.
|
||||
func (a *IntArray) InsertAfter(index int, value int) error {
|
||||
func (a *IntArray) InsertAfter(index int, values ...int) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if index < 0 || index >= len(a.array) {
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
||||
}
|
||||
rear := append([]int{}, a.array[index+1:]...)
|
||||
a.array = append(a.array[0:index+1], value)
|
||||
a.array = append(a.array[0:index+1], values...)
|
||||
a.array = append(a.array, rear...)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -155,28 +155,28 @@ func (a *StrArray) SortFunc(less func(v1, v2 string) bool) *StrArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// InsertBefore inserts the `value` to the front of `index`.
|
||||
func (a *StrArray) InsertBefore(index int, value string) error {
|
||||
// InsertBefore inserts the `values` to the front of `index`.
|
||||
func (a *StrArray) InsertBefore(index int, values ...string) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if index < 0 || index >= len(a.array) {
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
||||
}
|
||||
rear := append([]string{}, a.array[index:]...)
|
||||
a.array = append(a.array[0:index], value)
|
||||
a.array = append(a.array[0:index], values...)
|
||||
a.array = append(a.array, rear...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertAfter inserts the `value` to the back of `index`.
|
||||
func (a *StrArray) InsertAfter(index int, value string) error {
|
||||
// InsertAfter inserts the `values` to the back of `index`.
|
||||
func (a *StrArray) InsertAfter(index int, values ...string) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if index < 0 || index >= len(a.array) {
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
||||
}
|
||||
rear := append([]string{}, a.array[index+1:]...)
|
||||
a.array = append(a.array[0:index+1], value)
|
||||
a.array = append(a.array[0:index+1], values...)
|
||||
a.array = append(a.array, rear...)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -85,7 +85,9 @@ func (q *Queue) Pop() interface{} {
|
||||
// Notice: It would notify all goroutines return immediately,
|
||||
// which are being blocked reading using Pop method.
|
||||
func (q *Queue) Close() {
|
||||
q.closed.Set(true)
|
||||
if !q.closed.Cas(false, true) {
|
||||
return
|
||||
}
|
||||
if q.events != nil {
|
||||
close(q.events)
|
||||
}
|
||||
|
||||
@ -143,11 +143,11 @@ func (d *Driver) TableFields(
|
||||
ctx context.Context, table string, schema ...string,
|
||||
) (fields map[string]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
useSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
)
|
||||
if link, err = d.SlaveLink(useSchema); err != nil {
|
||||
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
|
||||
@ -30,6 +30,10 @@ type Driver struct {
|
||||
*gdb.Core
|
||||
}
|
||||
|
||||
const (
|
||||
quoteChar = `"`
|
||||
)
|
||||
|
||||
func init() {
|
||||
var (
|
||||
err error
|
||||
@ -72,7 +76,10 @@ func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
||||
// Demo of timezone setting:
|
||||
// &loc=Asia/Shanghai
|
||||
if config.Timezone != "" {
|
||||
source = fmt.Sprintf("%s&loc%s", source, url.QueryEscape(config.Timezone))
|
||||
if strings.Contains(config.Timezone, "/") {
|
||||
config.Timezone = url.QueryEscape(config.Timezone)
|
||||
}
|
||||
source = fmt.Sprintf("%s&loc%s", source, config.Timezone)
|
||||
}
|
||||
if config.Extra != "" {
|
||||
source = fmt.Sprintf("%s&%s", source, config.Extra)
|
||||
@ -89,7 +96,7 @@ func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
||||
}
|
||||
|
||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||
return `"`, `"`
|
||||
return quoteChar, quoteChar
|
||||
}
|
||||
|
||||
func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string, err error) {
|
||||
@ -169,9 +176,9 @@ func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args [
|
||||
// There should be no need to capitalize, because it has been done from field processing before
|
||||
newSql, err = gregex.ReplaceString(`["\n\t]`, "", sql)
|
||||
newSql = gstr.ReplaceI(newSql, "GROUP_CONCAT", "WM_CONCAT")
|
||||
// g.Dump("Driver.DoFilter()::newSql", newSql)
|
||||
// gutil.Dump("Driver.DoFilter()::newSql", newSql)
|
||||
newArgs = args
|
||||
// g.Dump("Driver.DoFilter()::newArgs", newArgs)
|
||||
// gutil.Dump("Driver.DoFilter()::newArgs", newArgs)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -582,125 +582,3 @@ func Test_Empty_Slice_Argument(t *testing.T) {
|
||||
t.Assert(len(result), 0)
|
||||
})
|
||||
}
|
||||
|
||||
// func Test_GROUP_CONCAT(t *testing.T) {
|
||||
// gtest.C(t, func(t *gtest.T) {
|
||||
// type GroupIdAndUserIDsInfo struct {
|
||||
// GroupID int64
|
||||
// UserIDs string
|
||||
// }
|
||||
// result := make([]GroupIdAndUserIDsInfo, 0)
|
||||
|
||||
// model := db.Model("t_inf_group", "groupinfo").Fields("groupinfo.id as group_id", "GROUP_CONCAT(userinfo.id) as user_ids")
|
||||
// model.InnerJoin("t_lin_user_group", "lin", "groupinfo.id = lin.group_id")
|
||||
// model.InnerJoin("t_inf_user", "userinfo", "lin.user_id = userinfo.id")
|
||||
// model.Where("groupinfo.enabled", 1).Where("groupinfo.deleted", 0)
|
||||
// model.Where("userinfo.enabled", 1).Where("userinfo.deleted", 0)
|
||||
// model.Group("groupinfo.id")
|
||||
|
||||
// err := model.Scan(&result)
|
||||
// gtest.Assert(err, nil)
|
||||
// g.Dump(result)
|
||||
// })
|
||||
// }
|
||||
|
||||
// func TestGroup(t *testing.T) {
|
||||
// gtest.C(t, func(t *gtest.T) {
|
||||
// type GroupListResult struct {
|
||||
// ID int64 `json:"group_id"`
|
||||
// GroupName string `json:"group_name"`
|
||||
// CategoryName string `json:"category_name"`
|
||||
// Description string `json:"description"`
|
||||
// RoleName string `json:"role_name"`
|
||||
// UserIDs []string `json:"user_ids"`
|
||||
// Enabled int64 `json:"enabled"`
|
||||
// CreatedTime string `json:"created_time"`
|
||||
// UpdateTime string `json:"updated_time"`
|
||||
// }
|
||||
// result := make([]GroupListResult, 0)
|
||||
|
||||
// model := db.Model("t_inf_group", "groupinfo")
|
||||
// model.LeftJoin("t_inf_group_category", "category", "groupinfo.category_id=category.id and (category.enabled = 1) and (category.deleted = 0)").
|
||||
// Where("groupinfo.deleted", 0).
|
||||
// Where("groupinfo.enabled", 1)
|
||||
|
||||
// total, err := model.Count()
|
||||
// gtest.Assert(err, nil)
|
||||
// model.Fields("distinct groupinfo.id, groupinfo.group_name, groupinfo.enabled, ifnull(category.category_name,'') as category_name", "groupinfo.created_time", "groupinfo.updated_time", "groupinfo.description")
|
||||
// err = model.Order("groupinfo.updated_time desc").Page(1, 100).Scan(&result)
|
||||
// gtest.Assert(err, nil)
|
||||
// g.Dump(result)
|
||||
// g.Dump(total)
|
||||
// })
|
||||
|
||||
// gtest.C(t, func(t *gtest.T) {
|
||||
// type GroupListByUserIdResult struct {
|
||||
// ID int64 `json:"group_id"`
|
||||
// GroupName string `json:"group_name"`
|
||||
// CategoryName string `json:"category_name"`
|
||||
// RoleName string `json:"role_name"`
|
||||
// }
|
||||
// result := make([]*GroupListByUserIdResult, 0)
|
||||
|
||||
// model := db.Model("t_inf_group", "groupinfo").Fields("distinct groupinfo.id, groupinfo.group_name, groupinfo.enabled, category.category_name,groupinfo.updated_time")
|
||||
|
||||
// model.LeftJoin("t_inf_group_category", "category", "groupinfo.category_id=category.id and (category.enabled = 1) and (category.deleted = 0)")
|
||||
|
||||
// // if userId != 0 {
|
||||
// // model.InnerJoin(grouptype.TLINUSERGROUP, "ug", "groupinfo.id=ug.group_id")
|
||||
// // model.InnerJoin(grouptype.TINFUSER, "u", "u.id=ug.user_id")
|
||||
// // model.Where("u.id = ?", userId).Where("u.deleted", consts.DataDeletedFalse)
|
||||
// // }
|
||||
|
||||
// model.Where("groupinfo.enabled", 1).Where("groupinfo.deleted", 0)
|
||||
|
||||
// err := model.Order("groupinfo.updated_time desc").Scan(&result)
|
||||
// //
|
||||
// gtest.Assert(err, nil)
|
||||
// g.Dump(result)
|
||||
// })
|
||||
// gtest.C(t, func(t *gtest.T) {
|
||||
|
||||
// model := db.Model("t_inf_role", "role").Fields("role.role_name", "role.id")
|
||||
// model.RightJoin("t_lin_group_role", "link", "link.role_id=role.id")
|
||||
// // model.Where("link.group_id", gid)
|
||||
// model.Where("role.deleted", 0)
|
||||
|
||||
// record, err := model.One()
|
||||
// gtest.Assert(err, nil)
|
||||
// g.Dump(record)
|
||||
|
||||
// })
|
||||
// gtest.C(t, func(t *gtest.T) {
|
||||
// type GroupInfos struct {
|
||||
// RoleName string `orm:"role_name"`
|
||||
// RoleID int64 `orm:"id"`
|
||||
// GroupID int64 `orm:"group_id"`
|
||||
// }
|
||||
// result := make([]GroupInfos, 0)
|
||||
|
||||
// model := db.Model("t_inf_role", "role").Fields("role.id", "role.role_name", "link.group_id")
|
||||
// model.RightJoin("t_lin_group_role", "link", "link.role_id=role.id")
|
||||
// model.Where("role.enabled", 1).Where("role.deleted", 0)
|
||||
// err := model.Scan(&result)
|
||||
// gtest.Assert(err, nil)
|
||||
// g.Dump(result)
|
||||
// })
|
||||
// gtest.C(t, func(t *gtest.T) {
|
||||
// type GroupIdAndRoleNameInfo struct {
|
||||
// GroupID int64
|
||||
// RoleID int64
|
||||
// RoleName string
|
||||
// }
|
||||
// result := make([]GroupIdAndRoleNameInfo, 0)
|
||||
// model := db.Model("t_inf_group", "groupinfo").Fields("groupinfo.id as group_id", "lin.role_id", "role.role_name")
|
||||
// model.InnerJoin("t_lin_group_role", "lin", "groupinfo.id = lin.group_id")
|
||||
// model.InnerJoin("t_inf_role", "role", "lin.role_id = role.id")
|
||||
// model.Where("groupinfo.enabled", 1).Where("groupinfo.deleted", 0)
|
||||
// model.Where("role.enabled", 1).Where("role.deleted", 0)
|
||||
|
||||
// err2 := model.Scan(&result)
|
||||
// gtest.Assert(err2, nil)
|
||||
// g.Dump(result)
|
||||
// })
|
||||
// }
|
||||
|
||||
@ -5,6 +5,6 @@ go 1.15
|
||||
replace github.com/gogf/gf/v2 => ../../../
|
||||
|
||||
require (
|
||||
gitee.com/chunanyong/dm v1.8.6
|
||||
gitee.com/chunanyong/dm v1.8.11
|
||||
github.com/gogf/gf/v2 v2.0.0
|
||||
)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
gitee.com/chunanyong/dm v1.8.6 h1:5UnOCW1f2+LYiSQvuHiloS6OTMnZAtjRQ4woi9i6QY4=
|
||||
gitee.com/chunanyong/dm v1.8.6/go.mod h1:EPRJnuPFgbyOFgJ0TRYCTGzhq+ZT4wdyaj/GW/LLcNg=
|
||||
gitee.com/chunanyong/dm v1.8.11 h1:JPwiS1PqHObo4QFodruLR8WOhLP+7Y/EKGGu2BJ5SJI=
|
||||
gitee.com/chunanyong/dm v1.8.11/go.mod h1:EPRJnuPFgbyOFgJ0TRYCTGzhq+ZT4wdyaj/GW/LLcNg=
|
||||
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
|
||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E=
|
||||
|
||||
@ -33,6 +33,10 @@ type Driver struct {
|
||||
*gdb.Core
|
||||
}
|
||||
|
||||
const (
|
||||
quoteChar = `"`
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := gdb.Register(`mssql`, New()); err != nil {
|
||||
panic(err)
|
||||
@ -95,7 +99,7 @@ func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
||||
|
||||
// GetChars returns the security char for this type of database.
|
||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||
return `"`, `"`
|
||||
return quoteChar, quoteChar
|
||||
}
|
||||
|
||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||
@ -237,11 +241,11 @@ func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string,
|
||||
// Also see DriverMysql.TableFields.
|
||||
func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
useSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
)
|
||||
if link, err = d.SlaveLink(useSchema); err != nil {
|
||||
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
structureSql := fmt.Sprintf(`
|
||||
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
@ -27,6 +28,10 @@ type Driver struct {
|
||||
*gdb.Core
|
||||
}
|
||||
|
||||
const (
|
||||
quoteChar = "`"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var (
|
||||
err error
|
||||
@ -76,7 +81,10 @@ func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
||||
config.User, config.Pass, config.Protocol, config.Host, config.Port, config.Name, config.Charset,
|
||||
)
|
||||
if config.Timezone != "" {
|
||||
source = fmt.Sprintf("%s&loc=%s", source, url.QueryEscape(config.Timezone))
|
||||
if strings.Contains(config.Timezone, "/") {
|
||||
config.Timezone = url.QueryEscape(config.Timezone)
|
||||
}
|
||||
source = fmt.Sprintf("%s&loc=%s", source, config.Timezone)
|
||||
}
|
||||
if config.Extra != "" {
|
||||
source = fmt.Sprintf("%s&%s", source, config.Extra)
|
||||
@ -94,7 +102,7 @@ func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
||||
|
||||
// GetChars returns the security char for this type of database.
|
||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||
return "`", "`"
|
||||
return quoteChar, quoteChar
|
||||
}
|
||||
|
||||
// DoFilter handles the sql before posts it to database.
|
||||
@ -138,11 +146,11 @@ func (d *Driver) TableFields(
|
||||
ctx context.Context, table string, schema ...string,
|
||||
) (fields map[string]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
useSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
)
|
||||
if link, err = d.SlaveLink(useSchema); err != nil {
|
||||
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DoSelect(
|
||||
|
||||
@ -646,7 +646,7 @@ CREATE TABLE %s (
|
||||
t.Assert(one["name"], "name_1")
|
||||
|
||||
// Soft deleting.
|
||||
r, err = db.Model(table1).Delete()
|
||||
r, err = db.Model(table1).Where(1).Delete()
|
||||
t.AssertNil(err)
|
||||
n, _ = r.RowsAffected()
|
||||
t.Assert(n, 1)
|
||||
|
||||
@ -21,6 +21,7 @@ import (
|
||||
"github.com/gogf/gf/v2/util/guid"
|
||||
)
|
||||
|
||||
// https://github.com/gogf/gf/issues/1934
|
||||
func Test_Issue1934(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
@ -460,12 +461,12 @@ func Test_Issue2105(t *testing.T) {
|
||||
|
||||
// https://github.com/gogf/gf/issues/2231
|
||||
func Test_Issue2231(t *testing.T) {
|
||||
linkPattern := `(\w+):([\w\-]*):(.*?)@(\w+?)\((.+?)\)/{0,1}([^\?]*)\?{0,1}(.*)`
|
||||
link := `mysql:root:12345678@tcp(127.0.0.1:3306)/a正bc式?loc=Local&parseTime=true`
|
||||
|
||||
var (
|
||||
pattern = `(\w+):([\w\-]*):(.*?)@(\w+?)\((.+?)\)/{0,1}([^\?]*)\?{0,1}(.*)`
|
||||
link = `mysql:root:12345678@tcp(127.0.0.1:3306)/a正bc式?loc=Local&parseTime=true`
|
||||
)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
|
||||
match, err := gregex.MatchString(linkPattern, link)
|
||||
match, err := gregex.MatchString(pattern, link)
|
||||
t.AssertNil(err)
|
||||
t.Assert(match[1], "mysql")
|
||||
t.Assert(match[2], "root")
|
||||
@ -476,3 +477,190 @@ func Test_Issue2231(t *testing.T) {
|
||||
t.Assert(match[7], "loc=Local&parseTime=true")
|
||||
})
|
||||
}
|
||||
|
||||
// https://github.com/gogf/gf/issues/2339
|
||||
func Test_Issue2339(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
model1 := db.Model(table, "u1").Where("id between ? and ?", 1, 9)
|
||||
model2 := db.Model("? as u2", model1)
|
||||
model3 := db.Model("? as u3", model2)
|
||||
all2, err := model2.WhereGT("id", 6).OrderAsc("id").All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(all2), 3)
|
||||
t.Assert(all2[0]["id"], 7)
|
||||
|
||||
all3, err := model3.WhereGT("id", 7).OrderAsc("id").All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(all3), 2)
|
||||
t.Assert(all3[0]["id"], 8)
|
||||
})
|
||||
}
|
||||
|
||||
// https://github.com/gogf/gf/issues/2356
|
||||
func Test_Issue2356(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
table := "demo_" + guid.S()
|
||||
if _, err := db.Exec(ctx, fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id BIGINT(20) UNSIGNED NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, table,
|
||||
)); err != nil {
|
||||
t.AssertNil(err)
|
||||
}
|
||||
defer dropTable(table)
|
||||
|
||||
if _, err := db.Exec(ctx, fmt.Sprintf(`INSERT INTO %s (id) VALUES (18446744073709551615);`, table)); err != nil {
|
||||
t.AssertNil(err)
|
||||
}
|
||||
|
||||
one, err := db.Model(table).One()
|
||||
t.AssertNil(err)
|
||||
t.AssertEQ(one["id"].Val(), uint64(18446744073709551615))
|
||||
})
|
||||
}
|
||||
|
||||
// https://github.com/gogf/gf/issues/2338
|
||||
func Test_Issue2338(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
table1 := "demo_" + guid.S()
|
||||
table2 := "demo_" + guid.S()
|
||||
if _, err := db.Schema(TestSchema1).Exec(ctx, fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'User ID',
|
||||
nickname varchar(45) DEFAULT 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;
|
||||
`, table1,
|
||||
)); err != nil {
|
||||
t.AssertNil(err)
|
||||
}
|
||||
if _, err := db.Schema(TestSchema2).Exec(ctx, fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'User ID',
|
||||
nickname varchar(45) DEFAULT 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;
|
||||
`, table2,
|
||||
)); err != nil {
|
||||
t.AssertNil(err)
|
||||
}
|
||||
defer dropTableWithDb(db.Schema(TestSchema1), table1)
|
||||
defer dropTableWithDb(db.Schema(TestSchema2), table2)
|
||||
|
||||
var err error
|
||||
_, err = db.Schema(TestSchema1).Model(table1).Insert(g.Map{
|
||||
"id": 1,
|
||||
"nickname": "name_1",
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
_, err = db.Schema(TestSchema2).Model(table2).Insert(g.Map{
|
||||
"id": 1,
|
||||
"nickname": "name_2",
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
tableName1 := fmt.Sprintf(`%s.%s`, TestSchema1, table1)
|
||||
tableName2 := fmt.Sprintf(`%s.%s`, TestSchema2, table2)
|
||||
all, err := db.Model(tableName1).As(`a`).
|
||||
LeftJoin(tableName2+" b", `a.id=b.id`).
|
||||
Fields(`a.id`, `b.nickname`).All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(all), 1)
|
||||
t.Assert(all[0]["nickname"], "name_2")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
table1 := "demo_" + guid.S()
|
||||
table2 := "demo_" + guid.S()
|
||||
if _, err := db.Schema(TestSchema1).Exec(ctx, fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'User ID',
|
||||
nickname varchar(45) DEFAULT NULL COMMENT 'User Nickname',
|
||||
create_at datetime DEFAULT NULL COMMENT 'Created Time',
|
||||
update_at datetime DEFAULT NULL COMMENT 'Updated Time',
|
||||
deleted_at datetime DEFAULT NULL COMMENT 'Deleted Time',
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, table1,
|
||||
)); err != nil {
|
||||
t.AssertNil(err)
|
||||
}
|
||||
if _, err := db.Schema(TestSchema2).Exec(ctx, fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'User ID',
|
||||
nickname varchar(45) DEFAULT NULL COMMENT 'User Nickname',
|
||||
create_at datetime DEFAULT NULL COMMENT 'Created Time',
|
||||
update_at datetime DEFAULT NULL COMMENT 'Updated Time',
|
||||
deleted_at datetime DEFAULT NULL COMMENT 'Deleted Time',
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, table2,
|
||||
)); err != nil {
|
||||
t.AssertNil(err)
|
||||
}
|
||||
defer dropTableWithDb(db.Schema(TestSchema1), table1)
|
||||
defer dropTableWithDb(db.Schema(TestSchema2), table2)
|
||||
|
||||
var err error
|
||||
_, err = db.Schema(TestSchema1).Model(table1).Insert(g.Map{
|
||||
"id": 1,
|
||||
"nickname": "name_1",
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
_, err = db.Schema(TestSchema2).Model(table2).Insert(g.Map{
|
||||
"id": 1,
|
||||
"nickname": "name_2",
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
tableName1 := fmt.Sprintf(`%s.%s`, TestSchema1, table1)
|
||||
tableName2 := fmt.Sprintf(`%s.%s`, TestSchema2, table2)
|
||||
all, err := db.Model(tableName1).As(`a`).
|
||||
LeftJoin(tableName2+" b", `a.id=b.id`).
|
||||
Fields(`a.id`, `b.nickname`).All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(all), 1)
|
||||
t.Assert(all[0]["nickname"], "name_2")
|
||||
})
|
||||
}
|
||||
|
||||
// https://github.com/gogf/gf/issues/2427
|
||||
func Test_Issue2427(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
table := "demo_" + guid.S()
|
||||
if _, err := db.Exec(ctx, fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'User ID',
|
||||
passport varchar(45) NOT NULL COMMENT 'User Passport',
|
||||
password varchar(45) NOT NULL COMMENT 'User Password',
|
||||
nickname 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;
|
||||
`, table,
|
||||
)); err != nil {
|
||||
t.AssertNil(err)
|
||||
}
|
||||
defer dropTable(table)
|
||||
|
||||
_, err1 := db.Model(table).Delete()
|
||||
t.Assert(err1, `there should be WHERE condition statement for DELETE operation`)
|
||||
|
||||
_, err2 := db.Model(table).Where(g.Map{}).Delete()
|
||||
t.Assert(err2, `there should be WHERE condition statement for DELETE operation`)
|
||||
|
||||
_, err3 := db.Model(table).Where(1).Delete()
|
||||
t.AssertNil(err3)
|
||||
})
|
||||
}
|
||||
|
||||
@ -35,6 +35,10 @@ type Driver struct {
|
||||
*gdb.Core
|
||||
}
|
||||
|
||||
const (
|
||||
quoteChar = `"`
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := gdb.Register(`oracle`, New()); err != nil {
|
||||
panic(err)
|
||||
@ -106,7 +110,7 @@ func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
||||
|
||||
// GetChars returns the security char for this type of database.
|
||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||
return `"`, `"`
|
||||
return quoteChar, quoteChar
|
||||
}
|
||||
|
||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||
@ -217,7 +221,7 @@ func (d *Driver) TableFields(
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
useSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
structureSql = fmt.Sprintf(`
|
||||
SELECT
|
||||
COLUMN_NAME AS FIELD,
|
||||
@ -230,7 +234,7 @@ FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`,
|
||||
strings.ToUpper(table),
|
||||
)
|
||||
)
|
||||
if link, err = d.SlaveLink(useSchema); err != nil {
|
||||
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
|
||||
|
||||
@ -36,6 +36,7 @@ type Driver struct {
|
||||
const (
|
||||
internalPrimaryKeyInCtx gctx.StrKey = "primary_key"
|
||||
defaultSchema = "public"
|
||||
quoteChar = `"`
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -113,7 +114,7 @@ func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
||||
|
||||
// GetChars returns the security char for this type of database.
|
||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||
return `"`, `"`
|
||||
return quoteChar, quoteChar
|
||||
}
|
||||
|
||||
// CheckLocalTypeForField checks and returns corresponding local golang type for given db type.
|
||||
@ -270,7 +271,7 @@ func (d *Driver) TableFields(ctx context.Context, table string, schema ...string
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
useSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
structureSql = fmt.Sprintf(`
|
||||
SELECT a.attname AS field, t.typname AS type,a.attnotnull as null,
|
||||
(case when d.contype is not null then 'pri' else '' end) as key
|
||||
@ -288,7 +289,7 @@ ORDER BY a.attnum`,
|
||||
table,
|
||||
)
|
||||
)
|
||||
if link, err = d.SlaveLink(useSchema); err != nil {
|
||||
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
|
||||
|
||||
@ -33,6 +33,10 @@ type Driver struct {
|
||||
*gdb.Core
|
||||
}
|
||||
|
||||
const (
|
||||
quoteChar = "`"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := gdb.Register(`sqlite`, New()); err != nil {
|
||||
panic(err)
|
||||
@ -105,7 +109,7 @@ func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
||||
|
||||
// GetChars returns the security char for this type of database.
|
||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||
return "`", "`"
|
||||
return quoteChar, quoteChar
|
||||
}
|
||||
|
||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||
@ -141,11 +145,11 @@ func (d *Driver) TableFields(
|
||||
ctx context.Context, table string, schema ...string,
|
||||
) (fields map[string]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
useSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
)
|
||||
if link, err = d.SlaveLink(useSchema); err != nil {
|
||||
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DoSelect(ctx, link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, table))
|
||||
|
||||
@ -38,6 +38,27 @@ func Test_New(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_New_Path_With_Colon(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
|
||||
dbFilePathWithColon := gfile.Join(dbDir, "test:1")
|
||||
if err := gfile.Mkdir(dbFilePathWithColon); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
node := gdb.ConfigNode{
|
||||
Type: "sqlite",
|
||||
Link: fmt.Sprintf(`sqlite::@file(%s)`, gfile.Join(dbFilePathWithColon, "test.db")),
|
||||
Charset: "utf8",
|
||||
}
|
||||
newDb, err := gdb.New(node)
|
||||
t.AssertNil(err)
|
||||
value, err := newDb.GetValue(ctx, `select 1`)
|
||||
t.AssertNil(err)
|
||||
t.Assert(value, `1`)
|
||||
t.AssertNil(newDb.Close(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DB_Ping(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err1 := db.PingMaster()
|
||||
|
||||
@ -246,7 +246,7 @@ func Test_AdapterRedis_SetIfNotExistFunc(t *testing.T) {
|
||||
return 11, nil
|
||||
}, 0)
|
||||
t.AssertNil(err)
|
||||
t.Assert(exist, false)
|
||||
t.Assert(exist, true)
|
||||
})
|
||||
}
|
||||
|
||||
@ -257,7 +257,7 @@ func Test_AdapterRedis_SetIfNotExistFuncLock(t *testing.T) {
|
||||
return 11, nil
|
||||
}, 0)
|
||||
t.AssertNil(err)
|
||||
t.Assert(exist, false)
|
||||
t.Assert(exist, true)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -125,13 +125,21 @@ func (c *Core) Close(ctx context.Context) (err error) {
|
||||
// Master creates and returns a connection from master node if master-slave configured.
|
||||
// It returns the default connection if master-slave not configured.
|
||||
func (c *Core) Master(schema ...string) (*sql.DB, error) {
|
||||
return c.getSqlDb(true, gutil.GetOrDefaultStr(c.schema, schema...))
|
||||
var (
|
||||
usedSchema = gutil.GetOrDefaultStr(c.schema, schema...)
|
||||
charL, charR = c.db.GetChars()
|
||||
)
|
||||
return c.getSqlDb(true, gstr.Trim(usedSchema, charL+charR))
|
||||
}
|
||||
|
||||
// Slave creates and returns a connection from slave node if master-slave configured.
|
||||
// It returns the default connection if master-slave not configured.
|
||||
func (c *Core) Slave(schema ...string) (*sql.DB, error) {
|
||||
return c.getSqlDb(false, gutil.GetOrDefaultStr(c.schema, schema...))
|
||||
var (
|
||||
usedSchema = gutil.GetOrDefaultStr(c.schema, schema...)
|
||||
charL, charR = c.db.GetChars()
|
||||
)
|
||||
return c.getSqlDb(false, gstr.Trim(usedSchema, charL+charR))
|
||||
}
|
||||
|
||||
// GetAll queries and returns data records from database.
|
||||
|
||||
@ -276,7 +276,7 @@ func parseConfigNodeLink(node *ConfigNode) *ConfigNode {
|
||||
node.Pass = match[3]
|
||||
node.Protocol = match[4]
|
||||
array := gstr.Split(match[5], ":")
|
||||
if len(array) == 2 {
|
||||
if len(array) == 2 && node.Protocol != "file" {
|
||||
node.Host = array[0]
|
||||
node.Port = array[1]
|
||||
node.Name = match[6]
|
||||
|
||||
@ -10,9 +10,10 @@ package gdb
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/v2"
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
@ -278,10 +279,9 @@ func (c *Core) DoCommit(ctx context.Context, in DoCommitInput) (out DoCommitOutp
|
||||
c.writeSqlToLogger(ctx, sqlObj)
|
||||
}
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
err = gerror.NewCodef(
|
||||
err = gerror.WrapCode(
|
||||
gcode.CodeDbOperationError,
|
||||
"%s, %s",
|
||||
err.Error(),
|
||||
err,
|
||||
FormatSqlWithArgs(in.Sql, in.Args),
|
||||
)
|
||||
}
|
||||
@ -364,26 +364,18 @@ func (c *Core) RowsToResult(ctx context.Context, rows *sql.Rows) (Result, error)
|
||||
return nil, nil
|
||||
}
|
||||
// Column names and types.
|
||||
columns, err := rows.ColumnTypes()
|
||||
columnTypes, err := rows.ColumnTypes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
columnTypes = make([]string, len(columns))
|
||||
columnNames = make([]string, len(columns))
|
||||
)
|
||||
for k, v := range columns {
|
||||
columnTypes[k] = v.DatabaseTypeName()
|
||||
columnNames[k] = v.Name()
|
||||
}
|
||||
if len(columnNames) > 0 {
|
||||
if len(columnTypes) > 0 {
|
||||
if internalData := c.GetInternalCtxDataFromCtx(ctx); internalData != nil {
|
||||
internalData.FirstResultColumn = columnNames[0]
|
||||
internalData.FirstResultColumn = columnTypes[0].Name()
|
||||
}
|
||||
}
|
||||
var (
|
||||
values = make([]interface{}, len(columnNames))
|
||||
values = make([]interface{}, len(columnTypes))
|
||||
result = make(Result, 0)
|
||||
scanArgs = make([]interface{}, len(values))
|
||||
)
|
||||
@ -399,13 +391,13 @@ func (c *Core) RowsToResult(ctx context.Context, rows *sql.Rows) (Result, error)
|
||||
if value == nil {
|
||||
// DO NOT use `gvar.New(nil)` here as it creates an initialized object
|
||||
// which will cause struct converting issue.
|
||||
record[columnNames[i]] = nil
|
||||
record[columnTypes[i].Name()] = nil
|
||||
} else {
|
||||
var convertedValue interface{}
|
||||
if convertedValue, err = c.db.ConvertValueForLocal(ctx, columnTypes[i], value); err != nil {
|
||||
if convertedValue, err = c.columnValueToLocalValue(ctx, value, columnTypes[i]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
record[columnNames[i]] = gvar.New(convertedValue)
|
||||
record[columnTypes[i].Name()] = gvar.New(convertedValue)
|
||||
}
|
||||
}
|
||||
result = append(result, record)
|
||||
@ -415,3 +407,23 @@ func (c *Core) RowsToResult(ctx context.Context, rows *sql.Rows) (Result, error)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *Core) columnValueToLocalValue(ctx context.Context, value interface{}, columnType *sql.ColumnType) (interface{}, error) {
|
||||
var scanType = columnType.ScanType()
|
||||
if scanType != nil {
|
||||
// Common basic builtin types.
|
||||
switch scanType.Kind() {
|
||||
case
|
||||
reflect.Bool,
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||
reflect.Float32, reflect.Float64:
|
||||
return gconv.Convert(
|
||||
gconv.String(value),
|
||||
columnType.ScanType().String(),
|
||||
), nil
|
||||
}
|
||||
}
|
||||
// Other complex types, especially custom types.
|
||||
return c.db.ConvertValueForLocal(ctx, columnType.DatabaseTypeName(), value)
|
||||
}
|
||||
|
||||
@ -563,16 +563,16 @@ func formatWhereHolder(ctx context.Context, db DB, in formatWhereHolderInput) (n
|
||||
if i >= len(in.Args) {
|
||||
break
|
||||
}
|
||||
// ===============================================================
|
||||
// Sub query, which is always used along with a string condition.
|
||||
if model, ok := in.Args[i].(*Model); ok {
|
||||
// ===============================================================
|
||||
if subModel, ok := in.Args[i].(*Model); ok {
|
||||
index := -1
|
||||
whereStr, _ = gregex.ReplaceStringFunc(`(\?)`, whereStr, func(s string) string {
|
||||
index++
|
||||
if i+len(newArgs) == index {
|
||||
sqlWithHolder, holderArgs := model.getFormattedSqlAndArgs(
|
||||
ctx, queryTypeNormal, false,
|
||||
)
|
||||
newArgs = append(newArgs, holderArgs...)
|
||||
sqlWithHolder, holderArgs := subModel.getHolderAndArgsAsSubModel(ctx)
|
||||
in.Args = gutil.SliceInsertAfter(in.Args, i, holderArgs...)
|
||||
// Automatically adding the brackets.
|
||||
return "(" + sqlWithHolder + ")"
|
||||
}
|
||||
|
||||
@ -9,11 +9,12 @@ package gdb
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// Delete does "DELETE FROM ... " statement for the model.
|
||||
@ -30,11 +31,27 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
|
||||
}
|
||||
}()
|
||||
var (
|
||||
fieldNameDelete = m.getSoftFieldNameDeleted()
|
||||
fieldNameDelete = m.getSoftFieldNameDeleted("", m.tablesInit)
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(ctx, false, false)
|
||||
conditionStr = conditionWhere + conditionExtra
|
||||
)
|
||||
if m.unscoped {
|
||||
fieldNameDelete = ""
|
||||
}
|
||||
if !gstr.ContainsI(conditionStr, " WHERE ") || (fieldNameDelete != "" && !gstr.ContainsI(conditionStr, " AND ")) {
|
||||
intlog.Printf(
|
||||
ctx,
|
||||
`sql condition string "%s" has no WHERE for DELETE operation, fieldNameDelete: %s`,
|
||||
conditionStr, fieldNameDelete,
|
||||
)
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeMissingParameter,
|
||||
"there should be WHERE condition statement for DELETE operation",
|
||||
)
|
||||
}
|
||||
|
||||
// Soft deleting.
|
||||
if !m.unscoped && fieldNameDelete != "" {
|
||||
if fieldNameDelete != "" {
|
||||
in := &HookUpdateInput{
|
||||
internalParamHookUpdate: internalParamHookUpdate{
|
||||
internalParamHook: internalParamHook{
|
||||
@ -45,18 +62,11 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
|
||||
Model: m,
|
||||
Table: m.tables,
|
||||
Data: fmt.Sprintf(`%s=?`, m.db.GetCore().QuoteString(fieldNameDelete)),
|
||||
Condition: conditionWhere + conditionExtra,
|
||||
Condition: conditionStr,
|
||||
Args: append([]interface{}{gtime.Now().String()}, conditionArgs...),
|
||||
}
|
||||
return in.Next(ctx)
|
||||
}
|
||||
conditionStr := conditionWhere + conditionExtra
|
||||
if !gstr.ContainsI(conditionStr, " WHERE ") {
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeMissingParameter,
|
||||
"there should be WHERE condition statement for DELETE operation",
|
||||
)
|
||||
}
|
||||
|
||||
in := &HookDeleteInput{
|
||||
internalParamHookDelete: internalParamHookDelete{
|
||||
|
||||
@ -255,8 +255,8 @@ func (m *Model) doInsertWithOption(ctx context.Context, insertOption int) (resul
|
||||
var (
|
||||
list List
|
||||
nowString = gtime.Now().String()
|
||||
fieldNameCreate = m.getSoftFieldNameCreated()
|
||||
fieldNameUpdate = m.getSoftFieldNameUpdated()
|
||||
fieldNameCreate = m.getSoftFieldNameCreated("", m.tablesInit)
|
||||
fieldNameUpdate = m.getSoftFieldNameUpdated("", m.tablesInit)
|
||||
)
|
||||
newData, err := m.filterDataForInsertOrUpdate(m.data)
|
||||
if err != nil {
|
||||
|
||||
@ -497,7 +497,9 @@ func (m *Model) doGetAllBySql(ctx context.Context, queryType queryType, sql stri
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Model) getFormattedSqlAndArgs(ctx context.Context, queryType queryType, limit1 bool) (sqlWithHolder string, holderArgs []interface{}) {
|
||||
func (m *Model) getFormattedSqlAndArgs(
|
||||
ctx context.Context, queryType queryType, limit1 bool,
|
||||
) (sqlWithHolder string, holderArgs []interface{}) {
|
||||
switch queryType {
|
||||
case queryTypeCount:
|
||||
queryFields := "COUNT(1)"
|
||||
@ -539,6 +541,14 @@ func (m *Model) getFormattedSqlAndArgs(ctx context.Context, queryType queryType,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) getHolderAndArgsAsSubModel(ctx context.Context) (holder string, args []interface{}) {
|
||||
holder, args = m.getFormattedSqlAndArgs(
|
||||
ctx, queryTypeNormal, false,
|
||||
)
|
||||
args = m.mergeArguments(args)
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Model) getAutoPrefix() string {
|
||||
autoPrefix := ""
|
||||
if gstr.Contains(m.tables, " JOIN ") {
|
||||
@ -607,7 +617,9 @@ func (m *Model) getFieldsFiltered() string {
|
||||
// Note that this function does not change any attribute value of the `m`.
|
||||
//
|
||||
// The parameter `limit1` specifies whether limits querying only one record if m.limit is not set.
|
||||
func (m *Model) formatCondition(ctx context.Context, limit1 bool, isCountStatement bool) (conditionWhere string, conditionExtra string, conditionArgs []interface{}) {
|
||||
func (m *Model) formatCondition(
|
||||
ctx context.Context, limit1 bool, isCountStatement bool,
|
||||
) (conditionWhere string, conditionExtra string, conditionArgs []interface{}) {
|
||||
var autoPrefix = m.getAutoPrefix()
|
||||
// GROUP BY.
|
||||
if m.groupBy != "" {
|
||||
|
||||
@ -32,70 +32,70 @@ func (m *Model) Unscoped() *Model {
|
||||
// getSoftFieldNameCreate checks and returns the field name for record creating time.
|
||||
// If there's no field name for storing creating time, it returns an empty string.
|
||||
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
|
||||
func (m *Model) getSoftFieldNameCreated(table ...string) string {
|
||||
func (m *Model) getSoftFieldNameCreated(schema string, table string) string {
|
||||
// It checks whether this feature disabled.
|
||||
if m.db.GetConfig().TimeMaintainDisabled {
|
||||
return ""
|
||||
}
|
||||
tableName := ""
|
||||
if len(table) > 0 {
|
||||
tableName = table[0]
|
||||
if table != "" {
|
||||
tableName = table
|
||||
} else {
|
||||
tableName = m.tablesInit
|
||||
}
|
||||
config := m.db.GetConfig()
|
||||
if config.CreatedAt != "" {
|
||||
return m.getSoftFieldName(tableName, []string{config.CreatedAt})
|
||||
return m.getSoftFieldName(schema, tableName, []string{config.CreatedAt})
|
||||
}
|
||||
return m.getSoftFieldName(tableName, createdFiledNames)
|
||||
return m.getSoftFieldName(schema, tableName, createdFiledNames)
|
||||
}
|
||||
|
||||
// getSoftFieldNameUpdate checks and returns the field name for record updating time.
|
||||
// If there's no field name for storing updating time, it returns an empty string.
|
||||
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
|
||||
func (m *Model) getSoftFieldNameUpdated(table ...string) (field string) {
|
||||
func (m *Model) getSoftFieldNameUpdated(schema string, table string) (field string) {
|
||||
// It checks whether this feature disabled.
|
||||
if m.db.GetConfig().TimeMaintainDisabled {
|
||||
return ""
|
||||
}
|
||||
tableName := ""
|
||||
if len(table) > 0 {
|
||||
tableName = table[0]
|
||||
if table != "" {
|
||||
tableName = table
|
||||
} else {
|
||||
tableName = m.tablesInit
|
||||
}
|
||||
config := m.db.GetConfig()
|
||||
if config.UpdatedAt != "" {
|
||||
return m.getSoftFieldName(tableName, []string{config.UpdatedAt})
|
||||
return m.getSoftFieldName(schema, tableName, []string{config.UpdatedAt})
|
||||
}
|
||||
return m.getSoftFieldName(tableName, updatedFiledNames)
|
||||
return m.getSoftFieldName(schema, tableName, updatedFiledNames)
|
||||
}
|
||||
|
||||
// getSoftFieldNameDelete checks and returns the field name for record deleting time.
|
||||
// If there's no field name for storing deleting time, it returns an empty string.
|
||||
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
|
||||
func (m *Model) getSoftFieldNameDeleted(table ...string) (field string) {
|
||||
func (m *Model) getSoftFieldNameDeleted(schema string, table string) (field string) {
|
||||
// It checks whether this feature disabled.
|
||||
if m.db.GetConfig().TimeMaintainDisabled {
|
||||
return ""
|
||||
}
|
||||
tableName := ""
|
||||
if len(table) > 0 {
|
||||
tableName = table[0]
|
||||
if table != "" {
|
||||
tableName = table
|
||||
} else {
|
||||
tableName = m.tablesInit
|
||||
}
|
||||
config := m.db.GetConfig()
|
||||
if config.DeletedAt != "" {
|
||||
return m.getSoftFieldName(tableName, []string{config.DeletedAt})
|
||||
return m.getSoftFieldName(schema, tableName, []string{config.DeletedAt})
|
||||
}
|
||||
return m.getSoftFieldName(tableName, deletedFiledNames)
|
||||
return m.getSoftFieldName(schema, tableName, deletedFiledNames)
|
||||
}
|
||||
|
||||
// getSoftFieldName retrieves and returns the field name of the table for possible key.
|
||||
func (m *Model) getSoftFieldName(table string, keys []string) (field string) {
|
||||
func (m *Model) getSoftFieldName(schema string, table string, keys []string) (field string) {
|
||||
// Ignore the error from TableFields.
|
||||
fieldsMap, _ := m.TableFields(table)
|
||||
fieldsMap, _ := m.TableFields(table, schema)
|
||||
if len(fieldsMap) > 0 {
|
||||
for _, key := range keys {
|
||||
field, _ = gutil.MapPossibleItemByKey(
|
||||
@ -141,26 +141,33 @@ func (m *Model) getConditionForSoftDeleting() string {
|
||||
return conditionArray.Join(" AND ")
|
||||
}
|
||||
// Only one table.
|
||||
if fieldName := m.getSoftFieldNameDeleted(); fieldName != "" {
|
||||
if fieldName := m.getSoftFieldNameDeleted("", m.tablesInit); fieldName != "" {
|
||||
return fmt.Sprintf(`%s IS NULL`, m.db.GetCore().QuoteWord(fieldName))
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// getConditionOfTableStringForSoftDeleting does something as its name describes.
|
||||
// Examples for `s`:
|
||||
// - `test`.`demo` as b
|
||||
// - `test`.`demo` b
|
||||
// - `demo`
|
||||
// - demo
|
||||
func (m *Model) getConditionOfTableStringForSoftDeleting(s string) string {
|
||||
var (
|
||||
field string
|
||||
table string
|
||||
schema string
|
||||
array1 = gstr.SplitAndTrim(s, " ")
|
||||
array2 = gstr.SplitAndTrim(array1[0], ".")
|
||||
)
|
||||
if len(array2) >= 2 {
|
||||
table = array2[1]
|
||||
schema = array2[0]
|
||||
} else {
|
||||
table = array2[0]
|
||||
}
|
||||
field = m.getSoftFieldNameDeleted(table)
|
||||
field = m.getSoftFieldNameDeleted(schema, table)
|
||||
if field == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ package gdb
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
@ -46,9 +47,14 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
var (
|
||||
updateData = m.data
|
||||
reflectInfo = reflection.OriginTypeAndKind(updateData)
|
||||
fieldNameUpdate = m.getSoftFieldNameUpdated()
|
||||
fieldNameUpdate = m.getSoftFieldNameUpdated("", m.tablesInit)
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(ctx, false, false)
|
||||
conditionStr = conditionWhere + conditionExtra
|
||||
)
|
||||
if m.unscoped {
|
||||
fieldNameUpdate = ""
|
||||
}
|
||||
|
||||
switch reflectInfo.OriginKind {
|
||||
case reflect.Map, reflect.Struct:
|
||||
var dataMap map[string]interface{}
|
||||
@ -57,7 +63,7 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
return nil, err
|
||||
}
|
||||
// Automatically update the record updating time.
|
||||
if !m.unscoped && fieldNameUpdate != "" {
|
||||
if fieldNameUpdate != "" {
|
||||
dataMap[fieldNameUpdate] = gtime.Now().String()
|
||||
}
|
||||
updateData = dataMap
|
||||
@ -65,7 +71,7 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
default:
|
||||
updates := gconv.String(m.data)
|
||||
// Automatically update the record updating time.
|
||||
if !m.unscoped && fieldNameUpdate != "" {
|
||||
if fieldNameUpdate != "" {
|
||||
if fieldNameUpdate != "" && !gstr.Contains(updates, fieldNameUpdate) {
|
||||
updates += fmt.Sprintf(`,%s='%s'`, fieldNameUpdate, gtime.Now().String())
|
||||
}
|
||||
@ -76,9 +82,17 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conditionStr := conditionWhere + conditionExtra
|
||||
|
||||
if !gstr.ContainsI(conditionStr, " WHERE ") {
|
||||
return nil, gerror.NewCode(gcode.CodeMissingParameter, "there should be WHERE condition statement for UPDATE operation")
|
||||
intlog.Printf(
|
||||
ctx,
|
||||
`sql condition string "%s" has no WHERE for UPDATE operation, fieldNameUpdate: %s`,
|
||||
conditionStr, fieldNameUpdate,
|
||||
)
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeMissingParameter,
|
||||
"there should be WHERE condition statement for UPDATE operation",
|
||||
)
|
||||
}
|
||||
|
||||
in := &HookUpdateInput{
|
||||
|
||||
@ -34,15 +34,15 @@ import (
|
||||
//
|
||||
// We can enable model association operations on attribute `UserDetail` and `UserScores` by:
|
||||
//
|
||||
// db.With(User{}.UserDetail).With(User{}.UserDetail).Scan(xxx)
|
||||
// db.With(User{}.UserDetail).With(User{}.UserScores).Scan(xxx)
|
||||
//
|
||||
// Or:
|
||||
//
|
||||
// db.With(UserDetail{}).With(UserDetail{}).Scan(xxx)
|
||||
// db.With(UserDetail{}).With(UserScores{}).Scan(xxx)
|
||||
//
|
||||
// Or:
|
||||
//
|
||||
// db.With(UserDetail{}, UserDetail{}).Scan(xxx)
|
||||
// db.With(UserDetail{}, UserScores{}).Scan(xxx)
|
||||
func (m *Model) With(objects ...interface{}) *Model {
|
||||
model := m.getModel()
|
||||
for _, object := range objects {
|
||||
|
||||
@ -13,7 +13,7 @@ import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
func Example_transaction() {
|
||||
func ExampleTransaction() {
|
||||
g.DB().Transaction(context.TODO(), func(ctx context.Context, tx gdb.TX) error {
|
||||
// user
|
||||
result, err := tx.Insert("user", g.Map{
|
||||
|
||||
@ -12,7 +12,7 @@ import (
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
)
|
||||
|
||||
func Example_conversionNormalFormats() {
|
||||
func ExampleConversionNormalFormats() {
|
||||
data :=
|
||||
`{
|
||||
"users" : {
|
||||
|
||||
@ -18,6 +18,7 @@ import (
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// Encode encodes `value` to an YAML format content as bytes.
|
||||
func Encode(value interface{}) (out []byte, err error) {
|
||||
if out, err = yaml.Marshal(value); err != nil {
|
||||
err = gerror.Wrap(err, `yaml.Marshal failed`)
|
||||
@ -25,6 +26,7 @@ func Encode(value interface{}) (out []byte, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// EncodeIndent encodes `value` to an YAML format content with indent as bytes.
|
||||
func EncodeIndent(value interface{}, indent string) (out []byte, err error) {
|
||||
out, err = Encode(value)
|
||||
if err != nil {
|
||||
@ -45,18 +47,20 @@ func EncodeIndent(value interface{}, indent string) (out []byte, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func Decode(value []byte) (interface{}, error) {
|
||||
// Decode parses `content` into and returns as map.
|
||||
func Decode(content []byte) (map[string]interface{}, error) {
|
||||
var (
|
||||
result map[string]interface{}
|
||||
err error
|
||||
)
|
||||
if err = yaml.Unmarshal(value, &result); err != nil {
|
||||
if err = yaml.Unmarshal(content, &result); err != nil {
|
||||
err = gerror.Wrap(err, `yaml.Unmarshal failed`)
|
||||
return nil, err
|
||||
}
|
||||
return gconv.MapDeep(result), nil
|
||||
}
|
||||
|
||||
// DecodeTo parses `content` into `result`.
|
||||
func DecodeTo(value []byte, result interface{}) (err error) {
|
||||
err = yaml.Unmarshal(value, result)
|
||||
if err != nil {
|
||||
@ -65,11 +69,12 @@ func DecodeTo(value []byte, result interface{}) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func ToJson(value []byte) (out []byte, err error) {
|
||||
// ToJson converts `content` to JSON format content.
|
||||
func ToJson(content []byte) (out []byte, err error) {
|
||||
var (
|
||||
result interface{}
|
||||
)
|
||||
if result, err = Decode(value); err != nil {
|
||||
if result, err = Decode(content); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return json.Marshal(result)
|
||||
|
||||
@ -76,9 +76,7 @@ func Test_Decode(t *testing.T) {
|
||||
result, err := gyaml.Decode([]byte(yamlStr))
|
||||
t.AssertNil(err)
|
||||
|
||||
m, ok := result.(map[string]interface{})
|
||||
t.Assert(ok, true)
|
||||
t.Assert(m, map[string]interface{}{
|
||||
t.Assert(result, map[string]interface{}{
|
||||
"url": "https://goframe.org",
|
||||
"server": g.Slice{"120.168.117.21", "120.168.117.22"},
|
||||
"pi": 3.14,
|
||||
|
||||
@ -92,7 +92,7 @@ func IsEmpty(value interface{}) bool {
|
||||
// Common interfaces checks.
|
||||
// =========================
|
||||
if f, ok := value.(iTime); ok {
|
||||
if f == nil {
|
||||
if f == (*time.Time)(nil) {
|
||||
return true
|
||||
}
|
||||
return f.IsZero()
|
||||
|
||||
@ -15,6 +15,7 @@ import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/net/ghttp/internal/response"
|
||||
"github.com/gogf/gf/v2/net/gtrace"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gres"
|
||||
@ -34,7 +35,7 @@ func newResponse(s *Server, w http.ResponseWriter) *Response {
|
||||
r := &Response{
|
||||
Server: s,
|
||||
ResponseWriter: &ResponseWriter{
|
||||
writer: w,
|
||||
writer: response.NewWriter(w),
|
||||
buffer: bytes.NewBuffer(nil),
|
||||
},
|
||||
}
|
||||
@ -152,7 +153,6 @@ func (r *Response) ClearBuffer() {
|
||||
//
|
||||
// See http.ServeContent
|
||||
func (r *Response) ServeContent(name string, modTime time.Time, content io.ReadSeeker) {
|
||||
r.wroteHeader = true
|
||||
http.ServeContent(r.Writer.RawWriter(), r.Request.Request, name, modTime, content)
|
||||
}
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ import (
|
||||
|
||||
// Write writes `content` to the response buffer.
|
||||
func (r *Response) Write(content ...interface{}) {
|
||||
if r.hijacked || len(content) == 0 {
|
||||
if r.writer.IsHijacked() || len(content) == 0 {
|
||||
return
|
||||
}
|
||||
if r.Status == 0 {
|
||||
|
||||
@ -12,15 +12,15 @@ import (
|
||||
"bytes"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/gogf/gf/v2/net/ghttp/internal/response"
|
||||
)
|
||||
|
||||
// ResponseWriter is the custom writer for http response.
|
||||
type ResponseWriter struct {
|
||||
Status int // HTTP status.
|
||||
writer http.ResponseWriter // The underlying ResponseWriter.
|
||||
buffer *bytes.Buffer // The output buffer.
|
||||
hijacked bool // Mark this request is hijacked or not.
|
||||
wroteHeader bool // Is header wrote or not, avoiding error: superfluous/multiple response.WriteHeader call.
|
||||
Status int // HTTP status.
|
||||
writer *response.Writer // The underlying ResponseWriter.
|
||||
buffer *bytes.Buffer // The output buffer.
|
||||
}
|
||||
|
||||
// RawWriter returns the underlying ResponseWriter.
|
||||
@ -46,18 +46,16 @@ func (w *ResponseWriter) WriteHeader(status int) {
|
||||
|
||||
// Hijack implements the interface function of http.Hijacker.Hijack.
|
||||
func (w *ResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
w.hijacked = true
|
||||
return w.writer.(http.Hijacker).Hijack()
|
||||
return w.writer.Hijack()
|
||||
}
|
||||
|
||||
// Flush outputs the buffer to clients and clears the buffer.
|
||||
func (w *ResponseWriter) Flush() {
|
||||
if w.hijacked {
|
||||
if w.writer.IsHijacked() {
|
||||
return
|
||||
}
|
||||
|
||||
if w.Status != 0 && !w.isHeaderWritten() {
|
||||
w.wroteHeader = true
|
||||
if w.Status != 0 && !w.writer.IsHeaderWrote() {
|
||||
w.writer.WriteHeader(w.Status)
|
||||
}
|
||||
// Default status text output.
|
||||
@ -69,14 +67,3 @@ func (w *ResponseWriter) Flush() {
|
||||
w.buffer.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
// isHeaderWrote checks and returns whether the header is written.
|
||||
func (w *ResponseWriter) isHeaderWritten() bool {
|
||||
if w.wroteHeader {
|
||||
return true
|
||||
}
|
||||
if _, ok := w.writer.Header()[responseHeaderContentLength]; ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
8
net/ghttp/internal/response/response.go
Normal file
8
net/ghttp/internal/response/response.go
Normal file
@ -0,0 +1,8 @@
|
||||
// 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 response provides wrapper for http.response.
|
||||
package response
|
||||
59
net/ghttp/internal/response/response_writer.go
Normal file
59
net/ghttp/internal/response/response_writer.go
Normal file
@ -0,0 +1,59 @@
|
||||
// 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 response
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Writer wraps http.ResponseWriter for extra features.
|
||||
type Writer struct {
|
||||
http.ResponseWriter // The underlying ResponseWriter.
|
||||
hijacked bool // Mark this request is hijacked or not.
|
||||
wroteHeader bool // Is header wrote or not, avoiding error: superfluous/multiple response.WriteHeader call.
|
||||
}
|
||||
|
||||
// NewWriter creates and returns a new Writer.
|
||||
func NewWriter(writer http.ResponseWriter) *Writer {
|
||||
return &Writer{
|
||||
ResponseWriter: writer,
|
||||
}
|
||||
}
|
||||
|
||||
// WriteHeader implements the interface of http.ResponseWriter.WriteHeader.
|
||||
func (w *Writer) WriteHeader(status int) {
|
||||
w.ResponseWriter.WriteHeader(status)
|
||||
w.wroteHeader = true
|
||||
}
|
||||
|
||||
// Hijack implements the interface function of http.Hijacker.Hijack.
|
||||
func (w *Writer) Hijack() (conn net.Conn, writer *bufio.ReadWriter, err error) {
|
||||
conn, writer, err = w.ResponseWriter.(http.Hijacker).Hijack()
|
||||
w.hijacked = true
|
||||
return
|
||||
}
|
||||
|
||||
// IsHeaderWrote returns if the header status is written.
|
||||
func (w *Writer) IsHeaderWrote() bool {
|
||||
return w.wroteHeader
|
||||
}
|
||||
|
||||
// IsHijacked returns if the connection is hijacked.
|
||||
func (w *Writer) IsHijacked() bool {
|
||||
return w.hijacked
|
||||
}
|
||||
|
||||
// Flush sends any buffered data to the client.
|
||||
func (w *Writer) Flush() {
|
||||
flusher, ok := w.ResponseWriter.(http.Flusher)
|
||||
if ok {
|
||||
flusher.Flush()
|
||||
w.wroteHeader = true
|
||||
}
|
||||
}
|
||||
@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type SchemaRefs []SchemaRef
|
||||
@ -54,13 +55,23 @@ func (oai *OpenApiV3) newSchemaRefWithGolangType(golangType reflect.Type, tagMap
|
||||
}
|
||||
schemaRef.Value = schema
|
||||
switch oaiType {
|
||||
case
|
||||
TypeInteger,
|
||||
TypeNumber,
|
||||
TypeString,
|
||||
TypeBoolean:
|
||||
// Nothing to do.
|
||||
|
||||
case TypeString:
|
||||
// Nothing to do.
|
||||
case TypeInteger:
|
||||
if schemaRef.Value.Default != nil {
|
||||
schemaRef.Value.Default = gconv.Int64(schemaRef.Value.Default)
|
||||
}
|
||||
// keep the default value as nil.
|
||||
case TypeNumber:
|
||||
if schemaRef.Value.Default != nil {
|
||||
schemaRef.Value.Default = gconv.Float64(schemaRef.Value.Default)
|
||||
}
|
||||
// keep the default value as nil.
|
||||
case TypeBoolean:
|
||||
if schemaRef.Value.Default != nil {
|
||||
schemaRef.Value.Default = gconv.Bool(schemaRef.Value.Default)
|
||||
}
|
||||
// keep the default value as nil.
|
||||
case
|
||||
TypeArray:
|
||||
subSchemaRef, err := oai.newSchemaRefWithGolangType(golangType.Elem(), nil)
|
||||
|
||||
@ -121,17 +121,17 @@ func (c *AdapterRedis) SetIfNotExist(ctx context.Context, key interface{}, value
|
||||
}
|
||||
ok, err = c.redis.SetNX(ctx, redisKey, value)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return ok, err
|
||||
}
|
||||
if ok && duration > 0 {
|
||||
// Set the expiration.
|
||||
_, err = c.redis.Expire(ctx, redisKey, int64(duration.Seconds()))
|
||||
if err != nil {
|
||||
return false, err
|
||||
return ok, err
|
||||
}
|
||||
return true, err
|
||||
return ok, err
|
||||
}
|
||||
return false, err
|
||||
return ok, err
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets `key` with result of function `f` and returns true
|
||||
|
||||
@ -14,7 +14,7 @@ import (
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
)
|
||||
|
||||
func Example_cronAddSingleton() {
|
||||
func ExampleCronAddSingleton() {
|
||||
gcron.AddSingleton(ctx, "* * * * * *", func(ctx context.Context) {
|
||||
glog.Print(context.TODO(), "doing")
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
@ -12,7 +12,7 @@ import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
func Example_context() {
|
||||
func ExampleContext() {
|
||||
ctx := context.WithValue(context.Background(), "Trace-Id", "123456789")
|
||||
g.Log().Error(ctx, "runtime error")
|
||||
|
||||
|
||||
@ -84,14 +84,14 @@ func (f *Field) Kind() reflect.Kind {
|
||||
// OriginalKind retrieves and returns the original reflect.Kind for Value of Field `f`.
|
||||
func (f *Field) OriginalKind() reflect.Kind {
|
||||
var (
|
||||
kind = f.Value.Kind()
|
||||
value = f.Value
|
||||
reflectType = f.Value.Type()
|
||||
reflectKind = reflectType.Kind()
|
||||
)
|
||||
for kind == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
kind = value.Kind()
|
||||
for reflectKind == reflect.Ptr {
|
||||
reflectType = reflectType.Elem()
|
||||
reflectKind = reflectType.Kind()
|
||||
}
|
||||
return kind
|
||||
return reflectKind
|
||||
}
|
||||
|
||||
// Fields retrieves and returns the fields of `pointer` as slice.
|
||||
|
||||
@ -13,6 +13,7 @@ import (
|
||||
"github.com/gogf/gf/v2/container/gtype"
|
||||
)
|
||||
|
||||
// New creates and returns a Timer.
|
||||
func New(options ...TimerOptions) *Timer {
|
||||
t := &Timer{
|
||||
queue: newPriorityQueue(),
|
||||
@ -98,7 +99,7 @@ func (t *Timer) AddTimes(ctx context.Context, interval time.Duration, times int,
|
||||
})
|
||||
}
|
||||
|
||||
// DelayAdd adds a timing job after delay of `interval` duration.
|
||||
// DelayAdd adds a timing job after delay of `delay` duration.
|
||||
// Also see Add.
|
||||
func (t *Timer) DelayAdd(ctx context.Context, delay time.Duration, interval time.Duration, job JobFunc) {
|
||||
t.AddOnce(ctx, delay, func(ctx context.Context) {
|
||||
@ -106,7 +107,7 @@ func (t *Timer) DelayAdd(ctx context.Context, delay time.Duration, interval time
|
||||
})
|
||||
}
|
||||
|
||||
// DelayAddEntry adds a timing job after delay of `interval` duration.
|
||||
// DelayAddEntry adds a timing job after delay of `delay` duration.
|
||||
// Also see AddEntry.
|
||||
func (t *Timer) DelayAddEntry(ctx context.Context, delay time.Duration, interval time.Duration, job JobFunc, isSingleton bool, times int, status int) {
|
||||
t.AddOnce(ctx, delay, func(ctx context.Context) {
|
||||
@ -114,7 +115,7 @@ func (t *Timer) DelayAddEntry(ctx context.Context, delay time.Duration, interval
|
||||
})
|
||||
}
|
||||
|
||||
// DelayAddSingleton adds a timing job after delay of `interval` duration.
|
||||
// DelayAddSingleton adds a timing job after delay of `delay` duration.
|
||||
// Also see AddSingleton.
|
||||
func (t *Timer) DelayAddSingleton(ctx context.Context, delay time.Duration, interval time.Duration, job JobFunc) {
|
||||
t.AddOnce(ctx, delay, func(ctx context.Context) {
|
||||
@ -122,7 +123,7 @@ func (t *Timer) DelayAddSingleton(ctx context.Context, delay time.Duration, inte
|
||||
})
|
||||
}
|
||||
|
||||
// DelayAddOnce adds a timing job after delay of `interval` duration.
|
||||
// DelayAddOnce adds a timing job after delay of `delay` duration.
|
||||
// Also see AddOnce.
|
||||
func (t *Timer) DelayAddOnce(ctx context.Context, delay time.Duration, interval time.Duration, job JobFunc) {
|
||||
t.AddOnce(ctx, delay, func(ctx context.Context) {
|
||||
@ -130,7 +131,7 @@ func (t *Timer) DelayAddOnce(ctx context.Context, delay time.Duration, interval
|
||||
})
|
||||
}
|
||||
|
||||
// DelayAddTimes adds a timing job after delay of `interval` duration.
|
||||
// DelayAddTimes adds a timing job after delay of `delay` duration.
|
||||
// Also see AddTimes.
|
||||
func (t *Timer) DelayAddTimes(ctx context.Context, delay time.Duration, interval time.Duration, times int, job JobFunc) {
|
||||
t.AddOnce(ctx, delay, func(ctx context.Context) {
|
||||
|
||||
@ -14,7 +14,7 @@ import (
|
||||
"github.com/gogf/gf/v2/os/gtimer"
|
||||
)
|
||||
|
||||
func Example_add() {
|
||||
func ExampleAdd() {
|
||||
var (
|
||||
ctx = context.Background()
|
||||
now = time.Now()
|
||||
|
||||
@ -366,15 +366,29 @@ func bindVarToStructAttr(structReflectValue reflect.Value, attrName string, valu
|
||||
if empty.IsNil(value) {
|
||||
structFieldValue.Set(reflect.Zero(structFieldValue.Type()))
|
||||
} else {
|
||||
// Special handling for certain types:
|
||||
// - Overwrite the default type converting logic of stdlib for time.Time/*time.Time.
|
||||
var structFieldTypeName = structFieldValue.Type().String()
|
||||
switch structFieldTypeName {
|
||||
case "time.Time", "*time.Time":
|
||||
doConvertWithReflectValueSet(structFieldValue, doConvertInput{
|
||||
FromValue: value,
|
||||
ToTypeName: structFieldTypeName,
|
||||
ReferValue: structFieldValue,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Common interface check.
|
||||
var ok bool
|
||||
if err, ok = bindVarToReflectValueWithInterfaceCheck(structFieldValue, value); ok {
|
||||
return err
|
||||
}
|
||||
|
||||
// Default converting.
|
||||
doConvertWithReflectValueSet(structFieldValue, doConvertInput{
|
||||
FromValue: value,
|
||||
ToTypeName: structFieldValue.Type().String(),
|
||||
ToTypeName: structFieldTypeName,
|
||||
ReferValue: structFieldValue,
|
||||
})
|
||||
}
|
||||
@ -382,7 +396,7 @@ func bindVarToStructAttr(structReflectValue reflect.Value, attrName string, valu
|
||||
}
|
||||
|
||||
// bindVarToReflectValueWithInterfaceCheck does bind using common interfaces checks.
|
||||
func bindVarToReflectValueWithInterfaceCheck(reflectValue reflect.Value, value interface{}) (err error, ok bool) {
|
||||
func bindVarToReflectValueWithInterfaceCheck(reflectValue reflect.Value, value interface{}) (error, bool) {
|
||||
var pointer interface{}
|
||||
if reflectValue.Kind() != reflect.Ptr && reflectValue.CanAddr() {
|
||||
reflectValueAddr := reflectValue.Addr()
|
||||
|
||||
@ -8,6 +8,7 @@ package gconv_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gtype"
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
@ -275,3 +276,19 @@ func Test_Issue2395(t *testing.T) {
|
||||
t.Assert(gconv.Interfaces(obj), []interface{}{obj})
|
||||
})
|
||||
}
|
||||
|
||||
// https://github.com/gogf/gf/issues/2371
|
||||
func Test_Issue2371(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
s = struct {
|
||||
Time time.Time `json:"time"`
|
||||
}{}
|
||||
jsonMap = map[string]interface{}{"time": "2022-12-15 16:11:34"}
|
||||
)
|
||||
|
||||
err := gconv.Struct(jsonMap, &s)
|
||||
t.AssertNil(err)
|
||||
t.Assert(s.Time.UTC(), `2022-12-15 08:11:34 +0000 UTC`)
|
||||
})
|
||||
}
|
||||
|
||||
@ -14,28 +14,52 @@ import (
|
||||
|
||||
// SliceCopy does a shallow copy of slice `data` for most commonly used slice type
|
||||
// []interface{}.
|
||||
func SliceCopy(data []interface{}) []interface{} {
|
||||
newData := make([]interface{}, len(data))
|
||||
copy(newData, data)
|
||||
return newData
|
||||
func SliceCopy(slice []interface{}) []interface{} {
|
||||
newSlice := make([]interface{}, len(slice))
|
||||
copy(newSlice, slice)
|
||||
return newSlice
|
||||
}
|
||||
|
||||
// SliceInsertBefore inserts the `values` to the front of `index` and returns a new slice.
|
||||
func SliceInsertBefore(slice []interface{}, index int, values ...interface{}) (newSlice []interface{}) {
|
||||
if index < 0 || index >= len(slice) {
|
||||
return slice
|
||||
}
|
||||
newSlice = make([]interface{}, len(slice)+len(values))
|
||||
copy(newSlice, slice[0:index])
|
||||
copy(newSlice[index:], values)
|
||||
copy(newSlice[index+len(values):], slice[index:])
|
||||
return
|
||||
}
|
||||
|
||||
// SliceInsertAfter inserts the `values` to the back of `index` and returns a new slice.
|
||||
func SliceInsertAfter(slice []interface{}, index int, values ...interface{}) (newSlice []interface{}) {
|
||||
if index < 0 || index >= len(slice) {
|
||||
return slice
|
||||
}
|
||||
newSlice = make([]interface{}, len(slice)+len(values))
|
||||
copy(newSlice, slice[0:index+1])
|
||||
copy(newSlice[index+1:], values)
|
||||
copy(newSlice[index+1+len(values):], slice[index+1:])
|
||||
return
|
||||
}
|
||||
|
||||
// SliceDelete deletes an element at `index` and returns the new slice.
|
||||
// It does nothing if the given `index` is invalid.
|
||||
func SliceDelete(data []interface{}, index int) (newSlice []interface{}) {
|
||||
if index < 0 || index >= len(data) {
|
||||
return data
|
||||
func SliceDelete(slice []interface{}, index int) (newSlice []interface{}) {
|
||||
if index < 0 || index >= len(slice) {
|
||||
return slice
|
||||
}
|
||||
// Determine array boundaries when deleting to improve deletion efficiency.
|
||||
if index == 0 {
|
||||
return data[1:]
|
||||
} else if index == len(data)-1 {
|
||||
return data[:index]
|
||||
return slice[1:]
|
||||
} else if index == len(slice)-1 {
|
||||
return slice[:index]
|
||||
}
|
||||
// If it is a non-boundary delete,
|
||||
// it will involve the creation of an array,
|
||||
// then the deletion is less efficient.
|
||||
return append(data[:index], data[index+1:]...)
|
||||
return append(slice[:index], slice[index+1:]...)
|
||||
}
|
||||
|
||||
// SliceToMap converts slice type variable `slice` to `map[string]interface{}`.
|
||||
|
||||
39
util/gutil/gutil_z_example_test.go
Normal file
39
util/gutil/gutil_z_example_test.go
Normal file
@ -0,0 +1,39 @@
|
||||
// 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 gutil_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
func ExampleSliceInsertBefore() {
|
||||
s1 := g.Slice{
|
||||
0, 1, 2, 3, 4,
|
||||
}
|
||||
s2 := gutil.SliceInsertBefore(s1, 1, 8, 9)
|
||||
fmt.Println(s1)
|
||||
fmt.Println(s2)
|
||||
|
||||
// Output:
|
||||
// [0 1 2 3 4]
|
||||
// [0 8 9 1 2 3 4]
|
||||
}
|
||||
|
||||
func ExampleSliceInsertAfter() {
|
||||
s1 := g.Slice{
|
||||
0, 1, 2, 3, 4,
|
||||
}
|
||||
s2 := gutil.SliceInsertAfter(s1, 1, 8, 9)
|
||||
fmt.Println(s1)
|
||||
fmt.Println(s2)
|
||||
|
||||
// Output:
|
||||
// [0 1 2 3 4]
|
||||
// [0 1 8 9 2 3 4]
|
||||
}
|
||||
@ -252,6 +252,13 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
|
||||
case reflect.Map, reflect.Struct, reflect.Slice, reflect.Array:
|
||||
// Recursively check attribute slice/map.
|
||||
_, value = gutil.MapPossibleItemByKey(inputParamMap, field.Name())
|
||||
if value == nil {
|
||||
switch field.Kind() {
|
||||
case reflect.Map, reflect.Ptr, reflect.Slice, reflect.Array:
|
||||
// Nothing to do.
|
||||
continue
|
||||
}
|
||||
}
|
||||
v.doCheckValueRecursively(ctx, doCheckValueRecursivelyInput{
|
||||
Value: value,
|
||||
Kind: field.OriginalKind(),
|
||||
|
||||
@ -14,7 +14,7 @@ import (
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
func Example_Rule_Required() {
|
||||
func ExampleRule_Required() {
|
||||
type BizReq struct {
|
||||
ID uint `v:"required"`
|
||||
Name string `v:"required"`
|
||||
@ -33,7 +33,7 @@ func Example_Rule_Required() {
|
||||
// The Name field is required
|
||||
}
|
||||
|
||||
func Example_Rule_RequiredIf() {
|
||||
func ExampleRule_RequiredIf() {
|
||||
type BizReq struct {
|
||||
ID uint `v:"required" dc:"Your ID"`
|
||||
Name string `v:"required" dc:"Your name"`
|
||||
@ -57,7 +57,7 @@ func Example_Rule_RequiredIf() {
|
||||
// The WifeName field is required
|
||||
}
|
||||
|
||||
func Example_Rule_RequiredUnless() {
|
||||
func ExampleRule_RequiredUnless() {
|
||||
type BizReq struct {
|
||||
ID uint `v:"required" dc:"Your ID"`
|
||||
Name string `v:"required" dc:"Your name"`
|
||||
@ -81,7 +81,7 @@ func Example_Rule_RequiredUnless() {
|
||||
// The WifeName field is required; The HusbandName field is required
|
||||
}
|
||||
|
||||
func Example_Rule_RequiredWith() {
|
||||
func ExampleRule_RequiredWith() {
|
||||
type BizReq struct {
|
||||
ID uint `v:"required" dc:"Your ID"`
|
||||
Name string `v:"required" dc:"Your name"`
|
||||
@ -106,7 +106,7 @@ func Example_Rule_RequiredWith() {
|
||||
// The HusbandName field is required
|
||||
}
|
||||
|
||||
func Example_Rule_RequiredWithAll() {
|
||||
func ExampleRule_RequiredWithAll() {
|
||||
type BizReq struct {
|
||||
ID uint `v:"required" dc:"Your ID"`
|
||||
Name string `v:"required" dc:"Your name"`
|
||||
@ -131,7 +131,7 @@ func Example_Rule_RequiredWithAll() {
|
||||
// The HusbandName field is required
|
||||
}
|
||||
|
||||
func Example_Rule_RequiredWithout() {
|
||||
func ExampleRule_RequiredWithout() {
|
||||
type BizReq struct {
|
||||
ID uint `v:"required" dc:"Your ID"`
|
||||
Name string `v:"required" dc:"Your name"`
|
||||
@ -155,7 +155,7 @@ func Example_Rule_RequiredWithout() {
|
||||
// The HusbandName field is required
|
||||
}
|
||||
|
||||
func Example_Rule_RequiredWithoutAll() {
|
||||
func ExampleRule_RequiredWithoutAll() {
|
||||
type BizReq struct {
|
||||
ID uint `v:"required" dc:"Your ID"`
|
||||
Name string `v:"required" dc:"Your name"`
|
||||
@ -178,7 +178,7 @@ func Example_Rule_RequiredWithoutAll() {
|
||||
// The HusbandName field is required
|
||||
}
|
||||
|
||||
func Example_Rule_Bail() {
|
||||
func ExampleRule_Bail() {
|
||||
type BizReq struct {
|
||||
Account string `v:"bail|required|length:6,16|same:QQ"`
|
||||
QQ string
|
||||
@ -202,7 +202,7 @@ func Example_Rule_Bail() {
|
||||
// The Account value `gf` length must be between 6 and 16
|
||||
}
|
||||
|
||||
func Example_Rule_CaseInsensitive() {
|
||||
func ExampleRule_CaseInsensitive() {
|
||||
type BizReq struct {
|
||||
Account string `v:"required"`
|
||||
Password string `v:"required|ci|same:Password2"`
|
||||
@ -223,7 +223,7 @@ func Example_Rule_CaseInsensitive() {
|
||||
// output:
|
||||
}
|
||||
|
||||
func Example_Rule_Date() {
|
||||
func ExampleRule_Date() {
|
||||
type BizReq struct {
|
||||
Date1 string `v:"date"`
|
||||
Date2 string `v:"date"`
|
||||
@ -252,7 +252,7 @@ func Example_Rule_Date() {
|
||||
// The Date5 value `2021/Oct/31` is not a valid date
|
||||
}
|
||||
|
||||
func Example_Rule_Datetime() {
|
||||
func ExampleRule_Datetime() {
|
||||
type BizReq struct {
|
||||
Date1 string `v:"datetime"`
|
||||
Date2 string `v:"datetime"`
|
||||
@ -279,7 +279,7 @@ func Example_Rule_Datetime() {
|
||||
// The Date4 value `2021/Dec/01 23:00:00` is not a valid datetime
|
||||
}
|
||||
|
||||
func Example_Rule_DateFormat() {
|
||||
func ExampleRule_DateFormat() {
|
||||
type BizReq struct {
|
||||
Date1 string `v:"date-format:Y-m-d"`
|
||||
Date2 string `v:"date-format:Y-m-d"`
|
||||
@ -305,7 +305,7 @@ func Example_Rule_DateFormat() {
|
||||
// The Date4 value `2021-11-01 23:00` does not match the format: Y-m-d H:i:s
|
||||
}
|
||||
|
||||
func Example_Rule_Email() {
|
||||
func ExampleRule_Email() {
|
||||
type BizReq struct {
|
||||
MailAddr1 string `v:"email"`
|
||||
MailAddr2 string `v:"email"`
|
||||
@ -331,7 +331,7 @@ func Example_Rule_Email() {
|
||||
// The MailAddr4 value `gf#goframe.org` is not a valid email address
|
||||
}
|
||||
|
||||
func Example_Rule_Phone() {
|
||||
func ExampleRule_Phone() {
|
||||
type BizReq struct {
|
||||
PhoneNumber1 string `v:"phone"`
|
||||
PhoneNumber2 string `v:"phone"`
|
||||
@ -358,7 +358,7 @@ func Example_Rule_Phone() {
|
||||
// The PhoneNumber4 value `1357891234` is not a valid phone number
|
||||
}
|
||||
|
||||
func Example_Rule_PhoneLoose() {
|
||||
func ExampleRule_PhoneLoose() {
|
||||
type BizReq struct {
|
||||
PhoneNumber1 string `v:"phone-loose"`
|
||||
PhoneNumber2 string `v:"phone-loose"`
|
||||
@ -384,7 +384,7 @@ func Example_Rule_PhoneLoose() {
|
||||
// The PhoneNumber4 value `1357891234` is not a valid phone number
|
||||
}
|
||||
|
||||
func Example_Rule_Telephone() {
|
||||
func ExampleRule_Telephone() {
|
||||
type BizReq struct {
|
||||
Telephone1 string `v:"telephone"`
|
||||
Telephone2 string `v:"telephone"`
|
||||
@ -410,7 +410,7 @@ func Example_Rule_Telephone() {
|
||||
// The Telephone4 value `775421451` is not a valid telephone number
|
||||
}
|
||||
|
||||
func Example_Rule_Passport() {
|
||||
func ExampleRule_Passport() {
|
||||
type BizReq struct {
|
||||
Passport1 string `v:"passport"`
|
||||
Passport2 string `v:"passport"`
|
||||
@ -437,7 +437,7 @@ func Example_Rule_Passport() {
|
||||
// The Passport4 value `gf` is not a valid passport format
|
||||
}
|
||||
|
||||
func Example_Rule_Password() {
|
||||
func ExampleRule_Password() {
|
||||
type BizReq struct {
|
||||
Password1 string `v:"password"`
|
||||
Password2 string `v:"password"`
|
||||
@ -458,7 +458,7 @@ func Example_Rule_Password() {
|
||||
// The Password2 value `gofra` is not a valid password format
|
||||
}
|
||||
|
||||
func Example_Rule_Password2() {
|
||||
func ExampleRule_Password2() {
|
||||
type BizReq struct {
|
||||
Password1 string `v:"password2"`
|
||||
Password2 string `v:"password2"`
|
||||
@ -485,7 +485,7 @@ func Example_Rule_Password2() {
|
||||
// The Password4 value `goframe123` is not a valid password format
|
||||
}
|
||||
|
||||
func Example_Rule_Password3() {
|
||||
func ExampleRule_Password3() {
|
||||
type BizReq struct {
|
||||
Password1 string `v:"password3"`
|
||||
Password2 string `v:"password3"`
|
||||
@ -509,7 +509,7 @@ func Example_Rule_Password3() {
|
||||
// The Password3 value `Goframe123` is not a valid password format
|
||||
}
|
||||
|
||||
func Example_Rule_Postcode() {
|
||||
func ExampleRule_Postcode() {
|
||||
type BizReq struct {
|
||||
Postcode1 string `v:"postcode"`
|
||||
Postcode2 string `v:"postcode"`
|
||||
@ -533,7 +533,7 @@ func Example_Rule_Postcode() {
|
||||
// The Postcode3 value `1000000` is not a valid postcode format
|
||||
}
|
||||
|
||||
func Example_Rule_ResidentId() {
|
||||
func ExampleRule_ResidentId() {
|
||||
type BizReq struct {
|
||||
ResidentID1 string `v:"resident-id"`
|
||||
}
|
||||
@ -552,7 +552,7 @@ func Example_Rule_ResidentId() {
|
||||
// The ResidentID1 value `320107199506285482` is not a valid resident id number
|
||||
}
|
||||
|
||||
func Example_Rule_BankCard() {
|
||||
func ExampleRule_BankCard() {
|
||||
type BizReq struct {
|
||||
BankCard1 string `v:"bank-card"`
|
||||
}
|
||||
@ -571,7 +571,7 @@ func Example_Rule_BankCard() {
|
||||
// The BankCard1 value `6225760079930218` is not a valid bank card number
|
||||
}
|
||||
|
||||
func Example_Rule_QQ() {
|
||||
func ExampleRule_QQ() {
|
||||
type BizReq struct {
|
||||
QQ1 string `v:"qq"`
|
||||
QQ2 string `v:"qq"`
|
||||
@ -595,7 +595,7 @@ func Example_Rule_QQ() {
|
||||
// The QQ3 value `514258412a` is not a valid QQ number
|
||||
}
|
||||
|
||||
func Example_Rule_IP() {
|
||||
func ExampleRule_IP() {
|
||||
type BizReq struct {
|
||||
IP1 string `v:"ip"`
|
||||
IP2 string `v:"ip"`
|
||||
@ -621,7 +621,7 @@ func Example_Rule_IP() {
|
||||
// The IP4 value `ze80::812b:1158:1f43:f0d1` is not a valid IP address
|
||||
}
|
||||
|
||||
func Example_Rule_IPV4() {
|
||||
func ExampleRule_IPV4() {
|
||||
type BizReq struct {
|
||||
IP1 string `v:"ipv4"`
|
||||
IP2 string `v:"ipv4"`
|
||||
@ -642,7 +642,7 @@ func Example_Rule_IPV4() {
|
||||
// The IP2 value `520.255.255.255` is not a valid IPv4 address
|
||||
}
|
||||
|
||||
func Example_Rule_IPV6() {
|
||||
func ExampleRule_IPV6() {
|
||||
type BizReq struct {
|
||||
IP1 string `v:"ipv6"`
|
||||
IP2 string `v:"ipv6"`
|
||||
@ -663,7 +663,7 @@ func Example_Rule_IPV6() {
|
||||
// The IP2 value `ze80::812b:1158:1f43:f0d1` is not a valid IPv6 address
|
||||
}
|
||||
|
||||
func Example_Rule_Mac() {
|
||||
func ExampleRule_Mac() {
|
||||
type BizReq struct {
|
||||
Mac1 string `v:"mac"`
|
||||
Mac2 string `v:"mac"`
|
||||
@ -684,7 +684,7 @@ func Example_Rule_Mac() {
|
||||
// The Mac2 value `Z0-CC-6A-D6-B1-1A` is not a valid MAC address
|
||||
}
|
||||
|
||||
func Example_Rule_Url() {
|
||||
func ExampleRule_Url() {
|
||||
type BizReq struct {
|
||||
URL1 string `v:"url"`
|
||||
URL2 string `v:"url"`
|
||||
@ -707,7 +707,7 @@ func Example_Rule_Url() {
|
||||
// The URL3 value `ws://goframe.org` is not a valid URL address
|
||||
}
|
||||
|
||||
func Example_Rule_Domain() {
|
||||
func ExampleRule_Domain() {
|
||||
type BizReq struct {
|
||||
Domain1 string `v:"domain"`
|
||||
Domain2 string `v:"domain"`
|
||||
@ -733,7 +733,7 @@ func Example_Rule_Domain() {
|
||||
// The Domain4 value `1a.2b` is not a valid domain format
|
||||
}
|
||||
|
||||
func Example_Rule_Size() {
|
||||
func ExampleRule_Size() {
|
||||
type BizReq struct {
|
||||
Size1 string `v:"size:10"`
|
||||
Size2 string `v:"size:5"`
|
||||
@ -754,7 +754,7 @@ func Example_Rule_Size() {
|
||||
// The Size2 value `goframe` length must be 5
|
||||
}
|
||||
|
||||
func Example_Rule_Length() {
|
||||
func ExampleRule_Length() {
|
||||
type BizReq struct {
|
||||
Length1 string `v:"length:5,10"`
|
||||
Length2 string `v:"length:10,15"`
|
||||
@ -775,7 +775,7 @@ func Example_Rule_Length() {
|
||||
// The Length2 value `goframe` length must be between 10 and 15
|
||||
}
|
||||
|
||||
func Example_Rule_MinLength() {
|
||||
func ExampleRule_MinLength() {
|
||||
type BizReq struct {
|
||||
MinLength1 string `v:"min-length:10"`
|
||||
MinLength2 string `v:"min-length:8"`
|
||||
@ -796,7 +796,7 @@ func Example_Rule_MinLength() {
|
||||
// The MinLength2 value `goframe` length must be equal or greater than 8
|
||||
}
|
||||
|
||||
func Example_Rule_MaxLength() {
|
||||
func ExampleRule_MaxLength() {
|
||||
type BizReq struct {
|
||||
MaxLength1 string `v:"max-length:10"`
|
||||
MaxLength2 string `v:"max-length:5"`
|
||||
@ -817,7 +817,7 @@ func Example_Rule_MaxLength() {
|
||||
// The MaxLength2 value `goframe` length must be equal or lesser than 5
|
||||
}
|
||||
|
||||
func Example_Rule_Between() {
|
||||
func ExampleRule_Between() {
|
||||
type BizReq struct {
|
||||
Age1 int `v:"between:1,100"`
|
||||
Age2 int `v:"between:1,100"`
|
||||
@ -843,7 +843,7 @@ func Example_Rule_Between() {
|
||||
// The Score2 value `-0.5` must be between 0 and 10
|
||||
}
|
||||
|
||||
func Example_Rule_Min() {
|
||||
func ExampleRule_Min() {
|
||||
type BizReq struct {
|
||||
Age1 int `v:"min:100"`
|
||||
Age2 int `v:"min:100"`
|
||||
@ -869,7 +869,7 @@ func Example_Rule_Min() {
|
||||
// The Score1 value `9.8` must be equal or greater than 10
|
||||
}
|
||||
|
||||
func Example_Rule_Max() {
|
||||
func ExampleRule_Max() {
|
||||
type BizReq struct {
|
||||
Age1 int `v:"max:100"`
|
||||
Age2 int `v:"max:100"`
|
||||
@ -895,7 +895,7 @@ func Example_Rule_Max() {
|
||||
// The Score2 value `10.1` must be equal or lesser than 10
|
||||
}
|
||||
|
||||
func Example_Rule_Json() {
|
||||
func ExampleRule_Json() {
|
||||
type BizReq struct {
|
||||
JSON1 string `v:"json"`
|
||||
JSON2 string `v:"json"`
|
||||
@ -916,7 +916,7 @@ func Example_Rule_Json() {
|
||||
// The JSON2 value `{"name":"goframe","author":"郭强","test"}` is not a valid JSON string
|
||||
}
|
||||
|
||||
func Example_Rule_Integer() {
|
||||
func ExampleRule_Integer() {
|
||||
type BizReq struct {
|
||||
Integer string `v:"integer"`
|
||||
Float string `v:"integer"`
|
||||
@ -940,7 +940,7 @@ func Example_Rule_Integer() {
|
||||
// The Str value `goframe` is not an integer
|
||||
}
|
||||
|
||||
func Example_Rule_Float() {
|
||||
func ExampleRule_Float() {
|
||||
type BizReq struct {
|
||||
Integer string `v:"float"`
|
||||
Float string `v:"float"`
|
||||
@ -963,7 +963,7 @@ func Example_Rule_Float() {
|
||||
// The Str value `goframe` is not of valid float type
|
||||
}
|
||||
|
||||
func Example_Rule_Boolean() {
|
||||
func ExampleRule_Boolean() {
|
||||
type BizReq struct {
|
||||
Boolean bool `v:"boolean"`
|
||||
Integer int `v:"boolean"`
|
||||
@ -993,7 +993,7 @@ func Example_Rule_Boolean() {
|
||||
// The Str3 value `goframe` field must be true or false
|
||||
}
|
||||
|
||||
func Example_Rule_Same() {
|
||||
func ExampleRule_Same() {
|
||||
type BizReq struct {
|
||||
Name string `v:"required"`
|
||||
Password string `v:"required|same:Password2"`
|
||||
@ -1015,7 +1015,7 @@ func Example_Rule_Same() {
|
||||
// The Password value `goframe.org` must be the same as field Password2
|
||||
}
|
||||
|
||||
func Example_Rule_Different() {
|
||||
func ExampleRule_Different() {
|
||||
type BizReq struct {
|
||||
Name string `v:"required"`
|
||||
MailAddr string `v:"required"`
|
||||
@ -1037,7 +1037,7 @@ func Example_Rule_Different() {
|
||||
// The OtherMailAddr value `gf@goframe.org` must be different from field MailAddr
|
||||
}
|
||||
|
||||
func Example_Rule_In() {
|
||||
func ExampleRule_In() {
|
||||
type BizReq struct {
|
||||
ID uint `v:"required" dc:"Your Id"`
|
||||
Name string `v:"required" dc:"Your name"`
|
||||
@ -1059,7 +1059,7 @@ func Example_Rule_In() {
|
||||
// The Gender value `3` is not in acceptable range: 0,1,2
|
||||
}
|
||||
|
||||
func Example_Rule_NotIn() {
|
||||
func ExampleRule_NotIn() {
|
||||
type BizReq struct {
|
||||
ID uint `v:"required" dc:"Your Id"`
|
||||
Name string `v:"required" dc:"Your name"`
|
||||
@ -1081,7 +1081,7 @@ func Example_Rule_NotIn() {
|
||||
// The InvalidIndex value `1` must not be in range: -1,0,1
|
||||
}
|
||||
|
||||
func Example_Rule_Regex() {
|
||||
func ExampleRule_Regex() {
|
||||
type BizReq struct {
|
||||
Regex1 string `v:"regex:[1-9][0-9]{4,14}"`
|
||||
Regex2 string `v:"regex:[1-9][0-9]{4,14}"`
|
||||
@ -1104,7 +1104,7 @@ func Example_Rule_Regex() {
|
||||
// The Regex2 value `01234` must be in regex of: [1-9][0-9]{4,14}
|
||||
}
|
||||
|
||||
func Example_Rule_NotRegex() {
|
||||
func ExampleRule_NotRegex() {
|
||||
type BizReq struct {
|
||||
Regex1 string `v:"regex:\\d{4}"`
|
||||
Regex2 string `v:"not-regex:\\d{4}"`
|
||||
@ -1124,7 +1124,7 @@ func Example_Rule_NotRegex() {
|
||||
// The Regex2 value `1234` should not be in regex of: \d{4}
|
||||
}
|
||||
|
||||
func Example_Rule_After() {
|
||||
func ExampleRule_After() {
|
||||
type BizReq struct {
|
||||
Time1 string
|
||||
Time2 string `v:"after:Time1"`
|
||||
@ -1146,7 +1146,7 @@ func Example_Rule_After() {
|
||||
// The Time2 value `2022-09-01` must be after field Time1 value `2022-09-01`
|
||||
}
|
||||
|
||||
func Example_Rule_AfterEqual() {
|
||||
func ExampleRule_AfterEqual() {
|
||||
type BizReq struct {
|
||||
Time1 string
|
||||
Time2 string `v:"after-equal:Time1"`
|
||||
@ -1168,7 +1168,7 @@ func Example_Rule_AfterEqual() {
|
||||
// The Time2 value `2022-09-01` must be after or equal to field Time1 value `2022-09-02`
|
||||
}
|
||||
|
||||
func Example_Rule_Before() {
|
||||
func ExampleRule_Before() {
|
||||
type BizReq struct {
|
||||
Time1 string `v:"before:Time3"`
|
||||
Time2 string `v:"before:Time3"`
|
||||
@ -1190,7 +1190,7 @@ func Example_Rule_Before() {
|
||||
// The Time2 value `2022-09-03` must be before field Time3 value `2022-09-03`
|
||||
}
|
||||
|
||||
func Example_Rule_BeforeEqual() {
|
||||
func ExampleRule_BeforeEqual() {
|
||||
type BizReq struct {
|
||||
Time1 string `v:"before-equal:Time3"`
|
||||
Time2 string `v:"before-equal:Time3"`
|
||||
@ -1212,7 +1212,7 @@ func Example_Rule_BeforeEqual() {
|
||||
// The Time1 value `2022-09-02` must be before or equal to field Time3
|
||||
}
|
||||
|
||||
func Example_Rule_Array() {
|
||||
func ExampleRule_Array() {
|
||||
type BizReq struct {
|
||||
Value1 string `v:"array"`
|
||||
Value2 string `v:"array"`
|
||||
@ -1236,7 +1236,7 @@ func Example_Rule_Array() {
|
||||
// The Value1 value `1,2,3` is not of valid array type
|
||||
}
|
||||
|
||||
func Example_Rule_EQ() {
|
||||
func ExampleRule_EQ() {
|
||||
type BizReq struct {
|
||||
Name string `v:"required"`
|
||||
Password string `v:"required|eq:Password2"`
|
||||
@ -1258,7 +1258,7 @@ func Example_Rule_EQ() {
|
||||
// The Password value `goframe.org` must be equal to field Password2 value `goframe.net`
|
||||
}
|
||||
|
||||
func Example_Rule_NotEQ() {
|
||||
func ExampleRule_NotEQ() {
|
||||
type BizReq struct {
|
||||
Name string `v:"required"`
|
||||
MailAddr string `v:"required"`
|
||||
@ -1280,7 +1280,7 @@ func Example_Rule_NotEQ() {
|
||||
// The OtherMailAddr value `gf@goframe.org` must not be equal to field MailAddr value `gf@goframe.org`
|
||||
}
|
||||
|
||||
func Example_Rule_GT() {
|
||||
func ExampleRule_GT() {
|
||||
type BizReq struct {
|
||||
Value1 int
|
||||
Value2 int `v:"gt:Value1"`
|
||||
@ -1302,7 +1302,7 @@ func Example_Rule_GT() {
|
||||
// The Value2 value `1` must be greater than field Value1 value `1`
|
||||
}
|
||||
|
||||
func Example_Rule_GTE() {
|
||||
func ExampleRule_GTE() {
|
||||
type BizReq struct {
|
||||
Value1 int
|
||||
Value2 int `v:"gte:Value1"`
|
||||
@ -1324,7 +1324,7 @@ func Example_Rule_GTE() {
|
||||
// The Value2 value `1` must be greater than or equal to field Value1 value `2`
|
||||
}
|
||||
|
||||
func Example_Rule_LT() {
|
||||
func ExampleRule_LT() {
|
||||
type BizReq struct {
|
||||
Value1 int
|
||||
Value2 int `v:"lt:Value1"`
|
||||
@ -1346,7 +1346,7 @@ func Example_Rule_LT() {
|
||||
// The Value3 value `2` must be lesser than field Value1 value `2`
|
||||
}
|
||||
|
||||
func Example_Rule_LTE() {
|
||||
func ExampleRule_LTE() {
|
||||
type BizReq struct {
|
||||
Value1 int
|
||||
Value2 int `v:"lte:Value1"`
|
||||
@ -1368,7 +1368,7 @@ func Example_Rule_LTE() {
|
||||
// The Value3 value `2` must be lesser than or equal to field Value1 value `1`
|
||||
}
|
||||
|
||||
func Example_Rule_Foreach() {
|
||||
func ExampleRule_Foreach() {
|
||||
type BizReq struct {
|
||||
Value1 []int `v:"foreach|in:1,2,3"`
|
||||
Value2 []int `v:"foreach|in:1,2,3"`
|
||||
|
||||
@ -421,3 +421,26 @@ func Test_Issue1921(t *testing.T) {
|
||||
t.Assert(err, "The Size value `10000` must be equal or lesser than 100")
|
||||
})
|
||||
}
|
||||
|
||||
// https://github.com/gogf/gf/issues/2011
|
||||
func Test_Issue2011(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Student struct {
|
||||
Name string `v:"required|min-length:6"`
|
||||
Age int
|
||||
}
|
||||
type Teacher struct {
|
||||
Student *Student
|
||||
}
|
||||
var (
|
||||
teacher = Teacher{}
|
||||
data = g.Map{
|
||||
"student": g.Map{
|
||||
"name": "john",
|
||||
},
|
||||
}
|
||||
)
|
||||
err := g.Validator().Assoc(data).Data(teacher).Run(ctx)
|
||||
t.Assert(err, "The Name value `john` length must be equal or greater than 6")
|
||||
})
|
||||
}
|
||||
|
||||
@ -2,5 +2,5 @@ package gf
|
||||
|
||||
const (
|
||||
// VERSION is the current GoFrame version.
|
||||
VERSION = "v2.3.1"
|
||||
VERSION = "v2.3.2"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user