fix migrate failed and org dashboard failed on MSSQL database (#1448)

tokarchuk/v1.17
Lunny Xiao 8 years ago committed by GitHub
parent cf6699fb4f
commit 5acfc7c4bc
  1. 25
      models/org.go
  2. 33
      vendor/github.com/go-xorm/xorm/README.md
  3. 29
      vendor/github.com/go-xorm/xorm/README_CN.md
  4. 1
      vendor/github.com/go-xorm/xorm/VERSION
  5. 37
      vendor/github.com/go-xorm/xorm/convert.go
  6. 31
      vendor/github.com/go-xorm/xorm/dialect_mssql.go
  7. 92
      vendor/github.com/go-xorm/xorm/dialect_mysql.go
  8. 60
      vendor/github.com/go-xorm/xorm/dialect_oracle.go
  9. 111
      vendor/github.com/go-xorm/xorm/dialect_postgres.go
  10. 10
      vendor/github.com/go-xorm/xorm/dialect_sqlite3.go
  11. 43
      vendor/github.com/go-xorm/xorm/doc.go
  12. 305
      vendor/github.com/go-xorm/xorm/engine.go
  13. 42
      vendor/github.com/go-xorm/xorm/goracle_driver.go
  14. 14
      vendor/github.com/go-xorm/xorm/helpers.go
  15. 65
      vendor/github.com/go-xorm/xorm/mymysql_driver.go
  16. 50
      vendor/github.com/go-xorm/xorm/mysql_driver.go
  17. 37
      vendor/github.com/go-xorm/xorm/oci8_driver.go
  18. 34
      vendor/github.com/go-xorm/xorm/odbc_driver.go
  19. 119
      vendor/github.com/go-xorm/xorm/pq_driver.go
  20. 19
      vendor/github.com/go-xorm/xorm/rows.go
  21. 956
      vendor/github.com/go-xorm/xorm/session.go
  22. 84
      vendor/github.com/go-xorm/xorm/session_cols.go
  23. 70
      vendor/github.com/go-xorm/xorm/session_cond.go
  24. 673
      vendor/github.com/go-xorm/xorm/session_convert.go
  25. 102
      vendor/github.com/go-xorm/xorm/session_find.go
  26. 52
      vendor/github.com/go-xorm/xorm/session_get.go
  27. 38
      vendor/github.com/go-xorm/xorm/session_insert.go
  28. 11
      vendor/github.com/go-xorm/xorm/session_raw.go
  29. 5
      vendor/github.com/go-xorm/xorm/session_schema.go
  30. 2
      vendor/github.com/go-xorm/xorm/session_sum.go
  31. 67
      vendor/github.com/go-xorm/xorm/session_update.go
  32. 20
      vendor/github.com/go-xorm/xorm/sqlite3_driver.go
  33. 94
      vendor/github.com/go-xorm/xorm/statement.go
  34. 281
      vendor/github.com/go-xorm/xorm/tag.go
  35. 3
      vendor/github.com/go-xorm/xorm/xorm.go
  36. 6
      vendor/vendor.json

@ -670,13 +670,30 @@ func (env *accessibleReposEnv) Repos(page, pageSize int) ([]*Repository, error)
Find(&repos) Find(&repos)
} }
func (env *accessibleReposEnv) MirrorRepos() ([]*Repository, error) { func (env *accessibleReposEnv) MirrorRepoIDs() ([]int64, error) {
repos := make([]*Repository, 0, 10) repoIDs := make([]int64, 0, 10)
return repos, x. return repoIDs, x.
Select("`repository`.*"). Table("repository").
Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id AND `repository`.is_mirror=?", true). Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id AND `repository`.is_mirror=?", true).
Where(env.cond()). Where(env.cond()).
GroupBy("`repository`.id"). GroupBy("`repository`.id").
OrderBy("updated_unix DESC"). OrderBy("updated_unix DESC").
Cols("`repository`.id").
Find(&repoIDs)
}
func (env *accessibleReposEnv) MirrorRepos() ([]*Repository, error) {
repoIDs, err := env.MirrorRepoIDs()
if err != nil {
return nil, fmt.Errorf("MirrorRepoIDs: %v", err)
}
repos := make([]*Repository, 0, len(repoIDs))
if len(repoIDs) <= 0 {
return repos, nil
}
return repos, x.
In("`repository`.id", repoIDs).
Find(&repos) Find(&repos)
} }

