diff --git a/.example/database/gdb/driver/driver/driver.go b/.example/database/gdb/driver/driver/driver.go
new file mode 100644
index 000000000..e253f2bff
--- /dev/null
+++ b/.example/database/gdb/driver/driver/driver.go
@@ -0,0 +1,119 @@
+// Copyright 2017 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
+
+package driver
+
+import (
+ "database/sql"
+ "fmt"
+ "github.com/gogf/gf/database/gdb"
+ "github.com/gogf/gf/internal/intlog"
+ "github.com/gogf/gf/text/gstr"
+)
+
+type MyDriver struct {
+ *gdb.Core
+}
+
+// Open creates and returns a underlying sql.DB object for mysql.
+func (d *MyDriver) Open(config *gdb.ConfigNode) (*sql.DB, error) {
+ var source string
+ if config.LinkInfo != "" {
+ source = config.LinkInfo
+ } else {
+ source = fmt.Sprintf(
+ "%s:%s@tcp(%s:%s)/%s?charset=%s&multiStatements=true&parseTime=true&loc=Local",
+ config.User, config.Pass, config.Host, config.Port, config.Name, config.Charset,
+ )
+ }
+ intlog.Printf("Open: %s", source)
+ if db, err := sql.Open("mysql", source); err == nil {
+ return db, nil
+ } else {
+ return nil, err
+ }
+}
+
+// getChars returns the security char for this type of database.
+func (d *MyDriver) GetChars() (charLeft string, charRight string) {
+ return "`", "`"
+}
+
+// handleSqlBeforeExec handles the sql before posts it to database.
+func (d *MyDriver) HandleSqlBeforeExec(sql string) string {
+ return sql
+}
+
+// Tables retrieves and returns the tables of current schema.
+func (d *MyDriver) Tables(schema ...string) (tables []string, err error) {
+ var result gdb.Result
+ link, err := d.DB.GetSlave(schema...)
+ if err != nil {
+ return nil, err
+ }
+ result, err = d.DB.DoGetAll(link, `SHOW TABLES`)
+ if err != nil {
+ return
+ }
+ for _, m := range result {
+ for _, v := range m {
+ tables = append(tables, v.String())
+ }
+ }
+ return
+}
+
+// gdb.TableFields retrieves and returns the fields information of specified table of current schema.
+//
+// Note that it returns a map containing the field name and its corresponding fields.
+// As a map is unsorted, the gdb.TableField struct has a "Index" field marks its sequence in the fields.
+//
+// It's using cache feature to enhance the performance, which is never expired util the process restarts.
+func (d *MyDriver) TableFields(table string, schema ...string) (fields map[string]*gdb.TableField, err error) {
+ table = gstr.Trim(table)
+ if gstr.Contains(table, " ") {
+ panic("function gdb.TableFields supports only single table operations")
+ }
+ checkSchema := d.DB.GetSchema()
+ if len(schema) > 0 && schema[0] != "" {
+ checkSchema = schema[0]
+ }
+ v := d.DB.GetCache().GetOrSetFunc(
+ fmt.Sprintf(`mysql_table_fields_%s_%s`, table, checkSchema),
+ func() interface{} {
+ var result gdb.Result
+ var link *sql.DB
+ link, err = d.DB.GetSlave(checkSchema)
+ if err != nil {
+ return nil
+ }
+ result, err = d.DB.DoGetAll(
+ link,
+ fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.DB.QuoteWord(table)),
+ )
+ if err != nil {
+ return nil
+ }
+ fields = make(map[string]*gdb.TableField)
+ for i, m := range result {
+ fields[m["Field"].String()] = &gdb.TableField{
+ Index: i,
+ Name: m["Field"].String(),
+ Type: m["Type"].String(),
+ Null: m["Null"].Bool(),
+ Key: m["Key"].String(),
+ Default: m["Default"].Val(),
+ Extra: m["Extra"].String(),
+ Comment: m["Comment"].String(),
+ }
+ }
+ return fields
+ }, 0)
+ if err == nil {
+ fields = v.(map[string]*gdb.TableField)
+ }
+ return
+}
diff --git a/.example/database/gdb/driver/main.go b/.example/database/gdb/driver/main.go
new file mode 100644
index 000000000..06ab7d0f9
--- /dev/null
+++ b/.example/database/gdb/driver/main.go
@@ -0,0 +1 @@
+package main
diff --git a/.example/database/gdb/mysql/gdb_all.go b/.example/database/gdb/mysql/gdb_all.go
index 681277607..30161552f 100644
--- a/.example/database/gdb/mysql/gdb_all.go
+++ b/.example/database/gdb/mysql/gdb_all.go
@@ -11,11 +11,11 @@ func main() {
// 开启调试模式,以便于记录所有执行的SQL
db.SetDebug(true)
- r, e := db.Table("test").OrderBy("id asc").All()
+ r, e := db.Table("test").Order("id asc").All()
if e != nil {
- panic(e)
+ fmt.Println(e)
}
if r != nil {
- fmt.Println(r.ToList())
+ fmt.Println(r.List())
}
}
diff --git a/.example/database/gdb/mysql/gdb_value.go b/.example/database/gdb/mysql/gdb_value.go
index 400323a16..c0e325189 100644
--- a/.example/database/gdb/mysql/gdb_value.go
+++ b/.example/database/gdb/mysql/gdb_value.go
@@ -9,5 +9,4 @@ func main() {
db.SetDebug(true)
db.Table("user").Fields("DISTINCT id,nickname").Filter().All()
-
}
diff --git a/.example/net/ghttp/server/template/conflicts-name/client b/.example/net/ghttp/server/template/conflicts-name/client
deleted file mode 100644
index e69de29bb..000000000
diff --git a/.example/other/test.go b/.example/other/test.go
index 774c42627..5db56e11d 100644
--- a/.example/other/test.go
+++ b/.example/other/test.go
@@ -1,37 +1,16 @@
package main
import (
- "net/http"
-
- "github.com/gogf/gf/frame/g"
- "github.com/gogf/gf/net/ghttp"
+ "fmt"
+ "github.com/gogf/gf/text/gregex"
)
-func MiddlewareAuth(r *ghttp.Request) {
- token := r.Get("token")
- if token == "123456" {
- r.Response.Writeln("auth")
- r.Middleware.Next()
- } else {
- r.Response.WriteStatus(http.StatusForbidden)
- }
-}
-
-func MiddlewareCORS(r *ghttp.Request) {
- r.Response.Writeln("cors")
- r.Response.CORSDefault()
- r.Middleware.Next()
-}
-
func main() {
- s := g.Server()
- s.Use(MiddlewareCORS)
- s.Group("/api.v2", func(group *ghttp.RouterGroup) {
- group.Middleware(MiddlewareAuth)
- group.ALL("/user/list", func(r *ghttp.Request) {
- r.Response.Writeln("list")
- })
+ data := "@var(.prefix)您收到的验证码为:@var(.code),请在@var(.expire)内完成验证"
+ result, err := gregex.ReplaceStringFuncMatch(`(@var\(\.\w+\))`, data, func(match []string) string {
+ fmt.Println(match)
+ return "#"
})
- s.SetPort(8199)
- s.Run()
+ fmt.Println(err)
+ fmt.Println(result)
}
diff --git a/.example/util/gpage/gpage.go b/.example/util/gpage/gpage.go
index ab75e32dc..9b6071f0d 100644
--- a/.example/util/gpage/gpage.go
+++ b/.example/util/gpage/gpage.go
@@ -4,13 +4,12 @@ import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/os/gview"
- "github.com/gogf/gf/util/gpage"
)
func main() {
s := ghttp.GetServer()
s.BindHandler("/page/demo", func(r *ghttp.Request) {
- page := gpage.New(100, 10, r.Get("page"), r.URL.String())
+ page := r.GetPage(100, 10)
buffer, _ := gview.ParseContent(`
diff --git a/.example/util/gpage/gpage_ajax.go b/.example/util/gpage/gpage_ajax.go
index 299715ceb..84c4facfa 100644
--- a/.example/util/gpage/gpage_ajax.go
+++ b/.example/util/gpage/gpage_ajax.go
@@ -4,14 +4,13 @@ import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/os/gview"
- "github.com/gogf/gf/util/gpage"
)
func main() {
s := ghttp.GetServer()
s.BindHandler("/page/ajax", func(r *ghttp.Request) {
- page := gpage.New(100, 10, r.Get("page"), r.URL.String(), r.Router)
- page.EnableAjax("DoAjax")
+ page := r.GetPage(100, 10)
+ page.AjaxActionName = "DoAjax"
buffer, _ := gview.ParseContent(`
@@ -29,11 +28,17 @@ func main() {
- {{.page}}
+ {{.page1}}
+ {{.page2}}
+ {{.page3}}
+ {{.page4}}
`, g.Map{
- "page": page.GetContent(1),
+ "page1": page.GetContent(1),
+ "page2": page.GetContent(2),
+ "page3": page.GetContent(3),
+ "page4": page.GetContent(4),
})
r.Response.Write(buffer)
})
diff --git a/.example/util/gpage/gpage_custom1.go b/.example/util/gpage/gpage_custom1.go
index 57cebaff1..9571c36aa 100644
--- a/.example/util/gpage/gpage_custom1.go
+++ b/.example/util/gpage/gpage_custom1.go
@@ -23,7 +23,7 @@ func wrapContent(page *gpage.Page) string {
func main() {
s := ghttp.GetServer()
s.BindHandler("/page/custom1/*page", func(r *ghttp.Request) {
- page := gpage.New(100, 10, r.Get("page"), r.URL.String(), r.Router)
+ page := r.GetPage(100, 10)
content := wrapContent(page)
buffer, _ := gview.ParseContent(`
diff --git a/.example/util/gpage/gpage_custom2.go b/.example/util/gpage/gpage_custom2.go
index f50428075..2e84042c2 100644
--- a/.example/util/gpage/gpage_custom2.go
+++ b/.example/util/gpage/gpage_custom2.go
@@ -15,7 +15,7 @@ func pageContent(page *gpage.Page) string {
page.LastPageTag = "LastPage"
pageStr := page.FirstPage()
pageStr += page.PrevPage()
- pageStr += page.PageBar("current-page")
+ pageStr += page.PageBar()
pageStr += page.NextPage()
pageStr += page.LastPage()
return pageStr
@@ -24,7 +24,7 @@ func pageContent(page *gpage.Page) string {
func main() {
s := ghttp.GetServer()
s.BindHandler("/page/custom2/*page", func(r *ghttp.Request) {
- page := gpage.New(100, 10, r.Get("page"), r.URL.String(), r.Router)
+ page := r.GetPage(100, 10)
buffer, _ := gview.ParseContent(`
diff --git a/.example/util/gpage/gpage_static1.go b/.example/util/gpage/gpage_static1.go
index 1d1bbf798..c62a0851a 100644
--- a/.example/util/gpage/gpage_static1.go
+++ b/.example/util/gpage/gpage_static1.go
@@ -4,13 +4,12 @@ import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/os/gview"
- "github.com/gogf/gf/util/gpage"
)
func main() {
s := g.Server()
s.BindHandler("/page/static/*page", func(r *ghttp.Request) {
- page := gpage.New(100, 10, r.Get("page"), r.URL.String(), r.Router)
+ page := r.GetPage(100, 10)
buffer, _ := gview.ParseContent(`
diff --git a/.example/util/gpage/gpage_static2.go b/.example/util/gpage/gpage_static2.go
index d0056fbd0..135736fc0 100644
--- a/.example/util/gpage/gpage_static2.go
+++ b/.example/util/gpage/gpage_static2.go
@@ -4,13 +4,12 @@ import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/os/gview"
- "github.com/gogf/gf/util/gpage"
)
func main() {
s := g.Server()
s.BindHandler("/:obj/*action/{page}.html", func(r *ghttp.Request) {
- page := gpage.New(100, 10, r.Get("page"), r.URL.String(), r.Router)
+ page := r.GetPage(100, 10)
buffer, _ := gview.ParseContent(`
diff --git a/.example/util/gpage/gpage_template.go b/.example/util/gpage/gpage_template.go
index c4e25e3d9..a06c27e4a 100644
--- a/.example/util/gpage/gpage_template.go
+++ b/.example/util/gpage/gpage_template.go
@@ -4,14 +4,13 @@ import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/os/gview"
- "github.com/gogf/gf/util/gpage"
)
func main() {
s := g.Server()
s.BindHandler("/page/template/{page}.html", func(r *ghttp.Request) {
- page := gpage.New(100, 10, r.Get("page"), r.URL.String())
- page.SetUrlTemplate("/order/list/{.page}.html")
+ page := r.GetPage(100, 10)
+ page.UrlTemplate = "/order/list/{.page}.html"
buffer, _ := gview.ParseContent(`
diff --git a/DONATOR.MD b/DONATOR.MD
index 418951da5..d5327b5a9 100644
--- a/DONATOR.MD
+++ b/DONATOR.MD
@@ -15,7 +15,7 @@ We currently accept donation by Alipay/WechatPay, please note your github/gitee
|[zhuhuan12](https://gitee.com/zhuhuan12)|gitee|¥50.00 |
|[zfan_codes](https://gitee.com/zfan_codes)|gitee|¥10.00 |
|[arden](https://github.com/arden)|alipay|¥10.00 |
-|[macnie](https://www.macnie.com)|wechat|¥100.00 |
+|[macnie](https://www.macnie.com)|wechat|¥110.00 |
|lah|wechat|¥100.00 |
|x*z|wechat|¥20.00 |
|潘兄|wechat|¥100.00 |
diff --git a/container/garray/garray_sorted_any.go b/container/garray/garray_sorted_any.go
index dd6c1690b..32439e8f8 100644
--- a/container/garray/garray_sorted_any.go
+++ b/container/garray/garray_sorted_any.go
@@ -439,6 +439,9 @@ func (a *SortedArray) SetUnique(unique bool) *SortedArray {
func (a *SortedArray) Unique() *SortedArray {
a.mu.Lock()
defer a.mu.Unlock()
+ if len(a.array) == 0 {
+ return a
+ }
i := 0
for {
if i == len(a.array)-1 {
diff --git a/container/garray/garray_sorted_int.go b/container/garray/garray_sorted_int.go
index 4d469aa51..fc92567dc 100644
--- a/container/garray/garray_sorted_int.go
+++ b/container/garray/garray_sorted_int.go
@@ -429,6 +429,10 @@ func (a *SortedIntArray) SetUnique(unique bool) *SortedIntArray {
// Unique uniques the array, clear repeated items.
func (a *SortedIntArray) Unique() *SortedIntArray {
a.mu.Lock()
+ defer a.mu.Unlock()
+ if len(a.array) == 0 {
+ return a
+ }
i := 0
for {
if i == len(a.array)-1 {
@@ -440,7 +444,6 @@ func (a *SortedIntArray) Unique() *SortedIntArray {
i++
}
}
- a.mu.Unlock()
return a
}
diff --git a/container/garray/garray_sorted_str.go b/container/garray/garray_sorted_str.go
index 1b8054c09..c7c9f03aa 100644
--- a/container/garray/garray_sorted_str.go
+++ b/container/garray/garray_sorted_str.go
@@ -414,6 +414,10 @@ func (a *SortedStrArray) SetUnique(unique bool) *SortedStrArray {
// Unique uniques the array, clear repeated items.
func (a *SortedStrArray) Unique() *SortedStrArray {
a.mu.Lock()
+ defer a.mu.Unlock()
+ if len(a.array) == 0 {
+ return a
+ }
i := 0
for {
if i == len(a.array)-1 {
@@ -425,7 +429,6 @@ func (a *SortedStrArray) Unique() *SortedStrArray {
i++
}
}
- a.mu.Unlock()
return a
}
diff --git a/container/garray/garray_z_example_test.go b/container/garray/garray_z_example_test.go
index 7ab4da733..eace2f821 100644
--- a/container/garray/garray_z_example_test.go
+++ b/container/garray/garray_z_example_test.go
@@ -13,8 +13,8 @@ import (
)
func Example_basic() {
- // 创建普通的数组,默认并发安全(带锁)
- a := garray.New(true)
+ // 创建普通的数组
+ a := garray.New()
// 添加数据项
for i := 0; i < 10; i++ {
diff --git a/container/gqueue/gqueue.go b/container/gqueue/gqueue.go
index 449c15adf..1ecfc9231 100644
--- a/container/gqueue/gqueue.go
+++ b/container/gqueue/gqueue.go
@@ -4,7 +4,7 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
-// Package gqueue provides a dynamic/static concurrent-safe queue.
+// Package gqueue provides dynamic/static concurrent-safe queue.
//
// Features:
//
@@ -25,6 +25,7 @@ import (
"github.com/gogf/gf/container/gtype"
)
+// Queue is a concurrent-safe queue built on doubly linked list and channel.
type Queue struct {
limit int // Limit for queue size.
list *glist.List // Underlying list structure for data maintaining.
@@ -54,14 +55,14 @@ func New(limit ...int) *Queue {
q.list = glist.New(true)
q.events = make(chan struct{}, math.MaxInt32)
q.C = make(chan interface{}, gDEFAULT_QUEUE_SIZE)
- go q.startAsyncLoop()
+ go q.asyncLoopFromListToChannel()
}
return q
}
-// startAsyncLoop starts an asynchronous goroutine,
+// asyncLoopFromListToChannel starts an asynchronous goroutine,
// which handles the data synchronization from list to channel .
-func (q *Queue) startAsyncLoop() {
+func (q *Queue) asyncLoopFromListToChannel() {
defer func() {
if q.closed.Val() {
_ = recover()
diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go
index b804ad059..f2241d756 100644
--- a/database/gdb/gdb.go
+++ b/database/gdb/gdb.go
@@ -22,7 +22,7 @@ import (
"github.com/gogf/gf/util/grand"
)
-// DB is the interface for ORM operations.
+// DB defines the interfaces for ORM operations.
type DB interface {
// Open creates a raw connection object for database with given node configuration.
// Note that it is not recommended using the this function manually.
@@ -34,14 +34,14 @@ type DB interface {
Prepare(sql string, execOnMaster ...bool) (*sql.Stmt, error)
// Internal APIs for CURD, which can be overwrote for custom CURD implements.
- doQuery(link dbLink, query string, args ...interface{}) (rows *sql.Rows, err error)
- doGetAll(link dbLink, query string, args ...interface{}) (result Result, err error)
- doExec(link dbLink, query string, args ...interface{}) (result sql.Result, err error)
- doPrepare(link dbLink, query string) (*sql.Stmt, error)
- doInsert(link dbLink, table string, data interface{}, option int, batch ...int) (result sql.Result, err error)
- doBatchInsert(link dbLink, table string, list interface{}, option int, batch ...int) (result sql.Result, err error)
- doUpdate(link dbLink, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error)
- doDelete(link dbLink, table string, condition string, args ...interface{}) (result sql.Result, err error)
+ DoQuery(link Link, query string, args ...interface{}) (rows *sql.Rows, err error)
+ DoGetAll(link Link, query string, args ...interface{}) (result Result, err error)
+ DoExec(link Link, query string, args ...interface{}) (result sql.Result, err error)
+ DoPrepare(link Link, query string) (*sql.Stmt, error)
+ DoInsert(link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error)
+ DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error)
+ DoUpdate(link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error)
+ DoDelete(link Link, table string, condition string, args ...interface{}) (result sql.Result, err error)
// Query APIs for convenience purpose.
GetAll(query string, args ...interface{}) (Result, error)
@@ -52,11 +52,11 @@ type DB interface {
GetStructs(objPointerSlice interface{}, query string, args ...interface{}) error
GetScan(objPointer interface{}, query string, args ...interface{}) error
- // Master/Slave support.
+ // Master/Slave specification support.
Master() (*sql.DB, error)
Slave() (*sql.DB, error)
- // Ping.
+ // Ping-Pong.
PingMaster() error
PingSlave() error
@@ -75,48 +75,49 @@ type DB interface {
Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error)
Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error)
- // Create model.
+ // Model creation.
From(tables string) *Model
Table(tables string) *Model
Schema(schema string) *Schema
// Configuration methods.
+ GetCache() *gcache.Cache
SetDebug(debug bool)
+ GetDebug() bool
SetSchema(schema string)
+ GetSchema() string
+ GetPrefix() string
SetLogger(logger *glog.Logger)
GetLogger() *glog.Logger
SetMaxIdleConnCount(n int)
SetMaxOpenConnCount(n int)
SetMaxConnLifetime(d time.Duration)
+
+ // Utility methods.
+ GetChars() (charLeft string, charRight string)
+ GetMaster(schema ...string) (*sql.DB, error)
+ GetSlave(schema ...string) (*sql.DB, error)
+ QuoteWord(s string) string
+ QuoteString(s string) string
+ QuotePrefixTableName(table string) string
Tables(schema ...string) (tables []string, err error)
TableFields(table string, schema ...string) (map[string]*TableField, error)
+ // HandleSqlBeforeCommit is a hook function, which deals with the sql string before
+ // it's committed to underlying driver. The parameter specifies the current
+ // database connection operation object. You can modify the sql string and its
+ // arguments as you wish before they're committed to driver.
+ HandleSqlBeforeCommit(link Link, query string, args []interface{}) (string, []interface{})
+
// Internal methods.
- getCache() *gcache.Cache
- getChars() (charLeft string, charRight string)
- getDebug() bool
- getPrefix() string
- getMaster(schema ...string) (*sql.DB, error)
- getSlave(schema ...string) (*sql.DB, error)
- quoteWord(s string) string
- quoteString(s string) string
- handleTableName(table string) string
filterFields(schema, table string, data map[string]interface{}) map[string]interface{}
convertValue(fieldValue []byte, fieldType string) interface{}
rowsToResult(rows *sql.Rows) (Result, error)
- handleSqlBeforeExec(sql string) string
}
-// dbLink is a common database function wrapper interface for internal usage.
-type dbLink interface {
- Query(query string, args ...interface{}) (*sql.Rows, error)
- Exec(sql string, args ...interface{}) (sql.Result, error)
- Prepare(sql string) (*sql.Stmt, error)
-}
-
-// dbBase is the base struct for database management.
-type dbBase struct {
- db DB // DB interface object.
+// Core is the base struct for database management.
+type Core struct {
+ DB DB // DB interface object.
group string // Configuration group name.
debug *gtype.Bool // Enable debug mode for the database.
cache *gcache.Cache // Cache manager.
@@ -128,6 +129,12 @@ type dbBase struct {
maxConnLifetime time.Duration // Max TTL for a connection.
}
+// Driver is the interface for integrating sql drivers into package gdb.
+type Driver interface {
+ // New creates and returns a database object for specified database server.
+ New(core *Core, node *ConfigNode) (DB, error)
+}
+
// Sql is the sql recording struct.
type Sql struct {
Sql string // SQL string(may contain reserved char '?').
@@ -150,6 +157,13 @@ type TableField struct {
Comment string // Comment.
}
+// Link is a common database function wrapper interface.
+type Link interface {
+ Query(query string, args ...interface{}) (*sql.Rows, error)
+ Exec(sql string, args ...interface{}) (sql.Result, error)
+ Prepare(sql string) (*sql.Stmt, error)
+}
+
// Value is the field value type.
type Value = *gvar.Var
@@ -176,10 +190,24 @@ const (
)
var (
- // Instance map.
+ // instances is the management map for instances.
instances = gmap.NewStrAnyMap(true)
+ // driverMap manages all custom registered driver.
+ driverMap = map[string]Driver{
+ "mysql": &DriverMysql{},
+ "mssql": &DriverMssql{},
+ "pgsql": &DriverPgsql{},
+ "oracle": &DriverOracle{},
+ "sqlite": &DriverSqlite{},
+ }
)
+// Register registers custom database driver to gdb.
+func Register(name string, driver Driver) error {
+ driverMap[name] = driver
+ return nil
+}
+
// New creates and returns an ORM object with global configurations.
// The parameter specifies the configuration group name,
// which is DEFAULT_GROUP_NAME in default.
@@ -196,31 +224,24 @@ func New(name ...string) (db DB, err error) {
}
if _, ok := configs.config[group]; ok {
if node, err := getConfigNodeByGroup(group, true); err == nil {
- base := &dbBase{
- group: group,
- debug: gtype.NewBool(),
- cache: gcache.New(),
- schema: gtype.NewString(),
- logger: glog.New(),
- prefix: node.Prefix,
- // Default max connection life time if user does not configure.
- maxConnLifetime: gDEFAULT_CONN_MAX_LIFE_TIME,
+ c := &Core{
+ group: group,
+ debug: gtype.NewBool(),
+ cache: gcache.New(),
+ schema: gtype.NewString(),
+ logger: glog.New(),
+ prefix: node.Prefix,
+ maxConnLifetime: gDEFAULT_CONN_MAX_LIFE_TIME, // Default max connection life time if user does not configure.
}
- switch node.Type {
- case "mysql":
- base.db = &dbMysql{dbBase: base}
- case "pgsql":
- base.db = &dbPgsql{dbBase: base}
- case "mssql":
- base.db = &dbMssql{dbBase: base}
- case "sqlite":
- base.db = &dbSqlite{dbBase: base}
- case "oracle":
- base.db = &dbOracle{dbBase: base}
- default:
+ if v, ok := driverMap[node.Type]; ok {
+ c.DB, err = v.New(c, node)
+ if err != nil {
+ return nil, err
+ }
+ return c.DB, nil
+ } else {
return nil, errors.New(fmt.Sprintf(`unsupported database type "%s"`, node.Type))
}
- return base.db, nil
} else {
return nil, err
}
@@ -321,9 +342,9 @@ func getConfigNodeByWeight(cg ConfigGroup) *ConfigNode {
// getSqlDb retrieves and returns a underlying database connection object.
// The parameter specifies whether retrieves master node connection if
// master-slave nodes are configured.
-func (bs *dbBase) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error) {
+func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error) {
// Load balance.
- node, err := getConfigNodeByGroup(bs.group, master)
+ node, err := getConfigNodeByGroup(c.group, master)
if err != nil {
return nil, err
}
@@ -332,7 +353,7 @@ func (bs *dbBase) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err er
node.Charset = "utf8"
}
// Changes the schema.
- nodeSchema := bs.schema.Val()
+ nodeSchema := c.schema.Val()
if len(schema) > 0 && schema[0] != "" {
nodeSchema = schema[0]
}
@@ -343,25 +364,25 @@ func (bs *dbBase) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err er
node = &n
}
// Cache the underlying connection object by node.
- v := bs.cache.GetOrSetFuncLock(node.String(), func() interface{} {
- sqlDb, err = bs.db.Open(node)
+ v := c.cache.GetOrSetFuncLock(node.String(), func() interface{} {
+ sqlDb, err = c.DB.Open(node)
if err != nil {
return nil
}
- if bs.maxIdleConnCount > 0 {
- sqlDb.SetMaxIdleConns(bs.maxIdleConnCount)
+ if c.maxIdleConnCount > 0 {
+ sqlDb.SetMaxIdleConns(c.maxIdleConnCount)
} else if node.MaxIdleConnCount > 0 {
sqlDb.SetMaxIdleConns(node.MaxIdleConnCount)
}
- if bs.maxOpenConnCount > 0 {
- sqlDb.SetMaxOpenConns(bs.maxOpenConnCount)
+ if c.maxOpenConnCount > 0 {
+ sqlDb.SetMaxOpenConns(c.maxOpenConnCount)
} else if node.MaxOpenConnCount > 0 {
sqlDb.SetMaxOpenConns(node.MaxOpenConnCount)
}
- if bs.maxConnLifetime > 0 {
- sqlDb.SetConnMaxLifetime(bs.maxConnLifetime * time.Second)
+ if c.maxConnLifetime > 0 {
+ sqlDb.SetConnMaxLifetime(c.maxConnLifetime * time.Second)
} else if node.MaxConnLifetime > 0 {
sqlDb.SetConnMaxLifetime(node.MaxConnLifetime * time.Second)
}
@@ -371,40 +392,7 @@ func (bs *dbBase) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err er
sqlDb = v.(*sql.DB)
}
if node.Debug {
- bs.db.SetDebug(node.Debug)
+ c.DB.SetDebug(node.Debug)
}
return
}
-
-// SetSchema changes the schema for this database connection object.
-// Importantly note that when schema configuration changed for the database,
-// it affects all operations on the database object in the future.
-func (bs *dbBase) SetSchema(schema string) {
- bs.schema.Set(schema)
-}
-
-// Master creates and returns a connection from master node if master-slave configured.
-// It returns the default connection if master-slave not configured.
-func (bs *dbBase) Master() (*sql.DB, error) {
- return bs.getSqlDb(true, bs.schema.Val())
-}
-
-// Slave creates and returns a connection from slave node if master-slave configured.
-// It returns the default connection if master-slave not configured.
-func (bs *dbBase) Slave() (*sql.DB, error) {
- return bs.getSqlDb(false, bs.schema.Val())
-}
-
-// getMaster acts like function Master but with additional parameter specifying
-// the schema for the connection. It is defined for internal usage.
-// Also see Master.
-func (bs *dbBase) getMaster(schema ...string) (*sql.DB, error) {
- return bs.getSqlDb(true, schema...)
-}
-
-// getSlave acts like function Slave but with additional parameter specifying
-// the schema for the connection. It is defined for internal usage.
-// Also see Slave.
-func (bs *dbBase) getSlave(schema ...string) (*sql.DB, error) {
- return bs.getSqlDb(false, schema...)
-}
diff --git a/database/gdb/gdb_base.go b/database/gdb/gdb_core.go
similarity index 66%
rename from database/gdb/gdb_base.go
rename to database/gdb/gdb_core.go
index cbefda532..077684a54 100644
--- a/database/gdb/gdb_base.go
+++ b/database/gdb/gdb_core.go
@@ -16,7 +16,6 @@ import (
"strings"
"github.com/gogf/gf/container/gvar"
- "github.com/gogf/gf/os/gcache"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/text/gregex"
"github.com/gogf/gf/util/gconv"
@@ -32,22 +31,34 @@ var (
lastOperatorReg = regexp.MustCompile(`[<>=]+\s*$`)
)
+// Master creates and returns a connection from master node if master-slave configured.
+// It returns the default connection if master-slave not configured.
+func (c *Core) Master() (*sql.DB, error) {
+ return c.getSqlDb(true, c.schema.Val())
+}
+
+// Slave creates and returns a connection from slave node if master-slave configured.
+// It returns the default connection if master-slave not configured.
+func (c *Core) Slave() (*sql.DB, error) {
+ return c.getSqlDb(false, c.schema.Val())
+}
+
// Query commits one query SQL to underlying driver and returns the execution result.
// It is most commonly used for data querying.
-func (bs *dbBase) Query(query string, args ...interface{}) (rows *sql.Rows, err error) {
- link, err := bs.db.Slave()
+func (c *Core) Query(query string, args ...interface{}) (rows *sql.Rows, err error) {
+ link, err := c.DB.Slave()
if err != nil {
return nil, err
}
- return bs.db.doQuery(link, query, args...)
+ return c.DB.DoQuery(link, query, args...)
}
// doQuery commits the query string and its arguments to underlying driver
// through given link object and returns the execution result.
-func (bs *dbBase) doQuery(link dbLink, query string, args ...interface{}) (rows *sql.Rows, err error) {
+func (c *Core) DoQuery(link Link, query string, args ...interface{}) (rows *sql.Rows, err error) {
query, args = formatQuery(query, args)
- query = bs.db.handleSqlBeforeExec(query)
- if bs.db.getDebug() {
+ query, args = c.DB.HandleSqlBeforeCommit(link, query, args)
+ if c.DB.GetDebug() {
mTime1 := gtime.TimestampMilli()
rows, err = link.Query(query, args...)
mTime2 := gtime.TimestampMilli()
@@ -59,7 +70,7 @@ func (bs *dbBase) doQuery(link dbLink, query string, args ...interface{}) (rows
Start: mTime1,
End: mTime2,
}
- bs.printSql(s)
+ c.writeSqlToLogger(s)
} else {
rows, err = link.Query(query, args...)
}
@@ -73,20 +84,20 @@ func (bs *dbBase) doQuery(link dbLink, query string, args ...interface{}) (rows
// Exec commits one query SQL to underlying driver and returns the execution result.
// It is most commonly used for data inserting and updating.
-func (bs *dbBase) Exec(query string, args ...interface{}) (result sql.Result, err error) {
- link, err := bs.db.Master()
+func (c *Core) Exec(query string, args ...interface{}) (result sql.Result, err error) {
+ link, err := c.DB.Master()
if err != nil {
return nil, err
}
- return bs.db.doExec(link, query, args...)
+ return c.DB.DoExec(link, query, args...)
}
// doExec commits the query string and its arguments to underlying driver
// through given link object and returns the execution result.
-func (bs *dbBase) doExec(link dbLink, query string, args ...interface{}) (result sql.Result, err error) {
+func (c *Core) DoExec(link Link, query string, args ...interface{}) (result sql.Result, err error) {
query, args = formatQuery(query, args)
- query = bs.db.handleSqlBeforeExec(query)
- if bs.db.getDebug() {
+ query, args = c.DB.HandleSqlBeforeCommit(link, query, args)
+ if c.DB.GetDebug() {
mTime1 := gtime.TimestampMilli()
result, err = link.Exec(query, args...)
mTime2 := gtime.TimestampMilli()
@@ -98,7 +109,7 @@ func (bs *dbBase) doExec(link dbLink, query string, args ...interface{}) (result
Start: mTime1,
End: mTime2,
}
- bs.printSql(s)
+ c.writeSqlToLogger(s)
} else {
result, err = link.Exec(query, args...)
}
@@ -113,50 +124,50 @@ func (bs *dbBase) doExec(link dbLink, query string, args ...interface{}) (result
//
// The parameter specifies whether executing the sql on master node,
// or else it executes the sql on slave node if master-slave configured.
-func (bs *dbBase) Prepare(query string, execOnMaster ...bool) (*sql.Stmt, error) {
+func (c *Core) Prepare(query string, execOnMaster ...bool) (*sql.Stmt, error) {
err := (error)(nil)
- link := (dbLink)(nil)
+ link := (Link)(nil)
if len(execOnMaster) > 0 && execOnMaster[0] {
- if link, err = bs.db.Master(); err != nil {
+ if link, err = c.DB.Master(); err != nil {
return nil, err
}
} else {
- if link, err = bs.db.Slave(); err != nil {
+ if link, err = c.DB.Slave(); err != nil {
return nil, err
}
}
- return bs.db.doPrepare(link, query)
+ return c.DB.DoPrepare(link, query)
}
// doPrepare calls prepare function on given link object and returns the statement object.
-func (bs *dbBase) doPrepare(link dbLink, query string) (*sql.Stmt, error) {
+func (c *Core) DoPrepare(link Link, query string) (*sql.Stmt, error) {
return link.Prepare(query)
}
// GetAll queries and returns data records from database.
-func (bs *dbBase) GetAll(query string, args ...interface{}) (Result, error) {
- return bs.db.doGetAll(nil, query, args...)
+func (c *Core) GetAll(query string, args ...interface{}) (Result, error) {
+ return c.DB.DoGetAll(nil, query, args...)
}
// doGetAll queries and returns data records from database.
-func (bs *dbBase) doGetAll(link dbLink, query string, args ...interface{}) (result Result, err error) {
+func (c *Core) DoGetAll(link Link, query string, args ...interface{}) (result Result, err error) {
if link == nil {
- link, err = bs.db.Slave()
+ link, err = c.DB.Slave()
if err != nil {
return nil, err
}
}
- rows, err := bs.doQuery(link, query, args...)
+ rows, err := c.DB.DoQuery(link, query, args...)
if err != nil || rows == nil {
return nil, err
}
defer rows.Close()
- return bs.db.rowsToResult(rows)
+ return c.DB.rowsToResult(rows)
}
// GetOne queries and returns one record from database.
-func (bs *dbBase) GetOne(query string, args ...interface{}) (Record, error) {
- list, err := bs.GetAll(query, args...)
+func (c *Core) GetOne(query string, args ...interface{}) (Record, error) {
+ list, err := c.DB.GetAll(query, args...)
if err != nil {
return nil, err
}
@@ -168,8 +179,8 @@ func (bs *dbBase) GetOne(query string, args ...interface{}) (Record, error) {
// GetStruct queries one record from database and converts it to given struct.
// The parameter should be a pointer to struct.
-func (bs *dbBase) GetStruct(pointer interface{}, query string, args ...interface{}) error {
- one, err := bs.GetOne(query, args...)
+func (c *Core) GetStruct(pointer interface{}, query string, args ...interface{}) error {
+ one, err := c.DB.GetOne(query, args...)
if err != nil {
return err
}
@@ -181,8 +192,8 @@ func (bs *dbBase) GetStruct(pointer interface{}, query string, args ...interface
// GetStructs queries records from database and converts them to given struct.
// The parameter should be type of struct slice: []struct/[]*struct.
-func (bs *dbBase) GetStructs(pointer interface{}, query string, args ...interface{}) error {
- all, err := bs.GetAll(query, args...)
+func (c *Core) GetStructs(pointer interface{}, query string, args ...interface{}) error {
+ all, err := c.DB.GetAll(query, args...)
if err != nil {
return err
}
@@ -198,7 +209,7 @@ func (bs *dbBase) GetStructs(pointer interface{}, query string, args ...interfac
// If parameter is type of struct pointer, it calls GetStruct internally for
// the conversion. If parameter is type of slice, it calls GetStructs internally
// for conversion.
-func (bs *dbBase) GetScan(pointer interface{}, query string, args ...interface{}) error {
+func (c *Core) GetScan(pointer interface{}, query string, args ...interface{}) error {
t := reflect.TypeOf(pointer)
k := t.Kind()
if k != reflect.Ptr {
@@ -207,9 +218,9 @@ func (bs *dbBase) GetScan(pointer interface{}, query string, args ...interface{}
k = t.Elem().Kind()
switch k {
case reflect.Array, reflect.Slice:
- return bs.db.GetStructs(pointer, query, args...)
+ return c.DB.GetStructs(pointer, query, args...)
case reflect.Struct:
- return bs.db.GetStruct(pointer, query, args...)
+ return c.DB.GetStruct(pointer, query, args...)
}
return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k)
}
@@ -217,8 +228,8 @@ func (bs *dbBase) GetScan(pointer interface{}, query string, args ...interface{}
// GetValue queries and returns the field value from database.
// The sql should queries only one field from database, or else it returns only one
// field of the result.
-func (bs *dbBase) GetValue(query string, args ...interface{}) (Value, error) {
- one, err := bs.GetOne(query, args...)
+func (c *Core) GetValue(query string, args ...interface{}) (Value, error) {
+ one, err := c.DB.GetOne(query, args...)
if err != nil {
return nil, err
}
@@ -229,13 +240,13 @@ func (bs *dbBase) GetValue(query string, args ...interface{}) (Value, error) {
}
// GetCount queries and returns the count from database.
-func (bs *dbBase) GetCount(query string, args ...interface{}) (int, error) {
+func (c *Core) GetCount(query string, args ...interface{}) (int, error) {
// If the query fields do not contains function "COUNT",
// it replaces the query string and adds the "COUNT" function to the fields.
if !gregex.IsMatchString(`(?i)SELECT\s+COUNT\(.+\)\s+FROM`, query) {
query, _ = gregex.ReplaceString(`(?i)(SELECT)\s+(.+)\s+(FROM)`, `$1 COUNT($2) $3`, query)
}
- value, err := bs.GetValue(query, args...)
+ value, err := c.DB.GetValue(query, args...)
if err != nil {
return 0, err
}
@@ -243,8 +254,8 @@ func (bs *dbBase) GetCount(query string, args ...interface{}) (int, error) {
}
// PingMaster pings the master node to check authentication or keeps the connection alive.
-func (bs *dbBase) PingMaster() error {
- if master, err := bs.db.Master(); err != nil {
+func (c *Core) PingMaster() error {
+ if master, err := c.DB.Master(); err != nil {
return err
} else {
return master.Ping()
@@ -252,8 +263,8 @@ func (bs *dbBase) PingMaster() error {
}
// PingSlave pings the slave node to check authentication or keeps the connection alive.
-func (bs *dbBase) PingSlave() error {
- if slave, err := bs.db.Slave(); err != nil {
+func (c *Core) PingSlave() error {
+ if slave, err := c.DB.Slave(); err != nil {
return err
} else {
return slave.Ping()
@@ -264,13 +275,13 @@ func (bs *dbBase) PingSlave() error {
// You should call Commit or Rollback functions of the transaction object
// if you no longer use the transaction. Commit or Rollback functions will also
// close the transaction automatically.
-func (bs *dbBase) Begin() (*TX, error) {
- if master, err := bs.db.Master(); err != nil {
+func (c *Core) Begin() (*TX, error) {
+ if master, err := c.DB.Master(); err != nil {
return nil, err
} else {
if tx, err := master.Begin(); err == nil {
return &TX{
- db: bs.db,
+ db: c.DB,
tx: tx,
master: master,
}, nil
@@ -289,8 +300,8 @@ func (bs *dbBase) Begin() (*TX, error) {
// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
//
// The parameter specifies the batch operation count when given data is slice.
-func (bs *dbBase) Insert(table string, data interface{}, batch ...int) (sql.Result, error) {
- return bs.db.doInsert(nil, table, data, gINSERT_OPTION_DEFAULT, batch...)
+func (c *Core) Insert(table string, data interface{}, batch ...int) (sql.Result, error) {
+ return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_DEFAULT, batch...)
}
// InsertIgnore does "INSERT IGNORE INTO ..." statement for the table.
@@ -302,8 +313,8 @@ func (bs *dbBase) Insert(table string, data interface{}, batch ...int) (sql.Resu
// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
//
// The parameter specifies the batch operation count when given data is slice.
-func (bs *dbBase) InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) {
- return bs.db.doInsert(nil, table, data, gINSERT_OPTION_IGNORE, batch...)
+func (c *Core) InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) {
+ return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_IGNORE, batch...)
}
// Replace does "REPLACE INTO ..." statement for the table.
@@ -318,8 +329,8 @@ func (bs *dbBase) InsertIgnore(table string, data interface{}, batch ...int) (sq
// The parameter can be type of map/gmap/struct/*struct/[]map/[]struct, etc.
// If given data is type of slice, it then does batch replacing, and the optional parameter
// specifies the batch operation count.
-func (bs *dbBase) Replace(table string, data interface{}, batch ...int) (sql.Result, error) {
- return bs.db.doInsert(nil, table, data, gINSERT_OPTION_REPLACE, batch...)
+func (c *Core) Replace(table string, data interface{}, batch ...int) (sql.Result, error) {
+ return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_REPLACE, batch...)
}
// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the table.
@@ -333,54 +344,60 @@ func (bs *dbBase) Replace(table string, data interface{}, batch ...int) (sql.Res
//
// If given data is type of slice, it then does batch saving, and the optional parameter
// specifies the batch operation count.
-func (bs *dbBase) Save(table string, data interface{}, batch ...int) (sql.Result, error) {
- return bs.db.doInsert(nil, table, data, gINSERT_OPTION_SAVE, batch...)
+func (c *Core) Save(table string, data interface{}, batch ...int) (sql.Result, error) {
+ return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_SAVE, batch...)
}
// doInsert inserts or updates data for given table.
//
+// The parameter can be type of map/gmap/struct/*struct/[]map/[]struct, etc.
+// Eg:
+// Data(g.Map{"uid": 10000, "name":"john"})
+// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
+//
// The parameter