Merge branch 'master' of https://github.com/DGuang21/gf into feature-clickhouse-driver

This commit is contained in:
daguang
2022-03-31 21:29:17 +08:00
336 changed files with 6041 additions and 2507 deletions

View File

@ -1,12 +1,11 @@
<!-- Please answer these questions before submitting your issue. Thanks! -->
<!-- 为高效处理您的疑问如果觉得是BUG类问题请您务必提供可复现该问题的最小可运行代码 -->
<!-- 为高效处理您的疑问如果觉得是BUG类问题请您务必提供可复现该问题的最小可运行代码 -->
<!-- 为高效处理您的疑问如果觉得是BUG类问题请您务必提供可复现该问题的最小可运行代码 -->
<!-- 为高效处理您的疑问如果觉得是BUG类问题请您务必提供可复现该问题的最小可运行代码否则issue可能会被延期处理 -->
<!-- 为高效处理您的疑问如果觉得是BUG类问题请您务必提供可复现该问题的最小可运行代码否则issue可能会被延期处理 -->
<!-- 为高效处理您的疑问如果觉得是BUG类问题请您务必提供可复现该问题的最小可运行代码否则issue可能会被延期处理 -->
<!-- 重要的事情说三遍! -->
### 1. What version of `Go` and system type/arch are you using?
<!--
Please paste the output of command `go version` from your terminal.
What expect to see is like: `go 1.12, linux/amd64`
@ -14,7 +13,6 @@ What expect to see is like: `go 1.12, linux/amd64`
### 2. What version of `GoFrame` are you using?
<!-- You can find the GF version from your `go.mod`, or from the `version.go` in `GF` -->
@ -23,7 +21,6 @@ What expect to see is like: `go 1.12, linux/amd64`
### 4. What did you do?
<!--
If possible, provide a copy of shortest codes for reproducing the error.
A complete runnable program is best.

View File

@ -6,6 +6,10 @@ on:
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
env:
TZ: Asia/Shanghai
jobs:
build:
name: Build And Release
@ -26,37 +30,21 @@ jobs:
GOOS=linux GOARCH=amd64 go build main.go
chmod +x main
./main install -y
# gf build
- name: Build CLI Binary For All Platform
run: |
cd cmd/gf
gf build main.go -n gf -a all -s all
# 处理gf-cli批量编译后的文件结构
- name: Move Files Before Upx
- name: Move Files Before Release
run: |
cd cmd/gf/bin
cd cmd/gf/temp
for OS in *;do for FILE in $OS/*;\
do if [[ ${OS} =~ 'windows' ]];\
then rm -rf noupx && mkdir noupx && mv $FILE noupx/gf_$OS.exe && rm -rf $OS;\
then mv $FILE gf_$OS.exe && rm -rf $OS;\
else mv $FILE gf_$OS && rm -rf $OS;\
fi;done;done
# UPX 加壳所有文件
- name: Upx All Binary
uses: gacts/upx@master
with:
dir: 'cmd/gf/bin'
upx_args: '-9'
# 移动未UPX的windows程序到上传bin目录下
- name: Move Files After Upx
run: |
cd cmd/gf/bin
mv noupx/* ./ && rm -rf noupx
ls -l
- name: Create Github Release
id: create_release
uses: actions/create-release@v1
@ -74,4 +62,4 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
asset_paths: '["cmd/gf/bin/gf_*"]'
asset_paths: '["cmd/gf/temp/gf_*"]'

View File

@ -96,7 +96,7 @@ jobs:
with:
timezoneLinux: "Asia/Shanghai"
- name: Checkout Repositary
- name: Checkout Repository
uses: actions/checkout@v2
- name: Set Up Go

1
.gitignore vendored
View File

@ -13,7 +13,6 @@ pkg/
bin/
cbuild
**/.DS_Store
.vscode/
.test/
main
gf

View File

