diff --git a/.gopmfile b/.gopmfile index 3590b536..ecd6e20e 100644 --- a/.gopmfile +++ b/.gopmfile @@ -1,2 +1,2 @@ [target] -path = github.com/lunny/xorm \ No newline at end of file +path = github.com/go-xorm/xorm \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6f65c2ae..fcbf9e31 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,11 +21,11 @@ We appreciate any bug reports, but especially ones with self-contained further) test cases. It's especially helpful if you can submit a pull request with just the failing test case (you'll probably want to pattern it after the tests in -[base_test.go](https://github.com/lunny/xorm/blob/master/base_test.go) AND -[benchmark_base_test.go](https://github.com/lunny/xorm/blob/master/benchmark_base_test.go). +[base_test.go](https://github.com/go-xorm/xorm/blob/master/base_test.go) AND +[benchmark_base_test.go](https://github.com/go-xorm/xorm/blob/master/benchmark_base_test.go). If you implements a new database interface, you maybe need to add a _test.go file. -For example, [mysql_test.go](https://github.com/lunny/xorm/blob/master/mysql_test.go) +For example, [mysql_test.go](https://github.com/go-xorm/xorm/blob/master/mysql_test.go) ### New functionality diff --git a/README.md b/README.md index a2a46fbb..9c9ecfa7 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -[中文](https://github.com/lunny/xorm/blob/master/README_CN.md) +[中文](https://github.com/go-xorm/xorm/blob/master/README_CN.md) Xorm is a simple and powerful ORM for Go. -[![Build Status](https://drone.io/github.com/go-xorm/xorm/status.png)](https://drone.io/github.com/go-xorm/xorm/latest) [![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/go-xorm/xorm) [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/lunny/go-xorm/trend.png)](https://bitdeli.com/free "Bitdeli Badge") +[![Build Status](https://drone.io/github.com/go-xorm/xorm/status.png)](https://drone.io/github.com/go-xorm/xorm/latest) [![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/go-xorm/xorm) [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/lunny/xorm/trend.png)](https://bitdeli.com/free "Bitdeli Badge") # Features @@ -41,6 +41,17 @@ Drivers for Go's sql package which currently support database/sql includes: # Changelog +* **v0.4.0 RC1** + Changes: + * moved xorm cmd to [github.com/go-xorm/cmd](github.com/go-xorm/cmd) + * refactored general DB operation a core lib at [github.com/go-xorm/core](https://github.com/go-xorm/core) + * moved tests to github.com/go-xorm/tests [github.com/go-xorm/tests](github.com/go-xorm/tests) + + Improvements: + * Prepared statement cache + * Add Incr API + * Specify Timezone Location + * **v0.3.2** Improvements: * Add AllCols & MustCols function diff --git a/README_CN.md b/README_CN.md index e36eec2e..c8f3f180 100644 --- a/README_CN.md +++ b/README_CN.md @@ -1,10 +1,10 @@ # xorm -[English](https://github.com/lunny/xorm/blob/master/README.md) +[English](https://github.com/go-xorm/xorm/blob/master/README.md) xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作非常简便。 -[![Build Status](https://drone.io/github.com/lunny/xorm/status.png)](https://drone.io/github.com/lunny/xorm/latest) [![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/lunny/xorm) +[![Build Status](https://drone.io/github.com/go-xorm/xorm/status.png)](https://drone.io/github.com/go-xorm/xorm/latest) [![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/go-xorm/xorm) ## 特性 @@ -42,12 +42,23 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作 ## 更新日志 +* **v0.4.0 RC1** + 新特性: + * 移动xorm cmd [github.com/go-xorm/cmd](github.com/go-xorm/cmd) + * 在重构一般DB操作核心库 [github.com/go-xorm/core](https://github.com/go-xorm/core) + * 移动测试github.com/复XORM/测试 [github.com/go-xorm/tests](github.com/go-xorm/tests) + + 改进: + * Prepared statement 缓存 + * 添加 Incr API + * 指定时区位置 + * **v0.3.2** - Improvements: + 新特性: * Add AllCols & MustCols function * Add TableName for custom table name - Bug Fixes: + Bug 修复: * #46 * #51 * #53 @@ -69,25 +80,25 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作 * 查询函数 Get()/Find()/Iterate() 在性能上的改进 -[更多更新日志...](https://github.com/lunny/xorm/blob/master/docs/ChangelogCN.md) +[更多更新日志...](https://github.com/go-xorm/xorm/blob/master/docs/ChangelogCN.md) ## 安装 推荐使用 [gopm](https://github.com/gpmgo/gopm) 进行安装: - gopm get github.com/lunny/xorm + gopm get github.com/go-xorm/xorm 或者您也可以使用go工具进行安装: - go get github.com/lunny/xorm + go get github.com/go-xorm/xorm ## 文档 -* [快速开始](https://github.com/lunny/xorm/blob/master/docs/QuickStart.md) +* [快速开始](https://github.com/go-xorm/xorm/blob/master/docs/QuickStart.md) -* [GoWalker代码文档](http://gowalker.org/github.com/lunny/xorm) +* [GoWalker代码文档](http://gowalker.org/github.com/go-xorm/xorm) -* [Godoc代码文档](http://godoc.org/github.com/lunny/xorm) +* [Godoc代码文档](http://godoc.org/github.com/go-xorm/xorm) ## 案例 @@ -115,7 +126,7 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作 # 贡献者 -如果您也想为Xorm贡献您的力量,请查看 [CONTRIBUTING](https://github.com/lunny/xorm/blob/master/CONTRIBUTING.md) +如果您也想为Xorm贡献您的力量,请查看 [CONTRIBUTING](https://github.com/go-xorm/xorm/blob/master/CONTRIBUTING.md) * [Lunny](https://github.com/lunny) * [Nashtsai](https://github.com/nashtsai) diff --git a/VERSION b/VERSION index df1861e4..eb1bde3d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -xorm v0.3.2 +xorm v0.4.0 RC1 diff --git a/base_test.go b/base_test.go deleted file mode 100644 index f8d9d794..00000000 --- a/base_test.go +++ /dev/null @@ -1,4151 +0,0 @@ -package xorm - -import ( - "errors" - "fmt" - "strings" - "testing" - "time" -) - -/* -CREATE TABLE `userinfo` ( - `id` INT(10) NULL AUTO_INCREMENT, - `username` VARCHAR(64) NULL, - `departname` VARCHAR(64) NULL, - `created` DATE NULL, - PRIMARY KEY (`uid`) -); -CREATE TABLE `userdeatail` ( - `id` INT(10) NULL, - `intro` TEXT NULL, - `profile` TEXT NULL, - PRIMARY KEY (`uid`) -); -*/ - -type Userinfo struct { - Uid int64 `xorm:"id pk not null autoincr"` - Username string `xorm:"unique"` - Departname string - Alias string `xorm:"-"` - Created time.Time - Detail Userdetail `xorm:"detail_id int(11)"` - Height float64 - Avatar []byte - IsMan bool -} - -type Userdetail struct { - Id int64 - Intro string `xorm:"text"` - Profile string `xorm:"varchar(2000)"` -} - -func directCreateTable(engine *Engine, t *testing.T) { - err := engine.DropTables(&Userinfo{}, &Userdetail{}) - if err != nil { - t.Error(err) - panic(err) - } - - err = engine.Sync(&Userinfo{}, &Userdetail{}) - if err != nil { - t.Error(err) - panic(err) - } - - err = engine.DropTables(&Userinfo{}, &Userdetail{}) - if err != nil { - t.Error(err) - panic(err) - } - - err = engine.CreateTables(&Userinfo{}, &Userdetail{}) - if err != nil { - t.Error(err) - panic(err) - } - - err = engine.CreateIndexes(&Userinfo{}) - if err != nil { - t.Error(err) - panic(err) - } - - err = engine.CreateIndexes(&Userdetail{}) - if err != nil { - t.Error(err) - panic(err) - } - - err = engine.CreateUniques(&Userinfo{}) - if err != nil { - t.Error(err) - panic(err) - } - - err = engine.CreateUniques(&Userdetail{}) - if err != nil { - t.Error(err) - panic(err) - } -} - -func insert(engine *Engine, t *testing.T) { - user := Userinfo{0, "xiaolunwen", "dev", "lunny", time.Now(), - Userdetail{Id: 1}, 1.78, []byte{1, 2, 3}, true} - cnt, err := engine.Insert(&user) - fmt.Println(user.Uid) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert not returned 1") - t.Error(err) - panic(err) - return - } - if user.Uid <= 0 { - err = errors.New("not return id error") - t.Error(err) - panic(err) - } - - user.Uid = 0 - cnt, err = engine.Insert(&user) - if err == nil { - err = errors.New("insert failed but no return error") - t.Error(err) - panic(err) - } - if cnt != 0 { - err = errors.New("insert not returned 1") - t.Error(err) - panic(err) - return - } -} - -func testQuery(engine *Engine, t *testing.T) { - sql := "select * from userinfo" - results, err := engine.Query(sql) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(results) -} - -func exec(engine *Engine, t *testing.T) { - sql := "update userinfo set username=? where id=?" - res, err := engine.Exec(sql, "xiaolun", 1) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(res) -} - -func querySameMapper(engine *Engine, t *testing.T) { - sql := "select * from `Userinfo`" - results, err := engine.Query(sql) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(results) -} - -func execSameMapper(engine *Engine, t *testing.T) { - sql := "update `Userinfo` set `Username`=? where (id)=?" - res, err := engine.Exec(sql, "xiaolun", 1) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(res) -} - -func insertAutoIncr(engine *Engine, t *testing.T) { - // auto increment insert - user := Userinfo{Username: "xiaolunwen2", Departname: "dev", Alias: "lunny", Created: time.Now(), - Detail: Userdetail{Id: 1}, Height: 1.78, Avatar: []byte{1, 2, 3}, IsMan: true} - cnt, err := engine.Insert(&user) - fmt.Println(user.Uid) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert not returned 1") - t.Error(err) - panic(err) - return - } - if user.Uid <= 0 { - t.Error(errors.New("not return id error")) - } -} - -type BigInsert struct { -} - -func insertDefault(engine *Engine, t *testing.T) { - -} - -func insertMulti(engine *Engine, t *testing.T) { - //engine.InsertMany = true - users := []Userinfo{ - {Username: "xlw", Departname: "dev", Alias: "lunny2", Created: time.Now()}, - {Username: "xlw2", Departname: "dev", Alias: "lunny3", Created: time.Now()}, - {Username: "xlw11", Departname: "dev", Alias: "lunny2", Created: time.Now()}, - {Username: "xlw22", Departname: "dev", Alias: "lunny3", Created: time.Now()}, - } - cnt, err := engine.Insert(&users) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != int64(len(users)) { - err = errors.New("insert not returned 1") - t.Error(err) - panic(err) - return - } - - users2 := []*Userinfo{ - &Userinfo{Username: "1xlw", Departname: "dev", Alias: "lunny2", Created: time.Now()}, - &Userinfo{Username: "1xlw2", Departname: "dev", Alias: "lunny3", Created: time.Now()}, - &Userinfo{Username: "1xlw11", Departname: "dev", Alias: "lunny2", Created: time.Now()}, - &Userinfo{Username: "1xlw22", Departname: "dev", Alias: "lunny3", Created: time.Now()}, - } - - cnt, err = engine.Insert(&users2) - if err != nil { - t.Error(err) - panic(err) - } - - if cnt != int64(len(users2)) { - err = errors.New(fmt.Sprintf("insert not returned %v", len(users2))) - t.Error(err) - panic(err) - return - } -} - -func insertTwoTable(engine *Engine, t *testing.T) { - userdetail := Userdetail{ /*Id: 1, */ Intro: "I'm a very beautiful women.", Profile: "sfsaf"} - userinfo := Userinfo{Username: "xlw3", Departname: "dev", Alias: "lunny4", Created: time.Now(), Detail: userdetail} - - cnt, err := engine.Insert(&userinfo, &userdetail) - if err != nil { - t.Error(err) - panic(err) - } - - if userinfo.Uid <= 0 { - err = errors.New("not return id error") - t.Error(err) - panic(err) - } - - if userdetail.Id <= 0 { - err = errors.New("not return id error") - t.Error(err) - panic(err) - } - - if cnt != 2 { - err = errors.New("insert not returned 2") - t.Error(err) - panic(err) - return - } -} - -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) { - // update by id - user := Userinfo{Username: "xxx", Height: 1.2} - cnt, err := engine.Id(1).Update(&user) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("update not returned 1") - t.Error(err) - panic(err) - return - } - - condi := Condi{"username": "zzz", "height": 0.0, "departname": ""} - cnt, err = engine.Table(&user).Id(1).Update(&condi) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("update not returned 1") - t.Error(err) - panic(err) - return - } - - cnt, err = engine.Update(&Userinfo{Username: "yyy"}, &user) - if err != nil { - t.Error(err) - panic(err) - } - total, err := engine.Count(&user) - if err != nil { - t.Error(err) - panic(err) - } - - if cnt != total { - err = errors.New("insert not returned 1") - t.Error(err) - panic(err) - return - } - - err = engine.Sync(&Article{}) - if err != nil { - t.Error(err) - panic(err) - } - - 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(fmt.Sprintf("insert not returned 1 but %d", cnt)) - t.Error(err) - panic(err) - } - - 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(fmt.Sprintf("insert not returned 1 but %d", cnt)) - t.Error(err) - 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 - } - - { - 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) { - // update by id - user := Userinfo{Username: "xxx", Height: 1.2} - cnt, err := engine.Id(1).Update(&user) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("update not returned 1") - t.Error(err) - panic(err) - return - } - - condi := Condi{"Username": "zzz", "Height": 0.0, "Departname": ""} - cnt, err = engine.Table(&user).Id(1).Update(&condi) - 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.Update(&Userinfo{Username: "yyy"}, &user) - if err != nil { - t.Error(err) - panic(err) - } - - if cnt != 1 { - err = errors.New("insert not returned 1") - t.Error(err) - panic(err) - return - } -} - -func testDelete(engine *Engine, t *testing.T) { - user := Userinfo{Uid: 1} - cnt, err := engine.Delete(&user) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("delete not returned 1") - t.Error(err) - panic(err) - return - } - - user.Uid = 0 - has, err := engine.Id(3).Get(&user) - if err != nil { - t.Error(err) - panic(err) - } - - if has { - //var tt time.Time - //user.Created = tt - cnt, err := engine.UseBool().Delete(&user) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - t.Error(errors.New("delete failed")) - panic(err) - } - } -} - -type NoIdUser struct { - User string `xorm:"unique"` - Remain int64 - Total int64 -} - -func get(engine *Engine, t *testing.T) { - user := Userinfo{Uid: 2} - - has, err := engine.Get(&user) - if err != nil { - t.Error(err) - panic(err) - } - if has { - fmt.Println(user) - } else { - fmt.Println("no record id is 2") - } - - err = engine.Sync(&NoIdUser{}) - if err != nil { - t.Error(err) - panic(err) - } - - _, err = engine.Where("`user` = ?", "xlw").Delete(&NoIdUser{}) - if err != nil { - t.Error(err) - panic(err) - } - - cnt, err := engine.Insert(&NoIdUser{"xlw", 20, 100}) - if err != nil { - t.Error(err) - panic(err) - } - - if cnt != 1 { - err = errors.New("insert not returned 1") - t.Error(err) - panic(err) - } - - noIdUser := new(NoIdUser) - has, err = engine.Where("`user` = ?", "xlw").Get(noIdUser) - if err != nil { - t.Error(err) - panic(err) - } - - if !has { - err = errors.New("get not returned 1") - t.Error(err) - panic(err) - } - fmt.Println(noIdUser) -} - -func cascadeGet(engine *Engine, t *testing.T) { - user := Userinfo{Uid: 11} - - has, err := engine.Get(&user) - if err != nil { - t.Error(err) - panic(err) - } - if has { - fmt.Println(user) - } else { - fmt.Println("no record id is 2") - } -} - -func find(engine *Engine, t *testing.T) { - users := make([]Userinfo, 0) - - err := engine.Find(&users) - if err != nil { - t.Error(err) - panic(err) - } - for _, user := range users { - fmt.Println(user) - } - - users2 := make([]Userinfo, 0) - err = engine.Sql("select * from userinfo").Find(&users2) - if err != nil { - t.Error(err) - panic(err) - } -} - -func find2(engine *Engine, t *testing.T) { - users := make([]*Userinfo, 0) - - err := engine.Find(&users) - if err != nil { - t.Error(err) - panic(err) - } - for _, user := range users { - fmt.Println(user) - } -} - -func findMap(engine *Engine, t *testing.T) { - users := make(map[int64]Userinfo) - - err := engine.Find(&users) - if err != nil { - t.Error(err) - panic(err) - } - for _, user := range users { - fmt.Println(user) - } -} - -func findMap2(engine *Engine, t *testing.T) { - users := make(map[int64]*Userinfo) - - err := engine.Find(&users) - if err != nil { - t.Error(err) - panic(err) - } - for id, user := range users { - fmt.Println(id, user) - } -} - -func count(engine *Engine, t *testing.T) { - user := Userinfo{Departname: "dev"} - total, err := engine.Count(&user) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Printf("Total %d records!!!\n", total) -} - -func where(engine *Engine, t *testing.T) { - users := make([]Userinfo, 0) - err := engine.Where("(id) > ?", 2).Find(&users) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(users) - - err = engine.Where("(id) > ?", 2).And("(id) < ?", 10).Find(&users) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(users) -} - -func in(engine *Engine, t *testing.T) { - users := make([]Userinfo, 0) - 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) - - 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) - } - } - - err = engine.In("(id)", 1).In("(id)", 2).In("departname", "dev").Find(&users) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(users) - - cnt, err := engine.In("(id)", 4).Update(&Userinfo{Departname: "dev-"}) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("update records not 1") - t.Error(err) - panic(err) - } - - user := new(Userinfo) - has, err := engine.Id(4).Get(user) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - err = errors.New("get record not 1") - t.Error(err) - panic(err) - } - if user.Departname != "dev-" { - err = errors.New("update not success") - t.Error(err) - panic(err) - } - - cnt, err = engine.In("(id)", 4).Update(&Userinfo{Departname: "dev"}) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("update records not 1") - t.Error(err) - panic(err) - } - - cnt, err = engine.In("(id)", 5).Delete(&Userinfo{}) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("deleted records not 1") - t.Error(err) - panic(err) - } -} - -func limit(engine *Engine, t *testing.T) { - users := make([]Userinfo, 0) - err := engine.Limit(2, 1).Find(&users) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(users) -} - -func order(engine *Engine, t *testing.T) { - users := make([]Userinfo, 0) - err := engine.OrderBy("id desc").Find(&users) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(users) - - users2 := make([]Userinfo, 0) - err = engine.Asc("id", "username").Desc("height").Find(&users2) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(users2) -} - -func join(engine *Engine, t *testing.T) { - users := make([]Userinfo, 0) - err := engine.Join("LEFT", "userdetail", "userinfo.id=userdetail.id").Find(&users) - if err != nil { - t.Error(err) - panic(err) - } -} - -func having(engine *Engine, t *testing.T) { - users := make([]Userinfo, 0) - err := engine.GroupBy("username").Having("username='xlw'").Find(&users) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(users) -} - -func orderSameMapper(engine *Engine, t *testing.T) { - users := make([]Userinfo, 0) - err := engine.OrderBy("(id) desc").Find(&users) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(users) - - users2 := make([]Userinfo, 0) - err = engine.Asc("(id)", "Username").Desc("Height").Find(&users2) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(users2) -} - -func joinSameMapper(engine *Engine, t *testing.T) { - users := make([]Userinfo, 0) - err := engine.Join("LEFT", `"Userdetail"`, `"Userinfo"."id"="Userdetail"."Id"`).Find(&users) - if err != nil { - t.Error(err) - panic(err) - } -} - -func havingSameMapper(engine *Engine, t *testing.T) { - users := make([]Userinfo, 0) - err := engine.GroupBy("Username").Having(`"Username"='xlw'`).Find(&users) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(users) -} - -func transaction(engine *Engine, t *testing.T) { - counter := func() { - total, err := engine.Count(&Userinfo{}) - if err != nil { - t.Error(err) - } - fmt.Printf("----now total %v records\n", total) - } - - counter() - defer counter() - session := engine.NewSession() - defer session.Close() - - err := session.Begin() - if err != nil { - t.Error(err) - panic(err) - } - //session.IsAutoRollback = false - user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()} - _, err = session.Insert(&user1) - if err != nil { - session.Rollback() - t.Error(err) - panic(err) - } - - user2 := Userinfo{Username: "yyy"} - _, err = session.Where("(id) = ?", 0).Update(&user2) - if err != nil { - session.Rollback() - fmt.Println(err) - //t.Error(err) - return - } - - _, err = session.Delete(&user2) - if err != nil { - session.Rollback() - t.Error(err) - panic(err) - } - - err = session.Commit() - if err != nil { - t.Error(err) - panic(err) - } - // panic(err) !nashtsai! should remove this -} - -func combineTransaction(engine *Engine, t *testing.T) { - counter := func() { - total, err := engine.Count(&Userinfo{}) - if err != nil { - t.Error(err) - } - fmt.Printf("----now total %v records\n", total) - } - - counter() - defer counter() - session := engine.NewSession() - defer session.Close() - - err := session.Begin() - if err != nil { - t.Error(err) - panic(err) - } - - user1 := Userinfo{Username: "xiaoxiao2", Departname: "dev", Alias: "lunny", Created: time.Now()} - _, err = session.Insert(&user1) - if err != nil { - session.Rollback() - t.Error(err) - panic(err) - } - user2 := Userinfo{Username: "zzz"} - _, err = session.Where("id = ?", 0).Update(&user2) - if err != nil { - session.Rollback() - t.Error(err) - panic(err) - } - - _, err = session.Exec("delete from userinfo where username = ?", user2.Username) - if err != nil { - session.Rollback() - t.Error(err) - panic(err) - } - - err = session.Commit() - if err != nil { - t.Error(err) - panic(err) - } -} - -func combineTransactionSameMapper(engine *Engine, t *testing.T) { - counter := func() { - total, err := engine.Count(&Userinfo{}) - if err != nil { - t.Error(err) - } - fmt.Printf("----now total %v records\n", total) - } - - counter() - defer counter() - session := engine.NewSession() - defer session.Close() - - err := session.Begin() - if err != nil { - t.Error(err) - panic(err) - } - //session.IsAutoRollback = false - user1 := Userinfo{Username: "xiaoxiao2", Departname: "dev", Alias: "lunny", Created: time.Now()} - _, err = session.Insert(&user1) - if err != nil { - session.Rollback() - t.Error(err) - panic(err) - } - user2 := Userinfo{Username: "zzz"} - _, err = session.Where("(id) = ?", 0).Update(&user2) - if err != nil { - session.Rollback() - t.Error(err) - panic(err) - } - - _, err = session.Exec("delete from `Userinfo` where `Username` = ?", user2.Username) - if err != nil { - session.Rollback() - t.Error(err) - panic(err) - } - - err = session.Commit() - if err != nil { - t.Error(err) - panic(err) - } -} - -func table(engine *Engine, t *testing.T) { - err := engine.DropTables("user_user") - if err != nil { - t.Error(err) - panic(err) - } - - err = engine.Table("user_user").CreateTable(&Userinfo{}) - if err != nil { - t.Error(err) - panic(err) - } -} - -func createMultiTables(engine *Engine, t *testing.T) { - session := engine.NewSession() - defer session.Close() - - user := &Userinfo{} - err := session.Begin() - if err != nil { - t.Error(err) - panic(err) - } - for i := 0; i < 10; i++ { - tableName := fmt.Sprintf("user_%v", i) - - err = session.DropTable(tableName) - if err != nil { - session.Rollback() - t.Error(err) - panic(err) - } - - err = session.Table(tableName).CreateTable(user) - if err != nil { - session.Rollback() - t.Error(err) - panic(err) - } - } - err = session.Commit() - if err != nil { - t.Error(err) - panic(err) - } -} - -func tableOp(engine *Engine, t *testing.T) { - user := Userinfo{Username: "tablexiao", Departname: "dev", Alias: "lunny", Created: time.Now()} - tableName := fmt.Sprintf("user_%v", len(user.Username)) - cnt, err := engine.Table(tableName).Insert(&user) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert not returned 1") - t.Error(err) - panic(err) - return - } - - has, err := engine.Table(tableName).Get(&Userinfo{Username: "tablexiao"}) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - err = errors.New("Get has return false") - t.Error(err) - panic(err) - return - } - - users := make([]Userinfo, 0) - err = engine.Table(tableName).Find(&users) - if err != nil { - t.Error(err) - panic(err) - } - - id := user.Uid - cnt, err = engine.Table(tableName).Id(id).Update(&Userinfo{Username: "tableda"}) - if err != nil { - t.Error(err) - panic(err) - } - - _, err = engine.Table(tableName).Id(id).Delete(&Userinfo{}) - if err != nil { - t.Error(err) - panic(err) - } -} - -func testCharst(engine *Engine, t *testing.T) { - err := engine.DropTables("user_charset") - if err != nil { - t.Error(err) - panic(err) - } - - err = engine.Charset("utf8").Table("user_charset").CreateTable(&Userinfo{}) - if err != nil { - t.Error(err) - panic(err) - } -} - -func testStoreEngine(engine *Engine, t *testing.T) { - err := engine.DropTables("user_store_engine") - if err != nil { - t.Error(err) - panic(err) - } - - err = engine.StoreEngine("InnoDB").Table("user_store_engine").CreateTable(&Userinfo{}) - if err != nil { - t.Error(err) - panic(err) - } -} - -type tempUser struct { - Id int64 - Username string -} - -func testCols(engine *Engine, t *testing.T) { - users := []Userinfo{} - err := engine.Cols("id, username").Find(&users) - if err != nil { - t.Error(err) - panic(err) - } - - fmt.Println(users) - - tmpUsers := []tempUser{} - err = engine.NoCache().Table("userinfo").Cols("id, username").Find(&tmpUsers) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(tmpUsers) - - user := &Userinfo{Uid: 1, Alias: "", Height: 0} - affected, err := engine.Cols("departname, height").Id(1).Update(user) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println("===================", user, affected) -} - -func testColsSameMapper(engine *Engine, t *testing.T) { - users := []Userinfo{} - err := engine.Cols("(id), Username").Find(&users) - if err != nil { - t.Error(err) - panic(err) - } - - fmt.Println(users) - - tmpUsers := []tempUser{} - err = engine.Table("Userinfo").Cols("(id), Username").Find(&tmpUsers) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(tmpUsers) - - user := &Userinfo{Uid: 1, Alias: "", Height: 0} - affected, err := engine.Cols("Departname, Height").Update(user) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println("===================", user, affected) -} - -type tempUser2 struct { - tempUser `xorm:"extends"` - Departname string -} - -func testExtends(engine *Engine, t *testing.T) { - err := engine.DropTables(&tempUser2{}) - if err != nil { - t.Error(err) - panic(err) - } - - err = engine.CreateTables(&tempUser2{}) - if err != nil { - t.Error(err) - panic(err) - } - - tu := &tempUser2{tempUser{0, "extends"}, "dev depart"} - _, err = engine.Insert(tu) - if err != nil { - t.Error(err) - panic(err) - } - - tu2 := &tempUser2{} - _, err = engine.Get(tu2) - if err != nil { - t.Error(err) - panic(err) - } - - tu3 := &tempUser2{tempUser{0, "extends update"}, ""} - _, err = engine.Id(tu2.Id).Update(tu3) - if err != nil { - t.Error(err) - panic(err) - } -} - -type allCols struct { - Bit int `xorm:"BIT"` - TinyInt int8 `xorm:"TINYINT"` - SmallInt int16 `xorm:"SMALLINT"` - MediumInt int32 `xorm:"MEDIUMINT"` - Int int `xorm:"INT"` - Integer int `xorm:"INTEGER"` - BigInt int64 `xorm:"BIGINT"` - - Char string `xorm:"CHAR(12)"` - Varchar string `xorm:"VARCHAR(54)"` - TinyText string `xorm:"TINYTEXT"` - Text string `xorm:"TEXT"` - MediumText string `xorm:"MEDIUMTEXT"` - LongText string `xorm:"LONGTEXT"` - Binary []byte `xorm:"BINARY(23)"` - VarBinary []byte `xorm:"VARBINARY(12)"` - - Date time.Time `xorm:"DATE"` - DateTime time.Time `xorm:"DATETIME"` - Time time.Time `xorm:"TIME"` - TimeStamp time.Time `xorm:"TIMESTAMP"` - - Decimal float64 `xorm:"DECIMAL"` - Numeric float64 `xorm:"NUMERIC"` - - Real float32 `xorm:"REAL"` - Float float32 `xorm:"FLOAT"` - Double float64 `xorm:"DOUBLE"` - - TinyBlob []byte `xorm:"TINYBLOB"` - Blob []byte `xorm:"BLOB"` - MediumBlob []byte `xorm:"MEDIUMBLOB"` - LongBlob []byte `xorm:"LONGBLOB"` - Bytea []byte `xorm:"BYTEA"` - - Bool bool `xorm:"BOOL"` - - Serial int `xorm:"SERIAL"` - //BigSerial int64 `xorm:"BIGSERIAL"` -} - -func testColTypes(engine *Engine, t *testing.T) { - err := engine.DropTables(&allCols{}) - if err != nil { - t.Error(err) - panic(err) - } - - err = engine.CreateTables(&allCols{}) - if err != nil { - t.Error(err) - panic(err) - } - - ac := &allCols{ - 1, - 4, - 8, - 16, - 32, - 64, - 128, - - "123", - "fafdafa", - "fafafafdsafdsafdaf", - "fdsafafdsafdsaf", - "fafdsafdsafdsfadasfsfafd", - "fadfdsafdsafasfdasfds", - []byte("fdafsafdasfdsafsa"), - []byte("fdsafsdafs"), - - time.Now(), - time.Now(), - time.Now(), - time.Now(), - - 1.34, - 2.44302346, - - 1.3344, - 2.59693523, - 3.2342523543, - - []byte("fafdasf"), - []byte("fafdfdsafdsafasf"), - []byte("faffadsfdsdasf"), - []byte("faffdasfdsadasf"), - []byte("fafasdfsadffdasf"), - - true, - - 0, - //21, - } - - cnt, err := engine.Insert(ac) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert return not 1") - t.Error(err) - panic(err) - } - newAc := &allCols{} - has, err := engine.Get(newAc) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - err = errors.New("error no ideas") - t.Error(err) - panic(err) - } - - // don't use this type as query condition - newAc.Real = 0 - newAc.Float = 0 - newAc.Double = 0 - newAc.LongText = "" - newAc.TinyText = "" - newAc.MediumText = "" - newAc.Text = "" - cnt, err = engine.Delete(newAc) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New(fmt.Sprintf("delete error, deleted counts is %v", cnt)) - t.Error(err) - panic(err) - } -} - -type MyInt int -type MyUInt uint -type MyFloat float64 -type MyString string - -/*func (s *MyString) FromDB(data []byte) error { - reflect. - s MyString(data) - return nil -} - -func (s *MyString) ToDB() ([]byte, error) { - return []byte(string(*s)), nil -}*/ - -type MyStruct struct { - Type MyInt - U MyUInt - F MyFloat - S MyString - IA []MyInt - UA []MyUInt - FA []MyFloat - SA []MyString - NameArray []string - Name string - UIA []uint - UIA8 []uint8 - UIA16 []uint16 - UIA32 []uint32 - UIA64 []uint64 - UI uint - //C64 complex64 - MSS map[string]string -} - -func testCustomType(engine *Engine, t *testing.T) { - err := engine.DropTables(&MyStruct{}) - if err != nil { - t.Error(err) - panic(err) - return - } - - err = engine.CreateTables(&MyStruct{}) - i := MyStruct{Name: "Test", Type: MyInt(1)} - i.U = 23 - i.F = 1.34 - i.S = "fafdsafdsaf" - i.UI = 2 - i.IA = []MyInt{1, 3, 5} - i.UIA = []uint{1, 3} - i.UIA16 = []uint16{2} - i.UIA32 = []uint32{4, 5} - i.UIA64 = []uint64{6, 7, 9} - i.UIA8 = []uint8{1, 2, 3, 4} - i.NameArray = []string{"ssss", "fsdf", "lllll, ss"} - i.MSS = map[string]string{"s": "sfds,ss", "x": "lfjljsl"} - cnt, err := engine.Insert(&i) - if err != nil { - t.Error(err) - panic(err) - return - } - if cnt != 1 { - err = errors.New("insert not returned 1") - t.Error(err) - panic(err) - return - } - - fmt.Println(i) - i.NameArray = []string{} - i.MSS = map[string]string{} - i.F = 0 - has, err := engine.Get(&i) - if err != nil { - t.Error(err) - panic(err) - } else if !has { - t.Error(errors.New("should get one record")) - panic(err) - } - - ss := []MyStruct{} - err = engine.Find(&ss) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(ss) - - sss := MyStruct{} - has, err = engine.Get(&sss) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(sss) - - if has { - sss.NameArray = []string{} - sss.MSS = map[string]string{} - cnt, err := engine.Delete(&sss) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - t.Error(errors.New("delete error")) - panic(err) - } - } -} - -type UserCU struct { - Id int64 - Name string - Created time.Time `xorm:"created"` - Updated time.Time `xorm:"updated"` -} - -func testCreatedAndUpdated(engine *Engine, t *testing.T) { - u := new(UserCU) - err := engine.DropTables(u) - if err != nil { - t.Error(err) - panic(err) - } - - err = engine.CreateTables(u) - if err != nil { - t.Error(err) - panic(err) - } - - u.Name = "sss" - cnt, err := engine.Insert(u) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert not returned 1") - t.Error(err) - panic(err) - return - } - - u.Name = "xxx" - cnt, err = engine.Id(u.Id).Update(u) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("update not returned 1") - t.Error(err) - panic(err) - return - } - - u.Id = 0 - u.Created = time.Now().Add(-time.Hour * 24 * 365) - u.Updated = u.Created - fmt.Println(u) - cnt, err = engine.NoAutoTime().Insert(u) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert not returned 1") - t.Error(err) - panic(err) - return - } -} - -type IndexOrUnique struct { - Id int64 - Index int `xorm:"index"` - Unique int `xorm:"unique"` - Group1 int `xorm:"index(ttt)"` - Group2 int `xorm:"index(ttt)"` - UniGroup1 int `xorm:"unique(lll)"` - UniGroup2 int `xorm:"unique(lll)"` -} - -func testIndexAndUnique(engine *Engine, t *testing.T) { - err := engine.CreateTables(&IndexOrUnique{}) - if err != nil { - t.Error(err) - //panic(err) - } - - err = engine.DropTables(&IndexOrUnique{}) - if err != nil { - t.Error(err) - //panic(err) - } - - err = engine.CreateTables(&IndexOrUnique{}) - if err != nil { - t.Error(err) - //panic(err) - } - - err = engine.CreateIndexes(&IndexOrUnique{}) - if err != nil { - t.Error(err) - //panic(err) - } - - err = engine.CreateUniques(&IndexOrUnique{}) - if err != nil { - t.Error(err) - //panic(err) - } -} - -type IntId struct { - Id int `xorm:"pk autoincr"` - Name string -} - -type Int32Id struct { - Id int32 `xorm:"pk autoincr"` - Name string -} - -type UintId struct { - Id uint `xorm:"pk autoincr"` - Name string -} - -type Uint32Id struct { - Id uint32 `xorm:"pk autoincr"` - Name string -} - -type Uint64Id struct { - Id uint64 `xorm:"pk autoincr"` - Name string -} - -type StringPK struct { - Id string `xorm:"pk notnull"` - Name string -} - -func testIntId(engine *Engine, t *testing.T) { - err := engine.DropTables(&IntId{}) - if err != nil { - t.Error(err) - panic(err) - } - - err = engine.CreateTables(&IntId{}) - if err != nil { - t.Error(err) - panic(err) - } - - cnt, err := engine.Insert(&IntId{Name: "test"}) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } - - bean := new(IntId) - has, err := engine.Get(bean) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } - - beans := make([]IntId, 0) - err = engine.Find(&beans) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans) != 1 { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } - - cnt, err = engine.Id(bean.Id).Delete(&IntId{}) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } -} - -func testInt32Id(engine *Engine, t *testing.T) { - err := engine.DropTables(&Int32Id{}) - if err != nil { - t.Error(err) - panic(err) - } - - err = engine.CreateTables(&Int32Id{}) - if err != nil { - t.Error(err) - panic(err) - } - - cnt, err := engine.Insert(&Int32Id{Name: "test"}) - if err != nil { - t.Error(err) - panic(err) - } - - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } - - bean := new(Int32Id) - has, err := engine.Get(bean) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } - - beans := make([]Int32Id, 0) - err = engine.Find(&beans) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans) != 1 { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } - - cnt, err = engine.Id(bean.Id).Delete(&Int32Id{}) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } -} - -func testUintId(engine *Engine, t *testing.T) { - err := engine.DropTables(&UintId{}) - if err != nil { - t.Error(err) - panic(err) - } - - err = engine.CreateTables(&UintId{}) - if err != nil { - t.Error(err) - panic(err) - } - - cnt, err := engine.Insert(&UintId{Name: "test"}) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } - - bean := new(UintId) - has, err := engine.Get(bean) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } - - beans := make([]UintId, 0) - err = engine.Find(&beans) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans) != 1 { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } - - cnt, err = engine.Id(bean.Id).Delete(&UintId{}) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } -} - -func testUint32Id(engine *Engine, t *testing.T) { - err := engine.DropTables(&Uint32Id{}) - if err != nil { - t.Error(err) - panic(err) - } - - err = engine.CreateTables(&Uint32Id{}) - if err != nil { - t.Error(err) - panic(err) - } - - cnt, err := engine.Insert(&Uint32Id{Name: "test"}) - if err != nil { - t.Error(err) - panic(err) - } - - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } - - bean := new(Uint32Id) - has, err := engine.Get(bean) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } - - beans := make([]Uint32Id, 0) - err = engine.Find(&beans) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans) != 1 { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } - - cnt, err = engine.Id(bean.Id).Delete(&Uint32Id{}) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } -} - -func testUint64Id(engine *Engine, t *testing.T) { - err := engine.DropTables(&Uint64Id{}) - if err != nil { - t.Error(err) - panic(err) - } - - err = engine.CreateTables(&Uint64Id{}) - if err != nil { - t.Error(err) - panic(err) - } - - cnt, err := engine.Insert(&Uint64Id{Name: "test"}) - if err != nil { - t.Error(err) - panic(err) - } - - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } - - bean := new(Uint64Id) - has, err := engine.Get(bean) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } - - beans := make([]Uint64Id, 0) - err = engine.Find(&beans) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans) != 1 { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } - - cnt, err = engine.Id(bean.Id).Delete(&Uint64Id{}) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } -} - -func testStringPK(engine *Engine, t *testing.T) { - err := engine.DropTables(&StringPK{}) - if err != nil { - t.Error(err) - panic(err) - } - - err = engine.CreateTables(&StringPK{}) - if err != nil { - t.Error(err) - panic(err) - } - - cnt, err := engine.Insert(&StringPK{Name: "test"}) - if err != nil { - t.Error(err) - panic(err) - } - - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } - - bean := new(StringPK) - has, err := engine.Get(bean) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } - - beans := make([]StringPK, 0) - err = engine.Find(&beans) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans) != 1 { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } - - cnt, err = engine.Id(bean.Id).Delete(&StringPK{}) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } -} - -func testMetaInfo(engine *Engine, t *testing.T) { - tables, err := engine.DBMetas() - if err != nil { - t.Error(err) - panic(err) - } - - for _, table := range tables { - fmt.Println(table.Name) - for _, col := range table.Columns { - fmt.Println(col.String(engine.dialect)) - } - - for _, index := range table.Indexes { - fmt.Println(index.Name, index.Type, strings.Join(index.Cols, ",")) - } - } -} - -func testIterate(engine *Engine, t *testing.T) { - err := engine.Omit("is_man").Iterate(new(Userinfo), func(idx int, bean interface{}) error { - user := bean.(*Userinfo) - fmt.Println(idx, "--", user) - return nil - }) - - if err != nil { - t.Error(err) - panic(err) - } -} - -func testRows(engine *Engine, t *testing.T) { - rows, err := engine.Omit("is_man").Rows(new(Userinfo)) - if err != nil { - t.Error(err) - panic(err) - } - defer rows.Close() - - idx := 0 - user := new(Userinfo) - for rows.Next() { - err = rows.Scan(user) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(idx, "--", user) - idx++ - } -} - -type StrangeName struct { - Id_t int64 `xorm:"pk autoincr"` - Name string -} - -func testStrangeName(engine *Engine, t *testing.T) { - err := engine.DropTables(new(StrangeName)) - if err != nil { - t.Error(err) - } - - err = engine.CreateTables(new(StrangeName)) - if err != nil { - t.Error(err) - } - - _, err = engine.Insert(&StrangeName{Name: "sfsfdsfds"}) - if err != nil { - t.Error(err) - } - - beans := make([]StrangeName, 0) - err = engine.Find(&beans) - if err != nil { - t.Error(err) - } -} - -type VersionS struct { - Id int64 - Name string - Ver int `xorm:"version"` -} - -func testVersion(engine *Engine, t *testing.T) { - err := engine.DropTables(new(VersionS)) - if err != nil { - t.Error(err) - panic(err) - } - - err = engine.CreateTables(new(VersionS)) - if err != nil { - t.Error(err) - panic(err) - } - - ver := &VersionS{Name: "sfsfdsfds"} - _, err = engine.Insert(ver) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(ver) - if ver.Ver != 1 { - err = errors.New("insert error") - t.Error(err) - panic(err) - } - - newVer := new(VersionS) - has, err := engine.Id(ver.Id).Get(newVer) - if err != nil { - t.Error(err) - panic(err) - } - - if !has { - t.Error(errors.New(fmt.Sprintf("no version id is %v", ver.Id))) - panic(err) - } - fmt.Println(newVer) - if newVer.Ver != 1 { - err = errors.New("insert error") - t.Error(err) - panic(err) - } - - newVer.Name = "-------" - _, err = engine.Id(ver.Id).Update(newVer) - if err != nil { - t.Error(err) - panic(err) - } - - newVer = new(VersionS) - has, err = engine.Id(ver.Id).Get(newVer) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(newVer) - if newVer.Ver != 2 { - err = errors.New("insert error") - t.Error(err) - panic(err) - } - - /* - newVer.Name = "-------" - _, err = engine.Id(ver.Id).Update(newVer) - if err != nil { - t.Error(err) - return - }*/ -} - -func testDistinct(engine *Engine, t *testing.T) { - users := make([]Userinfo, 0) - err := engine.Distinct("departname").Find(&users) - if err != nil { - t.Error(err) - panic(err) - } - if len(users) != 1 { - t.Error(err) - panic(errors.New("should be one record")) - } - - fmt.Println(users) - - type Depart struct { - Departname string - } - - users2 := make([]Depart, 0) - err = engine.Distinct("departname").Table(new(Userinfo)).Find(&users2) - if err != nil { - t.Error(err) - panic(err) - } - if len(users2) != 1 { - t.Error(err) - panic(errors.New("should be one record")) - } - fmt.Println(users2) -} - -func testUseBool(engine *Engine, t *testing.T) { - cnt1, err := engine.Count(&Userinfo{}) - if err != nil { - t.Error(err) - panic(err) - } - - users := make([]Userinfo, 0) - err = engine.Find(&users) - if err != nil { - t.Error(err) - panic(err) - } - var fNumber int64 - for _, u := range users { - if u.IsMan == false { - fNumber += 1 - } - } - - cnt2, err := engine.UseBool().Update(&Userinfo{IsMan: true}) - if err != nil { - t.Error(err) - panic(err) - } - if fNumber != cnt2 { - fmt.Println("cnt1", cnt1, "fNumber", fNumber, "cnt2", cnt2) - /*err = errors.New("Updated number is not corrected.") - t.Error(err) - panic(err)*/ - } - - _, err = engine.Update(&Userinfo{IsMan: true}) - if err == nil { - err = errors.New("error condition") - t.Error(err) - panic(err) - } -} - -func testBool(engine *Engine, t *testing.T) { - _, err := engine.UseBool().Update(&Userinfo{IsMan: true}) - if err != nil { - t.Error(err) - panic(err) - } - users := make([]Userinfo, 0) - err = engine.Find(&users) - if err != nil { - t.Error(err) - panic(err) - } - for _, user := range users { - if !user.IsMan { - err = errors.New("update bool or find bool error") - t.Error(err) - panic(err) - } - } - - _, err = engine.UseBool().Update(&Userinfo{IsMan: false}) - if err != nil { - t.Error(err) - panic(err) - } - users = make([]Userinfo, 0) - err = engine.Find(&users) - if err != nil { - t.Error(err) - panic(err) - } - for _, user := range users { - if user.IsMan { - err = errors.New("update bool or find bool error") - t.Error(err) - panic(err) - } - } -} - -type TTime struct { - Id int64 - T time.Time - Tz time.Time `xorm:"timestampz"` -} - -func testTime(engine *Engine, t *testing.T) { - err := engine.Sync(&TTime{}) - if err != nil { - t.Error(err) - panic(err) - } - - tt := &TTime{} - _, err = engine.Insert(tt) - if err != nil { - t.Error(err) - panic(err) - } - - tt2 := &TTime{Id: tt.Id} - has, err := engine.Get(tt2) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - err = errors.New("no record error") - t.Error(err) - panic(err) - } - - tt3 := &TTime{T: time.Now(), Tz: time.Now()} - _, err = engine.Insert(tt3) - if err != nil { - t.Error(err) - panic(err) - } - - tt4s := make([]TTime, 0) - err = engine.Find(&tt4s) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println("=======\n", tt4s, "=======\n") -} - -func testPrefixTableName(engine *Engine, t *testing.T) { - tempEngine, err := NewEngine(engine.DriverName, engine.DataSourceName) - if err != nil { - t.Error(err) - panic(err) - } - tempEngine.ShowSQL = true - mapper := NewPrefixMapper(SnakeMapper{}, "xlw_") - //tempEngine.SetMapper(mapper) - tempEngine.SetTableMapper(mapper) - exist, err := tempEngine.IsTableExist(&Userinfo{}) - if err != nil { - t.Error(err) - panic(err) - } - if exist { - err = tempEngine.DropTables(&Userinfo{}) - if err != nil { - t.Error(err) - panic(err) - } - } - err = tempEngine.CreateTables(&Userinfo{}) - if err != nil { - t.Error(err) - panic(err) - } -} - -type CreatedUpdated struct { - Id int64 - Name string - Value float64 `xorm:"numeric"` - Created time.Time `xorm:"created"` - Created2 time.Time `xorm:"created"` - Updated time.Time `xorm:"updated"` -} - -func testCreatedUpdated(engine *Engine, t *testing.T) { - err := engine.Sync(&CreatedUpdated{}) - if err != nil { - t.Error(err) - panic(err) - } - - c := &CreatedUpdated{Name: "test"} - _, err = engine.Insert(c) - if err != nil { - t.Error(err) - panic(err) - } - - c2 := new(CreatedUpdated) - has, err := engine.Id(c.Id).Get(c2) - if err != nil { - t.Error(err) - panic(err) - } - - if !has { - panic(errors.New("no id")) - } - - c2.Value -= 1 - _, err = engine.Id(c2.Id).Update(c2) - if err != nil { - t.Error(err) - panic(err) - } -} - -type ProcessorsStruct struct { - Id int64 - - B4InsertFlag int - AfterInsertedFlag int - B4UpdateFlag int - AfterUpdatedFlag int - B4DeleteFlag int `xorm:"-"` - AfterDeletedFlag int `xorm:"-"` - - B4InsertViaExt int - AfterInsertedViaExt int - B4UpdateViaExt int - AfterUpdatedViaExt int - B4DeleteViaExt int `xorm:"-"` - AfterDeletedViaExt int `xorm:"-"` -} - -func (p *ProcessorsStruct) BeforeInsert() { - p.B4InsertFlag = 1 -} - -func (p *ProcessorsStruct) BeforeUpdate() { - p.B4UpdateFlag = 1 -} - -func (p *ProcessorsStruct) BeforeDelete() { - p.B4DeleteFlag = 1 -} - -func (p *ProcessorsStruct) AfterInsert() { - p.AfterInsertedFlag = 1 -} - -func (p *ProcessorsStruct) AfterUpdate() { - p.AfterUpdatedFlag = 1 -} - -func (p *ProcessorsStruct) AfterDelete() { - p.AfterDeletedFlag = 1 -} - -func testProcessors(engine *Engine, t *testing.T) { - // tempEngine, err := NewEngine(engine.DriverName, engine.DataSourceName) - // if err != nil { - // t.Error(err) - // panic(err) - // } - - engine.ShowSQL = true - err := engine.DropTables(&ProcessorsStruct{}) - if err != nil { - t.Error(err) - panic(err) - } - p := &ProcessorsStruct{} - - err = engine.CreateTables(&ProcessorsStruct{}) - if err != nil { - t.Error(err) - panic(err) - } - - b4InsertFunc := func(bean interface{}) { - if v, ok := (bean).(*ProcessorsStruct); ok { - v.B4InsertViaExt = 1 - } else { - t.Error(errors.New("cast to ProcessorsStruct failed, how can this be!?")) - } - } - - afterInsertFunc := func(bean interface{}) { - if v, ok := (bean).(*ProcessorsStruct); ok { - v.AfterInsertedViaExt = 1 - } else { - t.Error(errors.New("cast to ProcessorsStruct failed, how can this be!?")) - } - } - - _, err = engine.Before(b4InsertFunc).After(afterInsertFunc).Insert(p) - if err != nil { - t.Error(err) - panic(err) - } else { - if p.B4InsertFlag == 0 { - t.Error(errors.New("B4InsertFlag not set")) - } - if p.AfterInsertedFlag == 0 { - t.Error(errors.New("B4InsertFlag not set")) - } - if p.B4InsertViaExt == 0 { - t.Error(errors.New("B4InsertFlag not set")) - } - if p.AfterInsertedViaExt == 0 { - t.Error(errors.New("AfterInsertedViaExt not set")) - } - } - - p2 := &ProcessorsStruct{} - _, err = engine.Id(p.Id).Get(p2) - if err != nil { - t.Error(err) - panic(err) - } else { - if p2.B4InsertFlag == 0 { - t.Error(errors.New("B4InsertFlag not set")) - } - if p2.AfterInsertedFlag != 0 { - t.Error(errors.New("AfterInsertedFlag is set")) - } - if p2.B4InsertViaExt == 0 { - t.Error(errors.New("B4InsertViaExt not set")) - } - if p2.AfterInsertedViaExt != 0 { - t.Error(errors.New("AfterInsertedViaExt is set")) - } - } - // -- - - // test update processors - b4UpdateFunc := func(bean interface{}) { - if v, ok := (bean).(*ProcessorsStruct); ok { - v.B4UpdateViaExt = 1 - } else { - t.Error(errors.New("cast to ProcessorsStruct failed, how can this be!?")) - } - } - - afterUpdateFunc := func(bean interface{}) { - if v, ok := (bean).(*ProcessorsStruct); ok { - v.AfterUpdatedViaExt = 1 - } else { - t.Error(errors.New("cast to ProcessorsStruct failed, how can this be!?")) - } - } - - p = p2 // reset - - _, err = engine.Before(b4UpdateFunc).After(afterUpdateFunc).Update(p) - if err != nil { - t.Error(err) - panic(err) - } else { - if p.B4UpdateFlag == 0 { - t.Error(errors.New("B4UpdateFlag not set")) - } - if p.AfterUpdatedFlag == 0 { - t.Error(errors.New("AfterUpdatedFlag not set")) - } - if p.B4UpdateViaExt == 0 { - t.Error(errors.New("B4UpdateViaExt not set")) - } - if p.AfterUpdatedViaExt == 0 { - t.Error(errors.New("AfterUpdatedViaExt not set")) - } - } - - p2 = &ProcessorsStruct{} - _, err = engine.Id(p.Id).Get(p2) - if err != nil { - t.Error(err) - panic(err) - } else { - if p2.B4UpdateFlag == 0 { - t.Error(errors.New("B4UpdateFlag not set")) - } - if p2.AfterUpdatedFlag != 0 { - t.Error(errors.New("AfterUpdatedFlag is set: " + string(p.AfterUpdatedFlag))) - } - if p2.B4UpdateViaExt == 0 { - t.Error(errors.New("B4UpdateViaExt not set")) - } - if p2.AfterUpdatedViaExt != 0 { - t.Error(errors.New("AfterUpdatedViaExt is set: " + string(p.AfterUpdatedViaExt))) - } - } - // -- - - // test delete processors - b4DeleteFunc := func(bean interface{}) { - if v, ok := (bean).(*ProcessorsStruct); ok { - v.B4DeleteViaExt = 1 - } else { - t.Error(errors.New("cast to ProcessorsStruct failed, how can this be!?")) - } - } - - afterDeleteFunc := func(bean interface{}) { - if v, ok := (bean).(*ProcessorsStruct); ok { - v.AfterDeletedViaExt = 1 - } else { - t.Error(errors.New("cast to ProcessorsStruct failed, how can this be!?")) - } - } - - p = p2 // reset - _, err = engine.Before(b4DeleteFunc).After(afterDeleteFunc).Delete(p) - if err != nil { - t.Error(err) - panic(err) - } else { - if p.B4DeleteFlag == 0 { - t.Error(errors.New("B4DeleteFlag not set")) - } - if p.AfterDeletedFlag == 0 { - t.Error(errors.New("AfterDeletedFlag not set")) - } - if p.B4DeleteViaExt == 0 { - t.Error(errors.New("B4DeleteViaExt not set")) - } - if p.AfterDeletedViaExt == 0 { - t.Error(errors.New("AfterDeletedViaExt not set")) - } - } - // -- - - // test insert multi - pslice := make([]*ProcessorsStruct, 0) - pslice = append(pslice, &ProcessorsStruct{}) - pslice = append(pslice, &ProcessorsStruct{}) - cnt, err := engine.Before(b4InsertFunc).After(afterInsertFunc).Insert(&pslice) - if err != nil { - t.Error(err) - panic(err) - } else { - if cnt != 2 { - t.Error(errors.New("incorrect insert count")) - } - for _, elem := range pslice { - if elem.B4InsertFlag == 0 { - t.Error(errors.New("B4InsertFlag not set")) - } - if elem.AfterInsertedFlag == 0 { - t.Error(errors.New("B4InsertFlag not set")) - } - if elem.B4InsertViaExt == 0 { - t.Error(errors.New("B4InsertFlag not set")) - } - if elem.AfterInsertedViaExt == 0 { - t.Error(errors.New("AfterInsertedViaExt not set")) - } - } - } - - for _, elem := range pslice { - p = &ProcessorsStruct{} - _, err = engine.Id(elem.Id).Get(p) - if err != nil { - t.Error(err) - panic(err) - } else { - if p2.B4InsertFlag == 0 { - t.Error(errors.New("B4InsertFlag not set")) - } - if p2.AfterInsertedFlag != 0 { - t.Error(errors.New("AfterInsertedFlag is set")) - } - if p2.B4InsertViaExt == 0 { - t.Error(errors.New("B4InsertViaExt not set")) - } - if p2.AfterInsertedViaExt != 0 { - t.Error(errors.New("AfterInsertedViaExt is set")) - } - } - } - // -- -} - -func testProcessorsTx(engine *Engine, t *testing.T) { - // tempEngine, err := NewEngine(engine.DriverName, engine.DataSourceName) - // if err != nil { - // t.Error(err) - // panic(err) - // } - - // tempEngine.ShowSQL = true - err := engine.DropTables(&ProcessorsStruct{}) - if err != nil { - t.Error(err) - panic(err) - } - - err = engine.CreateTables(&ProcessorsStruct{}) - if err != nil { - t.Error(err) - panic(err) - } - - // test insert processors with tx rollback - session := engine.NewSession() - err = session.Begin() - if err != nil { - t.Error(err) - panic(err) - } - - p := &ProcessorsStruct{} - b4InsertFunc := func(bean interface{}) { - if v, ok := (bean).(*ProcessorsStruct); ok { - v.B4InsertViaExt = 1 - } else { - t.Error(errors.New("cast to ProcessorsStruct failed, how can this be!?")) - } - } - - afterInsertFunc := func(bean interface{}) { - if v, ok := (bean).(*ProcessorsStruct); ok { - v.AfterInsertedViaExt = 1 - } else { - t.Error(errors.New("cast to ProcessorsStruct failed, how can this be!?")) - } - } - _, err = session.Before(b4InsertFunc).After(afterInsertFunc).Insert(p) - if err != nil { - t.Error(err) - panic(err) - } else { - if p.B4InsertFlag == 0 { - t.Error(errors.New("B4InsertFlag not set")) - } - if p.AfterInsertedFlag != 0 { - t.Error(errors.New("B4InsertFlag is set")) - } - if p.B4InsertViaExt == 0 { - t.Error(errors.New("B4InsertViaExt not set")) - } - if p.AfterInsertedViaExt != 0 { - t.Error(errors.New("AfterInsertedViaExt is set")) - } - } - - err = session.Rollback() - if err != nil { - t.Error(err) - panic(err) - } else { - if p.B4InsertFlag == 0 { - t.Error(errors.New("B4InsertFlag not set")) - } - if p.AfterInsertedFlag != 0 { - t.Error(errors.New("B4InsertFlag is set")) - } - if p.B4InsertViaExt == 0 { - t.Error(errors.New("B4InsertViaExt not set")) - } - if p.AfterInsertedViaExt != 0 { - t.Error(errors.New("AfterInsertedViaExt is set")) - } - } - session.Close() - p2 := &ProcessorsStruct{} - _, err = engine.Id(p.Id).Get(p2) - if err != nil { - t.Error(err) - panic(err) - } else { - if p2.Id > 0 { - err = errors.New("tx got committed upon insert!?") - t.Error(err) - panic(err) - } - } - // -- - - // test insert processors with tx commit - session = engine.NewSession() - err = session.Begin() - if err != nil { - t.Error(err) - panic(err) - } - - p = &ProcessorsStruct{} - _, err = session.Before(b4InsertFunc).After(afterInsertFunc).Insert(p) - if err != nil { - t.Error(err) - panic(err) - } else { - if p.B4InsertFlag == 0 { - t.Error(errors.New("B4InsertFlag not set")) - } - if p.AfterInsertedFlag != 0 { - t.Error(errors.New("AfterInsertedFlag is set")) - } - if p.B4InsertViaExt == 0 { - t.Error(errors.New("B4InsertViaExt not set")) - } - if p.AfterInsertedViaExt != 0 { - t.Error(errors.New("AfterInsertedViaExt is set")) - } - } - - err = session.Commit() - if err != nil { - t.Error(err) - panic(err) - } else { - if p.B4InsertFlag == 0 { - t.Error(errors.New("B4InsertFlag not set")) - } - if p.AfterInsertedFlag == 0 { - t.Error(errors.New("AfterInsertedFlag not set")) - } - if p.B4InsertViaExt == 0 { - t.Error(errors.New("B4InsertViaExt not set")) - } - if p.AfterInsertedViaExt == 0 { - t.Error(errors.New("AfterInsertedViaExt not set")) - } - } - session.Close() - p2 = &ProcessorsStruct{} - _, err = engine.Id(p.Id).Get(p2) - if err != nil { - t.Error(err) - panic(err) - } else { - if p2.B4InsertFlag == 0 { - t.Error(errors.New("B4InsertFlag not set")) - } - if p2.AfterInsertedFlag != 0 { - t.Error(errors.New("AfterInsertedFlag is set")) - } - if p2.B4InsertViaExt == 0 { - t.Error(errors.New("B4InsertViaExt not set")) - } - if p2.AfterInsertedViaExt != 0 { - t.Error(errors.New("AfterInsertedViaExt is set")) - } - } - insertedId := p2.Id - // -- - - // test update processors with tx rollback - session = engine.NewSession() - err = session.Begin() - if err != nil { - t.Error(err) - panic(err) - } - - b4UpdateFunc := func(bean interface{}) { - if v, ok := (bean).(*ProcessorsStruct); ok { - v.B4UpdateViaExt = 1 - } else { - t.Error(errors.New("cast to ProcessorsStruct failed, how can this be!?")) - } - } - - afterUpdateFunc := func(bean interface{}) { - if v, ok := (bean).(*ProcessorsStruct); ok { - v.AfterUpdatedViaExt = 1 - } else { - t.Error(errors.New("cast to ProcessorsStruct failed, how can this be!?")) - } - } - - p = p2 // reset - - _, err = session.Id(insertedId).Before(b4UpdateFunc).After(afterUpdateFunc).Update(p) - if err != nil { - t.Error(err) - panic(err) - } else { - if p.B4UpdateFlag == 0 { - t.Error(errors.New("B4UpdateFlag not set")) - } - if p.AfterUpdatedFlag != 0 { - t.Error(errors.New("AfterUpdatedFlag is set")) - } - if p.B4UpdateViaExt == 0 { - t.Error(errors.New("B4UpdateViaExt not set")) - } - if p.AfterUpdatedViaExt != 0 { - t.Error(errors.New("AfterUpdatedViaExt is set")) - } - } - err = session.Rollback() - if err != nil { - t.Error(err) - panic(err) - } else { - if p.B4UpdateFlag == 0 { - t.Error(errors.New("B4UpdateFlag not set")) - } - if p.AfterUpdatedFlag != 0 { - t.Error(errors.New("AfterUpdatedFlag is set")) - } - if p.B4UpdateViaExt == 0 { - t.Error(errors.New("B4UpdateViaExt not set")) - } - if p.AfterUpdatedViaExt != 0 { - t.Error(errors.New("AfterUpdatedViaExt is set")) - } - } - - session.Close() - p2 = &ProcessorsStruct{} - _, err = engine.Id(insertedId).Get(p2) - if err != nil { - t.Error(err) - panic(err) - } else { - if p2.B4UpdateFlag != 0 { - t.Error(errors.New("B4UpdateFlag is set")) - } - if p2.AfterUpdatedFlag != 0 { - t.Error(errors.New("AfterUpdatedFlag is set")) - } - if p2.B4UpdateViaExt != 0 { - t.Error(errors.New("B4UpdateViaExt not set")) - } - if p2.AfterUpdatedViaExt != 0 { - t.Error(errors.New("AfterUpdatedViaExt is set")) - } - } - // -- - - // test update processors with tx commit - session = engine.NewSession() - err = session.Begin() - if err != nil { - t.Error(err) - panic(err) - } - - p = &ProcessorsStruct{} - - _, err = session.Id(insertedId).Before(b4UpdateFunc).After(afterUpdateFunc).Update(p) - if err != nil { - t.Error(err) - panic(err) - } else { - if p.B4UpdateFlag == 0 { - t.Error(errors.New("B4UpdateFlag not set")) - } - if p.AfterUpdatedFlag != 0 { - t.Error(errors.New("AfterUpdatedFlag is set")) - } - if p.B4UpdateViaExt == 0 { - t.Error(errors.New("B4UpdateViaExt not set")) - } - if p.AfterUpdatedViaExt != 0 { - t.Error(errors.New("AfterUpdatedViaExt is set")) - } - } - err = session.Commit() - if err != nil { - t.Error(err) - panic(err) - } else { - if p.B4UpdateFlag == 0 { - t.Error(errors.New("B4UpdateFlag not set")) - } - if p.AfterUpdatedFlag == 0 { - t.Error(errors.New("AfterUpdatedFlag not set")) - } - if p.B4UpdateViaExt == 0 { - t.Error(errors.New("B4UpdateViaExt not set")) - } - if p.AfterUpdatedViaExt == 0 { - t.Error(errors.New("AfterUpdatedViaExt not set")) - } - } - session.Close() - p2 = &ProcessorsStruct{} - _, err = engine.Id(insertedId).Get(p2) - if err != nil { - t.Error(err) - panic(err) - } else { - if p.B4UpdateFlag == 0 { - t.Error(errors.New("B4UpdateFlag not set")) - } - if p.AfterUpdatedFlag == 0 { - t.Error(errors.New("AfterUpdatedFlag not set")) - } - if p.B4UpdateViaExt == 0 { - t.Error(errors.New("B4UpdateViaExt not set")) - } - if p.AfterUpdatedViaExt == 0 { - t.Error(errors.New("AfterUpdatedViaExt not set")) - } - } - // -- - - // test delete processors with tx rollback - session = engine.NewSession() - err = session.Begin() - if err != nil { - t.Error(err) - panic(err) - } - - b4DeleteFunc := func(bean interface{}) { - if v, ok := (bean).(*ProcessorsStruct); ok { - v.B4DeleteViaExt = 1 - } else { - t.Error(errors.New("cast to ProcessorsStruct failed, how can this be!?")) - } - } - - afterDeleteFunc := func(bean interface{}) { - if v, ok := (bean).(*ProcessorsStruct); ok { - v.AfterDeletedViaExt = 1 - } else { - t.Error(errors.New("cast to ProcessorsStruct failed, how can this be!?")) - } - } - - p = &ProcessorsStruct{} // reset - - _, err = session.Id(insertedId).Before(b4DeleteFunc).After(afterDeleteFunc).Delete(p) - if err != nil { - t.Error(err) - panic(err) - } else { - if p.B4DeleteFlag == 0 { - t.Error(errors.New("B4DeleteFlag not set")) - } - if p.AfterDeletedFlag != 0 { - t.Error(errors.New("AfterDeletedFlag is set")) - } - if p.B4DeleteViaExt == 0 { - t.Error(errors.New("B4DeleteViaExt not set")) - } - if p.AfterDeletedViaExt != 0 { - t.Error(errors.New("AfterDeletedViaExt is set")) - } - } - err = session.Rollback() - if err != nil { - t.Error(err) - panic(err) - } else { - if p.B4DeleteFlag == 0 { - t.Error(errors.New("B4DeleteFlag not set")) - } - if p.AfterDeletedFlag != 0 { - t.Error(errors.New("AfterDeletedFlag is set")) - } - if p.B4DeleteViaExt == 0 { - t.Error(errors.New("B4DeleteViaExt not set")) - } - if p.AfterDeletedViaExt != 0 { - t.Error(errors.New("AfterDeletedViaExt is set")) - } - } - session.Close() - - p2 = &ProcessorsStruct{} - _, err = engine.Id(insertedId).Get(p2) - if err != nil { - t.Error(err) - panic(err) - } else { - if p2.B4DeleteFlag != 0 { - t.Error(errors.New("B4DeleteFlag is set")) - } - if p2.AfterDeletedFlag != 0 { - t.Error(errors.New("AfterDeletedFlag is set")) - } - if p2.B4DeleteViaExt != 0 { - t.Error(errors.New("B4DeleteViaExt is set")) - } - if p2.AfterDeletedViaExt != 0 { - t.Error(errors.New("AfterDeletedViaExt is set")) - } - } - // -- - - // test delete processors with tx commit - session = engine.NewSession() - err = session.Begin() - if err != nil { - t.Error(err) - panic(err) - } - - p = &ProcessorsStruct{} - - _, err = session.Id(insertedId).Before(b4DeleteFunc).After(afterDeleteFunc).Delete(p) - if err != nil { - t.Error(err) - panic(err) - } else { - if p.B4DeleteFlag == 0 { - t.Error(errors.New("B4DeleteFlag not set")) - } - if p.AfterDeletedFlag != 0 { - t.Error(errors.New("AfterDeletedFlag is set")) - } - if p.B4DeleteViaExt == 0 { - t.Error(errors.New("B4DeleteViaExt not set")) - } - if p.AfterDeletedViaExt != 0 { - t.Error(errors.New("AfterDeletedViaExt is set")) - } - } - err = session.Commit() - if err != nil { - t.Error(err) - panic(err) - } else { - if p.B4DeleteFlag == 0 { - t.Error(errors.New("B4DeleteFlag not set")) - } - if p.AfterDeletedFlag == 0 { - t.Error(errors.New("AfterDeletedFlag not set")) - } - if p.B4DeleteViaExt == 0 { - t.Error(errors.New("B4DeleteViaExt not set")) - } - if p.AfterDeletedViaExt == 0 { - t.Error(errors.New("AfterDeletedViaExt not set")) - } - } - session.Close() - // -- -} - -type NullData struct { - Id int64 - StringPtr *string - StringPtr2 *string `xorm:"text"` - BoolPtr *bool - BytePtr *byte - UintPtr *uint - Uint8Ptr *uint8 - Uint16Ptr *uint16 - Uint32Ptr *uint32 - Uint64Ptr *uint64 - IntPtr *int - Int8Ptr *int8 - Int16Ptr *int16 - Int32Ptr *int32 - Int64Ptr *int64 - RunePtr *rune - Float32Ptr *float32 - Float64Ptr *float64 - // Complex64Ptr *complex64 // !nashtsai! XORM yet support complex128: 'json: unsupported type: complex128' - // Complex128Ptr *complex128 // !nashtsai! XORM yet support complex128: 'json: unsupported type: complex128' - TimePtr *time.Time -} - -type NullData2 struct { - Id int64 - StringPtr string - StringPtr2 string `xorm:"text"` - BoolPtr bool - BytePtr byte - UintPtr uint - Uint8Ptr uint8 - Uint16Ptr uint16 - Uint32Ptr uint32 - Uint64Ptr uint64 - IntPtr int - Int8Ptr int8 - Int16Ptr int16 - Int32Ptr int32 - Int64Ptr int64 - RunePtr rune - Float32Ptr float32 - Float64Ptr float64 - // Complex64Ptr complex64 // !nashtsai! XORM yet support complex128: 'json: unsupported type: complex128' - // Complex128Ptr complex128 // !nashtsai! XORM yet support complex128: 'json: unsupported type: complex128' - TimePtr time.Time -} - -type NullData3 struct { - Id int64 - StringPtr *string -} - -func testPointerData(engine *Engine, t *testing.T) { - - err := engine.DropTables(&NullData{}) - if err != nil { - t.Error(err) - panic(err) - } - - err = engine.CreateTables(&NullData{}) - if err != nil { - t.Error(err) - panic(err) - } - - nullData := NullData{ - StringPtr: new(string), - StringPtr2: new(string), - BoolPtr: new(bool), - BytePtr: new(byte), - UintPtr: new(uint), - Uint8Ptr: new(uint8), - Uint16Ptr: new(uint16), - Uint32Ptr: new(uint32), - Uint64Ptr: new(uint64), - IntPtr: new(int), - Int8Ptr: new(int8), - Int16Ptr: new(int16), - Int32Ptr: new(int32), - Int64Ptr: new(int64), - RunePtr: new(rune), - Float32Ptr: new(float32), - Float64Ptr: new(float64), - // Complex64Ptr: new(complex64), - // Complex128Ptr: new(complex128), - TimePtr: new(time.Time), - } - - *nullData.StringPtr = "abc" - *nullData.StringPtr2 = "123" - *nullData.BoolPtr = true - *nullData.BytePtr = 1 - *nullData.UintPtr = 1 - *nullData.Uint8Ptr = 1 - *nullData.Uint16Ptr = 1 - *nullData.Uint32Ptr = 1 - *nullData.Uint64Ptr = 1 - *nullData.IntPtr = -1 - *nullData.Int8Ptr = -1 - *nullData.Int16Ptr = -1 - *nullData.Int32Ptr = -1 - *nullData.Int64Ptr = -1 - *nullData.RunePtr = 1 - *nullData.Float32Ptr = -1.2 - *nullData.Float64Ptr = -1.1 - // *nullData.Complex64Ptr = 123456789012345678901234567890 - // *nullData.Complex128Ptr = 123456789012345678901234567890123456789012345678901234567890 - *nullData.TimePtr = time.Now() - - cnt, err := engine.Insert(&nullData) - fmt.Println(nullData.Id) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert not returned 1") - t.Error(err) - panic(err) - return - } - if nullData.Id <= 0 { - err = errors.New("not return id error") - t.Error(err) - panic(err) - } - - // verify get values - nullDataGet := NullData{} - has, err := engine.Id(nullData.Id).Get(&nullDataGet) - if err != nil { - t.Error(err) - panic(err) - } else if !has { - t.Error(errors.New("ID not found")) - } - - if *nullDataGet.StringPtr != *nullData.StringPtr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.StringPtr))) - } - - if *nullDataGet.StringPtr2 != *nullData.StringPtr2 { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.StringPtr2))) - } - - if *nullDataGet.BoolPtr != *nullData.BoolPtr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%t]", *nullDataGet.BoolPtr))) - } - - if *nullDataGet.UintPtr != *nullData.UintPtr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.UintPtr))) - } - - if *nullDataGet.Uint8Ptr != *nullData.Uint8Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Uint8Ptr))) - } - - if *nullDataGet.Uint16Ptr != *nullData.Uint16Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Uint16Ptr))) - } - - if *nullDataGet.Uint32Ptr != *nullData.Uint32Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Uint32Ptr))) - } - - if *nullDataGet.Uint64Ptr != *nullData.Uint64Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Uint64Ptr))) - } - - if *nullDataGet.IntPtr != *nullData.IntPtr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.IntPtr))) - } - - if *nullDataGet.Int8Ptr != *nullData.Int8Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Int8Ptr))) - } - - if *nullDataGet.Int16Ptr != *nullData.Int16Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Int16Ptr))) - } - - if *nullDataGet.Int32Ptr != *nullData.Int32Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Int32Ptr))) - } - - if *nullDataGet.Int64Ptr != *nullData.Int64Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Int64Ptr))) - } - - if *nullDataGet.RunePtr != *nullData.RunePtr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.RunePtr))) - } - - if *nullDataGet.Float32Ptr != *nullData.Float32Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Float32Ptr))) - } - - if *nullDataGet.Float64Ptr != *nullData.Float64Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Float64Ptr))) - } - - // if *nullDataGet.Complex64Ptr != *nullData.Complex64Ptr { - // t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Complex64Ptr))) - // } - - // if *nullDataGet.Complex128Ptr != *nullData.Complex128Ptr { - // t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Complex128Ptr))) - // } - - /*if (*nullDataGet.TimePtr).Unix() != (*nullData.TimePtr).Unix() { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]:[%v]", *nullDataGet.TimePtr, *nullData.TimePtr))) - } else { - // !nashtsai! mymysql driver will failed this test case, due the time is roundup to nearest second, I would considered this is a bug in mymysql driver - fmt.Printf("time value: [%v]:[%v]", *nullDataGet.TimePtr, *nullData.TimePtr) - fmt.Println() - }*/ - // -- - - // using instance type should just work too - nullData2Get := NullData2{} - - tableName := engine.tableMapper.Obj2Table("NullData") - - has, err = engine.Table(tableName).Id(nullData.Id).Get(&nullData2Get) - if err != nil { - t.Error(err) - panic(err) - } else if !has { - t.Error(errors.New("ID not found")) - } - - if nullData2Get.StringPtr != *nullData.StringPtr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", nullData2Get.StringPtr))) - } - - if nullData2Get.StringPtr2 != *nullData.StringPtr2 { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", nullData2Get.StringPtr2))) - } - - if nullData2Get.BoolPtr != *nullData.BoolPtr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%t]", nullData2Get.BoolPtr))) - } - - if nullData2Get.UintPtr != *nullData.UintPtr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", nullData2Get.UintPtr))) - } - - if nullData2Get.Uint8Ptr != *nullData.Uint8Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", nullData2Get.Uint8Ptr))) - } - - if nullData2Get.Uint16Ptr != *nullData.Uint16Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", nullData2Get.Uint16Ptr))) - } - - if nullData2Get.Uint32Ptr != *nullData.Uint32Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", nullData2Get.Uint32Ptr))) - } - - if nullData2Get.Uint64Ptr != *nullData.Uint64Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", nullData2Get.Uint64Ptr))) - } - - if nullData2Get.IntPtr != *nullData.IntPtr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", nullData2Get.IntPtr))) - } - - if nullData2Get.Int8Ptr != *nullData.Int8Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", nullData2Get.Int8Ptr))) - } - - if nullData2Get.Int16Ptr != *nullData.Int16Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", nullData2Get.Int16Ptr))) - } - - if nullData2Get.Int32Ptr != *nullData.Int32Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", nullData2Get.Int32Ptr))) - } - - if nullData2Get.Int64Ptr != *nullData.Int64Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", nullData2Get.Int64Ptr))) - } - - if nullData2Get.RunePtr != *nullData.RunePtr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", nullData2Get.RunePtr))) - } - - if nullData2Get.Float32Ptr != *nullData.Float32Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", nullData2Get.Float32Ptr))) - } - - if nullData2Get.Float64Ptr != *nullData.Float64Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", nullData2Get.Float64Ptr))) - } - - // if nullData2Get.Complex64Ptr != *nullData.Complex64Ptr { - // t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", nullData2Get.Complex64Ptr))) - // } - - // if nullData2Get.Complex128Ptr != *nullData.Complex128Ptr { - // t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", nullData2Get.Complex128Ptr))) - // } - - /*if nullData2Get.TimePtr.Unix() != (*nullData.TimePtr).Unix() { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]:[%v]", nullData2Get.TimePtr, *nullData.TimePtr))) - } else { - // !nashtsai! mymysql driver will failed this test case, due the time is roundup to nearest second, I would considered this is a bug in mymysql driver - fmt.Printf("time value: [%v]:[%v]", nullData2Get.TimePtr, *nullData.TimePtr) - fmt.Println() - }*/ - // -- -} - -func testNullValue(engine *Engine, t *testing.T) { - - err := engine.DropTables(&NullData{}) - if err != nil { - t.Error(err) - panic(err) - } - - err = engine.CreateTables(&NullData{}) - if err != nil { - t.Error(err) - panic(err) - } - - nullData := NullData{} - - cnt, err := engine.Insert(&nullData) - fmt.Println(nullData.Id) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert not returned 1") - t.Error(err) - panic(err) - return - } - if nullData.Id <= 0 { - err = errors.New("not return id error") - t.Error(err) - panic(err) - } - - nullDataGet := NullData{} - - has, err := engine.Id(nullData.Id).Get(&nullDataGet) - if err != nil { - t.Error(err) - panic(err) - } else if !has { - t.Error(errors.New("ID not found")) - } - - if nullDataGet.StringPtr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.StringPtr))) - } - - if nullDataGet.StringPtr2 != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.StringPtr2))) - } - - if nullDataGet.BoolPtr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%t]", *nullDataGet.BoolPtr))) - } - - if nullDataGet.UintPtr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.UintPtr))) - } - - if nullDataGet.Uint8Ptr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.Uint8Ptr))) - } - - if nullDataGet.Uint16Ptr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.Uint16Ptr))) - } - - if nullDataGet.Uint32Ptr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.Uint32Ptr))) - } - - if nullDataGet.Uint64Ptr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.Uint64Ptr))) - } - - if nullDataGet.IntPtr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.IntPtr))) - } - - if nullDataGet.Int8Ptr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.Int8Ptr))) - } - - if nullDataGet.Int16Ptr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.Int16Ptr))) - } - - if nullDataGet.Int32Ptr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.Int32Ptr))) - } - - if nullDataGet.Int64Ptr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.Int64Ptr))) - } - - if nullDataGet.RunePtr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.RunePtr))) - } - - if nullDataGet.Float32Ptr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.Float32Ptr))) - } - - if nullDataGet.Float64Ptr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.Float64Ptr))) - } - - // if nullDataGet.Complex64Ptr != nil { - // t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.Complex64Ptr))) - // } - - // if nullDataGet.Complex128Ptr != nil { - // t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.Complex128Ptr))) - // } - - if nullDataGet.TimePtr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.TimePtr))) - } - - nullDataUpdate := NullData{ - StringPtr: new(string), - StringPtr2: new(string), - BoolPtr: new(bool), - BytePtr: new(byte), - UintPtr: new(uint), - Uint8Ptr: new(uint8), - Uint16Ptr: new(uint16), - Uint32Ptr: new(uint32), - Uint64Ptr: new(uint64), - IntPtr: new(int), - Int8Ptr: new(int8), - Int16Ptr: new(int16), - Int32Ptr: new(int32), - Int64Ptr: new(int64), - RunePtr: new(rune), - Float32Ptr: new(float32), - Float64Ptr: new(float64), - // Complex64Ptr: new(complex64), - // Complex128Ptr: new(complex128), - TimePtr: new(time.Time), - } - - *nullDataUpdate.StringPtr = "abc" - *nullDataUpdate.StringPtr2 = "123" - *nullDataUpdate.BoolPtr = true - *nullDataUpdate.BytePtr = 1 - *nullDataUpdate.UintPtr = 1 - *nullDataUpdate.Uint8Ptr = 1 - *nullDataUpdate.Uint16Ptr = 1 - *nullDataUpdate.Uint32Ptr = 1 - *nullDataUpdate.Uint64Ptr = 1 - *nullDataUpdate.IntPtr = -1 - *nullDataUpdate.Int8Ptr = -1 - *nullDataUpdate.Int16Ptr = -1 - *nullDataUpdate.Int32Ptr = -1 - *nullDataUpdate.Int64Ptr = -1 - *nullDataUpdate.RunePtr = 1 - *nullDataUpdate.Float32Ptr = -1.2 - *nullDataUpdate.Float64Ptr = -1.1 - // *nullDataUpdate.Complex64Ptr = 123456789012345678901234567890 - // *nullDataUpdate.Complex128Ptr = 123456789012345678901234567890123456789012345678901234567890 - *nullDataUpdate.TimePtr = time.Now() - - cnt, err = engine.Id(nullData.Id).Update(&nullDataUpdate) - if err != nil { - t.Error(err) - panic(err) - } else if cnt != 1 { - t.Error(errors.New("update count == 0, how can this happen!?")) - return - } - - // verify get values - nullDataGet = NullData{} - has, err = engine.Id(nullData.Id).Get(&nullDataGet) - if err != nil { - t.Error(err) - return - } else if !has { - t.Error(errors.New("ID not found")) - return - } - - if *nullDataGet.StringPtr != *nullDataUpdate.StringPtr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.StringPtr))) - } - - if *nullDataGet.StringPtr2 != *nullDataUpdate.StringPtr2 { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.StringPtr2))) - } - - if *nullDataGet.BoolPtr != *nullDataUpdate.BoolPtr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%t]", *nullDataGet.BoolPtr))) - } - - if *nullDataGet.UintPtr != *nullDataUpdate.UintPtr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.UintPtr))) - } - - if *nullDataGet.Uint8Ptr != *nullDataUpdate.Uint8Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Uint8Ptr))) - } - - if *nullDataGet.Uint16Ptr != *nullDataUpdate.Uint16Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Uint16Ptr))) - } - - if *nullDataGet.Uint32Ptr != *nullDataUpdate.Uint32Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Uint32Ptr))) - } - - if *nullDataGet.Uint64Ptr != *nullDataUpdate.Uint64Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Uint64Ptr))) - } - - if *nullDataGet.IntPtr != *nullDataUpdate.IntPtr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.IntPtr))) - } - - if *nullDataGet.Int8Ptr != *nullDataUpdate.Int8Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Int8Ptr))) - } - - if *nullDataGet.Int16Ptr != *nullDataUpdate.Int16Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Int16Ptr))) - } - - if *nullDataGet.Int32Ptr != *nullDataUpdate.Int32Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Int32Ptr))) - } - - if *nullDataGet.Int64Ptr != *nullDataUpdate.Int64Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Int64Ptr))) - } - - if *nullDataGet.RunePtr != *nullDataUpdate.RunePtr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.RunePtr))) - } - - if *nullDataGet.Float32Ptr != *nullDataUpdate.Float32Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Float32Ptr))) - } - - if *nullDataGet.Float64Ptr != *nullDataUpdate.Float64Ptr { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Float64Ptr))) - } - - // if *nullDataGet.Complex64Ptr != *nullDataUpdate.Complex64Ptr { - // t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Complex64Ptr))) - // } - - // if *nullDataGet.Complex128Ptr != *nullDataUpdate.Complex128Ptr { - // t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Complex128Ptr))) - // } - - // !nashtsai! skipped mymysql test due to driver will round up time caused inaccuracy comparison - // skipped postgres test due to postgres driver doesn't read time.Time's timzezone info when stored in the db - // mysql and sqlite3 seem have done this correctly by storing datatime in UTC timezone, I think postgres driver - // prefer using timestamp with timezone to sovle the issue - if engine.DriverName != POSTGRES && engine.DriverName != MYMYSQL && - engine.DriverName != MYSQL { - if (*nullDataGet.TimePtr).Unix() != (*nullDataUpdate.TimePtr).Unix() { - t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]:[%v]", *nullDataGet.TimePtr, *nullDataUpdate.TimePtr))) - } else { - // !nashtsai! mymysql driver will failed this test case, due the time is roundup to nearest second, I would considered this is a bug in mymysql driver - // inserted value unmatch: [2013-12-25 12:12:45 +0800 CST]:[2013-12-25 12:12:44.878903653 +0800 CST] - fmt.Printf("time value: [%v]:[%v]", *nullDataGet.TimePtr, *nullDataUpdate.TimePtr) - fmt.Println() - } - } - - // update to null values - nullDataUpdate = NullData{} - - string_ptr := engine.columnMapper.Obj2Table("StringPtr") - - cnt, err = engine.Id(nullData.Id).Cols(string_ptr).Update(&nullDataUpdate) - if err != nil { - t.Error(err) - panic(err) - } else if cnt != 1 { - t.Error(errors.New("update count == 0, how can this happen!?")) - return - } - - // verify get values - nullDataGet = NullData{} - has, err = engine.Id(nullData.Id).Get(&nullDataGet) - if err != nil { - t.Error(err) - return - } else if !has { - t.Error(errors.New("ID not found")) - return - } - - fmt.Printf("%+v", nullDataGet) - fmt.Println() - - if nullDataGet.StringPtr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.StringPtr))) - } - /* - if nullDataGet.StringPtr2 != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.StringPtr2))) - } - - if nullDataGet.BoolPtr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%t]", *nullDataGet.BoolPtr))) - } - - if nullDataGet.UintPtr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.UintPtr))) - } - - if nullDataGet.Uint8Ptr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.Uint8Ptr))) - } - - if nullDataGet.Uint16Ptr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.Uint16Ptr))) - } - - if nullDataGet.Uint32Ptr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.Uint32Ptr))) - } - - if nullDataGet.Uint64Ptr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.Uint64Ptr))) - } - - if nullDataGet.IntPtr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.IntPtr))) - } - - if nullDataGet.Int8Ptr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.Int8Ptr))) - } - - if nullDataGet.Int16Ptr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.Int16Ptr))) - } - - if nullDataGet.Int32Ptr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.Int32Ptr))) - } - - if nullDataGet.Int64Ptr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.Int64Ptr))) - } - - if nullDataGet.RunePtr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.RunePtr))) - } - - if nullDataGet.Float32Ptr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.Float32Ptr))) - } - - if nullDataGet.Float64Ptr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.Float64Ptr))) - } - - // if nullDataGet.Complex64Ptr != nil { - // t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.Float64Ptr))) - // } - - // if nullDataGet.Complex128Ptr != nil { - // t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.Float64Ptr))) - // } - - if nullDataGet.TimePtr != nil { - t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.TimePtr))) - }*/ - // -- - -} - -type CompositeKey struct { - Id1 int64 `xorm:"id1 pk"` - Id2 int64 `xorm:"id2 pk"` - UpdateStr string -} - -func testCompositeKey(engine *Engine, t *testing.T) { - - err := engine.DropTables(&CompositeKey{}) - if err != nil { - t.Error(err) - panic(err) - } - - err = engine.CreateTables(&CompositeKey{}) - if err != nil { - t.Error(err) - panic(err) - } - - cnt, err := engine.Insert(&CompositeKey{11, 22, ""}) - if err != nil { - t.Error(err) - } else if cnt != 1 { - t.Error(errors.New("failed to insert CompositeKey{11, 22}")) - } - - cnt, err = engine.Insert(&CompositeKey{11, 22, ""}) - if err == nil || cnt == 1 { - t.Error(errors.New("inserted CompositeKey{11, 22}")) - } - - var compositeKeyVal CompositeKey - has, err := engine.Id(PK{11, 22}).Get(&compositeKeyVal) - if err != nil { - t.Error(err) - } else if !has { - t.Error(errors.New("can't get CompositeKey{11, 22}")) - } - - // test passing PK ptr, this test seem failed withCache - has, err = engine.Id(&PK{11, 22}).Get(&compositeKeyVal) - if err != nil { - t.Error(err) - } else if !has { - t.Error(errors.New("can't get CompositeKey{11, 22}")) - } - - compositeKeyVal = CompositeKey{UpdateStr: "test1"} - cnt, err = engine.Id(PK{11, 22}).Update(&compositeKeyVal) - if err != nil { - t.Error(err) - } else if cnt != 1 { - t.Error(errors.New("can't update CompositeKey{11, 22}")) - } - - cnt, err = engine.Id(PK{11, 22}).Delete(&CompositeKey{}) - if err != nil { - t.Error(err) - } else if cnt != 1 { - t.Error(errors.New("can't delete CompositeKey{11, 22}")) - } -} - -type Lowercase struct { - Id int64 - Name string - ended int64 `xorm:"-"` -} - -func testLowerCase(engine *Engine, t *testing.T) { - err := engine.Sync(&Lowercase{}) - _, 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) - } -} - -type User struct { - UserId string `xorm:"varchar(19) not null pk"` - NickName string `xorm:"varchar(19) not null"` - GameId uint32 `xorm:"integer pk"` - Score int32 `xorm:"integer"` -} - -func testCompositeKey2(engine *Engine, t *testing.T) { - err := engine.DropTables(&User{}) - - if err != nil { - t.Error(err) - panic(err) - } - - err = engine.CreateTables(&User{}) - if err != nil { - t.Error(err) - panic(err) - } - - cnt, err := engine.Insert(&User{"11", "nick", 22, 5}) - if err != nil { - t.Error(err) - } else if cnt != 1 { - t.Error(errors.New("failed to insert User{11, 22}")) - } - - cnt, err = engine.Insert(&User{"11", "nick", 22, 6}) - if err == nil || cnt == 1 { - t.Error(errors.New("inserted User{11, 22}")) - } - - var user User - has, err := engine.Id(PK{"11", 22}).Get(&user) - if err != nil { - t.Error(err) - } else if !has { - t.Error(errors.New("can't get User{11, 22}")) - } - - // test passing PK ptr, this test seem failed withCache - has, err = engine.Id(&PK{"11", 22}).Get(&user) - if err != nil { - t.Error(err) - } else if !has { - t.Error(errors.New("can't get User{11, 22}")) - } - - user = User{NickName: "test1"} - cnt, err = engine.Id(PK{"11", 22}).Update(&user) - if err != nil { - t.Error(err) - } else if cnt != 1 { - t.Error(errors.New("can't update User{11, 22}")) - } - - cnt, err = engine.Id(PK{"11", 22}).Delete(&User{}) - if err != nil { - t.Error(err) - } else if cnt != 1 { - t.Error(errors.New("can't delete CompositeKey{11, 22}")) - } -} - -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) - fmt.Println("-------------- insert --------------") - insert(engine, t) - fmt.Println("-------------- query --------------") - testQuery(engine, t) - fmt.Println("-------------- exec --------------") - exec(engine, t) - fmt.Println("-------------- insertAutoIncr --------------") - insertAutoIncr(engine, t) - fmt.Println("-------------- insertMulti --------------") - insertMulti(engine, t) - fmt.Println("-------------- insertTwoTable --------------") - insertTwoTable(engine, t) - fmt.Println("-------------- update --------------") - update(engine, t) - fmt.Println("-------------- testDelete --------------") - testDelete(engine, t) - fmt.Println("-------------- get --------------") - get(engine, t) - fmt.Println("-------------- cascadeGet --------------") - cascadeGet(engine, t) - fmt.Println("-------------- find --------------") - find(engine, t) - fmt.Println("-------------- find2 --------------") - find2(engine, t) - fmt.Println("-------------- findMap --------------") - findMap(engine, t) - fmt.Println("-------------- findMap2 --------------") - findMap2(engine, t) - fmt.Println("-------------- count --------------") - count(engine, t) - fmt.Println("-------------- where --------------") - where(engine, t) - fmt.Println("-------------- in --------------") - in(engine, t) - fmt.Println("-------------- limit --------------") - limit(engine, t) - fmt.Println("-------------- order --------------") - order(engine, t) - fmt.Println("-------------- join --------------") - join(engine, t) - fmt.Println("-------------- having --------------") - having(engine, t) -} - -func testAll2(engine *Engine, t *testing.T) { - fmt.Println("-------------- combineTransaction --------------") - combineTransaction(engine, t) - fmt.Println("-------------- table --------------") - table(engine, t) - fmt.Println("-------------- createMultiTables --------------") - createMultiTables(engine, t) - fmt.Println("-------------- tableOp --------------") - tableOp(engine, t) - fmt.Println("-------------- testCols --------------") - testCols(engine, t) - fmt.Println("-------------- testCharst --------------") - testCharst(engine, t) - fmt.Println("-------------- testStoreEngine --------------") - testStoreEngine(engine, t) - fmt.Println("-------------- testExtends --------------") - testExtends(engine, t) - fmt.Println("-------------- testColTypes --------------") - testColTypes(engine, t) - fmt.Println("-------------- testCustomType --------------") - testCustomType(engine, t) - fmt.Println("-------------- testCreatedAndUpdated --------------") - testCreatedAndUpdated(engine, t) - fmt.Println("-------------- testIndexAndUnique --------------") - testIndexAndUnique(engine, t) - fmt.Println("-------------- testIntId --------------") - testIntId(engine, t) - fmt.Println("-------------- testInt32Id --------------") - testInt32Id(engine, t) - fmt.Println("-------------- testUintId --------------") - testUintId(engine, t) - fmt.Println("-------------- testUint32Id --------------") - testUint32Id(engine, t) - fmt.Println("-------------- testUint64Id --------------") - testUint64Id(engine, t) - fmt.Println("-------------- testMetaInfo --------------") - testMetaInfo(engine, t) - fmt.Println("-------------- testIterate --------------") - testIterate(engine, t) - fmt.Println("-------------- testRows --------------") - testRows(engine, t) - fmt.Println("-------------- testStrangeName --------------") - testStrangeName(engine, t) - fmt.Println("-------------- testVersion --------------") - testVersion(engine, t) - fmt.Println("-------------- testDistinct --------------") - testDistinct(engine, t) - fmt.Println("-------------- testUseBool --------------") - testUseBool(engine, t) - fmt.Println("-------------- testBool --------------") - testBool(engine, t) - fmt.Println("-------------- testTime --------------") - testTime(engine, t) - fmt.Println("-------------- testPrefixTableName --------------") - testPrefixTableName(engine, t) - fmt.Println("-------------- testCreatedUpdated --------------") - testCreatedUpdated(engine, t) - fmt.Println("-------------- testLowercase ---------------") - testLowerCase(engine, t) - fmt.Println("-------------- processors --------------") - 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 -func testAll3(engine *Engine, t *testing.T) { - fmt.Println("-------------- processors TX --------------") - testProcessorsTx(engine, t) - fmt.Println("-------------- insert pointer data --------------") - testPointerData(engine, t) - fmt.Println("-------------- insert null data --------------") - testNullValue(engine, t) - fmt.Println("-------------- testCompositeKey --------------") - testCompositeKey(engine, t) - fmt.Println("-------------- testCompositeKey2 --------------") - testCompositeKey2(engine, t) - fmt.Println("-------------- testStringPK --------------") - testStringPK(engine, t) -} - -func testAllSnakeMapper(engine *Engine, t *testing.T) { - -} - -func testAllSameMapper(engine *Engine, t *testing.T) { - -} diff --git a/benchmark.bat b/benchmark.bat deleted file mode 100644 index d35fe044..00000000 --- a/benchmark.bat +++ /dev/null @@ -1 +0,0 @@ -go test -v -bench=. -run=XXX \ No newline at end of file diff --git a/benchmark.sh b/benchmark.sh deleted file mode 100755 index d35fe044..00000000 --- a/benchmark.sh +++ /dev/null @@ -1 +0,0 @@ -go test -v -bench=. -run=XXX \ No newline at end of file diff --git a/benchmark_base_test.go b/benchmark_base_test.go deleted file mode 100644 index d263da06..00000000 --- a/benchmark_base_test.go +++ /dev/null @@ -1,174 +0,0 @@ -package xorm - -import ( - "database/sql" - "testing" -) - -type BigStruct struct { - Id int64 - Name string - Title string - Age string - Alias string - NickName string -} - -func doBenchDriverInsert(db *sql.DB, b *testing.B) { - b.StartTimer() - for i := 0; i < b.N; i++ { - _, err := db.Exec(`insert into big_struct (name, title, age, alias, nick_name) - values ('fafdasf', 'fadfa', 'afadfsaf', 'fadfafdsafd', 'fadfafdsaf')`) - if err != nil { - b.Error(err) - return - } - } - b.StopTimer() -} - -func doBenchDriverFind(db *sql.DB, b *testing.B) { - b.StopTimer() - for i := 0; i < 50; i++ { - _, err := db.Exec(`insert into big_struct (name, title, age, alias, nick_name) - values ('fafdasf', 'fadfa', 'afadfsaf', 'fadfafdsafd', 'fadfafdsaf')`) - if err != nil { - b.Error(err) - return - } - } - - b.StartTimer() - for i := 0; i < b.N/50; i++ { - rows, err := db.Query("select * from big_struct limit 50") - if err != nil { - b.Error(err) - return - } - for rows.Next() { - s := &BigStruct{} - rows.Scan(&s.Id, &s.Name, &s.Title, &s.Age, &s.Alias, &s.NickName) - } - } - b.StopTimer() -} - -func doBenchDriver(newdriver func() (*sql.DB, error), createTableSql, - dropTableSql string, opFunc func(*sql.DB, *testing.B), t *testing.B) { - db, err := newdriver() - if err != nil { - t.Error(err) - return - } - defer db.Close() - - _, err = db.Exec(createTableSql) - if err != nil { - t.Error(err) - return - } - - opFunc(db, t) - - _, err = db.Exec(dropTableSql) - if err != nil { - t.Error(err) - return - } -} - -func doBenchInsert(engine *Engine, b *testing.B) { - b.StopTimer() - bs := &BigStruct{0, "fafdasf", "fadfa", "afadfsaf", "fadfafdsafd", "fadfafdsaf"} - err := engine.CreateTables(bs) - if err != nil { - b.Error(err) - return - } - - b.StartTimer() - for i := 0; i < b.N; i++ { - bs.Id = 0 - _, err = engine.Insert(bs) - if err != nil { - b.Error(err) - return - } - } - b.StopTimer() - err = engine.DropTables(bs) - if err != nil { - b.Error(err) - return - } -} - -func doBenchFind(engine *Engine, b *testing.B) { - b.StopTimer() - bs := &BigStruct{0, "fafdasf", "fadfa", "afadfsaf", "fadfafdsafd", "fadfafdsaf"} - err := engine.CreateTables(bs) - if err != nil { - b.Error(err) - return - } - - for i := 0; i < 100; i++ { - bs.Id = 0 - _, err = engine.Insert(bs) - if err != nil { - b.Error(err) - return - } - } - - b.StartTimer() - for i := 0; i < b.N/50; i++ { - bss := new([]BigStruct) - err = engine.Limit(50).Find(bss) - if err != nil { - b.Error(err) - return - } - } - b.StopTimer() - err = engine.DropTables(bs) - if err != nil { - b.Error(err) - return - } -} - -func doBenchFindPtr(engine *Engine, b *testing.B) { - b.StopTimer() - bs := &BigStruct{0, "fafdasf", "fadfa", "afadfsaf", "fadfafdsafd", "fadfafdsaf"} - err := engine.CreateTables(bs) - if err != nil { - b.Error(err) - return - } - - for i := 0; i < 100; i++ { - bs.Id = 0 - _, err = engine.Insert(bs) - if err != nil { - b.Error(err) - return - } - } - - b.StartTimer() - for i := 0; i < b.N/50; i++ { - bss := new([]*BigStruct) - err = engine.Limit(50).Find(bss) - if err != nil { - b.Error(err) - return - } - } - b.StopTimer() - err = engine.DropTables(bs) - if err != nil { - b.Error(err) - return - } -} diff --git a/doc.go b/doc.go index 765f2dfe..b183e8d2 100644 --- a/doc.go +++ b/doc.go @@ -1,4 +1,4 @@ -// Copyright 2013 The XORM Authors. All rights reserved. +// Copyright 2013 - 2014 The XORM Authors. All rights reserved. // Use of this source code is governed by a BSD // license that can be found in the LICENSE file. @@ -9,7 +9,7 @@ Installation Make sure you have installed Go 1.1+ and then: - go get github.com/lunny/xorm + go get github.com/go-xorm/xorm Create Engine @@ -137,6 +137,6 @@ The above 7 methods could use with condition methods. engine.Join("LEFT", "userdetail", "user.id=userdetail.id").Find() //SELECT * FROM user LEFT JOIN userdetail ON user.id=userdetail.id -More usage, please visit https://github.com/lunny/xorm/blob/master/docs/QuickStartEn.md +More usage, please visit https://github.com/go-xorm/xorm/blob/master/docs/QuickStartEn.md */ package xorm diff --git a/docs/AutoMap.md b/docs/AutoMap.md index f5b2aa57..cae66209 100644 --- a/docs/AutoMap.md +++ b/docs/AutoMap.md @@ -1,65 +1,65 @@ -When a struct auto mapping to a database's table, the below table describes how they change to each other: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +When a struct auto mapping to a database's table, the below table describes how they change to each other: + +
go type's kind - value methodxorm type -
implemented ConversionConversion.ToDB / Conversion.FromDBText
int, int8, int16, int32, uint, uint8, uint16, uint32 Int
int64, uint64BigInt
float32Float
float64Double
complex64, complex128json.Marshal / json.UnMarshalVarchar(64)
[]uint8Blob
array, slice, map except []uint8json.Marshal / json.UnMarshalText
bool1 or 0Bool
stringVarchar(255)
time.TimeDateTime
cascade structprimary key field valueBigInt
structjson.Marshal / json.UnMarshalText
- Others - - Text -
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
go type's kind + value methodxorm type +
implemented ConversionConversion.ToDB / Conversion.FromDBText
int, int8, int16, int32, uint, uint8, uint16, uint32 Int
int64, uint64BigInt
float32Float
float64Double
complex64, complex128json.Marshal / json.UnMarshalVarchar(64)
[]uint8Blob
array, slice, map except []uint8json.Marshal / json.UnMarshalText
bool1 or 0Bool
stringVarchar(255)
time.TimeDateTime
cascade structprimary key field valueBigInt
structjson.Marshal / json.UnMarshalText
+ Others + + Text +
\ No newline at end of file diff --git a/docs/COLUMNTYPE.md b/docs/COLUMNTYPE.md index eb8f2c8a..d5a7e85a 100644 --- a/docs/COLUMNTYPE.md +++ b/docs/COLUMNTYPE.md @@ -1,438 +1,438 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
xorm - mysql - sqlite3 - postgres - remark
BIT - BIT - INTEGER - BIT -
TINYINT - TINYINT - INTEGER - SMALLINT -
SMALLINT - SMALLINT - INTEGER - SMALLINT -
MEDIUMINT - MEDIUMINT - INTEGER - INTEGER -
INT - INT - INTEGER - INTEGER -
INTEGER - INTEGER - INTEGER - INTEGER -
BIGINT - BIGINT - INTEGER - BIGINT -
CHAR - CHAR - TEXT - CHAR -
VARCHAR - VARCHAR - TEXT - VARCHAR -
TINYTEXT - TINYTEXT - TEXT - TEXT -
TEXT - TEXT - TEXT - TEXT -
MEDIUMTEXT - MEDIUMTEXT - TEXT - TEXT -
LONGTEXT - LONGTEXT - TEXT - TEXT -
BINARY - BINARY - BLOB - BYTEA -
VARBINARY - VARBINARY - BLOB - BYTEA -
DATE - DATE - NUMERIC - DATE -
DATETIME - DATETIME - NUMERIC - TIMESTAMP -
TIME - TIME - NUMERIC - TIME -
TIMESTAMP - TIMESTAMP - NUMERIC - TIMESTAMP -
TIMESTAMPZ - TEXT - TEXT - TIMESTAMP with zone - timestamp with zone info
REAL - REAL - REAL - REAL -
FLOAT - FLOAT - REAL - REAL -
DOUBLE - DOUBLE - REAL - DOUBLE PRECISION -
DECIMAL - DECIMAL - NUMERIC - DECIMAL -
NUMERIC - NUMERIC - NUMERIC - NUMERIC -
TINYBLOB - TINYBLOB - BLOB - BYTEA -
BLOB - BLOB - BLOB - BYTEA -
MEDIUMBLOB - MEDIUMBLOB - BLOB - BYTEA -
LONGBLOB - LONGBLOB - BLOB - BYTEA -
BYTEA - BLOB - BLOB - BYTEA -
BOOL - TINYINT - INTEGER - BOOLEAN -
SERIAL - INT - INTEGER - SERIAL - auto increment
BIGSERIAL - BIGINT - INTEGER - BIGSERIAL - auto increment
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
xorm + mysql + sqlite3 + postgres + remark
BIT + BIT + INTEGER + BIT +
TINYINT + TINYINT + INTEGER + SMALLINT +
SMALLINT + SMALLINT + INTEGER + SMALLINT +
MEDIUMINT + MEDIUMINT + INTEGER + INTEGER +
INT + INT + INTEGER + INTEGER +
INTEGER + INTEGER + INTEGER + INTEGER +
BIGINT + BIGINT + INTEGER + BIGINT +
CHAR + CHAR + TEXT + CHAR +
VARCHAR + VARCHAR + TEXT + VARCHAR +
TINYTEXT + TINYTEXT + TEXT + TEXT +
TEXT + TEXT + TEXT + TEXT +
MEDIUMTEXT + MEDIUMTEXT + TEXT + TEXT +
LONGTEXT + LONGTEXT + TEXT + TEXT +
BINARY + BINARY + BLOB + BYTEA +
VARBINARY + VARBINARY + BLOB + BYTEA +
DATE + DATE + NUMERIC + DATE +
DATETIME + DATETIME + NUMERIC + TIMESTAMP +
TIME + TIME + NUMERIC + TIME +
TIMESTAMP + TIMESTAMP + NUMERIC + TIMESTAMP +
TIMESTAMPZ + TEXT + TEXT + TIMESTAMP with zone + timestamp with zone info
REAL + REAL + REAL + REAL +
FLOAT + FLOAT + REAL + REAL +
DOUBLE + DOUBLE + REAL + DOUBLE PRECISION +
DECIMAL + DECIMAL + NUMERIC + DECIMAL +
NUMERIC + NUMERIC + NUMERIC + NUMERIC +
TINYBLOB + TINYBLOB + BLOB + BYTEA +
BLOB + BLOB + BLOB + BYTEA +
MEDIUMBLOB + MEDIUMBLOB + BLOB + BYTEA +
LONGBLOB + LONGBLOB + BLOB + BYTEA +
BYTEA + BLOB + BLOB + BYTEA +
BOOL + TINYINT + INTEGER + BOOLEAN +
SERIAL + INT + INTEGER + SERIAL + auto increment
BIGSERIAL + BIGINT + INTEGER + BIGSERIAL + auto increment
\ No newline at end of file diff --git a/docs/Changelog.md b/docs/Changelog.md index 7f023d51..fb9b1ec3 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,5 +1,16 @@ ## Changelog +* **v0.4.0 RC1** + Changes: + * moved xorm cmd to [github.com/go-xorm/cmd](github.com/go-xorm/cmd) + * refactored general DB operation a core lib at [github.com/go-xorm/core](https://github.com/go-xorm/core) + * moved tests to github.com/go-xorm/tests [github.com/go-xorm/tests](github.com/go-xorm/tests) + + Improvements: + * Prepared statement cache + * Add Incr API + * Specify Timezone Location + * **v0.3.2** Improvements: * Add AllCols & MustCols function @@ -29,7 +40,7 @@ * **v0.2.3** : Improved documents; Optimistic Locking support; Timestamp with time zone support; Mapper change to tableMapper and columnMapper & added PrefixMapper & SuffixMapper support custom table or column name's prefix and suffix;Insert now return affected, err instead of id, err; Added UseBool & Distinct; * **v0.2.2** : Postgres drivers now support lib/pq; Added method Iterate for record by record to handler;Added SetMaxConns(go1.2+) support; some bugs fixed. -* **v0.2.1** : Added database reverse tool, now support generate go & c++ codes, see [Xorm Tool README](https://github.com/lunny/xorm/blob/master/xorm/README.md); some bug fixed. +* **v0.2.1** : Added database reverse tool, now support generate go & c++ codes, see [Xorm Tool README](https://github.com/go-xorm/xorm/blob/master/xorm/README.md); some bug fixed. * **v0.2.0** : Added Cache supported, select is speeder up 3~5x; Added SameMapper for same name between struct and table; Added Sync method for auto added tables, columns, indexes; * **v0.1.9** : Added postgres and mymysql supported; Added ` and ? supported on Raw SQL even if postgres; Added Cols, StoreEngine, Charset function, Added many column data type supported, please see [Mapping Rules](#mapping). * **v0.1.8** : Added union index and union unique supported, please see [Mapping Rules](#mapping). @@ -40,4 +51,4 @@ * **v0.1.3** : Find function now supports both slice and map; Add Table function for multi tables and temperory tables support * **v0.1.2** : Insert function now supports both struct and slice pointer parameters, batch inserting and auto transaction * **v0.1.1** : Add Id, In functions and improved README -* **v0.1.0** : Inital release. +* **v0.1.0** : Initial release. diff --git a/docs/ChangelogCN.md b/docs/ChangelogCN.md index 259d045e..1ea3f748 100644 --- a/docs/ChangelogCN.md +++ b/docs/ChangelogCN.md @@ -1,11 +1,22 @@ ## 更新日志 +* **v0.4.0 RC1** + 新特性: + * 移动xorm cmd [github.com/go-xorm/cmd](github.com/go-xorm/cmd) + * 在重构一般DB操作核心库 [github.com/go-xorm/core](https://github.com/go-xorm/core) + * 移动测试github.com/复XORM/测试 [github.com/go-xorm/tests](github.com/go-xorm/tests) + + 改进: + * Prepared statement 缓存 + * 添加 Incr API + * 指定时区位置 + * **v0.3.2** - Improvements: + 改进: * Add AllCols & MustCols function * Add TableName for custom table name - Bug Fixes: + Bug 修复: * #46 * #51 * #53 @@ -28,10 +39,10 @@ * **v0.2.3** : 改善了文档;提供了乐观锁支持;添加了带时区时间字段支持;Mapper现在分成表名Mapper和字段名Mapper,同时实现了表或字段的自定义前缀后缀;Insert方法的返回值含义从id, err更改为 affected, err,请大家注意;添加了UseBool 和 Distinct函数。 * **v0.2.2** : Postgres驱动新增了对lib/pq的支持;新增了逐条遍历方法Iterate;新增了SetMaxConns(go1.2+)支持,修复了bug若干; -* **v0.2.1** : 新增数据库反转工具,当前支持go和c++代码的生成,详见 [Xorm Tool README](https://github.com/lunny/xorm/blob/master/xorm/README.md); 修复了一些bug. -* **v0.2.0** : 新增 [缓存](https://github.com/lunny/xorm/blob/master/docs/QuickStart.md#120)支持,查询速度提升3-5倍; 新增数据库表和Struct同名的映射方式; 新增Sync同步表结构; -* **v0.1.9** : 新增 postgres 和 mymysql 驱动支持; 在Postgres中支持原始SQL语句中使用 ` 和 ? 符号; 新增Cols, StoreEngine, Charset 函数;SQL语句打印支持io.Writer接口,默认打印到控制台;新增更多的字段类型支持,详见 [映射规则](https://github.com/lunny/xorm/blob/master/docs/QuickStart.md#21);删除废弃的MakeSession和Create函数。 -* **v0.1.8** : 新增联合index,联合unique支持,请查看 [映射规则](https://github.com/lunny/xorm/blob/master/docs/QuickStart.md#21)。 +* **v0.2.1** : 新增数据库反转工具,当前支持go和c++代码的生成,详见 [Xorm Tool README](https://github.com/go-xorm/xorm/blob/master/xorm/README.md); 修复了一些bug. +* **v0.2.0** : 新增 [缓存](https://github.com/go-xorm/xorm/blob/master/docs/QuickStart.md#120)支持,查询速度提升3-5倍; 新增数据库表和Struct同名的映射方式; 新增Sync同步表结构; +* **v0.1.9** : 新增 postgres 和 mymysql 驱动支持; 在Postgres中支持原始SQL语句中使用 ` 和 ? 符号; 新增Cols, StoreEngine, Charset 函数;SQL语句打印支持io.Writer接口,默认打印到控制台;新增更多的字段类型支持,详见 [映射规则](https://github.com/go-xorm/xorm/blob/master/docs/QuickStartCn.md#21);删除废弃的MakeSession和Create函数。 +* **v0.1.8** : 新增联合index,联合unique支持,请查看 [映射规则](https://github.com/go-xorm/xorm/blob/master/docs/QuickStartCn.md#21)。 * **v0.1.7** : 新增IConnectPool接口以及NoneConnectPool, SysConnectPool, SimpleConnectPool三种实现,可以选择不使用连接池,使用系统连接池和使用自带连接池三种实现,默认为SysConnectPool,即系统自带的连接池。同时支持自定义连接池。Engine新增Close方法,在系统退出时应调用此方法。 * **v0.1.6** : 新增Conversion,支持自定义类型到数据库类型的转换;新增查询结构体自动检测匿名成员支持;新增单向映射支持; * **v0.1.5** : 新增对多线程的支持;新增Sql()函数;支持任意sql语句的struct查询;Get函数返回值变动;MakeSession和Create函数被NewSession和NewEngine函数替代; @@ -40,5 +51,3 @@ * **v0.1.2** : Insert函数支持混合struct和slice指针传入,并根据数据库类型自动批量插入,同时自动添加事务 * **v0.1.1** : 添加 Id, In 函数,改善 README 文档 * **v0.1.0** : 初始化工程 - - diff --git a/docs/QuickStart.md b/docs/QuickStart.md index c00e9727..f240806d 100644 --- a/docs/QuickStart.md +++ b/docs/QuickStart.md @@ -1,51 +1,47 @@ -xorm 快速入门 +Quick Start ===== -* [1.创建Orm引擎](#10) -* [2.定义表结构体](#20) - * [2.1.名称映射规则](#21) - * [2.2.前缀映射,后缀映射和缓存映射](#22) - * [2.3.使用Table和Tag改变名称映射](#23) - * [2.4.Column属性定义](#24) - * [2.5.Go与字段类型对应表](#25) -* [3.表结构操作](#30) - * [3.1 获取数据库信息](#31) - * [3.2 表操作](#32) - * [3.3 创建索引和唯一索引](#33) - * [3.4 同步数据库结构](#34) -* [4.插入数据](#50) -* [5.查询和统计数据](#60) - * [5.1.查询条件方法](#61) - * [5.2.临时开关方法](#62) - * [5.3.Get方法](#63) - * [5.4.Find方法](#64) - * [5.5.Iterate方法](#65) - * [5.6.Count方法](#66) - * [5.7.Rows方法](#67) -* [6.更新数据](#70) -* [6.1.乐观锁](#71) -* [7.删除数据](#80) -* [8.执行SQL查询](#90) -* [9.执行SQL命令](#100) -* [10.事务处理](#110) -* [11.缓存](#120) -* [12.事件](#125) -* [13.xorm工具](#130) - * [13.1.反转命令](#131) -* [14.Examples](#140) -* [15.案例](#150) -* [16.那些年我们踩过的坑](#160) -* [17.讨论](#170) +* [1.Create ORM Engine](#10) +* [2.Define a struct](#20) + * [2.1.Name mapping rule](#21) + * [2.2.Use Table or Tag to change table or column name](#22) + * [2.3.Column define](#23) +* [3. database schema operation](#30) + * [3.1.Retrieve database schema infomation](#31) + * [3.2.Table Operation](#32) + * [3.3.Create indexes and uniques](#33) + * [3.4.Sync database schema](#34) +* [4.Insert records](#40) +* [5.Query and Count records](#60) + * [5.1.Query condition methods](#61) + * [5.2.Temporory methods](#62) + * [5.3.Get](#63) + * [5.4.Find](#64) + * [5.5.Iterate](#65) + * [5.6.Count](#66) +* [6.Update records](#70) +* [6.1.Optimistic Locking](#71) +* [7.Delete records](#80) +* [8.Execute SQL command](#90) +* [9.Execute SQL query](#100) +* [10.Transaction](#110) +* [11.Cache](#120) +* [12.Xorm Tool](#130) + * [12.1.Reverse command](#131) +* [13.Examples](#140) +* [14.Cases](#150) +* [15.FAQ](#160) +* [16.Discuss](#170) -## 1.创建Orm引擎 +## 1.Create ORM Engine -在xorm里面,可以同时存在多个Orm引擎,一个Orm引擎称为Engine。因此在使用前必须调用NewEngine,如: +When using xorm, you can create multiple orm engines, an engine means a databse. So you can: ```Go import ( _ "github.com/go-sql-driver/mysql" - "github.com/lunny/xorm" + "github.com/go-xorm/xorm" ) engine, err := xorm.NewEngine("mysql", "root:123@/test?charset=utf8") defer engine.Close() @@ -56,15 +52,15 @@ or ```Go import ( _ "github.com/mattn/go-sqlite3" - "github.com/lunny/xorm" + "github.com/go-xorm/xorm" ) engine, err = xorm.NewEngine("sqlite3", "./test.db") defer engine.Close() ``` -一般如果只针对一个数据库进行操作,只需要创建一个Engine即可。Engine支持在多GoRutine下使用。 +Generally, you can only create one engine. Engine supports run on go rutines. -xorm当前支持五种驱动四个数据库如下: +xorm supports four drivers now: * Mysql: [github.com/Go-SQL-Driver/MySQL](https://github.com/Go-SQL-Driver/MySQL) @@ -76,18 +72,18 @@ xorm当前支持五种驱动四个数据库如下: * MsSql: [github.com/lunny/godbc](https://githubcom/lunny/godbc) -NewEngine传入的参数和`sql.Open`传入的参数完全相同,因此,使用哪个驱动前,请查看此驱动中关于传入参数的说明文档。 +NewEngine's parameters are the same as `sql.Open`. So you should read the drivers' document for parameters' usage. -在engine创建完成后可以进行一些设置,如: +After engine created, you can do some settings. -1.错误显示设置,默认如下均为`false` +1.Logs -* `engine.ShowSQL = true`,则会在控制台打印出生成的SQL语句; -* `engine.ShowDebug = true`,则会在控制台打印调试信息; -* `engine.ShowError = true`,则会在控制台打印错误信息; -* `engine.ShowWarn = true`,则会在控制台打印警告信息; +* `engine.ShowSQL = true`, Show SQL statement on standard output; +* `engine.ShowDebug = true`, Show debug infomation on standard output; +* `engine.ShowError = true`, Show error infomation on standard output; +* `engine.ShowWarn = true`, Show warnning information on standard output; -2.如果希望用其它方式记录,则可以`engine.Logger`赋值为一个`io.Writer`的实现。比如记录到Log文件,则可以: +2.If want to record infomation with another method: use `engine.Logger` as `io.Writer`: ```Go f, err := os.Create("sql.log") @@ -98,31 +94,31 @@ f, err := os.Create("sql.log") engine.Logger = f ``` -3.engine内部支持连接池接口,默认使用的Go所实现的连接池,同时自带了另外两种实现:一种是不使用连接池,另一种为一个自实现的连接池。推荐使用Go所实现的连接池。如果要使用自己实现的连接池,可以实现`xorm.IConnectPool`并通过`engine.SetPool`进行设置。推荐使用Go默认的连接池。 +3.Engine provide DB connection pool settings. -* 如果需要设置连接池的空闲数大小,可以使用`engine.SetIdleConns()`来实现。 -* 如果需要设置最大打开连接数,则可以使用`engine.SetMaxConns()`来实现。 +* Use `engine.SetMaxIdleConns()` to set idle connections. +* Use `engine.SetMaxOpenConns()` to set Max connections. This methods support only Go 1.2+. -## 2.定义表结构体 +## 2.Define struct -xorm支持将一个struct映射为数据库中对应的一张表。映射规则如下: +xorm maps 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支持结构体名称和对应的表名称以及结构体field名称与对应的表字段名称相同的命名。 +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.SetMapper(SameMapper{}) ``` -同时需要注意的是: +And you should notice: -* 如果你使用了别的命名规则映射方案,也可以自己实现一个IMapper。 -* 表名称和字段名称的映射规则默认是相同的,当然也可以设置为不同,如: +* If you want to use other mapping rule, implement IMapper +* Tables's mapping rule could be different from Columns': ```Go engine.SetTableMapper(SameMapper{}) @@ -130,23 +126,24 @@ engine.SetColumnMapper(SnakeMapper{}) ``` -### 2.2.前缀映射,后缀映射和缓存映射 +### 2.2.Prefix mapping, Suffix Mapping and Cache Mapping * 通过`engine.NewPrefixMapper(SnakeMapper{}, "prefix")`可以在SnakeMapper的基础上在命名中添加统一的前缀,当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。 * 通过`engine.NewSufffixMapper(SnakeMapper{}, "suffix")`可以在SnakeMapper的基础上在命名中添加统一的后缀,当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。 -* 通过`eneing.NewCacheMapper(SnakeMapper{})`可以组合其它的映射规则,起到在内存中缓存曾经映射过的命名映射。 +* 通过`eneing.NewCacheMapper(SnakeMapper{})`可以起到在内存中缓存曾经映射过的命名映射。 - -### 2.3.使用Table和Tag改变名称映射 +当然,如果你使用了别的命名规则映射方案,也可以自己实现一个IMapper。 + + +### 2.3.Tag mapping 如果所有的命名都是按照IMapper的映射来操作的,那当然是最理想的。但是如果碰到某个表名或者某个字段名跟映射规则不匹配时,我们就需要别的机制来改变。 -* 如果struct拥有`Tablename() string`的成员方法,那么此方法的返回值即是该struct默认对应的数据库表名。 - -* 通过`engine.Table()`方法可以改变struct对应的数据库表的名称,通过sturct中field对应的Tag中使用`xorm:"'column_name'"`可以使该field对应的Column名称为指定名称。这里使用两个单引号将Column名称括起来是为了防止名称冲突,因为我们在Tag中还可以对这个Column进行更多的定义。如果名称不冲突的情况,单引号也可以不使用。 +通过`engine.Table()`方法可以改变struct对应的数据库表的名称,通过sturct中field对应的Tag中使用`xorm:"'table_name'"`可以使该field对应的Column名称为指定名称。这里使用两个单引号将Column名称括起来是为了防止名称冲突,因为我们在Tag中还可以对这个Column进行更多的定义。如果名称不冲突的情况,单引号也可以不使用。 -### 2.4.Column属性定义 +### 2.4.Column defenition + 我们在field对应的Tag中对Column的一些属性进行定义,定义的方法基本和我们写SQL定义表结构类似,比如: ``` @@ -156,37 +153,37 @@ type User struct { } ``` -对于不同的数据库系统,数据类型其实是有些差异的。因此xorm中对数据类型有自己的定义,基本的原则是尽量兼容各种数据库的字段类型,具体的字段对应关系可以查看[字段类型对应表](https://github.com/lunny/xorm/blob/master/docs/COLUMNTYPE.md)。对于使用者,一般只要使用自己熟悉的数据库字段定义即可。 +For different DBMS, data types对于不同的数据库系统,数据类型其实是有些差异的。因此xorm中对数据类型有自己的定义,基本的原则是尽量兼容各种数据库的字段类型,具体的字段对应关系可以查看[字段类型对应表](https://github.com/go-xorm/xorm/blob/master/docs/COLUMNTYPE.md)。 具体的映射规则如下,另Tag中的关键字均不区分大小写,字段名区分大小写: - + - + - + - + - + - + - + - + @@ -195,26 +192,26 @@ type User struct { - + - - - - + - + + + +
name当前field对应的字段的名称,可选,如不写,则自动根据field名字和转换规则命名,如与其它关键字冲突,请使用单引号括起来。name or 'name'Column Name, optional
pk是否是Primary Key,如果在一个struct中有多个字段都使用了此标记,则这多个字段构成了复合主键,单主键当前支持int32,int,int64,uint32,uint,uint64,string这7种Go的数据类型,复合主键支持这7种Go的数据类型的组合。pkIf column is Primary Key
当前支持30多种字段类型,详情参见 [字段类型](https://github.com/lunny/xorm/blob/master/docs/COLUMNTYPE.md)字段类型当前支持30多种字段类型,详情参见 [字段类型](https://github.com/go-xorm/xorm/blob/master/docs/COLUMNTYPE.md)字段类型
autoincr是否是自增autoincrIf autoincrement column
[not ]null 或 notnull是否可以为空[not ]null | notnullif column could be blank
unique或unique(uniquename)是否是唯一,如不加括号则该字段不允许重复;如加上括号,则括号中为联合唯一索引的名字,此时如果有另外一个或多个字段和本unique的uniquename相同,则这些uniquename相同的字段组成联合唯一索引unique/unique(uniquename)是否是唯一,如不加括号则该字段不允许重复;如加上括号,则括号中为联合唯一索引的名字,此时如果有另外一个或多个字段和本unique的uniquename相同,则这些uniquename相同的字段组成联合唯一索引
index或index(indexname)是否是索引,如不加括号则该字段自身为索引,如加上括号,则括号中为联合索引的名字,此时如果有另外一个或多个字段和本index的indexname相同,则这些indexname相同的字段组成联合索引index/index(indexname)是否是索引,如不加括号则该字段自身为索引,如加上括号,则括号中为联合索引的名字,此时如果有另外一个或多个字段和本index的indexname相同,则这些indexname相同的字段组成联合索引
extends应用于一个匿名结构体之上,表示此匿名结构体的成员也映射到数据库中
-这个Field将不进行字段映射-This field will not be mapping
->这个Field将只写入到数据库而不从数据库读取<-这个Field将只从数据库读取,而不写入到数据库
created这个Field将在Insert时自动赋值为当前时间createdThis field will be filled in current time on insert
updated这个Field将在Insert或Update时自动赋值为当前时间
version这个Field将会在insert时默认为1,每次更新自动加1updatedThis field will be filled in current time on insert or update
default 0设置默认值,紧跟的内容如果是Varchar等需要加上单引号versionThis field will be filled 1 on insert and autoincrement on update
default 0 | default 'name'column default value
另外有如下几条自动映射的规则: -- 1.如果field名称为`Id`而且类型为`int64`并且没有定义tag,则会被xorm视为主键,并且拥有自增属性。如果想用`Id`以外的名字或非int64类型做为主键名,必须在对应的Tag上加上`xorm:"pk"`来定义主键,加上`xorm:"autoincr"`作为自增。这里需要注意的是,有些数据库并不允许非主键的自增属性。 +- 1.如果field名称为`Id`而且类型为`int64`的话,会被xorm视为主键,并且拥有自增属性。如果想用`Id`以外的名字做为主键名,可以在对应的Tag上加上`xorm:"pk"`来定义主键。 - 2.string类型默认映射为varchar(255),如果需要不同的定义,可以在tag中自定义 -- 3.支持`type MyString string`等自定义的field,支持Slice, Map等field成员,这些成员默认存储为Text类型,并且默认将使用Json格式来序列化和反序列化。也支持数据库字段类型为Blob类型,如果是Blob类型,则先使用Json格式序列化再转成[]byte格式。当然[]byte或者[]uint8默认为Blob类型并且都以二进制方式存储。具体参见 [go类型<->数据库类型对应表](https://github.com/lunny/xorm/blob/master/docs/AutoMap.md) +- 3.支持`type MyString string`等自定义的field,支持Slice, Map等field成员,这些成员默认存储为Text类型,并且默认将使用Json格式来序列化和反序列化。也支持数据库字段类型为Blob类型,如果是Blob类型,则先使用Json格式序列化再转成[]byte格式。当然[]byte或者[]uint8默认为Blob类型并且都以二进制方式存储。 - 4.实现了Conversion接口的类型或者结构体,将根据接口的转换方式在类型和数据库记录之间进行相互转换。 ```Go @@ -224,66 +221,50 @@ type Conversion interface { } ``` - -### 2.4.Go与字段类型对应表 - -如果不使用tag来定义field对应的数据库字段类型,那么系统会自动给出一个默认的字段类型,对应表如下: - -[go类型<->数据库类型对应表](https://github.com/lunny/xorm/blob/master/docs/AutoMap.md) - ## 3.表结构操作 xorm提供了一些动态获取和修改表结构的方法。对于一般的应用,很少动态修改表结构,则只需调用Sync()同步下表结构即可。 -## 3.1 获取数据库信息 +## 3.1 retrieve database meta info * DBMetas() - -xorm支持获取表结构信息,通过调用`engine.DBMetas()`可以获取到所有的表,字段,索引的信息。 +xorm支持获取表结构信息,通过调用`engine.DBMetas()`可以获取到所有的表的信息 -## 3.2.表操作 +## 3.2.directly table operation * CreateTables() - 创建表使用`engine.CreateTables()`,参数为一个或多个空的对应Struct的指针。同时可用的方法有Charset()和StoreEngine(),如果对应的数据库支持,这两个方法可以在创建表时指定表的字符编码和使用的引擎。当前仅支持Mysql数据库。 * IsTableEmpty() - 判断表是否为空,参数和CreateTables相同 * IsTableExist() - 判断表是否存在 * DropTables() - 删除表使用`engine.DropTables()`,参数为一个或多个空的对应Struct的指针或者表的名字。如果为string传入,则只删除对应的表,如果传入的为Struct,则删除表的同时还会删除对应的索引。 -## 3.3.创建索引和唯一索引 +## 3.3.create indexes and uniques * CreateIndexes - 根据struct中的tag来创建索引 * CreateUniques - 根据struct中的tag来创建唯一索引 ## 3.4.同步数据库结构 同步能够部分智能的根据结构体的变动检测表结构的变动,并自动同步。目前能够实现: - -* 1) 自动检测和创建表,这个检测是根据表的名字 -* 2)自动检测和新增表中的字段,这个检测是根据字段名 -* 3)自动检测和创建索引和唯一索引,这个检测是根据一个或多个字段名,而不根据索引名称 +1) 自动检测和创建表,这个检测是根据表的名字 +2)自动检测和新增表中的字段,这个检测是根据字段名 +3)自动检测和创建索引和唯一索引,这个检测是根据一个或多个字段名,而不根据索引名称 调用方法如下: - ```Go err := engine.Sync(new(User)) ``` @@ -291,25 +272,21 @@ err := engine.Sync(new(User)) ## 4.插入数据 -插入数据使用Insert方法,Insert方法的参数可以是一个或多个Struct的指针,一个或多个Struct的Slice的指针。 -如果传入的是Slice并且当数据库支持批量插入时,Insert会使用批量插入的方式进行插入。 - -* 插入一条数据 +Inserting records use Insert method. +* Insert one record ```Go user := new(User) user.Name = "myname" affected, err := engine.Insert(user) ``` -在插入单条数据成功后,如果该结构体有自增字段,则自增字段会被自动赋值为数据库中的id - +After inseted, `user.Id` will be filled with primary key column value. ```Go fmt.Println(user.Id) ``` -* 插入同一个表的多条数据 - +* Insert multiple records by Slice on one table ```Go users := make([]User, 0) users[0].Name = "name0" @@ -317,8 +294,7 @@ users[0].Name = "name0" affected, err := engine.Insert(&users) ``` -* 使用指针Slice插入多条记录 - +* Insert multiple records by Slice of pointer on one table ```Go users := make([]*User, 0) users[0] = new(User) @@ -327,8 +303,7 @@ users[0].Name = "name0" affected, err := engine.Insert(&users) ``` -* 插入不同表的一条记录 - +* Insert one record on two table. ```Go user := new(User) user.Name = "myname" @@ -337,8 +312,7 @@ question.Content = "whywhywhwy?" affected, err := engine.Insert(user, question) ``` -* 插入不同表的多条记录 - +* Insert multiple records on multiple tables. ```Go users := make([]User, 0) users[0].Name = "name0" @@ -348,7 +322,7 @@ questions[0].Content = "whywhywhwy?" affected, err := engine.Insert(&users, &questions) ``` -* 插入不同表的一条或多条记录 +* Insert one or multple records on multiple tables. ```Go user := new(User) user.Name = "myname" @@ -358,27 +332,23 @@ questions[0].Content = "whywhywhwy?" affected, err := engine.Insert(user, &questions) ``` -这里需要注意以下几点: -* 这里虽然支持同时插入,但这些插入并没有事务关系。因此有可能在中间插入出错后,后面的插入将不会继续。 -* 多条插入会自动生成`Insert into table values (),(),()`的语句,因此这样的语句有一个最大的记录数,根据经验测算在150条左右。大于150条后,生成的sql语句将太长可能导致执行失败。因此在插入大量数据时,目前需要自行分割成每150条插入一次。 +Notice: If you want to use transaction on inserting, you should use session.Begin() before calling Insert. -## 5.查询和统计数据 +## 5.Query and count -所有的查询条件不区分调用顺序,但必须在调用Get,Find,Count, Iterate, Rows这几个函数之前调用。同时需要注意的一点是,在调用的参数中,如果采用默认的`SnakeMapper`所有的字符字段名均为映射后的数据库的字段名,而不是field的名字。 +所有的查询条件不区分调用顺序,但必须在调用Get,Find,Count这三个函数之前调用。同时需要注意的一点是,在调用的参数中,所有的字符字段名均为映射后的数据库的字段名,而不是field的名字。 ### 5.1.查询条件方法 -查询和统计主要使用`Get`, `Find`, `Count`, `Rows`, `Iterate`这几个方法。在进行查询时可以使用多个方法来形成查询条件,条件函数如下: +查询和统计主要使用`Get`, `Find`, `Count`三个方法。在进行查询时可以使用多个方法来形成查询条件,条件函数如下: -* Id(interface{}) -传入一个PK字段的值,作为查询条件,如果是复合主键,则 -`Id(xorm.PK{1, 2})` -传入的两个参数按照struct中pk标记字段出现的顺序赋值。 +* Id(int64) +传入一个PK字段的值,作为查询条件 * Where(string, …interface{}) -和SQL中Where语句中的条件基本相同,作为条件 +和Where语句中的条件基本相同,作为条件 * And(string, …interface{}) 和Where函数中的条件基本相同,作为条件 @@ -399,7 +369,7 @@ affected, err := engine.Insert(user, &questions) 按照指定的顺序进行排序 * In(string, …interface{}) -某字段在一些值中,这里需要注意必须是[]interface{}才可以展开,由于Go语言的限制,[]int64等均不可以展开。 +某字段在一些值中 * Cols(…string) 只查询或更新某些指定的字段,默认是查询所有映射的字段或者根据Update的第一个参数来判断更新的字段。例如: @@ -410,11 +380,7 @@ engine.Cols("age", "name").Update(&user) // UPDATE user SET age=? AND name=? ``` -* AllCols() -查询或更新所有字段。 - -* MustCols(…string) -某些字段必须更新。 +其中的参数"age", "name"也可以写成"age, name",两种写法均可 * Omit(...string) 和cols相反,此函数指定排除某些指定的字段。注意:此方法和Cols方法不可同时使用 @@ -461,84 +427,65 @@ Having的参数字符串 * UseBool(...string) 当从一个struct来生成查询条件或更新字段时,xorm会判断struct的field是否为0,"",nil,如果为以上则不当做查询条件或者更新内容。因为bool类型只有true和false两种值,因此默认所有bool类型不会作为查询条件或者更新字段。如果可以使用此方法,如果默认不传参数,则所有的bool字段都将会被使用,如果参数不为空,则参数中指定的为字段名,则这些字段对应的bool值将被使用。 -* NoCascade() +* Cascade(bool) 是否自动关联查询field中的数据,如果struct的field也是一个struct并且映射为某个Id,则可以在查询时自动调用Get方法查询出对应的数据。 - -### 5.3.Get方法 - -查询单条数据使用`Get`方法,在调用Get方法时需要传入一个对应结构体的指针,同时结构体中的非空field自动成为查询的条件和前面的方法条件组合在一起查询。 - -如: - -1) 根据Id来获得单条数据: + +### 5.3.Get one record +Fetch a single object by user ```Go -user := new(User) -has, err := engine.Id(id).Get(user) -// 复合主键的获取方法 -// has, errr := engine.Id(xorm.PK{1,2}).Get(user) +var user = User{Id:27} +has, err := engine.Get(&user) +// or has, err := engine.Id(27).Get(&user) + +var user = User{Name:"xlw"} +has, err := engine.Get(&user) ``` -2) 根据Where来获得单条数据: + +### 5.4.Find +Fetch multipe objects into a slice or a map, use Find: ```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 -user := &User{Name:"xlw"} -has, err := engine.Get(user) -``` - -返回的结果为两个参数,一个`has`为该条记录是否存在,第二个参数`err`为是否有错误。不管err是否为nil,has都有可能为true或者false。 - - -### 5.4.Find方法 - -查询多条数据使用`Find`方法,Find方法的第一个参数为`slice`的指针或`Map`指针,即为查询后返回的结果,第二个参数可选,为查询的条件struct的指针。 - -1) 传入Slice用于返回数据 - -```Go -everyone := make([]Userinfo, 0) +var everyone []Userinfo err := engine.Find(&everyone) -pEveryOne := make([]*Userinfo, 0) -err := engine.Find(&pEveryOne) -``` - -2) 传入Map用户返回数据,map必须为`map[int64]Userinfo`的形式,map的key为id,因此对于复合主键无法使用这种方式。 - -```Go users := make(map[int64]Userinfo) err := engine.Find(&users) - -pUsers := make(map[int64]*Userinfo) -err := engine.Find(&pUsers) ``` -3) 也可以加入各种条件 +* also you can use Where, Limit ```Go -users := make([]Userinfo, 0) -err := engine.Where("age > ? or name = ?", 30, "xlw").Limit(20, 10).Find(&users) +var allusers []Userinfo +err := engine.Where("id > ?", "3").Limit(10,20).Find(&allusers) //Get id>3 limit 10 offset 20 ``` - -### 5.5.Iterate方法 +* or you can use a struct query -Iterate方法提供逐条执行查询到的记录的方法,他所能使用的条件和Find方法完全相同 +```Go +var tenusers []Userinfo +err := engine.Limit(10).Find(&tenusers, &Userinfo{Name:"xlw"}) //Get All Name="xlw" limit 10 offset 0 +``` + +* or In function + +```Go +var tenusers []Userinfo +err := engine.In("id", 1, 3, 5).Find(&tenusers) //Get All id in (1, 3, 5) +``` + +* The default will query all columns of a table. Use Cols function if you want to select some columns + +```Go +var tenusers []Userinfo +err := engine.Cols("id", "name").Find(&tenusers) //Find only id and name +``` + + +### 5.5.Iterate records +Iterate, like find, but handle records one by one ```Go err := engine.Where("age > ? or name=?)", 30, "xlw").Iterate(new(Userinfo), func(i int, bean interface{})error{ @@ -556,22 +503,6 @@ user := new(User) total, err := engine.Where("id >?", 1).Count(user) ``` - -### 5.7.Rows方法 - -Rows方法和Iterate方法类似,提供逐条执行查询到的记录的方法,不过Rows更加灵活好用。 -```Go -user := new(User) -rows, err := engine.Where("id >?", 1).Rows(user) -if err != nil { -} -defer rows.Close() -for rows.Next() { - err = rows.Scan(user) - //... -} -``` - ## 6.更新数据 @@ -585,19 +516,17 @@ 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}) ``` - + ### 6.1.乐观锁 要使用乐观锁,需要使用version标记 @@ -617,51 +546,53 @@ engine.Id(1).Update(&user) // UPDATE user SET ..., version = version + 1 WHERE id = ? AND version = ? ``` - -## 7.删除数据 -删除数据`Delete`方法,参数为struct的指针并且成为查询条件。 + +## 7.Delete one or more records +Delete one or more records + +* delete by id ```Go -user := new(User) -affected, err := engine.Id(id).Delete(user) +err := engine.Id(1).Delete(&User{}) ``` -`Delete`的返回值第一个参数为删除的记录数,第二个参数为错误。 +* delete by other conditions -注意:当删除时,如果user中包含有bool,float64或者float32类型,有可能会使删除失败。具体请查看 FAQ +```Go +err := engine.Delete(&User{Name:"xlw"}) +``` -## 8.执行SQL查询 +## 8.Execute SQL query -也可以直接执行一个SQL查询,即Select命令。在Postgres中支持原始SQL语句中使用 ` 和 ? 符号。 +Of course, SQL execution is also provided. + +If select then use Query ```Go sql := "select * from userinfo" results, err := engine.Query(sql) ``` -当调用`Query`时,第一个返回值`results`为`[]map[string][]byte`的形式。 - -## 9.执行SQL命令 - -也可以直接执行一个SQL命令,即执行Insert, Update, Delete 等操作。此时不管数据库是何种类型,都可以使用 ` 和 ? 符号。 +## 9.Execute SQL command +If insert, update or delete then use Exec ```Go -sql = "update `userinfo` set username=? where id=?" +sql = "update userinfo set username=? where id=?" res, err := engine.Exec(sql, "xiaolun", 1) ``` -## 10.事务处理 -当使用事务处理时,需要创建Session对象。在进行事物处理时,可以混用ORM方法和RAW方法,如下代码所示: +## 10.Transaction ```Go session := engine.NewSession() defer session.Close() + // add Begin() before any action -err := session.Begin() +err := session.Begin() user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()} _, err = session.Insert(&user1) if err != nil { @@ -688,147 +619,78 @@ if err != nil { } ``` -* 注意如果您使用的是mysql,数据库引擎为innodb事务才有效,myisam引擎是不支持事务的。 - ## 11.缓存 -xorm内置了一致性缓存支持,不过默认并没有开启。要开启缓存,需要在engine创建完后进行配置,如: -启用一个全局的内存缓存 +1. Global Cache +Xorm implements cache support. Defaultly, it's disabled. If enable it, use below code. ```Go cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000) engine.SetDefaultCacher(cacher) ``` -上述代码采用了LRU算法的一个缓存,缓存方式是存放到内存中,缓存struct的记录数为1000条,缓存针对的范围是所有具有主键的表,没有主键的表中的数据将不会被缓存。 -如果只想针对部分表,则: +If disable some tables' cache, then: + +```Go +engine.MapCacher(&user, nil) +``` + +2. Table's Cache +If only some tables need cache, then: ```Go cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000) engine.MapCacher(&user, cacher) ``` -如果要禁用某个表的缓存,则: +Caution: -```Go -engine.MapCacher(&user, nil) -``` +1. When use Cols methods on cache enabled, the system still return all the columns. -设置完之后,其它代码基本上就不需要改动了,缓存系统已经在后台运行。 - -当前实现了内存存储的CacheStore接口MemoryStore,如果需要采用其它设备存储,可以实现CacheStore接口。 - -不过需要特别注意不适用缓存或者需要手动编码的地方: - -1. 当使用了`Distinct`,`Having`,`GroupBy`方法将不会使用缓存 - -2. 在`Get`或者`Find`时使用了`Cols`,`Omit`方法,则在开启缓存后此方法无效,系统仍旧会取出这个表中的所有字段。 - -3. 在使用Exec方法执行了方法之后,可能会导致缓存与数据库不一致的地方。因此如果启用缓存,尽量避免使用Exec。如果必须使用,则需要在使用了Exec之后调用ClearCache手动做缓存清除的工作。比如: +2. When using Exec method, you should clear cache: ```Go engine.Exec("update user set name = ? where id = ?", "xlw", 1) engine.ClearCache(new(User)) ``` -缓存的实现原理如下图所示: +Cache implement theory below: -![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{}). +![cache design](https://raw.github.com/go-xorm/xorm/master/docs/cache_design.png) -## 13.xorm工具 +## 12.xorm tool xorm工具提供了xorm命令,能够帮助做很多事情。 -### 13.1.反转命令 -参见 [xorm工具](https://github.com/lunny/xorm/tree/master/xorm) +### 12.1.Reverse command +Please visit [xorm tool](https://github.com/go-xorm/xorm/tree/master/xorm) -## 14.Examples +## 13.Examples -请访问[https://github.com/lunny/xorm/tree/master/examples](https://github.com/lunny/xorm/tree/master/examples) +请访问[https://github.com/go-xorm/xorm/tree/master/examples](https://github.com/go-xorm/xorm/tree/master/examples) -## 15.案例 +## 14.Cases -* [Gowalker](http://gowalker.org),源代码 [github.com/Unknwon/gowalker](http://github.com/Unknwon/gowalker) +* [Gowalker](http://gowalker.org),source [github.com/Unknwon/gowalker](http://github.com/Unknwon/gowalker) -* [GoDaily Go语言学习网站](http://godaily.org),源代码 [github.com/govc/godaily](http://github.com/govc/godaily) +* [GoDaily](http://godaily.org),source [github.com/govc/godaily](http://github.com/govc/godaily) -* [Sudochina](http://sudochina.com) 和对应的源代码[github.com/insionng/toropress](http://github.com/insionng/toropress) +* [Sudochina](http://sudochina.com) source [github.com/insionng/toropress](http://github.com/insionng/toropress) * [VeryHour](http://veryhour.com) - -## 16.那些年我们踩过的坑 -* 怎么同时使用xorm的tag和json的tag? + +## 15.FAQ + +1.How the xorm tag use both with json? -答:使用空格 + Use space. ```Go type User struct { Name string `json:"name" xorm:"name"` } ``` - -* 我的struct里面包含bool类型,为什么它不能作为条件也没法用Update更新? - -答:默认bool类型因为无法判断是否为空,所以不会自动作为条件也不会作为Update的内容。可以使用UseBool函数,也可以使用Cols函数 - -```Go -engine.Cols("bool_field").Update(&Struct{BoolField:true}) -// UPDATE struct SET bool_field = true -``` - -* 我的struct里面包含float64和float32类型,为什么用他们作为查询条件总是不正确? - -答:默认float32和float64映射到数据库中为float,real,double这几种类型,这几种数据库类型数据库的实现一般都是非精确的。因此作为相等条件查询有可能不会返回正确的结果。如果一定要作为查询条件,请将数据库中的类型定义为Numeric或者Decimal。 - -```Go -type account struct { -money float64 `xorm:"Numeric"` -} -``` - -* 为什么Update时Sqlite3返回的affected和其它数据库不一样? - -答:Sqlite3默认Update时返回的是update的查询条件的记录数条数,不管记录是否真的有更新。而Mysql和Postgres默认情况下都是只返回记录中有字段改变的记录数。 - -* xorm有几种命名映射规则? - -答:目前支持SnakeMapper和SameMapper两种。SnakeMapper支持结构体和成员以驼峰式命名而数据库表和字段以下划线连接命名;SameMapper支持结构体和数据库的命名保持一致的映射。 - -* xorm支持复合主键吗? - -答:支持。在定义时,如果有多个字段标记了pk,则这些字段自动成为复合主键,顺序为在struct中出现的顺序。在使用Id方法时,可以用`Id(xorm.PK{1, 2})`的方式来用。 - - - -## 17.讨论 -请加入QQ群:280360085 进行讨论。 diff --git a/docs/QuickStartCn.md b/docs/QuickStartCn.md new file mode 100644 index 00000000..2b76ca8e --- /dev/null +++ b/docs/QuickStartCn.md @@ -0,0 +1,834 @@ +xorm 快速入门 +===== + +* [1.创建Orm引擎](#10) +* [2.定义表结构体](#20) + * [2.1.名称映射规则](#21) + * [2.2.前缀映射,后缀映射和缓存映射](#22) + * [2.3.使用Table和Tag改变名称映射](#23) + * [2.4.Column属性定义](#24) + * [2.5.Go与字段类型对应表](#25) +* [3.表结构操作](#30) + * [3.1 获取数据库信息](#31) + * [3.2 表操作](#32) + * [3.3 创建索引和唯一索引](#33) + * [3.4 同步数据库结构](#34) +* [4.插入数据](#50) +* [5.查询和统计数据](#60) + * [5.1.查询条件方法](#61) + * [5.2.临时开关方法](#62) + * [5.3.Get方法](#63) + * [5.4.Find方法](#64) + * [5.5.Iterate方法](#65) + * [5.6.Count方法](#66) + * [5.7.Rows方法](#67) +* [6.更新数据](#70) +* [6.1.乐观锁](#71) +* [7.删除数据](#80) +* [8.执行SQL查询](#90) +* [9.执行SQL命令](#100) +* [10.事务处理](#110) +* [11.缓存](#120) +* [12.事件](#125) +* [13.xorm工具](#130) + * [13.1.反转命令](#131) +* [14.Examples](#140) +* [15.案例](#150) +* [16.那些年我们踩过的坑](#160) +* [17.讨论](#170) + + +## 1.创建Orm引擎 + +在xorm里面,可以同时存在多个Orm引擎,一个Orm引擎称为Engine。因此在使用前必须调用NewEngine,如: + +```Go +import ( + _ "github.com/go-sql-driver/mysql" + "github.com/go-xorm/xorm" +) +engine, err := xorm.NewEngine("mysql", "root:123@/test?charset=utf8") +defer engine.Close() +``` + +or + +```Go +import ( + _ "github.com/mattn/go-sqlite3" + "github.com/go-xorm/xorm" + ) +engine, err = xorm.NewEngine("sqlite3", "./test.db") +defer engine.Close() +``` + +一般如果只针对一个数据库进行操作,只需要创建一个Engine即可。Engine支持在多GoRutine下使用。 + +xorm当前支持五种驱动四个数据库如下: + +* 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) + +* MsSql: [github.com/lunny/godbc](https://githubcom/lunny/godbc) + +NewEngine传入的参数和`sql.Open`传入的参数完全相同,因此,使用哪个驱动前,请查看此驱动中关于传入参数的说明文档。 + +在engine创建完成后可以进行一些设置,如: + +1.错误显示设置,默认如下均为`false` + +* `engine.ShowSQL = true`,则会在控制台打印出生成的SQL语句; +* `engine.ShowDebug = true`,则会在控制台打印调试信息; +* `engine.ShowError = true`,则会在控制台打印错误信息; +* `engine.ShowWarn = true`,则会在控制台打印警告信息; + +2.如果希望用其它方式记录,则可以`engine.Logger`赋值为一个`io.Writer`的实现。比如记录到Log文件,则可以: + +```Go +f, err := os.Create("sql.log") + if err != nil { + println(err.Error()) + return + } +engine.Logger = f +``` + +3.engine内部支持连接池接口。 + +* 如果需要设置连接池的空闲数大小,可以使用`engine.SetMaxIdleConns()`来实现。 +* 如果需要设置最大打开连接数,则可以使用`engine.SetMaxOpenConns()`来实现。 + + +## 2.定义表结构体 + +xorm支持将一个struct映射为数据库中对应的一张表。映射规则如下: + + +### 2.1.名称映射规则 + +名称映射规则主要负责结构体名称到表名和结构体field到表字段的名称映射。由xorm.IMapper接口的实现者来管理,xorm内置了两种IMapper实现:`SnakeMapper` 和 `SameMapper`。SnakeMapper支持struct为驼峰式命名,表结构为下划线命名之间的转换;SameMapper支持结构体名称和对应的表名称以及结构体field名称与对应的表字段名称相同的命名。 + +当前SnakeMapper为默认值,如果需要改变时,在engine创建完成后使用 + +```Go +engine.SetMapper(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{})`可以组合其它的映射规则,起到在内存中缓存曾经映射过的命名映射。 + + +### 2.3.使用Table和Tag改变名称映射 + +如果所有的命名都是按照IMapper的映射来操作的,那当然是最理想的。但是如果碰到某个表名或者某个字段名跟映射规则不匹配时,我们就需要别的机制来改变。 + +* 如果struct拥有`Tablename() string`的成员方法,那么此方法的返回值即是该struct默认对应的数据库表名。 + +* 通过`engine.Table()`方法可以改变struct对应的数据库表的名称,通过sturct中field对应的Tag中使用`xorm:"'column_name'"`可以使该field对应的Column名称为指定名称。这里使用两个单引号将Column名称括起来是为了防止名称冲突,因为我们在Tag中还可以对这个Column进行更多的定义。如果名称不冲突的情况,单引号也可以不使用。 + + +### 2.4.Column属性定义 +我们在field对应的Tag中对Column的一些属性进行定义,定义的方法基本和我们写SQL定义表结构类似,比如: + +``` +type User struct { + Id int64 + Name string `xorm:"varchar(25) not null unique 'usr_name'"` +} +``` + +对于不同的数据库系统,数据类型其实是有些差异的。因此xorm中对数据类型有自己的定义,基本的原则是尽量兼容各种数据库的字段类型,具体的字段对应关系可以查看[字段类型对应表](https://github.com/go-xorm/xorm/blob/master/docs/COLUMNTYPE.md)。对于使用者,一般只要使用自己熟悉的数据库字段定义即可。 + +具体的映射规则如下,另Tag中的关键字均不区分大小写,字段名区分大小写: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
name当前field对应的字段的名称,可选,如不写,则自动根据field名字和转换规则命名,如与其它关键字冲突,请使用单引号括起来。
pk是否是Primary Key,如果在一个struct中有多个字段都使用了此标记,则这多个字段构成了复合主键,单主键当前支持int32,int,int64,uint32,uint,uint64,string这7种Go的数据类型,复合主键支持这7种Go的数据类型的组合。
当前支持30多种字段类型,详情参见 [字段类型](https://github.com/go-xorm/xorm/blob/master/docs/COLUMNTYPE.md)字段类型
autoincr是否是自增
[not ]null 或 notnull是否可以为空
unique或unique(uniquename)是否是唯一,如不加括号则该字段不允许重复;如加上括号,则括号中为联合唯一索引的名字,此时如果有另外一个或多个字段和本unique的uniquename相同,则这些uniquename相同的字段组成联合唯一索引
index或index(indexname)是否是索引,如不加括号则该字段自身为索引,如加上括号,则括号中为联合索引的名字,此时如果有另外一个或多个字段和本index的indexname相同,则这些indexname相同的字段组成联合索引
extends应用于一个匿名结构体之上,表示此匿名结构体的成员也映射到数据库中
-这个Field将不进行字段映射
->这个Field将只写入到数据库而不从数据库读取
<-这个Field将只从数据库读取,而不写入到数据库
created这个Field将在Insert时自动赋值为当前时间
updated这个Field将在Insert或Update时自动赋值为当前时间
version这个Field将会在insert时默认为1,每次更新自动加1
default 0设置默认值,紧跟的内容如果是Varchar等需要加上单引号
+ +另外有如下几条自动映射的规则: + +- 1.如果field名称为`Id`而且类型为`int64`并且没有定义tag,则会被xorm视为主键,并且拥有自增属性。如果想用`Id`以外的名字或非int64类型做为主键名,必须在对应的Tag上加上`xorm:"pk"`来定义主键,加上`xorm:"autoincr"`作为自增。这里需要注意的是,有些数据库并不允许非主键的自增属性。 + +- 2.string类型默认映射为varchar(255),如果需要不同的定义,可以在tag中自定义 + +- 3.支持`type MyString string`等自定义的field,支持Slice, Map等field成员,这些成员默认存储为Text类型,并且默认将使用Json格式来序列化和反序列化。也支持数据库字段类型为Blob类型,如果是Blob类型,则先使用Json格式序列化再转成[]byte格式。当然[]byte或者[]uint8默认为Blob类型并且都以二进制方式存储。具体参见 [go类型<->数据库类型对应表](https://github.com/go-xorm/xorm/blob/master/docs/AutoMap.md) + +- 4.实现了Conversion接口的类型或者结构体,将根据接口的转换方式在类型和数据库记录之间进行相互转换。 +```Go +type Conversion interface { + FromDB([]byte) error + ToDB() ([]byte, error) +} +``` + + +### 2.4.Go与字段类型对应表 + +如果不使用tag来定义field对应的数据库字段类型,那么系统会自动给出一个默认的字段类型,对应表如下: + +[go类型<->数据库类型对应表](https://github.com/go-xorm/xorm/blob/master/docs/AutoMap.md) + + +## 3.表结构操作 + +xorm提供了一些动态获取和修改表结构的方法。对于一般的应用,很少动态修改表结构,则只需调用Sync()同步下表结构即可。 + + +## 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)自动检测和创建索引和唯一索引,这个检测是根据一个或多个字段名,而不根据索引名称 + +调用方法如下: + +```Go +err := engine.Sync(new(User)) +``` + + +## 4.插入数据 + +插入数据使用Insert方法,Insert方法的参数可以是一个或多个Struct的指针,一个或多个Struct的Slice的指针。 +如果传入的是Slice并且当数据库支持批量插入时,Insert会使用批量插入的方式进行插入。 + +* 插入一条数据 + +```Go +user := new(User) +user.Name = "myname" +affected, err := engine.Insert(user) +``` + +在插入单条数据成功后,如果该结构体有自增字段,则自增字段会被自动赋值为数据库中的id + +```Go +fmt.Println(user.Id) +``` + +* 插入同一个表的多条数据 + +```Go +users := make([]User, 0) +users[0].Name = "name0" +... +affected, err := engine.Insert(&users) +``` + +* 使用指针Slice插入多条记录 + +```Go +users := make([]*User, 0) +users[0] = new(User) +users[0].Name = "name0" +... +affected, err := engine.Insert(&users) +``` + +* 插入不同表的一条记录 + +```Go +user := new(User) +user.Name = "myname" +question := new(Question) +question.Content = "whywhywhwy?" +affected, err := engine.Insert(user, question) +``` + +* 插入不同表的多条记录 + +```Go +users := make([]User, 0) +users[0].Name = "name0" +... +questions := make([]Question, 0) +questions[0].Content = "whywhywhwy?" +affected, err := engine.Insert(&users, &questions) +``` + +* 插入不同表的一条或多条记录 +```Go +user := new(User) +user.Name = "myname" +... +questions := make([]Question, 0) +questions[0].Content = "whywhywhwy?" +affected, err := engine.Insert(user, &questions) +``` + +这里需要注意以下几点: +* 这里虽然支持同时插入,但这些插入并没有事务关系。因此有可能在中间插入出错后,后面的插入将不会继续。 +* 多条插入会自动生成`Insert into table values (),(),()`的语句,因此这样的语句有一个最大的记录数,根据经验测算在150条左右。大于150条后,生成的sql语句将太长可能导致执行失败。因此在插入大量数据时,目前需要自行分割成每150条插入一次。 + + +## 5.查询和统计数据 + +所有的查询条件不区分调用顺序,但必须在调用Get,Find,Count, Iterate, Rows这几个函数之前调用。同时需要注意的一点是,在调用的参数中,如果采用默认的`SnakeMapper`所有的字符字段名均为映射后的数据库的字段名,而不是field的名字。 + + +### 5.1.查询条件方法 + +查询和统计主要使用`Get`, `Find`, `Count`, `Rows`, `Iterate`这几个方法。在进行查询时可以使用多个方法来形成查询条件,条件函数如下: + +* Id(interface{}) +传入一个PK字段的值,作为查询条件,如果是复合主键,则 +`Id(xorm.PK{1, 2})` +传入的两个参数按照struct中pk标记字段出现的顺序赋值。 + +* Where(string, …interface{}) +和SQL中Where语句中的条件基本相同,作为条件 + +* And(string, …interface{}) +和Where函数中的条件基本相同,作为条件 + +* Or(string, …interface{}) +和Where函数中的条件基本相同,作为条件 + +* Sql(string, …interface{}) +执行指定的Sql语句,并把结果映射到结构体 + +* Asc(…string) +指定字段名正序排序 + +* Desc(…string) +指定字段名逆序排序 + +* OrderBy(string) +按照指定的顺序进行排序 + +* In(string, …interface{}) +某字段在一些值中,这里需要注意必须是[]interface{}才可以展开,由于Go语言的限制,[]int64等均不可以展开。 + +* Cols(…string) +只查询或更新某些指定的字段,默认是查询所有映射的字段或者根据Update的第一个参数来判断更新的字段。例如: +```Go +engine.Cols("age", "name").Find(&users) +// SELECT age, name FROM user +engine.Cols("age", "name").Update(&user) +// UPDATE user SET age=? AND name=? +``` + +* AllCols() +查询或更新所有字段。 + +* MustCols(…string) +某些字段必须更新。 + +* Omit(...string) +和cols相反,此函数指定排除某些指定的字段。注意:此方法和Cols方法不可同时使用 +```Go +engine.Cols("age").Update(&user) +// UPDATE user SET name = ? AND department = ? +``` + +* Distinct(…string) +按照参数中指定的字段归类结果 +```Go +engine.Distinct("age", "department").Find(&users) +// SELECT DISTINCT age, department FROM user +``` +注意:当开启了缓存时,此方法的调用将在当前查询中禁用缓存。因为缓存系统当前依赖Id,而此时无法获得Id + +* Table(nameOrStructPtr interface{}) +传入表名称或者结构体指针,如果传入的是结构体指针,则按照IMapper的规则提取出表名 + +* Limit(int, …int) +限制获取的数目,第一个参数为条数,第二个参数为可选,表示开始位置 + +* Top(int) +相当于Limit(int, 0) + +* Join(string,string,string) +第一个参数为连接类型,当前支持INNER, LEFT OUTER, CROSS中的一个值,第二个参数为表名,第三个参数为连接条件 + +* GroupBy(string) +Groupby的参数字符串 + +* Having(string) +Having的参数字符串 + + +### 5.2.临时开关方法 + +* NoAutoTime() +如果此方法执行,则此次生成的语句中Created和Updated字段将不自动赋值为当前时间 + +* NoCache() +如果此方法执行,则此次生成的语句则在非缓存模式下执行 + +* UseBool(...string) +当从一个struct来生成查询条件或更新字段时,xorm会判断struct的field是否为0,"",nil,如果为以上则不当做查询条件或者更新内容。因为bool类型只有true和false两种值,因此默认所有bool类型不会作为查询条件或者更新字段。如果可以使用此方法,如果默认不传参数,则所有的bool字段都将会被使用,如果参数不为空,则参数中指定的为字段名,则这些字段对应的bool值将被使用。 + +* NoCascade() +是否自动关联查询field中的数据,如果struct的field也是一个struct并且映射为某个Id,则可以在查询时自动调用Get方法查询出对应的数据。 + + +### 5.3.Get方法 + +查询单条数据使用`Get`方法,在调用Get方法时需要传入一个对应结构体的指针,同时结构体中的非空field自动成为查询的条件和前面的方法条件组合在一起查询。 + +如: + +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 +user := &User{Name:"xlw"} +has, err := engine.Get(user) +``` + +返回的结果为两个参数,一个`has`为该条记录是否存在,第二个参数`err`为是否有错误。不管err是否为nil,has都有可能为true或者false。 + + +### 5.4.Find方法 + +查询多条数据使用`Find`方法,Find方法的第一个参数为`slice`的指针或`Map`指针,即为查询后返回的结果,第二个参数可选,为查询的条件struct的指针。 + +1) 传入Slice用于返回数据 + +```Go +everyone := make([]Userinfo, 0) +err := engine.Find(&everyone) + +pEveryOne := make([]*Userinfo, 0) +err := engine.Find(&pEveryOne) +``` + +2) 传入Map用户返回数据,map必须为`map[int64]Userinfo`的形式,map的key为id,因此对于复合主键无法使用这种方式。 + +```Go +users := make(map[int64]Userinfo) +err := engine.Find(&users) + +pUsers := make(map[int64]*Userinfo) +err := engine.Find(&pUsers) +``` + +3) 也可以加入各种条件 + +```Go +users := make([]Userinfo, 0) +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) + //do somthing use i and user +}) +``` + + +### 5.6.Count方法 + +统计数据使用`Count`方法,Count方法的参数为struct的指针并且成为查询条件。 +```Go +user := new(User) +total, err := engine.Where("id >?", 1).Count(user) +``` + + +### 5.7.Rows方法 + +Rows方法和Iterate方法类似,提供逐条执行查询到的记录的方法,不过Rows更加灵活好用。 +```Go +user := new(User) +rows, err := engine.Where("id >?", 1).Rows(user) +if err != nil { +} +defer rows.Close() +for rows.Next() { + err = rows.Scan(user) + //... +} +``` + + +## 6.更新数据 + +更新数据使用`Update`方法,Update方法的第一个参数为需要更新的内容,可以为一个结构体指针或者一个Map[string]interface{}类型。当传入的为结构体指针时,只有非空和0的field才会被作为更新的字段。当传入的为Map类型时,key为数据库Column的名字,value为要更新的内容。 + +```Go +user := new(User) +user.Name = "myname" +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}) +``` + + +### 6.1.乐观锁 + +要使用乐观锁,需要使用version标记 +type User struct { + Id int64 + Name string + Version int `xorm:"version"` +} + +在Insert时,version标记的字段将会被设置为1,在Update时,Update的内容必须包含version原来的值。 + +```Go +var user User +engine.Id(1).Get(&user) +// SELECT * FROM user WHERE id = ? +engine.Id(1).Update(&user) +// UPDATE user SET ..., version = version + 1 WHERE id = ? AND version = ? +``` + + +## 7.删除数据 + +删除数据`Delete`方法,参数为struct的指针并且成为查询条件。 + +```Go +user := new(User) +affected, err := engine.Id(id).Delete(user) +``` + +`Delete`的返回值第一个参数为删除的记录数,第二个参数为错误。 + +注意:当删除时,如果user中包含有bool,float64或者float32类型,有可能会使删除失败。具体请查看 FAQ + + +## 8.执行SQL查询 + +也可以直接执行一个SQL查询,即Select命令。在Postgres中支持原始SQL语句中使用 ` 和 ? 符号。 + +```Go +sql := "select * from userinfo" +results, err := engine.Query(sql) +``` + +当调用`Query`时,第一个返回值`results`为`[]map[string][]byte`的形式。 + + +## 9.执行SQL命令 + +也可以直接执行一个SQL命令,即执行Insert, Update, Delete 等操作。此时不管数据库是何种类型,都可以使用 ` 和 ? 符号。 + +```Go +sql = "update `userinfo` set username=? where id=?" +res, err := engine.Exec(sql, "xiaolun", 1) +``` + + +## 10.事务处理 +当使用事务处理时,需要创建Session对象。在进行事物处理时,可以混用ORM方法和RAW方法,如下代码所示: + +```Go +session := engine.NewSession() +defer session.Close() +// add Begin() before any action +err := session.Begin() +user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()} +_, err = session.Insert(&user1) +if err != nil { + session.Rollback() + return +} +user2 := Userinfo{Username: "yyy"} +_, err = session.Where("id = ?", 2).Update(&user2) +if err != nil { + session.Rollback() + return +} + +_, err = session.Exec("delete from userinfo where username = ?", user2.Username) +if err != nil { + session.Rollback() + return +} + +// add Commit() after all actions +err = session.Commit() +if err != nil { + return +} +``` + +* 注意如果您使用的是mysql,数据库引擎为innodb事务才有效,myisam引擎是不支持事务的。 + + +## 11.缓存 + +xorm内置了一致性缓存支持,不过默认并没有开启。要开启缓存,需要在engine创建完后进行配置,如: +启用一个全局的内存缓存 + +```Go +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) +``` + +设置完之后,其它代码基本上就不需要改动了,缓存系统已经在后台运行。 + +当前实现了内存存储的CacheStore接口MemoryStore,如果需要采用其它设备存储,可以实现CacheStore接口。 + +不过需要特别注意不适用缓存或者需要手动编码的地方: + +1. 当使用了`Distinct`,`Having`,`GroupBy`方法将不会使用缓存 + +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)) +``` + +缓存的实现原理如下图所示: + +![cache design](https://raw.github.com/go-xorm/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{}). + + +## 13.xorm工具 +xorm工具提供了xorm命令,能够帮助做很多事情。 + +### 13.1.反转命令 +参见 [xorm工具](https://github.com/go-xorm/xorm/tree/master/xorm) + + +## 14.Examples + +请访问[https://github.com/go-xorm/xorm/tree/master/examples](https://github.com/go-xorm/xorm/tree/master/examples) + + +## 15.案例 + +* [Gowalker](http://gowalker.org),源代码 [github.com/Unknwon/gowalker](http://github.com/Unknwon/gowalker) + +* [GoDaily Go语言学习网站](http://godaily.org),源代码 [github.com/govc/godaily](http://github.com/govc/godaily) + +* [Sudochina](http://sudochina.com) 和对应的源代码[github.com/insionng/toropress](http://github.com/insionng/toropress) + +* [VeryHour](http://veryhour.com) + + +## 16.那些年我们踩过的坑 +* 怎么同时使用xorm的tag和json的tag? + +答:使用空格 + +```Go +type User struct { + Name string `json:"name" xorm:"name"` +} +``` + +* 我的struct里面包含bool类型,为什么它不能作为条件也没法用Update更新? + +答:默认bool类型因为无法判断是否为空,所以不会自动作为条件也不会作为Update的内容。可以使用UseBool函数,也可以使用Cols函数 + +```Go +engine.Cols("bool_field").Update(&Struct{BoolField:true}) +// UPDATE struct SET bool_field = true +``` + +* 我的struct里面包含float64和float32类型,为什么用他们作为查询条件总是不正确? + +答:默认float32和float64映射到数据库中为float,real,double这几种类型,这几种数据库类型数据库的实现一般都是非精确的。因此作为相等条件查询有可能不会返回正确的结果。如果一定要作为查询条件,请将数据库中的类型定义为Numeric或者Decimal。 + +```Go +type account struct { +money float64 `xorm:"Numeric"` +} +``` + +* 为什么Update时Sqlite3返回的affected和其它数据库不一样? + +答:Sqlite3默认Update时返回的是update的查询条件的记录数条数,不管记录是否真的有更新。而Mysql和Postgres默认情况下都是只返回记录中有字段改变的记录数。 + +* xorm有几种命名映射规则? + +答:目前支持SnakeMapper和SameMapper两种。SnakeMapper支持结构体和成员以驼峰式命名而数据库表和字段以下划线连接命名;SameMapper支持结构体和数据库的命名保持一致的映射。 + +* xorm支持复合主键吗? + +答:支持。在定义时,如果有多个字段标记了pk,则这些字段自动成为复合主键,顺序为在struct中出现的顺序。在使用Id方法时,可以用`Id(xorm.PK{1, 2})`的方式来用。 + + + +## 17.讨论 +请加入QQ群:280360085 进行讨论。 diff --git a/docs/QuickStartEn.md b/docs/QuickStartEn.md deleted file mode 100644 index 74baf2c0..00000000 --- a/docs/QuickStartEn.md +++ /dev/null @@ -1,693 +0,0 @@ -Quick Start -===== - -* [1.Create ORM Engine](#10) -* [2.Define a struct](#20) - * [2.1.Name mapping rule](#21) - * [2.2.Use Table or Tag to change table or column name](#22) - * [2.3.Column define](#23) -* [3. database schema operation](#30) - * [3.1.Retrieve database schema infomation](#31) - * [3.2.Table Operation](#32) - * [3.3.Create indexes and uniques](#33) - * [3.4.Sync database schema](#34) -* [4.Insert records](#40) -* [5.Query and Count records](#60) - * [5.1.Query condition methods](#61) - * [5.2.Temporory methods](#62) - * [5.3.Get](#63) - * [5.4.Find](#64) - * [5.5.Iterate](#65) - * [5.6.Count](#66) -* [6.Update records](#70) -* [6.1.Optimistic Locking](#71) -* [7.Delete records](#80) -* [8.Execute SQL command](#90) -* [9.Execute SQL query](#100) -* [10.Transaction](#110) -* [11.Cache](#120) -* [12.Xorm Tool](#130) - * [12.1.Reverse command](#131) -* [13.Examples](#140) -* [14.Cases](#150) -* [15.FAQ](#160) -* [16.Discuss](#170) - - -## 1.Create ORM Engine - -When using xorm, you can create multiple orm engines, an engine means a databse. So you can: - -```Go -import ( - _ "github.com/go-sql-driver/mysql" - "github.com/lunny/xorm" -) -engine, err := xorm.NewEngine("mysql", "root:123@/test?charset=utf8") -defer engine.Close() -``` - -or - -```Go -import ( - _ "github.com/mattn/go-sqlite3" - "github.com/lunny/xorm" - ) -engine, err = xorm.NewEngine("sqlite3", "./test.db") -defer engine.Close() -``` - -Generally, you can only create one engine. Engine supports run on go rutines. - -xorm supports four drivers now: - -* 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) - -NewEngine's parameters are the same as `sql.Open`. So you should read the drivers' document for parameters' usage. - -After engine created, you can do some settings. - -1.Logs - -* `engine.ShowSQL = true`, Show SQL statement on standard output; -* `engine.ShowDebug = true`, Show debug infomation on standard output; -* `engine.ShowError = true`, Show error infomation on standard output; -* `engine.ShowWarn = true`, Show warnning information on standard output; - -2.If want to record infomation with another method: use `engine.Logger` as `io.Writer`: - -```Go -f, err := os.Create("sql.log") - if err != nil { - println(err.Error()) - return - } -engine.Logger = f -``` - -3.Engine support connection pool. The default pool is database/sql's and also you can use custom pool. Xorm provides two connection pool `xorm.NonConnectionPool` & `xorm.SimpleConnectPool`. If you want to use yourself pool, you can use `engine.SetPool` to set it. - -* Use `engine.SetIdleConns()` to set idle connections. -* Use `engine.SetMaxConns()` to set Max connections. This methods support only Go 1.2+. - - -## 2.Define struct - -xorm map a struct to a database table, the rule is below. - - -### 2.1.name mapping rule - -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 is the default. - -```Go -engine.SetMapper(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.3.使用Table和Tag改变名称映射 - -如果所有的命名都是按照IMapper的映射来操作的,那当然是最理想的。但是如果碰到某个表名或者某个字段名跟映射规则不匹配时,我们就需要别的机制来改变。 - -通过`engine.Table()`方法可以改变struct对应的数据库表的名称,通过sturct中field对应的Tag中使用`xorm:"'table_name'"`可以使该field对应的Column名称为指定名称。这里使用两个单引号将Column名称括起来是为了防止名称冲突,因为我们在Tag中还可以对这个Column进行更多的定义。如果名称不冲突的情况,单引号也可以不使用。 - - -### 2.4.Column属性定义 -我们在field对应的Tag中对Column的一些属性进行定义,定义的方法基本和我们写SQL定义表结构类似,比如: - -``` -type User struct { - Id int64 - Name string `xorm:"varchar(25) not null unique 'usr_name'"` -} -``` - -对于不同的数据库系统,数据类型其实是有些差异的。因此xorm中对数据类型有自己的定义,基本的原则是尽量兼容各种数据库的字段类型,具体的字段对应关系可以查看[字段类型对应表](https://github.com/lunny/xorm/blob/master/docs/COLUMNTYPE.md)。 - -具体的映射规则如下,另Tag中的关键字均不区分大小写,字段名区分大小写: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
name当前field对应的字段的名称,可选,如不写,则自动根据field名字和转换规则命名
pk是否是Primary Key,当前仅支持int64类型
当前支持30多种字段类型,详情参见 [字段类型](https://github.com/lunny/xorm/blob/master/docs/COLUMNTYPE.md)字段类型
autoincr是否是自增
[not ]null是否可以为空
unique或unique(uniquename)是否是唯一,如不加括号则该字段不允许重复;如加上括号,则括号中为联合唯一索引的名字,此时如果有另外一个或多个字段和本unique的uniquename相同,则这些uniquename相同的字段组成联合唯一索引
index或index(indexname)是否是索引,如不加括号则该字段自身为索引,如加上括号,则括号中为联合索引的名字,此时如果有另外一个或多个字段和本index的indexname相同,则这些indexname相同的字段组成联合索引
extends应用于一个匿名结构体之上,表示此匿名结构体的成员也映射到数据库中
-这个Field将不进行字段映射
->这个Field将只写入到数据库而不从数据库读取
<-这个Field将只从数据库读取,而不写入到数据库
createdThis field will be filled in current time on insert
updatedThis field will be filled in current time on insert or update
versionThis field will be filled 1 on insert and autoincrement on update
default 0设置默认值,紧跟的内容如果是Varchar等需要加上单引号
- -另外有如下几条自动映射的规则: - -- 1.如果field名称为`Id`而且类型为`int64`的话,会被xorm视为主键,并且拥有自增属性。如果想用`Id`以外的名字做为主键名,可以在对应的Tag上加上`xorm:"pk"`来定义主键。 - -- 2.string类型默认映射为varchar(255),如果需要不同的定义,可以在tag中自定义 - -- 3.支持`type MyString string`等自定义的field,支持Slice, Map等field成员,这些成员默认存储为Text类型,并且默认将使用Json格式来序列化和反序列化。也支持数据库字段类型为Blob类型,如果是Blob类型,则先使用Json格式序列化再转成[]byte格式。当然[]byte或者[]uint8默认为Blob类型并且都以二进制方式存储。 - -- 4.实现了Conversion接口的类型或者结构体,将根据接口的转换方式在类型和数据库记录之间进行相互转换。 -```Go -type Conversion interface { - FromDB([]byte) error - ToDB() ([]byte, error) -} -``` - - -## 3.表结构操作 - -xorm提供了一些动态获取和修改表结构的方法。对于一般的应用,很少动态修改表结构,则只需调用Sync()同步下表结构即可。 - - -## 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)自动检测和创建索引和唯一索引,这个检测是根据一个或多个字段名,而不根据索引名称 - -调用方法如下: -```Go -err := engine.Sync(new(User)) -``` - - -## 4.插入数据 - -Inserting records use Insert method. - -* Insert one record -```Go -user := new(User) -user.Name = "myname" -affected, err := engine.Insert(user) -``` - -After inseted, `user.Id` will be filled with primary key column value. -```Go -fmt.Println(user.Id) -``` - -* Insert multiple records by Slice on one table -```Go -users := make([]User, 0) -users[0].Name = "name0" -... -affected, err := engine.Insert(&users) -``` - -* Insert multiple records by Slice of pointer on one table -```Go -users := make([]*User, 0) -users[0] = new(User) -users[0].Name = "name0" -... -affected, err := engine.Insert(&users) -``` - -* Insert one record on two table. -```Go -user := new(User) -user.Name = "myname" -question := new(Question) -question.Content = "whywhywhwy?" -affected, err := engine.Insert(user, question) -``` - -* Insert multiple records on multiple tables. -```Go -users := make([]User, 0) -users[0].Name = "name0" -... -questions := make([]Question, 0) -questions[0].Content = "whywhywhwy?" -affected, err := engine.Insert(&users, &questions) -``` - -* Insert one or multple records on multiple tables. -```Go -user := new(User) -user.Name = "myname" -... -questions := make([]Question, 0) -questions[0].Content = "whywhywhwy?" -affected, err := engine.Insert(user, &questions) -``` - -Notice: If you want to use transaction on inserting, you should use session.Begin() before calling Insert. - - -## 5.Query and count - -所有的查询条件不区分调用顺序,但必须在调用Get,Find,Count这三个函数之前调用。同时需要注意的一点是,在调用的参数中,所有的字符字段名均为映射后的数据库的字段名,而不是field的名字。 - - -### 5.1.查询条件方法 - -查询和统计主要使用`Get`, `Find`, `Count`三个方法。在进行查询时可以使用多个方法来形成查询条件,条件函数如下: - -* Id(int64) -传入一个PK字段的值,作为查询条件 - -* Where(string, …interface{}) -和Where语句中的条件基本相同,作为条件 - -* And(string, …interface{}) -和Where函数中的条件基本相同,作为条件 - -* Or(string, …interface{}) -和Where函数中的条件基本相同,作为条件 - -* Sql(string, …interface{}) -执行指定的Sql语句,并把结果映射到结构体 - -* Asc(…string) -指定字段名正序排序 - -* Desc(…string) -指定字段名逆序排序 - -* OrderBy(string) -按照指定的顺序进行排序 - -* In(string, …interface{}) -某字段在一些值中 - -* Cols(…string) -只查询或更新某些指定的字段,默认是查询所有映射的字段或者根据Update的第一个参数来判断更新的字段。例如: -```Go -engine.Cols("age", "name").Find(&users) -// SELECT age, name FROM user -engine.Cols("age", "name").Update(&user) -// UPDATE user SET age=? AND name=? -``` - -其中的参数"age", "name"也可以写成"age, name",两种写法均可 - -* Omit(...string) -和cols相反,此函数指定排除某些指定的字段。注意:此方法和Cols方法不可同时使用 -```Go -engine.Cols("age").Update(&user) -// UPDATE user SET name = ? AND department = ? -``` - -* Distinct(…string) -按照参数中指定的字段归类结果 -```Go -engine.Distinct("age", "department").Find(&users) -// SELECT DISTINCT age, department FROM user -``` -注意:当开启了缓存时,此方法的调用将在当前查询中禁用缓存。因为缓存系统当前依赖Id,而此时无法获得Id - -* Table(nameOrStructPtr interface{}) -传入表名称或者结构体指针,如果传入的是结构体指针,则按照IMapper的规则提取出表名 - -* Limit(int, …int) -限制获取的数目,第一个参数为条数,第二个参数为可选,表示开始位置 - -* Top(int) -相当于Limit(int, 0) - -* Join(string,string,string) -第一个参数为连接类型,当前支持INNER, LEFT OUTER, CROSS中的一个值,第二个参数为表名,第三个参数为连接条件 - -* GroupBy(string) -Groupby的参数字符串 - -* Having(string) -Having的参数字符串 - - -### 5.2.临时开关方法 - -* NoAutoTime() -如果此方法执行,则此次生成的语句中Created和Updated字段将不自动赋值为当前时间 - -* NoCache() -如果此方法执行,则此次生成的语句则在非缓存模式下执行 - -* UseBool(...string) -当从一个struct来生成查询条件或更新字段时,xorm会判断struct的field是否为0,"",nil,如果为以上则不当做查询条件或者更新内容。因为bool类型只有true和false两种值,因此默认所有bool类型不会作为查询条件或者更新字段。如果可以使用此方法,如果默认不传参数,则所有的bool字段都将会被使用,如果参数不为空,则参数中指定的为字段名,则这些字段对应的bool值将被使用。 - -* Cascade(bool) -是否自动关联查询field中的数据,如果struct的field也是一个struct并且映射为某个Id,则可以在查询时自动调用Get方法查询出对应的数据。 - - -### 5.3.Get one record -Fetch a single object by user - -```Go -var user = User{Id:27} -has, err := engine.Get(&user) -// or has, err := engine.Id(27).Get(&user) - -var user = User{Name:"xlw"} -has, err := engine.Get(&user) -``` - - -### 5.4.Find -Fetch multipe objects into a slice or a map, use Find: - -```Go -var everyone []Userinfo -err := engine.Find(&everyone) - -users := make(map[int64]Userinfo) -err := engine.Find(&users) -``` - -* also you can use Where, Limit - -```Go -var allusers []Userinfo -err := engine.Where("id > ?", "3").Limit(10,20).Find(&allusers) //Get id>3 limit 10 offset 20 -``` - -* or you can use a struct query - -```Go -var tenusers []Userinfo -err := engine.Limit(10).Find(&tenusers, &Userinfo{Name:"xlw"}) //Get All Name="xlw" limit 10 offset 0 -``` - -* or In function - -```Go -var tenusers []Userinfo -err := engine.In("id", 1, 3, 5).Find(&tenusers) //Get All id in (1, 3, 5) -``` - -* The default will query all columns of a table. Use Cols function if you want to select some columns - -```Go -var tenusers []Userinfo -err := engine.Cols("id", "name").Find(&tenusers) //Find only id and name -``` - - -### 5.5.Iterate records -Iterate, like find, but handle records one by one - -```Go -err := engine.Where("age > ? or name=?)", 30, "xlw").Iterate(new(Userinfo), func(i int, bean interface{})error{ - user := bean.(*Userinfo) - //do somthing use i and user -}) -``` - - -### 5.6.Count方法 - -统计数据使用`Count`方法,Count方法的参数为struct的指针并且成为查询条件。 -```Go -user := new(User) -total, err := engine.Where("id >?", 1).Count(user) -``` - - -## 6.更新数据 - -更新数据使用`Update`方法,Update方法的第一个参数为需要更新的内容,可以为一个结构体指针或者一个Map[string]interface{}类型。当传入的为结构体指针时,只有非空和0的field才会被作为更新的字段。当传入的为Map类型时,key为数据库Column的名字,value为要更新的内容。 - -```Go -user := new(User) -user.Name = "myname" -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}) -``` - - -### 6.1.乐观锁 - -要使用乐观锁,需要使用version标记 -type User struct { - Id int64 - Name string - Version int `xorm:"version"` -} - -在Insert时,version标记的字段将会被设置为1,在Update时,Update的内容必须包含version原来的值。 - -```Go -var user User -engine.Id(1).Get(&user) -// SELECT * FROM user WHERE id = ? -engine.Id(1).Update(&user) -// UPDATE user SET ..., version = version + 1 WHERE id = ? AND version = ? -``` - - - -## 7.Delete one or more records -Delete one or more records - -* delete by id - -```Go -err := engine.Id(1).Delete(&User{}) -``` - -* delete by other conditions - -```Go -err := engine.Delete(&User{Name:"xlw"}) -``` - - -## 8.Execute SQL query - -Of course, SQL execution is also provided. - -If select then use Query - -```Go -sql := "select * from userinfo" -results, err := engine.Query(sql) -``` - - -## 9.Execute SQL command -If insert, update or delete then use Exec - -```Go -sql = "update userinfo set username=? where id=?" -res, err := engine.Exec(sql, "xiaolun", 1) -``` - - -## 10.Transaction - -```Go -session := engine.NewSession() -defer session.Close() - -// add Begin() before any action -err := session.Begin() -user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()} -_, err = session.Insert(&user1) -if err != nil { - session.Rollback() - return -} -user2 := Userinfo{Username: "yyy"} -_, err = session.Where("id = ?", 2).Update(&user2) -if err != nil { - session.Rollback() - return -} - -_, err = session.Exec("delete from userinfo where username = ?", user2.Username) -if err != nil { - session.Rollback() - return -} - -// add Commit() after all actions -err = session.Commit() -if err != nil { - return -} -``` - - -## 11.缓存 - -1. Global Cache -Xorm implements cache support. Defaultly, it's disabled. If enable it, use below code. - -```Go -cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000) -engine.SetDefaultCacher(cacher) -``` - -If disable some tables' cache, then: - -```Go -engine.MapCacher(&user, nil) -``` - -2. Table's Cache -If only some tables need cache, then: - -```Go -cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000) -engine.MapCacher(&user, cacher) -``` - -Caution: - -1. When use Cols methods on cache enabled, the system still return all the columns. - -2. When using Exec method, you should clear cache: - -```Go -engine.Exec("update user set name = ? where id = ?", "xlw", 1) -engine.ClearCache(new(User)) -``` - -Cache implement theory below: - -![cache design](https://raw.github.com/lunny/xorm/master/docs/cache_design.png) - - -## 12.xorm tool -xorm工具提供了xorm命令,能够帮助做很多事情。 - -### 12.1.Reverse command -Please visit [xorm tool](https://github.com/lunny/xorm/tree/master/xorm) - - -## 13.Examples - -请访问[https://github.com/lunny/xorm/tree/master/examples](https://github.com/lunny/xorm/tree/master/examples) - - -## 14.Cases - -* [Gowalker](http://gowalker.org),source [github.com/Unknwon/gowalker](http://github.com/Unknwon/gowalker) - -* [GoDaily](http://godaily.org),source [github.com/govc/godaily](http://github.com/govc/godaily) - -* [Sudochina](http://sudochina.com) source [github.com/insionng/toropress](http://github.com/insionng/toropress) - -* [VeryHour](http://veryhour.com) - - -## 15.FAQ - -1.How the xorm tag use both with json? - - Use space. - -```Go -type User struct { - Name string `json:"name" xorm:"name"` -} -``` diff --git a/docs/cache_design.graffle b/docs/cache_design.graffle index 5b7c487b..bfd31dc9 100644 --- a/docs/cache_design.graffle +++ b/docs/cache_design.graffle @@ -1,2295 +1,2295 @@ - - - - - ActiveLayerIndex - 0 - ApplicationVersion - - com.omnigroup.OmniGrafflePro - 139.16.0.171715 - - AutoAdjust - - BackgroundGraphic - - Bounds - {{0, 0}, {771, 554.18930041152259}} - Class - SolidGraphic - ID - 2 - Style - - fill - - Color - - b - 0.989303 - g - 0.907286 - r - 0.795377 - - FillType - 2 - GradientAngle - 78 - GradientColor - - b - 1 - g - 0.854588 - r - 0.623912 - - MiddleColor - - b - 1 - g - 0.856844 - r - 0.43695 - - TrippleBlend - YES - - shadow - - Draws - NO - - stroke - - Draws - NO - - - - BaseZoom - 0 - CanvasOrigin - {0, 0} - CanvasSize - {771, 554.18930041152259} - ColumnAlign - 1 - ColumnSpacing - 36 - CreationDate - 2013-09-29 07:57:57 +0000 - Creator - Lunny Xiao - DisplayScale - 1.000 cm = 1.000 cm - FileType - flat - GraphDocumentVersion - 8 - GraphicsList - - - Bounds - {{409.89504441572683, 415.64570506990464}, {104.42639923095703, 79.447883605957031}} - Class - ShapedGraphic - FontInfo - - Color - - b - 0.8 - g - 0.8 - r - 0.8 - - Font - Verdana - Size - 18 - - ID - 30 - Shape - Rectangle - Style - - fill - - Color - - b - 0.6 - g - 0.6 - r - 0.6 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.4 - g - 0.4 - r - 0.4 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;\red204\green204\blue204;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf2 .\ -.\ -.\ -} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{276.44083898205287, 413.07252538018992}, {112.36092376708984, 79.447883605957031}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 29 - Shape - Rectangle - Style - - fill - - Color - - b - 0.776486 - g - 0.588495 - r - 0.670497 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.618021 - g - 0.412924 - r - 0.50312 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 .\ -.\ -.} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{409.89504441572689, 337.17180246145824}, {104.42639923095703, 51}} - Class - ShapedGraphic - FontInfo - - Color - - b - 0.8 - g - 0.8 - r - 0.8 - - Font - Verdana - Size - 18 - - ID - 28 - Shape - Rectangle - Style - - fill - - Color - - b - 0.6 - g - 0.6 - r - 0.6 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.4 - g - 0.4 - r - 0.4 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;\red204\green204\blue204;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf2 user-2:User\{\}} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{274.32251833907753, 322.94787397618234}, {112.36092376708984, 79.447883605957031}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 27 - Shape - Rectangle - Style - - fill - - Color - - b - 0.776486 - g - 0.588495 - r - 0.670497 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.618021 - g - 0.412924 - r - 0.50312 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 select id from tb3:[2,5]} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{406.08888702072045, 256.42244420026987}, {104.42639923095703, 51}} - Class - ShapedGraphic - FontInfo - - Color - - b - 0.8 - g - 0.8 - r - 0.8 - - Font - Verdana - Size - 18 - - ID - 25 - Shape - Rectangle - Style - - fill - - Color - - b - 0.6 - g - 0.6 - r - 0.6 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.4 - g - 0.4 - r - 0.4 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;\red204\green204\blue204;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf2 user-2:User\{\}} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{406.08888302813585, 187.47137690331695}, {104.42639923095703, 51}} - Class - ShapedGraphic - FontInfo - - Color - - b - 0.8 - g - 0.8 - r - 0.8 - - Font - Verdana - Size - 18 - - ID - 24 - Shape - Rectangle - Style - - fill - - Color - - b - 0.6 - g - 0.6 - r - 0.6 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.4 - g - 0.4 - r - 0.4 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;\red204\green204\blue204;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf2 table-1:Table\{\}} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{406.08887903555114, 118.52029512620169}, {104.42639923095703, 51}} - Class - ShapedGraphic - FontInfo - - Color - - b - 0.8 - g - 0.8 - r - 0.8 - - Font - Verdana - Size - 18 - - ID - 23 - Shape - Rectangle - Style - - fill - - Color - - b - 0.6 - g - 0.6 - r - 0.6 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.4 - g - 0.4 - r - 0.4 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;\red204\green204\blue204;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf2 user-1:User\{\}} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{556.54055354053583, 325.93280718390133}, {124.41892177446698, 51}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 22 - Shape - Rectangle - Style - - fill - - Color - - b - 0.793851 - g - 0.625208 - r - 0.562982 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.639673 - g - 0.450584 - r - 0.381079 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.511421 - g - 0.637255 - r - 0.120867 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 Del(k, v)} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{556.54055354053583, 240.81081643580876}, {124.41892177446698, 51}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 21 - Shape - Rectangle - Style - - fill - - Color - - b - 0.793851 - g - 0.625208 - r - 0.562982 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.639673 - g - 0.450584 - r - 0.381079 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.511421 - g - 0.637255 - r - 0.120867 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 Put(k, v)} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{556.54054526129187, 150.52220626433376}, {124.41893005371094, 51}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 20 - Shape - Rectangle - Style - - fill - - Color - - b - 0.793851 - g - 0.625208 - r - 0.562982 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.639673 - g - 0.450584 - r - 0.381079 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.511421 - g - 0.637255 - r - 0.120867 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 Get(k, v)} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{276.44083898205287, 214.72973288487913}, {112.36092376708984, 79.447883605957031}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 19 - Shape - Rectangle - Style - - fill - - Color - - b - 0.776486 - g - 0.588495 - r - 0.670497 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.618021 - g - 0.412924 - r - 0.50312 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 select id from tb2:[2,5]} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{274.32251963877655, 117.18245433711769}, {112.36092376708984, 68.777008056640625}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 18 - Shape - Rectangle - Style - - fill - - Color - - b - 0.776486 - g - 0.588495 - r - 0.670497 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.618021 - g - 0.412924 - r - 0.50312 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 select id from tb1:[1,2,3]} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{103.00000194954862, 30.108108889738791}, {80, 51}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 14 - Shape - Rectangle - Style - - fill - - Color - - b - 0.776486 - g - 0.588495 - r - 0.670497 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.618021 - g - 0.412924 - r - 0.50312 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset134 STHeitiSC-Light;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 SQL} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{532.04631600645348, 25.096524339927051}, {166.93052673339844, 79.447883605957031}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 13 - Shape - Rectangle - Style - - fill - - Color - - b - 0.793851 - g - 0.625208 - r - 0.562982 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.639673 - g - 0.450584 - r - 0.381079 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.511421 - g - 0.637255 - r - 0.120867 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;\f1\fnil\fcharset134 STHeitiSC-Light;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 Cache\ - -\f1 Store} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{298.9845516069733, 37.412179328792348}, {173.03089904785156, 51}} - Class - ShapedGraphic - FontInfo - - Color - - b - 0.8 - g - 0.8 - r - 0.8 - - Font - Verdana - Size - 18 - - ID - 17 - Shape - Rectangle - Style - - fill - - Color - - b - 0.6 - g - 0.6 - r - 0.6 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.4 - g - 0.4 - r - 0.4 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;\red204\green204\blue204;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf2 LRUCacher} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{99.822519035537013, 333.12161552787364}, {88, 51}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - NSKern - 0.0 - Size - 15 - - ID - 12 - Magnets - - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.806569 - g - 0.806569 - r - 0.806569 - - FillType - 2 - GradientAngle - 90 - GradientColor - - w - 0.653285 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.2 - g - 0.2 - r - 0.2 - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc - -\f0\fs30 \cf0 \expnd0\expndtw0\kerning0 -Delet\ -SQL} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{97.118322659032714, 226.09466078541047}, {88, 51}} - Class - ShapedGraphic - FontInfo - - Color - - b - 0 - g - 0 - r - 0.501961 - - Font - Verdana - NSKern - 0.0 - Size - 15 - - ID - 15 - Magnets - - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0 - g - 0.389485 - r - 1 - - FillType - 3 - GradientCenter - {-0.34285700000000002, -0.114286} - GradientColor - - b - 0 - g - 0.495748 - r - 1 - - MiddleColor - - b - 0 - g - 0.887657 - r - 1 - - MiddleFraction - 0.6269841194152832 - TrippleBlend - YES - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.2 - g - 0.2 - r - 0.2 - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;\red128\green0\blue0;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc - -\f0\fs30 \cf2 \expnd0\expndtw0\kerning0 -Update\ -SQL} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{103, 123.08108395608006}, {80, 51}} - Class - ShapedGraphic - FontInfo - - Color - - b - 0.821332 - g - 0.672602 - r - 0.928374 - - Font - Verdana - Size - 18 - - ID - 16 - Shape - Rectangle - Style - - fill - - Color - - b - 0.436973 - g - 0.155566 - r - 0.758999 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.25098 - g - 0 - r - 0.501961 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.511421 - g - 0.637255 - r - 0.120867 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset134 STHeitiSC-Light;\f1\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;\red237\green172\blue209;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf2 select -\f1 -\f0 SQL} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - GridInfo - - GuidesLocked - NO - GuidesVisible - YES - HPages - 2 - ImageCounter - 3 - KeepToScale - - Layers - - - Lock - NO - Name - 图层 1 - Print - YES - View - YES - - - LayoutInfo - - Animate - NO - AutoLayout - 2 - circoMinDist - 18 - circoSeparation - 0.0 - layoutEngine - neato - neatoLineLength - 0.92083334922790527 - neatoSeparation - 0.0 - twopiSeparation - 0.0 - - LinksVisible - NO - MagnetsVisible - NO - MasterSheets - - ModificationDate - 2013-09-29 08:24:57 +0000 - Modifier - Lunny Xiao - NotesVisible - NO - Orientation - 2 - OriginVisible - NO - OutlineStyle - Brainstorming/Clouds - PageBreaks - NO - PrintInfo - - NSBottomMargin - - float - 41 - - NSHorizonalPagination - - coded - BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG - - NSLeftMargin - - float - 18 - - NSPaperSize - - size - {595, 842} - - NSPrintReverseOrientation - - int - 0 - - NSRightMargin - - float - 18 - - NSTopMargin - - float - 18 - - - PrintOnePage - - ReadOnly - NO - RowAlign - 1 - RowSpacing - 36 - SheetTitle - 版面 1 - SmartAlignmentGuidesActive - YES - SmartDistanceGuidesActive - YES - UniqueID - 1 - UseEntirePage - - VPages - 1 - WindowInfo - - CurrentSheet - 0 - ExpandedCanvases - - FitInWindow - - Frame - {{138, 197}, {869, 617}} - ListView - - OutlineWidth - 142 - RightSidebar - - Sidebar - - SidebarWidth - 138 - VisibleRegion - {{1.0591603214876664, 1.0591603214876664}, {770.00955372153339, 553.94084813804955}} - Zoom - 0.94414412975311279 - ZoomValues - - - 版面 1 - 0.0 - 1 - - - - - + + + + + ActiveLayerIndex + 0 + ApplicationVersion + + com.omnigroup.OmniGrafflePro + 139.16.0.171715 + + AutoAdjust + + BackgroundGraphic + + Bounds + {{0, 0}, {771, 554.18930041152259}} + Class + SolidGraphic + ID + 2 + Style + + fill + + Color + + b + 0.989303 + g + 0.907286 + r + 0.795377 + + FillType + 2 + GradientAngle + 78 + GradientColor + + b + 1 + g + 0.854588 + r + 0.623912 + + MiddleColor + + b + 1 + g + 0.856844 + r + 0.43695 + + TrippleBlend + YES + + shadow + + Draws + NO + + stroke + + Draws + NO + + + + BaseZoom + 0 + CanvasOrigin + {0, 0} + CanvasSize + {771, 554.18930041152259} + ColumnAlign + 1 + ColumnSpacing + 36 + CreationDate + 2013-09-29 07:57:57 +0000 + Creator + Lunny Xiao + DisplayScale + 1.000 cm = 1.000 cm + FileType + flat + GraphDocumentVersion + 8 + GraphicsList + + + Bounds + {{409.89504441572683, 415.64570506990464}, {104.42639923095703, 79.447883605957031}} + Class + ShapedGraphic + FontInfo + + Color + + b + 0.8 + g + 0.8 + r + 0.8 + + Font + Verdana + Size + 18 + + ID + 30 + Shape + Rectangle + Style + + fill + + Color + + b + 0.6 + g + 0.6 + r + 0.6 + + FillType + 2 + GradientAngle + 90 + GradientColor + + b + 0.4 + g + 0.4 + r + 0.4 + + + shadow + + Beneath + YES + Color + + a + 0.15 + b + 0 + g + 0 + r + 0 + + Fuzziness + 0.0 + ShadowVector + {2, 2} + + stroke + + Color + + b + 0.590997 + g + 0.18677 + r + 0.567819 + + Draws + NO + Width + 2 + + + Text + + Text + {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 +\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} +{\colortbl;\red255\green255\blue255;\red204\green204\blue204;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf2 .\ +.\ +.\ +} + VerticalPad + 0 + + TextRelativeArea + {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} + + + Bounds + {{276.44083898205287, 413.07252538018992}, {112.36092376708984, 79.447883605957031}} + Class + ShapedGraphic + FontInfo + + Color + + archive + + YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy + WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O + U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 + gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO + U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO + U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE + 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl + c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob + HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o + fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB + AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl + + b + 0 + g + 0 + r + 0 + + Font + Verdana + Size + 18 + + ID + 29 + Shape + Rectangle + Style + + fill + + Color + + b + 0.776486 + g + 0.588495 + r + 0.670497 + + FillType + 2 + GradientAngle + 90 + GradientColor + + b + 0.618021 + g + 0.412924 + r + 0.50312 + + + shadow + + Beneath + YES + Color + + a + 0.15 + b + 0 + g + 0 + r + 0 + + Fuzziness + 0.0 + ShadowVector + {2, 2} + + stroke + + Color + + b + 0.590997 + g + 0.18677 + r + 0.567819 + + Draws + NO + Width + 2 + + + Text + + Text + {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 +\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf0 .\ +.\ +.} + VerticalPad + 0 + + TextRelativeArea + {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} + + + Bounds + {{409.89504441572689, 337.17180246145824}, {104.42639923095703, 51}} + Class + ShapedGraphic + FontInfo + + Color + + b + 0.8 + g + 0.8 + r + 0.8 + + Font + Verdana + Size + 18 + + ID + 28 + Shape + Rectangle + Style + + fill + + Color + + b + 0.6 + g + 0.6 + r + 0.6 + + FillType + 2 + GradientAngle + 90 + GradientColor + + b + 0.4 + g + 0.4 + r + 0.4 + + + shadow + + Beneath + YES + Color + + a + 0.15 + b + 0 + g + 0 + r + 0 + + Fuzziness + 0.0 + ShadowVector + {2, 2} + + stroke + + Color + + b + 0.590997 + g + 0.18677 + r + 0.567819 + + Draws + NO + Width + 2 + + + Text + + Text + {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 +\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} +{\colortbl;\red255\green255\blue255;\red204\green204\blue204;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf2 user-2:User\{\}} + VerticalPad + 0 + + TextRelativeArea + {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} + + + Bounds + {{274.32251833907753, 322.94787397618234}, {112.36092376708984, 79.447883605957031}} + Class + ShapedGraphic + FontInfo + + Color + + archive + + YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy + WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O + U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 + gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO + U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO + U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE + 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl + c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob + HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o + fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB + AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl + + b + 0 + g + 0 + r + 0 + + Font + Verdana + Size + 18 + + ID + 27 + Shape + Rectangle + Style + + fill + + Color + + b + 0.776486 + g + 0.588495 + r + 0.670497 + + FillType + 2 + GradientAngle + 90 + GradientColor + + b + 0.618021 + g + 0.412924 + r + 0.50312 + + + shadow + + Beneath + YES + Color + + a + 0.15 + b + 0 + g + 0 + r + 0 + + Fuzziness + 0.0 + ShadowVector + {2, 2} + + stroke + + Color + + b + 0.590997 + g + 0.18677 + r + 0.567819 + + Draws + NO + Width + 2 + + + Text + + Text + {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 +\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf0 select id from tb3:[2,5]} + VerticalPad + 0 + + TextRelativeArea + {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} + + + Bounds + {{406.08888702072045, 256.42244420026987}, {104.42639923095703, 51}} + Class + ShapedGraphic + FontInfo + + Color + + b + 0.8 + g + 0.8 + r + 0.8 + + Font + Verdana + Size + 18 + + ID + 25 + Shape + Rectangle + Style + + fill + + Color + + b + 0.6 + g + 0.6 + r + 0.6 + + FillType + 2 + GradientAngle + 90 + GradientColor + + b + 0.4 + g + 0.4 + r + 0.4 + + + shadow + + Beneath + YES + Color + + a + 0.15 + b + 0 + g + 0 + r + 0 + + Fuzziness + 0.0 + ShadowVector + {2, 2} + + stroke + + Color + + b + 0.590997 + g + 0.18677 + r + 0.567819 + + Draws + NO + Width + 2 + + + Text + + Text + {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 +\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} +{\colortbl;\red255\green255\blue255;\red204\green204\blue204;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf2 user-2:User\{\}} + VerticalPad + 0 + + TextRelativeArea + {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} + + + Bounds + {{406.08888302813585, 187.47137690331695}, {104.42639923095703, 51}} + Class + ShapedGraphic + FontInfo + + Color + + b + 0.8 + g + 0.8 + r + 0.8 + + Font + Verdana + Size + 18 + + ID + 24 + Shape + Rectangle + Style + + fill + + Color + + b + 0.6 + g + 0.6 + r + 0.6 + + FillType + 2 + GradientAngle + 90 + GradientColor + + b + 0.4 + g + 0.4 + r + 0.4 + + + shadow + + Beneath + YES + Color + + a + 0.15 + b + 0 + g + 0 + r + 0 + + Fuzziness + 0.0 + ShadowVector + {2, 2} + + stroke + + Color + + b + 0.590997 + g + 0.18677 + r + 0.567819 + + Draws + NO + Width + 2 + + + Text + + Text + {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 +\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} +{\colortbl;\red255\green255\blue255;\red204\green204\blue204;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf2 table-1:Table\{\}} + VerticalPad + 0 + + TextRelativeArea + {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} + + + Bounds + {{406.08887903555114, 118.52029512620169}, {104.42639923095703, 51}} + Class + ShapedGraphic + FontInfo + + Color + + b + 0.8 + g + 0.8 + r + 0.8 + + Font + Verdana + Size + 18 + + ID + 23 + Shape + Rectangle + Style + + fill + + Color + + b + 0.6 + g + 0.6 + r + 0.6 + + FillType + 2 + GradientAngle + 90 + GradientColor + + b + 0.4 + g + 0.4 + r + 0.4 + + + shadow + + Beneath + YES + Color + + a + 0.15 + b + 0 + g + 0 + r + 0 + + Fuzziness + 0.0 + ShadowVector + {2, 2} + + stroke + + Color + + b + 0.590997 + g + 0.18677 + r + 0.567819 + + Draws + NO + Width + 2 + + + Text + + Text + {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 +\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} +{\colortbl;\red255\green255\blue255;\red204\green204\blue204;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf2 user-1:User\{\}} + VerticalPad + 0 + + TextRelativeArea + {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} + + + Bounds + {{556.54055354053583, 325.93280718390133}, {124.41892177446698, 51}} + Class + ShapedGraphic + FontInfo + + Color + + archive + + YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy + WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O + U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 + gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO + U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO + U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE + 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl + c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob + HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o + fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB + AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl + + b + 0 + g + 0 + r + 0 + + Font + Verdana + Size + 18 + + ID + 22 + Shape + Rectangle + Style + + fill + + Color + + b + 0.793851 + g + 0.625208 + r + 0.562982 + + FillType + 2 + GradientAngle + 90 + GradientColor + + b + 0.639673 + g + 0.450584 + r + 0.381079 + + + shadow + + Beneath + YES + Color + + a + 0.15 + b + 0 + g + 0 + r + 0 + + Fuzziness + 0.0 + ShadowVector + {2, 2} + + stroke + + Color + + b + 0.511421 + g + 0.637255 + r + 0.120867 + + Draws + NO + Width + 2 + + + Text + + Text + {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 +\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf0 Del(k, v)} + VerticalPad + 0 + + TextRelativeArea + {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} + + + Bounds + {{556.54055354053583, 240.81081643580876}, {124.41892177446698, 51}} + Class + ShapedGraphic + FontInfo + + Color + + archive + + YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy + WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O + U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 + gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO + U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO + U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE + 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl + c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob + HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o + fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB + AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl + + b + 0 + g + 0 + r + 0 + + Font + Verdana + Size + 18 + + ID + 21 + Shape + Rectangle + Style + + fill + + Color + + b + 0.793851 + g + 0.625208 + r + 0.562982 + + FillType + 2 + GradientAngle + 90 + GradientColor + + b + 0.639673 + g + 0.450584 + r + 0.381079 + + + shadow + + Beneath + YES + Color + + a + 0.15 + b + 0 + g + 0 + r + 0 + + Fuzziness + 0.0 + ShadowVector + {2, 2} + + stroke + + Color + + b + 0.511421 + g + 0.637255 + r + 0.120867 + + Draws + NO + Width + 2 + + + Text + + Text + {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 +\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf0 Put(k, v)} + VerticalPad + 0 + + TextRelativeArea + {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} + + + Bounds + {{556.54054526129187, 150.52220626433376}, {124.41893005371094, 51}} + Class + ShapedGraphic + FontInfo + + Color + + archive + + YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy + WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O + U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 + gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO + U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO + U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE + 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl + c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob + HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o + fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB + AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl + + b + 0 + g + 0 + r + 0 + + Font + Verdana + Size + 18 + + ID + 20 + Shape + Rectangle + Style + + fill + + Color + + b + 0.793851 + g + 0.625208 + r + 0.562982 + + FillType + 2 + GradientAngle + 90 + GradientColor + + b + 0.639673 + g + 0.450584 + r + 0.381079 + + + shadow + + Beneath + YES + Color + + a + 0.15 + b + 0 + g + 0 + r + 0 + + Fuzziness + 0.0 + ShadowVector + {2, 2} + + stroke + + Color + + b + 0.511421 + g + 0.637255 + r + 0.120867 + + Draws + NO + Width + 2 + + + Text + + Text + {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 +\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf0 Get(k, v)} + VerticalPad + 0 + + TextRelativeArea + {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} + + + Bounds + {{276.44083898205287, 214.72973288487913}, {112.36092376708984, 79.447883605957031}} + Class + ShapedGraphic + FontInfo + + Color + + archive + + YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy + WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O + U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 + gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO + U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO + U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE + 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl + c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob + HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o + fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB + AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl + + b + 0 + g + 0 + r + 0 + + Font + Verdana + Size + 18 + + ID + 19 + Shape + Rectangle + Style + + fill + + Color + + b + 0.776486 + g + 0.588495 + r + 0.670497 + + FillType + 2 + GradientAngle + 90 + GradientColor + + b + 0.618021 + g + 0.412924 + r + 0.50312 + + + shadow + + Beneath + YES + Color + + a + 0.15 + b + 0 + g + 0 + r + 0 + + Fuzziness + 0.0 + ShadowVector + {2, 2} + + stroke + + Color + + b + 0.590997 + g + 0.18677 + r + 0.567819 + + Draws + NO + Width + 2 + + + Text + + Text + {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 +\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf0 select id from tb2:[2,5]} + VerticalPad + 0 + + TextRelativeArea + {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} + + + Bounds + {{274.32251963877655, 117.18245433711769}, {112.36092376708984, 68.777008056640625}} + Class + ShapedGraphic + FontInfo + + Color + + archive + + YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy + WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O + U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 + gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO + U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO + U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE + 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl + c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob + HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o + fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB + AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl + + b + 0 + g + 0 + r + 0 + + Font + Verdana + Size + 18 + + ID + 18 + Shape + Rectangle + Style + + fill + + Color + + b + 0.776486 + g + 0.588495 + r + 0.670497 + + FillType + 2 + GradientAngle + 90 + GradientColor + + b + 0.618021 + g + 0.412924 + r + 0.50312 + + + shadow + + Beneath + YES + Color + + a + 0.15 + b + 0 + g + 0 + r + 0 + + Fuzziness + 0.0 + ShadowVector + {2, 2} + + stroke + + Color + + b + 0.590997 + g + 0.18677 + r + 0.567819 + + Draws + NO + Width + 2 + + + Text + + Text + {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 +\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf0 select id from tb1:[1,2,3]} + VerticalPad + 0 + + TextRelativeArea + {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} + + + Bounds + {{103.00000194954862, 30.108108889738791}, {80, 51}} + Class + ShapedGraphic + FontInfo + + Color + + archive + + YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy + WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O + U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 + gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO + U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO + U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE + 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl + c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob + HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o + fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB + AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl + + b + 0 + g + 0 + r + 0 + + Font + Verdana + Size + 18 + + ID + 14 + Shape + Rectangle + Style + + fill + + Color + + b + 0.776486 + g + 0.588495 + r + 0.670497 + + FillType + 2 + GradientAngle + 90 + GradientColor + + b + 0.618021 + g + 0.412924 + r + 0.50312 + + + shadow + + Beneath + YES + Color + + a + 0.15 + b + 0 + g + 0 + r + 0 + + Fuzziness + 0.0 + ShadowVector + {2, 2} + + stroke + + Color + + b + 0.590997 + g + 0.18677 + r + 0.567819 + + Draws + NO + Width + 2 + + + Text + + Text + {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 +\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset134 STHeitiSC-Light;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf0 SQL} + VerticalPad + 0 + + TextRelativeArea + {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} + + + Bounds + {{532.04631600645348, 25.096524339927051}, {166.93052673339844, 79.447883605957031}} + Class + ShapedGraphic + FontInfo + + Color + + archive + + YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy + WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O + U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 + gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO + U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO + U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE + 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl + c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob + HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o + fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB + AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl + + b + 0 + g + 0 + r + 0 + + Font + Verdana + Size + 18 + + ID + 13 + Shape + Rectangle + Style + + fill + + Color + + b + 0.793851 + g + 0.625208 + r + 0.562982 + + FillType + 2 + GradientAngle + 90 + GradientColor + + b + 0.639673 + g + 0.450584 + r + 0.381079 + + + shadow + + Beneath + YES + Color + + a + 0.15 + b + 0 + g + 0 + r + 0 + + Fuzziness + 0.0 + ShadowVector + {2, 2} + + stroke + + Color + + b + 0.511421 + g + 0.637255 + r + 0.120867 + + Draws + NO + Width + 2 + + + Text + + Text + {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 +\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;\f1\fnil\fcharset134 STHeitiSC-Light;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf0 Cache\ + +\f1 Store} + VerticalPad + 0 + + TextRelativeArea + {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} + + + Bounds + {{298.9845516069733, 37.412179328792348}, {173.03089904785156, 51}} + Class + ShapedGraphic + FontInfo + + Color + + b + 0.8 + g + 0.8 + r + 0.8 + + Font + Verdana + Size + 18 + + ID + 17 + Shape + Rectangle + Style + + fill + + Color + + b + 0.6 + g + 0.6 + r + 0.6 + + FillType + 2 + GradientAngle + 90 + GradientColor + + b + 0.4 + g + 0.4 + r + 0.4 + + + shadow + + Beneath + YES + Color + + a + 0.15 + b + 0 + g + 0 + r + 0 + + Fuzziness + 0.0 + ShadowVector + {2, 2} + + stroke + + Color + + b + 0.590997 + g + 0.18677 + r + 0.567819 + + Draws + NO + Width + 2 + + + Text + + Text + {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 +\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} +{\colortbl;\red255\green255\blue255;\red204\green204\blue204;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf2 LRUCacher} + VerticalPad + 0 + + TextRelativeArea + {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} + + + Bounds + {{99.822519035537013, 333.12161552787364}, {88, 51}} + Class + ShapedGraphic + FontInfo + + Color + + archive + + YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy + WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O + U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 + gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO + U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO + U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE + 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl + c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob + HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o + fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB + AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl + + b + 0 + g + 0 + r + 0 + + Font + Verdana + NSKern + 0.0 + Size + 15 + + ID + 12 + Magnets + + {1, 0} + {-1, 0} + + Shape + Rectangle + Style + + fill + + Color + + b + 0.806569 + g + 0.806569 + r + 0.806569 + + FillType + 2 + GradientAngle + 90 + GradientColor + + w + 0.653285 + + + shadow + + Beneath + YES + Color + + a + 0.15 + b + 0 + g + 0 + r + 0 + + Fuzziness + 0.0 + ShadowVector + {2, 2} + + stroke + + Color + + b + 0.2 + g + 0.2 + r + 0.2 + + Draws + NO + + + Text + + Text + {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 +\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc + +\f0\fs30 \cf0 \expnd0\expndtw0\kerning0 +Delet\ +SQL} + VerticalPad + 0 + + TextRelativeArea + {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} + + + Bounds + {{97.118322659032714, 226.09466078541047}, {88, 51}} + Class + ShapedGraphic + FontInfo + + Color + + b + 0 + g + 0 + r + 0.501961 + + Font + Verdana + NSKern + 0.0 + Size + 15 + + ID + 15 + Magnets + + {1, 0} + {-1, 0} + + Shape + Rectangle + Style + + fill + + Color + + b + 0 + g + 0.389485 + r + 1 + + FillType + 3 + GradientCenter + {-0.34285700000000002, -0.114286} + GradientColor + + b + 0 + g + 0.495748 + r + 1 + + MiddleColor + + b + 0 + g + 0.887657 + r + 1 + + MiddleFraction + 0.6269841194152832 + TrippleBlend + YES + + shadow + + Beneath + YES + Color + + a + 0.15 + b + 0 + g + 0 + r + 0 + + Fuzziness + 0.0 + ShadowVector + {2, 2} + + stroke + + Color + + b + 0.2 + g + 0.2 + r + 0.2 + + Draws + NO + + + Text + + Text + {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 +\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} +{\colortbl;\red255\green255\blue255;\red128\green0\blue0;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc + +\f0\fs30 \cf2 \expnd0\expndtw0\kerning0 +Update\ +SQL} + VerticalPad + 0 + + TextRelativeArea + {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} + + + Bounds + {{103, 123.08108395608006}, {80, 51}} + Class + ShapedGraphic + FontInfo + + Color + + b + 0.821332 + g + 0.672602 + r + 0.928374 + + Font + Verdana + Size + 18 + + ID + 16 + Shape + Rectangle + Style + + fill + + Color + + b + 0.436973 + g + 0.155566 + r + 0.758999 + + FillType + 2 + GradientAngle + 90 + GradientColor + + b + 0.25098 + g + 0 + r + 0.501961 + + + shadow + + Beneath + YES + Color + + a + 0.15 + b + 0 + g + 0 + r + 0 + + Fuzziness + 0.0 + ShadowVector + {2, 2} + + stroke + + Color + + b + 0.511421 + g + 0.637255 + r + 0.120867 + + Draws + NO + Width + 2 + + + Text + + Text + {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 +\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset134 STHeitiSC-Light;\f1\fnil\fcharset0 Verdana;} +{\colortbl;\red255\green255\blue255;\red237\green172\blue209;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf2 select +\f1 +\f0 SQL} + VerticalPad + 0 + + TextRelativeArea + {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} + + + GridInfo + + GuidesLocked + NO + GuidesVisible + YES + HPages + 2 + ImageCounter + 3 + KeepToScale + + Layers + + + Lock + NO + Name + 图层 1 + Print + YES + View + YES + + + LayoutInfo + + Animate + NO + AutoLayout + 2 + circoMinDist + 18 + circoSeparation + 0.0 + layoutEngine + neato + neatoLineLength + 0.92083334922790527 + neatoSeparation + 0.0 + twopiSeparation + 0.0 + + LinksVisible + NO + MagnetsVisible + NO + MasterSheets + + ModificationDate + 2013-09-29 08:24:57 +0000 + Modifier + Lunny Xiao + NotesVisible + NO + Orientation + 2 + OriginVisible + NO + OutlineStyle + Brainstorming/Clouds + PageBreaks + NO + PrintInfo + + NSBottomMargin + + float + 41 + + NSHorizonalPagination + + coded + BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG + + NSLeftMargin + + float + 18 + + NSPaperSize + + size + {595, 842} + + NSPrintReverseOrientation + + int + 0 + + NSRightMargin + + float + 18 + + NSTopMargin + + float + 18 + + + PrintOnePage + + ReadOnly + NO + RowAlign + 1 + RowSpacing + 36 + SheetTitle + 版面 1 + SmartAlignmentGuidesActive + YES + SmartDistanceGuidesActive + YES + UniqueID + 1 + UseEntirePage + + VPages + 1 + WindowInfo + + CurrentSheet + 0 + ExpandedCanvases + + FitInWindow + + Frame + {{138, 197}, {869, 617}} + ListView + + OutlineWidth + 142 + RightSidebar + + Sidebar + + SidebarWidth + 138 + VisibleRegion + {{1.0591603214876664, 1.0591603214876664}, {770.00955372153339, 553.94084813804955}} + Zoom + 0.94414412975311279 + ZoomValues + + + 版面 1 + 0.0 + 1 + + + + + diff --git a/engine.go b/engine.go index b21e8e85..671c1b87 100644 --- a/engine.go +++ b/engine.go @@ -6,86 +6,59 @@ import ( "database/sql" "errors" "fmt" - "io" "os" "reflect" "strconv" "strings" "sync" "time" + + "github.com/go-xorm/core" ) -const ( - POSTGRES = "postgres" - SQLITE = "sqlite3" - MYSQL = "mysql" - MYMYSQL = "mymysql" - - MSSQL = "mssql" - - ORACLE_OCI = "oci8" - QL = "ql" -) - -// a dialect is a driver's wrapper -type dialect interface { - Init(DriverName, DataSourceName string) error - URI() *uri - DBType() string - SqlType(t *Column) string - SupportInsertMany() bool - QuoteStr() string - RollBackStr() string - DropTableSql(tableName string) string - AutoIncrStr() string - SupportEngine() bool - SupportCharset() bool - IndexOnTable() bool - IndexCheckSql(tableName, idxName string) (string, []interface{}) - TableCheckSql(tableName string) (string, []interface{}) - ColumnCheckSql(tableName, colName string) (string, []interface{}) - - GetColumns(tableName string) ([]string, map[string]*Column, error) - GetTables() ([]*Table, error) - GetIndexes(tableName string) (map[string]*Index, error) -} - -type PK []interface{} - // Engine is the major struct of xorm, it means a database manager. // Commonly, an application only need one engine type Engine struct { - columnMapper IMapper - tableMapper IMapper - TagIdentifier string - DriverName string - DataSourceName string - dialect dialect - Tables map[reflect.Type]*Table - mutex *sync.RWMutex - ShowSQL bool - ShowErr bool - ShowDebug bool - ShowWarn bool - Pool IConnectPool - Filters []Filter - Logger io.Writer - Cacher Cacher - UseCache bool - TimeZone string + db *core.DB + dialect core.Dialect + + ColumnMapper core.IMapper + TableMapper core.IMapper + TagIdentifier string + Tables map[reflect.Type]*core.Table + + mutex *sync.RWMutex + Cacher core.Cacher + + ShowSQL bool + ShowErr bool + ShowDebug bool + ShowWarn bool + //Pool IConnectPool + //Filters []core.Filter + Logger ILogger // io.Writer + TZLocation *time.Location } -func (engine *Engine) SetMapper(mapper IMapper) { +func (engine *Engine) DriverName() string { + return engine.dialect.DriverName() +} + +func (engine *Engine) DataSourceName() string { + return engine.dialect.DataSourceName() +} + +func (engine *Engine) SetMapper(mapper core.IMapper) { engine.SetTableMapper(mapper) engine.SetColumnMapper(mapper) } -func (engine *Engine) SetTableMapper(mapper IMapper) { - engine.tableMapper = mapper +func (engine *Engine) SetTableMapper(mapper core.IMapper) { + engine.TableMapper = mapper } -func (engine *Engine) SetColumnMapper(mapper IMapper) { - engine.columnMapper = mapper +func (engine *Engine) SetColumnMapper(mapper core.IMapper) { + engine.ColumnMapper = mapper } // If engine's database support batch insert records like @@ -104,11 +77,17 @@ func (engine *Engine) QuoteStr() string { // Use QuoteStr quote the string sql func (engine *Engine) Quote(sql string) string { + if len(sql) == 0 { + return sql + } + if string(sql[0]) == engine.dialect.QuoteStr() || sql[0] == '`' { + return sql + } return engine.dialect.QuoteStr() + sql + engine.dialect.QuoteStr() } -// A simple wrapper to dialect's SqlType method -func (engine *Engine) SqlType(c *Column) string { +// A simple wrapper to dialect's core.SqlType method +func (engine *Engine) SqlType(c *core.Column) string { return engine.dialect.SqlType(c) } @@ -117,30 +96,24 @@ func (engine *Engine) AutoIncrStr() string { return engine.dialect.AutoIncrStr() } -// Set engine's pool, the pool default is Go's standard library's connection pool. -func (engine *Engine) SetPool(pool IConnectPool) error { - engine.Pool = pool - return engine.Pool.Init(engine) +// SetMaxOpenConns is only available for go 1.2+ +func (engine *Engine) SetMaxOpenConns(conns int) { + engine.db.SetMaxOpenConns(conns) } -// SetMaxConns is only available for go 1.2+ +// @Deprecated func (engine *Engine) SetMaxConns(conns int) { - engine.Pool.SetMaxConns(conns) + engine.SetMaxOpenConns(conns) } // SetMaxIdleConns func (engine *Engine) SetMaxIdleConns(conns int) { - engine.Pool.SetMaxIdleConns(conns) + engine.db.SetMaxIdleConns(conns) } // SetDefaltCacher set the default cacher. Xorm's default not enable cacher. -func (engine *Engine) SetDefaultCacher(cacher Cacher) { - if cacher == nil { - engine.UseCache = false - } else { - engine.UseCache = true - engine.Cacher = cacher - } +func (engine *Engine) SetDefaultCacher(cacher core.Cacher) { + engine.Cacher = cacher } // If you has set default cacher, and you want temporilly stop use cache, @@ -158,15 +131,23 @@ func (engine *Engine) NoCascade() *Session { } // Set a table use a special cacher -func (engine *Engine) MapCacher(bean interface{}, cacher Cacher) { +func (engine *Engine) MapCacher(bean interface{}, cacher core.Cacher) { v := rValue(bean) engine.autoMapType(v) engine.Tables[v.Type()].Cacher = cacher } -// OpenDB provides a interface to operate database directly. -func (engine *Engine) OpenDB() (*sql.DB, error) { - return sql.Open(engine.DriverName, engine.DataSourceName) +// NewDB provides an interface to operate database directly +func (engine *Engine) NewDB() (*core.DB, error) { + return core.OpenDialect(engine.dialect) +} + +func (engine *Engine) DB() *core.DB { + return engine.db +} + +func (engine *Engine) Dialect() core.Dialect { + return engine.dialect } // New a session @@ -178,42 +159,51 @@ func (engine *Engine) NewSession() *Session { // Close the engine func (engine *Engine) Close() error { - return engine.Pool.Close(engine) + return engine.db.Close() } -// Ping tests if database is alive. +// Ping tests if database is alive func (engine *Engine) Ping() error { session := engine.NewSession() defer session.Close() - engine.LogSQL("PING DATABASE", engine.DriverName) + engine.LogInfo("PING DATABASE", engine.DriverName) return session.Ping() } // logging sql -func (engine *Engine) LogSQL(contents ...interface{}) { +func (engine *Engine) logSQL(sqlStr string, sqlArgs ...interface{}) { if engine.ShowSQL { - io.WriteString(engine.Logger, fmt.Sprintln(contents...)) + if len(sqlArgs) > 0 { + engine.LogInfo("[sql]", sqlStr, "[args]", sqlArgs) + } else { + engine.LogInfo("[sql]", sqlStr) + } } } // logging error func (engine *Engine) LogError(contents ...interface{}) { if engine.ShowErr { - io.WriteString(engine.Logger, fmt.Sprintln(contents...)) + engine.Logger.Err(fmt.Sprintln(contents...)) } } +// logging error +func (engine *Engine) LogInfo(contents ...interface{}) { + engine.Logger.Info(fmt.Sprintln(contents...)) +} + // logging debug func (engine *Engine) LogDebug(contents ...interface{}) { if engine.ShowDebug { - io.WriteString(engine.Logger, fmt.Sprintln(contents...)) + engine.Logger.Debug(fmt.Sprintln(contents...)) } } // logging warn func (engine *Engine) LogWarn(contents ...interface{}) { if engine.ShowWarn { - io.WriteString(engine.Logger, fmt.Sprintln(contents...)) + engine.Logger.Warning(fmt.Sprintln(contents...)) } } @@ -240,7 +230,7 @@ func (engine *Engine) NoAutoTime() *Session { } // Retrieve all tables, columns, indexes' informations from database. -func (engine *Engine) DBMetas() ([]*Table, error) { +func (engine *Engine) DBMetas() ([]*core.Table, error) { tables, err := engine.dialect.GetTables() if err != nil { return nil, err @@ -251,8 +241,11 @@ func (engine *Engine) DBMetas() ([]*Table, error) { if err != nil { return nil, err } - table.Columns = cols - table.ColumnsSeq = colSeq + for _, name := range colSeq { + table.AddColumn(cols[name]) + } + //table.Columns = cols + //table.ColumnsSeq = colSeq indexes, err := engine.dialect.GetIndexes(table.Name) if err != nil { @@ -262,10 +255,10 @@ func (engine *Engine) DBMetas() ([]*Table, error) { for _, index := range indexes { for _, name := range index.Cols { - if col, ok := table.Columns[name]; ok { + if col := table.GetColumn(name); col != nil { col.Indexes[index.Name] = true } else { - return nil, fmt.Errorf("Unknown col "+name+" in indexes %v", table.Columns) + return nil, fmt.Errorf("Unknown col "+name+" in indexes %v", index) } } } @@ -375,6 +368,13 @@ func (engine *Engine) In(column string, args ...interface{}) *Session { return session.In(column, args...) } +// Method Inc provides a update string like "column = column + ?" +func (engine *Engine) Incr(column string, arg ...interface{}) *Session { + session := engine.NewSession() + session.IsAutoClose = true + return session.Incr(column, arg...) +} + // Temporarily change the Get, Find, Update's table func (engine *Engine) Table(tableNameOrBean interface{}) *Session { session := engine.NewSession() @@ -437,7 +437,7 @@ func (engine *Engine) Having(conditions string) *Session { return session.Having(conditions) } -func (engine *Engine) autoMapType(v reflect.Value) *Table { +func (engine *Engine) autoMapType(v reflect.Value) *core.Table { t := v.Type() engine.mutex.RLock() table, ok := engine.Tables[t] @@ -451,34 +451,39 @@ func (engine *Engine) autoMapType(v reflect.Value) *Table { return table } -func (engine *Engine) autoMap(bean interface{}) *Table { +func (engine *Engine) autoMap(bean interface{}) *core.Table { v := rValue(bean) return engine.autoMapType(v) } -func (engine *Engine) newTable() *Table { - table := &Table{} - table.Indexes = make(map[string]*Index) - table.Columns = make(map[string]*Column) - table.ColumnsSeq = make([]string, 0) - table.Created = make(map[string]bool) - table.Cacher = engine.Cacher - return table -} +/*func (engine *Engine) mapType(t reflect.Type) *core.Table { + return mappingTable(t, engine.TableMapper, engine.ColumnMapper, engine.dialect, engine.TagIdentifier) +}*/ -func addIndex(indexName string, table *Table, col *Column, indexType int) { +/* +func mappingTable(t reflect.Type, tableMapper core.IMapper, colMapper core.IMapper, dialect core.Dialect, tagId string) *core.Table { + table := core.NewEmptyTable() + table.Name = tableMapper.Obj2Table(t.Name()) +*/ +func addIndex(indexName string, table *core.Table, col *core.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 := core.NewIndex(indexName, indexType) index.AddColumn(col.Name) table.AddIndex(index) col.Indexes[index.Name] = true } } -func (engine *Engine) mapType(v reflect.Value) *Table { +func (engine *Engine) newTable() *core.Table { + table := core.NewEmptyTable() + table.Cacher = engine.Cacher + return table +} + +func (engine *Engine) mapType(v reflect.Value) *core.Table { t := v.Type() table := engine.newTable() method := v.MethodByName("TableName") @@ -496,7 +501,7 @@ func (engine *Engine) mapType(v reflect.Value) *Table { } if table.Name == "" { - table.Name = engine.tableMapper.Obj2Table(t.Name()) + table.Name = engine.TableMapper.Obj2Table(t.Name()) } table.Type = t @@ -506,13 +511,13 @@ func (engine *Engine) mapType(v reflect.Value) *Table { for i := 0; i < t.NumField(); i++ { tag := t.Field(i).Tag ormTagStr := tag.Get(engine.TagIdentifier) - var col *Column + var col *core.Column fieldValue := v.Field(i) fieldType := fieldValue.Type() if ormTagStr != "" { - col = &Column{FieldName: t.Field(i).Name, Nullable: true, IsPrimaryKey: false, - IsAutoIncrement: false, MapType: TWOSIDES, Indexes: make(map[string]bool)} + col = &core.Column{FieldName: t.Field(i).Name, Nullable: true, IsPrimaryKey: false, + IsAutoIncrement: false, MapType: core.TWOSIDES, Indexes: make(map[string]bool)} tags := strings.Split(ormTagStr, " ") if len(tags) > 0 { @@ -521,14 +526,14 @@ func (engine *Engine) mapType(v reflect.Value) *Table { } if (strings.ToUpper(tags[0]) == "EXTENDS") && (fieldType.Kind() == reflect.Struct) { + + //parentTable := mappingTable(fieldType, tableMapper, colMapper, dialect, tagId) parentTable := engine.mapType(fieldValue) - for name, col := range parentTable.Columns { + for _, col := range parentTable.Columns() { col.FieldName = fmt.Sprintf("%v.%v", fieldType.Name(), col.FieldName) - table.Columns[strings.ToLower(name)] = col - table.ColumnsSeq = append(table.ColumnsSeq, name) + table.AddColumn(col) } - table.PrimaryKeys = parentTable.PrimaryKeys continue } @@ -539,9 +544,9 @@ func (engine *Engine) mapType(v reflect.Value) *Table { k := strings.ToUpper(key) switch { case k == "<-": - col.MapType = ONLYFROMDB + col.MapType = core.ONLYFROMDB case k == "->": - col.MapType = ONLYTODB + col.MapType = core.ONLYTODB case k == "PK": col.IsPrimaryKey = true col.Nullable = false @@ -570,12 +575,12 @@ func (engine *Engine) mapType(v reflect.Value) *Table { col.IsUpdated = true case strings.HasPrefix(k, "INDEX(") && strings.HasSuffix(k, ")"): indexName := k[len("INDEX")+1 : len(k)-1] - indexNames[indexName] = IndexType + indexNames[indexName] = core.IndexType case k == "INDEX": isIndex = true case strings.HasPrefix(k, "UNIQUE(") && strings.HasSuffix(k, ")"): indexName := k[len("UNIQUE")+1 : len(k)-1] - indexNames[indexName] = UniqueType + indexNames[indexName] = core.UniqueType case k == "UNIQUE": isUnique = true case k == "NOTNULL": @@ -588,11 +593,12 @@ func (engine *Engine) mapType(v reflect.Value) *Table { } } else if strings.Contains(k, "(") && strings.HasSuffix(k, ")") { fs := strings.Split(k, "(") - if _, ok := sqlTypes[fs[0]]; !ok { + + if _, ok := core.SqlTypes[fs[0]]; !ok { preKey = k continue } - col.SQLType = SQLType{fs[0], 0, 0} + col.SQLType = core.SQLType{fs[0], 0, 0} fs2 := strings.Split(fs[1][0:len(fs[1])-1], ",") if len(fs2) == 2 { col.Length, err = strconv.Atoi(fs2[0]) @@ -610,18 +616,18 @@ func (engine *Engine) mapType(v reflect.Value) *Table { } } } else { - if _, ok := sqlTypes[k]; ok { - col.SQLType = SQLType{k, 0, 0} - } else if preKey != "DEFAULT" { + if _, ok := core.SqlTypes[k]; ok { + col.SQLType = core.SQLType{k, 0, 0} + } else if key != col.Default { col.Name = key } } - engine.SqlType(col) + engine.dialect.SqlType(col) } preKey = k } if col.SQLType.Name == "" { - col.SQLType = Type2SQLType(fieldType) + col.SQLType = core.Type2SQLType(fieldType) } if col.Length == 0 { col.Length = col.SQLType.DefaultLength @@ -629,14 +635,15 @@ func (engine *Engine) mapType(v reflect.Value) *Table { if col.Length2 == 0 { col.Length2 = col.SQLType.DefaultLength2 } + //fmt.Println("======", col) if col.Name == "" { - col.Name = engine.columnMapper.Obj2Table(t.Field(i).Name) + col.Name = engine.ColumnMapper.Obj2Table(t.Field(i).Name) } if isUnique { - indexNames[col.Name] = UniqueType + indexNames[col.Name] = core.UniqueType } else if isIndex { - indexNames[col.Name] = IndexType + indexNames[col.Name] = core.IndexType } for indexName, indexType := range indexNames { @@ -644,25 +651,10 @@ func (engine *Engine) mapType(v reflect.Value) *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, - } + sqlType := core.Type2SQLType(fieldType) + col = core.NewColumn(engine.ColumnMapper.Obj2Table(t.Field(i).Name), + t.Field(i).Name, sqlType, sqlType.DefaultLength, + sqlType.DefaultLength2, true) } if col.IsAutoIncrement { col.Nullable = false @@ -676,7 +668,7 @@ func (engine *Engine) mapType(v reflect.Value) *Table { } if idFieldColName != "" && len(table.PrimaryKeys) == 0 { - col := table.Columns[strings.ToLower(idFieldColName)] + col := table.GetColumn(idFieldColName) col.IsPrimaryKey = true col.IsAutoIncrement = true col.Nullable = false @@ -725,6 +717,24 @@ func (engine *Engine) IsTableExist(bean interface{}) (bool, error) { return has, err } +func (engine *Engine) IdOf(bean interface{}) core.PK { + table := engine.autoMap(bean) + v := reflect.Indirect(reflect.ValueOf(bean)) + pk := make([]interface{}, len(table.PrimaryKeys)) + for i, col := range table.PKColumns() { + pkField := v.FieldByName(col.FieldName) + switch pkField.Kind() { + case reflect.String: + pk[i] = pkField.String() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + pk[i] = pkField.Int() + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + pk[i] = pkField.Uint() + } + } + return core.PK(pk) +} + // create indexes func (engine *Engine) CreateIndexes(bean interface{}) error { session := engine.NewSession() @@ -739,16 +749,31 @@ func (engine *Engine) CreateUniques(bean interface{}) error { return session.CreateUniques(bean) } +func (engine *Engine) getCacher2(table *core.Table) core.Cacher { + return table.Cacher +} + +func (engine *Engine) getCacher(v reflect.Value) core.Cacher { + if table := engine.autoMapType(v); table != nil { + return table.Cacher + } + return engine.Cacher +} + // If enabled cache, clear the cache bean -func (engine *Engine) ClearCacheBean(bean interface{}, id int64) error { +func (engine *Engine) ClearCacheBean(bean interface{}, id string) error { t := rType(bean) if t.Kind() != reflect.Struct { return errors.New("error params") } table := engine.autoMap(bean) - if table.Cacher != nil { - table.Cacher.ClearIds(table.Name) - table.Cacher.DelBean(table.Name, id) + cacher := table.Cacher + if cacher == nil { + cacher = engine.Cacher + } + if cacher != nil { + cacher.ClearIds(table.Name) + cacher.DelBean(table.Name, id) } return nil } @@ -761,9 +786,13 @@ func (engine *Engine) ClearCache(beans ...interface{}) error { return errors.New("error params") } table := engine.autoMap(bean) - if table.Cacher != nil { - table.Cacher.ClearIds(table.Name) - table.Cacher.ClearBeans(table.Name) + cacher := table.Cacher + if cacher == nil { + cacher = engine.Cacher + } + if cacher != nil { + cacher.ClearIds(table.Name) + cacher.ClearBeans(table.Name) } } return nil @@ -803,7 +832,7 @@ func (engine *Engine) Sync(beans ...interface{}) error { return err } } else { - for _, col := range table.Columns { + for _, col := range table.Columns() { session := engine.NewSession() session.Statement.RefTable = table defer session.Close() @@ -826,7 +855,7 @@ func (engine *Engine) Sync(beans ...interface{}) error { session := engine.NewSession() session.Statement.RefTable = table defer session.Close() - if index.Type == UniqueType { + if index.Type == core.UniqueType { //isExist, err := session.isIndexExist(table.Name, name, true) isExist, err := session.isIndexExist2(table.Name, index.Cols, true) if err != nil { @@ -841,7 +870,7 @@ func (engine *Engine) Sync(beans ...interface{}) error { return err } } - } else if index.Type == IndexType { + } else if index.Type == core.IndexType { isExist, err := session.isIndexExist2(table.Name, index.Cols, false) if err != nil { return err @@ -1074,33 +1103,8 @@ func (engine *Engine) Import(ddlPath string) ([]sql.Result, error) { return results, lastError } -func (engine *Engine) TZTime(t time.Time) (r time.Time) { - if t.Location() == nil { - return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), - t.Minute(), t.Second(), t.Nanosecond(), engine.TZLocation()) - } - - switch engine.TimeZone { - case "Local", "L": - r = t.Local() - case "UTC", "U": - fallthrough - default: - r = t.UTC() - } - return -} - -func (engine *Engine) TZLocation() (r *time.Location) { - switch engine.TimeZone { - case "Local", "L": - r = time.Local - case "UTC", "U": - fallthrough - default: - r = time.UTC - } - return +func (engine *Engine) TZTime(t time.Time) time.Time { + return t.In(engine.TZLocation) } func (engine *Engine) NowTime(sqlTypeName string) interface{} { @@ -1109,19 +1113,16 @@ func (engine *Engine) NowTime(sqlTypeName string) interface{} { } func (engine *Engine) FormatTime(sqlTypeName string, t time.Time) (v interface{}) { - fmt.Println("sqlTypeName:", sqlTypeName) switch sqlTypeName { - case Time: + case core.Time: s := engine.TZTime(t).Format("2006-01-02 15:04:05") //time.RFC3339 v = s[11:19] - case Date: + case core.Date: v = engine.TZTime(t).Format("2006-01-02") - case DateTime, TimeStamp: - l := engine.TZTime(t) - v = l.Format("2006-01-02 15:04:05") - fmt.Println("xxxx", t, l, v, engine.TimeZone) - case TimeStampz: - if engine.dialect.DBType() == MSSQL { + case core.DateTime, core.TimeStamp: + v = engine.TZTime(t).Format("2006-01-02 15:04:05") + case core.TimeStampz: + if engine.dialect.DBType() == core.MSSQL { v = engine.TZTime(t).Format("2006-01-02T15:04:05.9999999Z07:00") } else { v = engine.TZTime(t).Format(time.RFC3339Nano) diff --git a/examples/cache.go b/examples/cache.go index 9e47e77e..a86dfd2d 100644 --- a/examples/cache.go +++ b/examples/cache.go @@ -1,109 +1,110 @@ package main import ( - "fmt" - "github.com/lunny/xorm" - _ "github.com/mattn/go-sqlite3" - "os" + "fmt" + "os" + + "github.com/go-xorm/xorm" + _ "github.com/mattn/go-sqlite3" ) type User struct { - Id int64 - Name string + Id int64 + Name string } func main() { - f := "cache.db" - os.Remove(f) + f := "cache.db" + os.Remove(f) - Orm, err := xorm.NewEngine("sqlite3", f) - if err != nil { - fmt.Println(err) - return - } - Orm.ShowSQL = true - cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000) - Orm.SetDefaultCacher(cacher) + Orm, err := xorm.NewEngine("sqlite3", f) + if err != nil { + fmt.Println(err) + return + } + Orm.ShowSQL = true + cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000) + Orm.SetDefaultCacher(cacher) - err = Orm.CreateTables(&User{}) - if err != nil { - fmt.Println(err) - return - } + err = Orm.CreateTables(&User{}) + if err != nil { + fmt.Println(err) + return + } - _, err = Orm.Insert(&User{Name: "xlw"}) - if err != nil { - fmt.Println(err) - return - } + _, err = Orm.Insert(&User{Name: "xlw"}) + if err != nil { + fmt.Println(err) + return + } - users := make([]User, 0) - err = Orm.Find(&users) - if err != nil { - fmt.Println(err) - return - } + users := make([]User, 0) + err = Orm.Find(&users) + if err != nil { + fmt.Println(err) + return + } - fmt.Println("users:", users) + fmt.Println("users:", users) - users2 := make([]User, 0) + users2 := make([]User, 0) - err = Orm.Find(&users2) - if err != nil { - fmt.Println(err) - return - } + err = Orm.Find(&users2) + if err != nil { + fmt.Println(err) + return + } - fmt.Println("users2:", users2) + fmt.Println("users2:", users2) - users3 := make([]User, 0) + users3 := make([]User, 0) - err = Orm.Find(&users3) - if err != nil { - fmt.Println(err) - return - } + err = Orm.Find(&users3) + if err != nil { + fmt.Println(err) + return + } - fmt.Println("users3:", users3) + fmt.Println("users3:", users3) - user4 := new(User) - has, err := Orm.Id(1).Get(user4) - if err != nil { - fmt.Println(err) - return - } + user4 := new(User) + has, err := Orm.Id(1).Get(user4) + if err != nil { + fmt.Println(err) + return + } - fmt.Println("user4:", has, user4) + fmt.Println("user4:", has, user4) - user4.Name = "xiaolunwen" - _, err = Orm.Id(1).Update(user4) - if err != nil { - fmt.Println(err) - return - } - fmt.Println("user4:", user4) + user4.Name = "xiaolunwen" + _, err = Orm.Id(1).Update(user4) + if err != nil { + fmt.Println(err) + return + } + fmt.Println("user4:", user4) - user5 := new(User) - has, err = Orm.Id(1).Get(user5) - if err != nil { - fmt.Println(err) - return - } - fmt.Println("user5:", has, user5) + user5 := new(User) + has, err = Orm.Id(1).Get(user5) + if err != nil { + fmt.Println(err) + return + } + fmt.Println("user5:", has, user5) - _, err = Orm.Id(1).Delete(new(User)) - if err != nil { - fmt.Println(err) - return - } + _, err = Orm.Id(1).Delete(new(User)) + if err != nil { + fmt.Println(err) + return + } - for { - user6 := new(User) - has, err = Orm.Id(1).Get(user6) - if err != nil { - fmt.Println(err) - return - } - fmt.Println("user6:", has, user6) - } + for { + user6 := new(User) + has, err = Orm.Id(1).Get(user6) + if err != nil { + fmt.Println(err) + return + } + fmt.Println("user6:", has, user6) + } } diff --git a/examples/cachegoroutine.go b/examples/cachegoroutine.go index 1a5feb44..0e50f5ad 100644 --- a/examples/cachegoroutine.go +++ b/examples/cachegoroutine.go @@ -1,105 +1,107 @@ package main import ( - "fmt" - _ "github.com/go-sql-driver/mysql" - "github.com/lunny/xorm" - _ "github.com/mattn/go-sqlite3" - "os" + "fmt" + "os" + "time" + + _ "github.com/go-sql-driver/mysql" + "github.com/go-xorm/xorm" + _ "github.com/mattn/go-sqlite3" ) type User struct { - Id int64 - Name string + Id int64 + Name string } func sqliteEngine() (*xorm.Engine, error) { - os.Remove("./test.db") - return xorm.NewEngine("sqlite3", "./goroutine.db") + os.Remove("./test.db") + return xorm.NewEngine("sqlite3", "./goroutine.db") } func mysqlEngine() (*xorm.Engine, error) { - return xorm.NewEngine("mysql", "root:@/test?charset=utf8") + return xorm.NewEngine("mysql", "root:@/test?charset=utf8") } var u *User = &User{} func test(engine *xorm.Engine) { - err := engine.CreateTables(u) - if err != nil { - fmt.Println(err) - return - } + err := engine.CreateTables(u) + if err != nil { + fmt.Println(err) + return + } - size := 500 - queue := make(chan int, size) + size := 500 + queue := make(chan int, size) - for i := 0; i < size; i++ { - go func(x int) { - //x := i - err := engine.Ping() - if err != nil { - fmt.Println(err) - } else { - for j := 0; j < 10; j++ { - if x+j < 2 { - _, err = engine.Get(u) - } else if x+j < 4 { - users := make([]User, 0) - err = engine.Find(&users) - } else if x+j < 8 { - _, err = engine.Count(u) - } else if x+j < 16 { - _, err = engine.Insert(&User{Name: "xlw"}) - } else if x+j < 32 { - //_, err = engine.Id(1).Delete(u) - _, err = engine.Delete(u) - } - if err != nil { - fmt.Println(err) - queue <- x - return - } - } - fmt.Printf("%v success!\n", x) - } - queue <- x - }(i) - } + for i := 0; i < size; i++ { + go func(x int) { + //x := i + err := engine.Ping() + if err != nil { + fmt.Println(err) + } else { + for j := 0; j < 10; j++ { + if x+j < 2 { + _, err = engine.Get(u) + } else if x+j < 4 { + users := make([]User, 0) + err = engine.Find(&users) + } else if x+j < 8 { + _, err = engine.Count(u) + } else if x+j < 16 { + _, err = engine.Insert(&User{Name: "xlw"}) + } else if x+j < 32 { + //_, err = engine.Id(1).Delete(u) + _, err = engine.Delete(u) + } + if err != nil { + fmt.Println(err) + queue <- x + return + } + } + fmt.Printf("%v success!\n", x) + } + queue <- x + }(i) + } - for i := 0; i < size; i++ { - <-queue - } + for i := 0; i < size; i++ { + <-queue + } - //conns := atomic.LoadInt32(&xorm.ConnectionNum) - //fmt.Println("connection number:", conns) - fmt.Println("end") + //conns := atomic.LoadInt32(&xorm.ConnectionNum) + //fmt.Println("connection number:", conns) + fmt.Println("end") } func main() { - fmt.Println("-----start sqlite go routines-----") - engine, err := sqliteEngine() - if err != nil { - fmt.Println(err) - return - } - engine.ShowSQL = true - cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000) - engine.SetDefaultCacher(cacher) - fmt.Println(engine) - test(engine) - fmt.Println("test end") - engine.Close() + fmt.Println("-----start sqlite go routines-----") + engine, err := sqliteEngine() + if err != nil { + fmt.Println(err) + return + } + engine.ShowSQL = true + cacher := xorm.NewLRUCacher2(xorm.NewMemoryStore(), time.Hour, 1000) + engine.SetDefaultCacher(cacher) + fmt.Println(engine) + test(engine) + fmt.Println("test end") + engine.Close() - fmt.Println("-----start mysql go routines-----") - engine, err = mysqlEngine() - engine.ShowSQL = true - cacher = xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000) - engine.SetDefaultCacher(cacher) - if err != nil { - fmt.Println(err) - return - } - defer engine.Close() - test(engine) + fmt.Println("-----start mysql go routines-----") + engine, err = mysqlEngine() + engine.ShowSQL = true + cacher = xorm.NewLRUCacher2(xorm.NewMemoryStore(), time.Hour, 1000) + engine.SetDefaultCacher(cacher) + if err != nil { + fmt.Println(err) + return + } + defer engine.Close() + test(engine) } diff --git a/examples/conversion.go b/examples/conversion.go index 1d1b36a9..1a74dea8 100644 --- a/examples/conversion.go +++ b/examples/conversion.go @@ -1,76 +1,77 @@ package main import ( - "errors" - "fmt" - "github.com/lunny/xorm" - _ "github.com/mattn/go-sqlite3" - "os" + "errors" + "fmt" + "os" + + "github.com/go-xorm/xorm" + _ "github.com/mattn/go-sqlite3" ) type Status struct { - Name string - Color string + Name string + Color string } var ( - Registed Status = Status{"Registed", "white"} - Approved Status = Status{"Approved", "green"} - Removed Status = Status{"Removed", "red"} - Statuses map[string]Status = map[string]Status{ - Registed.Name: Registed, - Approved.Name: Approved, - Removed.Name: Removed, - } + Registed Status = Status{"Registed", "white"} + Approved Status = Status{"Approved", "green"} + Removed Status = Status{"Removed", "red"} + Statuses map[string]Status = map[string]Status{ + Registed.Name: Registed, + Approved.Name: Approved, + Removed.Name: Removed, + } ) func (s *Status) FromDB(bytes []byte) error { - if r, ok := Statuses[string(bytes)]; ok { - *s = r - return nil - } else { - return errors.New("no this data") - } + if r, ok := Statuses[string(bytes)]; ok { + *s = r + return nil + } else { + return errors.New("no this data") + } } func (s *Status) ToDB() ([]byte, error) { - return []byte(s.Name), nil + return []byte(s.Name), nil } type User struct { - Id int64 - Name string - Status Status `xorm:"varchar(40)"` + Id int64 + Name string + Status Status `xorm:"varchar(40)"` } func main() { - f := "conversion.db" - os.Remove(f) + f := "conversion.db" + os.Remove(f) - Orm, err := NewEngine("sqlite3", f) - if err != nil { - fmt.Println(err) - return - } - Orm.ShowSQL = true - err = Orm.CreateTables(&User{}) - if err != nil { - fmt.Println(err) - return - } + Orm, err := xorm.NewEngine("sqlite3", f) + if err != nil { + fmt.Println(err) + return + } + Orm.ShowSQL = true + err = Orm.CreateTables(&User{}) + if err != nil { + fmt.Println(err) + return + } - _, err = Orm.Insert(&User{1, "xlw", Registed}) - if err != nil { - fmt.Println(err) - return - } + _, err = Orm.Insert(&User{1, "xlw", Registed}) + if err != nil { + fmt.Println(err) + return + } - users := make([]User, 0) - err = Orm.Find(&users) - if err != nil { - fmt.Println(err) - return - } + users := make([]User, 0) + err = Orm.Find(&users) + if err != nil { + fmt.Println(err) + return + } - fmt.Println(users) + fmt.Println(users) } diff --git a/examples/derive.go b/examples/derive.go index 72a9b41a..2a340060 100644 --- a/examples/derive.go +++ b/examples/derive.go @@ -1,66 +1,67 @@ package main import ( - "fmt" - "github.com/lunny/xorm" - _ "github.com/mattn/go-sqlite3" - "os" + "fmt" + "os" + + "github.com/go-xorm/xorm" + _ "github.com/mattn/go-sqlite3" ) type User struct { - Id int64 - Name string + Id int64 + Name string } type LoginInfo struct { - Id int64 - IP string - UserId int64 + Id int64 + IP string + UserId int64 } type LoginInfo1 struct { - LoginInfo `xorm:"extends"` - UserName string + LoginInfo `xorm:"extends"` + UserName string } func main() { - f := "derive.db" - os.Remove(f) + f := "derive.db" + os.Remove(f) - Orm, err := NewEngine("sqlite3", f) - if err != nil { - fmt.Println(err) - return - } - defer Orm.Close() - Orm.ShowSQL = true - err = Orm.CreateTables(&User{}, &LoginInfo{}) - if err != nil { - fmt.Println(err) - return - } + Orm, err := xorm.NewEngine("sqlite3", f) + if err != nil { + fmt.Println(err) + return + } + defer Orm.Close() + Orm.ShowSQL = true + err = Orm.CreateTables(&User{}, &LoginInfo{}) + if err != nil { + fmt.Println(err) + return + } - _, err = Orm.Insert(&User{1, "xlw"}, &LoginInfo{1, "127.0.0.1", 1}) - if err != nil { - fmt.Println(err) - return - } + _, err = Orm.Insert(&User{1, "xlw"}, &LoginInfo{1, "127.0.0.1", 1}) + if err != nil { + fmt.Println(err) + return + } - info := LoginInfo{} - _, err = Orm.Id(1).Get(&info) - if err != nil { - fmt.Println(err) - return - } - fmt.Println(info) + info := LoginInfo{} + _, err = Orm.Id(1).Get(&info) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(info) - infos := make([]LoginInfo1, 0) - err = Orm.Sql(`select *, (select name from user where id = login_info.user_id) as user_name from + infos := make([]LoginInfo1, 0) + err = Orm.Sql(`select *, (select name from user where id = login_info.user_id) as user_name from login_info limit 10`).Find(&infos) - if err != nil { - fmt.Println(err) - return - } + if err != nil { + fmt.Println(err) + return + } - fmt.Println(infos) + fmt.Println(infos) } diff --git a/examples/goroutine.db-journal b/examples/goroutine.db-journal new file mode 100644 index 00000000..95fc238e Binary files /dev/null and b/examples/goroutine.db-journal differ diff --git a/examples/goroutine.go b/examples/goroutine.go index 9cf0a7d5..b18fe4f8 100644 --- a/examples/goroutine.go +++ b/examples/goroutine.go @@ -1,106 +1,107 @@ package main import ( - "fmt" - _ "github.com/go-sql-driver/mysql" - "github.com/lunny/xorm" - _ "github.com/mattn/go-sqlite3" - "os" - "runtime" + "fmt" + "os" + "runtime" + + _ "github.com/go-sql-driver/mysql" + "github.com/go-xorm/xorm" + _ "github.com/mattn/go-sqlite3" ) type User struct { - Id int64 - Name string + Id int64 + Name string } func sqliteEngine() (*xorm.Engine, error) { - os.Remove("./test.db") - return xorm.NewEngine("sqlite3", "./goroutine.db") + os.Remove("./test.db") + return xorm.NewEngine("sqlite3", "./goroutine.db") } func mysqlEngine() (*xorm.Engine, error) { - return xorm.NewEngine("mysql", "root:@/test?charset=utf8") + return xorm.NewEngine("mysql", "root:@/test?charset=utf8") } var u *User = &User{} func test(engine *xorm.Engine) { - err := engine.CreateTables(u) - if err != nil { - fmt.Println(err) - return - } + err := engine.CreateTables(u) + if err != nil { + fmt.Println(err) + return + } - size := 500 - queue := make(chan int, size) + size := 500 + queue := make(chan int, size) - for i := 0; i < size; i++ { - go func(x int) { - //x := i - err := engine.Test() - if err != nil { - fmt.Println(err) - } else { - err = engine.Map(u) - if err != nil { - fmt.Println("Map user failed") - } else { - for j := 0; j < 10; j++ { - if x+j < 2 { - _, err = engine.Get(u) - } else if x+j < 4 { - users := make([]User, 0) - err = engine.Find(&users) - } else if x+j < 8 { - _, err = engine.Count(u) - } else if x+j < 16 { - _, err = engine.Insert(&User{Name: "xlw"}) - } else if x+j < 32 { - _, err = engine.Id(1).Delete(u) - } - if err != nil { - fmt.Println(err) - queue <- x - return - } - } - fmt.Printf("%v success!\n", x) - } - } - queue <- x - }(i) - } + for i := 0; i < size; i++ { + go func(x int) { + //x := i + err := engine.Ping() + if err != nil { + fmt.Println(err) + } else { + /*err = engine.(u) + if err != nil { + fmt.Println("Map user failed") + } else {*/ + for j := 0; j < 10; j++ { + if x+j < 2 { + _, err = engine.Get(u) + } else if x+j < 4 { + users := make([]User, 0) + err = engine.Find(&users) + } else if x+j < 8 { + _, err = engine.Count(u) + } else if x+j < 16 { + _, err = engine.Insert(&User{Name: "xlw"}) + } else if x+j < 32 { + _, err = engine.Id(1).Delete(u) + } + if err != nil { + fmt.Println(err) + queue <- x + return + } + } + fmt.Printf("%v success!\n", x) + //} + } + queue <- x + }(i) + } - for i := 0; i < size; i++ { - <-queue - } + for i := 0; i < size; i++ { + <-queue + } - //conns := atomic.LoadInt32(&xorm.ConnectionNum) - //fmt.Println("connection number:", conns) - fmt.Println("end") + //conns := atomic.LoadInt32(&xorm.ConnectionNum) + //fmt.Println("connection number:", conns) + fmt.Println("end") } func main() { - runtime.GOMAXPROCS(2) - fmt.Println("-----start sqlite go routines-----") - engine, err := sqliteEngine() - if err != nil { - fmt.Println(err) - return - } - engine.ShowSQL = true - fmt.Println(engine) - test(engine) - fmt.Println("test end") - engine.Close() + runtime.GOMAXPROCS(1) + fmt.Println("-----start sqlite go routines-----") + engine, err := sqliteEngine() + if err != nil { + fmt.Println(err) + return + } + engine.ShowSQL = true + fmt.Println(engine) + test(engine) + fmt.Println("test end") + engine.Close() - fmt.Println("-----start mysql go routines-----") - engine, err = mysqlEngine() - if err != nil { - fmt.Println(err) - return - } - defer engine.Close() - test(engine) + fmt.Println("-----start mysql go routines-----") + engine, err = mysqlEngine() + if err != nil { + fmt.Println(err) + return + } + defer engine.Close() + test(engine) } diff --git a/examples/maxconnect.go b/examples/maxconnect.go index 86e5beb8..5f239d1e 100644 --- a/examples/maxconnect.go +++ b/examples/maxconnect.go @@ -1,106 +1,107 @@ package main import ( - "fmt" - _ "github.com/go-sql-driver/mysql" - "github.com/lunny/xorm" - xorm "github.com/lunny/xorm" - _ "github.com/mattn/go-sqlite3" - "os" - "runtime" + "fmt" + "os" + "runtime" + + _ "github.com/go-sql-driver/mysql" + "github.com/go-xorm/xorm" + _ "github.com/mattn/go-sqlite3" ) type User struct { - Id int64 - Name string + Id int64 + Name string } func sqliteEngine() (*xorm.Engine, error) { - os.Remove("./test.db") - return xorm.NewEngine("sqlite3", "./goroutine.db") + os.Remove("./test.db") + return xorm.NewEngine("sqlite3", "./goroutine.db") } func mysqlEngine() (*xorm.Engine, error) { - return xorm.NewEngine("mysql", "root:@/test?charset=utf8") + return xorm.NewEngine("mysql", "root:@/test?charset=utf8") } var u *User = &User{} func test(engine *xorm.Engine) { - err := engine.CreateTables(u) - if err != nil { - fmt.Println(err) - return - } + err := engine.CreateTables(u) + if err != nil { + fmt.Println(err) + return + } - engine.ShowSQL = true - engine.Pool.SetMaxConns(5) - size := 1000 - queue := make(chan int, size) + engine.ShowSQL = true + engine.SetMaxOpenConns(5) - for i := 0; i < size; i++ { - go func(x int) { - //x := i - err := engine.Test() - if err != nil { - fmt.Println(err) - } else { - err = engine.Map(u) - if err != nil { - fmt.Println("Map user failed") - } else { - for j := 0; j < 10; j++ { - if x+j < 2 { - _, err = engine.Get(u) - } else if x+j < 4 { - users := make([]User, 0) - err = engine.Find(&users) - } else if x+j < 8 { - _, err = engine.Count(u) - } else if x+j < 16 { - _, err = engine.Insert(&User{Name: "xlw"}) - } else if x+j < 32 { - _, err = engine.Id(1).Delete(u) - } - if err != nil { - fmt.Println(err) - queue <- x - return - } - } - fmt.Printf("%v success!\n", x) - } - } - queue <- x - }(i) - } + size := 1000 + queue := make(chan int, size) - for i := 0; i < size; i++ { - <-queue - } + for i := 0; i < size; i++ { + go func(x int) { + //x := i + err := engine.Ping() + if err != nil { + fmt.Println(err) + } else { + /*err = engine.Map(u) + if err != nil { + fmt.Println("Map user failed") + } else {*/ + for j := 0; j < 10; j++ { + if x+j < 2 { + _, err = engine.Get(u) + } else if x+j < 4 { + users := make([]User, 0) + err = engine.Find(&users) + } else if x+j < 8 { + _, err = engine.Count(u) + } else if x+j < 16 { + _, err = engine.Insert(&User{Name: "xlw"}) + } else if x+j < 32 { + _, err = engine.Id(1).Delete(u) + } + if err != nil { + fmt.Println(err) + queue <- x + return + } + } + fmt.Printf("%v success!\n", x) + //} + } + queue <- x + }(i) + } - fmt.Println("end") + for i := 0; i < size; i++ { + <-queue + } + + fmt.Println("end") } func main() { - runtime.GOMAXPROCS(2) - fmt.Println("create engine") - engine, err := sqliteEngine() - if err != nil { - fmt.Println(err) - return - } - engine.ShowSQL = true - fmt.Println(engine) - test(engine) - fmt.Println("------------------------") - engine.Close() + runtime.GOMAXPROCS(2) + fmt.Println("create engine") + engine, err := sqliteEngine() + if err != nil { + fmt.Println(err) + return + } + engine.ShowSQL = true + fmt.Println(engine) + test(engine) + fmt.Println("------------------------") + engine.Close() - engine, err = mysqlEngine() - if err != nil { - fmt.Println(err) - return - } - defer engine.Close() - test(engine) + engine, err = mysqlEngine() + if err != nil { + fmt.Println(err) + return + } + defer engine.Close() + test(engine) } diff --git a/examples/pool.go b/examples/pool.go deleted file mode 100644 index 16fe0d08..00000000 --- a/examples/pool.go +++ /dev/null @@ -1,45 +0,0 @@ -package main - -import ( - "fmt" - "github.com/lunny/xorm" - _ "github.com/mattn/go-sqlite3" - "os" -) - -type User struct { - Id int64 - Name string -} - -func main() { - f := "pool.db" - os.Remove(f) - - Orm, err := NewEngine("sqlite3", f) - if err != nil { - fmt.Println(err) - return - } - err = Orm.SetPool(NewSimpleConnectPool()) - if err != nil { - fmt.Println(err) - return - } - - Orm.ShowSQL = true - err = Orm.CreateTables(&User{}) - if err != nil { - fmt.Println(err) - return - } - - for i := 0; i < 10; i++ { - _, err = Orm.Get(&User{}) - if err != nil { - fmt.Println(err) - break - } - - } -} diff --git a/examples/singlemapping.go b/examples/singlemapping.go index e6aff445..f2d675b8 100644 --- a/examples/singlemapping.go +++ b/examples/singlemapping.go @@ -1,54 +1,55 @@ package main import ( - "fmt" - "github.com/lunny/xorm" - _ "github.com/mattn/go-sqlite3" - "os" + "fmt" + "os" + + "github.com/go-xorm/xorm" + _ "github.com/mattn/go-sqlite3" ) type User struct { - Id int64 - Name string + Id int64 + Name string } type LoginInfo struct { - Id int64 - IP string - UserId int64 - // timestamp should be updated by database, so only allow get from db - TimeStamp string `xorm:"<-"` - // assume - Nonuse int `xorm:"->"` + Id int64 + IP string + UserId int64 + // timestamp should be updated by database, so only allow get from db + TimeStamp string `xorm:"<-"` + // assume + Nonuse int `xorm:"->"` } func main() { - f := "singleMapping.db" - os.Remove(f) + f := "singleMapping.db" + os.Remove(f) - Orm, err := NewEngine("sqlite3", f) - if err != nil { - fmt.Println(err) - return - } - Orm.ShowSQL = true - err = Orm.CreateTables(&User{}, &LoginInfo{}) - if err != nil { - fmt.Println(err) - return - } + Orm, err := xorm.NewEngine("sqlite3", f) + if err != nil { + fmt.Println(err) + return + } + Orm.ShowSQL = true + err = Orm.CreateTables(&User{}, &LoginInfo{}) + if err != nil { + fmt.Println(err) + return + } - _, err = Orm.Insert(&User{1, "xlw"}, &LoginInfo{1, "127.0.0.1", 1, "", 23}) - if err != nil { - fmt.Println(err) - return - } + _, err = Orm.Insert(&User{1, "xlw"}, &LoginInfo{1, "127.0.0.1", 1, "", 23}) + if err != nil { + fmt.Println(err) + return + } - info := LoginInfo{} - _, err = Orm.Id(1).Get(&info) - if err != nil { - fmt.Println(err) - return - } - fmt.Println(info) + info := LoginInfo{} + _, err = Orm.Id(1).Get(&info) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(info) } diff --git a/examples/sync.go b/examples/sync.go index ba41bafe..ad28ad80 100644 --- a/examples/sync.go +++ b/examples/sync.go @@ -1,92 +1,93 @@ package main import ( - "fmt" - _ "github.com/bylevel/pq" - _ "github.com/go-sql-driver/mysql" - "github.com/lunny/xorm" - _ "github.com/mattn/go-sqlite3" + "fmt" + + _ "github.com/go-sql-driver/mysql" + "github.com/go-xorm/xorm" + _ "github.com/lib/pq" + _ "github.com/mattn/go-sqlite3" ) type SyncUser2 struct { - Id int64 - Name string `xorm:"unique"` - Age int `xorm:"index"` - Title string - Address string - Genre string - Area string - Date int + Id int64 + Name string `xorm:"unique"` + Age int `xorm:"index"` + Title string + Address string + Genre string + Area string + Date int } type SyncLoginInfo2 struct { - Id int64 - IP string `xorm:"index"` - UserId int64 - AddedCol int - // timestamp should be updated by database, so only allow get from db - TimeStamp string - // assume - Nonuse int `xorm:"unique"` - Newa string `xorm:"index"` + Id int64 + IP string `xorm:"index"` + UserId int64 + AddedCol int + // timestamp should be updated by database, so only allow get from db + TimeStamp string + // assume + Nonuse int `xorm:"unique"` + Newa string `xorm:"index"` } func sync(engine *xorm.Engine) error { - return engine.Sync(&SyncLoginInfo2{}, &SyncUser2{}) + return engine.Sync(&SyncLoginInfo2{}, &SyncUser2{}) } func sqliteEngine() (*xorm.Engine, error) { - f := "sync.db" - //os.Remove(f) + f := "sync.db" + //os.Remove(f) - return xorm.NewEngine("sqlite3", f) + return xorm.NewEngine("sqlite3", f) } func mysqlEngine() (*xorm.Engine, error) { - return xorm.NewEngine("mysql", "root:@/test?charset=utf8") + return xorm.NewEngine("mysql", "root:@/test?charset=utf8") } func postgresEngine() (*xorm.Engine, error) { - return xorm.NewEngine("postgres", "dbname=xorm_test sslmode=disable") + return xorm.NewEngine("postgres", "dbname=xorm_test sslmode=disable") } type engineFunc func() (*xorm.Engine, error) func main() { - //engines := []engineFunc{sqliteEngine, mysqlEngine, postgresEngine} - //engines := []engineFunc{sqliteEngine} - //engines := []engineFunc{mysqlEngine} - engines := []engineFunc{postgresEngine} - for _, enginefunc := range engines { - Orm, err := enginefunc() - fmt.Println("--------", Orm.DriverName, "----------") - if err != nil { - fmt.Println(err) - return - } - Orm.ShowSQL = true - err = sync(Orm) - if err != nil { - fmt.Println(err) - } + //engines := []engineFunc{sqliteEngine, mysqlEngine, postgresEngine} + //engines := []engineFunc{sqliteEngine} + //engines := []engineFunc{mysqlEngine} + engines := []engineFunc{postgresEngine} + for _, enginefunc := range engines { + Orm, err := enginefunc() + fmt.Println("--------", Orm.DriverName, "----------") + if err != nil { + fmt.Println(err) + return + } + Orm.ShowSQL = true + err = sync(Orm) + if err != nil { + fmt.Println(err) + } - _, err = Orm.Where("id > 0").Delete(&SyncUser2{}) - if err != nil { - fmt.Println(err) - } + _, err = Orm.Where("id > 0").Delete(&SyncUser2{}) + if err != nil { + fmt.Println(err) + } - user := &SyncUser2{ - Name: "testsdf", - Age: 15, - Title: "newsfds", - Address: "fasfdsafdsaf", - Genre: "fsafd", - Area: "fafdsafd", - Date: 1000, - } - _, err = Orm.Insert(user) - if err != nil { - fmt.Println(err) - } - } + user := &SyncUser2{ + Name: "testsdf", + Age: 15, + Title: "newsfds", + Address: "fasfdsafdsaf", + Genre: "fsafd", + Area: "fafdsafd", + Date: 1000, + } + _, err = Orm.Insert(user) + if err != nil { + fmt.Println(err) + } + } } diff --git a/filter.go b/filter.go deleted file mode 100644 index d2b6c468..00000000 --- a/filter.go +++ /dev/null @@ -1,49 +0,0 @@ -package xorm - -import ( - "fmt" - "strings" -) - -// Filter is an interface to filter SQL -type Filter interface { - Do(sql string, session *Session) string -} - -// PgSeqFilter filter SQL replace ?, ? ... to $1, $2 ... -type PgSeqFilter struct { -} - -func (s *PgSeqFilter) Do(sql string, session *Session) string { - segs := strings.Split(sql, "?") - size := len(segs) - res := "" - for i, c := range segs { - if i < size-1 { - res += c + fmt.Sprintf("$%v", i+1) - } - } - res += segs[size-1] - return res -} - -// QuoteFilter filter SQL replace ` to database's own quote character -type QuoteFilter struct { -} - -func (s *QuoteFilter) Do(sql string, session *Session) string { - return strings.Replace(sql, "`", session.Engine.QuoteStr(), -1) -} - -// IdFilter filter SQL replace (id) to primary key column name -type IdFilter struct { -} - -func (i *IdFilter) Do(sql string, session *Session) string { - if session.Statement.RefTable != nil && len(session.Statement.RefTable.PrimaryKeys) == 1 { - sql = strings.Replace(sql, "`(id)`", session.Engine.Quote(session.Statement.RefTable.PrimaryKeys[0]), -1) - sql = strings.Replace(sql, session.Engine.Quote("(id)"), session.Engine.Quote(session.Statement.RefTable.PrimaryKeys[0]), -1) - return strings.Replace(sql, "(id)", session.Engine.Quote(session.Statement.RefTable.PrimaryKeys[0]), -1) - } - return sql -} diff --git a/goracle_driver.go b/goracle_driver.go new file mode 100644 index 00000000..f24fe339 --- /dev/null +++ b/goracle_driver.go @@ -0,0 +1,38 @@ +package xorm + +import ( + "errors" + "regexp" + + "github.com/go-xorm/core" +) + +// func init() { +// core.RegisterDriver("goracle", &goracleDriver{}) +// } + +type goracleDriver struct { +} + +func (cfg *goracleDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { + db := &core.Uri{DbType: core.ORACLE} + dsnPattern := regexp.MustCompile( + `^(?:(?P.*?)(?::(?P.*))?@)?` + // [user[:password]@] + `(?:(?P[^\(]*)(?:\((?P[^\)]*)\))?)?` + // [net[(addr)]] + `\/(?P.*?)` + // /dbname + `(?:\?(?P[^\?]*))?$`) // [?param1=value1¶mN=valueN] + matches := dsnPattern.FindStringSubmatch(dataSourceName) + //tlsConfigRegister := make(map[string]*tls.Config) + names := dsnPattern.SubexpNames() + + for i, match := range matches { + switch names[i] { + case "dbname": + db.DbName = match + } + } + if db.DbName == "" { + return nil, errors.New("dbname is empty") + } + return db, nil +} diff --git a/helpers.go b/helpers.go index 25b6ddc8..fcf3c21e 100644 --- a/helpers.go +++ b/helpers.go @@ -1,12 +1,13 @@ package xorm import ( - "database/sql" "fmt" "reflect" "strconv" "strings" "time" + + "github.com/go-xorm/core" ) func indexNoCase(s, sep string) int { @@ -99,7 +100,7 @@ func value2Bytes(rawValue *reflect.Value) (data []byte, err error) { } //时间类型 case reflect.Struct: - if aa == reflect.TypeOf(c_TIME_DEFAULT) { + if aa == core.TimeType { str = rawValue.Interface().(time.Time).Format(time.RFC3339Nano) data = []byte(str) } else { @@ -124,7 +125,7 @@ func value2Bytes(rawValue *reflect.Value) (data []byte, err error) { return } -func rows2maps(rows *sql.Rows) (resultsSlice []map[string][]byte, err error) { +func rows2maps(rows *core.Rows) (resultsSlice []map[string][]byte, err error) { fields, err := rows.Columns() if err != nil { return nil, err diff --git a/logger.go b/logger.go new file mode 100644 index 00000000..c4c17bbb --- /dev/null +++ b/logger.go @@ -0,0 +1,48 @@ +package xorm + +import ( + "io" + "log" +) + +// logger interface, log/syslog conform with this interface +type ILogger interface { + Debug(m string) (err error) + Err(m string) (err error) + Info(m string) (err error) + Warning(m string) (err error) +} + +type SimpleLogger struct { + logger *log.Logger +} + +func NewSimpleLogger(out io.Writer) *SimpleLogger { + return &SimpleLogger{ + logger: log.New(out, "[xorm] ", log.Ldate|log.Lmicroseconds)} +} + +func NewSimpleLogger2(out io.Writer, prefix string, flag int) *SimpleLogger { + return &SimpleLogger{ + logger: log.New(out, prefix, flag)} +} + +func (s *SimpleLogger) Debug(m string) (err error) { + s.logger.Println("[debug]", m) + return +} + +func (s *SimpleLogger) Err(m string) (err error) { + s.logger.Println("[error]", m) + return +} + +func (s *SimpleLogger) Info(m string) (err error) { + s.logger.Println("[info]", m) + return +} + +func (s *SimpleLogger) Warning(m string) (err error) { + s.logger.Println("[warning]", m) + return +} diff --git a/cache.go b/lru_cacher.go similarity index 56% rename from cache.go rename to lru_cacher.go index e1ccc0d1..a81c4ccc 100644 --- a/cache.go +++ b/lru_cacher.go @@ -1,133 +1,43 @@ +//LRUCacher implements Cacher according to LRU algorithm package xorm import ( "container/list" - "errors" "fmt" - "strconv" - "strings" "sync" "time" + + "github.com/go-xorm/core" ) -const ( - // default cache expired time - CacheExpired = 60 * time.Minute - // not use now - CacheMaxMemory = 256 - // evey ten minutes to clear all expired nodes - CacheGcInterval = 10 * time.Minute - // each time when gc to removed max nodes - CacheGcMaxRemoved = 20 -) - -// CacheStore is a interface to store cache -type CacheStore interface { - Put(key, value interface{}) error - Get(key interface{}) (interface{}, error) - Del(key interface{}) error -} - -// MemoryStore implements CacheStore provide local machine -// memory store -type MemoryStore struct { - store map[interface{}]interface{} - mutex sync.RWMutex -} - -func NewMemoryStore() *MemoryStore { - return &MemoryStore{store: make(map[interface{}]interface{})} -} - -func (s *MemoryStore) Put(key, value interface{}) error { - s.mutex.Lock() - defer s.mutex.Unlock() - s.store[key] = value - return nil -} - -func (s *MemoryStore) Get(key interface{}) (interface{}, error) { - s.mutex.RLock() - defer s.mutex.RUnlock() - if v, ok := s.store[key]; ok { - return v, nil - } - - return nil, ErrNotExist -} - -func (s *MemoryStore) Del(key interface{}) error { - s.mutex.Lock() - defer s.mutex.Unlock() - delete(s.store, key) - return nil -} - -// Cacher is an interface to provide cache -type Cacher interface { - GetIds(tableName, sql string) interface{} - GetBean(tableName string, id int64) interface{} - PutIds(tableName, sql string, ids interface{}) - PutBean(tableName string, id int64, obj interface{}) - DelIds(tableName, sql string) - DelBean(tableName string, id int64) - ClearIds(tableName string) - ClearBeans(tableName string) -} - -type idNode struct { - tbName string - id int64 - lastVisit time.Time -} - -type sqlNode struct { - tbName string - sql string - lastVisit time.Time -} - -func newIdNode(tbName string, id int64) *idNode { - return &idNode{tbName, id, time.Now()} -} - -func newSqlNode(tbName, sql string) *sqlNode { - return &sqlNode{tbName, sql, time.Now()} -} - -// LRUCacher implements Cacher according to LRU algorithm type LRUCacher struct { - idList *list.List - sqlList *list.List - idIndex map[string]map[interface{}]*list.Element - sqlIndex map[string]map[interface{}]*list.Element - store CacheStore - Max int - mutex sync.Mutex - Expired time.Duration - maxSize int - GcInterval time.Duration + idList *list.List + sqlList *list.List + idIndex map[string]map[string]*list.Element + sqlIndex map[string]map[string]*list.Element + store core.CacheStore + mutex sync.Mutex + // maxSize int + MaxElementSize int + Expired time.Duration + GcInterval time.Duration } -func newLRUCacher(store CacheStore, expired time.Duration, maxSize int, max int) *LRUCacher { +func NewLRUCacher(store core.CacheStore, maxElementSize int) *LRUCacher { + return NewLRUCacher2(store, 0, maxElementSize) +} + +func NewLRUCacher2(store core.CacheStore, expired time.Duration, maxElementSize int) *LRUCacher { cacher := &LRUCacher{store: store, idList: list.New(), - sqlList: list.New(), Expired: expired, maxSize: maxSize, - GcInterval: CacheGcInterval, Max: max, - sqlIndex: make(map[string]map[interface{}]*list.Element), - idIndex: make(map[string]map[interface{}]*list.Element), + sqlList: list.New(), Expired: expired, + GcInterval: core.CacheGcInterval, MaxElementSize: maxElementSize, + sqlIndex: make(map[string]map[string]*list.Element), + idIndex: make(map[string]map[string]*list.Element), } cacher.RunGC() return cacher } -func NewLRUCacher(store CacheStore, max int) *LRUCacher { - return newLRUCacher(store, CacheExpired, CacheMaxMemory, max) -} - -func NewLRUCacher2(store CacheStore, expired time.Duration, max int) *LRUCacher { - return newLRUCacher(store, expired, 0, max) -} - //func NewLRUCacher3(store CacheStore, expired time.Duration, maxSize int) *LRUCacher { // return newLRUCacher(store, expired, maxSize, 0) //} @@ -148,7 +58,7 @@ func (m *LRUCacher) GC() { defer m.mutex.Unlock() var removedNum int for e := m.idList.Front(); e != nil; { - if removedNum <= CacheGcMaxRemoved && + if removedNum <= core.CacheGcMaxRemoved && time.Now().Sub(e.Value.(*idNode).lastVisit) > m.Expired { removedNum++ next := e.Next() @@ -164,7 +74,7 @@ func (m *LRUCacher) GC() { removedNum = 0 for e := m.sqlList.Front(); e != nil; { - if removedNum <= CacheGcMaxRemoved && + if removedNum <= core.CacheGcMaxRemoved && time.Now().Sub(e.Value.(*sqlNode).lastVisit) > m.Expired { removedNum++ next := e.Next() @@ -184,7 +94,7 @@ func (m *LRUCacher) GetIds(tableName, sql string) interface{} { m.mutex.Lock() defer m.mutex.Unlock() if _, ok := m.sqlIndex[tableName]; !ok { - m.sqlIndex[tableName] = make(map[interface{}]*list.Element) + m.sqlIndex[tableName] = make(map[string]*list.Element) } if v, err := m.store.Get(sql); err == nil { if el, ok := m.sqlIndex[tableName][sql]; !ok { @@ -209,11 +119,11 @@ func (m *LRUCacher) GetIds(tableName, sql string) interface{} { } // Get bean according tableName and id from cache -func (m *LRUCacher) GetBean(tableName string, id int64) interface{} { +func (m *LRUCacher) GetBean(tableName string, id string) interface{} { m.mutex.Lock() defer m.mutex.Unlock() if _, ok := m.idIndex[tableName]; !ok { - m.idIndex[tableName] = make(map[interface{}]*list.Element) + m.idIndex[tableName] = make(map[string]*list.Element) } tid := genId(tableName, id) if v, err := m.store.Get(tid); err == nil { @@ -248,7 +158,7 @@ func (m *LRUCacher) clearIds(tableName string) { m.store.Del(sql) } } - m.sqlIndex[tableName] = make(map[interface{}]*list.Element) + m.sqlIndex[tableName] = make(map[string]*list.Element) } func (m *LRUCacher) ClearIds(tableName string) { @@ -261,11 +171,11 @@ func (m *LRUCacher) clearBeans(tableName string) { if tis, ok := m.idIndex[tableName]; ok { for id, v := range tis { m.idList.Remove(v) - tid := genId(tableName, id.(int64)) + tid := genId(tableName, id) m.store.Del(tid) } } - m.idIndex[tableName] = make(map[interface{}]*list.Element) + m.idIndex[tableName] = make(map[string]*list.Element) } func (m *LRUCacher) ClearBeans(tableName string) { @@ -278,7 +188,7 @@ func (m *LRUCacher) PutIds(tableName, sql string, ids interface{}) { m.mutex.Lock() defer m.mutex.Unlock() if _, ok := m.sqlIndex[tableName]; !ok { - m.sqlIndex[tableName] = make(map[interface{}]*list.Element) + m.sqlIndex[tableName] = make(map[string]*list.Element) } if el, ok := m.sqlIndex[tableName][sql]; !ok { el = m.sqlList.PushBack(newSqlNode(tableName, sql)) @@ -287,14 +197,14 @@ func (m *LRUCacher) PutIds(tableName, sql string, ids interface{}) { el.Value.(*sqlNode).lastVisit = time.Now() } m.store.Put(sql, ids) - if m.sqlList.Len() > m.Max { + if m.sqlList.Len() > m.MaxElementSize { e := m.sqlList.Front() node := e.Value.(*sqlNode) m.delIds(node.tbName, node.sql) } } -func (m *LRUCacher) PutBean(tableName string, id int64, obj interface{}) { +func (m *LRUCacher) PutBean(tableName string, id string, obj interface{}) { m.mutex.Lock() defer m.mutex.Unlock() var el *list.Element @@ -308,7 +218,7 @@ func (m *LRUCacher) PutBean(tableName string, id int64, obj interface{}) { } m.store.Put(genId(tableName, id), obj) - if m.idList.Len() > m.Max { + if m.idList.Len() > m.MaxElementSize { e := m.idList.Front() node := e.Value.(*idNode) m.delBean(node.tbName, node.id) @@ -331,7 +241,7 @@ func (m *LRUCacher) DelIds(tableName, sql string) { m.delIds(tableName, sql) } -func (m *LRUCacher) delBean(tableName string, id int64) { +func (m *LRUCacher) delBean(tableName string, id string) { tid := genId(tableName, id) if el, ok := m.idIndex[tableName][id]; ok { delete(m.idIndex[tableName], id) @@ -341,55 +251,36 @@ func (m *LRUCacher) delBean(tableName string, id int64) { m.store.Del(tid) } -func (m *LRUCacher) DelBean(tableName string, id int64) { +func (m *LRUCacher) DelBean(tableName string, id string) { m.mutex.Lock() defer m.mutex.Unlock() m.delBean(tableName, id) } -func encodeIds(ids []int64) (s string) { - s = "[" - for _, id := range ids { - s += fmt.Sprintf("%v,", id) - } - s = s[:len(s)-1] + "]" - return +type idNode struct { + tbName string + id string + lastVisit time.Time } -func decodeIds(s string) []int64 { - res := make([]int64, 0) - if len(s) >= 2 { - ss := strings.Split(s[1:len(s)-1], ",") - for _, s := range ss { - i, err := strconv.ParseInt(s, 10, 64) - if err != nil { - return res - } - res = append(res, i) - } - } - return res -} - -func getCacheSql(m Cacher, tableName, sql string, args interface{}) ([]int64, error) { - bytes := m.GetIds(tableName, genSqlKey(sql, args)) - if bytes == nil { - return nil, errors.New("Not Exist") - } - objs := decodeIds(bytes.(string)) - return objs, nil -} - -func putCacheSql(m Cacher, ids []int64, tableName, sql string, args interface{}) error { - bytes := encodeIds(ids) - m.PutIds(tableName, genSqlKey(sql, args), bytes) - return nil +type sqlNode struct { + tbName string + sql string + lastVisit time.Time } func genSqlKey(sql string, args interface{}) string { return fmt.Sprintf("%v-%v", sql, args) } -func genId(prefix string, id int64) string { +func genId(prefix string, id string) string { return fmt.Sprintf("%v-%v", prefix, id) } + +func newIdNode(tbName string, id string) *idNode { + return &idNode{tbName, id, time.Now()} +} + +func newSqlNode(tbName, sql string) *sqlNode { + return &sqlNode{tbName, sql, time.Now()} +} diff --git a/mapper.go b/mapper.go deleted file mode 100644 index 358d222f..00000000 --- a/mapper.go +++ /dev/null @@ -1,176 +0,0 @@ -package xorm - -import ( - "strings" - "sync" -) - -// name translation between struct, fields names and table, column names -type IMapper interface { - Obj2Table(string) string - 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 { -} - -func (m SameMapper) Obj2Table(o string) string { - return o -} - -func (m SameMapper) Table2Obj(t string) string { - return t -} - -// SnakeMapper implements IMapper and provides name transaltion between -// struct and database table -type SnakeMapper struct { -} - -func snakeCasedName(name string) string { - newstr := make([]rune, 0) - for idx, chr := range name { - if isUpper := 'A' <= chr && chr <= 'Z'; isUpper { - if idx > 0 { - newstr = append(newstr, '_') - } - chr -= ('A' - 'a') - } - newstr = append(newstr, chr) - } - - return string(newstr) -} - -/*func pascal2Sql(s string) (d string) { - d = "" - lastIdx := 0 - for i := 0; i < len(s); i++ { - if s[i] >= 'A' && s[i] <= 'Z' { - if lastIdx < i { - d += s[lastIdx+1 : i] - } - if i != 0 { - d += "_" - } - d += string(s[i] + 32) - lastIdx = i - } - } - d += s[lastIdx+1:] - return -}*/ - -func (mapper SnakeMapper) Obj2Table(name string) string { - return snakeCasedName(name) -} - -func titleCasedName(name string) string { - newstr := make([]rune, 0) - upNextChar := true - - name = strings.ToLower(name) - - for _, chr := range name { - switch { - case upNextChar: - upNextChar = false - if 'a' <= chr && chr <= 'z' { - chr -= ('a' - 'A') - } - case chr == '_': - upNextChar = true - continue - } - - newstr = append(newstr, chr) - } - - return string(newstr) -} - -func (mapper SnakeMapper) Table2Obj(name string) string { - return titleCasedName(name) -} - -// provide prefix table name support -type PrefixMapper struct { - Mapper IMapper - Prefix string -} - -func (mapper PrefixMapper) Obj2Table(name string) string { - return mapper.Prefix + mapper.Mapper.Obj2Table(name) -} - -func (mapper PrefixMapper) Table2Obj(name string) string { - return mapper.Mapper.Table2Obj(name[len(mapper.Prefix):]) -} - -func NewPrefixMapper(mapper IMapper, prefix string) PrefixMapper { - return PrefixMapper{mapper, prefix} -} - -// provide suffix table name support -type SuffixMapper struct { - Mapper IMapper - Suffix string -} - -func (mapper SuffixMapper) Obj2Table(name string) string { - return mapper.Suffix + mapper.Mapper.Obj2Table(name) -} - -func (mapper SuffixMapper) Table2Obj(name string) string { - return mapper.Mapper.Table2Obj(name[len(mapper.Suffix):]) -} - -func NewSuffixMapper(mapper IMapper, suffix string) SuffixMapper { - return SuffixMapper{mapper, suffix} -} diff --git a/memroy_store.go b/memroy_store.go new file mode 100644 index 00000000..c5800e46 --- /dev/null +++ b/memroy_store.go @@ -0,0 +1,44 @@ +// MemoryStore implements CacheStore provide local machine +package xorm + +import ( + "sync" + + "github.com/go-xorm/core" +) + +var _ core.CacheStore = NewMemoryStore() + +// memory store +type MemoryStore struct { + store map[interface{}]interface{} + mutex sync.RWMutex +} + +func NewMemoryStore() *MemoryStore { + return &MemoryStore{store: make(map[interface{}]interface{})} +} + +func (s *MemoryStore) Put(key string, value interface{}) error { + s.mutex.Lock() + defer s.mutex.Unlock() + s.store[key] = value + return nil +} + +func (s *MemoryStore) Get(key string) (interface{}, error) { + s.mutex.RLock() + defer s.mutex.RUnlock() + if v, ok := s.store[key]; ok { + return v, nil + } + + return nil, ErrNotExist +} + +func (s *MemoryStore) Del(key string) error { + s.mutex.Lock() + defer s.mutex.Unlock() + delete(s.store, key) + return nil +} diff --git a/mssql.go b/mssql_dialect.go similarity index 53% rename from mssql.go rename to mssql_dialect.go index 54c93e71..4f5b5eb7 100644 --- a/mssql.go +++ b/mssql_dialect.go @@ -1,85 +1,63 @@ package xorm import ( - //"crypto/tls" - "database/sql" "errors" "fmt" - //"regexp" "strconv" "strings" - //"time" + + "github.com/go-xorm/core" ) +// func init() { +// RegisterDialect("mssql", &mssql{}) +// } + type mssql struct { - base - quoteFilter Filter + core.Base } -type odbcParser struct { +func (db *mssql) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error { + return db.Base.Init(d, db, uri, drivername, dataSourceName) } -func (p *odbcParser) parse(driverName, dataSourceName string) (*uri, error) { - kv := strings.Split(dataSourceName, ";") - var dbName string - - for _, c := range kv { - vv := strings.Split(strings.TrimSpace(c), "=") - if len(vv) == 2 { - switch strings.ToLower(vv[0]) { - case "database": - dbName = vv[1] - } - } - } - if dbName == "" { - return nil, errors.New("no db name provided") - } - return &uri{dbName: dbName, dbType: MSSQL}, nil -} - -func (db *mssql) Init(drivername, uri string) error { - db.quoteFilter = &QuoteFilter{} - return db.base.init(&odbcParser{}, drivername, uri) -} - -func (db *mssql) SqlType(c *Column) string { +func (db *mssql) SqlType(c *core.Column) string { var res string switch t := c.SQLType.Name; t { - case Bool: - res = TinyInt - case Serial: + case core.Bool: + res = core.TinyInt + case core.Serial: c.IsAutoIncrement = true c.IsPrimaryKey = true c.Nullable = false - res = Int - case BigSerial: + res = core.Int + case core.BigSerial: c.IsAutoIncrement = true c.IsPrimaryKey = true c.Nullable = false - res = BigInt - case Bytea, Blob, Binary, TinyBlob, MediumBlob, LongBlob: - res = VarBinary + res = core.BigInt + case core.Bytea, core.Blob, core.Binary, core.TinyBlob, core.MediumBlob, core.LongBlob: + res = core.VarBinary if c.Length == 0 { c.Length = 50 } - case TimeStamp: - res = DateTime - case TimeStampz: + case core.TimeStamp: + res = core.DateTime + case core.TimeStampz: res = "DATETIMEOFFSET" c.Length = 7 - case MediumInt: - res = Int - case MediumText, TinyText, LongText: - res = Text - case Double: - res = Real + case core.MediumInt: + res = core.Int + case core.MediumText, core.TinyText, core.LongText: + res = core.Text + case core.Double: + res = core.Real default: res = t } - if res == Int { - return Int + if res == core.Int { + return core.Int } var hasLen1 bool = (c.Length > 0) @@ -140,56 +118,48 @@ func (db *mssql) TableCheckSql(tableName string) (string, []interface{}) { return sql, args } -func (db *mssql) GetColumns(tableName string) ([]string, map[string]*Column, error) { +func (db *mssql) GetColumns(tableName string) ([]string, map[string]*core.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 where a.object_id=object_id('` + tableName + `')` - cnn, err := sql.Open(db.driverName, db.dataSourceName) + + rows, err := db.DB().Query(s, args...) if err != nil { return nil, nil, err } - defer cnn.Close() - res, err := query(cnn, s, args...) - if err != nil { - return nil, nil, err - } - cols := make(map[string]*Column) + cols := make(map[string]*core.Column) colSeq := make([]string, 0) - for _, record := range res { - col := new(Column) + for rows.Next() { + var name, ctype, precision, scale string + var maxLen int + err = rows.Scan(&name, &ctype, &maxLen, &precision, &scale) + if err != nil { + return nil, nil, err + } + + col := new(core.Column) col.Indexes = make(map[string]bool) - for name, content := range record { - switch name { - case "name": + col.Length = maxLen + col.Name = strings.Trim(name, "` ") - col.Name = strings.Trim(string(content), "` ") - case "ctype": - ct := strings.ToUpper(string(content)) - switch ct { - case "DATETIMEOFFSET": - col.SQLType = SQLType{TimeStampz, 0, 0} - case "NVARCHAR": - col.SQLType = SQLType{Varchar, 0, 0} - case "IMAGE": - col.SQLType = SQLType{VarBinary, 0, 0} - default: - if _, ok := sqlTypes[ct]; ok { - col.SQLType = SQLType{ct, 0, 0} - } else { - return nil, nil, errors.New(fmt.Sprintf("unknow colType %v for %v - %v", - ct, tableName, col.Name)) - } - } - - case "max_length": - len1, err := strconv.Atoi(strings.TrimSpace(string(content))) - if err != nil { - return nil, nil, err - } - col.Length = len1 + ct := strings.ToUpper(ctype) + switch ct { + case "DATETIMEOFFSET": + col.SQLType = core.SQLType{core.TimeStampz, 0, 0} + case "NVARCHAR": + col.SQLType = core.SQLType{core.Varchar, 0, 0} + case "IMAGE": + col.SQLType = core.SQLType{core.VarBinary, 0, 0} + default: + if _, ok := core.SqlTypes[ct]; ok { + col.SQLType = core.SQLType{ct, 0, 0} + } else { + return nil, nil, errors.New(fmt.Sprintf("unknow colType %v for %v - %v", + ct, tableName, col.Name)) } } + if col.SQLType.IsText() { if col.Default != "" { col.Default = "'" + col.Default + "'" @@ -205,34 +175,30 @@ where a.object_id=object_id('` + tableName + `')` return colSeq, cols, nil } -func (db *mssql) GetTables() ([]*Table, error) { +func (db *mssql) GetTables() ([]*core.Table, error) { args := []interface{}{} s := `select name from sysobjects where xtype ='U'` - cnn, err := sql.Open(db.driverName, db.dataSourceName) - if err != nil { - return nil, err - } - defer cnn.Close() - res, err := query(cnn, s, args...) + + rows, err := db.DB().Query(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 = strings.Trim(string(content), "` ") - } + tables := make([]*core.Table, 0) + for rows.Next() { + table := core.NewEmptyTable() + var name string + err = rows.Scan(&name) + if err != nil { + return nil, err } + table.Name = strings.Trim(name, "` ") tables = append(tables, table) } return tables, nil } -func (db *mssql) GetIndexes(tableName string) (map[string]*Index, error) { +func (db *mssql) GetIndexes(tableName string) (map[string]*core.Index, error) { args := []interface{}{tableName} s := `SELECT IXS.NAME AS [INDEX_NAME], @@ -248,48 +214,42 @@ 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) - if err != nil { - return nil, err - } - defer cnn.Close() - res, err := query(cnn, s, args...) + rows, err := db.DB().Query(s, args...) if err != nil { return nil, err } - indexes := make(map[string]*Index, 0) - for _, record := range res { + indexes := make(map[string]*core.Index, 0) + for rows.Next() { var indexType int - var indexName, colName string - for name, content := range record { - switch name { - case "IS_UNIQUE": - i, err := strconv.ParseBool(string(content)) - if err != nil { - return nil, err - } + var indexName, colName, isUnique string - if i { - indexType = UniqueType - } else { - indexType = IndexType - } - case "INDEX_NAME": - indexName = string(content) - case "COLUMN_NAME": - colName = strings.Trim(string(content), "` ") - } + err = rows.Scan(&indexName, &colName, &isUnique, nil) + if err != nil { + return nil, err } + i, err := strconv.ParseBool(isUnique) + if err != nil { + return nil, err + } + + if i { + indexType = core.UniqueType + } else { + indexType = core.IndexType + } + + colName = strings.Trim(colName, "` ") + if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { indexName = indexName[5+len(tableName) : len(indexName)] } - var index *Index + var index *core.Index var ok bool if index, ok = indexes[indexName]; !ok { - index = new(Index) + index = new(core.Index) index.Type = indexType index.Name = indexName indexes[indexName] = index @@ -298,3 +258,41 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? } return indexes, nil } + +func (db *mssql) CreateTablSql(table *core.Table, tableName, storeEngine, charset string) string { + var sql string + if tableName == "" { + tableName = table.Name + } + + sql = "IF NOT EXISTS (SELECT [name] FROM sys.tables WHERE [name] = '" + tableName + "' ) CREATE TABLE" + + sql += db.QuoteStr() + tableName + db.QuoteStr() + " (" + + pkList := table.PrimaryKeys + + for _, colName := range table.ColumnsSeq() { + col := table.GetColumn(colName) + if col.IsPrimaryKey && len(pkList) == 1 { + sql += col.String(db) + } else { + sql += col.StringNoPk(db) + } + sql = strings.TrimSpace(sql) + sql += ", " + } + + if len(pkList) > 1 { + sql += "PRIMARY KEY ( " + sql += strings.Join(pkList, ",") + sql += " ), " + } + + sql = sql[:len(sql)-2] + ")" + sql += ";" + return sql +} + +func (db *mssql) Filters() []core.Filter { + return []core.Filter{&core.IdFilter{}, &core.QuoteFilter{}} +} diff --git a/mssql_test.go b/mssql_test.go deleted file mode 100644 index a9093866..00000000 --- a/mssql_test.go +++ /dev/null @@ -1,142 +0,0 @@ -package xorm - -// -// +build windows - -import ( - "database/sql" - "testing" - - _ "github.com/lunny/godbc" -) - -const mssqlConnStr = "driver={SQL Server};Server=192.168.20.135;Database=xorm_test; uid=sa; pwd=1234;" - -func newMssqlEngine() (*Engine, error) { - return NewEngine("odbc", mssqlConnStr) -} - -func TestMssql(t *testing.T) { - engine, err := newMssqlEngine() - 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) -} - -func TestMssqlWithCache(t *testing.T) { - engine, err := newMssqlEngine() - 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) -} - -func newMssqlDriverDB() (*sql.DB, error) { - return sql.Open("odbc", mssqlConnStr) -} - -const ( - createTableMssql = `IF NOT EXISTS (SELECT [name] FROM sys.tables WHERE [name] = 'big_struct' ) CREATE TABLE - "big_struct" ("id" BIGINT PRIMARY KEY IDENTITY NOT NULL, "name" VARCHAR(255) NULL, "title" VARCHAR(255) NULL, - "age" VARCHAR(255) NULL, "alias" VARCHAR(255) NULL, "nick_name" VARCHAR(255) NULL); - ` - - dropTableMssql = "IF EXISTS (SELECT * FROM sysobjects WHERE id = object_id(N'big_struct') and OBJECTPROPERTY(id, N'IsUserTable') = 1) DROP TABLE IF EXISTS `big_struct`;" -) - -func BenchmarkMssqlDriverInsert(t *testing.B) { - doBenchDriver(newMssqlDriverDB, createTableMssql, dropTableMssql, - doBenchDriverInsert, t) -} - -func BenchmarkMssqlDriverFind(t *testing.B) { - doBenchDriver(newMssqlDriverDB, createTableMssql, dropTableMssql, - doBenchDriverFind, t) -} - -func BenchmarkMssqlNoCacheInsert(t *testing.B) { - engine, err := newMssqlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - //engine.ShowSQL = true - doBenchInsert(engine, t) -} - -func BenchmarkMssqlNoCacheFind(t *testing.B) { - engine, err := newMssqlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - //engine.ShowSQL = true - doBenchFind(engine, t) -} - -func BenchmarkMssqlNoCacheFindPtr(t *testing.B) { - engine, err := newMssqlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - //engine.ShowSQL = true - doBenchFindPtr(engine, t) -} - -func BenchmarkMssqlCacheInsert(t *testing.B) { - engine, err := newMssqlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) - - doBenchInsert(engine, t) -} - -func BenchmarkMssqlCacheFind(t *testing.B) { - engine, err := newMssqlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) - - doBenchFind(engine, t) -} - -func BenchmarkMssqlCacheFindPtr(t *testing.B) { - engine, err := newMssqlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) - - doBenchFindPtr(engine, t) -} diff --git a/mymysql.go b/mymysql_driver.go similarity index 67% rename from mymysql.go rename to mymysql_driver.go index 8101d4c0..a4e726a4 100644 --- a/mymysql.go +++ b/mymysql_driver.go @@ -4,17 +4,19 @@ import ( "errors" "strings" "time" + + "github.com/go-xorm/core" ) -type mymysql struct { - mysql +// func init() { +// core.RegisterDriver("mymysql", &mymysqlDriver{}) +// } + +type mymysqlDriver struct { } -type mymysqlParser struct { -} - -func (p *mymysqlParser) parse(driverName, dataSourceName string) (*uri, error) { - db := &uri{dbType: MYSQL} +func (p *mymysqlDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { + db := &core.Uri{DbType: core.MYSQL} pd := strings.SplitN(dataSourceName, "*", 2) if len(pd) == 2 { @@ -23,9 +25,9 @@ func (p *mymysqlParser) parse(driverName, dataSourceName string) (*uri, error) { if len(p) != 2 { return nil, errors.New("Wrong protocol part of URI") } - db.proto = p[0] + db.Proto = p[0] options := strings.Split(p[1], ",") - db.raddr = options[0] + db.Raddr = options[0] for _, o := range options[1:] { kv := strings.SplitN(o, "=", 2) var k, v string @@ -36,13 +38,13 @@ func (p *mymysqlParser) parse(driverName, dataSourceName string) (*uri, error) { } switch k { case "laddr": - db.laddr = v + db.Laddr = v case "timeout": to, err := time.ParseDuration(v) if err != nil { return nil, err } - db.timeout = to + db.Timeout = to default: return nil, errors.New("Unknown option: " + k) } @@ -55,13 +57,9 @@ func (p *mymysqlParser) parse(driverName, dataSourceName string) (*uri, error) { if len(dup) != 3 { return nil, errors.New("Wrong database part of URI") } - db.dbName = dup[0] - db.user = dup[1] - db.passwd = dup[2] + db.DbName = dup[0] + db.User = dup[1] + db.Passwd = dup[2] return db, nil } - -func (db *mymysql) Init(drivername, uri string) error { - return db.mysql.base.init(&mymysqlParser{}, drivername, uri) -} diff --git a/mymysql_test.go b/mymysql_test.go deleted file mode 100644 index 0ad8aace..00000000 --- a/mymysql_test.go +++ /dev/null @@ -1,167 +0,0 @@ -package xorm - -import ( - "database/sql" - "testing" - - _ "github.com/ziutek/mymysql/godrv" -) - -/* -CREATE DATABASE IF NOT EXISTS xorm_test CHARACTER SET -utf8 COLLATE utf8_general_ci; -*/ - -var showTestSql bool = true - -func TestMyMysql(t *testing.T) { - err := mymysqlDdlImport() - if err != nil { - t.Error(err) - return - } - engine, err := NewEngine("mymysql", "xorm_test/root/") - 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 TestMyMysqlWithCache(t *testing.T) { - err := mymysqlDdlImport() - if err != nil { - t.Error(err) - return - } - engine, err := NewEngine("mymysql", "xorm_test2/root/") - 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) -} - -func newMyMysqlEngine() (*Engine, error) { - return NewEngine("mymysql", "xorm_test2/root/") -} - -func newMyMysqlDriverDB() (*sql.DB, error) { - return sql.Open("mymysql", "xorm_test2/root/") -} - -func BenchmarkMyMysqlDriverInsert(t *testing.B) { - doBenchDriver(newMyMysqlDriverDB, createTableMySql, dropTableMySql, - doBenchDriverInsert, t) -} - -func BenchmarkMyMysqlDriverFind(t *testing.B) { - doBenchDriver(newMyMysqlDriverDB, createTableMySql, dropTableMySql, - doBenchDriverFind, t) -} - -func mymysqlDdlImport() error { - engine, err := NewEngine("mymysql", "/root/") - if err != nil { - return err - } - engine.ShowSQL = showTestSql - engine.ShowErr = showTestSql - engine.ShowWarn = showTestSql - engine.ShowDebug = showTestSql - - sqlResults, _ := engine.Import("tests/mysql_ddl.sql") - engine.LogDebug("sql results: %v", sqlResults) - engine.Close() - return nil -} - -func BenchmarkMyMysqlNoCacheInsert(t *testing.B) { - engine, err := newMyMysqlEngine() - if err != nil { - t.Error(err) - return - } - defer engine.Close() - - doBenchInsert(engine, t) -} - -func BenchmarkMyMysqlNoCacheFind(t *testing.B) { - engine, err := newMyMysqlEngine() - if err != nil { - t.Error(err) - return - } - defer engine.Close() - - //engine.ShowSQL = true - doBenchFind(engine, t) -} - -func BenchmarkMyMysqlNoCacheFindPtr(t *testing.B) { - engine, err := newMyMysqlEngine() - if err != nil { - t.Error(err) - return - } - defer engine.Close() - - //engine.ShowSQL = true - doBenchFindPtr(engine, t) -} - -func BenchmarkMyMysqlCacheInsert(t *testing.B) { - engine, err := newMyMysqlEngine() - if err != nil { - t.Error(err) - return - } - - defer engine.Close() - engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) - - doBenchInsert(engine, t) -} - -func BenchmarkMyMysqlCacheFind(t *testing.B) { - engine, err := newMyMysqlEngine() - if err != nil { - t.Error(err) - return - } - - defer engine.Close() - engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) - - doBenchFind(engine, t) -} - -func BenchmarkMyMysqlCacheFindPtr(t *testing.B) { - engine, err := newMyMysqlEngine() - if err != nil { - t.Error(err) - return - } - - defer engine.Close() - engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) - - doBenchFindPtr(engine, t) -} diff --git a/mysql.go b/mysql.go deleted file mode 100644 index 8d0cfaa3..00000000 --- a/mysql.go +++ /dev/null @@ -1,360 +0,0 @@ -package xorm - -import ( - "crypto/tls" - "database/sql" - "errors" - "fmt" - "regexp" - "strconv" - "strings" - "time" -) - -type uri struct { - dbType string - proto string - host string - port string - dbName string - user string - passwd string - charset string - laddr string - raddr string - timeout time.Duration -} - -type parser interface { - parse(driverName, dataSourceName string) (*uri, error) -} - -type mysqlParser struct { -} - -func (p *mysqlParser) parse(driverName, dataSourceName string) (*uri, error) { - dsnPattern := regexp.MustCompile( - `^(?:(?P.*?)(?::(?P.*))?@)?` + // [user[:password]@] - `(?:(?P[^\(]*)(?:\((?P[^\)]*)\))?)?` + // [net[(addr)]] - `\/(?P.*?)` + // /dbname - `(?:\?(?P[^\?]*))?$`) // [?param1=value1¶mN=valueN] - matches := dsnPattern.FindStringSubmatch(dataSourceName) - //tlsConfigRegister := make(map[string]*tls.Config) - names := dsnPattern.SubexpNames() - - uri := &uri{dbType: MYSQL} - - for i, match := range matches { - switch names[i] { - case "dbname": - uri.dbName = match - case "params": - if len(match) > 0 { - kvs := strings.Split(match, "&") - for _, kv := range kvs { - splits := strings.Split(kv, "=") - if len(splits) == 2 { - switch splits[0] { - case "charset": - uri.charset = splits[1] - } - } - } - } - - } - } - return uri, nil -} - -type base struct { - parser parser - driverName string - dataSourceName string - *uri -} - -func (b *base) init(parser parser, drivername, dataSourceName string) (err error) { - b.parser = parser - b.driverName, b.dataSourceName = drivername, dataSourceName - b.uri, err = b.parser.parse(b.driverName, b.dataSourceName) - return -} - -func (b *base) URI() *uri { - return b.uri -} - -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 - addr string - params map[string]string - loc *time.Location - timeout time.Duration - tls *tls.Config - allowAllFiles bool - allowOldPasswords bool - clientFoundRows bool -} - -func (db *mysql) Init(drivername, uri string) error { - return db.base.init(&mysqlParser{}, drivername, uri) -} - -func (db *mysql) SqlType(c *Column) string { - var res string - switch t := c.SQLType.Name; t { - case Bool: - res = TinyInt - c.Length = 1 - case Serial: - c.IsAutoIncrement = true - c.IsPrimaryKey = true - c.Nullable = false - res = Int - case BigSerial: - c.IsAutoIncrement = true - c.IsPrimaryKey = true - c.Nullable = false - res = BigInt - case Bytea: - res = Blob - case TimeStampz: - res = Char - c.Length = 64 - default: - res = t - } - - var hasLen1 bool = (c.Length > 0) - var hasLen2 bool = (c.Length2 > 0) - if hasLen1 { - res += "(" + strconv.Itoa(c.Length) + ")" - } else if hasLen2 { - res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" - } - return res -} - -func (db *mysql) SupportInsertMany() bool { - return true -} - -func (db *mysql) QuoteStr() string { - return "`" -} - -func (db *mysql) SupportEngine() bool { - return true -} - -func (db *mysql) AutoIncrStr() string { - return "AUTO_INCREMENT" -} - -func (db *mysql) SupportCharset() bool { - return true -} - -func (db *mysql) IndexOnTable() bool { - return true -} - -func (db *mysql) IndexCheckSql(tableName, idxName string) (string, []interface{}) { - args := []interface{}{db.dbName, tableName, idxName} - sql := "SELECT `INDEX_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS`" - sql += " WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `INDEX_NAME`=?" - return sql, args -} - -func (db *mysql) ColumnCheckSql(tableName, colName string) (string, []interface{}) { - args := []interface{}{db.dbName, tableName, colName} - sql := "SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ?" - return sql, args -} - -func (db *mysql) TableCheckSql(tableName string) (string, []interface{}) { - args := []interface{}{db.dbName, tableName} - sql := "SELECT `TABLE_NAME` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? and `TABLE_NAME`=?" - return sql, args -} - -func (db *mysql) GetColumns(tableName string) ([]string, map[string]*Column, error) { - args := []interface{}{db.dbName, tableName} - s := "SELECT `COLUMN_NAME`, `IS_NULLABLE`, `COLUMN_DEFAULT`, `COLUMN_TYPE`," + - " `COLUMN_KEY`, `EXTRA` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_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 - } - cols := make(map[string]*Column) - colSeq := make([]string, 0) - for _, record := range res { - col := new(Column) - col.Indexes = make(map[string]bool) - for name, content := range record { - switch name { - case "COLUMN_NAME": - col.Name = strings.Trim(string(content), "` ") - case "IS_NULLABLE": - if "YES" == string(content) { - col.Nullable = true - } - 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 - if len(cts) == 2 { - idx := strings.Index(cts[1], ")") - lens := strings.Split(cts[1][0:idx], ",") - len1, err = strconv.Atoi(strings.TrimSpace(lens[0])) - if err != nil { - return nil, nil, err - } - if len(lens) == 2 { - len2, err = strconv.Atoi(lens[1]) - if err != nil { - return nil, nil, err - } - } - } - colName := cts[0] - colType := strings.ToUpper(colName) - col.Length = len1 - col.Length2 = len2 - if _, ok := sqlTypes[colType]; ok { - col.SQLType = SQLType{colType, len1, len2} - } else { - return nil, nil, errors.New(fmt.Sprintf("unkonw colType %v", colType)) - } - case "COLUMN_KEY": - key := string(content) - if key == "PRI" { - col.IsPrimaryKey = true - } - if key == "UNI" { - //col.is - } - case "EXTRA": - extra := string(content) - if extra == "auto_increment" { - col.IsAutoIncrement = true - } - } - } - if col.SQLType.IsText() { - if col.Default != "" { - col.Default = "'" + col.Default + "'" - } else { - if col.DefaultIsEmpty { - col.Default = "''" - } - } - } - cols[col.Name] = col - colSeq = append(colSeq, col.Name) - } - return colSeq, cols, nil -} - -func (db *mysql) GetTables() ([]*Table, error) { - args := []interface{}{db.dbName} - s := "SELECT `TABLE_NAME`, `ENGINE`, `TABLE_ROWS`, `AUTO_INCREMENT` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=?" - 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 "TABLE_NAME": - table.Name = strings.Trim(string(content), "` ") - case "ENGINE": - } - } - tables = append(tables, table) - } - return tables, nil -} - -func (db *mysql) GetIndexes(tableName string) (map[string]*Index, error) { - args := []interface{}{db.dbName, tableName} - s := "SELECT `INDEX_NAME`, `NON_UNIQUE`, `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_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 { - var indexType int - var indexName, colName string - for name, content := range record { - switch name { - case "NON_UNIQUE": - if "YES" == string(content) || string(content) == "1" { - indexType = IndexType - } else { - indexType = UniqueType - } - case "INDEX_NAME": - indexName = string(content) - case "COLUMN_NAME": - colName = strings.Trim(string(content), "` ") - } - } - if indexName == "PRIMARY" { - continue - } - if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { - indexName = indexName[5+len(tableName) : len(indexName)] - } - - var index *Index - var ok bool - if index, ok = indexes[indexName]; !ok { - index = new(Index) - index.Type = indexType - index.Name = indexName - indexes[indexName] = index - } - index.AddColumn(colName) - } - return indexes, nil -} diff --git a/mysql_dialect.go b/mysql_dialect.go new file mode 100644 index 00000000..71273183 --- /dev/null +++ b/mysql_dialect.go @@ -0,0 +1,269 @@ +package xorm + +import ( + "crypto/tls" + "errors" + "fmt" + "strconv" + "strings" + "time" + + "github.com/go-xorm/core" +) + +// func init() { +// RegisterDialect("mysql", &mysql{}) +// } + +type mysql struct { + core.Base + net string + addr string + params map[string]string + loc *time.Location + timeout time.Duration + tls *tls.Config + allowAllFiles bool + allowOldPasswords bool + clientFoundRows bool +} + +func (db *mysql) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error { + return db.Base.Init(d, db, uri, drivername, dataSourceName) +} + +func (db *mysql) SqlType(c *core.Column) string { + var res string + switch t := c.SQLType.Name; t { + case core.Bool: + res = core.TinyInt + c.Length = 1 + case core.Serial: + c.IsAutoIncrement = true + c.IsPrimaryKey = true + c.Nullable = false + res = core.Int + case core.BigSerial: + c.IsAutoIncrement = true + c.IsPrimaryKey = true + c.Nullable = false + res = core.BigInt + case core.Bytea: + res = core.Blob + case core.TimeStampz: + res = core.Char + c.Length = 64 + default: + res = t + } + + var hasLen1 bool = (c.Length > 0) + var hasLen2 bool = (c.Length2 > 0) + if hasLen1 { + res += "(" + strconv.Itoa(c.Length) + ")" + } else if hasLen2 { + res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" + } + return res +} + +func (db *mysql) SupportInsertMany() bool { + return true +} + +func (db *mysql) QuoteStr() string { + return "`" +} + +func (db *mysql) SupportEngine() bool { + return true +} + +func (db *mysql) AutoIncrStr() string { + return "AUTO_INCREMENT" +} + +func (db *mysql) SupportCharset() bool { + return true +} + +func (db *mysql) IndexOnTable() bool { + return true +} + +func (db *mysql) IndexCheckSql(tableName, idxName string) (string, []interface{}) { + args := []interface{}{db.DbName, tableName, idxName} + sql := "SELECT `INDEX_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS`" + sql += " WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `INDEX_NAME`=?" + return sql, args +} + +func (db *mysql) ColumnCheckSql(tableName, colName string) (string, []interface{}) { + args := []interface{}{db.DbName, tableName, colName} + sql := "SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ?" + return sql, args +} + +func (db *mysql) TableCheckSql(tableName string) (string, []interface{}) { + args := []interface{}{db.DbName, tableName} + sql := "SELECT `TABLE_NAME` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? and `TABLE_NAME`=?" + return sql, args +} + +func (db *mysql) GetColumns(tableName string) ([]string, map[string]*core.Column, error) { + args := []interface{}{db.DbName, tableName} + s := "SELECT `COLUMN_NAME`, `IS_NULLABLE`, `COLUMN_DEFAULT`, `COLUMN_TYPE`," + + " `COLUMN_KEY`, `EXTRA` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?" + + rows, err := db.DB().Query(s, args...) + if err != nil { + return nil, nil, err + } + defer rows.Close() + + cols := make(map[string]*core.Column) + colSeq := make([]string, 0) + for rows.Next() { + col := new(core.Column) + col.Indexes = make(map[string]bool) + + var columnName, isNullable, colType, colKey, extra string + var colDefault *string + err = rows.Scan(&columnName, &isNullable, &colDefault, &colType, &colKey, &extra) + if err != nil { + return nil, nil, err + } + col.Name = strings.Trim(columnName, "` ") + if "YES" == isNullable { + col.Nullable = true + } + + if colDefault != nil { + col.Default = *colDefault + } + + cts := strings.Split(colType, "(") + var len1, len2 int + if len(cts) == 2 { + idx := strings.Index(cts[1], ")") + lens := strings.Split(cts[1][0:idx], ",") + len1, err = strconv.Atoi(strings.TrimSpace(lens[0])) + if err != nil { + return nil, nil, err + } + if len(lens) == 2 { + len2, err = strconv.Atoi(lens[1]) + if err != nil { + return nil, nil, err + } + } + } + colName := cts[0] + colType = strings.ToUpper(colName) + col.Length = len1 + col.Length2 = len2 + if _, ok := core.SqlTypes[colType]; ok { + col.SQLType = core.SQLType{colType, len1, len2} + } else { + return nil, nil, errors.New(fmt.Sprintf("unkonw colType %v", colType)) + } + + if colKey == "PRI" { + col.IsPrimaryKey = true + } + if colKey == "UNI" { + //col.is + } + + if extra == "auto_increment" { + col.IsAutoIncrement = true + } + + if col.SQLType.IsText() { + if col.Default != "" { + col.Default = "'" + col.Default + "'" + } + } + cols[col.Name] = col + colSeq = append(colSeq, col.Name) + } + return colSeq, cols, nil +} + +func (db *mysql) GetTables() ([]*core.Table, error) { + args := []interface{}{db.DbName} + s := "SELECT `TABLE_NAME`, `ENGINE`, `TABLE_ROWS`, `AUTO_INCREMENT` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=?" + + rows, err := db.DB().Query(s, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + tables := make([]*core.Table, 0) + for rows.Next() { + table := core.NewEmptyTable() + var name, engine, tableRows string + var autoIncr *string + err = rows.Scan(&name, &engine, &tableRows, &autoIncr) + if err != nil { + return nil, err + } + + table.Name = name + tables = append(tables, table) + } + return tables, nil +} + +func (db *mysql) GetIndexes(tableName string) (map[string]*core.Index, error) { + args := []interface{}{db.DbName, tableName} + s := "SELECT `INDEX_NAME`, `NON_UNIQUE`, `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?" + + rows, err := db.DB().Query(s, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + indexes := make(map[string]*core.Index, 0) + for rows.Next() { + var indexType int + var indexName, colName, nonUnique string + err = rows.Scan(&indexName, &nonUnique, &colName) + if err != nil { + return nil, err + } + + if indexName == "PRIMARY" { + continue + } + + if "YES" == nonUnique || nonUnique == "1" { + indexType = core.IndexType + } else { + indexType = core.UniqueType + } + + colName = strings.Trim(colName, "` ") + + if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { + indexName = indexName[5+len(tableName) : len(indexName)] + } + + var index *core.Index + var ok bool + if index, ok = indexes[indexName]; !ok { + index = new(core.Index) + index.Type = indexType + index.Name = indexName + indexes[indexName] = index + } + index.AddColumn(colName) + } + return indexes, nil +} + +func (db *mysql) Filters() []core.Filter { + return []core.Filter{&core.IdFilter{}} +} diff --git a/mysql_driver.go b/mysql_driver.go new file mode 100644 index 00000000..c36fa8b6 --- /dev/null +++ b/mysql_driver.go @@ -0,0 +1,50 @@ +package xorm + +import ( + "regexp" + "strings" + + "github.com/go-xorm/core" +) + +// func init() { +// core.RegisterDriver("mysql", &mysqlDriver{}) +// } + +type mysqlDriver struct { +} + +func (p *mysqlDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { + dsnPattern := regexp.MustCompile( + `^(?:(?P.*?)(?::(?P.*))?@)?` + // [user[:password]@] + `(?:(?P[^\(]*)(?:\((?P[^\)]*)\))?)?` + // [net[(addr)]] + `\/(?P.*?)` + // /dbname + `(?:\?(?P[^\?]*))?$`) // [?param1=value1¶mN=valueN] + matches := dsnPattern.FindStringSubmatch(dataSourceName) + //tlsConfigRegister := make(map[string]*tls.Config) + names := dsnPattern.SubexpNames() + + uri := &core.Uri{DbType: core.MYSQL} + + for i, match := range matches { + switch names[i] { + case "dbname": + uri.DbName = match + case "params": + if len(match) > 0 { + kvs := strings.Split(match, "&") + for _, kv := range kvs { + splits := strings.Split(kv, "=") + if len(splits) == 2 { + switch splits[0] { + case "charset": + uri.Charset = splits[1] + } + } + } + } + + } + } + return uri, nil +} diff --git a/mysql_test.go b/mysql_test.go deleted file mode 100644 index e0c3deac..00000000 --- a/mysql_test.go +++ /dev/null @@ -1,191 +0,0 @@ -package xorm - -import ( - "database/sql" - "testing" - - _ "github.com/go-sql-driver/mysql" -) - -/* -CREATE DATABASE IF NOT EXISTS xorm_test CHARACTER SET -utf8 COLLATE utf8_general_ci; -*/ - -func TestMysql(t *testing.T) { - err := mysqlDdlImport() - if err != nil { - t.Error(err) - return - } - - engine, err := NewEngine("mysql", "root:@/xorm_test?charset=utf8") - 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 TestMysqlSameMapper(t *testing.T) { - err := mysqlDdlImport() - if err != nil { - t.Error(err) - return - } - - engine, err := NewEngine("mysql", "root:@/xorm_test3?charset=utf8") - defer engine.Close() - if err != nil { - t.Error(err) - return - } - engine.ShowSQL = showTestSql - engine.ShowErr = showTestSql - engine.ShowWarn = showTestSql - engine.ShowDebug = showTestSql - engine.SetMapper(SameMapper{}) - - testAll(engine, t) - testAll2(engine, t) - testAll3(engine, t) -} - -func TestMysqlWithCache(t *testing.T) { - err := mysqlDdlImport() - if err != nil { - t.Error(err) - return - } - - engine, err := NewEngine("mysql", "root:@/xorm_test?charset=utf8") - 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) -} - -func newMysqlEngine() (*Engine, error) { - return NewEngine("mysql", "root:@/xorm_test?charset=utf8") -} - -func mysqlDdlImport() error { - engine, err := NewEngine("mysql", "root:@/?charset=utf8") - if err != nil { - return err - } - engine.ShowSQL = showTestSql - engine.ShowErr = showTestSql - engine.ShowWarn = showTestSql - engine.ShowDebug = showTestSql - - sqlResults, _ := engine.Import("tests/mysql_ddl.sql") - engine.LogDebug("sql results: %v", sqlResults) - engine.Close() - return nil -} - -func newMysqlDriverDB() (*sql.DB, error) { - return sql.Open("mysql", "root:@/xorm_test?charset=utf8") -} - -const ( - createTableMySql = "CREATE TABLE IF NOT EXISTS `big_struct` (`id` BIGINT PRIMARY KEY AUTO_INCREMENT NOT NULL, `name` VARCHAR(255) NULL, `title` VARCHAR(255) NULL, `age` VARCHAR(255) NULL, `alias` VARCHAR(255) NULL, `nick_name` VARCHAR(255) NULL);" - dropTableMySql = "DROP TABLE IF EXISTS `big_struct`;" -) - -func BenchmarkMysqlDriverInsert(t *testing.B) { - doBenchDriver(newMysqlDriverDB, createTableMySql, dropTableMySql, - doBenchDriverInsert, t) -} - -func BenchmarkMysqlDriverFind(t *testing.B) { - doBenchDriver(newMysqlDriverDB, createTableMySql, dropTableMySql, - doBenchDriverFind, t) -} - -func BenchmarkMysqlNoCacheInsert(t *testing.B) { - engine, err := newMysqlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - //engine.ShowSQL = true - doBenchInsert(engine, t) -} - -func BenchmarkMysqlNoCacheFind(t *testing.B) { - engine, err := newMysqlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - //engine.ShowSQL = true - doBenchFind(engine, t) -} - -func BenchmarkMysqlNoCacheFindPtr(t *testing.B) { - engine, err := newMysqlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - //engine.ShowSQL = true - doBenchFindPtr(engine, t) -} - -func BenchmarkMysqlCacheInsert(t *testing.B) { - engine, err := newMysqlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) - - doBenchInsert(engine, t) -} - -func BenchmarkMysqlCacheFind(t *testing.B) { - engine, err := newMysqlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) - - doBenchFind(engine, t) -} - -func BenchmarkMysqlCacheFindPtr(t *testing.B) { - engine, err := newMysqlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) - - doBenchFindPtr(engine, t) -} diff --git a/oci8_driver.go b/oci8_driver.go new file mode 100644 index 00000000..5bb1d16c --- /dev/null +++ b/oci8_driver.go @@ -0,0 +1,37 @@ +package xorm + +import ( + "errors" + "regexp" + + "github.com/go-xorm/core" +) + +// func init() { +// core.RegisterDriver("oci8", &oci8Driver{}) +// } + +type oci8Driver struct { +} + +//dataSourceName=user/password@ipv4:port/dbname +//dataSourceName=user/password@[ipv6]:port/dbname +func (p *oci8Driver) Parse(driverName, dataSourceName string) (*core.Uri, error) { + db := &core.Uri{DbType: core.ORACLE} + dsnPattern := regexp.MustCompile( + `^(?P.*)\/(?P.*)@` + // user:password@ + `(?P.*)` + // ip:port + `\/(?P.*)`) // dbname + matches := dsnPattern.FindStringSubmatch(dataSourceName) + names := dsnPattern.SubexpNames() + for i, match := range matches { + switch names[i] { + case "dbname": + db.DbName = match + } + } + if db.DbName == "" { + return nil, errors.New("dbname is empty") + } + return db, nil +} diff --git a/odbc_driver.go b/odbc_driver.go new file mode 100644 index 00000000..0f6d4e18 --- /dev/null +++ b/odbc_driver.go @@ -0,0 +1,34 @@ +package xorm + +import ( + "errors" + "strings" + + "github.com/go-xorm/core" +) + +// func init() { +// core.RegisterDriver("odbc", &odbcDriver{}) +// } + +type odbcDriver struct { +} + +func (p *odbcDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { + kv := strings.Split(dataSourceName, ";") + var dbName string + + for _, c := range kv { + vv := strings.Split(strings.TrimSpace(c), "=") + if len(vv) == 2 { + switch strings.ToLower(vv[0]) { + case "database": + dbName = vv[1] + } + } + } + if dbName == "" { + return nil, errors.New("no db name provided") + } + return &core.Uri{DbName: dbName, DbType: core.MSSQL}, nil +} diff --git a/oracle.go b/oracle.go deleted file mode 100644 index 0b4238ca..00000000 --- a/oracle.go +++ /dev/null @@ -1,265 +0,0 @@ -package xorm - -import ( - "database/sql" - "errors" - "fmt" - "regexp" - "strconv" - "strings" -) - -type oracle struct { - base -} - -type oracleParser struct { -} - -//dataSourceName=user/password@ipv4:port/dbname -//dataSourceName=user/password@[ipv6]:port/dbname -func (p *oracleParser) parse(driverName, dataSourceName string) (*uri, error) { - db := &uri{dbType: ORACLE_OCI} - dsnPattern := regexp.MustCompile( - `^(?P.*)\/(?P.*)@` + // user:password@ - `(?P.*)` + // ip:port - `\/(?P.*)`) // dbname - matches := dsnPattern.FindStringSubmatch(dataSourceName) - names := dsnPattern.SubexpNames() - for i, match := range matches { - switch names[i] { - case "dbname": - db.dbName = match - } - } - if db.dbName == "" { - return nil, errors.New("dbname is empty") - } - return db, nil -} - -func (db *oracle) Init(drivername, uri string) error { - return db.base.init(&oracleParser{}, drivername, uri) -} - -func (db *oracle) SqlType(c *Column) string { - var res string - switch t := c.SQLType.Name; t { - case Bit, TinyInt, SmallInt, MediumInt, Int, Integer, BigInt, Bool, Serial, BigSerial: - return "NUMBER" - case Binary, VarBinary, Blob, TinyBlob, MediumBlob, LongBlob, Bytea: - return Blob - case Time, DateTime, TimeStamp: - res = TimeStamp - case TimeStampz: - res = "TIMESTAMP WITH TIME ZONE" - case Float, Double, Numeric, Decimal: - res = "NUMBER" - case Text, MediumText, LongText: - res = "CLOB" - case Char, Varchar, TinyText: - return "VARCHAR2" - default: - res = t - } - - var hasLen1 bool = (c.Length > 0) - var hasLen2 bool = (c.Length2 > 0) - if hasLen1 { - res += "(" + strconv.Itoa(c.Length) + ")" - } else if hasLen2 { - res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" - } - return res -} - -func (db *oracle) SupportInsertMany() bool { - return true -} - -func (db *oracle) QuoteStr() string { - return "\"" -} - -func (db *oracle) AutoIncrStr() string { - return "" -} - -func (db *oracle) SupportEngine() bool { - return false -} - -func (db *oracle) SupportCharset() bool { - return false -} - -func (db *oracle) IndexOnTable() bool { - return false -} - -func (db *oracle) IndexCheckSql(tableName, idxName string) (string, []interface{}) { - args := []interface{}{strings.ToUpper(tableName), strings.ToUpper(idxName)} - return `SELECT INDEX_NAME FROM USER_INDEXES ` + - `WHERE TABLE_NAME = ? AND INDEX_NAME = ?`, args -} - -func (db *oracle) TableCheckSql(tableName string) (string, []interface{}) { - args := []interface{}{strings.ToUpper(tableName)} - return `SELECT table_name FROM user_tables WHERE table_name = ?`, args -} - -func (db *oracle) ColumnCheckSql(tableName, colName string) (string, []interface{}) { - args := []interface{}{strings.ToUpper(tableName), strings.ToUpper(colName)} - return "SELECT column_name FROM USER_TAB_COLUMNS WHERE table_name = ?" + - " AND column_name = ?", args -} - -func (db *oracle) GetColumns(tableName string) ([]string, map[string]*Column, error) { - args := []interface{}{strings.ToUpper(tableName)} - s := "SELECT column_name,data_default,data_type,data_length,data_precision,data_scale," + - "nullable FROM USER_TAB_COLUMNS WHERE table_name = :1" - - 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 - } - cols := make(map[string]*Column) - colSeq := make([]string, 0) - for _, record := range res { - col := new(Column) - col.Indexes = make(map[string]bool) - for name, content := range record { - switch name { - case "column_name": - 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 - } else { - col.Nullable = false - } - case "data_type": - ct := string(content) - switch ct { - case "VARCHAR2": - col.SQLType = SQLType{Varchar, 0, 0} - case "TIMESTAMP WITH TIME ZONE": - col.SQLType = SQLType{TimeStamp, 0, 0} - default: - col.SQLType = SQLType{strings.ToUpper(ct), 0, 0} - } - if _, ok := sqlTypes[col.SQLType.Name]; !ok { - return nil, nil, errors.New(fmt.Sprintf("unkonw colType %v", ct)) - } - case "data_length": - i, err := strconv.Atoi(string(content)) - if err != nil { - return nil, nil, errors.New("retrieve length error") - } - col.Length = i - case "data_precision": - case "data_scale": - } - } - if col.SQLType.IsText() { - if col.Default != "" { - col.Default = "'" + col.Default + "'" - }else{ - if col.DefaultIsEmpty { - col.Default = "''" - } - } - } - cols[col.Name] = col - colSeq = append(colSeq, col.Name) - } - - return colSeq, cols, nil -} - -func (db *oracle) GetTables() ([]*Table, error) { - args := []interface{}{} - s := "SELECT table_name FROM user_tables" - 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 "table_name": - table.Name = string(content) - } - } - tables = append(tables, table) - } - return tables, nil -} - -func (db *oracle) GetIndexes(tableName string) (map[string]*Index, error) { - args := []interface{}{tableName} - s := "SELECT t.column_name,i.table_name,i.uniqueness,i.index_name FROM user_ind_columns t,user_indexes i " + - "WHERE t.index_name = i.index_name and t.table_name = i.table_name and t.table_name =:1" - - 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 { - var indexType int - var indexName string - var colName string - - for name, content := range record { - switch name { - case "index_name": - indexName = strings.Trim(string(content), `" `) - case "uniqueness": - c := string(content) - if c == "UNIQUE" { - indexType = UniqueType - } else { - indexType = IndexType - } - case "column_name": - colName = string(content) - } - } - - var index *Index - var ok bool - if index, ok = indexes[indexName]; !ok { - index = new(Index) - index.Type = indexType - index.Name = indexName - indexes[indexName] = index - } - index.AddColumn(colName) - } - return indexes, nil -} diff --git a/oracle_dialect.go b/oracle_dialect.go new file mode 100644 index 00000000..36a2f62d --- /dev/null +++ b/oracle_dialect.go @@ -0,0 +1,241 @@ +package xorm + +import ( + "errors" + "fmt" + "strconv" + "strings" + + "github.com/go-xorm/core" +) + +// func init() { +// RegisterDialect("oracle", &oracle{}) +// } + +type oracle struct { + core.Base +} + +func (db *oracle) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error { + return db.Base.Init(d, db, uri, drivername, dataSourceName) +} + +func (db *oracle) SqlType(c *core.Column) string { + var res string + switch t := c.SQLType.Name; t { + case core.Bit, core.TinyInt, core.SmallInt, core.MediumInt, core.Int, core.Integer, core.BigInt, core.Bool, core.Serial, core.BigSerial: + return "NUMBER" + case core.Binary, core.VarBinary, core.Blob, core.TinyBlob, core.MediumBlob, core.LongBlob, core.Bytea: + return core.Blob + case core.Time, core.DateTime, core.TimeStamp: + res = core.TimeStamp + case core.TimeStampz: + res = "TIMESTAMP WITH TIME ZONE" + case core.Float, core.Double, core.Numeric, core.Decimal: + res = "NUMBER" + case core.Text, core.MediumText, core.LongText: + res = "CLOB" + case core.Char, core.Varchar, core.TinyText: + return "VARCHAR2" + default: + res = t + } + + var hasLen1 bool = (c.Length > 0) + var hasLen2 bool = (c.Length2 > 0) + if hasLen1 { + res += "(" + strconv.Itoa(c.Length) + ")" + } else if hasLen2 { + res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" + } + return res +} + +func (db *oracle) SupportInsertMany() bool { + return true +} + +func (db *oracle) QuoteStr() string { + return "\"" +} + +func (db *oracle) AutoIncrStr() string { + return "" +} + +func (db *oracle) SupportEngine() bool { + return false +} + +func (db *oracle) SupportCharset() bool { + return false +} + +func (db *oracle) IndexOnTable() bool { + return false +} + +func (db *oracle) IndexCheckSql(tableName, idxName string) (string, []interface{}) { + args := []interface{}{strings.ToUpper(tableName), strings.ToUpper(idxName)} + return `SELECT INDEX_NAME FROM USER_INDEXES ` + + `WHERE TABLE_NAME = ? AND INDEX_NAME = ?`, args +} + +func (db *oracle) TableCheckSql(tableName string) (string, []interface{}) { + args := []interface{}{strings.ToUpper(tableName)} + return `SELECT table_name FROM user_tables WHERE table_name = ?`, args +} + +func (db *oracle) ColumnCheckSql(tableName, colName string) (string, []interface{}) { + args := []interface{}{strings.ToUpper(tableName), strings.ToUpper(colName)} + return "SELECT column_name FROM USER_TAB_COLUMNS WHERE table_name = ?" + + " AND column_name = ?", args +} + +func (db *oracle) GetColumns(tableName string) ([]string, map[string]*core.Column, error) { + args := []interface{}{strings.ToUpper(tableName)} + s := "SELECT column_name,data_default,data_type,data_length,data_precision,data_scale," + + "nullable FROM USER_TAB_COLUMNS WHERE table_name = :1" + + rows, err := db.DB().Query(s, args...) + if err != nil { + return nil, nil, err + } + defer rows.Close() + + cols := make(map[string]*core.Column) + colSeq := make([]string, 0) + for rows.Next() { + col := new(core.Column) + col.Indexes = make(map[string]bool) + + var colName, colDefault, nullable, dataType, dataPrecision, dataScale string + var dataLen int + + err = rows.Scan(&colName, &colDefault, &dataType, &dataLen, &dataPrecision, + &dataScale, &nullable) + if err != nil { + return nil, nil, err + } + + col.Name = strings.Trim(colName, `" `) + col.Default = colDefault + + if nullable == "Y" { + col.Nullable = true + } else { + col.Nullable = false + } + + switch dataType { + case "VARCHAR2": + col.SQLType = core.SQLType{core.Varchar, 0, 0} + case "TIMESTAMP WITH TIME ZONE": + col.SQLType = core.SQLType{core.TimeStampz, 0, 0} + default: + col.SQLType = core.SQLType{strings.ToUpper(dataType), 0, 0} + } + if _, ok := core.SqlTypes[col.SQLType.Name]; !ok { + return nil, nil, errors.New(fmt.Sprintf("unkonw colType %v", dataType)) + } + + col.Length = dataLen + + if col.SQLType.IsText() { + if col.Default != "" { + col.Default = "'" + col.Default + "'" + } else { + if col.DefaultIsEmpty { + col.Default = "''" + } + } + } + cols[col.Name] = col + colSeq = append(colSeq, col.Name) + } + + return colSeq, cols, nil +} + +func (db *oracle) GetTables() ([]*core.Table, error) { + args := []interface{}{} + s := "SELECT table_name FROM user_tables" + + rows, err := db.DB().Query(s, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + tables := make([]*core.Table, 0) + for rows.Next() { + table := core.NewEmptyTable() + err = rows.Scan(&table.Name) + if err != nil { + return nil, err + } + + tables = append(tables, table) + } + return tables, nil +} + +func (db *oracle) GetIndexes(tableName string) (map[string]*core.Index, error) { + args := []interface{}{tableName} + s := "SELECT t.column_name,i.uniqueness,i.index_name FROM user_ind_columns t,user_indexes i " + + "WHERE t.index_name = i.index_name and t.table_name = i.table_name and t.table_name =:1" + + rows, err := db.DB().Query(s, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + indexes := make(map[string]*core.Index, 0) + for rows.Next() { + var indexType int + var indexName, colName, uniqueness string + + err = rows.Scan(&colName, &uniqueness, &indexName) + if err != nil { + return nil, err + } + + indexName = strings.Trim(indexName, `" `) + + if uniqueness == "UNIQUE" { + indexType = core.UniqueType + } else { + indexType = core.IndexType + } + + var index *core.Index + var ok bool + if index, ok = indexes[indexName]; !ok { + index = new(core.Index) + index.Type = indexType + index.Name = indexName + indexes[indexName] = index + } + index.AddColumn(colName) + } + return indexes, nil +} + +// PgSeqFilter filter SQL replace ?, ? ... to :1, :2 ... +type OracleSeqFilter struct { +} + +func (s *OracleSeqFilter) Do(sql string, dialect core.Dialect, table *core.Table) string { + counts := strings.Count(sql, "?") + for i := 1; i <= counts; i++ { + newstr := ":" + fmt.Sprintf("%v", i) + sql = strings.Replace(sql, "?", newstr, 1) + } + return sql +} + +func (db *oracle) Filters() []core.Filter { + return []core.Filter{&core.QuoteFilter{}, &OracleSeqFilter{}, &core.IdFilter{}} +} diff --git a/pool.go b/pool.go deleted file mode 100644 index 7a7b173e..00000000 --- a/pool.go +++ /dev/null @@ -1,286 +0,0 @@ -package xorm - -import ( - "database/sql" - //"fmt" - "sync" - //"sync/atomic" - "container/list" - "reflect" - "time" -) - -// Interface IConnecPool is a connection pool interface, all implements should implement -// Init, RetrieveDB, ReleaseDB and Close methods. -// Init for init when engine be created or invoke SetPool -// RetrieveDB for requesting a connection to db; -// ReleaseDB for releasing a db connection; -// Close for invoking when engine.Close -type IConnectPool interface { - Init(engine *Engine) error - RetrieveDB(engine *Engine) (*sql.DB, error) - ReleaseDB(engine *Engine, db *sql.DB) - Close(engine *Engine) error - SetMaxIdleConns(conns int) - MaxIdleConns() int - SetMaxConns(conns int) - MaxConns() int -} - -// Struct NoneConnectPool is a implement for IConnectPool. It provides directly invoke driver's -// open and release connection function -type NoneConnectPool struct { -} - -// NewNoneConnectPool new a NoneConnectPool. -func NewNoneConnectPool() IConnectPool { - return &NoneConnectPool{} -} - -// Init do nothing -func (p *NoneConnectPool) Init(engine *Engine) error { - return nil -} - -// RetrieveDB directly open a connection -func (p *NoneConnectPool) RetrieveDB(engine *Engine) (db *sql.DB, err error) { - db, err = engine.OpenDB() - return -} - -// ReleaseDB directly close a connection -func (p *NoneConnectPool) ReleaseDB(engine *Engine, db *sql.DB) { - db.Close() -} - -// Close do nothing -func (p *NoneConnectPool) Close(engine *Engine) error { - return nil -} - -func (p *NoneConnectPool) SetMaxIdleConns(conns int) { -} - -func (p *NoneConnectPool) MaxIdleConns() int { - return 0 -} - -// not implemented -func (p *NoneConnectPool) SetMaxConns(conns int) { -} - -// not implemented -func (p *NoneConnectPool) MaxConns() int { - return -1 -} - -// Struct SysConnectPool is a simple wrapper for using system default connection pool. -// About the system connection pool, you can review the code database/sql/sql.go -// It's currently default Pool implments. -type SysConnectPool struct { - db *sql.DB - maxIdleConns int - maxConns int - curConns int - mutex *sync.Mutex - queue *list.List -} - -// NewSysConnectPool new a SysConnectPool. -func NewSysConnectPool() IConnectPool { - return &SysConnectPool{} -} - -// Init create a db immediately and keep it util engine closed. -func (s *SysConnectPool) Init(engine *Engine) error { - db, err := engine.OpenDB() - if err != nil { - return err - } - s.db = db - s.maxIdleConns = 2 - s.maxConns = -1 - s.curConns = 0 - s.mutex = &sync.Mutex{} - s.queue = list.New() - return nil -} - -type node struct { - mutex sync.Mutex - cond *sync.Cond -} - -func newCondNode() *node { - n := &node{} - n.cond = sync.NewCond(&n.mutex) - return n -} - -// RetrieveDB just return the only db -func (s *SysConnectPool) RetrieveDB(engine *Engine) (db *sql.DB, err error) { - /*if s.maxConns > 0 { - fmt.Println("before retrieve") - s.mutex.Lock() - for s.curConns >= s.maxConns { - fmt.Println("before waiting...", s.curConns, s.queue.Len()) - s.mutex.Unlock() - n := NewNode() - n.cond.L.Lock() - s.queue.PushBack(n) - n.cond.Wait() - n.cond.L.Unlock() - s.mutex.Lock() - fmt.Println("after waiting...", s.curConns, s.queue.Len()) - } - s.curConns += 1 - s.mutex.Unlock() - fmt.Println("after retrieve") - }*/ - return s.db, nil -} - -// ReleaseDB do nothing -func (s *SysConnectPool) ReleaseDB(engine *Engine, db *sql.DB) { - /*if s.maxConns > 0 { - s.mutex.Lock() - fmt.Println("before release", s.queue.Len()) - s.curConns -= 1 - - if e := s.queue.Front(); e != nil { - n := e.Value.(*node) - //n.cond.L.Lock() - n.cond.Signal() - fmt.Println("signaled...") - s.queue.Remove(e) - //n.cond.L.Unlock() - } - fmt.Println("after released", s.queue.Len()) - s.mutex.Unlock() - }*/ -} - -// Close closed the only db -func (p *SysConnectPool) Close(engine *Engine) error { - return p.db.Close() -} - -func (p *SysConnectPool) SetMaxIdleConns(conns int) { - p.db.SetMaxIdleConns(conns) - p.maxIdleConns = conns -} - -func (p *SysConnectPool) MaxIdleConns() int { - return p.maxIdleConns -} - -// not implemented -func (p *SysConnectPool) SetMaxConns(conns int) { - p.maxConns = conns - // if support SetMaxOpenConns, go 1.2+, then set - if reflect.ValueOf(p.db).MethodByName("SetMaxOpenConns").IsValid() { - reflect.ValueOf(p.db).MethodByName("SetMaxOpenConns").Call([]reflect.Value{reflect.ValueOf(conns)}) - } - //p.db.SetMaxOpenConns(conns) -} - -// not implemented -func (p *SysConnectPool) MaxConns() int { - return p.maxConns -} - -// NewSimpleConnectPool new a SimpleConnectPool -func NewSimpleConnectPool() IConnectPool { - return &SimpleConnectPool{releasedConnects: make([]*sql.DB, 10), - usingConnects: map[*sql.DB]time.Time{}, - cur: -1, - maxWaitTimeOut: 14400, - maxIdleConns: 10, - mutex: &sync.Mutex{}, - } -} - -// Struct SimpleConnectPool is a simple implementation for IConnectPool. -// It's a custom connection pool and not use system connection pool. -// Opening or Closing a database connection must be enter a lock. -// This implements will be improved in furture. -type SimpleConnectPool struct { - releasedConnects []*sql.DB - cur int - usingConnects map[*sql.DB]time.Time - maxWaitTimeOut int - mutex *sync.Mutex - maxIdleConns int -} - -func (s *SimpleConnectPool) Init(engine *Engine) error { - return nil -} - -// RetrieveDB get a connection from connection pool -func (p *SimpleConnectPool) RetrieveDB(engine *Engine) (*sql.DB, error) { - p.mutex.Lock() - defer p.mutex.Unlock() - var db *sql.DB = nil - var err error = nil - //fmt.Printf("%x, rbegin - released:%v, using:%v\n", &p, p.cur+1, len(p.usingConnects)) - if p.cur < 0 { - db, err = engine.OpenDB() - if err != nil { - return nil, err - } - p.usingConnects[db] = time.Now() - } else { - db = p.releasedConnects[p.cur] - p.usingConnects[db] = time.Now() - p.releasedConnects[p.cur] = nil - p.cur = p.cur - 1 - } - - //fmt.Printf("%x, rend - released:%v, using:%v\n", &p, p.cur+1, len(p.usingConnects)) - return db, nil -} - -// ReleaseDB release a db from connection pool -func (p *SimpleConnectPool) ReleaseDB(engine *Engine, db *sql.DB) { - p.mutex.Lock() - defer p.mutex.Unlock() - //fmt.Printf("%x, lbegin - released:%v, using:%v\n", &p, p.cur+1, len(p.usingConnects)) - if p.cur >= p.maxIdleConns-1 { - db.Close() - } else { - p.cur = p.cur + 1 - p.releasedConnects[p.cur] = db - } - delete(p.usingConnects, db) - //fmt.Printf("%x, lend - released:%v, using:%v\n", &p, p.cur+1, len(p.usingConnects)) -} - -// Close release all db -func (p *SimpleConnectPool) Close(engine *Engine) error { - p.mutex.Lock() - defer p.mutex.Unlock() - for len(p.releasedConnects) > 0 { - p.releasedConnects[0].Close() - p.releasedConnects = p.releasedConnects[1:] - } - - return nil -} - -func (p *SimpleConnectPool) SetMaxIdleConns(conns int) { - p.maxIdleConns = conns -} - -func (p *SimpleConnectPool) MaxIdleConns() int { - return p.maxIdleConns -} - -// not implemented -func (p *SimpleConnectPool) SetMaxConns(conns int) { -} - -// not implemented -func (p *SimpleConnectPool) MaxConns() int { - return -1 -} diff --git a/postgres.go b/postgres.go deleted file mode 100644 index 4c7f97e2..00000000 --- a/postgres.go +++ /dev/null @@ -1,316 +0,0 @@ -package xorm - -import ( - "database/sql" - "errors" - "fmt" - "strconv" - "strings" -) - -type postgres struct { - base -} - -type values map[string]string - -func (vs values) Set(k, v string) { - vs[k] = v -} - -func (vs values) Get(k string) (v string) { - return vs[k] -} - -func errorf(s string, args ...interface{}) { - panic(fmt.Errorf("pq: %s", fmt.Sprintf(s, args...))) -} - -func parseOpts(name string, o values) { - if len(name) == 0 { - return - } - - name = strings.TrimSpace(name) - - ps := strings.Split(name, " ") - for _, p := range ps { - kv := strings.Split(p, "=") - if len(kv) < 2 { - errorf("invalid option: %q", p) - } - o.Set(kv[0], kv[1]) - } -} - -type postgresParser struct { -} - -func (p *postgresParser) parse(driverName, dataSourceName string) (*uri, error) { - db := &uri{dbType: POSTGRES} - o := make(values) - parseOpts(dataSourceName, o) - - db.dbName = o.Get("dbname") - if db.dbName == "" { - return nil, errors.New("dbname is empty") - } - return db, nil -} - -func (db *postgres) Init(drivername, uri string) error { - return db.base.init(&postgresParser{}, drivername, uri) -} - -func (db *postgres) SqlType(c *Column) string { - var res string - switch t := c.SQLType.Name; t { - case TinyInt: - res = SmallInt - return res - case MediumInt, Int, Integer: - if c.IsAutoIncrement { - return Serial - } - return Integer - case Serial, BigSerial: - c.IsAutoIncrement = true - c.Nullable = false - res = t - case Binary, VarBinary: - return Bytea - case DateTime: - res = TimeStamp - case TimeStampz: - return "timestamp with time zone" - case Float: - res = Real - case TinyText, MediumText, LongText: - res = Text - case Blob, TinyBlob, MediumBlob, LongBlob: - return Bytea - case Double: - return "DOUBLE PRECISION" - default: - if c.IsAutoIncrement { - return Serial - } - res = t - } - - var hasLen1 bool = (c.Length > 0) - var hasLen2 bool = (c.Length2 > 0) - if hasLen1 { - res += "(" + strconv.Itoa(c.Length) + ")" - } else if hasLen2 { - res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" - } - return res -} - -func (db *postgres) SupportInsertMany() bool { - return true -} - -func (db *postgres) QuoteStr() string { - return "\"" -} - -func (db *postgres) AutoIncrStr() string { - return "" -} - -func (db *postgres) SupportEngine() bool { - return false -} - -func (db *postgres) SupportCharset() bool { - return false -} - -func (db *postgres) IndexOnTable() bool { - return false -} - -func (db *postgres) IndexCheckSql(tableName, idxName string) (string, []interface{}) { - args := []interface{}{tableName, idxName} - return `SELECT indexname FROM pg_indexes ` + - `WHERE tablename = ? AND indexname = ?`, args -} - -func (db *postgres) TableCheckSql(tableName string) (string, []interface{}) { - args := []interface{}{tableName} - return `SELECT tablename FROM pg_tables WHERE tablename = ?`, args -} - -func (db *postgres) ColumnCheckSql(tableName, colName string) (string, []interface{}) { - args := []interface{}{tableName, colName} - return "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = ?" + - " AND column_name = ?", args -} - -func (db *postgres) GetColumns(tableName string) ([]string, map[string]*Column, error) { - args := []interface{}{tableName} - s := "SELECT column_name, column_default, is_nullable, data_type, character_maximum_length" + - ", numeric_precision, numeric_precision_radix FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = $1" - - 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 - } - cols := make(map[string]*Column) - colSeq := make([]string, 0) - for _, record := range res { - col := new(Column) - col.Indexes = make(map[string]bool) - for name, content := range record { - switch name { - case "column_name": - col.Name = strings.Trim(string(content), `" `) - case "column_default": - if strings.HasPrefix(string(content), "nextval") { - col.IsPrimaryKey = true - } else { - col.Default = string(content) - if col.Default == "" { - col.DefaultIsEmpty = true - } - } - case "is_nullable": - if string(content) == "YES" { - col.Nullable = true - } else { - col.Nullable = false - } - case "data_type": - ct := string(content) - switch ct { - case "character varying", "character": - col.SQLType = SQLType{Varchar, 0, 0} - case "timestamp without time zone": - col.SQLType = SQLType{DateTime, 0, 0} - case "timestamp with time zone": - col.SQLType = SQLType{TimeStampz, 0, 0} - case "double precision": - col.SQLType = SQLType{Double, 0, 0} - case "boolean": - col.SQLType = SQLType{Bool, 0, 0} - case "time without time zone": - col.SQLType = SQLType{Time, 0, 0} - default: - col.SQLType = SQLType{strings.ToUpper(ct), 0, 0} - } - if _, ok := sqlTypes[col.SQLType.Name]; !ok { - return nil, nil, errors.New(fmt.Sprintf("unkonw colType %v", ct)) - } - case "character_maximum_length": - i, err := strconv.Atoi(string(content)) - if err != nil { - return nil, nil, errors.New("retrieve length error") - } - col.Length = i - case "numeric_precision": - case "numeric_precision_radix": - } - } - if col.SQLType.IsText() { - if col.Default != "" { - col.Default = "'" + col.Default + "'" - }else{ - if col.DefaultIsEmpty { - col.Default = "''" - } - } - } - cols[col.Name] = col - colSeq = append(colSeq, col.Name) - } - - return colSeq, cols, nil -} - -func (db *postgres) GetTables() ([]*Table, error) { - args := []interface{}{} - s := "SELECT tablename FROM pg_tables where schemaname = 'public'" - 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 "tablename": - table.Name = string(content) - } - } - tables = append(tables, table) - } - return tables, nil -} - -func (db *postgres) GetIndexes(tableName string) (map[string]*Index, error) { - args := []interface{}{tableName} - s := "SELECT tablename, indexname, indexdef FROM pg_indexes WHERE schemaname = 'public' and tablename = $1" - - 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 { - var indexType int - var indexName string - var colNames []string - - for name, content := range record { - switch name { - case "indexname": - indexName = strings.Trim(string(content), `" `) - case "indexdef": - c := string(content) - if strings.HasPrefix(c, "CREATE UNIQUE INDEX") { - indexType = UniqueType - } else { - indexType = IndexType - } - cs := strings.Split(c, "(") - colNames = strings.Split(cs[1][0:len(cs[1])-1], ",") - } - } - if strings.HasSuffix(indexName, "_pkey") { - continue - } - if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { - newIdxName := indexName[5+len(tableName) : len(indexName)] - if newIdxName != "" { - indexName = newIdxName - } - } - - index := &Index{Name: indexName, Type: indexType, Cols: make([]string, 0)} - for _, colName := range colNames { - index.Cols = append(index.Cols, strings.Trim(colName, `" `)) - } - indexes[index.Name] = index - } - return indexes, nil -} diff --git a/postgres_dialect.go b/postgres_dialect.go new file mode 100644 index 00000000..943039e5 --- /dev/null +++ b/postgres_dialect.go @@ -0,0 +1,272 @@ +package xorm + +import ( + "errors" + "fmt" + "strconv" + "strings" + + "github.com/go-xorm/core" +) + +// func init() { +// RegisterDialect("postgres", &postgres{}) +// } + +type postgres struct { + core.Base +} + +func (db *postgres) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error { + return db.Base.Init(d, db, uri, drivername, dataSourceName) +} + +func (db *postgres) SqlType(c *core.Column) string { + var res string + switch t := c.SQLType.Name; t { + case core.TinyInt: + res = core.SmallInt + return res + case core.MediumInt, core.Int, core.Integer: + if c.IsAutoIncrement { + return core.Serial + } + return core.Integer + case core.Serial, core.BigSerial: + c.IsAutoIncrement = true + c.Nullable = false + res = t + case core.Binary, core.VarBinary: + return core.Bytea + case core.DateTime: + res = core.TimeStamp + case core.TimeStampz: + return "timestamp with time zone" + case core.Float: + res = core.Real + case core.TinyText, core.MediumText, core.LongText: + res = core.Text + case core.Blob, core.TinyBlob, core.MediumBlob, core.LongBlob: + return core.Bytea + case core.Double: + return "DOUBLE PRECISION" + default: + if c.IsAutoIncrement { + return core.Serial + } + res = t + } + + var hasLen1 bool = (c.Length > 0) + var hasLen2 bool = (c.Length2 > 0) + if hasLen1 { + res += "(" + strconv.Itoa(c.Length) + ")" + } else if hasLen2 { + res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" + } + return res +} + +func (db *postgres) SupportInsertMany() bool { + return true +} + +func (db *postgres) QuoteStr() string { + return "\"" +} + +func (db *postgres) AutoIncrStr() string { + return "" +} + +func (db *postgres) SupportEngine() bool { + return false +} + +func (db *postgres) SupportCharset() bool { + return false +} + +func (db *postgres) IndexOnTable() bool { + return false +} + +func (db *postgres) IndexCheckSql(tableName, idxName string) (string, []interface{}) { + args := []interface{}{tableName, idxName} + return `SELECT indexname FROM pg_indexes ` + + `WHERE tablename = ? AND indexname = ?`, args +} + +func (db *postgres) TableCheckSql(tableName string) (string, []interface{}) { + args := []interface{}{tableName} + return `SELECT tablename FROM pg_tables WHERE tablename = ?`, args +} + +func (db *postgres) ColumnCheckSql(tableName, colName string) (string, []interface{}) { + args := []interface{}{tableName, colName} + return "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = ?" + + " AND column_name = ?", args +} + +func (db *postgres) GetColumns(tableName string) ([]string, map[string]*core.Column, error) { + args := []interface{}{tableName} + s := "SELECT column_name, column_default, is_nullable, data_type, character_maximum_length" + + ", numeric_precision, numeric_precision_radix FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = $1" + + rows, err := db.DB().Query(s, args...) + if err != nil { + return nil, nil, err + } + defer rows.Close() + + cols := make(map[string]*core.Column) + colSeq := make([]string, 0) + + for rows.Next() { + col := new(core.Column) + col.Indexes = make(map[string]bool) + + var colName, isNullable, dataType string + var maxLenStr, colDefault, numPrecision, numRadix *string + err = rows.Scan(&colName, &colDefault, &isNullable, &dataType, &maxLenStr, &numPrecision, &numRadix) + if err != nil { + return nil, nil, err + } + + var maxLen int + if maxLenStr != nil { + maxLen, err = strconv.Atoi(*maxLenStr) + if err != nil { + return nil, nil, err + } + } + + col.Name = strings.Trim(colName, `" `) + + if colDefault != nil { + if strings.HasPrefix(*colDefault, "nextval") { + col.IsPrimaryKey = true + } else { + col.Default = *colDefault + } + } + + if isNullable == "YES" { + col.Nullable = true + } else { + col.Nullable = false + } + + switch dataType { + case "character varying", "character": + col.SQLType = core.SQLType{core.Varchar, 0, 0} + case "timestamp without time zone": + col.SQLType = core.SQLType{core.DateTime, 0, 0} + case "timestamp with time zone": + col.SQLType = core.SQLType{core.TimeStampz, 0, 0} + case "double precision": + col.SQLType = core.SQLType{core.Double, 0, 0} + case "boolean": + col.SQLType = core.SQLType{core.Bool, 0, 0} + case "time without time zone": + col.SQLType = core.SQLType{core.Time, 0, 0} + default: + col.SQLType = core.SQLType{strings.ToUpper(dataType), 0, 0} + } + if _, ok := core.SqlTypes[col.SQLType.Name]; !ok { + return nil, nil, errors.New(fmt.Sprintf("unkonw colType %v", dataType)) + } + + col.Length = maxLen + + if col.SQLType.IsText() { + if col.Default != "" { + col.Default = "'" + col.Default + "'" + } else { + if col.DefaultIsEmpty { + col.Default = "''" + } + } + } + cols[col.Name] = col + colSeq = append(colSeq, col.Name) + } + + return colSeq, cols, nil +} + +func (db *postgres) GetTables() ([]*core.Table, error) { + args := []interface{}{} + s := "SELECT tablename FROM pg_tables where schemaname = 'public'" + + rows, err := db.DB().Query(s, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + tables := make([]*core.Table, 0) + for rows.Next() { + table := core.NewEmptyTable() + var name string + err = rows.Scan(&name) + if err != nil { + return nil, err + } + table.Name = name + tables = append(tables, table) + } + return tables, nil +} + +func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error) { + args := []interface{}{tableName} + s := "SELECT indexname, indexdef FROM pg_indexes WHERE schemaname = 'public' and tablename = $1" + + rows, err := db.DB().Query(s, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + indexes := make(map[string]*core.Index, 0) + for rows.Next() { + var indexType int + var indexName, indexdef string + var colNames []string + err = rows.Scan(&indexName, &indexdef) + if err != nil { + return nil, err + } + indexName = strings.Trim(indexName, `" `) + + if strings.HasPrefix(indexdef, "CREATE UNIQUE INDEX") { + indexType = core.UniqueType + } else { + indexType = core.IndexType + } + cs := strings.Split(indexdef, "(") + colNames = strings.Split(cs[1][0:len(cs[1])-1], ",") + + if strings.HasSuffix(indexName, "_pkey") { + continue + } + if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { + newIdxName := indexName[5+len(tableName) : len(indexName)] + if newIdxName != "" { + indexName = newIdxName + } + } + + index := &core.Index{Name: indexName, Type: indexType, Cols: make([]string, 0)} + for _, colName := range colNames { + index.Cols = append(index.Cols, strings.Trim(colName, `" `)) + } + indexes[index.Name] = index + } + return indexes, nil +} + +func (db *postgres) Filters() []core.Filter { + return []core.Filter{&core.IdFilter{}, &core.QuoteFilter{}, &core.SeqFilter{"$", 1}} +} diff --git a/postgres_test.go b/postgres_test.go deleted file mode 100644 index 9657cffd..00000000 --- a/postgres_test.go +++ /dev/null @@ -1,241 +0,0 @@ -package xorm - -import ( - "database/sql" - "testing" - - _ "github.com/lib/pq" -) - -//var connStr string = "dbname=xorm_test user=lunny password=1234 sslmode=disable" - -var connStr string = "dbname=xorm_test sslmode=disable" - -func newPostgresEngine() (*Engine, error) { - return NewEngine("postgres", connStr) -} - -func newPostgresDriverDB() (*sql.DB, error) { - return sql.Open("postgres", connStr) -} - -func TestPostgres(t *testing.T) { - engine, err := newPostgresEngine() - if err != nil { - t.Error(err) - return - } - defer engine.Close() - engine.ShowSQL = showTestSql - engine.ShowErr = showTestSql - engine.ShowWarn = showTestSql - engine.ShowDebug = showTestSql - - testAll(engine, t) - testAll2(engine, t) - testAll3(engine, t) -} - -func TestPostgresWithCache(t *testing.T) { - engine, err := newPostgresEngine() - if err != nil { - t.Error(err) - return - } - engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) - defer engine.Close() - engine.ShowSQL = showTestSql - engine.ShowErr = showTestSql - engine.ShowWarn = showTestSql - engine.ShowDebug = showTestSql - - testAll(engine, t) - testAll2(engine, t) -} - -/* -func TestPostgres2(t *testing.T) { - engine, err := NewEngine("postgres", "dbname=xorm_test sslmode=disable") - if err != nil { - t.Error(err) - return - } - defer engine.Close() - engine.ShowSQL = showTestSql - engine.Mapper = SameMapper{} - - fmt.Println("-------------- directCreateTable --------------") - directCreateTable(engine, t) - fmt.Println("-------------- mapper --------------") - mapper(engine, t) - fmt.Println("-------------- insert --------------") - insert(engine, t) - fmt.Println("-------------- querySameMapper --------------") - querySameMapper(engine, t) - fmt.Println("-------------- execSameMapper --------------") - execSameMapper(engine, t) - fmt.Println("-------------- insertAutoIncr --------------") - insertAutoIncr(engine, t) - fmt.Println("-------------- insertMulti --------------") - insertMulti(engine, t) - fmt.Println("-------------- insertTwoTable --------------") - insertTwoTable(engine, t) - fmt.Println("-------------- updateSameMapper --------------") - updateSameMapper(engine, t) - fmt.Println("-------------- testdelete --------------") - testdelete(engine, t) - fmt.Println("-------------- get --------------") - get(engine, t) - fmt.Println("-------------- cascadeGet --------------") - cascadeGet(engine, t) - fmt.Println("-------------- find --------------") - find(engine, t) - fmt.Println("-------------- find2 --------------") - find2(engine, t) - fmt.Println("-------------- findMap --------------") - findMap(engine, t) - fmt.Println("-------------- findMap2 --------------") - findMap2(engine, t) - fmt.Println("-------------- count --------------") - count(engine, t) - fmt.Println("-------------- where --------------") - where(engine, t) - fmt.Println("-------------- in --------------") - in(engine, t) - fmt.Println("-------------- limit --------------") - limit(engine, t) - fmt.Println("-------------- orderSameMapper --------------") - orderSameMapper(engine, t) - fmt.Println("-------------- joinSameMapper --------------") - joinSameMapper(engine, t) - fmt.Println("-------------- havingSameMapper --------------") - havingSameMapper(engine, t) - fmt.Println("-------------- combineTransactionSameMapper --------------") - combineTransactionSameMapper(engine, t) - fmt.Println("-------------- table --------------") - table(engine, t) - fmt.Println("-------------- createMultiTables --------------") - createMultiTables(engine, t) - fmt.Println("-------------- tableOp --------------") - tableOp(engine, t) - fmt.Println("-------------- testColsSameMapper --------------") - testColsSameMapper(engine, t) - fmt.Println("-------------- testCharst --------------") - testCharst(engine, t) - fmt.Println("-------------- testStoreEngine --------------") - testStoreEngine(engine, t) - fmt.Println("-------------- testExtends --------------") - testExtends(engine, t) - fmt.Println("-------------- testColTypes --------------") - testColTypes(engine, t) - fmt.Println("-------------- testCustomType --------------") - testCustomType(engine, t) - fmt.Println("-------------- testCreatedAndUpdated --------------") - testCreatedAndUpdated(engine, t) - fmt.Println("-------------- testIndexAndUnique --------------") - testIndexAndUnique(engine, t) - fmt.Println("-------------- testMetaInfo --------------") - testMetaInfo(engine, t) - fmt.Println("-------------- testIterate --------------") - testIterate(engine, t) - fmt.Println("-------------- testStrangeName --------------") - testStrangeName(engine, t) - fmt.Println("-------------- testVersion --------------") - testVersion(engine, t) - fmt.Println("-------------- testDistinct --------------") - testDistinct(engine, t) - fmt.Println("-------------- testUseBool --------------") - testUseBool(engine, t) - fmt.Println("-------------- transaction --------------") - transaction(engine, t) -}*/ - -const ( - createTablePostgres = `CREATE TABLE IF NOT EXISTS "big_struct" ("id" SERIAL PRIMARY KEY NOT NULL, "name" VARCHAR(255) NULL, "title" VARCHAR(255) NULL, "age" VARCHAR(255) NULL, "alias" VARCHAR(255) NULL, "nick_name" VARCHAR(255) NULL);` - dropTablePostgres = `DROP TABLE IF EXISTS "big_struct";` -) - -func BenchmarkPostgresDriverInsert(t *testing.B) { - doBenchDriver(newPostgresDriverDB, createTablePostgres, dropTablePostgres, - doBenchDriverInsert, t) -} - -func BenchmarkPostgresDriverFind(t *testing.B) { - doBenchDriver(newPostgresDriverDB, createTablePostgres, dropTablePostgres, - doBenchDriverFind, t) -} - -func BenchmarkPostgresNoCacheInsert(t *testing.B) { - engine, err := newPostgresEngine() - - defer engine.Close() - if err != nil { - t.Error(err) - return - } - //engine.ShowSQL = true - doBenchInsert(engine, t) -} - -func BenchmarkPostgresNoCacheFind(t *testing.B) { - engine, err := newPostgresEngine() - - defer engine.Close() - if err != nil { - t.Error(err) - return - } - //engine.ShowSQL = true - doBenchFind(engine, t) -} - -func BenchmarkPostgresNoCacheFindPtr(t *testing.B) { - engine, err := newPostgresEngine() - - defer engine.Close() - if err != nil { - t.Error(err) - return - } - //engine.ShowSQL = true - doBenchFindPtr(engine, t) -} - -func BenchmarkPostgresCacheInsert(t *testing.B) { - engine, err := newPostgresEngine() - - defer engine.Close() - if err != nil { - t.Error(err) - return - } - engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) - - doBenchInsert(engine, t) -} - -func BenchmarkPostgresCacheFind(t *testing.B) { - engine, err := newPostgresEngine() - - defer engine.Close() - if err != nil { - t.Error(err) - return - } - engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) - - doBenchFind(engine, t) -} - -func BenchmarkPostgresCacheFindPtr(t *testing.B) { - engine, err := newPostgresEngine() - - defer engine.Close() - if err != nil { - t.Error(err) - return - } - engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) - - doBenchFindPtr(engine, t) -} diff --git a/pq_driver.go b/pq_driver.go new file mode 100644 index 00000000..a5bb6718 --- /dev/null +++ b/pq_driver.go @@ -0,0 +1,59 @@ +package xorm + +import ( + "errors" + "fmt" + "strings" + + "github.com/go-xorm/core" +) + +// func init() { +// core.RegisterDriver("postgres", &pqDriver{}) +// } + +type pqDriver struct { +} + +type values map[string]string + +func (vs values) Set(k, v string) { + vs[k] = v +} + +func (vs values) Get(k string) (v string) { + return vs[k] +} + +func errorf(s string, args ...interface{}) { + panic(fmt.Errorf("pq: %s", fmt.Sprintf(s, args...))) +} + +func parseOpts(name string, o values) { + if len(name) == 0 { + return + } + + name = strings.TrimSpace(name) + + ps := strings.Split(name, " ") + for _, p := range ps { + kv := strings.Split(p, "=") + if len(kv) < 2 { + errorf("invalid option: %q", p) + } + o.Set(kv[0], kv[1]) + } +} + +func (p *pqDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { + db := &core.Uri{DbType: core.POSTGRES} + o := make(values) + parseOpts(dataSourceName, o) + + db.DbName = o.Get("dbname") + if db.DbName == "" { + return nil, errors.New("dbname is empty") + } + return db, nil +} diff --git a/rows.go b/rows.go index 0ac6c956..99f724dd 100644 --- a/rows.go +++ b/rows.go @@ -4,14 +4,16 @@ import ( "database/sql" "fmt" "reflect" + + "github.com/go-xorm/core" ) type Rows struct { NoTypeCheck bool session *Session - stmt *sql.Stmt - rows *sql.Rows + stmt *core.Stmt + rows *core.Rows fields []string fieldsCount int beanType reflect.Type @@ -30,24 +32,23 @@ func newRows(session *Session, bean interface{}) (*Rows, error) { defer rows.session.Statement.Init() - var sql string + var sqlStr string var args []interface{} rows.session.Statement.RefTable = rows.session.Engine.autoMap(bean) if rows.session.Statement.RawSQL == "" { - sql, args = rows.session.Statement.genGetSql(bean) + sqlStr, args = rows.session.Statement.genGetSql(bean) } else { - sql = rows.session.Statement.RawSQL + sqlStr = rows.session.Statement.RawSQL args = rows.session.Statement.RawParams } - for _, filter := range rows.session.Engine.Filters { - sql = filter.Do(sql, session) + for _, filter := range rows.session.Engine.dialect.Filters() { + sqlStr = filter.Do(sqlStr, session.Engine.dialect, rows.session.Statement.RefTable) } - rows.session.Engine.LogSQL(sql) - rows.session.Engine.LogSQL(args) + rows.session.Engine.logSQL(sqlStr, args) - rows.stmt, err = rows.session.Db.Prepare(sql) + rows.stmt, err = rows.session.Db.Prepare(sqlStr) if err != nil { rows.lastError = err defer rows.Close() diff --git a/session.go b/session.go index aac0887b..feeda785 100644 --- a/session.go +++ b/session.go @@ -5,18 +5,21 @@ import ( "encoding/json" "errors" "fmt" + "hash/crc32" "reflect" "strconv" "strings" "time" + + "github.com/go-xorm/core" ) // Struct Session keep a pointer to sql.DB and provides all execution of all // kind of database operations. type Session struct { - Db *sql.DB + Db *core.DB Engine *Engine - Tx *sql.Tx + Tx *core.Tx Statement Statement IsAutoCommit bool IsCommitedOrRollbacked bool @@ -31,6 +34,8 @@ type Session struct { beforeClosures []func(interface{}) afterClosures []func(interface{}) + + stmtCache map[uint32]*core.Stmt //key: hash.Hash32 of (queryStr, len(queryStr)) } // Method Init reset the session as the init status. @@ -51,14 +56,17 @@ func (session *Session) Init() { // Method Close release the connection from pool func (session *Session) Close() { - defer func() { - if session.Db != nil { - session.Engine.Pool.ReleaseDB(session.Engine, session.Db) - session.Db = nil - session.Tx = nil - session.Init() - } - }() + for _, v := range session.stmtCache { + v.Close() + } + + if session.Db != nil { + //session.Engine.Pool.ReleaseDB(session.Engine, session.Db) + session.Db = nil + session.Tx = nil + session.stmtCache = nil + session.Init() + } } // Method Sql provides raw sql input parameter. When you have a complex SQL statement @@ -108,7 +116,7 @@ func (session *Session) After(closures func(interface{})) *Session { return session } -// Method Table can input a string or pointer to struct for special a table to operate. +// Method core.Table can input a string or pointer to struct for special a table to operate. func (session *Session) Table(tableNameOrBean interface{}) *Session { session.Statement.Table(tableNameOrBean) return session @@ -120,6 +128,12 @@ func (session *Session) In(column string, args ...interface{}) *Session { return session } +// Method In provides a query string like "count = count + 1" +func (session *Session) Incr(column string, arg ...interface{}) *Session { + session.Statement.Incr(column, arg...) + return session +} + // Method Cols provides some columns to special func (session *Session) Cols(columns ...string) *Session { session.Statement.Cols(columns...) @@ -141,11 +155,6 @@ func (session *Session) NoCascade() *Session { return session } -/* -func (session *Session) MustCols(columns ...string) *Session { - session.Statement.Must() -}*/ - // 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. @@ -259,11 +268,12 @@ func (session *Session) Having(conditions string) *Session { func (session *Session) newDb() error { if session.Db == nil { - db, err := session.Engine.Pool.RetrieveDB(session.Engine) + /*db, err := session.Engine.Pool.RetrieveDB(session.Engine) if err != nil { return err - } - session.Db = db + }*/ + session.Db = session.Engine.db + session.stmtCache = make(map[uint32]*core.Stmt, 0) } return nil } @@ -283,7 +293,7 @@ func (session *Session) Begin() error { session.IsCommitedOrRollbacked = false session.Tx = tx - session.Engine.LogSQL("BEGIN TRANSACTION") + session.Engine.logSQL("BEGIN TRANSACTION") } return nil } @@ -291,7 +301,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(session.Engine.dialect.RollBackStr()) + session.Engine.logSQL(session.Engine.dialect.RollBackStr()) session.IsCommitedOrRollbacked = true return session.Tx.Rollback() } @@ -301,7 +311,7 @@ func (session *Session) Rollback() error { // When using transaction, Commit will commit all operations. func (session *Session) Commit() error { if !session.IsAutoCommit && !session.IsCommitedOrRollbacked { - session.Engine.LogSQL("COMMIT") + session.Engine.logSQL("COMMIT") session.IsCommitedOrRollbacked = true var err error if err = session.Tx.Commit(); err == nil { @@ -363,14 +373,12 @@ func (session *Session) scanMapIntoStruct(obj interface{}, objMap map[string][]b return errors.New("Expected a pointer to a struct") } + var col *core.Column table := session.Engine.autoMapType(dataStruct) for key, data := range objMap { - key = strings.ToLower(key) - var col *Column - var ok bool - if col, ok = table.Columns[key]; !ok { - session.Engine.LogWarn(fmt.Sprintf("table %v's has not column %v. %v", table.Name, key, table.ColumnsSeq)) + if col = table.GetColumn(key); col == nil { + session.Engine.LogWarn(fmt.Sprintf("table %v's has not column %v. %v", table.Name, key, table.Columns())) continue } @@ -405,13 +413,13 @@ func (session *Session) scanMapIntoStruct(obj interface{}, objMap map[string][]b //Execute sql func (session *Session) innerExec(sqlStr string, args ...interface{}) (sql.Result, error) { - rs, err := session.Db.Prepare(sqlStr) + stmt, err := session.doPrepare(sqlStr) if err != nil { return nil, err } - defer rs.Close() + //defer stmt.Close() - res, err := rs.Exec(args...) + res, err := stmt.Exec(args...) if err != nil { return nil, err } @@ -419,12 +427,11 @@ func (session *Session) innerExec(sqlStr string, args ...interface{}) (sql.Resul } func (session *Session) exec(sqlStr string, args ...interface{}) (sql.Result, error) { - for _, filter := range session.Engine.Filters { - sqlStr = filter.Do(sqlStr, session) + for _, filter := range session.Engine.dialect.Filters() { + sqlStr = filter.Do(sqlStr, session.Engine.dialect, session.Statement.RefTable) } - session.Engine.LogSQL(sqlStr) - session.Engine.LogSQL(args) + session.Engine.logSQL(sqlStr, args) if session.IsAutoCommit { return session.innerExec(sqlStr, args...) @@ -592,6 +599,7 @@ func (statement *Statement) convertIdSql(sqlStr string) string { if len(sqls) != 2 { return "" } + //fmt.Println("-----", col) newsql := fmt.Sprintf("SELECT %v.%v FROM %v", statement.Engine.Quote(statement.TableName()), statement.Engine.Quote(col.Name), sqls[1]) return newsql @@ -605,25 +613,25 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf if session.Statement.RefTable == nil || len(session.Statement.RefTable.PrimaryKeys) != 1 { return false, ErrCacheFailed } - for _, filter := range session.Engine.Filters { - sqlStr = filter.Do(sqlStr, session) + for _, filter := range session.Engine.dialect.Filters() { + sqlStr = filter.Do(sqlStr, session.Engine.dialect, session.Statement.RefTable) } newsql := session.Statement.convertIdSql(sqlStr) if newsql == "" { return false, ErrCacheFailed } - cacher := session.Statement.RefTable.Cacher + cacher := session.Engine.getCacher2(session.Statement.RefTable) tableName := session.Statement.TableName() session.Engine.LogDebug("[xorm:cacheGet] find sql:", newsql, args) - ids, err := getCacheSql(cacher, tableName, newsql, args) + ids, err := core.GetCacheSql(cacher, tableName, newsql, args) if err != nil { resultsSlice, err := session.query(newsql, args...) if err != nil { return false, err } session.Engine.LogDebug("[xorm:cacheGet] query ids:", resultsSlice) - ids = make([]int64, 0) + ids = make([]core.PK, 0) if len(resultsSlice) > 0 { data := resultsSlice[0] var id int64 @@ -635,10 +643,10 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf return false, err } } - ids = append(ids, id) + ids = append(ids, core.PK{id}) } session.Engine.LogDebug("[xorm:cacheGet] cache ids:", newsql, ids) - err = putCacheSql(cacher, ids, tableName, newsql, args) + err = core.PutCacheSql(cacher, ids, tableName, newsql, args) if err != nil { return false, err } @@ -650,7 +658,11 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf structValue := reflect.Indirect(reflect.ValueOf(bean)) id := ids[0] session.Engine.LogDebug("[xorm:cacheGet] get bean:", tableName, id) - cacheBean := cacher.GetBean(tableName, id) + sid, err := id.ToString() + if err != nil { + return false, err + } + cacheBean := cacher.GetBean(tableName, sid) if cacheBean == nil { newSession := session.Engine.NewSession() defer newSession.Close() @@ -668,7 +680,7 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf } session.Engine.LogDebug("[xorm:cacheGet] cache bean:", tableName, id, cacheBean) - cacher.PutBean(tableName, id, cacheBean) + cacher.PutBean(tableName, sid, cacheBean) } else { session.Engine.LogDebug("[xorm:cacheGet] cached bean:", tableName, id, cacheBean) has = true @@ -688,8 +700,8 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in return ErrCacheFailed } - for _, filter := range session.Engine.Filters { - sqlStr = filter.Do(sqlStr, session) + for _, filter := range session.Engine.dialect.Filters() { + sqlStr = filter.Do(sqlStr, session.Engine.dialect, session.Statement.RefTable) } newsql := session.Statement.convertIdSql(sqlStr) @@ -698,8 +710,8 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in } table := session.Statement.RefTable - cacher := table.Cacher - ids, err := getCacheSql(cacher, session.Statement.TableName(), newsql, args) + cacher := session.Engine.getCacher2(table) + ids, err := core.GetCacheSql(cacher, session.Statement.TableName(), newsql, args) if err != nil { //session.Engine.LogError(err) resultsSlice, err := session.query(newsql, args...) @@ -713,7 +725,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in } tableName := session.Statement.TableName() - ids = make([]int64, 0) + ids = make([]core.PK, 0) if len(resultsSlice) > 0 { for _, data := range resultsSlice { //fmt.Println(data) @@ -726,11 +738,11 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in return err } } - ids = append(ids, id) + ids = append(ids, core.PK{id}) } } session.Engine.LogDebug("[xorm:cacheFind] cache ids:", ids, tableName, newsql, args) - err = putCacheSql(cacher, ids, tableName, newsql, args) + err = core.PutCacheSql(cacher, ids, tableName, newsql, args) if err != nil { return err } @@ -739,34 +751,32 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in } sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) - pkFieldName := session.Statement.RefTable.PKColumns()[0].FieldName + //pkFieldName := session.Statement.RefTable.PKColumns()[0].FieldName - ididxes := make(map[int64]int) - var ides []interface{} = make([]interface{}, 0) + ididxes := make(map[string]int) + var ides []core.PK = make([]core.PK, 0) var temps []interface{} = make([]interface{}, len(ids)) tableName := session.Statement.TableName() for idx, id := range ids { - bean := cacher.GetBean(tableName, id) + sid, err := id.ToString() + if err != nil { + return err + } + bean := cacher.GetBean(tableName, sid) if bean == nil { ides = append(ides, id) - ididxes[id] = idx + ididxes[sid] = idx } else { session.Engine.LogDebug("[xorm:cacheFind] cached bean:", tableName, id, bean) - pkField := reflect.Indirect(reflect.ValueOf(bean)).FieldByName(pkFieldName) - - var sid int64 - switch pkField.Type().Kind() { - case reflect.Int32, reflect.Int, reflect.Int64: - sid = pkField.Int() - case reflect.Uint, reflect.Uint32, reflect.Uint64: - sid = int64(pkField.Uint()) - default: - return ErrCacheFailed + pk := session.Engine.IdOf(bean) + xid, err := pk.ToString() + if err != nil { + return err } - if sid != id { - session.Engine.LogError("[xorm:cacheFind] error cache", id, sid, bean) + if sid != xid { + session.Engine.LogError("[xorm:cacheFind] error cache", xid, sid, bean) return ErrCacheFailed } temps[idx] = bean @@ -781,7 +791,19 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in beans := slices.Interface() //beans := reflect.New(sliceValue.Type()).Interface() //err = newSession.In("(id)", ides...).OrderBy(session.Statement.OrderStr).NoCache().Find(beans) - err = newSession.In("(id)", ides...).NoCache().Find(beans) + ff := make([][]interface{}, len(table.PrimaryKeys)) + for i, _ := range table.PrimaryKeys { + ff[i] = make([]interface{}, 0) + } + for _, ie := range ides { + for i, _ := range table.PrimaryKeys { + ff[i] = append(ff[i], ie[i]) + } + } + for i, name := range table.PrimaryKeys { + newSession.In(name, ff[i]...) + } + err = newSession.NoCache().Find(beans) if err != nil { return err } @@ -793,12 +815,16 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in rv = rv.Addr() } bean := rv.Interface() - id := reflect.Indirect(reflect.ValueOf(bean)).FieldByName(pkFieldName).Int() + id := session.Engine.IdOf(bean) + sid, err := id.ToString() + if err != nil { + return err + } //bean := vs.Index(i).Addr().Interface() - temps[ididxes[id]] = bean + temps[ididxes[sid]] = bean //temps[idxes[i]] = bean session.Engine.LogDebug("[xorm:cacheFind] cache bean:", tableName, id, bean) - cacher.PutBean(tableName, id, bean) + cacher.PutBean(tableName, sid, bean) } } @@ -815,16 +841,21 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in sliceValue.Set(reflect.Append(sliceValue, reflect.Indirect(reflect.ValueOf(bean)))) } } else if sliceValue.Kind() == reflect.Map { - var key int64 + var key core.PK if table.PrimaryKeys[0] != "" { key = ids[j] - } else { - key = int64(j) } - if t.Kind() == reflect.Ptr { - sliceValue.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(bean)) - } else { - sliceValue.SetMapIndex(reflect.ValueOf(key), reflect.Indirect(reflect.ValueOf(bean))) + + if len(key) == 1 { + ikey, err := strconv.ParseInt(fmt.Sprintf("%v", key[0]), 10, 64) + if err != nil { + return err + } + if t.Kind() == reflect.Ptr { + sliceValue.SetMapIndex(reflect.ValueOf(ikey), reflect.ValueOf(bean)) + } else { + sliceValue.SetMapIndex(reflect.ValueOf(ikey), reflect.Indirect(reflect.ValueOf(bean))) + } } } /*} else { @@ -877,6 +908,21 @@ func (session *Session) Iterate(bean interface{}, fun IterFunc) error { return nil } +func (session *Session) doPrepare(sqlStr string) (stmt *core.Stmt, err error) { + crc := crc32.ChecksumIEEE([]byte(sqlStr)) + // TODO try hash(sqlStr+len(sqlStr)) + var has bool + stmt, has = session.stmtCache[crc] + if !has { + stmt, err = session.Db.Prepare(sqlStr) + if err != nil { + return nil, err + } + session.stmtCache[crc] = stmt + } + return +} + // get retrieve one record from database, bean's non-empty fields // will be as conditions func (session *Session) Get(bean interface{}) (bool, error) { @@ -893,6 +939,7 @@ func (session *Session) Get(bean interface{}) (bool, error) { session.Statement.Limit(1) var sqlStr string var args []interface{} + session.Statement.RefTable = session.Engine.autoMap(bean) if session.Statement.RawSQL == "" { @@ -902,21 +949,21 @@ func (session *Session) Get(bean interface{}) (bool, error) { args = session.Statement.RawParams } - if session.Statement.RefTable.Cacher != nil && session.Statement.UseCache { + if cacher := session.Engine.getCacher2(session.Statement.RefTable); cacher != nil && session.Statement.UseCache { has, err := session.cacheGet(bean, sqlStr, args...) if err != ErrCacheFailed { return has, err } } - var rawRows *sql.Rows + var rawRows *core.Rows session.queryPreprocess(&sqlStr, args...) if session.IsAutoCommit { - stmt, err := session.Db.Prepare(sqlStr) + stmt, err := session.doPrepare(sqlStr) if err != nil { return false, err } - defer stmt.Close() + // defer stmt.Close() // !nashtsai! don't close due to stmt is cached and bounded to this session rawRows, err = stmt.Query(args...) } else { rawRows, err = session.Tx.Query(sqlStr, args...) @@ -1013,7 +1060,7 @@ func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) } sliceElementType := sliceValue.Type().Elem() - var table *Table + var table *core.Table if session.Statement.RefTable == nil { if sliceElementType.Kind() == reflect.Ptr { if sliceElementType.Elem().Kind() == reflect.Struct { @@ -1058,7 +1105,7 @@ func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) args = session.Statement.RawParams } - if table.Cacher != nil && + if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache && !session.Statement.IsDistinct { err = session.cacheFind(sliceElementType, sqlStr, rowsSlicePtr, args...) @@ -1070,8 +1117,8 @@ func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) } if sliceValue.Kind() != reflect.Map { - var rawRows *sql.Rows - var stmt *sql.Stmt + var rawRows *core.Rows + var stmt *core.Stmt session.queryPreprocess(&sqlStr, args...) // err = session.queryRows(&stmt, &rawRows, sqlStr, args...) @@ -1084,11 +1131,10 @@ func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) // defer rawRows.Close() if session.IsAutoCommit { - stmt, err = session.Db.Prepare(sqlStr) + stmt, err = session.doPrepare(sqlStr) if err != nil { return err } - defer stmt.Close() rawRows, err = stmt.Query(args...) } else { rawRows, err = session.Tx.Query(sqlStr, args...) @@ -1105,25 +1151,41 @@ func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) fieldsCount := len(fields) - for rawRows.Next() { - var newValue reflect.Value + var newElemFunc func() reflect.Value + if sliceElementType.Kind() == reflect.Ptr { + newElemFunc = func() reflect.Value { + return reflect.New(sliceElementType.Elem()) + } + } else { + newElemFunc = func() reflect.Value { + return reflect.New(sliceElementType) + } + } + + var sliceValueSetFunc func(*reflect.Value) + + if sliceValue.Kind() == reflect.Slice { if sliceElementType.Kind() == reflect.Ptr { - newValue = reflect.New(sliceElementType.Elem()) - } else { - newValue = reflect.New(sliceElementType) - } - err := session.row2Bean(rawRows, fields, fieldsCount, newValue.Interface()) - if err != nil { - return err - } - if sliceValue.Kind() == reflect.Slice { - if sliceElementType.Kind() == reflect.Ptr { + sliceValueSetFunc = func(newValue *reflect.Value) { sliceValue.Set(reflect.Append(sliceValue, reflect.ValueOf(newValue.Interface()))) - } else { + } + } else { + sliceValueSetFunc = func(newValue *reflect.Value) { sliceValue.Set(reflect.Append(sliceValue, reflect.Indirect(reflect.ValueOf(newValue.Interface())))) } } } + + for rawRows.Next() { + var newValue reflect.Value = newElemFunc() + if sliceValueSetFunc != nil { + err := session.row2Bean(rawRows, fields, fieldsCount, newValue.Interface()) + if err != nil { + return err + } + sliceValueSetFunc(&newValue) + } + } } else { resultsSlice, err := session.query(sqlStr, args...) if err != nil { @@ -1163,19 +1225,19 @@ func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) return nil } -func (session *Session) queryRows(rawStmt **sql.Stmt, rawRows **sql.Rows, sqlStr string, args ...interface{}) error { - var err error - if session.IsAutoCommit { - *rawStmt, err = session.Db.Prepare(sqlStr) - if err != nil { - return err - } - *rawRows, err = (*rawStmt).Query(args...) - } else { - *rawRows, err = session.Tx.Query(sqlStr, args...) - } - return err -} +// func (session *Session) queryRows(rawStmt **sql.Stmt, rawRows **sql.Rows, sqlStr string, args ...interface{}) error { +// var err error +// if session.IsAutoCommit { +// *rawStmt, err = session.doPrepare(sqlStr) +// if err != nil { +// return err +// } +// *rawRows, err = (*rawStmt).Query(args...) +// } else { +// *rawRows, err = session.Tx.Query(sqlStr, args...) +// } +// return err +// } // Test if database is ok func (session *Session) Ping() error { @@ -1249,9 +1311,9 @@ func (session *Session) isIndexExist2(tableName string, cols []string, unique bo for _, index := range indexes { if sliceEq(index.Cols, cols) { if unique { - return index.Type == UniqueType, nil + return index.Type == core.UniqueType, nil } else { - return index.Type == IndexType, nil + return index.Type == core.IndexType, nil } } } @@ -1269,7 +1331,7 @@ func (session *Session) addColumn(colName string) error { } //fmt.Println(session.Statement.RefTable) - col := session.Statement.RefTable.Columns[strings.ToLower(colName)] + col := session.Statement.RefTable.GetColumn(colName) sql, args := session.Statement.genAddColumnStr(col) _, err = session.exec(sql, args...) return err @@ -1284,10 +1346,10 @@ func (session *Session) addIndex(tableName, idxName string) error { if session.IsAutoClose { defer session.Close() } - //fmt.Println(idxName) - cols := session.Statement.RefTable.Indexes[idxName].Cols - sqlStr, args := session.Statement.genAddIndexStr(indexName(tableName, idxName), cols) - _, err = session.exec(sqlStr, args...) + index := session.Statement.RefTable.Indexes[idxName] + sqlStr := session.Engine.dialect.CreateIndexSql(tableName, index) + //genAddIndexStr(indexName(tableName, idxName), cols) + _, err = session.exec(sqlStr) return err } @@ -1301,9 +1363,9 @@ func (session *Session) addUnique(tableName, uqeName string) error { defer session.Close() } //fmt.Println(uqeName, session.Statement.RefTable.Uniques) - cols := session.Statement.RefTable.Indexes[uqeName].Cols - sqlStr, args := session.Statement.genAddUniqueStr(uniqueName(tableName, uqeName), cols) - _, err = session.exec(sqlStr, args...) + index := session.Statement.RefTable.Indexes[uqeName] + sqlStr := session.Engine.dialect.CreateIndexSql(tableName, index) + _, err = session.exec(sqlStr) return err } @@ -1330,12 +1392,12 @@ func (session *Session) dropAll() error { return nil } -func row2map(rows *sql.Rows, fields []string) (resultsMap map[string][]byte, err error) { +func row2map(rows *core.Rows, fields []string) (resultsMap map[string][]byte, err error) { result := make(map[string][]byte) - var scanResultContainers []interface{} + scanResultContainers := make([]interface{}, len(fields)) for i := 0; i < len(fields); i++ { var scanResultContainer interface{} - scanResultContainers = append(scanResultContainers, &scanResultContainer) + scanResultContainers[i] = &scanResultContainer } if err := rows.Scan(scanResultContainers...); err != nil { return nil, err @@ -1358,36 +1420,28 @@ func row2map(rows *sql.Rows, fields []string) (resultsMap map[string][]byte, err return result, nil } -func (session *Session) getField(dataStruct *reflect.Value, key string, table *Table) *reflect.Value { - key = strings.ToLower(key) - if _, ok := table.Columns[key]; !ok { - session.Engine.LogWarn(fmt.Sprintf("table %v's has not column %v. %v", table.Name, key, table.ColumnsSeq)) +func (session *Session) getField(dataStruct *reflect.Value, key string, table *core.Table) *reflect.Value { + var col *core.Column + if col = table.GetColumn(key); col == nil { + session.Engine.LogWarn(fmt.Sprintf("table %v's has not column %v. %v", table.Name, key, table.Columns())) return nil } - col := table.Columns[key] - fieldName := col.FieldName - fieldPath := strings.Split(fieldName, ".") - var fieldValue reflect.Value - if len(fieldPath) > 2 { - session.Engine.LogError("Unsupported mutliderive", fieldName) + + fieldValue, err := col.ValueOfV(dataStruct) + if err != nil { + session.Engine.LogError(err) return nil - } else if len(fieldPath) == 2 { - parentField := dataStruct.FieldByName(fieldPath[0]) - if parentField.IsValid() { - fieldValue = parentField.FieldByName(fieldPath[1]) - } - } else { - fieldValue = dataStruct.FieldByName(fieldName) } + if !fieldValue.IsValid() || !fieldValue.CanSet() { session.Engine.LogWarn("table %v's column %v is not valid or cannot set", table.Name, key) return nil } - return &fieldValue + return fieldValue } -func (session *Session) row2Bean(rows *sql.Rows, fields []string, fieldsCount int, bean interface{}) error { +func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount int, bean interface{}) error { dataStruct := rValue(bean) if dataStruct.Kind() != reflect.Struct { return errors.New("Expected a pointer to a struct") @@ -1395,10 +1449,10 @@ func (session *Session) row2Bean(rows *sql.Rows, fields []string, fieldsCount in table := session.Engine.autoMapType(dataStruct) - var scanResultContainers []interface{} - for i := 0; i < fieldsCount; i++ { + scanResultContainers := make([]interface{}, len(fields)) + for i := 0; i < len(fields); i++ { var scanResultContainer interface{} - scanResultContainers = append(scanResultContainers, &scanResultContainer) + scanResultContainers[i] = &scanResultContainer } if err := rows.Scan(scanResultContainers...); err != nil { return err @@ -1406,7 +1460,6 @@ func (session *Session) row2Bean(rows *sql.Rows, fields []string, fieldsCount in for ii, key := range fields { if fieldValue := session.getField(&dataStruct, key, table); fieldValue != nil { - rawValue := reflect.Indirect(reflect.ValueOf(scanResultContainers[ii])) //if row is null then ignore @@ -1415,7 +1468,7 @@ func (session *Session) row2Bean(rows *sql.Rows, fields []string, fieldsCount in continue } - if structConvert, ok := fieldValue.Addr().Interface().(Conversion); ok { + if structConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok { if data, err := value2Bytes(&rawValue); err == nil { structConvert.FromDB(data) } else { @@ -1441,7 +1494,7 @@ func (session *Session) row2Bean(rows *sql.Rows, fields []string, fieldsCount in x := reflect.New(fieldType) err := json.Unmarshal([]byte(vv.String()), x.Interface()) if err != nil { - session.Engine.LogSQL(err) + session.Engine.LogError(err) return err } fieldValue.Set(x.Elem()) @@ -1489,20 +1542,26 @@ func (session *Session) row2Bean(rows *sql.Rows, fields []string, fieldsCount in fieldValue.SetUint(uint64(vv.Int())) } case reflect.Struct: - if fieldType == reflect.TypeOf(c_TIME_DEFAULT) { - if rawValueType == reflect.TypeOf(c_TIME_DEFAULT) { + if fieldType == core.TimeType { + if rawValueType == core.TimeType { hasAssigned = true - if true { - t := vv.Interface().(time.Time) - f := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), - t.Minute(), t.Second(), t.Nanosecond(), session.Engine.TZLocation()) - fieldValue.Set(reflect.ValueOf(f)) - } else { - fieldValue.Set(vv) + + t := vv.Interface().(time.Time) + z, _ := t.Zone() + if len(z) == 0 || t.Year() == 0 { // !nashtsai! HACK tmp work around for lib/pq doesn't properly time with location + session.Engine.LogDebug("empty zone key[%v] : %v | zone: %v | location: %+v\n", key, t, z, *t.Location()) + tt := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), + t.Minute(), t.Second(), t.Nanosecond(), time.Local) + vv = reflect.ValueOf(tt) } - } else { - // TODO: - fmt.Println("=====unknow time type", rawValueType) + // !nashtsai! convert to engine location + t = vv.Interface().(time.Time).In(session.Engine.TZLocation) + vv = reflect.ValueOf(t) + fieldValue.Set(vv) + + // t = fieldValue.Interface().(time.Time) + // z, _ = t.Zone() + // session.Engine.LogDebug("fieldValue key[%v]: %v | zone: %v | location: %+v\n", key, t, z, *t.Location()) } } else if session.Statement.UseCascade { table := session.Engine.autoMapType(*fieldValue) @@ -1538,97 +1597,97 @@ func (session *Session) row2Bean(rows *sql.Rows, fields []string, fieldsCount in //typeStr := fieldType.String() switch fieldType { // following types case matching ptr's native type, therefore assign ptr directly - case reflect.TypeOf(&c_EMPTY_STRING): + case core.PtrStringType: if rawValueType.Kind() == reflect.String { x := vv.String() hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case reflect.TypeOf(&c_BOOL_DEFAULT): + case core.PtrBoolType: if rawValueType.Kind() == reflect.Bool { x := vv.Bool() hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case reflect.TypeOf(&c_TIME_DEFAULT): - if rawValueType == reflect.TypeOf(c_TIME_DEFAULT) { + case core.PtrTimeType: + if rawValueType == core.PtrTimeType { hasAssigned = true var x time.Time = rawValue.Interface().(time.Time) fieldValue.Set(reflect.ValueOf(&x)) } - case reflect.TypeOf(&c_FLOAT64_DEFAULT): + case core.PtrFloat64Type: if rawValueType.Kind() == reflect.Float64 { x := vv.Float() hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case reflect.TypeOf(&c_UINT64_DEFAULT): + case core.PtrUint64Type: if rawValueType.Kind() == reflect.Int64 { var x uint64 = uint64(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case reflect.TypeOf(&c_INT64_DEFAULT): + case core.PtrInt64Type: if rawValueType.Kind() == reflect.Int64 { x := vv.Int() hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case reflect.TypeOf(&c_FLOAT32_DEFAULT): + case core.PtrFloat32Type: if rawValueType.Kind() == reflect.Float64 { var x float32 = float32(vv.Float()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case reflect.TypeOf(&c_INT_DEFAULT): + case core.PtrIntType: if rawValueType.Kind() == reflect.Int64 { var x int = int(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case reflect.TypeOf(&c_INT32_DEFAULT): + case core.PtrInt32Type: if rawValueType.Kind() == reflect.Int64 { var x int32 = int32(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case reflect.TypeOf(&c_INT8_DEFAULT): + case core.PtrInt8Type: if rawValueType.Kind() == reflect.Int64 { var x int8 = int8(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case reflect.TypeOf(&c_INT16_DEFAULT): + case core.PtrInt16Type: if rawValueType.Kind() == reflect.Int64 { var x int16 = int16(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case reflect.TypeOf(&c_UINT_DEFAULT): + case core.PtrUintType: if rawValueType.Kind() == reflect.Int64 { var x uint = uint(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case reflect.TypeOf(&c_UINT32_DEFAULT): + case core.PtrUint32Type: if rawValueType.Kind() == reflect.Int64 { var x uint32 = uint32(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case reflect.TypeOf(&c_UINT8_DEFAULT): + case core.Uint8Type: if rawValueType.Kind() == reflect.Int64 { var x uint8 = uint8(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case reflect.TypeOf(&c_UINT16_DEFAULT): + case core.Uint16Type: if rawValueType.Kind() == reflect.Int64 { var x uint16 = uint16(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case reflect.TypeOf(&c_COMPLEX64_DEFAULT): + case core.Complex64Type: var x complex64 err := json.Unmarshal([]byte(vv.String()), &x) if err != nil { @@ -1637,7 +1696,7 @@ func (session *Session) row2Bean(rows *sql.Rows, fields []string, fieldsCount in fieldValue.Set(reflect.ValueOf(&x)) } hasAssigned = true - case reflect.TypeOf(&c_COMPLEX128_DEFAULT): + case core.Complex128Type: var x complex128 err := json.Unmarshal([]byte(vv.String()), &x) if err != nil { @@ -1655,7 +1714,7 @@ func (session *Session) row2Bean(rows *sql.Rows, fields []string, fieldsCount in if !hasAssigned { data, err := value2Bytes(&rawValue) if err == nil { - session.bytes2Value(table.Columns[strings.ToLower(key)], fieldValue, data) + session.bytes2Value(table.GetColumn(key), fieldValue, data) } else { session.Engine.LogError(err.Error()) } @@ -1667,12 +1726,11 @@ func (session *Session) row2Bean(rows *sql.Rows, fields []string, fieldsCount in } func (session *Session) queryPreprocess(sqlStr *string, paramStr ...interface{}) { - for _, filter := range session.Engine.Filters { - *sqlStr = filter.Do(*sqlStr, session) + for _, filter := range session.Engine.dialect.Filters() { + *sqlStr = filter.Do(*sqlStr, session.Engine.dialect, session.Statement.RefTable) } - session.Engine.LogSQL(*sqlStr) - session.Engine.LogSQL(paramStr) + session.Engine.logSQL(*sqlStr, paramStr) } func (session *Session) query(sqlStr string, paramStr ...interface{}) (resultsSlice []map[string][]byte, err error) { @@ -1684,7 +1742,7 @@ func (session *Session) query(sqlStr string, paramStr ...interface{}) (resultsSl return txQuery(session.Tx, sqlStr, paramStr...) } -func txQuery(tx *sql.Tx, sqlStr string, params ...interface{}) (resultsSlice []map[string][]byte, err error) { +func txQuery(tx *core.Tx, sqlStr string, params ...interface{}) (resultsSlice []map[string][]byte, err error) { rows, err := tx.Query(sqlStr, params...) if err != nil { return nil, err @@ -1694,7 +1752,7 @@ func txQuery(tx *sql.Tx, sqlStr string, params ...interface{}) (resultsSlice []m return rows2maps(rows) } -func query(db *sql.DB, sqlStr string, params ...interface{}) (resultsSlice []map[string][]byte, err error) { +func query(db *core.DB, sqlStr string, params ...interface{}) (resultsSlice []map[string][]byte, err error) { s, err := db.Prepare(sqlStr) if err != nil { return nil, err @@ -1785,7 +1843,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error colNames := make([]string, 0) colMultiPlaces := make([]string, 0) var args = make([]interface{}, 0) - cols := make([]*Column, 0) + cols := make([]*core.Column, 0) for i := 0; i < size; i++ { elemValue := sliceValue.Index(i).Interface() @@ -1803,12 +1861,12 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error // -- if i == 0 { - for _, col := range table.Columns { + for _, col := range table.Columns() { fieldValue := reflect.Indirect(reflect.ValueOf(elemValue)).FieldByName(col.FieldName) if col.IsAutoIncrement && fieldValue.Int() == 0 { continue } - if col.MapType == ONLYFROMDB { + if col.MapType == core.ONLYFROMDB { continue } if session.Statement.ColumnStr != "" { @@ -1836,7 +1894,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error if col.IsAutoIncrement && fieldValue.Int() == 0 { continue } - if col.MapType == ONLYFROMDB { + if col.MapType == core.ONLYFROMDB { continue } if session.Statement.ColumnStr != "" { @@ -1875,7 +1933,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error return 0, err } - if table.Cacher != nil && session.Statement.UseCache { + if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache { session.cacheInsert(session.Statement.TableName()) } @@ -1926,32 +1984,46 @@ func (session *Session) InsertMulti(rowsSlicePtr interface{}) (int64, error) { return session.innerInsertMulti(rowsSlicePtr) } -func (session *Session) byte2Time(col *Column, data []byte) (outTime time.Time, outErr error) { +func (session *Session) byte2Time(col *core.Column, data []byte) (outTime time.Time, outErr error) { sdata := strings.TrimSpace(string(data)) var x time.Time var err error if sdata == "0000-00-00 00:00:00" || sdata == "0001-01-01 00:00:00" { - } else if !strings.ContainsAny(sdata, "- :") { + } else if !strings.ContainsAny(sdata, "- :") { // !nashtsai! has only found that mymysql driver is using this for time type column // time stamp sd, err := strconv.ParseInt(sdata, 10, 64) if err == nil { x = time.Unix(0, sd) + // !nashtsai! HACK mymysql driver is casuing Local location being change to CHAT and cause wrong time conversion + x = x.In(time.UTC) + x = time.Date(x.Year(), x.Month(), x.Day(), x.Hour(), + x.Minute(), x.Second(), x.Nanosecond(), session.Engine.TZLocation) + session.Engine.LogDebug("time(0) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) + } else { + session.Engine.LogDebug("time(0) err key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) } } else if len(sdata) > 19 { - x, err = time.ParseInLocation(time.RFC3339Nano, sdata, session.Engine.TZLocation()) + + x, err = time.ParseInLocation(time.RFC3339Nano, sdata, session.Engine.TZLocation) + session.Engine.LogDebug("time(1) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) if err != nil { - x, err = time.ParseInLocation("2006-01-02 15:04:05.999999999", sdata, session.Engine.TZLocation()) + x, err = time.ParseInLocation("2006-01-02 15:04:05.999999999", sdata, session.Engine.TZLocation) + session.Engine.LogDebug("time(2) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) } if err != nil { - x, err = time.ParseInLocation("2006-01-02 15:04:05.9999999 Z07:00", sdata, session.Engine.TZLocation()) + x, err = time.ParseInLocation("2006-01-02 15:04:05.9999999 Z07:00", sdata, session.Engine.TZLocation) + session.Engine.LogDebug("time(3) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) } + } else if len(sdata) == 19 { - x, err = time.ParseInLocation("2006-01-02 15:04:05", sdata, session.Engine.TZLocation()) + x, err = time.ParseInLocation("2006-01-02 15:04:05", sdata, session.Engine.TZLocation) + session.Engine.LogDebug("time(4) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) } else if len(sdata) == 10 && sdata[4] == '-' && sdata[7] == '-' { - x, err = time.ParseInLocation("2006-01-02", sdata, session.Engine.TZLocation()) - } else if col.SQLType.Name == Time { + x, err = time.ParseInLocation("2006-01-02", sdata, session.Engine.TZLocation) + session.Engine.LogDebug("time(5) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) + } else if col.SQLType.Name == core.Time { if strings.Contains(sdata, " ") { ssd := strings.Split(sdata, " ") sdata = ssd[1] @@ -1959,13 +2031,14 @@ func (session *Session) byte2Time(col *Column, data []byte) (outTime time.Time, sdata = strings.TrimSpace(sdata) //fmt.Println(sdata) - if session.Engine.dialect.DBType() == MYSQL && len(sdata) > 8 { + if session.Engine.dialect.DBType() == core.MYSQL && len(sdata) > 8 { sdata = sdata[len(sdata)-8:] } //fmt.Println(sdata) st := fmt.Sprintf("2006-01-02 %v", sdata) - x, err = time.ParseInLocation("2006-01-02 15:04:05", st, session.Engine.TZLocation()) + x, err = time.ParseInLocation("2006-01-02 15:04:05", st, session.Engine.TZLocation) + session.Engine.LogDebug("time(6) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) } else { outErr = errors.New(fmt.Sprintf("unsupported time format %v", sdata)) return @@ -1979,8 +2052,8 @@ func (session *Session) byte2Time(col *Column, data []byte) (outTime time.Time, } // convert a db data([]byte) to a field value -func (session *Session) bytes2Value(col *Column, fieldValue *reflect.Value, data []byte) error { - if structConvert, ok := fieldValue.Addr().Interface().(Conversion); ok { +func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, data []byte) error { + if structConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok { return structConvert.FromDB(data) } @@ -2040,8 +2113,8 @@ func (session *Session) bytes2Value(col *Column, fieldValue *reflect.Value, data var x int64 var err error // for mysql, when use bit, it returned \x01 - if col.SQLType.Name == Bit && - session.Engine.dialect.DBType() == MYSQL { + if col.SQLType.Name == core.Bit && + session.Engine.dialect.DBType() == core.MYSQL { // !nashtsai! TODO dialect needs to provide conversion interface API if len(data) == 1 { x = int64(data[0]) } else { @@ -2077,7 +2150,7 @@ func (session *Session) bytes2Value(col *Column, fieldValue *reflect.Value, data fieldValue.SetUint(x) //Currently only support Time type case reflect.Struct: - if fieldType == reflect.TypeOf(c_TIME_DEFAULT) { + if fieldType == core.TimeType { x, err := session.byte2Time(col, data) if err != nil { return err @@ -2118,11 +2191,11 @@ func (session *Session) bytes2Value(col *Column, fieldValue *reflect.Value, data //typeStr := fieldType.String() switch fieldType { // case "*string": - case reflect.TypeOf(&c_EMPTY_STRING): + case core.PtrStringType: x := string(data) fieldValue.Set(reflect.ValueOf(&x)) // case "*bool": - case reflect.TypeOf(&c_BOOL_DEFAULT): + case core.PtrBoolType: d := string(data) v, err := strconv.ParseBool(d) if err != nil { @@ -2130,7 +2203,7 @@ func (session *Session) bytes2Value(col *Column, fieldValue *reflect.Value, data } fieldValue.Set(reflect.ValueOf(&v)) // case "*complex64": - case reflect.TypeOf(&c_COMPLEX64_DEFAULT): + case core.PtrComplex64Type: var x complex64 err := json.Unmarshal(data, &x) if err != nil { @@ -2139,7 +2212,7 @@ func (session *Session) bytes2Value(col *Column, fieldValue *reflect.Value, data } fieldValue.Set(reflect.ValueOf(&x)) // case "*complex128": - case reflect.TypeOf(&c_COMPLEX128_DEFAULT): + case core.PtrComplex128Type: var x complex128 err := json.Unmarshal(data, &x) if err != nil { @@ -2148,14 +2221,14 @@ func (session *Session) bytes2Value(col *Column, fieldValue *reflect.Value, data } fieldValue.Set(reflect.ValueOf(&x)) // case "*float64": - case reflect.TypeOf(&c_FLOAT64_DEFAULT): + case core.PtrFloat64Type: x, err := strconv.ParseFloat(string(data), 64) if err != nil { return fmt.Errorf("arg %v as float64: %s", key, err.Error()) } fieldValue.Set(reflect.ValueOf(&x)) // case "*float32": - case reflect.TypeOf(&c_FLOAT32_DEFAULT): + case core.PtrFloat32Type: var x float32 x1, err := strconv.ParseFloat(string(data), 32) if err != nil { @@ -2164,7 +2237,7 @@ func (session *Session) bytes2Value(col *Column, fieldValue *reflect.Value, data x = float32(x1) fieldValue.Set(reflect.ValueOf(&x)) // case "*time.Time": - case reflect.TypeOf(&c_TIME_DEFAULT): + case core.PtrTimeType: x, err := session.byte2Time(col, data) if err != nil { return err @@ -2172,7 +2245,7 @@ func (session *Session) bytes2Value(col *Column, fieldValue *reflect.Value, data v = x fieldValue.Set(reflect.ValueOf(&x)) // case "*uint64": - case reflect.TypeOf(&c_UINT64_DEFAULT): + case core.PtrUint64Type: var x uint64 x, err := strconv.ParseUint(string(data), 10, 64) if err != nil { @@ -2180,7 +2253,7 @@ func (session *Session) bytes2Value(col *Column, fieldValue *reflect.Value, data } fieldValue.Set(reflect.ValueOf(&x)) // case "*uint": - case reflect.TypeOf(&c_UINT_DEFAULT): + case core.PtrUintType: var x uint x1, err := strconv.ParseUint(string(data), 10, 64) if err != nil { @@ -2189,7 +2262,7 @@ func (session *Session) bytes2Value(col *Column, fieldValue *reflect.Value, data x = uint(x1) fieldValue.Set(reflect.ValueOf(&x)) // case "*uint32": - case reflect.TypeOf(&c_UINT32_DEFAULT): + case core.PtrUint32Type: var x uint32 x1, err := strconv.ParseUint(string(data), 10, 64) if err != nil { @@ -2198,7 +2271,7 @@ func (session *Session) bytes2Value(col *Column, fieldValue *reflect.Value, data x = uint32(x1) fieldValue.Set(reflect.ValueOf(&x)) // case "*uint8": - case reflect.TypeOf(&c_UINT8_DEFAULT): + case core.PtrUint8Type: var x uint8 x1, err := strconv.ParseUint(string(data), 10, 64) if err != nil { @@ -2207,7 +2280,7 @@ func (session *Session) bytes2Value(col *Column, fieldValue *reflect.Value, data x = uint8(x1) fieldValue.Set(reflect.ValueOf(&x)) // case "*uint16": - case reflect.TypeOf(&c_UINT16_DEFAULT): + case core.PtrUint16Type: var x uint16 x1, err := strconv.ParseUint(string(data), 10, 64) if err != nil { @@ -2216,13 +2289,13 @@ func (session *Session) bytes2Value(col *Column, fieldValue *reflect.Value, data x = uint16(x1) fieldValue.Set(reflect.ValueOf(&x)) // case "*int64": - case reflect.TypeOf(&c_INT64_DEFAULT): + case core.PtrInt64Type: sdata := string(data) var x int64 var err error // for mysql, when use bit, it returned \x01 - if col.SQLType.Name == Bit && - strings.Contains(session.Engine.DriverName, "mysql") { + if col.SQLType.Name == core.Bit && + strings.Contains(session.Engine.DriverName(), "mysql") { if len(data) == 1 { x = int64(data[0]) } else { @@ -2241,14 +2314,14 @@ func (session *Session) bytes2Value(col *Column, fieldValue *reflect.Value, data } fieldValue.Set(reflect.ValueOf(&x)) // case "*int": - case reflect.TypeOf(&c_INT_DEFAULT): + case core.PtrIntType: sdata := string(data) var x int var x1 int64 var err error // for mysql, when use bit, it returned \x01 - if col.SQLType.Name == Bit && - strings.Contains(session.Engine.DriverName, "mysql") { + if col.SQLType.Name == core.Bit && + strings.Contains(session.Engine.DriverName(), "mysql") { if len(data) == 1 { x = int(data[0]) } else { @@ -2270,14 +2343,14 @@ func (session *Session) bytes2Value(col *Column, fieldValue *reflect.Value, data } fieldValue.Set(reflect.ValueOf(&x)) // case "*int32": - case reflect.TypeOf(&c_INT32_DEFAULT): + case core.PtrInt32Type: sdata := string(data) var x int32 var x1 int64 var err error // for mysql, when use bit, it returned \x01 - if col.SQLType.Name == Bit && - strings.Contains(session.Engine.DriverName, "mysql") { + if col.SQLType.Name == core.Bit && + session.Engine.dialect.DBType() == core.MYSQL { if len(data) == 1 { x = int32(data[0]) } else { @@ -2299,14 +2372,14 @@ func (session *Session) bytes2Value(col *Column, fieldValue *reflect.Value, data } fieldValue.Set(reflect.ValueOf(&x)) // case "*int8": - case reflect.TypeOf(&c_INT8_DEFAULT): + case core.PtrInt8Type: sdata := string(data) var x int8 var x1 int64 var err error // for mysql, when use bit, it returned \x01 - if col.SQLType.Name == Bit && - strings.Contains(session.Engine.DriverName, "mysql") { + if col.SQLType.Name == core.Bit && + strings.Contains(session.Engine.DriverName(), "mysql") { if len(data) == 1 { x = int8(data[0]) } else { @@ -2328,14 +2401,14 @@ func (session *Session) bytes2Value(col *Column, fieldValue *reflect.Value, data } fieldValue.Set(reflect.ValueOf(&x)) // case "*int16": - case reflect.TypeOf(&c_INT16_DEFAULT): + case core.PtrInt16Type: sdata := string(data) var x int16 var x1 int64 var err error // for mysql, when use bit, it returned \x01 - if col.SQLType.Name == Bit && - strings.Contains(session.Engine.DriverName, "mysql") { + if col.SQLType.Name == core.Bit && + strings.Contains(session.Engine.DriverName(), "mysql") { if len(data) == 1 { x = int16(data[0]) } else { @@ -2367,9 +2440,9 @@ func (session *Session) bytes2Value(col *Column, fieldValue *reflect.Value, data } // convert a field value of a struct to interface for put into db -func (session *Session) value2Interface(col *Column, fieldValue reflect.Value) (interface{}, error) { +func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Value) (interface{}, error) { if fieldValue.CanAddr() { - if fieldConvert, ok := fieldValue.Addr().Interface().(Conversion); ok { + if fieldConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok { data, err := fieldConvert.ToDB() if err != nil { return 0, err @@ -2396,17 +2469,18 @@ func (session *Session) value2Interface(col *Column, fieldValue reflect.Value) ( switch k { case reflect.Bool: - if fieldValue.Bool() { + return fieldValue.Bool(), nil + /*if fieldValue.Bool() { return 1, nil } else { return 0, nil - } + }*/ case reflect.String: return fieldValue.String(), nil case reflect.Struct: - if fieldType == reflect.TypeOf(c_TIME_DEFAULT) { + if fieldType == core.TimeType { t := fieldValue.Interface().(time.Time) - if session.Engine.dialect.DBType() == MSSQL { + if session.Engine.dialect.DBType() == core.MSSQL { if t.IsZero() { return nil, nil } @@ -2432,7 +2506,7 @@ func (session *Session) value2Interface(col *Column, fieldValue reflect.Value) ( case reflect.Complex64, reflect.Complex128: bytes, err := json.Marshal(fieldValue.Interface()) if err != nil { - session.Engine.LogSQL(err) + session.Engine.LogError(err) return 0, err } return string(bytes), nil @@ -2444,7 +2518,7 @@ func (session *Session) value2Interface(col *Column, fieldValue reflect.Value) ( if col.SQLType.IsText() { bytes, err := json.Marshal(fieldValue.Interface()) if err != nil { - session.Engine.LogSQL(err) + session.Engine.LogError(err) return 0, err } return string(bytes), nil @@ -2457,7 +2531,7 @@ func (session *Session) value2Interface(col *Column, fieldValue reflect.Value) ( } else { bytes, err = json.Marshal(fieldValue.Interface()) if err != nil { - session.Engine.LogSQL(err) + session.Engine.LogError(err) return 0, err } } @@ -2485,7 +2559,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { } // -- - colNames, args, err := table.genCols(session, bean, false, false) + colNames, args, err := genCols(table, session, bean, false, false) if err != nil { return 0, err } @@ -2535,7 +2609,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { // for postgres, many of them didn't implement lastInsertId, so we should // implemented it ourself. - if session.Engine.DriverName != POSTGRES || table.AutoIncrement == "" { + if session.Engine.DriverName() != core.POSTGRES || table.AutoIncrement == "" { res, err := session.exec(sqlStr, args...) if err != nil { return 0, err @@ -2543,13 +2617,15 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { handleAfterInsertProcessorFunc(bean) } - if table.Cacher != nil && session.Statement.UseCache { + if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache { session.cacheInsert(session.Statement.TableName()) } if table.Version != "" && session.Statement.checkVersion { - verValue := table.VersionColumn().ValueOf(bean) - if verValue.IsValid() && verValue.CanSet() { + verValue, err := table.VersionColumn().ValueOf(bean) + if err != nil { + session.Engine.LogError(err) + } else if verValue.IsValid() && verValue.CanSet() { verValue.SetInt(1) } } @@ -2564,8 +2640,12 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { return res.RowsAffected() } - aiValue := table.AutoIncrColumn().ValueOf(bean) - if !aiValue.IsValid() /*|| aiValue.Int() != 0*/ || !aiValue.CanSet() { + aiValue, err := table.AutoIncrColumn().ValueOf(bean) + if err != nil { + session.Engine.LogError(err) + } + + if aiValue == nil || !aiValue.IsValid() /*|| aiValue.Int() != 0*/ || !aiValue.CanSet() { return res.RowsAffected() } @@ -2596,13 +2676,15 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { handleAfterInsertProcessorFunc(bean) } - if table.Cacher != nil && session.Statement.UseCache { + if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache { session.cacheInsert(session.Statement.TableName()) } if table.Version != "" && session.Statement.checkVersion { - verValue := table.VersionColumn().ValueOf(bean) - if verValue.IsValid() && verValue.CanSet() { + verValue, err := table.VersionColumn().ValueOf(bean) + if err != nil { + session.Engine.LogError(err) + } else if verValue.IsValid() && verValue.CanSet() { verValue.SetInt(1) } } @@ -2617,8 +2699,12 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { return 1, err } - aiValue := table.AutoIncrColumn().ValueOf(bean) - if !aiValue.IsValid() /*|| aiValue. != 0*/ || !aiValue.CanSet() { + aiValue, err := table.AutoIncrColumn().ValueOf(bean) + if err != nil { + session.Engine.LogError(err) + } + + if aiValue == nil || !aiValue.IsValid() /*|| aiValue. != 0*/ || !aiValue.CanSet() { return 1, nil } @@ -2675,9 +2761,9 @@ func (statement *Statement) convertUpdateSql(sqlStr string) (string, string) { //TODO: for postgres only, if any other database? var paraStr string - if statement.Engine.dialect.DBType() == POSTGRES { + if statement.Engine.dialect.DBType() == core.POSTGRES { paraStr = "$" - } else if statement.Engine.dialect.DBType() == MSSQL { + } else if statement.Engine.dialect.DBType() == core.MSSQL { paraStr = ":" } @@ -2703,7 +2789,7 @@ func (session *Session) cacheInsert(tables ...string) error { } table := session.Statement.RefTable - cacher := table.Cacher + cacher := session.Engine.getCacher2(table) for _, t := range tables { session.Engine.LogDebug("cache clear:", t) @@ -2722,8 +2808,8 @@ func (session *Session) cacheUpdate(sqlStr string, args ...interface{}) error { if newsql == "" { return ErrCacheFailed } - for _, filter := range session.Engine.Filters { - newsql = filter.Do(newsql, session) + for _, filter := range session.Engine.dialect.Filters() { + newsql = filter.Do(newsql, session.Engine.dialect, session.Statement.RefTable) } session.Engine.LogDebug("[xorm:cacheUpdate] new sql", oldhead, newsql) @@ -2737,10 +2823,10 @@ func (session *Session) cacheUpdate(sqlStr string, args ...interface{}) error { } } table := session.Statement.RefTable - cacher := table.Cacher + cacher := session.Engine.getCacher2(table) tableName := session.Statement.TableName() session.Engine.LogDebug("[xorm:cacheUpdate] get cache sql", newsql, args[nStart:]) - ids, err := getCacheSql(cacher, tableName, newsql, args[nStart:]) + ids, err := core.GetCacheSql(cacher, tableName, newsql, args[nStart:]) if err != nil { resultsSlice, err := session.query(newsql, args[nStart:]...) if err != nil { @@ -2748,7 +2834,7 @@ func (session *Session) cacheUpdate(sqlStr string, args ...interface{}) error { } session.Engine.LogDebug("[xorm:cacheUpdate] find updated id", resultsSlice) - ids = make([]int64, 0) + ids = make([]core.PK, 0) if len(resultsSlice) > 0 { for _, data := range resultsSlice { var id int64 @@ -2760,7 +2846,7 @@ func (session *Session) cacheUpdate(sqlStr string, args ...interface{}) error { return err } } - ids = append(ids, id) + ids = append(ids, core.PK{id}) } } } /*else { @@ -2769,7 +2855,11 @@ func (session *Session) cacheUpdate(sqlStr string, args ...interface{}) error { }*/ for _, id := range ids { - if bean := cacher.GetBean(tableName, id); bean != nil { + sid, err := id.ToString() + if err != nil { + return err + } + if bean := cacher.GetBean(tableName, sid); bean != nil { sqls := splitNNoCase(sqlStr, "where", 2) if len(sqls) == 0 || len(sqls) > 2 { return ErrCacheFailed @@ -2793,13 +2883,17 @@ func (session *Session) cacheUpdate(sqlStr string, args ...interface{}) error { return ErrCacheFailed } - if col, ok := table.Columns[strings.ToLower(colName)]; ok { - fieldValue := col.ValueOf(bean) - session.Engine.LogDebug("[xorm:cacheUpdate] set bean field", bean, colName, fieldValue.Interface()) - if col.IsVersion && session.Statement.checkVersion { - fieldValue.SetInt(fieldValue.Int() + 1) + if col := table.GetColumn(colName); col != nil { + fieldValue, err := col.ValueOf(bean) + if err != nil { + session.Engine.LogError(err) } else { - fieldValue.Set(reflect.ValueOf(args[idx])) + session.Engine.LogDebug("[xorm:cacheUpdate] set bean field", bean, colName, fieldValue.Interface()) + if col.IsVersion && session.Statement.checkVersion { + fieldValue.SetInt(fieldValue.Int() + 1) + } else { + fieldValue.Set(reflect.ValueOf(args[idx])) + } } } else { session.Engine.LogError("[xorm:cacheUpdate] ERROR: column %v is not table %v's", @@ -2808,7 +2902,7 @@ func (session *Session) cacheUpdate(sqlStr string, args ...interface{}) error { } session.Engine.LogDebug("[xorm:cacheUpdate] update cache", tableName, id, bean) - cacher.PutBean(tableName, id, bean) + cacher.PutBean(tableName, sid, bean) } } session.Engine.LogDebug("[xorm:cacheUpdate] clear cached table sql:", tableName) @@ -2836,7 +2930,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 var colNames []string var args []interface{} - var table *Table + var table *core.Table // handle before update processors for _, closure := range session.beforeClosures { @@ -2857,7 +2951,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 false, false, session.Statement.allUseBool, session.Statement.useAllCols, session.Statement.mustColumnMap) } else { - colNames, args, err = table.genCols(session, bean, true, true) + colNames, args, err = genCols(table, session, bean, true, true) if err != nil { return 0, err } @@ -2881,9 +2975,15 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 if session.Statement.UseAutoTime && table.Updated != "" { colNames = append(colNames, session.Engine.Quote(table.Updated)+" = ?") - args = append(args, session.Engine.NowTime(table.Columns[strings.ToLower(table.Updated)].SQLType.Name)) + args = append(args, session.Engine.NowTime(table.Columns()[strings.ToLower(table.Updated)].SQLType.Name)) } + //for update action to like "column = column + ?" + incColumns := session.Statement.getInc() + for _, v := range incColumns { + colNames = append(colNames, session.Engine.Quote(v.colName)+" = "+session.Engine.Quote(v.colName)+" + ?") + args = append(args, v.arg) + } var condiColNames []string var condiArgs []interface{} @@ -2914,7 +3014,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 var sqlStr, inSql string var inArgs []interface{} doIncVer := false - var verValue reflect.Value + var verValue *reflect.Value if table.Version != "" && session.Statement.checkVersion { if condition != "" { condition = fmt.Sprintf("WHERE (%v) AND %v = ?", condition, @@ -2937,10 +3037,10 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 session.Engine.Quote(table.Version)+" = "+session.Engine.Quote(table.Version)+" + 1", condition) - verValue = table.VersionColumn().ValueOf(bean) - //if err != nil { - // return 0, err - //} + verValue, err = table.VersionColumn().ValueOf(bean) + if err != nil { + return 0, err + } condiArgs = append(condiArgs, verValue.Interface()) doIncVer = true @@ -2974,10 +3074,10 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 verValue.SetInt(verValue.Int() + 1) } - if table.Cacher != nil && session.Statement.UseCache { + if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache { //session.cacheUpdate(sqlStr, args...) - table.Cacher.ClearIds(session.Statement.TableName()) - table.Cacher.ClearBeans(session.Statement.TableName()) + cacher.ClearIds(session.Statement.TableName()) + cacher.ClearBeans(session.Statement.TableName()) } // handle after update processors @@ -3017,8 +3117,8 @@ func (session *Session) cacheDelete(sqlStr string, args ...interface{}) error { return ErrCacheFailed } - for _, filter := range session.Engine.Filters { - sqlStr = filter.Do(sqlStr, session) + for _, filter := range session.Engine.dialect.Filters() { + sqlStr = filter.Do(sqlStr, session.Engine.dialect, session.Statement.RefTable) } newsql := session.Statement.convertIdSql(sqlStr) @@ -3026,15 +3126,15 @@ func (session *Session) cacheDelete(sqlStr string, args ...interface{}) error { return ErrCacheFailed } - cacher := session.Statement.RefTable.Cacher + cacher := session.Engine.getCacher2(session.Statement.RefTable) tableName := session.Statement.TableName() - ids, err := getCacheSql(cacher, tableName, newsql, args) + ids, err := core.GetCacheSql(cacher, tableName, newsql, args) if err != nil { resultsSlice, err := session.query(newsql, args...) if err != nil { return err } - ids = make([]int64, 0) + ids = make([]core.PK, 0) if len(resultsSlice) > 0 { for _, data := range resultsSlice { var id int64 @@ -3046,7 +3146,7 @@ func (session *Session) cacheDelete(sqlStr string, args ...interface{}) error { return err } } - ids = append(ids, id) + ids = append(ids, core.PK{id}) } } } /*else { @@ -3056,7 +3156,11 @@ func (session *Session) cacheDelete(sqlStr string, args ...interface{}) error { for _, id := range ids { session.Engine.LogDebug("[xorm:cacheDelete] delete cache obj", tableName, id) - cacher.DelBean(tableName, id) + sid, err := id.ToString() + if err != nil { + return err + } + cacher.DelBean(tableName, sid) } session.Engine.LogDebug("[xorm:cacheDelete] clear cache table", tableName) cacher.ClearIds(tableName) @@ -3119,7 +3223,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) { args = append(session.Statement.Params, args...) - if table.Cacher != nil && session.Statement.UseCache { + if cacher := session.Engine.getCacher2(session.Statement.RefTable); cacher != nil && session.Statement.UseCache { session.cacheDelete(sqlStr, args...) } @@ -3158,3 +3262,74 @@ func (session *Session) Delete(bean interface{}) (int64, error) { return res.RowsAffected() } + +func genCols(table *core.Table, session *Session, bean interface{}, useCol bool, includeQuote bool) ([]string, []interface{}, error) { + colNames := make([]string, 0) + args := make([]interface{}, 0) + + for _, col := range table.Columns() { + lColName := strings.ToLower(col.Name) + if useCol && !col.IsVersion && !col.IsCreated && !col.IsUpdated { + if _, ok := session.Statement.columnMap[lColName]; !ok { + continue + } + } + if col.MapType == core.ONLYFROMDB { + continue + } + + fieldValuePtr, err := col.ValueOf(bean) + if err != nil { + session.Engine.LogError(err) + continue + } + fieldValue := *fieldValuePtr + + if col.IsAutoIncrement { + switch fieldValue.Type().Kind() { + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64: + if fieldValue.Int() == 0 { + continue + } + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64: + if fieldValue.Uint() == 0 { + continue + } + case reflect.String: + if len(fieldValue.String()) == 0 { + continue + } + } + } + + if session.Statement.ColumnStr != "" { + if _, ok := session.Statement.columnMap[lColName]; !ok { + continue + } + } + if session.Statement.OmitStr != "" { + if _, ok := session.Statement.columnMap[lColName]; ok { + continue + } + } + + if (col.IsCreated || col.IsUpdated) && session.Statement.UseAutoTime { + args = append(args, time.Now()) + } else if col.IsVersion && session.Statement.checkVersion { + args = append(args, 1) + } else { + arg, err := session.value2Interface(col, fieldValue) + if err != nil { + return colNames, args, err + } + args = append(args, arg) + } + + if includeQuote { + colNames = append(colNames, session.Engine.Quote(col.Name)+" = ?") + } else { + colNames = append(colNames, col.Name) + } + } + return colNames, args, nil +} diff --git a/sqlite3.go b/sqlite3_dialect.go similarity index 60% rename from sqlite3.go rename to sqlite3_dialect.go index d52b2966..0e19f96c 100644 --- a/sqlite3.go +++ b/sqlite3_dialect.go @@ -1,46 +1,44 @@ package xorm import ( - "database/sql" "strings" + + "github.com/go-xorm/core" ) +// func init() { +// RegisterDialect("sqlite3", &sqlite3{}) +// } + type sqlite3 struct { - base + core.Base } -type sqlite3Parser struct { +func (db *sqlite3) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error { + return db.Base.Init(d, db, uri, drivername, dataSourceName) } -func (p *sqlite3Parser) parse(driverName, dataSourceName string) (*uri, error) { - return &uri{dbType: SQLITE, dbName: dataSourceName}, nil -} - -func (db *sqlite3) Init(drivername, dataSourceName string) error { - return db.base.init(&sqlite3Parser{}, drivername, dataSourceName) -} - -func (db *sqlite3) SqlType(c *Column) string { +func (db *sqlite3) SqlType(c *core.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: + case core.Date, core.DateTime, core.TimeStamp, core.Time: + return core.Numeric + case core.TimeStampz: + return core.Text + case core.Char, core.Varchar, core.TinyText, core.Text, core.MediumText, core.LongText: + return core.Text + case core.Bit, core.TinyInt, core.SmallInt, core.MediumInt, core.Int, core.Integer, core.BigInt, core.Bool: + return core.Integer + case core.Float, core.Double, core.Real: + return core.Real + case core.Decimal, core.Numeric: + return core.Numeric + case core.TinyBlob, core.Blob, core.MediumBlob, core.LongBlob, core.Bytea, core.Binary, core.VarBinary: + return core.Blob + case core.Serial, core.BigSerial: c.IsPrimaryKey = true c.IsAutoIncrement = true c.Nullable = false - return Integer + return core.Integer default: return t } @@ -86,36 +84,32 @@ func (db *sqlite3) ColumnCheckSql(tableName, colName string) (string, []interfac return sql, args } -func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*Column, error) { +func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*core.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) - } + rows, err := db.DB().Query(s, args...) + if err != nil { + return nil, nil, err + } + defer rows.Close() + + var name string + for rows.Next() { + err = rows.Scan(&name) + if err != nil { + return nil, nil, err } } - nStart := strings.Index(sql, "(") - nEnd := strings.Index(sql, ")") - colCreates := strings.Split(sql[nStart+1:nEnd], ",") - cols := make(map[string]*Column) + nStart := strings.Index(name, "(") + nEnd := strings.Index(name, ")") + colCreates := strings.Split(name[nStart+1:nEnd], ",") + cols := make(map[string]*core.Column) colSeq := make([]string, 0) for _, colStr := range colCreates { fields := strings.Fields(strings.TrimSpace(colStr)) - col := new(Column) + col := new(core.Column) col.Indexes = make(map[string]bool) col.Nullable = true for idx, field := range fields { @@ -123,7 +117,7 @@ func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*Column, e col.Name = strings.Trim(field, "`[] ") continue } else if idx == 1 { - col.SQLType = SQLType{field, 0, 0} + col.SQLType = core.SQLType{field, 0, 0} } switch field { case "PRIMARY": @@ -144,28 +138,22 @@ func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*Column, e return colSeq, cols, nil } -func (db *sqlite3) GetTables() ([]*Table, error) { +func (db *sqlite3) GetTables() ([]*core.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...) + rows, err := db.DB().Query(s, args...) if err != nil { return nil, err } + defer rows.Close() - tables := make([]*Table, 0) - for _, record := range res { - table := new(Table) - for name, content := range record { - switch name { - case "name": - table.Name = string(content) - } + tables := make([]*core.Table, 0) + for rows.Next() { + table := core.NewEmptyTable() + err = rows.Scan(&table.Name) + if err != nil { + return nil, err } if table.Name == "sqlite_sequence" { continue @@ -175,28 +163,29 @@ func (db *sqlite3) GetTables() ([]*Table, error) { return tables, nil } -func (db *sqlite3) GetIndexes(tableName string) (map[string]*Index, error) { +func (db *sqlite3) GetIndexes(tableName string) (map[string]*core.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"]) + rows, err := db.DB().Query(s, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + indexes := make(map[string]*core.Index, 0) + for rows.Next() { + var sql string + err = rows.Scan(&sql) + if err != nil { + return nil, err + } if sql == "" { continue } + index := new(core.Index) nNStart := strings.Index(sql, "INDEX") nNEnd := strings.Index(sql, "ON") if nNStart == -1 || nNEnd == -1 { @@ -212,9 +201,9 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*Index, error) { } if strings.HasPrefix(sql, "CREATE UNIQUE INDEX") { - index.Type = UniqueType + index.Type = core.UniqueType } else { - index.Type = IndexType + index.Type = core.IndexType } nStart := strings.Index(sql, "(") @@ -230,3 +219,7 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*Index, error) { return indexes, nil } + +func (db *sqlite3) Filters() []core.Filter { + return []core.Filter{&core.IdFilter{}} +} diff --git a/sqlite3_driver.go b/sqlite3_driver.go new file mode 100644 index 00000000..2ecd9edf --- /dev/null +++ b/sqlite3_driver.go @@ -0,0 +1,16 @@ +package xorm + +import ( + "github.com/go-xorm/core" +) + +// func init() { +// core.RegisterDriver("sqlite3", &sqlite3Driver{}) +// } + +type sqlite3Driver struct { +} + +func (p *sqlite3Driver) Parse(driverName, dataSourceName string) (*core.Uri, error) { + return &core.Uri{DbType: core.SQLITE, DbName: dataSourceName}, nil +} diff --git a/sqlite3_test.go b/sqlite3_test.go deleted file mode 100644 index b55702b0..00000000 --- a/sqlite3_test.go +++ /dev/null @@ -1,140 +0,0 @@ -package xorm - -import ( - "database/sql" - "os" - "testing" - - _ "github.com/mattn/go-sqlite3" -) - -func newSqlite3Engine() (*Engine, error) { - os.Remove("./test.db") - return NewEngine("sqlite3", "./test.db") -} - -func newSqlite3DriverDB() (*sql.DB, error) { - os.Remove("./test.db") - return sql.Open("sqlite3", "./test.db") -} - -func TestSqlite3(t *testing.T) { - engine, err := newSqlite3Engine() - 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 TestSqlite3WithCache(t *testing.T) { - engine, err := newSqlite3Engine() - 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 ( - createTableSqlite3 = "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);" - dropTableSqlite3 = "DROP TABLE IF EXISTS `big_struct`;" -) - -func BenchmarkSqlite3DriverInsert(t *testing.B) { - doBenchDriver(newSqlite3DriverDB, createTableSqlite3, dropTableSqlite3, - doBenchDriverInsert, t) -} - -func BenchmarkSqlite3DriverFind(t *testing.B) { - doBenchDriver(newSqlite3DriverDB, createTableSqlite3, dropTableSqlite3, - doBenchDriverFind, t) -} - -func BenchmarkSqlite3NoCacheInsert(t *testing.B) { - t.StopTimer() - engine, err := newSqlite3Engine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - //engine.ShowSQL = true - doBenchInsert(engine, t) -} - -func BenchmarkSqlite3NoCacheFind(t *testing.B) { - t.StopTimer() - engine, err := newSqlite3Engine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - //engine.ShowSQL = true - doBenchFind(engine, t) -} - -func BenchmarkSqlite3NoCacheFindPtr(t *testing.B) { - t.StopTimer() - engine, err := newSqlite3Engine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - //engine.ShowSQL = true - doBenchFindPtr(engine, t) -} - -func BenchmarkSqlite3CacheInsert(t *testing.B) { - t.StopTimer() - engine, err := newSqlite3Engine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) - doBenchInsert(engine, t) -} - -func BenchmarkSqlite3CacheFind(t *testing.B) { - t.StopTimer() - engine, err := newSqlite3Engine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) - doBenchFind(engine, t) -} - -func BenchmarkSqlite3CacheFindPtr(t *testing.B) { - t.StopTimer() - engine, err := newSqlite3Engine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) - doBenchFindPtr(engine, t) -} diff --git a/statement.go b/statement.go index c9161be7..2de1f84a 100644 --- a/statement.go +++ b/statement.go @@ -1,23 +1,33 @@ package xorm import ( - //"bytes" + "encoding/json" "fmt" "reflect" - //"strconv" - "encoding/json" "strings" "time" + + "github.com/go-xorm/core" ) +type inParam struct { + colName string + args []interface{} +} + +type incrParam struct { + colName string + arg interface{} +} + // statement save all the sql info for executing SQL type Statement struct { - RefTable *Table + RefTable *core.Table Engine *Engine Start int LimitN int WhereStr string - IdParam *PK + IdParam *core.PK Params []interface{} OrderStr string JoinStr string @@ -42,7 +52,8 @@ type Statement struct { allUseBool bool checkVersion bool mustColumnMap map[string]bool - inColumns map[string][]interface{} + inColumns map[string]*inParam + incrColumns map[string]incrParam } // init @@ -66,13 +77,14 @@ func (statement *Statement) Init() { statement.RawSQL = "" statement.RawParams = make([]interface{}, 0) statement.BeanArgs = make([]interface{}, 0) - statement.UseCache = statement.Engine.UseCache + statement.UseCache = true statement.UseAutoTime = true statement.IsDistinct = false statement.allUseBool = false statement.mustColumnMap = make(map[string]bool) statement.checkVersion = true - statement.inColumns = make(map[string][]interface{}) + statement.inColumns = make(map[string]*inParam) + statement.incrColumns = make(map[string]incrParam) } // add the raw sql statement @@ -92,7 +104,8 @@ func (statement *Statement) Where(querystring string, args ...interface{}) *Stat // add Where & and statment func (statement *Statement) And(querystring string, args ...interface{}) *Statement { if statement.WhereStr != "" { - statement.WhereStr = fmt.Sprintf("(%v) AND (%v)", statement.WhereStr, querystring) + statement.WhereStr = fmt.Sprintf("(%v) %s (%v)", statement.WhereStr, + statement.Engine.dialect.AndStr(), querystring) } else { statement.WhereStr = querystring } @@ -103,7 +116,8 @@ func (statement *Statement) And(querystring string, args ...interface{}) *Statem // add Where & Or statment func (statement *Statement) Or(querystring string, args ...interface{}) *Statement { if statement.WhereStr != "" { - statement.WhereStr = fmt.Sprintf("(%v) OR (%v)", statement.WhereStr, querystring) + statement.WhereStr = fmt.Sprintf("(%v) %s (%v)", statement.WhereStr, + statement.Engine.dialect.OrStr(), querystring) } else { statement.WhereStr = querystring } @@ -206,7 +220,7 @@ func (statement *Statement) Table(tableNameOrBean interface{}) *Statement { if col.SQLType.IsText() { bytes, err := json.Marshal(fieldValue.Interface()) if err != nil { - engine.LogSQL(err) + engine.LogError(err) continue } val = string(bytes) @@ -223,7 +237,7 @@ func (statement *Statement) Table(tableNameOrBean interface{}) *Statement { } else { bytes, err = json.Marshal(fieldValue.Interface()) if err != nil { - engine.LogSQL(err) + engine.LogError(err) continue } val = bytes @@ -240,14 +254,14 @@ func (statement *Statement) Table(tableNameOrBean interface{}) *Statement { }*/ // Auto generating conditions according a struct -func buildConditions(engine *Engine, table *Table, bean interface{}, +func buildConditions(engine *Engine, table *core.Table, bean interface{}, includeVersion bool, includeUpdated bool, includeNil bool, includeAutoIncr bool, allUseBool bool, useAllCols bool, mustColumnMap map[string]bool) ([]string, []interface{}) { colNames := make([]string, 0) var args = make([]interface{}, 0) - for _, col := range table.Columns { + for _, col := range table.Columns() { if !includeVersion && col.IsVersion { continue } @@ -259,10 +273,16 @@ func buildConditions(engine *Engine, table *Table, bean interface{}, } // //fmt.Println(engine.dialect.DBType(), Text) - if engine.dialect.DBType() == MSSQL && col.SQLType.Name == Text { + if engine.dialect.DBType() == core.MSSQL && col.SQLType.Name == core.Text { continue } - fieldValue := col.ValueOf(bean) + fieldValuePtr, err := col.ValueOf(bean) + if err != nil { + engine.LogError(err) + continue + } + + fieldValue := *fieldValuePtr fieldType := reflect.TypeOf(fieldValue.Interface()) requiredField := useAllCols @@ -362,7 +382,7 @@ func buildConditions(engine *Engine, table *Table, bean interface{}, if col.SQLType.IsText() { bytes, err := json.Marshal(fieldValue.Interface()) if err != nil { - engine.LogSQL(err) + engine.LogError(err) continue } val = string(bytes) @@ -379,7 +399,7 @@ func buildConditions(engine *Engine, table *Table, bean interface{}, } else { bytes, err = json.Marshal(fieldValue.Interface()) if err != nil { - engine.LogSQL(err) + engine.LogError(err) continue } val = bytes @@ -410,50 +430,56 @@ func (statement *Statement) TableName() string { return "" } +var ( + ptrPkType = reflect.TypeOf(&core.PK{}) + pkType = reflect.TypeOf(core.PK{}) +) + // Generate "where id = ? " statment or for composite key "where key1 = ? and key2 = ?" func (statement *Statement) Id(id interface{}) *Statement { - idValue := reflect.ValueOf(id) idType := reflect.TypeOf(idValue.Interface()) switch idType { - case reflect.TypeOf(&PK{}): - if pkPtr, ok := (id).(*PK); ok { + case ptrPkType: + if pkPtr, ok := (id).(*core.PK); ok { statement.IdParam = pkPtr } - case reflect.TypeOf(PK{}): - if pk, ok := (id).(PK); ok { + case pkType: + if pk, ok := (id).(core.PK); ok { statement.IdParam = &pk } default: - // TODO treat as int primitve for now, need to handle type check - statement.IdParam = &PK{id} - - // !nashtsai! REVIEW although it will be user's mistake if called Id() twice with - // different value and Id should be PK's field name, however, at this stage probably - // can't tell which table is gonna be used - // if statement.WhereStr == "" { - // statement.WhereStr = "(id)=?" - // statement.Params = []interface{}{id} - // } else { - // // TODO what if id param has already passed - // statement.WhereStr = statement.WhereStr + " AND (id)=?" - // statement.Params = append(statement.Params, id) - // } + // TODO: treat as int primitve for now, need to handle type check? + statement.IdParam = &core.PK{id} } - // !nashtsai! perhaps no need to validate pk values' type just let sql complaint happen - return statement } +// Generate "Update ... Set column = column + arg" statment +func (statement *Statement) Incr(column string, arg ...interface{}) *Statement { + k := strings.ToLower(column) + if len(arg) > 0 { + statement.incrColumns[k] = incrParam{column, arg[0]} + } else { + statement.incrColumns[k] = incrParam{column, 1} + } + return statement +} + +// Generate "Update ... Set column = column + arg" statment +func (statement *Statement) getInc() map[string]incrParam { + return statement.incrColumns +} + // Generate "Where column IN (?) " statment func (statement *Statement) In(column string, args ...interface{}) *Statement { k := strings.ToLower(column) - if params, ok := statement.inColumns[k]; ok { - statement.inColumns[k] = append(params, args...) + if _, ok := statement.inColumns[k]; ok { + statement.inColumns[k].args = append(statement.inColumns[k].args, args...) } else { - statement.inColumns[k] = args + statement.inColumns[k] = &inParam{column, args} } return statement } @@ -465,23 +491,24 @@ func (statement *Statement) genInSql() (string, []interface{}) { inStrs := make([]string, 0, len(statement.inColumns)) args := make([]interface{}, 0) - for column, params := range statement.inColumns { - inStrs = append(inStrs, fmt.Sprintf("(%v IN (%v))", statement.Engine.Quote(column), - strings.Join(makeArray("?", len(params)), ","))) - args = append(args, params...) + for _, params := range statement.inColumns { + inStrs = append(inStrs, fmt.Sprintf("(%v IN (%v))", + statement.Engine.Quote(params.colName), + strings.Join(makeArray("?", len(params.args)), ","))) + args = append(args, params.args...) } if len(statement.inColumns) == 1 { return inStrs[0], args } - return fmt.Sprintf("(%v)", strings.Join(inStrs, " AND ")), args + return fmt.Sprintf("(%v)", strings.Join(inStrs, " "+statement.Engine.dialect.AndStr()+" ")), args } func (statement *Statement) attachInSql() { inSql, inArgs := statement.genInSql() if len(inSql) > 0 { if statement.ConditionStr != "" { - statement.ConditionStr += " AND " + statement.ConditionStr += " " + statement.Engine.dialect.AndStr() + " " } statement.ConditionStr += inSql statement.Params = append(statement.Params, inArgs...) @@ -610,13 +637,13 @@ func (statement *Statement) Having(conditions string) *Statement { func (statement *Statement) genColumnStr() string { table := statement.RefTable colNames := make([]string, 0) - for _, col := range table.Columns { + for _, col := range table.Columns() { if statement.OmitStr != "" { if _, ok := statement.columnMap[strings.ToLower(col.Name)]; ok { continue } } - if col.MapType == ONLYTODB { + if col.MapType == core.ONLYTODB { continue } colNames = append(colNames, statement.Engine.Quote(statement.TableName())+"."+statement.Engine.Quote(col.Name)) @@ -625,54 +652,8 @@ func (statement *Statement) genColumnStr() string { } func (statement *Statement) genCreateTableSQL() string { - var sql string - if statement.Engine.dialect.DBType() == MSSQL { - sql = "IF NOT EXISTS (SELECT [name] FROM sys.tables WHERE [name] = '" + statement.TableName() + "' ) CREATE TABLE" - } else { - sql = "CREATE TABLE IF NOT EXISTS " - } - sql += statement.Engine.Quote(statement.TableName()) + " (" - - pkList := []string{} - - for _, colName := range statement.RefTable.ColumnsSeq { - col := statement.RefTable.Columns[strings.ToLower(colName)] - if col.IsPrimaryKey { - pkList = append(pkList, col.Name) - } - } - - statement.Engine.LogDebug("len:", len(pkList)) - for _, colName := range statement.RefTable.ColumnsSeq { - col := statement.RefTable.Columns[strings.ToLower(colName)] - if col.IsPrimaryKey && len(pkList) == 1 { - sql += col.String(statement.Engine.dialect) - } else { - sql += col.stringNoPk(statement.Engine.dialect) - } - sql = strings.TrimSpace(sql) - sql += ", " - } - - if len(pkList) > 1 { - sql += "PRIMARY KEY ( " - sql += strings.Join(pkList, ",") - sql += " ), " - } - - sql = sql[:len(sql)-2] + ")" - if statement.Engine.dialect.SupportEngine() && statement.StoreEngine != "" { - sql += " ENGINE=" + statement.StoreEngine - } - if statement.Engine.dialect.SupportCharset() { - if statement.Charset != "" { - sql += " DEFAULT CHARSET " + statement.Charset - } else if statement.Engine.dialect.URI().charset != "" { - sql += " DEFAULT CHARSET " + statement.Engine.dialect.URI().charset - } - } - sql += ";" - return sql + return statement.Engine.dialect.CreateTableSql(statement.RefTable, statement.AltTableName, + statement.StoreEngine, statement.Charset) } func indexName(tableName, idxName string) string { @@ -684,7 +665,7 @@ func (s *Statement) genIndexSQL() []string { tbName := s.TableName() quote := s.Engine.Quote for idxName, index := range s.RefTable.Indexes { - if index.Type == IndexType { + if index.Type == core.IndexType { sql := fmt.Sprintf("CREATE INDEX %v ON %v (%v);", quote(indexName(tbName, idxName)), quote(tbName), quote(strings.Join(index.Cols, quote(",")))) sqls = append(sqls, sql) @@ -700,11 +681,9 @@ func uniqueName(tableName, uqeName string) string { func (s *Statement) genUniqueSQL() []string { var sqls []string = make([]string, 0) tbName := s.TableName() - quote := s.Engine.Quote - for idxName, unique := range s.RefTable.Indexes { - if unique.Type == UniqueType { - sql := fmt.Sprintf("CREATE UNIQUE INDEX %v ON %v (%v);", quote(uniqueName(tbName, idxName)), - quote(tbName), quote(strings.Join(unique.Cols, quote(",")))) + for _, index := range s.RefTable.Indexes { + if index.Type == core.UniqueType { + sql := s.Engine.dialect.CreateIndexSql(tbName, index) sqls = append(sqls, sql) } } @@ -715,9 +694,9 @@ func (s *Statement) genDelIndexSQL() []string { var sqls []string = make([]string, 0) for idxName, index := range s.RefTable.Indexes { var rIdxName string - if index.Type == UniqueType { + if index.Type == core.UniqueType { rIdxName = uniqueName(s.TableName(), idxName) - } else if index.Type == IndexType { + } else if index.Type == core.IndexType { rIdxName = indexName(s.TableName(), idxName) } sql := fmt.Sprintf("DROP INDEX %v", s.Engine.Quote(rIdxName)) @@ -741,7 +720,7 @@ func (statement *Statement) genGetSql(bean interface{}) (string, []interface{}) false, true, statement.allUseBool, statement.useAllCols, statement.mustColumnMap) - statement.ConditionStr = strings.Join(colNames, " AND ") + statement.ConditionStr = strings.Join(colNames, " "+statement.Engine.dialect.AndStr()+" ") statement.BeanArgs = args var columnStr string = statement.ColumnStr @@ -752,14 +731,14 @@ func (statement *Statement) genGetSql(bean interface{}) (string, []interface{}) return statement.genSelectSql(columnStr), append(statement.Params, statement.BeanArgs...) } -func (s *Statement) genAddColumnStr(col *Column) (string, []interface{}) { +func (s *Statement) genAddColumnStr(col *core.Column) (string, []interface{}) { quote := s.Engine.Quote sql := fmt.Sprintf("ALTER TABLE %v ADD COLUMN %v;", quote(s.TableName()), col.String(s.Engine.dialect)) return sql, []interface{}{} } -func (s *Statement) genAddIndexStr(idxName string, cols []string) (string, []interface{}) { +/*func (s *Statement) genAddIndexStr(idxName string, cols []string) (string, []interface{}) { quote := s.Engine.Quote colstr := quote(strings.Join(cols, quote(", "))) sql := fmt.Sprintf("CREATE INDEX %v ON %v (%v);", quote(idxName), quote(s.TableName()), colstr) @@ -771,7 +750,7 @@ func (s *Statement) genAddUniqueStr(uqeName string, cols []string) (string, []in colstr := quote(strings.Join(cols, quote(", "))) sql := fmt.Sprintf("CREATE UNIQUE INDEX %v ON %v (%v);", quote(uqeName), quote(s.TableName()), colstr) return sql, []interface{}{} -} +}*/ func (statement *Statement) genCountSql(bean interface{}) (string, []interface{}) { table := statement.Engine.autoMap(bean) @@ -825,13 +804,14 @@ func (statement *Statement) genSelectSql(columnStr string) (a string) { if statement.OrderStr != "" { a = fmt.Sprintf("%v ORDER BY %v", a, statement.OrderStr) } - if statement.Engine.dialect.DBType() != MSSQL { + if statement.Engine.dialect.DBType() != core.MSSQL { if statement.Start > 0 { a = fmt.Sprintf("%v LIMIT %v OFFSET %v", a, statement.LimitN, statement.Start) } else if statement.LimitN > 0 { a = fmt.Sprintf("%v LIMIT %v", a, statement.LimitN) } } else { + //TODO: for mssql, should handler limit. /*SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (ORDER BY id desc) as row FROM "userinfo" ) a WHERE row > [start] and row <= [start+limit] order by id desc*/ @@ -841,30 +821,12 @@ func (statement *Statement) genSelectSql(columnStr string) (a string) { } func (statement *Statement) processIdParam() { - if statement.IdParam != nil { - i := 0 - colCnt := len(statement.RefTable.ColumnsSeq) - for _, elem := range *(statement.IdParam) { - for ; i < colCnt; i++ { - colName := statement.RefTable.ColumnsSeq[i] - col := statement.RefTable.Columns[strings.ToLower(colName)] - if col.IsPrimaryKey { - statement.And(fmt.Sprintf("%v=?", col.Name), elem) - i++ - break - } - } - } - - // !nashtsai! REVIEW what if statement.IdParam has insufficient pk item? handle it - // as empty string for now, so this will result sql exec failed instead of unexpected - // false update/delete - for ; i < colCnt; i++ { - colName := statement.RefTable.ColumnsSeq[i] - col := statement.RefTable.Columns[strings.ToLower(colName)] - if col.IsPrimaryKey { - statement.And(fmt.Sprintf("%v=?", col.Name), "") + for i, col := range statement.RefTable.PKColumns() { + if i < len(*(statement.IdParam)) { + statement.And(fmt.Sprintf("%v=?", statement.Engine.Quote(col.Name)), (*(statement.IdParam))[i]) + } else { + statement.And(fmt.Sprintf("%v=?", statement.Engine.Quote(col.Name)), "") } } } diff --git a/table.go b/table.go deleted file mode 100644 index 3e911ca2..00000000 --- a/table.go +++ /dev/null @@ -1,484 +0,0 @@ -package xorm - -import ( - "reflect" - "sort" - "strings" - "time" -) - -// xorm SQL types -type SQLType struct { - Name string - DefaultLength int - DefaultLength2 int -} - -func (s *SQLType) IsText() bool { - return s.Name == Char || s.Name == Varchar || s.Name == TinyText || - s.Name == Text || s.Name == MediumText || s.Name == LongText -} - -func (s *SQLType) IsBlob() bool { - return (s.Name == TinyBlob) || (s.Name == Blob) || - s.Name == MediumBlob || s.Name == LongBlob || - s.Name == Binary || s.Name == VarBinary || s.Name == Bytea -} - -const () - -var ( - Bit = "BIT" - TinyInt = "TINYINT" - SmallInt = "SMALLINT" - MediumInt = "MEDIUMINT" - Int = "INT" - Integer = "INTEGER" - BigInt = "BIGINT" - - Char = "CHAR" - Varchar = "VARCHAR" - TinyText = "TINYTEXT" - Text = "TEXT" - MediumText = "MEDIUMTEXT" - LongText = "LONGTEXT" - - Date = "DATE" - DateTime = "DATETIME" - Time = "TIME" - TimeStamp = "TIMESTAMP" - TimeStampz = "TIMESTAMPZ" - - Decimal = "DECIMAL" - Numeric = "NUMERIC" - - Real = "REAL" - Float = "FLOAT" - Double = "DOUBLE" - - Binary = "BINARY" - VarBinary = "VARBINARY" - TinyBlob = "TINYBLOB" - Blob = "BLOB" - MediumBlob = "MEDIUMBLOB" - LongBlob = "LONGBLOB" - Bytea = "BYTEA" - - Bool = "BOOL" - - Serial = "SERIAL" - BigSerial = "BIGSERIAL" - - sqlTypes = map[string]bool{ - Bit: true, - TinyInt: true, - SmallInt: true, - MediumInt: true, - Int: true, - Integer: true, - BigInt: true, - - Char: true, - Varchar: true, - TinyText: true, - Text: true, - MediumText: true, - LongText: true, - - Date: true, - DateTime: true, - Time: true, - TimeStamp: true, - TimeStampz: true, - - Decimal: true, - Numeric: true, - - Binary: true, - VarBinary: true, - Real: true, - Float: true, - Double: true, - TinyBlob: true, - Blob: true, - MediumBlob: true, - LongBlob: true, - Bytea: true, - - Bool: true, - - Serial: true, - BigSerial: true, - } - - intTypes = sort.StringSlice{"*int", "*int16", "*int32", "*int8"} - uintTypes = sort.StringSlice{"*uint", "*uint16", "*uint32", "*uint8"} -) - -// !nashtsai! treat following var as interal const values, these are used for reflect.TypeOf comparision -var ( - c_EMPTY_STRING string - c_BOOL_DEFAULT bool - c_BYTE_DEFAULT byte - c_COMPLEX64_DEFAULT complex64 - c_COMPLEX128_DEFAULT complex128 - c_FLOAT32_DEFAULT float32 - c_FLOAT64_DEFAULT float64 - c_INT64_DEFAULT int64 - c_UINT64_DEFAULT uint64 - c_INT32_DEFAULT int32 - c_UINT32_DEFAULT uint32 - c_INT16_DEFAULT int16 - c_UINT16_DEFAULT uint16 - c_INT8_DEFAULT int8 - c_UINT8_DEFAULT uint8 - c_INT_DEFAULT int - c_UINT_DEFAULT uint - c_TIME_DEFAULT time.Time -) - -func Type2SQLType(t reflect.Type) (st SQLType) { - switch k := t.Kind(); k { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: - st = SQLType{Int, 0, 0} - case reflect.Int64, reflect.Uint64: - st = SQLType{BigInt, 0, 0} - case reflect.Float32: - st = SQLType{Float, 0, 0} - case reflect.Float64: - st = SQLType{Double, 0, 0} - case reflect.Complex64, reflect.Complex128: - st = SQLType{Varchar, 64, 0} - case reflect.Array, reflect.Slice, reflect.Map: - if t.Elem() == reflect.TypeOf(c_BYTE_DEFAULT) { - st = SQLType{Blob, 0, 0} - } else { - st = SQLType{Text, 0, 0} - } - case reflect.Bool: - st = SQLType{Bool, 0, 0} - case reflect.String: - st = SQLType{Varchar, 255, 0} - case reflect.Struct: - if t == reflect.TypeOf(c_TIME_DEFAULT) { - st = SQLType{DateTime, 0, 0} - } else { - // TODO need to handle association struct - st = SQLType{Text, 0, 0} - } - case reflect.Ptr: - st, _ = ptrType2SQLType(t) - default: - st = SQLType{Text, 0, 0} - } - return -} - -func ptrType2SQLType(t reflect.Type) (st SQLType, has bool) { - has = true - - switch t { - case reflect.TypeOf(&c_EMPTY_STRING): - st = SQLType{Varchar, 255, 0} - return - case reflect.TypeOf(&c_BOOL_DEFAULT): - st = SQLType{Bool, 0, 0} - case reflect.TypeOf(&c_COMPLEX64_DEFAULT), reflect.TypeOf(&c_COMPLEX128_DEFAULT): - st = SQLType{Varchar, 64, 0} - case reflect.TypeOf(&c_FLOAT32_DEFAULT): - st = SQLType{Float, 0, 0} - case reflect.TypeOf(&c_FLOAT64_DEFAULT): - st = SQLType{Double, 0, 0} - case reflect.TypeOf(&c_INT64_DEFAULT), reflect.TypeOf(&c_UINT64_DEFAULT): - st = SQLType{BigInt, 0, 0} - case reflect.TypeOf(&c_TIME_DEFAULT): - st = SQLType{DateTime, 0, 0} - case reflect.TypeOf(&c_INT_DEFAULT), reflect.TypeOf(&c_INT32_DEFAULT), reflect.TypeOf(&c_INT8_DEFAULT), reflect.TypeOf(&c_INT16_DEFAULT), reflect.TypeOf(&c_UINT_DEFAULT), reflect.TypeOf(&c_UINT32_DEFAULT), reflect.TypeOf(&c_UINT8_DEFAULT), reflect.TypeOf(&c_UINT16_DEFAULT): - st = SQLType{Int, 0, 0} - default: - has = false - } - return -} - -// default sql type change to go types -func SQLType2Type(st SQLType) reflect.Type { - name := strings.ToUpper(st.Name) - switch name { - case Bit, TinyInt, SmallInt, MediumInt, Int, Integer, Serial: - return reflect.TypeOf(1) - case BigInt, BigSerial: - return reflect.TypeOf(int64(1)) - case Float, Real: - return reflect.TypeOf(float32(1)) - case Double: - return reflect.TypeOf(float64(1)) - case Char, Varchar, TinyText, Text, MediumText, LongText: - return reflect.TypeOf("") - case TinyBlob, Blob, LongBlob, Bytea, Binary, MediumBlob, VarBinary: - return reflect.TypeOf([]byte{}) - case Bool: - return reflect.TypeOf(true) - case DateTime, Date, Time, TimeStamp, TimeStampz: - return reflect.TypeOf(c_TIME_DEFAULT) - case Decimal, Numeric: - return reflect.TypeOf("") - default: - return reflect.TypeOf("") - } -} - -const ( - IndexType = iota + 1 - UniqueType -) - -// database index -type Index struct { - Name string - Type int - Cols []string -} - -// add columns which will be composite index -func (index *Index) AddColumn(cols ...string) { - for _, col := range cols { - index.Cols = append(index.Cols, col) - } -} - -// new an index -func NewIndex(name string, indexType int) *Index { - return &Index{name, indexType, make([]string, 0)} -} - -const ( - TWOSIDES = iota + 1 - ONLYTODB - ONLYFROMDB -) - -// database column -type Column struct { - Name string - FieldName string - SQLType SQLType - Length int - Length2 int - Nullable bool - Default string - Indexes map[string]bool - IsPrimaryKey bool - IsAutoIncrement bool - MapType int - IsCreated bool - IsUpdated bool - IsCascade bool - IsVersion bool - DefaultIsEmpty bool -} - -// generate column description string according dialect -func (col *Column) String(d dialect) string { - sql := d.QuoteStr() + col.Name + d.QuoteStr() + " " - - sql += d.SqlType(col) + " " - - if col.IsPrimaryKey { - sql += "PRIMARY KEY " - if col.IsAutoIncrement { - sql += d.AutoIncrStr() + " " - } - } - - if col.Nullable { - sql += "NULL " - } else { - sql += "NOT NULL " - } - - if col.Default != "" { - sql += "DEFAULT " + col.Default + " " - } else if col.IsVersion { - sql += "DEFAULT 1 " - } - - return sql -} - -func (col *Column) stringNoPk(d dialect) string { - sql := d.QuoteStr() + col.Name + d.QuoteStr() + " " - - sql += d.SqlType(col) + " " - - if col.Nullable { - sql += "NULL " - } else { - sql += "NOT NULL " - } - - if col.Default != "" { - sql += "DEFAULT " + col.Default + " " - } else if col.IsVersion { - sql += "DEFAULT 1 " - } - - return sql -} - -// return col's filed of struct's value -func (col *Column) ValueOf(bean interface{}) reflect.Value { - var fieldValue reflect.Value - if strings.Contains(col.FieldName, ".") { - fields := strings.Split(col.FieldName, ".") - if len(fields) > 2 { - return reflect.ValueOf(nil) - } - - fieldValue = reflect.Indirect(reflect.ValueOf(bean)).FieldByName(fields[0]) - fieldValue = fieldValue.FieldByName(fields[1]) - } else { - fieldValue = reflect.Indirect(reflect.ValueOf(bean)).FieldByName(col.FieldName) - } - return fieldValue -} - -// database table -type Table struct { - Name string - Type reflect.Type - ColumnsSeq []string - Columns map[string]*Column - Indexes map[string]*Index - PrimaryKeys []string - AutoIncrement string - Created map[string]bool - Updated string - Version string - Cacher Cacher -} - -/* -func NewTable(name string, t reflect.Type) *Table { - return &Table{Name: name, Type: t, - ColumnsSeq: make([]string, 0), - Columns: make(map[string]*Column), - Indexes: make(map[string]*Index), - Created: make(map[string]bool), - } -}*/ - -// if has primary key, return column -func (table *Table) PKColumns() []*Column { - columns := make([]*Column, 0) - for _, name := range table.PrimaryKeys { - columns = append(columns, table.Columns[strings.ToLower(name)]) - } - return columns -} - -func (table *Table) AutoIncrColumn() *Column { - return table.Columns[strings.ToLower(table.AutoIncrement)] -} - -func (table *Table) VersionColumn() *Column { - return table.Columns[strings.ToLower(table.Version)] -} - -// add a column to table -func (table *Table) AddColumn(col *Column) { - table.ColumnsSeq = append(table.ColumnsSeq, col.Name) - table.Columns[strings.ToLower(col.Name)] = col - if col.IsPrimaryKey { - table.PrimaryKeys = append(table.PrimaryKeys, col.Name) - } - if col.IsAutoIncrement { - table.AutoIncrement = col.Name - } - if col.IsCreated { - table.Created[col.Name] = true - } - if col.IsUpdated { - table.Updated = col.Name - } - if col.IsVersion { - table.Version = col.Name - } -} - -// add an index or an unique to table -func (table *Table) AddIndex(index *Index) { - table.Indexes[index.Name] = index -} - -func (table *Table) genCols(session *Session, bean interface{}, useCol bool, includeQuote bool) ([]string, []interface{}, error) { - colNames := make([]string, 0) - args := make([]interface{}, 0) - - for _, col := range table.Columns { - lColName := strings.ToLower(col.Name) - if useCol && !col.IsVersion && !col.IsCreated && !col.IsUpdated { - if _, ok := session.Statement.columnMap[lColName]; !ok { - continue - } - } - if col.MapType == ONLYFROMDB { - continue - } - - fieldValue := col.ValueOf(bean) - if col.IsAutoIncrement { - switch fieldValue.Type().Kind() { - case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64: - if fieldValue.Int() == 0 { - continue - } - case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64: - if fieldValue.Uint() == 0 { - continue - } - case reflect.String: - if len(fieldValue.String()) == 0 { - continue - } - } - } - - if session.Statement.ColumnStr != "" { - if _, ok := session.Statement.columnMap[lColName]; !ok { - continue - } - } - if session.Statement.OmitStr != "" { - if _, ok := session.Statement.columnMap[lColName]; ok { - continue - } - } - - if (col.IsCreated || col.IsUpdated) && session.Statement.UseAutoTime { - args = append(args, session.Engine.NowTime(col.SQLType.Name)) - } else if col.IsVersion && session.Statement.checkVersion { - args = append(args, 1) - } else { - arg, err := session.value2Interface(col, fieldValue) - if err != nil { - return colNames, args, err - } - args = append(args, arg) - } - - if includeQuote { - colNames = append(colNames, session.Engine.Quote(col.Name)+" = ?") - } else { - colNames = append(colNames, col.Name) - } - } - return colNames, args, nil -} - -// Conversion is an interface. A type implements Conversion will according -// the custom method to fill into database and retrieve from database. -type Conversion interface { - FromDB([]byte) error - ToDB() ([]byte, error) -} diff --git a/tests/mysql_ddl.sql b/tests/mysql_ddl.sql deleted file mode 100644 index 53c9f316..00000000 --- a/tests/mysql_ddl.sql +++ /dev/null @@ -1,4 +0,0 @@ ---DROP DATABASE xorm_test; ---DROP DATABASE xorm_test2; -CREATE DATABASE IF NOT EXISTS xorm_test CHARACTER SET utf8 COLLATE utf8_general_ci; -CREATE DATABASE IF NOT EXISTS xorm_test2 CHARACTER SET utf8 COLLATE utf8_general_ci; diff --git a/xorm.go b/xorm.go index 88d99e79..86a4ee55 100644 --- a/xorm.go +++ b/xorm.go @@ -1,18 +1,59 @@ package xorm import ( + "database/sql" "errors" "fmt" "os" "reflect" "runtime" "sync" + "time" + + "github.com/go-xorm/core" ) const ( - Version string = "0.3.1" + Version string = "0.4" ) +// !nashtsai! implicit register drivers and dialects is no good, as init() can be called before sql driver got registered +// func init() { +// regDrvsNDialects() +// } + +func regDrvsNDialects() bool { + if core.RegisteredDriverSize() == 0 { + providedDrvsNDialects := map[string]struct { + dbType core.DbType + getDriver func() core.Driver + getDialect func() core.Dialect + }{ + "odbc": {"mssql", func() core.Driver { return &odbcDriver{} }, func() core.Dialect { return &mssql{} }}, // !nashtsai! TODO change this when supporting MS Access + "mysql": {"mysql", func() core.Driver { return &mysqlDriver{} }, func() core.Dialect { return &mysql{} }}, + "mymysql": {"mysql", func() core.Driver { return &mymysqlDriver{} }, func() core.Dialect { return &mysql{} }}, + "postgres": {"postgres", func() core.Driver { return &pqDriver{} }, func() core.Dialect { return &postgres{} }}, + "sqlite3": {"sqlite3", func() core.Driver { return &sqlite3Driver{} }, func() core.Dialect { return &sqlite3{} }}, + "oci8": {"oracle", func() core.Driver { return &oci8Driver{} }, func() core.Dialect { return &oracle{} }}, + "goracle": {"oracle", func() core.Driver { return &goracleDriver{} }, func() core.Dialect { return &oracle{} }}, + } + + for driverName, v := range providedDrvsNDialects { + _, err := sql.Open(driverName, "") + if err == nil { + // fmt.Printf("driver succeed: %v\n", driverName) + core.RegisterDriver(driverName, v.getDriver()) + core.RegisterDialect(v.dbType, v.getDialect()) + } else { + // fmt.Printf("driver failed: %v | err: %v\n", driverName, err) + } + } + return true + } else { + return false + } +} + func close(engine *Engine) { engine.Close() } @@ -20,49 +61,53 @@ func close(engine *Engine) { // new a db manager according to the parameter. Currently support four // drivers func NewEngine(driverName string, dataSourceName string) (*Engine, error) { - engine := &Engine{ - DriverName: driverName, - DataSourceName: dataSourceName, - Filters: make([]Filter, 0), - TimeZone: "Local", - } - engine.SetMapper(SnakeMapper{}) - - if driverName == SQLITE { - engine.dialect = &sqlite3{} - } else if driverName == MYSQL { - engine.dialect = &mysql{} - } else if driverName == POSTGRES { - engine.dialect = &postgres{} - engine.Filters = append(engine.Filters, &PgSeqFilter{}) - engine.Filters = append(engine.Filters, &QuoteFilter{}) - } else if driverName == MYMYSQL { - engine.dialect = &mymysql{} - } else if driverName == "odbc" { - engine.dialect = &mssql{quoteFilter: &QuoteFilter{}} - engine.Filters = append(engine.Filters, &QuoteFilter{}) - } else if driverName == ORACLE_OCI { - engine.dialect = &oracle{} - engine.Filters = append(engine.Filters, &QuoteFilter{}) - } else { + regDrvsNDialects() + driver := core.QueryDriver(driverName) + if driver == nil { return nil, errors.New(fmt.Sprintf("Unsupported driver name: %v", driverName)) } - err := engine.dialect.Init(driverName, dataSourceName) + + uri, err := driver.Parse(driverName, dataSourceName) if err != nil { return nil, err } - engine.Tables = make(map[reflect.Type]*Table) - engine.mutex = &sync.RWMutex{} - engine.TagIdentifier = "xorm" + dialect := core.QueryDialect(uri.DbType) + if dialect == nil { + return nil, errors.New(fmt.Sprintf("Unsupported dialect type: %v", uri.DbType)) + } - engine.Filters = append(engine.Filters, &IdFilter{}) - engine.Logger = os.Stdout + db, err := core.Open(driverName, dataSourceName) + if err != nil { + return nil, err + } - //engine.Pool = NewSimpleConnectPool() - //engine.Pool = NewNoneConnectPool() + err = dialect.Init(db, uri, driverName, dataSourceName) + if err != nil { + return nil, err + } + + engine := &Engine{ + db: db, + dialect: dialect, + Tables: make(map[reflect.Type]*core.Table), + mutex: &sync.RWMutex{}, + TagIdentifier: "xorm", + Logger: NewSimpleLogger(os.Stdout), + TZLocation: time.Local, + } + + engine.SetMapper(core.NewCacheMapper(new(core.SnakeMapper))) + + //engine.Filters = dialect.Filters() //engine.Cacher = NewLRUCacher() - err = engine.SetPool(NewSysConnectPool()) + //err = engine.SetPool(NewSysConnectPool()) + runtime.SetFinalizer(engine, close) return engine, err } + +// clone an engine +func (engine *Engine) Clone() (*Engine, error) { + return NewEngine(engine.dialect.DriverName(), engine.dialect.DataSourceName()) +} diff --git a/xorm/.gopmfile b/xorm/.gopmfile deleted file mode 100644 index f5cdbb0c..00000000 --- a/xorm/.gopmfile +++ /dev/null @@ -1,2 +0,0 @@ -[deps] -github.com/lunny/xorm=../ \ No newline at end of file diff --git a/xorm/README.md b/xorm/README.md deleted file mode 100644 index b0d39b86..00000000 --- a/xorm/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# xorm tools - - -xorm tools is a set of tools for database operation. - -## Install - -`go get github.com/lunny/xorm/xorm` - -and you should install the depends below: - -* github.com/lunny/xorm - -* 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/bylevel/pq](https://github.com/bylevel/pq) - - -## Reverse - -After you installed the tool, you can type - -`xorm help reverse` - -to get help - -example: - -sqlite: -`xorm reverse sqite3 test.db templates/goxorm` - -mysql: -`xorm reverse mysql root:@/xorm_test?charset=utf8 templates/goxorm` - -mymysql: -`xorm reverse mymysql xorm_test2/root/ templates/goxorm` - -postgres: -`xorm reverse postgres "dbname=xorm_test sslmode=disable" templates/goxorm` - -will generated go files in `./model` directory - -## Template and Config - -Now, xorm tool supports go and c++ two languages and have go, goxorm, c++ three of default templates. In template directory, we can put a config file to control how to generating. - -```` -lang=go -genJson=1 -``` - -lang must be go or c++ now. -genJson can be 1 or 0, if 1 then the struct will have json tag. - -## LICENSE - - BSD License - [http://creativecommons.org/licenses/BSD/](http://creativecommons.org/licenses/BSD/) diff --git a/xorm/c++.go b/xorm/c++.go deleted file mode 100644 index bb9e850e..00000000 --- a/xorm/c++.go +++ /dev/null @@ -1,65 +0,0 @@ -package main - -import ( - //"fmt" - "github.com/go-xorm/xorm" - "strings" - "text/template" -) - -var ( - CPlusTmpl LangTmpl = LangTmpl{ - template.FuncMap{"Mapper": mapper.Table2Obj, - "Type": cPlusTypeStr, - "UnTitle": unTitle, - }, - nil, - genCPlusImports, - } -) - -func cPlusTypeStr(col *xorm.Column) string { - tp := col.SQLType - name := strings.ToUpper(tp.Name) - switch name { - case xorm.Bit, xorm.TinyInt, xorm.SmallInt, xorm.MediumInt, xorm.Int, xorm.Integer, xorm.Serial: - return "int" - case xorm.BigInt, xorm.BigSerial: - return "__int64" - case xorm.Char, xorm.Varchar, xorm.TinyText, xorm.Text, xorm.MediumText, xorm.LongText: - return "tstring" - case xorm.Date, xorm.DateTime, xorm.Time, xorm.TimeStamp: - return "time_t" - case xorm.Decimal, xorm.Numeric: - return "tstring" - case xorm.Real, xorm.Float: - return "float" - case xorm.Double: - return "double" - case xorm.TinyBlob, xorm.Blob, xorm.MediumBlob, xorm.LongBlob, xorm.Bytea: - return "tstring" - case xorm.Bool: - return "bool" - default: - return "tstring" - } - return "" -} - -func genCPlusImports(tables []*xorm.Table) map[string]string { - imports := make(map[string]string) - - for _, table := range tables { - for _, col := range table.Columns { - switch cPlusTypeStr(col) { - case "time_t": - imports[``] = `` - case "tstring": - imports[""] = "" - //case "__int64": - // imports[""] = "" - } - } - } - return imports -} diff --git a/xorm/cmd.go b/xorm/cmd.go deleted file mode 100644 index eec39e59..00000000 --- a/xorm/cmd.go +++ /dev/null @@ -1,78 +0,0 @@ -package main - -import ( - "fmt" - "os" - "strings" -) - -// A Command is an implementation of a go command -// like go build or go fix. -type Command struct { - // Run runs the command. - // The args are the arguments after the command name. - Run func(cmd *Command, args []string) - - // UsageLine is the one-line usage message. - // The first word in the line is taken to be the command name. - UsageLine string - - // Short is the short description shown in the 'go help' output. - Short string - - // Long is the long message shown in the 'go help ' output. - Long string - - // Flag is a set of flags specific to this command. - Flags map[string]bool -} - -// Name returns the command's name: the first word in the usage line. -func (c *Command) Name() string { - name := c.UsageLine - i := strings.Index(name, " ") - if i >= 0 { - name = name[:i] - } - return name -} - -func (c *Command) Usage() { - fmt.Fprintf(os.Stderr, "usage: %s\n\n", c.UsageLine) - fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSpace(c.Long)) - os.Exit(2) -} - -// Runnable reports whether the command can be run; otherwise -// it is a documentation pseudo-command such as importpath. -func (c *Command) Runnable() bool { - return c.Run != nil -} - -// checkFlags checks if the flag exists with correct format. -func checkFlags(flags map[string]bool, args []string, print func(string)) int { - num := 0 // Number of valid flags, use to cut out. - for i, f := range args { - // Check flag prefix '-'. - if !strings.HasPrefix(f, "-") { - // Not a flag, finish check process. - break - } - - // Check if it a valid flag. - if v, ok := flags[f]; ok { - flags[f] = !v - if !v { - print(f) - } else { - fmt.Println("DISABLE: " + f) - } - } else { - fmt.Printf("[ERRO] Unknown flag: %s.\n", f) - return -1 - } - num = i + 1 - } - - return num -} diff --git a/xorm/go.go b/xorm/go.go deleted file mode 100644 index 28c10137..00000000 --- a/xorm/go.go +++ /dev/null @@ -1,263 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "github.com/go-xorm/xorm" - "go/format" - "reflect" - "strings" - "text/template" -) - -var ( - GoLangTmpl LangTmpl = LangTmpl{ - template.FuncMap{"Mapper": mapper.Table2Obj, - "Type": typestring, - "Tag": tag, - "UnTitle": unTitle, - "gt": gt, - "getCol": getCol, - }, - formatGo, - genGoImports, - } -) - -var ( - errBadComparisonType = errors.New("invalid type for comparison") - errBadComparison = errors.New("incompatible types for comparison") - errNoComparison = errors.New("missing argument for comparison") -) - -type kind int - -const ( - invalidKind kind = iota - boolKind - complexKind - intKind - floatKind - integerKind - stringKind - uintKind -) - -func basicKind(v reflect.Value) (kind, error) { - switch v.Kind() { - case reflect.Bool: - return boolKind, nil - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return intKind, nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return uintKind, nil - case reflect.Float32, reflect.Float64: - return floatKind, nil - case reflect.Complex64, reflect.Complex128: - return complexKind, nil - case reflect.String: - return stringKind, nil - } - return invalidKind, errBadComparisonType -} - -// eq evaluates the comparison a == b || a == c || ... -func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) { - v1 := reflect.ValueOf(arg1) - k1, err := basicKind(v1) - if err != nil { - return false, err - } - if len(arg2) == 0 { - return false, errNoComparison - } - for _, arg := range arg2 { - v2 := reflect.ValueOf(arg) - k2, err := basicKind(v2) - if err != nil { - return false, err - } - if k1 != k2 { - return false, errBadComparison - } - truth := false - switch k1 { - case boolKind: - truth = v1.Bool() == v2.Bool() - case complexKind: - truth = v1.Complex() == v2.Complex() - case floatKind: - truth = v1.Float() == v2.Float() - case intKind: - truth = v1.Int() == v2.Int() - case stringKind: - truth = v1.String() == v2.String() - case uintKind: - truth = v1.Uint() == v2.Uint() - default: - panic("invalid kind") - } - if truth { - return true, nil - } - } - return false, nil -} - -// lt evaluates the comparison a < b. -func lt(arg1, arg2 interface{}) (bool, error) { - v1 := reflect.ValueOf(arg1) - k1, err := basicKind(v1) - if err != nil { - return false, err - } - v2 := reflect.ValueOf(arg2) - k2, err := basicKind(v2) - if err != nil { - return false, err - } - if k1 != k2 { - return false, errBadComparison - } - truth := false - switch k1 { - case boolKind, complexKind: - return false, errBadComparisonType - case floatKind: - truth = v1.Float() < v2.Float() - case intKind: - truth = v1.Int() < v2.Int() - case stringKind: - truth = v1.String() < v2.String() - case uintKind: - truth = v1.Uint() < v2.Uint() - default: - panic("invalid kind") - } - return truth, nil -} - -// le evaluates the comparison <= b. -func le(arg1, arg2 interface{}) (bool, error) { - // <= is < or ==. - lessThan, err := lt(arg1, arg2) - if lessThan || err != nil { - return lessThan, err - } - return eq(arg1, arg2) -} - -// gt evaluates the comparison a > b. -func gt(arg1, arg2 interface{}) (bool, error) { - // > is the inverse of <=. - lessOrEqual, err := le(arg1, arg2) - if err != nil { - return false, err - } - return !lessOrEqual, nil -} - -func getCol(cols map[string]*xorm.Column, name string) *xorm.Column { - return cols[name] -} - -func formatGo(src string) (string, error) { - source, err := format.Source([]byte(src)) - if err != nil { - return "", err - } - return string(source), nil -} - -func genGoImports(tables []*xorm.Table) map[string]string { - imports := make(map[string]string) - - for _, table := range tables { - for _, col := range table.Columns { - if typestring(col) == "time.Time" { - imports["time"] = "time" - } - } - } - return imports -} - -func typestring(col *xorm.Column) string { - st := col.SQLType - /*if col.IsPrimaryKey { - return "int64" - }*/ - t := xorm.SQLType2Type(st) - s := t.String() - if s == "[]uint8" { - return "[]byte" - } - return s -} - -func tag(table *xorm.Table, col *xorm.Column) string { - isNameId := (mapper.Table2Obj(col.Name) == "Id") - isIdPk := isNameId && typestring(col) == "int64" - - res := make([]string, 0) - if !col.Nullable { - if !isIdPk { - res = append(res, "not null") - } - } - if col.IsPrimaryKey { - if !isIdPk { - res = append(res, "pk") - } - } - if col.Default != "" { - res = append(res, "default "+col.Default) - } - if col.IsAutoIncrement { - if !isIdPk { - res = append(res, "autoincr") - } - } - if col.IsCreated { - res = append(res, "created") - } - if col.IsUpdated { - res = append(res, "updated") - } - for name, _ := range col.Indexes { - index := table.Indexes[name] - var uistr string - if index.Type == xorm.UniqueType { - uistr = "unique" - } else if index.Type == xorm.IndexType { - uistr = "index" - } - if len(index.Cols) > 1 { - uistr += "(" + index.Name + ")" - } - res = append(res, uistr) - } - - nstr := col.SQLType.Name - if col.Length != 0 { - if col.Length2 != 0 { - nstr += fmt.Sprintf("(%v,%v)", col.Length, col.Length2) - } else { - nstr += fmt.Sprintf("(%v)", col.Length) - } - } - res = append(res, nstr) - - var tags []string - if genJson { - tags = append(tags, "json:\""+col.Name+"\"") - } - if len(res) > 0 { - tags = append(tags, "xorm:\""+strings.Join(res, " ")+"\"") - } - if len(tags) > 0 { - return "`" + strings.Join(tags, " ") + "`" - } else { - return "" - } -} diff --git a/xorm/lang.go b/xorm/lang.go deleted file mode 100644 index 1ac3569d..00000000 --- a/xorm/lang.go +++ /dev/null @@ -1,51 +0,0 @@ -package main - -import ( - "github.com/go-xorm/xorm" - "io/ioutil" - "strings" - "text/template" -) - -type LangTmpl struct { - Funcs template.FuncMap - Formater func(string) (string, error) - GenImports func([]*xorm.Table) map[string]string -} - -var ( - mapper = &xorm.SnakeMapper{} - langTmpls = map[string]LangTmpl{ - "go": GoLangTmpl, - "c++": CPlusTmpl, - } -) - -func loadConfig(f string) map[string]string { - bts, err := ioutil.ReadFile(f) - if err != nil { - return nil - } - configs := make(map[string]string) - lines := strings.Split(string(bts), "\n") - for _, line := range lines { - line = strings.TrimRight(line, "\r") - vs := strings.Split(line, "=") - if len(vs) == 2 { - configs[strings.TrimSpace(vs[0])] = strings.TrimSpace(vs[1]) - } - } - return configs -} - -func unTitle(src string) string { - if src == "" { - return "" - } - - if len(src) == 1 { - return strings.ToLower(string(src[0])) - } else { - return strings.ToLower(string(src[0])) + src[1:] - } -} diff --git a/xorm/reverse.go b/xorm/reverse.go deleted file mode 100644 index c8b97bcb..00000000 --- a/xorm/reverse.go +++ /dev/null @@ -1,282 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "io/ioutil" - "os" - "path" - "path/filepath" - "strconv" - "strings" //[SWH|+] - "text/template" - - "github.com/dvirsky/go-pylog/logging" - _ "github.com/go-sql-driver/mysql" - "github.com/go-xorm/xorm" - _ "github.com/lib/pq" - _ "github.com/mattn/go-sqlite3" - _ "github.com/ziutek/mymysql/godrv" -) - -var CmdReverse = &Command{ - 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 - driverName Database driver name, now supported four: mysql mymysql sqlite3 postgres - datasourceName Database connection uri, for detail infomation please visit driver's project page - tmplPath Template dir for generated. the default templates dir has provide 1 template - generatedPath This parameter is optional, if blank, the default value is model, then will - generated all codes in model dir -`, -} - -func init() { - CmdReverse.Run = runReverse - CmdReverse.Flags = map[string]bool{ - "-s": false, - "-l": false, - } -} - -var ( - genJson bool = false -) - -func printReversePrompt(flag string) { -} - -type Tmpl struct { - 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 - } - - return true -} - -func runReverse(cmd *Command, args []string) { - 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 - } - - var isMultiFile bool = true - if use, ok := cmd.Flags["-s"]; ok { - isMultiFile = !use - } - - curPath, err := os.Getwd() - if err != nil { - fmt.Println(curPath) - return - } - - var genDir string - var model string - if len(args) == 4 { - - 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 - } - - if !dirExists(dir) { - logging.Error("Template %v path is not exist", dir) - return - } - - 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 - } - } - - if langTmpl, ok = langTmpls[lang]; !ok { - fmt.Println("Unsupported programing language", lang) - return - } - - os.MkdirAll(genDir, os.ModePerm) - - 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 - } - - filepath.Walk(dir, func(f string, info os.FileInfo, err error) error { - if info.IsDir() { - return nil - } - - if info.Name() == "config" { - return nil - } - - bs, err := ioutil.ReadFile(f) - if err != nil { - logging.Error("%v", err) - return err - } - - t := template.New(f) - t.Funcs(langTmpl.Funcs) - - 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) - - 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) - } - - newbytes := bytes.NewBufferString("") - - t := &Tmpl{Tables: tbls, 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", err) - return err - } - } else { - source = string(tplcontent) - } - - 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 - } - - newbytes := bytes.NewBufferString("") - - 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 - }) - -} diff --git a/xorm/shell.go b/xorm/shell.go deleted file mode 100644 index 5258e4c4..00000000 --- a/xorm/shell.go +++ /dev/null @@ -1,147 +0,0 @@ -package main - -import ( - "fmt" - "github.com/go-xorm/xorm" - "strings" -) - -var CmdShell = &Command{ - UsageLine: "shell driverName datasourceName", - Short: "a general shell to operate all kinds of database", - Long: ` -general database's shell for sqlite3, mysql, postgres. - - driverName Database driver name, now supported four: mysql mymysql sqlite3 postgres - datasourceName Database connection uri, for detail infomation please visit driver's project page -`, -} - -func init() { - CmdShell.Run = runShell - CmdShell.Flags = map[string]bool{} -} - -var engine *xorm.Engine - -func shellHelp() { - fmt.Println(` - show tables show all tables - columns show table's column info - indexes show table's index info - exit exit shell - source exec sql file to current database - dump [-nodata] dump structs or records to sql file - help show this document - SQL statement - `) -} - -func runShell(cmd *Command, args []string) { - if len(args) != 2 { - fmt.Println("params error, please see xorm help shell") - return - } - - var err error - engine, err = xorm.NewEngine(args[0], args[1]) - if err != nil { - fmt.Println(err) - return - } - - err = engine.Ping() - if err != nil { - fmt.Println(err) - return - } - - var scmd string - fmt.Print("xorm$ ") - for { - var input string - _, err := fmt.Scan(&input) - if err != nil { - fmt.Println(err) - continue - } - if strings.ToLower(input) == "exit" { - fmt.Println("bye") - return - } - if !strings.HasSuffix(input, ";") { - scmd = scmd + " " + input - continue - } - scmd = scmd + " " + input - lcmd := strings.TrimSpace(strings.ToLower(scmd)) - if strings.HasPrefix(lcmd, "select") { - res, err := engine.Query(scmd + "\n") - if err != nil { - fmt.Println(err) - } else { - if len(res) <= 0 { - fmt.Println("no records") - } else { - columns := make(map[string]int) - for k, _ := range res[0] { - columns[k] = len(k) - } - - for _, m := range res { - for k, s := range m { - l := len(string(s)) - if l > columns[k] { - columns[k] = l - } - } - } - - var maxlen = 0 - for _, l := range columns { - maxlen = maxlen + l + 3 - } - maxlen = maxlen + 1 - - fmt.Println(strings.Repeat("-", maxlen)) - fmt.Print("|") - slice := make([]string, 0) - for k, l := range columns { - fmt.Print(" " + k + " ") - fmt.Print(strings.Repeat(" ", l-len(k))) - fmt.Print("|") - slice = append(slice, k) - } - fmt.Print("\n") - for _, r := range res { - fmt.Print("|") - for _, k := range slice { - fmt.Print(" " + string(r[k]) + " ") - fmt.Print(strings.Repeat(" ", columns[k]-len(string(r[k])))) - fmt.Print("|") - } - fmt.Print("\n") - } - fmt.Println(strings.Repeat("-", maxlen)) - //fmt.Println(res) - } - } - } else if lcmd == "show tables;" { - /*tables, err := engine.DBMetas() - if err != nil { - fmt.Println(err) - } else { - - }*/ - } else { - cnt, err := engine.Exec(scmd) - if err != nil { - fmt.Println(err) - } else { - fmt.Printf("%d records changed.\n", cnt) - } - } - scmd = "" - fmt.Print("xorm$ ") - } -} diff --git a/xorm/templates/c++/class.h.tpl b/xorm/templates/c++/class.h.tpl deleted file mode 100644 index 50a32ff8..00000000 --- a/xorm/templates/c++/class.h.tpl +++ /dev/null @@ -1,21 +0,0 @@ -{{ range .Imports}} -#include {{.}} -{{ end }} - -{{range .Tables}}class {{Mapper .Name}} { -{{$table := .}} -public: -{{range .Columns}}{{$name := Mapper .Name}} {{Type .}} Get{{Mapper .Name}}() { - return this->m_{{UnTitle $name}}; - } - - void Set{{$name}}({{Type .}} {{UnTitle $name}}) { - this->m_{{UnTitle $name}} = {{UnTitle $name}}; - } - -{{end}}private: -{{range .Columns}}{{$name := Mapper .Name}} {{Type .}} m_{{UnTitle $name}}; -{{end}} -} - -{{end}} \ No newline at end of file diff --git a/xorm/templates/c++/config b/xorm/templates/c++/config deleted file mode 100644 index 4965bae3..00000000 --- a/xorm/templates/c++/config +++ /dev/null @@ -1 +0,0 @@ -lang=c++ \ No newline at end of file diff --git a/xorm/templates/go/config b/xorm/templates/go/config deleted file mode 100644 index 6fdeea2b..00000000 --- a/xorm/templates/go/config +++ /dev/null @@ -1 +0,0 @@ -lang=go \ No newline at end of file diff --git a/xorm/templates/go/struct.go.tpl b/xorm/templates/go/struct.go.tpl deleted file mode 100644 index 8e59d688..00000000 --- a/xorm/templates/go/struct.go.tpl +++ /dev/null @@ -1,14 +0,0 @@ -package {{.Model}} - -import ( - {{range .Imports}}"{{.}}"{{end}} -) - -{{range .Tables}} -type {{Mapper .Name}} struct { -{{$table := .}} -{{range .Columns}} {{Mapper .Name}} {{Type .}} -{{end}} -} - -{{end}} \ No newline at end of file diff --git a/xorm/templates/goxorm/config b/xorm/templates/goxorm/config deleted file mode 100644 index 5d7bf321..00000000 --- a/xorm/templates/goxorm/config +++ /dev/null @@ -1,3 +0,0 @@ -lang=go -genJson=0 -prefix=cos_ diff --git a/xorm/templates/goxorm/struct.go.tpl b/xorm/templates/goxorm/struct.go.tpl deleted file mode 100644 index 91b00854..00000000 --- a/xorm/templates/goxorm/struct.go.tpl +++ /dev/null @@ -1,18 +0,0 @@ -package {{.Model}} - -{{$ilen := len .Imports}} -{{if gt $ilen 0}} -import ( - {{range .Imports}}"{{.}}"{{end}} -) -{{end}} - -{{range .Tables}} -type {{Mapper .Name}} struct { -{{$table := .}} -{{$columns := .Columns}} -{{range .ColumnsSeq}}{{$col := getCol $columns .}} {{Mapper $col.Name}} {{Type $col}} {{Tag $table $col}} -{{end}} -} - -{{end}} \ No newline at end of file diff --git a/xorm/xorm.go b/xorm/xorm.go deleted file mode 100644 index a027f85f..00000000 --- a/xorm/xorm.go +++ /dev/null @@ -1,162 +0,0 @@ -package main - -import ( - "fmt" - "github.com/dvirsky/go-pylog/logging" - "io" - "os" - "runtime" - "strings" - "sync" - "text/template" - "unicode" - "unicode/utf8" -) - -// +build go1.1 - -// Test that go1.1 tag above is included in builds. main.go refers to this definition. -const go11tag = true - -const version = "0.1" - -// Commands lists the available commands and help topics. -// The order here is the order in which they are printed by 'gopm help'. -var commands = []*Command{ - CmdReverse, - CmdShell, -} - -func init() { - runtime.GOMAXPROCS(runtime.NumCPU()) -} - -func main() { - logging.SetLevel(logging.ALL) - // Check length of arguments. - args := os.Args[1:] - if len(args) < 1 { - usage() - return - } - - // Show help documentation. - if args[0] == "help" { - help(args[1:]) - return - } - - // Check commands and run. - for _, comm := range commands { - if comm.Name() == args[0] && comm.Run != nil { - comm.Run(comm, args[1:]) - exit() - return - } - } - - fmt.Fprintf(os.Stderr, "xorm: unknown subcommand %q\nRun 'xorm help' for usage.\n", args[0]) - setExitStatus(2) - exit() -} - -var exitStatus = 0 -var exitMu sync.Mutex - -func setExitStatus(n int) { - exitMu.Lock() - if exitStatus < n { - exitStatus = n - } - exitMu.Unlock() -} - -var usageTemplate = `xorm is a database tool based xorm package. -Usage: - - xorm command [arguments] - -The commands are: -{{range .}}{{if .Runnable}} - {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}} - -Use "xorm help [command]" for more information about a command. - -Additional help topics: -{{range .}}{{if not .Runnable}} - {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}} - -Use "xorm help [topic]" for more information about that topic. - -` - -var helpTemplate = `{{if .Runnable}}usage: xorm {{.UsageLine}} - -{{end}}{{.Long | trim}} -` - -// tmpl executes the given template text on data, writing the result to w. -func tmpl(w io.Writer, text string, data interface{}) { - t := template.New("top") - t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize}) - template.Must(t.Parse(text)) - if err := t.Execute(w, data); err != nil { - panic(err) - } -} - -func capitalize(s string) string { - if s == "" { - return s - } - r, n := utf8.DecodeRuneInString(s) - return string(unicode.ToTitle(r)) + s[n:] -} - -func printUsage(w io.Writer) { - tmpl(w, usageTemplate, commands) -} - -func usage() { - printUsage(os.Stderr) - os.Exit(2) -} - -// help implements the 'help' command. -func help(args []string) { - if len(args) == 0 { - printUsage(os.Stdout) - // not exit 2: succeeded at 'gopm help'. - return - } - if len(args) != 1 { - fmt.Fprintf(os.Stderr, "usage: xorm help command\n\nToo many arguments given.\n") - os.Exit(2) // failed at 'gopm help' - } - - arg := args[0] - - for _, cmd := range commands { - if cmd.Name() == arg { - tmpl(os.Stdout, helpTemplate, cmd) - // not exit 2: succeeded at 'gopm help cmd'. - return - } - } - - fmt.Fprintf(os.Stderr, "Unknown help topic %#q. Run 'xorm help'.\n", arg) - os.Exit(2) // failed at 'gopm help cmd' -} - -var atexitFuncs []func() - -func atexit(f func()) { - atexitFuncs = append(atexitFuncs, f) -} - -func exit() { - for _, f := range atexitFuncs { - f() - } - os.Exit(exitStatus) -}