From 8978112433baec98ee503e319bb7d44c7d4ab74a Mon Sep 17 00:00:00 2001 From: John Guo Date: Wed, 9 Dec 2020 01:29:23 +0800 Subject: [PATCH] add gdb.Raw for raw sql instead of prepare argument feature --- .example/database/gdb/mysql/gdb_insert.go | 9 ++- database/gdb/gdb.go | 35 +++++----- database/gdb/gdb_core.go | 53 +++++++++------ database/gdb/gdb_func.go | 12 +++- database/gdb/gdb_z_driver_test.go | 2 +- database/gdb/gdb_z_mysql_raw_test.go | 80 +++++++++++++++++++++++ 6 files changed, 144 insertions(+), 47 deletions(-) create mode 100644 database/gdb/gdb_z_mysql_raw_test.go diff --git a/.example/database/gdb/mysql/gdb_insert.go b/.example/database/gdb/mysql/gdb_insert.go index 509e30106..ee345b370 100644 --- a/.example/database/gdb/mysql/gdb_insert.go +++ b/.example/database/gdb/mysql/gdb_insert.go @@ -3,7 +3,7 @@ package main import ( "fmt" "github.com/gogf/gf/database/gdb" - "time" + "github.com/gogf/gf/frame/g" ) func main() { @@ -18,10 +18,9 @@ func main() { db.SetDebug(true) - type User struct { - CreateTime time.Time `orm:"create_time"` - } - r, e := db.Table("user").Data(User{CreateTime: time.Now()}).Insert() + r, e := db.Table("user").Data(g.Map{ + "create_at": "now()", + }).Unscoped().Insert() if e != nil { panic(e) } diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index d6890a154..e940aef03 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -225,21 +225,12 @@ type Counter struct { } type ( - // Value is the field value type. - Value = *gvar.Var - - // Record is the row record of the table. - Record map[string]Value - - // Result is the row record array. - Result []Record - - // Map is alias of map[string]interface{}, - // which is the most common usage map type. - Map = map[string]interface{} - - // List is type of map array. - List = []Map + Raw string // Raw is a raw sql that will not be treated as argument but as a direct sql part. + Value = *gvar.Var // Value is the field value type. + Record map[string]Value // Record is the row record of the table. + Result []Record // Result is the row record array. + Map = map[string]interface{} // Map is alias of map[string]interface{}, which is the most common usage map type. + List = []Map // List is type of map array. ) const ( @@ -247,10 +238,10 @@ const ( insertOptionReplace = 1 insertOptionSave = 2 insertOptionIgnore = 3 - defaultBatchNumber = 10 // Per count for batch insert/replace/save. - defaultMaxIdleConnCount = 10 // Max idle connection count in pool. - defaultMaxOpenConnCount = 100 // Max open connection count in pool. - defaultMaxConnLifeTime = 30 // Max life time for per connection in pool in seconds. + defaultBatchNumber = 10 // Per count for batch insert/replace/save. + defaultMaxIdleConnCount = 10 // Max idle connection count in pool. + defaultMaxOpenConnCount = 100 // Max open connection count in pool. + defaultMaxConnLifeTime = 30 * time.Second // Max life time for per connection in pool in seconds. ) var ( @@ -459,9 +450,13 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error } if c.config.MaxIdleConnCount > 0 { sqlDb.SetMaxIdleConns(c.config.MaxIdleConnCount) + } else { + sqlDb.SetMaxIdleConns(defaultMaxIdleConnCount) } if c.config.MaxOpenConnCount > 0 { sqlDb.SetMaxOpenConns(c.config.MaxOpenConnCount) + } else { + sqlDb.SetMaxOpenConns(defaultMaxOpenConnCount) } if c.config.MaxConnLifetime > 0 { // Automatically checks whether MaxConnLifetime is configured using string like: "30s", "60s", etc. @@ -471,6 +466,8 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error } else { sqlDb.SetConnMaxLifetime(c.config.MaxConnLifetime * time.Second) } + } else { + sqlDb.SetConnMaxLifetime(defaultMaxConnLifeTime) } return sqlDb, nil }, 0) diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index 27bab7409..c74f6e277 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -479,8 +479,12 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b ) for k, v := range dataMap { fields = append(fields, charL+k+charR) - values = append(values, "?") - params = append(params, v) + if s, ok := v.(Raw); ok { + values = append(values, gconv.String(s)) + } else { + values = append(values, "?") + params = append(params, v) + } } if option == insertOptionSave { for k, _ := range dataMap { @@ -615,19 +619,16 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i } } // Handle the field names and place holders. - holders := []string(nil) for k, _ := range listMap[0] { keys = append(keys, k) - holders = append(holders, "?") } // Prepare the batch result pointer. var ( - charL, charR = c.DB.GetChars() - batchResult = new(SqlResult) - keysStr = charL + strings.Join(keys, charR+","+charL) + charR - valueHolderStr = "(" + strings.Join(holders, ",") + ")" - operation = GetInsertOperationByOption(option) - updateStr = "" + charL, charR = c.DB.GetChars() + batchResult = new(SqlResult) + keysStr = charL + strings.Join(keys, charR+","+charL) + charR + operation = GetInsertOperationByOption(option) + updateStr = "" ) if option == insertOptionSave { for _, k := range keys { @@ -651,23 +652,30 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i if len(batch) > 0 && batch[0] > 0 { batchNum = batch[0] } - listMapLen := len(listMap) + var ( + listMapLen = len(listMap) + valueHolder = make([]string, 0) + ) for i := 0; i < listMapLen; i++ { + values = values[:0] // Note that the map type is unordered, // so it should use slice+key to retrieve the value. for _, k := range keys { - params = append(params, listMap[i][k]) + if s, ok := listMap[i][k].(Raw); ok { + values = append(values, gconv.String(s)) + } else { + values = append(values, "?") + params = append(params, listMap[i][k]) + } } - values = append(values, valueHolderStr) + valueHolder = append(valueHolder, "("+gstr.Join(values, ",")+")") if len(values) == batchNum || (i == listMapLen-1 && len(values) > 0) { r, err := c.DB.DoExec( link, fmt.Sprintf( "%s INTO %s(%s) VALUES%s %s", - operation, - table, - keysStr, - strings.Join(values, ","), + operation, table, keysStr, + gstr.Join(valueHolder, ","), updateStr, ), params..., @@ -682,7 +690,7 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i batchResult.affected += n } params = params[:0] - values = values[:0] + valueHolder = valueHolder[:0] } } return batchResult, nil @@ -743,8 +751,13 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str params = append(params, value.Value) } default: - fields = append(fields, c.DB.QuoteWord(k)+"=?") - params = append(params, v) + if s, ok := v.(Raw); ok { + fields = append(fields, c.DB.QuoteWord(k)+"="+gconv.String(s)) + } else { + fields = append(fields, c.DB.QuoteWord(k)+"=?") + params = append(params, v) + } + } } updates = strings.Join(fields, ",") diff --git a/database/gdb/gdb_func.go b/database/gdb/gdb_func.go index 6d5ca0c83..5e9afb5f5 100644 --- a/database/gdb/gdb_func.go +++ b/database/gdb/gdb_func.go @@ -501,7 +501,11 @@ func formatWhereInterfaces(db DB, where []interface{}, buffer *bytes.Buffer, new } else { buffer.WriteString(db.QuoteWord(str) + "=?") } - newArgs = append(newArgs, where[i+1]) + if s, ok := where[i+1].(Raw); ok { + buffer.WriteString(gconv.String(s)) + } else { + newArgs = append(newArgs, where[i+1]) + } } return newArgs } @@ -569,7 +573,11 @@ func formatWhereKeyValue(db DB, buffer *bytes.Buffer, newArgs []interface{}, key } else { buffer.WriteString(quotedKey) } - newArgs = append(newArgs, value) + if s, ok := value.(Raw); ok { + buffer.WriteString(gconv.String(s)) + } else { + newArgs = append(newArgs, value) + } } } return newArgs diff --git a/database/gdb/gdb_z_driver_test.go b/database/gdb/gdb_z_driver_test.go index a0097c988..6f0e59602 100644 --- a/database/gdb/gdb_z_driver_test.go +++ b/database/gdb/gdb_z_driver_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame 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, diff --git a/database/gdb/gdb_z_mysql_raw_test.go b/database/gdb/gdb_z_mysql_raw_test.go new file mode 100644 index 000000000..f7a7b0f2d --- /dev/null +++ b/database/gdb/gdb_z_mysql_raw_test.go @@ -0,0 +1,80 @@ +// Copyright GoFrame 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 gdb_test + +import ( + "github.com/gogf/gf/frame/g" + "testing" + + "github.com/gogf/gf/database/gdb" + "github.com/gogf/gf/test/gtest" +) + +func Test_Insert_Raw(t *testing.T) { + table := createTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + user := db.Table(table) + result, err := user.Filter().Data(g.Map{ + "id": gdb.Raw("id+2"), + "passport": "port_1", + "password": "pass_1", + "nickname": "name_1", + "create_time": gdb.Raw("now()"), + }).Insert() + t.Assert(err, nil) + n, _ := result.LastInsertId() + t.Assert(n, 2) + }) +} + +func Test_BatchInsert_Raw(t *testing.T) { + table := createTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + user := db.Table(table) + result, err := user.Filter().Data( + g.List{ + g.Map{ + "id": gdb.Raw("id+2"), + "passport": "port_2", + "password": "pass_2", + "nickname": "name_2", + "create_time": gdb.Raw("now()"), + }, + g.Map{ + "id": gdb.Raw("id+4"), + "passport": "port_4", + "password": "pass_4", + "nickname": "name_4", + "create_time": gdb.Raw("now()"), + }, + }, + ).Insert() + t.Assert(err, nil) + n, _ := result.LastInsertId() + t.Assert(n, 4) + }) +} + +func Test_Update_Raw(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + user := db.Table(table) + result, err := user.Data(g.Map{ + "id": gdb.Raw("id+100"), + "create_time": gdb.Raw("now()"), + }).Where("id", 1).Insert() + t.Assert(err, nil) + n, _ := result.RowsAffected() + t.Assert(n, 1) + }) +}