diff --git a/TODO b/TODO index 16078e2d7..5d8b505b1 100644 --- a/TODO +++ b/TODO @@ -11,7 +11,6 @@ Cookie&Session数据池化处理; ghttp.Client增加proxy特性; gtime增加对时区转换的封装,并简化失去转换时对类似+80500时区的支持; 改进gf-orm的where查询功能,参考thinkphp 里的where查询语法; -ORM增加获取被执行的sql语句的方法; gpage分页增加对自定义后缀的支持,如:2.html, 2.php等等; DONE: @@ -42,6 +41,6 @@ DONE: 25. gredis增加redis密码支持; 26. 改进ghttp.Server平滑重启机制,当新进程接管服务后,再使用进程间通信方式通知父进程销毁; 27. gproc进程间通信增加分组特性,不同的进程间可以通过进程ID以及分组名称发送/获取进程消息; - +28. ORM增加获取被执行的sql语句的方法; diff --git a/g/container/gring/gring.go b/g/container/gring/gring.go new file mode 100644 index 000000000..f54adde70 --- /dev/null +++ b/g/container/gring/gring.go @@ -0,0 +1,233 @@ +// Copyright 2018 gf Author(https://gitee.com/johng/gf). 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://gitee.com/johng/gf. + +// 并发安全的环. +package gring + +import ( + "container/ring" + "sync" + "gitee.com/johng/gf/g/container/gtype" +) + +type Ring struct { + mu sync.RWMutex // 互斥锁 + ring *ring.Ring // 底层环形数据结构 + len *gtype.Int // 数据大小(已使用的大小) + cap *gtype.Int // 总长度(分配的环大小,包括未使用的数据项数量) + dirty *gtype.Bool // 标记环是否脏了(需要重新计算大小,当环大小发生改变时做标记) +} + +func New(cap int) *Ring { + return &Ring { + ring : ring.New(cap), + len : gtype.NewInt(), + cap : gtype.NewInt(cap), + dirty : gtype.NewBool(), + } +} + +// 返回当前环指向的数据项值 +func (r *Ring) Val() interface{} { + r.mu.RLock() + v := r.ring.Value + r.mu.RUnlock() + return v +} + +// 返回当前环已有数据项大小 +func (r *Ring) Len() int { + r.checkAndUpdateLenAndCap() + return r.len.Val() +} + +// 返回当前环总大小(包含未使用长度) +func (r *Ring) Cap() int { + r.checkAndUpdateLenAndCap() + return r.cap.Val() +} + +// 检测并执行len和cap的更新(两者必须一起更新) +func (r *Ring) checkAndUpdateLenAndCap() { + if !r.dirty.Val() { + return + } + totalLen := 0 + emptyLen := 0 + if r.ring != nil { + r.mu.RLock() + for p := r.ring.Next(); p != r.ring; p = p.Next() { + if p.Value == nil { + emptyLen++ + } + totalLen++ + } + r.mu.RUnlock() + } + r.cap.Set(totalLen) + r.len.Set(totalLen - emptyLen) + r.dirty.Set(false) +} + +// 当前位置设置数据项值 +func (r *Ring) Set(value interface{}) *Ring { + r.mu.Lock() + if r.ring.Value == nil { + r.len.Add(1) + } + r.ring.Value = value + r.mu.Unlock() + return r +} + +// Set & Next +func (r *Ring) Put(value interface{}) *Ring { + r.mu.Lock() + if r.ring.Value == nil { + r.len.Add(1) + } + r.ring.Value = value + r.ring = r.ring.Next() + r.mu.Unlock() + return r +} + +// 环往后(n > 0)或者往前(n < 0)移动n个元素 +func (r *Ring) Move(n int) *Ring { + r.mu.Lock() + r.ring = r.ring.Move(n) + r.mu.Unlock() + return r +} + +// 环往前移动1个元素 +func (r *Ring) Prev() *Ring { + r.mu.Lock() + r.ring = r.ring.Prev() + r.mu.Unlock() + return r +} + +// 环往后移动1个元素 +func (r *Ring) Next() *Ring { + r.mu.Lock() + r.ring = r.ring.Next() + r.mu.Unlock() + return r +} + +// 连接两个环,两个环的大小和位置都有可能会发生改变。 +// 1、链接将环r与环s连接,使得r.Next()成为s并返回r.Next()的原始值。r一定不能为空。 +// 2、如果r和s指向同一个环,则链接它们会从环中移除r和s之间的元素。 +// 删除的元素形成子环,结果是对该子环的引用(如果没有删除元素,结果仍然是r.Next()的原始值,而不是nil)。 +// 3、如果r和s指向不同的环,则链接它们会创建一个单独的环,并在r之后插入s的元素。 结果指向插入后s的最后一个元素后面的元素。 +func (r *Ring) Link(s *Ring) *Ring { + r.mu.Lock() + s.mu.Lock() + r.ring.Link(s.ring) + s.mu.Unlock() + r.mu.Unlock() + r.dirty.Set(true) + s.dirty.Set(true) + return r +} + +// 删除环中当前位置往后的n个数据项 +func (r *Ring) Unlink(n int) *Ring { + r.mu.Lock() + r.ring = r.ring.Unlink(n) + r.dirty.Set(true) + r.mu.Unlock() + return r +} + +// 往后只读遍历,回调函数返回true表示继续遍历,否则退出遍历 +func (r *Ring) RLockIteratorNext(f func(value interface{}) bool) { + r.mu.RLock() + if !f(r.ring.Value) { + return + } + for p := r.ring.Next(); p != r.ring; p = p.Next() { + if !f(p.Value) { + break + } + } + r.mu.RUnlock() +} + +// 往前只读遍历,回调函数返回true表示继续遍历,否则退出遍历 +func (r *Ring) RLockIteratorPrev(f func(value interface{}) bool) { + r.mu.RLock() + if !f(r.ring.Value) { + return + } + for p := r.ring.Prev(); p != r.ring; p = p.Prev() { + if !f(p.Value) { + break + } + } + r.mu.RUnlock() +} + +// 往后写遍历,回调函数返回true表示继续遍历,否则退出遍历 +func (r *Ring) LockIteratorNext(f func(item *ring.Ring) bool) { + r.mu.Lock() + if !f(r.ring) { + return + } + for p := r.ring.Next(); p != r.ring; p = p.Next() { + if !f(p) { + break + } + } + r.mu.Unlock() +} + +// 往前写遍历,回调函数返回true表示继续遍历,否则退出遍历 +func (r *Ring) LockIteratorPrev(f func(item *ring.Ring) bool) { + r.mu.Lock() + if !f(r.ring) { + return + } + for p := r.ring.Prev(); p != r.ring; p = p.Prev() { + if !f(p) { + break + } + } + r.mu.Unlock() +} + +// 从当前位置,往后只读完整遍历,返回非空数据项值构成的数组 +func (r *Ring) SliceNext() []interface{} { + s := make([]interface{}, 0) + r.mu.RLock() + if r.ring.Value != nil { + s = append(s, r.ring.Value) + } + for p := r.ring.Next(); p != r.ring; p = p.Next() { + if p.Value != nil { + s = append(s, p.Value) + } + } + r.mu.RUnlock() + return s +} + +// 从当前位置,往前只读完整遍历,返回非空数据项值构成的数组 +func (r *Ring) SlicePrev() []interface{} { + s := make([]interface{}, 0) + r.mu.RLock() + if r.ring.Value != nil { + s = append(s, r.ring.Value) + } + for p := r.ring.Prev(); p != r.ring; p = p.Prev() { + if p.Value != nil { + s = append(s, p.Value) + } + } + r.mu.RUnlock() + return s +} \ No newline at end of file diff --git a/g/container/gring/gring_test.go b/g/container/gring/gring_test.go new file mode 100644 index 000000000..8b0667002 --- /dev/null +++ b/g/container/gring/gring_test.go @@ -0,0 +1,46 @@ +// Copyright 2018 gf Author(https://gitee.com/johng/gf). 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://gitee.com/johng/gf. + +// go test *.go -bench=".*" + +package gring + +import ( + "testing" +) + +var length = 10000 +var r1 = New(length) + +func BenchmarkRing_Put(b *testing.B) { + for i := 0; i < b.N; i++ { + r1.Put(i) + } +} + +func BenchmarkRing_Next(b *testing.B) { + for i := 0; i < b.N; i++ { + r1.Next() + } +} + +func BenchmarkRing_Set(b *testing.B) { + for i := 0; i < b.N; i++ { + r1.Set(i) + } +} + +func BenchmarkRing_Len(b *testing.B) { + for i := 0; i < b.N; i++ { + r1.Len() + } +} + +func BenchmarkRing_Cap(b *testing.B) { + for i := 0; i < b.N; i++ { + r1.Cap() + } +} \ No newline at end of file diff --git a/g/database/gdb/gdb.go b/g/database/gdb/gdb.go index 50256a701..0be4d383e 100644 --- a/g/database/gdb/gdb.go +++ b/g/database/gdb/gdb.go @@ -14,6 +14,8 @@ import ( "gitee.com/johng/gf/g/util/grand" _ "github.com/lib/pq" _ "github.com/go-sql-driver/mysql" + "gitee.com/johng/gf/g/container/gtype" + "gitee.com/johng/gf/g/container/gring" ) const ( @@ -81,11 +83,21 @@ type Link interface { // 数据库链接对象 type Db struct { - link Link - master *sql.DB - slave *sql.DB - charl string - charr string + link Link // 底层数据库类型管理对象 + master *sql.DB // 实例化数据库链接(master) + slave *sql.DB // 实例化数据库链接(slave,可能会与master相同) + charl string // SQL安全符号(左) + charr string // SQL安全符号(右) + debug *gtype.Bool // (默认关闭)是否开启调试模式,当开启时会启用一些调试特性 + sqls *gring.Ring // (debug=true时有效)已执行的SQL列表 +} + +// 执行的SQL对象 +type Sql struct { + Sql string // SQL语句(可能带有预处理占位符) + Args []interface{} // 预处理参数值列表 + Error error // 执行结果(nil为成功) + Func string // 执行方法名称 } // 返回数据表记录值 @@ -208,6 +220,7 @@ func newDb (masterNode *ConfigNode, slaveNode *ConfigNode) (*Db, error) { slave : slave, charl : link.getQuoteCharLeft(), charr : link.getQuoteCharRight(), + debug : gtype.NewBool(), }, nil } diff --git a/g/database/gdb/gdb_base.go b/g/database/gdb/gdb_base.go index 305d3b6c0..137d617fe 100644 --- a/g/database/gdb/gdb_base.go +++ b/g/database/gdb/gdb_base.go @@ -15,8 +15,38 @@ import ( "database/sql" "gitee.com/johng/gf/g/util/gstr" "gitee.com/johng/gf/g/util/gconv" + "gitee.com/johng/gf/g/container/gring" ) +const ( + gDEFAULT_DEBUG_SQL_LENGTH = 1000 // 默认调试模式下记录的SQL条数 +) + +// 是否开启调试服务 +func (db *Db) SetDebug(debug bool) { + db.debug.Set(debug) + if debug && db.sqls == nil { + db.sqls = gring.New(gDEFAULT_DEBUG_SQL_LENGTH) + } +} + +// 获取已经执行的SQL列表 +func (db *Db) GetQueriedSqls() []*Sql { + if db.sqls == nil { + return nil + } + sqls := make([]*Sql, 0) + db.sqls.Prev() + db.sqls.RLockIteratorPrev(func(value interface{}) bool { + if value == nil { + return false + } + sqls = append(sqls, value.(*Sql)) + return true + }) + return sqls +} + // 关闭链接 func (db *Db) Close() error { if db.master != nil { @@ -40,9 +70,18 @@ func (db *Db) Close() error { func (db *Db) Query(query string, args ...interface{}) (*sql.Rows, error) { p := db.link.handleSqlBeforeExec(&query) rows, err := db.slave.Query(*p, args ...) - err = db.formatError(err, p, args...) + if db.debug.Val() { + db.sqls.Put(&Sql{ + Sql : *p, + Args : args, + Error : err, + Func : "DB:Query", + }) + } if err == nil { return rows, nil + } else { + err = db.formatError(err, p, args...) } return nil, err } @@ -51,8 +90,15 @@ func (db *Db) Query(query string, args ...interface{}) (*sql.Rows, error) { func (db *Db) Exec(query string, args ...interface{}) (sql.Result, error) { p := db.link.handleSqlBeforeExec(&query) r, err := db.master.Exec(*p, args ...) - err = db.formatError(err, p, args...) - return r, err + if db.debug.Val() { + db.sqls.Put(&Sql{ + Sql : *p, + Args : args, + Error : err, + Func : "DB:Exec", + }) + } + return r, db.formatError(err, p, args...) } // 格式化错误信息 diff --git a/g/database/gdb/gdb_transaction.go b/g/database/gdb/gdb_transaction.go index 9ded8f744..8ad0f3696 100644 --- a/g/database/gdb/gdb_transaction.go +++ b/g/database/gdb/gdb_transaction.go @@ -37,9 +37,18 @@ func (tx *Tx) Rollback() error { func (tx *Tx) Query(query string, args ...interface{}) (*sql.Rows, error) { p := tx.db.link.handleSqlBeforeExec(&query) rows, err := tx.tx.Query(*p, args ...) - err = tx.db.formatError(err, p, args...) + if tx.db.debug.Val() { + tx.db.sqls.Put(&Sql{ + Sql : *p, + Args : args, + Error : err, + Func : "TX:Query", + }) + } if err == nil { return rows, nil + } else { + err = tx.db.formatError(err, p, args...) } return nil, err } @@ -48,8 +57,15 @@ func (tx *Tx) Query(query string, args ...interface{}) (*sql.Rows, error) { func (tx *Tx) Exec(query string, args ...interface{}) (sql.Result, error) { p := tx.db.link.handleSqlBeforeExec(&query) r, err := tx.tx.Exec(*p, args ...) - err = tx.db.formatError(err, p, args...) - return r, err + if tx.db.debug.Val() { + tx.db.sqls.Put(&Sql{ + Sql : *p, + Args : args, + Error : err, + Func : "TX:Exec", + }) + } + return r, tx.db.formatError(err, p, args...) } // 数据库查询,获取查询结果集,以列表结构返回 diff --git a/g/database/gredis/gredis.go b/g/database/gredis/gredis.go index 43ddd2f9e..510f382a6 100644 --- a/g/database/gredis/gredis.go +++ b/g/database/gredis/gredis.go @@ -59,9 +59,13 @@ func New(config Config) *Redis { if err != nil { return nil, err } - c.Do("SELECT", config.Db) + if _, err := c.Do("SELECT", config.Db); err != nil { + return nil, err + } if len(config.Pass) > 0 { - c.Do("AUTH", config.Pass) + if _, err := c.Do("AUTH", config.Pass); err != nil { + return nil, err + } } return c, nil }, diff --git a/g/os/gtime/gtime.go b/g/os/gtime/gtime.go index a61da09c3..2b72b0827 100644 --- a/g/os/gtime/gtime.go +++ b/g/os/gtime/gtime.go @@ -16,7 +16,7 @@ import ( ) const ( - TIME_REAGEX_PATTERN = `(\d{4}-\d{2}-\d{2})[\sT]{0,1}(\d{2}:\d{2}:\d{2}){0,1}\.{0,1}(\d{0,9})([\sZ]{0,1})([\+-]{0,1})([:\d]*)` + TIME_REAGEX_PATTERN = `(\d{4}[-/]\d{2}[-/]\d{2})[\sT]{0,1}(\d{2}:\d{2}:\d{2}){0,1}\.{0,1}(\d{0,9})([\sZ]{0,1})([\+-]{0,1})([:\d]*)` ) var ( diff --git a/geg/container/gring/gring.go b/geg/container/gring/gring.go new file mode 100644 index 000000000..030e3bff7 --- /dev/null +++ b/geg/container/gring/gring.go @@ -0,0 +1,27 @@ +package main + +import ( + "gitee.com/johng/gf/g/container/gring" + "fmt" +) + +func main() { + r1 := gring.New(10) + for i := 0; i < 5; i ++ { + r1.Set(i).Next() + } + fmt.Println("Len:", r1.Len()) + fmt.Println("Cap:", r1.Cap()) + fmt.Println(r1.SlicePrev()) + fmt.Println(r1.SliceNext()) + + r2 := gring.New(10) + for i := 0; i < 10; i ++ { + r2.Set(i).Next() + } + fmt.Println("Len:", r2.Len()) + fmt.Println("Cap:", r2.Cap()) + fmt.Println(r2.SlicePrev()) + fmt.Println(r2.SliceNext()) + +} diff --git a/geg/database/mysql/gdb.go b/geg/database/mysql/gdb.go index 420f03265..476878571 100644 --- a/geg/database/mysql/gdb.go +++ b/geg/database/mysql/gdb.go @@ -475,7 +475,19 @@ func mapToStruct() { } } +// getQueriedSqls +func getQueriedSqls() { + for k, v := range db.GetQueriedSqls() { + fmt.Println(k, ":") + fmt.Println("Sql :", v.Sql) + fmt.Println("Args :", v.Args) + fmt.Println("Error:", v.Error) + fmt.Println("Func :", v.Func) + } +} + func main() { + db.SetDebug(true) r, err := db.Table("test").Where("id=1").One() fmt.Println(r["datetime"]) fmt.Println(r["datetime"].Time().Date()) @@ -503,4 +515,7 @@ func main() { //transaction2() // //keepPing() + //likeQuery() + //mapToStruct() + getQueriedSqls() } \ No newline at end of file diff --git a/geg/other/test.go b/geg/other/test.go index ac383ab30..dff028ff9 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -1,13 +1,10 @@ package main import ( + "gitee.com/johng/gf/g/util/gregx" "fmt" - "gitee.com/johng/gf/g/encoding/gbinary" ) func main() { - i := 65533 - b := gbinary.EncodeByLength(3, i) - fmt.Println(b) - fmt.Println(gbinary.DecodeToInt32(b)) + fmt.Println(gregx.MatchString(`[-/]`, "-")) }