package
0.0.0-20210414064243-bac8e5f48e05
Repository: https://github.com/dawei101/gor.git
Documentation: pkg.go.dev

# README

rsql

基于 sqlxgosql

使用

Quick start!

import (
    "github.com/dawei101/gor/rsql"
)

func main(){
    rsql.QueryRowx("select * from users where id = 1")
}

数据库的连接基于下的封装,默认使用default connection, 如果需要使用其他connection,可以指定

// default db connection
rsql.Queryx("select * from users")

// appcourse db connection
rsql.Use("appcourse").Queryx("select * from users")

rsql包裹一层sqlx,所以可以直接使用sqlx的相关 function

使用sqlx的原始方法, 请查看 https://github.com/jmoiron/sqlx

//Exec
rsql.Exec("insert into users(name,email,created_at,updated_at) value(?,?,?,?)","test","[email protected]",time.Now(),time.Now())

//Queryx
rows,err := rsql.Queryx("select * from users")
for rows.Next() {
    user := &User{}
    err = rows.StructScan(user)
}
rows.Close()

//QueryRowx
user := &User{}
err := rsql.QueryRowx("select * from users where id = ?",1).StructScan(user)

//Get
user := &User{}
err := rsql.Get(user,"select * from users where id = ?",1)

//Select
users := make([]User)
err := rsql.Select(&users,"select * from users")

//Change database
db := rsql.Use("test")
db.Queryx("select * from tests")

你可以使用其他database的connection

rsql.Use("appcourse").Queryx("select * from users")

因此你所有使用的方法都是基于 connection apocourse

使用结构体

Model interface 的定义

type IModel interface {
	TableName() string
	PK() string
}

结构体demo

type User struct {
	Id        int       `db:"id"`
	Name      string    `db:"name"`
	Email     string    `db:"email"`
	Status    int       `db:"status"`
	CreatedAt time.Time `db:"created_at"`
	UpdatedAt time.Time `db:"updated_at"`
}

func (u *User) TableName() string {
	return "users"
}

func (u *User) PK() string {
	return "id" // 返回table中的field名
}

//Get
user := &User{}
rsql.Model(user).Where("id=?",1).Get()

//All
user := make([]User,0)
rsql.Model(&user).All()

//Create and auto set CreatedAt
rsql.Model(&User{Name:"test",Email:"[email protected]"}).Create()

//Update
rsql.Model(&User{Name:"test2",Email:"[email protected]"}).Where("id=?",1).Update()

//Delete
rsql.Model(&User{}).Where("id=?",1).Delete()

如果使用 struct 直接当做where的条件查询:

//Get where id = 1 and name = "test1"
user := &User{Id:1,Name:"test1"}
rsql.Model(&user).Get()

//Update 默认使用主键值作为where条件
// update user set id = 1, name = "test2" where id = 1
rsql.Model(&User{Id:1,Name:"test2"}).Update()

//Use custom conditions
//Builder => UPDATE users SET `id`=?,`name`=?,`updated_at`=? WHERE (status = ?)
rsql.Model(&User{Id:1,Name:"test2"}).Where("status = ?",1).Update()

//Delete
rsql.Model(&User{Id:1}).Delete()

⚠️ 特别注意:结构体中的int default=0的问题处理

user := &User{Id:1,Status:0}
// 如果你需要status加入where筛选中,需要在Get() 指定zero int字段
// where id=1 and status=0
rsql.Model(&user).Get("status")

// where id=1;
rsql.Model(&user).Get()


//如果你想update status=0 你需要在Update() 指定zero int字段
// update user set status=0 where id = 1;
rsql.Model(&User{Status:0}).Where("id=?",1).Update("status")

// 直接使用struct Update时,where 只使用主键筛选
// update user set id = 1, name = "test2" where id = 1
rsql.Model(&User{Id:1,Name:"test2"}).Update()

从db中生成结构体小工具 genstruct

事务

Tx 有一个实例方法,如果方法返回error,事务会回滚

rsql.Tx(func(tx *rsql.DB) error {
    for id := 1; id < 10; id++ {
        user := &User{
            Id:    id,
            Name:  "test" + strconv.Itoa(id),
            Email: "test" + strconv.Itoa(id) + "@test.com",
        }
		
		//do some database operations in the transaction (use 'tx' from this point, not 'rsql')
        tx.Model(user).Create()

        if id == 8 {
            return errors.New("interrupt the transaction")
        }
    }

    //query with transaction
    var num int
    err := tx.QueryRowx("select count(*) from user_id = 1").Scan(&num)

    if err != nil {
        return err
    }

    return nil
})

带context的事务

rsql.Txx(r.Context(), func(ctx context.Context, tx *rsql.DB) error {
    // do something
})

事务也可以使用流程化的 rsql.Begin() / rsql.Use("other").Begin() 完成:

tx, err := rsql.Begin()
if err != nil {
    return err
}

for id := 1; id < 10; id++ {
    _, err := tx.Exec("INSERT INTO users(id,name,status,created_at,updated_at) VALUES(?,?,?,?,?)", id, "test"+strconv.Itoa(id), 1, time.Now(), time.Now())
    if err != nil {
        return tx.Rollback()
    }
}

return tx.Commit()

创建/更新时间自动添加

如果表结构里有以下字段, 将会自动添加上当前时间

