diff --git a/DONATOR.MD b/DONATOR.MD index 5cd691921..c7a954564 100644 --- a/DONATOR.MD +++ b/DONATOR.MD @@ -69,7 +69,7 @@ We currently accept donation by Alipay/WechatPay, please note your github/gitee |1*1x|wechat|¥100.00| |[ywanbing](https://github.com/ywanbing)|wechat|¥66.66| |[侯哥](http://www.macnie.com)|wechat|¥10.00| -|如果🍋|alipay|¥100.00| 错误的奶茶^_^ +|如果🍋|alipay|¥100.00| 错过的奶茶^_^ |蔡蔡|wechat|¥666.00| gf真强大,让项目省心 diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index ad37c2c5d..209857895 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -89,6 +89,8 @@ type DB interface { SetSchema(schema string) GetSchema() string GetPrefix() string + SetDryRun(dryrun bool) + GetDryRun() bool SetLogger(logger *glog.Logger) GetLogger() *glog.Logger SetMaxIdleConnCount(n int) @@ -124,6 +126,7 @@ type Core struct { debug *gtype.Bool // Enable debug mode for the database. cache *gcache.Cache // Cache manager. schema *gtype.String // Custom schema for this object. + dryrun *gtype.Bool // Dry run. prefix string // Table prefix. logger *glog.Logger // Logger. maxIdleConnCount int // Max idle connection count. @@ -234,6 +237,7 @@ func New(name ...string) (db DB, err error) { debug: gtype.NewBool(), cache: gcache.New(), schema: gtype.NewString(), + dryrun: gtype.NewBool(), logger: glog.New(), prefix: node.Prefix, maxIdleConnCount: gDEFAULT_CONN_MAX_IDLE_COUNT, @@ -401,5 +405,8 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error if node.Debug { c.DB.SetDebug(node.Debug) } + if node.Debug { + c.DB.SetDryRun(node.DryRun) + } return } diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index 43aaa4b6b..31e064230 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -99,7 +99,11 @@ func (c *Core) DoExec(link Link, sql string, args ...interface{}) (result sql.Re sql, args = c.DB.HandleSqlBeforeCommit(link, sql, args) if c.DB.GetDebug() { mTime1 := gtime.TimestampMilli() - result, err = link.Exec(sql, args...) + if !c.DB.GetDryRun() { + result, err = link.Exec(sql, args...) + } else { + result = new(SqlResult) + } mTime2 := gtime.TimestampMilli() s := &Sql{ Sql: sql, @@ -111,7 +115,11 @@ func (c *Core) DoExec(link Link, sql string, args ...interface{}) (result sql.Re } c.writeSqlToLogger(s) } else { - result, err = link.Exec(sql, args...) + if !c.DB.GetDryRun() { + result, err = link.Exec(sql, args...) + } else { + result = new(SqlResult) + } } return result, formatError(err, sql, args...) } diff --git a/database/gdb/gdb_core_config.go b/database/gdb/gdb_core_config.go index 46e52ef54..3c78665a6 100644 --- a/database/gdb/gdb_core_config.go +++ b/database/gdb/gdb_core_config.go @@ -36,6 +36,7 @@ type ConfigNode struct { Role string // (Optional, "master" in default) Node role, used for master-slave mode: master, slave. Debug bool // (Optional) Debug mode enables debug information logging and output. Prefix string // (Optional) Table prefix. + DryRun bool // (Optional) Dry run, which does SELECT but no INSERT/UPDATE/DELETE statements. Weight int // (Optional) Weight for load balance calculating, it's useless if there's just one node. Charset string // (Optional, "utf8mb4" in default) Custom charset when operating on database. LinkInfo string // (Optional) Custom link information, when it is used, configuration Host/Port/User/Pass/Name are ignored. @@ -142,24 +143,19 @@ func (c *Core) SetMaxConnLifetime(d time.Duration) { // String returns the node as string. func (node *ConfigNode) String() string { - if node.LinkInfo != "" { - return node.LinkInfo - } return fmt.Sprintf( - `%s@%s:%s,%s,%s,%s,%s,%v,%d-%d-%d`, + `%s@%s:%s,%s,%s,%s,%s,%v,%d-%d-%d#%s`, node.User, node.Host, node.Port, node.Name, node.Type, node.Role, node.Charset, node.Debug, node.MaxIdleConnCount, node.MaxOpenConnCount, node.MaxConnLifetime, + node.LinkInfo, ) } // SetDebug enables/disables the debug mode. func (c *Core) SetDebug(debug bool) { - if c.debug.Val() == debug { - return - } c.debug.Set(debug) } @@ -178,6 +174,16 @@ func (c *Core) GetPrefix() string { return c.prefix } +// SetDryRun enables/disables the DryRun feature. +func (c *Core) SetDryRun(dryrun bool) { + c.dryrun.Set(dryrun) +} + +// GetDryRun returns the DryRun value. +func (c *Core) GetDryRun() bool { + return c.dryrun.Val() +} + // 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. diff --git a/database/gdb/gdb_driver_mysql.go b/database/gdb/gdb_driver_mysql.go index 2e781fd86..02f81660b 100644 --- a/database/gdb/gdb_driver_mysql.go +++ b/database/gdb/gdb_driver_mysql.go @@ -10,6 +10,7 @@ import ( "database/sql" "fmt" "github.com/gogf/gf/internal/intlog" + "github.com/gogf/gf/text/gregex" "github.com/gogf/gf/text/gstr" _ "github.com/go-sql-driver/mysql" @@ -33,6 +34,10 @@ func (d *DriverMysql) Open(config *ConfigNode) (*sql.DB, error) { var source string if config.LinkInfo != "" { source = config.LinkInfo + // Custom changing the schema in runtime. + if config.Name != "" { + source, _ = gregex.ReplaceString(`/([\w\.\-]+)+`, "/"+config.Name, source) + } } else { source = fmt.Sprintf( "%s:%s@tcp(%s:%s)/%s?charset=%s&multiStatements=true&parseTime=true&loc=Local", diff --git a/database/gdb/gdb_result.go b/database/gdb/gdb_result.go index 2ebfc55fe..4a1dd7d77 100644 --- a/database/gdb/gdb_result.go +++ b/database/gdb/gdb_result.go @@ -38,10 +38,16 @@ func (r *SqlResult) RowsAffected() (int64, error) { if r.affected > 0 { return r.affected, nil } + if r.result == nil { + return 0, nil + } return r.result.RowsAffected() } // see sql.Result.LastInsertId func (r *SqlResult) LastInsertId() (int64, error) { + if r.result == nil { + return 0, nil + } return r.result.LastInsertId() } diff --git a/database/gdb/gdb_type_result.go b/database/gdb/gdb_type_result.go index b73711a87..cfbeff838 100644 --- a/database/gdb/gdb_type_result.go +++ b/database/gdb/gdb_type_result.go @@ -9,11 +9,33 @@ package gdb import ( "database/sql" "fmt" + "math" "reflect" "github.com/gogf/gf/encoding/gparser" ) +// Chunk splits an Result into multiple Results, +// the size of each array is determined by . +// The last chunk may contain less than size elements. +func (r Result) Chunk(size int) []Result { + if size < 1 { + return nil + } + length := len(r) + chunks := int(math.Ceil(float64(length) / float64(size))) + var n []Result + for i, end := 0, 0; chunks > 0; chunks-- { + end = (i + 1) * size + if end > length { + end = length + } + n = append(n, r[i*size:end]) + i++ + } + return n +} + // Json converts to JSON format content. func (r Result) Json() string { content, _ := gparser.VarToJson(r.List()) diff --git a/database/gdb/gdb_unit_z_mysql_model_test.go b/database/gdb/gdb_unit_z_mysql_model_test.go index 0e478e2c7..7f2032acf 100644 --- a/database/gdb/gdb_unit_z_mysql_model_test.go +++ b/database/gdb/gdb_unit_z_mysql_model_test.go @@ -2123,3 +2123,38 @@ func Test_Model_OmitEmpty_Time(t *testing.T) { t.Assert(n, 1) }) } + +func Test_Result_Chunk(t *testing.T) { + table := createInitTable() + defer dropTable(table) + gtest.C(t, func(t *gtest.T) { + r, err := db.Table(table).Order("id asc").All() + t.Assert(err, nil) + chunks := r.Chunk(3) + t.Assert(len(chunks), 4) + t.Assert(chunks[0][0]["id"].Int(), 1) + t.Assert(chunks[1][0]["id"].Int(), 4) + t.Assert(chunks[2][0]["id"].Int(), 7) + t.Assert(chunks[3][0]["id"].Int(), 10) + }) +} + +func Test_Model_DryRun(t *testing.T) { + table := createInitTable() + defer dropTable(table) + db.SetDryRun(true) + defer db.SetDryRun(false) + + gtest.C(t, func(t *gtest.T) { + one, err := db.Table(table).FindOne(1) + t.Assert(err, nil) + t.Assert(one["id"], 1) + }) + gtest.C(t, func(t *gtest.T) { + r, err := db.Table(table).Data("passport", "port_1").WherePri(1).Update() + t.Assert(err, nil) + n, err := r.RowsAffected() + t.Assert(err, nil) + t.Assert(n, 0) + }) +} diff --git a/net/ghttp/ghttp_client_request.go b/net/ghttp/ghttp_client_request.go index 15a58eeaf..f3f793ff2 100644 --- a/net/ghttp/ghttp_client_request.go +++ b/net/ghttp/ghttp_client_request.go @@ -189,15 +189,9 @@ func (c *Client) DoRequest(method, url string, data ...interface{}) (resp *Clien req.Header.Set(k, v) } } - // For server requests Host specifies the host on which the - // URL is sought. Per RFC 2616, this is either the value of - // the "Host" header or the host name given in the URL itself. - // It may be of the form "host:port". - // - // For client requests Host optionally overrides the Host - // header to send. If empty, the Request.Write method uses - // the value of URL.Host. - if host := req.Header.Get("Host"); host != "" { + // It's necessary set the req.Host if you want to custom the host value of the request. + // It uses the "Host" value of the header. + if host := req.Header.Get("Host"); host != "" && req.Host == "" { req.Host = host } // Custom Cookie. diff --git a/net/ghttp/ghttp_response_write.go b/net/ghttp/ghttp_response_write.go index 9e8db6b1a..9c754c089 100644 --- a/net/ghttp/ghttp_response_write.go +++ b/net/ghttp/ghttp_response_write.go @@ -107,7 +107,7 @@ func (r *Response) WriteJson(content interface{}) error { switch content.(type) { case string, []byte: r.Header().Set("Content-Type", "application/json") - r.Write(content) + r.Write(gconv.String(content)) return nil } // Else use json.Marshal function to encode the parameter. @@ -139,7 +139,7 @@ func (r *Response) WriteJsonP(content interface{}) error { switch content.(type) { case string, []byte: r.Header().Set("Content-Type", "application/json") - r.Write(content) + r.Write(gconv.String(content)) return nil } // Else use json.Marshal function to encode the parameter. @@ -179,7 +179,7 @@ func (r *Response) WriteXml(content interface{}, rootTag ...string) error { switch content.(type) { case string, []byte: r.Header().Set("Content-Type", "application/xml") - r.Write(content) + r.Write(gconv.String(content)) return nil } // Else use gparser.VarToXml function to encode the parameter. diff --git a/os/genv/genv.go b/os/genv/genv.go index 8ab463420..e8b2c183e 100644 --- a/os/genv/genv.go +++ b/os/genv/genv.go @@ -7,7 +7,10 @@ // Package genv provides operations for environment variables of system. package genv -import "os" +import ( + "github.com/gogf/gf/container/gvar" + "os" +) import "strings" // All returns a copy of strings representing the environment, @@ -37,6 +40,17 @@ func Get(key string, def ...string) string { return v } +// GetVar creates and returns a Var with the value of the environment variable +// named by the . It uses the given if the variable does not exist +// in the environment. +func GetVar(key string, def ...interface{}) *gvar.Var { + v, ok := os.LookupEnv(key) + if !ok && len(def) > 0 { + return gvar.New(def[0]) + } + return gvar.New(v) +} + // Set sets the value of the environment variable named by the . // It returns an error, if any. func Set(key, value string) error {