Compare commits

...

24 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
d984f1a9d8 add auto go mod tidy after version upgraded for command up (#2407)
* add cli upgraded supported for command up

* improve unit case for package internal/mutex

* v2.3.1

* add auto  after version upgraded for command

Co-authored-by: houseme <housemecn@gmail.com>
2023-01-18 11:28:55 +08:00
Gin
28b8efe00c fix issue 2403 (#2404) 2023-01-18 10:17:16 +08:00
7b0fd6de9b add cli upgraded supported for command up (#2405)
* add cli upgraded supported for command up

* improve unit case for package internal/mutex

* v2.3.1

Co-authored-by: houseme <housemecn@gmail.com>
2023-01-18 10:12:00 +08:00
c0fa2e3a73 fix issue 2395 (#2399)
* v2.3.0

* fix #2391

* fix issue #2391

* fix issue #2395
2023-01-17 14:51:19 +08:00
3f6669e2b7 fix issue 2391 (#2398)
* v2.3.0

* fix #2391

* fix issue #2391
2023-01-16 16:00:18 +08:00
69 changed files with 1149 additions and 469 deletions

View File

@ -122,6 +122,7 @@ jobs:
- 1521:1521 - 1521:1521
# dm8 server # dm8 server
# docker run -d --name dm -p 5236:5236 loads/dm:v8.1.2.128_ent_x86_64_ctm_pack4
dm-server: dm-server:
image: loads/dm:v8.1.2.128_ent_x86_64_ctm_pack4 image: loads/dm:v8.1.2.128_ent_x86_64_ctm_pack4
ports: ports:

View File

@ -25,4 +25,4 @@ jobs:
inactive-label: 'inactive' inactive-label: 'inactive'
inactive-day: 7 inactive-day: 7
issue-state: open issue-state: open
exclude-labels: 'bug,$exclude-empty' exclude-labels: 'bug,planned,$exclude-empty'

3
.gitignore vendored
View File

@ -7,11 +7,8 @@
.settings/ .settings/
.vscode/ .vscode/
vendor/ vendor/
composer.lock
gitpush.sh
pkg/ pkg/
bin/ bin/
cbuild
**/.DS_Store **/.DS_Store
.test/ .test/
cmd/gf/main 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 -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 -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 = ` cDockerDc = `
The "docker" command builds the GF project to a docker images. 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` 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` 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` 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` 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` 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"` cDockerExtraBrief = `extra build options passed to "docker image"`
@ -61,6 +63,7 @@ func init() {
`cDockerShellBrief`: cDockerShellBrief, `cDockerShellBrief`: cDockerShellBrief,
`cDockerBuildBrief`: cDockerBuildBrief, `cDockerBuildBrief`: cDockerBuildBrief,
`cDockerPushBrief`: cDockerPushBrief, `cDockerPushBrief`: cDockerPushBrief,
`cDockerTagBrief`: cDockerTagBrief,
`cDockerTagNameBrief`: cDockerTagNameBrief, `cDockerTagNameBrief`: cDockerTagNameBrief,
`cDockerTagPrefixesBrief`: cDockerTagPrefixesBrief, `cDockerTagPrefixesBrief`: cDockerTagPrefixesBrief,
`cDockerExtraBrief`: cDockerExtraBrief, `cDockerExtraBrief`: cDockerExtraBrief,
@ -73,6 +76,7 @@ type cDockerInput struct {
File string `name:"file" short:"f" brief:"{cDockerFileBrief}" d:"manifest/docker/Dockerfile"` 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"` 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"` 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"` TagName string `name:"tagName" short:"tn" brief:"{cDockerTagNameBrief}" v:"required-with:TagPrefixes"`
TagPrefixes []string `name:"tagPrefixes" short:"tp" brief:"{cDockerTagPrefixesBrief}" v:"required-with:TagName"` TagPrefixes []string `name:"tagPrefixes" short:"tp" brief:"{cDockerTagPrefixesBrief}" v:"required-with:TagName"`
Push bool `name:"push" short:"p" brief:"{cDockerPushBrief}" orphan:"true"` 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 { if len(dockerTags) == 0 {
dockerTags = []string{""} dockerTags = []string{in.Tag}
} }
for i, dockerTag := range dockerTags { for i, dockerTag := range dockerTags {
if i > 0 { if i > 0 {

View File

@ -2,6 +2,7 @@ package cmd
import ( import (
"context" "context"
"github.com/gogf/gf/v2/os/gproc"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog" "github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/errors/gerror"
@ -19,8 +20,9 @@ type cFix struct {
} }
type cFixInput struct { type cFixInput struct {
g.Meta `name:"fix"` g.Meta `name:"fix"`
Path string `name:"path" brief:"directory path, it uses current working directory in default"` Path string `name:"path" short:"p" brief:"directory path, it uses current working directory in default"`
Version string `name:"version" short:"v" brief:"custom specified version to fix, leave it empty to auto detect"`
} }
type cFixOutput struct{} type cFixOutput struct{}
@ -31,38 +33,45 @@ type cFixItem struct {
} }
func (c cFix) Index(ctx context.Context, in cFixInput) (out *cFixOutput, err error) { func (c cFix) Index(ctx context.Context, in cFixInput) (out *cFixOutput, err error) {
mlog.Print(`start auto fixing...`)
defer mlog.Print(`done!`)
if in.Path == "" { if in.Path == "" {
in.Path = gfile.Pwd() in.Path = gfile.Pwd()
} }
if in.Version == "" {
in.Version, err = c.autoDetectVersion(in)
if err != nil {
mlog.Fatal(err)
}
if in.Version == "" {
mlog.Print(`no GoFrame usage found, exit fixing`)
return
}
mlog.Debugf(`current GoFrame version auto detect "%s"`, in.Version)
}
if !gproc.IsChild() {
mlog.Printf(`start auto fixing directory path "%s"...`, in.Path)
defer mlog.Print(`done!`)
}
err = c.doFix(in) err = c.doFix(in)
return return
} }
func (c cFix) doFix(in cFixInput) (err error) { func (c cFix) doFix(in cFixInput) (err error) {
version, err := c.getVersion(in)
if err != nil {
mlog.Fatal(err)
}
if version == "" {
mlog.Print(`no GoFrame usage found, exit fixing`)
return
}
mlog.Debugf(`current GoFrame version found "%s"`, version)
var items = []cFixItem{ var items = []cFixItem{
{Version: "v2.3", Func: c.doFixV23}, {Version: "v2.3", Func: c.doFixV23},
} }
for _, item := range items { for _, item := range items {
if gstr.CompareVersionGo(version, item.Version) < 0 { if gstr.CompareVersionGo(in.Version, item.Version) < 0 {
mlog.Debugf( mlog.Debugf(
`current GoFrame version "%s" is lesser than "%s", nothing to do`, `current GoFrame or contrib package version "%s" is lesser than "%s", nothing to do`,
version, item.Version, in.Version, item.Version,
) )
continue continue
} }
if err = item.Func(version); err != nil { if err = item.Func(in.Version); err != nil {
return return
} }
} }
@ -87,7 +96,7 @@ func (c cFix) doFixV23(version string) error {
return gfile.ReplaceDirFunc(replaceFunc, ".", "*.go", true) return gfile.ReplaceDirFunc(replaceFunc, ".", "*.go", true)
} }
func (c cFix) getVersion(in cFixInput) (string, error) { func (c cFix) autoDetectVersion(in cFixInput) (string, error) {
var ( var (
err error err error
path = gfile.Join(in.Path, "go.mod") path = gfile.Join(in.Path, "go.mod")

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"runtime" "runtime"
"strings"
"github.com/gogf/gf/v2/container/gtype" "github.com/gogf/gf/v2/container/gtype"
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
@ -154,7 +155,7 @@ func (app *cRunApp) Run(ctx context.Context) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
// Special handling for windows platform. // Special handling for windows platform.
// DO NOT USE "cmd /c" command. // DO NOT USE "cmd /c" command.
process = gproc.NewProcess(runCommand, nil) process = gproc.NewProcess(outputPath, strings.Fields(app.Args))
} else { } else {
process = gproc.NewProcessCmd(runCommand, nil) process = gproc.NewProcessCmd(runCommand, nil)
} }

View File

@ -3,6 +3,9 @@ package cmd
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
"github.com/gogf/gf/v2/container/gset"
"runtime"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog" "github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
@ -26,7 +29,7 @@ const (
gf up gf up
gf up -a gf up -a
gf up -c gf up -c
gf up -f -c gf up -cf
` `
) )
@ -39,8 +42,8 @@ func init() {
type cUpInput struct { type cUpInput struct {
g.Meta `name:"up" config:"gfcli.up"` g.Meta `name:"up" config:"gfcli.up"`
All bool `name:"all" short:"a" brief:"upgrade both version and cli, auto fix codes" orphan:"true"` All bool `name:"all" short:"a" brief:"upgrade both version and cli, auto fix codes" orphan:"true"`
Fix bool `name:"fix" short:"f" brief:"auto fix codes" orphan:"true"` Cli bool `name:"cli" short:"c" brief:"also upgrade CLI tool" orphan:"true"`
Cli bool `name:"cli" short:"c" brief:"also upgrade CLI tool (not supported yet)" orphan:"true"` Fix bool `name:"fix" short:"f" brief:"auto fix codes(it only make sense if cli is to be upgraded)" orphan:"true"`
} }
type cUpOutput struct{} type cUpOutput struct{}
@ -48,42 +51,72 @@ type cUpOutput struct{}
func (c cUp) Index(ctx context.Context, in cUpInput) (out *cUpOutput, err error) { func (c cUp) Index(ctx context.Context, in cUpInput) (out *cUpOutput, err error) {
defer func() { defer func() {
if err == nil { if err == nil {
mlog.Print(`done!`) mlog.Print()
mlog.Print(`👏congratulations! you've upgraded to the latest version of GoFrame! enjoy it!👏`)
mlog.Print()
} }
}() }()
var doUpgradeVersionOut *doUpgradeVersionOutput
if in.All { if in.All {
in.Cli = true in.Cli = true
in.Fix = true in.Fix = true
} }
if err = c.doUpgradeVersion(ctx, in); err != nil { if doUpgradeVersionOut, err = c.doUpgradeVersion(ctx, in); err != nil {
return nil, err return nil, err
} }
//if in.Cli {
// if err = c.doUpgradeCLI(ctx); err != nil { if in.Cli {
// return nil, err if err = c.doUpgradeCLI(ctx); err != nil {
// } return nil, err
//} }
}
if in.Cli && in.Fix {
if doUpgradeVersionOut != nil && len(doUpgradeVersionOut.Items) > 0 {
upgradedPathSet := gset.NewStrSet()
for _, item := range doUpgradeVersionOut.Items {
if !upgradedPathSet.AddIfNotExist(item.DirPath) {
continue
}
if err = c.doAutoFixing(ctx, item.DirPath, item.Version); err != nil {
return nil, err
}
}
}
}
return return
} }
func (c cUp) doUpgradeVersion(ctx context.Context, in cUpInput) (err error) { type doUpgradeVersionOutput struct {
mlog.Print(`start upgrading version...`) Items []doUpgradeVersionOutputItem
}
type doUpgradeVersionOutputItem struct {
DirPath string
Version string
}
func (c cUp) doUpgradeVersion(ctx context.Context, in cUpInput) (out *doUpgradeVersionOutput, err error) {
mlog.Print(`start upgrading version...`)
out = &doUpgradeVersionOutput{
Items: make([]doUpgradeVersionOutputItem, 0),
}
type Package struct { type Package struct {
Name string Name string
Version string Version string
} }
var ( var (
dir = gfile.Pwd() temp string
temp string dirPath = gfile.Pwd()
path = gfile.Join(dir, "go.mod") goModPath = gfile.Join(dirPath, "go.mod")
) )
// It recursively upgrades the go.mod from sub folder to its parent folders.
for { for {
if gfile.Exists(path) { if gfile.Exists(goModPath) {
var packages []Package var packages []Package
err = gfile.ReadLines(path, func(line string) error { err = gfile.ReadLines(goModPath, func(line string) error {
line = gstr.Trim(line) line = gstr.Trim(line)
if gstr.HasPrefix(line, gfPackage) { if gstr.HasPrefix(line, gfPackage) {
array := gstr.SplitAndTrim(line, " ") array := gstr.SplitAndTrim(line, " ")
@ -99,38 +132,76 @@ func (c cUp) doUpgradeVersion(ctx context.Context, in cUpInput) (err error) {
} }
for _, pkg := range packages { for _, pkg := range packages {
mlog.Printf(`upgrading "%s" from "%s" to "latest"`, pkg.Name, pkg.Version) mlog.Printf(`upgrading "%s" from "%s" to "latest"`, pkg.Name, pkg.Version)
command := fmt.Sprintf(`go get -u %s@latest`, pkg.Name) // go get -u
command := fmt.Sprintf(`cd %s && go get -u %s@latest`, dirPath, pkg.Name)
if err = gproc.ShellRun(ctx, command); err != nil { if err = gproc.ShellRun(ctx, command); err != nil {
return return
} }
mlog.Print() // go mod tidy
} if err = utils.GoModTidy(ctx, dirPath); err != nil {
if in.Fix { return nil, err
if err = c.doAutoFixing(ctx, dir); err != nil {
return err
} }
mlog.Print() out.Items = append(out.Items, doUpgradeVersionOutputItem{
DirPath: dirPath,
Version: pkg.Version,
})
} }
return return
} }
temp = gfile.Dir(dir) temp = gfile.Dir(dirPath)
if temp == "" || temp == dir { if temp == "" || temp == dirPath {
return return
} }
dir = temp dirPath = temp
path = gfile.Join(dir, "go.mod") goModPath = gfile.Join(dirPath, "go.mod")
} }
} }
// doUpgradeCLI downloads the new version binary with process.
func (c cUp) doUpgradeCLI(ctx context.Context) (err error) { func (c cUp) doUpgradeCLI(ctx context.Context) (err error) {
mlog.Print(`start upgrading cli...`) mlog.Print(`start upgrading cli...`)
var (
downloadUrl = fmt.Sprintf(
`https://github.com/gogf/gf/releases/latest/download/gf_%s_%s`,
runtime.GOOS, runtime.GOARCH,
)
localSaveFilePath = gfile.SelfPath() + "~"
)
mlog.Printf(`start downloading "%s" to "%s", it may take some time`, downloadUrl, localSaveFilePath)
err = utils.HTTPDownloadFileWithPercent(downloadUrl, localSaveFilePath)
if err != nil {
return err
}
defer func() {
mlog.Printf(`new version cli binary is successfully installed to "%s"`, gfile.SelfPath())
mlog.Printf(`remove temporary buffer file "%s"`, localSaveFilePath)
_ = gfile.Remove(localSaveFilePath)
}()
// It fails if file not exist or its size is less than 1MB.
if !gfile.Exists(localSaveFilePath) || gfile.Size(localSaveFilePath) < 1024*1024 {
mlog.Fatalf(`download "%s" to "%s" failed`, downloadUrl, localSaveFilePath)
}
// It replaces self binary with new version cli binary.
switch runtime.GOOS {
case "windows":
if err := gfile.Rename(localSaveFilePath, gfile.SelfPath()); err != nil {
mlog.Fatalf(`install failed: %s`, err.Error())
}
default:
if err := gfile.PutBytes(gfile.SelfPath(), gfile.GetBytes(localSaveFilePath)); err != nil {
mlog.Fatalf(`install failed: %s`, err.Error())
}
}
return return
} }
func (c cUp) doAutoFixing(ctx context.Context, dirPath string) (err error) { func (c cUp) doAutoFixing(ctx context.Context, dirPath string, version string) (err error) {
mlog.Printf(`auto fixing path "%s"...`, dirPath) mlog.Printf(`auto fixing directory path "%s" from version "%s" ...`, dirPath, version)
err = cFix{}.doFix(cFixInput{ command := fmt.Sprintf(`gf fix -p %s`, dirPath)
Path: dirPath, _ = gproc.ShellRun(ctx, command)
})
return return
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,12 +1,14 @@
package utils package utils
import ( import (
"github.com/gogf/gf/v2/os/gfile" "context"
"github.com/gogf/gf/v2/text/gstr" "fmt"
"golang.org/x/tools/imports"
"github.com/gogf/gf/cmd/gf/v2/internal/consts" "github.com/gogf/gf/cmd/gf/v2/internal/consts"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog" "github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gproc"
"github.com/gogf/gf/v2/text/gstr"
"golang.org/x/tools/imports"
) )
// GoFmt formats the source file and adds or removes import statements as necessary. // GoFmt formats the source file and adds or removes import statements as necessary.
@ -36,6 +38,13 @@ func GoFmt(path string) {
} }
} }
// GoModTidy executes `go mod tidy` at specified directory `dirPath`.
func GoModTidy(ctx context.Context, dirPath string) error {
command := fmt.Sprintf(`cd %s && go mod tidy`, dirPath)
err := gproc.ShellRun(ctx, command)
return err
}
// IsFileDoNotEdit checks and returns whether file contains `do not edit` key. // IsFileDoNotEdit checks and returns whether file contains `do not edit` key.
func IsFileDoNotEdit(filePath string) bool { func IsFileDoNotEdit(filePath string) bool {
if !gfile.Exists(filePath) { if !gfile.Exists(filePath) {

View File

@ -0,0 +1,98 @@
package utils
import (
"fmt"
"github.com/gogf/gf/v2/errors/gerror"
"io"
"net/http"
"os"
"strconv"
"time"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
)
// HTTPDownloadFileWithPercent downloads target url file to local path with percent process printing.
func HTTPDownloadFileWithPercent(url string, localSaveFilePath string) error {
start := time.Now()
out, err := os.Create(localSaveFilePath)
if err != nil {
return gerror.Wrapf(err, `download "%s" to "%s" failed`, url, localSaveFilePath)
}
defer out.Close()
headResp, err := http.Head(url)
if err != nil {
return gerror.Wrapf(err, `download "%s" to "%s" failed`, url, localSaveFilePath)
}
defer headResp.Body.Close()
size, err := strconv.Atoi(headResp.Header.Get("Content-Length"))
if err != nil {
return gerror.Wrap(err, "retrieve Content-Length failed")
}
doneCh := make(chan int64)
go doPrintDownloadPercent(doneCh, localSaveFilePath, int64(size))
resp, err := http.Get(url)
if err != nil {
return gerror.Wrapf(err, `download "%s" to "%s" failed`, url, localSaveFilePath)
}
defer resp.Body.Close()
wroteBytesCount, err := io.Copy(out, resp.Body)
if err != nil {
return gerror.Wrapf(err, `download "%s" to "%s" failed`, url, localSaveFilePath)
}
doneCh <- wroteBytesCount
elapsed := time.Since(start)
if elapsed > time.Minute {
mlog.Printf(`download completed in %.0fm`, float64(elapsed)/float64(time.Minute))
} else {
mlog.Printf(`download completed in %.0fs`, elapsed.Seconds())
}
return nil
}
func doPrintDownloadPercent(doneCh chan int64, localSaveFilePath string, total int64) {
var (
stop = false
lastPercentFmt string
)
for {
select {
case <-doneCh:
stop = true
default:
file, err := os.Open(localSaveFilePath)
if err != nil {
mlog.Fatal(err)
}
fi, err := file.Stat()
if err != nil {
mlog.Fatal(err)
}
size := fi.Size()
if size == 0 {
size = 1
}
var (
percent = float64(size) / float64(total) * 100
percentFmt = fmt.Sprintf(`%.0f`, percent) + "%"
)
if lastPercentFmt != percentFmt {
lastPercentFmt = percentFmt
mlog.Print(percentFmt)
}
}
if stop {
break
}
time.Sleep(time.Second)
}
}

View File

@ -173,28 +173,28 @@ func (a *Array) SortFunc(less func(v1, v2 interface{}) bool) *Array {
return a return a
} }
// InsertBefore inserts the `value` to the front of `index`. // InsertBefore inserts the `values` to the front of `index`.
func (a *Array) InsertBefore(index int, value interface{}) error { func (a *Array) InsertBefore(index int, values ...interface{}) error {
a.mu.Lock() a.mu.Lock()
defer a.mu.Unlock() defer a.mu.Unlock()
if index < 0 || index >= len(a.array) { if index < 0 || index >= len(a.array) {
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", 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:]...) 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...) a.array = append(a.array, rear...)
return nil return nil
} }
// InsertAfter inserts the `value` to the back of `index`. // InsertAfter inserts the `values` to the back of `index`.
func (a *Array) InsertAfter(index int, value interface{}) error { func (a *Array) InsertAfter(index int, values ...interface{}) error {
a.mu.Lock() a.mu.Lock()
defer a.mu.Unlock() defer a.mu.Unlock()
if index < 0 || index >= len(a.array) { if index < 0 || index >= len(a.array) {
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", 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:]...) 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...) a.array = append(a.array, rear...)
return nil return nil
} }

View File

@ -168,28 +168,28 @@ func (a *IntArray) SortFunc(less func(v1, v2 int) bool) *IntArray {
return a return a
} }
// InsertBefore inserts the `value` to the front of `index`. // InsertBefore inserts the `values` to the front of `index`.
func (a *IntArray) InsertBefore(index int, value int) error { func (a *IntArray) InsertBefore(index int, values ...int) error {
a.mu.Lock() a.mu.Lock()
defer a.mu.Unlock() defer a.mu.Unlock()
if index < 0 || index >= len(a.array) { if index < 0 || index >= len(a.array) {
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", 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:]...) 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...) a.array = append(a.array, rear...)
return nil return nil
} }
// InsertAfter inserts the `value` to the back of `index`. // 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() a.mu.Lock()
defer a.mu.Unlock() defer a.mu.Unlock()
if index < 0 || index >= len(a.array) { if index < 0 || index >= len(a.array) {
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", 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:]...) 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...) a.array = append(a.array, rear...)
return nil return nil
} }

View File

@ -155,28 +155,28 @@ func (a *StrArray) SortFunc(less func(v1, v2 string) bool) *StrArray {
return a return a
} }
// InsertBefore inserts the `value` to the front of `index`. // InsertBefore inserts the `values` to the front of `index`.
func (a *StrArray) InsertBefore(index int, value string) error { func (a *StrArray) InsertBefore(index int, values ...string) error {
a.mu.Lock() a.mu.Lock()
defer a.mu.Unlock() defer a.mu.Unlock()
if index < 0 || index >= len(a.array) { if index < 0 || index >= len(a.array) {
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", 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:]...) 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...) a.array = append(a.array, rear...)
return nil return nil
} }
// InsertAfter inserts the `value` to the back of `index`. // InsertAfter inserts the `values` to the back of `index`.
func (a *StrArray) InsertAfter(index int, value string) error { func (a *StrArray) InsertAfter(index int, values ...string) error {
a.mu.Lock() a.mu.Lock()
defer a.mu.Unlock() defer a.mu.Unlock()
if index < 0 || index >= len(a.array) { if index < 0 || index >= len(a.array) {
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", 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:]...) 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...) a.array = append(a.array, rear...)
return nil return nil
} }

View File

@ -85,7 +85,9 @@ func (q *Queue) Pop() interface{} {
// Notice: It would notify all goroutines return immediately, // Notice: It would notify all goroutines return immediately,
// which are being blocked reading using Pop method. // which are being blocked reading using Pop method.
func (q *Queue) Close() { func (q *Queue) Close() {
q.closed.Set(true) if !q.closed.Cas(false, true) {
return
}
if q.events != nil { if q.events != nil {
close(q.events) close(q.events)
} }

View File

@ -143,11 +143,11 @@ func (d *Driver) TableFields(
ctx context.Context, table string, schema ...string, ctx context.Context, table string, schema ...string,
) (fields map[string]*gdb.TableField, err error) { ) (fields map[string]*gdb.TableField, err error) {
var ( var (
result gdb.Result result gdb.Result
link gdb.Link link gdb.Link
useSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...) 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 return nil, err
} }
var ( var (

View File

@ -30,6 +30,10 @@ type Driver struct {
*gdb.Core *gdb.Core
} }
const (
quoteChar = `"`
)
func init() { func init() {
var ( var (
err error err error
@ -72,7 +76,10 @@ func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
// Demo of timezone setting: // Demo of timezone setting:
// &loc=Asia/Shanghai // &loc=Asia/Shanghai
if config.Timezone != "" { 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 != "" { if config.Extra != "" {
source = fmt.Sprintf("%s&%s", source, 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) { 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) { 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 // There should be no need to capitalize, because it has been done from field processing before
newSql, err = gregex.ReplaceString(`["\n\t]`, "", sql) newSql, err = gregex.ReplaceString(`["\n\t]`, "", sql)
newSql = gstr.ReplaceI(newSql, "GROUP_CONCAT", "WM_CONCAT") newSql = gstr.ReplaceI(newSql, "GROUP_CONCAT", "WM_CONCAT")
// g.Dump("Driver.DoFilter()::newSql", newSql) // gutil.Dump("Driver.DoFilter()::newSql", newSql)
newArgs = args newArgs = args
// g.Dump("Driver.DoFilter()::newArgs", newArgs) // gutil.Dump("Driver.DoFilter()::newArgs", newArgs)
return return
} }

View File

@ -582,125 +582,3 @@ func Test_Empty_Slice_Argument(t *testing.T) {
t.Assert(len(result), 0) 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 => ../../../ replace github.com/gogf/gf/v2 => ../../../
require ( require (
gitee.com/chunanyong/dm v1.8.6 gitee.com/chunanyong/dm v1.8.11
github.com/gogf/gf/v2 v2.0.0 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.11 h1:JPwiS1PqHObo4QFodruLR8WOhLP+7Y/EKGGu2BJ5SJI=
gitee.com/chunanyong/dm v1.8.6/go.mod h1:EPRJnuPFgbyOFgJ0TRYCTGzhq+ZT4wdyaj/GW/LLcNg= 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 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E= github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E=

View File

@ -33,6 +33,10 @@ type Driver struct {
*gdb.Core *gdb.Core
} }
const (
quoteChar = `"`
)
func init() { func init() {
if err := gdb.Register(`mssql`, New()); err != nil { if err := gdb.Register(`mssql`, New()); err != nil {
panic(err) 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. // GetChars returns the security char for this type of database.
func (d *Driver) GetChars() (charLeft string, charRight string) { 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. // 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. // Also see DriverMysql.TableFields.
func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*gdb.TableField, err error) { func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*gdb.TableField, err error) {
var ( var (
result gdb.Result result gdb.Result
link gdb.Link link gdb.Link
useSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...) 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 return nil, err
} }
structureSql := fmt.Sprintf(` structureSql := fmt.Sprintf(`

View File

@ -12,6 +12,7 @@ import (
"database/sql" "database/sql"
"fmt" "fmt"
"net/url" "net/url"
"strings"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
"github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/database/gdb"
@ -27,6 +28,10 @@ type Driver struct {
*gdb.Core *gdb.Core
} }
const (
quoteChar = "`"
)
func init() { func init() {
var ( var (
err error 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, config.User, config.Pass, config.Protocol, config.Host, config.Port, config.Name, config.Charset,
) )
if config.Timezone != "" { 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 != "" { if config.Extra != "" {
source = fmt.Sprintf("%s&%s", source, 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. // GetChars returns the security char for this type of database.
func (d *Driver) GetChars() (charLeft string, charRight string) { func (d *Driver) GetChars() (charLeft string, charRight string) {
return "`", "`" return quoteChar, quoteChar
} }
// DoFilter handles the sql before posts it to database. // DoFilter handles the sql before posts it to database.
@ -138,11 +146,11 @@ func (d *Driver) TableFields(
ctx context.Context, table string, schema ...string, ctx context.Context, table string, schema ...string,
) (fields map[string]*gdb.TableField, err error) { ) (fields map[string]*gdb.TableField, err error) {
var ( var (
result gdb.Result result gdb.Result
link gdb.Link link gdb.Link
useSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...) 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 return nil, err
} }
result, err = d.DoSelect( result, err = d.DoSelect(

View File

@ -9,8 +9,11 @@ package mysql_test
import ( import (
"testing" "testing"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/test/gtest" "github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/util/gmeta"
) )
func Test_Model_Builder(t *testing.T) { func Test_Model_Builder(t *testing.T) {
@ -59,4 +62,66 @@ func Test_Model_Builder(t *testing.T) {
t.AssertNil(err) t.AssertNil(err)
t.Assert(len(all), 6) t.Assert(len(all), 6)
}) })
// Where with struct which has a field type of *gtime.Time
gtest.C(t, func(t *gtest.T) {
m := db.Model(table)
b := m.Builder()
type Query struct {
Id interface{}
Nickname *gtime.Time
}
where, args := b.Where(&Query{Id: 1}).Build()
t.Assert(where, "`id`=? AND `nickname` IS NULL")
t.Assert(args, []interface{}{1})
})
// Where with struct which has a field type of *gjson.Json
gtest.C(t, func(t *gtest.T) {
m := db.Model(table)
b := m.Builder()
type Query struct {
Id interface{}
Nickname *gjson.Json
}
where, args := b.Where(&Query{Id: 1}).Build()
t.Assert(where, "`id`=? AND `nickname` IS NULL")
t.Assert(args, []interface{}{1})
})
// Where with do struct which has a field type of *gtime.Time and generated by gf cli
gtest.C(t, func(t *gtest.T) {
m := db.Model(table)
b := m.Builder()
type Query struct {
gmeta.Meta `orm:"do:true"`
Id interface{}
Nickname *gtime.Time
}
where, args := b.Where(&Query{Id: 1}).Build()
t.Assert(where, "`id`=?")
t.Assert(args, []interface{}{1})
})
// Where with do struct which has a field type of *gjson.Json and generated by gf cli
gtest.C(t, func(t *gtest.T) {
m := db.Model(table)
b := m.Builder()
type Query struct {
gmeta.Meta `orm:"do:true"`
Id interface{}
Nickname *gjson.Json
}
where, args := b.Where(&Query{Id: 1}).Build()
t.Assert(where, "`id`=?")
t.Assert(args, []interface{}{1})
})
} }

View File

@ -646,7 +646,7 @@ CREATE TABLE %s (
t.Assert(one["name"], "name_1") t.Assert(one["name"], "name_1")
// Soft deleting. // Soft deleting.
r, err = db.Model(table1).Delete() r, err = db.Model(table1).Where(1).Delete()
t.AssertNil(err) t.AssertNil(err)
n, _ = r.RowsAffected() n, _ = r.RowsAffected()
t.Assert(n, 1) t.Assert(n, 1)

View File

@ -21,6 +21,7 @@ import (
"github.com/gogf/gf/v2/util/guid" "github.com/gogf/gf/v2/util/guid"
) )
// https://github.com/gogf/gf/issues/1934
func Test_Issue1934(t *testing.T) { func Test_Issue1934(t *testing.T) {
table := createInitTable() table := createInitTable()
defer dropTable(table) defer dropTable(table)
@ -460,12 +461,12 @@ func Test_Issue2105(t *testing.T) {
// https://github.com/gogf/gf/issues/2231 // https://github.com/gogf/gf/issues/2231
func Test_Issue2231(t *testing.T) { func Test_Issue2231(t *testing.T) {
linkPattern := `(\w+):([\w\-]*):(.*?)@(\w+?)\((.+?)\)/{0,1}([^\?]*)\?{0,1}(.*)` var (
link := `mysql:root:12345678@tcp(127.0.0.1:3306)/a正bc式?loc=Local&parseTime=true` 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) { gtest.C(t, func(t *gtest.T) {
match, err := gregex.MatchString(pattern, link)
match, err := gregex.MatchString(linkPattern, link)
t.AssertNil(err) t.AssertNil(err)
t.Assert(match[1], "mysql") t.Assert(match[1], "mysql")
t.Assert(match[2], "root") t.Assert(match[2], "root")
@ -476,3 +477,190 @@ func Test_Issue2231(t *testing.T) {
t.Assert(match[7], "loc=Local&parseTime=true") 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 *gdb.Core
} }
const (
quoteChar = `"`
)
func init() { func init() {
if err := gdb.Register(`oracle`, New()); err != nil { if err := gdb.Register(`oracle`, New()); err != nil {
panic(err) 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. // GetChars returns the security char for this type of database.
func (d *Driver) GetChars() (charLeft string, charRight string) { 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. // DoFilter deals with the sql string before commits it to underlying sql driver.
@ -217,7 +221,7 @@ func (d *Driver) TableFields(
var ( var (
result gdb.Result result gdb.Result
link gdb.Link link gdb.Link
useSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...) usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
structureSql = fmt.Sprintf(` structureSql = fmt.Sprintf(`
SELECT SELECT
COLUMN_NAME AS FIELD, COLUMN_NAME AS FIELD,
@ -230,7 +234,7 @@ FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`,
strings.ToUpper(table), strings.ToUpper(table),
) )
) )
if link, err = d.SlaveLink(useSchema); err != nil { if link, err = d.SlaveLink(usedSchema); err != nil {
return nil, err return nil, err
} }
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql)) structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))

View File

@ -36,6 +36,7 @@ type Driver struct {
const ( const (
internalPrimaryKeyInCtx gctx.StrKey = "primary_key" internalPrimaryKeyInCtx gctx.StrKey = "primary_key"
defaultSchema = "public" defaultSchema = "public"
quoteChar = `"`
) )
func init() { 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. // GetChars returns the security char for this type of database.
func (d *Driver) GetChars() (charLeft string, charRight string) { func (d *Driver) GetChars() (charLeft string, charRight string) {
return `"`, `"` return quoteChar, quoteChar
} }
// CheckLocalTypeForField checks and returns corresponding local golang type for given db type. // 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 ( var (
result gdb.Result result gdb.Result
link gdb.Link link gdb.Link
useSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...) usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
structureSql = fmt.Sprintf(` structureSql = fmt.Sprintf(`
SELECT a.attname AS field, t.typname AS type,a.attnotnull as null, 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 (case when d.contype is not null then 'pri' else '' end) as key
@ -288,7 +289,7 @@ ORDER BY a.attnum`,
table, table,
) )
) )
if link, err = d.SlaveLink(useSchema); err != nil { if link, err = d.SlaveLink(usedSchema); err != nil {
return nil, err return nil, err
} }
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql)) structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))

View File

@ -33,6 +33,10 @@ type Driver struct {
*gdb.Core *gdb.Core
} }
const (
quoteChar = "`"
)
func init() { func init() {
if err := gdb.Register(`sqlite`, New()); err != nil { if err := gdb.Register(`sqlite`, New()); err != nil {
panic(err) 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. // GetChars returns the security char for this type of database.
func (d *Driver) GetChars() (charLeft string, charRight string) { 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. // 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, ctx context.Context, table string, schema ...string,
) (fields map[string]*gdb.TableField, err error) { ) (fields map[string]*gdb.TableField, err error) {
var ( var (
result gdb.Result result gdb.Result
link gdb.Link link gdb.Link
useSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...) 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 return nil, err
} }
result, err = d.DoSelect(ctx, link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, table)) 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) { func Test_DB_Ping(t *testing.T) {
gtest.C(t, func(t *gtest.T) { gtest.C(t, func(t *gtest.T) {
err1 := db.PingMaster() err1 := db.PingMaster()

View File

@ -246,7 +246,7 @@ func Test_AdapterRedis_SetIfNotExistFunc(t *testing.T) {
return 11, nil return 11, nil
}, 0) }, 0)
t.AssertNil(err) 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 return 11, nil
}, 0) }, 0)
t.AssertNil(err) 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. // Master creates and returns a connection from master node if master-slave configured.
// It returns the default connection if master-slave not configured. // It returns the default connection if master-slave not configured.
func (c *Core) Master(schema ...string) (*sql.DB, error) { 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. // Slave creates and returns a connection from slave node if master-slave configured.
// It returns the default connection if master-slave not configured. // It returns the default connection if master-slave not configured.
func (c *Core) Slave(schema ...string) (*sql.DB, error) { 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. // GetAll queries and returns data records from database.

View File

@ -276,7 +276,7 @@ func parseConfigNodeLink(node *ConfigNode) *ConfigNode {
node.Pass = match[3] node.Pass = match[3]
node.Protocol = match[4] node.Protocol = match[4]
array := gstr.Split(match[5], ":") array := gstr.Split(match[5], ":")
if len(array) == 2 { if len(array) == 2 && node.Protocol != "file" {
node.Host = array[0] node.Host = array[0]
node.Port = array[1] node.Port = array[1]
node.Name = match[6] node.Name = match[6]

View File

@ -10,9 +10,10 @@ package gdb
import ( import (
"context" "context"
"database/sql" "database/sql"
"github.com/gogf/gf/v2/util/gconv"
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
"reflect"
"github.com/gogf/gf/v2" "github.com/gogf/gf/v2"
"github.com/gogf/gf/v2/container/gvar" "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) c.writeSqlToLogger(ctx, sqlObj)
} }
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
err = gerror.NewCodef( err = gerror.WrapCode(
gcode.CodeDbOperationError, gcode.CodeDbOperationError,
"%s, %s", err,
err.Error(),
FormatSqlWithArgs(in.Sql, in.Args), FormatSqlWithArgs(in.Sql, in.Args),
) )
} }
@ -364,26 +364,18 @@ func (c *Core) RowsToResult(ctx context.Context, rows *sql.Rows) (Result, error)
return nil, nil return nil, nil
} }
// Column names and types. // Column names and types.
columns, err := rows.ColumnTypes() columnTypes, err := rows.ColumnTypes()
if err != nil { if err != nil {
return nil, err return nil, err
} }
var ( if len(columnTypes) > 0 {
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 internalData := c.GetInternalCtxDataFromCtx(ctx); internalData != nil { if internalData := c.GetInternalCtxDataFromCtx(ctx); internalData != nil {
internalData.FirstResultColumn = columnNames[0] internalData.FirstResultColumn = columnTypes[0].Name()
} }
} }
var ( var (
values = make([]interface{}, len(columnNames)) values = make([]interface{}, len(columnTypes))
result = make(Result, 0) result = make(Result, 0)
scanArgs = make([]interface{}, len(values)) scanArgs = make([]interface{}, len(values))
) )
@ -399,13 +391,13 @@ func (c *Core) RowsToResult(ctx context.Context, rows *sql.Rows) (Result, error)
if value == nil { if value == nil {
// DO NOT use `gvar.New(nil)` here as it creates an initialized object // DO NOT use `gvar.New(nil)` here as it creates an initialized object
// which will cause struct converting issue. // which will cause struct converting issue.
record[columnNames[i]] = nil record[columnTypes[i].Name()] = nil
} else { } else {
var convertedValue interface{} 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 return nil, err
} }
record[columnNames[i]] = gvar.New(convertedValue) record[columnTypes[i].Name()] = gvar.New(convertedValue)
} }
} }
result = append(result, record) result = append(result, record)
@ -415,3 +407,23 @@ func (c *Core) RowsToResult(ctx context.Context, rows *sql.Rows) (Result, error)
} }
return result, nil 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

@ -16,6 +16,7 @@ import (
"time" "time"
"github.com/gogf/gf/v2/container/garray" "github.com/gogf/gf/v2/container/garray"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/internal/empty" "github.com/gogf/gf/v2/internal/empty"
"github.com/gogf/gf/v2/internal/reflection" "github.com/gogf/gf/v2/internal/reflection"
"github.com/gogf/gf/v2/internal/utils" "github.com/gogf/gf/v2/internal/utils"
@ -210,7 +211,7 @@ func DataToMapDeep(value interface{}) map[string]interface{} {
m := gconv.Map(value, structTagPriority...) m := gconv.Map(value, structTagPriority...)
for k, v := range m { for k, v := range m {
switch v.(type) { switch v.(type) {
case time.Time, *time.Time, gtime.Time, *gtime.Time: case time.Time, *time.Time, gtime.Time, *gtime.Time, gjson.Json, *gjson.Json:
m[k] = v m[k] = v
default: default:
@ -562,16 +563,16 @@ func formatWhereHolder(ctx context.Context, db DB, in formatWhereHolderInput) (n
if i >= len(in.Args) { if i >= len(in.Args) {
break break
} }
// ===============================================================
// Sub query, which is always used along with a string condition. // 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 index := -1
whereStr, _ = gregex.ReplaceStringFunc(`(\?)`, whereStr, func(s string) string { whereStr, _ = gregex.ReplaceStringFunc(`(\?)`, whereStr, func(s string) string {
index++ index++
if i+len(newArgs) == index { if i+len(newArgs) == index {
sqlWithHolder, holderArgs := model.getFormattedSqlAndArgs( sqlWithHolder, holderArgs := subModel.getHolderAndArgsAsSubModel(ctx)
ctx, queryTypeNormal, false, in.Args = gutil.SliceInsertAfter(in.Args, i, holderArgs...)
)
newArgs = append(newArgs, holderArgs...)
// Automatically adding the brackets. // Automatically adding the brackets.
return "(" + sqlWithHolder + ")" return "(" + sqlWithHolder + ")"
} }

View File

@ -9,11 +9,12 @@ package gdb
import ( import (
"database/sql" "database/sql"
"fmt" "fmt"
"github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror" "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/text/gstr"
"github.com/gogf/gf/v2/os/gtime"
) )
// Delete does "DELETE FROM ... " statement for the model. // Delete does "DELETE FROM ... " statement for the model.
@ -30,11 +31,27 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
} }
}() }()
var ( var (
fieldNameDelete = m.getSoftFieldNameDeleted() fieldNameDelete = m.getSoftFieldNameDeleted("", m.tablesInit)
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(ctx, false, false) 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. // Soft deleting.
if !m.unscoped && fieldNameDelete != "" { if fieldNameDelete != "" {
in := &HookUpdateInput{ in := &HookUpdateInput{
internalParamHookUpdate: internalParamHookUpdate{ internalParamHookUpdate: internalParamHookUpdate{
internalParamHook: internalParamHook{ internalParamHook: internalParamHook{
@ -45,18 +62,11 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
Model: m, Model: m,
Table: m.tables, Table: m.tables,
Data: fmt.Sprintf(`%s=?`, m.db.GetCore().QuoteString(fieldNameDelete)), Data: fmt.Sprintf(`%s=?`, m.db.GetCore().QuoteString(fieldNameDelete)),
Condition: conditionWhere + conditionExtra, Condition: conditionStr,
Args: append([]interface{}{gtime.Now().String()}, conditionArgs...), Args: append([]interface{}{gtime.Now().String()}, conditionArgs...),
} }
return in.Next(ctx) 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{ in := &HookDeleteInput{
internalParamHookDelete: internalParamHookDelete{ internalParamHookDelete: internalParamHookDelete{

View File

@ -255,8 +255,8 @@ func (m *Model) doInsertWithOption(ctx context.Context, insertOption int) (resul
var ( var (
list List list List
nowString = gtime.Now().String() nowString = gtime.Now().String()
fieldNameCreate = m.getSoftFieldNameCreated() fieldNameCreate = m.getSoftFieldNameCreated("", m.tablesInit)
fieldNameUpdate = m.getSoftFieldNameUpdated() fieldNameUpdate = m.getSoftFieldNameUpdated("", m.tablesInit)
) )
newData, err := m.filterDataForInsertOrUpdate(m.data) newData, err := m.filterDataForInsertOrUpdate(m.data)
if err != nil { if err != nil {

View File

@ -497,7 +497,9 @@ func (m *Model) doGetAllBySql(ctx context.Context, queryType queryType, sql stri
return 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 { switch queryType {
case queryTypeCount: case queryTypeCount:
queryFields := "COUNT(1)" 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 { func (m *Model) getAutoPrefix() string {
autoPrefix := "" autoPrefix := ""
if gstr.Contains(m.tables, " JOIN ") { 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`. // 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. // 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() var autoPrefix = m.getAutoPrefix()
// GROUP BY. // GROUP BY.
if m.groupBy != "" { 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. // 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. // 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 '-'/'_'/'.'/' '. // 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. // It checks whether this feature disabled.
if m.db.GetConfig().TimeMaintainDisabled { if m.db.GetConfig().TimeMaintainDisabled {
return "" return ""
} }
tableName := "" tableName := ""
if len(table) > 0 { if table != "" {
tableName = table[0] tableName = table
} else { } else {
tableName = m.tablesInit tableName = m.tablesInit
} }
config := m.db.GetConfig() config := m.db.GetConfig()
if config.CreatedAt != "" { 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. // 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. // 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 '-'/'_'/'.'/' '. // 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. // It checks whether this feature disabled.
if m.db.GetConfig().TimeMaintainDisabled { if m.db.GetConfig().TimeMaintainDisabled {
return "" return ""
} }
tableName := "" tableName := ""
if len(table) > 0 { if table != "" {
tableName = table[0] tableName = table
} else { } else {
tableName = m.tablesInit tableName = m.tablesInit
} }
config := m.db.GetConfig() config := m.db.GetConfig()
if config.UpdatedAt != "" { 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. // 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. // 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 '-'/'_'/'.'/' '. // 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. // It checks whether this feature disabled.
if m.db.GetConfig().TimeMaintainDisabled { if m.db.GetConfig().TimeMaintainDisabled {
return "" return ""
} }
tableName := "" tableName := ""
if len(table) > 0 { if table != "" {
tableName = table[0] tableName = table
} else { } else {
tableName = m.tablesInit tableName = m.tablesInit
} }
config := m.db.GetConfig() config := m.db.GetConfig()
if config.DeletedAt != "" { 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. // 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. // Ignore the error from TableFields.
fieldsMap, _ := m.TableFields(table) fieldsMap, _ := m.TableFields(table, schema)
if len(fieldsMap) > 0 { if len(fieldsMap) > 0 {
for _, key := range keys { for _, key := range keys {
field, _ = gutil.MapPossibleItemByKey( field, _ = gutil.MapPossibleItemByKey(
@ -141,26 +141,33 @@ func (m *Model) getConditionForSoftDeleting() string {
return conditionArray.Join(" AND ") return conditionArray.Join(" AND ")
} }
// Only one table. // 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 fmt.Sprintf(`%s IS NULL`, m.db.GetCore().QuoteWord(fieldName))
} }
return "" return ""
} }
// getConditionOfTableStringForSoftDeleting does something as its name describes. // 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 { func (m *Model) getConditionOfTableStringForSoftDeleting(s string) string {
var ( var (
field string field string
table string table string
schema string
array1 = gstr.SplitAndTrim(s, " ") array1 = gstr.SplitAndTrim(s, " ")
array2 = gstr.SplitAndTrim(array1[0], ".") array2 = gstr.SplitAndTrim(array1[0], ".")
) )
if len(array2) >= 2 { if len(array2) >= 2 {
table = array2[1] table = array2[1]
schema = array2[0]
} else { } else {
table = array2[0] table = array2[0]
} }
field = m.getSoftFieldNameDeleted(table) field = m.getSoftFieldNameDeleted(schema, table)
if field == "" { if field == "" {
return "" return ""
} }

View File

@ -9,6 +9,7 @@ package gdb
import ( import (
"database/sql" "database/sql"
"fmt" "fmt"
"github.com/gogf/gf/v2/internal/intlog"
"reflect" "reflect"
"github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gcode"
@ -46,9 +47,14 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
var ( var (
updateData = m.data updateData = m.data
reflectInfo = reflection.OriginTypeAndKind(updateData) reflectInfo = reflection.OriginTypeAndKind(updateData)
fieldNameUpdate = m.getSoftFieldNameUpdated() fieldNameUpdate = m.getSoftFieldNameUpdated("", m.tablesInit)
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(ctx, false, false) conditionWhere, conditionExtra, conditionArgs = m.formatCondition(ctx, false, false)
conditionStr = conditionWhere + conditionExtra
) )
if m.unscoped {
fieldNameUpdate = ""
}
switch reflectInfo.OriginKind { switch reflectInfo.OriginKind {
case reflect.Map, reflect.Struct: case reflect.Map, reflect.Struct:
var dataMap map[string]interface{} var dataMap map[string]interface{}
@ -57,7 +63,7 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
return nil, err return nil, err
} }
// Automatically update the record updating time. // Automatically update the record updating time.
if !m.unscoped && fieldNameUpdate != "" { if fieldNameUpdate != "" {
dataMap[fieldNameUpdate] = gtime.Now().String() dataMap[fieldNameUpdate] = gtime.Now().String()
} }
updateData = dataMap updateData = dataMap
@ -65,7 +71,7 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
default: default:
updates := gconv.String(m.data) updates := gconv.String(m.data)
// Automatically update the record updating time. // Automatically update the record updating time.
if !m.unscoped && fieldNameUpdate != "" { if fieldNameUpdate != "" {
if fieldNameUpdate != "" && !gstr.Contains(updates, fieldNameUpdate) { if fieldNameUpdate != "" && !gstr.Contains(updates, fieldNameUpdate) {
updates += fmt.Sprintf(`,%s='%s'`, fieldNameUpdate, gtime.Now().String()) 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 { if err != nil {
return nil, err return nil, err
} }
conditionStr := conditionWhere + conditionExtra
if !gstr.ContainsI(conditionStr, " WHERE ") { 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{ in := &HookUpdateInput{

View File

@ -34,15 +34,15 @@ import (
// //
// We can enable model association operations on attribute `UserDetail` and `UserScores` by: // 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: // Or:
// //
// db.With(UserDetail{}).With(UserDetail{}).Scan(xxx) // db.With(UserDetail{}).With(UserScores{}).Scan(xxx)
// //
// Or: // Or:
// //
// db.With(UserDetail{}, UserDetail{}).Scan(xxx) // db.With(UserDetail{}, UserScores{}).Scan(xxx)
func (m *Model) With(objects ...interface{}) *Model { func (m *Model) With(objects ...interface{}) *Model {
model := m.getModel() model := m.getModel()
for _, object := range objects { for _, object := range objects {

View File

@ -13,7 +13,7 @@ import (
"github.com/gogf/gf/v2/frame/g" "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 { g.DB().Transaction(context.TODO(), func(ctx context.Context, tx gdb.TX) error {
// user // user
result, err := tx.Insert("user", g.Map{ result, err := tx.Insert("user", g.Map{

View File

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

View File

@ -18,6 +18,7 @@ import (
"github.com/gogf/gf/v2/util/gconv" "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) { func Encode(value interface{}) (out []byte, err error) {
if out, err = yaml.Marshal(value); err != nil { if out, err = yaml.Marshal(value); err != nil {
err = gerror.Wrap(err, `yaml.Marshal failed`) err = gerror.Wrap(err, `yaml.Marshal failed`)
@ -25,6 +26,7 @@ func Encode(value interface{}) (out []byte, err error) {
return return
} }
// EncodeIndent encodes `value` to an YAML format content with indent as bytes.
func EncodeIndent(value interface{}, indent string) (out []byte, err error) { func EncodeIndent(value interface{}, indent string) (out []byte, err error) {
out, err = Encode(value) out, err = Encode(value)
if err != nil { if err != nil {
@ -45,18 +47,20 @@ func EncodeIndent(value interface{}, indent string) (out []byte, err error) {
return return
} }
func Decode(value []byte) (interface{}, error) { // Decode parses `content` into and returns as map.
func Decode(content []byte) (map[string]interface{}, error) {
var ( var (
result map[string]interface{} result map[string]interface{}
err error 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`) err = gerror.Wrap(err, `yaml.Unmarshal failed`)
return nil, err return nil, err
} }
return gconv.MapDeep(result), nil return gconv.MapDeep(result), nil
} }
// DecodeTo parses `content` into `result`.
func DecodeTo(value []byte, result interface{}) (err error) { func DecodeTo(value []byte, result interface{}) (err error) {
err = yaml.Unmarshal(value, result) err = yaml.Unmarshal(value, result)
if err != nil { if err != nil {
@ -65,11 +69,12 @@ func DecodeTo(value []byte, result interface{}) (err error) {
return 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 ( var (
result interface{} result interface{}
) )
if result, err = Decode(value); err != nil { if result, err = Decode(content); err != nil {
return nil, err return nil, err
} else { } else {
return json.Marshal(result) return json.Marshal(result)

View File

@ -76,9 +76,7 @@ func Test_Decode(t *testing.T) {
result, err := gyaml.Decode([]byte(yamlStr)) result, err := gyaml.Decode([]byte(yamlStr))
t.AssertNil(err) t.AssertNil(err)
m, ok := result.(map[string]interface{}) t.Assert(result, map[string]interface{}{
t.Assert(ok, true)
t.Assert(m, map[string]interface{}{
"url": "https://goframe.org", "url": "https://goframe.org",
"server": g.Slice{"120.168.117.21", "120.168.117.22"}, "server": g.Slice{"120.168.117.21", "120.168.117.22"},
"pi": 3.14, "pi": 3.14,

View File

@ -92,7 +92,7 @@ func IsEmpty(value interface{}) bool {
// Common interfaces checks. // Common interfaces checks.
// ========================= // =========================
if f, ok := value.(iTime); ok { if f, ok := value.(iTime); ok {
if f == nil { if f == (*time.Time)(nil) {
return true return true
} }
return f.IsZero() return f.IsZero()

View File

@ -45,56 +45,58 @@ func TestSafeMutex(t *testing.T) {
go func() { go func() {
safeLock.Lock() safeLock.Lock()
array.Append(1) array.Append(1)
time.Sleep(100 * time.Millisecond) time.Sleep(1000 * time.Millisecond)
array.Append(1) array.Append(1)
safeLock.Unlock() safeLock.Unlock()
}() }()
go func() { go func() {
time.Sleep(10 * time.Millisecond) time.Sleep(100 * time.Millisecond)
safeLock.Lock() safeLock.Lock()
array.Append(1) array.Append(1)
time.Sleep(200 * time.Millisecond) time.Sleep(2000 * time.Millisecond)
array.Append(1) array.Append(1)
safeLock.Unlock() safeLock.Unlock()
}() }()
time.Sleep(50 * time.Millisecond) time.Sleep(500 * time.Millisecond)
t.Assert(array.Len(), 1) t.Assert(array.Len(), 1)
time.Sleep(80 * time.Millisecond) time.Sleep(800 * time.Millisecond)
t.Assert(array.Len(), 3) t.Assert(array.Len(), 3)
time.Sleep(100 * time.Millisecond) time.Sleep(1000 * time.Millisecond)
t.Assert(array.Len(), 3) t.Assert(array.Len(), 3)
time.Sleep(100 * time.Millisecond) time.Sleep(1000 * time.Millisecond)
t.Assert(array.Len(), 4) t.Assert(array.Len(), 4)
}) })
} }
func TestUnsafeMutex(t *testing.T) { func TestUnsafeMutex(t *testing.T) {
gtest.C(t, func(t *gtest.T) { gtest.C(t, func(t *gtest.T) {
unsafeLock := mutex.New() var (
array := garray.New(true) unsafeLock = mutex.New()
array = garray.New(true)
)
go func() { go func() {
unsafeLock.Lock() unsafeLock.Lock()
array.Append(1) array.Append(1)
time.Sleep(100 * time.Millisecond) time.Sleep(1000 * time.Millisecond)
array.Append(1) array.Append(1)
unsafeLock.Unlock() unsafeLock.Unlock()
}() }()
go func() { go func() {
time.Sleep(10 * time.Millisecond) time.Sleep(100 * time.Millisecond)
unsafeLock.Lock() unsafeLock.Lock()
array.Append(1) array.Append(1)
time.Sleep(200 * time.Millisecond) time.Sleep(2000 * time.Millisecond)
array.Append(1) array.Append(1)
unsafeLock.Unlock() unsafeLock.Unlock()
}() }()
time.Sleep(50 * time.Millisecond) time.Sleep(500 * time.Millisecond)
t.Assert(array.Len(), 2) t.Assert(array.Len(), 2)
time.Sleep(100 * time.Millisecond) time.Sleep(1000 * time.Millisecond)
t.Assert(array.Len(), 3) t.Assert(array.Len(), 3)
time.Sleep(50 * time.Millisecond) time.Sleep(500 * time.Millisecond)
t.Assert(array.Len(), 3) t.Assert(array.Len(), 3)
time.Sleep(100 * time.Millisecond) time.Sleep(1000 * time.Millisecond)
t.Assert(array.Len(), 4) t.Assert(array.Len(), 4)
}) })
} }

View File

@ -15,6 +15,7 @@ import (
"net/url" "net/url"
"time" "time"
"github.com/gogf/gf/v2/net/ghttp/internal/response"
"github.com/gogf/gf/v2/net/gtrace" "github.com/gogf/gf/v2/net/gtrace"
"github.com/gogf/gf/v2/os/gfile" "github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gres" "github.com/gogf/gf/v2/os/gres"
@ -34,7 +35,7 @@ func newResponse(s *Server, w http.ResponseWriter) *Response {
r := &Response{ r := &Response{
Server: s, Server: s,
ResponseWriter: &ResponseWriter{ ResponseWriter: &ResponseWriter{
writer: w, writer: response.NewWriter(w),
buffer: bytes.NewBuffer(nil), buffer: bytes.NewBuffer(nil),
}, },
} }
@ -152,7 +153,6 @@ func (r *Response) ClearBuffer() {
// //
// See http.ServeContent // See http.ServeContent
func (r *Response) ServeContent(name string, modTime time.Time, content io.ReadSeeker) { 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) 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. // Write writes `content` to the response buffer.
func (r *Response) Write(content ...interface{}) { func (r *Response) Write(content ...interface{}) {
if r.hijacked || len(content) == 0 { if r.writer.IsHijacked() || len(content) == 0 {
return return
} }
if r.Status == 0 { if r.Status == 0 {

View File

@ -12,15 +12,15 @@ import (
"bytes" "bytes"
"net" "net"
"net/http" "net/http"
"github.com/gogf/gf/v2/net/ghttp/internal/response"
) )
// ResponseWriter is the custom writer for http response. // ResponseWriter is the custom writer for http response.
type ResponseWriter struct { type ResponseWriter struct {
Status int // HTTP status. Status int // HTTP status.
writer http.ResponseWriter // The underlying ResponseWriter. writer *response.Writer // The underlying ResponseWriter.
buffer *bytes.Buffer // The output buffer. 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.
} }
// RawWriter returns the underlying ResponseWriter. // RawWriter returns the underlying ResponseWriter.
@ -46,18 +46,16 @@ func (w *ResponseWriter) WriteHeader(status int) {
// Hijack implements the interface function of http.Hijacker.Hijack. // Hijack implements the interface function of http.Hijacker.Hijack.
func (w *ResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { func (w *ResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
w.hijacked = true return w.writer.Hijack()
return w.writer.(http.Hijacker).Hijack()
} }
// Flush outputs the buffer to clients and clears the buffer. // Flush outputs the buffer to clients and clears the buffer.
func (w *ResponseWriter) Flush() { func (w *ResponseWriter) Flush() {
if w.hijacked { if w.writer.IsHijacked() {
return return
} }
if w.Status != 0 && !w.isHeaderWritten() { if w.Status != 0 && !w.writer.IsHeaderWrote() {
w.wroteHeader = true
w.writer.WriteHeader(w.Status) w.writer.WriteHeader(w.Status)
} }
// Default status text output. // Default status text output.
@ -69,14 +67,3 @@ func (w *ResponseWriter) Flush() {
w.buffer.Reset() 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/internal/json"
"github.com/gogf/gf/v2/text/gstr" "github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
) )
type SchemaRefs []SchemaRef type SchemaRefs []SchemaRef
@ -54,13 +55,23 @@ func (oai *OpenApiV3) newSchemaRefWithGolangType(golangType reflect.Type, tagMap
} }
schemaRef.Value = schema schemaRef.Value = schema
switch oaiType { switch oaiType {
case case TypeString:
TypeInteger, // Nothing to do.
TypeNumber, case TypeInteger:
TypeString, if schemaRef.Value.Default != nil {
TypeBoolean: schemaRef.Value.Default = gconv.Int64(schemaRef.Value.Default)
// Nothing to do. }
// 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 case
TypeArray: TypeArray:
subSchemaRef, err := oai.newSchemaRefWithGolangType(golangType.Elem(), nil) 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) ok, err = c.redis.SetNX(ctx, redisKey, value)
if err != nil { if err != nil {
return false, err return ok, err
} }
if ok && duration > 0 { if ok && duration > 0 {
// Set the expiration. // Set the expiration.
_, err = c.redis.Expire(ctx, redisKey, int64(duration.Seconds())) _, err = c.redis.Expire(ctx, redisKey, int64(duration.Seconds()))
if err != nil { 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 // 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" "github.com/gogf/gf/v2/os/glog"
) )
func Example_cronAddSingleton() { func ExampleCronAddSingleton() {
gcron.AddSingleton(ctx, "* * * * * *", func(ctx context.Context) { gcron.AddSingleton(ctx, "* * * * * *", func(ctx context.Context) {
glog.Print(context.TODO(), "doing") glog.Print(context.TODO(), "doing")
time.Sleep(2 * time.Second) time.Sleep(2 * time.Second)

View File

@ -12,7 +12,7 @@ import (
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
) )
func Example_context() { func ExampleContext() {
ctx := context.WithValue(context.Background(), "Trace-Id", "123456789") ctx := context.WithValue(context.Background(), "Trace-Id", "123456789")
g.Log().Error(ctx, "runtime error") 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`. // OriginalKind retrieves and returns the original reflect.Kind for Value of Field `f`.
func (f *Field) OriginalKind() reflect.Kind { func (f *Field) OriginalKind() reflect.Kind {
var ( var (
kind = f.Value.Kind() reflectType = f.Value.Type()
value = f.Value reflectKind = reflectType.Kind()
) )
for kind == reflect.Ptr { for reflectKind == reflect.Ptr {
value = value.Elem() reflectType = reflectType.Elem()
kind = value.Kind() reflectKind = reflectType.Kind()
} }
return kind return reflectKind
} }
// Fields retrieves and returns the fields of `pointer` as slice. // Fields retrieves and returns the fields of `pointer` as slice.

View File

@ -13,6 +13,7 @@ import (
"github.com/gogf/gf/v2/container/gtype" "github.com/gogf/gf/v2/container/gtype"
) )
// New creates and returns a Timer.
func New(options ...TimerOptions) *Timer { func New(options ...TimerOptions) *Timer {
t := &Timer{ t := &Timer{
queue: newPriorityQueue(), 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. // Also see Add.
func (t *Timer) DelayAdd(ctx context.Context, delay time.Duration, interval time.Duration, job JobFunc) { func (t *Timer) DelayAdd(ctx context.Context, delay time.Duration, interval time.Duration, job JobFunc) {
t.AddOnce(ctx, delay, func(ctx context.Context) { 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. // Also see AddEntry.
func (t *Timer) DelayAddEntry(ctx context.Context, delay time.Duration, interval time.Duration, job JobFunc, isSingleton bool, times int, status int) { 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) { 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. // Also see AddSingleton.
func (t *Timer) DelayAddSingleton(ctx context.Context, delay time.Duration, interval time.Duration, job JobFunc) { func (t *Timer) DelayAddSingleton(ctx context.Context, delay time.Duration, interval time.Duration, job JobFunc) {
t.AddOnce(ctx, delay, func(ctx context.Context) { 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. // Also see AddOnce.
func (t *Timer) DelayAddOnce(ctx context.Context, delay time.Duration, interval time.Duration, job JobFunc) { func (t *Timer) DelayAddOnce(ctx context.Context, delay time.Duration, interval time.Duration, job JobFunc) {
t.AddOnce(ctx, delay, func(ctx context.Context) { 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. // Also see AddTimes.
func (t *Timer) DelayAddTimes(ctx context.Context, delay time.Duration, interval time.Duration, times int, job JobFunc) { 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) { t.AddOnce(ctx, delay, func(ctx context.Context) {

View File

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

View File

@ -407,7 +407,7 @@ func doMapConvertForMapOrStructValue(in doMapConvertForMapOrStructValueInput) in
array[arrayIndex] = doMapConvertForMapOrStructValue( array[arrayIndex] = doMapConvertForMapOrStructValue(
doMapConvertForMapOrStructValueInput{ doMapConvertForMapOrStructValueInput{
IsRoot: false, IsRoot: false,
Value: rvAttrField.Index(arrayIndex), Value: rvAttrField.Index(arrayIndex).Interface(),
RecursiveType: in.RecursiveType, RecursiveType: in.RecursiveType,
RecursiveOption: in.RecursiveType == recursiveTypeTrue, RecursiveOption: in.RecursiveType == recursiveTypeTrue,
Tags: in.Tags, Tags: in.Tags,
@ -463,7 +463,7 @@ func doMapConvertForMapOrStructValue(in doMapConvertForMapOrStructValueInput) in
for i := 0; i < length; i++ { for i := 0; i < length; i++ {
array[i] = doMapConvertForMapOrStructValue(doMapConvertForMapOrStructValueInput{ array[i] = doMapConvertForMapOrStructValue(doMapConvertForMapOrStructValueInput{
IsRoot: false, IsRoot: false,
Value: reflectValue.Index(i), Value: reflectValue.Index(i).Interface(),
RecursiveType: in.RecursiveType, RecursiveType: in.RecursiveType,
RecursiveOption: in.RecursiveType == recursiveTypeTrue, RecursiveOption: in.RecursiveType == recursiveTypeTrue,
Tags: in.Tags, Tags: in.Tags,

View File

@ -125,9 +125,6 @@ func Interfaces(any interface{}) []interface{} {
return slice return slice
default: default:
if originValueAndKind.OriginValue.IsZero() {
return []interface{}{}
}
return []interface{}{any} return []interface{}{any}
} }
} }

View File

@ -366,15 +366,29 @@ func bindVarToStructAttr(structReflectValue reflect.Value, attrName string, valu
if empty.IsNil(value) { if empty.IsNil(value) {
structFieldValue.Set(reflect.Zero(structFieldValue.Type())) structFieldValue.Set(reflect.Zero(structFieldValue.Type()))
} else { } 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. // Common interface check.
var ok bool var ok bool
if err, ok = bindVarToReflectValueWithInterfaceCheck(structFieldValue, value); ok { if err, ok = bindVarToReflectValueWithInterfaceCheck(structFieldValue, value); ok {
return err return err
} }
// Default converting. // Default converting.
doConvertWithReflectValueSet(structFieldValue, doConvertInput{ doConvertWithReflectValueSet(structFieldValue, doConvertInput{
FromValue: value, FromValue: value,
ToTypeName: structFieldValue.Type().String(), ToTypeName: structFieldTypeName,
ReferValue: structFieldValue, ReferValue: structFieldValue,
}) })
} }
@ -382,7 +396,7 @@ func bindVarToStructAttr(structReflectValue reflect.Value, attrName string, valu
} }
// bindVarToReflectValueWithInterfaceCheck does bind using common interfaces checks. // 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{} var pointer interface{}
if reflectValue.Kind() != reflect.Ptr && reflectValue.CanAddr() { if reflectValue.Kind() != reflect.Ptr && reflectValue.CanAddr() {
reflectValueAddr := reflectValue.Addr() reflectValueAddr := reflectValue.Addr()

View File

@ -8,6 +8,7 @@ package gconv_test
import ( import (
"testing" "testing"
"time"
"github.com/gogf/gf/v2/container/gtype" "github.com/gogf/gf/v2/container/gtype"
"github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/encoding/gjson"
@ -223,3 +224,71 @@ func Test_Issue2381(t *testing.T) {
t.Assert(a1.Flag.String(), a2.Flag.String()) t.Assert(a1.Flag.String(), a2.Flag.String())
}) })
} }
// https://github.com/gogf/gf/issues/2391
func Test_Issue2391(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type Inherit struct {
Ids []int
Ids2 []int64
Flag *gjson.Json
Title string
}
type Test1 struct {
Inherit
}
type Test2 struct {
Inherit
}
var (
a1 Test1
a2 Test2
)
a1 = Test1{
Inherit{
Ids: []int{1, 2, 3},
Ids2: []int64{4, 5, 6},
Flag: gjson.New("[\"1\", \"2\"]"),
Title: "测试",
},
}
err := gconv.Scan(a1, &a2)
t.AssertNil(err)
t.Assert(a1.Ids, a2.Ids)
t.Assert(a1.Ids2, a2.Ids2)
t.Assert(a1.Title, a2.Title)
t.Assert(a1.Flag.String(), a2.Flag.String())
})
}
// https://github.com/gogf/gf/issues/2395
func Test_Issue2395(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type Test struct {
Num int
}
var ()
obj := Test{Num: 0}
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

@ -298,7 +298,7 @@ func Test_Slice_Empty(t *testing.T) {
t.Assert(gconv.Strings(nil), nil) t.Assert(gconv.Strings(nil), nil)
}) })
gtest.C(t, func(t *gtest.T) { gtest.C(t, func(t *gtest.T) {
t.AssertEQ(gconv.SliceAny(""), []interface{}{}) t.AssertEQ(gconv.SliceAny(""), []interface{}{""})
t.Assert(gconv.SliceAny(nil), nil) t.Assert(gconv.SliceAny(nil), nil)
}) })
} }

View File

@ -14,28 +14,52 @@ import (
// SliceCopy does a shallow copy of slice `data` for most commonly used slice type // SliceCopy does a shallow copy of slice `data` for most commonly used slice type
// []interface{}. // []interface{}.
func SliceCopy(data []interface{}) []interface{} { func SliceCopy(slice []interface{}) []interface{} {
newData := make([]interface{}, len(data)) newSlice := make([]interface{}, len(slice))
copy(newData, data) copy(newSlice, slice)
return newData 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. // SliceDelete deletes an element at `index` and returns the new slice.
// It does nothing if the given `index` is invalid. // It does nothing if the given `index` is invalid.
func SliceDelete(data []interface{}, index int) (newSlice []interface{}) { func SliceDelete(slice []interface{}, index int) (newSlice []interface{}) {
if index < 0 || index >= len(data) { if index < 0 || index >= len(slice) {
return data return slice
} }
// Determine array boundaries when deleting to improve deletion efficiency. // Determine array boundaries when deleting to improve deletion efficiency.
if index == 0 { if index == 0 {
return data[1:] return slice[1:]
} else if index == len(data)-1 { } else if index == len(slice)-1 {
return data[:index] return slice[:index]
} }
// If it is a non-boundary delete, // If it is a non-boundary delete,
// it will involve the creation of an array, // it will involve the creation of an array,
// then the deletion is less efficient. // 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{}`. // 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: case reflect.Map, reflect.Struct, reflect.Slice, reflect.Array:
// Recursively check attribute slice/map. // Recursively check attribute slice/map.
_, value = gutil.MapPossibleItemByKey(inputParamMap, field.Name()) _, 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{ v.doCheckValueRecursively(ctx, doCheckValueRecursivelyInput{
Value: value, Value: value,
Kind: field.OriginalKind(), Kind: field.OriginalKind(),

View File

@ -14,7 +14,7 @@ import (
"github.com/gogf/gf/v2/text/gstr" "github.com/gogf/gf/v2/text/gstr"
) )
func Example_Rule_Required() { func ExampleRule_Required() {
type BizReq struct { type BizReq struct {
ID uint `v:"required"` ID uint `v:"required"`
Name string `v:"required"` Name string `v:"required"`
@ -33,7 +33,7 @@ func Example_Rule_Required() {
// The Name field is required // The Name field is required
} }
func Example_Rule_RequiredIf() { func ExampleRule_RequiredIf() {
type BizReq struct { type BizReq struct {
ID uint `v:"required" dc:"Your ID"` ID uint `v:"required" dc:"Your ID"`
Name string `v:"required" dc:"Your name"` Name string `v:"required" dc:"Your name"`
@ -57,7 +57,7 @@ func Example_Rule_RequiredIf() {
// The WifeName field is required // The WifeName field is required
} }
func Example_Rule_RequiredUnless() { func ExampleRule_RequiredUnless() {
type BizReq struct { type BizReq struct {
ID uint `v:"required" dc:"Your ID"` ID uint `v:"required" dc:"Your ID"`
Name string `v:"required" dc:"Your name"` 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 // The WifeName field is required; The HusbandName field is required
} }
func Example_Rule_RequiredWith() { func ExampleRule_RequiredWith() {
type BizReq struct { type BizReq struct {
ID uint `v:"required" dc:"Your ID"` ID uint `v:"required" dc:"Your ID"`
Name string `v:"required" dc:"Your name"` Name string `v:"required" dc:"Your name"`
@ -106,7 +106,7 @@ func Example_Rule_RequiredWith() {
// The HusbandName field is required // The HusbandName field is required
} }
func Example_Rule_RequiredWithAll() { func ExampleRule_RequiredWithAll() {
type BizReq struct { type BizReq struct {
ID uint `v:"required" dc:"Your ID"` ID uint `v:"required" dc:"Your ID"`
Name string `v:"required" dc:"Your name"` Name string `v:"required" dc:"Your name"`
@ -131,7 +131,7 @@ func Example_Rule_RequiredWithAll() {
// The HusbandName field is required // The HusbandName field is required
} }
func Example_Rule_RequiredWithout() { func ExampleRule_RequiredWithout() {
type BizReq struct { type BizReq struct {
ID uint `v:"required" dc:"Your ID"` ID uint `v:"required" dc:"Your ID"`
Name string `v:"required" dc:"Your name"` Name string `v:"required" dc:"Your name"`
@ -155,7 +155,7 @@ func Example_Rule_RequiredWithout() {
// The HusbandName field is required // The HusbandName field is required
} }
func Example_Rule_RequiredWithoutAll() { func ExampleRule_RequiredWithoutAll() {
type BizReq struct { type BizReq struct {
ID uint `v:"required" dc:"Your ID"` ID uint `v:"required" dc:"Your ID"`
Name string `v:"required" dc:"Your name"` Name string `v:"required" dc:"Your name"`
@ -178,7 +178,7 @@ func Example_Rule_RequiredWithoutAll() {
// The HusbandName field is required // The HusbandName field is required
} }
func Example_Rule_Bail() { func ExampleRule_Bail() {
type BizReq struct { type BizReq struct {
Account string `v:"bail|required|length:6,16|same:QQ"` Account string `v:"bail|required|length:6,16|same:QQ"`
QQ string QQ string
@ -202,7 +202,7 @@ func Example_Rule_Bail() {
// The Account value `gf` length must be between 6 and 16 // The Account value `gf` length must be between 6 and 16
} }
func Example_Rule_CaseInsensitive() { func ExampleRule_CaseInsensitive() {
type BizReq struct { type BizReq struct {
Account string `v:"required"` Account string `v:"required"`
Password string `v:"required|ci|same:Password2"` Password string `v:"required|ci|same:Password2"`
@ -223,7 +223,7 @@ func Example_Rule_CaseInsensitive() {
// output: // output:
} }
func Example_Rule_Date() { func ExampleRule_Date() {
type BizReq struct { type BizReq struct {
Date1 string `v:"date"` Date1 string `v:"date"`
Date2 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 // The Date5 value `2021/Oct/31` is not a valid date
} }
func Example_Rule_Datetime() { func ExampleRule_Datetime() {
type BizReq struct { type BizReq struct {
Date1 string `v:"datetime"` Date1 string `v:"datetime"`
Date2 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 // The Date4 value `2021/Dec/01 23:00:00` is not a valid datetime
} }
func Example_Rule_DateFormat() { func ExampleRule_DateFormat() {
type BizReq struct { type BizReq struct {
Date1 string `v:"date-format:Y-m-d"` Date1 string `v:"date-format:Y-m-d"`
Date2 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 // 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 { type BizReq struct {
MailAddr1 string `v:"email"` MailAddr1 string `v:"email"`
MailAddr2 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 // The MailAddr4 value `gf#goframe.org` is not a valid email address
} }
func Example_Rule_Phone() { func ExampleRule_Phone() {
type BizReq struct { type BizReq struct {
PhoneNumber1 string `v:"phone"` PhoneNumber1 string `v:"phone"`
PhoneNumber2 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 // The PhoneNumber4 value `1357891234` is not a valid phone number
} }
func Example_Rule_PhoneLoose() { func ExampleRule_PhoneLoose() {
type BizReq struct { type BizReq struct {
PhoneNumber1 string `v:"phone-loose"` PhoneNumber1 string `v:"phone-loose"`
PhoneNumber2 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 // The PhoneNumber4 value `1357891234` is not a valid phone number
} }
func Example_Rule_Telephone() { func ExampleRule_Telephone() {
type BizReq struct { type BizReq struct {
Telephone1 string `v:"telephone"` Telephone1 string `v:"telephone"`
Telephone2 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 // The Telephone4 value `775421451` is not a valid telephone number
} }
func Example_Rule_Passport() { func ExampleRule_Passport() {
type BizReq struct { type BizReq struct {
Passport1 string `v:"passport"` Passport1 string `v:"passport"`
Passport2 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 // The Passport4 value `gf` is not a valid passport format
} }
func Example_Rule_Password() { func ExampleRule_Password() {
type BizReq struct { type BizReq struct {
Password1 string `v:"password"` Password1 string `v:"password"`
Password2 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 // The Password2 value `gofra` is not a valid password format
} }
func Example_Rule_Password2() { func ExampleRule_Password2() {
type BizReq struct { type BizReq struct {
Password1 string `v:"password2"` Password1 string `v:"password2"`
Password2 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 // The Password4 value `goframe123` is not a valid password format
} }
func Example_Rule_Password3() { func ExampleRule_Password3() {
type BizReq struct { type BizReq struct {
Password1 string `v:"password3"` Password1 string `v:"password3"`
Password2 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 // The Password3 value `Goframe123` is not a valid password format
} }
func Example_Rule_Postcode() { func ExampleRule_Postcode() {
type BizReq struct { type BizReq struct {
Postcode1 string `v:"postcode"` Postcode1 string `v:"postcode"`
Postcode2 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 // The Postcode3 value `1000000` is not a valid postcode format
} }
func Example_Rule_ResidentId() { func ExampleRule_ResidentId() {
type BizReq struct { type BizReq struct {
ResidentID1 string `v:"resident-id"` ResidentID1 string `v:"resident-id"`
} }
@ -552,7 +552,7 @@ func Example_Rule_ResidentId() {
// The ResidentID1 value `320107199506285482` is not a valid resident id number // The ResidentID1 value `320107199506285482` is not a valid resident id number
} }
func Example_Rule_BankCard() { func ExampleRule_BankCard() {
type BizReq struct { type BizReq struct {
BankCard1 string `v:"bank-card"` BankCard1 string `v:"bank-card"`
} }
@ -571,7 +571,7 @@ func Example_Rule_BankCard() {
// The BankCard1 value `6225760079930218` is not a valid bank card number // The BankCard1 value `6225760079930218` is not a valid bank card number
} }
func Example_Rule_QQ() { func ExampleRule_QQ() {
type BizReq struct { type BizReq struct {
QQ1 string `v:"qq"` QQ1 string `v:"qq"`
QQ2 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 // The QQ3 value `514258412a` is not a valid QQ number
} }
func Example_Rule_IP() { func ExampleRule_IP() {
type BizReq struct { type BizReq struct {
IP1 string `v:"ip"` IP1 string `v:"ip"`
IP2 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 // The IP4 value `ze80::812b:1158:1f43:f0d1` is not a valid IP address
} }
func Example_Rule_IPV4() { func ExampleRule_IPV4() {
type BizReq struct { type BizReq struct {
IP1 string `v:"ipv4"` IP1 string `v:"ipv4"`
IP2 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 // The IP2 value `520.255.255.255` is not a valid IPv4 address
} }
func Example_Rule_IPV6() { func ExampleRule_IPV6() {
type BizReq struct { type BizReq struct {
IP1 string `v:"ipv6"` IP1 string `v:"ipv6"`
IP2 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 // The IP2 value `ze80::812b:1158:1f43:f0d1` is not a valid IPv6 address
} }
func Example_Rule_Mac() { func ExampleRule_Mac() {
type BizReq struct { type BizReq struct {
Mac1 string `v:"mac"` Mac1 string `v:"mac"`
Mac2 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 // 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 { type BizReq struct {
URL1 string `v:"url"` URL1 string `v:"url"`
URL2 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 // The URL3 value `ws://goframe.org` is not a valid URL address
} }
func Example_Rule_Domain() { func ExampleRule_Domain() {
type BizReq struct { type BizReq struct {
Domain1 string `v:"domain"` Domain1 string `v:"domain"`
Domain2 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 // The Domain4 value `1a.2b` is not a valid domain format
} }
func Example_Rule_Size() { func ExampleRule_Size() {
type BizReq struct { type BizReq struct {
Size1 string `v:"size:10"` Size1 string `v:"size:10"`
Size2 string `v:"size:5"` Size2 string `v:"size:5"`
@ -754,7 +754,7 @@ func Example_Rule_Size() {
// The Size2 value `goframe` length must be 5 // The Size2 value `goframe` length must be 5
} }
func Example_Rule_Length() { func ExampleRule_Length() {
type BizReq struct { type BizReq struct {
Length1 string `v:"length:5,10"` Length1 string `v:"length:5,10"`
Length2 string `v:"length:10,15"` 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 // The Length2 value `goframe` length must be between 10 and 15
} }
func Example_Rule_MinLength() { func ExampleRule_MinLength() {
type BizReq struct { type BizReq struct {
MinLength1 string `v:"min-length:10"` MinLength1 string `v:"min-length:10"`
MinLength2 string `v:"min-length:8"` 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 // The MinLength2 value `goframe` length must be equal or greater than 8
} }
func Example_Rule_MaxLength() { func ExampleRule_MaxLength() {
type BizReq struct { type BizReq struct {
MaxLength1 string `v:"max-length:10"` MaxLength1 string `v:"max-length:10"`
MaxLength2 string `v:"max-length:5"` 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 // The MaxLength2 value `goframe` length must be equal or lesser than 5
} }
func Example_Rule_Between() { func ExampleRule_Between() {
type BizReq struct { type BizReq struct {
Age1 int `v:"between:1,100"` Age1 int `v:"between:1,100"`
Age2 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 // The Score2 value `-0.5` must be between 0 and 10
} }
func Example_Rule_Min() { func ExampleRule_Min() {
type BizReq struct { type BizReq struct {
Age1 int `v:"min:100"` Age1 int `v:"min:100"`
Age2 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 // The Score1 value `9.8` must be equal or greater than 10
} }
func Example_Rule_Max() { func ExampleRule_Max() {
type BizReq struct { type BizReq struct {
Age1 int `v:"max:100"` Age1 int `v:"max:100"`
Age2 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 // The Score2 value `10.1` must be equal or lesser than 10
} }
func Example_Rule_Json() { func ExampleRule_Json() {
type BizReq struct { type BizReq struct {
JSON1 string `v:"json"` JSON1 string `v:"json"`
JSON2 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 // The JSON2 value `{"name":"goframe","author":"郭强","test"}` is not a valid JSON string
} }
func Example_Rule_Integer() { func ExampleRule_Integer() {
type BizReq struct { type BizReq struct {
Integer string `v:"integer"` Integer string `v:"integer"`
Float string `v:"integer"` Float string `v:"integer"`
@ -940,7 +940,7 @@ func Example_Rule_Integer() {
// The Str value `goframe` is not an integer // The Str value `goframe` is not an integer
} }
func Example_Rule_Float() { func ExampleRule_Float() {
type BizReq struct { type BizReq struct {
Integer string `v:"float"` Integer string `v:"float"`
Float 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 // The Str value `goframe` is not of valid float type
} }
func Example_Rule_Boolean() { func ExampleRule_Boolean() {
type BizReq struct { type BizReq struct {
Boolean bool `v:"boolean"` Boolean bool `v:"boolean"`
Integer int `v:"boolean"` Integer int `v:"boolean"`
@ -993,7 +993,7 @@ func Example_Rule_Boolean() {
// The Str3 value `goframe` field must be true or false // The Str3 value `goframe` field must be true or false
} }
func Example_Rule_Same() { func ExampleRule_Same() {
type BizReq struct { type BizReq struct {
Name string `v:"required"` Name string `v:"required"`
Password string `v:"required|same:Password2"` 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 // The Password value `goframe.org` must be the same as field Password2
} }
func Example_Rule_Different() { func ExampleRule_Different() {
type BizReq struct { type BizReq struct {
Name string `v:"required"` Name string `v:"required"`
MailAddr 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 // The OtherMailAddr value `gf@goframe.org` must be different from field MailAddr
} }
func Example_Rule_In() { func ExampleRule_In() {
type BizReq struct { type BizReq struct {
ID uint `v:"required" dc:"Your Id"` ID uint `v:"required" dc:"Your Id"`
Name string `v:"required" dc:"Your name"` 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 // The Gender value `3` is not in acceptable range: 0,1,2
} }
func Example_Rule_NotIn() { func ExampleRule_NotIn() {
type BizReq struct { type BizReq struct {
ID uint `v:"required" dc:"Your Id"` ID uint `v:"required" dc:"Your Id"`
Name string `v:"required" dc:"Your name"` 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 // The InvalidIndex value `1` must not be in range: -1,0,1
} }
func Example_Rule_Regex() { func ExampleRule_Regex() {
type BizReq struct { type BizReq struct {
Regex1 string `v:"regex:[1-9][0-9]{4,14}"` Regex1 string `v:"regex:[1-9][0-9]{4,14}"`
Regex2 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} // 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 { type BizReq struct {
Regex1 string `v:"regex:\\d{4}"` Regex1 string `v:"regex:\\d{4}"`
Regex2 string `v:"not-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} // The Regex2 value `1234` should not be in regex of: \d{4}
} }
func Example_Rule_After() { func ExampleRule_After() {
type BizReq struct { type BizReq struct {
Time1 string Time1 string
Time2 string `v:"after:Time1"` 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` // 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 { type BizReq struct {
Time1 string Time1 string
Time2 string `v:"after-equal:Time1"` 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` // 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 { type BizReq struct {
Time1 string `v:"before:Time3"` Time1 string `v:"before:Time3"`
Time2 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` // 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 { type BizReq struct {
Time1 string `v:"before-equal:Time3"` Time1 string `v:"before-equal:Time3"`
Time2 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 // The Time1 value `2022-09-02` must be before or equal to field Time3
} }
func Example_Rule_Array() { func ExampleRule_Array() {
type BizReq struct { type BizReq struct {
Value1 string `v:"array"` Value1 string `v:"array"`
Value2 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 // The Value1 value `1,2,3` is not of valid array type
} }
func Example_Rule_EQ() { func ExampleRule_EQ() {
type BizReq struct { type BizReq struct {
Name string `v:"required"` Name string `v:"required"`
Password string `v:"required|eq:Password2"` 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` // The Password value `goframe.org` must be equal to field Password2 value `goframe.net`
} }
func Example_Rule_NotEQ() { func ExampleRule_NotEQ() {
type BizReq struct { type BizReq struct {
Name string `v:"required"` Name string `v:"required"`
MailAddr 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` // 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 { type BizReq struct {
Value1 int Value1 int
Value2 int `v:"gt:Value1"` 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` // The Value2 value `1` must be greater than field Value1 value `1`
} }
func Example_Rule_GTE() { func ExampleRule_GTE() {
type BizReq struct { type BizReq struct {
Value1 int Value1 int
Value2 int `v:"gte:Value1"` 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` // 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 { type BizReq struct {
Value1 int Value1 int
Value2 int `v:"lt:Value1"` 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` // The Value3 value `2` must be lesser than field Value1 value `2`
} }
func Example_Rule_LTE() { func ExampleRule_LTE() {
type BizReq struct { type BizReq struct {
Value1 int Value1 int
Value2 int `v:"lte:Value1"` 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` // 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 { type BizReq struct {
Value1 []int `v:"foreach|in:1,2,3"` Value1 []int `v:"foreach|in:1,2,3"`
Value2 []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") 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 ( const (
// VERSION is the current GoFrame version. // VERSION is the current GoFrame version.
VERSION = "v2.3.0" VERSION = "v2.3.2"
) )