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