mirror of
https://gitee.com/johng/gf
synced 2026-06-28 18:16:26 +08:00
Compare commits
68 Commits
v2.0.6
...
contrib/dr
| Author | SHA1 | Date | |
|---|---|---|---|
| bb3ca6d414 | |||
| 674eada9ed | |||
| 310baaf67d | |||
| c9971b21ec | |||
| be77779aff | |||
| e119f2a534 | |||
| a09c8497bc | |||
| ebad3eb93e | |||
| e4e4534c7c | |||
| b412fc6516 | |||
| f9c9750108 | |||
| 5dee3bb4d9 | |||
| 1e3d8cdadd | |||
| c5bf45f1ae | |||
| bf674060c0 | |||
| 878dbe4ab9 | |||
| d8b383719a | |||
| 9ff5f39701 | |||
| 5144cc0e08 | |||
| ee29b28575 | |||
| 7785082f19 | |||
| edf40ba430 | |||
| a228495ced | |||
| ed9dc70769 | |||
| e8581d4fd5 | |||
| 2d6fcf5d06 | |||
| 55e0262c37 | |||
| d5e5a48170 | |||
| d0f2928cec | |||
| 190a53647e | |||
| f9a3fa3c23 | |||
| f1fee72d6d | |||
| 0b4ae6b116 | |||
| a1ec7cb896 | |||
| 1935412db9 | |||
| c90a9d45ee | |||
| a594592151 | |||
| 119d8bf98c | |||
| 1e141d9f64 | |||
| 587af6dec8 | |||
| 793e862e5a | |||
| 09c3425dd3 | |||
| 4ca168412b | |||
| 66f24db6da | |||
| c39a58f812 | |||
| 5034f231a9 | |||
| 64afd5f64c | |||
| 0e0d2e1c45 | |||
| 52d8371ba9 | |||
| 1d74b58d36 | |||
| c7f51b8e77 | |||
| b57cbacc82 | |||
| 325887fa18 | |||
| 73ca527b0a | |||
| 147348e0d1 | |||
| ad202ea735 | |||
| d7bd1b74e8 | |||
| 5e3c0bd9aa | |||
| a6bbb8424c | |||
| 00daeb318c | |||
| 65341141fe | |||
| fe353c5fe3 | |||
| 008e5ea196 | |||
| 157e936f24 | |||
| 455d724c01 | |||
| ea60f7e054 | |||
| daf2b649ef | |||
| aa87d234e3 |
11
.github/workflows/gf.yml
vendored
11
.github/workflows/gf.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: GoFrame CI
|
||||
name: GoFrame Main CI
|
||||
|
||||
|
||||
on:
|
||||
@ -76,11 +76,18 @@ jobs:
|
||||
--health-timeout 5s
|
||||
--health-retries 10
|
||||
|
||||
clickhouse-server:
|
||||
image: yandex/clickhouse-server
|
||||
ports:
|
||||
- 9000:9000
|
||||
- 8123:8123
|
||||
- 9001:9001
|
||||
|
||||
|
||||
# strategy set
|
||||
strategy:
|
||||
matrix:
|
||||
go: ["1.15", "1.16", "1.17"]
|
||||
go: ["1.16", "1.17"]
|
||||
|
||||
|
||||
steps:
|
||||
|
||||
30
.github/workflows/tag.yml
vendored
Normal file
30
.github/workflows/tag.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
name: GoFrame AutoCreating SubMod Tags
|
||||
|
||||
on:
|
||||
push:
|
||||
# Sequence of patterns matched against refs/tags
|
||||
tags:
|
||||
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
||||
|
||||
env:
|
||||
TZ: Asia/Shanghai
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Auto Creating Tags
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Github Code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Auto Creating Tags
|
||||
run: |
|
||||
git config --global user.email "tagrobot@goframe.org"
|
||||
git config --global user.name "TagRobot"
|
||||
for file in `find contrib -name go.mod`; do
|
||||
tag=$(dirname $file)/$GITHUB_REF_NAME
|
||||
git tag $tag
|
||||
git push origin $tag
|
||||
done
|
||||
@ -23,7 +23,7 @@ You can also install `gf` tool using pre-built binaries: https://github.com/gogf
|
||||
## 2) Manually Install
|
||||
|
||||
```shell
|
||||
git clone https://github.com/gogf/gf && cd cmd/gf && go install
|
||||
git clone https://github.com/gogf/gf && cd gf/cmd/gf && go install
|
||||
```
|
||||
|
||||
|
||||
|
||||
@ -3,17 +3,17 @@ module github.com/gogf/gf/cmd/gf/v2
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.0.0-rc2
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.0.0-rc2
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.0.0-rc2
|
||||
github.com/gogf/gf/v2 v2.0.0
|
||||
github.com/longbridgeapp/sqlparser v0.3.1 // indirect
|
||||
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.0.6
|
||||
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.0.6
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.0.6
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.0.6
|
||||
github.com/gogf/gf/v2 v2.0.6
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/gogf/gf/contrib/drivers/mssql/v2 => ../../contrib/drivers/mssql/
|
||||
github.com/gogf/gf/contrib/drivers/oracle/v2 => ../../contrib/drivers/oracle/
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 => ../../contrib/drivers/pgsql/
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 => ../../contrib/drivers/sqlite/
|
||||
github.com/gogf/gf/v2 => ../../
|
||||
|
||||
@ -22,6 +22,7 @@ github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Px
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
|
||||
github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
@ -55,6 +56,8 @@ github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-oci8 v0.1.1 h1:aEUDxNAyDG0tv8CA3TArnDQNyc4EhnWlsfxRgDHABHM=
|
||||
github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-sqlite3 v1.14.10 h1:MLn+5bFRlWMGoSRmJour3CL1w/qL96mvipqpwQW/Sfk=
|
||||
@ -78,8 +81,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 h1:zzrxE1FKn5ryBNl9eKOeqQ58Y/Qpo3Q9QNxKHX5uzzQ=
|
||||
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.opentelemetry.io/otel v1.0.0 h1:qTTn6x71GVBvoafHK/yaRUmFzI4LcONZD0/kXxl5PHI=
|
||||
|
||||
@ -25,7 +25,7 @@ import (
|
||||
var (
|
||||
Build = cBuild{
|
||||
nodeNameInConfigFile: "gfcli.build",
|
||||
packedGoFileName: "build_pack_data.go",
|
||||
packedGoFileName: "internal/packed/build_pack_data.go",
|
||||
}
|
||||
)
|
||||
|
||||
@ -108,20 +108,21 @@ func init() {
|
||||
}
|
||||
|
||||
type cBuildInput struct {
|
||||
g.Meta `name:"build" config:"gfcli.build"`
|
||||
File string `name:"FILE" arg:"true" brief:"building file path"`
|
||||
Name string `short:"n" name:"name" brief:"output binary name"`
|
||||
Version string `short:"v" name:"version" brief:"output binary version"`
|
||||
Arch string `short:"a" name:"arch" brief:"output binary architecture, multiple arch separated with ','"`
|
||||
System string `short:"s" name:"system" brief:"output binary system, multiple os separated with ','"`
|
||||
Output string `short:"o" name:"output" brief:"output binary path, used when building single binary file"`
|
||||
Path string `short:"p" name:"path" brief:"output binary directory path, default is './temp'" d:"./temp"`
|
||||
Extra string `short:"e" name:"extra" brief:"extra custom \"go build\" options"`
|
||||
Mod string `short:"m" name:"mod" brief:"like \"-mod\" option of \"go build\", use \"-m none\" to disable go module"`
|
||||
Cgo bool `short:"c" name:"cgo" brief:"enable or disable cgo feature, it's disabled in default" orphan:"true"`
|
||||
VarMap g.Map `short:"r" name:"varMap" brief:"custom built embedded variable into binary"`
|
||||
Exit bool `name:"exit" brief:"exit building when any error occurs, default is false" orphan:"true"`
|
||||
Pack string `name:"pack" brief:"pack specified folder into temporary go file before building and removes it after built"`
|
||||
g.Meta `name:"build" config:"gfcli.build"`
|
||||
File string `name:"FILE" arg:"true" brief:"building file path"`
|
||||
Name string `short:"n" name:"name" brief:"output binary name"`
|
||||
Version string `short:"v" name:"version" brief:"output binary version"`
|
||||
Arch string `short:"a" name:"arch" brief:"output binary architecture, multiple arch separated with ','"`
|
||||
System string `short:"s" name:"system" brief:"output binary system, multiple os separated with ','"`
|
||||
Output string `short:"o" name:"output" brief:"output binary path, used when building single binary file"`
|
||||
Path string `short:"p" name:"path" brief:"output binary directory path, default is './temp'" d:"./temp"`
|
||||
Extra string `short:"e" name:"extra" brief:"extra custom \"go build\" options"`
|
||||
Mod string `short:"m" name:"mod" brief:"like \"-mod\" option of \"go build\", use \"-m none\" to disable go module"`
|
||||
Cgo bool `short:"c" name:"cgo" brief:"enable or disable cgo feature, it's disabled in default" orphan:"true"`
|
||||
VarMap g.Map `short:"r" name:"varMap" brief:"custom built embedded variable into binary"`
|
||||
PackSrc string `short:"ps" name:"packSrc" brief:"pack one or more folders into one go file before building"`
|
||||
PackDst string `short:"pd" name:"packDst" brief:"temporary go file path for pack, this go file will be automatically removed after built" d:"internal/packed/build_pack_data.go"`
|
||||
ExitWhenError bool `short:"ew" name:"exitWhenError" brief:"exit building when any error occurs, default is false" orphan:"true"`
|
||||
}
|
||||
type cBuildOutput struct{}
|
||||
|
||||
@ -189,16 +190,21 @@ func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, e
|
||||
platformMap[system][arch] = true
|
||||
}
|
||||
// Auto packing.
|
||||
if len(in.Pack) > 0 {
|
||||
dataFilePath := fmt.Sprintf(`packed/%s`, c.packedGoFileName)
|
||||
if !gfile.Exists(dataFilePath) {
|
||||
if in.PackSrc != "" {
|
||||
if in.PackDst == "" {
|
||||
mlog.Fatal(`parameter "packDst" should not be empty when "packSrc" is used`)
|
||||
}
|
||||
if gfile.Exists(in.PackDst) && !gfile.IsFile(in.PackDst) {
|
||||
mlog.Fatalf(`parameter "packDst" path "%s" should be type of file not directory`, in.PackDst)
|
||||
}
|
||||
if !gfile.Exists(in.PackDst) {
|
||||
// Remove the go file that is automatically packed resource.
|
||||
defer func() {
|
||||
_ = gfile.Remove(dataFilePath)
|
||||
mlog.Printf(`remove the automatically generated resource go file: %s`, dataFilePath)
|
||||
_ = gfile.Remove(in.PackDst)
|
||||
mlog.Printf(`remove the automatically generated resource go file: %s`, in.PackDst)
|
||||
}()
|
||||
}
|
||||
packCmd := fmt.Sprintf(`gf pack %s %s`, in.Pack, dataFilePath)
|
||||
packCmd := fmt.Sprintf(`gf pack %s %s`, in.PackSrc, in.PackDst)
|
||||
mlog.Print(packCmd)
|
||||
gproc.MustShellRun(packCmd)
|
||||
}
|
||||
@ -261,7 +267,7 @@ func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, e
|
||||
system, arch, gstr.Trim(result),
|
||||
`you may use command option "--debug" to enable debug info and check the details`,
|
||||
)
|
||||
if in.Exit {
|
||||
if in.ExitWhenError {
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -21,8 +21,8 @@ import (
|
||||
|
||||
_ "github.com/gogf/gf/contrib/drivers/mssql/v2"
|
||||
_ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
|
||||
// _ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
|
||||
// _ "github.com/gogf/gf/contrib/drivers/oracle/v2"
|
||||
_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
|
||||
//_ "github.com/gogf/gf/contrib/drivers/oracle/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@ -13,6 +13,7 @@ import (
|
||||
"github.com/gogf/gf/v2/os/gproc"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/os/gtimer"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gtag"
|
||||
)
|
||||
|
||||
@ -149,9 +150,9 @@ func (app *cRunApp) Run() {
|
||||
if runtime.GOOS == "windows" {
|
||||
// Special handling for windows platform.
|
||||
// DO NOT USE "cmd /c" command.
|
||||
process = gproc.NewProcess(runCommand, nil)
|
||||
process = gproc.NewProcess(outputPath, gstr.SplitAndTrim(" ", app.Args))
|
||||
} else {
|
||||
process = gproc.NewProcessCmd(runCommand, nil)
|
||||
process = gproc.NewProcessCmd(outputPath, gstr.SplitAndTrim(" ", app.Args))
|
||||
}
|
||||
if pid, err := process.Start(); err != nil {
|
||||
mlog.Printf("build running error: %s", err.Error())
|
||||
|
||||
@ -14,9 +14,11 @@ import _ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
|
||||
|
||||
# Supported Drivers
|
||||
|
||||
## MySQL
|
||||
## MySQL/MariaDB/TiDB
|
||||
|
||||
BuiltIn supported, nothing todo.
|
||||
```
|
||||
import _ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||
```
|
||||
|
||||
## SQLite
|
||||
```
|
||||
@ -50,6 +52,15 @@ Note:
|
||||
- It does not support `Save/Replace` features.
|
||||
- It does not support `LastInsertId`.
|
||||
|
||||
## ClickHouse
|
||||
```
|
||||
import _ "github.com/gogf/gf/contrib/drivers/clickhouse/v2"
|
||||
```
|
||||
Note:
|
||||
- It does not support `InsertIgnore/InsertGetId` features.
|
||||
- It does not support `Transaction` feature.
|
||||
- It does not support `RowsAffected` feature.
|
||||
|
||||
# Custom Drivers
|
||||
|
||||
It's quick and easy, please refer to current driver source.
|
||||
|
||||
@ -8,5 +8,363 @@
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
_ "github.com/ClickHouse/clickhouse-go"
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ClickHouse/clickhouse-go/v2"
|
||||
"github.com/longbridgeapp/sqlparser"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gmap"
|
||||
"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/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// Driver is the driver for postgresql database.
|
||||
type Driver struct {
|
||||
*gdb.Core
|
||||
}
|
||||
|
||||
var (
|
||||
// tableFieldsMap caches the table information retrieved from database.
|
||||
tableFieldsMap = gmap.New(true)
|
||||
errUnsupportedInsertIgnore = errors.New("unsupported method:InsertIgnore")
|
||||
errUnsupportedInsertGetId = errors.New("unsupported method:InsertGetId")
|
||||
errUnsupportedReplace = errors.New("unsupported method:Replace")
|
||||
errUnsupportedBegin = errors.New("unsupported method:Begin")
|
||||
errUnsupportedTransaction = errors.New("unsupported method:Transaction")
|
||||
errNotCondition = errors.New("there should be WHERE condition statement for UPDATE/DELETE operation")
|
||||
errNotAssignment = errors.New("there should be WHERE condition statement for Assignment operation")
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := gdb.Register(`clickhouse`, New()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// New create and returns a driver that implements gdb.Driver, which supports operations for clickhouse.
|
||||
func New() gdb.Driver {
|
||||
return &Driver{}
|
||||
}
|
||||
|
||||
// New creates and returns a database object for clickhouse.
|
||||
// It implements the interface of gdb.Driver for extra database driver installation.
|
||||
func (d *Driver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) {
|
||||
return &Driver{
|
||||
Core: core,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Open creates and returns an underlying sql.DB object for clickhouse.
|
||||
func (d *Driver) Open(config *gdb.ConfigNode) (*sql.DB, error) {
|
||||
var (
|
||||
source string
|
||||
driver = "clickhouse"
|
||||
)
|
||||
if config.Link != "" {
|
||||
source = config.Link
|
||||
} else if config.Pass != "" {
|
||||
source = fmt.Sprintf(
|
||||
"clickhouse://%s:%s@%s:%s/%s?charset=%s&debug=%t",
|
||||
config.User, url.PathEscape(config.Pass), config.Host, config.Port, config.Name, config.Charset, config.Debug)
|
||||
} else {
|
||||
source = fmt.Sprintf(
|
||||
"clickhouse://%s@%s:%s/%s?charset=%s&debug=%t",
|
||||
config.User, config.Host, config.Port, config.Name, config.Charset, config.Debug)
|
||||
}
|
||||
db, err := sql.Open(driver, source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// Tables retrieves and returns the tables of current schema.
|
||||
// It's mainly used in cli tool chain for automatically generating the models.
|
||||
func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string, err error) {
|
||||
var result gdb.Result
|
||||
link, err := d.SlaveLink(schema...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query := fmt.Sprintf("select name from `system`.tables where database = '%s'", d.GetConfig().Name)
|
||||
result, err = d.DoSelect(ctx, link, query)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, m := range result {
|
||||
tables = append(tables, m["name"].String())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TableFields retrieves and returns the fields' information of specified table of current schema.
|
||||
// Also see DriverMysql.TableFields.
|
||||
func (d *Driver) TableFields(
|
||||
ctx context.Context, table string, schema ...string,
|
||||
) (fields map[string]*gdb.TableField, err error) {
|
||||
charL, charR := d.GetChars()
|
||||
table = gstr.Trim(table, charL+charR)
|
||||
if gstr.Contains(table, " ") {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "function TableFields supports only single table operations")
|
||||
}
|
||||
useSchema := d.GetSchema()
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
useSchema = schema[0]
|
||||
}
|
||||
v := tableFieldsMap.GetOrSetFuncLock(
|
||||
fmt.Sprintf(`clickhouse_table_fields_%s_%s@group:%s`, table, useSchema, d.GetGroup()),
|
||||
func() interface{} {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
)
|
||||
if link, err = d.SlaveLink(useSchema); err != nil {
|
||||
return nil
|
||||
}
|
||||
getColumnsSql := fmt.Sprintf("select name,position,default_expression,comment,type from `system`.columns c where database = '%s' and `table` = '%s'", d.GetConfig().Name, table)
|
||||
result, err = d.DoSelect(ctx, link, getColumnsSql)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
fields = make(map[string]*gdb.TableField)
|
||||
for _, m := range result {
|
||||
var (
|
||||
isNull = false
|
||||
fieldType = m["type"].String()
|
||||
)
|
||||
// in clickhouse , filed type like is Nullable(int)
|
||||
fieldsResult, _ := gregex.MatchString(`^Nullable\((.*?)\)`, fieldType)
|
||||
if len(fieldsResult) == 2 {
|
||||
isNull = true
|
||||
fieldType = fieldsResult[1]
|
||||
}
|
||||
fields[m["name"].String()] = &gdb.TableField{
|
||||
Index: m["position"].Int(),
|
||||
Name: m["name"].String(),
|
||||
Default: m["default_expression"].Val(),
|
||||
Comment: m["comment"].String(),
|
||||
//Key: m["Key"].String(),
|
||||
Type: fieldType,
|
||||
Null: isNull,
|
||||
}
|
||||
}
|
||||
return fields
|
||||
},
|
||||
)
|
||||
if v != nil {
|
||||
fields = v.(map[string]*gdb.TableField)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FilteredLink retrieves and returns filtered `linkInfo` that can be using for
|
||||
// logging or tracing purpose.
|
||||
func (d *Driver) FilteredLink() string {
|
||||
linkInfo := d.GetConfig().Link
|
||||
if linkInfo == "" {
|
||||
return ""
|
||||
}
|
||||
s, _ := gregex.ReplaceString(
|
||||
`(.+?):(.+)@tcp(.+)`,
|
||||
`$1:xxx@tcp$3`,
|
||||
linkInfo,
|
||||
)
|
||||
return s
|
||||
}
|
||||
|
||||
// PingMaster pings the master node to check authentication or keeps the connection alive.
|
||||
func (d *Driver) PingMaster() error {
|
||||
conn, err := d.Master()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.ping(conn)
|
||||
}
|
||||
|
||||
// PingSlave pings the slave node to check authentication or keeps the connection alive.
|
||||
func (d *Driver) PingSlave() error {
|
||||
conn, err := d.Slave()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.ping(conn)
|
||||
}
|
||||
|
||||
// ping Returns the Clickhouse specific error.
|
||||
func (d *Driver) ping(conn *sql.DB) error {
|
||||
err := conn.Ping()
|
||||
if exception, ok := err.(*clickhouse.Exception); ok {
|
||||
return errors.New(fmt.Sprintf("[%d]%s", exception.Code, exception.Message))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// DoFilter handles the sql before posts it to database.
|
||||
func (d *Driver) DoFilter(
|
||||
ctx context.Context, link gdb.Link, originSql string, args []interface{},
|
||||
) (newSql string, newArgs []interface{}, err error) {
|
||||
if len(args) == 0 {
|
||||
return originSql, args, nil
|
||||
}
|
||||
var index int
|
||||
// Convert placeholder char '?' to string "$x".
|
||||
originSql, _ = gregex.ReplaceStringFunc(`\?`, originSql, func(s string) string {
|
||||
index++
|
||||
return fmt.Sprintf(`$%d`, index)
|
||||
})
|
||||
// replace STD SQL to Clickhouse SQL grammar
|
||||
parsedStmt, err := sqlparser.NewParser(strings.NewReader(originSql)).ParseStatement()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
switch stmt := parsedStmt.(type) {
|
||||
case *sqlparser.UpdateStatement:
|
||||
// MySQL eg: UPDATE visits SET xxx
|
||||
// Clickhouse eg: ALTER TABLE visits UPDATE xxx
|
||||
newSql, err = d.doFilterUpdate(stmt)
|
||||
if err != nil {
|
||||
return originSql, args, err
|
||||
}
|
||||
return newSql, args, nil
|
||||
case *sqlparser.DeleteStatement:
|
||||
// MySQL eg: DELETE FROM VISIT
|
||||
// Clickhouse eg: ALTER TABLE VISIT DELETE WHERE filter_expr
|
||||
newSql, err = d.doFilterDelete(stmt)
|
||||
if err != nil {
|
||||
return originSql, args, err
|
||||
}
|
||||
return newSql, args, nil
|
||||
}
|
||||
return originSql, args, nil
|
||||
}
|
||||
|
||||
func (d *Driver) doFilterDelete(stmt *sqlparser.DeleteStatement) (string, error) {
|
||||
if stmt.Condition == nil {
|
||||
return "", errNotCondition
|
||||
}
|
||||
var (
|
||||
condition = stmt.Condition.String()
|
||||
tableName = stmt.TableName
|
||||
)
|
||||
newSql := fmt.Sprintf("ALTER TABLE %s DELETE WHERE %s", tableName, condition)
|
||||
return newSql, nil
|
||||
}
|
||||
|
||||
func (d *Driver) doFilterUpdate(stmt *sqlparser.UpdateStatement) (string, error) {
|
||||
if stmt.Condition == nil {
|
||||
return "", errNotCondition
|
||||
}
|
||||
if len(stmt.Assignments) == 0 {
|
||||
return "", errNotAssignment
|
||||
}
|
||||
var (
|
||||
condition = stmt.Condition.String()
|
||||
assignment string
|
||||
tableName = stmt.TableName
|
||||
assignments = []string{}
|
||||
)
|
||||
for _, item := range stmt.Assignments {
|
||||
assignments = append(assignments, item.String())
|
||||
}
|
||||
assignment = strings.Join(assignments, ",")
|
||||
newSql := fmt.Sprintf("ALTER TABLE %s UPDATE %s WHERE %s", tableName, assignment, condition)
|
||||
return newSql, nil
|
||||
}
|
||||
|
||||
// DoCommit commits current sql and arguments to underlying sql driver.
|
||||
func (d *Driver) DoCommit(ctx context.Context, in gdb.DoCommitInput) (out gdb.DoCommitOutput, err error) {
|
||||
ctx = d.InjectIgnoreResult(ctx)
|
||||
return d.Core.DoCommit(ctx, in)
|
||||
}
|
||||
|
||||
func (d *Driver) DoInsert(
|
||||
ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
|
||||
) (result sql.Result, err error) {
|
||||
var (
|
||||
keys []string // Field names.
|
||||
valueHolder = make([]string, 0)
|
||||
)
|
||||
// Handle the field names and placeholders.
|
||||
for k := range list[0] {
|
||||
keys = append(keys, k)
|
||||
valueHolder = append(valueHolder, "?")
|
||||
}
|
||||
// Prepare the batch result pointer.
|
||||
var (
|
||||
charL, charR = d.Core.GetChars()
|
||||
keysStr = charL + strings.Join(keys, charR+","+charL) + charR
|
||||
holderStr = strings.Join(valueHolder, ",")
|
||||
tx = &gdb.TX{}
|
||||
stdSqlResult sql.Result
|
||||
stmt *gdb.Stmt
|
||||
)
|
||||
tx, err = d.Core.Begin(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
stmt, err = tx.Prepare(fmt.Sprintf(
|
||||
"INSERT INTO %s(%s) VALUES (%s)",
|
||||
d.QuotePrefixTableName(table), keysStr,
|
||||
holderStr,
|
||||
))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for i := 0; i < len(list); i++ {
|
||||
params := make([]interface{}, 0) // Values that will be committed to underlying database driver.
|
||||
for _, k := range keys {
|
||||
params = append(params, list[i][k])
|
||||
}
|
||||
// Prepare is allowed to execute only once in a transaction opened by clickhouse
|
||||
stdSqlResult, err = stmt.ExecContext(ctx, params...)
|
||||
if err != nil {
|
||||
return stdSqlResult, err
|
||||
}
|
||||
}
|
||||
return stdSqlResult, tx.Commit()
|
||||
}
|
||||
|
||||
// ConvertDataForRecord converting for any data that will be inserted into table/collection as a record.
|
||||
func (d *Driver) ConvertDataForRecord(ctx context.Context, value interface{}) map[string]interface{} {
|
||||
// Clickhouse does not need to preprocess the value and can be inserted directly
|
||||
// So it is not processed here
|
||||
return gconv.Map(value, gdb.OrmTagForStruct)
|
||||
}
|
||||
|
||||
func (d *Driver) ConvertDataForRecordValue(ctx context.Context, value interface{}) interface{} {
|
||||
// Clickhouse does not need to preprocess the value and can be inserted directly
|
||||
// So it is not processed here
|
||||
return value
|
||||
}
|
||||
|
||||
// InsertIgnore Other queries for modifying data parts are not supported: REPLACE, MERGE, UPSERT, INSERT UPDATE.
|
||||
func (d *Driver) InsertIgnore(ctx context.Context, table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return nil, errUnsupportedInsertIgnore
|
||||
}
|
||||
|
||||
// InsertAndGetId Other queries for modifying data parts are not supported: REPLACE, MERGE, UPSERT, INSERT UPDATE.
|
||||
func (d *Driver) InsertAndGetId(ctx context.Context, table string, data interface{}, batch ...int) (int64, error) {
|
||||
return 0, errUnsupportedInsertGetId
|
||||
}
|
||||
|
||||
// Replace Other queries for modifying data parts are not supported: REPLACE, MERGE, UPSERT, INSERT UPDATE.
|
||||
func (d *Driver) Replace(ctx context.Context, table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return nil, errUnsupportedReplace
|
||||
}
|
||||
|
||||
func (d *Driver) Begin(ctx context.Context) (tx *gdb.TX, err error) {
|
||||
return nil, errUnsupportedBegin
|
||||
}
|
||||
|
||||
func (d *Driver) Transaction(ctx context.Context, f func(ctx context.Context, tx *gdb.TX) error) error {
|
||||
return errUnsupportedTransaction
|
||||
}
|
||||
|
||||
436
contrib/drivers/clickhouse/clickhouse_test.go
Normal file
436
contrib/drivers/clickhouse/clickhouse_test.go
Normal file
@ -0,0 +1,436 @@
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/grand"
|
||||
)
|
||||
|
||||
const (
|
||||
sqlVisitsDDL = `
|
||||
CREATE TABLE IF NOT EXISTS visits (
|
||||
id UInt64,
|
||||
duration Float64,
|
||||
url String,
|
||||
created DateTime
|
||||
) ENGINE = MergeTree()
|
||||
PRIMARY KEY id
|
||||
ORDER BY id
|
||||
`
|
||||
dimSqlDDL = `
|
||||
CREATE TABLE IF NOT EXISTS dim (
|
||||
"code" String COMMENT '编码',
|
||||
"translation" String COMMENT '译文',
|
||||
"superior" UInt64 COMMENT '上级ID',
|
||||
"row_number" UInt16 COMMENT '行号',
|
||||
"is_active" UInt8 COMMENT '是否激活',
|
||||
"is_preset" UInt8 COMMENT '是否预置',
|
||||
"category" String COMMENT '类别',
|
||||
"tree_path" Array(String) COMMENT '树路径',
|
||||
"id" UInt64 COMMENT '代理主键ID',
|
||||
"scd" UInt64 COMMENT '缓慢变化维ID',
|
||||
"version" UInt64 COMMENT 'Merge版本ID',
|
||||
"sign" Int8 COMMENT '标识位',
|
||||
"created_by" UInt64 COMMENT '创建者ID',
|
||||
"created_at" DateTime64(3,'Asia/Shanghai') COMMENT '创建时间',
|
||||
"updated_by" UInt64 COMMENT '最后修改者ID',
|
||||
"updated_at" DateTime64(3,'Asia/Shanghai') COMMENT '最后修改时间',
|
||||
"updated_tick" UInt16 COMMENT '累计修改次数'
|
||||
) ENGINE = ReplacingMergeTree("version")
|
||||
ORDER BY ("id","scd")
|
||||
COMMENT '会计准则';
|
||||
`
|
||||
dimSqlDML = `
|
||||
insert into dim (code, translation, superior, row_number, is_active, is_preset, category, tree_path, id, scd, version, sign, created_by, created_at, updated_by, updated_at, updated_tick)
|
||||
values ('CN', '{"zh_CN":"中国大陆会计准则","en_US":"Chinese mainland accounting legislation"}', 0, 1, 1, 1, 1, '[''CN'']', 607972403489804288, 0, 0, 0, 607536279118155777, '2017-09-06 00:00:00', 607536279118155777, '2017-09-06 00:00:00', 0),
|
||||
('HK', '{"zh_CN":"中国香港会计准则","en_US":"Chinese Hong Kong accounting legislation"}', 0, 2, 1, 1, 1, '[''HK'']', 607972558544834566, 0, 0, 0, 607536279118155777, '2017-09-06 00:00:00', 607536279118155777, '2017-09-06 00:00:00', 0);
|
||||
`
|
||||
factSqlDDL = `
|
||||
CREATE TABLE IF NOT EXISTS fact (
|
||||
"adjustment_level" UInt64 COMMENT '调整层ID',
|
||||
"data_version" UInt64 COMMENT '数据版本ID',
|
||||
"accounting_legislation" UInt64 COMMENT '会计准则ID',
|
||||
"fiscal_year" UInt16 COMMENT '会计年度',
|
||||
"fiscal_period" UInt8 COMMENT '会计期间',
|
||||
"fiscal_year_period" UInt32 COMMENT '会计年度期间',
|
||||
"legal_entity" UInt64 COMMENT '法人主体ID',
|
||||
"cost_center" UInt64 COMMENT '成本中心ID',
|
||||
"legal_entity_partner" UInt64 COMMENT '内部关联方ID',
|
||||
"financial_posting" UInt64 COMMENT '凭证头ID',
|
||||
"line" UInt16 COMMENT '行号',
|
||||
"general_ledger_account" UInt64 COMMENT '总账科目ID',
|
||||
"debit" Decimal64(9) COMMENT '借方金额',
|
||||
"credit" Decimal64(9) COMMENT '贷方金额',
|
||||
"transaction_currency" UInt64 COMMENT '交易币种ID',
|
||||
"debit_tc" Decimal64(9) COMMENT '借方金额(交易币种)',
|
||||
"credit_tc" Decimal64(9) COMMENT '贷方金额(交易币种)',
|
||||
"posting_date" Date32 COMMENT '过账日期',
|
||||
"gc_year" UInt16 COMMENT '公历年',
|
||||
"gc_quarter" UInt8 COMMENT '公历季',
|
||||
"gc_month" UInt8 COMMENT '公历月',
|
||||
"gc_week" UInt8 COMMENT '公历周',
|
||||
"raw_info" String COMMENT '源信息',
|
||||
"summary" String COMMENT '摘要',
|
||||
"id" UInt64 COMMENT '代理主键ID',
|
||||
"version" UInt64 COMMENT 'Merge版本ID',
|
||||
"sign" Int8 COMMENT '标识位'
|
||||
) ENGINE = ReplacingMergeTree("version")
|
||||
ORDER BY ("adjustment_level","data_version","legal_entity","fiscal_year","fiscal_period","financial_posting","line")
|
||||
PARTITION BY ("adjustment_level","data_version","legal_entity","fiscal_year","fiscal_period")
|
||||
COMMENT '数据主表';
|
||||
`
|
||||
factSqlDML = `
|
||||
insert into fact (adjustment_level, data_version, accounting_legislation, fiscal_year, fiscal_period, fiscal_year_period, legal_entity, cost_center, legal_entity_partner, financial_posting, line, general_ledger_account, debit, credit, transaction_currency, debit_tc, credit_tc, posting_date, gc_year, gc_quarter, gc_month, gc_week, raw_info, summary, id, version, sign)
|
||||
values (607970943242866688, 607973669943119880, 607972403489804288, 2022, 3, 202203, 607974511316307985, 0, 607976190010986520, 607996702456025136, 1, 607985607569838111, 8674.39, 0, 607974898261823505, 8674.39, 0, '2022-03-05', 2022, 1, 3, 11, '{}', '摘要', 607992882741121073, 0, 0),
|
||||
(607970943242866688, 607973669943119880, 607972403489804288, 2022, 4, 202204, 607974511316307985, 0, 607976190010986520, 607993586419503145, 1, 607985607569838111, 9999.88, 0, 607974898261823505, 9999.88, 0, '2022-04-10', 2022, 2, 4, 18, '{}', '摘要', 607996939140599857, 0, 0);
|
||||
`
|
||||
expmSqlDDL = `
|
||||
CREATE TABLE IF NOT EXISTS data_type (
|
||||
Col1 UInt8
|
||||
, Col2 String
|
||||
, Col3 FixedString(3)
|
||||
, Col4 UUID
|
||||
, Col5 Map(String, UInt8)
|
||||
, Col6 Array(String)
|
||||
, Col7 Tuple(String, UInt8, Array(Map(String, String)))
|
||||
, Col8 DateTime
|
||||
) ENGINE = MergeTree()
|
||||
PRIMARY KEY Col4
|
||||
ORDER BY Col4
|
||||
`
|
||||
)
|
||||
|
||||
func clickhouseConfigDB() gdb.DB {
|
||||
connect, err := gdb.New(gdb.ConfigNode{
|
||||
Host: "127.0.0.1",
|
||||
Port: "9000",
|
||||
User: "default",
|
||||
Name: "default",
|
||||
Type: "clickhouse",
|
||||
})
|
||||
gtest.AssertNil(err)
|
||||
gtest.AssertNE(connect, nil)
|
||||
return connect
|
||||
}
|
||||
|
||||
func createClickhouseTableVisits(connect gdb.DB) error {
|
||||
_, err := connect.Exec(context.Background(), sqlVisitsDDL)
|
||||
return err
|
||||
}
|
||||
|
||||
func createClickhouseTableDim(connect gdb.DB) error {
|
||||
_, err := connect.Exec(context.Background(), dimSqlDDL)
|
||||
return err
|
||||
}
|
||||
|
||||
func createClickhouseTableFact(connect gdb.DB) error {
|
||||
_, err := connect.Exec(context.Background(), factSqlDDL)
|
||||
return err
|
||||
}
|
||||
|
||||
func createClickhouseExampleTable(connect gdb.DB) error {
|
||||
_, err := connect.Exec(context.Background(), expmSqlDDL)
|
||||
return err
|
||||
}
|
||||
|
||||
func dropClickhouseTableVisits(conn gdb.DB) {
|
||||
sqlStr := fmt.Sprintf("DROP TABLE IF EXISTS `visits`")
|
||||
_, _ = conn.Exec(context.Background(), sqlStr)
|
||||
}
|
||||
|
||||
func dropClickhouseTableDim(conn gdb.DB) {
|
||||
sqlStr := fmt.Sprintf("DROP TABLE IF EXISTS `dim`")
|
||||
_, _ = conn.Exec(context.Background(), sqlStr)
|
||||
}
|
||||
|
||||
func dropClickhouseTableFact(conn gdb.DB) {
|
||||
sqlStr := fmt.Sprintf("DROP TABLE IF EXISTS `fact`")
|
||||
_, _ = conn.Exec(context.Background(), sqlStr)
|
||||
}
|
||||
|
||||
func dropClickhouseExampleTable(conn gdb.DB) {
|
||||
sqlStr := fmt.Sprintf("DROP TABLE IF EXISTS `data_type`")
|
||||
_, _ = conn.Exec(context.Background(), sqlStr)
|
||||
}
|
||||
|
||||
func TestDriverClickhouse_Create(t *testing.T) {
|
||||
gtest.AssertNil(createClickhouseTableVisits(clickhouseConfigDB()))
|
||||
}
|
||||
|
||||
func TestDriverClickhouse_New(t *testing.T) {
|
||||
connect := clickhouseConfigDB()
|
||||
gtest.AssertNE(connect, nil)
|
||||
gtest.AssertNil(connect.PingMaster())
|
||||
gtest.AssertNil(connect.PingSlave())
|
||||
}
|
||||
|
||||
func TestDriverClickhouse_OpenLink_Ping(t *testing.T) {
|
||||
connect := clickhouseConfigDB()
|
||||
gtest.AssertNE(connect, nil)
|
||||
gtest.AssertNil(connect.PingMaster())
|
||||
}
|
||||
|
||||
func TestDriverClickhouse_Tables(t *testing.T) {
|
||||
connect := clickhouseConfigDB()
|
||||
gtest.AssertEQ(createClickhouseTableVisits(connect), nil)
|
||||
defer dropClickhouseTableVisits(connect)
|
||||
tables, err := connect.Tables(context.Background())
|
||||
gtest.AssertNil(err)
|
||||
gtest.AssertNE(len(tables), 0)
|
||||
}
|
||||
|
||||
func TestDriverClickhouse_TableFields_Use_Config(t *testing.T) {
|
||||
connect := clickhouseConfigDB()
|
||||
gtest.AssertNil(createClickhouseTableVisits(connect))
|
||||
defer dropClickhouseTableVisits(connect)
|
||||
field, err := connect.TableFields(context.Background(), "visits")
|
||||
gtest.AssertNil(err)
|
||||
gtest.AssertEQ(len(field), 4)
|
||||
gtest.AssertNQ(field, nil)
|
||||
}
|
||||
|
||||
func TestDriverClickhouse_TableFields_Use_Link(t *testing.T) {
|
||||
connect := clickhouseConfigDB()
|
||||
gtest.AssertNil(createClickhouseTableVisits(connect))
|
||||
defer dropClickhouseTableVisits(connect)
|
||||
field, err := connect.TableFields(context.Background(), "visits")
|
||||
gtest.AssertNil(err)
|
||||
gtest.AssertEQ(len(field), 4)
|
||||
gtest.AssertNQ(field, nil)
|
||||
}
|
||||
|
||||
func TestDriverClickhouse_Transaction(t *testing.T) {
|
||||
connect := clickhouseConfigDB()
|
||||
defer dropClickhouseTableVisits(connect)
|
||||
gtest.AssertNE(connect.Transaction(context.Background(), func(ctx context.Context, tx *gdb.TX) error {
|
||||
return nil
|
||||
}), nil)
|
||||
}
|
||||
|
||||
func TestDriverClickhouse_InsertIgnore(t *testing.T) {
|
||||
connect := clickhouseConfigDB()
|
||||
_, err := connect.InsertIgnore(context.Background(), "", nil)
|
||||
gtest.AssertEQ(err, errUnsupportedInsertIgnore)
|
||||
}
|
||||
|
||||
func TestDriverClickhouse_InsertAndGetId(t *testing.T) {
|
||||
connect := clickhouseConfigDB()
|
||||
_, err := connect.InsertAndGetId(context.Background(), "", nil)
|
||||
gtest.AssertEQ(err, errUnsupportedInsertGetId)
|
||||
}
|
||||
|
||||
func TestDriverClickhouse_InsertOne(t *testing.T) {
|
||||
connect := clickhouseConfigDB()
|
||||
gtest.AssertEQ(createClickhouseTableVisits(connect), nil)
|
||||
defer dropClickhouseTableVisits(connect)
|
||||
_, err := connect.Model("visits").Data(g.Map{
|
||||
"duration": float64(grand.Intn(999)),
|
||||
"url": gconv.String(grand.Intn(999)),
|
||||
"created": time.Now(),
|
||||
}).Insert()
|
||||
gtest.AssertNil(err)
|
||||
}
|
||||
|
||||
func TestDriverClickhouse_InsertMany(t *testing.T) {
|
||||
connect := clickhouseConfigDB()
|
||||
gtest.AssertEQ(createClickhouseTableVisits(connect), nil)
|
||||
defer dropClickhouseTableVisits(connect)
|
||||
tx, err := connect.Begin(context.Background())
|
||||
gtest.AssertEQ(err, errUnsupportedBegin)
|
||||
gtest.AssertNil(tx)
|
||||
}
|
||||
|
||||
func TestDriverClickhouse_Insert(t *testing.T) {
|
||||
connect := clickhouseConfigDB()
|
||||
gtest.AssertEQ(createClickhouseTableVisits(connect), nil)
|
||||
defer dropClickhouseTableVisits(connect)
|
||||
type insertItem struct {
|
||||
Id uint64 `orm:"id"`
|
||||
Duration float64 `orm:"duration"`
|
||||
Url string `orm:"url"`
|
||||
Created time.Time `orm:"created"`
|
||||
}
|
||||
var (
|
||||
insertUrl = "https://goframe.org"
|
||||
total = 0
|
||||
item = insertItem{
|
||||
Duration: 1,
|
||||
Url: insertUrl,
|
||||
Created: time.Now(),
|
||||
}
|
||||
)
|
||||
_, err := connect.Model("visits").Data(item).Insert()
|
||||
gtest.AssertNil(err)
|
||||
_, err = connect.Model("visits").Data(item).Save()
|
||||
gtest.AssertNil(err)
|
||||
total, err = connect.Model("visits").Count()
|
||||
gtest.AssertNil(err)
|
||||
gtest.AssertEQ(total, 2)
|
||||
var list []*insertItem
|
||||
for i := 0; i < 50; i++ {
|
||||
list = append(list, &insertItem{
|
||||
Duration: float64(grand.Intn(999)),
|
||||
Url: insertUrl,
|
||||
Created: time.Now(),
|
||||
})
|
||||
}
|
||||
_, err = connect.Model("visits").Data(list).Insert()
|
||||
gtest.AssertNil(err)
|
||||
_, err = connect.Model("visits").Data(list).Save()
|
||||
gtest.AssertNil(err)
|
||||
total, err = connect.Model("visits").Count()
|
||||
gtest.AssertNil(err)
|
||||
gtest.AssertEQ(total, 102)
|
||||
}
|
||||
|
||||
func TestDriverClickhouse_Insert_Use_Exec(t *testing.T) {
|
||||
connect := clickhouseConfigDB()
|
||||
gtest.AssertEQ(createClickhouseTableFact(connect), nil)
|
||||
defer dropClickhouseTableFact(connect)
|
||||
_, err := connect.Exec(context.Background(), factSqlDML)
|
||||
gtest.AssertNil(err)
|
||||
}
|
||||
|
||||
func TestDriverClickhouse_Delete(t *testing.T) {
|
||||
connect := clickhouseConfigDB()
|
||||
gtest.AssertEQ(createClickhouseTableVisits(connect), nil)
|
||||
defer dropClickhouseTableVisits(connect)
|
||||
_, err := connect.Model("visits").Where("created >", "2021-01-01 00:00:00").Delete()
|
||||
gtest.AssertNil(err)
|
||||
}
|
||||
|
||||
func TestDriverClickhouse_Update(t *testing.T) {
|
||||
connect := clickhouseConfigDB()
|
||||
gtest.AssertEQ(createClickhouseTableVisits(connect), nil)
|
||||
defer dropClickhouseTableVisits(connect)
|
||||
_, err := connect.Model("visits").Where("created > ", "2021-01-01 15:15:15").Data(g.Map{
|
||||
"created": time.Now().Format("2006-01-02 15:04:05"),
|
||||
}).Update()
|
||||
gtest.AssertNil(err)
|
||||
}
|
||||
|
||||
func TestDriverClickhouse_Replace(t *testing.T) {
|
||||
connect := clickhouseConfigDB()
|
||||
_, err := connect.Replace(context.Background(), "", nil)
|
||||
gtest.AssertEQ(err, errUnsupportedReplace)
|
||||
}
|
||||
|
||||
func TestDriverClickhouse_DoFilter(t *testing.T) {
|
||||
rawSQL := "select * from visits where 1 = 1"
|
||||
this := Driver{}
|
||||
replaceSQL, _, err := this.DoFilter(nil, nil, rawSQL, []interface{}{1})
|
||||
gtest.AssertNil(err)
|
||||
gtest.AssertEQ(rawSQL, replaceSQL)
|
||||
|
||||
// this SQL can't run ,clickhouse will report an error because there is no WHERE statement
|
||||
rawSQL = "update visit set url = '1'"
|
||||
replaceSQL, _, err = this.DoFilter(nil, nil, rawSQL, []interface{}{1})
|
||||
gtest.AssertEQ(err, errNotCondition)
|
||||
|
||||
// this SQL can't run ,clickhouse will report an error because there is no WHERE statement
|
||||
rawSQL = "delete from visit"
|
||||
replaceSQL, _, err = this.DoFilter(nil, nil, rawSQL, []interface{}{1})
|
||||
gtest.AssertEQ(err, errNotCondition)
|
||||
|
||||
rawSQL = "update visit set url = '1' where url = '0'"
|
||||
replaceSQL, _, err = this.DoFilter(nil, nil, rawSQL, []interface{}{1})
|
||||
gtest.AssertNil(err)
|
||||
gtest.AssertEQ(replaceSQL, "ALTER TABLE visit UPDATE url = '1' WHERE url = '0'")
|
||||
|
||||
rawSQL = "delete from visit where url='0'"
|
||||
replaceSQL, _, err = this.DoFilter(nil, nil, rawSQL, []interface{}{1})
|
||||
gtest.AssertNil(err)
|
||||
gtest.AssertEQ(replaceSQL, "ALTER TABLE visit DELETE WHERE url = '0'")
|
||||
}
|
||||
|
||||
func TestDriverClickhouse_Select(t *testing.T) {
|
||||
connect := clickhouseConfigDB()
|
||||
gtest.AssertNil(createClickhouseTableVisits(connect))
|
||||
defer dropClickhouseTableVisits(connect)
|
||||
_, err := connect.Model("visits").Data(g.Map{
|
||||
"url": "goframe.org",
|
||||
"duration": float64(1),
|
||||
}).Insert()
|
||||
gtest.AssertNil(err)
|
||||
temp, err := connect.Model("visits").Where("url", "goframe.org").Where("duration >= ", 1).One()
|
||||
gtest.AssertNil(err)
|
||||
gtest.AssertEQ(temp.IsEmpty(), false)
|
||||
_, err = connect.Model("visits").Data(g.Map{
|
||||
"url": "goframe.org",
|
||||
"duration": float64(2),
|
||||
}).Insert()
|
||||
gtest.AssertNil(err)
|
||||
data, err := connect.Model("visits").Where("url", "goframe.org").Where("duration >= ", 1).All()
|
||||
gtest.AssertNil(err)
|
||||
gtest.AssertEQ(len(data), 2)
|
||||
}
|
||||
|
||||
func TestDriverClickhouse_Exec_OPTIMIZE(t *testing.T) {
|
||||
connect := clickhouseConfigDB()
|
||||
gtest.AssertNil(createClickhouseTableVisits(connect))
|
||||
defer dropClickhouseTableVisits(connect)
|
||||
sqlStr := "OPTIMIZE table visits"
|
||||
_, err := connect.Exec(context.Background(), sqlStr)
|
||||
gtest.AssertNil(err)
|
||||
}
|
||||
|
||||
func TestDriverClickhouse_ExecInsert(t *testing.T) {
|
||||
connect := clickhouseConfigDB()
|
||||
gtest.AssertEQ(createClickhouseTableDim(connect), nil)
|
||||
defer dropClickhouseTableDim(connect)
|
||||
_, err := connect.Exec(context.Background(), dimSqlDML)
|
||||
gtest.AssertNil(err)
|
||||
}
|
||||
|
||||
func TestDriverClickhouse_BatchInsert(t *testing.T) {
|
||||
// example from
|
||||
// https://github.com/ClickHouse/clickhouse-go/blob/v2/examples/std/batch/main.go
|
||||
connect := clickhouseConfigDB()
|
||||
gtest.AssertNil(createClickhouseExampleTable(connect))
|
||||
defer dropClickhouseExampleTable(connect)
|
||||
insertData := []g.Map{}
|
||||
for i := 0; i < 10000; i++ {
|
||||
insertData = append(insertData, g.Map{
|
||||
"Col1": uint8(42),
|
||||
"Col2": "ClickHouse",
|
||||
"Col3": "Inc",
|
||||
"Col4": uuid.New(),
|
||||
"Col5": map[string]uint8{"key": 1}, // Map(String, UInt8)
|
||||
"Col6": []string{"Q", "W", "E", "R", "T", "Y"}, // Array(String)
|
||||
"Col7": []interface{}{ // Tuple(String, UInt8, Array(Map(String, String)))
|
||||
"String Value", uint8(5), []map[string]string{
|
||||
map[string]string{"key": "value"},
|
||||
map[string]string{"key": "value"},
|
||||
map[string]string{"key": "value"},
|
||||
},
|
||||
},
|
||||
"Col8": time.Now(),
|
||||
})
|
||||
}
|
||||
_, err := connect.Model("data_type").Data(insertData).Insert()
|
||||
gtest.AssertNil(err)
|
||||
count, err := connect.Model("data_type").Where("Col2", "ClickHouse").Where("Col3", "Inc").Count()
|
||||
gtest.AssertNil(err)
|
||||
gtest.AssertEQ(count, 10000)
|
||||
}
|
||||
|
||||
func TestDriverClickhouse_Open(t *testing.T) {
|
||||
// link
|
||||
// DSM
|
||||
// clickhouse://username:password@host1:9000,host2:9000/database?dial_timeout=200ms&max_execution_time=60
|
||||
link := "clickhouse://default@127.0.0.1:9000,127.0.0.1:9000/default?dial_timeout=200ms&max_execution_time=60"
|
||||
db, err := gdb.New(gdb.ConfigNode{
|
||||
Link: link,
|
||||
Type: "clickhouse",
|
||||
})
|
||||
gtest.AssertNil(err)
|
||||
gtest.AssertNil(db.PingMaster())
|
||||
}
|
||||
@ -3,11 +3,10 @@ module github.com/gogf/gf/contrib/drivers/clickhouse/v2
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/ClickHouse/clickhouse-go v1.5.2
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/stretchr/testify v1.7.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.0.12
|
||||
github.com/gogf/gf/v2 v2.0.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/longbridgeapp/sqlparser v0.3.1
|
||||
)
|
||||
|
||||
replace github.com/gogf/gf/v2 => ../../../
|
||||
|
||||
@ -1,32 +1,201 @@
|
||||
github.com/ClickHouse/clickhouse-go v1.5.2 h1:yXgaOZ8WEHrd+okvZXjzulSt1zS33nM4ujfx9lVncl8=
|
||||
github.com/ClickHouse/clickhouse-go v1.5.2/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
|
||||
github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk=
|
||||
github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
|
||||
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/ClickHouse/clickhouse-go v1.5.3 h1:Vok8zUb/wlqc9u8oEqQzBMBRDoFd8NxPRqgYEqMnV88=
|
||||
github.com/ClickHouse/clickhouse-go v1.5.3/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.0.12 h1:Nbl/NZwoM6LGJm7smNBgvtdr/rxjlIssSW3eG/Nmb9E=
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.0.12/go.mod h1:u4RoNQLLM2W6hNSPYrIESLJqaWSInZVmfM+MlaAhXcg=
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
|
||||
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E=
|
||||
github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||
github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg=
|
||||
github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
|
||||
github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0=
|
||||
github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/longbridgeapp/sqlparser v0.3.1 h1:iWOZWGIFgQrJRgobLXUNJdvqGRpbVXkyKUKUA5CNJBE=
|
||||
github.com/longbridgeapp/sqlparser v0.3.1/go.mod h1:GIHaUq8zvYyHLCLMJJykx1CdM6LHtkUih/QaJXySSx4=
|
||||
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mkevac/debugcharts v0.0.0-20191222103121-ae1c48aa8615/go.mod h1:Ad7oeElCZqA1Ufj0U9/liOF4BtVepxRcTvr2ey7zTvM=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
|
||||
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/paulmach/orb v0.4.0 h1:ilp1MQjRapLJ1+qcays1nZpe0mvkCY+b8JU/qBKRZ1A=
|
||||
github.com/paulmach/orb v0.4.0/go.mod h1:FkcWtplUAIVqAuhAOV2d3rpbnQyliDOjOcLW9dUrfdU=
|
||||
github.com/paulmach/protoscan v0.2.1-0.20210522164731-4e53c6875432/go.mod h1:2sV+uZ/oQh66m4XJVZm5iqUZ62BN88Ex1E+TTS0nLzI=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
|
||||
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/shirou/gopsutil v2.19.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
|
||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.opentelemetry.io/otel v1.0.0/go.mod h1:AjRVh9A5/5DE7S+mZtTR6t8vpKKryam+0lREnfmS4cg=
|
||||
go.opentelemetry.io/otel v1.4.1 h1:QbINgGDDcoQUoMJa2mMaWno49lja9sHwp6aoa2n3a4g=
|
||||
go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4=
|
||||
go.opentelemetry.io/otel/sdk v1.0.0 h1:BNPMYUONPNbLneMttKSjQhOTlFLOD9U22HNG1KrIN2Y=
|
||||
go.opentelemetry.io/otel/sdk v1.0.0/go.mod h1:PCrDHlSy5x1kjezSdL37PhbFUMjrsLRshJ2zCzeXwbM=
|
||||
go.opentelemetry.io/otel/trace v1.0.0/go.mod h1:PXTWqayeFUlJV1YDNhsJYB184+IvAH814St6o6ajzIs=
|
||||
go.opentelemetry.io/otel/trace v1.4.1 h1:O+16qcdTrT7zxv2J6GejTPFinSwA++cYerC5iSiF8EQ=
|
||||
go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwYZjulEtsmhFc=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191220220014-0732a990476f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211110154304-99a53858aa08 h1:WecRHqgE09JBkh/584XIE6PMz5KKE/vER4izNUi30AQ=
|
||||
golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 h1:GLw7MR8AfAG2GmGcmVgObFOHXYypgGjnGno25RDwn3Y=
|
||||
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@ -4,7 +4,7 @@ go 1.15
|
||||
|
||||
require (
|
||||
github.com/denisenkom/go-mssqldb v0.11.0
|
||||
github.com/gogf/gf/v2 v2.0.0-rc
|
||||
github.com/gogf/gf/v2 v2.0.0
|
||||
)
|
||||
|
||||
replace github.com/gogf/gf/v2 => ../../../
|
||||
|
||||
@ -2,6 +2,6 @@ module github.com/gogf/gf/contrib/drivers/mysql/v2
|
||||
|
||||
go 1.15
|
||||
|
||||
require github.com/gogf/gf/v2 v2.0.0-rc
|
||||
require github.com/gogf/gf/v2 v2.0.0
|
||||
|
||||
replace github.com/gogf/gf/v2 => ../../../
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/drivers/oracle/v2
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.0.0-rc
|
||||
github.com/gogf/gf/v2 v2.0.0
|
||||
github.com/mattn/go-oci8 v0.1.1
|
||||
)
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/drivers/pgsql/v2
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.0.0-rc
|
||||
github.com/gogf/gf/v2 v2.0.0
|
||||
github.com/lib/pq v1.10.4
|
||||
)
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/drivers/sqlite/v2
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.0.0-rc
|
||||
github.com/gogf/gf/v2 v2.0.0
|
||||
github.com/mattn/go-sqlite3 v1.14.10
|
||||
)
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/registry/etcd/v2
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.0.0-rc2
|
||||
github.com/gogf/gf/v2 v2.0.0
|
||||
go.etcd.io/etcd/client/v3 v3.5.1
|
||||
)
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/trace/jaeger/v2
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.0.0-rc2
|
||||
github.com/gogf/gf/v2 v2.0.0
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.3.0
|
||||
go.opentelemetry.io/otel/sdk v1.3.0
|
||||
)
|
||||
|
||||
@ -280,7 +280,6 @@ const (
|
||||
queryTypeCount = 1
|
||||
unionTypeNormal = 0
|
||||
unionTypeAll = 1
|
||||
defaultBatchNumber = 10 // Per count for batch insert/replace/save.
|
||||
defaultMaxIdleConnCount = 10 // Max idle connection count in pool.
|
||||
defaultMaxOpenConnCount = 0 // Max open connection count in pool. Default is no limit.
|
||||
defaultMaxConnLifeTime = 30 * time.Second // Max lifetime for per connection in pool in seconds.
|
||||
|
||||
@ -53,7 +53,7 @@ func (c *Core) Ctx(ctx context.Context) DB {
|
||||
panic(err)
|
||||
}
|
||||
newCore.ctx = WithDB(ctx, newCore.db)
|
||||
newCore.ctx = c.injectInternalCtxData(newCore.ctx)
|
||||
newCore.ctx = c.InjectInternalCtxData(newCore.ctx)
|
||||
return newCore.db
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ func (c *Core) GetCtx() context.Context {
|
||||
if ctx == nil {
|
||||
ctx = context.TODO()
|
||||
}
|
||||
return c.injectInternalCtxData(ctx)
|
||||
return c.InjectInternalCtxData(ctx)
|
||||
}
|
||||
|
||||
// GetCtxTimeout returns the context and cancel function for specified timeout type.
|
||||
|
||||
@ -26,9 +26,15 @@ type internalCtxData struct {
|
||||
|
||||
const (
|
||||
internalCtxDataKeyInCtx gctx.StrKey = "InternalCtxData"
|
||||
|
||||
// `ignoreResultKeyInCtx` is a mark for some db drivers that do not support `RowsAffected` function,
|
||||
// for example: `clickhouse`. The `clickhouse` does not support fetching insert/update results,
|
||||
// but returns errors when execute `RowsAffected`. It here ignores the calling of `RowsAffected`
|
||||
// to avoid triggering errors, rather than ignoring errors after they are triggered.
|
||||
ignoreResultKeyInCtx gctx.StrKey = "IgnoreResult"
|
||||
)
|
||||
|
||||
func (c *Core) injectInternalCtxData(ctx context.Context) context.Context {
|
||||
func (c *Core) InjectInternalCtxData(ctx context.Context) context.Context {
|
||||
// If the internal data is already injected, it does nothing.
|
||||
if ctx.Value(internalCtxDataKeyInCtx) != nil {
|
||||
return ctx
|
||||
@ -38,9 +44,23 @@ func (c *Core) injectInternalCtxData(ctx context.Context) context.Context {
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Core) getInternalCtxDataFromCtx(ctx context.Context) *internalCtxData {
|
||||
func (c *Core) GetInternalCtxDataFromCtx(ctx context.Context) *internalCtxData {
|
||||
if v := ctx.Value(internalCtxDataKeyInCtx); v != nil {
|
||||
return v.(*internalCtxData)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Core) InjectIgnoreResult(ctx context.Context) context.Context {
|
||||
if ctx.Value(ignoreResultKeyInCtx) != nil {
|
||||
return ctx
|
||||
}
|
||||
return context.WithValue(ctx, ignoreResultKeyInCtx, true)
|
||||
}
|
||||
|
||||
func (c *Core) GetIgnoreResultFromCtx(ctx context.Context) bool {
|
||||
if ctx.Value(ignoreResultKeyInCtx) != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@ -159,7 +159,7 @@ func (tx *TX) transactionKeyForNestedPoint() string {
|
||||
func (tx *TX) Ctx(ctx context.Context) *TX {
|
||||
tx.ctx = ctx
|
||||
if tx.ctx != nil {
|
||||
tx.ctx = tx.db.GetCore().injectInternalCtxData(tx.ctx)
|
||||
tx.ctx = tx.db.GetCore().InjectInternalCtxData(tx.ctx)
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
@ -11,6 +11,9 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/gogf/gf/v2"
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
@ -18,8 +21,6 @@ import (
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/guid"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// Query commits one query SQL to underlying driver and returns the execution result.
|
||||
@ -164,7 +165,7 @@ func (c *Core) sqlParsingHandler(ctx context.Context, in sqlParsingHandlerInput)
|
||||
// DoCommit commits current sql and arguments to underlying sql driver.
|
||||
func (c *Core) DoCommit(ctx context.Context, in DoCommitInput) (out DoCommitOutput, err error) {
|
||||
// Inject internal data into ctx, especially for transaction creating.
|
||||
ctx = c.injectInternalCtxData(ctx)
|
||||
ctx = c.InjectInternalCtxData(ctx)
|
||||
|
||||
var (
|
||||
sqlTx *sql.Tx
|
||||
@ -260,7 +261,7 @@ func (c *Core) DoCommit(ctx context.Context, in DoCommitInput) (out DoCommitOutp
|
||||
}
|
||||
// Result handling.
|
||||
switch {
|
||||
case sqlResult != nil:
|
||||
case sqlResult != nil && !c.GetIgnoreResultFromCtx(ctx):
|
||||
rowsAffected, err = sqlResult.RowsAffected()
|
||||
out.Result = sqlResult
|
||||
|
||||
@ -400,7 +401,7 @@ func (c *Core) RowsToResult(ctx context.Context, rows *sql.Rows) (Result, error)
|
||||
columnNames[k] = v.Name()
|
||||
}
|
||||
if len(columnNames) > 0 {
|
||||
if internalData := c.getInternalCtxDataFromCtx(ctx); internalData != nil {
|
||||
if internalData := c.GetInternalCtxDataFromCtx(ctx); internalData != nil {
|
||||
internalData.FirstResultColumn = columnNames[0]
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,7 +75,7 @@ func (m *Model) getSelectResultFromCache(ctx context.Context, sql string, args .
|
||||
)
|
||||
defer func() {
|
||||
if cacheItem != nil {
|
||||
if internalData := m.db.GetCore().getInternalCtxDataFromCtx(ctx); internalData != nil {
|
||||
if internalData := m.db.GetCore().GetInternalCtxDataFromCtx(ctx); internalData != nil {
|
||||
if internalData.FirstResultColumn == "" {
|
||||
internalData.FirstResultColumn = cacheItem.FirstResultColumn
|
||||
}
|
||||
@ -86,9 +86,12 @@ func (m *Model) getSelectResultFromCache(ctx context.Context, sql string, args .
|
||||
if cacheItem, ok = v.Val().(*selectCacheItem); ok {
|
||||
// In-memory cache.
|
||||
return cacheItem.Result, nil
|
||||
} else if err = json.UnmarshalUseNumber(v.Bytes(), &cacheItem); err != nil {
|
||||
} else {
|
||||
// Other cache, it needs conversion.
|
||||
return nil, err
|
||||
if err = json.UnmarshalUseNumber(v.Bytes(), &cacheItem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cacheItem.Result, nil
|
||||
}
|
||||
}
|
||||
return
|
||||
@ -114,7 +117,7 @@ func (m *Model) saveSelectResultToCache(ctx context.Context, result Result, sql
|
||||
var cacheItem = &selectCacheItem{
|
||||
Result: result,
|
||||
}
|
||||
if internalData := m.db.GetCore().getInternalCtxDataFromCtx(ctx); internalData != nil {
|
||||
if internalData := m.db.GetCore().GetInternalCtxDataFromCtx(ctx); internalData != nil {
|
||||
cacheItem.FirstResultColumn = internalData.FirstResultColumn
|
||||
}
|
||||
if errCache := cacheObj.Set(ctx, cacheKey, cacheItem, m.cacheOption.Duration); errCache != nil {
|
||||
|
||||
@ -43,12 +43,12 @@ func (m *Model) Fields(fieldNamesOrMapStruct ...interface{}) *Model {
|
||||
return m.appendFieldsByStr(gstr.Join(
|
||||
m.mappingAndFilterToTableFields([]string{r}, false), ",",
|
||||
))
|
||||
|
||||
case []string:
|
||||
return m.appendFieldsByStr(gstr.Join(
|
||||
m.mappingAndFilterToTableFields(r, true), ",",
|
||||
))
|
||||
|
||||
case Raw, *Raw:
|
||||
return m.appendFieldsByStr(gconv.String(structOrMap))
|
||||
default:
|
||||
return m.appendFieldsByStr(gstr.Join(
|
||||
m.mappingAndFilterToTableFields(getFieldsFromStructOrMap(structOrMap), true), ",",
|
||||
@ -92,6 +92,8 @@ func (m *Model) FieldsEx(fieldNamesOrMapStruct ...interface{}) *Model {
|
||||
model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields([]string{r}, false), ",")
|
||||
case []string:
|
||||
model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields(r, true), ",")
|
||||
case Raw, *Raw:
|
||||
model.fieldsEx = gconv.String(fieldNamesOrMapStruct[0])
|
||||
default:
|
||||
model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields(getFieldsFromStructOrMap(r), true), ",")
|
||||
}
|
||||
|
||||
@ -428,9 +428,5 @@ func (m *Model) formatOnDuplicateExKeys(onDuplicateEx interface{}) ([]string, er
|
||||
}
|
||||
|
||||
func (m *Model) getBatch() int {
|
||||
batch := defaultBatchNumber
|
||||
if m.batch > 0 {
|
||||
batch = m.batch
|
||||
}
|
||||
return batch
|
||||
return m.batch
|
||||
}
|
||||
|
||||
@ -50,8 +50,12 @@ func (m *Model) doGetAll(ctx context.Context, limit1 bool, where ...interface{})
|
||||
// really be committed to underlying database driver.
|
||||
func (m *Model) getFieldsFiltered() string {
|
||||
if m.fieldsEx == "" {
|
||||
// No filtering, containing special chars.
|
||||
if gstr.ContainsAny(m.fields, "()") {
|
||||
return m.fields
|
||||
}
|
||||
// No filtering.
|
||||
if !gstr.Contains(m.fields, ".") && !gstr.Contains(m.fields, " ") {
|
||||
if !gstr.ContainsAny(m.fields, ". ") {
|
||||
return m.db.GetCore().QuoteString(m.fields)
|
||||
}
|
||||
return m.fields
|
||||
@ -170,7 +174,7 @@ func (m *Model) Value(fieldsAndWhere ...interface{}) (Value, error) {
|
||||
if len(all) == 0 {
|
||||
return gvar.New(nil), nil
|
||||
}
|
||||
if internalData := m.db.GetCore().getInternalCtxDataFromCtx(ctx); internalData != nil {
|
||||
if internalData := m.db.GetCore().GetInternalCtxDataFromCtx(ctx); internalData != nil {
|
||||
record := all[0]
|
||||
if v, ok := record[internalData.FirstResultColumn]; ok {
|
||||
return v, nil
|
||||
@ -178,7 +182,7 @@ func (m *Model) Value(fieldsAndWhere ...interface{}) (Value, error) {
|
||||
}
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeInternalError,
|
||||
`query value error: the internal context data is missing. there's' internal issue should be fixed'`,
|
||||
`query value error: the internal context data is missing. there's internal issue should be fixed`,
|
||||
)
|
||||
}
|
||||
|
||||
@ -387,7 +391,7 @@ func (m *Model) Count(where ...interface{}) (int, error) {
|
||||
return 0, err
|
||||
}
|
||||
if len(all) > 0 {
|
||||
if internalData := m.db.GetCore().getInternalCtxDataFromCtx(ctx); internalData != nil {
|
||||
if internalData := m.db.GetCore().GetInternalCtxDataFromCtx(ctx); internalData != nil {
|
||||
record := all[0]
|
||||
if v, ok := record[internalData.FirstResultColumn]; ok {
|
||||
return v.Int(), nil
|
||||
@ -395,7 +399,7 @@ func (m *Model) Count(where ...interface{}) (int, error) {
|
||||
}
|
||||
return 0, gerror.NewCode(
|
||||
gcode.CodeInternalError,
|
||||
`query count error: the internal context data is missing. there's' internal issue should be fixed'`,
|
||||
`query count error: the internal context data is missing. there's internal issue should be fixed`,
|
||||
)
|
||||
}
|
||||
return 0, nil
|
||||
|
||||
@ -93,6 +93,15 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
return in.Next(ctx)
|
||||
}
|
||||
|
||||
// UpdateAndGetAffected performs update statement and returns the affected rows number.
|
||||
func (m *Model) UpdateAndGetAffected(dataAndWhere ...interface{}) (affected int64, err error) {
|
||||
result, err := m.Update(dataAndWhere...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected()
|
||||
}
|
||||
|
||||
// Increment increments a column's value by a given amount.
|
||||
// The parameter `amount` can be type of float or integer.
|
||||
func (m *Model) Increment(column string, amount interface{}) (sql.Result, error) {
|
||||
|
||||
@ -24,6 +24,7 @@ import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/guid"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
@ -432,6 +433,18 @@ func Test_Model_Update(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_UpdateAndGetAffected(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
n, err := db.Model(table).Data("nickname", "T100").
|
||||
Where(1).Order("id desc").Limit(2).
|
||||
UpdateAndGetAffected()
|
||||
t.AssertNil(err)
|
||||
t.Assert(n, 2)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Clone(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
@ -688,6 +701,18 @@ func Test_Model_Count(t *testing.T) {
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, TableSize)
|
||||
})
|
||||
// Count with cache, check internal ctx data feature.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
count, err := db.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second * 10,
|
||||
Name: guid.S(),
|
||||
Force: false,
|
||||
}).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, TableSize)
|
||||
}
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
count, err := db.Model(table).FieldsEx("id").Where("id>8").Count()
|
||||
t.AssertNil(err)
|
||||
@ -4202,3 +4227,62 @@ func Test_Model_WherePrefixLike(t *testing.T) {
|
||||
t.Assert(r[0]["id"], "3")
|
||||
})
|
||||
}
|
||||
|
||||
// https://github.com/gogf/gf/issues/1700
|
||||
func Test_Model_Issue1700(t *testing.T) {
|
||||
table := "user_" + gtime.Now().TimestampNanoStr()
|
||||
if _, err := db.Exec(ctx, fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
user_id int(10) unsigned NOT NULL,
|
||||
UserId int(10) unsigned NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, table,
|
||||
)); err != nil {
|
||||
gtest.AssertNil(err)
|
||||
}
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int `orm:"id"`
|
||||
Userid int `orm:"user_id"`
|
||||
UserId int `orm:"UserId"`
|
||||
}
|
||||
_, err := db.Model(table).Data(User{
|
||||
Id: 1,
|
||||
Userid: 2,
|
||||
UserId: 3,
|
||||
}).Insert()
|
||||
t.AssertNil(err)
|
||||
|
||||
one, err := db.Model(table).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one, g.Map{
|
||||
"id": 1,
|
||||
"user_id": 2,
|
||||
"UserId": 3,
|
||||
})
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
var user *User
|
||||
err = db.Model(table).Scan(&user)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user.Id, 1)
|
||||
t.Assert(user.Userid, 2)
|
||||
t.Assert(user.UserId, 3)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// https://github.com/gogf/gf/issues/1701
|
||||
func Test_Model_Issue1701(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
value, err := db.Model(table).Fields(gdb.Raw("if(id=1,100,null)")).WherePri(1).Value()
|
||||
t.AssertNil(err)
|
||||
t.Assert(value.String(), 100)
|
||||
})
|
||||
}
|
||||
|
||||
@ -22,8 +22,17 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// Separator char for hierarchical data access.
|
||||
defaultSplitChar = '.'
|
||||
ContentTypeJson = `json`
|
||||
ContentTypeJs = `js`
|
||||
ContentTypeXml = `xml`
|
||||
ContentTypeIni = `ini`
|
||||
ContentTypeYaml = `yaml`
|
||||
ContentTypeYml = `yml`
|
||||
ContentTypeToml = `toml`
|
||||
)
|
||||
|
||||
const (
|
||||
defaultSplitChar = '.' // Separator char for hierarchical data access.
|
||||
)
|
||||
|
||||
// Json is the customized JSON struct.
|
||||
@ -34,10 +43,11 @@ type Json struct {
|
||||
vc bool // Violence Check(false in default), which is used to access data when the hierarchical data key contains separator char.
|
||||
}
|
||||
|
||||
// Options for Json object creating.
|
||||
// Options for Json object creating/loading.
|
||||
type Options struct {
|
||||
Safe bool // Mark this object is for in concurrent-safe usage. This is especially for Json object creating.
|
||||
Tags string // Custom priority tags for decoding. Eg: "json,yaml,MyTag". This is especially for struct parsing into Json object.
|
||||
Tags string // Custom priority tags for decoding, eg: "json,yaml,MyTag". This is especially for struct parsing into Json object.
|
||||
Type string // Type specifies the data content type, eg: json, xml, yaml, toml, ini.
|
||||
StrNumber bool // StrNumber causes the Decoder to unmarshal a number into an interface{} as a string instead of as a float64.
|
||||
}
|
||||
|
||||
|
||||
@ -21,6 +21,7 @@ import (
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
@ -30,7 +31,7 @@ import (
|
||||
// The parameter `safe` specifies whether using this Json object in concurrent-safe context,
|
||||
// which is false in default.
|
||||
func New(data interface{}, safe ...bool) *Json {
|
||||
return NewWithTag(data, "json", safe...)
|
||||
return NewWithTag(data, ContentTypeJson, safe...)
|
||||
}
|
||||
|
||||
// NewWithTag creates a Json object with any variable type of `data`, but `data` should be a map
|
||||
@ -104,56 +105,73 @@ func Load(path string, safe ...bool) (*Json, error) {
|
||||
} else {
|
||||
path = p
|
||||
}
|
||||
option := Options{}
|
||||
if len(safe) > 0 && safe[0] {
|
||||
option.Safe = true
|
||||
options := Options{
|
||||
Type: gfile.Ext(path),
|
||||
}
|
||||
return doLoadContentWithOptions(gfile.Ext(path), gfile.GetBytesWithCache(path), option)
|
||||
if len(safe) > 0 && safe[0] {
|
||||
options.Safe = true
|
||||
}
|
||||
return doLoadContentWithOptions(gfile.GetBytesWithCache(path), options)
|
||||
}
|
||||
|
||||
// LoadWithOptions creates a Json object from given JSON format content and options.
|
||||
func LoadWithOptions(data interface{}, options Options) (*Json, error) {
|
||||
return doLoadContentWithOptions(gconv.Bytes(data), options)
|
||||
}
|
||||
|
||||
// LoadJson creates a Json object from given JSON format content.
|
||||
func LoadJson(data interface{}, safe ...bool) (*Json, error) {
|
||||
option := Options{}
|
||||
option := Options{
|
||||
Type: ContentTypeJson,
|
||||
}
|
||||
if len(safe) > 0 && safe[0] {
|
||||
option.Safe = true
|
||||
}
|
||||
return doLoadContentWithOptions("json", gconv.Bytes(data), option)
|
||||
return doLoadContentWithOptions(gconv.Bytes(data), option)
|
||||
}
|
||||
|
||||
// LoadXml creates a Json object from given XML format content.
|
||||
func LoadXml(data interface{}, safe ...bool) (*Json, error) {
|
||||
option := Options{}
|
||||
option := Options{
|
||||
Type: ContentTypeXml,
|
||||
}
|
||||
if len(safe) > 0 && safe[0] {
|
||||
option.Safe = true
|
||||
}
|
||||
return doLoadContentWithOptions("xml", gconv.Bytes(data), option)
|
||||
return doLoadContentWithOptions(gconv.Bytes(data), option)
|
||||
}
|
||||
|
||||
// LoadIni creates a Json object from given INI format content.
|
||||
func LoadIni(data interface{}, safe ...bool) (*Json, error) {
|
||||
option := Options{}
|
||||
option := Options{
|
||||
Type: ContentTypeIni,
|
||||
}
|
||||
if len(safe) > 0 && safe[0] {
|
||||
option.Safe = true
|
||||
}
|
||||
return doLoadContentWithOptions("ini", gconv.Bytes(data), option)
|
||||
return doLoadContentWithOptions(gconv.Bytes(data), option)
|
||||
}
|
||||
|
||||
// LoadYaml creates a Json object from given YAML format content.
|
||||
func LoadYaml(data interface{}, safe ...bool) (*Json, error) {
|
||||
option := Options{}
|
||||
option := Options{
|
||||
Type: ContentTypeYaml,
|
||||
}
|
||||
if len(safe) > 0 && safe[0] {
|
||||
option.Safe = true
|
||||
}
|
||||
return doLoadContentWithOptions("yaml", gconv.Bytes(data), option)
|
||||
return doLoadContentWithOptions(gconv.Bytes(data), option)
|
||||
}
|
||||
|
||||
// LoadToml creates a Json object from given TOML format content.
|
||||
func LoadToml(data interface{}, safe ...bool) (*Json, error) {
|
||||
option := Options{}
|
||||
option := Options{
|
||||
Type: ContentTypeToml,
|
||||
}
|
||||
if len(safe) > 0 && safe[0] {
|
||||
option.Safe = true
|
||||
}
|
||||
return doLoadContentWithOptions("toml", gconv.Bytes(data), option)
|
||||
return doLoadContentWithOptions(gconv.Bytes(data), option)
|
||||
}
|
||||
|
||||
// LoadContent creates a Json object from given content, it checks the data type of `content`
|
||||
@ -179,11 +197,13 @@ func LoadContentType(dataType string, data interface{}, safe ...bool) (*Json, er
|
||||
if content[0] == 0xEF && content[1] == 0xBB && content[2] == 0xBF {
|
||||
content = content[3:]
|
||||
}
|
||||
option := Options{}
|
||||
if len(safe) > 0 && safe[0] {
|
||||
option.Safe = true
|
||||
options := Options{
|
||||
Type: dataType,
|
||||
}
|
||||
return doLoadContentWithOptions(dataType, content, option)
|
||||
if len(safe) > 0 && safe[0] {
|
||||
options.Safe = true
|
||||
}
|
||||
return doLoadContentWithOptions(content, options)
|
||||
}
|
||||
|
||||
// IsValidDataType checks and returns whether given `dataType` a valid data type for loading.
|
||||
@ -195,7 +215,14 @@ func IsValidDataType(dataType string) bool {
|
||||
dataType = dataType[1:]
|
||||
}
|
||||
switch dataType {
|
||||
case "json", "js", "xml", "yaml", "yml", "toml", "ini":
|
||||
case
|
||||
ContentTypeJson,
|
||||
ContentTypeJs,
|
||||
ContentTypeXml,
|
||||
ContentTypeYaml,
|
||||
ContentTypeYml,
|
||||
ContentTypeToml,
|
||||
ContentTypeIni:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@ -206,10 +233,13 @@ func loadContentWithOptions(data interface{}, options Options) (*Json, error) {
|
||||
if len(content) == 0 {
|
||||
return NewWithOptions(nil, options), nil
|
||||
}
|
||||
return loadContentTypeWithOptions(checkDataType(content), content, options)
|
||||
if options.Type == "" {
|
||||
options.Type = checkDataType(content)
|
||||
}
|
||||
return loadContentTypeWithOptions(content, options)
|
||||
}
|
||||
|
||||
func loadContentTypeWithOptions(dataType string, data interface{}, options Options) (*Json, error) {
|
||||
func loadContentTypeWithOptions(data interface{}, options Options) (*Json, error) {
|
||||
content := gconv.Bytes(data)
|
||||
if len(content) == 0 {
|
||||
return NewWithOptions(nil, options), nil
|
||||
@ -218,13 +248,13 @@ func loadContentTypeWithOptions(dataType string, data interface{}, options Optio
|
||||
if content[0] == 0xEF && content[1] == 0xBB && content[2] == 0xBF {
|
||||
content = content[3:]
|
||||
}
|
||||
return doLoadContentWithOptions(dataType, content, options)
|
||||
return doLoadContentWithOptions(content, options)
|
||||
}
|
||||
|
||||
// doLoadContent creates a Json object from given content.
|
||||
// It supports data content type as follows:
|
||||
// JSON, XML, INI, YAML and TOML.
|
||||
func doLoadContentWithOptions(dataType string, data []byte, options Options) (*Json, error) {
|
||||
func doLoadContentWithOptions(data []byte, options Options) (*Json, error) {
|
||||
var (
|
||||
err error
|
||||
result interface{}
|
||||
@ -232,34 +262,39 @@ func doLoadContentWithOptions(dataType string, data []byte, options Options) (*J
|
||||
if len(data) == 0 {
|
||||
return NewWithOptions(nil, options), nil
|
||||
}
|
||||
if dataType == "" {
|
||||
dataType = checkDataType(data)
|
||||
if options.Type == "" {
|
||||
options.Type = checkDataType(data)
|
||||
}
|
||||
switch dataType {
|
||||
case "json", ".json", ".js":
|
||||
options.Type = gstr.TrimLeft(options.Type, ".")
|
||||
switch options.Type {
|
||||
case ContentTypeJson, ContentTypeJs:
|
||||
|
||||
case "xml", ".xml":
|
||||
case ContentTypeXml:
|
||||
if data, err = gxml.ToJson(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case "yml", "yaml", ".yml", ".yaml":
|
||||
case ContentTypeYaml, ContentTypeYml:
|
||||
if data, err = gyaml.ToJson(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case "toml", ".toml":
|
||||
case ContentTypeToml:
|
||||
if data, err = gtoml.ToJson(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case "ini", ".ini":
|
||||
case ContentTypeIni:
|
||||
if data, err = gini.ToJson(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
default:
|
||||
err = gerror.NewCodef(gcode.CodeInvalidParameter, `unsupported type "%s" for loading`, dataType)
|
||||
err = gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`unsupported type "%s" for loading`,
|
||||
options.Type,
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -268,7 +303,7 @@ func doLoadContentWithOptions(dataType string, data []byte, options Options) (*J
|
||||
if options.StrNumber {
|
||||
decoder.UseNumber()
|
||||
}
|
||||
if err := decoder.Decode(&result); err != nil {
|
||||
if err = decoder.Decode(&result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch result.(type) {
|
||||
@ -283,23 +318,23 @@ func doLoadContentWithOptions(dataType string, data []byte, options Options) (*J
|
||||
// functions to load the content for certain content type.
|
||||
func checkDataType(content []byte) string {
|
||||
if json.Valid(content) {
|
||||
return "json"
|
||||
return ContentTypeJson
|
||||
} else if gregex.IsMatch(`^<.+>[\S\s]+<.+>\s*$`, content) {
|
||||
return "xml"
|
||||
return ContentTypeXml
|
||||
} else if !gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*"""[\s\S]+"""`, content) &&
|
||||
!gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*'''[\s\S]+'''`, content) &&
|
||||
((gregex.IsMatch(`^[\n\r]*[\w\-\s\t]+\s*:\s*".+"`, content) || gregex.IsMatch(`^[\n\r]*[\w\-\s\t]+\s*:\s*\w+`, content)) ||
|
||||
(gregex.IsMatch(`[\n\r]+[\w\-\s\t]+\s*:\s*".+"`, content) || gregex.IsMatch(`[\n\r]+[\w\-\s\t]+\s*:\s*\w+`, content))) {
|
||||
return "yml"
|
||||
return ContentTypeYaml
|
||||
} else if !gregex.IsMatch(`^[\s\t\n\r]*;.+`, content) &&
|
||||
!gregex.IsMatch(`[\s\t\n\r]+;.+`, content) &&
|
||||
!gregex.IsMatch(`[\n\r]+[\s\t\w\-]+\.[\s\t\w\-]+\s*=\s*.+`, content) &&
|
||||
(gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*".+"`, content) || gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*\w+`, content)) {
|
||||
return "toml"
|
||||
return ContentTypeToml
|
||||
} else if gregex.IsMatch(`\[[\w\.]+\]`, content) &&
|
||||
(gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*".+"`, content) || gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*\w+`, content)) {
|
||||
// Must contain "[xxx]" section.
|
||||
return "ini"
|
||||
return ContentTypeIni
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
|
||||
@ -13,7 +13,10 @@ func (j Json) MarshalJSON() ([]byte, error) {
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (j *Json) UnmarshalJSON(b []byte) error {
|
||||
r, err := LoadContent(b)
|
||||
r, err := loadContentWithOptions(b, Options{
|
||||
Type: ContentTypeJson,
|
||||
StrNumber: true,
|
||||
})
|
||||
if r != nil {
|
||||
// Value copy.
|
||||
*j = *r
|
||||
@ -23,7 +26,9 @@ func (j *Json) UnmarshalJSON(b []byte) error {
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for Json.
|
||||
func (j *Json) UnmarshalValue(value interface{}) error {
|
||||
if r := New(value); r != nil {
|
||||
if r := NewWithOptions(value, Options{
|
||||
StrNumber: true,
|
||||
}); r != nil {
|
||||
// Value copy.
|
||||
*j = *r
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ import (
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
func Test_New(t *testing.T) {
|
||||
@ -576,3 +577,13 @@ func Test_Issue1617(t *testing.T) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// https://github.com/gogf/gf/issues/1747
|
||||
func Test_Issue1747(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var j *gjson.Json
|
||||
err := gconv.Struct(gvar.New("[1, 2, 336371793314971759]"), &j)
|
||||
t.AssertNil(err)
|
||||
t.Assert(j.Get("2"), `336371793314971759`)
|
||||
})
|
||||
}
|
||||
|
||||
@ -17,36 +17,6 @@ import (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
)
|
||||
|
||||
// iCode is the interface for Code feature.
|
||||
type iCode interface {
|
||||
Error() string
|
||||
Code() gcode.Code
|
||||
}
|
||||
|
||||
// iStack is the interface for Stack feature.
|
||||
type iStack interface {
|
||||
Error() string
|
||||
Stack() string
|
||||
}
|
||||
|
||||
// iCause is the interface for Cause feature.
|
||||
type iCause interface {
|
||||
Error() string
|
||||
Cause() error
|
||||
}
|
||||
|
||||
// iCurrent is the interface for Current feature.
|
||||
type iCurrent interface {
|
||||
Error() string
|
||||
Current() error
|
||||
}
|
||||
|
||||
// iNext is the interface for Next feature.
|
||||
type iNext interface {
|
||||
Error() string
|
||||
Next() error
|
||||
}
|
||||
|
||||
// New creates and returns an error which is formatted from given text.
|
||||
func New(text string) error {
|
||||
return &Error{
|
||||
@ -305,8 +275,39 @@ func Next(err error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unwrap is alias of function `Next`.
|
||||
// It is just for implements for stdlib errors.Unwrap from Go version 1.17.
|
||||
func Unwrap(err error) error {
|
||||
return Next(err)
|
||||
}
|
||||
|
||||
// HasStack checks and returns whether `err` implemented interface `iStack`.
|
||||
func HasStack(err error) bool {
|
||||
_, ok := err.(iStack)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Equal reports whether current error `err` equals to error `target`.
|
||||
// Please note that, in default comparison for `Error`,
|
||||
// the errors are considered the same if both the `code` and `text` of them are the same.
|
||||
func Equal(err, target error) bool {
|
||||
if err == target {
|
||||
return true
|
||||
}
|
||||
if e, ok := err.(iEqual); ok {
|
||||
return e.Equal(target)
|
||||
}
|
||||
if e, ok := target.(iEqual); ok {
|
||||
return e.Equal(err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Is reports whether current error `err` has error `target` in its chaining errors.
|
||||
// It is just for implements for stdlib errors.Unwrap from Go version 1.17.
|
||||
func Is(err, target error) bool {
|
||||
if e, ok := err.(iIs); ok {
|
||||
return e.Is(target)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@ -7,10 +7,8 @@
|
||||
package gerror
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
@ -59,18 +57,6 @@ func (err *Error) Error() string {
|
||||
return errStr
|
||||
}
|
||||
|
||||
// Code returns the error code.
|
||||
// It returns CodeNil if it has no error code.
|
||||
func (err *Error) Code() gcode.Code {
|
||||
if err == nil {
|
||||
return gcode.CodeNil
|
||||
}
|
||||
if err.code == gcode.CodeNil {
|
||||
return Code(err.Next())
|
||||
}
|
||||
return err.code
|
||||
}
|
||||
|
||||
// Cause returns the root cause error.
|
||||
func (err *Error) Cause() error {
|
||||
if err == nil {
|
||||
@ -97,64 +83,6 @@ func (err *Error) Cause() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Format formats the frame according to the fmt.Formatter interface.
|
||||
//
|
||||
// %v, %s : Print all the error string;
|
||||
// %-v, %-s : Print current level error string;
|
||||
// %+s : Print full stack error list;
|
||||
// %+v : Print the error string and full stack error list;
|
||||
func (err *Error) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 's', 'v':
|
||||
switch {
|
||||
case s.Flag('-'):
|
||||
if err.text != "" {
|
||||
_, _ = io.WriteString(s, err.text)
|
||||
} else {
|
||||
_, _ = io.WriteString(s, err.Error())
|
||||
}
|
||||
case s.Flag('+'):
|
||||
if verb == 's' {
|
||||
_, _ = io.WriteString(s, err.Stack())
|
||||
} else {
|
||||
_, _ = io.WriteString(s, err.Error()+"\n"+err.Stack())
|
||||
}
|
||||
default:
|
||||
_, _ = io.WriteString(s, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stack returns the stack callers as string.
|
||||
// It returns an empty string if the `err` does not support stacks.
|
||||
func (err *Error) Stack() string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
var (
|
||||
loop = err
|
||||
index = 1
|
||||
buffer = bytes.NewBuffer(nil)
|
||||
)
|
||||
for loop != nil {
|
||||
buffer.WriteString(fmt.Sprintf("%d. %-v\n", index, loop))
|
||||
index++
|
||||
formatSubStack(loop.stack, buffer)
|
||||
if loop.error != nil {
|
||||
if e, ok := loop.error.(*Error); ok {
|
||||
loop = e
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf("%d. %s\n", index, loop.error.Error()))
|
||||
index++
|
||||
break
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// Current creates and returns the current level error.
|
||||
// It returns nil if current level error is nil.
|
||||
func (err *Error) Current() error {
|
||||
@ -178,53 +106,46 @@ func (err *Error) Next() error {
|
||||
return err.error
|
||||
}
|
||||
|
||||
// SetCode updates the internal code with given code.
|
||||
func (err *Error) SetCode(code gcode.Code) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
err.code = code
|
||||
// Unwrap is alias of function `Next`.
|
||||
// It is just for implements for stdlib errors.Unwrap from Go version 1.17.
|
||||
func (err *Error) Unwrap() error {
|
||||
return err.Next()
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
// Note that do not use pointer as its receiver here.
|
||||
func (err Error) MarshalJSON() ([]byte, error) {
|
||||
return []byte(`"` + err.Error() + `"`), nil
|
||||
// Equal reports whether current error `err` equals to error `target`.
|
||||
// Please note that, in default comparison for `Error`,
|
||||
// the errors are considered the same if both the `code` and `text` of them are the same.
|
||||
func (err *Error) Equal(target error) bool {
|
||||
if err == target {
|
||||
return true
|
||||
}
|
||||
// Code should be the same.
|
||||
// Note that if both errors have `nil` code, they are also considered equal.
|
||||
if err.code != Code(target) {
|
||||
return false
|
||||
}
|
||||
// Text should be the same.
|
||||
if err.text != fmt.Sprintf(`%-s`, target) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// formatSubStack formats the stack for error.
|
||||
func formatSubStack(st stack, buffer *bytes.Buffer) {
|
||||
if st == nil {
|
||||
return
|
||||
// Is reports whether current error `err` has error `target` in its chaining errors.
|
||||
// It is just for implements for stdlib errors.Unwrap from Go version 1.17.
|
||||
func (err *Error) Is(target error) bool {
|
||||
if Equal(err, target) {
|
||||
return true
|
||||
}
|
||||
index := 1
|
||||
space := " "
|
||||
for _, p := range st {
|
||||
if fn := runtime.FuncForPC(p - 1); fn != nil {
|
||||
file, line := fn.FileLine(p - 1)
|
||||
// Custom filtering.
|
||||
if strings.Contains(file, stackFilterKeyLocal) {
|
||||
continue
|
||||
}
|
||||
// Avoid stack string like "`autogenerated`"
|
||||
if strings.Contains(file, "<") {
|
||||
continue
|
||||
}
|
||||
// Ignore GO ROOT paths.
|
||||
if goRootForFilter != "" &&
|
||||
len(file) >= len(goRootForFilter) &&
|
||||
file[0:len(goRootForFilter)] == goRootForFilter {
|
||||
continue
|
||||
}
|
||||
// Graceful indent.
|
||||
if index > 9 {
|
||||
space = " "
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf(
|
||||
" %d).%s%s\n \t%s:%d\n",
|
||||
index, space, fn.Name(), file, line,
|
||||
))
|
||||
index++
|
||||
}
|
||||
nextErr := err.Next()
|
||||
if nextErr == nil {
|
||||
return false
|
||||
}
|
||||
if Equal(nextErr, target) {
|
||||
return true
|
||||
}
|
||||
if e, ok := nextErr.(iIs); ok {
|
||||
return e.Is(target)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
31
errors/gerror/gerror_error_code.go
Normal file
31
errors/gerror/gerror_error_code.go
Normal file
@ -0,0 +1,31 @@
|
||||
// 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 gerror
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
)
|
||||
|
||||
// Code returns the error code.
|
||||
// It returns CodeNil if it has no error code.
|
||||
func (err *Error) Code() gcode.Code {
|
||||
if err == nil {
|
||||
return gcode.CodeNil
|
||||
}
|
||||
if err.code == gcode.CodeNil {
|
||||
return Code(err.Next())
|
||||
}
|
||||
return err.code
|
||||
}
|
||||
|
||||
// SetCode updates the internal code with given code.
|
||||
func (err *Error) SetCode(code gcode.Code) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
err.code = code
|
||||
}
|
||||
80
errors/gerror/gerror_error_format.go
Normal file
80
errors/gerror/gerror_error_format.go
Normal file
@ -0,0 +1,80 @@
|
||||
// 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 gerror
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Format formats the frame according to the fmt.Formatter interface.
|
||||
//
|
||||
// %v, %s : Print all the error string;
|
||||
// %-v, %-s : Print current level error string;
|
||||
// %+s : Print full stack error list;
|
||||
// %+v : Print the error string and full stack error list;
|
||||
func (err *Error) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 's', 'v':
|
||||
switch {
|
||||
case s.Flag('-'):
|
||||
if err.text != "" {
|
||||
_, _ = io.WriteString(s, err.text)
|
||||
} else {
|
||||
_, _ = io.WriteString(s, err.Error())
|
||||
}
|
||||
case s.Flag('+'):
|
||||
if verb == 's' {
|
||||
_, _ = io.WriteString(s, err.Stack())
|
||||
} else {
|
||||
_, _ = io.WriteString(s, err.Error()+"\n"+err.Stack())
|
||||
}
|
||||
default:
|
||||
_, _ = io.WriteString(s, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// formatSubStack formats the stack for error.
|
||||
func formatSubStack(st stack, buffer *bytes.Buffer) {
|
||||
if st == nil {
|
||||
return
|
||||
}
|
||||
index := 1
|
||||
space := " "
|
||||
for _, p := range st {
|
||||
if fn := runtime.FuncForPC(p - 1); fn != nil {
|
||||
file, line := fn.FileLine(p - 1)
|
||||
// Custom filtering.
|
||||
if strings.Contains(file, stackFilterKeyLocal) {
|
||||
continue
|
||||
}
|
||||
// Avoid stack string like "`autogenerated`"
|
||||
if strings.Contains(file, "<") {
|
||||
continue
|
||||
}
|
||||
// Ignore GO ROOT paths.
|
||||
if goRootForFilter != "" &&
|
||||
len(file) >= len(goRootForFilter) &&
|
||||
file[0:len(goRootForFilter)] == goRootForFilter {
|
||||
continue
|
||||
}
|
||||
// Graceful indent.
|
||||
if index > 9 {
|
||||
space = " "
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf(
|
||||
" %d).%s%s\n \t%s:%d\n",
|
||||
index, space, fn.Name(), file, line,
|
||||
))
|
||||
index++
|
||||
}
|
||||
}
|
||||
}
|
||||
13
errors/gerror/gerror_error_json.go
Normal file
13
errors/gerror/gerror_error_json.go
Normal file
@ -0,0 +1,13 @@
|
||||
// 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 gerror
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
// Note that do not use pointer as its receiver here.
|
||||
func (err Error) MarshalJSON() ([]byte, error) {
|
||||
return []byte(`"` + err.Error() + `"`), nil
|
||||
}
|
||||
42
errors/gerror/gerror_error_stack.go
Normal file
42
errors/gerror/gerror_error_stack.go
Normal file
@ -0,0 +1,42 @@
|
||||
// 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 gerror
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Stack returns the stack callers as string.
|
||||
// It returns an empty string if the `err` does not support stacks.
|
||||
func (err *Error) Stack() string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
var (
|
||||
loop = err
|
||||
index = 1
|
||||
buffer = bytes.NewBuffer(nil)
|
||||
)
|
||||
for loop != nil {
|
||||
buffer.WriteString(fmt.Sprintf("%d. %-v\n", index, loop))
|
||||
index++
|
||||
formatSubStack(loop.stack, buffer)
|
||||
if loop.error != nil {
|
||||
if e, ok := loop.error.(*Error); ok {
|
||||
loop = e
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf("%d. %s\n", index, loop.error.Error()))
|
||||
index++
|
||||
break
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
51
errors/gerror/gerror_interface.go
Normal file
51
errors/gerror/gerror_interface.go
Normal file
@ -0,0 +1,51 @@
|
||||
// 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 gerror
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
)
|
||||
|
||||
// iIs is the interface for Is feature.
|
||||
type iIs interface {
|
||||
Is(target error) bool
|
||||
}
|
||||
|
||||
// iEqual is the interface for Equal feature.
|
||||
type iEqual interface {
|
||||
Equal(target error) bool
|
||||
}
|
||||
|
||||
// iCode is the interface for Code feature.
|
||||
type iCode interface {
|
||||
Error() string
|
||||
Code() gcode.Code
|
||||
}
|
||||
|
||||
// iStack is the interface for Stack feature.
|
||||
type iStack interface {
|
||||
Error() string
|
||||
Stack() string
|
||||
}
|
||||
|
||||
// iCause is the interface for Cause feature.
|
||||
type iCause interface {
|
||||
Error() string
|
||||
Cause() error
|
||||
}
|
||||
|
||||
// iCurrent is the interface for Current feature.
|
||||
type iCurrent interface {
|
||||
Error() string
|
||||
Current() error
|
||||
}
|
||||
|
||||
// iNext is the interface for Next feature.
|
||||
type iNext interface {
|
||||
Error() string
|
||||
Next() error
|
||||
}
|
||||
@ -16,7 +16,7 @@ type Option struct {
|
||||
Code gcode.Code // Error code if necessary.
|
||||
}
|
||||
|
||||
// NewOption creates and returns an error with Option.
|
||||
// NewOption creates and returns a custom error with Option.
|
||||
// It is the senior usage for creating error, which is often used internally in framework.
|
||||
func NewOption(option Option) error {
|
||||
err := &Error{
|
||||
|
||||
@ -255,6 +255,24 @@ func Test_Next(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Unwrap(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := errors.New("1")
|
||||
err = gerror.Wrap(err, "2")
|
||||
err = gerror.Wrap(err, "3")
|
||||
t.Assert(err.Error(), "3: 2: 1")
|
||||
|
||||
err = gerror.Unwrap(err)
|
||||
t.Assert(err.Error(), "2: 1")
|
||||
|
||||
err = gerror.Unwrap(err)
|
||||
t.Assert(err.Error(), "1")
|
||||
|
||||
err = gerror.Unwrap(err)
|
||||
t.AssertNil(err)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Code(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := errors.New("123")
|
||||
@ -340,3 +358,26 @@ func Test_HasStack(t *testing.T) {
|
||||
t.Assert(gerror.HasStack(err2), true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Equal(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err1 := errors.New("1")
|
||||
err2 := errors.New("1")
|
||||
err3 := gerror.New("1")
|
||||
err4 := gerror.New("4")
|
||||
t.Assert(gerror.Equal(err1, err2), false)
|
||||
t.Assert(gerror.Equal(err1, err3), true)
|
||||
t.Assert(gerror.Equal(err2, err3), true)
|
||||
t.Assert(gerror.Equal(err3, err4), false)
|
||||
t.Assert(gerror.Equal(err1, err4), false)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Is(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err1 := errors.New("1")
|
||||
err2 := gerror.Wrap(err1, "2")
|
||||
err2 = gerror.Wrap(err2, "3")
|
||||
t.Assert(gerror.Is(err2, err1), true)
|
||||
})
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ import (
|
||||
"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/internal/consts"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/os/gcfg"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
@ -23,7 +24,6 @@ import (
|
||||
|
||||
const (
|
||||
frameCoreComponentNameDatabase = "gf.core.component.database"
|
||||
configNodeNameDatabase = "database"
|
||||
)
|
||||
|
||||
// Database returns an instance of database ORM object with specified configuration group name.
|
||||
@ -42,11 +42,11 @@ func Database(name ...string) gdb.DB {
|
||||
// It ignores returned error to avoid file no found error while it's not necessary.
|
||||
var (
|
||||
configMap map[string]interface{}
|
||||
configNodeKey = configNodeNameDatabase
|
||||
configNodeKey = consts.ConfigNodeNameDatabase
|
||||
)
|
||||
// It firstly searches the configuration of the instance name.
|
||||
if configData, _ := Config().Data(ctx); len(configData) > 0 {
|
||||
if v, _ := gutil.MapPossibleItemByKey(configData, configNodeNameDatabase); v != "" {
|
||||
if v, _ := gutil.MapPossibleItemByKey(configData, consts.ConfigNodeNameDatabase); v != "" {
|
||||
configNodeKey = v
|
||||
}
|
||||
}
|
||||
@ -89,7 +89,7 @@ func Database(name ...string) gdb.DB {
|
||||
err = gerror.NewCodef(
|
||||
gcode.CodeMissingConfiguration,
|
||||
`database initialization failed: "%s" node not found, is configuration file or configuration node missing?`,
|
||||
configNodeNameDatabase,
|
||||
consts.ConfigNodeNameDatabase,
|
||||
)
|
||||
panic(err)
|
||||
}
|
||||
@ -130,13 +130,16 @@ func Database(name ...string) gdb.DB {
|
||||
if node.Link != "" || node.Host != "" {
|
||||
cg = append(cg, *node)
|
||||
}
|
||||
|
||||
if len(cg) > 0 {
|
||||
if gdb.GetConfig(group) == nil {
|
||||
intlog.Printf(ctx, "add configuration for group: %s, %#v", gdb.DefaultGroupName, cg)
|
||||
gdb.SetConfigGroup(gdb.DefaultGroupName, cg)
|
||||
} else {
|
||||
intlog.Printf(ctx, "ignore configuration as it already exists for group: %s, %#v", gdb.DefaultGroupName, cg)
|
||||
intlog.Printf(
|
||||
ctx,
|
||||
"ignore configuration as it already exists for group: %s, %#v",
|
||||
gdb.DefaultGroupName, cg,
|
||||
)
|
||||
intlog.Printf(ctx, "%s, %#v", gdb.DefaultGroupName, cg)
|
||||
}
|
||||
}
|
||||
@ -147,7 +150,7 @@ func Database(name ...string) gdb.DB {
|
||||
// Initialize logger for ORM.
|
||||
var (
|
||||
loggerConfigMap map[string]interface{}
|
||||
loggerNodeName = fmt.Sprintf("%s.%s", configNodeKey, configNodeNameLogger)
|
||||
loggerNodeName = fmt.Sprintf("%s.%s", configNodeKey, consts.ConfigNodeNameLogger)
|
||||
)
|
||||
if v, _ := Config().Get(ctx, loggerNodeName); !v.IsEmpty() {
|
||||
loggerConfigMap = v.Map()
|
||||
|
||||
@ -10,13 +10,13 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/consts"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
const (
|
||||
frameCoreComponentNameLogger = "gf.core.component.logger"
|
||||
configNodeNameLogger = "logger"
|
||||
)
|
||||
|
||||
// Log returns an instance of glog.Logger.
|
||||
@ -36,11 +36,11 @@ func Log(name ...string) *glog.Logger {
|
||||
// To avoid file no found error while it's not necessary.
|
||||
var (
|
||||
configMap map[string]interface{}
|
||||
loggerNodeName = configNodeNameLogger
|
||||
loggerNodeName = consts.ConfigNodeNameLogger
|
||||
)
|
||||
// Try to find possible `loggerNodeName` in case-insensitive way.
|
||||
if configData, _ := Config().Data(ctx); len(configData) > 0 {
|
||||
if v, _ := gutil.MapPossibleItemByKey(configData, configNodeNameLogger); v != "" {
|
||||
if v, _ := gutil.MapPossibleItemByKey(configData, consts.ConfigNodeNameLogger); v != "" {
|
||||
loggerNodeName = v
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gredis"
|
||||
"github.com/gogf/gf/v2/internal/consts"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
@ -18,7 +19,6 @@ import (
|
||||
|
||||
const (
|
||||
frameCoreComponentNameRedis = "gf.core.component.redis"
|
||||
configNodeNameRedis = "redis"
|
||||
)
|
||||
|
||||
// Redis returns an instance of redis client with specified configuration group name.
|
||||
@ -47,7 +47,7 @@ func Redis(name ...string) *gredis.Redis {
|
||||
if configMap, err = Config().Data(ctx); err != nil {
|
||||
intlog.Errorf(ctx, `retrieve config data map failed: %+v`, err)
|
||||
}
|
||||
if _, v := gutil.MapPossibleItemByKey(configMap, configNodeNameRedis); v != nil {
|
||||
if _, v := gutil.MapPossibleItemByKey(configMap, consts.ConfigNodeNameRedis); v != nil {
|
||||
configMap = gconv.Map(v)
|
||||
}
|
||||
if len(configMap) > 0 {
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/consts"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
@ -17,9 +18,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
frameCoreComponentNameServer = "gf.core.component.server" // Prefix for HTTP server instance.
|
||||
configNodeNameServer = "server" // General version configuration item name.
|
||||
configNodeNameServerSecondary = "httpserver" // New version configuration item name support from v2.
|
||||
frameCoreComponentNameServer = "gf.core.component.server" // Prefix for HTTP server instance.
|
||||
|
||||
)
|
||||
|
||||
// Server returns an instance of http server with specified name.
|
||||
@ -49,11 +49,11 @@ func Server(name ...interface{}) *ghttp.Server {
|
||||
}
|
||||
// Find possible server configuration item by possible names.
|
||||
if len(configMap) > 0 {
|
||||
if v, _ := gutil.MapPossibleItemByKey(configMap, configNodeNameServer); v != "" {
|
||||
if v, _ := gutil.MapPossibleItemByKey(configMap, consts.ConfigNodeNameServer); v != "" {
|
||||
configNodeName = v
|
||||
}
|
||||
if configNodeName == "" {
|
||||
if v, _ := gutil.MapPossibleItemByKey(configMap, configNodeNameServerSecondary); v != "" {
|
||||
if v, _ := gutil.MapPossibleItemByKey(configMap, consts.ConfigNodeNameServerSecondary); v != "" {
|
||||
configNodeName = v
|
||||
}
|
||||
}
|
||||
@ -81,7 +81,7 @@ func Server(name ...interface{}) *ghttp.Server {
|
||||
// Server logger configuration checks.
|
||||
serverLoggerConfigMap = Config().MustGet(
|
||||
ctx,
|
||||
fmt.Sprintf(`%s.%s.%s`, configNodeName, instanceName, configNodeNameLogger),
|
||||
fmt.Sprintf(`%s.%s.%s`, configNodeName, instanceName, consts.ConfigNodeNameLogger),
|
||||
).Map()
|
||||
if len(serverLoggerConfigMap) > 0 {
|
||||
if err = server.Logger().SetConfigWithMap(serverLoggerConfigMap); err != nil {
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/consts"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/os/gview"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
@ -17,7 +18,6 @@ import (
|
||||
|
||||
const (
|
||||
frameCoreComponentNameViewer = "gf.core.component.viewer"
|
||||
configNodeNameViewer = "viewer"
|
||||
)
|
||||
|
||||
// View returns an instance of View with default settings.
|
||||
@ -47,13 +47,13 @@ func getViewInstance(name ...string) *gview.View {
|
||||
if Config().Available(ctx) {
|
||||
var (
|
||||
configMap map[string]interface{}
|
||||
configNodeName = configNodeNameViewer
|
||||
configNodeName = consts.ConfigNodeNameViewer
|
||||
)
|
||||
if configMap, err = Config().Data(ctx); err != nil {
|
||||
intlog.Errorf(ctx, `retrieve config data map failed: %+v`, err)
|
||||
}
|
||||
if len(configMap) > 0 {
|
||||
if v, _ := gutil.MapPossibleItemByKey(configMap, configNodeNameViewer); v != "" {
|
||||
if v, _ := gutil.MapPossibleItemByKey(configMap, consts.ConfigNodeNameViewer); v != "" {
|
||||
configNodeName = v
|
||||
}
|
||||
}
|
||||
|
||||
17
internal/consts/consts.go
Normal file
17
internal/consts/consts.go
Normal file
@ -0,0 +1,17 @@
|
||||
// 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 consts defines constants that are shared all among packages of framework.
|
||||
package consts
|
||||
|
||||
const (
|
||||
ConfigNodeNameDatabase = "database"
|
||||
ConfigNodeNameLogger = "logger"
|
||||
ConfigNodeNameRedis = "redis"
|
||||
ConfigNodeNameViewer = "viewer"
|
||||
ConfigNodeNameServer = "server" // General version configuration item name.
|
||||
ConfigNodeNameServerSecondary = "httpserver" // New version configuration item name support from v2.
|
||||
)
|
||||
@ -83,13 +83,14 @@ func IsMap(value interface{}) bool {
|
||||
|
||||
// IsStruct checks whether `value` is type of struct.
|
||||
func IsStruct(value interface{}) bool {
|
||||
var (
|
||||
reflectValue = reflect.ValueOf(value)
|
||||
reflectKind = reflectValue.Kind()
|
||||
)
|
||||
var reflectType = reflect.TypeOf(value)
|
||||
if reflectType == nil {
|
||||
return false
|
||||
}
|
||||
var reflectKind = reflectType.Kind()
|
||||
for reflectKind == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
reflectType = reflectType.Elem()
|
||||
reflectKind = reflectType.Kind()
|
||||
}
|
||||
switch reflectKind {
|
||||
case reflect.Struct:
|
||||
|
||||
@ -9,6 +9,7 @@ package ghttp
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"reflect"
|
||||
@ -152,9 +153,12 @@ func (r *Request) Get(key string, def ...interface{}) *gvar.Var {
|
||||
func (r *Request) GetBody() []byte {
|
||||
if r.bodyContent == nil {
|
||||
var err error
|
||||
r.bodyContent, err = ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
panic(gerror.WrapCode(gcode.CodeInternalError, err, `ReadAll from body failed`))
|
||||
if r.bodyContent, err = ioutil.ReadAll(r.Body); err != nil {
|
||||
errMsg := `Read from request Body failed`
|
||||
if gerror.Is(err, io.EOF) {
|
||||
errMsg += `, the Body might be closed or read manually from middleware/hook/other package previously`
|
||||
}
|
||||
panic(gerror.WrapCode(gcode.CodeInternalError, err, errMsg))
|
||||
}
|
||||
r.Body = utils.NewReadCloser(r.bodyContent, true)
|
||||
}
|
||||
@ -170,7 +174,10 @@ func (r *Request) GetBodyString() string {
|
||||
// GetJson parses current request content as JSON format, and returns the JSON object.
|
||||
// Note that the request content is read from request BODY, not from any field of FORM.
|
||||
func (r *Request) GetJson() (*gjson.Json, error) {
|
||||
return gjson.LoadJson(r.GetBody())
|
||||
return gjson.LoadWithOptions(r.GetBody(), gjson.Options{
|
||||
Type: gjson.ContentTypeJson,
|
||||
StrNumber: true,
|
||||
})
|
||||
}
|
||||
|
||||
// GetMap is an alias and convenient function for GetRequestMap.
|
||||
|
||||
@ -9,10 +9,16 @@ package ghttp
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/net/gtcp"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
@ -50,6 +56,9 @@ type ServerConfig struct {
|
||||
// HTTPSAddr specifies the HTTPS addresses, multiple addresses joined using char ','.
|
||||
HTTPSAddr string `json:"httpsAddr"`
|
||||
|
||||
// Listeners specifies the custom listeners.
|
||||
Listeners []net.Listener `json:"listeners"`
|
||||
|
||||
// HTTPSCertPath specifies certification file path for HTTPS service.
|
||||
HTTPSCertPath string `json:"httpsCertPath"`
|
||||
|
||||
@ -254,6 +263,7 @@ func NewConfig() ServerConfig {
|
||||
Name: DefaultServerName,
|
||||
Address: ":0",
|
||||
HTTPSAddr: "",
|
||||
Listeners: nil,
|
||||
Handler: nil,
|
||||
ReadTimeout: 60 * time.Second,
|
||||
WriteTimeout: 0, // No timeout.
|
||||
@ -330,7 +340,7 @@ func (s *Server) SetConfigWithMap(m map[string]interface{}) error {
|
||||
func (s *Server) SetConfig(c ServerConfig) error {
|
||||
s.config = c
|
||||
// Automatically add ':' prefix for address if it is missed.
|
||||
if s.config.Address != "" && !gstr.HasPrefix(s.config.Address, ":") {
|
||||
if s.config.Address != "" && !gstr.Contains(s.config.Address, ":") {
|
||||
s.config.Address = ":" + s.config.Address
|
||||
}
|
||||
// It checks and uses a random free port.
|
||||
@ -408,6 +418,25 @@ func (s *Server) SetHTTPSPort(port ...int) {
|
||||
}
|
||||
}
|
||||
|
||||
// SetListener set the custom listener for the server.
|
||||
func (s *Server) SetListener(listeners ...net.Listener) error {
|
||||
if listeners == nil {
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "SetListener failed: listener can not be nil")
|
||||
}
|
||||
if len(listeners) > 0 {
|
||||
ports := make([]string, len(listeners))
|
||||
for k, v := range listeners {
|
||||
if v == nil {
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "SetListener failed: listener can not be nil")
|
||||
}
|
||||
ports[k] = fmt.Sprintf(":%d", (v.Addr().(*net.TCPAddr)).Port)
|
||||
}
|
||||
s.config.Address = strings.Join(ports, ",")
|
||||
s.config.Listeners = listeners
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnableHTTPS enables HTTPS with given certification and key files for the server.
|
||||
// The optional parameter `tlsConfig` specifies custom TLS configuration.
|
||||
func (s *Server) EnableHTTPS(certFile, keyFile string, tlsConfig ...*tls.Config) {
|
||||
|
||||
@ -13,6 +13,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
@ -49,6 +50,18 @@ func (s *Server) newGracefulServer(address string, fd ...int) *gracefulServer {
|
||||
if len(fd) > 0 && fd[0] > 0 {
|
||||
gs.fd = uintptr(fd[0])
|
||||
}
|
||||
if s.config.Listeners != nil {
|
||||
addrArray := gstr.SplitAndTrim(address, ":")
|
||||
addrPort, err := strconv.Atoi(addrArray[len(addrArray)-1])
|
||||
if err == nil {
|
||||
for _, v := range s.config.Listeners {
|
||||
if listenerPort := (v.Addr().(*net.TCPAddr)).Port; listenerPort == addrPort {
|
||||
gs.rawListener = v
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return gs
|
||||
}
|
||||
|
||||
@ -175,6 +188,9 @@ func (s *gracefulServer) doServe(ctx context.Context) error {
|
||||
|
||||
// getNetListener retrieves and returns the wrapped net.Listener.
|
||||
func (s *gracefulServer) getNetListener() (net.Listener, error) {
|
||||
if s.rawListener != nil {
|
||||
return s.rawListener, nil
|
||||
}
|
||||
var (
|
||||
ln net.Listener
|
||||
err error
|
||||
|
||||
@ -8,9 +8,12 @@ package ghttp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/net/gtcp"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
@ -23,8 +26,15 @@ import (
|
||||
|
||||
func Test_ConfigFromMap(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
p, _ := gtcp.GetFreePort()
|
||||
addr := fmt.Sprintf(":%d", p)
|
||||
ln, err := net.Listen("tcp", addr)
|
||||
t.AssertNil(err)
|
||||
listeners := []net.Listener{ln}
|
||||
|
||||
m := g.Map{
|
||||
"address": ":8199",
|
||||
"address": addr,
|
||||
"listeners": listeners,
|
||||
"readTimeout": "60s",
|
||||
"indexFiles": g.Slice{"index.php", "main.php"},
|
||||
"errorLogEnabled": true,
|
||||
@ -38,6 +48,7 @@ func Test_ConfigFromMap(t *testing.T) {
|
||||
d1, _ := time.ParseDuration(gconv.String(m["readTimeout"]))
|
||||
d2, _ := time.ParseDuration(gconv.String(m["cookieMaxAge"]))
|
||||
t.Assert(config.Address, m["address"])
|
||||
t.Assert(config.Listeners, listeners)
|
||||
t.Assert(config.ReadTimeout, d1)
|
||||
t.Assert(config.CookieMaxAge, d2)
|
||||
t.Assert(config.IndexFiles, m["indexFiles"])
|
||||
@ -98,7 +109,7 @@ func Test_ClientMaxBodySize(t *testing.T) {
|
||||
}
|
||||
t.Assert(
|
||||
gstr.Trim(c.PostContent(ctx, "/", data)),
|
||||
`ReadAll from body failed: http: request body too large`,
|
||||
`Read from request Body failed: http: request body too large`,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
101
net/ghttp/ghttp_z_unit_feature_custom_listeners_test.go
Normal file
101
net/ghttp/ghttp_z_unit_feature_custom_listeners_test.go
Normal file
@ -0,0 +1,101 @@
|
||||
// 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 ghttp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/net/gtcp"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/guid"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
)
|
||||
|
||||
func Test_SetSingleCustomListener(t *testing.T) {
|
||||
p, _ := gtcp.GetFreePort()
|
||||
ln, _ := net.Listen("tcp", fmt.Sprintf(":%d", p))
|
||||
s := g.Server(guid.S())
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.GET("/test", func(r *ghttp.Request) {
|
||||
r.Response.Write("test")
|
||||
})
|
||||
})
|
||||
|
||||
s.SetListener(ln)
|
||||
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
c := g.Client()
|
||||
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
|
||||
t.Assert(
|
||||
gstr.Trim(c.GetContent(ctx, "/test")),
|
||||
"test",
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_SetMultipleCustomListeners(t *testing.T) {
|
||||
p1, _ := gtcp.GetFreePort()
|
||||
p2, _ := gtcp.GetFreePort()
|
||||
|
||||
ln1, _ := net.Listen("tcp", fmt.Sprintf(":%d", p1))
|
||||
ln2, _ := net.Listen("tcp", fmt.Sprintf(":%d", p2))
|
||||
|
||||
s := g.Server(guid.S())
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.GET("/test", func(r *ghttp.Request) {
|
||||
r.Response.Write("test")
|
||||
})
|
||||
})
|
||||
|
||||
s.SetListener(ln1, ln2)
|
||||
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
c := g.Client()
|
||||
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p1))
|
||||
|
||||
t.Assert(
|
||||
gstr.Trim(c.GetContent(ctx, "/test")),
|
||||
"test",
|
||||
)
|
||||
|
||||
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p2))
|
||||
|
||||
t.Assert(
|
||||
gstr.Trim(c.GetContent(ctx, "/test")),
|
||||
"test",
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_SetWrongCustomListeners(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.GET("/test", func(r *ghttp.Request) {
|
||||
r.Response.Write("test")
|
||||
})
|
||||
})
|
||||
err := s.SetListener(nil)
|
||||
t.AssertNQ(err, nil)
|
||||
})
|
||||
}
|
||||
@ -19,7 +19,7 @@ import (
|
||||
"github.com/gogf/gf/v2/util/guid"
|
||||
)
|
||||
|
||||
func Test_Router_Handler_Extended_Handler_WithObject(t *testing.T) {
|
||||
func Test_Router_Handler_Strict_WithObject(t *testing.T) {
|
||||
type TestReq struct {
|
||||
Age int
|
||||
Name string
|
||||
@ -130,7 +130,7 @@ func (ControllerForHandlerWithObjectAndMeta2) Test4(ctx context.Context, req *Te
|
||||
Name: req.Name,
|
||||
}, nil
|
||||
}
|
||||
func Test_Router_Handler_Extended_Handler_WithObjectAndMeta(t *testing.T) {
|
||||
func Test_Router_Handler_Strict_WithObjectAndMeta(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.Use(ghttp.MiddlewareHandlerResponse)
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
@ -152,7 +152,7 @@ func Test_Router_Handler_Extended_Handler_WithObjectAndMeta(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Router_Handler_Extended_Handler_Group_Bind(t *testing.T) {
|
||||
func Test_Router_Handler_Strict_Group_Bind(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.Use(ghttp.MiddlewareHandlerResponse)
|
||||
s.Group("/api/v1", func(group *ghttp.RouterGroup) {
|
||||
@ -190,3 +190,61 @@ func Test_Router_Handler_Extended_Handler_Group_Bind(t *testing.T) {
|
||||
t.Assert(client.GetContent(ctx, "/api/v2/custom-test4?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Name":"john"}}`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Issue1708(t *testing.T) {
|
||||
type Test struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
type Req struct {
|
||||
Page int `json:"page" dc:"分页码"`
|
||||
Size int `json:"size" dc:"分页数量"`
|
||||
TargetType string `json:"targetType" v:"required#评论内容类型错误" dc:"评论类型: topic/ask/article/reply"`
|
||||
TargetId uint `json:"targetId" v:"required#评论目标ID错误" dc:"对应内容ID"`
|
||||
Test [][]Test `json:"test"`
|
||||
}
|
||||
type Res struct {
|
||||
Page int `json:"page" dc:"分页码"`
|
||||
Size int `json:"size" dc:"分页数量"`
|
||||
TargetType string `json:"targetType" v:"required#评论内容类型错误" dc:"评论类型: topic/ask/article/reply"`
|
||||
TargetId uint `json:"targetId" v:"required#评论目标ID错误" dc:"对应内容ID"`
|
||||
Test [][]Test `json:"test"`
|
||||
}
|
||||
|
||||
s := g.Server(guid.S())
|
||||
s.Use(ghttp.MiddlewareHandlerResponse)
|
||||
s.BindHandler("/test", func(ctx context.Context, req *Req) (res *Res, err error) {
|
||||
return &Res{
|
||||
Page: req.Page,
|
||||
Size: req.Size,
|
||||
TargetType: req.TargetType,
|
||||
TargetId: req.TargetId,
|
||||
Test: req.Test,
|
||||
}, nil
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := g.Client()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
content := `
|
||||
{
|
||||
"targetType":"topic",
|
||||
"targetId":10785,
|
||||
"test":[
|
||||
[
|
||||
{
|
||||
"name":"123"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
`
|
||||
t.Assert(
|
||||
client.PostContent(ctx, "/test", content),
|
||||
`{"code":0,"message":"","data":{"page":0,"size":0,"targetType":"topic","targetId":10785,"test":[[{"name":"123"}]]}}`,
|
||||
)
|
||||
})
|
||||
}
|
||||
@ -81,7 +81,8 @@ func (i *HandlerInput) getDefaultBuffer(withColor bool) *bytes.Buffer {
|
||||
if i.Content != "" {
|
||||
i.addStringToBuffer(buffer, i.Content)
|
||||
}
|
||||
i.addStringToBuffer(buffer, "\n")
|
||||
// avoid a single space at the end of a line.
|
||||
buffer.WriteString("\n")
|
||||
return buffer
|
||||
}
|
||||
|
||||
|
||||
@ -87,6 +87,7 @@ func TagFields(pointer interface{}, priority []string) ([]Field, error) {
|
||||
// Note that,
|
||||
// 1. It only retrieves the exported attributes with first letter up-case from struct.
|
||||
// 2. The parameter `priority` should be given, it only retrieves fields that has given tag.
|
||||
// 3. If one field has no specified tag, it uses its field name as result map key.
|
||||
func TagMapName(pointer interface{}, priority []string) (map[string]string, error) {
|
||||
fields, err := TagFields(pointer, priority)
|
||||
if err != nil {
|
||||
@ -105,6 +106,7 @@ func TagMapName(pointer interface{}, priority []string) (map[string]string, erro
|
||||
// Note that,
|
||||
// 1. It only retrieves the exported attributes with first letter up-case from struct.
|
||||
// 2. The parameter `priority` should be given, it only retrieves fields that has given tag.
|
||||
// 3. If one field has no specified tag, it uses its field name as result map key.
|
||||
func TagMapField(object interface{}, priority []string) (map[string]Field, error) {
|
||||
fields, err := TagFields(object, priority)
|
||||
if err != nil {
|
||||
@ -174,7 +176,9 @@ exitLoop:
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
func getFieldValuesByTagPriority(pointer interface{}, priority []string, tagMap map[string]struct{}) ([]Field, error) {
|
||||
func getFieldValuesByTagPriority(
|
||||
pointer interface{}, priority []string, repeatedTagFilteringMap map[string]struct{},
|
||||
) ([]Field, error) {
|
||||
fields, err := getFieldValues(pointer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -199,7 +203,7 @@ func getFieldValuesByTagPriority(pointer interface{}, priority []string, tagMap
|
||||
}
|
||||
if tagValue != "" {
|
||||
// Filter repeated tag.
|
||||
if _, ok := tagMap[tagValue]; ok {
|
||||
if _, ok := repeatedTagFilteringMap[tagValue]; ok {
|
||||
continue
|
||||
}
|
||||
tagField := field
|
||||
@ -209,7 +213,8 @@ func getFieldValuesByTagPriority(pointer interface{}, priority []string, tagMap
|
||||
}
|
||||
// If this is an embedded attribute, it retrieves the tags recursively.
|
||||
if field.IsEmbedded() && field.OriginalKind() == reflect.Struct {
|
||||
if subTagFields, err := getFieldValuesByTagPriority(field.Value, priority, tagMap); err != nil {
|
||||
subTagFields, err := getFieldValuesByTagPriority(field.Value, priority, repeatedTagFilteringMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
tagFields = append(tagFields, subTagFields...)
|
||||
|
||||
@ -19,6 +19,7 @@ import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gmode"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
@ -27,9 +28,13 @@ func (view *View) buildInFuncDump(values ...interface{}) string {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
buffer.WriteString("\n")
|
||||
buffer.WriteString("<!--\n")
|
||||
for _, v := range values {
|
||||
gutil.DumpTo(buffer, v, gutil.DumpOption{})
|
||||
buffer.WriteString("\n")
|
||||
if gmode.IsDevelop() {
|
||||
for _, v := range values {
|
||||
gutil.DumpTo(buffer, v, gutil.DumpOption{})
|
||||
buffer.WriteString("\n")
|
||||
}
|
||||
} else {
|
||||
buffer.WriteString("dump feature is disabled as process is not running in develop mode\n")
|
||||
}
|
||||
buffer.WriteString("-->\n")
|
||||
return buffer.String()
|
||||
|
||||
@ -38,8 +38,7 @@ func getRegexp(pattern string) (regex *regexp.Regexp, err error) {
|
||||
}
|
||||
// If it does not exist in the cache,
|
||||
// it compiles the pattern and creates one.
|
||||
regex, err = regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
if regex, err = regexp.Compile(pattern); err != nil {
|
||||
err = gerror.Wrapf(err, `regexp.Compile failed for pattern "%s"`, pattern)
|
||||
return
|
||||
}
|
||||
|
||||
@ -41,258 +41,6 @@ var (
|
||||
StructTagPriority = []string{"gconv", "param", "params", "c", "p", "json"}
|
||||
)
|
||||
|
||||
// Convert converts the variable `fromValue` to the type `toTypeName`, the type `toTypeName` is specified by string.
|
||||
// The optional parameter `extraParams` is used for additional necessary parameter for this conversion.
|
||||
// It supports common types conversion as its conversion based on type name string.
|
||||
func Convert(fromValue interface{}, toTypeName string, extraParams ...interface{}) interface{} {
|
||||
return doConvert(doConvertInput{
|
||||
FromValue: fromValue,
|
||||
ToTypeName: toTypeName,
|
||||
ReferValue: nil,
|
||||
Extra: extraParams,
|
||||
})
|
||||
}
|
||||
|
||||
type doConvertInput struct {
|
||||
FromValue interface{} // Value that is converted from.
|
||||
ToTypeName string // Target value type name in string.
|
||||
ReferValue interface{} // Referred value, a value in type `ToTypeName`.
|
||||
Extra []interface{} // Extra values for implementing the converting.
|
||||
}
|
||||
|
||||
// doConvert does commonly use types converting.
|
||||
func doConvert(in doConvertInput) interface{} {
|
||||
switch in.ToTypeName {
|
||||
case "int":
|
||||
return Int(in.FromValue)
|
||||
case "*int":
|
||||
if _, ok := in.FromValue.(*int); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Int(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "int8":
|
||||
return Int8(in.FromValue)
|
||||
case "*int8":
|
||||
if _, ok := in.FromValue.(*int8); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Int8(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "int16":
|
||||
return Int16(in.FromValue)
|
||||
case "*int16":
|
||||
if _, ok := in.FromValue.(*int16); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Int16(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "int32":
|
||||
return Int32(in.FromValue)
|
||||
case "*int32":
|
||||
if _, ok := in.FromValue.(*int32); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Int32(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "int64":
|
||||
return Int64(in.FromValue)
|
||||
case "*int64":
|
||||
if _, ok := in.FromValue.(*int64); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Int64(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "uint":
|
||||
return Uint(in.FromValue)
|
||||
case "*uint":
|
||||
if _, ok := in.FromValue.(*uint); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Uint(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "uint8":
|
||||
return Uint8(in.FromValue)
|
||||
case "*uint8":
|
||||
if _, ok := in.FromValue.(*uint8); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Uint8(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "uint16":
|
||||
return Uint16(in.FromValue)
|
||||
case "*uint16":
|
||||
if _, ok := in.FromValue.(*uint16); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Uint16(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "uint32":
|
||||
return Uint32(in.FromValue)
|
||||
case "*uint32":
|
||||
if _, ok := in.FromValue.(*uint32); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Uint32(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "uint64":
|
||||
return Uint64(in.FromValue)
|
||||
case "*uint64":
|
||||
if _, ok := in.FromValue.(*uint64); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Uint64(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "float32":
|
||||
return Float32(in.FromValue)
|
||||
case "*float32":
|
||||
if _, ok := in.FromValue.(*float32); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Float32(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "float64":
|
||||
return Float64(in.FromValue)
|
||||
case "*float64":
|
||||
if _, ok := in.FromValue.(*float64); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Float64(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "bool":
|
||||
return Bool(in.FromValue)
|
||||
case "*bool":
|
||||
if _, ok := in.FromValue.(*bool); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Bool(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "string":
|
||||
return String(in.FromValue)
|
||||
case "*string":
|
||||
if _, ok := in.FromValue.(*string); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := String(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "[]byte":
|
||||
return Bytes(in.FromValue)
|
||||
case "[]int":
|
||||
return Ints(in.FromValue)
|
||||
case "[]int32":
|
||||
return Int32s(in.FromValue)
|
||||
case "[]int64":
|
||||
return Int64s(in.FromValue)
|
||||
case "[]uint":
|
||||
return Uints(in.FromValue)
|
||||
case "[]uint8":
|
||||
return Bytes(in.FromValue)
|
||||
case "[]uint32":
|
||||
return Uint32s(in.FromValue)
|
||||
case "[]uint64":
|
||||
return Uint64s(in.FromValue)
|
||||
case "[]float32":
|
||||
return Float32s(in.FromValue)
|
||||
case "[]float64":
|
||||
return Float64s(in.FromValue)
|
||||
case "[]string":
|
||||
return Strings(in.FromValue)
|
||||
|
||||
case "Time", "time.Time":
|
||||
if len(in.Extra) > 0 {
|
||||
return Time(in.FromValue, String(in.Extra[0]))
|
||||
}
|
||||
return Time(in.FromValue)
|
||||
case "*time.Time":
|
||||
var v interface{}
|
||||
if len(in.Extra) > 0 {
|
||||
v = Time(in.FromValue, String(in.Extra[0]))
|
||||
} else {
|
||||
if _, ok := in.FromValue.(*time.Time); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v = Time(in.FromValue)
|
||||
}
|
||||
return &v
|
||||
|
||||
case "GTime", "gtime.Time":
|
||||
if len(in.Extra) > 0 {
|
||||
if v := GTime(in.FromValue, String(in.Extra[0])); v != nil {
|
||||
return *v
|
||||
} else {
|
||||
return *gtime.New()
|
||||
}
|
||||
}
|
||||
if v := GTime(in.FromValue); v != nil {
|
||||
return *v
|
||||
} else {
|
||||
return *gtime.New()
|
||||
}
|
||||
case "*gtime.Time":
|
||||
if len(in.Extra) > 0 {
|
||||
if v := GTime(in.FromValue, String(in.Extra[0])); v != nil {
|
||||
return v
|
||||
} else {
|
||||
return gtime.New()
|
||||
}
|
||||
}
|
||||
if v := GTime(in.FromValue); v != nil {
|
||||
return v
|
||||
} else {
|
||||
return gtime.New()
|
||||
}
|
||||
|
||||
case "Duration", "time.Duration":
|
||||
return Duration(in.FromValue)
|
||||
case "*time.Duration":
|
||||
if _, ok := in.FromValue.(*time.Duration); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Duration(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "map[string]string":
|
||||
return MapStrStr(in.FromValue)
|
||||
|
||||
case "map[string]interface{}":
|
||||
return Map(in.FromValue)
|
||||
|
||||
case "[]map[string]interface{}":
|
||||
return Maps(in.FromValue)
|
||||
|
||||
case "json.RawMessage":
|
||||
return Bytes(in.FromValue)
|
||||
|
||||
default:
|
||||
if in.ReferValue != nil {
|
||||
var referReflectValue reflect.Value
|
||||
if v, ok := in.ReferValue.(reflect.Value); ok {
|
||||
referReflectValue = v
|
||||
} else {
|
||||
referReflectValue = reflect.ValueOf(in.ReferValue)
|
||||
}
|
||||
in.ToTypeName = referReflectValue.Kind().String()
|
||||
in.ReferValue = nil
|
||||
return reflect.ValueOf(doConvert(in)).Convert(referReflectValue.Type()).Interface()
|
||||
}
|
||||
return in.FromValue
|
||||
}
|
||||
}
|
||||
|
||||
// Byte converts `any` to byte.
|
||||
func Byte(any interface{}) byte {
|
||||
if v, ok := any.(byte); ok {
|
||||
|
||||
284
util/gconv/gconv_convert.go
Normal file
284
util/gconv/gconv_convert.go
Normal file
@ -0,0 +1,284 @@
|
||||
// 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 (
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// Convert converts the variable `fromValue` to the type `toTypeName`, the type `toTypeName` is specified by string.
|
||||
// The optional parameter `extraParams` is used for additional necessary parameter for this conversion.
|
||||
// It supports common types conversion as its conversion based on type name string.
|
||||
func Convert(fromValue interface{}, toTypeName string, extraParams ...interface{}) interface{} {
|
||||
return doConvert(doConvertInput{
|
||||
FromValue: fromValue,
|
||||
ToTypeName: toTypeName,
|
||||
ReferValue: nil,
|
||||
Extra: extraParams,
|
||||
})
|
||||
}
|
||||
|
||||
type doConvertInput struct {
|
||||
FromValue interface{} // Value that is converted from.
|
||||
ToTypeName string // Target value type name in string.
|
||||
ReferValue interface{} // Referred value, a value in type `ToTypeName`.
|
||||
Extra []interface{} // Extra values for implementing the converting.
|
||||
// Marks that the value is already converted and set to `ReferValue`. Caller can ignore the returned result.
|
||||
// It is an attribute for internal usage purpose.
|
||||
alreadySetToReferValue bool
|
||||
}
|
||||
|
||||
// doConvert does commonly use types converting.
|
||||
func doConvert(in doConvertInput) (convertedValue interface{}) {
|
||||
switch in.ToTypeName {
|
||||
case "int":
|
||||
return Int(in.FromValue)
|
||||
case "*int":
|
||||
if _, ok := in.FromValue.(*int); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Int(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "int8":
|
||||
return Int8(in.FromValue)
|
||||
case "*int8":
|
||||
if _, ok := in.FromValue.(*int8); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Int8(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "int16":
|
||||
return Int16(in.FromValue)
|
||||
case "*int16":
|
||||
if _, ok := in.FromValue.(*int16); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Int16(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "int32":
|
||||
return Int32(in.FromValue)
|
||||
case "*int32":
|
||||
if _, ok := in.FromValue.(*int32); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Int32(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "int64":
|
||||
return Int64(in.FromValue)
|
||||
case "*int64":
|
||||
if _, ok := in.FromValue.(*int64); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Int64(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "uint":
|
||||
return Uint(in.FromValue)
|
||||
case "*uint":
|
||||
if _, ok := in.FromValue.(*uint); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Uint(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "uint8":
|
||||
return Uint8(in.FromValue)
|
||||
case "*uint8":
|
||||
if _, ok := in.FromValue.(*uint8); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Uint8(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "uint16":
|
||||
return Uint16(in.FromValue)
|
||||
case "*uint16":
|
||||
if _, ok := in.FromValue.(*uint16); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Uint16(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "uint32":
|
||||
return Uint32(in.FromValue)
|
||||
case "*uint32":
|
||||
if _, ok := in.FromValue.(*uint32); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Uint32(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "uint64":
|
||||
return Uint64(in.FromValue)
|
||||
case "*uint64":
|
||||
if _, ok := in.FromValue.(*uint64); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Uint64(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "float32":
|
||||
return Float32(in.FromValue)
|
||||
case "*float32":
|
||||
if _, ok := in.FromValue.(*float32); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Float32(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "float64":
|
||||
return Float64(in.FromValue)
|
||||
case "*float64":
|
||||
if _, ok := in.FromValue.(*float64); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Float64(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "bool":
|
||||
return Bool(in.FromValue)
|
||||
case "*bool":
|
||||
if _, ok := in.FromValue.(*bool); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Bool(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "string":
|
||||
return String(in.FromValue)
|
||||
case "*string":
|
||||
if _, ok := in.FromValue.(*string); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := String(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "[]byte":
|
||||
return Bytes(in.FromValue)
|
||||
case "[]int":
|
||||
return Ints(in.FromValue)
|
||||
case "[]int32":
|
||||
return Int32s(in.FromValue)
|
||||
case "[]int64":
|
||||
return Int64s(in.FromValue)
|
||||
case "[]uint":
|
||||
return Uints(in.FromValue)
|
||||
case "[]uint8":
|
||||
return Bytes(in.FromValue)
|
||||
case "[]uint32":
|
||||
return Uint32s(in.FromValue)
|
||||
case "[]uint64":
|
||||
return Uint64s(in.FromValue)
|
||||
case "[]float32":
|
||||
return Float32s(in.FromValue)
|
||||
case "[]float64":
|
||||
return Float64s(in.FromValue)
|
||||
case "[]string":
|
||||
return Strings(in.FromValue)
|
||||
|
||||
case "Time", "time.Time":
|
||||
if len(in.Extra) > 0 {
|
||||
return Time(in.FromValue, String(in.Extra[0]))
|
||||
}
|
||||
return Time(in.FromValue)
|
||||
case "*time.Time":
|
||||
var v interface{}
|
||||
if len(in.Extra) > 0 {
|
||||
v = Time(in.FromValue, String(in.Extra[0]))
|
||||
} else {
|
||||
if _, ok := in.FromValue.(*time.Time); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v = Time(in.FromValue)
|
||||
}
|
||||
return &v
|
||||
|
||||
case "GTime", "gtime.Time":
|
||||
if len(in.Extra) > 0 {
|
||||
if v := GTime(in.FromValue, String(in.Extra[0])); v != nil {
|
||||
return *v
|
||||
} else {
|
||||
return *gtime.New()
|
||||
}
|
||||
}
|
||||
if v := GTime(in.FromValue); v != nil {
|
||||
return *v
|
||||
} else {
|
||||
return *gtime.New()
|
||||
}
|
||||
case "*gtime.Time":
|
||||
if len(in.Extra) > 0 {
|
||||
if v := GTime(in.FromValue, String(in.Extra[0])); v != nil {
|
||||
return v
|
||||
} else {
|
||||
return gtime.New()
|
||||
}
|
||||
}
|
||||
if v := GTime(in.FromValue); v != nil {
|
||||
return v
|
||||
} else {
|
||||
return gtime.New()
|
||||
}
|
||||
|
||||
case "Duration", "time.Duration":
|
||||
return Duration(in.FromValue)
|
||||
case "*time.Duration":
|
||||
if _, ok := in.FromValue.(*time.Duration); ok {
|
||||
return in.FromValue
|
||||
}
|
||||
v := Duration(in.FromValue)
|
||||
return &v
|
||||
|
||||
case "map[string]string":
|
||||
return MapStrStr(in.FromValue)
|
||||
|
||||
case "map[string]interface{}":
|
||||
return Map(in.FromValue)
|
||||
|
||||
case "[]map[string]interface{}":
|
||||
return Maps(in.FromValue)
|
||||
|
||||
case "json.RawMessage":
|
||||
return Bytes(in.FromValue)
|
||||
|
||||
default:
|
||||
if in.ReferValue != nil {
|
||||
var referReflectValue reflect.Value
|
||||
if v, ok := in.ReferValue.(reflect.Value); ok {
|
||||
referReflectValue = v
|
||||
} else {
|
||||
referReflectValue = reflect.ValueOf(in.ReferValue)
|
||||
}
|
||||
defer func() {
|
||||
if recover() != nil {
|
||||
if err := bindVarToReflectValue(referReflectValue, in.FromValue, nil); err == nil {
|
||||
in.alreadySetToReferValue = true
|
||||
convertedValue = referReflectValue.Interface()
|
||||
}
|
||||
}
|
||||
}()
|
||||
in.ToTypeName = referReflectValue.Kind().String()
|
||||
in.ReferValue = nil
|
||||
return reflect.ValueOf(doConvert(in)).Convert(referReflectValue.Type()).Interface()
|
||||
}
|
||||
return in.FromValue
|
||||
}
|
||||
}
|
||||
|
||||
func doConvertWithReflectValueSet(reflectValue reflect.Value, in doConvertInput) {
|
||||
convertedValue := doConvert(in)
|
||||
if !in.alreadySetToReferValue {
|
||||
reflectValue.Set(reflect.ValueOf(convertedValue))
|
||||
}
|
||||
}
|
||||
@ -192,11 +192,11 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
|
||||
// The key of the attrMap is the attribute name of the struct,
|
||||
// and the value is its replaced name for later comparison to improve performance.
|
||||
var (
|
||||
tempName string
|
||||
elemFieldType reflect.StructField
|
||||
elemFieldValue reflect.Value
|
||||
elemType = pointerElemReflectValue.Type()
|
||||
attrMap = make(map[string]string) // Attribute name to its check name which has no symbols.
|
||||
tempName string
|
||||
elemFieldType reflect.StructField
|
||||
elemFieldValue reflect.Value
|
||||
elemType = pointerElemReflectValue.Type()
|
||||
attrToCheckNameMap = make(map[string]string)
|
||||
)
|
||||
for i := 0; i < pointerElemReflectValue.NumField(); i++ {
|
||||
elemFieldType = elemType.Field(i)
|
||||
@ -219,46 +219,39 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
|
||||
}
|
||||
} else {
|
||||
tempName = elemFieldType.Name
|
||||
attrMap[tempName] = utils.RemoveSymbols(tempName)
|
||||
attrToCheckNameMap[tempName] = utils.RemoveSymbols(tempName)
|
||||
}
|
||||
}
|
||||
if len(attrMap) == 0 {
|
||||
if len(attrToCheckNameMap) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// The key of the tagMap is the attribute name of the struct,
|
||||
// and the value is its replaced tag name for later comparison to improve performance.
|
||||
var (
|
||||
tagMap = make(map[string]string) // Tag name to its check name which has no symbols.
|
||||
priorityTagArray []string
|
||||
attrToTagCheckNameMap = make(map[string]string)
|
||||
priorityTagArray []string
|
||||
)
|
||||
if priorityTag != "" {
|
||||
priorityTagArray = append(utils.SplitAndTrim(priorityTag, ","), StructTagPriority...)
|
||||
} else {
|
||||
priorityTagArray = StructTagPriority
|
||||
}
|
||||
tagToNameMap, err := gstructs.TagMapName(pointerElemReflectValue, priorityTagArray)
|
||||
tagToAttrNameMap, err := gstructs.TagMapName(pointerElemReflectValue, priorityTagArray)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for tagName, attributeName := range tagToNameMap {
|
||||
for tagName, attributeName := range tagToAttrNameMap {
|
||||
// If there's something else in the tag string,
|
||||
// it uses the first part which is split using char ','.
|
||||
// Eg:
|
||||
// orm:"id, priority"
|
||||
// orm:"name, with:uid=id"
|
||||
tagMap[attributeName] = utils.RemoveSymbols(strings.Split(tagName, ",")[0])
|
||||
attrToTagCheckNameMap[attributeName] = utils.RemoveSymbols(strings.Split(tagName, ",")[0])
|
||||
// If tag and attribute values both exist in `paramsMap`,
|
||||
// it then uses the tag value overwriting the attribute value in `paramsMap`.
|
||||
if paramsMap[tagName] != nil {
|
||||
for paramsKey, _ := range paramsMap {
|
||||
if paramsKey == tagName {
|
||||
continue
|
||||
}
|
||||
if utils.EqualFoldWithoutChars(paramsKey, attributeName) {
|
||||
paramsMap[paramsKey] = paramsMap[tagName]
|
||||
}
|
||||
}
|
||||
if paramsMap[tagName] != nil && paramsMap[attributeName] != nil {
|
||||
paramsMap[attributeName] = paramsMap[tagName]
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,31 +259,37 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
|
||||
attrName string
|
||||
checkName string
|
||||
)
|
||||
for mapK, mapV := range paramsMap {
|
||||
for paramName, paramValue := range paramsMap {
|
||||
attrName = ""
|
||||
// It firstly checks the passed mapping rules.
|
||||
if len(mapping) > 0 {
|
||||
if passedAttrKey, ok := mapping[mapK]; ok {
|
||||
if passedAttrKey, ok := mapping[paramName]; ok {
|
||||
attrName = passedAttrKey
|
||||
}
|
||||
}
|
||||
// It secondly checks the predefined tags and matching rules.
|
||||
if attrName == "" {
|
||||
checkName = utils.RemoveSymbols(mapK)
|
||||
// Loop to find the matched attribute name with or without
|
||||
// string cases and chars like '-'/'_'/'.'/' '.
|
||||
// It firstly considers `paramName` as accurate tag name,
|
||||
// and retrieve attribute name from `tagToAttrNameMap` .
|
||||
attrName = tagToAttrNameMap[paramName]
|
||||
if attrName == "" {
|
||||
checkName = utils.RemoveSymbols(paramName)
|
||||
// Loop to find the matched attribute name with or without
|
||||
// string cases and chars like '-'/'_'/'.'/' '.
|
||||
|
||||
// Matching the parameters to struct tag names.
|
||||
// The `attrKey` is the attribute name of the struct.
|
||||
for attrKey, cmpKey := range tagMap {
|
||||
if strings.EqualFold(checkName, cmpKey) {
|
||||
attrName = attrKey
|
||||
break
|
||||
// Matching the parameters to struct tag names.
|
||||
// The `attrKey` is the attribute name of the struct.
|
||||
for attrKey, cmpKey := range attrToTagCheckNameMap {
|
||||
if strings.EqualFold(checkName, cmpKey) {
|
||||
attrName = attrKey
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Matching the parameters to struct attributes.
|
||||
if attrName == "" {
|
||||
for attrKey, cmpKey := range attrMap {
|
||||
for attrKey, cmpKey := range attrToCheckNameMap {
|
||||
// Eg:
|
||||
// UserName eq user_name
|
||||
// User-Name eq username
|
||||
@ -314,7 +313,7 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
|
||||
}
|
||||
// Mark it done.
|
||||
doneMap[attrName] = struct{}{}
|
||||
if err = bindVarToStructAttr(pointerElemReflectValue, attrName, mapV, mapping); err != nil {
|
||||
if err = bindVarToStructAttr(pointerElemReflectValue, attrName, paramValue, mapping); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -322,8 +321,8 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
|
||||
}
|
||||
|
||||
// bindVarToStructAttr sets value to struct object attribute by name.
|
||||
func bindVarToStructAttr(elem reflect.Value, name string, value interface{}, mapping map[string]string) (err error) {
|
||||
structFieldValue := elem.FieldByName(name)
|
||||
func bindVarToStructAttr(structReflectValue reflect.Value, attrName string, value interface{}, mapping map[string]string) (err error) {
|
||||
structFieldValue := structReflectValue.FieldByName(attrName)
|
||||
if !structFieldValue.IsValid() {
|
||||
return nil
|
||||
}
|
||||
@ -334,7 +333,7 @@ func bindVarToStructAttr(elem reflect.Value, name string, value interface{}, map
|
||||
defer func() {
|
||||
if exception := recover(); exception != nil {
|
||||
if err = bindVarToReflectValue(structFieldValue, value, mapping); err != nil {
|
||||
err = gerror.Wrapf(err, `error binding value to attribute "%s"`, name)
|
||||
err = gerror.Wrapf(err, `error binding value to attribute "%s"`, attrName)
|
||||
}
|
||||
}
|
||||
}()
|
||||
@ -348,13 +347,11 @@ func bindVarToStructAttr(elem reflect.Value, name string, value interface{}, map
|
||||
return err
|
||||
}
|
||||
// Default converting.
|
||||
structFieldValue.Set(reflect.ValueOf(doConvert(
|
||||
doConvertInput{
|
||||
FromValue: value,
|
||||
ToTypeName: structFieldValue.Type().String(),
|
||||
ReferValue: structFieldValue,
|
||||
},
|
||||
)))
|
||||
doConvertWithReflectValueSet(structFieldValue, doConvertInput{
|
||||
FromValue: value,
|
||||
ToTypeName: structFieldValue.Type().String(),
|
||||
ReferValue: structFieldValue,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -431,7 +428,7 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, ma
|
||||
}
|
||||
|
||||
kind := structFieldValue.Kind()
|
||||
// Converting using interface, for some kinds.
|
||||
// Converting using `Set` interface implements, for some types.
|
||||
switch kind {
|
||||
case reflect.Slice, reflect.Array, reflect.Ptr, reflect.Interface:
|
||||
if !structFieldValue.IsNil() {
|
||||
@ -442,7 +439,7 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, ma
|
||||
}
|
||||
}
|
||||
|
||||
// Converting by kind.
|
||||
// Converting using reflection by kind.
|
||||
switch kind {
|
||||
case reflect.Map:
|
||||
return doMapToMap(value, structFieldValue, mapping)
|
||||
@ -487,11 +484,11 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, ma
|
||||
}
|
||||
}
|
||||
if !converted {
|
||||
elem.Set(reflect.ValueOf(doConvert(doConvertInput{
|
||||
doConvertWithReflectValueSet(elem, doConvertInput{
|
||||
FromValue: reflectValue.Index(i).Interface(),
|
||||
ToTypeName: elemTypeName,
|
||||
ReferValue: elem,
|
||||
})))
|
||||
})
|
||||
}
|
||||
if elemType.Kind() == reflect.Ptr {
|
||||
// Before it sets the `elem` to array, do pointer converting if necessary.
|
||||
@ -522,11 +519,11 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, ma
|
||||
}
|
||||
}
|
||||
if !converted {
|
||||
elem.Set(reflect.ValueOf(doConvert(doConvertInput{
|
||||
doConvertWithReflectValueSet(elem, doConvertInput{
|
||||
FromValue: value,
|
||||
ToTypeName: elemTypeName,
|
||||
ReferValue: elem,
|
||||
})))
|
||||
})
|
||||
}
|
||||
if elemType.Kind() == reflect.Ptr {
|
||||
// Before it sets the `elem` to array, do pointer converting if necessary.
|
||||
|
||||
@ -1270,8 +1270,8 @@ func Test_Struct_Issue1563(t *testing.T) {
|
||||
user := new(User)
|
||||
params2 := g.Map{
|
||||
"password1": "111",
|
||||
"PASS1": "222",
|
||||
"Pass1": "333",
|
||||
//"PASS1": "222",
|
||||
"Pass1": "333",
|
||||
}
|
||||
if err := gconv.Struct(params2, user); err == nil {
|
||||
t.Assert(user.Pass1, `111`)
|
||||
|
||||
@ -11,8 +11,9 @@
|
||||
package gtag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -21,20 +22,30 @@ var (
|
||||
)
|
||||
|
||||
// Set sets tag content for specified name.
|
||||
// Note that it panics if `name` already exists.
|
||||
func Set(name, value string) {
|
||||
if _, ok := data[name]; ok {
|
||||
panic(fmt.Sprintf(`value for tag "%s" already exists`, name))
|
||||
panic(gerror.Newf(`value for tag name "%s" already exists`, name))
|
||||
}
|
||||
data[name] = value
|
||||
}
|
||||
|
||||
// SetOver performs as Set, but it overwrites the old value if `name` already exists.
|
||||
func SetOver(name, value string) {
|
||||
data[name] = value
|
||||
}
|
||||
|
||||
// Sets sets multiple tag content by map.
|
||||
func Sets(m map[string]string) {
|
||||
for k, v := range m {
|
||||
if _, ok := data[k]; ok {
|
||||
panic(fmt.Sprintf(`value for tag "%s" already exists`, k))
|
||||
}
|
||||
data[k] = v
|
||||
Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// SetsOver performs as Sets, but it overwrites the old value if `name` already exists.
|
||||
func SetsOver(m map[string]string) {
|
||||
for k, v := range m {
|
||||
SetOver(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,8 +57,8 @@ func Get(name string) string {
|
||||
// Parse parses and returns the content by replacing all tag name variable to
|
||||
// its content for given `content`.
|
||||
// Eg:
|
||||
// If "Demo:content" in tag mapping,
|
||||
// Parse(`This is {Demo}`) -> `This is content`.
|
||||
// gtag.Set("demo", "content")
|
||||
// Parse(`This is {demo}`) -> `This is content`.
|
||||
func Parse(content string) string {
|
||||
return regex.ReplaceAllStringFunc(content, func(s string) string {
|
||||
if v, ok := data[s[1:len(s)-1]]; ok {
|
||||
|
||||
@ -11,26 +11,56 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/util/gtag"
|
||||
"github.com/gogf/gf/v2/util/guid"
|
||||
)
|
||||
|
||||
func Test_Set_Get(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
k := gtime.TimestampNanoStr()
|
||||
v := gtime.TimestampNanoStr()
|
||||
k := guid.S()
|
||||
v := guid.S()
|
||||
gtag.Set(k, v)
|
||||
t.Assert(gtag.Get(k), v)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_SetOver_Get(t *testing.T) {
|
||||
// panic by Set
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
k = guid.S()
|
||||
v1 = guid.S()
|
||||
v2 = guid.S()
|
||||
)
|
||||
gtag.Set(k, v1)
|
||||
t.Assert(gtag.Get(k), v1)
|
||||
defer func() {
|
||||
t.AssertNE(recover(), nil)
|
||||
}()
|
||||
gtag.Set(k, v2)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
k = guid.S()
|
||||
v1 = guid.S()
|
||||
v2 = guid.S()
|
||||
)
|
||||
gtag.SetOver(k, v1)
|
||||
t.Assert(gtag.Get(k), v1)
|
||||
gtag.SetOver(k, v2)
|
||||
t.Assert(gtag.Get(k), v2)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Sets_Get(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
k1 := gtime.TimestampNanoStr()
|
||||
k2 := gtime.TimestampNanoStr()
|
||||
v1 := gtime.TimestampNanoStr()
|
||||
v2 := gtime.TimestampNanoStr()
|
||||
var (
|
||||
k1 = guid.S()
|
||||
k2 = guid.S()
|
||||
v1 = guid.S()
|
||||
v2 = guid.S()
|
||||
)
|
||||
gtag.Sets(g.MapStrStr{
|
||||
k1: v1,
|
||||
k2: v2,
|
||||
@ -40,13 +70,60 @@ func Test_Sets_Get(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_SetsOver_Get(t *testing.T) {
|
||||
// panic by Sets
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
k1 = guid.S()
|
||||
k2 = guid.S()
|
||||
v1 = guid.S()
|
||||
v2 = guid.S()
|
||||
v3 = guid.S()
|
||||
)
|
||||
gtag.Sets(g.MapStrStr{
|
||||
k1: v1,
|
||||
k2: v2,
|
||||
})
|
||||
t.Assert(gtag.Get(k1), v1)
|
||||
t.Assert(gtag.Get(k2), v2)
|
||||
defer func() {
|
||||
t.AssertNE(recover(), nil)
|
||||
}()
|
||||
gtag.Sets(g.MapStrStr{
|
||||
k1: v3,
|
||||
k2: v3,
|
||||
})
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
k1 = guid.S()
|
||||
k2 = guid.S()
|
||||
v1 = guid.S()
|
||||
v2 = guid.S()
|
||||
v3 = guid.S()
|
||||
)
|
||||
gtag.SetsOver(g.MapStrStr{
|
||||
k1: v1,
|
||||
k2: v2,
|
||||
})
|
||||
t.Assert(gtag.Get(k1), v1)
|
||||
t.Assert(gtag.Get(k2), v2)
|
||||
gtag.SetsOver(g.MapStrStr{
|
||||
k1: v3,
|
||||
k2: v3,
|
||||
})
|
||||
t.Assert(gtag.Get(k1), v3)
|
||||
t.Assert(gtag.Get(k2), v3)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Parse(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
k1 = gtime.TimestampNanoStr()
|
||||
k2 = gtime.TimestampNanoStr()
|
||||
v1 = gtime.TimestampNanoStr()
|
||||
v2 = gtime.TimestampNanoStr()
|
||||
k1 = guid.S()
|
||||
k2 = guid.S()
|
||||
v1 = guid.S()
|
||||
v2 = guid.S()
|
||||
content = fmt.Sprintf(`this is {%s} and {%s}`, k1, k2)
|
||||
expect = fmt.Sprintf(`this is %s and %s`, v1, v2)
|
||||
)
|
||||
|
||||
@ -13,6 +13,8 @@ import (
|
||||
|
||||
"github.com/gogf/gf/v2/container/gtype"
|
||||
"github.com/gogf/gf/v2/encoding/ghash"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/net/gipv4"
|
||||
"github.com/gogf/gf/v2/util/grand"
|
||||
)
|
||||
@ -67,7 +69,7 @@ func init() {
|
||||
// Note that:
|
||||
// 1. The returned length is fixed to 32 bytes for performance purpose.
|
||||
// 2. The custom parameter `data` composed should have unique attribute in your
|
||||
// business situation.
|
||||
// business scenario.
|
||||
func S(data ...[]byte) string {
|
||||
var (
|
||||
b = make([]byte, 32)
|
||||
@ -92,7 +94,10 @@ func S(data ...[]byte) string {
|
||||
copy(b[n+12:], getSequence())
|
||||
copy(b[n+12+3:], getRandomStr(32-n-12-3))
|
||||
} else {
|
||||
panic("too many data parts, it should be no more than 2 parts")
|
||||
panic(gerror.NewCode(
|
||||
gcode.CodeInvalidParameter,
|
||||
"too many data parts, it should be no more than 2 parts",
|
||||
))
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
@ -35,14 +35,16 @@ type iMarshalJSON interface {
|
||||
|
||||
// DumpOption specifies the behavior of function Export.
|
||||
type DumpOption struct {
|
||||
WithType bool // WithType specifies dumping content with type information.
|
||||
WithType bool // WithType specifies dumping content with type information.
|
||||
ExportedOnly bool // Only dump Exported fields for structs.
|
||||
}
|
||||
|
||||
// Dump prints variables `values` to stdout with more manually readable.
|
||||
func Dump(values ...interface{}) {
|
||||
for _, value := range values {
|
||||
DumpWithOption(value, DumpOption{
|
||||
WithType: false,
|
||||
WithType: false,
|
||||
ExportedOnly: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -52,7 +54,8 @@ func Dump(values ...interface{}) {
|
||||
func DumpWithType(values ...interface{}) {
|
||||
for _, value := range values {
|
||||
DumpWithOption(value, DumpOption{
|
||||
WithType: true,
|
||||
WithType: true,
|
||||
ExportedOnly: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -61,7 +64,8 @@ func DumpWithType(values ...interface{}) {
|
||||
func DumpWithOption(value interface{}, option DumpOption) {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
DumpTo(buffer, value, DumpOption{
|
||||
WithType: option.WithType,
|
||||
WithType: option.WithType,
|
||||
ExportedOnly: option.ExportedOnly,
|
||||
})
|
||||
fmt.Println(buffer.String())
|
||||
}
|
||||
@ -70,13 +74,15 @@ func DumpWithOption(value interface{}, option DumpOption) {
|
||||
func DumpTo(writer io.Writer, value interface{}, option DumpOption) {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
doDump(value, "", buffer, doDumpOption{
|
||||
WithType: option.WithType,
|
||||
WithType: option.WithType,
|
||||
ExportedOnly: option.ExportedOnly,
|
||||
})
|
||||
_, _ = writer.Write(buffer.Bytes())
|
||||
}
|
||||
|
||||
type doDumpOption struct {
|
||||
WithType bool
|
||||
WithType bool
|
||||
ExportedOnly bool
|
||||
}
|
||||
|
||||
func doDump(value interface{}, indent string, buffer *bytes.Buffer, option doDumpOption) {
|
||||
@ -124,6 +130,7 @@ func doDump(value interface{}, indent string, buffer *bytes.Buffer, option doDum
|
||||
Option: option,
|
||||
ReflectValue: reflectValue,
|
||||
ReflectTypeName: reflectTypeName,
|
||||
ExportedOnly: option.ExportedOnly,
|
||||
}
|
||||
)
|
||||
switch reflectKind {
|
||||
@ -160,13 +167,13 @@ func doDump(value interface{}, indent string, buffer *bytes.Buffer, option doDum
|
||||
doDumpNumber(exportInternalInput)
|
||||
|
||||
case reflect.Chan:
|
||||
buffer.WriteString(fmt.Sprintf(`<%s>`, reflectTypeName))
|
||||
buffer.WriteString(fmt.Sprintf(`<%s>`, reflectValue.Type().String()))
|
||||
|
||||
case reflect.Func:
|
||||
if reflectValue.IsNil() || !reflectValue.IsValid() {
|
||||
buffer.WriteString(`<nil>`)
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf(`<%s>`, reflectTypeName))
|
||||
buffer.WriteString(fmt.Sprintf(`<%s>`, reflectValue.Type().String()))
|
||||
}
|
||||
|
||||
case reflect.Interface:
|
||||
@ -185,6 +192,7 @@ type doDumpInternalInput struct {
|
||||
Option doDumpOption
|
||||
ReflectValue reflect.Value
|
||||
ReflectTypeName string
|
||||
ExportedOnly bool
|
||||
}
|
||||
|
||||
func doDumpSlice(in doDumpInternalInput) {
|
||||
@ -223,9 +231,14 @@ func doDumpSlice(in doDumpInternalInput) {
|
||||
}
|
||||
|
||||
func doDumpMap(in doDumpInternalInput) {
|
||||
var (
|
||||
mapKeys = in.ReflectValue.MapKeys()
|
||||
)
|
||||
var mapKeys = make([]reflect.Value, 0)
|
||||
for _, key := range in.ReflectValue.MapKeys() {
|
||||
if !key.CanInterface() {
|
||||
continue
|
||||
}
|
||||
mapKey := key
|
||||
mapKeys = append(mapKeys, mapKey)
|
||||
}
|
||||
if len(mapKeys) == 0 {
|
||||
if !in.Option.WithType {
|
||||
in.Buffer.WriteString("{}")
|
||||
@ -339,6 +352,9 @@ dumpStructFields:
|
||||
tmpSpaceNum = 0
|
||||
)
|
||||
for _, field := range structFields {
|
||||
if in.ExportedOnly && !field.IsExported() {
|
||||
continue
|
||||
}
|
||||
tmpSpaceNum = len(field.Name())
|
||||
if tmpSpaceNum > maxSpaceNum {
|
||||
maxSpaceNum = tmpSpaceNum
|
||||
@ -350,6 +366,9 @@ dumpStructFields:
|
||||
in.Buffer.WriteString(fmt.Sprintf("%s(%d) {\n", in.ReflectTypeName, len(structFields)))
|
||||
}
|
||||
for _, field := range structFields {
|
||||
if in.ExportedOnly && !field.IsExported() {
|
||||
continue
|
||||
}
|
||||
tmpSpaceNum = len(fmt.Sprintf(`%v`, field.Name()))
|
||||
in.Buffer.WriteString(fmt.Sprintf(
|
||||
"%s%s:%s",
|
||||
|
||||
@ -276,6 +276,30 @@ func Test_CheckStruct_Recursively_SliceAttribute(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CheckStruct_Recursively_SliceAttribute_WithTypeAlias(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type ParamsItemBase struct {
|
||||
Component string `v:"required" dc:"组件名称"`
|
||||
Params string `v:"required" dc:"配置参数(一般是JSON)"`
|
||||
Version uint64 `v:"required" dc:"参数版本"`
|
||||
}
|
||||
type ParamsItem = ParamsItemBase
|
||||
type ParamsModifyReq struct {
|
||||
Revision uint64 `v:"required"`
|
||||
BizParams []ParamsItem `v:"required"`
|
||||
}
|
||||
var (
|
||||
req = ParamsModifyReq{}
|
||||
data = g.Map{
|
||||
"Revision": "1",
|
||||
"BizParams": `[{}]`,
|
||||
}
|
||||
)
|
||||
err := g.Validator().Assoc(data).Data(req).Run(ctx)
|
||||
t.Assert(err, `The Component field is required; The Params field is required; The Version field is required`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CheckStruct_Recursively_MapAttribute(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Student struct {
|
||||
|
||||
Reference in New Issue
Block a user