This commit is contained in:
Lunny Xiao 2014-01-21 22:27:28 +08:00
commit d67b6e7f80
27 changed files with 2280 additions and 834 deletions

33
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,33 @@
## Contributing to xorm
`xorm` has a backlog of pull requests, but contributions are still very
much welcome. You can help with patch review, submitting bug reports,
or adding new functionality. There is no formal style guide, but
please conform to the style of existing code and general Go formatting
conventions when submitting patches.
### Patch review
Help review existing open pull requests by commenting on the code or
proposed functionality.
### Bug reports
We appreciate any bug reports, but especially ones with self-contained
(doesn't depend on code outside of pq), minimal (can't be simplified
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).
If you implements a new database interface, you maybe need to add a <databasename>_test.go file.
For example, [mysql_test.go](https://github.com/lunny/xorm/blob/master/mysql_test.go)
### New functionality
There are a number of pending patches for new functionality, so
additional feature patches will take a while to merge. Still, patches
are generally reviewed based on usefulness and complexity in addition
to time-in-queue, so if you have a knockout idea, take a shot. Feel
free to open an issue discussion your proposed patch beforehand.

View File

@ -37,17 +37,25 @@ Drivers for Go's sql package which currently support database/sql includes:
* Postgres: [github.com/lib/pq](https://github.com/lib/pq)
* MsSql: [github.com/lunny/godbc](https://github.com/lunny/godbc)
# Changelog
* **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 handlerAdded 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.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.3.1**
Features:
* Support MSSQL DB via ODBC driver ([github.com/lunny/godbc](https://github.com/lunny/godbc));
* Composite Key, using multiple pk xorm tag
* Added Row() API as alternative to Iterate() API for traversing result set, provide similar usages to sql.Rows type
* ORM struct allowed declaration of pointer builtin type as members to allow null DB fields
* Before and After Event processors
Improvements:
* Allowed int/int32/int64/uint/uint32/uint64/string as Primary Key type
* Performance improvement for Get()/Find()/Iterate()
[More changelogs ...](https://github.com/lunny/xorm/blob/master/docs/Changelog.md)
# Installation
If you have [gopm](https://github.com/gpmgo/gopm) installed,
@ -76,12 +84,18 @@ Or
* [Very Hour](http://veryhour.com/)
# Todo
[Todo List](https://trello.com/b/IHsuAnhk/xorm)
# Discuss
Please visit [Xorm on Google Groups](https://groups.google.com/forum/#!forum/xorm)
# Contributors
If you want to pull request, please see [CONTRIBUTING](https://github.com/lunny/xorm/blob/master/CONTRIBUTING.md)
* [Lunny](https://github.com/lunny)
* [Nashtsai](https://github.com/nashtsai)

View File

@ -28,7 +28,7 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作
## 驱动支持
目前支持的Go数据库驱动如下
目前支持的Go数据库驱动和对应的数据库如下:
* Mysql: [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)
@ -38,13 +38,23 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作
* Postgres: [github.com/lib/pq](https://github.com/lib/pq)
* Postgres: [github.com/bylevel/pq](https://github.com/bylevel/pq)
* MsSql: [github.com/lunny/godbc](https://github.com/lunny/godbc)
## 更新日志
* **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.3.1**
新特性:
* 支持 MSSQL DB 通过 ODBC 驱动 ([github.com/lunny/godbc](https://github.com/lunny/godbc));
* 通过多个pk标记支持联合主键;
* 新增 Rows() API 用来遍历查询结果该函数提供了类似sql.Rows的相似用法可作为 Iterate() API 的可选替代;
* ORM 结构体现在允许内建类型的指针作为成员使得数据库为null成为可能
* Before 和 After 支持
改进:
* 允许 int/int32/int64/uint/uint32/uint64/string 作为主键类型
* 查询函数 Get()/Find()/Iterate() 在性能上的改进
[更多更新日志...](https://github.com/lunny/xorm/blob/master/docs/ChangelogCN.md)
@ -77,12 +87,18 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作
* [Very Hour](http://veryhour.com/)
## Todo
[开发计划](https://trello.com/b/IHsuAnhk/xorm)
## 讨论
请加入QQ群280360085 进行讨论。
# 贡献者
如果您也想为Xorm贡献您的力量请查看 [CONTRIBUTING](https://github.com/lunny/xorm/blob/master/CONTRIBUTING.md)
* [Lunny](https://github.com/lunny)
* [Nashtsai](https://github.com/nashtsai)

View File

@ -1 +1 @@
xorm v0.2.3
xorm v0.3.1

View File

@ -267,6 +267,16 @@ func insertTwoTable(engine *Engine, t *testing.T) {
}
}
type Article struct {
Id int32 `xorm:"pk INT autoincr"`
Name string `xorm:"VARCHAR(45)"`
Img string `xorm:"VARCHAR(100)"`
Aside string `xorm:"VARCHAR(200)"`
Desc string `xorm:"VARCHAR(200)"`
Content string `xorm:"TEXT"`
Status int8 `xorm:"TINYINT(4)"`
}
type Condi map[string]interface{}
func update(engine *Engine, t *testing.T) {
@ -314,6 +324,44 @@ func update(engine *Engine, t *testing.T) {
panic(err)
return
}
err = engine.Sync(&Article{})
if err != nil {
t.Error(err)
panic(err)
}
cnt, err = engine.Insert(&Article{0, "1", "2", "3", "4", "5", 2})
if err != nil {
t.Error(err)
panic(err)
}
if cnt != 1 {
err = errors.New("insert not returned 1")
t.Error(err)
panic(err)
return
}
cnt, err = engine.Id(1).Update(&Article{Name: "6"})
if err != nil {
t.Error(err)
panic(err)
}
if cnt != 1 {
err = errors.New("update not returned 1")
t.Error(err)
panic(err)
return
}
err = engine.DropTables(&Article{})
if err != nil {
t.Error(err)
panic(err)
}
}
func updateSameMapper(engine *Engine, t *testing.T) {
@ -359,7 +407,7 @@ func updateSameMapper(engine *Engine, t *testing.T) {
}
}
func testdelete(engine *Engine, t *testing.T) {
func testDelete(engine *Engine, t *testing.T) {
user := Userinfo{Uid: 1}
cnt, err := engine.Delete(&user)
if err != nil {
@ -557,20 +605,48 @@ func where(engine *Engine, t *testing.T) {
func in(engine *Engine, t *testing.T) {
users := make([]Userinfo, 0)
err := engine.In("(id)", 1, 2, 3).Find(&users)
err := engine.In("(id)", 7, 8, 9).Find(&users)
if err != nil {
t.Error(err)
panic(err)
}
fmt.Println(users)
if len(users) != 3 {
err = errors.New("in uses should be 7,8,9 total 3")
t.Error(err)
panic(err)
}
for _, user := range users {
if user.Uid != 7 && user.Uid != 8 && user.Uid != 9 {
err = errors.New("in uses should be 7,8,9 total 3")
t.Error(err)
panic(err)
}
}
users = make([]Userinfo, 0)
ids := []interface{}{7, 8, 9}
err = engine.Where("departname = ?", "dev").In("(id)", ids...).Find(&users)
if err != nil {
t.Error(err)
panic(err)
}
fmt.Println(users)
ids := []interface{}{1, 2, 3}
err = engine.Where("(id) > ?", 2).In("(id)", ids...).Find(&users)
if err != nil {
if len(users) != 3 {
err = errors.New("in uses should be 7,8,9 total 3")
t.Error(err)
panic(err)
}
fmt.Println(users)
for _, user := range users {
if user.Uid != 7 && user.Uid != 8 && user.Uid != 9 {
err = errors.New("in uses should be 7,8,9 total 3")
t.Error(err)
panic(err)
}
}
err = engine.In("(id)", 1).In("(id)", 2).In("departname", "dev").Find(&users)
if err != nil {
@ -1448,12 +1524,32 @@ func testIndexAndUnique(engine *Engine, t *testing.T) {
}
type IntId struct {
Id int
Id int `xorm:"pk autoincr"`
Name string
}
type Int32Id struct {
Id int32
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
}
@ -1470,11 +1566,51 @@ func testIntId(engine *Engine, t *testing.T) {
panic(err)
}
_, err = engine.Insert(&IntId{Name: "test"})
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) {
@ -1490,11 +1626,295 @@ func testInt32Id(engine *Engine, t *testing.T) {
panic(err)
}
_, err = engine.Insert(&Int32Id{Name: "test"})
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) {
@ -1529,6 +1949,27 @@ func testIterate(engine *Engine, t *testing.T) {
}
}
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
@ -2810,7 +3251,9 @@ func testPointerData(engine *Engine, t *testing.T) {
// using instance type should just work too
nullData2Get := NullData2{}
has, err = engine.Table("null_data").Id(nullData.Id).Get(&nullData2Get)
tableName := engine.tableMapper.Obj2Table("NullData")
has, err = engine.Table(tableName).Id(nullData.Id).Get(&nullData2Get)
if err != nil {
t.Error(err)
panic(err)
@ -3156,45 +3599,54 @@ func testNullValue(engine *Engine, t *testing.T) {
// t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Complex128Ptr)))
// }
/*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
fmt.Printf("time value: [%v]:[%v]", *nullDataGet.TimePtr, *nullDataUpdate.TimePtr)
fmt.Println()
}*/
// --
// !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{}
nullDataUpdate = NullData{}
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
}*/
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
}
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)))
}
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)))
}
@ -3342,17 +3794,11 @@ type Lowercase struct {
func testLowerCase(engine *Engine, t *testing.T) {
err := engine.Sync(&Lowercase{})
if err != nil {
t.Error(err)
panic(err)
}
_, err = engine.Where("id > 0").Delete(&Lowercase{})
if err != nil {
t.Error(err)
panic(err)
}
_, err = engine.Insert(&Lowercase{ended: 1})
if err != nil {
t.Error(err)
@ -3373,6 +3819,71 @@ func testLowerCase(engine *Engine, t *testing.T) {
}
}
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}"))
}
}
func testAll(engine *Engine, t *testing.T) {
fmt.Println("-------------- directCreateTable --------------")
directCreateTable(engine, t)
@ -3390,8 +3901,8 @@ func testAll(engine *Engine, t *testing.T) {
insertTwoTable(engine, t)
fmt.Println("-------------- update --------------")
update(engine, t)
fmt.Println("-------------- testdelete --------------")
testdelete(engine, t)
fmt.Println("-------------- testDelete --------------")
testDelete(engine, t)
fmt.Println("-------------- get --------------")
get(engine, t)
fmt.Println("-------------- cascadeGet --------------")
@ -3446,13 +3957,21 @@ func testAll2(engine *Engine, t *testing.T) {
fmt.Println("-------------- testIndexAndUnique --------------")
testIndexAndUnique(engine, t)
fmt.Println("-------------- testIntId --------------")
//testIntId(engine, t)
testIntId(engine, t)
fmt.Println("-------------- testInt32Id --------------")
//testInt32Id(engine, t)
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 --------------")
@ -3487,5 +4006,16 @@ func testAll3(engine *Engine, t *testing.T) {
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) {
}

1
benchmark.bat Normal file
View File

@ -0,0 +1 @@
go test -v -bench=. -run=XXX

9
doc.go
View File

@ -60,7 +60,14 @@ There are 7 major ORM methods and many helpful methods to use to operate databas
err := engine.Find(...)
// SELECT * FROM user
4. Query multiple records and record by record handle
4. Query multiple records and record by record handle, there two methods, one is Iterate,
another is Raws
raws, err := engine.Raws(...)
// SELECT * FROM user
for raws.Next() {
raws.Scan(bean)
}
err := engine.Iterate(...)
// SELECT * FROM user

View File

@ -1,5 +1,19 @@
## Changelog
* **v0.3.1**
Features:
* Support MSSQL DB via ODBC driver ([github.com/lunny/godbc](https://github.com/lunny/godbc));
* Composite Key, using multiple pk xorm tag
* Added Row() API as alternative to Iterate() API for traversing result set, provide similar usages to sql.Rows type
* ORM struct allowed declaration of pointer builtin type as members to allow null DB fields
* Before and After Event processors
Improvements:
* Allowed int/int32/int64/uint/uint32/uint64/string as Primary Key type
* Performance improvement for Get()/Find()/Iterate()
* **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 handlerAdded 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.

View File

@ -1,5 +1,18 @@
## 更新日志
* **v0.3.1**
新特性:
* 支持 MSSQL DB 通过 ODBC 驱动 ([github.com/lunny/godbc](https://github.com/lunny/godbc));
* 通过多个pk标记支持联合主键;
* 新增 Rows() API 用来遍历查询结果该函数提供了类似sql.Rows的相似用法可作为 Iterate() API 的可选替代;
* ORM 结构体现在允许内建类型的指针作为成员使得数据库为null成为可能
* Before 和 After 支持
改进:
* 允许 int/int32/int64/uint/uint32/uint64/string 作为主键类型
* 查询函数 Get()/Find()/Iterate() 在性能上的改进
* **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.

View File

@ -4,9 +4,10 @@ xorm 快速入门
* [1.创建Orm引擎](#10)
* [2.定义表结构体](#20)
* [2.1.名称映射规则](#21)
* [2.2.使用Table和Tag改变名称映射](#22)
* [2.3.Column属性定义](#23)
* [2.4.Go与字段类型对应表](#24)
* [2.2.前缀映射规则和后缀映射规则](#22)
* [2.3.使用Table和Tag改变名称映射](#23)
* [2.4.Column属性定义](#24)
* [2.5.Go与字段类型对应表](#25)
* [3.表结构操作](#30)
* [3.1 获取数据库信息](#31)
* [3.2 表操作](#32)
@ -20,6 +21,7 @@ xorm 快速入门
* [5.4.Find方法](#64)
* [5.5.Iterate方法](#65)
* [5.6.Count方法](#66)
* [5.7.Rows方法](#67)
* [6.更新数据](#70)
* [6.1.乐观锁](#71)
* [7.删除数据](#80)
@ -61,7 +63,7 @@ defer engine.Close()
一般如果只针对一个数据库进行操作只需要创建一个Engine即可。Engine支持在多GoRutine下使用。
xorm当前支持四种驱动如下:
xorm当前支持五种驱动四个数据库如下:
* Mysql: [github.com/Go-SQL-Driver/MySQL](https://github.com/Go-SQL-Driver/MySQL)
@ -71,11 +73,13 @@ xorm当前支持四种驱动如下
* Postgres: [github.com/lib/pq](https://github.com/lib/pq)
* MsSql: [github.com/lunny/godbc](https://githubcom/lunny/godbc)
NewEngine传入的参数和`sql.Open`传入的参数完全相同,因此,使用哪个驱动前,请查看此驱动中关于传入参数的说明文档。
在engine创建完成后可以进行一些设置
1.设置
1.错误显示设置,默认如下均为`false`
* `engine.ShowSQL = true`则会在控制台打印出生成的SQL语句
* `engine.ShowDebug = true`,则会在控制台打印调试信息;
@ -93,7 +97,7 @@ f, err := os.Create("sql.log")
engine.Logger = f
```
3.engine内部支持连接池接口默认使用的Go所实现的连接池同时自带了另外两种实现一种是不使用连接池另一种为一个自实现的连接池。推荐使用Go所实现的连接池。如果要使用自己实现的连接池可以实现`xorm.IConnectPool`并通过`engine.SetPool`进行设置。
3.engine内部支持连接池接口默认使用的Go所实现的连接池同时自带了另外两种实现一种是不使用连接池另一种为一个自实现的连接池。推荐使用Go所实现的连接池。如果要使用自己实现的连接池可以实现`xorm.IConnectPool`并通过`engine.SetPool`进行设置。推荐使用Go默认的连接池。
* 如果需要设置连接池的空闲数大小,可以使用`engine.SetIdleConns()`来实现。
* 如果需要设置最大打开连接数,则可以使用`engine.SetMaxConns()`来实现。
@ -106,25 +110,40 @@ xorm支持将一个struct映射为数据库中对应的一张表。映射规则
<a name="21" id="21"></a>
### 2.1.名称映射规则
名称映射规则主要负责结构体名称到表名和结构体field到表字段的名称映射。由xorm.IMapper接口的实现者来管理xorm内置了两种IMapper实现`SnakeMapper` 和 `SameMapper`。SnakeMapper支持struct为驼峰式命名表结构为下划线命名之间的转换SameMapper支持相同的命名。
名称映射规则主要负责结构体名称到表名和结构体field到表字段的名称映射。由xorm.IMapper接口的实现者来管理xorm内置了两种IMapper实现`SnakeMapper` 和 `SameMapper`。SnakeMapper支持struct为驼峰式命名表结构为下划线命名之间的转换SameMapper支持结构体名称和对应的表名称以及结构体field名称与对应的表字段名称相同的命名。
当前SnakeMapper为默认值如果需要改变时在engine创建完成后使用
```Go
engine.Mapper = SameMapper{}
engine.SetMapper(SameMapper{})
```
当然如果你使用了别的命名规则映射方案也可以自己实现一个IMapper。
同时需要注意的是:
* 如果你使用了别的命名规则映射方案也可以自己实现一个IMapper。
* 表名称和字段名称的映射规则默认是相同的,当然也可以设置为不同,如:
```Go
engine.SetTableMapper(SameMapper{})
engine.SetColumnMapper(SnakeMapper{})
```
<a name="22" id="22"></a>
### 2.2.使用Table和Tag改变名称映射
### 2.2.前缀映射规则和后缀映射规则
* 通过`engine.NewPrefixMapper(SnakeMapper{}, "prefix")`可以在SnakeMapper的基础上在命名中添加统一的前缀当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。
* 通过`engine.NewSufffixMapper(SnakeMapper{}, "suffix")`可以在SnakeMapper的基础上在命名中添加统一的后缀当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。
*
<a name="23" id="23"></a>
### 2.3.使用Table和Tag改变名称映射
如果所有的命名都是按照IMapper的映射来操作的那当然是最理想的。但是如果碰到某个表名或者某个字段名跟映射规则不匹配时我们就需要别的机制来改变。
通过`engine.Table()`方法可以改变struct对应的数据库表的名称通过sturct中field对应的Tag中使用`xorm:"'column_name'"`可以使该field对应的Column名称为指定名称。这里使用两个单引号将Column名称括起来是为了防止名称冲突因为我们在Tag中还可以对这个Column进行更多的定义。如果名称不冲突的情况单引号也可以不使用。
<a name="23" id="23"></a>
### 2.3.Column属性定义
### 2.4.Column属性定义
我们在field对应的Tag中对Column的一些属性进行定义定义的方法基本和我们写SQL定义表结构类似比如
```
@ -140,10 +159,10 @@ type User struct {
<table>
<tr>
<td>name</td><td>当前field对应的字段的名称可选如不写则自动根据field名字和转换规则命名</td>
<td>name</td><td>当前field对应的字段的名称可选如不写则自动根据field名字和转换规则命名,如与其它关键字冲突,请使用单引号括起来。</td>
</tr>
<tr>
<td>pk</td><td>是否是Primary Key如果在一个struct中有两个字段都使用了此标记,则这两个字段构成了复合主键</td>
<td>pk</td><td>是否是Primary Key如果在一个struct中有多个字段都使用了此标记则这多个字段构成了复合主键单主键当前支持int32,int,int64,uint32,uint,uint64,string这7种Go的数据类型复合主键支持这7种Go的数据类型的组合。</td>
</tr>
<tr>
<td>当前支持30多种字段类型详情参见 [字段类型](https://github.com/lunny/xorm/blob/master/docs/COLUMNTYPE.md)</td><td>字段类型</td>
@ -152,7 +171,7 @@ type User struct {
<td>autoincr</td><td>是否是自增</td>
</tr>
<tr>
<td>[not ]null</td><td>是否可以为空</td>
<td>[not ]null 或 notnull</td><td>是否可以为空</td>
</tr>
<tr>
<td>unique或unique(uniquename)</td><td>是否是唯一如不加括号则该字段不允许重复如加上括号则括号中为联合唯一索引的名字此时如果有另外一个或多个字段和本unique的uniquename相同则这些uniquename相同的字段组成联合唯一索引</td>
@ -188,11 +207,11 @@ type User struct {
另外有如下几条自动映射的规则:
- 1.如果field名称为`Id`而且类型为`int64`的话会被xorm视为主键并且拥有自增属性。如果想用`Id`以外的名字做为主键名,可以在对应的Tag上加上`xorm:"pk"`来定义主键。
- 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类型并且都以二进制方式存储。
- 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)
- 4.实现了Conversion接口的类型或者结构体将根据接口的转换方式在类型和数据库记录之间进行相互转换。
```Go
@ -218,41 +237,50 @@ xorm提供了一些动态获取和修改表结构的方法。对于一般的应
## 3.1 获取数据库信息
* DBMetas()
xorm支持获取表结构信息通过调用`engine.DBMetas()`可以获取到所有的表的信息
xorm支持获取表结构信息通过调用`engine.DBMetas()`可以获取到所有的表,字段,索引的信息。
<a name="31" id="31"></a>
## 3.2.表操作
* CreateTables()
创建表使用`engine.CreateTables()`参数为一个或多个空的对应Struct的指针。同时可用的方法有Charset()和StoreEngine()如果对应的数据库支持这两个方法可以在创建表时指定表的字符编码和使用的引擎。当前仅支持Mysql数据库。
* IsTableEmpty()
判断表是否为空参数和CreateTables相同
* IsTableExist()
判断表是否存在
* DropTables()
删除表使用`engine.DropTables()`参数为一个或多个空的对应Struct的指针或者表的名字。如果为string传入则只删除对应的表如果传入的为Struct则删除表的同时还会删除对应的索引。
<a name="32" id="32"></a>
## 3.3.创建索引和唯一索引
* CreateIndexes
根据struct中的tag来创建索引
* CreateUniques
根据struct中的tag来创建唯一索引
<a name="34" id="34"></a>
## 3.4.同步数据库结构
同步能够部分智能的根据结构体的变动检测表结构的变动,并自动同步。目前能够实现:
1) 自动检测和创建表,这个检测是根据表的名字
2自动检测和新增表中的字段这个检测是根据字段名
3自动检测和创建索引和唯一索引这个检测是根据一个或多个字段名而不根据索引名称
* 1) 自动检测和创建表,这个检测是根据表的名字
* 2自动检测和新增表中的字段这个检测是根据字段名
* 3自动检测和创建索引和唯一索引这个检测是根据一个或多个字段名而不根据索引名称
调用方法如下:
```Go
err := engine.Sync(new(User))
```
@ -264,18 +292,21 @@ err := engine.Sync(new(User))
如果传入的是Slice并且当数据库支持批量插入时Insert会使用批量插入的方式进行插入。
* 插入一条数据
```Go
user := new(User)
user.Name = "myname"
affected, err := engine.Insert(user)
```
在插入成功后如果该结构体有PK字段则PK字段会被自动赋值为数据库中的id
在插入单条数据成功后如果该结构体有自增字段则自增字段会被自动赋值为数据库中的id
```Go
fmt.Println(user.Id)
```
* 插入同一个表的多条数据
```Go
users := make([]User, 0)
users[0].Name = "name0"
@ -284,6 +315,7 @@ affected, err := engine.Insert(&users)
```
* 使用指针Slice插入多条记录
```Go
users := make([]*User, 0)
users[0] = new(User)
@ -293,6 +325,7 @@ affected, err := engine.Insert(&users)
```
* 插入不同表的一条记录
```Go
user := new(User)
user.Name = "myname"
@ -302,6 +335,7 @@ affected, err := engine.Insert(user, question)
```
* 插入不同表的多条记录
```Go
users := make([]User, 0)
users[0].Name = "name0"
@ -321,25 +355,27 @@ questions[0].Content = "whywhywhwy?"
affected, err := engine.Insert(user, &questions)
```
注意:这里虽然支持同时插入,但这些插入并没有事务关系。因此有可能在中间插入出错后,后面的插入将不会继续。
这里需要注意以下几点:
* 这里虽然支持同时插入,但这些插入并没有事务关系。因此有可能在中间插入出错后,后面的插入将不会继续。
* 多条插入会自动生成`Insert into table values (),(),()`的语句因此这样的语句有一个最大的记录数根据经验测算在150条左右。大于150条后生成的sql语句将太长可能导致执行失败。因此在插入大量数据时目前需要自行分割成每150条插入一次。
<a name="60" id="60"></a>
## 5.查询和统计数据
所有的查询条件不区分调用顺序但必须在调用GetFindCount这三个函数之前调用。同时需要注意的一点是在调用的参数中所有的字符字段名均为映射后的数据库的字段名而不是field的名字。
所有的查询条件不区分调用顺序但必须在调用GetFindCount, Iterate, Rows这几个函数之前调用。同时需要注意的一点是,在调用的参数中,如果采用默认的`SnakeMapper`所有的字符字段名均为映射后的数据库的字段名而不是field的名字。
<a name="61" id="61"></a>
### 5.1.查询条件方法
查询和统计主要使用`Get`, `Find`, `Count`个方法。在进行查询时可以使用多个方法来形成查询条件,条件函数如下:
查询和统计主要使用`Get`, `Find`, `Count`, `Rows`, `Iterate`这几个方法。在进行查询时可以使用多个方法来形成查询条件,条件函数如下:
* Id(interface{})
传入一个PK字段的值作为查询条件如果是复合主键
`Id(xorm.PK{1, 2})`
传入的两个参数按照struct中定义的顺序赋值。
传入的两个参数按照struct中pk标记字段出现的顺序赋值。
* Where(string, …interface{})
和Where语句中的条件基本相同作为条件
SQL中Where语句中的条件基本相同作为条件
* And(string, …interface{})
和Where函数中的条件基本相同作为条件
@ -360,7 +396,7 @@ affected, err := engine.Insert(user, &questions)
按照指定的顺序进行排序
* In(string, …interface{})
某字段在一些值中
某字段在一些值中,这里需要注意必须是[]interface{}才可以展开由于Go语言的限制[]int64等均不可以展开。
* Cols(…string)
只查询或更新某些指定的字段默认是查询所有映射的字段或者根据Update的第一个参数来判断更新的字段。例如
@ -429,20 +465,28 @@ Having的参数字符串
如:
1) 根据Id来获得单条数据:
```Go
user := new(User)
has, err := engine.Id(id).Get(user)
// 复合主键的获取方法
// has, errr := engine.Id(xorm.PK{1,2}).Get(user)
```
2) 根据Where来获得单条数据
```Go
user := new(User)
has, err := engine.Where("name=?", "xlw").Get(user)
```
3) 根据user结构体中已有的非空数据来获得单条数据
```Go
user := &User{Id:1}
has, err := engine.Get(user)
```
或者其它条件
```Go
@ -458,6 +502,7 @@ has, err := engine.Get(user)
查询多条数据使用`Find`方法Find方法的第一个参数为`slice`的指针或`Map`指针即为查询后返回的结果第二个参数可选为查询的条件struct的指针。
1) 传入Slice用于返回数据
```Go
everyone := make([]Userinfo, 0)
err := engine.Find(&everyone)
@ -466,7 +511,8 @@ pEveryOne := make([]*Userinfo, 0)
err := engine.Find(&pEveryOne)
```
2) 传入Map用户返回数据map必须为`map[int64]Userinfo`的形式map的key为id
2) 传入Map用户返回数据map必须为`map[int64]Userinfo`的形式map的key为id因此对于复合主键无法使用这种方式。
```Go
users := make(map[int64]Userinfo)
err := engine.Find(&users)
@ -476,6 +522,7 @@ err := engine.Find(&pUsers)
```
3) 也可以加入各种条件
```Go
users := make([]Userinfo, 0)
err := engine.Where("age > ? or name = ?", 30, "xlw").Limit(20, 10).Find(&users)
@ -485,6 +532,7 @@ err := engine.Where("age > ? or name = ?", 30, "xlw").Limit(20, 10).Find(&users)
### 5.5.Iterate方法
Iterate方法提供逐条执行查询到的记录的方法他所能使用的条件和Find方法完全相同
```Go
err := engine.Where("age > ? or name=?)", 30, "xlw").Iterate(new(Userinfo), func(i int, bean interface{})error{
user := bean.(*Userinfo)
@ -501,6 +549,22 @@ user := new(User)
total, err := engine.Where("id >?", 1).Count(user)
```
<a name="67" id="67"></a>
### 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)
//...
}
```
<a name="70" id="70"></a>
## 6.更新数据
@ -514,12 +578,14 @@ 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})
```
@ -548,6 +614,7 @@ engine.Id(1).Update(&user)
## 7.删除数据
删除数据`Delete`方法参数为struct的指针并且成为查询条件。
```Go
user := new(User)
affected, err := engine.Id(id).Delete(user)
@ -561,23 +628,27 @@ affected, err := engine.Id(id).Delete(user)
## 8.执行SQL查询
也可以直接执行一个SQL查询即Select命令。在Postgres中支持原始SQL语句中使用 ` 和 ? 符号。
```Go
sql := "select * from userinfo"
results, err := engine.Query(sql)
```
当调用`Query`时,第一个返回值`results`为`[]map[string][]byte`的形式。
<a name="100" id="100"></a>
## 9.执行SQL命令
也可以直接执行一个SQL命令即执行Insert Update Delete 等操作。同样在Postgres中支持原始SQL语句中使用 ` 和 ? 符号。
也可以直接执行一个SQL命令即执行Insert Update Delete 等操作。此时不管数据库是何种类型,都可以使用 ` 和 ? 符号。
```Go
sql = "update userinfo set username=? where id=?"
sql = "update `userinfo` set username=? where id=?"
res, err := engine.Exec(sql, "xiaolun", 1)
```
<a name="110" id="110"></a>
## 10.事务处理
当使用事务处理时需要创建Session对象。
当使用事务处理时需要创建Session对象。在进行事物处理时可以混用ORM方法和RAW方法如下代码所示
```Go
session := engine.NewSession()
@ -620,14 +691,17 @@ xorm内置了一致性缓存支持不过默认并没有开启。要开启缓
cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000)
engine.SetDefaultCacher(cacher)
```
上述代码采用了LRU算法的一个缓存缓存方式是存放到内存中缓存struct的记录数为1000条缓存针对的范围是所有具有主键的表没有主键的表中的数据将不会被缓存。
如果只想针对部分表,则:
```Go
cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000)
engine.MapCacher(&user, cacher)
```
如果要禁用某个表的缓存,则:
```Go
engine.MapCacher(&user, nil)
```
@ -638,16 +712,17 @@ engine.MapCacher(&user, nil)
不过需要特别注意不适用缓存或者需要手动编码的地方:
1. 在Get或者Find时使用了Cols方法在开启缓存后此方法无效系统仍旧会取出这个表中的所有字段。
1. 当使用了`Distinct`,`Having`,`GroupBy`方法将不会使用缓存
2. 在`Get`或者`Find`时使用了`Cols`,`Omit`方法,则在开启缓存后此方法无效,系统仍旧会取出这个表中的所有字段。
3. 在使用Exec方法执行了方法之后可能会导致缓存与数据库不一致的地方。因此如果启用缓存尽量避免使用Exec。如果必须使用则需要在使用了Exec之后调用ClearCache手动做缓存清除的工作。比如
2. 在使用Exec方法执行了方法之后可能会导致缓存与数据库不一致的地方。因此如果启用缓存尽量避免使用Exec。如果必须使用则需要在使用了Exec之后调用ClearCache手动做缓存清除的工作。比如
```Go
engine.Exec("update user set name = ? where id = ?", "xlw", 1)
engine.ClearCache(new(User))
```
ClearCacheBean
缓存的实现原理如下图所示:
![cache design](https://raw.github.com/lunny/xorm/master/docs/cache_design.png)

View File

@ -28,6 +28,7 @@ const (
// 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
@ -472,15 +473,16 @@ func (engine *Engine) mapType(t reflect.Type) *Table {
parentTable := engine.mapType(fieldType)
for name, col := range parentTable.Columns {
col.FieldName = fmt.Sprintf("%v.%v", fieldType.Name(), col.FieldName)
table.Columns[name] = col
table.Columns[strings.ToLower(name)] = col
table.ColumnsSeq = append(table.ColumnsSeq, name)
}
table.PrimaryKey = parentTable.PrimaryKey
table.PrimaryKeys = parentTable.PrimaryKeys
continue
}
var indexType int
var indexName string
var preKey string
for j, key := range tags {
k := strings.ToUpper(key)
switch {
@ -519,12 +521,13 @@ func (engine *Engine) mapType(t reflect.Type) *Table {
case k == "NOT":
default:
if strings.HasPrefix(k, "'") && strings.HasSuffix(k, "'") {
if key != col.Default {
if preKey != "DEFAULT" {
col.Name = key[1 : len(key)-1]
}
} else if strings.Contains(k, "(") && strings.HasSuffix(k, ")") {
fs := strings.Split(k, "(")
if _, ok := sqlTypes[fs[0]]; !ok {
preKey = k
continue
}
col.SQLType = SQLType{fs[0], 0, 0}
@ -538,12 +541,13 @@ func (engine *Engine) mapType(t reflect.Type) *Table {
} else {
if _, ok := sqlTypes[k]; ok {
col.SQLType = SQLType{k, 0, 0}
} else if key != col.Default {
} else if preKey != "DEFAULT" {
col.Name = key
}
}
engine.SqlType(col)
}
preKey = k
}
if col.SQLType.Name == "" {
col.SQLType = Type2SQLType(fieldType)
@ -602,12 +606,13 @@ func (engine *Engine) mapType(t reflect.Type) *Table {
}
}
if idFieldColName != "" && table.PrimaryKey == "" {
col := table.Columns[idFieldColName]
if idFieldColName != "" && len(table.PrimaryKeys) == 0 {
col := table.Columns[strings.ToLower(idFieldColName)]
col.IsPrimaryKey = true
col.IsAutoIncrement = true
col.Nullable = false
table.PrimaryKey = col.Name
table.PrimaryKeys = append(table.PrimaryKeys, col.Name)
table.AutoIncrement = col.Name
}
return table
@ -933,6 +938,13 @@ func (engine *Engine) Iterate(bean interface{}, fun IterFunc) error {
return session.Iterate(bean, fun)
}
// Return sql.Rows compatible Rows obj, as a forward Iterator object for iterating record by record, bean's non-empty fields
// are conditions.
func (engine *Engine) Rows(bean interface{}) (*Rows, error) {
session := engine.NewSession()
return session.Rows(bean)
}
// Count counts the records. bean's non-empty fields
// are conditions.
func (engine *Engine) Count(bean interface{}) (int64, error) {
@ -972,18 +984,22 @@ func (engine *Engine) Import(ddlPath string) ([]sql.Result, error) {
scanner.Split(semiColSpliter)
session := engine.NewSession()
session.IsAutoClose = false
defer session.Close()
err = session.newDb()
if err != nil {
return results, err
}
for scanner.Scan() {
query := scanner.Text()
query = strings.Trim(query, " \t")
if len(query) > 0 {
result, err := session.Exec(query)
result, err := session.Db.Exec(query)
results = append(results, result)
if err != nil {
lastError = err
}
}
}
session.Close()
return results, lastError
}

View File

@ -40,10 +40,10 @@ type IdFilter struct {
}
func (i *IdFilter) Do(sql string, session *Session) string {
if session.Statement.RefTable != nil && session.Statement.RefTable.PrimaryKey != "" {
sql = strings.Replace(sql, "`(id)`", session.Engine.Quote(session.Statement.RefTable.PrimaryKey), -1)
sql = strings.Replace(sql, session.Engine.Quote("(id)"), session.Engine.Quote(session.Statement.RefTable.PrimaryKey), -1)
return strings.Replace(sql, "(id)", session.Engine.Quote(session.Statement.RefTable.PrimaryKey), -1)
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
}

View File

@ -1,8 +1,12 @@
package xorm
import (
"database/sql"
"fmt"
"reflect"
"strconv"
"strings"
"time"
)
func indexNoCase(s, sep string) int {
@ -61,3 +65,72 @@ func sliceEq(left, right []string) bool {
return true
}
func value2Bytes(rawValue *reflect.Value) (data []byte, err error) {
aa := reflect.TypeOf((*rawValue).Interface())
vv := reflect.ValueOf((*rawValue).Interface())
var str string
switch aa.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
str = strconv.FormatInt(vv.Int(), 10)
data = []byte(str)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
str = strconv.FormatUint(vv.Uint(), 10)
data = []byte(str)
case reflect.Float32, reflect.Float64:
str = strconv.FormatFloat(vv.Float(), 'f', -1, 64)
data = []byte(str)
case reflect.String:
str = vv.String()
data = []byte(str)
case reflect.Array, reflect.Slice:
switch aa.Elem().Kind() {
case reflect.Uint8:
data = rawValue.Interface().([]byte)
default:
err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name())
}
//时间类型
case reflect.Struct:
if aa == reflect.TypeOf(c_TIME_DEFAULT) {
str = rawValue.Interface().(time.Time).Format(time.RFC3339Nano)
data = []byte(str)
} else {
err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name())
}
case reflect.Bool:
str = strconv.FormatBool(vv.Bool())
data = []byte(str)
case reflect.Complex128, reflect.Complex64:
str = fmt.Sprintf("%v", vv.Complex())
data = []byte(str)
/* TODO: unsupported types below
case reflect.Map:
case reflect.Ptr:
case reflect.Uintptr:
case reflect.UnsafePointer:
case reflect.Chan, reflect.Func, reflect.Interface:
*/
default:
err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name())
}
return
}
func rows2maps(rows *sql.Rows) (resultsSlice []map[string][]byte, err error) {
fields, err := rows.Columns()
if err != nil {
return nil, err
}
for rows.Next() {
result, err := row2map(rows, fields)
if err != nil {
return nil, err
}
resultsSlice = append(resultsSlice, result)
}
return resultsSlice, nil
}

View File

@ -22,6 +22,7 @@ type odbcParser struct {
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 {
@ -155,6 +156,7 @@ where a.object_id=object_id('` + tableName + `')`
for name, content := range record {
switch name {
case "name":
col.Name = strings.Trim(string(content), "` ")
case "ctype":
ct := strings.ToUpper(string(content))
@ -163,11 +165,14 @@ where a.object_id=object_id('` + tableName + `')`
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", ct, col))
return nil, nil, errors.New(fmt.Sprintf("unknow colType %v for %v - %v",
ct, tableName, col.Name))
}
}

View File

@ -4,18 +4,16 @@ package xorm
// +build windows
import (
"database/sql"
"testing"
_ "github.com/lunny/godbc"
)
/*
CREATE DATABASE IF NOT EXISTS xorm_test CHARACTER SET
utf8 COLLATE utf8_general_ci;
*/
const mssqlConnStr = "driver={SQL Server};Server=192.168.20.135;Database=xorm_test; uid=sa; pwd=1234;"
func newMssqlEngine() (*Engine, error) {
return NewEngine("odbc", "driver={SQL Server};Server=192.168.20.135;Database=xorm_test; uid=sa; pwd=1234;")
return NewEngine("odbc", mssqlConnStr)
}
func TestMssql(t *testing.T) {
@ -51,7 +49,41 @@ func TestMssqlWithCache(t *testing.T) {
testAll2(engine, t)
}
func BenchmarkMssqlNoCache(t *testing.B) {
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 {
@ -62,7 +94,18 @@ func BenchmarkMssqlNoCache(t *testing.B) {
doBenchFind(engine, t)
}
func BenchmarkMssqlCache(t *testing.B) {
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 {
@ -70,5 +113,30 @@ func BenchmarkMssqlCache(t *testing.B) {
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)
}

View File

@ -33,7 +33,6 @@ type mysqlParser struct {
}
func (p *mysqlParser) parse(driverName, dataSourceName string) (*uri, error) {
//cfg.params = make(map[string]string)
dsnPattern := regexp.MustCompile(
`^(?:(?P<user>.*?)(?::(?P<passwd>.*))?@)?` + // [user[:password]@]
`(?:(?P<net>[^\(]*)(?:\((?P<addr>[^\)]*)\))?)?` + // [net[(addr)]]
@ -49,6 +48,20 @@ func (p *mysqlParser) parse(driverName, dataSourceName string) (*uri, error) {
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
@ -68,6 +81,10 @@ func (b *base) init(parser parser, drivername, dataSourceName string) (err error
return
}
func (b *base) URI() *uri {
return b.uri
}
func (b *base) DBType() string {
return b.uri.dbType
}

View File

@ -12,8 +12,6 @@ CREATE DATABASE IF NOT EXISTS xorm_test CHARACTER SET
utf8 COLLATE utf8_general_ci;
*/
var mysqlShowTestSql bool = true
func TestMysql(t *testing.T) {
err := mysqlDdlImport()
if err != nil {
@ -27,10 +25,34 @@ func TestMysql(t *testing.T) {
t.Error(err)
return
}
engine.ShowSQL = mysqlShowTestSql
engine.ShowErr = mysqlShowTestSql
engine.ShowWarn = mysqlShowTestSql
engine.ShowDebug = mysqlShowTestSql
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)
@ -51,10 +73,10 @@ func TestMysqlWithCache(t *testing.T) {
return
}
engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000))
engine.ShowSQL = mysqlShowTestSql
engine.ShowErr = mysqlShowTestSql
engine.ShowWarn = mysqlShowTestSql
engine.ShowDebug = mysqlShowTestSql
engine.ShowSQL = showTestSql
engine.ShowErr = showTestSql
engine.ShowWarn = showTestSql
engine.ShowDebug = showTestSql
testAll(engine, t)
testAll2(engine, t)
@ -69,10 +91,10 @@ func mysqlDdlImport() error {
if err != nil {
return err
}
engine.ShowSQL = mysqlShowTestSql
engine.ShowErr = mysqlShowTestSql
engine.ShowWarn = mysqlShowTestSql
engine.ShowDebug = mysqlShowTestSql
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)

View File

@ -67,7 +67,11 @@ func (db *postgres) SqlType(c *Column) string {
switch t := c.SQLType.Name; t {
case TinyInt:
res = SmallInt
return res
case MediumInt, Int, Integer:
if c.IsAutoIncrement {
return Serial
}
return Integer
case Serial, BigSerial:
c.IsAutoIncrement = true

View File

@ -7,12 +7,16 @@ import (
_ "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", "dbname=xorm_test sslmode=disable")
return NewEngine("postgres", connStr)
}
func newPostgresDriverDB() (*sql.DB, error) {
return sql.Open("postgres", "dbname=xorm_test sslmode=disable")
return sql.Open("postgres", connStr)
}
func TestPostgres(t *testing.T) {

145
rows.go Normal file
View File

@ -0,0 +1,145 @@
package xorm
import (
"database/sql"
"fmt"
"reflect"
)
type Rows struct {
NoTypeCheck bool
session *Session
stmt *sql.Stmt
rows *sql.Rows
fields []string
fieldsCount int
beanType reflect.Type
lastError error
}
func newRows(session *Session, bean interface{}) (*Rows, error) {
rows := new(Rows)
rows.session = session
rows.beanType = reflect.Indirect(reflect.ValueOf(bean)).Type()
err := rows.session.newDb()
if err != nil {
return nil, err
}
defer rows.session.Statement.Init()
var sql 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)
} else {
sql = rows.session.Statement.RawSQL
args = rows.session.Statement.RawParams
}
for _, filter := range rows.session.Engine.Filters {
sql = filter.Do(sql, session)
}
rows.session.Engine.LogSQL(sql)
rows.session.Engine.LogSQL(args)
rows.stmt, err = rows.session.Db.Prepare(sql)
if err != nil {
rows.lastError = err
defer rows.Close()
return nil, err
}
rows.rows, err = rows.stmt.Query(args...)
if err != nil {
rows.lastError = err
defer rows.Close()
return nil, err
}
rows.fields, err = rows.rows.Columns()
if err != nil {
rows.lastError = err
defer rows.Close()
return nil, err
}
rows.fieldsCount = len(rows.fields)
return rows, nil
}
// move cursor to next record, return false if end has reached
func (rows *Rows) Next() bool {
if rows.lastError == nil && rows.rows != nil {
hasNext := rows.rows.Next()
if !hasNext {
rows.lastError = sql.ErrNoRows
}
return hasNext
}
return false
}
// Err returns the error, if any, that was encountered during iteration. Err may be called after an explicit or implicit Close.
func (rows *Rows) Err() error {
return rows.lastError
}
// scan row record to bean properties
func (rows *Rows) Scan(bean interface{}) error {
if rows.lastError != nil {
return rows.lastError
}
if !rows.NoTypeCheck && reflect.Indirect(reflect.ValueOf(bean)).Type() != rows.beanType {
return fmt.Errorf("scan arg is incompatible type to [%v]", rows.beanType)
}
return rows.session.row2Bean(rows.rows, rows.fields, rows.fieldsCount, bean)
// result, err := row2map(rows.rows, rows.fields) // !nashtsai! TODO remove row2map then scanMapIntoStruct conversation for better performance
// if err == nil {
// err = rows.session.scanMapIntoStruct(bean, result)
// }
// return err
}
// // Columns returns the column names. Columns returns an error if the rows are closed, or if the rows are from QueryRow and there was a deferred error.
// func (rows *Rows) Columns() ([]string, error) {
// if rows.lastError == nil && rows.rows != nil {
// return rows.rows.Columns()
// }
// return nil, rows.lastError
// }
// close session if session.IsAutoClose is true, and claimed any opened resources
func (rows *Rows) Close() error {
if rows.session.IsAutoClose {
defer rows.session.Close()
}
if rows.lastError == nil {
if rows.rows != nil {
rows.lastError = rows.rows.Close()
if rows.lastError != nil {
defer rows.stmt.Close()
return rows.lastError
}
}
if rows.stmt != nil {
rows.lastError = rows.stmt.Close()
}
} else {
if rows.stmt != nil {
defer rows.stmt.Close()
}
if rows.rows != nil {
defer rows.rows.Close()
}
}
return rows.lastError
}

File diff suppressed because it is too large Load Diff

View File

@ -190,16 +190,19 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*Index, error) {
indexes := make(map[string]*Index, 0)
for _, record := range res {
var sql string
index := new(Index)
for name, content := range record {
if name == "sql" {
sql = string(content)
}
sql := string(record["sql"])
if sql == "" {
continue
}
nNStart := strings.Index(sql, "INDEX")
nNEnd := strings.Index(sql, "ON")
if nNStart == -1 || nNEnd == -1 {
continue
}
indexName := strings.Trim(sql[nNStart+6:nNEnd], "` []")
//fmt.Println(indexName)
if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {

View File

@ -335,11 +335,15 @@ func buildConditions(engine *Engine, table *Table, bean interface{},
} else {
engine.autoMapType(fieldValue.Type())
if table, ok := engine.Tables[fieldValue.Type()]; ok {
pkField := reflect.Indirect(fieldValue).FieldByName(table.PKColumn().FieldName)
if pkField.Int() != 0 {
val = pkField.Interface()
if len(table.PrimaryKeys) == 1 {
pkField := reflect.Indirect(fieldValue).FieldByName(table.PKColumns()[0].FieldName)
if pkField.Int() != 0 {
val = pkField.Interface()
} else {
continue
}
} else {
continue
//TODO: how to handler?
}
} else {
val = fieldValue.Interface()
@ -506,7 +510,7 @@ func (statement *Statement) Distinct(columns ...string) *Statement {
func (statement *Statement) Cols(columns ...string) *Statement {
newColumns := col2NewCols(columns...)
for _, nc := range newColumns {
statement.columnMap[nc] = true
statement.columnMap[strings.ToLower(nc)] = true
}
statement.ColumnStr = statement.Engine.Quote(strings.Join(newColumns, statement.Engine.Quote(", ")))
return statement
@ -517,7 +521,7 @@ func (statement *Statement) UseBool(columns ...string) *Statement {
if len(columns) > 0 {
newColumns := col2NewCols(columns...)
for _, nc := range newColumns {
statement.boolColumnMap[nc] = true
statement.boolColumnMap[strings.ToLower(nc)] = true
}
} else {
statement.allUseBool = true
@ -529,7 +533,7 @@ func (statement *Statement) UseBool(columns ...string) *Statement {
func (statement *Statement) Omit(columns ...string) {
newColumns := col2NewCols(columns...)
for _, nc := range newColumns {
statement.columnMap[nc] = false
statement.columnMap[strings.ToLower(nc)] = false
}
statement.OmitStr = statement.Engine.Quote(strings.Join(newColumns, statement.Engine.Quote(", ")))
}
@ -582,7 +586,7 @@ func (statement *Statement) genColumnStr() string {
colNames := make([]string, 0)
for _, col := range table.Columns {
if statement.OmitStr != "" {
if _, ok := statement.columnMap[col.Name]; ok {
if _, ok := statement.columnMap[strings.ToLower(col.Name)]; ok {
continue
}
}
@ -606,7 +610,7 @@ func (statement *Statement) genCreateTableSQL() string {
pkList := []string{}
for _, colName := range statement.RefTable.ColumnsSeq {
col := statement.RefTable.Columns[colName]
col := statement.RefTable.Columns[strings.ToLower(colName)]
if col.IsPrimaryKey {
pkList = append(pkList, col.Name)
}
@ -614,7 +618,7 @@ func (statement *Statement) genCreateTableSQL() string {
statement.Engine.LogDebug("len:", len(pkList))
for _, colName := range statement.RefTable.ColumnsSeq {
col := statement.RefTable.Columns[colName]
col := statement.RefTable.Columns[strings.ToLower(colName)]
if col.IsPrimaryKey && len(pkList) == 1 {
sql += col.String(statement.Engine.dialect)
} else {
@ -634,8 +638,12 @@ func (statement *Statement) genCreateTableSQL() string {
if statement.Engine.dialect.SupportEngine() && statement.StoreEngine != "" {
sql += " ENGINE=" + statement.StoreEngine
}
if statement.Engine.dialect.SupportCharset() && statement.Charset != "" {
sql += " DEFAULT CHARSET " + statement.Charset
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
@ -753,9 +761,10 @@ func (statement *Statement) genCountSql(bean interface{}) (string, []interface{}
statement.ConditionStr = strings.Join(colNames, " AND ")
statement.BeanArgs = args
var id string = "*"
if table.PrimaryKey != "" {
id = statement.Engine.Quote(table.PrimaryKey)
// count(index fieldname) > count(0) > count(*)
var id string = "0"
if len(table.PrimaryKeys) == 1 {
id = statement.Engine.Quote(table.PrimaryKeys[0])
}
return statement.genSelectSql(fmt.Sprintf("COUNT(%v) AS %v", id, statement.Engine.Quote("total"))), append(statement.Params, statement.BeanArgs...)
}
@ -818,7 +827,7 @@ func (statement *Statement) processIdParam() {
for _, elem := range *(statement.IdParam) {
for ; i < colCnt; i++ {
colName := statement.RefTable.ColumnsSeq[i]
col := statement.RefTable.Columns[colName]
col := statement.RefTable.Columns[strings.ToLower(colName)]
if col.IsPrimaryKey {
statement.And(fmt.Sprintf("%v=?", col.Name), elem)
i++
@ -832,7 +841,7 @@ func (statement *Statement) processIdParam() {
// false update/delete
for ; i < colCnt; i++ {
colName := statement.RefTable.ColumnsSeq[i]
col := statement.RefTable.Columns[colName]
col := statement.RefTable.Columns[strings.ToLower(colName)]
if col.IsPrimaryKey {
statement.And(fmt.Sprintf("%v=?", col.Name), "")
}

View File

@ -163,6 +163,7 @@ func Type2SQLType(t reflect.Type) (st SQLType) {
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:
@ -297,6 +298,8 @@ func (col *Column) String(d dialect) string {
if col.Default != "" {
sql += "DEFAULT " + col.Default + " "
} else if col.IsVersion {
sql += "DEFAULT 1 "
}
return sql
@ -315,6 +318,8 @@ func (col *Column) stringNoPk(d dialect) string {
if col.Default != "" {
sql += "DEFAULT " + col.Default + " "
} else if col.IsVersion {
sql += "DEFAULT 1 "
}
return sql
@ -339,16 +344,17 @@ func (col *Column) ValueOf(bean interface{}) reflect.Value {
// database table
type Table struct {
Name string
Type reflect.Type
ColumnsSeq []string
Columns map[string]*Column
Indexes map[string]*Index
PrimaryKey string
Created map[string]bool
Updated string
Version string
Cacher Cacher
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
}
/*
@ -362,20 +368,31 @@ func NewTable(name string, t reflect.Type) *Table {
}*/
// if has primary key, return column
func (table *Table) PKColumn() *Column {
return table.Columns[table.PrimaryKey]
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[table.Version]
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[col.Name] = col
table.Columns[strings.ToLower(col.Name)] = col
if col.IsPrimaryKey {
table.PrimaryKey = col.Name
table.PrimaryKeys = append(table.PrimaryKeys, col.Name)
}
if col.IsAutoIncrement {
table.AutoIncrement = col.Name
}
if col.IsCreated {
table.Created[col.Name] = true
@ -398,8 +415,9 @@ func (table *Table) genCols(session *Session, bean interface{}, useCol bool, inc
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[col.Name]; !ok {
if _, ok := session.Statement.columnMap[lColName]; !ok {
continue
}
}
@ -408,17 +426,30 @@ func (table *Table) genCols(session *Session, bean interface{}, useCol bool, inc
}
fieldValue := col.ValueOf(bean)
if col.IsAutoIncrement && fieldValue.Int() == 0 {
continue
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[col.Name]; !ok {
if _, ok := session.Statement.columnMap[lColName]; !ok {
continue
}
}
if session.Statement.OmitStr != "" {
if _, ok := session.Statement.columnMap[col.Name]; ok {
if _, ok := session.Statement.columnMap[lColName]; ok {
continue
}
}

View File

@ -10,7 +10,7 @@ import (
)
const (
Version string = "0.2.3"
Version string = "0.3.1"
)
func close(engine *Engine) {