AUTO_CREATE_TIME_FIELDS = []string{
    "create_time",
    "create_at",
    "created_at",
    "update_time",
    "update_at",
    "updated_at",
}
AUTO_UPDATE_TIME_FIELDS = []string{
    "update_time",
    "update_at",
    "updated_at",
}

使用 Map

Create Update Delete Count 支持 map[string]interface, 举例:

//Create
rsql.Table("users").Create(map[string]interface{}{
    "id":         1,
    "name":       "test",
    "email":      "[email protected]",
    "created_at": "2018-07-11 11:58:21",
    "updated_at": "2018-07-11 11:58:21",
})

//Update
rsql.Table("users").Where("id = ?", 1).Update(map[string]interface{}{
    "name":  "fifsky",
    "email": "[email protected]",
})

//Delete
rsql.Table("users").Where("id = ?", 1).Delete()

//Count
rsql.Table("users").Where("id = ?", 1).Count()

//Change database
rsql.Use("db2").Table("users").Where("id = ?", 1).Count()

//Transaction `tx`
tx.Table("users").Where("id = ?", 1}).Count()

rsql.Expr

怎么使用raw sql,原始语句?

rsql.Table("users").Update(map[string]interface{}{
    "id":2,
    "count":rsql.Expr("count+?",1)
})
//Builder SQL
//UPDATE `users` SET `count`=count + ?,`id`=?; [1 2]

"In" 语句

database/sql 使用in语法,需要自己组合 sql,总是比较复杂, 现在你可以使用sqlx的简单写法:



//SELECT * FROM users WHERE level IN (?);

var levels = []int{4, 6, 7}
rows, err := rsql.Queryx("SELECT * FROM users WHERE level IN (?);", levels)

//or

user := make([]User, 0)
err := rsql.Select(&user, "select * from users where id in(?)",[]int{1,2,3})

Relation

rsql 使用 golang structure 来描述 tables 间的relationships, 你需要使用 relation Tag 来定义相关关系, 实例:

⚠️ 跨db的查询,可以直接使用connection tag, 来定义对应的connection

type RichMoment struct {
	models.Moment
	User   *models.User    `json:"user" db:"-" relation:"user_id,id"`         //one-to-one
	Photos []models.Photos `json:"photos" db:"-" relation:"id,moment_id" connection:"db2"`     //one-to-many
}

单条查询

moment := &RichMoment{}
err := rsql.Model(moment).Where("status = 1 and id = ?",14).Get()
//output User and Photos and you get the result

SQL:

2018/12/06 13:27:54
	Query: SELECT * FROM `moments` WHERE (status = 1 and id = ?);
	Args:  []interface {}{14}
	Time:  0.00300s

2018/12/06 13:27:54
	Query: SELECT * FROM `moment_users` WHERE (id=?);
	Args:  []interface {}{5}
	Time:  0.00081s

2018/12/06 13:27:54
	Query: SELECT * FROM `photos` WHERE (moment_id=?);
	Args:  []interface {}{14}
	Time:  0.00093s

多条查询结果, many-to-many

var moments = make([]RichMoment, 0)
err := rsql.Model(&moments).Where("status = 1").Limit(10).All()
//You get the total result  for *UserMoment slice

SQL:

2018/12/06 13:50:59
	Query: SELECT * FROM `moments` WHERE (status = 1) LIMIT 10;
	Time:  0.00319s

2018/12/06 13:50:59
	Query: SELECT * FROM `moment_users` WHERE (id in(?));
	Args:  []interface {}{[]interface {}{5}}
	Time:  0.00094s

2018/12/06 13:50:59
	Query: SELECT * FROM `photos` WHERE (moment_id in(?, ?, ?, ?, ?, ?, ?, ?, ?, ?));
	Args:  []interface {}{[]interface {}{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}}
	Time:  0.00087s

Relation中的Where:

moment := &RichMoment{}
err := rsql.Relation("User" , func(b *rsql.Builder) {
    //this is builder instance,
    b.Where("gender = 0")
}).Get(moment , "select * from moments")

使用钩子-Hooks

在一个Model的执行创建、查询、修改、删除语句时,你可以添加执行前或执行后的方法。

您仅需要为 Model 定义相关约定的方法即可,rsql 会在相应的操作时,自动调用钩子。如果任意一个钩子方法返回了error,rsql会停止接下来的操作,并将当前操作的事务回滚。

// begin transaction
BeforeChange
BeforeCreate
// update timestamp `CreatedAt`, `UpdatedAt`
// save
AfterCreate
AfterChange
// commit or rollback transaction

Example:

func (u *User) BeforeCreate() (err error) {
  if u.IsValid() {
    err = errors.New("can't save invalid data")
  }
  return
}

func (u *User) AfterCreate(tx *rsql.DB) (err error) {
  if u.Id == 1 {
    u.Email = "[email protected]"
    tx.Model(u).Update()
  }
  return
}

BeforeChange / AfterChange 仅在创建、更新、删除时执行

所有钩子方法Hooks:

BeforeChange
AfterChange
BeforeCreate
AfterCreate
BeforeUpdate
AfterUpdate
BeforeDelete
AfterDelete
BeforeFind
AfterFind

钩子方法支持多种传参和返回值的组合:

func (u *User) BeforeCreate()
func (u *User) BeforeCreate() (err error)
func (u *User) BeforeCreate(tx *rsql.DB)
func (u *User) BeforeCreate(tx *rsql.DB) (err error)