Compare commits

..

54 Commits

Author SHA1 Message Date
87609a3424 version update 2022-03-31 16:52:15 +08:00
b4184e4523 Merge branch 'master' of https://github.com/gogf/gf 2022-03-31 16:16:57 +08:00
05508e4fcb improve cache feature for package gdb 2022-03-31 16:15:44 +08:00
372bae4799 fix issue in missing first result column when in select cache scenario 2022-03-31 15:42:12 +08:00
21f48d3750 improve Unique function performance for normal arrays 2022-03-30 14:32:16 +08:00
126a81d89a Merge pull request #1697 from cuishuang/master
fix some typos
2022-03-29 20:38:20 +08:00
707dc6b346 add xextensions feature for package goai 2022-03-29 20:31:00 +08:00
c1c86c026f fix type integer from type number for package goai 2022-03-28 16:40:43 +08:00
5c4982cb0c add Sort field for Pat of package goai 2022-03-28 16:20:08 +08:00
fed38ea7ab add Sort field for Operation of package goai 2022-03-28 16:18:44 +08:00
4d6ef1c52d add sort field for path of package goai 2022-03-28 15:59:02 +08:00
c6aba6da4d improve ExternalDocs feature for package goai 2022-03-28 15:21:58 +08:00
ec92d2b7f4 improve empty slice/object validation logic for package gvalid 2022-03-25 17:53:58 +08:00
6810e71220 add UT case for package goai 2022-03-25 12:00:09 +08:00
f4192d695c remove sort feature for openapi 2022-03-24 22:08:06 +08:00
6664437b06 add sort feature for path of openapi 2022-03-24 21:56:37 +08:00
96a135834a improve openapi genereating for package ghttp 2022-03-24 20:15:54 +08:00
09ba1bf1fb imrove context handling for package gdb 2022-03-24 17:51:49 +08:00
cc01629b57 improve hook and sharding feature for package gdb 2022-03-24 15:33:30 +08:00
2d586859c3 fix some typos
Signed-off-by: cuishuang <imcusg@gmail.com>
2022-03-23 21:45:00 +08:00
a5e20e4939 improve openapi paths sequence in json as api defined sequence 2022-03-23 17:39:38 +08:00
0e3f4f45e0 improve hook feature for package gdb 2022-03-23 16:23:33 +08:00
045c3e132f improve hook feature for package gdb 2022-03-23 16:17:18 +08:00
80c068ae05 add example for properties of swagger schema object 2022-03-23 15:05:37 +08:00
6574b8cbfe change build-in swagger ui to public cdn 2022-03-23 14:48:34 +08:00
20c48b1712 change build-in swagger ui to public cdn 2022-03-23 14:46:56 +08:00
ee16b6df88 change build-in swagger ui to public cdn 2022-03-23 14:43:48 +08:00
439350836e fix example case for package gsession 2022-03-21 22:44:21 +08:00
5ee387672b enhancement from issue #1689 2022-03-21 22:36:06 +08:00
f670c24e2c fix issue #1681 2022-03-21 22:24:59 +08:00
f2e1f63396 fix issue #1679 2022-03-21 22:04:15 +08:00
6dacdd60dc add sharding feature for package gdb 2022-03-21 21:17:48 +08:00
87ccc27ee4 sharding feature develop 2022-03-21 14:26:56 +08:00
950695664c improve hook feature for package gdb 2022-03-19 23:38:57 +08:00
d1f76f3834 Merge branch 'master' of https://github.com/gogf/gf 2022-03-19 22:54:50 +08:00
66e6a05e5f cli template update 2022-03-19 22:54:38 +08:00
0f430c66ae Merge pull request #1684 from houseme/fix-1674
improve ignore and up websocket 1.5.0
2022-03-19 22:50:53 +08:00
8357b0f649 improve comment 2022-03-19 17:58:21 +08:00
7fc75bfeff improve ignore and up websocket 1.5.0 2022-03-19 16:16:18 +08:00
d7764e2968 Merge branch 'develop' 2022-03-18 11:52:02 +08:00
f865d6fa6a remove UT case of http server in package gins 2022-03-18 10:13:00 +08:00
e6bbead4e6 Merge pull request #1660 from qinyuguang/gdb_cache
gdb returns result when cache set failed
2022-03-17 22:05:06 +08:00
5f3a525d11 add Set function for AdapterFile for package gcfg 2022-03-17 21:41:10 +08:00
c5d80a2192 improve UT cases for package gins/gvalid 2022-03-17 21:31:07 +08:00
97b8f0f781 improve recursilve validation feature for package gvalid 2022-03-17 20:27:59 +08:00
bceb5fc7de rename gdebug.TestData* -> gtest.Data*; add UT case for http server 2022-03-17 16:58:04 +08:00
b3e66d8023 improve package gjson 2022-03-15 21:45:47 +08:00
e06f831205 improve package grand 2022-03-15 17:09:35 +08:00
60340a7348 fix UT case for package glog 2022-03-15 10:15:46 +08:00
dccfc1c8cd add hook feature for model of package gdb 2022-03-14 23:47:55 +08:00
d58186372f Merge branch 'master' into develop 2022-03-14 19:43:21 +08:00
d32246275a rename DoGetAll to DoSelect 2022-03-14 19:41:32 +08:00
bbab9f3934 rename DoGetAll to DoSelect 2022-03-14 19:36:43 +08:00
a4ab9c284f gdb returns result when cache set failed 2022-03-11 13:04:53 +08:00
182 changed files with 2730 additions and 1109 deletions

View File

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

1
.gitignore vendored
View File

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

View File