@ -46,12 +46,15 @@ Drivers for Go's sql package which currently support database/sql includes:
* MsSql: [github.com/denisenkom/go-mssqldb](https://github.com/denisenkom/go-mssqldb) * MsSql: [github.com/denisenkom/go-mssqldb](https://github.com/denisenkom/go-mssqldb)
* MsSql: [github.com/lunny/godbc](https://github.com/lunny/godbc)
* Oracle: [github.com/mattn/go-oci8](https://github.com/mattn/go-oci8) (experiment) * Oracle: [github.com/mattn/go-oci8](https://github.com/mattn/go-oci8) (experiment)
# Changelog # Changelog
* **v0.6.2**
* refactor tag parse methods
* add Scan features to Get
* add QueryString method
* **v0.6.0** * **v0.6.0**
* remove support for ql * remove support for ql
* add query condition builder support via [github.com/go-xorm/builder](https://github.com/go-xorm/builder), so `Where`, `And`, `Or` * add query condition builder support via [github.com/go-xorm/builder](https://github.com/go-xorm/builder), so `Where`, `And`, `Or`
@ -79,12 +82,6 @@ methods can use `builder.Cond` as parameter
# Installation # Installation
If you have [gopm](https://github.com/gpmgo/gopm) installed,
gopm get github.com/go-xorm/xorm
Or
go get github.com/go-xorm/xorm go get github.com/go-xorm/xorm
# Documents # Documents
@ -119,19 +116,21 @@ type User struct {
err := engine.Sync2(new(User)) err := engine.Sync2(new(User))
``` ```
* Query a SQL string, the returned results is []map[string][]byte * `Query` runs a SQL string, the returned results is `[]map[string][]byte`, `QueryString` returns `[]map[string]string`.
```Go ```Go
results, err := engine.Query("select * from user") results, err := engine.Query("select * from user")
results, err := engine.QueryString("select * from user")
``` ```
* Execute a SQL string, the returned results * `Execute` runs a SQL string, it returns `affetcted` and `error`
```Go ```Go
affected, err := engine.Exec("update user set age = ? where name = ?", age, name) affected, err := engine.Exec("update user set age = ? where name = ?", age, name)
``` ```
* Insert one or multiple records to database * `Insert` one or multiple records to database
```Go ```Go
affected, err := engine.Insert(&user) affected, err := engine.Insert(&user)
@ -153,6 +152,18 @@ has, err := engine.Get(&user)
// SELECT * FROM user LIMIT 1 // SELECT * FROM user LIMIT 1
has, err := engine.Where("name = ?", name).Desc("id").Get(&user) has, err := engine.Where("name = ?", name).Desc("id").Get(&user)
// SELECT * FROM user WHERE name = ? ORDER BY id DESC LIMIT 1 // SELECT * FROM user WHERE name = ? ORDER BY id DESC LIMIT 1
var name string
has, err := engine.Where("id = ?", id).Cols("name").Get(&name)
// SELECT name FROM user WHERE id = ?
var id int64
has, err := engine.Where("name = ?", name).Cols("id").Get(&id)
// SELECT id FROM user WHERE name = ?
var valuesMap = make(map[string]string)
has, err := engine.Where("id = ?", id).Get(&valuesMap)
// SELECT * FROM user WHERE id = ?
var valuesSlice = make([]interface{}, len(cols))
has, err := engine.Where("id = ?", id).Cols(cols...).Get(&valuesSlice)
// SELECT col1, col2, col3 FROM user WHERE id = ?
``` ```
* Query multiple records from database, also you can use join and extends * Query multiple records from database, also you can use join and extends

@ -54,6 +54,11 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作
## 更新日志 ## 更新日志
* **v0.6.2**
* 重构Tag解析方式
* Get方法新增类似Sacn的特性
* 新增 QueryString 方法
* **v0.6.0** * **v0.6.0**
* 去除对 ql 的支持 * 去除对 ql 的支持
* 新增条件查询分析器 [github.com/go-xorm/builder](https://github.com/go-xorm/builder), 从因此 `Where, And, Or` 函数 * 新增条件查询分析器 [github.com/go-xorm/builder](https://github.com/go-xorm/builder), 从因此 `Where, And, Or` 函数
@ -81,12 +86,6 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作
## 安装 ## 安装
推荐使用 [gopm](https://github.com/gpmgo/gopm) 进行安装:
gopm get github.com/go-xorm/xorm
或者您也可以使用go工具进行安装:
go get github.com/go-xorm/xorm go get github.com/go-xorm/xorm
## 文档 ## 文档
@ -121,13 +120,15 @@ type User struct {
err := engine.Sync2(new(User)) err := engine.Sync2(new(User))
``` ```
* 最原始的也支持SQL语句查询,返回的结果类型为 []map[string][]byte * `Query` 最原始的也支持SQL语句查询,返回的结果类型为 []map[string][]byte。`QueryString` 返回 []map[string]string
```Go ```Go
results, err := engine.Query("select * from user") results, err := engine.Query("select * from user")
results, err := engine.QueryString("select * from user")
``` ```
* 执行一个SQL语句 * `Exec` 执行一个SQL语句
```Go ```Go
affected, err := engine.Exec("update user set age = ? where name = ?", age, name) affected, err := engine.Exec("update user set age = ? where name = ?", age, name)
@ -155,6 +156,18 @@ has, err := engine.Get(&user)
// SELECT * FROM user LIMIT 1 // SELECT * FROM user LIMIT 1
has, err := engine.Where("name = ?", name).Desc("id").Get(&user) has, err := engine.Where("name = ?", name).Desc("id").Get(&user)
// SELECT * FROM user WHERE name = ? ORDER BY id DESC LIMIT 1 // SELECT * FROM user WHERE name = ? ORDER BY id DESC LIMIT 1
var name string
has, err := engine.Where("id = ?", id).Cols("name").Get(&name)
// SELECT name FROM user WHERE id = ?
var id int64
has, err := engine.Where("name = ?", name).Cols("id").Get(&id)
// SELECT id FROM user WHERE name = ?
var valuesMap = make(map[string]string)
has, err := engine.Where("id = ?", id).Get(&valuesMap)
// SELECT * FROM user WHERE id = ?
var valuesSlice = make([]interface{}, len(cols))
has, err := engine.Where("id = ?", id).Cols(cols...).Get(&valuesSlice)
// SELECT col1, col2, col3 FROM user WHERE id = ?
``` ```
* 查询多条记录,当然可以使用Join和extends来组合使用 * 查询多条记录,当然可以使用Join和extends来组合使用

@ -1 +0,0 @@
xorm v0.6.0.1022

@ -247,3 +247,40 @@ func convertAssign(dest, src interface{}) error {
return fmt.Errorf("unsupported Scan, storing driver.Value type %T into type %T", src, dest) return fmt.Errorf("unsupported Scan, storing driver.Value type %T into type %T", src, dest)
} }
func asKind(vv reflect.Value, tp reflect.Type) (interface{}, error) {
switch tp.Kind() {
case reflect.Int64:
return vv.Int(), nil
case reflect.Int:
return int(vv.Int()), nil
case reflect.Int32:
return int32(vv.Int()), nil
case reflect.Int16:
return int16(vv.Int()), nil
case reflect.Int8:
return int8(vv.Int()), nil
case reflect.Uint64:
return vv.Uint(), nil
case reflect.Uint:
return uint(vv.Uint()), nil
case reflect.Uint32:
return uint32(vv.Uint()), nil
case reflect.Uint16:
return uint16(vv.Uint()), nil
case reflect.Uint8:
return uint8(vv.Uint()), nil
case reflect.String:
return vv.String(), nil
case reflect.Slice:
if tp.Elem().Kind() == reflect.Uint8 {
v, err := strconv.ParseInt(string(vv.Interface().([]byte)), 10, 64)
if err != nil {
return nil, err
}
return v, nil
}
}
return nil, fmt.Errorf("unsupported primary key type: %v, %v", tp, vv)
}

@ -5,6 +5,7 @@
package xorm package xorm
import ( import (
"errors"
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
@ -215,9 +216,9 @@ func (db *mssql) SqlType(c *core.Column) string {
switch t := c.SQLType.Name; t { switch t := c.SQLType.Name; t {
case core.Bool: case core.Bool:
res = core.TinyInt res = core.TinyInt
if c.Default == "true" { if strings.EqualFold(c.Default, "true") {
c.Default = "1" c.Default = "1"
} else if c.Default == "false" { } else {
c.Default = "0" c.Default = "0"
} }
case core.Serial: case core.Serial:
@ -467,9 +468,10 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =?
} }
colName = strings.Trim(colName, "` ") colName = strings.Trim(colName, "` ")
var isRegular bool
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):] indexName = indexName[5+len(tableName):]
isRegular = true
} }
var index *core.Index var index *core.Index
@ -478,6 +480,7 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =?
index = new(core.Index) index = new(core.Index)
index.Type = indexType index.Type = indexType
index.Name = indexName index.Name = indexName
index.IsRegular = isRegular
indexes[indexName] = index indexes[indexName] = index
} }
index.AddColumn(colName) index.AddColumn(colName)
@ -526,3 +529,25 @@ func (db *mssql) ForUpdateSql(query string) string {
func (db *mssql) Filters() []core.Filter { func (db *mssql) Filters() []core.Filter {
return []core.Filter{&core.IdFilter{}, &core.QuoteFilter{}} return []core.Filter{&core.IdFilter{}, &core.QuoteFilter{}}
} }
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
}

@ -6,7 +6,9 @@ package xorm
import ( import (
"crypto/tls" "crypto/tls"
"errors"
"fmt" "fmt"
"regexp"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -486,3 +488,93 @@ func (db *mysql) GetIndexes(tableName string) (map[string]*core.Index, error) {
func (db *mysql) Filters() []core.Filter { func (db *mysql) Filters() []core.Filter {
return []core.Filter{&core.IdFilter{}} return []core.Filter{&core.IdFilter{}}
} }
type mymysqlDriver struct {
}
func (p *mymysqlDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) {
db := &core.Uri{DbType: core.MYSQL}
pd := strings.SplitN(dataSourceName, "*", 2)
if len(pd) == 2 {
// Parse protocol part of URI
p := strings.SplitN(pd[0], ":", 2)
if len(p) != 2 {
return nil, errors.New("Wrong protocol part of URI")
}
db.Proto = p[0]
options := strings.Split(p[1], ",")
db.Raddr = options[0]
for _, o := range options[1:] {
kv := strings.SplitN(o, "=", 2)
var k, v string
if len(kv) == 2 {
k, v = kv[0], kv[1]
} else {
k, v = o, "true"
}
switch k {
case "laddr":
db.Laddr = v
case "timeout":
to, err := time.ParseDuration(v)
if err != nil {
return nil, err
}
db.Timeout = to
default:
return nil, errors.New("Unknown option: " + k)
}
}
// Remove protocol part
pd = pd[1:]
}
// Parse database part of URI
dup := strings.SplitN(pd[0], "/", 3)
if len(dup) != 3 {
return nil, errors.New("Wrong database part of URI")
}
db.DbName = dup[0]
db.User = dup[1]
db.Passwd = dup[2]
return db, nil
}
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
}

@ -5,7 +5,9 @@
package xorm package xorm
import ( import (
"errors"
"fmt" "fmt"
"regexp"
"strconv" "strconv"
"strings" "strings"
@ -822,6 +824,12 @@ func (db *oracle) GetIndexes(tableName string) (map[string]*core.Index, error) {
indexName = strings.Trim(indexName, `" `) indexName = strings.Trim(indexName, `" `)
var isRegular bool
if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
indexName = indexName[5+len(tableName):]
isRegular = true
}
if uniqueness == "UNIQUE" { if uniqueness == "UNIQUE" {
indexType = core.UniqueType indexType = core.UniqueType
} else { } else {
@ -834,6 +842,7 @@ func (db *oracle) GetIndexes(tableName string) (map[string]*core.Index, error) {
index = new(core.Index) index = new(core.Index)
index.Type = indexType index.Type = indexType
index.Name = indexName index.Name = indexName
index.IsRegular = isRegular
indexes[indexName] = index indexes[indexName] = index
} }
index.AddColumn(colName) index.AddColumn(colName)
@ -844,3 +853,54 @@ func (db *oracle) GetIndexes(tableName string) (map[string]*core.Index, error) {
func (db *oracle) Filters() []core.Filter { func (db *oracle) Filters() []core.Filter {
return []core.Filter{&core.QuoteFilter{}, &core.SeqFilter{Prefix: ":", Start: 1}, &core.IdFilter{}} return []core.Filter{&core.QuoteFilter{}, &core.SeqFilter{Prefix: ":", Start: 1}, &core.IdFilter{}}
} }
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
}
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
}

@ -5,7 +5,10 @@
package xorm package xorm
import ( import (
"errors"
"fmt" "fmt"
"net/url"
"sort"
"strconv" "strconv"
"strings" "strings"
@ -1075,9 +1078,10 @@ func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error)
} }
cs := strings.Split(indexdef, "(") cs := strings.Split(indexdef, "(")
colNames = strings.Split(cs[1][0:len(cs[1])-1], ",") colNames = strings.Split(cs[1][0:len(cs[1])-1], ",")
var isRegular bool
if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
newIdxName := indexName[5+len(tableName):] newIdxName := indexName[5+len(tableName):]
isRegular = true
if newIdxName != "" { if newIdxName != "" {
indexName = newIdxName indexName = newIdxName
} }
@ -1087,6 +1091,7 @@ func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error)
for _, colName := range colNames { for _, colName := range colNames {
index.Cols = append(index.Cols, strings.Trim(colName, `" `)) index.Cols = append(index.Cols, strings.Trim(colName, `" `))
} }
index.IsRegular = isRegular
indexes[index.Name] = index indexes[index.Name] = index
} }
return indexes, nil return indexes, nil
@ -1095,3 +1100,107 @@ func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error)
func (db *postgres) Filters() []core.Filter { func (db *postgres) Filters() []core.Filter {
return []core.Filter{&core.IdFilter{}, &core.QuoteFilter{}, &core.SeqFilter{Prefix: "$", Start: 1}} return []core.Filter{&core.IdFilter{}, &core.QuoteFilter{}, &core.SeqFilter{Prefix: "$", Start: 1}}
} }
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 parseURL(connstr string) (string, error) {
u, err := url.Parse(connstr)
if err != nil {
return "", err
}
if u.Scheme != "postgresql" && u.Scheme != "postgres" {
return "", fmt.Errorf("invalid connection protocol: %s", u.Scheme)
}
var kvs []string
escaper := strings.NewReplacer(` `, `\ `, `'`, `\'`, `\`, `\\`)
accrue := func(k, v string) {
if v != "" {
kvs = append(kvs, k+"="+escaper.Replace(v))
}
}
if u.User != nil {
v := u.User.Username()
accrue("user", v)
v, _ = u.User.Password()
accrue("password", v)
}
i := strings.Index(u.Host, ":")
if i < 0 {
accrue("host", u.Host)
} else {
accrue("host", u.Host[:i])
accrue("port", u.Host[i+1:])
}
if u.Path != "" {
accrue("dbname", u.Path[1:])
}
q := u.Query()
for k := range q {
accrue(k, q.Get(k))
}
sort.Strings(kvs) // Makes testing easier (not a performance concern)
return strings.Join(kvs, " "), nil
}
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)
var err error
if strings.HasPrefix(dataSourceName, "postgresql://") || strings.HasPrefix(dataSourceName, "postgres://") {
dataSourceName, err = parseURL(dataSourceName)
if err != nil {
return nil, err
}
}
parseOpts(dataSourceName, o)
db.DbName = o.Get("dbname")
if db.DbName == "" {
return nil, errors.New("dbname is empty")
}
/*db.Schema = o.Get("schema")
if len(db.Schema) == 0 {
db.Schema = "public"
}*/
return db, nil
}

@ -405,8 +405,10 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*core.Index, error)
} }
indexName := strings.Trim(sql[nNStart+6:nNEnd], "` []") indexName := strings.Trim(sql[nNStart+6:nNEnd], "` []")
var isRegular bool
if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
index.Name = indexName[5+len(tableName):] index.Name = indexName[5+len(tableName):]
isRegular = true
} else { } else {
index.Name = indexName index.Name = indexName
} }
@ -425,6 +427,7 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*core.Index, error)
for _, col := range colIndexes { for _, col := range colIndexes {
index.Cols = append(index.Cols, strings.Trim(col, "` []")) index.Cols = append(index.Cols, strings.Trim(col, "` []"))
} }
index.IsRegular = isRegular
indexes[index.Name] = index indexes[index.Name] = index
} }
@ -434,3 +437,10 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*core.Index, error)
func (db *sqlite3) Filters() []core.Filter { func (db *sqlite3) Filters() []core.Filter {
return []core.Filter{&core.IdFilter{}} return []core.Filter{&core.IdFilter{}}
} }
type sqlite3Driver struct {
}
func (p *sqlite3Driver) Parse(driverName, dataSourceName string) (*core.Uri, error) {
return &core.Uri{DbType: core.SQLITE, DbName: dataSourceName}, nil
}

@ -24,7 +24,7 @@ Generally, one engine for an application is enough. You can set it as package va
Raw Methods Raw Methods
Xorm also support raw sql execution: XORM also support raw SQL execution:
1. query a SQL string, the returned results is []map[string][]byte 1. query a SQL string, the returned results is []map[string][]byte
@ -36,7 +36,7 @@ Xorm also support raw sql execution:
ORM Methods ORM Methods
There are 7 major ORM methods and many helpful methods to use to operate database. There are 8 major ORM methods and many helpful methods to use to operate database.
1. Insert one or multiple records to database 1. Insert one or multiple records to database
@ -58,10 +58,18 @@ There are 7 major ORM methods and many helpful methods to use to operate databas
3. Query multiple records from database 3. Query multiple records from database
sliceOfStructs := new(Struct) var sliceOfStructs []Struct
err := engine.Find(sliceOfStructs) err := engine.Find(&sliceOfStructs)
// SELECT * FROM user // SELECT * FROM user
var mapOfStructs = make(map[int64]Struct)
err := engine.Find(&mapOfStructs)
// SELECT * FROM user
var int64s []int64
err := engine.Table("user").Cols("id").Find(&int64s)
// SELECT id FROM user
4. Query multiple records and record by record handle, there two methods, one is Iterate, 4. Query multiple records and record by record handle, there two methods, one is Iterate,
another is Rows another is Rows
@ -91,20 +99,31 @@ another is Rows
counts, err := engine.Count(&user) counts, err := engine.Count(&user)
// SELECT count(*) AS total FROM user // SELECT count(*) AS total FROM user
8. Sum records
sumFloat64, err := engine.Sum(&user, "id")
// SELECT sum(id) from user
sumFloat64s, err := engine.Sums(&user, "id1", "id2")
// SELECT sum(id1), sum(id2) from user
sumInt64s, err := engine.SumsInt(&user, "id1", "id2")
// SELECT sum(id1), sum(id2) from user
Conditions Conditions
The above 7 methods could use with condition methods chainable. The above 8 methods could use with condition methods chainable.
Attention: the above 7 methods should be the last chainable method. Attention: the above 8 methods should be the last chainable method.
1. Id, In 1. ID, In
engine.Id(1).Get(&user) // for single primary key engine.ID(1).Get(&user) // for single primary key
// SELECT * FROM user WHERE id = 1 // SELECT * FROM user WHERE id = 1
engine.Id(core.PK{1, 2}).Get(&user) // for composite primary keys engine.ID(core.PK{1, 2}).Get(&user) // for composite primary keys
// SELECT * FROM user WHERE id1 = 1 AND id2 = 2 // SELECT * FROM user WHERE id1 = 1 AND id2 = 2
engine.In("id", 1, 2, 3).Find(&users) engine.In("id", 1, 2, 3).Find(&users)
// SELECT * FROM user WHERE id IN (1, 2, 3) // SELECT * FROM user WHERE id IN (1, 2, 3)
engine.In("id", []int{1, 2, 3}) engine.In("id", []int{1, 2, 3}).Find(&users)
// SELECT * FROM user WHERE id IN (1, 2, 3) // SELECT * FROM user WHERE id IN (1, 2, 3)
2. Where, And, Or 2. Where, And, Or
@ -127,10 +146,10 @@ Attention: the above 7 methods should be the last chainable method.
// SELECT TOP 5 * FROM user // for mssql // SELECT TOP 5 * FROM user // for mssql
// SELECT * FROM user LIMIT .. OFFSET 0 //for other databases // SELECT * FROM user LIMIT .. OFFSET 0 //for other databases
5. Sql, let you custom SQL 5. SQL, let you custom SQL
var users []User var users []User
engine.Sql("select * from user").Find(&users) engine.SQL("select * from user").Find(&users)
6. Cols, Omit, Distinct 6. Cols, Omit, Distinct

@ -44,6 +44,8 @@ type Engine struct {
DatabaseTZ *time.Location // The timezone of the database DatabaseTZ *time.Location // The timezone of the database
disableGlobalCache bool disableGlobalCache bool
tagHandlers map[string]tagHandler
} }
// ShowSQL show SQL statement or not on logger if log level is great than INFO // ShowSQL show SQL statement or not on logger if log level is great than INFO
@ -215,10 +217,15 @@ func (engine *Engine) NoCascade() *Session {
} }
// MapCacher Set a table use a special cacher // MapCacher Set a table use a special cacher
func (engine *Engine) MapCacher(bean interface{}, cacher core.Cacher) { func (engine *Engine) MapCacher(bean interface{}, cacher core.Cacher) error {
v := rValue(bean) v := rValue(bean)
tb := engine.autoMapType(v) tb, err := engine.autoMapType(v)
if err != nil {
return err
}
tb.Cacher = cacher tb.Cacher = cacher
return nil
} }
// NewDB provides an interface to operate database directly // NewDB provides an interface to operate database directly
@ -260,9 +267,9 @@ func (engine *Engine) Ping() error {
func (engine *Engine) logSQL(sqlStr string, sqlArgs ...interface{}) { func (engine *Engine) logSQL(sqlStr string, sqlArgs ...interface{}) {
if engine.showSQL && !engine.showExecTime { if engine.showSQL && !engine.showExecTime {
if len(sqlArgs) > 0 { if len(sqlArgs) > 0 {
engine.logger.Infof("[sql] %v [args] %v", sqlStr, sqlArgs) engine.logger.Infof("[SQL] %v %v", sqlStr, sqlArgs)
} else { } else {
engine.logger.Infof("[sql] %v", sqlStr) engine.logger.Infof("[SQL] %v", sqlStr)
} }
} }
} }
@ -273,9 +280,9 @@ func (engine *Engine) logSQLQueryTime(sqlStr string, args []interface{}, executi
stmt, res, err := executionBlock() stmt, res, err := executionBlock()
execDuration := time.Since(b4ExecTime) execDuration := time.Since(b4ExecTime)
if len(args) > 0 { if len(args) > 0 {
engine.logger.Infof("[sql] %s [args] %v - took: %v", sqlStr, args, execDuration) engine.logger.Infof("[SQL] %s %v - took: %v", sqlStr, args, execDuration)
} else { } else {
engine.logger.Infof("[sql] %s - took: %v", sqlStr, execDuration) engine.logger.Infof("[SQL] %s - took: %v", sqlStr, execDuration)
} }
return stmt, res, err return stmt, res, err
} }
@ -774,13 +781,18 @@ func (engine *Engine) Having(conditions string) *Session {
return session.Having(conditions) return session.Having(conditions)
} }
func (engine *Engine) autoMapType(v reflect.Value) *core.Table { func (engine *Engine) autoMapType(v reflect.Value) (*core.Table, error) {
t := v.Type() t := v.Type()
engine.mutex.Lock() engine.mutex.Lock()
defer engine.mutex.Unlock() defer engine.mutex.Unlock()
table, ok := engine.Tables[t] table, ok := engine.Tables[t]
if !ok { if !ok {
table = engine.mapType(v) var err error
table, err = engine.mapType(v)
if err != nil {
return nil, err
}
engine.Tables[t] = table engine.Tables[t] = table
if engine.Cacher != nil { if engine.Cacher != nil {
if v.CanAddr() { if v.CanAddr() {
@ -790,12 +802,11 @@ func (engine *Engine) autoMapType(v reflect.Value) *core.Table {
} }
} }
} }
return table return table, nil
} }
// GobRegister register one struct to gob for cache use // GobRegister register one struct to gob for cache use
func (engine *Engine) GobRegister(v interface{}) *Engine { func (engine *Engine) GobRegister(v interface{}) *Engine {
//fmt.Printf("Type: %[1]T => Data: %[1]#v\n", v)
gob.Register(v) gob.Register(v)
return engine return engine
} }
@ -806,10 +817,19 @@ type Table struct {
Name string Name string
} }
// IsValid if table is valid
func (t *Table) IsValid() bool {
return t.Table != nil && len(t.Name) > 0
}
// TableInfo get table info according to bean's content // TableInfo get table info according to bean's content
func (engine *Engine) TableInfo(bean interface{}) *Table { func (engine *Engine) TableInfo(bean interface{}) *Table {
v := rValue(bean) v := rValue(bean)
return &Table{engine.autoMapType(v), engine.tbName(v)} tb, err := engine.autoMapType(v)
if err != nil {
engine.logger.Error(err)
}
return &Table{tb, engine.tbName(v)}
} }
func addIndex(indexName string, table *core.Table, col *core.Column, indexType int) { func addIndex(indexName string, table *core.Table, col *core.Column, indexType int) {
@ -842,7 +862,7 @@ var (
tpTableName = reflect.TypeOf((*TableName)(nil)).Elem() tpTableName = reflect.TypeOf((*TableName)(nil)).Elem()
) )
func (engine *Engine) mapType(v reflect.Value) *core.Table { func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) {
t := v.Type() t := v.Type()
table := engine.newTable() table := engine.newTable()
if tb, ok := v.Interface().(TableName); ok { if tb, ok := v.Interface().(TableName); ok {
@ -861,7 +881,6 @@ func (engine *Engine) mapType(v reflect.Value) *core.Table {
table.Type = t table.Type = t
var idFieldColName string var idFieldColName string
var err error
var hasCacheTag, hasNoCacheTag bool var hasCacheTag, hasNoCacheTag bool
for i := 0; i < t.NumField(); i++ { for i := 0; i < t.NumField(); i++ {
@ -881,186 +900,94 @@ func (engine *Engine) mapType(v reflect.Value) *core.Table {
if tags[0] == "-" { if tags[0] == "-" {
continue continue
} }
var ctx = tagContext{
table: table,
col: col,
fieldValue: fieldValue,
indexNames: make(map[string]int),
engine: engine,
}
if strings.ToUpper(tags[0]) == "EXTENDS" { if strings.ToUpper(tags[0]) == "EXTENDS" {
switch fieldValue.Kind() { if err := ExtendsTagHandler(&ctx); err != nil {
case reflect.Ptr: return nil, err
f := fieldValue.Type().Elem()
if f.Kind() == reflect.Struct {
fieldPtr := fieldValue
fieldValue = fieldValue.Elem()
if !fieldValue.IsValid() || fieldPtr.IsNil() {
fieldValue = reflect.New(f).Elem()
}
}
fallthrough
case reflect.Struct:
parentTable := engine.mapType(fieldValue)
for _, col := range parentTable.Columns() {
col.FieldName = fmt.Sprintf("%v.%v", t.Field(i).Name, col.FieldName)
table.AddColumn(col)
for indexName, indexType := range col.Indexes {
addIndex(indexName, table, col, indexType)
}
}
continue
default:
//TODO: warning
} }
continue
} }
indexNames := make(map[string]int)
var isIndex, isUnique bool
var preKey string
for j, key := range tags { for j, key := range tags {
if ctx.ignoreNext {
ctx.ignoreNext = false
continue
}
k := strings.ToUpper(key) k := strings.ToUpper(key)
switch { ctx.tagName = k
case k == "<-":
col.MapType = core.ONLYFROMDB
case k == "->":
col.MapType = core.ONLYTODB
case k == "PK":
col.IsPrimaryKey = true
col.Nullable = false
case k == "NULL":
if j == 0 {
col.Nullable = true
} else {
col.Nullable = (strings.ToUpper(tags[j-1]) != "NOT")
}
// TODO: for postgres how add autoincr?
/*case strings.HasPrefix(k, "AUTOINCR(") && strings.HasSuffix(k, ")"):
col.IsAutoIncrement = true
autoStart := k[len("AUTOINCR")+1 : len(k)-1] pStart := strings.Index(k, "(")
autoStartInt, err := strconv.Atoi(autoStart) if pStart == 0 {
if err != nil { return nil, errors.New("( could not be the first charactor")
engine.LogError(err)
} }
col.AutoIncrStart = autoStartInt*/ if pStart > -1 {
case k == "AUTOINCR": if !strings.HasSuffix(k, ")") {
col.IsAutoIncrement = true return nil, errors.New("cannot match ) charactor")
//col.AutoIncrStart = 1
case k == "DEFAULT":
col.Default = tags[j+1]
case k == "CREATED":
col.IsCreated = true
case k == "VERSION":
col.IsVersion = true
col.Default = "1"
case k == "UTC":
col.TimeZone = time.UTC
case k == "LOCAL":
col.TimeZone = time.Local
case strings.HasPrefix(k, "LOCALE(") && strings.HasSuffix(k, ")"):
location := k[len("LOCALE")+1 : len(k)-1]
col.TimeZone, err = time.LoadLocation(location)
if err != nil {
engine.logger.Error(err)
}
case k == "UPDATED":
col.IsUpdated = true
case k == "DELETED":
col.IsDeleted = true
case strings.HasPrefix(k, "INDEX(") && strings.HasSuffix(k, ")"):
indexName := k[len("INDEX")+1 : len(k)-1]
indexNames[indexName] = core.IndexType
case k == "INDEX":
isIndex = true
case strings.HasPrefix(k, "UNIQUE(") && strings.HasSuffix(k, ")"):
indexName := k[len("UNIQUE")+1 : len(k)-1]
indexNames[indexName] = core.UniqueType
case k == "UNIQUE":
isUnique = true
case k == "NOTNULL":
col.Nullable = false
case k == "CACHE":
if !hasCacheTag {
hasCacheTag = true
} }
case k == "NOCACHE":
if !hasNoCacheTag { ctx.tagName = k[:pStart]
hasNoCacheTag = true ctx.params = strings.Split(k[pStart+1:len(k)-1], ",")
}
if j > 0 {
ctx.preTag = strings.ToUpper(tags[j-1])
}
if j < len(tags)-1 {
ctx.nextTag = strings.ToUpper(tags[j+1])
} else {
ctx.nextTag = ""
}
if h, ok := engine.tagHandlers[ctx.tagName]; ok {
if err := h(&ctx); err != nil {
return nil, err
} }
case k == "NOT": } else {
default: if strings.HasPrefix(key, "'") && strings.HasSuffix(key, "'") {
if strings.HasPrefix(k, "'") && strings.HasSuffix(k, "'") { col.Name = key[1 : len(key)-1]
if preKey != "DEFAULT" {
col.Name = key[1 : len(key)-1]
}
} else if strings.Contains(k, "(") && strings.HasSuffix(k, ")") {
fs := strings.Split(k, "(")
if _, ok := core.SqlTypes[fs[0]]; !ok {
preKey = k
continue
}
col.SQLType = core.SQLType{Name: fs[0]}
if fs[0] == core.Enum && fs[1][0] == '\'' { //enum
options := strings.Split(fs[1][0:len(fs[1])-1], ",")
col.EnumOptions = make(map[string]int)
for k, v := range options {
v = strings.TrimSpace(v)
v = strings.Trim(v, "'")
col.EnumOptions[v] = k
}
} else if fs[0] == core.Set && fs[1][0] == '\'' { //set
options := strings.Split(fs[1][0:len(fs[1])-1], ",")
col.SetOptions = make(map[string]int)
for k, v := range options {
v = strings.TrimSpace(v)
v = strings.Trim(v, "'")
col.SetOptions[v] = k
}
} else {
fs2 := strings.Split(fs[1][0:len(fs[1])-1], ",")
if len(fs2) == 2 {
col.Length, err = strconv.Atoi(fs2[0])
if err != nil {
engine.logger.Error(err)
}
col.Length2, err = strconv.Atoi(fs2[1])
if err != nil {
engine.logger.Error(err)
}
} else if len(fs2) == 1 {
col.Length, err = strconv.Atoi(fs2[0])
if err != nil {
engine.logger.Error(err)
}
}
}
} else { } else {
if _, ok := core.SqlTypes[k]; ok { col.Name = key
col.SQLType = core.SQLType{Name: k}
} else if key != col.Default {
col.Name = key
}
} }
engine.dialect.SqlType(col)
} }
preKey = k
if ctx.hasCacheTag {
hasCacheTag = true
}
if ctx.hasNoCacheTag {
hasNoCacheTag = true
}
} }
if col.SQLType.Name == "" { if col.SQLType.Name == "" {
col.SQLType = core.Type2SQLType(fieldType) col.SQLType = core.Type2SQLType(fieldType)
} }
engine.dialect.SqlType(col)
if col.Length == 0 { if col.Length == 0 {
col.Length = col.SQLType.DefaultLength col.Length = col.SQLType.DefaultLength
} }
if col.Length2 == 0 { if col.Length2 == 0 {
col.Length2 = col.SQLType.DefaultLength2 col.Length2 = col.SQLType.DefaultLength2
} }
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 ctx.isUnique {
indexNames[col.Name] = core.UniqueType ctx.indexNames[col.Name] = core.UniqueType
} else if isIndex { } else if ctx.isIndex {
indexNames[col.Name] = core.IndexType ctx.indexNames[col.Name] = core.IndexType
} }
for indexName, indexType := range indexNames { for indexName, indexType := range ctx.indexNames {
addIndex(indexName, table, col, indexType) addIndex(indexName, table, col, indexType)
} }
} }
@ -1114,7 +1041,7 @@ func (engine *Engine) mapType(v reflect.Value) *core.Table {
table.Cacher = nil table.Cacher = nil
} }
return table return table, nil
} }
// IsTableEmpty if a table has any reocrd // IsTableEmpty if a table has any reocrd
@ -1152,8 +1079,21 @@ func (engine *Engine) IdOfV(rv reflect.Value) core.PK {
// IDOfV get id from one value of struct // IDOfV get id from one value of struct
func (engine *Engine) IDOfV(rv reflect.Value) core.PK { func (engine *Engine) IDOfV(rv reflect.Value) core.PK {
pk, err := engine.idOfV(rv)
if err != nil {
engine.logger.Error(err)
return nil
}
return pk
}
func (engine *Engine) idOfV(rv reflect.Value) (core.PK, error) {
v := reflect.Indirect(rv) v := reflect.Indirect(rv)
table := engine.autoMapType(v) table, err := engine.autoMapType(v)
if err != nil {
return nil, err
}
pk := make([]interface{}, len(table.PrimaryKeys)) pk := make([]interface{}, len(table.PrimaryKeys))
for i, col := range table.PKColumns() { for i, col := range table.PKColumns() {
pkField := v.FieldByName(col.FieldName) pkField := v.FieldByName(col.FieldName)
@ -1166,7 +1106,7 @@ func (engine *Engine) IDOfV(rv reflect.Value) core.PK {
pk[i] = pkField.Uint() pk[i] = pkField.Uint()
} }
} }
return core.PK(pk) return core.PK(pk), nil
} }
// CreateIndexes create indexes // CreateIndexes create indexes
@ -1187,13 +1127,6 @@ func (engine *Engine) getCacher2(table *core.Table) core.Cacher {
return table.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
}
// ClearCacheBean if enabled cache, clear the cache bean // ClearCacheBean if enabled cache, clear the cache bean
func (engine *Engine) ClearCacheBean(bean interface{}, id string) error { func (engine *Engine) ClearCacheBean(bean interface{}, id string) error {
v := rValue(bean) v := rValue(bean)
@ -1202,7 +1135,10 @@ func (engine *Engine) ClearCacheBean(bean interface{}, id string) error {
return errors.New("error params") return errors.New("error params")
} }
tableName := engine.tbName(v) tableName := engine.tbName(v)
table := engine.autoMapType(v) table, err := engine.autoMapType(v)
if err != nil {
return err
}
cacher := table.Cacher cacher := table.Cacher
if cacher == nil { if cacher == nil {
cacher = engine.Cacher cacher = engine.Cacher
@ -1223,7 +1159,11 @@ func (engine *Engine) ClearCache(beans ...interface{}) error {
return errors.New("error params") return errors.New("error params")
} }
tableName := engine.tbName(v) tableName := engine.tbName(v)
table := engine.autoMapType(v) table, err := engine.autoMapType(v)
if err != nil {
return err
}
cacher := table.Cacher cacher := table.Cacher
if cacher == nil { if cacher == nil {
cacher = engine.Cacher cacher = engine.Cacher
@ -1243,7 +1183,11 @@ func (engine *Engine) Sync(beans ...interface{}) error {
for _, bean := range beans { for _, bean := range beans {
v := rValue(bean) v := rValue(bean)
tableName := engine.tbName(v) tableName := engine.tbName(v)
table := engine.autoMapType(v) table, err := engine.autoMapType(v)
fmt.Println(v, table, err)
if err != nil {
return err
}
s := engine.NewSession() s := engine.NewSession()
defer s.Close() defer s.Close()
@ -1426,6 +1370,13 @@ func (engine *Engine) Query(sql string, paramStr ...interface{}) (resultsSlice [
return session.Query(sql, paramStr...) return session.Query(sql, paramStr...)
} }
// QueryString runs a raw sql and return records as []map[string]string
func (engine *Engine) QueryString(sqlStr string, args ...interface{}) ([]map[string]string, error) {
session := engine.NewSession()
defer session.Close()
return session.QueryString(sqlStr, args...)
}
// Insert one or more records // Insert one or more records
func (engine *Engine) Insert(beans ...interface{}) (int64, error) { func (engine *Engine) Insert(beans ...interface{}) (int64, error) {
session := engine.NewSession() session := engine.NewSession()

@ -1,42 +0,0 @@
// Copyright 2015 The Xorm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
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
}

@ -180,6 +180,20 @@ func isStructZero(v reflect.Value) bool {
return true return true
} }
func isArrayValueZero(v reflect.Value) bool {
if !v.IsValid() || v.Len() == 0 {
return true
}
for i := 0; i < v.Len(); i++ {
if !isZero(v.Index(i).Interface()) {
return false
}
}
return true
}
func int64ToIntValue(id int64, tp reflect.Type) reflect.Value { func int64ToIntValue(id int64, tp reflect.Type) reflect.Value {
var v interface{} var v interface{}
switch tp.Kind() { switch tp.Kind() {

@ -1,65 +0,0 @@
// Copyright 2015 The Xorm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xorm
import (
"errors"
"strings"
"time"
"github.com/go-xorm/core"
)
type mymysqlDriver struct {
}
func (p *mymysqlDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) {
db := &core.Uri{DbType: core.MYSQL}
pd := strings.SplitN(dataSourceName, "*", 2)
if len(pd) == 2 {
// Parse protocol part of URI
p := strings.SplitN(pd[0], ":", 2)
if len(p) != 2 {
return nil, errors.New("Wrong protocol part of URI")
}
db.Proto = p[0]
options := strings.Split(p[1], ",")
db.Raddr = options[0]
for _, o := range options[1:] {
kv := strings.SplitN(o, "=", 2)
var k, v string
if len(kv) == 2 {
k, v = kv[0], kv[1]
} else {
k, v = o, "true"
}
switch k {
case "laddr":
db.Laddr = v
case "timeout":
to, err := time.ParseDuration(v)
if err != nil {
return nil, err
}
db.Timeout = to
default:
return nil, errors.New("Unknown option: " + k)
}
}
// Remove protocol part
pd = pd[1:]
}
// Parse database part of URI
dup := strings.SplitN(pd[0], "/", 3)
if len(dup) != 3 {
return nil, errors.New("Wrong database part of URI")
}
db.DbName = dup[0]
db.User = dup[1]
db.Passwd = dup[2]
return db, nil
}

@ -1,50 +0,0 @@
// Copyright 2015 The Xorm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xorm
import (
"regexp"
"strings"
"github.com/go-xorm/core"
)
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
}

@ -1,37 +0,0 @@
// Copyright 2015 The Xorm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xorm
import (
"errors"
"regexp"
"github.com/go-xorm/core"
)
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
}

@ -1,34 +0,0 @@
// Copyright 2015 The Xorm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xorm
import (
"errors"
"strings"
"github.com/go-xorm/core"
)
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
}

@ -1,119 +0,0 @@
// Copyright 2015 The Xorm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xorm
import (
"errors"
"fmt"
"net/url"
"sort"
"strings"
"github.com/go-xorm/core"
)
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 parseURL(connstr string) (string, error) {
u, err := url.Parse(connstr)
if err != nil {
return "", err
}
if u.Scheme != "postgresql" && u.Scheme != "postgres" {
return "", fmt.Errorf("invalid connection protocol: %s", u.Scheme)
}
var kvs []string
escaper := strings.NewReplacer(` `, `\ `, `'`, `\'`, `\`, `\\`)
accrue := func(k, v string) {
if v != "" {
kvs = append(kvs, k+"="+escaper.Replace(v))
}
}
if u.User != nil {
v := u.User.Username()
accrue("user", v)
v, _ = u.User.Password()
accrue("password", v)
}
i := strings.Index(u.Host, ":")
if i < 0 {
accrue("host", u.Host)
} else {
accrue("host", u.Host[:i])
accrue("port", u.Host[i+1:])
}
if u.Path != "" {
accrue("dbname", u.Path[1:])
}
q := u.Query()
for k := range q {
accrue(k, q.Get(k))
}
sort.Strings(kvs) // Makes testing easier (not a performance concern)
return strings.Join(kvs, " "), nil
}
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)
var err error
if strings.HasPrefix(dataSourceName, "postgresql://") || strings.HasPrefix(dataSourceName, "postgres://") {
dataSourceName, err = parseURL(dataSourceName)
if err != nil {
return nil, err
}
}
parseOpts(dataSourceName, o)
db.DbName = o.Get("dbname")
if db.DbName == "" {
return nil, errors.New("dbname is empty")
}
/*db.Schema = o.Get("schema")
if len(db.Schema) == 0 {
db.Schema = "public"
}*/
return db, nil
}

@ -16,13 +16,12 @@ import (
type Rows struct { type Rows struct {
NoTypeCheck bool NoTypeCheck bool
session *Session session *Session
stmt *core.Stmt stmt *core.Stmt
rows *core.Rows rows *core.Rows
fields []string fields []string
fieldsCount int beanType reflect.Type
beanType reflect.Type lastError error
lastError error
} }
func newRows(session *Session, bean interface{}) (*Rows, error) { func newRows(session *Session, bean interface{}) (*Rows, error) {
@ -82,7 +81,6 @@ func newRows(session *Session, bean interface{}) (*Rows, error) {
rows.Close() rows.Close()
return nil, err return nil, err
} }
rows.fieldsCount = len(rows.fields)
return rows, nil return rows, nil
} }
@ -114,7 +112,10 @@ func (rows *Rows) Scan(bean interface{}) error {
return fmt.Errorf("scan arg is incompatible type to [%v]", rows.beanType) return fmt.Errorf("scan arg is incompatible type to [%v]", rows.beanType)
} }
_, err := rows.session.row2Bean(rows.rows, rows.fields, rows.fieldsCount, bean) dataStruct := rValue(bean)
rows.session.Statement.setRefValue(dataStruct)
_, err := rows.session.row2Bean(rows.rows, rows.fields, len(rows.fields), bean, &dataStruct, rows.session.Statement.RefTable)
return err return err
} }

