| @@ -45,6 +45,10 @@ func (b *Builder) selectWriteTo(w Writer) error { | |||||
| } | } | ||||
| } | } | ||||
| if !b.cond.IsValid() { | |||||
| return nil | |||||
| } | |||||
| if _, err := fmt.Fprint(w, " WHERE "); err != nil { | if _, err := fmt.Fprint(w, " WHERE "); err != nil { | ||||
| return err | return err | ||||
| } | } | ||||
| @@ -25,7 +25,9 @@ func And(conds ...Cond) Cond { | |||||
| func (and condAnd) WriteTo(w Writer) error { | func (and condAnd) WriteTo(w Writer) error { | ||||
| for i, cond := range and { | for i, cond := range and { | ||||
| _, isOr := cond.(condOr) | _, isOr := cond.(condOr) | ||||
| if isOr { | |||||
| _, isExpr := cond.(expr) | |||||
| wrap := isOr || isExpr | |||||
| if wrap { | |||||
| fmt.Fprint(w, "(") | fmt.Fprint(w, "(") | ||||
| } | } | ||||
| @@ -34,7 +36,7 @@ func (and condAnd) WriteTo(w Writer) error { | |||||
| return err | return err | ||||
| } | } | ||||
| if isOr { | |||||
| if wrap { | |||||
| fmt.Fprint(w, ")") | fmt.Fprint(w, ")") | ||||
| } | } | ||||
| @@ -1,11 +1,12 @@ | |||||
| package core | package core | ||||
| import ( | import ( | ||||
| "bytes" | |||||
| "encoding/gob" | |||||
| "errors" | "errors" | ||||
| "fmt" | "fmt" | ||||
| "strings" | |||||
| "time" | "time" | ||||
| "bytes" | |||||
| "encoding/gob" | |||||
| ) | ) | ||||
| const ( | const ( | ||||
| @@ -55,11 +56,10 @@ func encodeIds(ids []PK) (string, error) { | |||||
| return buf.String(), err | return buf.String(), err | ||||
| } | } | ||||
| func decodeIds(s string) ([]PK, error) { | func decodeIds(s string) ([]PK, error) { | ||||
| pks := make([]PK, 0) | pks := make([]PK, 0) | ||||
| dec := gob.NewDecoder(bytes.NewBufferString(s)) | |||||
| dec := gob.NewDecoder(strings.NewReader(s)) | |||||
| err := dec.Decode(&pks) | err := dec.Decode(&pks) | ||||
| return pks, err | return pks, err | ||||
| @@ -11,4 +11,5 @@ database: | |||||
| test: | test: | ||||
| override: | override: | ||||
| # './...' is a relative pattern which means all subdirectories | # './...' is a relative pattern which means all subdirectories | ||||
| - go test -v -race | |||||
| - go test -v -race | |||||
| - go test -v -race --dbtype=sqlite3 | |||||
| @@ -79,6 +79,10 @@ func (col *Column) String(d Dialect) string { | |||||
| } | } | ||||
| } | } | ||||
| if col.Default != "" { | |||||
| sql += "DEFAULT " + col.Default + " " | |||||
| } | |||||
| if d.ShowCreateNull() { | if d.ShowCreateNull() { | ||||
| if col.Nullable { | if col.Nullable { | ||||
| sql += "NULL " | sql += "NULL " | ||||
| @@ -87,10 +91,6 @@ func (col *Column) String(d Dialect) string { | |||||
| } | } | ||||
| } | } | ||||
| if col.Default != "" { | |||||
| sql += "DEFAULT " + col.Default + " " | |||||
| } | |||||
| return sql | return sql | ||||
| } | } | ||||
| @@ -99,6 +99,10 @@ func (col *Column) StringNoPk(d Dialect) string { | |||||
| sql += d.SqlType(col) + " " | sql += d.SqlType(col) + " " | ||||
| if col.Default != "" { | |||||
| sql += "DEFAULT " + col.Default + " " | |||||
| } | |||||
| if d.ShowCreateNull() { | if d.ShowCreateNull() { | ||||
| if col.Nullable { | if col.Nullable { | ||||
| sql += "NULL " | sql += "NULL " | ||||
| @@ -107,10 +111,6 @@ func (col *Column) StringNoPk(d Dialect) string { | |||||
| } | } | ||||
| } | } | ||||
| if col.Default != "" { | |||||
| sql += "DEFAULT " + col.Default + " " | |||||
| } | |||||
| return sql | return sql | ||||
| } | } | ||||
| @@ -44,6 +44,9 @@ func convertTime(dest *NullTime, src interface{}) error { | |||||
| } | } | ||||
| *dest = NullTime(t) | *dest = NullTime(t) | ||||
| return nil | return nil | ||||
| case time.Time: | |||||
| *dest = NullTime(s) | |||||
| return nil | |||||
| case nil: | case nil: | ||||
| default: | default: | ||||
| return fmt.Errorf("unsupported driver -> Scan pair: %T -> %T", src, dest) | return fmt.Errorf("unsupported driver -> Scan pair: %T -> %T", src, dest) | ||||
| @@ -32,13 +32,10 @@ proposed functionality. | |||||
| We appreciate any bug reports, but especially ones with self-contained | We appreciate any bug reports, but especially ones with self-contained | ||||
| (doesn't depend on code outside of xorm), minimal (can't be simplified | (doesn't depend on code outside of xorm), minimal (can't be simplified | ||||
| further) test cases. It's especially helpful if you can submit a pull | further) test cases. It's especially helpful if you can submit a pull | ||||
| request with just the failing test case (you'll probably want to | |||||
| pattern it after the tests in | |||||
| [base.go](https://github.com/go-xorm/tests/blob/master/base.go) AND | |||||
| [benchmark.go](https://github.com/go-xorm/tests/blob/master/benchmark.go). | |||||
| request with just the failing test case(you can find some example test file like [session_get_test.go](https://github.com/go-xorm/xorm/blob/master/session_get_test.go)). | |||||
| If you implements a new database interface, you maybe need to add a <databasename>_test.go file. | |||||
| For example, [mysql_test.go](https://github.com/go-xorm/tests/blob/master/mysql/mysql_test.go) | |||||
| If you implements a new database interface, you maybe need to add a test_<databasename>.sh file. | |||||
| For example, [mysql_test.go](https://github.com/go-xorm/xorm/blob/master/test_mysql.sh) | |||||
| ### New functionality | ### New functionality | ||||
| @@ -28,6 +28,8 @@ Xorm is a simple and powerful ORM for Go. | |||||
| * SQL Builder support via [github.com/go-xorm/builder](https://github.com/go-xorm/builder) | * SQL Builder support via [github.com/go-xorm/builder](https://github.com/go-xorm/builder) | ||||
| * Automatical Read/Write seperatelly | |||||
| # Drivers Support | # Drivers Support | ||||
| Drivers for Go's sql package which currently support database/sql includes: | Drivers for Go's sql package which currently support database/sql includes: | ||||
| @@ -48,6 +50,13 @@ Drivers for Go's sql package which currently support database/sql includes: | |||||
| # Changelog | # Changelog | ||||
| * **v0.6.4** | |||||
| * Automatical Read/Write seperatelly | |||||
| * Query/QueryString/QueryInterface and action with Where/And | |||||
| * Get support non-struct variables | |||||
| * BufferSize on Iterate | |||||
| * fix some other bugs. | |||||
| * **v0.6.3** | * **v0.6.3** | ||||
| * merge tests to main project | * merge tests to main project | ||||
| * add `Exist` function | * add `Exist` function | ||||
| @@ -61,13 +70,6 @@ Drivers for Go's sql package which currently support database/sql includes: | |||||
| * add Scan features to Get | * add Scan features to Get | ||||
| * add QueryString method | * 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` | |||||
| methods can use `builder.Cond` as parameter | |||||
| * add Sum, SumInt, SumInt64 and NotIn methods | |||||
| * some bugs fixed | |||||
| [More changes ...](https://github.com/go-xorm/manual-en-US/tree/master/chapter-16) | [More changes ...](https://github.com/go-xorm/manual-en-US/tree/master/chapter-16) | ||||
| # Installation | # Installation | ||||
| @@ -106,15 +108,36 @@ type User struct { | |||||
| err := engine.Sync2(new(User)) | err := engine.Sync2(new(User)) | ||||
| ``` | ``` | ||||
| * `Query` runs a SQL string, the returned results is `[]map[string][]byte`, `QueryString` returns `[]map[string]string`. | |||||
| * Create Engine Group | |||||
| ```Go | |||||
| dataSourceNameSlice := []string{masterDataSourceName, slave1DataSourceName, slave2DataSourceName} | |||||
| engineGroup, err := xorm.NewEngineGroup(driverName, dataSourceNameSlice) | |||||
| ``` | |||||
| ```Go | |||||
| masterEngine, err := xorm.NewEngine(driverName, masterDataSourceName) | |||||
| slave1Engine, err := xorm.NewEngine(driverName, slave1DataSourceName) | |||||
| slave2Engine, err := xorm.NewEngine(driverName, slave2DataSourceName) | |||||
| engineGroup, err := xorm.NewEngineGroup(masterEngine, []*Engine{slave1Engine, slave2Engine}) | |||||
| ``` | |||||
| Then all place where `engine` you can just use `engineGroup`. | |||||
| * `Query` runs a SQL string, the returned results is `[]map[string][]byte`, `QueryString` returns `[]map[string]string`, `QueryInterface` returns `[]map[string]interface{}`. | |||||
| ```Go | ```Go | ||||
| results, err := engine.Query("select * from user") | results, err := engine.Query("select * from user") | ||||
| results, err := engine.Where("a = 1").Query() | |||||
| results, err := engine.QueryString("select * from user") | results, err := engine.QueryString("select * from user") | ||||
| results, err := engine.Where("a = 1").QueryString() | |||||
| results, err := engine.QueryInterface("select * from user") | |||||
| results, err := engine.Where("a = 1").QueryInterface() | |||||
| ``` | ``` | ||||
| * `Execute` runs a SQL string, it returns `affected` and `error` | |||||
| * `Exec` runs a SQL string, it returns `affected` and `error` | |||||
| ```Go | ```Go | ||||
| affected, err := engine.Exec("update user set age = ? where name = ?", age, name) | affected, err := engine.Exec("update user set age = ? where name = ?", age, name) | ||||
| @@ -125,62 +148,76 @@ affected, err := engine.Exec("update user set age = ? where name = ?", age, name | |||||
| ```Go | ```Go | ||||
| affected, err := engine.Insert(&user) | affected, err := engine.Insert(&user) | ||||
| // INSERT INTO struct () values () | // INSERT INTO struct () values () | ||||
| affected, err := engine.Insert(&user1, &user2) | affected, err := engine.Insert(&user1, &user2) | ||||
| // INSERT INTO struct1 () values () | // INSERT INTO struct1 () values () | ||||
| // INSERT INTO struct2 () values () | // INSERT INTO struct2 () values () | ||||
| affected, err := engine.Insert(&users) | affected, err := engine.Insert(&users) | ||||
| // INSERT INTO struct () values (),(),() | // INSERT INTO struct () values (),(),() | ||||
| affected, err := engine.Insert(&user1, &users) | affected, err := engine.Insert(&user1, &users) | ||||
| // INSERT INTO struct1 () values () | // INSERT INTO struct1 () values () | ||||
| // INSERT INTO struct2 () values (),(),() | // INSERT INTO struct2 () values (),(),() | ||||
| ``` | ``` | ||||
| * Query one record from database | |||||
| * `Get` query one record from database | |||||
| ```Go | ```Go | ||||
| has, err := engine.Get(&user) | has, err := engine.Get(&user) | ||||
| // SELECT * FROM user LIMIT 1 | // SELECT * FROM user LIMIT 1 | ||||
| has, err := engine.Where("name = ?", name).Desc("id").Get(&user) | has, err := engine.Where("name = ?", name).Desc("id").Get(&user) | ||||
| // SELECT * FROM user WHERE name = ? ORDER BY id DESC LIMIT 1 | // SELECT * FROM user WHERE name = ? ORDER BY id DESC LIMIT 1 | ||||
| var name string | var name string | ||||
| has, err := engine.Where("id = ?", id).Cols("name").Get(&name) | has, err := engine.Where("id = ?", id).Cols("name").Get(&name) | ||||
| // SELECT name FROM user WHERE id = ? | // SELECT name FROM user WHERE id = ? | ||||
| var id int64 | var id int64 | ||||
| has, err := engine.Where("name = ?", name).Cols("id").Get(&id) | has, err := engine.Where("name = ?", name).Cols("id").Get(&id) | ||||
| has, err := engine.SQL("select id from user").Get(&id) | |||||
| // SELECT id FROM user WHERE name = ? | // SELECT id FROM user WHERE name = ? | ||||
| var valuesMap = make(map[string]string) | var valuesMap = make(map[string]string) | ||||
| has, err := engine.Where("id = ?", id).Get(&valuesMap) | has, err := engine.Where("id = ?", id).Get(&valuesMap) | ||||
| // SELECT * FROM user WHERE id = ? | // SELECT * FROM user WHERE id = ? | ||||
| var valuesSlice = make([]interface{}, len(cols)) | var valuesSlice = make([]interface{}, len(cols)) | ||||
| has, err := engine.Where("id = ?", id).Cols(cols...).Get(&valuesSlice) | has, err := engine.Where("id = ?", id).Cols(cols...).Get(&valuesSlice) | ||||
| // SELECT col1, col2, col3 FROM user WHERE id = ? | // SELECT col1, col2, col3 FROM user WHERE id = ? | ||||
| ``` | ``` | ||||
| * Check if one record exist on table | |||||
| * `Exist` check if one record exist on table | |||||
| ```Go | ```Go | ||||
| has, err := testEngine.Exist(new(RecordExist)) | has, err := testEngine.Exist(new(RecordExist)) | ||||
| // SELECT * FROM record_exist LIMIT 1 | // SELECT * FROM record_exist LIMIT 1 | ||||
| has, err = testEngine.Exist(&RecordExist{ | has, err = testEngine.Exist(&RecordExist{ | ||||
| Name: "test1", | Name: "test1", | ||||
| }) | }) | ||||
| // SELECT * FROM record_exist WHERE name = ? LIMIT 1 | // SELECT * FROM record_exist WHERE name = ? LIMIT 1 | ||||
| has, err = testEngine.Where("name = ?", "test1").Exist(&RecordExist{}) | has, err = testEngine.Where("name = ?", "test1").Exist(&RecordExist{}) | ||||
| // SELECT * FROM record_exist WHERE name = ? LIMIT 1 | // SELECT * FROM record_exist WHERE name = ? LIMIT 1 | ||||
| has, err = testEngine.SQL("select * from record_exist where name = ?", "test1").Exist() | has, err = testEngine.SQL("select * from record_exist where name = ?", "test1").Exist() | ||||
| // select * from record_exist where name = ? | // select * from record_exist where name = ? | ||||
| has, err = testEngine.Table("record_exist").Exist() | has, err = testEngine.Table("record_exist").Exist() | ||||
| // SELECT * FROM record_exist LIMIT 1 | // SELECT * FROM record_exist LIMIT 1 | ||||
| has, err = testEngine.Table("record_exist").Where("name = ?", "test1").Exist() | has, err = testEngine.Table("record_exist").Where("name = ?", "test1").Exist() | ||||
| // SELECT * FROM record_exist WHERE name = ? LIMIT 1 | // SELECT * FROM record_exist WHERE name = ? LIMIT 1 | ||||
| ``` | ``` | ||||
| * Query multiple records from database, also you can use join and extends | |||||
| * `Find` query multiple records from database, also you can use join and extends | |||||
| ```Go | ```Go | ||||
| var users []User | var users []User | ||||
| err := engine.Where("name = ?", name).And("age > 10").Limit(10, 0).Find(&users) | err := engine.Where("name = ?", name).And("age > 10").Limit(10, 0).Find(&users) | ||||
| // SELECT * FROM user WHERE name = ? AND age > 10 limit 0 offset 10 | |||||
| // SELECT * FROM user WHERE name = ? AND age > 10 limit 10 offset 0 | |||||
| type Detail struct { | type Detail struct { | ||||
| Id int64 | Id int64 | ||||
| @@ -193,14 +230,14 @@ type UserDetail struct { | |||||
| } | } | ||||
| var users []UserDetail | var users []UserDetail | ||||
| err := engine.Table("user").Select("user.*, detail.*") | |||||
| err := engine.Table("user").Select("user.*, detail.*"). | |||||
| Join("INNER", "detail", "detail.user_id = user.id"). | Join("INNER", "detail", "detail.user_id = user.id"). | ||||
| Where("user.name = ?", name).Limit(10, 0). | Where("user.name = ?", name).Limit(10, 0). | ||||
| Find(&users) | Find(&users) | ||||
| // SELECT user.*, detail.* FROM user INNER JOIN detail WHERE user.name = ? limit 0 offset 10 | |||||
| // SELECT user.*, detail.* FROM user INNER JOIN detail WHERE user.name = ? limit 10 offset 0 | |||||
| ``` | ``` | ||||
| * Query multiple records and record by record handle, there are two methods Iterate and Rows | |||||
| * `Iterate` and `Rows` query multiple records and record by record handle, there are two methods Iterate and Rows | |||||
| ```Go | ```Go | ||||
| err := engine.Iterate(&User{Name:name}, func(idx int, bean interface{}) error { | err := engine.Iterate(&User{Name:name}, func(idx int, bean interface{}) error { | ||||
| @@ -209,6 +246,13 @@ err := engine.Iterate(&User{Name:name}, func(idx int, bean interface{}) error { | |||||
| }) | }) | ||||
| // SELECT * FROM user | // SELECT * FROM user | ||||
| err := engine.BufferSize(100).Iterate(&User{Name:name}, func(idx int, bean interface{}) error { | |||||
| user := bean.(*User) | |||||
| return nil | |||||
| }) | |||||
| // SELECT * FROM user Limit 0, 100 | |||||
| // SELECT * FROM user Limit 101, 100 | |||||
| rows, err := engine.Rows(&User{Name:name}) | rows, err := engine.Rows(&User{Name:name}) | ||||
| // SELECT * FROM user | // SELECT * FROM user | ||||
| defer rows.Close() | defer rows.Close() | ||||
| @@ -218,7 +262,7 @@ for rows.Next() { | |||||
| } | } | ||||
| ``` | ``` | ||||
| * Update one or more records, default will update non-empty and non-zero fields except when you use Cols, AllCols and so on. | |||||
| * `Update` update one or more records, default will update non-empty and non-zero fields except when you use Cols, AllCols and so on. | |||||
| ```Go | ```Go | ||||
| affected, err := engine.Id(1).Update(&user) | affected, err := engine.Id(1).Update(&user) | ||||
| @@ -243,21 +287,39 @@ affected, err := engine.Id(1).AllCols().Update(&user) | |||||
| // UPDATE user SET name=?,age=?,salt=?,passwd=?,updated=? Where id = ? | // UPDATE user SET name=?,age=?,salt=?,passwd=?,updated=? Where id = ? | ||||
| ``` | ``` | ||||
| * Delete one or more records, Delete MUST have condition | |||||
| * `Delete` delete one or more records, Delete MUST have condition | |||||
| ```Go | ```Go | ||||
| affected, err := engine.Where(...).Delete(&user) | affected, err := engine.Where(...).Delete(&user) | ||||
| // DELETE FROM user Where ... | // DELETE FROM user Where ... | ||||
| affected, err := engine.Id(2).Delete(&user) | |||||
| affected, err := engine.ID(2).Delete(&user) | |||||
| // DELETE FROM user Where id = ? | |||||
| ``` | ``` | ||||
| * Count records | |||||
| * `Count` count records | |||||
| ```Go | ```Go | ||||
| counts, err := engine.Count(&user) | counts, err := engine.Count(&user) | ||||
| // SELECT count(*) AS total FROM user | // SELECT count(*) AS total FROM user | ||||
| ``` | ``` | ||||
| * `Sum` sum functions | |||||
| ```Go | |||||
| agesFloat64, err := engine.Sum(&user, "age") | |||||
| // SELECT sum(age) AS total FROM user | |||||
| agesInt64, err := engine.SumInt(&user, "age") | |||||
| // SELECT sum(age) AS total FROM user | |||||
| sumFloat64Slice, err := engine.Sums(&user, "age", "score") | |||||
| // SELECT sum(age), sum(score) FROM user | |||||
| sumInt64Slice, err := engine.SumsInt(&user, "age", "score") | |||||
| // SELECT sum(age), sum(score) FROM user | |||||
| ``` | |||||
| * Query conditions builder | * Query conditions builder | ||||
| ```Go | ```Go | ||||
| @@ -265,6 +327,59 @@ err := engine.Where(builder.NotIn("a", 1, 2).And(builder.In("b", "c", "d", "e")) | |||||
| // SELECT id, name ... FROM user WHERE a NOT IN (?, ?) AND b IN (?, ?, ?) | // SELECT id, name ... FROM user WHERE a NOT IN (?, ?) AND b IN (?, ?, ?) | ||||
| ``` | ``` | ||||
| * Multiple operations in one go routine, no transation here but resue session memory | |||||
| ```Go | |||||
| session := engine.NewSession() | |||||
| defer session.Close() | |||||
| user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()} | |||||
| if _, err := session.Insert(&user1); err != nil { | |||||
| return err | |||||
| } | |||||
| user2 := Userinfo{Username: "yyy"} | |||||
| if _, err := session.Where("id = ?", 2).Update(&user2); err != nil { | |||||
| return err | |||||
| } | |||||
| if _, err := session.Exec("delete from userinfo where username = ?", user2.Username); err != nil { | |||||
| return err | |||||
| } | |||||
| return nil | |||||
| ``` | |||||
| * Transation should on one go routine. There is transaction and resue session memory | |||||
| ```Go | |||||
| session := engine.NewSession() | |||||
| defer session.Close() | |||||
| // add Begin() before any action | |||||
| if err := session.Begin(); err != nil { | |||||
| // if returned then will rollback automatically | |||||
| return err | |||||
| } | |||||
| user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()} | |||||
| if _, err := session.Insert(&user1); err != nil { | |||||
| return err | |||||
| } | |||||
| user2 := Userinfo{Username: "yyy"} | |||||
| if _, err := session.Where("id = ?", 2).Update(&user2); err != nil { | |||||
| return err | |||||
| } | |||||
| if _, err := session.Exec("delete from userinfo where username = ?", user2.Username); err != nil { | |||||
| return err | |||||
| } | |||||
| // add Commit() after all actions | |||||
| return session.Commit() | |||||
| ``` | |||||
| # Cases | # Cases | ||||
| * [studygolang](http://studygolang.com/) - [github.com/studygolang/studygolang](https://github.com/studygolang/studygolang) | * [studygolang](http://studygolang.com/) - [github.com/studygolang/studygolang](https://github.com/studygolang/studygolang) | ||||
| @@ -115,12 +115,33 @@ type User struct { | |||||
| err := engine.Sync2(new(User)) | err := engine.Sync2(new(User)) | ||||
| ``` | ``` | ||||
| * `Query` 最原始的也支持SQL语句查询,返回的结果类型为 []map[string][]byte。`QueryString` 返回 []map[string]string | |||||
| * 创建Engine组 | |||||
| ```Go | |||||
| dataSourceNameSlice := []string{masterDataSourceName, slave1DataSourceName, slave2DataSourceName} | |||||
| engineGroup, err := xorm.NewEngineGroup(driverName, dataSourceNameSlice) | |||||
| ``` | |||||
| ```Go | |||||
| masterEngine, err := xorm.NewEngine(driverName, masterDataSourceName) | |||||
| slave1Engine, err := xorm.NewEngine(driverName, slave1DataSourceName) | |||||
| slave2Engine, err := xorm.NewEngine(driverName, slave2DataSourceName) | |||||
| engineGroup, err := xorm.NewEngineGroup(masterEngine, []*Engine{slave1Engine, slave2Engine}) | |||||
| ``` | |||||
| 所有使用 `engine` 都可以简单的用 `engineGroup` 来替换。 | |||||
| * `Query` 最原始的也支持SQL语句查询,返回的结果类型为 []map[string][]byte。`QueryString` 返回 []map[string]string, `QueryInterface` 返回 `[]map[string]interface{}`. | |||||
| ```Go | ```Go | ||||
| results, err := engine.Query("select * from user") | results, err := engine.Query("select * from user") | ||||
| results, err := engine.Where("a = 1").Query() | |||||
| results, err := engine.QueryString("select * from user") | results, err := engine.QueryString("select * from user") | ||||
| results, err := engine.Where("a = 1").QueryString() | |||||
| results, err := engine.QueryInterface("select * from user") | |||||
| results, err := engine.Where("a = 1").QueryInterface() | |||||
| ``` | ``` | ||||
| * `Exec` 执行一个SQL语句 | * `Exec` 执行一个SQL语句 | ||||
| @@ -129,67 +150,81 @@ results, err := engine.QueryString("select * from user") | |||||
| affected, err := engine.Exec("update user set age = ? where name = ?", age, name) | affected, err := engine.Exec("update user set age = ? where name = ?", age, name) | ||||
| ``` | ``` | ||||
| * 插入一条或者多条记录 | |||||
| * `Insert` 插入一条或者多条记录 | |||||
| ```Go | ```Go | ||||
| affected, err := engine.Insert(&user) | affected, err := engine.Insert(&user) | ||||
| // INSERT INTO struct () values () | // INSERT INTO struct () values () | ||||
| affected, err := engine.Insert(&user1, &user2) | affected, err := engine.Insert(&user1, &user2) | ||||
| // INSERT INTO struct1 () values () | // INSERT INTO struct1 () values () | ||||
| // INSERT INTO struct2 () values () | // INSERT INTO struct2 () values () | ||||
| affected, err := engine.Insert(&users) | affected, err := engine.Insert(&users) | ||||
| // INSERT INTO struct () values (),(),() | // INSERT INTO struct () values (),(),() | ||||
| affected, err := engine.Insert(&user1, &users) | affected, err := engine.Insert(&user1, &users) | ||||
| // INSERT INTO struct1 () values () | // INSERT INTO struct1 () values () | ||||
| // INSERT INTO struct2 () values (),(),() | // INSERT INTO struct2 () values (),(),() | ||||
| ``` | ``` | ||||
| * 查询单条记录 | |||||
| * `Get` 查询单条记录 | |||||
| ```Go | ```Go | ||||
| has, err := engine.Get(&user) | has, err := engine.Get(&user) | ||||
| // SELECT * FROM user LIMIT 1 | // SELECT * FROM user LIMIT 1 | ||||
| has, err := engine.Where("name = ?", name).Desc("id").Get(&user) | has, err := engine.Where("name = ?", name).Desc("id").Get(&user) | ||||
| // SELECT * FROM user WHERE name = ? ORDER BY id DESC LIMIT 1 | // SELECT * FROM user WHERE name = ? ORDER BY id DESC LIMIT 1 | ||||
| var name string | var name string | ||||
| has, err := engine.Where("id = ?", id).Cols("name").Get(&name) | has, err := engine.Where("id = ?", id).Cols("name").Get(&name) | ||||
| // SELECT name FROM user WHERE id = ? | // SELECT name FROM user WHERE id = ? | ||||
| var id int64 | var id int64 | ||||
| has, err := engine.Where("name = ?", name).Cols("id").Get(&id) | has, err := engine.Where("name = ?", name).Cols("id").Get(&id) | ||||
| has, err := engine.SQL("select id from user").Get(&id) | |||||
| // SELECT id FROM user WHERE name = ? | // SELECT id FROM user WHERE name = ? | ||||
| var valuesMap = make(map[string]string) | var valuesMap = make(map[string]string) | ||||
| has, err := engine.Where("id = ?", id).Get(&valuesMap) | has, err := engine.Where("id = ?", id).Get(&valuesMap) | ||||
| // SELECT * FROM user WHERE id = ? | // SELECT * FROM user WHERE id = ? | ||||
| var valuesSlice = make([]interface{}, len(cols)) | var valuesSlice = make([]interface{}, len(cols)) | ||||
| has, err := engine.Where("id = ?", id).Cols(cols...).Get(&valuesSlice) | has, err := engine.Where("id = ?", id).Cols(cols...).Get(&valuesSlice) | ||||
| // SELECT col1, col2, col3 FROM user WHERE id = ? | // SELECT col1, col2, col3 FROM user WHERE id = ? | ||||
| ``` | ``` | ||||
| * 检测记录是否存在 | |||||
| * `Exist` 检测记录是否存在 | |||||
| ```Go | ```Go | ||||
| has, err := testEngine.Exist(new(RecordExist)) | has, err := testEngine.Exist(new(RecordExist)) | ||||
| // SELECT * FROM record_exist LIMIT 1 | // SELECT * FROM record_exist LIMIT 1 | ||||
| has, err = testEngine.Exist(&RecordExist{ | has, err = testEngine.Exist(&RecordExist{ | ||||
| Name: "test1", | Name: "test1", | ||||
| }) | }) | ||||
| // SELECT * FROM record_exist WHERE name = ? LIMIT 1 | // SELECT * FROM record_exist WHERE name = ? LIMIT 1 | ||||
| has, err = testEngine.Where("name = ?", "test1").Exist(&RecordExist{}) | has, err = testEngine.Where("name = ?", "test1").Exist(&RecordExist{}) | ||||
| // SELECT * FROM record_exist WHERE name = ? LIMIT 1 | // SELECT * FROM record_exist WHERE name = ? LIMIT 1 | ||||
| has, err = testEngine.SQL("select * from record_exist where name = ?", "test1").Exist() | has, err = testEngine.SQL("select * from record_exist where name = ?", "test1").Exist() | ||||
| // select * from record_exist where name = ? | // select * from record_exist where name = ? | ||||
| has, err = testEngine.Table("record_exist").Exist() | has, err = testEngine.Table("record_exist").Exist() | ||||
| // SELECT * FROM record_exist LIMIT 1 | // SELECT * FROM record_exist LIMIT 1 | ||||
| has, err = testEngine.Table("record_exist").Where("name = ?", "test1").Exist() | has, err = testEngine.Table("record_exist").Where("name = ?", "test1").Exist() | ||||
| // SELECT * FROM record_exist WHERE name = ? LIMIT 1 | // SELECT * FROM record_exist WHERE name = ? LIMIT 1 | ||||
| ``` | ``` | ||||
| * 查询多条记录,当然可以使用Join和extends来组合使用 | |||||
| * `Find` 查询多条记录,当然可以使用Join和extends来组合使用 | |||||
| ```Go | ```Go | ||||
| var users []User | var users []User | ||||
| err := engine.Where("name = ?", name).And("age > 10").Limit(10, 0).Find(&users) | err := engine.Where("name = ?", name).And("age > 10").Limit(10, 0).Find(&users) | ||||
| // SELECT * FROM user WHERE name = ? AND age > 10 limit 0 offset 10 | |||||
| // SELECT * FROM user WHERE name = ? AND age > 10 limit 10 offset 0 | |||||
| type Detail struct { | type Detail struct { | ||||
| Id int64 | Id int64 | ||||
| @@ -206,10 +241,10 @@ err := engine.Table("user").Select("user.*, detail.*") | |||||
| Join("INNER", "detail", "detail.user_id = user.id"). | Join("INNER", "detail", "detail.user_id = user.id"). | ||||
| Where("user.name = ?", name).Limit(10, 0). | Where("user.name = ?", name).Limit(10, 0). | ||||
| Find(&users) | Find(&users) | ||||
| // SELECT user.*, detail.* FROM user INNER JOIN detail WHERE user.name = ? limit 0 offset 10 | |||||
| // SELECT user.*, detail.* FROM user INNER JOIN detail WHERE user.name = ? limit 10 offset 0 | |||||
| ``` | ``` | ||||
| * 根据条件遍历数据库,可以有两种方式: Iterate and Rows | |||||
| * `Iterate` 和 `Rows` 根据条件遍历数据库,可以有两种方式: Iterate and Rows | |||||
| ```Go | ```Go | ||||
| err := engine.Iterate(&User{Name:name}, func(idx int, bean interface{}) error { | err := engine.Iterate(&User{Name:name}, func(idx int, bean interface{}) error { | ||||
| @@ -218,6 +253,13 @@ err := engine.Iterate(&User{Name:name}, func(idx int, bean interface{}) error { | |||||
| }) | }) | ||||
| // SELECT * FROM user | // SELECT * FROM user | ||||
| err := engine.BufferSize(100).Iterate(&User{Name:name}, func(idx int, bean interface{}) error { | |||||
| user := bean.(*User) | |||||
| return nil | |||||
| }) | |||||
| // SELECT * FROM user Limit 0, 100 | |||||
| // SELECT * FROM user Limit 101, 100 | |||||
| rows, err := engine.Rows(&User{Name:name}) | rows, err := engine.Rows(&User{Name:name}) | ||||
| // SELECT * FROM user | // SELECT * FROM user | ||||
| defer rows.Close() | defer rows.Close() | ||||
| @@ -227,7 +269,7 @@ for rows.Next() { | |||||
| } | } | ||||
| ``` | ``` | ||||
| * 更新数据,除非使用Cols,AllCols函数指明,默认只更新非空和非0的字段 | |||||
| * `Update` 更新数据,除非使用Cols,AllCols函数指明,默认只更新非空和非0的字段 | |||||
| ```Go | ```Go | ||||
| affected, err := engine.Id(1).Update(&user) | affected, err := engine.Id(1).Update(&user) | ||||
| @@ -252,20 +294,39 @@ affected, err := engine.Id(1).AllCols().Update(&user) | |||||
| // UPDATE user SET name=?,age=?,salt=?,passwd=?,updated=? Where id = ? | // UPDATE user SET name=?,age=?,salt=?,passwd=?,updated=? Where id = ? | ||||
| ``` | ``` | ||||
| * 删除记录,需要注意,删除必须至少有一个条件,否则会报错。要清空数据库可以用EmptyTable | |||||
| * `Delete` 删除记录,需要注意,删除必须至少有一个条件,否则会报错。要清空数据库可以用EmptyTable | |||||
| ```Go | ```Go | ||||
| affected, err := engine.Where(...).Delete(&user) | affected, err := engine.Where(...).Delete(&user) | ||||
| // DELETE FROM user Where ... | // DELETE FROM user Where ... | ||||
| affected, err := engine.ID(2).Delete(&user) | |||||
| // DELETE FROM user Where id = ? | |||||
| ``` | ``` | ||||
| * 获取记录条数 | |||||
| * `Count` 获取记录条数 | |||||
| ```Go | ```Go | ||||
| counts, err := engine.Count(&user) | counts, err := engine.Count(&user) | ||||
| // SELECT count(*) AS total FROM user | // SELECT count(*) AS total FROM user | ||||
| ``` | ``` | ||||
| * `Sum` 求和函数 | |||||
| ```Go | |||||
| agesFloat64, err := engine.Sum(&user, "age") | |||||
| // SELECT sum(age) AS total FROM user | |||||
| agesInt64, err := engine.SumInt(&user, "age") | |||||
| // SELECT sum(age) AS total FROM user | |||||
| sumFloat64Slice, err := engine.Sums(&user, "age", "score") | |||||
| // SELECT sum(age), sum(score) FROM user | |||||
| sumInt64Slice, err := engine.SumsInt(&user, "age", "score") | |||||
| // SELECT sum(age), sum(score) FROM user | |||||
| ``` | |||||
| * 条件编辑器 | * 条件编辑器 | ||||
| ```Go | ```Go | ||||
| @@ -273,6 +334,59 @@ err := engine.Where(builder.NotIn("a", 1, 2).And(builder.In("b", "c", "d", "e")) | |||||
| // SELECT id, name ... FROM user WHERE a NOT IN (?, ?) AND b IN (?, ?, ?) | // SELECT id, name ... FROM user WHERE a NOT IN (?, ?) AND b IN (?, ?, ?) | ||||
| ``` | ``` | ||||
| * 在一个Go程中多次操作数据库,但没有事务 | |||||
| ```Go | |||||
| session := engine.NewSession() | |||||
| defer session.Close() | |||||
| user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()} | |||||
| if _, err := session.Insert(&user1); err != nil { | |||||
| return err | |||||
| } | |||||
| user2 := Userinfo{Username: "yyy"} | |||||
| if _, err := session.Where("id = ?", 2).Update(&user2); err != nil { | |||||
| return err | |||||
| } | |||||
| if _, err := session.Exec("delete from userinfo where username = ?", user2.Username); err != nil { | |||||
| return err | |||||
| } | |||||
| return nil | |||||
| ``` | |||||
| * 在一个Go程中有事务 | |||||
| ```Go | |||||
| session := engine.NewSession() | |||||
| defer session.Close() | |||||
| // add Begin() before any action | |||||
| if err := session.Begin(); err != nil { | |||||
| // if returned then will rollback automatically | |||||
| return err | |||||
| } | |||||
| user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()} | |||||
| if _, err := session.Insert(&user1); err != nil { | |||||
| return err | |||||
| } | |||||
| user2 := Userinfo{Username: "yyy"} | |||||
| if _, err := session.Where("id = ?", 2).Update(&user2); err != nil { | |||||
| return err | |||||
| } | |||||
| if _, err := session.Exec("delete from userinfo where username = ?", user2.Username); err != nil { | |||||
| return err | |||||
| } | |||||
| // add Commit() after all actions | |||||
| return session.Commit() | |||||
| ``` | |||||
| # 案例 | # 案例 | ||||
| * [Go语言中文网](http://studygolang.com/) - [github.com/studygolang/studygolang](https://github.com/studygolang/studygolang) | * [Go语言中文网](http://studygolang.com/) - [github.com/studygolang/studygolang](https://github.com/studygolang/studygolang) | ||||
| @@ -0,0 +1,26 @@ | |||||
| // 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. | |||||
| // +build go1.8 | |||||
| package xorm | |||||
| import "context" | |||||
| // PingContext tests if database is alive | |||||
| func (engine *Engine) PingContext(ctx context.Context) error { | |||||
| session := engine.NewSession() | |||||
| defer session.Close() | |||||
| return session.PingContext(ctx) | |||||
| } | |||||
| // PingContext test if database is ok | |||||
| func (session *Session) PingContext(ctx context.Context) error { | |||||
| if session.isAutoClose { | |||||
| defer session.Close() | |||||
| } | |||||
| session.engine.logger.Infof("PING DATABASE %v", session.engine.DriverName()) | |||||
| return session.DB().PingContext(ctx) | |||||
| } | |||||
| @@ -209,10 +209,10 @@ func convertAssign(dest, src interface{}) error { | |||||
| if src == nil { | if src == nil { | ||||
| dv.Set(reflect.Zero(dv.Type())) | dv.Set(reflect.Zero(dv.Type())) | ||||
| return nil | return nil | ||||
| } else { | |||||
| dv.Set(reflect.New(dv.Type().Elem())) | |||||
| return convertAssign(dv.Interface(), src) | |||||
| } | } | ||||
| dv.Set(reflect.New(dv.Type().Elem())) | |||||
| return convertAssign(dv.Interface(), src) | |||||
| case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | ||||
| s := asString(src) | s := asString(src) | ||||
| i64, err := strconv.ParseInt(s, 10, dv.Type().Bits()) | i64, err := strconv.ParseInt(s, 10, dv.Type().Bits()) | ||||
| @@ -8,7 +8,6 @@ import ( | |||||
| "errors" | "errors" | ||||
| "fmt" | "fmt" | ||||
| "net/url" | "net/url" | ||||
| "sort" | |||||
| "strconv" | "strconv" | ||||
| "strings" | "strings" | ||||
| @@ -765,13 +764,18 @@ var ( | |||||
| "YES": true, | "YES": true, | ||||
| "ZONE": true, | "ZONE": true, | ||||
| } | } | ||||
| // DefaultPostgresSchema default postgres schema | |||||
| DefaultPostgresSchema = "public" | |||||
| ) | ) | ||||
| type postgres struct { | type postgres struct { | ||||
| core.Base | core.Base | ||||
| schema string | |||||
| } | } | ||||
| func (db *postgres) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error { | func (db *postgres) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error { | ||||
| db.schema = DefaultPostgresSchema | |||||
| return db.Base.Init(d, db, uri, drivername, dataSourceName) | return db.Base.Init(d, db, uri, drivername, dataSourceName) | ||||
| } | } | ||||
| @@ -923,7 +927,7 @@ func (db *postgres) IsColumnExist(tableName, colName string) (bool, error) { | |||||
| func (db *postgres) GetColumns(tableName string) ([]string, map[string]*core.Column, error) { | func (db *postgres) GetColumns(tableName string) ([]string, map[string]*core.Column, error) { | ||||
| // FIXME: the schema should be replaced by user custom's | // FIXME: the schema should be replaced by user custom's | ||||
| args := []interface{}{tableName, "public"} | |||||
| args := []interface{}{tableName, db.schema} | |||||
| s := `SELECT column_name, column_default, is_nullable, data_type, character_maximum_length, numeric_precision, numeric_precision_radix , | s := `SELECT column_name, column_default, is_nullable, data_type, character_maximum_length, numeric_precision, numeric_precision_radix , | ||||
| CASE WHEN p.contype = 'p' THEN true ELSE false END AS primarykey, | CASE WHEN p.contype = 'p' THEN true ELSE false END AS primarykey, | ||||
| CASE WHEN p.contype = 'u' THEN true ELSE false END AS uniquekey | CASE WHEN p.contype = 'u' THEN true ELSE false END AS uniquekey | ||||
| @@ -1024,8 +1028,7 @@ WHERE c.relkind = 'r'::char AND c.relname = $1 AND s.table_schema = $2 AND f.att | |||||
| } | } | ||||
| func (db *postgres) GetTables() ([]*core.Table, error) { | func (db *postgres) GetTables() ([]*core.Table, error) { | ||||
| // FIXME: replace public to user customrize schema | |||||
| args := []interface{}{"public"} | |||||
| args := []interface{}{db.schema} | |||||
| s := fmt.Sprintf("SELECT tablename FROM pg_tables WHERE schemaname = $1") | s := fmt.Sprintf("SELECT tablename FROM pg_tables WHERE schemaname = $1") | ||||
| db.LogSQL(s, args) | db.LogSQL(s, args) | ||||
| @@ -1050,8 +1053,7 @@ func (db *postgres) GetTables() ([]*core.Table, error) { | |||||
| } | } | ||||
| func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error) { | func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error) { | ||||
| // FIXME: replace the public schema to user specify schema | |||||
| args := []interface{}{"public", tableName} | |||||
| args := []interface{}{db.schema, tableName} | |||||
| s := fmt.Sprintf("SELECT indexname, indexdef FROM pg_indexes WHERE schemaname=$1 AND tablename=$2") | s := fmt.Sprintf("SELECT indexname, indexdef FROM pg_indexes WHERE schemaname=$1 AND tablename=$2") | ||||
| db.LogSQL(s, args) | db.LogSQL(s, args) | ||||
| @@ -1117,10 +1119,6 @@ func (vs values) Get(k string) (v string) { | |||||
| return vs[k] | return vs[k] | ||||
| } | } | ||||
| func errorf(s string, args ...interface{}) { | |||||
| panic(fmt.Errorf("pq: %s", fmt.Sprintf(s, args...))) | |||||
| } | |||||
| func parseURL(connstr string) (string, error) { | func parseURL(connstr string) (string, error) { | ||||
| u, err := url.Parse(connstr) | u, err := url.Parse(connstr) | ||||
| if err != nil { | if err != nil { | ||||
| @@ -1131,46 +1129,18 @@ func parseURL(connstr string) (string, error) { | |||||
| return "", fmt.Errorf("invalid connection protocol: %s", u.Scheme) | return "", fmt.Errorf("invalid connection protocol: %s", u.Scheme) | ||||
| } | } | ||||
| var kvs []string | |||||
| escaper := strings.NewReplacer(` `, `\ `, `'`, `\'`, `\`, `\\`) | 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 != "" { | if u.Path != "" { | ||||
| accrue("dbname", u.Path[1:]) | |||||
| return escaper.Replace(u.Path[1:]), nil | |||||
| } | } | ||||
| 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 | |||||
| return "", nil | |||||
| } | } | ||||
| func parseOpts(name string, o values) { | |||||
| func parseOpts(name string, o values) error { | |||||
| if len(name) == 0 { | if len(name) == 0 { | ||||
| return | |||||
| return fmt.Errorf("invalid options: %s", name) | |||||
| } | } | ||||
| name = strings.TrimSpace(name) | name = strings.TrimSpace(name) | ||||
| @@ -1179,31 +1149,36 @@ func parseOpts(name string, o values) { | |||||
| for _, p := range ps { | for _, p := range ps { | ||||
| kv := strings.Split(p, "=") | kv := strings.Split(p, "=") | ||||
| if len(kv) < 2 { | if len(kv) < 2 { | ||||
| errorf("invalid option: %q", p) | |||||
| return fmt.Errorf("invalid option: %q", p) | |||||
| } | } | ||||
| o.Set(kv[0], kv[1]) | o.Set(kv[0], kv[1]) | ||||
| } | } | ||||
| return nil | |||||
| } | } | ||||
| func (p *pqDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { | func (p *pqDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { | ||||
| db := &core.Uri{DbType: core.POSTGRES} | db := &core.Uri{DbType: core.POSTGRES} | ||||
| o := make(values) | |||||
| var err error | var err error | ||||
| if strings.HasPrefix(dataSourceName, "postgresql://") || strings.HasPrefix(dataSourceName, "postgres://") { | if strings.HasPrefix(dataSourceName, "postgresql://") || strings.HasPrefix(dataSourceName, "postgres://") { | ||||
| dataSourceName, err = parseURL(dataSourceName) | |||||
| db.DbName, err = parseURL(dataSourceName) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| } else { | |||||
| o := make(values) | |||||
| err = parseOpts(dataSourceName, o) | |||||
| if err != nil { | if err != nil { | ||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| db.DbName = o.Get("dbname") | |||||
| } | } | ||||
| parseOpts(dataSourceName, o) | |||||
| db.DbName = o.Get("dbname") | |||||
| if db.DbName == "" { | if db.DbName == "" { | ||||
| return nil, errors.New("dbname is empty") | return nil, errors.New("dbname is empty") | ||||
| } | } | ||||
| /*db.Schema = o.Get("schema") | |||||
| if len(db.Schema) == 0 { | |||||
| db.Schema = "public" | |||||
| }*/ | |||||
| return db, nil | return db, nil | ||||
| } | } | ||||
| @@ -47,6 +47,23 @@ type Engine struct { | |||||
| disableGlobalCache bool | disableGlobalCache bool | ||||
| tagHandlers map[string]tagHandler | tagHandlers map[string]tagHandler | ||||
| engineGroup *EngineGroup | |||||
| } | |||||
| // BufferSize sets buffer size for iterate | |||||
| func (engine *Engine) BufferSize(size int) *Session { | |||||
| session := engine.NewSession() | |||||
| session.isAutoClose = true | |||||
| return session.BufferSize(size) | |||||
| } | |||||
| // CondDeleted returns the conditions whether a record is soft deleted. | |||||
| func (engine *Engine) CondDeleted(colName string) builder.Cond { | |||||
| if engine.dialect.DBType() == core.MSSQL { | |||||
| return builder.IsNull{colName} | |||||
| } | |||||
| return builder.IsNull{colName}.Or(builder.Eq{colName: zeroTime1}) | |||||
| } | } | ||||
| // ShowSQL show SQL statement or not on logger if log level is great than INFO | // ShowSQL show SQL statement or not on logger if log level is great than INFO | ||||
| @@ -79,6 +96,11 @@ func (engine *Engine) SetLogger(logger core.ILogger) { | |||||
| engine.dialect.SetLogger(logger) | engine.dialect.SetLogger(logger) | ||||
| } | } | ||||
| // SetLogLevel sets the logger level | |||||
| func (engine *Engine) SetLogLevel(level core.LogLevel) { | |||||
| engine.logger.SetLevel(level) | |||||
| } | |||||
| // SetDisableGlobalCache disable global cache or not | // SetDisableGlobalCache disable global cache or not | ||||
| func (engine *Engine) SetDisableGlobalCache(disable bool) { | func (engine *Engine) SetDisableGlobalCache(disable bool) { | ||||
| if engine.disableGlobalCache != disable { | if engine.disableGlobalCache != disable { | ||||
| @@ -201,6 +223,11 @@ func (engine *Engine) SetDefaultCacher(cacher core.Cacher) { | |||||
| engine.Cacher = cacher | engine.Cacher = cacher | ||||
| } | } | ||||
| // GetDefaultCacher returns the default cacher | |||||
| func (engine *Engine) GetDefaultCacher() core.Cacher { | |||||
| return engine.Cacher | |||||
| } | |||||
| // NoCache If you has set default cacher, and you want temporilly stop use cache, | // NoCache If you has set default cacher, and you want temporilly stop use cache, | ||||
| // you can use NoCache() | // you can use NoCache() | ||||
| func (engine *Engine) NoCache() *Session { | func (engine *Engine) NoCache() *Session { | ||||
| @@ -736,6 +763,13 @@ func (engine *Engine) OrderBy(order string) *Session { | |||||
| return session.OrderBy(order) | return session.OrderBy(order) | ||||
| } | } | ||||
| // Prepare enables prepare statement | |||||
| func (engine *Engine) Prepare() *Session { | |||||
| session := engine.NewSession() | |||||
| session.isAutoClose = true | |||||
| return session.Prepare() | |||||
| } | |||||
| // Join the join_operator should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN | // Join the join_operator should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN | ||||
| func (engine *Engine) Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session { | func (engine *Engine) Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session { | ||||
| session := engine.NewSession() | session := engine.NewSession() | ||||
| @@ -757,7 +791,8 @@ func (engine *Engine) Having(conditions string) *Session { | |||||
| return session.Having(conditions) | return session.Having(conditions) | ||||
| } | } | ||||
| func (engine *Engine) unMapType(t reflect.Type) { | |||||
| // UnMapType removes the datbase mapper of a type | |||||
| func (engine *Engine) UnMapType(t reflect.Type) { | |||||
| engine.mutex.Lock() | engine.mutex.Lock() | ||||
| defer engine.mutex.Unlock() | defer engine.mutex.Unlock() | ||||
| delete(engine.Tables, t) | delete(engine.Tables, t) | ||||
| @@ -914,7 +949,7 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) { | |||||
| } | } | ||||
| if pStart > -1 { | if pStart > -1 { | ||||
| if !strings.HasSuffix(k, ")") { | if !strings.HasSuffix(k, ")") { | ||||
| return nil, errors.New("cannot match ) charactor") | |||||
| return nil, fmt.Errorf("field %s tag %s cannot match ) charactor", col.FieldName, key) | |||||
| } | } | ||||
| ctx.tagName = k[:pStart] | ctx.tagName = k[:pStart] | ||||
| @@ -1341,24 +1376,24 @@ func (engine *Engine) Exec(sql string, args ...interface{}) (sql.Result, error) | |||||
| } | } | ||||
| // Query a raw sql and return records as []map[string][]byte | // Query a raw sql and return records as []map[string][]byte | ||||
| func (engine *Engine) Query(sql string, paramStr ...interface{}) (resultsSlice []map[string][]byte, err error) { | |||||
| func (engine *Engine) Query(sqlorArgs ...interface{}) (resultsSlice []map[string][]byte, err error) { | |||||
| session := engine.NewSession() | session := engine.NewSession() | ||||
| defer session.Close() | defer session.Close() | ||||
| return session.Query(sql, paramStr...) | |||||
| return session.Query(sqlorArgs...) | |||||
| } | } | ||||
| // QueryString runs a raw sql and return records as []map[string]string | // QueryString runs a raw sql and return records as []map[string]string | ||||
| func (engine *Engine) QueryString(sqlStr string, args ...interface{}) ([]map[string]string, error) { | |||||
| func (engine *Engine) QueryString(sqlorArgs ...interface{}) ([]map[string]string, error) { | |||||
| session := engine.NewSession() | session := engine.NewSession() | ||||
| defer session.Close() | defer session.Close() | ||||
| return session.QueryString(sqlStr, args...) | |||||
| return session.QueryString(sqlorArgs...) | |||||
| } | } | ||||
| // QueryInterface runs a raw sql and return records as []map[string]interface{} | // QueryInterface runs a raw sql and return records as []map[string]interface{} | ||||
| func (engine *Engine) QueryInterface(sqlStr string, args ...interface{}) ([]map[string]interface{}, error) { | |||||
| func (engine *Engine) QueryInterface(sqlorArgs ...interface{}) ([]map[string]interface{}, error) { | |||||
| session := engine.NewSession() | session := engine.NewSession() | ||||
| defer session.Close() | defer session.Close() | ||||
| return session.QueryInterface(sqlStr, args...) | |||||
| return session.QueryInterface(sqlorArgs...) | |||||
| } | } | ||||
| // Insert one or more records | // Insert one or more records | ||||
| @@ -1564,24 +1599,39 @@ func (engine *Engine) formatTime(sqlTypeName string, t time.Time) (v interface{} | |||||
| return | return | ||||
| } | } | ||||
| // Unscoped always disable struct tag "deleted" | |||||
| func (engine *Engine) Unscoped() *Session { | |||||
| session := engine.NewSession() | |||||
| session.isAutoClose = true | |||||
| return session.Unscoped() | |||||
| // GetColumnMapper returns the column name mapper | |||||
| func (engine *Engine) GetColumnMapper() core.IMapper { | |||||
| return engine.ColumnMapper | |||||
| } | } | ||||
| // CondDeleted returns the conditions whether a record is soft deleted. | |||||
| func (engine *Engine) CondDeleted(colName string) builder.Cond { | |||||
| if engine.dialect.DBType() == core.MSSQL { | |||||
| return builder.IsNull{colName} | |||||
| } | |||||
| return builder.IsNull{colName}.Or(builder.Eq{colName: zeroTime1}) | |||||
| // GetTableMapper returns the table name mapper | |||||
| func (engine *Engine) GetTableMapper() core.IMapper { | |||||
| return engine.TableMapper | |||||
| } | } | ||||
| // BufferSize sets buffer size for iterate | |||||
| func (engine *Engine) BufferSize(size int) *Session { | |||||
| // GetTZLocation returns time zone of the application | |||||
| func (engine *Engine) GetTZLocation() *time.Location { | |||||
| return engine.TZLocation | |||||
| } | |||||
| // SetTZLocation sets time zone of the application | |||||
| func (engine *Engine) SetTZLocation(tz *time.Location) { | |||||
| engine.TZLocation = tz | |||||
| } | |||||
| // GetTZDatabase returns time zone of the database | |||||
| func (engine *Engine) GetTZDatabase() *time.Location { | |||||
| return engine.DatabaseTZ | |||||
| } | |||||
| // SetTZDatabase sets time zone of the database | |||||
| func (engine *Engine) SetTZDatabase(tz *time.Location) { | |||||
| engine.DatabaseTZ = tz | |||||
| } | |||||
| // Unscoped always disable struct tag "deleted" | |||||
| func (engine *Engine) Unscoped() *Session { | |||||
| session := engine.NewSession() | session := engine.NewSession() | ||||
| session.isAutoClose = true | session.isAutoClose = true | ||||
| return session.BufferSize(size) | |||||
| return session.Unscoped() | |||||
| } | } | ||||
| @@ -0,0 +1,194 @@ | |||||
| // 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/core" | |||||
| ) | |||||
| // EngineGroup defines an engine group | |||||
| type EngineGroup struct { | |||||
| *Engine | |||||
| slaves []*Engine | |||||
| policy GroupPolicy | |||||
| } | |||||
| // NewEngineGroup creates a new engine group | |||||
| func NewEngineGroup(args1 interface{}, args2 interface{}, policies ...GroupPolicy) (*EngineGroup, error) { | |||||
| var eg EngineGroup | |||||
| if len(policies) > 0 { | |||||
| eg.policy = policies[0] | |||||
| } else { | |||||
| eg.policy = RoundRobinPolicy() | |||||
| } | |||||
| driverName, ok1 := args1.(string) | |||||
| conns, ok2 := args2.([]string) | |||||
| if ok1 && ok2 { | |||||
| engines := make([]*Engine, len(conns)) | |||||
| for i, conn := range conns { | |||||
| engine, err := NewEngine(driverName, conn) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| engine.engineGroup = &eg | |||||
| engines[i] = engine | |||||
| } | |||||
| eg.Engine = engines[0] | |||||
| eg.slaves = engines[1:] | |||||
| return &eg, nil | |||||
| } | |||||
| master, ok3 := args1.(*Engine) | |||||
| slaves, ok4 := args2.([]*Engine) | |||||
| if ok3 && ok4 { | |||||
| master.engineGroup = &eg | |||||
| for i := 0; i < len(slaves); i++ { | |||||
| slaves[i].engineGroup = &eg | |||||
| } | |||||
| eg.Engine = master | |||||
| eg.slaves = slaves | |||||
| return &eg, nil | |||||
| } | |||||
| return nil, ErrParamsType | |||||
| } | |||||
| // Close the engine | |||||
| func (eg *EngineGroup) Close() error { | |||||
| err := eg.Engine.Close() | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| for i := 0; i < len(eg.slaves); i++ { | |||||
| err := eg.slaves[i].Close() | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| // Master returns the master engine | |||||
| func (eg *EngineGroup) Master() *Engine { | |||||
| return eg.Engine | |||||
| } | |||||
| // Ping tests if database is alive | |||||
| func (eg *EngineGroup) Ping() error { | |||||
| if err := eg.Engine.Ping(); err != nil { | |||||
| return err | |||||
| } | |||||
| for _, slave := range eg.slaves { | |||||
| if err := slave.Ping(); err != nil { | |||||
| return err | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| // SetColumnMapper set the column name mapping rule | |||||
| func (eg *EngineGroup) SetColumnMapper(mapper core.IMapper) { | |||||
| eg.Engine.ColumnMapper = mapper | |||||
| for i := 0; i < len(eg.slaves); i++ { | |||||
| eg.slaves[i].ColumnMapper = mapper | |||||
| } | |||||
| } | |||||
| // SetDefaultCacher set the default cacher | |||||
| func (eg *EngineGroup) SetDefaultCacher(cacher core.Cacher) { | |||||
| eg.Engine.SetDefaultCacher(cacher) | |||||
| for i := 0; i < len(eg.slaves); i++ { | |||||
| eg.slaves[i].SetDefaultCacher(cacher) | |||||
| } | |||||
| } | |||||
| // SetLogger set the new logger | |||||
| func (eg *EngineGroup) SetLogger(logger core.ILogger) { | |||||
| eg.Engine.SetLogger(logger) | |||||
| for i := 0; i < len(eg.slaves); i++ { | |||||
| eg.slaves[i].SetLogger(logger) | |||||
| } | |||||
| } | |||||
| // SetLogLevel sets the logger level | |||||
| func (eg *EngineGroup) SetLogLevel(level core.LogLevel) { | |||||
| eg.Engine.SetLogLevel(level) | |||||
| for i := 0; i < len(eg.slaves); i++ { | |||||
| eg.slaves[i].SetLogLevel(level) | |||||
| } | |||||
| } | |||||
| // SetMapper set the name mapping rules | |||||
| func (eg *EngineGroup) SetMapper(mapper core.IMapper) { | |||||
| eg.Engine.SetMapper(mapper) | |||||
| for i := 0; i < len(eg.slaves); i++ { | |||||
| eg.slaves[i].SetMapper(mapper) | |||||
| } | |||||
| } | |||||
| // SetMaxIdleConns set the max idle connections on pool, default is 2 | |||||
| func (eg *EngineGroup) SetMaxIdleConns(conns int) { | |||||
| eg.Engine.db.SetMaxIdleConns(conns) | |||||
| for i := 0; i < len(eg.slaves); i++ { | |||||
| eg.slaves[i].db.SetMaxIdleConns(conns) | |||||
| } | |||||
| } | |||||
| // SetMaxOpenConns is only available for go 1.2+ | |||||
| func (eg *EngineGroup) SetMaxOpenConns(conns int) { | |||||
| eg.Engine.db.SetMaxOpenConns(conns) | |||||
| for i := 0; i < len(eg.slaves); i++ { | |||||
| eg.slaves[i].db.SetMaxOpenConns(conns) | |||||
| } | |||||
| } | |||||
| // SetPolicy set the group policy | |||||
| func (eg *EngineGroup) SetPolicy(policy GroupPolicy) *EngineGroup { | |||||
| eg.policy = policy | |||||
| return eg | |||||
| } | |||||
| // SetTableMapper set the table name mapping rule | |||||
| func (eg *EngineGroup) SetTableMapper(mapper core.IMapper) { | |||||
| eg.Engine.TableMapper = mapper | |||||
| for i := 0; i < len(eg.slaves); i++ { | |||||
| eg.slaves[i].TableMapper = mapper | |||||
| } | |||||
| } | |||||
| // ShowExecTime show SQL statement and execute time or not on logger if log level is great than INFO | |||||
| func (eg *EngineGroup) ShowExecTime(show ...bool) { | |||||
| eg.Engine.ShowExecTime(show...) | |||||
| for i := 0; i < len(eg.slaves); i++ { | |||||
| eg.slaves[i].ShowExecTime(show...) | |||||
| } | |||||
| } | |||||
| // ShowSQL show SQL statement or not on logger if log level is great than INFO | |||||
| func (eg *EngineGroup) ShowSQL(show ...bool) { | |||||
| eg.Engine.ShowSQL(show...) | |||||
| for i := 0; i < len(eg.slaves); i++ { | |||||
| eg.slaves[i].ShowSQL(show...) | |||||
| } | |||||
| } | |||||
| // Slave returns one of the physical databases which is a slave according the policy | |||||
| func (eg *EngineGroup) Slave() *Engine { | |||||
| switch len(eg.slaves) { | |||||
| case 0: | |||||
| return eg.Engine | |||||
| case 1: | |||||
| return eg.slaves[0] | |||||
| } | |||||
| return eg.policy.Slave(eg) | |||||
| } | |||||
| // Slaves returns all the slaves | |||||
| func (eg *EngineGroup) Slaves() []*Engine { | |||||
| return eg.slaves | |||||
| } | |||||
| @@ -0,0 +1,116 @@ | |||||
| // 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 ( | |||||
| "math/rand" | |||||
| "sync" | |||||
| "time" | |||||
| ) | |||||
| // GroupPolicy is be used by chosing the current slave from slaves | |||||
| type GroupPolicy interface { | |||||
| Slave(*EngineGroup) *Engine | |||||
| } | |||||
| // GroupPolicyHandler should be used when a function is a GroupPolicy | |||||
| type GroupPolicyHandler func(*EngineGroup) *Engine | |||||
| // Slave implements the chosen of slaves | |||||
| func (h GroupPolicyHandler) Slave(eg *EngineGroup) *Engine { | |||||
| return h(eg) | |||||
| } | |||||
| // RandomPolicy implmentes randomly chose the slave of slaves | |||||
| func RandomPolicy() GroupPolicyHandler { | |||||
| var r = rand.New(rand.NewSource(time.Now().UnixNano())) | |||||
| return func(g *EngineGroup) *Engine { | |||||
| return g.Slaves()[r.Intn(len(g.Slaves()))] | |||||
| } | |||||
| } | |||||
| // WeightRandomPolicy implmentes randomly chose the slave of slaves | |||||
| func WeightRandomPolicy(weights []int) GroupPolicyHandler { | |||||
| var rands = make([]int, 0, len(weights)) | |||||
| for i := 0; i < len(weights); i++ { | |||||
| for n := 0; n < weights[i]; n++ { | |||||
| rands = append(rands, i) | |||||
| } | |||||
| } | |||||
| var r = rand.New(rand.NewSource(time.Now().UnixNano())) | |||||
| return func(g *EngineGroup) *Engine { | |||||
| var slaves = g.Slaves() | |||||
| idx := rands[r.Intn(len(rands))] | |||||
| if idx >= len(slaves) { | |||||
| idx = len(slaves) - 1 | |||||
| } | |||||
| return slaves[idx] | |||||
| } | |||||
| } | |||||
| func RoundRobinPolicy() GroupPolicyHandler { | |||||
| var pos = -1 | |||||
| var lock sync.Mutex | |||||
| return func(g *EngineGroup) *Engine { | |||||
| var slaves = g.Slaves() | |||||
| lock.Lock() | |||||
| defer lock.Unlock() | |||||
| pos++ | |||||
| if pos >= len(slaves) { | |||||
| pos = 0 | |||||
| } | |||||
| return slaves[pos] | |||||
| } | |||||
| } | |||||
| func WeightRoundRobinPolicy(weights []int) GroupPolicyHandler { | |||||
| var rands = make([]int, 0, len(weights)) | |||||
| for i := 0; i < len(weights); i++ { | |||||
| for n := 0; n < weights[i]; n++ { | |||||
| rands = append(rands, i) | |||||
| } | |||||
| } | |||||
| var pos = -1 | |||||
| var lock sync.Mutex | |||||
| return func(g *EngineGroup) *Engine { | |||||
| var slaves = g.Slaves() | |||||
| lock.Lock() | |||||
| defer lock.Unlock() | |||||
| pos++ | |||||
| if pos >= len(rands) { | |||||
| pos = 0 | |||||
| } | |||||
| idx := rands[pos] | |||||
| if idx >= len(slaves) { | |||||
| idx = len(slaves) - 1 | |||||
| } | |||||
| return slaves[idx] | |||||
| } | |||||
| } | |||||
| // LeastConnPolicy implements GroupPolicy, every time will get the least connections slave | |||||
| func LeastConnPolicy() GroupPolicyHandler { | |||||
| return func(g *EngineGroup) *Engine { | |||||
| var slaves = g.Slaves() | |||||
| connections := 0 | |||||
| idx := 0 | |||||
| for i := 0; i < len(slaves); i++ { | |||||
| openConnections := slaves[i].DB().Stats().OpenConnections | |||||
| if i == 0 { | |||||
| connections = openConnections | |||||
| idx = i | |||||
| } else if openConnections <= connections { | |||||
| connections = openConnections | |||||
| idx = i | |||||
| } | |||||
| } | |||||
| return slaves[idx] | |||||
| } | |||||
| } | |||||
| @@ -12,3 +12,11 @@ import "time" | |||||
| func (engine *Engine) SetConnMaxLifetime(d time.Duration) { | func (engine *Engine) SetConnMaxLifetime(d time.Duration) { | ||||
| engine.db.SetConnMaxLifetime(d) | engine.db.SetConnMaxLifetime(d) | ||||
| } | } | ||||
| // SetConnMaxLifetime sets the maximum amount of time a connection may be reused. | |||||
| func (eg *EngineGroup) SetConnMaxLifetime(d time.Duration) { | |||||
| eg.Engine.SetConnMaxLifetime(d) | |||||
| for i := 0; i < len(eg.slaves); i++ { | |||||
| eg.slaves[i].SetConnMaxLifetime(d) | |||||
| } | |||||
| } | |||||
| @@ -23,4 +23,6 @@ var ( | |||||
| ErrNeedDeletedCond = errors.New("Delete need at least one condition") | ErrNeedDeletedCond = errors.New("Delete need at least one condition") | ||||
| // ErrNotImplemented not implemented | // ErrNotImplemented not implemented | ||||
| ErrNotImplemented = errors.New("Not implemented") | ErrNotImplemented = errors.New("Not implemented") | ||||
| // ErrConditionType condition type unsupported | |||||
| ErrConditionType = errors.New("Unsupported conditon type") | |||||
| ) | ) | ||||
| @@ -0,0 +1,103 @@ | |||||
| // 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" | |||||
| "reflect" | |||||
| "time" | |||||
| "github.com/go-xorm/core" | |||||
| ) | |||||
| // Interface defines the interface which Engine, EngineGroup and Session will implementate. | |||||
| type Interface interface { | |||||
| AllCols() *Session | |||||
| Alias(alias string) *Session | |||||
| Asc(colNames ...string) *Session | |||||
| BufferSize(size int) *Session | |||||
| Cols(columns ...string) *Session | |||||
| Count(...interface{}) (int64, error) | |||||
| CreateIndexes(bean interface{}) error | |||||
| CreateUniques(bean interface{}) error | |||||
| Decr(column string, arg ...interface{}) *Session | |||||
| Desc(...string) *Session | |||||
| Delete(interface{}) (int64, error) | |||||
| Distinct(columns ...string) *Session | |||||
| DropIndexes(bean interface{}) error | |||||
| Exec(string, ...interface{}) (sql.Result, error) | |||||
| Exist(bean ...interface{}) (bool, error) | |||||
| Find(interface{}, ...interface{}) error | |||||
| Get(interface{}) (bool, error) | |||||
| GroupBy(keys string) *Session | |||||
| ID(interface{}) *Session | |||||
| In(string, ...interface{}) *Session | |||||
| Incr(column string, arg ...interface{}) *Session | |||||
| Insert(...interface{}) (int64, error) | |||||
| InsertOne(interface{}) (int64, error) | |||||
| IsTableEmpty(bean interface{}) (bool, error) | |||||
| IsTableExist(beanOrTableName interface{}) (bool, error) | |||||
| Iterate(interface{}, IterFunc) error | |||||
| Limit(int, ...int) *Session | |||||
| NoAutoCondition(...bool) *Session | |||||
| NotIn(string, ...interface{}) *Session | |||||
| Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session | |||||
| Omit(columns ...string) *Session | |||||
| OrderBy(order string) *Session | |||||
| Ping() error | |||||
| Query(sqlOrAgrs ...interface{}) (resultsSlice []map[string][]byte, err error) | |||||
| QueryInterface(sqlorArgs ...interface{}) ([]map[string]interface{}, error) | |||||
| QueryString(sqlorArgs ...interface{}) ([]map[string]string, error) | |||||
| Rows(bean interface{}) (*Rows, error) | |||||
| SetExpr(string, string) *Session | |||||
| SQL(interface{}, ...interface{}) *Session | |||||
| Sum(bean interface{}, colName string) (float64, error) | |||||
| SumInt(bean interface{}, colName string) (int64, error) | |||||
| Sums(bean interface{}, colNames ...string) ([]float64, error) | |||||
| SumsInt(bean interface{}, colNames ...string) ([]int64, error) | |||||
| Table(tableNameOrBean interface{}) *Session | |||||
| Unscoped() *Session | |||||
| Update(bean interface{}, condiBeans ...interface{}) (int64, error) | |||||
| UseBool(...string) *Session | |||||
| Where(interface{}, ...interface{}) *Session | |||||
| } | |||||
| // EngineInterface defines the interface which Engine, EngineGroup will implementate. | |||||
| type EngineInterface interface { | |||||
| Interface | |||||
| Before(func(interface{})) *Session | |||||
| Charset(charset string) *Session | |||||
| CreateTables(...interface{}) error | |||||
| DBMetas() ([]*core.Table, error) | |||||
| Dialect() core.Dialect | |||||
| DropTables(...interface{}) error | |||||
| DumpAllToFile(fp string, tp ...core.DbType) error | |||||
| GetColumnMapper() core.IMapper | |||||
| GetDefaultCacher() core.Cacher | |||||
| GetTableMapper() core.IMapper | |||||
| GetTZDatabase() *time.Location | |||||
| GetTZLocation() *time.Location | |||||
| NewSession() *Session | |||||
| NoAutoTime() *Session | |||||
| Quote(string) string | |||||
| SetDefaultCacher(core.Cacher) | |||||
| SetLogLevel(core.LogLevel) | |||||
| SetMapper(core.IMapper) | |||||
| SetTZDatabase(tz *time.Location) | |||||
| SetTZLocation(tz *time.Location) | |||||
| ShowSQL(show ...bool) | |||||
| Sync(...interface{}) error | |||||
| Sync2(...interface{}) error | |||||
| StoreEngine(storeEngine string) *Session | |||||
| TableInfo(bean interface{}) *Table | |||||
| UnMapType(reflect.Type) | |||||
| } | |||||
| var ( | |||||
| _ Interface = &Session{} | |||||
| _ EngineInterface = &Engine{} | |||||
| _ EngineInterface = &EngineGroup{} | |||||
| ) | |||||
| @@ -76,6 +76,7 @@ func (session *Session) Init() { | |||||
| session.afterDeleteBeans = make(map[interface{}]*[]func(interface{}), 0) | session.afterDeleteBeans = make(map[interface{}]*[]func(interface{}), 0) | ||||
| session.beforeClosures = make([]func(interface{}), 0) | session.beforeClosures = make([]func(interface{}), 0) | ||||
| session.afterClosures = make([]func(interface{}), 0) | session.afterClosures = make([]func(interface{}), 0) | ||||
| session.stmtCache = make(map[uint32]*core.Stmt) | |||||
| session.afterProcessors = make([]executedProcessor, 0) | session.afterProcessors = make([]executedProcessor, 0) | ||||
| @@ -262,13 +263,13 @@ func (session *Session) canCache() bool { | |||||
| return true | return true | ||||
| } | } | ||||
| func (session *Session) doPrepare(sqlStr string) (stmt *core.Stmt, err error) { | |||||
| func (session *Session) doPrepare(db *core.DB, sqlStr string) (stmt *core.Stmt, err error) { | |||||
| crc := crc32.ChecksumIEEE([]byte(sqlStr)) | crc := crc32.ChecksumIEEE([]byte(sqlStr)) | ||||
| // TODO try hash(sqlStr+len(sqlStr)) | // TODO try hash(sqlStr+len(sqlStr)) | ||||
| var has bool | var has bool | ||||
| stmt, has = session.stmtCache[crc] | stmt, has = session.stmtCache[crc] | ||||
| if !has { | if !has { | ||||
| stmt, err = session.DB().Prepare(sqlStr) | |||||
| stmt, err = db.Prepare(sqlStr) | |||||
| if err != nil { | if err != nil { | ||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| @@ -461,6 +462,10 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b | |||||
| hasAssigned = true | hasAssigned = true | ||||
| if len(bs) > 0 { | if len(bs) > 0 { | ||||
| if fieldType.Kind() == reflect.String { | |||||
| fieldValue.SetString(string(bs)) | |||||
| continue | |||||
| } | |||||
| if fieldValue.CanAddr() { | if fieldValue.CanAddr() { | ||||
| err := json.Unmarshal(bs, fieldValue.Addr().Interface()) | err := json.Unmarshal(bs, fieldValue.Addr().Interface()) | ||||
| if err != nil { | if err != nil { | ||||
| @@ -34,27 +34,27 @@ func (session *Session) str2Time(col *core.Column, data string) (outTime time.Ti | |||||
| sd, err := strconv.ParseInt(sdata, 10, 64) | sd, err := strconv.ParseInt(sdata, 10, 64) | ||||
| if err == nil { | if err == nil { | ||||
| x = time.Unix(sd, 0) | x = time.Unix(sd, 0) | ||||
| session.engine.logger.Debugf("time(0) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) | |||||
| //session.engine.logger.Debugf("time(0) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) | |||||
| } else { | } else { | ||||
| session.engine.logger.Debugf("time(0) err key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) | |||||
| //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, "-") { | } else if len(sdata) > 19 && strings.Contains(sdata, "-") { | ||||
| x, err = time.ParseInLocation(time.RFC3339Nano, sdata, parseLoc) | x, err = time.ParseInLocation(time.RFC3339Nano, sdata, parseLoc) | ||||
| session.engine.logger.Debugf("time(1) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) | session.engine.logger.Debugf("time(1) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) | ||||
| if err != nil { | if err != nil { | ||||
| x, err = time.ParseInLocation("2006-01-02 15:04:05.999999999", sdata, parseLoc) | x, err = time.ParseInLocation("2006-01-02 15:04:05.999999999", sdata, parseLoc) | ||||
| session.engine.logger.Debugf("time(2) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) | |||||
| //session.engine.logger.Debugf("time(2) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) | |||||
| } | } | ||||
| if err != nil { | if err != nil { | ||||
| x, err = time.ParseInLocation("2006-01-02 15:04:05.9999999 Z07:00", sdata, parseLoc) | x, err = time.ParseInLocation("2006-01-02 15:04:05.9999999 Z07:00", sdata, parseLoc) | ||||
| session.engine.logger.Debugf("time(3) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) | |||||
| //session.engine.logger.Debugf("time(3) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) | |||||
| } | } | ||||
| } else if len(sdata) == 19 && strings.Contains(sdata, "-") { | } else if len(sdata) == 19 && strings.Contains(sdata, "-") { | ||||
| x, err = time.ParseInLocation("2006-01-02 15:04:05", sdata, parseLoc) | x, err = time.ParseInLocation("2006-01-02 15:04:05", sdata, parseLoc) | ||||
| session.engine.logger.Debugf("time(4) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) | |||||
| //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] == '-' { | } else if len(sdata) == 10 && sdata[4] == '-' && sdata[7] == '-' { | ||||
| x, err = time.ParseInLocation("2006-01-02", sdata, parseLoc) | x, err = time.ParseInLocation("2006-01-02", sdata, parseLoc) | ||||
| session.engine.logger.Debugf("time(5) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) | |||||
| //session.engine.logger.Debugf("time(5) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) | |||||
| } else if col.SQLType.Name == core.Time { | } else if col.SQLType.Name == core.Time { | ||||
| if strings.Contains(sdata, " ") { | if strings.Contains(sdata, " ") { | ||||
| ssd := strings.Split(sdata, " ") | ssd := strings.Split(sdata, " ") | ||||
| @@ -68,7 +68,7 @@ func (session *Session) str2Time(col *core.Column, data string) (outTime time.Ti | |||||
| st := fmt.Sprintf("2006-01-02 %v", sdata) | st := fmt.Sprintf("2006-01-02 %v", sdata) | ||||
| x, err = time.ParseInLocation("2006-01-02 15:04:05", st, parseLoc) | x, err = time.ParseInLocation("2006-01-02 15:04:05", st, parseLoc) | ||||
| session.engine.logger.Debugf("time(6) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) | |||||
| //session.engine.logger.Debugf("time(6) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) | |||||
| } else { | } else { | ||||
| outErr = fmt.Errorf("unsupported time format %v", sdata) | outErr = fmt.Errorf("unsupported time format %v", sdata) | ||||
| return | return | ||||
| @@ -10,6 +10,7 @@ import ( | |||||
| "reflect" | "reflect" | ||||
| "github.com/go-xorm/builder" | "github.com/go-xorm/builder" | ||||
| "github.com/go-xorm/core" | |||||
| ) | ) | ||||
| // Exist returns true if the record exist otherwise return false | // Exist returns true if the record exist otherwise return false | ||||
| @@ -35,10 +36,18 @@ func (session *Session) Exist(bean ...interface{}) (bool, error) { | |||||
| return false, err | return false, err | ||||
| } | } | ||||
| sqlStr = fmt.Sprintf("SELECT * FROM %s WHERE %s LIMIT 1", tableName, condSQL) | |||||
| if session.engine.dialect.DBType() == core.MSSQL { | |||||
| sqlStr = fmt.Sprintf("SELECT top 1 * FROM %s WHERE %s", tableName, condSQL) | |||||
| } else { | |||||
| sqlStr = fmt.Sprintf("SELECT * FROM %s WHERE %s LIMIT 1", tableName, condSQL) | |||||
| } | |||||
| args = condArgs | args = condArgs | ||||
| } else { | } else { | ||||
| sqlStr = fmt.Sprintf("SELECT * FROM %s LIMIT 1", tableName) | |||||
| if session.engine.dialect.DBType() == core.MSSQL { | |||||
| sqlStr = fmt.Sprintf("SELECT top 1 * FROM %s", tableName) | |||||
| } else { | |||||
| sqlStr = fmt.Sprintf("SELECT * FROM %s LIMIT 1", tableName) | |||||
| } | |||||
| args = []interface{}{} | args = []interface{}{} | ||||
| } | } | ||||
| } else { | } else { | ||||
| @@ -5,6 +5,7 @@ | |||||
| package xorm | package xorm | ||||
| import ( | import ( | ||||
| "database/sql" | |||||
| "errors" | "errors" | ||||
| "reflect" | "reflect" | ||||
| "strconv" | "strconv" | ||||
| @@ -79,6 +80,13 @@ func (session *Session) nocacheGet(beanKind reflect.Kind, table *core.Table, bea | |||||
| return false, nil | return false, nil | ||||
| } | } | ||||
| switch bean.(type) { | |||||
| case sql.NullInt64, sql.NullBool, sql.NullFloat64, sql.NullString: | |||||
| return true, rows.Scan(&bean) | |||||
| case *sql.NullInt64, *sql.NullBool, *sql.NullFloat64, *sql.NullString: | |||||
| return true, rows.Scan(bean) | |||||
| } | |||||
| switch beanKind { | switch beanKind { | ||||
| case reflect.Struct: | case reflect.Struct: | ||||
| fields, err := rows.Columns() | fields, err := rows.Columns() | ||||
| @@ -400,7 +400,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { | |||||
| return 0, err | return 0, err | ||||
| } | } | ||||
| handleAfterInsertProcessorFunc(bean) | |||||
| defer handleAfterInsertProcessorFunc(bean) | |||||
| if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache { | if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache { | ||||
| session.cacheInsert(table, tableName) | session.cacheInsert(table, tableName) | ||||
| @@ -445,7 +445,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { | |||||
| if err != nil { | if err != nil { | ||||
| return 0, err | return 0, err | ||||
| } | } | ||||
| handleAfterInsertProcessorFunc(bean) | |||||
| defer handleAfterInsertProcessorFunc(bean) | |||||
| if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache { | if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache { | ||||
| session.cacheInsert(table, tableName) | session.cacheInsert(table, tableName) | ||||
| @@ -8,17 +8,92 @@ import ( | |||||
| "fmt" | "fmt" | ||||
| "reflect" | "reflect" | ||||
| "strconv" | "strconv" | ||||
| "strings" | |||||
| "time" | "time" | ||||
| "github.com/go-xorm/builder" | |||||
| "github.com/go-xorm/core" | "github.com/go-xorm/core" | ||||
| ) | ) | ||||
| func (session *Session) genQuerySQL(sqlorArgs ...interface{}) (string, []interface{}, error) { | |||||
| if len(sqlorArgs) > 0 { | |||||
| switch sqlorArgs[0].(type) { | |||||
| case string: | |||||
| return sqlorArgs[0].(string), sqlorArgs[1:], nil | |||||
| case *builder.Builder: | |||||
| return sqlorArgs[0].(*builder.Builder).ToSQL() | |||||
| case builder.Builder: | |||||
| bd := sqlorArgs[0].(builder.Builder) | |||||
| return bd.ToSQL() | |||||
| default: | |||||
| return "", nil, ErrUnSupportedType | |||||
| } | |||||
| } | |||||
| if session.statement.RawSQL != "" { | |||||
| return session.statement.RawSQL, session.statement.RawParams, nil | |||||
| } | |||||
| if len(session.statement.TableName()) <= 0 { | |||||
| return "", nil, ErrTableNotFound | |||||
| } | |||||
| var columnStr = session.statement.ColumnStr | |||||
| if len(session.statement.selectStr) > 0 { | |||||
| columnStr = session.statement.selectStr | |||||
| } else { | |||||
| if session.statement.JoinStr == "" { | |||||
| if columnStr == "" { | |||||
| if session.statement.GroupByStr != "" { | |||||
| columnStr = session.statement.Engine.Quote(strings.Replace(session.statement.GroupByStr, ",", session.engine.Quote(","), -1)) | |||||
| } else { | |||||
| columnStr = session.statement.genColumnStr() | |||||
| } | |||||
| } | |||||
| } else { | |||||
| if columnStr == "" { | |||||
| if session.statement.GroupByStr != "" { | |||||
| columnStr = session.statement.Engine.Quote(strings.Replace(session.statement.GroupByStr, ",", session.engine.Quote(","), -1)) | |||||
| } else { | |||||
| columnStr = "*" | |||||
| } | |||||
| } | |||||
| } | |||||
| if columnStr == "" { | |||||
| columnStr = "*" | |||||
| } | |||||
| } | |||||
| condSQL, condArgs, err := builder.ToSQL(session.statement.cond) | |||||
| if err != nil { | |||||
| return "", nil, err | |||||
| } | |||||
| args := append(session.statement.joinArgs, condArgs...) | |||||
| sqlStr, err := session.statement.genSelectSQL(columnStr, condSQL) | |||||
| if err != nil { | |||||
| return "", nil, err | |||||
| } | |||||
| // for mssql and use limit | |||||
| qs := strings.Count(sqlStr, "?") | |||||
| if len(args)*2 == qs { | |||||
| args = append(args, args...) | |||||
| } | |||||
| return sqlStr, args, nil | |||||
| } | |||||
| // Query runs 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, args ...interface{}) ([]map[string][]byte, error) { | |||||
| func (session *Session) Query(sqlorArgs ...interface{}) ([]map[string][]byte, error) { | |||||
| if session.isAutoClose { | if session.isAutoClose { | ||||
| defer session.Close() | defer session.Close() | ||||
| } | } | ||||
| sqlStr, args, err := session.genQuerySQL(sqlorArgs...) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| return session.queryBytes(sqlStr, args...) | return session.queryBytes(sqlStr, args...) | ||||
| } | } | ||||
| @@ -114,11 +189,16 @@ func rows2Strings(rows *core.Rows) (resultsSlice []map[string]string, err error) | |||||
| } | } | ||||
| // QueryString runs a raw sql and return records as []map[string]string | // QueryString runs a raw sql and return records as []map[string]string | ||||
| func (session *Session) QueryString(sqlStr string, args ...interface{}) ([]map[string]string, error) { | |||||
| func (session *Session) QueryString(sqlorArgs ...interface{}) ([]map[string]string, error) { | |||||
| if session.isAutoClose { | if session.isAutoClose { | ||||
| defer session.Close() | defer session.Close() | ||||
| } | } | ||||
| sqlStr, args, err := session.genQuerySQL(sqlorArgs...) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| rows, err := session.queryRows(sqlStr, args...) | rows, err := session.queryRows(sqlStr, args...) | ||||
| if err != nil { | if err != nil { | ||||
| return nil, err | return nil, err | ||||
| @@ -162,11 +242,16 @@ func rows2Interfaces(rows *core.Rows) (resultsSlice []map[string]interface{}, er | |||||
| } | } | ||||
| // QueryInterface runs a raw sql and return records as []map[string]interface{} | // QueryInterface runs a raw sql and return records as []map[string]interface{} | ||||
| func (session *Session) QueryInterface(sqlStr string, args ...interface{}) ([]map[string]interface{}, error) { | |||||
| func (session *Session) QueryInterface(sqlorArgs ...interface{}) ([]map[string]interface{}, error) { | |||||
| if session.isAutoClose { | if session.isAutoClose { | ||||
| defer session.Close() | defer session.Close() | ||||
| } | } | ||||
| sqlStr, args, err := session.genQuerySQL(sqlorArgs...) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| rows, err := session.queryRows(sqlStr, args...) | rows, err := session.queryRows(sqlStr, args...) | ||||
| if err != nil { | if err != nil { | ||||
| return nil, err | return nil, err | ||||
| @@ -47,9 +47,16 @@ func (session *Session) queryRows(sqlStr string, args ...interface{}) (*core.Row | |||||
| } | } | ||||
| if session.isAutoCommit { | if session.isAutoCommit { | ||||
| var db *core.DB | |||||
| if session.engine.engineGroup != nil { | |||||
| db = session.engine.engineGroup.Slave().DB() | |||||
| } else { | |||||
| db = session.DB() | |||||
| } | |||||
| if session.prepareStmt { | if session.prepareStmt { | ||||
| // don't clear stmt since session will cache them | // don't clear stmt since session will cache them | ||||
| stmt, err := session.doPrepare(sqlStr) | |||||
| stmt, err := session.doPrepare(db, sqlStr) | |||||
| if err != nil { | if err != nil { | ||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| @@ -61,7 +68,7 @@ func (session *Session) queryRows(sqlStr string, args ...interface{}) (*core.Row | |||||
| return rows, nil | return rows, nil | ||||
| } | } | ||||
| rows, err := session.DB().Query(sqlStr, args...) | |||||
| rows, err := db.Query(sqlStr, args...) | |||||
| if err != nil { | if err != nil { | ||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| @@ -171,7 +178,7 @@ func (session *Session) exec(sqlStr string, args ...interface{}) (sql.Result, er | |||||
| } | } | ||||
| if session.prepareStmt { | if session.prepareStmt { | ||||
| stmt, err := session.doPrepare(sqlStr) | |||||
| stmt, err := session.doPrepare(session.DB(), sqlStr) | |||||
| if err != nil { | if err != nil { | ||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| @@ -242,10 +242,23 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 | |||||
| var autoCond builder.Cond | var autoCond builder.Cond | ||||
| if !session.statement.noAutoCondition && len(condiBean) > 0 { | if !session.statement.noAutoCondition && len(condiBean) > 0 { | ||||
| var err error | |||||
| autoCond, err = session.statement.buildConds(session.statement.RefTable, condiBean[0], true, true, false, true, false) | |||||
| if err != nil { | |||||
| return 0, err | |||||
| if c, ok := condiBean[0].(map[string]interface{}); ok { | |||||
| autoCond = builder.Eq(c) | |||||
| } else { | |||||
| ct := reflect.TypeOf(condiBean[0]) | |||||
| k := ct.Kind() | |||||
| if k == reflect.Ptr { | |||||
| k = ct.Elem().Kind() | |||||
| } | |||||
| if k == reflect.Struct { | |||||
| var err error | |||||
| autoCond, err = session.statement.buildConds(session.statement.RefTable, condiBean[0], true, true, false, true, false) | |||||
| if err != nil { | |||||
| return 0, err | |||||
| } | |||||
| } else { | |||||
| return 0, ErrConditionType | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -160,6 +160,9 @@ func (statement *Statement) And(query interface{}, args ...interface{}) *Stateme | |||||
| case string: | case string: | ||||
| cond := builder.Expr(query.(string), args...) | cond := builder.Expr(query.(string), args...) | ||||
| statement.cond = statement.cond.And(cond) | statement.cond = statement.cond.And(cond) | ||||
| case map[string]interface{}: | |||||
| cond := builder.Eq(query.(map[string]interface{})) | |||||
| statement.cond = statement.cond.And(cond) | |||||
| case builder.Cond: | case builder.Cond: | ||||
| cond := query.(builder.Cond) | cond := query.(builder.Cond) | ||||
| statement.cond = statement.cond.And(cond) | statement.cond = statement.cond.And(cond) | ||||
| @@ -181,6 +184,9 @@ func (statement *Statement) Or(query interface{}, args ...interface{}) *Statemen | |||||
| case string: | case string: | ||||
| cond := builder.Expr(query.(string), args...) | cond := builder.Expr(query.(string), args...) | ||||
| statement.cond = statement.cond.Or(cond) | statement.cond = statement.cond.Or(cond) | ||||
| case map[string]interface{}: | |||||
| cond := builder.Eq(query.(map[string]interface{})) | |||||
| statement.cond = statement.cond.Or(cond) | |||||
| case builder.Cond: | case builder.Cond: | ||||
| cond := query.(builder.Cond) | cond := query.(builder.Cond) | ||||
| statement.cond = statement.cond.Or(cond) | statement.cond = statement.cond.Or(cond) | ||||
| @@ -901,8 +907,12 @@ func (statement *Statement) genDelIndexSQL() []string { | |||||
| func (statement *Statement) genAddColumnStr(col *core.Column) (string, []interface{}) { | func (statement *Statement) genAddColumnStr(col *core.Column) (string, []interface{}) { | ||||
| quote := statement.Engine.Quote | quote := statement.Engine.Quote | ||||
| sql := fmt.Sprintf("ALTER TABLE %v ADD %v;", quote(statement.TableName()), | |||||
| sql := fmt.Sprintf("ALTER TABLE %v ADD %v", quote(statement.TableName()), | |||||
| col.String(statement.Engine.dialect)) | col.String(statement.Engine.dialect)) | ||||
| if statement.Engine.dialect.DBType() == core.MYSQL && len(col.Comment) > 0 { | |||||
| sql += " COMMENT '" + col.Comment + "'" | |||||
| } | |||||
| sql += ";" | |||||
| return sql, []interface{}{} | return sql, []interface{}{} | ||||
| } | } | ||||
| @@ -462,16 +462,16 @@ | |||||
| "revisionTime": "2016-11-01T11:13:14Z" | "revisionTime": "2016-11-01T11:13:14Z" | ||||
| }, | }, | ||||
| { | { | ||||
| "checksumSHA1": "9SXbj96wb1PgppBZzxMIN0axbFQ=", | |||||
| "checksumSHA1": "HsUSlgz1VKEEiZdkXY5qdLzexWU=", | |||||
| "path": "github.com/go-xorm/builder", | "path": "github.com/go-xorm/builder", | ||||
| "revision": "c8871c857d2555fbfbd8524f895be5386d3d8836", | |||||
| "revisionTime": "2017-05-19T03:21:30Z" | |||||
| "revision": "488224409dd8aa2ce7a5baf8d10d55764a913738", | |||||
| "revisionTime": "2018-01-16T06:54:19Z" | |||||
| }, | }, | ||||
| { | { | ||||
| "checksumSHA1": "HMavuxvDhKOwmbbFnYt9hfT6jE0=", | |||||
| "checksumSHA1": "7JjlvSpGfLa49MHElks8NGBUfFA=", | |||||
| "path": "github.com/go-xorm/core", | "path": "github.com/go-xorm/core", | ||||
| "revision": "da1adaf7a28ca792961721a34e6e04945200c890", | |||||
| "revisionTime": "2017-09-09T08:56:53Z" | |||||
| "revision": "cb1d0ca71f42d3ee1bf4aba7daa16099bc31a7e9", | |||||
| "revisionTime": "2017-12-21T01:38:49Z" | |||||
| }, | }, | ||||
| { | { | ||||
| "checksumSHA1": "k52lEKLp8j5M+jFpe+3u+bIFpxQ=", | "checksumSHA1": "k52lEKLp8j5M+jFpe+3u+bIFpxQ=", | ||||
| @@ -480,10 +480,10 @@ | |||||
| "revisionTime": "2016-08-11T02:11:45Z" | "revisionTime": "2016-08-11T02:11:45Z" | ||||
| }, | }, | ||||
| { | { | ||||
| "checksumSHA1": "+KmPfckyKvrUZPIHBYHylg/7V8o=", | |||||
| "checksumSHA1": "eGBz6F3I/0naVUclZ6GZWc3EzQo=", | |||||
| "path": "github.com/go-xorm/xorm", | "path": "github.com/go-xorm/xorm", | ||||
| "revision": "29d4a0330a00b9be468b70e3fb0f74109348c358", | |||||
| "revisionTime": "2017-09-30T01:26:13Z" | |||||
| "revision": "d4149d1eee0c2c488a74a5863fd9caf13d60fd03", | |||||
| "revisionTime": "2018-01-22T13:32:35Z" | |||||
| }, | }, | ||||
| { | { | ||||
| "checksumSHA1": "1ft/4j5MFa7C9dPI9whL03HSUzk=", | "checksumSHA1": "1ft/4j5MFa7C9dPI9whL03HSUzk=", | ||||