| @@ -670,13 +670,30 @@ func (env *accessibleReposEnv) Repos(page, pageSize int) ([]*Repository, error) | |||
| Find(&repos) | |||
| } | |||
| func (env *accessibleReposEnv) MirrorRepos() ([]*Repository, error) { | |||
| repos := make([]*Repository, 0, 10) | |||
| return repos, x. | |||
| Select("`repository`.*"). | |||
| func (env *accessibleReposEnv) MirrorRepoIDs() ([]int64, error) { | |||
| repoIDs := make([]int64, 0, 10) | |||
| return repoIDs, x. | |||
| Table("repository"). | |||
| Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id AND `repository`.is_mirror=?", true). | |||
| Where(env.cond()). | |||
| GroupBy("`repository`.id"). | |||
| 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) | |||
| } | |||
| @@ -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/lunny/godbc](https://github.com/lunny/godbc) | |||
| * Oracle: [github.com/mattn/go-oci8](https://github.com/mattn/go-oci8) (experiment) | |||
| # Changelog | |||
| * **v0.6.2** | |||
| * refactor tag parse methods | |||
| * add Scan features to Get | |||
| * add QueryString method | |||
| * **v0.6.0** | |||
| * 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` | |||
| @@ -79,12 +82,6 @@ methods can use `builder.Cond` as parameter | |||
| # 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 | |||
| # Documents | |||
| @@ -119,19 +116,21 @@ type User struct { | |||
| 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 | |||
| 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 | |||
| 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 | |||
| affected, err := engine.Insert(&user) | |||
| @@ -153,6 +152,18 @@ has, err := engine.Get(&user) | |||
| // SELECT * FROM user LIMIT 1 | |||
| has, err := engine.Where("name = ?", name).Desc("id").Get(&user) | |||
| // 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 | |||
| @@ -54,6 +54,11 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作 | |||
| ## 更新日志 | |||
| * **v0.6.2** | |||
| * 重构Tag解析方式 | |||
| * Get方法新增类似Sacn的特性 | |||
| * 新增 QueryString 方法 | |||
| * **v0.6.0** | |||
| * 去除对 ql 的支持 | |||
| * 新增条件查询分析器 [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 | |||
| ## 文档 | |||
| @@ -121,13 +120,15 @@ type User struct { | |||
| err := engine.Sync2(new(User)) | |||
| ``` | |||
| * 最原始的也支持SQL语句查询,返回的结果类型为 []map[string][]byte | |||
| * `Query` 最原始的也支持SQL语句查询,返回的结果类型为 []map[string][]byte。`QueryString` 返回 []map[string]string | |||
| ```Go | |||
| results, err := engine.Query("select * from user") | |||
| results, err := engine.QueryString("select * from user") | |||
| ``` | |||
| * 执行一个SQL语句 | |||
| * `Exec` 执行一个SQL语句 | |||
| ```Go | |||
| 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 | |||
| has, err := engine.Where("name = ?", name).Desc("id").Get(&user) | |||
| // 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来组合使用 | |||
| @@ -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) | |||
| } | |||
| 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 | |||
| import ( | |||
| "errors" | |||
| "fmt" | |||
| "strconv" | |||
| "strings" | |||
| @@ -215,9 +216,9 @@ func (db *mssql) SqlType(c *core.Column) string { | |||
| switch t := c.SQLType.Name; t { | |||
| case core.Bool: | |||
| res = core.TinyInt | |||
| if c.Default == "true" { | |||
| if strings.EqualFold(c.Default, "true") { | |||
| c.Default = "1" | |||
| } else if c.Default == "false" { | |||
| } else { | |||
| c.Default = "0" | |||
| } | |||
| case core.Serial: | |||
| @@ -467,9 +468,10 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? | |||
| } | |||
| colName = strings.Trim(colName, "` ") | |||
| var isRegular bool | |||
| if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { | |||
| indexName = indexName[5+len(tableName):] | |||
| isRegular = true | |||
| } | |||
| var index *core.Index | |||
| @@ -478,6 +480,7 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? | |||
| index = new(core.Index) | |||
| index.Type = indexType | |||
| index.Name = indexName | |||
| index.IsRegular = isRegular | |||
| indexes[indexName] = index | |||
| } | |||
| index.AddColumn(colName) | |||
| @@ -526,3 +529,25 @@ func (db *mssql) ForUpdateSql(query string) string { | |||
| func (db *mssql) Filters() []core.Filter { | |||
| 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 ( | |||
| "crypto/tls" | |||
| "errors" | |||
| "fmt" | |||
| "regexp" | |||
| "strconv" | |||
| "strings" | |||
| "time" | |||
| @@ -486,3 +488,93 @@ func (db *mysql) GetIndexes(tableName string) (map[string]*core.Index, error) { | |||
| func (db *mysql) Filters() []core.Filter { | |||
| 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¶mN=valueN] | |||
| matches := dsnPattern.FindStringSubmatch(dataSourceName) | |||
| //tlsConfigRegister := make(map[string]*tls.Config) | |||
| names := dsnPattern.SubexpNames() | |||
| uri := &core.Uri{DbType: core.MYSQL} | |||
| for i, match := range matches { | |||
| switch names[i] { | |||
| case "dbname": | |||
| uri.DbName = match | |||
| case "params": | |||
| if len(match) > 0 { | |||
| kvs := strings.Split(match, "&") | |||
| for _, kv := range kvs { | |||
| splits := strings.Split(kv, "=") | |||
| if len(splits) == 2 { | |||
| switch splits[0] { | |||
| case "charset": | |||
| uri.Charset = splits[1] | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| return uri, nil | |||
| } | |||
| @@ -5,7 +5,9 @@ | |||
| package xorm | |||
| import ( | |||
| "errors" | |||
| "fmt" | |||
| "regexp" | |||
| "strconv" | |||
| "strings" | |||
| @@ -822,6 +824,12 @@ func (db *oracle) GetIndexes(tableName string) (map[string]*core.Index, error) { | |||
| 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" { | |||
| indexType = core.UniqueType | |||
| } else { | |||
| @@ -834,6 +842,7 @@ func (db *oracle) GetIndexes(tableName string) (map[string]*core.Index, error) { | |||
| index = new(core.Index) | |||
| index.Type = indexType | |||
| index.Name = indexName | |||
| index.IsRegular = isRegular | |||
| indexes[indexName] = index | |||
| } | |||
| index.AddColumn(colName) | |||
| @@ -844,3 +853,54 @@ func (db *oracle) GetIndexes(tableName string) (map[string]*core.Index, error) { | |||
| func (db *oracle) Filters() []core.Filter { | |||
| 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¶mN=valueN] | |||
| matches := dsnPattern.FindStringSubmatch(dataSourceName) | |||
| //tlsConfigRegister := make(map[string]*tls.Config) | |||
| names := dsnPattern.SubexpNames() | |||
| for i, match := range matches { | |||
| switch names[i] { | |||
| case "dbname": | |||
| db.DbName = match | |||
| } | |||
| } | |||
| if db.DbName == "" { | |||
| return nil, errors.New("dbname is empty") | |||
| } | |||
| return db, nil | |||
| } | |||
| 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 | |||
| import ( | |||
| "errors" | |||
| "fmt" | |||
| "net/url" | |||
| "sort" | |||
| "strconv" | |||
| "strings" | |||
| @@ -1075,9 +1078,10 @@ func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error) | |||
| } | |||
| cs := strings.Split(indexdef, "(") | |||
| colNames = strings.Split(cs[1][0:len(cs[1])-1], ",") | |||
| var isRegular bool | |||
| if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { | |||
| newIdxName := indexName[5+len(tableName):] | |||
| isRegular = true | |||
| if newIdxName != "" { | |||
| indexName = newIdxName | |||
| } | |||
| @@ -1087,6 +1091,7 @@ func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error) | |||
| for _, colName := range colNames { | |||
| index.Cols = append(index.Cols, strings.Trim(colName, `" `)) | |||
| } | |||
| index.IsRegular = isRegular | |||
| indexes[index.Name] = index | |||
| } | |||
| return indexes, nil | |||
| @@ -1095,3 +1100,107 @@ func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error) | |||
| func (db *postgres) Filters() []core.Filter { | |||
| 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], "` []") | |||
| var isRegular bool | |||
| if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { | |||
| index.Name = indexName[5+len(tableName):] | |||
| isRegular = true | |||
| } else { | |||
| index.Name = indexName | |||
| } | |||
| @@ -425,6 +427,7 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*core.Index, error) | |||
| for _, col := range colIndexes { | |||
| index.Cols = append(index.Cols, strings.Trim(col, "` []")) | |||
| } | |||
| index.IsRegular = isRegular | |||
| 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 { | |||
| 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 | |||
| Xorm also support raw sql execution: | |||
| XORM also support raw SQL execution: | |||
| 1. query a SQL string, the returned results is []map[string][]byte | |||
| @@ -36,7 +36,7 @@ Xorm also support raw sql execution: | |||
| 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 | |||
| @@ -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 | |||
| sliceOfStructs := new(Struct) | |||
| err := engine.Find(sliceOfStructs) | |||
| var sliceOfStructs []Struct | |||
| err := engine.Find(&sliceOfStructs) | |||
| // 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, | |||
| another is Rows | |||
| @@ -91,20 +99,31 @@ another is Rows | |||
| counts, err := engine.Count(&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 | |||
| The above 7 methods could use with condition methods chainable. | |||
| Attention: the above 7 methods should be the last chainable method. | |||
| The above 8 methods could use with condition methods chainable. | |||
| 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 | |||
| 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 | |||
| engine.In("id", 1, 2, 3).Find(&users) | |||
| // 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) | |||
| 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 * FROM user LIMIT .. OFFSET 0 //for other databases | |||
| 5. Sql, let you custom SQL | |||
| 5. SQL, let you custom SQL | |||
| var users []User | |||
| engine.Sql("select * from user").Find(&users) | |||
| engine.SQL("select * from user").Find(&users) | |||
| 6. Cols, Omit, Distinct | |||
| @@ -44,6 +44,8 @@ type Engine struct { | |||
| DatabaseTZ *time.Location // The timezone of the database | |||
| disableGlobalCache bool | |||
| tagHandlers map[string]tagHandler | |||
| } | |||
| // 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 | |||
| func (engine *Engine) MapCacher(bean interface{}, cacher core.Cacher) { | |||
| func (engine *Engine) MapCacher(bean interface{}, cacher core.Cacher) error { | |||
| v := rValue(bean) | |||
| tb := engine.autoMapType(v) | |||
| tb, err := engine.autoMapType(v) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| tb.Cacher = cacher | |||
| return nil | |||
| } | |||
| // 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{}) { | |||
| if engine.showSQL && !engine.showExecTime { | |||
| if len(sqlArgs) > 0 { | |||
| engine.logger.Infof("[sql] %v [args] %v", sqlStr, sqlArgs) | |||
| engine.logger.Infof("[SQL] %v %v", sqlStr, sqlArgs) | |||
| } 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() | |||
| execDuration := time.Since(b4ExecTime) | |||
| 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 { | |||
| engine.logger.Infof("[sql] %s - took: %v", sqlStr, execDuration) | |||
| engine.logger.Infof("[SQL] %s - took: %v", sqlStr, execDuration) | |||
| } | |||
| return stmt, res, err | |||
| } | |||
| @@ -774,13 +781,18 @@ func (engine *Engine) Having(conditions string) *Session { | |||
| 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() | |||
| engine.mutex.Lock() | |||
| defer engine.mutex.Unlock() | |||
| table, ok := engine.Tables[t] | |||
| if !ok { | |||
| table = engine.mapType(v) | |||
| var err error | |||
| table, err = engine.mapType(v) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| engine.Tables[t] = table | |||
| if engine.Cacher != nil { | |||
| 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 | |||
| func (engine *Engine) GobRegister(v interface{}) *Engine { | |||
| //fmt.Printf("Type: %[1]T => Data: %[1]#v\n", v) | |||
| gob.Register(v) | |||
| return engine | |||
| } | |||
| @@ -806,10 +817,19 @@ type Table struct { | |||
| 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 | |||
| func (engine *Engine) TableInfo(bean interface{}) *Table { | |||
| 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) { | |||
| @@ -842,7 +862,7 @@ var ( | |||
| 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() | |||
| table := engine.newTable() | |||
| if tb, ok := v.Interface().(TableName); ok { | |||
| @@ -861,7 +881,6 @@ func (engine *Engine) mapType(v reflect.Value) *core.Table { | |||
| table.Type = t | |||
| var idFieldColName string | |||
| var err error | |||
| var hasCacheTag, hasNoCacheTag bool | |||
| for i := 0; i < t.NumField(); i++ { | |||
| @@ -881,186 +900,94 @@ func (engine *Engine) mapType(v reflect.Value) *core.Table { | |||
| if tags[0] == "-" { | |||
| continue | |||
| } | |||
| var ctx = tagContext{ | |||
| table: table, | |||
| col: col, | |||
| fieldValue: fieldValue, | |||
| indexNames: make(map[string]int), | |||
| engine: engine, | |||
| } | |||
| if strings.ToUpper(tags[0]) == "EXTENDS" { | |||
| 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 := 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 | |||
| if err := ExtendsTagHandler(&ctx); err != nil { | |||
| return nil, err | |||
| } | |||
| continue | |||
| } | |||
| indexNames := make(map[string]int) | |||
| var isIndex, isUnique bool | |||
| var preKey string | |||
| for j, key := range tags { | |||
| if ctx.ignoreNext { | |||
| ctx.ignoreNext = false | |||
| continue | |||
| } | |||
| k := strings.ToUpper(key) | |||
| switch { | |||
| 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 | |||
| ctx.tagName = k | |||
| autoStart := k[len("AUTOINCR")+1 : len(k)-1] | |||
| autoStartInt, err := strconv.Atoi(autoStart) | |||
| if err != nil { | |||
| engine.LogError(err) | |||
| pStart := strings.Index(k, "(") | |||
| if pStart == 0 { | |||
| return nil, errors.New("( could not be the first charactor") | |||
| } | |||
| col.AutoIncrStart = autoStartInt*/ | |||
| case k == "AUTOINCR": | |||
| col.IsAutoIncrement = true | |||
| //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 | |||
| if pStart > -1 { | |||
| if !strings.HasSuffix(k, ")") { | |||
| return nil, errors.New("cannot match ) charactor") | |||
| } | |||
| case k == "NOCACHE": | |||
| if !hasNoCacheTag { | |||
| hasNoCacheTag = true | |||
| ctx.tagName = k[:pStart] | |||
| 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": | |||
| default: | |||
| if strings.HasPrefix(k, "'") && strings.HasSuffix(k, "'") { | |||
| 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 { | |||
| if strings.HasPrefix(key, "'") && strings.HasSuffix(key, "'") { | |||
| col.Name = key[1 : len(key)-1] | |||
| } else { | |||
| if _, ok := core.SqlTypes[k]; ok { | |||
| col.SQLType = core.SQLType{Name: k} | |||
| } else if key != col.Default { | |||
| col.Name = key | |||
| } | |||
| col.Name = key | |||
| } | |||
| engine.dialect.SqlType(col) | |||
| } | |||
| preKey = k | |||
| if ctx.hasCacheTag { | |||
| hasCacheTag = true | |||
| } | |||
| if ctx.hasNoCacheTag { | |||
| hasNoCacheTag = true | |||
| } | |||
| } | |||
| if col.SQLType.Name == "" { | |||
| col.SQLType = core.Type2SQLType(fieldType) | |||
| } | |||
| engine.dialect.SqlType(col) | |||
| if col.Length == 0 { | |||
| col.Length = col.SQLType.DefaultLength | |||
| } | |||
| if col.Length2 == 0 { | |||
| col.Length2 = col.SQLType.DefaultLength2 | |||
| } | |||
| if col.Name == "" { | |||
| col.Name = engine.ColumnMapper.Obj2Table(t.Field(i).Name) | |||
| } | |||
| if isUnique { | |||
| indexNames[col.Name] = core.UniqueType | |||
| } else if isIndex { | |||
| indexNames[col.Name] = core.IndexType | |||
| if ctx.isUnique { | |||
| ctx.indexNames[col.Name] = core.UniqueType | |||
| } else if ctx.isIndex { | |||
| ctx.indexNames[col.Name] = core.IndexType | |||
| } | |||
| for indexName, indexType := range indexNames { | |||
| for indexName, indexType := range ctx.indexNames { | |||
| addIndex(indexName, table, col, indexType) | |||
| } | |||
| } | |||
| @@ -1114,7 +1041,7 @@ func (engine *Engine) mapType(v reflect.Value) *core.Table { | |||
| table.Cacher = nil | |||
| } | |||
| return table | |||
| return table, nil | |||
| } | |||
| // 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 | |||
| 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) | |||
| table := engine.autoMapType(v) | |||
| table, err := engine.autoMapType(v) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| pk := make([]interface{}, len(table.PrimaryKeys)) | |||
| for i, col := range table.PKColumns() { | |||
| pkField := v.FieldByName(col.FieldName) | |||
| @@ -1166,7 +1106,7 @@ func (engine *Engine) IDOfV(rv reflect.Value) core.PK { | |||
| pk[i] = pkField.Uint() | |||
| } | |||
| } | |||
| return core.PK(pk) | |||
| return core.PK(pk), nil | |||
| } | |||
| // CreateIndexes create indexes | |||
| @@ -1187,13 +1127,6 @@ func (engine *Engine) getCacher2(table *core.Table) core.Cacher { | |||
| return table.Cacher | |||
| } | |||
| func (engine *Engine) getCacher(v reflect.Value) core.Cacher { | |||
| if table := engine.autoMapType(v); table != nil { | |||
| return table.Cacher | |||
| } | |||
| return engine.Cacher | |||
| } | |||
| // ClearCacheBean if enabled cache, clear the cache bean | |||
| func (engine *Engine) ClearCacheBean(bean interface{}, id string) error { | |||
| v := rValue(bean) | |||
| @@ -1202,7 +1135,10 @@ func (engine *Engine) ClearCacheBean(bean interface{}, id string) error { | |||
| return errors.New("error params") | |||
| } | |||
| tableName := engine.tbName(v) | |||
| table := engine.autoMapType(v) | |||
| table, err := engine.autoMapType(v) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| cacher := table.Cacher | |||
| if cacher == nil { | |||
| cacher = engine.Cacher | |||
| @@ -1223,7 +1159,11 @@ func (engine *Engine) ClearCache(beans ...interface{}) error { | |||
| return errors.New("error params") | |||
| } | |||
| tableName := engine.tbName(v) | |||
| table := engine.autoMapType(v) | |||
| table, err := engine.autoMapType(v) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| cacher := table.Cacher | |||
| if cacher == nil { | |||
| cacher = engine.Cacher | |||
| @@ -1243,7 +1183,11 @@ func (engine *Engine) Sync(beans ...interface{}) error { | |||
| for _, bean := range beans { | |||
| v := rValue(bean) | |||
| 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() | |||
| defer s.Close() | |||
| @@ -1426,6 +1370,13 @@ func (engine *Engine) Query(sql string, paramStr ...interface{}) (resultsSlice [ | |||
| 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 | |||
| func (engine *Engine) Insert(beans ...interface{}) (int64, error) { | |||
| 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¶mN=valueN] | |||
| matches := dsnPattern.FindStringSubmatch(dataSourceName) | |||
| //tlsConfigRegister := make(map[string]*tls.Config) | |||
| names := dsnPattern.SubexpNames() | |||
| for i, match := range matches { | |||
| switch names[i] { | |||
| case "dbname": | |||
| db.DbName = match | |||
| } | |||
| } | |||
| if db.DbName == "" { | |||
| return nil, errors.New("dbname is empty") | |||
| } | |||
| return db, nil | |||
| } | |||
| @@ -180,6 +180,20 @@ func isStructZero(v reflect.Value) bool { | |||
| 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 { | |||
| var v interface{} | |||
| 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¶mN=valueN] | |||
| matches := dsnPattern.FindStringSubmatch(dataSourceName) | |||
| //tlsConfigRegister := make(map[string]*tls.Config) | |||
| names := dsnPattern.SubexpNames() | |||
| uri := &core.Uri{DbType: core.MYSQL} | |||
| for i, match := range matches { | |||
| switch names[i] { | |||
| case "dbname": | |||
| uri.DbName = match | |||
| case "params": | |||
| if len(match) > 0 { | |||
| kvs := strings.Split(match, "&") | |||
| for _, kv := range kvs { | |||
| splits := strings.Split(kv, "=") | |||
| if len(splits) == 2 { | |||
| switch splits[0] { | |||
| case "charset": | |||
| uri.Charset = splits[1] | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| return uri, nil | |||
| } | |||
| @@ -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 { | |||
| NoTypeCheck bool | |||
| session *Session | |||
| stmt *core.Stmt | |||
| rows *core.Rows | |||
| fields []string | |||
| fieldsCount int | |||
| beanType reflect.Type | |||
| lastError error | |||
| session *Session | |||
| stmt *core.Stmt | |||
| rows *core.Rows | |||
| fields []string | |||
| beanType reflect.Type | |||
| lastError error | |||
| } | |||
| func newRows(session *Session, bean interface{}) (*Rows, error) { | |||
| @@ -82,7 +81,6 @@ func newRows(session *Session, bean interface{}) (*Rows, error) { | |||
| rows.Close() | |||
| return nil, err | |||
| } | |||
| rows.fieldsCount = len(rows.fields) | |||
| 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) | |||
| } | |||
| _, 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 | |||
| } | |||
| @@ -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 | |||
| } | |||
| var newElemFunc func() reflect.Value | |||
| var newElemFunc func(fields []string) reflect.Value | |||
| elemType := containerValue.Type().Elem() | |||
| var isPointer bool | |||
| if elemType.Kind() == reflect.Ptr { | |||
| newElemFunc = func() reflect.Value { | |||
| return reflect.New(elemType.Elem()) | |||
| } | |||
| } else { | |||
| newElemFunc = func() reflect.Value { | |||
| return reflect.New(elemType) | |||
| isPointer = true | |||
| elemType = elemType.Elem() | |||
| } | |||
| if elemType.Kind() == reflect.Ptr { | |||
| return errors.New("pointer to pointer is not supported") | |||
| } | |||
| 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 | |||
| if containerValue.Kind() == reflect.Slice { | |||
| if elemType.Kind() == reflect.Ptr { | |||
| containerValueSetFunc = func(newValue *reflect.Value, pk core.PK) error { | |||
| containerValue.Set(reflect.Append(containerValue, reflect.ValueOf(newValue.Interface()))) | |||
| return nil | |||
| } | |||
| } else { | |||
| containerValueSetFunc = func(newValue *reflect.Value, pk core.PK) error { | |||
| containerValue.Set(reflect.Append(containerValue, reflect.Indirect(reflect.ValueOf(newValue.Interface())))) | |||
| return nil | |||
| containerValueSetFunc = func(newValue *reflect.Value, pk core.PK) error { | |||
| if isPointer { | |||
| containerValue.Set(reflect.Append(containerValue, newValue.Elem().Addr())) | |||
| } else { | |||
| containerValue.Set(reflect.Append(containerValue, newValue.Elem())) | |||
| } | |||
| return nil | |||
| } | |||
| } else { | |||
| 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") | |||
| } | |||
| if elemType.Kind() == reflect.Ptr { | |||
| containerValueSetFunc = func(newValue *reflect.Value, pk core.PK) error { | |||
| keyValue := reflect.New(keyType) | |||
| err := convertPKToValue(table, keyValue.Interface(), pk) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| containerValue.SetMapIndex(keyValue.Elem(), reflect.ValueOf(newValue.Interface())) | |||
| return nil | |||
| containerValueSetFunc = func(newValue *reflect.Value, pk core.PK) error { | |||
| keyValue := reflect.New(keyType) | |||
| err := convertPKToValue(table, keyValue.Interface(), pk) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| } else { | |||
| containerValueSetFunc = func(newValue *reflect.Value, pk core.PK) error { | |||
| keyValue := reflect.New(keyType) | |||
| err := convertPKToValue(table, keyValue.Interface(), pk) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| containerValue.SetMapIndex(keyValue.Elem(), reflect.Indirect(reflect.ValueOf(newValue.Interface()))) | |||
| return nil | |||
| if isPointer { | |||
| containerValue.SetMapIndex(keyValue.Elem(), newValue.Elem().Addr()) | |||
| } else { | |||
| containerValue.SetMapIndex(keyValue.Elem(), newValue.Elem()) | |||
| } | |||
| return nil | |||
| } | |||
| } | |||
| var newValue = newElemFunc() | |||
| dataStruct := rValue(newValue.Interface()) | |||
| if dataStruct.Kind() == reflect.Struct { | |||
| return session.rows2Beans(rawRows, fields, len(fields), session.Engine.autoMapType(dataStruct), newElemFunc, containerValueSetFunc) | |||
| if elemType.Kind() == reflect.Struct { | |||
| var newValue = newElemFunc(fields) | |||
| dataStruct := rValue(newValue.Interface()) | |||
| tb, err := session.Engine.autoMapType(dataStruct) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return session.rows2Beans(rawRows, fields, len(fields), tb, newElemFunc, containerValueSetFunc) | |||
| } | |||
| for rawRows.Next() { | |||
| var newValue = newElemFunc() | |||
| var newValue = newElemFunc(fields) | |||
| 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 | |||
| } | |||
| @@ -394,7 +411,10 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in | |||
| if rv.Kind() != reflect.Ptr { | |||
| rv = rv.Addr() | |||
| } | |||
| id := session.Engine.IdOfV(rv) | |||
| id, err := session.Engine.idOfV(rv) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| sid, err := id.ToString() | |||
| if err != nil { | |||
| return err | |||
| @@ -20,7 +20,14 @@ func (session *Session) Get(bean interface{}) (bool, error) { | |||
| 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 args []interface{} | |||
| @@ -36,7 +43,7 @@ func (session *Session) Get(bean interface{}) (bool, error) { | |||
| 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 && | |||
| !session.Statement.unscoped { | |||
| 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 err error | |||
| session.queryPreprocess(&sqlStr, args...) | |||
| if session.IsAutoCommit { | |||
| _, rawRows, err = session.innerQuery(sqlStr, args...) | |||
| } else { | |||
| @@ -65,10 +73,24 @@ func (session *Session) nocacheGet(bean interface{}, sqlStr string, args ...inte | |||
| defer rawRows.Close() | |||
| if rawRows.Next() { | |||
| fields, err := rawRows.Columns() | |||
| if err == nil { | |||
| _, err = session.row2Bean(rawRows, fields, len(fields), bean) | |||
| switch beanKind { | |||
| case reflect.Struct: | |||
| 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 false, nil | |||
| @@ -145,20 +167,8 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf | |||
| } | |||
| cacheBean := cacher.GetBean(tableName, sid) | |||
| 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 | |||
| has, err = session.nocacheGet(cacheBean, sqlStr, args...) | |||
| has, err = session.nocacheGet(reflect.Struct, cacheBean, sqlStr, args...) | |||
| if err != nil || !has { | |||
| return has, err | |||
| } | |||
| @@ -210,13 +210,29 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error | |||
| } | |||
| cleanupProcessorsClosures(&session.beforeClosures) | |||
| statement := fmt.Sprintf("INSERT INTO %s (%v%v%v) VALUES (%v)", | |||
| session.Engine.Quote(session.Statement.TableName()), | |||
| session.Engine.QuoteStr(), | |||
| strings.Join(colNames, session.Engine.QuoteStr()+", "+session.Engine.QuoteStr()), | |||
| session.Engine.QuoteStr(), | |||
| strings.Join(colMultiPlaces, "),(")) | |||
| var sql = "INSERT INTO %s (%v%v%v) VALUES (%v)" | |||
| var statement string | |||
| if session.Engine.dialect.DBType() == core.ORACLE { | |||
| sql = "INSERT ALL INTO %s (%v%v%v) VALUES (%v) SELECT 1 FROM DUAL" | |||
| temp := fmt.Sprintf(") INTO %s (%v%v%v) VALUES (", | |||
| 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...) | |||
| if err != nil { | |||
| return 0, err | |||
| @@ -309,8 +325,8 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { | |||
| // remove the expr columns | |||
| for i, colName := range colNames { | |||
| if colName == v.colName { | |||
| colNames = append(colNames[:i], colNames[i+1:]...) | |||
| args = append(args[:i], args[i+1:]...) | |||
| colNames = append(colNames[:i], colNames[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) | |||
| } | |||
| colPlaces := strings.Repeat("?, ", len(colNames)-len(exprColumns)) | |||
| colPlaces := strings.Repeat("?, ", len(colNames) - len(exprColumns)) | |||
| if len(exprColVals) > 0 { | |||
| colPlaces = colPlaces + strings.Join(exprColVals, ", ") | |||
| } else { | |||
| colPlaces = colPlaces[0 : len(colPlaces)-2] | |||
| colPlaces = colPlaces[0 : len(colPlaces) - 2] | |||
| } | |||
| 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) | |||
| } | |||
| // 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) { | |||
| defer session.resetStatement() | |||
| if session.IsAutoClose { | |||
| @@ -80,6 +80,15 @@ func (session *Session) Query(sqlStr string, paramStr ...interface{}) (resultsSl | |||
| 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 | |||
| // ============================= | |||
| @@ -306,7 +306,10 @@ func (session *Session) Sync2(beans ...interface{}) error { | |||
| for _, bean := range beans { | |||
| v := rValue(bean) | |||
| table := engine.mapType(v) | |||
| table, err := engine.mapType(v) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| structTables = append(structTables, table) | |||
| var tbName = session.tbNameNoSchema(table) | |||
| @@ -123,7 +123,7 @@ func (session *Session) SumsInt(bean interface{}, columnNames ...string) ([]int6 | |||
| session.queryPreprocess(&sqlStr, args...) | |||
| var err error | |||
| var res = make([]int64, 0, len(columnNames)) | |||
| var res = make([]int64, len(columnNames), len(columnNames)) | |||
| if session.IsAutoCommit { | |||
| err = session.DB().QueryRow(sqlStr, args...).ScanSlice(&res) | |||
| } else { | |||
| @@ -253,48 +253,59 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 | |||
| var condSQL string | |||
| cond := session.Statement.cond.And(autoCond) | |||
| doIncVer := false | |||
| var doIncVer = (table != nil && table.Version != "" && session.Statement.checkVersion) | |||
| var verValue *reflect.Value | |||
| if table != nil && table.Version != "" && session.Statement.checkVersion { | |||
| if doIncVer { | |||
| verValue, err = table.VersionColumn().ValueOf(bean) | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| cond = cond.And(builder.Eq{session.Engine.Quote(table.Version): verValue.Interface()}) | |||
| condSQL, condArgs, _ = builder.ToSQL(cond) | |||
| if len(condSQL) > 0 { | |||
| condSQL = "WHERE " + condSQL | |||
| } | |||
| if st.LimitN > 0 { | |||
| condSQL = condSQL + fmt.Sprintf(" LIMIT %d", st.LimitN) | |||
| } | |||
| colNames = append(colNames, session.Engine.Quote(table.Version)+" = "+session.Engine.Quote(table.Version)+" + 1") | |||
| } | |||
| sqlStr = fmt.Sprintf("UPDATE %v SET %v, %v %v", | |||
| session.Engine.Quote(session.Statement.TableName()), | |||
| strings.Join(colNames, ", "), | |||
| session.Engine.Quote(table.Version)+" = "+session.Engine.Quote(table.Version)+" + 1", | |||
| condSQL) | |||
| condSQL, condArgs, _ = builder.ToSQL(cond) | |||
| if len(condSQL) > 0 { | |||
| condSQL = "WHERE " + condSQL | |||
| } | |||
| doIncVer = true | |||
| } else { | |||
| condSQL, condArgs, _ = builder.ToSQL(cond) | |||
| if len(condSQL) > 0 { | |||
| condSQL = "WHERE " + condSQL | |||
| } | |||
| if st.OrderStr != "" { | |||
| condSQL = condSQL + fmt.Sprintf(" ORDER BY %v", st.OrderStr) | |||
| } | |||
| 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) | |||
| } 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...)...) | |||
| if err != nil { | |||
| 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 | |||
| Start int | |||
| LimitN int | |||
| IdParam *core.PK | |||
| idParam *core.PK | |||
| OrderStr string | |||
| JoinStr string | |||
| joinArgs []interface{} | |||
| @@ -91,7 +91,7 @@ func (statement *Statement) Init() { | |||
| statement.columnMap = make(map[string]bool) | |||
| statement.AltTableName = "" | |||
| statement.tableName = "" | |||
| statement.IdParam = nil | |||
| statement.idParam = nil | |||
| statement.RawSQL = "" | |||
| statement.RawParams = make([]interface{}, 0) | |||
| statement.UseCache = true | |||
| @@ -195,29 +195,26 @@ func (statement *Statement) Or(query interface{}, args ...interface{}) *Statemen | |||
| // In generate "Where column IN (?) " statement | |||
| func (statement *Statement) In(column string, args ...interface{}) *Statement { | |||
| if len(args) == 0 { | |||
| return statement | |||
| } | |||
| in := builder.In(column, args...) | |||
| in := builder.In(statement.Engine.Quote(column), args...) | |||
| statement.cond = statement.cond.And(in) | |||
| return statement | |||
| } | |||
| // NotIn generate "Where column NOT IN (?) " statement | |||
| func (statement *Statement) NotIn(column string, args ...interface{}) *Statement { | |||
| if len(args) == 0 { | |||
| return statement | |||
| } | |||
| in := builder.NotIn(column, args...) | |||
| statement.cond = statement.cond.And(in) | |||
| notIn := builder.NotIn(statement.Engine.Quote(column), args...) | |||
| statement.cond = statement.cond.And(notIn) | |||
| return statement | |||
| } | |||
| func (statement *Statement) setRefValue(v reflect.Value) { | |||
| statement.RefTable = statement.Engine.autoMapType(reflect.Indirect(v)) | |||
| func (statement *Statement) setRefValue(v reflect.Value) error { | |||
| var err error | |||
| statement.RefTable, err = statement.Engine.autoMapType(reflect.Indirect(v)) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| statement.tableName = statement.Engine.tbName(v) | |||
| return nil | |||
| } | |||
| // 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 { | |||
| statement.AltTableName = tableNameOrBean.(string) | |||
| } 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) | |||
| } | |||
| return statement | |||
| @@ -418,7 +420,11 @@ func buildUpdates(engine *Engine, table *core.Table, bean interface{}, | |||
| if fieldValue == reflect.Zero(fieldType) { | |||
| 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 | |||
| } | |||
| } | |||
| @@ -433,13 +439,16 @@ func buildUpdates(engine *Engine, table *core.Table, bean interface{}, | |||
| } else if col.SQLType.IsBlob() { | |||
| var bytes []byte | |||
| var err error | |||
| if (fieldType.Kind() == reflect.Array || fieldType.Kind() == reflect.Slice) && | |||
| if fieldType.Kind() == reflect.Slice && | |||
| fieldType.Elem().Kind() == reflect.Uint8 { | |||
| if fieldValue.Len() > 0 { | |||
| val = fieldValue.Bytes() | |||
| } else { | |||
| continue | |||
| } | |||
| } else if fieldType.Kind() == reflect.Array && | |||
| fieldType.Elem().Kind() == reflect.Uint8 { | |||
| val = fieldValue.Slice(0, 0).Interface() | |||
| } else { | |||
| bytes, err = json.Marshal(fieldValue.Interface()) | |||
| 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) { | |||
| continue | |||
| } | |||
| @@ -706,13 +717,6 @@ func (statement *Statement) TableName() string { | |||
| 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 = ?" | |||
| func (statement *Statement) ID(id interface{}) *Statement { | |||
| idValue := reflect.ValueOf(id) | |||
| @@ -721,23 +725,23 @@ func (statement *Statement) ID(id interface{}) *Statement { | |||
| switch idType { | |||
| case ptrPkType: | |||
| if pkPtr, ok := (id).(*core.PK); ok { | |||
| statement.IdParam = pkPtr | |||
| statement.idParam = pkPtr | |||
| return statement | |||
| } | |||
| case pkType: | |||
| if pk, ok := (id).(core.PK); ok { | |||
| statement.IdParam = &pk | |||
| statement.idParam = &pk | |||
| return statement | |||
| } | |||
| } | |||
| switch idType.Kind() { | |||
| case reflect.String: | |||
| statement.IdParam = &core.PK{idValue.Convert(reflect.TypeOf("")).Interface()} | |||
| statement.idParam = &core.PK{idValue.Convert(reflect.TypeOf("")).Interface()} | |||
| return statement | |||
| } | |||
| statement.IdParam = &core.PK{id} | |||
| statement.idParam = &core.PK{id} | |||
| return statement | |||
| } | |||
| @@ -1120,7 +1124,11 @@ func (statement *Statement) genConds(bean interface{}) (string, []interface{}, e | |||
| } | |||
| 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 | |||
| if len(statement.selectStr) > 0 { | |||
| @@ -1139,14 +1147,22 @@ func (statement *Statement) genGetSQL(bean interface{}) (string, []interface{}) | |||
| if len(columnStr) == 0 { | |||
| if len(statement.GroupByStr) > 0 { | |||
| 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...) | |||
| } | |||
| @@ -1172,7 +1188,7 @@ func (statement *Statement) genSumSQL(bean interface{}, columns ...string) (stri | |||
| var sumStrs = make([]string, 0, len(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) | |||
| @@ -1182,7 +1198,7 @@ func (statement *Statement) genSumSQL(bean interface{}, columns ...string) (stri | |||
| func (statement *Statement) genSelectSQL(columnStr, condSQL string) (a string) { | |||
| var distinct string | |||
| if statement.IsDistinct { | |||
| if statement.IsDistinct && !strings.HasPrefix(columnStr, "count") { | |||
| distinct = "DISTINCT " | |||
| } | |||
| @@ -1289,14 +1305,14 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string) (a string) { | |||
| } | |||
| func (statement *Statement) processIDParam() { | |||
| if statement.IdParam == nil { | |||
| if statement.idParam == nil { | |||
| return | |||
| } | |||
| for i, col := range statement.RefTable.PKColumns() { | |||
| var colName = statement.colName(col, statement.TableName()) | |||
| if i < len(*(statement.IdParam)) { | |||
| statement.cond = statement.cond.And(builder.Eq{colName: (*(statement.IdParam))[i]}) | |||
| if i < len(*(statement.idParam)) { | |||
| statement.cond = statement.cond.And(builder.Eq{colName: (*(statement.idParam))[i]}) | |||
| } else { | |||
| 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 ( | |||
| // Version show the xorm's version | |||
| Version string = "0.6.0.1022" | |||
| Version string = "0.6.2.0401" | |||
| ) | |||
| func regDrvsNDialects() bool { | |||
| @@ -86,6 +86,7 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { | |||
| mutex: &sync.RWMutex{}, | |||
| TagIdentifier: "xorm", | |||
| TZLocation: time.Local, | |||
| tagHandlers: defaultTagHandlers, | |||
| } | |||
| logger := NewSimpleLogger(os.Stdout) | |||
| @@ -455,10 +455,10 @@ | |||
| "revisionTime": "2016-08-11T02:11:45Z" | |||
| }, | |||
| { | |||
| "checksumSHA1": "COlm4o3G1rUSqr33iumtjY1qKD8=", | |||
| "checksumSHA1": "3FEBM0FYERf8jpaResApwcQpr40=", | |||
| "path": "github.com/go-xorm/xorm", | |||
| "revision": "1bc93ba022236fcc94092fa40105b96e1d1d2346", | |||
| "revisionTime": "2017-02-20T09:51:59Z" | |||
| "revision": "7e70eb82224bc950d4fb936036e925a51947c245", | |||
| "revisionTime": "2017-04-02T10:02:47Z" | |||
| }, | |||
| { | |||
| "checksumSHA1": "1ft/4j5MFa7C9dPI9whL03HSUzk=", | |||