├── 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 | - 
11 | - 新版:
12 | - 
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 | [](https://godoc.org/github.com/tobycroft/gorose-pro)
4 | [](https://goreportcard.com/report/github.com/tobycroft/gorose-pro)
5 | [](https://github.com/tobycroft/gorose-pro/releases/latest)
6 | 
7 | 
8 |
9 |
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 | [](https://starchart.cc/tobycroft/gorose-pro)
185 |
186 |
187 |
--------------------------------------------------------------------------------
/README_en.md:
--------------------------------------------------------------------------------
1 | # GoRose-ORM-Pro A Free Go MySQL ORM
2 |
3 | [](https://godoc.org/github.com/tobycroft/gorose-pro)
4 | [](https://goreportcard.com/report/github.com/tobycroft/gorose-pro)
5 | [](https://github.com/tobycroft/gorose-pro/releases/latest)
6 | [](https://gitter.im/gorose-pro/wechat)
7 | 
8 | 
9 |
10 | 94537310
11 |
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 | [](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 |
--------------------------------------------------------------------------------