├── imgs └── er.jpg ├── doc ├── performance │ ├── img.png │ ├── img_1.png │ └── PaginatorWG.md ├── delete.md ├── join.md ├── security.md ├── model.md ├── find.md ├── special.md ├── select.md ├── base.md ├── update.md └── intro.md ├── go.mod ├── .editorconfig ├── logger_test.go ├── gorose-pro.iml ├── .gitignore ├── engin_interface.go ├── orm_api_interface.go ├── orm_execute_interface.go ├── orm_session_interface.go ├── config.go ├── binder_interface.go ├── session_interface.go ├── util_test.go ├── gorose_test.go ├── logger_interface.go ├── gorose.go ├── LICENSE ├── engin_test.go ├── orm_test.go ├── orm_api.go ├── orm_query_interface.go ├── err.go ├── orm_execute_test.go ├── orm_interface.go ├── logger.go ├── util.go ├── session_test.go ├── orm_execute.go ├── engin.go ├── README.md ├── README_en.md ├── orm_query_test.go ├── binder.go ├── orm.go ├── session.go └── orm_query.go /imgs/er.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobycroft/gorose-pro/HEAD/imgs/er.jpg -------------------------------------------------------------------------------- /doc/performance/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobycroft/gorose-pro/HEAD/doc/performance/img.png -------------------------------------------------------------------------------- /doc/performance/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobycroft/gorose-pro/HEAD/doc/performance/img_1.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tobycroft/gorose-pro 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/gohouse/golib v0.0.0-20210711163732-a5c22059eb75 7 | github.com/gohouse/t v0.0.0-20201007094014-630049a6bfe9 8 | github.com/mattn/go-sqlite3 v1.14.8 9 | ) 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 4 9 | end_of_line = lf 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.go] 14 | indent_style = tab 15 | indent_size = 4 16 | 17 | [*.md] 18 | trim_trailing_whitespace = false 19 | -------------------------------------------------------------------------------- /logger_test.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestDefaultLogger(t *testing.T) { 9 | l := NewLogger(&LogOption{FilePath: "/tmp/gorose.log", EnableErrorLog: true}) 10 | var sqlstr = "select xxx from xxx where a='a' and b=\"33\"" 11 | l.Sql(sqlstr, time.Duration(1<<4)) 12 | t.Log("logger success") 13 | } 14 | -------------------------------------------------------------------------------- /gorose-pro.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Thumbs.db 2 | .DS_Store 3 | *.swp 4 | /.idea/ 5 | /vendor/ 6 | glide.lock 7 | *.sqlite 8 | # Binaries for programs and plugins 9 | *.exe 10 | *.exe~ 11 | *.dll 12 | *.so 13 | *.dylib 14 | 15 | # Test binary, built with `go test -c` 16 | *.test 17 | 18 | # Output of the go coverage tool, specifically when used with LiteIDE 19 | *.out 20 | *.sqlite 21 | *.log 22 | /go.sum 23 | -------------------------------------------------------------------------------- /engin_interface.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | import "database/sql" 4 | 5 | // IEngin ... 6 | type IEngin interface { 7 | GetExecuteDB() *sql.DB 8 | GetQueryDB() *sql.DB 9 | //EnableSqlLog(e ...bool) 10 | //IfEnableSqlLog() (e bool) 11 | //SetPrefix(pre string) 12 | GetPrefix() (pre string) 13 | //NewSession() ISession 14 | //NewOrm() IOrm 15 | SetLogger(lg ILogger) 16 | GetLogger() ILogger 17 | GetDriver() string 18 | } 19 | -------------------------------------------------------------------------------- /orm_api_interface.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | // IOrmApi ... 4 | type IOrmApi interface { 5 | GetTable() string 6 | GetFields() []string 7 | SetWhere(arg [][]interface{}) 8 | GetWhere() [][]interface{} 9 | GetOrder() string 10 | GetLimit() int 11 | GetOffset() int 12 | GetJoin() [][]interface{} 13 | GetDistinct() bool 14 | GetGroup() string 15 | GetHaving() string 16 | GetData() interface{} 17 | ExtraCols(args ...string) IOrm 18 | ResetExtraCols() IOrm 19 | GetExtraCols() []string 20 | GetPessimisticLock() string 21 | } 22 | -------------------------------------------------------------------------------- /orm_execute_interface.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | // IOrmExecute ... 4 | type IOrmExecute interface { 5 | GetForce() bool 6 | // insert,insertGetId 7 | Insert(data ...interface{}) (int64, error) 8 | InsertGetId(data ...interface{}) (int64, error) 9 | Update(data ...interface{}) (int64, error) 10 | // updateOrInsert 11 | Replace(data ...interface{}) (int64, error) 12 | // increment,decrement 13 | // 在操作过程中你还可以指定额外的列进行更新: 14 | Increment(args ...interface{}) (int64, error) 15 | Decrement(args ...interface{}) (int64, error) 16 | // delete 17 | Delete() (int64, error) 18 | //LastInsertId() int64 19 | Force() IOrm 20 | } 21 | -------------------------------------------------------------------------------- /doc/delete.md: -------------------------------------------------------------------------------- 1 | # Delete方法 2 | 3 | 4 | ```go 5 | 6 | func Api_delete(qq interface{}) bool { 7 | db := tuuz.Db().Table(table) 8 | where := map[string]interface{}{ 9 | "qq": qq, 10 | } 11 | db.Where(where) 12 | _, err := db.Delete() 13 | if err != nil { 14 | Log.Dbrr(err, tuuz.FUNCTION_ALL()) 15 | return false 16 | } else { 17 | return true 18 | } 19 | } 20 | 21 | ``` 22 | 23 | 很简单,没啥说的,没有where的时候delete会出错 24 | 25 | 26 | 0.[基础准备](./base.md) 27 | 28 | 1.[select方法](./select.md) 29 | 30 | 2.[find/first方法](./find.md) 31 | 32 | 3.[update方法](./update.md) 33 | 34 | 5.[insert方法](./insert.md) 35 | 36 | 6.[安全相关](./security.md) 37 | -------------------------------------------------------------------------------- /orm_session_interface.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | // IOrmSession ... 4 | type IOrmSession interface { 5 | //Close() 6 | //Table(bind interface{}) IOrm 7 | //Bind(bind interface{}) ISession 8 | Begin() (err error) 9 | Rollback() (err error) 10 | Commit() (err error) 11 | //Transaction(closer ...func(session ISession) error) (err error) 12 | Query(sqlstring string, args ...interface{}) ([]Data, error) 13 | Execute(sqlstring string, args ...interface{}) (int64, error) 14 | //GetMasterDriver() string 15 | //GetSlaveDriver() string 16 | LastInsertId() int64 17 | LastSql() string 18 | //SetIBinder(b IBinder) 19 | //GetTableName() (string, error) 20 | GetIBinder() IBinder 21 | SetUnion(u interface{}) 22 | GetUnion() interface{} 23 | } 24 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | // Config ... 4 | type Config struct { 5 | Driver string `json:"driver"` // 驱动: mysql/sqlite3/oracle/mssql/postgres/clickhouse, 如果集群配置了驱动, 这里可以省略 6 | // mysql 示例: 7 | // root:root@tcp(localhost:3306)/test?charset=utf8mb4&parseTime=true 8 | Dsn string `json:"dsn"` // 数据库链接 9 | SetMaxOpenConns int `json:"setMaxOpenConns"` // (连接池)最大打开的连接数,默认值为0表示不限制 10 | SetMaxIdleConns int `json:"setMaxIdleConns"` // (连接池)闲置的连接数, 默认0 11 | Prefix string `json:"prefix"` // 表前缀, 如果集群配置了前缀, 这里可以省略 12 | } 13 | 14 | // ConfigCluster ... 15 | type ConfigCluster struct { 16 | Master []Config // 主 17 | Slave []Config // 从 18 | Driver string // 驱动 19 | Prefix string // 前缀 20 | } 21 | -------------------------------------------------------------------------------- /doc/join.md: -------------------------------------------------------------------------------- 1 | # 更多方法 2 | 3 | ## Join方法 4 | ```go 5 | func (self *Interface) Api_join_select(group_id, user_id interface{}) []gorose.Data { 6 | db := self.Db.Table(table) 7 | where := map[string]interface{}{ 8 | "group_id": group_id, 9 | "user_id": user_id, 10 | } 11 | db.Where(where) 12 | db.Join("coin on coin.id=cid") 13 | db.Where("amount", ">", 0) 14 | ret, err := db.Get() 15 | if err != nil { 16 | Log.Dbrr(err, tuuz.FUNCTION_ALL()) 17 | return nil 18 | } else { 19 | return ret 20 | } 21 | } 22 | ``` 23 | 24 | 25 | 0.[基础准备](./base.md) 26 | 27 | 1.[select方法](./select.md) 28 | 29 | 2.[find/first方法](./find.md) 30 | 31 | 3.[update方法](./update.md) 32 | 33 | 4.[delete方法](./delete.md) 34 | 35 | 5.[insert方法](./insert.md) 36 | 37 | 6.[安全相关](./security.md) 38 | -------------------------------------------------------------------------------- /binder_interface.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | import "reflect" 4 | 5 | // IBinder 数据绑定对象接口 6 | type IBinder interface { 7 | SetBindOrigin(arg interface{}) 8 | GetBindOrigin() interface{} 9 | SetBindName(arg string) 10 | GetBindName() string 11 | SetBindResult(arg interface{}) 12 | GetBindResult() interface{} 13 | SetBindResultSlice(arg reflect.Value) 14 | GetBindResultSlice() reflect.Value 15 | SetBindFields(arg []string) 16 | GetBindFields() []string 17 | SetBindType(arg BindType) 18 | GetBindType() BindType 19 | //SetBindLimit(arg int) 20 | //GetBindLimit() int 21 | BindParse(prefix string) error 22 | SetBindPrefix(arg string) 23 | GetBindPrefix() string 24 | ResetBindResultSlice() 25 | SetBindAll(arg []Data) 26 | GetBindAll() []Data 27 | ResetBinder() 28 | } 29 | -------------------------------------------------------------------------------- /session_interface.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | // ISession ... 4 | type ISession interface { 5 | Close() 6 | //Table(bind interface{}) IOrm 7 | Bind(bind interface{}) ISession 8 | Begin() (err error) 9 | Rollback() (err error) 10 | Commit() (err error) 11 | Transaction(closer ...func(session ISession) error) (err error) 12 | Query(sqlstring string, args ...interface{}) ([]Data, error) 13 | Execute(sqlstring string, args ...interface{}) (int64, error) 14 | //GetDriver() string 15 | GetIEngin() IEngin 16 | LastInsertId() int64 17 | LastSql() string 18 | //SetIBinder(b IBinder) 19 | GetTableName() (string, error) 20 | SetIBinder(ib IBinder) 21 | GetIBinder() IBinder 22 | SetUnion(u interface{}) 23 | GetUnion() interface{} 24 | SetTransaction(b bool) 25 | GetTransaction() bool 26 | //ResetBinder() 27 | GetBindAll() []Data 28 | GetErr() error 29 | } 30 | -------------------------------------------------------------------------------- /util_test.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestStructToMap(t *testing.T) { 9 | user := Users{Uid: 1, Name: "gorose"} 10 | data := StructToMap(user) 11 | t.Log(data) 12 | } 13 | 14 | func TestIf(t *testing.T) { 15 | closer := func() { 16 | time.Sleep(1 * time.Second) 17 | } 18 | withRunTimeContext(closer, func(td time.Duration) { 19 | t.Log("用时:", td, td.Seconds() > 1) 20 | }) 21 | } 22 | 23 | //func TestStructToMap2(t *testing.T) { 24 | // var u Users 25 | // //res := structForScan(&u) 26 | // res := structForScan(reflect.ValueOf(&u).Interface()) 27 | // for _, item := range res { 28 | // err := varBindValue.BindVal(item, 1234) 29 | // if err != nil { 30 | // t.Error(err.Error()) 31 | // } 32 | // } 33 | // t.Log(res, u) 34 | //} 35 | func Test_getRandomInt(t *testing.T) { 36 | t.Log(getRandomInt(2)) 37 | t.Log(getRandomInt(3)) 38 | t.Log(getRandomInt(2)) 39 | t.Log(getRandomInt(3)) 40 | } 41 | -------------------------------------------------------------------------------- /doc/performance/PaginatorWG.md: -------------------------------------------------------------------------------- 1 | # 性能表现Performance-Test 2 | 3 | ## 实际生产环境下延迟降低:71.9827586206897% 4 | 5 | (Lantency Decrease 71%) 6 | 7 | 阿里云RDS项目实测 8 | 9 | - 原版: 10 | - ![img_1.png](img_1.png) 11 | - 新版: 12 | - ![img.png](img.png) 13 | 14 | ## 远距离环境下延迟减少:28.9115646258503% 15 | 16 | 以下内容仅程序处理时间 17 | 18 | Lantency only for the binary file 19 | 20 | | Old-Paginator | New-PaginatorWG | 21 | |---------------|-----------------| 22 | | 1144ms | 971.641152ms | 23 | | 1134ms | 968.76604ms | 24 | | 1140ms | 964.523454ms | 25 | | 1137ms | 968.157527ms | 26 | | 1142ms | 882.433464ms | 27 | | 1143ms | 974.042662ms | 28 | | 1138ms | 970.14531ms | 29 | | 1146ms | 976.033914ms | 30 | | 1144ms | 973.388182ms | 31 | | 1138ms | 968.376514ms | 32 | | 1150ms | 975.206023ms | 33 | 34 | - 极限性能提高:28.9115646258503% 35 | - 最低性能提高:0.17827868852459% 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /doc/security.md: -------------------------------------------------------------------------------- 1 | # 安全相关 2 | 3 | 1.都用上prepare了,基本上不会有注入了,这里说个真实的案例,我最早是从PHP过来的,在PHP那边我对prepare滚瓜烂熟 4 | 但是毕竟golang是新的战场,再加上orm的不完善,另外就是我的业务逻辑很特殊,所以很多时候是需要直接写SQL语句的, 5 | 在早期,我使用query语句的时候,将值直接写到了sql语句中,那么懂行的人就知道这是会导致preparestatement降级成emulate 6 | 模式运行的罪魁祸首,并且会导致注入,加之当时比比较赶,又刚学Go,就没注意,就写完当天晚上怎么都睡不着,总感觉代码不对 7 | 然后起来看sql这块的,就看到了 8 | ~~~ 9 | db.query("select * from table where user=参数") 10 | ~~~ 11 | 当然我不是这么写的拉,只是举个栗子,我那个句子大概写满了半个屏幕,问题点大概是上面这样,直接写参数值的 12 | 13 | 我一想,不对啊!PHP人家都有处理方法怎么Go没有,我就到gorose开发那边去问,没问出结果,文档也没有 14 | 15 | 后来我才知道,使用 16 | ~~~ 17 | db.query("select * from table where user=?",[]interface{}{参数}) 18 | ~~~ 19 | 来查询,所以这里分享给大家 20 | 21 | 当然我的项目没受到这个影响,毕竟不是B类的项目,不涉及¥没人盯 22 | 23 | 其他地方的安全问题还有就是在where查询的时候 24 | ~~~ 25 | db.where("user=参数") 26 | ~~~ 27 | 也不要这么查询!这样也会导致注入 28 | 29 | 正确的方法应该是 30 | ~~~ 31 | db.where("user","=",参数) 32 | ~~~ 33 | 或者 34 | ~~~ 35 | db.where("user",参数) 36 | ~~~ 37 | 38 | 你看对于ORM来说,就是这些简单而又细节的地方可能导致你的程序出现风险,所以安全无小事,其他的安全部分, 39 | 你可以参考TP的安全说明,不过数据库这块也差不多就是我上面说的这些内容 40 | -------------------------------------------------------------------------------- /gorose_test.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | import ( 4 | "fmt" 5 | _ "github.com/mattn/go-sqlite3" 6 | ) 7 | 8 | func initDB() *Engin { 9 | e, err := Open(&Config{Driver: "sqlite3", Dsn: "./db.sqlite"}) 10 | 11 | if err != nil { 12 | panic(err.Error()) 13 | } 14 | 15 | e.TagName("orm") 16 | e.IgnoreName("ignore") 17 | 18 | initTable(e) 19 | 20 | return e 21 | } 22 | 23 | func initTable(e *Engin) { 24 | var sql = `CREATE TABLE IF NOT EXISTS "users" ( 25 | "uid" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 26 | "name" TEXT NOT NULL default "", 27 | "age" integer NOT NULL default 0 28 | )` 29 | var s = e.NewSession() 30 | var err error 31 | var aff int64 32 | 33 | aff, err = s.Execute(sql) 34 | if err != nil { 35 | return 36 | } 37 | if aff == 0 { 38 | return 39 | } 40 | 41 | aff, err = s.Execute("insert into users(name,age) VALUES(?,?),(?,?),(?,?)", 42 | "fizz", 18, "gorose", 19, "fizzday", 20) 43 | if err != nil { 44 | panic(err.Error()) 45 | } 46 | fmt.Println("初始化数据和表成功:", aff) 47 | } 48 | -------------------------------------------------------------------------------- /logger_interface.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | import ( 4 | "io" 5 | "time" 6 | ) 7 | 8 | // ILogger ... 9 | type ILogger interface { 10 | Sql(sqlStr string, runtime time.Duration) 11 | Slow(sqlStr string, runtime time.Duration) 12 | Error(msg string) 13 | EnableSqlLog() bool 14 | EnableErrorLog() bool 15 | EnableSlowLog() float64 16 | } 17 | 18 | // 暂时规划 19 | type ilogger interface { 20 | // Persist 持久化 21 | Persist(w io.Writer) 22 | // Info 常规日志 23 | Info(args ...string) 24 | // Error 错误日志 25 | Error(args ...string) 26 | // Debug 调试日志 27 | Debug(args ...string) 28 | 29 | Infof(format string, args ...string) 30 | Errorf(format string, args ...string) 31 | Debugf(format string, args ...string) 32 | InfoWithCtx(ctx interface{}, args ...string) 33 | ErrorWithCtx(ctx interface{}, args ...string) 34 | DebugWithCtx(ctx interface{}, args ...string) 35 | InfofWithCtxf(ctx interface{}, format string, args ...string) 36 | ErrorfWithCtxf(ctx interface{}, format string, args ...string) 37 | DebugfWithCtxf(ctx interface{}, format string, args ...string) 38 | } 39 | -------------------------------------------------------------------------------- /gorose.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | // GOROSE_IMG ... 4 | const GOROSE_IMG = ` 5 | 6 | ██████╗ ██████╗ ██████╗ ██████╗ ███████╗███████╗ ██████╗ ██████╗ ██████╗ 7 | ██╔════╝ ██╔═══██╗██╔══██╗██╔═══██╗██╔════╝██╔════╝ ██╔══██╗██╔══██╗██╔═══██╗ 8 | ██║ ███╗██║ ██║██████╔╝██║ ██║███████╗█████╗█████╗██████╔╝██████╔╝██║ ██║ 9 | ██║ ██║██║ ██║██╔══██╗██║ ██║╚════██║██╔══╝╚════╝██╔═══╝ ██╔══██╗██║ ██║ 10 | ╚██████╔╝╚██████╔╝██║ ██║╚██████╔╝███████║███████╗ ██║ ██║ ██║╚██████╔╝ 11 | ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ 12 | 13 | ` 14 | 15 | const ( 16 | // VERSION_TEXT ... 17 | VERSION_TEXT = "\ngolang orm of gorose's version : " 18 | // VERSION_NO ... 19 | VERSION_NO = "v1.3.0" 20 | // VERSION ... 21 | VERSION = VERSION_TEXT + VERSION_NO + GOROSE_IMG 22 | ) 23 | 24 | // Open ... 25 | func Open(conf ...interface{}) (engin *Engin, err error) { 26 | // 驱动engin 27 | engin, err = NewEngin(conf...) 28 | if err != nil { 29 | if engin.GetLogger().EnableErrorLog() { 30 | engin.GetLogger().Error(err.Error()) 31 | } 32 | return 33 | } 34 | 35 | return 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Gorose Team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /engin_test.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | import ( 4 | "github.com/gohouse/t" 5 | "testing" 6 | ) 7 | 8 | type aaa t.MapStringT 9 | 10 | func (u *aaa) TableName() string { 11 | return "users" 12 | } 13 | 14 | //type bbb MapRows 15 | type bbb []t.MapStringT 16 | 17 | func (u *bbb) TableName() string { 18 | return "users" 19 | } 20 | 21 | type UsersMap Data 22 | 23 | func (*UsersMap) TableName() string { 24 | return "users" 25 | } 26 | 27 | // 定义map多返回绑定表名,一定要像下边这样,单独定义,否则无法获取对应的 TableName() 28 | type UsersMapSlice []Data 29 | 30 | func (u *UsersMapSlice) TableName() string { 31 | return "users" 32 | } 33 | 34 | type Users struct { 35 | Uid int64 `orm:"uid"` 36 | Name string `orm:"name"` 37 | Age int64 `orm:"age"` 38 | Fi string `orm:"ignore"` 39 | } 40 | 41 | func (Users) TableName() string { 42 | return "users" 43 | } 44 | 45 | type Orders struct { 46 | Id int `orm:"id"` 47 | GoodsName string `orm:"goodsname"` 48 | Price float64 `orm:"price"` 49 | } 50 | 51 | func TestEngin(t *testing.T) { 52 | e := initDB() 53 | e.SetPrefix("pre_") 54 | 55 | t.Log(e.GetPrefix()) 56 | 57 | db := e.GetQueryDB() 58 | 59 | err := db.Ping() 60 | 61 | if err != nil { 62 | t.Error("gorose初始化失败") 63 | } 64 | t.Log("gorose初始化成功") 65 | t.Log(e.GetLogger()) 66 | } 67 | -------------------------------------------------------------------------------- /orm_test.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func DB() IOrm { 8 | return initDB().NewOrm() 9 | } 10 | func TestNewOrm(t *testing.T) { 11 | orm := DB() 12 | orm.Close() 13 | } 14 | func TestOrm_AddFields(t *testing.T) { 15 | orm := DB() 16 | //var u = Users{} 17 | var fieldStmt = orm.Table("users").Fields("a").Where("m", 55) 18 | a, b, err := fieldStmt.AddFields("b").Where("d", 1).BuildSql() 19 | if err != nil { 20 | t.Error(err.Error()) 21 | } 22 | t.Log(a, b) 23 | 24 | fieldStmt.Reset() 25 | d, e, err := fieldStmt.Fields("a").AddFields("c").Where("d", 2).BuildSql() 26 | if err != nil { 27 | t.Error(err.Error()) 28 | } 29 | t.Log(d, e) 30 | } 31 | 32 | func TestOrm_BuildSql(t *testing.T) { 33 | var u = Users{ 34 | Name: "gorose2", 35 | Age: 19, 36 | } 37 | 38 | //aff, err := db.Force().Data(&u) 39 | a, b, err := DB().Table(&u).Where("age", ">", 1).Data(&u).BuildSql("update") 40 | if err != nil { 41 | t.Error(err.Error()) 42 | } 43 | t.Log(a, b) 44 | } 45 | 46 | func TestOrm_BuildSql_where(t *testing.T) { 47 | var u = Users{ 48 | Name: "gorose2", 49 | Age: 19, 50 | } 51 | 52 | var db = DB() 53 | a, b, err := db.Table(&u).Where("age", ">", 1).Where(func() { 54 | db.Where("name", "like", "%fizz%").OrWhere(func() { 55 | db.Where("age", ">", 10).Where("uid", ">", 2) 56 | }) 57 | }).Limit(2).Offset(2).BuildSql() 58 | if err != nil { 59 | t.Error(err.Error()) 60 | } 61 | t.Log(a, b) 62 | } 63 | -------------------------------------------------------------------------------- /doc/model.md: -------------------------------------------------------------------------------- 1 | ## 示例model 2 | 3 | ```go 4 | func Api_update_password(qq, password interface{}) bool { 5 | //初始化db并且填写table名称 6 | db := tuuz.Db().Table(table) 7 | //创建一个where,使用类型是map[string]interface,这个和TP的Array是一样的 8 | where := map[string]interface{}{ 9 | "qq": qq, 10 | } 11 | //导入where 12 | db.Where(where) 13 | //这里如果是查询就db.dFind()或者db.Get()结束掉了,这里是修改,所以继续 14 | //使用同样的方法创建同类型数据map 15 | data := map[string]interface{}{ 16 | "password": password, 17 | } 18 | //导入map,使用Data方法 19 | db.Data(data) 20 | //这里使用db.Update来执行修改,会输出数据库数字和错误 21 | _, err := db.Update() 22 | //判断错误 23 | if err != nil { 24 | //记录错误(如果你使用TuuzGoWeb就请记录,你也可使用自动记录,我更偏爱手动) 25 | Log.Dbrr(err, tuuz.FUNCTION_ALL()) 26 | return false 27 | } else { 28 | return true 29 | } 30 | } 31 | ``` 32 | 33 | 34 | 如上方法是一个修改方法,那么如果是输出单条或者多条怎么用呢? 35 | 36 | 37 | 单条 38 | ```go 39 | func Api_find(qq interface{}) gorose.Data { 40 | db := tuuz.Db().Table(table) 41 | where := map[string]interface{}{ 42 | "qq": qq, 43 | } 44 | db.Where(where) 45 | ret, err := db.First() 46 | if err != nil { 47 | Log.Dbrr(err, tuuz.FUNCTION_ALL()) 48 | return nil 49 | } else { 50 | return ret 51 | } 52 | } 53 | ``` 54 | 多条 55 | ```go 56 | func Api_select_canShow() []gorose.Data { 57 | db := tuuz.Db().Table(table) 58 | where := map[string]interface{}{ 59 | "can_show": 1, 60 | } 61 | db.Where(where) 62 | data, err := db.Get() 63 | if err != nil { 64 | Log.Dbrr(err, tuuz.FUNCTION_ALL()) 65 | return nil 66 | } else { 67 | return data 68 | } 69 | } 70 | ``` 71 | -------------------------------------------------------------------------------- /doc/find.md: -------------------------------------------------------------------------------- 1 | # Find/First查找单条数据的方法 2 | 3 | 基础方法已经在base里面展示过了,这里说个特殊的 4 | 5 | 如果大多数时候希望直接创建法,在做资金查询的时候使用注入法,要怎么做呢? 6 | 7 | 很简单,先把注入法写好,再写一个创建法,创建后注入Interface里面即可 8 | 9 | ```go 10 | type Interface struct { 11 | Db gorose.IOrm 12 | } 13 | 14 | func Api_find(group_id, user_id, dj_id interface{}) gorose.Data { 15 | var self Interface 16 | self.Db = tuuz.Db() 17 | return Api_find(group_id, user_id, dj_id) 18 | } 19 | 20 | func (self *Interface) Api_find(group_id, user_id, dj_id interface{}) gorose.Data { 21 | db := self.Db.Table(table) 22 | where := map[string]interface{}{ 23 | "group_id": group_id, 24 | "user_id": user_id, 25 | "dj_id": dj_id, 26 | } 27 | db.Where(where) 28 | data, err := db.First() 29 | if err != nil { 30 | Log.Dbrr(err, tuuz.FUNCTION_ALL()) 31 | return nil 32 | } else { 33 | return data 34 | } 35 | } 36 | ``` 37 | 38 | 39 | find方法十分的简单,没有难度,相信大家会很快上手 40 | 41 | find方法中没有太多骚操作,除此之外还有value拉sum拉之类的方法,大同小异 42 | 43 | ```go 44 | func Api_value_balance(group_id, user_id interface{}) interface{} { 45 | db := tuuz.Db().Table(table) 46 | where := map[string]interface{}{ 47 | "group_id": group_id, 48 | "user_id": user_id, 49 | } 50 | db.Where(where) 51 | ret, err := db.Value("balance") 52 | if err != nil { 53 | Log.Dbrr(err, tuuz.FUNCTION_ALL()) 54 | return nil 55 | } else { 56 | return ret 57 | } 58 | } 59 | ``` 60 | 61 | 62 | 63 | 0.[基础准备](./base.md) 64 | 65 | 1.[select方法](./select.md) 66 | 67 | 3.[update方法](./update.md) 68 | 69 | 4.[delete方法](./delete.md) 70 | 71 | 5.[insert方法](./insert.md) 72 | 73 | 6.[安全相关](./security.md) 74 | -------------------------------------------------------------------------------- /doc/special.md: -------------------------------------------------------------------------------- 1 | # GorosePro编程警告 2 | 3 | ## 正常使用中可能会出现的故障: 4 | - 编程时没有加入限定参数(在GorosePro中修复) 5 | - WhereIn函数,需要[]any{}是有参数的情况下,原版框架在这里没有判断,这有助于提高"DejaVu性能" 6 | - 导致原因: 7 | - 可能数据库采用了直写编程思想,这有助于提高代码自释性,但是外部传入可能会传空 8 | - 会导致:[]any{}被传入,语句输出将变成select...where id in () ,这将导致数据库错误 9 | - 分页错误: 10 | - 例如在大量数据查询时,第一页最后一条和第二页第一或者提二条重复的问题 11 | - 案例:如我需要做积分排行,我需要将一段时间内的新增学分累积起来并排行那么我正常将写一个带有join的读取方法 12 | - 这时如果排行仅使用积分排行,那么相同积分的情况下就会出现随机排行的情况,这就是为什么在跨页排行时会出现如此故障 13 | - 这个故障是MySQL的原理导致的,请各位开发者在编程时要特别注意 14 | ```go 15 | func Api_paginator_byStudyTypeAndCoinIdAndSchoolIdAndYearAndClassAndDate(coin_id any, extra_like string, school_id, year, class, start_date, end_date any, groupby string, limit, page int) gorose.Paginate { 16 | db := tuuz.Db().Table(table + " a") 17 | db.Fields("a.*", "b.school_id", "b.year", "b.class", "sum(amount) as sum_amount", "b.name", "c.wx_name", "c.wx_img") 18 | db.LeftJoin(StudentModel.Table+" b", "a.student_id=b.id") 19 | db.LeftJoin(UserModel.Table+" c", "a.uid=c.id") 20 | if coin_id != nil { 21 | db.Where("coin_id", coin_id) 22 | } 23 | if extra_like != "" { 24 | db.Where("extra", "like", extra_like+".%") 25 | } 26 | if school_id != nil { 27 | db.Where("school_id", school_id) 28 | } 29 | if year != nil { 30 | db.Where("year", year) 31 | } 32 | if class != nil { 33 | db.Where("class", class) 34 | } 35 | db.Where("a.date", ">=", start_date) 36 | db.Where("a.date", "<", end_date) 37 | db.GroupBy(groupby) 38 | //错误这里需要在sum_amount的基础上加入id排行避免相同积分出现混乱排序 39 | //db.OrderBy("sum_amount desc") 40 | db.OrderBy("sum_amount desc,id desc") 41 | db.Limit(limit) 42 | db.Page(page) 43 | ret, err := db.Paginator() 44 | if err != nil { 45 | Log.DBrrsql(err, db, tuuz.FUNCTION_ALL()) 46 | return gorose.Paginate{} 47 | } else { 48 | return ret 49 | } 50 | } 51 | ``` 52 | -------------------------------------------------------------------------------- /orm_api.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | // OrmApi ... 4 | type OrmApi struct { 5 | table string 6 | fields []string 7 | where [][]interface{} 8 | order string 9 | limit int 10 | offset int 11 | join [][]interface{} 12 | distinct bool 13 | //union string 14 | group string 15 | having string 16 | data interface{} 17 | force bool 18 | extraCols []string 19 | // 悲观锁 20 | pessimisticLock string 21 | } 22 | 23 | // GetTable ... 24 | func (o *Orm) GetTable() string { 25 | return o.table 26 | } 27 | 28 | // GetFields ... 29 | func (o *Orm) GetFields() []string { 30 | return o.fields 31 | } 32 | 33 | // SetWhere ... 34 | func (o *Orm) SetWhere(arg [][]interface{}) { 35 | o.where = arg 36 | } 37 | 38 | // GetWhere ... 39 | func (o *Orm) GetWhere() [][]interface{} { 40 | return o.where 41 | } 42 | 43 | // GetOrder ... 44 | func (o *Orm) GetOrder() string { 45 | return o.order 46 | } 47 | 48 | // GetLimit ... 49 | func (o *Orm) GetLimit() int { 50 | return o.limit 51 | } 52 | 53 | // GetOffset ... 54 | func (o *Orm) GetOffset() int { 55 | return o.offset 56 | } 57 | 58 | // GetJoin ... 59 | func (o *Orm) GetJoin() [][]interface{} { 60 | return o.join 61 | } 62 | 63 | // GetDistinct ... 64 | func (o *Orm) GetDistinct() bool { 65 | return o.distinct 66 | } 67 | 68 | // GetGroup ... 69 | func (o *Orm) GetGroup() string { 70 | return o.group 71 | } 72 | 73 | // GetHaving ... 74 | func (o *Orm) GetHaving() string { 75 | return o.having 76 | } 77 | 78 | // GetData ... 79 | func (o *Orm) GetData() interface{} { 80 | return o.data 81 | } 82 | 83 | // GetForce ... 84 | func (o *Orm) GetForce() bool { 85 | return o.force 86 | } 87 | 88 | // GetExtraCols ... 89 | func (o *Orm) GetExtraCols() []string { 90 | return o.extraCols 91 | } 92 | 93 | // GetPessimisticLock ... 94 | func (o *Orm) GetPessimisticLock() string { 95 | return o.pessimisticLock 96 | } 97 | -------------------------------------------------------------------------------- /doc/select.md: -------------------------------------------------------------------------------- 1 | # Select方法 2 | 3 | ```go 4 | func Api_select(group_id, user_id interface{}) []gorose.Data { 5 | db := tuuz.Db().Table(table) 6 | where := map[string]interface{}{ 7 | "group_id": group_id, 8 | "user_id": user_id, 9 | } 10 | db.Where(where) 11 | data, err := db.Get() 12 | if err != nil { 13 | Log.Dbrr(err, tuuz.FUNCTION_ALL()) 14 | return nil 15 | } else { 16 | return data 17 | } 18 | } 19 | ``` 20 | 21 | 22 | 如果你看完了上一个技术分享,那么select就很好理解了,select没啥复杂的,就是where然后get,没了 23 | 24 | 说个稍微复杂点的查询 25 | 26 | ```go 27 | func Api_select_have(group_id, user_id interface{}) []gorose.Data { 28 | db := tuuz.Db().Table(table) 29 | where := map[string]interface{}{ 30 | "group_id": group_id, 31 | "user_id": user_id, 32 | } 33 | db.Where(where) 34 | db.Where("num", ">", 0) 35 | data, err := db.Get() 36 | if err != nil { 37 | Log.Dbrr(err, tuuz.FUNCTION_ALL()) 38 | return nil 39 | } else { 40 | return data 41 | } 42 | } 43 | 44 | ``` 45 | 这里就是在where的基础上,再次where,你会发现不是所有的数据都是可以对应上的,很多时候需要做范围查询, 46 | 那么这个时候使用单一的where的map就无法正常的将代码写清楚了,那么这个时候,你就可以再次使用where的指定模式 47 | 来再次对范围内容进行限定,如上 48 | 49 | 好的,接下来来一个升级的 50 | 51 | ```go 52 | func (self *Interface) Api_join_select(group_id, user_id interface{}) []gorose.Data { 53 | db := self.Db.Table(table) 54 | where := map[string]interface{}{ 55 | "group_id": group_id, 56 | "user_id": user_id, 57 | } 58 | db.Where(where) 59 | db.Join("coin on coin.id=cid") 60 | db.Where("amount", ">", 0) 61 | ret, err := db.Get() 62 | if err != nil { 63 | Log.Dbrr(err, tuuz.FUNCTION_ALL()) 64 | return nil 65 | } else { 66 | return ret 67 | } 68 | } 69 | ``` 70 | 71 | 这里来一个join方法,join方法是select方法中的骚操作,在Thinkphp中,有model方法来帮助我们做一对多多对一 72 | 73 | 那么用Gorose,we are on our own!难度不大,大家参考下就行了 74 | 75 | 使用数据库ORM很多时候需要我们自己处理数据,不过这也让我们对数据优化可以有更多的认识和提升 76 | 77 | 78 | 0.[基础准备](./base.md) 79 | 80 | 2.[find/first方法](./find.md) 81 | 82 | 3.[update方法](./update.md) 83 | 84 | 4.[delete方法](./delete.md) 85 | 86 | 5.[insert方法](./insert.md) 87 | 88 | 6.[安全相关](./security.md) 89 | -------------------------------------------------------------------------------- /orm_query_interface.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | // IOrmQuery ... 4 | type IOrmQuery interface { 5 | // 获取数据, 依据传入的绑定对象, 选择查询一条或多条数据并绑定到传入对象上 6 | // 当绑定对象传入的是string类型时, 返回多条结果集, 需要使用 Get() 来获取最终结果 7 | Select() error 8 | //Scan 方法传入struct{}可以解析单条,类似Find方法,输入[]struct{}将会解析成多条,类似Get方法 9 | Scan(scan_to_struct interface{}) error 10 | // 获取一条结果并返回, 只有当传入的table对象是字符串时生效 11 | First() (Data, error) 12 | Find() (Data, error) 13 | // 获取多条结果并返回, 只有当传入的table对象是字符串时生效 14 | Get() ([]Data, error) 15 | // 如果你不需要完整的一行,可以使用 value 方法从结果中获取单个值,该方法会直接返回指定列的值: 16 | Value(field string) (v interface{}, err error) 17 | Column(field string) (v []interface{}, err error) 18 | // 如果想要获取包含单个列值的数组,可以使用 pluck 方法 19 | // 还可以在返回数组中为列值指定自定义键(该自定义键必须是该表的其它字段列名,否则会报错) 20 | Pluck(field string, fieldKey ...string) (v interface{}, err error) 21 | // 查询构建器还提供了多个聚合方法,如count, max, min, avg 和 sum,你可以在构造查询之后调用这些方法: 22 | Count(args ...string) (int64, error) 23 | Counts(args ...string) (int64, error) 24 | Sum(sum string) (interface{}, error) 25 | Avg(avg string) (interface{}, error) 26 | Max(max string) (interface{}, error) 27 | Min(min string) (interface{}, error) 28 | // 分页, 返回分页需要的基本数据 29 | Paginate(page ...int) (res Data, err error) 30 | Paginator(page ...int) (res Paginate, err error) 31 | 32 | //PaginatorWG采用协程架构,在RDS环境中测试有20-50%的性能提高(IO时间越长提高越多) 33 | PaginatorWG(page ...int) (res Paginate, err error) 34 | // 组块结果集 35 | // 如果你需要处理成千上万或者更多条数据库记录,可以考虑使用 chunk 方法,该方法一次获取结果集的一小块, 36 | // 然后传递每一小块数据到闭包函数进行处理,该方法在编写处理大量数据库记录的 Artisan 命令的时候非常有用。 37 | // 例如,我们可以将处理全部 users 表数据分割成一次处理 100 条记录的小组块 38 | // 你可以通过从闭包函数中返回 err 来终止组块的运行 39 | Chunk(limit int, callback func([]Data) error) (err error) 40 | 41 | // ChunkWG : ChunkWG是保留Chunk的使用方法的基础上,新增多线程读取&多线程执行的方式,注意onetime_exec_thread不宜过多,推荐4,不宜过大因为采用的是盲读的方法,详情请参考github-wiki的介绍部分 42 | ChunkWG(onetime_exec_thread int, limit int, callback func([]Data) error) (err error) 43 | // 跟Chunk类似,只不过callback的是传入的结构体 44 | ChunkStruct(limit int, callback func() error) (err error) 45 | Loop(limit int, callback func([]Data) error) (err error) 46 | } 47 | -------------------------------------------------------------------------------- /err.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | // Error ... 9 | type Error uint 10 | 11 | // Lang ... 12 | type Lang uint 13 | 14 | const ( 15 | // CHINESE ... 16 | CHINESE Lang = iota 17 | // ENGLISH ... 18 | ENGLISH 19 | // CHINESE_TRADITIONAL ... 20 | CHINESE_TRADITIONAL 21 | ) 22 | 23 | const ( 24 | // ERR_PARAMS_COUNTS ... 25 | ERR_PARAMS_COUNTS Error = iota 26 | // ERR_PARAMS_MISSING ... 27 | ERR_PARAMS_MISSING 28 | // ERR_PARAMS_FORMAT ... 29 | ERR_PARAMS_FORMAT 30 | ) 31 | 32 | // Default ... 33 | func (e *Err) Default() map[Error]string { 34 | return map[Error]string{ 35 | ERR_PARAMS_COUNTS: "参数数量有误", 36 | ERR_PARAMS_MISSING: "参数缺失", 37 | ERR_PARAMS_FORMAT: "参数格式错误", 38 | } 39 | } 40 | 41 | var langString = map[Lang]string{ 42 | CHINESE: "chinese", 43 | ENGLISH: "english", 44 | CHINESE_TRADITIONAL: "chinese_traditional", 45 | } 46 | 47 | // String ... 48 | func (l Lang) String() string { 49 | return langString[l] 50 | } 51 | 52 | // Err ... 53 | type Err struct { 54 | lang Lang 55 | err map[Lang]map[Error]string 56 | } 57 | 58 | //var gOnce *sync.Once 59 | var gErr *Err 60 | 61 | func init() { 62 | var tmpLang = make(map[Lang]map[Error]string) 63 | gErr = &Err{err: tmpLang} 64 | gErr.lang = CHINESE 65 | gErr.Register(gErr.Default()) 66 | } 67 | 68 | // NewErr ... 69 | func NewErr() *Err { 70 | return gErr 71 | } 72 | 73 | // SetLang ... 74 | func (e *Err) SetLang(l Lang) { 75 | e.lang = l 76 | } 77 | 78 | // GetLang ... 79 | func (e *Err) GetLang() Lang { 80 | return e.lang 81 | } 82 | 83 | // Register ... 84 | func (e *Err) Register(err map[Error]string) { 85 | e.err[e.GetLang()] = err 86 | } 87 | 88 | // Get ... 89 | func (e *Err) Get(err Error) string { 90 | return e.err[e.GetLang()][err] 91 | } 92 | 93 | // GetErr ... 94 | func GetErr(err Error, args ...interface{}) error { 95 | var argreal string 96 | if len(args) > 0 { 97 | argreal = fmt.Sprint(":", args) 98 | } 99 | return errors.New(fmt.Sprint( 100 | NewErr(). 101 | Get(err), 102 | argreal)) 103 | } 104 | -------------------------------------------------------------------------------- /doc/base.md: -------------------------------------------------------------------------------- 1 | # 基础方法 2 | 3 | 4 | 以下两种方法将会贯穿我们的教程始终,请务必区分,根据实际情况来区分 5 | 6 | Tuuz.Db()方法是我的框架中创建数据库的方法,你可以根据你自己的情况来,后续不再赘述 7 | 8 | ~~~ 9 | 看教程中,请仔细看我的文字!!! 10 | 看教程中,请仔细看我的文字!!! 11 | 看教程中,请仔细看我的文字!!! 12 | 看教程中,请仔细看我的文字!!! 13 | 看教程中,请仔细看我的文字!!! 14 | 看教程中,请仔细看我的文字!!! 15 | 16 | ~~~ 17 | 18 | 注意观察DB的生成! 19 | 20 | 每次查询都创建新的数据库对象 21 | 22 | ```go 23 | func Api_find(group_id, user_id, cid interface{}) gorose.Data { 24 | //Tuuz.Db()方法是我的框架中创建数据库的方法,你可以根据你自己的情况来,后续不再赘述 25 | db := Tuuz.Db().Table(table) 26 | where := map[string]interface{}{ 27 | "group_id": group_id, 28 | "user_id": user_id, 29 | "cid": cid, 30 | } 31 | db.Where(where) 32 | ret, err := db.First() 33 | if err != nil { 34 | Log.Dbrr(err, tuuz.FUNCTION_ALL()) 35 | return nil 36 | } else { 37 | return ret 38 | } 39 | } 40 | ``` 41 | 42 | 每次查询使用外部注入的数据库对象,请注意这里的type 43 | 44 | ```go 45 | type Interface struct { 46 | Db gorose.IOrm 47 | } 48 | 49 | 50 | func (self *Interface) Api_find(group_id, user_id, cid interface{}) gorose.Data { 51 | db := self.Db.Table(table) 52 | where := map[string]interface{}{ 53 | "group_id": group_id, 54 | "user_id": user_id, 55 | "cid": cid, 56 | } 57 | db.Where(where) 58 | ret, err := db.First() 59 | if err != nil { 60 | Log.Dbrr(err, tuuz.FUNCTION_ALL()) 61 | return nil 62 | } else { 63 | return ret 64 | } 65 | } 66 | ``` 67 | 这两种方法需要在限定场景和非限定场景中选择 68 | 69 | 请大家务必根据需要来进行选择,这里推荐使用新建数据的方法,能达到最高效率 70 | 71 | 这里选择使用注入方法和新生成方法的原因是如果在Transaction或者Nested Transaction方法下,事务因为是隔离的 72 | 所以不在一个数据流(对象)下的查询是无法查询到隔离内的事务修改的,举个栗子 73 | 74 | 数据库中有一个字段A,里面只有一条数据,数据为123 75 | 76 | 例如在完全无事务的条件下流程是这样的: 77 | 78 | A->find[输出123]->update(A,456)->find[输出456] 79 | 80 | 在find使用创建法,update导入法 81 | 82 | A->开始事务->(不在事务中find[输出123])->update(A,456)->(不在事务中find[输出123])->提交事务->(不在事务中find[输出456]) 83 | 84 | find导入法,update导入法 85 | 86 | A->开始事务->find[输出123]->update(A,456)->find[输出456]->提交事务->find[输出456] 87 | 88 | 89 | 明白了吧?如果你做的是交易所或者资金类的程序,这个概念你必须要搞懂! 90 | 91 | 92 | 1.[select方法](./select.md) 93 | 94 | 2.[find/first方法](./find.md) 95 | 96 | 3.[update方法](./update.md) 97 | 98 | 4.[delete方法](./delete.md) 99 | 100 | 5.[insert方法](./insert.md) 101 | 102 | 6.[安全相关](./security.md) 103 | -------------------------------------------------------------------------------- /orm_execute_test.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestOrm_Update(t *testing.T) { 9 | db := DB() 10 | 11 | var u = []Users{{ 12 | Name: "gorose2", 13 | Age: 19, 14 | }} 15 | 16 | aff, err := db.Force().Update(&u) 17 | if err != nil { 18 | t.Error(err.Error()) 19 | } 20 | t.Log(aff, db.LastSql()) 21 | } 22 | 23 | func TestOrm_Update2(t *testing.T) { 24 | db := DB() 25 | 26 | //var u = []Users{{ 27 | // Name: "gorose2", 28 | // Age: 11, 29 | //}} 30 | 31 | aff, err := db.Table("users").Where("uid", 1).Update() 32 | if err != nil { 33 | //t.Error(err.Error()) 34 | t.Log(err.Error()) 35 | return 36 | } 37 | t.Log(aff, db.LastSql()) 38 | } 39 | 40 | func TestOrm_UpdateMap(t *testing.T) { 41 | db := DB() 42 | 43 | //var u = []UsersMap{{"name": "gorose2", "age": 19}} 44 | var u = UsersMap{"name": "gorose2", "age": 19} 45 | 46 | aff, err := db.Force().Update(&u) 47 | if err != nil { 48 | t.Error(err.Error()) 49 | } 50 | t.Log(aff, db.LastSql()) 51 | } 52 | 53 | func TestTrans(t *testing.T) { 54 | var db = DB() 55 | var db2 = DB() 56 | var res Users 57 | db.Begin() 58 | db2.Table(&res).Select() 59 | t.Log(res) 60 | db.Commit() 61 | t.Log(res) 62 | } 63 | 64 | func Test_Transaction(t *testing.T) { 65 | var db = DB() 66 | // 一键事务, 自动回滚和提交, 我们只需要关注业务即可 67 | err := db.Transaction( 68 | func(db IOrm) error { 69 | //db.Table("users").Limit(2).SharedLock().Get() 70 | //fmt.Println(db.LastSql()) 71 | _, err := db.Table("users").Where("uid", 2).Update(Data{"name": "gorose2"}) 72 | fmt.Println(db.LastSql()) 73 | if err != nil { 74 | return err 75 | } 76 | _, err = db.Insert(&UsersMap{"name": "gorose2", "age": 0}) 77 | fmt.Println(db.LastSql()) 78 | if err != nil { 79 | return err 80 | } 81 | return nil 82 | }, 83 | func(db IOrm) error { 84 | _, err := db.Table("users").Where("uid", 3).Update(Data{"name": "gorose3"}) 85 | fmt.Println(db.LastSql()) 86 | if err != nil { 87 | return err 88 | } 89 | _, err = db.Insert(&UsersMap{"name": "gorose2", "age": 0}) 90 | fmt.Println(db.LastSql()) 91 | if err != nil { 92 | return err 93 | } 94 | return nil 95 | }, 96 | ) 97 | if err != nil { 98 | t.Error(err.Error()) 99 | } 100 | t.Log("事务测试通过") 101 | } 102 | -------------------------------------------------------------------------------- /orm_interface.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | // IOrm ... 4 | type IOrm interface { 5 | IOrmApi 6 | IOrmQuery 7 | IOrmExecute 8 | IOrmSession 9 | //ISession 10 | Close() 11 | BuildSql(operType ...string) (string, []interface{}, error) 12 | Table(tab interface{}) IOrm 13 | SubQuery(sql, alias string, args []interface{}) IOrm 14 | SubWhere(field, condition, sql string, args []interface{}) IOrm 15 | // fields=select 16 | Fields(fields ...string) IOrm 17 | AddFields(fields ...string) IOrm 18 | // distinct 方法允许你强制查询返回不重复的结果集: 19 | Distinct() IOrm 20 | Data(data interface{}) IOrm 21 | // groupBy, orderBy, having 22 | Group(group string) IOrm 23 | GroupBy(group string) IOrm 24 | Having(having string) IOrm 25 | Order(order string) IOrm 26 | OrderBy(order string) IOrm 27 | Limit(limit int) IOrm 28 | Offset(offset int) IOrm 29 | Page(page int) IOrm 30 | // join(=innerJoin),leftJoin,rightJoin,crossJoin 31 | Join(args ...interface{}) IOrm 32 | LeftJoin(args ...interface{}) IOrm 33 | RightJoin(args ...interface{}) IOrm 34 | CrossJoin(args ...interface{}) IOrm 35 | // `Where`,`OrWhere`,`WhereNull / WhereNotNull`,`WhereIn / WhereNotIn / OrWhereIn / OrWhereNotIn`,`WhereBetween / WhereBetwee / OrWhereBetween / OrWhereNotBetween` 36 | Where(args ...interface{}) IOrm 37 | OrWhere(args ...interface{}) IOrm 38 | WhereOr(args ...interface{}) IOrm 39 | WhereOrAnd(args ...interface{}) IOrm 40 | WhereNull(arg string) IOrm 41 | OrWhereNull(arg string) IOrm 42 | WhereNotNull(arg string) IOrm 43 | OrWhereNotNull(arg string) IOrm 44 | WhereRegexp(arg string, expstr string) IOrm 45 | OrWhereRegexp(arg string, expstr string) IOrm 46 | WhereNotRegexp(arg string, expstr string) IOrm 47 | OrWhereNotRegexp(arg string, expstr string) IOrm 48 | WhereIn(needle string, hystack []interface{}) IOrm 49 | OrWhereIn(needle string, hystack []interface{}) IOrm 50 | WhereNotIn(needle string, hystack []interface{}) IOrm 51 | OrWhereNotIn(needle string, hystack []interface{}) IOrm 52 | WhereBetween(needle string, hystack []interface{}) IOrm 53 | OrWhereBetween(needle string, hystack []interface{}) IOrm 54 | WhereNotBetween(needle string, hystack []interface{}) IOrm 55 | OrWhereNotBetween(needle string, hystack []interface{}) IOrm 56 | // truncate 57 | Truncate() error 58 | GetDriver() string 59 | //GetIBinder() IBinder 60 | SetBindValues(v interface{}) 61 | GetBindValues() []interface{} 62 | IsSubQuery() bool 63 | ClearBindValues() 64 | Transaction(closers ...func(db IOrm) error) (err error) 65 | Reset() IOrm 66 | ResetTable() IOrm 67 | ResetWhere() IOrm 68 | GetISession() ISession 69 | GetOrmApi() *OrmApi 70 | // 悲观锁使用 71 | // sharedLock(lock in share mode) 不会阻塞其它事务读取被锁定行记录的值 72 | SharedLock() *Orm 73 | // 此外你还可以使用 lockForUpdate 方法。“for update”锁避免选择行被其它共享锁修改或删除: 74 | // 会阻塞其他锁定性读对锁定行的读取(非锁定性读仍然可以读取这些记录,lock in share mode 和 for update 都是锁定性读) 75 | LockForUpdate() *Orm 76 | //ResetUnion() IOrm 77 | } 78 | -------------------------------------------------------------------------------- /logger.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | // LogLevel 日志级别 9 | type LogLevel uint 10 | 11 | const ( 12 | // LOG_SQL ... 13 | LOG_SQL LogLevel = iota 14 | // LOG_SLOW ... 15 | LOG_SLOW 16 | // LOG_ERROR ... 17 | LOG_ERROR 18 | ) 19 | 20 | // String ... 21 | func (l LogLevel) String() string { 22 | switch l { 23 | case LOG_SQL: 24 | return "SQL" 25 | case LOG_SLOW: 26 | return "SLOW" 27 | case LOG_ERROR: 28 | return "ERROR" 29 | } 30 | return "" 31 | } 32 | 33 | // LogOption ... 34 | type LogOption struct { 35 | FilePath string 36 | EnableSqlLog bool 37 | // 是否记录慢查询, 默认0s, 不记录, 设置记录的时间阀值, 比如 1, 则表示超过1s的都记录 38 | EnableSlowLog float64 39 | EnableErrorLog bool 40 | } 41 | 42 | // Logger ... 43 | type Logger struct { 44 | filePath string 45 | sqlLog bool 46 | slowLog float64 47 | //infoLog bool 48 | errLog bool 49 | } 50 | 51 | var _ ILogger = (*Logger)(nil) 52 | 53 | //var onceLogger sync.Once 54 | var logger *Logger 55 | 56 | // NewLogger ... 57 | func NewLogger(o *LogOption) *Logger { 58 | //onceLogger.Do(func() { 59 | logger = &Logger{filePath: "./"} 60 | if o.FilePath != "" { 61 | logger.filePath = o.FilePath 62 | } 63 | logger.sqlLog = o.EnableSqlLog 64 | logger.slowLog = o.EnableSlowLog 65 | logger.errLog = o.EnableErrorLog 66 | //}) 67 | return logger 68 | } 69 | 70 | // DefaultLogger ... 71 | func DefaultLogger() func(e *Engin) { 72 | return func(e *Engin) { 73 | e.logger = NewLogger(&LogOption{}) 74 | } 75 | } 76 | 77 | // EnableSqlLog ... 78 | func (l *Logger) EnableSqlLog() bool { 79 | return l.sqlLog 80 | } 81 | 82 | // EnableErrorLog ... 83 | func (l *Logger) EnableErrorLog() bool { 84 | return l.errLog 85 | } 86 | 87 | // EnableSlowLog ... 88 | func (l *Logger) EnableSlowLog() float64 { 89 | return l.slowLog 90 | } 91 | 92 | // Slow ... 93 | func (l *Logger) Slow(sqlStr string, runtime time.Duration) { 94 | if l.EnableSlowLog() > 0 && runtime.Seconds() > l.EnableSlowLog() { 95 | logger.write(LOG_SLOW, "gorose_slow", sqlStr, runtime.String()) 96 | } 97 | } 98 | 99 | // Sql ... 100 | func (l *Logger) Sql(sqlStr string, runtime time.Duration) { 101 | if l.EnableSqlLog() { 102 | logger.write(LOG_SQL, "gorose_sql", sqlStr, runtime.String()) 103 | } 104 | } 105 | 106 | // Error ... 107 | func (l *Logger) Error(msg string) { 108 | if l.EnableErrorLog() { 109 | logger.write(LOG_ERROR, "gorose", msg, "0") 110 | } 111 | } 112 | 113 | func (l *Logger) write(ll LogLevel, filename string, msg string, runtime string) { 114 | now := time.Now() 115 | date := now.Format("20060102") 116 | datetime := now.Format("2006-01-02 15:04:05") 117 | f := readFile(fmt.Sprintf("%s/%v_%v.log", l.filePath, date, filename)) 118 | content := fmt.Sprintf("[%v] [%v] %v --- %v\n", ll.String(), datetime, runtime, msg) 119 | withLockContext(func() { 120 | defer f.Close() 121 | buf := []byte(content) 122 | f.Write(buf) 123 | }) 124 | } 125 | -------------------------------------------------------------------------------- /doc/update.md: -------------------------------------------------------------------------------- 1 | # Update方法 2 | 3 | 4 | 举两个例子,自己看,实在是很简单,通过XXX来查询XXX 5 | 6 | 这两个栗子,都是不需要事务处理的,因为应用场景中,只要if update成功后就可以直接显示成功了 7 | 8 | 因为场景非常简单,所以这里不需要使用“注入法”来使用数据库,直接使用创建法,便捷也快速 9 | 10 | 11 | ```go 12 | 13 | func Api_update_uname(qq, uname interface{}) bool { 14 | db := tuuz.Db().Table(table) 15 | where := map[string]interface{}{ 16 | "qq": qq, 17 | } 18 | db.Where(where) 19 | data := map[string]interface{}{ 20 | "uname": uname, 21 | } 22 | db.Data(data) 23 | _, err := db.Update() 24 | if err != nil { 25 | Log.Dbrr(err, tuuz.FUNCTION_ALL()) 26 | return false 27 | } else { 28 | return true 29 | } 30 | } 31 | ``` 32 | 33 | ```go 34 | 35 | func Api_update_all(qq, uname, password interface{}) bool { 36 | db := tuuz.Db().Table(table) 37 | where := map[string]interface{}{ 38 | "qq": qq, 39 | } 40 | db.Where(where) 41 | data := map[string]interface{}{ 42 | "uname": uname, 43 | "password": password, 44 | } 45 | db.Data(data) 46 | _, err := db.Update() 47 | if err != nil { 48 | Log.Dbrr(err, tuuz.FUNCTION_ALL()) 49 | return false 50 | } else { 51 | return true 52 | } 53 | } 54 | 55 | ``` 56 | 57 | 接下来的这个栗子,是资金修改的,所以需要使用导入法 58 | 59 | (self *Interface)这个没有特殊的意思,名字都可以自己取 60 | 61 | 请注意,一旦涉及到transaction必须使用导入法 62 | 63 | 如果涉及到嵌套事务,一定要使用导入法,否则事务和事务隔离均不能生效 64 | 65 | ```go 66 | //Interface你自己取个名字 67 | type Interface struct { 68 | //这里面的Db你也可以自己取名字 69 | Db gorose.IOrm 70 | } 71 | ``` 72 | 73 | ```go 74 | //这里的self你也可以自己取名字 75 | func (self *Interface) Api_update(group_id, user_id, balance interface{}) bool { 76 | db := self.Db.Table(table) 77 | where := map[string]interface{}{ 78 | "group_id": group_id, 79 | "user_id": user_id, 80 | } 81 | db.Where(where) 82 | data := map[string]interface{}{ 83 | "balance": balance, 84 | } 85 | db.Data(data) 86 | _, err := db.Update() 87 | if err != nil { 88 | Log.Dbrr(err, tuuz.FUNCTION_ALL()) 89 | return false 90 | } else { 91 | return true 92 | } 93 | } 94 | ``` 95 | 96 | 所以非常的简单的!如果你是PHP程序员相信你秒懂 97 | 98 | 99 | 这里说个特殊的Increasement方法,这个方法就很单纯,通过某个字段来递增,然后增量你可以自己定或者使用传入值 100 | 101 | 非常简单,这些值ORM都已经做成可接受interface数据了,所以只要你不作死去导入特殊值例如string之类的, 102 | 一般都没问题(就算传入特殊值ORM也会处理) 103 | 104 | ```go 105 | 106 | func (self *Interface) Api_incr(group_id, user_id, cid, amount interface{}) bool { 107 | db := self.Db.Table(table) 108 | where := map[string]interface{}{ 109 | "group_id": group_id, 110 | "user_id": user_id, 111 | "cid": cid, 112 | } 113 | db.Where(where) 114 | _, err := db.Increment("amount", amount) 115 | if err != nil { 116 | Log.Dbrr(err, tuuz.FUNCTION_ALL()) 117 | return false 118 | } else { 119 | return true 120 | } 121 | } 122 | 123 | ``` 124 | 递减也很简单,你也可以使用负值传入递增实现递减的效果,记得ABS后在负数哦~不然给人家撸口小可爱干了你的系统 125 | 126 | 你别怪我框架的问题啊! 127 | 128 | ```go 129 | 130 | func (self *Interface) Api_decr(group_id, user_id, dj_id interface{}) bool { 131 | db := self.Db.Table(table) 132 | where := map[string]interface{}{ 133 | "group_id": group_id, 134 | "user_id": user_id, 135 | "dj_id": dj_id, 136 | } 137 | db.Where(where) 138 | _, err := db.Decrement("num", 1) 139 | if err != nil { 140 | Log.Dbrr(err, tuuz.FUNCTION_ALL()) 141 | return false 142 | } else { 143 | return true 144 | } 145 | } 146 | ``` 147 | 148 | 149 | 0.[基础准备](./base.md) 150 | 151 | 1.[select方法](./select.md) 152 | 153 | 2.[find/first方法](./find.md) 154 | 155 | 4.[delete方法](./delete.md) 156 | 157 | 5.[insert方法](./insert.md) 158 | 159 | 6.[安全相关](./security.md) 160 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gohouse/t" 6 | "log" 7 | "math/rand" 8 | "os" 9 | "path" 10 | "reflect" 11 | "strings" 12 | "sync" 13 | "time" 14 | ) 15 | 16 | func getRandomInt(num int) int { 17 | rand.Seed(time.Now().UnixNano()) 18 | return rand.Intn(num) 19 | } 20 | 21 | func structForScan(u interface{}) []interface{} { 22 | val := reflect.Indirect(reflect.ValueOf(u)) 23 | v := make([]interface{}, 0) 24 | for i := 0; i < val.NumField(); i++ { 25 | valueField := val.Field(i) 26 | if val.Type().Field(i).Tag.Get(TAGNAME) != IGNORE { 27 | if valueField.CanAddr() { 28 | v = append(v, valueField.Addr().Interface()) 29 | } else { 30 | //v[i] = valueField 31 | v = append(v, valueField) 32 | } 33 | } 34 | } 35 | return v 36 | } 37 | 38 | // StructToMap ... 39 | func StructToMap(obj interface{}) map[string]interface{} { 40 | ty := reflect.TypeOf(obj) 41 | v := reflect.ValueOf(obj) 42 | 43 | var data = make(map[string]interface{}) 44 | for i := 0; i < ty.NumField(); i++ { 45 | data[ty.Field(i).Name] = v.Field(i).Interface() 46 | } 47 | return data 48 | } 49 | 50 | // getTagName 获取结构体中Tag的值,如果没有tag则返回字段值 51 | func getTagName(structName interface{}, tagstr string) []string { 52 | // 获取type 53 | tag := reflect.TypeOf(structName) 54 | // 如果是反射Ptr类型, 就获取他的 element type 55 | if tag.Kind() == reflect.Ptr { 56 | tag = tag.Elem() 57 | } 58 | 59 | // 判断是否是struct 60 | if tag.Kind() != reflect.Struct { 61 | log.Println("Check type error not Struct") 62 | return nil 63 | } 64 | // 获取字段数量 65 | fieldNum := tag.NumField() 66 | result := make([]string, 0, fieldNum) 67 | for i := 0; i < fieldNum; i++ { 68 | // tag 名字 69 | tagName := tag.Field(i).Tag.Get(tagstr) 70 | if tagName != IGNORE { 71 | // tag为-时, 不解析 72 | if tagName == "-" || tagName == "" { 73 | // 字段名字 74 | tagName = tag.Field(i).Name 75 | } 76 | result = append(result, tagName) 77 | } 78 | } 79 | return result 80 | } 81 | 82 | // If : ternary operator (三元运算) 83 | // condition:比较运算 84 | // trueVal:运算结果为真时的值 85 | // falseVal:运算结果为假时的值 86 | // return: 由于不知道传入值的类型, 所有, 必须在接收结果时, 指定对应的值类型 87 | func If(condition bool, trueVal, falseVal interface{}) interface{} { 88 | if condition { 89 | return trueVal 90 | } 91 | return falseVal 92 | } 93 | 94 | func addQuotes(data interface{}, sep string) string { 95 | ret := t.New(data).String() 96 | ret = strings.Replace(ret, `\`, `\\`, -1) 97 | ret = strings.Replace(ret, `"`, `\"`, -1) 98 | ret = strings.Replace(ret, `'`, `\'`, -1) 99 | return fmt.Sprintf("%s%s%s", sep, ret, sep) 100 | } 101 | 102 | // InArray :给定元素值 是否在 指定的数组中 103 | func inArray(needle, hystack interface{}) bool { 104 | nt := t.New(needle) 105 | for _, item := range t.New(hystack).Slice() { 106 | if strings.ToLower(nt.String()) == strings.ToLower(item.String()) { 107 | return true 108 | } 109 | } 110 | return false 111 | } 112 | 113 | func withLockContext(fn func()) { 114 | var mu sync.Mutex 115 | mu.Lock() 116 | defer mu.Unlock() 117 | fn() 118 | } 119 | 120 | func withRunTimeContext(closer func(), callback func(time.Duration)) { 121 | // 记录开始时间 122 | start := time.Now() 123 | closer() 124 | timeduration := time.Since(start) 125 | //log.Println("执行完毕,用时:", timeduration.Seconds(),timeduration.Seconds()>1.1) 126 | callback(timeduration) 127 | } 128 | 129 | func readFile(filepath string) *os.File { 130 | file, err := os.OpenFile(filepath, os.O_WRONLY|os.O_APPEND, 0666) 131 | if err != nil && os.IsNotExist(err) { 132 | _ = os.MkdirAll(path.Dir(filepath), os.ModePerm) 133 | file, _ = os.Create(filepath) 134 | } 135 | return file 136 | } 137 | -------------------------------------------------------------------------------- /doc/intro.md: -------------------------------------------------------------------------------- 1 | # 先说思想 2 | 3 | 1.为什么开头都叫Api?叫别的行不行? 4 | 5 | 受到Golang的限制,首字母大写挎包能调用到,那么前面几个关键字就很重要了, 6 | 所以在设计数据库层的时候,这里使用的是单例模式,每个对数据的调用都是API的形式, 7 | 所以开头叫Api,你也可以改成别的 8 | 9 | 2.为什么又蛇形又大小写? 10 | 11 | 可恶心到很多人,其实蛇形的目的是为了把动作分开, 12 | 13 | 例如通过“username字段找1个用户”,Java或者PHP开发喜欢写GetUser,Py开发喜欢get_user这也没问题, 14 | 很多朋友是大神喜欢直接写目的,不过后续2开让人接手就很吐了,或者过1年看自己代码就难受了,我一开始开发也是这样的, 15 | 后来才开始使用“过程”代替“目的”的命名方式 16 | 17 | 18 | 给个示例: 19 | 20 | 例如: 21 | ~~~ 22 | Api_find_byQqandPassword() 23 | ~~~ 24 | 25 | ~~~ 26 | 1.这种命名方式请问他是数据库方法还是一个逻辑方法 27 | 28 | 2.他是增删改查中的哪一种? 29 | 30 | 3.他返回的是单条数据还是多条数据,我是使用map[string]interface{}来处理还是[]map[string]interface{}来处理? 31 | 32 | 4.这个方法主要需要哪两种数据类型才能完成查询? 33 | ~~~ 34 | 35 | 好的如上问题请在看下面的代码前回答 36 | 37 | 好的如上问题请在看下面的代码前回答 38 | 39 | 好的如上问题请在看下面的代码前回答 40 | 41 | 好的如上问题请在看下面的代码前回答 42 | 43 | 好的如上问题请在看下面的代码前回答 44 | 45 | 好的如上问题请在看下面的代码前回答 46 | 47 | 好的如上问题请在看下面的代码前回答 48 | 49 | 好的如上问题请在看下面的代码前回答 50 | 51 | 好的如上问题请在看下面的代码前回答 52 | 53 | 好的如上问题请在看下面的代码前回答 54 | 55 | 好的如上问题请在看下面的代码前回答 56 | 57 | 好的如上问题请在看下面的代码前回答 58 | 59 | 60 | 61 | · 62 | 63 | · 64 | 65 | · 66 | 67 | · 68 | 69 | 70 | ```go 71 | func Api_find_byQqandPassword(qq, password interface{}) gorose.Data { 72 | db := tuuz.Db().Table(table) 73 | where := map[string]interface{}{ 74 | "qq": qq, 75 | "password": password, 76 | } 77 | db.Where(where) 78 | ret, err := db.First() 79 | if err != nil { 80 | Log.Dbrr(err, tuuz.FUNCTION_ALL()) 81 | return nil 82 | } else { 83 | return ret 84 | } 85 | } 86 | ``` 87 | 88 | 好的,你在对照下Model代码,你看过程式命名方法是不是给后面接手你代码的人带来了很大的便利 89 | 90 | 写代码就像玩魂斗罗,要么一人跳太快给另一人给拖死,要么后面的太慢了把前面的拖死,如果你还没有决定项目规范,GorosePro的这个方案可以作为你的备选呢! 91 | 92 | 93 | 那么如果是展示呢?一般在读取数据的时候,find和select后面都应该跟着重点字段,例如这个数据输出,他是什么类型的 94 | 例如这里我需要把可展示的内容列出,那么这里的命名就变成了canShow,因为字段的问题,所以前小后大,请大家注意 95 | 96 | 97 | ```go 98 | func Api_select_canShow() []gorose.Data { 99 | db := tuuz.Db().Table(table) 100 | where := map[string]interface{}{ 101 | "can_show": 1, 102 | } 103 | db.Where(where) 104 | data, err := db.Get() 105 | if err != nil { 106 | Log.Dbrr(err, tuuz.FUNCTION_ALL()) 107 | return nil 108 | } else { 109 | return data 110 | } 111 | } 112 | ``` 113 | 114 | 115 | 如果这些案例你能看懂那么接下来这个案例就比较特别,需要使用群id,用户id,道具id来查询对应那条数据的num字段 116 | 117 | 命名是Api_value_num,你会发现在这里没有by了,因为数据太复杂了,请注意,虽然过程命名法可以让后来的人更方便的读懂你的项目, 118 | 知道你每个方法期待调用什么类型的数据,但是你可别把所有的查询限定项都写到方法名称中去,这么做虽然方便别人了,但是容易挨揍! 119 | 120 | 所以各位用户请掌握好度 121 | 122 | 123 | ```go 124 | func (self *Interface) Api_value_num(group_id, user_id, dj_id interface{}) int64 { 125 | db := self.Db.Table(table) 126 | where := map[string]interface{}{ 127 | "group_id": group_id, 128 | "user_id": user_id, 129 | "dj_id": dj_id, 130 | } 131 | db.Where(where) 132 | data, err := db.Value("num") 133 | if err != nil { 134 | Log.Dbrr(err, tuuz.FUNCTION_ALL()) 135 | return 0 136 | } else { 137 | if data == nil { 138 | return 0 139 | } else { 140 | return data.(int64) 141 | } 142 | } 143 | } 144 | ``` 145 | 同理还有 146 | 147 | ```go 148 | func (self *Interface) Api_sum_byCid(cid interface{}) float64 { 149 | db := self.Db.Table(table) 150 | where := map[string]interface{}{ 151 | "cid": cid, 152 | } 153 | db.Where(where) 154 | ret, err := db.Sum("amount") 155 | if err != nil { 156 | Log.Dbrr(err, tuuz.FUNCTION_ALL()) 157 | return 0 158 | } else { 159 | if ret != nil { 160 | return ret.(float64) 161 | } else { 162 | return 0 163 | } 164 | } 165 | } 166 | ``` 167 | 168 | Tuuz.Db()方法是我的框架中创建数据库的方法,你可以根据你自己的情况来,后续不再赘述 169 | 170 | 171 | 以上是命名方法的解说 172 | 173 | 那么我们就要具体案例分析了: 174 | 175 | 0.[基础准备](./base.md) 176 | 177 | 1.[select方法](./select.md) 178 | 179 | 2.[find/first方法](./find.md) 180 | 181 | 3.[update方法](./update.md) 182 | 183 | 4.[delete方法](./delete.md) 184 | 185 | 5.[insert方法](./insert.md) 186 | 187 | 6.[安全相关](./security.md) 188 | 189 | ~~~ 190 | 好的如果你能平心静气的看到这里你就会发现 191 | 192 | 下划线只是为了分割动作!!!命名方式依旧遵循了标准驼峰,蛇形只是表象而已 193 | 194 | 如果你心平气和的读到这里,那么恭喜你你是一个接受能力很强且不会刚愎自用的人 195 | 196 | 我真诚的邀请您加入我的群:94537310 197 | ~~~ 198 | -------------------------------------------------------------------------------- /session_test.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | ) 7 | 8 | func initSession() ISession { 9 | return initDB().NewSession() 10 | } 11 | 12 | func TestSession_Query(t *testing.T) { 13 | var s = initSession() 14 | //var user []Users 15 | res, err := s.Query("select * from users where name=?", "gorose2") 16 | if err != nil { 17 | t.Error(err.Error()) 18 | } 19 | //t.Log(res) 20 | t.Log(res, s.LastSql()) 21 | } 22 | 23 | func TestSession_Query3(t *testing.T) { 24 | var s = initSession() 25 | var o []Users 26 | //var o []map[string]interface{} 27 | //var o []gorose.Data 28 | res, err := s.Bind(&o).Query("select * from users limit 2") 29 | if err != nil { 30 | t.Error(err.Error()) 31 | } 32 | t.Log(res) 33 | t.Log(o, s.LastSql()) 34 | } 35 | 36 | func TestSession_Execute(t *testing.T) { 37 | var sql = `CREATE TABLE IF NOT EXISTS "orders" ( 38 | "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 39 | "goodsname" TEXT NOT NULL default "", 40 | "price" decimal default "0.00" 41 | )` 42 | var s = initSession() 43 | var err error 44 | var aff int64 45 | 46 | aff, err = s.Execute(sql) 47 | if err != nil { 48 | t.Error(err.Error()) 49 | } 50 | if aff == 0 { 51 | return 52 | } 53 | 54 | aff, err = s.Execute("insert into orders(goodsname,price) VALUES(?,?),(?,?)", 55 | "goods1", 1.23, "goods2", 3.23) 56 | if err != nil { 57 | t.Error(err.Error()) 58 | } 59 | t.Log(aff) 60 | } 61 | 62 | func TestSession_Query_struct(t *testing.T) { 63 | var s = initSession() 64 | var err error 65 | // defer s.Close() 66 | 67 | var user []Users 68 | _, err = s.Bind(&user).Query("select * from users limit ?", 2) 69 | if err != nil { 70 | t.Error(err.Error()) 71 | } 72 | t.Log("多条struct绑定:", user) 73 | 74 | var user2 Users 75 | _, err = s.Bind(&user2).Query("select * from users limit ?", 2) 76 | if err != nil { 77 | t.Error(err.Error()) 78 | } 79 | t.Log("一条struct绑定:", user2) 80 | } 81 | 82 | //type UserMap map[string]interface{} 83 | 84 | func TestSession_Query_map(t *testing.T) { 85 | var s = initSession() 86 | var err error 87 | 88 | var user2 = aaa{} 89 | _, err = s.Bind(&user2).Query("select * from users limit ?", 2) 90 | if err != nil { 91 | t.Error(err.Error()) 92 | } 93 | t.Log("一条map绑定:", user2) 94 | t.Log("一条map绑定的uid为:", user2["uid"]) 95 | t.Log(s.LastSql()) 96 | 97 | var user = bbb{} 98 | _, err = s.Bind(&user).Query("select * from users limit ?", 2) 99 | if err != nil { 100 | t.Error(err.Error()) 101 | } 102 | t.Log("多条map绑定:", user) 103 | t.Log("多条map绑定:", user[0]["age"].Int()) 104 | t.Log(s.LastSql()) 105 | } 106 | 107 | func TestSession_Bind(t *testing.T) { 108 | var s = initSession() 109 | var err error 110 | 111 | var user2 = aaa{} 112 | _, err = s.Bind(&user2).Query("select * from users limit ?", 2) 113 | 114 | if err != nil { 115 | t.Error(err.Error()) 116 | } 117 | t.Log("session bind success") 118 | } 119 | 120 | func TestSession_Transaction(t *testing.T) { 121 | var s = initSession() 122 | // 一键事务, 自动回滚和提交, 我们只需要关注业务即可 123 | err := s.Transaction(trans1, trans2) 124 | if err != nil { 125 | t.Error(err.Error()) 126 | } 127 | t.Log("session transaction success") 128 | } 129 | 130 | func trans1(s ISession) error { 131 | var err error 132 | var aff int64 133 | aff, err = s.Execute("update users set name=?,age=? where uid=?", 134 | "gorose3", 21, 3) 135 | if err != nil { 136 | return err 137 | } 138 | if aff == 0 { 139 | return errors.New("fail") 140 | } 141 | 142 | aff, err = s.Execute("update users set name=?,age=? where uid=?", 143 | "gorose2", 20, 2) 144 | if err != nil { 145 | return err 146 | } 147 | if aff == 0 { 148 | return errors.New("fail") 149 | } 150 | 151 | return nil 152 | } 153 | func trans2(s ISession) error { 154 | var err error 155 | var aff int64 156 | aff, err = s.Execute("update users set name=?,age=? where uid=?", 157 | "gorose3", 21, 3) 158 | if err != nil { 159 | return err 160 | } 161 | if aff == 0 { 162 | return errors.New("fail") 163 | } 164 | 165 | aff, err = s.Execute("update users set name=?,age=? where uid=?", 166 | "gorose2", 20, 2) 167 | if err != nil { 168 | return err 169 | } 170 | if aff == 0 { 171 | return errors.New("fail") 172 | } 173 | 174 | return nil 175 | } 176 | -------------------------------------------------------------------------------- /orm_execute.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | import ( 4 | "errors" 5 | "github.com/gohouse/t" 6 | "reflect" 7 | ) 8 | 9 | // Insert : insert data and get affected rows 10 | func (dba *Orm) Insert(data ...interface{}) (int64, error) { 11 | return dba.exec("insert", data...) 12 | } 13 | 14 | // insertGetId : insert data and get id 15 | func (dba *Orm) InsertGetId(data ...interface{}) (int64, error) { 16 | _, err := dba.Insert(data...) 17 | if err != nil { 18 | return 0, err 19 | } 20 | return dba.GetISession().LastInsertId(), nil 21 | } 22 | 23 | // Update : update data 24 | func (dba *Orm) Update(data ...interface{}) (int64, error) { 25 | return dba.exec("update", data...) 26 | } 27 | 28 | // Replace : replace data and get affected rows 29 | func (dba *Orm) Replace(data ...interface{}) (int64, error) { 30 | return dba.exec("replace", data...) 31 | } 32 | 33 | // Force 强制执行没有where的删除和修改 34 | func (dba *Orm) Force() IOrm { 35 | dba.force = true 36 | return dba 37 | } 38 | 39 | // Delete : delete data 40 | func (dba *Orm) Delete() (int64, error) { 41 | return dba.exec("delete") 42 | } 43 | 44 | // exec : Execute a sql 45 | func (dba *Orm) exec(operType string, data ...interface{}) (int64, error) { 46 | if operType == "insert" || operType == "update" { 47 | if dba.GetData() == nil { 48 | if len(data) > 0 { 49 | dba.Data(data[0]) 50 | } else { 51 | return 0, GetErr(ERR_PARAMS_MISSING, "Data()") 52 | } 53 | } 54 | 55 | //if dba.GetISession().GetIBinder() == nil { 56 | // 如果这里是默认值, 则需要对其进行table处理 57 | //if dba.GetISession().GetIBinder().GetBindType() == OBJECT_NIL { 58 | // if dba.GetData() != nil { 59 | // dba.Table(dba.GetData()) 60 | // } else { 61 | // return 0, GetErr(ERR_PARAMS_MISSING, "Data() or Table()") 62 | // } 63 | //} 64 | rl := reflect.ValueOf(dba.GetData()) 65 | rl2 := reflect.Indirect(rl) 66 | 67 | switch rl2.Kind() { 68 | case reflect.Struct, reflect.Ptr: 69 | //return 0, errors.New("传入的结构体必须是对象的地址") 70 | if tn := rl2.MethodByName("TableName"); tn.IsValid() { 71 | dba.Table(dba.GetData()) 72 | } 73 | case reflect.Map: 74 | if tn := rl2.MethodByName("TableName"); tn.IsValid() { 75 | dba.Table(dba.GetData()) 76 | } 77 | if tn := rl.MethodByName("TableName"); tn.IsValid() { 78 | dba.Table(dba.GetData()) 79 | } 80 | case reflect.Slice: 81 | r2 := rl2.Type().Elem() 82 | r2val := reflect.New(r2) 83 | switch r2val.Kind() { 84 | case reflect.Struct, reflect.Ptr: 85 | if tn := r2val.MethodByName("TableName"); tn.IsValid() { 86 | dba.Table(dba.GetData()) 87 | } 88 | case reflect.Map: 89 | if tn := r2val.MethodByName("TableName"); tn.IsValid() { 90 | dba.Table(dba.GetData()) 91 | } 92 | default: 93 | return 0, errors.New("表名有误") 94 | } 95 | } 96 | 97 | } 98 | // 构建sql 99 | sqlStr, args, err := dba.BuildSql(operType) 100 | if err != nil { 101 | return 0, err 102 | } 103 | 104 | return dba.GetISession().Execute(sqlStr, args...) 105 | } 106 | 107 | // Increment : auto Increment +1 default 108 | // we can define step (such as 2, 3, 6 ...) if give the second params 109 | // we can use this method as decrement with the third param as "-" 110 | // orm.Increment("top") , orm.Increment("top", 2, "-")=orm.Decrement("top",2) 111 | func (dba *Orm) Increment(args ...interface{}) (int64, error) { 112 | argLen := len(args) 113 | var field string 114 | var mode = "+" 115 | var value = "1" 116 | switch argLen { 117 | case 1: 118 | field = t.New(args[0]).String() 119 | case 2: 120 | field = t.New(args[0]).String() 121 | value = t.New(args[1]).String() 122 | case 3: 123 | field = t.New(args[0]).String() 124 | value = t.New(args[1]).String() 125 | mode = t.New(args[2]).String() 126 | default: 127 | return 0, errors.New("参数数量只允许1个,2个或3个") 128 | } 129 | dba.Data(field + "=" + field + mode + value) 130 | return dba.Update() 131 | } 132 | 133 | // Decrement : auto Decrement -1 default 134 | // we can define step (such as 2, 3, 6 ...) if give the second params 135 | func (dba *Orm) Decrement(args ...interface{}) (int64, error) { 136 | arglen := len(args) 137 | switch arglen { 138 | case 1: 139 | args = append(args, 1) 140 | args = append(args, "-") 141 | case 2: 142 | args = append(args, "-") 143 | default: 144 | return 0, errors.New("Decrement参数个数有误") 145 | } 146 | return dba.Increment(args...) 147 | } 148 | -------------------------------------------------------------------------------- /engin.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | ) 7 | 8 | // TAGNAME ... 9 | var TAGNAME = "gorose" 10 | 11 | // IGNORE ... 12 | var IGNORE = "-" 13 | 14 | type cluster struct { 15 | master []*sql.DB 16 | masterSize int 17 | slave []*sql.DB 18 | slaveSize int 19 | } 20 | 21 | // Engin ... 22 | type Engin struct { 23 | config *ConfigCluster 24 | driver string 25 | prefix string 26 | dbs *cluster 27 | logger ILogger 28 | } 29 | 30 | var _ IEngin = (*Engin)(nil) 31 | 32 | // NewEngin : init Engin struct pointer 33 | // NewEngin : 初始化 Engin 结构体对象指针 34 | func NewEngin(conf ...interface{}) (e *Engin, err error) { 35 | engin := new(Engin) 36 | if len(conf) == 0 { 37 | return 38 | } 39 | 40 | // 使用默认的log, 如果自定义了logger, 则只需要调用 Use() 方法即可覆盖 41 | engin.Use(DefaultLogger()) 42 | 43 | switch conf[0].(type) { 44 | // 传入的是单个配置 45 | case *Config: 46 | err = engin.bootSingle(conf[0].(*Config)) 47 | // 传入的是集群配置 48 | case *ConfigCluster: 49 | engin.config = conf[0].(*ConfigCluster) 50 | err = engin.bootCluster() 51 | default: 52 | panic(fmt.Sprint("Open() need *gorose.Config or *gorose.ConfigCluster param, also can empty for build sql string only, but ", 53 | conf, " given")) 54 | } 55 | 56 | return engin, err 57 | } 58 | 59 | // Use ... 60 | func (c *Engin) Use(closers ...func(e *Engin)) { 61 | for _, closer := range closers { 62 | closer(c) 63 | } 64 | } 65 | 66 | // Ping ... 67 | func (c *Engin) Ping() error { 68 | //for _,item := range c.dbs.master { 69 | // 70 | //} 71 | return c.GetQueryDB().Ping() 72 | } 73 | 74 | // TagName 自定义结构体对应的orm字段,默认gorose 75 | func (c *Engin) TagName(arg string) { 76 | //c.tagName = arg 77 | TAGNAME = arg 78 | } 79 | 80 | // IgnoreName 自定义结构体对应的orm忽略字段名字,默认- 81 | func (c *Engin) IgnoreName(arg string) { 82 | //c.ignoreName = arg 83 | IGNORE = arg 84 | } 85 | 86 | // SetPrefix 设置表前缀 87 | func (c *Engin) SetPrefix(pre string) { 88 | c.prefix = pre 89 | } 90 | 91 | // GetPrefix 获取前缀 92 | func (c *Engin) GetPrefix() string { 93 | return c.prefix 94 | } 95 | 96 | // GetDriver ... 97 | func (c *Engin) GetDriver() string { 98 | return c.driver 99 | } 100 | 101 | // GetQueryDB : get a slave db for using query operation 102 | // GetQueryDB : 获取一个从库用来做查询操作 103 | func (c *Engin) GetQueryDB() *sql.DB { 104 | if c.dbs.slaveSize == 0 { 105 | return c.GetExecuteDB() 106 | } 107 | var randint = getRandomInt(c.dbs.slaveSize) 108 | return c.dbs.slave[randint] 109 | } 110 | 111 | // GetExecuteDB : get a master db for using execute operation 112 | // GetExecuteDB : 获取一个主库用来做查询之外的操作 113 | func (c *Engin) GetExecuteDB() *sql.DB { 114 | if c.dbs.masterSize == 0 { 115 | return nil 116 | } 117 | var randint = getRandomInt(c.dbs.masterSize) 118 | return c.dbs.master[randint] 119 | } 120 | 121 | // GetLogger ... 122 | func (c *Engin) GetLogger() ILogger { 123 | return c.logger 124 | } 125 | 126 | // SetLogger ... 127 | func (c *Engin) SetLogger(lg ILogger) { 128 | c.logger = lg 129 | } 130 | 131 | func (c *Engin) bootSingle(conf *Config) error { 132 | // 如果传入的是单一配置, 则转换成集群配置, 方便统一管理 133 | var cc = new(ConfigCluster) 134 | cc.Master = append(cc.Master, *conf) 135 | c.config = cc 136 | return c.bootCluster() 137 | } 138 | 139 | func (c *Engin) bootCluster() error { 140 | //fmt.Println(len(c.config.Slave)) 141 | if len(c.config.Slave) > 0 { 142 | for _, item := range c.config.Slave { 143 | if c.config.Driver != "" { 144 | item.Driver = c.config.Driver 145 | } 146 | if c.config.Prefix != "" { 147 | item.Prefix = c.config.Prefix 148 | } 149 | db, err := c.bootReal(item) 150 | if err != nil { 151 | return err 152 | } 153 | if c.dbs == nil { 154 | c.dbs = new(cluster) 155 | } 156 | c.dbs.slave = append(c.dbs.slave, db) 157 | c.dbs.slaveSize++ 158 | c.driver = item.Driver 159 | } 160 | } 161 | var pre, dr string 162 | if len(c.config.Master) > 0 { 163 | for _, item := range c.config.Master { 164 | if c.config.Driver != "" { 165 | item.Driver = c.config.Driver 166 | } 167 | if c.config.Prefix != "" { 168 | item.Prefix = c.config.Prefix 169 | } 170 | db, err := c.bootReal(item) 171 | 172 | if err != nil { 173 | return err 174 | } 175 | if c.dbs == nil { 176 | c.dbs = new(cluster) 177 | } 178 | c.dbs.master = append(c.dbs.master, db) 179 | c.dbs.masterSize = c.dbs.masterSize + 1 180 | c.driver = item.Driver 181 | //fmt.Println(c.dbs.masterSize) 182 | if item.Prefix != "" { 183 | pre = item.Prefix 184 | } 185 | if item.Driver != "" { 186 | dr = item.Driver 187 | } 188 | } 189 | } 190 | // 如果config没有设置prefix,且configcluster设置了prefix,则使用cluster的prefix 191 | if pre != "" && c.prefix == "" { 192 | c.prefix = pre 193 | } 194 | // 如果config没有设置driver,且configcluster设置了driver,则使用cluster的driver 195 | if dr != "" && c.driver == "" { 196 | c.driver = dr 197 | } 198 | 199 | return nil 200 | } 201 | 202 | // boot sql driver 203 | func (c *Engin) bootReal(dbConf Config) (db *sql.DB, err error) { 204 | //db, err = sql.Open("mysql", "root:root@tcp(localhost:3306)/test?charset=utf8mb4") 205 | // 开始驱动 206 | db, err = sql.Open(dbConf.Driver, dbConf.Dsn) 207 | if err != nil { 208 | return 209 | } 210 | 211 | // 检查是否可以ping通 212 | err = db.Ping() 213 | if err != nil { 214 | return 215 | } 216 | 217 | // 连接池设置 218 | if dbConf.SetMaxOpenConns > 0 { 219 | db.SetMaxOpenConns(dbConf.SetMaxOpenConns) 220 | } 221 | if dbConf.SetMaxIdleConns > 0 { 222 | db.SetMaxIdleConns(dbConf.SetMaxIdleConns) 223 | } 224 | 225 | return 226 | } 227 | 228 | // NewSession 获取session实例 229 | // 这是一个语法糖, 为了方便使用(engin.NewSession())添加的 230 | // 添加后会让engin和session耦合, 如果不想耦合, 就删掉此方法 231 | // 删掉这个方法后,可以使用 gorose.NewSession(gorose.IEngin) 232 | // 通过 gorose.IEngin 依赖注入的方式, 达到解耦的目的 233 | func (c *Engin) NewSession() ISession { 234 | return NewSession(c) 235 | } 236 | 237 | // NewOrm 获取orm实例 238 | // 这是一个语法糖, 为了方便使用(engin.NewOrm())添加的 239 | // 添加后会让engin和 orm 耦合, 如果不想耦合, 就删掉此方法 240 | // 删掉这个方法后,可以使用 gorose.NewOrm(gorose.NewSession(gorose.IEngin)) 241 | // 通过 gorose.ISession 依赖注入的方式, 达到解耦的目的 242 | func (c *Engin) NewOrm() IOrm { 243 | return NewOrm(c) 244 | } 245 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GoRose-ORM-Pro 完全免费的数据库ORM 2 | 3 | [![GoDoc](https://godoc.org/github.com/tobycroft/gorose-pro?status.svg)](https://godoc.org/github.com/tobycroft/gorose-pro) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/tobycroft/gorose-pro)](https://goreportcard.com/report/github.com/tobycroft/gorose-pro) 5 | [![GitHub release](https://img.shields.io/github/release/tobycroft/gorose-pro.svg)](https://github.com/tobycroft/gorose-pro/releases/latest) 6 | ![GitHub](https://img.shields.io/github/license/tobycroft/gorose-pro?color=blue) 7 | ![GitHub All Releases](https://img.shields.io/github/downloads/tobycroft/gorose-pro/total?color=blue) 8 | 9 | gorose-orm 10 | 11 | ~~~ 12 | ██████╗ ██████╗ ██████╗ ██████╗ ███████╗███████╗ ██████╗ ██████╗ ██████╗ 13 | ██╔════╝ ██╔═══██╗██╔══██╗██╔═══██╗██╔════╝██╔════╝ ██╔══██╗██╔══██╗██╔═══██╗ 14 | ██║ ███╗██║ ██║██████╔╝██║ ██║███████╗█████╗█████╗██████╔╝██████╔╝██║ ██║ 15 | ██║ ██║██║ ██║██╔══██╗██║ ██║╚════██║██╔══╝╚════╝██╔═══╝ ██╔══██╗██║ ██║ 16 | ╚██████╔╝╚██████╔╝██║ ██║╚██████╔╝███████║███████╗ ██║ ██║ ██║╚██████╔╝ 17 | ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ 18 | ~~~ 19 | 20 | ## EnglishDOC 21 | 22 | [English Document](./README_en.md) 23 | 24 | ## 原版和Pro版本区别(原版没有的功能)+(猜你关心) 25 | 26 | - 反馈群:94537310 27 | - 100%兼容原版 28 | - 本项目已经包含所有Gorose的更新以及Bug修复以及Issues中提到但未修复的问题 29 | - 更加适合ThinkPHP/Laravel开发人员 30 | - go get -u 直接升级,每次升级均做到向上向下兼容无需担心更新后不兼容导致的事故 31 | - 更深度支持MySQL和MariaDB 32 | - 详细文档支持 33 | - 更快的PR/BUG响应+修复速度 34 | - 所有的更新/Bug修复完全来自于当前正在编写的商业项目,不可能出现更新后不管的情况 35 | - (*Pro)支持事务嵌套 36 | - (*Pro)支持复杂Where/OrWhere条件下的and/or条件查询(复杂环境下极好用!) 37 | - (*Pro)CountGroup使用GroupBy的时候返回正确的行数 38 | - (*Pro)SubQuery,安全链式参数化查询操作无需编写语句,生成From *subquery语句* 39 | - (*Pro)SubWhere,安全链式参数化子查询,生成Where *field* *in/=/like...* *subquery* 40 | - (*Pro)修复原版Paginator会出现函数不正确的BUG,高效不出错 41 | - (*Pro)PagiantorWG高性能多线程分页器[性能指示](./doc/performance/PaginatorWG.md) 42 | - (*Pro)修复Executor可能导致故障或删除据的问题 43 | - (*Pro)Oracle数据库支持Replace()方法 44 | 45 | ## 为什么要使用本项目? 46 | 47 | - 项目支持周期`2021-10`~`2028-8` 48 | - 费用:本项目完全免费,劳烦Star 49 | - 本项目已用在金融支付商城教育等项目中,以及GOV项目 50 | - 目前我的项目没有因为GorosePro炸过,可以放心使用 51 | - 立项原因:`原版`商项开发时缺失很多功能,且已`无人维护` 52 | - 原版事务死局:事务在跨模块调用时异常繁琐且没有多级/分级回退功能,这将导致如果你的程序需要设计订单支付功能,在这里有很大的坑等着你 53 | - 原版在实现复杂需求时的代码冗余度非常高,原因是原版更符合面向过程式的开发环境,Pro版本同时支持面向过程和面向对象 54 | 55 | ## 故障修复(原版未修) 56 | 57 | - 修复了高并发下,where等参数的的脏数据问题(如果你在用原版,避免生产环境使用单db) 58 | - 修复了Paginate不能用的问题,并且新增Paginator,以及PaginatorWG多线程查询 59 | - 修复原版Count和GroupBy同时使用时会出现的Total(总条数)错误的问题 60 | - 新增Counts兼容并修复原版复杂场景下Count不按条数计数的问题 61 | - 修复原版Oracle不可用问题,替换驱动使M1以后的ARM芯片可直连 62 | 63 | ## 商业项目验证 64 | 65 | - MySQL支持已验证 66 | - 全版本支持 67 | - AliYun-RDS-MySQL8 68 | - BT 69 | - 8.0.11 70 | - 8.0.34+ 71 | - MariaDB支持已验证 72 | - 全版本支持 73 | - BT 74 | - 10.5 75 | - 10.7 76 | - 10.10 77 | - Oracle支持已验证 78 | - 12XE 79 | 80 | ## 实例文档(Wiki) 81 | 82 | 说明:*为GorosePro独有功能 83 | 84 | - 增删改 85 | - [增加Insert](../../wiki/Insert新增数据) 86 | - [删除Delete](../../wiki/Delete删除数据) 87 | - [更新Update](../../wiki/Update方法) 88 | - *[替换-Replace](../../wiki/Replace方法) 89 | - 单条查询(对象)(Map[string]interface{}) 90 | - [Find/First返回对象](../../wiki/Find-First查询返回Obj对象方法) 91 | - 多条/联合查询([]Map[string]interface{} 92 | - [Get/Select返回数组](../../wiki/Get-Select方法) 93 | - [Join联合查询](../../wiki/Join-Select方法) 94 | - *[Paginator复杂的子查询分页构建](../../wiki/Paginator复杂的子查询分页构建) 95 | - *[PaginatorWG高性能分页](../../wiki/PaginatorWG多线程分页) 96 | - Query方法 97 | - [Query方法使用原生语句查询](../../wiki/Query方法) 98 | - 嵌套事务 99 | - *[支付环境下复杂的嵌套事务实例](../../wiki/支付环境下复杂的嵌套事务) 100 | - 子查询subQuery 101 | - *[SubSql防注入From子查询](../../wiki/SubQuery安全子查询) 102 | - *[SubWhere防注入where子查询](../../wiki/SubWhere安全子查询) 103 | - 安全性能 104 | - [Paginator-Performance-by-ChatGPT](../../wiki/Paginator分页查询的性能问题-ChatGPT ) 105 | - 单条/多条查询(Struct) 106 | - *[Scan方法](../../wiki/Scan将结果独立输出到struct) 107 | - 分块读取 108 | - [Chunk分块数据读取](../../wiki/Chunk分块数据读取) 109 | - *[ChunkWG高性能分块数据读取](../../wiki/Chunk分块数据读取) 110 | 111 | ## 简介 112 | 113 | GorosePro是一个GolangOrm升级改版项目,在支持原框架所有功能的基础上修复了BUG,更加适合复杂的商业项目 114 | 115 | 支持解耦式开发和直觉式编程,大大降低你的试错成本,让小型项目开发速更快,让大型项目更加容易维护 116 | 117 | ## 安装 118 | 119 | - go.mod 中添加 120 | 121 | ```bash 122 | require github.com/tobycroft/gorose-pro v1.12.12 123 | ``` 124 | 125 | - go get 126 | 127 | ```bash 128 | go get -u github.com/tobycroft/gorose-pro 129 | ``` 130 | 131 | ## 支持驱动 132 | 133 | - mysql : https://github.com/go-sql-driver/mysql 134 | - sqlite3 : https://github.com/mattn/go-sqlite3 135 | - postgres : https://github.com/lib/pq 136 | - oracle : https://github.com/sijms/go-ora 137 | - mssql : https://github.com/denisenkom/go-mssqldb 138 | - clickhouse : https://github.com/kshvakov/clickhouse 139 | 140 | ## 配置和链接初始化 141 | 142 | - [MySQL的初始化方式](../../wiki/初始化方法MySQL) 143 | - [Oracle的初始化方式](../../wiki/初始化方法Oracle) 144 | 145 | 更多配置, 可以配置集群,甚至可以同时配置不同数据库在一个集群中, 数据库会随机选择集群的数据库来完成对应的读写操作, 146 | 其中master是写库, slave是读库, 需要自己做好主从复制, 这里只负责读写 147 | 148 | ```go 149 | var config1 = gorose.Config{Dsn: 上面的dsn} 150 | var config2 = gorose.Config{Dsn: 上面的dsn} 151 | var config3 = gorose.Config{Dsn: 上面的dsn} 152 | var config4 = gorose.Config{Dsn: 上面的dsn} 153 | var configCluster = &gorose.ConfigCluster{ 154 | Master: []gorose.Config{config3, config4}, 155 | Slave: []gorose.Config{config1, config2}, 156 | Driver: "sqlite3", 157 | } 158 | ``` 159 | 160 | 初始化使用 161 | 162 | ```go 163 | var engin *gorose.Engin 164 | engin, err := Open(config) 165 | //engin, err := Open(configCluster) 166 | 167 | if err != nil { 168 | panic(err.Error()) 169 | } 170 | ``` 171 | 172 | ## TODO 173 | 174 | - 加入缓存层 175 | - 如果不使用Struct来接收返回,可能会导致Hash返回的Column类型从非string统一变成string(这是Redis的问题) 176 | - 在开启缓存功能后避免使用断言是比较简单的解决方案,如果吧类型也存在Redis里面,固然能简单的解决但是也会增加Redis的链接负载 177 | - 可选泛型返回...已完成 178 | - ElasticSearch 179 | - 数据自动上载 180 | - 从ES中读取数据 181 | 182 | ## Stargazers over time 183 | 184 | [![Stargazers over time](https://starchart.cc/tobycroft/gorose-pro.svg?variant=adaptive)](https://starchart.cc/tobycroft/gorose-pro) 185 | 186 | 187 | -------------------------------------------------------------------------------- /README_en.md: -------------------------------------------------------------------------------- 1 | # GoRose-ORM-Pro A Free Go MySQL ORM 2 | 3 | [![GoDoc](https://godoc.org/github.com/tobycroft/gorose-pro?status.svg)](https://godoc.org/github.com/tobycroft/gorose-pro) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/tobycroft/gorose-pro)](https://goreportcard.com/report/github.com/tobycroft/gorose-pro) 5 | [![GitHub release](https://img.shields.io/github/release/tobycroft/gorose-pro.svg)](https://github.com/tobycroft/gorose-pro/releases/latest) 6 | [![Gitter](https://badges.gitter.im/tobycroft/gorose-pro.svg)](https://gitter.im/gorose-pro/wechat) 7 | ![GitHub](https://img.shields.io/github/license/tobycroft/gorose-pro?color=blue) 8 | ![GitHub All Releases](https://img.shields.io/github/downloads/tobycroft/gorose-pro/total?color=blue) 9 | 10 | 94537310 11 | gorose-orm 12 | 13 | ~~~ 14 | ██████╗ ██████╗ ██████╗ ██████╗ ███████╗███████╗ ██████╗ ██████╗ ██████╗ 15 | ██╔════╝ ██╔═══██╗██╔══██╗██╔═══██╗██╔════╝██╔════╝ ██╔══██╗██╔══██╗██╔═══██╗ 16 | ██║ ███╗██║ ██║██████╔╝██║ ██║███████╗█████╗█████╗██████╔╝██████╔╝██║ ██║ 17 | ██║ ██║██║ ██║██╔══██╗██║ ██║╚════██║██╔══╝╚════╝██╔═══╝ ██╔══██╗██║ ██║ 18 | ╚██████╔╝╚██████╔╝██║ ██║╚██████╔╝███████║███████╗ ██║ ██║ ██║╚██████╔╝ 19 | ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ 20 | ~~~ 21 | 22 | ## What the difference between this than the OriginalVer. 23 | 24 | - All functionality was updated to the latest version from the original version 25 | - Intuition Coding 26 | - Nested Transactions functionality 27 | - Simple update without any hesitation ,fully compatible with earlier versions 28 | - Fully support MySQL and MariaDB 29 | - Document support as detail as possible 30 | - 100% compatible with original function 31 | - Dealing PR & Bug fix much more sooner 32 | - All function has been tested and verified by commercial projects 33 | - Support complexity and/or sql in Where/OrWhere function 34 | - Paginator is now able to return the correct TOTAL number 35 | - Add CountGroup to return correct row count from sql sentence 36 | - SubQuery: Highly security parameterized query under prepared condition 37 | - SubWhere: Full Prepared condition parameterized where sql searching 38 | - Fixed original framework's Executor might have unintentionally error or deleted data 39 | - Oracle support "Replace()" function like MyBatis when a good performance 40 | 41 | ## Purpose of this project 42 | 43 | - Fee: Totally Free but only need your star 44 | - Avoid the risk of the deprecation of the original ver 45 | - To solve the shortage during coding in the real life 46 | - Massive demos from the document, what ever the skill you are, you still able to find a solution here 47 | 48 | ## Bug Fix 49 | 50 | - Dirty Read under concurrency circumstances(this will be only and easily triggered by using *db mode) 51 | - Paginate fixed, this function finally come back to life, new "Paginator" function make it much more easier to use 52 | - Fix the row_count(Total in Paginator mode) when using GroupBy function 53 | - Fix Oracle unable to connect & use problem, Fixed arm MacM1 chip's compatibility with Oracle 54 | 55 | ## Docs and Demos(Wiki) 56 | 57 | Notice:'*' means Only Support In GorosePro 58 | 59 | - CUD functions 60 | - [Insert](../../wiki/Insert新增数据) 61 | - [Delete](../../wiki/Delete删除数据) 62 | - [Update](../../wiki/Update方法) 63 | - *[Replace](../../wiki/Replace方法) 64 | - Read the first line data and return in map[string]any 65 | - [Find/First](../../wiki/Find-First查询返回Obj对象方法) 66 | - Read multiple data in array by []map[string]any 67 | - [Get/Select](../../wiki/Get-Select方法) 68 | - [Join](../../wiki/Join-Select方法) 69 | - *[Paginator](../../wiki/Paginator复杂的子查询分页构建) 70 | - *[Paginator in HighPerformant](../../wiki/PaginatorWG多线程分页) 71 | - Raw SQL sentence mode 72 | - [Query](../../wiki/Query方法) 73 | - Nested Transaction(only support in GorosePro) 74 | - *[Demos](../../wiki/支付环境下复杂的嵌套事务) 75 | - subQuery 76 | - *[SubSql](../../wiki/SubQuery安全子查询) 77 | - *[SubWhere](../../wiki/SubWhere安全子查询) 78 | - Security and Performance 79 | - [Paginator-Performance-by-ChatGPT](../../wiki/Paginator分页查询的性能问题-ChatGPT ) 80 | - Single/Multiple data to struct(Struct) 81 | - *[Scan方法](../../wiki/Scan将结果独立输出到struct) 82 | 83 | ## Introduction 84 | 85 | Gorosepro is an upgrade and revision project of GOORM. It fixes bugs on the basis of supporting all functions of the 86 | original 87 | framework and is more suitable for complex commercial projects 88 | 89 | Support decoupling development and intuitive programming, greatly reduce your trial and error cost, make small projects 90 | develop faster, and make large projects easier to maintain 91 | 92 | ## Installation 93 | 94 | - Add in go.mod 95 | 96 | ```bash 97 | require github.com/tobycroft/gorose-pro v1.2.5 98 | ``` 99 | 100 | - go get 101 | 102 | ```bash 103 | go get -u github.com/tobycroft/gorose-pro 104 | ``` 105 | 106 | ## Driver support 107 | 108 | - mysql : https://github.com/go-sql-driver/mysql 109 | - sqlite3 : https://github.com/mattn/go-sqlite3 110 | - postgres : https://github.com/lib/pq 111 | - oracle : https://github.com/sijms/go-ora 112 | - mssql : https://github.com/denisenkom/go-mssqldb 113 | - clickhouse : https://github.com/kshvakov/clickhouse 114 | 115 | ## Initializing 116 | 117 | - [MySQL initializing](../../wiki/初始化方法MySQL) 118 | - [Oracle initializing](../../wiki/初始化方法Oracle) 119 | 120 | For more configurations, you can configure the cluster, or even configure different databases at the same time. In a 121 | cluster, the database will randomly select the database of the cluster to complete the corresponding read-write 122 | operations. The master is the write database, and the slave is the read database. You need to do master-slave 123 | replication. Here, you are only responsible for reading and writing 124 | 125 | ```go 126 | var config1 = gorose.Config{Dsn: 上面的dsn} 127 | var config2 = gorose.Config{Dsn: 上面的dsn} 128 | var config3 = gorose.Config{Dsn: 上面的dsn} 129 | var config4 = gorose.Config{Dsn: 上面的dsn} 130 | var configCluster = &gorose.ConfigCluster{ 131 | Master: []gorose.Config{config3, config4}, 132 | Slave: []gorose.Config{config1, config2}, 133 | Driver: "sqlite3", 134 | } 135 | ``` 136 | 137 | Initialize then use 138 | 139 | ```go 140 | var engin *gorose.Engin 141 | engin, err := Open(config) 142 | //engin, err := Open(configCluster) 143 | 144 | if err != nil { 145 | panic(err.Error()) 146 | } 147 | ``` 148 | 149 | ## Stargazers over time 150 | 151 | [![Stargazers over time](https://starchart.cc/tobycroft/gorose-pro.svg)](https://starchart.cc/tobycroft/gorose-pro) 152 | -------------------------------------------------------------------------------- /orm_query_test.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestOrm_BuildSql2(t *testing.T) { 11 | db := DB() 12 | var u = "age=age+1,num=num+1" 13 | var wheres interface{} 14 | wheres = [][]interface{}{{"a", ">", "b"}, {"a", "b"}, {"a is null"}} 15 | sqlstr, a, b := db.Force().Table("users").Data(u).Where(wheres).BuildSql("update") 16 | 17 | t.Log(sqlstr, a, b) 18 | } 19 | 20 | func TestOrm_BuildSql3(t *testing.T) { 21 | db := DB() 22 | var u = "age=age+1,num=num+1" 23 | var wheres interface{} 24 | wheres = [][]interface{}{{"a", ">", "b"}, {"a", "b"}} 25 | sqlstr, a, b := db.Force().Table(Users{}).Data(u).Where(wheres).BuildSql("update") 26 | 27 | t.Log(sqlstr, a, b) 28 | } 29 | 30 | func TestOrm_BuildSql4(t *testing.T) { 31 | db := DB() 32 | //var wheres interface{} 33 | //wheres = [][]interface{}{{"a", ">", "b"},{"lock",1}} 34 | wheres := Data{"lock": 1, "`date`": 1} 35 | obj := db.Table(Users{}).Where(wheres).Where(func() { 36 | db.Where("c", 2).OrWhere("lock", ">", 4) 37 | }).Data(wheres) 38 | 39 | sqlstr, a, b := obj.BuildSql() 40 | t.Log(sqlstr, a, b) 41 | 42 | sqlstr, a, b = obj.BuildSql("update") 43 | t.Log(sqlstr, a, b) 44 | 45 | sqlstr, a, b = obj.BuildSql("insert") 46 | t.Log(sqlstr, a, b) 47 | } 48 | 49 | func TestOrm_BuildSql5(t *testing.T) { 50 | //ticker := time.NewTicker(100*time.Millisecond) 51 | go func(t *testing.T) { 52 | for { 53 | //<-ticker.C 54 | db := DB() 55 | sqlstr, a, b := db.Table("users").Where("uid", ">", 1).BuildSql() 56 | //c,d := db.Table("users").Get() 57 | //t.Log(db.LastSql()) 58 | count, d := db.First() 59 | 60 | t.Log(sqlstr, a, b) 61 | t.Log(count, d) 62 | t.Log(db.LastSql()) 63 | } 64 | }(t) 65 | time.Sleep(500 * time.Millisecond) 66 | } 67 | 68 | func TestOrm_BuildSql6(t *testing.T) { 69 | var db = DB() 70 | sqlstr, a, b := db.Table("users3").Limit(2).Offset(2).BuildSql() 71 | t.Log(sqlstr, a, b) 72 | 73 | sqlstr, a, b = db.Table("users2").Limit(2).Offset(2).BuildSql() 74 | t.Log(sqlstr, a, b) 75 | 76 | var u = Users{ 77 | Uid: 1111, 78 | Name: "2", 79 | Age: 3, 80 | } 81 | res, err := db.Table("xxx").Where("xx", "xx").Update(&u) 82 | t.Log(db.LastSql(), res, err) 83 | } 84 | 85 | func TestOrm_First(t *testing.T) { 86 | res, err := DB().Table(Users{}).Where("uid", 1).First() 87 | if err != nil { 88 | t.Error(err.Error()) 89 | } 90 | t.Log(res) 91 | } 92 | 93 | func TestOrm_Select(t *testing.T) { 94 | db := DB() 95 | var err error 96 | 97 | var u = []Users{} 98 | err = db.Table(&u).Select() 99 | t.Log(err, u, db.LastSql()) 100 | 101 | var u2 = Users{} 102 | err = db.Table(&u2).Select() 103 | t.Log(err, u2, db.LastSql()) 104 | 105 | var u3 Users 106 | err = db.Table(&u3).Select() 107 | t.Log(err, u3, db.LastSql()) 108 | 109 | var u4 []Users 110 | err = db.Table(&u4).Limit(2).Select() 111 | t.Log(err, u4, db.LastSql()) 112 | if err != nil { 113 | t.Error(err.Error()) 114 | } 115 | t.Log(u, u2, u3, u4) 116 | } 117 | 118 | func TestOrm_Select2(t *testing.T) { 119 | db := DB() 120 | var err error 121 | 122 | var u = []UsersMap{} 123 | err = db.Table(&u).Limit(2).Select() 124 | if err != nil { 125 | t.Error(err.Error()) 126 | } 127 | t.Log(u) 128 | 129 | var u3 = UsersMap{} 130 | err = db.Table(&u3).Limit(1).Select() 131 | if err != nil { 132 | t.Error(err.Error()) 133 | } 134 | t.Log(u) 135 | } 136 | 137 | type Users2 struct { 138 | Name string `orm:"name"` 139 | Age int `orm:"age"` 140 | Uid int `orm:"uid"` 141 | Fi string `orm:"ignore"` 142 | } 143 | 144 | func (u *Users2) TableName() string { 145 | return "users" 146 | } 147 | func TestOrm_Get2(t *testing.T) { 148 | db := DB() 149 | var err error 150 | var u []Users2 151 | 152 | res, err := db.Table("users").Where("uid", ">", 2). 153 | //Where("1","=","1"). 154 | Where("1 = 1"). 155 | Limit(2).Get() 156 | //res, err := db.Table(&u).Where("uid", ">", 0).Limit(2).Get() 157 | fmt.Println(db.LastSql()) 158 | if err != nil { 159 | t.Error(err.Error()) 160 | } 161 | t.Log(res, u) 162 | } 163 | 164 | func TestOrm_Get(t *testing.T) { 165 | orm := DB() 166 | 167 | var u = UsersMap{} 168 | ormObj := orm.Table(&u).Join("b", "a.id", "=", "b.id"). 169 | RightJoin("userinfo d on a.id=d.id"). 170 | Fields("a.uid,a.age"). 171 | Order("uid desc"). 172 | Where("a", 1). 173 | WhereNull("bb"). 174 | WhereNotNull("cc"). 175 | WhereIn("dd", []interface{}{1, 2}). 176 | OrWhereNotIn("ee", []interface{}{1, 2}). 177 | WhereBetween("ff", []interface{}{11, 21}). 178 | WhereNotBetween("ff", []interface{}{1, 2}). 179 | Where("a", "like", "%3%"). 180 | OrWhere(func() { 181 | orm.Where("c", 3).OrWhere(func() { 182 | orm.Where("d", ">", 4) 183 | }) 184 | }).Where("e", 5).Limit(5).Offset(2) 185 | s, a, err := ormObj.BuildSql() 186 | 187 | if err != nil { 188 | t.Error(err.Error()) 189 | } 190 | t.Log(s, a, u) 191 | } 192 | 193 | func TestOrm_Pluck(t *testing.T) { 194 | orm := DB() 195 | 196 | //var u = UsersMapSlice{} 197 | //var u []Users 198 | ormObj := orm.Table("users") 199 | //res,err := ormObj.Pluck("name", "uid") 200 | res, err := ormObj.Limit(5).Pluck("name", "uid") 201 | if err != nil { 202 | t.Error(err.Error()) 203 | } 204 | t.Log(res, orm.LastSql()) 205 | } 206 | 207 | func TestOrm_Value(t *testing.T) { 208 | db := DB() 209 | 210 | //var u = UsersMap{} 211 | //var u = UsersMapSlice{} 212 | //var u Users 213 | //var u []Users 214 | //ormObj := db.Table(&u) 215 | //ormObj := db.Table("users") 216 | ormObj := db.Table(Users{}) 217 | res, err := ormObj.Limit(5).Value("uid") 218 | if err != nil { 219 | t.Error(err.Error()) 220 | } 221 | t.Log(res, db.LastSql()) 222 | } 223 | 224 | func TestOrm_Count(t *testing.T) { 225 | db := DB() 226 | 227 | //var u = UsersMap{} 228 | //ormObj := db.Table(&u) 229 | ormObj := db.Table("users") 230 | 231 | res, err := ormObj.Count() 232 | if err != nil { 233 | t.Error(err.Error()) 234 | } 235 | t.Log(res, db.LastSql()) 236 | } 237 | 238 | func TestOrm_Count2(t *testing.T) { 239 | var u Users 240 | var count int64 241 | count, err := DB().Table(&u).Count() 242 | if err != nil { 243 | t.Error(err.Error()) 244 | } 245 | t.Log(count) 246 | } 247 | 248 | func TestOrm_Chunk(t *testing.T) { 249 | orm := DB() 250 | 251 | var u = []UsersMap{} 252 | err := orm.Table(&u).Chunk(1, func(data []Data) error { 253 | for _, item := range data { 254 | t.Log(item["name"]) 255 | } 256 | return errors.New("故意停止,防止数据过多,浪费时间") 257 | //return nil 258 | }) 259 | if err != nil && err.Error() != "故意停止,防止数据过多,浪费时间" { 260 | t.Error(err.Error()) 261 | } 262 | t.Log("Chunk() success") 263 | } 264 | 265 | func TestOrm_Chunk2(t *testing.T) { 266 | orm := DB() 267 | 268 | var u []Users 269 | var i int 270 | err := orm.Table(&u).ChunkStruct(2, func() error { 271 | //for _, item := range u { 272 | t.Log(u) 273 | //} 274 | if i == 2 { 275 | return errors.New("故意停止,防止数据过多,浪费时间") 276 | } 277 | i++ 278 | return nil 279 | }) 280 | if err != nil && err.Error() != "故意停止,防止数据过多,浪费时间" { 281 | t.Error(err.Error()) 282 | } 283 | t.Log("ChunkStruct() success") 284 | } 285 | 286 | func TestOrm_Loop(t *testing.T) { 287 | db := DB() 288 | 289 | var u = []UsersMap{} 290 | //aff,err := db.Table(&u).Force().Data(Data{"age": 18}).Update() 291 | //fmt.Println(aff,err) 292 | err := db.Table(&u).Where("age", 18).Loop(2, func(data []Data) error { 293 | for _, item := range data { 294 | _, err := DB().Table(&u).Data(Data{"age": 19}).Where("uid", item["uid"]).Update() 295 | if err != nil { 296 | t.Error(err.Error()) 297 | } 298 | } 299 | return errors.New("故意停止,防止数据过多,浪费时间") 300 | //return nil 301 | }) 302 | if err != nil && err.Error() != "故意停止,防止数据过多,浪费时间" { 303 | t.Error(err.Error()) 304 | } 305 | t.Log("Loop() success") 306 | } 307 | 308 | func TestOrm_Paginate(t *testing.T) { 309 | db := DB() 310 | 311 | var u []Users 312 | res, err := db.Table(&u).Limit(2).Paginate() 313 | if err != nil { 314 | t.Error(err.Error()) 315 | } 316 | t.Log(res, u) 317 | t.Log(db.LastSql()) 318 | } 319 | 320 | func TestOrm_Paginate2(t *testing.T) { 321 | db := DB() 322 | 323 | var u []Users 324 | res, err := db.Table(&u).Where("uid", ">", 1).Limit(2).Paginate(3) 325 | if err != nil { 326 | t.Error(err.Error()) 327 | } 328 | t.Log(res, u) 329 | t.Log(db.LastSql()) 330 | } 331 | 332 | func TestOrm_Sum(t *testing.T) { 333 | db := DB() 334 | 335 | var u Users 336 | //res, err := db.Table(Users{}).First() 337 | res, err := db.Table(&u).Where(Data{"uid": 1}).Sum("age") 338 | if err != nil { 339 | t.Error(err.Error()) 340 | } 341 | //fmt.Printf("%#v\n",res) 342 | t.Log(res, u) 343 | t.Log(db.LastSql()) 344 | } 345 | 346 | func BenchmarkNewOrm(b *testing.B) { 347 | engin := initDB() 348 | for i := 0; i < b.N; i++ { 349 | engin.NewOrm().Table("users").First() 350 | } 351 | } 352 | 353 | func BenchmarkNewOrm2(b *testing.B) { 354 | engin := initDB() 355 | for i := 0; i < b.N; i++ { 356 | engin.NewOrm().Table("users").First() 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /binder.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/gohouse/t" 7 | "reflect" 8 | ) 9 | 10 | // Map ... 11 | type Map t.MapStringT 12 | 13 | // Data ... 14 | type Data map[string]interface{} 15 | 16 | //Paginate 17 | type Paginate struct { 18 | Total int64 `json:"total",gorose:"total" :"total"` 19 | PerPage int `json:"per_page",gorose:"per_page" :"per_page"` 20 | CurrentPage int `json:"current_page",gorose:"current_page" :"current_page"` 21 | LastPage int `json:"last_page",gorose:"last_page" :"last_page"` 22 | FirstPageUrl int64 `json:"first_page_url",gorose:"first_page_url" :"first_page_url"` 23 | LastPageUrl int `json:"last_page_url",gorose:"last_page_url" :"last_page_url"` 24 | NextPageUrl interface{} `json:"next_page_url",gorose:"next_page_url" :"next_page_url"` 25 | PrevPageUrl interface{} `json:"prev_page_url",gorose:"prev_page_url" :"prev_page_url"` 26 | Data []Data `json:"data",gorose:"data" :"data"` 27 | } 28 | 29 | // BindType ... 30 | type BindType int 31 | 32 | const ( 33 | // OBJECT_STRUCT 结构体 一条数据 (struct) 34 | OBJECT_STRUCT BindType = iota 35 | // OBJECT_STRUCT_SLICE 结构体 多条数据 ([]struct) 36 | OBJECT_STRUCT_SLICE 37 | // OBJECT_MAP map 一条数据 (map[string]interface{}) 38 | OBJECT_MAP 39 | // OBJECT_MAP_SLICE map 多条数据 ([]map[string]interface{}) 40 | OBJECT_MAP_SLICE 41 | // OBJECT_STRING 非结构体 表名字符串 ("users") 42 | OBJECT_STRING 43 | // OBJECT_MAP_T map 一条数据 (map[string]t.Type) 44 | OBJECT_MAP_T 45 | // OBJECT_MAP_SLICE_T map 多条数据 ([]map[string]t.Type) 46 | OBJECT_MAP_SLICE_T 47 | // OBJECT_NIL 默认没有传入任何绑定对象,一般用于query直接返回 48 | OBJECT_NIL 49 | ) 50 | 51 | // BindString ... 52 | var BindString = map[BindType]string{ 53 | OBJECT_STRUCT: "OBJECT_STRUCT", 54 | OBJECT_STRUCT_SLICE: "OBJECT_STRUCT_SLICE", 55 | OBJECT_MAP: "OBJECT_MAP", 56 | OBJECT_MAP_SLICE: "OBJECT_MAP_SLICE", 57 | OBJECT_STRING: "OBJECT_STRING", 58 | OBJECT_MAP_T: "OBJECT_MAP_T", 59 | OBJECT_MAP_SLICE_T: "OBJECT_MAP_SLICE_T", 60 | OBJECT_NIL: "OBJECT_NIL", 61 | } 62 | 63 | // BindType.String ... 64 | func (b BindType) String() string { 65 | return BindString[b] 66 | } 67 | 68 | // Binder ... 69 | type Binder struct { 70 | // Bind是指传入的对象 [slice]map,[slice]struct 71 | // 传入的原始对象 72 | BindOrigin interface{} 73 | //BindOriginTableName []string 74 | // 解析出来的对象名字, 或者指定的method(TableName)获取到的名字 75 | BindName string 76 | // 一条结果的反射对象 77 | BindResult interface{} 78 | // 多条 79 | BindResultSlice reflect.Value 80 | // 传入结构体解析出来的字段 81 | BindFields []string 82 | // 传入的对象类型判定 83 | BindType BindType 84 | // 出入传入得是非slice对象, 则只需要取一条, 取多了也是浪费 85 | BindLimit int 86 | BindPrefix string 87 | // 多条map结果,传入的是string table时 88 | BindAll []Data 89 | } 90 | 91 | var _ IBinder = &Binder{} 92 | 93 | // NewBinder ... 94 | func NewBinder(o ...interface{}) *Binder { 95 | var binder = new(Binder) 96 | if len(o) > 0 { 97 | binder.SetBindOrigin(o[0]) 98 | } else { 99 | binder.BindType = OBJECT_NIL 100 | } 101 | return binder 102 | } 103 | 104 | // BindParse ... 105 | func (o *Binder) BindParse(prefix string) error { 106 | if o.GetBindOrigin() == nil { 107 | return nil 108 | } 109 | var BindName string 110 | switch o.GetBindOrigin().(type) { 111 | case string: // 直接传入的是表名 112 | o.SetBindType(OBJECT_STRING) 113 | BindName = o.GetBindOrigin().(string) 114 | //o.SetBindAll([]Map{}) 115 | 116 | // 传入的是struct或切片 117 | default: 118 | // 清空字段值,避免手动传入字段污染struct字段 119 | o.SetBindFields([]string{}) 120 | // make sure dst is an appropriate type 121 | dstVal := reflect.ValueOf(o.GetBindOrigin()) 122 | sliceVal := reflect.Indirect(dstVal) 123 | 124 | switch sliceVal.Kind() { 125 | case reflect.Struct: // struct 126 | o.SetBindType(OBJECT_STRUCT) 127 | BindName = sliceVal.Type().Name() 128 | o.SetBindResult(o.GetBindOrigin()) 129 | //// 默认只查一条 130 | //o.SetBindLimit(1) 131 | // 解析出字段 132 | o.parseFields() 133 | // 是否设置了表名 134 | switch dstVal.Kind() { 135 | case reflect.Ptr, reflect.Struct: 136 | if tn := dstVal.MethodByName("TableName"); tn.IsValid() { 137 | BindName = tn.Call(nil)[0].String() 138 | } 139 | default: 140 | return errors.New("传入的对象有误,示例:var user User,传入 &user{}") 141 | } 142 | case reflect.Map: // map 143 | o.SetBindType(OBJECT_MAP) 144 | //// 默认只查一条 145 | //o.SetBindLimit(1) 146 | // 147 | o.SetBindResult(o.GetBindOrigin()) 148 | //TODO 检查map的值类型, 是否是t.Type 149 | if sliceVal.Type().Elem() == reflect.ValueOf(map[string]t.Type{}).Type().Elem() { 150 | o.SetBindType(OBJECT_MAP_T) 151 | } 152 | // 是否设置了表名 153 | if dstVal.Kind() != reflect.Ptr { 154 | return errors.New("传入的不是map指针,如:var user gorose.Map,传入 &user{}") 155 | } 156 | if tn := dstVal.MethodByName("TableName"); tn.IsValid() { 157 | BindName = tn.Call(nil)[0].String() 158 | } 159 | 160 | case reflect.Slice: // []struct,[]map 161 | eltType := sliceVal.Type().Elem() 162 | 163 | switch eltType.Kind() { 164 | case reflect.Map: 165 | o.SetBindType(OBJECT_MAP_SLICE) 166 | o.SetBindResult(reflect.MakeMap(eltType).Interface()) 167 | o.SetBindResultSlice(sliceVal) 168 | //o.SetBindResultSlice(reflect.MakeSlice(sliceVal.Type(),0,0)) 169 | //TODO 检查map的值类型, 是否是t.Type 170 | if eltType.Elem() == reflect.ValueOf(map[string]t.Type{}).Type().Elem() { 171 | o.SetBindType(OBJECT_MAP_SLICE_T) 172 | } 173 | if dstVal.Kind() != reflect.Ptr { 174 | return errors.New("传入的不是map指针,如:var user gorose.Map,传入 &user{}") 175 | } 176 | // 检查设置表名 177 | r2val := reflect.New(eltType) 178 | if tn := r2val.MethodByName("TableName"); tn.IsValid() { 179 | BindName = tn.Call(nil)[0].String() 180 | } 181 | 182 | case reflect.Struct: 183 | o.SetBindType(OBJECT_STRUCT_SLICE) 184 | BindName = eltType.Name() 185 | br := reflect.New(eltType) 186 | o.SetBindResult(br.Interface()) 187 | o.SetBindResultSlice(sliceVal) 188 | // 解析出字段 189 | o.parseFields() 190 | 191 | // 是否设置了表名 192 | switch dstVal.Kind() { 193 | case reflect.Ptr, reflect.Struct: 194 | if tn := br.MethodByName("TableName"); tn.IsValid() { 195 | BindName = tn.Call(nil)[0].String() 196 | } 197 | default: 198 | return errors.New("传入的对象有误,示例:var user User,传入 &user{}") 199 | } 200 | default: 201 | return fmt.Errorf("table只接收 struct,[]struct,map[string]interface{},[]map[string]interface{}的对象和地址, 但是传入的是: %T", o.GetBindOrigin()) 202 | } 203 | // 是否设置了表名 204 | if tn := dstVal.MethodByName("TableName"); tn.IsValid() { 205 | BindName = tn.Call(nil)[0].String() 206 | } 207 | default: 208 | return fmt.Errorf("table只接收 struct,[]struct,map[string]interface{},[]map[string]interface{}, 但是传入的是: %T", o.GetBindOrigin()) 209 | } 210 | } 211 | 212 | o.SetBindName(prefix + BindName) 213 | o.SetBindPrefix(prefix) 214 | return nil 215 | } 216 | 217 | func (o *Binder) parseFields() { 218 | if len(o.GetBindFields()) == 0 { 219 | o.SetBindFields(getTagName(o.GetBindResult(), TAGNAME)) 220 | } 221 | } 222 | 223 | // ResetBindResultSlice ... 224 | func (o *Binder) ResetBindResultSlice() { 225 | if o.BindType == OBJECT_MAP_SLICE_T { 226 | o.BindResultSlice = reflect.New(o.BindResultSlice.Type()) 227 | } 228 | } 229 | 230 | // SetBindPrefix ... 231 | func (o *Binder) SetBindPrefix(arg string) { 232 | o.BindPrefix = arg 233 | } 234 | 235 | // GetBindPrefix ... 236 | func (o *Binder) GetBindPrefix() string { 237 | return o.BindPrefix 238 | } 239 | 240 | // SetBindOrigin ... 241 | func (o *Binder) SetBindOrigin(arg interface{}) { 242 | o.BindOrigin = arg 243 | } 244 | 245 | // GetBindOrigin ... 246 | func (o *Binder) GetBindOrigin() interface{} { 247 | return o.BindOrigin 248 | } 249 | 250 | // SetBindName ... 251 | func (o *Binder) SetBindName(arg string) { 252 | o.BindName = arg 253 | } 254 | 255 | // GetBindName ... 256 | func (o *Binder) GetBindName() string { 257 | return o.BindName 258 | } 259 | 260 | // SetBindResult ... 261 | func (o *Binder) SetBindResult(arg interface{}) { 262 | o.BindResult = arg 263 | } 264 | 265 | // GetBindResult ... 266 | func (o *Binder) GetBindResult() interface{} { 267 | return o.BindResult 268 | } 269 | 270 | // SetBindResultSlice ... 271 | func (o *Binder) SetBindResultSlice(arg reflect.Value) { 272 | o.BindResultSlice = arg 273 | } 274 | 275 | // GetBindResultSlice ... 276 | func (o *Binder) GetBindResultSlice() reflect.Value { 277 | return o.BindResultSlice 278 | } 279 | 280 | // SetBindFields ... 281 | func (o *Binder) SetBindFields(arg []string) { 282 | o.BindFields = arg 283 | } 284 | 285 | // GetBindFields ... 286 | func (o *Binder) GetBindFields() []string { 287 | return o.BindFields 288 | } 289 | 290 | // SetBindType ... 291 | func (o *Binder) SetBindType(arg BindType) { 292 | o.BindType = arg 293 | } 294 | 295 | // GetBindType ... 296 | func (o *Binder) GetBindType() BindType { 297 | return o.BindType 298 | } 299 | 300 | // SetBindAll ... 301 | func (o *Binder) SetBindAll(arg []Data) { 302 | o.BindAll = arg 303 | } 304 | 305 | // GetBindAll ... 306 | func (o *Binder) GetBindAll() []Data { 307 | return o.BindAll 308 | } 309 | 310 | // ResetBinder ... 311 | func (o *Binder) ResetBinder() { 312 | switch o.BindType { 313 | case OBJECT_STRUCT, OBJECT_MAP, OBJECT_MAP_T: 314 | // 清空结果 315 | o.SetBindOrigin(nil) 316 | case OBJECT_STRUCT_SLICE, OBJECT_MAP_SLICE, OBJECT_MAP_SLICE_T: 317 | //var rvResult = reflect.ValueOf(o.GetBindResult()) 318 | var rvResult = o.GetBindResultSlice() 319 | // 清空结果 320 | rvResult.Set(rvResult.Slice(0, 0)) 321 | default: 322 | o.SetBindAll([]Data{}) 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /orm.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | import ( 4 | "github.com/gohouse/t" 5 | "strings" 6 | ) 7 | 8 | // type TransactionHandlerFunc func(db IOrm) error 9 | // Orm ... 10 | type Orm struct { 11 | ISession 12 | //IBinder 13 | *OrmApi 14 | driver string 15 | bindValues []interface{} 16 | subQuery bool 17 | } 18 | 19 | var _ IOrm = (*Orm)(nil) 20 | 21 | // NewOrm ... 22 | func NewOrm(e IEngin) *Orm { 23 | var orm = new(Orm) 24 | orm.SetISession(NewSession(e)) 25 | //orm.IBinder = b 26 | orm.OrmApi = new(OrmApi) 27 | return orm 28 | } 29 | 30 | func NewOrmBuilder() *Orm { 31 | return new(Orm) 32 | } 33 | 34 | // Close ... 35 | func (dba *Orm) Close() { 36 | dba.GetISession().Close() 37 | } 38 | 39 | // ExtraCols 额外的字段 40 | func (dba *Orm) ExtraCols(args ...string) IOrm { 41 | dba.extraCols = append(dba.extraCols, args...) 42 | return dba 43 | } 44 | 45 | // ResetExtraCols ... 46 | func (dba *Orm) ResetExtraCols() IOrm { 47 | dba.extraCols = []string{} 48 | return dba 49 | } 50 | 51 | // SetBindValues ... 52 | func (dba *Orm) SetBindValues(v interface{}) { 53 | dba.bindValues = append(dba.bindValues, v) 54 | } 55 | 56 | // ClearBindValues ... 57 | func (dba *Orm) ClearBindValues() { 58 | dba.bindValues = []interface{}{} 59 | } 60 | 61 | // GetBindValues ... 62 | func (dba *Orm) GetBindValues() []interface{} { 63 | return dba.bindValues 64 | } 65 | 66 | // GetDriver ... 67 | func (dba *Orm) GetDriver() string { 68 | return dba.driver 69 | } 70 | 71 | // SetISession ... 72 | func (dba *Orm) SetISession(is ISession) { 73 | dba.ISession = is 74 | } 75 | 76 | // GetISession ... 77 | func (dba *Orm) GetISession() ISession { 78 | return dba.ISession 79 | } 80 | 81 | // GetOrmApi ... 82 | func (dba *Orm) GetOrmApi() *OrmApi { 83 | return dba.OrmApi 84 | } 85 | 86 | // Fields : select fields 87 | func (dba *Orm) Table(tab interface{}) IOrm { 88 | dba.GetISession().Bind(tab) 89 | //dba.table = dba.GetISession().GetTableName() 90 | return dba 91 | } 92 | 93 | // SubQuery : subquery sentence, args is the bind values 94 | func (dba *Orm) SubQuery(sql, alias string, args []interface{}) IOrm { 95 | dba.GetISession().Bind("(" + sql + ") " + alias) 96 | dba.bindValues = append(dba.bindValues, args...) 97 | dba.subQuery = true 98 | //fmt.Println(dba.bindValues) 99 | //dba.table = dba.GetISession().GetTableName() 100 | return dba 101 | } 102 | 103 | // IsSubQuery : iscurrentSubquery 104 | func (dba *Orm) IsSubQuery() bool { 105 | return dba.subQuery 106 | } 107 | 108 | // Fields : select fields 109 | func (dba *Orm) Fields(fields ...string) IOrm { 110 | dba.fields = fields 111 | return dba 112 | } 113 | 114 | // AddFields : If you already have a query builder instance and you wish to add a column to its existing select clause, you may use the AddFields method: 115 | func (dba *Orm) AddFields(fields ...string) IOrm { 116 | dba.fields = append(dba.fields, fields...) 117 | return dba 118 | } 119 | 120 | // Distinct : select distinct 121 | func (dba *Orm) Distinct() IOrm { 122 | dba.distinct = true 123 | 124 | return dba 125 | } 126 | 127 | // Data : insert or update data 128 | func (dba *Orm) Data(data interface{}) IOrm { 129 | dba.data = data 130 | return dba 131 | } 132 | 133 | // Group : select group by 134 | func (dba *Orm) Group(group string) IOrm { 135 | dba.group = group 136 | return dba 137 | } 138 | 139 | // GroupBy : equals Group() 140 | func (dba *Orm) GroupBy(group string) IOrm { 141 | return dba.Group(group) 142 | } 143 | 144 | // Having : select having 145 | func (dba *Orm) Having(having string) IOrm { 146 | dba.having = having 147 | return dba 148 | } 149 | 150 | // Order : select order by 151 | func (dba *Orm) Order(order string) IOrm { 152 | dba.order = order 153 | return dba 154 | } 155 | 156 | // OrderBy : equal order 157 | func (dba *Orm) OrderBy(order string) IOrm { 158 | return dba.Order(order) 159 | } 160 | 161 | // Limit : select limit 162 | func (dba *Orm) Limit(limit int) IOrm { 163 | dba.limit = limit 164 | return dba 165 | } 166 | 167 | // Offset : select offset 168 | func (dba *Orm) Offset(offset int) IOrm { 169 | dba.offset = offset 170 | return dba 171 | } 172 | 173 | // Page : select page 174 | func (dba *Orm) Page(page int) IOrm { 175 | dba.offset = (page - 1) * dba.GetLimit() 176 | return dba 177 | } 178 | 179 | // Where : query or execute where condition, the relation is and 180 | func (dba *Orm) SubWhere(field, condition, sql string, args []interface{}) IOrm { 181 | dba.Where(field, condition, sql, args) 182 | return dba 183 | } 184 | 185 | // Where : query or execute where condition, the relation is and 186 | func (dba *Orm) Where(args ...interface{}) IOrm { 187 | if len(args) == 0 || 188 | t.New(args[0]).Bool() == false { 189 | return dba 190 | } 191 | // 如果只传入一个参数, 则可能是字符串、一维对象、二维数组 192 | // 支持[]any{map[string]any}格式,格式,详情请参考文档,内联方式为 ...SQL... and (* and *) 模式 193 | // 重新组合为长度为3的数组, 第一项为关系(and/or), 第二项为具体传入的参数 []interface{} 194 | w := []interface{}{"and", args} 195 | 196 | dba.where = append(dba.where, w) 197 | 198 | return dba 199 | } 200 | 201 | /* 202 | WhereOr : 203 | 1.query or execute where condition, the relation is or , sub where is and 204 | 205 | 2. 如使用[]any{map[string]any}格式,格式,详情请参考文档,内联方式为 ...SQL... and (* or *) 模式 206 | */ 207 | func (dba *Orm) WhereOr(args ...interface{}) IOrm { 208 | // 在Where的基础上加入了and (sub_where1 or sub_where2)来解决子次序需要使用or连接的问题 209 | w := []interface{}{"andor", args} 210 | dba.where = append(dba.where, w) 211 | return dba 212 | } 213 | 214 | /* 215 | WhereOrAnd : 216 | 1.query or execute where condition, the relation is and , sub where is or 217 | 218 | 2.OrWhere将输出 ...sql... or 你的sql语句 ...sql... 219 | 220 | 3. 如使用[]any{map[string]any}格式,格式,详情请参考文档,内联方式为 ...SQL... or (* and *) 模式 221 | */ 222 | func (dba *Orm) WhereOrAnd(args ...interface{}) IOrm { 223 | // 在Where的基础上加入了or (sub_where1 and sub_where2)来解决子次序需要使用or连接的问题 224 | w := []interface{}{"orand", args} 225 | dba.where = append(dba.where, w) 226 | return dba 227 | } 228 | 229 | /* 230 | OrWhere : 231 | 1.query or execute where condition, the relation is or 232 | 233 | 2.OrWhere将输出 ...sql... or 你的sql语句 ...sql... 234 | 235 | 3. 在使用[]any{map[string]any}格式,格式,详情请参考文档,内联方式为 ...SQL... or (* and *) 模式 236 | */ 237 | func (dba *Orm) OrWhere(args ...interface{}) IOrm { 238 | // 如果只传入一个参数, 则可能是字符串、一维对象、二维数组 239 | // 重新组合为长度为3的数组, 第一项为关系(and/or), 第二项为具体传入的参数 []interface{} 240 | w := []interface{}{"or", args} 241 | dba.where = append(dba.where, w) 242 | return dba 243 | } 244 | 245 | // WhereNull ... 246 | func (dba *Orm) WhereNull(arg string) IOrm { 247 | return dba.Where(arg + " IS NULL") 248 | } 249 | 250 | // OrWhereNull ... 251 | func (dba *Orm) OrWhereNull(arg string) IOrm { 252 | return dba.OrWhere(arg + " IS NULL") 253 | } 254 | 255 | // WhereNotNull ... 256 | func (dba *Orm) WhereNotNull(arg string) IOrm { 257 | return dba.Where(arg + " IS NOT NULL") 258 | } 259 | 260 | // OrWhereNotNull ... 261 | func (dba *Orm) OrWhereNotNull(arg string) IOrm { 262 | return dba.OrWhere(arg + " IS NOT NULL") 263 | } 264 | 265 | // WhereRegexp ... 266 | func (dba *Orm) WhereRegexp(arg string, expstr string) IOrm { 267 | return dba.Where(arg, "REGEXP", expstr) 268 | } 269 | 270 | // OrWhereRegexp ... 271 | func (dba *Orm) OrWhereRegexp(arg string, expstr string) IOrm { 272 | return dba.OrWhere(arg, "REGEXP", expstr) 273 | } 274 | 275 | // WhereNotRegexp ... 276 | func (dba *Orm) WhereNotRegexp(arg string, expstr string) IOrm { 277 | return dba.Where(arg, "NOT REGEXP", expstr) 278 | } 279 | 280 | // OrWhereNotRegexp ... 281 | func (dba *Orm) OrWhereNotRegexp(arg string, expstr string) IOrm { 282 | return dba.OrWhere(arg, "NOT REGEXP", expstr) 283 | } 284 | 285 | // WhereIn ... 286 | func (dba *Orm) WhereIn(needle string, hystack []interface{}) IOrm { 287 | if len(hystack) > 0 { 288 | return dba.Where(needle, "IN", hystack) 289 | } else { 290 | return dba.WhereNull(needle) 291 | } 292 | } 293 | 294 | // OrWhereIn ... 295 | func (dba *Orm) OrWhereIn(needle string, hystack []interface{}) IOrm { 296 | if len(hystack) > 0 { 297 | return dba.OrWhere(needle, "IN", hystack) 298 | } else { 299 | return dba.OrWhereNull(needle) 300 | } 301 | } 302 | 303 | // WhereNotIn ... 304 | func (dba *Orm) WhereNotIn(needle string, hystack []interface{}) IOrm { 305 | if len(hystack) > 0 { 306 | return dba.Where(needle, "NOT IN", hystack) 307 | } else { 308 | return dba.WhereNotNull(needle) 309 | } 310 | } 311 | 312 | // OrWhereNotIn ... 313 | func (dba *Orm) OrWhereNotIn(needle string, hystack []interface{}) IOrm { 314 | if len(hystack) > 0 { 315 | dba.OrWhere(needle, "NOT IN", hystack) 316 | } else { 317 | dba.OrWhereNotNull(needle) 318 | } 319 | return dba 320 | } 321 | 322 | // WhereBetween ... 323 | func (dba *Orm) WhereBetween(needle string, hystack []interface{}) IOrm { 324 | return dba.Where(needle, "BETWEEN", hystack) 325 | } 326 | 327 | // OrWhereBetween ... 328 | func (dba *Orm) OrWhereBetween(needle string, hystack []interface{}) IOrm { 329 | return dba.OrWhere(needle, "BETWEEN", hystack) 330 | } 331 | 332 | // WhereNotBetween ... 333 | func (dba *Orm) WhereNotBetween(needle string, hystack []interface{}) IOrm { 334 | return dba.Where(needle, "NOT BETWEEN", hystack) 335 | } 336 | 337 | // OrWhereNotBetween ... 338 | func (dba *Orm) OrWhereNotBetween(needle string, hystack []interface{}) IOrm { 339 | return dba.OrWhere(needle, "NOT BETWEEN", hystack) 340 | } 341 | 342 | // Truncate ... 343 | func (dba *Orm) Truncate() (err error) { 344 | dba.table, err = dba.GetISession().GetTableName() 345 | if err != nil { 346 | dba.GetISession().GetIEngin().GetLogger().Error(err.Error()) 347 | return 348 | } 349 | _, err = dba.Execute("TRUNCATE " + dba.table) 350 | dba.ResetTable() 351 | return 352 | } 353 | 354 | // Join : select join query 355 | func (dba *Orm) Join(args ...interface{}) IOrm { 356 | dba._joinBuilder("INNER", args) 357 | return dba 358 | } 359 | 360 | // LeftJoin ... 361 | func (dba *Orm) LeftJoin(args ...interface{}) IOrm { 362 | dba._joinBuilder("LEFT", args) 363 | return dba 364 | } 365 | 366 | // RightJoin ... 367 | func (dba *Orm) RightJoin(args ...interface{}) IOrm { 368 | dba._joinBuilder("RIGHT", args) 369 | return dba 370 | } 371 | 372 | // CrossJoin ... 373 | func (dba *Orm) CrossJoin(args ...interface{}) IOrm { 374 | dba._joinBuilder("CROSS", args) 375 | return dba 376 | } 377 | 378 | // _joinBuilder 379 | func (dba *Orm) _joinBuilder(joinType string, args []interface{}) { 380 | dba.join = append(dba.join, []interface{}{joinType, args}) 381 | } 382 | 383 | // Reset orm api and bind values reset to init 384 | func (dba *Orm) Reset() IOrm { 385 | dba.OrmApi = new(OrmApi) 386 | dba.ClearBindValues() 387 | dba.ResetUnion() 388 | dba.ResetTable() 389 | dba.ResetWhere() 390 | dba.ResetExtraCols() 391 | return dba 392 | //return NewOrm(dba.GetIEngin()) 393 | } 394 | 395 | // Reset orm api and bind values reset to init 396 | func (dba *Orm) ResetWithOutNewApi() IOrm { 397 | dba.ClearBindValues() 398 | dba.ResetUnion() 399 | dba.ResetTable() 400 | dba.ResetWhere() 401 | dba.ResetExtraCols() 402 | return dba 403 | //return NewOrm(dba.GetIEngin()) 404 | } 405 | 406 | // ResetTable ... 407 | func (dba *Orm) ResetTable() IOrm { 408 | dba.GetISession().SetIBinder(NewBinder()) 409 | return dba 410 | } 411 | 412 | // ResetWhere ... 413 | func (dba *Orm) ResetWhere() IOrm { 414 | dba.where = [][]interface{}{} 415 | return dba 416 | } 417 | 418 | // ResetUnion ... 419 | func (dba *Orm) ResetUnion() IOrm { 420 | dba.GetISession().SetUnion(nil) 421 | return dba 422 | } 423 | 424 | // BuildSql 425 | // operType(select, insert, update, delete) 426 | func (dba *Orm) BuildSql(operType ...string) (a string, b []interface{}, err error) { 427 | // 解析table 428 | dba.table, err = dba.GetISession().GetTableName() 429 | if err != nil { 430 | dba.GetISession().GetIEngin().GetLogger().Error(err.Error()) 431 | return 432 | } 433 | // 解析字段 434 | // 如果有union操作, 则不需要 435 | if inArray(dba.GetIBinder().GetBindType(), []interface{}{OBJECT_STRUCT, OBJECT_STRUCT_SLICE}) && 436 | dba.GetUnion() == nil { 437 | dba.fields = getTagName(dba.GetIBinder().GetBindResult(), TAGNAME) 438 | } 439 | if len(operType) == 0 || (len(operType) > 0 && strings.ToLower(operType[0]) == "select") { 440 | //// 根据传入的struct, 设置limit, 有效的节约空间 441 | //if dba.union == "" { 442 | // var bindType = dba.GetIBinder().GetBindType() 443 | // if bindType == OBJECT_MAP || bindType == OBJECT_STRUCT { 444 | // dba.Limit(1) 445 | // } 446 | //} 447 | //fmt.Println("ssss", dba.bindValues) 448 | a, b, err = NewBuilder(dba.GetISession().GetIEngin().GetDriver()).BuildQuery(dba) 449 | //fmt.Println("aa", a, b, err) 450 | if dba.GetISession().GetTransaction() { 451 | a = a + dba.GetPessimisticLock() 452 | } 453 | } else { 454 | a, b, err = NewBuilder(dba.GetISession().GetIEngin().GetDriver()).BuildExecute(dba, strings.ToLower(operType[0])) 455 | // 重置强制获取更新或插入的字段, 防止复用时感染 456 | dba.ResetExtraCols() 457 | } 458 | // 如果是事务, 因为需要复用单一对象, 故参数会产生感染 459 | // 所以, 在这里做一下数据绑定重置操作 460 | if dba.GetISession().GetTransaction() { 461 | dba.Reset() 462 | } else { 463 | dba.ResetWithOutNewApi() 464 | } 465 | // 这里统一清理一下绑定的数据吧, 万一要复用了, 造成绑定数据感染, 就尴尬了 466 | dba.ClearBindValues() 467 | return 468 | } 469 | 470 | // Transaction ... 471 | func (dba *Orm) Transaction(closers ...func(db IOrm) error) (err error) { 472 | err = dba.ISession.Begin() 473 | if err != nil { 474 | dba.GetIEngin().GetLogger().Error(err.Error()) 475 | return err 476 | } 477 | 478 | for _, closer := range closers { 479 | err = closer(dba) 480 | if err != nil { 481 | dba.GetIEngin().GetLogger().Error(err.Error()) 482 | _ = dba.ISession.Rollback() 483 | return 484 | } 485 | } 486 | return dba.ISession.Commit() 487 | } 488 | 489 | // SharedLock 共享锁 490 | // select * from xxx lock in share mode 491 | func (dba *Orm) SharedLock() *Orm { 492 | dba.pessimisticLock = " lock in share mode" 493 | return dba 494 | } 495 | 496 | // LockForUpdate 497 | // select * from xxx for update 498 | func (dba *Orm) LockForUpdate() *Orm { 499 | dba.pessimisticLock = " for update" 500 | return dba 501 | } 502 | -------------------------------------------------------------------------------- /session.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | "fmt" 7 | "github.com/gohouse/t" 8 | "reflect" 9 | "strconv" 10 | "strings" 11 | "sync/atomic" 12 | "time" 13 | ) 14 | 15 | const beginStatus = 0 16 | 17 | // Session ... 18 | type Session struct { 19 | IEngin 20 | IBinder 21 | master *sql.DB 22 | tx *sql.Tx 23 | slave *sql.DB 24 | lastInsertId int64 25 | sqlLogs []string 26 | lastSql string 27 | union interface{} 28 | transaction bool 29 | tx_index int64 30 | err error 31 | } 32 | 33 | var _ ISession = (*Session)(nil) 34 | 35 | // NewSession : 初始化 Session 36 | func NewSession(e IEngin) *Session { 37 | 38 | var s = new(Session) 39 | s.IEngin = e 40 | // 初始化 IBinder 41 | s.SetIBinder(NewBinder()) 42 | 43 | s.master = e.GetExecuteDB() 44 | s.slave = e.GetQueryDB() 45 | //初始化数据 46 | s.tx_index = beginStatus 47 | 48 | return s 49 | } 50 | 51 | func (s *Session) Close() { 52 | s.master.Close() 53 | s.slave.Close() 54 | } 55 | 56 | // GetIEngin 获取engin 57 | func (s *Session) GetIEngin() IEngin { 58 | return s.IEngin 59 | } 60 | 61 | // GetDriver 获取驱动 62 | func (s *Session) SetIEngin(ie IEngin) { 63 | s.IEngin = ie 64 | } 65 | 66 | // Bind : 传入绑定结果的对象, 参数一为对象, 可以是 struct, gorose.MapRow 或对应的切片 67 | // 如果是做非query操作,第一个参数也可以仅仅指定为字符串表名 68 | func (s *Session) Bind(tab interface{}) ISession { 69 | //fmt.Println(tab, NewBinder(tab)) 70 | //s.SetIBinder(NewBinder(tab)) 71 | s.GetIBinder().SetBindOrigin(tab) 72 | s.err = s.IBinder.BindParse(s.GetIEngin().GetPrefix()) 73 | return s 74 | } 75 | 76 | // GetBinder 获取绑定对象 77 | func (s *Session) GetErr() error { 78 | return s.err 79 | } 80 | 81 | // GetBinder 获取绑定对象 82 | func (s *Session) SetIBinder(ib IBinder) { 83 | s.IBinder = ib 84 | } 85 | 86 | // GetBinder 获取绑定对象 87 | func (s *Session) GetIBinder() IBinder { 88 | return s.IBinder 89 | } 90 | 91 | // GetBinder 获取绑定对象 92 | func (s *Session) ResetBinderResult() { 93 | _ = s.IBinder.BindParse(s.GetIEngin().GetPrefix()) 94 | } 95 | 96 | // GetTableName 获取解析后的名字, 提供给orm使用 97 | // 为什么要在这里重复添加该方法, 而不是直接继承 IBinder 的方法呢? 98 | // 是因为, 这里涉及到表前缀的问题, 只能通过session来传递, 所以IOrm就可以选择直接继承 99 | func (s *Session) GetTableName() (string, error) { 100 | //err := s.IBinder.BindParse(s.GetIEngin().GetPrefix()) 101 | //fmt.Println(s.GetIBinder()) 102 | return s.GetIBinder().GetBindName(), s.err 103 | } 104 | 105 | // Begin ... 106 | func (s *Session) Begin() (err error) { 107 | s.SetTransaction(true) 108 | if s.tx == nil { 109 | s.tx, err = s.master.Begin() 110 | } else { 111 | num := atomic.AddInt64(&s.tx_index, 1) 112 | err = s.SavePoint("sp_" + strconv.FormatInt(num, 10)) 113 | if err != nil { 114 | s.GetIEngin().GetLogger().Error(err.Error()) 115 | return 116 | } else { 117 | } 118 | } 119 | return 120 | } 121 | 122 | // Rollback ... 123 | func (s *Session) Rollback() (err error) { 124 | if s.tx != nil && s.transaction == true { 125 | if atomic.LoadInt64(&s.tx_index) == beginStatus { 126 | s.SetTransaction(false) 127 | err = s.tx.Rollback() 128 | s.tx = nil 129 | } else { 130 | num := atomic.LoadInt64(&s.tx_index) 131 | err = s.RollbackTo("sp_" + strconv.FormatInt(num, 10)) 132 | if err != nil { 133 | s.GetIEngin().GetLogger().Error(err.Error()) 134 | return 135 | } else { 136 | atomic.AddInt64(&s.tx_index, -1) 137 | } 138 | } 139 | } 140 | return 141 | } 142 | 143 | // RollbackTo ... 144 | func (s *Session) RollbackTo(savepoint string) (err error) { 145 | if s.tx != nil && s.transaction == true { 146 | _, err = s.tx.Exec("rollback to " + savepoint) 147 | if err != nil { 148 | s.GetIEngin().GetLogger().Error(err.Error()) 149 | return 150 | } 151 | } 152 | return 153 | } 154 | 155 | func (s *Session) SavePoint(savepoint string) (err error) { 156 | if s.tx != nil && s.transaction == true { 157 | _, err = s.tx.Exec("savepoint " + savepoint) 158 | if err != nil { 159 | s.GetIEngin().GetLogger().Error(err.Error()) 160 | return 161 | } 162 | } 163 | return 164 | } 165 | 166 | // Commit ... 167 | func (s *Session) Commit() (err error) { 168 | if s.tx != nil { 169 | if atomic.LoadInt64(&s.tx_index) == beginStatus { 170 | s.SetTransaction(false) 171 | err = s.tx.Commit() 172 | s.tx = nil 173 | } else { 174 | atomic.AddInt64(&s.tx_index, -1) 175 | } 176 | } else { 177 | s.SetTransaction(false) 178 | } 179 | return 180 | } 181 | 182 | // Transaction ... 183 | func (s *Session) Transaction(closers ...func(ses ISession) error) (err error) { 184 | err = s.Begin() 185 | if err != nil { 186 | s.GetIEngin().GetLogger().Error(err.Error()) 187 | return err 188 | } 189 | 190 | for _, closer := range closers { 191 | err = closer(s) 192 | if err != nil { 193 | s.GetIEngin().GetLogger().Error(err.Error()) 194 | _ = s.Rollback() 195 | return 196 | } 197 | } 198 | return s.Commit() 199 | } 200 | 201 | // Query ... 202 | func (s *Session) Query(sqlstring string, args ...interface{}) (result []Data, err error) { 203 | // 记录开始时间 204 | start := time.Now() 205 | //withRunTimeContext(func() { 206 | if s.err != nil { 207 | err = s.err 208 | s.GetIEngin().GetLogger().Error(err.Error()) 209 | } 210 | // 记录sqlLog 211 | s.lastSql = fmt.Sprint(sqlstring, ", ", args) 212 | //if s.IfEnableSqlLog() { 213 | // s.sqlLogs = append(s.sqlLogs, s.lastSql) 214 | //} 215 | 216 | var stmt *sql.Stmt 217 | // 如果是事务, 则从主库中读写 218 | if s.tx == nil { 219 | stmt, err = s.slave.Prepare(sqlstring) 220 | } else { 221 | stmt, err = s.tx.Prepare(sqlstring) 222 | } 223 | 224 | if err != nil { 225 | s.GetIEngin().GetLogger().Error(err.Error()) 226 | return 227 | } 228 | 229 | defer stmt.Close() 230 | rows, err := stmt.Query(args...) 231 | if err != nil { 232 | s.GetIEngin().GetLogger().Error(err.Error()) 233 | return 234 | } 235 | 236 | // make sure we always close rows 237 | defer rows.Close() 238 | 239 | err = s.scan(rows) 240 | if err != nil { 241 | s.GetIEngin().GetLogger().Error(err.Error()) 242 | return 243 | } 244 | //}, func(duration time.Duration) { 245 | // //if duration.Seconds() > 1 { 246 | // // s.GetIEngin().GetLogger().Slow(s.LastSql(), duration) 247 | // //} else { 248 | // // s.GetIEngin().GetLogger().Sql(s.LastSql(), duration) 249 | // //} 250 | //}) 251 | 252 | timeduration := time.Since(start) 253 | //if timeduration.Seconds() > 1 { 254 | s.GetIEngin().GetLogger().Slow(s.LastSql(), timeduration) 255 | //} else { 256 | s.GetIEngin().GetLogger().Sql(s.LastSql(), timeduration) 257 | //} 258 | 259 | result = s.GetIBinder().GetBindAll() 260 | return 261 | } 262 | 263 | // Execute ... 264 | func (s *Session) Execute(sqlstring string, args ...interface{}) (rowsAffected int64, err error) { 265 | // 记录开始时间 266 | start := time.Now() 267 | //withRunTimeContext(func() { 268 | // err = s.GetIBinder().BindParse(s.GetIEngin().GetPrefix()) 269 | if s.err != nil { 270 | s.GetIEngin().GetLogger().Error(err.Error()) 271 | return 272 | } 273 | s.lastSql = fmt.Sprint(sqlstring, ", ", args) 274 | //// 记录sqlLog 275 | //if s.IfEnableSqlLog() { 276 | // s.sqlLogs = append(s.sqlLogs, s.lastSql) 277 | //} 278 | 279 | var operType = strings.ToLower(sqlstring[0:6]) 280 | if operType == "select" { 281 | err = errors.New("Execute does not allow select operations, please use Query") 282 | s.GetIEngin().GetLogger().Error(err.Error()) 283 | return 284 | } 285 | 286 | var stmt *sql.Stmt 287 | if s.tx == nil { 288 | stmt, err = s.master.Prepare(sqlstring) 289 | } else { 290 | stmt, err = s.tx.Prepare(sqlstring) 291 | } 292 | 293 | if err != nil { 294 | s.GetIEngin().GetLogger().Error(err.Error()) 295 | return 296 | } 297 | 298 | //var err error 299 | defer stmt.Close() 300 | result, err := stmt.Exec(args...) 301 | if err != nil { 302 | s.GetIEngin().GetLogger().Error(err.Error()) 303 | return 304 | } 305 | 306 | if operType == "insert" || operType == "replace" { 307 | // get last insert id 308 | lastInsertId, err := result.LastInsertId() 309 | if err == nil { 310 | s.lastInsertId = lastInsertId 311 | } else { 312 | s.GetIEngin().GetLogger().Error(err.Error()) 313 | } 314 | } 315 | // get rows affected 316 | rowsAffected, err = result.RowsAffected() 317 | timeduration := time.Since(start) 318 | //}, func(duration time.Duration) { 319 | if timeduration.Seconds() > 1 { 320 | s.GetIEngin().GetLogger().Slow(s.LastSql(), timeduration) 321 | } else { 322 | s.GetIEngin().GetLogger().Sql(s.LastSql(), timeduration) 323 | } 324 | //}) 325 | return 326 | } 327 | 328 | // LastInsertId ... 329 | func (s *Session) LastInsertId() int64 { 330 | return s.lastInsertId 331 | } 332 | 333 | // LastSql ... 334 | func (s *Session) LastSql() string { 335 | return s.lastSql 336 | } 337 | 338 | func (s *Session) scan(rows *sql.Rows) (err error) { 339 | // 如果不需要绑定, 则需要初始化一下binder 340 | if s.GetIBinder() == nil { 341 | s.SetIBinder(NewBinder()) 342 | } 343 | // 检查实多维数组还是一维数组 344 | switch s.GetBindType() { 345 | case OBJECT_STRING: 346 | err = s.scanAll(rows) 347 | case OBJECT_STRUCT, OBJECT_STRUCT_SLICE: 348 | err = s.scanStructAll(rows) 349 | //case OBJECT_MAP, OBJECT_MAP_T: 350 | // err = s.scanMap(rows, s.GetBindResult()) 351 | case OBJECT_MAP, OBJECT_MAP_T, OBJECT_MAP_SLICE, OBJECT_MAP_SLICE_T: 352 | err = s.scanMapAll(rows) 353 | case OBJECT_NIL: 354 | err = s.scanAll(rows) 355 | default: 356 | err = errors.New("Bind value error") 357 | } 358 | return 359 | } 360 | 361 | //func (s *Session) scanMap(rows *sql.Rows, dst interface{}) (err error) { 362 | // return s.scanMapAll(rows, dst) 363 | //} 364 | 365 | func (s *Session) scanMapAll(rows *sql.Rows) (err error) { 366 | var columns []string 367 | // 获取查询的所有字段 368 | if columns, err = rows.Columns(); err != nil { 369 | return 370 | } 371 | count := len(columns) 372 | 373 | for rows.Next() { 374 | // 定义要绑定的结果集 375 | values := make([]interface{}, count) 376 | scanArgs := make([]interface{}, count) 377 | for i := 0; i < count; i++ { 378 | scanArgs[i] = &values[i] 379 | } 380 | // 获取结果 381 | _ = rows.Scan(scanArgs...) 382 | 383 | // 定义预设的绑定对象 384 | //fmt.Println(reflect.TypeOf(s.GetBindResult()).Kind()) 385 | var bindResultTmp = reflect.MakeMap(reflect.Indirect(reflect.ValueOf(s.GetBindResult())).Type()) 386 | //// 定义union操作的map返回 387 | //var unionTmp = map[string]interface{}{} 388 | for i, col := range columns { 389 | var v interface{} 390 | val := values[i] 391 | if b, ok := val.([]byte); ok { 392 | v = string(b) 393 | } else { 394 | v = val 395 | } 396 | // 如果是union操作就不需要绑定数据直接返回, 否则就绑定数据 397 | //TODO 这里可能有点问题, 比如在group时, 返回的结果不止一条, 这里直接返回的就是第一条 398 | // 默认其实只是取了第一条, 满足常规的 union 操作(count,sum,max,min,avg)而已 399 | // 后边需要再行完善, 以便group时使用 400 | // 具体完善方法: 就是这里断点去掉, 不直接绑定union, 新增一个map,将结果放在map中,在方法最后统一返回 401 | if s.GetUnion() != nil { 402 | s.union = v 403 | return 404 | // 以下上通用解决方法 405 | //unionTmp[col] = v 406 | //s.union = unionTmp 407 | } else { 408 | br := reflect.Indirect(reflect.ValueOf(s.GetBindResult())) 409 | switch s.GetBindType() { 410 | case OBJECT_MAP_T, OBJECT_MAP_SLICE_T: // t.T类型 411 | // 绑定到一条数据结果对象上,方便其他地方的调用,永远存储最新一条 412 | br.SetMapIndex(reflect.ValueOf(col), reflect.ValueOf(t.New(v))) 413 | // 跟上一行干的事是一样的, 只不过防止上一行的数据被后续的数据改变, 而无法提供给下边多条数据报错的需要 414 | if s.GetBindType() == OBJECT_MAP_SLICE || s.GetBindType() == OBJECT_MAP_SLICE_T { 415 | 416 | bindResultTmp.SetMapIndex(reflect.ValueOf(col), reflect.ValueOf(t.New(v))) 417 | } 418 | default: // 普通类型map[string]interface{}, 具体代码注释参照 上一个 case 419 | br.SetMapIndex(reflect.ValueOf(col), reflect.ValueOf(v)) 420 | if s.GetBindType() == OBJECT_MAP_SLICE || s.GetBindType() == OBJECT_MAP_SLICE_T { 421 | bindResultTmp.SetMapIndex(reflect.ValueOf(col), reflect.ValueOf(v)) 422 | } 423 | } 424 | } 425 | } 426 | // 如果是union操作就不需要绑定数据直接返回, 否则就绑定数据 427 | if s.GetUnion() == nil { 428 | // 如果是多条数据集, 就插入到对应的结果集slice上 429 | if s.GetBindType() == OBJECT_MAP_SLICE || s.GetBindType() == OBJECT_MAP_SLICE_T { 430 | s.GetBindResultSlice().Set(reflect.Append(s.GetBindResultSlice(), bindResultTmp)) 431 | } 432 | } 433 | } 434 | return 435 | } 436 | 437 | // ScanAll scans all sql result rows into a slice of structs. 438 | // It reads all rows and closes rows when finished. 439 | // dst should be a pointer to a slice of the appropriate type. 440 | // The new results will be appended to any existing data in dst. 441 | func (s *Session) scanStructAll(rows *sql.Rows) error { 442 | // check if there is data waiting 443 | //if !rows.Next() { 444 | // if err := rows.Err(); err != nil { 445 | // s.GetIEngin().GetLogger().Error(err.Error()) 446 | // return err 447 | // } 448 | // return sql.ErrNoRows 449 | //} 450 | var sfs = structForScan(s.GetBindResult()) 451 | for rows.Next() { 452 | if s.GetUnion() != nil { 453 | var union interface{} 454 | err := rows.Scan(&union) 455 | if err != nil { 456 | s.GetIEngin().GetLogger().Error(err.Error()) 457 | return err 458 | } 459 | s.union = union 460 | return err 461 | } 462 | // scan it 463 | //fmt.Printf("%#v \n",structForScan(s.GetBindResult())) 464 | err := rows.Scan(sfs...) 465 | if err != nil { 466 | s.GetIEngin().GetLogger().Error(err.Error()) 467 | return err 468 | } 469 | // 如果是union操作就不需要绑定数据直接返回, 否则就绑定数据 470 | if s.GetUnion() == nil { 471 | // 如果是多条数据集, 就插入到对应的结果集slice上 472 | if s.GetBindType() == OBJECT_STRUCT_SLICE { 473 | // add to the result slice 474 | s.GetBindResultSlice().Set(reflect.Append(s.GetBindResultSlice(), 475 | reflect.Indirect(reflect.ValueOf(s.GetBindResult())))) 476 | } 477 | } 478 | } 479 | 480 | return rows.Err() 481 | } 482 | 483 | func (s *Session) scanAll(rows *sql.Rows) (err error) { 484 | var columns []string 485 | // 获取查询的所有字段 486 | if columns, err = rows.Columns(); err != nil { 487 | return 488 | } 489 | count := len(columns) 490 | 491 | var result = []Data{} 492 | for rows.Next() { 493 | // 定义要绑定的结果集 494 | values := make([]interface{}, count) 495 | scanArgs := make([]interface{}, count) 496 | for i := 0; i < count; i++ { 497 | scanArgs[i] = &values[i] 498 | } 499 | // 获取结果 500 | _ = rows.Scan(scanArgs...) 501 | 502 | // 定义预设的绑定对象 503 | var resultTmp = Data{} 504 | //// 定义union操作的map返回 505 | //var unionTmp = map[string]interface{}{} 506 | for i, col := range columns { 507 | var v interface{} 508 | val := values[i] 509 | if b, ok := val.([]byte); ok { 510 | v = string(b) 511 | } else { 512 | v = val 513 | } 514 | if s.GetUnion() != nil { 515 | s.union = v 516 | return 517 | // 以下上通用解决方法 518 | //unionTmp[col] = v 519 | //s.union = unionTmp 520 | } 521 | resultTmp[col] = v 522 | } 523 | result = append(result, resultTmp) 524 | } 525 | s.IBinder.SetBindAll(result) 526 | return 527 | } 528 | 529 | // SetUnion ... 530 | func (s *Session) SetUnion(u interface{}) { 531 | s.union = u 532 | } 533 | 534 | // GetUnion ... 535 | func (s *Session) GetUnion() interface{} { 536 | return s.union 537 | } 538 | 539 | // SetTransaction ... 540 | func (s *Session) SetTransaction(b bool) { 541 | s.transaction = b 542 | } 543 | 544 | // GetTransaction 提供给 orm 使用的, 方便reset操作 545 | func (s *Session) GetTransaction() bool { 546 | return s.transaction 547 | } 548 | -------------------------------------------------------------------------------- /orm_query.go: -------------------------------------------------------------------------------- 1 | package gorose 2 | 3 | import ( 4 | "errors" 5 | "github.com/gohouse/t" 6 | "math" 7 | "reflect" 8 | "strings" 9 | "sync" 10 | ) 11 | 12 | // Select : select one or more rows , relation limit set 13 | func (dba *Orm) Select() error { 14 | switch dba.GetIBinder().GetBindType() { 15 | case OBJECT_STRUCT, OBJECT_MAP, OBJECT_MAP_T: 16 | dba.Limit(1) 17 | } 18 | // 构建sql 19 | sqlStr, args, err := dba.BuildSql() 20 | if err != nil { 21 | return err 22 | } 23 | // 执行查询 24 | _, err = dba.GetISession().Query(sqlStr, args...) 25 | return err 26 | } 27 | 28 | func (dba *Orm) Scan(scan_to_struct interface{}) error { 29 | dstVal := reflect.ValueOf(scan_to_struct) 30 | sliceVal := reflect.Indirect(dstVal) 31 | switch sliceVal.Kind() { 32 | case reflect.Struct: // struct 33 | dba.Limit(1) 34 | sqlStr, args, err := dba.BuildSql() 35 | if err != nil { 36 | return err 37 | } 38 | dba.GetIBinder().SetBindType(OBJECT_STRUCT) 39 | dba.GetIBinder().SetBindResult(scan_to_struct) 40 | if len(dba.GetIBinder().GetBindFields()) == 0 { 41 | dba.GetIBinder().SetBindFields(getTagName(dba.GetIBinder().GetBindResult(), TAGNAME)) 42 | } 43 | switch dstVal.Kind() { 44 | case reflect.Ptr, reflect.Struct: 45 | break 46 | default: 47 | return errors.New("传入的对象有误,示例:var user User,传入 &user{}") 48 | } 49 | _, err = dba.GetISession().Query(sqlStr, args...) 50 | return err 51 | 52 | case reflect.Slice: 53 | eltType := sliceVal.Type().Elem() 54 | switch eltType.Kind() { 55 | case reflect.Struct: 56 | sqlStr, args, err := dba.BuildSql() 57 | if err != nil { 58 | return err 59 | } 60 | dba.GetIBinder().SetBindType(OBJECT_STRUCT_SLICE) 61 | br := reflect.New(eltType) 62 | dba.GetIBinder().SetBindResult(br.Interface()) 63 | dba.GetIBinder().SetBindResultSlice(sliceVal) 64 | if len(dba.GetIBinder().GetBindFields()) == 0 { 65 | dba.GetIBinder().SetBindFields(getTagName(dba.GetIBinder().GetBindResult(), TAGNAME)) 66 | } 67 | switch dstVal.Kind() { 68 | case reflect.Ptr, reflect.Struct: 69 | break 70 | default: 71 | return errors.New("传入的对象有误,示例:var user User,传入 &user{}") 72 | } 73 | _, err = dba.GetISession().Query(sqlStr, args...) 74 | return err 75 | 76 | default: 77 | return errors.New("传入[]struct{}将会解析成多条,类似Get方法,注意需要传入指针值,例如传入:&User{},而不是:User{},不要用这个方法传入Map") 78 | 79 | } 80 | 81 | default: 82 | return errors.New("传入struct{}可以解析单条,类似Find方法,传入[]struct{}将会解析成多条,类似Get方法,注意需要传入指针值,例如传入:&User{},而不是:User{},不要用这个方法传入Map") 83 | } 84 | } 85 | 86 | // First : select one row , relation limit set 87 | func (dba *Orm) First() (result Data, err error) { 88 | dba.GetIBinder().SetBindType(OBJECT_STRING) 89 | err = dba.Limit(1).Select() 90 | if err != nil { 91 | return 92 | } 93 | res := dba.GetISession().GetBindAll() 94 | if len(res) > 0 { 95 | result = res[0] 96 | } 97 | return 98 | } 99 | 100 | func (dba *Orm) Find() (result Data, err error) { 101 | return dba.First() 102 | } 103 | 104 | // Get : select more rows , relation limit set 105 | func (dba *Orm) Get() (result []Data, err error) { 106 | dba.GetIBinder().SetBindType(OBJECT_STRING) 107 | tabname := dba.GetISession().GetIBinder().GetBindName() 108 | prefix := dba.GetISession().GetIBinder().GetBindPrefix() 109 | tabname2 := strings.TrimPrefix(tabname, prefix) 110 | dba.ResetTable() 111 | dba.Table(tabname2) 112 | err = dba.Select() 113 | result = dba.GetISession().GetBindAll() 114 | return 115 | } 116 | 117 | // Count : select count rows 118 | func (dba *Orm) Count(args ...string) (int64, error) { 119 | fields := "*" 120 | if len(args) > 0 { 121 | fields = args[0] 122 | } 123 | count, err := dba._unionBuild("COUNT", fields) 124 | if count == nil { 125 | return 0, err 126 | } 127 | return t.New(count).Int64(), err 128 | } 129 | 130 | // Counts is a Nested-Count function in order to solve the wrong number output when using count and groupBy in the same time : select count (select count.....) 131 | func (dba *Orm) Counts(count_fileds ...string) (int64, error) { 132 | if dba.group == "" { 133 | return dba.Count(count_fileds...) 134 | } else { 135 | //temporary remove order in case of the renamed order not found which cause error sql 136 | order := dba.order 137 | dba.order = "" 138 | dba.limit = 0 139 | if len(dba.fields) < 1 { 140 | dba.fields = []string{"1"} 141 | } 142 | //fmt.Println(dba.fields) 143 | //dba.fields = []string{"count(DISTINCT " + dba.group + ") as count"} 144 | // 构建sql 145 | sqls, args, err := dba.BuildSql() 146 | //fmt.Println(sqls) 147 | if err != nil { 148 | return 0, err 149 | } 150 | dba.order = order 151 | total_number, err := dba.Query(`SELECT COUNT(*) as COUNT from(`+sqls+`) as COUNTS`, args...) 152 | if err != nil { 153 | return 0, err 154 | } 155 | //fmt.Println(dba.LastSql()) 156 | if len(total_number) < 1 { 157 | return 0, err 158 | } 159 | return t.New(total_number[0]["COUNT"]).Int64(), err 160 | } 161 | } 162 | 163 | // Sum : select sum field 164 | func (dba *Orm) Sum(sum string) (interface{}, error) { 165 | return dba._unionBuild("sum", sum) 166 | } 167 | 168 | // Avg : select avg field 169 | func (dba *Orm) Avg(avg string) (interface{}, error) { 170 | return dba._unionBuild("avg", avg) 171 | } 172 | 173 | // Max : select max field 174 | func (dba *Orm) Max(max string) (interface{}, error) { 175 | return dba._unionBuild("max", max) 176 | } 177 | 178 | // Min : select min field 179 | func (dba *Orm) Min(min string) (interface{}, error) { 180 | return dba._unionBuild("min", min) 181 | } 182 | 183 | // _unionBuild : build union select real 184 | func (dba *Orm) _unionBuild(union, field string) (interface{}, error) { 185 | fields := union + "(" + field + ") as " + union 186 | dba.fields = []string{fields} 187 | 188 | res, err := dba.First() 189 | if r, ok := res[union]; ok { 190 | return r, err 191 | } 192 | return 0, err 193 | } 194 | 195 | //func (dba *Orm) _unionBuild_bak(union, field string) (interface{}, error) { 196 | // var tmp interface{} 197 | // 198 | // dba.union = union + "(" + field + ") as " + union 199 | // // 缓存fields字段,暂时由union占用 200 | // fieldsTmp := dba.fields 201 | // dba.fields = []string{dba.union} 202 | // dba.GetISession().SetUnion(true) 203 | // 204 | // // 构建sql 205 | // sqls, args, err := dba.BuildSql() 206 | // if err != nil { 207 | // return tmp, err 208 | // } 209 | // 210 | // // 执行查询 211 | // _, err = dba.GetISession().Query(sqls, args...) 212 | // if err != nil { 213 | // return tmp, err 214 | // } 215 | // 216 | // // 重置union, 防止复用的时候感染 217 | // dba.union = "" 218 | // // 返还fields 219 | // dba.fields = fieldsTmp 220 | // 221 | // // 语法糖获取union值 222 | // if dba.GetISession().GetUnion() != nil { 223 | // tmp = dba.GetISession().GetUnion() 224 | // // 获取之后, 释放掉 225 | // dba.GetISession().SetUnion(nil) 226 | // } 227 | // 228 | // return tmp, nil 229 | //} 230 | 231 | // Pluck 获取一列数据, 第二个字段可以指定另一个字段的值作为这一列数据的key 232 | func (dba *Orm) Pluck(field string, fieldKey ...string) (v interface{}, err error) { 233 | var resMap = make(map[interface{}]interface{}, 0) 234 | var resSlice = make([]interface{}, 0) 235 | 236 | res, err := dba.Get() 237 | 238 | if err != nil { 239 | return 240 | } 241 | 242 | if len(res) > 0 { 243 | for _, val := range res { 244 | if len(fieldKey) > 0 { 245 | resMap[val[fieldKey[0]]] = val[field] 246 | } else { 247 | resSlice = append(resSlice, val[field]) 248 | } 249 | } 250 | } 251 | if len(fieldKey) > 0 { 252 | v = resMap 253 | } else { 254 | v = resSlice 255 | } 256 | return 257 | } 258 | 259 | // Pluck_bak ... 260 | func (dba *Orm) Pluck_bak(field string, fieldKey ...string) (v interface{}, err error) { 261 | var binder = dba.GetISession().GetIBinder() 262 | var resMap = make(map[interface{}]interface{}, 0) 263 | var resSlice = make([]interface{}, 0) 264 | 265 | err = dba.Select() 266 | if err != nil { 267 | return 268 | } 269 | 270 | switch binder.GetBindType() { 271 | case OBJECT_MAP, OBJECT_MAP_T, OBJECT_STRUCT: // row 272 | var key, val interface{} 273 | if len(fieldKey) > 0 { 274 | key, err = dba.Value(fieldKey[0]) 275 | if err != nil { 276 | return 277 | } 278 | val, err = dba.Value(field) 279 | if err != nil { 280 | return 281 | } 282 | resMap[key] = val 283 | } else { 284 | v, err = dba.Value(field) 285 | if err != nil { 286 | return 287 | } 288 | } 289 | case OBJECT_MAP_SLICE, OBJECT_MAP_SLICE_T: 290 | for _, item := range t.New(binder.GetBindResultSlice().Interface()).Slice() { 291 | val := item.MapInterfaceT() 292 | if len(fieldKey) > 0 { 293 | resMap[val[fieldKey[0]].Interface()] = val[field].Interface() 294 | } else { 295 | resSlice = append(resSlice, val[field].Interface()) 296 | } 297 | } 298 | case OBJECT_STRUCT_SLICE: // rows 299 | var brs = binder.GetBindResultSlice() 300 | for i := 0; i < brs.Len(); i++ { 301 | val := reflect.Indirect(brs.Index(i)) 302 | if len(fieldKey) > 0 { 303 | mapkey := dba._valueFromStruct(val, fieldKey[0]) 304 | mapVal := dba._valueFromStruct(val, field) 305 | resMap[mapkey] = mapVal 306 | } else { 307 | resSlice = append(resSlice, dba._valueFromStruct(val, field)) 308 | } 309 | } 310 | case OBJECT_STRING: 311 | res := dba.GetISession().GetBindAll() 312 | if len(res) > 0 { 313 | for _, val := range res { 314 | if len(fieldKey) > 0 { 315 | resMap[val[fieldKey[0]]] = val[field] 316 | } else { 317 | resSlice = append(resSlice, val[field]) 318 | } 319 | } 320 | } 321 | } 322 | if len(fieldKey) > 0 { 323 | v = resMap 324 | } else { 325 | v = resSlice 326 | } 327 | return 328 | } 329 | 330 | // Type is get a row of a field value 331 | func (dba *Orm) Value(field string) (v interface{}, err error) { 332 | res, err := dba.First() 333 | if v, ok := res[field]; ok { 334 | return v, err 335 | } 336 | return 337 | } 338 | 339 | // Type is get a row of a field value 340 | func (dba *Orm) Column(field string) (v []interface{}, err error) { 341 | dba.fields = []string{field} 342 | res, err := dba.Get() 343 | if err != nil { 344 | return 345 | } 346 | v = []interface{}{} 347 | for _, re := range res { 348 | if vt, ok := re[field]; ok { 349 | v = append(v, vt) 350 | } 351 | } 352 | return 353 | } 354 | 355 | // Value_bak ... 356 | func (dba *Orm) Value_bak(field string) (v interface{}, err error) { 357 | dba.Limit(1) 358 | err = dba.Select() 359 | if err != nil { 360 | return 361 | } 362 | var binder = dba.GetISession().GetIBinder() 363 | switch binder.GetBindType() { 364 | case OBJECT_MAP, OBJECT_MAP_SLICE, OBJECT_MAP_SLICE_T, OBJECT_MAP_T: 365 | v = reflect.ValueOf(binder.GetBindResult()).MapIndex(reflect.ValueOf(field)).Interface() 366 | case OBJECT_STRUCT, OBJECT_STRUCT_SLICE: 367 | bindResult := reflect.Indirect(reflect.ValueOf(binder.GetBindResult())) 368 | v = dba._valueFromStruct(bindResult, field) 369 | case OBJECT_STRING: 370 | res := dba.GetISession().GetBindAll() 371 | if len(res) > 0 { 372 | v = res[0][field] 373 | } 374 | } 375 | return 376 | } 377 | func (dba *Orm) _valueFromStruct(bindResult reflect.Value, field string) (v interface{}) { 378 | ostype := bindResult.Type() 379 | for i := 0; i < ostype.NumField(); i++ { 380 | tag := ostype.Field(i).Tag.Get(TAGNAME) 381 | if tag == field || ostype.Field(i).Name == field { 382 | v = bindResult.FieldByName(ostype.Field(i).Name).Interface() 383 | } 384 | } 385 | return 386 | } 387 | 388 | // Chunk : 分块处理数据,当要处理很多数据的时候, 我不需要知道具体是多少数据, 我只需要每次取limit条数据, 389 | // 然后不断的增加offset去取更多数据, 从而达到分块处理更多数据的目的 390 | func (dba *Orm) Chunk(limit int, callback func([]Data) error) (err error) { 391 | var page = 1 392 | var tabname = dba.GetISession().GetIBinder().GetBindName() 393 | prefix := dba.GetISession().GetIBinder().GetBindPrefix() 394 | tabname2 := strings.TrimPrefix(tabname, prefix) 395 | where, fields, group := dba.where, dba.fields, dba.group 396 | 397 | // 先执行一条看看是否报错, 同时设置指定的limit, offset 398 | dba.Table(tabname2).Limit(limit).Page(page) 399 | if err = dba.Select(); err != nil { 400 | return 401 | } 402 | result := dba.GetBindAll() 403 | for len(result) > 0 { 404 | if err = callback(result); err != nil { 405 | break 406 | } 407 | page++ 408 | // 清理绑定数据, 进行下一次操作, 因为绑定数据是每一次执行的时候都会解析并保存的 409 | // 而第二次以后执行的, 都会再次解析并保存, 数据结构是slice, 故会累积起来 410 | dba.ClearBindValues() 411 | dba.where, dba.fields, dba.group = where, fields, group 412 | dba.Table(tabname2).Limit(limit).Page(page) 413 | if err = dba.Select(); err != nil { 414 | break 415 | } 416 | result = dba.GetBindAll() 417 | } 418 | return 419 | } 420 | 421 | // ChunkWG : ChunkWG是保留Chunk的使用方法的基础上,新增多线程读取&多线程执行的方式,注意onetime_exec_thread不宜过多,推荐4,不宜过大因为采用的是盲读的方法,详情请参考github-wiki的介绍部分 422 | // 原理与PaginatorWG方法类似,保留了事务隔离性并且加入了 423 | func (dba *Orm) ChunkWG(onetime_exec_thread int, limit int, callback func([]Data) error) (err error) { 424 | if onetime_exec_thread <= 0 { 425 | onetime_exec_thread = 1 426 | } 427 | if onetime_exec_thread > 20 { 428 | onetime_exec_thread = 20 429 | } 430 | var page = 1 431 | tabname := dba.GetISession().GetIBinder().GetBindName() 432 | prefix := dba.GetISession().GetIBinder().GetBindPrefix() 433 | tabname2 := strings.TrimPrefix(tabname, prefix) 434 | where, fields, group := dba.where, dba.fields, dba.group 435 | dba.Table(tabname2).Limit(limit).Page(page) 436 | 437 | if err = dba.Select(); err != nil { 438 | return 439 | } 440 | result := dba.GetBindAll() 441 | if len(result) < 1 { 442 | return 443 | } 444 | if err = callback(result); err != nil { 445 | return 446 | } 447 | continue_run := true 448 | for continue_run { 449 | var mp sync.Map 450 | for i := 0; i < onetime_exec_thread; i++ { 451 | page++ 452 | dba.ClearBindValues() 453 | dba.Table(tabname2). 454 | Limit(limit). 455 | Page(page) 456 | dba.where, dba.fields, dba.group = where, fields, group 457 | sqlStr, args, err := dba.BuildSql() 458 | if err != nil { 459 | continue_run = false 460 | return err 461 | } 462 | mp.Store(sqlStr, args) 463 | } 464 | var wg sync.WaitGroup 465 | wg.Add(onetime_exec_thread) 466 | mp.Range(func(sqlStr, args interface{}) bool { 467 | go func(db *Orm, sql string, arg []interface{}) { 468 | _result, _err := db.Query(sql, arg...) 469 | if _err != nil { 470 | wg.Done() 471 | continue_run = false 472 | logger.Error(_err.Error()) 473 | return 474 | } 475 | if len(_result) < 1 { 476 | wg.Done() 477 | continue_run = false 478 | return 479 | } 480 | if _err = callback(_result); _err != nil { 481 | wg.Done() 482 | continue_run = false 483 | return 484 | } 485 | wg.Done() 486 | }(dba, sqlStr.(string), args.([]interface{})) 487 | return true 488 | }) 489 | wg.Wait() 490 | } 491 | return 492 | } 493 | 494 | // ChunkStruct : 同Chunk,只不过不用返回map, 而是绑定数据到传入的对象上 495 | // 这里一定要传入绑定struct 496 | func (dba *Orm) ChunkStruct(limit int, callback func() error) (err error) { 497 | var page = 0 498 | //var tableName = dba.GetISession().GetIBinder().GetBindName() 499 | // 先执行一条看看是否报错, 同时设置指定的limit, offset 500 | err = dba.Limit(limit).Offset(page * limit).Select() 501 | if err != nil { 502 | return 503 | } 504 | switch dba.GetIBinder().GetBindType() { 505 | case OBJECT_STRUCT, OBJECT_MAP, OBJECT_MAP_T: 506 | var ibinder = dba.GetIBinder() 507 | var result = ibinder.GetBindResult() 508 | for result != nil { 509 | if err = callback(); err != nil { 510 | break 511 | } 512 | page++ 513 | // 清空结果 514 | //result = nil 515 | var rfRes = reflect.ValueOf(result) 516 | rfRes.Set(reflect.Zero(rfRes.Type())) 517 | // 清理绑定数据, 进行下一次操作, 因为绑定数据是每一次执行的时候都会解析并保存的 518 | // 而第二次以后执行的, 都会再次解析并保存, 数据结构是slice, 故会累积起来 519 | dba.ClearBindValues() 520 | _ = dba.Table(ibinder.GetBindOrigin()).Offset(page * limit).Select() 521 | result = dba.GetIBinder().GetBindResultSlice() 522 | } 523 | case OBJECT_STRUCT_SLICE, OBJECT_MAP_SLICE, OBJECT_MAP_SLICE_T: 524 | var ibinder = dba.GetIBinder() 525 | var result = ibinder.GetBindResultSlice() 526 | for result.Interface() != nil { 527 | if err = callback(); err != nil { 528 | break 529 | } 530 | page++ 531 | // 清空结果 532 | result.Set(result.Slice(0, 0)) 533 | // 清理绑定数据, 进行下一次操作, 因为绑定数据是每一次执行的时候都会解析并保存的 534 | // 而第二次以后执行的, 都会再次解析并保存, 数据结构是slice, 故会累积起来 535 | dba.ClearBindValues() 536 | _ = dba.Table(ibinder.GetBindOrigin()).Offset(page * limit).Select() 537 | result = dba.GetIBinder().GetBindResultSlice() 538 | } 539 | } 540 | return 541 | } 542 | 543 | // Loop : 同chunk, 不过, 这个是循环的取前limit条数据, 为什么是循环取这些数据呢 544 | // 因为, 我们考虑到一种情况, 那就是where条件如果刚好是要修改的值, 545 | // 那么最后的修改结果因为offset的原因, 只会修改一半, 比如: 546 | // DB().Where("age", 18) ===> DB().Data(gorose.Data{"age":19}).Where().Update() 547 | func (dba *Orm) Loop(limit int, callback func([]Data) error) (err error) { 548 | var page = 0 549 | var tabname = dba.GetISession().GetIBinder().GetBindName() 550 | prefix := dba.GetISession().GetIBinder().GetBindPrefix() 551 | tabname2 := strings.TrimPrefix(tabname, prefix) 552 | // 先执行一条看看是否报错, 同时设置指定的limit 553 | result, err := dba.Table(tabname2).Limit(limit).Get() 554 | if err != nil { 555 | return 556 | } 557 | for len(result) > 0 { 558 | if err = callback(result); err != nil { 559 | break 560 | } 561 | page++ 562 | // 同chunk 563 | dba.ClearBindValues() 564 | result, _ = dba.Get() 565 | } 566 | return 567 | } 568 | 569 | // Paginate 自动分页 570 | // @param limit 每页展示数量 571 | // @param current_page 当前第几页, 从1开始 572 | // 以下是laravel的Paginate返回示例 573 | // 574 | // { 575 | // "total": 50, 576 | // "per_page": 15, 577 | // "current_page": 1, 578 | // "lastPage": 4, 579 | // "first_page_url": "http://laravel.app?page=1", 580 | // "lastPage_url": "http://laravel.app?page=4", 581 | // "nextPage_url": "http://laravel.app?page=2", 582 | // "prevPage_url": null, 583 | // "path": "http://laravel.app", 584 | // "from": 1, 585 | // "to": 15, 586 | // "data":[ 587 | // { 588 | // // Result Object 589 | // }, 590 | // { 591 | // // Result Object 592 | // } 593 | // ] 594 | // } 595 | func (dba *Orm) Paginate(page ...int) (res Data, err error) { 596 | if len(page) > 0 { 597 | dba.Page(page[0]) 598 | } 599 | var limit = dba.GetLimit() 600 | if limit == 0 { 601 | limit = 15 602 | } 603 | var offset = dba.GetOffset() 604 | var currentPage = int(math.Ceil(float64(offset+1) / float64(limit))) 605 | //dba.ResetUnion() 606 | // 统计总量 607 | tabname := dba.GetISession().GetIBinder().GetBindName() 608 | prefix := dba.GetISession().GetIBinder().GetBindPrefix() 609 | where := dba.where 610 | resData, err := dba.Get() 611 | //fmt.Println(dba.LastSql()) 612 | if err != nil { 613 | return 614 | } 615 | 616 | if resData == nil { 617 | resData = []Data{} 618 | } 619 | dba.offset = 0 620 | dba.GetISession().GetIBinder().SetBindName(tabname) 621 | dba.GetISession().GetIBinder().SetBindPrefix(prefix) 622 | dba.where = where 623 | count, err := dba.Count() 624 | //fmt.Println(dba.LastSql()) 625 | if err != nil { 626 | return 627 | } 628 | var lastPage = int(math.Ceil(float64(count) / float64(limit))) 629 | var nextPage = currentPage + 1 630 | var prevPage = currentPage - 1 631 | // 获取结果 632 | 633 | res = Data{ 634 | "total": count, 635 | "per_page": limit, 636 | "current_page": currentPage, 637 | "last_page": lastPage, 638 | "first_page_url": 1, 639 | "last_page_url": lastPage, 640 | "next_page_url": If(nextPage > lastPage, nil, nextPage), 641 | "prev_page_url": If(prevPage < 1, nil, prevPage), 642 | //"data": dba.GetISession().GetBindAll(), 643 | "data": resData, 644 | } 645 | return 646 | } 647 | 648 | // Paginate 自动分页 649 | // @param limit 每页展示数量 650 | // @param current_page 当前第几页, 从1开始 651 | // 以下是laravel的Paginate返回示例 652 | // 653 | // { 654 | // "total": 50, 655 | // "per_page": 15, 656 | // "current_page": 1, 657 | // "lastPage": 4, 658 | // "first_page_url": "http://laravel.app?page=1", 659 | // "lastPage_url": "http://laravel.app?page=4", 660 | // "nextPage_url": "http://laravel.app?page=2", 661 | // "prevPage_url": null, 662 | // "path": "http://laravel.app", 663 | // "from": 1, 664 | // "to": 15, 665 | // "data":[ 666 | // { 667 | // // Result Object 668 | // }, 669 | // { 670 | // // Result Object 671 | // } 672 | // ] 673 | // } 674 | func (dba *Orm) Paginator(page ...int) (res Paginate, err error) { 675 | if len(page) > 0 { 676 | dba.Page(page[0]) 677 | } 678 | var limit = dba.GetLimit() 679 | if limit == 0 { 680 | limit = 15 681 | } 682 | var offset = dba.GetOffset() 683 | var currentPage = int(math.Ceil(float64(offset+1) / float64(limit))) 684 | //dba.ResetUnion() 685 | // 统计总量 686 | tabname := dba.GetISession().GetIBinder().GetBindName() 687 | prefix := dba.GetISession().GetIBinder().GetBindPrefix() 688 | where := dba.where 689 | fields := dba.fields 690 | resData, err1 := dba.Get() 691 | //fmt.Println(dba.LastSql()) 692 | if err1 != nil { 693 | err = err1 694 | return 695 | } 696 | dba.offset = 0 697 | dba.fields = fields 698 | dba.GetISession().GetIBinder().SetBindName(tabname) 699 | dba.GetISession().GetIBinder().SetBindPrefix(prefix) 700 | dba.where = where 701 | count, err2 := dba.Counts() 702 | if err2 != nil { 703 | err = err2 704 | return 705 | } 706 | //fmt.Println(dba.LastSql()) 707 | 708 | var lastPage = int(math.Ceil(float64(count) / float64(limit))) 709 | var nextPage = currentPage + 1 710 | var prevPage = currentPage - 1 711 | // 获取结果 712 | 713 | res = Paginate{ 714 | Total: count, 715 | PerPage: limit, 716 | CurrentPage: currentPage, 717 | LastPage: lastPage, 718 | FirstPageUrl: 1, 719 | LastPageUrl: lastPage, 720 | NextPageUrl: If(nextPage > lastPage, nil, nextPage), 721 | PrevPageUrl: If(prevPage < 1, nil, prevPage), 722 | //"data": dba.GetISession().GetBindAll(), 723 | Data: resData, 724 | } 725 | return 726 | } 727 | 728 | // PaginatorWG this is a waitgroup Paginator function might be 20-50% faster than the original one,975ms->756ms 729 | func (dba *Orm) PaginatorWG(page ...int) (res Paginate, err error) { 730 | 731 | if len(page) > 0 { 732 | dba.Page(page[0]) 733 | } 734 | var limit = dba.GetLimit() 735 | if limit == 0 { 736 | limit = 15 737 | } 738 | var offset = dba.GetOffset() 739 | var currentPage = int(math.Ceil(float64(offset+1) / float64(limit))) 740 | //dba.ResetUnion() 741 | // 统计总量 742 | tabname := dba.GetISession().GetIBinder().GetBindName() 743 | prefix := dba.GetISession().GetIBinder().GetBindPrefix() 744 | where := dba.where 745 | fields := dba.fields 746 | 747 | var wg sync.WaitGroup 748 | wg.Add(2) 749 | 750 | var resData []Data 751 | var err1 error 752 | 753 | dba.GetIBinder().SetBindType(OBJECT_STRING) 754 | dba.ResetTable() 755 | dba.Table(strings.TrimPrefix(tabname, prefix)) 756 | sqlStr, args, err := dba.BuildSql() 757 | if err != nil { 758 | return 759 | } 760 | go func(db *Orm, data *[]Data, errs1 *error, sqls *string, ags *[]interface{}) { 761 | // 执行查询 762 | *data, *errs1 = db.Query(*sqls, *ags...) 763 | wg.Done() 764 | }(dba, &resData, &err1, &sqlStr, &args) 765 | 766 | var count int64 767 | var err2 error 768 | go func(db *Orm, c *int64, errs2 *error) { 769 | db.offset = 0 770 | new_fields := []string{} 771 | for _, field := range fields { 772 | field_low := strings.ToLower(field) 773 | if strings.Contains(field_low, "count(") { 774 | new_fields = append(new_fields, field) 775 | break 776 | } 777 | if strings.Contains(field_low, "sum(") { 778 | new_fields = append(new_fields, field) 779 | break 780 | } 781 | if strings.Contains(field_low, "max(") { 782 | new_fields = append(new_fields, field) 783 | break 784 | } 785 | if strings.Contains(field_low, "min(") { 786 | new_fields = append(new_fields, field) 787 | break 788 | } 789 | if strings.Contains(field_low, "avg(") { 790 | new_fields = append(new_fields, field) 791 | break 792 | } 793 | } 794 | db.fields = new_fields 795 | db.GetISession().GetIBinder().SetBindName(tabname) 796 | db.GetISession().GetIBinder().SetBindPrefix(prefix) 797 | db.where = where 798 | *c, *errs2 = db.Counts() 799 | if *errs2 != nil { 800 | wg.Done() 801 | return 802 | } 803 | wg.Done() 804 | }(dba, &count, &err2) 805 | 806 | wg.Wait() 807 | if err1 != nil { 808 | err = err1 809 | return 810 | } 811 | if err2 != nil { 812 | err = err2 813 | return 814 | } 815 | 816 | //fmt.Println(dba.LastSql()) 817 | 818 | var lastPage = int(math.Ceil(float64(count) / float64(limit))) 819 | var nextPage = currentPage + 1 820 | var prevPage = currentPage - 1 821 | // 获取结果 822 | 823 | res = Paginate{ 824 | Total: count, 825 | PerPage: limit, 826 | CurrentPage: currentPage, 827 | LastPage: lastPage, 828 | FirstPageUrl: 1, 829 | LastPageUrl: lastPage, 830 | NextPageUrl: If(nextPage > lastPage, nil, nextPage), 831 | PrevPageUrl: If(prevPage < 1, nil, prevPage), 832 | //"data": dba.GetISession().GetBindAll(), 833 | Data: resData, 834 | } 835 | return 836 | } 837 | --------------------------------------------------------------------------------