gdb.Model updates, rename Alterable function to Safe, change gdb.Model alterable in default, update Where function to support more cases when using map as its param, add more unit test cases for gdb.Model

This commit is contained in:
John
2019-03-11 16:14:55 +08:00
parent 8a3365d18e
commit 5db039bbce
7 changed files with 196 additions and 57 deletions

View File

@ -31,41 +31,61 @@ func formatCondition(where interface{}, args []interface{}) (newWhere string, ne
rv = rv.Elem()
kind = rv.Kind()
}
tmpArgs := []interface{}(nil)
switch kind {
// 注意当where为map/struct类型args参数必须为空。
// map/struct类型
case reflect.Map: fallthrough
case reflect.Struct:
for k, v := range gconv.Map(where) {
for key, value := range gconv.Map(where) {
if buffer.Len() > 0 {
buffer.WriteString(" AND ")
}
// 支持slice键值/属性,这个时候作为IN查询
switch reflect.ValueOf(v).Kind() {
// 支持slice键值/属性,如果只有一个?占位符号那么作为IN查询否则打散作为多个查询参数
rv := reflect.ValueOf(value)
switch rv.Kind() {
case reflect.Slice: fallthrough
case reflect.Array:
buffer.WriteString(k + " IN(?)")
default:
if gstr.Pos(k, "<") == -1 && gstr.Pos(k, ">") == -1 && gstr.Pos(k, "=") == -1 {
buffer.WriteString(k + "=?")
count := gstr.Count(key, "?")
if count == 0 {
buffer.WriteString(key + " IN(?)")
tmpArgs = append(tmpArgs, value)
} else if count != rv.Len() {
buffer.WriteString(key)
tmpArgs = append(tmpArgs, value)
} else {
buffer.WriteString(k + "?")
buffer.WriteString(key)
// 如果键名/属性名称中带有多个?占位符号,那么将参数打散
tmpArgs = append(tmpArgs, gconv.Interfaces(value)...)
}
default:
if value == nil {
buffer.WriteString(key)
} else {
if gstr.Pos(key, "?") == -1 {
if gstr.Pos(key, "<") == -1 && gstr.Pos(key, ">") == -1 && gstr.Pos(key, "=") == -1 {
buffer.WriteString(key + "=?")
} else {
buffer.WriteString(key + "?")
}
} else {
buffer.WriteString(key)
}
tmpArgs = append(tmpArgs, value)
}
}
// 当给定的Where参数为map/struct时args参数必定为空
// 考虑到后续还会对args做处理特别是判断slice类型这里直接给args赋值。
args = append(args, v)
}
newWhere = buffer.String()
default:
buffer.WriteString(gconv.String(where))
}
if buffer.Len() == 0 {
buffer.WriteString("1=1")
}
// 查询条件参数处理主要处理slice参数类型
newWhere = buffer.String()
if len(args) > 0 {
for index, arg := range args {
tmpArgs = append(tmpArgs, args...)
// 查询条件参数处理主要处理slice参数类型
if len(tmpArgs) > 0 {
for index, arg := range tmpArgs {
rv := reflect.ValueOf(arg)
kind := rv.Kind()
if kind == reflect.Ptr {
@ -90,6 +110,14 @@ func formatCondition(where interface{}, args []interface{}) (newWhere string, ne
return s
})
default:
// 支持例如 Where/And/Or("uid", 1) 这种格式
if gstr.Pos(newWhere, "?") == -1 {
if gstr.Pos(newWhere, "<") == -1 && gstr.Pos(newWhere, ">") == -1 && gstr.Pos(newWhere, "=") == -1 {
newWhere += "=?"
} else {
newWhere += "?"
}
}
newArgs = append(newArgs, arg)
}
}

View File

@ -3,17 +3,18 @@
// 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.
//
// @author john, ymrjqyy
package gdb
import (
"fmt"
"errors"
"database/sql"
"github.com/gogf/gf/g/util/gconv"
_ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql"
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/g/util/gconv"
_ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql"
"reflect"
"strings"
)
// 数据库链式操作模型对象
@ -35,7 +36,7 @@ type Model struct {
cacheEnabled bool // 当前SQL操作是否开启查询缓存功能
cacheTime int // 查询缓存时间
cacheName string // 查询缓存名称
alterable bool // 当前模型是否运行可修改模式(默认情况下链式操作不会修改当前模型,而是创建新的模型返回
safe bool // 当前模型是否运行安全模式(可修改当前模型,否则每一次链式操作都是返回新的模型对象
}
// 链式操作,数据表字段,可支持多个表,以半角逗号连接
@ -45,6 +46,7 @@ func (bs *dbBase) Table(tables string) (*Model) {
tablesInit : tables,
tables : tables,
fields : "*",
safe : false,
}
}
@ -60,6 +62,7 @@ func (tx *TX) Table(tables string) (*Model) {
tx : tx,
tablesInit : tables,
tables : tables,
safe : false,
}
}
@ -80,21 +83,25 @@ func (md *Model) Clone() *Model {
return newModel
}
// 标识当前对象可被修改。
// 标识当前对象运行安全模式(可被修改)
// 1. 默认情况下,模型对象的对象属性无法被修改,
// 每一次链式操作都是克隆一个新的模型对象,这样所有的操作都不会污染模型对象。
// 但是链式操作如果需要分开执行,那么需要将新的克隆对象赋值给旧的模型对象继续操作。
// 2. 当标识模型对象为可修改,那么在当前模型对象的所有链式操作均会影响下一次的链式操作,
// 即使是链式操作分开执行。
// 3. 大部分ORM框架默认模型对象是可修改的但是GF框架的ORM提供给开发者更灵活更安全的链式操作选项。
func (md *Model) Alterable() *Model {
md.alterable = true
func (md *Model) Safe(safe...bool) *Model {
if len(safe) > 0 {
md.safe = safe[0]
} else {
md.safe = true
}
return md
}
// 返回操作的模型对象可能是当前对象也可能是新的克隆对象根据alterable决定。
func (md *Model) getModel() *Model {
if md.alterable {
if !md.safe {
return md
} else {
return md.Clone()
@ -137,16 +144,15 @@ func (md *Model) Filter() (*Model) {
}
// 链式操作condition支持string & gdb.Map.
// 注意多个Where调用时相互覆盖只有最后一个Where语句生效
// 注意多个Where调用时自动转换为And条件调用
func (md *Model) Where(where interface{}, args ...interface{}) (*Model) {
model := md.getModel()
model := md.getModel()
if model.where != "" {
return md.And(where, args...)
}
newWhere, newArgs := formatCondition(where, args)
model.where = newWhere
model.whereArgs = newArgs
// 支持 Where("uid", 1)这种格式
if len(args) == 1 && strings.Index(model.where , "?") < 0 {
model.where += "=?"
}
return model
}
@ -154,8 +160,12 @@ func (md *Model) Where(where interface{}, args ...interface{}) (*Model) {
func (md *Model) And(where interface{}, args ...interface{}) (*Model) {
model := md.getModel()
newWhere, newArgs := formatCondition(where, args)
model.where += " AND " + newWhere
model.whereArgs = append(model.whereArgs, newArgs...)
if len(model.where) > 0 && model.where[0] == '(' {
model.where = fmt.Sprintf(`%s AND (%s)`, model.where, newWhere)
} else {
model.where = fmt.Sprintf(`(%s) AND (%s)`, model.where, newWhere)
}
model.whereArgs = append(model.whereArgs, newArgs...)
return model
}
@ -163,8 +173,12 @@ func (md *Model) And(where interface{}, args ...interface{}) (*Model) {
func (md *Model) Or(where interface{}, args ...interface{}) (*Model) {
model := md.getModel()
newWhere, newArgs := formatCondition(where, args)
model.where += " OR " + newWhere
model.whereArgs = append(model.whereArgs, newArgs...)
if len(model.where) > 0 && model.where[0] == '(' {
model.where = fmt.Sprintf(`%s OR (%s)`, model.where, newWhere)
} else {
model.where = fmt.Sprintf(`(%s) OR (%s)`, model.where, newWhere)
}
model.whereArgs = append(model.whereArgs, newArgs...)
return model
}
@ -190,8 +204,7 @@ func (md *Model) Limit(start int, limit int) (*Model) {
return model
}
// 链式操作,翻页
// @author ymrjqyy
// 链式操作,翻页注意分页页码从1开始而Limit方法从0开始。
func (md *Model) ForPage(page, limit int) (*Model) {
model := md.getModel()
model.start = (page - 1) * limit
@ -586,14 +599,13 @@ func (md *Model) getFormattedSql() string {
return s
}
// 组块结果集
// @author ymrjqyy
// @author 2018-08-15
// 组块结果集
func (md *Model) Chunk(limit int, callback func(result Result, err error) bool) {
page := 1
page := 1
model := md
for {
md.ForPage(page, limit)
data, err := md.getAll(md.getFormattedSql(), md.whereArgs...)
model = model.ForPage(page, limit)
data, err := model.All()
if err != nil {
callback(nil, err)
break

View File

@ -175,9 +175,9 @@ func TestModel_Clone(t *testing.T) {
gtest.Assert(result[1]["id"].Int(), 3)
}
func TestModel_Alterable(t *testing.T) {
func TestModel_Safe(t *testing.T) {
gtest.Case(t, func() {
md := db.Table("user").Alterable().Where("id IN(?)", g.Slice{1,3})
md := db.Table("user").Safe(false).Where("id IN(?)", g.Slice{1,3})
count, err := md.Count()
if err != nil {
gtest.Fatal(err)
@ -190,6 +190,20 @@ func TestModel_Alterable(t *testing.T) {
}
gtest.Assert(count, 1)
})
gtest.Case(t, func() {
md := db.Table("user").Safe(true).Where("id IN(?)", g.Slice{1,3})
count, err := md.Count()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(count, 2)
md.And("id = ?", 1)
count, err = md.Count()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(count, 2)
})
}
func TestModel_All(t *testing.T) {
@ -420,12 +434,52 @@ func TestModel_GroupBy(t *testing.T) {
func TestModel_Where(t *testing.T) {
// string
gtest.Case(t, func() {
result, err := db.Table("user").Where("id=? and nickname=?", 3, "T3").One()
result, err := db.Table("user").Where("id=? and nickname=?", 3, "T3").One()
if err != nil {
gtest.Fatal(err)
}
gtest.AssertGT(len(result), 0)
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table("user").Where("id", 3).One()
if err != nil {
gtest.Fatal(err)
}
gtest.AssertGT(len(result), 0)
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table("user").Where("id", 3).Where("nickname", "T3").One()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table("user").Where("id", 3).And("nickname", "T3").One()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table("user").Where("id", 30).Or("nickname", "T3").One()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table("user").Where("id", 30).Or("nickname", "T3").And("id>?", 1).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table("user").Where("id", 30).Or("nickname", "T3").And("id>", 1).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 3)
})
// map
gtest.Case(t, func() {
result, err := db.Table("user").Where(g.Map{"id" : 3, "nickname" : "T3"}).One()
@ -437,11 +491,39 @@ func TestModel_Where(t *testing.T) {
// map key operator
gtest.Case(t, func() {
result, err := db.Table("user").Where(g.Map{"id>" : 1, "id<" : 3}).One()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 2)
})
// complicated where 1
gtest.Case(t, func() {
//db.SetDebug(true)
conditions := g.Map{
"nickname like ?" : "%T%",
"id between ? and ?" : g.Slice{1,3},
"id > 0" : nil,
"create_time > 0" : nil,
"id" : g.Slice{1,2,3},
}
result, err := db.Table("user").Where(conditions).OrderBy("id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(result), 3)
gtest.Assert(result[0]["id"].Int(), 1)
})
// complicated where 2
gtest.Case(t, func() {
//db.SetDebug(true)
conditions := g.Map{
"nickname like ?" : "%T%",
"id between ? and ?" : g.Slice{1,3},
"id >= ?" : 1,
"create_time > ?" : 0,
"id in(?)" : g.Slice{1,2,3},
}
result, err := db.Table("user").Where(conditions).OrderBy("id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(result), 3)
gtest.Assert(result[0]["id"].Int(), 1)
})
// struct
gtest.Case(t, func() {
type User struct {

View File

@ -5,7 +5,7 @@
// You can obtain one at https://github.com/gogf/gf.
//
// HTTP Cookie管理对象
// 由于Cookie是和HTTP请求挂钩的因此被包含到 ghttp 包中进行管理
// 由于Cookie是和HTTP请求挂钩的因此被包含到 ghttp 包中进行管理
package ghttp

View File

@ -26,9 +26,9 @@ type Session struct {
request *Request // 关联的请求
}
// 生成一个唯一的SessionId字符串长度16位
// 生成一个唯一的SessionId字符串长度18位。
func makeSessionId() string {
return strings.ToUpper(strconv.FormatInt(gtime.Nanosecond(), 32) + grand.RandStr(3))
return strings.ToUpper(strconv.FormatInt(gtime.Nanosecond(), 36) + grand.RandStr(6))
}
// 获取或者生成一个session对象(延迟初始化)
@ -42,6 +42,7 @@ func GetSession(r *Request) *Session {
}
// 执行初始化(用于延迟初始化)
// @TODO 验证提交的SESSIONID合法性
func (s *Session) init() {
if len(s.id) == 0 {
s.id = s.request.Cookie.SessionId()

View File

@ -59,6 +59,20 @@ func ReplaceI(origin, search, replace string, count...int) string {
return origin
}
// Count counts the number of <substr> appears in <s>. It returns 0 if no <substr> found in <s>.
//
// 计算字符串substr在字符串s中出现的次数如果没有在s中找到substr那么返回0。
func Count(s, substr string) int {
return strings.Count(s, substr)
}
// Count counts the number of <substr> appears in <s>, case-insensitive. It returns 0 if no <substr> found in <s>.
//
// (非大小写敏感)计算字符串substr在字符串s中出现的次数如果没有在s中找到substr那么返回0。
func CountI(s, substr string) int {
return strings.Count(ToLower(s), ToLower(substr))
}
// Replace string by array/slice.
//
// 使用map进行字符串替换(大小写敏感)

View File

@ -2,10 +2,12 @@ package main
import (
"fmt"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/os/gtime"
"strconv"
"strings"
)
func main() {
t := gconv.GTime("2010-10-10 00:00:01")
fmt.Println(t.String())
//t := gconv.GTime("2010-10-10 00:00:01")
fmt.Println(strings.ToUpper(strconv.FormatInt(gtime.Nanosecond(), 36)))
}