@ -5,28 +5,27 @@
## 1. Install
## 1) PreCompiled Binary
You can also install `gf` tool using pre-built binaries: https://github.com/gogf/gf/releases
1. `Mac`
1. `Mac` & `Linux`
```shell
# Intel.
wget -O gf https://github.com/gogf/gf/releases/latest/download/gf_darwin_amd64 && chmod +x gf && ./gf install && rm ./gf
# M1.
wget -O gf https://github.com/gogf/gf/releases/latest/download/gf_darwin_arm64 && chmod +x gf && ./gf install && rm ./gf
wget -O gf https://github.com/gogf/gf/releases/latest/download/gf_$(go env GOOS)_$(go env GOARCH) && chmod +x gf && ./gf install -y && rm ./gf
```
> If you're using `zsh`, you might need rename your alias by command `alias gf=gf` to resolve the conflicts between `gf` and `git fetch`.
2. `Linux`
```shell
wget -O gf https://github.com/gogf/gf/releases/latest/download/gf_linux_amd64 && chmod +x gf && ./gf install && rm ./gf
```
3. `Windows`
2. `Windows`
Manually download, execute it and then follow the instruction.
4. Database `sqlite` and `oracle` are not support in `gf gen` command in default as it needs `cgo` and `gcc`, you can manually make some changes to the source codes and do the building.
3. Database `sqlite` and `oracle` are not support in `gf gen` command in default as it needs `cgo` and `gcc`, you can manually make some changes to the source codes and do the building.
## 2) Manually Install
```shell
git clone https://github.com/gogf/gf && cd cmd/gf && go install
```
## 2. Commands
```html

View File

@ -6,8 +6,10 @@ require (
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.0.0-rc2
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.0.0-rc2
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.0.0-rc2
github.com/gogf/gf/v2 v2.0.0-rc
github.com/gogf/gf/v2 v2.0.0
github.com/longbridgeapp/sqlparser v0.3.1 // indirect
github.com/olekukonko/tablewriter v0.0.5
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 // indirect
)
replace (

View File

@ -22,6 +22,7 @@ github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Px
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -40,13 +41,15 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0=
github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/longbridgeapp/sqlparser v0.3.1 h1:iWOZWGIFgQrJRgobLXUNJdvqGRpbVXkyKUKUA5CNJBE=
github.com/longbridgeapp/sqlparser v0.3.1/go.mod h1:GIHaUq8zvYyHLCLMJJykx1CdM6LHtkUih/QaJXySSx4=
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
@ -75,6 +78,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 h1:zzrxE1FKn5ryBNl9eKOeqQ58Y/Qpo3Q9QNxKHX5uzzQ=
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opentelemetry.io/otel v1.0.0 h1:qTTn6x71GVBvoafHK/yaRUmFzI4LcONZD0/kXxl5PHI=

View File

@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"os"
"regexp"
"runtime"
"strings"
@ -114,11 +115,12 @@ type cBuildInput struct {
Arch string `short:"a" name:"arch" brief:"output binary architecture, multiple arch separated with ','"`
System string `short:"s" name:"system" brief:"output binary system, multiple os separated with ','"`
Output string `short:"o" name:"output" brief:"output binary path, used when building single binary file"`
Path string `short:"p" name:"path" brief:"output binary directory path, default is './bin'" d:"./bin"`
Path string `short:"p" name:"path" brief:"output binary directory path, default is './temp'" d:"./temp"`
Extra string `short:"e" name:"extra" brief:"extra custom \"go build\" options"`
Mod string `short:"m" name:"mod" brief:"like \"-mod\" option of \"go build\", use \"-m none\" to disable go module"`
Cgo bool `short:"c" name:"cgo" brief:"enable or disable cgo feature, it's disabled in default" orphan:"true"`
VarMap g.Map `short:"r" name:"varMap" brief:"custom built embedded variable into binary"`
Exit bool `name:"exit" brief:"exit building when any error occurs, default is false" orphan:"true"`
Pack string `name:"pack" brief:"pack specified folder into temporary go file before building and removes it after built"`
}
type cBuildOutput struct{}
@ -254,7 +256,14 @@ func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, e
cmdShow, _ := gregex.ReplaceString(`\s+(-ldflags ".+?")\s+`, " ", cmd)
mlog.Print(cmdShow)
if result, err := gproc.ShellExec(cmd); err != nil {
mlog.Printf("failed to build, os:%s, arch:%s, error:\n%s\n", system, arch, gstr.Trim(result))
mlog.Printf(
"failed to build, os:%s, arch:%s, error:\n%s\n\n%s\n",
system, arch, gstr.Trim(result),
`you may use command option "--debug" to enable debug info and check the details`,
)
if in.Exit {
os.Exit(1)
}
} else {
mlog.Debug(gstr.Trim(result))
}
@ -264,6 +273,7 @@ func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, e
}
}
}
buildDone:
mlog.Print("done!")
return

View File

@ -81,6 +81,7 @@ func (c cDocker) Index(ctx context.Context, in cDockerInput) (out *cDockerOutput
}
// Binary build.
in.Build += " --exit"
if in.Main != "" {
if err = gproc.ShellRun(fmt.Sprintf(`gf build %s %s`, in.Main, in.Build)); err != nil {
return

View File

@ -21,8 +21,8 @@ import (
_ "github.com/gogf/gf/contrib/drivers/mssql/v2"
_ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
//_ "github.com/gogf/gf/contrib/drivers/oracle/v2"
// _ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
// _ "github.com/gogf/gf/contrib/drivers/oracle/v2"
)
const (
@ -35,7 +35,7 @@ const (
cGenDaoEg = `
gf gen dao
gf gen dao -l "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
gf gen dao -p ./model -c config.yaml -g user-center -t user,user_detail,user_login
gf gen dao -p ./model -g user-center -t user,user_detail,user_login
gf gen dao -r user_
`

View File

@ -11,10 +11,13 @@ import (
"{TplImportPrefix}/internal"
)
// internal{TplTableNameCamelCase}Dao is internal type for wrapping internal DAO implements.
type internal{TplTableNameCamelCase}Dao = *internal.{TplTableNameCamelCase}Dao
// {TplTableNameCamelLowerCase}Dao is the data access object for table {TplTableName}.
// You can define custom methods on it to extend its functionality as you wish.
type {TplTableNameCamelLowerCase}Dao struct {
*internal.{TplTableNameCamelCase}Dao
internal{TplTableNameCamelCase}Dao
}
var (

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -32,7 +32,7 @@ type serviceInstallAvailablePath struct {
func (s serviceInstall) Run(ctx context.Context) (err error) {
// Ask where to install.
paths := s.getInstallPathsData()
paths := s.getAvailablePaths()
if len(paths) <= 0 {
mlog.Printf("no path detected, you can manually install gf by copying the binary to path folder.")
return
@ -43,16 +43,21 @@ func (s serviceInstall) Run(ctx context.Context) (err error) {
// Print all paths status and determine the default selectedID value.
var (
selectedID = -1
newPaths []serviceInstallAvailablePath
pathSet = gset.NewStrSet() // Used for repeated items filtering.
)
for id, aPath := range paths {
if !pathSet.AddIfNotExist(aPath.dirPath) {
for _, path := range paths {
if !pathSet.AddIfNotExist(path.dirPath) {
continue
}
mlog.Printf(" %2d | %8t | %9t | %s", id, aPath.writable, aPath.installed, aPath.dirPath)
newPaths = append(newPaths, path)
}
paths = newPaths
for id, path := range paths {
mlog.Printf(" %2d | %8t | %9t | %s", id, path.writable, path.installed, path.dirPath)
if selectedID == -1 {
// Use the previously installed path as the most priority choice.
if aPath.installed {
if path.installed {
selectedID = id
}
}
@ -61,6 +66,7 @@ func (s serviceInstall) Run(ctx context.Context) (err error) {
if selectedID == -1 {
// Order by choosing priority.
commonPaths := garray.NewStrArrayFrom(g.SliceStr{
s.getGoPathBin(),
`/usr/local/bin`,
`/usr/bin`,
`/usr/sbin`,
@ -121,10 +127,10 @@ func (s serviceInstall) Run(ctx context.Context) (err error) {
}
// Uninstall the old binary.
for _, aPath := range paths {
for _, path := range paths {
// Do not delete myself.
if aPath.filePath != "" && aPath.filePath != dstPath.filePath && gfile.SelfPath() != aPath.filePath {
_ = gfile.Remove(aPath.filePath)
if path.filePath != "" && path.filePath != dstPath.filePath && gfile.SelfPath() != path.filePath {
_ = gfile.Remove(path.filePath)
}
}
return
@ -132,7 +138,7 @@ func (s serviceInstall) Run(ctx context.Context) (err error) {
// IsInstalled checks and returns whether the binary is installed.
func (s serviceInstall) IsInstalled() bool {
paths := s.getInstallPathsData()
paths := s.getAvailablePaths()
for _, aPath := range paths {
if aPath.installed {
return true
@ -141,11 +147,26 @@ func (s serviceInstall) IsInstalled() bool {
return false
}
// GetInstallPathsData returns the installation paths data for the binary.
func (s serviceInstall) getInstallPathsData() []serviceInstallAvailablePath {
var folderPaths []serviceInstallAvailablePath
// Pre generate binaryFileName.
binaryFileName := "gf" + gfile.Ext(gfile.SelfPath())
// getGoPathBinFilePath retrieves ad returns the GOPATH/bin path for binary.
func (s serviceInstall) getGoPathBin() string {
if goPath := genv.Get(`GOPATH`).String(); goPath != "" {
return gfile.Join(goPath, "bin")
}
return ""
}
// getAvailablePaths returns the installation paths data for the binary.
func (s serviceInstall) getAvailablePaths() []serviceInstallAvailablePath {
var (
folderPaths []serviceInstallAvailablePath
binaryFileName = "gf" + gfile.Ext(gfile.SelfPath())
)
// $GOPATH/bin
if goPathBin := s.getGoPathBin(); goPathBin != "" {
folderPaths = s.checkAndAppendToAvailablePath(
folderPaths, goPathBin, binaryFileName,
)
}
switch runtime.GOOS {
case "darwin":
darwinInstallationCheckPaths := []string{"/usr/local/bin"}
@ -157,21 +178,24 @@ func (s serviceInstall) getInstallPathsData() []serviceInstallAvailablePath {
fallthrough
default:
// $GOPATH/bin
gopath := gfile.Join(runtime.GOROOT(), "bin")
folderPaths = s.checkAndAppendToAvailablePath(
folderPaths, gopath, binaryFileName,
)
// Search and find the writable directory path.
envPath := genv.Get("PATH", genv.Get("Path").String()).String()
if gstr.Contains(envPath, ";") {
// windows.
for _, v := range gstr.SplitAndTrim(envPath, ";") {
if v == "." {
continue
}
folderPaths = s.checkAndAppendToAvailablePath(
folderPaths, v, binaryFileName,
)
}
} else if gstr.Contains(envPath, ":") {
// *nix.
for _, v := range gstr.SplitAndTrim(envPath, ":") {
if v == "." {
continue
}
folderPaths = s.checkAndAppendToAvailablePath(
folderPaths, v, binaryFileName,
)

View File

@ -3,7 +3,6 @@ package mlog
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/os/genv"
"github.com/gogf/gf/v2/os/glog"
@ -36,9 +35,9 @@ func init() {
func SetHeaderPrint(enabled bool) {
logger.SetHeaderPrint(enabled)
if enabled {
genv.Set(headerPrintEnvName, "1")
_ = genv.Set(headerPrintEnvName, "1")
} else {
genv.Set(headerPrintEnvName, "0")
_ = genv.Set(headerPrintEnvName, "0")
}
}
@ -51,17 +50,17 @@ func Printf(format string, v ...interface{}) {
}
func Fatal(v ...interface{}) {
logger.Fatal(ctx, append(g.Slice{"ERROR:"}, v...)...)
logger.Fatal(ctx, v...)
}
func Fatalf(format string, v ...interface{}) {
logger.Fatalf(ctx, "ERROR: "+format, v...)
logger.Fatalf(ctx, format, v...)
}
func Debug(v ...interface{}) {
logger.Debug(ctx, append(g.Slice{"DEBUG:"}, v...)...)
logger.Debug(ctx, v...)
}
func Debugf(format string, v ...interface{}) {
logger.Debugf(ctx, "DEBUG: "+format, v...)
logger.Debugf(ctx, format, v...)
}

View File

@ -503,16 +503,25 @@ func (a *Array) Search(value interface{}) int {
// Example: [1,1,2,3,2] -> [1,2,3]
func (a *Array) Unique() *Array {
a.mu.Lock()
for i := 0; i < len(a.array)-1; i++ {
for j := i + 1; j < len(a.array); {
if a.array[i] == a.array[j] {
a.array = append(a.array[:j], a.array[j+1:]...)
} else {
j++
}
}
defer a.mu.Unlock()
if len(a.array) == 0 {
return a
}
a.mu.Unlock()
var (
ok bool
temp interface{}
uniqueSet = make(map[interface{}]struct{})
uniqueArray = make([]interface{}, 0, len(a.array))
)
for i := 0; i < len(a.array); i++ {
temp = a.array[i]
if _, ok = uniqueSet[temp]; ok {
continue
}
uniqueSet[temp] = struct{}{}
uniqueArray = append(uniqueArray, temp)
}
a.array = uniqueArray
return a
}
@ -711,6 +720,9 @@ func (a *Array) IteratorDesc(f func(k int, v interface{}) bool) {
// String returns current array as a string, which implements like json.Marshal does.
func (a *Array) String() string {
if a == nil {
return ""
}
a.mu.RLock()
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)

View File

@ -514,16 +514,25 @@ func (a *IntArray) Search(value int) int {
// Example: [1,1,2,3,2] -> [1,2,3]
func (a *IntArray) Unique() *IntArray {
a.mu.Lock()
for i := 0; i < len(a.array)-1; i++ {
for j := i + 1; j < len(a.array); {
if a.array[i] == a.array[j] {
a.array = append(a.array[:j], a.array[j+1:]...)
} else {
j++
}
}
defer a.mu.Unlock()
if len(a.array) == 0 {
return a
}
a.mu.Unlock()
var (
ok bool
temp int
uniqueSet = make(map[int]struct{})
uniqueArray = make([]int, 0, len(a.array))
)
for i := 0; i < len(a.array); i++ {
temp = a.array[i]
if _, ok = uniqueSet[temp]; ok {
continue
}
uniqueSet[temp] = struct{}{}
uniqueArray = append(uniqueArray, temp)
}
a.array = uniqueArray
return a
}
@ -722,6 +731,9 @@ func (a *IntArray) IteratorDesc(f func(k int, v int) bool) {
// String returns current array as a string, which implements like json.Marshal does.
func (a *IntArray) String() string {
if a == nil {
return ""
}
return "[" + a.Join(",") + "]"
}

View File

@ -516,16 +516,25 @@ func (a *StrArray) Search(value string) int {
// Example: [1,1,2,3,2] -> [1,2,3]
func (a *StrArray) Unique() *StrArray {
a.mu.Lock()
for i := 0; i < len(a.array)-1; i++ {
for j := i + 1; j < len(a.array); {
if a.array[i] == a.array[j] {
a.array = append(a.array[:j], a.array[j+1:]...)
} else {
j++
}
}
defer a.mu.Unlock()
if len(a.array) == 0 {
return a
}
a.mu.Unlock()
var (
ok bool
temp string
uniqueSet = make(map[string]struct{})
uniqueArray = make([]string, 0, len(a.array))
)
for i := 0; i < len(a.array); i++ {
temp = a.array[i]
if _, ok = uniqueSet[temp]; ok {
continue
}
uniqueSet[temp] = struct{}{}
uniqueArray = append(uniqueArray, temp)
}
a.array = uniqueArray
return a
}
@ -724,6 +733,9 @@ func (a *StrArray) IteratorDesc(f func(k int, v string) bool) {
// String returns current array as a string, which implements like json.Marshal does.
func (a *StrArray) String() string {
if a == nil {
return ""
}
a.mu.RLock()
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)

View File

@ -36,9 +36,9 @@ type SortedArray struct {
// NewSortedArray creates and returns an empty sorted array.
// The parameter `safe` is used to specify whether using array in concurrent-safety, which is false in default.
// The parameter `comparator` used to compare values to sort in array,
// if it returns value < 0, means v1 < v2; the v1 will be inserted before v2;
// if it returns value = 0, means v1 = v2; the v1 will be replaced by v2;
// if it returns value > 0, means v1 > v2; the v1 will be inserted after v2;
// if it returns value < 0, means `a` < `b`; the `a` will be inserted before `b`;
// if it returns value = 0, means `a` = `b`; the `a` will be replaced by `b`;
// if it returns value > 0, means `a` > `b`; the `a` will be inserted after `b`;
func NewSortedArray(comparator func(a, b interface{}) int, safe ...bool) *SortedArray {
return NewSortedArraySize(0, comparator, safe...)
}
@ -653,6 +653,9 @@ func (a *SortedArray) IteratorDesc(f func(k int, v interface{}) bool) {
// String returns current array as a string, which implements like json.Marshal does.
func (a *SortedArray) String() string {
if a == nil {
return ""
}
a.mu.RLock()
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)

View File

@ -646,6 +646,9 @@ func (a *SortedIntArray) IteratorDesc(f func(k int, v int) bool) {
// String returns current array as a string, which implements like json.Marshal does.
func (a *SortedIntArray) String() string {
if a == nil {
return ""
}
return "[" + a.Join(",") + "]"
}

View File

@ -648,6 +648,9 @@ func (a *SortedStrArray) IteratorDesc(f func(k int, v string) bool) {
// String returns current array as a string, which implements like json.Marshal does.
func (a *SortedStrArray) String() string {
if a == nil {
return ""
}
a.mu.RLock()
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)

View File

@ -576,7 +576,7 @@ func TestArray_Json(t *testing.T) {
var a3 garray.Array
err := json.UnmarshalUseNumber(b2, &a3)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(a3.Slice(), s1)
})
// value.
@ -595,7 +595,7 @@ func TestArray_Json(t *testing.T) {
var a3 garray.Array
err := json.UnmarshalUseNumber(b2, &a3)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(a3.Slice(), s1)
})
// pointer
@ -609,11 +609,11 @@ func TestArray_Json(t *testing.T) {
"Scores": []int{99, 100, 98},
}
b, err := json.Marshal(data)
t.Assert(err, nil)
t.AssertNil(err)
user := new(User)
err = json.UnmarshalUseNumber(b, user)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(user.Name, data["Name"])
t.Assert(user.Scores, data["Scores"])
})
@ -628,11 +628,11 @@ func TestArray_Json(t *testing.T) {
"Scores": []int{99, 100, 98},
}
b, err := json.Marshal(data)
t.Assert(err, nil)
t.AssertNil(err)
user := new(User)
err = json.UnmarshalUseNumber(b, user)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(user.Name, data["Name"])
t.Assert(user.Scores, data["Scores"])
})
@ -720,7 +720,7 @@ func TestArray_UnmarshalValue(t *testing.T) {
"name": "john",
"array": g.Slice{1, 2, 3},
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Array.Slice(), g.Slice{1, 2, 3})
})

View File

@ -619,7 +619,7 @@ func TestIntArray_Json(t *testing.T) {
var a3 garray.IntArray
err := json.UnmarshalUseNumber(b2, &a3)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(a3.Slice(), s1)
})
// array value
@ -637,7 +637,7 @@ func TestIntArray_Json(t *testing.T) {
var a3 garray.IntArray
err := json.UnmarshalUseNumber(b2, &a3)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(a3.Slice(), s1)
})
// array pointer
@ -651,11 +651,11 @@ func TestIntArray_Json(t *testing.T) {
"Scores": []int{99, 100, 98},
}
b, err := json.Marshal(data)
t.Assert(err, nil)
t.AssertNil(err)
user := new(User)
err = json.UnmarshalUseNumber(b, user)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(user.Name, data["Name"])
t.Assert(user.Scores, data["Scores"])
})
@ -670,11 +670,11 @@ func TestIntArray_Json(t *testing.T) {
"Scores": []int{99, 100, 98},
}
b, err := json.Marshal(data)
t.Assert(err, nil)
t.AssertNil(err)
user := new(User)
err = json.UnmarshalUseNumber(b, user)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(user.Name, data["Name"])
t.Assert(user.Scores, data["Scores"])
})
@ -752,7 +752,7 @@ func TestIntArray_UnmarshalValue(t *testing.T) {
"name": "john",
"array": []byte(`[1,2,3]`),
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Array.Slice(), g.Slice{1, 2, 3})
})
@ -763,7 +763,7 @@ func TestIntArray_UnmarshalValue(t *testing.T) {
// "name": "john",
// "array": g.Slice{1, 2, 3},
// }, &v)
// t.Assert(err, nil)
// t.AssertNil(err)
// t.Assert(v.Name, "john")
// t.Assert(v.Array.Slice(), g.Slice{1, 2, 3})
// })

View File

@ -619,7 +619,7 @@ func TestStrArray_Json(t *testing.T) {
var a3 garray.StrArray
err := json.UnmarshalUseNumber(b2, &a3)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(a3.Slice(), s1)
})
// array value
@ -637,7 +637,7 @@ func TestStrArray_Json(t *testing.T) {
var a3 garray.StrArray
err := json.UnmarshalUseNumber(b2, &a3)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(a3.Slice(), s1)
})
// array pointer
@ -651,11 +651,11 @@ func TestStrArray_Json(t *testing.T) {
"Scores": []string{"A+", "A", "A"},
}
b, err := json.Marshal(data)
t.Assert(err, nil)
t.AssertNil(err)
user := new(User)
err = json.UnmarshalUseNumber(b, user)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(user.Name, data["Name"])
t.Assert(user.Scores, data["Scores"])
})
@ -670,11 +670,11 @@ func TestStrArray_Json(t *testing.T) {
"Scores": []string{"A+", "A", "A"},
}
b, err := json.Marshal(data)
t.Assert(err, nil)
t.AssertNil(err)
user := new(User)
err = json.UnmarshalUseNumber(b, user)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(user.Name, data["Name"])
t.Assert(user.Scores, data["Scores"])
})
@ -751,7 +751,7 @@ func TestStrArray_UnmarshalValue(t *testing.T) {
"name": "john",
"array": []byte(`["1","2","3"]`),
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Array.Slice(), g.SliceStr{"1", "2", "3"})
})
@ -762,7 +762,7 @@ func TestStrArray_UnmarshalValue(t *testing.T) {
"name": "john",
"array": g.SliceStr{"1", "2", "3"},
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Array.Slice(), g.SliceStr{"1", "2", "3"})
})

View File

@ -661,7 +661,7 @@ func TestSortedArray_Json(t *testing.T) {
var a3 garray.SortedArray
err := json.UnmarshalUseNumber(b2, &a3)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(a3.Slice(), s1)
t.Assert(a3.Interfaces(), s1)
})
@ -681,7 +681,7 @@ func TestSortedArray_Json(t *testing.T) {
var a3 garray.SortedArray
err := json.UnmarshalUseNumber(b2, &a3)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(a3.Slice(), s1)
t.Assert(a3.Interfaces(), s1)
})
@ -696,11 +696,11 @@ func TestSortedArray_Json(t *testing.T) {
"Scores": []int{99, 100, 98},
}
b, err := json.Marshal(data)
t.Assert(err, nil)
t.AssertNil(err)
user := new(User)
err = json.UnmarshalUseNumber(b, user)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(user.Name, data["Name"])
t.AssertNE(user.Scores, nil)
t.Assert(user.Scores.Len(), 3)
@ -732,11 +732,11 @@ func TestSortedArray_Json(t *testing.T) {
"Scores": []int{99, 100, 98},
}
b, err := json.Marshal(data)
t.Assert(err, nil)
t.AssertNil(err)
user := new(User)
err = json.UnmarshalUseNumber(b, user)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(user.Name, data["Name"])
t.AssertNE(user.Scores, nil)
t.Assert(user.Scores.Len(), 3)
@ -830,7 +830,7 @@ func TestSortedArray_UnmarshalValue(t *testing.T) {
"name": "john",
"array": []byte(`[2,3,1]`),
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Array.Slice(), g.Slice{1, 2, 3})
})
@ -841,7 +841,7 @@ func TestSortedArray_UnmarshalValue(t *testing.T) {
"name": "john",
"array": g.Slice{2, 3, 1},
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Array.Slice(), g.Slice{1, 2, 3})
})

View File

@ -561,7 +561,7 @@ func TestSortedIntArray_Json(t *testing.T) {
var a3 garray.SortedIntArray
err := json.UnmarshalUseNumber(b2, &a3)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(a3.Slice(), s1)
})
// array value
@ -580,7 +580,7 @@ func TestSortedIntArray_Json(t *testing.T) {
var a3 garray.SortedIntArray
err := json.UnmarshalUseNumber(b2, &a3)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(a3.Slice(), s1)
})
// array pointer
@ -594,11 +594,11 @@ func TestSortedIntArray_Json(t *testing.T) {
"Scores": []int{99, 100, 98},
}
b, err := json.Marshal(data)
t.Assert(err, nil)
t.AssertNil(err)
user := new(User)
err = json.UnmarshalUseNumber(b, user)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(user.Name, data["Name"])
t.Assert(user.Scores, []int{98, 99, 100})
})
@ -613,11 +613,11 @@ func TestSortedIntArray_Json(t *testing.T) {
"Scores": []int{99, 100, 98},
}
b, err := json.Marshal(data)
t.Assert(err, nil)
t.AssertNil(err)
user := new(User)
err = json.UnmarshalUseNumber(b, user)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(user.Name, data["Name"])
t.Assert(user.Scores, []int{98, 99, 100})
})
@ -695,7 +695,7 @@ func TestSortedIntArray_UnmarshalValue(t *testing.T) {
"name": "john",
"array": []byte(`[2,3,1]`),
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Array.Slice(), g.Slice{1, 2, 3})
})
@ -706,7 +706,7 @@ func TestSortedIntArray_UnmarshalValue(t *testing.T) {
"name": "john",
"array": g.Slice{2, 3, 1},
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Array.Slice(), g.Slice{1, 2, 3})
})

View File

@ -583,7 +583,7 @@ func TestSortedStrArray_Json(t *testing.T) {
var a3 garray.SortedStrArray
err := json.UnmarshalUseNumber(b2, &a3)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(a3.Slice(), s1)
t.Assert(a3.Interfaces(), s1)
})
@ -604,7 +604,7 @@ func TestSortedStrArray_Json(t *testing.T) {
var a3 garray.SortedStrArray
err := json.UnmarshalUseNumber(b2, &a3)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(a3.Slice(), s1)
t.Assert(a3.Interfaces(), s1)
})
@ -619,11 +619,11 @@ func TestSortedStrArray_Json(t *testing.T) {
"Scores": []string{"A+", "A", "A"},
}
b, err := json.Marshal(data)
t.Assert(err, nil)
t.AssertNil(err)
user := new(User)
err = json.UnmarshalUseNumber(b, user)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(user.Name, data["Name"])
t.Assert(user.Scores, []string{"A", "A", "A+"})
})
@ -638,11 +638,11 @@ func TestSortedStrArray_Json(t *testing.T) {
"Scores": []string{"A+", "A", "A"},
}
b, err := json.Marshal(data)
t.Assert(err, nil)
t.AssertNil(err)
user := new(User)
err = json.UnmarshalUseNumber(b, user)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(user.Name, data["Name"])
t.Assert(user.Scores, []string{"A", "A", "A+"})
})
@ -719,7 +719,7 @@ func TestSortedStrArray_UnmarshalValue(t *testing.T) {
"name": "john",
"array": []byte(`["1","3","2"]`),
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Array.Slice(), g.SliceStr{"1", "2", "3"})
})
@ -730,7 +730,7 @@ func TestSortedStrArray_UnmarshalValue(t *testing.T) {
"name": "john",
"array": g.SliceStr{"1", "3", "2"},
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Array.Slice(), g.SliceStr{"1", "2", "3"})
})

View File

@ -503,6 +503,9 @@ func (l *List) Join(glue string) string {
// String returns current list as a string.
func (l *List) String() string {
if l == nil {
return ""
}
return "[" + l.Join(",") + "]"
}

View File

@ -709,20 +709,20 @@ func TestList_Json(t *testing.T) {
a := []interface{}{"a", "b", "c"}
l := New()
b, err := json.Marshal(a)
t.Assert(err, nil)
t.AssertNil(err)
err = json.UnmarshalUseNumber(b, l)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(l.FrontAll(), a)
})
gtest.C(t, func(t *gtest.T) {
var l List
a := []interface{}{"a", "b", "c"}
b, err := json.Marshal(a)
t.Assert(err, nil)
t.AssertNil(err)
err = json.UnmarshalUseNumber(b, &l)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(l.FrontAll(), a)
})
}
@ -739,7 +739,7 @@ func TestList_UnmarshalValue(t *testing.T) {
"name": "john",
"list": []byte(`[1,2,3]`),
}, &tlist)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(tlist.Name, "john")
t.Assert(tlist.List.FrontAll(), []interface{}{1, 2, 3})
})
@ -750,7 +750,7 @@ func TestList_UnmarshalValue(t *testing.T) {
"name": "john",
"list": []interface{}{1, 2, 3},
}, &tlist)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(tlist.Name, "john")
t.Assert(tlist.List.FrontAll(), []interface{}{1, 2, 3})
})

View File

@ -456,6 +456,9 @@ func (m *AnyAnyMap) Merge(other *AnyAnyMap) {
// String returns the map as a string.
func (m *AnyAnyMap) String() string {
if m == nil {
return ""
}
b, _ := m.MarshalJSON()
return string(b)
}

View File

@ -455,6 +455,9 @@ func (m *IntAnyMap) Merge(other *IntAnyMap) {
// String returns the map as a string.
func (m *IntAnyMap) String() string {
if m == nil {
return ""
}
b, _ := m.MarshalJSON()
return string(b)
}

View File

@ -426,6 +426,9 @@ func (m *IntIntMap) Merge(other *IntIntMap) {
// String returns the map as a string.
func (m *IntIntMap) String() string {
if m == nil {
return ""
}
b, _ := m.MarshalJSON()
return string(b)
}

View File

@ -426,6 +426,9 @@ func (m *IntStrMap) Merge(other *IntStrMap) {
// String returns the map as a string.
func (m *IntStrMap) String() string {
if m == nil {
return ""
}
b, _ := m.MarshalJSON()
return string(b)
}

View File

@ -451,6 +451,9 @@ func (m *StrAnyMap) Merge(other *StrAnyMap) {
// String returns the map as a string.
func (m *StrAnyMap) String() string {
if m == nil {
return ""
}
b, _ := m.MarshalJSON()
return string(b)
}

View File

@ -430,6 +430,9 @@ func (m *StrIntMap) Merge(other *StrIntMap) {
// String returns the map as a string.
func (m *StrIntMap) String() string {
if m == nil {
return ""
}
b, _ := m.MarshalJSON()
return string(b)
}

View File

@ -429,6 +429,9 @@ func (m *StrStrMap) Merge(other *StrStrMap) {
// String returns the map as a string.
func (m *StrStrMap) String() string {
if m == nil {
return ""
}
b, _ := m.MarshalJSON()
return string(b)
}

View File

@ -7,6 +7,9 @@
package gmap
import (
"bytes"
"fmt"
"github.com/gogf/gf/v2/container/glist"
"github.com/gogf/gf/v2/container/gvar"
"github.com/gogf/gf/v2/internal/empty"
@ -15,6 +18,13 @@ import (
"github.com/gogf/gf/v2/util/gconv"
)
// ListMap is a map that preserves insertion-order.
//
// It is backed by a hash table to store values and doubly-linked list to store ordering.
//
// Structure is not thread safe.
//
// Reference: http://en.wikipedia.org/wiki/Associative_array
type ListMap struct {
mu rwmutex.RWMutex
data map[interface{}]*glist.Element
@ -58,7 +68,7 @@ func (m *ListMap) IteratorAsc(f func(key interface{}, value interface{}) bool) {
m.mu.RLock()
defer m.mu.RUnlock()
if m.list != nil {
node := (*gListMapNode)(nil)
var node *gListMapNode
m.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
return f(node.key, node.value)
@ -72,7 +82,7 @@ func (m *ListMap) IteratorDesc(f func(key interface{}, value interface{}) bool)
m.mu.RLock()
defer m.mu.RUnlock()
if m.list != nil {
node := (*gListMapNode)(nil)
var node *gListMapNode
m.list.IteratorDesc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
return f(node.key, node.value)
@ -146,8 +156,10 @@ func (m *ListMap) MapStrAny() map[string]interface{} {
func (m *ListMap) FilterEmpty() {
m.mu.Lock()
if m.list != nil {
keys := make([]interface{}, 0)
node := (*gListMapNode)(nil)
var (
keys = make([]interface{}, 0)
node *gListMapNode
)
m.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
if empty.IsEmpty(node.value) {
@ -495,7 +507,7 @@ func (m *ListMap) Merge(other *ListMap) {
other.mu.RLock()
defer other.mu.RUnlock()
}
node := (*gListMapNode)(nil)
var node *gListMapNode
other.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
if e, ok := m.data[node.key]; !ok {
@ -509,13 +521,34 @@ func (m *ListMap) Merge(other *ListMap) {
// String returns the map as a string.
func (m *ListMap) String() string {
if m == nil {
return ""
}
b, _ := m.MarshalJSON()
return string(b)
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m ListMap) MarshalJSON() ([]byte, error) {
return json.Marshal(gconv.Map(m.Map()))
func (m ListMap) MarshalJSON() (jsonBytes []byte, err error) {
if m.data == nil {
return []byte("null"), nil
}
buffer := bytes.NewBuffer(nil)
buffer.WriteByte('{')
m.Iterator(func(key, value interface{}) bool {
valueBytes, valueJsonErr := json.Marshal(value)
if valueJsonErr != nil {
err = valueJsonErr
return false
}
if buffer.Len() > 1 {
buffer.WriteByte(',')
}
buffer.WriteString(fmt.Sprintf(`"%v":%s`, key, valueBytes))
return true
})
buffer.WriteByte('}')
return buffer.Bytes(), nil
}
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.

View File

@ -8,6 +8,7 @@ package gmap_test
import (
"fmt"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/internal/json"
@ -195,7 +196,7 @@ func ExampleListMap_Sets() {
m.Sets(addMap)
fmt.Println(m)
// Output:
// May Output:
// {"key1":"val1","key2":"val2","key3":"val3"}
}
@ -562,12 +563,10 @@ func ExampleListMap_String() {
func ExampleListMap_MarshalJSON() {
var m gmap.ListMap
m.Sets(g.MapAnyAny{
"k1": "v1",
"k2": "v2",
"k3": "v3",
"k4": "v4",
})
m.Set("k1", "v1")
m.Set("k2", "v2")
m.Set("k3", "v3")
m.Set("k4", "v4")
bytes, err := json.Marshal(&m)
if err == nil {

View File

@ -8,6 +8,7 @@ package gmap_test
import (
"fmt"
"github.com/gogf/gf/v2/util/gutil"
"github.com/gogf/gf/v2/container/gmap"
@ -277,7 +278,7 @@ func ExampleNewListMapFrom() {
n := gmap.NewListMapFrom(m.Map(), true)
fmt.Println(n)
// Output:
// May Output:
// {"key1":"var1","key2":"var2"}
// {"key1":"var1","key2":"var2"}
}
@ -290,7 +291,7 @@ func ExampleNewTreeMap() {
fmt.Println(m.Map())
// Output:
// May Output:
// map[key1:var1 key2:var2]
}
@ -305,7 +306,7 @@ func ExampleNewTreeMapFrom() {
n := gmap.NewListMapFrom(m.Map(), true)
fmt.Println(n.Map())
// Output:
// May Output:
// map[key1:var1 key2:var2]
// map[key1:var1 key2:var2]
}

View File

@ -253,11 +253,11 @@ func Test_AnyAnyMap_Json(t *testing.T) {
"k2": "v2",
}
b, err := json.Marshal(gconv.Map(data))
t.Assert(err, nil)
t.AssertNil(err)
m := gmap.New()
err = json.UnmarshalUseNumber(b, m)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(m.Get("k1"), data["k1"])
t.Assert(m.Get("k2"), data["k2"])
})
@ -267,11 +267,11 @@ func Test_AnyAnyMap_Json(t *testing.T) {
"k2": "v2",
}
b, err := json.Marshal(gconv.Map(data))
t.Assert(err, nil)
t.AssertNil(err)
var m gmap.Map
err = json.UnmarshalUseNumber(b, &m)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(m.Get("k1"), data["k1"])
t.Assert(m.Get("k2"), data["k2"])
})
@ -342,7 +342,7 @@ func TestAnyAnyMap_UnmarshalValue(t *testing.T) {
"name": "john",
"map": []byte(`{"k1":"v1","k2":"v2"}`),
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Map.Size(), 2)
t.Assert(v.Map.Get("k1"), "v1")
@ -358,7 +358,7 @@ func TestAnyAnyMap_UnmarshalValue(t *testing.T) {
"k2": "v2",
},
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Map.Size(), 2)
t.Assert(v.Map.Get("k1"), "v1")

View File

@ -242,11 +242,11 @@ func Test_IntAnyMap_Json(t *testing.T) {
2: "v2",
}
b, err := json.Marshal(data)
t.Assert(err, nil)
t.AssertNil(err)
m := gmap.NewIntAnyMap()
err = json.UnmarshalUseNumber(b, m)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(m.Get(1), data[1])
t.Assert(m.Get(2), data[2])
})
@ -317,7 +317,7 @@ func TestIntAnyMap_UnmarshalValue(t *testing.T) {
"name": "john",
"map": []byte(`{"1":"v1","2":"v2"}`),
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Map.Size(), 2)
t.Assert(v.Map.Get(1), "v1")
@ -333,7 +333,7 @@ func TestIntAnyMap_UnmarshalValue(t *testing.T) {
2: "v2",
},
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Map.Size(), 2)
t.Assert(v.Map.Get(1), "v1")

View File

@ -248,11 +248,11 @@ func Test_IntIntMap_Json(t *testing.T) {
2: 20,
}
b, err := json.Marshal(data)
t.Assert(err, nil)
t.AssertNil(err)
m := gmap.NewIntIntMap()
err = json.UnmarshalUseNumber(b, m)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(m.Get(1), data[1])
t.Assert(m.Get(2), data[2])
})
@ -323,7 +323,7 @@ func TestIntIntMap_UnmarshalValue(t *testing.T) {
"name": "john",
"map": []byte(`{"1":1,"2":2}`),
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Map.Size(), 2)
t.Assert(v.Map.Get(1), "1")
@ -339,7 +339,7 @@ func TestIntIntMap_UnmarshalValue(t *testing.T) {
2: 2,
},
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Map.Size(), 2)
t.Assert(v.Map.Get(1), "1")

View File

@ -246,11 +246,11 @@ func Test_IntStrMap_Json(t *testing.T) {
2: "v2",
}
b, err := json.Marshal(data)
t.Assert(err, nil)
t.AssertNil(err)
m := gmap.NewIntStrMap()
err = json.UnmarshalUseNumber(b, m)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(m.Get(1), data[1])
t.Assert(m.Get(2), data[2])
})
@ -321,7 +321,7 @@ func TestIntStrMap_UnmarshalValue(t *testing.T) {
"name": "john",
"map": []byte(`{"1":"v1","2":"v2"}`),
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Map.Size(), 2)
t.Assert(v.Map.Get(1), "v1")
@ -337,7 +337,7 @@ func TestIntStrMap_UnmarshalValue(t *testing.T) {
2: "v2",
},
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Map.Size(), 2)
t.Assert(v.Map.Get(1), "v1")

View File

@ -240,11 +240,11 @@ func Test_StrAnyMap_Json(t *testing.T) {
"k2": "v2",
}
b, err := json.Marshal(data)
t.Assert(err, nil)
t.AssertNil(err)
m := gmap.NewStrAnyMap()
err = json.UnmarshalUseNumber(b, m)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(m.Get("k1"), data["k1"])
t.Assert(m.Get("k2"), data["k2"])
})
@ -254,11 +254,11 @@ func Test_StrAnyMap_Json(t *testing.T) {
"k2": "v2",
}
b, err := json.Marshal(data)
t.Assert(err, nil)
t.AssertNil(err)
var m gmap.StrAnyMap
err = json.UnmarshalUseNumber(b, &m)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(m.Get("k1"), data["k1"])
t.Assert(m.Get("k2"), data["k2"])
})
@ -329,7 +329,7 @@ func TestStrAnyMap_UnmarshalValue(t *testing.T) {
"name": "john",
"map": []byte(`{"k1":"v1","k2":"v2"}`),
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Map.Size(), 2)
t.Assert(v.Map.Get("k1"), "v1")
@ -345,7 +345,7 @@ func TestStrAnyMap_UnmarshalValue(t *testing.T) {
"k2": "v2",
},
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Map.Size(), 2)
t.Assert(v.Map.Get("k1"), "v1")

View File

@ -244,11 +244,11 @@ func Test_StrIntMap_Json(t *testing.T) {
"k2": 2,
}
b, err := json.Marshal(data)
t.Assert(err, nil)
t.AssertNil(err)
m := gmap.NewStrIntMap()
err = json.UnmarshalUseNumber(b, m)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(m.Get("k1"), data["k1"])
t.Assert(m.Get("k2"), data["k2"])
})
@ -258,11 +258,11 @@ func Test_StrIntMap_Json(t *testing.T) {
"k2": 2,
}
b, err := json.Marshal(data)
t.Assert(err, nil)
t.AssertNil(err)
var m gmap.StrIntMap
err = json.UnmarshalUseNumber(b, &m)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(m.Get("k1"), data["k1"])
t.Assert(m.Get("k2"), data["k2"])
})
@ -333,7 +333,7 @@ func TestStrIntMap_UnmarshalValue(t *testing.T) {
"name": "john",
"map": []byte(`{"k1":1,"k2":2}`),
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Map.Size(), 2)
t.Assert(v.Map.Get("k1"), 1)
@ -349,7 +349,7 @@ func TestStrIntMap_UnmarshalValue(t *testing.T) {
"k2": 2,
},
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Map.Size(), 2)
t.Assert(v.Map.Get("k1"), 1)

View File

@ -241,11 +241,11 @@ func Test_StrStrMap_Json(t *testing.T) {
"k2": "v2",
}
b, err := json.Marshal(data)
t.Assert(err, nil)
t.AssertNil(err)
m := gmap.NewStrStrMap()
err = json.UnmarshalUseNumber(b, m)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(m.Get("k1"), data["k1"])
t.Assert(m.Get("k2"), data["k2"])
})
@ -255,11 +255,11 @@ func Test_StrStrMap_Json(t *testing.T) {
"k2": "v2",
}
b, err := json.Marshal(data)
t.Assert(err, nil)
t.AssertNil(err)
var m gmap.StrStrMap
err = json.UnmarshalUseNumber(b, &m)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(m.Get("k1"), data["k1"])
t.Assert(m.Get("k2"), data["k2"])
})
@ -330,7 +330,7 @@ func TestStrStrMap_UnmarshalValue(t *testing.T) {
"name": "john",
"map": []byte(`{"k1":"v1","k2":"v2"}`),
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Map.Size(), 2)
t.Assert(v.Map.Get("k1"), "v1")
@ -346,7 +346,7 @@ func TestStrStrMap_UnmarshalValue(t *testing.T) {
"k2": "v2",
},
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Map.Size(), 2)
t.Assert(v.Map.Get("k1"), "v1")

View File

@ -186,12 +186,12 @@ func Test_ListMap_Json(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
data := g.MapAnyAny{
"k1": "v1",
"k2": "v2",
}
m1 := gmap.NewListMapFrom(data)
b1, err1 := json.Marshal(m1)
t.AssertNil(err1)
b2, err2 := json.Marshal(gconv.Map(data))
t.Assert(err1, err2)
t.AssertNil(err2)
t.Assert(b1, b2)
})
// Unmarshal
@ -201,11 +201,11 @@ func Test_ListMap_Json(t *testing.T) {
"k2": "v2",
}
b, err := json.Marshal(gconv.Map(data))
t.Assert(err, nil)
t.AssertNil(err)
m := gmap.NewListMap()
err = json.UnmarshalUseNumber(b, m)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(m.Get("k1"), data["k1"])
t.Assert(m.Get("k2"), data["k2"])
})
@ -216,16 +216,37 @@ func Test_ListMap_Json(t *testing.T) {
"k2": "v2",
}
b, err := json.Marshal(gconv.Map(data))
t.Assert(err, nil)
t.AssertNil(err)
var m gmap.ListMap
err = json.UnmarshalUseNumber(b, &m)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(m.Get("k1"), data["k1"])
t.Assert(m.Get("k2"), data["k2"])
})
}
func Test_ListMap_Json_Sequence(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewListMap()
for i := 'z'; i >= 'a'; i-- {
m.Set(string(i), i)
}
b, err := json.Marshal(m)
t.AssertNil(err)
t.Assert(b, `{"z":122,"y":121,"x":120,"w":119,"v":118,"u":117,"t":116,"s":115,"r":114,"q":113,"p":112,"o":111,"n":110,"m":109,"l":108,"k":107,"j":106,"i":105,"h":104,"g":103,"f":102,"e":101,"d":100,"c":99,"b":98,"a":97}`)
})
gtest.C(t, func(t *gtest.T) {
m := gmap.NewListMap()
for i := 'a'; i <= 'z'; i++ {
m.Set(string(i), i)
}
b, err := json.Marshal(m)
t.AssertNil(err)
t.Assert(b, `{"a":97,"b":98,"c":99,"d":100,"e":101,"f":102,"g":103,"h":104,"i":105,"j":106,"k":107,"l":108,"m":109,"n":110,"o":111,"p":112,"q":113,"r":114,"s":115,"t":116,"u":117,"v":118,"w":119,"x":120,"y":121,"z":122}`)
})
}
func Test_ListMap_Pop(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewListMapFrom(g.MapAnyAny{
@ -291,7 +312,7 @@ func TestListMap_UnmarshalValue(t *testing.T) {
"name": "john",
"map": []byte(`{"1":"v1","2":"v2"}`),
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Map.Size(), 2)
t.Assert(v.Map.Get("1"), "v1")
@ -307,7 +328,7 @@ func TestListMap_UnmarshalValue(t *testing.T) {
2: "v2",
},
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Map.Size(), 2)
t.Assert(v.Map.Get("1"), "v1")

View File

@ -185,11 +185,11 @@ func Test_TreeMap_Json(t *testing.T) {
"k2": "v2",
}
b, err := json.Marshal(gconv.Map(data))
t.Assert(err, nil)
t.AssertNil(err)
m := gmap.NewTreeMap(gutil.ComparatorString)
err = json.UnmarshalUseNumber(b, m)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(m.Get("k1"), data["k1"])
t.Assert(m.Get("k2"), data["k2"])
})
@ -199,11 +199,11 @@ func Test_TreeMap_Json(t *testing.T) {
"k2": "v2",
}
b, err := json.Marshal(gconv.Map(data))
t.Assert(err, nil)
t.AssertNil(err)
var m gmap.TreeMap
err = json.UnmarshalUseNumber(b, &m)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(m.Get("k1"), data["k1"])
t.Assert(m.Get("k2"), data["k2"])
})
@ -221,7 +221,7 @@ func TestTreeMap_UnmarshalValue(t *testing.T) {
"name": "john",
"map": []byte(`{"k1":"v1","k2":"v2"}`),
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Map.Size(), 2)
t.Assert(v.Map.Get("k1"), "v1")
@ -237,7 +237,7 @@ func TestTreeMap_UnmarshalValue(t *testing.T) {
"k2": "v2",
},
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Map.Size(), 2)
t.Assert(v.Map.Get("k1"), "v1")

View File

@ -109,7 +109,7 @@ func (p *Pool) Get() (interface{}, error) {
if f.expireAt == 0 || f.expireAt > gtime.TimestampMilli() {
return f.value, nil
} else if p.ExpireFunc != nil {
// TODO: move expire function calling asynchronously from `Get` operation.
// TODO: move expire function calling asynchronously out from `Get` operation.
p.ExpireFunc(f.value)
}
} else {
@ -129,7 +129,7 @@ func (p *Pool) Size() int {
// Close closes the pool. If `p` has ExpireFunc,
// then it automatically closes all items using this function before it's closed.
// Commonly you do not need call this function manually.
// Commonly you do not need to call this function manually.
func (p *Pool) Close() {
p.closed.Set(true)
}

View File

@ -223,6 +223,9 @@ func (set *Set) Join(glue string) string {
// String returns items as a string, which implements like json.Marshal does.
func (set *Set) String() string {
if set == nil {
return ""
}
set.mu.RLock()
defer set.mu.RUnlock()
var (

View File

@ -204,6 +204,9 @@ func (set *IntSet) Join(glue string) string {
// String returns items as a string, which implements like json.Marshal does.
func (set *IntSet) String() string {
if set == nil {
return ""
}
return "[" + set.Join(",") + "]"
}

View File

@ -218,6 +218,9 @@ func (set *StrSet) Join(glue string) string {
// String returns items as a string, which implements like json.Marshal does.
func (set *StrSet) String() string {
if set == nil {
return ""
}
set.mu.RLock()
defer set.mu.RUnlock()
var (

View File

@ -336,7 +336,7 @@ func TestSet_Json(t *testing.T) {
var a3 gset.Set
err := json.UnmarshalUseNumber(b2, &a3)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(a3.Contains("a"), true)
t.Assert(a3.Contains("b"), true)
t.Assert(a3.Contains("c"), true)
@ -438,7 +438,7 @@ func TestSet_UnmarshalValue(t *testing.T) {
"name": "john",
"set": []byte(`["k1","k2","k3"]`),
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Set.Size(), 3)
t.Assert(v.Set.Contains("k1"), true)
@ -453,7 +453,7 @@ func TestSet_UnmarshalValue(t *testing.T) {
"name": "john",
"set": g.Slice{"k1", "k2", "k3"},
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Set.Size(), 3)
t.Assert(v.Set.Contains("k1"), true)

View File

@ -368,7 +368,7 @@ func TestIntSet_Json(t *testing.T) {
var a3 gset.IntSet
err := json.UnmarshalUseNumber(b2, &a3)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(a2.Contains(1), true)
t.Assert(a2.Contains(2), true)
t.Assert(a2.Contains(3), true)
@ -402,7 +402,7 @@ func TestIntSet_UnmarshalValue(t *testing.T) {
"name": "john",
"set": []byte(`[1,2,3]`),
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Set.Size(), 3)
t.Assert(v.Set.Contains(1), true)
@ -417,7 +417,7 @@ func TestIntSet_UnmarshalValue(t *testing.T) {
"name": "john",
"set": g.Slice{1, 2, 3},
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Set.Size(), 3)
t.Assert(v.Set.Contains(1), true)

View File

@ -414,7 +414,7 @@ func TestStrSet_Json(t *testing.T) {
var a3 gset.StrSet
err := json.UnmarshalUseNumber(b2, &a3)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(a3.Contains("a"), true)
t.Assert(a3.Contains("b"), true)
t.Assert(a3.Contains("c"), true)
@ -453,7 +453,7 @@ func TestStrSet_UnmarshalValue(t *testing.T) {
"name": "john",
"set": []byte(`["1","2","3"]`),
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Set.Size(), 3)
t.Assert(v.Set.Contains("1"), true)
@ -468,7 +468,7 @@ func TestStrSet_UnmarshalValue(t *testing.T) {
"name": "john",
"set": g.SliceStr{"1", "2", "3"},
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Set.Size(), 3)
t.Assert(v.Set.Contains("1"), true)

View File

@ -7,6 +7,7 @@
package gtree
import (
"bytes"
"fmt"
"github.com/gogf/gf/v2/container/gvar"
@ -398,6 +399,9 @@ func (tree *AVLTree) Replace(data map[interface{}]interface{}) {
// String returns a string representation of container
func (tree *AVLTree) String() string {
if tree == nil {
return ""
}
tree.mu.RLock()
defer tree.mu.RUnlock()
str := ""
@ -780,8 +784,26 @@ func output(node *AVLTreeNode, prefix string, isTail bool, str *string) {
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (tree AVLTree) MarshalJSON() ([]byte, error) {
return json.Marshal(tree.MapStrAny())
func (tree AVLTree) MarshalJSON() (jsonBytes []byte, err error) {
if tree.root == nil {
return []byte("null"), nil
}
buffer := bytes.NewBuffer(nil)
buffer.WriteByte('{')
tree.Iterator(func(key, value interface{}) bool {
valueBytes, valueJsonErr := json.Marshal(value)
if valueJsonErr != nil {
err = valueJsonErr
return false
}
if buffer.Len() > 1 {
buffer.WriteByte(',')
}
buffer.WriteString(fmt.Sprintf(`"%v":%s`, key, valueBytes))
return true
})
buffer.WriteByte('}')
return buffer.Bytes(), nil
}
// getComparator returns the comparator if it's previously set,

View File

@ -367,6 +367,9 @@ func (tree *BTree) Right() *BTreeEntry {
// String returns a string representation of container (for debugging purposes)
func (tree *BTree) String() string {
if tree == nil {
return ""
}
tree.mu.RLock()
defer tree.mu.RUnlock()
var buffer bytes.Buffer
@ -944,8 +947,26 @@ func (tree *BTree) deleteChild(node *BTreeNode, index int) {
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (tree BTree) MarshalJSON() ([]byte, error) {
return json.Marshal(tree.MapStrAny())
func (tree BTree) MarshalJSON() (jsonBytes []byte, err error) {
if tree.root == nil {
return []byte("null"), nil
}
buffer := bytes.NewBuffer(nil)
buffer.WriteByte('{')
tree.Iterator(func(key, value interface{}) bool {
valueBytes, valueJsonErr := json.Marshal(value)
if valueJsonErr != nil {
err = valueJsonErr
return false
}
if buffer.Len() > 1 {
buffer.WriteByte(',')
}
buffer.WriteString(fmt.Sprintf(`"%v":%s`, key, valueBytes))
return true
})
buffer.WriteByte('}')
return buffer.Bytes(), nil
}
// getComparator returns the comparator if it's previously set,

View File

@ -7,6 +7,7 @@
package gtree
import (
"bytes"
"fmt"
"github.com/gogf/gf/v2/container/gvar"
@ -622,6 +623,9 @@ func (tree *RedBlackTree) Replace(data map[interface{}]interface{}) {
// String returns a string representation of container.
func (tree *RedBlackTree) String() string {
if tree == nil {
return ""
}
tree.mu.RLock()
defer tree.mu.RUnlock()
str := ""
@ -925,8 +929,26 @@ func (tree *RedBlackTree) nodeColor(node *RedBlackTreeNode) color {
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (tree RedBlackTree) MarshalJSON() ([]byte, error) {
return json.Marshal(gconv.Map(tree.Map()))
func (tree RedBlackTree) MarshalJSON() (jsonBytes []byte, err error) {
if tree.root == nil {
return []byte("null"), nil
}
buffer := bytes.NewBuffer(nil)
buffer.WriteByte('{')
tree.Iterator(func(key, value interface{}) bool {
valueBytes, valueJsonErr := json.Marshal(value)
if valueJsonErr != nil {
err = valueJsonErr
return false
}
if buffer.Len() > 1 {
buffer.WriteByte(',')
}
buffer.WriteString(fmt.Sprintf(`"%v":%s`, key, valueBytes))
return true
})
buffer.WriteByte('}')
return buffer.Bytes(), nil
}
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.

View File

@ -56,16 +56,16 @@ func Test_Bool_JSON(t *testing.T) {
var err error
i := gtype.NewBool()
err = json.UnmarshalUseNumber([]byte("true"), &i)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(i.Val(), true)
err = json.UnmarshalUseNumber([]byte("false"), &i)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(i.Val(), false)
err = json.UnmarshalUseNumber([]byte("1"), &i)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(i.Val(), true)
err = json.UnmarshalUseNumber([]byte("0"), &i)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(i.Val(), false)
})
@ -79,7 +79,7 @@ func Test_Bool_JSON(t *testing.T) {
i2 := gtype.NewBool()
err := json.UnmarshalUseNumber(b2, &i2)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(i2.Val(), i.Val())
})
gtest.C(t, func(t *gtest.T) {
@ -92,7 +92,7 @@ func Test_Bool_JSON(t *testing.T) {
i2 := gtype.NewBool()
err := json.UnmarshalUseNumber(b2, &i2)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(i2.Val(), i.Val())
})
}
@ -108,7 +108,7 @@ func Test_Bool_UnmarshalValue(t *testing.T) {
"name": "john",
"var": "true",
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Var.Val(), true)
})
@ -118,7 +118,7 @@ func Test_Bool_UnmarshalValue(t *testing.T) {
"name": "john",
"var": "false",
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Var.Val(), false)
})

View File

@ -54,7 +54,7 @@ func Test_Byte_JSON(t *testing.T) {
var err error
i := gtype.NewByte()
err = json.UnmarshalUseNumber([]byte("49"), &i)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(i.Val(), "49")
})
}
@ -70,7 +70,7 @@ func Test_Byte_UnmarshalValue(t *testing.T) {
"name": "john",
"var": "2",
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Var.Val(), "2")
})

View File

@ -40,7 +40,7 @@ func Test_Bytes_JSON(t *testing.T) {
i2 := gtype.NewBytes()
err := json.UnmarshalUseNumber(b2, &i2)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(i2.Val(), b)
})
}
@ -56,7 +56,7 @@ func Test_Bytes_UnmarshalValue(t *testing.T) {
"name": "john",
"var": "123",
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Var.Val(), "123")
})

View File

@ -42,7 +42,7 @@ func Test_Float32_JSON(t *testing.T) {
i2 := gtype.NewFloat32()
err := json.UnmarshalUseNumber(b2, &i2)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(i2.Val(), v)
})
}
@ -58,7 +58,7 @@ func Test_Float32_UnmarshalValue(t *testing.T) {
"name": "john",
"var": "123.456",
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Var.Val(), "123.456")
})

View File

@ -40,7 +40,7 @@ func Test_Float64_JSON(t *testing.T) {
i2 := gtype.NewFloat64()
err := json.UnmarshalUseNumber(b2, &i2)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(i2.Val(), v)
})
}
@ -56,7 +56,7 @@ func Test_Float64_UnmarshalValue(t *testing.T) {
"name": "john",
"var": "123.456",
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Var.Val(), "123.456")
})

View File

@ -53,7 +53,7 @@ func Test_Int32_JSON(t *testing.T) {
i2 := gtype.NewInt32()
err := json.UnmarshalUseNumber(b2, &i2)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(i2.Val(), v)
})
}
@ -69,7 +69,7 @@ func Test_Int32_UnmarshalValue(t *testing.T) {
"name": "john",
"var": "123",
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Var.Val(), "123")
})

View File

@ -52,7 +52,7 @@ func Test_Int64_JSON(t *testing.T) {
i2 := gtype.NewInt64()
err := json.UnmarshalUseNumber(b2, &i2)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(i2.Val(), i)
})
}
@ -68,7 +68,7 @@ func Test_Int64_UnmarshalValue(t *testing.T) {
"name": "john",
"var": "123",
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Var.Val(), "123")
})

View File

@ -52,7 +52,7 @@ func Test_Int_JSON(t *testing.T) {
i2 := gtype.NewInt()
err := json.UnmarshalUseNumber(b2, &i2)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(i2.Val(), v)
})
}
@ -68,7 +68,7 @@ func Test_Int_UnmarshalValue(t *testing.T) {
"name": "john",
"var": "123",
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Var.Val(), "123")
})

View File

@ -42,7 +42,7 @@ func Test_Interface_JSON(t *testing.T) {
i2 := gtype.New()
err := json.UnmarshalUseNumber(b2, &i2)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(i2.Val(), s)
})
}
@ -58,7 +58,7 @@ func Test_Interface_UnmarshalValue(t *testing.T) {
"name": "john",
"var": "123",
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Var.Val(), "123")
})

View File

@ -40,7 +40,7 @@ func Test_String_JSON(t *testing.T) {
i2 := gtype.NewString()
err := json.UnmarshalUseNumber(b2, &i2)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(i2.Val(), s)
})
}
@ -56,7 +56,7 @@ func Test_String_UnmarshalValue(t *testing.T) {
"name": "john",
"var": "123",
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Var.Val(), "123")
})

View File

@ -52,7 +52,7 @@ func Test_Uint32_JSON(t *testing.T) {
i2 := gtype.NewUint32()
err := json.UnmarshalUseNumber(b2, &i2)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(i2.Val(), i)
})
}
@ -68,7 +68,7 @@ func Test_Uint32_UnmarshalValue(t *testing.T) {
"name": "john",
"var": "123",
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Var.Val(), "123")
})

View File

@ -56,7 +56,7 @@ func Test_Uint64_JSON(t *testing.T) {
i2 := gtype.NewUint64()
err := json.UnmarshalUseNumber(b2, &i2)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(i2.Val(), i)
})
}
@ -72,7 +72,7 @@ func Test_Uint64_UnmarshalValue(t *testing.T) {
"name": "john",
"var": "123",
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Var.Val(), "123")
})

View File

@ -51,7 +51,7 @@ func Test_Uint_JSON(t *testing.T) {
i2 := gtype.NewUint()
err := json.UnmarshalUseNumber(b2, &i2)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(i2.Val(), i)
})
}
@ -67,7 +67,7 @@ func Test_Uint_UnmarshalValue(t *testing.T) {
"name": "john",
"var": "123",
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Var.Val(), "123")
})

View File

@ -18,10 +18,10 @@ import (
// New
func ExampleVarNew() {
v := gvar.New(400)
g.Dump(v)
fmt.Println(v)
// Output:
// "400"
// 400
}
// Clone

View File

@ -253,7 +253,7 @@ func Test_UnmarshalJson(t *testing.T) {
"name": "john",
"var": "v",
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Var.String(), "v")
})
@ -267,7 +267,7 @@ func Test_UnmarshalJson(t *testing.T) {
"name": "john",
"var": "v",
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Var.String(), "v")
})
@ -284,7 +284,7 @@ func Test_UnmarshalValue(t *testing.T) {
"name": "john",
"var": "v",
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Var.String(), "v")
})
@ -298,7 +298,7 @@ func Test_UnmarshalValue(t *testing.T) {
"name": "john",
"var": "v",
}, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Var.String(), "v")
})

View File

@ -40,10 +40,10 @@ func TestVar_Json(t *testing.T) {
s := "i love gf"
v := gvar.New(nil)
b, err := json.Marshal(s)
t.Assert(err, nil)
t.AssertNil(err)
err = json.UnmarshalUseNumber(b, v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.String(), s)
})
@ -51,10 +51,10 @@ func TestVar_Json(t *testing.T) {
var v gvar.Var
s := "i love gf"
b, err := json.Marshal(s)
t.Assert(err, nil)
t.AssertNil(err)
err = json.UnmarshalUseNumber(b, &v)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(v.String(), s)
})
}

View File

@ -55,7 +55,7 @@ func TestVar_Var_Attribute_Struct(t *testing.T) {
"uid": gvar.New(1),
"name": gvar.New("john"),
}, user)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(user.Uid, 1)
t.Assert(user.Name, "john")
})
@ -70,7 +70,7 @@ func TestVar_Var_Attribute_Struct(t *testing.T) {
"uid": gvar.New(1),
"name": gvar.New("john"),
}, &user)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(user.Uid, 1)
t.Assert(user.Name, "john")
})

View File

@ -223,7 +223,7 @@ func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string,
return nil, err
}
result, err = d.DoGetAll(ctx, link, `SELECT NAME FROM SYSOBJECTS WHERE XTYPE='U' AND STATUS >= 0 ORDER BY NAME`)
result, err = d.DoSelect(ctx, link, `SELECT NAME FROM SYSOBJECTS WHERE XTYPE='U' AND STATUS >= 0 ORDER BY NAME`)
if err != nil {
return
}
@ -289,7 +289,7 @@ ORDER BY a.id,a.colorder`,
table,
)
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
result, err = d.DoGetAll(ctx, link, structureSql)
result, err = d.DoSelect(ctx, link, structureSql)
if err != nil {
return nil
}

