merge
This commit is contained in:
commit
d67b6e7f80
|
@ -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.
|
24
README.md
24
README.md
|
@ -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)
|
* Postgres: [github.com/lib/pq](https://github.com/lib/pq)
|
||||||
|
|
||||||
|
* MsSql: [github.com/lunny/godbc](https://github.com/lunny/godbc)
|
||||||
|
|
||||||
# Changelog
|
# 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.3.1**
|
||||||
* **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.
|
Features:
|
||||||
* **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;
|
* 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)
|
[More changelogs ...](https://github.com/lunny/xorm/blob/master/docs/Changelog.md)
|
||||||
|
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
If you have [gopm](https://github.com/gpmgo/gopm) installed,
|
If you have [gopm](https://github.com/gpmgo/gopm) installed,
|
||||||
|
@ -76,12 +84,18 @@ Or
|
||||||
|
|
||||||
* [Very Hour](http://veryhour.com/)
|
* [Very Hour](http://veryhour.com/)
|
||||||
|
|
||||||
|
# Todo
|
||||||
|
|
||||||
|
[Todo List](https://trello.com/b/IHsuAnhk/xorm)
|
||||||
|
|
||||||
# Discuss
|
# Discuss
|
||||||
|
|
||||||
Please visit [Xorm on Google Groups](https://groups.google.com/forum/#!forum/xorm)
|
Please visit [Xorm on Google Groups](https://groups.google.com/forum/#!forum/xorm)
|
||||||
|
|
||||||
# Contributors
|
# Contributors
|
||||||
|
|
||||||
|
If you want to pull request, please see [CONTRIBUTING](https://github.com/lunny/xorm/blob/master/CONTRIBUTING.md)
|
||||||
|
|
||||||
* [Lunny](https://github.com/lunny)
|
* [Lunny](https://github.com/lunny)
|
||||||
* [Nashtsai](https://github.com/nashtsai)
|
* [Nashtsai](https://github.com/nashtsai)
|
||||||
|
|
||||||
|
|
28
README_CN.md
28
README_CN.md
|
@ -28,7 +28,7 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作
|
||||||
|
|
||||||
## 驱动支持
|
## 驱动支持
|
||||||
|
|
||||||
目前支持的Go数据库驱动如下:
|
目前支持的Go数据库驱动和对应的数据库如下:
|
||||||
|
|
||||||
* Mysql: [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)
|
* Mysql: [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)
|
||||||
|
|
||||||
|
@ -38,13 +38,23 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作
|
||||||
|
|
||||||
* Postgres: [github.com/lib/pq](https://github.com/lib/pq)
|
* 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.3.1**
|
||||||
* **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同步表结构;
|
新特性:
|
||||||
|
* 支持 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)
|
[更多更新日志...](https://github.com/lunny/xorm/blob/master/docs/ChangelogCN.md)
|
||||||
|
|
||||||
|
@ -77,12 +87,18 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作
|
||||||
|
|
||||||
* [Very Hour](http://veryhour.com/)
|
* [Very Hour](http://veryhour.com/)
|
||||||
|
|
||||||
|
## Todo
|
||||||
|
|
||||||
|
[开发计划](https://trello.com/b/IHsuAnhk/xorm)
|
||||||
|
|
||||||
## 讨论
|
## 讨论
|
||||||
|
|
||||||
请加入QQ群:280360085 进行讨论。
|
请加入QQ群:280360085 进行讨论。
|
||||||
|
|
||||||
# 贡献者
|
# 贡献者
|
||||||
|
|
||||||
|
如果您也想为Xorm贡献您的力量,请查看 [CONTRIBUTING](https://github.com/lunny/xorm/blob/master/CONTRIBUTING.md)
|
||||||
|
|
||||||
* [Lunny](https://github.com/lunny)
|
* [Lunny](https://github.com/lunny)
|
||||||
* [Nashtsai](https://github.com/nashtsai)
|
* [Nashtsai](https://github.com/nashtsai)
|
||||||
|
|
||||||
|
|
636
base_test.go
636
base_test.go
|
@ -267,6 +267,16 @@ func insertTwoTable(engine *Engine, t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Article struct {
|
||||||
|
Id int32 `xorm:"pk INT autoincr"`
|
||||||
|
Name string `xorm:"VARCHAR(45)"`
|
||||||
|
Img string `xorm:"VARCHAR(100)"`
|
||||||
|
Aside string `xorm:"VARCHAR(200)"`
|
||||||
|
Desc string `xorm:"VARCHAR(200)"`
|
||||||
|
Content string `xorm:"TEXT"`
|
||||||
|
Status int8 `xorm:"TINYINT(4)"`
|
||||||
|
}
|
||||||
|
|
||||||
type Condi map[string]interface{}
|
type Condi map[string]interface{}
|
||||||
|
|
||||||
func update(engine *Engine, t *testing.T) {
|
func update(engine *Engine, t *testing.T) {
|
||||||
|
@ -314,6 +324,44 @@ func update(engine *Engine, t *testing.T) {
|
||||||
panic(err)
|
panic(err)
|
||||||
return
|
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) {
|
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}
|
user := Userinfo{Uid: 1}
|
||||||
cnt, err := engine.Delete(&user)
|
cnt, err := engine.Delete(&user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -557,20 +605,48 @@ func where(engine *Engine, t *testing.T) {
|
||||||
|
|
||||||
func in(engine *Engine, t *testing.T) {
|
func in(engine *Engine, t *testing.T) {
|
||||||
users := make([]Userinfo, 0)
|
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 {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
fmt.Println(users)
|
fmt.Println(users)
|
||||||
|
|
||||||
ids := []interface{}{1, 2, 3}
|
if len(users) != 3 {
|
||||||
err = engine.Where("(id) > ?", 2).In("(id)", ids...).Find(&users)
|
err = errors.New("in uses should be 7,8,9 total 3")
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
panic(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)
|
err = engine.In("(id)", 1).In("(id)", 2).In("departname", "dev").Find(&users)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1448,12 +1524,32 @@ func testIndexAndUnique(engine *Engine, t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type IntId struct {
|
type IntId struct {
|
||||||
Id int
|
Id int `xorm:"pk autoincr"`
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Int32Id struct {
|
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
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1470,11 +1566,51 @@ func testIntId(engine *Engine, t *testing.T) {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = engine.Insert(&IntId{Name: "test"})
|
cnt, err := engine.Insert(&IntId{Name: "test"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
panic(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) {
|
func testInt32Id(engine *Engine, t *testing.T) {
|
||||||
|
@ -1490,11 +1626,295 @@ func testInt32Id(engine *Engine, t *testing.T) {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = engine.Insert(&Int32Id{Name: "test"})
|
cnt, err := engine.Insert(&Int32Id{Name: "test"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
panic(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) {
|
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 {
|
type StrangeName struct {
|
||||||
Id_t int64 `xorm:"pk autoincr"`
|
Id_t int64 `xorm:"pk autoincr"`
|
||||||
Name string
|
Name string
|
||||||
|
@ -2810,7 +3251,9 @@ func testPointerData(engine *Engine, t *testing.T) {
|
||||||
// using instance type should just work too
|
// using instance type should just work too
|
||||||
nullData2Get := NullData2{}
|
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 {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
panic(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)))
|
// t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]", *nullDataGet.Complex128Ptr)))
|
||||||
// }
|
// }
|
||||||
|
|
||||||
/*if (*nullDataGet.TimePtr).Unix() != (*nullDataUpdate.TimePtr).Unix() {
|
// !nashtsai! skipped mymysql test due to driver will round up time caused inaccuracy comparison
|
||||||
t.Error(errors.New(fmt.Sprintf("inserted value unmatch: [%v]:[%v]", *nullDataGet.TimePtr, *nullDataUpdate.TimePtr)))
|
// skipped postgres test due to postgres driver doesn't read time.Time's timzezone info when stored in the db
|
||||||
} else {
|
// mysql and sqlite3 seem have done this correctly by storing datatime in UTC timezone, I think postgres driver
|
||||||
// !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
|
// prefer using timestamp with timezone to sovle the issue
|
||||||
fmt.Printf("time value: [%v]:[%v]", *nullDataGet.TimePtr, *nullDataUpdate.TimePtr)
|
if engine.DriverName != POSTGRES && engine.DriverName != MYMYSQL &&
|
||||||
fmt.Println()
|
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
|
// update to null values
|
||||||
/*nullDataUpdate = NullData{}
|
nullDataUpdate = NullData{}
|
||||||
|
|
||||||
cnt, err = engine.Id(nullData.Id).Update(&nullDataUpdate)
|
string_ptr := engine.columnMapper.Obj2Table("StringPtr")
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
cnt, err = engine.Id(nullData.Id).Cols(string_ptr).Update(&nullDataUpdate)
|
||||||
panic(err)
|
if err != nil {
|
||||||
} else if cnt != 1 {
|
t.Error(err)
|
||||||
t.Error(errors.New("update count == 0, how can this happen!?"))
|
panic(err)
|
||||||
return
|
} else if cnt != 1 {
|
||||||
}*/
|
t.Error(errors.New("update count == 0, how can this happen!?"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// verify get values
|
// verify get values
|
||||||
/*nullDataGet = NullData{}
|
nullDataGet = NullData{}
|
||||||
has, err = engine.Id(nullData.Id).Get(&nullDataGet)
|
has, err = engine.Id(nullData.Id).Get(&nullDataGet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
} else if !has {
|
} else if !has {
|
||||||
t.Error(errors.New("ID not found"))
|
t.Error(errors.New("ID not found"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%+v", nullDataGet)
|
fmt.Printf("%+v", nullDataGet)
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
if nullDataGet.StringPtr != nil {
|
|
||||||
t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.StringPtr)))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if nullDataGet.StringPtr != nil {
|
||||||
|
t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.StringPtr)))
|
||||||
|
}
|
||||||
|
/*
|
||||||
if nullDataGet.StringPtr2 != nil {
|
if nullDataGet.StringPtr2 != nil {
|
||||||
t.Error(errors.New(fmt.Sprintf("not null value: [%v]", *nullDataGet.StringPtr2)))
|
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) {
|
func testLowerCase(engine *Engine, t *testing.T) {
|
||||||
err := engine.Sync(&Lowercase{})
|
err := engine.Sync(&Lowercase{})
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = engine.Where("id > 0").Delete(&Lowercase{})
|
_, err = engine.Where("id > 0").Delete(&Lowercase{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = engine.Insert(&Lowercase{ended: 1})
|
_, err = engine.Insert(&Lowercase{ended: 1})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
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) {
|
func testAll(engine *Engine, t *testing.T) {
|
||||||
fmt.Println("-------------- directCreateTable --------------")
|
fmt.Println("-------------- directCreateTable --------------")
|
||||||
directCreateTable(engine, t)
|
directCreateTable(engine, t)
|
||||||
|
@ -3390,8 +3901,8 @@ func testAll(engine *Engine, t *testing.T) {
|
||||||
insertTwoTable(engine, t)
|
insertTwoTable(engine, t)
|
||||||
fmt.Println("-------------- update --------------")
|
fmt.Println("-------------- update --------------")
|
||||||
update(engine, t)
|
update(engine, t)
|
||||||
fmt.Println("-------------- testdelete --------------")
|
fmt.Println("-------------- testDelete --------------")
|
||||||
testdelete(engine, t)
|
testDelete(engine, t)
|
||||||
fmt.Println("-------------- get --------------")
|
fmt.Println("-------------- get --------------")
|
||||||
get(engine, t)
|
get(engine, t)
|
||||||
fmt.Println("-------------- cascadeGet --------------")
|
fmt.Println("-------------- cascadeGet --------------")
|
||||||
|
@ -3446,13 +3957,21 @@ func testAll2(engine *Engine, t *testing.T) {
|
||||||
fmt.Println("-------------- testIndexAndUnique --------------")
|
fmt.Println("-------------- testIndexAndUnique --------------")
|
||||||
testIndexAndUnique(engine, t)
|
testIndexAndUnique(engine, t)
|
||||||
fmt.Println("-------------- testIntId --------------")
|
fmt.Println("-------------- testIntId --------------")
|
||||||
//testIntId(engine, t)
|
testIntId(engine, t)
|
||||||
fmt.Println("-------------- testInt32Id --------------")
|
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 --------------")
|
fmt.Println("-------------- testMetaInfo --------------")
|
||||||
testMetaInfo(engine, t)
|
testMetaInfo(engine, t)
|
||||||
fmt.Println("-------------- testIterate --------------")
|
fmt.Println("-------------- testIterate --------------")
|
||||||
testIterate(engine, t)
|
testIterate(engine, t)
|
||||||
|
fmt.Println("-------------- testRows --------------")
|
||||||
|
testRows(engine, t)
|
||||||
fmt.Println("-------------- testStrangeName --------------")
|
fmt.Println("-------------- testStrangeName --------------")
|
||||||
testStrangeName(engine, t)
|
testStrangeName(engine, t)
|
||||||
fmt.Println("-------------- testVersion --------------")
|
fmt.Println("-------------- testVersion --------------")
|
||||||
|
@ -3487,5 +4006,16 @@ func testAll3(engine *Engine, t *testing.T) {
|
||||||
testNullValue(engine, t)
|
testNullValue(engine, t)
|
||||||
fmt.Println("-------------- testCompositeKey --------------")
|
fmt.Println("-------------- testCompositeKey --------------")
|
||||||
testCompositeKey(engine, t)
|
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) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
go test -v -bench=. -run=XXX
|
9
doc.go
9
doc.go
|
@ -60,7 +60,14 @@ There are 7 major ORM methods and many helpful methods to use to operate databas
|
||||||
err := engine.Find(...)
|
err := engine.Find(...)
|
||||||
// SELECT * FROM user
|
// 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(...)
|
err := engine.Iterate(...)
|
||||||
// SELECT * FROM user
|
// SELECT * FROM user
|
||||||
|
|
|
@ -1,5 +1,19 @@
|
||||||
## Changelog
|
## 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.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.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/lunny/xorm/blob/master/xorm/README.md); some bug fixed.
|
||||||
|
|
|
@ -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.3** : 改善了文档;提供了乐观锁支持;添加了带时区时间字段支持;Mapper现在分成表名Mapper和字段名Mapper,同时实现了表或字段的自定义前缀后缀;Insert方法的返回值含义从id, err更改为 affected, err,请大家注意;添加了UseBool 和 Distinct函数。
|
||||||
* **v0.2.2** : Postgres驱动新增了对lib/pq的支持;新增了逐条遍历方法Iterate;新增了SetMaxConns(go1.2+)支持,修复了bug若干;
|
* **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.1** : 新增数据库反转工具,当前支持go和c++代码的生成,详见 [Xorm Tool README](https://github.com/lunny/xorm/blob/master/xorm/README.md); 修复了一些bug.
|
||||||
|
|
|
@ -4,9 +4,10 @@ xorm 快速入门
|
||||||
* [1.创建Orm引擎](#10)
|
* [1.创建Orm引擎](#10)
|
||||||
* [2.定义表结构体](#20)
|
* [2.定义表结构体](#20)
|
||||||
* [2.1.名称映射规则](#21)
|
* [2.1.名称映射规则](#21)
|
||||||
* [2.2.使用Table和Tag改变名称映射](#22)
|
* [2.2.前缀映射规则和后缀映射规则](#22)
|
||||||
* [2.3.Column属性定义](#23)
|
* [2.3.使用Table和Tag改变名称映射](#23)
|
||||||
* [2.4.Go与字段类型对应表](#24)
|
* [2.4.Column属性定义](#24)
|
||||||
|
* [2.5.Go与字段类型对应表](#25)
|
||||||
* [3.表结构操作](#30)
|
* [3.表结构操作](#30)
|
||||||
* [3.1 获取数据库信息](#31)
|
* [3.1 获取数据库信息](#31)
|
||||||
* [3.2 表操作](#32)
|
* [3.2 表操作](#32)
|
||||||
|
@ -20,6 +21,7 @@ xorm 快速入门
|
||||||
* [5.4.Find方法](#64)
|
* [5.4.Find方法](#64)
|
||||||
* [5.5.Iterate方法](#65)
|
* [5.5.Iterate方法](#65)
|
||||||
* [5.6.Count方法](#66)
|
* [5.6.Count方法](#66)
|
||||||
|
* [5.7.Rows方法](#67)
|
||||||
* [6.更新数据](#70)
|
* [6.更新数据](#70)
|
||||||
* [6.1.乐观锁](#71)
|
* [6.1.乐观锁](#71)
|
||||||
* [7.删除数据](#80)
|
* [7.删除数据](#80)
|
||||||
|
@ -61,7 +63,7 @@ defer engine.Close()
|
||||||
|
|
||||||
一般如果只针对一个数据库进行操作,只需要创建一个Engine即可。Engine支持在多GoRutine下使用。
|
一般如果只针对一个数据库进行操作,只需要创建一个Engine即可。Engine支持在多GoRutine下使用。
|
||||||
|
|
||||||
xorm当前支持四种驱动如下:
|
xorm当前支持五种驱动四个数据库如下:
|
||||||
|
|
||||||
* Mysql: [github.com/Go-SQL-Driver/MySQL](https://github.com/Go-SQL-Driver/MySQL)
|
* Mysql: [github.com/Go-SQL-Driver/MySQL](https://github.com/Go-SQL-Driver/MySQL)
|
||||||
|
|
||||||
|
@ -71,11 +73,13 @@ xorm当前支持四种驱动如下:
|
||||||
|
|
||||||
* Postgres: [github.com/lib/pq](https://github.com/lib/pq)
|
* Postgres: [github.com/lib/pq](https://github.com/lib/pq)
|
||||||
|
|
||||||
|
* MsSql: [github.com/lunny/godbc](https://githubcom/lunny/godbc)
|
||||||
|
|
||||||
NewEngine传入的参数和`sql.Open`传入的参数完全相同,因此,使用哪个驱动前,请查看此驱动中关于传入参数的说明文档。
|
NewEngine传入的参数和`sql.Open`传入的参数完全相同,因此,使用哪个驱动前,请查看此驱动中关于传入参数的说明文档。
|
||||||
|
|
||||||
在engine创建完成后可以进行一些设置,如:
|
在engine创建完成后可以进行一些设置,如:
|
||||||
|
|
||||||
1.设置
|
1.错误显示设置,默认如下均为`false`
|
||||||
|
|
||||||
* `engine.ShowSQL = true`,则会在控制台打印出生成的SQL语句;
|
* `engine.ShowSQL = true`,则会在控制台打印出生成的SQL语句;
|
||||||
* `engine.ShowDebug = true`,则会在控制台打印调试信息;
|
* `engine.ShowDebug = true`,则会在控制台打印调试信息;
|
||||||
|
@ -93,7 +97,7 @@ f, err := os.Create("sql.log")
|
||||||
engine.Logger = f
|
engine.Logger = f
|
||||||
```
|
```
|
||||||
|
|
||||||
3.engine内部支持连接池接口,默认使用的Go所实现的连接池,同时自带了另外两种实现:一种是不使用连接池,另一种为一个自实现的连接池。推荐使用Go所实现的连接池。如果要使用自己实现的连接池,可以实现`xorm.IConnectPool`并通过`engine.SetPool`进行设置。
|
3.engine内部支持连接池接口,默认使用的Go所实现的连接池,同时自带了另外两种实现:一种是不使用连接池,另一种为一个自实现的连接池。推荐使用Go所实现的连接池。如果要使用自己实现的连接池,可以实现`xorm.IConnectPool`并通过`engine.SetPool`进行设置。推荐使用Go默认的连接池。
|
||||||
|
|
||||||
* 如果需要设置连接池的空闲数大小,可以使用`engine.SetIdleConns()`来实现。
|
* 如果需要设置连接池的空闲数大小,可以使用`engine.SetIdleConns()`来实现。
|
||||||
* 如果需要设置最大打开连接数,则可以使用`engine.SetMaxConns()`来实现。
|
* 如果需要设置最大打开连接数,则可以使用`engine.SetMaxConns()`来实现。
|
||||||
|
@ -106,25 +110,40 @@ xorm支持将一个struct映射为数据库中对应的一张表。映射规则
|
||||||
<a name="21" id="21"></a>
|
<a name="21" id="21"></a>
|
||||||
### 2.1.名称映射规则
|
### 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创建完成后使用
|
当前SnakeMapper为默认值,如果需要改变时,在engine创建完成后使用
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
engine.Mapper = SameMapper{}
|
engine.SetMapper(SameMapper{})
|
||||||
```
|
```
|
||||||
|
|
||||||
当然,如果你使用了别的命名规则映射方案,也可以自己实现一个IMapper。
|
同时需要注意的是:
|
||||||
|
|
||||||
|
* 如果你使用了别的命名规则映射方案,也可以自己实现一个IMapper。
|
||||||
|
* 表名称和字段名称的映射规则默认是相同的,当然也可以设置为不同,如:
|
||||||
|
|
||||||
|
```Go
|
||||||
|
engine.SetTableMapper(SameMapper{})
|
||||||
|
engine.SetColumnMapper(SnakeMapper{})
|
||||||
|
```
|
||||||
|
|
||||||
<a name="22" id="22"></a>
|
<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的映射来操作的,那当然是最理想的。但是如果碰到某个表名或者某个字段名跟映射规则不匹配时,我们就需要别的机制来改变。
|
如果所有的命名都是按照IMapper的映射来操作的,那当然是最理想的。但是如果碰到某个表名或者某个字段名跟映射规则不匹配时,我们就需要别的机制来改变。
|
||||||
|
|
||||||
通过`engine.Table()`方法可以改变struct对应的数据库表的名称,通过sturct中field对应的Tag中使用`xorm:"'column_name'"`可以使该field对应的Column名称为指定名称。这里使用两个单引号将Column名称括起来是为了防止名称冲突,因为我们在Tag中还可以对这个Column进行更多的定义。如果名称不冲突的情况,单引号也可以不使用。
|
通过`engine.Table()`方法可以改变struct对应的数据库表的名称,通过sturct中field对应的Tag中使用`xorm:"'column_name'"`可以使该field对应的Column名称为指定名称。这里使用两个单引号将Column名称括起来是为了防止名称冲突,因为我们在Tag中还可以对这个Column进行更多的定义。如果名称不冲突的情况,单引号也可以不使用。
|
||||||
|
|
||||||
<a name="23" id="23"></a>
|
<a name="23" id="23"></a>
|
||||||
### 2.3.Column属性定义
|
### 2.4.Column属性定义
|
||||||
我们在field对应的Tag中对Column的一些属性进行定义,定义的方法基本和我们写SQL定义表结构类似,比如:
|
我们在field对应的Tag中对Column的一些属性进行定义,定义的方法基本和我们写SQL定义表结构类似,比如:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -140,10 +159,10 @@ type User struct {
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td>name</td><td>当前field对应的字段的名称,可选,如不写,则自动根据field名字和转换规则命名</td>
|
<td>name</td><td>当前field对应的字段的名称,可选,如不写,则自动根据field名字和转换规则命名,如与其它关键字冲突,请使用单引号括起来。</td>
|
||||||
</tr>
|
</tr>
|
||||||
<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>
|
||||||
<tr>
|
<tr>
|
||||||
<td>当前支持30多种字段类型,详情参见 [字段类型](https://github.com/lunny/xorm/blob/master/docs/COLUMNTYPE.md)</td><td>字段类型</td>
|
<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>
|
<td>autoincr</td><td>是否是自增</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>[not ]null</td><td>是否可以为空</td>
|
<td>[not ]null 或 notnull</td><td>是否可以为空</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>unique或unique(uniquename)</td><td>是否是唯一,如不加括号则该字段不允许重复;如加上括号,则括号中为联合唯一索引的名字,此时如果有另外一个或多个字段和本unique的uniquename相同,则这些uniquename相同的字段组成联合唯一索引</td>
|
<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中自定义
|
- 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接口的类型或者结构体,将根据接口的转换方式在类型和数据库记录之间进行相互转换。
|
- 4.实现了Conversion接口的类型或者结构体,将根据接口的转换方式在类型和数据库记录之间进行相互转换。
|
||||||
```Go
|
```Go
|
||||||
|
@ -218,41 +237,50 @@ xorm提供了一些动态获取和修改表结构的方法。对于一般的应
|
||||||
## 3.1 获取数据库信息
|
## 3.1 获取数据库信息
|
||||||
|
|
||||||
* DBMetas()
|
* DBMetas()
|
||||||
xorm支持获取表结构信息,通过调用`engine.DBMetas()`可以获取到所有的表的信息
|
|
||||||
|
xorm支持获取表结构信息,通过调用`engine.DBMetas()`可以获取到所有的表,字段,索引的信息。
|
||||||
|
|
||||||
<a name="31" id="31"></a>
|
<a name="31" id="31"></a>
|
||||||
## 3.2.表操作
|
## 3.2.表操作
|
||||||
|
|
||||||
* CreateTables()
|
* CreateTables()
|
||||||
|
|
||||||
创建表使用`engine.CreateTables()`,参数为一个或多个空的对应Struct的指针。同时可用的方法有Charset()和StoreEngine(),如果对应的数据库支持,这两个方法可以在创建表时指定表的字符编码和使用的引擎。当前仅支持Mysql数据库。
|
创建表使用`engine.CreateTables()`,参数为一个或多个空的对应Struct的指针。同时可用的方法有Charset()和StoreEngine(),如果对应的数据库支持,这两个方法可以在创建表时指定表的字符编码和使用的引擎。当前仅支持Mysql数据库。
|
||||||
|
|
||||||
* IsTableEmpty()
|
* IsTableEmpty()
|
||||||
|
|
||||||
判断表是否为空,参数和CreateTables相同
|
判断表是否为空,参数和CreateTables相同
|
||||||
|
|
||||||
* IsTableExist()
|
* IsTableExist()
|
||||||
|
|
||||||
判断表是否存在
|
判断表是否存在
|
||||||
|
|
||||||
* DropTables()
|
* DropTables()
|
||||||
|
|
||||||
删除表使用`engine.DropTables()`,参数为一个或多个空的对应Struct的指针或者表的名字。如果为string传入,则只删除对应的表,如果传入的为Struct,则删除表的同时还会删除对应的索引。
|
删除表使用`engine.DropTables()`,参数为一个或多个空的对应Struct的指针或者表的名字。如果为string传入,则只删除对应的表,如果传入的为Struct,则删除表的同时还会删除对应的索引。
|
||||||
|
|
||||||
<a name="32" id="32"></a>
|
<a name="32" id="32"></a>
|
||||||
## 3.3.创建索引和唯一索引
|
## 3.3.创建索引和唯一索引
|
||||||
|
|
||||||
* CreateIndexes
|
* CreateIndexes
|
||||||
|
|
||||||
根据struct中的tag来创建索引
|
根据struct中的tag来创建索引
|
||||||
|
|
||||||
* CreateUniques
|
* CreateUniques
|
||||||
|
|
||||||
根据struct中的tag来创建唯一索引
|
根据struct中的tag来创建唯一索引
|
||||||
|
|
||||||
<a name="34" id="34"></a>
|
<a name="34" id="34"></a>
|
||||||
## 3.4.同步数据库结构
|
## 3.4.同步数据库结构
|
||||||
|
|
||||||
同步能够部分智能的根据结构体的变动检测表结构的变动,并自动同步。目前能够实现:
|
同步能够部分智能的根据结构体的变动检测表结构的变动,并自动同步。目前能够实现:
|
||||||
1) 自动检测和创建表,这个检测是根据表的名字
|
|
||||||
2)自动检测和新增表中的字段,这个检测是根据字段名
|
* 1) 自动检测和创建表,这个检测是根据表的名字
|
||||||
3)自动检测和创建索引和唯一索引,这个检测是根据一个或多个字段名,而不根据索引名称
|
* 2)自动检测和新增表中的字段,这个检测是根据字段名
|
||||||
|
* 3)自动检测和创建索引和唯一索引,这个检测是根据一个或多个字段名,而不根据索引名称
|
||||||
|
|
||||||
调用方法如下:
|
调用方法如下:
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
err := engine.Sync(new(User))
|
err := engine.Sync(new(User))
|
||||||
```
|
```
|
||||||
|
@ -264,18 +292,21 @@ err := engine.Sync(new(User))
|
||||||
如果传入的是Slice并且当数据库支持批量插入时,Insert会使用批量插入的方式进行插入。
|
如果传入的是Slice并且当数据库支持批量插入时,Insert会使用批量插入的方式进行插入。
|
||||||
|
|
||||||
* 插入一条数据
|
* 插入一条数据
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
user := new(User)
|
user := new(User)
|
||||||
user.Name = "myname"
|
user.Name = "myname"
|
||||||
affected, err := engine.Insert(user)
|
affected, err := engine.Insert(user)
|
||||||
```
|
```
|
||||||
|
|
||||||
在插入成功后,如果该结构体有PK字段,则PK字段会被自动赋值为数据库中的id
|
在插入单条数据成功后,如果该结构体有自增字段,则自增字段会被自动赋值为数据库中的id
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
fmt.Println(user.Id)
|
fmt.Println(user.Id)
|
||||||
```
|
```
|
||||||
|
|
||||||
* 插入同一个表的多条数据
|
* 插入同一个表的多条数据
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
users := make([]User, 0)
|
users := make([]User, 0)
|
||||||
users[0].Name = "name0"
|
users[0].Name = "name0"
|
||||||
|
@ -284,6 +315,7 @@ affected, err := engine.Insert(&users)
|
||||||
```
|
```
|
||||||
|
|
||||||
* 使用指针Slice插入多条记录
|
* 使用指针Slice插入多条记录
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
users := make([]*User, 0)
|
users := make([]*User, 0)
|
||||||
users[0] = new(User)
|
users[0] = new(User)
|
||||||
|
@ -293,6 +325,7 @@ affected, err := engine.Insert(&users)
|
||||||
```
|
```
|
||||||
|
|
||||||
* 插入不同表的一条记录
|
* 插入不同表的一条记录
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
user := new(User)
|
user := new(User)
|
||||||
user.Name = "myname"
|
user.Name = "myname"
|
||||||
|
@ -302,6 +335,7 @@ affected, err := engine.Insert(user, question)
|
||||||
```
|
```
|
||||||
|
|
||||||
* 插入不同表的多条记录
|
* 插入不同表的多条记录
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
users := make([]User, 0)
|
users := make([]User, 0)
|
||||||
users[0].Name = "name0"
|
users[0].Name = "name0"
|
||||||
|
@ -321,25 +355,27 @@ questions[0].Content = "whywhywhwy?"
|
||||||
affected, err := engine.Insert(user, &questions)
|
affected, err := engine.Insert(user, &questions)
|
||||||
```
|
```
|
||||||
|
|
||||||
注意:这里虽然支持同时插入,但这些插入并没有事务关系。因此有可能在中间插入出错后,后面的插入将不会继续。
|
这里需要注意以下几点:
|
||||||
|
* 这里虽然支持同时插入,但这些插入并没有事务关系。因此有可能在中间插入出错后,后面的插入将不会继续。
|
||||||
|
* 多条插入会自动生成`Insert into table values (),(),()`的语句,因此这样的语句有一个最大的记录数,根据经验测算在150条左右。大于150条后,生成的sql语句将太长可能导致执行失败。因此在插入大量数据时,目前需要自行分割成每150条插入一次。
|
||||||
|
|
||||||
<a name="60" id="60"></a>
|
<a name="60" id="60"></a>
|
||||||
## 5.查询和统计数据
|
## 5.查询和统计数据
|
||||||
|
|
||||||
所有的查询条件不区分调用顺序,但必须在调用Get,Find,Count这三个函数之前调用。同时需要注意的一点是,在调用的参数中,所有的字符字段名均为映射后的数据库的字段名,而不是field的名字。
|
所有的查询条件不区分调用顺序,但必须在调用Get,Find,Count, Iterate, Rows这几个函数之前调用。同时需要注意的一点是,在调用的参数中,如果采用默认的`SnakeMapper`所有的字符字段名均为映射后的数据库的字段名,而不是field的名字。
|
||||||
|
|
||||||
<a name="61" id="61"></a>
|
<a name="61" id="61"></a>
|
||||||
### 5.1.查询条件方法
|
### 5.1.查询条件方法
|
||||||
|
|
||||||
查询和统计主要使用`Get`, `Find`, `Count`三个方法。在进行查询时可以使用多个方法来形成查询条件,条件函数如下:
|
查询和统计主要使用`Get`, `Find`, `Count`, `Rows`, `Iterate`这几个方法。在进行查询时可以使用多个方法来形成查询条件,条件函数如下:
|
||||||
|
|
||||||
* Id(interface{})
|
* Id(interface{})
|
||||||
传入一个PK字段的值,作为查询条件,如果是复合主键,则
|
传入一个PK字段的值,作为查询条件,如果是复合主键,则
|
||||||
`Id(xorm.PK{1, 2})`
|
`Id(xorm.PK{1, 2})`
|
||||||
传入的两个参数按照struct中定义的顺序赋值。
|
传入的两个参数按照struct中pk标记字段出现的顺序赋值。
|
||||||
|
|
||||||
* Where(string, …interface{})
|
* Where(string, …interface{})
|
||||||
和Where语句中的条件基本相同,作为条件
|
和SQL中Where语句中的条件基本相同,作为条件
|
||||||
|
|
||||||
* And(string, …interface{})
|
* And(string, …interface{})
|
||||||
和Where函数中的条件基本相同,作为条件
|
和Where函数中的条件基本相同,作为条件
|
||||||
|
@ -360,7 +396,7 @@ affected, err := engine.Insert(user, &questions)
|
||||||
按照指定的顺序进行排序
|
按照指定的顺序进行排序
|
||||||
|
|
||||||
* In(string, …interface{})
|
* In(string, …interface{})
|
||||||
某字段在一些值中
|
某字段在一些值中,这里需要注意必须是[]interface{}才可以展开,由于Go语言的限制,[]int64等均不可以展开。
|
||||||
|
|
||||||
* Cols(…string)
|
* Cols(…string)
|
||||||
只查询或更新某些指定的字段,默认是查询所有映射的字段或者根据Update的第一个参数来判断更新的字段。例如:
|
只查询或更新某些指定的字段,默认是查询所有映射的字段或者根据Update的第一个参数来判断更新的字段。例如:
|
||||||
|
@ -429,20 +465,28 @@ Having的参数字符串
|
||||||
如:
|
如:
|
||||||
|
|
||||||
1) 根据Id来获得单条数据:
|
1) 根据Id来获得单条数据:
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
user := new(User)
|
user := new(User)
|
||||||
has, err := engine.Id(id).Get(user)
|
has, err := engine.Id(id).Get(user)
|
||||||
|
// 复合主键的获取方法
|
||||||
|
// has, errr := engine.Id(xorm.PK{1,2}).Get(user)
|
||||||
```
|
```
|
||||||
|
|
||||||
2) 根据Where来获得单条数据:
|
2) 根据Where来获得单条数据:
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
user := new(User)
|
user := new(User)
|
||||||
has, err := engine.Where("name=?", "xlw").Get(user)
|
has, err := engine.Where("name=?", "xlw").Get(user)
|
||||||
```
|
```
|
||||||
|
|
||||||
3) 根据user结构体中已有的非空数据来获得单条数据:
|
3) 根据user结构体中已有的非空数据来获得单条数据:
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
user := &User{Id:1}
|
user := &User{Id:1}
|
||||||
has, err := engine.Get(user)
|
has, err := engine.Get(user)
|
||||||
```
|
```
|
||||||
|
|
||||||
或者其它条件
|
或者其它条件
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
|
@ -458,6 +502,7 @@ has, err := engine.Get(user)
|
||||||
查询多条数据使用`Find`方法,Find方法的第一个参数为`slice`的指针或`Map`指针,即为查询后返回的结果,第二个参数可选,为查询的条件struct的指针。
|
查询多条数据使用`Find`方法,Find方法的第一个参数为`slice`的指针或`Map`指针,即为查询后返回的结果,第二个参数可选,为查询的条件struct的指针。
|
||||||
|
|
||||||
1) 传入Slice用于返回数据
|
1) 传入Slice用于返回数据
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
everyone := make([]Userinfo, 0)
|
everyone := make([]Userinfo, 0)
|
||||||
err := engine.Find(&everyone)
|
err := engine.Find(&everyone)
|
||||||
|
@ -466,7 +511,8 @@ pEveryOne := make([]*Userinfo, 0)
|
||||||
err := engine.Find(&pEveryOne)
|
err := engine.Find(&pEveryOne)
|
||||||
```
|
```
|
||||||
|
|
||||||
2) 传入Map用户返回数据,map必须为`map[int64]Userinfo`的形式,map的key为id
|
2) 传入Map用户返回数据,map必须为`map[int64]Userinfo`的形式,map的key为id,因此对于复合主键无法使用这种方式。
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
users := make(map[int64]Userinfo)
|
users := make(map[int64]Userinfo)
|
||||||
err := engine.Find(&users)
|
err := engine.Find(&users)
|
||||||
|
@ -476,6 +522,7 @@ err := engine.Find(&pUsers)
|
||||||
```
|
```
|
||||||
|
|
||||||
3) 也可以加入各种条件
|
3) 也可以加入各种条件
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
users := make([]Userinfo, 0)
|
users := make([]Userinfo, 0)
|
||||||
err := engine.Where("age > ? or name = ?", 30, "xlw").Limit(20, 10).Find(&users)
|
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方法
|
### 5.5.Iterate方法
|
||||||
|
|
||||||
Iterate方法提供逐条执行查询到的记录的方法,他所能使用的条件和Find方法完全相同
|
Iterate方法提供逐条执行查询到的记录的方法,他所能使用的条件和Find方法完全相同
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
err := engine.Where("age > ? or name=?)", 30, "xlw").Iterate(new(Userinfo), func(i int, bean interface{})error{
|
err := engine.Where("age > ? or name=?)", 30, "xlw").Iterate(new(Userinfo), func(i int, bean interface{})error{
|
||||||
user := bean.(*Userinfo)
|
user := bean.(*Userinfo)
|
||||||
|
@ -501,6 +549,22 @@ user := new(User)
|
||||||
total, err := engine.Where("id >?", 1).Count(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>
|
<a name="70" id="70"></a>
|
||||||
## 6.更新数据
|
## 6.更新数据
|
||||||
|
|
||||||
|
@ -514,12 +578,14 @@ affected, err := engine.Id(id).Update(user)
|
||||||
|
|
||||||
这里需要注意,Update会自动从user结构体中提取非0和非nil得值作为需要更新的内容,因此,如果需要更新一个值为0,则此种方法将无法实现,因此有两种选择:
|
这里需要注意,Update会自动从user结构体中提取非0和非nil得值作为需要更新的内容,因此,如果需要更新一个值为0,则此种方法将无法实现,因此有两种选择:
|
||||||
|
|
||||||
1. 通过添加Cols函数指定需要更新结构体中的哪些值,未指定的将不更新,指定了的即使为0也会更新。
|
* 1.通过添加Cols函数指定需要更新结构体中的哪些值,未指定的将不更新,指定了的即使为0也会更新。
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
affected, err := engine.Id(id).Cols("age").Update(&user)
|
affected, err := engine.Id(id).Cols("age").Update(&user)
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 通过传入map[string]interface{}来进行更新,但这时需要额外指定更新到哪个表,因为通过map是无法自动检测更新哪个表的。
|
* 2.通过传入map[string]interface{}来进行更新,但这时需要额外指定更新到哪个表,因为通过map是无法自动检测更新哪个表的。
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
affected, err := engine.Table(new(User)).Id(id).Update(map[string]interface{}{"age":0})
|
affected, err := engine.Table(new(User)).Id(id).Update(map[string]interface{}{"age":0})
|
||||||
```
|
```
|
||||||
|
@ -548,6 +614,7 @@ engine.Id(1).Update(&user)
|
||||||
## 7.删除数据
|
## 7.删除数据
|
||||||
|
|
||||||
删除数据`Delete`方法,参数为struct的指针并且成为查询条件。
|
删除数据`Delete`方法,参数为struct的指针并且成为查询条件。
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
user := new(User)
|
user := new(User)
|
||||||
affected, err := engine.Id(id).Delete(user)
|
affected, err := engine.Id(id).Delete(user)
|
||||||
|
@ -561,23 +628,27 @@ affected, err := engine.Id(id).Delete(user)
|
||||||
## 8.执行SQL查询
|
## 8.执行SQL查询
|
||||||
|
|
||||||
也可以直接执行一个SQL查询,即Select命令。在Postgres中支持原始SQL语句中使用 ` 和 ? 符号。
|
也可以直接执行一个SQL查询,即Select命令。在Postgres中支持原始SQL语句中使用 ` 和 ? 符号。
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
sql := "select * from userinfo"
|
sql := "select * from userinfo"
|
||||||
results, err := engine.Query(sql)
|
results, err := engine.Query(sql)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
当调用`Query`时,第一个返回值`results`为`[]map[string][]byte`的形式。
|
||||||
|
|
||||||
<a name="100" id="100"></a>
|
<a name="100" id="100"></a>
|
||||||
## 9.执行SQL命令
|
## 9.执行SQL命令
|
||||||
|
|
||||||
也可以直接执行一个SQL命令,即执行Insert, Update, Delete 等操作。同样在Postgres中支持原始SQL语句中使用 ` 和 ? 符号。
|
也可以直接执行一个SQL命令,即执行Insert, Update, Delete 等操作。此时不管数据库是何种类型,都可以使用 ` 和 ? 符号。
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
sql = "update userinfo set username=? where id=?"
|
sql = "update `userinfo` set username=? where id=?"
|
||||||
res, err := engine.Exec(sql, "xiaolun", 1)
|
res, err := engine.Exec(sql, "xiaolun", 1)
|
||||||
```
|
```
|
||||||
|
|
||||||
<a name="110" id="110"></a>
|
<a name="110" id="110"></a>
|
||||||
## 10.事务处理
|
## 10.事务处理
|
||||||
当使用事务处理时,需要创建Session对象。
|
当使用事务处理时,需要创建Session对象。在进行事物处理时,可以混用ORM方法和RAW方法,如下代码所示:
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
session := engine.NewSession()
|
session := engine.NewSession()
|
||||||
|
@ -620,14 +691,17 @@ xorm内置了一致性缓存支持,不过默认并没有开启。要开启缓
|
||||||
cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000)
|
cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000)
|
||||||
engine.SetDefaultCacher(cacher)
|
engine.SetDefaultCacher(cacher)
|
||||||
```
|
```
|
||||||
|
|
||||||
上述代码采用了LRU算法的一个缓存,缓存方式是存放到内存中,缓存struct的记录数为1000条,缓存针对的范围是所有具有主键的表,没有主键的表中的数据将不会被缓存。
|
上述代码采用了LRU算法的一个缓存,缓存方式是存放到内存中,缓存struct的记录数为1000条,缓存针对的范围是所有具有主键的表,没有主键的表中的数据将不会被缓存。
|
||||||
如果只想针对部分表,则:
|
如果只想针对部分表,则:
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000)
|
cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000)
|
||||||
engine.MapCacher(&user, cacher)
|
engine.MapCacher(&user, cacher)
|
||||||
```
|
```
|
||||||
|
|
||||||
如果要禁用某个表的缓存,则:
|
如果要禁用某个表的缓存,则:
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
engine.MapCacher(&user, nil)
|
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
|
```Go
|
||||||
engine.Exec("update user set name = ? where id = ?", "xlw", 1)
|
engine.Exec("update user set name = ? where id = ?", "xlw", 1)
|
||||||
engine.ClearCache(new(User))
|
engine.ClearCache(new(User))
|
||||||
```
|
```
|
||||||
|
|
||||||
ClearCacheBean
|
|
||||||
|
|
||||||
缓存的实现原理如下图所示:
|
缓存的实现原理如下图所示:
|
||||||
|
|
||||||

|

|
||||||
|
|
36
engine.go
36
engine.go
|
@ -28,6 +28,7 @@ const (
|
||||||
// a dialect is a driver's wrapper
|
// a dialect is a driver's wrapper
|
||||||
type dialect interface {
|
type dialect interface {
|
||||||
Init(DriverName, DataSourceName string) error
|
Init(DriverName, DataSourceName string) error
|
||||||
|
URI() *uri
|
||||||
DBType() string
|
DBType() string
|
||||||
SqlType(t *Column) string
|
SqlType(t *Column) string
|
||||||
SupportInsertMany() bool
|
SupportInsertMany() bool
|
||||||
|
@ -472,15 +473,16 @@ func (engine *Engine) mapType(t reflect.Type) *Table {
|
||||||
parentTable := engine.mapType(fieldType)
|
parentTable := engine.mapType(fieldType)
|
||||||
for name, col := range parentTable.Columns {
|
for name, col := range parentTable.Columns {
|
||||||
col.FieldName = fmt.Sprintf("%v.%v", fieldType.Name(), col.FieldName)
|
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.ColumnsSeq = append(table.ColumnsSeq, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
table.PrimaryKey = parentTable.PrimaryKey
|
table.PrimaryKeys = parentTable.PrimaryKeys
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var indexType int
|
var indexType int
|
||||||
var indexName string
|
var indexName string
|
||||||
|
var preKey string
|
||||||
for j, key := range tags {
|
for j, key := range tags {
|
||||||
k := strings.ToUpper(key)
|
k := strings.ToUpper(key)
|
||||||
switch {
|
switch {
|
||||||
|
@ -519,12 +521,13 @@ func (engine *Engine) mapType(t reflect.Type) *Table {
|
||||||
case k == "NOT":
|
case k == "NOT":
|
||||||
default:
|
default:
|
||||||
if strings.HasPrefix(k, "'") && strings.HasSuffix(k, "'") {
|
if strings.HasPrefix(k, "'") && strings.HasSuffix(k, "'") {
|
||||||
if key != col.Default {
|
if preKey != "DEFAULT" {
|
||||||
col.Name = key[1 : len(key)-1]
|
col.Name = key[1 : len(key)-1]
|
||||||
}
|
}
|
||||||
} else if strings.Contains(k, "(") && strings.HasSuffix(k, ")") {
|
} else if strings.Contains(k, "(") && strings.HasSuffix(k, ")") {
|
||||||
fs := strings.Split(k, "(")
|
fs := strings.Split(k, "(")
|
||||||
if _, ok := sqlTypes[fs[0]]; !ok {
|
if _, ok := sqlTypes[fs[0]]; !ok {
|
||||||
|
preKey = k
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
col.SQLType = SQLType{fs[0], 0, 0}
|
col.SQLType = SQLType{fs[0], 0, 0}
|
||||||
|
@ -538,12 +541,13 @@ func (engine *Engine) mapType(t reflect.Type) *Table {
|
||||||
} else {
|
} else {
|
||||||
if _, ok := sqlTypes[k]; ok {
|
if _, ok := sqlTypes[k]; ok {
|
||||||
col.SQLType = SQLType{k, 0, 0}
|
col.SQLType = SQLType{k, 0, 0}
|
||||||
} else if key != col.Default {
|
} else if preKey != "DEFAULT" {
|
||||||
col.Name = key
|
col.Name = key
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
engine.SqlType(col)
|
engine.SqlType(col)
|
||||||
}
|
}
|
||||||
|
preKey = k
|
||||||
}
|
}
|
||||||
if col.SQLType.Name == "" {
|
if col.SQLType.Name == "" {
|
||||||
col.SQLType = Type2SQLType(fieldType)
|
col.SQLType = Type2SQLType(fieldType)
|
||||||
|
@ -602,12 +606,13 @@ func (engine *Engine) mapType(t reflect.Type) *Table {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if idFieldColName != "" && table.PrimaryKey == "" {
|
if idFieldColName != "" && len(table.PrimaryKeys) == 0 {
|
||||||
col := table.Columns[idFieldColName]
|
col := table.Columns[strings.ToLower(idFieldColName)]
|
||||||
col.IsPrimaryKey = true
|
col.IsPrimaryKey = true
|
||||||
col.IsAutoIncrement = true
|
col.IsAutoIncrement = true
|
||||||
col.Nullable = false
|
col.Nullable = false
|
||||||
table.PrimaryKey = col.Name
|
table.PrimaryKeys = append(table.PrimaryKeys, col.Name)
|
||||||
|
table.AutoIncrement = col.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
return table
|
return table
|
||||||
|
@ -933,6 +938,13 @@ func (engine *Engine) Iterate(bean interface{}, fun IterFunc) error {
|
||||||
return session.Iterate(bean, fun)
|
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
|
// Count counts the records. bean's non-empty fields
|
||||||
// are conditions.
|
// are conditions.
|
||||||
func (engine *Engine) Count(bean interface{}) (int64, error) {
|
func (engine *Engine) Count(bean interface{}) (int64, error) {
|
||||||
|
@ -972,18 +984,22 @@ func (engine *Engine) Import(ddlPath string) ([]sql.Result, error) {
|
||||||
scanner.Split(semiColSpliter)
|
scanner.Split(semiColSpliter)
|
||||||
|
|
||||||
session := engine.NewSession()
|
session := engine.NewSession()
|
||||||
session.IsAutoClose = false
|
defer session.Close()
|
||||||
|
err = session.newDb()
|
||||||
|
if err != nil {
|
||||||
|
return results, err
|
||||||
|
}
|
||||||
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
query := scanner.Text()
|
query := scanner.Text()
|
||||||
query = strings.Trim(query, " \t")
|
query = strings.Trim(query, " \t")
|
||||||
if len(query) > 0 {
|
if len(query) > 0 {
|
||||||
result, err := session.Exec(query)
|
result, err := session.Db.Exec(query)
|
||||||
results = append(results, result)
|
results = append(results, result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lastError = err
|
lastError = err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
session.Close()
|
|
||||||
return results, lastError
|
return results, lastError
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,10 +40,10 @@ type IdFilter struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *IdFilter) Do(sql string, session *Session) string {
|
func (i *IdFilter) Do(sql string, session *Session) string {
|
||||||
if session.Statement.RefTable != nil && session.Statement.RefTable.PrimaryKey != "" {
|
if session.Statement.RefTable != nil && len(session.Statement.RefTable.PrimaryKeys) == 1 {
|
||||||
sql = strings.Replace(sql, "`(id)`", session.Engine.Quote(session.Statement.RefTable.PrimaryKey), -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.PrimaryKey), -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.PrimaryKey), -1)
|
return strings.Replace(sql, "(id)", session.Engine.Quote(session.Statement.RefTable.PrimaryKeys[0]), -1)
|
||||||
}
|
}
|
||||||
return sql
|
return sql
|
||||||
}
|
}
|
||||||
|
|
73
helpers.go
73
helpers.go
|
@ -1,8 +1,12 @@
|
||||||
package xorm
|
package xorm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func indexNoCase(s, sep string) int {
|
func indexNoCase(s, sep string) int {
|
||||||
|
@ -61,3 +65,72 @@ func sliceEq(left, right []string) bool {
|
||||||
|
|
||||||
return true
|
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
|
||||||
|
}
|
||||||
|
|
7
mssql.go
7
mssql.go
|
@ -22,6 +22,7 @@ type odbcParser struct {
|
||||||
func (p *odbcParser) parse(driverName, dataSourceName string) (*uri, error) {
|
func (p *odbcParser) parse(driverName, dataSourceName string) (*uri, error) {
|
||||||
kv := strings.Split(dataSourceName, ";")
|
kv := strings.Split(dataSourceName, ";")
|
||||||
var dbName string
|
var dbName string
|
||||||
|
|
||||||
for _, c := range kv {
|
for _, c := range kv {
|
||||||
vv := strings.Split(strings.TrimSpace(c), "=")
|
vv := strings.Split(strings.TrimSpace(c), "=")
|
||||||
if len(vv) == 2 {
|
if len(vv) == 2 {
|
||||||
|
@ -155,6 +156,7 @@ where a.object_id=object_id('` + tableName + `')`
|
||||||
for name, content := range record {
|
for name, content := range record {
|
||||||
switch name {
|
switch name {
|
||||||
case "name":
|
case "name":
|
||||||
|
|
||||||
col.Name = strings.Trim(string(content), "` ")
|
col.Name = strings.Trim(string(content), "` ")
|
||||||
case "ctype":
|
case "ctype":
|
||||||
ct := strings.ToUpper(string(content))
|
ct := strings.ToUpper(string(content))
|
||||||
|
@ -163,11 +165,14 @@ where a.object_id=object_id('` + tableName + `')`
|
||||||
col.SQLType = SQLType{TimeStampz, 0, 0}
|
col.SQLType = SQLType{TimeStampz, 0, 0}
|
||||||
case "NVARCHAR":
|
case "NVARCHAR":
|
||||||
col.SQLType = SQLType{Varchar, 0, 0}
|
col.SQLType = SQLType{Varchar, 0, 0}
|
||||||
|
case "IMAGE":
|
||||||
|
col.SQLType = SQLType{VarBinary, 0, 0}
|
||||||
default:
|
default:
|
||||||
if _, ok := sqlTypes[ct]; ok {
|
if _, ok := sqlTypes[ct]; ok {
|
||||||
col.SQLType = SQLType{ct, 0, 0}
|
col.SQLType = SQLType{ct, 0, 0}
|
||||||
} else {
|
} 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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,18 +4,16 @@ package xorm
|
||||||
// +build windows
|
// +build windows
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
_ "github.com/lunny/godbc"
|
_ "github.com/lunny/godbc"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
const mssqlConnStr = "driver={SQL Server};Server=192.168.20.135;Database=xorm_test; uid=sa; pwd=1234;"
|
||||||
CREATE DATABASE IF NOT EXISTS xorm_test CHARACTER SET
|
|
||||||
utf8 COLLATE utf8_general_ci;
|
|
||||||
*/
|
|
||||||
|
|
||||||
func newMssqlEngine() (*Engine, error) {
|
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) {
|
func TestMssql(t *testing.T) {
|
||||||
|
@ -51,7 +49,41 @@ func TestMssqlWithCache(t *testing.T) {
|
||||||
testAll2(engine, 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()
|
engine, err := newMssqlEngine()
|
||||||
defer engine.Close()
|
defer engine.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -62,7 +94,18 @@ func BenchmarkMssqlNoCache(t *testing.B) {
|
||||||
doBenchFind(engine, t)
|
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()
|
engine, err := newMssqlEngine()
|
||||||
defer engine.Close()
|
defer engine.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -70,5 +113,30 @@ func BenchmarkMssqlCache(t *testing.B) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000))
|
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)
|
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)
|
||||||
|
}
|
||||||
|
|
19
mysql.go
19
mysql.go
|
@ -33,7 +33,6 @@ type mysqlParser struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *mysqlParser) parse(driverName, dataSourceName string) (*uri, error) {
|
func (p *mysqlParser) parse(driverName, dataSourceName string) (*uri, error) {
|
||||||
//cfg.params = make(map[string]string)
|
|
||||||
dsnPattern := regexp.MustCompile(
|
dsnPattern := regexp.MustCompile(
|
||||||
`^(?:(?P<user>.*?)(?::(?P<passwd>.*))?@)?` + // [user[:password]@]
|
`^(?:(?P<user>.*?)(?::(?P<passwd>.*))?@)?` + // [user[:password]@]
|
||||||
`(?:(?P<net>[^\(]*)(?:\((?P<addr>[^\)]*)\))?)?` + // [net[(addr)]]
|
`(?:(?P<net>[^\(]*)(?:\((?P<addr>[^\)]*)\))?)?` + // [net[(addr)]]
|
||||||
|
@ -49,6 +48,20 @@ func (p *mysqlParser) parse(driverName, dataSourceName string) (*uri, error) {
|
||||||
switch names[i] {
|
switch names[i] {
|
||||||
case "dbname":
|
case "dbname":
|
||||||
uri.dbName = match
|
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
|
return uri, nil
|
||||||
|
@ -68,6 +81,10 @@ func (b *base) init(parser parser, drivername, dataSourceName string) (err error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *base) URI() *uri {
|
||||||
|
return b.uri
|
||||||
|
}
|
||||||
|
|
||||||
func (b *base) DBType() string {
|
func (b *base) DBType() string {
|
||||||
return b.uri.dbType
|
return b.uri.dbType
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,6 @@ CREATE DATABASE IF NOT EXISTS xorm_test CHARACTER SET
|
||||||
utf8 COLLATE utf8_general_ci;
|
utf8 COLLATE utf8_general_ci;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var mysqlShowTestSql bool = true
|
|
||||||
|
|
||||||
func TestMysql(t *testing.T) {
|
func TestMysql(t *testing.T) {
|
||||||
err := mysqlDdlImport()
|
err := mysqlDdlImport()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -27,10 +25,34 @@ func TestMysql(t *testing.T) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
engine.ShowSQL = mysqlShowTestSql
|
engine.ShowSQL = showTestSql
|
||||||
engine.ShowErr = mysqlShowTestSql
|
engine.ShowErr = showTestSql
|
||||||
engine.ShowWarn = mysqlShowTestSql
|
engine.ShowWarn = showTestSql
|
||||||
engine.ShowDebug = mysqlShowTestSql
|
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)
|
testAll(engine, t)
|
||||||
testAll2(engine, t)
|
testAll2(engine, t)
|
||||||
|
@ -51,10 +73,10 @@ func TestMysqlWithCache(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000))
|
engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000))
|
||||||
engine.ShowSQL = mysqlShowTestSql
|
engine.ShowSQL = showTestSql
|
||||||
engine.ShowErr = mysqlShowTestSql
|
engine.ShowErr = showTestSql
|
||||||
engine.ShowWarn = mysqlShowTestSql
|
engine.ShowWarn = showTestSql
|
||||||
engine.ShowDebug = mysqlShowTestSql
|
engine.ShowDebug = showTestSql
|
||||||
|
|
||||||
testAll(engine, t)
|
testAll(engine, t)
|
||||||
testAll2(engine, t)
|
testAll2(engine, t)
|
||||||
|
@ -69,10 +91,10 @@ func mysqlDdlImport() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
engine.ShowSQL = mysqlShowTestSql
|
engine.ShowSQL = showTestSql
|
||||||
engine.ShowErr = mysqlShowTestSql
|
engine.ShowErr = showTestSql
|
||||||
engine.ShowWarn = mysqlShowTestSql
|
engine.ShowWarn = showTestSql
|
||||||
engine.ShowDebug = mysqlShowTestSql
|
engine.ShowDebug = showTestSql
|
||||||
|
|
||||||
sqlResults, _ := engine.Import("tests/mysql_ddl.sql")
|
sqlResults, _ := engine.Import("tests/mysql_ddl.sql")
|
||||||
engine.LogDebug("sql results: %v", sqlResults)
|
engine.LogDebug("sql results: %v", sqlResults)
|
||||||
|
|
|
@ -67,7 +67,11 @@ func (db *postgres) SqlType(c *Column) string {
|
||||||
switch t := c.SQLType.Name; t {
|
switch t := c.SQLType.Name; t {
|
||||||
case TinyInt:
|
case TinyInt:
|
||||||
res = SmallInt
|
res = SmallInt
|
||||||
|
return res
|
||||||
case MediumInt, Int, Integer:
|
case MediumInt, Int, Integer:
|
||||||
|
if c.IsAutoIncrement {
|
||||||
|
return Serial
|
||||||
|
}
|
||||||
return Integer
|
return Integer
|
||||||
case Serial, BigSerial:
|
case Serial, BigSerial:
|
||||||
c.IsAutoIncrement = true
|
c.IsAutoIncrement = true
|
||||||
|
|
|
@ -7,12 +7,16 @@ import (
|
||||||
_ "github.com/lib/pq"
|
_ "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) {
|
func newPostgresEngine() (*Engine, error) {
|
||||||
return NewEngine("postgres", "dbname=xorm_test sslmode=disable")
|
return NewEngine("postgres", connStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPostgresDriverDB() (*sql.DB, error) {
|
func newPostgresDriverDB() (*sql.DB, error) {
|
||||||
return sql.Open("postgres", "dbname=xorm_test sslmode=disable")
|
return sql.Open("postgres", connStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPostgres(t *testing.T) {
|
func TestPostgres(t *testing.T) {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
966
session.go
966
session.go
File diff suppressed because it is too large
Load Diff
13
sqlite3.go
13
sqlite3.go
|
@ -190,16 +190,19 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*Index, error) {
|
||||||
|
|
||||||
indexes := make(map[string]*Index, 0)
|
indexes := make(map[string]*Index, 0)
|
||||||
for _, record := range res {
|
for _, record := range res {
|
||||||
var sql string
|
|
||||||
index := new(Index)
|
index := new(Index)
|
||||||
for name, content := range record {
|
sql := string(record["sql"])
|
||||||
if name == "sql" {
|
|
||||||
sql = string(content)
|
if sql == "" {
|
||||||
}
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
nNStart := strings.Index(sql, "INDEX")
|
nNStart := strings.Index(sql, "INDEX")
|
||||||
nNEnd := strings.Index(sql, "ON")
|
nNEnd := strings.Index(sql, "ON")
|
||||||
|
if nNStart == -1 || nNEnd == -1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
indexName := strings.Trim(sql[nNStart+6:nNEnd], "` []")
|
indexName := strings.Trim(sql[nNStart+6:nNEnd], "` []")
|
||||||
//fmt.Println(indexName)
|
//fmt.Println(indexName)
|
||||||
if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
|
if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
|
||||||
|
|
43
statement.go
43
statement.go
|
@ -335,11 +335,15 @@ func buildConditions(engine *Engine, table *Table, bean interface{},
|
||||||
} else {
|
} else {
|
||||||
engine.autoMapType(fieldValue.Type())
|
engine.autoMapType(fieldValue.Type())
|
||||||
if table, ok := engine.Tables[fieldValue.Type()]; ok {
|
if table, ok := engine.Tables[fieldValue.Type()]; ok {
|
||||||
pkField := reflect.Indirect(fieldValue).FieldByName(table.PKColumn().FieldName)
|
if len(table.PrimaryKeys) == 1 {
|
||||||
if pkField.Int() != 0 {
|
pkField := reflect.Indirect(fieldValue).FieldByName(table.PKColumns()[0].FieldName)
|
||||||
val = pkField.Interface()
|
if pkField.Int() != 0 {
|
||||||
|
val = pkField.Interface()
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
continue
|
//TODO: how to handler?
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val = fieldValue.Interface()
|
val = fieldValue.Interface()
|
||||||
|
@ -506,7 +510,7 @@ func (statement *Statement) Distinct(columns ...string) *Statement {
|
||||||
func (statement *Statement) Cols(columns ...string) *Statement {
|
func (statement *Statement) Cols(columns ...string) *Statement {
|
||||||
newColumns := col2NewCols(columns...)
|
newColumns := col2NewCols(columns...)
|
||||||
for _, nc := range newColumns {
|
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(", ")))
|
statement.ColumnStr = statement.Engine.Quote(strings.Join(newColumns, statement.Engine.Quote(", ")))
|
||||||
return statement
|
return statement
|
||||||
|
@ -517,7 +521,7 @@ func (statement *Statement) UseBool(columns ...string) *Statement {
|
||||||
if len(columns) > 0 {
|
if len(columns) > 0 {
|
||||||
newColumns := col2NewCols(columns...)
|
newColumns := col2NewCols(columns...)
|
||||||
for _, nc := range newColumns {
|
for _, nc := range newColumns {
|
||||||
statement.boolColumnMap[nc] = true
|
statement.boolColumnMap[strings.ToLower(nc)] = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
statement.allUseBool = true
|
statement.allUseBool = true
|
||||||
|
@ -529,7 +533,7 @@ func (statement *Statement) UseBool(columns ...string) *Statement {
|
||||||
func (statement *Statement) Omit(columns ...string) {
|
func (statement *Statement) Omit(columns ...string) {
|
||||||
newColumns := col2NewCols(columns...)
|
newColumns := col2NewCols(columns...)
|
||||||
for _, nc := range newColumns {
|
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(", ")))
|
statement.OmitStr = statement.Engine.Quote(strings.Join(newColumns, statement.Engine.Quote(", ")))
|
||||||
}
|
}
|
||||||
|
@ -582,7 +586,7 @@ func (statement *Statement) genColumnStr() string {
|
||||||
colNames := make([]string, 0)
|
colNames := make([]string, 0)
|
||||||
for _, col := range table.Columns {
|
for _, col := range table.Columns {
|
||||||
if statement.OmitStr != "" {
|
if statement.OmitStr != "" {
|
||||||
if _, ok := statement.columnMap[col.Name]; ok {
|
if _, ok := statement.columnMap[strings.ToLower(col.Name)]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -606,7 +610,7 @@ func (statement *Statement) genCreateTableSQL() string {
|
||||||
pkList := []string{}
|
pkList := []string{}
|
||||||
|
|
||||||
for _, colName := range statement.RefTable.ColumnsSeq {
|
for _, colName := range statement.RefTable.ColumnsSeq {
|
||||||
col := statement.RefTable.Columns[colName]
|
col := statement.RefTable.Columns[strings.ToLower(colName)]
|
||||||
if col.IsPrimaryKey {
|
if col.IsPrimaryKey {
|
||||||
pkList = append(pkList, col.Name)
|
pkList = append(pkList, col.Name)
|
||||||
}
|
}
|
||||||
|
@ -614,7 +618,7 @@ func (statement *Statement) genCreateTableSQL() string {
|
||||||
|
|
||||||
statement.Engine.LogDebug("len:", len(pkList))
|
statement.Engine.LogDebug("len:", len(pkList))
|
||||||
for _, colName := range statement.RefTable.ColumnsSeq {
|
for _, colName := range statement.RefTable.ColumnsSeq {
|
||||||
col := statement.RefTable.Columns[colName]
|
col := statement.RefTable.Columns[strings.ToLower(colName)]
|
||||||
if col.IsPrimaryKey && len(pkList) == 1 {
|
if col.IsPrimaryKey && len(pkList) == 1 {
|
||||||
sql += col.String(statement.Engine.dialect)
|
sql += col.String(statement.Engine.dialect)
|
||||||
} else {
|
} else {
|
||||||
|
@ -634,8 +638,12 @@ func (statement *Statement) genCreateTableSQL() string {
|
||||||
if statement.Engine.dialect.SupportEngine() && statement.StoreEngine != "" {
|
if statement.Engine.dialect.SupportEngine() && statement.StoreEngine != "" {
|
||||||
sql += " ENGINE=" + statement.StoreEngine
|
sql += " ENGINE=" + statement.StoreEngine
|
||||||
}
|
}
|
||||||
if statement.Engine.dialect.SupportCharset() && statement.Charset != "" {
|
if statement.Engine.dialect.SupportCharset() {
|
||||||
sql += " DEFAULT CHARSET " + statement.Charset
|
if statement.Charset != "" {
|
||||||
|
sql += " DEFAULT CHARSET " + statement.Charset
|
||||||
|
} else if statement.Engine.dialect.URI().charset != "" {
|
||||||
|
sql += " DEFAULT CHARSET " + statement.Engine.dialect.URI().charset
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sql += ";"
|
sql += ";"
|
||||||
return sql
|
return sql
|
||||||
|
@ -753,9 +761,10 @@ func (statement *Statement) genCountSql(bean interface{}) (string, []interface{}
|
||||||
|
|
||||||
statement.ConditionStr = strings.Join(colNames, " AND ")
|
statement.ConditionStr = strings.Join(colNames, " AND ")
|
||||||
statement.BeanArgs = args
|
statement.BeanArgs = args
|
||||||
var id string = "*"
|
// count(index fieldname) > count(0) > count(*)
|
||||||
if table.PrimaryKey != "" {
|
var id string = "0"
|
||||||
id = statement.Engine.Quote(table.PrimaryKey)
|
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...)
|
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 _, elem := range *(statement.IdParam) {
|
||||||
for ; i < colCnt; i++ {
|
for ; i < colCnt; i++ {
|
||||||
colName := statement.RefTable.ColumnsSeq[i]
|
colName := statement.RefTable.ColumnsSeq[i]
|
||||||
col := statement.RefTable.Columns[colName]
|
col := statement.RefTable.Columns[strings.ToLower(colName)]
|
||||||
if col.IsPrimaryKey {
|
if col.IsPrimaryKey {
|
||||||
statement.And(fmt.Sprintf("%v=?", col.Name), elem)
|
statement.And(fmt.Sprintf("%v=?", col.Name), elem)
|
||||||
i++
|
i++
|
||||||
|
@ -832,7 +841,7 @@ func (statement *Statement) processIdParam() {
|
||||||
// false update/delete
|
// false update/delete
|
||||||
for ; i < colCnt; i++ {
|
for ; i < colCnt; i++ {
|
||||||
colName := statement.RefTable.ColumnsSeq[i]
|
colName := statement.RefTable.ColumnsSeq[i]
|
||||||
col := statement.RefTable.Columns[colName]
|
col := statement.RefTable.Columns[strings.ToLower(colName)]
|
||||||
if col.IsPrimaryKey {
|
if col.IsPrimaryKey {
|
||||||
statement.And(fmt.Sprintf("%v=?", col.Name), "")
|
statement.And(fmt.Sprintf("%v=?", col.Name), "")
|
||||||
}
|
}
|
||||||
|
|
71
table.go
71
table.go
|
@ -163,6 +163,7 @@ func Type2SQLType(t reflect.Type) (st SQLType) {
|
||||||
if t == reflect.TypeOf(c_TIME_DEFAULT) {
|
if t == reflect.TypeOf(c_TIME_DEFAULT) {
|
||||||
st = SQLType{DateTime, 0, 0}
|
st = SQLType{DateTime, 0, 0}
|
||||||
} else {
|
} else {
|
||||||
|
// TODO need to handle association struct
|
||||||
st = SQLType{Text, 0, 0}
|
st = SQLType{Text, 0, 0}
|
||||||
}
|
}
|
||||||
case reflect.Ptr:
|
case reflect.Ptr:
|
||||||
|
@ -297,6 +298,8 @@ func (col *Column) String(d dialect) string {
|
||||||
|
|
||||||
if col.Default != "" {
|
if col.Default != "" {
|
||||||
sql += "DEFAULT " + col.Default + " "
|
sql += "DEFAULT " + col.Default + " "
|
||||||
|
} else if col.IsVersion {
|
||||||
|
sql += "DEFAULT 1 "
|
||||||
}
|
}
|
||||||
|
|
||||||
return sql
|
return sql
|
||||||
|
@ -315,6 +318,8 @@ func (col *Column) stringNoPk(d dialect) string {
|
||||||
|
|
||||||
if col.Default != "" {
|
if col.Default != "" {
|
||||||
sql += "DEFAULT " + col.Default + " "
|
sql += "DEFAULT " + col.Default + " "
|
||||||
|
} else if col.IsVersion {
|
||||||
|
sql += "DEFAULT 1 "
|
||||||
}
|
}
|
||||||
|
|
||||||
return sql
|
return sql
|
||||||
|
@ -339,16 +344,17 @@ func (col *Column) ValueOf(bean interface{}) reflect.Value {
|
||||||
|
|
||||||
// database table
|
// database table
|
||||||
type Table struct {
|
type Table struct {
|
||||||
Name string
|
Name string
|
||||||
Type reflect.Type
|
Type reflect.Type
|
||||||
ColumnsSeq []string
|
ColumnsSeq []string
|
||||||
Columns map[string]*Column
|
Columns map[string]*Column
|
||||||
Indexes map[string]*Index
|
Indexes map[string]*Index
|
||||||
PrimaryKey string
|
PrimaryKeys []string
|
||||||
Created map[string]bool
|
AutoIncrement string
|
||||||
Updated string
|
Created map[string]bool
|
||||||
Version string
|
Updated string
|
||||||
Cacher Cacher
|
Version string
|
||||||
|
Cacher Cacher
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -362,20 +368,31 @@ func NewTable(name string, t reflect.Type) *Table {
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
// if has primary key, return column
|
// if has primary key, return column
|
||||||
func (table *Table) PKColumn() *Column {
|
func (table *Table) PKColumns() []*Column {
|
||||||
return table.Columns[table.PrimaryKey]
|
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 {
|
func (table *Table) VersionColumn() *Column {
|
||||||
return table.Columns[table.Version]
|
return table.Columns[strings.ToLower(table.Version)]
|
||||||
}
|
}
|
||||||
|
|
||||||
// add a column to table
|
// add a column to table
|
||||||
func (table *Table) AddColumn(col *Column) {
|
func (table *Table) AddColumn(col *Column) {
|
||||||
table.ColumnsSeq = append(table.ColumnsSeq, col.Name)
|
table.ColumnsSeq = append(table.ColumnsSeq, col.Name)
|
||||||
table.Columns[col.Name] = col
|
table.Columns[strings.ToLower(col.Name)] = col
|
||||||
if col.IsPrimaryKey {
|
if col.IsPrimaryKey {
|
||||||
table.PrimaryKey = col.Name
|
table.PrimaryKeys = append(table.PrimaryKeys, col.Name)
|
||||||
|
}
|
||||||
|
if col.IsAutoIncrement {
|
||||||
|
table.AutoIncrement = col.Name
|
||||||
}
|
}
|
||||||
if col.IsCreated {
|
if col.IsCreated {
|
||||||
table.Created[col.Name] = true
|
table.Created[col.Name] = true
|
||||||
|
@ -398,8 +415,9 @@ func (table *Table) genCols(session *Session, bean interface{}, useCol bool, inc
|
||||||
args := make([]interface{}, 0)
|
args := make([]interface{}, 0)
|
||||||
|
|
||||||
for _, col := range table.Columns {
|
for _, col := range table.Columns {
|
||||||
|
lColName := strings.ToLower(col.Name)
|
||||||
if useCol && !col.IsVersion && !col.IsCreated && !col.IsUpdated {
|
if useCol && !col.IsVersion && !col.IsCreated && !col.IsUpdated {
|
||||||
if _, ok := session.Statement.columnMap[col.Name]; !ok {
|
if _, ok := session.Statement.columnMap[lColName]; !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -408,17 +426,30 @@ func (table *Table) genCols(session *Session, bean interface{}, useCol bool, inc
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldValue := col.ValueOf(bean)
|
fieldValue := col.ValueOf(bean)
|
||||||
if col.IsAutoIncrement && fieldValue.Int() == 0 {
|
if col.IsAutoIncrement {
|
||||||
continue
|
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 session.Statement.ColumnStr != "" {
|
||||||
if _, ok := session.Statement.columnMap[col.Name]; !ok {
|
if _, ok := session.Statement.columnMap[lColName]; !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if session.Statement.OmitStr != "" {
|
if session.Statement.OmitStr != "" {
|
||||||
if _, ok := session.Statement.columnMap[col.Name]; ok {
|
if _, ok := session.Statement.columnMap[lColName]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue