Compare commits

..

147 Commits

Author SHA1 Message Date
e9ea58df64 Merge branch 'master' of https://github.com/gogf/gf 2021-08-04 13:29:17 +08:00
65fff6feae version updates 2021-08-04 13:29:06 +08:00
dbded5e753 Merge pull request #1365 from stardemo/master
Update Readme ,Github Action badge
2021-08-04 13:15:02 +08:00
3043645605 Update Readme ,Github Action badge 2021-08-03 23:29:34 +08:00
685bf56a30 fix issue #1325 2021-08-03 22:21:20 +08:00
a4497ed547 improve gdb.Modle for where holder 2021-08-03 21:32:05 +08:00
28cb0bef25 change logger of DB from glog.Logger to interface Logger 2021-08-03 20:34:26 +08:00
114cdb2351 improve package gtimer 2021-08-03 19:37:25 +08:00
a2bb8ad2f2 add more unit testing case for package gdb 2021-08-02 21:13:00 +08:00
5a4de52900 fix invalid separator char in packing with prefix folder string in OS widnows for package gres 2021-08-02 19:58:04 +08:00
ff70e54e3e fix unit testing case for package gtrace 2021-08-02 09:50:44 +08:00
fddc21670a add condition and order-by feature for with tag feature for package gdb 2021-08-02 00:38:56 +08:00
f02372cf58 improve package gvalid 2021-08-01 23:50:44 +08:00
ab5f809074 go.sum updates 2021-08-01 22:13:20 +08:00
121c1a0125 Merge branch 'develop' of https://github.com/gogf/gf into develop 2021-08-01 22:13:00 +08:00
7678540270 add bail rule for package gvalid 2021-08-01 22:12:44 +08:00
839ebd5b51 Merge pull request #1360 from houseme/master
Update go.mod
2021-08-01 11:29:36 +08:00
fa64df6f91 improve package gdb/glog 2021-08-01 10:33:33 +08:00
0acd118c03 add ModelHandler feature for package gdb 2021-08-01 10:17:03 +08:00
2b5244a54b unit testing cases update for package gredis/glog 2021-08-01 09:45:46 +08:00
2472dd5fac github action updates 2021-08-01 09:33:12 +08:00
5899f676f7 Merge pull request #1347 from stardemo/develop
add basic action scripts
2021-08-01 09:17:31 +08:00
bb57dc1ae7 improve request context feature for package ghttp 2021-08-01 09:17:37 +08:00
5a6c2c27df inject Request object into context 2021-07-30 17:17:13 +08:00
9f096fc63d improve error code for package gerror 2021-07-30 16:21:45 +08:00
4267aadd78 remove controller feature from package ghttp 2021-07-30 15:15:44 +08:00
ef77a54c7e improve OmitEmpty feature for package gdb; mark package gmvc deprecated 2021-07-30 14:58:23 +08:00
afb0af4afd add example for package gvalid 2021-07-30 11:29:48 +08:00
15aabfb4e7 Update go.mod
update otel v1.0.0-RC1 to otel v1.0.0-RC2
2021-07-28 21:32:08 +08:00
c83e899f1f add go version matrix 2021-07-22 09:29:26 +08:00
ebe90dcaa8 fix issue in Cache Penetration for cache feature of package gdb 2021-07-21 19:24:16 +08:00
1c3ae11eba Update go.yml 2021-07-21 11:18:10 +08:00
b718aa88a2 add report && i386 test 2021-07-21 00:02:15 +08:00
6240c3d90b disabled error test case to test action 2021-07-20 23:35:07 +08:00
c78f9d19f5 Update go.yml 2021-07-20 23:26:53 +08:00
0ddacdd7e2 add error code for components 2021-07-20 23:02:02 +08:00
f8486474aa fix scripts path 2021-07-19 23:23:24 +08:00
906c54ce61 add basic action scripts 2021-07-19 23:20:51 +08:00
f72d991c36 rename attribute names from lower-camel case to upper-camel case 2021-07-19 20:06:44 +08:00
528f0e5434 Merge branch 'feature/new-register-type' into develop 2021-07-19 19:41:09 +08:00
88009ee278 fix issue #1341 2021-07-15 22:19:00 +08:00
b192b7dd60 remove internal logging for package gtimer 2021-07-15 21:46:56 +08:00
03d51bd18c add internal logging for package gtimer 2021-07-15 21:40:26 +08:00
5c6c932a75 merge master 2021-07-15 21:23:36 +08:00
92c3c136f9 improve color feature for package glog 2021-07-15 21:20:29 +08:00
5069436fd2 Merge pull request #1346 from goflyfox/dev
update pgsql pri error #1340
2021-07-15 21:18:52 +08:00
141ba2e951 update pgsql pri error #1340 2021-07-15 14:53:21 +08:00
fae4dea37a improve color feature for package glog 2021-07-15 13:31:32 +08:00
860b22aba4 Merge pull request #1312 from wangle201210/develop
add log level prefix color
2021-07-15 09:16:27 +08:00
30dbccf99e 输出对应err 2021-07-14 21:28:23 +08:00
9b2497bc57 Merge branch 'develop-up' into develop
* develop-up: (38 commits)
  revert gerror usage for package gvalid
  unify error package to gerror
  add CtxStrict feature for package gdb
  rename constants of package gpage from skake to upper camel case
  add context for intlog
  improve Record/Result converting for package gdb
  add context for intlog/gsession;improve struct/structs converting for package gconv
  unify command or enviroment key names for packages
  improve details for package glog
  add file configuration support for logger in ghttp.Server
  version updates
  add more unit testing cases for package ghttp
  add automatic fields mapping and filtering for Model.Where statements
  improve Order feature for package gdb
  improve function Increment/Decrement for package gdb
  add auto fields filtering feature for function Scan of package gdb; mark funtcion Struct/Structs deprecated for gdb.Model
  improve record converting for package gdb
  improve transaction feature for package gdb
  improve caller path filtering for package gdebug
  improve caller path filtering for package gdebug
  ...

# Conflicts:
#	os/glog/glog_logger.go
#	os/glog/glog_logger_config.go
#	os/glog/glog_logger_level.go
2021-07-14 21:12:18 +08:00
0140808460 add handler extension feature for package ghttp 2021-07-13 23:01:31 +08:00
84aa30d9c2 允许日志文件中添加颜色 2021-07-13 22:45:35 +08:00
bc724deb5e 日志内容区分文件和控制台输出 2021-07-13 21:43:07 +08:00
fbfc23211c fix issue in Counter of package gdb 2021-07-13 19:37:16 +08:00
3d4d3a763a comment update for package gfsnotify 2021-07-12 22:05:03 +08:00
046749566d update sql of unit testing case for package gdb 2021-07-08 23:06:58 +08:00
3b1b8a8306 Merge branch 'master' of https://github.com/gogf/gf 2021-07-08 22:44:29 +08:00
5e92747737 fix issue #1291 2021-07-08 22:44:16 +08:00
82ad7e2acc add more unit testing case for package gdb 2021-07-08 22:08:45 +08:00
2d319d0856 update sql of unit testing cases for package gdb 2021-07-08 21:46:21 +08:00
c060904f3f Merge pull request #1328 from lgyaxx/patch-1
Update gdb_model_fields.go
2021-07-08 21:18:42 +08:00
a63c4b6441 add more unit testing case for package gdb 2021-07-08 21:06:49 +08:00
9df860a202 fix issue #1292 2021-07-08 21:02:36 +08:00
2a1634fd6f Update gdb_model_fields.go
annotation typo fix
2021-07-08 15:42:51 +08:00
2970864158 fix issue #1240 2021-07-06 21:18:26 +08:00
8e76d7a8ed improve function TableFileds for gussing table name from table string;add overwrote function DoInsert for mssql/pgsql/oracle/sqlite for unsupported features 2021-07-06 20:59:09 +08:00
50e5dd5bd0 fix issue in fields filtering for table name with as statement 2021-07-06 13:14:33 +08:00
7e2605188d improve session variable map in template parsing 2021-07-06 09:58:25 +08:00
e5ae1cb85c improve session variable map in template parsing 2021-07-06 09:53:35 +08:00
1e78734f2c remove gf cli from basic framework 2021-06-30 20:43:49 +08:00
d5fad88c56 gf cli updates 2021-06-30 20:16:49 +08:00
35a81b868f gf cli updates 2021-06-30 20:08:25 +08:00
84355c1ddd gf cli updates 2021-06-30 20:07:46 +08:00
012121ea77 gf cli updates 2021-06-30 20:00:50 +08:00
1e628b9edb move command tool from repo gf-cli to sub folder 2021-06-30 00:15:52 +08:00
bfdeb6c4f5 log color兼容win环境 2021-06-29 21:05:46 +08:00
c42a9d6e50 Merge pull request #1316 from qinyuguang/otel
Otel
2021-06-29 13:05:38 +08:00
083e32fd9e otel version bump to v1.0.0-RC1 2021-06-28 13:36:37 +08:00
2a350fd3ab merge develop 2021-06-28 13:15:40 +08:00
03928f1977 add logging level prefix with color or not config 2021-06-28 00:58:35 +08:00
a2771c7558 format code 2021-06-28 00:23:25 +08:00
968e1db94d add log level prefix color 2021-06-28 00:00:44 +08:00
bb0a3e09d6 revert gerror usage for package gvalid 2021-06-26 18:44:59 +08:00
d109706ad3 unify error package to gerror 2021-06-26 18:34:26 +08:00
b958689264 add CtxStrict feature for package gdb 2021-06-26 18:20:55 +08:00
859ea150ed rename constants of package gpage from skake to upper camel case 2021-06-26 17:00:32 +08:00
91ca79b300 add context for intlog 2021-06-26 16:51:26 +08:00
8210f40469 improve Record/Result converting for package gdb 2021-06-26 16:46:36 +08:00
50ffaef33f add context for intlog/gsession;improve struct/structs converting for package gconv 2021-06-26 16:23:54 +08:00
c25f88293b unify command or enviroment key names for packages 2021-06-26 12:08:18 +08:00
025cdd66c5 improve details for package glog 2021-06-26 11:25:54 +08:00
237f172ae5 add file configuration support for logger in ghttp.Server 2021-06-26 11:18:44 +08:00
4cd7e4e5a0 version updates 2021-06-23 21:57:13 +08:00
e6688b9e86 add more unit testing cases for package ghttp 2021-06-23 21:39:12 +08:00
65131c6f22 add automatic fields mapping and filtering for Model.Where statements 2021-06-23 12:04:16 +08:00
7667aca4c2 improve Order feature for package gdb 2021-06-23 09:42:10 +08:00
816791b9c1 improve function Increment/Decrement for package gdb 2021-06-23 09:34:53 +08:00
d6ea2220f7 add auto fields filtering feature for function Scan of package gdb; mark funtcion Struct/Structs deprecated for gdb.Model 2021-06-22 21:48:56 +08:00
69dd5db774 improve record converting for package gdb 2021-06-22 20:05:37 +08:00
adca9222ab improve transaction feature for package gdb 2021-06-22 17:42:31 +08:00
7144aa6999 improve caller path filtering for package gdebug 2021-06-22 15:34:26 +08:00
fbad5f60eb improve caller path filtering for package gdebug 2021-06-22 15:09:08 +08:00
266f592739 improve raw sql count statement for package gdb 2021-06-21 19:21:38 +08:00
7c8bbcb3af version update 2021-06-21 09:38:19 +08:00
ba18e2bf6b comment update 2021-06-21 09:37:12 +08:00
5fefe97b87 add Raw Model for package gdb 2021-06-18 15:27:49 +08:00
5fba250a14 add Raw Model for package gdb 2021-06-18 15:20:27 +08:00
2606ad83ac add OnDuplicate/OnDuplicateEx feature for package gdb 2021-06-16 21:51:44 +08:00
d450de8e0d add OnDuplicate/OnDuplicateEx feature for package gdb 2021-06-16 21:44:31 +08:00
e4b0de0d4f Merge branch 'master' of https://github.com/gogf/gf 2021-06-15 19:58:10 +08:00
2af4fd86cc rename configuration node name from LinkInfo to Link 2021-06-15 19:57:55 +08:00
ddd171bc18 Merge pull request #1284 from qinyuguang/gutil
add gutil.SliceToMapWithColumnAsKey
2021-06-11 09:52:30 +08:00
5ef4ef61f0 Merge pull request #1280 from imloama/develop 2021-06-11 09:50:17 +08:00
2679f92aa8 comment update for package gerror 2021-06-10 20:45:22 +08:00
cca438d77f fix issue #1209 2021-06-10 20:17:53 +08:00
f2bc29e5c1 add gutil.SliceToMapWithColumnAsKey 2021-06-09 18:49:49 +08:00
fe7209e76d rename HandleSqlBeforeCommit to DoCommit for package gdb 2021-06-08 21:55:55 +08:00
7c4a0453b7 improve handler feature for package glog 2021-06-08 21:35:54 +08:00
97879834bc remove deprecated functions for package gdb 2021-06-08 21:28:41 +08:00
332535901f Merge branch 'master' of https://github.com/gogf/gf 2021-06-08 20:32:52 +08:00
e68e7a3224 remove Batch*/DoBatchInsert functions for package gdb 2021-06-08 20:32:34 +08:00
b935a8c652 fix merge conflicts for gdb_driver_pgsql 2021-06-08 09:20:22 +08:00
65befd5ac4 Merge pull request #1276 from qinyuguang/gdb_config_timezone
add timezone configuration for package gdb
2021-06-07 20:01:06 +08:00
78bdb5ef71 Merge pull request #1277 from weicut/master
Fixed incorrect type conversion
2021-06-07 19:58:27 +08:00
6eb7261dfd add timezone configuration for package gdb, effective for mysql and pgsql 2021-06-07 13:45:40 +08:00
4f82be5bc0 Fixed incorrect type conversion 2021-06-07 10:17:23 +08:00
3ac5772059 add UNION/UNION ALL feature for package gdb 2021-06-06 23:06:39 +08:00
eb723e47c2 fix issue in gconv.MapDeep 2021-06-05 08:58:54 +08:00
8aa7f08350 improve DB interface for package gdb 2021-06-04 09:54:19 +08:00
a54559d016 add WhereLT/WhereLTE/WhereGT/WhereGTE/WhereOrLT/WhereOrLTE/WhereOrGT/WhereOrGTE functions for gdb.Model 2021-06-04 09:27:41 +08:00
8e1f6abac5 change github.com/go-sql-driver/mysql to github.com/gogf/mysql 2021-06-03 15:38:33 +08:00
7f3a2207a3 Merge branch 'master' of https://github.com/gogf/gf 2021-06-02 21:12:52 +08:00
742c7913ea fix issue in function Parse for package ghttp 2021-06-02 21:12:27 +08:00
2d8ab726e2 Merge pull request #1265 from weicut/master 2021-06-02 13:11:59 +08:00
702a296258 improve handler feature for package glog 2021-06-02 09:53:08 +08:00
c3c5414ce2 add custom handler feature 2021-06-02 09:42:27 +08:00
ee3d375532 补充浮点数单元测试 2021-06-02 09:18:52 +08:00
e3f5c9175c 补充浮点数单元测试 2021-06-02 09:17:50 +08:00
7c24449a24 Merge branch 'master' of github.com:gogf/gf 2021-06-02 09:08:45 +08:00
1c09846d3e fix issue #1272 2021-06-01 20:09:52 +08:00
db94346863 gitee/github template update 2021-06-01 19:59:57 +08:00
3e6b9864d5 fix issue #1256 2021-06-01 19:47:02 +08:00
392c81ad69 remove Link parameter from function TableFields for package gdb 2021-05-29 17:26:08 +08:00
7b32791006 修复浮点型排序
原因:
返回值,强制转成 int 类型,会导致浮点型比较不准确,例如:0.33,转成 int 类型时,会变成 0
2021-05-25 16:15:02 +08:00
aa04948319 修复浮点型排序
原因:
返回值,强制转成 int 类型,会导致浮点型比较不准确,例如:0.33,转成 int 类型时,会变成 0
2021-05-25 16:10:52 +08:00
max
f3983cd6b7 feat: pg查询表字段时获取字段描述信息 2021-04-28 14:51:40 +08:00
251 changed files with 6360 additions and 4259 deletions

View File

@ -1,12 +1,7 @@
// 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 driver
import (
"context"
"database/sql"
"github.com/gogf/gf/database/gdb"
"github.com/gogf/gf/os/gtime"
@ -47,9 +42,9 @@ func (d *MyDriver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) {
// DoQuery commits the sql string and its arguments to underlying driver
// through given link object and returns the execution result.
func (d *MyDriver) DoQuery(link gdb.Link, sql string, args ...interface{}) (rows *sql.Rows, err error) {
func (d *MyDriver) DoQuery(ctx context.Context, link gdb.Link, sql string, args ...interface{}) (rows *sql.Rows, err error) {
tsMilli := gtime.TimestampMilli()
rows, err = d.DriverMysql.DoQuery(link, sql, args...)
rows, err = d.DriverMysql.DoQuery(ctx, link, sql, args...)
link.Exec(
"INSERT INTO `monitor`(`sql`,`cost`,`time`,`error`) VALUES(?,?,?,?)",
gdb.FormatSqlWithArgs(sql, args),
@ -62,9 +57,9 @@ func (d *MyDriver) DoQuery(link gdb.Link, sql string, args ...interface{}) (rows
// DoExec commits the query string and its arguments to underlying driver
// through given link object and returns the execution result.
func (d *MyDriver) DoExec(link gdb.Link, sql string, args ...interface{}) (result sql.Result, err error) {
func (d *MyDriver) DoExec(ctx context.Context, link gdb.Link, sql string, args ...interface{}) (result sql.Result, err error) {
tsMilli := gtime.TimestampMilli()
result, err = d.DriverMysql.DoExec(link, sql, args...)
result, err = d.DriverMysql.DoExec(ctx, link, sql, args...)
link.Exec(
"INSERT INTO `monitor`(`sql`,`cost`,`time`,`error`) VALUES(?,?,?,?)",
gdb.FormatSqlWithArgs(sql, args),

View File

@ -10,9 +10,9 @@ func main() {
//db := g.DB()
gdb.AddDefaultConfigNode(gdb.ConfigNode{
LinkInfo: "root:12345678@tcp(127.0.0.1:3306)/test?parseTime=true&loc=Local",
Type: "mysql",
Charset: "utf8",
Link: "root:12345678@tcp(127.0.0.1:3306)/test?parseTime=true&loc=Local",
Type: "mysql",
Charset: "utf8",
})
db, _ := gdb.New()

View File

@ -0,0 +1,15 @@
package main
import (
"github.com/gogf/gf/frame/g"
)
func main() {
g.Log().Print("Print")
g.Log().Debug("Debug")
g.Log().Info("Info")
g.Log().Notice("Notice")
g.Log().Warning("Warning")
g.Log().Error("Error")
g.Log().Critical("Critical")
}

View File

@ -0,0 +1,35 @@
package main
import (
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/i18n/gi18n"
)
func main() {
type User struct {
Name string `v:"required#ReuiredUserName"`
Type int `v:"required#ReuiredUserType"`
Project string `v:"size:10#MustSize"`
}
s := g.Server()
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(func(r *ghttp.Request) {
lang := r.GetString("lang", "zh-CN")
r.SetCtx(gi18n.WithLanguage(r.Context(), lang))
r.Middleware.Next()
})
group.GET("/validate", func(r *ghttp.Request) {
var (
err error
user = User{}
)
if err = r.Parse(&user); err != nil {
r.Response.WriteExit(err)
}
r.Response.WriteExit(user)
})
})
s.SetPort(8199)
}

View File

@ -1,4 +1,7 @@
<!-- 为高效率地交流并解决问题请按照以下模板提交issue感谢 -->
<!-- 为高效处理您的疑问如果觉得是BUG类问题请您务必提供可复现该问题的最小可运行代码 -->
<!-- 为高效处理您的疑问如果觉得是BUG类问题请您务必提供可复现该问题的最小可运行代码 -->
<!-- 为高效处理您的疑问如果觉得是BUG类问题请您务必提供可复现该问题的最小可运行代码 -->
<!-- 重要的事情说三遍! -->
### 1. 您当前使用的`Go`版本,及系统版本、系统架构?

View File

@ -1,5 +1,10 @@
<!-- Please answer these questions before submitting your issue. Thanks! -->
<!-- 为高效处理您的疑问如果觉得是BUG类问题请您务必提供可复现该问题的最小可运行代码 -->
<!-- 为高效处理您的疑问如果觉得是BUG类问题请您务必提供可复现该问题的最小可运行代码 -->
<!-- 为高效处理您的疑问如果觉得是BUG类问题请您务必提供可复现该问题的最小可运行代码 -->
<!-- 重要的事情说三遍! -->
### 1. What version of `Go` and system type/arch are you using?
<!--
@ -13,7 +18,7 @@ What expect to see is like: `go 1.12, linux/amd64`
<!-- You can find the GF version from your `go.mod`, or from the `version.go` in `GF` -->
### 3. Can this issue be reproduced with the latest release?
### 3. Can this issue be re-produced with the latest release?

71
.github/workflows/go.yml vendored Normal file
View File

@ -0,0 +1,71 @@
name: GoFrame CI
on:
push:
branches:
- master
- develop
pull_request:
branches: [master, develop]
env:
GF_DEBUG: 1
jobs:
code-test:
runs-on: ubuntu-latest
# Service containers to run with `code-test`
services:
redis:
image : redis
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
# Maps tcp port 6379 on service container to the host
- 6379:6379
mysql:
image: mysql:5.7
env:
MYSQL_DATABASE : test
MYSQL_ROOT_PASSWORD: 12345678
ports:
# Maps tcp port 3306 on service container to the host
- 3306:3306
# strategy set
strategy:
matrix:
go: ["1.14", "1.15", "1.16"]
steps:
- name: Set Up Timezone
uses: szenius/set-timezone@v1.0
with:
timezoneLinux: "Asia/Shanghai"
- name: Checkout Repositary
uses: actions/checkout@v2
- name: Set Up Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go }}
- name: Before Script
run: |
date
find . -name "*.go" | xargs gofmt -w
git diff --name-only --exit-code || exit 1
sudo echo "127.0.0.1 local" | sudo tee -a /etc/hosts
- name: Run i386 Arch Test
run: GOARCH=386 go test -v ./... || exit 1
- name: Run amd64 Arch Test
run: GOARCH=amd64 go test -v ./... -race -coverprofile=coverage.txt -covermode=atomic
- name: Report Coverage
run: bash <(curl -s https://codecov.io/bash)

2
.gitignore vendored
View File

@ -15,3 +15,5 @@ cbuild
**/.DS_Store
.vscode/
.example/other/
main
gf

View File

@ -1,7 +1,7 @@
# GoFrame
[![Go Doc](https://godoc.org/github.com/gogf/gf?status.svg)](https://godoc.org/github.com/gogf/gf)
[![Build Status](https://travis-ci.com/gogf/gf.svg?branch=master)](https://travis-ci.org/gogf/gf)
[![GoFrame CI](https://github.com/gogf/gf/actions/workflows/go.yml/badge.svg)](https://github.com/gogf/gf/actions/workflows/go.yml)
[![Go Report](https://goreportcard.com/badge/github.com/gogf/gf?v=1)](https://goreportcard.com/report/github.com/gogf/gf)
[![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf/branch/master)
[![Production Ready](https://img.shields.io/badge/production-ready-blue.svg)](https://github.com/gogf/gf)

View File

@ -8,8 +8,8 @@ package garray
import (
"bytes"
"errors"
"fmt"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/empty"
"github.com/gogf/gf/internal/json"
"github.com/gogf/gf/text/gstr"
@ -30,7 +30,7 @@ type Array struct {
}
// New creates and returns an empty array.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func New(safe ...bool) *Array {
return NewArraySize(0, 0, safe...)
@ -42,7 +42,7 @@ func NewArray(safe ...bool) *Array {
}
// NewArraySize create and returns an array with given size and cap.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func NewArraySize(size int, cap int, safe ...bool) *Array {
return &Array{
@ -51,8 +51,8 @@ func NewArraySize(size int, cap int, safe ...bool) *Array {
}
}
// NewArrayRange creates and returns a array by a range from <start> to <end>
// with step value <step>.
// NewArrayRange creates and returns a array by a range from `start` to `end`
// with step value `step`.
func NewArrayRange(start, end, step int, safe ...bool) *Array {
if step == 0 {
panic(fmt.Sprintf(`invalid step value: %d`, step))
@ -66,18 +66,20 @@ func NewArrayRange(start, end, step int, safe ...bool) *Array {
return NewArrayFrom(slice, safe...)
}
// NewFrom is alias of NewArrayFrom.
// See NewArrayFrom.
func NewFrom(array []interface{}, safe ...bool) *Array {
return NewArrayFrom(array, safe...)
}
// NewFromCopy is alias of NewArrayFromCopy.
// See NewArrayFromCopy.
func NewFromCopy(array []interface{}, safe ...bool) *Array {
return NewArrayFromCopy(array, safe...)
}
// NewArrayFrom creates and returns an array with given slice <array>.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// NewArrayFrom creates and returns an array with given slice `array`.
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func NewArrayFrom(array []interface{}, safe ...bool) *Array {
return &Array{
@ -86,8 +88,8 @@ func NewArrayFrom(array []interface{}, safe ...bool) *Array {
}
}
// NewArrayFromCopy creates and returns an array from a copy of given slice <array>.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// NewArrayFromCopy creates and returns an array from a copy of given slice `array`.
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func NewArrayFromCopy(array []interface{}, safe ...bool) *Array {
newArray := make([]interface{}, len(array))
@ -98,8 +100,15 @@ func NewArrayFromCopy(array []interface{}, safe ...bool) *Array {
}
}
// At returns the value by the specified index.
// If the given `index` is out of range of the array, it returns `nil`.
func (a *Array) At(index int) (value interface{}) {
value, _ = a.Get(index)
return
}
// Get returns the value by the specified index.
// If the given <index> is out of range of the array, the <found> is false.
// If the given `index` is out of range of the array, the `found` is false.
func (a *Array) Get(index int) (value interface{}, found bool) {
a.mu.RLock()
defer a.mu.RUnlock()
@ -114,13 +123,13 @@ func (a *Array) Set(index int, value interface{}) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array)))
return gerror.NewCodef(gerror.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
}
a.array[index] = value
return nil
}
// SetArray sets the underlying slice array with the given <array>.
// SetArray sets the underlying slice array with the given `array`.
func (a *Array) SetArray(array []interface{}) *Array {
a.mu.Lock()
defer a.mu.Unlock()
@ -128,7 +137,7 @@ func (a *Array) SetArray(array []interface{}) *Array {
return a
}
// Replace replaces the array items by given <array> from the beginning of array.
// Replace replaces the array items by given `array` from the beginning of array.
func (a *Array) Replace(array []interface{}) *Array {
a.mu.Lock()
defer a.mu.Unlock()
@ -152,7 +161,7 @@ func (a *Array) Sum() (sum int) {
return
}
// SortFunc sorts the array by custom function <less>.
// SortFunc sorts the array by custom function `less`.
func (a *Array) SortFunc(less func(v1, v2 interface{}) bool) *Array {
a.mu.Lock()
defer a.mu.Unlock()
@ -162,12 +171,12 @@ func (a *Array) SortFunc(less func(v1, v2 interface{}) bool) *Array {
return a
}
// InsertBefore inserts the <value> to the front of <index>.
// InsertBefore inserts the `value` to the front of `index`.
func (a *Array) InsertBefore(index int, value interface{}) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array)))
return gerror.NewCodef(gerror.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
}
rear := append([]interface{}{}, a.array[index:]...)
a.array = append(a.array[0:index], value)
@ -175,12 +184,12 @@ func (a *Array) InsertBefore(index int, value interface{}) error {
return nil
}
// InsertAfter inserts the <value> to the back of <index>.
// InsertAfter inserts the `value` to the back of `index`.
func (a *Array) InsertAfter(index int, value interface{}) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array)))
return gerror.NewCodef(gerror.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
}
rear := append([]interface{}{}, a.array[index+1:]...)
a.array = append(a.array[0:index+1], value)
@ -189,7 +198,7 @@ func (a *Array) InsertAfter(index int, value interface{}) error {
}
// Remove removes an item by index.
// If the given <index> is out of range of the array, the <found> is false.
// If the given `index` is out of range of the array, the `found` is false.
func (a *Array) Remove(index int) (value interface{}, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
@ -247,14 +256,14 @@ func (a *Array) PushRight(value ...interface{}) *Array {
}
// PopRand randomly pops and return an item out of array.
// Note that if the array is empty, the <found> is false.
// Note that if the array is empty, the `found` is false.
func (a *Array) PopRand() (value interface{}, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
return a.doRemoveWithoutLock(grand.Intn(len(a.array)))
}
// PopRands randomly pops and returns <size> items out of array.
// PopRands randomly pops and returns `size` items out of array.
func (a *Array) PopRands(size int) []interface{} {
a.mu.Lock()
defer a.mu.Unlock()
@ -272,7 +281,7 @@ func (a *Array) PopRands(size int) []interface{} {
}
// PopLeft pops and returns an item from the beginning of array.
// Note that if the array is empty, the <found> is false.
// Note that if the array is empty, the `found` is false.
func (a *Array) PopLeft() (value interface{}, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
@ -285,7 +294,7 @@ func (a *Array) PopLeft() (value interface{}, found bool) {
}
// PopRight pops and returns an item from the end of array.
// Note that if the array is empty, the <found> is false.
// Note that if the array is empty, the `found` is false.
func (a *Array) PopRight() (value interface{}, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
@ -298,7 +307,7 @@ func (a *Array) PopRight() (value interface{}, found bool) {
return value, true
}
// PopLefts pops and returns <size> items from the beginning of array.
// PopLefts pops and returns `size` items from the beginning of array.
func (a *Array) PopLefts(size int) []interface{} {
a.mu.Lock()
defer a.mu.Unlock()
@ -315,7 +324,7 @@ func (a *Array) PopLefts(size int) []interface{} {
return value
}
// PopRights pops and returns <size> items from the end of array.
// PopRights pops and returns `size` items from the end of array.
func (a *Array) PopRights(size int) []interface{} {
a.mu.Lock()
defer a.mu.Unlock()
@ -337,8 +346,8 @@ func (a *Array) PopRights(size int) []interface{} {
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
//
// If <end> is negative, then the offset will start from the end of array.
// If <end> is omitted, then the sequence will have everything from start up
// If `end` is negative, then the offset will start from the end of array.
// If `end` is omitted, then the sequence will have everything from start up
// until the end of the array.
func (a *Array) Range(start int, end ...int) []interface{} {
a.mu.RLock()
@ -364,7 +373,7 @@ func (a *Array) Range(start int, end ...int) []interface{} {
}
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// by the `offset` and `size` parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
//
// If offset is non-negative, the sequence will start at that offset in the array.
@ -471,7 +480,7 @@ func (a *Array) Contains(value interface{}) bool {
return a.Search(value) != -1
}
// Search searches array by <value>, returns the index of <value>,
// Search searches array by `value`, returns the index of `value`,
// or returns -1 if not exists.
func (a *Array) Search(value interface{}) int {
a.mu.RLock()
@ -506,7 +515,7 @@ func (a *Array) Unique() *Array {
return a
}
// LockFunc locks writing by callback function <f>.
// LockFunc locks writing by callback function `f`.
func (a *Array) LockFunc(f func(array []interface{})) *Array {
a.mu.Lock()
defer a.mu.Unlock()
@ -514,7 +523,7 @@ func (a *Array) LockFunc(f func(array []interface{})) *Array {
return a
}
// RLockFunc locks reading by callback function <f>.
// RLockFunc locks reading by callback function `f`.
func (a *Array) RLockFunc(f func(array []interface{})) *Array {
a.mu.RLock()
defer a.mu.RUnlock()
@ -522,21 +531,21 @@ func (a *Array) RLockFunc(f func(array []interface{})) *Array {
return a
}
// Merge merges <array> into current array.
// The parameter <array> can be any garray or slice type.
// Merge merges `array` into current array.
// The parameter `array` can be any garray or slice type.
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *Array) Merge(array interface{}) *Array {
return a.Append(gconv.Interfaces(array)...)
}
// Fill fills an array with num entries of the value <value>,
// keys starting at the <startIndex> parameter.
// Fill fills an array with num entries of the value `value`,
// keys starting at the `startIndex` parameter.
func (a *Array) Fill(startIndex int, num int, value interface{}) error {
a.mu.Lock()
defer a.mu.Unlock()
if startIndex < 0 || startIndex > len(a.array) {
return errors.New(fmt.Sprintf("index %d out of array range %d", startIndex, len(a.array)))
return gerror.NewCodef(gerror.CodeInvalidParameter, "index %d out of array range %d", startIndex, len(a.array))
}
for i := startIndex; i < startIndex+num; i++ {
if i > len(a.array)-1 {
@ -549,7 +558,7 @@ func (a *Array) Fill(startIndex int, num int, value interface{}) error {
}
// Chunk splits an array into multiple arrays,
// the size of each array is determined by <size>.
// the size of each array is determined by `size`.
// The last chunk may contain less than size elements.
func (a *Array) Chunk(size int) [][]interface{} {
if size < 1 {
@ -571,9 +580,9 @@ func (a *Array) Chunk(size int) [][]interface{} {
return n
}
// Pad pads array to the specified length with <value>.
// Pad pads array to the specified length with `value`.
// If size is positive then the array is padded on the right, or negative on the left.
// If the absolute value of <size> is less than or equal to the length of the array
// If the absolute value of `size` is less than or equal to the length of the array
// then no padding takes place.
func (a *Array) Pad(size int, val interface{}) *Array {
a.mu.Lock()
@ -608,7 +617,7 @@ func (a *Array) Rand() (value interface{}, found bool) {
return a.array[grand.Intn(len(a.array))], true
}
// Rands randomly returns <size> items from array(no deleting).
// Rands randomly returns `size` items from array(no deleting).
func (a *Array) Rands(size int) []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
@ -642,7 +651,7 @@ func (a *Array) Reverse() *Array {
return a
}
// Join joins array elements with a string <glue>.
// Join joins array elements with a string `glue`.
func (a *Array) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
@ -675,8 +684,8 @@ func (a *Array) Iterator(f func(k int, v interface{}) bool) {
a.IteratorAsc(f)
}
// IteratorAsc iterates the array readonly in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
// IteratorAsc iterates the array readonly in ascending order with given callback function `f`.
// If `f` returns true, then it continues iterating; or false to stop.
func (a *Array) IteratorAsc(f func(k int, v interface{}) bool) {
a.mu.RLock()
defer a.mu.RUnlock()
@ -687,8 +696,8 @@ func (a *Array) IteratorAsc(f func(k int, v interface{}) bool) {
}
}
// IteratorDesc iterates the array readonly in descending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
// IteratorDesc iterates the array readonly in descending order with given callback function `f`.
// If `f` returns true, then it continues iterating; or false to stop.
func (a *Array) IteratorDesc(f func(k int, v interface{}) bool) {
a.mu.RLock()
defer a.mu.RUnlock()
@ -784,7 +793,7 @@ func (a *Array) FilterEmpty() *Array {
return a
}
// Walk applies a user supplied function <f> to every item of array.
// Walk applies a user supplied function `f` to every item of array.
func (a *Array) Walk(f func(value interface{}) interface{}) *Array {
a.mu.Lock()
defer a.mu.Unlock()

View File

@ -8,8 +8,8 @@ package garray
import (
"bytes"
"errors"
"fmt"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/json"
"math"
"sort"
@ -28,14 +28,14 @@ type IntArray struct {
}
// NewIntArray creates and returns an empty array.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func NewIntArray(safe ...bool) *IntArray {
return NewIntArraySize(0, 0, safe...)
}
// NewIntArraySize create and returns an array with given size and cap.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func NewIntArraySize(size int, cap int, safe ...bool) *IntArray {
return &IntArray{
@ -44,8 +44,8 @@ func NewIntArraySize(size int, cap int, safe ...bool) *IntArray {
}
}
// NewIntArrayRange creates and returns a array by a range from <start> to <end>
// with step value <step>.
// NewIntArrayRange creates and returns a array by a range from `start` to `end`
// with step value `step`.
func NewIntArrayRange(start, end, step int, safe ...bool) *IntArray {
if step == 0 {
panic(fmt.Sprintf(`invalid step value: %d`, step))
@ -59,8 +59,8 @@ func NewIntArrayRange(start, end, step int, safe ...bool) *IntArray {
return NewIntArrayFrom(slice, safe...)
}
// NewIntArrayFrom creates and returns an array with given slice <array>.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// NewIntArrayFrom creates and returns an array with given slice `array`.
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func NewIntArrayFrom(array []int, safe ...bool) *IntArray {
return &IntArray{
@ -69,8 +69,8 @@ func NewIntArrayFrom(array []int, safe ...bool) *IntArray {
}
}
// NewIntArrayFromCopy creates and returns an array from a copy of given slice <array>.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// NewIntArrayFromCopy creates and returns an array from a copy of given slice `array`.
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func NewIntArrayFromCopy(array []int, safe ...bool) *IntArray {
newArray := make([]int, len(array))
@ -81,8 +81,15 @@ func NewIntArrayFromCopy(array []int, safe ...bool) *IntArray {
}
}
// At returns the value by the specified index.
// If the given `index` is out of range of the array, it returns `0`.
func (a *IntArray) At(index int) (value int) {
value, _ = a.Get(index)
return
}
// Get returns the value by the specified index.
// If the given <index> is out of range of the array, the <found> is false.
// If the given `index` is out of range of the array, the `found` is false.
func (a *IntArray) Get(index int) (value int, found bool) {
a.mu.RLock()
defer a.mu.RUnlock()
@ -97,13 +104,13 @@ func (a *IntArray) Set(index int, value int) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array)))
return gerror.NewCodef(gerror.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
}
a.array[index] = value
return nil
}
// SetArray sets the underlying slice array with the given <array>.
// SetArray sets the underlying slice array with the given `array`.
func (a *IntArray) SetArray(array []int) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
@ -111,7 +118,7 @@ func (a *IntArray) SetArray(array []int) *IntArray {
return a
}
// Replace replaces the array items by given <array> from the beginning of array.
// Replace replaces the array items by given `array` from the beginning of array.
func (a *IntArray) Replace(array []int) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
@ -136,7 +143,7 @@ func (a *IntArray) Sum() (sum int) {
}
// Sort sorts the array in increasing order.
// The parameter <reverse> controls whether sort in increasing order(default) or decreasing order.
// The parameter `reverse` controls whether sort in increasing order(default) or decreasing order.
func (a *IntArray) Sort(reverse ...bool) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
@ -153,7 +160,7 @@ func (a *IntArray) Sort(reverse ...bool) *IntArray {
return a
}
// SortFunc sorts the array by custom function <less>.
// SortFunc sorts the array by custom function `less`.
func (a *IntArray) SortFunc(less func(v1, v2 int) bool) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
@ -163,12 +170,12 @@ func (a *IntArray) SortFunc(less func(v1, v2 int) bool) *IntArray {
return a
}
// InsertBefore inserts the <value> to the front of <index>.
// InsertBefore inserts the `value` to the front of `index`.
func (a *IntArray) InsertBefore(index int, value int) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array)))
return gerror.NewCodef(gerror.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
}
rear := append([]int{}, a.array[index:]...)
a.array = append(a.array[0:index], value)
@ -176,12 +183,12 @@ func (a *IntArray) InsertBefore(index int, value int) error {
return nil
}
// InsertAfter inserts the <value> to the back of <index>.
// InsertAfter inserts the `value` to the back of `index`.
func (a *IntArray) InsertAfter(index int, value int) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array)))
return gerror.NewCodef(gerror.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
}
rear := append([]int{}, a.array[index+1:]...)
a.array = append(a.array[0:index+1], value)
@ -190,7 +197,7 @@ func (a *IntArray) InsertAfter(index int, value int) error {
}
// Remove removes an item by index.
// If the given <index> is out of range of the array, the <found> is false.
// If the given `index` is out of range of the array, the `found` is false.
func (a *IntArray) Remove(index int) (value int, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
@ -248,7 +255,7 @@ func (a *IntArray) PushRight(value ...int) *IntArray {
}
// PopLeft pops and returns an item from the beginning of array.
// Note that if the array is empty, the <found> is false.
// Note that if the array is empty, the `found` is false.
func (a *IntArray) PopLeft() (value int, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
@ -261,7 +268,7 @@ func (a *IntArray) PopLeft() (value int, found bool) {
}
// PopRight pops and returns an item from the end of array.
// Note that if the array is empty, the <found> is false.
// Note that if the array is empty, the `found` is false.
func (a *IntArray) PopRight() (value int, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
@ -275,16 +282,16 @@ func (a *IntArray) PopRight() (value int, found bool) {
}
// PopRand randomly pops and return an item out of array.
// Note that if the array is empty, the <found> is false.
// Note that if the array is empty, the `found` is false.
func (a *IntArray) PopRand() (value int, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
return a.doRemoveWithoutLock(grand.Intn(len(a.array)))
}
// PopRands randomly pops and returns <size> items out of array.
// If the given <size> is greater than size of the array, it returns all elements of the array.
// Note that if given <size> <= 0 or the array is empty, it returns nil.
// PopRands randomly pops and returns `size` items out of array.
// If the given `size` is greater than size of the array, it returns all elements of the array.
// Note that if given `size` <= 0 or the array is empty, it returns nil.
func (a *IntArray) PopRands(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
@ -301,9 +308,9 @@ func (a *IntArray) PopRands(size int) []int {
return array
}
// PopLefts pops and returns <size> items from the beginning of array.
// If the given <size> is greater than size of the array, it returns all elements of the array.
// Note that if given <size> <= 0 or the array is empty, it returns nil.
// PopLefts pops and returns `size` items from the beginning of array.
// If the given `size` is greater than size of the array, it returns all elements of the array.
// Note that if given `size` <= 0 or the array is empty, it returns nil.
func (a *IntArray) PopLefts(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
@ -320,9 +327,9 @@ func (a *IntArray) PopLefts(size int) []int {
return value
}
// PopRights pops and returns <size> items from the end of array.
// If the given <size> is greater than size of the array, it returns all elements of the array.
// Note that if given <size> <= 0 or the array is empty, it returns nil.
// PopRights pops and returns `size` items from the end of array.
// If the given `size` is greater than size of the array, it returns all elements of the array.
// Note that if given `size` <= 0 or the array is empty, it returns nil.
func (a *IntArray) PopRights(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
@ -344,8 +351,8 @@ func (a *IntArray) PopRights(size int) []int {
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
//
// If <end> is negative, then the offset will start from the end of array.
// If <end> is omitted, then the sequence will have everything from start up
// If `end` is negative, then the offset will start from the end of array.
// If `end` is omitted, then the sequence will have everything from start up
// until the end of the array.
func (a *IntArray) Range(start int, end ...int) []int {
a.mu.RLock()
@ -371,7 +378,7 @@ func (a *IntArray) Range(start int, end ...int) []int {
}
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// by the `offset` and `size` parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
//
// If offset is non-negative, the sequence will start at that offset in the array.
@ -487,7 +494,7 @@ func (a *IntArray) Contains(value int) bool {
return a.Search(value) != -1
}
// Search searches array by <value>, returns the index of <value>,
// Search searches array by `value`, returns the index of `value`,
// or returns -1 if not exists.
func (a *IntArray) Search(value int) int {
a.mu.RLock()
@ -522,7 +529,7 @@ func (a *IntArray) Unique() *IntArray {
return a
}
// LockFunc locks writing by callback function <f>.
// LockFunc locks writing by callback function `f`.
func (a *IntArray) LockFunc(f func(array []int)) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
@ -530,7 +537,7 @@ func (a *IntArray) LockFunc(f func(array []int)) *IntArray {
return a
}
// RLockFunc locks reading by callback function <f>.
// RLockFunc locks reading by callback function `f`.
func (a *IntArray) RLockFunc(f func(array []int)) *IntArray {
a.mu.RLock()
defer a.mu.RUnlock()
@ -538,21 +545,21 @@ func (a *IntArray) RLockFunc(f func(array []int)) *IntArray {
return a
}
// Merge merges <array> into current array.
// The parameter <array> can be any garray or slice type.
// Merge merges `array` into current array.
// The parameter `array` can be any garray or slice type.
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *IntArray) Merge(array interface{}) *IntArray {
return a.Append(gconv.Ints(array)...)
}
// Fill fills an array with num entries of the value <value>,
// keys starting at the <startIndex> parameter.
// Fill fills an array with num entries of the value `value`,
// keys starting at the `startIndex` parameter.
func (a *IntArray) Fill(startIndex int, num int, value int) error {
a.mu.Lock()
defer a.mu.Unlock()
if startIndex < 0 || startIndex > len(a.array) {
return errors.New(fmt.Sprintf("index %d out of array range %d", startIndex, len(a.array)))
return gerror.NewCodef(gerror.CodeInvalidParameter, "index %d out of array range %d", startIndex, len(a.array))
}
for i := startIndex; i < startIndex+num; i++ {
if i > len(a.array)-1 {
@ -565,7 +572,7 @@ func (a *IntArray) Fill(startIndex int, num int, value int) error {
}
// Chunk splits an array into multiple arrays,
// the size of each array is determined by <size>.
// the size of each array is determined by `size`.
// The last chunk may contain less than size elements.
func (a *IntArray) Chunk(size int) [][]int {
if size < 1 {
@ -587,9 +594,9 @@ func (a *IntArray) Chunk(size int) [][]int {
return n
}
// Pad pads array to the specified length with <value>.
// Pad pads array to the specified length with `value`.
// If size is positive then the array is padded on the right, or negative on the left.
// If the absolute value of <size> is less than or equal to the length of the array
// If the absolute value of `size` is less than or equal to the length of the array
// then no padding takes place.
func (a *IntArray) Pad(size int, value int) *IntArray {
a.mu.Lock()
@ -624,7 +631,7 @@ func (a *IntArray) Rand() (value int, found bool) {
return a.array[grand.Intn(len(a.array))], true
}
// Rands randomly returns <size> items from array(no deleting).
// Rands randomly returns `size` items from array(no deleting).
func (a *IntArray) Rands(size int) []int {
a.mu.RLock()
defer a.mu.RUnlock()
@ -658,7 +665,7 @@ func (a *IntArray) Reverse() *IntArray {
return a
}
// Join joins array elements with a string <glue>.
// Join joins array elements with a string `glue`.
func (a *IntArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
@ -691,8 +698,8 @@ func (a *IntArray) Iterator(f func(k int, v int) bool) {
a.IteratorAsc(f)
}
// IteratorAsc iterates the array readonly in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
// IteratorAsc iterates the array readonly in ascending order with given callback function `f`.
// If `f` returns true, then it continues iterating; or false to stop.
func (a *IntArray) IteratorAsc(f func(k int, v int) bool) {
a.mu.RLock()
defer a.mu.RUnlock()
@ -703,8 +710,8 @@ func (a *IntArray) IteratorAsc(f func(k int, v int) bool) {
}
}
// IteratorDesc iterates the array readonly in descending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
// IteratorDesc iterates the array readonly in descending order with given callback function `f`.
// If `f` returns true, then it continues iterating; or false to stop.
func (a *IntArray) IteratorDesc(f func(k int, v int) bool) {
a.mu.RLock()
defer a.mu.RUnlock()
@ -768,7 +775,7 @@ func (a *IntArray) FilterEmpty() *IntArray {
return a
}
// Walk applies a user supplied function <f> to every item of array.
// Walk applies a user supplied function `f` to every item of array.
func (a *IntArray) Walk(f func(value int) int) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()

View File

@ -8,8 +8,7 @@ package garray
import (
"bytes"
"errors"
"fmt"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/json"
"github.com/gogf/gf/text/gstr"
"math"
@ -30,14 +29,14 @@ type StrArray struct {
}
// NewStrArray creates and returns an empty array.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func NewStrArray(safe ...bool) *StrArray {
return NewStrArraySize(0, 0, safe...)
}
// NewStrArraySize create and returns an array with given size and cap.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func NewStrArraySize(size int, cap int, safe ...bool) *StrArray {
return &StrArray{
@ -46,8 +45,8 @@ func NewStrArraySize(size int, cap int, safe ...bool) *StrArray {
}
}
// NewStrArrayFrom creates and returns an array with given slice <array>.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// NewStrArrayFrom creates and returns an array with given slice `array`.
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func NewStrArrayFrom(array []string, safe ...bool) *StrArray {
return &StrArray{
@ -56,8 +55,8 @@ func NewStrArrayFrom(array []string, safe ...bool) *StrArray {
}
}
// NewStrArrayFromCopy creates and returns an array from a copy of given slice <array>.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// NewStrArrayFromCopy creates and returns an array from a copy of given slice `array`.
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func NewStrArrayFromCopy(array []string, safe ...bool) *StrArray {
newArray := make([]string, len(array))
@ -68,8 +67,15 @@ func NewStrArrayFromCopy(array []string, safe ...bool) *StrArray {
}
}
// At returns the value by the specified index.
// If the given `index` is out of range of the array, it returns an empty string.
func (a *StrArray) At(index int) (value string) {
value, _ = a.Get(index)
return
}
// Get returns the value by the specified index.
// If the given <index> is out of range of the array, the <found> is false.
// If the given `index` is out of range of the array, the `found` is false.
func (a *StrArray) Get(index int) (value string, found bool) {
a.mu.RLock()
defer a.mu.RUnlock()
@ -84,13 +90,13 @@ func (a *StrArray) Set(index int, value string) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array)))
return gerror.NewCodef(gerror.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
}
a.array[index] = value
return nil
}
// SetArray sets the underlying slice array with the given <array>.
// SetArray sets the underlying slice array with the given `array`.
func (a *StrArray) SetArray(array []string) *StrArray {
a.mu.Lock()
defer a.mu.Unlock()
@ -98,7 +104,7 @@ func (a *StrArray) SetArray(array []string) *StrArray {
return a
}
// Replace replaces the array items by given <array> from the beginning of array.
// Replace replaces the array items by given `array` from the beginning of array.
func (a *StrArray) Replace(array []string) *StrArray {
a.mu.Lock()
defer a.mu.Unlock()
@ -123,7 +129,7 @@ func (a *StrArray) Sum() (sum int) {
}
// Sort sorts the array in increasing order.
// The parameter <reverse> controls whether sort
// The parameter `reverse` controls whether sort
// in increasing order(default) or decreasing order
func (a *StrArray) Sort(reverse ...bool) *StrArray {
a.mu.Lock()
@ -141,7 +147,7 @@ func (a *StrArray) Sort(reverse ...bool) *StrArray {
return a
}
// SortFunc sorts the array by custom function <less>.
// SortFunc sorts the array by custom function `less`.
func (a *StrArray) SortFunc(less func(v1, v2 string) bool) *StrArray {
a.mu.Lock()
defer a.mu.Unlock()
@ -151,12 +157,12 @@ func (a *StrArray) SortFunc(less func(v1, v2 string) bool) *StrArray {
return a
}
// InsertBefore inserts the <value> to the front of <index>.
// InsertBefore inserts the `value` to the front of `index`.
func (a *StrArray) InsertBefore(index int, value string) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array)))
return gerror.NewCodef(gerror.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
}
rear := append([]string{}, a.array[index:]...)
a.array = append(a.array[0:index], value)
@ -164,12 +170,12 @@ func (a *StrArray) InsertBefore(index int, value string) error {
return nil
}
// InsertAfter inserts the <value> to the back of <index>.
// InsertAfter inserts the `value` to the back of `index`.
func (a *StrArray) InsertAfter(index int, value string) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array)))
return gerror.NewCodef(gerror.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
}
rear := append([]string{}, a.array[index+1:]...)
a.array = append(a.array[0:index+1], value)
@ -178,7 +184,7 @@ func (a *StrArray) InsertAfter(index int, value string) error {
}
// Remove removes an item by index.
// If the given <index> is out of range of the array, the <found> is false.
// If the given `index` is out of range of the array, the `found` is false.
func (a *StrArray) Remove(index int) (value string, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
@ -236,7 +242,7 @@ func (a *StrArray) PushRight(value ...string) *StrArray {
}
// PopLeft pops and returns an item from the beginning of array.
// Note that if the array is empty, the <found> is false.
// Note that if the array is empty, the `found` is false.
func (a *StrArray) PopLeft() (value string, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
@ -249,7 +255,7 @@ func (a *StrArray) PopLeft() (value string, found bool) {
}
// PopRight pops and returns an item from the end of array.
// Note that if the array is empty, the <found> is false.
// Note that if the array is empty, the `found` is false.
func (a *StrArray) PopRight() (value string, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
@ -263,16 +269,16 @@ func (a *StrArray) PopRight() (value string, found bool) {
}
// PopRand randomly pops and return an item out of array.
// Note that if the array is empty, the <found> is false.
// Note that if the array is empty, the `found` is false.
func (a *StrArray) PopRand() (value string, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
return a.doRemoveWithoutLock(grand.Intn(len(a.array)))
}
// PopRands randomly pops and returns <size> items out of array.
// If the given <size> is greater than size of the array, it returns all elements of the array.
// Note that if given <size> <= 0 or the array is empty, it returns nil.
// PopRands randomly pops and returns `size` items out of array.
// If the given `size` is greater than size of the array, it returns all elements of the array.
// Note that if given `size` <= 0 or the array is empty, it returns nil.
func (a *StrArray) PopRands(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
@ -289,9 +295,9 @@ func (a *StrArray) PopRands(size int) []string {
return array
}
// PopLefts pops and returns <size> items from the beginning of array.
// If the given <size> is greater than size of the array, it returns all elements of the array.
// Note that if given <size> <= 0 or the array is empty, it returns nil.
// PopLefts pops and returns `size` items from the beginning of array.
// If the given `size` is greater than size of the array, it returns all elements of the array.
// Note that if given `size` <= 0 or the array is empty, it returns nil.
func (a *StrArray) PopLefts(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
@ -308,9 +314,9 @@ func (a *StrArray) PopLefts(size int) []string {
return value
}
// PopRights pops and returns <size> items from the end of array.
// If the given <size> is greater than size of the array, it returns all elements of the array.
// Note that if given <size> <= 0 or the array is empty, it returns nil.
// PopRights pops and returns `size` items from the end of array.
// If the given `size` is greater than size of the array, it returns all elements of the array.
// Note that if given `size` <= 0 or the array is empty, it returns nil.
func (a *StrArray) PopRights(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
@ -332,8 +338,8 @@ func (a *StrArray) PopRights(size int) []string {
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
//
// If <end> is negative, then the offset will start from the end of array.
// If <end> is omitted, then the sequence will have everything from start up
// If `end` is negative, then the offset will start from the end of array.
// If `end` is omitted, then the sequence will have everything from start up
// until the end of the array.
func (a *StrArray) Range(start int, end ...int) []string {
a.mu.RLock()
@ -359,7 +365,7 @@ func (a *StrArray) Range(start int, end ...int) []string {
}
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// by the `offset` and `size` parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
//
// If offset is non-negative, the sequence will start at that offset in the array.
@ -491,7 +497,7 @@ func (a *StrArray) ContainsI(value string) bool {
return false
}
// Search searches array by <value>, returns the index of <value>,
// Search searches array by `value`, returns the index of `value`,
// or returns -1 if not exists.
func (a *StrArray) Search(value string) int {
a.mu.RLock()
@ -526,7 +532,7 @@ func (a *StrArray) Unique() *StrArray {
return a
}
// LockFunc locks writing by callback function <f>.
// LockFunc locks writing by callback function `f`.
func (a *StrArray) LockFunc(f func(array []string)) *StrArray {
a.mu.Lock()
defer a.mu.Unlock()
@ -534,7 +540,7 @@ func (a *StrArray) LockFunc(f func(array []string)) *StrArray {
return a
}
// RLockFunc locks reading by callback function <f>.
// RLockFunc locks reading by callback function `f`.
func (a *StrArray) RLockFunc(f func(array []string)) *StrArray {
a.mu.RLock()
defer a.mu.RUnlock()
@ -542,21 +548,21 @@ func (a *StrArray) RLockFunc(f func(array []string)) *StrArray {
return a
}
// Merge merges <array> into current array.
// The parameter <array> can be any garray or slice type.
// Merge merges `array` into current array.
// The parameter `array` can be any garray or slice type.
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *StrArray) Merge(array interface{}) *StrArray {
return a.Append(gconv.Strings(array)...)
}
// Fill fills an array with num entries of the value <value>,
// keys starting at the <startIndex> parameter.
// Fill fills an array with num entries of the value `value`,
// keys starting at the `startIndex` parameter.
func (a *StrArray) Fill(startIndex int, num int, value string) error {
a.mu.Lock()
defer a.mu.Unlock()
if startIndex < 0 || startIndex > len(a.array) {
return errors.New(fmt.Sprintf("index %d out of array range %d", startIndex, len(a.array)))
return gerror.NewCodef(gerror.CodeInvalidParameter, "index %d out of array range %d", startIndex, len(a.array))
}
for i := startIndex; i < startIndex+num; i++ {
if i > len(a.array)-1 {
@ -569,7 +575,7 @@ func (a *StrArray) Fill(startIndex int, num int, value string) error {
}
// Chunk splits an array into multiple arrays,
// the size of each array is determined by <size>.
// the size of each array is determined by `size`.
// The last chunk may contain less than size elements.
func (a *StrArray) Chunk(size int) [][]string {
if size < 1 {
@ -591,9 +597,9 @@ func (a *StrArray) Chunk(size int) [][]string {
return n
}
// Pad pads array to the specified length with <value>.
// Pad pads array to the specified length with `value`.
// If size is positive then the array is padded on the right, or negative on the left.
// If the absolute value of <size> is less than or equal to the length of the array
// If the absolute value of `size` is less than or equal to the length of the array
// then no padding takes place.
func (a *StrArray) Pad(size int, value string) *StrArray {
a.mu.Lock()
@ -628,7 +634,7 @@ func (a *StrArray) Rand() (value string, found bool) {
return a.array[grand.Intn(len(a.array))], true
}
// Rands randomly returns <size> items from array(no deleting).
// Rands randomly returns `size` items from array(no deleting).
func (a *StrArray) Rands(size int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
@ -662,7 +668,7 @@ func (a *StrArray) Reverse() *StrArray {
return a
}
// Join joins array elements with a string <glue>.
// Join joins array elements with a string `glue`.
func (a *StrArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
@ -695,8 +701,8 @@ func (a *StrArray) Iterator(f func(k int, v string) bool) {
a.IteratorAsc(f)
}
// IteratorAsc iterates the array readonly in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
// IteratorAsc iterates the array readonly in ascending order with given callback function `f`.
// If `f` returns true, then it continues iterating; or false to stop.
func (a *StrArray) IteratorAsc(f func(k int, v string) bool) {
a.mu.RLock()
defer a.mu.RUnlock()
@ -707,8 +713,8 @@ func (a *StrArray) IteratorAsc(f func(k int, v string) bool) {
}
}
// IteratorDesc iterates the array readonly in descending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
// IteratorDesc iterates the array readonly in descending order with given callback function `f`.
// If `f` returns true, then it continues iterating; or false to stop.
func (a *StrArray) IteratorDesc(f func(k int, v string) bool) {
a.mu.RLock()
defer a.mu.RUnlock()
@ -783,7 +789,7 @@ func (a *StrArray) FilterEmpty() *StrArray {
return a
}
// Walk applies a user supplied function <f> to every item of array.
// Walk applies a user supplied function `f` to every item of array.
func (a *StrArray) Walk(f func(value string) string) *StrArray {
a.mu.Lock()
defer a.mu.Unlock()

View File

@ -34,8 +34,8 @@ type SortedArray struct {
}
// NewSortedArray creates and returns an empty sorted array.
// The parameter <safe> is used to specify whether using array in concurrent-safety, which is false in default.
// The parameter <comparator> used to compare values to sort in array,
// The parameter `safe` is used to specify whether using array in concurrent-safety, which is false in default.
// The parameter `comparator` used to compare values to sort in array,
// if it returns value < 0, means v1 < v2; the v1 will be inserted before v2;
// if it returns value = 0, means v1 = v2; the v1 will be replaced by v2;
// if it returns value > 0, means v1 > v2; the v1 will be inserted after v2;
@ -44,7 +44,7 @@ func NewSortedArray(comparator func(a, b interface{}) int, safe ...bool) *Sorted
}
// NewSortedArraySize create and returns an sorted array with given size and cap.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func NewSortedArraySize(cap int, comparator func(a, b interface{}) int, safe ...bool) *SortedArray {
return &SortedArray{
@ -54,8 +54,8 @@ func NewSortedArraySize(cap int, comparator func(a, b interface{}) int, safe ...
}
}
// NewSortedArrayRange creates and returns a array by a range from <start> to <end>
// with step value <step>.
// NewSortedArrayRange creates and returns a array by a range from `start` to `end`
// with step value `step`.
func NewSortedArrayRange(start, end, step int, comparator func(a, b interface{}) int, safe ...bool) *SortedArray {
if step == 0 {
panic(fmt.Sprintf(`invalid step value: %d`, step))
@ -69,8 +69,8 @@ func NewSortedArrayRange(start, end, step int, comparator func(a, b interface{})
return NewSortedArrayFrom(slice, comparator, safe...)
}
// NewSortedArrayFrom creates and returns an sorted array with given slice <array>.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// NewSortedArrayFrom creates and returns an sorted array with given slice `array`.
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func NewSortedArrayFrom(array []interface{}, comparator func(a, b interface{}) int, safe ...bool) *SortedArray {
a := NewSortedArraySize(0, comparator, safe...)
@ -81,8 +81,8 @@ func NewSortedArrayFrom(array []interface{}, comparator func(a, b interface{}) i
return a
}
// NewSortedArrayFromCopy creates and returns an sorted array from a copy of given slice <array>.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// NewSortedArrayFromCopy creates and returns an sorted array from a copy of given slice `array`.
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func NewSortedArrayFromCopy(array []interface{}, comparator func(a, b interface{}) int, safe ...bool) *SortedArray {
newArray := make([]interface{}, len(array))
@ -90,7 +90,14 @@ func NewSortedArrayFromCopy(array []interface{}, comparator func(a, b interface{
return NewSortedArrayFrom(newArray, comparator, safe...)
}
// SetArray sets the underlying slice array with the given <array>.
// At returns the value by the specified index.
// If the given `index` is out of range of the array, it returns `nil`.
func (a *SortedArray) At(index int) (value interface{}) {
value, _ = a.Get(index)
return
}
// SetArray sets the underlying slice array with the given `array`.
func (a *SortedArray) SetArray(array []interface{}) *SortedArray {
a.mu.Lock()
defer a.mu.Unlock()
@ -113,7 +120,7 @@ func (a *SortedArray) SetComparator(comparator func(a, b interface{}) int) {
}
// Sort sorts the array in increasing order.
// The parameter <reverse> controls whether sort
// The parameter `reverse` controls whether sort
// in increasing order(default) or decreasing order
func (a *SortedArray) Sort() *SortedArray {
a.mu.Lock()
@ -157,7 +164,7 @@ func (a *SortedArray) Append(values ...interface{}) *SortedArray {
}
// Get returns the value by the specified index.
// If the given <index> is out of range of the array, the <found> is false.
// If the given `index` is out of range of the array, the `found` is false.
func (a *SortedArray) Get(index int) (value interface{}, found bool) {
a.mu.RLock()
defer a.mu.RUnlock()
@ -168,7 +175,7 @@ func (a *SortedArray) Get(index int) (value interface{}, found bool) {
}
// Remove removes an item by index.
// If the given <index> is out of range of the array, the <found> is false.
// If the given `index` is out of range of the array, the `found` is false.
func (a *SortedArray) Remove(index int) (value interface{}, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
@ -209,7 +216,7 @@ func (a *SortedArray) RemoveValue(value interface{}) bool {
}
// PopLeft pops and returns an item from the beginning of array.
// Note that if the array is empty, the <found> is false.
// Note that if the array is empty, the `found` is false.
func (a *SortedArray) PopLeft() (value interface{}, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
@ -222,7 +229,7 @@ func (a *SortedArray) PopLeft() (value interface{}, found bool) {
}
// PopRight pops and returns an item from the end of array.
// Note that if the array is empty, the <found> is false.
// Note that if the array is empty, the `found` is false.
func (a *SortedArray) PopRight() (value interface{}, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
@ -236,14 +243,14 @@ func (a *SortedArray) PopRight() (value interface{}, found bool) {
}
// PopRand randomly pops and return an item out of array.
// Note that if the array is empty, the <found> is false.
// Note that if the array is empty, the `found` is false.
func (a *SortedArray) PopRand() (value interface{}, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
return a.doRemoveWithoutLock(grand.Intn(len(a.array)))
}
// PopRands randomly pops and returns <size> items out of array.
// PopRands randomly pops and returns `size` items out of array.
func (a *SortedArray) PopRands(size int) []interface{} {
a.mu.Lock()
defer a.mu.Unlock()
@ -260,7 +267,7 @@ func (a *SortedArray) PopRands(size int) []interface{} {
return array
}
// PopLefts pops and returns <size> items from the beginning of array.
// PopLefts pops and returns `size` items from the beginning of array.
func (a *SortedArray) PopLefts(size int) []interface{} {
a.mu.Lock()
defer a.mu.Unlock()
@ -277,7 +284,7 @@ func (a *SortedArray) PopLefts(size int) []interface{} {
return value
}
// PopRights pops and returns <size> items from the end of array.
// PopRights pops and returns `size` items from the end of array.
func (a *SortedArray) PopRights(size int) []interface{} {
a.mu.Lock()
defer a.mu.Unlock()
@ -299,8 +306,8 @@ func (a *SortedArray) PopRights(size int) []interface{} {
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
//
// If <end> is negative, then the offset will start from the end of array.
// If <end> is omitted, then the sequence will have everything from start up
// If `end` is negative, then the offset will start from the end of array.
// If `end` is omitted, then the sequence will have everything from start up
// until the end of the array.
func (a *SortedArray) Range(start int, end ...int) []interface{} {
a.mu.RLock()
@ -326,7 +333,7 @@ func (a *SortedArray) Range(start int, end ...int) []interface{} {
}
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// by the `offset` and `size` parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
//
// If offset is non-negative, the sequence will start at that offset in the array.
@ -419,7 +426,7 @@ func (a *SortedArray) Contains(value interface{}) bool {
return a.Search(value) != -1
}
// Search searches array by <value>, returns the index of <value>,
// Search searches array by `value`, returns the index of `value`,
// or returns -1 if not exists.
func (a *SortedArray) Search(value interface{}) (index int) {
if i, r := a.binSearch(value, true); r == 0 {
@ -430,9 +437,9 @@ func (a *SortedArray) Search(value interface{}) (index int) {
// Binary search.
// It returns the last compared index and the result.
// If <result> equals to 0, it means the value at <index> is equals to <value>.
// If <result> lesser than 0, it means the value at <index> is lesser than <value>.
// If <result> greater than 0, it means the value at <index> is greater than <value>.
// If `result` equals to 0, it means the value at `index` is equals to `value`.
// If `result` lesser than 0, it means the value at `index` is lesser than `value`.
// If `result` greater than 0, it means the value at `index` is greater than `value`.
func (a *SortedArray) binSearch(value interface{}, lock bool) (index int, result int) {
if lock {
a.mu.RLock()
@ -512,7 +519,7 @@ func (a *SortedArray) Clear() *SortedArray {
return a
}
// LockFunc locks writing by callback function <f>.
// LockFunc locks writing by callback function `f`.
func (a *SortedArray) LockFunc(f func(array []interface{})) *SortedArray {
a.mu.Lock()
defer a.mu.Unlock()
@ -526,7 +533,7 @@ func (a *SortedArray) LockFunc(f func(array []interface{})) *SortedArray {
return a
}
// RLockFunc locks reading by callback function <f>.
// RLockFunc locks reading by callback function `f`.
func (a *SortedArray) RLockFunc(f func(array []interface{})) *SortedArray {
a.mu.RLock()
defer a.mu.RUnlock()
@ -534,8 +541,8 @@ func (a *SortedArray) RLockFunc(f func(array []interface{})) *SortedArray {
return a
}
// Merge merges <array> into current array.
// The parameter <array> can be any garray or slice type.
// Merge merges `array` into current array.
// The parameter `array` can be any garray or slice type.
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *SortedArray) Merge(array interface{}) *SortedArray {
@ -543,7 +550,7 @@ func (a *SortedArray) Merge(array interface{}) *SortedArray {
}
// Chunk splits an array into multiple arrays,
// the size of each array is determined by <size>.
// the size of each array is determined by `size`.
// The last chunk may contain less than size elements.
func (a *SortedArray) Chunk(size int) [][]interface{} {
if size < 1 {
@ -575,7 +582,7 @@ func (a *SortedArray) Rand() (value interface{}, found bool) {
return a.array[grand.Intn(len(a.array))], true
}
// Rands randomly returns <size> items from array(no deleting).
// Rands randomly returns `size` items from array(no deleting).
func (a *SortedArray) Rands(size int) []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
@ -589,7 +596,7 @@ func (a *SortedArray) Rands(size int) []interface{} {
return array
}
// Join joins array elements with a string <glue>.
// Join joins array elements with a string `glue`.
func (a *SortedArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
@ -622,8 +629,8 @@ func (a *SortedArray) Iterator(f func(k int, v interface{}) bool) {
a.IteratorAsc(f)
}
// IteratorAsc iterates the array readonly in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
// IteratorAsc iterates the array readonly in ascending order with given callback function `f`.
// If `f` returns true, then it continues iterating; or false to stop.
func (a *SortedArray) IteratorAsc(f func(k int, v interface{}) bool) {
a.mu.RLock()
defer a.mu.RUnlock()
@ -634,8 +641,8 @@ func (a *SortedArray) IteratorAsc(f func(k int, v interface{}) bool) {
}
}
// IteratorDesc iterates the array readonly in descending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
// IteratorDesc iterates the array readonly in descending order with given callback function `f`.
// If `f` returns true, then it continues iterating; or false to stop.
func (a *SortedArray) IteratorDesc(f func(k int, v interface{}) bool) {
a.mu.RLock()
defer a.mu.RUnlock()
@ -761,7 +768,7 @@ func (a *SortedArray) FilterEmpty() *SortedArray {
return a
}
// Walk applies a user supplied function <f> to every item of array.
// Walk applies a user supplied function `f` to every item of array.
func (a *SortedArray) Walk(f func(value interface{}) interface{}) *SortedArray {
a.mu.Lock()
defer a.mu.Unlock()

View File

@ -31,14 +31,14 @@ type SortedIntArray struct {
}
// NewSortedIntArray creates and returns an empty sorted array.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func NewSortedIntArray(safe ...bool) *SortedIntArray {
return NewSortedIntArraySize(0, safe...)
}
// NewSortedIntArrayComparator creates and returns an empty sorted array with specified comparator.
// The parameter <safe> is used to specify whether using array in concurrent-safety which is false in default.
// The parameter `safe` is used to specify whether using array in concurrent-safety which is false in default.
func NewSortedIntArrayComparator(comparator func(a, b int) int, safe ...bool) *SortedIntArray {
array := NewSortedIntArray(safe...)
array.comparator = comparator
@ -46,7 +46,7 @@ func NewSortedIntArrayComparator(comparator func(a, b int) int, safe ...bool) *S
}
// NewSortedIntArraySize create and returns an sorted array with given size and cap.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func NewSortedIntArraySize(cap int, safe ...bool) *SortedIntArray {
return &SortedIntArray{
@ -56,8 +56,8 @@ func NewSortedIntArraySize(cap int, safe ...bool) *SortedIntArray {
}
}
// NewSortedIntArrayRange creates and returns a array by a range from <start> to <end>
// with step value <step>.
// NewSortedIntArrayRange creates and returns a array by a range from `start` to `end`
// with step value `step`.
func NewSortedIntArrayRange(start, end, step int, safe ...bool) *SortedIntArray {
if step == 0 {
panic(fmt.Sprintf(`invalid step value: %d`, step))
@ -71,8 +71,8 @@ func NewSortedIntArrayRange(start, end, step int, safe ...bool) *SortedIntArray
return NewSortedIntArrayFrom(slice, safe...)
}
// NewIntArrayFrom creates and returns an sorted array with given slice <array>.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// NewSortedIntArrayFrom creates and returns an sorted array with given slice `array`.
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func NewSortedIntArrayFrom(array []int, safe ...bool) *SortedIntArray {
a := NewSortedIntArraySize(0, safe...)
@ -81,8 +81,8 @@ func NewSortedIntArrayFrom(array []int, safe ...bool) *SortedIntArray {
return a
}
// NewSortedIntArrayFromCopy creates and returns an sorted array from a copy of given slice <array>.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// NewSortedIntArrayFromCopy creates and returns an sorted array from a copy of given slice `array`.
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func NewSortedIntArrayFromCopy(array []int, safe ...bool) *SortedIntArray {
newArray := make([]int, len(array))
@ -90,7 +90,14 @@ func NewSortedIntArrayFromCopy(array []int, safe ...bool) *SortedIntArray {
return NewSortedIntArrayFrom(newArray, safe...)
}
// SetArray sets the underlying slice array with the given <array>.
// At returns the value by the specified index.
// If the given `index` is out of range of the array, it returns `0`.
func (a *SortedIntArray) At(index int) (value int) {
value, _ = a.Get(index)
return
}
// SetArray sets the underlying slice array with the given `array`.
func (a *SortedIntArray) SetArray(array []int) *SortedIntArray {
a.mu.Lock()
defer a.mu.Unlock()
@ -100,7 +107,7 @@ func (a *SortedIntArray) SetArray(array []int) *SortedIntArray {
}
// Sort sorts the array in increasing order.
// The parameter <reverse> controls whether sort
// The parameter `reverse` controls whether sort
// in increasing order(default) or decreasing order.
func (a *SortedIntArray) Sort() *SortedIntArray {
a.mu.Lock()
@ -142,7 +149,7 @@ func (a *SortedIntArray) Append(values ...int) *SortedIntArray {
}
// Get returns the value by the specified index.
// If the given <index> is out of range of the array, the <found> is false.
// If the given `index` is out of range of the array, the `found` is false.
func (a *SortedIntArray) Get(index int) (value int, found bool) {
a.mu.RLock()
defer a.mu.RUnlock()
@ -153,7 +160,7 @@ func (a *SortedIntArray) Get(index int) (value int, found bool) {
}
// Remove removes an item by index.
// If the given <index> is out of range of the array, the <found> is false.
// If the given `index` is out of range of the array, the `found` is false.
func (a *SortedIntArray) Remove(index int) (value int, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
@ -194,7 +201,7 @@ func (a *SortedIntArray) RemoveValue(value int) bool {
}
// PopLeft pops and returns an item from the beginning of array.
// Note that if the array is empty, the <found> is false.
// Note that if the array is empty, the `found` is false.
func (a *SortedIntArray) PopLeft() (value int, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
@ -207,7 +214,7 @@ func (a *SortedIntArray) PopLeft() (value int, found bool) {
}
// PopRight pops and returns an item from the end of array.
// Note that if the array is empty, the <found> is false.
// Note that if the array is empty, the `found` is false.
func (a *SortedIntArray) PopRight() (value int, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
@ -221,16 +228,16 @@ func (a *SortedIntArray) PopRight() (value int, found bool) {
}
// PopRand randomly pops and return an item out of array.
// Note that if the array is empty, the <found> is false.
// Note that if the array is empty, the `found` is false.
func (a *SortedIntArray) PopRand() (value int, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
return a.doRemoveWithoutLock(grand.Intn(len(a.array)))
}
// PopRands randomly pops and returns <size> items out of array.
// If the given <size> is greater than size of the array, it returns all elements of the array.
// Note that if given <size> <= 0 or the array is empty, it returns nil.
// PopRands randomly pops and returns `size` items out of array.
// If the given `size` is greater than size of the array, it returns all elements of the array.
// Note that if given `size` <= 0 or the array is empty, it returns nil.
func (a *SortedIntArray) PopRands(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
@ -247,9 +254,9 @@ func (a *SortedIntArray) PopRands(size int) []int {
return array
}
// PopLefts pops and returns <size> items from the beginning of array.
// If the given <size> is greater than size of the array, it returns all elements of the array.
// Note that if given <size> <= 0 or the array is empty, it returns nil.
// PopLefts pops and returns `size` items from the beginning of array.
// If the given `size` is greater than size of the array, it returns all elements of the array.
// Note that if given `size` <= 0 or the array is empty, it returns nil.
func (a *SortedIntArray) PopLefts(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
@ -266,9 +273,9 @@ func (a *SortedIntArray) PopLefts(size int) []int {
return value
}
// PopRights pops and returns <size> items from the end of array.
// If the given <size> is greater than size of the array, it returns all elements of the array.
// Note that if given <size> <= 0 or the array is empty, it returns nil.
// PopRights pops and returns `size` items from the end of array.
// If the given `size` is greater than size of the array, it returns all elements of the array.
// Note that if given `size` <= 0 or the array is empty, it returns nil.
func (a *SortedIntArray) PopRights(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
@ -290,8 +297,8 @@ func (a *SortedIntArray) PopRights(size int) []int {
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
//
// If <end> is negative, then the offset will start from the end of array.
// If <end> is omitted, then the sequence will have everything from start up
// If `end` is negative, then the offset will start from the end of array.
// If `end` is omitted, then the sequence will have everything from start up
// until the end of the array.
func (a *SortedIntArray) Range(start int, end ...int) []int {
a.mu.RLock()
@ -317,7 +324,7 @@ func (a *SortedIntArray) Range(start int, end ...int) []int {
}
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// by the `offset` and `size` parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
//
// If offset is non-negative, the sequence will start at that offset in the array.
@ -416,7 +423,7 @@ func (a *SortedIntArray) Contains(value int) bool {
return a.Search(value) != -1
}
// Search searches array by <value>, returns the index of <value>,
// Search searches array by `value`, returns the index of `value`,
// or returns -1 if not exists.
func (a *SortedIntArray) Search(value int) (index int) {
if i, r := a.binSearch(value, true); r == 0 {
@ -427,9 +434,9 @@ func (a *SortedIntArray) Search(value int) (index int) {
// Binary search.
// It returns the last compared index and the result.
// If <result> equals to 0, it means the value at <index> is equals to <value>.
// If <result> lesser than 0, it means the value at <index> is lesser than <value>.
// If <result> greater than 0, it means the value at <index> is greater than <value>.
// If `result` equals to 0, it means the value at `index` is equals to `value`.
// If `result` lesser than 0, it means the value at `index` is lesser than `value`.
// If `result` greater than 0, it means the value at `index` is greater than `value`.
func (a *SortedIntArray) binSearch(value int, lock bool) (index int, result int) {
if lock {
a.mu.RLock()
@ -509,7 +516,7 @@ func (a *SortedIntArray) Clear() *SortedIntArray {
return a
}
// LockFunc locks writing by callback function <f>.
// LockFunc locks writing by callback function `f`.
func (a *SortedIntArray) LockFunc(f func(array []int)) *SortedIntArray {
a.mu.Lock()
defer a.mu.Unlock()
@ -517,7 +524,7 @@ func (a *SortedIntArray) LockFunc(f func(array []int)) *SortedIntArray {
return a
}
// RLockFunc locks reading by callback function <f>.
// RLockFunc locks reading by callback function `f`.
func (a *SortedIntArray) RLockFunc(f func(array []int)) *SortedIntArray {
a.mu.RLock()
defer a.mu.RUnlock()
@ -525,8 +532,8 @@ func (a *SortedIntArray) RLockFunc(f func(array []int)) *SortedIntArray {
return a
}
// Merge merges <array> into current array.
// The parameter <array> can be any garray or slice type.
// Merge merges `array` into current array.
// The parameter `array` can be any garray or slice type.
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *SortedIntArray) Merge(array interface{}) *SortedIntArray {
@ -534,7 +541,7 @@ func (a *SortedIntArray) Merge(array interface{}) *SortedIntArray {
}
// Chunk splits an array into multiple arrays,
// the size of each array is determined by <size>.
// the size of each array is determined by `size`.
// The last chunk may contain less than size elements.
func (a *SortedIntArray) Chunk(size int) [][]int {
if size < 1 {
@ -566,7 +573,7 @@ func (a *SortedIntArray) Rand() (value int, found bool) {
return a.array[grand.Intn(len(a.array))], true
}
// Rands randomly returns <size> items from array(no deleting).
// Rands randomly returns `size` items from array(no deleting).
func (a *SortedIntArray) Rands(size int) []int {
a.mu.RLock()
defer a.mu.RUnlock()
@ -580,7 +587,7 @@ func (a *SortedIntArray) Rands(size int) []int {
return array
}
// Join joins array elements with a string <glue>.
// Join joins array elements with a string `glue`.
func (a *SortedIntArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
@ -613,8 +620,8 @@ func (a *SortedIntArray) Iterator(f func(k int, v int) bool) {
a.IteratorAsc(f)
}
// IteratorAsc iterates the array readonly in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
// IteratorAsc iterates the array readonly in ascending order with given callback function `f`.
// If `f` returns true, then it continues iterating; or false to stop.
func (a *SortedIntArray) IteratorAsc(f func(k int, v int) bool) {
a.mu.RLock()
defer a.mu.RUnlock()
@ -625,8 +632,8 @@ func (a *SortedIntArray) IteratorAsc(f func(k int, v int) bool) {
}
}
// IteratorDesc iterates the array readonly in descending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
// IteratorDesc iterates the array readonly in descending order with given callback function `f`.
// If `f` returns true, then it continues iterating; or false to stop.
func (a *SortedIntArray) IteratorDesc(f func(k int, v int) bool) {
a.mu.RLock()
defer a.mu.RUnlock()
@ -707,7 +714,7 @@ func (a *SortedIntArray) FilterEmpty() *SortedIntArray {
return a
}
// Walk applies a user supplied function <f> to every item of array.
// Walk applies a user supplied function `f` to every item of array.
func (a *SortedIntArray) Walk(f func(value int) int) *SortedIntArray {
a.mu.Lock()
defer a.mu.Unlock()

View File

@ -32,14 +32,14 @@ type SortedStrArray struct {
}
// NewSortedStrArray creates and returns an empty sorted array.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func NewSortedStrArray(safe ...bool) *SortedStrArray {
return NewSortedStrArraySize(0, safe...)
}
// NewSortedStrArrayComparator creates and returns an empty sorted array with specified comparator.
// The parameter <safe> is used to specify whether using array in concurrent-safety which is false in default.
// The parameter `safe` is used to specify whether using array in concurrent-safety which is false in default.
func NewSortedStrArrayComparator(comparator func(a, b string) int, safe ...bool) *SortedStrArray {
array := NewSortedStrArray(safe...)
array.comparator = comparator
@ -47,7 +47,7 @@ func NewSortedStrArrayComparator(comparator func(a, b string) int, safe ...bool)
}
// NewSortedStrArraySize create and returns an sorted array with given size and cap.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func NewSortedStrArraySize(cap int, safe ...bool) *SortedStrArray {
return &SortedStrArray{
@ -57,8 +57,8 @@ func NewSortedStrArraySize(cap int, safe ...bool) *SortedStrArray {
}
}
// NewSortedStrArrayFrom creates and returns an sorted array with given slice <array>.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// NewSortedStrArrayFrom creates and returns an sorted array with given slice `array`.
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func NewSortedStrArrayFrom(array []string, safe ...bool) *SortedStrArray {
a := NewSortedStrArraySize(0, safe...)
@ -67,8 +67,8 @@ func NewSortedStrArrayFrom(array []string, safe ...bool) *SortedStrArray {
return a
}
// NewSortedStrArrayFromCopy creates and returns an sorted array from a copy of given slice <array>.
// The parameter <safe> is used to specify whether using array in concurrent-safety,
// NewSortedStrArrayFromCopy creates and returns an sorted array from a copy of given slice `array`.
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func NewSortedStrArrayFromCopy(array []string, safe ...bool) *SortedStrArray {
newArray := make([]string, len(array))
@ -76,7 +76,7 @@ func NewSortedStrArrayFromCopy(array []string, safe ...bool) *SortedStrArray {
return NewSortedStrArrayFrom(newArray, safe...)
}
// SetArray sets the underlying slice array with the given <array>.
// SetArray sets the underlying slice array with the given `array`.
func (a *SortedStrArray) SetArray(array []string) *SortedStrArray {
a.mu.Lock()
defer a.mu.Unlock()
@ -85,8 +85,15 @@ func (a *SortedStrArray) SetArray(array []string) *SortedStrArray {
return a
}
// At returns the value by the specified index.
// If the given `index` is out of range of the array, it returns an empty string.
func (a *SortedStrArray) At(index int) (value string) {
value, _ = a.Get(index)
return
}
// Sort sorts the array in increasing order.
// The parameter <reverse> controls whether sort
// The parameter `reverse` controls whether sort
// in increasing order(default) or decreasing order.
func (a *SortedStrArray) Sort() *SortedStrArray {
a.mu.Lock()
@ -128,7 +135,7 @@ func (a *SortedStrArray) Append(values ...string) *SortedStrArray {
}
// Get returns the value by the specified index.
// If the given <index> is out of range of the array, the <found> is false.
// If the given `index` is out of range of the array, the `found` is false.
func (a *SortedStrArray) Get(index int) (value string, found bool) {
a.mu.RLock()
defer a.mu.RUnlock()
@ -139,7 +146,7 @@ func (a *SortedStrArray) Get(index int) (value string, found bool) {
}
// Remove removes an item by index.
// If the given <index> is out of range of the array, the <found> is false.
// If the given `index` is out of range of the array, the `found` is false.
func (a *SortedStrArray) Remove(index int) (value string, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
@ -180,7 +187,7 @@ func (a *SortedStrArray) RemoveValue(value string) bool {
}
// PopLeft pops and returns an item from the beginning of array.
// Note that if the array is empty, the <found> is false.
// Note that if the array is empty, the `found` is false.
func (a *SortedStrArray) PopLeft() (value string, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
@ -193,7 +200,7 @@ func (a *SortedStrArray) PopLeft() (value string, found bool) {
}
// PopRight pops and returns an item from the end of array.
// Note that if the array is empty, the <found> is false.
// Note that if the array is empty, the `found` is false.
func (a *SortedStrArray) PopRight() (value string, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
@ -207,16 +214,16 @@ func (a *SortedStrArray) PopRight() (value string, found bool) {
}
// PopRand randomly pops and return an item out of array.
// Note that if the array is empty, the <found> is false.
// Note that if the array is empty, the `found` is false.
func (a *SortedStrArray) PopRand() (value string, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
return a.doRemoveWithoutLock(grand.Intn(len(a.array)))
}
// PopRands randomly pops and returns <size> items out of array.
// If the given <size> is greater than size of the array, it returns all elements of the array.
// Note that if given <size> <= 0 or the array is empty, it returns nil.
// PopRands randomly pops and returns `size` items out of array.
// If the given `size` is greater than size of the array, it returns all elements of the array.
// Note that if given `size` <= 0 or the array is empty, it returns nil.
func (a *SortedStrArray) PopRands(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
@ -233,9 +240,9 @@ func (a *SortedStrArray) PopRands(size int) []string {
return array
}
// PopLefts pops and returns <size> items from the beginning of array.
// If the given <size> is greater than size of the array, it returns all elements of the array.
// Note that if given <size> <= 0 or the array is empty, it returns nil.
// PopLefts pops and returns `size` items from the beginning of array.
// If the given `size` is greater than size of the array, it returns all elements of the array.
// Note that if given `size` <= 0 or the array is empty, it returns nil.
func (a *SortedStrArray) PopLefts(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
@ -252,9 +259,9 @@ func (a *SortedStrArray) PopLefts(size int) []string {
return value
}
// PopRights pops and returns <size> items from the end of array.
// If the given <size> is greater than size of the array, it returns all elements of the array.
// Note that if given <size> <= 0 or the array is empty, it returns nil.
// PopRights pops and returns `size` items from the end of array.
// If the given `size` is greater than size of the array, it returns all elements of the array.
// Note that if given `size` <= 0 or the array is empty, it returns nil.
func (a *SortedStrArray) PopRights(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
@ -276,8 +283,8 @@ func (a *SortedStrArray) PopRights(size int) []string {
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
//
// If <end> is negative, then the offset will start from the end of array.
// If <end> is omitted, then the sequence will have everything from start up
// If `end` is negative, then the offset will start from the end of array.
// If `end` is omitted, then the sequence will have everything from start up
// until the end of the array.
func (a *SortedStrArray) Range(start int, end ...int) []string {
a.mu.RLock()
@ -303,7 +310,7 @@ func (a *SortedStrArray) Range(start int, end ...int) []string {
}
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// by the `offset` and `size` parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
//
// If offset is non-negative, the sequence will start at that offset in the array.
@ -418,7 +425,7 @@ func (a *SortedStrArray) ContainsI(value string) bool {
return false
}
// Search searches array by <value>, returns the index of <value>,
// Search searches array by `value`, returns the index of `value`,
// or returns -1 if not exists.
func (a *SortedStrArray) Search(value string) (index int) {
if i, r := a.binSearch(value, true); r == 0 {
@ -429,9 +436,9 @@ func (a *SortedStrArray) Search(value string) (index int) {
// Binary search.
// It returns the last compared index and the result.
// If <result> equals to 0, it means the value at <index> is equals to <value>.
// If <result> lesser than 0, it means the value at <index> is lesser than <value>.
// If <result> greater than 0, it means the value at <index> is greater than <value>.
// If `result` equals to 0, it means the value at `index` is equals to `value`.
// If `result` lesser than 0, it means the value at `index` is lesser than `value`.
// If `result` greater than 0, it means the value at `index` is greater than `value`.
func (a *SortedStrArray) binSearch(value string, lock bool) (index int, result int) {
if lock {
a.mu.RLock()
@ -511,7 +518,7 @@ func (a *SortedStrArray) Clear() *SortedStrArray {
return a
}
// LockFunc locks writing by callback function <f>.
// LockFunc locks writing by callback function `f`.
func (a *SortedStrArray) LockFunc(f func(array []string)) *SortedStrArray {
a.mu.Lock()
defer a.mu.Unlock()
@ -519,7 +526,7 @@ func (a *SortedStrArray) LockFunc(f func(array []string)) *SortedStrArray {
return a
}
// RLockFunc locks reading by callback function <f>.
// RLockFunc locks reading by callback function `f`.
func (a *SortedStrArray) RLockFunc(f func(array []string)) *SortedStrArray {
a.mu.RLock()
defer a.mu.RUnlock()
@ -527,8 +534,8 @@ func (a *SortedStrArray) RLockFunc(f func(array []string)) *SortedStrArray {
return a
}
// Merge merges <array> into current array.
// The parameter <array> can be any garray or slice type.
// Merge merges `array` into current array.
// The parameter `array` can be any garray or slice type.
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *SortedStrArray) Merge(array interface{}) *SortedStrArray {
@ -536,7 +543,7 @@ func (a *SortedStrArray) Merge(array interface{}) *SortedStrArray {
}
// Chunk splits an array into multiple arrays,
// the size of each array is determined by <size>.
// the size of each array is determined by `size`.
// The last chunk may contain less than size elements.
func (a *SortedStrArray) Chunk(size int) [][]string {
if size < 1 {
@ -568,7 +575,7 @@ func (a *SortedStrArray) Rand() (value string, found bool) {
return a.array[grand.Intn(len(a.array))], true
}
// Rands randomly returns <size> items from array(no deleting).
// Rands randomly returns `size` items from array(no deleting).
func (a *SortedStrArray) Rands(size int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
@ -582,7 +589,7 @@ func (a *SortedStrArray) Rands(size int) []string {
return array
}
// Join joins array elements with a string <glue>.
// Join joins array elements with a string `glue`.
func (a *SortedStrArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
@ -615,8 +622,8 @@ func (a *SortedStrArray) Iterator(f func(k int, v string) bool) {
a.IteratorAsc(f)
}
// IteratorAsc iterates the array readonly in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
// IteratorAsc iterates the array readonly in ascending order with given callback function `f`.
// If `f` returns true, then it continues iterating; or false to stop.
func (a *SortedStrArray) IteratorAsc(f func(k int, v string) bool) {
a.mu.RLock()
defer a.mu.RUnlock()
@ -627,8 +634,8 @@ func (a *SortedStrArray) IteratorAsc(f func(k int, v string) bool) {
}
}
// IteratorDesc iterates the array readonly in descending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
// IteratorDesc iterates the array readonly in descending order with given callback function `f`.
// If `f` returns true, then it continues iterating; or false to stop.
func (a *SortedStrArray) IteratorDesc(f func(k int, v string) bool) {
a.mu.RLock()
defer a.mu.RUnlock()
@ -720,7 +727,7 @@ func (a *SortedStrArray) FilterEmpty() *SortedStrArray {
return a
}
// Walk applies a user supplied function <f> to every item of array.
// Walk applies a user supplied function `f` to every item of array.
func (a *SortedStrArray) Walk(f func(value string) string) *SortedStrArray {
a.mu.Lock()
defer a.mu.Unlock()

View File

@ -75,14 +75,14 @@ func ExampleNew() {
func ExampleArray_Iterator() {
array := garray.NewArrayFrom(g.Slice{"a", "b", "c"})
// Iterator is alias of IteratorAsc, which iterates the array readonly in ascending order
// with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
// with given callback function `f`.
// If `f` returns true, then it continues iterating; or false to stop.
array.Iterator(func(k int, v interface{}) bool {
fmt.Println(k, v)
return true
})
// IteratorDesc iterates the array readonly in descending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
// IteratorDesc iterates the array readonly in descending order with given callback function `f`.
// If `f` returns true, then it continues iterating; or false to stop.
array.IteratorDesc(func(k int, v interface{}) bool {
fmt.Println(k, v)
return true
@ -150,7 +150,7 @@ func ExampleArray_Chunk() {
array := garray.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9})
// Chunk splits an array into multiple arrays,
// the size of each array is determined by <size>.
// the size of each array is determined by `size`.
// The last chunk may contain less than size elements.
fmt.Println(array.Chunk(2))

View File

@ -8,7 +8,7 @@
package gpool
import (
"errors"
"github.com/gogf/gf/errors/gerror"
"time"
"github.com/gogf/gf/container/glist"
@ -66,7 +66,7 @@ func New(ttl time.Duration, newFunc NewFunc, expireFunc ...ExpireFunc) *Pool {
// Put puts an item to pool.
func (p *Pool) Put(value interface{}) error {
if p.closed.Val() {
return errors.New("pool is closed")
return gerror.NewCode(gerror.CodeInvalidOperation, "pool is closed")
}
item := &poolItem{
value: value,
@ -117,7 +117,7 @@ func (p *Pool) Get() (interface{}, error) {
if p.NewFunc != nil {
return p.NewFunc()
}
return nil, errors.New("pool is empty")
return nil, gerror.NewCode(gerror.CodeInvalidOperation, "pool is empty")
}
// Size returns the count of available items of pool.

View File

@ -21,7 +21,7 @@ type StrSet struct {
data map[string]struct{}
}
// New create and returns a new set, which contains un-repeated items.
// NewStrSet create and returns a new set, which contains un-repeated items.
// The parameter <safe> is used to specify whether using set in concurrent-safety,
// which is false in default.
func NewStrSet(safe ...bool) *StrSet {

View File

@ -7,92 +7,45 @@
package gvar
import (
"github.com/gogf/gf/internal/empty"
"reflect"
"github.com/gogf/gf/internal/utils"
)
// IsNil checks whether <v> is nil.
// IsNil checks whether `v` is nil.
func (v *Var) IsNil() bool {
return v.Val() == nil
return utils.IsNil(v.Val())
}
// IsEmpty checks whether <v> is empty.
// IsEmpty checks whether `v` is empty.
func (v *Var) IsEmpty() bool {
return empty.IsEmpty(v.Val())
return utils.IsEmpty(v.Val())
}
// IsInt checks whether <v> is type of int.
// IsInt checks whether `v` is type of int.
func (v *Var) IsInt() bool {
switch v.Val().(type) {
case int, *int, int8, *int8, int16, *int16, int32, *int32, int64, *int64:
return true
}
return false
return utils.IsInt(v.Val())
}
// IsUint checks whether <v> is type of uint.
// IsUint checks whether `v` is type of uint.
func (v *Var) IsUint() bool {
switch v.Val().(type) {
case uint, *uint, uint8, *uint8, uint16, *uint16, uint32, *uint32, uint64, *uint64:
return true
}
return false
return utils.IsUint(v.Val())
}
// IsFloat checks whether <v> is type of float.
// IsFloat checks whether `v` is type of float.
func (v *Var) IsFloat() bool {
switch v.Val().(type) {
case float32, *float32, float64, *float64:
return true
}
return false
return utils.IsFloat(v.Val())
}
// IsSlice checks whether <v> is type of slice.
// IsSlice checks whether `v` is type of slice.
func (v *Var) IsSlice() bool {
var (
reflectValue = reflect.ValueOf(v.Val())
reflectKind = reflectValue.Kind()
)
for reflectKind == reflect.Ptr {
reflectValue = reflectValue.Elem()
}
switch reflectKind {
case reflect.Slice, reflect.Array:
return true
}
return false
return utils.IsSlice(v.Val())
}
// IsMap checks whether <v> is type of map.
// IsMap checks whether `v` is type of map.
func (v *Var) IsMap() bool {
var (
reflectValue = reflect.ValueOf(v.Val())
reflectKind = reflectValue.Kind()
)
for reflectKind == reflect.Ptr {
reflectValue = reflectValue.Elem()
}
switch reflectKind {
case reflect.Map:
return true
}
return false
return utils.IsMap(v.Val())
}
// IsStruct checks whether <v> is type of struct.
// IsStruct checks whether `v` is type of struct.
func (v *Var) IsStruct() bool {
var (
reflectValue = reflect.ValueOf(v.Val())
reflectKind = reflectValue.Kind()
)
for reflectKind == reflect.Ptr {
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
}
switch reflectKind {
case reflect.Struct:
return true
}
return false
return utils.IsStruct(v.Val())
}

View File

@ -41,7 +41,7 @@ func (v *Var) MapDeep(tags ...string) map[string]interface{} {
return gconv.MapDeep(v.Val(), tags...)
}
// MapDeep converts and returns <v> as map[string]string recursively.
// MapStrStrDeep converts and returns <v> as map[string]string recursively.
func (v *Var) MapStrStrDeep(tags ...string) map[string]string {
return gconv.MapStrStrDeep(v.Val(), tags...)
}

View File

@ -11,7 +11,7 @@ import (
"bytes"
"crypto/aes"
"crypto/cipher"
"errors"
"github.com/gogf/gf/errors/gerror"
)
var (
@ -63,7 +63,7 @@ func DecryptCBC(cipherText []byte, key []byte, iv ...[]byte) ([]byte, error) {
}
blockSize := block.BlockSize()
if len(cipherText) < blockSize {
return nil, errors.New("cipherText too short")
return nil, gerror.NewCode(gerror.CodeInvalidParameter, "cipherText too short")
}
ivValue := ([]byte)(nil)
if len(iv) > 0 {
@ -72,7 +72,7 @@ func DecryptCBC(cipherText []byte, key []byte, iv ...[]byte) ([]byte, error) {
ivValue = []byte(IVDefaultValue)
}
if len(cipherText)%blockSize != 0 {
return nil, errors.New("cipherText is not a multiple of the block size")
return nil, gerror.NewCode(gerror.CodeInvalidParameter, "cipherText is not a multiple of the block size")
}
blockModel := cipher.NewCBCDecrypter(block, ivValue)
plainText := make([]byte, len(cipherText))
@ -93,22 +93,22 @@ func PKCS5Padding(src []byte, blockSize int) []byte {
func PKCS5UnPadding(src []byte, blockSize int) ([]byte, error) {
length := len(src)
if blockSize <= 0 {
return nil, errors.New("invalid blocklen")
return nil, gerror.NewCode(gerror.CodeInvalidParameter, "invalid blocklen")
}
if length%blockSize != 0 || length == 0 {
return nil, errors.New("invalid data len")
return nil, gerror.NewCode(gerror.CodeInvalidParameter, "invalid data len")
}
unpadding := int(src[length-1])
if unpadding > blockSize || unpadding == 0 {
return nil, errors.New("invalid padding")
return nil, gerror.NewCode(gerror.CodeInvalidParameter, "invalid padding")
}
padding := src[length-unpadding:]
for i := 0; i < unpadding; i++ {
if padding[i] != byte(unpadding) {
return nil, errors.New("invalid padding")
return nil, gerror.NewCode(gerror.CodeInvalidParameter, "invalid padding")
}
}
@ -146,7 +146,7 @@ func DecryptCFB(cipherText []byte, key []byte, unPadding int, iv ...[]byte) ([]b
return nil, err
}
if len(cipherText) < aes.BlockSize {
return nil, errors.New("cipherText too short")
return nil, gerror.NewCode(gerror.CodeInvalidParameter, "cipherText too short")
}
ivValue := ([]byte)(nil)
if len(iv) > 0 {

View File

@ -11,7 +11,7 @@ import (
"bytes"
"crypto/cipher"
"crypto/des"
"errors"
"github.com/gogf/gf/errors/gerror"
)
const (
@ -66,7 +66,7 @@ func DecryptECB(cipherText []byte, key []byte, padding int) ([]byte, error) {
// The length of the <key> should be either 16 or 24 bytes.
func EncryptECBTriple(plainText []byte, key []byte, padding int) ([]byte, error) {
if len(key) != 16 && len(key) != 24 {
return nil, errors.New("key length error")
return nil, gerror.NewCode(gerror.CodeInvalidParameter, "key length error")
}
text, err := Padding(plainText, padding)
@ -100,7 +100,7 @@ func EncryptECBTriple(plainText []byte, key []byte, padding int) ([]byte, error)
// The length of the <key> should be either 16 or 24 bytes.
func DecryptECBTriple(cipherText []byte, key []byte, padding int) ([]byte, error) {
if len(key) != 16 && len(key) != 24 {
return nil, errors.New("key length error")
return nil, gerror.NewCode(gerror.CodeInvalidParameter, "key length error")
}
var newKey []byte
@ -138,7 +138,7 @@ func EncryptCBC(plainText []byte, key []byte, iv []byte, padding int) ([]byte, e
}
if len(iv) != block.BlockSize() {
return nil, errors.New("iv length invalid")
return nil, gerror.NewCode(gerror.CodeInvalidParameter, "iv length invalid")
}
text, err := Padding(plainText, padding)
@ -161,7 +161,7 @@ func DecryptCBC(cipherText []byte, key []byte, iv []byte, padding int) ([]byte,
}
if len(iv) != block.BlockSize() {
return nil, errors.New("iv length invalid")
return nil, gerror.NewCode(gerror.CodeInvalidParameter, "iv length invalid")
}
text := make([]byte, len(cipherText))
@ -179,7 +179,7 @@ func DecryptCBC(cipherText []byte, key []byte, iv []byte, padding int) ([]byte,
// EncryptCBCTriple encrypts <plainText> using TripleDES and CBC mode.
func EncryptCBCTriple(plainText []byte, key []byte, iv []byte, padding int) ([]byte, error) {
if len(key) != 16 && len(key) != 24 {
return nil, errors.New("key length invalid")
return nil, gerror.NewCode(gerror.CodeInvalidParameter, "key length invalid")
}
var newKey []byte
@ -196,7 +196,7 @@ func EncryptCBCTriple(plainText []byte, key []byte, iv []byte, padding int) ([]b
}
if len(iv) != block.BlockSize() {
return nil, errors.New("iv length invalid")
return nil, gerror.NewCode(gerror.CodeInvalidParameter, "iv length invalid")
}
text, err := Padding(plainText, padding)
@ -214,7 +214,7 @@ func EncryptCBCTriple(plainText []byte, key []byte, iv []byte, padding int) ([]b
// DecryptCBCTriple decrypts <cipherText> using TripleDES and CBC mode.
func DecryptCBCTriple(cipherText []byte, key []byte, iv []byte, padding int) ([]byte, error) {
if len(key) != 16 && len(key) != 24 {
return nil, errors.New("key length invalid")
return nil, gerror.NewCode(gerror.CodeInvalidParameter, "key length invalid")
}
var newKey []byte
@ -231,7 +231,7 @@ func DecryptCBCTriple(cipherText []byte, key []byte, iv []byte, padding int) ([]
}
if len(iv) != block.BlockSize() {
return nil, errors.New("iv length invalid")
return nil, gerror.NewCode(gerror.CodeInvalidParameter, "iv length invalid")
}
text := make([]byte, len(cipherText))
@ -262,12 +262,12 @@ func Padding(text []byte, padding int) ([]byte, error) {
switch padding {
case NOPADDING:
if len(text)%8 != 0 {
return nil, errors.New("text length invalid")
return nil, gerror.NewCode(gerror.CodeInvalidParameter, "text length invalid")
}
case PKCS5PADDING:
return PaddingPKCS5(text, 8), nil
default:
return nil, errors.New("padding type error")
return nil, gerror.NewCode(gerror.CodeInvalidParameter, "padding type error")
}
return text, nil
@ -277,12 +277,12 @@ func UnPadding(text []byte, padding int) ([]byte, error) {
switch padding {
case NOPADDING:
if len(text)%8 != 0 {
return nil, errors.New("text length invalid")
return nil, gerror.NewCode(gerror.CodeInvalidParameter, "text length invalid")
}
case PKCS5PADDING:
return UnPaddingPKCS5(text), nil
default:
return nil, errors.New("padding type error")
return nil, gerror.NewCode(gerror.CodeInvalidParameter, "padding type error")
}
return text, nil
}

View File

@ -10,7 +10,6 @@ package gdb
import (
"context"
"database/sql"
"fmt"
"time"
"github.com/gogf/gf/errors/gerror"
@ -38,6 +37,7 @@ type DB interface {
// relational databases but also for NoSQL databases in the future. The name
// "Table" is not proper for that purpose any more.
// Also see Core.Table.
// Deprecated.
Table(tableNameOrStruct ...interface{}) *Model
// Model creates and returns a new ORM model from given schema.
@ -51,6 +51,9 @@ type DB interface {
// Also see Core.Model.
Model(tableNameOrStruct ...interface{}) *Model
// Raw creates and returns a model based on a raw sql not a table.
Raw(rawSql string, args ...interface{}) *Model
// Schema creates and returns a schema.
// Also see Core.Schema.
Schema(schema string) *Schema
@ -66,8 +69,6 @@ type DB interface {
// Ctx is a chaining function, which creates and returns a new DB that is a shallow copy
// of current DB object and with given context in it.
// Note that this returned DB object can be used only once, so do not assign it to
// a global or package variable for long using.
// Also see Core.Ctx.
Ctx(ctx context.Context) DB
@ -83,31 +84,39 @@ type DB interface {
// Common APIs for CURD.
// ===========================================================================
Insert(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.Insert.
InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.InsertIgnore.
InsertAndGetId(table string, data interface{}, batch ...int) (int64, error) // See Core.InsertAndGetId.
Replace(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.Replace.
Save(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.Save.
BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error) // See Core.BatchInsert.
BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error) // See Core.BatchReplace.
BatchSave(table string, list interface{}, batch ...int) (sql.Result, error) // See Core.BatchSave.
Insert(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.Insert.
InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.InsertIgnore.
InsertAndGetId(table string, data interface{}, batch ...int) (int64, error) // See Core.InsertAndGetId.
Replace(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.Replace.
Save(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.Save.
Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) // See Core.Update.
Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) // See Core.Delete.
// ===========================================================================
// Internal APIs for CURD, which can be overwrote for custom CURD implements.
// ===========================================================================
DoGetAll(ctx context.Context, link Link, sql string, args ...interface{}) (result Result, err error) // See Core.DoGetAll.
DoInsert(ctx context.Context, link Link, table string, data List, option DoInsertOption) (result sql.Result, err error) // See Core.DoInsert.
DoUpdate(ctx context.Context, link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) // See Core.DoUpdate.
DoDelete(ctx context.Context, link Link, table string, condition string, args ...interface{}) (result sql.Result, err error) // See Core.DoDelete.
DoQuery(ctx context.Context, link Link, sql string, args ...interface{}) (rows *sql.Rows, err error) // See Core.DoQuery.
DoExec(ctx context.Context, link Link, sql string, args ...interface{}) (result sql.Result, err error) // See Core.DoExec.
DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) // See Core.DoCommit.
DoPrepare(ctx context.Context, link Link, sql string) (*Stmt, error) // See Core.DoPrepare.
// ===========================================================================
// Query APIs for convenience purpose.
// ===========================================================================
GetAll(sql string, args ...interface{}) (Result, error) // See Core.GetAll.
GetOne(sql string, args ...interface{}) (Record, error) // See Core.GetOne.
GetValue(sql string, args ...interface{}) (Value, error) // See Core.GetValue.
GetArray(sql string, args ...interface{}) ([]Value, error) // See Core.GetArray.
GetCount(sql string, args ...interface{}) (int, error) // See Core.GetCount.
GetStruct(objPointer interface{}, sql string, args ...interface{}) error // See Core.GetStruct.
GetStructs(objPointerSlice interface{}, sql string, args ...interface{}) error // See Core.GetStructs.
GetScan(objPointer interface{}, sql string, args ...interface{}) error // See Core.GetScan.
GetAll(sql string, args ...interface{}) (Result, error) // See Core.GetAll.
GetOne(sql string, args ...interface{}) (Record, error) // See Core.GetOne.
GetValue(sql string, args ...interface{}) (Value, error) // See Core.GetValue.
GetArray(sql string, args ...interface{}) ([]Value, error) // See Core.GetArray.
GetCount(sql string, args ...interface{}) (int, error) // See Core.GetCount.
GetScan(objPointer interface{}, sql string, args ...interface{}) error // See Core.GetScan.
Union(unions ...*Model) *Model // See Core.Union.
UnionAll(unions ...*Model) *Model // See Core.UnionAll.
// ===========================================================================
// Master/Slave specification support.
@ -143,8 +152,8 @@ type DB interface {
GetGroup() string // See Core.GetGroup.
SetDryRun(enabled bool) // See Core.SetDryRun.
GetDryRun() bool // See Core.GetDryRun.
SetLogger(logger *glog.Logger) // See Core.SetLogger.
GetLogger() *glog.Logger // See Core.GetLogger.
SetLogger(logger Logger) // See Core.SetLogger.
GetLogger() Logger // See Core.GetLogger.
GetConfig() *ConfigNode // See Core.GetConfig.
SetMaxIdleConnCount(n int) // See Core.SetMaxIdleConnCount.
SetMaxOpenConnCount(n int) // See Core.SetMaxOpenConnCount.
@ -154,19 +163,12 @@ type DB interface {
// Utility methods.
// ===========================================================================
GetCtx() context.Context // See Core.GetCtx.
GetCore() *Core // See Core.GetCore
GetChars() (charLeft string, charRight string) // See Core.GetChars.
Tables(ctx context.Context, schema ...string) (tables []string, err error) // See Core.Tables.
TableFields(ctx context.Context, link Link, table string, schema ...string) (map[string]*TableField, error) // See Core.TableFields.
FilteredLinkInfo() string // See Core.FilteredLinkInfo.
// HandleSqlBeforeCommit is a hook function, which deals with the sql string before
// it's committed to underlying driver. The parameter `link` specifies the current
// database connection operation object. You can modify the sql string `sql` and its
// arguments `args` as you wish before they're committed to driver.
// Also see Core.HandleSqlBeforeCommit.
HandleSqlBeforeCommit(ctx context.Context, link Link, sql string, args []interface{}) (string, []interface{})
GetCtx() context.Context // See Core.GetCtx.
GetCore() *Core // See Core.GetCore
GetChars() (charLeft string, charRight string) // See Core.GetChars.
Tables(ctx context.Context, schema ...string) (tables []string, err error) // See Core.Tables.
TableFields(ctx context.Context, table string, schema ...string) (map[string]*TableField, error) // See Core.TableFields.
FilteredLink() string // FilteredLink is used for filtering sensitive information in `Link` configuration before output it to tracing server.
}
// Core is the base struct for database management.
@ -177,7 +179,7 @@ type Core struct {
debug *gtype.Bool // Enable debug mode for the database, which can be changed in runtime.
cache *gcache.Cache // Cache manager, SQL result cache only.
schema *gtype.String // Custom schema for this object.
logger *glog.Logger // Logger.
logger Logger // Logger for logging functionality.
config *ConfigNode // Current config node.
}
@ -198,6 +200,12 @@ type Link interface {
IsTransaction() bool
}
// Logger is the logging interface for DB.
type Logger interface {
Error(ctx context.Context, s string)
Debug(ctx context.Context, s string)
}
// Sql is the sql recording struct.
type Sql struct {
Sql string // SQL string(may contain reserved char '?').
@ -211,6 +219,14 @@ type Sql struct {
IsTransaction bool // IsTransaction marks whether this sql is executed in transaction.
}
// DoInsertOption is the input struct for function DoInsert.
type DoInsertOption struct {
OnDuplicateStr string
OnDuplicateMap map[string]interface{}
InsertOption int // Insert operation.
BatchCount int // Batch count for batch inserting.
}
// TableField is the struct for table field.
type TableField struct {
Index int // For ordering purpose as map is unordered.
@ -239,6 +255,10 @@ type (
)
const (
queryTypeNormal = 0
queryTypeCount = 1
unionTypeNormal = 0
unionTypeAll = 1
insertOptionDefault = 0
insertOptionReplace = 1
insertOptionSave = 2
@ -250,12 +270,12 @@ const (
ctxTimeoutTypeExec = iota
ctxTimeoutTypeQuery
ctxTimeoutTypePrepare
commandEnvKeyForDryRun = "gf.gdb.dryrun"
ctxStrictKeyName = "gf.gdb.CtxStrictEnabled"
ctxStrictErrorStr = "context is required for database operation, did you missing call function Ctx"
)
var (
// ErrNoRows is alias of sql.ErrNoRows.
ErrNoRows = sql.ErrNoRows
// instances is the management map for instances.
instances = gmap.NewStrAnyMap(true)
@ -294,7 +314,7 @@ var (
func init() {
// allDryRun is initialized from environment or command options.
allDryRun = gcmd.GetOptWithEnv("gf.gdb.dryrun", false).Bool()
allDryRun = gcmd.GetOptWithEnv(commandEnvKeyForDryRun, false).Bool()
}
// Register registers custom database driver to gdb.
@ -315,7 +335,10 @@ func New(group ...string) (db DB, err error) {
defer configs.RUnlock()
if len(configs.config) < 1 {
return nil, gerror.New("database configuration is empty, please set the database configuration before using")
return nil, gerror.NewCode(
gerror.CodeInvalidConfiguration,
"database configuration is empty, please set the database configuration before using",
)
}
if _, ok := configs.config[groupName]; ok {
if node, err := getConfigNodeByGroup(groupName, true); err == nil {
@ -324,7 +347,7 @@ func New(group ...string) (db DB, err error) {
debug: gtype.NewBool(),
cache: gcache.New(),
schema: gtype.NewString(),
logger: glog.New(),
logger: LoggerImp{glog.New()},
config: node,
}
if v, ok := driverMap[node.Type]; ok {
@ -334,7 +357,8 @@ func New(group ...string) (db DB, err error) {
}
return c.db, nil
} else {
return nil, gerror.Newf(
return nil, gerror.NewCodef(
gerror.CodeInvalidConfiguration,
`cannot find database driver for specified database type "%s", did you misspell type name "%s" or forget importing the database driver?`,
node.Type, node.Type,
)
@ -343,7 +367,8 @@ func New(group ...string) (db DB, err error) {
return nil, err
}
} else {
return nil, gerror.Newf(
return nil, gerror.NewCodef(
gerror.CodeInvalidConfiguration,
`database configuration node "%s" is not found, did you misspell group name "%s" or miss the database configuration?`,
groupName, groupName,
)
@ -386,7 +411,7 @@ func getConfigNodeByGroup(group string, master bool) (*ConfigNode, error) {
}
}
if len(masterList) < 1 {
return nil, gerror.New("at least one master node configuration's need to make sense")
return nil, gerror.NewCode(gerror.CodeInvalidConfiguration, "at least one master node configuration's need to make sense")
}
if len(slaveList) < 1 {
slaveList = masterList
@ -397,7 +422,7 @@ func getConfigNodeByGroup(group string, master bool) (*ConfigNode, error) {
return getConfigNodeByWeight(slaveList), nil
}
} else {
return nil, gerror.New(fmt.Sprintf("empty database configuration for item name '%s'", group))
return nil, gerror.NewCodef(gerror.CodeInvalidConfiguration, "empty database configuration for item name '%s'", group)
}
}
@ -466,14 +491,16 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error
// Cache the underlying connection pool object by node.
v, _ := internalCache.GetOrSetFuncLock(node.String(), func() (interface{}, error) {
intlog.Printf(
c.db.GetCtx(),
`open new connection, master:%#v, config:%#v, node:%#v`,
master, c.config, node,
)
defer func() {
if err != nil {
intlog.Printf(`open new connection failed: %v, %#v`, err, node)
intlog.Printf(c.db.GetCtx(), `open new connection failed: %v, %#v`, err, node)
} else {
intlog.Printf(
c.db.GetCtx(),
`open new connection success, master:%#v, config:%#v, node:%#v`,
master, c.config, node,
)

View File

@ -40,6 +40,7 @@ func (c *Core) Ctx(ctx context.Context) DB {
if c.ctx != nil {
return c.db
}
ctx = context.WithValue(ctx, ctxStrictKeyName, 1)
// It makes a shallow copy of current db and changes its context for next chaining operation.
var (
err error
@ -88,7 +89,7 @@ func (c *Core) GetCtxTimeout(timeoutType int, ctx context.Context) (context.Cont
return context.WithTimeout(ctx, c.db.GetConfig().PrepareTimeout)
}
default:
panic(gerror.Newf("invalid context timeout type: %d", timeoutType))
panic(gerror.NewCodef(gerror.CodeInvalidParameter, "invalid context timeout type: %d", timeoutType))
}
return ctx, func() {}
}
@ -119,12 +120,12 @@ func (c *Core) Slave(schema ...string) (*sql.DB, error) {
// GetAll queries and returns data records from database.
func (c *Core) GetAll(sql string, args ...interface{}) (Result, error) {
return c.DoGetAll(c.GetCtx(), nil, sql, args...)
return c.db.DoGetAll(c.GetCtx(), nil, sql, args...)
}
// DoGetAll queries and returns data records from database.
func (c *Core) DoGetAll(ctx context.Context, link Link, sql string, args ...interface{}) (result Result, err error) {
rows, err := c.DoQuery(ctx, link, sql, args...)
rows, err := c.db.DoQuery(ctx, link, sql, args...)
if err != nil || rows == nil {
return nil, err
}
@ -147,7 +148,7 @@ func (c *Core) GetOne(sql string, args ...interface{}) (Record, error) {
// GetArray queries and returns data values as slice from database.
// Note that if there are multiple columns in the result, it returns just one column values randomly.
func (c *Core) GetArray(sql string, args ...interface{}) ([]Value, error) {
all, err := c.DoGetAll(c.GetCtx(), nil, sql, args...)
all, err := c.db.DoGetAll(c.GetCtx(), nil, sql, args...)
if err != nil {
return nil, err
}
@ -189,9 +190,9 @@ func (c *Core) GetScan(pointer interface{}, sql string, args ...interface{}) err
k = t.Elem().Kind()
switch k {
case reflect.Array, reflect.Slice:
return c.db.GetStructs(pointer, sql, args...)
return c.db.GetCore().GetStructs(pointer, sql, args...)
case reflect.Struct:
return c.db.GetStruct(pointer, sql, args...)
return c.db.GetCore().GetStruct(pointer, sql, args...)
}
return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k)
}
@ -224,6 +225,39 @@ func (c *Core) GetCount(sql string, args ...interface{}) (int, error) {
return value.Int(), nil
}
// Union does "(SELECT xxx FROM xxx) UNION (SELECT xxx FROM xxx) ..." statement.
func (c *Core) Union(unions ...*Model) *Model {
return c.doUnion(unionTypeNormal, unions...)
}
// UnionAll does "(SELECT xxx FROM xxx) UNION ALL (SELECT xxx FROM xxx) ..." statement.
func (c *Core) UnionAll(unions ...*Model) *Model {
return c.doUnion(unionTypeAll, unions...)
}
func (c *Core) doUnion(unionType int, unions ...*Model) *Model {
var (
unionTypeStr string
composedSqlStr string
composedArgs = make([]interface{}, 0)
)
if unionType == unionTypeAll {
unionTypeStr = "UNION ALL"
} else {
unionTypeStr = "UNION"
}
for _, v := range unions {
sqlWithHolder, holderArgs := v.getFormattedSqlAndArgs(queryTypeNormal, false)
if composedSqlStr == "" {
composedSqlStr += fmt.Sprintf(`(%s)`, sqlWithHolder)
} else {
composedSqlStr += fmt.Sprintf(` %s (%s)`, unionTypeStr, sqlWithHolder)
}
composedArgs = append(composedArgs, holderArgs...)
}
return c.db.Raw(composedSqlStr, composedArgs...)
}
// PingMaster pings the master node to check authentication or keeps the connection alive.
func (c *Core) PingMaster() error {
if master, err := c.db.Master(); err != nil {
@ -319,7 +353,7 @@ func (c *Core) Save(table string, data interface{}, batch ...int) (sql.Result, e
return c.Model(table).Data(data).Save()
}
// DoInsert inserts or updates data for given table.
// DoInsert inserts or updates data forF given table.
// This function is usually used for custom interface definition, you do not need call it manually.
// The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc.
// Eg:
@ -331,181 +365,15 @@ func (c *Core) Save(table string, data interface{}, batch ...int) (sql.Result, e
// 1: replace: if there's unique/primary key in the data, it deletes it from table and inserts a new one;
// 2: save: if there's unique/primary key in the data, it updates it or else inserts a new one;
// 3: ignore: if there's unique/primary key in the data, it ignores the inserting;
func (c *Core) DoInsert(ctx context.Context, link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error) {
table = c.QuotePrefixTableName(table)
func (c *Core) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) {
var (
fields []string
values []string
params []interface{}
dataMap Map
reflectValue = reflect.ValueOf(data)
reflectKind = reflectValue.Kind()
keys []string // Field names.
values []string // Value holder string array, like: (?,?,?)
params []interface{} // Values that will be committed to underlying database driver.
onDuplicateStr string // onDuplicateStr is used in "ON DUPLICATE KEY UPDATE" statement.
)
if reflectKind == reflect.Ptr {
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
}
switch reflectKind {
case reflect.Slice, reflect.Array:
return c.DoBatchInsert(ctx, link, table, data, option, batch...)
case reflect.Struct:
if _, ok := data.(apiInterfaces); ok {
return c.DoBatchInsert(ctx, link, table, data, option, batch...)
} else {
dataMap = ConvertDataForTableRecord(data)
}
case reflect.Map:
dataMap = ConvertDataForTableRecord(data)
default:
return result, gerror.New(fmt.Sprint("unsupported data type:", reflectKind))
}
if len(dataMap) == 0 {
return nil, gerror.New("data cannot be empty")
}
var (
charL, charR = c.db.GetChars()
operation = GetInsertOperationByOption(option)
updateStr = ""
)
for k, v := range dataMap {
fields = append(fields, charL+k+charR)
if s, ok := v.(Raw); ok {
values = append(values, gconv.String(s))
} else {
values = append(values, "?")
params = append(params, v)
}
}
if option == insertOptionSave {
for k, _ := range dataMap {
// If it's SAVE operation,
// do not automatically update the creating time.
if c.isSoftCreatedFiledName(k) {
continue
}
if len(updateStr) > 0 {
updateStr += ","
}
updateStr += fmt.Sprintf(
"%s%s%s=VALUES(%s%s%s)",
charL, k, charR,
charL, k, charR,
)
}
updateStr = fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", updateStr)
}
if link == nil {
if link, err = c.MasterLink(); err != nil {
return nil, err
}
}
return c.DoExec(ctx, link, fmt.Sprintf(
"%s INTO %s(%s) VALUES(%s) %s",
operation, table, strings.Join(fields, ","),
strings.Join(values, ","), updateStr,
), params...)
}
// BatchInsert batch inserts data.
// The parameter `list` must be type of slice of map or struct.
func (c *Core) BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error) {
if len(batch) > 0 {
return c.Model(table).Data(list).Batch(batch[0]).Insert()
}
return c.Model(table).Data(list).Insert()
}
// BatchInsertIgnore batch inserts data with ignore option.
// The parameter `list` must be type of slice of map or struct.
func (c *Core) BatchInsertIgnore(table string, list interface{}, batch ...int) (sql.Result, error) {
if len(batch) > 0 {
return c.Model(table).Data(list).Batch(batch[0]).InsertIgnore()
}
return c.Model(table).Data(list).InsertIgnore()
}
// BatchReplace batch replaces data.
// The parameter `list` must be type of slice of map or struct.
func (c *Core) BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error) {
if len(batch) > 0 {
return c.Model(table).Data(list).Batch(batch[0]).Replace()
}
return c.Model(table).Data(list).Replace()
}
// BatchSave batch replaces data.
// The parameter `list` must be type of slice of map or struct.
func (c *Core) BatchSave(table string, list interface{}, batch ...int) (sql.Result, error) {
if len(batch) > 0 {
return c.Model(table).Data(list).Batch(batch[0]).Save()
}
return c.Model(table).Data(list).Save()
}
// DoBatchInsert batch inserts/replaces/saves data.
// This function is usually used for custom interface definition, you do not need call it manually.
func (c *Core) DoBatchInsert(ctx context.Context, link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) {
table = c.QuotePrefixTableName(table)
var (
keys []string // Field names.
values []string // Value holder string array, like: (?,?,?)
params []interface{} // Values that will be committed to underlying database driver.
listMap List // The data list that passed from caller.
)
switch value := list.(type) {
case Result:
listMap = value.List()
case Record:
listMap = List{value.Map()}
case List:
listMap = value
case Map:
listMap = List{value}
default:
var (
rv = reflect.ValueOf(list)
kind = rv.Kind()
)
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
// If it's slice type, it then converts it to List type.
case reflect.Slice, reflect.Array:
listMap = make(List, rv.Len())
for i := 0; i < rv.Len(); i++ {
listMap[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
}
case reflect.Map:
listMap = List{ConvertDataForTableRecord(value)}
case reflect.Struct:
if v, ok := value.(apiInterfaces); ok {
var (
array = v.Interfaces()
list = make(List, len(array))
)
for i := 0; i < len(array); i++ {
list[i] = ConvertDataForTableRecord(array[i])
}
listMap = list
} else {
listMap = List{ConvertDataForTableRecord(value)}
}
default:
return result, gerror.New(fmt.Sprint("unsupported list type:", kind))
}
}
if len(listMap) < 1 {
return result, gerror.New("data list cannot be empty")
}
if link == nil {
if link, err = c.MasterLink(); err != nil {
return
}
}
// Handle the field names and place holders.
for k, _ := range listMap[0] {
for k, _ := range list[0] {
keys = append(keys, k)
}
// Prepare the batch result pointer.
@ -513,54 +381,35 @@ func (c *Core) DoBatchInsert(ctx context.Context, link Link, table string, list
charL, charR = c.db.GetChars()
batchResult = new(SqlResult)
keysStr = charL + strings.Join(keys, charR+","+charL) + charR
operation = GetInsertOperationByOption(option)
updateStr = ""
operation = GetInsertOperationByOption(option.InsertOption)
)
if option == insertOptionSave {
for _, k := range keys {
// If it's SAVE operation,
// do not automatically update the creating time.
if c.isSoftCreatedFiledName(k) {
continue
}
if len(updateStr) > 0 {
updateStr += ","
}
updateStr += fmt.Sprintf(
"%s%s%s=VALUES(%s%s%s)",
charL, k, charR,
charL, k, charR,
)
}
updateStr = fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", updateStr)
}
batchNum := defaultBatchNumber
if len(batch) > 0 && batch[0] > 0 {
batchNum = batch[0]
if option.InsertOption == insertOptionSave {
onDuplicateStr = c.formatOnDuplicate(keys, option)
}
var (
listMapLen = len(listMap)
listLength = len(list)
valueHolder = make([]string, 0)
)
for i := 0; i < listMapLen; i++ {
for i := 0; i < listLength; i++ {
values = values[:0]
// Note that the map type is unordered,
// so it should use slice+key to retrieve the value.
for _, k := range keys {
if s, ok := listMap[i][k].(Raw); ok {
if s, ok := list[i][k].(Raw); ok {
values = append(values, gconv.String(s))
} else {
values = append(values, "?")
params = append(params, listMap[i][k])
params = append(params, list[i][k])
}
}
valueHolder = append(valueHolder, "("+gstr.Join(values, ",")+")")
if len(valueHolder) == batchNum || (i == listMapLen-1 && len(valueHolder) > 0) {
r, err := c.DoExec(ctx, link, fmt.Sprintf(
// Batch package checks: It meets the batch number or it is the last element.
if len(valueHolder) == option.BatchCount || (i == listLength-1 && len(valueHolder) > 0) {
r, err := c.db.DoExec(ctx, link, fmt.Sprintf(
"%s INTO %s(%s) VALUES%s %s",
operation, table, keysStr,
operation, c.QuotePrefixTableName(table), keysStr,
gstr.Join(valueHolder, ","),
updateStr,
onDuplicateStr,
), params...)
if err != nil {
return r, err
@ -578,6 +427,52 @@ func (c *Core) DoBatchInsert(ctx context.Context, link Link, table string, list
return batchResult, nil
}
func (c *Core) formatOnDuplicate(columns []string, option DoInsertOption) string {
var (
onDuplicateStr string
)
if option.OnDuplicateStr != "" {
onDuplicateStr = option.OnDuplicateStr
} else if len(option.OnDuplicateMap) > 0 {
for k, v := range option.OnDuplicateMap {
if len(onDuplicateStr) > 0 {
onDuplicateStr += ","
}
switch v.(type) {
case Raw, *Raw:
onDuplicateStr += fmt.Sprintf(
"%s=%s",
c.QuoteWord(k),
v,
)
default:
onDuplicateStr += fmt.Sprintf(
"%s=VALUES(%s)",
c.QuoteWord(k),
c.QuoteWord(gconv.String(v)),
)
}
}
} else {
for _, column := range columns {
// If it's SAVE operation,
// do not automatically update the creating time.
if c.isSoftCreatedFilledName(column) {
continue
}
if len(onDuplicateStr) > 0 {
onDuplicateStr += ","
}
onDuplicateStr += fmt.Sprintf(
"%s=VALUES(%s)",
c.QuoteWord(column),
c.QuoteWord(column),
)
}
}
return fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", onDuplicateStr)
}
// Update does "UPDATE ... " statement for the table.
//
// The parameter `data` can be type of string/map/gmap/struct/*struct, etc.
@ -615,29 +510,34 @@ func (c *Core) DoUpdate(ctx context.Context, link Link, table string, data inter
switch kind {
case reflect.Map, reflect.Struct:
var (
fields []string
dataMap = ConvertDataForTableRecord(data)
fields []string
dataMap = ConvertDataForTableRecord(data)
counterHandler = func(column string, counter Counter) {
if counter.Value != 0 {
var (
column = c.QuoteWord(column)
columnRef = c.QuoteWord(counter.Field)
columnVal = counter.Value
operator = "+"
)
if columnVal < 0 {
operator = "-"
columnVal = -columnVal
}
fields = append(fields, fmt.Sprintf("%s=%s%s?", column, columnRef, operator))
params = append(params, columnVal)
}
}
)
for k, v := range dataMap {
switch value := v.(type) {
case *Counter:
if value.Value != 0 {
column := k
if value.Field != "" {
column = c.QuoteWord(value.Field)
}
fields = append(fields, fmt.Sprintf("%s=%s+?", column, column))
params = append(params, value.Value)
}
counterHandler(k, *value)
case Counter:
if value.Value != 0 {
column := k
if value.Field != "" {
column = c.QuoteWord(value.Field)
}
fields = append(fields, fmt.Sprintf("%s=%s+?", column, column))
params = append(params, value.Value)
}
counterHandler(k, value)
default:
if s, ok := v.(Raw); ok {
fields = append(fields, c.QuoteWord(k)+"="+gconv.String(s))
@ -648,11 +548,12 @@ func (c *Core) DoUpdate(ctx context.Context, link Link, table string, data inter
}
}
updates = strings.Join(fields, ",")
default:
updates = gconv.String(data)
}
if len(updates) == 0 {
return nil, gerror.New("data cannot be empty")
return nil, gerror.NewCode(gerror.CodeMissingParameter, "data cannot be empty")
}
if len(params) > 0 {
args = append(params, args...)
@ -663,7 +564,7 @@ func (c *Core) DoUpdate(ctx context.Context, link Link, table string, data inter
return nil, err
}
}
return c.DoExec(ctx, link, fmt.Sprintf("UPDATE %s SET %s%s", table, updates, condition), args...)
return c.db.DoExec(ctx, link, fmt.Sprintf("UPDATE %s SET %s%s", table, updates, condition), args...)
}
// Delete does "DELETE FROM ... " statement for the table.
@ -690,7 +591,7 @@ func (c *Core) DoDelete(ctx context.Context, link Link, table string, condition
}
}
table = c.QuotePrefixTableName(table)
return c.DoExec(ctx, link, fmt.Sprintf("DELETE FROM %s%s", table, condition), args...)
return c.db.DoExec(ctx, link, fmt.Sprintf("DELETE FROM %s%s", table, condition), args...)
}
// convertRowsToResult converts underlying data record type sql.Rows to Result type.
@ -711,7 +612,7 @@ func (c *Core) convertRowsToResult(rows *sql.Rows) (Result, error) {
}
var (
values = make([]interface{}, len(columnNames))
records = make(Result, 0)
result = make(Result, 0)
scanArgs = make([]interface{}, len(values))
)
for i := range values {
@ -719,22 +620,22 @@ func (c *Core) convertRowsToResult(rows *sql.Rows) (Result, error) {
}
for {
if err := rows.Scan(scanArgs...); err != nil {
return records, err
return result, err
}
row := make(Record)
record := Record{}
for i, value := range values {
if value == nil {
row[columnNames[i]] = gvar.New(nil)
record[columnNames[i]] = gvar.New(nil)
} else {
row[columnNames[i]] = gvar.New(c.convertFieldValueToLocalValue(value, columnTypes[i]))
record[columnNames[i]] = gvar.New(c.convertFieldValueToLocalValue(value, columnTypes[i]))
}
}
records = append(records, row)
result = append(result, record)
if !rows.Next() {
break
}
}
return records, nil
return result, nil
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
@ -758,9 +659,9 @@ func (c *Core) writeSqlToLogger(ctx context.Context, sql *Sql) {
s := fmt.Sprintf("[%3d ms] [%s] %s%s", sql.End-sql.Start, sql.Group, transactionIdStr, sql.Format)
if sql.Error != nil {
s += "\nError: " + sql.Error.Error()
c.logger.Ctx(ctx).Error(s)
c.logger.Error(ctx, s)
} else {
c.logger.Ctx(ctx).Debug(s)
c.logger.Debug(ctx, s)
}
}
@ -778,8 +679,8 @@ func (c *Core) HasTable(name string) (bool, error) {
return false, nil
}
// isSoftCreatedFiledName checks and returns whether given filed name is an automatic-filled created time.
func (c *Core) isSoftCreatedFiledName(fieldName string) bool {
// isSoftCreatedFilledName checks and returns whether given filed name is an automatic-filled created time.
func (c *Core) isSoftCreatedFilledName(fieldName string) bool {
if fieldName == "" {
return false
}

View File

@ -12,8 +12,6 @@ import (
"time"
"github.com/gogf/gf/os/gcache"
"github.com/gogf/gf/os/glog"
)
// Config is the configuration management object.
@ -30,13 +28,14 @@ type ConfigNode struct {
Pass string `json:"pass"` // Authentication password.
Name string `json:"name"` // Default used database name.
Type string `json:"type"` // Database type: mysql, sqlite, mssql, pgsql, oracle.
Link string `json:"link"` // (Optional) Custom link information, when it is used, configuration Host/Port/User/Pass/Name are ignored.
Role string `json:"role"` // (Optional, "master" in default) Node role, used for master-slave mode: master, slave.
Debug bool `json:"debug"` // (Optional) Debug mode enables debug information logging and output.
Prefix string `json:"prefix"` // (Optional) Table prefix.
DryRun bool `json:"dryRun"` // (Optional) Dry run, which does SELECT but no INSERT/UPDATE/DELETE statements.
Weight int `json:"weight"` // (Optional) Weight for load balance calculating, it's useless if there's just one node.
Charset string `json:"charset"` // (Optional, "utf8mb4" in default) Custom charset when operating on database.
LinkInfo string `json:"link"` // (Optional) Custom link information, when it is used, configuration Host/Port/User/Pass/Name are ignored.
Timezone string `json:"timezone"` // (Optional) Sets the time zone for displaying and interpreting time stamps.
MaxIdleConnCount int `json:"maxIdle"` // (Optional) Max idle connection configuration for underlying connection pool.
MaxOpenConnCount int `json:"maxOpen"` // (Optional) Max open connection configuration for underlying connection pool.
MaxConnLifeTime time.Duration `json:"maxLifeTime"` // (Optional) Max amount of time a connection may be idle before being closed.
@ -48,6 +47,7 @@ type ConfigNode struct {
UpdatedAt string `json:"updatedAt"` // (Optional) The filed name of table for automatic-filled updated datetime.
DeletedAt string `json:"deletedAt"` // (Optional) The filed name of table for automatic-filled updated datetime.
TimeMaintainDisabled bool `json:"timeMaintainDisabled"` // (Optional) Disable the automatic time maintaining feature.
CtxStrict bool `json:"ctxStrict"` // (Optional) Strictly require context input for all database operations.
}
const (
@ -133,12 +133,12 @@ func IsConfigured() bool {
}
// SetLogger sets the logger for orm.
func (c *Core) SetLogger(logger *glog.Logger) {
func (c *Core) SetLogger(logger Logger) {
c.logger = logger
}
// GetLogger returns the logger of the orm.
func (c *Core) GetLogger() *glog.Logger {
func (c *Core) GetLogger() Logger {
return c.logger
}
@ -186,7 +186,7 @@ func (node *ConfigNode) String() string {
node.MaxIdleConnCount,
node.MaxOpenConnCount,
node.MaxConnLifeTime,
node.LinkInfo,
node.Link,
)
}

View File

@ -0,0 +1,27 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gdb
import (
"context"
"github.com/gogf/gf/os/glog"
)
// LoggerImp is the default implementation of interface Logger for DB.
type LoggerImp struct {
*glog.Logger
}
// Error implements function Error for interface Logger.
func (l LoggerImp) Error(ctx context.Context, s string) {
l.Ctx(ctx).Error(s)
}
// Debug implements function Debug for interface Logger.
func (l LoggerImp) Debug(ctx context.Context, s string) {
l.Ctx(ctx).Debug(s)
}

View File

@ -151,7 +151,7 @@ func (c *Core) convertFieldValueToLocalValue(fieldValue interface{}, fieldType s
// mappingAndFilterData automatically mappings the map key to table field and removes
// all key-value pairs that are not the field of given table.
func (c *Core) mappingAndFilterData(schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error) {
if fieldsMap, err := c.db.TableFields(c.GetCtx(), nil, table, schema); err == nil {
if fieldsMap, err := c.db.TableFields(c.GetCtx(), table, schema); err == nil {
fieldsKeyMap := make(map[string]interface{}, len(fieldsMap))
for k, _ := range fieldsMap {
fieldsKeyMap[k] = nil

View File

@ -12,7 +12,6 @@ import (
"fmt"
"github.com/gogf/gf"
"github.com/gogf/gf/net/gtrace"
"github.com/gogf/gf/os/gcmd"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
@ -34,19 +33,9 @@ const (
tracingEventDbExecutionType = "db.execution.type"
)
var (
// tracingInternal enables tracing for internal type spans.
// It's true in default.
tracingInternal = true
)
func init() {
tracingInternal = gcmd.GetOptWithEnv("gf.tracing.internal", true).Bool()
}
// addSqlToTracing adds sql information to tracer if it's enabled.
func (c *Core) addSqlToTracing(ctx context.Context, sql *Sql) {
if !tracingInternal || !gtrace.IsActivated(ctx) {
if !gtrace.IsTracingInternal() || !gtrace.IsActivated(ctx) {
return
}
tr := otel.GetTracerProvider().Tracer(
@ -76,8 +65,8 @@ func (c *Core) addSqlToTracing(ctx context.Context, sql *Sql) {
if c.db.GetConfig().User != "" {
labels = append(labels, attribute.String(tracingAttrDbUser, c.db.GetConfig().User))
}
if filteredLinkInfo := c.db.FilteredLinkInfo(); filteredLinkInfo != "" {
labels = append(labels, attribute.String(tracingAttrDbLink, c.db.FilteredLinkInfo()))
if filteredLink := c.db.FilteredLink(); filteredLink != "" {
labels = append(labels, attribute.String(tracingAttrDbLink, c.db.FilteredLink()))
}
if group := c.db.GetGroup(); group != "" {
labels = append(labels, attribute.String(tracingAttrDbGroup, group))

View File

@ -28,6 +28,7 @@ type TX struct {
master *sql.DB // master is the raw and underlying database manager.
transactionId string // transactionId is an unique id generated by this object for this transaction.
transactionCount int // transactionCount marks the times that Begins.
isClosed bool // isClosed marks this transaction has already been committed or rolled back.
}
const (
@ -162,6 +163,9 @@ func TXFromCtx(ctx context.Context, group string) *TX {
v := ctx.Value(transactionKeyForContext(group))
if v != nil {
tx := v.(*TX)
if tx.IsClosed() {
return nil
}
tx.ctx = ctx
return tx
}
@ -210,6 +214,7 @@ func (tx *TX) Commit() error {
IsTransaction: true,
}
)
tx.isClosed = true
tx.db.GetCore().addSqlToTracing(tx.ctx, sqlObj)
if tx.db.GetDebug() {
tx.db.GetCore().writeSqlToLogger(tx.ctx, sqlObj)
@ -243,6 +248,7 @@ func (tx *TX) Rollback() error {
IsTransaction: true,
}
)
tx.isClosed = true
tx.db.GetCore().addSqlToTracing(tx.ctx, sqlObj)
if tx.db.GetDebug() {
tx.db.GetCore().writeSqlToLogger(tx.ctx, sqlObj)
@ -250,6 +256,11 @@ func (tx *TX) Rollback() error {
return err
}
// IsClosed checks and returns this transaction has already been committed or rolled back.
func (tx *TX) IsClosed() bool {
return tx.isClosed
}
// Begin starts a nested transaction procedure.
func (tx *TX) Begin() error {
_, err := tx.Exec("SAVEPOINT " + tx.transactionKeyForNestedPoint())
@ -317,13 +328,13 @@ func (tx *TX) Transaction(ctx context.Context, f func(ctx context.Context, tx *T
// Query does query operation on transaction.
// See Core.Query.
func (tx *TX) Query(sql string, args ...interface{}) (rows *sql.Rows, err error) {
return tx.db.GetCore().DoQuery(tx.ctx, &txLink{tx.tx}, sql, args...)
return tx.db.DoQuery(tx.ctx, &txLink{tx.tx}, sql, args...)
}
// Exec does none query operation on transaction.
// See Core.Exec.
func (tx *TX) Exec(sql string, args ...interface{}) (sql.Result, error) {
return tx.db.GetCore().DoExec(tx.ctx, &txLink{tx.tx}, sql, args...)
return tx.db.DoExec(tx.ctx, &txLink{tx.tx}, sql, args...)
}
// Prepare creates a prepared statement for later queries or executions.
@ -332,7 +343,7 @@ func (tx *TX) Exec(sql string, args ...interface{}) (sql.Result, error) {
// The caller must call the statement's Close method
// when the statement is no longer needed.
func (tx *TX) Prepare(sql string) (*Stmt, error) {
return tx.db.GetCore().DoPrepare(tx.ctx, &txLink{tx.tx}, sql)
return tx.db.DoPrepare(tx.ctx, &txLink{tx.tx}, sql)
}
// GetAll queries and returns data records from database.
@ -503,42 +514,6 @@ func (tx *TX) Save(table string, data interface{}, batch ...int) (sql.Result, er
return tx.Model(table).Ctx(tx.ctx).Data(data).Save()
}
// BatchInsert batch inserts data.
// The parameter `list` must be type of slice of map or struct.
func (tx *TX) BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error) {
if len(batch) > 0 {
return tx.Model(table).Ctx(tx.ctx).Data(list).Batch(batch[0]).Insert()
}
return tx.Model(table).Ctx(tx.ctx).Data(list).Insert()
}
// BatchInsertIgnore batch inserts data with ignore option.
// The parameter `list` must be type of slice of map or struct.
func (tx *TX) BatchInsertIgnore(table string, list interface{}, batch ...int) (sql.Result, error) {
if len(batch) > 0 {
return tx.Model(table).Ctx(tx.ctx).Data(list).Batch(batch[0]).InsertIgnore()
}
return tx.Model(table).Ctx(tx.ctx).Data(list).InsertIgnore()
}
// BatchReplace batch replaces data.
// The parameter `list` must be type of slice of map or struct.
func (tx *TX) BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error) {
if len(batch) > 0 {
return tx.Model(table).Ctx(tx.ctx).Data(list).Batch(batch[0]).Replace()
}
return tx.Model(table).Ctx(tx.ctx).Data(list).Replace()
}
// BatchSave batch replaces data.
// The parameter `list` must be type of slice of map or struct.
func (tx *TX) BatchSave(table string, list interface{}, batch ...int) (sql.Result, error) {
if len(batch) > 0 {
return tx.Model(table).Ctx(tx.ctx).Data(list).Batch(batch[0]).Save()
}
return tx.Model(table).Ctx(tx.ctx).Data(list).Save()
}
// Update does "UPDATE ... " statement for the table.
//
// The parameter `data` can be type of string/map/gmap/struct/*struct, etc.

View File

@ -10,6 +10,7 @@ package gdb
import (
"context"
"database/sql"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/os/gtime"
)
@ -17,7 +18,7 @@ import (
// Query commits one query SQL to underlying driver and returns the execution result.
// It is most commonly used for data querying.
func (c *Core) Query(sql string, args ...interface{}) (rows *sql.Rows, err error) {
return c.DoQuery(c.GetCtx(), nil, sql, args...)
return c.db.DoQuery(c.GetCtx(), nil, sql, args...)
}
// DoQuery commits the sql string and its arguments to underlying driver
@ -33,12 +34,17 @@ func (c *Core) DoQuery(ctx context.Context, link Link, sql string, args ...inter
link = &txLink{tx.tx}
}
}
// Link execution.
sql, args = formatSql(sql, args)
sql, args = c.db.HandleSqlBeforeCommit(ctx, link, sql, args)
if c.GetConfig().QueryTimeout > 0 {
ctx, _ = context.WithTimeout(ctx, c.GetConfig().QueryTimeout)
}
// Link execution.
sql, args = formatSql(sql, args)
sql, args, err = c.db.DoCommit(ctx, link, sql, args)
if err != nil {
return nil, err
}
mTime1 := gtime.TimestampMilli()
rows, err = link.QueryContext(ctx, sql, args...)
mTime2 := gtime.TimestampMilli()
@ -69,7 +75,7 @@ func (c *Core) DoQuery(ctx context.Context, link Link, sql string, args ...inter
// Exec commits one query SQL to underlying driver and returns the execution result.
// It is most commonly used for data inserting and updating.
func (c *Core) Exec(sql string, args ...interface{}) (result sql.Result, err error) {
return c.DoExec(c.GetCtx(), nil, sql, args...)
return c.db.DoExec(c.GetCtx(), nil, sql, args...)
}
// DoExec commits the sql string and its arguments to underlying driver
@ -85,15 +91,19 @@ func (c *Core) DoExec(ctx context.Context, link Link, sql string, args ...interf
link = &txLink{tx.tx}
}
}
// Link execution.
sql, args = formatSql(sql, args)
sql, args = c.db.HandleSqlBeforeCommit(ctx, link, sql, args)
if c.GetConfig().ExecTimeout > 0 {
var cancelFunc context.CancelFunc
ctx, cancelFunc = context.WithTimeout(ctx, c.GetConfig().ExecTimeout)
defer cancelFunc()
}
// Link execution.
sql, args = formatSql(sql, args)
sql, args, err = c.db.DoCommit(ctx, link, sql, args)
if err != nil {
return nil, err
}
mTime1 := gtime.TimestampMilli()
if !c.db.GetDryRun() {
result, err = link.ExecContext(ctx, sql, args...)
@ -120,6 +130,18 @@ func (c *Core) DoExec(ctx context.Context, link Link, sql string, args ...interf
return result, formatError(err, sql, args...)
}
// DoCommit is a hook function, which deals with the sql string before it's committed to underlying driver.
// The parameter `link` specifies the current database connection operation object. You can modify the sql
// string `sql` and its arguments `args` as you wish before they're committed to driver.
func (c *Core) DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
if c.db.GetConfig().CtxStrict {
if v := ctx.Value(ctxStrictKeyName); v == nil {
return sql, args, gerror.NewCode(gerror.CodeMissingParameter, ctxStrictErrorStr)
}
}
return sql, args, nil
}
// Prepare creates a prepared statement for later queries or executions.
// Multiple queries or executions may be run concurrently from the
// returned statement.
@ -142,7 +164,7 @@ func (c *Core) Prepare(sql string, execOnMaster ...bool) (*Stmt, error) {
return nil, err
}
}
return c.DoPrepare(c.GetCtx(), link, sql)
return c.db.DoPrepare(c.GetCtx(), link, sql)
}
// DoPrepare calls prepare function on given link object and returns the statement object.
@ -156,6 +178,13 @@ func (c *Core) DoPrepare(ctx context.Context, link Link, sql string) (*Stmt, err
// DO NOT USE cancel function in prepare statement.
ctx, _ = context.WithTimeout(ctx, c.GetConfig().PrepareTimeout)
}
if c.db.GetConfig().CtxStrict {
if v := ctx.Value(ctxStrictKeyName); v == nil {
return nil, gerror.NewCode(gerror.CodeMissingParameter, ctxStrictErrorStr)
}
}
var (
mTime1 = gtime.TimestampMilli()
stmt, err = link.PrepareContext(ctx, sql)

View File

@ -63,12 +63,6 @@ func (c *Core) GetChars() (charLeft string, charRight string) {
return "", ""
}
// HandleSqlBeforeCommit handles the sql before posts it to database.
// It does nothing in default.
func (c *Core) HandleSqlBeforeCommit(sql string) string {
return sql
}
// Tables retrieves and returns the tables of current schema.
// It's mainly used in cli tool chain for automatically generating the models.
//

View File

@ -42,15 +42,15 @@ func (d *DriverMssql) New(core *Core, node *ConfigNode) (DB, error) {
// Open creates and returns a underlying sql.DB object for mssql.
func (d *DriverMssql) Open(config *ConfigNode) (*sql.DB, error) {
source := ""
if config.LinkInfo != "" {
source = config.LinkInfo
if config.Link != "" {
source = config.Link
} else {
source = fmt.Sprintf(
"user id=%s;password=%s;server=%s;port=%s;database=%s;encrypt=disable",
config.User, config.Pass, config.Host, config.Port, config.Name,
)
}
intlog.Printf("Open: %s", source)
intlog.Printf(d.GetCtx(), "Open: %s", source)
if db, err := sql.Open("sqlserver", source); err == nil {
return db, nil
} else {
@ -58,17 +58,17 @@ func (d *DriverMssql) Open(config *ConfigNode) (*sql.DB, error) {
}
}
// FilteredLinkInfo retrieves and returns filtered `linkInfo` that can be using for
// FilteredLink retrieves and returns filtered `linkInfo` that can be using for
// logging or tracing purpose.
func (d *DriverMssql) FilteredLinkInfo() string {
linkInfo := d.GetConfig().LinkInfo
func (d *DriverMssql) FilteredLink() string {
linkInfo := d.GetConfig().Link
if linkInfo == "" {
return ""
}
s, _ := gregex.ReplaceString(
`(.+);\s*password=(.+);\s*server=(.+)`,
`$1;password=xxx;server=$3`,
d.GetConfig().LinkInfo,
d.GetConfig().Link,
)
return s
}
@ -78,8 +78,11 @@ func (d *DriverMssql) GetChars() (charLeft string, charRight string) {
return "\"", "\""
}
// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
func (d *DriverMssql) HandleSqlBeforeCommit(ctx context.Context, link Link, sql string, args []interface{}) (string, []interface{}) {
// DoCommit deals with the sql string before commits it to underlying sql driver.
func (d *DriverMssql) DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
defer func() {
newSql, newArgs, err = d.Core.DoCommit(ctx, link, newSql, newArgs)
}()
var index int
// Convert place holder char '?' to string "@px".
str, _ := gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
@ -87,7 +90,7 @@ func (d *DriverMssql) HandleSqlBeforeCommit(ctx context.Context, link Link, sql
return fmt.Sprintf("@p%d", index)
})
str, _ = gregex.ReplaceString("\"", "", str)
return d.parseSql(str), args
return d.parseSql(str), args, nil
}
// parseSql does some replacement of the sql before commits it to underlying driver,
@ -207,11 +210,11 @@ func (d *DriverMssql) Tables(ctx context.Context, schema ...string) (tables []st
// TableFields retrieves and returns the fields information of specified table of current schema.
//
// Also see DriverMysql.TableFields.
func (d *DriverMssql) TableFields(ctx context.Context, link Link, table string, schema ...string) (fields map[string]*TableField, err error) {
func (d *DriverMssql) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*TableField, err error) {
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
return nil, gerror.New("function TableFields supports only single table operations")
return nil, gerror.NewCode(gerror.CodeInvalidParameter, "function TableFields supports only single table operations")
}
useSchema := d.db.GetSchema()
if len(schema) > 0 && schema[0] != "" {
@ -223,13 +226,11 @@ func (d *DriverMssql) TableFields(ctx context.Context, link Link, table string,
)
v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} {
var (
result Result
)
if link == nil {
result Result
link, err = d.SlaveLink(useSchema)
if err != nil {
return nil
}
)
if err != nil {
return nil
}
structureSql := fmt.Sprintf(`
SELECT
@ -286,3 +287,17 @@ ORDER BY a.id,a.colorder`,
}
return
}
// DoInsert is not supported in mssql.
func (d *DriverMssql) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) {
switch option.InsertOption {
case insertOptionSave:
return nil, gerror.NewCode(gerror.CodeNotSupported, `Save operation is not supported by mssql driver`)
case insertOptionReplace:
return nil, gerror.NewCode(gerror.CodeNotSupported, `Replace operation is not supported by mssql driver`)
default:
return d.Core.DoInsert(ctx, link, table, list, option)
}
}

View File

@ -10,13 +10,14 @@ import (
"context"
"database/sql"
"fmt"
"net/url"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/text/gregex"
"github.com/gogf/gf/text/gstr"
_ "github.com/go-sql-driver/mysql"
_ "github.com/gogf/mysql"
)
// DriverMysql is the driver for mysql database.
@ -36,8 +37,8 @@ func (d *DriverMysql) New(core *Core, node *ConfigNode) (DB, error) {
// Note that it converts time.Time argument to local timezone in default.
func (d *DriverMysql) Open(config *ConfigNode) (*sql.DB, error) {
var source string
if config.LinkInfo != "" {
source = config.LinkInfo
if config.Link != "" {
source = config.Link
// Custom changing the schema in runtime.
if config.Name != "" {
source, _ = gregex.ReplaceString(`/([\w\.\-]+)+`, "/"+config.Name, source)
@ -47,8 +48,11 @@ func (d *DriverMysql) Open(config *ConfigNode) (*sql.DB, error) {
"%s:%s@tcp(%s:%s)/%s?charset=%s",
config.User, config.Pass, config.Host, config.Port, config.Name, config.Charset,
)
if config.Timezone != "" {
source = fmt.Sprintf("%s&loc=%s", source, url.QueryEscape(config.Timezone))
}
}
intlog.Printf("Open: %s", source)
intlog.Printf(d.GetCtx(), "Open: %s", source)
if db, err := sql.Open("mysql", source); err == nil {
return db, nil
} else {
@ -56,10 +60,10 @@ func (d *DriverMysql) Open(config *ConfigNode) (*sql.DB, error) {
}
}
// FilteredLinkInfo retrieves and returns filtered `linkInfo` that can be using for
// FilteredLink retrieves and returns filtered `linkInfo` that can be using for
// logging or tracing purpose.
func (d *DriverMysql) FilteredLinkInfo() string {
linkInfo := d.GetConfig().LinkInfo
func (d *DriverMysql) FilteredLink() string {
linkInfo := d.GetConfig().Link
if linkInfo == "" {
return ""
}
@ -76,9 +80,9 @@ func (d *DriverMysql) GetChars() (charLeft string, charRight string) {
return "`", "`"
}
// HandleSqlBeforeCommit handles the sql before posts it to database.
func (d *DriverMysql) HandleSqlBeforeCommit(ctx context.Context, link Link, sql string, args []interface{}) (string, []interface{}) {
return sql, args
// DoCommit handles the sql before posts it to database.
func (d *DriverMysql) DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
return d.Core.DoCommit(ctx, link, sql, args)
}
// Tables retrieves and returns the tables of current schema.
@ -113,11 +117,11 @@ func (d *DriverMysql) Tables(ctx context.Context, schema ...string) (tables []st
//
// It's using cache feature to enhance the performance, which is never expired util the
// process restarts.
func (d *DriverMysql) TableFields(ctx context.Context, link Link, table string, schema ...string) (fields map[string]*TableField, err error) {
func (d *DriverMysql) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*TableField, err error) {
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
return nil, gerror.New("function TableFields supports only single table operations")
return nil, gerror.NewCode(gerror.CodeInvalidParameter, "function TableFields supports only single table operations")
}
useSchema := d.schema.Val()
if len(schema) > 0 && schema[0] != "" {
@ -129,20 +133,16 @@ func (d *DriverMysql) TableFields(ctx context.Context, link Link, table string,
)
v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} {
var (
result Result
)
if link == nil {
result Result
link, err = d.SlaveLink(useSchema)
if err != nil {
return nil
}
}
result, err = d.DoGetAll(ctx, link,
fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.QuoteWord(table)),
)
if err != nil {
return nil
}
result, err = d.DoGetAll(ctx, link, fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.QuoteWord(table)))
if err != nil {
return nil
}
fields = make(map[string]*TableField)
for i, m := range result {
fields[m["Field"].String()] = &TableField{

View File

@ -32,11 +32,6 @@ type DriverOracle struct {
*Core
}
const (
tableAlias1 = "GFORM1"
tableAlias2 = "GFORM2"
)
// New creates and returns a database object for oracle.
// It implements the interface of gdb.Driver for extra database driver installation.
func (d *DriverOracle) New(core *Core, node *ConfigNode) (DB, error) {
@ -48,15 +43,15 @@ func (d *DriverOracle) New(core *Core, node *ConfigNode) (DB, error) {
// Open creates and returns a underlying sql.DB object for oracle.
func (d *DriverOracle) Open(config *ConfigNode) (*sql.DB, error) {
var source string
if config.LinkInfo != "" {
source = config.LinkInfo
if config.Link != "" {
source = config.Link
} else {
source = fmt.Sprintf(
"%s/%s@%s:%s/%s",
config.User, config.Pass, config.Host, config.Port, config.Name,
)
}
intlog.Printf("Open: %s", source)
intlog.Printf(d.GetCtx(), "Open: %s", source)
if db, err := sql.Open("oci8", source); err == nil {
return db, nil
} else {
@ -64,10 +59,10 @@ func (d *DriverOracle) Open(config *ConfigNode) (*sql.DB, error) {
}
}
// FilteredLinkInfo retrieves and returns filtered `linkInfo` that can be using for
// FilteredLink retrieves and returns filtered `linkInfo` that can be using for
// logging or tracing purpose.
func (d *DriverOracle) FilteredLinkInfo() string {
linkInfo := d.GetConfig().LinkInfo
func (d *DriverOracle) FilteredLink() string {
linkInfo := d.GetConfig().Link
if linkInfo == "" {
return ""
}
@ -84,8 +79,12 @@ func (d *DriverOracle) GetChars() (charLeft string, charRight string) {
return "\"", "\""
}
// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
func (d *DriverOracle) HandleSqlBeforeCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}) {
// DoCommit deals with the sql string before commits it to underlying sql driver.
func (d *DriverOracle) DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
defer func() {
newSql, newArgs, err = d.Core.DoCommit(ctx, link, newSql, newArgs)
}()
var index int
// Convert place holder char '?' to string ":vx".
newSql, _ = gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
@ -183,11 +182,11 @@ func (d *DriverOracle) Tables(ctx context.Context, schema ...string) (tables []s
// TableFields retrieves and returns the fields information of specified table of current schema.
//
// Also see DriverMysql.TableFields.
func (d *DriverOracle) TableFields(ctx context.Context, link Link, table string, schema ...string) (fields map[string]*TableField, err error) {
func (d *DriverOracle) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*TableField, err error) {
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
return nil, gerror.New("function TableFields supports only single table operations")
return nil, gerror.NewCode(gerror.CodeInvalidParameter, "function TableFields supports only single table operations")
}
useSchema := d.db.GetSchema()
if len(schema) > 0 && schema[0] != "" {
@ -200,6 +199,7 @@ func (d *DriverOracle) TableFields(ctx context.Context, link Link, table string,
v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} {
var (
result Result
link, err = d.SlaveLink(useSchema)
structureSql = fmt.Sprintf(`
SELECT
COLUMN_NAME AS FIELD,
@ -211,13 +211,10 @@ FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`,
strings.ToUpper(table),
)
)
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
if link == nil {
link, err = d.SlaveLink(useSchema)
if err != nil {
return nil
}
if err != nil {
return nil
}
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
result, err = d.DoGetAll(ctx, link, structureSql)
if err != nil {
return nil
@ -266,203 +263,60 @@ func (d *DriverOracle) getTableUniqueIndex(table string) (fields map[string]map[
return
}
func (d *DriverOracle) DoInsert(ctx context.Context, link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error) {
var (
fields []string
values []string
params []interface{}
dataMap Map
rv = reflect.ValueOf(data)
kind = rv.Kind()
)
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
case reflect.Slice, reflect.Array:
return d.DoBatchInsert(ctx, link, table, data, option, batch...)
case reflect.Map:
fallthrough
case reflect.Struct:
dataMap = ConvertDataForTableRecord(data)
default:
return result, gerror.New(fmt.Sprint("unsupported data type:", kind))
}
var (
indexes = make([]string, 0)
indexMap = make(map[string]string)
indexExists = false
)
if option != insertOptionDefault {
index, err := d.getTableUniqueIndex(table)
if err != nil {
return nil, err
}
// DoInsert inserts or updates data for given table.
// This function is usually used for custom interface definition, you do not need call it manually.
// The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc.
// Eg:
// Data(g.Map{"uid": 10000, "name":"john"})
// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
//
// The parameter `option` values are as follows:
// 0: insert: just insert, if there's unique/primary key in the data, it returns error;
// 1: replace: if there's unique/primary key in the data, it deletes it from table and inserts a new one;
// 2: save: if there's unique/primary key in the data, it updates it or else inserts a new one;
// 3: ignore: if there's unique/primary key in the data, it ignores the inserting;
func (d *DriverOracle) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) {
switch option.InsertOption {
case insertOptionSave:
return nil, gerror.NewCode(gerror.CodeNotSupported, `Save operation is not supported by mssql driver`)
if len(index) > 0 {
for _, v := range index {
for k, _ := range v {
indexes = append(indexes, k)
}
indexMap = v
indexExists = true
break
}
}
}
var (
subSqlStr = make([]string, 0)
onStr = make([]string, 0)
updateStr = make([]string, 0)
)
charL, charR := d.db.GetChars()
for k, v := range dataMap {
k = strings.ToUpper(k)
// 操作类型为REPLACE/SAVE时且存在唯一索引才使用merge否则使用insert
if (option == insertOptionReplace || option == insertOptionSave) && indexExists {
fields = append(fields, tableAlias1+"."+charL+k+charR)
values = append(values, tableAlias2+"."+charL+k+charR)
params = append(params, v)
subSqlStr = append(subSqlStr, fmt.Sprintf("%s?%s %s", charL, charR, k))
//m erge中的on子句中由唯一索引组成, update子句中不含唯一索引
if _, ok := indexMap[k]; ok {
onStr = append(onStr, fmt.Sprintf("%s.%s = %s.%s ", tableAlias1, k, tableAlias2, k))
} else {
updateStr = append(updateStr, fmt.Sprintf("%s.%s = %s.%s ", tableAlias1, k, tableAlias2, k))
}
} else {
fields = append(fields, charL+k+charR)
values = append(values, "?")
params = append(params, v)
}
case insertOptionReplace:
return nil, gerror.NewCode(gerror.CodeNotSupported, `Replace operation is not supported by mssql driver`)
}
if link == nil {
if link, err = d.MasterLink(); err != nil {
return nil, err
}
}
if indexExists && option != insertOptionDefault {
switch option {
case
insertOptionReplace,
insertOptionSave:
tmp := fmt.Sprintf(
"MERGE INTO %s %s USING(SELECT %s FROM DUAL) %s ON(%s) WHEN MATCHED THEN UPDATE SET %s WHEN NOT MATCHED THEN INSERT (%s) VALUES(%s)",
table, tableAlias1, strings.Join(subSqlStr, ","), tableAlias2,
strings.Join(onStr, "AND"), strings.Join(updateStr, ","), strings.Join(fields, ","), strings.Join(values, ","),
)
return d.DoExec(ctx, link, tmp, params...)
case insertOptionIgnore:
return d.DoExec(ctx, link, fmt.Sprintf(
"INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(%s(%s)) */ INTO %s(%s) VALUES(%s)",
table, strings.Join(indexes, ","), table, strings.Join(fields, ","), strings.Join(values, ","),
), params...)
}
}
return d.DoExec(ctx, link,
fmt.Sprintf(
"INSERT INTO %s(%s) VALUES(%s)",
table, strings.Join(fields, ","), strings.Join(values, ","),
),
params...)
}
func (d *DriverOracle) DoBatchInsert(ctx context.Context, link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) {
var (
keys []string
values []string
params []interface{}
)
listMap := (List)(nil)
switch v := list.(type) {
case Result:
listMap = v.List()
case Record:
listMap = List{v.Map()}
case List:
listMap = v
case Map:
listMap = List{v}
default:
var (
rv = reflect.ValueOf(list)
kind = rv.Kind()
)
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
case reflect.Slice, reflect.Array:
listMap = make(List, rv.Len())
for i := 0; i < rv.Len(); i++ {
listMap[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
}
case reflect.Map:
fallthrough
case reflect.Struct:
listMap = List{ConvertDataForTableRecord(list)}
default:
return result, gerror.New(fmt.Sprint("unsupported list type:", kind))
}
}
if len(listMap) < 1 {
return result, gerror.New("empty data list")
}
if link == nil {
if link, err = d.MasterLink(); err != nil {
return
}
}
// Retrieve the table fields and length.
holders := []string(nil)
for k, _ := range listMap[0] {
var (
listLength = len(list)
valueHolder = make([]string, 0)
)
for k, _ := range list[0] {
keys = append(keys, k)
holders = append(holders, "?")
valueHolder = append(valueHolder, "?")
}
var (
batchResult = new(SqlResult)
charL, charR = d.db.GetChars()
keyStr = charL + strings.Join(keys, charL+","+charR) + charR
valueHolderStr = strings.Join(holders, ",")
valueHolderStr = strings.Join(valueHolder, ",")
)
if option != insertOptionDefault {
for _, v := range listMap {
r, err := d.DoInsert(ctx, link, table, v, option, 1)
if err != nil {
return r, err
}
if n, err := r.RowsAffected(); err != nil {
return r, err
} else {
batchResult.result = r
batchResult.affected += n
}
}
return batchResult, nil
}
batchNum := defaultBatchNumber
if len(batch) > 0 {
batchNum = batch[0]
}
// Format "INSERT...INTO..." statement.
intoStr := make([]string, 0)
for i := 0; i < len(listMap); i++ {
for i := 0; i < len(list); i++ {
for _, k := range keys {
params = append(params, listMap[i][k])
params = append(params, list[i][k])
}
values = append(values, valueHolderStr)
intoStr = append(intoStr, fmt.Sprintf(" INTO %s(%s) VALUES(%s) ", table, keyStr, valueHolderStr))
if len(intoStr) == batchNum {
r, err := d.DoExec(ctx, link, fmt.Sprintf("INSERT ALL %s SELECT * FROM DUAL", strings.Join(intoStr, " ")), params...)
intoStr = append(intoStr, fmt.Sprintf("INTO %s(%s) VALUES(%s)", table, keyStr, valueHolderStr))
if len(intoStr) == option.BatchCount || (i == listLength-1 && len(valueHolder) > 0) {
r, err := d.DoExec(ctx, link, fmt.Sprintf(
"INSERT ALL %s SELECT * FROM DUAL",
strings.Join(intoStr, " "),
), params...)
if err != nil {
return r, err
}
@ -476,18 +330,5 @@ func (d *DriverOracle) DoBatchInsert(ctx context.Context, link Link, table strin
intoStr = intoStr[:0]
}
}
// The leftover data.
if len(intoStr) > 0 {
r, err := d.DoExec(ctx, link, fmt.Sprintf("INSERT ALL %s SELECT * FROM DUAL", strings.Join(intoStr, " ")), params...)
if err != nil {
return r, err
}
if n, err := r.RowsAffected(); err != nil {
return r, err
} else {
batchResult.result = r
batchResult.affected += n
}
}
return batchResult, nil
}

View File

@ -40,15 +40,18 @@ func (d *DriverPgsql) New(core *Core, node *ConfigNode) (DB, error) {
// Open creates and returns a underlying sql.DB object for pgsql.
func (d *DriverPgsql) Open(config *ConfigNode) (*sql.DB, error) {
var source string
if config.LinkInfo != "" {
source = config.LinkInfo
if config.Link != "" {
source = config.Link
} else {
source = fmt.Sprintf(
"user=%s password=%s host=%s port=%s dbname=%s sslmode=disable",
config.User, config.Pass, config.Host, config.Port, config.Name,
)
if config.Timezone != "" {
source = fmt.Sprintf("%s timezone=%s", source, config.Timezone)
}
}
intlog.Printf("Open: %s", source)
intlog.Printf(d.GetCtx(), "Open: %s", source)
if db, err := sql.Open("postgres", source); err == nil {
return db, nil
} else {
@ -56,10 +59,10 @@ func (d *DriverPgsql) Open(config *ConfigNode) (*sql.DB, error) {
}
}
// FilteredLinkInfo retrieves and returns filtered `linkInfo` that can be using for
// FilteredLink retrieves and returns filtered `linkInfo` that can be using for
// logging or tracing purpose.
func (d *DriverPgsql) FilteredLinkInfo() string {
linkInfo := d.GetConfig().LinkInfo
func (d *DriverPgsql) FilteredLink() string {
linkInfo := d.GetConfig().Link
if linkInfo == "" {
return ""
}
@ -76,16 +79,20 @@ func (d *DriverPgsql) GetChars() (charLeft string, charRight string) {
return "\"", "\""
}
// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
func (d *DriverPgsql) HandleSqlBeforeCommit(ctx context.Context, link Link, sql string, args []interface{}) (string, []interface{}) {
// DoCommit deals with the sql string before commits it to underlying sql driver.
func (d *DriverPgsql) DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
defer func() {
newSql, newArgs, err = d.Core.DoCommit(ctx, link, newSql, newArgs)
}()
var index int
// Convert place holder char '?' to string "$x".
sql, _ = gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
index++
return fmt.Sprintf("$%d", index)
})
sql, _ = gregex.ReplaceString(` LIMIT (\d+),\s*(\d+)`, ` LIMIT $2 OFFSET $1`, sql)
return sql, args
newSql, _ = gregex.ReplaceString(` LIMIT (\d+),\s*(\d+)`, ` LIMIT $2 OFFSET $1`, sql)
return newSql, args, nil
}
// Tables retrieves and returns the tables of current schema.
@ -115,11 +122,11 @@ func (d *DriverPgsql) Tables(ctx context.Context, schema ...string) (tables []st
// TableFields retrieves and returns the fields information of specified table of current schema.
//
// Also see DriverMysql.TableFields.
func (d *DriverPgsql) TableFields(ctx context.Context, link Link, table string, schema ...string) (fields map[string]*TableField, err error) {
func (d *DriverPgsql) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*TableField, err error) {
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
return nil, gerror.New("function TableFields supports only single table operations")
return nil, gerror.NewCode(gerror.CodeInvalidParameter, "function TableFields supports only single table operations")
}
table, _ = gregex.ReplaceString("\"", "", table)
useSchema := d.db.GetSchema()
@ -133,21 +140,28 @@ func (d *DriverPgsql) TableFields(ctx context.Context, link Link, table string,
v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} {
var (
result Result
link, err = d.SlaveLink(useSchema)
structureSql = fmt.Sprintf(`
SELECT a.attname AS field, t.typname AS type FROM pg_class c, pg_attribute a
LEFT OUTER JOIN pg_description b ON a.attrelid=b.objoid AND a.attnum = b.objsubid,pg_type t
WHERE c.relname = '%s' and a.attnum > 0 and a.attrelid = c.oid and a.atttypid = t.oid
SELECT a.attname AS field, t.typname AS type,a.attnotnull as null,
(case when d.contype is not null then 'pri' else '' end) as key
,ic.column_default as default_value,b.description as comment
,coalesce(character_maximum_length, numeric_precision, -1) as length
,numeric_scale as scale
FROM pg_attribute a
left join pg_class c on a.attrelid = c.oid
left join pg_constraint d on d.conrelid = c.oid and a.attnum = d.conkey[1]
left join pg_description b ON a.attrelid=b.objoid AND a.attnum = b.objsubid
left join pg_type t ON a.atttypid = t.oid
left join information_schema.columns ic on ic.column_name = a.attname and ic.table_name = c.relname
WHERE c.relname = '%s' and a.attnum > 0
ORDER BY a.attnum`,
strings.ToLower(table),
)
)
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
if link == nil {
link, err = d.SlaveLink(useSchema)
if err != nil {
return nil
}
if err != nil {
return nil
}
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
result, err = d.DoGetAll(ctx, link, structureSql)
if err != nil {
return nil
@ -155,9 +169,13 @@ ORDER BY a.attnum`,
fields = make(map[string]*TableField)
for i, m := range result {
fields[m["field"].String()] = &TableField{
Index: i,
Name: m["field"].String(),
Type: m["type"].String(),
Index: i,
Name: m["field"].String(),
Type: m["type"].String(),
Null: m["null"].Bool(),
Key: m["key"].String(),
Default: m["default_value"].Val(),
Comment: m["comment"].String(),
}
}
return fields
@ -167,3 +185,17 @@ ORDER BY a.attnum`,
}
return
}
// DoInsert is not supported in pgsql.
func (d *DriverPgsql) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) {
switch option.InsertOption {
case insertOptionSave:
return nil, gerror.NewCode(gerror.CodeNotSupported, `Save operation is not supported by pgsql driver`)
case insertOptionReplace:
return nil, gerror.NewCode(gerror.CodeNotSupported, `Replace operation is not supported by pgsql driver`)
default:
return d.Core.DoInsert(ctx, link, table, list, option)
}
}

View File

@ -38,8 +38,8 @@ func (d *DriverSqlite) New(core *Core, node *ConfigNode) (DB, error) {
// Open creates and returns a underlying sql.DB object for sqlite.
func (d *DriverSqlite) Open(config *ConfigNode) (*sql.DB, error) {
var source string
if config.LinkInfo != "" {
source = config.LinkInfo
if config.Link != "" {
source = config.Link
} else {
source = config.Name
}
@ -47,7 +47,7 @@ func (d *DriverSqlite) Open(config *ConfigNode) (*sql.DB, error) {
if absolutePath, _ := gfile.Search(source); absolutePath != "" {
source = absolutePath
}
intlog.Printf("Open: %s", source)
intlog.Printf(d.GetCtx(), "Open: %s", source)
if db, err := sql.Open("sqlite3", source); err == nil {
return db, nil
} else {
@ -55,10 +55,10 @@ func (d *DriverSqlite) Open(config *ConfigNode) (*sql.DB, error) {
}
}
// FilteredLinkInfo retrieves and returns filtered `linkInfo` that can be using for
// FilteredLink retrieves and returns filtered `linkInfo` that can be using for
// logging or tracing purpose.
func (d *DriverSqlite) FilteredLinkInfo() string {
return d.GetConfig().LinkInfo
func (d *DriverSqlite) FilteredLink() string {
return d.GetConfig().Link
}
// GetChars returns the security char for this type of database.
@ -66,11 +66,9 @@ func (d *DriverSqlite) GetChars() (charLeft string, charRight string) {
return "`", "`"
}
// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
// TODO 需要增加对Save方法的支持可使用正则来实现替换
// TODO 将ON DUPLICATE KEY UPDATE触发器修改为两条SQL语句(INSERT OR IGNORE & UPDATE)
func (d *DriverSqlite) HandleSqlBeforeCommit(ctx context.Context, link Link, sql string, args []interface{}) (string, []interface{}) {
return sql, args
// DoCommit deals with the sql string before commits it to underlying sql driver.
func (d *DriverSqlite) DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
return d.Core.DoCommit(ctx, link, sql, args)
}
// Tables retrieves and returns the tables of current schema.
@ -97,11 +95,11 @@ func (d *DriverSqlite) Tables(ctx context.Context, schema ...string) (tables []s
// TableFields retrieves and returns the fields information of specified table of current schema.
//
// Also see DriverMysql.TableFields.
func (d *DriverSqlite) TableFields(ctx context.Context, link Link, table string, schema ...string) (fields map[string]*TableField, err error) {
func (d *DriverSqlite) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*TableField, err error) {
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
return nil, gerror.New("function TableFields supports only single table operations")
return nil, gerror.NewCode(gerror.CodeInvalidParameter, "function TableFields supports only single table operations")
}
useSchema := d.db.GetSchema()
if len(schema) > 0 && schema[0] != "" {
@ -113,13 +111,11 @@ func (d *DriverSqlite) TableFields(ctx context.Context, link Link, table string,
)
v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} {
var (
result Result
)
if link == nil {
result Result
link, err = d.SlaveLink(useSchema)
if err != nil {
return nil
}
)
if err != nil {
return nil
}
result, err = d.DoGetAll(ctx, link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, table))
if err != nil {
@ -140,3 +136,17 @@ func (d *DriverSqlite) TableFields(ctx context.Context, link Link, table string,
}
return
}
// DoInsert is not supported in sqlite.
func (d *DriverSqlite) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) {
switch option.InsertOption {
case insertOptionSave:
return nil, gerror.NewCode(gerror.CodeNotSupported, `Save operation is not supported by sqlite driver`)
case insertOptionReplace:
return nil, gerror.NewCode(gerror.CodeNotSupported, `Replace operation is not supported by sqlite driver`)
default:
return d.Core.DoInsert(ctx, link, table, list, option)
}
}

View File

@ -8,6 +8,7 @@ package gdb
import (
"bytes"
"database/sql"
"fmt"
"reflect"
"regexp"
@ -55,11 +56,13 @@ type apiTableName interface {
}
const (
OrmTagForStruct = "orm"
OrmTagForUnique = "unique"
OrmTagForPrimary = "primary"
OrmTagForTable = "table"
OrmTagForWith = "with"
OrmTagForStruct = "orm"
OrmTagForUnique = "unique"
OrmTagForPrimary = "primary"
OrmTagForTable = "table"
OrmTagForWith = "with"
OrmTagForWithWhere = "where"
OrmTagForWithOrder = "order"
)
var (
@ -70,6 +73,32 @@ var (
structTagPriority = append([]string{OrmTagForStruct}, gconv.StructTagPriority...)
)
// guessPrimaryTableName parses and returns the primary table name.
func (m *Model) guessPrimaryTableName(tableStr string) string {
if tableStr == "" {
return ""
}
var (
guessedTableName = ""
array1 = gstr.SplitAndTrim(tableStr, ",")
array2 = gstr.SplitAndTrim(array1[0], " ")
array3 = gstr.SplitAndTrim(array2[0], ".")
)
if len(array3) >= 2 {
guessedTableName = array3[1]
} else {
guessedTableName = array3[0]
}
charL, charR := m.db.GetChars()
if charL != "" || charR != "" {
guessedTableName = gstr.Trim(guessedTableName, charL+charR)
}
if !gregex.IsMatchString(regularFieldNameRegPattern, guessedTableName) {
return ""
}
return guessedTableName
}
// getTableNameFromOrmTag retrieves and returns the table name from struct object.
func getTableNameFromOrmTag(object interface{}) string {
var tableName string
@ -142,8 +171,8 @@ func GetInsertOperationByOption(option int) string {
// ConvertDataForTableRecord is a very important function, which does converting for any data that
// will be inserted into table as a record.
//
// The parameter `obj` should be type of *map/map/*struct/struct.
// It supports inherit struct definition for struct.
// The parameter `value` should be type of *map/map/*struct/struct.
// It supports embedded struct definition for struct.
func ConvertDataForTableRecord(value interface{}) map[string]interface{} {
var (
rvValue reflect.Value
@ -164,12 +193,15 @@ func ConvertDataForTableRecord(value interface{}) map[string]interface{} {
// Convert the value to JSON.
data[k], _ = json.Marshal(v)
}
case reflect.Struct:
switch v.(type) {
case time.Time, *time.Time, gtime.Time, *gtime.Time:
continue
case Counter, *Counter:
continue
default:
// Use string conversion in default.
if s, ok := v.(apiString); ok {
@ -186,7 +218,7 @@ func ConvertDataForTableRecord(value interface{}) map[string]interface{} {
// DataToMapDeep converts `value` to map type recursively.
// The parameter `value` should be type of *map/map/*struct/struct.
// It supports inherit struct definition for struct.
// It supports embedded struct definition for struct.
func DataToMapDeep(value interface{}) map[string]interface{} {
if v, ok := value.(apiMapStrAny); ok {
return v.MapStrAny()
@ -236,7 +268,7 @@ func DataToMapDeep(value interface{}) map[string]interface{} {
name = ""
fieldTag = rtField.Tag
for _, tag := range structTagPriority {
if s := fieldTag.Get(tag); s != "" && gregex.IsMatchString(regularFieldNameWithoutDotRegPattern, s) {
if s := fieldTag.Get(tag); s != "" {
name = s
break
}
@ -325,14 +357,15 @@ func doQuoteWord(s, charLeft, charRight string) string {
return s
}
// doQuoteString quotes string with quote chars. It handles strings like:
// "user",
// "user u",
// "user,user_detail",
// "user u, user_detail ut",
// "user.user u, user.user_detail ut",
// "u.id, u.name, u.age",
// "u.id asc".
// doQuoteString quotes string with quote chars.
// For example, if quote char is '`':
// "user" => "`user`"
// "user u" => "`user` u"
// "user,user_detail" => "`user`,`user_detail`"
// "user u, user_detail ut" => "`user` u,`user_detail` ut"
// "user.user u, user.user_detail ut" => "`user`.`user` u,`user`.`user_detail` ut"
// "u.id, u.name, u.age" => "`u`.`id`,`u`.`name`,`u`.`age`"
// "u.id asc" => "`u`.`id` asc"
func doQuoteString(s, charLeft, charRight string) string {
array1 := gstr.SplitAndTrim(s, ",")
for k1, v1 := range array1 {
@ -445,7 +478,7 @@ func formatSql(sql string, args []interface{}) (newSql string, newArgs []interfa
}
// formatWhere formats where statement and its arguments for `Where` and `Having` statements.
func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (newWhere string, newArgs []interface{}) {
func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool, schema, table string) (newWhere string, newArgs []interface{}) {
var (
buffer = bytes.NewBuffer(nil)
rv = reflect.ValueOf(where)
@ -483,7 +516,12 @@ func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (
})
break
}
for key, value := range DataToMapDeep(where) {
// Automatically mapping and filtering the struct attribute.
data := DataToMapDeep(where)
if table != "" {
data, _ = db.GetCore().mappingAndFilterData(schema, table, data, true)
}
for key, value := range data {
if omitEmpty && empty.IsEmpty(value) {
continue
}
@ -769,9 +807,9 @@ func handleArguments(sql string, args []interface{}) (newSql string, newArgs []i
}
// formatError customizes and returns the SQL error.
func formatError(err error, sql string, args ...interface{}) error {
if err != nil && err != ErrNoRows {
return gerror.New(fmt.Sprintf("%s, %s\n", err.Error(), FormatSqlWithArgs(sql, args)))
func formatError(err error, s string, args ...interface{}) error {
if err != nil && err != sql.ErrNoRows {
return gerror.NewCodef(gerror.CodeDbOperationError, "%s, %s\n", err.Error(), FormatSqlWithArgs(s, args))
}
return err
}
@ -781,7 +819,9 @@ func formatError(err error, sql string, args ...interface{}) error {
func FormatSqlWithArgs(sql string, args []interface{}) string {
index := -1
newQuery, _ := gregex.ReplaceStringFunc(
`(\?|:v\d+|\$\d+|@p\d+)`, sql, func(s string) string {
`(\?|:v\d+|\$\d+|@p\d+)`,
sql,
func(s string) string {
index++
if len(args) > index {
if args[index] == nil {
@ -801,6 +841,7 @@ func FormatSqlWithArgs(sql string, args []interface{}) string {
switch kind {
case reflect.String, reflect.Map, reflect.Slice, reflect.Array:
return `'` + gstr.QuoteMeta(gconv.String(args[index]), `'`) + `'`
case reflect.Struct:
if t, ok := args[index].(time.Time); ok {
return `'` + t.Format(`2006-01-02 15:04:05`) + `'`

View File

@ -17,54 +17,62 @@ import (
"github.com/gogf/gf/text/gstr"
)
// Model is the DAO for ORM.
// Model is core struct implementing the DAO for ORM.
type Model struct {
db DB // Underlying DB interface.
tx *TX // Underlying TX interface.
schema string // Custom database schema.
linkType int // Mark for operation on master or slave.
tablesInit string // Table names when model initialization.
tables string // Operation table names, which can be more than one table names and aliases, like: "user", "user u", "user u, user_detail ud".
fields string // Operation fields, multiple fields joined using char ','.
fieldsEx string // Excluded operation fields, multiple fields joined using char ','.
withArray []interface{} // Arguments for With feature.
withAll bool // Enable model association operations on all objects that have "with" tag in the struct.
extraArgs []interface{} // Extra custom arguments for sql, which are prepended to the arguments before sql committed to underlying driver.
whereHolder []*whereHolder // Condition strings for where operation.
groupBy string // Used for "group by" statement.
orderBy string // Used for "order by" statement.
having []interface{} // Used for "having..." statement.
start int // Used for "select ... start, limit ..." statement.
limit int // Used for "select ... start, limit ..." statement.
option int // Option for extra operation features.
offset int // Offset statement for some databases grammar.
data interface{} // Data for operation, which can be type of map/[]map/struct/*struct/string, etc.
batch int // Batch number for batch Insert/Replace/Save operations.
filter bool // Filter data and where key-value pairs according to the fields of the table.
distinct string // Force the query to only return distinct results.
lockInfo string // Lock for update or in shared lock.
cacheEnabled bool // Enable sql result cache feature.
cacheDuration time.Duration // Cache TTL duration.
cacheName string // Cache name for custom operation.
unscoped bool // Disables soft deleting features when select/delete operations.
safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model.
db DB // Underlying DB interface.
tx *TX // Underlying TX interface.
rawSql string // rawSql is the raw SQL string which marks a raw SQL based Model not a table based Model.
schema string // Custom database schema.
linkType int // Mark for operation on master or slave.
tablesInit string // Table names when model initialization.
tables string // Operation table names, which can be more than one table names and aliases, like: "user", "user u", "user u, user_detail ud".
fields string // Operation fields, multiple fields joined using char ','.
fieldsEx string // Excluded operation fields, multiple fields joined using char ','.
withArray []interface{} // Arguments for With feature.
withAll bool // Enable model association operations on all objects that have "with" tag in the struct.
extraArgs []interface{} // Extra custom arguments for sql, which are prepended to the arguments before sql committed to underlying driver.
whereHolder []ModelWhereHolder // Condition strings for where operation.
groupBy string // Used for "group by" statement.
orderBy string // Used for "order by" statement.
having []interface{} // Used for "having..." statement.
start int // Used for "select ... start, limit ..." statement.
limit int // Used for "select ... start, limit ..." statement.
option int // Option for extra operation features.
offset int // Offset statement for some databases grammar.
data interface{} // Data for operation, which can be type of map/[]map/struct/*struct/string, etc.
batch int // Batch number for batch Insert/Replace/Save operations.
filter bool // Filter data and where key-value pairs according to the fields of the table.
distinct string // Force the query to only return distinct results.
lockInfo string // Lock for update or in shared lock.
cacheEnabled bool // Enable sql result cache feature.
cacheDuration time.Duration // Cache TTL duration (< 1 for removing cache, >= 0 for saving cache).
cacheName string // Cache name for custom operation.
unscoped bool // Disables soft deleting features when select/delete operations.
safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model.
onDuplicate interface{} // onDuplicate is used for ON "DUPLICATE KEY UPDATE" statement.
onDuplicateEx interface{} // onDuplicateEx is used for excluding some columns ON "DUPLICATE KEY UPDATE" statement.
}
// whereHolder is the holder for where condition preparing.
type whereHolder struct {
operator int // Operator for this holder.
where interface{} // Where parameter.
args []interface{} // Arguments for where parameter.
// ModelHandler is a function that handles given Model and returns a new Model that is custom modified.
type ModelHandler func(m *Model) *Model
// ChunkHandler is a function that is used in function Chunk, which handles given Result and error.
// It returns true if it wants continue chunking, or else it returns false to stop chunking.
type ChunkHandler func(result Result, err error) bool
// ModelWhereHolder is the holder for where condition preparing.
type ModelWhereHolder struct {
Operator int // Operator for this holder.
Where interface{} // Where parameter, which can commonly be type of string/map/struct.
Args []interface{} // Arguments for where parameter.
}
const (
OptionOmitEmpty = 1
OptionAllowEmpty = 2
linkTypeMaster = 1
linkTypeSlave = 2
whereHolderWhere = 1
whereHolderAnd = 2
whereHolderOr = 3
linkTypeMaster = 1
linkTypeSlave = 2
whereHolderOperatorWhere = 1
whereHolderOperatorAnd = 2
whereHolderOperatorOr = 3
)
// Table is alias of Core.Model.
@ -77,29 +85,32 @@ func (c *Core) Table(tableNameQueryOrStruct ...interface{}) *Model {
// Model creates and returns a new ORM model from given schema.
// The parameter `tableNameQueryOrStruct` can be more than one table names, and also alias name, like:
// 1. Model names:
// Model("user")
// Model("user u")
// Model("user, user_detail")
// Model("user u, user_detail ud")
// 2. Model name with alias: Model("user", "u")
// db.Model("user")
// db.Model("user u")
// db.Model("user, user_detail")
// db.Model("user u, user_detail ud")
// 2. Model name with alias:
// db.Model("user", "u")
// 3. Model name with sub-query:
// db.Model("? AS a, ? AS b", subQuery1, subQuery2)
func (c *Core) Model(tableNameQueryOrStruct ...interface{}) *Model {
var (
tableStr string
tableName string
extraArgs []interface{}
tableNames = make([]string, len(tableNameQueryOrStruct))
tableStr string
tableName string
extraArgs []interface{}
)
// Model creation with sub-query.
if len(tableNameQueryOrStruct) > 1 {
conditionStr := gconv.String(tableNameQueryOrStruct[0])
if gstr.Contains(conditionStr, "?") {
tableStr, extraArgs = formatWhere(
c.db, conditionStr, tableNameQueryOrStruct[1:], false,
c.db, conditionStr, tableNameQueryOrStruct[1:], false, "", "",
)
}
}
// Normal model creation.
if tableStr == "" {
tableNames := make([]string, len(tableNameQueryOrStruct))
for k, v := range tableNameQueryOrStruct {
if s, ok := v.(string); ok {
tableNames[k] = s
@ -107,7 +118,6 @@ func (c *Core) Model(tableNameQueryOrStruct ...interface{}) *Model {
tableNames[k] = tableName
}
}
if len(tableNames) > 1 {
tableStr = fmt.Sprintf(
`%s AS %s`, c.QuotePrefixTableName(tableNames[0]), c.QuoteWord(tableNames[1]),
@ -123,23 +133,41 @@ func (c *Core) Model(tableNameQueryOrStruct ...interface{}) *Model {
fields: "*",
start: -1,
offset: -1,
option: OptionAllowEmpty,
filter: true,
extraArgs: extraArgs,
}
}
// Raw creates and returns a model based on a raw sql not a table.
// Example:
// db.Raw("SELECT * FROM `user` WHERE `name` = ?", "john").Scan(&result)
func (c *Core) Raw(rawSql string, args ...interface{}) *Model {
model := c.Model()
model.rawSql = rawSql
model.extraArgs = args
return model
}
// Raw creates and returns a model based on a raw sql not a table.
// Example:
// db.Raw("SELECT * FROM `user` WHERE `name` = ?", "john").Scan(&result)
// See Core.Raw.
func (m *Model) Raw(rawSql string, args ...interface{}) *Model {
model := m.db.Raw(rawSql, args...)
model.db = m.db
model.tx = m.tx
return model
}
func (tx *TX) Raw(rawSql string, args ...interface{}) *Model {
return tx.Model().Raw(rawSql, args...)
}
// With creates and returns an ORM model based on meta data of given object.
func (c *Core) With(objects ...interface{}) *Model {
return c.db.Model().With(objects...)
}
// Table is alias of tx.Model.
// Deprecated, use Model instead.
func (tx *TX) Table(tableNameQueryOrStruct ...interface{}) *Model {
return tx.Model(tableNameQueryOrStruct...)
}
// Model acts like Core.Model except it operates on transaction.
// See Core.Model.
func (tx *TX) Model(tableNameQueryOrStruct ...interface{}) *Model {
@ -182,7 +210,7 @@ func (m *Model) As(as string) *Model {
if m.tables != "" {
model := m.getModel()
split := " JOIN "
if gstr.Contains(model.tables, split) {
if gstr.ContainsI(model.tables, split) {
// For join table.
array := gstr.Split(model.tables, split)
array[len(array)-1], _ = gregex.ReplaceString(`(.+) ON`, fmt.Sprintf(`$1 AS %s ON`, as), array[len(array)-1])
@ -234,7 +262,7 @@ func (m *Model) Clone() *Model {
copy(newModel.extraArgs, m.extraArgs)
}
if n := len(m.whereHolder); n > 0 {
newModel.whereHolder = make([]*whereHolder, n)
newModel.whereHolder = make([]ModelWhereHolder, n)
copy(newModel.whereHolder, m.whereHolder)
}
if n := len(m.withArray); n > 0 {
@ -276,3 +304,13 @@ func (m *Model) Args(args ...interface{}) *Model {
model.extraArgs = append(model.extraArgs, args)
return model
}
// Handler calls each of `handlers` on current Model and returns a new Model.
// ModelHandler is a function that handles given Model and returns a new Model that is custom modified.
func (m *Model) Handler(handlers ...ModelHandler) *Model {
model := m.getModel()
for _, handler := range handlers {
model = handler(model)
}
return model
}

View File

@ -8,6 +8,7 @@ package gdb
import (
"fmt"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
"strings"
)
@ -26,12 +27,12 @@ import (
func (m *Model) Where(where interface{}, args ...interface{}) *Model {
model := m.getModel()
if model.whereHolder == nil {
model.whereHolder = make([]*whereHolder, 0)
model.whereHolder = make([]ModelWhereHolder, 0)
}
model.whereHolder = append(model.whereHolder, &whereHolder{
operator: whereHolderWhere,
where: where,
args: args,
model.whereHolder = append(model.whereHolder, ModelWhereHolder{
Operator: whereHolderOperatorWhere,
Where: where,
Args: args,
})
return model
}
@ -60,55 +61,86 @@ func (m *Model) WherePri(where interface{}, args ...interface{}) *Model {
return m.Where(newWhere[0], newWhere[1:]...)
}
// WhereBetween builds `xxx BETWEEN x AND y` statement.
// Wheref builds condition string using fmt.Sprintf and arguments.
// Note that if the number of `args` is more than the place holder in `format`,
// the extra `args` will be used as the where condition arguments of the Model.
func (m *Model) Wheref(format string, args ...interface{}) *Model {
var (
placeHolderCount = gstr.Count(format, "?")
conditionStr = fmt.Sprintf(format, args[:len(args)-placeHolderCount]...)
)
return m.Where(conditionStr, args[len(args)-placeHolderCount:]...)
}
// WhereLT builds `column < value` statement.
func (m *Model) WhereLT(column string, value interface{}) *Model {
return m.Wheref(`%s < ?`, column, value)
}
// WhereLTE builds `column <= value` statement.
func (m *Model) WhereLTE(column string, value interface{}) *Model {
return m.Wheref(`%s <= ?`, column, value)
}
// WhereGT builds `column > value` statement.
func (m *Model) WhereGT(column string, value interface{}) *Model {
return m.Wheref(`%s > ?`, column, value)
}
// WhereGTE builds `column >= value` statement.
func (m *Model) WhereGTE(column string, value interface{}) *Model {
return m.Wheref(`%s >= ?`, column, value)
}
// WhereBetween builds `column BETWEEN min AND max` statement.
func (m *Model) WhereBetween(column string, min, max interface{}) *Model {
return m.Where(fmt.Sprintf(`%s BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column)), min, max)
return m.Wheref(`%s BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column), min, max)
}
// WhereLike builds `xxx LIKE x` statement.
// WhereLike builds `column LIKE like` statement.
func (m *Model) WhereLike(column string, like interface{}) *Model {
return m.Where(fmt.Sprintf(`%s LIKE ?`, m.db.GetCore().QuoteWord(column)), like)
return m.Wheref(`%s LIKE ?`, m.db.GetCore().QuoteWord(column), like)
}
// WhereIn builds `xxx IN (x)` statement.
// WhereIn builds `column IN (in)` statement.
func (m *Model) WhereIn(column string, in interface{}) *Model {
return m.Where(fmt.Sprintf(`%s IN (?)`, m.db.GetCore().QuoteWord(column)), in)
return m.Wheref(`%s IN (?)`, m.db.GetCore().QuoteWord(column), in)
}
// WhereNull builds `xxx IS NULL` statement.
// WhereNull builds `columns[0] IS NULL AND columns[1] IS NULL ...` statement.
func (m *Model) WhereNull(columns ...string) *Model {
model := m
for _, column := range columns {
model = m.Where(fmt.Sprintf(`%s IS NULL`, m.db.GetCore().QuoteWord(column)))
model = m.Wheref(`%s IS NULL`, m.db.GetCore().QuoteWord(column))
}
return model
}
// WhereNotBetween builds `xxx NOT BETWEEN x AND y` statement.
// WhereNotBetween builds `column NOT BETWEEN min AND max` statement.
func (m *Model) WhereNotBetween(column string, min, max interface{}) *Model {
return m.Where(fmt.Sprintf(`%s NOT BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column)), min, max)
return m.Wheref(`%s NOT BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column), min, max)
}
// WhereNotLike builds `xxx NOT LIKE x` statement.
// WhereNotLike builds `column NOT LIKE like` statement.
func (m *Model) WhereNotLike(column string, like interface{}) *Model {
return m.Where(fmt.Sprintf(`%s NOT LIKE ?`, m.db.GetCore().QuoteWord(column)), like)
return m.Wheref(`%s NOT LIKE ?`, m.db.GetCore().QuoteWord(column), like)
}
// WhereNot builds `xxx != x` statement.
// WhereNot builds `column != value` statement.
func (m *Model) WhereNot(column string, value interface{}) *Model {
return m.Where(fmt.Sprintf(`%s != ?`, m.db.GetCore().QuoteWord(column)), value)
return m.Wheref(`%s != ?`, m.db.GetCore().QuoteWord(column), value)
}
// WhereNotIn builds `xxx NOT IN (x)` statement.
// WhereNotIn builds `column NOT IN (in)` statement.
func (m *Model) WhereNotIn(column string, in interface{}) *Model {
return m.Where(fmt.Sprintf(`%s NOT IN (?)`, m.db.GetCore().QuoteWord(column)), in)
return m.Wheref(`%s NOT IN (?)`, m.db.GetCore().QuoteWord(column), in)
}
// WhereNotNull builds `xxx IS NOT NULL` statement.
// WhereNotNull builds `columns[0] IS NOT NULL AND columns[1] IS NOT NULL ...` statement.
func (m *Model) WhereNotNull(columns ...string) *Model {
model := m
for _, column := range columns {
model = m.Where(fmt.Sprintf(`%s IS NOT NULL`, m.db.GetCore().QuoteWord(column)))
model = m.Wheref(`%s IS NOT NULL`, m.db.GetCore().QuoteWord(column))
}
return model
}
@ -117,60 +149,89 @@ func (m *Model) WhereNotNull(columns ...string) *Model {
func (m *Model) WhereOr(where interface{}, args ...interface{}) *Model {
model := m.getModel()
if model.whereHolder == nil {
model.whereHolder = make([]*whereHolder, 0)
model.whereHolder = make([]ModelWhereHolder, 0)
}
model.whereHolder = append(model.whereHolder, &whereHolder{
operator: whereHolderOr,
where: where,
args: args,
model.whereHolder = append(model.whereHolder, ModelWhereHolder{
Operator: whereHolderOperatorOr,
Where: where,
Args: args,
})
return model
}
// WhereOrBetween builds `xxx BETWEEN x AND y` statement in `OR` conditions.
// WhereOrf builds `OR` condition string using fmt.Sprintf and arguments.
func (m *Model) WhereOrf(format string, args ...interface{}) *Model {
var (
placeHolderCount = gstr.Count(format, "?")
conditionStr = fmt.Sprintf(format, args[:len(args)-placeHolderCount]...)
)
return m.WhereOr(conditionStr, args[len(args)-placeHolderCount:]...)
}
// WhereOrLT builds `column < value` statement in `OR` conditions..
func (m *Model) WhereOrLT(column string, value interface{}) *Model {
return m.WhereOrf(`%s < ?`, column, value)
}
// WhereOrLTE builds `column <= value` statement in `OR` conditions..
func (m *Model) WhereOrLTE(column string, value interface{}) *Model {
return m.WhereOrf(`%s <= ?`, column, value)
}
// WhereOrGT builds `column > value` statement in `OR` conditions..
func (m *Model) WhereOrGT(column string, value interface{}) *Model {
return m.WhereOrf(`%s > ?`, column, value)
}
// WhereOrGTE builds `column >= value` statement in `OR` conditions..
func (m *Model) WhereOrGTE(column string, value interface{}) *Model {
return m.WhereOrf(`%s >= ?`, column, value)
}
// WhereOrBetween builds `column BETWEEN min AND max` statement in `OR` conditions.
func (m *Model) WhereOrBetween(column string, min, max interface{}) *Model {
return m.WhereOr(fmt.Sprintf(`%s BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column)), min, max)
return m.WhereOrf(`%s BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column), min, max)
}
// WhereOrLike builds `xxx LIKE x` statement in `OR` conditions.
// WhereOrLike builds `column LIKE like` statement in `OR` conditions.
func (m *Model) WhereOrLike(column string, like interface{}) *Model {
return m.WhereOr(fmt.Sprintf(`%s LIKE ?`, m.db.GetCore().QuoteWord(column)), like)
return m.WhereOrf(`%s LIKE ?`, m.db.GetCore().QuoteWord(column), like)
}
// WhereOrIn builds `xxx IN (x)` statement in `OR` conditions.
// WhereOrIn builds `column IN (in)` statement in `OR` conditions.
func (m *Model) WhereOrIn(column string, in interface{}) *Model {
return m.WhereOr(fmt.Sprintf(`%s IN (?)`, m.db.GetCore().QuoteWord(column)), in)
return m.WhereOrf(`%s IN (?)`, m.db.GetCore().QuoteWord(column), in)
}
// WhereOrNull builds `xxx IS NULL` statement in `OR` conditions.
// WhereOrNull builds `columns[0] IS NULL OR columns[1] IS NULL ...` statement in `OR` conditions.
func (m *Model) WhereOrNull(columns ...string) *Model {
model := m
for _, column := range columns {
model = m.WhereOr(fmt.Sprintf(`%s IS NULL`, m.db.GetCore().QuoteWord(column)))
model = m.WhereOrf(`%s IS NULL`, m.db.GetCore().QuoteWord(column))
}
return model
}
// WhereOrNotBetween builds `xxx NOT BETWEEN x AND y` statement in `OR` conditions.
// WhereOrNotBetween builds `column NOT BETWEEN min AND max` statement in `OR` conditions.
func (m *Model) WhereOrNotBetween(column string, min, max interface{}) *Model {
return m.WhereOr(fmt.Sprintf(`%s NOT BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column)), min, max)
return m.WhereOrf(`%s NOT BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column), min, max)
}
// WhereOrNotLike builds `xxx NOT LIKE x` statement in `OR` conditions.
// WhereOrNotLike builds `column NOT LIKE like` statement in `OR` conditions.
func (m *Model) WhereOrNotLike(column string, like interface{}) *Model {
return m.WhereOr(fmt.Sprintf(`%s NOT LIKE ?`, m.db.GetCore().QuoteWord(column)), like)
return m.WhereOrf(`%s NOT LIKE ?`, m.db.GetCore().QuoteWord(column), like)
}
// WhereOrNotIn builds `xxx NOT IN (x)` statement.
// WhereOrNotIn builds `column NOT IN (in)` statement.
func (m *Model) WhereOrNotIn(column string, in interface{}) *Model {
return m.WhereOr(fmt.Sprintf(`%s NOT IN (?)`, m.db.GetCore().QuoteWord(column)), in)
return m.WhereOrf(`%s NOT IN (?)`, m.db.GetCore().QuoteWord(column), in)
}
// WhereOrNotNull builds `xxx IS NOT NULL` statement in `OR` conditions.
// WhereOrNotNull builds `columns[0] IS NOT NULL OR columns[1] IS NOT NULL ...` statement in `OR` conditions.
func (m *Model) WhereOrNotNull(columns ...string) *Model {
model := m
for _, column := range columns {
model = m.WhereOr(fmt.Sprintf(`%s IS NOT NULL`, m.db.GetCore().QuoteWord(column)))
model = m.WhereOrf(`%s IS NOT NULL`, m.db.GetCore().QuoteWord(column))
}
return model
}
@ -187,12 +248,12 @@ func (m *Model) Group(groupBy string) *Model {
func (m *Model) And(where interface{}, args ...interface{}) *Model {
model := m.getModel()
if model.whereHolder == nil {
model.whereHolder = make([]*whereHolder, 0)
model.whereHolder = make([]ModelWhereHolder, 0)
}
model.whereHolder = append(model.whereHolder, &whereHolder{
operator: whereHolderAnd,
where: where,
args: args,
model.whereHolder = append(model.whereHolder, ModelWhereHolder{
Operator: whereHolderOperatorAnd,
Where: where,
Args: args,
})
return model
}
@ -216,6 +277,9 @@ func (m *Model) Order(orderBy ...string) *Model {
return m
}
model := m.getModel()
if model.orderBy != "" {
model.orderBy += ","
}
model.orderBy = m.db.GetCore().QuoteString(strings.Join(orderBy, " "))
return model
}
@ -226,6 +290,9 @@ func (m *Model) OrderAsc(column string) *Model {
return m
}
model := m.getModel()
if model.orderBy != "" {
model.orderBy += ","
}
model.orderBy = m.db.GetCore().QuoteWord(column) + " ASC"
return model
}
@ -236,6 +303,9 @@ func (m *Model) OrderDesc(column string) *Model {
return m
}
model := m.getModel()
if model.orderBy != "" {
model.orderBy += ","
}
model.orderBy = m.db.GetCore().QuoteWord(column) + " DESC"
return model
}
@ -312,11 +382,11 @@ func (m *Model) ForPage(page, limit int) *Model {
func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWhere string, conditionExtra string, conditionArgs []interface{}) {
if len(m.whereHolder) > 0 {
for _, v := range m.whereHolder {
switch v.operator {
case whereHolderWhere:
switch v.Operator {
case whereHolderOperatorWhere:
if conditionWhere == "" {
newWhere, newArgs := formatWhere(
m.db, v.where, v.args, m.option&OptionOmitEmpty > 0,
m.db, v.Where, v.Args, m.option&optionOmitEmptyWhere > 0, m.schema, m.tables,
)
if len(newWhere) > 0 {
conditionWhere = newWhere
@ -326,9 +396,9 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
}
fallthrough
case whereHolderAnd:
case whereHolderOperatorAnd:
newWhere, newArgs := formatWhere(
m.db, v.where, v.args, m.option&OptionOmitEmpty > 0,
m.db, v.Where, v.Args, m.option&optionOmitEmptyWhere > 0, m.schema, m.tables,
)
if len(newWhere) > 0 {
if len(conditionWhere) == 0 {
@ -341,9 +411,9 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
conditionArgs = append(conditionArgs, newArgs...)
}
case whereHolderOr:
case whereHolderOperatorOr:
newWhere, newArgs := formatWhere(
m.db, v.where, v.args, m.option&OptionOmitEmpty > 0,
m.db, v.Where, v.Args, m.option&optionOmitEmptyWhere > 0, m.schema, m.tables,
)
if len(newWhere) > 0 {
if len(conditionWhere) == 0 {
@ -360,7 +430,13 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
}
// Soft deletion.
softDeletingCondition := m.getConditionForSoftDeleting()
if !m.unscoped && softDeletingCondition != "" {
if m.rawSql != "" && conditionWhere != "" {
if gstr.ContainsI(m.rawSql, " WHERE ") {
conditionWhere = " AND " + conditionWhere
} else {
conditionWhere = " WHERE " + conditionWhere
}
} else if !m.unscoped && softDeletingCondition != "" {
if conditionWhere == "" {
conditionWhere = fmt.Sprintf(` WHERE %s`, softDeletingCondition)
} else {
@ -371,6 +447,7 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
conditionWhere = " WHERE " + conditionWhere
}
}
// GROUP BY.
if m.groupBy != "" {
conditionExtra += " GROUP BY " + m.groupBy
@ -378,7 +455,7 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
// HAVING.
if len(m.having) > 0 {
havingStr, havingArgs := formatWhere(
m.db, m.having[0], gconv.Interfaces(m.having[1]), m.option&OptionOmitEmpty > 0,
m.db, m.having[0], gconv.Interfaces(m.having[1]), m.option&optionOmitEmptyWhere > 0, m.schema, m.tables,
)
if len(havingStr) > 0 {
conditionExtra += " HAVING " + havingStr

View File

@ -33,7 +33,7 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
)
// Soft deleting.
if !m.unscoped && fieldNameDelete != "" {
return m.db.GetCore().DoUpdate(
return m.db.DoUpdate(
m.GetCtx(),
m.getLink(true),
m.tables,
@ -44,7 +44,7 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
}
conditionStr := conditionWhere + conditionExtra
if !gstr.ContainsI(conditionStr, " WHERE ") {
return nil, gerror.New("there should be WHERE condition statement for DELETE operation")
return nil, gerror.NewCode(gerror.CodeMissingParameter, "there should be WHERE condition statement for DELETE operation")
}
return m.db.GetCore().DoDelete(m.GetCtx(), m.getLink(true), m.tables, conditionStr, conditionArgs...)
return m.db.DoDelete(m.GetCtx(), m.getLink(true), m.tables, conditionStr, conditionArgs...)
}

View File

@ -90,13 +90,13 @@ func (m *Model) FieldsStr(prefix ...string) string {
}
// GetFieldsStr retrieves and returns all fields from the table, joined with char ','.
// The optional parameter `prefix` specifies the prefix for each field, eg: FieldsStr("u.").
// The optional parameter `prefix` specifies the prefix for each field, eg: GetFieldsStr("u.").
func (m *Model) GetFieldsStr(prefix ...string) string {
prefixStr := ""
if len(prefix) > 0 {
prefixStr = prefix[0]
}
tableFields, err := m.TableFields(m.tables)
tableFields, err := m.TableFields(m.tablesInit)
if err != nil {
panic(err)
}
@ -136,7 +136,7 @@ func (m *Model) GetFieldsExStr(fields string, prefix ...string) string {
if len(prefix) > 0 {
prefixStr = prefix[0]
}
tableFields, err := m.TableFields(m.tables)
tableFields, err := m.TableFields(m.tablesInit)
if err != nil {
panic(err)
}
@ -164,7 +164,7 @@ func (m *Model) GetFieldsExStr(fields string, prefix ...string) string {
// HasField determine whether the field exists in the table.
func (m *Model) HasField(field string) (bool, error) {
tableFields, err := m.TableFields(m.tables)
tableFields, err := m.TableFields(m.tablesInit)
if err != nil {
return false, err
}

View File

@ -8,6 +8,7 @@ package gdb
import (
"database/sql"
"github.com/gogf/gf/container/gset"
"reflect"
"github.com/gogf/gf/errors/gerror"
@ -51,16 +52,20 @@ func (m *Model) Data(data ...interface{}) *Model {
switch params := data[0].(type) {
case Result:
model.data = params.List()
case Record:
model.data = params.Map()
case List:
list := make(List, len(params))
for k, v := range params {
list[k] = gutil.MapCopy(v)
}
model.data = list
case Map:
model.data = gutil.MapCopy(params)
default:
var (
rv = reflect.ValueOf(params)
@ -100,6 +105,48 @@ func (m *Model) Data(data ...interface{}) *Model {
return model
}
// OnDuplicate sets the operations when columns conflicts occurs.
// In MySQL, this is used for "ON DUPLICATE KEY UPDATE" statement.
// The parameter `onDuplicate` can be type of string/Raw/*Raw/map/slice.
// Example:
// OnDuplicate("nickname, age")
// OnDuplicate("nickname", "age")
// OnDuplicate(g.Map{
// "nickname": gdb.Raw("CONCAT('name_', VALUES(`nickname`))"),
// })
// OnDuplicate(g.Map{
// "nickname": "passport",
// })
func (m *Model) OnDuplicate(onDuplicate ...interface{}) *Model {
model := m.getModel()
if len(onDuplicate) > 1 {
model.onDuplicate = onDuplicate
} else {
model.onDuplicate = onDuplicate[0]
}
return model
}
// OnDuplicateEx sets the excluding columns for operations when columns conflicts occurs.
// In MySQL, this is used for "ON DUPLICATE KEY UPDATE" statement.
// The parameter `onDuplicateEx` can be type of string/map/slice.
// Example:
// OnDuplicateEx("passport, password")
// OnDuplicateEx("passport", "password")
// OnDuplicateEx(g.Map{
// "passport": "",
// "password": "",
// })
func (m *Model) OnDuplicateEx(onDuplicateEx ...interface{}) *Model {
model := m.getModel()
if len(onDuplicateEx) > 1 {
model.onDuplicateEx = onDuplicateEx
} else {
model.onDuplicateEx = onDuplicateEx[0]
}
return model
}
// Insert does "INSERT INTO ..." statement for the model.
// The optional parameter `data` is the same as the parameter of Model.Data function,
// see Model.Data.
@ -156,78 +203,224 @@ func (m *Model) Save(data ...interface{}) (result sql.Result, err error) {
}
// doInsertWithOption inserts data with option parameter.
func (m *Model) doInsertWithOption(option int) (result sql.Result, err error) {
func (m *Model) doInsertWithOption(insertOption int) (result sql.Result, err error) {
defer func() {
if err == nil {
m.checkAndRemoveCache()
}
}()
if m.data == nil {
return nil, gerror.New("inserting into table with empty data")
return nil, gerror.NewCode(gerror.CodeMissingParameter, "inserting into table with empty data")
}
var (
list List
nowString = gtime.Now().String()
fieldNameCreate = m.getSoftFieldNameCreated()
fieldNameUpdate = m.getSoftFieldNameUpdated()
fieldNameDelete = m.getSoftFieldNameDeleted()
)
// Batch operation.
if list, ok := m.data.(List); ok {
batch := defaultBatchNumber
if m.batch > 0 {
batch = m.batch
}
newData, err := m.filterDataForInsertOrUpdate(list)
if err != nil {
return nil, err
}
list = newData.(List)
// Automatic handling for creating/updating time.
if !m.unscoped && (fieldNameCreate != "" || fieldNameUpdate != "") {
for k, v := range list {
gutil.MapDelete(v, fieldNameCreate, fieldNameUpdate, fieldNameDelete)
if fieldNameCreate != "" {
v[fieldNameCreate] = nowString
}
if fieldNameUpdate != "" {
v[fieldNameUpdate] = nowString
}
list[k] = v
}
}
return m.db.GetCore().DoBatchInsert(
m.GetCtx(),
m.getLink(true),
m.tables,
newData,
option,
batch,
)
newData, err := m.filterDataForInsertOrUpdate(m.data)
if err != nil {
return nil, err
}
// Single operation.
if data, ok := m.data.(Map); ok {
newData, err := m.filterDataForInsertOrUpdate(data)
if err != nil {
return nil, err
// It converts any data to List type for inserting.
switch value := newData.(type) {
case Result:
list = value.List()
case Record:
list = List{value.Map()}
case List:
list = value
for i, v := range list {
list[i] = ConvertDataForTableRecord(v)
}
data = newData.(Map)
// Automatic handling for creating/updating time.
if !m.unscoped && (fieldNameCreate != "" || fieldNameUpdate != "") {
gutil.MapDelete(data, fieldNameCreate, fieldNameUpdate, fieldNameDelete)
case Map:
list = List{ConvertDataForTableRecord(value)}
default:
var (
rv = reflect.ValueOf(newData)
kind = rv.Kind()
)
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
// If it's slice type, it then converts it to List type.
case reflect.Slice, reflect.Array:
list = make(List, rv.Len())
for i := 0; i < rv.Len(); i++ {
list[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
}
case reflect.Map:
list = List{ConvertDataForTableRecord(value)}
case reflect.Struct:
if v, ok := value.(apiInterfaces); ok {
var (
array = v.Interfaces()
)
list = make(List, len(array))
for i := 0; i < len(array); i++ {
list[i] = ConvertDataForTableRecord(array[i])
}
} else {
list = List{ConvertDataForTableRecord(value)}
}
default:
return result, gerror.NewCodef(gerror.CodeInvalidParameter, "unsupported list type:%v", kind)
}
}
if len(list) < 1 {
return result, gerror.NewCode(gerror.CodeMissingParameter, "data list cannot be empty")
}
// Automatic handling for creating/updating time.
if !m.unscoped && (fieldNameCreate != "" || fieldNameUpdate != "") {
for k, v := range list {
gutil.MapDelete(v, fieldNameCreate, fieldNameUpdate, fieldNameDelete)
if fieldNameCreate != "" {
data[fieldNameCreate] = nowString
v[fieldNameCreate] = nowString
}
if fieldNameUpdate != "" {
data[fieldNameUpdate] = nowString
v[fieldNameUpdate] = nowString
}
list[k] = v
}
}
// Format DoInsertOption, especially for "ON DUPLICATE KEY UPDATE" statement.
columnNames := make([]string, 0, len(list[0]))
for k, _ := range list[0] {
columnNames = append(columnNames, k)
}
doInsertOption, err := m.formatDoInsertOption(insertOption, columnNames)
if err != nil {
return result, err
}
return m.db.DoInsert(m.GetCtx(), m.getLink(true), m.tables, list, doInsertOption)
}
func (m *Model) formatDoInsertOption(insertOption int, columnNames []string) (option DoInsertOption, err error) {
option = DoInsertOption{
InsertOption: insertOption,
BatchCount: m.getBatch(),
}
if insertOption == insertOptionSave {
onDuplicateExKeys, err := m.formatOnDuplicateExKeys(m.onDuplicateEx)
if err != nil {
return option, err
}
var (
onDuplicateExKeySet = gset.NewStrSetFrom(onDuplicateExKeys)
)
if m.onDuplicate != nil {
switch m.onDuplicate.(type) {
case Raw, *Raw:
option.OnDuplicateStr = gconv.String(m.onDuplicate)
default:
var (
reflectValue = reflect.ValueOf(m.onDuplicate)
reflectKind = reflectValue.Kind()
)
for reflectKind == reflect.Ptr {
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
}
switch reflectKind {
case reflect.String:
option.OnDuplicateMap = make(map[string]interface{})
for _, v := range gstr.SplitAndTrim(reflectValue.String(), ",") {
if onDuplicateExKeySet.Contains(v) {
continue
}
option.OnDuplicateMap[v] = v
}
case reflect.Map:
option.OnDuplicateMap = make(map[string]interface{})
for k, v := range gconv.Map(m.onDuplicate) {
if onDuplicateExKeySet.Contains(k) {
continue
}
option.OnDuplicateMap[k] = v
}
case reflect.Slice, reflect.Array:
option.OnDuplicateMap = make(map[string]interface{})
for _, v := range gconv.Strings(m.onDuplicate) {
if onDuplicateExKeySet.Contains(v) {
continue
}
option.OnDuplicateMap[v] = v
}
default:
return option, gerror.NewCodef(
gerror.CodeInvalidParameter,
`unsupported OnDuplicate parameter type "%s"`,
reflect.TypeOf(m.onDuplicate),
)
}
}
} else if onDuplicateExKeySet.Size() > 0 {
option.OnDuplicateMap = make(map[string]interface{})
for _, v := range columnNames {
if onDuplicateExKeySet.Contains(v) {
continue
}
option.OnDuplicateMap[v] = v
}
}
return m.db.GetCore().DoInsert(
m.GetCtx(),
m.getLink(true),
m.tables,
newData,
option,
}
return
}
func (m *Model) formatOnDuplicateExKeys(onDuplicateEx interface{}) ([]string, error) {
if onDuplicateEx == nil {
return nil, nil
}
var (
reflectValue = reflect.ValueOf(onDuplicateEx)
reflectKind = reflectValue.Kind()
)
for reflectKind == reflect.Ptr {
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
}
switch reflectKind {
case reflect.String:
return gstr.SplitAndTrim(reflectValue.String(), ","), nil
case reflect.Map:
return gutil.Keys(onDuplicateEx), nil
case reflect.Slice, reflect.Array:
return gconv.Strings(onDuplicateEx), nil
default:
return nil, gerror.NewCodef(
gerror.CodeInvalidParameter,
`unsupported OnDuplicateEx parameter type "%s"`,
reflect.TypeOf(onDuplicateEx),
)
}
return nil, gerror.New("inserting into table with invalid data type")
}
func (m *Model) getBatch() int {
batch := defaultBatchNumber
if m.batch > 0 {
batch = m.batch
}
return batch
}

View File

@ -56,9 +56,9 @@ func (m *Model) InnerJoin(table ...string) *Model {
// doJoin does "LEFT/RIGHT/INNER JOIN ... ON ..." statement on the model.
// The parameter `table` can be joined table and its joined condition,
// and also with its alias name, like:
// Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
// Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
// Table("user", "u").InnerJoin("SELECT xxx FROM xxx AS a", "a.uid=u.uid")
// Model("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
// Model("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
// Model("user", "u").InnerJoin("SELECT xxx FROM xxx AS a", "a.uid=u.uid")
// Related issues:
// https://github.com/gogf/gf/issues/1024
func (m *Model) doJoin(operator string, table ...string) *Model {

View File

@ -6,22 +6,40 @@
package gdb
const (
optionOmitEmpty = optionOmitEmptyWhere | optionOmitEmptyData
optionOmitEmptyWhere = 1 << iota // 8
optionOmitEmptyData // 16
)
// Option adds extra operation option for the model.
// Deprecated, use separate operations instead.
func (m *Model) Option(option int) *Model {
model := m.getModel()
model.option = model.option | option
return model
}
// OptionOmitEmpty sets OptionOmitEmpty option for the model, which automatically filers
// the data and where attributes for empty values.
// Deprecated, use OmitEmpty instead.
func (m *Model) OptionOmitEmpty() *Model {
return m.Option(OptionOmitEmpty)
// OmitEmpty sets OmitEmpty option for the model, which automatically filers
// the data and where parameters for `empty` values.
func (m *Model) OmitEmpty() *Model {
model := m.getModel()
model.option = model.option | optionOmitEmpty
return model
}
// OmitEmpty sets OptionOmitEmpty option for the model, which automatically filers
// the data and where attributes for empty values.
func (m *Model) OmitEmpty() *Model {
return m.Option(OptionOmitEmpty)
// OmitEmptyWhere sets OmitEmptyWhere option for the model, which automatically filers
// the Where/Having parameters for `empty` values.
func (m *Model) OmitEmptyWhere() *Model {
model := m.getModel()
model.option = model.option | optionOmitEmptyWhere
return model
}
// OmitEmptyData sets OmitEmptyData option for the model, which automatically filers
// the Data parameters for `empty` values.
func (m *Model) OmitEmptyData() *Model {
model := m.getModel()
model.option = model.option | optionOmitEmptyData
return model
}

View File

@ -8,6 +8,7 @@ package gdb
import (
"fmt"
"github.com/gogf/gf/errors/gerror"
"reflect"
"github.com/gogf/gf/container/gset"
@ -18,11 +19,6 @@ import (
"github.com/gogf/gf/util/gconv"
)
const (
queryTypeNormal = "NormalQuery"
queryTypeCount = "CountQuery"
)
// Select is alias of Model.All.
// See Model.All.
// Deprecated, use All instead.
@ -80,7 +76,7 @@ func (m *Model) getFieldsFiltered() string {
panic("function FieldsEx supports only single table operations")
}
// Filter table fields with fieldEx.
tableFields, err := m.TableFields(m.tables)
tableFields, err := m.TableFields(m.tablesInit)
if err != nil {
panic(err)
}
@ -105,27 +101,27 @@ func (m *Model) getFieldsFiltered() string {
return newFields
}
// Chunk iterates the query result with given size and callback function.
func (m *Model) Chunk(limit int, callback func(result Result, err error) bool) {
// Chunk iterates the query result with given `size` and `handler` function.
func (m *Model) Chunk(size int, handler ChunkHandler) {
page := m.start
if page <= 0 {
page = 1
}
model := m
for {
model = model.Page(page, limit)
model = model.Page(page, size)
data, err := model.All()
if err != nil {
callback(nil, err)
handler(nil, err)
break
}
if len(data) == 0 {
break
}
if callback(data, err) == false {
if handler(data, err) == false {
break
}
if len(data) < limit {
if len(data) < size {
break
}
page++
@ -200,6 +196,15 @@ func (m *Model) Array(fieldsAndWhere ...interface{}) ([]Value, error) {
return all.Array(), nil
}
// Struct retrieves one record from table and converts it into given struct.
// The parameter `pointer` should be type of *struct/**struct. If type **struct is given,
// it can create the struct internally during converting.
//
// Deprecated, use Scan instead.
func (m *Model) Struct(pointer interface{}, where ...interface{}) error {
return m.doStruct(pointer, where...)
}
// Struct retrieves one record from table and converts it into given struct.
// The parameter `pointer` should be type of *struct/**struct. If type **struct is given,
// it can create the struct internally during converting.
@ -207,24 +212,38 @@ func (m *Model) Array(fieldsAndWhere ...interface{}) ([]Value, error) {
// The optional parameter `where` is the same as the parameter of Model.Where function,
// see Model.Where.
//
// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions
// from table and `pointer` is not nil.
// Note that it returns sql.ErrNoRows if the given parameter `pointer` pointed to a variable that has
// default value and there's no record retrieved with the given conditions from table.
//
// Eg:
// Example:
// user := new(User)
// err := db.Model("user").Where("id", 1).Struct(user)
// err := db.Model("user").Where("id", 1).Scan(user)
//
// user := (*User)(nil)
// err := db.Model("user").Where("id", 1).Struct(&user)
func (m *Model) Struct(pointer interface{}, where ...interface{}) error {
one, err := m.One(where...)
// err := db.Model("user").Where("id", 1).Scan(&user)
func (m *Model) doStruct(pointer interface{}, where ...interface{}) error {
model := m
// Auto selecting fields by struct attributes.
if model.fieldsEx == "" && (model.fields == "" || model.fields == "*") {
model = m.Fields(pointer)
}
one, err := model.One(where...)
if err != nil {
return err
}
if err = one.Struct(pointer); err != nil {
return err
}
return m.doWithScanStruct(pointer)
return model.doWithScanStruct(pointer)
}
// Structs retrieves records from table and converts them into given struct slice.
// The parameter `pointer` should be type of *[]struct/*[]*struct. It can create and fill the struct
// slice internally during converting.
//
// Deprecated, use Scan instead.
func (m *Model) Structs(pointer interface{}, where ...interface{}) error {
return m.doStructs(pointer, where...)
}
// Structs retrieves records from table and converts them into given struct slice.
@ -234,37 +253,45 @@ func (m *Model) Struct(pointer interface{}, where ...interface{}) error {
// The optional parameter `where` is the same as the parameter of Model.Where function,
// see Model.Where.
//
// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions
// from table and `pointer` is not empty.
// Note that it returns sql.ErrNoRows if the given parameter `pointer` pointed to a variable that has
// default value and there's no record retrieved with the given conditions from table.
//
// Eg:
// Example:
// users := ([]User)(nil)
// err := db.Model("user").Structs(&users)
// err := db.Model("user").Scan(&users)
//
// users := ([]*User)(nil)
// err := db.Model("user").Structs(&users)
func (m *Model) Structs(pointer interface{}, where ...interface{}) error {
all, err := m.All(where...)
// err := db.Model("user").Scan(&users)
func (m *Model) doStructs(pointer interface{}, where ...interface{}) error {
model := m
// Auto selecting fields by struct attributes.
if model.fieldsEx == "" && (model.fields == "" || model.fields == "*") {
model = m.Fields(
reflect.New(
reflect.ValueOf(pointer).Elem().Type().Elem(),
).Interface(),
)
}
all, err := model.All(where...)
if err != nil {
return err
}
if err = all.Structs(pointer); err != nil {
return err
}
return m.doWithScanStructs(pointer)
return model.doWithScanStructs(pointer)
}
// Scan automatically calls Struct or Structs function according to the type of parameter `pointer`.
// It calls function Struct if `pointer` is type of *struct/**struct.
// It calls function Structs if `pointer` is type of *[]struct/*[]*struct.
// It calls function doStruct if `pointer` is type of *struct/**struct.
// It calls function doStructs if `pointer` is type of *[]struct/*[]*struct.
//
// The optional parameter `where` is the same as the parameter of Model.Where function,
// see Model.Where.
// The optional parameter `where` is the same as the parameter of Model.Where function, see Model.Where.
//
// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions
// from table.
// Note that it returns sql.ErrNoRows if the given parameter `pointer` pointed to a variable that has
// default value and there's no record retrieved with the given conditions from table.
//
// Eg:
// Example:
// user := new(User)
// err := db.Model("user").Where("id", 1).Scan(user)
//
@ -277,16 +304,38 @@ func (m *Model) Structs(pointer interface{}, where ...interface{}) error {
// users := ([]*User)(nil)
// err := db.Model("user").Scan(&users)
func (m *Model) Scan(pointer interface{}, where ...interface{}) error {
var reflectType reflect.Type
var (
reflectValue reflect.Value
reflectKind reflect.Kind
)
if v, ok := pointer.(reflect.Value); ok {
reflectType = v.Type()
reflectValue = v
} else {
reflectType = reflect.TypeOf(pointer)
reflectValue = reflect.ValueOf(pointer)
}
if gstr.Contains(reflectType.String(), "[]") {
return m.Structs(pointer, where...)
reflectKind = reflectValue.Kind()
if reflectKind != reflect.Ptr {
return gerror.NewCode(gerror.CodeInvalidParameter, `the parameter "pointer" for function Scan should type of pointer`)
}
for reflectKind == reflect.Ptr {
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
}
switch reflectKind {
case reflect.Slice, reflect.Array:
return m.doStructs(pointer, where...)
case reflect.Struct, reflect.Invalid:
return m.doStruct(pointer, where...)
default:
return gerror.NewCode(
gerror.CodeInvalidParameter,
`element of parameter "pointer" for function Scan should type of struct/*struct/[]struct/[]*struct`,
)
}
return m.Struct(pointer, where...)
}
// ScanList converts `r` to struct slice which contains other complex struct attributes.
@ -312,11 +361,11 @@ func (m *Model) Scan(pointer interface{}, where ...interface{}) error {
// parameter.
// See the example or unit testing cases for clear understanding for this function.
func (m *Model) ScanList(listPointer interface{}, attributeName string, relation ...string) (err error) {
all, err := m.All()
result, err := m.All()
if err != nil {
return err
}
return all.ScanList(listPointer, attributeName, relation...)
return doScanList(m, result, listPointer, attributeName, relation...)
}
// Count does "SELECT COUNT(x) FROM ..." statement for the model.
@ -458,6 +507,16 @@ func (m *Model) FindScan(pointer interface{}, where ...interface{}) error {
return m.Scan(pointer)
}
// Union does "(SELECT xxx FROM xxx) UNION (SELECT xxx FROM xxx) ..." statement for the model.
func (m *Model) Union(unions ...*Model) *Model {
return m.db.Union(unions...)
}
// UnionAll does "(SELECT xxx FROM xxx) UNION ALL (SELECT xxx FROM xxx) ..." statement for the model.
func (m *Model) UnionAll(unions ...*Model) *Model {
return m.db.UnionAll(unions...)
}
// doGetAllBySql does the select statement on the database.
func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, err error) {
cacheKey := ""
@ -483,25 +542,29 @@ func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, e
}
}
}
result, err = m.db.GetCore().DoGetAll(
result, err = m.db.DoGetAll(
m.GetCtx(), m.getLink(false), sql, m.mergeArguments(args)...,
)
// Cache the result.
if cacheKey != "" && err == nil {
if m.cacheDuration < 0 {
if _, err := cacheObj.Remove(cacheKey); err != nil {
intlog.Error(err)
intlog.Error(m.GetCtx(), err)
}
} else {
// In case of Cache Penetration.
if result == nil {
result = Result{}
}
if err := cacheObj.Set(cacheKey, result, m.cacheDuration); err != nil {
intlog.Error(err)
intlog.Error(m.GetCtx(), err)
}
}
}
return result, err
}
func (m *Model) getFormattedSqlAndArgs(queryType string, limit1 bool) (sqlWithHolder string, holderArgs []interface{}) {
func (m *Model) getFormattedSqlAndArgs(queryType int, limit1 bool) (sqlWithHolder string, holderArgs []interface{}) {
switch queryType {
case queryTypeCount:
countFields := "COUNT(1)"
@ -510,6 +573,11 @@ func (m *Model) getFormattedSqlAndArgs(queryType string, limit1 bool) (sqlWithHo
// DISTINCT t.user_id uid
countFields = fmt.Sprintf(`COUNT(%s%s)`, m.distinct, m.fields)
}
// Raw SQL Model.
if m.rawSql != "" {
sqlWithHolder = fmt.Sprintf("SELECT %s FROM (%s) AS T", countFields, m.rawSql)
return sqlWithHolder, nil
}
conditionWhere, conditionExtra, conditionArgs := m.formatCondition(false, true)
sqlWithHolder = fmt.Sprintf("SELECT %s FROM %s%s", countFields, m.tables, conditionWhere+conditionExtra)
if len(m.groupBy) > 0 {
@ -519,6 +587,15 @@ func (m *Model) getFormattedSqlAndArgs(queryType string, limit1 bool) (sqlWithHo
default:
conditionWhere, conditionExtra, conditionArgs := m.formatCondition(limit1, false)
// Raw SQL Model, especially for UNION/UNION ALL featured SQL.
if m.rawSql != "" {
sqlWithHolder = fmt.Sprintf(
"%s%s",
m.rawSql,
conditionWhere+conditionExtra,
)
return sqlWithHolder, conditionArgs
}
// DO NOT quote the m.fields where, in case of fields like:
// DISTINCT t.user_id uid
sqlWithHolder = fmt.Sprintf(

View File

@ -40,7 +40,7 @@ func (m *Model) getSoftFieldNameCreated(table ...string) string {
if len(table) > 0 {
tableName = table[0]
} else {
tableName = m.getPrimaryTableName()
tableName = m.tablesInit
}
config := m.db.GetConfig()
if config.CreatedAt != "" {
@ -61,7 +61,7 @@ func (m *Model) getSoftFieldNameUpdated(table ...string) (field string) {
if len(table) > 0 {
tableName = table[0]
} else {
tableName = m.getPrimaryTableName()
tableName = m.tablesInit
}
config := m.db.GetConfig()
if config.UpdatedAt != "" {
@ -82,7 +82,7 @@ func (m *Model) getSoftFieldNameDeleted(table ...string) (field string) {
if len(table) > 0 {
tableName = table[0]
} else {
tableName = m.getPrimaryTableName()
tableName = m.tablesInit
}
config := m.db.GetConfig()
if config.UpdatedAt != "" {
@ -170,14 +170,3 @@ func (m *Model) getConditionOfTableStringForSoftDeleting(s string) string {
}
return fmt.Sprintf(`%s.%s IS NULL`, m.db.GetCore().QuoteWord(table), m.db.GetCore().QuoteWord(field))
}
// getPrimaryTableName parses and returns the primary table name.
func (m *Model) getPrimaryTableName() string {
array1 := gstr.SplitAndTrim(m.tables, ",")
array2 := gstr.SplitAndTrim(array1[0], " ")
array3 := gstr.SplitAndTrim(array2[0], ".")
if len(array3) >= 2 {
return array3[1]
}
return array3[0]
}

View File

@ -39,7 +39,7 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
}
}()
if m.data == nil {
return nil, gerror.New("updating table with empty data")
return nil, gerror.NewCode(gerror.CodeMissingParameter, "updating table with empty data")
}
var (
updateData = m.data
@ -80,9 +80,9 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
}
conditionStr := conditionWhere + conditionExtra
if !gstr.ContainsI(conditionStr, " WHERE ") {
return nil, gerror.New("there should be WHERE condition statement for UPDATE operation")
return nil, gerror.NewCode(gerror.CodeMissingParameter, "there should be WHERE condition statement for UPDATE operation")
}
return m.db.GetCore().DoUpdate(
return m.db.DoUpdate(
m.GetCtx(),
m.getLink(true),
m.tables,
@ -93,17 +93,19 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
}
// Increment increments a column's value by a given amount.
func (m *Model) Increment(column string, amount float64) (sql.Result, error) {
// The parameter `amount` can be type of float or integer.
func (m *Model) Increment(column string, amount interface{}) (sql.Result, error) {
return m.getModel().Data(column, &Counter{
Field: column,
Value: amount,
Value: gconv.Float64(amount),
}).Update()
}
// Decrement decrements a column's value by a given amount.
func (m *Model) Decrement(column string, amount float64) (sql.Result, error) {
// The parameter `amount` can be type of float or integer.
func (m *Model) Decrement(column string, amount interface{}) (sql.Result, error) {
return m.getModel().Data(column, &Counter{
Field: column,
Value: -amount,
Value: -gconv.Float64(amount),
}).Update()
}

View File

@ -21,15 +21,12 @@ import (
// schema.
//
// Also see DriverMysql.TableFields.
func (m *Model) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
charL, charR := m.db.GetChars()
if charL != "" || charR != "" {
table = gstr.Trim(table, charL+charR)
func (m *Model) TableFields(tableStr string, schema ...string) (fields map[string]*TableField, err error) {
useSchema := m.schema
if len(schema) > 0 && schema[0] != "" {
useSchema = schema[0]
}
if !gregex.IsMatchString(regularFieldNameRegPattern, table) {
return nil, nil
}
return m.db.TableFields(m.GetCtx(), m.getLink(false), table, schema...)
return m.db.TableFields(m.GetCtx(), m.guessPrimaryTableName(tableStr), useSchema)
}
// getModel creates and returns a cloned model of current model if `safe` is true, or else it returns
@ -47,7 +44,7 @@ func (m *Model) getModel() *Model {
// ID -> id
// NICK_Name -> nickname
func (m *Model) mappingAndFilterToTableFields(fields []string, filter bool) []string {
fieldsMap, err := m.TableFields(m.tables)
fieldsMap, err := m.TableFields(m.tablesInit)
if err != nil || len(fieldsMap) == 0 {
return fields
}
@ -106,12 +103,14 @@ func (m *Model) filterDataForInsertOrUpdate(data interface{}) (interface{}, erro
// Note that, it does not filter list item, which is also type of map, for "omit empty" feature.
func (m *Model) doMappingAndFilterForInsertOrUpdateDataMap(data Map, allowOmitEmpty bool) (Map, error) {
var err error
data, err = m.db.GetCore().mappingAndFilterData(m.schema, m.tables, data, m.filter)
data, err = m.db.GetCore().mappingAndFilterData(
m.schema, m.guessPrimaryTableName(m.tablesInit), data, m.filter,
)
if err != nil {
return nil, err
}
// Remove key-value pairs of which the value is empty.
if allowOmitEmpty && m.option&OptionOmitEmpty > 0 {
if allowOmitEmpty && m.option&optionOmitEmptyData > 0 {
tempMap := make(Map, len(data))
for k, v := range data {
if empty.IsEmpty(v) {
@ -201,7 +200,7 @@ func (m *Model) getLink(master bool) Link {
// It parses m.tables to retrieve the primary table name, supporting m.tables like:
// "user", "user u", "user as u, user_detail as ud".
func (m *Model) getPrimaryKey() string {
table := gstr.SplitAndTrim(m.tables, " ")[0]
table := gstr.SplitAndTrim(m.tablesInit, " ")[0]
tableFields, err := m.TableFields(table)
if err != nil {
return ""

View File

@ -39,7 +39,10 @@ func (m *Model) With(objects ...interface{}) *Model {
model := m.getModel()
for _, object := range objects {
if m.tables == "" {
m.tables = m.db.GetCore().QuotePrefixTableName(getTableNameFromOrmTag(object))
m.tablesInit = m.db.GetCore().QuotePrefixTableName(
getTableNameFromOrmTag(object),
)
m.tables = m.tablesInit
return model
}
model.withArray = append(model.withArray, object)
@ -60,7 +63,11 @@ func (m *Model) doWithScanStruct(pointer interface{}) error {
err error
allowedTypeStrArray = make([]string, 0)
)
fieldMap, err := structs.FieldMap(pointer, nil, false)
fieldMap, err := structs.FieldMap(structs.FieldMapInput{
Pointer: pointer,
PriorityTagArray: nil,
RecursiveOption: structs.RecursiveOptionEmbeddedNoTag,
})
if err != nil {
return err
}
@ -76,7 +83,7 @@ func (m *Model) doWithScanStruct(pointer interface{}) error {
fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]")
withItemReflectValueTypeStr = gstr.TrimAll(withItemReflectValueType.String(), "*[]")
)
// It does select operation if the field type is in the specified with type array.
// It does select operation if the field type is in the specified "with" type array.
if gstr.Compare(fieldTypeStr, withItemReflectValueTypeStr) == 0 {
allowedTypeStrArray = append(allowedTypeStrArray, fieldTypeStr)
}
@ -85,28 +92,21 @@ func (m *Model) doWithScanStruct(pointer interface{}) error {
}
for _, field := range fieldMap {
var (
withTag string
ormTag = field.Tag(OrmTagForStruct)
fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]")
match, _ = gregex.MatchString(
fmt.Sprintf(`%s\s*:\s*([^,]+)`, OrmTagForWith),
ormTag,
)
fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]")
parsedTagOutput = m.parseWithTagInFieldStruct(field)
)
if len(match) > 1 {
withTag = match[1]
}
if withTag == "" {
if parsedTagOutput.With == "" {
continue
}
// Just handler "with" type attribute struct.
if !m.withAll && !gstr.InArray(allowedTypeStrArray, fieldTypeStr) {
continue
}
array := gstr.SplitAndTrim(withTag, "=")
array := gstr.SplitAndTrim(parsedTagOutput.With, "=")
if len(array) == 1 {
// It supports using only one column name
// if both tables associates using the same column name.
array = append(array, withTag)
array = append(array, parsedTagOutput.With)
}
var (
model *Model
@ -123,9 +123,10 @@ func (m *Model) doWithScanStruct(pointer interface{}) error {
}
}
if relatedFieldValue == nil {
return gerror.Newf(
`cannot find the related value for attribute name "%s" of with tag "%s"`,
relatedAttrName, withTag,
return gerror.NewCodef(
gerror.CodeInvalidParameter,
`cannot find the related value of attribute name "%s" in with tag "%s" for attribute "%s.%s"`,
relatedAttrName, parsedTagOutput.With, reflect.TypeOf(pointer).Elem(), field.Name(),
)
}
bindToReflectValue := field.Value
@ -150,6 +151,12 @@ func (m *Model) doWithScanStruct(pointer interface{}) error {
} else {
model = model.With(m.withArray...)
}
if parsedTagOutput.Where != "" {
model = model.Where(parsedTagOutput.Where)
}
if parsedTagOutput.Order != "" {
model = model.Order(parsedTagOutput.Order)
}
err = model.Fields(fieldKeys).Where(relatedFieldName, relatedFieldValue).Scan(bindToReflectValue)
if err != nil {
@ -163,11 +170,19 @@ func (m *Model) doWithScanStruct(pointer interface{}) error {
// doWithScanStructs handles model association operations feature for struct slice.
// Also see doWithScanStruct.
func (m *Model) doWithScanStructs(pointer interface{}) error {
if v, ok := pointer.(reflect.Value); ok {
pointer = v.Interface()
}
var (
err error
allowedTypeStrArray = make([]string, 0)
)
fieldMap, err := structs.FieldMap(pointer, nil, false)
fieldMap, err := structs.FieldMap(structs.FieldMapInput{
Pointer: pointer,
PriorityTagArray: nil,
RecursiveOption: structs.RecursiveOptionEmbeddedNoTag,
})
if err != nil {
return err
}
@ -193,28 +208,20 @@ func (m *Model) doWithScanStructs(pointer interface{}) error {
for fieldName, field := range fieldMap {
var (
withTag string
ormTag = field.Tag(OrmTagForStruct)
fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]")
match, _ = gregex.MatchString(
fmt.Sprintf(`%s\s*:\s*([^,]+)`, OrmTagForWith),
ormTag,
)
fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]")
parsedTagOutput = m.parseWithTagInFieldStruct(field)
)
if len(match) > 1 {
withTag = match[1]
}
if withTag == "" {
if parsedTagOutput.With == "" {
continue
}
if !m.withAll && !gstr.InArray(allowedTypeStrArray, fieldTypeStr) {
continue
}
array := gstr.SplitAndTrim(withTag, "=")
array := gstr.SplitAndTrim(parsedTagOutput.With, "=")
if len(array) == 1 {
// It supports using only one column name
// if both tables associates using the same column name.
array = append(array, withTag)
array = append(array, parsedTagOutput.With)
}
var (
model *Model
@ -231,9 +238,10 @@ func (m *Model) doWithScanStructs(pointer interface{}) error {
}
}
if relatedFieldValue == nil {
return gerror.Newf(
return gerror.NewCodef(
gerror.CodeInvalidParameter,
`cannot find the related value for attribute name "%s" of with tag "%s"`,
relatedAttrName, withTag,
relatedAttrName, parsedTagOutput.With,
)
}
@ -251,11 +259,58 @@ func (m *Model) doWithScanStructs(pointer interface{}) error {
} else {
model = model.With(m.withArray...)
}
if parsedTagOutput.Where != "" {
model = model.Where(parsedTagOutput.Where)
}
if parsedTagOutput.Order != "" {
model = model.Order(parsedTagOutput.Order)
}
err = model.Fields(fieldKeys).Where(relatedFieldName, relatedFieldValue).ScanList(pointer, fieldName, withTag)
err = model.Fields(fieldKeys).Where(relatedFieldName, relatedFieldValue).ScanList(pointer, fieldName, parsedTagOutput.With)
if err != nil {
return err
}
}
return nil
}
type parseWithTagInFieldStructOutput struct {
With string
Where string
Order string
}
func (m *Model) parseWithTagInFieldStruct(field *structs.Field) (output parseWithTagInFieldStructOutput) {
var (
match []string
ormTag = field.Tag(OrmTagForStruct)
)
// with tag.
match, _ = gregex.MatchString(
fmt.Sprintf(`%s\s*:\s*([^,]+),{0,1}`, OrmTagForWith),
ormTag,
)
if len(match) > 1 {
output.With = match[1]
}
if len(match) > 2 {
output.Where = gstr.Trim(match[2])
}
// where string.
match, _ = gregex.MatchString(
fmt.Sprintf(`%s\s*:.+,\s*%s:\s*([^,]+),{0,1}`, OrmTagForWith, OrmTagForWithWhere),
ormTag,
)
if len(match) > 1 {
output.Where = gstr.Trim(match[1])
}
// order string.
match, _ = gregex.MatchString(
fmt.Sprintf(`%s\s*:.+,\s*%s:\s*([^,]+),{0,1}`, OrmTagForWith, OrmTagForWithOrder),
ormTag,
)
if len(match) > 1 {
output.Order = gstr.Trim(match[1])
}
return
}

View File

@ -33,7 +33,10 @@ func (r *SqlResult) MustGetInsertId() int64 {
return id
}
// see sql.Result.RowsAffected
// RowsAffected returns the number of rows affected by an
// update, insert, or delete. Not every database or database
// driver may support this.
// Also See sql.Result.
func (r *SqlResult) RowsAffected() (int64, error) {
if r.affected > 0 {
return r.affected, nil
@ -44,7 +47,12 @@ func (r *SqlResult) RowsAffected() (int64, error) {
return r.result.RowsAffected()
}
// see sql.Result.LastInsertId
// LastInsertId returns the integer generated by the database
// in response to a command. Typically this will be from an
// "auto increment" column when inserting a new row. Not all
// databases support this feature, and the syntax of such
// statements varies.
// Also See sql.Result.
func (r *SqlResult) LastInsertId() (int64, error) {
if r.result == nil {
return 0, nil

View File

@ -37,7 +37,7 @@ const (
)
// doStmtCommit commits statement according to given `stmtType`.
func (s *Stmt) doStmtCommit(stmtType string, ctx context.Context, args ...interface{}) (result interface{}, err error) {
func (s *Stmt) doStmtCommit(ctx context.Context, stmtType string, args ...interface{}) (result interface{}, err error) {
var (
cancelFuncForTimeout context.CancelFunc
timestampMilli1 = gtime.TimestampMilli()
@ -59,7 +59,7 @@ func (s *Stmt) doStmtCommit(stmtType string, ctx context.Context, args ...interf
result = s.Stmt.QueryRowContext(ctx, args...)
default:
panic(gerror.Newf(`invalid stmtType: %s`, stmtType))
panic(gerror.NewCodef(gerror.CodeInvalidParameter, `invalid stmtType: %s`, stmtType))
}
var (
timestampMilli2 = gtime.TimestampMilli()
@ -86,7 +86,7 @@ func (s *Stmt) doStmtCommit(stmtType string, ctx context.Context, args ...interf
// ExecContext executes a prepared statement with the given arguments and
// returns a Result summarizing the effect of the statement.
func (s *Stmt) ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error) {
result, err := s.doStmtCommit(stmtTypeExecContext, ctx, args...)
result, err := s.doStmtCommit(ctx, stmtTypeExecContext, args...)
if result != nil {
return result.(sql.Result), err
}
@ -96,7 +96,7 @@ func (s *Stmt) ExecContext(ctx context.Context, args ...interface{}) (sql.Result
// QueryContext executes a prepared query statement with the given arguments
// and returns the query results as a *Rows.
func (s *Stmt) QueryContext(ctx context.Context, args ...interface{}) (*sql.Rows, error) {
result, err := s.doStmtCommit(stmtTypeQueryContext, ctx, args...)
result, err := s.doStmtCommit(ctx, stmtTypeQueryContext, args...)
if result != nil {
return result.(*sql.Rows), err
}
@ -110,7 +110,7 @@ func (s *Stmt) QueryContext(ctx context.Context, args ...interface{}) (*sql.Rows
// Otherwise, the *Row's Scan scans the first selected row and discards
// the rest.
func (s *Stmt) QueryRowContext(ctx context.Context, args ...interface{}) *sql.Row {
result, _ := s.doStmtCommit(stmtTypeQueryRowContext, ctx, args...)
result, _ := s.doStmtCommit(ctx, stmtTypeQueryRowContext, args...)
if result != nil {
return result.(*sql.Row)
}

View File

@ -14,6 +14,11 @@ import (
"github.com/gogf/gf/util/gconv"
)
// Interface converts and returns `r` as type of interface{}.
func (r Record) Interface() interface{} {
return r
}
// Json converts `r` to JSON format content.
func (r Record) Json() string {
content, _ := gparser.VarToJson(r.Map())
@ -52,7 +57,7 @@ func (r Record) Struct(pointer interface{}) error {
}
return nil
}
return gconv.StructTag(r.Map(), pointer, OrmTagForStruct)
return gconv.StructTag(r, pointer, OrmTagForStruct)
}
// IsEmpty checks and returns whether `r` is empty.

View File

@ -1,27 +0,0 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gdb
// Deprecated, use Json instead.
func (r Record) ToJson() string {
return r.Json()
}
// Deprecated, use Xml instead.
func (r Record) ToXml(rootTag ...string) string {
return r.Xml(rootTag...)
}
// Deprecated, use Map instead.
func (r Record) ToMap() Map {
return r.Map()
}
// Deprecated, use Struct instead.
func (r Record) ToStruct(pointer interface{}) error {
return r.Struct(pointer)
}

View File

@ -13,6 +13,11 @@ import (
"math"
)
// Interface converts and returns `r` as type of interface{}.
func (r Result) Interface() interface{} {
return r
}
// IsEmpty checks and returns whether `r` is empty.
func (r Result) IsEmpty() bool {
return r.Len() == 0
@ -72,6 +77,7 @@ func (r Result) List() List {
// Array retrieves and returns specified column values as slice.
// The parameter `field` is optional is the column field is only one.
// The default `field` is the first field name of the first item in `Result` if parameter `field` is not given.
func (r Result) Array(field ...string) []Value {
array := make([]Value, len(r))
if len(r) == 0 {
@ -153,7 +159,7 @@ func (r Result) MapKeyUint(key string) map[uint]Map {
return m
}
// RecordKeyInt converts `r` to a map[int]Record of which key is specified by `key`.
// RecordKeyStr converts `r` to a map[string]Record of which key is specified by `key`.
func (r Result) RecordKeyStr(key string) map[string]Record {
m := make(map[string]Record)
for _, item := range r {
@ -189,5 +195,5 @@ func (r Result) RecordKeyUint(key string) map[uint]Record {
// Structs converts `r` to struct slice.
// Note that the parameter `pointer` should be type of *[]struct/*[]*struct.
func (r Result) Structs(pointer interface{}) (err error) {
return gconv.StructsTag(r.List(), pointer, OrmTagForStruct)
return gconv.StructsTag(r, pointer, OrmTagForStruct)
}

View File

@ -1,57 +0,0 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gdb
// Deprecated, use Json instead.
func (r Result) ToJson() string {
return r.Json()
}
// Deprecated, use Xml instead.
func (r Result) ToXml(rootTag ...string) string {
return r.Xml(rootTag...)
}
// Deprecated, use List instead.
func (r Result) ToList() List {
return r.List()
}
// Deprecated, use MapKeyStr instead.
func (r Result) ToStringMap(key string) map[string]Map {
return r.MapKeyStr(key)
}
// Deprecated, use MapKetInt instead.
func (r Result) ToIntMap(key string) map[int]Map {
return r.MapKeyInt(key)
}
// Deprecated, use MapKeyUint instead.
func (r Result) ToUintMap(key string) map[uint]Map {
return r.MapKeyUint(key)
}
// Deprecated, use RecordKeyStr instead.
func (r Result) ToStringRecord(key string) map[string]Record {
return r.RecordKeyStr(key)
}
// Deprecated, use RecordKetInt instead.
func (r Result) ToIntRecord(key string) map[int]Record {
return r.RecordKeyInt(key)
}
// Deprecated, use RecordKetUint instead.
func (r Result) ToUintRecord(key string) map[uint]Record {
return r.RecordKeyUint(key)
}
// Deprecated, use Structs instead.
func (r Result) ToStructs(pointer interface{}) (err error) {
return r.Structs(pointer)
}

View File

@ -42,21 +42,21 @@ import (
//
// See the example or unit testing cases for clear understanding for this function.
func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relationKV ...string) (err error) {
if r.IsEmpty() {
return doScanList(nil, r, listPointer, bindToAttrName, relationKV...)
}
// doScanList converts `result` to struct slice which contains other complex struct attributes recursively.
// The parameter `model` is used for recursively scanning purpose, which means, it can scans the attribute struct/structs recursively but
// it needs the Model for database accessing.
// Note that the parameter `listPointer` should be type of *[]struct/*[]*struct.
func doScanList(model *Model, result Result, listPointer interface{}, bindToAttrName string, relationKV ...string) (err error) {
if result.IsEmpty() {
return nil
}
// Necessary checks for parameters.
if bindToAttrName == "" {
return gerror.New(`bindToAttrName should not be empty`)
return gerror.NewCode(gerror.CodeInvalidParameter, `bindToAttrName should not be empty`)
}
//if len(relation) > 0 {
// if len(relation) < 2 {
// return gerror.New(`relation name and key should are both necessary`)
// }
// if relation[0] == "" || relation[1] == "" {
// return gerror.New(`relation name and key should not be empty`)
// }
//}
var (
reflectValue = reflect.ValueOf(listPointer)
@ -67,14 +67,14 @@ func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relatio
reflectKind = reflectValue.Kind()
}
if reflectKind != reflect.Ptr {
return gerror.Newf("parameter should be type of *[]struct/*[]*struct, but got: %v", reflectKind)
return gerror.NewCodef(gerror.CodeInvalidParameter, "listPointer should be type of *[]struct/*[]*struct, but got: %v", reflectKind)
}
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
if reflectKind != reflect.Slice && reflectKind != reflect.Array {
return gerror.Newf("parameter should be type of *[]struct/*[]*struct, but got: %v", reflectKind)
return gerror.NewCodef(gerror.CodeInvalidParameter, "listPointer should be type of *[]struct/*[]*struct, but got: %v", reflectKind)
}
length := len(r)
length := len(result)
if length == 0 {
// The pointed slice is not empty.
if reflectValue.Len() > 0 {
@ -134,8 +134,9 @@ func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relatio
// uid:UserId
relationResultFieldName = array[0]
relationBindToSubAttrName = array[1]
if key, _ := gutil.MapPossibleItemByKey(r[0].Map(), relationResultFieldName); key == "" {
return gerror.Newf(
if key, _ := gutil.MapPossibleItemByKey(result[0].Map(), relationResultFieldName); key == "" {
return gerror.NewCodef(
gerror.CodeInvalidParameter,
`cannot find possible related table field name "%s" from given relation key "%s"`,
relationResultFieldName,
relationKVStr,
@ -144,13 +145,14 @@ func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relatio
relationResultFieldName = key
}
} else {
return gerror.New(`parameter relationKV should be format of "ResultFieldName:BindToAttrName"`)
return gerror.NewCode(gerror.CodeInvalidParameter, `parameter relationKV should be format of "ResultFieldName:BindToAttrName"`)
}
if relationResultFieldName != "" {
relationDataMap = r.MapKeyValue(relationResultFieldName)
// Note that the value might be type of slice.
relationDataMap = result.MapKeyValue(relationResultFieldName)
}
if len(relationDataMap) == 0 {
return gerror.Newf(`cannot find the relation data map, maybe invalid relation given "%v"`, relationKV)
return gerror.NewCodef(gerror.CodeInvalidParameter, `cannot find the relation data map, maybe invalid relation given "%v"`, relationKV)
}
}
// Bind to target attribute.
@ -163,11 +165,19 @@ func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relatio
)
if arrayItemType.Kind() == reflect.Ptr {
if bindToAttrField, ok = arrayItemType.Elem().FieldByName(bindToAttrName); !ok {
return gerror.Newf(`invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`, bindToAttrName)
return gerror.NewCodef(
gerror.CodeInvalidParameter,
`invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`,
bindToAttrName,
)
}
} else {
if bindToAttrField, ok = arrayItemType.FieldByName(bindToAttrName); !ok {
return gerror.Newf(`invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`, bindToAttrName)
return gerror.NewCodef(
gerror.CodeInvalidParameter,
`invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`,
bindToAttrName,
)
}
}
bindToAttrType = bindToAttrField.Type
@ -209,7 +219,7 @@ func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relatio
relationFromAttrValue = arrayElemValue
}
if len(relationDataMap) > 0 && !relationFromAttrValue.IsValid() {
return gerror.Newf(`invalid relation specified: "%v"`, relationKV)
return gerror.NewCodef(gerror.CodeInvalidParameter, `invalid relation specified: "%v"`, relationKV)
}
// Check and find possible bind to attribute name.
if relationKVStr != "" && !relationBindToSubAttrNameChecked {
@ -223,7 +233,8 @@ func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relatio
filedMap[relationFromAttrType.Field(i).Name] = struct{}{}
}
if key, _ := gutil.MapPossibleItemByKey(filedMap, relationBindToSubAttrName); key == "" {
return gerror.Newf(
return gerror.NewCodef(
gerror.CodeInvalidParameter,
`cannot find possible related attribute name "%s" from given relation key "%s"`,
relationBindToSubAttrName,
relationKVStr,
@ -239,18 +250,29 @@ func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relatio
if len(relationDataMap) > 0 {
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToSubAttrName)
if relationFromAttrField.IsValid() {
if err = gconv.Structs(
relationDataMap[gconv.String(relationFromAttrField.Interface())],
bindToAttrValue.Addr(),
); err != nil {
results := make(Result, 0)
for _, v := range relationDataMap[gconv.String(relationFromAttrField.Interface())].Slice() {
results = append(results, v.(Record))
}
if err = results.Structs(bindToAttrValue.Addr()); err != nil {
return err
}
// Recursively Scan.
if model != nil {
if err = model.doWithScanStructs(bindToAttrValue.Addr()); err != nil {
return nil
}
}
} else {
// May be the attribute does not exist yet.
return gerror.Newf(`invalid relation specified: "%v"`, relationKV)
return gerror.NewCodef(gerror.CodeInvalidParameter, `invalid relation specified: "%v"`, relationKV)
}
} else {
return gerror.Newf(`relationKey should not be empty as field "%s" is slice`, bindToAttrName)
return gerror.NewCodef(
gerror.CodeInvalidParameter,
`relationKey should not be empty as field "%s" is slice`,
bindToAttrName,
)
}
case reflect.Ptr:
@ -268,24 +290,36 @@ func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relatio
// There's no relational data.
continue
}
if err = gconv.Struct(v, element); err != nil {
return err
if v.IsSlice() {
if err = v.Slice()[0].(Record).Struct(element); err != nil {
return err
}
} else {
if err = v.Val().(Record).Struct(element); err != nil {
return err
}
}
} else {
// May be the attribute does not exist yet.
return gerror.Newf(`invalid relation specified: "%v"`, relationKV)
return gerror.NewCodef(gerror.CodeInvalidParameter, `invalid relation specified: "%v"`, relationKV)
}
} else {
if i >= len(r) {
if i >= len(result) {
// There's no relational data.
continue
}
v := r[i]
v := result[i]
if v == nil {
// There's no relational data.
continue
}
if err = gconv.Struct(v, element); err != nil {
if err = v.Struct(element); err != nil {
return err
}
}
// Recursively Scan.
if model != nil {
if err = model.doWithScanStruct(element); err != nil {
return err
}
}
@ -300,30 +334,42 @@ func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relatio
// There's no relational data.
continue
}
if err = gconv.Struct(relationDataItem, bindToAttrValue); err != nil {
return err
if relationDataItem.IsSlice() {
if err = relationDataItem.Slice()[0].(Record).Struct(bindToAttrValue); err != nil {
return err
}
} else {
if err = relationDataItem.Val().(Record).Struct(bindToAttrValue); err != nil {
return err
}
}
} else {
// May be the attribute does not exist yet.
return gerror.Newf(`invalid relation specified: "%v"`, relationKV)
return gerror.NewCodef(gerror.CodeInvalidParameter, `invalid relation specified: "%v"`, relationKV)
}
} else {
if i >= len(r) {
if i >= len(result) {
// There's no relational data.
continue
}
relationDataItem := r[i]
relationDataItem := result[i]
if relationDataItem == nil {
// There's no relational data.
continue
}
if err = gconv.Struct(relationDataItem, bindToAttrValue); err != nil {
if err = relationDataItem.Struct(bindToAttrValue); err != nil {
return err
}
}
// Recursively Scan.
if model != nil {
if err = model.doWithScanStruct(bindToAttrValue); err != nil {
return err
}
}
default:
return gerror.Newf(`unsupported attribute type: %s`, bindToAttrKind.String())
return gerror.NewCodef(gerror.CodeInvalidParameter, `unsupported attribute type: %s`, bindToAttrKind.String())
}
}
reflect.ValueOf(listPointer).Elem().Set(arrayValue)

View File

@ -18,9 +18,9 @@ import (
// MyDriver is a custom database driver, which is used for testing only.
// For simplifying the unit testing case purpose, MyDriver struct inherits the mysql driver
// gdb.DriverMysql and overwrites its function HandleSqlBeforeCommit.
// So if there's any sql execution, it goes through MyDriver.HandleSqlBeforeCommit firstly and
// then gdb.DriverMysql.HandleSqlBeforeCommit.
// gdb.DriverMysql and overwrites its function DoCommit.
// So if there's any sql execution, it goes through MyDriver.DoCommit firstly and
// then gdb.DriverMysql.DoCommit.
// You can call it sql "HOOK" or "HiJack" as your will.
type MyDriver struct {
*gdb.DriverMysql
@ -41,11 +41,11 @@ func (d *MyDriver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) {
}, nil
}
// HandleSqlBeforeCommit handles the sql before posts it to database.
// DoCommit handles the sql before posts it to database.
// It here overwrites the same method of gdb.DriverMysql and makes some custom changes.
func (d *MyDriver) HandleSqlBeforeCommit(ctx context.Context, link gdb.Link, sql string, args []interface{}) (string, []interface{}) {
func (d *MyDriver) DoCommit(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
latestSqlString.Set(sql)
return d.DriverMysql.HandleSqlBeforeCommit(ctx, link, sql, args)
return d.DriverMysql.DoCommit(ctx, link, sql, args)
}
func init() {

View File

@ -7,6 +7,7 @@
package gdb_test
import (
"context"
"fmt"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/frame/g"
@ -29,9 +30,10 @@ const (
)
var (
db gdb.DB
dbPrefix gdb.DB
configNode gdb.ConfigNode
db gdb.DB
dbPrefix gdb.DB
dbCtxStrict gdb.DB
configNode gdb.ConfigNode
)
func init() {
@ -56,9 +58,15 @@ func init() {
}
nodePrefix := configNode
nodePrefix.Prefix = TableNamePrefix1
nodeCtxStrict := configNode
nodeCtxStrict.CtxStrict = true
gdb.AddConfigNode("test", configNode)
gdb.AddConfigNode("prefix", nodePrefix)
gdb.AddConfigNode("ctxstrict", nodeCtxStrict)
gdb.AddConfigNode(gdb.DefaultGroupName, configNode)
// Default db.
if r, err := gdb.New(); err != nil {
gtest.Error(err)
@ -87,6 +95,20 @@ func init() {
gtest.Error(err)
}
dbPrefix.SetSchema(TestSchema1)
// CtxStrict db.
if r, err := gdb.New("ctxstrict"); err != nil {
gtest.Error(err)
} else {
dbCtxStrict = r
}
if _, err := dbCtxStrict.Ctx(context.TODO()).Exec(fmt.Sprintf(schemaTemplate, TestSchema1)); err != nil {
gtest.Error(err)
}
if _, err := dbCtxStrict.Ctx(context.TODO()).Exec(fmt.Sprintf(schemaTemplate, TestSchema2)); err != nil {
gtest.Error(err)
}
dbCtxStrict.SetSchema(TestSchema1)
}
func createTable(table ...string) string {
@ -111,7 +133,7 @@ func createTableWithDb(db gdb.DB, table ...string) (name string) {
switch configNode.Type {
case "sqlite":
if _, err := db.Exec(fmt.Sprintf(`
if _, err := db.Ctx(context.TODO()).Exec(fmt.Sprintf(`
CREATE TABLE %s (
id bigint unsigned NOT NULL AUTO_INCREMENT,
passport varchar(45),
@ -124,7 +146,7 @@ func createTableWithDb(db gdb.DB, table ...string) (name string) {
gtest.Fatal(err)
}
case "pgsql":
if _, err := db.Exec(fmt.Sprintf(`
if _, err := db.Ctx(context.TODO()).Exec(fmt.Sprintf(`
CREATE TABLE %s (
id bigint NOT NULL,
passport varchar(45),
@ -137,7 +159,7 @@ func createTableWithDb(db gdb.DB, table ...string) (name string) {
gtest.Fatal(err)
}
case "mssql":
if _, err := db.Exec(fmt.Sprintf(`
if _, err := db.Ctx(context.TODO()).Exec(fmt.Sprintf(`
IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='%s' and xtype='U')
CREATE TABLE %s (
ID numeric(10,0) NOT NULL,
@ -151,7 +173,7 @@ func createTableWithDb(db gdb.DB, table ...string) (name string) {
gtest.Fatal(err)
}
case "oracle":
if _, err := db.Exec(fmt.Sprintf(`
if _, err := db.Ctx(context.TODO()).Exec(fmt.Sprintf(`
CREATE TABLE %s (
ID NUMBER(10) NOT NULL,
PASSPORT VARCHAR(45) NOT NULL,
@ -164,7 +186,7 @@ func createTableWithDb(db gdb.DB, table ...string) (name string) {
gtest.Fatal(err)
}
case "mysql":
if _, err := db.Exec(fmt.Sprintf(`
if _, err := db.Ctx(context.TODO()).Exec(fmt.Sprintf(`
CREATE TABLE %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
passport varchar(45) NULL,
@ -195,7 +217,7 @@ func createInitTableWithDb(db gdb.DB, table ...string) (name string) {
})
}
result, err := db.BatchInsert(name, array.Slice())
result, err := db.Ctx(context.TODO()).Insert(name, array.Slice())
gtest.AssertNil(err)
n, e := result.RowsAffected()
@ -205,7 +227,7 @@ func createInitTableWithDb(db gdb.DB, table ...string) (name string) {
}
func dropTableWithDb(db gdb.DB, table string) {
if _, err := db.Exec(fmt.Sprintf("DROP TABLE IF EXISTS `%s`", table)); err != nil {
if _, err := db.Ctx(context.TODO()).Exec(fmt.Sprintf("DROP TABLE IF EXISTS `%s`", table)); err != nil {
gtest.Error(err)
}
}

View File

@ -8,12 +8,83 @@ package gdb_test
import (
"fmt"
"github.com/gogf/gf/debug/gdebug"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/os/gfile"
"github.com/gogf/gf/test/gtest"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gmeta"
"testing"
)
/*
mysql> show tables;
+----------------+
| Tables_in_test |
+----------------+
| user |
| user_detail |
| user_score |
+----------------+
3 rows in set (0.01 sec)
mysql> select * from `user`;
+----+--------+
| id | name |
+----+--------+
| 1 | name_1 |
| 2 | name_2 |
| 3 | name_3 |
| 4 | name_4 |
| 5 | name_5 |
+----+--------+
5 rows in set (0.01 sec)
mysql> select * from `user_detail`;
+-----+-----------+
| uid | address |
+-----+-----------+
| 1 | address_1 |
| 2 | address_2 |
| 3 | address_3 |
| 4 | address_4 |
| 5 | address_5 |
+-----+-----------+
5 rows in set (0.00 sec)
mysql> select * from `user_score`;
+----+-----+-------+
| id | uid | score |
+----+-----+-------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
| 4 | 1 | 4 |
| 5 | 1 | 5 |
| 6 | 2 | 1 |
| 7 | 2 | 2 |
| 8 | 2 | 3 |
| 9 | 2 | 4 |
| 10 | 2 | 5 |
| 11 | 3 | 1 |
| 12 | 3 | 2 |
| 13 | 3 | 3 |
| 14 | 3 | 4 |
| 15 | 3 | 5 |
| 16 | 4 | 1 |
| 17 | 4 | 2 |
| 18 | 4 | 3 |
| 19 | 4 | 4 |
| 20 | 4 | 5 |
| 21 | 5 | 1 |
| 22 | 5 | 2 |
| 23 | 5 | 3 |
| 24 | 5 | 4 |
| 25 | 5 | 5 |
+----+-----+-------+
25 rows in set (0.00 sec)
*/
func Test_Table_Relation_With_Scan(t *testing.T) {
var (
tableUser = "user"
@ -667,7 +738,143 @@ PRIMARY KEY (id)
})
}
func Test_Table_Relation_WithAll_Embedded(t *testing.T) {
func Test_Table_Relation_WithAllCondition_List(t *testing.T) {
var (
tableUser = "user"
tableUserDetail = "user_detail"
tableUserScores = "user_scores"
)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
name varchar(45) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUser)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUser)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
address varchar(45) NOT NULL,
PRIMARY KEY (uid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUserDetail)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUserDetail)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
uid int(10) unsigned NOT NULL,
score int(10) unsigned NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUserScores)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUserScores)
type UserDetail struct {
gmeta.Meta `orm:"table:user_detail"`
Uid int `json:"uid"`
Address string `json:"address"`
}
type UserScores struct {
gmeta.Meta `orm:"table:user_scores"`
Id int `json:"id"`
Uid int `json:"uid"`
Score int `json:"score"`
}
type User struct {
gmeta.Meta `orm:"table:user"`
Id int `json:"id"`
Name string `json:"name"`
UserDetail *UserDetail `orm:"with:uid=id, where:uid > 3"`
UserScores []*UserScores `orm:"with:uid=id, where:score>1 and score<5, order:score desc"`
}
// Initialize the data.
var err error
for i := 1; i <= 5; i++ {
// User.
_, err = db.Insert(tableUser, g.Map{
"id": i,
"name": fmt.Sprintf(`name_%d`, i),
})
gtest.Assert(err, nil)
// Detail.
_, err = db.Insert(tableUserDetail, g.Map{
"uid": i,
"address": fmt.Sprintf(`address_%d`, i),
})
gtest.Assert(err, nil)
// Scores.
for j := 1; j <= 5; j++ {
_, err = db.Insert(tableUserScores, g.Map{
"uid": i,
"score": j,
})
gtest.Assert(err, nil)
}
}
db.SetDebug(true)
defer db.SetDebug(false)
gtest.C(t, func(t *gtest.T) {
var users []*User
err := db.Model(tableUser).WithAll().Where("id", []int{3, 4}).Scan(&users)
t.AssertNil(err)
t.Assert(len(users), 2)
t.Assert(users[0].Id, 3)
t.Assert(users[0].Name, "name_3")
t.Assert(users[0].UserDetail, nil)
t.Assert(users[1].Id, 4)
t.Assert(users[1].Name, "name_4")
t.AssertNE(users[1].UserDetail, nil)
t.Assert(users[1].UserDetail.Uid, 4)
t.Assert(users[1].UserDetail.Address, "address_4")
t.Assert(len(users[1].UserScores), 3)
t.Assert(users[1].UserScores[0].Uid, 4)
t.Assert(users[1].UserScores[0].Score, 4)
t.Assert(users[1].UserScores[2].Uid, 4)
t.Assert(users[1].UserScores[2].Score, 2)
})
gtest.C(t, func(t *gtest.T) {
var users []User
err := db.Model(tableUser).WithAll().Where("id", []int{3, 4}).Scan(&users)
t.AssertNil(err)
t.Assert(len(users), 2)
t.Assert(users[0].Id, 3)
t.Assert(users[0].Name, "name_3")
t.Assert(users[0].UserDetail, nil)
t.Assert(len(users[0].UserScores), 3)
t.Assert(users[0].UserScores[0].Uid, 3)
t.Assert(users[0].UserScores[0].Score, 4)
t.Assert(users[0].UserScores[2].Uid, 3)
t.Assert(users[0].UserScores[2].Score, 2)
t.Assert(users[1].Id, 4)
t.Assert(users[1].Name, "name_4")
t.AssertNE(users[1].UserDetail, nil)
t.Assert(users[1].UserDetail.Uid, 4)
t.Assert(users[1].UserDetail.Address, "address_4")
t.Assert(len(users[1].UserScores), 3)
t.Assert(users[1].UserScores[0].Uid, 4)
t.Assert(users[1].UserScores[0].Score, 4)
t.Assert(users[1].UserScores[2].Uid, 4)
t.Assert(users[1].UserScores[2].Score, 2)
})
}
func Test_Table_Relation_WithAll_Embedded_With_SelfMaintained_Attributes(t *testing.T) {
var (
tableUser = "user"
tableUserDetail = "user_detail"
@ -782,6 +989,245 @@ PRIMARY KEY (id)
})
}
func Test_Table_Relation_WithAll_Embedded_Without_SelfMaintained_Attributes(t *testing.T) {
var (
tableUser = "user"
tableUserDetail = "user_detail"
tableUserScores = "user_scores"
)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
name varchar(45) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUser)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUser)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
address varchar(45) NOT NULL,
PRIMARY KEY (uid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUserDetail)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUserDetail)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
uid int(10) unsigned NOT NULL,
score int(10) unsigned NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUserScores)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUserScores)
type UserDetail struct {
gmeta.Meta `orm:"table:user_detail"`
Uid int `json:"uid"`
Address string `json:"address"`
}
type UserScores struct {
gmeta.Meta `orm:"table:user_scores"`
Id int `json:"id"`
Uid int `json:"uid"`
Score int `json:"score"`
}
// For Test Only
type UserEmbedded struct {
Id int `json:"id"`
Name string `json:"name"`
}
type User struct {
gmeta.Meta `orm:"table:user"`
*UserDetail `orm:"with:uid=id"`
UserEmbedded
UserScores []*UserScores `orm:"with:uid=id"`
}
// Initialize the data.
var err error
for i := 1; i <= 5; i++ {
// User.
_, err = db.Insert(tableUser, g.Map{
"id": i,
"name": fmt.Sprintf(`name_%d`, i),
})
gtest.Assert(err, nil)
// Detail.
_, err = db.Insert(tableUserDetail, g.Map{
"uid": i,
"address": fmt.Sprintf(`address_%d`, i),
})
gtest.Assert(err, nil)
// Scores.
for j := 1; j <= 5; j++ {
_, err = db.Insert(tableUserScores, g.Map{
"uid": i,
"score": j,
})
gtest.Assert(err, nil)
}
}
db.SetDebug(true)
defer db.SetDebug(false)
gtest.C(t, func(t *gtest.T) {
var user *User
err := db.Model(tableUser).WithAll().Where("id", 3).Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 3)
t.AssertNE(user.UserDetail, nil)
t.Assert(user.UserDetail.Uid, 3)
t.Assert(user.UserDetail.Address, `address_3`)
t.Assert(len(user.UserScores), 5)
t.Assert(user.UserScores[0].Uid, 3)
t.Assert(user.UserScores[0].Score, 1)
t.Assert(user.UserScores[4].Uid, 3)
t.Assert(user.UserScores[4].Score, 5)
})
gtest.C(t, func(t *gtest.T) {
var user User
err := db.Model(tableUser).WithAll().Where("id", 4).Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 4)
t.AssertNE(user.UserDetail, nil)
t.Assert(user.UserDetail.Uid, 4)
t.Assert(user.UserDetail.Address, `address_4`)
t.Assert(len(user.UserScores), 5)
t.Assert(user.UserScores[0].Uid, 4)
t.Assert(user.UserScores[0].Score, 1)
t.Assert(user.UserScores[4].Uid, 4)
t.Assert(user.UserScores[4].Score, 5)
})
}
func Test_Table_Relation_WithAll_Embedded_WithoutMeta(t *testing.T) {
var (
tableUser = "user"
tableUserDetail = "user_detail"
tableUserScores = "user_scores"
)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
name varchar(45) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUser)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUser)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
address varchar(45) NOT NULL,
PRIMARY KEY (uid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUserDetail)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUserDetail)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
uid int(10) unsigned NOT NULL,
score int(10) unsigned NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUserScores)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUserScores)
type UserDetailBase struct {
Uid int `json:"uid"`
Address string `json:"address"`
}
type UserDetail struct {
UserDetailBase
}
type UserScores struct {
Id int `json:"id"`
Uid int `json:"uid"`
Score int `json:"score"`
}
type User struct {
*UserDetail `orm:"with:uid=id"`
Id int `json:"id"`
Name string `json:"name"`
UserScores []*UserScores `orm:"with:uid=id"`
}
// Initialize the data.
var err error
for i := 1; i <= 5; i++ {
// User.
_, err = db.Insert(tableUser, g.Map{
"id": i,
"name": fmt.Sprintf(`name_%d`, i),
})
gtest.Assert(err, nil)
// Detail.
_, err = db.Insert(tableUserDetail, g.Map{
"uid": i,
"address": fmt.Sprintf(`address_%d`, i),
})
gtest.Assert(err, nil)
// Scores.
for j := 1; j <= 5; j++ {
_, err = db.Insert(tableUserScores, g.Map{
"uid": i,
"score": j,
})
gtest.Assert(err, nil)
}
}
gtest.C(t, func(t *gtest.T) {
var user *User
err := db.Model(tableUser).WithAll().Where("id", 3).Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 3)
t.AssertNE(user.UserDetail, nil)
t.Assert(user.UserDetail.Uid, 3)
t.Assert(user.UserDetail.Address, `address_3`)
t.Assert(len(user.UserScores), 5)
t.Assert(user.UserScores[0].Uid, 3)
t.Assert(user.UserScores[0].Score, 1)
t.Assert(user.UserScores[4].Uid, 3)
t.Assert(user.UserScores[4].Score, 5)
})
gtest.C(t, func(t *gtest.T) {
var user User
err := db.Model(tableUser).WithAll().Where("id", 4).Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 4)
t.AssertNE(user.UserDetail, nil)
t.Assert(user.UserDetail.Uid, 4)
t.Assert(user.UserDetail.Address, `address_4`)
t.Assert(len(user.UserScores), 5)
t.Assert(user.UserScores[0].Uid, 4)
t.Assert(user.UserScores[0].Score, 1)
t.Assert(user.UserScores[4].Uid, 4)
t.Assert(user.UserScores[4].Score, 5)
})
}
func Test_Table_Relation_WithAll_AttributeStructAlsoHasWithTag(t *testing.T) {
var (
tableUser = "user"
@ -1189,3 +1635,234 @@ PRIMARY KEY (id)
t.Assert(user.UserDetail.UserScores[4].Score, 5)
})
}
func Test_Table_Relation_With_MultipleDepends1(t *testing.T) {
defer func() {
dropTable("table_a")
dropTable("table_b")
dropTable("table_c")
}()
for _, v := range gstr.SplitAndTrim(gfile.GetContents(gdebug.TestDataPath("with_multiple_depends.sql")), ";") {
if _, err := db.Exec(v); err != nil {
gtest.Error(err)
}
}
type TableC struct {
gmeta.Meta `orm:"table_c"`
Id int `orm:"id,primary" json:"id"`
TableBId int `orm:"table_b_id" json:"table_b_id"`
}
type TableB struct {
gmeta.Meta `orm:"table_b"`
Id int `orm:"id,primary" json:"id"`
TableAId int `orm:"table_a_id" json:"table_a_id"`
TableC *TableC `orm:"with:table_b_id=id" json:"table_c"`
}
type TableA struct {
gmeta.Meta `orm:"table_a"`
Id int `orm:"id,primary" json:"id"`
TableB *TableB `orm:"with:table_a_id=id" json:"table_b"`
}
db.SetDebug(true)
defer db.SetDebug(false)
// Struct.
gtest.C(t, func(t *gtest.T) {
var tableA *TableA
err := db.Model("table_a").WithAll().Scan(&tableA)
//g.Dump(tableA)
t.AssertNil(err)
t.AssertNE(tableA, nil)
t.Assert(tableA.Id, 1)
t.AssertNE(tableA.TableB, nil)
t.AssertNE(tableA.TableB.TableC, nil)
t.Assert(tableA.TableB.TableAId, 1)
t.Assert(tableA.TableB.TableC.Id, 100)
t.Assert(tableA.TableB.TableC.TableBId, 10)
})
// Structs
gtest.C(t, func(t *gtest.T) {
var tableA []*TableA
err := db.Model("table_a").WithAll().OrderAsc("id").Scan(&tableA)
//g.Dump(tableA)
t.AssertNil(err)
t.Assert(len(tableA), 2)
t.AssertNE(tableA[0].TableB, nil)
t.AssertNE(tableA[1].TableB, nil)
t.AssertNE(tableA[0].TableB.TableC, nil)
t.AssertNE(tableA[1].TableB.TableC, nil)
t.Assert(tableA[0].Id, 1)
t.Assert(tableA[0].TableB.Id, 10)
t.Assert(tableA[0].TableB.TableC.Id, 100)
t.Assert(tableA[1].Id, 2)
t.Assert(tableA[1].TableB.Id, 20)
t.Assert(tableA[1].TableB.TableC.Id, 300)
})
}
func Test_Table_Relation_With_MultipleDepends2(t *testing.T) {
defer func() {
dropTable("table_a")
dropTable("table_b")
dropTable("table_c")
}()
for _, v := range gstr.SplitAndTrim(gfile.GetContents(gdebug.TestDataPath("with_multiple_depends.sql")), ";") {
if _, err := db.Exec(v); err != nil {
gtest.Error(err)
}
}
type TableC struct {
gmeta.Meta `orm:"table_c"`
Id int `orm:"id,primary" json:"id"`
TableBId int `orm:"table_b_id" json:"table_b_id"`
}
type TableB struct {
gmeta.Meta `orm:"table_b"`
Id int `orm:"id,primary" json:"id"`
TableAId int `orm:"table_a_id" json:"table_a_id"`
TableC []*TableC `orm:"with:table_b_id=id" json:"table_c"`
}
type TableA struct {
gmeta.Meta `orm:"table_a"`
Id int `orm:"id,primary" json:"id"`
TableB []*TableB `orm:"with:table_a_id=id" json:"table_b"`
}
db.SetDebug(true)
defer db.SetDebug(false)
// Struct.
gtest.C(t, func(t *gtest.T) {
var tableA *TableA
err := db.Model("table_a").WithAll().Scan(&tableA)
//g.Dump(tableA)
t.AssertNil(err)
t.AssertNE(tableA, nil)
t.Assert(tableA.Id, 1)
t.Assert(len(tableA.TableB), 2)
t.Assert(tableA.TableB[0].Id, 10)
t.Assert(tableA.TableB[1].Id, 30)
t.Assert(len(tableA.TableB[0].TableC), 2)
t.Assert(len(tableA.TableB[1].TableC), 1)
t.Assert(tableA.TableB[0].TableC[0].Id, 100)
t.Assert(tableA.TableB[0].TableC[0].TableBId, 10)
t.Assert(tableA.TableB[0].TableC[1].Id, 200)
t.Assert(tableA.TableB[0].TableC[1].TableBId, 10)
t.Assert(tableA.TableB[1].TableC[0].Id, 400)
t.Assert(tableA.TableB[1].TableC[0].TableBId, 30)
})
// Structs
gtest.C(t, func(t *gtest.T) {
var tableA []*TableA
err := db.Model("table_a").WithAll().OrderAsc("id").Scan(&tableA)
//g.Dump(tableA)
t.AssertNil(err)
t.Assert(len(tableA), 2)
t.Assert(len(tableA[0].TableB), 2)
t.Assert(tableA[0].TableB[0].Id, 10)
t.Assert(tableA[0].TableB[1].Id, 30)
t.Assert(len(tableA[0].TableB[0].TableC), 2)
t.Assert(len(tableA[0].TableB[1].TableC), 1)
t.Assert(tableA[0].TableB[0].TableC[0].Id, 100)
t.Assert(tableA[0].TableB[0].TableC[0].TableBId, 10)
t.Assert(tableA[0].TableB[0].TableC[1].Id, 200)
t.Assert(tableA[0].TableB[0].TableC[1].TableBId, 10)
t.Assert(tableA[0].TableB[1].TableC[0].Id, 400)
t.Assert(tableA[0].TableB[1].TableC[0].TableBId, 30)
t.Assert(tableA[1].TableB[0].TableC[0].Id, 300)
t.Assert(tableA[1].TableB[0].TableC[0].TableBId, 20)
t.Assert(tableA[1].TableB[1].Id, 40)
t.Assert(tableA[1].TableB[1].TableAId, 2)
t.Assert(tableA[1].TableB[1].TableC, nil)
})
}
func Test_Table_Relation_With_MultipleDepends_Embedded(t *testing.T) {
defer func() {
dropTable("table_a")
dropTable("table_b")
dropTable("table_c")
}()
for _, v := range gstr.SplitAndTrim(gfile.GetContents(gdebug.TestDataPath("with_multiple_depends.sql")), ";") {
if _, err := db.Exec(v); err != nil {
gtest.Error(err)
}
}
type TableC struct {
gmeta.Meta `orm:"table_c"`
Id int `orm:"id,primary" json:"id"`
TableBId int `orm:"table_b_id" json:"table_b_id"`
}
type TableB struct {
gmeta.Meta `orm:"table_b"`
Id int `orm:"id,primary" json:"id"`
TableAId int `orm:"table_a_id" json:"table_a_id"`
*TableC `orm:"with:table_b_id=id" json:"table_c"`
}
type TableA struct {
gmeta.Meta `orm:"table_a"`
Id int `orm:"id,primary" json:"id"`
*TableB `orm:"with:table_a_id=id" json:"table_b"`
}
db.SetDebug(true)
defer db.SetDebug(false)
// Struct.
gtest.C(t, func(t *gtest.T) {
var tableA *TableA
err := db.Model("table_a").WithAll().Scan(&tableA)
//g.Dump(tableA)
t.AssertNil(err)
t.AssertNE(tableA, nil)
t.Assert(tableA.Id, 1)
t.AssertNE(tableA.TableB, nil)
t.AssertNE(tableA.TableB.TableC, nil)
t.Assert(tableA.TableB.TableAId, 1)
t.Assert(tableA.TableB.TableC.Id, 100)
t.Assert(tableA.TableB.TableC.TableBId, 10)
})
// Structs
gtest.C(t, func(t *gtest.T) {
var tableA []*TableA
err := db.Model("table_a").WithAll().OrderAsc("id").Scan(&tableA)
//g.Dump(tableA)
t.AssertNil(err)
t.Assert(len(tableA), 2)
t.AssertNE(tableA[0].TableB, nil)
t.AssertNE(tableA[1].TableB, nil)
t.AssertNE(tableA[0].TableB.TableC, nil)
t.AssertNE(tableA[1].TableB.TableC, nil)
t.Assert(tableA[0].Id, 1)
t.Assert(tableA[0].TableB.Id, 10)
t.Assert(tableA[0].TableB.TableC.Id, 100)
t.Assert(tableA[1].Id, 2)
t.Assert(tableA[1].TableB.Id, 20)
t.Assert(tableA[1].TableB.TableC.Id, 300)
})
}

View File

@ -30,7 +30,7 @@ func Test_Ctx(t *testing.T) {
}
func Test_Ctx_Query(t *testing.T) {
db.GetLogger().SetCtxKeys("SpanId", "TraceId")
db.GetLogger().(gdb.LoggerImp).SetCtxKeys("SpanId", "TraceId")
gtest.C(t, func(t *gtest.T) {
db.SetDebug(true)
defer db.SetDebug(false)
@ -48,7 +48,7 @@ func Test_Ctx_Query(t *testing.T) {
func Test_Ctx_Model(t *testing.T) {
table := createInitTable()
defer dropTable(table)
db.GetLogger().SetCtxKeys("SpanId", "TraceId")
db.GetLogger().(gdb.LoggerImp).SetCtxKeys("SpanId", "TraceId")
gtest.C(t, func(t *gtest.T) {
db.SetDebug(true)
defer db.SetDebug(false)
@ -62,3 +62,23 @@ func Test_Ctx_Model(t *testing.T) {
db.Model(table).All()
})
}
func Test_Ctx_Strict(t *testing.T) {
table := createInitTableWithDb(dbCtxStrict)
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
_, err := dbCtxStrict.Query("select 1")
t.AssertNE(err, nil)
})
gtest.C(t, func(t *gtest.T) {
r, err := dbCtxStrict.Model(table).All()
t.AssertNE(err, nil)
t.Assert(len(r), 0)
})
gtest.C(t, func(t *gtest.T) {
r, err := dbCtxStrict.Model(table).Ctx(context.TODO()).All()
t.AssertNil(err)
t.Assert(len(r), TableSize)
})
}

View File

@ -8,11 +8,11 @@ package gdb
import (
"fmt"
"github.com/go-sql-driver/mysql"
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/os/gcmd"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/test/gtest"
"github.com/gogf/mysql"
"testing"
)

View File

@ -329,7 +329,7 @@ func Test_DB_BatchInsert(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
table := createTable()
defer dropTable(table)
r, err := db.BatchInsert(table, g.List{
r, err := db.Insert(table, g.List{
{
"id": 2,
"passport": "t2",
@ -357,7 +357,7 @@ func Test_DB_BatchInsert(t *testing.T) {
table := createTable()
defer dropTable(table)
// []interface{}
r, err := db.BatchInsert(table, g.Slice{
r, err := db.Insert(table, g.Slice{
g.Map{
"id": 2,
"passport": "t2",
@ -382,7 +382,7 @@ func Test_DB_BatchInsert(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
table := createTable()
defer dropTable(table)
result, err := db.BatchInsert(table, g.Map{
result, err := db.Insert(table, g.Map{
"id": 1,
"passport": "t1",
"password": "p1",
@ -416,7 +416,7 @@ func Test_DB_BatchInsert_Struct(t *testing.T) {
NickName: "T1",
CreateTime: gtime.Now(),
}
result, err := db.BatchInsert(table, user)
result, err := db.Insert(table, user)
t.AssertNil(err)
n, _ := result.RowsAffected()
t.Assert(n, 1)
@ -584,7 +584,7 @@ func Test_DB_GetStruct(t *testing.T) {
CreateTime gtime.Time
}
user := new(User)
err := db.GetStruct(user, fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 3)
err := db.GetScan(user, fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 3)
t.AssertNil(err)
t.Assert(user.NickName, "name_3")
})
@ -597,7 +597,7 @@ func Test_DB_GetStruct(t *testing.T) {
CreateTime *gtime.Time
}
user := new(User)
err := db.GetStruct(user, fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 3)
err := db.GetScan(user, fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 3)
t.AssertNil(err)
t.Assert(user.NickName, "name_3")
})
@ -615,7 +615,7 @@ func Test_DB_GetStructs(t *testing.T) {
CreateTime gtime.Time
}
var users []User
err := db.GetStructs(&users, fmt.Sprintf("SELECT * FROM %s WHERE id>?", table), 1)
err := db.GetScan(&users, fmt.Sprintf("SELECT * FROM %s WHERE id>?", table), 1)
t.AssertNil(err)
t.Assert(len(users), TableSize-1)
t.Assert(users[0].Id, 2)
@ -635,7 +635,7 @@ func Test_DB_GetStructs(t *testing.T) {
CreateTime *gtime.Time
}
var users []User
err := db.GetStructs(&users, fmt.Sprintf("SELECT * FROM %s WHERE id>?", table), 1)
err := db.GetScan(&users, fmt.Sprintf("SELECT * FROM %s WHERE id>?", table), 1)
t.AssertNil(err)
t.Assert(len(users), TableSize-1)
t.Assert(users[0].Id, 2)
@ -1283,7 +1283,7 @@ func Test_DB_Prefix(t *testing.T) {
})
}
result, err := db.BatchInsert(name, array.Slice())
result, err := db.Insert(name, array.Slice())
t.AssertNil(err)
n, e := result.RowsAffected()
@ -1436,7 +1436,7 @@ func Test_DB_UpdateCounter(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
gdbCounter := &gdb.Counter{
Field: "views",
Field: "id",
Value: 1,
}
updateData := g.Map{
@ -1449,7 +1449,7 @@ func Test_DB_UpdateCounter(t *testing.T) {
one, err := db.Model(tableName).Where("id", 1).One()
t.AssertNil(err)
t.Assert(one["id"].Int(), 1)
t.Assert(one["views"].Int(), 1)
t.Assert(one["views"].Int(), 2)
})
gtest.C(t, func(t *gtest.T) {
@ -1468,7 +1468,7 @@ func Test_DB_UpdateCounter(t *testing.T) {
one, err := db.Model(tableName).Where("id", 1).One()
t.AssertNil(err)
t.Assert(one["id"].Int(), 1)
t.Assert(one["views"].Int(), 0)
t.Assert(one["views"].Int(), 1)
})
}

View File

@ -899,7 +899,7 @@ func Test_Model_Struct(t *testing.T) {
CreateTime gtime.Time
}
user := new(User)
err := db.Model(table).Where("id=1").Struct(user)
err := db.Model(table).Where("id=1").Scan(user)
t.AssertNil(err)
t.Assert(user.NickName, "name_1")
t.Assert(user.CreateTime.String(), "2018-10-24 10:00:00")
@ -913,7 +913,7 @@ func Test_Model_Struct(t *testing.T) {
CreateTime *gtime.Time
}
user := new(User)
err := db.Model(table).Where("id=1").Struct(user)
err := db.Model(table).Where("id=1").Scan(user)
t.AssertNil(err)
t.Assert(user.NickName, "name_1")
t.Assert(user.CreateTime.String(), "2018-10-24 10:00:00")
@ -928,7 +928,7 @@ func Test_Model_Struct(t *testing.T) {
CreateTime *gtime.Time
}
user := (*User)(nil)
err := db.Model(table).Where("id=1").Struct(&user)
err := db.Model(table).Where("id=1").Scan(&user)
t.AssertNil(err)
t.Assert(user.NickName, "name_1")
t.Assert(user.CreateTime.String(), "2018-10-24 10:00:00")
@ -960,7 +960,7 @@ func Test_Model_Struct(t *testing.T) {
CreateTime *gtime.Time
}
user := new(User)
err := db.Model(table).Where("id=-1").Struct(user)
err := db.Model(table).Where("id=-1").Scan(user)
t.Assert(err, sql.ErrNoRows)
})
gtest.C(t, func(t *gtest.T) {
@ -972,7 +972,7 @@ func Test_Model_Struct(t *testing.T) {
CreateTime *gtime.Time
}
var user *User
err := db.Model(table).Where("id=-1").Struct(&user)
err := db.Model(table).Where("id=-1").Scan(&user)
t.AssertNil(err)
})
}
@ -992,7 +992,7 @@ func Test_Model_Struct_CustomType(t *testing.T) {
CreateTime gtime.Time
}
user := new(User)
err := db.Model(table).Where("id=1").Struct(user)
err := db.Model(table).Where("id=1").Scan(user)
t.AssertNil(err)
t.Assert(user.NickName, "name_1")
t.Assert(user.CreateTime.String(), "2018-10-24 10:00:00")
@ -1012,7 +1012,7 @@ func Test_Model_Structs(t *testing.T) {
CreateTime gtime.Time
}
var users []User
err := db.Model(table).Order("id asc").Structs(&users)
err := db.Model(table).Order("id asc").Scan(&users)
if err != nil {
gtest.Error(err)
}
@ -1035,7 +1035,7 @@ func Test_Model_Structs(t *testing.T) {
CreateTime *gtime.Time
}
var users []*User
err := db.Model(table).Order("id asc").Structs(&users)
err := db.Model(table).Order("id asc").Scan(&users)
if err != nil {
gtest.Error(err)
}
@ -1081,38 +1081,40 @@ func Test_Model_Structs(t *testing.T) {
CreateTime *gtime.Time
}
var users []*User
err := db.Model(table).Where("id<0").Structs(&users)
err := db.Model(table).Where("id<0").Scan(&users)
t.AssertNil(err)
})
}
func Test_Model_StructsWithJsonTag(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
type User struct {
Uid int `json:"id"`
Passport string
Password string
Name string `json:"nick_name"`
Time gtime.Time `json:"create_time"`
}
var users []User
err := db.Model(table).Order("id asc").Structs(&users)
if err != nil {
gtest.Error(err)
}
t.Assert(len(users), TableSize)
t.Assert(users[0].Uid, 1)
t.Assert(users[1].Uid, 2)
t.Assert(users[2].Uid, 3)
t.Assert(users[0].Name, "name_1")
t.Assert(users[1].Name, "name_2")
t.Assert(users[2].Name, "name_3")
t.Assert(users[0].Time.String(), "2018-10-24 10:00:00")
})
}
// JSON tag is only used for JSON Marshal/Unmarshal, DO NOT use it in multiple purposes!
//func Test_Model_StructsWithJsonTag(t *testing.T) {
// table := createInitTable()
// defer dropTable(table)
//
// db.SetDebug(true)
// gtest.C(t, func(t *gtest.T) {
// type User struct {
// Uid int `json:"id"`
// Passport string
// Password string
// Name string `json:"nick_name"`
// Time gtime.Time `json:"create_time"`
// }
// var users []User
// err := db.Model(table).Order("id asc").Scan(&users)
// if err != nil {
// gtest.Error(err)
// }
// t.Assert(len(users), TableSize)
// t.Assert(users[0].Uid, 1)
// t.Assert(users[1].Uid, 2)
// t.Assert(users[2].Uid, 3)
// t.Assert(users[0].Name, "name_1")
// t.Assert(users[1].Name, "name_2")
// t.Assert(users[2].Name, "name_3")
// t.Assert(users[0].Time.String(), "2018-10-24 10:00:00")
// })
//}
func Test_Model_Scan(t *testing.T) {
table := createInitTable()
@ -1469,11 +1471,11 @@ func Test_Model_Where(t *testing.T) {
t.Assert(len(result), 3)
t.Assert(result[0]["id"].Int(), 1)
})
// struct
// struct, automatic mapping and filtering.
gtest.C(t, func(t *gtest.T) {
type User struct {
Id int `json:"id"`
Nickname string `gconv:"nickname"`
Id int
Nickname string
}
result, err := db.Model(table).Where(User{3, "name_3"}).One()
t.AssertNil(err)
@ -1936,7 +1938,7 @@ func Test_Model_Option_Map(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
table := createTable()
defer dropTable(table)
r, err := db.Model(table).Option(gdb.OptionOmitEmpty).Data(g.Map{
r, err := db.Model(table).OmitEmptyData().Data(g.Map{
"id": 1,
"passport": 0,
"password": 0,
@ -1956,7 +1958,7 @@ func Test_Model_Option_Map(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
table := createInitTable()
defer dropTable(table)
_, err := db.Model(table).Option(gdb.OptionOmitEmpty).Data(g.Map{
_, err := db.Model(table).OmitEmptyData().Data(g.Map{
"id": 1,
"passport": 0,
"password": 0,
@ -1992,7 +1994,7 @@ func Test_Model_Option_Map(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
table := createTable()
defer dropTable(table)
_, err := db.Model(table).Option(gdb.OptionOmitEmpty).Data(g.Map{
_, err := db.Model(table).OmitEmptyData().Data(g.Map{
"id": 1,
"passport": 0,
"password": 0,
@ -2029,7 +2031,7 @@ func Test_Model_Option_Map(t *testing.T) {
n, _ := r.RowsAffected()
t.Assert(n, 1)
_, err = db.Model(table).Option(gdb.OptionOmitEmpty).Data(g.Map{"nickname": ""}).Where("id", 2).Update()
_, err = db.Model(table).OmitEmptyData().Data(g.Map{"nickname": ""}).Where("id", 2).Update()
t.AssertNE(err, nil)
r, err = db.Model(table).OmitEmpty().Data(g.Map{"nickname": "", "password": "123"}).Where("id", 3).Update()
@ -2287,6 +2289,21 @@ func Test_Model_Prefix(t *testing.T) {
t.Assert(r[0]["id"], "1")
t.Assert(r[1]["id"], "2")
})
// Select with alias to struct.
gtest.C(t, func(t *gtest.T) {
type User struct {
Id int
Passport string
Password string
NickName string
}
var users []User
err := db.Model(table+" u").Where("u.id in (?)", g.Slice{1, 5}).Order("u.id asc").Scan(&users)
t.AssertNil(err)
t.Assert(len(users), 2)
t.Assert(users[0].Id, 1)
t.Assert(users[1].Id, 5)
})
// Select with alias and join statement.
gtest.C(t, func(t *gtest.T) {
r, err := db.Model(table+" as u1").LeftJoin(table+" as u2", "u2.id=u1.id").Where("u1.id in (?)", g.Slice{1, 2}).Order("u1.id asc").All()
@ -3098,7 +3115,7 @@ func Test_TimeZoneInsert(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
_, _ = db.Model(tableName).Unscoped().Insert(u)
userEntity := &User{}
err := db.Model(tableName).Where("id", 1).Unscoped().Struct(&userEntity)
err := db.Model(tableName).Where("id", 1).Unscoped().Scan(&userEntity)
t.AssertNil(err)
t.Assert(userEntity.CreatedAt.String(), "2020-11-22 04:23:45")
t.Assert(userEntity.UpdatedAt.String(), "2020-11-22 05:23:45")
@ -3129,7 +3146,7 @@ func Test_Model_Fields_Map_Struct(t *testing.T) {
XXX_TYPE int
}
var a = A{}
err := db.Model(table).Fields(a).Where("id", 1).Struct(&a)
err := db.Model(table).Fields(a).Where("id", 1).Scan(&a)
t.AssertNil(err)
t.Assert(a.ID, 1)
t.Assert(a.PASSPORT, "user_1")
@ -3143,7 +3160,7 @@ func Test_Model_Fields_Map_Struct(t *testing.T) {
XXX_TYPE int
}
var a *A
err := db.Model(table).Fields(a).Where("id", 1).Struct(&a)
err := db.Model(table).Fields(a).Where("id", 1).Scan(&a)
t.AssertNil(err)
t.Assert(a.ID, 1)
t.Assert(a.PASSPORT, "user_1")
@ -3157,7 +3174,7 @@ func Test_Model_Fields_Map_Struct(t *testing.T) {
XXX_TYPE int
}
var a *A
err := db.Model(table).Fields(&a).Where("id", 1).Struct(&a)
err := db.Model(table).Fields(&a).Where("id", 1).Scan(&a)
t.AssertNil(err)
t.Assert(a.ID, 1)
t.Assert(a.PASSPORT, "user_1")
@ -3366,6 +3383,104 @@ func Test_Model_WhereOrNotNull(t *testing.T) {
})
}
func Test_Model_WhereLT(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereLT("id", 3).OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), 2)
t.Assert(result[0]["id"], 1)
})
}
func Test_Model_WhereLTE(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereLTE("id", 3).OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), 3)
t.Assert(result[0]["id"], 1)
})
}
func Test_Model_WhereGT(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereGT("id", 8).OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), 2)
t.Assert(result[0]["id"], 9)
})
}
func Test_Model_WhereGTE(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereGTE("id", 8).OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), 3)
t.Assert(result[0]["id"], 8)
})
}
func Test_Model_WhereOrLT(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereLT("id", 3).WhereOrLT("id", 4).OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), 3)
t.Assert(result[0]["id"], 1)
t.Assert(result[2]["id"], 3)
})
}
func Test_Model_WhereOrLTE(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereLTE("id", 3).WhereOrLTE("id", 4).OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), 4)
t.Assert(result[0]["id"], 1)
t.Assert(result[3]["id"], 4)
})
}
func Test_Model_WhereOrGT(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereGT("id", 8).WhereOrGT("id", 7).OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), 3)
t.Assert(result[0]["id"], 8)
})
}
func Test_Model_WhereOrGTE(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereGTE("id", 8).WhereOrGTE("id", 7).OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), 4)
t.Assert(result[0]["id"], 7)
})
}
func Test_Model_Min_Max_Avg_Sum(t *testing.T) {
table := createInitTable()
defer dropTable(table)
@ -3453,3 +3568,203 @@ func Test_Model_Increment_Decrement(t *testing.T) {
t.Assert(count, 1)
})
}
func Test_Model_OnDuplicate(t *testing.T) {
table := createInitTable()
defer dropTable(table)
// string.
gtest.C(t, func(t *gtest.T) {
data := g.Map{
"id": 1,
"passport": "pp1",
"password": "pw1",
"nickname": "n1",
"create_time": "2016-06-06",
}
_, err := db.Model(table).OnDuplicate("passport,password").Data(data).Save()
t.AssertNil(err)
one, err := db.Model(table).FindOne(1)
t.AssertNil(err)
t.Assert(one["passport"], data["passport"])
t.Assert(one["password"], data["password"])
t.Assert(one["nickname"], "name_1")
})
// slice.
gtest.C(t, func(t *gtest.T) {
data := g.Map{
"id": 1,
"passport": "pp1",
"password": "pw1",
"nickname": "n1",
"create_time": "2016-06-06",
}
_, err := db.Model(table).OnDuplicate(g.Slice{"passport", "password"}).Data(data).Save()
t.AssertNil(err)
one, err := db.Model(table).FindOne(1)
t.AssertNil(err)
t.Assert(one["passport"], data["passport"])
t.Assert(one["password"], data["password"])
t.Assert(one["nickname"], "name_1")
})
// map.
gtest.C(t, func(t *gtest.T) {
data := g.Map{
"id": 1,
"passport": "pp1",
"password": "pw1",
"nickname": "n1",
"create_time": "2016-06-06",
}
_, err := db.Model(table).OnDuplicate(g.Map{
"passport": "nickname",
"password": "nickname",
}).Data(data).Save()
t.AssertNil(err)
one, err := db.Model(table).FindOne(1)
t.AssertNil(err)
t.Assert(one["passport"], data["nickname"])
t.Assert(one["password"], data["nickname"])
t.Assert(one["nickname"], "name_1")
})
// map+raw.
gtest.C(t, func(t *gtest.T) {
data := g.MapStrStr{
"id": "1",
"passport": "pp1",
"password": "pw1",
"nickname": "n1",
"create_time": "2016-06-06",
}
_, err := db.Model(table).OnDuplicate(g.Map{
"passport": gdb.Raw("CONCAT(VALUES(`passport`), '1')"),
"password": gdb.Raw("CONCAT(VALUES(`password`), '2')"),
}).Data(data).Save()
t.AssertNil(err)
one, err := db.Model(table).FindOne(1)
t.AssertNil(err)
t.Assert(one["passport"], data["passport"]+"1")
t.Assert(one["password"], data["password"]+"2")
t.Assert(one["nickname"], "name_1")
})
}
func Test_Model_OnDuplicateEx(t *testing.T) {
table := createInitTable()
defer dropTable(table)
// string.
gtest.C(t, func(t *gtest.T) {
data := g.Map{
"id": 1,
"passport": "pp1",
"password": "pw1",
"nickname": "n1",
"create_time": "2016-06-06",
}
_, err := db.Model(table).OnDuplicateEx("nickname,create_time").Data(data).Save()
t.AssertNil(err)
one, err := db.Model(table).FindOne(1)
t.AssertNil(err)
t.Assert(one["passport"], data["passport"])
t.Assert(one["password"], data["password"])
t.Assert(one["nickname"], "name_1")
})
// slice.
gtest.C(t, func(t *gtest.T) {
data := g.Map{
"id": 1,
"passport": "pp1",
"password": "pw1",
"nickname": "n1",
"create_time": "2016-06-06",
}
_, err := db.Model(table).OnDuplicateEx(g.Slice{"nickname", "create_time"}).Data(data).Save()
t.AssertNil(err)
one, err := db.Model(table).FindOne(1)
t.AssertNil(err)
t.Assert(one["passport"], data["passport"])
t.Assert(one["password"], data["password"])
t.Assert(one["nickname"], "name_1")
})
// map.
gtest.C(t, func(t *gtest.T) {
data := g.Map{
"id": 1,
"passport": "pp1",
"password": "pw1",
"nickname": "n1",
"create_time": "2016-06-06",
}
_, err := db.Model(table).OnDuplicateEx(g.Map{
"nickname": "nickname",
"create_time": "nickname",
}).Data(data).Save()
t.AssertNil(err)
one, err := db.Model(table).FindOne(1)
t.AssertNil(err)
t.Assert(one["passport"], data["passport"])
t.Assert(one["password"], data["password"])
t.Assert(one["nickname"], "name_1")
})
}
func Test_Model_Raw(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
all, err := db.
Raw(fmt.Sprintf("select * from %s where id in (?)", table), g.Slice{1, 5, 7, 8, 9, 10}).
WhereLT("id", 8).
WhereIn("id", g.Slice{1, 2, 3, 4, 5, 6, 7}).
OrderDesc("id").
Limit(2).
All()
t.AssertNil(err)
t.Assert(len(all), 2)
t.Assert(all[0]["id"], 7)
t.Assert(all[1]["id"], 5)
})
gtest.C(t, func(t *gtest.T) {
count, err := db.
Raw(fmt.Sprintf("select * from %s where id in (?)", table), g.Slice{1, 5, 7, 8, 9, 10}).
WhereLT("id", 8).
WhereIn("id", g.Slice{1, 2, 3, 4, 5, 6, 7}).
OrderDesc("id").
Limit(2).
Count()
t.AssertNil(err)
t.Assert(count, 6)
})
}
func Test_Model_Handler(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
m := db.Model(table).Safe().Handler(
func(m *gdb.Model) *gdb.Model {
return m.Page(0, 3)
},
func(m *gdb.Model) *gdb.Model {
return m.Where("id", g.Slice{1, 2, 3, 4, 5, 6})
},
func(m *gdb.Model) *gdb.Model {
return m.OrderDesc("id")
},
)
all, err := m.All()
t.AssertNil(err)
t.Assert(len(all), 3)
t.Assert(all[0]["id"], 6)
t.Assert(all[2]["id"], 4)
})
}

View File

@ -8,10 +8,13 @@ package gdb_test
import (
"database/sql"
"github.com/gogf/gf/database/gdb"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/test/gtest"
"github.com/gogf/gf/util/gconv"
"reflect"
"testing"
)
@ -121,7 +124,7 @@ func Test_Struct_Pointer_Attribute(t *testing.T) {
})
gtest.C(t, func(t *gtest.T) {
user := new(User)
err := db.Model(table).Struct(user, "id=1")
err := db.Model(table).Scan(user, "id=1")
t.AssertNil(err)
t.Assert(*user.Id, 1)
t.Assert(*user.Passport, "user_1")
@ -130,7 +133,7 @@ func Test_Struct_Pointer_Attribute(t *testing.T) {
})
gtest.C(t, func(t *gtest.T) {
var user *User
err := db.Model(table).Struct(&user, "id=1")
err := db.Model(table).Scan(&user, "id=1")
t.AssertNil(err)
t.Assert(*user.Id, 1)
t.Assert(*user.Passport, "user_1")
@ -201,7 +204,7 @@ func Test_Structs_Pointer_Attribute(t *testing.T) {
// Structs
gtest.C(t, func(t *gtest.T) {
users := make([]User, 0)
err := db.Model(table).Structs(&users, "id < 3")
err := db.Model(table).Scan(&users, "id < 3")
t.AssertNil(err)
t.Assert(len(users), 2)
t.Assert(*users[0].Id, 1)
@ -211,7 +214,7 @@ func Test_Structs_Pointer_Attribute(t *testing.T) {
})
gtest.C(t, func(t *gtest.T) {
users := make([]*User, 0)
err := db.Model(table).Structs(&users, "id < 3")
err := db.Model(table).Scan(&users, "id < 3")
t.AssertNil(err)
t.Assert(len(users), 2)
t.Assert(*users[0].Id, 1)
@ -221,7 +224,7 @@ func Test_Structs_Pointer_Attribute(t *testing.T) {
})
gtest.C(t, func(t *gtest.T) {
var users []User
err := db.Model(table).Structs(&users, "id < 3")
err := db.Model(table).Scan(&users, "id < 3")
t.AssertNil(err)
t.Assert(len(users), 2)
t.Assert(*users[0].Id, 1)
@ -231,7 +234,7 @@ func Test_Structs_Pointer_Attribute(t *testing.T) {
})
gtest.C(t, func(t *gtest.T) {
var users []*User
err := db.Model(table).Structs(&users, "id < 3")
err := db.Model(table).Scan(&users, "id < 3")
t.AssertNil(err)
t.Assert(len(users), 2)
t.Assert(*users[0].Id, 1)
@ -254,7 +257,7 @@ func Test_Struct_Empty(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
user := new(User)
err := db.Model(table).Where("id=100").Struct(user)
err := db.Model(table).Where("id=100").Scan(user)
t.Assert(err, sql.ErrNoRows)
t.AssertNE(user, nil)
})
@ -269,7 +272,7 @@ func Test_Struct_Empty(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var user *User
err := db.Model(table).Where("id=100").Struct(&user)
err := db.Model(table).Where("id=100").Scan(&user)
t.AssertNil(err)
t.Assert(user, nil)
})
@ -395,17 +398,17 @@ type User struct {
}
func (user *User) UnmarshalValue(value interface{}) error {
switch result := value.(type) {
case map[string]interface{}:
user.Id = result["id"].(int)
user.Passport = result["passport"].(string)
user.Password = ""
user.Nickname = result["nickname"].(string)
user.CreateTime = gtime.New(result["create_time"])
if record, ok := value.(gdb.Record); ok {
*user = User{
Id: record["id"].Int(),
Passport: record["passport"].String(),
Password: "",
Nickname: record["nickname"].String(),
CreateTime: record["create_time"].GTime(),
}
return nil
default:
return gconv.Struct(value, user)
}
return gerror.NewCodef(gerror.CodeInvalidParameter, `unsupported value type for UnmarshalValue: %v`, reflect.TypeOf(value))
}
func Test_Model_Scan_UnmarshalValue(t *testing.T) {
@ -452,3 +455,27 @@ func Test_Model_Scan_Map(t *testing.T) {
t.Assert(users[9].CreateTime.String(), CreateTime)
})
}
func Test_Scan_AutoFilteringByStructAttributes(t *testing.T) {
table := createInitTable()
defer dropTable(table)
type User struct {
Id int
Passport string
}
//db.SetDebug(true)
gtest.C(t, func(t *gtest.T) {
var user *User
err := db.Model(table).OrderAsc("id").Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 1)
})
gtest.C(t, func(t *gtest.T) {
var users []User
err := db.Model(table).OrderAsc("id").Scan(&users)
t.AssertNil(err)
t.Assert(len(users), TableSize)
t.Assert(users[0].Id, 1)
})
}

View File

@ -53,7 +53,7 @@ func Test_Model_SubQuery_Having(t *testing.T) {
func Test_Model_SubQuery_Model(t *testing.T) {
table := createInitTable()
defer dropTable(table)
db.SetDebug(true)
gtest.C(t, func(t *gtest.T) {
subQuery1 := db.Model(table).Where("id", g.Slice{1, 3, 5})
subQuery2 := db.Model(table).Where("id", g.Slice{5, 7, 9})

View File

@ -163,7 +163,7 @@ func Test_TX_BatchInsert(t *testing.T) {
if err != nil {
gtest.Error(err)
}
if _, err := tx.BatchInsert(table, g.List{
if _, err := tx.Insert(table, g.List{
{
"id": 2,
"passport": "t",
@ -201,7 +201,7 @@ func Test_TX_BatchReplace(t *testing.T) {
if err != nil {
gtest.Error(err)
}
if _, err := tx.BatchReplace(table, g.List{
if _, err := tx.Replace(table, g.List{
{
"id": 2,
"passport": "USER_2",
@ -244,7 +244,7 @@ func Test_TX_BatchSave(t *testing.T) {
if err != nil {
gtest.Error(err)
}
if _, err := tx.BatchSave(table, g.List{
if _, err := tx.Save(table, g.List{
{
"id": 4,
"passport": "USER_4",
@ -349,7 +349,7 @@ func Test_TX_Update(t *testing.T) {
if err := tx.Commit(); err != nil {
gtest.Error(err)
}
_, err = tx.Table(table).Fields("create_time").Where("id", 3).Value()
_, err = tx.Model(table).Fields("create_time").Where("id", 3).Value()
t.AssertNE(err, nil)
if value, err := db.Model(table).Fields("create_time").Where("id", 3).Value(); err != nil {
@ -666,7 +666,6 @@ func Test_TX_GetScan(t *testing.T) {
}
func Test_TX_Delete(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
table := createInitTable()
defer dropTable(table)
@ -685,6 +684,8 @@ func Test_TX_Delete(t *testing.T) {
} else {
t.Assert(n, 0)
}
t.Assert(tx.IsClosed(), true)
})
gtest.C(t, func(t *gtest.T) {
@ -697,7 +698,7 @@ func Test_TX_Delete(t *testing.T) {
if _, err := tx.Delete(table, 1); err != nil {
gtest.Error(err)
}
if n, err := tx.Table(table).Count(); err != nil {
if n, err := tx.Model(table).Count(); err != nil {
gtest.Error(err)
} else {
t.Assert(n, 0)
@ -711,6 +712,8 @@ func Test_TX_Delete(t *testing.T) {
t.Assert(n, TableSize)
t.AssertNE(n, 0)
}
t.Assert(tx.IsClosed(), true)
})
}
@ -721,7 +724,7 @@ func Test_Transaction(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
ctx := context.TODO()
err := db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
if _, err := tx.Replace(table, g.Map{
if _, err := tx.Ctx(ctx).Replace(table, g.Map{
"id": 1,
"passport": "USER_1",
"password": "PASS_1",
@ -730,11 +733,12 @@ func Test_Transaction(t *testing.T) {
}); err != nil {
t.Error(err)
}
t.Assert(tx.IsClosed(), false)
return gerror.New("error")
})
t.AssertNE(err, nil)
if value, err := db.Model(table).Fields("nickname").Where("id", 1).Value(); err != nil {
if value, err := db.Model(table).Ctx(ctx).Fields("nickname").Where("id", 1).Value(); err != nil {
gtest.Error(err)
} else {
t.Assert(value.String(), "name_1")
@ -956,8 +960,8 @@ func Test_Transaction_Nested_TX_Transaction_UseDB(t *testing.T) {
table := createTable()
defer dropTable(table)
db.SetDebug(true)
defer db.SetDebug(false)
//db.SetDebug(true)
//defer db.SetDebug(false)
gtest.C(t, func(t *gtest.T) {
var (

View File

@ -87,7 +87,7 @@ func Test_Types(t *testing.T) {
TinyInt bool
}
var obj *T
err = db.Model("types").Struct(&obj)
err = db.Model("types").Scan(&obj)
t.AssertNil(err)
t.Assert(obj.Id, 1)
t.Assert(obj.Blob, data["blob"])

View File

@ -0,0 +1,146 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gdb_test
import (
"github.com/gogf/gf/frame/g"
"testing"
"github.com/gogf/gf/test/gtest"
)
func Test_Union(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
r, err := db.Union(
db.Model(table).Where("id", 1),
db.Model(table).Where("id", 2),
db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).OrderDesc("id"),
).OrderDesc("id").All()
t.AssertNil(err)
t.Assert(len(r), 3)
t.Assert(r[0]["id"], 3)
t.Assert(r[1]["id"], 2)
t.Assert(r[2]["id"], 1)
})
gtest.C(t, func(t *gtest.T) {
r, err := db.Union(
db.Model(table).Where("id", 1),
db.Model(table).Where("id", 2),
db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).OrderDesc("id"),
).OrderDesc("id").One()
t.AssertNil(err)
t.Assert(r["id"], 3)
})
}
func Test_UnionAll(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
r, err := db.UnionAll(
db.Model(table).Where("id", 1),
db.Model(table).Where("id", 2),
db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).OrderDesc("id"),
).OrderDesc("id").All()
t.AssertNil(err)
t.Assert(len(r), 5)
t.Assert(r[0]["id"], 3)
t.Assert(r[1]["id"], 2)
t.Assert(r[2]["id"], 2)
t.Assert(r[3]["id"], 1)
t.Assert(r[4]["id"], 1)
})
gtest.C(t, func(t *gtest.T) {
r, err := db.UnionAll(
db.Model(table).Where("id", 1),
db.Model(table).Where("id", 2),
db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).OrderDesc("id"),
).OrderDesc("id").One()
t.AssertNil(err)
t.Assert(r["id"], 3)
})
}
func Test_Model_Union(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
r, err := db.Model(table).Union(
db.Model(table).Where("id", 1),
db.Model(table).Where("id", 2),
db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).OrderDesc("id"),
).OrderDesc("id").All()
t.AssertNil(err)
t.Assert(len(r), 3)
t.Assert(r[0]["id"], 3)
t.Assert(r[1]["id"], 2)
t.Assert(r[2]["id"], 1)
})
gtest.C(t, func(t *gtest.T) {
r, err := db.Model(table).Union(
db.Model(table).Where("id", 1),
db.Model(table).Where("id", 2),
db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).OrderDesc("id"),
).OrderDesc("id").One()
t.AssertNil(err)
t.Assert(r["id"], 3)
})
}
func Test_Model_UnionAll(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
r, err := db.Model(table).UnionAll(
db.Model(table).Where("id", 1),
db.Model(table).Where("id", 2),
db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).OrderDesc("id"),
).OrderDesc("id").All()
t.AssertNil(err)
t.Assert(len(r), 5)
t.Assert(r[0]["id"], 3)
t.Assert(r[1]["id"], 2)
t.Assert(r[2]["id"], 2)
t.Assert(r[3]["id"], 1)
t.Assert(r[4]["id"], 1)
})
gtest.C(t, func(t *gtest.T) {
r, err := db.Model(table).UnionAll(
db.Model(table).Where("id", 1),
db.Model(table).Where("id", 2),
db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).OrderDesc("id"),
).OrderDesc("id").One()
t.AssertNil(err)
t.Assert(r["id"], 3)
})
}

View File

@ -0,0 +1,33 @@
CREATE TABLE `table_a` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`alias` varchar(255) NULL DEFAULT '',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB;
INSERT INTO `table_a` VALUES (1, 'table_a_test1');
INSERT INTO `table_a` VALUES (2, 'table_a_test2');
CREATE TABLE `table_b` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`table_a_id` int(11) NOT NULL,
`alias` varchar(255) NULL DEFAULT '',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB;
INSERT INTO `table_b` VALUES (10, 1, 'table_b_test1');
INSERT INTO `table_b` VALUES (20, 2, 'table_b_test2');
INSERT INTO `table_b` VALUES (30, 1, 'table_b_test3');
INSERT INTO `table_b` VALUES (40, 2, 'table_b_test4');
CREATE TABLE `table_c` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`table_b_id` int(11) NOT NULL,
`alias` varchar(255) NULL DEFAULT '',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB;
INSERT INTO `table_c` VALUES (100, 10, 'table_c_test1');
INSERT INTO `table_c` VALUES (200, 10, 'table_c_test2');
INSERT INTO `table_c` VALUES (300, 20, 'table_c_test3');
INSERT INTO `table_c` VALUES (400, 30, 'table_c_test4');

View File

@ -114,7 +114,7 @@ func New(config *Config) *Redis {
if err != nil {
return nil, err
}
intlog.Printf(`open new connection, config:%+v`, config)
intlog.Printf(context.TODO(), `open new connection, config:%+v`, config)
// AUTH
if len(config.Pass) > 0 {
if _, err := c.Do("AUTH", config.Pass); err != nil {

View File

@ -7,6 +7,7 @@
package gredis
import (
"context"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/intlog"
@ -36,7 +37,7 @@ func SetConfig(config *Config, name ...string) {
configs.Set(group, config)
instances.Remove(group)
intlog.Printf(`SetConfig for group "%s": %+v`, group, config)
intlog.Printf(context.TODO(), `SetConfig for group "%s": %+v`, group, config)
}
// SetConfigByStr sets the global configuration for specified group with string.
@ -78,7 +79,7 @@ func RemoveConfig(name ...string) {
configs.Remove(group)
instances.Remove(group)
intlog.Printf(`RemoveConfig: %s`, group)
intlog.Printf(context.TODO(), `RemoveConfig: %s`, group)
}
// ConfigFromStr parses and returns config from given str.
@ -113,7 +114,7 @@ func ConfigFromStr(str string) (config *Config, err error) {
config.Port = DefaultRedisPort
}
} else {
err = gerror.Newf(`invalid redis configuration: "%s"`, str)
err = gerror.NewCodef(gerror.CodeInvalidConfiguration, `invalid redis configuration: "%s"`, str)
}
return
}

View File

@ -8,8 +8,8 @@ package gredis
import (
"context"
"errors"
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/json"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/util/gconv"
@ -50,7 +50,7 @@ func (c *Conn) do(timeout time.Duration, commandName string, args ...interface{}
if timeout > 0 {
conn, ok := c.Conn.(redis.ConnWithTimeout)
if !ok {
return gvar.New(nil), errors.New(`current connection does not support "ConnWithTimeout"`)
return gvar.New(nil), gerror.NewCode(gerror.CodeNotSupported, `current connection does not support "ConnWithTimeout"`)
}
return conn.DoWithTimeout(timeout, commandName, args...)
}
@ -107,7 +107,7 @@ func (c *Conn) ReceiveVar() (*gvar.Var, error) {
func (c *Conn) ReceiveVarWithTimeout(timeout time.Duration) (*gvar.Var, error) {
conn, ok := c.Conn.(redis.ConnWithTimeout)
if !ok {
return gvar.New(nil), errors.New(`current connection does not support "ConnWithTimeout"`)
return gvar.New(nil), gerror.NewCode(gerror.CodeNotSupported, `current connection does not support "ConnWithTimeout"`)
}
return resultToVar(conn.ReceiveWithTimeout(timeout))
}

View File

@ -12,7 +12,6 @@ import (
"github.com/gogf/gf"
"github.com/gogf/gf/internal/json"
"github.com/gogf/gf/net/gtrace"
"github.com/gogf/gf/os/gcmd"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
@ -38,19 +37,9 @@ const (
tracingEventRedisExecutionArguments = "redis.execution.arguments"
)
var (
// tracingInternal enables tracing for internal type spans.
// It's true in default.
tracingInternal = true
)
func init() {
tracingInternal = gcmd.GetOptWithEnv("gf.tracing.internal", true).Bool()
}
// addTracingItem checks and adds redis tracing information to OpenTelemetry.
func (c *Conn) addTracingItem(item *tracingItem) {
if !tracingInternal || !gtrace.IsActivated(c.ctx) {
if !gtrace.IsTracingInternal() || !gtrace.IsActivated(c.ctx) {
return
}
tr := otel.GetTracerProvider().Tracer(

View File

@ -140,7 +140,7 @@ func Test_Instance(t *testing.T) {
func Test_Error(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
config1 := &gredis.Config{
Host: "127.0.0.2",
Host: "192.111.0.2",
Port: 6379,
Db: 1,
ConnectTimeout: time.Second,
@ -149,16 +149,6 @@ func Test_Error(t *testing.T) {
_, err := redis.Do("info")
t.AssertNE(err, nil)
config1 = &gredis.Config{
Host: "127.0.0.1",
Port: 6379,
Db: 1,
Pass: "666666",
}
redis = gredis.New(config1)
_, err = redis.Do("info")
t.AssertNE(err, nil)
config1 = &gredis.Config{
Host: "127.0.0.1",
Port: 6379,

View File

@ -8,6 +8,7 @@ package gdebug
import (
"fmt"
"github.com/gogf/gf/internal/utils"
"os"
"os/exec"
"path/filepath"
@ -53,11 +54,13 @@ func Caller(skip ...int) (function string, path string, line int) {
//
// The parameter <filter> is used to filter the path of the caller.
func CallerWithFilter(filter string, skip ...int) (function string, path string, line int) {
number := 0
var (
number = 0
ok = true
)
if len(skip) > 0 {
number = skip[0]
}
ok := true
pc, file, line, start := callerFromIndex([]string{filter})
if start != -1 {
for i := start + number; i < maxCallerDepth; i++ {
@ -65,12 +68,6 @@ func CallerWithFilter(filter string, skip ...int) (function string, path string,
pc, file, line, ok = runtime.Caller(i)
}
if ok {
if filter != "" && strings.Contains(file, filter) {
continue
}
if strings.Contains(file, stackFilterKey) {
continue
}
function := ""
if fn := runtime.FuncForPC(pc); fn == nil {
function = "unknown"
@ -104,8 +101,14 @@ func callerFromIndex(filters []string) (pc uintptr, file string, line int, index
if filtered {
continue
}
if strings.Contains(file, stackFilterKey) {
continue
if !utils.IsDebugEnabled() {
if strings.Contains(file, utils.StackFilterKeyForGoFrame) {
continue
}
} else {
if strings.Contains(file, stackFilterKey) {
continue
}
}
if index > 0 {
index--

View File

@ -80,14 +80,17 @@ func StackWithFilters(filters []string, skip ...int) string {
if filtered {
continue
}
if strings.Contains(file, stackFilterKey) {
continue
}
if !utils.IsDebugEnabled() {
if strings.Contains(file, utils.StackFilterKeyForGoFrame) {
continue
}
} else {
if strings.Contains(file, stackFilterKey) {
continue
}
}
if fn := runtime.FuncForPC(pc); fn == nil {
name = "unknown"
} else {

View File

@ -21,8 +21,7 @@ package gcharset
import (
"bytes"
"errors"
"fmt"
"github.com/gogf/gf/errors/gerror"
"io/ioutil"
"golang.org/x/text/encoding"
@ -60,11 +59,11 @@ func Convert(dstCharset string, srcCharset string, src string) (dst string, err
transform.NewReader(bytes.NewReader([]byte(src)), e.NewDecoder()),
)
if err != nil {
return "", fmt.Errorf("%s to utf8 failed. %v", srcCharset, err)
return "", gerror.WrapCodef(gerror.CodeInternalError, err, "%s to utf8 failed", srcCharset)
}
src = string(tmp)
} else {
return dst, errors.New(fmt.Sprintf("unsupport srcCharset: %s", srcCharset))
return dst, gerror.NewCodef(gerror.CodeInvalidParameter, "unsupported srcCharset: %s", srcCharset)
}
}
// Do the converting from UTF-8 to <dstCharset>.
@ -74,11 +73,11 @@ func Convert(dstCharset string, srcCharset string, src string) (dst string, err
transform.NewReader(bytes.NewReader([]byte(src)), e.NewEncoder()),
)
if err != nil {
return "", fmt.Errorf("utf to %s failed. %v", dstCharset, err)
return "", gerror.WrapCodef(gerror.CodeInternalError, err, "utf to %s failed", dstCharset)
}
dst = string(tmp)
} else {
return dst, errors.New(fmt.Sprintf("unsupport dstCharset: %s", dstCharset))
return dst, gerror.NewCodef(gerror.CodeInvalidParameter, "unsupported dstCharset: %s", dstCharset)
}
} else {
dst = src

View File

@ -9,6 +9,7 @@ package gcompress
import (
"archive/zip"
"bytes"
"context"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/os/gfile"
"github.com/gogf/gf/text/gstr"
@ -92,7 +93,7 @@ func doZipPathWriter(path string, exclude string, zipWriter *zip.Writer, prefix
headerPrefix = strings.Replace(headerPrefix, "//", "/", -1)
for _, file := range files {
if exclude == file {
intlog.Printf(`exclude file path: %s`, file)
intlog.Printf(context.TODO(), `exclude file path: %s`, file)
continue
}
dir := gfile.Dir(file[len(path):])

View File

@ -10,8 +10,8 @@ package gini
import (
"bufio"
"bytes"
"errors"
"fmt"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/json"
"io"
"strings"
@ -70,7 +70,7 @@ func Decode(data []byte) (res map[string]interface{}, err error) {
}
if haveSection == false {
return nil, errors.New("failed to parse INI file, section not found")
return nil, gerror.NewCode(gerror.CodeInvalidParameter, "failed to parse INI file, section not found")
}
return res, nil
}

View File

@ -8,6 +8,7 @@
package gjson
import (
"github.com/gogf/gf/internal/utils"
"reflect"
"strconv"
"strings"
@ -37,11 +38,23 @@ type Options struct {
StrNumber bool // StrNumber causes the Decoder to unmarshal a number into an interface{} as a string instead of as a float64.
}
// apiInterface is used for type assert api for Interface().
type apiInterface interface {
Interface() interface{}
}
// setValue sets <value> to <j> by <pattern>.
// Note:
// 1. If value is nil and removed is true, means deleting this value;
// 2. It's quite complicated in hierarchical data search, node creating and data assignment;
func (j *Json) setValue(pattern string, value interface{}, removed bool) error {
if value != nil {
if utils.IsStruct(value) {
if v, ok := value.(apiInterface); ok {
value = v.Interface()
}
}
}
array := strings.Split(pattern, string(j.c))
length := len(array)
value = j.convertValue(value)

View File

@ -18,7 +18,13 @@ import (
)
// Value returns the json value.
// Deprecated, use Interface instead.
func (j *Json) Value() interface{} {
return j.Interface()
}
// Interface returns the json value.
func (j *Json) Interface() interface{} {
j.mu.RLock()
defer j.mu.RUnlock()
return *(j.p)
@ -319,23 +325,11 @@ func (j *Json) GetStruct(pattern string, pointer interface{}, mapping ...map[str
return gconv.Struct(j.Get(pattern), pointer, mapping...)
}
// GetStructDeep does GetStruct recursively.
// Deprecated, use GetStruct instead.
func (j *Json) GetStructDeep(pattern string, pointer interface{}, mapping ...map[string]string) error {
return gconv.StructDeep(j.Get(pattern), pointer, mapping...)
}
// GetStructs converts any slice to given struct slice.
func (j *Json) GetStructs(pattern string, pointer interface{}, mapping ...map[string]string) error {
return gconv.Structs(j.Get(pattern), pointer, mapping...)
}
// GetStructsDeep converts any slice to given struct slice recursively.
// Deprecated, use GetStructs instead.
func (j *Json) GetStructsDeep(pattern string, pointer interface{}, mapping ...map[string]string) error {
return gconv.StructsDeep(j.Get(pattern), pointer, mapping...)
}
// GetScan automatically calls Struct or Structs function according to the type of parameter
// <pointer> to implement the converting..
func (j *Json) GetScan(pattern string, pointer interface{}, mapping ...map[string]string) error {

View File

@ -70,7 +70,7 @@ func (j *Json) MustToJsonIndentString() string {
// ========================================================================
func (j *Json) ToXml(rootTag ...string) ([]byte, error) {
return gxml.Encode(j.ToMap(), rootTag...)
return gxml.Encode(j.Map(), rootTag...)
}
func (j *Json) ToXmlString(rootTag ...string) (string, error) {
@ -79,7 +79,7 @@ func (j *Json) ToXmlString(rootTag ...string) (string, error) {
}
func (j *Json) ToXmlIndent(rootTag ...string) ([]byte, error) {
return gxml.EncodeWithIndent(j.ToMap(), rootTag...)
return gxml.EncodeWithIndent(j.Map(), rootTag...)
}
func (j *Json) ToXmlIndentString(rootTag ...string) (string, error) {

View File

@ -8,8 +8,8 @@ package gjson
import (
"bytes"
"errors"
"fmt"
"github.com/gogf/gf/errors/gerror"
"reflect"
"github.com/gogf/gf/internal/json"
@ -264,7 +264,7 @@ func doLoadContentWithOptions(dataType string, data []byte, options Options) (*J
return nil, err
}
default:
err = errors.New("unsupported type for loading")
err = gerror.NewCode(gerror.CodeInvalidParameter, "unsupported type for loading")
}
if err != nil {
return nil, err

View File

@ -1,104 +0,0 @@
// 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 gjson
import "github.com/gogf/gf/util/gconv"
// ToMap converts current Json object to map[string]interface{}.
// It returns nil if fails.
// Deprecated, use Map instead.
func (j *Json) ToMap() map[string]interface{} {
j.mu.RLock()
defer j.mu.RUnlock()
return gconv.Map(*(j.p))
}
// ToArray converts current Json object to []interface{}.
// It returns nil if fails.
// Deprecated, use Array instead.
func (j *Json) ToArray() []interface{} {
j.mu.RLock()
defer j.mu.RUnlock()
return gconv.Interfaces(*(j.p))
}
// ToStruct converts current Json object to specified object.
// The <pointer> should be a pointer type of *struct.
// Deprecated, use Struct instead.
func (j *Json) ToStruct(pointer interface{}, mapping ...map[string]string) error {
j.mu.RLock()
defer j.mu.RUnlock()
return gconv.Struct(*(j.p), pointer, mapping...)
}
// ToStructDeep converts current Json object to specified object recursively.
// The <pointer> should be a pointer type of *struct.
// Deprecated, use Struct instead.
func (j *Json) ToStructDeep(pointer interface{}, mapping ...map[string]string) error {
j.mu.RLock()
defer j.mu.RUnlock()
return gconv.StructDeep(*(j.p), pointer, mapping...)
}
// ToStructs converts current Json object to specified object slice.
// The <pointer> should be a pointer type of []struct/*struct.
// Deprecated, use Structs instead.
func (j *Json) ToStructs(pointer interface{}, mapping ...map[string]string) error {
j.mu.RLock()
defer j.mu.RUnlock()
return gconv.Structs(*(j.p), pointer, mapping...)
}
// ToStructsDeep converts current Json object to specified object slice recursively.
// The <pointer> should be a pointer type of []struct/*struct.
// Deprecated, use Structs instead.
func (j *Json) ToStructsDeep(pointer interface{}, mapping ...map[string]string) error {
j.mu.RLock()
defer j.mu.RUnlock()
return gconv.StructsDeep(*(j.p), pointer, mapping...)
}
// ToScan automatically calls Struct or Structs function according to the type of parameter
// <pointer> to implement the converting..
// Deprecated, use Scan instead.
func (j *Json) ToScan(pointer interface{}, mapping ...map[string]string) error {
return gconv.Scan(*(j.p), pointer, mapping...)
}
// ToScanDeep automatically calls StructDeep or StructsDeep function according to the type of
// parameter <pointer> to implement the converting..
// Deprecated, use Scan instead.
func (j *Json) ToScanDeep(pointer interface{}, mapping ...map[string]string) error {
return gconv.ScanDeep(*(j.p), pointer, mapping...)
}
// ToMapToMap converts current Json object to specified map variable.
// The parameter of <pointer> should be type of *map.
// Deprecated, use MapToMap instead.
func (j *Json) ToMapToMap(pointer interface{}, mapping ...map[string]string) error {
j.mu.RLock()
defer j.mu.RUnlock()
return gconv.MapToMap(*(j.p), pointer, mapping...)
}
// ToMapToMaps converts current Json object to specified map variable slice.
// The parameter of <pointer> should be type of []map/*map.
// Deprecated, use MapToMaps instead.
func (j *Json) ToMapToMaps(pointer interface{}, mapping ...map[string]string) error {
j.mu.RLock()
defer j.mu.RUnlock()
return gconv.MapToMaps(*(j.p), pointer, mapping...)
}
// ToMapToMapsDeep converts current Json object to specified map variable slice recursively.
// The parameter of <pointer> should be type of []map/*map.
// Deprecated, use MapToMaps instead.
func (j *Json) ToMapToMapsDeep(pointer interface{}, mapping ...map[string]string) error {
j.mu.RLock()
defer j.mu.RUnlock()
return gconv.MapToMapsDeep(*(j.p), pointer, mapping...)
}

View File

@ -100,7 +100,7 @@ func Example_conversionToStruct() {
Array []string
}
users := new(Users)
if err := j.ToStruct(users); err != nil {
if err := j.Struct(users); err != nil {
panic(err)
}
fmt.Printf(`%+v`, users)

View File

@ -353,7 +353,7 @@ func Test_Convert2(t *testing.T) {
t.Assert(j.GetGTime("time").Format("Y-m-d"), "2019-06-12")
t.Assert(j.GetDuration("time").String(), "0s")
err := j.ToStruct(&name)
err := j.Struct(&name)
t.Assert(err, nil)
t.Assert(name.Name, "gf")
//j.Dump()
@ -369,7 +369,7 @@ func Test_Convert2(t *testing.T) {
t.Assert(err, nil)
j = gjson.New(`[1,2,3]`)
t.Assert(len(j.ToArray()), 3)
t.Assert(len(j.Array()), 3)
})
}
@ -400,7 +400,7 @@ func Test_Basic(t *testing.T) {
err = j.Remove("1")
t.Assert(err, nil)
t.Assert(j.Get("0"), 1)
t.Assert(len(j.ToArray()), 2)
t.Assert(len(j.Array()), 2)
j = gjson.New(`[1,2,3]`)
// If index 0 is delete, its next item will be at index 0.
@ -408,13 +408,13 @@ func Test_Basic(t *testing.T) {
t.Assert(j.Remove("0"), nil)
t.Assert(j.Remove("0"), nil)
t.Assert(j.Get("0"), nil)
t.Assert(len(j.ToArray()), 0)
t.Assert(len(j.Array()), 0)
j = gjson.New(`[1,2,3]`)
err = j.Remove("3")
t.Assert(err, nil)
t.Assert(j.Get("0"), 1)
t.Assert(len(j.ToArray()), 3)
t.Assert(len(j.Array()), 3)
j = gjson.New(`[1,2,3]`)
err = j.Remove("0.3")
@ -485,3 +485,17 @@ func TestJson_IsNil(t *testing.T) {
t.Assert(j.IsNil(), true)
})
}
func TestJson_Set_With_Struct(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
v := gjson.New(g.Map{
"user1": g.Map{"name": "user1"},
"user2": g.Map{"name": "user2"},
"user3": g.Map{"name": "user3"},
})
user1 := v.GetJson("user1")
user1.Set("id", 111)
v.Set("user1", user1)
t.Assert(v.Get("user1.id"), 111)
})
}

View File

@ -61,7 +61,7 @@ func Test_MapAttributeConvert(t *testing.T) {
Title map[string]interface{}
}{}
err = j.ToStruct(&tx)
err = j.Struct(&tx)
gtest.Assert(err, nil)
t.Assert(tx.Title, g.Map{
"l1": "标签1", "l2": "标签2",
@ -76,7 +76,7 @@ func Test_MapAttributeConvert(t *testing.T) {
Title map[string]string
}{}
err = j.ToStruct(&tx)
err = j.Struct(&tx)
gtest.Assert(err, nil)
t.Assert(tx.Title, g.Map{
"l1": "标签1", "l2": "标签2",

View File

@ -230,14 +230,14 @@ func Test_Convert(t *testing.T) {
err := p.GetStruct("person", &name)
t.Assert(err, nil)
t.Assert(name.Name, "gf")
t.Assert(p.ToMap()["name"], "gf")
err = p.ToStruct(&name)
t.Assert(p.Map()["name"], "gf")
err = p.Struct(&name)
t.Assert(err, nil)
t.Assert(name.Name, "gf")
//p.Dump()
p = gparser.New(`[0,1,2]`)
t.Assert(p.ToArray()[0], 0)
t.Assert(p.Array()[0], 0)
})
}

View File

@ -4,7 +4,7 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// Package errors provides simple functions to manipulate errors.
// Package gerror provides simple functions to manipulate errors.
//
// Very note that, this package is quite a base package, which should not import extra
// packages except standard packages, to avoid cycle imports.
@ -49,7 +49,7 @@ func New(text string) error {
return &Error{
stack: callers(),
text: text,
code: -1,
code: CodeNil,
}
}
@ -58,7 +58,7 @@ func Newf(format string, args ...interface{}) error {
return &Error{
stack: callers(),
text: fmt.Sprintf(format, args...),
code: -1,
code: CodeNil,
}
}
@ -68,7 +68,7 @@ func NewSkip(skip int, text string) error {
return &Error{
stack: callers(skip),
text: text,
code: -1,
code: CodeNil,
}
}
@ -78,7 +78,7 @@ func NewSkipf(skip int, format string, args ...interface{}) error {
return &Error{
stack: callers(skip),
text: fmt.Sprintf(format, args...),
code: -1,
code: CodeNil,
}
}
@ -237,7 +237,7 @@ func WrapCodeSkipf(code, skip int, err error, format string, args ...interface{}
}
}
// Cause returns the error code of current error.
// Code returns the error code of current error.
// It returns -1 if it has no error code or it does not implements interface Code.
func Code(err error) int {
if err != nil {

View File

@ -0,0 +1,40 @@
// 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
// Reserved internal error code of framework: code < 1000.
const (
// ===============================================================================
// Common system codes.
// ===============================================================================
CodeNil = -1 // No error code specified.
CodeOk = 0 // It is OK.
CodeInternalError = 50 + iota // An error occurred internally.
CodeValidationFailed // Data validation failed.
CodeDbOperationError // Database operation error.
CodeInvalidParameter // The given parameter for current operation is invalid.
CodeMissingParameter // Parameter for current operation is missing.
CodeInvalidOperation // The function cannot be used like this.
CodeInvalidConfiguration // The configuration is invalid for current operation.
CodeMissingConfiguration // The configuration is missing for current operation.
CodeNotImplemented // The operation is not implemented yet.
CodeNotSupported // The operation is not supported yet.
CodeOperationFailed // I tried, but I cannot give you what you want.
CodeNotAuthorized // Not Authorized.
CodeSecurityReason // Security Reason.
CodeServerBusy // Server is busy, please try again later.
CodeUnknown // Unknown error.
CodeResourceNotExist // Resource does not exist.
// ===============================================================================
// Common business codes.
// ===============================================================================
CodeBusinessValidationFailed = 300 + iota // Business validation failed.
)

View File

@ -179,6 +179,9 @@ func (err *Error) MarshalJSON() ([]byte, 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 {

View File

@ -0,0 +1,29 @@
// 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
// Option is option for creating error.
type Option struct {
Error error // Wrapped error if any.
Stack bool // Whether recording stack information into error.
Text string // Error text, which is created by New* functions.
Code int // Error code if necessary.
}
// NewOption creates and returns an 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{
error: option.Error,
text: option.Text,
code: option.Code,
}
if option.Stack {
err.stack = callers()
}
return err
}

View File

@ -7,6 +7,7 @@
package g
import (
"context"
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/internal/empty"
"github.com/gogf/gf/net/ghttp"
@ -75,3 +76,8 @@ func IsNil(value interface{}, traceSource ...bool) bool {
func IsEmpty(value interface{}) bool {
return empty.IsEmpty(value)
}
// RequestFromCtx retrieves and returns the Request object from context.
func RequestFromCtx(ctx context.Context) *ghttp.Request {
return ghttp.RequestFromCtx(ctx)
}

View File

@ -7,6 +7,7 @@
package gins
import (
"context"
"fmt"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/intlog"
@ -52,21 +53,24 @@ func Database(name ...string) gdb.DB {
if configFilePath == "" {
exampleFileName := "config.example.toml"
if exampleConfigFilePath, _ := Config().GetFilePath(exampleFileName); exampleConfigFilePath != "" {
panic(gerror.Wrapf(
panic(gerror.WrapCodef(
gerror.CodeMissingConfiguration,
err,
`configuration file "%s" not found, but found "%s", did you miss renaming the example configuration file?`,
Config().GetFileName(),
exampleFileName,
))
} else {
panic(gerror.Wrapf(
panic(gerror.WrapCodef(
gerror.CodeMissingConfiguration,
err,
`configuration file "%s" not found, did you miss the configuration file or the misspell the configuration file name?`,
Config().GetFileName(),
))
}
}
panic(gerror.Wrapf(
panic(gerror.WrapCodef(
gerror.CodeMissingConfiguration,
err,
`database initialization failed: "%s" node not found, is configuration file or configuration node missing?`,
configNodeNameDatabase,
@ -92,11 +96,11 @@ func Database(name ...string) gdb.DB {
}
if len(cg) > 0 {
if gdb.GetConfig(group) == nil {
intlog.Printf("add configuration for group: %s, %#v", g, cg)
intlog.Printf(context.TODO(), "add configuration for group: %s, %#v", g, cg)
gdb.SetConfigGroup(g, cg)
} else {
intlog.Printf("ignore configuration as it already exists for group: %s, %#v", g, cg)
intlog.Printf("%s, %#v", g, cg)
intlog.Printf(context.TODO(), "ignore configuration as it already exists for group: %s, %#v", g, cg)
intlog.Printf(context.TODO(), "%s, %#v", g, cg)
}
}
}
@ -104,17 +108,17 @@ func Database(name ...string) gdb.DB {
// which is the default group configuration.
if node := parseDBConfigNode(configMap); node != nil {
cg := gdb.ConfigGroup{}
if node.LinkInfo != "" || node.Host != "" {
if node.Link != "" || node.Host != "" {
cg = append(cg, *node)
}
if len(cg) > 0 {
if gdb.GetConfig(group) == nil {
intlog.Printf("add configuration for group: %s, %#v", gdb.DefaultGroupName, cg)
intlog.Printf(context.TODO(), "add configuration for group: %s, %#v", gdb.DefaultGroupName, cg)
gdb.SetConfigGroup(gdb.DefaultGroupName, cg)
} else {
intlog.Printf("ignore configuration as it already exists for group: %s, %#v", gdb.DefaultGroupName, cg)
intlog.Printf("%s, %#v", gdb.DefaultGroupName, cg)
intlog.Printf(context.TODO(), "ignore configuration as it already exists for group: %s, %#v", gdb.DefaultGroupName, cg)
intlog.Printf(context.TODO(), "%s, %#v", gdb.DefaultGroupName, cg)
}
}
}
@ -128,8 +132,10 @@ func Database(name ...string) gdb.DB {
loggerConfigMap = Config().GetMap(configNodeKey)
}
if len(loggerConfigMap) > 0 {
if err := db.GetLogger().SetConfigWithMap(loggerConfigMap); err != nil {
panic(err)
if logger, ok := db.GetLogger().(gdb.LoggerImp); ok {
if err := logger.SetConfigWithMap(loggerConfigMap); err != nil {
panic(err)
}
}
}
}
@ -156,15 +162,19 @@ func parseDBConfigNode(value interface{}) *gdb.ConfigNode {
if err != nil {
panic(err)
}
if _, v := gutil.MapPossibleItemByKey(nodeMap, "link"); v != nil {
node.LinkInfo = gconv.String(v)
// To be compatible with old version.
if _, v := gutil.MapPossibleItemByKey(nodeMap, "LinkInfo"); v != nil {
node.Link = gconv.String(v)
}
if _, v := gutil.MapPossibleItemByKey(nodeMap, "Link"); v != nil {
node.Link = gconv.String(v)
}
// Parse link syntax.
if node.LinkInfo != "" && node.Type == "" {
match, _ := gregex.MatchString(`([a-z]+):(.+)`, node.LinkInfo)
if node.Link != "" && node.Type == "" {
match, _ := gregex.MatchString(`([a-z]+):(.+)`, node.Link)
if len(match) == 3 {
node.Type = gstr.Trim(match[1])
node.LinkInfo = gstr.Trim(match[2])
node.Link = gstr.Trim(match[2])
}
}
return node

View File

@ -24,17 +24,30 @@ func Server(name ...interface{}) *ghttp.Server {
s := ghttp.GetServer(name...)
// To avoid file no found error while it's not necessary.
if Config().Available() {
var m map[string]interface{}
var (
serverConfigMap map[string]interface{}
serverLoggerConfigMap map[string]interface{}
)
nodeKey, _ := gutil.MapPossibleItemByKey(Config().GetMap("."), configNodeNameServer)
if nodeKey == "" {
nodeKey = configNodeNameServer
}
m = Config().GetMap(fmt.Sprintf(`%s.%s`, nodeKey, s.GetName()))
if len(m) == 0 {
m = Config().GetMap(nodeKey)
// Server configuration.
serverConfigMap = Config().GetMap(fmt.Sprintf(`%s.%s`, nodeKey, s.GetName()))
if len(serverConfigMap) == 0 {
serverConfigMap = Config().GetMap(nodeKey)
}
if len(m) > 0 {
if err := s.SetConfigWithMap(m); err != nil {
if len(serverConfigMap) > 0 {
if err := s.SetConfigWithMap(serverConfigMap); err != nil {
panic(err)
}
}
// Server logger configuration.
serverLoggerConfigMap = Config().GetMap(
fmt.Sprintf(`%s.%s.%s`, nodeKey, s.GetName(), configNodeNameLogger),
)
if len(serverLoggerConfigMap) > 0 {
if err := s.Logger().SetConfigWithMap(serverLoggerConfigMap); err != nil {
panic(err)
}
}

View File

@ -5,6 +5,7 @@
// You can obtain one at https://github.com/gogf/gf.
// Package gmvc provides basic object classes for MVC.
// Deprecated, no longer suggested.
package gmvc
import (
@ -12,6 +13,7 @@ import (
)
// Controller is used for controller register of ghttp.Server.
// Deprecated, no longer suggested.
type Controller struct {
Request *ghttp.Request
Response *ghttp.Response

View File

@ -9,6 +9,11 @@ package gmvc
import "github.com/gogf/gf/database/gdb"
type (
M = Model // M is alias for Model, just for short write purpose.
Model = *gdb.Model // Model is alias for *gdb.Model.
// M is alias for Model, just for short write purpose.
// Deprecated, no longer suggested.
M = *gdb.Model
// Model is alias for *gdb.Model.
// Deprecated, no longer suggested.
Model = *gdb.Model
)

View File

@ -19,6 +19,7 @@ import (
// View is the view object for controller.
// It's initialized when controller request initializes and destroyed
// when the controller request closes.
// Deprecated, no longer suggested.
type View struct {
mu sync.RWMutex
view *gview.View
@ -27,6 +28,7 @@ type View struct {
}
// NewView creates and returns a controller view object.
// Deprecated, no longer suggested.
func NewView(w *ghttp.Response) *View {
return &View{
view: gins.View(),
@ -76,7 +78,7 @@ func (view *View) LockFunc(f func(data gview.Params)) {
f(view.data)
}
// LockFunc locks reading for template variables by callback function <f>.
// RLockFunc locks reading for template variables by callback function <f>.
func (view *View) RLockFunc(f func(data gview.Params)) {
view.mu.RLock()
defer view.mu.RUnlock()

9
go.mod
View File

@ -5,16 +5,17 @@ go 1.14
require (
github.com/BurntSushi/toml v0.3.1
github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28
github.com/fatih/color v1.12.0
github.com/fsnotify/fsnotify v1.4.9
github.com/go-sql-driver/mysql v1.5.0
github.com/gogf/mysql v1.6.1-0.20210603073548-16164ae25579
github.com/gomodule/redigo v2.0.0+incompatible
github.com/gorilla/websocket v1.4.1
github.com/grokify/html-strip-tags-go v0.0.0-20190921062105-daaa06bf1aaf
github.com/mattn/go-runewidth v0.0.10 // indirect
github.com/olekukonko/tablewriter v0.0.5
go.opentelemetry.io/otel v0.19.0
go.opentelemetry.io/otel/oteltest v0.19.0
go.opentelemetry.io/otel/trace v0.19.0
go.opentelemetry.io/otel v1.0.0-RC2
go.opentelemetry.io/otel/oteltest v1.0.0-RC2
go.opentelemetry.io/otel/trace v1.0.0-RC2
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102
golang.org/x/text v0.3.4
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c

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