View File

@ -194,7 +194,7 @@ func (d *Driver) parseSql(sql string) string {
// Note that it ignores the parameter `schema` in oracle database, as it is not necessary.
func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string, err error) {
var result gdb.Result
result, err = d.DoGetAll(ctx, nil, "SELECT TABLE_NAME FROM USER_TABLES ORDER BY TABLE_NAME")
result, err = d.DoSelect(ctx, nil, "SELECT TABLE_NAME FROM USER_TABLES ORDER BY TABLE_NAME")
if err != nil {
return
}
@ -245,7 +245,7 @@ FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`,
return nil
}
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
result, err = d.DoGetAll(ctx, link, structureSql)
result, err = d.DoSelect(ctx, link, structureSql)
if err != nil {
return nil
}

View File

@ -142,7 +142,7 @@ func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string,
schema[0],
)
}
result, err = d.DoGetAll(ctx, link, query)
result, err = d.DoSelect(ctx, link, query)
if err != nil {
return
}
@ -198,7 +198,7 @@ ORDER BY a.attnum`,
return nil
}
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
result, err = d.DoGetAll(ctx, link, structureSql)
result, err = d.DoSelect(ctx, link, structureSql)
if err != nil {
return nil
}

View File

@ -106,7 +106,7 @@ func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string,
return nil, err
}
result, err = d.DoGetAll(ctx, link, `SELECT NAME FROM SQLITE_MASTER WHERE TYPE='table' ORDER BY NAME`)
result, err = d.DoSelect(ctx, link, `SELECT NAME FROM SQLITE_MASTER WHERE TYPE='table' ORDER BY NAME`)
if err != nil {
return
}
@ -141,7 +141,7 @@ func (d *Driver) TableFields(ctx context.Context, table string, schema ...string
if link, err = d.SlaveLink(useSchema); err != nil {
return nil
}
result, err = d.DoGetAll(ctx, link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, table))
result, err = d.DoSelect(ctx, link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, table))
if err != nil {
return nil
}