File diff suppressed because it is too large Load Diff

@ -0,0 +1,84 @@
// Copyright 2017 The Xorm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xorm
// Incr provides a query string like "count = count + 1"
func (session *Session) Incr(column string, arg ...interface{}) *Session {
session.Statement.Incr(column, arg...)
return session
}
// Decr provides a query string like "count = count - 1"
func (session *Session) Decr(column string, arg ...interface{}) *Session {
session.Statement.Decr(column, arg...)
return session
}
// SetExpr provides a query string like "column = {expression}"
func (session *Session) SetExpr(column string, expression string) *Session {
session.Statement.SetExpr(column, expression)
return session
}
// Select provides some columns to special
func (session *Session) Select(str string) *Session {
session.Statement.Select(str)
return session
}
// Cols provides some columns to special
func (session *Session) Cols(columns ...string) *Session {
session.Statement.Cols(columns...)
return session
}
// AllCols ask all columns
func (session *Session) AllCols() *Session {
session.Statement.AllCols()
return session
}
// MustCols specify some columns must use even if they are empty
func (session *Session) MustCols(columns ...string) *Session {
session.Statement.MustCols(columns...)
return session
}
// UseBool automatically retrieve condition according struct, but
// if struct has bool field, it will ignore them. So use UseBool
// to tell system to do not ignore them.
// If no parameters, it will use all the bool field of struct, or
// it will use parameters's columns
func (session *Session) UseBool(columns ...string) *Session {
session.Statement.UseBool(columns...)
return session
}
// Distinct use for distinct columns. Caution: when you are using cache,
// distinct will not be cached because cache system need id,
// but distinct will not provide id
func (session *Session) Distinct(columns ...string) *Session {
session.Statement.Distinct(columns...)
return session
}
// Omit Only not use the parameters as select or update columns
func (session *Session) Omit(columns ...string) *Session {
session.Statement.Omit(columns...)
return session
}
// Nullable Set null when column is zero-value and nullable for update
func (session *Session) Nullable(columns ...string) *Session {
session.Statement.Nullable(columns...)
return session
}
// NoAutoTime means do not automatically give created field and updated field
// the current time on the current session temporarily
func (session *Session) NoAutoTime() *Session {
session.Statement.UseAutoTime = false
return session
}

