Compare commits

...

68 Commits

Author SHA1 Message Date
bb3ca6d414 improve clickhouse driver 2022-04-19 14:21:03 +08:00
674eada9ed improve clickhouse driver 2022-04-19 14:20:00 +08:00
310baaf67d improve clickhouse driver 2022-04-19 14:18:25 +08:00
c9971b21ec improve clickhouse driver 2022-04-19 14:15:53 +08:00
be77779aff Merge branch 'master' of https://github.com/gogf/gf into develop 2022-04-18 20:29:35 +08:00
e119f2a534 improve cache handlement for package gdb 2022-04-18 20:29:24 +08:00
a09c8497bc Merge branch 'develop' of https://github.com/gogf/gf into develop 2022-04-18 20:28:14 +08:00
ebad3eb93e error meesage update for package gdb; remove default batch number for batch insert statement for package gdb 2022-04-18 20:28:00 +08:00
e4e4534c7c Merge pull request #1759 from qinyuguang/gdb_cache
fix issue #1755
2022-04-18 20:22:38 +08:00
b412fc6516 Merge pull request #1749 from qinyuguang/gjson_unmarshalvalue
fix issue #1747
2022-04-18 20:19:07 +08:00
f9c9750108 improve gutil.Dump 2022-04-15 18:00:16 +08:00
5dee3bb4d9 add auto creating tags github workflow; go.mod update for crontrib packages 2022-04-15 14:47:02 +08:00
1e3d8cdadd fix issue #1721 2022-04-13 21:58:35 +08:00
c5bf45f1ae fix issue #1755 2022-04-13 21:49:08 +08:00
bf674060c0 add internal package consts to manage shared constants; improve buildin function dump only available in develop mode for package gview 2022-04-13 21:08:12 +08:00
878dbe4ab9 fix issue #1740 2022-04-13 20:42:39 +08:00
d8b383719a improve package gtag 2022-04-13 11:21:24 +08:00
9ff5f39701 fix issue #1747 2022-04-12 23:30:18 +08:00
5144cc0e08 Merge pull request #1735 from yuancjun/patch-1
avoid a single space at the end of a line.
2022-04-12 21:36:44 +08:00
ee29b28575 improve clickhouse driver 2022-04-12 21:31:51 +08:00
7785082f19 Merge branch 'master' of https://github.com/gogf/gf into develop 2022-04-12 21:17:32 +08:00
edf40ba430 Merge pull request #1616 from DGuang21/feature-clickhouse-driver
Feature - clickhouse driver
2022-04-12 21:14:12 +08:00
a228495ced improve error message for package ghttp 2022-04-12 16:15:54 +08:00
ed9dc70769 add UT case for package gvalid 2022-04-12 16:09:24 +08:00
e8581d4fd5 add Is/Equal/Unwrap functions for package gerror 2022-04-12 15:45:26 +08:00
2d6fcf5d06 fix issue #1708 2022-04-12 12:09:09 +08:00
55e0262c37 improve package gconv 2022-04-11 21:54:23 +08:00
d5e5a48170 fix issue #1747 2022-04-11 20:49:33 +08:00
d0f2928cec fix issue of nil pointer in package internal/utils 2022-04-11 20:43:32 +08:00
190a53647e fix issue #1701 2022-04-11 20:38:48 +08:00
f9a3fa3c23 fix issue #1700 2022-04-11 17:58:07 +08:00
f1fee72d6d fix issue #1700 2022-04-08 18:11:17 +08:00
0b4ae6b116 add UpdateAndGetAffected for gdb.Model 2022-04-08 17:22:07 +08:00
a1ec7cb896 improve clickhouse driver 2022-04-08 10:08:04 +08:00
1935412db9 improve clickhouse driver 2022-04-08 10:07:14 +08:00
c90a9d45ee improve clickhouse driver 2022-04-08 09:44:42 +08:00
a594592151 Merge branch 'feature-clickhouse-driver' of https://github.com/DGuang21/gf into feature-clickhouse-driver 2022-04-08 09:43:09 +08:00
119d8bf98c improve command gf run 2022-04-07 22:07:47 +08:00
1e141d9f64 improve package gjson/ghttp 2022-04-07 21:26:39 +08:00
587af6dec8 add sqlite support for cli tool 2022-04-07 20:29:15 +08:00
793e862e5a Merge pull request #1730 from xiaoping378/patch-2
Update README.MD
2022-04-07 20:16:29 +08:00
09c3425dd3 Merge pull request #1683 from Macrow/master
feat: support custom listener
2022-04-07 20:05:16 +08:00
4ca168412b avoid a single space at the end of a line. 2022-04-06 11:57:22 +08:00
66f24db6da Update README.MD 2022-04-04 21:48:36 +08:00
c39a58f812 improve clickhouse driver 2022-04-04 14:56:44 +08:00
5034f231a9 improve clickhouse driver 2022-04-04 12:46:11 +08:00
64afd5f64c Merge branch 'master' of https://github.com/DGuang21/gf into feature-clickhouse-driver 2022-04-02 18:35:12 +08:00
0e0d2e1c45 fix: break when finished set custom listener in newGracefulServer method 2022-04-01 09:03:36 +08:00
52d8371ba9 add UT case for package gdb 2022-03-31 21:40:28 +08:00
1d74b58d36 Merge branch 'master' of https://github.com/DGuang21/gf into feature-clickhouse-driver 2022-03-31 21:29:17 +08:00
c7f51b8e77 fix: SetListener test data race error 2022-03-30 14:55:03 +08:00
b57cbacc82 refactor: method SetListener accepts slice of net.Listener and remove method SetListeners 2022-03-29 23:22:23 +08:00
325887fa18 fix: SetListener overwrite default address 2022-03-22 21:35:53 +08:00
73ca527b0a feat: add SetListener and throw error in SetListeners 2022-03-22 13:34:15 +08:00
147348e0d1 refactor: remove unnecessary code in method getListenAddress of Server 2022-03-20 10:17:44 +08:00
ad202ea735 refactor: adjust method SetListeners of server and add unit test 2022-03-20 02:31:21 +08:00
d7bd1b74e8 feat: support custom listener 2022-03-18 20:54:32 +08:00
5e3c0bd9aa improve clickhouse driver 2022-02-24 21:06:26 +08:00
a6bbb8424c improve clickhouse driver 2022-02-24 13:50:28 +08:00
00daeb318c improve clickhouse driver 2022-02-24 13:03:01 +08:00
65341141fe improve clickhouse driver 2022-02-24 12:58:57 +08:00
fe353c5fe3 Merge branch 'gogf:master' into feature-clickhouse-driver 2022-02-24 10:12:05 +08:00
008e5ea196 improve clickhouse driver 2022-02-23 22:53:28 +08:00
157e936f24 improve clickhouse driver 2022-02-23 22:51:37 +08:00
455d724c01 improve clickhouse driver 2022-02-19 23:10:31 +08:00
ea60f7e054 improve clickhouse driver 2022-02-19 17:49:53 +08:00
daf2b649ef improve clickhouse driver 2022-02-19 16:59:17 +08:00
aa87d234e3 improve clickhouse driver 2022-02-19 15:09:44 +08:00
70 changed files with 2459 additions and 664 deletions

View File

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

View File

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

View File

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

View File

@ -22,6 +22,7 @@ github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Px
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-test/deep v1.0.7 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=

View File

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

View File

@ -21,8 +21,8 @@ import (
_ "github.com/gogf/gf/contrib/drivers/mssql/v2"
_ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
// _ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
// _ "github.com/gogf/gf/contrib/drivers/oracle/v2"
_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
//_ "github.com/gogf/gf/contrib/drivers/oracle/v2"
)
const (

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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