View File

@ -41,19 +41,19 @@ var (
func TestEncrypt(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
data, err := gaes.Encrypt(content, key_16)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(data, []byte(content_16))
data, err = gaes.Encrypt(content, key_24)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(data, []byte(content_24))
data, err = gaes.Encrypt(content, key_32)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(data, []byte(content_32))
data, err = gaes.Encrypt(content, key_16, iv)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(data, []byte(content_16_iv))
data, err = gaes.Encrypt(content, key_32, iv)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(data, []byte(content_32_iv))
})
}
@ -61,23 +61,23 @@ func TestEncrypt(t *testing.T) {
func TestDecrypt(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
decrypt, err := gaes.Decrypt([]byte(content_16), key_16)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(decrypt, content)
decrypt, err = gaes.Decrypt([]byte(content_24), key_24)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(decrypt, content)
decrypt, err = gaes.Decrypt([]byte(content_32), key_32)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(decrypt, content)
decrypt, err = gaes.Decrypt([]byte(content_16_iv), key_16, iv)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(decrypt, content)
decrypt, err = gaes.Decrypt([]byte(content_32_iv), key_32, iv)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(decrypt, content)
decrypt, err = gaes.Decrypt([]byte(content_32_iv), keys, iv)
@ -134,7 +134,7 @@ func TestEncryptCFB(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var padding int = 0
data, err := gaes.EncryptCFB(content, key_16, &padding, iv)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(padding, padding_size)
t.Assert(data, []byte(content_16_cfb))
})
@ -143,7 +143,7 @@ func TestEncryptCFB(t *testing.T) {
func TestDecryptCFB(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
decrypt, err := gaes.DecryptCFB([]byte(content_16_cfb), key_16, padding_size, iv)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(decrypt, content)
})
}

View File

