Merge branch 'master' of github.com:go-xorm/xorm
Conflicts: engine.go session.go
This commit is contained in:
commit
00be2b792a
|
|
@ -1,2 +1,2 @@
|
|||
[target]
|
||||
path = github.com/lunny/xorm
|
||||
path = github.com/go-xorm/xorm
|
||||
|
|
@ -21,11 +21,11 @@ We appreciate any bug reports, but especially ones with self-contained
|
|||
further) test cases. It's especially helpful if you can submit a pull
|
||||
request with just the failing test case (you'll probably want to
|
||||
pattern it after the tests in
|
||||
[base_test.go](https://github.com/lunny/xorm/blob/master/base_test.go) AND
|
||||
[benchmark_base_test.go](https://github.com/lunny/xorm/blob/master/benchmark_base_test.go).
|
||||
[base_test.go](https://github.com/go-xorm/xorm/blob/master/base_test.go) AND
|
||||
[benchmark_base_test.go](https://github.com/go-xorm/xorm/blob/master/benchmark_base_test.go).
|
||||
|
||||
If you implements a new database interface, you maybe need to add a <databasename>_test.go file.
|
||||
For example, [mysql_test.go](https://github.com/lunny/xorm/blob/master/mysql_test.go)
|
||||
For example, [mysql_test.go](https://github.com/go-xorm/xorm/blob/master/mysql_test.go)
|
||||
|
||||
### New functionality
|
||||
|
||||
|
|
|
|||
15
README.md
15
README.md
|
|
@ -1,8 +1,8 @@
|
|||
[中文](https://github.com/lunny/xorm/blob/master/README_CN.md)
|
||||
[中文](https://github.com/go-xorm/xorm/blob/master/README_CN.md)
|
||||
|
||||
Xorm is a simple and powerful ORM for Go.
|
||||
|
||||
[](https://drone.io/github.com/go-xorm/xorm/latest) [](http://gowalker.org/github.com/go-xorm/xorm) [](https://bitdeli.com/free "Bitdeli Badge")
|
||||
[](https://drone.io/github.com/go-xorm/xorm/latest) [](http://gowalker.org/github.com/go-xorm/xorm) [](https://bitdeli.com/free "Bitdeli Badge")
|
||||
|
||||
# Features
|
||||
|
||||
|
|
@ -41,6 +41,17 @@ Drivers for Go's sql package which currently support database/sql includes:
|
|||
|
||||
# Changelog
|
||||
|
||||
* **v0.4.0 RC1**
|
||||
Changes:
|
||||
* moved xorm cmd to [github.com/go-xorm/cmd](github.com/go-xorm/cmd)
|
||||
* refactored general DB operation a core lib at [github.com/go-xorm/core](https://github.com/go-xorm/core)
|
||||
* moved tests to github.com/go-xorm/tests [github.com/go-xorm/tests](github.com/go-xorm/tests)
|
||||
|
||||
Improvements:
|
||||
* Prepared statement cache
|
||||
* Add Incr API
|
||||
* Specify Timezone Location
|
||||
|
||||
* **v0.3.2**
|
||||
Improvements:
|
||||
* Add AllCols & MustCols function
|
||||
|
|
|
|||
33
README_CN.md
33
README_CN.md
|
|
@ -1,10 +1,10 @@
|
|||
# xorm
|
||||
|
||||
[English](https://github.com/lunny/xorm/blob/master/README.md)
|
||||
[English](https://github.com/go-xorm/xorm/blob/master/README.md)
|
||||
|
||||
xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作非常简便。
|
||||
|
||||
[](https://drone.io/github.com/lunny/xorm/latest) [](http://gowalker.org/github.com/lunny/xorm)
|
||||
[](https://drone.io/github.com/go-xorm/xorm/latest) [](http://gowalker.org/github.com/go-xorm/xorm)
|
||||
|
||||
## 特性
|
||||
|
||||
|
|
@ -42,12 +42,23 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作
|
|||
|
||||
## 更新日志
|
||||
|
||||
* **v0.4.0 RC1**
|
||||
新特性:
|
||||
* 移动xorm cmd [github.com/go-xorm/cmd](github.com/go-xorm/cmd)
|
||||
* 在重构一般DB操作核心库 [github.com/go-xorm/core](https://github.com/go-xorm/core)
|
||||
* 移动测试github.com/复XORM/测试 [github.com/go-xorm/tests](github.com/go-xorm/tests)
|
||||
|
||||
改进:
|
||||
* Prepared statement 缓存
|
||||
* 添加 Incr API
|
||||
* 指定时区位置
|
||||
|
||||
* **v0.3.2**
|
||||
Improvements:
|
||||
新特性:
|
||||
* Add AllCols & MustCols function
|
||||
* Add TableName for custom table name
|
||||
|
||||
Bug Fixes:
|
||||
Bug 修复:
|
||||
* #46
|
||||
* #51
|
||||
* #53
|
||||
|
|
@ -69,25 +80,25 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作
|
|||
* 查询函数 Get()/Find()/Iterate() 在性能上的改进
|
||||
|
||||
|
||||
[更多更新日志...](https://github.com/lunny/xorm/blob/master/docs/ChangelogCN.md)
|
||||
[更多更新日志...](https://github.com/go-xorm/xorm/blob/master/docs/ChangelogCN.md)
|
||||
|
||||
## 安装
|
||||
|
||||
推荐使用 [gopm](https://github.com/gpmgo/gopm) 进行安装:
|
||||
|
||||
gopm get github.com/lunny/xorm
|
||||
gopm get github.com/go-xorm/xorm
|
||||
|
||||
或者您也可以使用go工具进行安装:
|
||||
|
||||
go get github.com/lunny/xorm
|
||||
go get github.com/go-xorm/xorm
|
||||
|
||||
## 文档
|
||||
|
||||
* [快速开始](https://github.com/lunny/xorm/blob/master/docs/QuickStart.md)
|
||||
* [快速开始](https://github.com/go-xorm/xorm/blob/master/docs/QuickStart.md)
|
||||
|
||||
* [GoWalker代码文档](http://gowalker.org/github.com/lunny/xorm)
|
||||
* [GoWalker代码文档](http://gowalker.org/github.com/go-xorm/xorm)
|
||||
|
||||
* [Godoc代码文档](http://godoc.org/github.com/lunny/xorm)
|
||||
* [Godoc代码文档](http://godoc.org/github.com/go-xorm/xorm)
|
||||
|
||||
|
||||
## 案例
|
||||
|
|
@ -115,7 +126,7 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作
|
|||
|
||||
# 贡献者
|
||||
|
||||
如果您也想为Xorm贡献您的力量,请查看 [CONTRIBUTING](https://github.com/lunny/xorm/blob/master/CONTRIBUTING.md)
|
||||
如果您也想为Xorm贡献您的力量,请查看 [CONTRIBUTING](https://github.com/go-xorm/xorm/blob/master/CONTRIBUTING.md)
|
||||
|
||||
* [Lunny](https://github.com/lunny)
|
||||
* [Nashtsai](https://github.com/nashtsai)
|
||||
|
|
|
|||
4151
base_test.go
4151
base_test.go
File diff suppressed because it is too large
Load Diff
|
|
@ -1 +0,0 @@
|
|||
go test -v -bench=. -run=XXX
|
||||
|
|
@ -1 +0,0 @@
|
|||
go test -v -bench=. -run=XXX
|
||||
|
|
@ -1,174 +0,0 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type BigStruct struct {
|
||||
Id int64
|
||||
Name string
|
||||
Title string
|
||||
Age string
|
||||
Alias string
|
||||
NickName string
|
||||
}
|
||||
|
||||
func doBenchDriverInsert(db *sql.DB, b *testing.B) {
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := db.Exec(`insert into big_struct (name, title, age, alias, nick_name)
|
||||
values ('fafdasf', 'fadfa', 'afadfsaf', 'fadfafdsafd', 'fadfafdsaf')`)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
func doBenchDriverFind(db *sql.DB, b *testing.B) {
|
||||
b.StopTimer()
|
||||
for i := 0; i < 50; i++ {
|
||||
_, err := db.Exec(`insert into big_struct (name, title, age, alias, nick_name)
|
||||
values ('fafdasf', 'fadfa', 'afadfsaf', 'fadfafdsafd', 'fadfafdsaf')`)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N/50; i++ {
|
||||
rows, err := db.Query("select * from big_struct limit 50")
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
for rows.Next() {
|
||||
s := &BigStruct{}
|
||||
rows.Scan(&s.Id, &s.Name, &s.Title, &s.Age, &s.Alias, &s.NickName)
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
func doBenchDriver(newdriver func() (*sql.DB, error), createTableSql,
|
||||
dropTableSql string, opFunc func(*sql.DB, *testing.B), t *testing.B) {
|
||||
db, err := newdriver()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
_, err = db.Exec(createTableSql)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
opFunc(db, t)
|
||||
|
||||
_, err = db.Exec(dropTableSql)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func doBenchInsert(engine *Engine, b *testing.B) {
|
||||
b.StopTimer()
|
||||
bs := &BigStruct{0, "fafdasf", "fadfa", "afadfsaf", "fadfafdsafd", "fadfafdsaf"}
|
||||
err := engine.CreateTables(bs)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
bs.Id = 0
|
||||
_, err = engine.Insert(bs)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
err = engine.DropTables(bs)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func doBenchFind(engine *Engine, b *testing.B) {
|
||||
b.StopTimer()
|
||||
bs := &BigStruct{0, "fafdasf", "fadfa", "afadfsaf", "fadfafdsafd", "fadfafdsaf"}
|
||||
err := engine.CreateTables(bs)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
bs.Id = 0
|
||||
_, err = engine.Insert(bs)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N/50; i++ {
|
||||
bss := new([]BigStruct)
|
||||
err = engine.Limit(50).Find(bss)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
err = engine.DropTables(bs)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func doBenchFindPtr(engine *Engine, b *testing.B) {
|
||||
b.StopTimer()
|
||||
bs := &BigStruct{0, "fafdasf", "fadfa", "afadfsaf", "fadfafdsafd", "fadfafdsaf"}
|
||||
err := engine.CreateTables(bs)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
bs.Id = 0
|
||||
_, err = engine.Insert(bs)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N/50; i++ {
|
||||
bss := new([]*BigStruct)
|
||||
err = engine.Limit(50).Find(bss)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
err = engine.DropTables(bs)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
6
doc.go
6
doc.go
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2013 The XORM Authors. All rights reserved.
|
||||
// Copyright 2013 - 2014 The XORM Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ Installation
|
|||
|
||||
Make sure you have installed Go 1.1+ and then:
|
||||
|
||||
go get github.com/lunny/xorm
|
||||
go get github.com/go-xorm/xorm
|
||||
|
||||
Create Engine
|
||||
|
||||
|
|
@ -137,6 +137,6 @@ The above 7 methods could use with condition methods.
|
|||
engine.Join("LEFT", "userdetail", "user.id=userdetail.id").Find()
|
||||
//SELECT * FROM user LEFT JOIN userdetail ON user.id=userdetail.id
|
||||
|
||||
More usage, please visit https://github.com/lunny/xorm/blob/master/docs/QuickStartEn.md
|
||||
More usage, please visit https://github.com/go-xorm/xorm/blob/master/docs/QuickStartEn.md
|
||||
*/
|
||||
package xorm
|
||||
|
|
|
|||
128
docs/AutoMap.md
128
docs/AutoMap.md
|
|
@ -1,65 +1,65 @@
|
|||
When a struct auto mapping to a database's table, the below table describes how they change to each other:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>go type's kind
|
||||
</td>
|
||||
<td>value method</td>
|
||||
<td>xorm type
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>implemented Conversion</td>
|
||||
<td>Conversion.ToDB / Conversion.FromDB</td>
|
||||
<td>Text</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>int, int8, int16, int32, uint, uint8, uint16, uint32</td>
|
||||
<td></td>
|
||||
<td> Int </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>int64, uint64</td><td></td><td>BigInt</td>
|
||||
</tr>
|
||||
<tr><td>float32</td><td></td><td>Float</td>
|
||||
</tr>
|
||||
<tr><td>float64</td><td></td><td>Double</td>
|
||||
</tr>
|
||||
<tr><td>complex64, complex128</td>
|
||||
<td>json.Marshal / json.UnMarshal</td>
|
||||
<td>Varchar(64)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>[]uint8</td><td></td><td>Blob</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>array, slice, map except []uint8</td>
|
||||
<td>json.Marshal / json.UnMarshal</td>
|
||||
<td>Text</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>bool</td><td>1 or 0</td><td>Bool</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>string</td><td></td><td>Varchar(255)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>time.Time</td><td></td><td>DateTime</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>cascade struct</td><td>primary key field value</td><td>BigInt</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>struct</td><td>json.Marshal / json.UnMarshal</td><td>Text</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Others
|
||||
</td>
|
||||
<td></td>
|
||||
<td>
|
||||
Text
|
||||
</td>
|
||||
</tr>
|
||||
When a struct auto mapping to a database's table, the below table describes how they change to each other:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>go type's kind
|
||||
</td>
|
||||
<td>value method</td>
|
||||
<td>xorm type
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>implemented Conversion</td>
|
||||
<td>Conversion.ToDB / Conversion.FromDB</td>
|
||||
<td>Text</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>int, int8, int16, int32, uint, uint8, uint16, uint32</td>
|
||||
<td></td>
|
||||
<td> Int </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>int64, uint64</td><td></td><td>BigInt</td>
|
||||
</tr>
|
||||
<tr><td>float32</td><td></td><td>Float</td>
|
||||
</tr>
|
||||
<tr><td>float64</td><td></td><td>Double</td>
|
||||
</tr>
|
||||
<tr><td>complex64, complex128</td>
|
||||
<td>json.Marshal / json.UnMarshal</td>
|
||||
<td>Varchar(64)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>[]uint8</td><td></td><td>Blob</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>array, slice, map except []uint8</td>
|
||||
<td>json.Marshal / json.UnMarshal</td>
|
||||
<td>Text</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>bool</td><td>1 or 0</td><td>Bool</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>string</td><td></td><td>Varchar(255)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>time.Time</td><td></td><td>DateTime</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>cascade struct</td><td>primary key field value</td><td>BigInt</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>struct</td><td>json.Marshal / json.UnMarshal</td><td>Text</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Others
|
||||
</td>
|
||||
<td></td>
|
||||
<td>
|
||||
Text
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
@ -1,438 +1,438 @@
|
|||
<table>
|
||||
<tr>
|
||||
<td>xorm
|
||||
</td>
|
||||
<td>mysql
|
||||
</td>
|
||||
<td>sqlite3
|
||||
</td>
|
||||
<td>postgres
|
||||
</td>
|
||||
<td>remark</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>BIT
|
||||
</td>
|
||||
<td>BIT
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td>BIT
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>TINYINT
|
||||
</td>
|
||||
<td>TINYINT
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td>SMALLINT
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>SMALLINT
|
||||
</td>
|
||||
<td>SMALLINT
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td>SMALLINT
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>MEDIUMINT
|
||||
</td>
|
||||
<td>MEDIUMINT
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>INT
|
||||
</td>
|
||||
<td>INT
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>BIGINT
|
||||
</td>
|
||||
<td>BIGINT
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td>BIGINT
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr><td cols="5"></td></tr>
|
||||
|
||||
<tr>
|
||||
<td>CHAR
|
||||
</td>
|
||||
<td>CHAR
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td>CHAR
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>VARCHAR
|
||||
</td>
|
||||
<td>VARCHAR
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td>VARCHAR
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>TINYTEXT
|
||||
</td>
|
||||
<td>TINYTEXT
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>MEDIUMTEXT
|
||||
</td>
|
||||
<td>MEDIUMTEXT
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>LONGTEXT
|
||||
</td>
|
||||
<td>LONGTEXT
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr><td cols="5"></td></tr>
|
||||
|
||||
<tr>
|
||||
<td>BINARY
|
||||
</td>
|
||||
<td>BINARY
|
||||
</td>
|
||||
<td>BLOB
|
||||
</td>
|
||||
<td>BYTEA
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>VARBINARY
|
||||
</td>
|
||||
<td>VARBINARY
|
||||
</td>
|
||||
<td>BLOB
|
||||
</td>
|
||||
<td>BYTEA
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr><td cols="5"></td></tr>
|
||||
|
||||
<tr>
|
||||
<td>DATE
|
||||
</td>
|
||||
<td>DATE
|
||||
</td>
|
||||
<td>NUMERIC
|
||||
</td>
|
||||
<td>DATE
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>DATETIME
|
||||
</td>
|
||||
<td>DATETIME
|
||||
</td>
|
||||
<td>NUMERIC
|
||||
</td>
|
||||
<td>TIMESTAMP
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>TIME
|
||||
</td>
|
||||
<td>TIME
|
||||
</td>
|
||||
<td>NUMERIC
|
||||
</td>
|
||||
<td>TIME
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>TIMESTAMP
|
||||
</td>
|
||||
<td>TIMESTAMP
|
||||
</td>
|
||||
<td>NUMERIC
|
||||
</td>
|
||||
<td>TIMESTAMP
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>TIMESTAMPZ
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td>TIMESTAMP with zone
|
||||
</td>
|
||||
<td>timestamp with zone info</td>
|
||||
</tr>
|
||||
|
||||
<tr><td cols="5"></td></tr>
|
||||
<tr>
|
||||
<td>REAL
|
||||
</td>
|
||||
<td>REAL
|
||||
</td>
|
||||
<td>REAL
|
||||
</td>
|
||||
<td>REAL
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>FLOAT
|
||||
</td>
|
||||
<td>FLOAT
|
||||
</td>
|
||||
<td>REAL
|
||||
</td>
|
||||
<td>REAL
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>DOUBLE
|
||||
</td>
|
||||
<td>DOUBLE
|
||||
</td>
|
||||
<td>REAL
|
||||
</td>
|
||||
<td>DOUBLE PRECISION
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr><td cols="5"></td></tr>
|
||||
<tr>
|
||||
<td>DECIMAL
|
||||
</td>
|
||||
<td>DECIMAL
|
||||
</td>
|
||||
<td>NUMERIC
|
||||
</td>
|
||||
<td>DECIMAL
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>NUMERIC
|
||||
</td>
|
||||
<td>NUMERIC
|
||||
</td>
|
||||
<td>NUMERIC
|
||||
</td>
|
||||
<td>NUMERIC
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr><td cols="5"></td></tr>
|
||||
<tr>
|
||||
<td>TINYBLOB
|
||||
</td>
|
||||
<td>TINYBLOB
|
||||
</td>
|
||||
<td>BLOB
|
||||
</td>
|
||||
<td>BYTEA
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>BLOB
|
||||
</td>
|
||||
<td>BLOB
|
||||
</td>
|
||||
<td>BLOB
|
||||
</td>
|
||||
<td>BYTEA
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>MEDIUMBLOB
|
||||
</td>
|
||||
<td>MEDIUMBLOB
|
||||
</td>
|
||||
<td>BLOB
|
||||
</td>
|
||||
<td>BYTEA
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>LONGBLOB
|
||||
</td>
|
||||
<td>LONGBLOB
|
||||
</td>
|
||||
<td>BLOB
|
||||
</td>
|
||||
<td>BYTEA
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>BYTEA
|
||||
</td>
|
||||
<td>BLOB
|
||||
</td>
|
||||
<td>BLOB
|
||||
</td>
|
||||
<td>BYTEA
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr><td cols="5"></td></tr>
|
||||
|
||||
<tr>
|
||||
<td>BOOL
|
||||
</td>
|
||||
<td>TINYINT
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td>BOOLEAN
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>SERIAL
|
||||
</td>
|
||||
<td>INT
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td>SERIAL
|
||||
</td>
|
||||
<td>auto increment</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>BIGSERIAL
|
||||
</td>
|
||||
<td>BIGINT
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td>BIGSERIAL
|
||||
</td>
|
||||
<td>auto increment</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>xorm
|
||||
</td>
|
||||
<td>mysql
|
||||
</td>
|
||||
<td>sqlite3
|
||||
</td>
|
||||
<td>postgres
|
||||
</td>
|
||||
<td>remark</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>BIT
|
||||
</td>
|
||||
<td>BIT
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td>BIT
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>TINYINT
|
||||
</td>
|
||||
<td>TINYINT
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td>SMALLINT
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>SMALLINT
|
||||
</td>
|
||||
<td>SMALLINT
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td>SMALLINT
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>MEDIUMINT
|
||||
</td>
|
||||
<td>MEDIUMINT
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>INT
|
||||
</td>
|
||||
<td>INT
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>BIGINT
|
||||
</td>
|
||||
<td>BIGINT
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td>BIGINT
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr><td cols="5"></td></tr>
|
||||
|
||||
<tr>
|
||||
<td>CHAR
|
||||
</td>
|
||||
<td>CHAR
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td>CHAR
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>VARCHAR
|
||||
</td>
|
||||
<td>VARCHAR
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td>VARCHAR
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>TINYTEXT
|
||||
</td>
|
||||
<td>TINYTEXT
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>MEDIUMTEXT
|
||||
</td>
|
||||
<td>MEDIUMTEXT
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>LONGTEXT
|
||||
</td>
|
||||
<td>LONGTEXT
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr><td cols="5"></td></tr>
|
||||
|
||||
<tr>
|
||||
<td>BINARY
|
||||
</td>
|
||||
<td>BINARY
|
||||
</td>
|
||||
<td>BLOB
|
||||
</td>
|
||||
<td>BYTEA
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>VARBINARY
|
||||
</td>
|
||||
<td>VARBINARY
|
||||
</td>
|
||||
<td>BLOB
|
||||
</td>
|
||||
<td>BYTEA
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr><td cols="5"></td></tr>
|
||||
|
||||
<tr>
|
||||
<td>DATE
|
||||
</td>
|
||||
<td>DATE
|
||||
</td>
|
||||
<td>NUMERIC
|
||||
</td>
|
||||
<td>DATE
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>DATETIME
|
||||
</td>
|
||||
<td>DATETIME
|
||||
</td>
|
||||
<td>NUMERIC
|
||||
</td>
|
||||
<td>TIMESTAMP
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>TIME
|
||||
</td>
|
||||
<td>TIME
|
||||
</td>
|
||||
<td>NUMERIC
|
||||
</td>
|
||||
<td>TIME
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>TIMESTAMP
|
||||
</td>
|
||||
<td>TIMESTAMP
|
||||
</td>
|
||||
<td>NUMERIC
|
||||
</td>
|
||||
<td>TIMESTAMP
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>TIMESTAMPZ
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td>TEXT
|
||||
</td>
|
||||
<td>TIMESTAMP with zone
|
||||
</td>
|
||||
<td>timestamp with zone info</td>
|
||||
</tr>
|
||||
|
||||
<tr><td cols="5"></td></tr>
|
||||
<tr>
|
||||
<td>REAL
|
||||
</td>
|
||||
<td>REAL
|
||||
</td>
|
||||
<td>REAL
|
||||
</td>
|
||||
<td>REAL
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>FLOAT
|
||||
</td>
|
||||
<td>FLOAT
|
||||
</td>
|
||||
<td>REAL
|
||||
</td>
|
||||
<td>REAL
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>DOUBLE
|
||||
</td>
|
||||
<td>DOUBLE
|
||||
</td>
|
||||
<td>REAL
|
||||
</td>
|
||||
<td>DOUBLE PRECISION
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr><td cols="5"></td></tr>
|
||||
<tr>
|
||||
<td>DECIMAL
|
||||
</td>
|
||||
<td>DECIMAL
|
||||
</td>
|
||||
<td>NUMERIC
|
||||
</td>
|
||||
<td>DECIMAL
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>NUMERIC
|
||||
</td>
|
||||
<td>NUMERIC
|
||||
</td>
|
||||
<td>NUMERIC
|
||||
</td>
|
||||
<td>NUMERIC
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr><td cols="5"></td></tr>
|
||||
<tr>
|
||||
<td>TINYBLOB
|
||||
</td>
|
||||
<td>TINYBLOB
|
||||
</td>
|
||||
<td>BLOB
|
||||
</td>
|
||||
<td>BYTEA
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>BLOB
|
||||
</td>
|
||||
<td>BLOB
|
||||
</td>
|
||||
<td>BLOB
|
||||
</td>
|
||||
<td>BYTEA
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>MEDIUMBLOB
|
||||
</td>
|
||||
<td>MEDIUMBLOB
|
||||
</td>
|
||||
<td>BLOB
|
||||
</td>
|
||||
<td>BYTEA
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>LONGBLOB
|
||||
</td>
|
||||
<td>LONGBLOB
|
||||
</td>
|
||||
<td>BLOB
|
||||
</td>
|
||||
<td>BYTEA
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>BYTEA
|
||||
</td>
|
||||
<td>BLOB
|
||||
</td>
|
||||
<td>BLOB
|
||||
</td>
|
||||
<td>BYTEA
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr><td cols="5"></td></tr>
|
||||
|
||||
<tr>
|
||||
<td>BOOL
|
||||
</td>
|
||||
<td>TINYINT
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td>BOOLEAN
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>SERIAL
|
||||
</td>
|
||||
<td>INT
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td>SERIAL
|
||||
</td>
|
||||
<td>auto increment</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>BIGSERIAL
|
||||
</td>
|
||||
<td>BIGINT
|
||||
</td>
|
||||
<td>INTEGER
|
||||
</td>
|
||||
<td>BIGSERIAL
|
||||
</td>
|
||||
<td>auto increment</td>
|
||||
</tr>
|
||||
|
||||
|
||||
</table>
|
||||
|
|
@ -1,5 +1,16 @@
|
|||
## Changelog
|
||||
|
||||
* **v0.4.0 RC1**
|
||||
Changes:
|
||||
* moved xorm cmd to [github.com/go-xorm/cmd](github.com/go-xorm/cmd)
|
||||
* refactored general DB operation a core lib at [github.com/go-xorm/core](https://github.com/go-xorm/core)
|
||||
* moved tests to github.com/go-xorm/tests [github.com/go-xorm/tests](github.com/go-xorm/tests)
|
||||
|
||||
Improvements:
|
||||
* Prepared statement cache
|
||||
* Add Incr API
|
||||
* Specify Timezone Location
|
||||
|
||||
* **v0.3.2**
|
||||
Improvements:
|
||||
* Add AllCols & MustCols function
|
||||
|
|
@ -29,7 +40,7 @@
|
|||
|
||||
* **v0.2.3** : Improved documents; Optimistic Locking support; Timestamp with time zone support; Mapper change to tableMapper and columnMapper & added PrefixMapper & SuffixMapper support custom table or column name's prefix and suffix;Insert now return affected, err instead of id, err; Added UseBool & Distinct;
|
||||
* **v0.2.2** : Postgres drivers now support lib/pq; Added method Iterate for record by record to handler;Added SetMaxConns(go1.2+) support; some bugs fixed.
|
||||
* **v0.2.1** : Added database reverse tool, now support generate go & c++ codes, see [Xorm Tool README](https://github.com/lunny/xorm/blob/master/xorm/README.md); some bug fixed.
|
||||
* **v0.2.1** : Added database reverse tool, now support generate go & c++ codes, see [Xorm Tool README](https://github.com/go-xorm/xorm/blob/master/xorm/README.md); some bug fixed.
|
||||
* **v0.2.0** : Added Cache supported, select is speeder up 3~5x; Added SameMapper for same name between struct and table; Added Sync method for auto added tables, columns, indexes;
|
||||
* **v0.1.9** : Added postgres and mymysql supported; Added ` and ? supported on Raw SQL even if postgres; Added Cols, StoreEngine, Charset function, Added many column data type supported, please see [Mapping Rules](#mapping).
|
||||
* **v0.1.8** : Added union index and union unique supported, please see [Mapping Rules](#mapping).
|
||||
|
|
@ -40,4 +51,4 @@
|
|||
* **v0.1.3** : Find function now supports both slice and map; Add Table function for multi tables and temperory tables support
|
||||
* **v0.1.2** : Insert function now supports both struct and slice pointer parameters, batch inserting and auto transaction
|
||||
* **v0.1.1** : Add Id, In functions and improved README
|
||||
* **v0.1.0** : Inital release.
|
||||
* **v0.1.0** : Initial release.
|
||||
|
|
|
|||
|
|
@ -1,11 +1,22 @@
|
|||
## 更新日志
|
||||
|
||||
* **v0.4.0 RC1**
|
||||
新特性:
|
||||
* 移动xorm cmd [github.com/go-xorm/cmd](github.com/go-xorm/cmd)
|
||||
* 在重构一般DB操作核心库 [github.com/go-xorm/core](https://github.com/go-xorm/core)
|
||||
* 移动测试github.com/复XORM/测试 [github.com/go-xorm/tests](github.com/go-xorm/tests)
|
||||
|
||||
改进:
|
||||
* Prepared statement 缓存
|
||||
* 添加 Incr API
|
||||
* 指定时区位置
|
||||
|
||||
* **v0.3.2**
|
||||
Improvements:
|
||||
改进:
|
||||
* Add AllCols & MustCols function
|
||||
* Add TableName for custom table name
|
||||
|
||||
Bug Fixes:
|
||||
Bug 修复:
|
||||
* #46
|
||||
* #51
|
||||
* #53
|
||||
|
|
@ -28,10 +39,10 @@
|
|||
|
||||
* **v0.2.3** : 改善了文档;提供了乐观锁支持;添加了带时区时间字段支持;Mapper现在分成表名Mapper和字段名Mapper,同时实现了表或字段的自定义前缀后缀;Insert方法的返回值含义从id, err更改为 affected, err,请大家注意;添加了UseBool 和 Distinct函数。
|
||||
* **v0.2.2** : Postgres驱动新增了对lib/pq的支持;新增了逐条遍历方法Iterate;新增了SetMaxConns(go1.2+)支持,修复了bug若干;
|
||||
* **v0.2.1** : 新增数据库反转工具,当前支持go和c++代码的生成,详见 [Xorm Tool README](https://github.com/lunny/xorm/blob/master/xorm/README.md); 修复了一些bug.
|
||||
* **v0.2.0** : 新增 [缓存](https://github.com/lunny/xorm/blob/master/docs/QuickStart.md#120)支持,查询速度提升3-5倍; 新增数据库表和Struct同名的映射方式; 新增Sync同步表结构;
|
||||
* **v0.1.9** : 新增 postgres 和 mymysql 驱动支持; 在Postgres中支持原始SQL语句中使用 ` 和 ? 符号; 新增Cols, StoreEngine, Charset 函数;SQL语句打印支持io.Writer接口,默认打印到控制台;新增更多的字段类型支持,详见 [映射规则](https://github.com/lunny/xorm/blob/master/docs/QuickStart.md#21);删除废弃的MakeSession和Create函数。
|
||||
* **v0.1.8** : 新增联合index,联合unique支持,请查看 [映射规则](https://github.com/lunny/xorm/blob/master/docs/QuickStart.md#21)。
|
||||
* **v0.2.1** : 新增数据库反转工具,当前支持go和c++代码的生成,详见 [Xorm Tool README](https://github.com/go-xorm/xorm/blob/master/xorm/README.md); 修复了一些bug.
|
||||
* **v0.2.0** : 新增 [缓存](https://github.com/go-xorm/xorm/blob/master/docs/QuickStart.md#120)支持,查询速度提升3-5倍; 新增数据库表和Struct同名的映射方式; 新增Sync同步表结构;
|
||||
* **v0.1.9** : 新增 postgres 和 mymysql 驱动支持; 在Postgres中支持原始SQL语句中使用 ` 和 ? 符号; 新增Cols, StoreEngine, Charset 函数;SQL语句打印支持io.Writer接口,默认打印到控制台;新增更多的字段类型支持,详见 [映射规则](https://github.com/go-xorm/xorm/blob/master/docs/QuickStartCn.md#21);删除废弃的MakeSession和Create函数。
|
||||
* **v0.1.8** : 新增联合index,联合unique支持,请查看 [映射规则](https://github.com/go-xorm/xorm/blob/master/docs/QuickStartCn.md#21)。
|
||||
* **v0.1.7** : 新增IConnectPool接口以及NoneConnectPool, SysConnectPool, SimpleConnectPool三种实现,可以选择不使用连接池,使用系统连接池和使用自带连接池三种实现,默认为SysConnectPool,即系统自带的连接池。同时支持自定义连接池。Engine新增Close方法,在系统退出时应调用此方法。
|
||||
* **v0.1.6** : 新增Conversion,支持自定义类型到数据库类型的转换;新增查询结构体自动检测匿名成员支持;新增单向映射支持;
|
||||
* **v0.1.5** : 新增对多线程的支持;新增Sql()函数;支持任意sql语句的struct查询;Get函数返回值变动;MakeSession和Create函数被NewSession和NewEngine函数替代;
|
||||
|
|
@ -40,5 +51,3 @@
|
|||
* **v0.1.2** : Insert函数支持混合struct和slice指针传入,并根据数据库类型自动批量插入,同时自动添加事务
|
||||
* **v0.1.1** : 添加 Id, In 函数,改善 README 文档
|
||||
* **v0.1.0** : 初始化工程
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,51 +1,47 @@
|
|||
xorm 快速入门
|
||||
Quick Start
|
||||
=====
|
||||
|
||||
* [1.创建Orm引擎](#10)
|
||||
* [2.定义表结构体](#20)
|
||||
* [2.1.名称映射规则](#21)
|
||||
* [2.2.前缀映射,后缀映射和缓存映射](#22)
|
||||
* [2.3.使用Table和Tag改变名称映射](#23)
|
||||
* [2.4.Column属性定义](#24)
|
||||
* [2.5.Go与字段类型对应表](#25)
|
||||
* [3.表结构操作](#30)
|
||||
* [3.1 获取数据库信息](#31)
|
||||
* [3.2 表操作](#32)
|
||||
* [3.3 创建索引和唯一索引](#33)
|
||||
* [3.4 同步数据库结构](#34)
|
||||
* [4.插入数据](#50)
|
||||
* [5.查询和统计数据](#60)
|
||||
* [5.1.查询条件方法](#61)
|
||||
* [5.2.临时开关方法](#62)
|
||||
* [5.3.Get方法](#63)
|
||||
* [5.4.Find方法](#64)
|
||||
* [5.5.Iterate方法](#65)
|
||||
* [5.6.Count方法](#66)
|
||||
* [5.7.Rows方法](#67)
|
||||
* [6.更新数据](#70)
|
||||
* [6.1.乐观锁](#71)
|
||||
* [7.删除数据](#80)
|
||||
* [8.执行SQL查询](#90)
|
||||
* [9.执行SQL命令](#100)
|
||||
* [10.事务处理](#110)
|
||||
* [11.缓存](#120)
|
||||
* [12.事件](#125)
|
||||
* [13.xorm工具](#130)
|
||||
* [13.1.反转命令](#131)
|
||||
* [14.Examples](#140)
|
||||
* [15.案例](#150)
|
||||
* [16.那些年我们踩过的坑](#160)
|
||||
* [17.讨论](#170)
|
||||
* [1.Create ORM Engine](#10)
|
||||
* [2.Define a struct](#20)
|
||||
* [2.1.Name mapping rule](#21)
|
||||
* [2.2.Use Table or Tag to change table or column name](#22)
|
||||
* [2.3.Column define](#23)
|
||||
* [3. database schema operation](#30)
|
||||
* [3.1.Retrieve database schema infomation](#31)
|
||||
* [3.2.Table Operation](#32)
|
||||
* [3.3.Create indexes and uniques](#33)
|
||||
* [3.4.Sync database schema](#34)
|
||||
* [4.Insert records](#40)
|
||||
* [5.Query and Count records](#60)
|
||||
* [5.1.Query condition methods](#61)
|
||||
* [5.2.Temporory methods](#62)
|
||||
* [5.3.Get](#63)
|
||||
* [5.4.Find](#64)
|
||||
* [5.5.Iterate](#65)
|
||||
* [5.6.Count](#66)
|
||||
* [6.Update records](#70)
|
||||
* [6.1.Optimistic Locking](#71)
|
||||
* [7.Delete records](#80)
|
||||
* [8.Execute SQL command](#90)
|
||||
* [9.Execute SQL query](#100)
|
||||
* [10.Transaction](#110)
|
||||
* [11.Cache](#120)
|
||||
* [12.Xorm Tool](#130)
|
||||
* [12.1.Reverse command](#131)
|
||||
* [13.Examples](#140)
|
||||
* [14.Cases](#150)
|
||||
* [15.FAQ](#160)
|
||||
* [16.Discuss](#170)
|
||||
|
||||
<a name="10" id="10"></a>
|
||||
## 1.创建Orm引擎
|
||||
## 1.Create ORM Engine
|
||||
|
||||
在xorm里面,可以同时存在多个Orm引擎,一个Orm引擎称为Engine。因此在使用前必须调用NewEngine,如:
|
||||
When using xorm, you can create multiple orm engines, an engine means a databse. So you can:
|
||||
|
||||
```Go
|
||||
import (
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/lunny/xorm"
|
||||
"github.com/go-xorm/xorm"
|
||||
)
|
||||
engine, err := xorm.NewEngine("mysql", "root:123@/test?charset=utf8")
|
||||
defer engine.Close()
|
||||
|
|
@ -56,15 +52,15 @@ or
|
|||
```Go
|
||||
import (
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/lunny/xorm"
|
||||
"github.com/go-xorm/xorm"
|
||||
)
|
||||
engine, err = xorm.NewEngine("sqlite3", "./test.db")
|
||||
defer engine.Close()
|
||||
```
|
||||
|
||||
一般如果只针对一个数据库进行操作,只需要创建一个Engine即可。Engine支持在多GoRutine下使用。
|
||||
Generally, you can only create one engine. Engine supports run on go rutines.
|
||||
|
||||
xorm当前支持五种驱动四个数据库如下:
|
||||
xorm supports four drivers now:
|
||||
|
||||
* Mysql: [github.com/Go-SQL-Driver/MySQL](https://github.com/Go-SQL-Driver/MySQL)
|
||||
|
||||
|
|
@ -76,18 +72,18 @@ xorm当前支持五种驱动四个数据库如下:
|
|||
|
||||
* MsSql: [github.com/lunny/godbc](https://githubcom/lunny/godbc)
|
||||
|
||||
NewEngine传入的参数和`sql.Open`传入的参数完全相同,因此,使用哪个驱动前,请查看此驱动中关于传入参数的说明文档。
|
||||
NewEngine's parameters are the same as `sql.Open`. So you should read the drivers' document for parameters' usage.
|
||||
|
||||
在engine创建完成后可以进行一些设置,如:
|
||||
After engine created, you can do some settings.
|
||||
|
||||
1.错误显示设置,默认如下均为`false`
|
||||
1.Logs
|
||||
|
||||
* `engine.ShowSQL = true`,则会在控制台打印出生成的SQL语句;
|
||||
* `engine.ShowDebug = true`,则会在控制台打印调试信息;
|
||||
* `engine.ShowError = true`,则会在控制台打印错误信息;
|
||||
* `engine.ShowWarn = true`,则会在控制台打印警告信息;
|
||||
* `engine.ShowSQL = true`, Show SQL statement on standard output;
|
||||
* `engine.ShowDebug = true`, Show debug infomation on standard output;
|
||||
* `engine.ShowError = true`, Show error infomation on standard output;
|
||||
* `engine.ShowWarn = true`, Show warnning information on standard output;
|
||||
|
||||
2.如果希望用其它方式记录,则可以`engine.Logger`赋值为一个`io.Writer`的实现。比如记录到Log文件,则可以:
|
||||
2.If want to record infomation with another method: use `engine.Logger` as `io.Writer`:
|
||||
|
||||
```Go
|
||||
f, err := os.Create("sql.log")
|
||||
|
|
@ -98,31 +94,31 @@ f, err := os.Create("sql.log")
|
|||
engine.Logger = f
|
||||
```
|
||||
|
||||
3.engine内部支持连接池接口,默认使用的Go所实现的连接池,同时自带了另外两种实现:一种是不使用连接池,另一种为一个自实现的连接池。推荐使用Go所实现的连接池。如果要使用自己实现的连接池,可以实现`xorm.IConnectPool`并通过`engine.SetPool`进行设置。推荐使用Go默认的连接池。
|
||||
3.Engine provide DB connection pool settings.
|
||||
|
||||
* 如果需要设置连接池的空闲数大小,可以使用`engine.SetIdleConns()`来实现。
|
||||
* 如果需要设置最大打开连接数,则可以使用`engine.SetMaxConns()`来实现。
|
||||
* Use `engine.SetMaxIdleConns()` to set idle connections.
|
||||
* Use `engine.SetMaxOpenConns()` to set Max connections. This methods support only Go 1.2+.
|
||||
|
||||
<a name="20" id="20"></a>
|
||||
## 2.定义表结构体
|
||||
## 2.Define struct
|
||||
|
||||
xorm支持将一个struct映射为数据库中对应的一张表。映射规则如下:
|
||||
xorm maps a struct to a database table, the rule is below.
|
||||
|
||||
<a name="21" id="21"></a>
|
||||
### 2.1.名称映射规则
|
||||
### 2.1.name mapping rule
|
||||
|
||||
名称映射规则主要负责结构体名称到表名和结构体field到表字段的名称映射。由xorm.IMapper接口的实现者来管理,xorm内置了两种IMapper实现:`SnakeMapper` 和 `SameMapper`。SnakeMapper支持struct为驼峰式命名,表结构为下划线命名之间的转换;SameMapper支持结构体名称和对应的表名称以及结构体field名称与对应的表字段名称相同的命名。
|
||||
use xorm.IMapper interface to implement. There are two IMapper implemented: `SnakeMapper` and `SameMapper`. SnakeMapper means struct name is word by word and table name or column name as 下划线. SameMapper means same name between struct and table.
|
||||
|
||||
当前SnakeMapper为默认值,如果需要改变时,在engine创建完成后使用
|
||||
SnakeMapper is the default.
|
||||
|
||||
```Go
|
||||
engine.SetMapper(SameMapper{})
|
||||
```
|
||||
|
||||
同时需要注意的是:
|
||||
And you should notice:
|
||||
|
||||
* 如果你使用了别的命名规则映射方案,也可以自己实现一个IMapper。
|
||||
* 表名称和字段名称的映射规则默认是相同的,当然也可以设置为不同,如:
|
||||
* If you want to use other mapping rule, implement IMapper
|
||||
* Tables's mapping rule could be different from Columns':
|
||||
|
||||
```Go
|
||||
engine.SetTableMapper(SameMapper{})
|
||||
|
|
@ -130,23 +126,24 @@ engine.SetColumnMapper(SnakeMapper{})
|
|||
```
|
||||
|
||||
<a name="22" id="22"></a>
|
||||
### 2.2.前缀映射,后缀映射和缓存映射
|
||||
### 2.2.Prefix mapping, Suffix Mapping and Cache Mapping
|
||||
|
||||
* 通过`engine.NewPrefixMapper(SnakeMapper{}, "prefix")`可以在SnakeMapper的基础上在命名中添加统一的前缀,当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。
|
||||
* 通过`engine.NewSufffixMapper(SnakeMapper{}, "suffix")`可以在SnakeMapper的基础上在命名中添加统一的后缀,当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。
|
||||
* 通过`eneing.NewCacheMapper(SnakeMapper{})`可以组合其它的映射规则,起到在内存中缓存曾经映射过的命名映射。
|
||||
* 通过`eneing.NewCacheMapper(SnakeMapper{})`可以起到在内存中缓存曾经映射过的命名映射。
|
||||
|
||||
<a name="23" id="23"></a>
|
||||
### 2.3.使用Table和Tag改变名称映射
|
||||
当然,如果你使用了别的命名规则映射方案,也可以自己实现一个IMapper。
|
||||
|
||||
<a name="22" id="22"></a>
|
||||
### 2.3.Tag mapping
|
||||
|
||||
如果所有的命名都是按照IMapper的映射来操作的,那当然是最理想的。但是如果碰到某个表名或者某个字段名跟映射规则不匹配时,我们就需要别的机制来改变。
|
||||
|
||||
* 如果struct拥有`Tablename() string`的成员方法,那么此方法的返回值即是该struct默认对应的数据库表名。
|
||||
|
||||
* 通过`engine.Table()`方法可以改变struct对应的数据库表的名称,通过sturct中field对应的Tag中使用`xorm:"'column_name'"`可以使该field对应的Column名称为指定名称。这里使用两个单引号将Column名称括起来是为了防止名称冲突,因为我们在Tag中还可以对这个Column进行更多的定义。如果名称不冲突的情况,单引号也可以不使用。
|
||||
通过`engine.Table()`方法可以改变struct对应的数据库表的名称,通过sturct中field对应的Tag中使用`xorm:"'table_name'"`可以使该field对应的Column名称为指定名称。这里使用两个单引号将Column名称括起来是为了防止名称冲突,因为我们在Tag中还可以对这个Column进行更多的定义。如果名称不冲突的情况,单引号也可以不使用。
|
||||
|
||||
<a name="23" id="23"></a>
|
||||
### 2.4.Column属性定义
|
||||
### 2.4.Column defenition
|
||||
|
||||
我们在field对应的Tag中对Column的一些属性进行定义,定义的方法基本和我们写SQL定义表结构类似,比如:
|
||||
|
||||
```
|
||||
|
|
@ -156,37 +153,37 @@ type User struct {
|
|||
}
|
||||
```
|
||||
|
||||
对于不同的数据库系统,数据类型其实是有些差异的。因此xorm中对数据类型有自己的定义,基本的原则是尽量兼容各种数据库的字段类型,具体的字段对应关系可以查看[字段类型对应表](https://github.com/lunny/xorm/blob/master/docs/COLUMNTYPE.md)。对于使用者,一般只要使用自己熟悉的数据库字段定义即可。
|
||||
For different DBMS, data types对于不同的数据库系统,数据类型其实是有些差异的。因此xorm中对数据类型有自己的定义,基本的原则是尽量兼容各种数据库的字段类型,具体的字段对应关系可以查看[字段类型对应表](https://github.com/go-xorm/xorm/blob/master/docs/COLUMNTYPE.md)。
|
||||
|
||||
具体的映射规则如下,另Tag中的关键字均不区分大小写,字段名区分大小写:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>name</td><td>当前field对应的字段的名称,可选,如不写,则自动根据field名字和转换规则命名,如与其它关键字冲突,请使用单引号括起来。</td>
|
||||
<td>name or 'name'</td><td>Column Name, optional</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>pk</td><td>是否是Primary Key,如果在一个struct中有多个字段都使用了此标记,则这多个字段构成了复合主键,单主键当前支持int32,int,int64,uint32,uint,uint64,string这7种Go的数据类型,复合主键支持这7种Go的数据类型的组合。</td>
|
||||
<td>pk</td><td>If column is Primary Key</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>当前支持30多种字段类型,详情参见 [字段类型](https://github.com/lunny/xorm/blob/master/docs/COLUMNTYPE.md)</td><td>字段类型</td>
|
||||
<td>当前支持30多种字段类型,详情参见 [字段类型](https://github.com/go-xorm/xorm/blob/master/docs/COLUMNTYPE.md)</td><td>字段类型</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>autoincr</td><td>是否是自增</td>
|
||||
<td>autoincr</td><td>If autoincrement column</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>[not ]null 或 notnull</td><td>是否可以为空</td>
|
||||
<td>[not ]null | notnull</td><td>if column could be blank</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>unique或unique(uniquename)</td><td>是否是唯一,如不加括号则该字段不允许重复;如加上括号,则括号中为联合唯一索引的名字,此时如果有另外一个或多个字段和本unique的uniquename相同,则这些uniquename相同的字段组成联合唯一索引</td>
|
||||
<td>unique/unique(uniquename)</td><td>是否是唯一,如不加括号则该字段不允许重复;如加上括号,则括号中为联合唯一索引的名字,此时如果有另外一个或多个字段和本unique的uniquename相同,则这些uniquename相同的字段组成联合唯一索引</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>index或index(indexname)</td><td>是否是索引,如不加括号则该字段自身为索引,如加上括号,则括号中为联合索引的名字,此时如果有另外一个或多个字段和本index的indexname相同,则这些indexname相同的字段组成联合索引</td>
|
||||
<td>index/index(indexname)</td><td>是否是索引,如不加括号则该字段自身为索引,如加上括号,则括号中为联合索引的名字,此时如果有另外一个或多个字段和本index的indexname相同,则这些indexname相同的字段组成联合索引</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>extends</td><td>应用于一个匿名结构体之上,表示此匿名结构体的成员也映射到数据库中</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td><td>这个Field将不进行字段映射</td>
|
||||
<td>-</td><td>This field will not be mapping</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-></td><td>这个Field将只写入到数据库而不从数据库读取</td>
|
||||
|
|
@ -195,26 +192,26 @@ type User struct {
|
|||
<td><-</td><td>这个Field将只从数据库读取,而不写入到数据库</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>created</td><td>这个Field将在Insert时自动赋值为当前时间</td>
|
||||
<td>created</td><td>This field will be filled in current time on insert</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>updated</td><td>这个Field将在Insert或Update时自动赋值为当前时间</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>version</td><td>这个Field将会在insert时默认为1,每次更新自动加1</td>
|
||||
<td>updated</td><td>This field will be filled in current time on insert or update</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>default 0</td><td>设置默认值,紧跟的内容如果是Varchar等需要加上单引号</td>
|
||||
<td>version</td><td>This field will be filled 1 on insert and autoincrement on update</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>default 0 | default 'name'</td><td>column default value</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
另外有如下几条自动映射的规则:
|
||||
|
||||
- 1.如果field名称为`Id`而且类型为`int64`并且没有定义tag,则会被xorm视为主键,并且拥有自增属性。如果想用`Id`以外的名字或非int64类型做为主键名,必须在对应的Tag上加上`xorm:"pk"`来定义主键,加上`xorm:"autoincr"`作为自增。这里需要注意的是,有些数据库并不允许非主键的自增属性。
|
||||
- 1.如果field名称为`Id`而且类型为`int64`的话,会被xorm视为主键,并且拥有自增属性。如果想用`Id`以外的名字做为主键名,可以在对应的Tag上加上`xorm:"pk"`来定义主键。
|
||||
|
||||
- 2.string类型默认映射为varchar(255),如果需要不同的定义,可以在tag中自定义
|
||||
|
||||
- 3.支持`type MyString string`等自定义的field,支持Slice, Map等field成员,这些成员默认存储为Text类型,并且默认将使用Json格式来序列化和反序列化。也支持数据库字段类型为Blob类型,如果是Blob类型,则先使用Json格式序列化再转成[]byte格式。当然[]byte或者[]uint8默认为Blob类型并且都以二进制方式存储。具体参见 [go类型<->数据库类型对应表](https://github.com/lunny/xorm/blob/master/docs/AutoMap.md)
|
||||
- 3.支持`type MyString string`等自定义的field,支持Slice, Map等field成员,这些成员默认存储为Text类型,并且默认将使用Json格式来序列化和反序列化。也支持数据库字段类型为Blob类型,如果是Blob类型,则先使用Json格式序列化再转成[]byte格式。当然[]byte或者[]uint8默认为Blob类型并且都以二进制方式存储。
|
||||
|
||||
- 4.实现了Conversion接口的类型或者结构体,将根据接口的转换方式在类型和数据库记录之间进行相互转换。
|
||||
```Go
|
||||
|
|
@ -224,66 +221,50 @@ type Conversion interface {
|
|||
}
|
||||
```
|
||||
|
||||
<a name="24" id="24"></a>
|
||||
### 2.4.Go与字段类型对应表
|
||||
|
||||
如果不使用tag来定义field对应的数据库字段类型,那么系统会自动给出一个默认的字段类型,对应表如下:
|
||||
|
||||
[go类型<->数据库类型对应表](https://github.com/lunny/xorm/blob/master/docs/AutoMap.md)
|
||||
|
||||
<a name="30" id="30"></a>
|
||||
## 3.表结构操作
|
||||
|
||||
xorm提供了一些动态获取和修改表结构的方法。对于一般的应用,很少动态修改表结构,则只需调用Sync()同步下表结构即可。
|
||||
|
||||
<a name="31" id="31"></a>
|
||||
## 3.1 获取数据库信息
|
||||
## 3.1 retrieve database meta info
|
||||
|
||||
* DBMetas()
|
||||
|
||||
xorm支持获取表结构信息,通过调用`engine.DBMetas()`可以获取到所有的表,字段,索引的信息。
|
||||
xorm支持获取表结构信息,通过调用`engine.DBMetas()`可以获取到所有的表的信息
|
||||
|
||||
<a name="31" id="31"></a>
|
||||
## 3.2.表操作
|
||||
## 3.2.directly table operation
|
||||
|
||||
* CreateTables()
|
||||
|
||||
创建表使用`engine.CreateTables()`,参数为一个或多个空的对应Struct的指针。同时可用的方法有Charset()和StoreEngine(),如果对应的数据库支持,这两个方法可以在创建表时指定表的字符编码和使用的引擎。当前仅支持Mysql数据库。
|
||||
|
||||
* IsTableEmpty()
|
||||
|
||||
判断表是否为空,参数和CreateTables相同
|
||||
|
||||
* IsTableExist()
|
||||
|
||||
判断表是否存在
|
||||
|
||||
* DropTables()
|
||||
|
||||
删除表使用`engine.DropTables()`,参数为一个或多个空的对应Struct的指针或者表的名字。如果为string传入,则只删除对应的表,如果传入的为Struct,则删除表的同时还会删除对应的索引。
|
||||
|
||||
<a name="32" id="32"></a>
|
||||
## 3.3.创建索引和唯一索引
|
||||
## 3.3.create indexes and uniques
|
||||
|
||||
* CreateIndexes
|
||||
|
||||
根据struct中的tag来创建索引
|
||||
|
||||
* CreateUniques
|
||||
|
||||
根据struct中的tag来创建唯一索引
|
||||
|
||||
<a name="34" id="34"></a>
|
||||
## 3.4.同步数据库结构
|
||||
|
||||
同步能够部分智能的根据结构体的变动检测表结构的变动,并自动同步。目前能够实现:
|
||||
|
||||
* 1) 自动检测和创建表,这个检测是根据表的名字
|
||||
* 2)自动检测和新增表中的字段,这个检测是根据字段名
|
||||
* 3)自动检测和创建索引和唯一索引,这个检测是根据一个或多个字段名,而不根据索引名称
|
||||
1) 自动检测和创建表,这个检测是根据表的名字
|
||||
2)自动检测和新增表中的字段,这个检测是根据字段名
|
||||
3)自动检测和创建索引和唯一索引,这个检测是根据一个或多个字段名,而不根据索引名称
|
||||
|
||||
调用方法如下:
|
||||
|
||||
```Go
|
||||
err := engine.Sync(new(User))
|
||||
```
|
||||
|
|
@ -291,25 +272,21 @@ err := engine.Sync(new(User))
|
|||
<a name="50" id="50"></a>
|
||||
## 4.插入数据
|
||||
|
||||
插入数据使用Insert方法,Insert方法的参数可以是一个或多个Struct的指针,一个或多个Struct的Slice的指针。
|
||||
如果传入的是Slice并且当数据库支持批量插入时,Insert会使用批量插入的方式进行插入。
|
||||
|
||||
* 插入一条数据
|
||||
Inserting records use Insert method.
|
||||
|
||||
* Insert one record
|
||||
```Go
|
||||
user := new(User)
|
||||
user.Name = "myname"
|
||||
affected, err := engine.Insert(user)
|
||||
```
|
||||
|
||||
在插入单条数据成功后,如果该结构体有自增字段,则自增字段会被自动赋值为数据库中的id
|
||||
|
||||
After inseted, `user.Id` will be filled with primary key column value.
|
||||
```Go
|
||||
fmt.Println(user.Id)
|
||||
```
|
||||
|
||||
* 插入同一个表的多条数据
|
||||
|
||||
* Insert multiple records by Slice on one table
|
||||
```Go
|
||||
users := make([]User, 0)
|
||||
users[0].Name = "name0"
|
||||
|
|
@ -317,8 +294,7 @@ users[0].Name = "name0"
|
|||
affected, err := engine.Insert(&users)
|
||||
```
|
||||
|
||||
* 使用指针Slice插入多条记录
|
||||
|
||||
* Insert multiple records by Slice of pointer on one table
|
||||
```Go
|
||||
users := make([]*User, 0)
|
||||
users[0] = new(User)
|
||||
|
|
@ -327,8 +303,7 @@ users[0].Name = "name0"
|
|||
affected, err := engine.Insert(&users)
|
||||
```
|
||||
|
||||
* 插入不同表的一条记录
|
||||
|
||||
* Insert one record on two table.
|
||||
```Go
|
||||
user := new(User)
|
||||
user.Name = "myname"
|
||||
|
|
@ -337,8 +312,7 @@ question.Content = "whywhywhwy?"
|
|||
affected, err := engine.Insert(user, question)
|
||||
```
|
||||
|
||||
* 插入不同表的多条记录
|
||||
|
||||
* Insert multiple records on multiple tables.
|
||||
```Go
|
||||
users := make([]User, 0)
|
||||
users[0].Name = "name0"
|
||||
|
|
@ -348,7 +322,7 @@ questions[0].Content = "whywhywhwy?"
|
|||
affected, err := engine.Insert(&users, &questions)
|
||||
```
|
||||
|
||||
* 插入不同表的一条或多条记录
|
||||
* Insert one or multple records on multiple tables.
|
||||
```Go
|
||||
user := new(User)
|
||||
user.Name = "myname"
|
||||
|
|
@ -358,27 +332,23 @@ questions[0].Content = "whywhywhwy?"
|
|||
affected, err := engine.Insert(user, &questions)
|
||||
```
|
||||
|
||||
这里需要注意以下几点:
|
||||
* 这里虽然支持同时插入,但这些插入并没有事务关系。因此有可能在中间插入出错后,后面的插入将不会继续。
|
||||
* 多条插入会自动生成`Insert into table values (),(),()`的语句,因此这样的语句有一个最大的记录数,根据经验测算在150条左右。大于150条后,生成的sql语句将太长可能导致执行失败。因此在插入大量数据时,目前需要自行分割成每150条插入一次。
|
||||
Notice: If you want to use transaction on inserting, you should use session.Begin() before calling Insert.
|
||||
|
||||
<a name="60" id="60"></a>
|
||||
## 5.查询和统计数据
|
||||
## 5.Query and count
|
||||
|
||||
所有的查询条件不区分调用顺序,但必须在调用Get,Find,Count, Iterate, Rows这几个函数之前调用。同时需要注意的一点是,在调用的参数中,如果采用默认的`SnakeMapper`所有的字符字段名均为映射后的数据库的字段名,而不是field的名字。
|
||||
所有的查询条件不区分调用顺序,但必须在调用Get,Find,Count这三个函数之前调用。同时需要注意的一点是,在调用的参数中,所有的字符字段名均为映射后的数据库的字段名,而不是field的名字。
|
||||
|
||||
<a name="61" id="61"></a>
|
||||
### 5.1.查询条件方法
|
||||
|
||||
查询和统计主要使用`Get`, `Find`, `Count`, `Rows`, `Iterate`这几个方法。在进行查询时可以使用多个方法来形成查询条件,条件函数如下:
|
||||
查询和统计主要使用`Get`, `Find`, `Count`三个方法。在进行查询时可以使用多个方法来形成查询条件,条件函数如下:
|
||||
|
||||
* Id(interface{})
|
||||
传入一个PK字段的值,作为查询条件,如果是复合主键,则
|
||||
`Id(xorm.PK{1, 2})`
|
||||
传入的两个参数按照struct中pk标记字段出现的顺序赋值。
|
||||
* Id(int64)
|
||||
传入一个PK字段的值,作为查询条件
|
||||
|
||||
* Where(string, …interface{})
|
||||
和SQL中Where语句中的条件基本相同,作为条件
|
||||
和Where语句中的条件基本相同,作为条件
|
||||
|
||||
* And(string, …interface{})
|
||||
和Where函数中的条件基本相同,作为条件
|
||||
|
|
@ -399,7 +369,7 @@ affected, err := engine.Insert(user, &questions)
|
|||
按照指定的顺序进行排序
|
||||
|
||||
* In(string, …interface{})
|
||||
某字段在一些值中,这里需要注意必须是[]interface{}才可以展开,由于Go语言的限制,[]int64等均不可以展开。
|
||||
某字段在一些值中
|
||||
|
||||
* Cols(…string)
|
||||
只查询或更新某些指定的字段,默认是查询所有映射的字段或者根据Update的第一个参数来判断更新的字段。例如:
|
||||
|
|
@ -410,11 +380,7 @@ engine.Cols("age", "name").Update(&user)
|
|||
// UPDATE user SET age=? AND name=?
|
||||
```
|
||||
|
||||
* AllCols()
|
||||
查询或更新所有字段。
|
||||
|
||||
* MustCols(…string)
|
||||
某些字段必须更新。
|
||||
其中的参数"age", "name"也可以写成"age, name",两种写法均可
|
||||
|
||||
* Omit(...string)
|
||||
和cols相反,此函数指定排除某些指定的字段。注意:此方法和Cols方法不可同时使用
|
||||
|
|
@ -461,84 +427,65 @@ Having的参数字符串
|
|||
* UseBool(...string)
|
||||
当从一个struct来生成查询条件或更新字段时,xorm会判断struct的field是否为0,"",nil,如果为以上则不当做查询条件或者更新内容。因为bool类型只有true和false两种值,因此默认所有bool类型不会作为查询条件或者更新字段。如果可以使用此方法,如果默认不传参数,则所有的bool字段都将会被使用,如果参数不为空,则参数中指定的为字段名,则这些字段对应的bool值将被使用。
|
||||
|
||||
* NoCascade()
|
||||
* Cascade(bool)
|
||||
是否自动关联查询field中的数据,如果struct的field也是一个struct并且映射为某个Id,则可以在查询时自动调用Get方法查询出对应的数据。
|
||||
|
||||
<a name="63" id="63"></a>
|
||||
### 5.3.Get方法
|
||||
|
||||
查询单条数据使用`Get`方法,在调用Get方法时需要传入一个对应结构体的指针,同时结构体中的非空field自动成为查询的条件和前面的方法条件组合在一起查询。
|
||||
|
||||
如:
|
||||
|
||||
1) 根据Id来获得单条数据:
|
||||
<a name="50" id="50"></a>
|
||||
### 5.3.Get one record
|
||||
Fetch a single object by user
|
||||
|
||||
```Go
|
||||
user := new(User)
|
||||
has, err := engine.Id(id).Get(user)
|
||||
// 复合主键的获取方法
|
||||
// has, errr := engine.Id(xorm.PK{1,2}).Get(user)
|
||||
var user = User{Id:27}
|
||||
has, err := engine.Get(&user)
|
||||
// or has, err := engine.Id(27).Get(&user)
|
||||
|
||||
var user = User{Name:"xlw"}
|
||||
has, err := engine.Get(&user)
|
||||
```
|
||||
|
||||
2) 根据Where来获得单条数据:
|
||||
<a name="60" id="60"></a>
|
||||
### 5.4.Find
|
||||
Fetch multipe objects into a slice or a map, use Find:
|
||||
|
||||
```Go
|
||||
user := new(User)
|
||||
has, err := engine.Where("name=?", "xlw").Get(user)
|
||||
```
|
||||
|
||||
3) 根据user结构体中已有的非空数据来获得单条数据:
|
||||
|
||||
```Go
|
||||
user := &User{Id:1}
|
||||
has, err := engine.Get(user)
|
||||
```
|
||||
|
||||
或者其它条件
|
||||
|
||||
```Go
|
||||
user := &User{Name:"xlw"}
|
||||
has, err := engine.Get(user)
|
||||
```
|
||||
|
||||
返回的结果为两个参数,一个`has`为该条记录是否存在,第二个参数`err`为是否有错误。不管err是否为nil,has都有可能为true或者false。
|
||||
|
||||
<a name="64" id="64"></a>
|
||||
### 5.4.Find方法
|
||||
|
||||
查询多条数据使用`Find`方法,Find方法的第一个参数为`slice`的指针或`Map`指针,即为查询后返回的结果,第二个参数可选,为查询的条件struct的指针。
|
||||
|
||||
1) 传入Slice用于返回数据
|
||||
|
||||
```Go
|
||||
everyone := make([]Userinfo, 0)
|
||||
var everyone []Userinfo
|
||||
err := engine.Find(&everyone)
|
||||
|
||||
pEveryOne := make([]*Userinfo, 0)
|
||||
err := engine.Find(&pEveryOne)
|
||||
```
|
||||
|
||||
2) 传入Map用户返回数据,map必须为`map[int64]Userinfo`的形式,map的key为id,因此对于复合主键无法使用这种方式。
|
||||
|
||||
```Go
|
||||
users := make(map[int64]Userinfo)
|
||||
err := engine.Find(&users)
|
||||
|
||||
pUsers := make(map[int64]*Userinfo)
|
||||
err := engine.Find(&pUsers)
|
||||
```
|
||||
|
||||
3) 也可以加入各种条件
|
||||
* also you can use Where, Limit
|
||||
|
||||
```Go
|
||||
users := make([]Userinfo, 0)
|
||||
err := engine.Where("age > ? or name = ?", 30, "xlw").Limit(20, 10).Find(&users)
|
||||
var allusers []Userinfo
|
||||
err := engine.Where("id > ?", "3").Limit(10,20).Find(&allusers) //Get id>3 limit 10 offset 20
|
||||
```
|
||||
|
||||
<a name="65" id="65"></a>
|
||||
### 5.5.Iterate方法
|
||||
* or you can use a struct query
|
||||
|
||||
Iterate方法提供逐条执行查询到的记录的方法,他所能使用的条件和Find方法完全相同
|
||||
```Go
|
||||
var tenusers []Userinfo
|
||||
err := engine.Limit(10).Find(&tenusers, &Userinfo{Name:"xlw"}) //Get All Name="xlw" limit 10 offset 0
|
||||
```
|
||||
|
||||
* or In function
|
||||
|
||||
```Go
|
||||
var tenusers []Userinfo
|
||||
err := engine.In("id", 1, 3, 5).Find(&tenusers) //Get All id in (1, 3, 5)
|
||||
```
|
||||
|
||||
* The default will query all columns of a table. Use Cols function if you want to select some columns
|
||||
|
||||
```Go
|
||||
var tenusers []Userinfo
|
||||
err := engine.Cols("id", "name").Find(&tenusers) //Find only id and name
|
||||
```
|
||||
|
||||
<a name="70" id="70"></a>
|
||||
### 5.5.Iterate records
|
||||
Iterate, like find, but handle records one by one
|
||||
|
||||
```Go
|
||||
err := engine.Where("age > ? or name=?)", 30, "xlw").Iterate(new(Userinfo), func(i int, bean interface{})error{
|
||||
|
|
@ -556,22 +503,6 @@ user := new(User)
|
|||
total, err := engine.Where("id >?", 1).Count(user)
|
||||
```
|
||||
|
||||
<a name="67" id="67"></a>
|
||||
### 5.7.Rows方法
|
||||
|
||||
Rows方法和Iterate方法类似,提供逐条执行查询到的记录的方法,不过Rows更加灵活好用。
|
||||
```Go
|
||||
user := new(User)
|
||||
rows, err := engine.Where("id >?", 1).Rows(user)
|
||||
if err != nil {
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
err = rows.Scan(user)
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
<a name="70" id="70"></a>
|
||||
## 6.更新数据
|
||||
|
||||
|
|
@ -585,19 +516,17 @@ affected, err := engine.Id(id).Update(user)
|
|||
|
||||
这里需要注意,Update会自动从user结构体中提取非0和非nil得值作为需要更新的内容,因此,如果需要更新一个值为0,则此种方法将无法实现,因此有两种选择:
|
||||
|
||||
* 1.通过添加Cols函数指定需要更新结构体中的哪些值,未指定的将不更新,指定了的即使为0也会更新。
|
||||
|
||||
1. 通过添加Cols函数指定需要更新结构体中的哪些值,未指定的将不更新,指定了的即使为0也会更新。
|
||||
```Go
|
||||
affected, err := engine.Id(id).Cols("age").Update(&user)
|
||||
```
|
||||
|
||||
* 2.通过传入map[string]interface{}来进行更新,但这时需要额外指定更新到哪个表,因为通过map是无法自动检测更新哪个表的。
|
||||
|
||||
2. 通过传入map[string]interface{}来进行更新,但这时需要额外指定更新到哪个表,因为通过map是无法自动检测更新哪个表的。
|
||||
```Go
|
||||
affected, err := engine.Table(new(User)).Id(id).Update(map[string]interface{}{"age":0})
|
||||
```
|
||||
|
||||
<a name="71" id="71"></a>
|
||||
|
||||
### 6.1.乐观锁
|
||||
|
||||
要使用乐观锁,需要使用version标记
|
||||
|
|
@ -617,51 +546,53 @@ engine.Id(1).Update(&user)
|
|||
// UPDATE user SET ..., version = version + 1 WHERE id = ? AND version = ?
|
||||
```
|
||||
|
||||
<a name="80" id="80"></a>
|
||||
## 7.删除数据
|
||||
|
||||
删除数据`Delete`方法,参数为struct的指针并且成为查询条件。
|
||||
<a name="80" id="80"></a>
|
||||
## 7.Delete one or more records
|
||||
Delete one or more records
|
||||
|
||||
* delete by id
|
||||
|
||||
```Go
|
||||
user := new(User)
|
||||
affected, err := engine.Id(id).Delete(user)
|
||||
err := engine.Id(1).Delete(&User{})
|
||||
```
|
||||
|
||||
`Delete`的返回值第一个参数为删除的记录数,第二个参数为错误。
|
||||
* delete by other conditions
|
||||
|
||||
注意:当删除时,如果user中包含有bool,float64或者float32类型,有可能会使删除失败。具体请查看 <a href="#160">FAQ</a>
|
||||
```Go
|
||||
err := engine.Delete(&User{Name:"xlw"})
|
||||
```
|
||||
|
||||
<a name="90" id="90"></a>
|
||||
## 8.执行SQL查询
|
||||
## 8.Execute SQL query
|
||||
|
||||
也可以直接执行一个SQL查询,即Select命令。在Postgres中支持原始SQL语句中使用 ` 和 ? 符号。
|
||||
Of course, SQL execution is also provided.
|
||||
|
||||
If select then use Query
|
||||
|
||||
```Go
|
||||
sql := "select * from userinfo"
|
||||
results, err := engine.Query(sql)
|
||||
```
|
||||
|
||||
当调用`Query`时,第一个返回值`results`为`[]map[string][]byte`的形式。
|
||||
|
||||
<a name="100" id="100"></a>
|
||||
## 9.执行SQL命令
|
||||
|
||||
也可以直接执行一个SQL命令,即执行Insert, Update, Delete 等操作。此时不管数据库是何种类型,都可以使用 ` 和 ? 符号。
|
||||
## 9.Execute SQL command
|
||||
If insert, update or delete then use Exec
|
||||
|
||||
```Go
|
||||
sql = "update `userinfo` set username=? where id=?"
|
||||
sql = "update userinfo set username=? where id=?"
|
||||
res, err := engine.Exec(sql, "xiaolun", 1)
|
||||
```
|
||||
|
||||
<a name="110" id="110"></a>
|
||||
## 10.事务处理
|
||||
当使用事务处理时,需要创建Session对象。在进行事物处理时,可以混用ORM方法和RAW方法,如下代码所示:
|
||||
## 10.Transaction
|
||||
|
||||
```Go
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
// add Begin() before any action
|
||||
err := session.Begin()
|
||||
err := session.Begin()
|
||||
user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()}
|
||||
_, err = session.Insert(&user1)
|
||||
if err != nil {
|
||||
|
|
@ -688,147 +619,78 @@ if err != nil {
|
|||
}
|
||||
```
|
||||
|
||||
* 注意如果您使用的是mysql,数据库引擎为innodb事务才有效,myisam引擎是不支持事务的。
|
||||
|
||||
<a name="120" id="120"></a>
|
||||
## 11.缓存
|
||||
|
||||
xorm内置了一致性缓存支持,不过默认并没有开启。要开启缓存,需要在engine创建完后进行配置,如:
|
||||
启用一个全局的内存缓存
|
||||
1. Global Cache
|
||||
Xorm implements cache support. Defaultly, it's disabled. If enable it, use below code.
|
||||
|
||||
```Go
|
||||
cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000)
|
||||
engine.SetDefaultCacher(cacher)
|
||||
```
|
||||
|
||||
上述代码采用了LRU算法的一个缓存,缓存方式是存放到内存中,缓存struct的记录数为1000条,缓存针对的范围是所有具有主键的表,没有主键的表中的数据将不会被缓存。
|
||||
如果只想针对部分表,则:
|
||||
If disable some tables' cache, then:
|
||||
|
||||
```Go
|
||||
engine.MapCacher(&user, nil)
|
||||
```
|
||||
|
||||
2. Table's Cache
|
||||
If only some tables need cache, then:
|
||||
|
||||
```Go
|
||||
cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000)
|
||||
engine.MapCacher(&user, cacher)
|
||||
```
|
||||
|
||||
如果要禁用某个表的缓存,则:
|
||||
Caution:
|
||||
|
||||
```Go
|
||||
engine.MapCacher(&user, nil)
|
||||
```
|
||||
1. When use Cols methods on cache enabled, the system still return all the columns.
|
||||
|
||||
设置完之后,其它代码基本上就不需要改动了,缓存系统已经在后台运行。
|
||||
|
||||
当前实现了内存存储的CacheStore接口MemoryStore,如果需要采用其它设备存储,可以实现CacheStore接口。
|
||||
|
||||
不过需要特别注意不适用缓存或者需要手动编码的地方:
|
||||
|
||||
1. 当使用了`Distinct`,`Having`,`GroupBy`方法将不会使用缓存
|
||||
|
||||
2. 在`Get`或者`Find`时使用了`Cols`,`Omit`方法,则在开启缓存后此方法无效,系统仍旧会取出这个表中的所有字段。
|
||||
|
||||
3. 在使用Exec方法执行了方法之后,可能会导致缓存与数据库不一致的地方。因此如果启用缓存,尽量避免使用Exec。如果必须使用,则需要在使用了Exec之后调用ClearCache手动做缓存清除的工作。比如:
|
||||
2. When using Exec method, you should clear cache:
|
||||
|
||||
```Go
|
||||
engine.Exec("update user set name = ? where id = ?", "xlw", 1)
|
||||
engine.ClearCache(new(User))
|
||||
```
|
||||
|
||||
缓存的实现原理如下图所示:
|
||||
Cache implement theory below:
|
||||
|
||||

|
||||
|
||||
<a name="125" id="125"></a>
|
||||
## 12.事件
|
||||
xorm支持两种方式的事件,一种是在Struct中的特定方法来作为事件的方法,一种是在执行语句的过程中执行事件。
|
||||
|
||||
在Struct中作为成员方法的事件如下:
|
||||
|
||||
* BeforeInsert()
|
||||
|
||||
* BeforeUpdate()
|
||||
|
||||
* BeforeDelete()
|
||||
|
||||
* AfterInsert()
|
||||
|
||||
* AfterUpdate()
|
||||
|
||||
* AfterDelete()
|
||||
|
||||
在语句执行过程中的事件方法为:
|
||||
|
||||
* Before(beforeFunc interface{})
|
||||
|
||||
* After(afterFunc interface{})
|
||||
|
||||
其中beforeFunc和afterFunc的原型为func(bean interface{}).
|
||||

|
||||
|
||||
<a name="130" id="130"></a>
|
||||
## 13.xorm工具
|
||||
## 12.xorm tool
|
||||
xorm工具提供了xorm命令,能够帮助做很多事情。
|
||||
|
||||
### 13.1.反转命令
|
||||
参见 [xorm工具](https://github.com/lunny/xorm/tree/master/xorm)
|
||||
### 12.1.Reverse command
|
||||
Please visit [xorm tool](https://github.com/go-xorm/xorm/tree/master/xorm)
|
||||
|
||||
<a name="140" id="140"></a>
|
||||
## 14.Examples
|
||||
## 13.Examples
|
||||
|
||||
请访问[https://github.com/lunny/xorm/tree/master/examples](https://github.com/lunny/xorm/tree/master/examples)
|
||||
请访问[https://github.com/go-xorm/xorm/tree/master/examples](https://github.com/go-xorm/xorm/tree/master/examples)
|
||||
|
||||
<a name="150" id="150"></a>
|
||||
## 15.案例
|
||||
## 14.Cases
|
||||
|
||||
* [Gowalker](http://gowalker.org),源代码 [github.com/Unknwon/gowalker](http://github.com/Unknwon/gowalker)
|
||||
* [Gowalker](http://gowalker.org),source [github.com/Unknwon/gowalker](http://github.com/Unknwon/gowalker)
|
||||
|
||||
* [GoDaily Go语言学习网站](http://godaily.org),源代码 [github.com/govc/godaily](http://github.com/govc/godaily)
|
||||
* [GoDaily](http://godaily.org),source [github.com/govc/godaily](http://github.com/govc/godaily)
|
||||
|
||||
* [Sudochina](http://sudochina.com) 和对应的源代码[github.com/insionng/toropress](http://github.com/insionng/toropress)
|
||||
* [Sudochina](http://sudochina.com) source [github.com/insionng/toropress](http://github.com/insionng/toropress)
|
||||
|
||||
* [VeryHour](http://veryhour.com)
|
||||
|
||||
<a name="160" id="160"></a>
|
||||
## 16.那些年我们踩过的坑
|
||||
* 怎么同时使用xorm的tag和json的tag?
|
||||
<a name="160"></a>
|
||||
## 15.FAQ
|
||||
|
||||
1.How the xorm tag use both with json?
|
||||
|
||||
答:使用空格
|
||||
Use space.
|
||||
|
||||
```Go
|
||||
type User struct {
|
||||
Name string `json:"name" xorm:"name"`
|
||||
}
|
||||
```
|
||||
|
||||
* 我的struct里面包含bool类型,为什么它不能作为条件也没法用Update更新?
|
||||
|
||||
答:默认bool类型因为无法判断是否为空,所以不会自动作为条件也不会作为Update的内容。可以使用UseBool函数,也可以使用Cols函数
|
||||
|
||||
```Go
|
||||
engine.Cols("bool_field").Update(&Struct{BoolField:true})
|
||||
// UPDATE struct SET bool_field = true
|
||||
```
|
||||
|
||||
* 我的struct里面包含float64和float32类型,为什么用他们作为查询条件总是不正确?
|
||||
|
||||
答:默认float32和float64映射到数据库中为float,real,double这几种类型,这几种数据库类型数据库的实现一般都是非精确的。因此作为相等条件查询有可能不会返回正确的结果。如果一定要作为查询条件,请将数据库中的类型定义为Numeric或者Decimal。
|
||||
|
||||
```Go
|
||||
type account struct {
|
||||
money float64 `xorm:"Numeric"`
|
||||
}
|
||||
```
|
||||
|
||||
* 为什么Update时Sqlite3返回的affected和其它数据库不一样?
|
||||
|
||||
答:Sqlite3默认Update时返回的是update的查询条件的记录数条数,不管记录是否真的有更新。而Mysql和Postgres默认情况下都是只返回记录中有字段改变的记录数。
|
||||
|
||||
* xorm有几种命名映射规则?
|
||||
|
||||
答:目前支持SnakeMapper和SameMapper两种。SnakeMapper支持结构体和成员以驼峰式命名而数据库表和字段以下划线连接命名;SameMapper支持结构体和数据库的命名保持一致的映射。
|
||||
|
||||
* xorm支持复合主键吗?
|
||||
|
||||
答:支持。在定义时,如果有多个字段标记了pk,则这些字段自动成为复合主键,顺序为在struct中出现的顺序。在使用Id方法时,可以用`Id(xorm.PK{1, 2})`的方式来用。
|
||||
|
||||
|
||||
<a name="170" id="170"></a>
|
||||
## 17.讨论
|
||||
请加入QQ群:280360085 进行讨论。
|
||||
|
|
|
|||
|
|
@ -0,0 +1,834 @@
|
|||
xorm 快速入门
|
||||
=====
|
||||
|
||||
* [1.创建Orm引擎](#10)
|
||||
* [2.定义表结构体](#20)
|
||||
* [2.1.名称映射规则](#21)
|
||||
* [2.2.前缀映射,后缀映射和缓存映射](#22)
|
||||
* [2.3.使用Table和Tag改变名称映射](#23)
|
||||
* [2.4.Column属性定义](#24)
|
||||
* [2.5.Go与字段类型对应表](#25)
|
||||
* [3.表结构操作](#30)
|
||||
* [3.1 获取数据库信息](#31)
|
||||
* [3.2 表操作](#32)
|
||||
* [3.3 创建索引和唯一索引](#33)
|
||||
* [3.4 同步数据库结构](#34)
|
||||
* [4.插入数据](#50)
|
||||
* [5.查询和统计数据](#60)
|
||||
* [5.1.查询条件方法](#61)
|
||||
* [5.2.临时开关方法](#62)
|
||||
* [5.3.Get方法](#63)
|
||||
* [5.4.Find方法](#64)
|
||||
* [5.5.Iterate方法](#65)
|
||||
* [5.6.Count方法](#66)
|
||||
* [5.7.Rows方法](#67)
|
||||
* [6.更新数据](#70)
|
||||
* [6.1.乐观锁](#71)
|
||||
* [7.删除数据](#80)
|
||||
* [8.执行SQL查询](#90)
|
||||
* [9.执行SQL命令](#100)
|
||||
* [10.事务处理](#110)
|
||||
* [11.缓存](#120)
|
||||
* [12.事件](#125)
|
||||
* [13.xorm工具](#130)
|
||||
* [13.1.反转命令](#131)
|
||||
* [14.Examples](#140)
|
||||
* [15.案例](#150)
|
||||
* [16.那些年我们踩过的坑](#160)
|
||||
* [17.讨论](#170)
|
||||
|
||||
<a name="10" id="10"></a>
|
||||
## 1.创建Orm引擎
|
||||
|
||||
在xorm里面,可以同时存在多个Orm引擎,一个Orm引擎称为Engine。因此在使用前必须调用NewEngine,如:
|
||||
|
||||
```Go
|
||||
import (
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/go-xorm/xorm"
|
||||
)
|
||||
engine, err := xorm.NewEngine("mysql", "root:123@/test?charset=utf8")
|
||||
defer engine.Close()
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```Go
|
||||
import (
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/go-xorm/xorm"
|
||||
)
|
||||
engine, err = xorm.NewEngine("sqlite3", "./test.db")
|
||||
defer engine.Close()
|
||||
```
|
||||
|
||||
一般如果只针对一个数据库进行操作,只需要创建一个Engine即可。Engine支持在多GoRutine下使用。
|
||||
|
||||
xorm当前支持五种驱动四个数据库如下:
|
||||
|
||||
* Mysql: [github.com/Go-SQL-Driver/MySQL](https://github.com/Go-SQL-Driver/MySQL)
|
||||
|
||||
* MyMysql: [github.com/ziutek/mymysql/godrv](https://github.com/ziutek/mymysql/godrv)
|
||||
|
||||
* SQLite: [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3)
|
||||
|
||||
* Postgres: [github.com/lib/pq](https://github.com/lib/pq)
|
||||
|
||||
* MsSql: [github.com/lunny/godbc](https://githubcom/lunny/godbc)
|
||||
|
||||
NewEngine传入的参数和`sql.Open`传入的参数完全相同,因此,使用哪个驱动前,请查看此驱动中关于传入参数的说明文档。
|
||||
|
||||
在engine创建完成后可以进行一些设置,如:
|
||||
|
||||
1.错误显示设置,默认如下均为`false`
|
||||
|
||||
* `engine.ShowSQL = true`,则会在控制台打印出生成的SQL语句;
|
||||
* `engine.ShowDebug = true`,则会在控制台打印调试信息;
|
||||
* `engine.ShowError = true`,则会在控制台打印错误信息;
|
||||
* `engine.ShowWarn = true`,则会在控制台打印警告信息;
|
||||
|
||||
2.如果希望用其它方式记录,则可以`engine.Logger`赋值为一个`io.Writer`的实现。比如记录到Log文件,则可以:
|
||||
|
||||
```Go
|
||||
f, err := os.Create("sql.log")
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
return
|
||||
}
|
||||
engine.Logger = f
|
||||
```
|
||||
|
||||
3.engine内部支持连接池接口。
|
||||
|
||||
* 如果需要设置连接池的空闲数大小,可以使用`engine.SetMaxIdleConns()`来实现。
|
||||
* 如果需要设置最大打开连接数,则可以使用`engine.SetMaxOpenConns()`来实现。
|
||||
|
||||
<a name="20" id="20"></a>
|
||||
## 2.定义表结构体
|
||||
|
||||
xorm支持将一个struct映射为数据库中对应的一张表。映射规则如下:
|
||||
|
||||
<a name="21" id="21"></a>
|
||||
### 2.1.名称映射规则
|
||||
|
||||
名称映射规则主要负责结构体名称到表名和结构体field到表字段的名称映射。由xorm.IMapper接口的实现者来管理,xorm内置了两种IMapper实现:`SnakeMapper` 和 `SameMapper`。SnakeMapper支持struct为驼峰式命名,表结构为下划线命名之间的转换;SameMapper支持结构体名称和对应的表名称以及结构体field名称与对应的表字段名称相同的命名。
|
||||
|
||||
当前SnakeMapper为默认值,如果需要改变时,在engine创建完成后使用
|
||||
|
||||
```Go
|
||||
engine.SetMapper(SameMapper{})
|
||||
```
|
||||
|
||||
同时需要注意的是:
|
||||
|
||||
* 如果你使用了别的命名规则映射方案,也可以自己实现一个IMapper。
|
||||
* 表名称和字段名称的映射规则默认是相同的,当然也可以设置为不同,如:
|
||||
|
||||
```Go
|
||||
engine.SetTableMapper(SameMapper{})
|
||||
engine.SetColumnMapper(SnakeMapper{})
|
||||
```
|
||||
|
||||
<a name="22" id="22"></a>
|
||||
### 2.2.前缀映射,后缀映射和缓存映射
|
||||
|
||||
* 通过`engine.NewPrefixMapper(SnakeMapper{}, "prefix")`可以在SnakeMapper的基础上在命名中添加统一的前缀,当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。
|
||||
* 通过`engine.NewSufffixMapper(SnakeMapper{}, "suffix")`可以在SnakeMapper的基础上在命名中添加统一的后缀,当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。
|
||||
* 通过`eneing.NewCacheMapper(SnakeMapper{})`可以组合其它的映射规则,起到在内存中缓存曾经映射过的命名映射。
|
||||
|
||||
<a name="23" id="23"></a>
|
||||
### 2.3.使用Table和Tag改变名称映射
|
||||
|
||||
如果所有的命名都是按照IMapper的映射来操作的,那当然是最理想的。但是如果碰到某个表名或者某个字段名跟映射规则不匹配时,我们就需要别的机制来改变。
|
||||
|
||||
* 如果struct拥有`Tablename() string`的成员方法,那么此方法的返回值即是该struct默认对应的数据库表名。
|
||||
|
||||
* 通过`engine.Table()`方法可以改变struct对应的数据库表的名称,通过sturct中field对应的Tag中使用`xorm:"'column_name'"`可以使该field对应的Column名称为指定名称。这里使用两个单引号将Column名称括起来是为了防止名称冲突,因为我们在Tag中还可以对这个Column进行更多的定义。如果名称不冲突的情况,单引号也可以不使用。
|
||||
|
||||
<a name="23" id="23"></a>
|
||||
### 2.4.Column属性定义
|
||||
我们在field对应的Tag中对Column的一些属性进行定义,定义的方法基本和我们写SQL定义表结构类似,比如:
|
||||
|
||||
```
|
||||
type User struct {
|
||||
Id int64
|
||||
Name string `xorm:"varchar(25) not null unique 'usr_name'"`
|
||||
}
|
||||
```
|
||||
|
||||
对于不同的数据库系统,数据类型其实是有些差异的。因此xorm中对数据类型有自己的定义,基本的原则是尽量兼容各种数据库的字段类型,具体的字段对应关系可以查看[字段类型对应表](https://github.com/go-xorm/xorm/blob/master/docs/COLUMNTYPE.md)。对于使用者,一般只要使用自己熟悉的数据库字段定义即可。
|
||||
|
||||
具体的映射规则如下,另Tag中的关键字均不区分大小写,字段名区分大小写:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>name</td><td>当前field对应的字段的名称,可选,如不写,则自动根据field名字和转换规则命名,如与其它关键字冲突,请使用单引号括起来。</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>pk</td><td>是否是Primary Key,如果在一个struct中有多个字段都使用了此标记,则这多个字段构成了复合主键,单主键当前支持int32,int,int64,uint32,uint,uint64,string这7种Go的数据类型,复合主键支持这7种Go的数据类型的组合。</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>当前支持30多种字段类型,详情参见 [字段类型](https://github.com/go-xorm/xorm/blob/master/docs/COLUMNTYPE.md)</td><td>字段类型</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>autoincr</td><td>是否是自增</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>[not ]null 或 notnull</td><td>是否可以为空</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>unique或unique(uniquename)</td><td>是否是唯一,如不加括号则该字段不允许重复;如加上括号,则括号中为联合唯一索引的名字,此时如果有另外一个或多个字段和本unique的uniquename相同,则这些uniquename相同的字段组成联合唯一索引</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>index或index(indexname)</td><td>是否是索引,如不加括号则该字段自身为索引,如加上括号,则括号中为联合索引的名字,此时如果有另外一个或多个字段和本index的indexname相同,则这些indexname相同的字段组成联合索引</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>extends</td><td>应用于一个匿名结构体之上,表示此匿名结构体的成员也映射到数据库中</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td><td>这个Field将不进行字段映射</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-></td><td>这个Field将只写入到数据库而不从数据库读取</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><-</td><td>这个Field将只从数据库读取,而不写入到数据库</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>created</td><td>这个Field将在Insert时自动赋值为当前时间</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>updated</td><td>这个Field将在Insert或Update时自动赋值为当前时间</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>version</td><td>这个Field将会在insert时默认为1,每次更新自动加1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>default 0</td><td>设置默认值,紧跟的内容如果是Varchar等需要加上单引号</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
另外有如下几条自动映射的规则:
|
||||
|
||||
- 1.如果field名称为`Id`而且类型为`int64`并且没有定义tag,则会被xorm视为主键,并且拥有自增属性。如果想用`Id`以外的名字或非int64类型做为主键名,必须在对应的Tag上加上`xorm:"pk"`来定义主键,加上`xorm:"autoincr"`作为自增。这里需要注意的是,有些数据库并不允许非主键的自增属性。
|
||||
|
||||
- 2.string类型默认映射为varchar(255),如果需要不同的定义,可以在tag中自定义
|
||||
|
||||
- 3.支持`type MyString string`等自定义的field,支持Slice, Map等field成员,这些成员默认存储为Text类型,并且默认将使用Json格式来序列化和反序列化。也支持数据库字段类型为Blob类型,如果是Blob类型,则先使用Json格式序列化再转成[]byte格式。当然[]byte或者[]uint8默认为Blob类型并且都以二进制方式存储。具体参见 [go类型<->数据库类型对应表](https://github.com/go-xorm/xorm/blob/master/docs/AutoMap.md)
|
||||
|
||||
- 4.实现了Conversion接口的类型或者结构体,将根据接口的转换方式在类型和数据库记录之间进行相互转换。
|
||||
```Go
|
||||
type Conversion interface {
|
||||
FromDB([]byte) error
|
||||
ToDB() ([]byte, error)
|
||||
}
|
||||
```
|
||||
|
||||
<a name="24" id="24"></a>
|
||||
### 2.4.Go与字段类型对应表
|
||||
|
||||
如果不使用tag来定义field对应的数据库字段类型,那么系统会自动给出一个默认的字段类型,对应表如下:
|
||||
|
||||
[go类型<->数据库类型对应表](https://github.com/go-xorm/xorm/blob/master/docs/AutoMap.md)
|
||||
|
||||
<a name="30" id="30"></a>
|
||||
## 3.表结构操作
|
||||
|
||||
xorm提供了一些动态获取和修改表结构的方法。对于一般的应用,很少动态修改表结构,则只需调用Sync()同步下表结构即可。
|
||||
|
||||
<a name="31" id="31"></a>
|
||||
## 3.1 获取数据库信息
|
||||
|
||||
* DBMetas()
|
||||
|
||||
xorm支持获取表结构信息,通过调用`engine.DBMetas()`可以获取到所有的表,字段,索引的信息。
|
||||
|
||||
<a name="31" id="31"></a>
|
||||
## 3.2.表操作
|
||||
|
||||
* CreateTables()
|
||||
|
||||
创建表使用`engine.CreateTables()`,参数为一个或多个空的对应Struct的指针。同时可用的方法有Charset()和StoreEngine(),如果对应的数据库支持,这两个方法可以在创建表时指定表的字符编码和使用的引擎。当前仅支持Mysql数据库。
|
||||
|
||||
* IsTableEmpty()
|
||||
|
||||
判断表是否为空,参数和CreateTables相同
|
||||
|
||||
* IsTableExist()
|
||||
|
||||
判断表是否存在
|
||||
|
||||
* DropTables()
|
||||
|
||||
删除表使用`engine.DropTables()`,参数为一个或多个空的对应Struct的指针或者表的名字。如果为string传入,则只删除对应的表,如果传入的为Struct,则删除表的同时还会删除对应的索引。
|
||||
|
||||
<a name="32" id="32"></a>
|
||||
## 3.3.创建索引和唯一索引
|
||||
|
||||
* CreateIndexes
|
||||
|
||||
根据struct中的tag来创建索引
|
||||
|
||||
* CreateUniques
|
||||
|
||||
根据struct中的tag来创建唯一索引
|
||||
|
||||
<a name="34" id="34"></a>
|
||||
## 3.4.同步数据库结构
|
||||
|
||||
同步能够部分智能的根据结构体的变动检测表结构的变动,并自动同步。目前能够实现:
|
||||
|
||||
* 1) 自动检测和创建表,这个检测是根据表的名字
|
||||
* 2)自动检测和新增表中的字段,这个检测是根据字段名
|
||||
* 3)自动检测和创建索引和唯一索引,这个检测是根据一个或多个字段名,而不根据索引名称
|
||||
|
||||
调用方法如下:
|
||||
|
||||
```Go
|
||||
err := engine.Sync(new(User))
|
||||
```
|
||||
|
||||
<a name="50" id="50"></a>
|
||||
## 4.插入数据
|
||||
|
||||
插入数据使用Insert方法,Insert方法的参数可以是一个或多个Struct的指针,一个或多个Struct的Slice的指针。
|
||||
如果传入的是Slice并且当数据库支持批量插入时,Insert会使用批量插入的方式进行插入。
|
||||
|
||||
* 插入一条数据
|
||||
|
||||
```Go
|
||||
user := new(User)
|
||||
user.Name = "myname"
|
||||
affected, err := engine.Insert(user)
|
||||
```
|
||||
|
||||
在插入单条数据成功后,如果该结构体有自增字段,则自增字段会被自动赋值为数据库中的id
|
||||
|
||||
```Go
|
||||
fmt.Println(user.Id)
|
||||
```
|
||||
|
||||
* 插入同一个表的多条数据
|
||||
|
||||
```Go
|
||||
users := make([]User, 0)
|
||||
users[0].Name = "name0"
|
||||
...
|
||||
affected, err := engine.Insert(&users)
|
||||
```
|
||||
|
||||
* 使用指针Slice插入多条记录
|
||||
|
||||
```Go
|
||||
users := make([]*User, 0)
|
||||
users[0] = new(User)
|
||||
users[0].Name = "name0"
|
||||
...
|
||||
affected, err := engine.Insert(&users)
|
||||
```
|
||||
|
||||
* 插入不同表的一条记录
|
||||
|
||||
```Go
|
||||
user := new(User)
|
||||
user.Name = "myname"
|
||||
question := new(Question)
|
||||
question.Content = "whywhywhwy?"
|
||||
affected, err := engine.Insert(user, question)
|
||||
```
|
||||
|
||||
* 插入不同表的多条记录
|
||||
|
||||
```Go
|
||||
users := make([]User, 0)
|
||||
users[0].Name = "name0"
|
||||
...
|
||||
questions := make([]Question, 0)
|
||||
questions[0].Content = "whywhywhwy?"
|
||||
affected, err := engine.Insert(&users, &questions)
|
||||
```
|
||||
|
||||
* 插入不同表的一条或多条记录
|
||||
```Go
|
||||
user := new(User)
|
||||
user.Name = "myname"
|
||||
...
|
||||
questions := make([]Question, 0)
|
||||
questions[0].Content = "whywhywhwy?"
|
||||
affected, err := engine.Insert(user, &questions)
|
||||
```
|
||||
|
||||
这里需要注意以下几点:
|
||||
* 这里虽然支持同时插入,但这些插入并没有事务关系。因此有可能在中间插入出错后,后面的插入将不会继续。
|
||||
* 多条插入会自动生成`Insert into table values (),(),()`的语句,因此这样的语句有一个最大的记录数,根据经验测算在150条左右。大于150条后,生成的sql语句将太长可能导致执行失败。因此在插入大量数据时,目前需要自行分割成每150条插入一次。
|
||||
|
||||
<a name="60" id="60"></a>
|
||||
## 5.查询和统计数据
|
||||
|
||||
所有的查询条件不区分调用顺序,但必须在调用Get,Find,Count, Iterate, Rows这几个函数之前调用。同时需要注意的一点是,在调用的参数中,如果采用默认的`SnakeMapper`所有的字符字段名均为映射后的数据库的字段名,而不是field的名字。
|
||||
|
||||
<a name="61" id="61"></a>
|
||||
### 5.1.查询条件方法
|
||||
|
||||
查询和统计主要使用`Get`, `Find`, `Count`, `Rows`, `Iterate`这几个方法。在进行查询时可以使用多个方法来形成查询条件,条件函数如下:
|
||||
|
||||
* Id(interface{})
|
||||
传入一个PK字段的值,作为查询条件,如果是复合主键,则
|
||||
`Id(xorm.PK{1, 2})`
|
||||
传入的两个参数按照struct中pk标记字段出现的顺序赋值。
|
||||
|
||||
* Where(string, …interface{})
|
||||
和SQL中Where语句中的条件基本相同,作为条件
|
||||
|
||||
* And(string, …interface{})
|
||||
和Where函数中的条件基本相同,作为条件
|
||||
|
||||
* Or(string, …interface{})
|
||||
和Where函数中的条件基本相同,作为条件
|
||||
|
||||
* Sql(string, …interface{})
|
||||
执行指定的Sql语句,并把结果映射到结构体
|
||||
|
||||
* Asc(…string)
|
||||
指定字段名正序排序
|
||||
|
||||
* Desc(…string)
|
||||
指定字段名逆序排序
|
||||
|
||||
* OrderBy(string)
|
||||
按照指定的顺序进行排序
|
||||
|
||||
* In(string, …interface{})
|
||||
某字段在一些值中,这里需要注意必须是[]interface{}才可以展开,由于Go语言的限制,[]int64等均不可以展开。
|
||||
|
||||
* Cols(…string)
|
||||
只查询或更新某些指定的字段,默认是查询所有映射的字段或者根据Update的第一个参数来判断更新的字段。例如:
|
||||
```Go
|
||||
engine.Cols("age", "name").Find(&users)
|
||||
// SELECT age, name FROM user
|
||||
engine.Cols("age", "name").Update(&user)
|
||||
// UPDATE user SET age=? AND name=?
|
||||
```
|
||||
|
||||
* AllCols()
|
||||
查询或更新所有字段。
|
||||
|
||||
* MustCols(…string)
|
||||
某些字段必须更新。
|
||||
|
||||
* Omit(...string)
|
||||
和cols相反,此函数指定排除某些指定的字段。注意:此方法和Cols方法不可同时使用
|
||||
```Go
|
||||
engine.Cols("age").Update(&user)
|
||||
// UPDATE user SET name = ? AND department = ?
|
||||
```
|
||||
|
||||
* Distinct(…string)
|
||||
按照参数中指定的字段归类结果
|
||||
```Go
|
||||
engine.Distinct("age", "department").Find(&users)
|
||||
// SELECT DISTINCT age, department FROM user
|
||||
```
|
||||
注意:当开启了缓存时,此方法的调用将在当前查询中禁用缓存。因为缓存系统当前依赖Id,而此时无法获得Id
|
||||
|
||||
* Table(nameOrStructPtr interface{})
|
||||
传入表名称或者结构体指针,如果传入的是结构体指针,则按照IMapper的规则提取出表名
|
||||
|
||||
* Limit(int, …int)
|
||||
限制获取的数目,第一个参数为条数,第二个参数为可选,表示开始位置
|
||||
|
||||
* Top(int)
|
||||
相当于Limit(int, 0)
|
||||
|
||||
* Join(string,string,string)
|
||||
第一个参数为连接类型,当前支持INNER, LEFT OUTER, CROSS中的一个值,第二个参数为表名,第三个参数为连接条件
|
||||
|
||||
* GroupBy(string)
|
||||
Groupby的参数字符串
|
||||
|
||||
* Having(string)
|
||||
Having的参数字符串
|
||||
|
||||
<a name="62" id="62"></a>
|
||||
### 5.2.临时开关方法
|
||||
|
||||
* NoAutoTime()
|
||||
如果此方法执行,则此次生成的语句中Created和Updated字段将不自动赋值为当前时间
|
||||
|
||||
* NoCache()
|
||||
如果此方法执行,则此次生成的语句则在非缓存模式下执行
|
||||
|
||||
* UseBool(...string)
|
||||
当从一个struct来生成查询条件或更新字段时,xorm会判断struct的field是否为0,"",nil,如果为以上则不当做查询条件或者更新内容。因为bool类型只有true和false两种值,因此默认所有bool类型不会作为查询条件或者更新字段。如果可以使用此方法,如果默认不传参数,则所有的bool字段都将会被使用,如果参数不为空,则参数中指定的为字段名,则这些字段对应的bool值将被使用。
|
||||
|
||||
* NoCascade()
|
||||
是否自动关联查询field中的数据,如果struct的field也是一个struct并且映射为某个Id,则可以在查询时自动调用Get方法查询出对应的数据。
|
||||
|
||||
<a name="63" id="63"></a>
|
||||
### 5.3.Get方法
|
||||
|
||||
查询单条数据使用`Get`方法,在调用Get方法时需要传入一个对应结构体的指针,同时结构体中的非空field自动成为查询的条件和前面的方法条件组合在一起查询。
|
||||
|
||||
如:
|
||||
|
||||
1) 根据Id来获得单条数据:
|
||||
|
||||
```Go
|
||||
user := new(User)
|
||||
has, err := engine.Id(id).Get(user)
|
||||
// 复合主键的获取方法
|
||||
// has, errr := engine.Id(xorm.PK{1,2}).Get(user)
|
||||
```
|
||||
|
||||
2) 根据Where来获得单条数据:
|
||||
|
||||
```Go
|
||||
user := new(User)
|
||||
has, err := engine.Where("name=?", "xlw").Get(user)
|
||||
```
|
||||
|
||||
3) 根据user结构体中已有的非空数据来获得单条数据:
|
||||
|
||||
```Go
|
||||
user := &User{Id:1}
|
||||
has, err := engine.Get(user)
|
||||
```
|
||||
|
||||
或者其它条件
|
||||
|
||||
```Go
|
||||
user := &User{Name:"xlw"}
|
||||
has, err := engine.Get(user)
|
||||
```
|
||||
|
||||
返回的结果为两个参数,一个`has`为该条记录是否存在,第二个参数`err`为是否有错误。不管err是否为nil,has都有可能为true或者false。
|
||||
|
||||
<a name="64" id="64"></a>
|
||||
### 5.4.Find方法
|
||||
|
||||
查询多条数据使用`Find`方法,Find方法的第一个参数为`slice`的指针或`Map`指针,即为查询后返回的结果,第二个参数可选,为查询的条件struct的指针。
|
||||
|
||||
1) 传入Slice用于返回数据
|
||||
|
||||
```Go
|
||||
everyone := make([]Userinfo, 0)
|
||||
err := engine.Find(&everyone)
|
||||
|
||||
pEveryOne := make([]*Userinfo, 0)
|
||||
err := engine.Find(&pEveryOne)
|
||||
```
|
||||
|
||||
2) 传入Map用户返回数据,map必须为`map[int64]Userinfo`的形式,map的key为id,因此对于复合主键无法使用这种方式。
|
||||
|
||||
```Go
|
||||
users := make(map[int64]Userinfo)
|
||||
err := engine.Find(&users)
|
||||
|
||||
pUsers := make(map[int64]*Userinfo)
|
||||
err := engine.Find(&pUsers)
|
||||
```
|
||||
|
||||
3) 也可以加入各种条件
|
||||
|
||||
```Go
|
||||
users := make([]Userinfo, 0)
|
||||
err := engine.Where("age > ? or name = ?", 30, "xlw").Limit(20, 10).Find(&users)
|
||||
```
|
||||
|
||||
<a name="65" id="65"></a>
|
||||
### 5.5.Iterate方法
|
||||
|
||||
Iterate方法提供逐条执行查询到的记录的方法,他所能使用的条件和Find方法完全相同
|
||||
|
||||
```Go
|
||||
err := engine.Where("age > ? or name=?)", 30, "xlw").Iterate(new(Userinfo), func(i int, bean interface{})error{
|
||||
user := bean.(*Userinfo)
|
||||
//do somthing use i and user
|
||||
})
|
||||
```
|
||||
|
||||
<a name="66" id="66"></a>
|
||||
### 5.6.Count方法
|
||||
|
||||
统计数据使用`Count`方法,Count方法的参数为struct的指针并且成为查询条件。
|
||||
```Go
|
||||
user := new(User)
|
||||
total, err := engine.Where("id >?", 1).Count(user)
|
||||
```
|
||||
|
||||
<a name="67" id="67"></a>
|
||||
### 5.7.Rows方法
|
||||
|
||||
Rows方法和Iterate方法类似,提供逐条执行查询到的记录的方法,不过Rows更加灵活好用。
|
||||
```Go
|
||||
user := new(User)
|
||||
rows, err := engine.Where("id >?", 1).Rows(user)
|
||||
if err != nil {
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
err = rows.Scan(user)
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
<a name="70" id="70"></a>
|
||||
## 6.更新数据
|
||||
|
||||
更新数据使用`Update`方法,Update方法的第一个参数为需要更新的内容,可以为一个结构体指针或者一个Map[string]interface{}类型。当传入的为结构体指针时,只有非空和0的field才会被作为更新的字段。当传入的为Map类型时,key为数据库Column的名字,value为要更新的内容。
|
||||
|
||||
```Go
|
||||
user := new(User)
|
||||
user.Name = "myname"
|
||||
affected, err := engine.Id(id).Update(user)
|
||||
```
|
||||
|
||||
这里需要注意,Update会自动从user结构体中提取非0和非nil得值作为需要更新的内容,因此,如果需要更新一个值为0,则此种方法将无法实现,因此有两种选择:
|
||||
|
||||
* 1.通过添加Cols函数指定需要更新结构体中的哪些值,未指定的将不更新,指定了的即使为0也会更新。
|
||||
|
||||
```Go
|
||||
affected, err := engine.Id(id).Cols("age").Update(&user)
|
||||
```
|
||||
|
||||
* 2.通过传入map[string]interface{}来进行更新,但这时需要额外指定更新到哪个表,因为通过map是无法自动检测更新哪个表的。
|
||||
|
||||
```Go
|
||||
affected, err := engine.Table(new(User)).Id(id).Update(map[string]interface{}{"age":0})
|
||||
```
|
||||
|
||||
<a name="71" id="71"></a>
|
||||
### 6.1.乐观锁
|
||||
|
||||
要使用乐观锁,需要使用version标记
|
||||
type User struct {
|
||||
Id int64
|
||||
Name string
|
||||
Version int `xorm:"version"`
|
||||
}
|
||||
|
||||
在Insert时,version标记的字段将会被设置为1,在Update时,Update的内容必须包含version原来的值。
|
||||
|
||||
```Go
|
||||
var user User
|
||||
engine.Id(1).Get(&user)
|
||||
// SELECT * FROM user WHERE id = ?
|
||||
engine.Id(1).Update(&user)
|
||||
// UPDATE user SET ..., version = version + 1 WHERE id = ? AND version = ?
|
||||
```
|
||||
|
||||
<a name="80" id="80"></a>
|
||||
## 7.删除数据
|
||||
|
||||
删除数据`Delete`方法,参数为struct的指针并且成为查询条件。
|
||||
|
||||
```Go
|
||||
user := new(User)
|
||||
affected, err := engine.Id(id).Delete(user)
|
||||
```
|
||||
|
||||
`Delete`的返回值第一个参数为删除的记录数,第二个参数为错误。
|
||||
|
||||
注意:当删除时,如果user中包含有bool,float64或者float32类型,有可能会使删除失败。具体请查看 <a href="#160">FAQ</a>
|
||||
|
||||
<a name="90" id="90"></a>
|
||||
## 8.执行SQL查询
|
||||
|
||||
也可以直接执行一个SQL查询,即Select命令。在Postgres中支持原始SQL语句中使用 ` 和 ? 符号。
|
||||
|
||||
```Go
|
||||
sql := "select * from userinfo"
|
||||
results, err := engine.Query(sql)
|
||||
```
|
||||
|
||||
当调用`Query`时,第一个返回值`results`为`[]map[string][]byte`的形式。
|
||||
|
||||
<a name="100" id="100"></a>
|
||||
## 9.执行SQL命令
|
||||
|
||||
也可以直接执行一个SQL命令,即执行Insert, Update, Delete 等操作。此时不管数据库是何种类型,都可以使用 ` 和 ? 符号。
|
||||
|
||||
```Go
|
||||
sql = "update `userinfo` set username=? where id=?"
|
||||
res, err := engine.Exec(sql, "xiaolun", 1)
|
||||
```
|
||||
|
||||
<a name="110" id="110"></a>
|
||||
## 10.事务处理
|
||||
当使用事务处理时,需要创建Session对象。在进行事物处理时,可以混用ORM方法和RAW方法,如下代码所示:
|
||||
|
||||
```Go
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
// add Begin() before any action
|
||||
err := session.Begin()
|
||||
user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()}
|
||||
_, err = session.Insert(&user1)
|
||||
if err != nil {
|
||||
session.Rollback()
|
||||
return
|
||||
}
|
||||
user2 := Userinfo{Username: "yyy"}
|
||||
_, err = session.Where("id = ?", 2).Update(&user2)
|
||||
if err != nil {
|
||||
session.Rollback()
|
||||
return
|
||||
}
|
||||
|
||||
_, err = session.Exec("delete from userinfo where username = ?", user2.Username)
|
||||
if err != nil {
|
||||
session.Rollback()
|
||||
return
|
||||
}
|
||||
|
||||
// add Commit() after all actions
|
||||
err = session.Commit()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
* 注意如果您使用的是mysql,数据库引擎为innodb事务才有效,myisam引擎是不支持事务的。
|
||||
|
||||
<a name="120" id="120"></a>
|
||||
## 11.缓存
|
||||
|
||||
xorm内置了一致性缓存支持,不过默认并没有开启。要开启缓存,需要在engine创建完后进行配置,如:
|
||||
启用一个全局的内存缓存
|
||||
|
||||
```Go
|
||||
cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000)
|
||||
engine.SetDefaultCacher(cacher)
|
||||
```
|
||||
|
||||
上述代码采用了LRU算法的一个缓存,缓存方式是存放到内存中,缓存struct的记录数为1000条,缓存针对的范围是所有具有主键的表,没有主键的表中的数据将不会被缓存。
|
||||
如果只想针对部分表,则:
|
||||
|
||||
```Go
|
||||
cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000)
|
||||
engine.MapCacher(&user, cacher)
|
||||
```
|
||||
|
||||
如果要禁用某个表的缓存,则:
|
||||
|
||||
```Go
|
||||
engine.MapCacher(&user, nil)
|
||||
```
|
||||
|
||||
设置完之后,其它代码基本上就不需要改动了,缓存系统已经在后台运行。
|
||||
|
||||
当前实现了内存存储的CacheStore接口MemoryStore,如果需要采用其它设备存储,可以实现CacheStore接口。
|
||||
|
||||
不过需要特别注意不适用缓存或者需要手动编码的地方:
|
||||
|
||||
1. 当使用了`Distinct`,`Having`,`GroupBy`方法将不会使用缓存
|
||||
|
||||
2. 在`Get`或者`Find`时使用了`Cols`,`Omit`方法,则在开启缓存后此方法无效,系统仍旧会取出这个表中的所有字段。
|
||||
|
||||
3. 在使用Exec方法执行了方法之后,可能会导致缓存与数据库不一致的地方。因此如果启用缓存,尽量避免使用Exec。如果必须使用,则需要在使用了Exec之后调用ClearCache手动做缓存清除的工作。比如:
|
||||
|
||||
```Go
|
||||
engine.Exec("update user set name = ? where id = ?", "xlw", 1)
|
||||
engine.ClearCache(new(User))
|
||||
```
|
||||
|
||||
缓存的实现原理如下图所示:
|
||||
|
||||

|
||||
|
||||
<a name="125" id="125"></a>
|
||||
## 12.事件
|
||||
xorm支持两种方式的事件,一种是在Struct中的特定方法来作为事件的方法,一种是在执行语句的过程中执行事件。
|
||||
|
||||
在Struct中作为成员方法的事件如下:
|
||||
|
||||
* BeforeInsert()
|
||||
|
||||
* BeforeUpdate()
|
||||
|
||||
* BeforeDelete()
|
||||
|
||||
* AfterInsert()
|
||||
|
||||
* AfterUpdate()
|
||||
|
||||
* AfterDelete()
|
||||
|
||||
在语句执行过程中的事件方法为:
|
||||
|
||||
* Before(beforeFunc interface{})
|
||||
|
||||
* After(afterFunc interface{})
|
||||
|
||||
其中beforeFunc和afterFunc的原型为func(bean interface{}).
|
||||
|
||||
<a name="130" id="130"></a>
|
||||
## 13.xorm工具
|
||||
xorm工具提供了xorm命令,能够帮助做很多事情。
|
||||
|
||||
### 13.1.反转命令
|
||||
参见 [xorm工具](https://github.com/go-xorm/xorm/tree/master/xorm)
|
||||
|
||||
<a name="140" id="140"></a>
|
||||
## 14.Examples
|
||||
|
||||
请访问[https://github.com/go-xorm/xorm/tree/master/examples](https://github.com/go-xorm/xorm/tree/master/examples)
|
||||
|
||||
<a name="150" id="150"></a>
|
||||
## 15.案例
|
||||
|
||||
* [Gowalker](http://gowalker.org),源代码 [github.com/Unknwon/gowalker](http://github.com/Unknwon/gowalker)
|
||||
|
||||
* [GoDaily Go语言学习网站](http://godaily.org),源代码 [github.com/govc/godaily](http://github.com/govc/godaily)
|
||||
|
||||
* [Sudochina](http://sudochina.com) 和对应的源代码[github.com/insionng/toropress](http://github.com/insionng/toropress)
|
||||
|
||||
* [VeryHour](http://veryhour.com)
|
||||
|
||||
<a name="160" id="160"></a>
|
||||
## 16.那些年我们踩过的坑
|
||||
* 怎么同时使用xorm的tag和json的tag?
|
||||
|
||||
答:使用空格
|
||||
|
||||
```Go
|
||||
type User struct {
|
||||
Name string `json:"name" xorm:"name"`
|
||||
}
|
||||
```
|
||||
|
||||
* 我的struct里面包含bool类型,为什么它不能作为条件也没法用Update更新?
|
||||
|
||||
答:默认bool类型因为无法判断是否为空,所以不会自动作为条件也不会作为Update的内容。可以使用UseBool函数,也可以使用Cols函数
|
||||
|
||||
```Go
|
||||
engine.Cols("bool_field").Update(&Struct{BoolField:true})
|
||||
// UPDATE struct SET bool_field = true
|
||||
```
|
||||
|
||||
* 我的struct里面包含float64和float32类型,为什么用他们作为查询条件总是不正确?
|
||||
|
||||
答:默认float32和float64映射到数据库中为float,real,double这几种类型,这几种数据库类型数据库的实现一般都是非精确的。因此作为相等条件查询有可能不会返回正确的结果。如果一定要作为查询条件,请将数据库中的类型定义为Numeric或者Decimal。
|
||||
|
||||
```Go
|
||||
type account struct {
|
||||
money float64 `xorm:"Numeric"`
|
||||
}
|
||||
```
|
||||
|
||||
* 为什么Update时Sqlite3返回的affected和其它数据库不一样?
|
||||
|
||||
答:Sqlite3默认Update时返回的是update的查询条件的记录数条数,不管记录是否真的有更新。而Mysql和Postgres默认情况下都是只返回记录中有字段改变的记录数。
|
||||
|
||||
* xorm有几种命名映射规则?
|
||||
|
||||
答:目前支持SnakeMapper和SameMapper两种。SnakeMapper支持结构体和成员以驼峰式命名而数据库表和字段以下划线连接命名;SameMapper支持结构体和数据库的命名保持一致的映射。
|
||||
|
||||
* xorm支持复合主键吗?
|
||||
|
||||
答:支持。在定义时,如果有多个字段标记了pk,则这些字段自动成为复合主键,顺序为在struct中出现的顺序。在使用Id方法时,可以用`Id(xorm.PK{1, 2})`的方式来用。
|
||||
|
||||
|
||||
<a name="170" id="170"></a>
|
||||
## 17.讨论
|
||||
请加入QQ群:280360085 进行讨论。
|
||||
|
|
@ -1,693 +0,0 @@
|
|||
Quick Start
|
||||
=====
|
||||
|
||||
* [1.Create ORM Engine](#10)
|
||||
* [2.Define a struct](#20)
|
||||
* [2.1.Name mapping rule](#21)
|
||||
* [2.2.Use Table or Tag to change table or column name](#22)
|
||||
* [2.3.Column define](#23)
|
||||
* [3. database schema operation](#30)
|
||||
* [3.1.Retrieve database schema infomation](#31)
|
||||
* [3.2.Table Operation](#32)
|
||||
* [3.3.Create indexes and uniques](#33)
|
||||
* [3.4.Sync database schema](#34)
|
||||
* [4.Insert records](#40)
|
||||
* [5.Query and Count records](#60)
|
||||
* [5.1.Query condition methods](#61)
|
||||
* [5.2.Temporory methods](#62)
|
||||
* [5.3.Get](#63)
|
||||
* [5.4.Find](#64)
|
||||
* [5.5.Iterate](#65)
|
||||
* [5.6.Count](#66)
|
||||
* [6.Update records](#70)
|
||||
* [6.1.Optimistic Locking](#71)
|
||||
* [7.Delete records](#80)
|
||||
* [8.Execute SQL command](#90)
|
||||
* [9.Execute SQL query](#100)
|
||||
* [10.Transaction](#110)
|
||||
* [11.Cache](#120)
|
||||
* [12.Xorm Tool](#130)
|
||||
* [12.1.Reverse command](#131)
|
||||
* [13.Examples](#140)
|
||||
* [14.Cases](#150)
|
||||
* [15.FAQ](#160)
|
||||
* [16.Discuss](#170)
|
||||
|
||||
<a name="10" id="10"></a>
|
||||
## 1.Create ORM Engine
|
||||
|
||||
When using xorm, you can create multiple orm engines, an engine means a databse. So you can:
|
||||
|
||||
```Go
|
||||
import (
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/lunny/xorm"
|
||||
)
|
||||
engine, err := xorm.NewEngine("mysql", "root:123@/test?charset=utf8")
|
||||
defer engine.Close()
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```Go
|
||||
import (
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/lunny/xorm"
|
||||
)
|
||||
engine, err = xorm.NewEngine("sqlite3", "./test.db")
|
||||
defer engine.Close()
|
||||
```
|
||||
|
||||
Generally, you can only create one engine. Engine supports run on go rutines.
|
||||
|
||||
xorm supports four drivers now:
|
||||
|
||||
* Mysql: [github.com/Go-SQL-Driver/MySQL](https://github.com/Go-SQL-Driver/MySQL)
|
||||
|
||||
* MyMysql: [github.com/ziutek/mymysql/godrv](https://github.com/ziutek/mymysql/godrv)
|
||||
|
||||
* SQLite: [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3)
|
||||
|
||||
* Postgres: [github.com/lib/pq](https://github.com/lib/pq)
|
||||
|
||||
NewEngine's parameters are the same as `sql.Open`. So you should read the drivers' document for parameters' usage.
|
||||
|
||||
After engine created, you can do some settings.
|
||||
|
||||
1.Logs
|
||||
|
||||
* `engine.ShowSQL = true`, Show SQL statement on standard output;
|
||||
* `engine.ShowDebug = true`, Show debug infomation on standard output;
|
||||
* `engine.ShowError = true`, Show error infomation on standard output;
|
||||
* `engine.ShowWarn = true`, Show warnning information on standard output;
|
||||
|
||||
2.If want to record infomation with another method: use `engine.Logger` as `io.Writer`:
|
||||
|
||||
```Go
|
||||
f, err := os.Create("sql.log")
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
return
|
||||
}
|
||||
engine.Logger = f
|
||||
```
|
||||
|
||||
3.Engine support connection pool. The default pool is database/sql's and also you can use custom pool. Xorm provides two connection pool `xorm.NonConnectionPool` & `xorm.SimpleConnectPool`. If you want to use yourself pool, you can use `engine.SetPool` to set it.
|
||||
|
||||
* Use `engine.SetIdleConns()` to set idle connections.
|
||||
* Use `engine.SetMaxConns()` to set Max connections. This methods support only Go 1.2+.
|
||||
|
||||
<a name="20" id="20"></a>
|
||||
## 2.Define struct
|
||||
|
||||
xorm map a struct to a database table, the rule is below.
|
||||
|
||||
<a name="21" id="21"></a>
|
||||
### 2.1.name mapping rule
|
||||
|
||||
use xorm.IMapper interface to implement. There are two IMapper implemented: `SnakeMapper` and `SameMapper`. SnakeMapper means struct name is word by word and table name or column name as 下划线. SameMapper means same name between struct and table.
|
||||
|
||||
SnakeMapper is the default.
|
||||
|
||||
```Go
|
||||
engine.SetMapper(SameMapper{})
|
||||
```
|
||||
|
||||
同时需要注意的是:
|
||||
|
||||
* 如果你使用了别的命名规则映射方案,也可以自己实现一个IMapper。
|
||||
* 表名称和字段名称的映射规则默认是相同的,当然也可以设置为不同,如:
|
||||
|
||||
```Go
|
||||
engine.SetTableMapper(SameMapper{})
|
||||
engine.SetColumnMapper(SnakeMapper{})
|
||||
```
|
||||
|
||||
<a name="22" id="22"></a>
|
||||
### 2.2.前缀映射规则,后缀映射规则和缓存映射规则
|
||||
|
||||
* 通过`engine.NewPrefixMapper(SnakeMapper{}, "prefix")`可以在SnakeMapper的基础上在命名中添加统一的前缀,当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。
|
||||
* 通过`engine.NewSufffixMapper(SnakeMapper{}, "suffix")`可以在SnakeMapper的基础上在命名中添加统一的后缀,当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。
|
||||
* 通过`eneing.NewCacheMapper(SnakeMapper{})`可以起到在内存中缓存曾经映射过的命名映射。
|
||||
|
||||
当然,如果你使用了别的命名规则映射方案,也可以自己实现一个IMapper。
|
||||
|
||||
<a name="22" id="22"></a>
|
||||
### 2.3.使用Table和Tag改变名称映射
|
||||
|
||||
如果所有的命名都是按照IMapper的映射来操作的,那当然是最理想的。但是如果碰到某个表名或者某个字段名跟映射规则不匹配时,我们就需要别的机制来改变。
|
||||
|
||||
通过`engine.Table()`方法可以改变struct对应的数据库表的名称,通过sturct中field对应的Tag中使用`xorm:"'table_name'"`可以使该field对应的Column名称为指定名称。这里使用两个单引号将Column名称括起来是为了防止名称冲突,因为我们在Tag中还可以对这个Column进行更多的定义。如果名称不冲突的情况,单引号也可以不使用。
|
||||
|
||||
<a name="23" id="23"></a>
|
||||
### 2.4.Column属性定义
|
||||
我们在field对应的Tag中对Column的一些属性进行定义,定义的方法基本和我们写SQL定义表结构类似,比如:
|
||||
|
||||
```
|
||||
type User struct {
|
||||
Id int64
|
||||
Name string `xorm:"varchar(25) not null unique 'usr_name'"`
|
||||
}
|
||||
```
|
||||
|
||||
对于不同的数据库系统,数据类型其实是有些差异的。因此xorm中对数据类型有自己的定义,基本的原则是尽量兼容各种数据库的字段类型,具体的字段对应关系可以查看[字段类型对应表](https://github.com/lunny/xorm/blob/master/docs/COLUMNTYPE.md)。
|
||||
|
||||
具体的映射规则如下,另Tag中的关键字均不区分大小写,字段名区分大小写:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>name</td><td>当前field对应的字段的名称,可选,如不写,则自动根据field名字和转换规则命名</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>pk</td><td>是否是Primary Key,当前仅支持int64类型</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>当前支持30多种字段类型,详情参见 [字段类型](https://github.com/lunny/xorm/blob/master/docs/COLUMNTYPE.md)</td><td>字段类型</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>autoincr</td><td>是否是自增</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>[not ]null</td><td>是否可以为空</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>unique或unique(uniquename)</td><td>是否是唯一,如不加括号则该字段不允许重复;如加上括号,则括号中为联合唯一索引的名字,此时如果有另外一个或多个字段和本unique的uniquename相同,则这些uniquename相同的字段组成联合唯一索引</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>index或index(indexname)</td><td>是否是索引,如不加括号则该字段自身为索引,如加上括号,则括号中为联合索引的名字,此时如果有另外一个或多个字段和本index的indexname相同,则这些indexname相同的字段组成联合索引</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>extends</td><td>应用于一个匿名结构体之上,表示此匿名结构体的成员也映射到数据库中</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td><td>这个Field将不进行字段映射</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-></td><td>这个Field将只写入到数据库而不从数据库读取</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><-</td><td>这个Field将只从数据库读取,而不写入到数据库</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>created</td><td>This field will be filled in current time on insert</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>updated</td><td>This field will be filled in current time on insert or update</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>version</td><td>This field will be filled 1 on insert and autoincrement on update</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>default 0</td><td>设置默认值,紧跟的内容如果是Varchar等需要加上单引号</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
另外有如下几条自动映射的规则:
|
||||
|
||||
- 1.如果field名称为`Id`而且类型为`int64`的话,会被xorm视为主键,并且拥有自增属性。如果想用`Id`以外的名字做为主键名,可以在对应的Tag上加上`xorm:"pk"`来定义主键。
|
||||
|
||||
- 2.string类型默认映射为varchar(255),如果需要不同的定义,可以在tag中自定义
|
||||
|
||||
- 3.支持`type MyString string`等自定义的field,支持Slice, Map等field成员,这些成员默认存储为Text类型,并且默认将使用Json格式来序列化和反序列化。也支持数据库字段类型为Blob类型,如果是Blob类型,则先使用Json格式序列化再转成[]byte格式。当然[]byte或者[]uint8默认为Blob类型并且都以二进制方式存储。
|
||||
|
||||
- 4.实现了Conversion接口的类型或者结构体,将根据接口的转换方式在类型和数据库记录之间进行相互转换。
|
||||
```Go
|
||||
type Conversion interface {
|
||||
FromDB([]byte) error
|
||||
ToDB() ([]byte, error)
|
||||
}
|
||||
```
|
||||
|
||||
<a name="30" id="30"></a>
|
||||
## 3.表结构操作
|
||||
|
||||
xorm提供了一些动态获取和修改表结构的方法。对于一般的应用,很少动态修改表结构,则只需调用Sync()同步下表结构即可。
|
||||
|
||||
<a name="31" id="31"></a>
|
||||
## 3.1 获取数据库信息
|
||||
|
||||
* DBMetas()
|
||||
xorm支持获取表结构信息,通过调用`engine.DBMetas()`可以获取到所有的表的信息
|
||||
|
||||
<a name="31" id="31"></a>
|
||||
## 3.2.表操作
|
||||
|
||||
* CreateTables()
|
||||
创建表使用`engine.CreateTables()`,参数为一个或多个空的对应Struct的指针。同时可用的方法有Charset()和StoreEngine(),如果对应的数据库支持,这两个方法可以在创建表时指定表的字符编码和使用的引擎。当前仅支持Mysql数据库。
|
||||
|
||||
* IsTableEmpty()
|
||||
判断表是否为空,参数和CreateTables相同
|
||||
|
||||
* IsTableExist()
|
||||
判断表是否存在
|
||||
|
||||
* DropTables()
|
||||
删除表使用`engine.DropTables()`,参数为一个或多个空的对应Struct的指针或者表的名字。如果为string传入,则只删除对应的表,如果传入的为Struct,则删除表的同时还会删除对应的索引。
|
||||
|
||||
<a name="32" id="32"></a>
|
||||
## 3.3.创建索引和唯一索引
|
||||
|
||||
* CreateIndexes
|
||||
根据struct中的tag来创建索引
|
||||
|
||||
* CreateUniques
|
||||
根据struct中的tag来创建唯一索引
|
||||
|
||||
<a name="34" id="34"></a>
|
||||
## 3.4.同步数据库结构
|
||||
|
||||
同步能够部分智能的根据结构体的变动检测表结构的变动,并自动同步。目前能够实现:
|
||||
1) 自动检测和创建表,这个检测是根据表的名字
|
||||
2)自动检测和新增表中的字段,这个检测是根据字段名
|
||||
3)自动检测和创建索引和唯一索引,这个检测是根据一个或多个字段名,而不根据索引名称
|
||||
|
||||
调用方法如下:
|
||||
```Go
|
||||
err := engine.Sync(new(User))
|
||||
```
|
||||
|
||||
<a name="50" id="50"></a>
|
||||
## 4.插入数据
|
||||
|
||||
Inserting records use Insert method.
|
||||
|
||||
* Insert one record
|
||||
```Go
|
||||
user := new(User)
|
||||
user.Name = "myname"
|
||||
affected, err := engine.Insert(user)
|
||||
```
|
||||
|
||||
After inseted, `user.Id` will be filled with primary key column value.
|
||||
```Go
|
||||
fmt.Println(user.Id)
|
||||
```
|
||||
|
||||
* Insert multiple records by Slice on one table
|
||||
```Go
|
||||
users := make([]User, 0)
|
||||
users[0].Name = "name0"
|
||||
...
|
||||
affected, err := engine.Insert(&users)
|
||||
```
|
||||
|
||||
* Insert multiple records by Slice of pointer on one table
|
||||
```Go
|
||||
users := make([]*User, 0)
|
||||
users[0] = new(User)
|
||||
users[0].Name = "name0"
|
||||
...
|
||||
affected, err := engine.Insert(&users)
|
||||
```
|
||||
|
||||
* Insert one record on two table.
|
||||
```Go
|
||||
user := new(User)
|
||||
user.Name = "myname"
|
||||
question := new(Question)
|
||||
question.Content = "whywhywhwy?"
|
||||
affected, err := engine.Insert(user, question)
|
||||
```
|
||||
|
||||
* Insert multiple records on multiple tables.
|
||||
```Go
|
||||
users := make([]User, 0)
|
||||
users[0].Name = "name0"
|
||||
...
|
||||
questions := make([]Question, 0)
|
||||
questions[0].Content = "whywhywhwy?"
|
||||
affected, err := engine.Insert(&users, &questions)
|
||||
```
|
||||
|
||||
* Insert one or multple records on multiple tables.
|
||||
```Go
|
||||
user := new(User)
|
||||
user.Name = "myname"
|
||||
...
|
||||
questions := make([]Question, 0)
|
||||
questions[0].Content = "whywhywhwy?"
|
||||
affected, err := engine.Insert(user, &questions)
|
||||
```
|
||||
|
||||
Notice: If you want to use transaction on inserting, you should use session.Begin() before calling Insert.
|
||||
|
||||
<a name="60" id="60"></a>
|
||||
## 5.Query and count
|
||||
|
||||
所有的查询条件不区分调用顺序,但必须在调用Get,Find,Count这三个函数之前调用。同时需要注意的一点是,在调用的参数中,所有的字符字段名均为映射后的数据库的字段名,而不是field的名字。
|
||||
|
||||
<a name="61" id="61"></a>
|
||||
### 5.1.查询条件方法
|
||||
|
||||
查询和统计主要使用`Get`, `Find`, `Count`三个方法。在进行查询时可以使用多个方法来形成查询条件,条件函数如下:
|
||||
|
||||
* Id(int64)
|
||||
传入一个PK字段的值,作为查询条件
|
||||
|
||||
* Where(string, …interface{})
|
||||
和Where语句中的条件基本相同,作为条件
|
||||
|
||||
* And(string, …interface{})
|
||||
和Where函数中的条件基本相同,作为条件
|
||||
|
||||
* Or(string, …interface{})
|
||||
和Where函数中的条件基本相同,作为条件
|
||||
|
||||
* Sql(string, …interface{})
|
||||
执行指定的Sql语句,并把结果映射到结构体
|
||||
|
||||
* Asc(…string)
|
||||
指定字段名正序排序
|
||||
|
||||
* Desc(…string)
|
||||
指定字段名逆序排序
|
||||
|
||||
* OrderBy(string)
|
||||
按照指定的顺序进行排序
|
||||
|
||||
* In(string, …interface{})
|
||||
某字段在一些值中
|
||||
|
||||
* Cols(…string)
|
||||
只查询或更新某些指定的字段,默认是查询所有映射的字段或者根据Update的第一个参数来判断更新的字段。例如:
|
||||
```Go
|
||||
engine.Cols("age", "name").Find(&users)
|
||||
// SELECT age, name FROM user
|
||||
engine.Cols("age", "name").Update(&user)
|
||||
// UPDATE user SET age=? AND name=?
|
||||
```
|
||||
|
||||
其中的参数"age", "name"也可以写成"age, name",两种写法均可
|
||||
|
||||
* Omit(...string)
|
||||
和cols相反,此函数指定排除某些指定的字段。注意:此方法和Cols方法不可同时使用
|
||||
```Go
|
||||
engine.Cols("age").Update(&user)
|
||||
// UPDATE user SET name = ? AND department = ?
|
||||
```
|
||||
|
||||
* Distinct(…string)
|
||||
按照参数中指定的字段归类结果
|
||||
```Go
|
||||
engine.Distinct("age", "department").Find(&users)
|
||||
// SELECT DISTINCT age, department FROM user
|
||||
```
|
||||
注意:当开启了缓存时,此方法的调用将在当前查询中禁用缓存。因为缓存系统当前依赖Id,而此时无法获得Id
|
||||
|
||||
* Table(nameOrStructPtr interface{})
|
||||
传入表名称或者结构体指针,如果传入的是结构体指针,则按照IMapper的规则提取出表名
|
||||
|
||||
* Limit(int, …int)
|
||||
限制获取的数目,第一个参数为条数,第二个参数为可选,表示开始位置
|
||||
|
||||
* Top(int)
|
||||
相当于Limit(int, 0)
|
||||
|
||||
* Join(string,string,string)
|
||||
第一个参数为连接类型,当前支持INNER, LEFT OUTER, CROSS中的一个值,第二个参数为表名,第三个参数为连接条件
|
||||
|
||||
* GroupBy(string)
|
||||
Groupby的参数字符串
|
||||
|
||||
* Having(string)
|
||||
Having的参数字符串
|
||||
|
||||
<a name="62" id="62"></a>
|
||||
### 5.2.临时开关方法
|
||||
|
||||
* NoAutoTime()
|
||||
如果此方法执行,则此次生成的语句中Created和Updated字段将不自动赋值为当前时间
|
||||
|
||||
* NoCache()
|
||||
如果此方法执行,则此次生成的语句则在非缓存模式下执行
|
||||
|
||||
* UseBool(...string)
|
||||
当从一个struct来生成查询条件或更新字段时,xorm会判断struct的field是否为0,"",nil,如果为以上则不当做查询条件或者更新内容。因为bool类型只有true和false两种值,因此默认所有bool类型不会作为查询条件或者更新字段。如果可以使用此方法,如果默认不传参数,则所有的bool字段都将会被使用,如果参数不为空,则参数中指定的为字段名,则这些字段对应的bool值将被使用。
|
||||
|
||||
* Cascade(bool)
|
||||
是否自动关联查询field中的数据,如果struct的field也是一个struct并且映射为某个Id,则可以在查询时自动调用Get方法查询出对应的数据。
|
||||
|
||||
<a name="50" id="50"></a>
|
||||
### 5.3.Get one record
|
||||
Fetch a single object by user
|
||||
|
||||
```Go
|
||||
var user = User{Id:27}
|
||||
has, err := engine.Get(&user)
|
||||
// or has, err := engine.Id(27).Get(&user)
|
||||
|
||||
var user = User{Name:"xlw"}
|
||||
has, err := engine.Get(&user)
|
||||
```
|
||||
|
||||
<a name="60" id="60"></a>
|
||||
### 5.4.Find
|
||||
Fetch multipe objects into a slice or a map, use Find:
|
||||
|
||||
```Go
|
||||
var everyone []Userinfo
|
||||
err := engine.Find(&everyone)
|
||||
|
||||
users := make(map[int64]Userinfo)
|
||||
err := engine.Find(&users)
|
||||
```
|
||||
|
||||
* also you can use Where, Limit
|
||||
|
||||
```Go
|
||||
var allusers []Userinfo
|
||||
err := engine.Where("id > ?", "3").Limit(10,20).Find(&allusers) //Get id>3 limit 10 offset 20
|
||||
```
|
||||
|
||||
* or you can use a struct query
|
||||
|
||||
```Go
|
||||
var tenusers []Userinfo
|
||||
err := engine.Limit(10).Find(&tenusers, &Userinfo{Name:"xlw"}) //Get All Name="xlw" limit 10 offset 0
|
||||
```
|
||||
|
||||
* or In function
|
||||
|
||||
```Go
|
||||
var tenusers []Userinfo
|
||||
err := engine.In("id", 1, 3, 5).Find(&tenusers) //Get All id in (1, 3, 5)
|
||||
```
|
||||
|
||||
* The default will query all columns of a table. Use Cols function if you want to select some columns
|
||||
|
||||
```Go
|
||||
var tenusers []Userinfo
|
||||
err := engine.Cols("id", "name").Find(&tenusers) //Find only id and name
|
||||
```
|
||||
|
||||
<a name="70" id="70"></a>
|
||||
### 5.5.Iterate records
|
||||
Iterate, like find, but handle records one by one
|
||||
|
||||
```Go
|
||||
err := engine.Where("age > ? or name=?)", 30, "xlw").Iterate(new(Userinfo), func(i int, bean interface{})error{
|
||||
user := bean.(*Userinfo)
|
||||
//do somthing use i and user
|
||||
})
|
||||
```
|
||||
|
||||
<a name="66" id="66"></a>
|
||||
### 5.6.Count方法
|
||||
|
||||
统计数据使用`Count`方法,Count方法的参数为struct的指针并且成为查询条件。
|
||||
```Go
|
||||
user := new(User)
|
||||
total, err := engine.Where("id >?", 1).Count(user)
|
||||
```
|
||||
|
||||
<a name="70" id="70"></a>
|
||||
## 6.更新数据
|
||||
|
||||
更新数据使用`Update`方法,Update方法的第一个参数为需要更新的内容,可以为一个结构体指针或者一个Map[string]interface{}类型。当传入的为结构体指针时,只有非空和0的field才会被作为更新的字段。当传入的为Map类型时,key为数据库Column的名字,value为要更新的内容。
|
||||
|
||||
```Go
|
||||
user := new(User)
|
||||
user.Name = "myname"
|
||||
affected, err := engine.Id(id).Update(user)
|
||||
```
|
||||
|
||||
这里需要注意,Update会自动从user结构体中提取非0和非nil得值作为需要更新的内容,因此,如果需要更新一个值为0,则此种方法将无法实现,因此有两种选择:
|
||||
|
||||
1. 通过添加Cols函数指定需要更新结构体中的哪些值,未指定的将不更新,指定了的即使为0也会更新。
|
||||
```Go
|
||||
affected, err := engine.Id(id).Cols("age").Update(&user)
|
||||
```
|
||||
|
||||
2. 通过传入map[string]interface{}来进行更新,但这时需要额外指定更新到哪个表,因为通过map是无法自动检测更新哪个表的。
|
||||
```Go
|
||||
affected, err := engine.Table(new(User)).Id(id).Update(map[string]interface{}{"age":0})
|
||||
```
|
||||
|
||||
|
||||
### 6.1.乐观锁
|
||||
|
||||
要使用乐观锁,需要使用version标记
|
||||
type User struct {
|
||||
Id int64
|
||||
Name string
|
||||
Version int `xorm:"version"`
|
||||
}
|
||||
|
||||
在Insert时,version标记的字段将会被设置为1,在Update时,Update的内容必须包含version原来的值。
|
||||
|
||||
```Go
|
||||
var user User
|
||||
engine.Id(1).Get(&user)
|
||||
// SELECT * FROM user WHERE id = ?
|
||||
engine.Id(1).Update(&user)
|
||||
// UPDATE user SET ..., version = version + 1 WHERE id = ? AND version = ?
|
||||
```
|
||||
|
||||
|
||||
<a name="80" id="80"></a>
|
||||
## 7.Delete one or more records
|
||||
Delete one or more records
|
||||
|
||||
* delete by id
|
||||
|
||||
```Go
|
||||
err := engine.Id(1).Delete(&User{})
|
||||
```
|
||||
|
||||
* delete by other conditions
|
||||
|
||||
```Go
|
||||
err := engine.Delete(&User{Name:"xlw"})
|
||||
```
|
||||
|
||||
<a name="90" id="90"></a>
|
||||
## 8.Execute SQL query
|
||||
|
||||
Of course, SQL execution is also provided.
|
||||
|
||||
If select then use Query
|
||||
|
||||
```Go
|
||||
sql := "select * from userinfo"
|
||||
results, err := engine.Query(sql)
|
||||
```
|
||||
|
||||
<a name="100" id="100"></a>
|
||||
## 9.Execute SQL command
|
||||
If insert, update or delete then use Exec
|
||||
|
||||
```Go
|
||||
sql = "update userinfo set username=? where id=?"
|
||||
res, err := engine.Exec(sql, "xiaolun", 1)
|
||||
```
|
||||
|
||||
<a name="110" id="110"></a>
|
||||
## 10.Transaction
|
||||
|
||||
```Go
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
// add Begin() before any action
|
||||
err := session.Begin()
|
||||
user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()}
|
||||
_, err = session.Insert(&user1)
|
||||
if err != nil {
|
||||
session.Rollback()
|
||||
return
|
||||
}
|
||||
user2 := Userinfo{Username: "yyy"}
|
||||
_, err = session.Where("id = ?", 2).Update(&user2)
|
||||
if err != nil {
|
||||
session.Rollback()
|
||||
return
|
||||
}
|
||||
|
||||
_, err = session.Exec("delete from userinfo where username = ?", user2.Username)
|
||||
if err != nil {
|
||||
session.Rollback()
|
||||
return
|
||||
}
|
||||
|
||||
// add Commit() after all actions
|
||||
err = session.Commit()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
<a name="120" id="120"></a>
|
||||
## 11.缓存
|
||||
|
||||
1. Global Cache
|
||||
Xorm implements cache support. Defaultly, it's disabled. If enable it, use below code.
|
||||
|
||||
```Go
|
||||
cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000)
|
||||
engine.SetDefaultCacher(cacher)
|
||||
```
|
||||
|
||||
If disable some tables' cache, then:
|
||||
|
||||
```Go
|
||||
engine.MapCacher(&user, nil)
|
||||
```
|
||||
|
||||
2. Table's Cache
|
||||
If only some tables need cache, then:
|
||||
|
||||
```Go
|
||||
cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000)
|
||||
engine.MapCacher(&user, cacher)
|
||||
```
|
||||
|
||||
Caution:
|
||||
|
||||
1. When use Cols methods on cache enabled, the system still return all the columns.
|
||||
|
||||
2. When using Exec method, you should clear cache:
|
||||
|
||||
```Go
|
||||
engine.Exec("update user set name = ? where id = ?", "xlw", 1)
|
||||
engine.ClearCache(new(User))
|
||||
```
|
||||
|
||||
Cache implement theory below:
|
||||
|
||||

|
||||
|
||||
<a name="130" id="130"></a>
|
||||
## 12.xorm tool
|
||||
xorm工具提供了xorm命令,能够帮助做很多事情。
|
||||
|
||||
### 12.1.Reverse command
|
||||
Please visit [xorm tool](https://github.com/lunny/xorm/tree/master/xorm)
|
||||
|
||||
<a name="140" id="140"></a>
|
||||
## 13.Examples
|
||||
|
||||
请访问[https://github.com/lunny/xorm/tree/master/examples](https://github.com/lunny/xorm/tree/master/examples)
|
||||
|
||||
<a name="150" id="150"></a>
|
||||
## 14.Cases
|
||||
|
||||
* [Gowalker](http://gowalker.org),source [github.com/Unknwon/gowalker](http://github.com/Unknwon/gowalker)
|
||||
|
||||
* [GoDaily](http://godaily.org),source [github.com/govc/godaily](http://github.com/govc/godaily)
|
||||
|
||||
* [Sudochina](http://sudochina.com) source [github.com/insionng/toropress](http://github.com/insionng/toropress)
|
||||
|
||||
* [VeryHour](http://veryhour.com)
|
||||
|
||||
<a name="160"></a>
|
||||
## 15.FAQ
|
||||
|
||||
1.How the xorm tag use both with json?
|
||||
|
||||
Use space.
|
||||
|
||||
```Go
|
||||
type User struct {
|
||||
Name string `json:"name" xorm:"name"`
|
||||
}
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
393
engine.go
393
engine.go
|
|
@ -6,86 +6,59 @@ import (
|
|||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-xorm/core"
|
||||
)
|
||||
|
||||
const (
|
||||
POSTGRES = "postgres"
|
||||
SQLITE = "sqlite3"
|
||||
MYSQL = "mysql"
|
||||
MYMYSQL = "mymysql"
|
||||
|
||||
MSSQL = "mssql"
|
||||
|
||||
ORACLE_OCI = "oci8"
|
||||
QL = "ql"
|
||||
)
|
||||
|
||||
// a dialect is a driver's wrapper
|
||||
type dialect interface {
|
||||
Init(DriverName, DataSourceName string) error
|
||||
URI() *uri
|
||||
DBType() string
|
||||
SqlType(t *Column) string
|
||||
SupportInsertMany() bool
|
||||
QuoteStr() string
|
||||
RollBackStr() string
|
||||
DropTableSql(tableName string) string
|
||||
AutoIncrStr() string
|
||||
SupportEngine() bool
|
||||
SupportCharset() bool
|
||||
IndexOnTable() bool
|
||||
IndexCheckSql(tableName, idxName string) (string, []interface{})
|
||||
TableCheckSql(tableName string) (string, []interface{})
|
||||
ColumnCheckSql(tableName, colName string) (string, []interface{})
|
||||
|
||||
GetColumns(tableName string) ([]string, map[string]*Column, error)
|
||||
GetTables() ([]*Table, error)
|
||||
GetIndexes(tableName string) (map[string]*Index, error)
|
||||
}
|
||||
|
||||
type PK []interface{}
|
||||
|
||||
// Engine is the major struct of xorm, it means a database manager.
|
||||
// Commonly, an application only need one engine
|
||||
type Engine struct {
|
||||
columnMapper IMapper
|
||||
tableMapper IMapper
|
||||
TagIdentifier string
|
||||
DriverName string
|
||||
DataSourceName string
|
||||
dialect dialect
|
||||
Tables map[reflect.Type]*Table
|
||||
mutex *sync.RWMutex
|
||||
ShowSQL bool
|
||||
ShowErr bool
|
||||
ShowDebug bool
|
||||
ShowWarn bool
|
||||
Pool IConnectPool
|
||||
Filters []Filter
|
||||
Logger io.Writer
|
||||
Cacher Cacher
|
||||
UseCache bool
|
||||
TimeZone string
|
||||
db *core.DB
|
||||
dialect core.Dialect
|
||||
|
||||
ColumnMapper core.IMapper
|
||||
TableMapper core.IMapper
|
||||
TagIdentifier string
|
||||
Tables map[reflect.Type]*core.Table
|
||||
|
||||
mutex *sync.RWMutex
|
||||
Cacher core.Cacher
|
||||
|
||||
ShowSQL bool
|
||||
ShowErr bool
|
||||
ShowDebug bool
|
||||
ShowWarn bool
|
||||
//Pool IConnectPool
|
||||
//Filters []core.Filter
|
||||
Logger ILogger // io.Writer
|
||||
TZLocation *time.Location
|
||||
}
|
||||
|
||||
func (engine *Engine) SetMapper(mapper IMapper) {
|
||||
func (engine *Engine) DriverName() string {
|
||||
return engine.dialect.DriverName()
|
||||
}
|
||||
|
||||
func (engine *Engine) DataSourceName() string {
|
||||
return engine.dialect.DataSourceName()
|
||||
}
|
||||
|
||||
func (engine *Engine) SetMapper(mapper core.IMapper) {
|
||||
engine.SetTableMapper(mapper)
|
||||
engine.SetColumnMapper(mapper)
|
||||
}
|
||||
|
||||
func (engine *Engine) SetTableMapper(mapper IMapper) {
|
||||
engine.tableMapper = mapper
|
||||
func (engine *Engine) SetTableMapper(mapper core.IMapper) {
|
||||
engine.TableMapper = mapper
|
||||
}
|
||||
|
||||
func (engine *Engine) SetColumnMapper(mapper IMapper) {
|
||||
engine.columnMapper = mapper
|
||||
func (engine *Engine) SetColumnMapper(mapper core.IMapper) {
|
||||
engine.ColumnMapper = mapper
|
||||
}
|
||||
|
||||
// If engine's database support batch insert records like
|
||||
|
|
@ -104,11 +77,17 @@ func (engine *Engine) QuoteStr() string {
|
|||
|
||||
// Use QuoteStr quote the string sql
|
||||
func (engine *Engine) Quote(sql string) string {
|
||||
if len(sql) == 0 {
|
||||
return sql
|
||||
}
|
||||
if string(sql[0]) == engine.dialect.QuoteStr() || sql[0] == '`' {
|
||||
return sql
|
||||
}
|
||||
return engine.dialect.QuoteStr() + sql + engine.dialect.QuoteStr()
|
||||
}
|
||||
|
||||
// A simple wrapper to dialect's SqlType method
|
||||
func (engine *Engine) SqlType(c *Column) string {
|
||||
// A simple wrapper to dialect's core.SqlType method
|
||||
func (engine *Engine) SqlType(c *core.Column) string {
|
||||
return engine.dialect.SqlType(c)
|
||||
}
|
||||
|
||||
|
|
@ -117,30 +96,24 @@ func (engine *Engine) AutoIncrStr() string {
|
|||
return engine.dialect.AutoIncrStr()
|
||||
}
|
||||
|
||||
// Set engine's pool, the pool default is Go's standard library's connection pool.
|
||||
func (engine *Engine) SetPool(pool IConnectPool) error {
|
||||
engine.Pool = pool
|
||||
return engine.Pool.Init(engine)
|
||||
// SetMaxOpenConns is only available for go 1.2+
|
||||
func (engine *Engine) SetMaxOpenConns(conns int) {
|
||||
engine.db.SetMaxOpenConns(conns)
|
||||
}
|
||||
|
||||
// SetMaxConns is only available for go 1.2+
|
||||
// @Deprecated
|
||||
func (engine *Engine) SetMaxConns(conns int) {
|
||||
engine.Pool.SetMaxConns(conns)
|
||||
engine.SetMaxOpenConns(conns)
|
||||
}
|
||||
|
||||
// SetMaxIdleConns
|
||||
func (engine *Engine) SetMaxIdleConns(conns int) {
|
||||
engine.Pool.SetMaxIdleConns(conns)
|
||||
engine.db.SetMaxIdleConns(conns)
|
||||
}
|
||||
|
||||
// SetDefaltCacher set the default cacher. Xorm's default not enable cacher.
|
||||
func (engine *Engine) SetDefaultCacher(cacher Cacher) {
|
||||
if cacher == nil {
|
||||
engine.UseCache = false
|
||||
} else {
|
||||
engine.UseCache = true
|
||||
engine.Cacher = cacher
|
||||
}
|
||||
func (engine *Engine) SetDefaultCacher(cacher core.Cacher) {
|
||||
engine.Cacher = cacher
|
||||
}
|
||||
|
||||
// If you has set default cacher, and you want temporilly stop use cache,
|
||||
|
|
@ -158,15 +131,23 @@ func (engine *Engine) NoCascade() *Session {
|
|||
}
|
||||
|
||||
// Set a table use a special cacher
|
||||
func (engine *Engine) MapCacher(bean interface{}, cacher Cacher) {
|
||||
func (engine *Engine) MapCacher(bean interface{}, cacher core.Cacher) {
|
||||
v := rValue(bean)
|
||||
engine.autoMapType(v)
|
||||
engine.Tables[v.Type()].Cacher = cacher
|
||||
}
|
||||
|
||||
// OpenDB provides a interface to operate database directly.
|
||||
func (engine *Engine) OpenDB() (*sql.DB, error) {
|
||||
return sql.Open(engine.DriverName, engine.DataSourceName)
|
||||
// NewDB provides an interface to operate database directly
|
||||
func (engine *Engine) NewDB() (*core.DB, error) {
|
||||
return core.OpenDialect(engine.dialect)
|
||||
}
|
||||
|
||||
func (engine *Engine) DB() *core.DB {
|
||||
return engine.db
|
||||
}
|
||||
|
||||
func (engine *Engine) Dialect() core.Dialect {
|
||||
return engine.dialect
|
||||
}
|
||||
|
||||
// New a session
|
||||
|
|
@ -178,42 +159,51 @@ func (engine *Engine) NewSession() *Session {
|
|||
|
||||
// Close the engine
|
||||
func (engine *Engine) Close() error {
|
||||
return engine.Pool.Close(engine)
|
||||
return engine.db.Close()
|
||||
}
|
||||
|
||||
// Ping tests if database is alive.
|
||||
// Ping tests if database is alive
|
||||
func (engine *Engine) Ping() error {
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
engine.LogSQL("PING DATABASE", engine.DriverName)
|
||||
engine.LogInfo("PING DATABASE", engine.DriverName)
|
||||
return session.Ping()
|
||||
}
|
||||
|
||||
// logging sql
|
||||
func (engine *Engine) LogSQL(contents ...interface{}) {
|
||||
func (engine *Engine) logSQL(sqlStr string, sqlArgs ...interface{}) {
|
||||
if engine.ShowSQL {
|
||||
io.WriteString(engine.Logger, fmt.Sprintln(contents...))
|
||||
if len(sqlArgs) > 0 {
|
||||
engine.LogInfo("[sql]", sqlStr, "[args]", sqlArgs)
|
||||
} else {
|
||||
engine.LogInfo("[sql]", sqlStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// logging error
|
||||
func (engine *Engine) LogError(contents ...interface{}) {
|
||||
if engine.ShowErr {
|
||||
io.WriteString(engine.Logger, fmt.Sprintln(contents...))
|
||||
engine.Logger.Err(fmt.Sprintln(contents...))
|
||||
}
|
||||
}
|
||||
|
||||
// logging error
|
||||
func (engine *Engine) LogInfo(contents ...interface{}) {
|
||||
engine.Logger.Info(fmt.Sprintln(contents...))
|
||||
}
|
||||
|
||||
// logging debug
|
||||
func (engine *Engine) LogDebug(contents ...interface{}) {
|
||||
if engine.ShowDebug {
|
||||
io.WriteString(engine.Logger, fmt.Sprintln(contents...))
|
||||
engine.Logger.Debug(fmt.Sprintln(contents...))
|
||||
}
|
||||
}
|
||||
|
||||
// logging warn
|
||||
func (engine *Engine) LogWarn(contents ...interface{}) {
|
||||
if engine.ShowWarn {
|
||||
io.WriteString(engine.Logger, fmt.Sprintln(contents...))
|
||||
engine.Logger.Warning(fmt.Sprintln(contents...))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -240,7 +230,7 @@ func (engine *Engine) NoAutoTime() *Session {
|
|||
}
|
||||
|
||||
// Retrieve all tables, columns, indexes' informations from database.
|
||||
func (engine *Engine) DBMetas() ([]*Table, error) {
|
||||
func (engine *Engine) DBMetas() ([]*core.Table, error) {
|
||||
tables, err := engine.dialect.GetTables()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -251,8 +241,11 @@ func (engine *Engine) DBMetas() ([]*Table, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
table.Columns = cols
|
||||
table.ColumnsSeq = colSeq
|
||||
for _, name := range colSeq {
|
||||
table.AddColumn(cols[name])
|
||||
}
|
||||
//table.Columns = cols
|
||||
//table.ColumnsSeq = colSeq
|
||||
|
||||
indexes, err := engine.dialect.GetIndexes(table.Name)
|
||||
if err != nil {
|
||||
|
|
@ -262,10 +255,10 @@ func (engine *Engine) DBMetas() ([]*Table, error) {
|
|||
|
||||
for _, index := range indexes {
|
||||
for _, name := range index.Cols {
|
||||
if col, ok := table.Columns[name]; ok {
|
||||
if col := table.GetColumn(name); col != nil {
|
||||
col.Indexes[index.Name] = true
|
||||
} else {
|
||||
return nil, fmt.Errorf("Unknown col "+name+" in indexes %v", table.Columns)
|
||||
return nil, fmt.Errorf("Unknown col "+name+" in indexes %v", index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -375,6 +368,13 @@ func (engine *Engine) In(column string, args ...interface{}) *Session {
|
|||
return session.In(column, args...)
|
||||
}
|
||||
|
||||
// Method Inc provides a update string like "column = column + ?"
|
||||
func (engine *Engine) Incr(column string, arg ...interface{}) *Session {
|
||||
session := engine.NewSession()
|
||||
session.IsAutoClose = true
|
||||
return session.Incr(column, arg...)
|
||||
}
|
||||
|
||||
// Temporarily change the Get, Find, Update's table
|
||||
func (engine *Engine) Table(tableNameOrBean interface{}) *Session {
|
||||
session := engine.NewSession()
|
||||
|
|
@ -437,7 +437,7 @@ func (engine *Engine) Having(conditions string) *Session {
|
|||
return session.Having(conditions)
|
||||
}
|
||||
|
||||
func (engine *Engine) autoMapType(v reflect.Value) *Table {
|
||||
func (engine *Engine) autoMapType(v reflect.Value) *core.Table {
|
||||
t := v.Type()
|
||||
engine.mutex.RLock()
|
||||
table, ok := engine.Tables[t]
|
||||
|
|
@ -451,34 +451,39 @@ func (engine *Engine) autoMapType(v reflect.Value) *Table {
|
|||
return table
|
||||
}
|
||||
|
||||
func (engine *Engine) autoMap(bean interface{}) *Table {
|
||||
func (engine *Engine) autoMap(bean interface{}) *core.Table {
|
||||
v := rValue(bean)
|
||||
return engine.autoMapType(v)
|
||||
}
|
||||
|
||||
func (engine *Engine) newTable() *Table {
|
||||
table := &Table{}
|
||||
table.Indexes = make(map[string]*Index)
|
||||
table.Columns = make(map[string]*Column)
|
||||
table.ColumnsSeq = make([]string, 0)
|
||||
table.Created = make(map[string]bool)
|
||||
table.Cacher = engine.Cacher
|
||||
return table
|
||||
}
|
||||
/*func (engine *Engine) mapType(t reflect.Type) *core.Table {
|
||||
return mappingTable(t, engine.TableMapper, engine.ColumnMapper, engine.dialect, engine.TagIdentifier)
|
||||
}*/
|
||||
|
||||
func addIndex(indexName string, table *Table, col *Column, indexType int) {
|
||||
/*
|
||||
func mappingTable(t reflect.Type, tableMapper core.IMapper, colMapper core.IMapper, dialect core.Dialect, tagId string) *core.Table {
|
||||
table := core.NewEmptyTable()
|
||||
table.Name = tableMapper.Obj2Table(t.Name())
|
||||
*/
|
||||
func addIndex(indexName string, table *core.Table, col *core.Column, indexType int) {
|
||||
if index, ok := table.Indexes[indexName]; ok {
|
||||
index.AddColumn(col.Name)
|
||||
col.Indexes[index.Name] = true
|
||||
} else {
|
||||
index := NewIndex(indexName, indexType)
|
||||
index := core.NewIndex(indexName, indexType)
|
||||
index.AddColumn(col.Name)
|
||||
table.AddIndex(index)
|
||||
col.Indexes[index.Name] = true
|
||||
}
|
||||
}
|
||||
|
||||
func (engine *Engine) mapType(v reflect.Value) *Table {
|
||||
func (engine *Engine) newTable() *core.Table {
|
||||
table := core.NewEmptyTable()
|
||||
table.Cacher = engine.Cacher
|
||||
return table
|
||||
}
|
||||
|
||||
func (engine *Engine) mapType(v reflect.Value) *core.Table {
|
||||
t := v.Type()
|
||||
table := engine.newTable()
|
||||
method := v.MethodByName("TableName")
|
||||
|
|
@ -496,7 +501,7 @@ func (engine *Engine) mapType(v reflect.Value) *Table {
|
|||
}
|
||||
|
||||
if table.Name == "" {
|
||||
table.Name = engine.tableMapper.Obj2Table(t.Name())
|
||||
table.Name = engine.TableMapper.Obj2Table(t.Name())
|
||||
}
|
||||
table.Type = t
|
||||
|
||||
|
|
@ -506,13 +511,13 @@ func (engine *Engine) mapType(v reflect.Value) *Table {
|
|||
for i := 0; i < t.NumField(); i++ {
|
||||
tag := t.Field(i).Tag
|
||||
ormTagStr := tag.Get(engine.TagIdentifier)
|
||||
var col *Column
|
||||
var col *core.Column
|
||||
fieldValue := v.Field(i)
|
||||
fieldType := fieldValue.Type()
|
||||
|
||||
if ormTagStr != "" {
|
||||
col = &Column{FieldName: t.Field(i).Name, Nullable: true, IsPrimaryKey: false,
|
||||
IsAutoIncrement: false, MapType: TWOSIDES, Indexes: make(map[string]bool)}
|
||||
col = &core.Column{FieldName: t.Field(i).Name, Nullable: true, IsPrimaryKey: false,
|
||||
IsAutoIncrement: false, MapType: core.TWOSIDES, Indexes: make(map[string]bool)}
|
||||
tags := strings.Split(ormTagStr, " ")
|
||||
|
||||
if len(tags) > 0 {
|
||||
|
|
@ -521,14 +526,14 @@ func (engine *Engine) mapType(v reflect.Value) *Table {
|
|||
}
|
||||
if (strings.ToUpper(tags[0]) == "EXTENDS") &&
|
||||
(fieldType.Kind() == reflect.Struct) {
|
||||
|
||||
//parentTable := mappingTable(fieldType, tableMapper, colMapper, dialect, tagId)
|
||||
parentTable := engine.mapType(fieldValue)
|
||||
for name, col := range parentTable.Columns {
|
||||
for _, col := range parentTable.Columns() {
|
||||
col.FieldName = fmt.Sprintf("%v.%v", fieldType.Name(), col.FieldName)
|
||||
table.Columns[strings.ToLower(name)] = col
|
||||
table.ColumnsSeq = append(table.ColumnsSeq, name)
|
||||
table.AddColumn(col)
|
||||
}
|
||||
|
||||
table.PrimaryKeys = parentTable.PrimaryKeys
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -539,9 +544,9 @@ func (engine *Engine) mapType(v reflect.Value) *Table {
|
|||
k := strings.ToUpper(key)
|
||||
switch {
|
||||
case k == "<-":
|
||||
col.MapType = ONLYFROMDB
|
||||
col.MapType = core.ONLYFROMDB
|
||||
case k == "->":
|
||||
col.MapType = ONLYTODB
|
||||
col.MapType = core.ONLYTODB
|
||||
case k == "PK":
|
||||
col.IsPrimaryKey = true
|
||||
col.Nullable = false
|
||||
|
|
@ -570,12 +575,12 @@ func (engine *Engine) mapType(v reflect.Value) *Table {
|
|||
col.IsUpdated = true
|
||||
case strings.HasPrefix(k, "INDEX(") && strings.HasSuffix(k, ")"):
|
||||
indexName := k[len("INDEX")+1 : len(k)-1]
|
||||
indexNames[indexName] = IndexType
|
||||
indexNames[indexName] = core.IndexType
|
||||
case k == "INDEX":
|
||||
isIndex = true
|
||||
case strings.HasPrefix(k, "UNIQUE(") && strings.HasSuffix(k, ")"):
|
||||
indexName := k[len("UNIQUE")+1 : len(k)-1]
|
||||
indexNames[indexName] = UniqueType
|
||||
indexNames[indexName] = core.UniqueType
|
||||
case k == "UNIQUE":
|
||||
isUnique = true
|
||||
case k == "NOTNULL":
|
||||
|
|
@ -588,11 +593,12 @@ func (engine *Engine) mapType(v reflect.Value) *Table {
|
|||
}
|
||||
} else if strings.Contains(k, "(") && strings.HasSuffix(k, ")") {
|
||||
fs := strings.Split(k, "(")
|
||||
if _, ok := sqlTypes[fs[0]]; !ok {
|
||||
|
||||
if _, ok := core.SqlTypes[fs[0]]; !ok {
|
||||
preKey = k
|
||||
continue
|
||||
}
|
||||
col.SQLType = SQLType{fs[0], 0, 0}
|
||||
col.SQLType = core.SQLType{fs[0], 0, 0}
|
||||
fs2 := strings.Split(fs[1][0:len(fs[1])-1], ",")
|
||||
if len(fs2) == 2 {
|
||||
col.Length, err = strconv.Atoi(fs2[0])
|
||||
|
|
@ -610,18 +616,18 @@ func (engine *Engine) mapType(v reflect.Value) *Table {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
if _, ok := sqlTypes[k]; ok {
|
||||
col.SQLType = SQLType{k, 0, 0}
|
||||
} else if preKey != "DEFAULT" {
|
||||
if _, ok := core.SqlTypes[k]; ok {
|
||||
col.SQLType = core.SQLType{k, 0, 0}
|
||||
} else if key != col.Default {
|
||||
col.Name = key
|
||||
}
|
||||
}
|
||||
engine.SqlType(col)
|
||||
engine.dialect.SqlType(col)
|
||||
}
|
||||
preKey = k
|
||||
}
|
||||
if col.SQLType.Name == "" {
|
||||
col.SQLType = Type2SQLType(fieldType)
|
||||
col.SQLType = core.Type2SQLType(fieldType)
|
||||
}
|
||||
if col.Length == 0 {
|
||||
col.Length = col.SQLType.DefaultLength
|
||||
|
|
@ -629,14 +635,15 @@ func (engine *Engine) mapType(v reflect.Value) *Table {
|
|||
if col.Length2 == 0 {
|
||||
col.Length2 = col.SQLType.DefaultLength2
|
||||
}
|
||||
//fmt.Println("======", col)
|
||||
if col.Name == "" {
|
||||
col.Name = engine.columnMapper.Obj2Table(t.Field(i).Name)
|
||||
col.Name = engine.ColumnMapper.Obj2Table(t.Field(i).Name)
|
||||
}
|
||||
|
||||
if isUnique {
|
||||
indexNames[col.Name] = UniqueType
|
||||
indexNames[col.Name] = core.UniqueType
|
||||
} else if isIndex {
|
||||
indexNames[col.Name] = IndexType
|
||||
indexNames[col.Name] = core.IndexType
|
||||
}
|
||||
|
||||
for indexName, indexType := range indexNames {
|
||||
|
|
@ -644,25 +651,10 @@ func (engine *Engine) mapType(v reflect.Value) *Table {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
sqlType := Type2SQLType(fieldType)
|
||||
col = &Column{
|
||||
Name: engine.columnMapper.Obj2Table(t.Field(i).Name),
|
||||
FieldName: t.Field(i).Name,
|
||||
SQLType: sqlType,
|
||||
Length: sqlType.DefaultLength,
|
||||
Length2: sqlType.DefaultLength2,
|
||||
Nullable: true,
|
||||
Default: "",
|
||||
Indexes: make(map[string]bool),
|
||||
IsPrimaryKey: false,
|
||||
IsAutoIncrement: false,
|
||||
MapType: TWOSIDES,
|
||||
IsCreated: false,
|
||||
IsUpdated: false,
|
||||
IsCascade: false,
|
||||
IsVersion: false,
|
||||
DefaultIsEmpty: false,
|
||||
}
|
||||
sqlType := core.Type2SQLType(fieldType)
|
||||
col = core.NewColumn(engine.ColumnMapper.Obj2Table(t.Field(i).Name),
|
||||
t.Field(i).Name, sqlType, sqlType.DefaultLength,
|
||||
sqlType.DefaultLength2, true)
|
||||
}
|
||||
if col.IsAutoIncrement {
|
||||
col.Nullable = false
|
||||
|
|
@ -676,7 +668,7 @@ func (engine *Engine) mapType(v reflect.Value) *Table {
|
|||
}
|
||||
|
||||
if idFieldColName != "" && len(table.PrimaryKeys) == 0 {
|
||||
col := table.Columns[strings.ToLower(idFieldColName)]
|
||||
col := table.GetColumn(idFieldColName)
|
||||
col.IsPrimaryKey = true
|
||||
col.IsAutoIncrement = true
|
||||
col.Nullable = false
|
||||
|
|
@ -725,6 +717,24 @@ func (engine *Engine) IsTableExist(bean interface{}) (bool, error) {
|
|||
return has, err
|
||||
}
|
||||
|
||||
func (engine *Engine) IdOf(bean interface{}) core.PK {
|
||||
table := engine.autoMap(bean)
|
||||
v := reflect.Indirect(reflect.ValueOf(bean))
|
||||
pk := make([]interface{}, len(table.PrimaryKeys))
|
||||
for i, col := range table.PKColumns() {
|
||||
pkField := v.FieldByName(col.FieldName)
|
||||
switch pkField.Kind() {
|
||||
case reflect.String:
|
||||
pk[i] = pkField.String()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
pk[i] = pkField.Int()
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
pk[i] = pkField.Uint()
|
||||
}
|
||||
}
|
||||
return core.PK(pk)
|
||||
}
|
||||
|
||||
// create indexes
|
||||
func (engine *Engine) CreateIndexes(bean interface{}) error {
|
||||
session := engine.NewSession()
|
||||
|
|
@ -739,16 +749,31 @@ func (engine *Engine) CreateUniques(bean interface{}) error {
|
|||
return session.CreateUniques(bean)
|
||||
}
|
||||
|
||||
func (engine *Engine) getCacher2(table *core.Table) core.Cacher {
|
||||
return table.Cacher
|
||||
}
|
||||
|
||||
func (engine *Engine) getCacher(v reflect.Value) core.Cacher {
|
||||
if table := engine.autoMapType(v); table != nil {
|
||||
return table.Cacher
|
||||
}
|
||||
return engine.Cacher
|
||||
}
|
||||
|
||||
// If enabled cache, clear the cache bean
|
||||
func (engine *Engine) ClearCacheBean(bean interface{}, id int64) error {
|
||||
func (engine *Engine) ClearCacheBean(bean interface{}, id string) error {
|
||||
t := rType(bean)
|
||||
if t.Kind() != reflect.Struct {
|
||||
return errors.New("error params")
|
||||
}
|
||||
table := engine.autoMap(bean)
|
||||
if table.Cacher != nil {
|
||||
table.Cacher.ClearIds(table.Name)
|
||||
table.Cacher.DelBean(table.Name, id)
|
||||
cacher := table.Cacher
|
||||
if cacher == nil {
|
||||
cacher = engine.Cacher
|
||||
}
|
||||
if cacher != nil {
|
||||
cacher.ClearIds(table.Name)
|
||||
cacher.DelBean(table.Name, id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -761,9 +786,13 @@ func (engine *Engine) ClearCache(beans ...interface{}) error {
|
|||
return errors.New("error params")
|
||||
}
|
||||
table := engine.autoMap(bean)
|
||||
if table.Cacher != nil {
|
||||
table.Cacher.ClearIds(table.Name)
|
||||
table.Cacher.ClearBeans(table.Name)
|
||||
cacher := table.Cacher
|
||||
if cacher == nil {
|
||||
cacher = engine.Cacher
|
||||
}
|
||||
if cacher != nil {
|
||||
cacher.ClearIds(table.Name)
|
||||
cacher.ClearBeans(table.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
@ -803,7 +832,7 @@ func (engine *Engine) Sync(beans ...interface{}) error {
|
|||
return err
|
||||
}
|
||||
} else {
|
||||
for _, col := range table.Columns {
|
||||
for _, col := range table.Columns() {
|
||||
session := engine.NewSession()
|
||||
session.Statement.RefTable = table
|
||||
defer session.Close()
|
||||
|
|
@ -826,7 +855,7 @@ func (engine *Engine) Sync(beans ...interface{}) error {
|
|||
session := engine.NewSession()
|
||||
session.Statement.RefTable = table
|
||||
defer session.Close()
|
||||
if index.Type == UniqueType {
|
||||
if index.Type == core.UniqueType {
|
||||
//isExist, err := session.isIndexExist(table.Name, name, true)
|
||||
isExist, err := session.isIndexExist2(table.Name, index.Cols, true)
|
||||
if err != nil {
|
||||
|
|
@ -841,7 +870,7 @@ func (engine *Engine) Sync(beans ...interface{}) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
} else if index.Type == IndexType {
|
||||
} else if index.Type == core.IndexType {
|
||||
isExist, err := session.isIndexExist2(table.Name, index.Cols, false)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -1074,33 +1103,8 @@ func (engine *Engine) Import(ddlPath string) ([]sql.Result, error) {
|
|||
return results, lastError
|
||||
}
|
||||
|
||||
func (engine *Engine) TZTime(t time.Time) (r time.Time) {
|
||||
if t.Location() == nil {
|
||||
return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(),
|
||||
t.Minute(), t.Second(), t.Nanosecond(), engine.TZLocation())
|
||||
}
|
||||
|
||||
switch engine.TimeZone {
|
||||
case "Local", "L":
|
||||
r = t.Local()
|
||||
case "UTC", "U":
|
||||
fallthrough
|
||||
default:
|
||||
r = t.UTC()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (engine *Engine) TZLocation() (r *time.Location) {
|
||||
switch engine.TimeZone {
|
||||
case "Local", "L":
|
||||
r = time.Local
|
||||
case "UTC", "U":
|
||||
fallthrough
|
||||
default:
|
||||
r = time.UTC
|
||||
}
|
||||
return
|
||||
func (engine *Engine) TZTime(t time.Time) time.Time {
|
||||
return t.In(engine.TZLocation)
|
||||
}
|
||||
|
||||
func (engine *Engine) NowTime(sqlTypeName string) interface{} {
|
||||
|
|
@ -1109,19 +1113,16 @@ func (engine *Engine) NowTime(sqlTypeName string) interface{} {
|
|||
}
|
||||
|
||||
func (engine *Engine) FormatTime(sqlTypeName string, t time.Time) (v interface{}) {
|
||||
fmt.Println("sqlTypeName:", sqlTypeName)
|
||||
switch sqlTypeName {
|
||||
case Time:
|
||||
case core.Time:
|
||||
s := engine.TZTime(t).Format("2006-01-02 15:04:05") //time.RFC3339
|
||||
v = s[11:19]
|
||||
case Date:
|
||||
case core.Date:
|
||||
v = engine.TZTime(t).Format("2006-01-02")
|
||||
case DateTime, TimeStamp:
|
||||
l := engine.TZTime(t)
|
||||
v = l.Format("2006-01-02 15:04:05")
|
||||
fmt.Println("xxxx", t, l, v, engine.TimeZone)
|
||||
case TimeStampz:
|
||||
if engine.dialect.DBType() == MSSQL {
|
||||
case core.DateTime, core.TimeStamp:
|
||||
v = engine.TZTime(t).Format("2006-01-02 15:04:05")
|
||||
case core.TimeStampz:
|
||||
if engine.dialect.DBType() == core.MSSQL {
|
||||
v = engine.TZTime(t).Format("2006-01-02T15:04:05.9999999Z07:00")
|
||||
} else {
|
||||
v = engine.TZTime(t).Format(time.RFC3339Nano)
|
||||
|
|
|
|||
|
|
@ -1,109 +1,110 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/lunny/xorm"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"os"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Id int64
|
||||
Name string
|
||||
Id int64
|
||||
Name string
|
||||
}
|
||||
|
||||
func main() {
|
||||
f := "cache.db"
|
||||
os.Remove(f)
|
||||
f := "cache.db"
|
||||
os.Remove(f)
|
||||
|
||||
Orm, err := xorm.NewEngine("sqlite3", f)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
Orm.ShowSQL = true
|
||||
cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000)
|
||||
Orm.SetDefaultCacher(cacher)
|
||||
Orm, err := xorm.NewEngine("sqlite3", f)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
Orm.ShowSQL = true
|
||||
cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000)
|
||||
Orm.SetDefaultCacher(cacher)
|
||||
|
||||
err = Orm.CreateTables(&User{})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
err = Orm.CreateTables(&User{})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = Orm.Insert(&User{Name: "xlw"})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
_, err = Orm.Insert(&User{Name: "xlw"})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
users := make([]User, 0)
|
||||
err = Orm.Find(&users)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
users := make([]User, 0)
|
||||
err = Orm.Find(&users)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("users:", users)
|
||||
fmt.Println("users:", users)
|
||||
|
||||
users2 := make([]User, 0)
|
||||
users2 := make([]User, 0)
|
||||
|
||||
err = Orm.Find(&users2)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
err = Orm.Find(&users2)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("users2:", users2)
|
||||
fmt.Println("users2:", users2)
|
||||
|
||||
users3 := make([]User, 0)
|
||||
users3 := make([]User, 0)
|
||||
|
||||
err = Orm.Find(&users3)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
err = Orm.Find(&users3)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("users3:", users3)
|
||||
fmt.Println("users3:", users3)
|
||||
|
||||
user4 := new(User)
|
||||
has, err := Orm.Id(1).Get(user4)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
user4 := new(User)
|
||||
has, err := Orm.Id(1).Get(user4)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("user4:", has, user4)
|
||||
fmt.Println("user4:", has, user4)
|
||||
|
||||
user4.Name = "xiaolunwen"
|
||||
_, err = Orm.Id(1).Update(user4)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println("user4:", user4)
|
||||
user4.Name = "xiaolunwen"
|
||||
_, err = Orm.Id(1).Update(user4)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println("user4:", user4)
|
||||
|
||||
user5 := new(User)
|
||||
has, err = Orm.Id(1).Get(user5)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println("user5:", has, user5)
|
||||
user5 := new(User)
|
||||
has, err = Orm.Id(1).Get(user5)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println("user5:", has, user5)
|
||||
|
||||
_, err = Orm.Id(1).Delete(new(User))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
_, err = Orm.Id(1).Delete(new(User))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
user6 := new(User)
|
||||
has, err = Orm.Id(1).Get(user6)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println("user6:", has, user6)
|
||||
}
|
||||
for {
|
||||
user6 := new(User)
|
||||
has, err = Orm.Id(1).Get(user6)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println("user6:", has, user6)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,105 +1,107 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/lunny/xorm"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"os"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/go-xorm/xorm"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Id int64
|
||||
Name string
|
||||
Id int64
|
||||
Name string
|
||||
}
|
||||
|
||||
func sqliteEngine() (*xorm.Engine, error) {
|
||||
os.Remove("./test.db")
|
||||
return xorm.NewEngine("sqlite3", "./goroutine.db")
|
||||
os.Remove("./test.db")
|
||||
return xorm.NewEngine("sqlite3", "./goroutine.db")
|
||||
}
|
||||
|
||||
func mysqlEngine() (*xorm.Engine, error) {
|
||||
return xorm.NewEngine("mysql", "root:@/test?charset=utf8")
|
||||
return xorm.NewEngine("mysql", "root:@/test?charset=utf8")
|
||||
}
|
||||
|
||||
var u *User = &User{}
|
||||
|
||||
func test(engine *xorm.Engine) {
|
||||
err := engine.CreateTables(u)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
err := engine.CreateTables(u)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
size := 500
|
||||
queue := make(chan int, size)
|
||||
size := 500
|
||||
queue := make(chan int, size)
|
||||
|
||||
for i := 0; i < size; i++ {
|
||||
go func(x int) {
|
||||
//x := i
|
||||
err := engine.Ping()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
for j := 0; j < 10; j++ {
|
||||
if x+j < 2 {
|
||||
_, err = engine.Get(u)
|
||||
} else if x+j < 4 {
|
||||
users := make([]User, 0)
|
||||
err = engine.Find(&users)
|
||||
} else if x+j < 8 {
|
||||
_, err = engine.Count(u)
|
||||
} else if x+j < 16 {
|
||||
_, err = engine.Insert(&User{Name: "xlw"})
|
||||
} else if x+j < 32 {
|
||||
//_, err = engine.Id(1).Delete(u)
|
||||
_, err = engine.Delete(u)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
queue <- x
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.Printf("%v success!\n", x)
|
||||
}
|
||||
queue <- x
|
||||
}(i)
|
||||
}
|
||||
for i := 0; i < size; i++ {
|
||||
go func(x int) {
|
||||
//x := i
|
||||
err := engine.Ping()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
for j := 0; j < 10; j++ {
|
||||
if x+j < 2 {
|
||||
_, err = engine.Get(u)
|
||||
} else if x+j < 4 {
|
||||
users := make([]User, 0)
|
||||
err = engine.Find(&users)
|
||||
} else if x+j < 8 {
|
||||
_, err = engine.Count(u)
|
||||
} else if x+j < 16 {
|
||||
_, err = engine.Insert(&User{Name: "xlw"})
|
||||
} else if x+j < 32 {
|
||||
//_, err = engine.Id(1).Delete(u)
|
||||
_, err = engine.Delete(u)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
queue <- x
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.Printf("%v success!\n", x)
|
||||
}
|
||||
queue <- x
|
||||
}(i)
|
||||
}
|
||||
|
||||
for i := 0; i < size; i++ {
|
||||
<-queue
|
||||
}
|
||||
for i := 0; i < size; i++ {
|
||||
<-queue
|
||||
}
|
||||
|
||||
//conns := atomic.LoadInt32(&xorm.ConnectionNum)
|
||||
//fmt.Println("connection number:", conns)
|
||||
fmt.Println("end")
|
||||
//conns := atomic.LoadInt32(&xorm.ConnectionNum)
|
||||
//fmt.Println("connection number:", conns)
|
||||
fmt.Println("end")
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("-----start sqlite go routines-----")
|
||||
engine, err := sqliteEngine()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
engine.ShowSQL = true
|
||||
cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000)
|
||||
engine.SetDefaultCacher(cacher)
|
||||
fmt.Println(engine)
|
||||
test(engine)
|
||||
fmt.Println("test end")
|
||||
engine.Close()
|
||||
fmt.Println("-----start sqlite go routines-----")
|
||||
engine, err := sqliteEngine()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
engine.ShowSQL = true
|
||||
cacher := xorm.NewLRUCacher2(xorm.NewMemoryStore(), time.Hour, 1000)
|
||||
engine.SetDefaultCacher(cacher)
|
||||
fmt.Println(engine)
|
||||
test(engine)
|
||||
fmt.Println("test end")
|
||||
engine.Close()
|
||||
|
||||
fmt.Println("-----start mysql go routines-----")
|
||||
engine, err = mysqlEngine()
|
||||
engine.ShowSQL = true
|
||||
cacher = xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000)
|
||||
engine.SetDefaultCacher(cacher)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer engine.Close()
|
||||
test(engine)
|
||||
fmt.Println("-----start mysql go routines-----")
|
||||
engine, err = mysqlEngine()
|
||||
engine.ShowSQL = true
|
||||
cacher = xorm.NewLRUCacher2(xorm.NewMemoryStore(), time.Hour, 1000)
|
||||
engine.SetDefaultCacher(cacher)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer engine.Close()
|
||||
test(engine)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,76 +1,77 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/lunny/xorm"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"os"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
Name string
|
||||
Color string
|
||||
Name string
|
||||
Color string
|
||||
}
|
||||
|
||||
var (
|
||||
Registed Status = Status{"Registed", "white"}
|
||||
Approved Status = Status{"Approved", "green"}
|
||||
Removed Status = Status{"Removed", "red"}
|
||||
Statuses map[string]Status = map[string]Status{
|
||||
Registed.Name: Registed,
|
||||
Approved.Name: Approved,
|
||||
Removed.Name: Removed,
|
||||
}
|
||||
Registed Status = Status{"Registed", "white"}
|
||||
Approved Status = Status{"Approved", "green"}
|
||||
Removed Status = Status{"Removed", "red"}
|
||||
Statuses map[string]Status = map[string]Status{
|
||||
Registed.Name: Registed,
|
||||
Approved.Name: Approved,
|
||||
Removed.Name: Removed,
|
||||
}
|
||||
)
|
||||
|
||||
func (s *Status) FromDB(bytes []byte) error {
|
||||
if r, ok := Statuses[string(bytes)]; ok {
|
||||
*s = r
|
||||
return nil
|
||||
} else {
|
||||
return errors.New("no this data")
|
||||
}
|
||||
if r, ok := Statuses[string(bytes)]; ok {
|
||||
*s = r
|
||||
return nil
|
||||
} else {
|
||||
return errors.New("no this data")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Status) ToDB() ([]byte, error) {
|
||||
return []byte(s.Name), nil
|
||||
return []byte(s.Name), nil
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Id int64
|
||||
Name string
|
||||
Status Status `xorm:"varchar(40)"`
|
||||
Id int64
|
||||
Name string
|
||||
Status Status `xorm:"varchar(40)"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
f := "conversion.db"
|
||||
os.Remove(f)
|
||||
f := "conversion.db"
|
||||
os.Remove(f)
|
||||
|
||||
Orm, err := NewEngine("sqlite3", f)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
Orm.ShowSQL = true
|
||||
err = Orm.CreateTables(&User{})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
Orm, err := xorm.NewEngine("sqlite3", f)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
Orm.ShowSQL = true
|
||||
err = Orm.CreateTables(&User{})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = Orm.Insert(&User{1, "xlw", Registed})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
_, err = Orm.Insert(&User{1, "xlw", Registed})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
users := make([]User, 0)
|
||||
err = Orm.Find(&users)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
users := make([]User, 0)
|
||||
err = Orm.Find(&users)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(users)
|
||||
fmt.Println(users)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,66 +1,67 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/lunny/xorm"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"os"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Id int64
|
||||
Name string
|
||||
Id int64
|
||||
Name string
|
||||
}
|
||||
|
||||
type LoginInfo struct {
|
||||
Id int64
|
||||
IP string
|
||||
UserId int64
|
||||
Id int64
|
||||
IP string
|
||||
UserId int64
|
||||
}
|
||||
|
||||
type LoginInfo1 struct {
|
||||
LoginInfo `xorm:"extends"`
|
||||
UserName string
|
||||
LoginInfo `xorm:"extends"`
|
||||
UserName string
|
||||
}
|
||||
|
||||
func main() {
|
||||
f := "derive.db"
|
||||
os.Remove(f)
|
||||
f := "derive.db"
|
||||
os.Remove(f)
|
||||
|
||||
Orm, err := NewEngine("sqlite3", f)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer Orm.Close()
|
||||
Orm.ShowSQL = true
|
||||
err = Orm.CreateTables(&User{}, &LoginInfo{})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
Orm, err := xorm.NewEngine("sqlite3", f)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer Orm.Close()
|
||||
Orm.ShowSQL = true
|
||||
err = Orm.CreateTables(&User{}, &LoginInfo{})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = Orm.Insert(&User{1, "xlw"}, &LoginInfo{1, "127.0.0.1", 1})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
_, err = Orm.Insert(&User{1, "xlw"}, &LoginInfo{1, "127.0.0.1", 1})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
info := LoginInfo{}
|
||||
_, err = Orm.Id(1).Get(&info)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(info)
|
||||
info := LoginInfo{}
|
||||
_, err = Orm.Id(1).Get(&info)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(info)
|
||||
|
||||
infos := make([]LoginInfo1, 0)
|
||||
err = Orm.Sql(`select *, (select name from user where id = login_info.user_id) as user_name from
|
||||
infos := make([]LoginInfo1, 0)
|
||||
err = Orm.Sql(`select *, (select name from user where id = login_info.user_id) as user_name from
|
||||
login_info limit 10`).Find(&infos)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(infos)
|
||||
fmt.Println(infos)
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,106 +1,107 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/lunny/xorm"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"os"
|
||||
"runtime"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/go-xorm/xorm"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Id int64
|
||||
Name string
|
||||
Id int64
|
||||
Name string
|
||||
}
|
||||
|
||||
func sqliteEngine() (*xorm.Engine, error) {
|
||||
os.Remove("./test.db")
|
||||
return xorm.NewEngine("sqlite3", "./goroutine.db")
|
||||
os.Remove("./test.db")
|
||||
return xorm.NewEngine("sqlite3", "./goroutine.db")
|
||||
}
|
||||
|
||||
func mysqlEngine() (*xorm.Engine, error) {
|
||||
return xorm.NewEngine("mysql", "root:@/test?charset=utf8")
|
||||
return xorm.NewEngine("mysql", "root:@/test?charset=utf8")
|
||||
}
|
||||
|
||||
var u *User = &User{}
|
||||
|
||||
func test(engine *xorm.Engine) {
|
||||
err := engine.CreateTables(u)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
err := engine.CreateTables(u)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
size := 500
|
||||
queue := make(chan int, size)
|
||||
size := 500
|
||||
queue := make(chan int, size)
|
||||
|
||||
for i := 0; i < size; i++ {
|
||||
go func(x int) {
|
||||
//x := i
|
||||
err := engine.Test()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
err = engine.Map(u)
|
||||
if err != nil {
|
||||
fmt.Println("Map user failed")
|
||||
} else {
|
||||
for j := 0; j < 10; j++ {
|
||||
if x+j < 2 {
|
||||
_, err = engine.Get(u)
|
||||
} else if x+j < 4 {
|
||||
users := make([]User, 0)
|
||||
err = engine.Find(&users)
|
||||
} else if x+j < 8 {
|
||||
_, err = engine.Count(u)
|
||||
} else if x+j < 16 {
|
||||
_, err = engine.Insert(&User{Name: "xlw"})
|
||||
} else if x+j < 32 {
|
||||
_, err = engine.Id(1).Delete(u)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
queue <- x
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.Printf("%v success!\n", x)
|
||||
}
|
||||
}
|
||||
queue <- x
|
||||
}(i)
|
||||
}
|
||||
for i := 0; i < size; i++ {
|
||||
go func(x int) {
|
||||
//x := i
|
||||
err := engine.Ping()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
/*err = engine.(u)
|
||||
if err != nil {
|
||||
fmt.Println("Map user failed")
|
||||
} else {*/
|
||||
for j := 0; j < 10; j++ {
|
||||
if x+j < 2 {
|
||||
_, err = engine.Get(u)
|
||||
} else if x+j < 4 {
|
||||
users := make([]User, 0)
|
||||
err = engine.Find(&users)
|
||||
} else if x+j < 8 {
|
||||
_, err = engine.Count(u)
|
||||
} else if x+j < 16 {
|
||||
_, err = engine.Insert(&User{Name: "xlw"})
|
||||
} else if x+j < 32 {
|
||||
_, err = engine.Id(1).Delete(u)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
queue <- x
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.Printf("%v success!\n", x)
|
||||
//}
|
||||
}
|
||||
queue <- x
|
||||
}(i)
|
||||
}
|
||||
|
||||
for i := 0; i < size; i++ {
|
||||
<-queue
|
||||
}
|
||||
for i := 0; i < size; i++ {
|
||||
<-queue
|
||||
}
|
||||
|
||||
//conns := atomic.LoadInt32(&xorm.ConnectionNum)
|
||||
//fmt.Println("connection number:", conns)
|
||||
fmt.Println("end")
|
||||
//conns := atomic.LoadInt32(&xorm.ConnectionNum)
|
||||
//fmt.Println("connection number:", conns)
|
||||
fmt.Println("end")
|
||||
}
|
||||
|
||||
func main() {
|
||||
runtime.GOMAXPROCS(2)
|
||||
fmt.Println("-----start sqlite go routines-----")
|
||||
engine, err := sqliteEngine()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
engine.ShowSQL = true
|
||||
fmt.Println(engine)
|
||||
test(engine)
|
||||
fmt.Println("test end")
|
||||
engine.Close()
|
||||
runtime.GOMAXPROCS(1)
|
||||
fmt.Println("-----start sqlite go routines-----")
|
||||
engine, err := sqliteEngine()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
engine.ShowSQL = true
|
||||
fmt.Println(engine)
|
||||
test(engine)
|
||||
fmt.Println("test end")
|
||||
engine.Close()
|
||||
|
||||
fmt.Println("-----start mysql go routines-----")
|
||||
engine, err = mysqlEngine()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer engine.Close()
|
||||
test(engine)
|
||||
fmt.Println("-----start mysql go routines-----")
|
||||
engine, err = mysqlEngine()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer engine.Close()
|
||||
test(engine)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,106 +1,107 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/lunny/xorm"
|
||||
xorm "github.com/lunny/xorm"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"os"
|
||||
"runtime"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/go-xorm/xorm"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Id int64
|
||||
Name string
|
||||
Id int64
|
||||
Name string
|
||||
}
|
||||
|
||||
func sqliteEngine() (*xorm.Engine, error) {
|
||||
os.Remove("./test.db")
|
||||
return xorm.NewEngine("sqlite3", "./goroutine.db")
|
||||
os.Remove("./test.db")
|
||||
return xorm.NewEngine("sqlite3", "./goroutine.db")
|
||||
}
|
||||
|
||||
func mysqlEngine() (*xorm.Engine, error) {
|
||||
return xorm.NewEngine("mysql", "root:@/test?charset=utf8")
|
||||
return xorm.NewEngine("mysql", "root:@/test?charset=utf8")
|
||||
}
|
||||
|
||||
var u *User = &User{}
|
||||
|
||||
func test(engine *xorm.Engine) {
|
||||
err := engine.CreateTables(u)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
err := engine.CreateTables(u)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
engine.ShowSQL = true
|
||||
engine.Pool.SetMaxConns(5)
|
||||
size := 1000
|
||||
queue := make(chan int, size)
|
||||
engine.ShowSQL = true
|
||||
engine.SetMaxOpenConns(5)
|
||||
|
||||
for i := 0; i < size; i++ {
|
||||
go func(x int) {
|
||||
//x := i
|
||||
err := engine.Test()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
err = engine.Map(u)
|
||||
if err != nil {
|
||||
fmt.Println("Map user failed")
|
||||
} else {
|
||||
for j := 0; j < 10; j++ {
|
||||
if x+j < 2 {
|
||||
_, err = engine.Get(u)
|
||||
} else if x+j < 4 {
|
||||
users := make([]User, 0)
|
||||
err = engine.Find(&users)
|
||||
} else if x+j < 8 {
|
||||
_, err = engine.Count(u)
|
||||
} else if x+j < 16 {
|
||||
_, err = engine.Insert(&User{Name: "xlw"})
|
||||
} else if x+j < 32 {
|
||||
_, err = engine.Id(1).Delete(u)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
queue <- x
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.Printf("%v success!\n", x)
|
||||
}
|
||||
}
|
||||
queue <- x
|
||||
}(i)
|
||||
}
|
||||
size := 1000
|
||||
queue := make(chan int, size)
|
||||
|
||||
for i := 0; i < size; i++ {
|
||||
<-queue
|
||||
}
|
||||
for i := 0; i < size; i++ {
|
||||
go func(x int) {
|
||||
//x := i
|
||||
err := engine.Ping()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
/*err = engine.Map(u)
|
||||
if err != nil {
|
||||
fmt.Println("Map user failed")
|
||||
} else {*/
|
||||
for j := 0; j < 10; j++ {
|
||||
if x+j < 2 {
|
||||
_, err = engine.Get(u)
|
||||
} else if x+j < 4 {
|
||||
users := make([]User, 0)
|
||||
err = engine.Find(&users)
|
||||
} else if x+j < 8 {
|
||||
_, err = engine.Count(u)
|
||||
} else if x+j < 16 {
|
||||
_, err = engine.Insert(&User{Name: "xlw"})
|
||||
} else if x+j < 32 {
|
||||
_, err = engine.Id(1).Delete(u)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
queue <- x
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.Printf("%v success!\n", x)
|
||||
//}
|
||||
}
|
||||
queue <- x
|
||||
}(i)
|
||||
}
|
||||
|
||||
fmt.Println("end")
|
||||
for i := 0; i < size; i++ {
|
||||
<-queue
|
||||
}
|
||||
|
||||
fmt.Println("end")
|
||||
}
|
||||
|
||||
func main() {
|
||||
runtime.GOMAXPROCS(2)
|
||||
fmt.Println("create engine")
|
||||
engine, err := sqliteEngine()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
engine.ShowSQL = true
|
||||
fmt.Println(engine)
|
||||
test(engine)
|
||||
fmt.Println("------------------------")
|
||||
engine.Close()
|
||||
runtime.GOMAXPROCS(2)
|
||||
fmt.Println("create engine")
|
||||
engine, err := sqliteEngine()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
engine.ShowSQL = true
|
||||
fmt.Println(engine)
|
||||
test(engine)
|
||||
fmt.Println("------------------------")
|
||||
engine.Close()
|
||||
|
||||
engine, err = mysqlEngine()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer engine.Close()
|
||||
test(engine)
|
||||
engine, err = mysqlEngine()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer engine.Close()
|
||||
test(engine)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/lunny/xorm"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"os"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Id int64
|
||||
Name string
|
||||
}
|
||||
|
||||
func main() {
|
||||
f := "pool.db"
|
||||
os.Remove(f)
|
||||
|
||||
Orm, err := NewEngine("sqlite3", f)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
err = Orm.SetPool(NewSimpleConnectPool())
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
Orm.ShowSQL = true
|
||||
err = Orm.CreateTables(&User{})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
_, err = Orm.Get(&User{})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +1,55 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/lunny/xorm"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"os"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Id int64
|
||||
Name string
|
||||
Id int64
|
||||
Name string
|
||||
}
|
||||
|
||||
type LoginInfo struct {
|
||||
Id int64
|
||||
IP string
|
||||
UserId int64
|
||||
// timestamp should be updated by database, so only allow get from db
|
||||
TimeStamp string `xorm:"<-"`
|
||||
// assume
|
||||
Nonuse int `xorm:"->"`
|
||||
Id int64
|
||||
IP string
|
||||
UserId int64
|
||||
// timestamp should be updated by database, so only allow get from db
|
||||
TimeStamp string `xorm:"<-"`
|
||||
// assume
|
||||
Nonuse int `xorm:"->"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
f := "singleMapping.db"
|
||||
os.Remove(f)
|
||||
f := "singleMapping.db"
|
||||
os.Remove(f)
|
||||
|
||||
Orm, err := NewEngine("sqlite3", f)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
Orm.ShowSQL = true
|
||||
err = Orm.CreateTables(&User{}, &LoginInfo{})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
Orm, err := xorm.NewEngine("sqlite3", f)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
Orm.ShowSQL = true
|
||||
err = Orm.CreateTables(&User{}, &LoginInfo{})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = Orm.Insert(&User{1, "xlw"}, &LoginInfo{1, "127.0.0.1", 1, "", 23})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
_, err = Orm.Insert(&User{1, "xlw"}, &LoginInfo{1, "127.0.0.1", 1, "", 23})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
info := LoginInfo{}
|
||||
_, err = Orm.Id(1).Get(&info)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(info)
|
||||
info := LoginInfo{}
|
||||
_, err = Orm.Id(1).Get(&info)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(info)
|
||||
}
|
||||
|
|
|
|||
125
examples/sync.go
125
examples/sync.go
|
|
@ -1,92 +1,93 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
_ "github.com/bylevel/pq"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/lunny/xorm"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"fmt"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/go-xorm/xorm"
|
||||
_ "github.com/lib/pq"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
type SyncUser2 struct {
|
||||
Id int64
|
||||
Name string `xorm:"unique"`
|
||||
Age int `xorm:"index"`
|
||||
Title string
|
||||
Address string
|
||||
Genre string
|
||||
Area string
|
||||
Date int
|
||||
Id int64
|
||||
Name string `xorm:"unique"`
|
||||
Age int `xorm:"index"`
|
||||
Title string
|
||||
Address string
|
||||
Genre string
|
||||
Area string
|
||||
Date int
|
||||
}
|
||||
|
||||
type SyncLoginInfo2 struct {
|
||||
Id int64
|
||||
IP string `xorm:"index"`
|
||||
UserId int64
|
||||
AddedCol int
|
||||
// timestamp should be updated by database, so only allow get from db
|
||||
TimeStamp string
|
||||
// assume
|
||||
Nonuse int `xorm:"unique"`
|
||||
Newa string `xorm:"index"`
|
||||
Id int64
|
||||
IP string `xorm:"index"`
|
||||
UserId int64
|
||||
AddedCol int
|
||||
// timestamp should be updated by database, so only allow get from db
|
||||
TimeStamp string
|
||||
// assume
|
||||
Nonuse int `xorm:"unique"`
|
||||
Newa string `xorm:"index"`
|
||||
}
|
||||
|
||||
func sync(engine *xorm.Engine) error {
|
||||
return engine.Sync(&SyncLoginInfo2{}, &SyncUser2{})
|
||||
return engine.Sync(&SyncLoginInfo2{}, &SyncUser2{})
|
||||
}
|
||||
|
||||
func sqliteEngine() (*xorm.Engine, error) {
|
||||
f := "sync.db"
|
||||
//os.Remove(f)
|
||||
f := "sync.db"
|
||||
//os.Remove(f)
|
||||
|
||||
return xorm.NewEngine("sqlite3", f)
|
||||
return xorm.NewEngine("sqlite3", f)
|
||||
}
|
||||
|
||||
func mysqlEngine() (*xorm.Engine, error) {
|
||||
return xorm.NewEngine("mysql", "root:@/test?charset=utf8")
|
||||
return xorm.NewEngine("mysql", "root:@/test?charset=utf8")
|
||||
}
|
||||
|
||||
func postgresEngine() (*xorm.Engine, error) {
|
||||
return xorm.NewEngine("postgres", "dbname=xorm_test sslmode=disable")
|
||||
return xorm.NewEngine("postgres", "dbname=xorm_test sslmode=disable")
|
||||
}
|
||||
|
||||
type engineFunc func() (*xorm.Engine, error)
|
||||
|
||||
func main() {
|
||||
//engines := []engineFunc{sqliteEngine, mysqlEngine, postgresEngine}
|
||||
//engines := []engineFunc{sqliteEngine}
|
||||
//engines := []engineFunc{mysqlEngine}
|
||||
engines := []engineFunc{postgresEngine}
|
||||
for _, enginefunc := range engines {
|
||||
Orm, err := enginefunc()
|
||||
fmt.Println("--------", Orm.DriverName, "----------")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
Orm.ShowSQL = true
|
||||
err = sync(Orm)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
//engines := []engineFunc{sqliteEngine, mysqlEngine, postgresEngine}
|
||||
//engines := []engineFunc{sqliteEngine}
|
||||
//engines := []engineFunc{mysqlEngine}
|
||||
engines := []engineFunc{postgresEngine}
|
||||
for _, enginefunc := range engines {
|
||||
Orm, err := enginefunc()
|
||||
fmt.Println("--------", Orm.DriverName, "----------")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
Orm.ShowSQL = true
|
||||
err = sync(Orm)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
_, err = Orm.Where("id > 0").Delete(&SyncUser2{})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
_, err = Orm.Where("id > 0").Delete(&SyncUser2{})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
user := &SyncUser2{
|
||||
Name: "testsdf",
|
||||
Age: 15,
|
||||
Title: "newsfds",
|
||||
Address: "fasfdsafdsaf",
|
||||
Genre: "fsafd",
|
||||
Area: "fafdsafd",
|
||||
Date: 1000,
|
||||
}
|
||||
_, err = Orm.Insert(user)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
user := &SyncUser2{
|
||||
Name: "testsdf",
|
||||
Age: 15,
|
||||
Title: "newsfds",
|
||||
Address: "fasfdsafdsaf",
|
||||
Genre: "fsafd",
|
||||
Area: "fafdsafd",
|
||||
Date: 1000,
|
||||
}
|
||||
_, err = Orm.Insert(user)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
49
filter.go
49
filter.go
|
|
@ -1,49 +0,0 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Filter is an interface to filter SQL
|
||||
type Filter interface {
|
||||
Do(sql string, session *Session) string
|
||||
}
|
||||
|
||||
// PgSeqFilter filter SQL replace ?, ? ... to $1, $2 ...
|
||||
type PgSeqFilter struct {
|
||||
}
|
||||
|
||||
func (s *PgSeqFilter) Do(sql string, session *Session) string {
|
||||
segs := strings.Split(sql, "?")
|
||||
size := len(segs)
|
||||
res := ""
|
||||
for i, c := range segs {
|
||||
if i < size-1 {
|
||||
res += c + fmt.Sprintf("$%v", i+1)
|
||||
}
|
||||
}
|
||||
res += segs[size-1]
|
||||
return res
|
||||
}
|
||||
|
||||
// QuoteFilter filter SQL replace ` to database's own quote character
|
||||
type QuoteFilter struct {
|
||||
}
|
||||
|
||||
func (s *QuoteFilter) Do(sql string, session *Session) string {
|
||||
return strings.Replace(sql, "`", session.Engine.QuoteStr(), -1)
|
||||
}
|
||||
|
||||
// IdFilter filter SQL replace (id) to primary key column name
|
||||
type IdFilter struct {
|
||||
}
|
||||
|
||||
func (i *IdFilter) Do(sql string, session *Session) string {
|
||||
if session.Statement.RefTable != nil && len(session.Statement.RefTable.PrimaryKeys) == 1 {
|
||||
sql = strings.Replace(sql, "`(id)`", session.Engine.Quote(session.Statement.RefTable.PrimaryKeys[0]), -1)
|
||||
sql = strings.Replace(sql, session.Engine.Quote("(id)"), session.Engine.Quote(session.Statement.RefTable.PrimaryKeys[0]), -1)
|
||||
return strings.Replace(sql, "(id)", session.Engine.Quote(session.Statement.RefTable.PrimaryKeys[0]), -1)
|
||||
}
|
||||
return sql
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
|
||||
"github.com/go-xorm/core"
|
||||
)
|
||||
|
||||
// func init() {
|
||||
// core.RegisterDriver("goracle", &goracleDriver{})
|
||||
// }
|
||||
|
||||
type goracleDriver struct {
|
||||
}
|
||||
|
||||
func (cfg *goracleDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) {
|
||||
db := &core.Uri{DbType: core.ORACLE}
|
||||
dsnPattern := regexp.MustCompile(
|
||||
`^(?:(?P<user>.*?)(?::(?P<passwd>.*))?@)?` + // [user[:password]@]
|
||||
`(?:(?P<net>[^\(]*)(?:\((?P<addr>[^\)]*)\))?)?` + // [net[(addr)]]
|
||||
`\/(?P<dbname>.*?)` + // /dbname
|
||||
`(?:\?(?P<params>[^\?]*))?$`) // [?param1=value1¶mN=valueN]
|
||||
matches := dsnPattern.FindStringSubmatch(dataSourceName)
|
||||
//tlsConfigRegister := make(map[string]*tls.Config)
|
||||
names := dsnPattern.SubexpNames()
|
||||
|
||||
for i, match := range matches {
|
||||
switch names[i] {
|
||||
case "dbname":
|
||||
db.DbName = match
|
||||
}
|
||||
}
|
||||
if db.DbName == "" {
|
||||
return nil, errors.New("dbname is empty")
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-xorm/core"
|
||||
)
|
||||
|
||||
func indexNoCase(s, sep string) int {
|
||||
|
|
@ -99,7 +100,7 @@ func value2Bytes(rawValue *reflect.Value) (data []byte, err error) {
|
|||
}
|
||||
//时间类型
|
||||
case reflect.Struct:
|
||||
if aa == reflect.TypeOf(c_TIME_DEFAULT) {
|
||||
if aa == core.TimeType {
|
||||
str = rawValue.Interface().(time.Time).Format(time.RFC3339Nano)
|
||||
data = []byte(str)
|
||||
} else {
|
||||
|
|
@ -124,7 +125,7 @@ func value2Bytes(rawValue *reflect.Value) (data []byte, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func rows2maps(rows *sql.Rows) (resultsSlice []map[string][]byte, err error) {
|
||||
func rows2maps(rows *core.Rows) (resultsSlice []map[string][]byte, err error) {
|
||||
fields, err := rows.Columns()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
)
|
||||
|
||||
// logger interface, log/syslog conform with this interface
|
||||
type ILogger interface {
|
||||
Debug(m string) (err error)
|
||||
Err(m string) (err error)
|
||||
Info(m string) (err error)
|
||||
Warning(m string) (err error)
|
||||
}
|
||||
|
||||
type SimpleLogger struct {
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func NewSimpleLogger(out io.Writer) *SimpleLogger {
|
||||
return &SimpleLogger{
|
||||
logger: log.New(out, "[xorm] ", log.Ldate|log.Lmicroseconds)}
|
||||
}
|
||||
|
||||
func NewSimpleLogger2(out io.Writer, prefix string, flag int) *SimpleLogger {
|
||||
return &SimpleLogger{
|
||||
logger: log.New(out, prefix, flag)}
|
||||
}
|
||||
|
||||
func (s *SimpleLogger) Debug(m string) (err error) {
|
||||
s.logger.Println("[debug]", m)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *SimpleLogger) Err(m string) (err error) {
|
||||
s.logger.Println("[error]", m)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *SimpleLogger) Info(m string) (err error) {
|
||||
s.logger.Println("[info]", m)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *SimpleLogger) Warning(m string) (err error) {
|
||||
s.logger.Println("[warning]", m)
|
||||
return
|
||||
}
|
||||
|
|
@ -1,133 +1,43 @@
|
|||
//LRUCacher implements Cacher according to LRU algorithm
|
||||
package xorm
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-xorm/core"
|
||||
)
|
||||
|
||||
const (
|
||||
// default cache expired time
|
||||
CacheExpired = 60 * time.Minute
|
||||
// not use now
|
||||
CacheMaxMemory = 256
|
||||
// evey ten minutes to clear all expired nodes
|
||||
CacheGcInterval = 10 * time.Minute
|
||||
// each time when gc to removed max nodes
|
||||
CacheGcMaxRemoved = 20
|
||||
)
|
||||
|
||||
// CacheStore is a interface to store cache
|
||||
type CacheStore interface {
|
||||
Put(key, value interface{}) error
|
||||
Get(key interface{}) (interface{}, error)
|
||||
Del(key interface{}) error
|
||||
}
|
||||
|
||||
// MemoryStore implements CacheStore provide local machine
|
||||
// memory store
|
||||
type MemoryStore struct {
|
||||
store map[interface{}]interface{}
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func NewMemoryStore() *MemoryStore {
|
||||
return &MemoryStore{store: make(map[interface{}]interface{})}
|
||||
}
|
||||
|
||||
func (s *MemoryStore) Put(key, value interface{}) error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
s.store[key] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MemoryStore) Get(key interface{}) (interface{}, error) {
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
if v, ok := s.store[key]; ok {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
return nil, ErrNotExist
|
||||
}
|
||||
|
||||
func (s *MemoryStore) Del(key interface{}) error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
delete(s.store, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Cacher is an interface to provide cache
|
||||
type Cacher interface {
|
||||
GetIds(tableName, sql string) interface{}
|
||||
GetBean(tableName string, id int64) interface{}
|
||||
PutIds(tableName, sql string, ids interface{})
|
||||
PutBean(tableName string, id int64, obj interface{})
|
||||
DelIds(tableName, sql string)
|
||||
DelBean(tableName string, id int64)
|
||||
ClearIds(tableName string)
|
||||
ClearBeans(tableName string)
|
||||
}
|
||||
|
||||
type idNode struct {
|
||||
tbName string
|
||||
id int64
|
||||
lastVisit time.Time
|
||||
}
|
||||
|
||||
type sqlNode struct {
|
||||
tbName string
|
||||
sql string
|
||||
lastVisit time.Time
|
||||
}
|
||||
|
||||
func newIdNode(tbName string, id int64) *idNode {
|
||||
return &idNode{tbName, id, time.Now()}
|
||||
}
|
||||
|
||||
func newSqlNode(tbName, sql string) *sqlNode {
|
||||
return &sqlNode{tbName, sql, time.Now()}
|
||||
}
|
||||
|
||||
// LRUCacher implements Cacher according to LRU algorithm
|
||||
type LRUCacher struct {
|
||||
idList *list.List
|
||||
sqlList *list.List
|
||||
idIndex map[string]map[interface{}]*list.Element
|
||||
sqlIndex map[string]map[interface{}]*list.Element
|
||||
store CacheStore
|
||||
Max int
|
||||
mutex sync.Mutex
|
||||
Expired time.Duration
|
||||
maxSize int
|
||||
GcInterval time.Duration
|
||||
idList *list.List
|
||||
sqlList *list.List
|
||||
idIndex map[string]map[string]*list.Element
|
||||
sqlIndex map[string]map[string]*list.Element
|
||||
store core.CacheStore
|
||||
mutex sync.Mutex
|
||||
// maxSize int
|
||||
MaxElementSize int
|
||||
Expired time.Duration
|
||||
GcInterval time.Duration
|
||||
}
|
||||
|
||||
func newLRUCacher(store CacheStore, expired time.Duration, maxSize int, max int) *LRUCacher {
|
||||
func NewLRUCacher(store core.CacheStore, maxElementSize int) *LRUCacher {
|
||||
return NewLRUCacher2(store, 0, maxElementSize)
|
||||
}
|
||||
|
||||
func NewLRUCacher2(store core.CacheStore, expired time.Duration, maxElementSize int) *LRUCacher {
|
||||
cacher := &LRUCacher{store: store, idList: list.New(),
|
||||
sqlList: list.New(), Expired: expired, maxSize: maxSize,
|
||||
GcInterval: CacheGcInterval, Max: max,
|
||||
sqlIndex: make(map[string]map[interface{}]*list.Element),
|
||||
idIndex: make(map[string]map[interface{}]*list.Element),
|
||||
sqlList: list.New(), Expired: expired,
|
||||
GcInterval: core.CacheGcInterval, MaxElementSize: maxElementSize,
|
||||
sqlIndex: make(map[string]map[string]*list.Element),
|
||||
idIndex: make(map[string]map[string]*list.Element),
|
||||
}
|
||||
cacher.RunGC()
|
||||
return cacher
|
||||
}
|
||||
|
||||
func NewLRUCacher(store CacheStore, max int) *LRUCacher {
|
||||
return newLRUCacher(store, CacheExpired, CacheMaxMemory, max)
|
||||
}
|
||||
|
||||
func NewLRUCacher2(store CacheStore, expired time.Duration, max int) *LRUCacher {
|
||||
return newLRUCacher(store, expired, 0, max)
|
||||
}
|
||||
|
||||
//func NewLRUCacher3(store CacheStore, expired time.Duration, maxSize int) *LRUCacher {
|
||||
// return newLRUCacher(store, expired, maxSize, 0)
|
||||
//}
|
||||
|
|
@ -148,7 +58,7 @@ func (m *LRUCacher) GC() {
|
|||
defer m.mutex.Unlock()
|
||||
var removedNum int
|
||||
for e := m.idList.Front(); e != nil; {
|
||||
if removedNum <= CacheGcMaxRemoved &&
|
||||
if removedNum <= core.CacheGcMaxRemoved &&
|
||||
time.Now().Sub(e.Value.(*idNode).lastVisit) > m.Expired {
|
||||
removedNum++
|
||||
next := e.Next()
|
||||
|
|
@ -164,7 +74,7 @@ func (m *LRUCacher) GC() {
|
|||
|
||||
removedNum = 0
|
||||
for e := m.sqlList.Front(); e != nil; {
|
||||
if removedNum <= CacheGcMaxRemoved &&
|
||||
if removedNum <= core.CacheGcMaxRemoved &&
|
||||
time.Now().Sub(e.Value.(*sqlNode).lastVisit) > m.Expired {
|
||||
removedNum++
|
||||
next := e.Next()
|
||||
|
|
@ -184,7 +94,7 @@ func (m *LRUCacher) GetIds(tableName, sql string) interface{} {
|
|||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
if _, ok := m.sqlIndex[tableName]; !ok {
|
||||
m.sqlIndex[tableName] = make(map[interface{}]*list.Element)
|
||||
m.sqlIndex[tableName] = make(map[string]*list.Element)
|
||||
}
|
||||
if v, err := m.store.Get(sql); err == nil {
|
||||
if el, ok := m.sqlIndex[tableName][sql]; !ok {
|
||||
|
|
@ -209,11 +119,11 @@ func (m *LRUCacher) GetIds(tableName, sql string) interface{} {
|
|||
}
|
||||
|
||||
// Get bean according tableName and id from cache
|
||||
func (m *LRUCacher) GetBean(tableName string, id int64) interface{} {
|
||||
func (m *LRUCacher) GetBean(tableName string, id string) interface{} {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
if _, ok := m.idIndex[tableName]; !ok {
|
||||
m.idIndex[tableName] = make(map[interface{}]*list.Element)
|
||||
m.idIndex[tableName] = make(map[string]*list.Element)
|
||||
}
|
||||
tid := genId(tableName, id)
|
||||
if v, err := m.store.Get(tid); err == nil {
|
||||
|
|
@ -248,7 +158,7 @@ func (m *LRUCacher) clearIds(tableName string) {
|
|||
m.store.Del(sql)
|
||||
}
|
||||
}
|
||||
m.sqlIndex[tableName] = make(map[interface{}]*list.Element)
|
||||
m.sqlIndex[tableName] = make(map[string]*list.Element)
|
||||
}
|
||||
|
||||
func (m *LRUCacher) ClearIds(tableName string) {
|
||||
|
|
@ -261,11 +171,11 @@ func (m *LRUCacher) clearBeans(tableName string) {
|
|||
if tis, ok := m.idIndex[tableName]; ok {
|
||||
for id, v := range tis {
|
||||
m.idList.Remove(v)
|
||||
tid := genId(tableName, id.(int64))
|
||||
tid := genId(tableName, id)
|
||||
m.store.Del(tid)
|
||||
}
|
||||
}
|
||||
m.idIndex[tableName] = make(map[interface{}]*list.Element)
|
||||
m.idIndex[tableName] = make(map[string]*list.Element)
|
||||
}
|
||||
|
||||
func (m *LRUCacher) ClearBeans(tableName string) {
|
||||
|
|
@ -278,7 +188,7 @@ func (m *LRUCacher) PutIds(tableName, sql string, ids interface{}) {
|
|||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
if _, ok := m.sqlIndex[tableName]; !ok {
|
||||
m.sqlIndex[tableName] = make(map[interface{}]*list.Element)
|
||||
m.sqlIndex[tableName] = make(map[string]*list.Element)
|
||||
}
|
||||
if el, ok := m.sqlIndex[tableName][sql]; !ok {
|
||||
el = m.sqlList.PushBack(newSqlNode(tableName, sql))
|
||||
|
|
@ -287,14 +197,14 @@ func (m *LRUCacher) PutIds(tableName, sql string, ids interface{}) {
|
|||
el.Value.(*sqlNode).lastVisit = time.Now()
|
||||
}
|
||||
m.store.Put(sql, ids)
|
||||
if m.sqlList.Len() > m.Max {
|
||||
if m.sqlList.Len() > m.MaxElementSize {
|
||||
e := m.sqlList.Front()
|
||||
node := e.Value.(*sqlNode)
|
||||
m.delIds(node.tbName, node.sql)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *LRUCacher) PutBean(tableName string, id int64, obj interface{}) {
|
||||
func (m *LRUCacher) PutBean(tableName string, id string, obj interface{}) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
var el *list.Element
|
||||
|
|
@ -308,7 +218,7 @@ func (m *LRUCacher) PutBean(tableName string, id int64, obj interface{}) {
|
|||
}
|
||||
|
||||
m.store.Put(genId(tableName, id), obj)
|
||||
if m.idList.Len() > m.Max {
|
||||
if m.idList.Len() > m.MaxElementSize {
|
||||
e := m.idList.Front()
|
||||
node := e.Value.(*idNode)
|
||||
m.delBean(node.tbName, node.id)
|
||||
|
|
@ -331,7 +241,7 @@ func (m *LRUCacher) DelIds(tableName, sql string) {
|
|||
m.delIds(tableName, sql)
|
||||
}
|
||||
|
||||
func (m *LRUCacher) delBean(tableName string, id int64) {
|
||||
func (m *LRUCacher) delBean(tableName string, id string) {
|
||||
tid := genId(tableName, id)
|
||||
if el, ok := m.idIndex[tableName][id]; ok {
|
||||
delete(m.idIndex[tableName], id)
|
||||
|
|
@ -341,55 +251,36 @@ func (m *LRUCacher) delBean(tableName string, id int64) {
|
|||
m.store.Del(tid)
|
||||
}
|
||||
|
||||
func (m *LRUCacher) DelBean(tableName string, id int64) {
|
||||
func (m *LRUCacher) DelBean(tableName string, id string) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
m.delBean(tableName, id)
|
||||
}
|
||||
|
||||
func encodeIds(ids []int64) (s string) {
|
||||
s = "["
|
||||
for _, id := range ids {
|
||||
s += fmt.Sprintf("%v,", id)
|
||||
}
|
||||
s = s[:len(s)-1] + "]"
|
||||
return
|
||||
type idNode struct {
|
||||
tbName string
|
||||
id string
|
||||
lastVisit time.Time
|
||||
}
|
||||
|
||||
func decodeIds(s string) []int64 {
|
||||
res := make([]int64, 0)
|
||||
if len(s) >= 2 {
|
||||
ss := strings.Split(s[1:len(s)-1], ",")
|
||||
for _, s := range ss {
|
||||
i, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
return res
|
||||
}
|
||||
res = append(res, i)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func getCacheSql(m Cacher, tableName, sql string, args interface{}) ([]int64, error) {
|
||||
bytes := m.GetIds(tableName, genSqlKey(sql, args))
|
||||
if bytes == nil {
|
||||
return nil, errors.New("Not Exist")
|
||||
}
|
||||
objs := decodeIds(bytes.(string))
|
||||
return objs, nil
|
||||
}
|
||||
|
||||
func putCacheSql(m Cacher, ids []int64, tableName, sql string, args interface{}) error {
|
||||
bytes := encodeIds(ids)
|
||||
m.PutIds(tableName, genSqlKey(sql, args), bytes)
|
||||
return nil
|
||||
type sqlNode struct {
|
||||
tbName string
|
||||
sql string
|
||||
lastVisit time.Time
|
||||
}
|
||||
|
||||
func genSqlKey(sql string, args interface{}) string {
|
||||
return fmt.Sprintf("%v-%v", sql, args)
|
||||
}
|
||||
|
||||
func genId(prefix string, id int64) string {
|
||||
func genId(prefix string, id string) string {
|
||||
return fmt.Sprintf("%v-%v", prefix, id)
|
||||
}
|
||||
|
||||
func newIdNode(tbName string, id string) *idNode {
|
||||
return &idNode{tbName, id, time.Now()}
|
||||
}
|
||||
|
||||
func newSqlNode(tbName, sql string) *sqlNode {
|
||||
return &sqlNode{tbName, sql, time.Now()}
|
||||
}
|
||||
176
mapper.go
176
mapper.go
|
|
@ -1,176 +0,0 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// name translation between struct, fields names and table, column names
|
||||
type IMapper interface {
|
||||
Obj2Table(string) string
|
||||
Table2Obj(string) string
|
||||
}
|
||||
|
||||
type CacheMapper struct {
|
||||
oriMapper IMapper
|
||||
obj2tableCache map[string]string
|
||||
obj2tableMutex sync.RWMutex
|
||||
table2objCache map[string]string
|
||||
table2objMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func NewCacheMapper(mapper IMapper) *CacheMapper {
|
||||
return &CacheMapper{oriMapper: mapper, obj2tableCache: make(map[string]string),
|
||||
table2objCache: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *CacheMapper) Obj2Table(o string) string {
|
||||
m.obj2tableMutex.RLock()
|
||||
t, ok := m.obj2tableCache[o]
|
||||
m.obj2tableMutex.RUnlock()
|
||||
if ok {
|
||||
return t
|
||||
}
|
||||
|
||||
t = m.oriMapper.Obj2Table(o)
|
||||
m.obj2tableMutex.Lock()
|
||||
m.obj2tableCache[o] = t
|
||||
m.obj2tableMutex.Unlock()
|
||||
return t
|
||||
}
|
||||
|
||||
func (m *CacheMapper) Table2Obj(t string) string {
|
||||
m.table2objMutex.RLock()
|
||||
o, ok := m.table2objCache[t]
|
||||
m.table2objMutex.RUnlock()
|
||||
if ok {
|
||||
return o
|
||||
}
|
||||
|
||||
o = m.oriMapper.Table2Obj(t)
|
||||
m.table2objMutex.Lock()
|
||||
m.table2objCache[t] = o
|
||||
m.table2objMutex.Unlock()
|
||||
return o
|
||||
}
|
||||
|
||||
// SameMapper implements IMapper and provides same name between struct and
|
||||
// database table
|
||||
type SameMapper struct {
|
||||
}
|
||||
|
||||
func (m SameMapper) Obj2Table(o string) string {
|
||||
return o
|
||||
}
|
||||
|
||||
func (m SameMapper) Table2Obj(t string) string {
|
||||
return t
|
||||
}
|
||||
|
||||
// SnakeMapper implements IMapper and provides name transaltion between
|
||||
// struct and database table
|
||||
type SnakeMapper struct {
|
||||
}
|
||||
|
||||
func snakeCasedName(name string) string {
|
||||
newstr := make([]rune, 0)
|
||||
for idx, chr := range name {
|
||||
if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
|
||||
if idx > 0 {
|
||||
newstr = append(newstr, '_')
|
||||
}
|
||||
chr -= ('A' - 'a')
|
||||
}
|
||||
newstr = append(newstr, chr)
|
||||
}
|
||||
|
||||
return string(newstr)
|
||||
}
|
||||
|
||||
/*func pascal2Sql(s string) (d string) {
|
||||
d = ""
|
||||
lastIdx := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] >= 'A' && s[i] <= 'Z' {
|
||||
if lastIdx < i {
|
||||
d += s[lastIdx+1 : i]
|
||||
}
|
||||
if i != 0 {
|
||||
d += "_"
|
||||
}
|
||||
d += string(s[i] + 32)
|
||||
lastIdx = i
|
||||
}
|
||||
}
|
||||
d += s[lastIdx+1:]
|
||||
return
|
||||
}*/
|
||||
|
||||
func (mapper SnakeMapper) Obj2Table(name string) string {
|
||||
return snakeCasedName(name)
|
||||
}
|
||||
|
||||
func titleCasedName(name string) string {
|
||||
newstr := make([]rune, 0)
|
||||
upNextChar := true
|
||||
|
||||
name = strings.ToLower(name)
|
||||
|
||||
for _, chr := range name {
|
||||
switch {
|
||||
case upNextChar:
|
||||
upNextChar = false
|
||||
if 'a' <= chr && chr <= 'z' {
|
||||
chr -= ('a' - 'A')
|
||||
}
|
||||
case chr == '_':
|
||||
upNextChar = true
|
||||
continue
|
||||
}
|
||||
|
||||
newstr = append(newstr, chr)
|
||||
}
|
||||
|
||||
return string(newstr)
|
||||
}
|
||||
|
||||
func (mapper SnakeMapper) Table2Obj(name string) string {
|
||||
return titleCasedName(name)
|
||||
}
|
||||
|
||||
// provide prefix table name support
|
||||
type PrefixMapper struct {
|
||||
Mapper IMapper
|
||||
Prefix string
|
||||
}
|
||||
|
||||
func (mapper PrefixMapper) Obj2Table(name string) string {
|
||||
return mapper.Prefix + mapper.Mapper.Obj2Table(name)
|
||||
}
|
||||
|
||||
func (mapper PrefixMapper) Table2Obj(name string) string {
|
||||
return mapper.Mapper.Table2Obj(name[len(mapper.Prefix):])
|
||||
}
|
||||
|
||||
func NewPrefixMapper(mapper IMapper, prefix string) PrefixMapper {
|
||||
return PrefixMapper{mapper, prefix}
|
||||
}
|
||||
|
||||
// provide suffix table name support
|
||||
type SuffixMapper struct {
|
||||
Mapper IMapper
|
||||
Suffix string
|
||||
}
|
||||
|
||||
func (mapper SuffixMapper) Obj2Table(name string) string {
|
||||
return mapper.Suffix + mapper.Mapper.Obj2Table(name)
|
||||
}
|
||||
|
||||
func (mapper SuffixMapper) Table2Obj(name string) string {
|
||||
return mapper.Mapper.Table2Obj(name[len(mapper.Suffix):])
|
||||
}
|
||||
|
||||
func NewSuffixMapper(mapper IMapper, suffix string) SuffixMapper {
|
||||
return SuffixMapper{mapper, suffix}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
// MemoryStore implements CacheStore provide local machine
|
||||
package xorm
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/go-xorm/core"
|
||||
)
|
||||
|
||||
var _ core.CacheStore = NewMemoryStore()
|
||||
|
||||
// memory store
|
||||
type MemoryStore struct {
|
||||
store map[interface{}]interface{}
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func NewMemoryStore() *MemoryStore {
|
||||
return &MemoryStore{store: make(map[interface{}]interface{})}
|
||||
}
|
||||
|
||||
func (s *MemoryStore) Put(key string, value interface{}) error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
s.store[key] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MemoryStore) Get(key string) (interface{}, error) {
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
if v, ok := s.store[key]; ok {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
return nil, ErrNotExist
|
||||
}
|
||||
|
||||
func (s *MemoryStore) Del(key string) error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
delete(s.store, key)
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,85 +1,63 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
//"crypto/tls"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
//"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
//"time"
|
||||
|
||||
"github.com/go-xorm/core"
|
||||
)
|
||||
|
||||
// func init() {
|
||||
// RegisterDialect("mssql", &mssql{})
|
||||
// }
|
||||
|
||||
type mssql struct {
|
||||
base
|
||||
quoteFilter Filter
|
||||
core.Base
|
||||
}
|
||||
|
||||
type odbcParser struct {
|
||||
func (db *mssql) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error {
|
||||
return db.Base.Init(d, db, uri, drivername, dataSourceName)
|
||||
}
|
||||
|
||||
func (p *odbcParser) parse(driverName, dataSourceName string) (*uri, error) {
|
||||
kv := strings.Split(dataSourceName, ";")
|
||||
var dbName string
|
||||
|
||||
for _, c := range kv {
|
||||
vv := strings.Split(strings.TrimSpace(c), "=")
|
||||
if len(vv) == 2 {
|
||||
switch strings.ToLower(vv[0]) {
|
||||
case "database":
|
||||
dbName = vv[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
if dbName == "" {
|
||||
return nil, errors.New("no db name provided")
|
||||
}
|
||||
return &uri{dbName: dbName, dbType: MSSQL}, nil
|
||||
}
|
||||
|
||||
func (db *mssql) Init(drivername, uri string) error {
|
||||
db.quoteFilter = &QuoteFilter{}
|
||||
return db.base.init(&odbcParser{}, drivername, uri)
|
||||
}
|
||||
|
||||
func (db *mssql) SqlType(c *Column) string {
|
||||
func (db *mssql) SqlType(c *core.Column) string {
|
||||
var res string
|
||||
switch t := c.SQLType.Name; t {
|
||||
case Bool:
|
||||
res = TinyInt
|
||||
case Serial:
|
||||
case core.Bool:
|
||||
res = core.TinyInt
|
||||
case core.Serial:
|
||||
c.IsAutoIncrement = true
|
||||
c.IsPrimaryKey = true
|
||||
c.Nullable = false
|
||||
res = Int
|
||||
case BigSerial:
|
||||
res = core.Int
|
||||
case core.BigSerial:
|
||||
c.IsAutoIncrement = true
|
||||
c.IsPrimaryKey = true
|
||||
c.Nullable = false
|
||||
res = BigInt
|
||||
case Bytea, Blob, Binary, TinyBlob, MediumBlob, LongBlob:
|
||||
res = VarBinary
|
||||
res = core.BigInt
|
||||
case core.Bytea, core.Blob, core.Binary, core.TinyBlob, core.MediumBlob, core.LongBlob:
|
||||
res = core.VarBinary
|
||||
if c.Length == 0 {
|
||||
c.Length = 50
|
||||
}
|
||||
case TimeStamp:
|
||||
res = DateTime
|
||||
case TimeStampz:
|
||||
case core.TimeStamp:
|
||||
res = core.DateTime
|
||||
case core.TimeStampz:
|
||||
res = "DATETIMEOFFSET"
|
||||
c.Length = 7
|
||||
case MediumInt:
|
||||
res = Int
|
||||
case MediumText, TinyText, LongText:
|
||||
res = Text
|
||||
case Double:
|
||||
res = Real
|
||||
case core.MediumInt:
|
||||
res = core.Int
|
||||
case core.MediumText, core.TinyText, core.LongText:
|
||||
res = core.Text
|
||||
case core.Double:
|
||||
res = core.Real
|
||||
default:
|
||||
res = t
|
||||
}
|
||||
|
||||
if res == Int {
|
||||
return Int
|
||||
if res == core.Int {
|
||||
return core.Int
|
||||
}
|
||||
|
||||
var hasLen1 bool = (c.Length > 0)
|
||||
|
|
@ -140,56 +118,48 @@ func (db *mssql) TableCheckSql(tableName string) (string, []interface{}) {
|
|||
return sql, args
|
||||
}
|
||||
|
||||
func (db *mssql) GetColumns(tableName string) ([]string, map[string]*Column, error) {
|
||||
func (db *mssql) GetColumns(tableName string) ([]string, map[string]*core.Column, error) {
|
||||
args := []interface{}{}
|
||||
s := `select a.name as name, b.name as ctype,a.max_length,a.precision,a.scale
|
||||
from sys.columns a left join sys.types b on a.user_type_id=b.user_type_id
|
||||
where a.object_id=object_id('` + tableName + `')`
|
||||
cnn, err := sql.Open(db.driverName, db.dataSourceName)
|
||||
|
||||
rows, err := db.DB().Query(s, args...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer cnn.Close()
|
||||
res, err := query(cnn, s, args...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cols := make(map[string]*Column)
|
||||
cols := make(map[string]*core.Column)
|
||||
colSeq := make([]string, 0)
|
||||
for _, record := range res {
|
||||
col := new(Column)
|
||||
for rows.Next() {
|
||||
var name, ctype, precision, scale string
|
||||
var maxLen int
|
||||
err = rows.Scan(&name, &ctype, &maxLen, &precision, &scale)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
col := new(core.Column)
|
||||
col.Indexes = make(map[string]bool)
|
||||
for name, content := range record {
|
||||
switch name {
|
||||
case "name":
|
||||
col.Length = maxLen
|
||||
col.Name = strings.Trim(name, "` ")
|
||||
|
||||
col.Name = strings.Trim(string(content), "` ")
|
||||
case "ctype":
|
||||
ct := strings.ToUpper(string(content))
|
||||
switch ct {
|
||||
case "DATETIMEOFFSET":
|
||||
col.SQLType = SQLType{TimeStampz, 0, 0}
|
||||
case "NVARCHAR":
|
||||
col.SQLType = SQLType{Varchar, 0, 0}
|
||||
case "IMAGE":
|
||||
col.SQLType = SQLType{VarBinary, 0, 0}
|
||||
default:
|
||||
if _, ok := sqlTypes[ct]; ok {
|
||||
col.SQLType = SQLType{ct, 0, 0}
|
||||
} else {
|
||||
return nil, nil, errors.New(fmt.Sprintf("unknow colType %v for %v - %v",
|
||||
ct, tableName, col.Name))
|
||||
}
|
||||
}
|
||||
|
||||
case "max_length":
|
||||
len1, err := strconv.Atoi(strings.TrimSpace(string(content)))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
col.Length = len1
|
||||
ct := strings.ToUpper(ctype)
|
||||
switch ct {
|
||||
case "DATETIMEOFFSET":
|
||||
col.SQLType = core.SQLType{core.TimeStampz, 0, 0}
|
||||
case "NVARCHAR":
|
||||
col.SQLType = core.SQLType{core.Varchar, 0, 0}
|
||||
case "IMAGE":
|
||||
col.SQLType = core.SQLType{core.VarBinary, 0, 0}
|
||||
default:
|
||||
if _, ok := core.SqlTypes[ct]; ok {
|
||||
col.SQLType = core.SQLType{ct, 0, 0}
|
||||
} else {
|
||||
return nil, nil, errors.New(fmt.Sprintf("unknow colType %v for %v - %v",
|
||||
ct, tableName, col.Name))
|
||||
}
|
||||
}
|
||||
|
||||
if col.SQLType.IsText() {
|
||||
if col.Default != "" {
|
||||
col.Default = "'" + col.Default + "'"
|
||||
|
|
@ -205,34 +175,30 @@ where a.object_id=object_id('` + tableName + `')`
|
|||
return colSeq, cols, nil
|
||||
}
|
||||
|
||||
func (db *mssql) GetTables() ([]*Table, error) {
|
||||
func (db *mssql) GetTables() ([]*core.Table, error) {
|
||||
args := []interface{}{}
|
||||
s := `select name from sysobjects where xtype ='U'`
|
||||
cnn, err := sql.Open(db.driverName, db.dataSourceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cnn.Close()
|
||||
res, err := query(cnn, s, args...)
|
||||
|
||||
rows, err := db.DB().Query(s, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tables := make([]*Table, 0)
|
||||
for _, record := range res {
|
||||
table := new(Table)
|
||||
for name, content := range record {
|
||||
switch name {
|
||||
case "name":
|
||||
table.Name = strings.Trim(string(content), "` ")
|
||||
}
|
||||
tables := make([]*core.Table, 0)
|
||||
for rows.Next() {
|
||||
table := core.NewEmptyTable()
|
||||
var name string
|
||||
err = rows.Scan(&name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
table.Name = strings.Trim(name, "` ")
|
||||
tables = append(tables, table)
|
||||
}
|
||||
return tables, nil
|
||||
}
|
||||
|
||||
func (db *mssql) GetIndexes(tableName string) (map[string]*Index, error) {
|
||||
func (db *mssql) GetIndexes(tableName string) (map[string]*core.Index, error) {
|
||||
args := []interface{}{tableName}
|
||||
s := `SELECT
|
||||
IXS.NAME AS [INDEX_NAME],
|
||||
|
|
@ -248,48 +214,42 @@ INNER JOIN SYS.COLUMNS C ON IXS.OBJECT_ID=C.OBJECT_ID
|
|||
AND IXCS.COLUMN_ID=C.COLUMN_ID
|
||||
WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =?
|
||||
`
|
||||
cnn, err := sql.Open(db.driverName, db.dataSourceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cnn.Close()
|
||||
res, err := query(cnn, s, args...)
|
||||
rows, err := db.DB().Query(s, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
indexes := make(map[string]*Index, 0)
|
||||
for _, record := range res {
|
||||
indexes := make(map[string]*core.Index, 0)
|
||||
for rows.Next() {
|
||||
var indexType int
|
||||
var indexName, colName string
|
||||
for name, content := range record {
|
||||
switch name {
|
||||
case "IS_UNIQUE":
|
||||
i, err := strconv.ParseBool(string(content))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var indexName, colName, isUnique string
|
||||
|
||||
if i {
|
||||
indexType = UniqueType
|
||||
} else {
|
||||
indexType = IndexType
|
||||
}
|
||||
case "INDEX_NAME":
|
||||
indexName = string(content)
|
||||
case "COLUMN_NAME":
|
||||
colName = strings.Trim(string(content), "` ")
|
||||
}
|
||||
err = rows.Scan(&indexName, &colName, &isUnique, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
i, err := strconv.ParseBool(isUnique)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if i {
|
||||
indexType = core.UniqueType
|
||||
} else {
|
||||
indexType = core.IndexType
|
||||
}
|
||||
|
||||
colName = strings.Trim(colName, "` ")
|
||||
|
||||
if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
|
||||
indexName = indexName[5+len(tableName) : len(indexName)]
|
||||
}
|
||||
|
||||
var index *Index
|
||||
var index *core.Index
|
||||
var ok bool
|
||||
if index, ok = indexes[indexName]; !ok {
|
||||
index = new(Index)
|
||||
index = new(core.Index)
|
||||
index.Type = indexType
|
||||
index.Name = indexName
|
||||
indexes[indexName] = index
|
||||
|
|
@ -298,3 +258,41 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =?
|
|||
}
|
||||
return indexes, nil
|
||||
}
|
||||
|
||||
func (db *mssql) CreateTablSql(table *core.Table, tableName, storeEngine, charset string) string {
|
||||
var sql string
|
||||
if tableName == "" {
|
||||
tableName = table.Name
|
||||
}
|
||||
|
||||
sql = "IF NOT EXISTS (SELECT [name] FROM sys.tables WHERE [name] = '" + tableName + "' ) CREATE TABLE"
|
||||
|
||||
sql += db.QuoteStr() + tableName + db.QuoteStr() + " ("
|
||||
|
||||
pkList := table.PrimaryKeys
|
||||
|
||||
for _, colName := range table.ColumnsSeq() {
|
||||
col := table.GetColumn(colName)
|
||||
if col.IsPrimaryKey && len(pkList) == 1 {
|
||||
sql += col.String(db)
|
||||
} else {
|
||||
sql += col.StringNoPk(db)
|
||||
}
|
||||
sql = strings.TrimSpace(sql)
|
||||
sql += ", "
|
||||
}
|
||||
|
||||
if len(pkList) > 1 {
|
||||
sql += "PRIMARY KEY ( "
|
||||
sql += strings.Join(pkList, ",")
|
||||
sql += " ), "
|
||||
}
|
||||
|
||||
sql = sql[:len(sql)-2] + ")"
|
||||
sql += ";"
|
||||
return sql
|
||||
}
|
||||
|
||||
func (db *mssql) Filters() []core.Filter {
|
||||
return []core.Filter{&core.IdFilter{}, &core.QuoteFilter{}}
|
||||
}
|
||||
142
mssql_test.go
142
mssql_test.go
|
|
@ -1,142 +0,0 @@
|
|||
package xorm
|
||||
|
||||
//
|
||||
// +build windows
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
_ "github.com/lunny/godbc"
|
||||
)
|
||||
|
||||
const mssqlConnStr = "driver={SQL Server};Server=192.168.20.135;Database=xorm_test; uid=sa; pwd=1234;"
|
||||
|
||||
func newMssqlEngine() (*Engine, error) {
|
||||
return NewEngine("odbc", mssqlConnStr)
|
||||
}
|
||||
|
||||
func TestMssql(t *testing.T) {
|
||||
engine, err := newMssqlEngine()
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
engine.ShowSQL = showTestSql
|
||||
engine.ShowErr = showTestSql
|
||||
engine.ShowWarn = showTestSql
|
||||
engine.ShowDebug = showTestSql
|
||||
|
||||
testAll(engine, t)
|
||||
testAll2(engine, t)
|
||||
}
|
||||
|
||||
func TestMssqlWithCache(t *testing.T) {
|
||||
engine, err := newMssqlEngine()
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000))
|
||||
engine.ShowSQL = showTestSql
|
||||
engine.ShowErr = showTestSql
|
||||
engine.ShowWarn = showTestSql
|
||||
engine.ShowDebug = showTestSql
|
||||
|
||||
testAll(engine, t)
|
||||
testAll2(engine, t)
|
||||
}
|
||||
|
||||
func newMssqlDriverDB() (*sql.DB, error) {
|
||||
return sql.Open("odbc", mssqlConnStr)
|
||||
}
|
||||
|
||||
const (
|
||||
createTableMssql = `IF NOT EXISTS (SELECT [name] FROM sys.tables WHERE [name] = 'big_struct' ) CREATE TABLE
|
||||
"big_struct" ("id" BIGINT PRIMARY KEY IDENTITY NOT NULL, "name" VARCHAR(255) NULL, "title" VARCHAR(255) NULL,
|
||||
"age" VARCHAR(255) NULL, "alias" VARCHAR(255) NULL, "nick_name" VARCHAR(255) NULL);
|
||||
`
|
||||
|
||||
dropTableMssql = "IF EXISTS (SELECT * FROM sysobjects WHERE id = object_id(N'big_struct') and OBJECTPROPERTY(id, N'IsUserTable') = 1) DROP TABLE IF EXISTS `big_struct`;"
|
||||
)
|
||||
|
||||
func BenchmarkMssqlDriverInsert(t *testing.B) {
|
||||
doBenchDriver(newMssqlDriverDB, createTableMssql, dropTableMssql,
|
||||
doBenchDriverInsert, t)
|
||||
}
|
||||
|
||||
func BenchmarkMssqlDriverFind(t *testing.B) {
|
||||
doBenchDriver(newMssqlDriverDB, createTableMssql, dropTableMssql,
|
||||
doBenchDriverFind, t)
|
||||
}
|
||||
|
||||
func BenchmarkMssqlNoCacheInsert(t *testing.B) {
|
||||
engine, err := newMssqlEngine()
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
//engine.ShowSQL = true
|
||||
doBenchInsert(engine, t)
|
||||
}
|
||||
|
||||
func BenchmarkMssqlNoCacheFind(t *testing.B) {
|
||||
engine, err := newMssqlEngine()
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
//engine.ShowSQL = true
|
||||
doBenchFind(engine, t)
|
||||
}
|
||||
|
||||
func BenchmarkMssqlNoCacheFindPtr(t *testing.B) {
|
||||
engine, err := newMssqlEngine()
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
//engine.ShowSQL = true
|
||||
doBenchFindPtr(engine, t)
|
||||
}
|
||||
|
||||
func BenchmarkMssqlCacheInsert(t *testing.B) {
|
||||
engine, err := newMssqlEngine()
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000))
|
||||
|
||||
doBenchInsert(engine, t)
|
||||
}
|
||||
|
||||
func BenchmarkMssqlCacheFind(t *testing.B) {
|
||||
engine, err := newMssqlEngine()
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000))
|
||||
|
||||
doBenchFind(engine, t)
|
||||
}
|
||||
|
||||
func BenchmarkMssqlCacheFindPtr(t *testing.B) {
|
||||
engine, err := newMssqlEngine()
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000))
|
||||
|
||||
doBenchFindPtr(engine, t)
|
||||
}
|
||||
|
|
@ -4,17 +4,19 @@ import (
|
|||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-xorm/core"
|
||||
)
|
||||
|
||||
type mymysql struct {
|
||||
mysql
|
||||
// func init() {
|
||||
// core.RegisterDriver("mymysql", &mymysqlDriver{})
|
||||
// }
|
||||
|
||||
type mymysqlDriver struct {
|
||||
}
|
||||
|
||||
type mymysqlParser struct {
|
||||
}
|
||||
|
||||
func (p *mymysqlParser) parse(driverName, dataSourceName string) (*uri, error) {
|
||||
db := &uri{dbType: MYSQL}
|
||||
func (p *mymysqlDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) {
|
||||
db := &core.Uri{DbType: core.MYSQL}
|
||||
|
||||
pd := strings.SplitN(dataSourceName, "*", 2)
|
||||
if len(pd) == 2 {
|
||||
|
|
@ -23,9 +25,9 @@ func (p *mymysqlParser) parse(driverName, dataSourceName string) (*uri, error) {
|
|||
if len(p) != 2 {
|
||||
return nil, errors.New("Wrong protocol part of URI")
|
||||
}
|
||||
db.proto = p[0]
|
||||
db.Proto = p[0]
|
||||
options := strings.Split(p[1], ",")
|
||||
db.raddr = options[0]
|
||||
db.Raddr = options[0]
|
||||
for _, o := range options[1:] {
|
||||
kv := strings.SplitN(o, "=", 2)
|
||||
var k, v string
|
||||
|
|
@ -36,13 +38,13 @@ func (p *mymysqlParser) parse(driverName, dataSourceName string) (*uri, error) {
|
|||
}
|
||||
switch k {
|
||||
case "laddr":
|
||||
db.laddr = v
|
||||
db.Laddr = v
|
||||
case "timeout":
|
||||
to, err := time.ParseDuration(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db.timeout = to
|
||||
db.Timeout = to
|
||||
default:
|
||||
return nil, errors.New("Unknown option: " + k)
|
||||
}
|
||||
|
|
@ -55,13 +57,9 @@ func (p *mymysqlParser) parse(driverName, dataSourceName string) (*uri, error) {
|
|||
if len(dup) != 3 {
|
||||
return nil, errors.New("Wrong database part of URI")
|
||||
}
|
||||
db.dbName = dup[0]
|
||||
db.user = dup[1]
|
||||
db.passwd = dup[2]
|
||||
db.DbName = dup[0]
|
||||
db.User = dup[1]
|
||||
db.Passwd = dup[2]
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func (db *mymysql) Init(drivername, uri string) error {
|
||||
return db.mysql.base.init(&mymysqlParser{}, drivername, uri)
|
||||
}
|
||||
167
mymysql_test.go
167
mymysql_test.go
|
|
@ -1,167 +0,0 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
_ "github.com/ziutek/mymysql/godrv"
|
||||
)
|
||||
|
||||
/*
|
||||
CREATE DATABASE IF NOT EXISTS xorm_test CHARACTER SET
|
||||
utf8 COLLATE utf8_general_ci;
|
||||
*/
|
||||
|
||||
var showTestSql bool = true
|
||||
|
||||
func TestMyMysql(t *testing.T) {
|
||||
err := mymysqlDdlImport()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
engine, err := NewEngine("mymysql", "xorm_test/root/")
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
engine.ShowSQL = showTestSql
|
||||
engine.ShowErr = showTestSql
|
||||
engine.ShowWarn = showTestSql
|
||||
engine.ShowDebug = showTestSql
|
||||
|
||||
testAll(engine, t)
|
||||
testAll2(engine, t)
|
||||
testAll3(engine, t)
|
||||
}
|
||||
|
||||
func TestMyMysqlWithCache(t *testing.T) {
|
||||
err := mymysqlDdlImport()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
engine, err := NewEngine("mymysql", "xorm_test2/root/")
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000))
|
||||
engine.ShowSQL = showTestSql
|
||||
engine.ShowErr = showTestSql
|
||||
engine.ShowWarn = showTestSql
|
||||
engine.ShowDebug = showTestSql
|
||||
|
||||
testAll(engine, t)
|
||||
testAll2(engine, t)
|
||||
}
|
||||
|
||||
func newMyMysqlEngine() (*Engine, error) {
|
||||
return NewEngine("mymysql", "xorm_test2/root/")
|
||||
}
|
||||
|
||||
func newMyMysqlDriverDB() (*sql.DB, error) {
|
||||
return sql.Open("mymysql", "xorm_test2/root/")
|
||||
}
|
||||
|
||||
func BenchmarkMyMysqlDriverInsert(t *testing.B) {
|
||||
doBenchDriver(newMyMysqlDriverDB, createTableMySql, dropTableMySql,
|
||||
doBenchDriverInsert, t)
|
||||
}
|
||||
|
||||
func BenchmarkMyMysqlDriverFind(t *testing.B) {
|
||||
doBenchDriver(newMyMysqlDriverDB, createTableMySql, dropTableMySql,
|
||||
doBenchDriverFind, t)
|
||||
}
|
||||
|
||||
func mymysqlDdlImport() error {
|
||||
engine, err := NewEngine("mymysql", "/root/")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
engine.ShowSQL = showTestSql
|
||||
engine.ShowErr = showTestSql
|
||||
engine.ShowWarn = showTestSql
|
||||
engine.ShowDebug = showTestSql
|
||||
|
||||
sqlResults, _ := engine.Import("tests/mysql_ddl.sql")
|
||||
engine.LogDebug("sql results: %v", sqlResults)
|
||||
engine.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func BenchmarkMyMysqlNoCacheInsert(t *testing.B) {
|
||||
engine, err := newMyMysqlEngine()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer engine.Close()
|
||||
|
||||
doBenchInsert(engine, t)
|
||||
}
|
||||
|
||||
func BenchmarkMyMysqlNoCacheFind(t *testing.B) {
|
||||
engine, err := newMyMysqlEngine()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer engine.Close()
|
||||
|
||||
//engine.ShowSQL = true
|
||||
doBenchFind(engine, t)
|
||||
}
|
||||
|
||||
func BenchmarkMyMysqlNoCacheFindPtr(t *testing.B) {
|
||||
engine, err := newMyMysqlEngine()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer engine.Close()
|
||||
|
||||
//engine.ShowSQL = true
|
||||
doBenchFindPtr(engine, t)
|
||||
}
|
||||
|
||||
func BenchmarkMyMysqlCacheInsert(t *testing.B) {
|
||||
engine, err := newMyMysqlEngine()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
defer engine.Close()
|
||||
engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000))
|
||||
|
||||
doBenchInsert(engine, t)
|
||||
}
|
||||
|
||||
func BenchmarkMyMysqlCacheFind(t *testing.B) {
|
||||
engine, err := newMyMysqlEngine()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
defer engine.Close()
|
||||
engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000))
|
||||
|
||||
doBenchFind(engine, t)
|
||||
}
|
||||
|
||||
func BenchmarkMyMysqlCacheFindPtr(t *testing.B) {
|
||||
engine, err := newMyMysqlEngine()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
defer engine.Close()
|
||||
engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000))
|
||||
|
||||
doBenchFindPtr(engine, t)
|
||||
}
|
||||
360
mysql.go
360
mysql.go
|
|
@ -1,360 +0,0 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type uri struct {
|
||||
dbType string
|
||||
proto string
|
||||
host string
|
||||
port string
|
||||
dbName string
|
||||
user string
|
||||
passwd string
|
||||
charset string
|
||||
laddr string
|
||||
raddr string
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
type parser interface {
|
||||
parse(driverName, dataSourceName string) (*uri, error)
|
||||
}
|
||||
|
||||
type mysqlParser struct {
|
||||
}
|
||||
|
||||
func (p *mysqlParser) parse(driverName, dataSourceName string) (*uri, error) {
|
||||
dsnPattern := regexp.MustCompile(
|
||||
`^(?:(?P<user>.*?)(?::(?P<passwd>.*))?@)?` + // [user[:password]@]
|
||||
`(?:(?P<net>[^\(]*)(?:\((?P<addr>[^\)]*)\))?)?` + // [net[(addr)]]
|
||||
`\/(?P<dbname>.*?)` + // /dbname
|
||||
`(?:\?(?P<params>[^\?]*))?$`) // [?param1=value1¶mN=valueN]
|
||||
matches := dsnPattern.FindStringSubmatch(dataSourceName)
|
||||
//tlsConfigRegister := make(map[string]*tls.Config)
|
||||
names := dsnPattern.SubexpNames()
|
||||
|
||||
uri := &uri{dbType: MYSQL}
|
||||
|
||||
for i, match := range matches {
|
||||
switch names[i] {
|
||||
case "dbname":
|
||||
uri.dbName = match
|
||||
case "params":
|
||||
if len(match) > 0 {
|
||||
kvs := strings.Split(match, "&")
|
||||
for _, kv := range kvs {
|
||||
splits := strings.Split(kv, "=")
|
||||
if len(splits) == 2 {
|
||||
switch splits[0] {
|
||||
case "charset":
|
||||
uri.charset = splits[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return uri, nil
|
||||
}
|
||||
|
||||
type base struct {
|
||||
parser parser
|
||||
driverName string
|
||||
dataSourceName string
|
||||
*uri
|
||||
}
|
||||
|
||||
func (b *base) init(parser parser, drivername, dataSourceName string) (err error) {
|
||||
b.parser = parser
|
||||
b.driverName, b.dataSourceName = drivername, dataSourceName
|
||||
b.uri, err = b.parser.parse(b.driverName, b.dataSourceName)
|
||||
return
|
||||
}
|
||||
|
||||
func (b *base) URI() *uri {
|
||||
return b.uri
|
||||
}
|
||||
|
||||
func (b *base) DBType() string {
|
||||
return b.uri.dbType
|
||||
}
|
||||
|
||||
func (db *base) RollBackStr() string {
|
||||
return "ROLL BACK"
|
||||
}
|
||||
|
||||
func (db *base) DropTableSql(tableName string) string {
|
||||
return fmt.Sprintf("DROP TABLE IF EXISTS `%s`", tableName)
|
||||
}
|
||||
|
||||
type mysql struct {
|
||||
base
|
||||
net string
|
||||
addr string
|
||||
params map[string]string
|
||||
loc *time.Location
|
||||
timeout time.Duration
|
||||
tls *tls.Config
|
||||
allowAllFiles bool
|
||||
allowOldPasswords bool
|
||||
clientFoundRows bool
|
||||
}
|
||||
|
||||
func (db *mysql) Init(drivername, uri string) error {
|
||||
return db.base.init(&mysqlParser{}, drivername, uri)
|
||||
}
|
||||
|
||||
func (db *mysql) SqlType(c *Column) string {
|
||||
var res string
|
||||
switch t := c.SQLType.Name; t {
|
||||
case Bool:
|
||||
res = TinyInt
|
||||
c.Length = 1
|
||||
case Serial:
|
||||
c.IsAutoIncrement = true
|
||||
c.IsPrimaryKey = true
|
||||
c.Nullable = false
|
||||
res = Int
|
||||
case BigSerial:
|
||||
c.IsAutoIncrement = true
|
||||
c.IsPrimaryKey = true
|
||||
c.Nullable = false
|
||||
res = BigInt
|
||||
case Bytea:
|
||||
res = Blob
|
||||
case TimeStampz:
|
||||
res = Char
|
||||
c.Length = 64
|
||||
default:
|
||||
res = t
|
||||
}
|
||||
|
||||
var hasLen1 bool = (c.Length > 0)
|
||||
var hasLen2 bool = (c.Length2 > 0)
|
||||
if hasLen1 {
|
||||
res += "(" + strconv.Itoa(c.Length) + ")"
|
||||
} else if hasLen2 {
|
||||
res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")"
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (db *mysql) SupportInsertMany() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (db *mysql) QuoteStr() string {
|
||||
return "`"
|
||||
}
|
||||
|
||||
func (db *mysql) SupportEngine() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (db *mysql) AutoIncrStr() string {
|
||||
return "AUTO_INCREMENT"
|
||||
}
|
||||
|
||||
func (db *mysql) SupportCharset() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (db *mysql) IndexOnTable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (db *mysql) IndexCheckSql(tableName, idxName string) (string, []interface{}) {
|
||||
args := []interface{}{db.dbName, tableName, idxName}
|
||||
sql := "SELECT `INDEX_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS`"
|
||||
sql += " WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `INDEX_NAME`=?"
|
||||
return sql, args
|
||||
}
|
||||
|
||||
func (db *mysql) ColumnCheckSql(tableName, colName string) (string, []interface{}) {
|
||||
args := []interface{}{db.dbName, tableName, colName}
|
||||
sql := "SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ?"
|
||||
return sql, args
|
||||
}
|
||||
|
||||
func (db *mysql) TableCheckSql(tableName string) (string, []interface{}) {
|
||||
args := []interface{}{db.dbName, tableName}
|
||||
sql := "SELECT `TABLE_NAME` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? and `TABLE_NAME`=?"
|
||||
return sql, args
|
||||
}
|
||||
|
||||
func (db *mysql) GetColumns(tableName string) ([]string, map[string]*Column, error) {
|
||||
args := []interface{}{db.dbName, tableName}
|
||||
s := "SELECT `COLUMN_NAME`, `IS_NULLABLE`, `COLUMN_DEFAULT`, `COLUMN_TYPE`," +
|
||||
" `COLUMN_KEY`, `EXTRA` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?"
|
||||
cnn, err := sql.Open(db.driverName, db.dataSourceName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer cnn.Close()
|
||||
res, err := query(cnn, s, args...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cols := make(map[string]*Column)
|
||||
colSeq := make([]string, 0)
|
||||
for _, record := range res {
|
||||
col := new(Column)
|
||||
col.Indexes = make(map[string]bool)
|
||||
for name, content := range record {
|
||||
switch name {
|
||||
case "COLUMN_NAME":
|
||||
col.Name = strings.Trim(string(content), "` ")
|
||||
case "IS_NULLABLE":
|
||||
if "YES" == string(content) {
|
||||
col.Nullable = true
|
||||
}
|
||||
case "COLUMN_DEFAULT":
|
||||
// add ''
|
||||
col.Default = string(content)
|
||||
if col.Default == "" {
|
||||
col.DefaultIsEmpty = true
|
||||
}
|
||||
case "COLUMN_TYPE":
|
||||
cts := strings.Split(string(content), "(")
|
||||
var len1, len2 int
|
||||
if len(cts) == 2 {
|
||||
idx := strings.Index(cts[1], ")")
|
||||
lens := strings.Split(cts[1][0:idx], ",")
|
||||
len1, err = strconv.Atoi(strings.TrimSpace(lens[0]))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(lens) == 2 {
|
||||
len2, err = strconv.Atoi(lens[1])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
colName := cts[0]
|
||||
colType := strings.ToUpper(colName)
|
||||
col.Length = len1
|
||||
col.Length2 = len2
|
||||
if _, ok := sqlTypes[colType]; ok {
|
||||
col.SQLType = SQLType{colType, len1, len2}
|
||||
} else {
|
||||
return nil, nil, errors.New(fmt.Sprintf("unkonw colType %v", colType))
|
||||
}
|
||||
case "COLUMN_KEY":
|
||||
key := string(content)
|
||||
if key == "PRI" {
|
||||
col.IsPrimaryKey = true
|
||||
}
|
||||
if key == "UNI" {
|
||||
//col.is
|
||||
}
|
||||
case "EXTRA":
|
||||
extra := string(content)
|
||||
if extra == "auto_increment" {
|
||||
col.IsAutoIncrement = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if col.SQLType.IsText() {
|
||||
if col.Default != "" {
|
||||
col.Default = "'" + col.Default + "'"
|
||||
} else {
|
||||
if col.DefaultIsEmpty {
|
||||
col.Default = "''"
|
||||
}
|
||||
}
|
||||
}
|
||||
cols[col.Name] = col
|
||||
colSeq = append(colSeq, col.Name)
|
||||
}
|
||||
return colSeq, cols, nil
|
||||
}
|
||||
|
||||
func (db *mysql) GetTables() ([]*Table, error) {
|
||||
args := []interface{}{db.dbName}
|
||||
s := "SELECT `TABLE_NAME`, `ENGINE`, `TABLE_ROWS`, `AUTO_INCREMENT` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=?"
|
||||
cnn, err := sql.Open(db.driverName, db.dataSourceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cnn.Close()
|
||||
res, err := query(cnn, s, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tables := make([]*Table, 0)
|
||||
for _, record := range res {
|
||||
table := new(Table)
|
||||
for name, content := range record {
|
||||
switch name {
|
||||
case "TABLE_NAME":
|
||||
table.Name = strings.Trim(string(content), "` ")
|
||||
case "ENGINE":
|
||||
}
|
||||
}
|
||||
tables = append(tables, table)
|
||||
}
|
||||
return tables, nil
|
||||
}
|
||||
|
||||
func (db *mysql) GetIndexes(tableName string) (map[string]*Index, error) {
|
||||
args := []interface{}{db.dbName, tableName}
|
||||
s := "SELECT `INDEX_NAME`, `NON_UNIQUE`, `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?"
|
||||
cnn, err := sql.Open(db.driverName, db.dataSourceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cnn.Close()
|
||||
res, err := query(cnn, s, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
indexes := make(map[string]*Index, 0)
|
||||
for _, record := range res {
|
||||
var indexType int
|
||||
var indexName, colName string
|
||||
for name, content := range record {
|
||||
switch name {
|
||||
case "NON_UNIQUE":
|
||||
if "YES" == string(content) || string(content) == "1" {
|
||||
indexType = IndexType
|
||||
} else {
|
||||
indexType = UniqueType
|
||||
}
|
||||
case "INDEX_NAME":
|
||||
indexName = string(content)
|
||||
case "COLUMN_NAME":
|
||||
colName = strings.Trim(string(content), "` ")
|
||||
}
|
||||
}
|
||||
if indexName == "PRIMARY" {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
|
||||
indexName = indexName[5+len(tableName) : len(indexName)]
|
||||
}
|
||||
|
||||
var index *Index
|
||||
var ok bool
|
||||
if index, ok = indexes[indexName]; !ok {
|
||||
index = new(Index)
|
||||
index.Type = indexType
|
||||
index.Name = indexName
|
||||
indexes[indexName] = index
|
||||
}
|
||||
index.AddColumn(colName)
|
||||
}
|
||||
return indexes, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,269 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-xorm/core"
|
||||
)
|
||||
|
||||
// func init() {
|
||||
// RegisterDialect("mysql", &mysql{})
|
||||
// }
|
||||
|
||||
type mysql struct {
|
||||
core.Base
|
||||
net string
|
||||
addr string
|
||||
params map[string]string
|
||||
loc *time.Location
|
||||
timeout time.Duration
|
||||
tls *tls.Config
|
||||
allowAllFiles bool
|
||||
allowOldPasswords bool
|
||||
clientFoundRows bool
|
||||
}
|
||||
|
||||
func (db *mysql) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error {
|
||||
return db.Base.Init(d, db, uri, drivername, dataSourceName)
|
||||
}
|
||||
|
||||
func (db *mysql) SqlType(c *core.Column) string {
|
||||
var res string
|
||||
switch t := c.SQLType.Name; t {
|
||||
case core.Bool:
|
||||
res = core.TinyInt
|
||||
c.Length = 1
|
||||
case core.Serial:
|
||||
c.IsAutoIncrement = true
|
||||
c.IsPrimaryKey = true
|
||||
c.Nullable = false
|
||||
res = core.Int
|
||||
case core.BigSerial:
|
||||
c.IsAutoIncrement = true
|
||||
c.IsPrimaryKey = true
|
||||
c.Nullable = false
|
||||
res = core.BigInt
|
||||
case core.Bytea:
|
||||
res = core.Blob
|
||||
case core.TimeStampz:
|
||||
res = core.Char
|
||||
c.Length = 64
|
||||
default:
|
||||
res = t
|
||||
}
|
||||
|
||||
var hasLen1 bool = (c.Length > 0)
|
||||
var hasLen2 bool = (c.Length2 > 0)
|
||||
if hasLen1 {
|
||||
res += "(" + strconv.Itoa(c.Length) + ")"
|
||||
} else if hasLen2 {
|
||||
res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")"
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (db *mysql) SupportInsertMany() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (db *mysql) QuoteStr() string {
|
||||
return "`"
|
||||
}
|
||||
|
||||
func (db *mysql) SupportEngine() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (db *mysql) AutoIncrStr() string {
|
||||
return "AUTO_INCREMENT"
|
||||
}
|
||||
|
||||
func (db *mysql) SupportCharset() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (db *mysql) IndexOnTable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (db *mysql) IndexCheckSql(tableName, idxName string) (string, []interface{}) {
|
||||
args := []interface{}{db.DbName, tableName, idxName}
|
||||
sql := "SELECT `INDEX_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS`"
|
||||
sql += " WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `INDEX_NAME`=?"
|
||||
return sql, args
|
||||
}
|
||||
|
||||
func (db *mysql) ColumnCheckSql(tableName, colName string) (string, []interface{}) {
|
||||
args := []interface{}{db.DbName, tableName, colName}
|
||||
sql := "SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ?"
|
||||
return sql, args
|
||||
}
|
||||
|
||||
func (db *mysql) TableCheckSql(tableName string) (string, []interface{}) {
|
||||
args := []interface{}{db.DbName, tableName}
|
||||
sql := "SELECT `TABLE_NAME` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? and `TABLE_NAME`=?"
|
||||
return sql, args
|
||||
}
|
||||
|
||||
func (db *mysql) GetColumns(tableName string) ([]string, map[string]*core.Column, error) {
|
||||
args := []interface{}{db.DbName, tableName}
|
||||
s := "SELECT `COLUMN_NAME`, `IS_NULLABLE`, `COLUMN_DEFAULT`, `COLUMN_TYPE`," +
|
||||
" `COLUMN_KEY`, `EXTRA` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?"
|
||||
|
||||
rows, err := db.DB().Query(s, args...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
cols := make(map[string]*core.Column)
|
||||
colSeq := make([]string, 0)
|
||||
for rows.Next() {
|
||||
col := new(core.Column)
|
||||
col.Indexes = make(map[string]bool)
|
||||
|
||||
var columnName, isNullable, colType, colKey, extra string
|
||||
var colDefault *string
|
||||
err = rows.Scan(&columnName, &isNullable, &colDefault, &colType, &colKey, &extra)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
col.Name = strings.Trim(columnName, "` ")
|
||||
if "YES" == isNullable {
|
||||
col.Nullable = true
|
||||
}
|
||||
|
||||
if colDefault != nil {
|
||||
col.Default = *colDefault
|
||||
}
|
||||
|
||||
cts := strings.Split(colType, "(")
|
||||
var len1, len2 int
|
||||
if len(cts) == 2 {
|
||||
idx := strings.Index(cts[1], ")")
|
||||
lens := strings.Split(cts[1][0:idx], ",")
|
||||
len1, err = strconv.Atoi(strings.TrimSpace(lens[0]))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(lens) == 2 {
|
||||
len2, err = strconv.Atoi(lens[1])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
colName := cts[0]
|
||||
colType = strings.ToUpper(colName)
|
||||
col.Length = len1
|
||||
col.Length2 = len2
|
||||
if _, ok := core.SqlTypes[colType]; ok {
|
||||
col.SQLType = core.SQLType{colType, len1, len2}
|
||||
} else {
|
||||
return nil, nil, errors.New(fmt.Sprintf("unkonw colType %v", colType))
|
||||
}
|
||||
|
||||
if colKey == "PRI" {
|
||||
col.IsPrimaryKey = true
|
||||
}
|
||||
if colKey == "UNI" {
|
||||
//col.is
|
||||
}
|
||||
|
||||
if extra == "auto_increment" {
|
||||
col.IsAutoIncrement = true
|
||||
}
|
||||
|
||||
if col.SQLType.IsText() {
|
||||
if col.Default != "" {
|
||||
col.Default = "'" + col.Default + "'"
|
||||
}
|
||||
}
|
||||
cols[col.Name] = col
|
||||
colSeq = append(colSeq, col.Name)
|
||||
}
|
||||
return colSeq, cols, nil
|
||||
}
|
||||
|
||||
func (db *mysql) GetTables() ([]*core.Table, error) {
|
||||
args := []interface{}{db.DbName}
|
||||
s := "SELECT `TABLE_NAME`, `ENGINE`, `TABLE_ROWS`, `AUTO_INCREMENT` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=?"
|
||||
|
||||
rows, err := db.DB().Query(s, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
tables := make([]*core.Table, 0)
|
||||
for rows.Next() {
|
||||
table := core.NewEmptyTable()
|
||||
var name, engine, tableRows string
|
||||
var autoIncr *string
|
||||
err = rows.Scan(&name, &engine, &tableRows, &autoIncr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
table.Name = name
|
||||
tables = append(tables, table)
|
||||
}
|
||||
return tables, nil
|
||||
}
|
||||
|
||||
func (db *mysql) GetIndexes(tableName string) (map[string]*core.Index, error) {
|
||||
args := []interface{}{db.DbName, tableName}
|
||||
s := "SELECT `INDEX_NAME`, `NON_UNIQUE`, `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?"
|
||||
|
||||
rows, err := db.DB().Query(s, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
indexes := make(map[string]*core.Index, 0)
|
||||
for rows.Next() {
|
||||
var indexType int
|
||||
var indexName, colName, nonUnique string
|
||||
err = rows.Scan(&indexName, &nonUnique, &colName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if indexName == "PRIMARY" {
|
||||
continue
|
||||
}
|
||||
|
||||
if "YES" == nonUnique || nonUnique == "1" {
|
||||
indexType = core.IndexType
|
||||
} else {
|
||||
indexType = core.UniqueType
|
||||
}
|
||||
|
||||
colName = strings.Trim(colName, "` ")
|
||||
|
||||
if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
|
||||
indexName = indexName[5+len(tableName) : len(indexName)]
|
||||
}
|
||||
|
||||
var index *core.Index
|
||||
var ok bool
|
||||
if index, ok = indexes[indexName]; !ok {
|
||||
index = new(core.Index)
|
||||
index.Type = indexType
|
||||
index.Name = indexName
|
||||
indexes[indexName] = index
|
||||
}
|
||||
index.AddColumn(colName)
|
||||
}
|
||||
return indexes, nil
|
||||
}
|
||||
|
||||
func (db *mysql) Filters() []core.Filter {
|
||||
return []core.Filter{&core.IdFilter{}}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-xorm/core"
|
||||
)
|
||||
|
||||
// func init() {
|
||||
// core.RegisterDriver("mysql", &mysqlDriver{})
|
||||
// }
|
||||
|
||||
type mysqlDriver struct {
|
||||
}
|
||||
|
||||
func (p *mysqlDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) {
|
||||
dsnPattern := regexp.MustCompile(
|
||||
`^(?:(?P<user>.*?)(?::(?P<passwd>.*))?@)?` + // [user[:password]@]
|
||||
`(?:(?P<net>[^\(]*)(?:\((?P<addr>[^\)]*)\))?)?` + // [net[(addr)]]
|
||||
`\/(?P<dbname>.*?)` + // /dbname
|
||||
`(?:\?(?P<params>[^\?]*))?$`) // [?param1=value1¶mN=valueN]
|
||||
matches := dsnPattern.FindStringSubmatch(dataSourceName)
|
||||
//tlsConfigRegister := make(map[string]*tls.Config)
|
||||
names := dsnPattern.SubexpNames()
|
||||
|
||||
uri := &core.Uri{DbType: core.MYSQL}
|
||||
|
||||
for i, match := range matches {
|
||||
switch names[i] {
|
||||
case "dbname":
|
||||
uri.DbName = match
|
||||
case "params":
|
||||
if len(match) > 0 {
|
||||
kvs := strings.Split(match, "&")
|
||||
for _, kv := range kvs {
|
||||
splits := strings.Split(kv, "=")
|
||||
if len(splits) == 2 {
|
||||
switch splits[0] {
|
||||
case "charset":
|
||||
uri.Charset = splits[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return uri, nil
|
||||
}
|
||||
191
mysql_test.go
191
mysql_test.go
|
|
@ -1,191 +0,0 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
/*
|
||||
CREATE DATABASE IF NOT EXISTS xorm_test CHARACTER SET
|
||||
utf8 COLLATE utf8_general_ci;
|
||||
*/
|
||||
|
||||
func TestMysql(t *testing.T) {
|
||||
err := mysqlDdlImport()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
engine, err := NewEngine("mysql", "root:@/xorm_test?charset=utf8")
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
engine.ShowSQL = showTestSql
|
||||
engine.ShowErr = showTestSql
|
||||
engine.ShowWarn = showTestSql
|
||||
engine.ShowDebug = showTestSql
|
||||
|
||||
testAll(engine, t)
|
||||
testAll2(engine, t)
|
||||
testAll3(engine, t)
|
||||
}
|
||||
|
||||
func TestMysqlSameMapper(t *testing.T) {
|
||||
err := mysqlDdlImport()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
engine, err := NewEngine("mysql", "root:@/xorm_test3?charset=utf8")
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
engine.ShowSQL = showTestSql
|
||||
engine.ShowErr = showTestSql
|
||||
engine.ShowWarn = showTestSql
|
||||
engine.ShowDebug = showTestSql
|
||||
engine.SetMapper(SameMapper{})
|
||||
|
||||
testAll(engine, t)
|
||||
testAll2(engine, t)
|
||||
testAll3(engine, t)
|
||||
}
|
||||
|
||||
func TestMysqlWithCache(t *testing.T) {
|
||||
err := mysqlDdlImport()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
engine, err := NewEngine("mysql", "root:@/xorm_test?charset=utf8")
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000))
|
||||
engine.ShowSQL = showTestSql
|
||||
engine.ShowErr = showTestSql
|
||||
engine.ShowWarn = showTestSql
|
||||
engine.ShowDebug = showTestSql
|
||||
|
||||
testAll(engine, t)
|
||||
testAll2(engine, t)
|
||||
}
|
||||
|
||||
func newMysqlEngine() (*Engine, error) {
|
||||
return NewEngine("mysql", "root:@/xorm_test?charset=utf8")
|
||||
}
|
||||
|
||||
func mysqlDdlImport() error {
|
||||
engine, err := NewEngine("mysql", "root:@/?charset=utf8")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
engine.ShowSQL = showTestSql
|
||||
engine.ShowErr = showTestSql
|
||||
engine.ShowWarn = showTestSql
|
||||
engine.ShowDebug = showTestSql
|
||||
|
||||
sqlResults, _ := engine.Import("tests/mysql_ddl.sql")
|
||||
engine.LogDebug("sql results: %v", sqlResults)
|
||||
engine.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func newMysqlDriverDB() (*sql.DB, error) {
|
||||
return sql.Open("mysql", "root:@/xorm_test?charset=utf8")
|
||||
}
|
||||
|
||||
const (
|
||||
createTableMySql = "CREATE TABLE IF NOT EXISTS `big_struct` (`id` BIGINT PRIMARY KEY AUTO_INCREMENT NOT NULL, `name` VARCHAR(255) NULL, `title` VARCHAR(255) NULL, `age` VARCHAR(255) NULL, `alias` VARCHAR(255) NULL, `nick_name` VARCHAR(255) NULL);"
|
||||
dropTableMySql = "DROP TABLE IF EXISTS `big_struct`;"
|
||||
)
|
||||
|
||||
func BenchmarkMysqlDriverInsert(t *testing.B) {
|
||||
doBenchDriver(newMysqlDriverDB, createTableMySql, dropTableMySql,
|
||||
doBenchDriverInsert, t)
|
||||
}
|
||||
|
||||
func BenchmarkMysqlDriverFind(t *testing.B) {
|
||||
doBenchDriver(newMysqlDriverDB, createTableMySql, dropTableMySql,
|
||||
doBenchDriverFind, t)
|
||||
}
|
||||
|
||||
func BenchmarkMysqlNoCacheInsert(t *testing.B) {
|
||||
engine, err := newMysqlEngine()
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
//engine.ShowSQL = true
|
||||
doBenchInsert(engine, t)
|
||||
}
|
||||
|
||||
func BenchmarkMysqlNoCacheFind(t *testing.B) {
|
||||
engine, err := newMysqlEngine()
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
//engine.ShowSQL = true
|
||||
doBenchFind(engine, t)
|
||||
}
|
||||
|
||||
func BenchmarkMysqlNoCacheFindPtr(t *testing.B) {
|
||||
engine, err := newMysqlEngine()
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
//engine.ShowSQL = true
|
||||
doBenchFindPtr(engine, t)
|
||||
}
|
||||
|
||||
func BenchmarkMysqlCacheInsert(t *testing.B) {
|
||||
engine, err := newMysqlEngine()
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000))
|
||||
|
||||
doBenchInsert(engine, t)
|
||||
}
|
||||
|
||||
func BenchmarkMysqlCacheFind(t *testing.B) {
|
||||
engine, err := newMysqlEngine()
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000))
|
||||
|
||||
doBenchFind(engine, t)
|
||||
}
|
||||
|
||||
func BenchmarkMysqlCacheFindPtr(t *testing.B) {
|
||||
engine, err := newMysqlEngine()
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000))
|
||||
|
||||
doBenchFindPtr(engine, t)
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
|
||||
"github.com/go-xorm/core"
|
||||
)
|
||||
|
||||
// func init() {
|
||||
// core.RegisterDriver("oci8", &oci8Driver{})
|
||||
// }
|
||||
|
||||
type oci8Driver struct {
|
||||
}
|
||||
|
||||
//dataSourceName=user/password@ipv4:port/dbname
|
||||
//dataSourceName=user/password@[ipv6]:port/dbname
|
||||
func (p *oci8Driver) Parse(driverName, dataSourceName string) (*core.Uri, error) {
|
||||
db := &core.Uri{DbType: core.ORACLE}
|
||||
dsnPattern := regexp.MustCompile(
|
||||
`^(?P<user>.*)\/(?P<password>.*)@` + // user:password@
|
||||
`(?P<net>.*)` + // ip:port
|
||||
`\/(?P<dbname>.*)`) // dbname
|
||||
matches := dsnPattern.FindStringSubmatch(dataSourceName)
|
||||
names := dsnPattern.SubexpNames()
|
||||
for i, match := range matches {
|
||||
switch names[i] {
|
||||
case "dbname":
|
||||
db.DbName = match
|
||||
}
|
||||
}
|
||||
if db.DbName == "" {
|
||||
return nil, errors.New("dbname is empty")
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/go-xorm/core"
|
||||
)
|
||||
|
||||
// func init() {
|
||||
// core.RegisterDriver("odbc", &odbcDriver{})
|
||||
// }
|
||||
|
||||
type odbcDriver struct {
|
||||
}
|
||||
|
||||
func (p *odbcDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) {
|
||||
kv := strings.Split(dataSourceName, ";")
|
||||
var dbName string
|
||||
|
||||
for _, c := range kv {
|
||||
vv := strings.Split(strings.TrimSpace(c), "=")
|
||||
if len(vv) == 2 {
|
||||
switch strings.ToLower(vv[0]) {
|
||||
case "database":
|
||||
dbName = vv[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
if dbName == "" {
|
||||
return nil, errors.New("no db name provided")
|
||||
}
|
||||
return &core.Uri{DbName: dbName, DbType: core.MSSQL}, nil
|
||||
}
|
||||
265
oracle.go
265
oracle.go
|
|
@ -1,265 +0,0 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type oracle struct {
|
||||
base
|
||||
}
|
||||
|
||||
type oracleParser struct {
|
||||
}
|
||||
|
||||
//dataSourceName=user/password@ipv4:port/dbname
|
||||
//dataSourceName=user/password@[ipv6]:port/dbname
|
||||
func (p *oracleParser) parse(driverName, dataSourceName string) (*uri, error) {
|
||||
db := &uri{dbType: ORACLE_OCI}
|
||||
dsnPattern := regexp.MustCompile(
|
||||
`^(?P<user>.*)\/(?P<password>.*)@` + // user:password@
|
||||
`(?P<net>.*)` + // ip:port
|
||||
`\/(?P<dbname>.*)`) // dbname
|
||||
matches := dsnPattern.FindStringSubmatch(dataSourceName)
|
||||
names := dsnPattern.SubexpNames()
|
||||
for i, match := range matches {
|
||||
switch names[i] {
|
||||
case "dbname":
|
||||
db.dbName = match
|
||||
}
|
||||
}
|
||||
if db.dbName == "" {
|
||||
return nil, errors.New("dbname is empty")
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func (db *oracle) Init(drivername, uri string) error {
|
||||
return db.base.init(&oracleParser{}, drivername, uri)
|
||||
}
|
||||
|
||||
func (db *oracle) SqlType(c *Column) string {
|
||||
var res string
|
||||
switch t := c.SQLType.Name; t {
|
||||
case Bit, TinyInt, SmallInt, MediumInt, Int, Integer, BigInt, Bool, Serial, BigSerial:
|
||||
return "NUMBER"
|
||||
case Binary, VarBinary, Blob, TinyBlob, MediumBlob, LongBlob, Bytea:
|
||||
return Blob
|
||||
case Time, DateTime, TimeStamp:
|
||||
res = TimeStamp
|
||||
case TimeStampz:
|
||||
res = "TIMESTAMP WITH TIME ZONE"
|
||||
case Float, Double, Numeric, Decimal:
|
||||
res = "NUMBER"
|
||||
case Text, MediumText, LongText:
|
||||
res = "CLOB"
|
||||
case Char, Varchar, TinyText:
|
||||
return "VARCHAR2"
|
||||
default:
|
||||
res = t
|
||||
}
|
||||
|
||||
var hasLen1 bool = (c.Length > 0)
|
||||
var hasLen2 bool = (c.Length2 > 0)
|
||||
if hasLen1 {
|
||||
res += "(" + strconv.Itoa(c.Length) + ")"
|
||||
} else if hasLen2 {
|
||||
res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")"
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (db *oracle) SupportInsertMany() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (db *oracle) QuoteStr() string {
|
||||
return "\""
|
||||
}
|
||||
|
||||
func (db *oracle) AutoIncrStr() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (db *oracle) SupportEngine() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (db *oracle) SupportCharset() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (db *oracle) IndexOnTable() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (db *oracle) IndexCheckSql(tableName, idxName string) (string, []interface{}) {
|
||||
args := []interface{}{strings.ToUpper(tableName), strings.ToUpper(idxName)}
|
||||
return `SELECT INDEX_NAME FROM USER_INDEXES ` +
|
||||
`WHERE TABLE_NAME = ? AND INDEX_NAME = ?`, args
|
||||
}
|
||||
|
||||
func (db *oracle) TableCheckSql(tableName string) (string, []interface{}) {
|
||||
args := []interface{}{strings.ToUpper(tableName)}
|
||||
return `SELECT table_name FROM user_tables WHERE table_name = ?`, args
|
||||
}
|
||||
|
||||
func (db *oracle) ColumnCheckSql(tableName, colName string) (string, []interface{}) {
|
||||
args := []interface{}{strings.ToUpper(tableName), strings.ToUpper(colName)}
|
||||
return "SELECT column_name FROM USER_TAB_COLUMNS WHERE table_name = ?" +
|
||||
" AND column_name = ?", args
|
||||
}
|
||||
|
||||
func (db *oracle) GetColumns(tableName string) ([]string, map[string]*Column, error) {
|
||||
args := []interface{}{strings.ToUpper(tableName)}
|
||||
s := "SELECT column_name,data_default,data_type,data_length,data_precision,data_scale," +
|
||||
"nullable FROM USER_TAB_COLUMNS WHERE table_name = :1"
|
||||
|
||||
cnn, err := sql.Open(db.driverName, db.dataSourceName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer cnn.Close()
|
||||
res, err := query(cnn, s, args...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cols := make(map[string]*Column)
|
||||
colSeq := make([]string, 0)
|
||||
for _, record := range res {
|
||||
col := new(Column)
|
||||
col.Indexes = make(map[string]bool)
|
||||
for name, content := range record {
|
||||
switch name {
|
||||
case "column_name":
|
||||
col.Name = strings.Trim(string(content), `" `)
|
||||
case "data_default":
|
||||
col.Default = string(content)
|
||||
if col.Default == "" {
|
||||
col.DefaultIsEmpty = true
|
||||
}
|
||||
case "nullable":
|
||||
if string(content) == "Y" {
|
||||
col.Nullable = true
|
||||
} else {
|
||||
col.Nullable = false
|
||||
}
|
||||
case "data_type":
|
||||
ct := string(content)
|
||||
switch ct {
|
||||
case "VARCHAR2":
|
||||
col.SQLType = SQLType{Varchar, 0, 0}
|
||||
case "TIMESTAMP WITH TIME ZONE":
|
||||
col.SQLType = SQLType{TimeStamp, 0, 0}
|
||||
default:
|
||||
col.SQLType = SQLType{strings.ToUpper(ct), 0, 0}
|
||||
}
|
||||
if _, ok := sqlTypes[col.SQLType.Name]; !ok {
|
||||
return nil, nil, errors.New(fmt.Sprintf("unkonw colType %v", ct))
|
||||
}
|
||||
case "data_length":
|
||||
i, err := strconv.Atoi(string(content))
|
||||
if err != nil {
|
||||
return nil, nil, errors.New("retrieve length error")
|
||||
}
|
||||
col.Length = i
|
||||
case "data_precision":
|
||||
case "data_scale":
|
||||
}
|
||||
}
|
||||
if col.SQLType.IsText() {
|
||||
if col.Default != "" {
|
||||
col.Default = "'" + col.Default + "'"
|
||||
}else{
|
||||
if col.DefaultIsEmpty {
|
||||
col.Default = "''"
|
||||
}
|
||||
}
|
||||
}
|
||||
cols[col.Name] = col
|
||||
colSeq = append(colSeq, col.Name)
|
||||
}
|
||||
|
||||
return colSeq, cols, nil
|
||||
}
|
||||
|
||||
func (db *oracle) GetTables() ([]*Table, error) {
|
||||
args := []interface{}{}
|
||||
s := "SELECT table_name FROM user_tables"
|
||||
cnn, err := sql.Open(db.driverName, db.dataSourceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cnn.Close()
|
||||
res, err := query(cnn, s, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tables := make([]*Table, 0)
|
||||
for _, record := range res {
|
||||
table := new(Table)
|
||||
for name, content := range record {
|
||||
switch name {
|
||||
case "table_name":
|
||||
table.Name = string(content)
|
||||
}
|
||||
}
|
||||
tables = append(tables, table)
|
||||
}
|
||||
return tables, nil
|
||||
}
|
||||
|
||||
func (db *oracle) GetIndexes(tableName string) (map[string]*Index, error) {
|
||||
args := []interface{}{tableName}
|
||||
s := "SELECT t.column_name,i.table_name,i.uniqueness,i.index_name FROM user_ind_columns t,user_indexes i " +
|
||||
"WHERE t.index_name = i.index_name and t.table_name = i.table_name and t.table_name =:1"
|
||||
|
||||
cnn, err := sql.Open(db.driverName, db.dataSourceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cnn.Close()
|
||||
res, err := query(cnn, s, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
indexes := make(map[string]*Index, 0)
|
||||
for _, record := range res {
|
||||
var indexType int
|
||||
var indexName string
|
||||
var colName string
|
||||
|
||||
for name, content := range record {
|
||||
switch name {
|
||||
case "index_name":
|
||||
indexName = strings.Trim(string(content), `" `)
|
||||
case "uniqueness":
|
||||
c := string(content)
|
||||
if c == "UNIQUE" {
|
||||
indexType = UniqueType
|
||||
} else {
|
||||
indexType = IndexType
|
||||
}
|
||||
case "column_name":
|
||||
colName = string(content)
|
||||
}
|
||||
}
|
||||
|
||||
var index *Index
|
||||
var ok bool
|
||||
if index, ok = indexes[indexName]; !ok {
|
||||
index = new(Index)
|
||||
index.Type = indexType
|
||||
index.Name = indexName
|
||||
indexes[indexName] = index
|
||||
}
|
||||
index.AddColumn(colName)
|
||||
}
|
||||
return indexes, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,241 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-xorm/core"
|
||||
)
|
||||
|
||||
// func init() {
|
||||
// RegisterDialect("oracle", &oracle{})
|
||||
// }
|
||||
|
||||
type oracle struct {
|
||||
core.Base
|
||||
}
|
||||
|
||||
func (db *oracle) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error {
|
||||
return db.Base.Init(d, db, uri, drivername, dataSourceName)
|
||||
}
|
||||
|
||||
func (db *oracle) SqlType(c *core.Column) string {
|
||||
var res string
|
||||
switch t := c.SQLType.Name; t {
|
||||
case core.Bit, core.TinyInt, core.SmallInt, core.MediumInt, core.Int, core.Integer, core.BigInt, core.Bool, core.Serial, core.BigSerial:
|
||||
return "NUMBER"
|
||||
case core.Binary, core.VarBinary, core.Blob, core.TinyBlob, core.MediumBlob, core.LongBlob, core.Bytea:
|
||||
return core.Blob
|
||||
case core.Time, core.DateTime, core.TimeStamp:
|
||||
res = core.TimeStamp
|
||||
case core.TimeStampz:
|
||||
res = "TIMESTAMP WITH TIME ZONE"
|
||||
case core.Float, core.Double, core.Numeric, core.Decimal:
|
||||
res = "NUMBER"
|
||||
case core.Text, core.MediumText, core.LongText:
|
||||
res = "CLOB"
|
||||
case core.Char, core.Varchar, core.TinyText:
|
||||
return "VARCHAR2"
|
||||
default:
|
||||
res = t
|
||||
}
|
||||
|
||||
var hasLen1 bool = (c.Length > 0)
|
||||
var hasLen2 bool = (c.Length2 > 0)
|
||||
if hasLen1 {
|
||||
res += "(" + strconv.Itoa(c.Length) + ")"
|
||||
} else if hasLen2 {
|
||||
res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")"
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (db *oracle) SupportInsertMany() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (db *oracle) QuoteStr() string {
|
||||
return "\""
|
||||
}
|
||||
|
||||
func (db *oracle) AutoIncrStr() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (db *oracle) SupportEngine() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (db *oracle) SupportCharset() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (db *oracle) IndexOnTable() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (db *oracle) IndexCheckSql(tableName, idxName string) (string, []interface{}) {
|
||||
args := []interface{}{strings.ToUpper(tableName), strings.ToUpper(idxName)}
|
||||
return `SELECT INDEX_NAME FROM USER_INDEXES ` +
|
||||
`WHERE TABLE_NAME = ? AND INDEX_NAME = ?`, args
|
||||
}
|
||||
|
||||
func (db *oracle) TableCheckSql(tableName string) (string, []interface{}) {
|
||||
args := []interface{}{strings.ToUpper(tableName)}
|
||||
return `SELECT table_name FROM user_tables WHERE table_name = ?`, args
|
||||
}
|
||||
|
||||
func (db *oracle) ColumnCheckSql(tableName, colName string) (string, []interface{}) {
|
||||
args := []interface{}{strings.ToUpper(tableName), strings.ToUpper(colName)}
|
||||
return "SELECT column_name FROM USER_TAB_COLUMNS WHERE table_name = ?" +
|
||||
" AND column_name = ?", args
|
||||
}
|
||||
|
||||
func (db *oracle) GetColumns(tableName string) ([]string, map[string]*core.Column, error) {
|
||||
args := []interface{}{strings.ToUpper(tableName)}
|
||||
s := "SELECT column_name,data_default,data_type,data_length,data_precision,data_scale," +
|
||||
"nullable FROM USER_TAB_COLUMNS WHERE table_name = :1"
|
||||
|
||||
rows, err := db.DB().Query(s, args...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
cols := make(map[string]*core.Column)
|
||||
colSeq := make([]string, 0)
|
||||
for rows.Next() {
|
||||
col := new(core.Column)
|
||||
col.Indexes = make(map[string]bool)
|
||||
|
||||
var colName, colDefault, nullable, dataType, dataPrecision, dataScale string
|
||||
var dataLen int
|
||||
|
||||
err = rows.Scan(&colName, &colDefault, &dataType, &dataLen, &dataPrecision,
|
||||
&dataScale, &nullable)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
col.Name = strings.Trim(colName, `" `)
|
||||
col.Default = colDefault
|
||||
|
||||
if nullable == "Y" {
|
||||
col.Nullable = true
|
||||
} else {
|
||||
col.Nullable = false
|
||||
}
|
||||
|
||||
switch dataType {
|
||||
case "VARCHAR2":
|
||||
col.SQLType = core.SQLType{core.Varchar, 0, 0}
|
||||
case "TIMESTAMP WITH TIME ZONE":
|
||||
col.SQLType = core.SQLType{core.TimeStampz, 0, 0}
|
||||
default:
|
||||
col.SQLType = core.SQLType{strings.ToUpper(dataType), 0, 0}
|
||||
}
|
||||
if _, ok := core.SqlTypes[col.SQLType.Name]; !ok {
|
||||
return nil, nil, errors.New(fmt.Sprintf("unkonw colType %v", dataType))
|
||||
}
|
||||
|
||||
col.Length = dataLen
|
||||
|
||||
if col.SQLType.IsText() {
|
||||
if col.Default != "" {
|
||||
col.Default = "'" + col.Default + "'"
|
||||
} else {
|
||||
if col.DefaultIsEmpty {
|
||||
col.Default = "''"
|
||||
}
|
||||
}
|
||||
}
|
||||
cols[col.Name] = col
|
||||
colSeq = append(colSeq, col.Name)
|
||||
}
|
||||
|
||||
return colSeq, cols, nil
|
||||
}
|
||||
|
||||
func (db *oracle) GetTables() ([]*core.Table, error) {
|
||||
args := []interface{}{}
|
||||
s := "SELECT table_name FROM user_tables"
|
||||
|
||||
rows, err := db.DB().Query(s, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
tables := make([]*core.Table, 0)
|
||||
for rows.Next() {
|
||||
table := core.NewEmptyTable()
|
||||
err = rows.Scan(&table.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tables = append(tables, table)
|
||||
}
|
||||
return tables, nil
|
||||
}
|
||||
|
||||
func (db *oracle) GetIndexes(tableName string) (map[string]*core.Index, error) {
|
||||
args := []interface{}{tableName}
|
||||
s := "SELECT t.column_name,i.uniqueness,i.index_name FROM user_ind_columns t,user_indexes i " +
|
||||
"WHERE t.index_name = i.index_name and t.table_name = i.table_name and t.table_name =:1"
|
||||
|
||||
rows, err := db.DB().Query(s, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
indexes := make(map[string]*core.Index, 0)
|
||||
for rows.Next() {
|
||||
var indexType int
|
||||
var indexName, colName, uniqueness string
|
||||
|
||||
err = rows.Scan(&colName, &uniqueness, &indexName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
indexName = strings.Trim(indexName, `" `)
|
||||
|
||||
if uniqueness == "UNIQUE" {
|
||||
indexType = core.UniqueType
|
||||
} else {
|
||||
indexType = core.IndexType
|
||||
}
|
||||
|
||||
var index *core.Index
|
||||
var ok bool
|
||||
if index, ok = indexes[indexName]; !ok {
|
||||
index = new(core.Index)
|
||||
index.Type = indexType
|
||||
index.Name = indexName
|
||||
indexes[indexName] = index
|
||||
}
|
||||
index.AddColumn(colName)
|
||||
}
|
||||
return indexes, nil
|
||||
}
|
||||
|
||||
// PgSeqFilter filter SQL replace ?, ? ... to :1, :2 ...
|
||||
type OracleSeqFilter struct {
|
||||
}
|
||||
|
||||
func (s *OracleSeqFilter) Do(sql string, dialect core.Dialect, table *core.Table) string {
|
||||
counts := strings.Count(sql, "?")
|
||||
for i := 1; i <= counts; i++ {
|
||||
newstr := ":" + fmt.Sprintf("%v", i)
|
||||
sql = strings.Replace(sql, "?", newstr, 1)
|
||||
}
|
||||
return sql
|
||||
}
|
||||
|
||||
func (db *oracle) Filters() []core.Filter {
|
||||
return []core.Filter{&core.QuoteFilter{}, &OracleSeqFilter{}, &core.IdFilter{}}
|
||||
}
|
||||
286
pool.go
286
pool.go
|
|
@ -1,286 +0,0 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
//"fmt"
|
||||
"sync"
|
||||
//"sync/atomic"
|
||||
"container/list"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Interface IConnecPool is a connection pool interface, all implements should implement
|
||||
// Init, RetrieveDB, ReleaseDB and Close methods.
|
||||
// Init for init when engine be created or invoke SetPool
|
||||
// RetrieveDB for requesting a connection to db;
|
||||
// ReleaseDB for releasing a db connection;
|
||||
// Close for invoking when engine.Close
|
||||
type IConnectPool interface {
|
||||
Init(engine *Engine) error
|
||||
RetrieveDB(engine *Engine) (*sql.DB, error)
|
||||
ReleaseDB(engine *Engine, db *sql.DB)
|
||||
Close(engine *Engine) error
|
||||
SetMaxIdleConns(conns int)
|
||||
MaxIdleConns() int
|
||||
SetMaxConns(conns int)
|
||||
MaxConns() int
|
||||
}
|
||||
|
||||
// Struct NoneConnectPool is a implement for IConnectPool. It provides directly invoke driver's
|
||||
// open and release connection function
|
||||
type NoneConnectPool struct {
|
||||
}
|
||||
|
||||
// NewNoneConnectPool new a NoneConnectPool.
|
||||
func NewNoneConnectPool() IConnectPool {
|
||||
return &NoneConnectPool{}
|
||||
}
|
||||
|
||||
// Init do nothing
|
||||
func (p *NoneConnectPool) Init(engine *Engine) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RetrieveDB directly open a connection
|
||||
func (p *NoneConnectPool) RetrieveDB(engine *Engine) (db *sql.DB, err error) {
|
||||
db, err = engine.OpenDB()
|
||||
return
|
||||
}
|
||||
|
||||
// ReleaseDB directly close a connection
|
||||
func (p *NoneConnectPool) ReleaseDB(engine *Engine, db *sql.DB) {
|
||||
db.Close()
|
||||
}
|
||||
|
||||
// Close do nothing
|
||||
func (p *NoneConnectPool) Close(engine *Engine) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *NoneConnectPool) SetMaxIdleConns(conns int) {
|
||||
}
|
||||
|
||||
func (p *NoneConnectPool) MaxIdleConns() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// not implemented
|
||||
func (p *NoneConnectPool) SetMaxConns(conns int) {
|
||||
}
|
||||
|
||||
// not implemented
|
||||
func (p *NoneConnectPool) MaxConns() int {
|
||||
return -1
|
||||
}
|
||||
|
||||
// Struct SysConnectPool is a simple wrapper for using system default connection pool.
|
||||
// About the system connection pool, you can review the code database/sql/sql.go
|
||||
// It's currently default Pool implments.
|
||||
type SysConnectPool struct {
|
||||
db *sql.DB
|
||||
maxIdleConns int
|
||||
maxConns int
|
||||
curConns int
|
||||
mutex *sync.Mutex
|
||||
queue *list.List
|
||||
}
|
||||
|
||||
// NewSysConnectPool new a SysConnectPool.
|
||||
func NewSysConnectPool() IConnectPool {
|
||||
return &SysConnectPool{}
|
||||
}
|
||||
|
||||
// Init create a db immediately and keep it util engine closed.
|
||||
func (s *SysConnectPool) Init(engine *Engine) error {
|
||||
db, err := engine.OpenDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.db = db
|
||||
s.maxIdleConns = 2
|
||||
s.maxConns = -1
|
||||
s.curConns = 0
|
||||
s.mutex = &sync.Mutex{}
|
||||
s.queue = list.New()
|
||||
return nil
|
||||
}
|
||||
|
||||
type node struct {
|
||||
mutex sync.Mutex
|
||||
cond *sync.Cond
|
||||
}
|
||||
|
||||
func newCondNode() *node {
|
||||
n := &node{}
|
||||
n.cond = sync.NewCond(&n.mutex)
|
||||
return n
|
||||
}
|
||||
|
||||
// RetrieveDB just return the only db
|
||||
func (s *SysConnectPool) RetrieveDB(engine *Engine) (db *sql.DB, err error) {
|
||||
/*if s.maxConns > 0 {
|
||||
fmt.Println("before retrieve")
|
||||
s.mutex.Lock()
|
||||
for s.curConns >= s.maxConns {
|
||||
fmt.Println("before waiting...", s.curConns, s.queue.Len())
|
||||
s.mutex.Unlock()
|
||||
n := NewNode()
|
||||
n.cond.L.Lock()
|
||||
s.queue.PushBack(n)
|
||||
n.cond.Wait()
|
||||
n.cond.L.Unlock()
|
||||
s.mutex.Lock()
|
||||
fmt.Println("after waiting...", s.curConns, s.queue.Len())
|
||||
}
|
||||
s.curConns += 1
|
||||
s.mutex.Unlock()
|
||||
fmt.Println("after retrieve")
|
||||
}*/
|
||||
return s.db, nil
|
||||
}
|
||||
|
||||
// ReleaseDB do nothing
|
||||
func (s *SysConnectPool) ReleaseDB(engine *Engine, db *sql.DB) {
|
||||
/*if s.maxConns > 0 {
|
||||
s.mutex.Lock()
|
||||
fmt.Println("before release", s.queue.Len())
|
||||
s.curConns -= 1
|
||||
|
||||
if e := s.queue.Front(); e != nil {
|
||||
n := e.Value.(*node)
|
||||
//n.cond.L.Lock()
|
||||
n.cond.Signal()
|
||||
fmt.Println("signaled...")
|
||||
s.queue.Remove(e)
|
||||
//n.cond.L.Unlock()
|
||||
}
|
||||
fmt.Println("after released", s.queue.Len())
|
||||
s.mutex.Unlock()
|
||||
}*/
|
||||
}
|
||||
|
||||
// Close closed the only db
|
||||
func (p *SysConnectPool) Close(engine *Engine) error {
|
||||
return p.db.Close()
|
||||
}
|
||||
|
||||
func (p *SysConnectPool) SetMaxIdleConns(conns int) {
|
||||
p.db.SetMaxIdleConns(conns)
|
||||
p.maxIdleConns = conns
|
||||
}
|
||||
|
||||
func (p *SysConnectPool) MaxIdleConns() int {
|
||||
return p.maxIdleConns
|
||||
}
|
||||
|
||||
// not implemented
|
||||
func (p *SysConnectPool) SetMaxConns(conns int) {
|
||||
p.maxConns = conns
|
||||
// if support SetMaxOpenConns, go 1.2+, then set
|
||||
if reflect.ValueOf(p.db).MethodByName("SetMaxOpenConns").IsValid() {
|
||||
reflect.ValueOf(p.db).MethodByName("SetMaxOpenConns").Call([]reflect.Value{reflect.ValueOf(conns)})
|
||||
}
|
||||
//p.db.SetMaxOpenConns(conns)
|
||||
}
|
||||
|
||||
// not implemented
|
||||
func (p *SysConnectPool) MaxConns() int {
|
||||
return p.maxConns
|
||||
}
|
||||
|
||||
// NewSimpleConnectPool new a SimpleConnectPool
|
||||
func NewSimpleConnectPool() IConnectPool {
|
||||
return &SimpleConnectPool{releasedConnects: make([]*sql.DB, 10),
|
||||
usingConnects: map[*sql.DB]time.Time{},
|
||||
cur: -1,
|
||||
maxWaitTimeOut: 14400,
|
||||
maxIdleConns: 10,
|
||||
mutex: &sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
// Struct SimpleConnectPool is a simple implementation for IConnectPool.
|
||||
// It's a custom connection pool and not use system connection pool.
|
||||
// Opening or Closing a database connection must be enter a lock.
|
||||
// This implements will be improved in furture.
|
||||
type SimpleConnectPool struct {
|
||||
releasedConnects []*sql.DB
|
||||
cur int
|
||||
usingConnects map[*sql.DB]time.Time
|
||||
maxWaitTimeOut int
|
||||
mutex *sync.Mutex
|
||||
maxIdleConns int
|
||||
}
|
||||
|
||||
func (s *SimpleConnectPool) Init(engine *Engine) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RetrieveDB get a connection from connection pool
|
||||
func (p *SimpleConnectPool) RetrieveDB(engine *Engine) (*sql.DB, error) {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
var db *sql.DB = nil
|
||||
var err error = nil
|
||||
//fmt.Printf("%x, rbegin - released:%v, using:%v\n", &p, p.cur+1, len(p.usingConnects))
|
||||
if p.cur < 0 {
|
||||
db, err = engine.OpenDB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.usingConnects[db] = time.Now()
|
||||
} else {
|
||||
db = p.releasedConnects[p.cur]
|
||||
p.usingConnects[db] = time.Now()
|
||||
p.releasedConnects[p.cur] = nil
|
||||
p.cur = p.cur - 1
|
||||
}
|
||||
|
||||
//fmt.Printf("%x, rend - released:%v, using:%v\n", &p, p.cur+1, len(p.usingConnects))
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// ReleaseDB release a db from connection pool
|
||||
func (p *SimpleConnectPool) ReleaseDB(engine *Engine, db *sql.DB) {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
//fmt.Printf("%x, lbegin - released:%v, using:%v\n", &p, p.cur+1, len(p.usingConnects))
|
||||
if p.cur >= p.maxIdleConns-1 {
|
||||
db.Close()
|
||||
} else {
|
||||
p.cur = p.cur + 1
|
||||
p.releasedConnects[p.cur] = db
|
||||
}
|
||||
delete(p.usingConnects, db)
|
||||
//fmt.Printf("%x, lend - released:%v, using:%v\n", &p, p.cur+1, len(p.usingConnects))
|
||||
}
|
||||
|
||||
// Close release all db
|
||||
func (p *SimpleConnectPool) Close(engine *Engine) error {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
for len(p.releasedConnects) > 0 {
|
||||
p.releasedConnects[0].Close()
|
||||
p.releasedConnects = p.releasedConnects[1:]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *SimpleConnectPool) SetMaxIdleConns(conns int) {
|
||||
p.maxIdleConns = conns
|
||||
}
|
||||
|
||||
func (p *SimpleConnectPool) MaxIdleConns() int {
|
||||
return p.maxIdleConns
|
||||
}
|
||||
|
||||
// not implemented
|
||||
func (p *SimpleConnectPool) SetMaxConns(conns int) {
|
||||
}
|
||||
|
||||
// not implemented
|
||||
func (p *SimpleConnectPool) MaxConns() int {
|
||||
return -1
|
||||
}
|
||||
316
postgres.go
316
postgres.go
|
|
@ -1,316 +0,0 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type postgres struct {
|
||||
base
|
||||
}
|
||||
|
||||
type values map[string]string
|
||||
|
||||
func (vs values) Set(k, v string) {
|
||||
vs[k] = v
|
||||
}
|
||||
|
||||
func (vs values) Get(k string) (v string) {
|
||||
return vs[k]
|
||||
}
|
||||
|
||||
func errorf(s string, args ...interface{}) {
|
||||
panic(fmt.Errorf("pq: %s", fmt.Sprintf(s, args...)))
|
||||
}
|
||||
|
||||
func parseOpts(name string, o values) {
|
||||
if len(name) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
name = strings.TrimSpace(name)
|
||||
|
||||
ps := strings.Split(name, " ")
|
||||
for _, p := range ps {
|
||||
kv := strings.Split(p, "=")
|
||||
if len(kv) < 2 {
|
||||
errorf("invalid option: %q", p)
|
||||
}
|
||||
o.Set(kv[0], kv[1])
|
||||
}
|
||||
}
|
||||
|
||||
type postgresParser struct {
|
||||
}
|
||||
|
||||
func (p *postgresParser) parse(driverName, dataSourceName string) (*uri, error) {
|
||||
db := &uri{dbType: POSTGRES}
|
||||
o := make(values)
|
||||
parseOpts(dataSourceName, o)
|
||||
|
||||
db.dbName = o.Get("dbname")
|
||||
if db.dbName == "" {
|
||||
return nil, errors.New("dbname is empty")
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func (db *postgres) Init(drivername, uri string) error {
|
||||
return db.base.init(&postgresParser{}, drivername, uri)
|
||||
}
|
||||
|
||||
func (db *postgres) SqlType(c *Column) string {
|
||||
var res string
|
||||
switch t := c.SQLType.Name; t {
|
||||
case TinyInt:
|
||||
res = SmallInt
|
||||
return res
|
||||
case MediumInt, Int, Integer:
|
||||
if c.IsAutoIncrement {
|
||||
return Serial
|
||||
}
|
||||
return Integer
|
||||
case Serial, BigSerial:
|
||||
c.IsAutoIncrement = true
|
||||
c.Nullable = false
|
||||
res = t
|
||||
case Binary, VarBinary:
|
||||
return Bytea
|
||||
case DateTime:
|
||||
res = TimeStamp
|
||||
case TimeStampz:
|
||||
return "timestamp with time zone"
|
||||
case Float:
|
||||
res = Real
|
||||
case TinyText, MediumText, LongText:
|
||||
res = Text
|
||||
case Blob, TinyBlob, MediumBlob, LongBlob:
|
||||
return Bytea
|
||||
case Double:
|
||||
return "DOUBLE PRECISION"
|
||||
default:
|
||||
if c.IsAutoIncrement {
|
||||
return Serial
|
||||
}
|
||||
res = t
|
||||
}
|
||||
|
||||
var hasLen1 bool = (c.Length > 0)
|
||||
var hasLen2 bool = (c.Length2 > 0)
|
||||
if hasLen1 {
|
||||
res += "(" + strconv.Itoa(c.Length) + ")"
|
||||
} else if hasLen2 {
|
||||
res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")"
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (db *postgres) SupportInsertMany() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (db *postgres) QuoteStr() string {
|
||||
return "\""
|
||||
}
|
||||
|
||||
func (db *postgres) AutoIncrStr() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (db *postgres) SupportEngine() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (db *postgres) SupportCharset() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (db *postgres) IndexOnTable() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (db *postgres) IndexCheckSql(tableName, idxName string) (string, []interface{}) {
|
||||
args := []interface{}{tableName, idxName}
|
||||
return `SELECT indexname FROM pg_indexes ` +
|
||||
`WHERE tablename = ? AND indexname = ?`, args
|
||||
}
|
||||
|
||||
func (db *postgres) TableCheckSql(tableName string) (string, []interface{}) {
|
||||
args := []interface{}{tableName}
|
||||
return `SELECT tablename FROM pg_tables WHERE tablename = ?`, args
|
||||
}
|
||||
|
||||
func (db *postgres) ColumnCheckSql(tableName, colName string) (string, []interface{}) {
|
||||
args := []interface{}{tableName, colName}
|
||||
return "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = ?" +
|
||||
" AND column_name = ?", args
|
||||
}
|
||||
|
||||
func (db *postgres) GetColumns(tableName string) ([]string, map[string]*Column, error) {
|
||||
args := []interface{}{tableName}
|
||||
s := "SELECT column_name, column_default, is_nullable, data_type, character_maximum_length" +
|
||||
", numeric_precision, numeric_precision_radix FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = $1"
|
||||
|
||||
cnn, err := sql.Open(db.driverName, db.dataSourceName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer cnn.Close()
|
||||
res, err := query(cnn, s, args...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cols := make(map[string]*Column)
|
||||
colSeq := make([]string, 0)
|
||||
for _, record := range res {
|
||||
col := new(Column)
|
||||
col.Indexes = make(map[string]bool)
|
||||
for name, content := range record {
|
||||
switch name {
|
||||
case "column_name":
|
||||
col.Name = strings.Trim(string(content), `" `)
|
||||
case "column_default":
|
||||
if strings.HasPrefix(string(content), "nextval") {
|
||||
col.IsPrimaryKey = true
|
||||
} else {
|
||||
col.Default = string(content)
|
||||
if col.Default == "" {
|
||||
col.DefaultIsEmpty = true
|
||||
}
|
||||
}
|
||||
case "is_nullable":
|
||||
if string(content) == "YES" {
|
||||
col.Nullable = true
|
||||
} else {
|
||||
col.Nullable = false
|
||||
}
|
||||
case "data_type":
|
||||
ct := string(content)
|
||||
switch ct {
|
||||
case "character varying", "character":
|
||||
col.SQLType = SQLType{Varchar, 0, 0}
|
||||
case "timestamp without time zone":
|
||||
col.SQLType = SQLType{DateTime, 0, 0}
|
||||
case "timestamp with time zone":
|
||||
col.SQLType = SQLType{TimeStampz, 0, 0}
|
||||
case "double precision":
|
||||
col.SQLType = SQLType{Double, 0, 0}
|
||||
case "boolean":
|
||||
col.SQLType = SQLType{Bool, 0, 0}
|
||||
case "time without time zone":
|
||||
col.SQLType = SQLType{Time, 0, 0}
|
||||
default:
|
||||
col.SQLType = SQLType{strings.ToUpper(ct), 0, 0}
|
||||
}
|
||||
if _, ok := sqlTypes[col.SQLType.Name]; !ok {
|
||||
return nil, nil, errors.New(fmt.Sprintf("unkonw colType %v", ct))
|
||||
}
|
||||
case "character_maximum_length":
|
||||
i, err := strconv.Atoi(string(content))
|
||||
if err != nil {
|
||||
return nil, nil, errors.New("retrieve length error")
|
||||
}
|
||||
col.Length = i
|
||||
case "numeric_precision":
|
||||
case "numeric_precision_radix":
|
||||
}
|
||||
}
|
||||
if col.SQLType.IsText() {
|
||||
if col.Default != "" {
|
||||
col.Default = "'" + col.Default + "'"
|
||||
}else{
|
||||
if col.DefaultIsEmpty {
|
||||
col.Default = "''"
|
||||
}
|
||||
}
|
||||
}
|
||||
cols[col.Name] = col
|
||||
colSeq = append(colSeq, col.Name)
|
||||
}
|
||||
|
||||
return colSeq, cols, nil
|
||||
}
|
||||
|
||||
func (db *postgres) GetTables() ([]*Table, error) {
|
||||
args := []interface{}{}
|
||||
s := "SELECT tablename FROM pg_tables where schemaname = 'public'"
|
||||
cnn, err := sql.Open(db.driverName, db.dataSourceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cnn.Close()
|
||||
res, err := query(cnn, s, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tables := make([]*Table, 0)
|
||||
for _, record := range res {
|
||||
table := new(Table)
|
||||
for name, content := range record {
|
||||
switch name {
|
||||
case "tablename":
|
||||
table.Name = string(content)
|
||||
}
|
||||
}
|
||||
tables = append(tables, table)
|
||||
}
|
||||
return tables, nil
|
||||
}
|
||||
|
||||
func (db *postgres) GetIndexes(tableName string) (map[string]*Index, error) {
|
||||
args := []interface{}{tableName}
|
||||
s := "SELECT tablename, indexname, indexdef FROM pg_indexes WHERE schemaname = 'public' and tablename = $1"
|
||||
|
||||
cnn, err := sql.Open(db.driverName, db.dataSourceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cnn.Close()
|
||||
res, err := query(cnn, s, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
indexes := make(map[string]*Index, 0)
|
||||
for _, record := range res {
|
||||
var indexType int
|
||||
var indexName string
|
||||
var colNames []string
|
||||
|
||||
for name, content := range record {
|
||||
switch name {
|
||||
case "indexname":
|
||||
indexName = strings.Trim(string(content), `" `)
|
||||
case "indexdef":
|
||||
c := string(content)
|
||||
if strings.HasPrefix(c, "CREATE UNIQUE INDEX") {
|
||||
indexType = UniqueType
|
||||
} else {
|
||||
indexType = IndexType
|
||||
}
|
||||
cs := strings.Split(c, "(")
|
||||
colNames = strings.Split(cs[1][0:len(cs[1])-1], ",")
|
||||
}
|
||||
}
|
||||
if strings.HasSuffix(indexName, "_pkey") {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
|
||||
newIdxName := indexName[5+len(tableName) : len(indexName)]
|
||||
if newIdxName != "" {
|
||||
indexName = newIdxName
|
||||
}
|
||||
}
|
||||
|
||||
index := &Index{Name: indexName, Type: indexType, Cols: make([]string, 0)}
|
||||
for _, colName := range colNames {
|
||||
index.Cols = append(index.Cols, strings.Trim(colName, `" `))
|
||||
}
|
||||
indexes[index.Name] = index
|
||||
}
|
||||
return indexes, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,272 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-xorm/core"
|
||||
)
|
||||
|
||||
// func init() {
|
||||
// RegisterDialect("postgres", &postgres{})
|
||||
// }
|
||||
|
||||
type postgres struct {
|
||||
core.Base
|
||||
}
|
||||
|
||||
func (db *postgres) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error {
|
||||
return db.Base.Init(d, db, uri, drivername, dataSourceName)
|
||||
}
|
||||
|
||||
func (db *postgres) SqlType(c *core.Column) string {
|
||||
var res string
|
||||
switch t := c.SQLType.Name; t {
|
||||
case core.TinyInt:
|
||||
res = core.SmallInt
|
||||
return res
|
||||
case core.MediumInt, core.Int, core.Integer:
|
||||
if c.IsAutoIncrement {
|
||||
return core.Serial
|
||||
}
|
||||
return core.Integer
|
||||
case core.Serial, core.BigSerial:
|
||||
c.IsAutoIncrement = true
|
||||
c.Nullable = false
|
||||
res = t
|
||||
case core.Binary, core.VarBinary:
|
||||
return core.Bytea
|
||||
case core.DateTime:
|
||||
res = core.TimeStamp
|
||||
case core.TimeStampz:
|
||||
return "timestamp with time zone"
|
||||
case core.Float:
|
||||
res = core.Real
|
||||
case core.TinyText, core.MediumText, core.LongText:
|
||||
res = core.Text
|
||||
case core.Blob, core.TinyBlob, core.MediumBlob, core.LongBlob:
|
||||
return core.Bytea
|
||||
case core.Double:
|
||||
return "DOUBLE PRECISION"
|
||||
default:
|
||||
if c.IsAutoIncrement {
|
||||
return core.Serial
|
||||
}
|
||||
res = t
|
||||
}
|
||||
|
||||
var hasLen1 bool = (c.Length > 0)
|
||||
var hasLen2 bool = (c.Length2 > 0)
|
||||
if hasLen1 {
|
||||
res += "(" + strconv.Itoa(c.Length) + ")"
|
||||
} else if hasLen2 {
|
||||
res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")"
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (db *postgres) SupportInsertMany() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (db *postgres) QuoteStr() string {
|
||||
return "\""
|
||||
}
|
||||
|
||||
func (db *postgres) AutoIncrStr() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (db *postgres) SupportEngine() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (db *postgres) SupportCharset() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (db *postgres) IndexOnTable() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (db *postgres) IndexCheckSql(tableName, idxName string) (string, []interface{}) {
|
||||
args := []interface{}{tableName, idxName}
|
||||
return `SELECT indexname FROM pg_indexes ` +
|
||||
`WHERE tablename = ? AND indexname = ?`, args
|
||||
}
|
||||
|
||||
func (db *postgres) TableCheckSql(tableName string) (string, []interface{}) {
|
||||
args := []interface{}{tableName}
|
||||
return `SELECT tablename FROM pg_tables WHERE tablename = ?`, args
|
||||
}
|
||||
|
||||
func (db *postgres) ColumnCheckSql(tableName, colName string) (string, []interface{}) {
|
||||
args := []interface{}{tableName, colName}
|
||||
return "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = ?" +
|
||||
" AND column_name = ?", args
|
||||
}
|
||||
|
||||
func (db *postgres) GetColumns(tableName string) ([]string, map[string]*core.Column, error) {
|
||||
args := []interface{}{tableName}
|
||||
s := "SELECT column_name, column_default, is_nullable, data_type, character_maximum_length" +
|
||||
", numeric_precision, numeric_precision_radix FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = $1"
|
||||
|
||||
rows, err := db.DB().Query(s, args...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
cols := make(map[string]*core.Column)
|
||||
colSeq := make([]string, 0)
|
||||
|
||||
for rows.Next() {
|
||||
col := new(core.Column)
|
||||
col.Indexes = make(map[string]bool)
|
||||
|
||||
var colName, isNullable, dataType string
|
||||
var maxLenStr, colDefault, numPrecision, numRadix *string
|
||||
err = rows.Scan(&colName, &colDefault, &isNullable, &dataType, &maxLenStr, &numPrecision, &numRadix)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var maxLen int
|
||||
if maxLenStr != nil {
|
||||
maxLen, err = strconv.Atoi(*maxLenStr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
col.Name = strings.Trim(colName, `" `)
|
||||
|
||||
if colDefault != nil {
|
||||
if strings.HasPrefix(*colDefault, "nextval") {
|
||||
col.IsPrimaryKey = true
|
||||
} else {
|
||||
col.Default = *colDefault
|
||||
}
|
||||
}
|
||||
|
||||
if isNullable == "YES" {
|
||||
col.Nullable = true
|
||||
} else {
|
||||
col.Nullable = false
|
||||
}
|
||||
|
||||
switch dataType {
|
||||
case "character varying", "character":
|
||||
col.SQLType = core.SQLType{core.Varchar, 0, 0}
|
||||
case "timestamp without time zone":
|
||||
col.SQLType = core.SQLType{core.DateTime, 0, 0}
|
||||
case "timestamp with time zone":
|
||||
col.SQLType = core.SQLType{core.TimeStampz, 0, 0}
|
||||
case "double precision":
|
||||
col.SQLType = core.SQLType{core.Double, 0, 0}
|
||||
case "boolean":
|
||||
col.SQLType = core.SQLType{core.Bool, 0, 0}
|
||||
case "time without time zone":
|
||||
col.SQLType = core.SQLType{core.Time, 0, 0}
|
||||
default:
|
||||
col.SQLType = core.SQLType{strings.ToUpper(dataType), 0, 0}
|
||||
}
|
||||
if _, ok := core.SqlTypes[col.SQLType.Name]; !ok {
|
||||
return nil, nil, errors.New(fmt.Sprintf("unkonw colType %v", dataType))
|
||||
}
|
||||
|
||||
col.Length = maxLen
|
||||
|
||||
if col.SQLType.IsText() {
|
||||
if col.Default != "" {
|
||||
col.Default = "'" + col.Default + "'"
|
||||
} else {
|
||||
if col.DefaultIsEmpty {
|
||||
col.Default = "''"
|
||||
}
|
||||
}
|
||||
}
|
||||
cols[col.Name] = col
|
||||
colSeq = append(colSeq, col.Name)
|
||||
}
|
||||
|
||||
return colSeq, cols, nil
|
||||
}
|
||||
|
||||
func (db *postgres) GetTables() ([]*core.Table, error) {
|
||||
args := []interface{}{}
|
||||
s := "SELECT tablename FROM pg_tables where schemaname = 'public'"
|
||||
|
||||
rows, err := db.DB().Query(s, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
tables := make([]*core.Table, 0)
|
||||
for rows.Next() {
|
||||
table := core.NewEmptyTable()
|
||||
var name string
|
||||
err = rows.Scan(&name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
table.Name = name
|
||||
tables = append(tables, table)
|
||||
}
|
||||
return tables, nil
|
||||
}
|
||||
|
||||
func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error) {
|
||||
args := []interface{}{tableName}
|
||||
s := "SELECT indexname, indexdef FROM pg_indexes WHERE schemaname = 'public' and tablename = $1"
|
||||
|
||||
rows, err := db.DB().Query(s, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
indexes := make(map[string]*core.Index, 0)
|
||||
for rows.Next() {
|
||||
var indexType int
|
||||
var indexName, indexdef string
|
||||
var colNames []string
|
||||
err = rows.Scan(&indexName, &indexdef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
indexName = strings.Trim(indexName, `" `)
|
||||
|
||||
if strings.HasPrefix(indexdef, "CREATE UNIQUE INDEX") {
|
||||
indexType = core.UniqueType
|
||||
} else {
|
||||
indexType = core.IndexType
|
||||
}
|
||||
cs := strings.Split(indexdef, "(")
|
||||
colNames = strings.Split(cs[1][0:len(cs[1])-1], ",")
|
||||
|
||||
if strings.HasSuffix(indexName, "_pkey") {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
|
||||
newIdxName := indexName[5+len(tableName) : len(indexName)]
|
||||
if newIdxName != "" {
|
||||
indexName = newIdxName
|
||||
}
|
||||
}
|
||||
|
||||
index := &core.Index{Name: indexName, Type: indexType, Cols: make([]string, 0)}
|
||||
for _, colName := range colNames {
|
||||
index.Cols = append(index.Cols, strings.Trim(colName, `" `))
|
||||
}
|
||||
indexes[index.Name] = index
|
||||
}
|
||||
return indexes, nil
|
||||
}
|
||||
|
||||
func (db *postgres) Filters() []core.Filter {
|
||||
return []core.Filter{&core.IdFilter{}, &core.QuoteFilter{}, &core.SeqFilter{"$", 1}}
|
||||
}
|
||||
241
postgres_test.go
241
postgres_test.go
|
|
@ -1,241 +0,0 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
//var connStr string = "dbname=xorm_test user=lunny password=1234 sslmode=disable"
|
||||
|
||||
var connStr string = "dbname=xorm_test sslmode=disable"
|
||||
|
||||
func newPostgresEngine() (*Engine, error) {
|
||||
return NewEngine("postgres", connStr)
|
||||
}
|
||||
|
||||
func newPostgresDriverDB() (*sql.DB, error) {
|
||||
return sql.Open("postgres", connStr)
|
||||
}
|
||||
|
||||
func TestPostgres(t *testing.T) {
|
||||
engine, err := newPostgresEngine()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer engine.Close()
|
||||
engine.ShowSQL = showTestSql
|
||||
engine.ShowErr = showTestSql
|
||||
engine.ShowWarn = showTestSql
|
||||
engine.ShowDebug = showTestSql
|
||||
|
||||
testAll(engine, t)
|
||||
testAll2(engine, t)
|
||||
testAll3(engine, t)
|
||||
}
|
||||
|
||||
func TestPostgresWithCache(t *testing.T) {
|
||||
engine, err := newPostgresEngine()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000))
|
||||
defer engine.Close()
|
||||
engine.ShowSQL = showTestSql
|
||||
engine.ShowErr = showTestSql
|
||||
engine.ShowWarn = showTestSql
|
||||
engine.ShowDebug = showTestSql
|
||||
|
||||
testAll(engine, t)
|
||||
testAll2(engine, t)
|
||||
}
|
||||
|
||||
/*
|
||||
func TestPostgres2(t *testing.T) {
|
||||
engine, err := NewEngine("postgres", "dbname=xorm_test sslmode=disable")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer engine.Close()
|
||||
engine.ShowSQL = showTestSql
|
||||
engine.Mapper = SameMapper{}
|
||||
|
||||
fmt.Println("-------------- directCreateTable --------------")
|
||||
directCreateTable(engine, t)
|
||||
fmt.Println("-------------- mapper --------------")
|
||||
mapper(engine, t)
|
||||
fmt.Println("-------------- insert --------------")
|
||||
insert(engine, t)
|
||||
fmt.Println("-------------- querySameMapper --------------")
|
||||
querySameMapper(engine, t)
|
||||
fmt.Println("-------------- execSameMapper --------------")
|
||||
execSameMapper(engine, t)
|
||||
fmt.Println("-------------- insertAutoIncr --------------")
|
||||
insertAutoIncr(engine, t)
|
||||
fmt.Println("-------------- insertMulti --------------")
|
||||
insertMulti(engine, t)
|
||||
fmt.Println("-------------- insertTwoTable --------------")
|
||||
insertTwoTable(engine, t)
|
||||
fmt.Println("-------------- updateSameMapper --------------")
|
||||
updateSameMapper(engine, t)
|
||||
fmt.Println("-------------- testdelete --------------")
|
||||
testdelete(engine, t)
|
||||
fmt.Println("-------------- get --------------")
|
||||
get(engine, t)
|
||||
fmt.Println("-------------- cascadeGet --------------")
|
||||
cascadeGet(engine, t)
|
||||
fmt.Println("-------------- find --------------")
|
||||
find(engine, t)
|
||||
fmt.Println("-------------- find2 --------------")
|
||||
find2(engine, t)
|
||||
fmt.Println("-------------- findMap --------------")
|
||||
findMap(engine, t)
|
||||
fmt.Println("-------------- findMap2 --------------")
|
||||
findMap2(engine, t)
|
||||
fmt.Println("-------------- count --------------")
|
||||
count(engine, t)
|
||||
fmt.Println("-------------- where --------------")
|
||||
where(engine, t)
|
||||
fmt.Println("-------------- in --------------")
|
||||
in(engine, t)
|
||||
fmt.Println("-------------- limit --------------")
|
||||
limit(engine, t)
|
||||
fmt.Println("-------------- orderSameMapper --------------")
|
||||
orderSameMapper(engine, t)
|
||||
fmt.Println("-------------- joinSameMapper --------------")
|
||||
joinSameMapper(engine, t)
|
||||
fmt.Println("-------------- havingSameMapper --------------")
|
||||
havingSameMapper(engine, t)
|
||||
fmt.Println("-------------- combineTransactionSameMapper --------------")
|
||||
combineTransactionSameMapper(engine, t)
|
||||
fmt.Println("-------------- table --------------")
|
||||
table(engine, t)
|
||||
fmt.Println("-------------- createMultiTables --------------")
|
||||
createMultiTables(engine, t)
|
||||
fmt.Println("-------------- tableOp --------------")
|
||||
tableOp(engine, t)
|
||||
fmt.Println("-------------- testColsSameMapper --------------")
|
||||
testColsSameMapper(engine, t)
|
||||
fmt.Println("-------------- testCharst --------------")
|
||||
testCharst(engine, t)
|
||||
fmt.Println("-------------- testStoreEngine --------------")
|
||||
testStoreEngine(engine, t)
|
||||
fmt.Println("-------------- testExtends --------------")
|
||||
testExtends(engine, t)
|
||||
fmt.Println("-------------- testColTypes --------------")
|
||||
testColTypes(engine, t)
|
||||
fmt.Println("-------------- testCustomType --------------")
|
||||
testCustomType(engine, t)
|
||||
fmt.Println("-------------- testCreatedAndUpdated --------------")
|
||||
testCreatedAndUpdated(engine, t)
|
||||
fmt.Println("-------------- testIndexAndUnique --------------")
|
||||
testIndexAndUnique(engine, t)
|
||||
fmt.Println("-------------- testMetaInfo --------------")
|
||||
testMetaInfo(engine, t)
|
||||
fmt.Println("-------------- testIterate --------------")
|
||||
testIterate(engine, t)
|
||||
fmt.Println("-------------- testStrangeName --------------")
|
||||
testStrangeName(engine, t)
|
||||
fmt.Println("-------------- testVersion --------------")
|
||||
testVersion(engine, t)
|
||||
fmt.Println("-------------- testDistinct --------------")
|
||||
testDistinct(engine, t)
|
||||
fmt.Println("-------------- testUseBool --------------")
|
||||
testUseBool(engine, t)
|
||||
fmt.Println("-------------- transaction --------------")
|
||||
transaction(engine, t)
|
||||
}*/
|
||||
|
||||
const (
|
||||
createTablePostgres = `CREATE TABLE IF NOT EXISTS "big_struct" ("id" SERIAL PRIMARY KEY NOT NULL, "name" VARCHAR(255) NULL, "title" VARCHAR(255) NULL, "age" VARCHAR(255) NULL, "alias" VARCHAR(255) NULL, "nick_name" VARCHAR(255) NULL);`
|
||||
dropTablePostgres = `DROP TABLE IF EXISTS "big_struct";`
|
||||
)
|
||||
|
||||
func BenchmarkPostgresDriverInsert(t *testing.B) {
|
||||
doBenchDriver(newPostgresDriverDB, createTablePostgres, dropTablePostgres,
|
||||
doBenchDriverInsert, t)
|
||||
}
|
||||
|
||||
func BenchmarkPostgresDriverFind(t *testing.B) {
|
||||
doBenchDriver(newPostgresDriverDB, createTablePostgres, dropTablePostgres,
|
||||
doBenchDriverFind, t)
|
||||
}
|
||||
|
||||
func BenchmarkPostgresNoCacheInsert(t *testing.B) {
|
||||
engine, err := newPostgresEngine()
|
||||
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
//engine.ShowSQL = true
|
||||
doBenchInsert(engine, t)
|
||||
}
|
||||
|
||||
func BenchmarkPostgresNoCacheFind(t *testing.B) {
|
||||
engine, err := newPostgresEngine()
|
||||
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
//engine.ShowSQL = true
|
||||
doBenchFind(engine, t)
|
||||
}
|
||||
|
||||
func BenchmarkPostgresNoCacheFindPtr(t *testing.B) {
|
||||
engine, err := newPostgresEngine()
|
||||
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
//engine.ShowSQL = true
|
||||
doBenchFindPtr(engine, t)
|
||||
}
|
||||
|
||||
func BenchmarkPostgresCacheInsert(t *testing.B) {
|
||||
engine, err := newPostgresEngine()
|
||||
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000))
|
||||
|
||||
doBenchInsert(engine, t)
|
||||
}
|
||||
|
||||
func BenchmarkPostgresCacheFind(t *testing.B) {
|
||||
engine, err := newPostgresEngine()
|
||||
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000))
|
||||
|
||||
doBenchFind(engine, t)
|
||||
}
|
||||
|
||||
func BenchmarkPostgresCacheFindPtr(t *testing.B) {
|
||||
engine, err := newPostgresEngine()
|
||||
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000))
|
||||
|
||||
doBenchFindPtr(engine, t)
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-xorm/core"
|
||||
)
|
||||
|
||||
// func init() {
|
||||
// core.RegisterDriver("postgres", &pqDriver{})
|
||||
// }
|
||||
|
||||
type pqDriver struct {
|
||||
}
|
||||
|
||||
type values map[string]string
|
||||
|
||||
func (vs values) Set(k, v string) {
|
||||
vs[k] = v
|
||||
}
|
||||
|
||||
func (vs values) Get(k string) (v string) {
|
||||
return vs[k]
|
||||
}
|
||||
|
||||
func errorf(s string, args ...interface{}) {
|
||||
panic(fmt.Errorf("pq: %s", fmt.Sprintf(s, args...)))
|
||||
}
|
||||
|
||||
func parseOpts(name string, o values) {
|
||||
if len(name) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
name = strings.TrimSpace(name)
|
||||
|
||||
ps := strings.Split(name, " ")
|
||||
for _, p := range ps {
|
||||
kv := strings.Split(p, "=")
|
||||
if len(kv) < 2 {
|
||||
errorf("invalid option: %q", p)
|
||||
}
|
||||
o.Set(kv[0], kv[1])
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pqDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) {
|
||||
db := &core.Uri{DbType: core.POSTGRES}
|
||||
o := make(values)
|
||||
parseOpts(dataSourceName, o)
|
||||
|
||||
db.DbName = o.Get("dbname")
|
||||
if db.DbName == "" {
|
||||
return nil, errors.New("dbname is empty")
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
21
rows.go
21
rows.go
|
|
@ -4,14 +4,16 @@ import (
|
|||
"database/sql"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/go-xorm/core"
|
||||
)
|
||||
|
||||
type Rows struct {
|
||||
NoTypeCheck bool
|
||||
|
||||
session *Session
|
||||
stmt *sql.Stmt
|
||||
rows *sql.Rows
|
||||
stmt *core.Stmt
|
||||
rows *core.Rows
|
||||
fields []string
|
||||
fieldsCount int
|
||||
beanType reflect.Type
|
||||
|
|
@ -30,24 +32,23 @@ func newRows(session *Session, bean interface{}) (*Rows, error) {
|
|||
|
||||
defer rows.session.Statement.Init()
|
||||
|
||||
var sql string
|
||||
var sqlStr string
|
||||
var args []interface{}
|
||||
rows.session.Statement.RefTable = rows.session.Engine.autoMap(bean)
|
||||
if rows.session.Statement.RawSQL == "" {
|
||||
sql, args = rows.session.Statement.genGetSql(bean)
|
||||
sqlStr, args = rows.session.Statement.genGetSql(bean)
|
||||
} else {
|
||||
sql = rows.session.Statement.RawSQL
|
||||
sqlStr = rows.session.Statement.RawSQL
|
||||
args = rows.session.Statement.RawParams
|
||||
}
|
||||
|
||||
for _, filter := range rows.session.Engine.Filters {
|
||||
sql = filter.Do(sql, session)
|
||||
for _, filter := range rows.session.Engine.dialect.Filters() {
|
||||
sqlStr = filter.Do(sqlStr, session.Engine.dialect, rows.session.Statement.RefTable)
|
||||
}
|
||||
|
||||
rows.session.Engine.LogSQL(sql)
|
||||
rows.session.Engine.LogSQL(args)
|
||||
rows.session.Engine.logSQL(sqlStr, args)
|
||||
|
||||
rows.stmt, err = rows.session.Db.Prepare(sql)
|
||||
rows.stmt, err = rows.session.Db.Prepare(sqlStr)
|
||||
if err != nil {
|
||||
rows.lastError = err
|
||||
defer rows.Close()
|
||||
|
|
|
|||
761
session.go
761
session.go
File diff suppressed because it is too large
Load Diff
|
|
@ -1,46 +1,44 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"strings"
|
||||
|
||||
"github.com/go-xorm/core"
|
||||
)
|
||||
|
||||
// func init() {
|
||||
// RegisterDialect("sqlite3", &sqlite3{})
|
||||
// }
|
||||
|
||||
type sqlite3 struct {
|
||||
base
|
||||
core.Base
|
||||
}
|
||||
|
||||
type sqlite3Parser struct {
|
||||
func (db *sqlite3) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error {
|
||||
return db.Base.Init(d, db, uri, drivername, dataSourceName)
|
||||
}
|
||||
|
||||
func (p *sqlite3Parser) parse(driverName, dataSourceName string) (*uri, error) {
|
||||
return &uri{dbType: SQLITE, dbName: dataSourceName}, nil
|
||||
}
|
||||
|
||||
func (db *sqlite3) Init(drivername, dataSourceName string) error {
|
||||
return db.base.init(&sqlite3Parser{}, drivername, dataSourceName)
|
||||
}
|
||||
|
||||
func (db *sqlite3) SqlType(c *Column) string {
|
||||
func (db *sqlite3) SqlType(c *core.Column) string {
|
||||
switch t := c.SQLType.Name; t {
|
||||
case Date, DateTime, TimeStamp, Time:
|
||||
return Numeric
|
||||
case TimeStampz:
|
||||
return Text
|
||||
case Char, Varchar, TinyText, Text, MediumText, LongText:
|
||||
return Text
|
||||
case Bit, TinyInt, SmallInt, MediumInt, Int, Integer, BigInt, Bool:
|
||||
return Integer
|
||||
case Float, Double, Real:
|
||||
return Real
|
||||
case Decimal, Numeric:
|
||||
return Numeric
|
||||
case TinyBlob, Blob, MediumBlob, LongBlob, Bytea, Binary, VarBinary:
|
||||
return Blob
|
||||
case Serial, BigSerial:
|
||||
case core.Date, core.DateTime, core.TimeStamp, core.Time:
|
||||
return core.Numeric
|
||||
case core.TimeStampz:
|
||||
return core.Text
|
||||
case core.Char, core.Varchar, core.TinyText, core.Text, core.MediumText, core.LongText:
|
||||
return core.Text
|
||||
case core.Bit, core.TinyInt, core.SmallInt, core.MediumInt, core.Int, core.Integer, core.BigInt, core.Bool:
|
||||
return core.Integer
|
||||
case core.Float, core.Double, core.Real:
|
||||
return core.Real
|
||||
case core.Decimal, core.Numeric:
|
||||
return core.Numeric
|
||||
case core.TinyBlob, core.Blob, core.MediumBlob, core.LongBlob, core.Bytea, core.Binary, core.VarBinary:
|
||||
return core.Blob
|
||||
case core.Serial, core.BigSerial:
|
||||
c.IsPrimaryKey = true
|
||||
c.IsAutoIncrement = true
|
||||
c.Nullable = false
|
||||
return Integer
|
||||
return core.Integer
|
||||
default:
|
||||
return t
|
||||
}
|
||||
|
|
@ -86,36 +84,32 @@ func (db *sqlite3) ColumnCheckSql(tableName, colName string) (string, []interfac
|
|||
return sql, args
|
||||
}
|
||||
|
||||
func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*Column, error) {
|
||||
func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*core.Column, error) {
|
||||
args := []interface{}{tableName}
|
||||
s := "SELECT sql FROM sqlite_master WHERE type='table' and name = ?"
|
||||
cnn, err := sql.Open(db.driverName, db.dataSourceName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer cnn.Close()
|
||||
res, err := query(cnn, s, args...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var sql string
|
||||
for _, record := range res {
|
||||
for name, content := range record {
|
||||
if name == "sql" {
|
||||
sql = string(content)
|
||||
}
|
||||
rows, err := db.DB().Query(s, args...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var name string
|
||||
for rows.Next() {
|
||||
err = rows.Scan(&name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
nStart := strings.Index(sql, "(")
|
||||
nEnd := strings.Index(sql, ")")
|
||||
colCreates := strings.Split(sql[nStart+1:nEnd], ",")
|
||||
cols := make(map[string]*Column)
|
||||
nStart := strings.Index(name, "(")
|
||||
nEnd := strings.Index(name, ")")
|
||||
colCreates := strings.Split(name[nStart+1:nEnd], ",")
|
||||
cols := make(map[string]*core.Column)
|
||||
colSeq := make([]string, 0)
|
||||
for _, colStr := range colCreates {
|
||||
fields := strings.Fields(strings.TrimSpace(colStr))
|
||||
col := new(Column)
|
||||
col := new(core.Column)
|
||||
col.Indexes = make(map[string]bool)
|
||||
col.Nullable = true
|
||||
for idx, field := range fields {
|
||||
|
|
@ -123,7 +117,7 @@ func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*Column, e
|
|||
col.Name = strings.Trim(field, "`[] ")
|
||||
continue
|
||||
} else if idx == 1 {
|
||||
col.SQLType = SQLType{field, 0, 0}
|
||||
col.SQLType = core.SQLType{field, 0, 0}
|
||||
}
|
||||
switch field {
|
||||
case "PRIMARY":
|
||||
|
|
@ -144,28 +138,22 @@ func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*Column, e
|
|||
return colSeq, cols, nil
|
||||
}
|
||||
|
||||
func (db *sqlite3) GetTables() ([]*Table, error) {
|
||||
func (db *sqlite3) GetTables() ([]*core.Table, error) {
|
||||
args := []interface{}{}
|
||||
s := "SELECT name FROM sqlite_master WHERE type='table'"
|
||||
|
||||
cnn, err := sql.Open(db.driverName, db.dataSourceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cnn.Close()
|
||||
res, err := query(cnn, s, args...)
|
||||
rows, err := db.DB().Query(s, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
tables := make([]*Table, 0)
|
||||
for _, record := range res {
|
||||
table := new(Table)
|
||||
for name, content := range record {
|
||||
switch name {
|
||||
case "name":
|
||||
table.Name = string(content)
|
||||
}
|
||||
tables := make([]*core.Table, 0)
|
||||
for rows.Next() {
|
||||
table := core.NewEmptyTable()
|
||||
err = rows.Scan(&table.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if table.Name == "sqlite_sequence" {
|
||||
continue
|
||||
|
|
@ -175,28 +163,29 @@ func (db *sqlite3) GetTables() ([]*Table, error) {
|
|||
return tables, nil
|
||||
}
|
||||
|
||||
func (db *sqlite3) GetIndexes(tableName string) (map[string]*Index, error) {
|
||||
func (db *sqlite3) GetIndexes(tableName string) (map[string]*core.Index, error) {
|
||||
args := []interface{}{tableName}
|
||||
s := "SELECT sql FROM sqlite_master WHERE type='index' and tbl_name = ?"
|
||||
cnn, err := sql.Open(db.driverName, db.dataSourceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cnn.Close()
|
||||
res, err := query(cnn, s, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
indexes := make(map[string]*Index, 0)
|
||||
for _, record := range res {
|
||||
index := new(Index)
|
||||
sql := string(record["sql"])
|
||||
rows, err := db.DB().Query(s, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
indexes := make(map[string]*core.Index, 0)
|
||||
for rows.Next() {
|
||||
var sql string
|
||||
err = rows.Scan(&sql)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sql == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
index := new(core.Index)
|
||||
nNStart := strings.Index(sql, "INDEX")
|
||||
nNEnd := strings.Index(sql, "ON")
|
||||
if nNStart == -1 || nNEnd == -1 {
|
||||
|
|
@ -212,9 +201,9 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*Index, error) {
|
|||
}
|
||||
|
||||
if strings.HasPrefix(sql, "CREATE UNIQUE INDEX") {
|
||||
index.Type = UniqueType
|
||||
index.Type = core.UniqueType
|
||||
} else {
|
||||
index.Type = IndexType
|
||||
index.Type = core.IndexType
|
||||
}
|
||||
|
||||
nStart := strings.Index(sql, "(")
|
||||
|
|
@ -230,3 +219,7 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*Index, error) {
|
|||
|
||||
return indexes, nil
|
||||
}
|
||||
|
||||
func (db *sqlite3) Filters() []core.Filter {
|
||||
return []core.Filter{&core.IdFilter{}}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"github.com/go-xorm/core"
|
||||
)
|
||||
|
||||
// func init() {
|
||||
// core.RegisterDriver("sqlite3", &sqlite3Driver{})
|
||||
// }
|
||||
|
||||
type sqlite3Driver struct {
|
||||
}
|
||||
|
||||
func (p *sqlite3Driver) Parse(driverName, dataSourceName string) (*core.Uri, error) {
|
||||
return &core.Uri{DbType: core.SQLITE, DbName: dataSourceName}, nil
|
||||
}
|
||||
140
sqlite3_test.go
140
sqlite3_test.go
|
|
@ -1,140 +0,0 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
func newSqlite3Engine() (*Engine, error) {
|
||||
os.Remove("./test.db")
|
||||
return NewEngine("sqlite3", "./test.db")
|
||||
}
|
||||
|
||||
func newSqlite3DriverDB() (*sql.DB, error) {
|
||||
os.Remove("./test.db")
|
||||
return sql.Open("sqlite3", "./test.db")
|
||||
}
|
||||
|
||||
func TestSqlite3(t *testing.T) {
|
||||
engine, err := newSqlite3Engine()
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
engine.ShowSQL = showTestSql
|
||||
engine.ShowErr = showTestSql
|
||||
engine.ShowWarn = showTestSql
|
||||
engine.ShowDebug = showTestSql
|
||||
|
||||
testAll(engine, t)
|
||||
testAll2(engine, t)
|
||||
testAll3(engine, t)
|
||||
}
|
||||
|
||||
func TestSqlite3WithCache(t *testing.T) {
|
||||
engine, err := newSqlite3Engine()
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000))
|
||||
engine.ShowSQL = showTestSql
|
||||
engine.ShowErr = showTestSql
|
||||
engine.ShowWarn = showTestSql
|
||||
engine.ShowDebug = showTestSql
|
||||
|
||||
testAll(engine, t)
|
||||
testAll2(engine, t)
|
||||
}
|
||||
|
||||
const (
|
||||
createTableSqlite3 = "CREATE TABLE IF NOT EXISTS `big_struct` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NULL, `title` TEXT NULL, `age` TEXT NULL, `alias` TEXT NULL, `nick_name` TEXT NULL);"
|
||||
dropTableSqlite3 = "DROP TABLE IF EXISTS `big_struct`;"
|
||||
)
|
||||
|
||||
func BenchmarkSqlite3DriverInsert(t *testing.B) {
|
||||
doBenchDriver(newSqlite3DriverDB, createTableSqlite3, dropTableSqlite3,
|
||||
doBenchDriverInsert, t)
|
||||
}
|
||||
|
||||
func BenchmarkSqlite3DriverFind(t *testing.B) {
|
||||
doBenchDriver(newSqlite3DriverDB, createTableSqlite3, dropTableSqlite3,
|
||||
doBenchDriverFind, t)
|
||||
}
|
||||
|
||||
func BenchmarkSqlite3NoCacheInsert(t *testing.B) {
|
||||
t.StopTimer()
|
||||
engine, err := newSqlite3Engine()
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
//engine.ShowSQL = true
|
||||
doBenchInsert(engine, t)
|
||||
}
|
||||
|
||||
func BenchmarkSqlite3NoCacheFind(t *testing.B) {
|
||||
t.StopTimer()
|
||||
engine, err := newSqlite3Engine()
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
//engine.ShowSQL = true
|
||||
doBenchFind(engine, t)
|
||||
}
|
||||
|
||||
func BenchmarkSqlite3NoCacheFindPtr(t *testing.B) {
|
||||
t.StopTimer()
|
||||
engine, err := newSqlite3Engine()
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
//engine.ShowSQL = true
|
||||
doBenchFindPtr(engine, t)
|
||||
}
|
||||
|
||||
func BenchmarkSqlite3CacheInsert(t *testing.B) {
|
||||
t.StopTimer()
|
||||
engine, err := newSqlite3Engine()
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000))
|
||||
doBenchInsert(engine, t)
|
||||
}
|
||||
|
||||
func BenchmarkSqlite3CacheFind(t *testing.B) {
|
||||
t.StopTimer()
|
||||
engine, err := newSqlite3Engine()
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000))
|
||||
doBenchFind(engine, t)
|
||||
}
|
||||
|
||||
func BenchmarkSqlite3CacheFindPtr(t *testing.B) {
|
||||
t.StopTimer()
|
||||
engine, err := newSqlite3Engine()
|
||||
defer engine.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000))
|
||||
doBenchFindPtr(engine, t)
|
||||
}
|
||||
230
statement.go
230
statement.go
|
|
@ -1,23 +1,33 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
//"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
//"strconv"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-xorm/core"
|
||||
)
|
||||
|
||||
type inParam struct {
|
||||
colName string
|
||||
args []interface{}
|
||||
}
|
||||
|
||||
type incrParam struct {
|
||||
colName string
|
||||
arg interface{}
|
||||
}
|
||||
|
||||
// statement save all the sql info for executing SQL
|
||||
type Statement struct {
|
||||
RefTable *Table
|
||||
RefTable *core.Table
|
||||
Engine *Engine
|
||||
Start int
|
||||
LimitN int
|
||||
WhereStr string
|
||||
IdParam *PK
|
||||
IdParam *core.PK
|
||||
Params []interface{}
|
||||
OrderStr string
|
||||
JoinStr string
|
||||
|
|
@ -42,7 +52,8 @@ type Statement struct {
|
|||
allUseBool bool
|
||||
checkVersion bool
|
||||
mustColumnMap map[string]bool
|
||||
inColumns map[string][]interface{}
|
||||
inColumns map[string]*inParam
|
||||
incrColumns map[string]incrParam
|
||||
}
|
||||
|
||||
// init
|
||||
|
|
@ -66,13 +77,14 @@ func (statement *Statement) Init() {
|
|||
statement.RawSQL = ""
|
||||
statement.RawParams = make([]interface{}, 0)
|
||||
statement.BeanArgs = make([]interface{}, 0)
|
||||
statement.UseCache = statement.Engine.UseCache
|
||||
statement.UseCache = true
|
||||
statement.UseAutoTime = true
|
||||
statement.IsDistinct = false
|
||||
statement.allUseBool = false
|
||||
statement.mustColumnMap = make(map[string]bool)
|
||||
statement.checkVersion = true
|
||||
statement.inColumns = make(map[string][]interface{})
|
||||
statement.inColumns = make(map[string]*inParam)
|
||||
statement.incrColumns = make(map[string]incrParam)
|
||||
}
|
||||
|
||||
// add the raw sql statement
|
||||
|
|
@ -92,7 +104,8 @@ func (statement *Statement) Where(querystring string, args ...interface{}) *Stat
|
|||
// add Where & and statment
|
||||
func (statement *Statement) And(querystring string, args ...interface{}) *Statement {
|
||||
if statement.WhereStr != "" {
|
||||
statement.WhereStr = fmt.Sprintf("(%v) AND (%v)", statement.WhereStr, querystring)
|
||||
statement.WhereStr = fmt.Sprintf("(%v) %s (%v)", statement.WhereStr,
|
||||
statement.Engine.dialect.AndStr(), querystring)
|
||||
} else {
|
||||
statement.WhereStr = querystring
|
||||
}
|
||||
|
|
@ -103,7 +116,8 @@ func (statement *Statement) And(querystring string, args ...interface{}) *Statem
|
|||
// add Where & Or statment
|
||||
func (statement *Statement) Or(querystring string, args ...interface{}) *Statement {
|
||||
if statement.WhereStr != "" {
|
||||
statement.WhereStr = fmt.Sprintf("(%v) OR (%v)", statement.WhereStr, querystring)
|
||||
statement.WhereStr = fmt.Sprintf("(%v) %s (%v)", statement.WhereStr,
|
||||
statement.Engine.dialect.OrStr(), querystring)
|
||||
} else {
|
||||
statement.WhereStr = querystring
|
||||
}
|
||||
|
|
@ -206,7 +220,7 @@ func (statement *Statement) Table(tableNameOrBean interface{}) *Statement {
|
|||
if col.SQLType.IsText() {
|
||||
bytes, err := json.Marshal(fieldValue.Interface())
|
||||
if err != nil {
|
||||
engine.LogSQL(err)
|
||||
engine.LogError(err)
|
||||
continue
|
||||
}
|
||||
val = string(bytes)
|
||||
|
|
@ -223,7 +237,7 @@ func (statement *Statement) Table(tableNameOrBean interface{}) *Statement {
|
|||
} else {
|
||||
bytes, err = json.Marshal(fieldValue.Interface())
|
||||
if err != nil {
|
||||
engine.LogSQL(err)
|
||||
engine.LogError(err)
|
||||
continue
|
||||
}
|
||||
val = bytes
|
||||
|
|
@ -240,14 +254,14 @@ func (statement *Statement) Table(tableNameOrBean interface{}) *Statement {
|
|||
}*/
|
||||
|
||||
// Auto generating conditions according a struct
|
||||
func buildConditions(engine *Engine, table *Table, bean interface{},
|
||||
func buildConditions(engine *Engine, table *core.Table, bean interface{},
|
||||
includeVersion bool, includeUpdated bool, includeNil bool,
|
||||
includeAutoIncr bool, allUseBool bool, useAllCols bool,
|
||||
mustColumnMap map[string]bool) ([]string, []interface{}) {
|
||||
|
||||
colNames := make([]string, 0)
|
||||
var args = make([]interface{}, 0)
|
||||
for _, col := range table.Columns {
|
||||
for _, col := range table.Columns() {
|
||||
if !includeVersion && col.IsVersion {
|
||||
continue
|
||||
}
|
||||
|
|
@ -259,10 +273,16 @@ func buildConditions(engine *Engine, table *Table, bean interface{},
|
|||
}
|
||||
//
|
||||
//fmt.Println(engine.dialect.DBType(), Text)
|
||||
if engine.dialect.DBType() == MSSQL && col.SQLType.Name == Text {
|
||||
if engine.dialect.DBType() == core.MSSQL && col.SQLType.Name == core.Text {
|
||||
continue
|
||||
}
|
||||
fieldValue := col.ValueOf(bean)
|
||||
fieldValuePtr, err := col.ValueOf(bean)
|
||||
if err != nil {
|
||||
engine.LogError(err)
|
||||
continue
|
||||
}
|
||||
|
||||
fieldValue := *fieldValuePtr
|
||||
fieldType := reflect.TypeOf(fieldValue.Interface())
|
||||
|
||||
requiredField := useAllCols
|
||||
|
|
@ -362,7 +382,7 @@ func buildConditions(engine *Engine, table *Table, bean interface{},
|
|||
if col.SQLType.IsText() {
|
||||
bytes, err := json.Marshal(fieldValue.Interface())
|
||||
if err != nil {
|
||||
engine.LogSQL(err)
|
||||
engine.LogError(err)
|
||||
continue
|
||||
}
|
||||
val = string(bytes)
|
||||
|
|
@ -379,7 +399,7 @@ func buildConditions(engine *Engine, table *Table, bean interface{},
|
|||
} else {
|
||||
bytes, err = json.Marshal(fieldValue.Interface())
|
||||
if err != nil {
|
||||
engine.LogSQL(err)
|
||||
engine.LogError(err)
|
||||
continue
|
||||
}
|
||||
val = bytes
|
||||
|
|
@ -410,50 +430,56 @@ func (statement *Statement) TableName() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
var (
|
||||
ptrPkType = reflect.TypeOf(&core.PK{})
|
||||
pkType = reflect.TypeOf(core.PK{})
|
||||
)
|
||||
|
||||
// Generate "where id = ? " statment or for composite key "where key1 = ? and key2 = ?"
|
||||
func (statement *Statement) Id(id interface{}) *Statement {
|
||||
|
||||
idValue := reflect.ValueOf(id)
|
||||
idType := reflect.TypeOf(idValue.Interface())
|
||||
|
||||
switch idType {
|
||||
case reflect.TypeOf(&PK{}):
|
||||
if pkPtr, ok := (id).(*PK); ok {
|
||||
case ptrPkType:
|
||||
if pkPtr, ok := (id).(*core.PK); ok {
|
||||
statement.IdParam = pkPtr
|
||||
}
|
||||
case reflect.TypeOf(PK{}):
|
||||
if pk, ok := (id).(PK); ok {
|
||||
case pkType:
|
||||
if pk, ok := (id).(core.PK); ok {
|
||||
statement.IdParam = &pk
|
||||
}
|
||||
default:
|
||||
// TODO treat as int primitve for now, need to handle type check
|
||||
statement.IdParam = &PK{id}
|
||||
|
||||
// !nashtsai! REVIEW although it will be user's mistake if called Id() twice with
|
||||
// different value and Id should be PK's field name, however, at this stage probably
|
||||
// can't tell which table is gonna be used
|
||||
// if statement.WhereStr == "" {
|
||||
// statement.WhereStr = "(id)=?"
|
||||
// statement.Params = []interface{}{id}
|
||||
// } else {
|
||||
// // TODO what if id param has already passed
|
||||
// statement.WhereStr = statement.WhereStr + " AND (id)=?"
|
||||
// statement.Params = append(statement.Params, id)
|
||||
// }
|
||||
// TODO: treat as int primitve for now, need to handle type check?
|
||||
statement.IdParam = &core.PK{id}
|
||||
}
|
||||
|
||||
// !nashtsai! perhaps no need to validate pk values' type just let sql complaint happen
|
||||
|
||||
return statement
|
||||
}
|
||||
|
||||
// Generate "Update ... Set column = column + arg" statment
|
||||
func (statement *Statement) Incr(column string, arg ...interface{}) *Statement {
|
||||
k := strings.ToLower(column)
|
||||
if len(arg) > 0 {
|
||||
statement.incrColumns[k] = incrParam{column, arg[0]}
|
||||
} else {
|
||||
statement.incrColumns[k] = incrParam{column, 1}
|
||||
}
|
||||
return statement
|
||||
}
|
||||
|
||||
// Generate "Update ... Set column = column + arg" statment
|
||||
func (statement *Statement) getInc() map[string]incrParam {
|
||||
return statement.incrColumns
|
||||
}
|
||||
|
||||
// Generate "Where column IN (?) " statment
|
||||
func (statement *Statement) In(column string, args ...interface{}) *Statement {
|
||||
k := strings.ToLower(column)
|
||||
if params, ok := statement.inColumns[k]; ok {
|
||||
statement.inColumns[k] = append(params, args...)
|
||||
if _, ok := statement.inColumns[k]; ok {
|
||||
statement.inColumns[k].args = append(statement.inColumns[k].args, args...)
|
||||
} else {
|
||||
statement.inColumns[k] = args
|
||||
statement.inColumns[k] = &inParam{column, args}
|
||||
}
|
||||
return statement
|
||||
}
|
||||
|
|
@ -465,23 +491,24 @@ func (statement *Statement) genInSql() (string, []interface{}) {
|
|||
|
||||
inStrs := make([]string, 0, len(statement.inColumns))
|
||||
args := make([]interface{}, 0)
|
||||
for column, params := range statement.inColumns {
|
||||
inStrs = append(inStrs, fmt.Sprintf("(%v IN (%v))", statement.Engine.Quote(column),
|
||||
strings.Join(makeArray("?", len(params)), ",")))
|
||||
args = append(args, params...)
|
||||
for _, params := range statement.inColumns {
|
||||
inStrs = append(inStrs, fmt.Sprintf("(%v IN (%v))",
|
||||
statement.Engine.Quote(params.colName),
|
||||
strings.Join(makeArray("?", len(params.args)), ",")))
|
||||
args = append(args, params.args...)
|
||||
}
|
||||
|
||||
if len(statement.inColumns) == 1 {
|
||||
return inStrs[0], args
|
||||
}
|
||||
return fmt.Sprintf("(%v)", strings.Join(inStrs, " AND ")), args
|
||||
return fmt.Sprintf("(%v)", strings.Join(inStrs, " "+statement.Engine.dialect.AndStr()+" ")), args
|
||||
}
|
||||
|
||||
func (statement *Statement) attachInSql() {
|
||||
inSql, inArgs := statement.genInSql()
|
||||
if len(inSql) > 0 {
|
||||
if statement.ConditionStr != "" {
|
||||
statement.ConditionStr += " AND "
|
||||
statement.ConditionStr += " " + statement.Engine.dialect.AndStr() + " "
|
||||
}
|
||||
statement.ConditionStr += inSql
|
||||
statement.Params = append(statement.Params, inArgs...)
|
||||
|
|
@ -610,13 +637,13 @@ func (statement *Statement) Having(conditions string) *Statement {
|
|||
func (statement *Statement) genColumnStr() string {
|
||||
table := statement.RefTable
|
||||
colNames := make([]string, 0)
|
||||
for _, col := range table.Columns {
|
||||
for _, col := range table.Columns() {
|
||||
if statement.OmitStr != "" {
|
||||
if _, ok := statement.columnMap[strings.ToLower(col.Name)]; ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if col.MapType == ONLYTODB {
|
||||
if col.MapType == core.ONLYTODB {
|
||||
continue
|
||||
}
|
||||
colNames = append(colNames, statement.Engine.Quote(statement.TableName())+"."+statement.Engine.Quote(col.Name))
|
||||
|
|
@ -625,54 +652,8 @@ func (statement *Statement) genColumnStr() string {
|
|||
}
|
||||
|
||||
func (statement *Statement) genCreateTableSQL() string {
|
||||
var sql string
|
||||
if statement.Engine.dialect.DBType() == MSSQL {
|
||||
sql = "IF NOT EXISTS (SELECT [name] FROM sys.tables WHERE [name] = '" + statement.TableName() + "' ) CREATE TABLE"
|
||||
} else {
|
||||
sql = "CREATE TABLE IF NOT EXISTS "
|
||||
}
|
||||
sql += statement.Engine.Quote(statement.TableName()) + " ("
|
||||
|
||||
pkList := []string{}
|
||||
|
||||
for _, colName := range statement.RefTable.ColumnsSeq {
|
||||
col := statement.RefTable.Columns[strings.ToLower(colName)]
|
||||
if col.IsPrimaryKey {
|
||||
pkList = append(pkList, col.Name)
|
||||
}
|
||||
}
|
||||
|
||||
statement.Engine.LogDebug("len:", len(pkList))
|
||||
for _, colName := range statement.RefTable.ColumnsSeq {
|
||||
col := statement.RefTable.Columns[strings.ToLower(colName)]
|
||||
if col.IsPrimaryKey && len(pkList) == 1 {
|
||||
sql += col.String(statement.Engine.dialect)
|
||||
} else {
|
||||
sql += col.stringNoPk(statement.Engine.dialect)
|
||||
}
|
||||
sql = strings.TrimSpace(sql)
|
||||
sql += ", "
|
||||
}
|
||||
|
||||
if len(pkList) > 1 {
|
||||
sql += "PRIMARY KEY ( "
|
||||
sql += strings.Join(pkList, ",")
|
||||
sql += " ), "
|
||||
}
|
||||
|
||||
sql = sql[:len(sql)-2] + ")"
|
||||
if statement.Engine.dialect.SupportEngine() && statement.StoreEngine != "" {
|
||||
sql += " ENGINE=" + statement.StoreEngine
|
||||
}
|
||||
if statement.Engine.dialect.SupportCharset() {
|
||||
if statement.Charset != "" {
|
||||
sql += " DEFAULT CHARSET " + statement.Charset
|
||||
} else if statement.Engine.dialect.URI().charset != "" {
|
||||
sql += " DEFAULT CHARSET " + statement.Engine.dialect.URI().charset
|
||||
}
|
||||
}
|
||||
sql += ";"
|
||||
return sql
|
||||
return statement.Engine.dialect.CreateTableSql(statement.RefTable, statement.AltTableName,
|
||||
statement.StoreEngine, statement.Charset)
|
||||
}
|
||||
|
||||
func indexName(tableName, idxName string) string {
|
||||
|
|
@ -684,7 +665,7 @@ func (s *Statement) genIndexSQL() []string {
|
|||
tbName := s.TableName()
|
||||
quote := s.Engine.Quote
|
||||
for idxName, index := range s.RefTable.Indexes {
|
||||
if index.Type == IndexType {
|
||||
if index.Type == core.IndexType {
|
||||
sql := fmt.Sprintf("CREATE INDEX %v ON %v (%v);", quote(indexName(tbName, idxName)),
|
||||
quote(tbName), quote(strings.Join(index.Cols, quote(","))))
|
||||
sqls = append(sqls, sql)
|
||||
|
|
@ -700,11 +681,9 @@ func uniqueName(tableName, uqeName string) string {
|
|||
func (s *Statement) genUniqueSQL() []string {
|
||||
var sqls []string = make([]string, 0)
|
||||
tbName := s.TableName()
|
||||
quote := s.Engine.Quote
|
||||
for idxName, unique := range s.RefTable.Indexes {
|
||||
if unique.Type == UniqueType {
|
||||
sql := fmt.Sprintf("CREATE UNIQUE INDEX %v ON %v (%v);", quote(uniqueName(tbName, idxName)),
|
||||
quote(tbName), quote(strings.Join(unique.Cols, quote(","))))
|
||||
for _, index := range s.RefTable.Indexes {
|
||||
if index.Type == core.UniqueType {
|
||||
sql := s.Engine.dialect.CreateIndexSql(tbName, index)
|
||||
sqls = append(sqls, sql)
|
||||
}
|
||||
}
|
||||
|
|
@ -715,9 +694,9 @@ func (s *Statement) genDelIndexSQL() []string {
|
|||
var sqls []string = make([]string, 0)
|
||||
for idxName, index := range s.RefTable.Indexes {
|
||||
var rIdxName string
|
||||
if index.Type == UniqueType {
|
||||
if index.Type == core.UniqueType {
|
||||
rIdxName = uniqueName(s.TableName(), idxName)
|
||||
} else if index.Type == IndexType {
|
||||
} else if index.Type == core.IndexType {
|
||||
rIdxName = indexName(s.TableName(), idxName)
|
||||
}
|
||||
sql := fmt.Sprintf("DROP INDEX %v", s.Engine.Quote(rIdxName))
|
||||
|
|
@ -741,7 +720,7 @@ func (statement *Statement) genGetSql(bean interface{}) (string, []interface{})
|
|||
false, true, statement.allUseBool, statement.useAllCols,
|
||||
statement.mustColumnMap)
|
||||
|
||||
statement.ConditionStr = strings.Join(colNames, " AND ")
|
||||
statement.ConditionStr = strings.Join(colNames, " "+statement.Engine.dialect.AndStr()+" ")
|
||||
statement.BeanArgs = args
|
||||
|
||||
var columnStr string = statement.ColumnStr
|
||||
|
|
@ -752,14 +731,14 @@ func (statement *Statement) genGetSql(bean interface{}) (string, []interface{})
|
|||
return statement.genSelectSql(columnStr), append(statement.Params, statement.BeanArgs...)
|
||||
}
|
||||
|
||||
func (s *Statement) genAddColumnStr(col *Column) (string, []interface{}) {
|
||||
func (s *Statement) genAddColumnStr(col *core.Column) (string, []interface{}) {
|
||||
quote := s.Engine.Quote
|
||||
sql := fmt.Sprintf("ALTER TABLE %v ADD COLUMN %v;", quote(s.TableName()),
|
||||
col.String(s.Engine.dialect))
|
||||
return sql, []interface{}{}
|
||||
}
|
||||
|
||||
func (s *Statement) genAddIndexStr(idxName string, cols []string) (string, []interface{}) {
|
||||
/*func (s *Statement) genAddIndexStr(idxName string, cols []string) (string, []interface{}) {
|
||||
quote := s.Engine.Quote
|
||||
colstr := quote(strings.Join(cols, quote(", ")))
|
||||
sql := fmt.Sprintf("CREATE INDEX %v ON %v (%v);", quote(idxName), quote(s.TableName()), colstr)
|
||||
|
|
@ -771,7 +750,7 @@ func (s *Statement) genAddUniqueStr(uqeName string, cols []string) (string, []in
|
|||
colstr := quote(strings.Join(cols, quote(", ")))
|
||||
sql := fmt.Sprintf("CREATE UNIQUE INDEX %v ON %v (%v);", quote(uqeName), quote(s.TableName()), colstr)
|
||||
return sql, []interface{}{}
|
||||
}
|
||||
}*/
|
||||
|
||||
func (statement *Statement) genCountSql(bean interface{}) (string, []interface{}) {
|
||||
table := statement.Engine.autoMap(bean)
|
||||
|
|
@ -825,13 +804,14 @@ func (statement *Statement) genSelectSql(columnStr string) (a string) {
|
|||
if statement.OrderStr != "" {
|
||||
a = fmt.Sprintf("%v ORDER BY %v", a, statement.OrderStr)
|
||||
}
|
||||
if statement.Engine.dialect.DBType() != MSSQL {
|
||||
if statement.Engine.dialect.DBType() != core.MSSQL {
|
||||
if statement.Start > 0 {
|
||||
a = fmt.Sprintf("%v LIMIT %v OFFSET %v", a, statement.LimitN, statement.Start)
|
||||
} else if statement.LimitN > 0 {
|
||||
a = fmt.Sprintf("%v LIMIT %v", a, statement.LimitN)
|
||||
}
|
||||
} else {
|
||||
//TODO: for mssql, should handler limit.
|
||||
/*SELECT * FROM (
|
||||
SELECT *, ROW_NUMBER() OVER (ORDER BY id desc) as row FROM "userinfo"
|
||||
) a WHERE row > [start] and row <= [start+limit] order by id desc*/
|
||||
|
|
@ -841,30 +821,12 @@ func (statement *Statement) genSelectSql(columnStr string) (a string) {
|
|||
}
|
||||
|
||||
func (statement *Statement) processIdParam() {
|
||||
|
||||
if statement.IdParam != nil {
|
||||
i := 0
|
||||
colCnt := len(statement.RefTable.ColumnsSeq)
|
||||
for _, elem := range *(statement.IdParam) {
|
||||
for ; i < colCnt; i++ {
|
||||
colName := statement.RefTable.ColumnsSeq[i]
|
||||
col := statement.RefTable.Columns[strings.ToLower(colName)]
|
||||
if col.IsPrimaryKey {
|
||||
statement.And(fmt.Sprintf("%v=?", col.Name), elem)
|
||||
i++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// !nashtsai! REVIEW what if statement.IdParam has insufficient pk item? handle it
|
||||
// as empty string for now, so this will result sql exec failed instead of unexpected
|
||||
// false update/delete
|
||||
for ; i < colCnt; i++ {
|
||||
colName := statement.RefTable.ColumnsSeq[i]
|
||||
col := statement.RefTable.Columns[strings.ToLower(colName)]
|
||||
if col.IsPrimaryKey {
|
||||
statement.And(fmt.Sprintf("%v=?", col.Name), "")
|
||||
for i, col := range statement.RefTable.PKColumns() {
|
||||
if i < len(*(statement.IdParam)) {
|
||||
statement.And(fmt.Sprintf("%v=?", statement.Engine.Quote(col.Name)), (*(statement.IdParam))[i])
|
||||
} else {
|
||||
statement.And(fmt.Sprintf("%v=?", statement.Engine.Quote(col.Name)), "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
484
table.go
484
table.go
|
|
@ -1,484 +0,0 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// xorm SQL types
|
||||
type SQLType struct {
|
||||
Name string
|
||||
DefaultLength int
|
||||
DefaultLength2 int
|
||||
}
|
||||
|
||||
func (s *SQLType) IsText() bool {
|
||||
return s.Name == Char || s.Name == Varchar || s.Name == TinyText ||
|
||||
s.Name == Text || s.Name == MediumText || s.Name == LongText
|
||||
}
|
||||
|
||||
func (s *SQLType) IsBlob() bool {
|
||||
return (s.Name == TinyBlob) || (s.Name == Blob) ||
|
||||
s.Name == MediumBlob || s.Name == LongBlob ||
|
||||
s.Name == Binary || s.Name == VarBinary || s.Name == Bytea
|
||||
}
|
||||
|
||||
const ()
|
||||
|
||||
var (
|
||||
Bit = "BIT"
|
||||
TinyInt = "TINYINT"
|
||||
SmallInt = "SMALLINT"
|
||||
MediumInt = "MEDIUMINT"
|
||||
Int = "INT"
|
||||
Integer = "INTEGER"
|
||||
BigInt = "BIGINT"
|
||||
|
||||
Char = "CHAR"
|
||||
Varchar = "VARCHAR"
|
||||
TinyText = "TINYTEXT"
|
||||
Text = "TEXT"
|
||||
MediumText = "MEDIUMTEXT"
|
||||
LongText = "LONGTEXT"
|
||||
|
||||
Date = "DATE"
|
||||
DateTime = "DATETIME"
|
||||
Time = "TIME"
|
||||
TimeStamp = "TIMESTAMP"
|
||||
TimeStampz = "TIMESTAMPZ"
|
||||
|
||||
Decimal = "DECIMAL"
|
||||
Numeric = "NUMERIC"
|
||||
|
||||
Real = "REAL"
|
||||
Float = "FLOAT"
|
||||
Double = "DOUBLE"
|
||||
|
||||
Binary = "BINARY"
|
||||
VarBinary = "VARBINARY"
|
||||
TinyBlob = "TINYBLOB"
|
||||
Blob = "BLOB"
|
||||
MediumBlob = "MEDIUMBLOB"
|
||||
LongBlob = "LONGBLOB"
|
||||
Bytea = "BYTEA"
|
||||
|
||||
Bool = "BOOL"
|
||||
|
||||
Serial = "SERIAL"
|
||||
BigSerial = "BIGSERIAL"
|
||||
|
||||
sqlTypes = map[string]bool{
|
||||
Bit: true,
|
||||
TinyInt: true,
|
||||
SmallInt: true,
|
||||
MediumInt: true,
|
||||
Int: true,
|
||||
Integer: true,
|
||||
BigInt: true,
|
||||
|
||||
Char: true,
|
||||
Varchar: true,
|
||||
TinyText: true,
|
||||
Text: true,
|
||||
MediumText: true,
|
||||
LongText: true,
|
||||
|
||||
Date: true,
|
||||
DateTime: true,
|
||||
Time: true,
|
||||
TimeStamp: true,
|
||||
TimeStampz: true,
|
||||
|
||||
Decimal: true,
|
||||
Numeric: true,
|
||||
|
||||
Binary: true,
|
||||
VarBinary: true,
|
||||
Real: true,
|
||||
Float: true,
|
||||
Double: true,
|
||||
TinyBlob: true,
|
||||
Blob: true,
|
||||
MediumBlob: true,
|
||||
LongBlob: true,
|
||||
Bytea: true,
|
||||
|
||||
Bool: true,
|
||||
|
||||
Serial: true,
|
||||
BigSerial: true,
|
||||
}
|
||||
|
||||
intTypes = sort.StringSlice{"*int", "*int16", "*int32", "*int8"}
|
||||
uintTypes = sort.StringSlice{"*uint", "*uint16", "*uint32", "*uint8"}
|
||||
)
|
||||
|
||||
// !nashtsai! treat following var as interal const values, these are used for reflect.TypeOf comparision
|
||||
var (
|
||||
c_EMPTY_STRING string
|
||||
c_BOOL_DEFAULT bool
|
||||
c_BYTE_DEFAULT byte
|
||||
c_COMPLEX64_DEFAULT complex64
|
||||
c_COMPLEX128_DEFAULT complex128
|
||||
c_FLOAT32_DEFAULT float32
|
||||
c_FLOAT64_DEFAULT float64
|
||||
c_INT64_DEFAULT int64
|
||||
c_UINT64_DEFAULT uint64
|
||||
c_INT32_DEFAULT int32
|
||||
c_UINT32_DEFAULT uint32
|
||||
c_INT16_DEFAULT int16
|
||||
c_UINT16_DEFAULT uint16
|
||||
c_INT8_DEFAULT int8
|
||||
c_UINT8_DEFAULT uint8
|
||||
c_INT_DEFAULT int
|
||||
c_UINT_DEFAULT uint
|
||||
c_TIME_DEFAULT time.Time
|
||||
)
|
||||
|
||||
func Type2SQLType(t reflect.Type) (st SQLType) {
|
||||
switch k := t.Kind(); k {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
||||
st = SQLType{Int, 0, 0}
|
||||
case reflect.Int64, reflect.Uint64:
|
||||
st = SQLType{BigInt, 0, 0}
|
||||
case reflect.Float32:
|
||||
st = SQLType{Float, 0, 0}
|
||||
case reflect.Float64:
|
||||
st = SQLType{Double, 0, 0}
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
st = SQLType{Varchar, 64, 0}
|
||||
case reflect.Array, reflect.Slice, reflect.Map:
|
||||
if t.Elem() == reflect.TypeOf(c_BYTE_DEFAULT) {
|
||||
st = SQLType{Blob, 0, 0}
|
||||
} else {
|
||||
st = SQLType{Text, 0, 0}
|
||||
}
|
||||
case reflect.Bool:
|
||||
st = SQLType{Bool, 0, 0}
|
||||
case reflect.String:
|
||||
st = SQLType{Varchar, 255, 0}
|
||||
case reflect.Struct:
|
||||
if t == reflect.TypeOf(c_TIME_DEFAULT) {
|
||||
st = SQLType{DateTime, 0, 0}
|
||||
} else {
|
||||
// TODO need to handle association struct
|
||||
st = SQLType{Text, 0, 0}
|
||||
}
|
||||
case reflect.Ptr:
|
||||
st, _ = ptrType2SQLType(t)
|
||||
default:
|
||||
st = SQLType{Text, 0, 0}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ptrType2SQLType(t reflect.Type) (st SQLType, has bool) {
|
||||
has = true
|
||||
|
||||
switch t {
|
||||
case reflect.TypeOf(&c_EMPTY_STRING):
|
||||
st = SQLType{Varchar, 255, 0}
|
||||
return
|
||||
case reflect.TypeOf(&c_BOOL_DEFAULT):
|
||||
st = SQLType{Bool, 0, 0}
|
||||
case reflect.TypeOf(&c_COMPLEX64_DEFAULT), reflect.TypeOf(&c_COMPLEX128_DEFAULT):
|
||||
st = SQLType{Varchar, 64, 0}
|
||||
case reflect.TypeOf(&c_FLOAT32_DEFAULT):
|
||||
st = SQLType{Float, 0, 0}
|
||||
case reflect.TypeOf(&c_FLOAT64_DEFAULT):
|
||||
st = SQLType{Double, 0, 0}
|
||||
case reflect.TypeOf(&c_INT64_DEFAULT), reflect.TypeOf(&c_UINT64_DEFAULT):
|
||||
st = SQLType{BigInt, 0, 0}
|
||||
case reflect.TypeOf(&c_TIME_DEFAULT):
|
||||
st = SQLType{DateTime, 0, 0}
|
||||
case reflect.TypeOf(&c_INT_DEFAULT), reflect.TypeOf(&c_INT32_DEFAULT), reflect.TypeOf(&c_INT8_DEFAULT), reflect.TypeOf(&c_INT16_DEFAULT), reflect.TypeOf(&c_UINT_DEFAULT), reflect.TypeOf(&c_UINT32_DEFAULT), reflect.TypeOf(&c_UINT8_DEFAULT), reflect.TypeOf(&c_UINT16_DEFAULT):
|
||||
st = SQLType{Int, 0, 0}
|
||||
default:
|
||||
has = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// default sql type change to go types
|
||||
func SQLType2Type(st SQLType) reflect.Type {
|
||||
name := strings.ToUpper(st.Name)
|
||||
switch name {
|
||||
case Bit, TinyInt, SmallInt, MediumInt, Int, Integer, Serial:
|
||||
return reflect.TypeOf(1)
|
||||
case BigInt, BigSerial:
|
||||
return reflect.TypeOf(int64(1))
|
||||
case Float, Real:
|
||||
return reflect.TypeOf(float32(1))
|
||||
case Double:
|
||||
return reflect.TypeOf(float64(1))
|
||||
case Char, Varchar, TinyText, Text, MediumText, LongText:
|
||||
return reflect.TypeOf("")
|
||||
case TinyBlob, Blob, LongBlob, Bytea, Binary, MediumBlob, VarBinary:
|
||||
return reflect.TypeOf([]byte{})
|
||||
case Bool:
|
||||
return reflect.TypeOf(true)
|
||||
case DateTime, Date, Time, TimeStamp, TimeStampz:
|
||||
return reflect.TypeOf(c_TIME_DEFAULT)
|
||||
case Decimal, Numeric:
|
||||
return reflect.TypeOf("")
|
||||
default:
|
||||
return reflect.TypeOf("")
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
IndexType = iota + 1
|
||||
UniqueType
|
||||
)
|
||||
|
||||
// database index
|
||||
type Index struct {
|
||||
Name string
|
||||
Type int
|
||||
Cols []string
|
||||
}
|
||||
|
||||
// add columns which will be composite index
|
||||
func (index *Index) AddColumn(cols ...string) {
|
||||
for _, col := range cols {
|
||||
index.Cols = append(index.Cols, col)
|
||||
}
|
||||
}
|
||||
|
||||
// new an index
|
||||
func NewIndex(name string, indexType int) *Index {
|
||||
return &Index{name, indexType, make([]string, 0)}
|
||||
}
|
||||
|
||||
const (
|
||||
TWOSIDES = iota + 1
|
||||
ONLYTODB
|
||||
ONLYFROMDB
|
||||
)
|
||||
|
||||
// database column
|
||||
type Column struct {
|
||||
Name string
|
||||
FieldName string
|
||||
SQLType SQLType
|
||||
Length int
|
||||
Length2 int
|
||||
Nullable bool
|
||||
Default string
|
||||
Indexes map[string]bool
|
||||
IsPrimaryKey bool
|
||||
IsAutoIncrement bool
|
||||
MapType int
|
||||
IsCreated bool
|
||||
IsUpdated bool
|
||||
IsCascade bool
|
||||
IsVersion bool
|
||||
DefaultIsEmpty bool
|
||||
}
|
||||
|
||||
// generate column description string according dialect
|
||||
func (col *Column) String(d dialect) string {
|
||||
sql := d.QuoteStr() + col.Name + d.QuoteStr() + " "
|
||||
|
||||
sql += d.SqlType(col) + " "
|
||||
|
||||
if col.IsPrimaryKey {
|
||||
sql += "PRIMARY KEY "
|
||||
if col.IsAutoIncrement {
|
||||
sql += d.AutoIncrStr() + " "
|
||||
}
|
||||
}
|
||||
|
||||
if col.Nullable {
|
||||
sql += "NULL "
|
||||
} else {
|
||||
sql += "NOT NULL "
|
||||
}
|
||||
|
||||
if col.Default != "" {
|
||||
sql += "DEFAULT " + col.Default + " "
|
||||
} else if col.IsVersion {
|
||||
sql += "DEFAULT 1 "
|
||||
}
|
||||
|
||||
return sql
|
||||
}
|
||||
|
||||
func (col *Column) stringNoPk(d dialect) string {
|
||||
sql := d.QuoteStr() + col.Name + d.QuoteStr() + " "
|
||||
|
||||
sql += d.SqlType(col) + " "
|
||||
|
||||
if col.Nullable {
|
||||
sql += "NULL "
|
||||
} else {
|
||||
sql += "NOT NULL "
|
||||
}
|
||||
|
||||
if col.Default != "" {
|
||||
sql += "DEFAULT " + col.Default + " "
|
||||
} else if col.IsVersion {
|
||||
sql += "DEFAULT 1 "
|
||||
}
|
||||
|
||||
return sql
|
||||
}
|
||||
|
||||
// return col's filed of struct's value
|
||||
func (col *Column) ValueOf(bean interface{}) reflect.Value {
|
||||
var fieldValue reflect.Value
|
||||
if strings.Contains(col.FieldName, ".") {
|
||||
fields := strings.Split(col.FieldName, ".")
|
||||
if len(fields) > 2 {
|
||||
return reflect.ValueOf(nil)
|
||||
}
|
||||
|
||||
fieldValue = reflect.Indirect(reflect.ValueOf(bean)).FieldByName(fields[0])
|
||||
fieldValue = fieldValue.FieldByName(fields[1])
|
||||
} else {
|
||||
fieldValue = reflect.Indirect(reflect.ValueOf(bean)).FieldByName(col.FieldName)
|
||||
}
|
||||
return fieldValue
|
||||
}
|
||||
|
||||
// database table
|
||||
type Table struct {
|
||||
Name string
|
||||
Type reflect.Type
|
||||
ColumnsSeq []string
|
||||
Columns map[string]*Column
|
||||
Indexes map[string]*Index
|
||||
PrimaryKeys []string
|
||||
AutoIncrement string
|
||||
Created map[string]bool
|
||||
Updated string
|
||||
Version string
|
||||
Cacher Cacher
|
||||
}
|
||||
|
||||
/*
|
||||
func NewTable(name string, t reflect.Type) *Table {
|
||||
return &Table{Name: name, Type: t,
|
||||
ColumnsSeq: make([]string, 0),
|
||||
Columns: make(map[string]*Column),
|
||||
Indexes: make(map[string]*Index),
|
||||
Created: make(map[string]bool),
|
||||
}
|
||||
}*/
|
||||
|
||||
// if has primary key, return column
|
||||
func (table *Table) PKColumns() []*Column {
|
||||
columns := make([]*Column, 0)
|
||||
for _, name := range table.PrimaryKeys {
|
||||
columns = append(columns, table.Columns[strings.ToLower(name)])
|
||||
}
|
||||
return columns
|
||||
}
|
||||
|
||||
func (table *Table) AutoIncrColumn() *Column {
|
||||
return table.Columns[strings.ToLower(table.AutoIncrement)]
|
||||
}
|
||||
|
||||
func (table *Table) VersionColumn() *Column {
|
||||
return table.Columns[strings.ToLower(table.Version)]
|
||||
}
|
||||
|
||||
// add a column to table
|
||||
func (table *Table) AddColumn(col *Column) {
|
||||
table.ColumnsSeq = append(table.ColumnsSeq, col.Name)
|
||||
table.Columns[strings.ToLower(col.Name)] = col
|
||||
if col.IsPrimaryKey {
|
||||
table.PrimaryKeys = append(table.PrimaryKeys, col.Name)
|
||||
}
|
||||
if col.IsAutoIncrement {
|
||||
table.AutoIncrement = col.Name
|
||||
}
|
||||
if col.IsCreated {
|
||||
table.Created[col.Name] = true
|
||||
}
|
||||
if col.IsUpdated {
|
||||
table.Updated = col.Name
|
||||
}
|
||||
if col.IsVersion {
|
||||
table.Version = col.Name
|
||||
}
|
||||
}
|
||||
|
||||
// add an index or an unique to table
|
||||
func (table *Table) AddIndex(index *Index) {
|
||||
table.Indexes[index.Name] = index
|
||||
}
|
||||
|
||||
func (table *Table) genCols(session *Session, bean interface{}, useCol bool, includeQuote bool) ([]string, []interface{}, error) {
|
||||
colNames := make([]string, 0)
|
||||
args := make([]interface{}, 0)
|
||||
|
||||
for _, col := range table.Columns {
|
||||
lColName := strings.ToLower(col.Name)
|
||||
if useCol && !col.IsVersion && !col.IsCreated && !col.IsUpdated {
|
||||
if _, ok := session.Statement.columnMap[lColName]; !ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if col.MapType == ONLYFROMDB {
|
||||
continue
|
||||
}
|
||||
|
||||
fieldValue := col.ValueOf(bean)
|
||||
if col.IsAutoIncrement {
|
||||
switch fieldValue.Type().Kind() {
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64:
|
||||
if fieldValue.Int() == 0 {
|
||||
continue
|
||||
}
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64:
|
||||
if fieldValue.Uint() == 0 {
|
||||
continue
|
||||
}
|
||||
case reflect.String:
|
||||
if len(fieldValue.String()) == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if session.Statement.ColumnStr != "" {
|
||||
if _, ok := session.Statement.columnMap[lColName]; !ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if session.Statement.OmitStr != "" {
|
||||
if _, ok := session.Statement.columnMap[lColName]; ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (col.IsCreated || col.IsUpdated) && session.Statement.UseAutoTime {
|
||||
args = append(args, session.Engine.NowTime(col.SQLType.Name))
|
||||
} else if col.IsVersion && session.Statement.checkVersion {
|
||||
args = append(args, 1)
|
||||
} else {
|
||||
arg, err := session.value2Interface(col, fieldValue)
|
||||
if err != nil {
|
||||
return colNames, args, err
|
||||
}
|
||||
args = append(args, arg)
|
||||
}
|
||||
|
||||
if includeQuote {
|
||||
colNames = append(colNames, session.Engine.Quote(col.Name)+" = ?")
|
||||
} else {
|
||||
colNames = append(colNames, col.Name)
|
||||
}
|
||||
}
|
||||
return colNames, args, nil
|
||||
}
|
||||
|
||||
// Conversion is an interface. A type implements Conversion will according
|
||||
// the custom method to fill into database and retrieve from database.
|
||||
type Conversion interface {
|
||||
FromDB([]byte) error
|
||||
ToDB() ([]byte, error)
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
--DROP DATABASE xorm_test;
|
||||
--DROP DATABASE xorm_test2;
|
||||
CREATE DATABASE IF NOT EXISTS xorm_test CHARACTER SET utf8 COLLATE utf8_general_ci;
|
||||
CREATE DATABASE IF NOT EXISTS xorm_test2 CHARACTER SET utf8 COLLATE utf8_general_ci;
|
||||
115
xorm.go
115
xorm.go
|
|
@ -1,18 +1,59 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-xorm/core"
|
||||
)
|
||||
|
||||
const (
|
||||
Version string = "0.3.1"
|
||||
Version string = "0.4"
|
||||
)
|
||||
|
||||
// !nashtsai! implicit register drivers and dialects is no good, as init() can be called before sql driver got registered
|
||||
// func init() {
|
||||
// regDrvsNDialects()
|
||||
// }
|
||||
|
||||
func regDrvsNDialects() bool {
|
||||
if core.RegisteredDriverSize() == 0 {
|
||||
providedDrvsNDialects := map[string]struct {
|
||||
dbType core.DbType
|
||||
getDriver func() core.Driver
|
||||
getDialect func() core.Dialect
|
||||
}{
|
||||
"odbc": {"mssql", func() core.Driver { return &odbcDriver{} }, func() core.Dialect { return &mssql{} }}, // !nashtsai! TODO change this when supporting MS Access
|
||||
"mysql": {"mysql", func() core.Driver { return &mysqlDriver{} }, func() core.Dialect { return &mysql{} }},
|
||||
"mymysql": {"mysql", func() core.Driver { return &mymysqlDriver{} }, func() core.Dialect { return &mysql{} }},
|
||||
"postgres": {"postgres", func() core.Driver { return &pqDriver{} }, func() core.Dialect { return &postgres{} }},
|
||||
"sqlite3": {"sqlite3", func() core.Driver { return &sqlite3Driver{} }, func() core.Dialect { return &sqlite3{} }},
|
||||
"oci8": {"oracle", func() core.Driver { return &oci8Driver{} }, func() core.Dialect { return &oracle{} }},
|
||||
"goracle": {"oracle", func() core.Driver { return &goracleDriver{} }, func() core.Dialect { return &oracle{} }},
|
||||
}
|
||||
|
||||
for driverName, v := range providedDrvsNDialects {
|
||||
_, err := sql.Open(driverName, "")
|
||||
if err == nil {
|
||||
// fmt.Printf("driver succeed: %v\n", driverName)
|
||||
core.RegisterDriver(driverName, v.getDriver())
|
||||
core.RegisterDialect(v.dbType, v.getDialect())
|
||||
} else {
|
||||
// fmt.Printf("driver failed: %v | err: %v\n", driverName, err)
|
||||
}
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func close(engine *Engine) {
|
||||
engine.Close()
|
||||
}
|
||||
|
|
@ -20,49 +61,53 @@ func close(engine *Engine) {
|
|||
// new a db manager according to the parameter. Currently support four
|
||||
// drivers
|
||||
func NewEngine(driverName string, dataSourceName string) (*Engine, error) {
|
||||
engine := &Engine{
|
||||
DriverName: driverName,
|
||||
DataSourceName: dataSourceName,
|
||||
Filters: make([]Filter, 0),
|
||||
TimeZone: "Local",
|
||||
}
|
||||
engine.SetMapper(SnakeMapper{})
|
||||
|
||||
if driverName == SQLITE {
|
||||
engine.dialect = &sqlite3{}
|
||||
} else if driverName == MYSQL {
|
||||
engine.dialect = &mysql{}
|
||||
} else if driverName == POSTGRES {
|
||||
engine.dialect = &postgres{}
|
||||
engine.Filters = append(engine.Filters, &PgSeqFilter{})
|
||||
engine.Filters = append(engine.Filters, &QuoteFilter{})
|
||||
} else if driverName == MYMYSQL {
|
||||
engine.dialect = &mymysql{}
|
||||
} else if driverName == "odbc" {
|
||||
engine.dialect = &mssql{quoteFilter: &QuoteFilter{}}
|
||||
engine.Filters = append(engine.Filters, &QuoteFilter{})
|
||||
} else if driverName == ORACLE_OCI {
|
||||
engine.dialect = &oracle{}
|
||||
engine.Filters = append(engine.Filters, &QuoteFilter{})
|
||||
} else {
|
||||
regDrvsNDialects()
|
||||
driver := core.QueryDriver(driverName)
|
||||
if driver == nil {
|
||||
return nil, errors.New(fmt.Sprintf("Unsupported driver name: %v", driverName))
|
||||
}
|
||||
err := engine.dialect.Init(driverName, dataSourceName)
|
||||
|
||||
uri, err := driver.Parse(driverName, dataSourceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
engine.Tables = make(map[reflect.Type]*Table)
|
||||
engine.mutex = &sync.RWMutex{}
|
||||
engine.TagIdentifier = "xorm"
|
||||
dialect := core.QueryDialect(uri.DbType)
|
||||
if dialect == nil {
|
||||
return nil, errors.New(fmt.Sprintf("Unsupported dialect type: %v", uri.DbType))
|
||||
}
|
||||
|
||||
engine.Filters = append(engine.Filters, &IdFilter{})
|
||||
engine.Logger = os.Stdout
|
||||
db, err := core.Open(driverName, dataSourceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//engine.Pool = NewSimpleConnectPool()
|
||||
//engine.Pool = NewNoneConnectPool()
|
||||
err = dialect.Init(db, uri, driverName, dataSourceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
engine := &Engine{
|
||||
db: db,
|
||||
dialect: dialect,
|
||||
Tables: make(map[reflect.Type]*core.Table),
|
||||
mutex: &sync.RWMutex{},
|
||||
TagIdentifier: "xorm",
|
||||
Logger: NewSimpleLogger(os.Stdout),
|
||||
TZLocation: time.Local,
|
||||
}
|
||||
|
||||
engine.SetMapper(core.NewCacheMapper(new(core.SnakeMapper)))
|
||||
|
||||
//engine.Filters = dialect.Filters()
|
||||
//engine.Cacher = NewLRUCacher()
|
||||
err = engine.SetPool(NewSysConnectPool())
|
||||
//err = engine.SetPool(NewSysConnectPool())
|
||||
|
||||
runtime.SetFinalizer(engine, close)
|
||||
return engine, err
|
||||
}
|
||||
|
||||
// clone an engine
|
||||
func (engine *Engine) Clone() (*Engine, error) {
|
||||
return NewEngine(engine.dialect.DriverName(), engine.dialect.DataSourceName())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
[deps]
|
||||
github.com/lunny/xorm=../
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
# xorm tools
|
||||
|
||||
|
||||
xorm tools is a set of tools for database operation.
|
||||
|
||||
## Install
|
||||
|
||||
`go get github.com/lunny/xorm/xorm`
|
||||
|
||||
and you should install the depends below:
|
||||
|
||||
* github.com/lunny/xorm
|
||||
|
||||
* Mysql: [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)
|
||||
|
||||
* MyMysql: [github.com/ziutek/mymysql/godrv](https://github.com/ziutek/mymysql/godrv)
|
||||
|
||||
* SQLite: [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3)
|
||||
|
||||
* Postgres: [github.com/bylevel/pq](https://github.com/bylevel/pq)
|
||||
|
||||
|
||||
## Reverse
|
||||
|
||||
After you installed the tool, you can type
|
||||
|
||||
`xorm help reverse`
|
||||
|
||||
to get help
|
||||
|
||||
example:
|
||||
|
||||
sqlite:
|
||||
`xorm reverse sqite3 test.db templates/goxorm`
|
||||
|
||||
mysql:
|
||||
`xorm reverse mysql root:@/xorm_test?charset=utf8 templates/goxorm`
|
||||
|
||||
mymysql:
|
||||
`xorm reverse mymysql xorm_test2/root/ templates/goxorm`
|
||||
|
||||
postgres:
|
||||
`xorm reverse postgres "dbname=xorm_test sslmode=disable" templates/goxorm`
|
||||
|
||||
will generated go files in `./model` directory
|
||||
|
||||
## Template and Config
|
||||
|
||||
Now, xorm tool supports go and c++ two languages and have go, goxorm, c++ three of default templates. In template directory, we can put a config file to control how to generating.
|
||||
|
||||
````
|
||||
lang=go
|
||||
genJson=1
|
||||
```
|
||||
|
||||
lang must be go or c++ now.
|
||||
genJson can be 1 or 0, if 1 then the struct will have json tag.
|
||||
|
||||
## LICENSE
|
||||
|
||||
BSD License
|
||||
[http://creativecommons.org/licenses/BSD/](http://creativecommons.org/licenses/BSD/)
|
||||
65
xorm/c++.go
65
xorm/c++.go
|
|
@ -1,65 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
//"fmt"
|
||||
"github.com/go-xorm/xorm"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var (
|
||||
CPlusTmpl LangTmpl = LangTmpl{
|
||||
template.FuncMap{"Mapper": mapper.Table2Obj,
|
||||
"Type": cPlusTypeStr,
|
||||
"UnTitle": unTitle,
|
||||
},
|
||||
nil,
|
||||
genCPlusImports,
|
||||
}
|
||||
)
|
||||
|
||||
func cPlusTypeStr(col *xorm.Column) string {
|
||||
tp := col.SQLType
|
||||
name := strings.ToUpper(tp.Name)
|
||||
switch name {
|
||||
case xorm.Bit, xorm.TinyInt, xorm.SmallInt, xorm.MediumInt, xorm.Int, xorm.Integer, xorm.Serial:
|
||||
return "int"
|
||||
case xorm.BigInt, xorm.BigSerial:
|
||||
return "__int64"
|
||||
case xorm.Char, xorm.Varchar, xorm.TinyText, xorm.Text, xorm.MediumText, xorm.LongText:
|
||||
return "tstring"
|
||||
case xorm.Date, xorm.DateTime, xorm.Time, xorm.TimeStamp:
|
||||
return "time_t"
|
||||
case xorm.Decimal, xorm.Numeric:
|
||||
return "tstring"
|
||||
case xorm.Real, xorm.Float:
|
||||
return "float"
|
||||
case xorm.Double:
|
||||
return "double"
|
||||
case xorm.TinyBlob, xorm.Blob, xorm.MediumBlob, xorm.LongBlob, xorm.Bytea:
|
||||
return "tstring"
|
||||
case xorm.Bool:
|
||||
return "bool"
|
||||
default:
|
||||
return "tstring"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func genCPlusImports(tables []*xorm.Table) map[string]string {
|
||||
imports := make(map[string]string)
|
||||
|
||||
for _, table := range tables {
|
||||
for _, col := range table.Columns {
|
||||
switch cPlusTypeStr(col) {
|
||||
case "time_t":
|
||||
imports[`<time.h>`] = `<time.h>`
|
||||
case "tstring":
|
||||
imports["<string>"] = "<string>"
|
||||
//case "__int64":
|
||||
// imports[""] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
return imports
|
||||
}
|
||||
78
xorm/cmd.go
78
xorm/cmd.go
|
|
@ -1,78 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A Command is an implementation of a go command
|
||||
// like go build or go fix.
|
||||
type Command struct {
|
||||
// Run runs the command.
|
||||
// The args are the arguments after the command name.
|
||||
Run func(cmd *Command, args []string)
|
||||
|
||||
// UsageLine is the one-line usage message.
|
||||
// The first word in the line is taken to be the command name.
|
||||
UsageLine string
|
||||
|
||||
// Short is the short description shown in the 'go help' output.
|
||||
Short string
|
||||
|
||||
// Long is the long message shown in the 'go help <this-command>' output.
|
||||
Long string
|
||||
|
||||
// Flag is a set of flags specific to this command.
|
||||
Flags map[string]bool
|
||||
}
|
||||
|
||||
// Name returns the command's name: the first word in the usage line.
|
||||
func (c *Command) Name() string {
|
||||
name := c.UsageLine
|
||||
i := strings.Index(name, " ")
|
||||
if i >= 0 {
|
||||
name = name[:i]
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func (c *Command) Usage() {
|
||||
fmt.Fprintf(os.Stderr, "usage: %s\n\n", c.UsageLine)
|
||||
fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSpace(c.Long))
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// Runnable reports whether the command can be run; otherwise
|
||||
// it is a documentation pseudo-command such as importpath.
|
||||
func (c *Command) Runnable() bool {
|
||||
return c.Run != nil
|
||||
}
|
||||
|
||||
// checkFlags checks if the flag exists with correct format.
|
||||
func checkFlags(flags map[string]bool, args []string, print func(string)) int {
|
||||
num := 0 // Number of valid flags, use to cut out.
|
||||
for i, f := range args {
|
||||
// Check flag prefix '-'.
|
||||
if !strings.HasPrefix(f, "-") {
|
||||
// Not a flag, finish check process.
|
||||
break
|
||||
}
|
||||
|
||||
// Check if it a valid flag.
|
||||
if v, ok := flags[f]; ok {
|
||||
flags[f] = !v
|
||||
if !v {
|
||||
print(f)
|
||||
} else {
|
||||
fmt.Println("DISABLE: " + f)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("[ERRO] Unknown flag: %s.\n", f)
|
||||
return -1
|
||||
}
|
||||
num = i + 1
|
||||
}
|
||||
|
||||
return num
|
||||
}
|
||||
263
xorm/go.go
263
xorm/go.go
|
|
@ -1,263 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-xorm/xorm"
|
||||
"go/format"
|
||||
"reflect"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var (
|
||||
GoLangTmpl LangTmpl = LangTmpl{
|
||||
template.FuncMap{"Mapper": mapper.Table2Obj,
|
||||
"Type": typestring,
|
||||
"Tag": tag,
|
||||
"UnTitle": unTitle,
|
||||
"gt": gt,
|
||||
"getCol": getCol,
|
||||
},
|
||||
formatGo,
|
||||
genGoImports,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
errBadComparisonType = errors.New("invalid type for comparison")
|
||||
errBadComparison = errors.New("incompatible types for comparison")
|
||||
errNoComparison = errors.New("missing argument for comparison")
|
||||
)
|
||||
|
||||
type kind int
|
||||
|
||||
const (
|
||||
invalidKind kind = iota
|
||||
boolKind
|
||||
complexKind
|
||||
intKind
|
||||
floatKind
|
||||
integerKind
|
||||
stringKind
|
||||
uintKind
|
||||
)
|
||||
|
||||
func basicKind(v reflect.Value) (kind, error) {
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
return boolKind, nil
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return intKind, nil
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return uintKind, nil
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return floatKind, nil
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return complexKind, nil
|
||||
case reflect.String:
|
||||
return stringKind, nil
|
||||
}
|
||||
return invalidKind, errBadComparisonType
|
||||
}
|
||||
|
||||
// eq evaluates the comparison a == b || a == c || ...
|
||||
func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) {
|
||||
v1 := reflect.ValueOf(arg1)
|
||||
k1, err := basicKind(v1)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(arg2) == 0 {
|
||||
return false, errNoComparison
|
||||
}
|
||||
for _, arg := range arg2 {
|
||||
v2 := reflect.ValueOf(arg)
|
||||
k2, err := basicKind(v2)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if k1 != k2 {
|
||||
return false, errBadComparison
|
||||
}
|
||||
truth := false
|
||||
switch k1 {
|
||||
case boolKind:
|
||||
truth = v1.Bool() == v2.Bool()
|
||||
case complexKind:
|
||||
truth = v1.Complex() == v2.Complex()
|
||||
case floatKind:
|
||||
truth = v1.Float() == v2.Float()
|
||||
case intKind:
|
||||
truth = v1.Int() == v2.Int()
|
||||
case stringKind:
|
||||
truth = v1.String() == v2.String()
|
||||
case uintKind:
|
||||
truth = v1.Uint() == v2.Uint()
|
||||
default:
|
||||
panic("invalid kind")
|
||||
}
|
||||
if truth {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// lt evaluates the comparison a < b.
|
||||
func lt(arg1, arg2 interface{}) (bool, error) {
|
||||
v1 := reflect.ValueOf(arg1)
|
||||
k1, err := basicKind(v1)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
v2 := reflect.ValueOf(arg2)
|
||||
k2, err := basicKind(v2)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if k1 != k2 {
|
||||
return false, errBadComparison
|
||||
}
|
||||
truth := false
|
||||
switch k1 {
|
||||
case boolKind, complexKind:
|
||||
return false, errBadComparisonType
|
||||
case floatKind:
|
||||
truth = v1.Float() < v2.Float()
|
||||
case intKind:
|
||||
truth = v1.Int() < v2.Int()
|
||||
case stringKind:
|
||||
truth = v1.String() < v2.String()
|
||||
case uintKind:
|
||||
truth = v1.Uint() < v2.Uint()
|
||||
default:
|
||||
panic("invalid kind")
|
||||
}
|
||||
return truth, nil
|
||||
}
|
||||
|
||||
// le evaluates the comparison <= b.
|
||||
func le(arg1, arg2 interface{}) (bool, error) {
|
||||
// <= is < or ==.
|
||||
lessThan, err := lt(arg1, arg2)
|
||||
if lessThan || err != nil {
|
||||
return lessThan, err
|
||||
}
|
||||
return eq(arg1, arg2)
|
||||
}
|
||||
|
||||
// gt evaluates the comparison a > b.
|
||||
func gt(arg1, arg2 interface{}) (bool, error) {
|
||||
// > is the inverse of <=.
|
||||
lessOrEqual, err := le(arg1, arg2)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return !lessOrEqual, nil
|
||||
}
|
||||
|
||||
func getCol(cols map[string]*xorm.Column, name string) *xorm.Column {
|
||||
return cols[name]
|
||||
}
|
||||
|
||||
func formatGo(src string) (string, error) {
|
||||
source, err := format.Source([]byte(src))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(source), nil
|
||||
}
|
||||
|
||||
func genGoImports(tables []*xorm.Table) map[string]string {
|
||||
imports := make(map[string]string)
|
||||
|
||||
for _, table := range tables {
|
||||
for _, col := range table.Columns {
|
||||
if typestring(col) == "time.Time" {
|
||||
imports["time"] = "time"
|
||||
}
|
||||
}
|
||||
}
|
||||
return imports
|
||||
}
|
||||
|
||||
func typestring(col *xorm.Column) string {
|
||||
st := col.SQLType
|
||||
/*if col.IsPrimaryKey {
|
||||
return "int64"
|
||||
}*/
|
||||
t := xorm.SQLType2Type(st)
|
||||
s := t.String()
|
||||
if s == "[]uint8" {
|
||||
return "[]byte"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func tag(table *xorm.Table, col *xorm.Column) string {
|
||||
isNameId := (mapper.Table2Obj(col.Name) == "Id")
|
||||
isIdPk := isNameId && typestring(col) == "int64"
|
||||
|
||||
res := make([]string, 0)
|
||||
if !col.Nullable {
|
||||
if !isIdPk {
|
||||
res = append(res, "not null")
|
||||
}
|
||||
}
|
||||
if col.IsPrimaryKey {
|
||||
if !isIdPk {
|
||||
res = append(res, "pk")
|
||||
}
|
||||
}
|
||||
if col.Default != "" {
|
||||
res = append(res, "default "+col.Default)
|
||||
}
|
||||
if col.IsAutoIncrement {
|
||||
if !isIdPk {
|
||||
res = append(res, "autoincr")
|
||||
}
|
||||
}
|
||||
if col.IsCreated {
|
||||
res = append(res, "created")
|
||||
}
|
||||
if col.IsUpdated {
|
||||
res = append(res, "updated")
|
||||
}
|
||||
for name, _ := range col.Indexes {
|
||||
index := table.Indexes[name]
|
||||
var uistr string
|
||||
if index.Type == xorm.UniqueType {
|
||||
uistr = "unique"
|
||||
} else if index.Type == xorm.IndexType {
|
||||
uistr = "index"
|
||||
}
|
||||
if len(index.Cols) > 1 {
|
||||
uistr += "(" + index.Name + ")"
|
||||
}
|
||||
res = append(res, uistr)
|
||||
}
|
||||
|
||||
nstr := col.SQLType.Name
|
||||
if col.Length != 0 {
|
||||
if col.Length2 != 0 {
|
||||
nstr += fmt.Sprintf("(%v,%v)", col.Length, col.Length2)
|
||||
} else {
|
||||
nstr += fmt.Sprintf("(%v)", col.Length)
|
||||
}
|
||||
}
|
||||
res = append(res, nstr)
|
||||
|
||||
var tags []string
|
||||
if genJson {
|
||||
tags = append(tags, "json:\""+col.Name+"\"")
|
||||
}
|
||||
if len(res) > 0 {
|
||||
tags = append(tags, "xorm:\""+strings.Join(res, " ")+"\"")
|
||||
}
|
||||
if len(tags) > 0 {
|
||||
return "`" + strings.Join(tags, " ") + "`"
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
51
xorm/lang.go
51
xorm/lang.go
|
|
@ -1,51 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/go-xorm/xorm"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
type LangTmpl struct {
|
||||
Funcs template.FuncMap
|
||||
Formater func(string) (string, error)
|
||||
GenImports func([]*xorm.Table) map[string]string
|
||||
}
|
||||
|
||||
var (
|
||||
mapper = &xorm.SnakeMapper{}
|
||||
langTmpls = map[string]LangTmpl{
|
||||
"go": GoLangTmpl,
|
||||
"c++": CPlusTmpl,
|
||||
}
|
||||
)
|
||||
|
||||
func loadConfig(f string) map[string]string {
|
||||
bts, err := ioutil.ReadFile(f)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
configs := make(map[string]string)
|
||||
lines := strings.Split(string(bts), "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimRight(line, "\r")
|
||||
vs := strings.Split(line, "=")
|
||||
if len(vs) == 2 {
|
||||
configs[strings.TrimSpace(vs[0])] = strings.TrimSpace(vs[1])
|
||||
}
|
||||
}
|
||||
return configs
|
||||
}
|
||||
|
||||
func unTitle(src string) string {
|
||||
if src == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if len(src) == 1 {
|
||||
return strings.ToLower(string(src[0]))
|
||||
} else {
|
||||
return strings.ToLower(string(src[0])) + src[1:]
|
||||
}
|
||||
}
|
||||
282
xorm/reverse.go
282
xorm/reverse.go
|
|
@ -1,282 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings" //[SWH|+]
|
||||
"text/template"
|
||||
|
||||
"github.com/dvirsky/go-pylog/logging"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/go-xorm/xorm"
|
||||
_ "github.com/lib/pq"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
_ "github.com/ziutek/mymysql/godrv"
|
||||
)
|
||||
|
||||
var CmdReverse = &Command{
|
||||
UsageLine: "reverse [-m] driverName datasourceName tmplPath [generatedPath]",
|
||||
Short: "reverse a db to codes",
|
||||
Long: `
|
||||
according database's tables and columns to generate codes for Go, C++ and etc.
|
||||
|
||||
-m Generated one go file for every table
|
||||
driverName Database driver name, now supported four: mysql mymysql sqlite3 postgres
|
||||
datasourceName Database connection uri, for detail infomation please visit driver's project page
|
||||
tmplPath Template dir for generated. the default templates dir has provide 1 template
|
||||
generatedPath This parameter is optional, if blank, the default value is model, then will
|
||||
generated all codes in model dir
|
||||
`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
CmdReverse.Run = runReverse
|
||||
CmdReverse.Flags = map[string]bool{
|
||||
"-s": false,
|
||||
"-l": false,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
genJson bool = false
|
||||
)
|
||||
|
||||
func printReversePrompt(flag string) {
|
||||
}
|
||||
|
||||
type Tmpl struct {
|
||||
Tables []*xorm.Table
|
||||
Imports map[string]string
|
||||
Model string
|
||||
}
|
||||
|
||||
func dirExists(dir string) bool {
|
||||
d, e := os.Stat(dir)
|
||||
switch {
|
||||
case e != nil:
|
||||
return false
|
||||
case !d.IsDir():
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func runReverse(cmd *Command, args []string) {
|
||||
num := checkFlags(cmd.Flags, args, printReversePrompt)
|
||||
if num == -1 {
|
||||
return
|
||||
}
|
||||
args = args[num:]
|
||||
|
||||
if len(args) < 3 {
|
||||
fmt.Println("params error, please see xorm help reverse")
|
||||
return
|
||||
}
|
||||
|
||||
var isMultiFile bool = true
|
||||
if use, ok := cmd.Flags["-s"]; ok {
|
||||
isMultiFile = !use
|
||||
}
|
||||
|
||||
curPath, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Println(curPath)
|
||||
return
|
||||
}
|
||||
|
||||
var genDir string
|
||||
var model string
|
||||
if len(args) == 4 {
|
||||
|
||||
genDir, err = filepath.Abs(args[3])
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
//[SWH|+] 经测试,path.Base不能解析windows下的“\”,需要替换为“/”
|
||||
genDir = strings.Replace(genDir, "\\", "/", -1)
|
||||
model = path.Base(genDir)
|
||||
} else {
|
||||
model = "model"
|
||||
genDir = path.Join(curPath, model)
|
||||
}
|
||||
|
||||
dir, err := filepath.Abs(args[2])
|
||||
if err != nil {
|
||||
logging.Error("%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !dirExists(dir) {
|
||||
logging.Error("Template %v path is not exist", dir)
|
||||
return
|
||||
}
|
||||
|
||||
var langTmpl LangTmpl
|
||||
var ok bool
|
||||
var lang string = "go"
|
||||
var prefix string = "" //[SWH|+]
|
||||
cfgPath := path.Join(dir, "config")
|
||||
info, err := os.Stat(cfgPath)
|
||||
var configs map[string]string
|
||||
if err == nil && !info.IsDir() {
|
||||
configs = loadConfig(cfgPath)
|
||||
if l, ok := configs["lang"]; ok {
|
||||
lang = l
|
||||
}
|
||||
if j, ok := configs["genJson"]; ok {
|
||||
genJson, err = strconv.ParseBool(j)
|
||||
}
|
||||
//[SWH|+]
|
||||
if j, ok := configs["prefix"]; ok {
|
||||
prefix = j
|
||||
}
|
||||
}
|
||||
|
||||
if langTmpl, ok = langTmpls[lang]; !ok {
|
||||
fmt.Println("Unsupported programing language", lang)
|
||||
return
|
||||
}
|
||||
|
||||
os.MkdirAll(genDir, os.ModePerm)
|
||||
|
||||
Orm, err := xorm.NewEngine(args[0], args[1])
|
||||
if err != nil {
|
||||
logging.Error("%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
tables, err := Orm.DBMetas()
|
||||
if err != nil {
|
||||
logging.Error("%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
filepath.Walk(dir, func(f string, info os.FileInfo, err error) error {
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if info.Name() == "config" {
|
||||
return nil
|
||||
}
|
||||
|
||||
bs, err := ioutil.ReadFile(f)
|
||||
if err != nil {
|
||||
logging.Error("%v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
t := template.New(f)
|
||||
t.Funcs(langTmpl.Funcs)
|
||||
|
||||
tmpl, err := t.Parse(string(bs))
|
||||
if err != nil {
|
||||
logging.Error("%v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
var w *os.File
|
||||
fileName := info.Name()
|
||||
newFileName := fileName[:len(fileName)-4]
|
||||
ext := path.Ext(newFileName)
|
||||
|
||||
if !isMultiFile {
|
||||
w, err = os.OpenFile(path.Join(genDir, newFileName), os.O_RDWR|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
logging.Error("%v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
imports := langTmpl.GenImports(tables)
|
||||
tbls := make([]*xorm.Table, 0)
|
||||
for _, table := range tables {
|
||||
//[SWH|+]
|
||||
if prefix != "" {
|
||||
table.Name = strings.TrimPrefix(table.Name, prefix)
|
||||
}
|
||||
tbls = append(tbls, table)
|
||||
}
|
||||
|
||||
newbytes := bytes.NewBufferString("")
|
||||
|
||||
t := &Tmpl{Tables: tbls, Imports: imports, Model: model}
|
||||
err = tmpl.Execute(newbytes, t)
|
||||
if err != nil {
|
||||
logging.Error("%v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
tplcontent, err := ioutil.ReadAll(newbytes)
|
||||
if err != nil {
|
||||
logging.Error("%v", err)
|
||||
return err
|
||||
}
|
||||
var source string
|
||||
if langTmpl.Formater != nil {
|
||||
source, err = langTmpl.Formater(string(tplcontent))
|
||||
if err != nil {
|
||||
logging.Error("%v", err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
source = string(tplcontent)
|
||||
}
|
||||
|
||||
w.WriteString(source)
|
||||
w.Close()
|
||||
} else {
|
||||
for _, table := range tables {
|
||||
//[SWH|+]
|
||||
if prefix != "" {
|
||||
table.Name = strings.TrimPrefix(table.Name, prefix)
|
||||
}
|
||||
// imports
|
||||
tbs := []*xorm.Table{table}
|
||||
imports := langTmpl.GenImports(tbs)
|
||||
w, err := os.OpenFile(path.Join(genDir, unTitle(mapper.Table2Obj(table.Name))+ext), os.O_RDWR|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
logging.Error("%v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
newbytes := bytes.NewBufferString("")
|
||||
|
||||
t := &Tmpl{Tables: tbs, Imports: imports, Model: model}
|
||||
err = tmpl.Execute(newbytes, t)
|
||||
if err != nil {
|
||||
logging.Error("%v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
tplcontent, err := ioutil.ReadAll(newbytes)
|
||||
if err != nil {
|
||||
logging.Error("%v", err)
|
||||
return err
|
||||
}
|
||||
var source string
|
||||
if langTmpl.Formater != nil {
|
||||
source, err = langTmpl.Formater(string(tplcontent))
|
||||
if err != nil {
|
||||
logging.Error("%v-%v", err, string(tplcontent))
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
source = string(tplcontent)
|
||||
}
|
||||
|
||||
w.WriteString(source)
|
||||
w.Close()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
147
xorm/shell.go
147
xorm/shell.go
|
|
@ -1,147 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/go-xorm/xorm"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var CmdShell = &Command{
|
||||
UsageLine: "shell driverName datasourceName",
|
||||
Short: "a general shell to operate all kinds of database",
|
||||
Long: `
|
||||
general database's shell for sqlite3, mysql, postgres.
|
||||
|
||||
driverName Database driver name, now supported four: mysql mymysql sqlite3 postgres
|
||||
datasourceName Database connection uri, for detail infomation please visit driver's project page
|
||||
`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
CmdShell.Run = runShell
|
||||
CmdShell.Flags = map[string]bool{}
|
||||
}
|
||||
|
||||
var engine *xorm.Engine
|
||||
|
||||
func shellHelp() {
|
||||
fmt.Println(`
|
||||
show tables show all tables
|
||||
columns <table_name> show table's column info
|
||||
indexes <table_name> show table's index info
|
||||
exit exit shell
|
||||
source <sql_file> exec sql file to current database
|
||||
dump [-nodata] <sql_file> dump structs or records to sql file
|
||||
help show this document
|
||||
<statement> SQL statement
|
||||
`)
|
||||
}
|
||||
|
||||
func runShell(cmd *Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
fmt.Println("params error, please see xorm help shell")
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
engine, err = xorm.NewEngine(args[0], args[1])
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = engine.Ping()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
var scmd string
|
||||
fmt.Print("xorm$ ")
|
||||
for {
|
||||
var input string
|
||||
_, err := fmt.Scan(&input)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
continue
|
||||
}
|
||||
if strings.ToLower(input) == "exit" {
|
||||
fmt.Println("bye")
|
||||
return
|
||||
}
|
||||
if !strings.HasSuffix(input, ";") {
|
||||
scmd = scmd + " " + input
|
||||
continue
|
||||
}
|
||||
scmd = scmd + " " + input
|
||||
lcmd := strings.TrimSpace(strings.ToLower(scmd))
|
||||
if strings.HasPrefix(lcmd, "select") {
|
||||
res, err := engine.Query(scmd + "\n")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
if len(res) <= 0 {
|
||||
fmt.Println("no records")
|
||||
} else {
|
||||
columns := make(map[string]int)
|
||||
for k, _ := range res[0] {
|
||||
columns[k] = len(k)
|
||||
}
|
||||
|
||||
for _, m := range res {
|
||||
for k, s := range m {
|
||||
l := len(string(s))
|
||||
if l > columns[k] {
|
||||
columns[k] = l
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var maxlen = 0
|
||||
for _, l := range columns {
|
||||
maxlen = maxlen + l + 3
|
||||
}
|
||||
maxlen = maxlen + 1
|
||||
|
||||
fmt.Println(strings.Repeat("-", maxlen))
|
||||
fmt.Print("|")
|
||||
slice := make([]string, 0)
|
||||
for k, l := range columns {
|
||||
fmt.Print(" " + k + " ")
|
||||
fmt.Print(strings.Repeat(" ", l-len(k)))
|
||||
fmt.Print("|")
|
||||
slice = append(slice, k)
|
||||
}
|
||||
fmt.Print("\n")
|
||||
for _, r := range res {
|
||||
fmt.Print("|")
|
||||
for _, k := range slice {
|
||||
fmt.Print(" " + string(r[k]) + " ")
|
||||
fmt.Print(strings.Repeat(" ", columns[k]-len(string(r[k]))))
|
||||
fmt.Print("|")
|
||||
}
|
||||
fmt.Print("\n")
|
||||
}
|
||||
fmt.Println(strings.Repeat("-", maxlen))
|
||||
//fmt.Println(res)
|
||||
}
|
||||
}
|
||||
} else if lcmd == "show tables;" {
|
||||
/*tables, err := engine.DBMetas()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
|
||||
}*/
|
||||
} else {
|
||||
cnt, err := engine.Exec(scmd)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
fmt.Printf("%d records changed.\n", cnt)
|
||||
}
|
||||
}
|
||||
scmd = ""
|
||||
fmt.Print("xorm$ ")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
{{ range .Imports}}
|
||||
#include {{.}}
|
||||
{{ end }}
|
||||
|
||||
{{range .Tables}}class {{Mapper .Name}} {
|
||||
{{$table := .}}
|
||||
public:
|
||||
{{range .Columns}}{{$name := Mapper .Name}} {{Type .}} Get{{Mapper .Name}}() {
|
||||
return this->m_{{UnTitle $name}};
|
||||
}
|
||||
|
||||
void Set{{$name}}({{Type .}} {{UnTitle $name}}) {
|
||||
this->m_{{UnTitle $name}} = {{UnTitle $name}};
|
||||
}
|
||||
|
||||
{{end}}private:
|
||||
{{range .Columns}}{{$name := Mapper .Name}} {{Type .}} m_{{UnTitle $name}};
|
||||
{{end}}
|
||||
}
|
||||
|
||||
{{end}}
|
||||
|
|
@ -1 +0,0 @@
|
|||
lang=c++
|
||||
|
|
@ -1 +0,0 @@
|
|||
lang=go
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package {{.Model}}
|
||||
|
||||
import (
|
||||
{{range .Imports}}"{{.}}"{{end}}
|
||||
)
|
||||
|
||||
{{range .Tables}}
|
||||
type {{Mapper .Name}} struct {
|
||||
{{$table := .}}
|
||||
{{range .Columns}} {{Mapper .Name}} {{Type .}}
|
||||
{{end}}
|
||||
}
|
||||
|
||||
{{end}}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
lang=go
|
||||
genJson=0
|
||||
prefix=cos_
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
package {{.Model}}
|
||||
|
||||
{{$ilen := len .Imports}}
|
||||
{{if gt $ilen 0}}
|
||||
import (
|
||||
{{range .Imports}}"{{.}}"{{end}}
|
||||
)
|
||||
{{end}}
|
||||
|
||||
{{range .Tables}}
|
||||
type {{Mapper .Name}} struct {
|
||||
{{$table := .}}
|
||||
{{$columns := .Columns}}
|
||||
{{range .ColumnsSeq}}{{$col := getCol $columns .}} {{Mapper $col.Name}} {{Type $col}} {{Tag $table $col}}
|
||||
{{end}}
|
||||
}
|
||||
|
||||
{{end}}
|
||||
162
xorm/xorm.go
162
xorm/xorm.go
|
|
@ -1,162 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dvirsky/go-pylog/logging"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// +build go1.1
|
||||
|
||||
// Test that go1.1 tag above is included in builds. main.go refers to this definition.
|
||||
const go11tag = true
|
||||
|
||||
const version = "0.1"
|
||||
|
||||
// Commands lists the available commands and help topics.
|
||||
// The order here is the order in which they are printed by 'gopm help'.
|
||||
var commands = []*Command{
|
||||
CmdReverse,
|
||||
CmdShell,
|
||||
}
|
||||
|
||||
func init() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
}
|
||||
|
||||
func main() {
|
||||
logging.SetLevel(logging.ALL)
|
||||
// Check length of arguments.
|
||||
args := os.Args[1:]
|
||||
if len(args) < 1 {
|
||||
usage()
|
||||
return
|
||||
}
|
||||
|
||||
// Show help documentation.
|
||||
if args[0] == "help" {
|
||||
help(args[1:])
|
||||
return
|
||||
}
|
||||
|
||||
// Check commands and run.
|
||||
for _, comm := range commands {
|
||||
if comm.Name() == args[0] && comm.Run != nil {
|
||||
comm.Run(comm, args[1:])
|
||||
exit()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "xorm: unknown subcommand %q\nRun 'xorm help' for usage.\n", args[0])
|
||||
setExitStatus(2)
|
||||
exit()
|
||||
}
|
||||
|
||||
var exitStatus = 0
|
||||
var exitMu sync.Mutex
|
||||
|
||||
func setExitStatus(n int) {
|
||||
exitMu.Lock()
|
||||
if exitStatus < n {
|
||||
exitStatus = n
|
||||
}
|
||||
exitMu.Unlock()
|
||||
}
|
||||
|
||||
var usageTemplate = `xorm is a database tool based xorm package.
|
||||
Usage:
|
||||
|
||||
xorm command [arguments]
|
||||
|
||||
The commands are:
|
||||
{{range .}}{{if .Runnable}}
|
||||
{{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}
|
||||
|
||||
Use "xorm help [command]" for more information about a command.
|
||||
|
||||
Additional help topics:
|
||||
{{range .}}{{if not .Runnable}}
|
||||
{{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}
|
||||
|
||||
Use "xorm help [topic]" for more information about that topic.
|
||||
|
||||
`
|
||||
|
||||
var helpTemplate = `{{if .Runnable}}usage: xorm {{.UsageLine}}
|
||||
|
||||
{{end}}{{.Long | trim}}
|
||||
`
|
||||
|
||||
// tmpl executes the given template text on data, writing the result to w.
|
||||
func tmpl(w io.Writer, text string, data interface{}) {
|
||||
t := template.New("top")
|
||||
t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize})
|
||||
template.Must(t.Parse(text))
|
||||
if err := t.Execute(w, data); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func capitalize(s string) string {
|
||||
if s == "" {
|
||||
return s
|
||||
}
|
||||
r, n := utf8.DecodeRuneInString(s)
|
||||
return string(unicode.ToTitle(r)) + s[n:]
|
||||
}
|
||||
|
||||
func printUsage(w io.Writer) {
|
||||
tmpl(w, usageTemplate, commands)
|
||||
}
|
||||
|
||||
func usage() {
|
||||
printUsage(os.Stderr)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// help implements the 'help' command.
|
||||
func help(args []string) {
|
||||
if len(args) == 0 {
|
||||
printUsage(os.Stdout)
|
||||
// not exit 2: succeeded at 'gopm help'.
|
||||
return
|
||||
}
|
||||
if len(args) != 1 {
|
||||
fmt.Fprintf(os.Stderr, "usage: xorm help command\n\nToo many arguments given.\n")
|
||||
os.Exit(2) // failed at 'gopm help'
|
||||
}
|
||||
|
||||
arg := args[0]
|
||||
|
||||
for _, cmd := range commands {
|
||||
if cmd.Name() == arg {
|
||||
tmpl(os.Stdout, helpTemplate, cmd)
|
||||
// not exit 2: succeeded at 'gopm help cmd'.
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Unknown help topic %#q. Run 'xorm help'.\n", arg)
|
||||
os.Exit(2) // failed at 'gopm help cmd'
|
||||
}
|
||||
|
||||
var atexitFuncs []func()
|
||||
|
||||
func atexit(f func()) {
|
||||
atexitFuncs = append(atexitFuncs, f)
|
||||
}
|
||||
|
||||
func exit() {
|
||||
for _, f := range atexitFuncs {
|
||||
f()
|
||||
}
|
||||
os.Exit(exitStatus)
|
||||
}
|
||||
Loading…
Reference in New Issue