mirror of
https://gitee.com/johng/gf
synced 2026-06-08 02:27:42 +08:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 87609a3424 | |||
| b4184e4523 | |||
| 05508e4fcb | |||
| 372bae4799 | |||
| 21f48d3750 | |||
| 126a81d89a | |||
| 707dc6b346 | |||
| c1c86c026f | |||
| 5c4982cb0c | |||
| fed38ea7ab | |||
| 4d6ef1c52d | |||
| c6aba6da4d | |||
| ec92d2b7f4 | |||
| 6810e71220 | |||
| f4192d695c | |||
| 6664437b06 | |||
| 96a135834a | |||
| 09ba1bf1fb | |||
| cc01629b57 | |||
| 2d586859c3 | |||
| a5e20e4939 | |||
| 0e3f4f45e0 | |||
| 045c3e132f | |||
| 80c068ae05 | |||
| 6574b8cbfe | |||
| 20c48b1712 | |||
| ee16b6df88 | |||
| 439350836e | |||
| 5ee387672b | |||
| f670c24e2c | |||
| f2e1f63396 | |||
| 6dacdd60dc | |||
| 87ccc27ee4 | |||
| 950695664c | |||
| d1f76f3834 | |||
| 66e6a05e5f | |||
| 0f430c66ae | |||
| 8357b0f649 | |||
| 7fc75bfeff | |||
| d7764e2968 | |||
| f865d6fa6a | |||
| e6bbead4e6 | |||
| 5f3a525d11 | |||
| c5d80a2192 | |||
| 97b8f0f781 | |||
| bceb5fc7de | |||
| b3e66d8023 | |||
| e06f831205 | |||
| 60340a7348 | |||
| dccfc1c8cd | |||
| d58186372f | |||
| d32246275a | |||
| bbab9f3934 | |||
| a4ab9c284f |
2
.github/workflows/gf.yml
vendored
2
.github/workflows/gf.yml
vendored
@ -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
1
.gitignore
vendored
@ -13,7 +13,6 @@ pkg/
|
||||
bin/
|
||||
cbuild
|
||||
**/.DS_Store
|
||||
.vscode/
|
||||
.test/
|
||||
main
|
||||
gf
|
||||
@ -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
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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=
|
||||
|
||||
@ -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_
|
||||
`
|
||||
|
||||
|
||||
@ -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
@ -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)
|
||||
|
||||
@ -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(",") + "]"
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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(",") + "]"
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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(",") + "]"
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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(",") + "]"
|
||||
}
|
||||
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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 := ""
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 := ""
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
46
database/gdb/gdb_core_ctx.go
Normal file
46
database/gdb/gdb_core_ctx.go
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package 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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
245
database/gdb/gdb_core_sharding.go
Normal file
245
database/gdb/gdb_core_sharding.go
Normal 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())
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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)),
|
||||
)
|
||||
|
||||
@ -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 + ")"
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
159
database/gdb/gdb_model_hook.go
Normal file
159
database/gdb/gdb_model_hook.go
Normal 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
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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 {
|
||||
|
||||
136
database/gdb/gdb_z_mysql_feature_hook_test.go
Normal file
136
database/gdb/gdb_z_mysql_feature_hook_test.go
Normal 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`)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -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)
|
||||
|
||||
138
database/gdb/gdb_z_mysql_feature_sharding_test.go
Normal file
138
database/gdb/gdb_z_mysql_feature_sharding_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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, "")
|
||||
|
||||
@ -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 ""
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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")
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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())
|
||||
}
|
||||
|
||||
@ -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"))
|
||||
|
||||
@ -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=
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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"),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
35
frame/gins/gins_z_unit_server_test.go
Normal file
35
frame/gins/gins_z_unit_server_test.go
Normal 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`)
|
||||
// })
|
||||
//}
|
||||
@ -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()
|
||||
|
||||
4
frame/gins/testdata/server/config.yaml
vendored
Normal file
4
frame/gins/testdata/server/config.yaml
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
server:
|
||||
address: ":8000"
|
||||
tempByInstanceName:
|
||||
address: ":8003"
|
||||
3
go.mod
3
go.mod
@ -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
8
go.sum
@ -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=
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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"}`,
|
||||
|
||||
@ -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"}
|
||||
)
|
||||
|
||||
@ -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...)
|
||||
}
|
||||
|
||||
@ -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"`
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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{})
|
||||
)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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])
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
Reference in New Issue
Block a user