@ -65,7 +65,7 @@ func TestEncryptFile(t *testing.T) {
file, err := os.Create(path)
defer os.Remove(path)
defer file.Close()
t.Assert(err, nil)
t.AssertNil(err)
_, _ = file.Write([]byte("Hello Go Frame"))
encryptFile, _ := gmd5.EncryptFile(path)
t.AssertEQ(encryptFile, result)

View File

@ -48,7 +48,7 @@ func TestEncryptFile(t *testing.T) {
file, err := os.Create(path)
defer os.Remove(path)
defer file.Close()
t.Assert(err, nil)
t.AssertNil(err)
_, _ = file.Write([]byte("Hello Go Frame"))
encryptFile, _ := gsha1.EncryptFile(path)
t.AssertEQ(encryptFile, result)

View File

@ -20,6 +20,7 @@ import (
"github.com/gogf/gf/v2/internal/intlog"
"github.com/gogf/gf/v2/os/gcache"
"github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/glog"
"github.com/gogf/gf/v2/util/grand"
)
@ -94,15 +95,18 @@ type DB interface {
// Internal APIs for CURD, which can be overwritten by custom CURD implements.
// ===========================================================================
DoGetAll(ctx context.Context, link Link, sql string, args ...interface{}) (result Result, err error) // See Core.DoGetAll.
DoSelect(ctx context.Context, link Link, sql string, args ...interface{}) (result Result, err error) // See Core.DoSelect.
DoInsert(ctx context.Context, link Link, table string, data List, option DoInsertOption) (result sql.Result, err error) // See Core.DoInsert.
DoUpdate(ctx context.Context, link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) // See Core.DoUpdate.
DoDelete(ctx context.Context, link Link, table string, condition string, args ...interface{}) (result sql.Result, err error) // See Core.DoDelete.
DoQuery(ctx context.Context, link Link, sql string, args ...interface{}) (result Result, err error) // See Core.DoQuery.
DoExec(ctx context.Context, link Link, sql string, args ...interface{}) (result sql.Result, err error) // See Core.DoExec.
DoFilter(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) // See Core.DoFilter.
DoCommit(ctx context.Context, in DoCommitInput) (out DoCommitOutput, err error) // See Core.DoCommit.
DoPrepare(ctx context.Context, link Link, sql string) (*Stmt, error) // See Core.DoPrepare.
DoQuery(ctx context.Context, link Link, sql string, args ...interface{}) (result Result, err error) // See Core.DoQuery.
DoExec(ctx context.Context, link Link, sql string, args ...interface{}) (result sql.Result, err error) // See Core.DoExec.
DoFilter(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) // See Core.DoFilter.
DoCommit(ctx context.Context, in DoCommitInput) (out DoCommitOutput, err error) // See Core.DoCommit.
DoPrepare(ctx context.Context, link Link, sql string) (*Stmt, error) // See Core.DoPrepare.
// ===========================================================================
// Query APIs for convenience purpose.
@ -211,13 +215,12 @@ type Driver interface {
}
// Link is a common database function wrapper interface.
// Note that, any operation using `Link` will have no SQL logging.
type Link interface {
Query(sql string, args ...interface{}) (*sql.Rows, error)
Exec(sql string, args ...interface{}) (sql.Result, error)
Prepare(sql string) (*sql.Stmt, error)
QueryContext(ctx context.Context, sql string, args ...interface{}) (*sql.Rows, error)
ExecContext(ctx context.Context, sql string, args ...interface{}) (sql.Result, error)
PrepareContext(ctx context.Context, sql string) (*sql.Stmt, error)
IsOnMaster() bool
IsTransaction() bool
}
@ -237,10 +240,10 @@ type Sql struct {
// DoInsertOption is the input struct for function DoInsert.
type DoInsertOption struct {
OnDuplicateStr string
OnDuplicateMap map[string]interface{}
InsertOption int // Insert operation.
BatchCount int // Batch count for batch inserting.
OnDuplicateStr string // Custom string for `on duplicated` statement.
OnDuplicateMap map[string]interface{} // Custom key-value map from `OnDuplicateEx` function for `on duplicated` statement.
InsertOption int // Insert operation in constant value.
BatchCount int // Batch count for batch inserting.
}
// TableField is the struct for table field.
@ -284,9 +287,10 @@ const (
ctxTimeoutTypeExec = iota
ctxTimeoutTypeQuery
ctxTimeoutTypePrepare
commandEnvKeyForDryRun = "gf.gdb.dryrun"
modelForDaoSuffix = `ForDao`
dbRoleSlave = `slave`
commandEnvKeyForDryRun = "gf.gdb.dryrun"
modelForDaoSuffix = `ForDao`
dbRoleSlave = `slave`
contextKeyForDB gctx.StrKey = `DBInContext`
)
const (
@ -409,7 +413,8 @@ func doNewByNode(node ConfigNode, group string) (db DB, err error) {
return c.db, nil
}
errorMsg := `cannot find database driver for specified database type "%s"`
errorMsg += `, did you misspell type name "%s" or forget importing the database driver?`
errorMsg += `, did you misspell type name "%s" or forget importing the database driver? `
errorMsg += `possible reference: https://github.com/gogf/gf/tree/master/contrib/drivers`
return nil, gerror.NewCodef(gcode.CodeInvalidConfiguration, errorMsg, node.Type, node.Type)
}
@ -517,8 +522,11 @@ func getConfigNodeByWeight(cg ConfigGroup) *ConfigNode {
// The parameter `master` specifies whether retrieves master node connection if
// master-slave nodes are configured.
func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error) {
var (
ctx = c.db.GetCtx()
node *ConfigNode
)
// Load balance.
var node *ConfigNode
if c.group != "" {
node, err = getConfigNodeByGroup(c.group, master)
if err != nil {
@ -544,20 +552,12 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error
}
// Cache the underlying connection pool object by node.
v := c.links.GetOrSetFuncLock(node.String(), func() interface{} {
intlog.Printf(
c.db.GetCtx(),
`open new connection, master:%#v, config:%#v, node:%#v`,
master, c.config, node,
)
intlog.Printf(ctx, `open new connection, master:%#v, config:%#v, node:%#v`, master, c.config, node)
defer func() {
if err != nil {
intlog.Printf(c.db.GetCtx(), `open new connection failed: %v, %#v`, err, node)
intlog.Printf(ctx, `open new connection failed: %v, %#v`, err, node)
} else {
intlog.Printf(
c.db.GetCtx(),
`open new connection success, master:%#v, config:%#v, node:%#v`,
master, c.config, node,
)
intlog.Printf(ctx, `open new connection success, master:%#v, config:%#v, node:%#v`, master, c.config, node)
}
}()

View File

@ -18,6 +18,7 @@ import (
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/intlog"
"github.com/gogf/gf/v2/internal/reflection"
"github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
@ -44,7 +45,6 @@ func (c *Core) Ctx(ctx context.Context) DB {
configNode = c.db.GetConfig()
)
*newCore = *c
newCore.ctx = ctx
// It creates a new DB object, which is commonly a wrapper for object `Core`.
newCore.db, err = driverMap[configNode.Type].New(newCore, configNode)
if err != nil {
@ -52,22 +52,25 @@ func (c *Core) Ctx(ctx context.Context) DB {
// Do not let it continue.
panic(err)
}
newCore.ctx = WithDB(ctx, newCore.db)
newCore.ctx = c.injectInternalCtxData(newCore.ctx)
return newCore.db
}
// GetCtx returns the context for current DB.
// It returns `context.Background()` is there's no context previously set.
func (c *Core) GetCtx() context.Context {
if c.ctx != nil {
return c.ctx
ctx := c.ctx
if ctx == nil {
ctx = context.TODO()
}
return context.TODO()
return c.injectInternalCtxData(ctx)
}
// GetCtxTimeout returns the context and cancel function for specified timeout type.
func (c *Core) GetCtxTimeout(timeoutType int, ctx context.Context) (context.Context, context.CancelFunc) {
func (c *Core) GetCtxTimeout(ctx context.Context, timeoutType int) (context.Context, context.CancelFunc) {
if ctx == nil {
ctx = c.GetCtx()
ctx = c.db.GetCtx()
} else {
ctx = context.WithValue(ctx, "WrappedByGetCtxTimeout", nil)
}
@ -144,11 +147,11 @@ func (c *Core) Slave(schema ...string) (*sql.DB, error) {
// GetAll queries and returns data records from database.
func (c *Core) GetAll(ctx context.Context, sql string, args ...interface{}) (Result, error) {
return c.db.DoGetAll(ctx, nil, sql, args...)
return c.db.DoSelect(ctx, nil, sql, args...)
}
// DoGetAll queries and returns data records from database.
func (c *Core) DoGetAll(ctx context.Context, link Link, sql string, args ...interface{}) (result Result, err error) {
// DoSelect queries and returns data records from database.
func (c *Core) DoSelect(ctx context.Context, link Link, sql string, args ...interface{}) (result Result, err error) {
return c.db.DoQuery(ctx, link, sql, args...)
}
@ -167,7 +170,7 @@ func (c *Core) GetOne(ctx context.Context, sql string, args ...interface{}) (Rec
// GetArray queries and returns data values as slice from database.
// Note that if there are multiple columns in the result, it returns just one column values randomly.
func (c *Core) GetArray(ctx context.Context, sql string, args ...interface{}) ([]Value, error) {
all, err := c.db.DoGetAll(ctx, nil, sql, args...)
all, err := c.db.DoSelect(ctx, nil, sql, args...)
if err != nil {
return nil, err
}
@ -201,7 +204,7 @@ func (c *Core) GetStructs(ctx context.Context, pointer interface{}, sql string,
// the conversion. If parameter `pointer` is type of slice, it calls GetStructs internally
// for conversion.
func (c *Core) GetScan(ctx context.Context, pointer interface{}, sql string, args ...interface{}) error {
reflectInfo := utils.OriginTypeAndKind(pointer)
reflectInfo := reflection.OriginTypeAndKind(pointer)
if reflectInfo.InputKind != reflect.Ptr {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
@ -239,7 +242,7 @@ func (c *Core) GetValue(ctx context.Context, sql string, args ...interface{}) (V
// GetCount queries and returns the count from database.
func (c *Core) GetCount(ctx context.Context, sql string, args ...interface{}) (int, error) {
// If the query fields do not contains function "COUNT",
// If the query fields do not contain function "COUNT",
// it replaces the sql string and adds the "COUNT" function to the fields.
if !gregex.IsMatchString(`(?i)SELECT\s+COUNT\(.+\)\s+FROM`, sql) {
sql, _ = gregex.ReplaceString(`(?i)(SELECT)\s+(.+)\s+(FROM)`, `$1 COUNT($2) $3`, sql)
@ -253,15 +256,17 @@ func (c *Core) GetCount(ctx context.Context, sql string, args ...interface{}) (i
// Union does "(SELECT xxx FROM xxx) UNION (SELECT xxx FROM xxx) ..." statement.
func (c *Core) Union(unions ...*Model) *Model {
return c.doUnion(unionTypeNormal, unions...)
var ctx = c.db.GetCtx()
return c.doUnion(ctx, unionTypeNormal, unions...)
}
// UnionAll does "(SELECT xxx FROM xxx) UNION ALL (SELECT xxx FROM xxx) ..." statement.
func (c *Core) UnionAll(unions ...*Model) *Model {
return c.doUnion(unionTypeAll, unions...)
var ctx = c.db.GetCtx()
return c.doUnion(ctx, unionTypeAll, unions...)
}
func (c *Core) doUnion(unionType int, unions ...*Model) *Model {
func (c *Core) doUnion(ctx context.Context, unionType int, unions ...*Model) *Model {
var (
unionTypeStr string
composedSqlStr string
@ -273,7 +278,7 @@ func (c *Core) doUnion(unionType int, unions ...*Model) *Model {
unionTypeStr = "UNION"
}
for _, v := range unions {
sqlWithHolder, holderArgs := v.getFormattedSqlAndArgs(queryTypeNormal, false)
sqlWithHolder, holderArgs := v.getFormattedSqlAndArgs(ctx, queryTypeNormal, false)
if composedSqlStr == "" {
composedSqlStr += fmt.Sprintf(`(%s)`, sqlWithHolder)
} else {
@ -286,10 +291,11 @@ func (c *Core) doUnion(unionType int, unions ...*Model) *Model {
// PingMaster pings the master node to check authentication or keeps the connection alive.
func (c *Core) PingMaster() error {
var ctx = c.db.GetCtx()
if master, err := c.db.Master(); err != nil {
return err
} else {
if err = master.PingContext(c.GetCtx()); err != nil {
if err = master.PingContext(ctx); err != nil {
err = gerror.WrapCode(gcode.CodeDbOperationError, err, `master.Ping failed`)
}
return err
@ -298,10 +304,11 @@ func (c *Core) PingMaster() error {
// PingSlave pings the slave node to check authentication or keeps the connection alive.
func (c *Core) PingSlave() error {
var ctx = c.db.GetCtx()
if slave, err := c.db.Slave(); err != nil {
return err
} else {
if err = slave.PingContext(c.GetCtx()); err != nil {
if err = slave.PingContext(ctx); err != nil {
err = gerror.WrapCode(gcode.CodeDbOperationError, err, `slave.Ping failed`)
}
return err
@ -660,21 +667,22 @@ func (c *Core) writeSqlToLogger(ctx context.Context, sql *Sql) {
// HasTable determine whether the table name exists in the database.
func (c *Core) HasTable(name string) (bool, error) {
result, err := c.GetCache().GetOrSetFuncLock(
c.GetCtx(),
fmt.Sprintf(`HasTable: %s`, name),
func(ctx context.Context) (interface{}, error) {
tableList, err := c.db.Tables(ctx)
if err != nil {
return false, err
var (
ctx = c.db.GetCtx()
cacheKey = fmt.Sprintf(`HasTable: %s`, name)
)
result, err := c.GetCache().GetOrSetFuncLock(ctx, cacheKey, func(ctx context.Context) (interface{}, error) {
tableList, err := c.db.Tables(ctx)
if err != nil {
return false, err
}
for _, table := range tableList {
if table == name {
return true, nil
}
for _, table := range tableList {
if table == name {
return true, nil
}
}
return false, nil
}, 0,
}
return false, nil
}, 0,
)
if err != nil {
return false, err

View File

@ -0,0 +1,46 @@
// 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 gdb
import (
"context"
"github.com/gogf/gf/v2/os/gctx"
)
// internalCtxData stores data in ctx for internal usage purpose.
type internalCtxData struct {
// Operation DB.
DB DB
// The first column in result response from database server.
// This attribute is used for Value/Count selection statement purpose,
// which is to avoid HOOK handler that might modify the result columns
// that can confuse the Value/Count selection statement logic.
FirstResultColumn string
}
const (
internalCtxDataKeyInCtx gctx.StrKey = "InternalCtxData"
)
func (c *Core) injectInternalCtxData(ctx context.Context) context.Context {
// If the internal data is already injected, it does nothing.
if ctx.Value(internalCtxDataKeyInCtx) != nil {
return ctx
}
return context.WithValue(ctx, internalCtxDataKeyInCtx, &internalCtxData{
DB: c.db,
})
}
func (c *Core) getInternalCtxDataFromCtx(ctx context.Context) *internalCtxData {
if v := ctx.Value(internalCtxDataKeyInCtx); v != nil {
return v.(*internalCtxData)
}
return nil
}

View File

@ -12,7 +12,8 @@ import (
// dbLink is used to implement interface Link for DB.
type dbLink struct {
*sql.DB
*sql.DB // Underlying DB object.
isOnMaster bool // isOnMaster marks whether current link is operated on master node.
}
// txLink is used to implement interface Link for TX.
@ -21,11 +22,22 @@ type txLink struct {
}
// IsTransaction returns if current Link is a transaction.
func (*dbLink) IsTransaction() bool {
func (l *dbLink) IsTransaction() bool {
return false
}
// IsOnMaster checks and returns whether current link is operated on master node.
func (l *dbLink) IsOnMaster() bool {
return l.isOnMaster
}
// IsTransaction returns if current Link is a transaction.
func (*txLink) IsTransaction() bool {
func (l *txLink) IsTransaction() bool {
return true
}
// IsOnMaster checks and returns whether current link is operated on master node.
// Note that, transaction operation is always operated on master node.
func (l *txLink) IsOnMaster() bool {
return true
}

View File

@ -0,0 +1,245 @@
// 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 gdb
import (
"context"
"strings"
"github.com/gogf/gf/v2/container/gvar"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gctx"
"github.com/longbridgeapp/sqlparser"
)
// ShardingInput is input parameters for custom sharding handler.
type ShardingInput struct {
Table string // Current operation table name.
Schema string // Current operation schema, usually empty string which means uses default schema from configuration.
OperationData map[string]Value // Accurate readonly key-value data pairs from INSERT/UPDATE statement.
ConditionData map[string]Value // Accurate readonly key-value condition pairs from SELECT/UPDATE/DELETE statement.
}
// ShardingOutput is output parameters for custom sharding handler.
type ShardingOutput struct {
Table string // New table name for current operation. Use empty string for no changes of table name.
Schema string // New schema name for current operation. Use empty string for using default schema from configuration.
}
// ShardingHandler is a custom function for custom sharding table and schema for DB operation.
type ShardingHandler func(ctx context.Context, in ShardingInput) (out *ShardingOutput, err error)
const (
ctxKeyForShardingHandler gctx.StrKey = "ShardingHandler"
)
// Sharding creates and returns a new model with sharding handler.
func (m *Model) Sharding(handler ShardingHandler) *Model {
var (
ctx = m.GetCtx()
model = m.getModel()
)
model.shardingHandler = handler
// Inject sharding handler into context.
model = model.Ctx(model.injectShardingInputCaller(ctx))
return model
}
// injectShardingInputCaller injects custom sharding handler into context.
func (m *Model) injectShardingInputCaller(ctx context.Context) context.Context {
if m.shardingHandler == nil {
return ctx
}
if ctx.Value(ctxKeyForShardingHandler) != nil {
return ctx
}
return context.WithValue(ctx, ctxKeyForShardingHandler, m.shardingHandler)
}
type callShardingHandlerFromCtxInput struct {
Sql string
FormattedSql string
}
type callShardingHandlerFromCtxOutput struct {
Sql string
Table string
Schema string
ParsedSqlOutput *parseFormattedSqlOutput
}
func (c *Core) callShardingHandlerFromCtx(
ctx context.Context, in callShardingHandlerFromCtxInput,
) (out *callShardingHandlerFromCtxOutput, err error) {
var (
newSql = in.Sql
ctxValue interface{}
shardingHandler ShardingHandler
ok bool
)
// If no sharding handler, it does nothing.
if ctxValue = ctx.Value(ctxKeyForShardingHandler); ctxValue == nil {
return nil, nil
}
if shardingHandler, ok = ctxValue.(ShardingHandler); !ok {
return nil, nil
}
parsedOut, err := c.parseFormattedSql(in.FormattedSql)
if err != nil {
return nil, err
}
var shardingIn = ShardingInput{
Table: parsedOut.Table,
Schema: c.db.GetSchema(),
OperationData: parsedOut.OperationData,
ConditionData: parsedOut.ConditionData,
}
shardingOut, err := shardingHandler(ctx, shardingIn)
if err != nil {
return nil, gerror.Wrap(err, `calling sharding handler failed`)
}
if shardingOut.Table != shardingIn.Table || shardingOut.Schema != shardingIn.Schema {
if shardingOut.Table != shardingIn.Table {
newSql, err = c.formatSqlWithNewTable(in.Sql, shardingOut.Table)
if err != nil {
return nil, err
}
}
out = &callShardingHandlerFromCtxOutput{
Sql: newSql,
Table: shardingOut.Table,
Schema: shardingOut.Schema,
ParsedSqlOutput: parsedOut,
}
return out, nil
}
return nil, nil
}
// formatSqlWithNewTable modifies given `sql` and returns a sql with new table name `table`.
func (c *Core) formatSqlWithNewTable(sql, table string) (newSql string, err error) {
parsedStmt, err := sqlparser.NewParser(strings.NewReader(sql)).ParseStatement()
if err != nil {
return "", gerror.Wrapf(err, `parse failed for SQL: %s`, sql)
}
newTable := &sqlparser.TableName{Name: &sqlparser.Ident{Name: table}}
switch stmt := parsedStmt.(type) {
case *sqlparser.SelectStatement:
stmt.FromItems = newTable
return stmt.String(), nil
case *sqlparser.InsertStatement:
stmt.TableName = newTable
return stmt.String(), nil
case *sqlparser.UpdateStatement:
stmt.TableName = newTable
return stmt.String(), nil
case *sqlparser.DeleteStatement:
stmt.TableName = newTable
return stmt.String(), nil
default:
return "", gerror.Wrapf(err, `unsupported SQL: %s`, sql)
}
}
type parseFormattedSqlOutput struct {
Table string
OperationData map[string]Value
ConditionData map[string]Value
ParsedStmt sqlparser.Statement
SelectedFields []string
}
func (c *Core) parseFormattedSql(formattedSql string) (*parseFormattedSqlOutput, error) {
var (
condition sqlparser.Expr
err error
out = &parseFormattedSqlOutput{
SelectedFields: make([]string, 0),
OperationData: make(map[string]Value),
ConditionData: make(map[string]Value),
}
)
out.ParsedStmt, err = sqlparser.NewParser(strings.NewReader(formattedSql)).ParseStatement()
if err != nil {
return nil, gerror.Wrapf(err, `parse failed for SQL: %s`, formattedSql)
}
switch stmt := out.ParsedStmt.(type) {
case *sqlparser.SelectStatement:
if stmt.FromItems != nil {
table, ok := stmt.FromItems.(*sqlparser.TableName)
if !ok {
return nil, gerror.Newf(
`invalid table name "%s" in SQL: %s`,
stmt.FromItems.String(), formattedSql,
)
}
out.Table = table.TableName()
}
condition = stmt.Condition
if stmt.Columns != nil {
for _, column := range *stmt.Columns {
if column.Alias != nil {
out.SelectedFields = append(out.SelectedFields, column.Alias.Name)
} else if column.Expr != nil {
out.SelectedFields = append(out.SelectedFields, column.Expr.String())
}
}
}
case *sqlparser.InsertStatement:
out.Table = stmt.TableName.TableName()
if len(stmt.Expressions) > 0 && len(stmt.ColumnNames) > 0 {
names := make([]string, len(stmt.ColumnNames))
for i, ident := range stmt.ColumnNames {
names[i] = ident.Name
}
// It just uses the first item.
for i, expr := range stmt.Expressions[0].Exprs {
c.injectDataByExpr(out.OperationData, names[i], expr)
}
}
case *sqlparser.UpdateStatement:
out.Table = stmt.TableName.TableName()
condition = stmt.Condition
if len(stmt.Assignments) > 0 {
for _, assignment := range stmt.Assignments {
if len(assignment.Columns) > 0 {
c.injectDataByExpr(out.OperationData, assignment.Columns[0].Name, assignment.Expr)
}
}
}
case *sqlparser.DeleteStatement:
out.Table = stmt.TableName.TableName()
condition = stmt.Condition
default:
return nil, gerror.Wrapf(err, `unsupported SQL: %s`, formattedSql)
}
err = sqlparser.Walk(sqlparser.VisitFunc(func(node sqlparser.Node) error {
if n, ok := node.(*sqlparser.BinaryExpr); ok {
if x, ok := n.X.(*sqlparser.Ident); ok {
if n.Op == sqlparser.EQ {
c.injectDataByExpr(out.ConditionData, x.Name, n.Y)
}
}
}
return nil
}), condition)
return out, err
}
func (c *Core) injectDataByExpr(data map[string]Value, name string, expr sqlparser.Expr) {
switch exprImp := expr.(type) {
case *sqlparser.StringLit:
data[name] = gvar.New(exprImp.Value)
case *sqlparser.NumberLit:
data[name] = gvar.New(exprImp.Value)
default:
data[name] = gvar.New(exprImp.String())
}
}

View File

@ -14,7 +14,7 @@ import (
"github.com/gogf/gf/v2/container/gtype"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/internal/reflection"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/util/gconv"
)
@ -71,7 +71,7 @@ func (c *Core) doBeginCtx(ctx context.Context) (*TX, error) {
func (c *Core) Transaction(ctx context.Context, f func(ctx context.Context, tx *TX) error) (err error) {
var tx *TX
if ctx == nil {
ctx = c.GetCtx()
ctx = c.db.GetCtx()
}
// Check transaction object from context.
tx = TXFromCtx(ctx, c.db.GetGroup())
@ -158,6 +158,9 @@ func (tx *TX) transactionKeyForNestedPoint() string {
// Ctx sets the context for current transaction.
func (tx *TX) Ctx(ctx context.Context) *TX {
tx.ctx = ctx
if tx.ctx != nil {
tx.ctx = tx.db.GetCore().injectInternalCtxData(tx.ctx)
}
return tx
}
@ -341,7 +344,7 @@ func (tx *TX) GetStructs(objPointerSlice interface{}, sql string, args ...interf
// the conversion. If parameter `pointer` is type of slice, it calls GetStructs internally
// for conversion.
func (tx *TX) GetScan(pointer interface{}, sql string, args ...interface{}) error {
reflectInfo := utils.OriginTypeAndKind(pointer)
reflectInfo := reflection.OriginTypeAndKind(pointer)
if reflectInfo.InputKind != reflect.Ptr {
return gerror.NewCodef(
gcode.CodeInvalidParameter,

View File

@ -129,8 +129,45 @@ func (c *Core) DoFilter(ctx context.Context, link Link, sql string, args []inter
return sql, args, nil
}
type sqlParsingHandlerInput struct {
DoCommitInput
FormattedSql string
}
type sqlParsingHandlerOutput struct {
DoCommitInput
}
func (c *Core) sqlParsingHandler(ctx context.Context, in sqlParsingHandlerInput) (out *sqlParsingHandlerOutput, err error) {
var shardingOut *callShardingHandlerFromCtxOutput
// Sharding handling.
shardingOut, err = c.callShardingHandlerFromCtx(ctx, callShardingHandlerFromCtxInput{
Sql: in.Sql,
FormattedSql: in.FormattedSql,
})
if err != nil {
return
}
if shardingOut != nil {
if shardingOut.Sql != "" {
in.Sql = shardingOut.Sql
}
// If schema changes, it here creates and uses a new DB link operation object.
if shardingOut.Schema != c.db.GetSchema() {
in.Link, err = c.db.GetCore().GetLink(ctx, in.Link.IsOnMaster(), shardingOut.Schema)
}
}
out = &sqlParsingHandlerOutput{
DoCommitInput: in.DoCommitInput,
}
return
}
// DoCommit commits current sql and arguments to underlying sql driver.
func (c *Core) DoCommit(ctx context.Context, in DoCommitInput) (out DoCommitOutput, err error) {
// Inject internal data into ctx, especially for transaction creating.
ctx = c.injectInternalCtxData(ctx)
var (
sqlTx *sql.Tx
sqlStmt *sql.Stmt
@ -140,9 +177,22 @@ func (c *Core) DoCommit(ctx context.Context, in DoCommitInput) (out DoCommitOutp
stmtSqlRow *sql.Row
rowsAffected int64
cancelFuncForTimeout context.CancelFunc
formattedSql = FormatSqlWithArgs(in.Sql, in.Args)
timestampMilli1 = gtime.TimestampMilli()
)
// SQL parser handler.
sqlParsingHandlerOut, err := c.sqlParsingHandler(ctx, sqlParsingHandlerInput{
DoCommitInput: in,
FormattedSql: formattedSql,
})
if err != nil {
return
}
if sqlParsingHandlerOut != nil {
in = sqlParsingHandlerOut.DoCommitInput
}
// Trace span start.
tr := otel.GetTracerProvider().Tracer(traceInstrumentName, trace.WithInstrumentationVersion(gf.VERSION))
ctx, span := tr.Start(ctx, in.Type, trace.WithSpanKind(trace.SpanKindInternal))
@ -186,7 +236,7 @@ func (c *Core) DoCommit(ctx context.Context, in DoCommitInput) (out DoCommitOutp
out.RawResult = sqlStmt
case SqlTypeStmtExecContext:
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctxTimeoutTypeExec, ctx)
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctx, ctxTimeoutTypeExec)
defer cancelFuncForTimeout()
if c.db.GetDryRun() {
sqlResult = new(SqlResult)
@ -196,13 +246,13 @@ func (c *Core) DoCommit(ctx context.Context, in DoCommitInput) (out DoCommitOutp
out.RawResult = sqlResult
case SqlTypeStmtQueryContext:
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctxTimeoutTypeQuery, ctx)
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctx, ctxTimeoutTypeQuery)
defer cancelFuncForTimeout()
stmtSqlRows, err = in.Stmt.QueryContext(ctx, in.Args...)
out.RawResult = stmtSqlRows
case SqlTypeStmtQueryRowContext:
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctxTimeoutTypeQuery, ctx)
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctx, ctxTimeoutTypeQuery)
defer cancelFuncForTimeout()
stmtSqlRow = in.Stmt.QueryRowContext(ctx, in.Args...)
out.RawResult = stmtSqlRow
@ -234,7 +284,7 @@ func (c *Core) DoCommit(ctx context.Context, in DoCommitInput) (out DoCommitOutp
Sql: in.Sql,
Type: in.Type,
Args: in.Args,
Format: FormatSqlWithArgs(in.Sql, in.Args),
Format: formattedSql,
Error: err,
Start: timestampMilli1,
End: timestampMilli2,
@ -351,6 +401,11 @@ func (c *Core) RowsToResult(ctx context.Context, rows *sql.Rows) (Result, error)
columnTypes[k] = v.DatabaseTypeName()
columnNames[k] = v.Name()
}
if len(columnNames) > 0 {
if internalData := c.getInternalCtxDataFromCtx(ctx); internalData != nil {
internalData.FirstResultColumn = columnNames[0]
}
}
var (
values = make([]interface{}, len(columnNames))
result = make(Result, 0)

View File

@ -8,12 +8,60 @@
package gdb
import (
"context"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
)
// WithDB injects given db object into context and returns a new context.
func WithDB(ctx context.Context, db DB) context.Context {
if db == nil {
return ctx
}
dbCtx := db.GetCtx()
if ctxDb := DBFromCtx(dbCtx); ctxDb != nil {
return dbCtx
}
ctx = context.WithValue(ctx, contextKeyForDB, db)
return ctx
}
// DBFromCtx retrieves and returns DB object from context.
func DBFromCtx(ctx context.Context) DB {
if ctx == nil {
return nil
}
v := ctx.Value(contextKeyForDB)
if v != nil {
return v.(DB)
}
return nil
}
// GetLink creates and returns the underlying database link object with transaction checks.
// The parameter `master` specifies whether using the master node if master-slave configured.
func (c *Core) GetLink(ctx context.Context, master bool, schema string) (Link, error) {
tx := TXFromCtx(ctx, c.db.GetGroup())
if tx != nil {
return &txLink{tx.tx}, nil
}
if master {
link, err := c.db.GetCore().MasterLink(schema)
if err != nil {
return nil, err
}
return link, nil
}
link, err := c.db.GetCore().SlaveLink(schema)
if err != nil {
return nil, err
}
return link, nil
}
// MasterLink acts like function Master but with additional `schema` parameter specifying
// the schema for the connection. It is defined for internal usage.
// Also see Master.
@ -22,7 +70,10 @@ func (c *Core) MasterLink(schema ...string) (Link, error) {
if err != nil {
return nil, err
}
return &dbLink{db}, nil
return &dbLink{
DB: db,
isOnMaster: true,
}, nil
}
// SlaveLink acts like function Slave but with additional `schema` parameter specifying
@ -33,7 +84,10 @@ func (c *Core) SlaveLink(schema ...string) (Link, error) {
if err != nil {
return nil, err
}
return &dbLink{db}, nil
return &dbLink{
DB: db,
isOnMaster: false,
}, nil
}
// QuoteWord checks given string `s` a word,
@ -96,11 +150,12 @@ func (c *Core) Tables(schema ...string) (tables []string, err error) {
//
// It does nothing in default.
func (c *Core) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
var ctx = c.db.GetCtx()
// It does nothing if given table is empty, especially in sub-query.
if table == "" {
return map[string]*TableField{}, nil
}
return c.db.TableFields(c.GetCtx(), table, schema...)
return c.db.TableFields(ctx, table, schema...)
}
// HasField determine whether the field exists in the table.

View File

@ -38,6 +38,7 @@ func (d *DriverMysql) New(core *Core, node *ConfigNode) (DB, error) {
// Note that it converts time.Time argument to local timezone in default.
func (d *DriverMysql) Open(config *ConfigNode) (db *sql.DB, err error) {
var (
ctx = d.GetCtx()
source string
underlyingDriverName = "mysql"
)
@ -56,7 +57,7 @@ func (d *DriverMysql) Open(config *ConfigNode) (db *sql.DB, err error) {
source = fmt.Sprintf("%s&loc=%s", source, url.QueryEscape(config.Timezone))
}
}
intlog.Printf(d.GetCtx(), "Open: %s", source)
intlog.Printf(ctx, "Open: %s", source)
if db, err = sql.Open(underlyingDriverName, source); err != nil {
err = gerror.WrapCodef(
gcode.CodeDbOperationError, err,
@ -100,7 +101,7 @@ func (d *DriverMysql) Tables(ctx context.Context, schema ...string) (tables []st
if err != nil {
return nil, err
}
result, err = d.DoGetAll(ctx, link, `SHOW TABLES`)
result, err = d.DoSelect(ctx, link, `SHOW TABLES`)
if err != nil {
return
}
@ -147,7 +148,7 @@ func (d *DriverMysql) TableFields(ctx context.Context, table string, schema ...s
if link, err = d.SlaveLink(useSchema); err != nil {
return nil
}
result, err = d.DoGetAll(
result, err = d.DoSelect(
ctx, link,
fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.QuoteWord(table)),
)

View File

@ -8,6 +8,7 @@ package gdb
import (
"bytes"
"context"
"fmt"
"reflect"
"regexp"
@ -15,6 +16,7 @@ import (
"time"
"github.com/gogf/gf/v2/internal/empty"
"github.com/gogf/gf/v2/internal/reflection"
"github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/os/gstructs"
"github.com/gogf/gf/v2/os/gtime"
@ -179,12 +181,12 @@ func DataToMapDeep(value interface{}) map[string]interface{} {
return m
}
// doHandleTableName adds prefix string and quote chars for the table. It handles table string like:
// doHandleTableName adds prefix string and quote chars for table name. It handles table string like:
// "user", "user u", "user,user_detail", "user u, user_detail ut", "user as u, user_detail as ut",
// "user.user u", "`user`.`user` u".
//
// Note that, this will automatically checks the table prefix whether already added, if true it does
// nothing to the table name, or else adds the prefix to the table name.
// Note that, this will automatically check the table prefix whether already added, if true it does
// nothing to the table name, or else adds the prefix to the table name and returns new table name with prefix.
func doHandleTableName(table, prefix, charLeft, charRight string) string {
var (
index = 0
@ -363,10 +365,10 @@ func isKeyValueCanBeOmitEmpty(omitEmpty bool, whereType string, key, value inter
}
// formatWhereHolder formats where statement and its arguments for `Where` and `Having` statements.
func formatWhereHolder(db DB, in formatWhereHolderInput) (newWhere string, newArgs []interface{}) {
func formatWhereHolder(ctx context.Context, db DB, in formatWhereHolderInput) (newWhere string, newArgs []interface{}) {
var (
buffer = bytes.NewBuffer(nil)
reflectInfo = utils.OriginValueAndKind(in.Where)
reflectInfo = reflection.OriginValueAndKind(in.Where)
)
switch reflectInfo.OriginKind {
case reflect.Array, reflect.Slice:
@ -392,7 +394,7 @@ func formatWhereHolder(db DB, in formatWhereHolderInput) (newWhere string, newAr
}
case reflect.Struct:
// If the `where` parameter is DO struct, it then adds `OmitNil` option for this condition,
// If the `where` parameter is `DO` struct, it then adds `OmitNil` option for this condition,
// which will filter all nil parameters in `where`.
if isDoStruct(in.Where) {
in.OmitNil = true
@ -522,7 +524,9 @@ func formatWhereHolder(db DB, in formatWhereHolderInput) (newWhere string, newAr
whereStr, _ = gregex.ReplaceStringFunc(`(\?)`, whereStr, func(s string) string {
index++
if i+len(newArgs) == index {
sqlWithHolder, holderArgs := model.getFormattedSqlAndArgs(queryTypeNormal, false)
sqlWithHolder, holderArgs := model.getFormattedSqlAndArgs(
ctx, queryTypeNormal, false,
)
newArgs = append(newArgs, holderArgs...)
// Automatically adding the brackets.
return "(" + sqlWithHolder + ")"
@ -707,7 +711,7 @@ func handleArguments(sql string, args []interface{}) (newSql string, newArgs []i
// Handles the slice arguments.
if len(args) > 0 {
for index, arg := range args {
reflectInfo := utils.OriginValueAndKind(arg)
reflectInfo := reflection.OriginValueAndKind(arg)
switch reflectInfo.OriginKind {
case reflect.Slice, reflect.Array:
// It does not split the type of []byte.
@ -817,7 +821,7 @@ func FormatSqlWithArgs(sql string, args []interface{}) string {
if v, ok := args[index].(Raw); ok {
return gconv.String(v)
}
reflectInfo := utils.OriginValueAndKind(args[index])
reflectInfo := reflection.OriginValueAndKind(args[index])
if reflectInfo.OriginKind == reflect.Ptr &&
(reflectInfo.OriginValue.IsNil() || !reflectInfo.OriginValue.IsValid()) {
return "null"

View File

@ -17,37 +17,39 @@ import (
// Model is core struct implementing the DAO for ORM.
type Model struct {
db DB // Underlying DB interface.
tx *TX // Underlying TX interface.
rawSql string // rawSql is the raw SQL string which marks a raw SQL based Model not a table based Model.
schema string // Custom database schema.
linkType int // Mark for operation on master or slave.
tablesInit string // Table names when model initialization.
tables string // Operation table names, which can be more than one table names and aliases, like: "user", "user u", "user u, user_detail ud".
fields string // Operation fields, multiple fields joined using char ','.
fieldsEx string // Excluded operation fields, multiple fields joined using char ','.
withArray []interface{} // Arguments for With feature.
withAll bool // Enable model association operations on all objects that have "with" tag in the struct.
extraArgs []interface{} // Extra custom arguments for sql, which are prepended to the arguments before sql committed to underlying driver.
whereHolder []ModelWhereHolder // Condition strings for where operation.
groupBy string // Used for "group by" statement.
orderBy string // Used for "order by" statement.
having []interface{} // Used for "having..." statement.
start int // Used for "select ... start, limit ..." statement.
limit int // Used for "select ... start, limit ..." statement.
option int // Option for extra operation features.
offset int // Offset statement for some databases grammar.
data interface{} // Data for operation, which can be type of map/[]map/struct/*struct/string, etc.
batch int // Batch number for batch Insert/Replace/Save operations.
filter bool // Filter data and where key-value pairs according to the fields of the table.
distinct string // Force the query to only return distinct results.
lockInfo string // Lock for update or in shared lock.
cacheEnabled bool // Enable sql result cache feature, which is mainly for indicating cache duration(especially 0) usage.
cacheOption CacheOption // Cache option for query statement.
unscoped bool // Disables soft deleting features when select/delete operations.
safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model.
onDuplicate interface{} // onDuplicate is used for ON "DUPLICATE KEY UPDATE" statement.
onDuplicateEx interface{} // onDuplicateEx is used for excluding some columns ON "DUPLICATE KEY UPDATE" statement.
db DB // Underlying DB interface.
tx *TX // Underlying TX interface.
rawSql string // rawSql is the raw SQL string which marks a raw SQL based Model not a table based Model.
schema string // Custom database schema.
linkType int // Mark for operation on master or slave.
tablesInit string // Table names when model initialization.
tables string // Operation table names, which can be more than one table names and aliases, like: "user", "user u", "user u, user_detail ud".
fields string // Operation fields, multiple fields joined using char ','.
fieldsEx string // Excluded operation fields, multiple fields joined using char ','.
withArray []interface{} // Arguments for With feature.
withAll bool // Enable model association operations on all objects that have "with" tag in the struct.
extraArgs []interface{} // Extra custom arguments for sql, which are prepended to the arguments before sql committed to underlying driver.
whereHolder []ModelWhereHolder // Condition strings for where operation.
groupBy string // Used for "group by" statement.
orderBy string // Used for "order by" statement.
having []interface{} // Used for "having..." statement.
start int // Used for "select ... start, limit ..." statement.
limit int // Used for "select ... start, limit ..." statement.
option int // Option for extra operation features.
offset int // Offset statement for some databases grammar.
data interface{} // Data for operation, which can be type of map/[]map/struct/*struct/string, etc.
batch int // Batch number for batch Insert/Replace/Save operations.
filter bool // Filter data and where key-value pairs according to the fields of the table.
distinct string // Force the query to only return distinct results.
lockInfo string // Lock for update or in shared lock.
cacheEnabled bool // Enable sql result cache feature, which is mainly for indicating cache duration(especially 0) usage.
cacheOption CacheOption // Cache option for query statement.
hookHandler HookHandler // Hook functions for model hook feature.
shardingHandler ShardingHandler // Custom sharding handler for sharding feature.
unscoped bool // Disables soft deleting features when select/delete operations.
safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model.
onDuplicate interface{} // onDuplicate is used for ON "DUPLICATE KEY UPDATE" statement.
onDuplicateEx interface{} // onDuplicateEx is used for excluding some columns ON "DUPLICATE KEY UPDATE" statement.
}
// ModelHandler is a function that handles given Model and returns a new Model that is custom modified.
@ -91,6 +93,7 @@ const (
// db.Model("? AS a, ? AS b", subQuery1, subQuery2)
func (c *Core) Model(tableNameQueryOrStruct ...interface{}) *Model {
var (
ctx = c.db.GetCtx()
tableStr string
tableName string
extraArgs []interface{}
@ -103,7 +106,7 @@ func (c *Core) Model(tableNameQueryOrStruct ...interface{}) *Model {
Where: conditionStr,
Args: tableNameQueryOrStruct[1:],
}
tableStr, extraArgs = formatWhereHolder(c.db, formatWhereHolderInput{
tableStr, extraArgs = formatWhereHolder(ctx, c.db, formatWhereHolderInput{
ModelWhereHolder: whereHolder,
OmitNil: false,
OmitEmpty: false,
@ -264,6 +267,7 @@ func (m *Model) Clone() *Model {
} else {
newModel = m.db.Model(m.tablesInit)
}
// Basic attributes copy.
*newModel = *m
// Shallow copy slice attributes.
if n := len(m.extraArgs); n > 0 {

View File

@ -7,9 +7,14 @@
package gdb
import (
"context"
"fmt"
"time"
"github.com/gogf/gf/v2/crypto/gmd5"
"github.com/gogf/gf/v2/internal/intlog"
"github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/util/gconv"
)
type CacheOption struct {
@ -29,6 +34,12 @@ type CacheOption struct {
Force bool
}
// selectCacheItem is the cache item for SELECT statement result.
type selectCacheItem struct {
Result Result // Sql result of SELECT statement.
FirstResultColumn string // The first column name of result, for Value/Count functions.
}
// Cache sets the cache feature for the model. It caches the result of the sql, which means
// if there's another same sql request, it just reads and returns the result from cache, it
// but not committed and executed into the database.
@ -42,14 +53,85 @@ func (m *Model) Cache(option CacheOption) *Model {
return model
}
// checkAndRemoveCache checks and removes the cache in insert/update/delete statement if
// checkAndRemoveSelectCache checks and removes the cache in insert/update/delete statement if
// cache feature is enabled.
func (m *Model) checkAndRemoveCache() {
func (m *Model) checkAndRemoveSelectCache(ctx context.Context) {
if m.cacheEnabled && m.cacheOption.Duration < 0 && len(m.cacheOption.Name) > 0 {
ctx := m.GetCtx()
_, err := m.db.GetCache().Remove(ctx, m.cacheOption.Name)
if err != nil {
if _, err := m.db.GetCache().Remove(ctx, m.cacheOption.Name); err != nil {
intlog.Errorf(ctx, `%+v`, err)
}
}
}
func (m *Model) getSelectResultFromCache(ctx context.Context, sql string, args ...interface{}) (result Result, err error) {
if !m.cacheEnabled || m.tx != nil {
return
}
var (
ok bool
cacheItem *selectCacheItem
cacheKey = m.makeSelectCacheKey(sql, args...)
cacheObj = m.db.GetCache()
)
defer func() {
if cacheItem != nil {
if internalData := m.db.GetCore().getInternalCtxDataFromCtx(ctx); internalData != nil {
if internalData.FirstResultColumn == "" {
internalData.FirstResultColumn = cacheItem.FirstResultColumn
}
}
}
}()
if v, _ := cacheObj.Get(ctx, cacheKey); !v.IsNil() {
if cacheItem, ok = v.Val().(*selectCacheItem); ok {
// In-memory cache.
return cacheItem.Result, nil
} else if err = json.UnmarshalUseNumber(v.Bytes(), &cacheItem); err != nil {
// Other cache, it needs conversion.
return nil, err
}
}
return
}
func (m *Model) saveSelectResultToCache(ctx context.Context, result Result, sql string, args ...interface{}) (err error) {
if !m.cacheEnabled || m.tx != nil {
return
}
var (
cacheKey = m.makeSelectCacheKey(sql, args...)
cacheObj = m.db.GetCache()
)
if m.cacheOption.Duration < 0 {
if _, errCache := cacheObj.Remove(ctx, cacheKey); errCache != nil {
intlog.Errorf(ctx, `%+v`, errCache)
}
} else {
// In case of Cache Penetration.
if result.IsEmpty() && m.cacheOption.Force {
result = Result{}
}
var cacheItem = &selectCacheItem{
Result: result,
}
if internalData := m.db.GetCore().getInternalCtxDataFromCtx(ctx); internalData != nil {
cacheItem.FirstResultColumn = internalData.FirstResultColumn
}
if errCache := cacheObj.Set(ctx, cacheKey, cacheItem, m.cacheOption.Duration); errCache != nil {
intlog.Errorf(ctx, `%+v`, errCache)
}
}
return nil
}
func (m *Model) makeSelectCacheKey(sql string, args ...interface{}) string {
var cacheKey = m.cacheOption.Name
if len(cacheKey) == 0 {
cacheKey = fmt.Sprintf(
`GCache@Schema(%s):%s`,
m.db.GetSchema(),
gmd5.MustEncryptString(sql+", @PARAMS:"+gconv.String(args)),
)
}
return cacheKey
}

View File

@ -20,32 +20,55 @@ import (
// The optional parameter `where` is the same as the parameter of Model.Where function,
// see Model.Where.
func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
var ctx = m.GetCtx()
if len(where) > 0 {
return m.Where(where[0], where[1:]...).Delete()
}
defer func() {
if err == nil {
m.checkAndRemoveCache()
m.checkAndRemoveSelectCache(ctx)
}
}()
var (
fieldNameDelete = m.getSoftFieldNameDeleted()
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false, false)
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(ctx, false, false)
)
// Soft deleting.
if !m.unscoped && fieldNameDelete != "" {
return m.db.DoUpdate(
m.GetCtx(),
m.getLink(true),
m.tables,
fmt.Sprintf(`%s=?`, m.db.GetCore().QuoteString(fieldNameDelete)),
conditionWhere+conditionExtra,
append([]interface{}{gtime.Now().String()}, conditionArgs...),
)
in := &HookUpdateInput{
internalParamHookUpdate: internalParamHookUpdate{
internalParamHook: internalParamHook{
link: m.getLink(true),
model: m,
},
handler: m.hookHandler.Update,
},
Table: m.tables,
Data: fmt.Sprintf(`%s=?`, m.db.GetCore().QuoteString(fieldNameDelete)),
Condition: conditionWhere + conditionExtra,
Args: append([]interface{}{gtime.Now().String()}, conditionArgs...),
}
return in.Next(ctx)
}
conditionStr := conditionWhere + conditionExtra
if !gstr.ContainsI(conditionStr, " WHERE ") {
return nil, gerror.NewCode(gcode.CodeMissingParameter, "there should be WHERE condition statement for DELETE operation")
return nil, gerror.NewCode(
gcode.CodeMissingParameter,
"there should be WHERE condition statement for DELETE operation",
)
}
return m.db.DoDelete(m.GetCtx(), m.getLink(true), m.tables, conditionStr, conditionArgs...)
in := &HookDeleteInput{
internalParamHookDelete: internalParamHookDelete{
internalParamHook: internalParamHook{
link: m.getLink(true),
model: m,
},
handler: m.hookHandler.Delete,
},
Table: m.tables,
Condition: conditionStr,
Args: conditionArgs,
}
return in.Next(ctx)
}

View File

@ -0,0 +1,159 @@
// 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 gdb
import (
"context"
"database/sql"
"github.com/gogf/gf/v2/text/gstr"
)
type (
HookFuncSelect func(ctx context.Context, in *HookSelectInput) (result Result, err error)
HookFuncInsert func(ctx context.Context, in *HookInsertInput) (result sql.Result, err error)
HookFuncUpdate func(ctx context.Context, in *HookUpdateInput) (result sql.Result, err error)
HookFuncDelete func(ctx context.Context, in *HookDeleteInput) (result sql.Result, err error)
)
// HookHandler manages all supported hook functions for Model.
type HookHandler struct {
Select HookFuncSelect
Insert HookFuncInsert
Update HookFuncUpdate
Delete HookFuncDelete
}
// internalParamHook manages all internal parameters for hook operations.
// The `internal` obviously means you cannot access these parameters outside this package.
type internalParamHook struct {
link Link // Connection object from third party sql driver.
model *Model // Underlying Model object.
handlerCalled bool // Simple mark for custom handler called, in case of recursive calling.
removedWhere bool // Removed mark for condition string that was removed `WHERE` prefix.
}
type internalParamHookSelect struct {
internalParamHook
handler HookFuncSelect
}
type internalParamHookInsert struct {
internalParamHook
handler HookFuncInsert
}
type internalParamHookUpdate struct {
internalParamHook
handler HookFuncUpdate
}
type internalParamHookDelete struct {
internalParamHook
handler HookFuncDelete
}
// HookSelectInput holds the parameters for select hook operation.
// Note that, COUNT statement will also be hooked by this feature,
// which is usually not be interesting for upper business hook handler.
type HookSelectInput struct {
internalParamHookSelect
Table string
Sql string
Args []interface{}
}
// HookInsertInput holds the parameters for insert hook operation.
type HookInsertInput struct {
internalParamHookInsert
Table string
Data List
Option DoInsertOption
}
// HookUpdateInput holds the parameters for update hook operation.
type HookUpdateInput struct {
internalParamHookUpdate
Table string
Data interface{} // Data can be type of: map[string]interface{}/string. You can use type assertion on `Data`.
Condition string
Args []interface{}
}
// HookDeleteInput holds the parameters for delete hook operation.
type HookDeleteInput struct {
internalParamHookDelete
Table string
Condition string
Args []interface{}
}
const (
whereKeyInCondition = " WHERE "
)
// IsTransaction checks and returns whether current operation is during transaction.
func (h *internalParamHook) IsTransaction() bool {
return h.link.IsTransaction()
}
// Next calls the next hook handler.
func (h *HookSelectInput) Next(ctx context.Context) (result Result, err error) {
if h.handler != nil && !h.handlerCalled {
h.handlerCalled = true
return h.handler(ctx, h)
}
return h.model.db.DoSelect(ctx, h.link, h.Sql, h.Args...)
}
// Next calls the next hook handler.
func (h *HookInsertInput) Next(ctx context.Context) (result sql.Result, err error) {
if h.handler != nil && !h.handlerCalled {
h.handlerCalled = true
return h.handler(ctx, h)
}
return h.model.db.DoInsert(ctx, h.link, h.Table, h.Data, h.Option)
}
// Next calls the next hook handler.
func (h *HookUpdateInput) Next(ctx context.Context) (result sql.Result, err error) {
if h.handler != nil && !h.handlerCalled {
h.handlerCalled = true
if gstr.HasPrefix(h.Condition, whereKeyInCondition) {
h.removedWhere = true
h.Condition = gstr.TrimLeftStr(h.Condition, whereKeyInCondition)
}
return h.handler(ctx, h)
}
if h.removedWhere {
h.Condition = whereKeyInCondition + h.Condition
}
return h.model.db.DoUpdate(ctx, h.link, h.Table, h.Data, h.Condition, h.Args...)
}
// Next calls the next hook handler.
func (h *HookDeleteInput) Next(ctx context.Context) (result sql.Result, err error) {
if h.handler != nil && !h.handlerCalled {
h.handlerCalled = true
if gstr.HasPrefix(h.Condition, whereKeyInCondition) {
h.removedWhere = true
h.Condition = gstr.TrimLeftStr(h.Condition, whereKeyInCondition)
}
return h.handler(ctx, h)
}
if h.removedWhere {
h.Condition = whereKeyInCondition + h.Condition
}
return h.model.db.DoDelete(ctx, h.link, h.Table, h.Condition, h.Args...)
}
// Hook sets the hook functions for current model.
func (m *Model) Hook(hook HookHandler) *Model {
model := m.getModel()
model.hookHandler = hook
return model
}

View File

@ -7,13 +7,14 @@
package gdb
import (
"context"
"database/sql"
"reflect"
"github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/internal/reflection"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
@ -38,7 +39,10 @@ func (m *Model) Batch(batch int) *Model {
// Data(g.Map{"uid": 10000, "name":"john"})
// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}).
func (m *Model) Data(data ...interface{}) *Model {
model := m.getModel()
var (
ctx = m.GetCtx()
model = m.getModel()
)
if len(data) > 1 {
if s := gconv.String(data[0]); gstr.Contains(s, "?") {
model.data = s
@ -69,7 +73,7 @@ func (m *Model) Data(data ...interface{}) *Model {
model.data = gutil.MapCopy(value)
default:
reflectInfo := utils.OriginValueAndKind(value)
reflectInfo := reflection.OriginValueAndKind(value)
switch reflectInfo.OriginKind {
case reflect.Slice, reflect.Array:
if reflectInfo.OriginValue.Len() > 0 {
@ -83,7 +87,7 @@ func (m *Model) Data(data ...interface{}) *Model {
}
list := make(List, reflectInfo.OriginValue.Len())
for i := 0; i < reflectInfo.OriginValue.Len(); i++ {
list[i] = m.db.ConvertDataForRecord(m.GetCtx(), reflectInfo.OriginValue.Index(i).Interface())
list[i] = m.db.ConvertDataForRecord(ctx, reflectInfo.OriginValue.Index(i).Interface())
}
model.data = list
@ -100,15 +104,15 @@ func (m *Model) Data(data ...interface{}) *Model {
list = make(List, len(array))
)
for i := 0; i < len(array); i++ {
list[i] = m.db.ConvertDataForRecord(m.GetCtx(), array[i])
list[i] = m.db.ConvertDataForRecord(ctx, array[i])
}
model.data = list
} else {
model.data = m.db.ConvertDataForRecord(m.GetCtx(), data[0])
model.data = m.db.ConvertDataForRecord(ctx, data[0])
}
case reflect.Map:
model.data = m.db.ConvertDataForRecord(m.GetCtx(), data[0])
model.data = m.db.ConvertDataForRecord(ctx, data[0])
default:
model.data = data[0]
@ -140,7 +144,7 @@ func (m *Model) OnDuplicate(onDuplicate ...interface{}) *Model {
return model
}
// OnDuplicateEx sets the excluding columns for operations when columns conflicts occurs.
// OnDuplicateEx sets the excluding columns for operations when columns conflict occurs.
// In MySQL, this is used for "ON DUPLICATE KEY UPDATE" statement.
// The parameter `onDuplicateEx` can be type of string/map/slice.
// Example:
@ -164,18 +168,20 @@ func (m *Model) OnDuplicateEx(onDuplicateEx ...interface{}) *Model {
// The optional parameter `data` is the same as the parameter of Model.Data function,
// see Model.Data.
func (m *Model) Insert(data ...interface{}) (result sql.Result, err error) {
var ctx = m.GetCtx()
if len(data) > 0 {
return m.Data(data...).Insert()
}
return m.doInsertWithOption(InsertOptionDefault)
return m.doInsertWithOption(ctx, InsertOptionDefault)
}
// InsertAndGetId performs action Insert and returns the last insert id that automatically generated.
func (m *Model) InsertAndGetId(data ...interface{}) (lastInsertId int64, err error) {
var ctx = m.GetCtx()
if len(data) > 0 {
return m.Data(data...).InsertAndGetId()
}
result, err := m.doInsertWithOption(InsertOptionDefault)
result, err := m.doInsertWithOption(ctx, InsertOptionDefault)
if err != nil {
return 0, err
}
@ -186,20 +192,22 @@ func (m *Model) InsertAndGetId(data ...interface{}) (lastInsertId int64, err err
// The optional parameter `data` is the same as the parameter of Model.Data function,
// see Model.Data.
func (m *Model) InsertIgnore(data ...interface{}) (result sql.Result, err error) {
var ctx = m.GetCtx()
if len(data) > 0 {
return m.Data(data...).InsertIgnore()
}
return m.doInsertWithOption(InsertOptionIgnore)
return m.doInsertWithOption(ctx, InsertOptionIgnore)
}
// Replace does "REPLACE INTO ..." statement for the model.
// The optional parameter `data` is the same as the parameter of Model.Data function,
// see Model.Data.
func (m *Model) Replace(data ...interface{}) (result sql.Result, err error) {
var ctx = m.GetCtx()
if len(data) > 0 {
return m.Data(data...).Replace()
}
return m.doInsertWithOption(InsertOptionReplace)
return m.doInsertWithOption(ctx, InsertOptionReplace)
}
// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the model.
@ -209,17 +217,18 @@ func (m *Model) Replace(data ...interface{}) (result sql.Result, err error) {
// It updates the record if there's primary or unique index in the saving data,
// or else it inserts a new record into the table.
func (m *Model) Save(data ...interface{}) (result sql.Result, err error) {
var ctx = m.GetCtx()
if len(data) > 0 {
return m.Data(data...).Save()
}
return m.doInsertWithOption(InsertOptionSave)
return m.doInsertWithOption(ctx, InsertOptionSave)
}
// doInsertWithOption inserts data with option parameter.
func (m *Model) doInsertWithOption(insertOption int) (result sql.Result, err error) {
func (m *Model) doInsertWithOption(ctx context.Context, insertOption int) (result sql.Result, err error) {
defer func() {
if err == nil {
m.checkAndRemoveCache()
m.checkAndRemoveSelectCache(ctx)
}
}()
if m.data == nil {
@ -246,34 +255,34 @@ func (m *Model) doInsertWithOption(insertOption int) (result sql.Result, err err
case List:
list = value
for i, v := range list {
list[i] = m.db.ConvertDataForRecord(m.GetCtx(), v)
list[i] = m.db.ConvertDataForRecord(ctx, v)
}
case Map:
list = List{m.db.ConvertDataForRecord(m.GetCtx(), value)}
list = List{m.db.ConvertDataForRecord(ctx, value)}
default:
reflectInfo := utils.OriginValueAndKind(newData)
reflectInfo := reflection.OriginValueAndKind(newData)
switch reflectInfo.OriginKind {
// If it's slice type, it then converts it to List type.
case reflect.Slice, reflect.Array:
list = make(List, reflectInfo.OriginValue.Len())
for i := 0; i < reflectInfo.OriginValue.Len(); i++ {
list[i] = m.db.ConvertDataForRecord(m.GetCtx(), reflectInfo.OriginValue.Index(i).Interface())
list[i] = m.db.ConvertDataForRecord(ctx, reflectInfo.OriginValue.Index(i).Interface())
}
case reflect.Map:
list = List{m.db.ConvertDataForRecord(m.GetCtx(), value)}
list = List{m.db.ConvertDataForRecord(ctx, value)}
case reflect.Struct:
if v, ok := value.(iInterfaces); ok {
array := v.Interfaces()
list = make(List, len(array))
for i := 0; i < len(array); i++ {
list[i] = m.db.ConvertDataForRecord(m.GetCtx(), array[i])
list[i] = m.db.ConvertDataForRecord(ctx, array[i])
}
} else {
list = List{m.db.ConvertDataForRecord(m.GetCtx(), value)}
list = List{m.db.ConvertDataForRecord(ctx, value)}
}
default:
@ -310,7 +319,20 @@ func (m *Model) doInsertWithOption(insertOption int) (result sql.Result, err err
if err != nil {
return result, err
}
return m.db.DoInsert(m.GetCtx(), m.getLink(true), m.tables, list, doInsertOption)
in := &HookInsertInput{
internalParamHookInsert: internalParamHookInsert{
internalParamHook: internalParamHook{
link: m.getLink(true),
model: m,
},
handler: m.hookHandler.Insert,
},
Table: m.tables,
Data: list,
Option: doInsertOption,
}
return in.Next(ctx)
}
func (m *Model) formatDoInsertOption(insertOption int, columnNames []string) (option DoInsertOption, err error) {
@ -330,7 +352,7 @@ func (m *Model) formatDoInsertOption(insertOption int, columnNames []string) (op
option.OnDuplicateStr = gconv.String(m.onDuplicate)
default:
reflectInfo := utils.OriginValueAndKind(m.onDuplicate)
reflectInfo := reflection.OriginValueAndKind(m.onDuplicate)
switch reflectInfo.OriginKind {
case reflect.String:
option.OnDuplicateMap = make(map[string]interface{})
@ -385,7 +407,7 @@ func (m *Model) formatOnDuplicateExKeys(onDuplicateEx interface{}) ([]string, er
return nil, nil
}
reflectInfo := utils.OriginValueAndKind(onDuplicateEx)
reflectInfo := reflection.OriginValueAndKind(onDuplicateEx)
switch reflectInfo.OriginKind {
case reflect.String:
return gstr.SplitAndTrim(reflectInfo.OriginValue.String(), ","), nil

View File

@ -6,15 +6,22 @@
package gdb
import "strings"
import (
"strings"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)
// Order sets the "ORDER BY" statement for the model.
//
// Eg:
// Order("id desc")
// Order("id", "desc")
// Order("id desc,name asc").
func (m *Model) Order(orderBy ...string) *Model {
// Order("id", "desc").
// Order("id desc,name asc")
// Order("id desc").Order("name asc")
// Order(gdb.Raw("field(id, 3,1,2)")).
func (m *Model) Order(orderBy ...interface{}) *Model {
if len(orderBy) == 0 {
return m
}
@ -22,7 +29,14 @@ func (m *Model) Order(orderBy ...string) *Model {
if model.orderBy != "" {
model.orderBy += ","
}
model.orderBy = model.db.GetCore().QuoteString(strings.Join(orderBy, " "))
for _, v := range orderBy {
switch v.(type) {
case Raw, *Raw:
model.orderBy += gconv.String(v)
return model
}
}
model.orderBy += model.db.GetCore().QuoteString(gstr.JoinAny(orderBy, " "))
return model
}

View File

@ -7,17 +7,15 @@
package gdb
import (
"context"
"fmt"
"reflect"
"github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/container/gvar"
"github.com/gogf/gf/v2/crypto/gmd5"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/intlog"
"github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/internal/reflection"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)
@ -29,7 +27,8 @@ import (
// The optional parameter `where` is the same as the parameter of Model.Where function,
// see Model.Where.
func (m *Model) All(where ...interface{}) (Result, error) {
return m.doGetAll(false, where...)
var ctx = m.GetCtx()
return m.doGetAll(ctx, false, where...)
}
// doGetAll does "SELECT FROM ..." statement for the model.
@ -39,12 +38,12 @@ func (m *Model) All(where ...interface{}) (Result, error) {
// The parameter `limit1` specifies whether limits querying only one record if m.limit is not set.
// The optional parameter `where` is the same as the parameter of Model.Where function,
// see Model.Where.
func (m *Model) doGetAll(limit1 bool, where ...interface{}) (Result, error) {
func (m *Model) doGetAll(ctx context.Context, limit1 bool, where ...interface{}) (Result, error) {
if len(where) > 0 {
return m.Where(where[0], where[1:]...).All()
}
sqlWithHolder, holderArgs := m.getFormattedSqlAndArgs(queryTypeNormal, limit1)
return m.doGetAllBySql(sqlWithHolder, holderArgs...)
sqlWithHolder, holderArgs := m.getFormattedSqlAndArgs(ctx, queryTypeNormal, limit1)
return m.doGetAllBySql(ctx, queryTypeNormal, sqlWithHolder, holderArgs...)
}
// getFieldsFiltered checks the fields and fieldsEx attributes, filters and returns the fields that will
@ -130,10 +129,11 @@ func (m *Model) Chunk(size int, handler ChunkHandler) {
// The optional parameter `where` is the same as the parameter of Model.Where function,
// see Model.Where.
func (m *Model) One(where ...interface{}) (Record, error) {
var ctx = m.GetCtx()
if len(where) > 0 {
return m.Where(where[0], where[1:]...).One()
}
all, err := m.doGetAll(true)
all, err := m.doGetAll(ctx, true)
if err != nil {
return nil, err
}
@ -150,6 +150,7 @@ func (m *Model) One(where ...interface{}) (Record, error) {
// and fieldsAndWhere[1:] is treated as where condition fields.
// Also see Model.Fields and Model.Where functions.
func (m *Model) Value(fieldsAndWhere ...interface{}) (Value, error) {
var ctx = m.GetCtx()
if len(fieldsAndWhere) > 0 {
if len(fieldsAndWhere) > 2 {
return m.Fields(gconv.String(fieldsAndWhere[0])).Where(fieldsAndWhere[1], fieldsAndWhere[2:]...).Value()
@ -159,14 +160,26 @@ func (m *Model) Value(fieldsAndWhere ...interface{}) (Value, error) {
return m.Fields(gconv.String(fieldsAndWhere[0])).Value()
}
}
one, err := m.One()
if err != nil {
return gvar.New(nil), err
var (
all Result
err error
)
if all, err = m.doGetAll(ctx, true); err != nil {
return nil, err
}
for _, v := range one {
return v, nil
if len(all) == 0 {
return gvar.New(nil), nil
}
return gvar.New(nil), nil
if internalData := m.db.GetCore().getInternalCtxDataFromCtx(ctx); internalData != nil {
record := all[0]
if v, ok := record[internalData.FirstResultColumn]; ok {
return v, nil
}
}
return nil, gerror.NewCode(
gcode.CodeInternalError,
`query value error: the internal context data is missing. there's' internal issue should be fixed'`,
)
}
// Array queries and returns data values as slice from database.
@ -294,7 +307,7 @@ func (m *Model) doStructs(pointer interface{}, where ...interface{}) error {
// users := ([]*User)(nil)
// err := db.Model("user").Scan(&users).
func (m *Model) Scan(pointer interface{}, where ...interface{}) error {
reflectInfo := utils.OriginTypeAndKind(pointer)
reflectInfo := reflection.OriginTypeAndKind(pointer)
if reflectInfo.InputKind != reflect.Ptr {
return gerror.NewCode(
gcode.CodeInvalidParameter,
@ -362,20 +375,28 @@ func (m *Model) ScanList(structSlicePointer interface{}, bindToAttrName string,
// The optional parameter `where` is the same as the parameter of Model.Where function,
// see Model.Where.
func (m *Model) Count(where ...interface{}) (int, error) {
var ctx = m.GetCtx()
if len(where) > 0 {
return m.Where(where[0], where[1:]...).Count()
}
var (
sqlWithHolder, holderArgs = m.getFormattedSqlAndArgs(queryTypeCount, false)
list, err = m.doGetAllBySql(sqlWithHolder, holderArgs...)
sqlWithHolder, holderArgs = m.getFormattedSqlAndArgs(ctx, queryTypeCount, false)
all, err = m.doGetAllBySql(ctx, queryTypeCount, sqlWithHolder, holderArgs...)
)
if err != nil {
return 0, err
}
if len(list) > 0 {
for _, v := range list[0] {
return v.Int(), nil
if len(all) > 0 {
if internalData := m.db.GetCore().getInternalCtxDataFromCtx(ctx); internalData != nil {
record := all[0]
if v, ok := record[internalData.FirstResultColumn]; ok {
return v.Int(), nil
}
}
return 0, gerror.NewCode(
gcode.CodeInternalError,
`query count error: the internal context data is missing. there's' internal issue should be fixed'`,
)
}
return 0, nil
}
@ -502,81 +523,54 @@ func (m *Model) Having(having interface{}, args ...interface{}) *Model {
}
// doGetAllBySql does the select statement on the database.
func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, err error) {
var (
ok bool
ctx = m.GetCtx()
cacheKey = ""
cacheObj = m.db.GetCache()
)
// Retrieve from cache.
if m.cacheEnabled && m.tx == nil {
cacheKey = m.cacheOption.Name
if len(cacheKey) == 0 {
cacheKey = fmt.Sprintf(
`GCache@Schema(%s):%s`,
m.db.GetSchema(),
gmd5.MustEncryptString(sql+", @PARAMS:"+gconv.String(args)),
)
}
if v, _ := cacheObj.Get(ctx, cacheKey); !v.IsNil() {
if result, ok = v.Val().(Result); ok {
// In-memory cache.
return result, nil
}
// Other cache, it needs conversion.
if err = json.UnmarshalUseNumber(v.Bytes(), &result); err != nil {
return nil, err
} else {
return result, nil
}
}
func (m *Model) doGetAllBySql(ctx context.Context, queryType int, sql string, args ...interface{}) (result Result, err error) {
if result, err = m.getSelectResultFromCache(ctx, sql, args...); err != nil || result != nil {
return
}
result, err = m.db.DoGetAll(
m.GetCtx(), m.getLink(false), sql, m.mergeArguments(args)...,
)
// Cache the result.
if cacheKey != "" && err == nil {
if m.cacheOption.Duration < 0 {
if _, err = cacheObj.Remove(ctx, cacheKey); err != nil {
intlog.Errorf(m.GetCtx(), `%+v`, err)
}
} else {
// In case of Cache Penetration.
if result.IsEmpty() && m.cacheOption.Force {
result = Result{}
}
if err = cacheObj.Set(ctx, cacheKey, result, m.cacheOption.Duration); err != nil {
intlog.Errorf(m.GetCtx(), `%+v`, err)
}
}
in := &HookSelectInput{
internalParamHookSelect: internalParamHookSelect{
internalParamHook: internalParamHook{
link: m.getLink(false),
model: m,
},
handler: m.hookHandler.Select,
},
Table: m.tables,
Sql: sql,
Args: m.mergeArguments(args),
}
return result, err
if result, err = in.Next(ctx); err != nil {
return
}
err = m.saveSelectResultToCache(ctx, result, sql, args...)
return
}
func (m *Model) getFormattedSqlAndArgs(queryType int, limit1 bool) (sqlWithHolder string, holderArgs []interface{}) {
func (m *Model) getFormattedSqlAndArgs(ctx context.Context, queryType int, limit1 bool) (sqlWithHolder string, holderArgs []interface{}) {
switch queryType {
case queryTypeCount:
countFields := "COUNT(1)"
queryFields := "COUNT(1)"
if m.fields != "" && m.fields != "*" {
// DO NOT quote the m.fields here, in case of fields like:
// DISTINCT t.user_id uid
countFields = fmt.Sprintf(`COUNT(%s%s)`, m.distinct, m.fields)
queryFields = fmt.Sprintf(`COUNT(%s%s)`, m.distinct, m.fields)
}
// Raw SQL Model.
if m.rawSql != "" {
sqlWithHolder = fmt.Sprintf("SELECT %s FROM (%s) AS T", countFields, m.rawSql)
sqlWithHolder = fmt.Sprintf("SELECT %s FROM (%s) AS T", queryFields, m.rawSql)
return sqlWithHolder, nil
}
conditionWhere, conditionExtra, conditionArgs := m.formatCondition(false, true)
sqlWithHolder = fmt.Sprintf("SELECT %s FROM %s%s", countFields, m.tables, conditionWhere+conditionExtra)
conditionWhere, conditionExtra, conditionArgs := m.formatCondition(ctx, false, true)
sqlWithHolder = fmt.Sprintf("SELECT %s FROM %s%s", queryFields, m.tables, conditionWhere+conditionExtra)
if len(m.groupBy) > 0 {
sqlWithHolder = fmt.Sprintf("SELECT COUNT(1) FROM (%s) count_alias", sqlWithHolder)
}
return sqlWithHolder, conditionArgs
default:
conditionWhere, conditionExtra, conditionArgs := m.formatCondition(limit1, false)
conditionWhere, conditionExtra, conditionArgs := m.formatCondition(ctx, limit1, false)
// Raw SQL Model, especially for UNION/UNION ALL featured SQL.
if m.rawSql != "" {
sqlWithHolder = fmt.Sprintf(
@ -590,10 +584,7 @@ func (m *Model) getFormattedSqlAndArgs(queryType int, limit1 bool) (sqlWithHolde
// DISTINCT t.user_id uid
sqlWithHolder = fmt.Sprintf(
"SELECT %s%s FROM %s%s",
m.distinct,
m.getFieldsFiltered(),
m.tables,
conditionWhere+conditionExtra,
m.distinct, m.getFieldsFiltered(), m.tables, conditionWhere+conditionExtra,
)
return sqlWithHolder, conditionArgs
}
@ -603,7 +594,7 @@ func (m *Model) getFormattedSqlAndArgs(queryType int, limit1 bool) (sqlWithHolde
// Note that this function does not change any attribute value of the `m`.
//
// The parameter `limit1` specifies whether limits querying only one record if m.limit is not set.
func (m *Model) formatCondition(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{}) {
autoPrefix := ""
if gstr.Contains(m.tables, " JOIN ") {
autoPrefix = m.db.GetCore().QuoteWord(
@ -623,7 +614,7 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
switch holder.Operator {
case whereHolderOperatorWhere:
if conditionWhere == "" {
newWhere, newArgs := formatWhereHolder(m.db, formatWhereHolderInput{
newWhere, newArgs := formatWhereHolder(ctx, m.db, formatWhereHolderInput{
ModelWhereHolder: holder,
OmitNil: m.option&optionOmitNilWhere > 0,
OmitEmpty: m.option&optionOmitEmptyWhere > 0,
@ -639,7 +630,7 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
fallthrough
case whereHolderOperatorAnd:
newWhere, newArgs := formatWhereHolder(m.db, formatWhereHolderInput{
newWhere, newArgs := formatWhereHolder(ctx, m.db, formatWhereHolderInput{
ModelWhereHolder: holder,
OmitNil: m.option&optionOmitNilWhere > 0,
OmitEmpty: m.option&optionOmitEmptyWhere > 0,
@ -658,7 +649,7 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
}
case whereHolderOperatorOr:
newWhere, newArgs := formatWhereHolder(m.db, formatWhereHolderInput{
newWhere, newArgs := formatWhereHolder(ctx, m.db, formatWhereHolderInput{
ModelWhereHolder: holder,
OmitNil: m.option&optionOmitNilWhere > 0,
OmitEmpty: m.option&optionOmitEmptyWhere > 0,
@ -709,7 +700,7 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
Args: gconv.Interfaces(m.having[1]),
Prefix: autoPrefix,
}
havingStr, havingArgs := formatWhereHolder(m.db, formatWhereHolderInput{
havingStr, havingArgs := formatWhereHolder(ctx, m.db, formatWhereHolderInput{
ModelWhereHolder: havingHolder,
OmitNil: m.option&optionOmitNilWhere > 0,
OmitEmpty: m.option&optionOmitEmptyWhere > 0,

View File

@ -13,7 +13,7 @@ import (
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/internal/reflection"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
@ -25,6 +25,7 @@ import (
// and dataAndWhere[1:] is treated as where condition fields.
// Also see Model.Data and Model.Where functions.
func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err error) {
var ctx = m.GetCtx()
if len(dataAndWhere) > 0 {
if len(dataAndWhere) > 2 {
return m.Data(dataAndWhere[0]).Where(dataAndWhere[1], dataAndWhere[2:]...).Update()
@ -36,7 +37,7 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
}
defer func() {
if err == nil {
m.checkAndRemoveCache()
m.checkAndRemoveSelectCache(ctx)
}
}()
if m.data == nil {
@ -44,27 +45,28 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
}
var (
updateData = m.data
reflectInfo = reflection.OriginTypeAndKind(updateData)
fieldNameUpdate = m.getSoftFieldNameUpdated()
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false, false)
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(ctx, false, false)
)
// Automatically update the record updating time.
if !m.unscoped && fieldNameUpdate != "" {
reflectInfo := utils.OriginTypeAndKind(m.data)
switch reflectInfo.OriginKind {
case reflect.Map, reflect.Struct:
dataMap := m.db.ConvertDataForRecord(m.GetCtx(), m.data)
if fieldNameUpdate != "" {
dataMap[fieldNameUpdate] = gtime.Now().String()
}
updateData = dataMap
switch reflectInfo.OriginKind {
case reflect.Map, reflect.Struct:
dataMap := m.db.ConvertDataForRecord(ctx, m.data)
// Automatically update the record updating time.
if !m.unscoped && fieldNameUpdate != "" {
dataMap[fieldNameUpdate] = gtime.Now().String()
}
updateData = dataMap
default:
updates := gconv.String(m.data)
default:
updates := gconv.String(m.data)
// Automatically update the record updating time.
if !m.unscoped && fieldNameUpdate != "" {
if fieldNameUpdate != "" && !gstr.Contains(updates, fieldNameUpdate) {
updates += fmt.Sprintf(`,%s='%s'`, fieldNameUpdate, gtime.Now().String())
}
updateData = updates
}
updateData = updates
}
newData, err := m.filterDataForInsertOrUpdate(updateData)
if err != nil {
@ -74,14 +76,21 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
if !gstr.ContainsI(conditionStr, " WHERE ") {
return nil, gerror.NewCode(gcode.CodeMissingParameter, "there should be WHERE condition statement for UPDATE operation")
}
return m.db.DoUpdate(
m.GetCtx(),
m.getLink(true),
m.tables,
newData,
conditionStr,
m.mergeArguments(conditionArgs)...,
)
in := &HookUpdateInput{
internalParamHookUpdate: internalParamHookUpdate{
internalParamHook: internalParamHook{
link: m.getLink(true),
model: m,
},
handler: m.hookHandler.Update,
},
Table: m.tables,
Data: newData,
Condition: conditionStr,
Args: m.mergeArguments(conditionArgs),
}
return in.Next(ctx)
}
// Increment increments a column's value by a given amount.

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