@ -5,6 +5,7 @@
## 1. Install
## 1) PreCompiled Binary
You can also install `gf` tool using pre-built binaries: https://github.com/gogf/gf/releases
1. `Mac` & `Linux`
@ -19,6 +20,13 @@ You can also install `gf` tool using pre-built binaries: https://github.com/gogf
3. Database `sqlite` and `oracle` are not support in `gf gen` command in default as it needs `cgo` and `gcc`, you can manually make some changes to the source codes and do the building.
## 2) Manually Install
```shell
git clone https://github.com/gogf/gf && cd cmd/gf && go install
```
## 2. Commands
```html
$ gf

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -399,6 +399,9 @@ func (tree *AVLTree) Replace(data map[interface{}]interface{}) {
// String returns a string representation of container
func (tree *AVLTree) String() string {
if tree == nil {
return ""
}
tree.mu.RLock()
defer tree.mu.RUnlock()
str := ""

View File

@ -367,6 +367,9 @@ func (tree *BTree) Right() *BTreeEntry {
// String returns a string representation of container (for debugging purposes)
func (tree *BTree) String() string {
if tree == nil {
return ""
}
tree.mu.RLock()
defer tree.mu.RUnlock()
var buffer bytes.Buffer

View File

@ -623,6 +623,9 @@ func (tree *RedBlackTree) Replace(data map[interface{}]interface{}) {
// String returns a string representation of container.
func (tree *RedBlackTree) String() string {
if tree == nil {
return ""
}
tree.mu.RLock()
defer tree.mu.RUnlock()
str := ""

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,46 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gdb
import (
"context"
"github.com/gogf/gf/v2/os/gctx"
)
// internalCtxData stores data in ctx for internal usage purpose.
type internalCtxData struct {
// Operation DB.
DB DB
// The first column in result response from database server.
// This attribute is used for Value/Count selection statement purpose,
// which is to avoid HOOK handler that might modify the result columns
// that can confuse the Value/Count selection statement logic.
FirstResultColumn string
}
const (
internalCtxDataKeyInCtx gctx.StrKey = "InternalCtxData"
)
func (c *Core) injectInternalCtxData(ctx context.Context) context.Context {
// If the internal data is already injected, it does nothing.
if ctx.Value(internalCtxDataKeyInCtx) != nil {
return ctx
}
return context.WithValue(ctx, internalCtxDataKeyInCtx, &internalCtxData{
DB: c.db,
})
}
func (c *Core) getInternalCtxDataFromCtx(ctx context.Context) *internalCtxData {
if v := ctx.Value(internalCtxDataKeyInCtx); v != nil {
return v.(*internalCtxData)
}
return nil
}

View File

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

View File

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

View File

@ -71,7 +71,7 @@ func (c *Core) doBeginCtx(ctx context.Context) (*TX, error) {
func (c *Core) Transaction(ctx context.Context, f func(ctx context.Context, tx *TX) error) (err error) {
var tx *TX
if ctx == nil {
ctx = c.GetCtx()
ctx = c.db.GetCtx()
}
// Check transaction object from context.
tx = TXFromCtx(ctx, c.db.GetGroup())
@ -158,6 +158,9 @@ func (tx *TX) transactionKeyForNestedPoint() string {
// Ctx sets the context for current transaction.
func (tx *TX) Ctx(ctx context.Context) *TX {
tx.ctx = ctx
if tx.ctx != nil {
tx.ctx = tx.db.GetCore().injectInternalCtxData(tx.ctx)
}
return tx
}

View File

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

View File

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

View File

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

View File

@ -8,6 +8,7 @@ package gdb
import (
"bytes"
"context"
"fmt"
"reflect"
"regexp"
@ -180,12 +181,12 @@ func DataToMapDeep(value interface{}) map[string]interface{} {
return m
}
// doHandleTableName adds prefix string and quote chars for the table. It handles table string like:
// doHandleTableName adds prefix string and quote chars for table name. It handles table string like:
// "user", "user u", "user,user_detail", "user u, user_detail ut", "user as u, user_detail as ut",
// "user.user u", "`user`.`user` u".
//
// Note that, this will automatically checks the table prefix whether already added, if true it does
// nothing to the table name, or else adds the prefix to the table name.
// Note that, this will automatically check the table prefix whether already added, if true it does
// nothing to the table name, or else adds the prefix to the table name and returns new table name with prefix.
func doHandleTableName(table, prefix, charLeft, charRight string) string {
var (
index = 0
@ -364,7 +365,7 @@ func isKeyValueCanBeOmitEmpty(omitEmpty bool, whereType string, key, value inter
}
// formatWhereHolder formats where statement and its arguments for `Where` and `Having` statements.
func formatWhereHolder(db DB, in formatWhereHolderInput) (newWhere string, newArgs []interface{}) {
func formatWhereHolder(ctx context.Context, db DB, in formatWhereHolderInput) (newWhere string, newArgs []interface{}) {
var (
buffer = bytes.NewBuffer(nil)
reflectInfo = reflection.OriginValueAndKind(in.Where)
@ -393,7 +394,7 @@ func formatWhereHolder(db DB, in formatWhereHolderInput) (newWhere string, newAr
}
case reflect.Struct:
// If the `where` parameter is DO struct, it then adds `OmitNil` option for this condition,
// If the `where` parameter is `DO` struct, it then adds `OmitNil` option for this condition,
// which will filter all nil parameters in `where`.
if isDoStruct(in.Where) {
in.OmitNil = true
@ -523,7 +524,9 @@ func formatWhereHolder(db DB, in formatWhereHolderInput) (newWhere string, newAr
whereStr, _ = gregex.ReplaceStringFunc(`(\?)`, whereStr, func(s string) string {
index++
if i+len(newArgs) == index {
sqlWithHolder, holderArgs := model.getFormattedSqlAndArgs(queryTypeNormal, false)
sqlWithHolder, holderArgs := model.getFormattedSqlAndArgs(
ctx, queryTypeNormal, false,
)
newArgs = append(newArgs, holderArgs...)
// Automatically adding the brackets.
return "(" + sqlWithHolder + ")"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -143,7 +143,7 @@ func (m *Model) doWithScanStruct(pointer interface{}) error {
}
// Recursively with feature checks.
model = m.db.With(field.Value)
model = m.db.With(field.Value).Hook(m.hookHandler)
if m.withAll {
model = model.WithAll()
} else {
@ -258,7 +258,7 @@ func (m *Model) doWithScanStructs(pointer interface{}) error {
fieldKeys = structType.FieldKeys()
}
// Recursively with feature checks.
model = m.db.With(field.Value)
model = m.db.With(field.Value).Hook(m.hookHandler)
if m.withAll {
model = model.WithAll()
} else {

View File

@ -0,0 +1,136 @@
// 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 (
"context"
"database/sql"
"fmt"
"testing"
"github.com/gogf/gf/v2/container/gvar"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/test/gtest"
)
func Test_Model_Hook_Select(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
m := db.Model(table).Hook(gdb.HookHandler{
Select: func(ctx context.Context, in *gdb.HookSelectInput) (result gdb.Result, err error) {
result, err = in.Next(ctx)
if err != nil {
return
}
for i, record := range result {
record["test"] = gvar.New(100 + record["id"].Int())
result[i] = record
}
return
},
})
all, err := m.Where(`id > 6`).OrderAsc(`id`).All()
t.AssertNil(err)
t.Assert(len(all), 4)
t.Assert(all[0]["id"].Int(), 7)
t.Assert(all[0]["test"].Int(), 107)
t.Assert(all[1]["test"].Int(), 108)
t.Assert(all[2]["test"].Int(), 109)
t.Assert(all[3]["test"].Int(), 110)
})
}
func Test_Model_Hook_Insert(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
m := db.Model(table).Hook(gdb.HookHandler{
Insert: func(ctx context.Context, in *gdb.HookInsertInput) (result sql.Result, err error) {
for i, item := range in.Data {
item["passport"] = fmt.Sprintf(`test_port_%d`, item["id"])
item["nickname"] = fmt.Sprintf(`test_name_%d`, item["id"])
in.Data[i] = item
}
return in.Next(ctx)
},
})
_, err := m.Insert(g.Map{
"id": 1,
"nickname": "name_1",
})
t.AssertNil(err)
one, err := m.One()
t.AssertNil(err)
t.Assert(one["id"].Int(), 1)
t.Assert(one["passport"], `test_port_1`)
t.Assert(one["nickname"], `test_name_1`)
})
}
func Test_Model_Hook_Update(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
m := db.Model(table).Hook(gdb.HookHandler{
Update: func(ctx context.Context, in *gdb.HookUpdateInput) (result sql.Result, err error) {
switch value := in.Data.(type) {
case gdb.List:
for i, data := range value {
data["passport"] = `port`
data["nickname"] = `name`
value[i] = data
}
in.Data = value
case gdb.Map:
value["passport"] = `port`
value["nickname"] = `name`
in.Data = value
}
return in.Next(ctx)
},
})
_, err := m.Data(g.Map{
"nickname": "name_1",
}).WherePri(1).Update()
t.AssertNil(err)
one, err := m.One()
t.AssertNil(err)
t.Assert(one["id"].Int(), 1)
t.Assert(one["passport"], `port`)
t.Assert(one["nickname"], `name`)
})
}
func Test_Model_Hook_Delete(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
m := db.Model(table).Hook(gdb.HookHandler{
Delete: func(ctx context.Context, in *gdb.HookDeleteInput) (result sql.Result, err error) {
return db.Model(table).Data(g.Map{
"nickname": `deleted`,
}).Where(in.Condition).Update()
},
})
_, err := m.Where(1).Delete()
t.AssertNil(err)
all, err := m.All()
t.AssertNil(err)
for _, item := range all {
t.Assert(item["nickname"].String(), `deleted`)
}
})
}

View File

@ -550,7 +550,7 @@ func Test_Scan_JsonAttributes(t *testing.T) {
}
table := "jfy_gift"
array := gstr.SplitAndTrim(gtest.TestDataContent(`issue1380.sql`), ";")
array := gstr.SplitAndTrim(gtest.DataContent(`issue1380.sql`), ";")
for _, v := range array {
if _, err := db.Exec(ctx, v); err != nil {
gtest.Error(err)

View File

@ -0,0 +1,138 @@
// 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 (
"context"
"testing"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/test/gtest"
)
func Test_Model_Sharding(t *testing.T) {
table1 := createTable()
table2 := createTable()
defer dropTable(table1)
defer dropTable(table2)
gtest.C(t, func(t *gtest.T) {
_, err1 := db.Model(table1).Data(g.Map{
"id": 1,
}).Insert()
t.AssertNil(err1)
_, err2 := db.Model(table2).Data(g.Map{
"id": 2,
}).Insert()
t.AssertNil(err2)
})
// no sharding.
gtest.C(t, func(t *gtest.T) {
all, err := db.Model(table1).All()
t.AssertNil(err)
t.Assert(len(all), 1)
t.Assert(all[0]["id"].String(), 1)
})
// with sharding handler.
gtest.C(t, func(t *gtest.T) {
all, err := db.Model(table1).Sharding(func(ctx context.Context, in gdb.ShardingInput) (out *gdb.ShardingOutput, err error) {
out = &gdb.ShardingOutput{
Table: table2,
}
return
}).All()
t.AssertNil(err)
t.Assert(len(all), 1)
t.Assert(all[0]["id"].String(), 2)
})
// with sharding handler and no existence table name.
gtest.C(t, func(t *gtest.T) {
all, err := db.Model("none").Sharding(func(ctx context.Context, in gdb.ShardingInput) (out *gdb.ShardingOutput, err error) {
out = &gdb.ShardingOutput{
Table: table2,
}
return
}).All()
t.AssertNil(err)
t.Assert(len(all), 1)
t.Assert(all[0]["id"].String(), 2)
})
// with sharding handler and no existence table name and tables fields retrieving.
gtest.C(t, func(t *gtest.T) {
type User struct {
Id int
Passport string
Password string
NickName string
}
var users []User
err := db.Model("none").Sharding(func(ctx context.Context, in gdb.ShardingInput) (out *gdb.ShardingOutput, err error) {
out = &gdb.ShardingOutput{
Table: table2,
}
return
}).Scan(&users)
t.AssertNil(err)
t.Assert(len(users), 1)
t.Assert(users[0].Id, 2)
})
}
func Test_Model_Sharding_Schema(t *testing.T) {
var (
db1 = db
db2 = db.Schema(TestSchema2)
table1 = createTableWithDb(db1)
table2 = createTableWithDb(db2)
)
defer dropTableWithDb(db1, table1)
defer dropTableWithDb(db2, table2)
gtest.C(t, func(t *gtest.T) {
_, err1 := db1.Model(table1).Data(g.Map{
"id": 1,
}).Insert()
t.AssertNil(err1)
_, err2 := db2.Model(table2).Data(g.Map{
"id": 2,
}).Insert()
t.AssertNil(err2)
})
// no sharding.
gtest.C(t, func(t *gtest.T) {
all, err := db1.Model(table1).All()
t.AssertNil(err)
t.Assert(len(all), 1)
t.Assert(all[0]["id"].String(), 1)
})
gtest.C(t, func(t *gtest.T) {
_, err := db1.Model(table2).All()
// Table not exist error.
t.AssertNE(err, nil)
})
gtest.C(t, func(t *gtest.T) {
all, err := db2.Model(table2).All()
t.AssertNil(err)
t.Assert(len(all), 1)
t.Assert(all[0]["id"].String(), 2)
})
// with sharding handler and no existence table name and schema change.
gtest.C(t, func(t *gtest.T) {
all, err := db1.Model("none").Sharding(func(ctx context.Context, in gdb.ShardingInput) (out *gdb.ShardingOutput, err error) {
out = &gdb.ShardingOutput{
Table: table2,
Schema: TestSchema2,
}
return
}).All()
t.AssertNil(err)
t.Assert(len(all), 1)
t.Assert(all[0]["id"].String(), 2)
})
}

View File

@ -10,7 +10,6 @@ import (
"fmt"
"testing"
"github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/test/gtest"
@ -1644,7 +1643,7 @@ func Test_Table_Relation_With_MultipleDepends1(t *testing.T) {
dropTable("table_b")
dropTable("table_c")
}()
for _, v := range gstr.SplitAndTrim(gfile.GetContents(gdebug.TestDataPath("with_multiple_depends.sql")), ";") {
for _, v := range gstr.SplitAndTrim(gfile.GetContents(gtest.DataPath("with_multiple_depends.sql")), ";") {
if _, err := db.Exec(ctx, v); err != nil {
gtest.Error(err)
}
@ -1716,7 +1715,7 @@ func Test_Table_Relation_With_MultipleDepends2(t *testing.T) {
dropTable("table_b")
dropTable("table_c")
}()
for _, v := range gstr.SplitAndTrim(gfile.GetContents(gdebug.TestDataPath("with_multiple_depends.sql")), ";") {
for _, v := range gstr.SplitAndTrim(gfile.GetContents(gtest.DataPath("with_multiple_depends.sql")), ";") {
if _, err := db.Exec(ctx, v); err != nil {
gtest.Error(err)
}
@ -1803,7 +1802,7 @@ func Test_Table_Relation_With_MultipleDepends_Embedded(t *testing.T) {
dropTable("table_b")
dropTable("table_c")
}()
for _, v := range gstr.SplitAndTrim(gfile.GetContents(gdebug.TestDataPath("with_multiple_depends.sql")), ";") {
for _, v := range gstr.SplitAndTrim(gfile.GetContents(gtest.DataPath("with_multiple_depends.sql")), ";") {
if _, err := db.Exec(ctx, v); err != nil {
gtest.Error(err)
}
@ -1996,7 +1995,7 @@ func Test_With_Feature_Issue1401(t *testing.T) {
table1 = "parcels"
table2 = "parcel_items"
)
array := gstr.SplitAndTrim(gtest.TestDataContent(`issue1401.sql`), ";")
array := gstr.SplitAndTrim(gtest.DataContent(`issue1401.sql`), ";")
for _, v := range array {
if _, err := db.Exec(ctx, v); err != nil {
gtest.Error(err)
@ -2038,7 +2037,7 @@ func Test_With_Feature_Issue1412(t *testing.T) {
table1 = "parcels"
table2 = "items"
)
array := gstr.SplitAndTrim(gtest.TestDataContent(`issue1412.sql`), ";")
array := gstr.SplitAndTrim(gtest.DataContent(`issue1412.sql`), ";")
for _, v := range array {
if _, err := db.Exec(ctx, v); err != nil {
gtest.Error(err)

View File

@ -18,7 +18,6 @@ import (
"github.com/gogf/gf/v2/container/garray"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
@ -2195,7 +2194,7 @@ func Test_Model_FieldsEx_WithReservedWords(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
table = "fieldsex_test_table"
sqlTpcPath = gdebug.TestDataPath("reservedwords_table_tpl.sql")
sqlTpcPath = gtest.DataPath("reservedwords_table_tpl.sql")
sqlContent = gfile.GetContents(sqlTpcPath)
)
t.AssertNE(sqlContent, "")

View File

@ -1,36 +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 gdebug
import (
"io/ioutil"
"path/filepath"
)
// TestDataPath retrieves and returns the testdata path of current package,
// which is used for unit testing cases only.
// The optional parameter `names` specifies the sub-folders/sub-files,
// which will be joined with current system separator and returned with the path.
func TestDataPath(names ...string) string {
path := CallerDirectory() + string(filepath.Separator) + "testdata"
for _, name := range names {
path += string(filepath.Separator) + name
}
return path
}
// TestDataContent retrieves and returns the file content for specified testdata path of current package
func TestDataContent(names ...string) string {
path := TestDataPath(names...)
if path != "" {
data, err := ioutil.ReadFile(path)
if err == nil {
return string(data)
}
}
return ""
}

View File

@ -9,7 +9,6 @@ package gbase64_test
import (
"testing"
"github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/encoding/gbase64"
"github.com/gogf/gf/v2/test/gtest"
)
@ -66,7 +65,7 @@ func Test_Basic(t *testing.T) {
}
func Test_File(t *testing.T) {
path := gdebug.TestDataPath("test")
path := gtest.DataPath("test")
expect := "dGVzdA=="
gtest.C(t, func(t *gtest.T) {
b, err := gbase64.EncodeFile(path)

View File

@ -9,7 +9,6 @@ package gcompress_test
import (
"testing"
"github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/encoding/gcompress"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gtime"
@ -43,7 +42,7 @@ func Test_Gzip_UnGzip(t *testing.T) {
}
func Test_Gzip_UnGzip_File(t *testing.T) {
srcPath := gdebug.TestDataPath("gzip", "file.txt")
srcPath := gtest.DataPath("gzip", "file.txt")
dstPath1 := gfile.Temp(gtime.TimestampNanoStr(), "gzip.zip")
dstPath2 := gfile.Temp(gtime.TimestampNanoStr(), "file.txt")

View File

@ -10,7 +10,6 @@ import (
"bytes"
"testing"
"github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/encoding/gcompress"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gtime"
@ -20,8 +19,8 @@ import (
func Test_ZipPath(t *testing.T) {
// file
gtest.C(t, func(t *gtest.T) {
srcPath := gdebug.TestDataPath("zip", "path1", "1.txt")
dstPath := gdebug.TestDataPath("zip", "zip.zip")
srcPath := gtest.DataPath("zip", "path1", "1.txt")
dstPath := gtest.DataPath("zip", "zip.zip")
t.Assert(gfile.Exists(dstPath), false)
t.Assert(gcompress.ZipPath(srcPath, dstPath), nil)
@ -42,8 +41,8 @@ func Test_ZipPath(t *testing.T) {
// multiple files
gtest.C(t, func(t *gtest.T) {
var (
srcPath1 = gdebug.TestDataPath("zip", "path1", "1.txt")
srcPath2 = gdebug.TestDataPath("zip", "path2", "2.txt")
srcPath1 = gtest.DataPath("zip", "path1", "1.txt")
srcPath2 = gtest.DataPath("zip", "path2", "2.txt")
dstPath = gfile.Temp(gtime.TimestampNanoStr(), "zip.zip")
)
if p := gfile.Dir(dstPath); !gfile.Exists(p) {
@ -75,8 +74,8 @@ func Test_ZipPath(t *testing.T) {
// one dir and one file.
gtest.C(t, func(t *gtest.T) {
var (
srcPath1 = gdebug.TestDataPath("zip", "path1")
srcPath2 = gdebug.TestDataPath("zip", "path2", "2.txt")
srcPath1 = gtest.DataPath("zip", "path1")
srcPath2 = gtest.DataPath("zip", "path2", "2.txt")
dstPath = gfile.Temp(gtime.TimestampNanoStr(), "zip.zip")
)
if p := gfile.Dir(dstPath); !gfile.Exists(p) {
@ -107,8 +106,8 @@ func Test_ZipPath(t *testing.T) {
})
// directory.
gtest.C(t, func(t *gtest.T) {
srcPath := gdebug.TestDataPath("zip")
dstPath := gdebug.TestDataPath("zip", "zip.zip")
srcPath := gtest.DataPath("zip")
dstPath := gtest.DataPath("zip", "zip.zip")
pwd := gfile.Pwd()
err := gfile.Chdir(srcPath)
@ -141,10 +140,10 @@ func Test_ZipPath(t *testing.T) {
// multiple directory paths joined using char ','.
gtest.C(t, func(t *gtest.T) {
var (
srcPath = gdebug.TestDataPath("zip")
srcPath1 = gdebug.TestDataPath("zip", "path1")
srcPath2 = gdebug.TestDataPath("zip", "path2")
dstPath = gdebug.TestDataPath("zip", "zip.zip")
srcPath = gtest.DataPath("zip")
srcPath1 = gtest.DataPath("zip", "path1")
srcPath2 = gtest.DataPath("zip", "path2")
dstPath = gtest.DataPath("zip", "zip.zip")
)
pwd := gfile.Pwd()
err := gfile.Chdir(srcPath)
@ -181,9 +180,9 @@ func Test_ZipPath(t *testing.T) {
func Test_ZipPathWriter(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
srcPath = gdebug.TestDataPath("zip")
srcPath1 = gdebug.TestDataPath("zip", "path1")
srcPath2 = gdebug.TestDataPath("zip", "path2")
srcPath = gtest.DataPath("zip")
srcPath1 = gtest.DataPath("zip", "path1")
srcPath2 = gtest.DataPath("zip", "path2")
)
pwd := gfile.Pwd()
err := gfile.Chdir(srcPath)

View File

@ -41,7 +41,7 @@ func (j *Json) IsNil() bool {
}
// Get retrieves and returns value by specified `pattern`.
// It returns all values of current Json object if `pattern` is given empty or string ".".
// It returns all values of current Json object if `pattern` is given ".".
// It returns nil if no value found by `pattern`.
//
// We can also access slice item by its index number in `pattern` like:
@ -71,7 +71,7 @@ func (j *Json) Get(pattern string, def ...interface{}) *gvar.Var {
}
// GetJson gets the value by specified `pattern`,
// and converts it to a un-concurrent-safe Json object.
// and converts it to an un-concurrent-safe Json object.
func (j *Json) GetJson(pattern string, def ...interface{}) *Json {
return New(j.Get(pattern, def...).Val())
}

View File

@ -9,17 +9,17 @@ package gjson_test
import (
"fmt"
"github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/test/gtest"
)
func ExampleLoad() {
jsonFilePath := gdebug.TestDataPath("json", "data1.json")
jsonFilePath := gtest.DataPath("json", "data1.json")
j, _ := gjson.Load(jsonFilePath)
fmt.Println(j.Get("name"))
fmt.Println(j.Get("score"))
notExistFilePath := gdebug.TestDataPath("json", "data2.json")
notExistFilePath := gtest.DataPath("json", "data2.json")
j2, _ := gjson.Load(notExistFilePath)
fmt.Println(j2.Get("name"))
@ -195,7 +195,7 @@ func ExampleIsValidDataType() {
}
func ExampleLoad_Xml() {
jsonFilePath := gdebug.TestDataPath("xml", "data1.xml")
jsonFilePath := gtest.DataPath("xml", "data1.xml")
j, _ := gjson.Load(jsonFilePath)
fmt.Println(j.Get("doc.name"))
fmt.Println(j.Get("doc.score"))

View File

@ -76,6 +76,7 @@ github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfC
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogf/katyusha v0.3.0/go.mod h1:AknlfKGS7HjZfLiz74Nd/eL2uq7bg+9aucZgfvXw8vQ=
github.com/gogf/katyusha v0.3.1-0.20220128101623-e25b27a99b29 h1:s28bNu6QekQG3XFFB3G6YV3AGvQz8Uj4lBu/WXIeF28=
@ -114,6 +115,8 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0=
github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
@ -135,6 +138,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/longbridgeapp/sqlparser v0.3.1 h1:iWOZWGIFgQrJRgobLXUNJdvqGRpbVXkyKUKUA5CNJBE=
github.com/longbridgeapp/sqlparser v0.3.1/go.mod h1:GIHaUq8zvYyHLCLMJJykx1CdM6LHtkUih/QaJXySSx4=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=

View File

@ -9,7 +9,7 @@ import (
)
type HelloReq struct {
g.Meta `path:"/hello" method:"get"`
g.Meta `path:"/hello" method:"get" sort:"1"`
Name string `v:"required" dc:"Your name"`
}
type HelloRes struct {

View File

@ -58,10 +58,10 @@ func Server(name ...interface{}) *ghttp.Server {
}
}
}
// Server configuration.
// Automatically retrieve configuration by instance name.
serverConfigMap = Config().MustGet(
ctx,
fmt.Sprintf(`%s.%s`, configNodeName, server.GetName()),
fmt.Sprintf(`%s.%s`, configNodeName, instanceName),
).Map()
if len(serverConfigMap) == 0 {
serverConfigMap = Config().MustGet(ctx, configNodeName).Map()
@ -81,7 +81,7 @@ func Server(name ...interface{}) *ghttp.Server {
// Server logger configuration checks.
serverLoggerConfigMap = Config().MustGet(
ctx,
fmt.Sprintf(`%s.%s.%s`, configNodeName, server.GetName(), configNodeNameLogger),
fmt.Sprintf(`%s.%s.%s`, configNodeName, instanceName, configNodeNameLogger),
).Map()
if len(serverLoggerConfigMap) > 0 {
if err = server.Logger().SetConfigWithMap(serverLoggerConfigMap); err != nil {

View File

@ -12,7 +12,6 @@ import (
"testing"
"time"
"github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/frame/gins"
"github.com/gogf/gf/v2/os/gcfg"
"github.com/gogf/gf/v2/os/gfile"
@ -23,7 +22,7 @@ import (
var (
ctx = context.Background()
configContent = gfile.GetContents(
gdebug.TestDataPath("config", "config.toml"),
gtest.DataPath("config", "config.toml"),
)
)

View File

@ -10,7 +10,6 @@ import (
"testing"
"time"
"github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/frame/gins"
"github.com/gogf/gf/v2/os/gcfg"
"github.com/gogf/gf/v2/os/gfile"
@ -20,7 +19,7 @@ import (
func Test_Database(t *testing.T) {
databaseContent := gfile.GetContents(
gdebug.TestDataPath("database", "config.toml"),
gtest.DataPath("database", "config.toml"),
)
gtest.C(t, func(t *gtest.T) {
var err error

View File

@ -10,7 +10,6 @@ import (
"testing"
"time"
"github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/frame/gins"
"github.com/gogf/gf/v2/os/gcfg"
"github.com/gogf/gf/v2/os/gfile"
@ -20,7 +19,7 @@ import (
func Test_Redis(t *testing.T) {
redisContent := gfile.GetContents(
gdebug.TestDataPath("redis", "config.toml"),
gtest.DataPath("redis", "config.toml"),
)
gtest.C(t, func(t *gtest.T) {

View File

@ -0,0 +1,35 @@
// 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 gins
//func Test_Server(t *testing.T) {
// gtest.C(t, func(t *gtest.T) {
// var (
// path = gcfg.DefaultConfigFileName
// serverConfigContent = gtest.DataContent("server", "config.yaml")
// err = gfile.PutContents(path, serverConfigContent)
// )
// t.AssertNil(err)
// defer gfile.Remove(path)
//
// time.Sleep(time.Second)
//
// localInstances.Clear()
// defer localInstances.Clear()
//
// s := Server("tempByInstanceName")
// s.BindHandler("/", func(r *ghttp.Request) {
// r.Response.Write("hello")
// })
// s.SetDumpRouterMap(false)
// t.AssertNil(s.Start())
// defer t.AssertNil(s.Shutdown())
//
// content := HttpClient().GetContent(gctx.New(), `http://127.0.0.1:8003/`)
// t.Assert(content, `hello`)
// })
//}

