From 68e64d101ee8e463689d127ddb61cd480adc2c06 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 14 Jan 2014 16:57:05 +0800 Subject: [PATCH 01/33] improved test --- base_test.go | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/base_test.go b/base_test.go index 37532250..eba9d3bc 100644 --- a/base_test.go +++ b/base_test.go @@ -557,20 +557,48 @@ func where(engine *Engine, t *testing.T) { func in(engine *Engine, t *testing.T) { users := make([]Userinfo, 0) - err := engine.In("(id)", 1, 2, 3).Find(&users) + err := engine.In("(id)", 7, 8, 9).Find(&users) + if err != nil { + t.Error(err) + panic(err) + } + fmt.Println(users) + if len(users) != 3 { + err = errors.New("in uses should be 7,8,9 total 3") + t.Error(err) + panic(err) + } + + for _, user := range users { + if user.Uid != 7 && user.Uid != 8 && user.Uid != 9 { + err = errors.New("in uses should be 7,8,9 total 3") + t.Error(err) + panic(err) + } + } + + users = make([]Userinfo, 0) + ids := []interface{}{7, 8, 9} + err = engine.Where("departname = ?", "dev").In("(id)", ids...).Find(&users) if err != nil { t.Error(err) panic(err) } fmt.Println(users) - ids := []interface{}{1, 2, 3} - err = engine.Where("(id) > ?", 2).In("(id)", ids...).Find(&users) - if err != nil { + if len(users) != 3 { + err = errors.New("in uses should be 7,8,9 total 3") t.Error(err) panic(err) } - fmt.Println(users) + + for _, user := range users { + if user.Uid != 7 && user.Uid != 8 && user.Uid != 9 { + err = errors.New("in uses should be 7,8,9 total 3") + t.Error(err) + panic(err) + } + } err = engine.In("(id)", 1).In("(id)", 2).In("departname", "dev").Find(&users) if err != nil { From adb8edb323d17ac7ac85ddfa1d1259b04b64361c Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 15 Jan 2014 10:31:14 +0800 Subject: [PATCH 02/33] improved docs --- docs/QuickStart.md | 64 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/docs/QuickStart.md b/docs/QuickStart.md index 719269e5..d77ee672 100644 --- a/docs/QuickStart.md +++ b/docs/QuickStart.md @@ -63,7 +63,7 @@ defer engine.Close() 一般如果只针对一个数据库进行操作,只需要创建一个Engine即可。Engine支持在多GoRutine下使用。 -xorm当前支持四种驱动如下: +xorm当前支持五种驱动四个数据库如下: * Mysql: [github.com/Go-SQL-Driver/MySQL](https://github.com/Go-SQL-Driver/MySQL) @@ -110,7 +110,7 @@ xorm支持将一个struct映射为数据库中对应的一张表。映射规则 ### 2.1.名称映射规则 -名称映射规则主要负责结构体名称到表名和结构体field到表字段的名称映射。由xorm.IMapper接口的实现者来管理,xorm内置了两种IMapper实现:`SnakeMapper` 和 `SameMapper`。SnakeMapper支持struct为驼峰式命名,表结构为下划线命名之间的转换;SameMapper支持相同的命名。 +名称映射规则主要负责结构体名称到表名和结构体field到表字段的名称映射。由xorm.IMapper接口的实现者来管理,xorm内置了两种IMapper实现:`SnakeMapper` 和 `SameMapper`。SnakeMapper支持struct为驼峰式命名,表结构为下划线命名之间的转换;SameMapper支持结构体名称和对应的表名称以及结构体field名称与对应的表字段名称相同的命名。 当前SnakeMapper为默认值,如果需要改变时,在engine创建完成后使用 @@ -121,7 +121,8 @@ engine.SetMapper(SameMapper{}) 同时需要注意的是: * 如果你使用了别的命名规则映射方案,也可以自己实现一个IMapper。 -* 表名称和字段名称的映射规则默认是相同的,当然也可以设置为不同,如: +* 表名称和字段名称的映射规则默认是相同的,当然也可以设置为不同,如: + ```Go engine.SetTableMapper(SameMapper{}) engine.SetColumnMapper(SnakeMapper{}) @@ -130,8 +131,9 @@ engine.SetColumnMapper(SnakeMapper{}) ### 2.2.前缀映射规则和后缀映射规则 -* 通过`engine.NewPrefixMapper(SnakeMapper{}, "prefix")`可以在SnakeMapper的基础上在命名中添加统一的前缀; -* 通过`engine.NewSufffixMapper(SnakeMapper{}, "prefix")`可以在SnakeMapper的基础上在命名中添加统一的后缀。 +* 通过`engine.NewPrefixMapper(SnakeMapper{}, "prefix")`可以在SnakeMapper的基础上在命名中添加统一的前缀,当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。 +* 通过`engine.NewSufffixMapper(SnakeMapper{}, "suffix")`可以在SnakeMapper的基础上在命名中添加统一的后缀,当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。 +* ### 2.3.使用Table和Tag改变名称映射 @@ -160,7 +162,7 @@ type User struct { name当前field对应的字段的名称,可选,如不写,则自动根据field名字和转换规则命名,如与其它关键字冲突,请使用单引号括起来。 - pk是否是Primary Key,如果在一个struct中有两个字段都使用了此标记,则这两个字段构成了复合主键,单主键当前支持int32,int,int64,uint32,uint,uint64,string这7中Go的数据类型。 + pk是否是Primary Key,如果在一个struct中有多个字段都使用了此标记,则这多个字段构成了复合主键,单主键当前支持int32,int,int64,uint32,uint,uint64,string这7种Go的数据类型,复合主键支持这7种Go的数据类型的组合。 当前支持30多种字段类型,详情参见 [字段类型](https://github.com/lunny/xorm/blob/master/docs/COLUMNTYPE.md)字段类型 @@ -205,7 +207,7 @@ type User struct { 另外有如下几条自动映射的规则: -- 1.如果field名称为`Id`而且类型为`int64`的话,会被xorm视为主键,并且拥有自增属性。如果想用`Id`以外的名字或非int64类型做为主键名,必须在对应的Tag上加上`xorm:"pk"`来定义主键,加上`xorm:"autoincr"`作为自增。这里需要注意的是,有些数据库并不允许非主键的自增属性。 +- 1.如果field名称为`Id`而且类型为`int64`并且没有定义tag,则会被xorm视为主键,并且拥有自增属性。如果想用`Id`以外的名字或非int64类型做为主键名,必须在对应的Tag上加上`xorm:"pk"`来定义主键,加上`xorm:"autoincr"`作为自增。这里需要注意的是,有些数据库并不允许非主键的自增属性。 - 2.string类型默认映射为varchar(255),如果需要不同的定义,可以在tag中自定义 @@ -235,41 +237,50 @@ xorm提供了一些动态获取和修改表结构的方法。对于一般的应 ## 3.1 获取数据库信息 * DBMetas() + xorm支持获取表结构信息,通过调用`engine.DBMetas()`可以获取到所有的表,字段,索引的信息。 ## 3.2.表操作 * CreateTables() + 创建表使用`engine.CreateTables()`,参数为一个或多个空的对应Struct的指针。同时可用的方法有Charset()和StoreEngine(),如果对应的数据库支持,这两个方法可以在创建表时指定表的字符编码和使用的引擎。当前仅支持Mysql数据库。 * IsTableEmpty() + 判断表是否为空,参数和CreateTables相同 * IsTableExist() + 判断表是否存在 * DropTables() + 删除表使用`engine.DropTables()`,参数为一个或多个空的对应Struct的指针或者表的名字。如果为string传入,则只删除对应的表,如果传入的为Struct,则删除表的同时还会删除对应的索引。 ## 3.3.创建索引和唯一索引 * CreateIndexes + 根据struct中的tag来创建索引 * CreateUniques + 根据struct中的tag来创建唯一索引 ## 3.4.同步数据库结构 同步能够部分智能的根据结构体的变动检测表结构的变动,并自动同步。目前能够实现: -1) 自动检测和创建表,这个检测是根据表的名字 -2)自动检测和新增表中的字段,这个检测是根据字段名 -3)自动检测和创建索引和唯一索引,这个检测是根据一个或多个字段名,而不根据索引名称 + +* 1) 自动检测和创建表,这个检测是根据表的名字 +* 2)自动检测和新增表中的字段,这个检测是根据字段名 +* 3)自动检测和创建索引和唯一索引,这个检测是根据一个或多个字段名,而不根据索引名称 调用方法如下: + ```Go err := engine.Sync(new(User)) ``` @@ -281,6 +292,7 @@ err := engine.Sync(new(User)) 如果传入的是Slice并且当数据库支持批量插入时,Insert会使用批量插入的方式进行插入。 * 插入一条数据 + ```Go user := new(User) user.Name = "myname" @@ -288,11 +300,13 @@ affected, err := engine.Insert(user) ``` 在插入单条数据成功后,如果该结构体有自增字段,则自增字段会被自动赋值为数据库中的id + ```Go fmt.Println(user.Id) ``` * 插入同一个表的多条数据 + ```Go users := make([]User, 0) users[0].Name = "name0" @@ -301,6 +315,7 @@ affected, err := engine.Insert(&users) ``` * 使用指针Slice插入多条记录 + ```Go users := make([]*User, 0) users[0] = new(User) @@ -310,6 +325,7 @@ affected, err := engine.Insert(&users) ``` * 插入不同表的一条记录 + ```Go user := new(User) user.Name = "myname" @@ -319,6 +335,7 @@ affected, err := engine.Insert(user, question) ``` * 插入不同表的多条记录 + ```Go users := make([]User, 0) users[0].Name = "name0" @@ -355,10 +372,10 @@ affected, err := engine.Insert(user, &questions) * Id(interface{}) 传入一个PK字段的值,作为查询条件,如果是复合主键,则 `Id(xorm.PK{1, 2})` -传入的两个参数按照struct中字段出现的顺序赋值。 +传入的两个参数按照struct中pk标记字段出现的顺序赋值。 * Where(string, …interface{}) -和Where语句中的条件基本相同,作为条件 +和SQL中Where语句中的条件基本相同,作为条件 * And(string, …interface{}) 和Where函数中的条件基本相同,作为条件 @@ -379,7 +396,7 @@ affected, err := engine.Insert(user, &questions) 按照指定的顺序进行排序 * In(string, …interface{}) -某字段在一些值中 +某字段在一些值中,这里需要注意必须是[]interface{}才可以展开,由于Go语言的限制,[]int64等均不可以展开。 * Cols(…string) 只查询或更新某些指定的字段,默认是查询所有映射的字段或者根据Update的第一个参数来判断更新的字段。例如: @@ -448,20 +465,28 @@ Having的参数字符串 如: 1) 根据Id来获得单条数据: + ```Go user := new(User) has, err := engine.Id(id).Get(user) +// 复合主键的获取方法 +// has, errr := engine.Id(xorm.PK{1,2}).Get(user) ``` + 2) 根据Where来获得单条数据: + ```Go user := new(User) has, err := engine.Where("name=?", "xlw").Get(user) ``` + 3) 根据user结构体中已有的非空数据来获得单条数据: + ```Go user := &User{Id:1} has, err := engine.Get(user) ``` + 或者其它条件 ```Go @@ -477,6 +502,7 @@ has, err := engine.Get(user) 查询多条数据使用`Find`方法,Find方法的第一个参数为`slice`的指针或`Map`指针,即为查询后返回的结果,第二个参数可选,为查询的条件struct的指针。 1) 传入Slice用于返回数据 + ```Go everyone := make([]Userinfo, 0) err := engine.Find(&everyone) @@ -485,7 +511,8 @@ pEveryOne := make([]*Userinfo, 0) err := engine.Find(&pEveryOne) ``` -2) 传入Map用户返回数据,map必须为`map[int64]Userinfo`的形式,map的key为id +2) 传入Map用户返回数据,map必须为`map[int64]Userinfo`的形式,map的key为id,因此对于复合主键无法使用这种方式。 + ```Go users := make(map[int64]Userinfo) err := engine.Find(&users) @@ -495,6 +522,7 @@ err := engine.Find(&pUsers) ``` 3) 也可以加入各种条件 + ```Go users := make([]Userinfo, 0) err := engine.Where("age > ? or name = ?", 30, "xlw").Limit(20, 10).Find(&users) @@ -504,6 +532,7 @@ err := engine.Where("age > ? or name = ?", 30, "xlw").Limit(20, 10).Find(&users) ### 5.5.Iterate方法 Iterate方法提供逐条执行查询到的记录的方法,他所能使用的条件和Find方法完全相同 + ```Go err := engine.Where("age > ? or name=?)", 30, "xlw").Iterate(new(Userinfo), func(i int, bean interface{})error{ user := bean.(*Userinfo) @@ -550,11 +579,13 @@ affected, err := engine.Id(id).Update(user) 这里需要注意,Update会自动从user结构体中提取非0和非nil得值作为需要更新的内容,因此,如果需要更新一个值为0,则此种方法将无法实现,因此有两种选择: 1. 通过添加Cols函数指定需要更新结构体中的哪些值,未指定的将不更新,指定了的即使为0也会更新。 + ```Go affected, err := engine.Id(id).Cols("age").Update(&user) ``` 2. 通过传入map[string]interface{}来进行更新,但这时需要额外指定更新到哪个表,因为通过map是无法自动检测更新哪个表的。 + ```Go affected, err := engine.Table(new(User)).Id(id).Update(map[string]interface{}{"age":0}) ``` @@ -583,6 +614,7 @@ engine.Id(1).Update(&user) ## 7.删除数据 删除数据`Delete`方法,参数为struct的指针并且成为查询条件。 + ```Go user := new(User) affected, err := engine.Id(id).Delete(user) @@ -659,14 +691,17 @@ xorm内置了一致性缓存支持,不过默认并没有开启。要开启缓 cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000) engine.SetDefaultCacher(cacher) ``` + 上述代码采用了LRU算法的一个缓存,缓存方式是存放到内存中,缓存struct的记录数为1000条,缓存针对的范围是所有具有主键的表,没有主键的表中的数据将不会被缓存。 如果只想针对部分表,则: + ```Go cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000) engine.MapCacher(&user, cacher) ``` 如果要禁用某个表的缓存,则: + ```Go engine.MapCacher(&user, nil) ``` @@ -682,6 +717,7 @@ engine.MapCacher(&user, nil) 2. 在`Get`或者`Find`时使用了`Cols`,`Omit`方法,则在开启缓存后此方法无效,系统仍旧会取出这个表中的所有字段。 3. 在使用Exec方法执行了方法之后,可能会导致缓存与数据库不一致的地方。因此如果启用缓存,尽量避免使用Exec。如果必须使用,则需要在使用了Exec之后调用ClearCache手动做缓存清除的工作。比如: + ```Go engine.Exec("update user set name = ? where id = ?", "xlw", 1) engine.ClearCache(new(User)) From 004974d44dd370035db0cc9d06b359b13fe47460 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 15 Jan 2014 10:34:41 +0800 Subject: [PATCH 03/33] improved docs --- docs/QuickStart.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/QuickStart.md b/docs/QuickStart.md index d77ee672..6fc55f7b 100644 --- a/docs/QuickStart.md +++ b/docs/QuickStart.md @@ -578,13 +578,13 @@ affected, err := engine.Id(id).Update(user) 这里需要注意,Update会自动从user结构体中提取非0和非nil得值作为需要更新的内容,因此,如果需要更新一个值为0,则此种方法将无法实现,因此有两种选择: -1. 通过添加Cols函数指定需要更新结构体中的哪些值,未指定的将不更新,指定了的即使为0也会更新。 +* 1.通过添加Cols函数指定需要更新结构体中的哪些值,未指定的将不更新,指定了的即使为0也会更新。 ```Go affected, err := engine.Id(id).Cols("age").Update(&user) ``` -2. 通过传入map[string]interface{}来进行更新,但这时需要额外指定更新到哪个表,因为通过map是无法自动检测更新哪个表的。 +* 2.通过传入map[string]interface{}来进行更新,但这时需要额外指定更新到哪个表,因为通过map是无法自动检测更新哪个表的。 ```Go affected, err := engine.Table(new(User)).Id(id).Update(map[string]interface{}{"age":0}) From 6147651a6e1e8541a81eb4bcf2157b7e52d99fda Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 15 Jan 2014 10:39:16 +0800 Subject: [PATCH 04/33] bug fixed --- table.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/table.go b/table.go index e63db20e..76b4c3ae 100644 --- a/table.go +++ b/table.go @@ -298,6 +298,8 @@ func (col *Column) String(d dialect) string { if col.Default != "" { sql += "DEFAULT " + col.Default + " " + } else if col.IsVersion { + sql += "DEFAULT 1 " } return sql @@ -316,6 +318,8 @@ func (col *Column) stringNoPk(d dialect) string { if col.Default != "" { sql += "DEFAULT " + col.Default + " " + } else if col.IsVersion { + sql += "DEFAULT 1 " } return sql From 346681289d6b5ee80b48a7a33e59f4597fea1b9e Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 16 Jan 2014 23:03:56 +0800 Subject: [PATCH 05/33] bug fixed #51 --- base_test.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ engine.go | 7 +++++-- postgres.go | 2 +- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/base_test.go b/base_test.go index eba9d3bc..c5d88c86 100644 --- a/base_test.go +++ b/base_test.go @@ -267,6 +267,16 @@ func insertTwoTable(engine *Engine, t *testing.T) { } } +type Article struct { + Id int32 `xorm:"pk INT autoincr"` + Name string `xorm:"VARCHAR(45)"` + Img string `xorm:"VARCHAR(100)"` + Aside string `xorm:"VARCHAR(200)"` + Desc string `xorm:"VARCHAR(200)"` + Content string `xorm:"TEXT"` + Status int8 `xorm:"TINYINT(4)"` +} + type Condi map[string]interface{} func update(engine *Engine, t *testing.T) { @@ -314,6 +324,44 @@ func update(engine *Engine, t *testing.T) { panic(err) return } + + err = engine.Sync(&Article{}) + if err != nil { + t.Error(err) + panic(err) + } + + cnt, err = engine.Insert(&Article{0, "1", "2", "3", "4", "5", 2}) + if err != nil { + t.Error(err) + panic(err) + } + + if cnt != 1 { + err = errors.New("insert not returned 1") + t.Error(err) + panic(err) + return + } + + cnt, err = engine.Id(1).Update(&Article{Name: "6"}) + if err != nil { + t.Error(err) + panic(err) + } + + if cnt != 1 { + err = errors.New("update not returned 1") + t.Error(err) + panic(err) + return + } + + err = engine.DropTables(&Article{}) + if err != nil { + t.Error(err) + panic(err) + } } func updateSameMapper(engine *Engine, t *testing.T) { diff --git a/engine.go b/engine.go index b5fd317f..93494048 100644 --- a/engine.go +++ b/engine.go @@ -482,6 +482,7 @@ func (engine *Engine) mapType(t reflect.Type) *Table { } var indexType int var indexName string + var preKey string for j, key := range tags { k := strings.ToUpper(key) switch { @@ -520,12 +521,13 @@ func (engine *Engine) mapType(t reflect.Type) *Table { case k == "NOT": default: if strings.HasPrefix(k, "'") && strings.HasSuffix(k, "'") { - if key != col.Default { + if preKey != "DEFAULT" { col.Name = key[1 : len(key)-1] } } else if strings.Contains(k, "(") && strings.HasSuffix(k, ")") { fs := strings.Split(k, "(") if _, ok := sqlTypes[fs[0]]; !ok { + preKey = k continue } col.SQLType = SQLType{fs[0], 0, 0} @@ -539,12 +541,13 @@ func (engine *Engine) mapType(t reflect.Type) *Table { } else { if _, ok := sqlTypes[k]; ok { col.SQLType = SQLType{k, 0, 0} - } else if key != col.Default { + } else if preKey != "DEFAULT" { col.Name = key } } engine.SqlType(col) } + preKey = k } if col.SQLType.Name == "" { col.SQLType = Type2SQLType(fieldType) diff --git a/postgres.go b/postgres.go index 9e12b009..97550543 100644 --- a/postgres.go +++ b/postgres.go @@ -67,7 +67,7 @@ func (db *postgres) SqlType(c *Column) string { switch t := c.SQLType.Name; t { case TinyInt: res = SmallInt - + return res case MediumInt, Int, Integer: if c.IsAutoIncrement { return Serial From 19b0a57dcd52278686dd2c0fc3f0bebe808e2b6b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 21 Jan 2014 22:22:06 +0800 Subject: [PATCH 06/33] add lowertest --- base_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ session.go | 1 + 2 files changed, 43 insertions(+) diff --git a/base_test.go b/base_test.go index d2ea0606..b10643a4 100644 --- a/base_test.go +++ b/base_test.go @@ -3334,6 +3334,45 @@ func testCompositeKey(engine *Engine, t *testing.T) { } } +type Lowercase struct { + Id int64 + Name string + ended int64 `xorm:"-"` +} + +func testLowerCase(engine *Engine, t *testing.T) { + err := engine.Sync(&Lowercase{}) + if err != nil { + t.Error(err) + panic(err) + } + + _, err = engine.Where("id > 0").Delete(&Lowercase{}) + if err != nil { + t.Error(err) + panic(err) + } + + _, err = engine.Insert(&Lowercase{ended: 1}) + if err != nil { + t.Error(err) + panic(err) + } + + ls := make([]Lowercase, 0) + err = engine.Find(&ls) + if err != nil { + t.Error(err) + panic(err) + } + + if len(ls) != 1 { + err = errors.New("should be 1") + t.Error(err) + panic(err) + } +} + func testAll(engine *Engine, t *testing.T) { fmt.Println("-------------- directCreateTable --------------") directCreateTable(engine, t) @@ -3430,6 +3469,8 @@ func testAll2(engine *Engine, t *testing.T) { testPrefixTableName(engine, t) fmt.Println("-------------- testCreatedUpdated --------------") testCreatedUpdated(engine, t) + fmt.Println("-------------- testLowercase ---------------") + testLowerCase(engine, t) fmt.Println("-------------- processors --------------") testProcessors(engine, t) fmt.Println("-------------- transaction --------------") @@ -3446,4 +3487,5 @@ func testAll3(engine *Engine, t *testing.T) { testNullValue(engine, t) fmt.Println("-------------- testCompositeKey --------------") testCompositeKey(engine, t) + } diff --git a/session.go b/session.go index 3840c721..9c0c4b22 100644 --- a/session.go +++ b/session.go @@ -2153,6 +2153,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { } colPlaces := strings.Repeat("?, ", len(colNames)) + fmt.Println(colNames, args) colPlaces = colPlaces[0 : len(colPlaces)-2] sql := fmt.Sprintf("INSERT INTO %v%v%v (%v%v%v) VALUES (%v)", From 2699e20b7b57fc48dff678347a4a873cc177daf7 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 21 Jan 2014 23:23:53 +0800 Subject: [PATCH 07/33] add case gocms --- README.md | 2 ++ README_CN.md | 3 +++ 2 files changed, 5 insertions(+) diff --git a/README.md b/README.md index ecdfa721..85e5d46c 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,8 @@ Or * [Very Hour](http://veryhour.com/) +* [GoCMS](https://github.com/zzdboy/GoCMS) + # Todo [Todo List](https://trello.com/b/IHsuAnhk/xorm) diff --git a/README_CN.md b/README_CN.md index d1b88e67..8088f423 100644 --- a/README_CN.md +++ b/README_CN.md @@ -87,6 +87,9 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作 * [Very Hour](http://veryhour.com/) +* [GoCMS](https://github.com/zzdboy/GoCMS) + + ## Todo [开发计划](https://trello.com/b/IHsuAnhk/xorm) From d71a6af18a09591079a7a385d8db1b1cb29fd427 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 23 Jan 2014 10:19:18 +0800 Subject: [PATCH 08/33] autoincr(start) support --- engine.go | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/engine.go b/engine.go index 93494048..1f2312de 100644 --- a/engine.go +++ b/engine.go @@ -452,6 +452,7 @@ func (engine *Engine) mapType(t reflect.Type) *Table { table.Type = t var idFieldColName string + var err error for i := 0; i < t.NumField(); i++ { tag := t.Field(i).Tag @@ -495,8 +496,18 @@ func (engine *Engine) mapType(t reflect.Type) *Table { col.Nullable = false case k == "NULL": col.Nullable = (strings.ToUpper(tags[j-1]) != "NOT") + /*case strings.HasPrefix(k, "AUTOINCR(") && strings.HasSuffix(k, ")"): + col.IsAutoIncrement = true + + autoStart := k[len("AUTOINCR")+1 : len(k)-1] + autoStartInt, err := strconv.Atoi(autoStart) + if err != nil { + engine.LogError(err) + } + col.AutoIncrStart = autoStartInt*/ case k == "AUTOINCR": col.IsAutoIncrement = true + //col.AutoIncrStart = 1 case k == "DEFAULT": col.Default = tags[j+1] case k == "CREATED": @@ -533,10 +544,19 @@ func (engine *Engine) mapType(t reflect.Type) *Table { col.SQLType = SQLType{fs[0], 0, 0} fs2 := strings.Split(fs[1][0:len(fs[1])-1], ",") if len(fs2) == 2 { - col.Length, _ = strconv.Atoi(fs2[0]) - col.Length2, _ = strconv.Atoi(fs2[1]) + col.Length, err = strconv.Atoi(fs2[0]) + if err != nil { + engine.LogError(err) + } + col.Length2, err = strconv.Atoi(fs2[1]) + if err != nil { + engine.LogError(err) + } } else if len(fs2) == 1 { - col.Length, _ = strconv.Atoi(fs2[0]) + col.Length, err = strconv.Atoi(fs2[0]) + if err != nil { + engine.LogError(err) + } } } else { if _, ok := sqlTypes[k]; ok { From 42e5fb880f44a648eb5056e45ae8b7c99c72f4e7 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 24 Jan 2014 09:07:10 +0800 Subject: [PATCH 09/33] comment debug info --- session.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/session.go b/session.go index bd647de6..75570fcd 100644 --- a/session.go +++ b/session.go @@ -2475,7 +2475,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { } colPlaces := strings.Repeat("?, ", len(colNames)) - fmt.Println(colNames, args) + //fmt.Println(colNames, args) colPlaces = colPlaces[0 : len(colPlaces)-2] sqlStr := fmt.Sprintf("INSERT INTO %v%v%v (%v%v%v) VALUES (%v)", From 9e267ad5c3ce8c493c11a41b2c95d63cabee34bf Mon Sep 17 00:00:00 2001 From: "S.W.H" Date: Mon, 27 Jan 2014 21:25:49 +0800 Subject: [PATCH 10/33] fixbug: parse DECIMAL(10, 2) failure. --- engine.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/engine.go b/engine.go index 1f2312de..e0aa2bf2 100644 --- a/engine.go +++ b/engine.go @@ -484,7 +484,8 @@ func (engine *Engine) mapType(t reflect.Type) *Table { var indexType int var indexName string var preKey string - for j, key := range tags { + for j,ln := 0,len(tags); j < ln; j++ { + key := tags[j] k := strings.ToUpper(key) switch { case k == "<-": @@ -535,7 +536,18 @@ func (engine *Engine) mapType(t reflect.Type) *Table { if preKey != "DEFAULT" { col.Name = key[1 : len(key)-1] } - } else if strings.Contains(k, "(") && strings.HasSuffix(k, ")") { + } else if strings.Contains(k, "(") && (strings.HasSuffix(k, ")") || strings.HasSuffix(k, ",")) { + //[SWH|+] + if strings.HasSuffix(k, ",") { + j++ + for j < ln { + k += tags[j] + if strings.HasSuffix(tags[j], ")") { + break + } + j++ + } + } fs := strings.Split(k, "(") if _, ok := sqlTypes[fs[0]]; !ok { preKey = k From d4f8196920edb632b625f70526a456495e556117 Mon Sep 17 00:00:00 2001 From: "S.W.H" Date: Mon, 27 Jan 2014 21:28:13 +0800 Subject: [PATCH 11/33] fixbug: parse DECIMAL(10, 2) failure. --- engine.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/engine.go b/engine.go index 1f2312de..e0aa2bf2 100644 --- a/engine.go +++ b/engine.go @@ -484,7 +484,8 @@ func (engine *Engine) mapType(t reflect.Type) *Table { var indexType int var indexName string var preKey string - for j, key := range tags { + for j,ln := 0,len(tags); j < ln; j++ { + key := tags[j] k := strings.ToUpper(key) switch { case k == "<-": @@ -535,7 +536,18 @@ func (engine *Engine) mapType(t reflect.Type) *Table { if preKey != "DEFAULT" { col.Name = key[1 : len(key)-1] } - } else if strings.Contains(k, "(") && strings.HasSuffix(k, ")") { + } else if strings.Contains(k, "(") && (strings.HasSuffix(k, ")") || strings.HasSuffix(k, ",")) { + //[SWH|+] + if strings.HasSuffix(k, ",") { + j++ + for j < ln { + k += tags[j] + if strings.HasSuffix(tags[j], ")") { + break + } + j++ + } + } fs := strings.Split(k, "(") if _, ok := sqlTypes[fs[0]]; !ok { preKey = k From 46fe2ce87ed20148720734f0b642a179b86337c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=95=86=E8=AE=AF=E5=9C=A8=E7=BA=BF?= Date: Thu, 30 Jan 2014 13:10:15 +0800 Subject: [PATCH 12/33] =?UTF-8?q?=E4=B8=80=E3=80=81xorm=E5=8F=8D=E8=BD=AC?= =?UTF-8?q?=E5=B7=A5=E5=85=B7bug=E4=BF=AE=E5=A4=8D=EF=BC=9A=201=E3=80=81xo?= =?UTF-8?q?rm=E5=8F=8D=E8=BD=AC=E5=B7=A5=E5=85=B7=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E8=A1=A8=E5=89=8D=E7=BC=80=E6=94=AF=E6=8C=81=EF=BC=9B=202?= =?UTF-8?q?=E3=80=81=E4=BF=AE=E6=AD=A3decimal(5,=202)=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E4=B8=AD=E6=8B=AC=E5=8F=B7=E5=86=85=E5=87=BA=E7=8E=B0=E7=A9=BA?= =?UTF-8?q?=E6=A0=BC=E5=AF=BC=E8=87=B4=E8=A7=A3=E6=9E=90=E5=87=BA=E9=94=99?= =?UTF-8?q?=E7=9A=84bug=EF=BC=9B=203=E3=80=81=E4=BF=AE=E6=AD=A3xorm?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E5=B7=A5=E5=85=B7=E5=9C=A8windows=E7=8E=AF?= =?UTF-8?q?=E5=A2=83=E4=B8=8B=EF=BC=8C=E6=8C=87=E5=AE=9A=E7=94=9F=E6=88=90?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E6=97=B6model=E5=90=8D=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E4=B8=8D=E6=AD=A3=E7=A1=AE=E7=9A=84bug=20=E4=BA=8C=E3=80=81xor?= =?UTF-8?q?m=E5=AF=B9=E4=BA=8E=E6=95=B0=E6=8D=AE=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E4=B8=BA=E6=96=87=E6=9C=AC=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E5=80=BC=E4=B8=BA=E7=A9=BA=E7=99=BD=E5=AD=97?= =?UTF-8?q?=E7=AC=A6=E7=9A=84=E6=83=85=E5=86=B5=E4=B8=8B=EF=BC=8C=E7=94=9F?= =?UTF-8?q?=E6=88=90=E7=9A=84struct=E4=B8=ADdefault=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E4=B8=A2=E5=A4=B1=E7=9A=84bug=EF=BC=8C=E5=B7=B2=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 商讯在线 --- engine.go | 37 ++-- mssql.go | 32 +-- mysql.go | 7 + oracle.go | 7 + postgres.go | 7 + table.go | 1 + xorm/go.go | 2 +- xorm/reverse.go | 419 ++++++++++++++++++----------------- xorm/templates/goxorm/config | 1 + 9 files changed, 278 insertions(+), 235 deletions(-) diff --git a/engine.go b/engine.go index e0aa2bf2..71ef63b4 100644 --- a/engine.go +++ b/engine.go @@ -484,8 +484,7 @@ func (engine *Engine) mapType(t reflect.Type) *Table { var indexType int var indexName string var preKey string - for j,ln := 0,len(tags); j < ln; j++ { - key := tags[j] + for j, key := range tags { k := strings.ToUpper(key) switch { case k == "<-": @@ -536,18 +535,7 @@ func (engine *Engine) mapType(t reflect.Type) *Table { if preKey != "DEFAULT" { col.Name = key[1 : len(key)-1] } - } else if strings.Contains(k, "(") && (strings.HasSuffix(k, ")") || strings.HasSuffix(k, ",")) { - //[SWH|+] - if strings.HasSuffix(k, ",") { - j++ - for j < ln { - k += tags[j] - if strings.HasSuffix(tags[j], ")") { - break - } - j++ - } - } + } else if strings.Contains(k, "(") && strings.HasSuffix(k, ")") { fs := strings.Split(k, "(") if _, ok := sqlTypes[fs[0]]; !ok { preKey = k @@ -623,9 +611,24 @@ func (engine *Engine) mapType(t reflect.Type) *Table { } } else { sqlType := Type2SQLType(fieldType) - col = &Column{engine.columnMapper.Obj2Table(t.Field(i).Name), t.Field(i).Name, sqlType, - sqlType.DefaultLength, sqlType.DefaultLength2, true, "", make(map[string]bool), false, false, - TWOSIDES, false, false, false, false} + col = &Column{ + Name: engine.columnMapper.Obj2Table(t.Field(i).Name), + FieldName: t.Field(i).Name, + SQLType: sqlType, + Length: sqlType.DefaultLength, + Length2: sqlType.DefaultLength2, + Nullable: true, + Default: "", + Indexes: make(map[string]bool), + IsPrimaryKey: false, + IsAutoIncrement:false, + MapType: TWOSIDES, + IsCreated: false, + IsUpdated: false, + IsCascade: false, + IsVersion: false, + DefaultIsEmpty: false, + } } if col.IsAutoIncrement { col.Nullable = false diff --git a/mssql.go b/mssql.go index 3606332f..6e9776d2 100644 --- a/mssql.go +++ b/mssql.go @@ -136,8 +136,8 @@ func (db *mssql) TableCheckSql(tableName string) (string, []interface{}) { func (db *mssql) GetColumns(tableName string) ([]string, map[string]*Column, error) { args := []interface{}{} - s := `select a.name as name, b.name as ctype,a.max_length,a.precision,a.scale -from sys.columns a left join sys.types b on a.user_type_id=b.user_type_id + s := `select a.name as name, b.name as ctype,a.max_length,a.precision,a.scale +from sys.columns a left join sys.types b on a.user_type_id=b.user_type_id where a.object_id=object_id('` + tableName + `')` cnn, err := sql.Open(db.driverName, db.dataSourceName) if err != nil { @@ -187,6 +187,10 @@ where a.object_id=object_id('` + tableName + `')` if col.SQLType.IsText() { if col.Default != "" { col.Default = "'" + col.Default + "'" + }else{ + if col.DefaultIsEmpty { + col.Default = "''" + } } } cols[col.Name] = col @@ -224,18 +228,18 @@ func (db *mssql) GetTables() ([]*Table, error) { func (db *mssql) GetIndexes(tableName string) (map[string]*Index, error) { args := []interface{}{tableName} - s := `SELECT -IXS.NAME AS [INDEX_NAME], -C.NAME AS [COLUMN_NAME], -IXS.is_unique AS [IS_UNIQUE], -CASE IXCS.IS_INCLUDED_COLUMN -WHEN 0 THEN 'NONE' -ELSE 'INCLUDED' END AS [IS_INCLUDED_COLUMN] -FROM SYS.INDEXES IXS -INNER JOIN SYS.INDEX_COLUMNS IXCS -ON IXS.OBJECT_ID=IXCS.OBJECT_ID AND IXS.INDEX_ID = IXCS.INDEX_ID -INNER JOIN SYS.COLUMNS C ON IXS.OBJECT_ID=C.OBJECT_ID -AND IXCS.COLUMN_ID=C.COLUMN_ID + s := `SELECT +IXS.NAME AS [INDEX_NAME], +C.NAME AS [COLUMN_NAME], +IXS.is_unique AS [IS_UNIQUE], +CASE IXCS.IS_INCLUDED_COLUMN +WHEN 0 THEN 'NONE' +ELSE 'INCLUDED' END AS [IS_INCLUDED_COLUMN] +FROM SYS.INDEXES IXS +INNER JOIN SYS.INDEX_COLUMNS IXCS +ON IXS.OBJECT_ID=IXCS.OBJECT_ID AND IXS.INDEX_ID = IXCS.INDEX_ID +INNER JOIN SYS.COLUMNS C ON IXS.OBJECT_ID=C.OBJECT_ID +AND IXCS.COLUMN_ID=C.COLUMN_ID WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? ` cnn, err := sql.Open(db.driverName, db.dataSourceName) diff --git a/mysql.go b/mysql.go index 4dcde839..aff13333 100644 --- a/mysql.go +++ b/mysql.go @@ -212,6 +212,9 @@ func (db *mysql) GetColumns(tableName string) ([]string, map[string]*Column, err case "COLUMN_DEFAULT": // add '' col.Default = string(content) + if col.Default == "" { + col.DefaultIsEmpty = true + } case "COLUMN_TYPE": cts := strings.Split(string(content), "(") var len1, len2 int @@ -256,6 +259,10 @@ func (db *mysql) GetColumns(tableName string) ([]string, map[string]*Column, err if col.SQLType.IsText() { if col.Default != "" { col.Default = "'" + col.Default + "'" + }else{ + if col.DefaultIsEmpty { + col.Default = "''" + } } } cols[col.Name] = col diff --git a/oracle.go b/oracle.go index 4e3c6fb6..0b4238ca 100644 --- a/oracle.go +++ b/oracle.go @@ -139,6 +139,9 @@ func (db *oracle) GetColumns(tableName string) ([]string, map[string]*Column, er col.Name = strings.Trim(string(content), `" `) case "data_default": col.Default = string(content) + if col.Default == "" { + col.DefaultIsEmpty = true + } case "nullable": if string(content) == "Y" { col.Nullable = true @@ -171,6 +174,10 @@ func (db *oracle) GetColumns(tableName string) ([]string, map[string]*Column, er if col.SQLType.IsText() { if col.Default != "" { col.Default = "'" + col.Default + "'" + }else{ + if col.DefaultIsEmpty { + col.Default = "''" + } } } cols[col.Name] = col diff --git a/postgres.go b/postgres.go index 97550543..4c7f97e2 100644 --- a/postgres.go +++ b/postgres.go @@ -177,6 +177,9 @@ func (db *postgres) GetColumns(tableName string) ([]string, map[string]*Column, col.IsPrimaryKey = true } else { col.Default = string(content) + if col.Default == "" { + col.DefaultIsEmpty = true + } } case "is_nullable": if string(content) == "YES" { @@ -218,6 +221,10 @@ func (db *postgres) GetColumns(tableName string) ([]string, map[string]*Column, if col.SQLType.IsText() { if col.Default != "" { col.Default = "'" + col.Default + "'" + }else{ + if col.DefaultIsEmpty { + col.Default = "''" + } } } cols[col.Name] = col diff --git a/table.go b/table.go index 76b4c3ae..34cb0862 100644 --- a/table.go +++ b/table.go @@ -275,6 +275,7 @@ type Column struct { IsUpdated bool IsCascade bool IsVersion bool + DefaultIsEmpty bool } // generate column description string according dialect diff --git a/xorm/go.go b/xorm/go.go index 682f6b0b..533ce026 100644 --- a/xorm/go.go +++ b/xorm/go.go @@ -241,7 +241,7 @@ func tag(table *xorm.Table, col *xorm.Column) string { nstr := col.SQLType.Name if col.Length != 0 { if col.Length2 != 0 { - nstr += fmt.Sprintf("(%v, %v)", col.Length, col.Length2) + nstr += fmt.Sprintf("(%v,%v)", col.Length, col.Length2) } else { nstr += fmt.Sprintf("(%v)", col.Length) } diff --git a/xorm/reverse.go b/xorm/reverse.go index 17accfe5..7eab1980 100644 --- a/xorm/reverse.go +++ b/xorm/reverse.go @@ -1,26 +1,27 @@ package main import ( - "bytes" - "fmt" - _ "github.com/bylevel/pq" - "github.com/dvirsky/go-pylog/logging" - _ "github.com/go-sql-driver/mysql" - "github.com/lunny/xorm" - _ "github.com/mattn/go-sqlite3" - _ "github.com/ziutek/mymysql/godrv" - "io/ioutil" - "os" - "path" - "path/filepath" - "strconv" - "text/template" + "bytes" + "fmt" + _ "github.com/bylevel/pq" + "github.com/dvirsky/go-pylog/logging" + _ "github.com/go-sql-driver/mysql" + "github.com/lunny/xorm" + _ "github.com/mattn/go-sqlite3" + _ "github.com/ziutek/mymysql/godrv" + "io/ioutil" + "os" + "path" + "path/filepath" + "strconv" + "strings" //[SWH|+] + "text/template" ) var CmdReverse = &Command{ - UsageLine: "reverse [-m] driverName datasourceName tmplPath [generatedPath]", - Short: "reverse a db to codes", - Long: ` + UsageLine: "reverse [-m] driverName datasourceName tmplPath [generatedPath]", + Short: "reverse a db to codes", + Long: ` according database's tables and columns to generate codes for Go, C++ and etc. -m Generated one go file for every table @@ -33,236 +34,248 @@ according database's tables and columns to generate codes for Go, C++ and etc. } func init() { - CmdReverse.Run = runReverse - CmdReverse.Flags = map[string]bool{ - "-s": false, - "-l": false, - } + CmdReverse.Run = runReverse + CmdReverse.Flags = map[string]bool{ + "-s": false, + "-l": false, + } } var ( - genJson bool = false + genJson bool = false ) func printReversePrompt(flag string) { } type Tmpl struct { - Tables []*xorm.Table - Imports map[string]string - Model string + Tables []*xorm.Table + Imports map[string]string + Model string } func dirExists(dir string) bool { - d, e := os.Stat(dir) - switch { - case e != nil: - return false - case !d.IsDir(): - return false - } + d, e := os.Stat(dir) + switch { + case e != nil: + return false + case !d.IsDir(): + return false + } - return true + return true } func runReverse(cmd *Command, args []string) { - num := checkFlags(cmd.Flags, args, printReversePrompt) - if num == -1 { - return - } - args = args[num:] + num := checkFlags(cmd.Flags, args, printReversePrompt) + if num == -1 { + return + } + args = args[num:] - if len(args) < 3 { - fmt.Println("params error, please see xorm help reverse") - return - } + if len(args) < 3 { + fmt.Println("params error, please see xorm help reverse") + return + } - var isMultiFile bool = true - if use, ok := cmd.Flags["-s"]; ok { - isMultiFile = !use - } + var isMultiFile bool = true + if use, ok := cmd.Flags["-s"]; ok { + isMultiFile = !use + } - curPath, err := os.Getwd() - if err != nil { - fmt.Println(curPath) - return - } + curPath, err := os.Getwd() + if err != nil { + fmt.Println(curPath) + return + } - var genDir string - var model string - if len(args) == 4 { + var genDir string + var model string + if len(args) == 4 { - genDir, err = filepath.Abs(args[3]) - if err != nil { - fmt.Println(err) - return - } - model = path.Base(genDir) - } else { - model = "model" - genDir = path.Join(curPath, model) - } + genDir, err = filepath.Abs(args[3]) + if err != nil { + fmt.Println(err) + return + } + //[SWH|+] 经测试,path.Base不能解析windows下的“\”,需要替换为“/” + genDir = strings.Replace(genDir, "\\", "/", -1) + model = path.Base(genDir) + } else { + model = "model" + genDir = path.Join(curPath, model) + } - dir, err := filepath.Abs(args[2]) - if err != nil { - logging.Error("%v", err) - return - } + dir, err := filepath.Abs(args[2]) + if err != nil { + logging.Error("%v", err) + return + } - if !dirExists(dir) { - logging.Error("Template %v path is not exist", dir) - return - } + if !dirExists(dir) { + logging.Error("Template %v path is not exist", dir) + return + } - var langTmpl LangTmpl - var ok bool - var lang string = "go" + var langTmpl LangTmpl + var ok bool + var lang string = "go" + var prefix string = "" //[SWH|+] + cfgPath := path.Join(dir, "config") + info, err := os.Stat(cfgPath) + var configs map[string]string + if err == nil && !info.IsDir() { + configs = loadConfig(cfgPath) + if l, ok := configs["lang"]; ok { + lang = l + } + if j, ok := configs["genJson"]; ok { + genJson, err = strconv.ParseBool(j) + } + //[SWH|+] + if j, ok := configs["prefix"]; ok { + prefix = j + } + } - cfgPath := path.Join(dir, "config") - info, err := os.Stat(cfgPath) - var configs map[string]string - if err == nil && !info.IsDir() { - configs = loadConfig(cfgPath) - if l, ok := configs["lang"]; ok { - lang = l - } - if j, ok := configs["genJson"]; ok { - genJson, err = strconv.ParseBool(j) - } - } + if langTmpl, ok = langTmpls[lang]; !ok { + fmt.Println("Unsupported programing language", lang) + return + } - if langTmpl, ok = langTmpls[lang]; !ok { - fmt.Println("Unsupported programing language", lang) - return - } + os.MkdirAll(genDir, os.ModePerm) - os.MkdirAll(genDir, os.ModePerm) + Orm, err := xorm.NewEngine(args[0], args[1]) + if err != nil { + logging.Error("%v", err) + return + } - Orm, err := xorm.NewEngine(args[0], args[1]) - if err != nil { - logging.Error("%v", err) - return - } + tables, err := Orm.DBMetas() + if err != nil { + logging.Error("%v", err) + return + } - tables, err := Orm.DBMetas() - if err != nil { - logging.Error("%v", err) - return - } + filepath.Walk(dir, func(f string, info os.FileInfo, err error) error { + if info.IsDir() { + return nil + } - filepath.Walk(dir, func(f string, info os.FileInfo, err error) error { - if info.IsDir() { - return nil - } + if info.Name() == "config" { + return nil + } - if info.Name() == "config" { - return nil - } + bs, err := ioutil.ReadFile(f) + if err != nil { + logging.Error("%v", err) + return err + } - bs, err := ioutil.ReadFile(f) - if err != nil { - logging.Error("%v", err) - return err - } + t := template.New(f) + t.Funcs(langTmpl.Funcs) - t := template.New(f) - t.Funcs(langTmpl.Funcs) + tmpl, err := t.Parse(string(bs)) + if err != nil { + logging.Error("%v", err) + return err + } - tmpl, err := t.Parse(string(bs)) - if err != nil { - logging.Error("%v", err) - return err - } + var w *os.File + fileName := info.Name() + newFileName := fileName[:len(fileName)-4] + ext := path.Ext(newFileName) - var w *os.File - fileName := info.Name() - newFileName := fileName[:len(fileName)-4] - ext := path.Ext(newFileName) + if !isMultiFile { + w, err = os.OpenFile(path.Join(genDir, newFileName), os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + logging.Error("%v", err) + return err + } - if !isMultiFile { - w, err = os.OpenFile(path.Join(genDir, newFileName), os.O_RDWR|os.O_CREATE, 0600) - if err != nil { - logging.Error("%v", err) - return err - } + imports := langTmpl.GenImports(tables) + tbls := make([]*xorm.Table, 0) + for _, table := range tables { + //[SWH|+] + if prefix != "" { + table.Name = strings.TrimPrefix(table.Name, prefix) + } + tbls = append(tbls, table) + } - imports := langTmpl.GenImports(tables) + newbytes := bytes.NewBufferString("") - tbls := make([]*xorm.Table, 0) - for _, table := range tables { - tbls = append(tbls, table) - } + t := &Tmpl{Tables: tbls, Imports: imports, Model: model} + err = tmpl.Execute(newbytes, t) + if err != nil { + logging.Error("%v", err) + return err + } - newbytes := bytes.NewBufferString("") + tplcontent, err := ioutil.ReadAll(newbytes) + if err != nil { + logging.Error("%v", err) + return err + } + var source string + if langTmpl.Formater != nil { + source, err = langTmpl.Formater(string(tplcontent)) + if err != nil { + logging.Error("%v", err) + return err + } + } else { + source = string(tplcontent) + } - t := &Tmpl{Tables: tbls, Imports: imports, Model: model} - err = tmpl.Execute(newbytes, t) - if err != nil { - logging.Error("%v", err) - return err - } + w.WriteString(source) + w.Close() + } else { + for _, table := range tables { + //[SWH|+] + if prefix != "" { + table.Name = strings.TrimPrefix(table.Name, prefix) + } + // imports + tbs := []*xorm.Table{table} + imports := langTmpl.GenImports(tbs) + w, err := os.OpenFile(path.Join(genDir, unTitle(mapper.Table2Obj(table.Name))+ext), os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + logging.Error("%v", err) + return err + } - tplcontent, err := ioutil.ReadAll(newbytes) - if err != nil { - logging.Error("%v", err) - return err - } - var source string - if langTmpl.Formater != nil { - source, err = langTmpl.Formater(string(tplcontent)) - if err != nil { - logging.Error("%v", err) - return err - } - } else { - source = string(tplcontent) - } + newbytes := bytes.NewBufferString("") - w.WriteString(source) - w.Close() - } else { - for _, table := range tables { - // imports - tbs := []*xorm.Table{table} - imports := langTmpl.GenImports(tbs) + t := &Tmpl{Tables: tbs, Imports: imports, Model: model} + err = tmpl.Execute(newbytes, t) + if err != nil { + logging.Error("%v", err) + return err + } - w, err := os.OpenFile(path.Join(genDir, unTitle(mapper.Table2Obj(table.Name))+ext), os.O_RDWR|os.O_CREATE, 0600) - if err != nil { - logging.Error("%v", err) - return err - } + tplcontent, err := ioutil.ReadAll(newbytes) + if err != nil { + logging.Error("%v", err) + return err + } + var source string + if langTmpl.Formater != nil { + source, err = langTmpl.Formater(string(tplcontent)) + if err != nil { + logging.Error("%v-%v", err, string(tplcontent)) + return err + } + } else { + source = string(tplcontent) + } - newbytes := bytes.NewBufferString("") + w.WriteString(source) + w.Close() + } + } - t := &Tmpl{Tables: tbs, Imports: imports, Model: model} - err = tmpl.Execute(newbytes, t) - if err != nil { - logging.Error("%v", err) - return err - } - - tplcontent, err := ioutil.ReadAll(newbytes) - if err != nil { - logging.Error("%v", err) - return err - } - var source string - if langTmpl.Formater != nil { - source, err = langTmpl.Formater(string(tplcontent)) - if err != nil { - logging.Error("%v-%v", err, string(tplcontent)) - return err - } - } else { - source = string(tplcontent) - } - - w.WriteString(source) - w.Close() - } - } - - return nil - }) + return nil + }) } diff --git a/xorm/templates/goxorm/config b/xorm/templates/goxorm/config index e99ad029..5d7bf321 100644 --- a/xorm/templates/goxorm/config +++ b/xorm/templates/goxorm/config @@ -1,2 +1,3 @@ lang=go genJson=0 +prefix=cos_ From 781062130a8be265b2bf96e3d83659f90e826da0 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 10 Feb 2014 11:26:08 +0800 Subject: [PATCH 13/33] add cachemapper --- mapper.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/mapper.go b/mapper.go index 2e9c220a..358d222f 100644 --- a/mapper.go +++ b/mapper.go @@ -2,6 +2,7 @@ package xorm import ( "strings" + "sync" ) // name translation between struct, fields names and table, column names @@ -10,6 +11,50 @@ type IMapper interface { Table2Obj(string) string } +type CacheMapper struct { + oriMapper IMapper + obj2tableCache map[string]string + obj2tableMutex sync.RWMutex + table2objCache map[string]string + table2objMutex sync.RWMutex +} + +func NewCacheMapper(mapper IMapper) *CacheMapper { + return &CacheMapper{oriMapper: mapper, obj2tableCache: make(map[string]string), + table2objCache: make(map[string]string), + } +} + +func (m *CacheMapper) Obj2Table(o string) string { + m.obj2tableMutex.RLock() + t, ok := m.obj2tableCache[o] + m.obj2tableMutex.RUnlock() + if ok { + return t + } + + t = m.oriMapper.Obj2Table(o) + m.obj2tableMutex.Lock() + m.obj2tableCache[o] = t + m.obj2tableMutex.Unlock() + return t +} + +func (m *CacheMapper) Table2Obj(t string) string { + m.table2objMutex.RLock() + o, ok := m.table2objCache[t] + m.table2objMutex.RUnlock() + if ok { + return o + } + + o = m.oriMapper.Table2Obj(t) + m.table2objMutex.Lock() + m.table2objCache[t] = o + m.table2objMutex.Unlock() + return o +} + // SameMapper implements IMapper and provides same name between struct and // database table type SameMapper struct { From da96e0cc86fa239083abea3394a3708949e17405 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 10 Feb 2014 13:56:29 +0800 Subject: [PATCH 14/33] a little perfomance improved --- engine.go | 42 ++++++++++++++++++++++-------------------- xorm.go | 2 +- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/engine.go b/engine.go index 714692e5..73adc8df 100644 --- a/engine.go +++ b/engine.go @@ -58,7 +58,7 @@ type Engine struct { DataSourceName string dialect dialect Tables map[reflect.Type]*Table - mutex *sync.Mutex + mutex *sync.RWMutex ShowSQL bool ShowErr bool ShowDebug bool @@ -421,12 +421,14 @@ func (engine *Engine) Having(conditions string) *Session { } func (engine *Engine) autoMapType(t reflect.Type) *Table { - engine.mutex.Lock() - defer engine.mutex.Unlock() + engine.mutex.RLock() table, ok := engine.Tables[t] + engine.mutex.RUnlock() if !ok { table = engine.mapType(t) + engine.mutex.Lock() engine.Tables[t] = table + engine.mutex.Unlock() } return table } @@ -484,7 +486,7 @@ func (engine *Engine) mapType(t reflect.Type) *Table { var indexType int var indexName string var preKey string - for j,ln := 0,len(tags); j < ln; j++ { + for j, ln := 0, len(tags); j < ln; j++ { key := tags[j] k := strings.ToUpper(key) switch { @@ -624,22 +626,22 @@ func (engine *Engine) mapType(t reflect.Type) *Table { } else { sqlType := Type2SQLType(fieldType) col = &Column{ - Name: engine.columnMapper.Obj2Table(t.Field(i).Name), - FieldName: t.Field(i).Name, - SQLType: sqlType, - Length: sqlType.DefaultLength, - Length2: sqlType.DefaultLength2, - Nullable: true, - Default: "", - Indexes: make(map[string]bool), - IsPrimaryKey: false, - IsAutoIncrement:false, - MapType: TWOSIDES, - IsCreated: false, - IsUpdated: false, - IsCascade: false, - IsVersion: false, - DefaultIsEmpty: false, + Name: engine.columnMapper.Obj2Table(t.Field(i).Name), + FieldName: t.Field(i).Name, + SQLType: sqlType, + Length: sqlType.DefaultLength, + Length2: sqlType.DefaultLength2, + Nullable: true, + Default: "", + Indexes: make(map[string]bool), + IsPrimaryKey: false, + IsAutoIncrement: false, + MapType: TWOSIDES, + IsCreated: false, + IsUpdated: false, + IsCascade: false, + IsVersion: false, + DefaultIsEmpty: false, } } if col.IsAutoIncrement { diff --git a/xorm.go b/xorm.go index bff3f1bc..0a18d5e3 100644 --- a/xorm.go +++ b/xorm.go @@ -49,7 +49,7 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { } engine.Tables = make(map[reflect.Type]*Table) - engine.mutex = &sync.Mutex{} + engine.mutex = &sync.RWMutex{} engine.TagIdentifier = "xorm" engine.Filters = append(engine.Filters, &IdFilter{}) From d0567a63b49ef16462c99228a953b66d9400c9ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=95=86=E8=AE=AF=E5=9C=A8=E7=BA=BF?= Date: Tue, 11 Feb 2014 21:42:14 +0800 Subject: [PATCH 15/33] =?UTF-8?q?=E8=A1=A5=E9=81=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 商讯在线 --- engine.go | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/engine.go b/engine.go index 73adc8df..6e2cbad5 100644 --- a/engine.go +++ b/engine.go @@ -486,8 +486,7 @@ func (engine *Engine) mapType(t reflect.Type) *Table { var indexType int var indexName string var preKey string - for j, ln := 0, len(tags); j < ln; j++ { - key := tags[j] + for j, key := range tags { k := strings.ToUpper(key) switch { case k == "<-": @@ -538,18 +537,7 @@ func (engine *Engine) mapType(t reflect.Type) *Table { if preKey != "DEFAULT" { col.Name = key[1 : len(key)-1] } - } else if strings.Contains(k, "(") && (strings.HasSuffix(k, ")") || strings.HasSuffix(k, ",")) { - //[SWH|+] - if strings.HasSuffix(k, ",") { - j++ - for j < ln { - k += tags[j] - if strings.HasSuffix(tags[j], ")") { - break - } - j++ - } - } + } else if strings.Contains(k, "(") && strings.HasSuffix(k, ")") { fs := strings.Split(k, "(") if _, ok := sqlTypes[fs[0]]; !ok { preKey = k From 1927a64cf31f64a9672f070be1c012b562640c89 Mon Sep 17 00:00:00 2001 From: Nash Tsai Date: Wed, 12 Feb 2014 11:29:27 +0800 Subject: [PATCH 16/33] Update CONTRIBUTING.md add links for fork a repo to --- CONTRIBUTING.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c7fde071..6f65c2ae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,14 +1,17 @@ ## Contributing to xorm -`xorm` has a backlog of pull requests, but contributions are still very +`xorm` has a backlog of [pull requests](https://help.github.com/articles/using-pull-requests), but contributions are still very much welcome. You can help with patch review, submitting bug reports, or adding new functionality. There is no formal style guide, but please conform to the style of existing code and general Go formatting conventions when submitting patches. +* [fork a repo](https://help.github.com/articles/fork-a-repo) +* [creating a pull request ](https://help.github.com/articles/creating-a-pull-request) + ### Patch review -Help review existing open pull requests by commenting on the code or +Help review existing open [pull requests](https://help.github.com/articles/using-pull-requests) by commenting on the code or proposed functionality. ### Bug reports From f76bd3b102c97d25ec7ad242d4367e5f603ee792 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 12 Feb 2014 15:10:37 +0800 Subject: [PATCH 17/33] add gobuild as cases --- README.md | 2 ++ README_CN.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index 85e5d46c..c5fb4ca7 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,8 @@ Or * [Gowalker](http://gowalker.org) - [github.com/Unknwon/gowalker](http://github.com/Unknwon/gowalker) +* [Gobuild.io](http://gobuild.io) - [github.com/shxsun/gobuild](http://github.com/shxsun/gobuild) + * [Sudo China](http://sudochina.com) - [github.com/insionng/toropress](http://github.com/insionng/toropress) * [Godaily](http://godaily.org) - [github.com/govc/godaily](http://github.com/govc/godaily) diff --git a/README_CN.md b/README_CN.md index 8088f423..d7e8de80 100644 --- a/README_CN.md +++ b/README_CN.md @@ -81,6 +81,8 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作 * [Gowalker](http://gowalker.org) - [github.com/Unknwon/gowalker](http://github.com/Unknwon/gowalker) +* [Gobuild.io](http://gobuild.io) - [github.com/shxsun/gobuild](http://github.com/shxsun/gobuild) + * [Sudo China](http://sudochina.com) - [github.com/insionng/toropress](http://github.com/insionng/toropress) * [Godaily](http://godaily.org) - [github.com/govc/godaily](http://github.com/govc/godaily) From 146d5db5eae5fb854e607591d4aa0cc66b0cc4c4 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 18 Feb 2014 14:10:51 +0800 Subject: [PATCH 18/33] bug fixed --- mysql.go | 3 ++- session.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mysql.go b/mysql.go index aff13333..23b53641 100644 --- a/mysql.go +++ b/mysql.go @@ -111,6 +111,7 @@ func (db *mysql) SqlType(c *Column) string { switch t := c.SQLType.Name; t { case Bool: res = TinyInt + c.Length = 1 case Serial: c.IsAutoIncrement = true c.IsPrimaryKey = true @@ -259,7 +260,7 @@ func (db *mysql) GetColumns(tableName string) ([]string, map[string]*Column, err if col.SQLType.IsText() { if col.Default != "" { col.Default = "'" + col.Default + "'" - }else{ + } else { if col.DefaultIsEmpty { col.Default = "''" } diff --git a/session.go b/session.go index 75570fcd..825acc46 100644 --- a/session.go +++ b/session.go @@ -2627,7 +2627,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { // Method InsertOne insert only one struct into database as a record. // The in parameter bean must a struct or a point to struct. The return -// parameter is lastInsertId and error +// parameter is inserted and error func (session *Session) InsertOne(bean interface{}) (int64, error) { err := session.newDb() if err != nil { From 45b3f9775eff90e984129cb08e77b0b7f883ef0f Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 25 Feb 2014 18:45:24 +0800 Subject: [PATCH 19/33] bug fixed for statement.IdParam --- statement.go | 1 + 1 file changed, 1 insertion(+) diff --git a/statement.go b/statement.go index b36a2ebf..4bde5c7b 100644 --- a/statement.go +++ b/statement.go @@ -61,6 +61,7 @@ func (statement *Statement) Init() { statement.columnMap = make(map[string]bool) statement.ConditionStr = "" statement.AltTableName = "" + statement.IdParam = nil statement.RawSQL = "" statement.RawParams = make([]interface{}, 0) statement.BeanArgs = make([]interface{}, 0) From df3bda568b07bcf4b64806c2bb96a5bfe20bd5c2 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 12 Mar 2014 23:23:01 +0800 Subject: [PATCH 20/33] improved QuickStart.md --- docs/QuickStart.md | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/docs/QuickStart.md b/docs/QuickStart.md index 6fc55f7b..dc2f0cd3 100644 --- a/docs/QuickStart.md +++ b/docs/QuickStart.md @@ -3,7 +3,7 @@ xorm 快速入门 * [1.创建Orm引擎](#10) * [2.定义表结构体](#20) - * [2.1.名称映射规则](#21) + * [2.1.名称映射规则](#21) * [2.2.前缀映射规则和后缀映射规则](#22) * [2.3.使用Table和Tag改变名称映射](#23) * [2.4.Column属性定义](#24) @@ -20,7 +20,7 @@ xorm 快速入门 * [5.3.Get方法](#63) * [5.4.Find方法](#64) * [5.5.Iterate方法](#65) - * [5.6.Count方法](#66) + * [5.6.Count方法](#66) * [5.7.Rows方法](#67) * [6.更新数据](#70) * [6.1.乐观锁](#71) @@ -45,7 +45,7 @@ xorm 快速入门 import ( _ "github.com/go-sql-driver/mysql" "github.com/lunny/xorm" -) +) engine, err := xorm.NewEngine("mysql", "root:123@/test?charset=utf8") defer engine.Close() ``` @@ -58,20 +58,20 @@ import ( "github.com/lunny/xorm" ) engine, err = xorm.NewEngine("sqlite3", "./test.db") -defer engine.Close() +defer engine.Close() ``` 一般如果只针对一个数据库进行操作,只需要创建一个Engine即可。Engine支持在多GoRutine下使用。 xorm当前支持五种驱动四个数据库如下: -* Mysql: [github.com/Go-SQL-Driver/MySQL](https://github.com/Go-SQL-Driver/MySQL) - +* Mysql: [github.com/Go-SQL-Driver/MySQL](https://github.com/Go-SQL-Driver/MySQL) + * MyMysql: [github.com/ziutek/mymysql/godrv](https://github.com/ziutek/mymysql/godrv) * SQLite: [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) -* Postgres: [github.com/lib/pq](https://github.com/lib/pq) +* Postgres: [github.com/lib/pq](https://github.com/lib/pq) * MsSql: [github.com/lunny/godbc](https://githubcom/lunny/godbc) @@ -120,9 +120,9 @@ engine.SetMapper(SameMapper{}) 同时需要注意的是: -* 如果你使用了别的命名规则映射方案,也可以自己实现一个IMapper。 +* 如果你使用了别的命名规则映射方案,也可以自己实现一个IMapper。 * 表名称和字段名称的映射规则默认是相同的,当然也可以设置为不同,如: - + ```Go engine.SetTableMapper(SameMapper{}) engine.SetColumnMapper(SnakeMapper{}) @@ -356,7 +356,7 @@ affected, err := engine.Insert(user, &questions) ``` 这里需要注意以下几点: -* 这里虽然支持同时插入,但这些插入并没有事务关系。因此有可能在中间插入出错后,后面的插入将不会继续。 +* 这里虽然支持同时插入,但这些插入并没有事务关系。因此有可能在中间插入出错后,后面的插入将不会继续。 * 多条插入会自动生成`Insert into table values (),(),()`的语句,因此这样的语句有一个最大的记录数,根据经验测算在150条左右。大于150条后,生成的sql语句将太长可能导致执行失败。因此在插入大量数据时,目前需要自行分割成每150条插入一次。 @@ -504,7 +504,7 @@ has, err := engine.Get(user) 1) 传入Slice用于返回数据 ```Go -everyone := make([]Userinfo, 0) +everyone := make([]Userinfo, 0) err := engine.Find(&everyone) pEveryOne := make([]*Userinfo, 0) @@ -643,7 +643,7 @@ results, err := engine.Query(sql) ```Go sql = "update `userinfo` set username=? where id=?" -res, err := engine.Exec(sql, "xiaolun", 1) +res, err := engine.Exec(sql, "xiaolun", 1) ``` @@ -678,9 +678,11 @@ if err != nil { err = session.Commit() if err != nil { return -} +} ``` +* 注意如果您使用的是mysql,数据库引擎为innodb事务才有效,myisam引擎是不支持事务的。 + ## 11.缓存 @@ -715,7 +717,7 @@ engine.MapCacher(&user, nil) 1. 当使用了`Distinct`,`Having`,`GroupBy`方法将不会使用缓存 2. 在`Get`或者`Find`时使用了`Cols`,`Omit`方法,则在开启缓存后此方法无效,系统仍旧会取出这个表中的所有字段。 - + 3. 在使用Exec方法执行了方法之后,可能会导致缓存与数据库不一致的地方。因此如果启用缓存,尽量避免使用Exec。如果必须使用,则需要在使用了Exec之后调用ClearCache手动做缓存清除的工作。比如: ```Go @@ -752,14 +754,14 @@ xorm工具提供了xorm命令,能够帮助做很多事情。 ## 15.那些年我们踩过的坑 -* 怎么同时使用xorm的tag和json的tag? - -答:使用空格 - -```Go -type User struct { - Name string `json:"name" xorm:"name"` -} +* 怎么同时使用xorm的tag和json的tag? + +答:使用空格 + +```Go +type User struct { + Name string `json:"name" xorm:"name"` +} ``` * 我的struct里面包含bool类型,为什么它不能作为条件也没法用Update更新? From 1c81f5257604dcc768e3e33480ad358acbb8f14f Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 14 Mar 2014 15:05:58 +0800 Subject: [PATCH 21/33] update tests --- base_test.go | 30 +++++++++++++++++++----------- xorm/reverse.go | 13 +++++++------ 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/base_test.go b/base_test.go index 91da58c6..92eb6290 100644 --- a/base_test.go +++ b/base_test.go @@ -331,37 +331,45 @@ func update(engine *Engine, t *testing.T) { panic(err) } - cnt, err = engine.Insert(&Article{0, "1", "2", "3", "4", "5", 2}) + defer func() { + err = engine.DropTables(&Article{}) + if err != nil { + t.Error(err) + panic(err) + } + }() + + a := &Article{0, "1", "2", "3", "4", "5", 2} + cnt, err = engine.Insert(a) if err != nil { t.Error(err) panic(err) } if cnt != 1 { - err = errors.New("insert not returned 1") + err = errors.New(fmt.Sprintf("insert not returned 1 but %d", cnt)) t.Error(err) panic(err) - return } - cnt, err = engine.Id(1).Update(&Article{Name: "6"}) + if a.Id == 0 { + err = errors.New("insert returned id is 0") + t.Error(err) + panic(err) + } + + cnt, err = engine.Id(a.Id).Update(&Article{Name: "6"}) if err != nil { t.Error(err) panic(err) } if cnt != 1 { - err = errors.New("update not returned 1") + err = errors.New(fmt.Sprintf("insert not returned 1 but %d", cnt)) t.Error(err) panic(err) return } - - err = engine.DropTables(&Article{}) - if err != nil { - t.Error(err) - panic(err) - } } func updateSameMapper(engine *Engine, t *testing.T) { diff --git a/xorm/reverse.go b/xorm/reverse.go index 7eab1980..e0a0f3b3 100644 --- a/xorm/reverse.go +++ b/xorm/reverse.go @@ -3,12 +3,6 @@ package main import ( "bytes" "fmt" - _ "github.com/bylevel/pq" - "github.com/dvirsky/go-pylog/logging" - _ "github.com/go-sql-driver/mysql" - "github.com/lunny/xorm" - _ "github.com/mattn/go-sqlite3" - _ "github.com/ziutek/mymysql/godrv" "io/ioutil" "os" "path" @@ -16,6 +10,13 @@ import ( "strconv" "strings" //[SWH|+] "text/template" + + "github.com/dvirsky/go-pylog/logging" + _ "github.com/go-sql-driver/mysql" + _ "github.com/lib/pq" + "github.com/lunny/xorm" + _ "github.com/mattn/go-sqlite3" + _ "github.com/ziutek/mymysql/godrv" ) var CmdReverse = &Command{ From d97ff5ee86a0bc7d2f75e850bb7acec785303b6e Mon Sep 17 00:00:00 2001 From: xiaoxiao Date: Wed, 19 Mar 2014 16:55:50 +0800 Subject: [PATCH 22/33] Init commit --- .gitignore | 22 ++++++++++++++++++++++ LICENSE | 27 +++++++++++++++++++++++++++ README.md | 4 ++++ 3 files changed, 53 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..6cd1df2b --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..3af16b07 --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2014 +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the {organization} nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..523200ea --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +xorm +==== + +xorm \ No newline at end of file From 11aa2a973cba567f294c4516c30cc5852c9e3ff9 Mon Sep 17 00:00:00 2001 From: lunny Date: Thu, 20 Mar 2014 13:46:32 +0000 Subject: [PATCH 23/33] Init commit --- .gitignore | 22 ++++++++++++++++++++++ LICENSE | 27 +++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..6cd1df2b --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..3af16b07 --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2014 +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the {organization} nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file From 8d5bd092d8d2f471daeff3f22cc39c210feb9851 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 23 Mar 2014 17:16:11 +0800 Subject: [PATCH 24/33] license --- LICENSE | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 3af16b07..9ac0c261 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014 +Copyright (c) 2013 - 2014 All rights reserved. Redistribution and use in source and binary forms, with or without @@ -24,4 +24,4 @@ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 3aed2090a024c88472e7d5f5db97c38adfa564ea Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 24 Mar 2014 20:41:07 +0800 Subject: [PATCH 25/33] add AllCols for update all cols --- base_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ session.go | 17 +++++++++++++---- statement.go | 16 ++++++++++++---- 3 files changed, 72 insertions(+), 8 deletions(-) diff --git a/base_test.go b/base_test.go index 92eb6290..53d15bfc 100644 --- a/base_test.go +++ b/base_test.go @@ -370,6 +370,53 @@ func update(engine *Engine, t *testing.T) { panic(err) return } + + type UpdateAllCols struct { + Id int64 + Bool bool + String string + } + + col1 := &UpdateAllCols{} + 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 := &UpdateAllCols{col1.Id, true, ""} + _, err = engine.Id(col2.Id).AllCols().Update(col2) + if err != nil { + t.Error(err) + panic(err) + } + + col3 := &UpdateAllCols{} + 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/session.go b/session.go index 825acc46..731027ce 100644 --- a/session.go +++ b/session.go @@ -126,6 +126,11 @@ func (session *Session) Cols(columns ...string) *Session { return session } +func (session *Session) AllCols() *Session { + session.Statement.AllCols() + return session +} + func (session *Session) NoCascade() *Session { session.Statement.UseCascade = false return session @@ -1023,7 +1028,8 @@ 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.boolColumnMap) + false, true, session.Statement.allUseBool, session.Statement.useAllCols, + session.Statement.boolColumnMap) session.Statement.ConditionStr = strings.Join(colNames, " AND ") session.Statement.BeanArgs = args } @@ -2838,7 +2844,8 @@ 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.boolColumnMap) + false, false, session.Statement.allUseBool, session.Statement.useAllCols, + session.Statement.boolColumnMap) } else { colNames, args, err = table.genCols(session, bean, true, true) if err != nil { @@ -2872,7 +2879,8 @@ 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.boolColumnMap) + false, true, session.Statement.allUseBool, session.Statement.useAllCols, + session.Statement.boolColumnMap) } var condition = "" @@ -3060,7 +3068,8 @@ func (session *Session) Delete(bean interface{}) (int64, error) { table := session.Engine.autoMap(bean) session.Statement.RefTable = table colNames, args := buildConditions(session.Engine, table, bean, true, true, - false, true, session.Statement.allUseBool, session.Statement.boolColumnMap) + false, true, session.Statement.allUseBool, session.Statement.useAllCols, + session.Statement.boolColumnMap) var condition = "" diff --git a/statement.go b/statement.go index 4bde5c7b..80c5dccb 100644 --- a/statement.go +++ b/statement.go @@ -25,6 +25,7 @@ type Statement struct { HavingStr string ColumnStr string columnMap map[string]bool + useAllCols bool OmitStr string ConditionStr string AltTableName string @@ -239,7 +240,8 @@ func (statement *Statement) Table(tableNameOrBean interface{}) *Statement { // Auto generating conditions according a struct func buildConditions(engine *Engine, table *Table, bean interface{}, - includeVersion bool, includeUpdated bool, includeNil bool, includeAutoIncr bool, allUseBool bool, + includeVersion bool, includeUpdated bool, includeNil bool, + includeAutoIncr bool, allUseBool bool, useAllCols bool, boolColumnMap map[string]bool) ([]string, []interface{}) { colNames := make([]string, 0) @@ -262,7 +264,7 @@ func buildConditions(engine *Engine, table *Table, bean interface{}, fieldValue := col.ValueOf(bean) fieldType := reflect.TypeOf(fieldValue.Interface()) - requiredField := false + requiredField := useAllCols if fieldType.Kind() == reflect.Ptr { if fieldValue.IsNil() { if includeNil { @@ -517,6 +519,11 @@ func (statement *Statement) Cols(columns ...string) *Statement { return statement } +func (statement *Statement) AllCols() *Statement { + statement.useAllCols = true + return statement +} + // indicates that use bool fields as update contents and query contiditions func (statement *Statement) UseBool(columns ...string) *Statement { if len(columns) > 0 { @@ -719,7 +726,8 @@ func (statement *Statement) genGetSql(bean interface{}) (string, []interface{}) statement.RefTable = table colNames, args := buildConditions(statement.Engine, table, bean, true, true, - false, true, statement.allUseBool, statement.boolColumnMap) + false, true, statement.allUseBool, statement.useAllCols, + statement.boolColumnMap) statement.ConditionStr = strings.Join(colNames, " AND ") statement.BeanArgs = args @@ -758,7 +766,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.boolColumnMap) + true, statement.allUseBool, statement.useAllCols,statement.boolColumnMap) statement.ConditionStr = strings.Join(colNames, " AND ") statement.BeanArgs = args From 182428e13db4959977eb01db61e17eba560b2bd4 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 27 Mar 2014 22:16:06 +0800 Subject: [PATCH 26/33] add use case gogs --- README.md | 8 +++----- README_CN.md | 9 +++------ 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 1ed46fed..4f85368b 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,8 @@ Or # Cases +* [Gogs](http://try.gogits.org) - [github.com/gogits/gogs](http://github.com/gogits/gogs) + * [Gowalker](http://gowalker.org) - [github.com/Unknwon/gowalker](http://github.com/Unknwon/gowalker) * [Gobuild.io](http://gobuild.io) - [github.com/shxsun/gobuild](http://github.com/shxsun/gobuild) @@ -86,11 +88,7 @@ Or * [Very Hour](http://veryhour.com/) -* [GoCMS](https://github.com/zzdboy/GoCMS) - -# Todo - -[Todo List](https://trello.com/b/IHsuAnhk/xorm) +* [GoCMS - github.com/zzboy/GoCMS](https://github.com/zzdboy/GoCMS) # Discuss diff --git a/README_CN.md b/README_CN.md index d7e8de80..35b7ed70 100644 --- a/README_CN.md +++ b/README_CN.md @@ -79,6 +79,8 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作 ## 案例 +* [Gogs](http://try.gogits.org) - [github.com/gogits/gogs](http://github.com/gogits/gogs) + * [Gowalker](http://gowalker.org) - [github.com/Unknwon/gowalker](http://github.com/Unknwon/gowalker) * [Gobuild.io](http://gobuild.io) - [github.com/shxsun/gobuild](http://github.com/shxsun/gobuild) @@ -89,12 +91,7 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作 * [Very Hour](http://veryhour.com/) -* [GoCMS](https://github.com/zzdboy/GoCMS) - - -## Todo - -[开发计划](https://trello.com/b/IHsuAnhk/xorm) +* [GoCMS - github.com/zzboy/GoCMS](https://github.com/zzdboy/GoCMS) ## 讨论 From ff3a06b3dc619d096025235a3e49af40e1bd4e6a Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 28 Mar 2014 15:03:35 +0800 Subject: [PATCH 27/33] ql support --- engine.go | 1 + ql.go | 232 +++++++++++++++++++++++++++++++++++++++++++++++++++++ ql_test.go | 140 ++++++++++++++++++++++++++++++++ xorm.go | 4 + 4 files changed, 377 insertions(+) create mode 100644 ql.go create mode 100644 ql_test.go diff --git a/engine.go b/engine.go index 6e2cbad5..23038e1d 100644 --- a/engine.go +++ b/engine.go @@ -23,6 +23,7 @@ const ( MSSQL = "mssql" ORACLE_OCI = "oci8" + QL = "ql" ) // a dialect is a driver's wrapper diff --git a/ql.go b/ql.go new file mode 100644 index 00000000..8f26f3b5 --- /dev/null +++ b/ql.go @@ -0,0 +1,232 @@ +package xorm + +import ( + "database/sql" + "strings" +) + +type ql struct { + base +} + +type qlParser struct { +} + +func (p *qlParser) parse(driverName, dataSourceName string) (*uri, error) { + return &uri{dbType: QL, dbName: dataSourceName}, nil +} + +func (db *ql) Init(drivername, dataSourceName string) error { + return db.base.init(&qlParser{}, drivername, dataSourceName) +} + +func (db *ql) SqlType(c *Column) string { + switch t := c.SQLType.Name; t { + case Date, DateTime, TimeStamp, Time: + return Numeric + case TimeStampz: + return Text + case Char, Varchar, TinyText, Text, MediumText, LongText: + return Text + case Bit, TinyInt, SmallInt, MediumInt, Int, Integer, BigInt, Bool: + return Integer + case Float, Double, Real: + return Real + case Decimal, Numeric: + return Numeric + case TinyBlob, Blob, MediumBlob, LongBlob, Bytea, Binary, VarBinary: + return Blob + case Serial, BigSerial: + c.IsPrimaryKey = true + c.IsAutoIncrement = true + c.Nullable = false + return Integer + default: + return t + } +} + +func (db *ql) SupportInsertMany() bool { + return true +} + +func (db *ql) QuoteStr() string { + return "" +} + +func (db *ql) AutoIncrStr() string { + return "AUTOINCREMENT" +} + +func (db *ql) SupportEngine() bool { + return false +} + +func (db *ql) SupportCharset() bool { + return false +} + +func (db *ql) IndexOnTable() bool { + return false +} + +func (db *ql) IndexCheckSql(tableName, idxName string) (string, []interface{}) { + args := []interface{}{idxName} + return "SELECT name FROM sqlite_master WHERE type='index' and name = ?", args +} + +func (db *ql) TableCheckSql(tableName string) (string, []interface{}) { + args := []interface{}{tableName} + return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args +} + +func (db *ql) ColumnCheckSql(tableName, colName string) (string, []interface{}) { + args := []interface{}{tableName} + sql := "SELECT name FROM sqlite_master WHERE type='table' and name = ? and ((sql like '%`" + colName + "`%') or (sql like '%[" + colName + "]%'))" + return sql, args +} + +func (db *ql) GetColumns(tableName string) ([]string, map[string]*Column, error) { + args := []interface{}{tableName} + s := "SELECT sql FROM sqlite_master WHERE type='table' and name = ?" + cnn, err := sql.Open(db.driverName, db.dataSourceName) + if err != nil { + return nil, nil, err + } + defer cnn.Close() + res, err := query(cnn, s, args...) + if err != nil { + return nil, nil, err + } + + var sql string + for _, record := range res { + for name, content := range record { + if name == "sql" { + sql = string(content) + } + } + } + + nStart := strings.Index(sql, "(") + nEnd := strings.Index(sql, ")") + colCreates := strings.Split(sql[nStart+1:nEnd], ",") + cols := make(map[string]*Column) + colSeq := make([]string, 0) + for _, colStr := range colCreates { + fields := strings.Fields(strings.TrimSpace(colStr)) + col := new(Column) + col.Indexes = make(map[string]bool) + col.Nullable = true + for idx, field := range fields { + if idx == 0 { + col.Name = strings.Trim(field, "`[] ") + continue + } else if idx == 1 { + col.SQLType = SQLType{field, 0, 0} + } + switch field { + case "PRIMARY": + col.IsPrimaryKey = true + case "AUTOINCREMENT": + col.IsAutoIncrement = true + case "NULL": + if fields[idx-1] == "NOT" { + col.Nullable = false + } else { + col.Nullable = true + } + } + } + cols[col.Name] = col + colSeq = append(colSeq, col.Name) + } + return colSeq, cols, nil +} + +func (db *ql) GetTables() ([]*Table, error) { + args := []interface{}{} + s := "SELECT name FROM sqlite_master WHERE type='table'" + + cnn, err := sql.Open(db.driverName, db.dataSourceName) + if err != nil { + return nil, err + } + defer cnn.Close() + res, err := query(cnn, s, args...) + if err != nil { + return nil, err + } + + tables := make([]*Table, 0) + for _, record := range res { + table := new(Table) + for name, content := range record { + switch name { + case "name": + table.Name = string(content) + } + } + if table.Name == "sqlite_sequence" { + continue + } + tables = append(tables, table) + } + return tables, nil +} + +func (db *ql) GetIndexes(tableName string) (map[string]*Index, error) { + args := []interface{}{tableName} + s := "SELECT sql FROM sqlite_master WHERE type='index' and tbl_name = ?" + cnn, err := sql.Open(db.driverName, db.dataSourceName) + if err != nil { + return nil, err + } + defer cnn.Close() + res, err := query(cnn, s, args...) + if err != nil { + return nil, err + } + + indexes := make(map[string]*Index, 0) + for _, record := range res { + index := new(Index) + sql := string(record["sql"]) + + if sql == "" { + continue + } + + nNStart := strings.Index(sql, "INDEX") + nNEnd := strings.Index(sql, "ON") + if nNStart == -1 || nNEnd == -1 { + continue + } + + indexName := strings.Trim(sql[nNStart+6:nNEnd], "` []") + //fmt.Println(indexName) + if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { + index.Name = indexName[5+len(tableName) : len(indexName)] + } else { + index.Name = indexName + } + + if strings.HasPrefix(sql, "CREATE UNIQUE INDEX") { + index.Type = UniqueType + } else { + index.Type = IndexType + } + + nStart := strings.Index(sql, "(") + nEnd := strings.Index(sql, ")") + colIndexes := strings.Split(sql[nStart+1:nEnd], ",") + + index.Cols = make([]string, 0) + for _, col := range colIndexes { + index.Cols = append(index.Cols, strings.Trim(col, "` []")) + } + indexes[index.Name] = index + } + + return indexes, nil +} diff --git a/ql_test.go b/ql_test.go new file mode 100644 index 00000000..46d0104d --- /dev/null +++ b/ql_test.go @@ -0,0 +1,140 @@ +package xorm + +import ( + "database/sql" + "os" + "testing" + + _ "github.com/mattn/ql-driver" +) + +func newQlEngine() (*Engine, error) { + os.Remove("./ql.db") + return NewEngine("ql", "./ql.db") +} + +func newQlDriverDB() (*sql.DB, error) { + os.Remove("./ql.db") + return sql.Open("ql", "./ql.db") +} + +func TestQl(t *testing.T) { + engine, err := newQlEngine() + defer engine.Close() + if err != nil { + t.Error(err) + return + } + engine.ShowSQL = showTestSql + engine.ShowErr = showTestSql + engine.ShowWarn = showTestSql + engine.ShowDebug = showTestSql + + testAll(engine, t) + testAll2(engine, t) + testAll3(engine, t) +} + +func TestQlWithCache(t *testing.T) { + engine, err := newQlEngine() + defer engine.Close() + if err != nil { + t.Error(err) + return + } + engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) + engine.ShowSQL = showTestSql + engine.ShowErr = showTestSql + engine.ShowWarn = showTestSql + engine.ShowDebug = showTestSql + + testAll(engine, t) + testAll2(engine, t) +} + +const ( + createTableQl = "CREATE TABLE IF NOT EXISTS `big_struct` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NULL, `title` TEXT NULL, `age` TEXT NULL, `alias` TEXT NULL, `nick_name` TEXT NULL);" + dropTableQl = "DROP TABLE IF EXISTS `big_struct`;" +) + +func BenchmarkQlDriverInsert(t *testing.B) { + doBenchDriver(newQlDriverDB, createTableQl, dropTableQl, + doBenchDriverInsert, t) +} + +func BenchmarkQlDriverFind(t *testing.B) { + doBenchDriver(newQlDriverDB, createTableQl, dropTableQl, + doBenchDriverFind, t) +} + +func BenchmarkQlNoCacheInsert(t *testing.B) { + t.StopTimer() + engine, err := newQlEngine() + defer engine.Close() + if err != nil { + t.Error(err) + return + } + //engine.ShowSQL = true + doBenchInsert(engine, t) +} + +func BenchmarkQlNoCacheFind(t *testing.B) { + t.StopTimer() + engine, err := newQlEngine() + defer engine.Close() + if err != nil { + t.Error(err) + return + } + //engine.ShowSQL = true + doBenchFind(engine, t) +} + +func BenchmarkQlNoCacheFindPtr(t *testing.B) { + t.StopTimer() + engine, err := newQlEngine() + defer engine.Close() + if err != nil { + t.Error(err) + return + } + //engine.ShowSQL = true + doBenchFindPtr(engine, t) +} + +func BenchmarkQlCacheInsert(t *testing.B) { + t.StopTimer() + engine, err := newQlEngine() + defer engine.Close() + if err != nil { + t.Error(err) + return + } + engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) + doBenchInsert(engine, t) +} + +func BenchmarkQlCacheFind(t *testing.B) { + t.StopTimer() + engine, err := newQlEngine() + defer engine.Close() + if err != nil { + t.Error(err) + return + } + engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) + doBenchFind(engine, t) +} + +func BenchmarkQlCacheFindPtr(t *testing.B) { + t.StopTimer() + engine, err := newQlEngine() + defer engine.Close() + if err != nil { + t.Error(err) + return + } + engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) + doBenchFindPtr(engine, t) +} diff --git a/xorm.go b/xorm.go index 0a18d5e3..a4bded59 100644 --- a/xorm.go +++ b/xorm.go @@ -40,6 +40,10 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { } else if driverName == ORACLE_OCI { engine.dialect = &oracle{} engine.Filters = append(engine.Filters, &QuoteFilter{}) + } else if driverName == QL { + engine.dialect = &ql{} + engine.Filters = append(engine.Filters, &PgSeqFilter{}) + engine.Filters = append(engine.Filters, &QuoteFilter{}) } else { return nil, errors.New(fmt.Sprintf("Unsupported driver name: %v", driverName)) } From 9d64ef50133ec413bcd426f55c65d331c2af6f49 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 5 Apr 2014 22:14:00 +0800 Subject: [PATCH 28/33] bug fixed --- engine.go | 67 ++++++++------- mssql.go | 8 +- mysql.go | 8 ++ ql.go | 232 --------------------------------------------------- ql_test.go | 140 ------------------------------- session.go | 2 +- statement.go | 14 +--- 7 files changed, 53 insertions(+), 418 deletions(-) delete mode 100644 ql.go delete mode 100644 ql_test.go diff --git a/engine.go b/engine.go index 23038e1d..205ed873 100644 --- a/engine.go +++ b/engine.go @@ -34,6 +34,8 @@ type dialect interface { SqlType(t *Column) string SupportInsertMany() bool QuoteStr() string + RollBackStr() string + DropTableSql(tableName string) string AutoIncrStr() string SupportEngine() bool SupportCharset() bool @@ -449,6 +451,18 @@ func (engine *Engine) newTable() *Table { return table } +func addIndex(indexName string, table *Table, col *Column, indexType int) { + if index, ok := table.Indexes[indexName]; ok { + index.AddColumn(col.Name) + col.Indexes[index.Name] = true + } else { + index := NewIndex(indexName, indexType) + index.AddColumn(col.Name) + table.AddIndex(index) + col.Indexes[index.Name] = true + } +} + func (engine *Engine) mapType(t reflect.Type) *Table { table := engine.newTable() table.Name = engine.tableMapper.Obj2Table(t.Name()) @@ -484,8 +498,9 @@ func (engine *Engine) mapType(t reflect.Type) *Table { table.PrimaryKeys = parentTable.PrimaryKeys continue } - var indexType int - var indexName string + + indexNames := make(map[string]int) + var isIndex, isUnique bool var preKey string for j, key := range tags { k := strings.ToUpper(key) @@ -521,15 +536,15 @@ func (engine *Engine) mapType(t reflect.Type) *Table { case k == "UPDATED": col.IsUpdated = true case strings.HasPrefix(k, "INDEX(") && strings.HasSuffix(k, ")"): - indexType = IndexType - indexName = k[len("INDEX")+1 : len(k)-1] + indexName := k[len("INDEX")+1 : len(k)-1] + indexNames[indexName] = IndexType case k == "INDEX": - indexType = IndexType + isIndex = true case strings.HasPrefix(k, "UNIQUE(") && strings.HasSuffix(k, ")"): - indexName = k[len("UNIQUE")+1 : len(k)-1] - indexType = UniqueType + indexName := k[len("UNIQUE")+1 : len(k)-1] + indexNames[indexName] = UniqueType case k == "UNIQUE": - indexType = UniqueType + isUnique = true case k == "NOTNULL": col.Nullable = false case k == "NOT": @@ -584,32 +599,15 @@ func (engine *Engine) mapType(t reflect.Type) *Table { if col.Name == "" { col.Name = engine.columnMapper.Obj2Table(t.Field(i).Name) } - if indexType == IndexType { - if indexName == "" { - indexName = col.Name - } - if index, ok := table.Indexes[indexName]; ok { - index.AddColumn(col.Name) - col.Indexes[index.Name] = true - } else { - index := NewIndex(indexName, IndexType) - index.AddColumn(col.Name) - table.AddIndex(index) - col.Indexes[index.Name] = true - } - } else if indexType == UniqueType { - if indexName == "" { - indexName = col.Name - } - if index, ok := table.Indexes[indexName]; ok { - index.AddColumn(col.Name) - col.Indexes[index.Name] = true - } else { - index := NewIndex(indexName, UniqueType) - index.AddColumn(col.Name) - table.AddIndex(index) - col.Indexes[index.Name] = true - } + + if isUnique { + indexNames[col.Name] = UniqueType + } else if isIndex { + indexNames[col.Name] = IndexType + } + + for indexName, indexType := range indexNames { + addIndex(indexName, table, col, indexType) } } } else { @@ -810,6 +808,7 @@ func (engine *Engine) Sync(beans ...interface{}) error { } } } else if index.Type == IndexType { + fmt.Println("index:", table.Name, name, index) isExist, err := session.isIndexExist2(table.Name, index.Cols, false) if err != nil { return err diff --git a/mssql.go b/mssql.go index 6e9776d2..54c93e71 100644 --- a/mssql.go +++ b/mssql.go @@ -108,6 +108,12 @@ func (db *mssql) AutoIncrStr() string { return "IDENTITY" } +func (db *mssql) DropTableSql(tableName string) string { + return fmt.Sprintf("IF EXISTS (SELECT * FROM sysobjects WHERE id = "+ + "object_id(N'%s') and OBJECTPROPERTY(id, N'IsUserTable') = 1) "+ + "DROP TABLE \"%s\"", tableName, tableName) +} + func (db *mssql) SupportCharset() bool { return false } @@ -187,7 +193,7 @@ where a.object_id=object_id('` + tableName + `')` if col.SQLType.IsText() { if col.Default != "" { col.Default = "'" + col.Default + "'" - }else{ + } else { if col.DefaultIsEmpty { col.Default = "''" } diff --git a/mysql.go b/mysql.go index 23b53641..8d0cfaa3 100644 --- a/mysql.go +++ b/mysql.go @@ -89,6 +89,14 @@ func (b *base) DBType() string { return b.uri.dbType } +func (db *base) RollBackStr() string { + return "ROLL BACK" +} + +func (db *base) DropTableSql(tableName string) string { + return fmt.Sprintf("DROP TABLE IF EXISTS `%s`", tableName) +} + type mysql struct { base net string diff --git a/ql.go b/ql.go deleted file mode 100644 index 8f26f3b5..00000000 --- a/ql.go +++ /dev/null @@ -1,232 +0,0 @@ -package xorm - -import ( - "database/sql" - "strings" -) - -type ql struct { - base -} - -type qlParser struct { -} - -func (p *qlParser) parse(driverName, dataSourceName string) (*uri, error) { - return &uri{dbType: QL, dbName: dataSourceName}, nil -} - -func (db *ql) Init(drivername, dataSourceName string) error { - return db.base.init(&qlParser{}, drivername, dataSourceName) -} - -func (db *ql) SqlType(c *Column) string { - switch t := c.SQLType.Name; t { - case Date, DateTime, TimeStamp, Time: - return Numeric - case TimeStampz: - return Text - case Char, Varchar, TinyText, Text, MediumText, LongText: - return Text - case Bit, TinyInt, SmallInt, MediumInt, Int, Integer, BigInt, Bool: - return Integer - case Float, Double, Real: - return Real - case Decimal, Numeric: - return Numeric - case TinyBlob, Blob, MediumBlob, LongBlob, Bytea, Binary, VarBinary: - return Blob - case Serial, BigSerial: - c.IsPrimaryKey = true - c.IsAutoIncrement = true - c.Nullable = false - return Integer - default: - return t - } -} - -func (db *ql) SupportInsertMany() bool { - return true -} - -func (db *ql) QuoteStr() string { - return "" -} - -func (db *ql) AutoIncrStr() string { - return "AUTOINCREMENT" -} - -func (db *ql) SupportEngine() bool { - return false -} - -func (db *ql) SupportCharset() bool { - return false -} - -func (db *ql) IndexOnTable() bool { - return false -} - -func (db *ql) IndexCheckSql(tableName, idxName string) (string, []interface{}) { - args := []interface{}{idxName} - return "SELECT name FROM sqlite_master WHERE type='index' and name = ?", args -} - -func (db *ql) TableCheckSql(tableName string) (string, []interface{}) { - args := []interface{}{tableName} - return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args -} - -func (db *ql) ColumnCheckSql(tableName, colName string) (string, []interface{}) { - args := []interface{}{tableName} - sql := "SELECT name FROM sqlite_master WHERE type='table' and name = ? and ((sql like '%`" + colName + "`%') or (sql like '%[" + colName + "]%'))" - return sql, args -} - -func (db *ql) GetColumns(tableName string) ([]string, map[string]*Column, error) { - args := []interface{}{tableName} - s := "SELECT sql FROM sqlite_master WHERE type='table' and name = ?" - cnn, err := sql.Open(db.driverName, db.dataSourceName) - if err != nil { - return nil, nil, err - } - defer cnn.Close() - res, err := query(cnn, s, args...) - if err != nil { - return nil, nil, err - } - - var sql string - for _, record := range res { - for name, content := range record { - if name == "sql" { - sql = string(content) - } - } - } - - nStart := strings.Index(sql, "(") - nEnd := strings.Index(sql, ")") - colCreates := strings.Split(sql[nStart+1:nEnd], ",") - cols := make(map[string]*Column) - colSeq := make([]string, 0) - for _, colStr := range colCreates { - fields := strings.Fields(strings.TrimSpace(colStr)) - col := new(Column) - col.Indexes = make(map[string]bool) - col.Nullable = true - for idx, field := range fields { - if idx == 0 { - col.Name = strings.Trim(field, "`[] ") - continue - } else if idx == 1 { - col.SQLType = SQLType{field, 0, 0} - } - switch field { - case "PRIMARY": - col.IsPrimaryKey = true - case "AUTOINCREMENT": - col.IsAutoIncrement = true - case "NULL": - if fields[idx-1] == "NOT" { - col.Nullable = false - } else { - col.Nullable = true - } - } - } - cols[col.Name] = col - colSeq = append(colSeq, col.Name) - } - return colSeq, cols, nil -} - -func (db *ql) GetTables() ([]*Table, error) { - args := []interface{}{} - s := "SELECT name FROM sqlite_master WHERE type='table'" - - cnn, err := sql.Open(db.driverName, db.dataSourceName) - if err != nil { - return nil, err - } - defer cnn.Close() - res, err := query(cnn, s, args...) - if err != nil { - return nil, err - } - - tables := make([]*Table, 0) - for _, record := range res { - table := new(Table) - for name, content := range record { - switch name { - case "name": - table.Name = string(content) - } - } - if table.Name == "sqlite_sequence" { - continue - } - tables = append(tables, table) - } - return tables, nil -} - -func (db *ql) GetIndexes(tableName string) (map[string]*Index, error) { - args := []interface{}{tableName} - s := "SELECT sql FROM sqlite_master WHERE type='index' and tbl_name = ?" - cnn, err := sql.Open(db.driverName, db.dataSourceName) - if err != nil { - return nil, err - } - defer cnn.Close() - res, err := query(cnn, s, args...) - if err != nil { - return nil, err - } - - indexes := make(map[string]*Index, 0) - for _, record := range res { - index := new(Index) - sql := string(record["sql"]) - - if sql == "" { - continue - } - - nNStart := strings.Index(sql, "INDEX") - nNEnd := strings.Index(sql, "ON") - if nNStart == -1 || nNEnd == -1 { - continue - } - - indexName := strings.Trim(sql[nNStart+6:nNEnd], "` []") - //fmt.Println(indexName) - if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { - index.Name = indexName[5+len(tableName) : len(indexName)] - } else { - index.Name = indexName - } - - if strings.HasPrefix(sql, "CREATE UNIQUE INDEX") { - index.Type = UniqueType - } else { - index.Type = IndexType - } - - nStart := strings.Index(sql, "(") - nEnd := strings.Index(sql, ")") - colIndexes := strings.Split(sql[nStart+1:nEnd], ",") - - index.Cols = make([]string, 0) - for _, col := range colIndexes { - index.Cols = append(index.Cols, strings.Trim(col, "` []")) - } - indexes[index.Name] = index - } - - return indexes, nil -} diff --git a/ql_test.go b/ql_test.go deleted file mode 100644 index 46d0104d..00000000 --- a/ql_test.go +++ /dev/null @@ -1,140 +0,0 @@ -package xorm - -import ( - "database/sql" - "os" - "testing" - - _ "github.com/mattn/ql-driver" -) - -func newQlEngine() (*Engine, error) { - os.Remove("./ql.db") - return NewEngine("ql", "./ql.db") -} - -func newQlDriverDB() (*sql.DB, error) { - os.Remove("./ql.db") - return sql.Open("ql", "./ql.db") -} - -func TestQl(t *testing.T) { - engine, err := newQlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - engine.ShowSQL = showTestSql - engine.ShowErr = showTestSql - engine.ShowWarn = showTestSql - engine.ShowDebug = showTestSql - - testAll(engine, t) - testAll2(engine, t) - testAll3(engine, t) -} - -func TestQlWithCache(t *testing.T) { - engine, err := newQlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) - engine.ShowSQL = showTestSql - engine.ShowErr = showTestSql - engine.ShowWarn = showTestSql - engine.ShowDebug = showTestSql - - testAll(engine, t) - testAll2(engine, t) -} - -const ( - createTableQl = "CREATE TABLE IF NOT EXISTS `big_struct` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NULL, `title` TEXT NULL, `age` TEXT NULL, `alias` TEXT NULL, `nick_name` TEXT NULL);" - dropTableQl = "DROP TABLE IF EXISTS `big_struct`;" -) - -func BenchmarkQlDriverInsert(t *testing.B) { - doBenchDriver(newQlDriverDB, createTableQl, dropTableQl, - doBenchDriverInsert, t) -} - -func BenchmarkQlDriverFind(t *testing.B) { - doBenchDriver(newQlDriverDB, createTableQl, dropTableQl, - doBenchDriverFind, t) -} - -func BenchmarkQlNoCacheInsert(t *testing.B) { - t.StopTimer() - engine, err := newQlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - //engine.ShowSQL = true - doBenchInsert(engine, t) -} - -func BenchmarkQlNoCacheFind(t *testing.B) { - t.StopTimer() - engine, err := newQlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - //engine.ShowSQL = true - doBenchFind(engine, t) -} - -func BenchmarkQlNoCacheFindPtr(t *testing.B) { - t.StopTimer() - engine, err := newQlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - //engine.ShowSQL = true - doBenchFindPtr(engine, t) -} - -func BenchmarkQlCacheInsert(t *testing.B) { - t.StopTimer() - engine, err := newQlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) - doBenchInsert(engine, t) -} - -func BenchmarkQlCacheFind(t *testing.B) { - t.StopTimer() - engine, err := newQlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) - doBenchFind(engine, t) -} - -func BenchmarkQlCacheFindPtr(t *testing.B) { - t.StopTimer() - engine, err := newQlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) - doBenchFindPtr(engine, t) -} diff --git a/session.go b/session.go index 731027ce..10bba1bb 100644 --- a/session.go +++ b/session.go @@ -286,7 +286,7 @@ func (session *Session) Begin() error { // When using transaction, you can rollback if any error func (session *Session) Rollback() error { if !session.IsAutoCommit && !session.IsCommitedOrRollbacked { - session.Engine.LogSQL("ROLL BACK") + session.Engine.LogSQL(session.Engine.dialect.RollBackStr()) session.IsCommitedOrRollbacked = true return session.Tx.Rollback() } diff --git a/statement.go b/statement.go index 80c5dccb..e4ea1a2c 100644 --- a/statement.go +++ b/statement.go @@ -25,7 +25,7 @@ type Statement struct { HavingStr string ColumnStr string columnMap map[string]bool - useAllCols bool + useAllCols bool OmitStr string ConditionStr string AltTableName string @@ -240,7 +240,7 @@ func (statement *Statement) Table(tableNameOrBean interface{}) *Statement { // Auto generating conditions according a struct func buildConditions(engine *Engine, table *Table, bean interface{}, - includeVersion bool, includeUpdated bool, includeNil bool, + includeVersion bool, includeUpdated bool, includeNil bool, includeAutoIncr bool, allUseBool bool, useAllCols bool, boolColumnMap map[string]bool) ([]string, []interface{}) { @@ -712,13 +712,7 @@ func (s *Statement) genDelIndexSQL() []string { } func (s *Statement) genDropSQL() string { - if s.Engine.dialect.DBType() == MSSQL { - return "IF EXISTS (SELECT * FROM sysobjects WHERE id = object_id(N'" + - s.TableName() + "') and OBJECTPROPERTY(id, N'IsUserTable') = 1) " + - "DROP TABLE " + s.Engine.Quote(s.TableName()) + ";" - } else { - return "DROP TABLE IF EXISTS " + s.Engine.Quote(s.TableName()) + ";" - } + return s.Engine.dialect.DropTableSql(s.TableName()) + ";" } func (statement *Statement) genGetSql(bean interface{}) (string, []interface{}) { @@ -766,7 +760,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.boolColumnMap) statement.ConditionStr = strings.Join(colNames, " AND ") statement.BeanArgs = args From a1e3dd8db0ec2440ccf13d5ac090a839e158cd8d Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 5 Apr 2014 22:17:12 +0800 Subject: [PATCH 29/33] comment ql --- xorm.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/xorm.go b/xorm.go index a4bded59..0a18d5e3 100644 --- a/xorm.go +++ b/xorm.go @@ -40,10 +40,6 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { } else if driverName == ORACLE_OCI { engine.dialect = &oracle{} engine.Filters = append(engine.Filters, &QuoteFilter{}) - } else if driverName == QL { - engine.dialect = &ql{} - engine.Filters = append(engine.Filters, &PgSeqFilter{}) - engine.Filters = append(engine.Filters, &QuoteFilter{}) } else { return nil, errors.New(fmt.Sprintf("Unsupported driver name: %v", driverName)) } From 6e7cead1ec71ad5ea0369b86e1cdf9c64adaf883 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 5 Apr 2014 22:18:25 +0800 Subject: [PATCH 30/33] remove debug info --- engine.go | 1 - 1 file changed, 1 deletion(-) diff --git a/engine.go b/engine.go index 205ed873..a4d1912a 100644 --- a/engine.go +++ b/engine.go @@ -808,7 +808,6 @@ func (engine *Engine) Sync(beans ...interface{}) error { } } } else if index.Type == IndexType { - fmt.Println("index:", table.Name, name, index) isExist, err := session.isIndexExist2(table.Name, index.Cols, false) if err != nil { return err From 9d5f834eb2e8996509c6928a02fc73c935be63f6 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 6 Apr 2014 12:58:16 +0800 Subject: [PATCH 31/33] 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 From 9b23e7d6c089c72ab9abeac334fd3f8bb4acb64f Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 8 Apr 2014 16:46:23 +0800 Subject: [PATCH 32/33] resolved #89: if struct has func, the struct's name is the result --- base_test.go | 24 +++++++++++++++++++++++ engine.go | 54 ++++++++++++++++++++++++++++++++++------------------ helpers.go | 7 ++++++- session.go | 24 ++++++++++++----------- statement.go | 7 ++++--- 5 files changed, 83 insertions(+), 33 deletions(-) diff --git a/base_test.go b/base_test.go index 3199b850..f8d9d794 100644 --- a/base_test.go +++ b/base_test.go @@ -3990,6 +3990,28 @@ func testCompositeKey2(engine *Engine, t *testing.T) { } } +type CustomTableName struct { + Id int64 + Name string +} + +func (c *CustomTableName) TableName() string { + return "customtablename" +} + +func testCustomTableName(engine *Engine, t *testing.T) { + c := new(CustomTableName) + err := engine.DropTables(c) + if err != nil { + t.Error(err) + } + + err = engine.CreateTables(c) + if err != nil { + t.Error(err) + } +} + func testAll(engine *Engine, t *testing.T) { fmt.Println("-------------- directCreateTable --------------") directCreateTable(engine, t) @@ -4100,6 +4122,8 @@ func testAll2(engine *Engine, t *testing.T) { testProcessors(engine, t) fmt.Println("-------------- transaction --------------") transaction(engine, t) + fmt.Println("-------------- testCustomTableName --------------") + testCustomTableName(engine, t) } // !nash! the 3rd set of the test is intended for non-cache enabled engine diff --git a/engine.go b/engine.go index 070c718c..2499d494 100644 --- a/engine.go +++ b/engine.go @@ -157,9 +157,9 @@ func (engine *Engine) NoCascade() *Session { // Set a table use a special cacher func (engine *Engine) MapCacher(bean interface{}, cacher Cacher) { - t := rType(bean) - engine.autoMapType(t) - engine.Tables[t].Cacher = cacher + v := rValue(bean) + engine.autoMapType(v) + engine.Tables[v.Type()].Cacher = cacher } // OpenDB provides a interface to operate database directly. @@ -435,12 +435,13 @@ func (engine *Engine) Having(conditions string) *Session { return session.Having(conditions) } -func (engine *Engine) autoMapType(t reflect.Type) *Table { +func (engine *Engine) autoMapType(v reflect.Value) *Table { + t := v.Type() engine.mutex.RLock() table, ok := engine.Tables[t] engine.mutex.RUnlock() if !ok { - table = engine.mapType(t) + table = engine.mapType(v) engine.mutex.Lock() engine.Tables[t] = table engine.mutex.Unlock() @@ -449,8 +450,8 @@ func (engine *Engine) autoMapType(t reflect.Type) *Table { } func (engine *Engine) autoMap(bean interface{}) *Table { - t := rType(bean) - return engine.autoMapType(t) + v := rValue(bean) + return engine.autoMapType(v) } func (engine *Engine) newTable() *Table { @@ -475,9 +476,24 @@ func addIndex(indexName string, table *Table, col *Column, indexType int) { } } -func (engine *Engine) mapType(t reflect.Type) *Table { +func (engine *Engine) mapType(v reflect.Value) *Table { + t := v.Type() table := engine.newTable() - table.Name = engine.tableMapper.Obj2Table(t.Name()) + method := v.MethodByName("TableName") + if !method.IsValid() { + method = v.Addr().MethodByName("TableName") + } + if method.IsValid() { + params := []reflect.Value{} + results := method.Call(params) + if len(results) == 1 { + table.Name = results[0].Interface().(string) + } + } + + if table.Name == "" { + table.Name = engine.tableMapper.Obj2Table(t.Name()) + } table.Type = t var idFieldColName string @@ -487,7 +503,8 @@ func (engine *Engine) mapType(t reflect.Type) *Table { tag := t.Field(i).Tag ormTagStr := tag.Get(engine.TagIdentifier) var col *Column - fieldType := t.Field(i).Type + fieldValue := v.Field(i) + fieldType := fieldValue.Type() if ormTagStr != "" { col = &Column{FieldName: t.Field(i).Name, Nullable: true, IsPrimaryKey: false, @@ -500,7 +517,7 @@ func (engine *Engine) mapType(t reflect.Type) *Table { } if (strings.ToUpper(tags[0]) == "EXTENDS") && (fieldType.Kind() == reflect.Struct) { - parentTable := engine.mapType(fieldType) + parentTable := engine.mapType(fieldValue) for name, col := range parentTable.Columns { col.FieldName = fmt.Sprintf("%v.%v", fieldType.Name(), col.FieldName) table.Columns[strings.ToLower(name)] = col @@ -671,19 +688,20 @@ func (engine *Engine) mapping(beans ...interface{}) (e error) { engine.mutex.Lock() defer engine.mutex.Unlock() for _, bean := range beans { - t := rType(bean) - engine.Tables[t] = engine.mapType(t) + v := rValue(bean) + engine.Tables[v.Type()] = engine.mapType(v) } return } // If a table has any reocrd func (engine *Engine) IsTableEmpty(bean interface{}) (bool, error) { - t := rType(bean) + v := rValue(bean) + t := v.Type() if t.Kind() != reflect.Struct { return false, errors.New("bean should be a struct or struct's point") } - engine.autoMapType(t) + engine.autoMapType(v) session := engine.NewSession() defer session.Close() rows, err := session.Count(bean) @@ -692,11 +710,11 @@ func (engine *Engine) IsTableEmpty(bean interface{}) (bool, error) { // If a table is exist func (engine *Engine) IsTableExist(bean interface{}) (bool, error) { - t := rType(bean) - if t.Kind() != reflect.Struct { + v := rValue(bean) + if v.Type().Kind() != reflect.Struct { return false, errors.New("bean should be a struct or struct's point") } - table := engine.autoMapType(t) + table := engine.autoMapType(v) session := engine.NewSession() defer session.Close() has, err := session.isTableExist(table.Name) diff --git a/helpers.go b/helpers.go index 96f118f2..25b6ddc8 100644 --- a/helpers.go +++ b/helpers.go @@ -37,9 +37,14 @@ func makeArray(elem string, count int) []string { return res } +func rValue(bean interface{}) reflect.Value { + return reflect.Indirect(reflect.ValueOf(bean)) +} + func rType(bean interface{}) reflect.Type { sliceValue := reflect.Indirect(reflect.ValueOf(bean)) - return reflect.TypeOf(sliceValue.Interface()) + //return reflect.TypeOf(sliceValue.Interface()) + return sliceValue.Type() } func structName(v reflect.Type) string { diff --git a/session.go b/session.go index bc718b9b..b8035acc 100644 --- a/session.go +++ b/session.go @@ -358,12 +358,12 @@ func cleanupProcessorsClosures(slices *[]func(interface{})) { } func (session *Session) scanMapIntoStruct(obj interface{}, objMap map[string][]byte) error { - dataStruct := reflect.Indirect(reflect.ValueOf(obj)) + dataStruct := rValue(obj) if dataStruct.Kind() != reflect.Struct { return errors.New("Expected a pointer to a struct") } - table := session.Engine.autoMapType(rType(obj)) + table := session.Engine.autoMapType(dataStruct) for key, data := range objMap { key = strings.ToLower(key) @@ -1017,12 +1017,14 @@ func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) if session.Statement.RefTable == nil { if sliceElementType.Kind() == reflect.Ptr { if sliceElementType.Elem().Kind() == reflect.Struct { - table = session.Engine.autoMapType(sliceElementType.Elem()) + pv := reflect.New(sliceElementType.Elem()) + table = session.Engine.autoMapType(pv.Elem()) } else { return errors.New("slice type") } } else if sliceElementType.Kind() == reflect.Struct { - table = session.Engine.autoMapType(sliceElementType) + pv := reflect.New(sliceElementType) + table = session.Engine.autoMapType(pv.Elem()) } else { return errors.New("slice type") } @@ -1386,13 +1388,12 @@ func (session *Session) getField(dataStruct *reflect.Value, key string, table *T } func (session *Session) row2Bean(rows *sql.Rows, fields []string, fieldsCount int, bean interface{}) error { - - dataStruct := reflect.Indirect(reflect.ValueOf(bean)) + dataStruct := rValue(bean) if dataStruct.Kind() != reflect.Struct { return errors.New("Expected a pointer to a struct") } - table := session.Engine.autoMapType(rType(bean)) + table := session.Engine.autoMapType(dataStruct) var scanResultContainers []interface{} for i := 0; i < fieldsCount; i++ { @@ -1494,7 +1495,7 @@ func (session *Session) row2Bean(rows *sql.Rows, fields []string, fieldsCount in fieldValue.Set(vv) } } else if session.Statement.UseCascade { - table := session.Engine.autoMapType(fieldValue.Type()) + table := session.Engine.autoMapType(*fieldValue) if table != nil { var x int64 if rawValueType.Kind() == reflect.Int64 { @@ -1763,9 +1764,10 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error } bean := sliceValue.Index(0).Interface() - sliceElementType := rType(bean) + elementValue := rValue(bean) + //sliceElementType := elementValue.Type() - table := session.Engine.autoMapType(sliceElementType) + table := session.Engine.autoMapType(elementValue) session.Statement.RefTable = table size := sliceValue.Len() @@ -2073,7 +2075,7 @@ func (session *Session) bytes2Value(col *Column, fieldValue *reflect.Value, data v = x fieldValue.Set(reflect.ValueOf(v)) } else if session.Statement.UseCascade { - table := session.Engine.autoMapType(fieldValue.Type()) + table := session.Engine.autoMapType(*fieldValue) if table != nil { x, err := strconv.ParseInt(string(data), 10, 64) if err != nil { diff --git a/statement.go b/statement.go index 805f5955..773dd378 100644 --- a/statement.go +++ b/statement.go @@ -113,11 +113,12 @@ func (statement *Statement) Or(querystring string, args ...interface{}) *Stateme // tempororily set table name func (statement *Statement) Table(tableNameOrBean interface{}) *Statement { - t := rType(tableNameOrBean) + v := rValue(tableNameOrBean) + t := v.Type() if t.Kind() == reflect.String { statement.AltTableName = tableNameOrBean.(string) } else if t.Kind() == reflect.Struct { - statement.RefTable = statement.Engine.autoMapType(t) + statement.RefTable = statement.Engine.autoMapType(v) } return statement } @@ -342,7 +343,7 @@ func buildConditions(engine *Engine, table *Table, bean interface{}, val = t } } else { - engine.autoMapType(fieldValue.Type()) + engine.autoMapType(fieldValue) if table, ok := engine.Tables[fieldValue.Type()]; ok { if len(table.PrimaryKeys) == 1 { pkField := reflect.Indirect(fieldValue).FieldByName(table.PKColumns()[0].FieldName) From 61813611169ae385bb2bca36e74015d774e4bd35 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 8 Apr 2014 16:50:43 +0800 Subject: [PATCH 33/33] docs added TableName --- docs/QuickStart.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/QuickStart.md b/docs/QuickStart.md index d787cb0b..c00e9727 100644 --- a/docs/QuickStart.md +++ b/docs/QuickStart.md @@ -141,7 +141,9 @@ engine.SetColumnMapper(SnakeMapper{}) 如果所有的命名都是按照IMapper的映射来操作的,那当然是最理想的。但是如果碰到某个表名或者某个字段名跟映射规则不匹配时,我们就需要别的机制来改变。 -通过`engine.Table()`方法可以改变struct对应的数据库表的名称,通过sturct中field对应的Tag中使用`xorm:"'column_name'"`可以使该field对应的Column名称为指定名称。这里使用两个单引号将Column名称括起来是为了防止名称冲突,因为我们在Tag中还可以对这个Column进行更多的定义。如果名称不冲突的情况,单引号也可以不使用。 +* 如果struct拥有`Tablename() string`的成员方法,那么此方法的返回值即是该struct默认对应的数据库表名。 + +* 通过`engine.Table()`方法可以改变struct对应的数据库表的名称,通过sturct中field对应的Tag中使用`xorm:"'column_name'"`可以使该field对应的Column名称为指定名称。这里使用两个单引号将Column名称括起来是为了防止名称冲突,因为我们在Tag中还可以对这个Column进行更多的定义。如果名称不冲突的情况,单引号也可以不使用。 ### 2.4.Column属性定义