@ -0,0 +1,70 @@
// Copyright 2017 The Xorm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xorm
import "github.com/go-xorm/builder"
// Sql provides raw sql input parameter. When you have a complex SQL statement
// and cannot use Where, Id, In and etc. Methods to describe, you can use SQL.
//
// Deprecated: use SQL instead.
func (session *Session) Sql(query string, args ...interface{}) *Session {
return session.SQL(query, args...)
}
// SQL provides raw sql input parameter. When you have a complex SQL statement
// and cannot use Where, Id, In and etc. Methods to describe, you can use SQL.
func (session *Session) SQL(query interface{}, args ...interface{}) *Session {
session.Statement.SQL(query, args...)
return session
}
// Where provides custom query condition.
func (session *Session) Where(query interface{}, args ...interface{}) *Session {
session.Statement.Where(query, args...)
return session
}
// And provides custom query condition.
func (session *Session) And(query interface{}, args ...interface{}) *Session {
session.Statement.And(query, args...)
return session
}
// Or provides custom query condition.
func (session *Session) Or(query interface{}, args ...interface{}) *Session {
session.Statement.Or(query, args...)
return session
}
// Id provides converting id as a query condition
//
// Deprecated: use ID instead
func (session *Session) Id(id interface{}) *Session {
return session.ID(id)
}
// ID provides converting id as a query condition
func (session *Session) ID(id interface{}) *Session {
session.Statement.ID(id)
return session
}
// In provides a query string like "id in (1, 2, 3)"
func (session *Session) In(column string, args ...interface{}) *Session {
session.Statement.In(column, args...)
return session
}
// NotIn provides a query string like "id in (1, 2, 3)"
func (session *Session) NotIn(column string, args ...interface{}) *Session {
session.Statement.NotIn(column, args...)
return session
}
// Conds returns session query conditions
func (session *Session) Conds() builder.Cond {
return session.Statement.cond
}

