resolved merge from origin/dev branch

This commit is contained in:
Nash Tsai 2014-04-17 21:28:26 +08:00
commit 52f7a96bfe
77 changed files with 6541 additions and 13663 deletions

View File

@ -1,2 +1,2 @@
[target] [target]
path = github.com/lunny/xorm path = github.com/go-xorm/xorm

View File

@ -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 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 request with just the failing test case (you'll probably want to
pattern it after the tests in pattern it after the tests in
[base_test.go](https://github.com/lunny/xorm/blob/master/base_test.go) AND [base_test.go](https://github.com/go-xorm/xorm/blob/master/base_test.go) AND
[benchmark_base_test.go](https://github.com/lunny/xorm/blob/master/benchmark_base_test.go). [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. 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 ### New functionality

View File

@ -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. Xorm is a simple and powerful ORM for Go.
[![Build Status](https://drone.io/github.com/go-xorm/xorm/status.png)](https://drone.io/github.com/go-xorm/xorm/latest) [![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/go-xorm/xorm) [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/lunny/go-xorm/trend.png)](https://bitdeli.com/free "Bitdeli Badge") [![Build Status](https://drone.io/github.com/go-xorm/xorm/status.png)](https://drone.io/github.com/go-xorm/xorm/latest) [![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/go-xorm/xorm) [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/lunny/xorm/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
# Features # Features

View File

@ -1,10 +1,10 @@
# xorm # 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库. 通过它可以使数据库操作非常简便。 xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作非常简便。
[![Build Status](https://drone.io/github.com/lunny/xorm/status.png)](https://drone.io/github.com/lunny/xorm/latest) [![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/lunny/xorm) [![Build Status](https://drone.io/github.com/go-xorm/xorm/status.png)](https://drone.io/github.com/go-xorm/xorm/latest) [![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/go-xorm/xorm)
## 特性 ## 特性
@ -69,25 +69,25 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作
* 查询函数 Get()/Find()/Iterate() 在性能上的改进 * 查询函数 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](https://github.com/gpmgo/gopm) 进行安装:
gopm get github.com/lunny/xorm gopm get github.com/go-xorm/xorm
或者您也可以使用go工具进行安装 或者您也可以使用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 +115,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) * [Lunny](https://github.com/lunny)
* [Nashtsai](https://github.com/nashtsai) * [Nashtsai](https://github.com/nashtsai)

View File

@ -1 +1 @@
xorm v0.3.2 xorm v0.4.0

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -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
View File

@ -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 // Use of this source code is governed by a BSD
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -9,7 +9,7 @@ Installation
Make sure you have installed Go 1.1+ and then: 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 Create Engine
@ -137,6 +137,6 @@ The above 7 methods could use with condition methods.
engine.Join("LEFT", "userdetail", "user.id=userdetail.id").Find() engine.Join("LEFT", "userdetail", "user.id=userdetail.id").Find()
//SELECT * FROM user LEFT JOIN userdetail ON user.id=userdetail.id //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 package xorm

View File

@ -29,7 +29,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.3** : Improved documents; Optimistic Locking support; Timestamp with time zone support; Mapper change to tableMapper and columnMapper & added PrefixMapper & SuffixMapper support custom table or column name's prefix and suffix;Insert now return affected, err instead of id, err; Added UseBool & Distinct;
* **v0.2.2** : Postgres drivers now support lib/pq; Added method Iterate for record by record to handlerAdded SetMaxConns(go1.2+) support; some bugs fixed. * **v0.2.2** : Postgres drivers now support lib/pq; Added method Iterate for record by record to handlerAdded SetMaxConns(go1.2+) support; some bugs fixed.
* **v0.2.1** : Added database reverse tool, now support generate go & c++ codes, see [Xorm Tool README](https://github.com/lunny/xorm/blob/master/xorm/README.md); some bug fixed. * **v0.2.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.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.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). * **v0.1.8** : Added union index and union unique supported, please see [Mapping Rules](#mapping).
@ -40,4 +40,4 @@
* **v0.1.3** : Find function now supports both slice and map; Add Table function for multi tables and temperory tables support * **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.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.1** : Add Id, In functions and improved README
* **v0.1.0** : Inital release. * **v0.1.0** : Initial release.

View File

@ -28,10 +28,10 @@
* **v0.2.3** : 改善了文档提供了乐观锁支持添加了带时区时间字段支持Mapper现在分成表名Mapper和字段名Mapper同时实现了表或字段的自定义前缀后缀Insert方法的返回值含义从id, err更改为 affected, err请大家注意添加了UseBool 和 Distinct函数。 * **v0.2.3** : 改善了文档提供了乐观锁支持添加了带时区时间字段支持Mapper现在分成表名Mapper和字段名Mapper同时实现了表或字段的自定义前缀后缀Insert方法的返回值含义从id, err更改为 affected, err请大家注意添加了UseBool 和 Distinct函数。
* **v0.2.2** : Postgres驱动新增了对lib/pq的支持新增了逐条遍历方法Iterate新增了SetMaxConns(go1.2+)支持修复了bug若干 * **v0.2.2** : Postgres驱动新增了对lib/pq的支持新增了逐条遍历方法Iterate新增了SetMaxConns(go1.2+)支持修复了bug若干
* **v0.2.1** : 新增数据库反转工具当前支持go和c++代码的生成,详见 [Xorm Tool README](https://github.com/lunny/xorm/blob/master/xorm/README.md); 修复了一些bug. * **v0.2.1** : 新增数据库反转工具当前支持go和c++代码的生成,详见 [Xorm Tool README](https://github.com/go-xorm/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.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/lunny/xorm/blob/master/docs/QuickStart.md#21)删除废弃的MakeSession和Create函数。 * **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/lunny/xorm/blob/master/docs/QuickStart.md#21)。 * **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.7** : 新增IConnectPool接口以及NoneConnectPool, SysConnectPool, SimpleConnectPool三种实现可以选择不使用连接池使用系统连接池和使用自带连接池三种实现默认为SysConnectPool即系统自带的连接池。同时支持自定义连接池。Engine新增Close方法在系统退出时应调用此方法。
* **v0.1.6** : 新增Conversion支持自定义类型到数据库类型的转换新增查询结构体自动检测匿名成员支持新增单向映射支持 * **v0.1.6** : 新增Conversion支持自定义类型到数据库类型的转换新增查询结构体自动检测匿名成员支持新增单向映射支持
* **v0.1.5** : 新增对多线程的支持新增Sql()函数支持任意sql语句的struct查询Get函数返回值变动MakeSession和Create函数被NewSession和NewEngine函数替代 * **v0.1.5** : 新增对多线程的支持新增Sql()函数支持任意sql语句的struct查询Get函数返回值变动MakeSession和Create函数被NewSession和NewEngine函数替代
@ -40,5 +40,3 @@
* **v0.1.2** : Insert函数支持混合struct和slice指针传入并根据数据库类型自动批量插入同时自动添加事务 * **v0.1.2** : Insert函数支持混合struct和slice指针传入并根据数据库类型自动批量插入同时自动添加事务
* **v0.1.1** : 添加 Id, In 函数,改善 README 文档 * **v0.1.1** : 添加 Id, In 函数,改善 README 文档
* **v0.1.0** : 初始化工程 * **v0.1.0** : 初始化工程

View File

@ -1,51 +1,47 @@
xorm 快速入门 Quick Start
===== =====
* [1.创建Orm引擎](#10) * [1.Create ORM Engine](#10)
* [2.定义表结构体](#20) * [2.Define a struct](#20)
* [2.1.名称映射规则](#21) * [2.1.Name mapping rule](#21)
* [2.2.前缀映射,后缀映射和缓存映射](#22) * [2.2.Use Table or Tag to change table or column name](#22)
* [2.3.使用Table和Tag改变名称映射](#23) * [2.3.Column define](#23)
* [2.4.Column属性定义](#24) * [3. database schema operation](#30)
* [2.5.Go与字段类型对应表](#25) * [3.1.Retrieve database schema infomation](#31)
* [3.表结构操作](#30) * [3.2.Table Operation](#32)
* [3.1 获取数据库信息](#31) * [3.3.Create indexes and uniques](#33)
* [3.2 表操作](#32) * [3.4.Sync database schema](#34)
* [3.3 创建索引和唯一索引](#33) * [4.Insert records](#40)
* [3.4 同步数据库结构](#34) * [5.Query and Count records](#60)
* [4.插入数据](#50) * [5.1.Query condition methods](#61)
* [5.查询和统计数据](#60) * [5.2.Temporory methods](#62)
* [5.1.查询条件方法](#61) * [5.3.Get](#63)
* [5.2.临时开关方法](#62) * [5.4.Find](#64)
* [5.3.Get方法](#63) * [5.5.Iterate](#65)
* [5.4.Find方法](#64) * [5.6.Count](#66)
* [5.5.Iterate方法](#65) * [6.Update records](#70)
* [5.6.Count方法](#66) * [6.1.Optimistic Locking](#71)
* [5.7.Rows方法](#67) * [7.Delete records](#80)
* [6.更新数据](#70) * [8.Execute SQL command](#90)
* [6.1.乐观锁](#71) * [9.Execute SQL query](#100)
* [7.删除数据](#80) * [10.Transaction](#110)
* [8.执行SQL查询](#90) * [11.Cache](#120)
* [9.执行SQL命令](#100) * [12.Xorm Tool](#130)
* [10.事务处理](#110) * [12.1.Reverse command](#131)
* [11.缓存](#120) * [13.Examples](#140)
* [12.事件](#125) * [14.Cases](#150)
* [13.xorm工具](#130) * [15.FAQ](#160)
* [13.1.反转命令](#131) * [16.Discuss](#170)
* [14.Examples](#140)
* [15.案例](#150)
* [16.那些年我们踩过的坑](#160)
* [17.讨论](#170)
<a name="10" id="10"></a> <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 ```Go
import ( import (
_ "github.com/go-sql-driver/mysql" _ "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") engine, err := xorm.NewEngine("mysql", "root:123@/test?charset=utf8")
defer engine.Close() defer engine.Close()
@ -56,15 +52,15 @@ or
```Go ```Go
import ( import (
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
"github.com/lunny/xorm" "github.com/go-xorm/xorm"
) )
engine, err = xorm.NewEngine("sqlite3", "./test.db") engine, err = xorm.NewEngine("sqlite3", "./test.db")
defer engine.Close() 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) * Mysql: [github.com/Go-SQL-Driver/MySQL](https://github.com/Go-SQL-Driver/MySQL)
@ -74,20 +70,18 @@ xorm当前支持五种驱动四个数据库如下
* Postgres: [github.com/lib/pq](https://github.com/lib/pq) * Postgres: [github.com/lib/pq](https://github.com/lib/pq)
* MsSql: [github.com/lunny/godbc](https://githubcom/lunny/godbc) NewEngine's parameters are the same as `sql.Open`. So you should read the drivers' document for parameters' usage.
NewEngine传入的参数和`sql.Open`传入的参数完全相同,因此,使用哪个驱动前,请查看此驱动中关于传入参数的说明文档。 After engine created, you can do some settings.
在engine创建完成后可以进行一些设置 1.Logs
1.错误显示设置,默认如下均为`false` * `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;
* `engine.ShowSQL = true`则会在控制台打印出生成的SQL语句 2.If want to record infomation with another method: use `engine.Logger` as `io.Writer`:
* `engine.ShowDebug = true`,则会在控制台打印调试信息;
* `engine.ShowError = true`,则会在控制台打印错误信息;
* `engine.ShowWarn = true`,则会在控制台打印警告信息;
2.如果希望用其它方式记录,则可以`engine.Logger`赋值为一个`io.Writer`的实现。比如记录到Log文件则可以
```Go ```Go
f, err := os.Create("sql.log") f, err := os.Create("sql.log")
@ -98,22 +92,22 @@ f, err := os.Create("sql.log")
engine.Logger = f engine.Logger = f
``` ```
3.engine内部支持连接池接口默认使用的Go所实现的连接池同时自带了另外两种实现一种是不使用连接池另一种为一个自实现的连接池。推荐使用Go所实现的连接池。如果要使用自己实现的连接池可以实现`xorm.IConnectPool`并通过`engine.SetPool`进行设置。推荐使用Go默认的连接池。 3.Engine provide DB connection pool settings.
* 如果需要设置连接池的空闲数大小,可以使用`engine.SetIdleConns()`来实现。 * Use `engine.SetIdleConns()` to set idle connections.
* 如果需要设置最大打开连接数,则可以使用`engine.SetMaxConns()`来实现。 * Use `engine.SetMaxConns()` to set Max connections. This methods support only Go 1.2+.
<a name="20" id="20"></a> <a name="20" id="20"></a>
## 2.定义表结构体 ## 2.Define struct
xorm支持将一个struct映射为数据库中对应的一张表。映射规则如下 xorm map a struct to a database table, the rule is below.
<a name="21" id="21"></a> <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 ```Go
engine.SetMapper(SameMapper{}) engine.SetMapper(SameMapper{})
@ -130,20 +124,20 @@ engine.SetColumnMapper(SnakeMapper{})
``` ```
<a name="22" id="22"></a> <a name="22" id="22"></a>
### 2.2.前缀映射,后缀映射和缓存映射 ### 2.2.前缀映射规则,后缀映射规则和缓存映射规则
* 通过`engine.NewPrefixMapper(SnakeMapper{}, "prefix")`可以在SnakeMapper的基础上在命名中添加统一的前缀当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。 * 通过`engine.NewPrefixMapper(SnakeMapper{}, "prefix")`可以在SnakeMapper的基础上在命名中添加统一的前缀当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。
* 通过`engine.NewSufffixMapper(SnakeMapper{}, "suffix")`可以在SnakeMapper的基础上在命名中添加统一的后缀当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。 * 通过`engine.NewSufffixMapper(SnakeMapper{}, "suffix")`可以在SnakeMapper的基础上在命名中添加统一的后缀当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。
* 通过`eneing.NewCacheMapper(SnakeMapper{})`可以组合其它的映射规则,起到在内存中缓存曾经映射过的命名映射。 * 通过`eneing.NewCacheMapper(SnakeMapper{})`可以起到在内存中缓存曾经映射过的命名映射。
<a name="23" id="23"></a> 当然如果你使用了别的命名规则映射方案也可以自己实现一个IMapper。
<a name="22" id="22"></a>
### 2.3.使用Table和Tag改变名称映射 ### 2.3.使用Table和Tag改变名称映射
如果所有的命名都是按照IMapper的映射来操作的那当然是最理想的。但是如果碰到某个表名或者某个字段名跟映射规则不匹配时我们就需要别的机制来改变。 如果所有的命名都是按照IMapper的映射来操作的那当然是最理想的。但是如果碰到某个表名或者某个字段名跟映射规则不匹配时我们就需要别的机制来改变。
* 如果struct拥有`Tablename() string`的成员方法那么此方法的返回值即是该struct默认对应的数据库表名。 通过`engine.Table()`方法可以改变struct对应的数据库表的名称通过sturct中field对应的Tag中使用`xorm:"'table_name'"`可以使该field对应的Column名称为指定名称。这里使用两个单引号将Column名称括起来是为了防止名称冲突因为我们在Tag中还可以对这个Column进行更多的定义。如果名称不冲突的情况单引号也可以不使用。
* 通过`engine.Table()`方法可以改变struct对应的数据库表的名称通过sturct中field对应的Tag中使用`xorm:"'column_name'"`可以使该field对应的Column名称为指定名称。这里使用两个单引号将Column名称括起来是为了防止名称冲突因为我们在Tag中还可以对这个Column进行更多的定义。如果名称不冲突的情况单引号也可以不使用。
<a name="23" id="23"></a> <a name="23" id="23"></a>
### 2.4.Column属性定义 ### 2.4.Column属性定义
@ -156,25 +150,25 @@ type User struct {
} }
``` ```
对于不同的数据库系统数据类型其实是有些差异的。因此xorm中对数据类型有自己的定义基本的原则是尽量兼容各种数据库的字段类型具体的字段对应关系可以查看[字段类型对应表](https://github.com/lunny/xorm/blob/master/docs/COLUMNTYPE.md)。对于使用者,一般只要使用自己熟悉的数据库字段定义即可 对于不同的数据库系统数据类型其实是有些差异的。因此xorm中对数据类型有自己的定义基本的原则是尽量兼容各种数据库的字段类型具体的字段对应关系可以查看[字段类型对应表](https://github.com/go-xorm/xorm/blob/master/docs/COLUMNTYPE.md)
具体的映射规则如下另Tag中的关键字均不区分大小写字段名区分大小写 具体的映射规则如下另Tag中的关键字均不区分大小写字段名区分大小写
<table> <table>
<tr> <tr>
<td>name</td><td>当前field对应的字段的名称可选如不写则自动根据field名字和转换规则命名,如与其它关键字冲突,请使用单引号括起来。</td> <td>name</td><td>当前field对应的字段的名称可选如不写则自动根据field名字和转换规则命名</td>
</tr> </tr>
<tr> <tr>
<td>pk</td><td>是否是Primary Key如果在一个struct中有多个字段都使用了此标记则这多个字段构成了复合主键单主键当前支持int32,int,int64,uint32,uint,uint64,string这7种Go的数据类型复合主键支持这7种Go的数据类型的组合。</td> <td>pk</td><td>是否是Primary Key当前支持int64类型</td>
</tr> </tr>
<tr> <tr>
<td>当前支持30多种字段类型详情参见 [字段类型](https://github.com/lunny/xorm/blob/master/docs/COLUMNTYPE.md)</td><td>字段类型</td> <td>当前支持30多种字段类型详情参见 [字段类型](https://github.com/go-xorm/xorm/blob/master/docs/COLUMNTYPE.md)</td><td>字段类型</td>
</tr> </tr>
<tr> <tr>
<td>autoincr</td><td>是否是自增</td> <td>autoincr</td><td>是否是自增</td>
</tr> </tr>
<tr> <tr>
<td>[not ]null 或 notnull</td><td>是否可以为空</td> <td>[not ]null</td><td>是否可以为空</td>
</tr> </tr>
<tr> <tr>
<td>unique或unique(uniquename)</td><td>是否是唯一如不加括号则该字段不允许重复如加上括号则括号中为联合唯一索引的名字此时如果有另外一个或多个字段和本unique的uniquename相同则这些uniquename相同的字段组成联合唯一索引</td> <td>unique或unique(uniquename)</td><td>是否是唯一如不加括号则该字段不允许重复如加上括号则括号中为联合唯一索引的名字此时如果有另外一个或多个字段和本unique的uniquename相同则这些uniquename相同的字段组成联合唯一索引</td>
@ -195,13 +189,13 @@ type User struct {
<td>&lt;-</td><td>这个Field将只从数据库读取而不写入到数据库</td> <td>&lt;-</td><td>这个Field将只从数据库读取而不写入到数据库</td>
</tr> </tr>
<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>
<tr> <tr>
<td>updated</td><td>这个Field将在Insert或Update时自动赋值为当前时间</td> <td>updated</td><td>This field will be filled in current time on insert or update</td>
</tr> </tr>
<tr> <tr>
<td>version</td><td>这个Field将会在insert时默认为1每次更新自动加1</td> <td>version</td><td>This field will be filled 1 on insert and autoincrement on update</td>
</tr> </tr>
<tr> <tr>
<td>default 0</td><td>设置默认值紧跟的内容如果是Varchar等需要加上单引号</td> <td>default 0</td><td>设置默认值紧跟的内容如果是Varchar等需要加上单引号</td>
@ -210,11 +204,11 @@ type User struct {
另外有如下几条自动映射的规则: 另外有如下几条自动映射的规则:
- 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中自定义 - 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接口的类型或者结构体将根据接口的转换方式在类型和数据库记录之间进行相互转换。 - 4.实现了Conversion接口的类型或者结构体将根据接口的转换方式在类型和数据库记录之间进行相互转换。
```Go ```Go
@ -224,13 +218,6 @@ 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> <a name="30" id="30"></a>
## 3.表结构操作 ## 3.表结构操作
@ -240,50 +227,41 @@ xorm提供了一些动态获取和修改表结构的方法。对于一般的应
## 3.1 获取数据库信息 ## 3.1 获取数据库信息
* DBMetas() * DBMetas()
xorm支持获取表结构信息通过调用`engine.DBMetas()`可以获取到所有的表的信息
xorm支持获取表结构信息通过调用`engine.DBMetas()`可以获取到所有的表,字段,索引的信息。
<a name="31" id="31"></a> <a name="31" id="31"></a>
## 3.2.表操作 ## 3.2.表操作
* CreateTables() * CreateTables()
创建表使用`engine.CreateTables()`参数为一个或多个空的对应Struct的指针。同时可用的方法有Charset()和StoreEngine()如果对应的数据库支持这两个方法可以在创建表时指定表的字符编码和使用的引擎。当前仅支持Mysql数据库。 创建表使用`engine.CreateTables()`参数为一个或多个空的对应Struct的指针。同时可用的方法有Charset()和StoreEngine()如果对应的数据库支持这两个方法可以在创建表时指定表的字符编码和使用的引擎。当前仅支持Mysql数据库。
* IsTableEmpty() * IsTableEmpty()
判断表是否为空参数和CreateTables相同 判断表是否为空参数和CreateTables相同
* IsTableExist() * IsTableExist()
判断表是否存在 判断表是否存在
* DropTables() * DropTables()
删除表使用`engine.DropTables()`参数为一个或多个空的对应Struct的指针或者表的名字。如果为string传入则只删除对应的表如果传入的为Struct则删除表的同时还会删除对应的索引。 删除表使用`engine.DropTables()`参数为一个或多个空的对应Struct的指针或者表的名字。如果为string传入则只删除对应的表如果传入的为Struct则删除表的同时还会删除对应的索引。
<a name="32" id="32"></a> <a name="32" id="32"></a>
## 3.3.创建索引和唯一索引 ## 3.3.创建索引和唯一索引
* CreateIndexes * CreateIndexes
根据struct中的tag来创建索引 根据struct中的tag来创建索引
* CreateUniques * CreateUniques
根据struct中的tag来创建唯一索引 根据struct中的tag来创建唯一索引
<a name="34" id="34"></a> <a name="34" id="34"></a>
## 3.4.同步数据库结构 ## 3.4.同步数据库结构
同步能够部分智能的根据结构体的变动检测表结构的变动,并自动同步。目前能够实现: 同步能够部分智能的根据结构体的变动检测表结构的变动,并自动同步。目前能够实现:
1) 自动检测和创建表,这个检测是根据表的名字
* 1) 自动检测和创建表,这个检测是根据表的名字 2自动检测和新增表中的字段这个检测是根据字段名
* 2自动检测和新增表中的字段这个检测是根据字段名 3自动检测和创建索引和唯一索引这个检测是根据一个或多个字段名而不根据索引名称
* 3自动检测和创建索引和唯一索引这个检测是根据一个或多个字段名而不根据索引名称
调用方法如下: 调用方法如下:
```Go ```Go
err := engine.Sync(new(User)) err := engine.Sync(new(User))
``` ```
@ -291,25 +269,21 @@ err := engine.Sync(new(User))
<a name="50" id="50"></a> <a name="50" id="50"></a>
## 4.插入数据 ## 4.插入数据
插入数据使用Insert方法Insert方法的参数可以是一个或多个Struct的指针一个或多个Struct的Slice的指针。 Inserting records use Insert method.
如果传入的是Slice并且当数据库支持批量插入时Insert会使用批量插入的方式进行插入。
* 插入一条数据
* Insert one record
```Go ```Go
user := new(User) user := new(User)
user.Name = "myname" user.Name = "myname"
affected, err := engine.Insert(user) affected, err := engine.Insert(user)
``` ```
在插入单条数据成功后如果该结构体有自增字段则自增字段会被自动赋值为数据库中的id After inseted, `user.Id` will be filled with primary key column value.
```Go ```Go
fmt.Println(user.Id) fmt.Println(user.Id)
``` ```
* 插入同一个表的多条数据 * Insert multiple records by Slice on one table
```Go ```Go
users := make([]User, 0) users := make([]User, 0)
users[0].Name = "name0" users[0].Name = "name0"
@ -317,8 +291,7 @@ users[0].Name = "name0"
affected, err := engine.Insert(&users) affected, err := engine.Insert(&users)
``` ```
* 使用指针Slice插入多条记录 * Insert multiple records by Slice of pointer on one table
```Go ```Go
users := make([]*User, 0) users := make([]*User, 0)
users[0] = new(User) users[0] = new(User)
@ -327,8 +300,7 @@ users[0].Name = "name0"
affected, err := engine.Insert(&users) affected, err := engine.Insert(&users)
``` ```
* 插入不同表的一条记录 * Insert one record on two table.
```Go ```Go
user := new(User) user := new(User)
user.Name = "myname" user.Name = "myname"
@ -337,8 +309,7 @@ question.Content = "whywhywhwy?"
affected, err := engine.Insert(user, question) affected, err := engine.Insert(user, question)
``` ```
* 插入不同表的多条记录 * Insert multiple records on multiple tables.
```Go ```Go
users := make([]User, 0) users := make([]User, 0)
users[0].Name = "name0" users[0].Name = "name0"
@ -348,7 +319,7 @@ questions[0].Content = "whywhywhwy?"
affected, err := engine.Insert(&users, &questions) affected, err := engine.Insert(&users, &questions)
``` ```
* 插入不同表的一条或多条记录 * Insert one or multple records on multiple tables.
```Go ```Go
user := new(User) user := new(User)
user.Name = "myname" user.Name = "myname"
@ -358,27 +329,23 @@ questions[0].Content = "whywhywhwy?"
affected, err := engine.Insert(user, &questions) affected, err := engine.Insert(user, &questions)
``` ```
这里需要注意以下几点: Notice: If you want to use transaction on inserting, you should use session.Begin() before calling Insert.
* 这里虽然支持同时插入,但这些插入并没有事务关系。因此有可能在中间插入出错后,后面的插入将不会继续。
* 多条插入会自动生成`Insert into table values (),(),()`的语句因此这样的语句有一个最大的记录数根据经验测算在150条左右。大于150条后生成的sql语句将太长可能导致执行失败。因此在插入大量数据时目前需要自行分割成每150条插入一次。
<a name="60" id="60"></a> <a name="60" id="60"></a>
## 5.查询和统计数据 ## 5.Query and count
所有的查询条件不区分调用顺序但必须在调用GetFindCount, Iterate, Rows这几个函数之前调用。同时需要注意的一点是,在调用的参数中,如果采用默认的`SnakeMapper`所有的字符字段名均为映射后的数据库的字段名而不是field的名字。 所有的查询条件不区分调用顺序但必须在调用GetFindCount这三个函数之前调用。同时需要注意的一点是在调用的参数中所有的字符字段名均为映射后的数据库的字段名而不是field的名字。
<a name="61" id="61"></a> <a name="61" id="61"></a>
### 5.1.查询条件方法 ### 5.1.查询条件方法
查询和统计主要使用`Get`, `Find`, `Count`, `Rows`, `Iterate`这几个方法。在进行查询时可以使用多个方法来形成查询条件,条件函数如下: 查询和统计主要使用`Get`, `Find`, `Count`个方法。在进行查询时可以使用多个方法来形成查询条件,条件函数如下:
* Id(interface{}) * Id(int64)
传入一个PK字段的值作为查询条件如果是复合主键 传入一个PK字段的值作为查询条件
`Id(xorm.PK{1, 2})`
传入的两个参数按照struct中pk标记字段出现的顺序赋值。
* Where(string, …interface{}) * Where(string, …interface{})
SQL中Where语句中的条件基本相同作为条件 和Where语句中的条件基本相同作为条件
* And(string, …interface{}) * And(string, …interface{})
和Where函数中的条件基本相同作为条件 和Where函数中的条件基本相同作为条件
@ -399,7 +366,7 @@ affected, err := engine.Insert(user, &questions)
按照指定的顺序进行排序 按照指定的顺序进行排序
* In(string, …interface{}) * In(string, …interface{})
某字段在一些值中,这里需要注意必须是[]interface{}才可以展开由于Go语言的限制[]int64等均不可以展开。 某字段在一些值中
* Cols(…string) * Cols(…string)
只查询或更新某些指定的字段默认是查询所有映射的字段或者根据Update的第一个参数来判断更新的字段。例如 只查询或更新某些指定的字段默认是查询所有映射的字段或者根据Update的第一个参数来判断更新的字段。例如
@ -410,11 +377,7 @@ engine.Cols("age", "name").Update(&user)
// UPDATE user SET age=? AND name=? // UPDATE user SET age=? AND name=?
``` ```
* AllCols() 其中的参数"age", "name"也可以写成"age, name",两种写法均可
查询或更新所有字段。
* MustCols(…string)
某些字段必须更新。
* Omit(...string) * Omit(...string)
和cols相反此函数指定排除某些指定的字段。注意此方法和Cols方法不可同时使用 和cols相反此函数指定排除某些指定的字段。注意此方法和Cols方法不可同时使用
@ -461,84 +424,65 @@ Having的参数字符串
* UseBool(...string) * UseBool(...string)
当从一个struct来生成查询条件或更新字段时xorm会判断struct的field是否为0,"",nil如果为以上则不当做查询条件或者更新内容。因为bool类型只有true和false两种值因此默认所有bool类型不会作为查询条件或者更新字段。如果可以使用此方法如果默认不传参数则所有的bool字段都将会被使用如果参数不为空则参数中指定的为字段名则这些字段对应的bool值将被使用。 当从一个struct来生成查询条件或更新字段时xorm会判断struct的field是否为0,"",nil如果为以上则不当做查询条件或者更新内容。因为bool类型只有true和false两种值因此默认所有bool类型不会作为查询条件或者更新字段。如果可以使用此方法如果默认不传参数则所有的bool字段都将会被使用如果参数不为空则参数中指定的为字段名则这些字段对应的bool值将被使用。
* NoCascade() * Cascade(bool)
是否自动关联查询field中的数据如果struct的field也是一个struct并且映射为某个Id则可以在查询时自动调用Get方法查询出对应的数据。 是否自动关联查询field中的数据如果struct的field也是一个struct并且映射为某个Id则可以在查询时自动调用Get方法查询出对应的数据。
<a name="63" id="63"></a> <a name="50" id="50"></a>
### 5.3.Get方法 ### 5.3.Get one record
Fetch a single object by user
查询单条数据使用`Get`方法在调用Get方法时需要传入一个对应结构体的指针同时结构体中的非空field自动成为查询的条件和前面的方法条件组合在一起查询。
如:
1) 根据Id来获得单条数据:
```Go ```Go
user := new(User) var user = User{Id:27}
has, err := engine.Id(id).Get(user) has, err := engine.Get(&user)
// 复合主键的获取方法 // or has, err := engine.Id(27).Get(&user)
// has, errr := engine.Id(xorm.PK{1,2}).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 ```Go
user := new(User) var everyone []Userinfo
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是否为nilhas都有可能为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) 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) users := make(map[int64]Userinfo)
err := engine.Find(&users) err := engine.Find(&users)
pUsers := make(map[int64]*Userinfo)
err := engine.Find(&pUsers)
``` ```
3) 也可以加入各种条件 * also you can use Where, Limit
```Go ```Go
users := make([]Userinfo, 0) var allusers []Userinfo
err := engine.Where("age > ? or name = ?", 30, "xlw").Limit(20, 10).Find(&users) err := engine.Where("id > ?", "3").Limit(10,20).Find(&allusers) //Get id>3 limit 10 offset 20
``` ```
<a name="65" id="65"></a> * or you can use a struct query
### 5.5.Iterate方法
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 ```Go
err := engine.Where("age > ? or name=?)", 30, "xlw").Iterate(new(Userinfo), func(i int, bean interface{})error{ err := engine.Where("age > ? or name=?)", 30, "xlw").Iterate(new(Userinfo), func(i int, bean interface{})error{
@ -556,22 +500,6 @@ user := new(User)
total, err := engine.Where("id >?", 1).Count(user) total, err := engine.Where("id >?", 1).Count(user)
``` ```
<a name="67" id="67"></a>
### 5.7.Rows方法
Rows方法和Iterate方法类似提供逐条执行查询到的记录的方法不过Rows更加灵活好用。
```Go
user := new(User)
rows, err := engine.Where("id >?", 1).Rows(user)
if err != nil {
}
defer rows.Close()
for rows.Next() {
err = rows.Scan(user)
//...
}
```
<a name="70" id="70"></a> <a name="70" id="70"></a>
## 6.更新数据 ## 6.更新数据
@ -585,19 +513,17 @@ affected, err := engine.Id(id).Update(user)
这里需要注意Update会自动从user结构体中提取非0和非nil得值作为需要更新的内容因此如果需要更新一个值为0则此种方法将无法实现因此有两种选择 这里需要注意Update会自动从user结构体中提取非0和非nil得值作为需要更新的内容因此如果需要更新一个值为0则此种方法将无法实现因此有两种选择
* 1.通过添加Cols函数指定需要更新结构体中的哪些值未指定的将不更新指定了的即使为0也会更新。 1. 通过添加Cols函数指定需要更新结构体中的哪些值未指定的将不更新指定了的即使为0也会更新。
```Go ```Go
affected, err := engine.Id(id).Cols("age").Update(&user) affected, err := engine.Id(id).Cols("age").Update(&user)
``` ```
* 2.通过传入map[string]interface{}来进行更新但这时需要额外指定更新到哪个表因为通过map是无法自动检测更新哪个表的。 2. 通过传入map[string]interface{}来进行更新但这时需要额外指定更新到哪个表因为通过map是无法自动检测更新哪个表的。
```Go ```Go
affected, err := engine.Table(new(User)).Id(id).Update(map[string]interface{}{"age":0}) affected, err := engine.Table(new(User)).Id(id).Update(map[string]interface{}{"age":0})
``` ```
<a name="71" id="71"></a>
### 6.1.乐观锁 ### 6.1.乐观锁
要使用乐观锁需要使用version标记 要使用乐观锁需要使用version标记
@ -617,49 +543,51 @@ engine.Id(1).Update(&user)
// UPDATE user SET ..., version = version + 1 WHERE id = ? AND version = ? // 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 ```Go
user := new(User) err := engine.Id(1).Delete(&User{})
affected, err := engine.Id(id).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> <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 ```Go
sql := "select * from userinfo" sql := "select * from userinfo"
results, err := engine.Query(sql) results, err := engine.Query(sql)
``` ```
当调用`Query`时,第一个返回值`results`为`[]map[string][]byte`的形式。
<a name="100" id="100"></a> <a name="100" id="100"></a>
## 9.执行SQL命令 ## 9.Execute SQL command
If insert, update or delete then use Exec
也可以直接执行一个SQL命令即执行Insert Update Delete 等操作。此时不管数据库是何种类型,都可以使用 ` 和 ? 符号。
```Go ```Go
sql = "update `userinfo` set username=? where id=?" sql = "update userinfo set username=? where id=?"
res, err := engine.Exec(sql, "xiaolun", 1) res, err := engine.Exec(sql, "xiaolun", 1)
``` ```
<a name="110" id="110"></a> <a name="110" id="110"></a>
## 10.事务处理 ## 10.Transaction
当使用事务处理时需要创建Session对象。在进行事物处理时可以混用ORM方法和RAW方法如下代码所示
```Go ```Go
session := engine.NewSession() session := engine.NewSession()
defer session.Close() defer session.Close()
// add Begin() before any action // add Begin() before any action
err := session.Begin() err := session.Begin()
user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()} user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()}
@ -688,147 +616,78 @@ if err != nil {
} }
``` ```
* 注意如果您使用的是mysql数据库引擎为innodb事务才有效myisam引擎是不支持事务的。
<a name="120" id="120"></a> <a name="120" id="120"></a>
## 11.缓存 ## 11.缓存
xorm内置了一致性缓存支持不过默认并没有开启。要开启缓存需要在engine创建完后进行配置 1. Global Cache
启用一个全局的内存缓存 Xorm implements cache support. Defaultly, it's disabled. If enable it, use below code.
```Go ```Go
cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000) cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000)
engine.SetDefaultCacher(cacher) 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 ```Go
cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000) cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000)
engine.MapCacher(&user, cacher) engine.MapCacher(&user, cacher)
``` ```
如果要禁用某个表的缓存,则: Caution:
```Go 1. When use Cols methods on cache enabled, the system still return all the columns.
engine.MapCacher(&user, nil)
```
设置完之后,其它代码基本上就不需要改动了,缓存系统已经在后台运行。 2. When using Exec method, you should clear cache
当前实现了内存存储的CacheStore接口MemoryStore如果需要采用其它设备存储可以实现CacheStore接口。
不过需要特别注意不适用缓存或者需要手动编码的地方:
1. 当使用了`Distinct`,`Having`,`GroupBy`方法将不会使用缓存
2. 在`Get`或者`Find`时使用了`Cols`,`Omit`方法,则在开启缓存后此方法无效,系统仍旧会取出这个表中的所有字段。
3. 在使用Exec方法执行了方法之后可能会导致缓存与数据库不一致的地方。因此如果启用缓存尽量避免使用Exec。如果必须使用则需要在使用了Exec之后调用ClearCache手动做缓存清除的工作。比如
```Go ```Go
engine.Exec("update user set name = ? where id = ?", "xlw", 1) engine.Exec("update user set name = ? where id = ?", "xlw", 1)
engine.ClearCache(new(User)) engine.ClearCache(new(User))
``` ```
缓存的实现原理如下图所示: Cache implement theory below:
![cache design](https://raw.github.com/lunny/xorm/master/docs/cache_design.png) ![cache design](https://raw.github.com/go-xorm/xorm/master/docs/cache_design.png)
<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> <a name="130" id="130"></a>
## 13.xorm工具 ## 12.xorm tool
xorm工具提供了xorm命令能够帮助做很多事情。 xorm工具提供了xorm命令能够帮助做很多事情。
### 13.1.反转命令 ### 12.1.Reverse command
参见 [xorm工具](https://github.com/lunny/xorm/tree/master/xorm) Please visit [xorm tool](https://github.com/go-xorm/xorm/tree/master/xorm)
<a name="140" id="140"></a> <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> <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) * [VeryHour](http://veryhour.com)
<a name="160" id="160"></a> <a name="160"></a>
## 16.那些年我们踩过的坑 ## 15.FAQ
* 怎么同时使用xorm的tag和json的tag
答:使用空格 1.How the xorm tag use both with json?
Use space.
```Go ```Go
type User struct { type User struct {
Name string `json:"name" xorm:"name"` 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 进行讨论。

829
docs/QuickStartCn.md Normal file
View File

@ -0,0 +1,829 @@
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
```
<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>&lt;-</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.查询和统计数据
所有的查询条件不区分调用顺序但必须在调用GetFindCount, 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是否为nilhas都有可能为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))
```
缓存的实现原理如下图所示:
![cache design](https://raw.github.com/go-xorm/xorm/master/docs/cache_design.png)
<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 进行讨论。

View File

@ -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>&lt;-</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
所有的查询条件不区分调用顺序但必须在调用GetFindCount这三个函数之前调用。同时需要注意的一点是在调用的参数中所有的字符字段名均为映射后的数据库的字段名而不是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:
![cache design](https://raw.github.com/lunny/xorm/master/docs/cache_design.png)
<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"`
}
```

328
engine.go
View File

@ -6,86 +6,59 @@ import (
"database/sql" "database/sql"
"errors" "errors"
"fmt" "fmt"
"io"
"os" "os"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time" "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. // Engine is the major struct of xorm, it means a database manager.
// Commonly, an application only need one engine // Commonly, an application only need one engine
type Engine struct { type Engine struct {
columnMapper IMapper db *core.DB
tableMapper IMapper dialect core.Dialect
ColumnMapper core.IMapper
TableMapper core.IMapper
TagIdentifier string TagIdentifier string
DriverName string Tables map[reflect.Type]*core.Table
DataSourceName string
dialect dialect
Tables map[reflect.Type]*Table
mutex *sync.RWMutex mutex *sync.RWMutex
Cacher core.Cacher
ShowSQL bool ShowSQL bool
ShowErr bool ShowErr bool
ShowDebug bool ShowDebug bool
ShowWarn bool ShowWarn bool
Pool IConnectPool //Pool IConnectPool
Filters []Filter //Filters []core.Filter
Logger io.Writer Logger ILogger // io.Writer
Cacher Cacher
UseCache bool
TimeZone string TimeZone string
} }
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.SetTableMapper(mapper)
engine.SetColumnMapper(mapper) engine.SetColumnMapper(mapper)
} }
func (engine *Engine) SetTableMapper(mapper IMapper) { func (engine *Engine) SetTableMapper(mapper core.IMapper) {
engine.tableMapper = mapper engine.TableMapper = mapper
} }
func (engine *Engine) SetColumnMapper(mapper IMapper) { func (engine *Engine) SetColumnMapper(mapper core.IMapper) {
engine.columnMapper = mapper engine.ColumnMapper = mapper
} }
// If engine's database support batch insert records like // If engine's database support batch insert records like
@ -107,8 +80,8 @@ func (engine *Engine) Quote(sql string) string {
return engine.dialect.QuoteStr() + sql + engine.dialect.QuoteStr() return engine.dialect.QuoteStr() + sql + engine.dialect.QuoteStr()
} }
// A simple wrapper to dialect's SqlType method // A simple wrapper to dialect's core.SqlType method
func (engine *Engine) SqlType(c *Column) string { func (engine *Engine) SqlType(c *core.Column) string {
return engine.dialect.SqlType(c) return engine.dialect.SqlType(c)
} }
@ -118,29 +91,24 @@ func (engine *Engine) AutoIncrStr() string {
} }
// Set engine's pool, the pool default is Go's standard library's connection pool. // Set engine's pool, the pool default is Go's standard library's connection pool.
func (engine *Engine) SetPool(pool IConnectPool) error { /*func (engine *Engine) SetPool(pool IConnectPool) error {
engine.Pool = pool engine.Pool = pool
return engine.Pool.Init(engine) return engine.Pool.Init(engine)
} }*/
// SetMaxConns is only available for go 1.2+ // SetMaxConns is only available for go 1.2+
func (engine *Engine) SetMaxConns(conns int) { func (engine *Engine) SetMaxConns(conns int) {
engine.Pool.SetMaxConns(conns) engine.db.SetMaxOpenConns(conns)
} }
// SetMaxIdleConns // SetMaxIdleConns
func (engine *Engine) SetMaxIdleConns(conns int) { 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. // SetDefaltCacher set the default cacher. Xorm's default not enable cacher.
func (engine *Engine) SetDefaultCacher(cacher Cacher) { func (engine *Engine) SetDefaultCacher(cacher core.Cacher) {
if cacher == nil {
engine.UseCache = false
} else {
engine.UseCache = true
engine.Cacher = cacher engine.Cacher = cacher
}
} }
// If you has set default cacher, and you want temporilly stop use cache, // If you has set default cacher, and you want temporilly stop use cache,
@ -158,15 +126,19 @@ func (engine *Engine) NoCascade() *Session {
} }
// Set a table use a special cacher // 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) v := rValue(bean)
engine.autoMapType(v) engine.autoMapType(v)
engine.Tables[v.Type()].Cacher = cacher engine.Tables[v.Type()].Cacher = cacher
} }
// OpenDB provides a interface to operate database directly. // NewDB provides an interface to operate database directly
func (engine *Engine) OpenDB() (*sql.DB, error) { func (engine *Engine) NewDB() (*core.DB, error) {
return sql.Open(engine.DriverName, engine.DataSourceName) return core.OpenDialect(engine.dialect)
}
func (engine *Engine) DB() *core.DB {
return engine.db
} }
// New a session // New a session
@ -178,42 +150,51 @@ func (engine *Engine) NewSession() *Session {
// Close the engine // Close the engine
func (engine *Engine) Close() error { 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 { func (engine *Engine) Ping() error {
session := engine.NewSession() session := engine.NewSession()
defer session.Close() defer session.Close()
engine.LogSQL("PING DATABASE", engine.DriverName) engine.LogInfo("PING DATABASE", engine.DriverName)
return session.Ping() return session.Ping()
} }
// logging sql // logging sql
func (engine *Engine) LogSQL(contents ...interface{}) { func (engine *Engine) logSQL(sqlStr string, sqlArgs ...interface{}) {
if engine.ShowSQL { 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 // logging error
func (engine *Engine) LogError(contents ...interface{}) { func (engine *Engine) LogError(contents ...interface{}) {
if engine.ShowErr { 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 // logging debug
func (engine *Engine) LogDebug(contents ...interface{}) { func (engine *Engine) LogDebug(contents ...interface{}) {
if engine.ShowDebug { if engine.ShowDebug {
io.WriteString(engine.Logger, fmt.Sprintln(contents...)) engine.Logger.Debug(fmt.Sprintln(contents...))
} }
} }
// logging warn // logging warn
func (engine *Engine) LogWarn(contents ...interface{}) { func (engine *Engine) LogWarn(contents ...interface{}) {
if engine.ShowWarn { if engine.ShowWarn {
io.WriteString(engine.Logger, fmt.Sprintln(contents...)) engine.Logger.Warning(fmt.Sprintln(contents...))
} }
} }
@ -240,7 +221,7 @@ func (engine *Engine) NoAutoTime() *Session {
} }
// Retrieve all tables, columns, indexes' informations from database. // 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() tables, err := engine.dialect.GetTables()
if err != nil { if err != nil {
return nil, err return nil, err
@ -251,8 +232,11 @@ func (engine *Engine) DBMetas() ([]*Table, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
table.Columns = cols for _, name := range colSeq {
table.ColumnsSeq = colSeq table.AddColumn(cols[name])
}
//table.Columns = cols
//table.ColumnsSeq = colSeq
indexes, err := engine.dialect.GetIndexes(table.Name) indexes, err := engine.dialect.GetIndexes(table.Name)
if err != nil { if err != nil {
@ -262,10 +246,10 @@ func (engine *Engine) DBMetas() ([]*Table, error) {
for _, index := range indexes { for _, index := range indexes {
for _, name := range index.Cols { for _, name := range index.Cols {
if col, ok := table.Columns[name]; ok { if col := table.GetColumn(name); col != nil {
col.Indexes[index.Name] = true col.Indexes[index.Name] = true
} else { } 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 +359,13 @@ func (engine *Engine) In(column string, args ...interface{}) *Session {
return session.In(column, args...) 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 // Temporarily change the Get, Find, Update's table
func (engine *Engine) Table(tableNameOrBean interface{}) *Session { func (engine *Engine) Table(tableNameOrBean interface{}) *Session {
session := engine.NewSession() session := engine.NewSession()
@ -437,7 +428,7 @@ func (engine *Engine) Having(conditions string) *Session {
return session.Having(conditions) return session.Having(conditions)
} }
func (engine *Engine) autoMapType(v reflect.Value) *Table { func (engine *Engine) autoMapType(v reflect.Value) *core.Table {
t := v.Type() t := v.Type()
engine.mutex.RLock() engine.mutex.RLock()
table, ok := engine.Tables[t] table, ok := engine.Tables[t]
@ -451,34 +442,39 @@ func (engine *Engine) autoMapType(v reflect.Value) *Table {
return table return table
} }
func (engine *Engine) autoMap(bean interface{}) *Table { func (engine *Engine) autoMap(bean interface{}) *core.Table {
v := rValue(bean) v := rValue(bean)
return engine.autoMapType(v) return engine.autoMapType(v)
} }
func (engine *Engine) newTable() *Table { /*func (engine *Engine) mapType(t reflect.Type) *core.Table {
table := &Table{} return mappingTable(t, engine.TableMapper, engine.ColumnMapper, engine.dialect, engine.TagIdentifier)
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 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 { if index, ok := table.Indexes[indexName]; ok {
index.AddColumn(col.Name) index.AddColumn(col.Name)
col.Indexes[index.Name] = true col.Indexes[index.Name] = true
} else { } else {
index := NewIndex(indexName, indexType) index := core.NewIndex(indexName, indexType)
index.AddColumn(col.Name) index.AddColumn(col.Name)
table.AddIndex(index) table.AddIndex(index)
col.Indexes[index.Name] = true 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() t := v.Type()
table := engine.newTable() table := engine.newTable()
method := v.MethodByName("TableName") method := v.MethodByName("TableName")
@ -496,7 +492,7 @@ func (engine *Engine) mapType(v reflect.Value) *Table {
} }
if table.Name == "" { if table.Name == "" {
table.Name = engine.tableMapper.Obj2Table(t.Name()) table.Name = engine.TableMapper.Obj2Table(t.Name())
} }
table.Type = t table.Type = t
@ -506,13 +502,13 @@ func (engine *Engine) mapType(v reflect.Value) *Table {
for i := 0; i < t.NumField(); i++ { for i := 0; i < t.NumField(); i++ {
tag := t.Field(i).Tag tag := t.Field(i).Tag
ormTagStr := tag.Get(engine.TagIdentifier) ormTagStr := tag.Get(engine.TagIdentifier)
var col *Column var col *core.Column
fieldValue := v.Field(i) fieldValue := v.Field(i)
fieldType := fieldValue.Type() fieldType := fieldValue.Type()
if ormTagStr != "" { if ormTagStr != "" {
col = &Column{FieldName: t.Field(i).Name, Nullable: true, IsPrimaryKey: false, col = &core.Column{FieldName: t.Field(i).Name, Nullable: true, IsPrimaryKey: false,
IsAutoIncrement: false, MapType: TWOSIDES, Indexes: make(map[string]bool)} IsAutoIncrement: false, MapType: core.TWOSIDES, Indexes: make(map[string]bool)}
tags := strings.Split(ormTagStr, " ") tags := strings.Split(ormTagStr, " ")
if len(tags) > 0 { if len(tags) > 0 {
@ -521,14 +517,14 @@ func (engine *Engine) mapType(v reflect.Value) *Table {
} }
if (strings.ToUpper(tags[0]) == "EXTENDS") && if (strings.ToUpper(tags[0]) == "EXTENDS") &&
(fieldType.Kind() == reflect.Struct) { (fieldType.Kind() == reflect.Struct) {
//parentTable := mappingTable(fieldType, tableMapper, colMapper, dialect, tagId)
parentTable := engine.mapType(fieldValue) 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) col.FieldName = fmt.Sprintf("%v.%v", fieldType.Name(), col.FieldName)
table.Columns[strings.ToLower(name)] = col table.AddColumn(col)
table.ColumnsSeq = append(table.ColumnsSeq, name)
} }
table.PrimaryKeys = parentTable.PrimaryKeys
continue continue
} }
@ -539,9 +535,9 @@ func (engine *Engine) mapType(v reflect.Value) *Table {
k := strings.ToUpper(key) k := strings.ToUpper(key)
switch { switch {
case k == "<-": case k == "<-":
col.MapType = ONLYFROMDB col.MapType = core.ONLYFROMDB
case k == "->": case k == "->":
col.MapType = ONLYTODB col.MapType = core.ONLYTODB
case k == "PK": case k == "PK":
col.IsPrimaryKey = true col.IsPrimaryKey = true
col.Nullable = false col.Nullable = false
@ -570,12 +566,12 @@ func (engine *Engine) mapType(v reflect.Value) *Table {
col.IsUpdated = true col.IsUpdated = true
case strings.HasPrefix(k, "INDEX(") && strings.HasSuffix(k, ")"): case strings.HasPrefix(k, "INDEX(") && strings.HasSuffix(k, ")"):
indexName := k[len("INDEX")+1 : len(k)-1] indexName := k[len("INDEX")+1 : len(k)-1]
indexNames[indexName] = IndexType indexNames[indexName] = core.IndexType
case k == "INDEX": case k == "INDEX":
isIndex = true isIndex = true
case strings.HasPrefix(k, "UNIQUE(") && strings.HasSuffix(k, ")"): case strings.HasPrefix(k, "UNIQUE(") && strings.HasSuffix(k, ")"):
indexName := k[len("UNIQUE")+1 : len(k)-1] indexName := k[len("UNIQUE")+1 : len(k)-1]
indexNames[indexName] = UniqueType indexNames[indexName] = core.UniqueType
case k == "UNIQUE": case k == "UNIQUE":
isUnique = true isUnique = true
case k == "NOTNULL": case k == "NOTNULL":
@ -588,11 +584,12 @@ func (engine *Engine) mapType(v reflect.Value) *Table {
} }
} else if strings.Contains(k, "(") && strings.HasSuffix(k, ")") { } else if strings.Contains(k, "(") && strings.HasSuffix(k, ")") {
fs := strings.Split(k, "(") fs := strings.Split(k, "(")
if _, ok := sqlTypes[fs[0]]; !ok {
if _, ok := core.SqlTypes[fs[0]]; !ok {
preKey = k preKey = k
continue 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], ",") fs2 := strings.Split(fs[1][0:len(fs[1])-1], ",")
if len(fs2) == 2 { if len(fs2) == 2 {
col.Length, err = strconv.Atoi(fs2[0]) col.Length, err = strconv.Atoi(fs2[0])
@ -610,18 +607,18 @@ func (engine *Engine) mapType(v reflect.Value) *Table {
} }
} }
} else { } else {
if _, ok := sqlTypes[k]; ok { if _, ok := core.SqlTypes[k]; ok {
col.SQLType = SQLType{k, 0, 0} col.SQLType = core.SQLType{k, 0, 0}
} else if preKey != "DEFAULT" { } else if key != col.Default {
col.Name = key col.Name = key
} }
} }
engine.SqlType(col) engine.dialect.SqlType(col)
} }
preKey = k preKey = k
} }
if col.SQLType.Name == "" { if col.SQLType.Name == "" {
col.SQLType = Type2SQLType(fieldType) col.SQLType = core.Type2SQLType(fieldType)
} }
if col.Length == 0 { if col.Length == 0 {
col.Length = col.SQLType.DefaultLength col.Length = col.SQLType.DefaultLength
@ -629,14 +626,15 @@ func (engine *Engine) mapType(v reflect.Value) *Table {
if col.Length2 == 0 { if col.Length2 == 0 {
col.Length2 = col.SQLType.DefaultLength2 col.Length2 = col.SQLType.DefaultLength2
} }
//fmt.Println("======", col)
if col.Name == "" { if col.Name == "" {
col.Name = engine.columnMapper.Obj2Table(t.Field(i).Name) col.Name = engine.ColumnMapper.Obj2Table(t.Field(i).Name)
} }
if isUnique { if isUnique {
indexNames[col.Name] = UniqueType indexNames[col.Name] = core.UniqueType
} else if isIndex { } else if isIndex {
indexNames[col.Name] = IndexType indexNames[col.Name] = core.IndexType
} }
for indexName, indexType := range indexNames { for indexName, indexType := range indexNames {
@ -644,25 +642,10 @@ func (engine *Engine) mapType(v reflect.Value) *Table {
} }
} }
} else { } else {
sqlType := Type2SQLType(fieldType) sqlType := core.Type2SQLType(fieldType)
col = &Column{ col = core.NewColumn(engine.ColumnMapper.Obj2Table(t.Field(i).Name),
Name: engine.columnMapper.Obj2Table(t.Field(i).Name), t.Field(i).Name, sqlType, sqlType.DefaultLength,
FieldName: t.Field(i).Name, sqlType.DefaultLength2, true)
SQLType: sqlType,
Length: sqlType.DefaultLength,
Length2: sqlType.DefaultLength2,
Nullable: true,
Default: "",
Indexes: make(map[string]bool),
IsPrimaryKey: false,
IsAutoIncrement: false,
MapType: TWOSIDES,
IsCreated: false,
IsUpdated: false,
IsCascade: false,
IsVersion: false,
DefaultIsEmpty: false,
}
} }
if col.IsAutoIncrement { if col.IsAutoIncrement {
col.Nullable = false col.Nullable = false
@ -676,7 +659,7 @@ func (engine *Engine) mapType(v reflect.Value) *Table {
} }
if idFieldColName != "" && len(table.PrimaryKeys) == 0 { if idFieldColName != "" && len(table.PrimaryKeys) == 0 {
col := table.Columns[strings.ToLower(idFieldColName)] col := table.GetColumn(idFieldColName)
col.IsPrimaryKey = true col.IsPrimaryKey = true
col.IsAutoIncrement = true col.IsAutoIncrement = true
col.Nullable = false col.Nullable = false
@ -725,6 +708,24 @@ func (engine *Engine) IsTableExist(bean interface{}) (bool, error) {
return has, err 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 // create indexes
func (engine *Engine) CreateIndexes(bean interface{}) error { func (engine *Engine) CreateIndexes(bean interface{}) error {
session := engine.NewSession() session := engine.NewSession()
@ -739,16 +740,31 @@ func (engine *Engine) CreateUniques(bean interface{}) error {
return session.CreateUniques(bean) 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 // 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) t := rType(bean)
if t.Kind() != reflect.Struct { if t.Kind() != reflect.Struct {
return errors.New("error params") return errors.New("error params")
} }
table := engine.autoMap(bean) table := engine.autoMap(bean)
if table.Cacher != nil { cacher := table.Cacher
table.Cacher.ClearIds(table.Name) if cacher == nil {
table.Cacher.DelBean(table.Name, id) cacher = engine.Cacher
}
if cacher != nil {
cacher.ClearIds(table.Name)
cacher.DelBean(table.Name, id)
} }
return nil return nil
} }
@ -761,9 +777,13 @@ func (engine *Engine) ClearCache(beans ...interface{}) error {
return errors.New("error params") return errors.New("error params")
} }
table := engine.autoMap(bean) table := engine.autoMap(bean)
if table.Cacher != nil { cacher := table.Cacher
table.Cacher.ClearIds(table.Name) if cacher == nil {
table.Cacher.ClearBeans(table.Name) cacher = engine.Cacher
}
if cacher != nil {
cacher.ClearIds(table.Name)
cacher.ClearBeans(table.Name)
} }
} }
return nil return nil
@ -803,7 +823,7 @@ func (engine *Engine) Sync(beans ...interface{}) error {
return err return err
} }
} else { } else {
for _, col := range table.Columns { for _, col := range table.Columns() {
session := engine.NewSession() session := engine.NewSession()
session.Statement.RefTable = table session.Statement.RefTable = table
defer session.Close() defer session.Close()
@ -826,7 +846,7 @@ func (engine *Engine) Sync(beans ...interface{}) error {
session := engine.NewSession() session := engine.NewSession()
session.Statement.RefTable = table session.Statement.RefTable = table
defer session.Close() defer session.Close()
if index.Type == UniqueType { if index.Type == core.UniqueType {
//isExist, err := session.isIndexExist(table.Name, name, true) //isExist, err := session.isIndexExist(table.Name, name, true)
isExist, err := session.isIndexExist2(table.Name, index.Cols, true) isExist, err := session.isIndexExist2(table.Name, index.Cols, true)
if err != nil { if err != nil {
@ -841,7 +861,7 @@ func (engine *Engine) Sync(beans ...interface{}) error {
return err return err
} }
} }
} else if index.Type == IndexType { } else if index.Type == core.IndexType {
isExist, err := session.isIndexExist2(table.Name, index.Cols, false) isExist, err := session.isIndexExist2(table.Name, index.Cols, false)
if err != nil { if err != nil {
return err return err
@ -1105,15 +1125,15 @@ func (engine *Engine) NowTime(sqlTypeName string) interface{} {
func (engine *Engine) FormatTime(sqlTypeName string, t time.Time) (v interface{}) { func (engine *Engine) FormatTime(sqlTypeName string, t time.Time) (v interface{}) {
switch sqlTypeName { switch sqlTypeName {
case Time: case core.Time:
s := engine.TZTime(t).Format("2006-01-02 15:04:05") //time.RFC3339 s := engine.TZTime(t).Format("2006-01-02 15:04:05") //time.RFC3339
v = s[11:19] v = s[11:19]
case Date: case core.Date:
v = engine.TZTime(t).Format("2006-01-02") v = engine.TZTime(t).Format("2006-01-02")
case DateTime, TimeStamp: case core.DateTime, core.TimeStamp:
v = engine.TZTime(t).Format("2006-01-02 15:04:05") v = engine.TZTime(t).Format("2006-01-02 15:04:05")
case TimeStampz: case core.TimeStampz:
if engine.dialect.DBType() == MSSQL { if engine.dialect.DBType() == core.MSSQL {
v = engine.TZTime(t).Format("2006-01-02T15:04:05.9999999Z07:00") v = engine.TZTime(t).Format("2006-01-02T15:04:05.9999999Z07:00")
} else { } else {
v = engine.TZTime(t).Format(time.RFC3339Nano) v = engine.TZTime(t).Format(time.RFC3339Nano)

View File

@ -2,9 +2,10 @@ package main
import ( import (
"fmt" "fmt"
"github.com/lunny/xorm"
_ "github.com/mattn/go-sqlite3"
"os" "os"
"github.com/go-xorm/xorm"
_ "github.com/mattn/go-sqlite3"
) )
type User struct { type User struct {

View File

@ -3,7 +3,7 @@ package main
import ( import (
"fmt" "fmt"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
"github.com/lunny/xorm" "github.com/go-xorm/xorm"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
"os" "os"
) )

View File

@ -3,9 +3,10 @@ package main
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/lunny/xorm"
_ "github.com/mattn/go-sqlite3"
"os" "os"
"github.com/go-xorm/xorm"
_ "github.com/mattn/go-sqlite3"
) )
type Status struct { type Status struct {
@ -47,7 +48,7 @@ func main() {
f := "conversion.db" f := "conversion.db"
os.Remove(f) os.Remove(f)
Orm, err := NewEngine("sqlite3", f) Orm, err := xorm.NewEngine("sqlite3", f)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return return

View File

@ -2,9 +2,10 @@ package main
import ( import (
"fmt" "fmt"
"github.com/lunny/xorm"
_ "github.com/mattn/go-sqlite3"
"os" "os"
"github.com/go-xorm/xorm"
_ "github.com/mattn/go-sqlite3"
) )
type User struct { type User struct {
@ -27,7 +28,7 @@ func main() {
f := "derive.db" f := "derive.db"
os.Remove(f) os.Remove(f)
Orm, err := NewEngine("sqlite3", f) Orm, err := xorm.NewEngine("sqlite3", f)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return return

Binary file not shown.

View File

@ -2,11 +2,12 @@ package main
import ( import (
"fmt" "fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/lunny/xorm"
_ "github.com/mattn/go-sqlite3"
"os" "os"
"runtime" "runtime"
_ "github.com/go-sql-driver/mysql"
"github.com/go-xorm/xorm"
_ "github.com/mattn/go-sqlite3"
) )
type User struct { type User struct {
@ -38,14 +39,14 @@ func test(engine *xorm.Engine) {
for i := 0; i < size; i++ { for i := 0; i < size; i++ {
go func(x int) { go func(x int) {
//x := i //x := i
err := engine.Test() err := engine.Ping()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
} else { } else {
err = engine.Map(u) /*err = engine.(u)
if err != nil { if err != nil {
fmt.Println("Map user failed") fmt.Println("Map user failed")
} else { } else {*/
for j := 0; j < 10; j++ { for j := 0; j < 10; j++ {
if x+j < 2 { if x+j < 2 {
_, err = engine.Get(u) _, err = engine.Get(u)
@ -66,7 +67,7 @@ func test(engine *xorm.Engine) {
} }
} }
fmt.Printf("%v success!\n", x) fmt.Printf("%v success!\n", x)
} //}
} }
queue <- x queue <- x
}(i) }(i)
@ -82,7 +83,7 @@ func test(engine *xorm.Engine) {
} }
func main() { func main() {
runtime.GOMAXPROCS(2) runtime.GOMAXPROCS(1)
fmt.Println("-----start sqlite go routines-----") fmt.Println("-----start sqlite go routines-----")
engine, err := sqliteEngine() engine, err := sqliteEngine()
if err != nil { if err != nil {

View File

@ -2,12 +2,12 @@ package main
import ( import (
"fmt" "fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/lunny/xorm"
xorm "github.com/lunny/xorm"
_ "github.com/mattn/go-sqlite3"
"os" "os"
"runtime" "runtime"
_ "github.com/go-sql-driver/mysql"
"github.com/go-xorm/xorm"
_ "github.com/mattn/go-sqlite3"
) )
type User struct { type User struct {
@ -34,21 +34,21 @@ func test(engine *xorm.Engine) {
} }
engine.ShowSQL = true engine.ShowSQL = true
engine.Pool.SetMaxConns(5) engine.SetMaxConns(5)
size := 1000 size := 1000
queue := make(chan int, size) queue := make(chan int, size)
for i := 0; i < size; i++ { for i := 0; i < size; i++ {
go func(x int) { go func(x int) {
//x := i //x := i
err := engine.Test() err := engine.Ping()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
} else { } else {
err = engine.Map(u) /*err = engine.Map(u)
if err != nil { if err != nil {
fmt.Println("Map user failed") fmt.Println("Map user failed")
} else { } else {*/
for j := 0; j < 10; j++ { for j := 0; j < 10; j++ {
if x+j < 2 { if x+j < 2 {
_, err = engine.Get(u) _, err = engine.Get(u)
@ -69,7 +69,7 @@ func test(engine *xorm.Engine) {
} }
} }
fmt.Printf("%v success!\n", x) fmt.Printf("%v success!\n", x)
} //}
} }
queue <- x queue <- x
}(i) }(i)

View File

@ -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
}
}
}

View File

@ -2,9 +2,10 @@ package main
import ( import (
"fmt" "fmt"
"github.com/lunny/xorm"
_ "github.com/mattn/go-sqlite3"
"os" "os"
"github.com/go-xorm/xorm"
_ "github.com/mattn/go-sqlite3"
) )
type User struct { type User struct {
@ -26,7 +27,7 @@ func main() {
f := "singleMapping.db" f := "singleMapping.db"
os.Remove(f) os.Remove(f)
Orm, err := NewEngine("sqlite3", f) Orm, err := xorm.NewEngine("sqlite3", f)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return return

View File

@ -4,7 +4,7 @@ import (
"fmt" "fmt"
_ "github.com/bylevel/pq" _ "github.com/bylevel/pq"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
"github.com/lunny/xorm" "github.com/go-xorm/xorm"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
) )

View File

@ -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
}

38
goracle_driver.go Normal file
View File

@ -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&paramN=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
}

View File

@ -1,12 +1,13 @@
package xorm package xorm
import ( import (
"database/sql"
"fmt" "fmt"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/go-xorm/core"
) )
func indexNoCase(s, sep string) int { func indexNoCase(s, sep string) int {
@ -99,7 +100,7 @@ func value2Bytes(rawValue *reflect.Value) (data []byte, err error) {
} }
//时间类型 //时间类型
case reflect.Struct: case reflect.Struct:
if aa == reflect.TypeOf(c_TIME_DEFAULT) { if aa == core.TimeType {
str = rawValue.Interface().(time.Time).Format(time.RFC3339Nano) str = rawValue.Interface().(time.Time).Format(time.RFC3339Nano)
data = []byte(str) data = []byte(str)
} else { } else {
@ -124,7 +125,7 @@ func value2Bytes(rawValue *reflect.Value) (data []byte, err error) {
return 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() fields, err := rows.Columns()
if err != nil { if err != nil {
return nil, err return nil, err

48
logger.go Normal file
View File

@ -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
}

View File

@ -1,133 +1,43 @@
//LRUCacher implements Cacher according to LRU algorithm
package xorm package xorm
import ( import (
"container/list" "container/list"
"errors"
"fmt" "fmt"
"strconv"
"strings"
"sync" "sync"
"time" "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 { type LRUCacher struct {
idList *list.List idList *list.List
sqlList *list.List sqlList *list.List
idIndex map[string]map[interface{}]*list.Element idIndex map[string]map[string]*list.Element
sqlIndex map[string]map[interface{}]*list.Element sqlIndex map[string]map[string]*list.Element
store CacheStore store core.CacheStore
Max int
mutex sync.Mutex mutex sync.Mutex
// maxSize int
MaxElementSize int
Expired time.Duration Expired time.Duration
maxSize int
GcInterval 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(), cacher := &LRUCacher{store: store, idList: list.New(),
sqlList: list.New(), Expired: expired, maxSize: maxSize, sqlList: list.New(), Expired: expired,
GcInterval: CacheGcInterval, Max: max, GcInterval: core.CacheGcInterval, MaxElementSize: maxElementSize,
sqlIndex: make(map[string]map[interface{}]*list.Element), sqlIndex: make(map[string]map[string]*list.Element),
idIndex: make(map[string]map[interface{}]*list.Element), idIndex: make(map[string]map[string]*list.Element),
} }
cacher.RunGC() cacher.RunGC()
return cacher 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 { //func NewLRUCacher3(store CacheStore, expired time.Duration, maxSize int) *LRUCacher {
// return newLRUCacher(store, expired, maxSize, 0) // return newLRUCacher(store, expired, maxSize, 0)
//} //}
@ -148,7 +58,7 @@ func (m *LRUCacher) GC() {
defer m.mutex.Unlock() defer m.mutex.Unlock()
var removedNum int var removedNum int
for e := m.idList.Front(); e != nil; { for e := m.idList.Front(); e != nil; {
if removedNum <= CacheGcMaxRemoved && if removedNum <= core.CacheGcMaxRemoved &&
time.Now().Sub(e.Value.(*idNode).lastVisit) > m.Expired { time.Now().Sub(e.Value.(*idNode).lastVisit) > m.Expired {
removedNum++ removedNum++
next := e.Next() next := e.Next()
@ -164,7 +74,7 @@ func (m *LRUCacher) GC() {
removedNum = 0 removedNum = 0
for e := m.sqlList.Front(); e != nil; { for e := m.sqlList.Front(); e != nil; {
if removedNum <= CacheGcMaxRemoved && if removedNum <= core.CacheGcMaxRemoved &&
time.Now().Sub(e.Value.(*sqlNode).lastVisit) > m.Expired { time.Now().Sub(e.Value.(*sqlNode).lastVisit) > m.Expired {
removedNum++ removedNum++
next := e.Next() next := e.Next()
@ -184,7 +94,7 @@ func (m *LRUCacher) GetIds(tableName, sql string) interface{} {
m.mutex.Lock() m.mutex.Lock()
defer m.mutex.Unlock() defer m.mutex.Unlock()
if _, ok := m.sqlIndex[tableName]; !ok { 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 v, err := m.store.Get(sql); err == nil {
if el, ok := m.sqlIndex[tableName][sql]; !ok { 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 // 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() m.mutex.Lock()
defer m.mutex.Unlock() defer m.mutex.Unlock()
if _, ok := m.idIndex[tableName]; !ok { 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) tid := genId(tableName, id)
if v, err := m.store.Get(tid); err == nil { if v, err := m.store.Get(tid); err == nil {
@ -248,7 +158,7 @@ func (m *LRUCacher) clearIds(tableName string) {
m.store.Del(sql) 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) { func (m *LRUCacher) ClearIds(tableName string) {
@ -261,11 +171,11 @@ func (m *LRUCacher) clearBeans(tableName string) {
if tis, ok := m.idIndex[tableName]; ok { if tis, ok := m.idIndex[tableName]; ok {
for id, v := range tis { for id, v := range tis {
m.idList.Remove(v) m.idList.Remove(v)
tid := genId(tableName, id.(int64)) tid := genId(tableName, id)
m.store.Del(tid) 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) { func (m *LRUCacher) ClearBeans(tableName string) {
@ -278,7 +188,7 @@ func (m *LRUCacher) PutIds(tableName, sql string, ids interface{}) {
m.mutex.Lock() m.mutex.Lock()
defer m.mutex.Unlock() defer m.mutex.Unlock()
if _, ok := m.sqlIndex[tableName]; !ok { 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 { if el, ok := m.sqlIndex[tableName][sql]; !ok {
el = m.sqlList.PushBack(newSqlNode(tableName, sql)) 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() el.Value.(*sqlNode).lastVisit = time.Now()
} }
m.store.Put(sql, ids) m.store.Put(sql, ids)
if m.sqlList.Len() > m.Max { if m.sqlList.Len() > m.MaxElementSize {
e := m.sqlList.Front() e := m.sqlList.Front()
node := e.Value.(*sqlNode) node := e.Value.(*sqlNode)
m.delIds(node.tbName, node.sql) 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() m.mutex.Lock()
defer m.mutex.Unlock() defer m.mutex.Unlock()
var el *list.Element 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) m.store.Put(genId(tableName, id), obj)
if m.idList.Len() > m.Max { if m.idList.Len() > m.MaxElementSize {
e := m.idList.Front() e := m.idList.Front()
node := e.Value.(*idNode) node := e.Value.(*idNode)
m.delBean(node.tbName, node.id) m.delBean(node.tbName, node.id)
@ -331,7 +241,7 @@ func (m *LRUCacher) DelIds(tableName, sql string) {
m.delIds(tableName, sql) m.delIds(tableName, sql)
} }
func (m *LRUCacher) delBean(tableName string, id int64) { func (m *LRUCacher) delBean(tableName string, id string) {
tid := genId(tableName, id) tid := genId(tableName, id)
if el, ok := m.idIndex[tableName][id]; ok { if el, ok := m.idIndex[tableName][id]; ok {
delete(m.idIndex[tableName], id) delete(m.idIndex[tableName], id)
@ -341,55 +251,36 @@ func (m *LRUCacher) delBean(tableName string, id int64) {
m.store.Del(tid) m.store.Del(tid)
} }
func (m *LRUCacher) DelBean(tableName string, id int64) { func (m *LRUCacher) DelBean(tableName string, id string) {
m.mutex.Lock() m.mutex.Lock()
defer m.mutex.Unlock() defer m.mutex.Unlock()
m.delBean(tableName, id) m.delBean(tableName, id)
} }
func encodeIds(ids []int64) (s string) { type idNode struct {
s = "[" tbName string
for _, id := range ids { id string
s += fmt.Sprintf("%v,", id) lastVisit time.Time
}
s = s[:len(s)-1] + "]"
return
} }
func decodeIds(s string) []int64 { type sqlNode struct {
res := make([]int64, 0) tbName string
if len(s) >= 2 { sql string
ss := strings.Split(s[1:len(s)-1], ",") lastVisit time.Time
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
} }
func genSqlKey(sql string, args interface{}) string { func genSqlKey(sql string, args interface{}) string {
return fmt.Sprintf("%v-%v", sql, args) 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) 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
View File

@ -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}
}

44
memroy_store.go Normal file
View File

@ -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
}

View File

@ -1,85 +1,63 @@
package xorm package xorm
import ( import (
//"crypto/tls"
"database/sql"
"errors" "errors"
"fmt" "fmt"
//"regexp"
"strconv" "strconv"
"strings" "strings"
//"time"
"github.com/go-xorm/core"
) )
// func init() {
// RegisterDialect("mssql", &mssql{})
// }
type mssql struct { type mssql struct {
base core.Base
quoteFilter Filter
} }
type odbcParser struct { func (db *mssql) Init(uri *core.Uri, drivername, dataSourceName string) error {
return db.Base.Init(db, uri, drivername, dataSourceName)
} }
func (p *odbcParser) parse(driverName, dataSourceName string) (*uri, error) { func (db *mssql) SqlType(c *core.Column) string {
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 {
var res string var res string
switch t := c.SQLType.Name; t { switch t := c.SQLType.Name; t {
case Bool: case core.Bool:
res = TinyInt res = core.TinyInt
case Serial: case core.Serial:
c.IsAutoIncrement = true c.IsAutoIncrement = true
c.IsPrimaryKey = true c.IsPrimaryKey = true
c.Nullable = false c.Nullable = false
res = Int res = core.Int
case BigSerial: case core.BigSerial:
c.IsAutoIncrement = true c.IsAutoIncrement = true
c.IsPrimaryKey = true c.IsPrimaryKey = true
c.Nullable = false c.Nullable = false
res = BigInt res = core.BigInt
case Bytea, Blob, Binary, TinyBlob, MediumBlob, LongBlob: case core.Bytea, core.Blob, core.Binary, core.TinyBlob, core.MediumBlob, core.LongBlob:
res = VarBinary res = core.VarBinary
if c.Length == 0 { if c.Length == 0 {
c.Length = 50 c.Length = 50
} }
case TimeStamp: case core.TimeStamp:
res = DateTime res = core.DateTime
case TimeStampz: case core.TimeStampz:
res = "DATETIMEOFFSET" res = "DATETIMEOFFSET"
c.Length = 7 c.Length = 7
case MediumInt: case core.MediumInt:
res = Int res = core.Int
case MediumText, TinyText, LongText: case core.MediumText, core.TinyText, core.LongText:
res = Text res = core.Text
case Double: case core.Double:
res = Real res = core.Real
default: default:
res = t res = t
} }
if res == Int { if res == core.Int {
return Int return core.Int
} }
var hasLen1 bool = (c.Length > 0) var hasLen1 bool = (c.Length > 0)
@ -140,56 +118,53 @@ func (db *mssql) TableCheckSql(tableName string) (string, []interface{}) {
return sql, args 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{}{} args := []interface{}{}
s := `select a.name as name, b.name as ctype,a.max_length,a.precision,a.scale 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 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 + `')` where a.object_id=object_id('` + tableName + `')`
cnn, err := sql.Open(db.driverName, db.dataSourceName) cnn, err := core.Open(db.DriverName(), db.DataSourceName())
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
defer cnn.Close() defer cnn.Close()
res, err := query(cnn, s, args...)
rows, err := cnn.Query(s, args...)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
cols := make(map[string]*Column) cols := make(map[string]*core.Column)
colSeq := make([]string, 0) colSeq := make([]string, 0)
for _, record := range res { for rows.Next() {
col := new(Column) var name, ctype, precision, scale string
col.Indexes = make(map[string]bool) var maxLen int
for name, content := range record { err = rows.Scan(&name, &ctype, &maxLen, &precision, &scale)
switch name { if err != nil {
case "name": return nil, nil, err
}
col.Name = strings.Trim(string(content), "` ") col := new(core.Column)
case "ctype": col.Indexes = make(map[string]bool)
ct := strings.ToUpper(string(content)) col.Length = maxLen
col.Name = strings.Trim(name, "` ")
ct := strings.ToUpper(ctype)
switch ct { switch ct {
case "DATETIMEOFFSET": case "DATETIMEOFFSET":
col.SQLType = SQLType{TimeStampz, 0, 0} col.SQLType = core.SQLType{core.TimeStampz, 0, 0}
case "NVARCHAR": case "NVARCHAR":
col.SQLType = SQLType{Varchar, 0, 0} col.SQLType = core.SQLType{core.Varchar, 0, 0}
case "IMAGE": case "IMAGE":
col.SQLType = SQLType{VarBinary, 0, 0} col.SQLType = core.SQLType{core.VarBinary, 0, 0}
default: default:
if _, ok := sqlTypes[ct]; ok { if _, ok := core.SqlTypes[ct]; ok {
col.SQLType = SQLType{ct, 0, 0} col.SQLType = core.SQLType{ct, 0, 0}
} else { } else {
return nil, nil, errors.New(fmt.Sprintf("unknow colType %v for %v - %v", return nil, nil, errors.New(fmt.Sprintf("unknow colType %v for %v - %v",
ct, tableName, col.Name)) 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
}
}
if col.SQLType.IsText() { if col.SQLType.IsText() {
if col.Default != "" { if col.Default != "" {
col.Default = "'" + col.Default + "'" col.Default = "'" + col.Default + "'"
@ -205,34 +180,34 @@ where a.object_id=object_id('` + tableName + `')`
return colSeq, cols, nil return colSeq, cols, nil
} }
func (db *mssql) GetTables() ([]*Table, error) { func (db *mssql) GetTables() ([]*core.Table, error) {
args := []interface{}{} args := []interface{}{}
s := `select name from sysobjects where xtype ='U'` s := `select name from sysobjects where xtype ='U'`
cnn, err := sql.Open(db.driverName, db.dataSourceName) cnn, err := core.Open(db.DriverName(), db.DataSourceName())
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer cnn.Close() defer cnn.Close()
res, err := query(cnn, s, args...) rows, err := cnn.Query(s, args...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
tables := make([]*Table, 0) tables := make([]*core.Table, 0)
for _, record := range res { for rows.Next() {
table := new(Table) table := core.NewEmptyTable()
for name, content := range record { var name string
switch name { err = rows.Scan(&name)
case "name": if err != nil {
table.Name = strings.Trim(string(content), "` ") return nil, err
}
} }
table.Name = strings.Trim(name, "` ")
tables = append(tables, table) tables = append(tables, table)
} }
return tables, nil 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} args := []interface{}{tableName}
s := `SELECT s := `SELECT
IXS.NAME AS [INDEX_NAME], IXS.NAME AS [INDEX_NAME],
@ -248,48 +223,47 @@ INNER JOIN SYS.COLUMNS C ON IXS.OBJECT_ID=C.OBJECT_ID
AND IXCS.COLUMN_ID=C.COLUMN_ID AND IXCS.COLUMN_ID=C.COLUMN_ID
WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =?
` `
cnn, err := sql.Open(db.driverName, db.dataSourceName) cnn, err := core.Open(db.DriverName(), db.DataSourceName())
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer cnn.Close() defer cnn.Close()
res, err := query(cnn, s, args...) rows, err := cnn.Query(s, args...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
indexes := make(map[string]*Index, 0) indexes := make(map[string]*core.Index, 0)
for _, record := range res { for rows.Next() {
var indexType int var indexType int
var indexName, colName string var indexName, colName, isUnique string
for name, content := range record {
switch name { err = rows.Scan(&indexName, &colName, &isUnique, nil)
case "IS_UNIQUE": if err != nil {
i, err := strconv.ParseBool(string(content)) return nil, err
}
i, err := strconv.ParseBool(isUnique)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if i { if i {
indexType = UniqueType indexType = core.UniqueType
} else { } else {
indexType = IndexType indexType = core.IndexType
}
case "INDEX_NAME":
indexName = string(content)
case "COLUMN_NAME":
colName = strings.Trim(string(content), "` ")
}
} }
colName = strings.Trim(colName, "` ")
if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
indexName = indexName[5+len(tableName) : len(indexName)] indexName = indexName[5+len(tableName) : len(indexName)]
} }
var index *Index var index *core.Index
var ok bool var ok bool
if index, ok = indexes[indexName]; !ok { if index, ok = indexes[indexName]; !ok {
index = new(Index) index = new(core.Index)
index.Type = indexType index.Type = indexType
index.Name = indexName index.Name = indexName
indexes[indexName] = index indexes[indexName] = index
@ -298,3 +272,41 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =?
} }
return indexes, nil 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{}}
}

View File

@ -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)
}

View File

@ -4,17 +4,19 @@ import (
"errors" "errors"
"strings" "strings"
"time" "time"
"github.com/go-xorm/core"
) )
type mymysql struct { // func init() {
mysql // core.RegisterDriver("mymysql", &mymysqlDriver{})
// }
type mymysqlDriver struct {
} }
type mymysqlParser struct { func (p *mymysqlDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) {
} db := &core.Uri{DbType: core.MYSQL}
func (p *mymysqlParser) parse(driverName, dataSourceName string) (*uri, error) {
db := &uri{dbType: MYSQL}
pd := strings.SplitN(dataSourceName, "*", 2) pd := strings.SplitN(dataSourceName, "*", 2)
if len(pd) == 2 { if len(pd) == 2 {
@ -23,9 +25,9 @@ func (p *mymysqlParser) parse(driverName, dataSourceName string) (*uri, error) {
if len(p) != 2 { if len(p) != 2 {
return nil, errors.New("Wrong protocol part of URI") return nil, errors.New("Wrong protocol part of URI")
} }
db.proto = p[0] db.Proto = p[0]
options := strings.Split(p[1], ",") options := strings.Split(p[1], ",")
db.raddr = options[0] db.Raddr = options[0]
for _, o := range options[1:] { for _, o := range options[1:] {
kv := strings.SplitN(o, "=", 2) kv := strings.SplitN(o, "=", 2)
var k, v string var k, v string
@ -36,13 +38,13 @@ func (p *mymysqlParser) parse(driverName, dataSourceName string) (*uri, error) {
} }
switch k { switch k {
case "laddr": case "laddr":
db.laddr = v db.Laddr = v
case "timeout": case "timeout":
to, err := time.ParseDuration(v) to, err := time.ParseDuration(v)
if err != nil { if err != nil {
return nil, err return nil, err
} }
db.timeout = to db.Timeout = to
default: default:
return nil, errors.New("Unknown option: " + k) return nil, errors.New("Unknown option: " + k)
} }
@ -55,13 +57,9 @@ func (p *mymysqlParser) parse(driverName, dataSourceName string) (*uri, error) {
if len(dup) != 3 { if len(dup) != 3 {
return nil, errors.New("Wrong database part of URI") return nil, errors.New("Wrong database part of URI")
} }
db.dbName = dup[0] db.DbName = dup[0]
db.user = dup[1] db.User = dup[1]
db.passwd = dup[2] db.Passwd = dup[2]
return db, nil return db, nil
} }
func (db *mymysql) Init(drivername, uri string) error {
return db.mysql.base.init(&mymysqlParser{}, drivername, uri)
}

View File

@ -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
View File

@ -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&paramN=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
}

280
mysql_dialect.go Normal file
View File

@ -0,0 +1,280 @@
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(uri *core.Uri, drivername, dataSourceName string) error {
return db.Base.Init(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` = ?"
cnn, err := core.Open(db.DriverName(), db.DataSourceName())
if err != nil {
return nil, nil, err
}
defer cnn.Close()
rows, err := cnn.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`=?"
cnn, err := core.Open(db.DriverName(), db.DataSourceName())
if err != nil {
return nil, err
}
defer cnn.Close()
rows, err := cnn.Query(s, args...)
if err != nil {
return nil, err
}
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` = ?"
cnn, err := core.Open(db.DriverName(), db.DataSourceName())
if err != nil {
return nil, err
}
defer cnn.Close()
rows, err := cnn.Query(s, args...)
if err != nil {
return nil, err
}
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{}}
}

50
mysql_driver.go Normal file
View File

@ -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&paramN=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
}

View File

@ -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)
}

37
oci8_driver.go Normal file
View File

@ -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
}

34
odbc_driver.go Normal file
View File

@ -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
View File

@ -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
}

254
oracle_dialect.go Normal file
View File

@ -0,0 +1,254 @@
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(uri *core.Uri, drivername, dataSourceName string) error {
return db.Base.Init(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"
cnn, err := core.Open(db.DriverName(), db.DataSourceName())
if err != nil {
return nil, nil, err
}
defer cnn.Close()
rows, err := cnn.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"
cnn, err := core.Open(db.DriverName(), db.DataSourceName())
if err != nil {
return nil, err
}
defer cnn.Close()
rows, err := cnn.Query(s, args...)
if err != nil {
return nil, err
}
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"
cnn, err := core.Open(db.DriverName(), db.DataSourceName())
if err != nil {
return nil, err
}
defer cnn.Close()
rows, err := cnn.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
View File

@ -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
}

View File

@ -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
}

281
postgres_dialect.go Normal file
View File

@ -0,0 +1,281 @@
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(uri *core.Uri, drivername, dataSourceName string) error {
return db.Base.Init(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"
cnn, err := core.Open(db.DriverName(), db.DataSourceName())
if err != nil {
return nil, nil, err
}
defer cnn.Close()
rows, err := cnn.Query(s, args...)
if err != nil {
return nil, nil, err
}
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'"
cnn, err := core.Open(db.DriverName(), db.DataSourceName())
if err != nil {
return nil, err
}
defer cnn.Close()
rows, err := cnn.Query(s, args...)
if err != nil {
return nil, err
}
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"
cnn, err := core.Open(db.DriverName(), db.DataSourceName())
if err != nil {
return nil, err
}
defer cnn.Close()
rows, err := cnn.Query(s, args...)
if err != nil {
return nil, err
}
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}}
}

View File

@ -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)
}

59
pq_driver.go Normal file
View File

@ -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
View File

@ -4,14 +4,16 @@ import (
"database/sql" "database/sql"
"fmt" "fmt"
"reflect" "reflect"
"github.com/go-xorm/core"
) )
type Rows struct { type Rows struct {
NoTypeCheck bool NoTypeCheck bool
session *Session session *Session
stmt *sql.Stmt stmt *core.Stmt
rows *sql.Rows rows *core.Rows
fields []string fields []string
fieldsCount int fieldsCount int
beanType reflect.Type beanType reflect.Type
@ -30,24 +32,23 @@ func newRows(session *Session, bean interface{}) (*Rows, error) {
defer rows.session.Statement.Init() defer rows.session.Statement.Init()
var sql string var sqlStr string
var args []interface{} var args []interface{}
rows.session.Statement.RefTable = rows.session.Engine.autoMap(bean) rows.session.Statement.RefTable = rows.session.Engine.autoMap(bean)
if rows.session.Statement.RawSQL == "" { if rows.session.Statement.RawSQL == "" {
sql, args = rows.session.Statement.genGetSql(bean) sqlStr, args = rows.session.Statement.genGetSql(bean)
} else { } else {
sql = rows.session.Statement.RawSQL sqlStr = rows.session.Statement.RawSQL
args = rows.session.Statement.RawParams args = rows.session.Statement.RawParams
} }
for _, filter := range rows.session.Engine.Filters { for _, filter := range rows.session.Engine.dialect.Filters() {
sql = filter.Do(sql, session) sqlStr = filter.Do(sqlStr, session.Engine.dialect, rows.session.Statement.RefTable)
} }
rows.session.Engine.LogSQL(sql) rows.session.Engine.logSQL(sqlStr, args)
rows.session.Engine.LogSQL(args)
rows.stmt, err = rows.session.Db.Prepare(sql) rows.stmt, err = rows.session.Db.Prepare(sqlStr)
if err != nil { if err != nil {
rows.lastError = err rows.lastError = err
defer rows.Close() defer rows.Close()

File diff suppressed because it is too large Load Diff

View File

@ -1,46 +1,44 @@
package xorm package xorm
import ( import (
"database/sql"
"strings" "strings"
"github.com/go-xorm/core"
) )
// func init() {
// RegisterDialect("sqlite3", &sqlite3{})
// }
type sqlite3 struct { type sqlite3 struct {
base core.Base
} }
type sqlite3Parser struct { func (db *sqlite3) Init(uri *core.Uri, drivername, dataSourceName string) error {
return db.Base.Init(db, uri, drivername, dataSourceName)
} }
func (p *sqlite3Parser) parse(driverName, dataSourceName string) (*uri, error) { func (db *sqlite3) SqlType(c *core.Column) string {
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 {
switch t := c.SQLType.Name; t { switch t := c.SQLType.Name; t {
case Date, DateTime, TimeStamp, Time: case core.Date, core.DateTime, core.TimeStamp, core.Time:
return Numeric return core.Numeric
case TimeStampz: case core.TimeStampz:
return Text return core.Text
case Char, Varchar, TinyText, Text, MediumText, LongText: case core.Char, core.Varchar, core.TinyText, core.Text, core.MediumText, core.LongText:
return Text return core.Text
case Bit, TinyInt, SmallInt, MediumInt, Int, Integer, BigInt, Bool: case core.Bit, core.TinyInt, core.SmallInt, core.MediumInt, core.Int, core.Integer, core.BigInt, core.Bool:
return Integer return core.Integer
case Float, Double, Real: case core.Float, core.Double, core.Real:
return Real return core.Real
case Decimal, Numeric: case core.Decimal, core.Numeric:
return Numeric return core.Numeric
case TinyBlob, Blob, MediumBlob, LongBlob, Bytea, Binary, VarBinary: case core.TinyBlob, core.Blob, core.MediumBlob, core.LongBlob, core.Bytea, core.Binary, core.VarBinary:
return Blob return core.Blob
case Serial, BigSerial: case core.Serial, core.BigSerial:
c.IsPrimaryKey = true c.IsPrimaryKey = true
c.IsAutoIncrement = true c.IsAutoIncrement = true
c.Nullable = false c.Nullable = false
return Integer return core.Integer
default: default:
return t return t
} }
@ -86,36 +84,37 @@ func (db *sqlite3) ColumnCheckSql(tableName, colName string) (string, []interfac
return sql, args 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} args := []interface{}{tableName}
s := "SELECT sql FROM sqlite_master WHERE type='table' and name = ?" s := "SELECT sql FROM sqlite_master WHERE type='table' and name = ?"
cnn, err := sql.Open(db.driverName, db.dataSourceName) cnn, err := core.Open(db.DriverName(), db.DataSourceName())
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
defer cnn.Close() defer cnn.Close()
res, err := query(cnn, s, args...)
rows, err := cnn.Query(s, args...)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
defer rows.Close()
var sql string var name string
for _, record := range res { for rows.Next() {
for name, content := range record { err = rows.Scan(&name)
if name == "sql" { if err != nil {
sql = string(content) return nil, nil, err
}
} }
} }
nStart := strings.Index(sql, "(") nStart := strings.Index(name, "(")
nEnd := strings.Index(sql, ")") nEnd := strings.Index(name, ")")
colCreates := strings.Split(sql[nStart+1:nEnd], ",") colCreates := strings.Split(name[nStart+1:nEnd], ",")
cols := make(map[string]*Column) cols := make(map[string]*core.Column)
colSeq := make([]string, 0) colSeq := make([]string, 0)
for _, colStr := range colCreates { for _, colStr := range colCreates {
fields := strings.Fields(strings.TrimSpace(colStr)) fields := strings.Fields(strings.TrimSpace(colStr))
col := new(Column) col := new(core.Column)
col.Indexes = make(map[string]bool) col.Indexes = make(map[string]bool)
col.Nullable = true col.Nullable = true
for idx, field := range fields { for idx, field := range fields {
@ -123,7 +122,7 @@ func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*Column, e
col.Name = strings.Trim(field, "`[] ") col.Name = strings.Trim(field, "`[] ")
continue continue
} else if idx == 1 { } else if idx == 1 {
col.SQLType = SQLType{field, 0, 0} col.SQLType = core.SQLType{field, 0, 0}
} }
switch field { switch field {
case "PRIMARY": case "PRIMARY":
@ -144,28 +143,27 @@ func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*Column, e
return colSeq, cols, nil return colSeq, cols, nil
} }
func (db *sqlite3) GetTables() ([]*Table, error) { func (db *sqlite3) GetTables() ([]*core.Table, error) {
args := []interface{}{} args := []interface{}{}
s := "SELECT name FROM sqlite_master WHERE type='table'" s := "SELECT name FROM sqlite_master WHERE type='table'"
cnn, err := sql.Open(db.driverName, db.dataSourceName) cnn, err := core.Open(db.DriverName(), db.DataSourceName())
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer cnn.Close() defer cnn.Close()
res, err := query(cnn, s, args...) rows, err := cnn.Query(s, args...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer rows.Close()
tables := make([]*Table, 0) tables := make([]*core.Table, 0)
for _, record := range res { for rows.Next() {
table := new(Table) table := core.NewEmptyTable()
for name, content := range record { err = rows.Scan(&table.Name)
switch name { if err != nil {
case "name": return nil, err
table.Name = string(content)
}
} }
if table.Name == "sqlite_sequence" { if table.Name == "sqlite_sequence" {
continue continue
@ -175,28 +173,33 @@ func (db *sqlite3) GetTables() ([]*Table, error) {
return tables, nil 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} args := []interface{}{tableName}
s := "SELECT sql FROM sqlite_master WHERE type='index' and tbl_name = ?" s := "SELECT sql FROM sqlite_master WHERE type='index' and tbl_name = ?"
cnn, err := sql.Open(db.driverName, db.dataSourceName) cnn, err := core.Open(db.DriverName(), db.DataSourceName())
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer cnn.Close() defer cnn.Close()
res, err := query(cnn, s, args...) rows, err := cnn.Query(s, args...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer rows.Close()
indexes := make(map[string]*Index, 0) indexes := make(map[string]*core.Index, 0)
for _, record := range res { for rows.Next() {
index := new(Index) var sql string
sql := string(record["sql"]) err = rows.Scan(&sql)
if err != nil {
return nil, err
}
if sql == "" { if sql == "" {
continue continue
} }
index := new(core.Index)
nNStart := strings.Index(sql, "INDEX") nNStart := strings.Index(sql, "INDEX")
nNEnd := strings.Index(sql, "ON") nNEnd := strings.Index(sql, "ON")
if nNStart == -1 || nNEnd == -1 { if nNStart == -1 || nNEnd == -1 {
@ -212,9 +215,9 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*Index, error) {
} }
if strings.HasPrefix(sql, "CREATE UNIQUE INDEX") { if strings.HasPrefix(sql, "CREATE UNIQUE INDEX") {
index.Type = UniqueType index.Type = core.UniqueType
} else { } else {
index.Type = IndexType index.Type = core.IndexType
} }
nStart := strings.Index(sql, "(") nStart := strings.Index(sql, "(")
@ -230,3 +233,7 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*Index, error) {
return indexes, nil return indexes, nil
} }
func (db *sqlite3) Filters() []core.Filter {
return []core.Filter{&core.IdFilter{}}
}

16
sqlite3_driver.go Normal file
View File

@ -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
}

View File

@ -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)
}

View File

@ -1,23 +1,28 @@
package xorm package xorm
import ( import (
//"bytes" "encoding/json"
"fmt" "fmt"
"reflect" "reflect"
//"strconv"
"encoding/json"
"strings" "strings"
"time" "time"
"github.com/go-xorm/core"
) )
type inParam struct {
colName string
args []interface{}
}
// statement save all the sql info for executing SQL // statement save all the sql info for executing SQL
type Statement struct { type Statement struct {
RefTable *Table RefTable *core.Table
Engine *Engine Engine *Engine
Start int Start int
LimitN int LimitN int
WhereStr string WhereStr string
IdParam *PK IdParam *core.PK
Params []interface{} Params []interface{}
OrderStr string OrderStr string
JoinStr string JoinStr string
@ -42,7 +47,8 @@ type Statement struct {
allUseBool bool allUseBool bool
checkVersion bool checkVersion bool
mustColumnMap map[string]bool mustColumnMap map[string]bool
inColumns map[string][]interface{} inColumns map[string]*inParam
incColumns map[string]interface{}
} }
// init // init
@ -66,13 +72,14 @@ func (statement *Statement) Init() {
statement.RawSQL = "" statement.RawSQL = ""
statement.RawParams = make([]interface{}, 0) statement.RawParams = make([]interface{}, 0)
statement.BeanArgs = make([]interface{}, 0) statement.BeanArgs = make([]interface{}, 0)
statement.UseCache = statement.Engine.UseCache statement.UseCache = true
statement.UseAutoTime = true statement.UseAutoTime = true
statement.IsDistinct = false statement.IsDistinct = false
statement.allUseBool = false statement.allUseBool = false
statement.mustColumnMap = make(map[string]bool) statement.mustColumnMap = make(map[string]bool)
statement.checkVersion = true statement.checkVersion = true
statement.inColumns = make(map[string][]interface{}) statement.inColumns = make(map[string]*inParam)
statement.incColumns = make(map[string]interface{}, 0)
} }
// add the raw sql statement // add the raw sql statement
@ -206,7 +213,7 @@ func (statement *Statement) Table(tableNameOrBean interface{}) *Statement {
if col.SQLType.IsText() { if col.SQLType.IsText() {
bytes, err := json.Marshal(fieldValue.Interface()) bytes, err := json.Marshal(fieldValue.Interface())
if err != nil { if err != nil {
engine.LogSQL(err) engine.LogError(err)
continue continue
} }
val = string(bytes) val = string(bytes)
@ -223,7 +230,7 @@ func (statement *Statement) Table(tableNameOrBean interface{}) *Statement {
} else { } else {
bytes, err = json.Marshal(fieldValue.Interface()) bytes, err = json.Marshal(fieldValue.Interface())
if err != nil { if err != nil {
engine.LogSQL(err) engine.LogError(err)
continue continue
} }
val = bytes val = bytes
@ -240,14 +247,14 @@ func (statement *Statement) Table(tableNameOrBean interface{}) *Statement {
}*/ }*/
// Auto generating conditions according a struct // 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, includeVersion bool, includeUpdated bool, includeNil bool,
includeAutoIncr bool, allUseBool bool, useAllCols bool, includeAutoIncr bool, allUseBool bool, useAllCols bool,
mustColumnMap map[string]bool) ([]string, []interface{}) { mustColumnMap map[string]bool) ([]string, []interface{}) {
colNames := make([]string, 0) colNames := make([]string, 0)
var args = make([]interface{}, 0) var args = make([]interface{}, 0)
for _, col := range table.Columns { for _, col := range table.Columns() {
if !includeVersion && col.IsVersion { if !includeVersion && col.IsVersion {
continue continue
} }
@ -259,10 +266,16 @@ func buildConditions(engine *Engine, table *Table, bean interface{},
} }
// //
//fmt.Println(engine.dialect.DBType(), Text) //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 continue
} }
fieldValue := col.ValueOf(bean) fieldValuePtr, err := col.ValueOf(bean)
if err != nil {
engine.LogError(err)
continue
}
fieldValue := *fieldValuePtr
fieldType := reflect.TypeOf(fieldValue.Interface()) fieldType := reflect.TypeOf(fieldValue.Interface())
requiredField := useAllCols requiredField := useAllCols
@ -361,7 +374,7 @@ func buildConditions(engine *Engine, table *Table, bean interface{},
if col.SQLType.IsText() { if col.SQLType.IsText() {
bytes, err := json.Marshal(fieldValue.Interface()) bytes, err := json.Marshal(fieldValue.Interface())
if err != nil { if err != nil {
engine.LogSQL(err) engine.LogError(err)
continue continue
} }
val = string(bytes) val = string(bytes)
@ -378,7 +391,7 @@ func buildConditions(engine *Engine, table *Table, bean interface{},
} else { } else {
bytes, err = json.Marshal(fieldValue.Interface()) bytes, err = json.Marshal(fieldValue.Interface())
if err != nil { if err != nil {
engine.LogSQL(err) engine.LogError(err)
continue continue
} }
val = bytes val = bytes
@ -409,24 +422,28 @@ func (statement *Statement) TableName() string {
return "" return ""
} }
var (
ptrPkType = reflect.TypeOf(&core.PK{})
pkType = reflect.TypeOf(core.PK{})
)
// Generate "where id = ? " statment or for composite key "where key1 = ? and key2 = ?" // Generate "where id = ? " statment or for composite key "where key1 = ? and key2 = ?"
func (statement *Statement) Id(id interface{}) *Statement { func (statement *Statement) Id(id interface{}) *Statement {
idValue := reflect.ValueOf(id) idValue := reflect.ValueOf(id)
idType := reflect.TypeOf(idValue.Interface()) idType := reflect.TypeOf(idValue.Interface())
switch idType { switch idType {
case reflect.TypeOf(&PK{}): case ptrPkType:
if pkPtr, ok := (id).(*PK); ok { if pkPtr, ok := (id).(*core.PK); ok {
statement.IdParam = pkPtr statement.IdParam = pkPtr
} }
case reflect.TypeOf(PK{}): case pkType:
if pk, ok := (id).(PK); ok { if pk, ok := (id).(core.PK); ok {
statement.IdParam = &pk statement.IdParam = &pk
} }
default: default:
// TODO treat as int primitve for now, need to handle type check // TODO treat as int primitve for now, need to handle type check
statement.IdParam = &PK{id} statement.IdParam = &core.PK{id}
// !nashtsai! REVIEW although it will be user's mistake if called Id() twice with // !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 // different value and Id should be PK's field name, however, at this stage probably
@ -446,13 +463,29 @@ func (statement *Statement) Id(id interface{}) *Statement {
return statement 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.incColumns[k] = arg[0]
} else {
statement.incColumns[k] = 1
}
return statement
}
// Generate "Update ... Set column = column + arg" statment
func (statement *Statement) getInc() map[string]interface{} {
return statement.incColumns
}
// Generate "Where column IN (?) " statment // Generate "Where column IN (?) " statment
func (statement *Statement) In(column string, args ...interface{}) *Statement { func (statement *Statement) In(column string, args ...interface{}) *Statement {
k := strings.ToLower(column) k := strings.ToLower(column)
if params, ok := statement.inColumns[k]; ok { if _, ok := statement.inColumns[k]; ok {
statement.inColumns[k] = append(params, args...) statement.inColumns[k].args = append(statement.inColumns[k].args, args...)
} else { } else {
statement.inColumns[k] = args statement.inColumns[k] = &inParam{column, args}
} }
return statement return statement
} }
@ -464,10 +497,11 @@ func (statement *Statement) genInSql() (string, []interface{}) {
inStrs := make([]string, 0, len(statement.inColumns)) inStrs := make([]string, 0, len(statement.inColumns))
args := make([]interface{}, 0) args := make([]interface{}, 0)
for column, params := range statement.inColumns { for _, params := range statement.inColumns {
inStrs = append(inStrs, fmt.Sprintf("(%v IN (%v))", statement.Engine.Quote(column), inStrs = append(inStrs, fmt.Sprintf("(%v IN (%v))",
strings.Join(makeArray("?", len(params)), ","))) statement.Engine.Quote(params.colName),
args = append(args, params...) strings.Join(makeArray("?", len(params.args)), ",")))
args = append(args, params.args...)
} }
if len(statement.inColumns) == 1 { if len(statement.inColumns) == 1 {
@ -609,13 +643,13 @@ func (statement *Statement) Having(conditions string) *Statement {
func (statement *Statement) genColumnStr() string { func (statement *Statement) genColumnStr() string {
table := statement.RefTable table := statement.RefTable
colNames := make([]string, 0) colNames := make([]string, 0)
for _, col := range table.Columns { for _, col := range table.Columns() {
if statement.OmitStr != "" { if statement.OmitStr != "" {
if _, ok := statement.columnMap[strings.ToLower(col.Name)]; ok { if _, ok := statement.columnMap[strings.ToLower(col.Name)]; ok {
continue continue
} }
} }
if col.MapType == ONLYTODB { if col.MapType == core.ONLYTODB {
continue continue
} }
colNames = append(colNames, statement.Engine.Quote(statement.TableName())+"."+statement.Engine.Quote(col.Name)) colNames = append(colNames, statement.Engine.Quote(statement.TableName())+"."+statement.Engine.Quote(col.Name))
@ -624,54 +658,8 @@ func (statement *Statement) genColumnStr() string {
} }
func (statement *Statement) genCreateTableSQL() string { func (statement *Statement) genCreateTableSQL() string {
var sql string return statement.Engine.dialect.CreateTableSql(statement.RefTable, statement.AltTableName,
if statement.Engine.dialect.DBType() == MSSQL { statement.StoreEngine, statement.Charset)
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
} }
func indexName(tableName, idxName string) string { func indexName(tableName, idxName string) string {
@ -683,7 +671,7 @@ func (s *Statement) genIndexSQL() []string {
tbName := s.TableName() tbName := s.TableName()
quote := s.Engine.Quote quote := s.Engine.Quote
for idxName, index := range s.RefTable.Indexes { 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)), sql := fmt.Sprintf("CREATE INDEX %v ON %v (%v);", quote(indexName(tbName, idxName)),
quote(tbName), quote(strings.Join(index.Cols, quote(",")))) quote(tbName), quote(strings.Join(index.Cols, quote(","))))
sqls = append(sqls, sql) sqls = append(sqls, sql)
@ -701,7 +689,7 @@ func (s *Statement) genUniqueSQL() []string {
tbName := s.TableName() tbName := s.TableName()
quote := s.Engine.Quote quote := s.Engine.Quote
for idxName, unique := range s.RefTable.Indexes { for idxName, unique := range s.RefTable.Indexes {
if unique.Type == UniqueType { if unique.Type == core.UniqueType {
sql := fmt.Sprintf("CREATE UNIQUE INDEX %v ON %v (%v);", quote(uniqueName(tbName, idxName)), sql := fmt.Sprintf("CREATE UNIQUE INDEX %v ON %v (%v);", quote(uniqueName(tbName, idxName)),
quote(tbName), quote(strings.Join(unique.Cols, quote(",")))) quote(tbName), quote(strings.Join(unique.Cols, quote(","))))
sqls = append(sqls, sql) sqls = append(sqls, sql)
@ -714,9 +702,9 @@ func (s *Statement) genDelIndexSQL() []string {
var sqls []string = make([]string, 0) var sqls []string = make([]string, 0)
for idxName, index := range s.RefTable.Indexes { for idxName, index := range s.RefTable.Indexes {
var rIdxName string var rIdxName string
if index.Type == UniqueType { if index.Type == core.UniqueType {
rIdxName = uniqueName(s.TableName(), idxName) rIdxName = uniqueName(s.TableName(), idxName)
} else if index.Type == IndexType { } else if index.Type == core.IndexType {
rIdxName = indexName(s.TableName(), idxName) rIdxName = indexName(s.TableName(), idxName)
} }
sql := fmt.Sprintf("DROP INDEX %v", s.Engine.Quote(rIdxName)) sql := fmt.Sprintf("DROP INDEX %v", s.Engine.Quote(rIdxName))
@ -751,7 +739,7 @@ func (statement *Statement) genGetSql(bean interface{}) (string, []interface{})
return statement.genSelectSql(columnStr), append(statement.Params, statement.BeanArgs...) 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 quote := s.Engine.Quote
sql := fmt.Sprintf("ALTER TABLE %v ADD COLUMN %v;", quote(s.TableName()), sql := fmt.Sprintf("ALTER TABLE %v ADD COLUMN %v;", quote(s.TableName()),
col.String(s.Engine.dialect)) col.String(s.Engine.dialect))
@ -824,13 +812,14 @@ func (statement *Statement) genSelectSql(columnStr string) (a string) {
if statement.OrderStr != "" { if statement.OrderStr != "" {
a = fmt.Sprintf("%v ORDER BY %v", a, 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 { if statement.Start > 0 {
a = fmt.Sprintf("%v LIMIT %v OFFSET %v", a, statement.LimitN, statement.Start) a = fmt.Sprintf("%v LIMIT %v OFFSET %v", a, statement.LimitN, statement.Start)
} else if statement.LimitN > 0 { } else if statement.LimitN > 0 {
a = fmt.Sprintf("%v LIMIT %v", a, statement.LimitN) a = fmt.Sprintf("%v LIMIT %v", a, statement.LimitN)
} }
} else { } else {
//TODO: for mssql, should handler limit.
/*SELECT * FROM ( /*SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (ORDER BY id desc) as row FROM "userinfo" SELECT *, ROW_NUMBER() OVER (ORDER BY id desc) as row FROM "userinfo"
) a WHERE row > [start] and row <= [start+limit] order by id desc*/ ) a WHERE row > [start] and row <= [start+limit] order by id desc*/
@ -840,30 +829,12 @@ func (statement *Statement) genSelectSql(columnStr string) (a string) {
} }
func (statement *Statement) processIdParam() { func (statement *Statement) processIdParam() {
if statement.IdParam != nil { if statement.IdParam != nil {
i := 0 for i, col := range statement.RefTable.PKColumns() {
colCnt := len(statement.RefTable.ColumnsSeq) if i < len(*(statement.IdParam)) {
for _, elem := range *(statement.IdParam) { statement.And(fmt.Sprintf("%v=?", statement.Engine.Quote(col.Name)), (*(statement.IdParam))[i])
for ; i < colCnt; i++ { } else {
colName := statement.RefTable.ColumnsSeq[i] statement.And(fmt.Sprintf("%v=?", statement.Engine.Quote(col.Name)), "")
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), "")
} }
} }
} }

484
table.go
View File

@ -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)
}

View File

@ -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;

113
xorm.go
View File

@ -1,8 +1,10 @@
package xorm package xorm
import ( import (
"database/sql"
"errors" "errors"
"fmt" "fmt"
"github.com/go-xorm/core"
"os" "os"
"reflect" "reflect"
"runtime" "runtime"
@ -10,9 +12,46 @@ import (
) )
const ( 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) { func close(engine *Engine) {
engine.Close() engine.Close()
} }
@ -20,49 +59,53 @@ func close(engine *Engine) {
// new a db manager according to the parameter. Currently support four // new a db manager according to the parameter. Currently support four
// drivers // drivers
func NewEngine(driverName string, dataSourceName string) (*Engine, error) { func NewEngine(driverName string, dataSourceName string) (*Engine, error) {
engine := &Engine{ regDrvsNDialects()
DriverName: driverName, driver := core.QueryDriver(driverName)
DataSourceName: dataSourceName, if driver == nil {
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 {
return nil, errors.New(fmt.Sprintf("Unsupported driver name: %v", driverName)) 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 { if err != nil {
return nil, err return nil, err
} }
engine.Tables = make(map[reflect.Type]*Table) dialect := core.QueryDialect(uri.DbType)
engine.mutex = &sync.RWMutex{} if dialect == nil {
engine.TagIdentifier = "xorm" return nil, errors.New(fmt.Sprintf("Unsupported dialect type: %v", uri.DbType))
}
engine.Filters = append(engine.Filters, &IdFilter{}) err = dialect.Init(uri, driverName, dataSourceName)
engine.Logger = os.Stdout if err != nil {
return nil, err
}
//engine.Pool = NewSimpleConnectPool() db, err := core.OpenDialect(dialect)
//engine.Pool = NewNoneConnectPool() 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),
TimeZone: "Local",
}
engine.SetMapper(core.NewCacheMapper(new(core.SnakeMapper)))
//engine.Filters = dialect.Filters()
//engine.Cacher = NewLRUCacher() //engine.Cacher = NewLRUCacher()
err = engine.SetPool(NewSysConnectPool()) //err = engine.SetPool(NewSysConnectPool())
runtime.SetFinalizer(engine, close) runtime.SetFinalizer(engine, close)
return engine, err return engine, err
} }
// clone an engine
func (engine *Engine) Clone() (*Engine, error) {
return NewEngine(engine.dialect.DriverName(), engine.dialect.DataSourceName())
}

View File

@ -1,2 +0,0 @@
[deps]
github.com/lunny/xorm=../

View File

@ -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/)

View File

@ -1,65 +0,0 @@
package main
import (
//"fmt"
"github.com/lunny/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
}

View File

@ -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
}

View File

@ -1,263 +0,0 @@
package main
import (
"errors"
"fmt"
"github.com/lunny/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 ""
}
}

View File

@ -1,51 +0,0 @@
package main
import (
"github.com/lunny/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:]
}
}

View File

@ -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/lib/pq"
"github.com/lunny/xorm"
_ "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
})
}

View File

@ -1,147 +0,0 @@
package main
import (
"fmt"
"github.com/lunny/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$ ")
}
}

View File

@ -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}}

View File

@ -1 +0,0 @@
lang=c++

View File

@ -1 +0,0 @@
lang=go

View File

@ -1,14 +0,0 @@
package {{.Model}}
import (
{{range .Imports}}"{{.}}"{{end}}
)
{{range .Tables}}
type {{Mapper .Name}} struct {
{{$table := .}}
{{range .Columns}} {{Mapper .Name}} {{Type .}}
{{end}}
}
{{end}}

View File

@ -1,3 +0,0 @@
lang=go
genJson=0
prefix=cos_

View File

@ -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}}

View File

@ -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)
}