View File

@ -11,7 +11,6 @@ import (
"fmt"
"testing"
"github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/os/gcfg"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gtime"
@ -53,7 +52,7 @@ func Test_View(t *testing.T) {
func Test_View_Config(t *testing.T) {
// view1 test1
gtest.C(t, func(t *gtest.T) {
dirPath := gdebug.TestDataPath("view1")
dirPath := gtest.DataPath("view1")
Config().GetAdapter().(*gcfg.AdapterFile).SetContent(gfile.GetContents(gfile.Join(dirPath, "config.toml")))
defer Config().GetAdapter().(*gcfg.AdapterFile).ClearContent()
defer localInstances.Clear()
@ -75,7 +74,7 @@ func Test_View_Config(t *testing.T) {
})
// view1 test2
gtest.C(t, func(t *gtest.T) {
dirPath := gdebug.TestDataPath("view1")
dirPath := gtest.DataPath("view1")
Config().GetAdapter().(*gcfg.AdapterFile).SetContent(gfile.GetContents(gfile.Join(dirPath, "config.toml")))
defer Config().GetAdapter().(*gcfg.AdapterFile).ClearContent()
defer localInstances.Clear()
@ -97,7 +96,7 @@ func Test_View_Config(t *testing.T) {
})
// view2
gtest.C(t, func(t *gtest.T) {
dirPath := gdebug.TestDataPath("view2")
dirPath := gtest.DataPath("view2")
Config().GetAdapter().(*gcfg.AdapterFile).SetContent(gfile.GetContents(gfile.Join(dirPath, "config.toml")))
defer Config().GetAdapter().(*gcfg.AdapterFile).ClearContent()
defer localInstances.Clear()
@ -119,7 +118,7 @@ func Test_View_Config(t *testing.T) {
})
// view2
gtest.C(t, func(t *gtest.T) {
dirPath := gdebug.TestDataPath("view2")
dirPath := gtest.DataPath("view2")
Config().GetAdapter().(*gcfg.AdapterFile).SetContent(gfile.GetContents(gfile.Join(dirPath, "config.toml")))
defer Config().GetAdapter().(*gcfg.AdapterFile).ClearContent()
defer localInstances.Clear()

View File

@ -0,0 +1,4 @@
server:
address: ":8000"
tempByInstanceName:
address: ":8003"

3
go.mod
View File

@ -9,8 +9,9 @@ require (
github.com/fsnotify/fsnotify v1.5.1
github.com/go-redis/redis/v8 v8.11.4
github.com/go-sql-driver/mysql v1.6.0
github.com/gorilla/websocket v1.4.2
github.com/gorilla/websocket v1.5.0
github.com/grokify/html-strip-tags-go v0.0.1
github.com/longbridgeapp/sqlparser v0.3.1
github.com/olekukonko/tablewriter v0.0.5
go.opentelemetry.io/otel v1.0.0
go.opentelemetry.io/otel/sdk v1.0.0

8
go.sum
View File

@ -20,6 +20,8 @@ github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Px
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@ -36,11 +38,13 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0=
github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/longbridgeapp/sqlparser v0.3.1 h1:iWOZWGIFgQrJRgobLXUNJdvqGRpbVXkyKUKUA5CNJBE=
github.com/longbridgeapp/sqlparser v0.3.1/go.mod h1:GIHaUq8zvYyHLCLMJJykx1CdM6LHtkUih/QaJXySSx4=
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=

View File

@ -24,7 +24,7 @@ import (
func Test_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
i18n := gi18n.New(gi18n.Options{
Path: gdebug.TestDataPath("i18n"),
Path: gtest.DataPath("i18n"),
})
i18n.SetLanguage("none")
t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "{#hello}{#world}")
@ -41,7 +41,7 @@ func Test_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
i18n := gi18n.New(gi18n.Options{
Path: gdebug.TestDataPath("i18n-file"),
Path: gtest.DataPath("i18n-file"),
})
i18n.SetLanguage("none")
t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "{#hello}{#world}")
@ -72,7 +72,7 @@ func Test_TranslateFormat(t *testing.T) {
// Tf
gtest.C(t, func(t *gtest.T) {
i18n := gi18n.New(gi18n.Options{
Path: gdebug.TestDataPath("i18n"),
Path: gtest.DataPath("i18n"),
})
i18n.SetLanguage("none")
t.Assert(i18n.Tf(context.Background(), "{#hello}{#world} %d", 2020), "{#hello}{#world} 2020")
@ -84,7 +84,7 @@ func Test_TranslateFormat(t *testing.T) {
func Test_DefaultManager(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
err := gi18n.SetPath(gdebug.TestDataPath("i18n"))
err := gi18n.SetPath(gtest.DataPath("i18n"))
t.AssertNil(err)
gi18n.SetLanguage("none")

View File

@ -15,7 +15,6 @@ import (
"testing"
"time"
"github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/gclient"
@ -337,7 +336,7 @@ func Test_Client_File_And_Param(t *testing.T) {
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
path := gdebug.TestDataPath("upload", "file1.txt")
path := gtest.DataPath("upload", "file1.txt")
data := g.Map{
"file": "@file:" + path,
"json": `{"uuid": "luijquiopm", "isRelative": false, "fileName": "test111.xls"}`,

View File

@ -12,11 +12,11 @@ import (
"reflect"
"time"
"github.com/gogf/gf/v2/net/gsvc"
"github.com/gorilla/websocket"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/container/gtype"
"github.com/gogf/gf/v2/net/gsvc"
"github.com/gogf/gf/v2/os/gcache"
"github.com/gogf/gf/v2/os/gsession"
"github.com/gogf/gf/v2/protocol/goai"
@ -31,7 +31,7 @@ type (
servers []*gracefulServer // Underlying http.Server array.
serverCount *gtype.Int // Underlying http.Server count.
closeChan chan struct{} // Used for underlying server closing event notification.
serveTree map[string]interface{} // The route map tree.
serveTree map[string]interface{} // The route maps tree.
serveCache *gcache.Cache // Server caches for internal usage.
routesMap map[string][]registeredRouteItem // Route map mainly for route dumps and repeated route checks.
statusHandlerMap map[string][]HandlerFunc // Custom status handler map.
@ -70,8 +70,8 @@ type (
// handlerFuncInfo contains the HandlerFunc address and its reflection type.
handlerFuncInfo struct {
Func HandlerFunc // Handler function address.
Type reflect.Type // Reflect type information for current handler, which is used for extension of handler feature.
Value reflect.Value // Reflect value information for current handler, which is used for extension of handler feature.
Type reflect.Type // Reflect type information for current handler, which is used for extensions of the handler feature.
Value reflect.Value // Reflect value information for current handler, which is used for extensions of the handler feature.
}
// handlerItem is the registered handler for route handling,
@ -84,7 +84,7 @@ type (
InitFunc HandlerFunc // Initialization function when request enters the object (only available for object register type).
ShutFunc HandlerFunc // Shutdown function when request leaves out the object (only available for object register type).
Middleware []HandlerFunc // Bound middleware array.
HookName string // Hook type name, only available for hook type.
HookName string // Hook type name, only available for the hook type.
Router *Router // Router object.
Source string // Registering source file `path:line`.
}
@ -141,15 +141,15 @@ const (
)
var (
// methodsMap stores all supported HTTP method,
// it is used for quick HTTP method searching using map.
// methodsMap stores all supported HTTP method.
// It is used for quick HTTP method searching using map.
methodsMap = make(map[string]struct{})
// serverMapping stores more than one server instances for current process.
// serverMapping stores more than one server instances for current processes.
// The key is the name of the server, and the value is its instance.
serverMapping = gmap.NewStrAnyMap(true)
// serverRunning marks the running server count.
// serverRunning marks the running server counts.
// If there is no successful server running or all servers' shutdown, this value is 0.
serverRunning = gtype.NewInt()
@ -160,7 +160,7 @@ var (
return true
},
}
// allDoneChan is the event for all server have done its serving and exit.
// allDoneChan is the event for all servers have done its serving and exit.
// It is used for process blocking purpose.
allDoneChan = make(chan struct{}, 1000)
@ -168,9 +168,9 @@ var (
// The process can only be initialized once.
serverProcessInitialized = gtype.NewBool()
// gracefulEnabled is used for graceful reload feature, which is false in default.
// gracefulEnabled is used for a graceful reload feature, which is false in default.
gracefulEnabled = false
// defaultValueTags is the struct tag names for default value storing.
// defaultValueTags are the struct tag names for default value storing.
defaultValueTags = []string{"d", "default"}
)

View File

@ -15,7 +15,7 @@ import (
// BuildParams builds the request string for the http client. The `params` can be type of:
// string/[]byte/map/struct/*struct.
//
// The optional parameter `noUrlEncode` specifies whether ignore the url encoding for the data.
// The optional parameter `noUrlEncode` specifies whether to ignore the url encoding for the data.
func BuildParams(params interface{}, noUrlEncode ...bool) (encodedParamStr string) {
return httputil.BuildParams(params, noUrlEncode...)
}

View File

@ -14,6 +14,7 @@ import (
"github.com/gogf/gf/v2/internal/intlog"
)
// DefaultHandlerResponse is the default implementation of HandlerResponse.
type DefaultHandlerResponse struct {
Code int `json:"code" dc:"Error code"`
Message string `json:"message" dc:"Error message"`

View File

@ -11,7 +11,6 @@ import (
"fmt"
"io/ioutil"
"github.com/gogf/gf/v2/os/gctx"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
@ -22,6 +21,7 @@ import (
"github.com/gogf/gf/v2/internal/httputil"
"github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/net/gtrace"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)
@ -73,7 +73,7 @@ func internalMiddlewareServerTracing(r *Request) {
// Inject tracing context.
r.SetCtx(ctx)
// If it is now using default trace provider, it then does no complex tracing jobs.
// If it is now using a default trace provider, it then does no complex tracing jobs.
if gtrace.IsUsingDefaultProvider() {
r.Middleware.Next()
return

View File

@ -32,7 +32,7 @@ type Request struct {
Response *Response // Corresponding Response of this request.
Router *Router // Matched Router for this request. Note that it's not available in HOOK handler.
EnterTime int64 // Request starting time in microseconds.
LeaveTime int64 // Request ending time in microseconds.
LeaveTime int64 // Request to end time in microseconds.
Middleware *middleware // Middleware manager.
StaticFile *staticFile // Static file object for static file serving.
@ -49,10 +49,10 @@ type Request struct {
parsedBody bool // A bool marking whether the request body parsed.
parsedForm bool // A bool marking whether request Form parsed for HTTP method PUT, POST, PATCH.
paramsMap map[string]interface{} // Custom parameters map.
routerMap map[string]string // Router parameters map, which might be nil if there're no router parameters.
routerMap map[string]string // Router parameters map, which might be nil if there are no router parameters.
queryMap map[string]interface{} // Query parameters map, which is nil if there's no query string.
formMap map[string]interface{} // Form parameters map, which is nil if there's no form data from client.
bodyMap map[string]interface{} // Body parameters map, which might be nil if there're no body content.
formMap map[string]interface{} // Form parameters map, which is nil if there's no form of data from the client.
bodyMap map[string]interface{} // Body parameters map, which might be nil if their nobody content.
error error // Current executing error of the request.
exitAll bool // A bool marking whether current request is exited.
parsedHost string // The parsed host name for current host used by GetHost function.

View File

@ -14,7 +14,7 @@ import (
"github.com/gogf/gf/v2/encoding/gbase64"
)
// BasicAuth enables the http basic authentication feature with given passport and password
// BasicAuth enables the http basic authentication feature with a given passport and password
// and asks client for authentication. It returns true if authentication success, else returns
// false if failure.
func (r *Request) BasicAuth(user, pass string, tips ...string) bool {

View File

@ -29,7 +29,7 @@ func (m *middleware) Next() {
var item *handlerParsedItem
var loop = true
for loop {
// Check whether the request is exited.
// Check whether the request is excited.
if m.request.IsExited() || m.handlerIndex >= len(m.request.handlers) {
break
}
@ -112,7 +112,7 @@ func (m *middleware) Next() {
loop = false
})
}
// Check the http status code after all handler and middleware done.
// Check the http status code after all handlers and middleware done.
if m.request.IsExited() || m.handlerIndex >= len(m.request.handlers) {
if m.request.Response.Status == 0 {
if m.request.Middleware.served {
@ -133,9 +133,7 @@ func (m *middleware) callHandlerFunc(funcInfo handlerFuncInfo) {
reflect.ValueOf(m.request.Context()),
}
if funcInfo.Type.NumIn() == 2 {
var (
inputObject reflect.Value
)
var inputObject reflect.Value
if funcInfo.Type.In(1).Kind() == reflect.Ptr {
inputObject = reflect.New(funcInfo.Type.In(1).Elem())
m.request.error = m.request.Parse(inputObject.Interface())

View File

@ -320,7 +320,7 @@ func (r *Request) parseForm() {
}
}
// GetMultipartForm parses and returns the form as multipart form.
// GetMultipartForm parses and returns the form as multipart forms.
func (r *Request) GetMultipartForm() *multipart.Form {
r.parseForm()
return r.MultipartForm

View File

@ -48,7 +48,7 @@ func (r *Request) SetCtx(ctx context.Context) {
r.context = ctx
}
// GetCtxVar retrieves and returns a Var with given key name.
// GetCtxVar retrieves and returns a Var with a given key name.
// The optional parameter `def` specifies the default value of the Var if given `key`
// does not exist in the context.
func (r *Request) GetCtxVar(key interface{}, def ...interface{}) *gvar.Var {
@ -59,7 +59,7 @@ func (r *Request) GetCtxVar(key interface{}, def ...interface{}) *gvar.Var {
return gvar.New(value)
}
// SetCtxVar sets custom parameter to context with key-value pair.
// SetCtxVar sets custom parameter to context with key-value pairs.
func (r *Request) SetCtxVar(key interface{}, value interface{}) {
r.context = context.WithValue(r.Context(), key, value)
}

View File

@ -27,7 +27,7 @@ type UploadFile struct {
ctx context.Context
}
// UploadFiles is array type for *UploadFile.
// UploadFiles is an array type of *UploadFile.
type UploadFiles []*UploadFile
// Save saves the single uploading file to directory path and returns the saved file name.

View File

@ -11,7 +11,7 @@ import (
"github.com/gogf/gf/v2/util/gconv"
)
// SetForm sets custom form value with key-value pair.
// SetForm sets custom form value with key-value pairs.
func (r *Request) SetForm(key string, value interface{}) {
r.parseForm()
if r.formMap == nil {

View File

@ -15,7 +15,7 @@ import (
)
// GetPage creates and returns the pagination object for given `totalSize` and `pageSize`.
// NOTE THAT the page parameter name from client is constantly defined as gpage.DefaultPageName
// NOTE THAT the page parameter name from clients is constantly defined as gpage.DefaultPageName
// for simplification and convenience.
func (r *Request) GetPage(totalSize, pageSize int) *gpage.Page {
// It must have Router object attribute.

View File

@ -8,7 +8,7 @@ package ghttp
import "github.com/gogf/gf/v2/container/gvar"
// SetParam sets custom parameter with key-value pair.
// SetParam sets custom parameter with key-value pairs.
func (r *Request) SetParam(key string, value interface{}) {
if r.paramsMap == nil {
r.paramsMap = make(map[string]interface{})
@ -16,7 +16,7 @@ func (r *Request) SetParam(key string, value interface{}) {
r.paramsMap[key] = value
}
// SetParamMap sets custom parameter with key-value pair map.
// SetParamMap sets custom parameter with key-value pair maps.
func (r *Request) SetParamMap(data map[string]interface{}) {
if r.paramsMap == nil {
r.paramsMap = make(map[string]interface{})
@ -26,7 +26,7 @@ func (r *Request) SetParamMap(data map[string]interface{}) {
}
}
// GetParam returns custom parameter with given name `key`.
// GetParam returns custom parameter with a given name `key`.
// It returns `def` if `key` does not exist.
// It returns nil if `def` is not passed.
func (r *Request) GetParam(key string, def ...interface{}) *gvar.Var {

View File

@ -11,7 +11,7 @@ import (
"github.com/gogf/gf/v2/util/gconv"
)
// SetQuery sets custom query value with key-value pair.
// SetQuery sets custom query value with key-value pairs.
func (r *Request) SetQuery(key string, value interface{}) {
r.parseQuery()
if r.queryMap == nil {
@ -20,7 +20,7 @@ func (r *Request) SetQuery(key string, value interface{}) {
r.queryMap[key] = value
}
// GetQuery retrieves and returns parameter with given name `key` from query string
// GetQuery retrieves and return parameter with the given name `key` from query string
// and request body. It returns `def` if `key` does not exist in the query and `def` is given,
// or else it returns nil.
//
@ -47,8 +47,8 @@ func (r *Request) GetQuery(key string, def ...interface{}) *gvar.Var {
return nil
}
// GetQueryMap retrieves and returns all parameters passed from client using HTTP GET method
// as map. The parameter `kvMap` specifies the keys retrieving from client parameters,
// GetQueryMap retrieves and returns all parameters passed from the client using HTTP GET method
// as the map. The parameter `kvMap` specifies the keys retrieving from client parameters,
// the associated values are the default values if the client does not pass.
//
// Note that if there are multiple parameters with the same name, the parameters are retrieved and overwrote
@ -94,8 +94,8 @@ func (r *Request) GetQueryMap(kvMap ...map[string]interface{}) map[string]interf
return m
}
// GetQueryMapStrStr retrieves and returns all parameters passed from client using HTTP GET method
// as map[string]string. The parameter `kvMap` specifies the keys
// GetQueryMapStrStr retrieves and returns all parameters passed from the client using the HTTP GET method as a
// map[string]string. The parameter `kvMap` specifies the keys
// retrieving from client parameters, the associated values are the default values if the client
// does not pass.
func (r *Request) GetQueryMapStrStr(kvMap ...map[string]interface{}) map[string]string {
@ -110,7 +110,7 @@ func (r *Request) GetQueryMapStrStr(kvMap ...map[string]interface{}) map[string]
return nil
}
// GetQueryMapStrVar retrieves and returns all parameters passed from client using HTTP GET method
// GetQueryMapStrVar retrieves and returns all parameters passed from the client using the HTTP GET method
// as map[string]*gvar.Var. The parameter `kvMap` specifies the keys
// retrieving from client parameters, the associated values are the default values if the client
// does not pass.
@ -126,8 +126,8 @@ func (r *Request) GetQueryMapStrVar(kvMap ...map[string]interface{}) map[string]
return nil
}
// GetQueryStruct retrieves all parameters passed from client using HTTP GET method
// and converts them to given struct object. Note that the parameter `pointer` is a pointer
// GetQueryStruct retrieves all parameters passed from the client using the HTTP GET method
// and converts them to a given struct object. Note that the parameter `pointer` is a pointer
// to the struct object. The optional parameter `mapping` is used to specify the key to
// attribute mapping.
func (r *Request) GetQueryStruct(pointer interface{}, mapping ...map[string]string) error {

View File

@ -14,7 +14,7 @@ import (
"github.com/gogf/gf/v2/util/gutil"
)
// GetRequest retrieves and returns the parameter named `key` passed from client and
// GetRequest retrieves and returns the parameter named `key` passed from the client and
// custom params as interface{}, no matter what HTTP method the client is using. The
// parameter `def` specifies the default value if the `key` does not exist.
//
@ -50,8 +50,8 @@ func (r *Request) GetRequest(key string, def ...interface{}) *gvar.Var {
return nil
}
// GetRequestMap retrieves and returns all parameters passed from client and custom params
// as map, no matter what HTTP method the client is using. The parameter `kvMap` specifies
// GetRequestMap retrieves and returns all parameters passed from the client and custom params
// as the map, no matter what HTTP method the client is using. The parameter `kvMap` specifies
// the keys retrieving from client parameters, the associated values are the default values
// if the client does not pass the according keys.
//
@ -131,7 +131,7 @@ func (r *Request) GetRequestMap(kvMap ...map[string]interface{}) map[string]inte
return m
}
// GetRequestMapStrStr retrieves and returns all parameters passed from client and custom
// GetRequestMapStrStr retrieve and returns all parameters passed from the client and custom
// params as map[string]string, no matter what HTTP method the client is using. The parameter
// `kvMap` specifies the keys retrieving from client parameters, the associated values are the
// default values if the client does not pass.
@ -147,7 +147,7 @@ func (r *Request) GetRequestMapStrStr(kvMap ...map[string]interface{}) map[strin
return nil
}
// GetRequestMapStrVar retrieves and returns all parameters passed from client and custom
// GetRequestMapStrVar retrieve and returns all parameters passed from the client and custom
// params as map[string]*gvar.Var, no matter what HTTP method the client is using. The parameter
// `kvMap` specifies the keys retrieving from client parameters, the associated values are the
// default values if the client does not pass.
@ -163,8 +163,8 @@ func (r *Request) GetRequestMapStrVar(kvMap ...map[string]interface{}) map[strin
return nil
}
// GetRequestStruct retrieves all parameters passed from client and custom params no matter
// what HTTP method the client is using, and converts them to given struct object. Note that
// GetRequestStruct retrieves all parameters passed from the client and custom params no matter
// what HTTP method the client is using, and converts them to give the struct object. Note that
// the parameter `pointer` is a pointer to the struct object.
// The optional parameter `mapping` is used to specify the key to attribute mapping.
func (r *Request) GetRequestStruct(pointer interface{}, mapping ...map[string]string) error {

View File

@ -8,7 +8,7 @@ package ghttp
import "github.com/gogf/gf/v2/container/gvar"
// GetRouterMap retrieves and returns a copy of router map.
// GetRouterMap retrieves and returns a copy of the router map.
func (r *Request) GetRouterMap() map[string]string {
if r.routerMap != nil {
m := make(map[string]string, len(r.routerMap))

View File

@ -96,7 +96,7 @@ func (r *Response) ServeFileDownload(path string, name ...string) {
r.Server.serveFile(r.Request, serveFile)
}
// RedirectTo redirects client to another location.
// RedirectTo redirects the client to another location.
// The optional parameter `code` specifies the http status code for redirecting,
// which commonly can be 301 or 302. It's 302 in default.
func (r *Response) RedirectTo(location string, code ...int) {
@ -109,7 +109,7 @@ func (r *Response) RedirectTo(location string, code ...int) {
r.Request.Exit()
}
// RedirectBack redirects client back to referer.
// RedirectBack redirects the client back to referer.
// The optional parameter `code` specifies the http status code for redirecting,
// which commonly can be 301 or 302. It's 302 in default.
func (r *Response) RedirectBack(code ...int) {

View File

@ -31,7 +31,7 @@ type CORSOptions struct {
var (
// defaultAllowHeaders is the default allowed headers for CORS.
// It's defined another map for better header key searching performance.
// It defined another map for better header key searching performance.
defaultAllowHeaders = "Origin,Content-Type,Accept,User-Agent,Cookie,Authorization,X-Auth-Token,X-Requested-With"
defaultAllowHeadersMap = make(map[string]struct{})
)

View File

@ -93,7 +93,7 @@ func (r *Response) buildInVars(params ...map[string]interface{}) map[string]inte
"Cookie": r.Request.Cookie.Map(),
"Session": sessionMap,
})
// Note that it should assign no Config variable to template
// Note that it should assign no Config variable to a template
// if there's no configuration file.
if v, _ := gcfg.Instance().Data(r.Request.Context()); len(v) > 0 {
m["Config"] = v

View File

@ -37,7 +37,7 @@ func (r *Response) Write(content ...interface{}) {
}
// WriteExit writes `content` to the response buffer and exits executing of current handler.
// The "Exit" feature is commonly used to replace usage of return statement in the handler,
// The "Exit" feature is commonly used to replace usage of return statements in the handler,
// for convenience.
func (r *Response) WriteExit(content ...interface{}) {
r.Write(content...)
@ -52,7 +52,7 @@ func (r *Response) WriteOver(content ...interface{}) {
// WriteOverExit overwrites the response buffer with `content` and exits executing
// of current handler. The "Exit" feature is commonly used to replace usage of return
// statement in the handler, for convenience.
// statements in the handler, for convenience.
func (r *Response) WriteOverExit(content ...interface{}) {
r.WriteOver(content...)
r.Request.Exit()
@ -64,7 +64,7 @@ func (r *Response) Writef(format string, params ...interface{}) {
}
// WritefExit writes the response with fmt.Sprintf and exits executing of current handler.
// The "Exit" feature is commonly used to replace usage of return statement in the handler,
// The "Exit" feature is commonly used to replace usage of return statements in the handler,
// for convenience.
func (r *Response) WritefExit(format string, params ...interface{}) {
r.Writef(format, params...)
@ -82,7 +82,7 @@ func (r *Response) Writeln(content ...interface{}) {
// WritelnExit writes the response with `content` and new line and exits executing
// of current handler. The "Exit" feature is commonly used to replace usage of return
// statement in the handler, for convenience.
// statements in the handler, for convenience.
func (r *Response) WritelnExit(content ...interface{}) {
r.Writeln(content...)
r.Request.Exit()
@ -104,7 +104,7 @@ func (r *Response) WriteflnExit(format string, params ...interface{}) {
// WriteJson writes `content` to the response with JSON format.
func (r *Response) WriteJson(content interface{}) error {
r.Header().Set("Content-Type", contentTypeJson)
// If given string/[]byte, response it directly to client.
// If given string/[]byte, response it directly to the client.
switch content.(type) {
case string, []byte:
r.Write(gconv.String(content))
@ -121,7 +121,7 @@ func (r *Response) WriteJson(content interface{}) error {
// WriteJsonExit writes `content` to the response with JSON format and exits executing
// of current handler if success. The "Exit" feature is commonly used to replace usage of
// return statement in the handler, for convenience.
// return statements in the handler, for convenience.
func (r *Response) WriteJsonExit(content interface{}) error {
if err := r.WriteJson(content); err != nil {
return err
@ -161,7 +161,7 @@ func (r *Response) WriteJsonP(content interface{}) error {
// WriteJsonPExit writes `content` to the response with JSONP format and exits executing
// of current handler if success. The "Exit" feature is commonly used to replace usage of
// return statement in the handler, for convenience.
// return statements in the handler, for convenience.
//
// Note that there should be a "callback" parameter in the request for JSONP format.
func (r *Response) WriteJsonPExit(content interface{}) error {
@ -175,7 +175,7 @@ func (r *Response) WriteJsonPExit(content interface{}) error {
// WriteXml writes `content` to the response with XML format.
func (r *Response) WriteXml(content interface{}, rootTag ...string) error {
r.Header().Set("Content-Type", contentTypeXml)
// If given string/[]byte, response it directly to client.
// If given string/[]byte, response it directly to clients.
switch content.(type) {
case string, []byte:
r.Write(gconv.String(content))
@ -191,7 +191,7 @@ func (r *Response) WriteXml(content interface{}, rootTag ...string) error {
// WriteXmlExit writes `content` to the response with XML format and exits executing
// of current handler if success. The "Exit" feature is commonly used to replace usage
// of return statement in the handler, for convenience.
// of return statements in the handler, for convenience.
func (r *Response) WriteXmlExit(content interface{}, rootTag ...string) error {
if err := r.WriteXml(content, rootTag...); err != nil {
return err
@ -201,7 +201,7 @@ func (r *Response) WriteXmlExit(content interface{}, rootTag ...string) error {
}
// WriteStatus writes HTTP `status` and `content` to the response.
// Note that do not set Content-Type header here.
// Note that it does not set a Content-Type header here.
func (r *Response) WriteStatus(status int, content ...interface{}) {
r.WriteHeader(status)
if len(content) > 0 {
@ -213,7 +213,7 @@ func (r *Response) WriteStatus(status int, content ...interface{}) {
// WriteStatusExit writes HTTP `status` and `content` to the response and exits executing
// of current handler if success. The "Exit" feature is commonly used to replace usage of
// return statement in the handler, for convenience.
// return statements in the handler, for convenience.
func (r *Response) WriteStatusExit(status int, content ...interface{}) {
r.WriteStatus(status, content...)
r.Request.Exit()

View File

@ -50,7 +50,7 @@ func (w *ResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return w.writer.(http.Hijacker).Hijack()
}
// Flush outputs the buffer to client and clears the buffer.
// Flush outputs the buffer to clients and clears the buffer.
func (w *ResponseWriter) Flush() {
if w.hijacked {
return

View File

@ -16,7 +16,6 @@ import (
"strings"
"time"
"github.com/gogf/gf/v2/net/ghttp/internal/swaggerui"
"github.com/olekukonko/tablewriter"
"github.com/gogf/gf/v2/container/garray"
@ -25,6 +24,7 @@ import (
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/intlog"
"github.com/gogf/gf/v2/net/ghttp/internal/swaggerui"
"github.com/gogf/gf/v2/os/gcache"
"github.com/gogf/gf/v2/os/genv"
"github.com/gogf/gf/v2/os/gfile"
@ -39,7 +39,7 @@ import (
)
func init() {
// Initialize the methods map.
// Initialize the method map.
for _, v := range strings.Split(supportedHttpMethods, ",") {
methodsMap[v] = struct{}{}
}
@ -51,7 +51,7 @@ func serverProcessInit() {
if !serverProcessInitialized.Cas(false, true) {
return
}
// This means it is a restart server, it should kill its parent before starting its listening,
// This means it is a restart server. It should kill its parent before starting its listening,
// to avoid duplicated port listening in two processes.
if !genv.Get(adminActionRestartEnvKey).IsEmpty() {
if p, err := os.FindProcess(gproc.PPid()); err == nil {
@ -70,7 +70,7 @@ func serverProcessInit() {
go handleProcessSignal()
// Process message handler.
// It's enabled only graceful feature is enabled.
// It enabled only a graceful feature is enabled.
if gracefulEnabled {
intlog.Printf(ctx, "%d: graceful reload feature is enabled", gproc.Pid())
go handleProcessMessage()
@ -80,7 +80,7 @@ func serverProcessInit() {
// It's an ugly calling for better initializing the main package path
// in source development environment. It is useful only be used in main goroutine.
// It fails retrieving the main package path in asynchronous goroutines.
// It fails to retrieve the main package path in asynchronous goroutines.
gfile.MainPkgPath()
}
@ -213,7 +213,7 @@ func (s *Server) Start() error {
// Check the group routes again.
s.handlePreBindItems(ctx)
// If there's no route registered and no static service enabled,
// If there's no route registered and no static service enabled,
// it then returns an error of invalid usage of server.
if len(s.routesMap) == 0 && !s.config.FileServerEnabled {
return gerror.NewCode(
@ -525,8 +525,8 @@ func (s *Server) startServer(fdMap listenerFdMap) {
)
if len(addrAndFd) > 1 {
itemFunc = addrAndFd[0]
// The Windows OS does not support socket file descriptor passing
// from parent process.
// The Window OS does not support socket file descriptor passing
// from the parent process.
if runtime.GOOS != "windows" {
fd = gconv.Int(addrAndFd[1])
}

View File

@ -196,7 +196,7 @@ func bufferToServerFdMap(buffer []byte) map[string]listenerFdMap {
sfm := make(map[string]listenerFdMap)
if len(buffer) > 0 {
j, _ := gjson.LoadContent(buffer)
for k, _ := range j.Var().Map() {
for k := range j.Var().Map() {
m := make(map[string]string)
for k, v := range j.Get(k).Map() {
m[k] = gconv.String(v)

View File

@ -18,10 +18,10 @@ import (
"github.com/gogf/gf/v2/internal/intlog"
)
// procSignalChan is the channel for listening the signal.
// procSignalChan is the channel for listening to the signal.
var procSignalChan = make(chan os.Signal)
// handleProcessSignal handles all signal from system.
// handleProcessSignal handles all signals from system.
func handleProcessSignal() {
var (
ctx = context.TODO()

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