Compare commits

...

19 Commits

Author SHA1 Message Date
ad737ded3c fix issue #2447 (#2448) 2023-02-15 14:13:32 +08:00
ac6b0c0980 fix issue #2427 (#2442) 2023-02-15 09:45:40 +08:00
b69e0ff9f7 fix issue #2338 (#2444) 2023-02-14 09:45:29 +08:00
0361f9f7de fix #2435 (#2437) 2023-02-13 19:18:30 +08:00
005668aca8 gdb error should wrap original underlying database error like MySQLError (#2402) 2023-02-08 19:38:11 +08:00
013f8b216a improve Timezone escape for driver dm/mysql (#2412) 2023-02-08 19:35:48 +08:00
8ecfa91e5d comment updates for with function of package gdb (#2418) 2023-02-08 19:10:03 +08:00
117fc6eda2 fix issue #2339 (#2433) 2023-02-08 19:08:10 +08:00
d66af122c7 fix issue #2331 (#2432) 2023-02-08 19:07:05 +08:00
a7467945ca fix issue #2355 (#2430) 2023-02-08 14:17:21 +08:00
81d8aa55cd fix issue #2371 (#2429) 2023-02-08 14:17:11 +08:00
4a6630138d fix issue 2356 (#2428) 2023-02-08 14:17:00 +08:00
3adae3a9aa fix type of default value in swagger ui for package goai (#2413) 2023-02-08 14:16:12 +08:00
21ebf48072 .gitignore updates (#2426) 2023-02-07 21:13:20 +08:00
2b90bcfab6 fix issue #2050: add -t option support for command gf docker to compatable with older version (#2423) 2023-02-07 17:41:43 +08:00
5f0641f348 fix issue #2015 (#2422) 2023-02-07 14:06:26 +08:00
38c9cac578 fix issue #2011 (#2421) 2023-02-07 11:37:39 +08:00
9ba49fa454 fix issue in gf run failed with arguments passed in windows platform (#2414) 2023-02-06 20:35:11 +08:00
39fede66e6 add label planned for ci to check issue inactive (#2408)
add label planned for ci to check issue inactive
2023-01-18 17:04:26 +08:00
60 changed files with 767 additions and 391 deletions

View File

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

View File

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

@ -7,11 +7,8 @@
.settings/
.vscode/
vendor/
composer.lock
gitpush.sh
pkg/
bin/
cbuild
**/.DS_Store
.test/
cmd/gf/main

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 != "" {

View File

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,7 @@ import (
"github.com/gogf/gf/v2/encoding/gjson"
)
func Example_conversionNormalFormats() {
func ExampleConversionNormalFormats() {
data :=
`{
"users" : {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,7 +14,7 @@ import (
"github.com/gogf/gf/v2/os/gtimer"
)
func Example_add() {
func ExampleAdd() {
var (
ctx = context.Background()
now = time.Now()

View File

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

View File

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

View File

@ -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{}`.

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

View File

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

View File

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

View File

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

View File

@ -2,5 +2,5 @@ package gf
const (
// VERSION is the current GoFrame version.
VERSION = "v2.3.1"
VERSION = "v2.3.2"
)