@ -0,0 +1,673 @@
// Copyright 2017 The Xorm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xorm
import (
"database/sql"
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
"reflect"
"strconv"
"strings"
"time"
"github.com/go-xorm/core"
)
func (session *Session) str2Time(col *core.Column, data string) (outTime time.Time, outErr error) {
sdata := strings.TrimSpace(data)
var x time.Time
var err error
if sdata == "0000-00-00 00:00:00" ||
sdata == "0001-01-01 00:00:00" {
} else if !strings.ContainsAny(sdata, "- :") { // !nashtsai! has only found that mymysql driver is using this for time type column
// time stamp
sd, err := strconv.ParseInt(sdata, 10, 64)
if err == nil {
x = time.Unix(sd, 0)
// !nashtsai! HACK mymysql driver is causing Local location being change to CHAT and cause wrong time conversion
if col.TimeZone == nil {
x = x.In(session.Engine.TZLocation)
} else {
x = x.In(col.TimeZone)
}
session.Engine.logger.Debugf("time(0) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
} else {
session.Engine.logger.Debugf("time(0) err key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
}
} else if len(sdata) > 19 && strings.Contains(sdata, "-") {
x, err = time.ParseInLocation(time.RFC3339Nano, sdata, session.Engine.TZLocation)
session.Engine.logger.Debugf("time(1) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
if err != nil {
x, err = time.ParseInLocation("2006-01-02 15:04:05.999999999", sdata, session.Engine.TZLocation)
session.Engine.logger.Debugf("time(2) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
}
if err != nil {
x, err = time.ParseInLocation("2006-01-02 15:04:05.9999999 Z07:00", sdata, session.Engine.TZLocation)
session.Engine.logger.Debugf("time(3) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
}
} else if len(sdata) == 19 && strings.Contains(sdata, "-") {
x, err = time.ParseInLocation("2006-01-02 15:04:05", sdata, session.Engine.TZLocation)
session.Engine.logger.Debugf("time(4) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
} else if len(sdata) == 10 && sdata[4] == '-' && sdata[7] == '-' {
x, err = time.ParseInLocation("2006-01-02", sdata, session.Engine.TZLocation)
session.Engine.logger.Debugf("time(5) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
} else if col.SQLType.Name == core.Time {
if strings.Contains(sdata, " ") {
ssd := strings.Split(sdata, " ")
sdata = ssd[1]
}
sdata = strings.TrimSpace(sdata)
if session.Engine.dialect.DBType() == core.MYSQL && len(sdata) > 8 {
sdata = sdata[len(sdata)-8:]
}
st := fmt.Sprintf("2006-01-02 %v", sdata)
x, err = time.ParseInLocation("2006-01-02 15:04:05", st, session.Engine.TZLocation)
session.Engine.logger.Debugf("time(6) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
} else {
outErr = fmt.Errorf("unsupported time format %v", sdata)
return
}
if err != nil {
outErr = fmt.Errorf("unsupported time format %v: %v", sdata, err)
return
}
outTime = x
return
}
func (session *Session) byte2Time(col *core.Column, data []byte) (outTime time.Time, outErr error) {
return session.str2Time(col, string(data))
}
// convert a db data([]byte) to a field value
func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, data []byte) error {
if structConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok {
return structConvert.FromDB(data)
}
if structConvert, ok := fieldValue.Interface().(core.Conversion); ok {
return structConvert.FromDB(data)
}
var v interface{}
key := col.Name
fieldType := fieldValue.Type()
switch fieldType.Kind() {
case reflect.Complex64, reflect.Complex128:
x := reflect.New(fieldType)
if len(data) > 0 {
err := json.Unmarshal(data, x.Interface())
if err != nil {
session.Engine.logger.Error(err)
return err
}
fieldValue.Set(x.Elem())
}
case reflect.Slice, reflect.Array, reflect.Map:
v = data
t := fieldType.Elem()
k := t.Kind()
if col.SQLType.IsText() {
x := reflect.New(fieldType)
if len(data) > 0 {
err := json.Unmarshal(data, x.Interface())
if err != nil {
session.Engine.logger.Error(err)
return err
}
fieldValue.Set(x.Elem())
}
} else if col.SQLType.IsBlob() {
if k == reflect.Uint8 {
fieldValue.Set(reflect.ValueOf(v))
} else {
x := reflect.New(fieldType)
if len(data) > 0 {
err := json.Unmarshal(data, x.Interface())
if err != nil {
session.Engine.logger.Error(err)
return err
}
fieldValue.Set(x.Elem())
}
}
} else {
return ErrUnSupportedType
}
case reflect.String:
fieldValue.SetString(string(data))
case reflect.Bool:
d := string(data)
v, err := strconv.ParseBool(d)
if err != nil {
return fmt.Errorf("arg %v as bool: %s", key, err.Error())
}
fieldValue.Set(reflect.ValueOf(v))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
sdata := string(data)
var x int64
var err error
// for mysql, when use bit, it returned \x01
if col.SQLType.Name == core.Bit &&
session.Engine.dialect.DBType() == core.MYSQL { // !nashtsai! TODO dialect needs to provide conversion interface API
if len(data) == 1 {
x = int64(data[0])
} else {
x = 0
}
} else if strings.HasPrefix(sdata, "0x") {
x, err = strconv.ParseInt(sdata, 16, 64)
} else if strings.HasPrefix(sdata, "0") {
x, err = strconv.ParseInt(sdata, 8, 64)
} else if strings.EqualFold(sdata, "true") {
x = 1
} else if strings.EqualFold(sdata, "false") {
x = 0
} else {
x, err = strconv.ParseInt(sdata, 10, 64)
}
if err != nil {
return fmt.Errorf("arg %v as int: %s", key, err.Error())
}
fieldValue.SetInt(x)
case reflect.Float32, reflect.Float64:
x, err := strconv.ParseFloat(string(data), 64)
if err != nil {
return fmt.Errorf("arg %v as float64: %s", key, err.Error())
}
fieldValue.SetFloat(x)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
x, err := strconv.ParseUint(string(data), 10, 64)
if err != nil {
return fmt.Errorf("arg %v as int: %s", key, err.Error())
}
fieldValue.SetUint(x)
//Currently only support Time type
case reflect.Struct:
// !<winxxp>! 增加支持sql.Scanner接口的结构,如sql.NullString
if nulVal, ok := fieldValue.Addr().Interface().(sql.Scanner); ok {
if err := nulVal.Scan(data); err != nil {
return fmt.Errorf("sql.Scan(%v) failed: %s ", data, err.Error())
}
} else {
if fieldType.ConvertibleTo(core.TimeType) {
x, err := session.byte2Time(col, data)
if err != nil {
return err
}
v = x
fieldValue.Set(reflect.ValueOf(v).Convert(fieldType))
} else if session.Statement.UseCascade {
table, err := session.Engine.autoMapType(*fieldValue)
if err != nil {
return err
}
// TODO: current only support 1 primary key
if len(table.PrimaryKeys) > 1 {
panic("unsupported composited primary key cascade")
}
var pk = make(core.PK, len(table.PrimaryKeys))
rawValueType := table.ColumnType(table.PKColumns()[0].FieldName)
pk[0], err = str2PK(string(data), rawValueType)
if err != nil {
return err
}
if !isPKZero(pk) {
// !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch
// however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne
// property to be fetched lazily
structInter := reflect.New(fieldValue.Type())
newsession := session.Engine.NewSession()
defer newsession.Close()
has, err := newsession.Id(pk).NoCascade().Get(structInter.Interface())
if err != nil {
return err
}
if has {
v = structInter.Elem().Interface()
fieldValue.Set(reflect.ValueOf(v))
} else {
return errors.New("cascade obj is not exist")
}
}
}
}
case reflect.Ptr:
// !nashtsai! TODO merge duplicated codes above
//typeStr := fieldType.String()
switch fieldType.Elem().Kind() {
// case "*string":
case core.StringType.Kind():
x := string(data)
fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
// case "*bool":
case core.BoolType.Kind():
d := string(data)
v, err := strconv.ParseBool(d)
if err != nil {
return fmt.Errorf("arg %v as bool: %s", key, err.Error())
}
fieldValue.Set(reflect.ValueOf(&v).Convert(fieldType))
// case "*complex64":
case core.Complex64Type.Kind():
var x complex64
if len(data) > 0 {
err := json.Unmarshal(data, &x)
if err != nil {
session.Engine.logger.Error(err)
return err
}
fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
}
// case "*complex128":
case core.Complex128Type.Kind():
var x complex128
if len(data) > 0 {
err := json.Unmarshal(data, &x)
if err != nil {
session.Engine.logger.Error(err)
return err
}
fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
}
// case "*float64":
case core.Float64Type.Kind():
x, err := strconv.ParseFloat(string(data), 64)
if err != nil {
return fmt.Errorf("arg %v as float64: %s", key, err.Error())
}
fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
// case "*float32":
case core.Float32Type.Kind():
var x float32
x1, err := strconv.ParseFloat(string(data), 32)
if err != nil {
return fmt.Errorf("arg %v as float32: %s", key, err.Error())
}
x = float32(x1)
fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
// case "*uint64":
case core.Uint64Type.Kind():
var x uint64
x, err := strconv.ParseUint(string(data), 10, 64)
if err != nil {
return fmt.Errorf("arg %v as int: %s", key, err.Error())
}
fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
// case "*uint":
case core.UintType.Kind():
var x uint
x1, err := strconv.ParseUint(string(data), 10, 64)
if err != nil {
return fmt.Errorf("arg %v as int: %s", key, err.Error())
}
x = uint(x1)
fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
// case "*uint32":
case core.Uint32Type.Kind():
var x uint32
x1, err := strconv.ParseUint(string(data), 10, 64)
if err != nil {
return fmt.Errorf("arg %v as int: %s", key, err.Error())
}
x = uint32(x1)
fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
// case "*uint8":
case core.Uint8Type.Kind():
var x uint8
x1, err := strconv.ParseUint(string(data), 10, 64)
if err != nil {
return fmt.Errorf("arg %v as int: %s", key, err.Error())
}
x = uint8(x1)
fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
// case "*uint16":
case core.Uint16Type.Kind():
var x uint16
x1, err := strconv.ParseUint(string(data), 10, 64)
if err != nil {
return fmt.Errorf("arg %v as int: %s", key, err.Error())
}
x = uint16(x1)
fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
// case "*int64":
case core.Int64Type.Kind():
sdata := string(data)
var x int64
var err error
// for mysql, when use bit, it returned \x01
if col.SQLType.Name == core.Bit &&
strings.Contains(session.Engine.DriverName(), "mysql") {
if len(data) == 1 {
x = int64(data[0])
} else {
x = 0
}
} else if strings.HasPrefix(sdata, "0x") {
x, err = strconv.ParseInt(sdata, 16, 64)
} else if strings.HasPrefix(sdata, "0") {
x, err = strconv.ParseInt(sdata, 8, 64)
} else {
x, err = strconv.ParseInt(sdata, 10, 64)
}
if err != nil {
return fmt.Errorf("arg %v as int: %s", key, err.Error())
}
fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
// case "*int":
case core.IntType.Kind():
sdata := string(data)
var x int
var x1 int64
var err error
// for mysql, when use bit, it returned \x01
if col.SQLType.Name == core.Bit &&
strings.Contains(session.Engine.DriverName(), "mysql") {
if len(data) == 1 {
x = int(data[0])
} else {
x = 0
}
} else if strings.HasPrefix(sdata, "0x") {
x1, err = strconv.ParseInt(sdata, 16, 64)
x = int(x1)
} else if strings.HasPrefix(sdata, "0") {
x1, err = strconv.ParseInt(sdata, 8, 64)
x = int(x1)
} else {
x1, err = strconv.ParseInt(sdata, 10, 64)
x = int(x1)
}
if err != nil {
return fmt.Errorf("arg %v as int: %s", key, err.Error())
}
fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
// case "*int32":
case core.Int32Type.Kind():
sdata := string(data)
var x int32
var x1 int64
var err error
// for mysql, when use bit, it returned \x01
if col.SQLType.Name == core.Bit &&
session.Engine.dialect.DBType() == core.MYSQL {
if len(data) == 1 {
x = int32(data[0])
} else {
x = 0
}
} else if strings.HasPrefix(sdata, "0x") {
x1, err = strconv.ParseInt(sdata, 16, 64)
x = int32(x1)
} else if strings.HasPrefix(sdata, "0") {
x1, err = strconv.ParseInt(sdata, 8, 64)
x = int32(x1)
} else {
x1, err = strconv.ParseInt(sdata, 10, 64)
x = int32(x1)
}
if err != nil {
return fmt.Errorf("arg %v as int: %s", key, err.Error())
}
fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
// case "*int8":
case core.Int8Type.Kind():
sdata := string(data)
var x int8
var x1 int64
var err error
// for mysql, when use bit, it returned \x01
if col.SQLType.Name == core.Bit &&
strings.Contains(session.Engine.DriverName(), "mysql") {
if len(data) == 1 {
x = int8(data[0])
} else {
x = 0
}
} else if strings.HasPrefix(sdata, "0x") {
x1, err = strconv.ParseInt(sdata, 16, 64)
x = int8(x1)
} else if strings.HasPrefix(sdata, "0") {
x1, err = strconv.ParseInt(sdata, 8, 64)
x = int8(x1)
} else {
x1, err = strconv.ParseInt(sdata, 10, 64)
x = int8(x1)
}
if err != nil {
return fmt.Errorf("arg %v as int: %s", key, err.Error())
}
fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
// case "*int16":
case core.Int16Type.Kind():
sdata := string(data)
var x int16
var x1 int64
var err error
// for mysql, when use bit, it returned \x01
if col.SQLType.Name == core.Bit &&
strings.Contains(session.Engine.DriverName(), "mysql") {
if len(data) == 1 {
x = int16(data[0])
} else {
x = 0
}
} else if strings.HasPrefix(sdata, "0x") {
x1, err = strconv.ParseInt(sdata, 16, 64)
x = int16(x1)
} else if strings.HasPrefix(sdata, "0") {
x1, err = strconv.ParseInt(sdata, 8, 64)
x = int16(x1)
} else {
x1, err = strconv.ParseInt(sdata, 10, 64)
x = int16(x1)
}
if err != nil {
return fmt.Errorf("arg %v as int: %s", key, err.Error())
}
fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
// case "*SomeStruct":
case reflect.Struct:
switch fieldType {
// case "*.time.Time":
case core.PtrTimeType:
x, err := session.byte2Time(col, data)
if err != nil {
return err
}
v = x
fieldValue.Set(reflect.ValueOf(&x))
default:
if session.Statement.UseCascade {
structInter := reflect.New(fieldType.Elem())
table, err := session.Engine.autoMapType(structInter.Elem())
if err != nil {
return err
}
if len(table.PrimaryKeys) > 1 {
panic("unsupported composited primary key cascade")
}
var pk = make(core.PK, len(table.PrimaryKeys))
rawValueType := table.ColumnType(table.PKColumns()[0].FieldName)
pk[0], err = str2PK(string(data), rawValueType)
if err != nil {
return err
}
if !isPKZero(pk) {
// !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch
// however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne
// property to be fetched lazily
newsession := session.Engine.NewSession()
defer newsession.Close()
has, err := newsession.Id(pk).NoCascade().Get(structInter.Interface())
if err != nil {
return err
}
if has {
v = structInter.Interface()
fieldValue.Set(reflect.ValueOf(v))
} else {
return errors.New("cascade obj is not exist")
}
}
} else {
return fmt.Errorf("unsupported struct type in Scan: %s", fieldValue.Type().String())
}
}
default:
return fmt.Errorf("unsupported type in Scan: %s", fieldValue.Type().String())
}
default:
return fmt.Errorf("unsupported type in Scan: %s", fieldValue.Type().String())
}
return nil
}
// convert a field value of a struct to interface for put into db
func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Value) (interface{}, error) {
if fieldValue.CanAddr() {
if fieldConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok {
data, err := fieldConvert.ToDB()
if err != nil {
return 0, err
}
if col.SQLType.IsBlob() {
return data, nil
}
return string(data), nil
}
}
if fieldConvert, ok := fieldValue.Interface().(core.Conversion); ok {
data, err := fieldConvert.ToDB()
if err != nil {
return 0, err
}
if col.SQLType.IsBlob() {
return data, nil
}
return string(data), nil
}
fieldType := fieldValue.Type()
k := fieldType.Kind()
if k == reflect.Ptr {
if fieldValue.IsNil() {
return nil, nil
} else if !fieldValue.IsValid() {
session.Engine.logger.Warn("the field[", col.FieldName, "] is invalid")
return nil, nil
} else {
// !nashtsai! deference pointer type to instance type
fieldValue = fieldValue.Elem()
fieldType = fieldValue.Type()
k = fieldType.Kind()
}
}
switch k {
case reflect.Bool:
return fieldValue.Bool(), nil
case reflect.String:
return fieldValue.String(), nil
case reflect.Struct:
if fieldType.ConvertibleTo(core.TimeType) {
t := fieldValue.Convert(core.TimeType).Interface().(time.Time)
if session.Engine.dialect.DBType() == core.MSSQL {
if t.IsZero() {
return nil, nil
}
}
tf := session.Engine.FormatTime(col.SQLType.Name, t)
return tf, nil
}
if !col.SQLType.IsJson() {
// !<winxxp>! 增加支持driver.Valuer接口的结构,如sql.NullString
if v, ok := fieldValue.Interface().(driver.Valuer); ok {
return v.Value()
}
fieldTable, err := session.Engine.autoMapType(fieldValue)
if err != nil {
return nil, err
}
if len(fieldTable.PrimaryKeys) == 1 {
pkField := reflect.Indirect(fieldValue).FieldByName(fieldTable.PKColumns()[0].FieldName)
return pkField.Interface(), nil
}
return 0, fmt.Errorf("no primary key for col %v", col.Name)
}
if col.SQLType.IsText() {
bytes, err := json.Marshal(fieldValue.Interface())
if err != nil {
session.Engine.logger.Error(err)
return 0, err
}
return string(bytes), nil
} else if col.SQLType.IsBlob() {
bytes, err := json.Marshal(fieldValue.Interface())
if err != nil {
session.Engine.logger.Error(err)
return 0, err
}
return bytes, nil
}
return nil, fmt.Errorf("Unsupported type %v", fieldValue.Type())
case reflect.Complex64, reflect.Complex128:
bytes, err := json.Marshal(fieldValue.Interface())
if err != nil {
session.Engine.logger.Error(err)
return 0, err
}
return string(bytes), nil
case reflect.Array, reflect.Slice, reflect.Map:
if !fieldValue.IsValid() {
return fieldValue.Interface(), nil
}
if col.SQLType.IsText() {
bytes, err := json.Marshal(fieldValue.Interface())
if err != nil {
session.Engine.logger.Error(err)
return 0, err
}
return string(bytes), nil
} else if col.SQLType.IsBlob() {
var bytes []byte
var err error
if (k == reflect.Array || k == reflect.Slice) &&
(fieldValue.Type().Elem().Kind() == reflect.Uint8) {
bytes = fieldValue.Bytes()
} else {
bytes, err = json.Marshal(fieldValue.Interface())
if err != nil {
session.Engine.logger.Error(err)
return 0, err
}
}
return bytes, nil
}
return nil, ErrUnSupportedType
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
return int64(fieldValue.Uint()), nil
default:
return fieldValue.Interface(), nil
}
}

@ -169,31 +169,43 @@ func (session *Session) noCacheFind(table *core.Table, containerValue reflect.Va
return err return err
} }
var newElemFunc func() reflect.Value var newElemFunc func(fields []string) reflect.Value
elemType := containerValue.Type().Elem() elemType := containerValue.Type().Elem()
var isPointer bool
if elemType.Kind() == reflect.Ptr { if elemType.Kind() == reflect.Ptr {
newElemFunc = func() reflect.Value { isPointer = true
return reflect.New(elemType.Elem()) elemType = elemType.Elem()
} }
} else { if elemType.Kind() == reflect.Ptr {
newElemFunc = func() reflect.Value { return errors.New("pointer to pointer is not supported")
return reflect.New(elemType) }
newElemFunc = func(fields []string) reflect.Value {
switch elemType.Kind() {
case reflect.Slice:
slice := reflect.MakeSlice(elemType, len(fields), len(fields))
x := reflect.New(slice.Type())
x.Elem().Set(slice)
return x
case reflect.Map:
mp := reflect.MakeMap(elemType)
x := reflect.New(mp.Type())
x.Elem().Set(mp)
return x
} }
return reflect.New(elemType)
} }
var containerValueSetFunc func(*reflect.Value, core.PK) error var containerValueSetFunc func(*reflect.Value, core.PK) error
if containerValue.Kind() == reflect.Slice { if containerValue.Kind() == reflect.Slice {
if elemType.Kind() == reflect.Ptr { containerValueSetFunc = func(newValue *reflect.Value, pk core.PK) error {
containerValueSetFunc = func(newValue *reflect.Value, pk core.PK) error { if isPointer {
containerValue.Set(reflect.Append(containerValue, reflect.ValueOf(newValue.Interface()))) containerValue.Set(reflect.Append(containerValue, newValue.Elem().Addr()))
return nil } else {
} containerValue.Set(reflect.Append(containerValue, newValue.Elem()))
} else {
containerValueSetFunc = func(newValue *reflect.Value, pk core.PK) error {
containerValue.Set(reflect.Append(containerValue, reflect.Indirect(reflect.ValueOf(newValue.Interface()))))
return nil
} }
return nil
} }
} else { } else {
keyType := containerValue.Type().Key() keyType := containerValue.Type().Key()
@ -204,40 +216,45 @@ func (session *Session) noCacheFind(table *core.Table, containerValue reflect.Va
return errors.New("don't support multiple primary key's map has non-slice key type") return errors.New("don't support multiple primary key's map has non-slice key type")
} }
if elemType.Kind() == reflect.Ptr { containerValueSetFunc = func(newValue *reflect.Value, pk core.PK) error {
containerValueSetFunc = func(newValue *reflect.Value, pk core.PK) error { keyValue := reflect.New(keyType)
keyValue := reflect.New(keyType) err := convertPKToValue(table, keyValue.Interface(), pk)
err := convertPKToValue(table, keyValue.Interface(), pk) if err != nil {
if err != nil { return err
return err
}
containerValue.SetMapIndex(keyValue.Elem(), reflect.ValueOf(newValue.Interface()))
return nil
} }
} else { if isPointer {
containerValueSetFunc = func(newValue *reflect.Value, pk core.PK) error { containerValue.SetMapIndex(keyValue.Elem(), newValue.Elem().Addr())
keyValue := reflect.New(keyType) } else {
err := convertPKToValue(table, keyValue.Interface(), pk) containerValue.SetMapIndex(keyValue.Elem(), newValue.Elem())
if err != nil {
return err
}
containerValue.SetMapIndex(keyValue.Elem(), reflect.Indirect(reflect.ValueOf(newValue.Interface())))
return nil
} }
return nil
} }
} }
var newValue = newElemFunc() if elemType.Kind() == reflect.Struct {
dataStruct := rValue(newValue.Interface()) var newValue = newElemFunc(fields)
if dataStruct.Kind() == reflect.Struct { dataStruct := rValue(newValue.Interface())
return session.rows2Beans(rawRows, fields, len(fields), session.Engine.autoMapType(dataStruct), newElemFunc, containerValueSetFunc) tb, err := session.Engine.autoMapType(dataStruct)
if err != nil {
return err
}
return session.rows2Beans(rawRows, fields, len(fields), tb, newElemFunc, containerValueSetFunc)
} }
for rawRows.Next() { for rawRows.Next() {
var newValue = newElemFunc() var newValue = newElemFunc(fields)
bean := newValue.Interface() bean := newValue.Interface()
if err := rawRows.Scan(bean); err != nil { switch elemType.Kind() {
case reflect.Slice:
err = rawRows.ScanSlice(bean)
case reflect.Map:
err = rawRows.ScanMap(bean)
default:
err = rawRows.Scan(bean)
}
if err != nil {
return err return err
} }
@ -394,7 +411,10 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
if rv.Kind() != reflect.Ptr { if rv.Kind() != reflect.Ptr {
rv = rv.Addr() rv = rv.Addr()
} }
id := session.Engine.IdOfV(rv) id, err := session.Engine.idOfV(rv)
if err != nil {
return err
}
sid, err := id.ToString() sid, err := id.ToString()
if err != nil { if err != nil {
return err return err

@ -20,7 +20,14 @@ func (session *Session) Get(bean interface{}) (bool, error) {
defer session.Close() defer session.Close()
} }
session.Statement.setRefValue(rValue(bean)) beanValue := reflect.ValueOf(bean)
if beanValue.Kind() != reflect.Ptr {
return false, errors.New("needs a pointer")
}
if beanValue.Elem().Kind() == reflect.Struct {
session.Statement.setRefValue(beanValue.Elem())
}
var sqlStr string var sqlStr string
var args []interface{} var args []interface{}
@ -36,7 +43,7 @@ func (session *Session) Get(bean interface{}) (bool, error) {
args = session.Statement.RawParams args = session.Statement.RawParams
} }
if session.canCache() { if session.canCache() && beanValue.Elem().Kind() == reflect.Struct {
if cacher := session.Engine.getCacher2(session.Statement.RefTable); cacher != nil && if cacher := session.Engine.getCacher2(session.Statement.RefTable); cacher != nil &&
!session.Statement.unscoped { !session.Statement.unscoped {
has, err := session.cacheGet(bean, sqlStr, args...) has, err := session.cacheGet(bean, sqlStr, args...)
@ -46,13 +53,14 @@ func (session *Session) Get(bean interface{}) (bool, error) {
} }
} }
return session.nocacheGet(bean, sqlStr, args...) return session.nocacheGet(beanValue.Elem().Kind(), bean, sqlStr, args...)
} }
func (session *Session) nocacheGet(bean interface{}, sqlStr string, args ...interface{}) (bool, error) { func (session *Session) nocacheGet(beanKind reflect.Kind, bean interface{}, sqlStr string, args ...interface{}) (bool, error) {
session.queryPreprocess(&sqlStr, args...)
var rawRows *core.Rows var rawRows *core.Rows
var err error var err error
session.queryPreprocess(&sqlStr, args...)
if session.IsAutoCommit { if session.IsAutoCommit {
_, rawRows, err = session.innerQuery(sqlStr, args...) _, rawRows, err = session.innerQuery(sqlStr, args...)
} else { } else {
@ -65,10 +73,24 @@ func (session *Session) nocacheGet(bean interface{}, sqlStr string, args ...inte
defer rawRows.Close() defer rawRows.Close()
if rawRows.Next() { if rawRows.Next() {
fields, err := rawRows.Columns() switch beanKind {
if err == nil { case reflect.Struct:
_, err = session.row2Bean(rawRows, fields, len(fields), bean) fields, err := rawRows.Columns()
if err != nil {
// WARN: Alougth rawRows return true, but get fields failed
return true, err
}
dataStruct := rValue(bean)
session.Statement.setRefValue(dataStruct)
_, err = session.row2Bean(rawRows, fields, len(fields), bean, &dataStruct, session.Statement.RefTable)
case reflect.Slice:
err = rawRows.ScanSlice(bean)
case reflect.Map:
err = rawRows.ScanMap(bean)
default:
err = rawRows.Scan(bean)
} }
return true, err return true, err
} }
return false, nil return false, nil
@ -145,20 +167,8 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf
} }
cacheBean := cacher.GetBean(tableName, sid) cacheBean := cacher.GetBean(tableName, sid)
if cacheBean == nil { if cacheBean == nil {
/*newSession := session.Engine.NewSession()
defer newSession.Close()
cacheBean = reflect.New(structValue.Type()).Interface()
newSession.Id(id).NoCache()
if session.Statement.AltTableName != "" {
newSession.Table(session.Statement.AltTableName)
}
if !session.Statement.UseCascade {
newSession.NoCascade()
}
has, err = newSession.Get(cacheBean)
*/
cacheBean = bean cacheBean = bean
has, err = session.nocacheGet(cacheBean, sqlStr, args...) has, err = session.nocacheGet(reflect.Struct, cacheBean, sqlStr, args...)
if err != nil || !has { if err != nil || !has {
return has, err return has, err
} }

@ -210,13 +210,29 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error
} }
cleanupProcessorsClosures(&session.beforeClosures) cleanupProcessorsClosures(&session.beforeClosures)
statement := fmt.Sprintf("INSERT INTO %s (%v%v%v) VALUES (%v)", var sql = "INSERT INTO %s (%v%v%v) VALUES (%v)"
session.Engine.Quote(session.Statement.TableName()), var statement string
session.Engine.QuoteStr(), if session.Engine.dialect.DBType() == core.ORACLE {
strings.Join(colNames, session.Engine.QuoteStr()+", "+session.Engine.QuoteStr()), sql = "INSERT ALL INTO %s (%v%v%v) VALUES (%v) SELECT 1 FROM DUAL"
session.Engine.QuoteStr(), temp := fmt.Sprintf(") INTO %s (%v%v%v) VALUES (",
strings.Join(colMultiPlaces, "),(")) session.Engine.Quote(session.Statement.TableName()),
session.Engine.QuoteStr(),
strings.Join(colNames, session.Engine.QuoteStr() + ", " + session.Engine.QuoteStr()),
session.Engine.QuoteStr())
statement = fmt.Sprintf(sql,
session.Engine.Quote(session.Statement.TableName()),
session.Engine.QuoteStr(),
strings.Join(colNames, session.Engine.QuoteStr() + ", " + session.Engine.QuoteStr()),
session.Engine.QuoteStr(),
strings.Join(colMultiPlaces, temp))
} else {
statement = fmt.Sprintf(sql,
session.Engine.Quote(session.Statement.TableName()),
session.Engine.QuoteStr(),
strings.Join(colNames, session.Engine.QuoteStr() + ", " + session.Engine.QuoteStr()),
session.Engine.QuoteStr(),
strings.Join(colMultiPlaces, "),("))
}
res, err := session.exec(statement, args...) res, err := session.exec(statement, args...)
if err != nil { if err != nil {
return 0, err return 0, err
@ -309,8 +325,8 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) {
// remove the expr columns // remove the expr columns
for i, colName := range colNames { for i, colName := range colNames {
if colName == v.colName { if colName == v.colName {
colNames = append(colNames[:i], colNames[i+1:]...) colNames = append(colNames[:i], colNames[i + 1:]...)
args = append(args[:i], args[i+1:]...) args = append(args[:i], args[i + 1:]...)
} }
} }
@ -319,11 +335,11 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) {
exprColVals = append(exprColVals, v.expr) exprColVals = append(exprColVals, v.expr)
} }
colPlaces := strings.Repeat("?, ", len(colNames)-len(exprColumns)) colPlaces := strings.Repeat("?, ", len(colNames) - len(exprColumns))
if len(exprColVals) > 0 { if len(exprColVals) > 0 {
colPlaces = colPlaces + strings.Join(exprColVals, ", ") colPlaces = colPlaces + strings.Join(exprColVals, ", ")
} else { } else {
colPlaces = colPlaces[0 : len(colPlaces)-2] colPlaces = colPlaces[0 : len(colPlaces) - 2]
} }
sqlStr := fmt.Sprintf("INSERT INTO %s (%v%v%v) VALUES (%v)", sqlStr := fmt.Sprintf("INSERT INTO %s (%v%v%v) VALUES (%v)",

@ -70,7 +70,7 @@ func (session *Session) innerQuery2(sqlStr string, params ...interface{}) ([]map
return rows2maps(rows) return rows2maps(rows)
} }
// Query a raw sql and return records as []map[string][]byte // Query runs a raw sql and return records as []map[string][]byte
func (session *Session) Query(sqlStr string, paramStr ...interface{}) (resultsSlice []map[string][]byte, err error) { func (session *Session) Query(sqlStr string, paramStr ...interface{}) (resultsSlice []map[string][]byte, err error) {
defer session.resetStatement() defer session.resetStatement()
if session.IsAutoClose { if session.IsAutoClose {
@ -80,6 +80,15 @@ func (session *Session) Query(sqlStr string, paramStr ...interface{}) (resultsSl
return session.query(sqlStr, paramStr...) return session.query(sqlStr, paramStr...)
} }
// QueryString runs a raw sql and return records as []map[string]string
func (session *Session) QueryString(sqlStr string, args ...interface{}) ([]map[string]string, error) {
defer session.resetStatement()
if session.IsAutoClose {
defer session.Close()
}
return session.query2(sqlStr, args...)
}
// ============================= // =============================
// for string // for string
// ============================= // =============================

@ -306,7 +306,10 @@ func (session *Session) Sync2(beans ...interface{}) error {
for _, bean := range beans { for _, bean := range beans {
v := rValue(bean) v := rValue(bean)
table := engine.mapType(v) table, err := engine.mapType(v)
if err != nil {
return err
}
structTables = append(structTables, table) structTables = append(structTables, table)
var tbName = session.tbNameNoSchema(table) var tbName = session.tbNameNoSchema(table)

@ -123,7 +123,7 @@ func (session *Session) SumsInt(bean interface{}, columnNames ...string) ([]int6
session.queryPreprocess(&sqlStr, args...) session.queryPreprocess(&sqlStr, args...)
var err error var err error
var res = make([]int64, 0, len(columnNames)) var res = make([]int64, len(columnNames), len(columnNames))
if session.IsAutoCommit { if session.IsAutoCommit {
err = session.DB().QueryRow(sqlStr, args...).ScanSlice(&res) err = session.DB().QueryRow(sqlStr, args...).ScanSlice(&res)
} else { } else {

@ -253,48 +253,59 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6
var condSQL string var condSQL string
cond := session.Statement.cond.And(autoCond) cond := session.Statement.cond.And(autoCond)
doIncVer := false var doIncVer = (table != nil && table.Version != "" && session.Statement.checkVersion)
var verValue *reflect.Value var verValue *reflect.Value
if table != nil && table.Version != "" && session.Statement.checkVersion { if doIncVer {
verValue, err = table.VersionColumn().ValueOf(bean) verValue, err = table.VersionColumn().ValueOf(bean)
if err != nil { if err != nil {
return 0, err return 0, err
} }
cond = cond.And(builder.Eq{session.Engine.Quote(table.Version): verValue.Interface()}) cond = cond.And(builder.Eq{session.Engine.Quote(table.Version): verValue.Interface()})
condSQL, condArgs, _ = builder.ToSQL(cond) colNames = append(colNames, session.Engine.Quote(table.Version)+" = "+session.Engine.Quote(table.Version)+" + 1")
}
if len(condSQL) > 0 {
condSQL = "WHERE " + condSQL
}
if st.LimitN > 0 {
condSQL = condSQL + fmt.Sprintf(" LIMIT %d", st.LimitN)
}
sqlStr = fmt.Sprintf("UPDATE %v SET %v, %v %v", condSQL, condArgs, _ = builder.ToSQL(cond)
session.Engine.Quote(session.Statement.TableName()), if len(condSQL) > 0 {
strings.Join(colNames, ", "), condSQL = "WHERE " + condSQL
session.Engine.Quote(table.Version)+" = "+session.Engine.Quote(table.Version)+" + 1", }
condSQL)
doIncVer = true if st.OrderStr != "" {
} else { condSQL = condSQL + fmt.Sprintf(" ORDER BY %v", st.OrderStr)
condSQL, condArgs, _ = builder.ToSQL(cond) }
if len(condSQL) > 0 {
condSQL = "WHERE " + condSQL
}
if st.LimitN > 0 { // TODO: Oracle support needed
var top string
if st.LimitN > 0 {
if st.Engine.dialect.DBType() == core.MYSQL {
condSQL = condSQL + fmt.Sprintf(" LIMIT %d", st.LimitN) condSQL = condSQL + fmt.Sprintf(" LIMIT %d", st.LimitN)
} else if st.Engine.dialect.DBType() == core.SQLITE {
tempCondSQL := condSQL + fmt.Sprintf(" LIMIT %d", st.LimitN)
cond = cond.And(builder.Expr(fmt.Sprintf("rowid IN (SELECT rowid FROM %v %v)",
session.Engine.Quote(session.Statement.TableName()), tempCondSQL), condArgs...))
condSQL, condArgs, _ = builder.ToSQL(cond)
if len(condSQL) > 0 {
condSQL = "WHERE " + condSQL
}
} else if st.Engine.dialect.DBType() == core.POSTGRES {
tempCondSQL := condSQL + fmt.Sprintf(" LIMIT %d", st.LimitN)
cond = cond.And(builder.Expr(fmt.Sprintf("CTID IN (SELECT CTID FROM %v %v)",
session.Engine.Quote(session.Statement.TableName()), tempCondSQL), condArgs...))
condSQL, condArgs, _ = builder.ToSQL(cond)
if len(condSQL) > 0 {
condSQL = "WHERE " + condSQL
}
} else if st.Engine.dialect.DBType() == core.MSSQL {
top = fmt.Sprintf("top (%d) ", st.LimitN)
} }
sqlStr = fmt.Sprintf("UPDATE %v SET %v %v",
session.Engine.Quote(session.Statement.TableName()),
strings.Join(colNames, ", "),
condSQL)
} }
sqlStr = fmt.Sprintf("UPDATE %v%v SET %v %v",
top,
session.Engine.Quote(session.Statement.TableName()),
strings.Join(colNames, ", "),
condSQL)
res, err := session.exec(sqlStr, append(args, condArgs...)...) res, err := session.exec(sqlStr, append(args, condArgs...)...)
if err != nil { if err != nil {
return 0, err return 0, err

@ -1,20 +0,0 @@
// Copyright 2015 The Xorm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
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
}

@ -39,7 +39,7 @@ type Statement struct {
Engine *Engine Engine *Engine
Start int Start int
LimitN int LimitN int
IdParam *core.PK idParam *core.PK
OrderStr string OrderStr string
JoinStr string JoinStr string
joinArgs []interface{} joinArgs []interface{}
@ -91,7 +91,7 @@ func (statement *Statement) Init() {
statement.columnMap = make(map[string]bool) statement.columnMap = make(map[string]bool)
statement.AltTableName = "" statement.AltTableName = ""
statement.tableName = "" statement.tableName = ""
statement.IdParam = nil statement.idParam = nil
statement.RawSQL = "" statement.RawSQL = ""
statement.RawParams = make([]interface{}, 0) statement.RawParams = make([]interface{}, 0)
statement.UseCache = true statement.UseCache = true
@ -195,29 +195,26 @@ func (statement *Statement) Or(query interface{}, args ...interface{}) *Statemen
// In generate "Where column IN (?) " statement // In generate "Where column IN (?) " statement
func (statement *Statement) In(column string, args ...interface{}) *Statement { func (statement *Statement) In(column string, args ...interface{}) *Statement {
if len(args) == 0 { in := builder.In(statement.Engine.Quote(column), args...)
return statement
}
in := builder.In(column, args...)
statement.cond = statement.cond.And(in) statement.cond = statement.cond.And(in)
return statement return statement
} }
// NotIn generate "Where column NOT IN (?) " statement // NotIn generate "Where column NOT IN (?) " statement
func (statement *Statement) NotIn(column string, args ...interface{}) *Statement { func (statement *Statement) NotIn(column string, args ...interface{}) *Statement {
if len(args) == 0 { notIn := builder.NotIn(statement.Engine.Quote(column), args...)
return statement statement.cond = statement.cond.And(notIn)
}
in := builder.NotIn(column, args...)
statement.cond = statement.cond.And(in)
return statement return statement
} }
func (statement *Statement) setRefValue(v reflect.Value) { func (statement *Statement) setRefValue(v reflect.Value) error {
statement.RefTable = statement.Engine.autoMapType(reflect.Indirect(v)) var err error
statement.RefTable, err = statement.Engine.autoMapType(reflect.Indirect(v))
if err != nil {
return err
}
statement.tableName = statement.Engine.tbName(v) statement.tableName = statement.Engine.tbName(v)
return nil
} }
// Table tempororily set table name, the parameter could be a string or a pointer of struct // Table tempororily set table name, the parameter could be a string or a pointer of struct
@ -227,7 +224,12 @@ func (statement *Statement) Table(tableNameOrBean interface{}) *Statement {
if t.Kind() == reflect.String { if t.Kind() == reflect.String {
statement.AltTableName = tableNameOrBean.(string) statement.AltTableName = tableNameOrBean.(string)
} else if t.Kind() == reflect.Struct { } else if t.Kind() == reflect.Struct {
statement.RefTable = statement.Engine.autoMapType(v) var err error
statement.RefTable, err = statement.Engine.autoMapType(v)
if err != nil {
statement.Engine.logger.Error(err)
return statement
}
statement.AltTableName = statement.Engine.tbName(v) statement.AltTableName = statement.Engine.tbName(v)
} }
return statement return statement
@ -418,7 +420,11 @@ func buildUpdates(engine *Engine, table *core.Table, bean interface{},
if fieldValue == reflect.Zero(fieldType) { if fieldValue == reflect.Zero(fieldType) {
continue continue
} }
if fieldValue.IsNil() || !fieldValue.IsValid() || fieldValue.Len() == 0 { if fieldType.Kind() == reflect.Array {
if isArrayValueZero(fieldValue) {
continue
}
} else if fieldValue.IsNil() || !fieldValue.IsValid() || fieldValue.Len() == 0 {
continue continue
} }
} }
@ -433,13 +439,16 @@ func buildUpdates(engine *Engine, table *core.Table, bean interface{},
} else if col.SQLType.IsBlob() { } else if col.SQLType.IsBlob() {
var bytes []byte var bytes []byte
var err error var err error
if (fieldType.Kind() == reflect.Array || fieldType.Kind() == reflect.Slice) && if fieldType.Kind() == reflect.Slice &&
fieldType.Elem().Kind() == reflect.Uint8 { fieldType.Elem().Kind() == reflect.Uint8 {
if fieldValue.Len() > 0 { if fieldValue.Len() > 0 {
val = fieldValue.Bytes() val = fieldValue.Bytes()
} else { } else {
continue continue
} }
} else if fieldType.Kind() == reflect.Array &&
fieldType.Elem().Kind() == reflect.Uint8 {
val = fieldValue.Slice(0, 0).Interface()
} else { } else {
bytes, err = json.Marshal(fieldValue.Interface()) bytes, err = json.Marshal(fieldValue.Interface())
if err != nil { if err != nil {
@ -651,7 +660,9 @@ func buildConds(engine *Engine, table *core.Table, bean interface{},
} }
} }
} }
case reflect.Array, reflect.Slice, reflect.Map: case reflect.Array:
continue
case reflect.Slice, reflect.Map:
if fieldValue == reflect.Zero(fieldType) { if fieldValue == reflect.Zero(fieldType) {
continue continue
} }
@ -706,13 +717,6 @@ func (statement *Statement) TableName() string {
return statement.tableName return statement.tableName
} }
// Id generate "where id = ? " statement or for composite key "where key1 = ? and key2 = ?"
//
// Deprecated: use ID instead
func (statement *Statement) Id(id interface{}) *Statement {
return statement.ID(id)
}
// ID generate "where id = ? " statement or for composite key "where key1 = ? and key2 = ?" // ID generate "where id = ? " statement 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)
@ -721,23 +725,23 @@ func (statement *Statement) ID(id interface{}) *Statement {
switch idType { switch idType {
case ptrPkType: case ptrPkType:
if pkPtr, ok := (id).(*core.PK); ok { if pkPtr, ok := (id).(*core.PK); ok {
statement.IdParam = pkPtr statement.idParam = pkPtr
return statement return statement
} }
case pkType: case pkType:
if pk, ok := (id).(core.PK); ok { if pk, ok := (id).(core.PK); ok {
statement.IdParam = &pk statement.idParam = &pk
return statement return statement
} }
} }
switch idType.Kind() { switch idType.Kind() {
case reflect.String: case reflect.String:
statement.IdParam = &core.PK{idValue.Convert(reflect.TypeOf("")).Interface()} statement.idParam = &core.PK{idValue.Convert(reflect.TypeOf("")).Interface()}
return statement return statement
} }
statement.IdParam = &core.PK{id} statement.idParam = &core.PK{id}
return statement return statement
} }
@ -1120,7 +1124,11 @@ func (statement *Statement) genConds(bean interface{}) (string, []interface{}, e
} }
func (statement *Statement) genGetSQL(bean interface{}) (string, []interface{}) { func (statement *Statement) genGetSQL(bean interface{}) (string, []interface{}) {
statement.setRefValue(rValue(bean)) v := rValue(bean)
isStruct := v.Kind() == reflect.Struct
if isStruct {
statement.setRefValue(v)
}
var columnStr = statement.ColumnStr var columnStr = statement.ColumnStr
if len(statement.selectStr) > 0 { if len(statement.selectStr) > 0 {
@ -1139,14 +1147,22 @@ func (statement *Statement) genGetSQL(bean interface{}) (string, []interface{})
if len(columnStr) == 0 { if len(columnStr) == 0 {
if len(statement.GroupByStr) > 0 { if len(statement.GroupByStr) > 0 {
columnStr = statement.Engine.Quote(strings.Replace(statement.GroupByStr, ",", statement.Engine.Quote(","), -1)) columnStr = statement.Engine.Quote(strings.Replace(statement.GroupByStr, ",", statement.Engine.Quote(","), -1))
} else {
columnStr = "*"
} }
} }
} }
} }
condSQL, condArgs, _ := statement.genConds(bean) if len(columnStr) == 0 {
columnStr = "*"
}
var condSQL string
var condArgs []interface{}
if isStruct {
condSQL, condArgs, _ = statement.genConds(bean)
} else {
condSQL, condArgs, _ = builder.ToSQL(statement.cond)
}
return statement.genSelectSQL(columnStr, condSQL), append(statement.joinArgs, condArgs...) return statement.genSelectSQL(columnStr, condSQL), append(statement.joinArgs, condArgs...)
} }
@ -1172,7 +1188,7 @@ func (statement *Statement) genSumSQL(bean interface{}, columns ...string) (stri
var sumStrs = make([]string, 0, len(columns)) var sumStrs = make([]string, 0, len(columns))
for _, colName := range columns { for _, colName := range columns {
sumStrs = append(sumStrs, fmt.Sprintf("COALESCE(sum(%s),0)", colName)) sumStrs = append(sumStrs, fmt.Sprintf("COALESCE(sum(%s),0)", statement.Engine.Quote(colName)))
} }
condSQL, condArgs, _ := statement.genConds(bean) condSQL, condArgs, _ := statement.genConds(bean)
@ -1182,7 +1198,7 @@ func (statement *Statement) genSumSQL(bean interface{}, columns ...string) (stri
func (statement *Statement) genSelectSQL(columnStr, condSQL string) (a string) { func (statement *Statement) genSelectSQL(columnStr, condSQL string) (a string) {
var distinct string var distinct string
if statement.IsDistinct { if statement.IsDistinct && !strings.HasPrefix(columnStr, "count") {
distinct = "DISTINCT " distinct = "DISTINCT "
} }
@ -1289,14 +1305,14 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string) (a string) {
} }
func (statement *Statement) processIDParam() { func (statement *Statement) processIDParam() {
if statement.IdParam == nil { if statement.idParam == nil {
return return
} }
for i, col := range statement.RefTable.PKColumns() { for i, col := range statement.RefTable.PKColumns() {
var colName = statement.colName(col, statement.TableName()) var colName = statement.colName(col, statement.TableName())
if i < len(*(statement.IdParam)) { if i < len(*(statement.idParam)) {
statement.cond = statement.cond.And(builder.Eq{colName: (*(statement.IdParam))[i]}) statement.cond = statement.cond.And(builder.Eq{colName: (*(statement.idParam))[i]})
} else { } else {
statement.cond = statement.cond.And(builder.Eq{colName: ""}) statement.cond = statement.cond.And(builder.Eq{colName: ""})
} }

@ -0,0 +1,281 @@
// Copyright 2017 The Xorm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xorm
import (
"fmt"
"reflect"
"strconv"
"strings"
"time"
"github.com/go-xorm/core"
)
type tagContext struct {
tagName string
params []string
preTag, nextTag string
table *core.Table
col *core.Column
fieldValue reflect.Value
isIndex bool
isUnique bool
indexNames map[string]int
engine *Engine
hasCacheTag bool
hasNoCacheTag bool
ignoreNext bool
}
// tagHandler describes tag handler for XORM
type tagHandler func(ctx *tagContext) error
var (
// defaultTagHandlers enumerates all the default tag handler
defaultTagHandlers = map[string]tagHandler{
"<-": OnlyFromDBTagHandler,
"->": OnlyToDBTagHandler,
"PK": PKTagHandler,
"NULL": NULLTagHandler,
"NOT": IgnoreTagHandler,
"AUTOINCR": AutoIncrTagHandler,
"DEFAULT": DefaultTagHandler,
"CREATED": CreatedTagHandler,
"UPDATED": UpdatedTagHandler,
"DELETED": DeletedTagHandler,
"VERSION": VersionTagHandler,
"UTC": UTCTagHandler,
"LOCAL": LocalTagHandler,
"NOTNULL": NotNullTagHandler,
"INDEX": IndexTagHandler,
"UNIQUE": UniqueTagHandler,
"CACHE": CacheTagHandler,
"NOCACHE": NoCacheTagHandler,
}
)
func init() {
for k := range core.SqlTypes {
defaultTagHandlers[k] = SQLTypeTagHandler
}
}
// IgnoreTagHandler describes ignored tag handler
func IgnoreTagHandler(ctx *tagContext) error {
return nil
}
// OnlyFromDBTagHandler describes mapping direction tag handler
func OnlyFromDBTagHandler(ctx *tagContext) error {
ctx.col.MapType = core.ONLYFROMDB
return nil
}
// OnlyToDBTagHandler describes mapping direction tag handler
func OnlyToDBTagHandler(ctx *tagContext) error {
ctx.col.MapType = core.ONLYTODB
return nil
}
// PKTagHandler decribes primary key tag handler
func PKTagHandler(ctx *tagContext) error {
ctx.col.IsPrimaryKey = true
ctx.col.Nullable = false
return nil
}
// NULLTagHandler describes null tag handler
func NULLTagHandler(ctx *tagContext) error {
ctx.col.Nullable = (strings.ToUpper(ctx.preTag) != "NOT")
return nil
}
// NotNullTagHandler describes notnull tag handler
func NotNullTagHandler(ctx *tagContext) error {
ctx.col.Nullable = false
return nil
}
// AutoIncrTagHandler describes autoincr tag handler
func AutoIncrTagHandler(ctx *tagContext) error {
ctx.col.IsAutoIncrement = true
/*
if len(ctx.params) > 0 {
autoStartInt, err := strconv.Atoi(ctx.params[0])
if err != nil {
return err
}
ctx.col.AutoIncrStart = autoStartInt
} else {
ctx.col.AutoIncrStart = 1
}
*/
return nil
}
// DefaultTagHandler describes default tag handler
func DefaultTagHandler(ctx *tagContext) error {
if len(ctx.params) > 0 {
ctx.col.Default = ctx.params[0]
} else {
ctx.col.Default = ctx.nextTag
ctx.ignoreNext = true
}
return nil
}
// CreatedTagHandler describes created tag handler
func CreatedTagHandler(ctx *tagContext) error {
ctx.col.IsCreated = true
return nil
}
// VersionTagHandler describes version tag handler
func VersionTagHandler(ctx *tagContext) error {
ctx.col.IsVersion = true
ctx.col.Default = "1"
return nil
}
// UTCTagHandler describes utc tag handler
func UTCTagHandler(ctx *tagContext) error {
ctx.col.TimeZone = time.UTC
return nil
}
// LocalTagHandler describes local tag handler
func LocalTagHandler(ctx *tagContext) error {
if len(ctx.params) == 0 {
ctx.col.TimeZone = time.Local
} else {
var err error
ctx.col.TimeZone, err = time.LoadLocation(ctx.params[0])
if err != nil {
return err
}
}
return nil
}
// UpdatedTagHandler describes updated tag handler
func UpdatedTagHandler(ctx *tagContext) error {
ctx.col.IsUpdated = true
return nil
}
// DeletedTagHandler describes deleted tag handler
func DeletedTagHandler(ctx *tagContext) error {
ctx.col.IsDeleted = true
return nil
}
// IndexTagHandler describes index tag handler
func IndexTagHandler(ctx *tagContext) error {
if len(ctx.params) > 0 {
ctx.indexNames[ctx.params[0]] = core.IndexType
} else {
ctx.isIndex = true
}
return nil
}
// UniqueTagHandler describes unique tag handler
func UniqueTagHandler(ctx *tagContext) error {
if len(ctx.params) > 0 {
ctx.indexNames[ctx.params[0]] = core.UniqueType
} else {
ctx.isUnique = true
}
return nil
}
// SQLTypeTagHandler describes SQL Type tag handler
func SQLTypeTagHandler(ctx *tagContext) error {
ctx.col.SQLType = core.SQLType{Name: ctx.tagName}
if len(ctx.params) > 0 {
if ctx.tagName == core.Enum {
ctx.col.EnumOptions = make(map[string]int)
for k, v := range ctx.params {
v = strings.TrimSpace(v)
v = strings.Trim(v, "'")
ctx.col.EnumOptions[v] = k
}
} else if ctx.tagName == core.Set {
ctx.col.SetOptions = make(map[string]int)
for k, v := range ctx.params {
v = strings.TrimSpace(v)
v = strings.Trim(v, "'")
ctx.col.SetOptions[v] = k
}
} else {
var err error
if len(ctx.params) == 2 {
ctx.col.Length, err = strconv.Atoi(ctx.params[0])
if err != nil {
return err
}
ctx.col.Length2, err = strconv.Atoi(ctx.params[1])
if err != nil {
return err
}
} else if len(ctx.params) == 1 {
ctx.col.Length, err = strconv.Atoi(ctx.params[0])
if err != nil {
return err
}
}
}
}
return nil
}
// ExtendsTagHandler describes extends tag handler
func ExtendsTagHandler(ctx *tagContext) error {
var fieldValue = ctx.fieldValue
switch fieldValue.Kind() {
case reflect.Ptr:
f := fieldValue.Type().Elem()
if f.Kind() == reflect.Struct {
fieldPtr := fieldValue
fieldValue = fieldValue.Elem()
if !fieldValue.IsValid() || fieldPtr.IsNil() {
fieldValue = reflect.New(f).Elem()
}
}
fallthrough
case reflect.Struct:
parentTable, err := ctx.engine.mapType(fieldValue)
if err != nil {
return err
}
for _, col := range parentTable.Columns() {
col.FieldName = fmt.Sprintf("%v.%v", ctx.col.FieldName, col.FieldName)
ctx.table.AddColumn(col)
for indexName, indexType := range col.Indexes {
addIndex(indexName, ctx.table, col, indexType)
}
}
default:
//TODO: warning
}
return nil
}
// CacheTagHandler describes cache tag handler
func CacheTagHandler(ctx *tagContext) error {
if !ctx.hasCacheTag {
ctx.hasCacheTag = true
}
return nil
}
// NoCacheTagHandler describes nocache tag handler
func NoCacheTagHandler(ctx *tagContext) error {
if !ctx.hasNoCacheTag {
ctx.hasNoCacheTag = true
}
return nil
}

@ -17,7 +17,7 @@ import (
const ( const (
// Version show the xorm's version // Version show the xorm's version
Version string = "0.6.0.1022" Version string = "0.6.2.0401"
) )
func regDrvsNDialects() bool { func regDrvsNDialects() bool {
@ -86,6 +86,7 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) {
mutex: &sync.RWMutex{}, mutex: &sync.RWMutex{},
TagIdentifier: "xorm", TagIdentifier: "xorm",
TZLocation: time.Local, TZLocation: time.Local,
tagHandlers: defaultTagHandlers,
} }
logger := NewSimpleLogger(os.Stdout) logger := NewSimpleLogger(os.Stdout)

@ -455,10 +455,10 @@
"revisionTime": "2016-08-11T02:11:45Z" "revisionTime": "2016-08-11T02:11:45Z"
}, },
{ {
"checksumSHA1": "COlm4o3G1rUSqr33iumtjY1qKD8=", "checksumSHA1": "3FEBM0FYERf8jpaResApwcQpr40=",
"path": "github.com/go-xorm/xorm", "path": "github.com/go-xorm/xorm",
"revision": "1bc93ba022236fcc94092fa40105b96e1d1d2346", "revision": "7e70eb82224bc950d4fb936036e925a51947c245",
"revisionTime": "2017-02-20T09:51:59Z" "revisionTime": "2017-04-02T10:02:47Z"
}, },
{ {
"checksumSHA1": "1ft/4j5MFa7C9dPI9whL03HSUzk=", "checksumSHA1": "1ft/4j5MFa7C9dPI9whL03HSUzk=",

Loading…
Cancel
Save