From 9d5f834eb2e8996509c6928a02fc73c935be63f6 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 6 Apr 2014 12:58:16 +0800 Subject: [PATCH] bug fixed & add MustCols function & improved docs --- base_test.go | 57 ++++++++++++++++++++++++++++++++++++-- docs/QuickStart.md | 65 ++++++++++++++++++++++++++++++++------------ docs/QuickStartEn.md | 29 ++++++++++++++++---- engine.go | 12 ++++++++ session.go | 13 ++++++--- statement.go | 44 ++++++++++++++++++++++-------- 6 files changed, 179 insertions(+), 41 deletions(-) diff --git a/base_test.go b/base_test.go index 53d15bfc..3199b850 100644 --- a/base_test.go +++ b/base_test.go @@ -372,8 +372,8 @@ func update(engine *Engine, t *testing.T) { } type UpdateAllCols struct { - Id int64 - Bool bool + Id int64 + Bool bool String string } @@ -383,7 +383,7 @@ func update(engine *Engine, t *testing.T) { t.Error(err) panic(err) } - + _, err = engine.Insert(col1) if err != nil { t.Error(err) @@ -417,6 +417,57 @@ func update(engine *Engine, t *testing.T) { panic(err) return } + + { + type UpdateMustCols struct { + Id int64 + Bool bool + String string + } + + col1 := &UpdateMustCols{} + err = engine.Sync(col1) + if err != nil { + t.Error(err) + panic(err) + } + + _, err = engine.Insert(col1) + if err != nil { + t.Error(err) + panic(err) + } + + col2 := &UpdateMustCols{col1.Id, true, ""} + boolStr := engine.columnMapper.Obj2Table("Bool") + stringStr := engine.columnMapper.Obj2Table("String") + _, err = engine.Id(col2.Id).MustCols(boolStr, stringStr).Update(col2) + if err != nil { + t.Error(err) + panic(err) + } + + col3 := &UpdateMustCols{} + has, err := engine.Id(col2.Id).Get(col3) + if err != nil { + t.Error(err) + panic(err) + } + + if !has { + err = errors.New(fmt.Sprintf("cannot get id %d", col2.Id)) + t.Error(err) + panic(err) + return + } + + if *col2 != *col3 { + err = errors.New(fmt.Sprintf("col2 should eq col3")) + t.Error(err) + panic(err) + return + } + } } func updateSameMapper(engine *Engine, t *testing.T) { diff --git a/docs/QuickStart.md b/docs/QuickStart.md index dc2f0cd3..d787cb0b 100644 --- a/docs/QuickStart.md +++ b/docs/QuickStart.md @@ -4,7 +4,7 @@ xorm 快速入门 * [1.创建Orm引擎](#10) * [2.定义表结构体](#20) * [2.1.名称映射规则](#21) - * [2.2.前缀映射规则和后缀映射规则](#22) + * [2.2.前缀映射,后缀映射和缓存映射](#22) * [2.3.使用Table和Tag改变名称映射](#23) * [2.4.Column属性定义](#24) * [2.5.Go与字段类型对应表](#25) @@ -29,12 +29,13 @@ xorm 快速入门 * [9.执行SQL命令](#100) * [10.事务处理](#110) * [11.缓存](#120) -* [12.xorm工具](#130) - * [12.1.反转命令](#131) -* [13.Examples](#140) -* [14.案例](#150) -* [15.那些年我们踩过的坑](#160) -* [16.讨论](#170) +* [12.事件](#125) +* [13.xorm工具](#130) + * [13.1.反转命令](#131) +* [14.Examples](#140) +* [15.案例](#150) +* [16.那些年我们踩过的坑](#160) +* [17.讨论](#170) ## 1.创建Orm引擎 @@ -129,11 +130,11 @@ engine.SetColumnMapper(SnakeMapper{}) ``` -### 2.2.前缀映射规则和后缀映射规则 +### 2.2.前缀映射,后缀映射和缓存映射 * 通过`engine.NewPrefixMapper(SnakeMapper{}, "prefix")`可以在SnakeMapper的基础上在命名中添加统一的前缀,当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。 * 通过`engine.NewSufffixMapper(SnakeMapper{}, "suffix")`可以在SnakeMapper的基础上在命名中添加统一的后缀,当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。 -* +* 通过`eneing.NewCacheMapper(SnakeMapper{})`可以组合其它的映射规则,起到在内存中缓存曾经映射过的命名映射。 ### 2.3.使用Table和Tag改变名称映射 @@ -153,7 +154,7 @@ type User struct { } ``` -对于不同的数据库系统,数据类型其实是有些差异的。因此xorm中对数据类型有自己的定义,基本的原则是尽量兼容各种数据库的字段类型,具体的字段对应关系可以查看[字段类型对应表](https://github.com/lunny/xorm/blob/master/docs/COLUMNTYPE.md)。 +对于不同的数据库系统,数据类型其实是有些差异的。因此xorm中对数据类型有自己的定义,基本的原则是尽量兼容各种数据库的字段类型,具体的字段对应关系可以查看[字段类型对应表](https://github.com/lunny/xorm/blob/master/docs/COLUMNTYPE.md)。对于使用者,一般只要使用自己熟悉的数据库字段定义即可。 具体的映射规则如下,另Tag中的关键字均不区分大小写,字段名区分大小写: @@ -407,7 +408,11 @@ engine.Cols("age", "name").Update(&user) // UPDATE user SET age=? AND name=? ``` -其中的参数"age", "name"也可以写成"age, name",两种写法均可 +* AllCols() +查询或更新所有字段。 + +* MustCols(…string) +某些字段必须更新。 * Omit(...string) 和cols相反,此函数指定排除某些指定的字段。注意:此方法和Cols方法不可同时使用 @@ -729,20 +734,46 @@ engine.ClearCache(new(User)) ![cache design](https://raw.github.com/lunny/xorm/master/docs/cache_design.png) + +## 12.事件 +xorm支持两种方式的事件,一种是在Struct中的特定方法来作为事件的方法,一种是在执行语句的过程中执行事件。 + +在Struct中作为成员方法的事件如下: + +* BeforeInsert() + +* BeforeUpdate() + +* BeforeDelete() + +* AfterInsert() + +* AfterUpdate() + +* AfterDelete() + +在语句执行过程中的事件方法为: + +* Before(beforeFunc interface{}) + +* After(afterFunc interface{}) + +其中beforeFunc和afterFunc的原型为func(bean interface{}). + -## 12.xorm工具 +## 13.xorm工具 xorm工具提供了xorm命令,能够帮助做很多事情。 -### 12.1.反转命令 +### 13.1.反转命令 参见 [xorm工具](https://github.com/lunny/xorm/tree/master/xorm) -## 13.Examples +## 14.Examples 请访问[https://github.com/lunny/xorm/tree/master/examples](https://github.com/lunny/xorm/tree/master/examples) -## 14.案例 +## 15.案例 * [Gowalker](http://gowalker.org),源代码 [github.com/Unknwon/gowalker](http://github.com/Unknwon/gowalker) @@ -753,7 +784,7 @@ xorm工具提供了xorm命令,能够帮助做很多事情。 * [VeryHour](http://veryhour.com) -## 15.那些年我们踩过的坑 +## 16.那些年我们踩过的坑 * 怎么同时使用xorm的tag和json的tag? 答:使用空格 @@ -797,5 +828,5 @@ money float64 `xorm:"Numeric"` -## 16.讨论 +## 17.讨论 请加入QQ群:280360085 进行讨论。 diff --git a/docs/QuickStartEn.md b/docs/QuickStartEn.md index 5249c81f..7cc08e2e 100644 --- a/docs/QuickStartEn.md +++ b/docs/QuickStartEn.md @@ -100,30 +100,47 @@ engine.Logger = f ## 2.Define struct -xorm支持将一个struct映射为数据库中对应的一张表。映射规则如下: +xorm map a struct to a database table, the rule is below. -### 2.1.名称映射规则 +### 2.1.name mapping rule -名称映射规则主要负责结构体名称到表名和结构体field到表字段的名称映射。由xorm.IMapper接口的实现者来管理,xorm内置了两种IMapper实现:`SnakeMapper` 和 `SameMapper`。SnakeMapper支持struct为驼峰式命名,表结构为下划线命名之间的转换;SameMapper支持相同的命名。 +use xorm.IMapper interface to implement. There are two IMapper implemented: `SnakeMapper` and `SameMapper`. SnakeMapper means struct name is word by word and table name or column name as 下划线. SameMapper means same name between struct and table. -当前SnakeMapper为默认值,如果需要改变时,在engine创建完成后使用 +SnakeMapper is the default. ```Go engine.Mapper = SameMapper{} ``` +同时需要注意的是: + +* 如果你使用了别的命名规则映射方案,也可以自己实现一个IMapper。 +* 表名称和字段名称的映射规则默认是相同的,当然也可以设置为不同,如: + +```Go +engine.SetTableMapper(SameMapper{}) +engine.SetColumnMapper(SnakeMapper{}) +``` + + +### 2.2.前缀映射规则,后缀映射规则和缓存映射规则 + +* 通过`engine.NewPrefixMapper(SnakeMapper{}, "prefix")`可以在SnakeMapper的基础上在命名中添加统一的前缀,当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。 +* 通过`engine.NewSufffixMapper(SnakeMapper{}, "suffix")`可以在SnakeMapper的基础上在命名中添加统一的后缀,当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。 +* 通过`eneing.NewCacheMapper(SnakeMapper{})`可以起到在内存中缓存曾经映射过的命名映射。 + 当然,如果你使用了别的命名规则映射方案,也可以自己实现一个IMapper。 -### 2.2.使用Table和Tag改变名称映射 +### 2.3.使用Table和Tag改变名称映射 如果所有的命名都是按照IMapper的映射来操作的,那当然是最理想的。但是如果碰到某个表名或者某个字段名跟映射规则不匹配时,我们就需要别的机制来改变。 通过`engine.Table()`方法可以改变struct对应的数据库表的名称,通过sturct中field对应的Tag中使用`xorm:"'table_name'"`可以使该field对应的Column名称为指定名称。这里使用两个单引号将Column名称括起来是为了防止名称冲突,因为我们在Tag中还可以对这个Column进行更多的定义。如果名称不冲突的情况,单引号也可以不使用。 -### 2.3.Column属性定义 +### 2.4.Column属性定义 我们在field对应的Tag中对Column的一些属性进行定义,定义的方法基本和我们写SQL定义表结构类似,比如: ``` diff --git a/engine.go b/engine.go index a4d1912a..070c718c 100644 --- a/engine.go +++ b/engine.go @@ -336,6 +336,18 @@ func (engine *Engine) Cols(columns ...string) *Session { return session.Cols(columns...) } +func (engine *Engine) AllCols() *Session { + session := engine.NewSession() + session.IsAutoClose = true + return session.AllCols() +} + +func (engine *Engine) MustCols(columns ...string) *Session { + session := engine.NewSession() + session.IsAutoClose = true + return session.MustCols(columns...) +} + // Xorm automatically retrieve condition according struct, but // if struct has bool field, it will ignore them. So use UseBool // to tell system to do not ignore them. diff --git a/session.go b/session.go index 10bba1bb..bc718b9b 100644 --- a/session.go +++ b/session.go @@ -131,6 +131,11 @@ func (session *Session) AllCols() *Session { return session } +func (session *Session) MustCols(columns ...string) *Session { + session.Statement.MustCols(columns...) + return session +} + func (session *Session) NoCascade() *Session { session.Statement.UseCascade = false return session @@ -1029,7 +1034,7 @@ func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) if len(condiBean) > 0 { colNames, args := buildConditions(session.Engine, table, condiBean[0], true, true, false, true, session.Statement.allUseBool, session.Statement.useAllCols, - session.Statement.boolColumnMap) + session.Statement.mustColumnMap) session.Statement.ConditionStr = strings.Join(colNames, " AND ") session.Statement.BeanArgs = args } @@ -2845,7 +2850,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 if session.Statement.ColumnStr == "" { colNames, args = buildConditions(session.Engine, table, bean, false, false, false, false, session.Statement.allUseBool, session.Statement.useAllCols, - session.Statement.boolColumnMap) + session.Statement.mustColumnMap) } else { colNames, args, err = table.genCols(session, bean, true, true) if err != nil { @@ -2880,7 +2885,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 if len(condiBean) > 0 { condiColNames, condiArgs = buildConditions(session.Engine, session.Statement.RefTable, condiBean[0], true, true, false, true, session.Statement.allUseBool, session.Statement.useAllCols, - session.Statement.boolColumnMap) + session.Statement.mustColumnMap) } var condition = "" @@ -3069,7 +3074,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) { session.Statement.RefTable = table colNames, args := buildConditions(session.Engine, table, bean, true, true, false, true, session.Statement.allUseBool, session.Statement.useAllCols, - session.Statement.boolColumnMap) + session.Statement.mustColumnMap) var condition = "" diff --git a/statement.go b/statement.go index e4ea1a2c..805f5955 100644 --- a/statement.go +++ b/statement.go @@ -41,7 +41,7 @@ type Statement struct { IsDistinct bool allUseBool bool checkVersion bool - boolColumnMap map[string]bool + mustColumnMap map[string]bool inColumns map[string][]interface{} } @@ -70,7 +70,7 @@ func (statement *Statement) Init() { statement.UseAutoTime = true statement.IsDistinct = false statement.allUseBool = false - statement.boolColumnMap = make(map[string]bool) + statement.mustColumnMap = make(map[string]bool) statement.checkVersion = true statement.inColumns = make(map[string][]interface{}) } @@ -242,7 +242,7 @@ func (statement *Statement) Table(tableNameOrBean interface{}) *Statement { func buildConditions(engine *Engine, table *Table, bean interface{}, includeVersion bool, includeUpdated bool, includeNil bool, includeAutoIncr bool, allUseBool bool, useAllCols bool, - boolColumnMap map[string]bool) ([]string, []interface{}) { + mustColumnMap map[string]bool) ([]string, []interface{}) { colNames := make([]string, 0) var args = make([]interface{}, 0) @@ -265,6 +265,14 @@ func buildConditions(engine *Engine, table *Table, bean interface{}, fieldType := reflect.TypeOf(fieldValue.Interface()) requiredField := useAllCols + if b, ok := mustColumnMap[strings.ToLower(col.Name)]; ok { + if b { + requiredField = true + } else { + continue + } + } + if fieldType.Kind() == reflect.Ptr { if fieldValue.IsNil() { if includeNil { @@ -287,8 +295,6 @@ func buildConditions(engine *Engine, table *Table, bean interface{}, case reflect.Bool: if allUseBool || requiredField { val = fieldValue.Interface() - } else if _, ok := boolColumnMap[col.Name]; ok { - val = fieldValue.Interface() } else { // if a bool in a struct, it will not be as a condition because it default is false, // please use Where() instead @@ -519,18 +525,34 @@ func (statement *Statement) Cols(columns ...string) *Statement { return statement } +// Update use only: update all columns func (statement *Statement) AllCols() *Statement { statement.useAllCols = true return statement } +// Update use only: must update columns +func (statement *Statement) MustCols(columns ...string) *Statement { + newColumns := col2NewCols(columns...) + for _, nc := range newColumns { + statement.mustColumnMap[strings.ToLower(nc)] = true + } + return statement +} + +// Update use only: not update columns +/*func (statement *Statement) NotCols(columns ...string) *Statement { + newColumns := col2NewCols(columns...) + for _, nc := range newColumns { + statement.mustColumnMap[strings.ToLower(nc)] = false + } + return statement +}*/ + // indicates that use bool fields as update contents and query contiditions func (statement *Statement) UseBool(columns ...string) *Statement { if len(columns) > 0 { - newColumns := col2NewCols(columns...) - for _, nc := range newColumns { - statement.boolColumnMap[strings.ToLower(nc)] = true - } + statement.MustCols(columns...) } else { statement.allUseBool = true } @@ -721,7 +743,7 @@ func (statement *Statement) genGetSql(bean interface{}) (string, []interface{}) colNames, args := buildConditions(statement.Engine, table, bean, true, true, false, true, statement.allUseBool, statement.useAllCols, - statement.boolColumnMap) + statement.mustColumnMap) statement.ConditionStr = strings.Join(colNames, " AND ") statement.BeanArgs = args @@ -760,7 +782,7 @@ func (statement *Statement) genCountSql(bean interface{}) (string, []interface{} statement.RefTable = table colNames, args := buildConditions(statement.Engine, table, bean, true, true, false, - true, statement.allUseBool, statement.useAllCols, statement.boolColumnMap) + true, statement.allUseBool, statement.useAllCols, statement.mustColumnMap) statement.ConditionStr = strings.Join(colNames, " AND ") statement.BeanArgs = args