fix issue in gdb.Model for repeated condition statements; remove concurrent safety feature of gview; add default template file feature for gview

This commit is contained in:
John 2019-11-19 21:50:17 +08:00
parent 43531c2680
commit a4f191c1c6
5 changed files with 113 additions and 58 deletions

View File

@ -31,7 +31,6 @@ type Model struct {
tables string // 数据库操作表
fields string // 操作字段
fieldsEx string // 操作字段(排除)
where string // 操作条件
whereArgs []interface{} // 操作条件参数
whereHolder []*whereHolder // 操作条件预处理
groupBy string // 分组语句
@ -550,12 +549,13 @@ func (md *Model) Update() (result sql.Result, err error) {
if md.data == nil {
return nil, errors.New("updating table with empty data")
}
condition, conditionArgs := md.formatCondition()
return md.db.doUpdate(
md.getLink(),
md.tables,
md.filterDataForInsertOrUpdate(md.data),
md.getConditionSql(),
md.whereArgs...,
condition,
conditionArgs...,
)
}
@ -566,7 +566,8 @@ func (md *Model) Delete() (result sql.Result, err error) {
md.checkAndRemoveCache()
}
}()
return md.db.doDelete(md.getLink(), md.tables, md.getConditionSql(), md.whereArgs...)
condition, conditionArgs := md.formatCondition()
return md.db.doDelete(md.getLink(), md.tables, condition, conditionArgs...)
}
// 链式操作select
@ -576,7 +577,8 @@ func (md *Model) Select() (Result, error) {
// 链式操作,查询所有记录
func (md *Model) All() (Result, error) {
return md.getAll(fmt.Sprintf("SELECT %s FROM %s%s", md.fields, md.tables, md.getConditionSql()), md.whereArgs...)
condition, conditionArgs := md.formatCondition()
return md.getAll(fmt.Sprintf("SELECT %s FROM %s%s", md.fields, md.tables, condition), conditionArgs...)
}
// 链式操作,查询单条记录
@ -651,11 +653,12 @@ func (md *Model) Count() (int, error) {
} else {
md.fields = fmt.Sprintf(`COUNT(%s)`, md.fields)
}
s := fmt.Sprintf("SELECT %s FROM %s %s", md.fields, md.tables, md.getConditionSql())
condition, conditionArgs := md.formatCondition()
s := fmt.Sprintf("SELECT %s FROM %s %s", md.fields, md.tables, condition)
if len(md.groupBy) > 0 {
s = fmt.Sprintf("SELECT COUNT(1) FROM (%s) count_alias", s)
}
list, err := md.getAll(s, md.whereArgs...)
list, err := md.getAll(s, conditionArgs...)
if err != nil {
return 0, err
}
@ -716,16 +719,17 @@ func (md *Model) checkAndRemoveCache() {
}
// 格式化当前输入参数返回SQL条件语句不带参数
func (md *Model) getConditionSql() string {
func (md *Model) formatCondition() (condition string, conditionArgs []interface{}) {
var where string
if len(md.whereHolder) > 0 {
for _, v := range md.whereHolder {
switch v.operator {
case gWHERE_HOLDER_WHERE:
if md.where == "" {
if where == "" {
newWhere, newArgs := formatWhere(md.db, v.where, v.args, md.option&OPTION_OMITEMPTY > 0)
if len(newWhere) > 0 {
md.where = newWhere
md.whereArgs = newArgs
where = newWhere
conditionArgs = newArgs
}
continue
}
@ -734,48 +738,47 @@ func (md *Model) getConditionSql() string {
case gWHERE_HOLDER_AND:
newWhere, newArgs := formatWhere(md.db, v.where, v.args, md.option&OPTION_OMITEMPTY > 0)
if len(newWhere) > 0 {
if md.where[0] == '(' {
md.where = fmt.Sprintf(`%s AND (%s)`, md.where, newWhere)
if where[0] == '(' {
where = fmt.Sprintf(`%s AND (%s)`, where, newWhere)
} else {
md.where = fmt.Sprintf(`(%s) AND (%s)`, md.where, newWhere)
where = fmt.Sprintf(`(%s) AND (%s)`, where, newWhere)
}
md.whereArgs = append(md.whereArgs, newArgs...)
conditionArgs = append(conditionArgs, newArgs...)
}
case gWHERE_HOLDER_OR:
newWhere, newArgs := formatWhere(md.db, v.where, v.args, md.option&OPTION_OMITEMPTY > 0)
if len(newWhere) > 0 {
if md.where[0] == '(' {
md.where = fmt.Sprintf(`%s OR (%s)`, md.where, newWhere)
if where[0] == '(' {
where = fmt.Sprintf(`%s OR (%s)`, where, newWhere)
} else {
md.where = fmt.Sprintf(`(%s) OR (%s)`, md.where, newWhere)
where = fmt.Sprintf(`(%s) OR (%s)`, where, newWhere)
}
md.whereArgs = append(md.whereArgs, newArgs...)
conditionArgs = append(conditionArgs, newArgs...)
}
}
}
}
s := ""
if md.where != "" {
s += " WHERE " + md.where
if where != "" {
condition += " WHERE " + where
}
if md.groupBy != "" {
s += " GROUP BY " + md.groupBy
condition += " GROUP BY " + md.groupBy
}
if md.orderBy != "" {
s += " ORDER BY " + md.orderBy
condition += " ORDER BY " + md.orderBy
}
if md.limit != 0 {
if md.start >= 0 {
s += fmt.Sprintf(" LIMIT %d,%d", md.start, md.limit)
condition += fmt.Sprintf(" LIMIT %d,%d", md.start, md.limit)
} else {
s += fmt.Sprintf(" LIMIT %d", md.limit)
condition += fmt.Sprintf(" LIMIT %d", md.limit)
}
}
if md.offset >= 0 {
s += fmt.Sprintf(" OFFSET %d", md.offset)
condition += fmt.Sprintf(" OFFSET %d", md.offset)
}
return s
return
}
// 组块结果集。

View File

@ -298,6 +298,57 @@ func Test_Model_Safe(t *testing.T) {
gtest.Assert(err, nil)
gtest.Assert(count, 2)
})
gtest.Case(t, func() {
md1 := db.Table(table).Safe()
md2 := md1.Where("id in (?)", g.Slice{1, 3})
count, err := md2.Count()
gtest.Assert(err, nil)
gtest.Assert(count, 2)
all, err := md2.All()
gtest.Assert(err, nil)
gtest.Assert(len(all), 2)
all, err = md2.ForPage(1, 10).All()
gtest.Assert(err, nil)
gtest.Assert(len(all), 2)
})
gtest.Case(t, func() {
md1 := db.Table(table).Where("id>", 0).Safe()
md2 := md1.Where("id in (?)", g.Slice{1, 3})
md3 := md1.Where("id in (?)", g.Slice{4, 5, 6})
// 1,3
count, err := md2.Count()
gtest.Assert(err, nil)
gtest.Assert(count, 2)
all, err := md2.OrderBy("id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(all), 2)
gtest.Assert(all[0]["id"].Int(), 1)
gtest.Assert(all[1]["id"].Int(), 3)
all, err = md2.ForPage(1, 10).All()
gtest.Assert(err, nil)
gtest.Assert(len(all), 2)
// 4,5,6
count, err = md3.Count()
gtest.Assert(err, nil)
gtest.Assert(count, 3)
all, err = md3.OrderBy("id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(all), 3)
gtest.Assert(all[0]["id"].Int(), 4)
gtest.Assert(all[1]["id"].Int(), 5)
gtest.Assert(all[2]["id"].Int(), 6)
all, err = md3.ForPage(1, 10).All()
gtest.Assert(err, nil)
gtest.Assert(len(all), 3)
})
}
func Test_Model_All(t *testing.T) {

View File

@ -14,10 +14,8 @@ import (
"errors"
"fmt"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/internal/intlog"
"sync"
"github.com/gogf/gf/i18n/gi18n"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/os/gres"
@ -31,11 +29,11 @@ import (
// View object for template engine.
type View struct {
mu sync.RWMutex
paths *garray.StrArray // Searching path array, NOT concurrent safe for performance purpose.
data map[string]interface{} // Global template variables.
funcMap map[string]interface{} // Global template function map.
fileCacheMap *gmap.StrAnyMap // File cache map.
defaultFile string // Default template file for parsing.
i18nManager *gi18n.Manager // I18n manager for this view.
delimiters []string // Customized template delimiters.
}
@ -46,8 +44,15 @@ type Params = map[string]interface{}
// FuncMap is type for custom template functions.
type FuncMap = map[string]interface{}
// Default view object.
var defaultViewObj *View
const (
// Default template file for parsing.
defaultParsingFile = "index.html"
)
var (
// Default view object.
defaultViewObj *View
)
// checkAndInitDefaultView checks and initializes the default view object.
// The default view object will be initialized just once.
@ -72,6 +77,7 @@ func New(path ...string) *View {
data: make(map[string]interface{}),
funcMap: make(map[string]interface{}),
fileCacheMap: gmap.NewStrAnyMap(true),
defaultFile: defaultParsingFile,
i18nManager: gi18n.Instance(),
delimiters: make([]string, 2),
}

View File

@ -8,55 +8,50 @@ package gview
import "github.com/gogf/gf/i18n/gi18n"
// Assign binds multiple template variables to current view object.
// Each goroutine will take effect after the call, so it is concurrent-safe.
// Assign binds multiple global template variables to current view object.
// Note that it's not concurrent-safe, which means it would panic
// if it's called in multiple goroutines in runtime.
func (view *View) Assigns(data Params) {
view.mu.Lock()
for k, v := range data {
view.data[k] = v
}
view.mu.Unlock()
}
// Assign binds a template variable to current view object.
// Each goroutine will take effect after the call, so it is concurrent-safe.
// Assign binds a global template variable to current view object.
// Note that it's not concurrent-safe, which means it would panic
// if it's called in multiple goroutines in runtime.
func (view *View) Assign(key string, value interface{}) {
view.mu.Lock()
view.data[key] = value
view.mu.Unlock()
}
// SetDefaultFile sets default template file for parsing.
func (view *View) SetDefaultFile(file string) {
view.defaultFile = file
}
// SetDelimiters sets customized delimiters for template parsing.
func (view *View) SetDelimiters(left, right string) {
view.mu.Lock()
view.delimiters[0] = left
view.delimiters[1] = right
view.mu.Unlock()
}
// BindFunc registers customized template function named <name>
// BindFunc registers customized global template function named <name>
// with given function <function> to current view object.
// The <name> is the function name which can be called in template content.
func (view *View) BindFunc(name string, function interface{}) {
view.mu.Lock()
view.funcMap[name] = function
view.mu.Unlock()
}
// BindFuncMap registers customized template functions by map to current view object.
// BindFuncMap registers customized global template functions by map to current view object.
// The key of map is the template function name
// and the value of map is the address of customized function.
func (view *View) BindFuncMap(funcMap FuncMap) {
view.mu.Lock()
for k, v := range funcMap {
view.funcMap[k] = v
}
view.mu.Unlock()
}
// SetI18n binds i18n manager to view engine.
// SetI18n binds i18n manager to current view engine.
func (view *View) SetI18n(manager *gi18n.Manager) {
view.mu.Lock()
view.i18nManager = manager
view.mu.Unlock()
}

View File

@ -52,9 +52,6 @@ type fileCacheItem struct {
// with given template parameters <params> and function map <funcMap>
// and returns the parsed string content.
func (view *View) Parse(file string, params ...Params) (result string, err error) {
view.mu.RLock()
defer view.mu.RUnlock()
var tpl *template.Template
// It caches the file, folder and its content to enhance performance.
r := view.fileCacheMap.GetOrSetFuncLock(file, func() interface{} {
@ -141,12 +138,15 @@ func (view *View) Parse(file string, params ...Params) (result string, err error
return result, nil
}
// ParseDefault parses the default template file with params.
func (view *View) ParseDefault(params ...Params) (result string, err error) {
return view.Parse(view.defaultFile, params...)
}
// ParseContent parses given template content <content>
// with given template parameters <params> and function map <funcMap>
// and returns the parsed content in []byte.
func (view *View) ParseContent(content string, params ...Params) (string, error) {
view.mu.RLock()
defer view.mu.RUnlock()
err := (error)(nil)
tpl := templates.GetOrSetFuncLock(gCONTENT_TEMPLATE_NAME, func() interface{} {
return template.New(gCONTENT_TEMPLATE_NAME).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcMap)