mirror of
https://gitee.com/johng/gf
synced 2026-06-10 19:31:44 +08:00
Compare commits
13 Commits
hinego-pat
...
contrib/dr
| Author | SHA1 | Date | |
|---|---|---|---|
| 607f079b23 | |||
| cab6b89446 | |||
| 6ed3038312 | |||
| 9b48da459e | |||
| fbd266fad0 | |||
| 240dadff92 | |||
| 290f4a3e65 | |||
| 97fcd9d726 | |||
| df15d70466 | |||
| 680ae8616b | |||
| 4feb8219fa | |||
| 509cc47d3f | |||
| 849b104c31 |
15
.github/workflows/ci-main.yml
vendored
15
.github/workflows/ci-main.yml
vendored
@ -66,6 +66,15 @@ jobs:
|
||||
ports:
|
||||
- 3306:3306
|
||||
|
||||
# MariaDb backend server.
|
||||
mariadb:
|
||||
image: loads/mariadb:10.4
|
||||
env:
|
||||
MARIADB_DATABASE: test
|
||||
MARIADB_ROOT_PASSWORD: 12345678
|
||||
ports:
|
||||
- 3307:3306
|
||||
|
||||
# PostgreSQL backend server.
|
||||
# docker run -d --name postgres \
|
||||
# -p 5432:5432 \
|
||||
@ -176,7 +185,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ "1.18", "1.19", "1.20", "1.21" ]
|
||||
go-version: [ "1.18", "1.19", "1.20", "1.21", "1.22" ]
|
||||
goarch: [ "386", "amd64" ]
|
||||
|
||||
steps:
|
||||
@ -202,7 +211,7 @@ jobs:
|
||||
run: docker-compose -f ".github/workflows/consul/docker-compose.yml" up -d --build
|
||||
|
||||
- name: Setup Golang ${{ matrix.go-version }}
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
cache-dependency-path: '**/go.sum'
|
||||
@ -231,7 +240,7 @@ jobs:
|
||||
run: docker-compose -f ".github/workflows/consul/docker-compose.yml" down
|
||||
|
||||
- name: Report Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: codecov/codecov-action@v4
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
with:
|
||||
flags: go-${{ matrix.go-version }}-${{ matrix.goarch }}
|
||||
|
||||
4
.github/workflows/ci-sub.yml
vendored
4
.github/workflows/ci-sub.yml
vendored
@ -37,7 +37,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ "1.18", "1.19", "1.20", "1.21" ]
|
||||
go-version: [ "1.18", "1.19", "1.20", "1.22" ]
|
||||
goarch: [ "386", "amd64" ]
|
||||
|
||||
steps:
|
||||
@ -53,7 +53,7 @@ jobs:
|
||||
uses: medyagh/setup-minikube@master
|
||||
|
||||
- name: Setup Golang ${{ matrix.go-version }}
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
cache-dependency-path: '**/go.sum'
|
||||
|
||||
8
.github/workflows/golangci-lint.yml
vendored
8
.github/workflows/golangci-lint.yml
vendored
@ -36,19 +36,19 @@ jobs:
|
||||
golangci:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ '1.18','1.19','1.20','1.21.4' ]
|
||||
go-version: [ '1.18','1.19','1.20','1.21.4','1.22' ]
|
||||
name: golangci-lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Golang ${{ matrix.go-version }}
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
uses: golangci/golangci-lint-action@v4
|
||||
with:
|
||||
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
|
||||
version: v1.52.2
|
||||
version: v1.56.2
|
||||
args: --timeout 3m0s
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set Up Golang Environment
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.20.8
|
||||
|
||||
|
||||
@ -262,7 +262,7 @@ linters-settings:
|
||||
# Select the Go version to target.
|
||||
# Default: "1.13"
|
||||
# Deprecated: use the global `run.go` instead.
|
||||
go: "1.15"
|
||||
go: "1.20"
|
||||
# SAxxxx checks in https://staticcheck.io/docs/configuration/options/#checks
|
||||
# Default: ["*"]
|
||||
checks: [ "all","-SA1019","-SA4015","-SA1029","-SA1016","-SA9003","-SA4006","-SA6003" ]
|
||||
|
||||
@ -3,13 +3,13 @@ module github.com/gogf/gf/cmd/gf/v2
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.6.3
|
||||
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.6.3
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.6.3
|
||||
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.6.3
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.6.3
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.6.3
|
||||
github.com/gogf/gf/v2 v2.6.3
|
||||
github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.6.4
|
||||
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.6.4
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.6.4
|
||||
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.6.4
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.6.4
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.6.4
|
||||
github.com/gogf/gf/v2 v2.6.4
|
||||
github.com/gogf/selfupdate v0.0.0-20231215043001-5c48c528462f
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
golang.org/x/mod v0.9.0
|
||||
|
||||
@ -393,3 +393,78 @@ func Test_Gen_Dao_Issue2616(t *testing.T) {
|
||||
t.Assert(gstr.Contains(daoUser2Content, keyStr), false)
|
||||
})
|
||||
}
|
||||
|
||||
// https://github.com/gogf/gf/issues/2746
|
||||
func Test_Gen_Dao_Issue2746(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
err error
|
||||
mdb gdb.DB
|
||||
link2746 = "mariadb:root:12345678@tcp(127.0.0.1:3307)/test?loc=Local&parseTime=true"
|
||||
table = "issue2746"
|
||||
sqlContent = fmt.Sprintf(
|
||||
gtest.DataContent(`issue`, `2746`, `sql.sql`),
|
||||
table,
|
||||
)
|
||||
)
|
||||
mdb, err = gdb.New(gdb.ConfigNode{
|
||||
Link: link2746,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
array := gstr.SplitAndTrim(sqlContent, ";")
|
||||
for _, v := range array {
|
||||
if _, err = mdb.Exec(ctx, v); err != nil {
|
||||
t.AssertNil(err)
|
||||
}
|
||||
}
|
||||
defer dropTableWithDb(mdb, table)
|
||||
|
||||
var (
|
||||
path = gfile.Temp(guid.S())
|
||||
group = "test"
|
||||
in = gendao.CGenDaoInput{
|
||||
Path: path,
|
||||
Link: link2746,
|
||||
Tables: "",
|
||||
TablesEx: "",
|
||||
Group: group,
|
||||
Prefix: "",
|
||||
RemovePrefix: "",
|
||||
JsonCase: "SnakeScreaming",
|
||||
ImportPrefix: "",
|
||||
DaoPath: "",
|
||||
DoPath: "",
|
||||
EntityPath: "",
|
||||
TplDaoIndexPath: "",
|
||||
TplDaoInternalPath: "",
|
||||
TplDaoDoPath: "",
|
||||
TplDaoEntityPath: "",
|
||||
StdTime: false,
|
||||
WithTime: false,
|
||||
GJsonSupport: true,
|
||||
OverwriteDao: false,
|
||||
DescriptionTag: false,
|
||||
NoJsonTag: false,
|
||||
NoModelComment: false,
|
||||
Clear: false,
|
||||
TypeMapping: nil,
|
||||
}
|
||||
)
|
||||
err = gutil.FillStructWithDefault(&in)
|
||||
t.AssertNil(err)
|
||||
|
||||
err = gfile.Mkdir(path)
|
||||
t.AssertNil(err)
|
||||
|
||||
_, err = gendao.CGenDao{}.Dao(ctx, in)
|
||||
t.AssertNil(err)
|
||||
defer gfile.Remove(path)
|
||||
|
||||
var (
|
||||
file = filepath.FromSlash(path + "/model/entity/issue_2746.go")
|
||||
expectContent = gtest.DataContent(`issue`, `2746`, `issue_2746.go`)
|
||||
)
|
||||
t.Assert(expectContent, gfile.GetContents(file))
|
||||
})
|
||||
}
|
||||
|
||||
@ -201,8 +201,8 @@ type (
|
||||
NoModelComment bool `name:"noModelComment" short:"m" brief:"{CGenDaoBriefNoModelComment}" orphan:"true"`
|
||||
Clear bool `name:"clear" short:"a" brief:"{CGenDaoBriefClear}" orphan:"true"`
|
||||
|
||||
TypeMapping map[DBFieldTypeName]CustomAttributeType `name:"typeMapping" short:"y" brief:"{CGenDaoBriefTypeMapping}" orphan:"true"`
|
||||
generatedFilePaths *CGenDaoInternalGeneratedFilePaths
|
||||
TypeMapping map[DBFieldTypeName]CustomAttributeType `name:"typeMapping" short:"y" brief:"{CGenDaoBriefTypeMapping}" orphan:"true"`
|
||||
genItems *CGenDaoInternalGenItems
|
||||
}
|
||||
CGenDaoOutput struct{}
|
||||
|
||||
@ -213,13 +213,6 @@ type (
|
||||
NewTableNames []string
|
||||
}
|
||||
|
||||
CGenDaoInternalGeneratedFilePaths struct {
|
||||
DaoFilePaths []string
|
||||
DaoInternalFilePaths []string
|
||||
DoFilePaths []string
|
||||
EntityFilePaths []string
|
||||
}
|
||||
|
||||
DBFieldTypeName = string
|
||||
CustomAttributeType struct {
|
||||
Type string `brief:"custom attribute type name"`
|
||||
@ -228,12 +221,7 @@ type (
|
||||
)
|
||||
|
||||
func (c CGenDao) Dao(ctx context.Context, in CGenDaoInput) (out *CGenDaoOutput, err error) {
|
||||
in.generatedFilePaths = &CGenDaoInternalGeneratedFilePaths{
|
||||
DaoFilePaths: make([]string, 0),
|
||||
DaoInternalFilePaths: make([]string, 0),
|
||||
DoFilePaths: make([]string, 0),
|
||||
EntityFilePaths: make([]string, 0),
|
||||
}
|
||||
in.genItems = newCGenDaoInternalGenItems()
|
||||
if g.Cfg().Available(ctx) {
|
||||
v := g.Cfg().MustGet(ctx, CGenDaoConfig)
|
||||
if v.IsSlice() {
|
||||
@ -246,6 +234,7 @@ func (c CGenDao) Dao(ctx context.Context, in CGenDaoInput) (out *CGenDaoOutput,
|
||||
} else {
|
||||
doGenDaoForArray(ctx, -1, in)
|
||||
}
|
||||
doClear(in.genItems)
|
||||
mlog.Print("done!")
|
||||
return
|
||||
}
|
||||
@ -326,6 +315,8 @@ func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) {
|
||||
newTableNames[i] = newTableName
|
||||
}
|
||||
|
||||
in.genItems.Scale()
|
||||
|
||||
// Dao: index and internal.
|
||||
generateDao(ctx, CGenDaoInternalInput{
|
||||
CGenDaoInput: in,
|
||||
@ -348,9 +339,7 @@ func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) {
|
||||
NewTableNames: newTableNames,
|
||||
})
|
||||
|
||||
if in.Clear {
|
||||
doClear(ctx, in)
|
||||
}
|
||||
in.genItems.SetClear(in.Clear)
|
||||
}
|
||||
|
||||
func getImportPartContent(ctx context.Context, source string, isDo bool, appendImports []string) string {
|
||||
|
||||
@ -7,27 +7,40 @@
|
||||
package gendao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
||||
)
|
||||
|
||||
func doClear(ctx context.Context, in CGenDaoInput) {
|
||||
filePaths, err := gfile.ScanDirFile(in.Path, "*.go", true)
|
||||
if err != nil {
|
||||
mlog.Fatal(err)
|
||||
}
|
||||
func doClear(items *CGenDaoInternalGenItems) {
|
||||
var allGeneratedFilePaths = make([]string, 0)
|
||||
allGeneratedFilePaths = append(allGeneratedFilePaths, in.generatedFilePaths.DaoFilePaths...)
|
||||
allGeneratedFilePaths = append(allGeneratedFilePaths, in.generatedFilePaths.DaoInternalFilePaths...)
|
||||
allGeneratedFilePaths = append(allGeneratedFilePaths, in.generatedFilePaths.EntityFilePaths...)
|
||||
allGeneratedFilePaths = append(allGeneratedFilePaths, in.generatedFilePaths.DoFilePaths...)
|
||||
for _, filePath := range filePaths {
|
||||
for _, item := range items.Items {
|
||||
allGeneratedFilePaths = append(allGeneratedFilePaths, item.GeneratedFilePaths...)
|
||||
}
|
||||
for i, v := range allGeneratedFilePaths {
|
||||
allGeneratedFilePaths[i] = gfile.RealPath(v)
|
||||
}
|
||||
for _, item := range items.Items {
|
||||
if !item.Clear {
|
||||
continue
|
||||
}
|
||||
doClearItem(item, allGeneratedFilePaths)
|
||||
}
|
||||
}
|
||||
|
||||
func doClearItem(item CGenDaoInternalGenItem, allGeneratedFilePaths []string) {
|
||||
var generatedFilePaths = make([]string, 0)
|
||||
for _, dirPath := range item.StorageDirPaths {
|
||||
filePaths, err := gfile.ScanDirFile(dirPath, "*.go", true)
|
||||
if err != nil {
|
||||
mlog.Fatal(err)
|
||||
}
|
||||
generatedFilePaths = append(generatedFilePaths, filePaths...)
|
||||
}
|
||||
for _, filePath := range generatedFilePaths {
|
||||
if !gstr.InArray(allGeneratedFilePaths, filePath) {
|
||||
if err = gfile.Remove(filePath); err != nil {
|
||||
if err := gfile.Remove(filePath); err != nil {
|
||||
mlog.Print(err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,6 +30,7 @@ func generateDao(ctx context.Context, in CGenDaoInternalInput) {
|
||||
dirPathDao = gfile.Join(in.Path, in.DaoPath)
|
||||
dirPathDaoInternal = gfile.Join(dirPathDao, "internal")
|
||||
)
|
||||
in.genItems.AppendDirPath(dirPathDao)
|
||||
for i := 0; i < len(in.TableNames); i++ {
|
||||
generateDaoSingle(ctx, generateDaoSingleInput{
|
||||
CGenDaoInternalInput: in,
|
||||
@ -106,10 +107,7 @@ type generateDaoIndexInput struct {
|
||||
func generateDaoIndex(in generateDaoIndexInput) {
|
||||
path := filepath.FromSlash(gfile.Join(in.DirPathDao, in.FileName+".go"))
|
||||
// It should add path to result slice whenever it would generate the path file or not.
|
||||
in.generatedFilePaths.DaoFilePaths = append(
|
||||
in.generatedFilePaths.DaoFilePaths,
|
||||
path,
|
||||
)
|
||||
in.genItems.AppendGeneratedFilePath(path)
|
||||
if in.OverwriteDao || !gfile.Exists(path) {
|
||||
indexContent := gstr.ReplaceByMap(
|
||||
getTemplateFromPathOrDefault(in.TplDaoIndexPath, consts.TemplateGenDaoIndexContent),
|
||||
@ -153,10 +151,7 @@ func generateDaoInternal(in generateDaoInternalInput) {
|
||||
tplVarColumnNames: gstr.Trim(generateColumnNamesForDao(in.FieldMap, removeFieldPrefixArray)),
|
||||
})
|
||||
modelContent = replaceDefaultVar(in.CGenDaoInternalInput, modelContent)
|
||||
in.generatedFilePaths.DaoInternalFilePaths = append(
|
||||
in.generatedFilePaths.DaoInternalFilePaths,
|
||||
path,
|
||||
)
|
||||
in.genItems.AppendGeneratedFilePath(path)
|
||||
if err := gfile.PutContents(path, strings.TrimSpace(modelContent)); err != nil {
|
||||
mlog.Fatalf("writing content to '%s' failed: %v", path, err)
|
||||
} else {
|
||||
|
||||
@ -24,6 +24,7 @@ import (
|
||||
|
||||
func generateDo(ctx context.Context, in CGenDaoInternalInput) {
|
||||
var dirPathDo = filepath.FromSlash(gfile.Join(in.Path, in.DoPath))
|
||||
in.genItems.AppendDirPath(dirPathDo)
|
||||
in.NoJsonTag = true
|
||||
in.DescriptionTag = false
|
||||
in.NoModelComment = false
|
||||
@ -63,10 +64,7 @@ func generateDo(ctx context.Context, in CGenDaoInternalInput) {
|
||||
gstr.CaseCamel(newTableName),
|
||||
structDefinition,
|
||||
)
|
||||
in.generatedFilePaths.DoFilePaths = append(
|
||||
in.generatedFilePaths.DoFilePaths,
|
||||
doFilePath,
|
||||
)
|
||||
in.genItems.AppendGeneratedFilePath(doFilePath)
|
||||
err = gfile.PutContents(doFilePath, strings.TrimSpace(modelContent))
|
||||
if err != nil {
|
||||
mlog.Fatalf(`writing content to "%s" failed: %v`, doFilePath, err)
|
||||
|
||||
@ -22,6 +22,7 @@ import (
|
||||
|
||||
func generateEntity(ctx context.Context, in CGenDaoInternalInput) {
|
||||
var dirPathEntity = gfile.Join(in.Path, in.EntityPath)
|
||||
in.genItems.AppendDirPath(dirPathEntity)
|
||||
// Model content.
|
||||
for i, tableName := range in.TableNames {
|
||||
fieldMap, err := in.DB.TableFields(ctx, tableName)
|
||||
@ -48,10 +49,7 @@ func generateEntity(ctx context.Context, in CGenDaoInternalInput) {
|
||||
appendImports,
|
||||
)
|
||||
)
|
||||
in.generatedFilePaths.EntityFilePaths = append(
|
||||
in.generatedFilePaths.EntityFilePaths,
|
||||
entityFilePath,
|
||||
)
|
||||
in.genItems.AppendGeneratedFilePath(entityFilePath)
|
||||
err = gfile.PutContents(entityFilePath, strings.TrimSpace(entityContent))
|
||||
if err != nil {
|
||||
mlog.Fatalf("writing content to '%s' failed: %v", entityFilePath, err)
|
||||
|
||||
53
cmd/gf/internal/cmd/gendao/gendao_gen_item.go
Normal file
53
cmd/gf/internal/cmd/gendao/gendao_gen_item.go
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright GoFrame gf 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 gendao
|
||||
|
||||
type (
|
||||
CGenDaoInternalGenItems struct {
|
||||
index int
|
||||
Items []CGenDaoInternalGenItem
|
||||
}
|
||||
CGenDaoInternalGenItem struct {
|
||||
Clear bool
|
||||
StorageDirPaths []string
|
||||
GeneratedFilePaths []string
|
||||
}
|
||||
)
|
||||
|
||||
func newCGenDaoInternalGenItems() *CGenDaoInternalGenItems {
|
||||
return &CGenDaoInternalGenItems{
|
||||
index: -1,
|
||||
Items: make([]CGenDaoInternalGenItem, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (i *CGenDaoInternalGenItems) Scale() {
|
||||
i.Items = append(i.Items, CGenDaoInternalGenItem{
|
||||
StorageDirPaths: make([]string, 0),
|
||||
GeneratedFilePaths: make([]string, 0),
|
||||
Clear: false,
|
||||
})
|
||||
i.index++
|
||||
}
|
||||
|
||||
func (i *CGenDaoInternalGenItems) SetClear(clear bool) {
|
||||
i.Items[i.index].Clear = clear
|
||||
}
|
||||
|
||||
func (i CGenDaoInternalGenItems) AppendDirPath(storageDirPath string) {
|
||||
i.Items[i.index].StorageDirPaths = append(
|
||||
i.Items[i.index].StorageDirPaths,
|
||||
storageDirPath,
|
||||
)
|
||||
}
|
||||
|
||||
func (i CGenDaoInternalGenItems) AppendGeneratedFilePath(generatedFilePath string) {
|
||||
i.Items[i.index].GeneratedFilePaths = append(
|
||||
i.Items[i.index].GeneratedFilePaths,
|
||||
generatedFilePath,
|
||||
)
|
||||
}
|
||||
18
cmd/gf/internal/cmd/testdata/issue/2746/issue_2746.go
vendored
Normal file
18
cmd/gf/internal/cmd/testdata/issue/2746/issue_2746.go
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package entity
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
)
|
||||
|
||||
// Issue2746 is the golang structure for table issue2746.
|
||||
type Issue2746 struct {
|
||||
Id uint `json:"ID" ` // User ID
|
||||
Nickname string `json:"NICKNAME" ` // User Nickname
|
||||
Tag *gjson.Json `json:"TAG" ` //
|
||||
Info string `json:"INFO" ` //
|
||||
Tag2 *gjson.Json `json:"TAG_2" ` // Tag2
|
||||
}
|
||||
9
cmd/gf/internal/cmd/testdata/issue/2746/sql.sql
vendored
Normal file
9
cmd/gf/internal/cmd/testdata/issue/2746/sql.sql
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
CREATE TABLE %s (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'User ID',
|
||||
`nickname` varchar(45) NOT NULL COMMENT 'User Nickname',
|
||||
`tag` json NOT NULL,
|
||||
`info` longtext DEFAULT NULL,
|
||||
`tag2` json COMMENT 'Tag2',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
@ -4,7 +4,7 @@ go 1.18
|
||||
|
||||
require (
|
||||
github.com/apolloconfig/agollo/v4 v4.3.1
|
||||
github.com/gogf/gf/v2 v2.6.3
|
||||
github.com/gogf/gf/v2 v2.6.4
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/config/consul/v2
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.6.3
|
||||
github.com/gogf/gf/v2 v2.6.4
|
||||
github.com/hashicorp/consul/api v1.24.0
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2
|
||||
)
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/config/kubecm/v2
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.6.3
|
||||
github.com/gogf/gf/v2 v2.6.4
|
||||
k8s.io/api v0.27.4
|
||||
k8s.io/apimachinery v0.27.4
|
||||
k8s.io/client-go v0.27.4
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/config/nacos/v2
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.6.3
|
||||
github.com/gogf/gf/v2 v2.6.4
|
||||
github.com/nacos-group/nacos-sdk-go/v2 v2.2.5
|
||||
)
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/config/polaris/v2
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.6.3
|
||||
github.com/gogf/gf/v2 v2.6.4
|
||||
github.com/polarismesh/polaris-go v1.5.5
|
||||
)
|
||||
|
||||
|
||||
@ -39,16 +39,13 @@ import _ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||
```
|
||||
import _ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
|
||||
```
|
||||
Note:
|
||||
- It does not support `Save` features.
|
||||
|
||||
## PostgreSQL
|
||||
```
|
||||
import _ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
|
||||
```
|
||||
Note:
|
||||
- It does not support `Save/Replace` features.
|
||||
- It does not support `LastInsertId`.
|
||||
- It does not support `Replace` features.
|
||||
|
||||
## SQL Server
|
||||
```
|
||||
|
||||
@ -4,7 +4,7 @@ go 1.18
|
||||
|
||||
require (
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.0.15
|
||||
github.com/gogf/gf/v2 v2.6.3
|
||||
github.com/gogf/gf/v2 v2.6.4
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/shopspring/decimal v1.3.1
|
||||
)
|
||||
|
||||
@ -6,7 +6,7 @@ replace github.com/gogf/gf/v2 => ../../../
|
||||
|
||||
require (
|
||||
gitee.com/chunanyong/dm v1.8.12
|
||||
github.com/gogf/gf/v2 v2.6.3
|
||||
github.com/gogf/gf/v2 v2.6.4
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@ -4,7 +4,7 @@ go 1.18
|
||||
|
||||
require (
|
||||
github.com/denisenkom/go-mssqldb v0.12.3
|
||||
github.com/gogf/gf/v2 v2.6.3
|
||||
github.com/gogf/gf/v2 v2.6.4
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@ -15,8 +15,6 @@ import (
|
||||
_ "github.com/denisenkom/go-mssqldb"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
// Driver is the driver for SQL server database.
|
||||
@ -34,21 +32,6 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// formatSqlTmp formats sql template string into one line.
|
||||
func formatSqlTmp(sqlTmp string) string {
|
||||
var err error
|
||||
// format sql template string.
|
||||
sqlTmp, err = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(sqlTmp))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sqlTmp, err = gregex.ReplaceString(`\s{2,}`, " ", gstr.Trim(sqlTmp))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sqlTmp
|
||||
}
|
||||
|
||||
// New create and returns a driver that implements gdb.Driver, which supports operations for Mssql.
|
||||
func New() gdb.Driver {
|
||||
return &Driver{}
|
||||
|
||||
@ -9,7 +9,6 @@ package mssql
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -27,7 +26,7 @@ WHERE TMP_.ROWNUMBER_ > %d AND TMP_.ROWNUMBER_ <= %d
|
||||
)
|
||||
|
||||
func init() {
|
||||
selectWithOrderSqlTmp = formatSqlTmp(selectWithOrderSqlTmp)
|
||||
selectWithOrderSqlTmp = gdb.FormatMultiLineSqlToSingle(selectWithOrderSqlTmp)
|
||||
}
|
||||
|
||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||
|
||||
@ -47,7 +47,7 @@ ORDER BY a.id,a.colorder
|
||||
)
|
||||
|
||||
func init() {
|
||||
tableFieldsSqlTmp = formatSqlTmp(tableFieldsSqlTmp)
|
||||
tableFieldsSqlTmp = gdb.FormatMultiLineSqlToSingle(tableFieldsSqlTmp)
|
||||
}
|
||||
|
||||
// TableFields retrieves and returns the fields' information of specified table of current schema.
|
||||
|
||||
@ -4,7 +4,7 @@ go 1.18
|
||||
|
||||
require (
|
||||
github.com/go-sql-driver/mysql v1.7.1
|
||||
github.com/gogf/gf/v2 v2.6.3
|
||||
github.com/gogf/gf/v2 v2.6.4
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@ -14,6 +14,32 @@ import (
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
var (
|
||||
tableFieldsSqlByMariadb = `
|
||||
SELECT
|
||||
c.COLUMN_NAME AS 'Field',
|
||||
( CASE WHEN ch.CHECK_CLAUSE LIKE 'json_valid%%' THEN 'json' ELSE c.COLUMN_TYPE END ) AS 'Type',
|
||||
c.COLLATION_NAME AS 'Collation',
|
||||
c.IS_NULLABLE AS 'Null',
|
||||
c.COLUMN_KEY AS 'Key',
|
||||
( CASE WHEN c.COLUMN_DEFAULT = 'NULL' OR c.COLUMN_DEFAULT IS NULL THEN NULL ELSE c.COLUMN_DEFAULT END) AS 'Default',
|
||||
c.EXTRA AS 'Extra',
|
||||
c.PRIVILEGES AS 'Privileges',
|
||||
c.COLUMN_COMMENT AS 'Comment'
|
||||
FROM
|
||||
information_schema.COLUMNS AS c
|
||||
LEFT JOIN information_schema.CHECK_CONSTRAINTS AS ch ON c.TABLE_NAME = ch.TABLE_NAME
|
||||
AND c.COLUMN_NAME = ch.CONSTRAINT_NAME
|
||||
WHERE
|
||||
c.TABLE_SCHEMA = '%s'
|
||||
AND c.TABLE_NAME = '%s'
|
||||
ORDER BY c.ORDINAL_POSITION`
|
||||
)
|
||||
|
||||
func init() {
|
||||
tableFieldsSqlByMariadb = gdb.FormatMultiLineSqlToSingle(tableFieldsSqlByMariadb)
|
||||
}
|
||||
|
||||
// TableFields retrieves and returns the fields' information of specified table of current
|
||||
// schema.
|
||||
//
|
||||
@ -28,16 +54,25 @@ import (
|
||||
// process restarts.
|
||||
func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
tableFieldsSql string
|
||||
)
|
||||
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbType := d.GetConfig().Type
|
||||
switch dbType {
|
||||
case "mariadb":
|
||||
tableFieldsSql = fmt.Sprintf(tableFieldsSqlByMariadb, usedSchema, table)
|
||||
default:
|
||||
tableFieldsSql = fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.QuoteWord(table))
|
||||
}
|
||||
|
||||
result, err = d.DoSelect(
|
||||
ctx, link,
|
||||
fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.QuoteWord(table)),
|
||||
tableFieldsSql,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/drivers/oracle/v2
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.6.3
|
||||
github.com/gogf/gf/v2 v2.6.4
|
||||
github.com/sijms/go-ora/v2 v2.7.10
|
||||
)
|
||||
|
||||
|
||||
@ -13,8 +13,6 @@ package oracle
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
// Driver is the driver for oracle database.
|
||||
@ -32,21 +30,6 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// formatSqlTmp formats sql template string into one line.
|
||||
func formatSqlTmp(sqlTmp string) string {
|
||||
var err error
|
||||
// format sql template string.
|
||||
sqlTmp, err = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(sqlTmp))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sqlTmp, err = gregex.ReplaceString(`\s{2,}`, " ", gstr.Trim(sqlTmp))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sqlTmp
|
||||
}
|
||||
|
||||
// New create and returns a driver that implements gdb.Driver, which supports operations for Oracle.
|
||||
func New() gdb.Driver {
|
||||
return &Driver{}
|
||||
|
||||
@ -27,7 +27,7 @@ SELECT * FROM (
|
||||
)
|
||||
|
||||
func init() {
|
||||
newSqlReplacementTmp = formatSqlTmp(newSqlReplacementTmp)
|
||||
newSqlReplacementTmp = gdb.FormatMultiLineSqlToSingle(newSqlReplacementTmp)
|
||||
}
|
||||
|
||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||
|
||||
@ -29,7 +29,7 @@ FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID
|
||||
)
|
||||
|
||||
func init() {
|
||||
tableFieldsSqlTmp = formatSqlTmp(tableFieldsSqlTmp)
|
||||
tableFieldsSqlTmp = gdb.FormatMultiLineSqlToSingle(tableFieldsSqlTmp)
|
||||
}
|
||||
|
||||
// TableFields retrieves and returns the fields' information of specified table of current schema.
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/drivers/pgsql/v2
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.6.3
|
||||
github.com/gogf/gf/v2 v2.6.4
|
||||
github.com/lib/pq v1.10.9
|
||||
)
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
// Package pgsql implements gdb.Driver, which supports operations for database PostgreSQL.
|
||||
//
|
||||
// Note:
|
||||
// 1. It does not support Save/Replace features.
|
||||
// 1. It does not support Replace features.
|
||||
// 2. It does not support Insert Ignore features.
|
||||
package pgsql
|
||||
|
||||
@ -16,8 +16,6 @@ import (
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
// Driver is the driver for postgresql database.
|
||||
@ -37,21 +35,6 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// formatSqlTmp formats sql template string into one line.
|
||||
func formatSqlTmp(sqlTmp string) string {
|
||||
var err error
|
||||
// format sql template string.
|
||||
sqlTmp, err = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(sqlTmp))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sqlTmp, err = gregex.ReplaceString(`\s{2,}`, " ", gstr.Trim(sqlTmp))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sqlTmp
|
||||
}
|
||||
|
||||
// New create and returns a driver that implements gdb.Driver, which supports operations for PostgreSql.
|
||||
func New() gdb.Driver {
|
||||
return &Driver{}
|
||||
|
||||
@ -18,12 +18,6 @@ import (
|
||||
// DoInsert inserts or updates data forF given table.
|
||||
func (d *Driver) DoInsert(ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption) (result sql.Result, err error) {
|
||||
switch option.InsertOption {
|
||||
case gdb.InsertOptionSave:
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeNotSupported,
|
||||
`Save operation is not supported by pgsql driver`,
|
||||
)
|
||||
|
||||
case gdb.InsertOptionReplace:
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeNotSupported,
|
||||
|
||||
68
contrib/drivers/pgsql/pgsql_format_upsert.go
Normal file
68
contrib/drivers/pgsql/pgsql_format_upsert.go
Normal file
@ -0,0 +1,68 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package pgsql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// FormatUpsert returns SQL clause of type upsert for PgSQL.
|
||||
// For example: ON CONFLICT (id) DO UPDATE SET ...
|
||||
func (d *Driver) FormatUpsert(columns []string, list gdb.List, option gdb.DoInsertOption) (string, error) {
|
||||
if len(option.OnConflict) == 0 {
|
||||
return "", gerror.New("Please specify conflict columns")
|
||||
}
|
||||
|
||||
var onDuplicateStr string
|
||||
if option.OnDuplicateStr != "" {
|
||||
onDuplicateStr = option.OnDuplicateStr
|
||||
} else if len(option.OnDuplicateMap) > 0 {
|
||||
for k, v := range option.OnDuplicateMap {
|
||||
if len(onDuplicateStr) > 0 {
|
||||
onDuplicateStr += ","
|
||||
}
|
||||
switch v.(type) {
|
||||
case gdb.Raw, *gdb.Raw:
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=%s",
|
||||
d.Core.QuoteWord(k),
|
||||
v,
|
||||
)
|
||||
default:
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=EXCLUDED.%s",
|
||||
d.Core.QuoteWord(k),
|
||||
d.Core.QuoteWord(gconv.String(v)),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, column := range columns {
|
||||
// If it's SAVE operation, do not automatically update the creating time.
|
||||
if d.Core.IsSoftCreatedFieldName(column) {
|
||||
continue
|
||||
}
|
||||
if len(onDuplicateStr) > 0 {
|
||||
onDuplicateStr += ","
|
||||
}
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=EXCLUDED.%s",
|
||||
d.Core.QuoteWord(column),
|
||||
d.Core.QuoteWord(column),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
conflictKeys := gstr.Join(option.OnConflict, ",")
|
||||
|
||||
return fmt.Sprintf("ON CONFLICT (%s) DO UPDATE SET ", conflictKeys) + onDuplicateStr, nil
|
||||
}
|
||||
@ -17,7 +17,7 @@ import (
|
||||
var (
|
||||
tableFieldsSqlTmp = `
|
||||
SELECT a.attname AS field, t.typname AS type,a.attnotnull as null,
|
||||
(case when d.contype is not null then 'pri' else '' end) as key
|
||||
(case when d.contype = 'p' then 'pri' when d.contype = 'u' then 'uni' else '' end) as key
|
||||
,ic.column_default as default_value,b.description as comment
|
||||
,coalesce(character_maximum_length, numeric_precision, -1) as length
|
||||
,numeric_scale as scale
|
||||
@ -32,7 +32,7 @@ ORDER BY a.attnum`
|
||||
)
|
||||
|
||||
func init() {
|
||||
tableFieldsSqlTmp = formatSqlTmp(tableFieldsSqlTmp)
|
||||
tableFieldsSqlTmp = gdb.FormatMultiLineSqlToSingle(tableFieldsSqlTmp)
|
||||
}
|
||||
|
||||
// TableFields retrieves and returns the fields' information of specified table of current schema.
|
||||
|
||||
@ -35,7 +35,7 @@ ORDER BY
|
||||
)
|
||||
|
||||
func init() {
|
||||
tablesSqlTmp = formatSqlTmp(tablesSqlTmp)
|
||||
tablesSqlTmp = gdb.FormatMultiLineSqlToSingle(tablesSqlTmp)
|
||||
}
|
||||
|
||||
// Tables retrieves and returns the tables of current schema.
|
||||
|
||||
@ -78,12 +78,12 @@ func createTableWithDb(db gdb.DB, table ...string) (name string) {
|
||||
|
||||
if _, err := db.Exec(ctx, fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id bigserial NOT NULL,
|
||||
passport varchar(45) NOT NULL,
|
||||
password varchar(32) NOT NULL,
|
||||
nickname varchar(45) NOT NULL,
|
||||
create_time timestamp NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
id bigserial NOT NULL,
|
||||
passport varchar(45) NOT NULL,
|
||||
password varchar(32) NOT NULL,
|
||||
nickname varchar(45) NOT NULL,
|
||||
create_time timestamp NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ;`, name,
|
||||
)); err != nil {
|
||||
gtest.Fatal(err)
|
||||
|
||||
75
contrib/drivers/pgsql/pgsql_z_unit_issue_test.go
Normal file
75
contrib/drivers/pgsql/pgsql_z_unit_issue_test.go
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package pgsql_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
// https://github.com/gogf/gf/issues/3330
|
||||
func Test_Issue3330(t *testing.T) {
|
||||
var (
|
||||
table = fmt.Sprintf(`%s_%d`, TablePrefix+"test", gtime.TimestampNano())
|
||||
uniqueName = fmt.Sprintf(`%s_%d`, TablePrefix+"test_unique", gtime.TimestampNano())
|
||||
)
|
||||
if _, err := db.Exec(ctx, fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id bigserial NOT NULL,
|
||||
passport varchar(45) NOT NULL,
|
||||
password varchar(32) NOT NULL,
|
||||
nickname varchar(45) NOT NULL,
|
||||
create_time timestamp NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT %s unique ("password")
|
||||
) ;`, table, uniqueName,
|
||||
)); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
list []map[string]interface{}
|
||||
one gdb.Record
|
||||
err error
|
||||
)
|
||||
|
||||
fields, err := db.TableFields(ctx, table)
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(fields["id"].Key, "pri")
|
||||
t.Assert(fields["password"].Key, "uni")
|
||||
|
||||
for i := 1; i <= 10; i++ {
|
||||
list = append(list, g.Map{
|
||||
"id": i,
|
||||
"passport": fmt.Sprintf("p%d", i),
|
||||
"password": fmt.Sprintf("pw%d", i),
|
||||
"nickname": fmt.Sprintf("n%d", i),
|
||||
"create_time": "2016-06-01 00:00:00",
|
||||
})
|
||||
}
|
||||
|
||||
_, err = db.Model(table).Data(list).Insert()
|
||||
t.AssertNil(err)
|
||||
|
||||
for i := 1; i <= 10; i++ {
|
||||
one, err = db.Model(table).WherePri(i).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["id"], list[i-1]["id"])
|
||||
t.Assert(one["passport"], list[i-1]["passport"])
|
||||
t.Assert(one["password"], list[i-1]["password"])
|
||||
t.Assert(one["nickname"], list[i-1]["nickname"])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -7,8 +7,11 @@
|
||||
package pgsql_test
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
@ -258,14 +261,58 @@ func Test_Model_Save(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
_, err := db.Model(table).Data(g.Map{
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
NickName string
|
||||
CreateTime *gtime.Time
|
||||
}
|
||||
var (
|
||||
user User
|
||||
count int
|
||||
result sql.Result
|
||||
err error
|
||||
)
|
||||
|
||||
result, err = db.Model(table).Data(g.Map{
|
||||
"id": 1,
|
||||
"passport": "t111",
|
||||
"password": "25d55ad283aa400af464c76d713c07ad",
|
||||
"nickname": "T111",
|
||||
"create_time": "2018-10-24 10:00:00",
|
||||
}).Save()
|
||||
t.Assert(err, "Save operation is not supported by pgsql driver")
|
||||
"passport": "p1",
|
||||
"password": "pw1",
|
||||
"nickname": "n1",
|
||||
"create_time": CreateTime,
|
||||
}).OnConflict("id").Save()
|
||||
t.AssertNil(nil)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, 1)
|
||||
|
||||
err = db.Model(table).Scan(&user)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(user.Id, 1)
|
||||
t.Assert(user.Passport, "p1")
|
||||
t.Assert(user.Password, "pw1")
|
||||
t.Assert(user.NickName, "n1")
|
||||
t.Assert(user.CreateTime.String(), CreateTime)
|
||||
|
||||
_, err = db.Model(table).Data(g.Map{
|
||||
"id": 1,
|
||||
"passport": "p1",
|
||||
"password": "pw2",
|
||||
"nickname": "n2",
|
||||
"create_time": CreateTime,
|
||||
}).OnConflict("id").Save()
|
||||
t.AssertNil(err)
|
||||
|
||||
err = db.Model(table).Scan(&user)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(user.Passport, "p1")
|
||||
t.Assert(user.Password, "pw2")
|
||||
t.Assert(user.NickName, "n2")
|
||||
t.Assert(user.CreateTime.String(), CreateTime)
|
||||
|
||||
count, err = db.Model(table).Count()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(count, 1)
|
||||
})
|
||||
}
|
||||
|
||||
@ -284,3 +331,259 @@ func Test_Model_Replace(t *testing.T) {
|
||||
t.Assert(err, "Replace operation is not supported by pgsql driver")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_OnConflict(t *testing.T) {
|
||||
var (
|
||||
table = fmt.Sprintf(`%s_%d`, TablePrefix+"test", gtime.TimestampNano())
|
||||
uniqueName = fmt.Sprintf(`%s_%d`, TablePrefix+"test_unique", gtime.TimestampNano())
|
||||
)
|
||||
if _, err := db.Exec(ctx, fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id bigserial NOT NULL,
|
||||
passport varchar(45) NOT NULL,
|
||||
password varchar(32) NOT NULL,
|
||||
nickname varchar(45) NOT NULL,
|
||||
create_time timestamp NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT %s UNIQUE ("passport", "password")
|
||||
) ;`, table, uniqueName,
|
||||
)); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
defer dropTable(table)
|
||||
|
||||
// string type 1.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "pp1",
|
||||
"password": "pw1",
|
||||
"nickname": "n1",
|
||||
"create_time": "2016-06-06",
|
||||
}
|
||||
_, err := db.Model(table).OnConflict("passport,password").Data(data).Save()
|
||||
t.AssertNil(err)
|
||||
one, err := db.Model(table).Where("id", 1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], data["passport"])
|
||||
t.Assert(one["password"], data["password"])
|
||||
t.Assert(one["nickname"], "n1")
|
||||
})
|
||||
|
||||
// string type 2.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "pp1",
|
||||
"password": "pw1",
|
||||
"nickname": "n1",
|
||||
"create_time": "2016-06-06",
|
||||
}
|
||||
_, err := db.Model(table).OnConflict("passport", "password").Data(data).Save()
|
||||
t.AssertNil(err)
|
||||
one, err := db.Model(table).Where("id", 1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], data["passport"])
|
||||
t.Assert(one["password"], data["password"])
|
||||
t.Assert(one["nickname"], "n1")
|
||||
})
|
||||
|
||||
// slice.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "pp1",
|
||||
"password": "pw1",
|
||||
"nickname": "n1",
|
||||
"create_time": "2016-06-06",
|
||||
}
|
||||
_, err := db.Model(table).OnConflict(g.Slice{"passport", "password"}).Data(data).Save()
|
||||
t.AssertNil(err)
|
||||
one, err := db.Model(table).Where("id", 1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], data["passport"])
|
||||
t.Assert(one["password"], data["password"])
|
||||
t.Assert(one["nickname"], "n1")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_OnDuplicate(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
// string type 1.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "pp1",
|
||||
"password": "pw1",
|
||||
"nickname": "n1",
|
||||
"create_time": "2016-06-06",
|
||||
}
|
||||
_, err := db.Model(table).OnConflict("id").OnDuplicate("passport,password").Data(data).Save()
|
||||
t.AssertNil(err)
|
||||
one, err := db.Model(table).WherePri(1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], data["passport"])
|
||||
t.Assert(one["password"], data["password"])
|
||||
t.Assert(one["nickname"], "name_1")
|
||||
})
|
||||
|
||||
// string type 2.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "pp1",
|
||||
"password": "pw1",
|
||||
"nickname": "n1",
|
||||
"create_time": "2016-06-06",
|
||||
}
|
||||
_, err := db.Model(table).OnConflict("id").OnDuplicate("passport", "password").Data(data).Save()
|
||||
t.AssertNil(err)
|
||||
one, err := db.Model(table).WherePri(1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], data["passport"])
|
||||
t.Assert(one["password"], data["password"])
|
||||
t.Assert(one["nickname"], "name_1")
|
||||
})
|
||||
|
||||
// slice.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "pp1",
|
||||
"password": "pw1",
|
||||
"nickname": "n1",
|
||||
"create_time": "2016-06-06",
|
||||
}
|
||||
_, err := db.Model(table).OnConflict("id").OnDuplicate(g.Slice{"passport", "password"}).Data(data).Save()
|
||||
t.AssertNil(err)
|
||||
one, err := db.Model(table).WherePri(1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], data["passport"])
|
||||
t.Assert(one["password"], data["password"])
|
||||
t.Assert(one["nickname"], "name_1")
|
||||
})
|
||||
|
||||
// map.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "pp1",
|
||||
"password": "pw1",
|
||||
"nickname": "n1",
|
||||
"create_time": "2016-06-06",
|
||||
}
|
||||
_, err := db.Model(table).OnConflict("id").OnDuplicate(g.Map{
|
||||
"passport": "nickname",
|
||||
"password": "nickname",
|
||||
}).Data(data).Save()
|
||||
t.AssertNil(err)
|
||||
one, err := db.Model(table).WherePri(1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], data["nickname"])
|
||||
t.Assert(one["password"], data["nickname"])
|
||||
t.Assert(one["nickname"], "name_1")
|
||||
})
|
||||
|
||||
// map+raw.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.MapStrStr{
|
||||
"id": "1",
|
||||
"passport": "pp1",
|
||||
"password": "pw1",
|
||||
"nickname": "n1",
|
||||
"create_time": "2016-06-06",
|
||||
}
|
||||
_, err := db.Model(table).OnConflict("id").OnDuplicate(g.Map{
|
||||
"passport": gdb.Raw("CONCAT(EXCLUDED.passport, '1')"),
|
||||
"password": gdb.Raw("CONCAT(EXCLUDED.password, '2')"),
|
||||
}).Data(data).Save()
|
||||
t.AssertNil(err)
|
||||
one, err := db.Model(table).WherePri(1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], data["passport"]+"1")
|
||||
t.Assert(one["password"], data["password"]+"2")
|
||||
t.Assert(one["nickname"], "name_1")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_OnDuplicateEx(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
// string type 1.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "pp1",
|
||||
"password": "pw1",
|
||||
"nickname": "n1",
|
||||
"create_time": "2016-06-06",
|
||||
}
|
||||
_, err := db.Model(table).OnConflict("id").OnDuplicateEx("nickname,create_time").Data(data).Save()
|
||||
t.AssertNil(err)
|
||||
one, err := db.Model(table).WherePri(1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], data["passport"])
|
||||
t.Assert(one["password"], data["password"])
|
||||
t.Assert(one["nickname"], "name_1")
|
||||
})
|
||||
|
||||
// string type 2.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "pp1",
|
||||
"password": "pw1",
|
||||
"nickname": "n1",
|
||||
"create_time": "2016-06-06",
|
||||
}
|
||||
_, err := db.Model(table).OnConflict("id").OnDuplicateEx("nickname", "create_time").Data(data).Save()
|
||||
t.AssertNil(err)
|
||||
one, err := db.Model(table).WherePri(1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], data["passport"])
|
||||
t.Assert(one["password"], data["password"])
|
||||
t.Assert(one["nickname"], "name_1")
|
||||
})
|
||||
|
||||
// slice.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "pp1",
|
||||
"password": "pw1",
|
||||
"nickname": "n1",
|
||||
"create_time": "2016-06-06",
|
||||
}
|
||||
_, err := db.Model(table).OnConflict("id").OnDuplicateEx(g.Slice{"nickname", "create_time"}).Data(data).Save()
|
||||
t.AssertNil(err)
|
||||
one, err := db.Model(table).WherePri(1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], data["passport"])
|
||||
t.Assert(one["password"], data["password"])
|
||||
t.Assert(one["nickname"], "name_1")
|
||||
})
|
||||
|
||||
// map.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "pp1",
|
||||
"password": "pw1",
|
||||
"nickname": "n1",
|
||||
"create_time": "2016-06-06",
|
||||
}
|
||||
_, err := db.Model(table).OnConflict("id").OnDuplicateEx(g.Map{
|
||||
"nickname": "nickname",
|
||||
"create_time": "nickname",
|
||||
}).Data(data).Save()
|
||||
t.AssertNil(err)
|
||||
one, err := db.Model(table).WherePri(1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], data["passport"])
|
||||
t.Assert(one["password"], data["password"])
|
||||
t.Assert(one["nickname"], "name_1")
|
||||
})
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ go 1.18
|
||||
|
||||
require (
|
||||
github.com/glebarez/go-sqlite v1.21.2
|
||||
github.com/gogf/gf/v2 v2.6.3
|
||||
github.com/gogf/gf/v2 v2.6.4
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@ -5,9 +5,6 @@
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package sqlite implements gdb.Driver, which supports operations for database SQLite.
|
||||
//
|
||||
// Note:
|
||||
// 1. It does not support Save features.
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
|
||||
@ -10,8 +10,6 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
@ -24,14 +22,6 @@ func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args [
|
||||
|
||||
case gstr.HasPrefix(sql, gdb.InsertOperationReplace):
|
||||
sql = "INSERT OR REPLACE" + sql[len(gdb.InsertOperationReplace):]
|
||||
|
||||
default:
|
||||
if gstr.Contains(sql, gdb.InsertOnDuplicateKeyUpdate) {
|
||||
return sql, args, gerror.NewCode(
|
||||
gcode.CodeNotSupported,
|
||||
`Save operation is not supported by sqlite driver`,
|
||||
)
|
||||
}
|
||||
}
|
||||
return d.Core.DoFilter(ctx, link, sql, args)
|
||||
}
|
||||
|
||||
68
contrib/drivers/sqlite/sqlite_format_upsert.go
Normal file
68
contrib/drivers/sqlite/sqlite_format_upsert.go
Normal file
@ -0,0 +1,68 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// FormatUpsert returns SQL clause of type upsert for SQLite.
|
||||
// For example: ON CONFLICT (id) DO UPDATE SET ...
|
||||
func (d *Driver) FormatUpsert(columns []string, list gdb.List, option gdb.DoInsertOption) (string, error) {
|
||||
if len(option.OnConflict) == 0 {
|
||||
return "", gerror.New("Please specify conflict columns")
|
||||
}
|
||||
|
||||
var onDuplicateStr string
|
||||
if option.OnDuplicateStr != "" {
|
||||
onDuplicateStr = option.OnDuplicateStr
|
||||
} else if len(option.OnDuplicateMap) > 0 {
|
||||
for k, v := range option.OnDuplicateMap {
|
||||
if len(onDuplicateStr) > 0 {
|
||||
onDuplicateStr += ","
|
||||
}
|
||||
switch v.(type) {
|
||||
case gdb.Raw, *gdb.Raw:
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=%s",
|
||||
d.Core.QuoteWord(k),
|
||||
v,
|
||||
)
|
||||
default:
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=EXCLUDED.%s",
|
||||
d.Core.QuoteWord(k),
|
||||
d.Core.QuoteWord(gconv.String(v)),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, column := range columns {
|
||||
// If it's SAVE operation, do not automatically update the creating time.
|
||||
if d.Core.IsSoftCreatedFieldName(column) {
|
||||
continue
|
||||
}
|
||||
if len(onDuplicateStr) > 0 {
|
||||
onDuplicateStr += ","
|
||||
}
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=EXCLUDED.%s",
|
||||
d.Core.QuoteWord(column),
|
||||
d.Core.QuoteWord(column),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
conflictKeys := gstr.Join(option.OnConflict, ",")
|
||||
|
||||
return fmt.Sprintf("ON CONFLICT (%s) DO UPDATE SET ", conflictKeys) + onDuplicateStr, nil
|
||||
}
|
||||
@ -425,19 +425,22 @@ func Test_DB_BatchInsert_Struct(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_DB_Save(t *testing.T) {
|
||||
table := createInitTable()
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timeStr := gtime.Now().String()
|
||||
_, err := db.Save(ctx, table, g.Map{
|
||||
"id": 1,
|
||||
"passport": "t1",
|
||||
"password": "25d55ad283aa400af464c76d713c07ad",
|
||||
"nickname": "T11",
|
||||
"create_time": timeStr,
|
||||
})
|
||||
t.Assert(err, ErrorSave)
|
||||
createTable("t_user")
|
||||
defer dropTable("t_user")
|
||||
|
||||
i := 10
|
||||
data := g.Map{
|
||||
"id": i,
|
||||
"passport": fmt.Sprintf(`t%d`, i),
|
||||
"password": fmt.Sprintf(`p%d`, i),
|
||||
"nickname": fmt.Sprintf(`T%d`, i),
|
||||
"create_time": gtime.Now().String(),
|
||||
}
|
||||
_, err := db.Save(ctx, "t_user", data, 10)
|
||||
gtest.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -11,8 +11,6 @@ import (
|
||||
|
||||
"github.com/gogf/gf/v2/container/garray"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
@ -27,9 +25,6 @@ var (
|
||||
configNode gdb.ConfigNode
|
||||
dbDir = gfile.Temp("sqlite")
|
||||
ctx = gctx.New()
|
||||
|
||||
// Error
|
||||
ErrorSave = gerror.NewCode(gcode.CodeNotSupported, `Save operation is not supported by sqlite driver`)
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@ -364,14 +364,58 @@ func Test_Model_Save(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
_, err := db.Model(table).Data(g.Map{
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
NickName string
|
||||
CreateTime *gtime.Time
|
||||
}
|
||||
var (
|
||||
user User
|
||||
count int
|
||||
result sql.Result
|
||||
err error
|
||||
)
|
||||
|
||||
result, err = db.Model(table).Data(g.Map{
|
||||
"id": 1,
|
||||
"passport": "t111",
|
||||
"password": "25d55ad283aa400af464c76d713c07ad",
|
||||
"nickname": "T111",
|
||||
"passport": "CN",
|
||||
"password": "12345678",
|
||||
"nickname": "oldme",
|
||||
"create_time": CreateTime,
|
||||
}).Save()
|
||||
t.Assert(err, ErrorSave)
|
||||
}).OnConflict("id").Save()
|
||||
t.AssertNil(nil)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, 1)
|
||||
|
||||
err = db.Model(table).Scan(&user)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(user.Id, 1)
|
||||
t.Assert(user.Passport, "CN")
|
||||
t.Assert(user.Password, "12345678")
|
||||
t.Assert(user.NickName, "oldme")
|
||||
t.Assert(user.CreateTime.String(), CreateTime)
|
||||
|
||||
_, err = db.Model(table).Data(g.Map{
|
||||
"id": 1,
|
||||
"passport": "CN",
|
||||
"password": "abc123456",
|
||||
"nickname": "to be not to be",
|
||||
"create_time": CreateTime,
|
||||
}).OnConflict("id").Save()
|
||||
t.AssertNil(err)
|
||||
|
||||
err = db.Model(table).Scan(&user)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(user.Passport, "CN")
|
||||
t.Assert(user.Password, "abc123456")
|
||||
t.Assert(user.NickName, "to be not to be")
|
||||
t.Assert(user.CreateTime.String(), CreateTime)
|
||||
|
||||
count, err = db.Model(table).Count()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(count, 1)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -3,8 +3,8 @@ module github.com/gogf/gf/contrib/drivers/sqlitecgo/v2
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.6.3
|
||||
github.com/gogf/gf/v2 v2.6.3
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.6.4
|
||||
github.com/gogf/gf/v2 v2.6.4
|
||||
github.com/mattn/go-sqlite3 v1.14.17
|
||||
)
|
||||
|
||||
|
||||
@ -425,19 +425,22 @@ func Test_DB_BatchInsert_Struct(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_DB_Save(t *testing.T) {
|
||||
table := createInitTable()
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timeStr := gtime.Now().String()
|
||||
_, err := db.Save(ctx, table, g.Map{
|
||||
"id": 1,
|
||||
"passport": "t1",
|
||||
"password": "25d55ad283aa400af464c76d713c07ad",
|
||||
"nickname": "T11",
|
||||
"create_time": timeStr,
|
||||
})
|
||||
t.Assert(err, ErrorSave)
|
||||
createTable("t_user")
|
||||
defer dropTable("t_user")
|
||||
|
||||
i := 10
|
||||
data := g.Map{
|
||||
"id": i,
|
||||
"passport": fmt.Sprintf(`t%d`, i),
|
||||
"password": fmt.Sprintf(`p%d`, i),
|
||||
"nickname": fmt.Sprintf(`T%d`, i),
|
||||
"create_time": gtime.Now().String(),
|
||||
}
|
||||
_, err := db.Save(ctx, "t_user", data, 10)
|
||||
gtest.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -364,14 +364,58 @@ func Test_Model_Save(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
_, err := db.Model(table).Data(g.Map{
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
NickName string
|
||||
CreateTime *gtime.Time
|
||||
}
|
||||
var (
|
||||
user User
|
||||
count int
|
||||
result sql.Result
|
||||
err error
|
||||
)
|
||||
|
||||
result, err = db.Model(table).Data(g.Map{
|
||||
"id": 1,
|
||||
"passport": "t111",
|
||||
"password": "25d55ad283aa400af464c76d713c07ad",
|
||||
"nickname": "T111",
|
||||
"passport": "CN",
|
||||
"password": "12345678",
|
||||
"nickname": "oldme",
|
||||
"create_time": CreateTime,
|
||||
}).Save()
|
||||
t.Assert(err, ErrorSave)
|
||||
}).OnConflict("id").Save()
|
||||
t.AssertNil(nil)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, 1)
|
||||
|
||||
err = db.Model(table).Scan(&user)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(user.Id, 1)
|
||||
t.Assert(user.Passport, "CN")
|
||||
t.Assert(user.Password, "12345678")
|
||||
t.Assert(user.NickName, "oldme")
|
||||
t.Assert(user.CreateTime.String(), CreateTime)
|
||||
|
||||
_, err = db.Model(table).Data(g.Map{
|
||||
"id": 1,
|
||||
"passport": "CN",
|
||||
"password": "abc123456",
|
||||
"nickname": "to be not to be",
|
||||
"create_time": CreateTime,
|
||||
}).OnConflict("id").Save()
|
||||
t.AssertNil(err)
|
||||
|
||||
err = db.Model(table).Scan(&user)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(user.Passport, "CN")
|
||||
t.Assert(user.Password, "abc123456")
|
||||
t.Assert(user.NickName, "to be not to be")
|
||||
t.Assert(user.CreateTime.String(), CreateTime)
|
||||
|
||||
count, err = db.Model(table).Count()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(count, 1)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/nosql/redis/v2
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.6.3
|
||||
github.com/gogf/gf/v2 v2.6.4
|
||||
github.com/redis/go-redis/v9 v9.2.1
|
||||
go.opentelemetry.io/otel v1.14.0
|
||||
go.opentelemetry.io/otel/trace v1.14.0
|
||||
|
||||
@ -44,23 +44,25 @@ func init() {
|
||||
func New(config *gredis.Config) *Redis {
|
||||
fillWithDefaultConfiguration(config)
|
||||
opts := &redis.UniversalOptions{
|
||||
Addrs: gstr.SplitAndTrim(config.Address, ","),
|
||||
Username: config.User,
|
||||
Password: config.Pass,
|
||||
DB: config.Db,
|
||||
MaxRetries: defaultMaxRetries,
|
||||
PoolSize: config.MaxActive,
|
||||
MinIdleConns: config.MinIdle,
|
||||
MaxIdleConns: config.MaxIdle,
|
||||
ConnMaxLifetime: config.MaxConnLifetime,
|
||||
ConnMaxIdleTime: config.IdleTimeout,
|
||||
PoolTimeout: config.WaitTimeout,
|
||||
DialTimeout: config.DialTimeout,
|
||||
ReadTimeout: config.ReadTimeout,
|
||||
WriteTimeout: config.WriteTimeout,
|
||||
MasterName: config.MasterName,
|
||||
TLSConfig: config.TLSConfig,
|
||||
Protocol: config.Protocol,
|
||||
Addrs: gstr.SplitAndTrim(config.Address, ","),
|
||||
Username: config.User,
|
||||
Password: config.Pass,
|
||||
SentinelUsername: config.SentinelUser,
|
||||
SentinelPassword: config.SentinelPass,
|
||||
DB: config.Db,
|
||||
MaxRetries: defaultMaxRetries,
|
||||
PoolSize: config.MaxActive,
|
||||
MinIdleConns: config.MinIdle,
|
||||
MaxIdleConns: config.MaxIdle,
|
||||
ConnMaxLifetime: config.MaxConnLifetime,
|
||||
ConnMaxIdleTime: config.IdleTimeout,
|
||||
PoolTimeout: config.WaitTimeout,
|
||||
DialTimeout: config.DialTimeout,
|
||||
ReadTimeout: config.ReadTimeout,
|
||||
WriteTimeout: config.WriteTimeout,
|
||||
MasterName: config.MasterName,
|
||||
TLSConfig: config.TLSConfig,
|
||||
Protocol: config.Protocol,
|
||||
}
|
||||
|
||||
var client redis.UniversalClient
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/registry/etcd/v2
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.6.3
|
||||
github.com/gogf/gf/v2 v2.6.4
|
||||
go.etcd.io/etcd/client/v3 v3.5.7
|
||||
)
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ module github.com/gogf/gf/contrib/registry/file/v2
|
||||
|
||||
go 1.18
|
||||
|
||||
require github.com/gogf/gf/v2 v2.6.3
|
||||
require github.com/gogf/gf/v2 v2.6.4
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.2.0 // indirect
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/registry/nacos/v2
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.6.3
|
||||
github.com/gogf/gf/v2 v2.6.4
|
||||
github.com/joy999/nacos-sdk-go v0.0.0-20231120071639-10a34b3e7288
|
||||
)
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/registry/polaris/v2
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.6.3
|
||||
github.com/gogf/gf/v2 v2.6.4
|
||||
github.com/polarismesh/polaris-go v1.5.5
|
||||
)
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ go 1.18
|
||||
|
||||
require (
|
||||
github.com/go-zookeeper/zk v1.0.3
|
||||
github.com/gogf/gf/v2 v2.6.3
|
||||
github.com/gogf/gf/v2 v2.6.4
|
||||
golang.org/x/sync v0.4.0
|
||||
)
|
||||
|
||||
|
||||
@ -3,8 +3,8 @@ module github.com/gogf/gf/contrib/rpc/grpcx/v2
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/contrib/registry/file/v2 v2.6.3
|
||||
github.com/gogf/gf/v2 v2.6.3
|
||||
github.com/gogf/gf/contrib/registry/file/v2 v2.6.4
|
||||
github.com/gogf/gf/v2 v2.6.4
|
||||
go.opentelemetry.io/otel v1.14.0
|
||||
go.opentelemetry.io/otel/trace v1.14.0
|
||||
google.golang.org/grpc v1.57.2
|
||||
|
||||
@ -2,7 +2,7 @@ module github.com/gogf/gf/contrib/sdk/httpclient/v2
|
||||
|
||||
go 1.18
|
||||
|
||||
require github.com/gogf/gf/v2 v2.6.3
|
||||
require github.com/gogf/gf/v2 v2.6.4
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.2.0 // indirect
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/trace/jaeger/v2
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.6.3
|
||||
github.com/gogf/gf/v2 v2.6.4
|
||||
go.opentelemetry.io/otel v1.14.0
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.14.0
|
||||
go.opentelemetry.io/otel/sdk v1.14.0
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/trace/otlpgrpc/v2
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.6.3
|
||||
github.com/gogf/gf/v2 v2.6.4
|
||||
go.opentelemetry.io/otel v1.19.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/trace/otlphttp/v2
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.6.3
|
||||
github.com/gogf/gf/v2 v2.6.4
|
||||
go.opentelemetry.io/otel v1.19.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gdb provides ORM features for popular relationship databases.
|
||||
//
|
||||
// TODO use context.Context as required parameter for all DB operations.
|
||||
package gdb
|
||||
|
||||
import (
|
||||
@ -175,6 +177,7 @@ type DB interface {
|
||||
ConvertValueForField(ctx context.Context, fieldType string, fieldValue interface{}) (interface{}, error) // See Core.ConvertValueForField
|
||||
ConvertValueForLocal(ctx context.Context, fieldType string, fieldValue interface{}) (interface{}, error) // See Core.ConvertValueForLocal
|
||||
CheckLocalTypeForField(ctx context.Context, fieldType string, fieldValue interface{}) (LocalType, error) // See Core.CheckLocalTypeForField
|
||||
FormatUpsert(columns []string, list List, option DoInsertOption) (string, error) // See Core.DoFormatUpsert
|
||||
}
|
||||
|
||||
// TX defines the interfaces for ORM transaction operations.
|
||||
@ -320,6 +323,7 @@ type Sql struct {
|
||||
type DoInsertOption struct {
|
||||
OnDuplicateStr string // Custom string for `on duplicated` statement.
|
||||
OnDuplicateMap map[string]interface{} // Custom key-value map from `OnDuplicateEx` function for `on duplicated` statement.
|
||||
OnConflict []string // Custom conflict key of upsert clause, if the database needs it.
|
||||
InsertOption InsertOption // Insert operation in constant value.
|
||||
BatchCount int // Batch count for batch inserting.
|
||||
}
|
||||
|
||||
@ -487,9 +487,12 @@ func (c *Core) DoInsert(ctx context.Context, link Link, table string, list List,
|
||||
keysStr = charL + strings.Join(keys, charR+","+charL) + charR
|
||||
operation = GetInsertOperationByOption(option.InsertOption)
|
||||
)
|
||||
// `ON DUPLICATED...` statement only takes effect on Save operation.
|
||||
// Upsert clause only takes effect on Save operation.
|
||||
if option.InsertOption == InsertOptionSave {
|
||||
onDuplicateStr = c.formatOnDuplicate(keys, option)
|
||||
onDuplicateStr, err = c.db.FormatUpsert(keys, list, option)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var (
|
||||
listLength = len(list)
|
||||
@ -537,49 +540,6 @@ func (c *Core) DoInsert(ctx context.Context, link Link, table string, list List,
|
||||
return batchResult, nil
|
||||
}
|
||||
|
||||
func (c *Core) formatOnDuplicate(columns []string, option DoInsertOption) string {
|
||||
var onDuplicateStr string
|
||||
if option.OnDuplicateStr != "" {
|
||||
onDuplicateStr = option.OnDuplicateStr
|
||||
} else if len(option.OnDuplicateMap) > 0 {
|
||||
for k, v := range option.OnDuplicateMap {
|
||||
if len(onDuplicateStr) > 0 {
|
||||
onDuplicateStr += ","
|
||||
}
|
||||
switch v.(type) {
|
||||
case Raw, *Raw:
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=%s",
|
||||
c.QuoteWord(k),
|
||||
v,
|
||||
)
|
||||
default:
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=VALUES(%s)",
|
||||
c.QuoteWord(k),
|
||||
c.QuoteWord(gconv.String(v)),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, column := range columns {
|
||||
// If it's SAVE operation, do not automatically update the creating time.
|
||||
if c.isSoftCreatedFieldName(column) {
|
||||
continue
|
||||
}
|
||||
if len(onDuplicateStr) > 0 {
|
||||
onDuplicateStr += ","
|
||||
}
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=VALUES(%s)",
|
||||
c.QuoteWord(column),
|
||||
c.QuoteWord(column),
|
||||
)
|
||||
}
|
||||
}
|
||||
return InsertOnDuplicateKeyUpdate + " " + onDuplicateStr
|
||||
}
|
||||
|
||||
// Update does "UPDATE ... " statement for the table.
|
||||
//
|
||||
// The parameter `data` can be type of string/map/gmap/struct/*struct, etc.
|
||||
@ -798,8 +758,8 @@ func (c *Core) GetTablesWithCache() ([]string, error) {
|
||||
return result.Strings(), nil
|
||||
}
|
||||
|
||||
// isSoftCreatedFieldName checks and returns whether given field name is an automatic-filled created time.
|
||||
func (c *Core) isSoftCreatedFieldName(fieldName string) bool {
|
||||
// IsSoftCreatedFieldName checks and returns whether given field name is an automatic-filled created time.
|
||||
func (c *Core) IsSoftCreatedFieldName(fieldName string) bool {
|
||||
if fieldName == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ type ConfigNode struct {
|
||||
User string `json:"user"` // Authentication username.
|
||||
Pass string `json:"pass"` // Authentication password.
|
||||
Name string `json:"name"` // Default used database name.
|
||||
Type string `json:"type"` // Database type: mysql, sqlite, mssql, pgsql, oracle.
|
||||
Type string `json:"type"` // Database type: mysql, mariadb, sqlite, mssql, pgsql, oracle, clickhouse, dm.
|
||||
Link string `json:"link"` // (Optional) Custom link information for all configuration in one single string.
|
||||
Extra string `json:"extra"` // (Optional) Extra configuration according the registered third-party database driver.
|
||||
Role string `json:"role"` // (Optional, "master" in default) Node role, used for master-slave mode: master, slave.
|
||||
|
||||
@ -10,6 +10,7 @@ package gdb
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
@ -352,6 +353,52 @@ func (c *Core) DoPrepare(ctx context.Context, link Link, sql string) (stmt *Stmt
|
||||
return out.Stmt, err
|
||||
}
|
||||
|
||||
// FormatUpsert formats and returns SQL clause part for upsert statement.
|
||||
// In default implements, this function performs upsert statement for MySQL like:
|
||||
// `INSERT INTO ... ON DUPLICATE KEY UPDATE x=VALUES(z),m=VALUES(y)...`
|
||||
func (c *Core) FormatUpsert(columns []string, list List, option DoInsertOption) (string, error) {
|
||||
var onDuplicateStr string
|
||||
if option.OnDuplicateStr != "" {
|
||||
onDuplicateStr = option.OnDuplicateStr
|
||||
} else if len(option.OnDuplicateMap) > 0 {
|
||||
for k, v := range option.OnDuplicateMap {
|
||||
if len(onDuplicateStr) > 0 {
|
||||
onDuplicateStr += ","
|
||||
}
|
||||
switch v.(type) {
|
||||
case Raw, *Raw:
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=%s",
|
||||
c.QuoteWord(k),
|
||||
v,
|
||||
)
|
||||
default:
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=VALUES(%s)",
|
||||
c.QuoteWord(k),
|
||||
c.QuoteWord(gconv.String(v)),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, column := range columns {
|
||||
// If it's SAVE operation, do not automatically update the creating time.
|
||||
if c.IsSoftCreatedFieldName(column) {
|
||||
continue
|
||||
}
|
||||
if len(onDuplicateStr) > 0 {
|
||||
onDuplicateStr += ","
|
||||
}
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=VALUES(%s)",
|
||||
c.QuoteWord(column),
|
||||
c.QuoteWord(column),
|
||||
)
|
||||
}
|
||||
}
|
||||
return InsertOnDuplicateKeyUpdate + " " + onDuplicateStr, nil
|
||||
}
|
||||
|
||||
// RowsToResult converts underlying data record type sql.Rows to Result type.
|
||||
func (c *Core) RowsToResult(ctx context.Context, rows *sql.Rows) (Result, error) {
|
||||
if rows == nil {
|
||||
|
||||
@ -929,3 +929,18 @@ func FormatSqlWithArgs(sql string, args []interface{}) string {
|
||||
})
|
||||
return newQuery
|
||||
}
|
||||
|
||||
// FormatMultiLineSqlToSingle formats sql template string into one line.
|
||||
func FormatMultiLineSqlToSingle(sqlTmp string) string {
|
||||
var err error
|
||||
// format sql template string.
|
||||
sqlTmp, err = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(sqlTmp))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sqlTmp, err = gregex.ReplaceString(`\s{2,}`, " ", gstr.Trim(sqlTmp))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sqlTmp
|
||||
}
|
||||
|
||||
@ -48,8 +48,9 @@ type Model struct {
|
||||
hookHandler HookHandler // Hook functions for model hook 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.
|
||||
onDuplicate interface{} // onDuplicate is used for on Upsert clause.
|
||||
onDuplicateEx interface{} // onDuplicateEx is used for excluding some columns on Upsert clause.
|
||||
onConflict interface{} // onConflict is used for conflict keys on Upsert clause.
|
||||
tableAliasMap map[string]string // Table alias to true table name, usually used in join statements.
|
||||
softTimeOption SoftTimeOption // SoftTimeOption is the option to customize soft time feature for Model.
|
||||
}
|
||||
|
||||
@ -11,7 +11,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
)
|
||||
|
||||
// CacheOption is options for model cache control in query.
|
||||
@ -67,7 +66,6 @@ func (m *Model) getSelectResultFromCache(ctx context.Context, sql string, args .
|
||||
return
|
||||
}
|
||||
var (
|
||||
ok bool
|
||||
cacheItem *selectCacheItem
|
||||
cacheKey = m.makeSelectCacheKey(sql, args...)
|
||||
cacheObj = m.db.GetCache()
|
||||
@ -82,12 +80,7 @@ func (m *Model) getSelectResultFromCache(ctx context.Context, sql string, args .
|
||||
}
|
||||
}()
|
||||
if v, _ := cacheObj.Get(ctx, cacheKey); !v.IsNil() {
|
||||
if cacheItem, ok = v.Val().(*selectCacheItem); ok {
|
||||
// In-memory cache.
|
||||
return cacheItem.Result, nil
|
||||
}
|
||||
// Other cache, it needs conversion.
|
||||
if err = json.UnmarshalUseNumber(v.Bytes(), &cacheItem); err != nil {
|
||||
if err = v.Scan(&cacheItem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cacheItem.Result, nil
|
||||
|
||||
@ -118,8 +118,24 @@ func (m *Model) Data(data ...interface{}) *Model {
|
||||
return model
|
||||
}
|
||||
|
||||
// OnConflict sets the primary key or index when columns conflicts occurs.
|
||||
// It's not necessary for MySQL driver.
|
||||
func (m *Model) OnConflict(onConflict ...interface{}) *Model {
|
||||
if len(onConflict) == 0 {
|
||||
return m
|
||||
}
|
||||
model := m.getModel()
|
||||
if len(onConflict) > 1 {
|
||||
model.onConflict = onConflict
|
||||
} else if len(onConflict) == 1 {
|
||||
model.onConflict = onConflict[0]
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
// OnDuplicate sets the operations when columns conflicts occurs.
|
||||
// In MySQL, this is used for "ON DUPLICATE KEY UPDATE" statement.
|
||||
// In PgSQL, this is used for "ON CONFLICT (id) DO UPDATE SET" statement.
|
||||
// The parameter `onDuplicate` can be type of string/Raw/*Raw/map/slice.
|
||||
// Example:
|
||||
//
|
||||
@ -148,6 +164,7 @@ func (m *Model) OnDuplicate(onDuplicate ...interface{}) *Model {
|
||||
|
||||
// OnDuplicateEx sets the excluding columns for operations when columns conflict occurs.
|
||||
// In MySQL, this is used for "ON DUPLICATE KEY UPDATE" statement.
|
||||
// In PgSQL, this is used for "ON CONFLICT (id) DO UPDATE SET" statement.
|
||||
// The parameter `onDuplicateEx` can be type of string/map/slice.
|
||||
// Example:
|
||||
//
|
||||
@ -320,63 +337,71 @@ func (m *Model) formatDoInsertOption(insertOption InsertOption, columnNames []st
|
||||
InsertOption: insertOption,
|
||||
BatchCount: m.getBatch(),
|
||||
}
|
||||
if insertOption == InsertOptionSave {
|
||||
onDuplicateExKeys, err := m.formatOnDuplicateExKeys(m.onDuplicateEx)
|
||||
if err != nil {
|
||||
return option, err
|
||||
}
|
||||
onDuplicateExKeySet := gset.NewStrSetFrom(onDuplicateExKeys)
|
||||
if m.onDuplicate != nil {
|
||||
switch m.onDuplicate.(type) {
|
||||
case Raw, *Raw:
|
||||
option.OnDuplicateStr = gconv.String(m.onDuplicate)
|
||||
if insertOption != InsertOptionSave {
|
||||
return
|
||||
}
|
||||
|
||||
onConflictKeys, err := m.formatOnConflictKeys(m.onConflict)
|
||||
if err != nil {
|
||||
return option, err
|
||||
}
|
||||
option.OnConflict = onConflictKeys
|
||||
|
||||
onDuplicateExKeys, err := m.formatOnDuplicateExKeys(m.onDuplicateEx)
|
||||
if err != nil {
|
||||
return option, err
|
||||
}
|
||||
onDuplicateExKeySet := gset.NewStrSetFrom(onDuplicateExKeys)
|
||||
if m.onDuplicate != nil {
|
||||
switch m.onDuplicate.(type) {
|
||||
case Raw, *Raw:
|
||||
option.OnDuplicateStr = gconv.String(m.onDuplicate)
|
||||
|
||||
default:
|
||||
reflectInfo := reflection.OriginValueAndKind(m.onDuplicate)
|
||||
switch reflectInfo.OriginKind {
|
||||
case reflect.String:
|
||||
option.OnDuplicateMap = make(map[string]interface{})
|
||||
for _, v := range gstr.SplitAndTrim(reflectInfo.OriginValue.String(), ",") {
|
||||
if onDuplicateExKeySet.Contains(v) {
|
||||
continue
|
||||
}
|
||||
option.OnDuplicateMap[v] = v
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
option.OnDuplicateMap = make(map[string]interface{})
|
||||
for k, v := range gconv.Map(m.onDuplicate) {
|
||||
if onDuplicateExKeySet.Contains(k) {
|
||||
continue
|
||||
}
|
||||
option.OnDuplicateMap[k] = v
|
||||
}
|
||||
|
||||
case reflect.Slice, reflect.Array:
|
||||
option.OnDuplicateMap = make(map[string]interface{})
|
||||
for _, v := range gconv.Strings(m.onDuplicate) {
|
||||
if onDuplicateExKeySet.Contains(v) {
|
||||
continue
|
||||
}
|
||||
option.OnDuplicateMap[v] = v
|
||||
}
|
||||
|
||||
default:
|
||||
reflectInfo := reflection.OriginValueAndKind(m.onDuplicate)
|
||||
switch reflectInfo.OriginKind {
|
||||
case reflect.String:
|
||||
option.OnDuplicateMap = make(map[string]interface{})
|
||||
for _, v := range gstr.SplitAndTrim(reflectInfo.OriginValue.String(), ",") {
|
||||
if onDuplicateExKeySet.Contains(v) {
|
||||
continue
|
||||
}
|
||||
option.OnDuplicateMap[v] = v
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
option.OnDuplicateMap = make(map[string]interface{})
|
||||
for k, v := range gconv.Map(m.onDuplicate) {
|
||||
if onDuplicateExKeySet.Contains(k) {
|
||||
continue
|
||||
}
|
||||
option.OnDuplicateMap[k] = v
|
||||
}
|
||||
|
||||
case reflect.Slice, reflect.Array:
|
||||
option.OnDuplicateMap = make(map[string]interface{})
|
||||
for _, v := range gconv.Strings(m.onDuplicate) {
|
||||
if onDuplicateExKeySet.Contains(v) {
|
||||
continue
|
||||
}
|
||||
option.OnDuplicateMap[v] = v
|
||||
}
|
||||
|
||||
default:
|
||||
return option, gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`unsupported OnDuplicate parameter type "%s"`,
|
||||
reflect.TypeOf(m.onDuplicate),
|
||||
)
|
||||
}
|
||||
return option, gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`unsupported OnDuplicate parameter type "%s"`,
|
||||
reflect.TypeOf(m.onDuplicate),
|
||||
)
|
||||
}
|
||||
} else if onDuplicateExKeySet.Size() > 0 {
|
||||
option.OnDuplicateMap = make(map[string]interface{})
|
||||
for _, v := range columnNames {
|
||||
if onDuplicateExKeySet.Contains(v) {
|
||||
continue
|
||||
}
|
||||
option.OnDuplicateMap[v] = v
|
||||
}
|
||||
} else if onDuplicateExKeySet.Size() > 0 {
|
||||
option.OnDuplicateMap = make(map[string]interface{})
|
||||
for _, v := range columnNames {
|
||||
if onDuplicateExKeySet.Contains(v) {
|
||||
continue
|
||||
}
|
||||
option.OnDuplicateMap[v] = v
|
||||
}
|
||||
}
|
||||
return
|
||||
@ -407,6 +432,28 @@ func (m *Model) formatOnDuplicateExKeys(onDuplicateEx interface{}) ([]string, er
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) formatOnConflictKeys(onConflict interface{}) ([]string, error) {
|
||||
if onConflict == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
reflectInfo := reflection.OriginValueAndKind(onConflict)
|
||||
switch reflectInfo.OriginKind {
|
||||
case reflect.String:
|
||||
return gstr.SplitAndTrim(reflectInfo.OriginValue.String(), ","), nil
|
||||
|
||||
case reflect.Slice, reflect.Array:
|
||||
return gconv.Strings(onConflict), nil
|
||||
|
||||
default:
|
||||
return nil, gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`unsupported onConflict parameter type "%s"`,
|
||||
reflect.TypeOf(onConflict),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) getBatch() int {
|
||||
return m.batch
|
||||
}
|
||||
|
||||
@ -219,7 +219,10 @@ func (m *softTimeMaintainer) getSoftFieldNameAndType(
|
||||
intlog.Error(ctx, err)
|
||||
}
|
||||
if result != nil {
|
||||
var cacheItem = result.Val().(getSoftFieldNameAndTypeCacheItem)
|
||||
var cacheItem getSoftFieldNameAndTypeCacheItem
|
||||
if err = result.Scan(&cacheItem); err != nil {
|
||||
return "", ""
|
||||
}
|
||||
fieldName = cacheItem.FieldName
|
||||
fieldType = cacheItem.FieldType
|
||||
}
|
||||
|
||||
@ -25,6 +25,8 @@ type Config struct {
|
||||
Db int `json:"db"` // Redis db.
|
||||
User string `json:"user"` // Username for AUTH.
|
||||
Pass string `json:"pass"` // Password for AUTH.
|
||||
SentinelUser string `json:"sentinel_user"` // Username for sentinel AUTH.
|
||||
SentinelPass string `json:"sentinel_pass"` // Password for sentinel AUTH.
|
||||
MinIdle int `json:"minIdle"` // Minimum number of connections allowed to be idle (default is 0)
|
||||
MaxIdle int `json:"maxIdle"` // Maximum number of connections allowed to be idle (default is 10)
|
||||
MaxActive int `json:"maxActive"` // Maximum number of connections limit (default is 0 means no limit).
|
||||
|
||||
@ -3,21 +3,21 @@ module github.com/gogf/gf/example
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/contrib/config/apollo/v2 v2.6.3
|
||||
github.com/gogf/gf/contrib/config/consul/v2 v2.6.3
|
||||
github.com/gogf/gf/contrib/config/kubecm/v2 v2.6.3
|
||||
github.com/gogf/gf/contrib/config/nacos/v2 v2.6.3
|
||||
github.com/gogf/gf/contrib/config/polaris/v2 v2.6.3
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.6.3
|
||||
github.com/gogf/gf/contrib/nosql/redis/v2 v2.6.3
|
||||
github.com/gogf/gf/contrib/registry/etcd/v2 v2.6.3
|
||||
github.com/gogf/gf/contrib/registry/file/v2 v2.6.3
|
||||
github.com/gogf/gf/contrib/registry/nacos/v2 v2.6.3
|
||||
github.com/gogf/gf/contrib/registry/polaris/v2 v2.6.3
|
||||
github.com/gogf/gf/contrib/rpc/grpcx/v2 v2.6.3
|
||||
github.com/gogf/gf/contrib/trace/otlpgrpc/v2 v2.6.3
|
||||
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.6.3
|
||||
github.com/gogf/gf/v2 v2.6.3
|
||||
github.com/gogf/gf/contrib/config/apollo/v2 v2.6.4
|
||||
github.com/gogf/gf/contrib/config/consul/v2 v2.6.4
|
||||
github.com/gogf/gf/contrib/config/kubecm/v2 v2.6.4
|
||||
github.com/gogf/gf/contrib/config/nacos/v2 v2.6.4
|
||||
github.com/gogf/gf/contrib/config/polaris/v2 v2.6.4
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.6.4
|
||||
github.com/gogf/gf/contrib/nosql/redis/v2 v2.6.4
|
||||
github.com/gogf/gf/contrib/registry/etcd/v2 v2.6.4
|
||||
github.com/gogf/gf/contrib/registry/file/v2 v2.6.4
|
||||
github.com/gogf/gf/contrib/registry/nacos/v2 v2.6.4
|
||||
github.com/gogf/gf/contrib/registry/polaris/v2 v2.6.4
|
||||
github.com/gogf/gf/contrib/rpc/grpcx/v2 v2.6.4
|
||||
github.com/gogf/gf/contrib/trace/otlpgrpc/v2 v2.6.4
|
||||
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.6.4
|
||||
github.com/gogf/gf/v2 v2.6.4
|
||||
github.com/hashicorp/consul/api v1.24.0
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2
|
||||
github.com/nacos-group/nacos-sdk-go/v2 v2.2.5
|
||||
|
||||
@ -107,6 +107,11 @@ func internalMiddlewareServerTracing(r *Request) {
|
||||
// Continue executing.
|
||||
r.Middleware.Next()
|
||||
|
||||
// parse after set route as span name
|
||||
if r.Router.Uri != defaultMiddlewarePattern || r.Router.RegNames != nil {
|
||||
span.SetName(r.Router.Uri)
|
||||
}
|
||||
|
||||
// Error logging.
|
||||
if err = r.GetError(); err != nil {
|
||||
span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, err))
|
||||
|
||||
@ -8,13 +8,12 @@ package gproc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
// SigHandler defines a function type for signal handling.
|
||||
@ -23,6 +22,8 @@ type SigHandler func(sig os.Signal)
|
||||
var (
|
||||
// Use internal variable to guarantee concurrent safety
|
||||
// when multiple Listen happen.
|
||||
listenOnce = sync.Once{}
|
||||
waitChan = make(chan struct{})
|
||||
signalChan = make(chan os.Signal, 1)
|
||||
signalHandlerMu sync.Mutex
|
||||
signalHandlerMap = make(map[os.Signal][]SigHandler)
|
||||
@ -48,6 +49,7 @@ func AddSigHandler(handler SigHandler, signals ...os.Signal) {
|
||||
for _, sig := range signals {
|
||||
signalHandlerMap[sig] = append(signalHandlerMap[sig], handler)
|
||||
}
|
||||
notifySignals()
|
||||
}
|
||||
|
||||
// AddSigHandlerShutdown adds custom signal handler for shutdown signals:
|
||||
@ -64,17 +66,26 @@ func AddSigHandlerShutdown(handler ...SigHandler) {
|
||||
signalHandlerMap[sig] = append(signalHandlerMap[sig], h)
|
||||
}
|
||||
}
|
||||
notifySignals()
|
||||
}
|
||||
|
||||
// Listen blocks and does signal listening and handling.
|
||||
func Listen() {
|
||||
listenOnce.Do(func() {
|
||||
go listen()
|
||||
})
|
||||
|
||||
<-waitChan
|
||||
}
|
||||
|
||||
func listen() {
|
||||
defer close(waitChan)
|
||||
|
||||
var (
|
||||
signals = getHandlerSignals()
|
||||
ctx = context.Background()
|
||||
wg = sync.WaitGroup{}
|
||||
sig os.Signal
|
||||
ctx = context.Background()
|
||||
wg = sync.WaitGroup{}
|
||||
sig os.Signal
|
||||
)
|
||||
signal.Notify(signalChan, signals...)
|
||||
for {
|
||||
sig = <-signalChan
|
||||
intlog.Printf(ctx, `signal received: %s`, sig.String())
|
||||
@ -108,14 +119,12 @@ func Listen() {
|
||||
}
|
||||
}
|
||||
|
||||
func getHandlerSignals() []os.Signal {
|
||||
signalHandlerMu.Lock()
|
||||
defer signalHandlerMu.Unlock()
|
||||
func notifySignals() {
|
||||
var signals = make([]os.Signal, 0)
|
||||
for s := range signalHandlerMap {
|
||||
signals = append(signals, s)
|
||||
}
|
||||
return signals
|
||||
signal.Notify(signalChan, signals...)
|
||||
}
|
||||
|
||||
func getHandlersBySignal(sig os.Signal) []SigHandler {
|
||||
|
||||
103
os/gproc/gproc_z_signal_test.go
Normal file
103
os/gproc/gproc_z_signal_test.go
Normal file
@ -0,0 +1,103 @@
|
||||
// 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 gproc
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"os"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test_Signal(t *testing.T) {
|
||||
go Listen()
|
||||
|
||||
// non shutdown signal
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
sigRec := make(chan os.Signal, 1)
|
||||
AddSigHandler(func(sig os.Signal) {
|
||||
sigRec <- sig
|
||||
}, syscall.SIGUSR1, syscall.SIGUSR2)
|
||||
|
||||
sendSignal(syscall.SIGUSR1)
|
||||
select {
|
||||
case s := <-sigRec:
|
||||
t.AssertEQ(s, syscall.SIGUSR1)
|
||||
t.AssertEQ(false, isWaitChClosed())
|
||||
case <-time.After(time.Second):
|
||||
t.Error("signal SIGUSR1 handler timeout")
|
||||
}
|
||||
|
||||
sendSignal(syscall.SIGUSR2)
|
||||
select {
|
||||
case s := <-sigRec:
|
||||
t.AssertEQ(s, syscall.SIGUSR2)
|
||||
t.AssertEQ(false, isWaitChClosed())
|
||||
case <-time.After(time.Second):
|
||||
t.Error("signal SIGUSR2 handler timeout")
|
||||
}
|
||||
|
||||
sendSignal(syscall.SIGHUP)
|
||||
select {
|
||||
case <-sigRec:
|
||||
t.Error("signal SIGHUP should not be listen")
|
||||
case <-time.After(time.Millisecond * 100):
|
||||
}
|
||||
|
||||
// multiple listen
|
||||
go Listen()
|
||||
go Listen()
|
||||
sendSignal(syscall.SIGUSR1)
|
||||
cnt := 0
|
||||
timeout := time.After(time.Second)
|
||||
for {
|
||||
select {
|
||||
case <-sigRec:
|
||||
cnt++
|
||||
case <-timeout:
|
||||
if cnt == 0 {
|
||||
t.Error("signal SIGUSR2 handler timeout")
|
||||
}
|
||||
if cnt != 1 {
|
||||
t.Error("multi Listen() repetitive execution")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// test shutdown signal
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
sigRec := make(chan os.Signal, 1)
|
||||
AddSigHandlerShutdown(func(sig os.Signal) {
|
||||
sigRec <- sig
|
||||
})
|
||||
|
||||
sendSignal(syscall.SIGTERM)
|
||||
select {
|
||||
case s := <-sigRec:
|
||||
t.AssertEQ(s, syscall.SIGTERM)
|
||||
t.AssertEQ(true, isWaitChClosed())
|
||||
case <-time.After(time.Second):
|
||||
t.Error("signal SIGUSR2 handler timeout")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func sendSignal(sig os.Signal) {
|
||||
signalChan <- sig
|
||||
}
|
||||
|
||||
func isWaitChClosed() bool {
|
||||
select {
|
||||
case _, ok := <-waitChan:
|
||||
return !ok
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -11,14 +11,13 @@ import (
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
)
|
||||
|
||||
// MapToMap converts any map type variable `params` to another map type variable `pointer`
|
||||
// using reflect.
|
||||
// See doMapToMap.
|
||||
func MapToMap(params interface{}, pointer interface{}, mapping ...map[string]string) error {
|
||||
return doMapToMap(params, pointer, mapping...)
|
||||
return Scan(params, pointer, mapping...)
|
||||
}
|
||||
|
||||
// doMapToMap converts any map type variable `params` to another map type variable `pointer`.
|
||||
@ -32,29 +31,6 @@ func MapToMap(params interface{}, pointer interface{}, mapping ...map[string]str
|
||||
// The optional parameter `mapping` is used for struct attribute to map key mapping, which makes
|
||||
// sense only if the items of original map `params` is type struct.
|
||||
func doMapToMap(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
|
||||
// If given `params` is JSON, it then uses json.Unmarshal doing the converting.
|
||||
switch r := params.(type) {
|
||||
case []byte:
|
||||
if json.Valid(r) {
|
||||
if rv, ok := pointer.(reflect.Value); ok {
|
||||
if rv.Kind() == reflect.Ptr {
|
||||
return json.UnmarshalUseNumber(r, rv.Interface())
|
||||
}
|
||||
} else {
|
||||
return json.UnmarshalUseNumber(r, pointer)
|
||||
}
|
||||
}
|
||||
case string:
|
||||
if paramsBytes := []byte(r); json.Valid(paramsBytes) {
|
||||
if rv, ok := pointer.(reflect.Value); ok {
|
||||
if rv.Kind() == reflect.Ptr {
|
||||
return json.UnmarshalUseNumber(paramsBytes, rv.Interface())
|
||||
}
|
||||
} else {
|
||||
return json.UnmarshalUseNumber(paramsBytes, pointer)
|
||||
}
|
||||
}
|
||||
}
|
||||
var (
|
||||
paramsRv reflect.Value
|
||||
paramsKind reflect.Kind
|
||||
@ -92,7 +68,11 @@ func doMapToMap(params interface{}, pointer interface{}, mapping ...map[string]s
|
||||
pointerKind = pointerRv.Kind()
|
||||
}
|
||||
if pointerKind != reflect.Map {
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "pointer should be type of *map, but got:%s", pointerKind)
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`destination pointer should be type of *map, but got: %s`,
|
||||
pointerKind,
|
||||
)
|
||||
}
|
||||
defer func() {
|
||||
// Catch the panic, especially the reflection operation panics.
|
||||
@ -119,7 +99,9 @@ func doMapToMap(params interface{}, pointer interface{}, mapping ...map[string]s
|
||||
mapValue := reflect.New(pointerValueType).Elem()
|
||||
switch pointerValueKind {
|
||||
case reflect.Map, reflect.Struct:
|
||||
if err = doStruct(paramsRv.MapIndex(key).Interface(), mapValue, keyToAttributeNameMapping, ""); err != nil {
|
||||
if err = doStruct(
|
||||
paramsRv.MapIndex(key).Interface(), mapValue, keyToAttributeNameMapping, "",
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
|
||||
@ -11,13 +11,12 @@ import (
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
)
|
||||
|
||||
// MapToMaps converts any slice type variable `params` to another map slice type variable `pointer`.
|
||||
// See doMapToMaps.
|
||||
func MapToMaps(params interface{}, pointer interface{}, mapping ...map[string]string) error {
|
||||
return doMapToMaps(params, pointer, mapping...)
|
||||
return Scan(params, pointer, mapping...)
|
||||
}
|
||||
|
||||
// doMapToMaps converts any map type variable `params` to another map slice variable `pointer`.
|
||||
@ -29,29 +28,6 @@ func MapToMaps(params interface{}, pointer interface{}, mapping ...map[string]st
|
||||
// The optional parameter `mapping` is used for struct attribute to map key mapping, which makes
|
||||
// sense only if the item of `params` is type struct.
|
||||
func doMapToMaps(params interface{}, pointer interface{}, paramKeyToAttrMap ...map[string]string) (err error) {
|
||||
// If given `params` is JSON, it then uses json.Unmarshal doing the converting.
|
||||
switch r := params.(type) {
|
||||
case []byte:
|
||||
if json.Valid(r) {
|
||||
if rv, ok := pointer.(reflect.Value); ok {
|
||||
if rv.Kind() == reflect.Ptr {
|
||||
return json.UnmarshalUseNumber(r, rv.Interface())
|
||||
}
|
||||
} else {
|
||||
return json.UnmarshalUseNumber(r, pointer)
|
||||
}
|
||||
}
|
||||
case string:
|
||||
if paramsBytes := []byte(r); json.Valid(paramsBytes) {
|
||||
if rv, ok := pointer.(reflect.Value); ok {
|
||||
if rv.Kind() == reflect.Ptr {
|
||||
return json.UnmarshalUseNumber(paramsBytes, rv.Interface())
|
||||
}
|
||||
} else {
|
||||
return json.UnmarshalUseNumber(paramsBytes, pointer)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Params and its element type check.
|
||||
var (
|
||||
paramsRv reflect.Value
|
||||
@ -68,7 +44,10 @@ func doMapToMaps(params interface{}, pointer interface{}, paramKeyToAttrMap ...m
|
||||
paramsKind = paramsRv.Kind()
|
||||
}
|
||||
if paramsKind != reflect.Array && paramsKind != reflect.Slice {
|
||||
return gerror.NewCode(gcode.CodeInvalidParameter, "params should be type of slice, eg: []map/[]*map/[]struct/[]*struct")
|
||||
return gerror.NewCode(
|
||||
gcode.CodeInvalidParameter,
|
||||
"params should be type of slice, example: []map/[]*map/[]struct/[]*struct",
|
||||
)
|
||||
}
|
||||
var (
|
||||
paramsElem = paramsRv.Type().Elem()
|
||||
@ -78,8 +57,14 @@ func doMapToMaps(params interface{}, pointer interface{}, paramKeyToAttrMap ...m
|
||||
paramsElem = paramsElem.Elem()
|
||||
paramsElemKind = paramsElem.Kind()
|
||||
}
|
||||
if paramsElemKind != reflect.Map && paramsElemKind != reflect.Struct && paramsElemKind != reflect.Interface {
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "params element should be type of map/*map/struct/*struct, but got: %s", paramsElemKind)
|
||||
if paramsElemKind != reflect.Map &&
|
||||
paramsElemKind != reflect.Struct &&
|
||||
paramsElemKind != reflect.Interface {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
"params element should be type of map/*map/struct/*struct, but got: %s",
|
||||
paramsElemKind,
|
||||
)
|
||||
}
|
||||
// Empty slice, no need continue.
|
||||
if paramsRv.Len() == 0 {
|
||||
|
||||
@ -7,91 +7,98 @@
|
||||
package gconv
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"reflect"
|
||||
|
||||
"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/os/gstructs"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
)
|
||||
|
||||
// Scan automatically checks the type of `pointer` and converts `params` to `pointer`. It supports `pointer`
|
||||
// with type of `*map/*[]map/*[]*map/*struct/**struct/*[]struct/*[]*struct` for converting.
|
||||
// Scan automatically checks the type of `pointer` and converts `params` to `pointer`.
|
||||
// It supports `pointer` in type of `*map/*[]map/*[]*map/*struct/**struct/*[]struct/*[]*struct` for converting.
|
||||
//
|
||||
// It calls function `doMapToMap` internally if `pointer` is type of *map for converting.
|
||||
// It calls function `doMapToMaps` internally if `pointer` is type of *[]map/*[]*map for converting.
|
||||
// It calls function `doStruct` internally if `pointer` is type of *struct/**struct for converting.
|
||||
// It calls function `doStructs` internally if `pointer` is type of *[]struct/*[]*struct for converting.
|
||||
func Scan(params interface{}, pointer interface{}, paramKeyToAttrMap ...map[string]string) (err error) {
|
||||
var (
|
||||
pointerType reflect.Type
|
||||
pointerKind reflect.Kind
|
||||
pointerValue reflect.Value
|
||||
)
|
||||
if v, ok := pointer.(reflect.Value); ok {
|
||||
pointerValue = v
|
||||
pointerType = v.Type()
|
||||
} else {
|
||||
pointerValue = reflect.ValueOf(pointer)
|
||||
pointerType = reflect.TypeOf(pointer) // Do not use pointerValue.Type() as pointerValue might be zero.
|
||||
// TODO change `paramKeyToAttrMap` to `ScanOption` to be more scalable; add `DeepCopy` option for `ScanOption`.
|
||||
func Scan(srcValue interface{}, dstPointer interface{}, paramKeyToAttrMap ...map[string]string) (err error) {
|
||||
if srcValue == nil {
|
||||
// If `srcValue` is nil, no conversion.
|
||||
return nil
|
||||
}
|
||||
if dstPointer == nil {
|
||||
return gerror.NewCode(
|
||||
gcode.CodeInvalidParameter,
|
||||
`destination pointer should not be nil`,
|
||||
)
|
||||
}
|
||||
|
||||
if pointerType == nil {
|
||||
return gerror.NewCode(gcode.CodeInvalidParameter, "parameter pointer should not be nil")
|
||||
// json converting check.
|
||||
ok, err := doConvertWithJsonCheck(srcValue, dstPointer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pointerKind = pointerType.Kind()
|
||||
if pointerKind != reflect.Ptr {
|
||||
if pointerValue.CanAddr() {
|
||||
pointerValue = pointerValue.Addr()
|
||||
pointerType = pointerValue.Type()
|
||||
pointerKind = pointerType.Kind()
|
||||
} else {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
"params should be type of pointer, but got type: %v",
|
||||
pointerType,
|
||||
)
|
||||
}
|
||||
}
|
||||
// Direct assignment checks!
|
||||
var (
|
||||
paramsType reflect.Type
|
||||
paramsValue reflect.Value
|
||||
)
|
||||
if v, ok := params.(reflect.Value); ok {
|
||||
paramsValue = v
|
||||
paramsType = paramsValue.Type()
|
||||
} else {
|
||||
paramsValue = reflect.ValueOf(params)
|
||||
paramsType = reflect.TypeOf(params) // Do not use paramsValue.Type() as paramsValue might be zero.
|
||||
}
|
||||
// If `params` and `pointer` are the same type, the do directly assignment.
|
||||
// For performance enhancement purpose.
|
||||
var (
|
||||
pointerValueElem = pointerValue.Elem()
|
||||
)
|
||||
if pointerValueElem.CanSet() && paramsType == pointerValueElem.Type() {
|
||||
pointerValueElem.Set(paramsValue)
|
||||
if ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Converting.
|
||||
var (
|
||||
pointerElem = pointerType.Elem()
|
||||
pointerElemKind = pointerElem.Kind()
|
||||
keyToAttributeNameMapping map[string]string
|
||||
dstPointerReflectType reflect.Type
|
||||
dstPointerReflectValue reflect.Value
|
||||
)
|
||||
if v, ok := dstPointer.(reflect.Value); ok {
|
||||
dstPointerReflectValue = v
|
||||
dstPointerReflectType = v.Type()
|
||||
} else {
|
||||
dstPointerReflectValue = reflect.ValueOf(dstPointer)
|
||||
// do not use dstPointerReflectValue.Type() as dstPointerReflectValue might be zero.
|
||||
dstPointerReflectType = reflect.TypeOf(dstPointer)
|
||||
}
|
||||
|
||||
// pointer kind validation.
|
||||
var dstPointerReflectKind = dstPointerReflectType.Kind()
|
||||
if dstPointerReflectKind != reflect.Ptr {
|
||||
if dstPointerReflectValue.CanAddr() {
|
||||
dstPointerReflectValue = dstPointerReflectValue.Addr()
|
||||
dstPointerReflectType = dstPointerReflectValue.Type()
|
||||
dstPointerReflectKind = dstPointerReflectType.Kind()
|
||||
} else {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`destination pointer should be type of pointer, but got type: %v`,
|
||||
dstPointerReflectType,
|
||||
)
|
||||
}
|
||||
}
|
||||
// direct assignment checks!
|
||||
var srcValueReflectValue reflect.Value
|
||||
if v, ok := srcValue.(reflect.Value); ok {
|
||||
srcValueReflectValue = v
|
||||
} else {
|
||||
srcValueReflectValue = reflect.ValueOf(srcValue)
|
||||
}
|
||||
// if `srcValue` and `dstPointer` are the same type, the do directly assignment.
|
||||
// For performance enhancement purpose.
|
||||
var dstPointerReflectValueElem = dstPointerReflectValue.Elem()
|
||||
// if `srcValue` and `dstPointer` are the same type, the do directly assignment.
|
||||
// for performance enhancement purpose.
|
||||
if ok = doConvertWithTypeCheck(srcValueReflectValue, dstPointerReflectValueElem); ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// do the converting.
|
||||
var (
|
||||
dstPointerReflectTypeElem = dstPointerReflectType.Elem()
|
||||
dstPointerReflectTypeElemKind = dstPointerReflectTypeElem.Kind()
|
||||
keyToAttributeNameMapping map[string]string
|
||||
)
|
||||
if len(paramKeyToAttrMap) > 0 {
|
||||
keyToAttributeNameMapping = paramKeyToAttrMap[0]
|
||||
}
|
||||
switch pointerElemKind {
|
||||
switch dstPointerReflectTypeElemKind {
|
||||
case reflect.Map:
|
||||
return doMapToMap(params, pointer, paramKeyToAttrMap...)
|
||||
return doMapToMap(srcValue, dstPointer, paramKeyToAttrMap...)
|
||||
|
||||
case reflect.Array, reflect.Slice:
|
||||
var (
|
||||
sliceElem = pointerElem.Elem()
|
||||
sliceElem = dstPointerReflectTypeElem.Elem()
|
||||
sliceElemKind = sliceElem.Kind()
|
||||
)
|
||||
for sliceElemKind == reflect.Ptr {
|
||||
@ -99,432 +106,100 @@ func Scan(params interface{}, pointer interface{}, paramKeyToAttrMap ...map[stri
|
||||
sliceElemKind = sliceElem.Kind()
|
||||
}
|
||||
if sliceElemKind == reflect.Map {
|
||||
return doMapToMaps(params, pointer, paramKeyToAttrMap...)
|
||||
return doMapToMaps(srcValue, dstPointer, paramKeyToAttrMap...)
|
||||
}
|
||||
return doStructs(params, pointer, keyToAttributeNameMapping, "")
|
||||
return doStructs(srcValue, dstPointer, keyToAttributeNameMapping, "")
|
||||
|
||||
default:
|
||||
return doStruct(params, pointer, keyToAttributeNameMapping, "")
|
||||
return doStruct(srcValue, dstPointer, keyToAttributeNameMapping, "")
|
||||
}
|
||||
}
|
||||
|
||||
// ScanList converts `structSlice` to struct slice which contains other complex struct attributes.
|
||||
// Note that the parameter `structSlicePointer` should be type of *[]struct/*[]*struct.
|
||||
//
|
||||
// Usage example 1: Normal attribute struct relation:
|
||||
//
|
||||
// type EntityUser struct {
|
||||
// Uid int
|
||||
// Name string
|
||||
// }
|
||||
//
|
||||
// type EntityUserDetail struct {
|
||||
// Uid int
|
||||
// Address string
|
||||
// }
|
||||
//
|
||||
// type EntityUserScores struct {
|
||||
// Id int
|
||||
// Uid int
|
||||
// Score int
|
||||
// Course string
|
||||
// }
|
||||
//
|
||||
// type Entity struct {
|
||||
// User *EntityUser
|
||||
// UserDetail *EntityUserDetail
|
||||
// UserScores []*EntityUserScores
|
||||
// }
|
||||
//
|
||||
// var users []*Entity
|
||||
// var userRecords = EntityUser{Uid: 1, Name:"john"}
|
||||
// var detailRecords = EntityUser{Uid: 1, Address: "chengdu"}
|
||||
// var scoresRecords = EntityUser{Id: 1, Uid: 1, Score: 100, Course: "math"}
|
||||
// ScanList(userRecords, &users, "User")
|
||||
// ScanList(userRecords, &users, "User", "uid")
|
||||
// ScanList(detailRecords, &users, "UserDetail", "User", "uid:Uid")
|
||||
// ScanList(scoresRecords, &users, "UserScores", "User", "uid:Uid")
|
||||
// ScanList(scoresRecords, &users, "UserScores", "User", "uid")
|
||||
//
|
||||
// Usage example 2: Embedded attribute struct relation:
|
||||
//
|
||||
// type EntityUser struct {
|
||||
// Uid int
|
||||
// Name string
|
||||
// }
|
||||
//
|
||||
// type EntityUserDetail struct {
|
||||
// Uid int
|
||||
// Address string
|
||||
// }
|
||||
//
|
||||
// type EntityUserScores struct {
|
||||
// Id int
|
||||
// Uid int
|
||||
// Score int
|
||||
// }
|
||||
//
|
||||
// type Entity struct {
|
||||
// EntityUser
|
||||
// UserDetail EntityUserDetail
|
||||
// UserScores []EntityUserScores
|
||||
// }
|
||||
//
|
||||
// var userRecords = EntityUser{Uid: 1, Name:"john"}
|
||||
// var detailRecords = EntityUser{Uid: 1, Address: "chengdu"}
|
||||
// var scoresRecords = EntityUser{Id: 1, Uid: 1, Score: 100, Course: "math"}
|
||||
// ScanList(userRecords, &users)
|
||||
// ScanList(detailRecords, &users, "UserDetail", "uid")
|
||||
// ScanList(scoresRecords, &users, "UserScores", "uid")
|
||||
//
|
||||
// The parameters "User/UserDetail/UserScores" in the example codes specify the target attribute struct
|
||||
// that current result will be bound to.
|
||||
//
|
||||
// The "uid" in the example codes is the table field name of the result, and the "Uid" is the relational
|
||||
// struct attribute name - not the attribute name of the bound to target. In the example codes, it's attribute
|
||||
// name "Uid" of "User" of entity "Entity". It automatically calculates the HasOne/HasMany relationship with
|
||||
// given `relation` parameter.
|
||||
//
|
||||
// See the example or unit testing cases for clear understanding for this function.
|
||||
func ScanList(structSlice interface{}, structSlicePointer interface{}, bindToAttrName string, relationAttrNameAndFields ...string) (err error) {
|
||||
var (
|
||||
relationAttrName string
|
||||
relationFields string
|
||||
)
|
||||
switch len(relationAttrNameAndFields) {
|
||||
case 2:
|
||||
relationAttrName = relationAttrNameAndFields[0]
|
||||
relationFields = relationAttrNameAndFields[1]
|
||||
case 1:
|
||||
relationFields = relationAttrNameAndFields[0]
|
||||
func doConvertWithTypeCheck(srcValueReflectValue, dstPointerReflectValueElem reflect.Value) (ok bool) {
|
||||
if !dstPointerReflectValueElem.IsValid() || !srcValueReflectValue.IsValid() {
|
||||
return false
|
||||
}
|
||||
switch {
|
||||
// Example:
|
||||
// UploadFile => UploadFile
|
||||
// []UploadFile => []UploadFile
|
||||
// *UploadFile => *UploadFile
|
||||
// *[]UploadFile => *[]UploadFile
|
||||
// map => map
|
||||
// []map => []map
|
||||
// *[]map => *[]map
|
||||
case dstPointerReflectValueElem.Type() == srcValueReflectValue.Type():
|
||||
dstPointerReflectValueElem.Set(srcValueReflectValue)
|
||||
return true
|
||||
|
||||
// Example:
|
||||
// UploadFile => *UploadFile
|
||||
// []UploadFile => *[]UploadFile
|
||||
// map => *map
|
||||
// []map => *[]map
|
||||
case dstPointerReflectValueElem.Kind() == reflect.Ptr &&
|
||||
dstPointerReflectValueElem.Elem().IsValid() &&
|
||||
dstPointerReflectValueElem.Elem().Type() == srcValueReflectValue.Type():
|
||||
dstPointerReflectValueElem.Elem().Set(srcValueReflectValue)
|
||||
return true
|
||||
|
||||
// Example:
|
||||
// *UploadFile => UploadFile
|
||||
// *[]UploadFile => []UploadFile
|
||||
// *map => map
|
||||
// *[]map => []map
|
||||
case srcValueReflectValue.Kind() == reflect.Ptr &&
|
||||
srcValueReflectValue.Elem().IsValid() &&
|
||||
dstPointerReflectValueElem.Type() == srcValueReflectValue.Elem().Type():
|
||||
dstPointerReflectValueElem.Set(srcValueReflectValue.Elem())
|
||||
return true
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return doScanList(structSlice, structSlicePointer, bindToAttrName, relationAttrName, relationFields)
|
||||
}
|
||||
|
||||
// doScanList converts `structSlice` to struct slice which contains other complex struct attributes recursively.
|
||||
// Note that the parameter `structSlicePointer` should be type of *[]struct/*[]*struct.
|
||||
func doScanList(
|
||||
structSlice interface{}, structSlicePointer interface{}, bindToAttrName, relationAttrName, relationFields string,
|
||||
) (err error) {
|
||||
var (
|
||||
maps = Maps(structSlice)
|
||||
)
|
||||
if len(maps) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Necessary checks for parameters.
|
||||
if bindToAttrName == "" {
|
||||
return gerror.NewCode(gcode.CodeInvalidParameter, `bindToAttrName should not be empty`)
|
||||
}
|
||||
|
||||
if relationAttrName == "." {
|
||||
relationAttrName = ""
|
||||
}
|
||||
|
||||
var (
|
||||
reflectValue = reflect.ValueOf(structSlicePointer)
|
||||
reflectKind = reflectValue.Kind()
|
||||
)
|
||||
if reflectKind == reflect.Interface {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
if reflectKind != reflect.Ptr {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
"structSlicePointer should be type of *[]struct/*[]*struct, but got: %v",
|
||||
reflectKind,
|
||||
)
|
||||
}
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
if reflectKind != reflect.Slice && reflectKind != reflect.Array {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
"structSlicePointer should be type of *[]struct/*[]*struct, but got: %v",
|
||||
reflectKind,
|
||||
)
|
||||
}
|
||||
length := len(maps)
|
||||
if length == 0 {
|
||||
// The pointed slice is not empty.
|
||||
if reflectValue.Len() > 0 {
|
||||
// It here checks if it has struct item, which is already initialized.
|
||||
// It then returns error to warn the developer its empty and no conversion.
|
||||
if v := reflectValue.Index(0); v.Kind() != reflect.Ptr {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
}
|
||||
// Do nothing for empty struct slice.
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
arrayValue reflect.Value // Like: []*Entity
|
||||
arrayItemType reflect.Type // Like: *Entity
|
||||
reflectType = reflect.TypeOf(structSlicePointer)
|
||||
)
|
||||
if reflectValue.Len() > 0 {
|
||||
arrayValue = reflectValue
|
||||
} else {
|
||||
arrayValue = reflect.MakeSlice(reflectType.Elem(), length, length)
|
||||
}
|
||||
|
||||
// Slice element item.
|
||||
arrayItemType = arrayValue.Index(0).Type()
|
||||
|
||||
// Relation variables.
|
||||
var (
|
||||
relationDataMap map[string]interface{}
|
||||
relationFromFieldName string // Eg: relationKV: id:uid -> id
|
||||
relationBindToFieldName string // Eg: relationKV: id:uid -> uid
|
||||
)
|
||||
if len(relationFields) > 0 {
|
||||
// The relation key string of table field name and attribute name
|
||||
// can be joined with char '=' or ':'.
|
||||
array := utils.SplitAndTrim(relationFields, "=")
|
||||
if len(array) == 1 {
|
||||
// Compatible with old splitting char ':'.
|
||||
array = utils.SplitAndTrim(relationFields, ":")
|
||||
}
|
||||
if len(array) == 1 {
|
||||
// The relation names are the same.
|
||||
array = []string{relationFields, relationFields}
|
||||
}
|
||||
if len(array) == 2 {
|
||||
// Defined table field to relation attribute name.
|
||||
// Like:
|
||||
// uid:Uid
|
||||
// uid:UserId
|
||||
relationFromFieldName = array[0]
|
||||
relationBindToFieldName = array[1]
|
||||
if key, _ := utils.MapPossibleItemByKey(maps[0], relationFromFieldName); key == "" {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`cannot find possible related table field name "%s" from given relation fields "%s"`,
|
||||
relationFromFieldName,
|
||||
relationFields,
|
||||
)
|
||||
} else {
|
||||
relationFromFieldName = key
|
||||
}
|
||||
} else {
|
||||
return gerror.NewCode(
|
||||
gcode.CodeInvalidParameter,
|
||||
`parameter relationKV should be format of "ResultFieldName:BindToAttrName"`,
|
||||
)
|
||||
}
|
||||
if relationFromFieldName != "" {
|
||||
// Note that the value might be type of slice.
|
||||
relationDataMap = utils.ListToMapByKey(maps, relationFromFieldName)
|
||||
}
|
||||
if len(relationDataMap) == 0 {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`cannot find the relation data map, maybe invalid relation fields given "%v"`,
|
||||
relationFields,
|
||||
)
|
||||
}
|
||||
}
|
||||
// Bind to target attribute.
|
||||
var (
|
||||
ok bool
|
||||
bindToAttrValue reflect.Value
|
||||
bindToAttrKind reflect.Kind
|
||||
bindToAttrType reflect.Type
|
||||
bindToAttrField reflect.StructField
|
||||
)
|
||||
if arrayItemType.Kind() == reflect.Ptr {
|
||||
if bindToAttrField, ok = arrayItemType.Elem().FieldByName(bindToAttrName); !ok {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`,
|
||||
bindToAttrName,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if bindToAttrField, ok = arrayItemType.FieldByName(bindToAttrName); !ok {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`,
|
||||
bindToAttrName,
|
||||
)
|
||||
}
|
||||
}
|
||||
bindToAttrType = bindToAttrField.Type
|
||||
bindToAttrKind = bindToAttrType.Kind()
|
||||
|
||||
// Bind to relation conditions.
|
||||
var (
|
||||
relationFromAttrValue reflect.Value
|
||||
relationFromAttrField reflect.Value
|
||||
relationBindToFieldNameChecked bool
|
||||
)
|
||||
for i := 0; i < arrayValue.Len(); i++ {
|
||||
arrayElemValue := arrayValue.Index(i)
|
||||
// The FieldByName should be called on non-pointer reflect.Value.
|
||||
if arrayElemValue.Kind() == reflect.Ptr {
|
||||
// Like: []*Entity
|
||||
arrayElemValue = arrayElemValue.Elem()
|
||||
if !arrayElemValue.IsValid() {
|
||||
// The element is nil, then create one and set it to the slice.
|
||||
// The "reflect.New(itemType.Elem())" creates a new element and returns the address of it.
|
||||
// For example:
|
||||
// reflect.New(itemType.Elem()) => *Entity
|
||||
// reflect.New(itemType.Elem()).Elem() => Entity
|
||||
arrayElemValue = reflect.New(arrayItemType.Elem()).Elem()
|
||||
arrayValue.Index(i).Set(arrayElemValue.Addr())
|
||||
}
|
||||
} else {
|
||||
// Like: []Entity
|
||||
}
|
||||
bindToAttrValue = arrayElemValue.FieldByName(bindToAttrName)
|
||||
if relationAttrName != "" {
|
||||
// Attribute value of current slice element.
|
||||
relationFromAttrValue = arrayElemValue.FieldByName(relationAttrName)
|
||||
if relationFromAttrValue.Kind() == reflect.Ptr {
|
||||
relationFromAttrValue = relationFromAttrValue.Elem()
|
||||
}
|
||||
} else {
|
||||
// Current slice element.
|
||||
relationFromAttrValue = arrayElemValue
|
||||
}
|
||||
if len(relationDataMap) > 0 && !relationFromAttrValue.IsValid() {
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
|
||||
}
|
||||
// Check and find possible bind to attribute name.
|
||||
if relationFields != "" && !relationBindToFieldNameChecked {
|
||||
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
|
||||
if !relationFromAttrField.IsValid() {
|
||||
var (
|
||||
fieldMap, _ = gstructs.FieldMap(gstructs.FieldMapInput{
|
||||
Pointer: relationFromAttrValue,
|
||||
RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag,
|
||||
})
|
||||
)
|
||||
if key, _ := utils.MapPossibleItemByKey(Map(fieldMap), relationBindToFieldName); key == "" {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`cannot find possible related attribute name "%s" from given relation fields "%s"`,
|
||||
relationBindToFieldName,
|
||||
relationFields,
|
||||
)
|
||||
} else {
|
||||
relationBindToFieldName = key
|
||||
}
|
||||
}
|
||||
relationBindToFieldNameChecked = true
|
||||
}
|
||||
switch bindToAttrKind {
|
||||
case reflect.Array, reflect.Slice:
|
||||
if len(relationDataMap) > 0 {
|
||||
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
|
||||
if relationFromAttrField.IsValid() {
|
||||
// results := make(Result, 0)
|
||||
results := make([]interface{}, 0)
|
||||
for _, v := range SliceAny(relationDataMap[String(relationFromAttrField.Interface())]) {
|
||||
item := v
|
||||
results = append(results, item)
|
||||
// doConvertWithJsonCheck does json converting check.
|
||||
// If given `params` is JSON, it then uses json.Unmarshal doing the converting.
|
||||
func doConvertWithJsonCheck(srcValue interface{}, dstPointer interface{}) (ok bool, err error) {
|
||||
switch valueResult := srcValue.(type) {
|
||||
case []byte:
|
||||
if json.Valid(valueResult) {
|
||||
if dstPointerReflectType, ok := dstPointer.(reflect.Value); ok {
|
||||
if dstPointerReflectType.Kind() == reflect.Ptr {
|
||||
if dstPointerReflectType.IsNil() {
|
||||
return false, nil
|
||||
}
|
||||
if err = Structs(results, bindToAttrValue.Addr()); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Maybe the attribute does not exist yet.
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
|
||||
return true, json.UnmarshalUseNumber(valueResult, dstPointerReflectType.Interface())
|
||||
} else if dstPointerReflectType.CanAddr() {
|
||||
return true, json.UnmarshalUseNumber(valueResult, dstPointerReflectType.Addr().Interface())
|
||||
}
|
||||
} else {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`relationKey should not be empty as field "%s" is slice`,
|
||||
bindToAttrName,
|
||||
)
|
||||
return true, json.UnmarshalUseNumber(valueResult, dstPointer)
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
var element reflect.Value
|
||||
if bindToAttrValue.IsNil() {
|
||||
element = reflect.New(bindToAttrType.Elem()).Elem()
|
||||
} else {
|
||||
element = bindToAttrValue.Elem()
|
||||
}
|
||||
if len(relationDataMap) > 0 {
|
||||
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
|
||||
if relationFromAttrField.IsValid() {
|
||||
v := relationDataMap[String(relationFromAttrField.Interface())]
|
||||
if v == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
case string:
|
||||
if valueBytes := []byte(valueResult); json.Valid(valueBytes) {
|
||||
if dstPointerReflectType, ok := dstPointer.(reflect.Value); ok {
|
||||
if dstPointerReflectType.Kind() == reflect.Ptr {
|
||||
if dstPointerReflectType.IsNil() {
|
||||
return false, nil
|
||||
}
|
||||
if utils.IsSlice(v) {
|
||||
if err = Struct(SliceAny(v)[0], element); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = Struct(v, element); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Maybe the attribute does not exist yet.
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
|
||||
return true, json.UnmarshalUseNumber(valueBytes, dstPointerReflectType.Interface())
|
||||
} else if dstPointerReflectType.CanAddr() {
|
||||
return true, json.UnmarshalUseNumber(valueBytes, dstPointerReflectType.Addr().Interface())
|
||||
}
|
||||
} else {
|
||||
if i >= len(maps) {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
v := maps[i]
|
||||
if v == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if err = Struct(v, element); err != nil {
|
||||
return err
|
||||
}
|
||||
return true, json.UnmarshalUseNumber(valueBytes, dstPointer)
|
||||
}
|
||||
bindToAttrValue.Set(element.Addr())
|
||||
}
|
||||
|
||||
case reflect.Struct:
|
||||
if len(relationDataMap) > 0 {
|
||||
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
|
||||
if relationFromAttrField.IsValid() {
|
||||
relationDataItem := relationDataMap[String(relationFromAttrField.Interface())]
|
||||
if relationDataItem == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if utils.IsSlice(relationDataItem) {
|
||||
if err = Struct(SliceAny(relationDataItem)[0], bindToAttrValue); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = Struct(relationDataItem, bindToAttrValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Maybe the attribute does not exist yet.
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
|
||||
}
|
||||
} else {
|
||||
if i >= len(maps) {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
relationDataItem := maps[i]
|
||||
if relationDataItem == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if err = Struct(relationDataItem, bindToAttrValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `unsupported attribute type: %s`, bindToAttrKind.String())
|
||||
default:
|
||||
// The `params` might be struct that implements interface function Interface, eg: gvar.Var.
|
||||
if v, ok := srcValue.(iInterface); ok {
|
||||
return doConvertWithJsonCheck(v.Interface(), dstPointer)
|
||||
}
|
||||
}
|
||||
reflect.ValueOf(structSlicePointer).Elem().Set(arrayValue)
|
||||
return nil
|
||||
return false, nil
|
||||
}
|
||||
|
||||
438
util/gconv/gconv_scan_list.go
Normal file
438
util/gconv/gconv_scan_list.go
Normal file
@ -0,0 +1,438 @@
|
||||
// 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 gconv
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"reflect"
|
||||
|
||||
"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/os/gstructs"
|
||||
)
|
||||
|
||||
// ScanList converts `structSlice` to struct slice which contains other complex struct attributes.
|
||||
// Note that the parameter `structSlicePointer` should be type of *[]struct/*[]*struct.
|
||||
//
|
||||
// Usage example 1: Normal attribute struct relation:
|
||||
//
|
||||
// type EntityUser struct {
|
||||
// Uid int
|
||||
// Name string
|
||||
// }
|
||||
//
|
||||
// type EntityUserDetail struct {
|
||||
// Uid int
|
||||
// Address string
|
||||
// }
|
||||
//
|
||||
// type EntityUserScores struct {
|
||||
// Id int
|
||||
// Uid int
|
||||
// Score int
|
||||
// Course string
|
||||
// }
|
||||
//
|
||||
// type Entity struct {
|
||||
// User *EntityUser
|
||||
// UserDetail *EntityUserDetail
|
||||
// UserScores []*EntityUserScores
|
||||
// }
|
||||
//
|
||||
// var users []*Entity
|
||||
// var userRecords = EntityUser{Uid: 1, Name:"john"}
|
||||
// var detailRecords = EntityUser{Uid: 1, Address: "chengdu"}
|
||||
// var scoresRecords = EntityUser{Id: 1, Uid: 1, Score: 100, Course: "math"}
|
||||
// ScanList(userRecords, &users, "User")
|
||||
// ScanList(userRecords, &users, "User", "uid")
|
||||
// ScanList(detailRecords, &users, "UserDetail", "User", "uid:Uid")
|
||||
// ScanList(scoresRecords, &users, "UserScores", "User", "uid:Uid")
|
||||
// ScanList(scoresRecords, &users, "UserScores", "User", "uid")
|
||||
//
|
||||
// Usage example 2: Embedded attribute struct relation:
|
||||
//
|
||||
// type EntityUser struct {
|
||||
// Uid int
|
||||
// Name string
|
||||
// }
|
||||
//
|
||||
// type EntityUserDetail struct {
|
||||
// Uid int
|
||||
// Address string
|
||||
// }
|
||||
//
|
||||
// type EntityUserScores struct {
|
||||
// Id int
|
||||
// Uid int
|
||||
// Score int
|
||||
// }
|
||||
//
|
||||
// type Entity struct {
|
||||
// EntityUser
|
||||
// UserDetail EntityUserDetail
|
||||
// UserScores []EntityUserScores
|
||||
// }
|
||||
//
|
||||
// var userRecords = EntityUser{Uid: 1, Name:"john"}
|
||||
// var detailRecords = EntityUser{Uid: 1, Address: "chengdu"}
|
||||
// var scoresRecords = EntityUser{Id: 1, Uid: 1, Score: 100, Course: "math"}
|
||||
// ScanList(userRecords, &users)
|
||||
// ScanList(detailRecords, &users, "UserDetail", "uid")
|
||||
// ScanList(scoresRecords, &users, "UserScores", "uid")
|
||||
//
|
||||
// The parameters "User/UserDetail/UserScores" in the example codes specify the target attribute struct
|
||||
// that current result will be bound to.
|
||||
//
|
||||
// The "uid" in the example codes is the table field name of the result, and the "Uid" is the relational
|
||||
// struct attribute name - not the attribute name of the bound to target. In the example codes, it's attribute
|
||||
// name "Uid" of "User" of entity "Entity". It automatically calculates the HasOne/HasMany relationship with
|
||||
// given `relation` parameter.
|
||||
//
|
||||
// See the example or unit testing cases for clear understanding for this function.
|
||||
func ScanList(structSlice interface{}, structSlicePointer interface{}, bindToAttrName string, relationAttrNameAndFields ...string) (err error) {
|
||||
var (
|
||||
relationAttrName string
|
||||
relationFields string
|
||||
)
|
||||
switch len(relationAttrNameAndFields) {
|
||||
case 2:
|
||||
relationAttrName = relationAttrNameAndFields[0]
|
||||
relationFields = relationAttrNameAndFields[1]
|
||||
case 1:
|
||||
relationFields = relationAttrNameAndFields[0]
|
||||
}
|
||||
return doScanList(structSlice, structSlicePointer, bindToAttrName, relationAttrName, relationFields)
|
||||
}
|
||||
|
||||
// doScanList converts `structSlice` to struct slice which contains other complex struct attributes recursively.
|
||||
// Note that the parameter `structSlicePointer` should be type of *[]struct/*[]*struct.
|
||||
func doScanList(
|
||||
structSlice interface{}, structSlicePointer interface{}, bindToAttrName, relationAttrName, relationFields string,
|
||||
) (err error) {
|
||||
var (
|
||||
maps = Maps(structSlice)
|
||||
)
|
||||
if len(maps) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Necessary checks for parameters.
|
||||
if bindToAttrName == "" {
|
||||
return gerror.NewCode(gcode.CodeInvalidParameter, `bindToAttrName should not be empty`)
|
||||
}
|
||||
|
||||
if relationAttrName == "." {
|
||||
relationAttrName = ""
|
||||
}
|
||||
|
||||
var (
|
||||
reflectValue = reflect.ValueOf(structSlicePointer)
|
||||
reflectKind = reflectValue.Kind()
|
||||
)
|
||||
if reflectKind == reflect.Interface {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
if reflectKind != reflect.Ptr {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
"structSlicePointer should be type of *[]struct/*[]*struct, but got: %v",
|
||||
reflectKind,
|
||||
)
|
||||
}
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
if reflectKind != reflect.Slice && reflectKind != reflect.Array {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
"structSlicePointer should be type of *[]struct/*[]*struct, but got: %v",
|
||||
reflectKind,
|
||||
)
|
||||
}
|
||||
length := len(maps)
|
||||
if length == 0 {
|
||||
// The pointed slice is not empty.
|
||||
if reflectValue.Len() > 0 {
|
||||
// It here checks if it has struct item, which is already initialized.
|
||||
// It then returns error to warn the developer its empty and no conversion.
|
||||
if v := reflectValue.Index(0); v.Kind() != reflect.Ptr {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
}
|
||||
// Do nothing for empty struct slice.
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
arrayValue reflect.Value // Like: []*Entity
|
||||
arrayItemType reflect.Type // Like: *Entity
|
||||
reflectType = reflect.TypeOf(structSlicePointer)
|
||||
)
|
||||
if reflectValue.Len() > 0 {
|
||||
arrayValue = reflectValue
|
||||
} else {
|
||||
arrayValue = reflect.MakeSlice(reflectType.Elem(), length, length)
|
||||
}
|
||||
|
||||
// Slice element item.
|
||||
arrayItemType = arrayValue.Index(0).Type()
|
||||
|
||||
// Relation variables.
|
||||
var (
|
||||
relationDataMap map[string]interface{}
|
||||
relationFromFieldName string // Eg: relationKV: id:uid -> id
|
||||
relationBindToFieldName string // Eg: relationKV: id:uid -> uid
|
||||
)
|
||||
if len(relationFields) > 0 {
|
||||
// The relation key string of table field name and attribute name
|
||||
// can be joined with char '=' or ':'.
|
||||
array := utils.SplitAndTrim(relationFields, "=")
|
||||
if len(array) == 1 {
|
||||
// Compatible with old splitting char ':'.
|
||||
array = utils.SplitAndTrim(relationFields, ":")
|
||||
}
|
||||
if len(array) == 1 {
|
||||
// The relation names are the same.
|
||||
array = []string{relationFields, relationFields}
|
||||
}
|
||||
if len(array) == 2 {
|
||||
// Defined table field to relation attribute name.
|
||||
// Like:
|
||||
// uid:Uid
|
||||
// uid:UserId
|
||||
relationFromFieldName = array[0]
|
||||
relationBindToFieldName = array[1]
|
||||
if key, _ := utils.MapPossibleItemByKey(maps[0], relationFromFieldName); key == "" {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`cannot find possible related table field name "%s" from given relation fields "%s"`,
|
||||
relationFromFieldName,
|
||||
relationFields,
|
||||
)
|
||||
} else {
|
||||
relationFromFieldName = key
|
||||
}
|
||||
} else {
|
||||
return gerror.NewCode(
|
||||
gcode.CodeInvalidParameter,
|
||||
`parameter relationKV should be format of "ResultFieldName:BindToAttrName"`,
|
||||
)
|
||||
}
|
||||
if relationFromFieldName != "" {
|
||||
// Note that the value might be type of slice.
|
||||
relationDataMap = utils.ListToMapByKey(maps, relationFromFieldName)
|
||||
}
|
||||
if len(relationDataMap) == 0 {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`cannot find the relation data map, maybe invalid relation fields given "%v"`,
|
||||
relationFields,
|
||||
)
|
||||
}
|
||||
}
|
||||
// Bind to target attribute.
|
||||
var (
|
||||
ok bool
|
||||
bindToAttrValue reflect.Value
|
||||
bindToAttrKind reflect.Kind
|
||||
bindToAttrType reflect.Type
|
||||
bindToAttrField reflect.StructField
|
||||
)
|
||||
if arrayItemType.Kind() == reflect.Ptr {
|
||||
if bindToAttrField, ok = arrayItemType.Elem().FieldByName(bindToAttrName); !ok {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`,
|
||||
bindToAttrName,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if bindToAttrField, ok = arrayItemType.FieldByName(bindToAttrName); !ok {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`,
|
||||
bindToAttrName,
|
||||
)
|
||||
}
|
||||
}
|
||||
bindToAttrType = bindToAttrField.Type
|
||||
bindToAttrKind = bindToAttrType.Kind()
|
||||
|
||||
// Bind to relation conditions.
|
||||
var (
|
||||
relationFromAttrValue reflect.Value
|
||||
relationFromAttrField reflect.Value
|
||||
relationBindToFieldNameChecked bool
|
||||
)
|
||||
for i := 0; i < arrayValue.Len(); i++ {
|
||||
arrayElemValue := arrayValue.Index(i)
|
||||
// The FieldByName should be called on non-pointer reflect.Value.
|
||||
if arrayElemValue.Kind() == reflect.Ptr {
|
||||
// Like: []*Entity
|
||||
arrayElemValue = arrayElemValue.Elem()
|
||||
if !arrayElemValue.IsValid() {
|
||||
// The element is nil, then create one and set it to the slice.
|
||||
// The "reflect.New(itemType.Elem())" creates a new element and returns the address of it.
|
||||
// For example:
|
||||
// reflect.New(itemType.Elem()) => *Entity
|
||||
// reflect.New(itemType.Elem()).Elem() => Entity
|
||||
arrayElemValue = reflect.New(arrayItemType.Elem()).Elem()
|
||||
arrayValue.Index(i).Set(arrayElemValue.Addr())
|
||||
}
|
||||
} else {
|
||||
// Like: []Entity
|
||||
}
|
||||
bindToAttrValue = arrayElemValue.FieldByName(bindToAttrName)
|
||||
if relationAttrName != "" {
|
||||
// Attribute value of current slice element.
|
||||
relationFromAttrValue = arrayElemValue.FieldByName(relationAttrName)
|
||||
if relationFromAttrValue.Kind() == reflect.Ptr {
|
||||
relationFromAttrValue = relationFromAttrValue.Elem()
|
||||
}
|
||||
} else {
|
||||
// Current slice element.
|
||||
relationFromAttrValue = arrayElemValue
|
||||
}
|
||||
if len(relationDataMap) > 0 && !relationFromAttrValue.IsValid() {
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
|
||||
}
|
||||
// Check and find possible bind to attribute name.
|
||||
if relationFields != "" && !relationBindToFieldNameChecked {
|
||||
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
|
||||
if !relationFromAttrField.IsValid() {
|
||||
var (
|
||||
fieldMap, _ = gstructs.FieldMap(gstructs.FieldMapInput{
|
||||
Pointer: relationFromAttrValue,
|
||||
RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag,
|
||||
})
|
||||
)
|
||||
if key, _ := utils.MapPossibleItemByKey(Map(fieldMap), relationBindToFieldName); key == "" {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`cannot find possible related attribute name "%s" from given relation fields "%s"`,
|
||||
relationBindToFieldName,
|
||||
relationFields,
|
||||
)
|
||||
} else {
|
||||
relationBindToFieldName = key
|
||||
}
|
||||
}
|
||||
relationBindToFieldNameChecked = true
|
||||
}
|
||||
switch bindToAttrKind {
|
||||
case reflect.Array, reflect.Slice:
|
||||
if len(relationDataMap) > 0 {
|
||||
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
|
||||
if relationFromAttrField.IsValid() {
|
||||
// results := make(Result, 0)
|
||||
results := make([]interface{}, 0)
|
||||
for _, v := range SliceAny(relationDataMap[String(relationFromAttrField.Interface())]) {
|
||||
item := v
|
||||
results = append(results, item)
|
||||
}
|
||||
if err = Structs(results, bindToAttrValue.Addr()); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Maybe the attribute does not exist yet.
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
|
||||
}
|
||||
} else {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`relationKey should not be empty as field "%s" is slice`,
|
||||
bindToAttrName,
|
||||
)
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
var element reflect.Value
|
||||
if bindToAttrValue.IsNil() {
|
||||
element = reflect.New(bindToAttrType.Elem()).Elem()
|
||||
} else {
|
||||
element = bindToAttrValue.Elem()
|
||||
}
|
||||
if len(relationDataMap) > 0 {
|
||||
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
|
||||
if relationFromAttrField.IsValid() {
|
||||
v := relationDataMap[String(relationFromAttrField.Interface())]
|
||||
if v == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if utils.IsSlice(v) {
|
||||
if err = Struct(SliceAny(v)[0], element); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = Struct(v, element); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Maybe the attribute does not exist yet.
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
|
||||
}
|
||||
} else {
|
||||
if i >= len(maps) {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
v := maps[i]
|
||||
if v == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if err = Struct(v, element); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
bindToAttrValue.Set(element.Addr())
|
||||
|
||||
case reflect.Struct:
|
||||
if len(relationDataMap) > 0 {
|
||||
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
|
||||
if relationFromAttrField.IsValid() {
|
||||
relationDataItem := relationDataMap[String(relationFromAttrField.Interface())]
|
||||
if relationDataItem == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if utils.IsSlice(relationDataItem) {
|
||||
if err = Struct(SliceAny(relationDataItem)[0], bindToAttrValue); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = Struct(relationDataItem, bindToAttrValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Maybe the attribute does not exist yet.
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
|
||||
}
|
||||
} else {
|
||||
if i >= len(maps) {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
relationDataItem := maps[i]
|
||||
if relationDataItem == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if err = Struct(relationDataItem, bindToAttrValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `unsupported attribute type: %s`, bindToAttrKind.String())
|
||||
}
|
||||
}
|
||||
reflect.ValueOf(structSlicePointer).Elem().Set(arrayValue)
|
||||
return nil
|
||||
}
|
||||
@ -43,50 +43,10 @@ func StructTag(params interface{}, pointer interface{}, priorityTag string) (err
|
||||
return doStruct(params, pointer, nil, priorityTag)
|
||||
}
|
||||
|
||||
// doStructWithJsonCheck checks if given `params` is JSON, it then uses json.Unmarshal doing the converting.
|
||||
func doStructWithJsonCheck(params interface{}, pointer interface{}) (err error, ok bool) {
|
||||
switch r := params.(type) {
|
||||
case []byte:
|
||||
if json.Valid(r) {
|
||||
if rv, ok := pointer.(reflect.Value); ok {
|
||||
if rv.Kind() == reflect.Ptr {
|
||||
if rv.IsNil() {
|
||||
return nil, false
|
||||
}
|
||||
return json.UnmarshalUseNumber(r, rv.Interface()), true
|
||||
} else if rv.CanAddr() {
|
||||
return json.UnmarshalUseNumber(r, rv.Addr().Interface()), true
|
||||
}
|
||||
} else {
|
||||
return json.UnmarshalUseNumber(r, pointer), true
|
||||
}
|
||||
}
|
||||
case string:
|
||||
if paramsBytes := []byte(r); json.Valid(paramsBytes) {
|
||||
if rv, ok := pointer.(reflect.Value); ok {
|
||||
if rv.Kind() == reflect.Ptr {
|
||||
if rv.IsNil() {
|
||||
return nil, false
|
||||
}
|
||||
return json.UnmarshalUseNumber(paramsBytes, rv.Interface()), true
|
||||
} else if rv.CanAddr() {
|
||||
return json.UnmarshalUseNumber(paramsBytes, rv.Addr().Interface()), true
|
||||
}
|
||||
} else {
|
||||
return json.UnmarshalUseNumber(paramsBytes, pointer), true
|
||||
}
|
||||
}
|
||||
default:
|
||||
// The `params` might be struct that implements interface function Interface, eg: gvar.Var.
|
||||
if v, ok := params.(iInterface); ok {
|
||||
return doStructWithJsonCheck(v.Interface(), pointer)
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// doStruct is the core internal converting function for any data to struct.
|
||||
func doStruct(params interface{}, pointer interface{}, paramKeyToAttrMap map[string]string, priorityTag string) (err error) {
|
||||
func doStruct(
|
||||
params interface{}, pointer interface{}, paramKeyToAttrMap map[string]string, priorityTag string,
|
||||
) (err error) {
|
||||
if params == nil {
|
||||
// If `params` is nil, no conversion.
|
||||
return nil
|
||||
@ -95,6 +55,15 @@ func doStruct(params interface{}, pointer interface{}, paramKeyToAttrMap map[str
|
||||
return gerror.NewCode(gcode.CodeInvalidParameter, "object pointer cannot be nil")
|
||||
}
|
||||
|
||||
// JSON content converting.
|
||||
ok, err := doConvertWithJsonCheck(params, pointer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// Catch the panic, especially the reflection operation panics.
|
||||
if exception := recover(); exception != nil {
|
||||
@ -106,15 +75,6 @@ func doStruct(params interface{}, pointer interface{}, paramKeyToAttrMap map[str
|
||||
}
|
||||
}()
|
||||
|
||||
// JSON content converting.
|
||||
err, ok := doStructWithJsonCheck(params, pointer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
paramsReflectValue reflect.Value
|
||||
paramsInterface interface{} // DO NOT use `params` directly as it might be type `reflect.Value`
|
||||
@ -135,49 +95,35 @@ func doStruct(params interface{}, pointer interface{}, paramKeyToAttrMap map[str
|
||||
pointerReflectValue = reflect.ValueOf(pointer)
|
||||
pointerReflectKind = pointerReflectValue.Kind()
|
||||
if pointerReflectKind != reflect.Ptr {
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "object pointer should be type of '*struct', but got '%v'", pointerReflectKind)
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
"destination pointer should be type of '*struct', but got '%v'",
|
||||
pointerReflectKind,
|
||||
)
|
||||
}
|
||||
// Using IsNil on reflect.Ptr variable is OK.
|
||||
if !pointerReflectValue.IsValid() || pointerReflectValue.IsNil() {
|
||||
return gerror.NewCode(gcode.CodeInvalidParameter, "object pointer cannot be nil")
|
||||
return gerror.NewCode(
|
||||
gcode.CodeInvalidParameter,
|
||||
"destination pointer cannot be nil",
|
||||
)
|
||||
}
|
||||
pointerElemReflectValue = pointerReflectValue.Elem()
|
||||
}
|
||||
|
||||
// custom convert try first
|
||||
// If `params` and `pointer` are the same type, the do directly assignment.
|
||||
// For performance enhancement purpose.
|
||||
if ok = doConvertWithTypeCheck(paramsReflectValue, pointerElemReflectValue); ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// custom convert.
|
||||
if ok, err = callCustomConverter(paramsReflectValue, pointerReflectValue); ok {
|
||||
return err
|
||||
}
|
||||
|
||||
// If `params` and `pointer` are the same type, the do directly assignment.
|
||||
// For performance enhancement purpose.
|
||||
if pointerElemReflectValue.IsValid() {
|
||||
switch {
|
||||
// Eg:
|
||||
// UploadFile => UploadFile
|
||||
// *UploadFile => *UploadFile
|
||||
case pointerElemReflectValue.Type() == paramsReflectValue.Type():
|
||||
pointerElemReflectValue.Set(paramsReflectValue)
|
||||
return nil
|
||||
|
||||
// Eg:
|
||||
// UploadFile => *UploadFile
|
||||
case pointerElemReflectValue.Kind() == reflect.Ptr && pointerElemReflectValue.Elem().IsValid() &&
|
||||
pointerElemReflectValue.Elem().Type() == paramsReflectValue.Type():
|
||||
pointerElemReflectValue.Elem().Set(paramsReflectValue)
|
||||
return nil
|
||||
|
||||
// Eg:
|
||||
// *UploadFile => UploadFile
|
||||
case paramsReflectValue.Kind() == reflect.Ptr && paramsReflectValue.Elem().IsValid() &&
|
||||
pointerElemReflectValue.Type() == paramsReflectValue.Elem().Type():
|
||||
pointerElemReflectValue.Set(paramsReflectValue.Elem())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Normal unmarshalling interfaces checks.
|
||||
if err, ok = bindVarToReflectValueWithInterfaceCheck(pointerReflectValue, paramsInterface); ok {
|
||||
if ok, err = bindVarToReflectValueWithInterfaceCheck(pointerReflectValue, paramsInterface); ok {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -198,7 +144,7 @@ func doStruct(params interface{}, pointer interface{}, paramKeyToAttrMap map[str
|
||||
// return v.UnmarshalValue(params)
|
||||
// }
|
||||
// Note that it's `pointerElemReflectValue` here not `pointerReflectValue`.
|
||||
if err, ok = bindVarToReflectValueWithInterfaceCheck(pointerElemReflectValue, paramsInterface); ok {
|
||||
if ok, err := bindVarToReflectValueWithInterfaceCheck(pointerElemReflectValue, paramsInterface); ok {
|
||||
return err
|
||||
}
|
||||
// Retrieve its element, may be struct at last.
|
||||
@ -538,7 +484,7 @@ func bindVarToStructAttr(
|
||||
}
|
||||
|
||||
// Common interface check.
|
||||
if err, ok = bindVarToReflectValueWithInterfaceCheck(structFieldValue, value); ok {
|
||||
if ok, err = bindVarToReflectValueWithInterfaceCheck(structFieldValue, value); ok {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -553,24 +499,24 @@ func bindVarToStructAttr(
|
||||
}
|
||||
|
||||
// bindVarToReflectValueWithInterfaceCheck does bind using common interfaces checks.
|
||||
func bindVarToReflectValueWithInterfaceCheck(reflectValue reflect.Value, value interface{}) (error, bool) {
|
||||
func bindVarToReflectValueWithInterfaceCheck(reflectValue reflect.Value, value interface{}) (bool, error) {
|
||||
var pointer interface{}
|
||||
if reflectValue.Kind() != reflect.Ptr && reflectValue.CanAddr() {
|
||||
reflectValueAddr := reflectValue.Addr()
|
||||
if reflectValueAddr.IsNil() || !reflectValueAddr.IsValid() {
|
||||
return nil, false
|
||||
return false, nil
|
||||
}
|
||||
// Not a pointer, but can token address, that makes it can be unmarshalled.
|
||||
pointer = reflectValue.Addr().Interface()
|
||||
} else {
|
||||
if reflectValue.IsNil() || !reflectValue.IsValid() {
|
||||
return nil, false
|
||||
return false, nil
|
||||
}
|
||||
pointer = reflectValue.Interface()
|
||||
}
|
||||
// UnmarshalValue.
|
||||
if v, ok := pointer.(iUnmarshalValue); ok {
|
||||
return v.UnmarshalValue(value), ok
|
||||
return ok, v.UnmarshalValue(value)
|
||||
}
|
||||
// UnmarshalText.
|
||||
if v, ok := pointer.(iUnmarshalText); ok {
|
||||
@ -583,7 +529,7 @@ func bindVarToReflectValueWithInterfaceCheck(reflectValue reflect.Value, value i
|
||||
valueBytes = []byte(f.String())
|
||||
}
|
||||
if len(valueBytes) > 0 {
|
||||
return v.UnmarshalText(valueBytes), ok
|
||||
return ok, v.UnmarshalText(valueBytes)
|
||||
}
|
||||
}
|
||||
// UnmarshalJSON.
|
||||
@ -606,14 +552,14 @@ func bindVarToReflectValueWithInterfaceCheck(reflectValue reflect.Value, value i
|
||||
copy(newValueBytes[1:], valueBytes)
|
||||
valueBytes = newValueBytes
|
||||
}
|
||||
return v.UnmarshalJSON(valueBytes), ok
|
||||
return ok, v.UnmarshalJSON(valueBytes)
|
||||
}
|
||||
}
|
||||
if v, ok := pointer.(iSet); ok {
|
||||
v.Set(value)
|
||||
return nil, ok
|
||||
return ok, nil
|
||||
}
|
||||
return nil, false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// bindVarToReflectValue sets `value` to reflect value object `structFieldValue`.
|
||||
@ -621,7 +567,7 @@ func bindVarToReflectValue(
|
||||
structFieldValue reflect.Value, value interface{}, paramKeyToAttrMap map[string]string,
|
||||
) (err error) {
|
||||
// JSON content converting.
|
||||
err, ok := doStructWithJsonCheck(value, structFieldValue)
|
||||
ok, err := doConvertWithJsonCheck(value, structFieldValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -755,7 +701,7 @@ func bindVarToReflectValue(
|
||||
if structFieldValue.IsNil() || structFieldValue.IsZero() {
|
||||
// Nil or empty pointer, it creates a new one.
|
||||
item := reflect.New(structFieldValue.Type().Elem())
|
||||
if err, ok = bindVarToReflectValueWithInterfaceCheck(item, value); ok {
|
||||
if ok, err = bindVarToReflectValueWithInterfaceCheck(item, value); ok {
|
||||
structFieldValue.Set(item)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
)
|
||||
|
||||
// Structs converts any slice to given struct slice.
|
||||
@ -42,18 +41,6 @@ func StructsTag(params interface{}, pointer interface{}, priorityTag string) (er
|
||||
func doStructs(
|
||||
params interface{}, pointer interface{}, paramKeyToAttrMap map[string]string, priorityTag string,
|
||||
) (err error) {
|
||||
if params == nil {
|
||||
// If `params` is nil, no conversion.
|
||||
return nil
|
||||
}
|
||||
if pointer == nil {
|
||||
return gerror.NewCode(gcode.CodeInvalidParameter, "object pointer cannot be nil")
|
||||
}
|
||||
|
||||
if doStructsByDirectReflectSet(params, pointer) {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// Catch the panic, especially the reflection operation panics.
|
||||
if exception := recover(); exception != nil {
|
||||
@ -64,35 +51,16 @@ func doStructs(
|
||||
}
|
||||
}
|
||||
}()
|
||||
// If given `params` is JSON, it then uses json.Unmarshal doing the converting.
|
||||
switch r := params.(type) {
|
||||
case []byte:
|
||||
if json.Valid(r) {
|
||||
if rv, ok := pointer.(reflect.Value); ok {
|
||||
if rv.Kind() == reflect.Ptr {
|
||||
return json.UnmarshalUseNumber(r, rv.Interface())
|
||||
}
|
||||
} else {
|
||||
return json.UnmarshalUseNumber(r, pointer)
|
||||
}
|
||||
}
|
||||
case string:
|
||||
if paramsBytes := []byte(r); json.Valid(paramsBytes) {
|
||||
if rv, ok := pointer.(reflect.Value); ok {
|
||||
if rv.Kind() == reflect.Ptr {
|
||||
return json.UnmarshalUseNumber(paramsBytes, rv.Interface())
|
||||
}
|
||||
} else {
|
||||
return json.UnmarshalUseNumber(paramsBytes, pointer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pointer type check.
|
||||
pointerRv, ok := pointer.(reflect.Value)
|
||||
if !ok {
|
||||
pointerRv = reflect.ValueOf(pointer)
|
||||
if kind := pointerRv.Kind(); kind != reflect.Ptr {
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "pointer should be type of pointer, but got: %v", kind)
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
"pointer should be type of pointer, but got: %v", kind,
|
||||
)
|
||||
}
|
||||
}
|
||||
// Converting `params` to map slice.
|
||||
@ -163,17 +131,3 @@ func doStructs(
|
||||
pointerRv.Elem().Set(reflectElemArray)
|
||||
return nil
|
||||
}
|
||||
|
||||
// doStructsByDirectReflectSet do the converting directly using reflect Set.
|
||||
// It returns true if success, or else false.
|
||||
func doStructsByDirectReflectSet(params interface{}, pointer interface{}) (ok bool) {
|
||||
v1 := reflect.ValueOf(pointer)
|
||||
v2 := reflect.ValueOf(params)
|
||||
if v1.Kind() == reflect.Ptr {
|
||||
if elem := v1.Elem(); elem.IsValid() && elem.Type() == v2.Type() {
|
||||
elem.Set(v2)
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package gconv_test
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -115,6 +116,25 @@ func Test_Issue1227(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// https://github.com/gogf/gf/issues/1607
|
||||
func Test_Issue1607(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Demo struct {
|
||||
B Float64
|
||||
}
|
||||
rat := &big.Rat{}
|
||||
rat.SetFloat64(1.5)
|
||||
|
||||
var demos = make([]Demo, 1)
|
||||
err := gconv.Scan([]map[string]interface{}{
|
||||
{"A": 1, "B": rat},
|
||||
}, &demos)
|
||||
t.AssertNil(err)
|
||||
t.Assert(demos[0].B, 1.5)
|
||||
})
|
||||
}
|
||||
|
||||
// https://github.com/gogf/gf/issues/1946
|
||||
func Test_Issue1946(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type B struct {
|
||||
@ -292,3 +312,17 @@ func Test_Issue2371(t *testing.T) {
|
||||
t.Assert(s.Time.UTC(), `2022-12-15 08:11:34 +0000 UTC`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Issue2901(t *testing.T) {
|
||||
type GameApp2 struct {
|
||||
ForceUpdateTime *time.Time
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
src := map[string]interface{}{
|
||||
"FORCE_UPDATE_TIME": time.Now(),
|
||||
}
|
||||
m := GameApp2{}
|
||||
err := gconv.Scan(src, &m)
|
||||
t.AssertNil(err)
|
||||
})
|
||||
}
|
||||
|
||||
@ -641,19 +641,52 @@ func (f *Float64) UnmarshalValue(value interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func Test_Issue1607(t *testing.T) {
|
||||
func Test_Scan_AutoCreatingPointerElem(t *testing.T) {
|
||||
type A struct {
|
||||
Name string
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Demo struct {
|
||||
B Float64
|
||||
var dst A
|
||||
var src = A{
|
||||
Name: "john",
|
||||
}
|
||||
rat := &big.Rat{}
|
||||
rat.SetFloat64(1.5)
|
||||
|
||||
var demos = make([]Demo, 1)
|
||||
err := gconv.Scan([]map[string]interface{}{
|
||||
{"A": 1, "B": rat},
|
||||
}, &demos)
|
||||
err := gconv.Scan(src, &dst)
|
||||
t.AssertNil(err)
|
||||
t.Assert(demos[0].B, 1.5)
|
||||
t.Assert(src, dst)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var dst = &A{
|
||||
Name: "smith",
|
||||
}
|
||||
var src = A{
|
||||
Name: "john",
|
||||
}
|
||||
err := gconv.Scan(src, &dst)
|
||||
t.AssertNil(err)
|
||||
t.Assert(src, dst)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var dst = A{
|
||||
Name: "smith",
|
||||
}
|
||||
var src = &A{
|
||||
Name: "john",
|
||||
}
|
||||
err := gconv.Scan(src, &dst)
|
||||
t.AssertNil(err)
|
||||
t.Assert(src, dst)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var dst *A
|
||||
var src = &A{
|
||||
Name: "john",
|
||||
}
|
||||
err := gconv.Scan(src, &dst)
|
||||
t.AssertNil(err)
|
||||
t.Assert(src, dst)
|
||||
|
||||
// Note that the dst points to src.
|
||||
src.Name = "smith"
|
||||
t.Assert(src, dst)
|
||||
})
|
||||
}
|
||||
|
||||
@ -78,17 +78,3 @@ func Test_Time_Slice_Attribute(t *testing.T) {
|
||||
t.Assert(s.Arr[1], "2021-01-12 12:34:57")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Issue2901(t *testing.T) {
|
||||
type GameApp2 struct {
|
||||
ForceUpdateTime *time.Time
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
src := map[string]interface{}{
|
||||
"FORCE_UPDATE_TIME": time.Now(),
|
||||
}
|
||||
m := GameApp2{}
|
||||
err := gconv.Scan(src, &m)
|
||||
t.AssertNil(err)
|
||||
})
|
||||
}
|
||||
|
||||
@ -10,7 +10,6 @@ package gutil
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
@ -18,13 +17,6 @@ const (
|
||||
dumpIndent = ` `
|
||||
)
|
||||
|
||||
// IsEmpty checks given `value` empty or not.
|
||||
// It returns false if `value` is: integer(0), bool(false), slice/map(len=0), nil;
|
||||
// or else returns true.
|
||||
func IsEmpty(value interface{}) bool {
|
||||
return empty.IsEmpty(value)
|
||||
}
|
||||
|
||||
// Keys retrieves and returns the keys from given map or struct.
|
||||
func Keys(mapOrStruct interface{}) (keysOrAttrs []string) {
|
||||
keysOrAttrs = make([]string, 0)
|
||||
|
||||
25
util/gutil/gutil_is.go
Normal file
25
util/gutil/gutil_is.go
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gutil
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
)
|
||||
|
||||
// IsEmpty checks given `value` empty or not.
|
||||
// It returns false if `value` is: integer(0), bool(false), slice/map(len=0), nil;
|
||||
// or else returns true.
|
||||
func IsEmpty(value interface{}) bool {
|
||||
return empty.IsEmpty(value)
|
||||
}
|
||||
|
||||
// IsTypeOf checks and returns whether the type of `value` and `valueInExpectType` equal.
|
||||
func IsTypeOf(value, valueInExpectType interface{}) bool {
|
||||
return reflect.TypeOf(value) == reflect.TypeOf(valueInExpectType)
|
||||
}
|
||||
46
util/gutil/gutil_z_unit_is_test.go
Normal file
46
util/gutil/gutil_z_unit_is_test.go
Normal 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 gutil_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
func Test_IsEmpty(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.Assert(gutil.IsEmpty(1), false)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_IsTypeOf(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.Assert(gutil.IsTypeOf(1, 0), true)
|
||||
t.Assert(gutil.IsTypeOf(1.1, 0.1), true)
|
||||
t.Assert(gutil.IsTypeOf(1.1, 1), false)
|
||||
t.Assert(gutil.IsTypeOf(true, false), true)
|
||||
t.Assert(gutil.IsTypeOf(true, 1), false)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type A struct {
|
||||
Name string
|
||||
}
|
||||
type B struct {
|
||||
Name string
|
||||
}
|
||||
t.Assert(gutil.IsTypeOf(1, A{}), false)
|
||||
t.Assert(gutil.IsTypeOf(A{}, B{}), false)
|
||||
t.Assert(gutil.IsTypeOf(A{Name: "john"}, &A{Name: "john"}), false)
|
||||
t.Assert(gutil.IsTypeOf(A{Name: "john"}, A{Name: "john"}), true)
|
||||
t.Assert(gutil.IsTypeOf(A{Name: "john"}, A{}), true)
|
||||
t.Assert(gutil.IsTypeOf(&A{Name: "john"}, &A{}), true)
|
||||
t.Assert(gutil.IsTypeOf(&A{Name: "john"}, &B{}), false)
|
||||
t.Assert(gutil.IsTypeOf(A{Name: "john"}, B{Name: "john"}), false)
|
||||
})
|
||||
}
|
||||
@ -62,12 +62,6 @@ func Test_TryCatch(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_IsEmpty(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.Assert(gutil.IsEmpty(1), false)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Throw(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
defer func() {
|
||||
|
||||
@ -2,5 +2,5 @@ package gf
|
||||
|
||||
const (
|
||||
// VERSION is the current GoFrame version.
|
||||
VERSION = "v2.6.3"
|
||||
VERSION = "v2.6.4"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user