parent
e666c3aee4
commit
b202078cd9
@ -0,0 +1,22 @@
|
||||
package gojobs
|
||||
|
||||
import (
|
||||
"github.com/go-redis/redis/v8"
|
||||
"go.etcd.io/etcd/client/v3"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GetDb 数据库驱动
|
||||
func (j *JobsGorm) GetDb() *gorm.DB {
|
||||
return j.service.gormClient
|
||||
}
|
||||
|
||||
// GetRedis 缓存数据库驱动
|
||||
func (j *JobsGorm) GetRedis() *redis.Client {
|
||||
return j.db.redisClient
|
||||
}
|
||||
|
||||
// GetEtcd 分布式缓存驱动
|
||||
func (j *JobsGorm) GetEtcd() *clientv3.Client {
|
||||
return j.db.etcdClient
|
||||
}
|
@ -1,36 +1,67 @@
|
||||
package gojobs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go.dtapp.net/dorm"
|
||||
"go.dtapp.net/gojobs/jobs_gorm_model"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Lock 上锁
|
||||
func (j *JobsGorm) Lock(info jobs_gorm_model.Task, id any) string {
|
||||
cacheName := fmt.Sprintf("cron:%v:%v", info.Type, id)
|
||||
judgeCache := j.redis.NewStringOperation().Get(cacheName).UnwrapOr("")
|
||||
if judgeCache != "" {
|
||||
return judgeCache
|
||||
func (j *JobsGorm) Lock(info jobs_gorm_model.Task, id any) (string, error) {
|
||||
|
||||
if j.config.lockType == "" {
|
||||
return "", errors.New("没有配置")
|
||||
}
|
||||
|
||||
var (
|
||||
redisKey = fmt.Sprintf("%s:%v:%v", j.config.lockPrefix, info.Type, id)
|
||||
etcdKey = fmt.Sprintf("%s/%v/%v", j.config.lockPrefix, info.Type, id)
|
||||
val = fmt.Sprintf("已在%s@%s机器上锁成功", j.config.insideIp, j.config.outsideIp)
|
||||
ttl = info.Frequency * 3
|
||||
)
|
||||
|
||||
if j.config.lockType == lockTypeRedis {
|
||||
return j.service.lockRedisClient.Lock(redisKey, val, ttl)
|
||||
}
|
||||
j.redis.NewStringOperation().Set(cacheName, fmt.Sprintf("已在%v机器上锁成功", j.outsideIp), dorm.WithExpire(time.Millisecond*time.Duration(info.Frequency)*3))
|
||||
return ""
|
||||
|
||||
return j.service.lockEtcdClient.Lock(etcdKey, val, ttl)
|
||||
}
|
||||
|
||||
// Unlock Lock 解锁
|
||||
func (j *JobsGorm) Unlock(info jobs_gorm_model.Task, id any) {
|
||||
cacheName := fmt.Sprintf("cron:%v:%v", info.Type, id)
|
||||
j.redis.NewStringOperation().Del(cacheName)
|
||||
func (j *JobsGorm) Unlock(info jobs_gorm_model.Task, id any) error {
|
||||
|
||||
if j.config.lockType == "" {
|
||||
return errors.New("没有配置")
|
||||
}
|
||||
|
||||
var (
|
||||
redisKey = fmt.Sprintf("%s:%v:%v", j.config.lockPrefix, info.Type, id)
|
||||
etcdKey = fmt.Sprintf("%s/%v/%v", j.config.lockPrefix, info.Type, id)
|
||||
)
|
||||
|
||||
if j.config.lockType == lockTypeRedis {
|
||||
return j.service.lockRedisClient.Unlock(redisKey)
|
||||
}
|
||||
|
||||
return j.service.lockEtcdClient.Unlock(etcdKey)
|
||||
}
|
||||
|
||||
// LockForever 永远上锁
|
||||
func (j *JobsGorm) LockForever(info jobs_gorm_model.Task, id any) string {
|
||||
cacheName := fmt.Sprintf("cron:%v:%v", info.Type, id)
|
||||
judgeCache := j.redis.NewStringOperation().Get(cacheName).UnwrapOr("")
|
||||
if judgeCache != "" {
|
||||
return judgeCache
|
||||
func (j *JobsGorm) LockForever(info jobs_gorm_model.Task, id any) (string, error) {
|
||||
|
||||
if j.config.lockType == "" {
|
||||
return "", errors.New("没有配置")
|
||||
}
|
||||
j.redis.NewStringOperation().Set(cacheName, fmt.Sprintf("已在%v机器永远上锁成功", j.outsideIp))
|
||||
return ""
|
||||
|
||||
var (
|
||||
redisKey = fmt.Sprintf("%s:%v:%v", j.config.lockPrefix, info.Type, id)
|
||||
etcdKey = fmt.Sprintf("%s/%v/%v", j.config.lockPrefix, info.Type, id)
|
||||
val = fmt.Sprintf("已在%s@%s机器永远上锁成功", j.config.insideIp, j.config.outsideIp)
|
||||
)
|
||||
|
||||
if j.config.lockType == lockTypeRedis {
|
||||
return j.service.lockRedisClient.LockForever(redisKey, val)
|
||||
}
|
||||
|
||||
return j.service.lockEtcdClient.LockForever(etcdKey, val)
|
||||
}
|
||||
|
@ -0,0 +1,265 @@
|
||||
package gojobs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go.dtapp.net/gojobs/jobs_gorm_model"
|
||||
"go.dtapp.net/gostring"
|
||||
"gorm.io/gorm"
|
||||
"log"
|
||||
)
|
||||
|
||||
// Run 运行
|
||||
func (j *JobsGorm) Run(info jobs_gorm_model.Task, status int, desc string) {
|
||||
// 请求函数记录
|
||||
err := j.service.gormClient.Create(&jobs_gorm_model.TaskLog{
|
||||
TaskId: info.Id,
|
||||
StatusCode: status,
|
||||
Desc: desc,
|
||||
Version: j.config.runVersion,
|
||||
}).Error
|
||||
if err != nil {
|
||||
log.Println("statusCreate", err.Error())
|
||||
}
|
||||
if status == 0 {
|
||||
err = j.EditTask(j.service.gormClient, info.Id).
|
||||
Select("run_id").
|
||||
Updates(jobs_gorm_model.Task{
|
||||
RunId: gostring.GetUuId(),
|
||||
}).Error
|
||||
if err != nil {
|
||||
log.Println("statusEdit", err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
// 任务
|
||||
if status == CodeSuccess {
|
||||
// 执行成功
|
||||
err = j.EditTask(j.service.gormClient, info.Id).
|
||||
Select("status_desc", "number", "run_id", "updated_ip", "result").
|
||||
Updates(jobs_gorm_model.Task{
|
||||
StatusDesc: "执行成功",
|
||||
Number: info.Number + 1,
|
||||
RunId: gostring.GetUuId(),
|
||||
UpdatedIp: j.config.outsideIp,
|
||||
Result: desc,
|
||||
}).Error
|
||||
if err != nil {
|
||||
log.Println("statusEdit", err.Error())
|
||||
}
|
||||
}
|
||||
if status == CodeEnd {
|
||||
// 执行成功、提前结束
|
||||
err = j.EditTask(j.service.gormClient, info.Id).
|
||||
Select("status", "status_desc", "number", "updated_ip", "result").
|
||||
Updates(jobs_gorm_model.Task{
|
||||
Status: TASK_SUCCESS,
|
||||
StatusDesc: "结束执行",
|
||||
Number: info.Number + 1,
|
||||
UpdatedIp: j.config.outsideIp,
|
||||
Result: desc,
|
||||
}).Error
|
||||
if err != nil {
|
||||
log.Println("statusEdit", err.Error())
|
||||
}
|
||||
}
|
||||
if status == CodeError {
|
||||
// 执行失败
|
||||
err = j.EditTask(j.service.gormClient, info.Id).
|
||||
Select("status_desc", "number", "run_id", "updated_ip", "result").
|
||||
Updates(jobs_gorm_model.Task{
|
||||
StatusDesc: "执行失败",
|
||||
Number: info.Number + 1,
|
||||
RunId: gostring.GetUuId(),
|
||||
UpdatedIp: j.config.outsideIp,
|
||||
Result: desc,
|
||||
}).Error
|
||||
if err != nil {
|
||||
log.Println("statusEdit", err.Error())
|
||||
}
|
||||
}
|
||||
if info.MaxNumber != 0 {
|
||||
if info.Number+1 >= info.MaxNumber {
|
||||
// 关闭执行
|
||||
err = j.EditTask(j.service.gormClient, info.Id).
|
||||
Select("status").
|
||||
Updates(jobs_gorm_model.Task{
|
||||
Status: TASK_TIMEOUT,
|
||||
}).Error
|
||||
if err != nil {
|
||||
log.Println("statusEdit", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RunAddLog 任务执行日志
|
||||
func (j *JobsGorm) RunAddLog(id uint, runId string) error {
|
||||
return j.service.gormClient.Create(&jobs_gorm_model.TaskLogRun{
|
||||
TaskId: id,
|
||||
RunId: runId,
|
||||
InsideIp: j.config.insideIp,
|
||||
OutsideIp: j.config.outsideIp,
|
||||
Os: j.config.os,
|
||||
Arch: j.config.arch,
|
||||
Gomaxprocs: j.config.maxProCs,
|
||||
GoVersion: j.config.version,
|
||||
MacAddrs: j.config.macAddrS,
|
||||
}).Error
|
||||
}
|
||||
|
||||
// ConfigCreateInCustomId 创建正在运行任务
|
||||
type ConfigCreateInCustomId struct {
|
||||
Tx *gorm.DB // 驱动
|
||||
Params string // 参数
|
||||
Frequency int64 // 频率(秒单位)
|
||||
CustomId string // 自定义编号
|
||||
CustomSequence int64 // 自定义顺序
|
||||
Type string // 类型
|
||||
SpecifyIp string // 指定外网IP
|
||||
CurrentIp string // 当前ip
|
||||
}
|
||||
|
||||
// CreateInCustomId 创建正在运行任务
|
||||
func (j *JobsGorm) CreateInCustomId(config *ConfigCreateInCustomId) error {
|
||||
if config.CurrentIp == "" {
|
||||
config.CurrentIp = j.config.outsideIp
|
||||
}
|
||||
err := config.Tx.Create(&jobs_gorm_model.Task{
|
||||
Status: TASK_IN,
|
||||
Params: config.Params,
|
||||
StatusDesc: "首次添加任务",
|
||||
Frequency: config.Frequency,
|
||||
RunId: gostring.GetUuId(),
|
||||
CustomId: config.CustomId,
|
||||
CustomSequence: config.CustomSequence,
|
||||
Type: config.Type,
|
||||
CreatedIp: config.CurrentIp,
|
||||
SpecifyIp: config.SpecifyIp,
|
||||
UpdatedIp: config.CurrentIp,
|
||||
}).Error
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("创建[%s@%s]任务失败:%s", config.CustomId, config.Type, err.Error()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigCreateInCustomIdOnly 创建正在运行唯一任务
|
||||
type ConfigCreateInCustomIdOnly struct {
|
||||
Tx *gorm.DB // 驱动
|
||||
Params string // 参数
|
||||
Frequency int64 // 频率(秒单位)
|
||||
CustomId string // 自定义编号
|
||||
CustomSequence int64 // 自定义顺序
|
||||
Type string // 类型
|
||||
SpecifyIp string // 指定外网IP
|
||||
CurrentIp string // 当前ip
|
||||
}
|
||||
|
||||
// CreateInCustomIdOnly 创建正在运行唯一任务
|
||||
func (j *JobsGorm) CreateInCustomIdOnly(config *ConfigCreateInCustomIdOnly) error {
|
||||
query := j.TaskTypeTakeIn(config.Tx, config.CustomId, config.Type)
|
||||
if query.Id != 0 {
|
||||
return errors.New(fmt.Sprintf("%d:[%s@%s]任务已存在", query.Id, config.CustomId, config.Type))
|
||||
}
|
||||
if config.CurrentIp == "" {
|
||||
config.CurrentIp = j.config.outsideIp
|
||||
}
|
||||
err := config.Tx.Create(&jobs_gorm_model.Task{
|
||||
Status: TASK_IN,
|
||||
Params: config.Params,
|
||||
StatusDesc: "首次添加任务",
|
||||
Frequency: config.Frequency,
|
||||
RunId: gostring.GetUuId(),
|
||||
CustomId: config.CustomId,
|
||||
CustomSequence: config.CustomSequence,
|
||||
Type: config.Type,
|
||||
CreatedIp: config.CurrentIp,
|
||||
SpecifyIp: config.SpecifyIp,
|
||||
UpdatedIp: config.CurrentIp,
|
||||
}).Error
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("创建[%s@%s]任务失败:%s", config.CustomId, config.Type, err.Error()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigCreateInCustomIdMaxNumber 创建正在运行任务并限制数量
|
||||
type ConfigCreateInCustomIdMaxNumber struct {
|
||||
Tx *gorm.DB // 驱动
|
||||
Params string // 参数
|
||||
Frequency int64 // 频率(秒单位)
|
||||
MaxNumber int64 // 最大次数
|
||||
CustomId string // 自定义编号
|
||||
CustomSequence int64 // 自定义顺序
|
||||
Type string // 类型
|
||||
SpecifyIp string // 指定外网IP
|
||||
CurrentIp string // 当前ip
|
||||
}
|
||||
|
||||
// CreateInCustomIdMaxNumber 创建正在运行任务并限制数量
|
||||
func (j *JobsGorm) CreateInCustomIdMaxNumber(config *ConfigCreateInCustomIdMaxNumber) error {
|
||||
if config.CurrentIp == "" {
|
||||
config.CurrentIp = j.config.outsideIp
|
||||
}
|
||||
err := config.Tx.Create(&jobs_gorm_model.Task{
|
||||
Status: TASK_IN,
|
||||
Params: config.Params,
|
||||
StatusDesc: "首次添加任务",
|
||||
Frequency: config.Frequency,
|
||||
MaxNumber: config.MaxNumber,
|
||||
RunId: gostring.GetUuId(),
|
||||
CustomId: config.CustomId,
|
||||
CustomSequence: config.CustomSequence,
|
||||
Type: config.Type,
|
||||
CreatedIp: config.CurrentIp,
|
||||
SpecifyIp: config.SpecifyIp,
|
||||
UpdatedIp: config.CurrentIp,
|
||||
}).Error
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("创建[%s@%s]任务失败:%s", config.CustomId, config.Type, err.Error()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigCreateInCustomIdMaxNumberOnly 创建正在运行唯一任务并限制数量
|
||||
type ConfigCreateInCustomIdMaxNumberOnly struct {
|
||||
Tx *gorm.DB // 驱动
|
||||
Params string // 参数
|
||||
Frequency int64 // 频率(秒单位)
|
||||
MaxNumber int64 // 最大次数
|
||||
CustomId string // 自定义编号
|
||||
CustomSequence int64 // 自定义顺序
|
||||
Type string // 类型
|
||||
SpecifyIp string // 指定外网IP
|
||||
CurrentIp string // 当前ip
|
||||
}
|
||||
|
||||
// CreateInCustomIdMaxNumberOnly 创建正在运行唯一任务并限制数量
|
||||
func (j *JobsGorm) CreateInCustomIdMaxNumberOnly(config *ConfigCreateInCustomIdMaxNumberOnly) error {
|
||||
query := j.TaskTypeTakeIn(config.Tx, config.CustomId, config.Type)
|
||||
if query.Id != 0 {
|
||||
return errors.New(fmt.Sprintf("%d:[%s@%s]任务已存在", query.Id, config.CustomId, config.Type))
|
||||
}
|
||||
if config.CurrentIp == "" {
|
||||
config.CurrentIp = j.config.outsideIp
|
||||
}
|
||||
err := config.Tx.Create(&jobs_gorm_model.Task{
|
||||
Status: TASK_IN,
|
||||
Params: config.Params,
|
||||
StatusDesc: "首次添加任务",
|
||||
Frequency: config.Frequency,
|
||||
MaxNumber: config.MaxNumber,
|
||||
RunId: gostring.GetUuId(),
|
||||
CustomId: config.CustomId,
|
||||
CustomSequence: config.CustomSequence,
|
||||
Type: config.Type,
|
||||
CreatedIp: config.CurrentIp,
|
||||
SpecifyIp: config.SpecifyIp,
|
||||
UpdatedIp: config.CurrentIp,
|
||||
}).Error
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("创建[%s@%s]任务失败:%s", config.CustomId, config.Type, err.Error()))
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package gojobs
|
||||
|
||||
//
|
||||
//type JobsOption func(*JobsCron)
|
||||
//
|
||||
//// WithRedis 缓存服务驱动
|
||||
//func WithRedis(db *goredis.Client) JobsOption {
|
||||
// return func(opts *JobsCron) {
|
||||
// opts.redis = db
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// WithGorm 数据库服务驱动
|
||||
//func WithGorm(db *gorm.DB) JobsOption {
|
||||
// return func(opts *JobsCron) {
|
||||
// opts.db = db
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// WithMainService 是否主要服务(主要服务可删除过期服务)
|
||||
//func WithMainService(status int) JobsOption {
|
||||
// return func(opts *JobsCron) {
|
||||
// opts.mainService = status
|
||||
// }
|
||||
//}
|
@ -1,34 +0,0 @@
|
||||
# idea ignore
|
||||
.idea/
|
||||
*.ipr
|
||||
*.iml
|
||||
*.iws
|
||||
|
||||
.vscode/
|
||||
|
||||
*.swp
|
||||
|
||||
# temp ignore
|
||||
*.log
|
||||
*.cache
|
||||
*.diff
|
||||
*.exe
|
||||
*.exe~
|
||||
*.patch
|
||||
*.tmp
|
||||
*debug.test
|
||||
debug.test
|
||||
go.sum
|
||||
|
||||
# system ignore
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# project
|
||||
*.cert
|
||||
*.key
|
||||
.test
|
||||
iprepo.txt
|
||||
|
||||
|
||||
_output
|
File diff suppressed because it is too large
Load Diff
@ -1,143 +0,0 @@
|
||||
package zorm
|
||||
|
||||
//IEntityStruct "struct"实体类的接口,所有的struct实体类都要实现这个接口
|
||||
//IEntityStruct The interface of the "struct" entity class, all struct entity classes must implement this interface
|
||||
type IEntityStruct interface {
|
||||
//获取表名称
|
||||
//Get the table name.
|
||||
GetTableName() string
|
||||
|
||||
//获取数据库表的主键字段名称.因为要兼容Map,只能是数据库的字段名称
|
||||
//Get the primary key field name of the database table. Because it is compatible with Map, it can only be the field name of the database
|
||||
GetPKColumnName() string
|
||||
|
||||
//GetPkSequence 主键序列,因为需要兼容多种数据库的序列,所以使用map
|
||||
//key是DBType,value是序列的值,例如oracle的TESTSEQ.NEXTVAL,如果有值,优先级最高
|
||||
//如果key对应的value是 "",则代表是触发器触发的序列,兼容自增关键字,例如 ["oracle"]""
|
||||
//GetPkSequence Primary key sequence, because it needs to be compatible with multiple database sequences, map is used
|
||||
//The key is the DB Type, and the value is the value of the sequence,
|
||||
//such as Oracle's TESTSEQ.NEXTVAL. If there is a value, the priority is the highest
|
||||
//If the value corresponding to the key is "", it means the sequence triggered by the trigger
|
||||
//Compatible with auto-increment keywords, such as ["oracle"]""
|
||||
GetPkSequence() map[string]string
|
||||
}
|
||||
|
||||
//IEntityMap 使用Map保存数据,用于不方便使用struct的场景,如果主键是自增或者序列,不要"entityMap.Set"主键的值
|
||||
//IEntityMap Use Map to save data for scenarios where it is not convenient to use struct
|
||||
//If the primary key is auto-increment or sequence, do not "entity Map.Set" the value of the primary key
|
||||
type IEntityMap interface {
|
||||
//获取表名称
|
||||
//Get the table name
|
||||
GetTableName() string
|
||||
|
||||
//获取数据库表的主键字段名称.因为要兼容Map,只能是数据库的字段名称.
|
||||
//Get the primary key field name of the database table. Because it is compatible with Map, it can only be the field name of the database.
|
||||
GetPKColumnName() string
|
||||
|
||||
//GetPkSequence 主键序列,因为需要兼容多种数据库的序列,所以使用map
|
||||
//key是DBType,value是序列的值,例如oracle的TESTSEQ.NEXTVAL,如果有值,优先级最高
|
||||
//如果key对应的value是 "",则代表是触发器触发的序列,兼容自增关键字,例如 ["oracle"]""
|
||||
//GetPkSequence Primary key sequence, because it needs to be compatible with multiple database sequences, map is used
|
||||
//The key is the DB Type, and the value is the value of the sequence,
|
||||
//such as Oracle's TESTSEQ.NEXTVAL. If there is a value, the priority is the highest
|
||||
//If the value corresponding to the key is "", it means the sequence triggered by the trigger
|
||||
//Compatible with auto-increment keywords, such as ["oracle"]""
|
||||
GetPkSequence() map[string]string
|
||||
|
||||
//针对Map类型,记录数据库字段
|
||||
//For Map type, record database fields.
|
||||
GetDBFieldMap() map[string]interface{}
|
||||
//设置数据库字段的值
|
||||
//Set the value of a database field.
|
||||
Set(key string, value interface{}) map[string]interface{}
|
||||
}
|
||||
|
||||
//EntityStruct "IBaseEntity" 的基础实现,所有的实体类都匿名注入.这样就类似实现继承了,如果接口增加方法,调整这个默认实现即可
|
||||
//EntityStruct The basic implementation of "IBaseEntity", all entity classes are injected anonymously
|
||||
//This is similar to implementation inheritance. If the interface adds methods, adjust the default implementation
|
||||
type EntityStruct struct {
|
||||
}
|
||||
|
||||
//默认数据库的主键列名
|
||||
//Primary key column name of the default database
|
||||
const defaultPkName = "id"
|
||||
|
||||
//获取表名称
|
||||
/*
|
||||
func (entity *EntityStruct) GetTableName() string {
|
||||
return ""
|
||||
}
|
||||
*/
|
||||
|
||||
//GetPKColumnName 获取数据库表的主键字段名称.因为要兼容Map,只能是数据库的字段名称
|
||||
//GetPKColumnName Get the primary key field name of the database table
|
||||
//Because it is compatible with Map, it can only be the field name of the database
|
||||
func (entity *EntityStruct) GetPKColumnName() string {
|
||||
return defaultPkName
|
||||
}
|
||||
|
||||
//var defaultPkSequence = make(map[string]string, 0)
|
||||
|
||||
//GetPkSequence 主键序列,需要兼容多种数据库的序列,使用map,key是DBType,value是序列的值,例如oracle的TESTSEQ.NEXTVAL,如果有值,优先级最高
|
||||
//如果key对应的value是 "",则代表是触发器触发的序列,兼容自增关键字,例如 ["oracle"]""
|
||||
func (entity *EntityStruct) GetPkSequence() map[string]string {
|
||||
return nil
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------//
|
||||
|
||||
//EntityMap IEntityMap的基础实现,可以直接使用或者匿名注入
|
||||
type EntityMap struct {
|
||||
//表名
|
||||
tableName string
|
||||
//主键列名
|
||||
PkColumnName string
|
||||
//主键序列,需要兼容多种数据库的序列,使用map,key是DBType,value是序列的值,例如oracle的TESTSEQ.NEXTVAL,如果有值,优先级最高
|
||||
PkSequence map[string]string
|
||||
//数据库字段,不暴露外部
|
||||
dbFieldMap map[string]interface{}
|
||||
}
|
||||
|
||||
//NewEntityMap 初始化Map,必须传入表名称
|
||||
func NewEntityMap(tbName string) *EntityMap {
|
||||
entityMap := EntityMap{}
|
||||
entityMap.dbFieldMap = map[string]interface{}{}
|
||||
entityMap.tableName = tbName
|
||||
entityMap.PkColumnName = defaultPkName
|
||||
return &entityMap
|
||||
}
|
||||
|
||||
//GetTableName 获取表名称
|
||||
func (entity *EntityMap) GetTableName() string {
|
||||
return entity.tableName
|
||||
}
|
||||
|
||||
//GetPKColumnName 获取数据库表的主键字段名称.因为要兼容Map,只能是数据库的字段名称
|
||||
func (entity *EntityMap) GetPKColumnName() string {
|
||||
return entity.PkColumnName
|
||||
}
|
||||
|
||||
//GetPkSequence 主键序列,因为需要兼容多种数据库的序列,所以使用map
|
||||
//key是DBType,value是序列的值,例如oracle的TESTSEQ.NEXTVAL,如果有值,优先级最高
|
||||
//如果key对应的value是 "",则代表是触发器触发的序列,兼容自增关键字,例如 ["oracle"]""
|
||||
//GetPkSequence Primary key sequence, because it needs to be compatible with multiple database sequences, map is used
|
||||
//The key is the DB Type, and the value is the value of the sequence,
|
||||
//such as Oracle's TESTSEQ.NEXTVAL. If there is a value, the priority is the highest
|
||||
//If the value corresponding to the key is "", it means the sequence triggered by the trigger
|
||||
//Compatible with auto-increment keywords, such as ["oracle"]""
|
||||
func (entity *EntityMap) GetPkSequence() map[string]string {
|
||||
return entity.PkSequence
|
||||
}
|
||||
|
||||
//GetDBFieldMap 针对Map类型,记录数据库字段
|
||||
//GetDBFieldMap For Map type, record database fields
|
||||
func (entity *EntityMap) GetDBFieldMap() map[string]interface{} {
|
||||
return entity.dbFieldMap
|
||||
}
|
||||
|
||||
//Set 设置数据库字段
|
||||
//Set Set database fields
|
||||
func (entity *EntityMap) Set(key string, value interface{}) map[string]interface{} {
|
||||
entity.dbFieldMap[key] = value
|
||||
return entity.dbFieldMap
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
package zorm
|
||||
|
||||
import "context"
|
||||
|
||||
// ISeataGlobalTransaction seata-golang的包装接口,隔离seata-golang的依赖
|
||||
// 声明一个struct,实现这个接口,并配置实现 FuncSeataGlobalTransaction 函数
|
||||
/**
|
||||
|
||||
//不使用proxy代理模式,全局托管,不修改业务代码,零侵入实现分布式事务
|
||||
//tm.Implement(svc.ProxySvc)
|
||||
|
||||
|
||||
// 分布式事务示例代码
|
||||
_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
|
||||
|
||||
// 获取当前分布式事务的XID.不用考虑怎么来的,如果是分布式事务环境,会自动设置值
|
||||
// xid := ctx.Value("XID").(string)
|
||||
|
||||
// 把xid传递到第三方应用
|
||||
// req.Header.Set("XID", xid)
|
||||
|
||||
// 如果返回的err不是nil,本地事务和分布式事务就会回滚
|
||||
return nil, err
|
||||
})
|
||||
|
||||
///----------第三方应用-------///
|
||||
|
||||
// 第三方应用开启事务前,ctx需要绑定XID,例如使用了gin框架
|
||||
|
||||
// 接受传递过来的XID,绑定到本地ctx
|
||||
// xid:=c.Request.Header.Get("XID")
|
||||
// 获取到ctx
|
||||
// ctx := c.Request.Context()
|
||||
// ctx = context.WithValue(ctx,"XID",xid)
|
||||
|
||||
// ctx绑定XID之后,调用业务事务
|
||||
_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
|
||||
|
||||
// 业务代码......
|
||||
|
||||
// 如果返回的err不是nil,本地事务和分布式事务就会回滚
|
||||
return nil, err
|
||||
})
|
||||
|
||||
|
||||
// 建议以下代码放到单独的文件里
|
||||
//................//
|
||||
|
||||
// ZormSeataGlobalTransaction 包装seata的*tm.DefaultGlobalTransaction,实现zorm.ISeataGlobalTransaction接口
|
||||
type ZormSeataGlobalTransaction struct {
|
||||
*tm.DefaultGlobalTransaction
|
||||
}
|
||||
|
||||
// MyFuncSeataGlobalTransaction zorm适配seata分布式事务的函数
|
||||
// 重要!!!!需要配置zorm.DataSourceConfig.FuncSeataGlobalTransaction=MyFuncSeataGlobalTransaction 重要!!!
|
||||
func MyFuncSeataGlobalTransaction(ctx context.Context) (zorm.ISeataGlobalTransaction, context.Context, error) {
|
||||
//获取seata的rootContext
|
||||
rootContext := seataContext.NewRootContext(ctx)
|
||||
//创建seata事务
|
||||
seataTx := tm.GetCurrentOrCreate(rootContext)
|
||||
//使用zorm.ISeataGlobalTransaction接口对象包装seata事务,隔离seata-golang依赖
|
||||
seataGlobalTransaction := ZormSeataGlobalTransaction{seataTx}
|
||||
|
||||
return seataGlobalTransaction, rootContext, nil
|
||||
}
|
||||
//实现zorm.ISeataGlobalTransaction接口
|
||||
func (gtx ZormSeataGlobalTransaction) SeataBegin(ctx context.Context) error {
|
||||
rootContext := ctx.(*seataContext.RootContext)
|
||||
return gtx.BeginWithTimeout(int32(6000), rootContext)
|
||||
}
|
||||
|
||||
func (gtx ZormSeataGlobalTransaction) SeataCommit(ctx context.Context) error {
|
||||
rootContext := ctx.(*seataContext.RootContext)
|
||||
return gtx.Commit(rootContext)
|
||||
}
|
||||
|
||||
func (gtx ZormSeataGlobalTransaction) SeataRollback(ctx context.Context) error {
|
||||
rootContext := ctx.(*seataContext.RootContext)
|
||||
//如果是Participant角色,修改为Launcher角色,允许分支事务提交全局事务.
|
||||
if gtx.Role != tm.Launcher {
|
||||
gtx.Role = tm.Launcher
|
||||
}
|
||||
return gtx.Rollback(rootContext)
|
||||
}
|
||||
|
||||
func (gtx ZormSeataGlobalTransaction) GetSeataXID(ctx context.Context) string {
|
||||
rootContext := ctx.(*seataContext.RootContext)
|
||||
return rootContext.GetXID()
|
||||
}
|
||||
//................//
|
||||
**/
|
||||
|
||||
type ISeataGlobalTransaction interface {
|
||||
//开启seata全局事务
|
||||
SeataBegin(ctx context.Context) error
|
||||
|
||||
//提交seata全局事务
|
||||
SeataCommit(ctx context.Context) error
|
||||
|
||||
//回滚seata全局事务
|
||||
SeataRollback(ctx context.Context) error
|
||||
|
||||
//获取seata事务的XID
|
||||
GetSeataXID(ctx context.Context) string
|
||||
|
||||
//重新包装为seata的context.RootContext
|
||||
//context.RootContext 如果后续使用了 context.WithValue,类型就是context.valueCtx 就会造成无法再类型断言为 context.RootContext
|
||||
//所以DBDao里使用了 seataRootContext变量,区分业务的ctx和seata的RootContext
|
||||
//SeataNewRootContext(ctx context.Context) context.Context
|
||||
}
|
@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
@ -1,43 +0,0 @@
|
||||
package zorm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
func init() {
|
||||
//设置默认的日志显示信息,显示文件和行号
|
||||
//Set the default log display information, display file and line number.
|
||||
log.SetFlags(log.Llongfile | log.LstdFlags)
|
||||
}
|
||||
|
||||
//LogCallDepth 记录日志调用层级,用于定位到业务层代码
|
||||
//Log Call Depth Record the log call level, used to locate the business layer code
|
||||
var LogCallDepth = 4
|
||||
|
||||
//FuncLogError 记录error日志
|
||||
//FuncLogError Record error log
|
||||
var FuncLogError func(err error) = defaultLogError
|
||||
|
||||
//FuncLogPanic 记录panic日志,默认使用"defaultLogError"实现
|
||||
//FuncLogPanic Record panic log, using "defaultLogError" by default
|
||||
var FuncLogPanic func(err error) = defaultLogPanic
|
||||
|
||||
//FuncPrintSQL 打印sql语句和参数
|
||||
//FuncPrintSQL Print sql statement and parameters
|
||||
var FuncPrintSQL func(sqlstr string, args []interface{}) = defaultPrintSQL
|
||||
|
||||
func defaultLogError(err error) {
|
||||
log.Output(LogCallDepth, fmt.Sprintln(err))
|
||||
}
|
||||
func defaultLogPanic(err error) {
|
||||
defaultLogError(err)
|
||||
}
|
||||
func defaultPrintSQL(sqlstr string, args []interface{}) {
|
||||
if args != nil {
|
||||
log.Output(LogCallDepth, fmt.Sprintln("sql:", sqlstr, ",args:", args))
|
||||
} else {
|
||||
log.Output(LogCallDepth, fmt.Sprintln("sql:", sqlstr))
|
||||
}
|
||||
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
package zorm
|
||||
|
||||
//Page 分页对象
|
||||
//Page Pagination object
|
||||
type Page struct {
|
||||
//当前页码,从1开始
|
||||
//Current page number, starting from 1
|
||||
PageNo int
|
||||
|
||||
//每页多少条,默认20条
|
||||
//How many items per page, 20 items by default
|
||||
PageSize int
|
||||
|
||||
//数据总条数
|
||||
//Total number of data
|
||||
TotalCount int
|
||||
|
||||
//共多少页
|
||||
//How many pages
|
||||
PageCount int
|
||||
|
||||
//是否是第一页
|
||||
//Is it the first page
|
||||
FirstPage bool
|
||||
|
||||
//是否有上一页
|
||||
//Whether there is a previous page
|
||||
HasPrev bool
|
||||
|
||||
//是否有下一页
|
||||
//Is there a next page
|
||||
HasNext bool
|
||||
|
||||
//是否是最后一页
|
||||
//Is it the last page
|
||||
LastPage bool
|
||||
}
|
||||
|
||||
//NewPage 创建Page对象
|
||||
//NewPage Create Page object
|
||||
func NewPage() *Page {
|
||||
page := Page{}
|
||||
page.PageNo = 1
|
||||
page.PageSize = 20
|
||||
return &page
|
||||
}
|
||||
|
||||
//setTotalCount 设置总条数,计算其他值
|
||||
//setTotalCount Set the total number of bars, calculate other values
|
||||
func (page *Page) setTotalCount(total int) {
|
||||
page.TotalCount = total
|
||||
page.PageCount = (page.TotalCount + page.PageSize - 1) / page.PageSize
|
||||
if page.PageNo >= page.PageCount {
|
||||
page.LastPage = true
|
||||
} else {
|
||||
page.HasNext = true
|
||||
}
|
||||
if page.PageNo > 1 {
|
||||
page.HasPrev = true
|
||||
} else {
|
||||
page.FirstPage = true
|
||||
}
|
||||
|
||||
}
|
@ -1,266 +0,0 @@
|
||||
package zorm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// dataSorce对象,隔离sql原生对象
|
||||
// dataSorce Isolate sql native objects
|
||||
type dataSource struct {
|
||||
*sql.DB
|
||||
//config *DataSourceConfig
|
||||
}
|
||||
|
||||
// DataSourceConfig 数据库连接池的配置
|
||||
// DateSourceConfig Database connection pool configuration
|
||||
type DataSourceConfig struct {
|
||||
//DSN dataSourceName 连接字符串
|
||||
//DSN DataSourceName Database connection string
|
||||
DSN string
|
||||
//数据库驱动名称:mysql,postgres,oci8,sqlserver,sqlite3,clickhouse,dm,kingbase 和DBType对应,处理数据库有多个驱动
|
||||
//Database diver name:mysql,dm,postgres,opi8,sqlserver,sqlite3,clickhouse,kingbase corresponds to DBType,A database may have multiple drivers
|
||||
DriverName string
|
||||
//数据库类型(方言判断依据):mysql,postgresql,oracle,mssql,sqlite,clickhouse,dm,kingbase 和 DriverName 对应,处理数据库有多个驱动
|
||||
//Database Type:mysql,postgresql,oracle,mssql,sqlite,clickhouse,dm,kingbase corresponds to DriverName,A database may have multiple drivers
|
||||
DBType string
|
||||
//PrintSQL 是否打印SQL语句.使用zorm.PrintSQL记录SQL
|
||||
//PrintSQL Whether to print SQL, use zorm.PrintSQL record sql
|
||||
PrintSQL bool
|
||||
//MaxOpenConns 数据库最大连接数,默认50
|
||||
//MaxOpenConns Maximum number of database connections, Default 50
|
||||
MaxOpenConns int
|
||||
//MaxIdleConns 数据库最大空闲连接数,默认50
|
||||
//MaxIdleConns The maximum number of free connections to the database default 50
|
||||
MaxIdleConns int
|
||||
//ConnMaxLifetimeSecond 连接存活秒时间. 默认600(10分钟)后连接被销毁重建.避免数据库主动断开连接,造成死连接.MySQL默认wait_timeout 28800秒(8小时)
|
||||
//ConnMaxLifetimeSecond: (Connection survival time in seconds)Destroy and rebuild the connection after the default 600 seconds (10 minutes)
|
||||
//Prevent the database from actively disconnecting and causing dead connections. MySQL Default wait_timeout 28800 seconds
|
||||
ConnMaxLifetimeSecond int
|
||||
|
||||
//事务隔离级别的默认配置,默认为nil
|
||||
DefaultTxOptions *sql.TxOptions
|
||||
|
||||
//全局禁用事务,默认false.为了处理某些数据库不支持事务,比如clickhouse
|
||||
//禁用事务应该有驱动实现,不应该由orm实现
|
||||
//DisableTransaction bool
|
||||
|
||||
//MockSQLDB 用于mock测试的入口,如果MockSQLDB不为nil,则不使用DSN,直接使用MockSQLDB
|
||||
//db, mock, err := sqlmock.New()
|
||||
//MockSQLDB *sql.DB
|
||||
|
||||
//FuncSeataGlobalTransaction seata-golang分布式的适配函数,返回ISeataGlobalTransaction接口的实现
|
||||
FuncSeataGlobalTransaction func(ctx context.Context) (ISeataGlobalTransaction, context.Context, error)
|
||||
}
|
||||
|
||||
// newDataSource 创建一个新的datasource,内部调用,避免外部直接使用datasource
|
||||
// newDAtaSource Create a new datasource and call it internally to avoid direct external use of the datasource
|
||||
func newDataSource(config *DataSourceConfig) (*dataSource, error) {
|
||||
if config.DSN == "" {
|
||||
return nil, errors.New("DSN cannot be empty")
|
||||
}
|
||||
if config.DriverName == "" {
|
||||
return nil, errors.New("DriverName cannot be empty")
|
||||
}
|
||||
if config.DBType == "" {
|
||||
return nil, errors.New("DBType cannot be empty")
|
||||
}
|
||||
var db *sql.DB
|
||||
var errSQLOpen error
|
||||
//if config.MockSQLDB == nil {
|
||||
db, errSQLOpen = sql.Open(config.DriverName, config.DSN)
|
||||
if errSQLOpen != nil {
|
||||
errSQLOpen = fmt.Errorf("newDataSource-->open数据库打开失败:%w", errSQLOpen)
|
||||
FuncLogError(errSQLOpen)
|
||||
return nil, errSQLOpen
|
||||
}
|
||||
// } else {
|
||||
// db = config.MockSQLDB
|
||||
// }
|
||||
|
||||
if config.MaxOpenConns == 0 {
|
||||
config.MaxOpenConns = 50
|
||||
}
|
||||
if config.MaxIdleConns == 0 {
|
||||
config.MaxIdleConns = 50
|
||||
}
|
||||
|
||||
if config.ConnMaxLifetimeSecond == 0 {
|
||||
config.ConnMaxLifetimeSecond = 600
|
||||
}
|
||||
|
||||
//设置数据库最大连接数
|
||||
//Set the maximum number of database connections
|
||||
db.SetMaxOpenConns(config.MaxOpenConns)
|
||||
//设置数据库最大空闲连接数
|
||||
//Set the maximum number of free connections to the database
|
||||
db.SetMaxIdleConns(config.MaxIdleConns)
|
||||
//连接存活秒时间. 默认600(10分钟)后连接被销毁重建.避免数据库主动断开连接,造成死连接.MySQL默认wait_timeout 28800秒(8小时)
|
||||
//(Connection survival time in seconds) Destroy and rebuild the connection after the default 600 seconds (10 minutes)
|
||||
//Prevent the database from actively disconnecting and causing dead connections. MySQL Default wait_timeout 28800 seconds
|
||||
db.SetConnMaxLifetime(time.Second * time.Duration(config.ConnMaxLifetimeSecond))
|
||||
|
||||
//验证连接
|
||||
if pingerr := db.Ping(); pingerr != nil {
|
||||
pingerr = fmt.Errorf("newDataSource-->ping数据库失败:%w", pingerr)
|
||||
FuncLogError(pingerr)
|
||||
db.Close()
|
||||
return nil, pingerr
|
||||
}
|
||||
|
||||
return &dataSource{db}, nil
|
||||
}
|
||||
|
||||
// 事务参照:https://www.jianshu.com/p/2a144332c3db
|
||||
//Transaction reference: https://www.jianshu.com/p/2a144332c3db
|
||||
|
||||
// const beginStatus = 1
|
||||
|
||||
// dataBaseConnection 数据库dbConnection会话,可以原生查询或者事务
|
||||
// dataBaseConnection Database session, native query or transaction.
|
||||
type dataBaseConnection struct {
|
||||
|
||||
// 原生db
|
||||
// native db
|
||||
db *sql.DB
|
||||
// 原生事务
|
||||
// native Transaction
|
||||
tx *sql.Tx
|
||||
// 数据库配置
|
||||
config *DataSourceConfig
|
||||
|
||||
//commitSign int8 // 提交标记,控制是否提交事务
|
||||
//rollbackSign bool // 回滚标记,控制是否回滚事务
|
||||
}
|
||||
|
||||
// beginTx 开启事务
|
||||
// beginTx Open transaction
|
||||
func (dbConnection *dataBaseConnection) beginTx(ctx context.Context) error {
|
||||
//s.rollbackSign = true
|
||||
if dbConnection.tx == nil {
|
||||
|
||||
//设置事务配置,主要是隔离级别
|
||||
var txOptions *sql.TxOptions
|
||||
contextTxOptions := ctx.Value(contextTxOptionsKey)
|
||||
if contextTxOptions != nil {
|
||||
txOptions, _ = contextTxOptions.(*sql.TxOptions)
|
||||
} else {
|
||||
txOptions = dbConnection.config.DefaultTxOptions
|
||||
}
|
||||
|
||||
tx, err := dbConnection.db.BeginTx(ctx, txOptions)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("beginTx事务开启失败:%w", err)
|
||||
return err
|
||||
}
|
||||
dbConnection.tx = tx
|
||||
//s.commitSign = beginStatus
|
||||
return nil
|
||||
}
|
||||
//s.commitSign++
|
||||
return nil
|
||||
}
|
||||
|
||||
// rollback 回滚事务
|
||||
// rollback Rollback transaction
|
||||
func (dbConnection *dataBaseConnection) rollback() error {
|
||||
//if s.tx != nil && s.rollbackSign == true {
|
||||
if dbConnection.tx != nil {
|
||||
err := dbConnection.tx.Rollback()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("rollback事务回滚失败:%w", err)
|
||||
return err
|
||||
}
|
||||
dbConnection.tx = nil
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// commit 提交事务
|
||||
// commit Commit transaction
|
||||
func (dbConnection *dataBaseConnection) commit() error {
|
||||
//s.rollbackSign = false
|
||||
if dbConnection.tx == nil {
|
||||
return errors.New("commit事务为空")
|
||||
|
||||
}
|
||||
err := dbConnection.tx.Commit()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("commit事务提交失败:%w", err)
|
||||
return err
|
||||
}
|
||||
dbConnection.tx = nil
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// execContext 执行sql语句,如果已经开启事务,就以事务方式执行,如果没有开启事务,就以非事务方式执行
|
||||
// execContext Execute sql statement,If the transaction has been opened,it will be executed in transaction mode, if the transaction is not opened,it will be executed in non-transactional mode
|
||||
func (dbConnection *dataBaseConnection) execContext(ctx context.Context, execsql *string, args []interface{}) (*sql.Result, error) {
|
||||
|
||||
//打印SQL
|
||||
//print SQL
|
||||
if dbConnection.config.PrintSQL {
|
||||
//logger.Info("printSQL", logger.String("sql", execsql), logger.Any("args", args))
|
||||
FuncPrintSQL(*execsql, args)
|
||||
}
|
||||
|
||||
if dbConnection.tx != nil {
|
||||
res, reserr := dbConnection.tx.ExecContext(ctx, *execsql, args...)
|
||||
return &res, reserr
|
||||
}
|
||||
res, reserr := dbConnection.db.ExecContext(ctx, *execsql, args...)
|
||||
return &res, reserr
|
||||
}
|
||||
|
||||
// queryRowContext 如果已经开启事务,就以事务方式执行,如果没有开启事务,就以非事务方式执行
|
||||
func (dbConnection *dataBaseConnection) queryRowContext(ctx context.Context, query *string, args []interface{}) *sql.Row {
|
||||
//打印SQL
|
||||
if dbConnection.config.PrintSQL {
|
||||
//logger.Info("printSQL", logger.String("sql", query), logger.Any("args", args))
|
||||
FuncPrintSQL(*query, args)
|
||||
}
|
||||
|
||||
if dbConnection.tx != nil {
|
||||
return dbConnection.tx.QueryRowContext(ctx, *query, args...)
|
||||
}
|
||||
return dbConnection.db.QueryRowContext(ctx, *query, args...)
|
||||
}
|
||||
|
||||
// queryContext 查询数据,如果已经开启事务,就以事务方式执行,如果没有开启事务,就以非事务方式执行
|
||||
// queryRowContext Execute sql row statement,If the transaction has been opened,it will be executed in transaction mode, if the transaction is not opened,it will be executed in non-transactional mode
|
||||
func (dbConnection *dataBaseConnection) queryContext(ctx context.Context, query *string, args []interface{}) (*sql.Rows, error) {
|
||||
//打印SQL
|
||||
if dbConnection.config.PrintSQL {
|
||||
//logger.Info("printSQL", logger.String("sql", query), logger.Any("args", args))
|
||||
FuncPrintSQL(*query, args)
|
||||
}
|
||||
|
||||
if dbConnection.tx != nil {
|
||||
return dbConnection.tx.QueryContext(ctx, *query, args...)
|
||||
}
|
||||
return dbConnection.db.QueryContext(ctx, *query, args...)
|
||||
}
|
||||
|
||||
/*
|
||||
// prepareContext 预执行,如果已经开启事务,就以事务方式执行,如果没有开启事务,就以非事务方式执行
|
||||
// prepareContext Pre-execution,If the transaction has been opened,it will be executed in transaction mode,if the transaction is not opened,it will be executed in non-transactional mode
|
||||
func (dbConnection *dataBaseConnection) prepareContext(ctx context.Context, query *string) (*sql.Stmt, error) {
|
||||
//打印SQL
|
||||
//print SQL
|
||||
if dbConnection.config.PrintSQL {
|
||||
//logger.Info("printSQL", logger.String("sql", query))
|
||||
FuncPrintSQL(*query, nil)
|
||||
}
|
||||
|
||||
if dbConnection.tx != nil {
|
||||
return dbConnection.tx.PrepareContext(ctx, *query)
|
||||
}
|
||||
|
||||
return dbConnection.db.PrepareContext(ctx, *query)
|
||||
}
|
||||
*/
|
@ -1,415 +0,0 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Multiprecision decimal numbers.
|
||||
// For floating-point formatting only; not general purpose.
|
||||
// Only operations are assign and (binary) left/right shift.
|
||||
// Can do binary floating point in multiprecision decimal precisely
|
||||
// because 2 divides 10; cannot do decimal floating point
|
||||
// in multiprecision binary precisely.
|
||||
|
||||
package decimal
|
||||
|
||||
type decimal struct {
|
||||
d [800]byte // digits, big-endian representation
|
||||
nd int // number of digits used
|
||||
dp int // decimal point
|
||||
neg bool // negative flag
|
||||
trunc bool // discarded nonzero digits beyond d[:nd]
|
||||
}
|
||||
|
||||
func (a *decimal) String() string {
|
||||
n := 10 + a.nd
|
||||
if a.dp > 0 {
|
||||
n += a.dp
|
||||
}
|
||||
if a.dp < 0 {
|
||||
n += -a.dp
|
||||
}
|
||||
|
||||
buf := make([]byte, n)
|
||||
w := 0
|
||||
switch {
|
||||
case a.nd == 0:
|
||||
return "0"
|
||||
|
||||
case a.dp <= 0:
|
||||
// zeros fill space between decimal point and digits
|
||||
buf[w] = '0'
|
||||
w++
|
||||
buf[w] = '.'
|
||||
w++
|
||||
w += digitZero(buf[w : w+-a.dp])
|
||||
w += copy(buf[w:], a.d[0:a.nd])
|
||||
|
||||
case a.dp < a.nd:
|
||||
// decimal point in middle of digits
|
||||
w += copy(buf[w:], a.d[0:a.dp])
|
||||
buf[w] = '.'
|
||||
w++
|
||||
w += copy(buf[w:], a.d[a.dp:a.nd])
|
||||
|
||||
default:
|
||||
// zeros fill space between digits and decimal point
|
||||
w += copy(buf[w:], a.d[0:a.nd])
|
||||
w += digitZero(buf[w : w+a.dp-a.nd])
|
||||
}
|
||||
return string(buf[0:w])
|
||||
}
|
||||
|
||||
func digitZero(dst []byte) int {
|
||||
for i := range dst {
|
||||
dst[i] = '0'
|
||||
}
|
||||
return len(dst)
|
||||
}
|
||||
|
||||
// trim trailing zeros from number.
|
||||
// (They are meaningless; the decimal point is tracked
|
||||
// independent of the number of digits.)
|
||||
func trim(a *decimal) {
|
||||
for a.nd > 0 && a.d[a.nd-1] == '0' {
|
||||
a.nd--
|
||||
}
|
||||
if a.nd == 0 {
|
||||
a.dp = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Assign v to a.
|
||||
func (a *decimal) Assign(v uint64) {
|
||||
var buf [24]byte
|
||||
|
||||
// Write reversed decimal in buf.
|
||||
n := 0
|
||||
for v > 0 {
|
||||
v1 := v / 10
|
||||
v -= 10 * v1
|
||||
buf[n] = byte(v + '0')
|
||||
n++
|
||||
v = v1
|
||||
}
|
||||
|
||||
// Reverse again to produce forward decimal in a.d.
|
||||
a.nd = 0
|
||||
for n--; n >= 0; n-- {
|
||||
a.d[a.nd] = buf[n]
|
||||
a.nd++
|
||||
}
|
||||
a.dp = a.nd
|
||||
trim(a)
|
||||
}
|
||||
|
||||
// Maximum shift that we can do in one pass without overflow.
|
||||
// A uint has 32 or 64 bits, and we have to be able to accommodate 9<<k.
|
||||
const uintSize = 32 << (^uint(0) >> 63)
|
||||
const maxShift = uintSize - 4
|
||||
|
||||
// Binary shift right (/ 2) by k bits. k <= maxShift to avoid overflow.
|
||||
func rightShift(a *decimal, k uint) {
|
||||
r := 0 // read pointer
|
||||
w := 0 // write pointer
|
||||
|
||||
// Pick up enough leading digits to cover first shift.
|
||||
var n uint
|
||||
for ; n>>k == 0; r++ {
|
||||
if r >= a.nd {
|
||||
if n == 0 {
|
||||
// a == 0; shouldn't get here, but handle anyway.
|
||||
a.nd = 0
|
||||
return
|
||||
}
|
||||
for n>>k == 0 {
|
||||
n = n * 10
|
||||
r++
|
||||
}
|
||||
break
|
||||
}
|
||||
c := uint(a.d[r])
|
||||
n = n*10 + c - '0'
|
||||
}
|
||||
a.dp -= r - 1
|
||||
|
||||
var mask uint = (1 << k) - 1
|
||||
|
||||
// Pick up a digit, put down a digit.
|
||||
for ; r < a.nd; r++ {
|
||||
c := uint(a.d[r])
|
||||
dig := n >> k
|
||||
n &= mask
|
||||
a.d[w] = byte(dig + '0')
|
||||
w++
|
||||
n = n*10 + c - '0'
|
||||
}
|
||||
|
||||
// Put down extra digits.
|
||||
for n > 0 {
|
||||
dig := n >> k
|
||||
n &= mask
|
||||
if w < len(a.d) {
|
||||
a.d[w] = byte(dig + '0')
|
||||
w++
|
||||
} else if dig > 0 {
|
||||
a.trunc = true
|
||||
}
|
||||
n = n * 10
|
||||
}
|
||||
|
||||
a.nd = w
|
||||
trim(a)
|
||||
}
|
||||
|
||||
// Cheat sheet for left shift: table indexed by shift count giving
|
||||
// number of new digits that will be introduced by that shift.
|
||||
//
|
||||
// For example, leftcheats[4] = {2, "625"}. That means that
|
||||
// if we are shifting by 4 (multiplying by 16), it will add 2 digits
|
||||
// when the string prefix is "625" through "999", and one fewer digit
|
||||
// if the string prefix is "000" through "624".
|
||||
//
|
||||
// Credit for this trick goes to Ken.
|
||||
|
||||
type leftCheat struct {
|
||||
delta int // number of new digits
|
||||
cutoff string // minus one digit if original < a.
|
||||
}
|
||||
|
||||
var leftcheats = []leftCheat{
|
||||
// Leading digits of 1/2^i = 5^i.
|
||||
// 5^23 is not an exact 64-bit floating point number,
|
||||
// so have to use bc for the math.
|
||||
// Go up to 60 to be large enough for 32bit and 64bit platforms.
|
||||
/*
|
||||
seq 60 | sed 's/^/5^/' | bc |
|
||||
awk 'BEGIN{ print "\t{ 0, \"\" }," }
|
||||
{
|
||||
log2 = log(2)/log(10)
|
||||
printf("\t{ %d, \"%s\" },\t// * %d\n",
|
||||
int(log2*NR+1), $0, 2**NR)
|
||||
}'
|
||||
*/
|
||||
{0, ""},
|
||||
{1, "5"}, // * 2
|
||||
{1, "25"}, // * 4
|
||||
{1, "125"}, // * 8
|
||||
{2, "625"}, // * 16
|
||||
{2, "3125"}, // * 32
|
||||
{2, "15625"}, // * 64
|
||||
{3, "78125"}, // * 128
|
||||
{3, "390625"}, // * 256
|
||||
{3, "1953125"}, // * 512
|
||||
{4, "9765625"}, // * 1024
|
||||
{4, "48828125"}, // * 2048
|
||||
{4, "244140625"}, // * 4096
|
||||
{4, "1220703125"}, // * 8192
|
||||
{5, "6103515625"}, // * 16384
|
||||
{5, "30517578125"}, // * 32768
|
||||
{5, "152587890625"}, // * 65536
|
||||
{6, "762939453125"}, // * 131072
|
||||
{6, "3814697265625"}, // * 262144
|
||||
{6, "19073486328125"}, // * 524288
|
||||
{7, "95367431640625"}, // * 1048576
|
||||
{7, "476837158203125"}, // * 2097152
|
||||
{7, "2384185791015625"}, // * 4194304
|
||||
{7, "11920928955078125"}, // * 8388608
|
||||
{8, "59604644775390625"}, // * 16777216
|
||||
{8, "298023223876953125"}, // * 33554432
|
||||
{8, "1490116119384765625"}, // * 67108864
|
||||
{9, "7450580596923828125"}, // * 134217728
|
||||
{9, "37252902984619140625"}, // * 268435456
|
||||
{9, "186264514923095703125"}, // * 536870912
|
||||
{10, "931322574615478515625"}, // * 1073741824
|
||||
{10, "4656612873077392578125"}, // * 2147483648
|
||||
{10, "23283064365386962890625"}, // * 4294967296
|
||||
{10, "116415321826934814453125"}, // * 8589934592
|
||||
{11, "582076609134674072265625"}, // * 17179869184
|
||||
{11, "2910383045673370361328125"}, // * 34359738368
|
||||
{11, "14551915228366851806640625"}, // * 68719476736
|
||||
{12, "72759576141834259033203125"}, // * 137438953472
|
||||
{12, "363797880709171295166015625"}, // * 274877906944
|
||||
{12, "1818989403545856475830078125"}, // * 549755813888
|
||||
{13, "9094947017729282379150390625"}, // * 1099511627776
|
||||
{13, "45474735088646411895751953125"}, // * 2199023255552
|
||||
{13, "227373675443232059478759765625"}, // * 4398046511104
|
||||
{13, "1136868377216160297393798828125"}, // * 8796093022208
|
||||
{14, "5684341886080801486968994140625"}, // * 17592186044416
|
||||
{14, "28421709430404007434844970703125"}, // * 35184372088832
|
||||
{14, "142108547152020037174224853515625"}, // * 70368744177664
|
||||
{15, "710542735760100185871124267578125"}, // * 140737488355328
|
||||
{15, "3552713678800500929355621337890625"}, // * 281474976710656
|
||||
{15, "17763568394002504646778106689453125"}, // * 562949953421312
|
||||
{16, "88817841970012523233890533447265625"}, // * 1125899906842624
|
||||
{16, "444089209850062616169452667236328125"}, // * 2251799813685248
|
||||
{16, "2220446049250313080847263336181640625"}, // * 4503599627370496
|
||||
{16, "11102230246251565404236316680908203125"}, // * 9007199254740992
|
||||
{17, "55511151231257827021181583404541015625"}, // * 18014398509481984
|
||||
{17, "277555756156289135105907917022705078125"}, // * 36028797018963968
|
||||
{17, "1387778780781445675529539585113525390625"}, // * 72057594037927936
|
||||
{18, "6938893903907228377647697925567626953125"}, // * 144115188075855872
|
||||
{18, "34694469519536141888238489627838134765625"}, // * 288230376151711744
|
||||
{18, "173472347597680709441192448139190673828125"}, // * 576460752303423488
|
||||
{19, "867361737988403547205962240695953369140625"}, // * 1152921504606846976
|
||||
}
|
||||
|
||||
// Is the leading prefix of b lexicographically less than s?
|
||||
func prefixIsLessThan(b []byte, s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if i >= len(b) {
|
||||
return true
|
||||
}
|
||||
if b[i] != s[i] {
|
||||
return b[i] < s[i]
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Binary shift left (* 2) by k bits. k <= maxShift to avoid overflow.
|
||||
func leftShift(a *decimal, k uint) {
|
||||
delta := leftcheats[k].delta
|
||||
if prefixIsLessThan(a.d[0:a.nd], leftcheats[k].cutoff) {
|
||||
delta--
|
||||
}
|
||||
|
||||
r := a.nd // read index
|
||||
w := a.nd + delta // write index
|
||||
|
||||
// Pick up a digit, put down a digit.
|
||||
var n uint
|
||||
for r--; r >= 0; r-- {
|
||||
n += (uint(a.d[r]) - '0') << k
|
||||
quo := n / 10
|
||||
rem := n - 10*quo
|
||||
w--
|
||||
if w < len(a.d) {
|
||||
a.d[w] = byte(rem + '0')
|
||||
} else if rem != 0 {
|
||||
a.trunc = true
|
||||
}
|
||||
n = quo
|
||||
}
|
||||
|
||||
// Put down extra digits.
|
||||
for n > 0 {
|
||||
quo := n / 10
|
||||
rem := n - 10*quo
|
||||
w--
|
||||
if w < len(a.d) {
|
||||
a.d[w] = byte(rem + '0')
|
||||
} else if rem != 0 {
|
||||
a.trunc = true
|
||||
}
|
||||
n = quo
|
||||
}
|
||||
|
||||
a.nd += delta
|
||||
if a.nd >= len(a.d) {
|
||||
a.nd = len(a.d)
|
||||
}
|
||||
a.dp += delta
|
||||
trim(a)
|
||||
}
|
||||
|
||||
// Binary shift left (k > 0) or right (k < 0).
|
||||
func (a *decimal) Shift(k int) {
|
||||
switch {
|
||||
case a.nd == 0:
|
||||
// nothing to do: a == 0
|
||||
case k > 0:
|
||||
for k > maxShift {
|
||||
leftShift(a, maxShift)
|
||||
k -= maxShift
|
||||
}
|
||||
leftShift(a, uint(k))
|
||||
case k < 0:
|
||||
for k < -maxShift {
|
||||
rightShift(a, maxShift)
|
||||
k += maxShift
|
||||
}
|
||||
rightShift(a, uint(-k))
|
||||
}
|
||||
}
|
||||
|
||||
// If we chop a at nd digits, should we round up?
|
||||
func shouldRoundUp(a *decimal, nd int) bool {
|
||||
if nd < 0 || nd >= a.nd {
|
||||
return false
|
||||
}
|
||||
if a.d[nd] == '5' && nd+1 == a.nd { // exactly halfway - round to even
|
||||
// if we truncated, a little higher than what's recorded - always round up
|
||||
if a.trunc {
|
||||
return true
|
||||
}
|
||||
return nd > 0 && (a.d[nd-1]-'0')%2 != 0
|
||||
}
|
||||
// not halfway - digit tells all
|
||||
return a.d[nd] >= '5'
|
||||
}
|
||||
|
||||
// Round a to nd digits (or fewer).
|
||||
// If nd is zero, it means we're rounding
|
||||
// just to the left of the digits, as in
|
||||
// 0.09 -> 0.1.
|
||||
func (a *decimal) Round(nd int) {
|
||||
if nd < 0 || nd >= a.nd {
|
||||
return
|
||||
}
|
||||
if shouldRoundUp(a, nd) {
|
||||
a.RoundUp(nd)
|
||||
} else {
|
||||
a.RoundDown(nd)
|
||||
}
|
||||
}
|
||||
|
||||
// Round a down to nd digits (or fewer).
|
||||
func (a *decimal) RoundDown(nd int) {
|
||||
if nd < 0 || nd >= a.nd {
|
||||
return
|
||||
}
|
||||
a.nd = nd
|
||||
trim(a)
|
||||
}
|
||||
|
||||
// Round a up to nd digits (or fewer).
|
||||
func (a *decimal) RoundUp(nd int) {
|
||||
if nd < 0 || nd >= a.nd {
|
||||
return
|
||||
}
|
||||
|
||||
// round up
|
||||
for i := nd - 1; i >= 0; i-- {
|
||||
c := a.d[i]
|
||||
if c < '9' { // can stop after this digit
|
||||
a.d[i]++
|
||||
a.nd = i + 1
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Number is all 9s.
|
||||
// Change to single 1 with adjusted decimal point.
|
||||
a.d[0] = '1'
|
||||
a.nd = 1
|
||||
a.dp++
|
||||
}
|
||||
|
||||
// Extract integer part, rounded appropriately.
|
||||
// No guarantees about overflow.
|
||||
func (a *decimal) RoundedInteger() uint64 {
|
||||
if a.dp > 20 {
|
||||
return 0xFFFFFFFFFFFFFFFF
|
||||
}
|
||||
var i int
|
||||
n := uint64(0)
|
||||
for i = 0; i < a.dp && i < a.nd; i++ {
|
||||
n = n*10 + uint64(a.d[i]-'0')
|
||||
}
|
||||
for ; i < a.dp; i++ {
|
||||
n *= 10
|
||||
}
|
||||
if shouldRoundUp(a, a.dp) {
|
||||
n++
|
||||
}
|
||||
return n
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,160 +0,0 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Multiprecision decimal numbers.
|
||||
// For floating-point formatting only; not general purpose.
|
||||
// Only operations are assign and (binary) left/right shift.
|
||||
// Can do binary floating point in multiprecision decimal precisely
|
||||
// because 2 divides 10; cannot do decimal floating point
|
||||
// in multiprecision binary precisely.
|
||||
|
||||
package decimal
|
||||
|
||||
type floatInfo struct {
|
||||
mantbits uint
|
||||
expbits uint
|
||||
bias int
|
||||
}
|
||||
|
||||
var float32info = floatInfo{23, 8, -127}
|
||||
var float64info = floatInfo{52, 11, -1023}
|
||||
|
||||
// roundShortest rounds d (= mant * 2^exp) to the shortest number of digits
|
||||
// that will let the original floating point value be precisely reconstructed.
|
||||
func roundShortest(d *decimal, mant uint64, exp int, flt *floatInfo) {
|
||||
// If mantissa is zero, the number is zero; stop now.
|
||||
if mant == 0 {
|
||||
d.nd = 0
|
||||
return
|
||||
}
|
||||
|
||||
// Compute upper and lower such that any decimal number
|
||||
// between upper and lower (possibly inclusive)
|
||||
// will round to the original floating point number.
|
||||
|
||||
// We may see at once that the number is already shortest.
|
||||
//
|
||||
// Suppose d is not denormal, so that 2^exp <= d < 10^dp.
|
||||
// The closest shorter number is at least 10^(dp-nd) away.
|
||||
// The lower/upper bounds computed below are at distance
|
||||
// at most 2^(exp-mantbits).
|
||||
//
|
||||
// So the number is already shortest if 10^(dp-nd) > 2^(exp-mantbits),
|
||||
// or equivalently log2(10)*(dp-nd) > exp-mantbits.
|
||||
// It is true if 332/100*(dp-nd) >= exp-mantbits (log2(10) > 3.32).
|
||||
minexp := flt.bias + 1 // minimum possible exponent
|
||||
if exp > minexp && 332*(d.dp-d.nd) >= 100*(exp-int(flt.mantbits)) {
|
||||
// The number is already shortest.
|
||||
return
|
||||
}
|
||||
|
||||
// d = mant << (exp - mantbits)
|
||||
// Next highest floating point number is mant+1 << exp-mantbits.
|
||||
// Our upper bound is halfway between, mant*2+1 << exp-mantbits-1.
|
||||
upper := new(decimal)
|
||||
upper.Assign(mant*2 + 1)
|
||||
upper.Shift(exp - int(flt.mantbits) - 1)
|
||||
|
||||
// d = mant << (exp - mantbits)
|
||||
// Next lowest floating point number is mant-1 << exp-mantbits,
|
||||
// unless mant-1 drops the significant bit and exp is not the minimum exp,
|
||||
// in which case the next lowest is mant*2-1 << exp-mantbits-1.
|
||||
// Either way, call it mantlo << explo-mantbits.
|
||||
// Our lower bound is halfway between, mantlo*2+1 << explo-mantbits-1.
|
||||
var mantlo uint64
|
||||
var explo int
|
||||
if mant > 1<<flt.mantbits || exp == minexp {
|
||||
mantlo = mant - 1
|
||||
explo = exp
|
||||
} else {
|
||||
mantlo = mant*2 - 1
|
||||
explo = exp - 1
|
||||
}
|
||||
lower := new(decimal)
|
||||
lower.Assign(mantlo*2 + 1)
|
||||
lower.Shift(explo - int(flt.mantbits) - 1)
|
||||
|
||||
// The upper and lower bounds are possible outputs only if
|
||||
// the original mantissa is even, so that IEEE round-to-even
|
||||
// would round to the original mantissa and not the neighbors.
|
||||
inclusive := mant%2 == 0
|
||||
|
||||
// As we walk the digits we want to know whether rounding up would fall
|
||||
// within the upper bound. This is tracked by upperdelta:
|
||||
//
|
||||
// If upperdelta == 0, the digits of d and upper are the same so far.
|
||||
//
|
||||
// If upperdelta == 1, we saw a difference of 1 between d and upper on a
|
||||
// previous digit and subsequently only 9s for d and 0s for upper.
|
||||
// (Thus rounding up may fall outside the bound, if it is exclusive.)
|
||||
//
|
||||
// If upperdelta == 2, then the difference is greater than 1
|
||||
// and we know that rounding up falls within the bound.
|
||||
var upperdelta uint8
|
||||
|
||||
// Now we can figure out the minimum number of digits required.
|
||||
// Walk along until d has distinguished itself from upper and lower.
|
||||
for ui := 0; ; ui++ {
|
||||
// lower, d, and upper may have the decimal points at different
|
||||
// places. In this case upper is the longest, so we iterate from
|
||||
// ui==0 and start li and mi at (possibly) -1.
|
||||
mi := ui - upper.dp + d.dp
|
||||
if mi >= d.nd {
|
||||
break
|
||||
}
|
||||
li := ui - upper.dp + lower.dp
|
||||
l := byte('0') // lower digit
|
||||
if li >= 0 && li < lower.nd {
|
||||
l = lower.d[li]
|
||||
}
|
||||
m := byte('0') // middle digit
|
||||
if mi >= 0 {
|
||||
m = d.d[mi]
|
||||
}
|
||||
u := byte('0') // upper digit
|
||||
if ui < upper.nd {
|
||||
u = upper.d[ui]
|
||||
}
|
||||
|
||||
// Okay to round down (truncate) if lower has a different digit
|
||||
// or if lower is inclusive and is exactly the result of rounding
|
||||
// down (i.e., and we have reached the final digit of lower).
|
||||
okdown := l != m || inclusive && li+1 == lower.nd
|
||||
|
||||
switch {
|
||||
case upperdelta == 0 && m+1 < u:
|
||||
// Example:
|
||||
// m = 12345xxx
|
||||
// u = 12347xxx
|
||||
upperdelta = 2
|
||||
case upperdelta == 0 && m != u:
|
||||
// Example:
|
||||
// m = 12345xxx
|
||||
// u = 12346xxx
|
||||
upperdelta = 1
|
||||
case upperdelta == 1 && (m != '9' || u != '0'):
|
||||
// Example:
|
||||
// m = 1234598x
|
||||
// u = 1234600x
|
||||
upperdelta = 2
|
||||
}
|
||||
// Okay to round up if upper has a different digit and either upper
|
||||
// is inclusive or upper is bigger than the result of rounding up.
|
||||
okup := upperdelta > 0 && (inclusive || upperdelta > 1 || ui+1 < upper.nd)
|
||||
|
||||
// If it's okay to do either, then round to the nearest one.
|
||||
// If it's okay to do only one, do it.
|
||||
switch {
|
||||
case okdown && okup:
|
||||
d.Round(mi + 1)
|
||||
return
|
||||
case okdown:
|
||||
d.RoundDown(mi + 1)
|
||||
return
|
||||
case okup:
|
||||
d.RoundUp(mi + 1)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
@ -1,762 +0,0 @@
|
||||
package zorm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
//allowBaseTypeMap 允许基础类型查询,用于查询单个基础类型字段,例如 select id from t_user 查询返回的是字符串类型
|
||||
/*
|
||||
var allowBaseTypeMap = map[reflect.Kind]bool{
|
||||
reflect.String: true,
|
||||
|
||||
reflect.Int: true,
|
||||
reflect.Int8: true,
|
||||
reflect.Int16: true,
|
||||
reflect.Int32: true,
|
||||
reflect.Int64: true,
|
||||
|
||||
reflect.Uint: true,
|
||||
reflect.Uint8: true,
|
||||
reflect.Uint16: true,
|
||||
reflect.Uint32: true,
|
||||
reflect.Uint64: true,
|
||||
|
||||
reflect.Float32: true,
|
||||
reflect.Float64: true,
|
||||
}
|
||||
*/
|
||||
|
||||
const (
|
||||
//tag标签的名称
|
||||
tagColumnName = "column"
|
||||
|
||||
//输出字段 缓存的前缀
|
||||
exportPrefix = "_exportStructFields_"
|
||||
//私有字段 缓存的前缀
|
||||
privatePrefix = "_privateStructFields_"
|
||||
//数据库列名 缓存的前缀
|
||||
dbColumnNamePrefix = "_dbColumnName_"
|
||||
|
||||
//field对应的column的tag值 缓存的前缀
|
||||
//structFieldTagPrefix = "_structFieldTag_"
|
||||
//数据库主键 缓存的前缀
|
||||
//dbPKNamePrefix = "_dbPKName_"
|
||||
)
|
||||
|
||||
//cacheStructFieldInfoMap 用于缓存反射的信息,sync.Map内部处理了并发锁
|
||||
//var cacheStructFieldInfoMap *sync.Map = &sync.Map{}
|
||||
var cacheStructFieldInfoMap = make(map[string]map[string]reflect.StructField)
|
||||
|
||||
//用于缓存field对应的column的tag值
|
||||
//var cacheStructFieldTagInfoMap = make(map[string]map[string]string)
|
||||
|
||||
//structFieldInfo 获取StructField的信息.只对struct或者*struct判断,如果是指针,返回指针下实际的struct类型
|
||||
//第一个返回值是可以输出的字段(首字母大写),第二个是不能输出的字段(首字母小写)
|
||||
func structFieldInfo(typeOf *reflect.Type) error {
|
||||
|
||||
if typeOf == nil {
|
||||
return errors.New("structFieldInfo数据为空")
|
||||
}
|
||||
|
||||
entityName := (*typeOf).String()
|
||||
|
||||
//缓存的key
|
||||
//所有输出的属性,包含数据库字段,key是struct属性的名称,不区分大小写
|
||||
exportCacheKey := exportPrefix + entityName
|
||||
//所有私有变量的属性,key是struct属性的名称,不区分大小写
|
||||
privateCacheKey := privatePrefix + entityName
|
||||
//所有数据库的属性,key是数据库的字段名称,不区分大小写
|
||||
dbColumnCacheKey := dbColumnNamePrefix + entityName
|
||||
//structFieldTagCacheKey := structFieldTagPrefix + entityName
|
||||
//dbPKNameCacheKey := dbPKNamePrefix + entityName
|
||||
//缓存的数据库主键值
|
||||
//_, exportOk := cacheStructFieldInfoMap.Load(exportCacheKey)
|
||||
_, exportOk := cacheStructFieldInfoMap[exportCacheKey]
|
||||
//如果存在值,认为缓存中有所有的信息,不再处理
|
||||
if exportOk {
|
||||
return nil
|
||||
}
|
||||
//获取字段长度
|
||||
fieldNum := (*typeOf).NumField()
|
||||
//如果没有字段
|
||||
if fieldNum < 1 {
|
||||
return errors.New("structFieldInfo-->NumField entity没有属性")
|
||||
}
|
||||
|
||||
// 声明所有字段的载体
|
||||
var allFieldMap *sync.Map = &sync.Map{}
|
||||
anonymous := make([]reflect.StructField, 0)
|
||||
|
||||
//遍历所有字段,记录匿名属性
|
||||
for i := 0; i < fieldNum; i++ {
|
||||
field := (*typeOf).Field(i)
|
||||
if _, ok := allFieldMap.Load(field.Name); !ok {
|
||||
allFieldMap.Store(field.Name, field)
|
||||
}
|
||||
if field.Anonymous { //如果是匿名的
|
||||
anonymous = append(anonymous, field)
|
||||
}
|
||||
}
|
||||
//调用匿名struct的递归方法
|
||||
recursiveAnonymousStruct(allFieldMap, anonymous)
|
||||
|
||||
//缓存的数据
|
||||
exportStructFieldMap := make(map[string]reflect.StructField)
|
||||
privateStructFieldMap := make(map[string]reflect.StructField)
|
||||
dbColumnFieldMap := make(map[string]reflect.StructField)
|
||||
//structFieldTagMap := make(map[string]string)
|
||||
|
||||
//遍历sync.Map,要求输入一个func作为参数
|
||||
//这个函数的入参、出参的类型都已经固定,不能修改
|
||||
//可以在函数体内编写自己的代码,调用map中的k,v
|
||||
f := func(k, v interface{}) bool {
|
||||
// fmt.Println(k, ":", v)
|
||||
field := v.(reflect.StructField)
|
||||
fieldName := field.Name
|
||||
if ast.IsExported(fieldName) { //如果是可以输出的,不区分大小写
|
||||
exportStructFieldMap[strings.ToLower(fieldName)] = field
|
||||
//如果是数据库字段
|
||||
tagColumnValue := field.Tag.Get(tagColumnName)
|
||||
if len(tagColumnValue) > 0 {
|
||||
//dbColumnFieldMap[tagColumnValue] = field
|
||||
//使用数据库字段的小写,处理oracle和达梦数据库的sql返回值大写
|
||||
dbColumnFieldMap[strings.ToLower(tagColumnValue)] = field
|
||||
//structFieldTagMap[fieldName] = tagColumnValue
|
||||
}
|
||||
|
||||
} else { //私有属性
|
||||
privateStructFieldMap[strings.ToLower(fieldName)] = field
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
allFieldMap.Range(f)
|
||||
|
||||
//加入缓存
|
||||
//cacheStructFieldInfoMap.Store(exportCacheKey, exportStructFieldMap)
|
||||
//cacheStructFieldInfoMap.Store(privateCacheKey, privateStructFieldMap)
|
||||
//cacheStructFieldInfoMap.Store(dbColumnCacheKey, dbColumnFieldMap)
|
||||
|
||||
cacheStructFieldInfoMap[exportCacheKey] = exportStructFieldMap
|
||||
cacheStructFieldInfoMap[privateCacheKey] = privateStructFieldMap
|
||||
cacheStructFieldInfoMap[dbColumnCacheKey] = dbColumnFieldMap
|
||||
//cacheStructFieldTagInfoMap[structFieldTagCacheKey] = structFieldTagMap
|
||||
return nil
|
||||
}
|
||||
|
||||
//recursiveAnonymousStruct 递归调用struct的匿名属性,就近覆盖属性
|
||||
func recursiveAnonymousStruct(allFieldMap *sync.Map, anonymous []reflect.StructField) {
|
||||
|
||||
for i := 0; i < len(anonymous); i++ {
|
||||
field := anonymous[i]
|
||||
typeOf := field.Type
|
||||
|
||||
if typeOf.Kind() == reflect.Ptr {
|
||||
//获取指针下的Struct类型
|
||||
typeOf = typeOf.Elem()
|
||||
}
|
||||
|
||||
//只处理Struct类型
|
||||
if typeOf.Kind() != reflect.Struct {
|
||||
continue
|
||||
}
|
||||
|
||||
//获取字段长度
|
||||
fieldNum := typeOf.NumField()
|
||||
//如果没有字段
|
||||
if fieldNum < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
// 匿名struct里自身又有匿名struct
|
||||
anonymousField := make([]reflect.StructField, 0)
|
||||
|
||||
//遍历所有字段
|
||||
for i := 0; i < fieldNum; i++ {
|
||||
field := typeOf.Field(i)
|
||||
if _, ok := allFieldMap.Load(field.Name); ok { //如果存在属性名
|
||||
continue
|
||||
} else { //不存在属性名,加入到allFieldMap
|
||||
allFieldMap.Store(field.Name, field)
|
||||
}
|
||||
|
||||
if field.Anonymous { //匿名struct里自身又有匿名struct
|
||||
anonymousField = append(anonymousField, field)
|
||||
}
|
||||
}
|
||||
|
||||
//递归调用匿名struct
|
||||
recursiveAnonymousStruct(allFieldMap, anonymousField)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//setFieldValueByColumnName 根据数据库的字段名,找到struct映射的字段,并赋值
|
||||
func setFieldValueByColumnName(entity interface{}, columnName string, value interface{}) error {
|
||||
//先从本地缓存中查找
|
||||
typeOf := reflect.TypeOf(entity)
|
||||
valueOf := reflect.ValueOf(entity)
|
||||
if typeOf.Kind() == reflect.Ptr { //如果是指针
|
||||
typeOf = typeOf.Elem()
|
||||
valueOf = valueOf.Elem()
|
||||
}
|
||||
|
||||
dbMap, err := getDBColumnFieldMap(&typeOf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f, ok := dbMap[strings.ToLower(columnName)]
|
||||
if ok { //给主键赋值
|
||||
valueOf.FieldByName(f.Name).Set(reflect.ValueOf(value))
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
//structFieldValue 获取指定字段的值
|
||||
func structFieldValue(s interface{}, fieldName string) (interface{}, error) {
|
||||
|
||||
if s == nil || len(fieldName) < 1 {
|
||||
return nil, errors.New("structFieldValue数据为空")
|
||||
}
|
||||
//entity的s类型
|
||||
valueOf := reflect.ValueOf(s)
|
||||
|
||||
kind := valueOf.Kind()
|
||||
if !(kind == reflect.Ptr || kind == reflect.Struct) {
|
||||
return nil, errors.New("structFieldValue必须是Struct或者*Struct类型")
|
||||
}
|
||||
|
||||
if kind == reflect.Ptr {
|
||||
//获取指针下的Struct类型
|
||||
valueOf = valueOf.Elem()
|
||||
if valueOf.Kind() != reflect.Struct {
|
||||
return nil, errors.New("structFieldValue必须是Struct或者*Struct类型")
|
||||
}
|
||||
}
|
||||
|
||||
//FieldByName方法返回的是reflect.Value类型,调用Interface()方法,返回原始类型的数据值
|
||||
value := valueOf.FieldByName(fieldName).Interface()
|
||||
|
||||
return value, nil
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
//deepCopy 深度拷贝对象.golang没有构造函数,反射复制对象时,对象中struct类型的属性无法初始化,指针属性也会收到影响.使用深度对象拷贝
|
||||
func deepCopy(dst, src interface{}) error {
|
||||
var buf bytes.Buffer
|
||||
if err := gob.NewEncoder(&buf).Encode(src); err != nil {
|
||||
return err
|
||||
}
|
||||
return gob.NewDecoder(bytes.NewBuffer(buf.Bytes())).Decode(dst)
|
||||
}
|
||||
*/
|
||||
|
||||
//getDBColumnExportFieldMap 获取实体类的数据库字段,key是数据库的字段名称.同时返回所有的字段属性的map,key是实体类的属性.不区分大小写
|
||||
func getDBColumnExportFieldMap(typeOf *reflect.Type) (map[string]reflect.StructField, map[string]reflect.StructField, error) {
|
||||
dbColumnFieldMap, err := getCacheStructFieldInfoMap(typeOf, dbColumnNamePrefix)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
exportFieldMap, err := getCacheStructFieldInfoMap(typeOf, exportPrefix)
|
||||
return dbColumnFieldMap, exportFieldMap, err
|
||||
}
|
||||
|
||||
//getDBColumnFieldMap 获取实体类的数据库字段,key是数据库的字段名称.不区分大小写
|
||||
func getDBColumnFieldMap(typeOf *reflect.Type) (map[string]reflect.StructField, error) {
|
||||
return getCacheStructFieldInfoMap(typeOf, dbColumnNamePrefix)
|
||||
}
|
||||
|
||||
//getCacheStructFieldInfoMap 根据类型和key,获取缓存的字段信息
|
||||
func getCacheStructFieldInfoMap(typeOf *reflect.Type, keyPrefix string) (map[string]reflect.StructField, error) {
|
||||
if typeOf == nil {
|
||||
return nil, errors.New("getCacheStructFieldInfoMap-->typeOf不能为空")
|
||||
}
|
||||
key := keyPrefix + (*typeOf).String()
|
||||
//dbColumnFieldMap, dbOk := cacheStructFieldInfoMap.Load(key)
|
||||
dbColumnFieldMap, dbOk := cacheStructFieldInfoMap[key]
|
||||
if !dbOk { //缓存不存在
|
||||
//获取实体类的输出字段和私有 字段
|
||||
err := structFieldInfo(typeOf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//dbColumnFieldMap, dbOk = cacheStructFieldInfoMap.Load(key)
|
||||
dbColumnFieldMap, dbOk = cacheStructFieldInfoMap[key]
|
||||
if !dbOk {
|
||||
return dbColumnFieldMap, errors.New("getCacheStructFieldInfoMap()-->获取数据库字段dbColumnFieldMap异常")
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
dbMap, efOK := dbColumnFieldMap.(map[string]reflect.StructField)
|
||||
if !efOK {
|
||||
return nil, errors.New("缓存数据库字段异常")
|
||||
}
|
||||
return dbMap, nil
|
||||
*/
|
||||
return dbColumnFieldMap, nil
|
||||
}
|
||||
|
||||
/*
|
||||
//获取 fileName 属性 中 tag column的值
|
||||
func getStructFieldTagColumnValue(typeOf reflect.Type, fieldName string) string {
|
||||
entityName := typeOf.String()
|
||||
structFieldTagMap, dbOk := cacheStructFieldTagInfoMap[structFieldTagPrefix+entityName]
|
||||
if !dbOk { //缓存不存在
|
||||
//获取实体类的输出字段和私有 字段
|
||||
err := structFieldInfo(typeOf)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
structFieldTagMap, dbOk = cacheStructFieldTagInfoMap[structFieldTagPrefix+entityName]
|
||||
}
|
||||
|
||||
return structFieldTagMap[fieldName]
|
||||
}
|
||||
*/
|
||||
|
||||
//columnAndValue 根据保存的对象,返回插入的语句,需要插入的字段,字段的值
|
||||
func columnAndValue(entity interface{}) (reflect.Type, []reflect.StructField, []interface{}, error) {
|
||||
typeOf, checkerr := checkEntityKind(entity)
|
||||
if checkerr != nil {
|
||||
return typeOf, nil, nil, checkerr
|
||||
}
|
||||
// 获取实体类的反射,指针下的struct
|
||||
valueOf := reflect.ValueOf(entity).Elem()
|
||||
//reflect.Indirect
|
||||
|
||||
//先从本地缓存中查找
|
||||
//typeOf := reflect.TypeOf(entity).Elem()
|
||||
|
||||
dbMap, err := getDBColumnFieldMap(&typeOf)
|
||||
if err != nil {
|
||||
return typeOf, nil, nil, err
|
||||
}
|
||||
|
||||
//实体类公开字段的长度
|
||||
fLen := len(dbMap)
|
||||
//接收列的数组,这里是做一个副本,避免外部更改掉原始的列信息
|
||||
columns := make([]reflect.StructField, 0, fLen)
|
||||
//接收值的数组
|
||||
values := make([]interface{}, 0, fLen)
|
||||
|
||||
//遍历所有数据库属性
|
||||
for _, field := range dbMap {
|
||||
//获取字段类型的Kind
|
||||
// fieldKind := field.Type.Kind()
|
||||
//if !allowTypeMap[fieldKind] { //不允许的类型
|
||||
// continue
|
||||
//}
|
||||
|
||||
columns = append(columns, field)
|
||||
//FieldByName方法返回的是reflect.Value类型,调用Interface()方法,返回原始类型的数据值.字段不会重名,不使用FieldByIndex()函数
|
||||
value := valueOf.FieldByName(field.Name).Interface()
|
||||
|
||||
/*
|
||||
if value != nil { //如果不是nil
|
||||
timeValue, ok := value.(time.Time)
|
||||
if ok && timeValue.IsZero() { //如果是日期零时,需要设置一个初始值1970-01-01 00:00:01,兼容数据库
|
||||
value = defaultZeroTime
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
//添加到记录值的数组
|
||||
values = append(values, value)
|
||||
|
||||
}
|
||||
|
||||
//缓存数据库的列
|
||||
|
||||
return typeOf, columns, values, nil
|
||||
|
||||
}
|
||||
|
||||
//entityPKFieldName 获取实体类主键属性名称
|
||||
func entityPKFieldName(entity IEntityStruct, typeOf *reflect.Type) (string, error) {
|
||||
|
||||
//检查是否是指针对象
|
||||
//typeOf, checkerr := checkEntityKind(entity)
|
||||
//if checkerr != nil {
|
||||
// return "", checkerr
|
||||
//}
|
||||
|
||||
//缓存的key,TypeOf和ValueOf的String()方法,返回值不一样
|
||||
//typeOf := reflect.TypeOf(entity).Elem()
|
||||
|
||||
dbMap, err := getDBColumnFieldMap(typeOf)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
field := dbMap[strings.ToLower(entity.GetPKColumnName())]
|
||||
return field.Name, nil
|
||||
|
||||
}
|
||||
|
||||
//checkEntityKind 检查entity类型必须是*struct类型或者基础类型的指针
|
||||
func checkEntityKind(entity interface{}) (reflect.Type, error) {
|
||||
if entity == nil {
|
||||
return nil, errors.New("checkEntityKind参数不能为空,必须是*struct类型或者基础类型的指针")
|
||||
}
|
||||
typeOf := reflect.TypeOf(entity)
|
||||
if typeOf.Kind() != reflect.Ptr { //如果不是指针
|
||||
return nil, errors.New("checkEntityKind必须是*struct类型或者基础类型的指针")
|
||||
}
|
||||
typeOf = typeOf.Elem()
|
||||
//if !(typeOf.Kind() == reflect.Struct || allowBaseTypeMap[typeOf.Kind()]) { //如果不是指针
|
||||
// return nil, errors.New("checkEntityKind必须是*struct类型或者基础类型的指针")
|
||||
//}
|
||||
return typeOf, nil
|
||||
}
|
||||
|
||||
// sqlRowsValues 包装接收sqlRows的Values数组,反射rows屏蔽数据库null值
|
||||
// fix:converting NULL to int is unsupported
|
||||
// 当读取数据库的值为NULL时,由于基本类型不支持为NULL,通过反射将未知driver.Value改为interface{},不再映射到struct实体类
|
||||
// 感谢@fastabler提交的pr
|
||||
func sqlRowsValues(rows *sql.Rows, driverValue *reflect.Value, columnTypes []*sql.ColumnType, dbColumnFieldMap map[string]reflect.StructField, exportFieldMap map[string]reflect.StructField, valueOf *reflect.Value, finder *Finder, cdvMapHasBool bool) error {
|
||||
//声明载体数组,用于存放struct的属性指针
|
||||
//Declare a carrier array to store the attribute pointer of the struct
|
||||
values := make([]interface{}, len(columnTypes))
|
||||
|
||||
//记录需要类型转换的字段信息
|
||||
var fieldTempDriverValueMap map[reflect.Value]*driverValueInfo
|
||||
if cdvMapHasBool {
|
||||
fieldTempDriverValueMap = make(map[reflect.Value]*driverValueInfo)
|
||||
}
|
||||
|
||||
//反射获取 []driver.Value的值
|
||||
//driverValue := reflect.Indirect(reflect.ValueOf(rows))
|
||||
//driverValue = driverValue.FieldByName("lastcols")
|
||||
//遍历数据库的列名
|
||||
//Traverse the database column names
|
||||
for i, columnType := range columnTypes {
|
||||
columnName := strings.ToLower(columnType.Name())
|
||||
//从缓存中获取列名的field字段
|
||||
//Get the field field of the column name from the cache
|
||||
field, fok := dbColumnFieldMap[columnName]
|
||||
//如果列名不存在,就尝试去获取实体类属性,兼容处理临时字段,如果还找不到,就初始化为空指
|
||||
//If the column name does not exist, initialize a null value
|
||||
if !fok {
|
||||
field, fok = exportFieldMap[columnName]
|
||||
if !fok {
|
||||
//尝试驼峰
|
||||
cname := strings.ReplaceAll(columnName, "_", "")
|
||||
field, fok = exportFieldMap[cname]
|
||||
if !fok {
|
||||
values[i] = new(interface{})
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
dv := driverValue.Index(i)
|
||||
if dv.IsValid() && dv.InterfaceData()[0] == 0 { // 该字段的数据库值是null,取默认值
|
||||
values[i] = new(interface{})
|
||||
} else {
|
||||
|
||||
//字段的反射值
|
||||
fieldValue := valueOf.FieldByName(field.Name)
|
||||
//根据接收的类型,获取到类型转换的接口实现
|
||||
var converFunc CustomDriverValueConver
|
||||
var converOK = false
|
||||
if cdvMapHasBool {
|
||||
converFunc, converOK = CustomDriverValueMap[dv.Elem().Type().String()]
|
||||
}
|
||||
|
||||
//类型转换的临时值
|
||||
var tempDriverValue driver.Value
|
||||
var errGetDriverValue error
|
||||
//如果需要类型转换
|
||||
if converOK {
|
||||
//获取需要转换的临时值
|
||||
typeOf := fieldValue.Type()
|
||||
tempDriverValue, errGetDriverValue = converFunc.GetDriverValue(columnType, &typeOf, finder)
|
||||
if errGetDriverValue != nil {
|
||||
errGetDriverValue = fmt.Errorf("sqlRowsValues-->conver.GetDriverValue异常:%w", errGetDriverValue)
|
||||
FuncLogError(errGetDriverValue)
|
||||
return errGetDriverValue
|
||||
}
|
||||
//如果需要类型转换
|
||||
if tempDriverValue != nil {
|
||||
values[i] = tempDriverValue
|
||||
dvinfo := driverValueInfo{}
|
||||
dvinfo.converFunc = converFunc
|
||||
dvinfo.columnType = columnType
|
||||
dvinfo.tempDriverValue = tempDriverValue
|
||||
fieldTempDriverValueMap[fieldValue] = &dvinfo
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
//不需要类型转换,正常逻辑
|
||||
//获取struct的属性值的指针地址,字段不会重名,不使用FieldByIndex()函数
|
||||
//Get the pointer address of the attribute value of the struct,the field will not have the same name, and the Field By Index() function is not used
|
||||
value := fieldValue.Addr().Interface()
|
||||
//把指针地址放到数组
|
||||
//Put the pointer address into the array
|
||||
values[i] = value
|
||||
}
|
||||
}
|
||||
//scan赋值.是一个指针数组,已经根据struct的属性类型初始化了,sql驱动能感知到参数类型,所以可以直接赋值给struct的指针.这样struct的属性就有值了
|
||||
//Scan assignment. It is an array of pointers that has been initialized according to the attribute type of the struct.The sql driver can perceive the parameter type,so it can be directly assigned to the pointer of the struct. In this way, the attributes of the struct have values
|
||||
scanerr := rows.Scan(values...)
|
||||
if scanerr != nil {
|
||||
return scanerr
|
||||
}
|
||||
|
||||
//循环需要替换的值
|
||||
for fieldValue, driverValueInfo := range fieldTempDriverValueMap {
|
||||
//根据列名,字段类型,新值 返回符合接收类型值的指针,返回值是个指针,指针,指针!!!!
|
||||
typeOf := fieldValue.Type()
|
||||
rightValue, errConverDriverValue := driverValueInfo.converFunc.ConverDriverValue(driverValueInfo.columnType, &typeOf, driverValueInfo.tempDriverValue, finder)
|
||||
if errConverDriverValue != nil {
|
||||
errConverDriverValue = fmt.Errorf("sqlRowsValues-->conver.ConverDriverValue异常:%w", errConverDriverValue)
|
||||
FuncLogError(errConverDriverValue)
|
||||
return errConverDriverValue
|
||||
}
|
||||
//给字段赋值
|
||||
fieldValue.Set(reflect.ValueOf(rightValue).Elem())
|
||||
}
|
||||
|
||||
return scanerr
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
// sqlRowsValuesFast 包装接收sqlRows的Values数组,快速模式,数据库表不能有null值
|
||||
// Deprecated: 暂时不用
|
||||
func sqlRowsValuesFast(rows *sql.Rows, columns []string, dbColumnFieldMap map[string]reflect.StructField, valueOf reflect.Value) error {
|
||||
//声明载体数组,用于存放struct的属性指针
|
||||
values := make([]interface{}, len(columns))
|
||||
|
||||
//遍历数据库的列名
|
||||
for i, column := range columns {
|
||||
//从缓存中获取列名的field字段
|
||||
field, fok := dbColumnFieldMap[column]
|
||||
if !fok { //如果列名不存在,就初始化一个空值
|
||||
values[i] = new(interface{})
|
||||
continue
|
||||
}
|
||||
|
||||
//获取struct的属性值的指针地址,字段不会重名,不使用FieldByIndex()函数
|
||||
value := valueOf.FieldByName(field.Name).Addr().Interface()
|
||||
//把指针地址放到数组
|
||||
values[i] = value
|
||||
}
|
||||
//scan赋值.是一个指针数组,已经根据struct的属性类型初始化了,sql驱动能感知到参数类型,所以可以直接赋值给struct的指针.这样struct的属性就有值了
|
||||
scanerr := rows.Scan(values...)
|
||||
if scanerr != nil {
|
||||
scanerr = fmt.Errorf("rows.Scan异常:%w", scanerr)
|
||||
FuncLogError(scanerr)
|
||||
return scanerr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// sqlRowsValues 包装接收sqlRows的Values数组
|
||||
// 基础类型使用sql.Nullxxx替换,放到sqlNullMap[field.name]*sql.Nullxxx,用于接受数据库的值,用于处理数据库为null的情况,然后再重新替换回去
|
||||
// Deprecated: 暂时不用
|
||||
func sqlRowsValues2(rows *sql.Rows, columns []string, dbColumnFieldMap map[string]reflect.StructField, valueOf reflect.Value) error {
|
||||
//声明载体数组,用于存放struct的属性指针
|
||||
values := make([]interface{}, len(columns))
|
||||
|
||||
//基础类型使用sql.Nullxxx替换,放到sqlNullMap[field.name]*sql.Nullxxx,用于接受数据库的值,用于处理数据库为null的情况,然后再重新替换回去
|
||||
sqlNullMap := make(map[string]interface{})
|
||||
|
||||
//遍历数据库的列名
|
||||
for i, column := range columns {
|
||||
//从缓存中获取列名的field字段
|
||||
field, fok := dbColumnFieldMap[column]
|
||||
if !fok { //如果列名不存在,就初始化一个空值
|
||||
values[i] = new(interface{})
|
||||
continue
|
||||
}
|
||||
//values 中的值
|
||||
var value interface{}
|
||||
//struct中的字段属性名称
|
||||
//fieldName := field.Name
|
||||
|
||||
//fmt.Println(fieldName, "----", field.Type, "--------", field.Type.Kind(), "++++", field.Type.String())
|
||||
ftypeString := field.Type.String()
|
||||
fkind := field.Type.Kind()
|
||||
//判断字段类型
|
||||
switch fkind {
|
||||
case reflect.String:
|
||||
value = &sql.NullString{}
|
||||
sqlNullMap[column] = value
|
||||
case reflect.Int8, reflect.Int16, reflect.Int, reflect.Int32:
|
||||
value = &sql.NullInt32{}
|
||||
sqlNullMap[column] = value
|
||||
case reflect.Int64:
|
||||
value = &sql.NullInt64{}
|
||||
sqlNullMap[column] = value
|
||||
case reflect.Float32, reflect.Float64:
|
||||
value = &sql.NullFloat64{}
|
||||
sqlNullMap[column] = value
|
||||
case reflect.Bool:
|
||||
value = &sql.NullBool{}
|
||||
sqlNullMap[column] = value
|
||||
case reflect.Struct:
|
||||
if ftypeString == "time.Time" {
|
||||
value = &sql.NullTime{}
|
||||
sqlNullMap[column] = value
|
||||
} else if ftypeString == "decimal.Decimal" {
|
||||
value = &decimal.NullDecimal{}
|
||||
sqlNullMap[column] = value
|
||||
} else {
|
||||
//获取struct的属性值的指针地址,字段不会重名,不使用FieldByIndex()函数
|
||||
value = valueOf.FieldByName(field.Name).Addr().Interface()
|
||||
}
|
||||
sqlNullMap[column] = value
|
||||
default:
|
||||
//获取struct的属性值的指针地址,字段不会重名,不使用FieldByIndex()函数
|
||||
value = valueOf.FieldByName(field.Name).Addr().Interface()
|
||||
}
|
||||
|
||||
//把指针地址放到数组
|
||||
values[i] = value
|
||||
}
|
||||
//scan赋值.是一个指针数组,已经根据struct的属性类型初始化了,sql驱动能感知到参数类型,所以可以直接赋值给struct的指针.这样struct的属性就有值了
|
||||
scanerr := rows.Scan(values...)
|
||||
if scanerr != nil {
|
||||
return scanerr
|
||||
}
|
||||
|
||||
//循环需要处理的字段
|
||||
for column, value := range sqlNullMap {
|
||||
//从缓存中获取列名的field字段
|
||||
field, fok := dbColumnFieldMap[column]
|
||||
if !fok { //如果列名不存在,就初始化一个空值
|
||||
continue
|
||||
}
|
||||
ftypeString := field.Type.String()
|
||||
fkind := field.Type.Kind()
|
||||
fieldName := field.Name
|
||||
//判断字段类型
|
||||
switch fkind {
|
||||
case reflect.String:
|
||||
vptr, ok := value.(*sql.NullString)
|
||||
if vptr.Valid && ok {
|
||||
valueOf.FieldByName(fieldName).SetString(vptr.String)
|
||||
}
|
||||
case reflect.Int8, reflect.Int16, reflect.Int, reflect.Int32:
|
||||
vptr, ok := value.(*sql.NullInt32)
|
||||
if vptr.Valid && ok {
|
||||
valueOf.FieldByName(fieldName).Set(reflect.ValueOf(int(vptr.Int32)))
|
||||
}
|
||||
case reflect.Int64:
|
||||
vptr, ok := value.(*sql.NullInt64)
|
||||
if vptr.Valid && ok {
|
||||
valueOf.FieldByName(fieldName).SetInt(vptr.Int64)
|
||||
}
|
||||
case reflect.Float32:
|
||||
vptr, ok := value.(*sql.NullFloat64)
|
||||
if vptr.Valid && ok {
|
||||
valueOf.FieldByName(fieldName).Set(reflect.ValueOf(float32(vptr.Float64)))
|
||||
}
|
||||
case reflect.Float64:
|
||||
vptr, ok := value.(*sql.NullFloat64)
|
||||
if vptr.Valid && ok {
|
||||
valueOf.FieldByName(fieldName).SetFloat(vptr.Float64)
|
||||
}
|
||||
case reflect.Bool:
|
||||
vptr, ok := value.(*sql.NullBool)
|
||||
if vptr.Valid && ok {
|
||||
valueOf.FieldByName(fieldName).SetBool(vptr.Bool)
|
||||
}
|
||||
case reflect.Struct:
|
||||
if ftypeString == "time.Time" {
|
||||
vptr, ok := value.(*sql.NullTime)
|
||||
if vptr.Valid && ok {
|
||||
valueOf.FieldByName(fieldName).Set(reflect.ValueOf(vptr.Time))
|
||||
}
|
||||
} else if ftypeString == "decimal.Decimal" {
|
||||
vptr, ok := value.(*decimal.NullDecimal)
|
||||
if vptr.Valid && ok {
|
||||
valueOf.FieldByName(fieldName).Set(reflect.ValueOf(vptr.Decimal))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return scanerr
|
||||
}
|
||||
*/
|
||||
|
||||
//CustomDriverValueMap 用于配置driver.Value和对应的处理关系,key是 drier.Value 的字符串,例如 *dm.DmClob
|
||||
//一般是放到init方法里进行添加
|
||||
var CustomDriverValueMap = make(map[string]CustomDriverValueConver)
|
||||
|
||||
//CustomDriverValueConver 自定义类型转化接口,用于解决 类似达梦 text --> dm.DmClob --> string类型接收的问题
|
||||
type CustomDriverValueConver interface {
|
||||
//GetDriverValue 根据数据库列类型,实体类属性类型,Finder对象,返回driver.Value的实例
|
||||
//如果无法获取到structFieldType,例如Map查询,会传入nil
|
||||
//如果返回值为nil,接口扩展逻辑无效,使用原生的方式接收数据库字段值
|
||||
GetDriverValue(columnType *sql.ColumnType, structFieldType *reflect.Type, finder *Finder) (driver.Value, error)
|
||||
|
||||
//ConverDriverValue 数据库列类型,实体类属性类型,GetDriverValue返回的driver.Value的临时接收值,Finder对象
|
||||
//如果无法获取到structFieldType,例如Map查询,会传入nil
|
||||
//返回符合接收类型值的指针,指针,指针!!!!
|
||||
ConverDriverValue(columnType *sql.ColumnType, structFieldType *reflect.Type, tempDriverValue driver.Value, finder *Finder) (interface{}, error)
|
||||
}
|
||||
type driverValueInfo struct {
|
||||
converFunc CustomDriverValueConver
|
||||
columnType *sql.ColumnType
|
||||
tempDriverValue interface{}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
//实现CustomDriverValueConver接口,扩展自定义类型,例如 达梦数据库text类型,映射出来的是dm.DmClob类型,无法使用string类型直接接收
|
||||
type CustomDMText struct{}
|
||||
//GetDriverValue 根据数据库列类型,实体类属性类型,Finder对象,返回driver.Value的实例
|
||||
//如果无法获取到structFieldType,例如Map查询,会传入nil
|
||||
//如果返回值为nil,接口扩展逻辑无效,使用原生的方式接收数据库字段值
|
||||
func (dmtext CustomDMText) GetDriverValue(columnType *sql.ColumnType, structFieldType reflect.Type, finder *zorm.Finder) (driver.Value, error) {
|
||||
return &dm.DmClob{}, nil
|
||||
}
|
||||
//ConverDriverValue 数据库列类型,实体类属性类型,GetDriverValue返回的driver.Value的临时接收值,Finder对象
|
||||
//如果无法获取到structFieldType,例如Map查询,会传入nil
|
||||
//返回符合接收类型值的指针,指针,指针!!!!
|
||||
func (dmtext CustomDMText) ConverDriverValue(columnType *sql.ColumnType, structFieldType reflect.Type, tempDriverValue driver.Value, finder *zorm.Finder) (interface{}, error) {
|
||||
//类型转换
|
||||
dmClob, isok := tempDriverValue.(*dm.DmClob)
|
||||
if !isok {
|
||||
return tempDriverValue, errors.New("转换至*dm.DmClob类型失败")
|
||||
}
|
||||
|
||||
//获取长度
|
||||
dmlen, errLength := dmClob.GetLength()
|
||||
if errLength != nil {
|
||||
return dmClob, errLength
|
||||
}
|
||||
|
||||
//int64转成int类型
|
||||
strInt64 := strconv.FormatInt(dmlen, 10)
|
||||
dmlenInt, errAtoi := strconv.Atoi(strInt64)
|
||||
if errAtoi != nil {
|
||||
return dmClob, errAtoi
|
||||
}
|
||||
|
||||
//读取字符串
|
||||
str, errReadString := dmClob.ReadString(1, dmlenInt)
|
||||
return &str, errReadString
|
||||
}
|
||||
//CustomDriverValueMap 用于配置driver.Value和对应的处理关系,key是 drier.Value 的字符串,例如 *dm.DmClob
|
||||
//一般是放到init方法里进行添加
|
||||
zorm.CustomDriverValueMap["*dm.DmClob"] = CustomDMText{}
|
||||
|
||||
**/
|
@ -1,471 +0,0 @@
|
||||
package zorm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"gitee.com/chunanyong/zorm/decimal"
|
||||
)
|
||||
|
||||
func typeConvertFloat32(i interface{}) float32 {
|
||||
if i == nil {
|
||||
return 0
|
||||
}
|
||||
if v, ok := i.(float32); ok {
|
||||
return v
|
||||
}
|
||||
v, _ := strconv.ParseFloat(typeConvertString(i), 32)
|
||||
return float32(v)
|
||||
}
|
||||
|
||||
func typeConvertFloat64(i interface{}) float64 {
|
||||
if i == nil {
|
||||
return 0
|
||||
}
|
||||
if v, ok := i.(float64); ok {
|
||||
return v
|
||||
}
|
||||
v, _ := strconv.ParseFloat(typeConvertString(i), 64)
|
||||
return v
|
||||
}
|
||||
|
||||
func typeConvertDecimal(i interface{}) decimal.Decimal {
|
||||
if i == nil {
|
||||
return decimal.Zero
|
||||
}
|
||||
if v, ok := i.(decimal.Decimal); ok {
|
||||
return v
|
||||
}
|
||||
v, _ := decimal.NewFromString(typeConvertString(i))
|
||||
return v
|
||||
}
|
||||
|
||||
func typeConvertInt64toInt(from int64) (int, error) {
|
||||
//int64 转 int
|
||||
strInt64 := strconv.FormatInt(from, 10)
|
||||
to, err := strconv.Atoi(strInt64)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return to, err
|
||||
}
|
||||
|
||||
func typeConvertTime(i interface{}, format string, TZLocation ...*time.Location) time.Time {
|
||||
s := typeConvertString(i)
|
||||
t, _ := typeConvertStrToTime(s, format, TZLocation...)
|
||||
return t
|
||||
}
|
||||
|
||||
func typeConvertStrToTime(str string, format string, TZLocation ...*time.Location) (time.Time, error) {
|
||||
if len(TZLocation) > 0 {
|
||||
t, err := time.ParseInLocation(format, str, TZLocation[0])
|
||||
if err == nil {
|
||||
return t, nil
|
||||
}
|
||||
return time.Time{}, err
|
||||
|
||||
}
|
||||
t, err := time.ParseInLocation(format, str, time.Local)
|
||||
if err == nil {
|
||||
return t, nil
|
||||
}
|
||||
return time.Time{}, err
|
||||
|
||||
}
|
||||
|
||||
func typeConvertInt64(i interface{}) int64 {
|
||||
if i == nil {
|
||||
return 0
|
||||
}
|
||||
if v, ok := i.(int64); ok {
|
||||
return v
|
||||
}
|
||||
return int64(typeConvertInt(i))
|
||||
}
|
||||
|
||||
func typeConvertString(i interface{}) string {
|
||||
if i == nil {
|
||||
return ""
|
||||
}
|
||||
switch value := i.(type) {
|
||||
case int:
|
||||
return strconv.Itoa(value)
|
||||
case int8:
|
||||
return strconv.Itoa(int(value))
|
||||
case int16:
|
||||
return strconv.Itoa(int(value))
|
||||
case int32:
|
||||
return strconv.Itoa(int(value))
|
||||
case int64:
|
||||
return strconv.Itoa(int(value))
|
||||
case uint:
|
||||
return strconv.FormatUint(uint64(value), 10)
|
||||
case uint8:
|
||||
return strconv.FormatUint(uint64(value), 10)
|
||||
case uint16:
|
||||
return strconv.FormatUint(uint64(value), 10)
|
||||
case uint32:
|
||||
return strconv.FormatUint(uint64(value), 10)
|
||||
case uint64:
|
||||
return strconv.FormatUint(uint64(value), 10)
|
||||
case float32:
|
||||
return strconv.FormatFloat(float64(value), 'f', -1, 32)
|
||||
case float64:
|
||||
return strconv.FormatFloat(value, 'f', -1, 64)
|
||||
case bool:
|
||||
return strconv.FormatBool(value)
|
||||
case string:
|
||||
return value
|
||||
case []byte:
|
||||
return string(value)
|
||||
default:
|
||||
return fmt.Sprintf("%v", value)
|
||||
}
|
||||
}
|
||||
|
||||
//false: "", 0, false, off
|
||||
func typeConvertBool(i interface{}) bool {
|
||||
if i == nil {
|
||||
return false
|
||||
}
|
||||
if v, ok := i.(bool); ok {
|
||||
return v
|
||||
}
|
||||
if s := typeConvertString(i); s != "" && s != "0" && s != "false" && s != "off" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func typeConvertInt(i interface{}) int {
|
||||
if i == nil {
|
||||
return 0
|
||||
}
|
||||
switch value := i.(type) {
|
||||
case int:
|
||||
return value
|
||||
case int8:
|
||||
return int(value)
|
||||
case int16:
|
||||
return int(value)
|
||||
case int32:
|
||||
return int(value)
|
||||
case int64:
|
||||
return int(value)
|
||||
case uint:
|
||||
return int(value)
|
||||
case uint8:
|
||||
return int(value)
|
||||
case uint16:
|
||||
return int(value)
|
||||
case uint32:
|
||||
return int(value)
|
||||
case uint64:
|
||||
return int(value)
|
||||
case float32:
|
||||
return int(value)
|
||||
case float64:
|
||||
return int(value)
|
||||
case bool:
|
||||
if value {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
default:
|
||||
v, _ := strconv.Atoi(typeConvertString(value))
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func encodeString(s string) []byte {
|
||||
return []byte(s)
|
||||
}
|
||||
|
||||
func decodeToString(b []byte) string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func encodeBool(b bool) []byte {
|
||||
if b {
|
||||
return []byte{1}
|
||||
}
|
||||
return []byte{0}
|
||||
|
||||
}
|
||||
|
||||
func encodeInt(i int) []byte {
|
||||
if i <= math.MaxInt8 {
|
||||
return encodeInt8(int8(i))
|
||||
} else if i <= math.MaxInt16 {
|
||||
return encodeInt16(int16(i))
|
||||
} else if i <= math.MaxInt32 {
|
||||
return encodeInt32(int32(i))
|
||||
} else {
|
||||
return encodeInt64(int64(i))
|
||||
}
|
||||
}
|
||||
|
||||
func encodeUint(i uint) []byte {
|
||||
if i <= math.MaxUint8 {
|
||||
return encodeUint8(uint8(i))
|
||||
} else if i <= math.MaxUint16 {
|
||||
return encodeUint16(uint16(i))
|
||||
} else if i <= math.MaxUint32 {
|
||||
return encodeUint32(uint32(i))
|
||||
} else {
|
||||
return encodeUint64(uint64(i))
|
||||
}
|
||||
}
|
||||
|
||||
func encodeInt8(i int8) []byte {
|
||||
return []byte{byte(i)}
|
||||
}
|
||||
|
||||
func encodeUint8(i uint8) []byte {
|
||||
return []byte{byte(i)}
|
||||
}
|
||||
|
||||
func encodeInt16(i int16) []byte {
|
||||
bytes := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(bytes, uint16(i))
|
||||
return bytes
|
||||
}
|
||||
|
||||
func encodeUint16(i uint16) []byte {
|
||||
bytes := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(bytes, i)
|
||||
return bytes
|
||||
}
|
||||
|
||||
func encodeInt32(i int32) []byte {
|
||||
bytes := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(bytes, uint32(i))
|
||||
return bytes
|
||||
}
|
||||
|
||||
func encodeUint32(i uint32) []byte {
|
||||
bytes := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(bytes, i)
|
||||
return bytes
|
||||
}
|
||||
|
||||
func encodeInt64(i int64) []byte {
|
||||
bytes := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(bytes, uint64(i))
|
||||
return bytes
|
||||
}
|
||||
|
||||
func encodeUint64(i uint64) []byte {
|
||||
bytes := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(bytes, i)
|
||||
return bytes
|
||||
}
|
||||
|
||||
func encodeFloat32(f float32) []byte {
|
||||
bits := math.Float32bits(f)
|
||||
bytes := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(bytes, bits)
|
||||
return bytes
|
||||
}
|
||||
|
||||
func encodeFloat64(f float64) []byte {
|
||||
bits := math.Float64bits(f)
|
||||
bytes := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(bytes, bits)
|
||||
return bytes
|
||||
}
|
||||
|
||||
func encode(vs ...interface{}) []byte {
|
||||
buf := new(bytes.Buffer)
|
||||
for i := 0; i < len(vs); i++ {
|
||||
switch value := vs[i].(type) {
|
||||
case int:
|
||||
buf.Write(encodeInt(value))
|
||||
case int8:
|
||||
buf.Write(encodeInt8(value))
|
||||
case int16:
|
||||
buf.Write(encodeInt16(value))
|
||||
case int32:
|
||||
buf.Write(encodeInt32(value))
|
||||
case int64:
|
||||
buf.Write(encodeInt64(value))
|
||||
case uint:
|
||||
buf.Write(encodeUint(value))
|
||||
case uint8:
|
||||
buf.Write(encodeUint8(value))
|
||||
case uint16:
|
||||
buf.Write(encodeUint16(value))
|
||||
case uint32:
|
||||
buf.Write(encodeUint32(value))
|
||||
case uint64:
|
||||
buf.Write(encodeUint64(value))
|
||||
case bool:
|
||||
buf.Write(encodeBool(value))
|
||||
case string:
|
||||
buf.Write(encodeString(value))
|
||||
case []byte:
|
||||
buf.Write(value)
|
||||
case float32:
|
||||
buf.Write(encodeFloat32(value))
|
||||
case float64:
|
||||
buf.Write(encodeFloat64(value))
|
||||
default:
|
||||
if err := binary.Write(buf, binary.LittleEndian, value); err != nil {
|
||||
buf.Write(encodeString(fmt.Sprintf("%v", value)))
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func isNumeric(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] < byte('0') || s[i] > byte('9') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
func typeConvertTimeDuration(i interface{}) time.Duration {
|
||||
return time.Duration(typeConvertInt64(i))
|
||||
}
|
||||
|
||||
func typeConvertBytes(i interface{}) []byte {
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
if r, ok := i.([]byte); ok {
|
||||
return r
|
||||
}
|
||||
return encode(i)
|
||||
|
||||
}
|
||||
|
||||
func typeConvertStrings(i interface{}) []string {
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
if r, ok := i.([]string); ok {
|
||||
return r
|
||||
} else if r, ok := i.([]interface{}); ok {
|
||||
strs := make([]string, len(r))
|
||||
for k, v := range r {
|
||||
strs[k] = typeConvertString(v)
|
||||
}
|
||||
return strs
|
||||
}
|
||||
return []string{fmt.Sprintf("%v", i)}
|
||||
}
|
||||
|
||||
func typeConvertInt8(i interface{}) int8 {
|
||||
if i == nil {
|
||||
return 0
|
||||
}
|
||||
if v, ok := i.(int8); ok {
|
||||
return v
|
||||
}
|
||||
return int8(typeConvertInt(i))
|
||||
}
|
||||
|
||||
func typeConvertInt16(i interface{}) int16 {
|
||||
if i == nil {
|
||||
return 0
|
||||
}
|
||||
if v, ok := i.(int16); ok {
|
||||
return v
|
||||
}
|
||||
return int16(typeConvertInt(i))
|
||||
}
|
||||
|
||||
func typeConvertInt32(i interface{}) int32 {
|
||||
if i == nil {
|
||||
return 0
|
||||
}
|
||||
if v, ok := i.(int32); ok {
|
||||
return v
|
||||
}
|
||||
return int32(typeConvertInt(i))
|
||||
}
|
||||
|
||||
func typeConvertUint(i interface{}) uint {
|
||||
if i == nil {
|
||||
return 0
|
||||
}
|
||||
switch value := i.(type) {
|
||||
case int:
|
||||
return uint(value)
|
||||
case int8:
|
||||
return uint(value)
|
||||
case int16:
|
||||
return uint(value)
|
||||
case int32:
|
||||
return uint(value)
|
||||
case int64:
|
||||
return uint(value)
|
||||
case uint:
|
||||
return value
|
||||
case uint8:
|
||||
return uint(value)
|
||||
case uint16:
|
||||
return uint(value)
|
||||
case uint32:
|
||||
return uint(value)
|
||||
case uint64:
|
||||
return uint(value)
|
||||
case float32:
|
||||
return uint(value)
|
||||
case float64:
|
||||
return uint(value)
|
||||
case bool:
|
||||
if value {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
default:
|
||||
v, _ := strconv.ParseUint(typeConvertString(value), 10, 64)
|
||||
return uint(v)
|
||||
}
|
||||
}
|
||||
|
||||
func typeConvertUint8(i interface{}) uint8 {
|
||||
if i == nil {
|
||||
return 0
|
||||
}
|
||||
if v, ok := i.(uint8); ok {
|
||||
return v
|
||||
}
|
||||
return uint8(typeConvertUint(i))
|
||||
}
|
||||
|
||||
func typeConvertUint16(i interface{}) uint16 {
|
||||
if i == nil {
|
||||
return 0
|
||||
}
|
||||
if v, ok := i.(uint16); ok {
|
||||
return v
|
||||
}
|
||||
return uint16(typeConvertUint(i))
|
||||
}
|
||||
|
||||
func typeConvertUint32(i interface{}) uint32 {
|
||||
if i == nil {
|
||||
return 0
|
||||
}
|
||||
if v, ok := i.(uint32); ok {
|
||||
return v
|
||||
}
|
||||
return uint32(typeConvertUint(i))
|
||||
}
|
||||
|
||||
func typeConvertUint64(i interface{}) uint64 {
|
||||
if i == nil {
|
||||
return 0
|
||||
}
|
||||
if v, ok := i.(uint64); ok {
|
||||
return v
|
||||
}
|
||||
return uint64(typeConvertUint(i))
|
||||
}
|
||||
*/
|
@ -1,13 +0,0 @@
|
||||
Copyright 2014 astaxie
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
@ -1,6 +0,0 @@
|
||||
package clauses
|
||||
|
||||
const (
|
||||
ExprSep = "__"
|
||||
ExprDot = "."
|
||||
)
|
@ -1,104 +0,0 @@
|
||||
package order_clause
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm/clauses"
|
||||
)
|
||||
|
||||
type Sort int8
|
||||
|
||||
const (
|
||||
None Sort = 0
|
||||
Ascending Sort = 1
|
||||
Descending Sort = 2
|
||||
)
|
||||
|
||||
type Option func(order *Order)
|
||||
|
||||
type Order struct {
|
||||
column string
|
||||
sort Sort
|
||||
isRaw bool
|
||||
}
|
||||
|
||||
func Clause(options ...Option) *Order {
|
||||
o := &Order{}
|
||||
for _, option := range options {
|
||||
option(o)
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *Order) GetColumn() string {
|
||||
return o.column
|
||||
}
|
||||
|
||||
func (o *Order) GetSort() Sort {
|
||||
return o.sort
|
||||
}
|
||||
|
||||
func (o *Order) SortString() string {
|
||||
switch o.GetSort() {
|
||||
case Ascending:
|
||||
return "ASC"
|
||||
case Descending:
|
||||
return "DESC"
|
||||
}
|
||||
|
||||
return ``
|
||||
}
|
||||
|
||||
func (o *Order) IsRaw() bool {
|
||||
return o.isRaw
|
||||
}
|
||||
|
||||
func ParseOrder(expressions ...string) []*Order {
|
||||
var orders []*Order
|
||||
for _, expression := range expressions {
|
||||
sort := Ascending
|
||||
column := strings.ReplaceAll(expression, clauses.ExprSep, clauses.ExprDot)
|
||||
if column[0] == '-' {
|
||||
sort = Descending
|
||||
column = column[1:]
|
||||
}
|
||||
|
||||
orders = append(orders, &Order{
|
||||
column: column,
|
||||
sort: sort,
|
||||
})
|
||||
}
|
||||
|
||||
return orders
|
||||
}
|
||||
|
||||
func Column(column string) Option {
|
||||
return func(order *Order) {
|
||||
order.column = strings.ReplaceAll(column, clauses.ExprSep, clauses.ExprDot)
|
||||
}
|
||||
}
|
||||
|
||||
func sort(sort Sort) Option {
|
||||
return func(order *Order) {
|
||||
order.sort = sort
|
||||
}
|
||||
}
|
||||
|
||||
func SortAscending() Option {
|
||||
return sort(Ascending)
|
||||
}
|
||||
|
||||
func SortDescending() Option {
|
||||
return sort(Descending)
|
||||
}
|
||||
|
||||
func SortNone() Option {
|
||||
return sort(None)
|
||||
}
|
||||
|
||||
func Raw() Option {
|
||||
return func(order *Order) {
|
||||
order.isRaw = true
|
||||
}
|
||||
}
|
@ -1,299 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type commander interface {
|
||||
Parse([]string)
|
||||
Run() error
|
||||
}
|
||||
|
||||
var commands = make(map[string]commander)
|
||||
|
||||
// print help.
|
||||
func printHelp(errs ...string) {
|
||||
content := `orm command usage:
|
||||
|
||||
syncdb - auto create tables
|
||||
sqlall - print sql of create tables
|
||||
help - print this help
|
||||
`
|
||||
|
||||
if len(errs) > 0 {
|
||||
fmt.Println(errs[0])
|
||||
}
|
||||
fmt.Println(content)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// RunCommand listens for orm command and runs if command arguments have been passed.
|
||||
func RunCommand() {
|
||||
if len(os.Args) < 2 || os.Args[1] != "orm" {
|
||||
return
|
||||
}
|
||||
|
||||
BootStrap()
|
||||
|
||||
args := argString(os.Args[2:])
|
||||
name := args.Get(0)
|
||||
|
||||
if name == "help" {
|
||||
printHelp()
|
||||
}
|
||||
|
||||
if cmd, ok := commands[name]; ok {
|
||||
cmd.Parse(os.Args[3:])
|
||||
cmd.Run()
|
||||
os.Exit(0)
|
||||
} else {
|
||||
if name == "" {
|
||||
printHelp()
|
||||
} else {
|
||||
printHelp(fmt.Sprintf("unknown command %s", name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sync database struct command interface.
|
||||
type commandSyncDb struct {
|
||||
al *alias
|
||||
force bool
|
||||
verbose bool
|
||||
noInfo bool
|
||||
rtOnError bool
|
||||
}
|
||||
|
||||
// Parse the orm command line arguments.
|
||||
func (d *commandSyncDb) Parse(args []string) {
|
||||
var name string
|
||||
|
||||
flagSet := flag.NewFlagSet("orm command: syncdb", flag.ExitOnError)
|
||||
flagSet.StringVar(&name, "db", "default", "DataBase alias name")
|
||||
flagSet.BoolVar(&d.force, "force", false, "drop tables before create")
|
||||
flagSet.BoolVar(&d.verbose, "v", false, "verbose info")
|
||||
flagSet.Parse(args)
|
||||
|
||||
d.al = getDbAlias(name)
|
||||
}
|
||||
|
||||
// Run orm line command.
|
||||
func (d *commandSyncDb) Run() error {
|
||||
var drops []string
|
||||
var err error
|
||||
if d.force {
|
||||
drops, err = defaultModelCache.getDbDropSQL(d.al)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
db := d.al.DB
|
||||
|
||||
if d.force && len(drops) > 0 {
|
||||
for i, mi := range defaultModelCache.allOrdered() {
|
||||
query := drops[i]
|
||||
if !d.noInfo {
|
||||
fmt.Printf("drop table `%s`\n", mi.table)
|
||||
}
|
||||
_, err := db.Exec(query)
|
||||
if d.verbose {
|
||||
fmt.Printf(" %s\n\n", query)
|
||||
}
|
||||
if err != nil {
|
||||
if d.rtOnError {
|
||||
return err
|
||||
}
|
||||
fmt.Printf(" %s\n", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createQueries, indexes, err := defaultModelCache.getDbCreateSQL(d.al)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tables, err := d.al.DbBaser.GetTables(db)
|
||||
if err != nil {
|
||||
if d.rtOnError {
|
||||
return err
|
||||
}
|
||||
fmt.Printf(" %s\n", err.Error())
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
for i, mi := range defaultModelCache.allOrdered() {
|
||||
|
||||
if !isApplicableTableForDB(mi.addrField, d.al.Name) {
|
||||
fmt.Printf("table `%s` is not applicable to database '%s'\n", mi.table, d.al.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
if tables[mi.table] {
|
||||
if !d.noInfo {
|
||||
fmt.Printf("table `%s` already exists, skip\n", mi.table)
|
||||
}
|
||||
|
||||
var fields []*fieldInfo
|
||||
columns, err := d.al.DbBaser.GetColumns(ctx, db, mi.table)
|
||||
if err != nil {
|
||||
if d.rtOnError {
|
||||
return err
|
||||
}
|
||||
fmt.Printf(" %s\n", err.Error())
|
||||
}
|
||||
|
||||
for _, fi := range mi.fields.fieldsDB {
|
||||
if _, ok := columns[fi.column]; !ok {
|
||||
fields = append(fields, fi)
|
||||
}
|
||||
}
|
||||
|
||||
for _, fi := range fields {
|
||||
query := getColumnAddQuery(d.al, fi)
|
||||
|
||||
if !d.noInfo {
|
||||
fmt.Printf("add column `%s` for table `%s`\n", fi.fullName, mi.table)
|
||||
}
|
||||
|
||||
_, err := db.Exec(query)
|
||||
if d.verbose {
|
||||
fmt.Printf(" %s\n", query)
|
||||
}
|
||||
if err != nil {
|
||||
if d.rtOnError {
|
||||
return err
|
||||
}
|
||||
fmt.Printf(" %s\n", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
for _, idx := range indexes[mi.table] {
|
||||
if !d.al.DbBaser.IndexExists(ctx, db, idx.Table, idx.Name) {
|
||||
if !d.noInfo {
|
||||
fmt.Printf("create index `%s` for table `%s`\n", idx.Name, idx.Table)
|
||||
}
|
||||
|
||||
query := idx.SQL
|
||||
_, err := db.Exec(query)
|
||||
if d.verbose {
|
||||
fmt.Printf(" %s\n", query)
|
||||
}
|
||||
if err != nil {
|
||||
if d.rtOnError {
|
||||
return err
|
||||
}
|
||||
fmt.Printf(" %s\n", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if !d.noInfo {
|
||||
fmt.Printf("create table `%s` \n", mi.table)
|
||||
}
|
||||
|
||||
queries := []string{createQueries[i]}
|
||||
for _, idx := range indexes[mi.table] {
|
||||
queries = append(queries, idx.SQL)
|
||||
}
|
||||
|
||||
for _, query := range queries {
|
||||
_, err := db.Exec(query)
|
||||
if d.verbose {
|
||||
query = " " + strings.Join(strings.Split(query, "\n"), "\n ")
|
||||
fmt.Println(query)
|
||||
}
|
||||
if err != nil {
|
||||
if d.rtOnError {
|
||||
return err
|
||||
}
|
||||
fmt.Printf(" %s\n", err.Error())
|
||||
}
|
||||
}
|
||||
if d.verbose {
|
||||
fmt.Println("")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// database creation commander interface implement.
|
||||
type commandSQLAll struct {
|
||||
al *alias
|
||||
}
|
||||
|
||||
// Parse orm command line arguments.
|
||||
func (d *commandSQLAll) Parse(args []string) {
|
||||
var name string
|
||||
|
||||
flagSet := flag.NewFlagSet("orm command: sqlall", flag.ExitOnError)
|
||||
flagSet.StringVar(&name, "db", "default", "DataBase alias name")
|
||||
flagSet.Parse(args)
|
||||
|
||||
d.al = getDbAlias(name)
|
||||
}
|
||||
|
||||
// Run orm line command.
|
||||
func (d *commandSQLAll) Run() error {
|
||||
createQueries, indexes, err := defaultModelCache.getDbCreateSQL(d.al)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var all []string
|
||||
for i, mi := range defaultModelCache.allOrdered() {
|
||||
queries := []string{createQueries[i]}
|
||||
for _, idx := range indexes[mi.table] {
|
||||
queries = append(queries, idx.SQL)
|
||||
}
|
||||
sql := strings.Join(queries, "\n")
|
||||
all = append(all, sql)
|
||||
}
|
||||
fmt.Println(strings.Join(all, "\n\n"))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
commands["syncdb"] = new(commandSyncDb)
|
||||
commands["sqlall"] = new(commandSQLAll)
|
||||
}
|
||||
|
||||
// RunSyncdb run syncdb command line.
|
||||
// name: Table's alias name (default is "default")
|
||||
// force: Run the next sql command even if the current gave an error
|
||||
// verbose: Print all information, useful for debugging
|
||||
func RunSyncdb(name string, force bool, verbose bool) error {
|
||||
BootStrap()
|
||||
|
||||
al := getDbAlias(name)
|
||||
cmd := new(commandSyncDb)
|
||||
cmd.al = al
|
||||
cmd.force = force
|
||||
cmd.noInfo = !verbose
|
||||
cmd.verbose = verbose
|
||||
cmd.rtOnError = true
|
||||
return cmd.Run()
|
||||
}
|
@ -1,169 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type dbIndex struct {
|
||||
Table string
|
||||
Name string
|
||||
SQL string
|
||||
}
|
||||
|
||||
// get database column type string.
|
||||
func getColumnTyp(al *alias, fi *fieldInfo) (col string) {
|
||||
T := al.DbBaser.DbTypes()
|
||||
fieldType := fi.fieldType
|
||||
fieldSize := fi.size
|
||||
|
||||
checkColumn:
|
||||
switch fieldType {
|
||||
case TypeBooleanField:
|
||||
col = T["bool"]
|
||||
case TypeVarCharField:
|
||||
if al.Driver == DRPostgres && fi.toText {
|
||||
col = T["string-text"]
|
||||
} else {
|
||||
col = fmt.Sprintf(T["string"], fieldSize)
|
||||
}
|
||||
case TypeCharField:
|
||||
col = fmt.Sprintf(T["string-char"], fieldSize)
|
||||
case TypeTextField:
|
||||
col = T["string-text"]
|
||||
case TypeTimeField:
|
||||
col = T["time.Time-clock"]
|
||||
case TypeDateField:
|
||||
col = T["time.Time-date"]
|
||||
case TypeDateTimeField:
|
||||
// the precision of sqlite is not implemented
|
||||
if al.Driver == 2 || fi.timePrecision == nil {
|
||||
col = T["time.Time"]
|
||||
} else {
|
||||
s := T["time.Time-precision"]
|
||||
col = fmt.Sprintf(s, *fi.timePrecision)
|
||||
}
|
||||
|
||||
case TypeBitField:
|
||||
col = T["int8"]
|
||||
case TypeSmallIntegerField:
|
||||
col = T["int16"]
|
||||
case TypeIntegerField:
|
||||
col = T["int32"]
|
||||
case TypeBigIntegerField:
|
||||
if al.Driver == DRSqlite {
|
||||
fieldType = TypeIntegerField
|
||||
goto checkColumn
|
||||
}
|
||||
col = T["int64"]
|
||||
case TypePositiveBitField:
|
||||
col = T["uint8"]
|
||||
case TypePositiveSmallIntegerField:
|
||||
col = T["uint16"]
|
||||
case TypePositiveIntegerField:
|
||||
col = T["uint32"]
|
||||
case TypePositiveBigIntegerField:
|
||||
col = T["uint64"]
|
||||
case TypeFloatField:
|
||||
col = T["float64"]
|
||||
case TypeDecimalField:
|
||||
s := T["float64-decimal"]
|
||||
if !strings.Contains(s, "%d") {
|
||||
col = s
|
||||
} else {
|
||||
col = fmt.Sprintf(s, fi.digits, fi.decimals)
|
||||
}
|
||||
case TypeJSONField:
|
||||
if al.Driver != DRPostgres {
|
||||
fieldType = TypeVarCharField
|
||||
goto checkColumn
|
||||
}
|
||||
col = T["json"]
|
||||
case TypeJsonbField:
|
||||
if al.Driver != DRPostgres {
|
||||
fieldType = TypeVarCharField
|
||||
goto checkColumn
|
||||
}
|
||||
col = T["jsonb"]
|
||||
case RelForeignKey, RelOneToOne:
|
||||
fieldType = fi.relModelInfo.fields.pk.fieldType
|
||||
fieldSize = fi.relModelInfo.fields.pk.size
|
||||
goto checkColumn
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// create alter sql string.
|
||||
func getColumnAddQuery(al *alias, fi *fieldInfo) string {
|
||||
Q := al.DbBaser.TableQuote()
|
||||
typ := getColumnTyp(al, fi)
|
||||
|
||||
if !fi.null {
|
||||
typ += " " + "NOT NULL"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("ALTER TABLE %s%s%s ADD COLUMN %s%s%s %s %s",
|
||||
Q, fi.mi.table, Q,
|
||||
Q, fi.column, Q,
|
||||
typ, getColumnDefault(fi),
|
||||
)
|
||||
}
|
||||
|
||||
// Get string value for the attribute "DEFAULT" for the CREATE, ALTER commands
|
||||
func getColumnDefault(fi *fieldInfo) string {
|
||||
var v, t, d string
|
||||
|
||||
// Skip default attribute if field is in relations
|
||||
if fi.rel || fi.reverse {
|
||||
return v
|
||||
}
|
||||
|
||||
t = " DEFAULT '%s' "
|
||||
|
||||
// These defaults will be useful if there no config value orm:"default" and NOT NULL is on
|
||||
switch fi.fieldType {
|
||||
case TypeTimeField, TypeDateField, TypeDateTimeField, TypeTextField:
|
||||
return v
|
||||
|
||||
case TypeBitField, TypeSmallIntegerField, TypeIntegerField,
|
||||
TypeBigIntegerField, TypePositiveBitField, TypePositiveSmallIntegerField,
|
||||
TypePositiveIntegerField, TypePositiveBigIntegerField, TypeFloatField,
|
||||
TypeDecimalField:
|
||||
t = " DEFAULT %s "
|
||||
d = "0"
|
||||
case TypeBooleanField:
|
||||
t = " DEFAULT %s "
|
||||
d = "FALSE"
|
||||
case TypeJSONField, TypeJsonbField:
|
||||
d = "{}"
|
||||
}
|
||||
|
||||
if fi.colDefault {
|
||||
if !fi.initial.Exist() {
|
||||
v = fmt.Sprintf(t, "")
|
||||
} else {
|
||||
v = fmt.Sprintf(t, fi.initial.String())
|
||||
}
|
||||
} else {
|
||||
if !fi.null {
|
||||
v = fmt.Sprintf(t, d)
|
||||
}
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,599 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
)
|
||||
|
||||
// DriverType database driver constant int.
|
||||
type DriverType int
|
||||
|
||||
// Enum the Database driver
|
||||
const (
|
||||
_ DriverType = iota // int enum type
|
||||
DRMySQL // mysql
|
||||
DRSqlite // sqlite
|
||||
DROracle // oracle
|
||||
DRPostgres // pgsql
|
||||
DRTiDB // TiDB
|
||||
)
|
||||
|
||||
// database driver string.
|
||||
type driver string
|
||||
|
||||
// get type constant int of current driver..
|
||||
func (d driver) Type() DriverType {
|
||||
a, _ := dataBaseCache.get(string(d))
|
||||
return a.Driver
|
||||
}
|
||||
|
||||
// get name of current driver
|
||||
func (d driver) Name() string {
|
||||
return string(d)
|
||||
}
|
||||
|
||||
// check driver iis implemented Driver interface or not.
|
||||
var _ Driver = new(driver)
|
||||
|
||||
var (
|
||||
dataBaseCache = &_dbCache{cache: make(map[string]*alias)}
|
||||
drivers = map[string]DriverType{
|
||||
"mysql": DRMySQL,
|
||||
"postgres": DRPostgres,
|
||||
"sqlite3": DRSqlite,
|
||||
"tidb": DRTiDB,
|
||||
"oracle": DROracle,
|
||||
"oci8": DROracle, // github.com/mattn/go-oci8
|
||||
"ora": DROracle, // https://github.com/rana/ora
|
||||
}
|
||||
dbBasers = map[DriverType]dbBaser{
|
||||
DRMySQL: newdbBaseMysql(),
|
||||
DRSqlite: newdbBaseSqlite(),
|
||||
DROracle: newdbBaseOracle(),
|
||||
DRPostgres: newdbBasePostgres(),
|
||||
DRTiDB: newdbBaseTidb(),
|
||||
}
|
||||
)
|
||||
|
||||
// database alias cacher.
|
||||
type _dbCache struct {
|
||||
mux sync.RWMutex
|
||||
cache map[string]*alias
|
||||
}
|
||||
|
||||
// add database alias with original name.
|
||||
func (ac *_dbCache) add(name string, al *alias) (added bool) {
|
||||
ac.mux.Lock()
|
||||
defer ac.mux.Unlock()
|
||||
if _, ok := ac.cache[name]; !ok {
|
||||
ac.cache[name] = al
|
||||
added = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// get database alias if cached.
|
||||
func (ac *_dbCache) get(name string) (al *alias, ok bool) {
|
||||
ac.mux.RLock()
|
||||
defer ac.mux.RUnlock()
|
||||
al, ok = ac.cache[name]
|
||||
return
|
||||
}
|
||||
|
||||
// get default alias.
|
||||
func (ac *_dbCache) getDefault() (al *alias) {
|
||||
al, _ = ac.get("default")
|
||||
return
|
||||
}
|
||||
|
||||
type DB struct {
|
||||
*sync.RWMutex
|
||||
DB *sql.DB
|
||||
stmtDecorators *lru.Cache
|
||||
stmtDecoratorsLimit int
|
||||
}
|
||||
|
||||
var (
|
||||
_ dbQuerier = new(DB)
|
||||
_ txer = new(DB)
|
||||
)
|
||||
|
||||
func (d *DB) Begin() (*sql.Tx, error) {
|
||||
return d.DB.Begin()
|
||||
}
|
||||
|
||||
func (d *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) {
|
||||
return d.DB.BeginTx(ctx, opts)
|
||||
}
|
||||
|
||||
// su must call release to release *sql.Stmt after using
|
||||
func (d *DB) getStmtDecorator(query string) (*stmtDecorator, error) {
|
||||
d.RLock()
|
||||
c, ok := d.stmtDecorators.Get(query)
|
||||
if ok {
|
||||
c.(*stmtDecorator).acquire()
|
||||
d.RUnlock()
|
||||
return c.(*stmtDecorator), nil
|
||||
}
|
||||
d.RUnlock()
|
||||
|
||||
d.Lock()
|
||||
c, ok = d.stmtDecorators.Get(query)
|
||||
if ok {
|
||||
c.(*stmtDecorator).acquire()
|
||||
d.Unlock()
|
||||
return c.(*stmtDecorator), nil
|
||||
}
|
||||
|
||||
stmt, err := d.Prepare(query)
|
||||
if err != nil {
|
||||
d.Unlock()
|
||||
return nil, err
|
||||
}
|
||||
sd := newStmtDecorator(stmt)
|
||||
sd.acquire()
|
||||
d.stmtDecorators.Add(query, sd)
|
||||
d.Unlock()
|
||||
|
||||
return sd, nil
|
||||
}
|
||||
|
||||
func (d *DB) Prepare(query string) (*sql.Stmt, error) {
|
||||
return d.DB.Prepare(query)
|
||||
}
|
||||
|
||||
func (d *DB) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) {
|
||||
return d.DB.PrepareContext(ctx, query)
|
||||
}
|
||||
|
||||
func (d *DB) Exec(query string, args ...interface{}) (sql.Result, error) {
|
||||
return d.ExecContext(context.Background(), query, args...)
|
||||
}
|
||||
|
||||
func (d *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
|
||||
if d.stmtDecorators == nil {
|
||||
return d.DB.ExecContext(ctx, query, args...)
|
||||
}
|
||||
|
||||
sd, err := d.getStmtDecorator(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stmt := sd.getStmt()
|
||||
defer sd.release()
|
||||
return stmt.ExecContext(ctx, args...)
|
||||
}
|
||||
|
||||
func (d *DB) Query(query string, args ...interface{}) (*sql.Rows, error) {
|
||||
return d.QueryContext(context.Background(), query, args...)
|
||||
}
|
||||
|
||||
func (d *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
|
||||
if d.stmtDecorators == nil {
|
||||
return d.DB.QueryContext(ctx, query, args...)
|
||||
}
|
||||
|
||||
sd, err := d.getStmtDecorator(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stmt := sd.getStmt()
|
||||
defer sd.release()
|
||||
return stmt.QueryContext(ctx, args...)
|
||||
}
|
||||
|
||||
func (d *DB) QueryRow(query string, args ...interface{}) *sql.Row {
|
||||
return d.QueryRowContext(context.Background(), query, args...)
|
||||
}
|
||||
|
||||
func (d *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
|
||||
if d.stmtDecorators == nil {
|
||||
return d.DB.QueryRowContext(ctx, query, args...)
|
||||
}
|
||||
|
||||
sd, err := d.getStmtDecorator(query)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
stmt := sd.getStmt()
|
||||
defer sd.release()
|
||||
return stmt.QueryRowContext(ctx, args...)
|
||||
}
|
||||
|
||||
type TxDB struct {
|
||||
tx *sql.Tx
|
||||
}
|
||||
|
||||
var (
|
||||
_ dbQuerier = new(TxDB)
|
||||
_ txEnder = new(TxDB)
|
||||
)
|
||||
|
||||
func (t *TxDB) Commit() error {
|
||||
return t.tx.Commit()
|
||||
}
|
||||
|
||||
func (t *TxDB) Rollback() error {
|
||||
return t.tx.Rollback()
|
||||
}
|
||||
|
||||
func (t *TxDB) RollbackUnlessCommit() error {
|
||||
err := t.tx.Rollback()
|
||||
if err != sql.ErrTxDone {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
_ dbQuerier = new(TxDB)
|
||||
_ txEnder = new(TxDB)
|
||||
)
|
||||
|
||||
func (t *TxDB) Prepare(query string) (*sql.Stmt, error) {
|
||||
return t.PrepareContext(context.Background(), query)
|
||||
}
|
||||
|
||||
func (t *TxDB) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) {
|
||||
return t.tx.PrepareContext(ctx, query)
|
||||
}
|
||||
|
||||
func (t *TxDB) Exec(query string, args ...interface{}) (sql.Result, error) {
|
||||
return t.ExecContext(context.Background(), query, args...)
|
||||
}
|
||||
|
||||
func (t *TxDB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
|
||||
return t.tx.ExecContext(ctx, query, args...)
|
||||
}
|
||||
|
||||
func (t *TxDB) Query(query string, args ...interface{}) (*sql.Rows, error) {
|
||||
return t.QueryContext(context.Background(), query, args...)
|
||||
}
|
||||
|
||||
func (t *TxDB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
|
||||
return t.tx.QueryContext(ctx, query, args...)
|
||||
}
|
||||
|
||||
func (t *TxDB) QueryRow(query string, args ...interface{}) *sql.Row {
|
||||
return t.QueryRowContext(context.Background(), query, args...)
|
||||
}
|
||||
|
||||
func (t *TxDB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
|
||||
return t.tx.QueryRowContext(ctx, query, args...)
|
||||
}
|
||||
|
||||
type alias struct {
|
||||
Name string
|
||||
Driver DriverType
|
||||
DriverName string
|
||||
DataSource string
|
||||
MaxIdleConns int
|
||||
MaxOpenConns int
|
||||
ConnMaxLifetime time.Duration
|
||||
StmtCacheSize int
|
||||
DB *DB
|
||||
DbBaser dbBaser
|
||||
TZ *time.Location
|
||||
Engine string
|
||||
}
|
||||
|
||||
func detectTZ(al *alias) {
|
||||
// orm timezone system match database
|
||||
// default use Local
|
||||
al.TZ = DefaultTimeLoc
|
||||
|
||||
if al.DriverName == "sphinx" {
|
||||
return
|
||||
}
|
||||
|
||||
switch al.Driver {
|
||||
case DRMySQL:
|
||||
row := al.DB.QueryRow("SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP)")
|
||||
var tz string
|
||||
row.Scan(&tz)
|
||||
if len(tz) >= 8 {
|
||||
if tz[0] != '-' {
|
||||
tz = "+" + tz
|
||||
}
|
||||
t, err := time.Parse("-07:00:00", tz)
|
||||
if err == nil {
|
||||
if t.Location().String() != "" {
|
||||
al.TZ = t.Location()
|
||||
}
|
||||
} else {
|
||||
DebugLog.Printf("Detect DB timezone: %s %s\n", tz, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// get default engine from current database
|
||||
row = al.DB.QueryRow("SELECT ENGINE, TRANSACTIONS FROM information_schema.engines WHERE SUPPORT = 'DEFAULT'")
|
||||
var engine string
|
||||
var tx bool
|
||||
row.Scan(&engine, &tx)
|
||||
|
||||
if engine != "" {
|
||||
al.Engine = engine
|
||||
} else {
|
||||
al.Engine = "INNODB"
|
||||
}
|
||||
|
||||
case DRSqlite, DROracle:
|
||||
al.TZ = time.UTC
|
||||
|
||||
case DRPostgres:
|
||||
row := al.DB.QueryRow("SELECT current_setting('TIMEZONE')")
|
||||
var tz string
|
||||
row.Scan(&tz)
|
||||
loc, err := time.LoadLocation(tz)
|
||||
if err == nil {
|
||||
al.TZ = loc
|
||||
} else {
|
||||
DebugLog.Printf("Detect DB timezone: %s %s\n", tz, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addAliasWthDB(aliasName, driverName string, db *sql.DB, params ...DBOption) (*alias, error) {
|
||||
existErr := fmt.Errorf("DataBase alias name `%s` already registered, cannot reuse", aliasName)
|
||||
if _, ok := dataBaseCache.get(aliasName); ok {
|
||||
return nil, existErr
|
||||
}
|
||||
|
||||
al, err := newAliasWithDb(aliasName, driverName, db, params...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !dataBaseCache.add(aliasName, al) {
|
||||
return nil, existErr
|
||||
}
|
||||
|
||||
return al, nil
|
||||
}
|
||||
|
||||
func newAliasWithDb(aliasName, driverName string, db *sql.DB, params ...DBOption) (*alias, error) {
|
||||
al := &alias{}
|
||||
al.DB = &DB{
|
||||
RWMutex: new(sync.RWMutex),
|
||||
DB: db,
|
||||
}
|
||||
|
||||
for _, p := range params {
|
||||
p(al)
|
||||
}
|
||||
|
||||
var stmtCache *lru.Cache
|
||||
var stmtCacheSize int
|
||||
|
||||
if al.StmtCacheSize > 0 {
|
||||
_stmtCache, errC := newStmtDecoratorLruWithEvict(al.StmtCacheSize)
|
||||
if errC != nil {
|
||||
return nil, errC
|
||||
} else {
|
||||
stmtCache = _stmtCache
|
||||
stmtCacheSize = al.StmtCacheSize
|
||||
}
|
||||
}
|
||||
|
||||
al.Name = aliasName
|
||||
al.DriverName = driverName
|
||||
al.DB.stmtDecorators = stmtCache
|
||||
al.DB.stmtDecoratorsLimit = stmtCacheSize
|
||||
|
||||
if dr, ok := drivers[driverName]; ok {
|
||||
al.DbBaser = dbBasers[dr]
|
||||
al.Driver = dr
|
||||
} else {
|
||||
return nil, fmt.Errorf("driver name `%s` have not registered", driverName)
|
||||
}
|
||||
|
||||
err := db.Ping()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("register db Ping `%s`, %s", aliasName, err.Error())
|
||||
}
|
||||
|
||||
detectTZ(al)
|
||||
|
||||
return al, nil
|
||||
}
|
||||
|
||||
// SetMaxIdleConns Change the max idle conns for *sql.DB, use specify database alias name
|
||||
// Deprecated you should not use this, we will remove it in the future
|
||||
func SetMaxIdleConns(aliasName string, maxIdleConns int) {
|
||||
al := getDbAlias(aliasName)
|
||||
al.SetMaxIdleConns(maxIdleConns)
|
||||
}
|
||||
|
||||
// SetMaxOpenConns Change the max open conns for *sql.DB, use specify database alias name
|
||||
// Deprecated you should not use this, we will remove it in the future
|
||||
func SetMaxOpenConns(aliasName string, maxOpenConns int) {
|
||||
al := getDbAlias(aliasName)
|
||||
al.SetMaxOpenConns(maxOpenConns)
|
||||
}
|
||||
|
||||
// SetMaxIdleConns Change the max idle conns for *sql.DB, use specify database alias name
|
||||
func (al *alias) SetMaxIdleConns(maxIdleConns int) {
|
||||
al.MaxIdleConns = maxIdleConns
|
||||
al.DB.DB.SetMaxIdleConns(maxIdleConns)
|
||||
}
|
||||
|
||||
// SetMaxOpenConns Change the max open conns for *sql.DB, use specify database alias name
|
||||
func (al *alias) SetMaxOpenConns(maxOpenConns int) {
|
||||
al.MaxOpenConns = maxOpenConns
|
||||
al.DB.DB.SetMaxOpenConns(maxOpenConns)
|
||||
}
|
||||
|
||||
func (al *alias) SetConnMaxLifetime(lifeTime time.Duration) {
|
||||
al.ConnMaxLifetime = lifeTime
|
||||
al.DB.DB.SetConnMaxLifetime(lifeTime)
|
||||
}
|
||||
|
||||
// AddAliasWthDB add a aliasName for the drivename
|
||||
func AddAliasWthDB(aliasName, driverName string, db *sql.DB, params ...DBOption) error {
|
||||
_, err := addAliasWthDB(aliasName, driverName, db, params...)
|
||||
return err
|
||||
}
|
||||
|
||||
// RegisterDataBase Setting the database connect params. Use the database driver self dataSource args.
|
||||
func RegisterDataBase(aliasName, driverName, dataSource string, params ...DBOption) error {
|
||||
var (
|
||||
err error
|
||||
db *sql.DB
|
||||
al *alias
|
||||
)
|
||||
|
||||
db, err = sql.Open(driverName, dataSource)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("register db `%s`, %s", aliasName, err.Error())
|
||||
goto end
|
||||
}
|
||||
|
||||
al, err = addAliasWthDB(aliasName, driverName, db, params...)
|
||||
if err != nil {
|
||||
goto end
|
||||
}
|
||||
|
||||
al.DataSource = dataSource
|
||||
|
||||
end:
|
||||
if err != nil {
|
||||
if db != nil {
|
||||
db.Close()
|
||||
}
|
||||
DebugLog.Println(err.Error())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// RegisterDriver Register a database driver use specify driver name, this can be definition the driver is which database type.
|
||||
func RegisterDriver(driverName string, typ DriverType) error {
|
||||
if t, ok := drivers[driverName]; !ok {
|
||||
drivers[driverName] = typ
|
||||
} else {
|
||||
if t != typ {
|
||||
return fmt.Errorf("driverName `%s` db driver already registered and is other type", driverName)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDataBaseTZ Change the database default used timezone
|
||||
func SetDataBaseTZ(aliasName string, tz *time.Location) error {
|
||||
if al, ok := dataBaseCache.get(aliasName); ok {
|
||||
al.TZ = tz
|
||||
} else {
|
||||
return fmt.Errorf("DataBase alias name `%s` not registered", aliasName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDB Get *sql.DB from registered database by db alias name.
|
||||
// Use "default" as alias name if you not set.
|
||||
func GetDB(aliasNames ...string) (*sql.DB, error) {
|
||||
var name string
|
||||
if len(aliasNames) > 0 {
|
||||
name = aliasNames[0]
|
||||
} else {
|
||||
name = "default"
|
||||
}
|
||||
al, ok := dataBaseCache.get(name)
|
||||
if ok {
|
||||
return al.DB.DB, nil
|
||||
}
|
||||
return nil, fmt.Errorf("DataBase of alias name `%s` not found", name)
|
||||
}
|
||||
|
||||
type stmtDecorator struct {
|
||||
wg sync.WaitGroup
|
||||
stmt *sql.Stmt
|
||||
}
|
||||
|
||||
func (s *stmtDecorator) getStmt() *sql.Stmt {
|
||||
return s.stmt
|
||||
}
|
||||
|
||||
// acquire will add one
|
||||
// since this method will be used inside read lock scope,
|
||||
// so we can not do more things here
|
||||
// we should think about refactor this
|
||||
func (s *stmtDecorator) acquire() {
|
||||
s.wg.Add(1)
|
||||
}
|
||||
|
||||
func (s *stmtDecorator) release() {
|
||||
s.wg.Done()
|
||||
}
|
||||
|
||||
// garbage recycle for stmt
|
||||
func (s *stmtDecorator) destroy() {
|
||||
go func() {
|
||||
s.wg.Wait()
|
||||
_ = s.stmt.Close()
|
||||
}()
|
||||
}
|
||||
|
||||
func newStmtDecorator(sqlStmt *sql.Stmt) *stmtDecorator {
|
||||
return &stmtDecorator{
|
||||
stmt: sqlStmt,
|
||||
}
|
||||
}
|
||||
|
||||
func newStmtDecoratorLruWithEvict(cacheSize int) (*lru.Cache, error) {
|
||||
cache, err := lru.NewWithEvict(cacheSize, func(key interface{}, value interface{}) {
|
||||
value.(*stmtDecorator).destroy()
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cache, nil
|
||||
}
|
||||
|
||||
type DBOption func(al *alias)
|
||||
|
||||
// MaxIdleConnections return a hint about MaxIdleConnections
|
||||
func MaxIdleConnections(maxIdleConn int) DBOption {
|
||||
return func(al *alias) {
|
||||
al.SetMaxIdleConns(maxIdleConn)
|
||||
}
|
||||
}
|
||||
|
||||
// MaxOpenConnections return a hint about MaxOpenConnections
|
||||
func MaxOpenConnections(maxOpenConn int) DBOption {
|
||||
return func(al *alias) {
|
||||
al.SetMaxOpenConns(maxOpenConn)
|
||||
}
|
||||
}
|
||||
|
||||
// ConnMaxLifetime return a hint about ConnMaxLifetime
|
||||
func ConnMaxLifetime(v time.Duration) DBOption {
|
||||
return func(al *alias) {
|
||||
al.SetConnMaxLifetime(v)
|
||||
}
|
||||
}
|
||||
|
||||
// MaxStmtCacheSize return a hint about MaxStmtCacheSize
|
||||
func MaxStmtCacheSize(v int) DBOption {
|
||||
return func(al *alias) {
|
||||
al.StmtCacheSize = v
|
||||
}
|
||||
}
|
@ -1,192 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// mysql operators.
|
||||
var mysqlOperators = map[string]string{
|
||||
"exact": "= ?",
|
||||
"iexact": "LIKE ?",
|
||||
"strictexact": "= BINARY ?",
|
||||
"contains": "LIKE BINARY ?",
|
||||
"icontains": "LIKE ?",
|
||||
// "regex": "REGEXP BINARY ?",
|
||||
// "iregex": "REGEXP ?",
|
||||
"gt": "> ?",
|
||||
"gte": ">= ?",
|
||||
"lt": "< ?",
|
||||
"lte": "<= ?",
|
||||
"eq": "= ?",
|
||||
"ne": "!= ?",
|
||||
"startswith": "LIKE BINARY ?",
|
||||
"endswith": "LIKE BINARY ?",
|
||||
"istartswith": "LIKE ?",
|
||||
"iendswith": "LIKE ?",
|
||||
}
|
||||
|
||||
// mysql column field types.
|
||||
var mysqlTypes = map[string]string{
|
||||
"auto": "AUTO_INCREMENT NOT NULL PRIMARY KEY",
|
||||
"pk": "NOT NULL PRIMARY KEY",
|
||||
"bool": "bool",
|
||||
"string": "varchar(%d)",
|
||||
"string-char": "char(%d)",
|
||||
"string-text": "longtext",
|
||||
"time.Time-date": "date",
|
||||
"time.Time": "datetime",
|
||||
"int8": "tinyint",
|
||||
"int16": "smallint",
|
||||
"int32": "integer",
|
||||
"int64": "bigint",
|
||||
"uint8": "tinyint unsigned",
|
||||
"uint16": "smallint unsigned",
|
||||
"uint32": "integer unsigned",
|
||||
"uint64": "bigint unsigned",
|
||||
"float64": "double precision",
|
||||
"float64-decimal": "numeric(%d, %d)",
|
||||
"time.Time-precision": "datetime(%d)",
|
||||
}
|
||||
|
||||
// mysql dbBaser implementation.
|
||||
type dbBaseMysql struct {
|
||||
dbBase
|
||||
}
|
||||
|
||||
var _ dbBaser = new(dbBaseMysql)
|
||||
|
||||
// get mysql operator.
|
||||
func (d *dbBaseMysql) OperatorSQL(operator string) string {
|
||||
return mysqlOperators[operator]
|
||||
}
|
||||
|
||||
// get mysql table field types.
|
||||
func (d *dbBaseMysql) DbTypes() map[string]string {
|
||||
return mysqlTypes
|
||||
}
|
||||
|
||||
// show table sql for mysql.
|
||||
func (d *dbBaseMysql) ShowTablesQuery() string {
|
||||
return "SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND table_schema = DATABASE()"
|
||||
}
|
||||
|
||||
// show columns sql of table for mysql.
|
||||
func (d *dbBaseMysql) ShowColumnsQuery(table string) string {
|
||||
return fmt.Sprintf("SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE FROM information_schema.columns "+
|
||||
"WHERE table_schema = DATABASE() AND table_name = '%s'", table)
|
||||
}
|
||||
|
||||
// execute sql to check index exist.
|
||||
func (d *dbBaseMysql) IndexExists(ctx context.Context, db dbQuerier, table string, name string) bool {
|
||||
row := db.QueryRowContext(ctx, "SELECT count(*) FROM information_schema.statistics "+
|
||||
"WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ?", table, name)
|
||||
var cnt int
|
||||
row.Scan(&cnt)
|
||||
return cnt > 0
|
||||
}
|
||||
|
||||
// InsertOrUpdate a row
|
||||
// If your primary key or unique column conflict will update
|
||||
// If no will insert
|
||||
// Add "`" for mysql sql building
|
||||
func (d *dbBaseMysql) InsertOrUpdate(ctx context.Context, q dbQuerier, mi *modelInfo, ind reflect.Value, a *alias, args ...string) (int64, error) {
|
||||
var iouStr string
|
||||
argsMap := map[string]string{}
|
||||
|
||||
iouStr = "ON DUPLICATE KEY UPDATE"
|
||||
|
||||
// Get on the key-value pairs
|
||||
for _, v := range args {
|
||||
kv := strings.Split(v, "=")
|
||||
if len(kv) == 2 {
|
||||
argsMap[strings.ToLower(kv[0])] = kv[1]
|
||||
}
|
||||
}
|
||||
|
||||
isMulti := false
|
||||
names := make([]string, 0, len(mi.fields.dbcols)-1)
|
||||
Q := d.ins.TableQuote()
|
||||
values, _, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, &names, a.TZ)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
marks := make([]string, len(names))
|
||||
updateValues := make([]interface{}, 0)
|
||||
updates := make([]string, len(names))
|
||||
|
||||
for i, v := range names {
|
||||
marks[i] = "?"
|
||||
valueStr := argsMap[strings.ToLower(v)]
|
||||
if valueStr != "" {
|
||||
updates[i] = "`" + v + "`" + "=" + valueStr
|
||||
} else {
|
||||
updates[i] = "`" + v + "`" + "=?"
|
||||
updateValues = append(updateValues, values[i])
|
||||
}
|
||||
}
|
||||
|
||||
values = append(values, updateValues...)
|
||||
|
||||
sep := fmt.Sprintf("%s, %s", Q, Q)
|
||||
qmarks := strings.Join(marks, ", ")
|
||||
qupdates := strings.Join(updates, ", ")
|
||||
columns := strings.Join(names, sep)
|
||||
|
||||
multi := len(values) / len(names)
|
||||
|
||||
if isMulti {
|
||||
qmarks = strings.Repeat(qmarks+"), (", multi-1) + qmarks
|
||||
}
|
||||
// conflitValue maybe is a int,can`t use fmt.Sprintf
|
||||
query := fmt.Sprintf("INSERT INTO %s%s%s (%s%s%s) VALUES (%s) %s "+qupdates, Q, mi.table, Q, Q, columns, Q, qmarks, iouStr)
|
||||
|
||||
d.ins.ReplaceMarks(&query)
|
||||
|
||||
if isMulti || !d.ins.HasReturningID(mi, &query) {
|
||||
res, err := q.ExecContext(ctx, query, values...)
|
||||
if err == nil {
|
||||
if isMulti {
|
||||
return res.RowsAffected()
|
||||
}
|
||||
|
||||
lastInsertId, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
DebugLog.Println(ErrLastInsertIdUnavailable, ':', err)
|
||||
return lastInsertId, ErrLastInsertIdUnavailable
|
||||
} else {
|
||||
return lastInsertId, nil
|
||||
}
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
row := q.QueryRowContext(ctx, query, values...)
|
||||
var id int64
|
||||
err = row.Scan(&id)
|
||||
return id, err
|
||||
}
|
||||
|
||||
// create new mysql dbBaser.
|
||||
func newdbBaseMysql() dbBaser {
|
||||
b := new(dbBaseMysql)
|
||||
b.ins = b
|
||||
return b
|
||||
}
|
@ -1,171 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm/hints"
|
||||
)
|
||||
|
||||
// oracle operators.
|
||||
var oracleOperators = map[string]string{
|
||||
"exact": "= ?",
|
||||
"gt": "> ?",
|
||||
"gte": ">= ?",
|
||||
"lt": "< ?",
|
||||
"lte": "<= ?",
|
||||
"//iendswith": "LIKE ?",
|
||||
}
|
||||
|
||||
// oracle column field types.
|
||||
var oracleTypes = map[string]string{
|
||||
"pk": "NOT NULL PRIMARY KEY",
|
||||
"bool": "bool",
|
||||
"string": "VARCHAR2(%d)",
|
||||
"string-char": "CHAR(%d)",
|
||||
"string-text": "VARCHAR2(%d)",
|
||||
"time.Time-date": "DATE",
|
||||
"time.Time": "TIMESTAMP",
|
||||
"int8": "INTEGER",
|
||||
"int16": "INTEGER",
|
||||
"int32": "INTEGER",
|
||||
"int64": "INTEGER",
|
||||
"uint8": "INTEGER",
|
||||
"uint16": "INTEGER",
|
||||
"uint32": "INTEGER",
|
||||
"uint64": "INTEGER",
|
||||
"float64": "NUMBER",
|
||||
"float64-decimal": "NUMBER(%d, %d)",
|
||||
"time.Time-precision": "TIMESTAMP(%d)",
|
||||
}
|
||||
|
||||
// oracle dbBaser
|
||||
type dbBaseOracle struct {
|
||||
dbBase
|
||||
}
|
||||
|
||||
var _ dbBaser = new(dbBaseOracle)
|
||||
|
||||
// create oracle dbBaser.
|
||||
func newdbBaseOracle() dbBaser {
|
||||
b := new(dbBaseOracle)
|
||||
b.ins = b
|
||||
return b
|
||||
}
|
||||
|
||||
// OperatorSQL get oracle operator.
|
||||
func (d *dbBaseOracle) OperatorSQL(operator string) string {
|
||||
return oracleOperators[operator]
|
||||
}
|
||||
|
||||
// DbTypes get oracle table field types.
|
||||
func (d *dbBaseOracle) DbTypes() map[string]string {
|
||||
return oracleTypes
|
||||
}
|
||||
|
||||
// ShowTablesQuery show all the tables in database
|
||||
func (d *dbBaseOracle) ShowTablesQuery() string {
|
||||
return "SELECT TABLE_NAME FROM USER_TABLES"
|
||||
}
|
||||
|
||||
// Oracle
|
||||
func (d *dbBaseOracle) ShowColumnsQuery(table string) string {
|
||||
return fmt.Sprintf("SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS "+
|
||||
"WHERE TABLE_NAME ='%s'", strings.ToUpper(table))
|
||||
}
|
||||
|
||||
// check index is exist
|
||||
func (d *dbBaseOracle) IndexExists(ctx context.Context, db dbQuerier, table string, name string) bool {
|
||||
row := db.QueryRowContext(ctx, "SELECT COUNT(*) FROM USER_IND_COLUMNS, USER_INDEXES "+
|
||||
"WHERE USER_IND_COLUMNS.INDEX_NAME = USER_INDEXES.INDEX_NAME "+
|
||||
"AND USER_IND_COLUMNS.TABLE_NAME = ? AND USER_IND_COLUMNS.INDEX_NAME = ?", strings.ToUpper(table), strings.ToUpper(name))
|
||||
|
||||
var cnt int
|
||||
row.Scan(&cnt)
|
||||
return cnt > 0
|
||||
}
|
||||
|
||||
func (d *dbBaseOracle) GenerateSpecifyIndex(tableName string, useIndex int, indexes []string) string {
|
||||
var s []string
|
||||
Q := d.TableQuote()
|
||||
for _, index := range indexes {
|
||||
tmp := fmt.Sprintf(`%s%s%s`, Q, index, Q)
|
||||
s = append(s, tmp)
|
||||
}
|
||||
|
||||
var hint string
|
||||
|
||||
switch useIndex {
|
||||
case hints.KeyUseIndex, hints.KeyForceIndex:
|
||||
hint = `INDEX`
|
||||
case hints.KeyIgnoreIndex:
|
||||
hint = `NO_INDEX`
|
||||
default:
|
||||
DebugLog.Println("[WARN] Not a valid specifying action, so that action is ignored")
|
||||
return ``
|
||||
}
|
||||
|
||||
return fmt.Sprintf(` /*+ %s(%s %s)*/ `, hint, tableName, strings.Join(s, `,`))
|
||||
}
|
||||
|
||||
// execute insert sql with given struct and given values.
|
||||
// insert the given values, not the field values in struct.
|
||||
func (d *dbBaseOracle) InsertValue(ctx context.Context, q dbQuerier, mi *modelInfo, isMulti bool, names []string, values []interface{}) (int64, error) {
|
||||
Q := d.ins.TableQuote()
|
||||
|
||||
marks := make([]string, len(names))
|
||||
for i := range marks {
|
||||
marks[i] = ":" + names[i]
|
||||
}
|
||||
|
||||
sep := fmt.Sprintf("%s, %s", Q, Q)
|
||||
qmarks := strings.Join(marks, ", ")
|
||||
columns := strings.Join(names, sep)
|
||||
|
||||
multi := len(values) / len(names)
|
||||
|
||||
if isMulti {
|
||||
qmarks = strings.Repeat(qmarks+"), (", multi-1) + qmarks
|
||||
}
|
||||
|
||||
query := fmt.Sprintf("INSERT INTO %s%s%s (%s%s%s) VALUES (%s)", Q, mi.table, Q, Q, columns, Q, qmarks)
|
||||
|
||||
d.ins.ReplaceMarks(&query)
|
||||
|
||||
if isMulti || !d.ins.HasReturningID(mi, &query) {
|
||||
res, err := q.ExecContext(ctx, query, values...)
|
||||
if err == nil {
|
||||
if isMulti {
|
||||
return res.RowsAffected()
|
||||
}
|
||||
|
||||
lastInsertId, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
DebugLog.Println(ErrLastInsertIdUnavailable, ':', err)
|
||||
return lastInsertId, ErrLastInsertIdUnavailable
|
||||
} else {
|
||||
return lastInsertId, nil
|
||||
}
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
row := q.QueryRowContext(ctx, query, values...)
|
||||
var id int64
|
||||
err := row.Scan(&id)
|
||||
return id, err
|
||||
}
|
@ -1,197 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// postgresql operators.
|
||||
var postgresOperators = map[string]string{
|
||||
"exact": "= ?",
|
||||
"iexact": "= UPPER(?)",
|
||||
"contains": "LIKE ?",
|
||||
"icontains": "LIKE UPPER(?)",
|
||||
"gt": "> ?",
|
||||
"gte": ">= ?",
|
||||
"lt": "< ?",
|
||||
"lte": "<= ?",
|
||||
"eq": "= ?",
|
||||
"ne": "!= ?",
|
||||
"startswith": "LIKE ?",
|
||||
"endswith": "LIKE ?",
|
||||
"istartswith": "LIKE UPPER(?)",
|
||||
"iendswith": "LIKE UPPER(?)",
|
||||
}
|
||||
|
||||
// postgresql column field types.
|
||||
var postgresTypes = map[string]string{
|
||||
"auto": "serial NOT NULL PRIMARY KEY",
|
||||
"pk": "NOT NULL PRIMARY KEY",
|
||||
"bool": "bool",
|
||||
"string": "varchar(%d)",
|
||||
"string-char": "char(%d)",
|
||||
"string-text": "text",
|
||||
"time.Time-date": "date",
|
||||
"time.Time": "timestamp with time zone",
|
||||
"int8": `smallint CHECK("%COL%" >= -127 AND "%COL%" <= 128)`,
|
||||
"int16": "smallint",
|
||||
"int32": "integer",
|
||||
"int64": "bigint",
|
||||
"uint8": `smallint CHECK("%COL%" >= 0 AND "%COL%" <= 255)`,
|
||||
"uint16": `integer CHECK("%COL%" >= 0)`,
|
||||
"uint32": `bigint CHECK("%COL%" >= 0)`,
|
||||
"uint64": `bigint CHECK("%COL%" >= 0)`,
|
||||
"float64": "double precision",
|
||||
"float64-decimal": "numeric(%d, %d)",
|
||||
"json": "json",
|
||||
"jsonb": "jsonb",
|
||||
"time.Time-precision": "timestamp(%d) with time zone",
|
||||
}
|
||||
|
||||
// postgresql dbBaser.
|
||||
type dbBasePostgres struct {
|
||||
dbBase
|
||||
}
|
||||
|
||||
var _ dbBaser = new(dbBasePostgres)
|
||||
|
||||
// get postgresql operator.
|
||||
func (d *dbBasePostgres) OperatorSQL(operator string) string {
|
||||
return postgresOperators[operator]
|
||||
}
|
||||
|
||||
// generate functioned sql string, such as contains(text).
|
||||
func (d *dbBasePostgres) GenerateOperatorLeftCol(fi *fieldInfo, operator string, leftCol *string) {
|
||||
switch operator {
|
||||
case "contains", "startswith", "endswith":
|
||||
*leftCol = fmt.Sprintf("%s::text", *leftCol)
|
||||
case "iexact", "icontains", "istartswith", "iendswith":
|
||||
*leftCol = fmt.Sprintf("UPPER(%s::text)", *leftCol)
|
||||
}
|
||||
}
|
||||
|
||||
// postgresql unsupports updating joined record.
|
||||
func (d *dbBasePostgres) SupportUpdateJoin() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *dbBasePostgres) MaxLimit() uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// postgresql quote is ".
|
||||
func (d *dbBasePostgres) TableQuote() string {
|
||||
return `"`
|
||||
}
|
||||
|
||||
// postgresql value placeholder is $n.
|
||||
// replace default ? to $n.
|
||||
func (d *dbBasePostgres) ReplaceMarks(query *string) {
|
||||
q := *query
|
||||
num := 0
|
||||
for _, c := range q {
|
||||
if c == '?' {
|
||||
num++
|
||||
}
|
||||
}
|
||||
if num == 0 {
|
||||
return
|
||||
}
|
||||
data := make([]byte, 0, len(q)+num)
|
||||
num = 1
|
||||
for i := 0; i < len(q); i++ {
|
||||
c := q[i]
|
||||
if c == '?' {
|
||||
data = append(data, '$')
|
||||
data = append(data, []byte(strconv.Itoa(num))...)
|
||||
num++
|
||||
} else {
|
||||
data = append(data, c)
|
||||
}
|
||||
}
|
||||
*query = string(data)
|
||||
}
|
||||
|
||||
// make returning sql support for postgresql.
|
||||
func (d *dbBasePostgres) HasReturningID(mi *modelInfo, query *string) bool {
|
||||
fi := mi.fields.pk
|
||||
if fi.fieldType&IsPositiveIntegerField == 0 && fi.fieldType&IsIntegerField == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if query != nil {
|
||||
*query = fmt.Sprintf(`%s RETURNING "%s"`, *query, fi.column)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// sync auto key
|
||||
func (d *dbBasePostgres) setval(ctx context.Context, db dbQuerier, mi *modelInfo, autoFields []string) error {
|
||||
if len(autoFields) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
Q := d.ins.TableQuote()
|
||||
for _, name := range autoFields {
|
||||
query := fmt.Sprintf("SELECT setval(pg_get_serial_sequence('%s', '%s'), (SELECT MAX(%s%s%s) FROM %s%s%s));",
|
||||
mi.table, name,
|
||||
Q, name, Q,
|
||||
Q, mi.table, Q)
|
||||
if _, err := db.ExecContext(ctx, query); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// show table sql for postgresql.
|
||||
func (d *dbBasePostgres) ShowTablesQuery() string {
|
||||
return "SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND table_schema NOT IN ('pg_catalog', 'information_schema')"
|
||||
}
|
||||
|
||||
// show table columns sql for postgresql.
|
||||
func (d *dbBasePostgres) ShowColumnsQuery(table string) string {
|
||||
return fmt.Sprintf("SELECT column_name, data_type, is_nullable FROM information_schema.columns where table_schema NOT IN ('pg_catalog', 'information_schema') and table_name = '%s'", table)
|
||||
}
|
||||
|
||||
// get column types of postgresql.
|
||||
func (d *dbBasePostgres) DbTypes() map[string]string {
|
||||
return postgresTypes
|
||||
}
|
||||
|
||||
// check index exist in postgresql.
|
||||
func (d *dbBasePostgres) IndexExists(ctx context.Context, db dbQuerier, table string, name string) bool {
|
||||
query := fmt.Sprintf("SELECT COUNT(*) FROM pg_indexes WHERE tablename = '%s' AND indexname = '%s'", table, name)
|
||||
row := db.QueryRowContext(ctx, query)
|
||||
var cnt int
|
||||
row.Scan(&cnt)
|
||||
return cnt > 0
|
||||
}
|
||||
|
||||
// GenerateSpecifyIndex return a specifying index clause
|
||||
func (d *dbBasePostgres) GenerateSpecifyIndex(tableName string, useIndex int, indexes []string) string {
|
||||
DebugLog.Println("[WARN] Not support any specifying index action, so that action is ignored")
|
||||
return ``
|
||||
}
|
||||
|
||||
// create new postgresql dbBaser.
|
||||
func newdbBasePostgres() dbBaser {
|
||||
b := new(dbBasePostgres)
|
||||
b.ins = b
|
||||
return b
|
||||
}
|
@ -1,184 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm/hints"
|
||||
)
|
||||
|
||||
// sqlite operators.
|
||||
var sqliteOperators = map[string]string{
|
||||
"exact": "= ?",
|
||||
"iexact": "LIKE ? ESCAPE '\\'",
|
||||
"contains": "LIKE ? ESCAPE '\\'",
|
||||
"icontains": "LIKE ? ESCAPE '\\'",
|
||||
"gt": "> ?",
|
||||
"gte": ">= ?",
|
||||
"lt": "< ?",
|
||||
"lte": "<= ?",
|
||||
"eq": "= ?",
|
||||
"ne": "!= ?",
|
||||
"startswith": "LIKE ? ESCAPE '\\'",
|
||||
"endswith": "LIKE ? ESCAPE '\\'",
|
||||
"istartswith": "LIKE ? ESCAPE '\\'",
|
||||
"iendswith": "LIKE ? ESCAPE '\\'",
|
||||
}
|
||||
|
||||
// sqlite column types.
|
||||
var sqliteTypes = map[string]string{
|
||||
"auto": "integer NOT NULL PRIMARY KEY AUTOINCREMENT",
|
||||
"pk": "NOT NULL PRIMARY KEY",
|
||||
"bool": "bool",
|
||||
"string": "varchar(%d)",
|
||||
"string-char": "character(%d)",
|
||||
"string-text": "text",
|
||||
"time.Time-date": "date",
|
||||
"time.Time": "datetime",
|
||||
"time.Time-precision": "datetime(%d)",
|
||||
"int8": "tinyint",
|
||||
"int16": "smallint",
|
||||
"int32": "integer",
|
||||
"int64": "bigint",
|
||||
"uint8": "tinyint unsigned",
|
||||
"uint16": "smallint unsigned",
|
||||
"uint32": "integer unsigned",
|
||||
"uint64": "bigint unsigned",
|
||||
"float64": "real",
|
||||
"float64-decimal": "decimal",
|
||||
}
|
||||
|
||||
// sqlite dbBaser.
|
||||
type dbBaseSqlite struct {
|
||||
dbBase
|
||||
}
|
||||
|
||||
var _ dbBaser = new(dbBaseSqlite)
|
||||
|
||||
// override base db read for update behavior as SQlite does not support syntax
|
||||
func (d *dbBaseSqlite) Read(ctx context.Context, q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string, isForUpdate bool) error {
|
||||
if isForUpdate {
|
||||
DebugLog.Println("[WARN] SQLite does not support SELECT FOR UPDATE query, isForUpdate param is ignored and always as false to do the work")
|
||||
}
|
||||
return d.dbBase.Read(ctx, q, mi, ind, tz, cols, false)
|
||||
}
|
||||
|
||||
// get sqlite operator.
|
||||
func (d *dbBaseSqlite) OperatorSQL(operator string) string {
|
||||
return sqliteOperators[operator]
|
||||
}
|
||||
|
||||
// generate functioned sql for sqlite.
|
||||
// only support DATE(text).
|
||||
func (d *dbBaseSqlite) GenerateOperatorLeftCol(fi *fieldInfo, operator string, leftCol *string) {
|
||||
if fi.fieldType == TypeDateField {
|
||||
*leftCol = fmt.Sprintf("DATE(%s)", *leftCol)
|
||||
}
|
||||
}
|
||||
|
||||
// unable updating joined record in sqlite.
|
||||
func (d *dbBaseSqlite) SupportUpdateJoin() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// max int in sqlite.
|
||||
func (d *dbBaseSqlite) MaxLimit() uint64 {
|
||||
return 9223372036854775807
|
||||
}
|
||||
|
||||
// get column types in sqlite.
|
||||
func (d *dbBaseSqlite) DbTypes() map[string]string {
|
||||
return sqliteTypes
|
||||
}
|
||||
|
||||
// get show tables sql in sqlite.
|
||||
func (d *dbBaseSqlite) ShowTablesQuery() string {
|
||||
return "SELECT name FROM sqlite_master WHERE type = 'table'"
|
||||
}
|
||||
|
||||
// get columns in sqlite.
|
||||
func (d *dbBaseSqlite) GetColumns(ctx context.Context, db dbQuerier, table string) (map[string][3]string, error) {
|
||||
query := d.ins.ShowColumnsQuery(table)
|
||||
rows, err := db.QueryContext(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
columns := make(map[string][3]string)
|
||||
for rows.Next() {
|
||||
var tmp, name, typ, null sql.NullString
|
||||
err := rows.Scan(&tmp, &name, &typ, &null, &tmp, &tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
columns[name.String] = [3]string{name.String, typ.String, null.String}
|
||||
}
|
||||
|
||||
return columns, nil
|
||||
}
|
||||
|
||||
// get show columns sql in sqlite.
|
||||
func (d *dbBaseSqlite) ShowColumnsQuery(table string) string {
|
||||
return fmt.Sprintf("pragma table_info('%s')", table)
|
||||
}
|
||||
|
||||
// check index exist in sqlite.
|
||||
func (d *dbBaseSqlite) IndexExists(ctx context.Context, db dbQuerier, table string, name string) bool {
|
||||
query := fmt.Sprintf("PRAGMA index_list('%s')", table)
|
||||
rows, err := db.QueryContext(ctx, query)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var tmp, index sql.NullString
|
||||
rows.Scan(&tmp, &index, &tmp, &tmp, &tmp)
|
||||
if name == index.String {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GenerateSpecifyIndex return a specifying index clause
|
||||
func (d *dbBaseSqlite) GenerateSpecifyIndex(tableName string, useIndex int, indexes []string) string {
|
||||
var s []string
|
||||
Q := d.TableQuote()
|
||||
for _, index := range indexes {
|
||||
tmp := fmt.Sprintf(`%s%s%s`, Q, index, Q)
|
||||
s = append(s, tmp)
|
||||
}
|
||||
|
||||
switch useIndex {
|
||||
case hints.KeyUseIndex, hints.KeyForceIndex:
|
||||
return fmt.Sprintf(` INDEXED BY %s `, strings.Join(s, `,`))
|
||||
default:
|
||||
DebugLog.Println("[WARN] Not a valid specifying action, so that action is ignored")
|
||||
return ``
|
||||
}
|
||||
}
|
||||
|
||||
// create new sqlite dbBaser.
|
||||
func newdbBaseSqlite() dbBaser {
|
||||
b := new(dbBaseSqlite)
|
||||
b.ins = b
|
||||
return b
|
||||
}
|
@ -1,499 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm/clauses"
|
||||
"github.com/beego/beego/v2/client/orm/clauses/order_clause"
|
||||
)
|
||||
|
||||
// table info struct.
|
||||
type dbTable struct {
|
||||
id int
|
||||
index string
|
||||
name string
|
||||
names []string
|
||||
sel bool
|
||||
inner bool
|
||||
mi *modelInfo
|
||||
fi *fieldInfo
|
||||
jtl *dbTable
|
||||
}
|
||||
|
||||
// tables collection struct, contains some tables.
|
||||
type dbTables struct {
|
||||
tablesM map[string]*dbTable
|
||||
tables []*dbTable
|
||||
mi *modelInfo
|
||||
base dbBaser
|
||||
skipEnd bool
|
||||
}
|
||||
|
||||
// set table info to collection.
|
||||
// if not exist, create new.
|
||||
func (t *dbTables) set(names []string, mi *modelInfo, fi *fieldInfo, inner bool) *dbTable {
|
||||
name := strings.Join(names, ExprSep)
|
||||
if j, ok := t.tablesM[name]; ok {
|
||||
j.name = name
|
||||
j.mi = mi
|
||||
j.fi = fi
|
||||
j.inner = inner
|
||||
} else {
|
||||
i := len(t.tables) + 1
|
||||
jt := &dbTable{i, fmt.Sprintf("T%d", i), name, names, false, inner, mi, fi, nil}
|
||||
t.tablesM[name] = jt
|
||||
t.tables = append(t.tables, jt)
|
||||
}
|
||||
return t.tablesM[name]
|
||||
}
|
||||
|
||||
// add table info to collection.
|
||||
func (t *dbTables) add(names []string, mi *modelInfo, fi *fieldInfo, inner bool) (*dbTable, bool) {
|
||||
name := strings.Join(names, ExprSep)
|
||||
if _, ok := t.tablesM[name]; !ok {
|
||||
i := len(t.tables) + 1
|
||||
jt := &dbTable{i, fmt.Sprintf("T%d", i), name, names, false, inner, mi, fi, nil}
|
||||
t.tablesM[name] = jt
|
||||
t.tables = append(t.tables, jt)
|
||||
return jt, true
|
||||
}
|
||||
return t.tablesM[name], false
|
||||
}
|
||||
|
||||
// get table info in collection.
|
||||
func (t *dbTables) get(name string) (*dbTable, bool) {
|
||||
j, ok := t.tablesM[name]
|
||||
return j, ok
|
||||
}
|
||||
|
||||
// get related fields info in recursive depth loop.
|
||||
// loop once, depth decreases one.
|
||||
func (t *dbTables) loopDepth(depth int, prefix string, fi *fieldInfo, related []string) []string {
|
||||
if depth < 0 || fi.fieldType == RelManyToMany {
|
||||
return related
|
||||
}
|
||||
|
||||
if prefix == "" {
|
||||
prefix = fi.name
|
||||
} else {
|
||||
prefix = prefix + ExprSep + fi.name
|
||||
}
|
||||
related = append(related, prefix)
|
||||
|
||||
depth--
|
||||
for _, fi := range fi.relModelInfo.fields.fieldsRel {
|
||||
related = t.loopDepth(depth, prefix, fi, related)
|
||||
}
|
||||
|
||||
return related
|
||||
}
|
||||
|
||||
// parse related fields.
|
||||
func (t *dbTables) parseRelated(rels []string, depth int) {
|
||||
relsNum := len(rels)
|
||||
related := make([]string, relsNum)
|
||||
copy(related, rels)
|
||||
|
||||
relDepth := depth
|
||||
|
||||
if relsNum != 0 {
|
||||
relDepth = 0
|
||||
}
|
||||
|
||||
relDepth--
|
||||
for _, fi := range t.mi.fields.fieldsRel {
|
||||
related = t.loopDepth(relDepth, "", fi, related)
|
||||
}
|
||||
|
||||
for i, s := range related {
|
||||
var (
|
||||
exs = strings.Split(s, ExprSep)
|
||||
names = make([]string, 0, len(exs))
|
||||
mmi = t.mi
|
||||
cancel = true
|
||||
jtl *dbTable
|
||||
)
|
||||
|
||||
inner := true
|
||||
|
||||
for _, ex := range exs {
|
||||
if fi, ok := mmi.fields.GetByAny(ex); ok && fi.rel && fi.fieldType != RelManyToMany {
|
||||
names = append(names, fi.name)
|
||||
mmi = fi.relModelInfo
|
||||
|
||||
if fi.null || t.skipEnd {
|
||||
inner = false
|
||||
}
|
||||
|
||||
jt := t.set(names, mmi, fi, inner)
|
||||
jt.jtl = jtl
|
||||
|
||||
if fi.reverse {
|
||||
cancel = false
|
||||
}
|
||||
|
||||
if cancel {
|
||||
jt.sel = depth > 0
|
||||
|
||||
if i < relsNum {
|
||||
jt.sel = true
|
||||
}
|
||||
}
|
||||
|
||||
jtl = jt
|
||||
|
||||
} else {
|
||||
panic(fmt.Errorf("unknown model/table name `%s`", ex))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// generate join string.
|
||||
func (t *dbTables) getJoinSQL() (join string) {
|
||||
Q := t.base.TableQuote()
|
||||
|
||||
for _, jt := range t.tables {
|
||||
if jt.inner {
|
||||
join += "INNER JOIN "
|
||||
} else {
|
||||
join += "LEFT OUTER JOIN "
|
||||
}
|
||||
var (
|
||||
table string
|
||||
t1, t2 string
|
||||
c1, c2 string
|
||||
)
|
||||
t1 = "T0"
|
||||
if jt.jtl != nil {
|
||||
t1 = jt.jtl.index
|
||||
}
|
||||
t2 = jt.index
|
||||
table = jt.mi.table
|
||||
|
||||
switch {
|
||||
case jt.fi.fieldType == RelManyToMany || jt.fi.fieldType == RelReverseMany || jt.fi.reverse && jt.fi.reverseFieldInfo.fieldType == RelManyToMany:
|
||||
c1 = jt.fi.mi.fields.pk.column
|
||||
for _, ffi := range jt.mi.fields.fieldsRel {
|
||||
if jt.fi.mi == ffi.relModelInfo {
|
||||
c2 = ffi.column
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
c1 = jt.fi.column
|
||||
c2 = jt.fi.relModelInfo.fields.pk.column
|
||||
|
||||
if jt.fi.reverse {
|
||||
c1 = jt.mi.fields.pk.column
|
||||
c2 = jt.fi.reverseFieldInfo.column
|
||||
}
|
||||
}
|
||||
|
||||
join += fmt.Sprintf("%s%s%s %s ON %s.%s%s%s = %s.%s%s%s ", Q, table, Q, t2,
|
||||
t2, Q, c2, Q, t1, Q, c1, Q)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// parse orm model struct field tag expression.
|
||||
func (t *dbTables) parseExprs(mi *modelInfo, exprs []string) (index, name string, info *fieldInfo, success bool) {
|
||||
var (
|
||||
jtl *dbTable
|
||||
fi *fieldInfo
|
||||
fiN *fieldInfo
|
||||
mmi = mi
|
||||
)
|
||||
|
||||
num := len(exprs) - 1
|
||||
var names []string
|
||||
|
||||
inner := true
|
||||
|
||||
loopFor:
|
||||
for i, ex := range exprs {
|
||||
|
||||
var ok, okN bool
|
||||
|
||||
if fiN != nil {
|
||||
fi = fiN
|
||||
ok = true
|
||||
fiN = nil
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
fi, ok = mmi.fields.GetByAny(ex)
|
||||
}
|
||||
|
||||
_ = okN
|
||||
|
||||
if ok {
|
||||
|
||||
isRel := fi.rel || fi.reverse
|
||||
|
||||
names = append(names, fi.name)
|
||||
|
||||
switch {
|
||||
case fi.rel:
|
||||
mmi = fi.relModelInfo
|
||||
if fi.fieldType == RelManyToMany {
|
||||
mmi = fi.relThroughModelInfo
|
||||
}
|
||||
case fi.reverse:
|
||||
mmi = fi.reverseFieldInfo.mi
|
||||
}
|
||||
|
||||
if i < num {
|
||||
fiN, okN = mmi.fields.GetByAny(exprs[i+1])
|
||||
}
|
||||
|
||||
if isRel && (!fi.mi.isThrough || num != i) {
|
||||
if fi.null || t.skipEnd {
|
||||
inner = false
|
||||
}
|
||||
|
||||
if t.skipEnd && okN || !t.skipEnd {
|
||||
if t.skipEnd && okN && fiN.pk {
|
||||
goto loopEnd
|
||||
}
|
||||
|
||||
jt, _ := t.add(names, mmi, fi, inner)
|
||||
jt.jtl = jtl
|
||||
jtl = jt
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if num != i {
|
||||
continue
|
||||
}
|
||||
|
||||
loopEnd:
|
||||
|
||||
if i == 0 || jtl == nil {
|
||||
index = "T0"
|
||||
} else {
|
||||
index = jtl.index
|
||||
}
|
||||
|
||||
info = fi
|
||||
|
||||
if jtl == nil {
|
||||
name = fi.name
|
||||
} else {
|
||||
name = jtl.name + ExprSep + fi.name
|
||||
}
|
||||
|
||||
switch {
|
||||
case fi.rel:
|
||||
|
||||
case fi.reverse:
|
||||
switch fi.reverseFieldInfo.fieldType {
|
||||
case RelOneToOne, RelForeignKey:
|
||||
index = jtl.index
|
||||
info = fi.reverseFieldInfo.mi.fields.pk
|
||||
name = info.name
|
||||
}
|
||||
}
|
||||
|
||||
break loopFor
|
||||
|
||||
} else {
|
||||
index = ""
|
||||
name = ""
|
||||
info = nil
|
||||
success = false
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
success = index != "" && info != nil
|
||||
return
|
||||
}
|
||||
|
||||
// generate condition sql.
|
||||
func (t *dbTables) getCondSQL(cond *Condition, sub bool, tz *time.Location) (where string, params []interface{}) {
|
||||
if cond == nil || cond.IsEmpty() {
|
||||
return
|
||||
}
|
||||
|
||||
Q := t.base.TableQuote()
|
||||
|
||||
mi := t.mi
|
||||
|
||||
for i, p := range cond.params {
|
||||
if i > 0 {
|
||||
if p.isOr {
|
||||
where += "OR "
|
||||
} else {
|
||||
where += "AND "
|
||||
}
|
||||
}
|
||||
if p.isNot {
|
||||
where += "NOT "
|
||||
}
|
||||
if p.isCond {
|
||||
w, ps := t.getCondSQL(p.cond, true, tz)
|
||||
if w != "" {
|
||||
w = fmt.Sprintf("( %s) ", w)
|
||||
}
|
||||
where += w
|
||||
params = append(params, ps...)
|
||||
} else {
|
||||
exprs := p.exprs
|
||||
|
||||
num := len(exprs) - 1
|
||||
operator := ""
|
||||
if operators[exprs[num]] {
|
||||
operator = exprs[num]
|
||||
exprs = exprs[:num]
|
||||
}
|
||||
|
||||
index, _, fi, suc := t.parseExprs(mi, exprs)
|
||||
if !suc {
|
||||
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(p.exprs, ExprSep)))
|
||||
}
|
||||
|
||||
if operator == "" {
|
||||
operator = "exact"
|
||||
}
|
||||
|
||||
var operSQL string
|
||||
var args []interface{}
|
||||
if p.isRaw {
|
||||
operSQL = p.sql
|
||||
} else {
|
||||
operSQL, args = t.base.GenerateOperatorSQL(mi, fi, operator, p.args, tz)
|
||||
}
|
||||
|
||||
leftCol := fmt.Sprintf("%s.%s%s%s", index, Q, fi.column, Q)
|
||||
t.base.GenerateOperatorLeftCol(fi, operator, &leftCol)
|
||||
|
||||
where += fmt.Sprintf("%s %s ", leftCol, operSQL)
|
||||
params = append(params, args...)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if !sub && where != "" {
|
||||
where = "WHERE " + where
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// generate group sql.
|
||||
func (t *dbTables) getGroupSQL(groups []string) (groupSQL string) {
|
||||
if len(groups) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
Q := t.base.TableQuote()
|
||||
|
||||
groupSqls := make([]string, 0, len(groups))
|
||||
for _, group := range groups {
|
||||
exprs := strings.Split(group, ExprSep)
|
||||
|
||||
index, _, fi, suc := t.parseExprs(t.mi, exprs)
|
||||
if !suc {
|
||||
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(exprs, ExprSep)))
|
||||
}
|
||||
|
||||
groupSqls = append(groupSqls, fmt.Sprintf("%s.%s%s%s", index, Q, fi.column, Q))
|
||||
}
|
||||
|
||||
groupSQL = fmt.Sprintf("GROUP BY %s ", strings.Join(groupSqls, ", "))
|
||||
return
|
||||
}
|
||||
|
||||
// generate order sql.
|
||||
func (t *dbTables) getOrderSQL(orders []*order_clause.Order) (orderSQL string) {
|
||||
if len(orders) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
Q := t.base.TableQuote()
|
||||
|
||||
orderSqls := make([]string, 0, len(orders))
|
||||
for _, order := range orders {
|
||||
column := order.GetColumn()
|
||||
clause := strings.Split(column, clauses.ExprDot)
|
||||
|
||||
if order.IsRaw() {
|
||||
if len(clause) == 2 {
|
||||
orderSqls = append(orderSqls, fmt.Sprintf("%s.%s%s%s %s", clause[0], Q, clause[1], Q, order.SortString()))
|
||||
} else if len(clause) == 1 {
|
||||
orderSqls = append(orderSqls, fmt.Sprintf("%s%s%s %s", Q, clause[0], Q, order.SortString()))
|
||||
} else {
|
||||
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(clause, ExprSep)))
|
||||
}
|
||||
} else {
|
||||
index, _, fi, suc := t.parseExprs(t.mi, clause)
|
||||
if !suc {
|
||||
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(clause, ExprSep)))
|
||||
}
|
||||
|
||||
orderSqls = append(orderSqls, fmt.Sprintf("%s.%s%s%s %s", index, Q, fi.column, Q, order.SortString()))
|
||||
}
|
||||
}
|
||||
|
||||
orderSQL = fmt.Sprintf("ORDER BY %s ", strings.Join(orderSqls, ", "))
|
||||
return
|
||||
}
|
||||
|
||||
// generate limit sql.
|
||||
func (t *dbTables) getLimitSQL(mi *modelInfo, offset int64, limit int64) (limits string) {
|
||||
if limit == 0 {
|
||||
limit = int64(DefaultRowsLimit)
|
||||
}
|
||||
if limit < 0 {
|
||||
// no limit
|
||||
if offset > 0 {
|
||||
maxLimit := t.base.MaxLimit()
|
||||
if maxLimit == 0 {
|
||||
limits = fmt.Sprintf("OFFSET %d", offset)
|
||||
} else {
|
||||
limits = fmt.Sprintf("LIMIT %d OFFSET %d", maxLimit, offset)
|
||||
}
|
||||
}
|
||||
} else if offset <= 0 {
|
||||
limits = fmt.Sprintf("LIMIT %d", limit)
|
||||
} else {
|
||||
limits = fmt.Sprintf("LIMIT %d OFFSET %d", limit, offset)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// getIndexSql generate index sql.
|
||||
func (t *dbTables) getIndexSql(tableName string, useIndex int, indexes []string) (clause string) {
|
||||
if len(indexes) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
return t.base.GenerateSpecifyIndex(tableName, useIndex, indexes)
|
||||
}
|
||||
|
||||
// crete new tables collection.
|
||||
func newDbTables(mi *modelInfo, base dbBaser) *dbTables {
|
||||
tables := &dbTables{}
|
||||
tables.tablesM = make(map[string]*dbTable)
|
||||
tables.mi = mi
|
||||
tables.base = base
|
||||
return tables
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
// Copyright 2015 TiDB Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// mysql dbBaser implementation.
|
||||
type dbBaseTidb struct {
|
||||
dbBase
|
||||
}
|
||||
|
||||
var _ dbBaser = new(dbBaseTidb)
|
||||
|
||||
// get mysql operator.
|
||||
func (d *dbBaseTidb) OperatorSQL(operator string) string {
|
||||
return mysqlOperators[operator]
|
||||
}
|
||||
|
||||
// get mysql table field types.
|
||||
func (d *dbBaseTidb) DbTypes() map[string]string {
|
||||
return mysqlTypes
|
||||
}
|
||||
|
||||
// show table sql for mysql.
|
||||
func (d *dbBaseTidb) ShowTablesQuery() string {
|
||||
return "SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND table_schema = DATABASE()"
|
||||
}
|
||||
|
||||
// show columns sql of table for mysql.
|
||||
func (d *dbBaseTidb) ShowColumnsQuery(table string) string {
|
||||
return fmt.Sprintf("SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE FROM information_schema.columns "+
|
||||
"WHERE table_schema = DATABASE() AND table_name = '%s'", table)
|
||||
}
|
||||
|
||||
// execute sql to check index exist.
|
||||
func (d *dbBaseTidb) IndexExists(ctx context.Context, db dbQuerier, table string, name string) bool {
|
||||
row := db.QueryRowContext(ctx, "SELECT count(*) FROM information_schema.statistics "+
|
||||
"WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ?", table, name)
|
||||
var cnt int
|
||||
row.Scan(&cnt)
|
||||
return cnt > 0
|
||||
}
|
||||
|
||||
// create new mysql dbBaser.
|
||||
func newdbBaseTidb() dbBaser {
|
||||
b := new(dbBaseTidb)
|
||||
b.ins = b
|
||||
return b
|
||||
}
|
@ -1,175 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
// get table alias.
|
||||
func getDbAlias(name string) *alias {
|
||||
if al, ok := dataBaseCache.get(name); ok {
|
||||
return al
|
||||
}
|
||||
panic(fmt.Errorf("unknown DataBase alias name %s", name))
|
||||
}
|
||||
|
||||
// get pk column info.
|
||||
func getExistPk(mi *modelInfo, ind reflect.Value) (column string, value interface{}, exist bool) {
|
||||
fi := mi.fields.pk
|
||||
|
||||
v := ind.FieldByIndex(fi.fieldIndex)
|
||||
if fi.fieldType&IsPositiveIntegerField > 0 {
|
||||
vu := v.Uint()
|
||||
exist = vu > 0
|
||||
value = vu
|
||||
} else if fi.fieldType&IsIntegerField > 0 {
|
||||
vu := v.Int()
|
||||
exist = true
|
||||
value = vu
|
||||
} else if fi.fieldType&IsRelField > 0 {
|
||||
_, value, exist = getExistPk(fi.relModelInfo, reflect.Indirect(v))
|
||||
} else {
|
||||
vu := v.String()
|
||||
exist = vu != ""
|
||||
value = vu
|
||||
}
|
||||
|
||||
column = fi.column
|
||||
return
|
||||
}
|
||||
|
||||
// get fields description as flatted string.
|
||||
func getFlatParams(fi *fieldInfo, args []interface{}, tz *time.Location) (params []interface{}) {
|
||||
outFor:
|
||||
for _, arg := range args {
|
||||
if arg == nil {
|
||||
params = append(params, arg)
|
||||
continue
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(arg)
|
||||
kind := val.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
val = val.Elem()
|
||||
kind = val.Kind()
|
||||
arg = val.Interface()
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case reflect.String:
|
||||
v := val.String()
|
||||
if fi != nil {
|
||||
if fi.fieldType == TypeTimeField || fi.fieldType == TypeDateField || fi.fieldType == TypeDateTimeField {
|
||||
var t time.Time
|
||||
var err error
|
||||
if len(v) >= 19 {
|
||||
s := v[:19]
|
||||
t, err = time.ParseInLocation(formatDateTime, s, DefaultTimeLoc)
|
||||
} else if len(v) >= 10 {
|
||||
s := v
|
||||
if len(v) > 10 {
|
||||
s = v[:10]
|
||||
}
|
||||
t, err = time.ParseInLocation(formatDate, s, tz)
|
||||
} else {
|
||||
s := v
|
||||
if len(s) > 8 {
|
||||
s = v[:8]
|
||||
}
|
||||
t, err = time.ParseInLocation(formatTime, s, tz)
|
||||
}
|
||||
if err == nil {
|
||||
if fi.fieldType == TypeDateField {
|
||||
v = t.In(tz).Format(formatDate)
|
||||
} else if fi.fieldType == TypeDateTimeField {
|
||||
v = t.In(tz).Format(formatDateTime)
|
||||
} else {
|
||||
v = t.In(tz).Format(formatTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
arg = v
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
arg = val.Int()
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
arg = val.Uint()
|
||||
case reflect.Float32:
|
||||
arg, _ = StrTo(ToStr(arg)).Float64()
|
||||
case reflect.Float64:
|
||||
arg = val.Float()
|
||||
case reflect.Bool:
|
||||
arg = val.Bool()
|
||||
case reflect.Slice, reflect.Array:
|
||||
if _, ok := arg.([]byte); ok {
|
||||
continue outFor
|
||||
}
|
||||
|
||||
var args []interface{}
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
v := val.Index(i)
|
||||
|
||||
var vu interface{}
|
||||
if v.CanInterface() {
|
||||
vu = v.Interface()
|
||||
}
|
||||
|
||||
if vu == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
args = append(args, vu)
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
p := getFlatParams(fi, args, tz)
|
||||
params = append(params, p...)
|
||||
}
|
||||
continue outFor
|
||||
case reflect.Struct:
|
||||
if v, ok := arg.(time.Time); ok {
|
||||
if fi != nil && fi.fieldType == TypeDateField {
|
||||
arg = v.In(tz).Format(formatDate)
|
||||
} else if fi != nil && fi.fieldType == TypeDateTimeField {
|
||||
arg = v.In(tz).Format(formatDateTime)
|
||||
} else if fi != nil && fi.fieldType == TypeTimeField {
|
||||
arg = v.In(tz).Format(formatTime)
|
||||
} else {
|
||||
arg = v.In(tz).Format(formatDateTime)
|
||||
}
|
||||
} else {
|
||||
typ := val.Type()
|
||||
name := getFullName(typ)
|
||||
var value interface{}
|
||||
if mmi, ok := defaultModelCache.getByFullName(name); ok {
|
||||
if _, vu, exist := getExistPk(mmi, val); exist {
|
||||
value = vu
|
||||
}
|
||||
}
|
||||
arg = value
|
||||
|
||||
if arg == nil {
|
||||
panic(fmt.Errorf("need a valid args value, unknown table or value `%s`", name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
params = append(params, arg)
|
||||
}
|
||||
return
|
||||
}
|
@ -1,181 +0,0 @@
|
||||
// Copyright 2020 beego
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils"
|
||||
)
|
||||
|
||||
// DoNothingOrm won't do anything, usually you use this to custom your mock Ormer implementation
|
||||
// I think golang mocking interface is hard to use
|
||||
// this may help you to integrate with Ormer
|
||||
|
||||
var _ Ormer = new(DoNothingOrm)
|
||||
|
||||
type DoNothingOrm struct{}
|
||||
|
||||
func (d *DoNothingOrm) Read(md interface{}, cols ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) ReadWithCtx(ctx context.Context, md interface{}, cols ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) ReadForUpdate(md interface{}, cols ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) ReadForUpdateWithCtx(ctx context.Context, md interface{}, cols ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) {
|
||||
return false, 0, nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 string, cols ...string) (bool, int64, error) {
|
||||
return false, 0, nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) LoadRelated(md interface{}, name string, args ...utils.KV) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) LoadRelatedWithCtx(ctx context.Context, md interface{}, name string, args ...utils.KV) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) QueryM2M(md interface{}, name string) QueryM2Mer {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NOTE: this method is deprecated, context parameter will not take effect.
|
||||
func (d *DoNothingOrm) QueryM2MWithCtx(ctx context.Context, md interface{}, name string) QueryM2Mer {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) QueryTable(ptrStructOrTableName interface{}) QuerySeter {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NOTE: this method is deprecated, context parameter will not take effect.
|
||||
func (d *DoNothingOrm) QueryTableWithCtx(ctx context.Context, ptrStructOrTableName interface{}) QuerySeter {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) DBStats() *sql.DBStats {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) Insert(md interface{}) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) InsertWithCtx(ctx context.Context, md interface{}) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) InsertOrUpdateWithCtx(ctx context.Context, md interface{}, colConflitAndArgs ...string) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) InsertMulti(bulk int, mds interface{}) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) InsertMultiWithCtx(ctx context.Context, bulk int, mds interface{}) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) Update(md interface{}, cols ...string) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) UpdateWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) Delete(md interface{}, cols ...string) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) DeleteWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) Raw(query string, args ...interface{}) RawSeter {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) RawWithCtx(ctx context.Context, query string, args ...interface{}) RawSeter {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) Driver() Driver {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) Begin() (TxOrmer, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) BeginWithCtx(ctx context.Context) (TxOrmer, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) BeginWithOpts(opts *sql.TxOptions) (TxOrmer, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) DoTx(task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) DoTxWithCtx(ctx context.Context, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) DoTxWithOpts(opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DoNothingOrm) DoTxWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DoNothingTxOrm is similar with DoNothingOrm, usually you use it to test
|
||||
type DoNothingTxOrm struct {
|
||||
DoNothingOrm
|
||||
}
|
||||
|
||||
func (d *DoNothingTxOrm) Commit() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DoNothingTxOrm) Rollback() error {
|
||||
return nil
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
// Copyright 2020 beego
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// FilterChain is used to build a Filter
|
||||
// don't forget to call next(...) inside your Filter
|
||||
type FilterChain func(next Filter) Filter
|
||||
|
||||
// Filter's behavior is a little big strange.
|
||||
// it's only be called when users call methods of Ormer
|
||||
// return value is an array. it's a little bit hard to understand,
|
||||
// for example, the Ormer's Read method only return error
|
||||
// so the filter processing this method should return an array whose first element is error
|
||||
// and, Ormer's ReadOrCreateWithCtx return three values, so the Filter's result should contains three values
|
||||
type Filter func(ctx context.Context, inv *Invocation) []interface{}
|
||||
|
||||
var globalFilterChains = make([]FilterChain, 0, 4)
|
||||
|
||||
// AddGlobalFilterChain adds a new FilterChain
|
||||
// All orm instances built after this invocation will use this filterChain,
|
||||
// but instances built before this invocation will not be affected
|
||||
func AddGlobalFilterChain(filterChain ...FilterChain) {
|
||||
globalFilterChains = append(globalFilterChains, filterChain...)
|
||||
}
|
@ -1,534 +0,0 @@
|
||||
// Copyright 2020 beego
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/beego/beego/v2/core/logs"
|
||||
"github.com/beego/beego/v2/core/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
TxNameKey = "TxName"
|
||||
)
|
||||
|
||||
var (
|
||||
_ Ormer = new(filterOrmDecorator)
|
||||
_ TxOrmer = new(filterOrmDecorator)
|
||||
)
|
||||
|
||||
type filterOrmDecorator struct {
|
||||
ormer
|
||||
TxBeginner
|
||||
TxCommitter
|
||||
|
||||
root Filter
|
||||
|
||||
insideTx bool
|
||||
txStartTime time.Time
|
||||
txName string
|
||||
}
|
||||
|
||||
func NewFilterOrmDecorator(delegate Ormer, filterChains ...FilterChain) Ormer {
|
||||
res := &filterOrmDecorator{
|
||||
ormer: delegate,
|
||||
TxBeginner: delegate,
|
||||
root: func(ctx context.Context, inv *Invocation) []interface{} {
|
||||
return inv.execute(ctx)
|
||||
},
|
||||
}
|
||||
|
||||
for i := len(filterChains) - 1; i >= 0; i-- {
|
||||
node := filterChains[i]
|
||||
res.root = node(res.root)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func NewFilterTxOrmDecorator(delegate TxOrmer, root Filter, txName string) TxOrmer {
|
||||
res := &filterOrmDecorator{
|
||||
ormer: delegate,
|
||||
TxCommitter: delegate,
|
||||
root: root,
|
||||
insideTx: true,
|
||||
txStartTime: time.Now(),
|
||||
txName: txName,
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) Read(md interface{}, cols ...string) error {
|
||||
return f.ReadWithCtx(context.Background(), md, cols...)
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) ReadWithCtx(ctx context.Context, md interface{}, cols ...string) error {
|
||||
mi, _ := defaultModelCache.getByMd(md)
|
||||
inv := &Invocation{
|
||||
Method: "ReadWithCtx",
|
||||
Args: []interface{}{md, cols},
|
||||
Md: md,
|
||||
mi: mi,
|
||||
InsideTx: f.insideTx,
|
||||
TxStartTime: f.txStartTime,
|
||||
f: func(c context.Context) []interface{} {
|
||||
err := f.ormer.ReadWithCtx(c, md, cols...)
|
||||
return []interface{}{err}
|
||||
},
|
||||
}
|
||||
res := f.root(ctx, inv)
|
||||
return f.convertError(res[0])
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) ReadForUpdate(md interface{}, cols ...string) error {
|
||||
return f.ReadForUpdateWithCtx(context.Background(), md, cols...)
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) ReadForUpdateWithCtx(ctx context.Context, md interface{}, cols ...string) error {
|
||||
mi, _ := defaultModelCache.getByMd(md)
|
||||
inv := &Invocation{
|
||||
Method: "ReadForUpdateWithCtx",
|
||||
Args: []interface{}{md, cols},
|
||||
Md: md,
|
||||
mi: mi,
|
||||
InsideTx: f.insideTx,
|
||||
TxStartTime: f.txStartTime,
|
||||
f: func(c context.Context) []interface{} {
|
||||
err := f.ormer.ReadForUpdateWithCtx(c, md, cols...)
|
||||
return []interface{}{err}
|
||||
},
|
||||
}
|
||||
res := f.root(ctx, inv)
|
||||
return f.convertError(res[0])
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) {
|
||||
return f.ReadOrCreateWithCtx(context.Background(), md, col1, cols...)
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 string, cols ...string) (bool, int64, error) {
|
||||
mi, _ := defaultModelCache.getByMd(md)
|
||||
inv := &Invocation{
|
||||
Method: "ReadOrCreateWithCtx",
|
||||
Args: []interface{}{md, col1, cols},
|
||||
Md: md,
|
||||
mi: mi,
|
||||
InsideTx: f.insideTx,
|
||||
TxStartTime: f.txStartTime,
|
||||
f: func(c context.Context) []interface{} {
|
||||
ok, res, err := f.ormer.ReadOrCreateWithCtx(c, md, col1, cols...)
|
||||
return []interface{}{ok, res, err}
|
||||
},
|
||||
}
|
||||
res := f.root(ctx, inv)
|
||||
return res[0].(bool), res[1].(int64), f.convertError(res[2])
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) LoadRelated(md interface{}, name string, args ...utils.KV) (int64, error) {
|
||||
return f.LoadRelatedWithCtx(context.Background(), md, name, args...)
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) LoadRelatedWithCtx(ctx context.Context, md interface{}, name string, args ...utils.KV) (int64, error) {
|
||||
mi, _ := defaultModelCache.getByMd(md)
|
||||
inv := &Invocation{
|
||||
Method: "LoadRelatedWithCtx",
|
||||
Args: []interface{}{md, name, args},
|
||||
Md: md,
|
||||
mi: mi,
|
||||
InsideTx: f.insideTx,
|
||||
TxStartTime: f.txStartTime,
|
||||
f: func(c context.Context) []interface{} {
|
||||
res, err := f.ormer.LoadRelatedWithCtx(c, md, name, args...)
|
||||
return []interface{}{res, err}
|
||||
},
|
||||
}
|
||||
res := f.root(ctx, inv)
|
||||
return res[0].(int64), f.convertError(res[1])
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) QueryM2M(md interface{}, name string) QueryM2Mer {
|
||||
mi, _ := defaultModelCache.getByMd(md)
|
||||
inv := &Invocation{
|
||||
Method: "QueryM2M",
|
||||
Args: []interface{}{md, name},
|
||||
Md: md,
|
||||
mi: mi,
|
||||
InsideTx: f.insideTx,
|
||||
TxStartTime: f.txStartTime,
|
||||
f: func(c context.Context) []interface{} {
|
||||
res := f.ormer.QueryM2M(md, name)
|
||||
return []interface{}{res}
|
||||
},
|
||||
}
|
||||
res := f.root(context.Background(), inv)
|
||||
if res[0] == nil {
|
||||
return nil
|
||||
}
|
||||
return res[0].(QueryM2Mer)
|
||||
}
|
||||
|
||||
// NOTE: this method is deprecated, context parameter will not take effect.
|
||||
func (f *filterOrmDecorator) QueryM2MWithCtx(_ context.Context, md interface{}, name string) QueryM2Mer {
|
||||
logs.Warn("QueryM2MWithCtx is DEPRECATED. Use methods with `WithCtx` on QueryM2Mer suffix as replacement.")
|
||||
return f.QueryM2M(md, name)
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) QueryTable(ptrStructOrTableName interface{}) QuerySeter {
|
||||
var (
|
||||
name string
|
||||
md interface{}
|
||||
mi *modelInfo
|
||||
)
|
||||
|
||||
if table, ok := ptrStructOrTableName.(string); ok {
|
||||
name = table
|
||||
} else {
|
||||
name = getFullName(indirectType(reflect.TypeOf(ptrStructOrTableName)))
|
||||
md = ptrStructOrTableName
|
||||
}
|
||||
|
||||
if m, ok := defaultModelCache.getByFullName(name); ok {
|
||||
mi = m
|
||||
}
|
||||
|
||||
inv := &Invocation{
|
||||
Method: "QueryTable",
|
||||
Args: []interface{}{ptrStructOrTableName},
|
||||
InsideTx: f.insideTx,
|
||||
TxStartTime: f.txStartTime,
|
||||
Md: md,
|
||||
mi: mi,
|
||||
f: func(c context.Context) []interface{} {
|
||||
res := f.ormer.QueryTable(ptrStructOrTableName)
|
||||
return []interface{}{res}
|
||||
},
|
||||
}
|
||||
res := f.root(context.Background(), inv)
|
||||
|
||||
if res[0] == nil {
|
||||
return nil
|
||||
}
|
||||
return res[0].(QuerySeter)
|
||||
}
|
||||
|
||||
// NOTE: this method is deprecated, context parameter will not take effect.
|
||||
func (f *filterOrmDecorator) QueryTableWithCtx(_ context.Context, ptrStructOrTableName interface{}) QuerySeter {
|
||||
logs.Warn("QueryTableWithCtx is DEPRECATED. Use methods with `WithCtx`on QuerySeter suffix as replacement.")
|
||||
return f.QueryTable(ptrStructOrTableName)
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) DBStats() *sql.DBStats {
|
||||
inv := &Invocation{
|
||||
Method: "DBStats",
|
||||
InsideTx: f.insideTx,
|
||||
TxStartTime: f.txStartTime,
|
||||
f: func(c context.Context) []interface{} {
|
||||
res := f.ormer.DBStats()
|
||||
return []interface{}{res}
|
||||
},
|
||||
}
|
||||
res := f.root(context.Background(), inv)
|
||||
|
||||
if res[0] == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return res[0].(*sql.DBStats)
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) Insert(md interface{}) (int64, error) {
|
||||
return f.InsertWithCtx(context.Background(), md)
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) InsertWithCtx(ctx context.Context, md interface{}) (int64, error) {
|
||||
mi, _ := defaultModelCache.getByMd(md)
|
||||
inv := &Invocation{
|
||||
Method: "InsertWithCtx",
|
||||
Args: []interface{}{md},
|
||||
Md: md,
|
||||
mi: mi,
|
||||
InsideTx: f.insideTx,
|
||||
TxStartTime: f.txStartTime,
|
||||
f: func(c context.Context) []interface{} {
|
||||
res, err := f.ormer.InsertWithCtx(c, md)
|
||||
return []interface{}{res, err}
|
||||
},
|
||||
}
|
||||
res := f.root(ctx, inv)
|
||||
return res[0].(int64), f.convertError(res[1])
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) {
|
||||
return f.InsertOrUpdateWithCtx(context.Background(), md, colConflitAndArgs...)
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) InsertOrUpdateWithCtx(ctx context.Context, md interface{}, colConflitAndArgs ...string) (int64, error) {
|
||||
mi, _ := defaultModelCache.getByMd(md)
|
||||
inv := &Invocation{
|
||||
Method: "InsertOrUpdateWithCtx",
|
||||
Args: []interface{}{md, colConflitAndArgs},
|
||||
Md: md,
|
||||
mi: mi,
|
||||
InsideTx: f.insideTx,
|
||||
TxStartTime: f.txStartTime,
|
||||
f: func(c context.Context) []interface{} {
|
||||
res, err := f.ormer.InsertOrUpdateWithCtx(c, md, colConflitAndArgs...)
|
||||
return []interface{}{res, err}
|
||||
},
|
||||
}
|
||||
res := f.root(ctx, inv)
|
||||
return res[0].(int64), f.convertError(res[1])
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) InsertMulti(bulk int, mds interface{}) (int64, error) {
|
||||
return f.InsertMultiWithCtx(context.Background(), bulk, mds)
|
||||
}
|
||||
|
||||
// InsertMultiWithCtx uses the first element's model info
|
||||
func (f *filterOrmDecorator) InsertMultiWithCtx(ctx context.Context, bulk int, mds interface{}) (int64, error) {
|
||||
var (
|
||||
md interface{}
|
||||
mi *modelInfo
|
||||
)
|
||||
|
||||
sind := reflect.Indirect(reflect.ValueOf(mds))
|
||||
|
||||
if (sind.Kind() == reflect.Array || sind.Kind() == reflect.Slice) && sind.Len() > 0 {
|
||||
ind := reflect.Indirect(sind.Index(0))
|
||||
md = ind.Interface()
|
||||
mi, _ = defaultModelCache.getByMd(md)
|
||||
}
|
||||
|
||||
inv := &Invocation{
|
||||
Method: "InsertMultiWithCtx",
|
||||
Args: []interface{}{bulk, mds},
|
||||
Md: md,
|
||||
mi: mi,
|
||||
InsideTx: f.insideTx,
|
||||
TxStartTime: f.txStartTime,
|
||||
f: func(c context.Context) []interface{} {
|
||||
res, err := f.ormer.InsertMultiWithCtx(c, bulk, mds)
|
||||
return []interface{}{res, err}
|
||||
},
|
||||
}
|
||||
res := f.root(ctx, inv)
|
||||
return res[0].(int64), f.convertError(res[1])
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) Update(md interface{}, cols ...string) (int64, error) {
|
||||
return f.UpdateWithCtx(context.Background(), md, cols...)
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) UpdateWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) {
|
||||
mi, _ := defaultModelCache.getByMd(md)
|
||||
inv := &Invocation{
|
||||
Method: "UpdateWithCtx",
|
||||
Args: []interface{}{md, cols},
|
||||
Md: md,
|
||||
mi: mi,
|
||||
InsideTx: f.insideTx,
|
||||
TxStartTime: f.txStartTime,
|
||||
f: func(c context.Context) []interface{} {
|
||||
res, err := f.ormer.UpdateWithCtx(c, md, cols...)
|
||||
return []interface{}{res, err}
|
||||
},
|
||||
}
|
||||
res := f.root(ctx, inv)
|
||||
return res[0].(int64), f.convertError(res[1])
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) Delete(md interface{}, cols ...string) (int64, error) {
|
||||
return f.DeleteWithCtx(context.Background(), md, cols...)
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) DeleteWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) {
|
||||
mi, _ := defaultModelCache.getByMd(md)
|
||||
inv := &Invocation{
|
||||
Method: "DeleteWithCtx",
|
||||
Args: []interface{}{md, cols},
|
||||
Md: md,
|
||||
mi: mi,
|
||||
InsideTx: f.insideTx,
|
||||
TxStartTime: f.txStartTime,
|
||||
f: func(c context.Context) []interface{} {
|
||||
res, err := f.ormer.DeleteWithCtx(c, md, cols...)
|
||||
return []interface{}{res, err}
|
||||
},
|
||||
}
|
||||
res := f.root(ctx, inv)
|
||||
return res[0].(int64), f.convertError(res[1])
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) Raw(query string, args ...interface{}) RawSeter {
|
||||
return f.RawWithCtx(context.Background(), query, args...)
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) RawWithCtx(ctx context.Context, query string, args ...interface{}) RawSeter {
|
||||
inv := &Invocation{
|
||||
Method: "RawWithCtx",
|
||||
Args: []interface{}{query, args},
|
||||
InsideTx: f.insideTx,
|
||||
TxStartTime: f.txStartTime,
|
||||
f: func(c context.Context) []interface{} {
|
||||
res := f.ormer.RawWithCtx(c, query, args...)
|
||||
return []interface{}{res}
|
||||
},
|
||||
}
|
||||
res := f.root(ctx, inv)
|
||||
|
||||
if res[0] == nil {
|
||||
return nil
|
||||
}
|
||||
return res[0].(RawSeter)
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) Driver() Driver {
|
||||
inv := &Invocation{
|
||||
Method: "Driver",
|
||||
InsideTx: f.insideTx,
|
||||
TxStartTime: f.txStartTime,
|
||||
f: func(c context.Context) []interface{} {
|
||||
res := f.ormer.Driver()
|
||||
return []interface{}{res}
|
||||
},
|
||||
}
|
||||
res := f.root(context.Background(), inv)
|
||||
if res[0] == nil {
|
||||
return nil
|
||||
}
|
||||
return res[0].(Driver)
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) Begin() (TxOrmer, error) {
|
||||
return f.BeginWithCtxAndOpts(context.Background(), nil)
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) BeginWithCtx(ctx context.Context) (TxOrmer, error) {
|
||||
return f.BeginWithCtxAndOpts(ctx, nil)
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) BeginWithOpts(opts *sql.TxOptions) (TxOrmer, error) {
|
||||
return f.BeginWithCtxAndOpts(context.Background(), opts)
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error) {
|
||||
inv := &Invocation{
|
||||
Method: "BeginWithCtxAndOpts",
|
||||
Args: []interface{}{opts},
|
||||
InsideTx: f.insideTx,
|
||||
TxStartTime: f.txStartTime,
|
||||
f: func(c context.Context) []interface{} {
|
||||
res, err := f.TxBeginner.BeginWithCtxAndOpts(c, opts)
|
||||
res = NewFilterTxOrmDecorator(res, f.root, getTxNameFromCtx(c))
|
||||
return []interface{}{res, err}
|
||||
},
|
||||
}
|
||||
res := f.root(ctx, inv)
|
||||
return res[0].(TxOrmer), f.convertError(res[1])
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) DoTx(task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||
return f.DoTxWithCtxAndOpts(context.Background(), nil, task)
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) DoTxWithCtx(ctx context.Context, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||
return f.DoTxWithCtxAndOpts(ctx, nil, task)
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) DoTxWithOpts(opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||
return f.DoTxWithCtxAndOpts(context.Background(), opts, task)
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) DoTxWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||
inv := &Invocation{
|
||||
Method: "DoTxWithCtxAndOpts",
|
||||
Args: []interface{}{opts, task},
|
||||
InsideTx: f.insideTx,
|
||||
TxStartTime: f.txStartTime,
|
||||
TxName: getTxNameFromCtx(ctx),
|
||||
f: func(c context.Context) []interface{} {
|
||||
err := doTxTemplate(c, f, opts, task)
|
||||
return []interface{}{err}
|
||||
},
|
||||
}
|
||||
res := f.root(ctx, inv)
|
||||
return f.convertError(res[0])
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) Commit() error {
|
||||
inv := &Invocation{
|
||||
Method: "Commit",
|
||||
Args: []interface{}{},
|
||||
InsideTx: f.insideTx,
|
||||
TxStartTime: f.txStartTime,
|
||||
TxName: f.txName,
|
||||
f: func(c context.Context) []interface{} {
|
||||
err := f.TxCommitter.Commit()
|
||||
return []interface{}{err}
|
||||
},
|
||||
}
|
||||
res := f.root(context.Background(), inv)
|
||||
return f.convertError(res[0])
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) Rollback() error {
|
||||
inv := &Invocation{
|
||||
Method: "Rollback",
|
||||
Args: []interface{}{},
|
||||
InsideTx: f.insideTx,
|
||||
TxStartTime: f.txStartTime,
|
||||
TxName: f.txName,
|
||||
f: func(c context.Context) []interface{} {
|
||||
err := f.TxCommitter.Rollback()
|
||||
return []interface{}{err}
|
||||
},
|
||||
}
|
||||
res := f.root(context.Background(), inv)
|
||||
return f.convertError(res[0])
|
||||
}
|
||||
|
||||
func (f *filterOrmDecorator) RollbackUnlessCommit() error {
|
||||
inv := &Invocation{
|
||||
Method: "RollbackUnlessCommit",
|
||||
Args: []interface{}{},
|
||||
InsideTx: f.insideTx,
|
||||
TxStartTime: f.txStartTime,
|
||||
TxName: f.txName,
|
||||
f: func(c context.Context) []interface{} {
|
||||
err := f.TxCommitter.RollbackUnlessCommit()
|
||||
return []interface{}{err}
|
||||
},
|
||||
}
|
||||
res := f.root(context.Background(), inv)
|
||||
return f.convertError(res[0])
|
||||
}
|
||||
|
||||
func (*filterOrmDecorator) convertError(v interface{}) error {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
return v.(error)
|
||||
}
|
||||
|
||||
func getTxNameFromCtx(ctx context.Context) string {
|
||||
txName := ""
|
||||
if n, ok := ctx.Value(TxNameKey).(string); ok {
|
||||
txName = n
|
||||
}
|
||||
return txName
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
// Copyright 2020 beego-dev
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package hints
|
||||
|
||||
import (
|
||||
"github.com/beego/beego/v2/core/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
// query level
|
||||
KeyForceIndex = iota
|
||||
KeyUseIndex
|
||||
KeyIgnoreIndex
|
||||
KeyForUpdate
|
||||
KeyLimit
|
||||
KeyOffset
|
||||
KeyOrderBy
|
||||
KeyRelDepth
|
||||
)
|
||||
|
||||
type Hint struct {
|
||||
key interface{}
|
||||
value interface{}
|
||||
}
|
||||
|
||||
var _ utils.KV = new(Hint)
|
||||
|
||||
// GetKey return key
|
||||
func (s *Hint) GetKey() interface{} {
|
||||
return s.key
|
||||
}
|
||||
|
||||
// GetValue return value
|
||||
func (s *Hint) GetValue() interface{} {
|
||||
return s.value
|
||||
}
|
||||
|
||||
var _ utils.KV = new(Hint)
|
||||
|
||||
// ForceIndex return a hint about ForceIndex
|
||||
func ForceIndex(indexes ...string) *Hint {
|
||||
return NewHint(KeyForceIndex, indexes)
|
||||
}
|
||||
|
||||
// UseIndex return a hint about UseIndex
|
||||
func UseIndex(indexes ...string) *Hint {
|
||||
return NewHint(KeyUseIndex, indexes)
|
||||
}
|
||||
|
||||
// IgnoreIndex return a hint about IgnoreIndex
|
||||
func IgnoreIndex(indexes ...string) *Hint {
|
||||
return NewHint(KeyIgnoreIndex, indexes)
|
||||
}
|
||||
|
||||
// ForUpdate return a hint about ForUpdate
|
||||
func ForUpdate() *Hint {
|
||||
return NewHint(KeyForUpdate, true)
|
||||
}
|
||||
|
||||
// DefaultRelDepth return a hint about DefaultRelDepth
|
||||
func DefaultRelDepth() *Hint {
|
||||
return NewHint(KeyRelDepth, true)
|
||||
}
|
||||
|
||||
// RelDepth return a hint about RelDepth
|
||||
func RelDepth(d int) *Hint {
|
||||
return NewHint(KeyRelDepth, d)
|
||||
}
|
||||
|
||||
// Limit return a hint about Limit
|
||||
func Limit(d int64) *Hint {
|
||||
return NewHint(KeyLimit, d)
|
||||
}
|
||||
|
||||
// Offset return a hint about Offset
|
||||
func Offset(d int64) *Hint {
|
||||
return NewHint(KeyOffset, d)
|
||||
}
|
||||
|
||||
// OrderBy return a hint about OrderBy
|
||||
func OrderBy(s string) *Hint {
|
||||
return NewHint(KeyOrderBy, s)
|
||||
}
|
||||
|
||||
// NewHint return a hint
|
||||
func NewHint(key interface{}, value interface{}) *Hint {
|
||||
return &Hint{
|
||||
key: key,
|
||||
value: value,
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
// Copyright 2020 beego
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Invocation represents an "Orm" invocation
|
||||
type Invocation struct {
|
||||
Method string
|
||||
// Md may be nil in some cases. It depends on method
|
||||
Md interface{}
|
||||
// the args are all arguments except context.Context
|
||||
Args []interface{}
|
||||
|
||||
mi *modelInfo
|
||||
// f is the Orm operation
|
||||
f func(ctx context.Context) []interface{}
|
||||
|
||||
// insideTx indicates whether this is inside a transaction
|
||||
InsideTx bool
|
||||
TxStartTime time.Time
|
||||
TxName string
|
||||
}
|
||||
|
||||
func (inv *Invocation) GetTableName() string {
|
||||
if inv.mi != nil {
|
||||
return inv.mi.table
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (inv *Invocation) execute(ctx context.Context) []interface{} {
|
||||
return inv.f(ctx)
|
||||
}
|
||||
|
||||
// GetPkFieldName return the primary key of this table
|
||||
// if not found, "" is returned
|
||||
func (inv *Invocation) GetPkFieldName() string {
|
||||
if inv.mi.fields.pk != nil {
|
||||
return inv.mi.fields.pk.name
|
||||
}
|
||||
return ""
|
||||
}
|
@ -1,573 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
odCascade = "cascade"
|
||||
odSetNULL = "set_null"
|
||||
odSetDefault = "set_default"
|
||||
odDoNothing = "do_nothing"
|
||||
defaultStructTagName = "orm"
|
||||
defaultStructTagDelim = ";"
|
||||
)
|
||||
|
||||
var defaultModelCache = NewModelCacheHandler()
|
||||
|
||||
// model info collection
|
||||
type modelCache struct {
|
||||
sync.RWMutex // only used outsite for bootStrap
|
||||
orders []string
|
||||
cache map[string]*modelInfo
|
||||
cacheByFullName map[string]*modelInfo
|
||||
done bool
|
||||
}
|
||||
|
||||
// NewModelCacheHandler generator of modelCache
|
||||
func NewModelCacheHandler() *modelCache {
|
||||
return &modelCache{
|
||||
cache: make(map[string]*modelInfo),
|
||||
cacheByFullName: make(map[string]*modelInfo),
|
||||
}
|
||||
}
|
||||
|
||||
// get all model info
|
||||
func (mc *modelCache) all() map[string]*modelInfo {
|
||||
m := make(map[string]*modelInfo, len(mc.cache))
|
||||
for k, v := range mc.cache {
|
||||
m[k] = v
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// get ordered model info
|
||||
func (mc *modelCache) allOrdered() []*modelInfo {
|
||||
m := make([]*modelInfo, 0, len(mc.orders))
|
||||
for _, table := range mc.orders {
|
||||
m = append(m, mc.cache[table])
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// get model info by table name
|
||||
func (mc *modelCache) get(table string) (mi *modelInfo, ok bool) {
|
||||
mi, ok = mc.cache[table]
|
||||
return
|
||||
}
|
||||
|
||||
// get model info by full name
|
||||
func (mc *modelCache) getByFullName(name string) (mi *modelInfo, ok bool) {
|
||||
mi, ok = mc.cacheByFullName[name]
|
||||
return
|
||||
}
|
||||
|
||||
func (mc *modelCache) getByMd(md interface{}) (*modelInfo, bool) {
|
||||
val := reflect.ValueOf(md)
|
||||
ind := reflect.Indirect(val)
|
||||
typ := ind.Type()
|
||||
name := getFullName(typ)
|
||||
return mc.getByFullName(name)
|
||||
}
|
||||
|
||||
// set model info to collection
|
||||
func (mc *modelCache) set(table string, mi *modelInfo) *modelInfo {
|
||||
mii := mc.cache[table]
|
||||
mc.cache[table] = mi
|
||||
mc.cacheByFullName[mi.fullName] = mi
|
||||
if mii == nil {
|
||||
mc.orders = append(mc.orders, table)
|
||||
}
|
||||
return mii
|
||||
}
|
||||
|
||||
// clean all model info.
|
||||
func (mc *modelCache) clean() {
|
||||
mc.Lock()
|
||||
defer mc.Unlock()
|
||||
|
||||
mc.orders = make([]string, 0)
|
||||
mc.cache = make(map[string]*modelInfo)
|
||||
mc.cacheByFullName = make(map[string]*modelInfo)
|
||||
mc.done = false
|
||||
}
|
||||
|
||||
// bootstrap bootstrap for models
|
||||
func (mc *modelCache) bootstrap() {
|
||||
mc.Lock()
|
||||
defer mc.Unlock()
|
||||
if mc.done {
|
||||
return
|
||||
}
|
||||
var (
|
||||
err error
|
||||
models map[string]*modelInfo
|
||||
)
|
||||
if dataBaseCache.getDefault() == nil {
|
||||
err = fmt.Errorf("must have one register DataBase alias named `default`")
|
||||
goto end
|
||||
}
|
||||
|
||||
// set rel and reverse model
|
||||
// RelManyToMany set the relTable
|
||||
models = mc.all()
|
||||
for _, mi := range models {
|
||||
for _, fi := range mi.fields.columns {
|
||||
if fi.rel || fi.reverse {
|
||||
elm := fi.addrValue.Type().Elem()
|
||||
if fi.fieldType == RelReverseMany || fi.fieldType == RelManyToMany {
|
||||
elm = elm.Elem()
|
||||
}
|
||||
// check the rel or reverse model already register
|
||||
name := getFullName(elm)
|
||||
mii, ok := mc.getByFullName(name)
|
||||
if !ok || mii.pkg != elm.PkgPath() {
|
||||
err = fmt.Errorf("can not find rel in field `%s`, `%s` may be miss register", fi.fullName, elm.String())
|
||||
goto end
|
||||
}
|
||||
fi.relModelInfo = mii
|
||||
|
||||
switch fi.fieldType {
|
||||
case RelManyToMany:
|
||||
if fi.relThrough != "" {
|
||||
if i := strings.LastIndex(fi.relThrough, "."); i != -1 && len(fi.relThrough) > (i+1) {
|
||||
pn := fi.relThrough[:i]
|
||||
rmi, ok := mc.getByFullName(fi.relThrough)
|
||||
if !ok || pn != rmi.pkg {
|
||||
err = fmt.Errorf("field `%s` wrong rel_through value `%s` cannot find table", fi.fullName, fi.relThrough)
|
||||
goto end
|
||||
}
|
||||
fi.relThroughModelInfo = rmi
|
||||
fi.relTable = rmi.table
|
||||
} else {
|
||||
err = fmt.Errorf("field `%s` wrong rel_through value `%s`", fi.fullName, fi.relThrough)
|
||||
goto end
|
||||
}
|
||||
} else {
|
||||
i := newM2MModelInfo(mi, mii)
|
||||
if fi.relTable != "" {
|
||||
i.table = fi.relTable
|
||||
}
|
||||
if v := mc.set(i.table, i); v != nil {
|
||||
err = fmt.Errorf("the rel table name `%s` already registered, cannot be use, please change one", fi.relTable)
|
||||
goto end
|
||||
}
|
||||
fi.relTable = i.table
|
||||
fi.relThroughModelInfo = i
|
||||
}
|
||||
|
||||
fi.relThroughModelInfo.isThrough = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check the rel filed while the relModelInfo also has filed point to current model
|
||||
// if not exist, add a new field to the relModelInfo
|
||||
models = mc.all()
|
||||
for _, mi := range models {
|
||||
for _, fi := range mi.fields.fieldsRel {
|
||||
switch fi.fieldType {
|
||||
case RelForeignKey, RelOneToOne, RelManyToMany:
|
||||
inModel := false
|
||||
for _, ffi := range fi.relModelInfo.fields.fieldsReverse {
|
||||
if ffi.relModelInfo == mi {
|
||||
inModel = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !inModel {
|
||||
rmi := fi.relModelInfo
|
||||
ffi := new(fieldInfo)
|
||||
ffi.name = mi.name
|
||||
ffi.column = ffi.name
|
||||
ffi.fullName = rmi.fullName + "." + ffi.name
|
||||
ffi.reverse = true
|
||||
ffi.relModelInfo = mi
|
||||
ffi.mi = rmi
|
||||
if fi.fieldType == RelOneToOne {
|
||||
ffi.fieldType = RelReverseOne
|
||||
} else {
|
||||
ffi.fieldType = RelReverseMany
|
||||
}
|
||||
if !rmi.fields.Add(ffi) {
|
||||
added := false
|
||||
for cnt := 0; cnt < 5; cnt++ {
|
||||
ffi.name = fmt.Sprintf("%s%d", mi.name, cnt)
|
||||
ffi.column = ffi.name
|
||||
ffi.fullName = rmi.fullName + "." + ffi.name
|
||||
if added = rmi.fields.Add(ffi); added {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !added {
|
||||
panic(fmt.Errorf("cannot generate auto reverse field info `%s` to `%s`", fi.fullName, ffi.fullName))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
models = mc.all()
|
||||
for _, mi := range models {
|
||||
for _, fi := range mi.fields.fieldsRel {
|
||||
switch fi.fieldType {
|
||||
case RelManyToMany:
|
||||
for _, ffi := range fi.relThroughModelInfo.fields.fieldsRel {
|
||||
switch ffi.fieldType {
|
||||
case RelOneToOne, RelForeignKey:
|
||||
if ffi.relModelInfo == fi.relModelInfo {
|
||||
fi.reverseFieldInfoTwo = ffi
|
||||
}
|
||||
if ffi.relModelInfo == mi {
|
||||
fi.reverseField = ffi.name
|
||||
fi.reverseFieldInfo = ffi
|
||||
}
|
||||
}
|
||||
}
|
||||
if fi.reverseFieldInfoTwo == nil {
|
||||
err = fmt.Errorf("can not find m2m field for m2m model `%s`, ensure your m2m model defined correct",
|
||||
fi.relThroughModelInfo.fullName)
|
||||
goto end
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
models = mc.all()
|
||||
for _, mi := range models {
|
||||
for _, fi := range mi.fields.fieldsReverse {
|
||||
switch fi.fieldType {
|
||||
case RelReverseOne:
|
||||
found := false
|
||||
mForA:
|
||||
for _, ffi := range fi.relModelInfo.fields.fieldsByType[RelOneToOne] {
|
||||
if ffi.relModelInfo == mi {
|
||||
found = true
|
||||
fi.reverseField = ffi.name
|
||||
fi.reverseFieldInfo = ffi
|
||||
|
||||
ffi.reverseField = fi.name
|
||||
ffi.reverseFieldInfo = fi
|
||||
break mForA
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
err = fmt.Errorf("reverse field `%s` not found in model `%s`", fi.fullName, fi.relModelInfo.fullName)
|
||||
goto end
|
||||
}
|
||||
case RelReverseMany:
|
||||
found := false
|
||||
mForB:
|
||||
for _, ffi := range fi.relModelInfo.fields.fieldsByType[RelForeignKey] {
|
||||
if ffi.relModelInfo == mi {
|
||||
found = true
|
||||
fi.reverseField = ffi.name
|
||||
fi.reverseFieldInfo = ffi
|
||||
|
||||
ffi.reverseField = fi.name
|
||||
ffi.reverseFieldInfo = fi
|
||||
|
||||
break mForB
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
mForC:
|
||||
for _, ffi := range fi.relModelInfo.fields.fieldsByType[RelManyToMany] {
|
||||
conditions := fi.relThrough != "" && fi.relThrough == ffi.relThrough ||
|
||||
fi.relTable != "" && fi.relTable == ffi.relTable ||
|
||||
fi.relThrough == "" && fi.relTable == ""
|
||||
if ffi.relModelInfo == mi && conditions {
|
||||
found = true
|
||||
|
||||
fi.reverseField = ffi.reverseFieldInfoTwo.name
|
||||
fi.reverseFieldInfo = ffi.reverseFieldInfoTwo
|
||||
fi.relThroughModelInfo = ffi.relThroughModelInfo
|
||||
fi.reverseFieldInfoTwo = ffi.reverseFieldInfo
|
||||
fi.reverseFieldInfoM2M = ffi
|
||||
ffi.reverseFieldInfoM2M = fi
|
||||
|
||||
break mForC
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
err = fmt.Errorf("reverse field for `%s` not found in model `%s`", fi.fullName, fi.relModelInfo.fullName)
|
||||
goto end
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
debug.PrintStack()
|
||||
}
|
||||
mc.done = true
|
||||
}
|
||||
|
||||
// register register models to model cache
|
||||
func (mc *modelCache) register(prefixOrSuffixStr string, prefixOrSuffix bool, models ...interface{}) (err error) {
|
||||
for _, model := range models {
|
||||
val := reflect.ValueOf(model)
|
||||
typ := reflect.Indirect(val).Type()
|
||||
|
||||
if val.Kind() != reflect.Ptr {
|
||||
err = fmt.Errorf("<orm.RegisterModel> cannot use non-ptr model struct `%s`", getFullName(typ))
|
||||
return
|
||||
}
|
||||
// For this case:
|
||||
// u := &User{}
|
||||
// registerModel(&u)
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
err = fmt.Errorf("<orm.RegisterModel> only allow ptr model struct, it looks you use two reference to the struct `%s`", typ)
|
||||
return
|
||||
}
|
||||
if val.Elem().Kind() == reflect.Slice {
|
||||
val = reflect.New(val.Elem().Type().Elem())
|
||||
}
|
||||
table := getTableName(val)
|
||||
|
||||
if prefixOrSuffixStr != "" {
|
||||
if prefixOrSuffix {
|
||||
table = prefixOrSuffixStr + table
|
||||
} else {
|
||||
table = table + prefixOrSuffixStr
|
||||
}
|
||||
}
|
||||
|
||||
// models's fullname is pkgpath + struct name
|
||||
name := getFullName(typ)
|
||||
if _, ok := mc.getByFullName(name); ok {
|
||||
err = fmt.Errorf("<orm.RegisterModel> model `%s` repeat register, must be unique\n", name)
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := mc.get(table); ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
mi := newModelInfo(val)
|
||||
if mi.fields.pk == nil {
|
||||
outFor:
|
||||
for _, fi := range mi.fields.fieldsDB {
|
||||
if strings.ToLower(fi.name) == "id" {
|
||||
switch fi.addrValue.Elem().Kind() {
|
||||
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint32, reflect.Uint64:
|
||||
fi.auto = true
|
||||
fi.pk = true
|
||||
mi.fields.pk = fi
|
||||
break outFor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mi.table = table
|
||||
mi.pkg = typ.PkgPath()
|
||||
mi.model = model
|
||||
mi.manual = true
|
||||
|
||||
mc.set(table, mi)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// getDbDropSQL get database scheme drop sql queries
|
||||
func (mc *modelCache) getDbDropSQL(al *alias) (queries []string, err error) {
|
||||
if len(mc.cache) == 0 {
|
||||
err = errors.New("no Model found, need register your model")
|
||||
return
|
||||
}
|
||||
|
||||
Q := al.DbBaser.TableQuote()
|
||||
|
||||
for _, mi := range mc.allOrdered() {
|
||||
queries = append(queries, fmt.Sprintf(`DROP TABLE IF EXISTS %s%s%s`, Q, mi.table, Q))
|
||||
}
|
||||
return queries, nil
|
||||
}
|
||||
|
||||
// getDbCreateSQL get database scheme creation sql queries
|
||||
func (mc *modelCache) getDbCreateSQL(al *alias) (queries []string, tableIndexes map[string][]dbIndex, err error) {
|
||||
if len(mc.cache) == 0 {
|
||||
err = errors.New("no Model found, need register your model")
|
||||
return
|
||||
}
|
||||
|
||||
Q := al.DbBaser.TableQuote()
|
||||
T := al.DbBaser.DbTypes()
|
||||
sep := fmt.Sprintf("%s, %s", Q, Q)
|
||||
|
||||
tableIndexes = make(map[string][]dbIndex)
|
||||
|
||||
for _, mi := range mc.allOrdered() {
|
||||
sql := fmt.Sprintf("-- %s\n", strings.Repeat("-", 50))
|
||||
sql += fmt.Sprintf("-- Table Structure for `%s`\n", mi.fullName)
|
||||
sql += fmt.Sprintf("-- %s\n", strings.Repeat("-", 50))
|
||||
|
||||
sql += fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s%s%s (\n", Q, mi.table, Q)
|
||||
|
||||
columns := make([]string, 0, len(mi.fields.fieldsDB))
|
||||
|
||||
sqlIndexes := [][]string{}
|
||||
var commentIndexes []int // store comment indexes for postgres
|
||||
|
||||
for i, fi := range mi.fields.fieldsDB {
|
||||
column := fmt.Sprintf(" %s%s%s ", Q, fi.column, Q)
|
||||
col := getColumnTyp(al, fi)
|
||||
|
||||
if fi.auto {
|
||||
switch al.Driver {
|
||||
case DRSqlite, DRPostgres:
|
||||
column += T["auto"]
|
||||
default:
|
||||
column += col + " " + T["auto"]
|
||||
}
|
||||
} else if fi.pk {
|
||||
column += col + " " + T["pk"]
|
||||
} else {
|
||||
column += col
|
||||
|
||||
if !fi.null {
|
||||
column += " " + "NOT NULL"
|
||||
}
|
||||
|
||||
// if fi.initial.String() != "" {
|
||||
// column += " DEFAULT " + fi.initial.String()
|
||||
// }
|
||||
|
||||
// Append attribute DEFAULT
|
||||
column += getColumnDefault(fi)
|
||||
|
||||
if fi.unique {
|
||||
column += " " + "UNIQUE"
|
||||
}
|
||||
|
||||
if fi.index {
|
||||
sqlIndexes = append(sqlIndexes, []string{fi.column})
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Contains(column, "%COL%") {
|
||||
column = strings.Replace(column, "%COL%", fi.column, -1)
|
||||
}
|
||||
|
||||
if fi.description != "" && al.Driver != DRSqlite {
|
||||
if al.Driver == DRPostgres {
|
||||
commentIndexes = append(commentIndexes, i)
|
||||
} else {
|
||||
column += " " + fmt.Sprintf("COMMENT '%s'", fi.description)
|
||||
}
|
||||
}
|
||||
|
||||
columns = append(columns, column)
|
||||
}
|
||||
|
||||
if mi.model != nil {
|
||||
allnames := getTableUnique(mi.addrField)
|
||||
if !mi.manual && len(mi.uniques) > 0 {
|
||||
allnames = append(allnames, mi.uniques)
|
||||
}
|
||||
for _, names := range allnames {
|
||||
cols := make([]string, 0, len(names))
|
||||
for _, name := range names {
|
||||
if fi, ok := mi.fields.GetByAny(name); ok && fi.dbcol {
|
||||
cols = append(cols, fi.column)
|
||||
} else {
|
||||
panic(fmt.Errorf("cannot found column `%s` when parse UNIQUE in `%s.TableUnique`", name, mi.fullName))
|
||||
}
|
||||
}
|
||||
column := fmt.Sprintf(" UNIQUE (%s%s%s)", Q, strings.Join(cols, sep), Q)
|
||||
columns = append(columns, column)
|
||||
}
|
||||
}
|
||||
|
||||
sql += strings.Join(columns, ",\n")
|
||||
sql += "\n)"
|
||||
|
||||
if al.Driver == DRMySQL {
|
||||
var engine string
|
||||
if mi.model != nil {
|
||||
engine = getTableEngine(mi.addrField)
|
||||
}
|
||||
if engine == "" {
|
||||
engine = al.Engine
|
||||
}
|
||||
sql += " ENGINE=" + engine
|
||||
}
|
||||
|
||||
sql += ";"
|
||||
if al.Driver == DRPostgres && len(commentIndexes) > 0 {
|
||||
// append comments for postgres only
|
||||
for _, index := range commentIndexes {
|
||||
sql += fmt.Sprintf("\nCOMMENT ON COLUMN %s%s%s.%s%s%s is '%s';",
|
||||
Q,
|
||||
mi.table,
|
||||
Q,
|
||||
Q,
|
||||
mi.fields.fieldsDB[index].column,
|
||||
Q,
|
||||
mi.fields.fieldsDB[index].description)
|
||||
}
|
||||
}
|
||||
queries = append(queries, sql)
|
||||
|
||||
if mi.model != nil {
|
||||
for _, names := range getTableIndex(mi.addrField) {
|
||||
cols := make([]string, 0, len(names))
|
||||
for _, name := range names {
|
||||
if fi, ok := mi.fields.GetByAny(name); ok && fi.dbcol {
|
||||
cols = append(cols, fi.column)
|
||||
} else {
|
||||
panic(fmt.Errorf("cannot found column `%s` when parse INDEX in `%s.TableIndex`", name, mi.fullName))
|
||||
}
|
||||
}
|
||||
sqlIndexes = append(sqlIndexes, cols)
|
||||
}
|
||||
}
|
||||
|
||||
for _, names := range sqlIndexes {
|
||||
name := mi.table + "_" + strings.Join(names, "_")
|
||||
cols := strings.Join(names, sep)
|
||||
sql := fmt.Sprintf("CREATE INDEX %s%s%s ON %s%s%s (%s%s%s);", Q, name, Q, Q, mi.table, Q, Q, cols, Q)
|
||||
|
||||
index := dbIndex{}
|
||||
index.Table = mi.table
|
||||
index.Name = name
|
||||
index.SQL = sql
|
||||
|
||||
tableIndexes[mi.table] = append(tableIndexes[mi.table], index)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ResetModelCache Clean model cache. Then you can re-RegisterModel.
|
||||
// Common use this api for test case.
|
||||
func ResetModelCache() {
|
||||
defaultModelCache.clean()
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
// RegisterModel register models
|
||||
func RegisterModel(models ...interface{}) {
|
||||
RegisterModelWithPrefix("", models...)
|
||||
}
|
||||
|
||||
// RegisterModelWithPrefix register models with a prefix
|
||||
func RegisterModelWithPrefix(prefix string, models ...interface{}) {
|
||||
if err := defaultModelCache.register(prefix, true, models...); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterModelWithSuffix register models with a suffix
|
||||
func RegisterModelWithSuffix(suffix string, models ...interface{}) {
|
||||
if err := defaultModelCache.register(suffix, false, models...); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// BootStrap bootstrap models.
|
||||
// make all model parsed and can not add more models
|
||||
func BootStrap() {
|
||||
defaultModelCache.bootstrap()
|
||||
}
|
@ -1,485 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var errSkipField = errors.New("skip field")
|
||||
|
||||
// field info collection
|
||||
type fields struct {
|
||||
pk *fieldInfo
|
||||
columns map[string]*fieldInfo
|
||||
fields map[string]*fieldInfo
|
||||
fieldsLow map[string]*fieldInfo
|
||||
fieldsByType map[int][]*fieldInfo
|
||||
fieldsRel []*fieldInfo
|
||||
fieldsReverse []*fieldInfo
|
||||
fieldsDB []*fieldInfo
|
||||
rels []*fieldInfo
|
||||
orders []string
|
||||
dbcols []string
|
||||
}
|
||||
|
||||
// add field info
|
||||
func (f *fields) Add(fi *fieldInfo) (added bool) {
|
||||
if f.fields[fi.name] == nil && f.columns[fi.column] == nil {
|
||||
f.columns[fi.column] = fi
|
||||
f.fields[fi.name] = fi
|
||||
f.fieldsLow[strings.ToLower(fi.name)] = fi
|
||||
} else {
|
||||
return
|
||||
}
|
||||
if _, ok := f.fieldsByType[fi.fieldType]; !ok {
|
||||
f.fieldsByType[fi.fieldType] = make([]*fieldInfo, 0)
|
||||
}
|
||||
f.fieldsByType[fi.fieldType] = append(f.fieldsByType[fi.fieldType], fi)
|
||||
f.orders = append(f.orders, fi.column)
|
||||
if fi.dbcol {
|
||||
f.dbcols = append(f.dbcols, fi.column)
|
||||
f.fieldsDB = append(f.fieldsDB, fi)
|
||||
}
|
||||
if fi.rel {
|
||||
f.fieldsRel = append(f.fieldsRel, fi)
|
||||
}
|
||||
if fi.reverse {
|
||||
f.fieldsReverse = append(f.fieldsReverse, fi)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// get field info by name
|
||||
func (f *fields) GetByName(name string) *fieldInfo {
|
||||
return f.fields[name]
|
||||
}
|
||||
|
||||
// get field info by column name
|
||||
func (f *fields) GetByColumn(column string) *fieldInfo {
|
||||
return f.columns[column]
|
||||
}
|
||||
|
||||
// get field info by string, name is prior
|
||||
func (f *fields) GetByAny(name string) (*fieldInfo, bool) {
|
||||
if fi, ok := f.fields[name]; ok {
|
||||
return fi, ok
|
||||
}
|
||||
if fi, ok := f.fieldsLow[strings.ToLower(name)]; ok {
|
||||
return fi, ok
|
||||
}
|
||||
if fi, ok := f.columns[name]; ok {
|
||||
return fi, ok
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// create new field info collection
|
||||
func newFields() *fields {
|
||||
f := new(fields)
|
||||
f.fields = make(map[string]*fieldInfo)
|
||||
f.fieldsLow = make(map[string]*fieldInfo)
|
||||
f.columns = make(map[string]*fieldInfo)
|
||||
f.fieldsByType = make(map[int][]*fieldInfo)
|
||||
return f
|
||||
}
|
||||
|
||||
// single field info
|
||||
type fieldInfo struct {
|
||||
dbcol bool // table column fk and onetoone
|
||||
inModel bool
|
||||
auto bool
|
||||
pk bool
|
||||
null bool
|
||||
index bool
|
||||
unique bool
|
||||
colDefault bool // whether has default tag
|
||||
toText bool
|
||||
autoNow bool
|
||||
autoNowAdd bool
|
||||
rel bool // if type equal to RelForeignKey, RelOneToOne, RelManyToMany then true
|
||||
reverse bool
|
||||
isFielder bool // implement Fielder interface
|
||||
mi *modelInfo
|
||||
fieldIndex []int
|
||||
fieldType int
|
||||
name string
|
||||
fullName string
|
||||
column string
|
||||
addrValue reflect.Value
|
||||
sf reflect.StructField
|
||||
initial StrTo // store the default value
|
||||
size int
|
||||
reverseField string
|
||||
reverseFieldInfo *fieldInfo
|
||||
reverseFieldInfoTwo *fieldInfo
|
||||
reverseFieldInfoM2M *fieldInfo
|
||||
relTable string
|
||||
relThrough string
|
||||
relThroughModelInfo *modelInfo
|
||||
relModelInfo *modelInfo
|
||||
digits int
|
||||
decimals int
|
||||
onDelete string
|
||||
description string
|
||||
timePrecision *int
|
||||
}
|
||||
|
||||
// new field info
|
||||
func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField, mName string) (fi *fieldInfo, err error) {
|
||||
var (
|
||||
tag string
|
||||
tagValue string
|
||||
initial StrTo // store the default value
|
||||
fieldType int
|
||||
attrs map[string]bool
|
||||
tags map[string]string
|
||||
addrField reflect.Value
|
||||
)
|
||||
|
||||
fi = new(fieldInfo)
|
||||
|
||||
// if field which CanAddr is the follow type
|
||||
// A value is addressable if it is an element of a slice,
|
||||
// an element of an addressable array, a field of an
|
||||
// addressable struct, or the result of dereferencing a pointer.
|
||||
addrField = field
|
||||
if field.CanAddr() && field.Kind() != reflect.Ptr {
|
||||
addrField = field.Addr()
|
||||
if _, ok := addrField.Interface().(Fielder); !ok {
|
||||
if field.Kind() == reflect.Slice {
|
||||
addrField = field
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attrs, tags = parseStructTag(sf.Tag.Get(defaultStructTagName))
|
||||
|
||||
if _, ok := attrs["-"]; ok {
|
||||
return nil, errSkipField
|
||||
}
|
||||
|
||||
digits := tags["digits"]
|
||||
decimals := tags["decimals"]
|
||||
size := tags["size"]
|
||||
onDelete := tags["on_delete"]
|
||||
precision := tags["precision"]
|
||||
initial.Clear()
|
||||
if v, ok := tags["default"]; ok {
|
||||
initial.Set(v)
|
||||
}
|
||||
|
||||
checkType:
|
||||
switch f := addrField.Interface().(type) {
|
||||
case Fielder:
|
||||
fi.isFielder = true
|
||||
if field.Kind() == reflect.Ptr {
|
||||
err = fmt.Errorf("the model Fielder can not be use ptr")
|
||||
goto end
|
||||
}
|
||||
fieldType = f.FieldType()
|
||||
if fieldType&IsRelField > 0 {
|
||||
err = fmt.Errorf("unsupport type custom field, please refer to https://github.com/beego/beego/v2/blob/master/orm/models_fields.go#L24-L42")
|
||||
goto end
|
||||
}
|
||||
default:
|
||||
tag = "rel"
|
||||
tagValue = tags[tag]
|
||||
if tagValue != "" {
|
||||
switch tagValue {
|
||||
case "fk":
|
||||
fieldType = RelForeignKey
|
||||
break checkType
|
||||
case "one":
|
||||
fieldType = RelOneToOne
|
||||
break checkType
|
||||
case "m2m":
|
||||
fieldType = RelManyToMany
|
||||
if tv := tags["rel_table"]; tv != "" {
|
||||
fi.relTable = tv
|
||||
} else if tv := tags["rel_through"]; tv != "" {
|
||||
fi.relThrough = tv
|
||||
}
|
||||
break checkType
|
||||
default:
|
||||
err = fmt.Errorf("rel only allow these value: fk, one, m2m")
|
||||
goto wrongTag
|
||||
}
|
||||
}
|
||||
tag = "reverse"
|
||||
tagValue = tags[tag]
|
||||
if tagValue != "" {
|
||||
switch tagValue {
|
||||
case "one":
|
||||
fieldType = RelReverseOne
|
||||
break checkType
|
||||
case "many":
|
||||
fieldType = RelReverseMany
|
||||
if tv := tags["rel_table"]; tv != "" {
|
||||
fi.relTable = tv
|
||||
} else if tv := tags["rel_through"]; tv != "" {
|
||||
fi.relThrough = tv
|
||||
}
|
||||
break checkType
|
||||
default:
|
||||
err = fmt.Errorf("reverse only allow these value: one, many")
|
||||
goto wrongTag
|
||||
}
|
||||
}
|
||||
|
||||
fieldType, err = getFieldType(addrField)
|
||||
if err != nil {
|
||||
goto end
|
||||
}
|
||||
if fieldType == TypeVarCharField {
|
||||
switch tags["type"] {
|
||||
case "char":
|
||||
fieldType = TypeCharField
|
||||
case "text":
|
||||
fieldType = TypeTextField
|
||||
case "json":
|
||||
fieldType = TypeJSONField
|
||||
case "jsonb":
|
||||
fieldType = TypeJsonbField
|
||||
}
|
||||
}
|
||||
if fieldType == TypeFloatField && (digits != "" || decimals != "") {
|
||||
fieldType = TypeDecimalField
|
||||
}
|
||||
if fieldType == TypeDateTimeField && tags["type"] == "date" {
|
||||
fieldType = TypeDateField
|
||||
}
|
||||
if fieldType == TypeTimeField && tags["type"] == "time" {
|
||||
fieldType = TypeTimeField
|
||||
}
|
||||
}
|
||||
|
||||
// check the rel and reverse type
|
||||
// rel should Ptr
|
||||
// reverse should slice []*struct
|
||||
switch fieldType {
|
||||
case RelForeignKey, RelOneToOne, RelReverseOne:
|
||||
if field.Kind() != reflect.Ptr {
|
||||
err = fmt.Errorf("rel/reverse:one field must be *%s", field.Type().Name())
|
||||
goto end
|
||||
}
|
||||
case RelManyToMany, RelReverseMany:
|
||||
if field.Kind() != reflect.Slice {
|
||||
err = fmt.Errorf("rel/reverse:many field must be slice")
|
||||
goto end
|
||||
} else {
|
||||
if field.Type().Elem().Kind() != reflect.Ptr {
|
||||
err = fmt.Errorf("rel/reverse:many slice must be []*%s", field.Type().Elem().Name())
|
||||
goto end
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if fieldType&IsFieldType == 0 {
|
||||
err = fmt.Errorf("wrong field type")
|
||||
goto end
|
||||
}
|
||||
|
||||
fi.fieldType = fieldType
|
||||
fi.name = sf.Name
|
||||
fi.column = getColumnName(fieldType, addrField, sf, tags["column"])
|
||||
fi.addrValue = addrField
|
||||
fi.sf = sf
|
||||
fi.fullName = mi.fullName + mName + "." + sf.Name
|
||||
|
||||
fi.description = tags["description"]
|
||||
fi.null = attrs["null"]
|
||||
fi.index = attrs["index"]
|
||||
fi.auto = attrs["auto"]
|
||||
fi.pk = attrs["pk"]
|
||||
fi.unique = attrs["unique"]
|
||||
|
||||
// Mark object property if there is attribute "default" in the orm configuration
|
||||
if _, ok := tags["default"]; ok {
|
||||
fi.colDefault = true
|
||||
}
|
||||
|
||||
switch fieldType {
|
||||
case RelManyToMany, RelReverseMany, RelReverseOne:
|
||||
fi.null = false
|
||||
fi.index = false
|
||||
fi.auto = false
|
||||
fi.pk = false
|
||||
fi.unique = false
|
||||
default:
|
||||
fi.dbcol = true
|
||||
}
|
||||
|
||||
switch fieldType {
|
||||
case RelForeignKey, RelOneToOne, RelManyToMany:
|
||||
fi.rel = true
|
||||
if fieldType == RelOneToOne {
|
||||
fi.unique = true
|
||||
}
|
||||
case RelReverseMany, RelReverseOne:
|
||||
fi.reverse = true
|
||||
}
|
||||
|
||||
if fi.rel && fi.dbcol {
|
||||
switch onDelete {
|
||||
case odCascade, odDoNothing:
|
||||
case odSetDefault:
|
||||
if !initial.Exist() {
|
||||
err = errors.New("on_delete: set_default need set field a default value")
|
||||
goto end
|
||||
}
|
||||
case odSetNULL:
|
||||
if !fi.null {
|
||||
err = errors.New("on_delete: set_null need set field null")
|
||||
goto end
|
||||
}
|
||||
default:
|
||||
if onDelete == "" {
|
||||
onDelete = odCascade
|
||||
} else {
|
||||
err = fmt.Errorf("on_delete value expected choice in `cascade,set_null,set_default,do_nothing`, unknown `%s`", onDelete)
|
||||
goto end
|
||||
}
|
||||
}
|
||||
|
||||
fi.onDelete = onDelete
|
||||
}
|
||||
|
||||
switch fieldType {
|
||||
case TypeBooleanField:
|
||||
case TypeVarCharField, TypeCharField, TypeJSONField, TypeJsonbField:
|
||||
if size != "" {
|
||||
v, e := StrTo(size).Int32()
|
||||
if e != nil {
|
||||
err = fmt.Errorf("wrong size value `%s`", size)
|
||||
} else {
|
||||
fi.size = int(v)
|
||||
}
|
||||
} else {
|
||||
fi.size = 255
|
||||
fi.toText = true
|
||||
}
|
||||
case TypeTextField:
|
||||
fi.index = false
|
||||
fi.unique = false
|
||||
case TypeTimeField, TypeDateField, TypeDateTimeField:
|
||||
if fieldType == TypeDateTimeField {
|
||||
if precision != "" {
|
||||
v, e := StrTo(precision).Int()
|
||||
if e != nil {
|
||||
err = fmt.Errorf("convert %s to int error:%v", precision, e)
|
||||
} else {
|
||||
fi.timePrecision = &v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if attrs["auto_now"] {
|
||||
fi.autoNow = true
|
||||
} else if attrs["auto_now_add"] {
|
||||
fi.autoNowAdd = true
|
||||
}
|
||||
case TypeFloatField:
|
||||
case TypeDecimalField:
|
||||
d1 := digits
|
||||
d2 := decimals
|
||||
v1, er1 := StrTo(d1).Int8()
|
||||
v2, er2 := StrTo(d2).Int8()
|
||||
if er1 != nil || er2 != nil {
|
||||
err = fmt.Errorf("wrong digits/decimals value %s/%s", d2, d1)
|
||||
goto end
|
||||
}
|
||||
fi.digits = int(v1)
|
||||
fi.decimals = int(v2)
|
||||
default:
|
||||
switch {
|
||||
case fieldType&IsIntegerField > 0:
|
||||
case fieldType&IsRelField > 0:
|
||||
}
|
||||
}
|
||||
|
||||
if fieldType&IsIntegerField == 0 {
|
||||
if fi.auto {
|
||||
err = fmt.Errorf("non-integer type cannot set auto")
|
||||
goto end
|
||||
}
|
||||
}
|
||||
|
||||
if fi.auto || fi.pk {
|
||||
if fi.auto {
|
||||
switch addrField.Elem().Kind() {
|
||||
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint32, reflect.Uint64:
|
||||
default:
|
||||
err = fmt.Errorf("auto primary key only support int, int32, int64, uint, uint32, uint64 but found `%s`", addrField.Elem().Kind())
|
||||
goto end
|
||||
}
|
||||
fi.pk = true
|
||||
}
|
||||
fi.null = false
|
||||
fi.index = false
|
||||
fi.unique = false
|
||||
}
|
||||
|
||||
if fi.unique {
|
||||
fi.index = false
|
||||
}
|
||||
|
||||
// can not set default for these type
|
||||
if fi.auto || fi.pk || fi.unique || fieldType == TypeTimeField || fieldType == TypeDateField || fieldType == TypeDateTimeField {
|
||||
initial.Clear()
|
||||
}
|
||||
|
||||
if initial.Exist() {
|
||||
v := initial
|
||||
switch fieldType {
|
||||
case TypeBooleanField:
|
||||
_, err = v.Bool()
|
||||
case TypeFloatField, TypeDecimalField:
|
||||
_, err = v.Float64()
|
||||
case TypeBitField:
|
||||
_, err = v.Int8()
|
||||
case TypeSmallIntegerField:
|
||||
_, err = v.Int16()
|
||||
case TypeIntegerField:
|
||||
_, err = v.Int32()
|
||||
case TypeBigIntegerField:
|
||||
_, err = v.Int64()
|
||||
case TypePositiveBitField:
|
||||
_, err = v.Uint8()
|
||||
case TypePositiveSmallIntegerField:
|
||||
_, err = v.Uint16()
|
||||
case TypePositiveIntegerField:
|
||||
_, err = v.Uint32()
|
||||
case TypePositiveBigIntegerField:
|
||||
_, err = v.Uint64()
|
||||
}
|
||||
if err != nil {
|
||||
tag, tagValue = "default", tags["default"]
|
||||
goto wrongTag
|
||||
}
|
||||
}
|
||||
|
||||
fi.initial = initial
|
||||
end:
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
wrongTag:
|
||||
return nil, fmt.Errorf("wrong tag format: `%s:\"%s\"`, %s", tag, tagValue, err)
|
||||
}
|
@ -1,148 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// single model info
|
||||
type modelInfo struct {
|
||||
manual bool
|
||||
isThrough bool
|
||||
pkg string
|
||||
name string
|
||||
fullName string
|
||||
table string
|
||||
model interface{}
|
||||
fields *fields
|
||||
addrField reflect.Value // store the original struct value
|
||||
uniques []string
|
||||
}
|
||||
|
||||
// new model info
|
||||
func newModelInfo(val reflect.Value) (mi *modelInfo) {
|
||||
mi = &modelInfo{}
|
||||
mi.fields = newFields()
|
||||
ind := reflect.Indirect(val)
|
||||
mi.addrField = val
|
||||
mi.name = ind.Type().Name()
|
||||
mi.fullName = getFullName(ind.Type())
|
||||
addModelFields(mi, ind, "", []int{})
|
||||
return
|
||||
}
|
||||
|
||||
// index: FieldByIndex returns the nested field corresponding to index
|
||||
func addModelFields(mi *modelInfo, ind reflect.Value, mName string, index []int) {
|
||||
var (
|
||||
err error
|
||||
fi *fieldInfo
|
||||
sf reflect.StructField
|
||||
)
|
||||
|
||||
for i := 0; i < ind.NumField(); i++ {
|
||||
field := ind.Field(i)
|
||||
sf = ind.Type().Field(i)
|
||||
// if the field is unexported skip
|
||||
if sf.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
// add anonymous struct fields
|
||||
if sf.Anonymous {
|
||||
addModelFields(mi, field, mName+"."+sf.Name, append(index, i))
|
||||
continue
|
||||
}
|
||||
|
||||
fi, err = newFieldInfo(mi, field, sf, mName)
|
||||
if err == errSkipField {
|
||||
err = nil
|
||||
continue
|
||||
} else if err != nil {
|
||||
break
|
||||
}
|
||||
// record current field index
|
||||
fi.fieldIndex = append(fi.fieldIndex, index...)
|
||||
fi.fieldIndex = append(fi.fieldIndex, i)
|
||||
fi.mi = mi
|
||||
fi.inModel = true
|
||||
if !mi.fields.Add(fi) {
|
||||
err = fmt.Errorf("duplicate column name: %s", fi.column)
|
||||
break
|
||||
}
|
||||
if fi.pk {
|
||||
if mi.fields.pk != nil {
|
||||
err = fmt.Errorf("one model must have one pk field only")
|
||||
break
|
||||
} else {
|
||||
mi.fields.pk = fi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(fmt.Errorf("field: %s.%s, %s", ind.Type(), sf.Name, err))
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
|
||||
// combine related model info to new model info.
|
||||
// prepare for relation models query.
|
||||
func newM2MModelInfo(m1, m2 *modelInfo) (mi *modelInfo) {
|
||||
mi = new(modelInfo)
|
||||
mi.fields = newFields()
|
||||
mi.table = m1.table + "_" + m2.table + "s"
|
||||
mi.name = camelString(mi.table)
|
||||
mi.fullName = m1.pkg + "." + mi.name
|
||||
|
||||
fa := new(fieldInfo) // pk
|
||||
f1 := new(fieldInfo) // m1 table RelForeignKey
|
||||
f2 := new(fieldInfo) // m2 table RelForeignKey
|
||||
fa.fieldType = TypeBigIntegerField
|
||||
fa.auto = true
|
||||
fa.pk = true
|
||||
fa.dbcol = true
|
||||
fa.name = "Id"
|
||||
fa.column = "id"
|
||||
fa.fullName = mi.fullName + "." + fa.name
|
||||
|
||||
f1.dbcol = true
|
||||
f2.dbcol = true
|
||||
f1.fieldType = RelForeignKey
|
||||
f2.fieldType = RelForeignKey
|
||||
f1.name = camelString(m1.table)
|
||||
f2.name = camelString(m2.table)
|
||||
f1.fullName = mi.fullName + "." + f1.name
|
||||
f2.fullName = mi.fullName + "." + f2.name
|
||||
f1.column = m1.table + "_id"
|
||||
f2.column = m2.table + "_id"
|
||||
f1.rel = true
|
||||
f2.rel = true
|
||||
f1.relTable = m1.table
|
||||
f2.relTable = m2.table
|
||||
f1.relModelInfo = m1
|
||||
f2.relModelInfo = m2
|
||||
f1.mi = mi
|
||||
f2.mi = mi
|
||||
|
||||
mi.fields.Add(fa)
|
||||
mi.fields.Add(f1)
|
||||
mi.fields.Add(f2)
|
||||
mi.fields.pk = fa
|
||||
|
||||
mi.uniques = []string{f1.column, f2.column}
|
||||
return
|
||||
}
|
@ -1,243 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 1 is attr
|
||||
// 2 is tag
|
||||
var supportTag = map[string]int{
|
||||
"-": 1,
|
||||
"null": 1,
|
||||
"index": 1,
|
||||
"unique": 1,
|
||||
"pk": 1,
|
||||
"auto": 1,
|
||||
"auto_now": 1,
|
||||
"auto_now_add": 1,
|
||||
"size": 2,
|
||||
"column": 2,
|
||||
"default": 2,
|
||||
"rel": 2,
|
||||
"reverse": 2,
|
||||
"rel_table": 2,
|
||||
"rel_through": 2,
|
||||
"digits": 2,
|
||||
"decimals": 2,
|
||||
"on_delete": 2,
|
||||
"type": 2,
|
||||
"description": 2,
|
||||
"precision": 2,
|
||||
}
|
||||
|
||||
// get reflect.Type name with package path.
|
||||
func getFullName(typ reflect.Type) string {
|
||||
return typ.PkgPath() + "." + typ.Name()
|
||||
}
|
||||
|
||||
// getTableName get struct table name.
|
||||
// If the struct implement the TableName, then get the result as tablename
|
||||
// else use the struct name which will apply snakeString.
|
||||
func getTableName(val reflect.Value) string {
|
||||
if fun := val.MethodByName("TableName"); fun.IsValid() {
|
||||
vals := fun.Call([]reflect.Value{})
|
||||
// has return and the first val is string
|
||||
if len(vals) > 0 && vals[0].Kind() == reflect.String {
|
||||
return vals[0].String()
|
||||
}
|
||||
}
|
||||
return snakeString(reflect.Indirect(val).Type().Name())
|
||||
}
|
||||
|
||||
// get table engine, myisam or innodb.
|
||||
func getTableEngine(val reflect.Value) string {
|
||||
fun := val.MethodByName("TableEngine")
|
||||
if fun.IsValid() {
|
||||
vals := fun.Call([]reflect.Value{})
|
||||
if len(vals) > 0 && vals[0].Kind() == reflect.String {
|
||||
return vals[0].String()
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// get table index from method.
|
||||
func getTableIndex(val reflect.Value) [][]string {
|
||||
fun := val.MethodByName("TableIndex")
|
||||
if fun.IsValid() {
|
||||
vals := fun.Call([]reflect.Value{})
|
||||
if len(vals) > 0 && vals[0].CanInterface() {
|
||||
if d, ok := vals[0].Interface().([][]string); ok {
|
||||
return d
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// get table unique from method
|
||||
func getTableUnique(val reflect.Value) [][]string {
|
||||
fun := val.MethodByName("TableUnique")
|
||||
if fun.IsValid() {
|
||||
vals := fun.Call([]reflect.Value{})
|
||||
if len(vals) > 0 && vals[0].CanInterface() {
|
||||
if d, ok := vals[0].Interface().([][]string); ok {
|
||||
return d
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// get whether the table needs to be created for the database alias
|
||||
func isApplicableTableForDB(val reflect.Value, db string) bool {
|
||||
if !val.IsValid() {
|
||||
return true
|
||||
}
|
||||
fun := val.MethodByName("IsApplicableTableForDB")
|
||||
if fun.IsValid() {
|
||||
vals := fun.Call([]reflect.Value{reflect.ValueOf(db)})
|
||||
if len(vals) > 0 && vals[0].Kind() == reflect.Bool {
|
||||
return vals[0].Bool()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// get snaked column name
|
||||
func getColumnName(ft int, addrField reflect.Value, sf reflect.StructField, col string) string {
|
||||
column := col
|
||||
if col == "" {
|
||||
column = nameStrategyMap[nameStrategy](sf.Name)
|
||||
}
|
||||
switch ft {
|
||||
case RelForeignKey, RelOneToOne:
|
||||
if len(col) == 0 {
|
||||
column = column + "_id"
|
||||
}
|
||||
case RelManyToMany, RelReverseMany, RelReverseOne:
|
||||
column = sf.Name
|
||||
}
|
||||
return column
|
||||
}
|
||||
|
||||
// return field type as type constant from reflect.Value
|
||||
func getFieldType(val reflect.Value) (ft int, err error) {
|
||||
switch val.Type() {
|
||||
case reflect.TypeOf(new(int8)):
|
||||
ft = TypeBitField
|
||||
case reflect.TypeOf(new(int16)):
|
||||
ft = TypeSmallIntegerField
|
||||
case reflect.TypeOf(new(int32)),
|
||||
reflect.TypeOf(new(int)):
|
||||
ft = TypeIntegerField
|
||||
case reflect.TypeOf(new(int64)):
|
||||
ft = TypeBigIntegerField
|
||||
case reflect.TypeOf(new(uint8)):
|
||||
ft = TypePositiveBitField
|
||||
case reflect.TypeOf(new(uint16)):
|
||||
ft = TypePositiveSmallIntegerField
|
||||
case reflect.TypeOf(new(uint32)),
|
||||
reflect.TypeOf(new(uint)):
|
||||
ft = TypePositiveIntegerField
|
||||
case reflect.TypeOf(new(uint64)):
|
||||
ft = TypePositiveBigIntegerField
|
||||
case reflect.TypeOf(new(float32)),
|
||||
reflect.TypeOf(new(float64)):
|
||||
ft = TypeFloatField
|
||||
case reflect.TypeOf(new(bool)):
|
||||
ft = TypeBooleanField
|
||||
case reflect.TypeOf(new(string)):
|
||||
ft = TypeVarCharField
|
||||
case reflect.TypeOf(new(time.Time)):
|
||||
ft = TypeDateTimeField
|
||||
default:
|
||||
elm := reflect.Indirect(val)
|
||||
switch elm.Kind() {
|
||||
case reflect.Int8:
|
||||
ft = TypeBitField
|
||||
case reflect.Int16:
|
||||
ft = TypeSmallIntegerField
|
||||
case reflect.Int32, reflect.Int:
|
||||
ft = TypeIntegerField
|
||||
case reflect.Int64:
|
||||
ft = TypeBigIntegerField
|
||||
case reflect.Uint8:
|
||||
ft = TypePositiveBitField
|
||||
case reflect.Uint16:
|
||||
ft = TypePositiveSmallIntegerField
|
||||
case reflect.Uint32, reflect.Uint:
|
||||
ft = TypePositiveIntegerField
|
||||
case reflect.Uint64:
|
||||
ft = TypePositiveBigIntegerField
|
||||
case reflect.Float32, reflect.Float64:
|
||||
ft = TypeFloatField
|
||||
case reflect.Bool:
|
||||
ft = TypeBooleanField
|
||||
case reflect.String:
|
||||
ft = TypeVarCharField
|
||||
default:
|
||||
if elm.Interface() == nil {
|
||||
panic(fmt.Errorf("%s is nil pointer, may be miss setting tag", val))
|
||||
}
|
||||
switch elm.Interface().(type) {
|
||||
case sql.NullInt64:
|
||||
ft = TypeBigIntegerField
|
||||
case sql.NullFloat64:
|
||||
ft = TypeFloatField
|
||||
case sql.NullBool:
|
||||
ft = TypeBooleanField
|
||||
case sql.NullString:
|
||||
ft = TypeVarCharField
|
||||
case time.Time:
|
||||
ft = TypeDateTimeField
|
||||
}
|
||||
}
|
||||
}
|
||||
if ft&IsFieldType == 0 {
|
||||
err = fmt.Errorf("unsupport field type %s, may be miss setting tag", val)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// parse struct tag string
|
||||
func parseStructTag(data string) (attrs map[string]bool, tags map[string]string) {
|
||||
attrs = make(map[string]bool)
|
||||
tags = make(map[string]string)
|
||||
for _, v := range strings.Split(data, defaultStructTagDelim) {
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
v = strings.TrimSpace(v)
|
||||
if t := strings.ToLower(v); supportTag[t] == 1 {
|
||||
attrs[t] = true
|
||||
} else if i := strings.Index(v, "("); i > 0 && strings.Index(v, ")") == len(v)-1 {
|
||||
name := t[:i]
|
||||
if supportTag[name] == 2 {
|
||||
v = v[i+1 : len(v)-1]
|
||||
tags[name] = v
|
||||
}
|
||||
} else {
|
||||
DebugLog.Println("unsupport orm tag", v)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
@ -1,661 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build go1.8
|
||||
// +build go1.8
|
||||
|
||||
// Package orm provide ORM for MySQL/PostgreSQL/sqlite
|
||||
// Simple Usage
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "fmt"
|
||||
// "github.com/beego/beego/v2/client/orm"
|
||||
// _ "github.com/go-sql-driver/mysql" // import your used driver
|
||||
// )
|
||||
//
|
||||
// // Model Struct
|
||||
// type User struct {
|
||||
// Id int `orm:"auto"`
|
||||
// Name string `orm:"size(100)"`
|
||||
// }
|
||||
//
|
||||
// func init() {
|
||||
// orm.RegisterDataBase("default", "mysql", "root:root@/my_db?charset=utf8", 30)
|
||||
// }
|
||||
//
|
||||
// func main() {
|
||||
// o := orm.NewOrm()
|
||||
// user := User{Name: "slene"}
|
||||
// // insert
|
||||
// id, err := o.Insert(&user)
|
||||
// // update
|
||||
// user.Name = "astaxie"
|
||||
// num, err := o.Update(&user)
|
||||
// // read one
|
||||
// u := User{Id: user.Id}
|
||||
// err = o.Read(&u)
|
||||
// // delete
|
||||
// num, err = o.Delete(&u)
|
||||
// }
|
||||
//
|
||||
// more docs: http://beego.vip/docs/mvc/model/overview.md
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm/clauses/order_clause"
|
||||
"github.com/beego/beego/v2/client/orm/hints"
|
||||
"github.com/beego/beego/v2/core/logs"
|
||||
"github.com/beego/beego/v2/core/utils"
|
||||
)
|
||||
|
||||
// DebugQueries define the debug
|
||||
const (
|
||||
DebugQueries = iota
|
||||
)
|
||||
|
||||
// Define common vars
|
||||
var (
|
||||
Debug = false
|
||||
DebugLog = NewLog(os.Stdout)
|
||||
DefaultRowsLimit = -1
|
||||
DefaultRelsDepth = 2
|
||||
DefaultTimeLoc = time.Local
|
||||
ErrTxDone = errors.New("<TxOrmer.Commit/Rollback> transaction already done")
|
||||
ErrMultiRows = errors.New("<QuerySeter> return multi rows")
|
||||
ErrNoRows = errors.New("<QuerySeter> no row found")
|
||||
ErrStmtClosed = errors.New("<QuerySeter> stmt already closed")
|
||||
ErrArgs = errors.New("<Ormer> args error may be empty")
|
||||
ErrNotImplement = errors.New("have not implement")
|
||||
|
||||
ErrLastInsertIdUnavailable = errors.New("<Ormer> last insert id is unavailable")
|
||||
)
|
||||
|
||||
// Params stores the Params
|
||||
type Params map[string]interface{}
|
||||
|
||||
// ParamsList stores paramslist
|
||||
type ParamsList []interface{}
|
||||
|
||||
type ormBase struct {
|
||||
alias *alias
|
||||
db dbQuerier
|
||||
}
|
||||
|
||||
var (
|
||||
_ DQL = new(ormBase)
|
||||
_ DML = new(ormBase)
|
||||
_ DriverGetter = new(ormBase)
|
||||
)
|
||||
|
||||
// get model info and model reflect value
|
||||
func (*ormBase) getMi(md interface{}) (mi *modelInfo) {
|
||||
val := reflect.ValueOf(md)
|
||||
ind := reflect.Indirect(val)
|
||||
typ := ind.Type()
|
||||
mi = getTypeMi(typ)
|
||||
return
|
||||
}
|
||||
|
||||
// get need ptr model info and model reflect value
|
||||
func (*ormBase) getPtrMiInd(md interface{}) (mi *modelInfo, ind reflect.Value) {
|
||||
val := reflect.ValueOf(md)
|
||||
ind = reflect.Indirect(val)
|
||||
typ := ind.Type()
|
||||
if val.Kind() != reflect.Ptr {
|
||||
panic(fmt.Errorf("<Ormer> cannot use non-ptr model struct `%s`", getFullName(typ)))
|
||||
}
|
||||
mi = getTypeMi(typ)
|
||||
return
|
||||
}
|
||||
|
||||
func getTypeMi(mdTyp reflect.Type) *modelInfo {
|
||||
name := getFullName(mdTyp)
|
||||
if mi, ok := defaultModelCache.getByFullName(name); ok {
|
||||
return mi
|
||||
}
|
||||
panic(fmt.Errorf("<Ormer> table: `%s` not found, make sure it was registered with `RegisterModel()`", name))
|
||||
}
|
||||
|
||||
// get field info from model info by given field name
|
||||
func (*ormBase) getFieldInfo(mi *modelInfo, name string) *fieldInfo {
|
||||
fi, ok := mi.fields.GetByAny(name)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("<Ormer> cannot find field `%s` for model `%s`", name, mi.fullName))
|
||||
}
|
||||
return fi
|
||||
}
|
||||
|
||||
// read data to model
|
||||
func (o *ormBase) Read(md interface{}, cols ...string) error {
|
||||
return o.ReadWithCtx(context.Background(), md, cols...)
|
||||
}
|
||||
|
||||
func (o *ormBase) ReadWithCtx(ctx context.Context, md interface{}, cols ...string) error {
|
||||
mi, ind := o.getPtrMiInd(md)
|
||||
return o.alias.DbBaser.Read(ctx, o.db, mi, ind, o.alias.TZ, cols, false)
|
||||
}
|
||||
|
||||
// read data to model, like Read(), but use "SELECT FOR UPDATE" form
|
||||
func (o *ormBase) ReadForUpdate(md interface{}, cols ...string) error {
|
||||
return o.ReadForUpdateWithCtx(context.Background(), md, cols...)
|
||||
}
|
||||
|
||||
func (o *ormBase) ReadForUpdateWithCtx(ctx context.Context, md interface{}, cols ...string) error {
|
||||
mi, ind := o.getPtrMiInd(md)
|
||||
return o.alias.DbBaser.Read(ctx, o.db, mi, ind, o.alias.TZ, cols, true)
|
||||
}
|
||||
|
||||
// Try to read a row from the database, or insert one if it doesn't exist
|
||||
func (o *ormBase) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) {
|
||||
return o.ReadOrCreateWithCtx(context.Background(), md, col1, cols...)
|
||||
}
|
||||
|
||||
func (o *ormBase) ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 string, cols ...string) (bool, int64, error) {
|
||||
cols = append([]string{col1}, cols...)
|
||||
mi, ind := o.getPtrMiInd(md)
|
||||
err := o.alias.DbBaser.Read(ctx, o.db, mi, ind, o.alias.TZ, cols, false)
|
||||
if err == ErrNoRows {
|
||||
// Create
|
||||
id, err := o.InsertWithCtx(ctx, md)
|
||||
return err == nil, id, err
|
||||
}
|
||||
|
||||
id, vid := int64(0), ind.FieldByIndex(mi.fields.pk.fieldIndex)
|
||||
if mi.fields.pk.fieldType&IsPositiveIntegerField > 0 {
|
||||
id = int64(vid.Uint())
|
||||
} else if mi.fields.pk.rel {
|
||||
return o.ReadOrCreateWithCtx(ctx, vid.Interface(), mi.fields.pk.relModelInfo.fields.pk.name)
|
||||
} else {
|
||||
id = vid.Int()
|
||||
}
|
||||
|
||||
return false, id, err
|
||||
}
|
||||
|
||||
// insert model data to database
|
||||
func (o *ormBase) Insert(md interface{}) (int64, error) {
|
||||
return o.InsertWithCtx(context.Background(), md)
|
||||
}
|
||||
|
||||
func (o *ormBase) InsertWithCtx(ctx context.Context, md interface{}) (int64, error) {
|
||||
mi, ind := o.getPtrMiInd(md)
|
||||
id, err := o.alias.DbBaser.Insert(ctx, o.db, mi, ind, o.alias.TZ)
|
||||
if err != nil {
|
||||
return id, err
|
||||
}
|
||||
|
||||
o.setPk(mi, ind, id)
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// set auto pk field
|
||||
func (*ormBase) setPk(mi *modelInfo, ind reflect.Value, id int64) {
|
||||
if mi.fields.pk.auto {
|
||||
if mi.fields.pk.fieldType&IsPositiveIntegerField > 0 {
|
||||
ind.FieldByIndex(mi.fields.pk.fieldIndex).SetUint(uint64(id))
|
||||
} else {
|
||||
ind.FieldByIndex(mi.fields.pk.fieldIndex).SetInt(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// insert some models to database
|
||||
func (o *ormBase) InsertMulti(bulk int, mds interface{}) (int64, error) {
|
||||
return o.InsertMultiWithCtx(context.Background(), bulk, mds)
|
||||
}
|
||||
|
||||
func (o *ormBase) InsertMultiWithCtx(ctx context.Context, bulk int, mds interface{}) (int64, error) {
|
||||
var cnt int64
|
||||
|
||||
sind := reflect.Indirect(reflect.ValueOf(mds))
|
||||
|
||||
switch sind.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
if sind.Len() == 0 {
|
||||
return cnt, ErrArgs
|
||||
}
|
||||
default:
|
||||
return cnt, ErrArgs
|
||||
}
|
||||
|
||||
if bulk <= 1 {
|
||||
for i := 0; i < sind.Len(); i++ {
|
||||
ind := reflect.Indirect(sind.Index(i))
|
||||
mi := o.getMi(ind.Interface())
|
||||
id, err := o.alias.DbBaser.Insert(ctx, o.db, mi, ind, o.alias.TZ)
|
||||
if err != nil {
|
||||
return cnt, err
|
||||
}
|
||||
|
||||
o.setPk(mi, ind, id)
|
||||
|
||||
cnt++
|
||||
}
|
||||
} else {
|
||||
mi := o.getMi(sind.Index(0).Interface())
|
||||
return o.alias.DbBaser.InsertMulti(ctx, o.db, mi, sind, bulk, o.alias.TZ)
|
||||
}
|
||||
return cnt, nil
|
||||
}
|
||||
|
||||
// InsertOrUpdate data to database
|
||||
func (o *ormBase) InsertOrUpdate(md interface{}, colConflictAndArgs ...string) (int64, error) {
|
||||
return o.InsertOrUpdateWithCtx(context.Background(), md, colConflictAndArgs...)
|
||||
}
|
||||
|
||||
func (o *ormBase) InsertOrUpdateWithCtx(ctx context.Context, md interface{}, colConflitAndArgs ...string) (int64, error) {
|
||||
mi, ind := o.getPtrMiInd(md)
|
||||
id, err := o.alias.DbBaser.InsertOrUpdate(ctx, o.db, mi, ind, o.alias, colConflitAndArgs...)
|
||||
if err != nil {
|
||||
return id, err
|
||||
}
|
||||
|
||||
o.setPk(mi, ind, id)
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// update model to database.
|
||||
// cols set the columns those want to update.
|
||||
func (o *ormBase) Update(md interface{}, cols ...string) (int64, error) {
|
||||
return o.UpdateWithCtx(context.Background(), md, cols...)
|
||||
}
|
||||
|
||||
func (o *ormBase) UpdateWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) {
|
||||
mi, ind := o.getPtrMiInd(md)
|
||||
return o.alias.DbBaser.Update(ctx, o.db, mi, ind, o.alias.TZ, cols)
|
||||
}
|
||||
|
||||
// delete model in database
|
||||
// cols shows the delete conditions values read from. default is pk
|
||||
func (o *ormBase) Delete(md interface{}, cols ...string) (int64, error) {
|
||||
return o.DeleteWithCtx(context.Background(), md, cols...)
|
||||
}
|
||||
|
||||
func (o *ormBase) DeleteWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) {
|
||||
mi, ind := o.getPtrMiInd(md)
|
||||
num, err := o.alias.DbBaser.Delete(ctx, o.db, mi, ind, o.alias.TZ, cols)
|
||||
return num, err
|
||||
}
|
||||
|
||||
// create a models to models queryer
|
||||
func (o *ormBase) QueryM2M(md interface{}, name string) QueryM2Mer {
|
||||
mi, ind := o.getPtrMiInd(md)
|
||||
fi := o.getFieldInfo(mi, name)
|
||||
|
||||
switch {
|
||||
case fi.fieldType == RelManyToMany:
|
||||
case fi.fieldType == RelReverseMany && fi.reverseFieldInfo.mi.isThrough:
|
||||
default:
|
||||
panic(fmt.Errorf("<Ormer.QueryM2M> model `%s` . name `%s` is not a m2m field", fi.name, mi.fullName))
|
||||
}
|
||||
|
||||
return newQueryM2M(md, o, mi, fi, ind)
|
||||
}
|
||||
|
||||
// NOTE: this method is deprecated, context parameter will not take effect.
|
||||
func (o *ormBase) QueryM2MWithCtx(_ context.Context, md interface{}, name string) QueryM2Mer {
|
||||
logs.Warn("QueryM2MWithCtx is DEPRECATED. Use methods with `WithCtx` suffix on QueryM2M as replacement please.")
|
||||
return o.QueryM2M(md, name)
|
||||
}
|
||||
|
||||
// load related models to md model.
|
||||
// args are limit, offset int and order string.
|
||||
//
|
||||
// example:
|
||||
// orm.LoadRelated(post,"Tags")
|
||||
// for _,tag := range post.Tags{...}
|
||||
//
|
||||
// make sure the relation is defined in model struct tags.
|
||||
func (o *ormBase) LoadRelated(md interface{}, name string, args ...utils.KV) (int64, error) {
|
||||
return o.LoadRelatedWithCtx(context.Background(), md, name, args...)
|
||||
}
|
||||
|
||||
func (o *ormBase) LoadRelatedWithCtx(_ context.Context, md interface{}, name string, args ...utils.KV) (int64, error) {
|
||||
_, fi, ind, qs := o.queryRelated(md, name)
|
||||
|
||||
var relDepth int
|
||||
var limit, offset int64
|
||||
var order string
|
||||
|
||||
kvs := utils.NewKVs(args...)
|
||||
kvs.IfContains(hints.KeyRelDepth, func(value interface{}) {
|
||||
if v, ok := value.(bool); ok {
|
||||
if v {
|
||||
relDepth = DefaultRelsDepth
|
||||
}
|
||||
} else if v, ok := value.(int); ok {
|
||||
relDepth = v
|
||||
}
|
||||
}).IfContains(hints.KeyLimit, func(value interface{}) {
|
||||
if v, ok := value.(int64); ok {
|
||||
limit = v
|
||||
}
|
||||
}).IfContains(hints.KeyOffset, func(value interface{}) {
|
||||
if v, ok := value.(int64); ok {
|
||||
offset = v
|
||||
}
|
||||
}).IfContains(hints.KeyOrderBy, func(value interface{}) {
|
||||
if v, ok := value.(string); ok {
|
||||
order = v
|
||||
}
|
||||
})
|
||||
|
||||
switch fi.fieldType {
|
||||
case RelOneToOne, RelForeignKey, RelReverseOne:
|
||||
limit = 1
|
||||
offset = 0
|
||||
}
|
||||
|
||||
qs.limit = limit
|
||||
qs.offset = offset
|
||||
qs.relDepth = relDepth
|
||||
|
||||
if len(order) > 0 {
|
||||
qs.orders = order_clause.ParseOrder(order)
|
||||
}
|
||||
|
||||
find := ind.FieldByIndex(fi.fieldIndex)
|
||||
|
||||
var nums int64
|
||||
var err error
|
||||
switch fi.fieldType {
|
||||
case RelOneToOne, RelForeignKey, RelReverseOne:
|
||||
val := reflect.New(find.Type().Elem())
|
||||
container := val.Interface()
|
||||
err = qs.One(container)
|
||||
if err == nil {
|
||||
find.Set(val)
|
||||
nums = 1
|
||||
}
|
||||
default:
|
||||
nums, err = qs.All(find.Addr().Interface())
|
||||
}
|
||||
|
||||
return nums, err
|
||||
}
|
||||
|
||||
// get QuerySeter for related models to md model
|
||||
func (o *ormBase) queryRelated(md interface{}, name string) (*modelInfo, *fieldInfo, reflect.Value, *querySet) {
|
||||
mi, ind := o.getPtrMiInd(md)
|
||||
fi := o.getFieldInfo(mi, name)
|
||||
|
||||
_, _, exist := getExistPk(mi, ind)
|
||||
if !exist {
|
||||
panic(ErrMissPK)
|
||||
}
|
||||
|
||||
var qs *querySet
|
||||
|
||||
switch fi.fieldType {
|
||||
case RelOneToOne, RelForeignKey, RelManyToMany:
|
||||
if !fi.inModel {
|
||||
break
|
||||
}
|
||||
qs = o.getRelQs(md, mi, fi)
|
||||
case RelReverseOne, RelReverseMany:
|
||||
if !fi.inModel {
|
||||
break
|
||||
}
|
||||
qs = o.getReverseQs(md, mi, fi)
|
||||
}
|
||||
|
||||
if qs == nil {
|
||||
panic(fmt.Errorf("<Ormer> name `%s` for model `%s` is not an available rel/reverse field", md, name))
|
||||
}
|
||||
|
||||
return mi, fi, ind, qs
|
||||
}
|
||||
|
||||
// get reverse relation QuerySeter
|
||||
func (o *ormBase) getReverseQs(md interface{}, mi *modelInfo, fi *fieldInfo) *querySet {
|
||||
switch fi.fieldType {
|
||||
case RelReverseOne, RelReverseMany:
|
||||
default:
|
||||
panic(fmt.Errorf("<Ormer> name `%s` for model `%s` is not an available reverse field", fi.name, mi.fullName))
|
||||
}
|
||||
|
||||
var q *querySet
|
||||
|
||||
if fi.fieldType == RelReverseMany && fi.reverseFieldInfo.mi.isThrough {
|
||||
q = newQuerySet(o, fi.relModelInfo).(*querySet)
|
||||
q.cond = NewCondition().And(fi.reverseFieldInfoM2M.column+ExprSep+fi.reverseFieldInfo.column, md)
|
||||
} else {
|
||||
q = newQuerySet(o, fi.reverseFieldInfo.mi).(*querySet)
|
||||
q.cond = NewCondition().And(fi.reverseFieldInfo.column, md)
|
||||
}
|
||||
|
||||
return q
|
||||
}
|
||||
|
||||
// get relation QuerySeter
|
||||
func (o *ormBase) getRelQs(md interface{}, mi *modelInfo, fi *fieldInfo) *querySet {
|
||||
switch fi.fieldType {
|
||||
case RelOneToOne, RelForeignKey, RelManyToMany:
|
||||
default:
|
||||
panic(fmt.Errorf("<Ormer> name `%s` for model `%s` is not an available rel field", fi.name, mi.fullName))
|
||||
}
|
||||
|
||||
q := newQuerySet(o, fi.relModelInfo).(*querySet)
|
||||
q.cond = NewCondition()
|
||||
|
||||
if fi.fieldType == RelManyToMany {
|
||||
q.cond = q.cond.And(fi.reverseFieldInfoM2M.column+ExprSep+fi.reverseFieldInfo.column, md)
|
||||
} else {
|
||||
q.cond = q.cond.And(fi.reverseFieldInfo.column, md)
|
||||
}
|
||||
|
||||
return q
|
||||
}
|
||||
|
||||
// return a QuerySeter for table operations.
|
||||
// table name can be string or struct.
|
||||
// e.g. QueryTable("user"), QueryTable(&user{}) or QueryTable((*User)(nil)),
|
||||
func (o *ormBase) QueryTable(ptrStructOrTableName interface{}) (qs QuerySeter) {
|
||||
var name string
|
||||
if table, ok := ptrStructOrTableName.(string); ok {
|
||||
name = nameStrategyMap[defaultNameStrategy](table)
|
||||
if mi, ok := defaultModelCache.get(name); ok {
|
||||
qs = newQuerySet(o, mi)
|
||||
}
|
||||
} else {
|
||||
name = getFullName(indirectType(reflect.TypeOf(ptrStructOrTableName)))
|
||||
if mi, ok := defaultModelCache.getByFullName(name); ok {
|
||||
qs = newQuerySet(o, mi)
|
||||
}
|
||||
}
|
||||
if qs == nil {
|
||||
panic(fmt.Errorf("<Ormer.QueryTable> table name: `%s` not exists", name))
|
||||
}
|
||||
return qs
|
||||
}
|
||||
|
||||
// NOTE: this method is deprecated, context parameter will not take effect.
|
||||
func (o *ormBase) QueryTableWithCtx(_ context.Context, ptrStructOrTableName interface{}) (qs QuerySeter) {
|
||||
logs.Warn("QueryTableWithCtx is DEPRECATED. Use methods with `WithCtx` suffix on QuerySeter as replacement please.")
|
||||
return o.QueryTable(ptrStructOrTableName)
|
||||
}
|
||||
|
||||
// return a raw query seter for raw sql string.
|
||||
func (o *ormBase) Raw(query string, args ...interface{}) RawSeter {
|
||||
return o.RawWithCtx(context.Background(), query, args...)
|
||||
}
|
||||
|
||||
func (o *ormBase) RawWithCtx(_ context.Context, query string, args ...interface{}) RawSeter {
|
||||
return newRawSet(o, query, args)
|
||||
}
|
||||
|
||||
// return current using database Driver
|
||||
func (o *ormBase) Driver() Driver {
|
||||
return driver(o.alias.Name)
|
||||
}
|
||||
|
||||
// return sql.DBStats for current database
|
||||
func (o *ormBase) DBStats() *sql.DBStats {
|
||||
if o.alias != nil && o.alias.DB != nil {
|
||||
stats := o.alias.DB.DB.Stats()
|
||||
return &stats
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type orm struct {
|
||||
ormBase
|
||||
}
|
||||
|
||||
var _ Ormer = new(orm)
|
||||
|
||||
func (o *orm) Begin() (TxOrmer, error) {
|
||||
return o.BeginWithCtx(context.Background())
|
||||
}
|
||||
|
||||
func (o *orm) BeginWithCtx(ctx context.Context) (TxOrmer, error) {
|
||||
return o.BeginWithCtxAndOpts(ctx, nil)
|
||||
}
|
||||
|
||||
func (o *orm) BeginWithOpts(opts *sql.TxOptions) (TxOrmer, error) {
|
||||
return o.BeginWithCtxAndOpts(context.Background(), opts)
|
||||
}
|
||||
|
||||
func (o *orm) BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error) {
|
||||
tx, err := o.db.(txer).BeginTx(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_txOrm := &txOrm{
|
||||
ormBase: ormBase{
|
||||
alias: o.alias,
|
||||
db: &TxDB{tx: tx},
|
||||
},
|
||||
}
|
||||
|
||||
if Debug {
|
||||
_txOrm.db = newDbQueryLog(o.alias, _txOrm.db)
|
||||
}
|
||||
|
||||
var taskTxOrm TxOrmer = _txOrm
|
||||
return taskTxOrm, nil
|
||||
}
|
||||
|
||||
func (o *orm) DoTx(task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||
return o.DoTxWithCtx(context.Background(), task)
|
||||
}
|
||||
|
||||
func (o *orm) DoTxWithCtx(ctx context.Context, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||
return o.DoTxWithCtxAndOpts(ctx, nil, task)
|
||||
}
|
||||
|
||||
func (o *orm) DoTxWithOpts(opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||
return o.DoTxWithCtxAndOpts(context.Background(), opts, task)
|
||||
}
|
||||
|
||||
func (o *orm) DoTxWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||
return doTxTemplate(ctx, o, opts, task)
|
||||
}
|
||||
|
||||
func doTxTemplate(ctx context.Context, o TxBeginner, opts *sql.TxOptions,
|
||||
task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||
_txOrm, err := o.BeginWithCtxAndOpts(ctx, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
panicked := true
|
||||
defer func() {
|
||||
if panicked || err != nil {
|
||||
e := _txOrm.Rollback()
|
||||
if e != nil {
|
||||
logs.Error("rollback transaction failed: %v,%v", e, panicked)
|
||||
}
|
||||
} else {
|
||||
e := _txOrm.Commit()
|
||||
if e != nil {
|
||||
logs.Error("commit transaction failed: %v,%v", e, panicked)
|
||||
}
|
||||
}
|
||||
}()
|
||||
taskTxOrm := _txOrm
|
||||
err = task(ctx, taskTxOrm)
|
||||
panicked = false
|
||||
return err
|
||||
}
|
||||
|
||||
type txOrm struct {
|
||||
ormBase
|
||||
}
|
||||
|
||||
var _ TxOrmer = new(txOrm)
|
||||
|
||||
func (t *txOrm) Commit() error {
|
||||
return t.db.(txEnder).Commit()
|
||||
}
|
||||
|
||||
func (t *txOrm) Rollback() error {
|
||||
return t.db.(txEnder).Rollback()
|
||||
}
|
||||
|
||||
func (t *txOrm) RollbackUnlessCommit() error {
|
||||
return t.db.(txEnder).RollbackUnlessCommit()
|
||||
}
|
||||
|
||||
// NewOrm create new orm
|
||||
func NewOrm() Ormer {
|
||||
BootStrap() // execute only once
|
||||
return NewOrmUsingDB(`default`)
|
||||
}
|
||||
|
||||
// NewOrmUsingDB create new orm with the name
|
||||
func NewOrmUsingDB(aliasName string) Ormer {
|
||||
if al, ok := dataBaseCache.get(aliasName); ok {
|
||||
return newDBWithAlias(al)
|
||||
}
|
||||
panic(fmt.Errorf("<Ormer.Using> unknown db alias name `%s`", aliasName))
|
||||
}
|
||||
|
||||
// NewOrmWithDB create a new ormer object with specify *sql.DB for query
|
||||
func NewOrmWithDB(driverName, aliasName string, db *sql.DB, params ...DBOption) (Ormer, error) {
|
||||
al, err := newAliasWithDb(aliasName, driverName, db, params...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newDBWithAlias(al), nil
|
||||
}
|
||||
|
||||
func newDBWithAlias(al *alias) Ormer {
|
||||
o := new(orm)
|
||||
o.alias = al
|
||||
|
||||
if Debug {
|
||||
o.db = newDbQueryLog(al, al.DB)
|
||||
} else {
|
||||
o.db = al.DB
|
||||
}
|
||||
|
||||
if len(globalFilterChains) > 0 {
|
||||
return NewFilterOrmDecorator(o, globalFilterChains...)
|
||||
}
|
||||
return o
|
||||
}
|
@ -1,160 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm/clauses"
|
||||
)
|
||||
|
||||
// ExprSep define the expression separation
|
||||
const (
|
||||
ExprSep = clauses.ExprSep
|
||||
)
|
||||
|
||||
type condValue struct {
|
||||
exprs []string
|
||||
args []interface{}
|
||||
cond *Condition
|
||||
isOr bool
|
||||
isNot bool
|
||||
isCond bool
|
||||
isRaw bool
|
||||
sql string
|
||||
}
|
||||
|
||||
// Condition struct.
|
||||
// work for WHERE conditions.
|
||||
type Condition struct {
|
||||
params []condValue
|
||||
}
|
||||
|
||||
// NewCondition return new condition struct
|
||||
func NewCondition() *Condition {
|
||||
c := &Condition{}
|
||||
return c
|
||||
}
|
||||
|
||||
// Raw add raw sql to condition
|
||||
func (c Condition) Raw(expr string, sql string) *Condition {
|
||||
if len(sql) == 0 {
|
||||
panic(fmt.Errorf("<Condition.Raw> sql cannot empty"))
|
||||
}
|
||||
c.params = append(c.params, condValue{exprs: strings.Split(expr, ExprSep), sql: sql, isRaw: true})
|
||||
return &c
|
||||
}
|
||||
|
||||
// And add expression to condition
|
||||
func (c Condition) And(expr string, args ...interface{}) *Condition {
|
||||
if expr == "" || len(args) == 0 {
|
||||
panic(fmt.Errorf("<Condition.And> args cannot empty"))
|
||||
}
|
||||
c.params = append(c.params, condValue{exprs: strings.Split(expr, ExprSep), args: args})
|
||||
return &c
|
||||
}
|
||||
|
||||
// AndNot add NOT expression to condition
|
||||
func (c Condition) AndNot(expr string, args ...interface{}) *Condition {
|
||||
if expr == "" || len(args) == 0 {
|
||||
panic(fmt.Errorf("<Condition.AndNot> args cannot empty"))
|
||||
}
|
||||
c.params = append(c.params, condValue{exprs: strings.Split(expr, ExprSep), args: args, isNot: true})
|
||||
return &c
|
||||
}
|
||||
|
||||
// AndCond combine a condition to current condition
|
||||
func (c *Condition) AndCond(cond *Condition) *Condition {
|
||||
if c == cond {
|
||||
panic(fmt.Errorf("<Condition.AndCond> cannot use self as sub cond"))
|
||||
}
|
||||
|
||||
c = c.clone()
|
||||
|
||||
if cond != nil {
|
||||
c.params = append(c.params, condValue{cond: cond, isCond: true})
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// AndNotCond combine a AND NOT condition to current condition
|
||||
func (c *Condition) AndNotCond(cond *Condition) *Condition {
|
||||
c = c.clone()
|
||||
if c == cond {
|
||||
panic(fmt.Errorf("<Condition.AndNotCond> cannot use self as sub cond"))
|
||||
}
|
||||
|
||||
if cond != nil {
|
||||
c.params = append(c.params, condValue{cond: cond, isCond: true, isNot: true})
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Or add OR expression to condition
|
||||
func (c Condition) Or(expr string, args ...interface{}) *Condition {
|
||||
if expr == "" || len(args) == 0 {
|
||||
panic(fmt.Errorf("<Condition.Or> args cannot empty"))
|
||||
}
|
||||
c.params = append(c.params, condValue{exprs: strings.Split(expr, ExprSep), args: args, isOr: true})
|
||||
return &c
|
||||
}
|
||||
|
||||
// OrNot add OR NOT expression to condition
|
||||
func (c Condition) OrNot(expr string, args ...interface{}) *Condition {
|
||||
if expr == "" || len(args) == 0 {
|
||||
panic(fmt.Errorf("<Condition.OrNot> args cannot empty"))
|
||||
}
|
||||
c.params = append(c.params, condValue{exprs: strings.Split(expr, ExprSep), args: args, isNot: true, isOr: true})
|
||||
return &c
|
||||
}
|
||||
|
||||
// OrCond combine a OR condition to current condition
|
||||
func (c *Condition) OrCond(cond *Condition) *Condition {
|
||||
c = c.clone()
|
||||
if c == cond {
|
||||
panic(fmt.Errorf("<Condition.OrCond> cannot use self as sub cond"))
|
||||
}
|
||||
if cond != nil {
|
||||
c.params = append(c.params, condValue{cond: cond, isCond: true, isOr: true})
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// OrNotCond combine a OR NOT condition to current condition
|
||||
func (c *Condition) OrNotCond(cond *Condition) *Condition {
|
||||
c = c.clone()
|
||||
if c == cond {
|
||||
panic(fmt.Errorf("<Condition.OrNotCond> cannot use self as sub cond"))
|
||||
}
|
||||
|
||||
if cond != nil {
|
||||
c.params = append(c.params, condValue{cond: cond, isCond: true, isNot: true, isOr: true})
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// IsEmpty check the condition arguments are empty or not.
|
||||
func (c *Condition) IsEmpty() bool {
|
||||
return len(c.params) == 0
|
||||
}
|
||||
|
||||
// clone clone a condition
|
||||
func (c Condition) clone() *Condition {
|
||||
params := make([]condValue, len(c.params))
|
||||
copy(params, c.params)
|
||||
c.params = params
|
||||
return &c
|
||||
}
|
@ -1,228 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Log implement the log.Logger
|
||||
type Log struct {
|
||||
*log.Logger
|
||||
}
|
||||
|
||||
// costomer log func
|
||||
var LogFunc func(query map[string]interface{})
|
||||
|
||||
// NewLog set io.Writer to create a Logger.
|
||||
func NewLog(out io.Writer) *Log {
|
||||
d := new(Log)
|
||||
d.Logger = log.New(out, "[ORM]", log.LstdFlags)
|
||||
return d
|
||||
}
|
||||
|
||||
func debugLogQueies(alias *alias, operaton, query string, t time.Time, err error, args ...interface{}) {
|
||||
logMap := make(map[string]interface{})
|
||||
sub := time.Since(t) / 1e5
|
||||
elsp := float64(int(sub)) / 10.0
|
||||
logMap["cost_time"] = elsp
|
||||
flag := " OK"
|
||||
if err != nil {
|
||||
flag = "FAIL"
|
||||
}
|
||||
logMap["flag"] = flag
|
||||
con := fmt.Sprintf(" -[Queries/%s] - [%s / %11s / %7.1fms] - [%s]", alias.Name, flag, operaton, elsp, query)
|
||||
cons := make([]string, 0, len(args))
|
||||
for _, arg := range args {
|
||||
cons = append(cons, fmt.Sprintf("%v", arg))
|
||||
}
|
||||
if len(cons) > 0 {
|
||||
con += fmt.Sprintf(" - `%s`", strings.Join(cons, "`, `"))
|
||||
}
|
||||
if err != nil {
|
||||
con += " - " + err.Error()
|
||||
}
|
||||
logMap["sql"] = fmt.Sprintf("%s-`%s`", query, strings.Join(cons, "`, `"))
|
||||
if LogFunc != nil {
|
||||
LogFunc(logMap)
|
||||
}
|
||||
DebugLog.Println(con)
|
||||
}
|
||||
|
||||
// statement query logger struct.
|
||||
// if dev mode, use stmtQueryLog, or use stmtQuerier.
|
||||
type stmtQueryLog struct {
|
||||
alias *alias
|
||||
query string
|
||||
stmt stmtQuerier
|
||||
}
|
||||
|
||||
var _ stmtQuerier = new(stmtQueryLog)
|
||||
|
||||
func (d *stmtQueryLog) Close() error {
|
||||
a := time.Now()
|
||||
err := d.stmt.Close()
|
||||
debugLogQueies(d.alias, "st.Close", d.query, a, err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *stmtQueryLog) Exec(args ...interface{}) (sql.Result, error) {
|
||||
return d.ExecContext(context.Background(), args...)
|
||||
}
|
||||
|
||||
func (d *stmtQueryLog) ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error) {
|
||||
a := time.Now()
|
||||
res, err := d.stmt.ExecContext(ctx, args...)
|
||||
debugLogQueies(d.alias, "st.Exec", d.query, a, err, args...)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (d *stmtQueryLog) Query(args ...interface{}) (*sql.Rows, error) {
|
||||
return d.QueryContext(context.Background(), args...)
|
||||
}
|
||||
|
||||
func (d *stmtQueryLog) QueryContext(ctx context.Context, args ...interface{}) (*sql.Rows, error) {
|
||||
a := time.Now()
|
||||
res, err := d.stmt.QueryContext(ctx, args...)
|
||||
debugLogQueies(d.alias, "st.Query", d.query, a, err, args...)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (d *stmtQueryLog) QueryRow(args ...interface{}) *sql.Row {
|
||||
return d.QueryRowContext(context.Background(), args...)
|
||||
}
|
||||
|
||||
func (d *stmtQueryLog) QueryRowContext(ctx context.Context, args ...interface{}) *sql.Row {
|
||||
a := time.Now()
|
||||
res := d.stmt.QueryRow(args...)
|
||||
debugLogQueies(d.alias, "st.QueryRow", d.query, a, nil, args...)
|
||||
return res
|
||||
}
|
||||
|
||||
func newStmtQueryLog(alias *alias, stmt stmtQuerier, query string) stmtQuerier {
|
||||
d := new(stmtQueryLog)
|
||||
d.stmt = stmt
|
||||
d.alias = alias
|
||||
d.query = query
|
||||
return d
|
||||
}
|
||||
|
||||
// database query logger struct.
|
||||
// if dev mode, use dbQueryLog, or use dbQuerier.
|
||||
type dbQueryLog struct {
|
||||
alias *alias
|
||||
db dbQuerier
|
||||
tx txer
|
||||
txe txEnder
|
||||
}
|
||||
|
||||
var (
|
||||
_ dbQuerier = new(dbQueryLog)
|
||||
_ txer = new(dbQueryLog)
|
||||
_ txEnder = new(dbQueryLog)
|
||||
)
|
||||
|
||||
func (d *dbQueryLog) Prepare(query string) (*sql.Stmt, error) {
|
||||
return d.PrepareContext(context.Background(), query)
|
||||
}
|
||||
|
||||
func (d *dbQueryLog) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) {
|
||||
a := time.Now()
|
||||
stmt, err := d.db.PrepareContext(ctx, query)
|
||||
debugLogQueies(d.alias, "db.Prepare", query, a, err)
|
||||
return stmt, err
|
||||
}
|
||||
|
||||
func (d *dbQueryLog) Exec(query string, args ...interface{}) (sql.Result, error) {
|
||||
return d.ExecContext(context.Background(), query, args...)
|
||||
}
|
||||
|
||||
func (d *dbQueryLog) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
|
||||
a := time.Now()
|
||||
res, err := d.db.ExecContext(ctx, query, args...)
|
||||
debugLogQueies(d.alias, "db.Exec", query, a, err, args...)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (d *dbQueryLog) Query(query string, args ...interface{}) (*sql.Rows, error) {
|
||||
return d.QueryContext(context.Background(), query, args...)
|
||||
}
|
||||
|
||||
func (d *dbQueryLog) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
|
||||
a := time.Now()
|
||||
res, err := d.db.QueryContext(ctx, query, args...)
|
||||
debugLogQueies(d.alias, "db.Query", query, a, err, args...)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (d *dbQueryLog) QueryRow(query string, args ...interface{}) *sql.Row {
|
||||
return d.QueryRowContext(context.Background(), query, args...)
|
||||
}
|
||||
|
||||
func (d *dbQueryLog) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
|
||||
a := time.Now()
|
||||
res := d.db.QueryRowContext(ctx, query, args...)
|
||||
debugLogQueies(d.alias, "db.QueryRow", query, a, nil, args...)
|
||||
return res
|
||||
}
|
||||
|
||||
func (d *dbQueryLog) Begin() (*sql.Tx, error) {
|
||||
return d.BeginTx(context.Background(), nil)
|
||||
}
|
||||
|
||||
func (d *dbQueryLog) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) {
|
||||
a := time.Now()
|
||||
tx, err := d.db.(txer).BeginTx(ctx, opts)
|
||||
debugLogQueies(d.alias, "db.BeginTx", "START TRANSACTION", a, err)
|
||||
return tx, err
|
||||
}
|
||||
|
||||
func (d *dbQueryLog) Commit() error {
|
||||
a := time.Now()
|
||||
err := d.db.(txEnder).Commit()
|
||||
debugLogQueies(d.alias, "tx.Commit", "COMMIT", a, err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *dbQueryLog) Rollback() error {
|
||||
a := time.Now()
|
||||
err := d.db.(txEnder).Rollback()
|
||||
debugLogQueies(d.alias, "tx.Rollback", "ROLLBACK", a, err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *dbQueryLog) RollbackUnlessCommit() error {
|
||||
a := time.Now()
|
||||
err := d.db.(txEnder).RollbackUnlessCommit()
|
||||
debugLogQueies(d.alias, "tx.RollbackUnlessCommit", "ROLLBACK UNLESS COMMIT", a, err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *dbQueryLog) SetDB(db dbQuerier) {
|
||||
d.db = db
|
||||
}
|
||||
|
||||
func newDbQueryLog(alias *alias, db dbQuerier) dbQuerier {
|
||||
d := new(dbQueryLog)
|
||||
d.alias = alias
|
||||
d.db = db
|
||||
return d
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// an insert queryer struct
|
||||
type insertSet struct {
|
||||
mi *modelInfo
|
||||
orm *ormBase
|
||||
stmt stmtQuerier
|
||||
closed bool
|
||||
}
|
||||
|
||||
var _ Inserter = new(insertSet)
|
||||
|
||||
// insert model ignore it's registered or not.
|
||||
func (o *insertSet) Insert(md interface{}) (int64, error) {
|
||||
return o.InsertWithCtx(context.Background(), md)
|
||||
}
|
||||
|
||||
func (o *insertSet) InsertWithCtx(ctx context.Context, md interface{}) (int64, error) {
|
||||
if o.closed {
|
||||
return 0, ErrStmtClosed
|
||||
}
|
||||
val := reflect.ValueOf(md)
|
||||
ind := reflect.Indirect(val)
|
||||
typ := ind.Type()
|
||||
name := getFullName(typ)
|
||||
if val.Kind() != reflect.Ptr {
|
||||
panic(fmt.Errorf("<Inserter.Insert> cannot use non-ptr model struct `%s`", name))
|
||||
}
|
||||
if name != o.mi.fullName {
|
||||
panic(fmt.Errorf("<Inserter.Insert> need model `%s` but found `%s`", o.mi.fullName, name))
|
||||
}
|
||||
id, err := o.orm.alias.DbBaser.InsertStmt(ctx, o.stmt, o.mi, ind, o.orm.alias.TZ)
|
||||
if err != nil {
|
||||
return id, err
|
||||
}
|
||||
if id > 0 {
|
||||
if o.mi.fields.pk.auto {
|
||||
if o.mi.fields.pk.fieldType&IsPositiveIntegerField > 0 {
|
||||
ind.FieldByIndex(o.mi.fields.pk.fieldIndex).SetUint(uint64(id))
|
||||
} else {
|
||||
ind.FieldByIndex(o.mi.fields.pk.fieldIndex).SetInt(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// close insert queryer statement
|
||||
func (o *insertSet) Close() error {
|
||||
if o.closed {
|
||||
return ErrStmtClosed
|
||||
}
|
||||
o.closed = true
|
||||
return o.stmt.Close()
|
||||
}
|
||||
|
||||
// create new insert queryer.
|
||||
func newInsertSet(ctx context.Context, orm *ormBase, mi *modelInfo) (Inserter, error) {
|
||||
bi := new(insertSet)
|
||||
bi.orm = orm
|
||||
bi.mi = mi
|
||||
st, query, err := orm.alias.DbBaser.PrepareInsert(ctx, orm.db, mi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if Debug {
|
||||
bi.stmt = newStmtQueryLog(orm.alias, st, query)
|
||||
} else {
|
||||
bi.stmt = st
|
||||
}
|
||||
return bi, nil
|
||||
}
|
@ -1,163 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// model to model struct
|
||||
type queryM2M struct {
|
||||
md interface{}
|
||||
mi *modelInfo
|
||||
fi *fieldInfo
|
||||
qs *querySet
|
||||
ind reflect.Value
|
||||
}
|
||||
|
||||
// add models to origin models when creating queryM2M.
|
||||
// example:
|
||||
// m2m := orm.QueryM2M(post,"Tag")
|
||||
// m2m.Add(&Tag1{},&Tag2{})
|
||||
// for _,tag := range post.Tags{}
|
||||
//
|
||||
// make sure the relation is defined in post model struct tag.
|
||||
func (o *queryM2M) Add(mds ...interface{}) (int64, error) {
|
||||
return o.AddWithCtx(context.Background(), mds...)
|
||||
}
|
||||
|
||||
func (o *queryM2M) AddWithCtx(ctx context.Context, mds ...interface{}) (int64, error) {
|
||||
fi := o.fi
|
||||
mi := fi.relThroughModelInfo
|
||||
mfi := fi.reverseFieldInfo
|
||||
rfi := fi.reverseFieldInfoTwo
|
||||
|
||||
orm := o.qs.orm
|
||||
dbase := orm.alias.DbBaser
|
||||
|
||||
var models []interface{}
|
||||
var otherValues []interface{}
|
||||
var otherNames []string
|
||||
|
||||
for _, colname := range mi.fields.dbcols {
|
||||
if colname != mfi.column && colname != rfi.column && colname != fi.mi.fields.pk.column &&
|
||||
mi.fields.columns[colname] != mi.fields.pk {
|
||||
otherNames = append(otherNames, colname)
|
||||
}
|
||||
}
|
||||
for i, md := range mds {
|
||||
if reflect.Indirect(reflect.ValueOf(md)).Kind() != reflect.Struct && i > 0 {
|
||||
otherValues = append(otherValues, md)
|
||||
mds = append(mds[:i], mds[i+1:]...)
|
||||
}
|
||||
}
|
||||
for _, md := range mds {
|
||||
val := reflect.ValueOf(md)
|
||||
if val.Kind() == reflect.Slice || val.Kind() == reflect.Array {
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
v := val.Index(i)
|
||||
if v.CanInterface() {
|
||||
models = append(models, v.Interface())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
models = append(models, md)
|
||||
}
|
||||
}
|
||||
|
||||
_, v1, exist := getExistPk(o.mi, o.ind)
|
||||
if !exist {
|
||||
panic(ErrMissPK)
|
||||
}
|
||||
|
||||
names := []string{mfi.column, rfi.column}
|
||||
|
||||
values := make([]interface{}, 0, len(models)*2)
|
||||
for _, md := range models {
|
||||
|
||||
ind := reflect.Indirect(reflect.ValueOf(md))
|
||||
var v2 interface{}
|
||||
if ind.Kind() != reflect.Struct {
|
||||
v2 = ind.Interface()
|
||||
} else {
|
||||
_, v2, exist = getExistPk(fi.relModelInfo, ind)
|
||||
if !exist {
|
||||
panic(ErrMissPK)
|
||||
}
|
||||
}
|
||||
values = append(values, v1, v2)
|
||||
|
||||
}
|
||||
names = append(names, otherNames...)
|
||||
values = append(values, otherValues...)
|
||||
return dbase.InsertValue(ctx, orm.db, mi, true, names, values)
|
||||
}
|
||||
|
||||
// remove models following the origin model relationship
|
||||
func (o *queryM2M) Remove(mds ...interface{}) (int64, error) {
|
||||
return o.RemoveWithCtx(context.Background(), mds...)
|
||||
}
|
||||
|
||||
func (o *queryM2M) RemoveWithCtx(ctx context.Context, mds ...interface{}) (int64, error) {
|
||||
fi := o.fi
|
||||
qs := o.qs.Filter(fi.reverseFieldInfo.name, o.md)
|
||||
|
||||
return qs.Filter(fi.reverseFieldInfoTwo.name+ExprSep+"in", mds).Delete()
|
||||
}
|
||||
|
||||
// check model is existed in relationship of origin model
|
||||
func (o *queryM2M) Exist(md interface{}) bool {
|
||||
return o.ExistWithCtx(context.Background(), md)
|
||||
}
|
||||
|
||||
func (o *queryM2M) ExistWithCtx(ctx context.Context, md interface{}) bool {
|
||||
fi := o.fi
|
||||
return o.qs.Filter(fi.reverseFieldInfo.name, o.md).
|
||||
Filter(fi.reverseFieldInfoTwo.name, md).ExistWithCtx(ctx)
|
||||
}
|
||||
|
||||
// clean all models in related of origin model
|
||||
func (o *queryM2M) Clear() (int64, error) {
|
||||
return o.ClearWithCtx(context.Background())
|
||||
}
|
||||
|
||||
func (o *queryM2M) ClearWithCtx(ctx context.Context) (int64, error) {
|
||||
fi := o.fi
|
||||
return o.qs.Filter(fi.reverseFieldInfo.name, o.md).DeleteWithCtx(ctx)
|
||||
}
|
||||
|
||||
// count all related models of origin model
|
||||
func (o *queryM2M) Count() (int64, error) {
|
||||
return o.CountWithCtx(context.Background())
|
||||
}
|
||||
|
||||
func (o *queryM2M) CountWithCtx(ctx context.Context) (int64, error) {
|
||||
fi := o.fi
|
||||
return o.qs.Filter(fi.reverseFieldInfo.name, o.md).CountWithCtx(ctx)
|
||||
}
|
||||
|
||||
var _ QueryM2Mer = new(queryM2M)
|
||||
|
||||
// create new M2M queryer.
|
||||
func newQueryM2M(md interface{}, o *ormBase, mi *modelInfo, fi *fieldInfo, ind reflect.Value) QueryM2Mer {
|
||||
qm2m := new(queryM2M)
|
||||
qm2m.md = md
|
||||
qm2m.mi = mi
|
||||
qm2m.fi = fi
|
||||
qm2m.ind = ind
|
||||
qm2m.qs = newQuerySet(o, fi.relThroughModelInfo).(*querySet)
|
||||
return qm2m
|
||||
}
|
@ -1,376 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm/clauses/order_clause"
|
||||
"github.com/beego/beego/v2/client/orm/hints"
|
||||
)
|
||||
|
||||
type colValue struct {
|
||||
value int64
|
||||
opt operator
|
||||
}
|
||||
|
||||
type operator int
|
||||
|
||||
// define Col operations
|
||||
const (
|
||||
ColAdd operator = iota
|
||||
ColMinus
|
||||
ColMultiply
|
||||
ColExcept
|
||||
ColBitAnd
|
||||
ColBitRShift
|
||||
ColBitLShift
|
||||
ColBitXOR
|
||||
ColBitOr
|
||||
)
|
||||
|
||||
// ColValue do the field raw changes. e.g Nums = Nums + 10. usage:
|
||||
// Params{
|
||||
// "Nums": ColValue(Col_Add, 10),
|
||||
// }
|
||||
func ColValue(opt operator, value interface{}) interface{} {
|
||||
switch opt {
|
||||
case ColAdd, ColMinus, ColMultiply, ColExcept, ColBitAnd, ColBitRShift,
|
||||
ColBitLShift, ColBitXOR, ColBitOr:
|
||||
default:
|
||||
panic(fmt.Errorf("orm.ColValue wrong operator"))
|
||||
}
|
||||
v, err := StrTo(ToStr(value)).Int64()
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("orm.ColValue doesn't support non string/numeric type, %s", err))
|
||||
}
|
||||
var val colValue
|
||||
val.value = v
|
||||
val.opt = opt
|
||||
return val
|
||||
}
|
||||
|
||||
// real query struct
|
||||
type querySet struct {
|
||||
mi *modelInfo
|
||||
cond *Condition
|
||||
related []string
|
||||
relDepth int
|
||||
limit int64
|
||||
offset int64
|
||||
groups []string
|
||||
orders []*order_clause.Order
|
||||
distinct bool
|
||||
forUpdate bool
|
||||
useIndex int
|
||||
indexes []string
|
||||
orm *ormBase
|
||||
aggregate string
|
||||
}
|
||||
|
||||
var _ QuerySeter = new(querySet)
|
||||
|
||||
// add condition expression to QuerySeter.
|
||||
func (o querySet) Filter(expr string, args ...interface{}) QuerySeter {
|
||||
if o.cond == nil {
|
||||
o.cond = NewCondition()
|
||||
}
|
||||
o.cond = o.cond.And(expr, args...)
|
||||
return &o
|
||||
}
|
||||
|
||||
// add raw sql to querySeter.
|
||||
func (o querySet) FilterRaw(expr string, sql string) QuerySeter {
|
||||
if o.cond == nil {
|
||||
o.cond = NewCondition()
|
||||
}
|
||||
o.cond = o.cond.Raw(expr, sql)
|
||||
return &o
|
||||
}
|
||||
|
||||
// add NOT condition to querySeter.
|
||||
func (o querySet) Exclude(expr string, args ...interface{}) QuerySeter {
|
||||
if o.cond == nil {
|
||||
o.cond = NewCondition()
|
||||
}
|
||||
o.cond = o.cond.AndNot(expr, args...)
|
||||
return &o
|
||||
}
|
||||
|
||||
// set offset number
|
||||
func (o *querySet) setOffset(num interface{}) {
|
||||
o.offset = ToInt64(num)
|
||||
}
|
||||
|
||||
// add LIMIT value.
|
||||
// args[0] means offset, e.g. LIMIT num,offset.
|
||||
func (o querySet) Limit(limit interface{}, args ...interface{}) QuerySeter {
|
||||
o.limit = ToInt64(limit)
|
||||
if len(args) > 0 {
|
||||
o.setOffset(args[0])
|
||||
}
|
||||
return &o
|
||||
}
|
||||
|
||||
// add OFFSET value
|
||||
func (o querySet) Offset(offset interface{}) QuerySeter {
|
||||
o.setOffset(offset)
|
||||
return &o
|
||||
}
|
||||
|
||||
// add GROUP expression
|
||||
func (o querySet) GroupBy(exprs ...string) QuerySeter {
|
||||
o.groups = exprs
|
||||
return &o
|
||||
}
|
||||
|
||||
// add ORDER expression.
|
||||
// "column" means ASC, "-column" means DESC.
|
||||
func (o querySet) OrderBy(expressions ...string) QuerySeter {
|
||||
if len(expressions) <= 0 {
|
||||
return &o
|
||||
}
|
||||
o.orders = order_clause.ParseOrder(expressions...)
|
||||
return &o
|
||||
}
|
||||
|
||||
// add ORDER expression.
|
||||
func (o querySet) OrderClauses(orders ...*order_clause.Order) QuerySeter {
|
||||
if len(orders) <= 0 {
|
||||
return &o
|
||||
}
|
||||
o.orders = orders
|
||||
return &o
|
||||
}
|
||||
|
||||
// add DISTINCT to SELECT
|
||||
func (o querySet) Distinct() QuerySeter {
|
||||
o.distinct = true
|
||||
return &o
|
||||
}
|
||||
|
||||
// add FOR UPDATE to SELECT
|
||||
func (o querySet) ForUpdate() QuerySeter {
|
||||
o.forUpdate = true
|
||||
return &o
|
||||
}
|
||||
|
||||
// ForceIndex force index for query
|
||||
func (o querySet) ForceIndex(indexes ...string) QuerySeter {
|
||||
o.useIndex = hints.KeyForceIndex
|
||||
o.indexes = indexes
|
||||
return &o
|
||||
}
|
||||
|
||||
// UseIndex use index for query
|
||||
func (o querySet) UseIndex(indexes ...string) QuerySeter {
|
||||
o.useIndex = hints.KeyUseIndex
|
||||
o.indexes = indexes
|
||||
return &o
|
||||
}
|
||||
|
||||
// IgnoreIndex ignore index for query
|
||||
func (o querySet) IgnoreIndex(indexes ...string) QuerySeter {
|
||||
o.useIndex = hints.KeyIgnoreIndex
|
||||
o.indexes = indexes
|
||||
return &o
|
||||
}
|
||||
|
||||
// set relation model to query together.
|
||||
// it will query relation models and assign to parent model.
|
||||
func (o querySet) RelatedSel(params ...interface{}) QuerySeter {
|
||||
if len(params) == 0 {
|
||||
o.relDepth = DefaultRelsDepth
|
||||
} else {
|
||||
for _, p := range params {
|
||||
switch val := p.(type) {
|
||||
case string:
|
||||
o.related = append(o.related, val)
|
||||
case int:
|
||||
o.relDepth = val
|
||||
default:
|
||||
panic(fmt.Errorf("<QuerySeter.RelatedSel> wrong param kind: %v", val))
|
||||
}
|
||||
}
|
||||
}
|
||||
return &o
|
||||
}
|
||||
|
||||
// set condition to QuerySeter.
|
||||
func (o querySet) SetCond(cond *Condition) QuerySeter {
|
||||
o.cond = cond
|
||||
return &o
|
||||
}
|
||||
|
||||
// get condition from QuerySeter
|
||||
func (o querySet) GetCond() *Condition {
|
||||
return o.cond
|
||||
}
|
||||
|
||||
// return QuerySeter execution result number
|
||||
func (o *querySet) Count() (int64, error) {
|
||||
return o.CountWithCtx(context.Background())
|
||||
}
|
||||
|
||||
func (o *querySet) CountWithCtx(ctx context.Context) (int64, error) {
|
||||
return o.orm.alias.DbBaser.Count(ctx, o.orm.db, o, o.mi, o.cond, o.orm.alias.TZ)
|
||||
}
|
||||
|
||||
// check result empty or not after QuerySeter executed
|
||||
func (o *querySet) Exist() bool {
|
||||
return o.ExistWithCtx(context.Background())
|
||||
}
|
||||
|
||||
func (o *querySet) ExistWithCtx(ctx context.Context) bool {
|
||||
cnt, _ := o.orm.alias.DbBaser.Count(ctx, o.orm.db, o, o.mi, o.cond, o.orm.alias.TZ)
|
||||
return cnt > 0
|
||||
}
|
||||
|
||||
// execute update with parameters
|
||||
func (o *querySet) Update(values Params) (int64, error) {
|
||||
return o.UpdateWithCtx(context.Background(), values)
|
||||
}
|
||||
|
||||
func (o *querySet) UpdateWithCtx(ctx context.Context, values Params) (int64, error) {
|
||||
return o.orm.alias.DbBaser.UpdateBatch(ctx, o.orm.db, o, o.mi, o.cond, values, o.orm.alias.TZ)
|
||||
}
|
||||
|
||||
// execute delete
|
||||
func (o *querySet) Delete() (int64, error) {
|
||||
return o.DeleteWithCtx(context.Background())
|
||||
}
|
||||
|
||||
func (o *querySet) DeleteWithCtx(ctx context.Context) (int64, error) {
|
||||
return o.orm.alias.DbBaser.DeleteBatch(ctx, o.orm.db, o, o.mi, o.cond, o.orm.alias.TZ)
|
||||
}
|
||||
|
||||
// return a insert queryer.
|
||||
// it can be used in times.
|
||||
// example:
|
||||
// i,err := sq.PrepareInsert()
|
||||
// i.Add(&user1{},&user2{})
|
||||
func (o *querySet) PrepareInsert() (Inserter, error) {
|
||||
return o.PrepareInsertWithCtx(context.Background())
|
||||
}
|
||||
|
||||
func (o *querySet) PrepareInsertWithCtx(ctx context.Context) (Inserter, error) {
|
||||
return newInsertSet(ctx, o.orm, o.mi)
|
||||
}
|
||||
|
||||
// query all data and map to containers.
|
||||
// cols means the columns when querying.
|
||||
func (o *querySet) All(container interface{}, cols ...string) (int64, error) {
|
||||
return o.AllWithCtx(context.Background(), container, cols...)
|
||||
}
|
||||
|
||||
func (o *querySet) AllWithCtx(ctx context.Context, container interface{}, cols ...string) (int64, error) {
|
||||
return o.orm.alias.DbBaser.ReadBatch(ctx, o.orm.db, o, o.mi, o.cond, container, o.orm.alias.TZ, cols)
|
||||
}
|
||||
|
||||
// query one row data and map to containers.
|
||||
// cols means the columns when querying.
|
||||
func (o *querySet) One(container interface{}, cols ...string) error {
|
||||
return o.OneWithCtx(context.Background(), container, cols...)
|
||||
}
|
||||
|
||||
func (o *querySet) OneWithCtx(ctx context.Context, container interface{}, cols ...string) error {
|
||||
o.limit = 1
|
||||
num, err := o.orm.alias.DbBaser.ReadBatch(ctx, o.orm.db, o, o.mi, o.cond, container, o.orm.alias.TZ, cols)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if num == 0 {
|
||||
return ErrNoRows
|
||||
}
|
||||
|
||||
if num > 1 {
|
||||
return ErrMultiRows
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// query all data and map to []map[string]interface.
|
||||
// expres means condition expression.
|
||||
// it converts data to []map[column]value.
|
||||
func (o *querySet) Values(results *[]Params, exprs ...string) (int64, error) {
|
||||
return o.ValuesWithCtx(context.Background(), results, exprs...)
|
||||
}
|
||||
|
||||
func (o *querySet) ValuesWithCtx(ctx context.Context, results *[]Params, exprs ...string) (int64, error) {
|
||||
return o.orm.alias.DbBaser.ReadValues(ctx, o.orm.db, o, o.mi, o.cond, exprs, results, o.orm.alias.TZ)
|
||||
}
|
||||
|
||||
// query all data and map to [][]interface
|
||||
// it converts data to [][column_index]value
|
||||
func (o *querySet) ValuesList(results *[]ParamsList, exprs ...string) (int64, error) {
|
||||
return o.ValuesListWithCtx(context.Background(), results, exprs...)
|
||||
}
|
||||
|
||||
func (o *querySet) ValuesListWithCtx(ctx context.Context, results *[]ParamsList, exprs ...string) (int64, error) {
|
||||
return o.orm.alias.DbBaser.ReadValues(ctx, o.orm.db, o, o.mi, o.cond, exprs, results, o.orm.alias.TZ)
|
||||
}
|
||||
|
||||
// query all data and map to []interface.
|
||||
// it's designed for one row record set, auto change to []value, not [][column]value.
|
||||
func (o *querySet) ValuesFlat(result *ParamsList, expr string) (int64, error) {
|
||||
return o.ValuesFlatWithCtx(context.Background(), result, expr)
|
||||
}
|
||||
|
||||
func (o *querySet) ValuesFlatWithCtx(ctx context.Context, result *ParamsList, expr string) (int64, error) {
|
||||
return o.orm.alias.DbBaser.ReadValues(ctx, o.orm.db, o, o.mi, o.cond, []string{expr}, result, o.orm.alias.TZ)
|
||||
}
|
||||
|
||||
// query all rows into map[string]interface with specify key and value column name.
|
||||
// keyCol = "name", valueCol = "value"
|
||||
// table data
|
||||
// name | value
|
||||
// total | 100
|
||||
// found | 200
|
||||
// to map[string]interface{}{
|
||||
// "total": 100,
|
||||
// "found": 200,
|
||||
// }
|
||||
func (o *querySet) RowsToMap(result *Params, keyCol, valueCol string) (int64, error) {
|
||||
panic(ErrNotImplement)
|
||||
}
|
||||
|
||||
// query all rows into struct with specify key and value column name.
|
||||
// keyCol = "name", valueCol = "value"
|
||||
// table data
|
||||
// name | value
|
||||
// total | 100
|
||||
// found | 200
|
||||
// to struct {
|
||||
// Total int
|
||||
// Found int
|
||||
// }
|
||||
func (o *querySet) RowsToStruct(ptrStruct interface{}, keyCol, valueCol string) (int64, error) {
|
||||
panic(ErrNotImplement)
|
||||
}
|
||||
|
||||
// create new QuerySeter.
|
||||
func newQuerySet(orm *ormBase, mi *modelInfo) QuerySeter {
|
||||
o := new(querySet)
|
||||
o.mi = mi
|
||||
o.orm = orm
|
||||
return o
|
||||
}
|
||||
|
||||
// aggregate func
|
||||
func (o querySet) Aggregate(s string) QuerySeter {
|
||||
o.aggregate = s
|
||||
return &o
|
||||
}
|
@ -1,910 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// raw sql string prepared statement
|
||||
type rawPrepare struct {
|
||||
rs *rawSet
|
||||
stmt stmtQuerier
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (o *rawPrepare) Exec(args ...interface{}) (sql.Result, error) {
|
||||
if o.closed {
|
||||
return nil, ErrStmtClosed
|
||||
}
|
||||
flatParams := getFlatParams(nil, args, o.rs.orm.alias.TZ)
|
||||
return o.stmt.Exec(flatParams...)
|
||||
}
|
||||
|
||||
func (o *rawPrepare) Close() error {
|
||||
o.closed = true
|
||||
return o.stmt.Close()
|
||||
}
|
||||
|
||||
func newRawPreparer(rs *rawSet) (RawPreparer, error) {
|
||||
o := new(rawPrepare)
|
||||
o.rs = rs
|
||||
|
||||
query := rs.query
|
||||
rs.orm.alias.DbBaser.ReplaceMarks(&query)
|
||||
|
||||
st, err := rs.orm.db.Prepare(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if Debug {
|
||||
o.stmt = newStmtQueryLog(rs.orm.alias, st, query)
|
||||
} else {
|
||||
o.stmt = st
|
||||
}
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// raw query seter
|
||||
type rawSet struct {
|
||||
query string
|
||||
args []interface{}
|
||||
orm *ormBase
|
||||
}
|
||||
|
||||
var _ RawSeter = new(rawSet)
|
||||
|
||||
// set args for every query
|
||||
func (o rawSet) SetArgs(args ...interface{}) RawSeter {
|
||||
o.args = args
|
||||
return &o
|
||||
}
|
||||
|
||||
// execute raw sql and return sql.Result
|
||||
func (o *rawSet) Exec() (sql.Result, error) {
|
||||
query := o.query
|
||||
o.orm.alias.DbBaser.ReplaceMarks(&query)
|
||||
|
||||
args := getFlatParams(nil, o.args, o.orm.alias.TZ)
|
||||
return o.orm.db.Exec(query, args...)
|
||||
}
|
||||
|
||||
// set field value to row container
|
||||
func (o *rawSet) setFieldValue(ind reflect.Value, value interface{}) {
|
||||
switch ind.Kind() {
|
||||
case reflect.Bool:
|
||||
if value == nil {
|
||||
ind.SetBool(false)
|
||||
} else if v, ok := value.(bool); ok {
|
||||
ind.SetBool(v)
|
||||
} else {
|
||||
v, _ := StrTo(ToStr(value)).Bool()
|
||||
ind.SetBool(v)
|
||||
}
|
||||
|
||||
case reflect.String:
|
||||
if value == nil {
|
||||
ind.SetString("")
|
||||
} else {
|
||||
ind.SetString(ToStr(value))
|
||||
}
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if value == nil {
|
||||
ind.SetInt(0)
|
||||
} else {
|
||||
val := reflect.ValueOf(value)
|
||||
switch val.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
ind.SetInt(val.Int())
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
ind.SetInt(int64(val.Uint()))
|
||||
default:
|
||||
v, _ := StrTo(ToStr(value)).Int64()
|
||||
ind.SetInt(v)
|
||||
}
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
if value == nil {
|
||||
ind.SetUint(0)
|
||||
} else {
|
||||
val := reflect.ValueOf(value)
|
||||
switch val.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
ind.SetUint(uint64(val.Int()))
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
ind.SetUint(val.Uint())
|
||||
default:
|
||||
v, _ := StrTo(ToStr(value)).Uint64()
|
||||
ind.SetUint(v)
|
||||
}
|
||||
}
|
||||
case reflect.Float64, reflect.Float32:
|
||||
if value == nil {
|
||||
ind.SetFloat(0)
|
||||
} else {
|
||||
val := reflect.ValueOf(value)
|
||||
switch val.Kind() {
|
||||
case reflect.Float64:
|
||||
ind.SetFloat(val.Float())
|
||||
default:
|
||||
v, _ := StrTo(ToStr(value)).Float64()
|
||||
ind.SetFloat(v)
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Struct:
|
||||
if value == nil {
|
||||
ind.Set(reflect.Zero(ind.Type()))
|
||||
return
|
||||
}
|
||||
switch ind.Interface().(type) {
|
||||
case time.Time:
|
||||
var str string
|
||||
switch d := value.(type) {
|
||||
case time.Time:
|
||||
o.orm.alias.DbBaser.TimeFromDB(&d, o.orm.alias.TZ)
|
||||
ind.Set(reflect.ValueOf(d))
|
||||
case []byte:
|
||||
str = string(d)
|
||||
case string:
|
||||
str = d
|
||||
}
|
||||
if str != "" {
|
||||
if len(str) >= 19 {
|
||||
str = str[:19]
|
||||
t, err := time.ParseInLocation(formatDateTime, str, o.orm.alias.TZ)
|
||||
if err == nil {
|
||||
t = t.In(DefaultTimeLoc)
|
||||
ind.Set(reflect.ValueOf(t))
|
||||
}
|
||||
} else if len(str) >= 10 {
|
||||
str = str[:10]
|
||||
t, err := time.ParseInLocation(formatDate, str, DefaultTimeLoc)
|
||||
if err == nil {
|
||||
ind.Set(reflect.ValueOf(t))
|
||||
}
|
||||
} else if len(str) >= 8 {
|
||||
str = str[:8]
|
||||
t, err := time.ParseInLocation(formatTime, str, DefaultTimeLoc)
|
||||
if err == nil {
|
||||
ind.Set(reflect.ValueOf(t))
|
||||
}
|
||||
}
|
||||
}
|
||||
case sql.NullString, sql.NullInt64, sql.NullFloat64, sql.NullBool:
|
||||
indi := reflect.New(ind.Type()).Interface()
|
||||
sc, ok := indi.(sql.Scanner)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
err := sc.Scan(value)
|
||||
if err == nil {
|
||||
ind.Set(reflect.Indirect(reflect.ValueOf(sc)))
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
if value == nil {
|
||||
ind.Set(reflect.Zero(ind.Type()))
|
||||
break
|
||||
}
|
||||
ind.Set(reflect.New(ind.Type().Elem()))
|
||||
o.setFieldValue(reflect.Indirect(ind), value)
|
||||
}
|
||||
}
|
||||
|
||||
// set field value in loop for slice container
|
||||
func (o *rawSet) loopSetRefs(refs []interface{}, sInds []reflect.Value, nIndsPtr *[]reflect.Value, eTyps []reflect.Type, init bool) {
|
||||
nInds := *nIndsPtr
|
||||
|
||||
cur := 0
|
||||
for i := 0; i < len(sInds); i++ {
|
||||
sInd := sInds[i]
|
||||
eTyp := eTyps[i]
|
||||
|
||||
typ := eTyp
|
||||
isPtr := false
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
isPtr = true
|
||||
typ = typ.Elem()
|
||||
}
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
isPtr = true
|
||||
typ = typ.Elem()
|
||||
}
|
||||
|
||||
var nInd reflect.Value
|
||||
if init {
|
||||
nInd = reflect.New(sInd.Type()).Elem()
|
||||
} else {
|
||||
nInd = nInds[i]
|
||||
}
|
||||
|
||||
val := reflect.New(typ)
|
||||
ind := val.Elem()
|
||||
|
||||
tpName := ind.Type().String()
|
||||
|
||||
if ind.Kind() == reflect.Struct {
|
||||
if tpName == "time.Time" {
|
||||
value := reflect.ValueOf(refs[cur]).Elem().Interface()
|
||||
if isPtr && value == nil {
|
||||
val = reflect.New(val.Type()).Elem()
|
||||
} else {
|
||||
o.setFieldValue(ind, value)
|
||||
}
|
||||
cur++
|
||||
}
|
||||
} else {
|
||||
value := reflect.ValueOf(refs[cur]).Elem().Interface()
|
||||
if isPtr && value == nil {
|
||||
val = reflect.New(val.Type()).Elem()
|
||||
} else {
|
||||
o.setFieldValue(ind, value)
|
||||
}
|
||||
cur++
|
||||
}
|
||||
|
||||
if nInd.Kind() == reflect.Slice {
|
||||
if isPtr {
|
||||
nInd = reflect.Append(nInd, val)
|
||||
} else {
|
||||
nInd = reflect.Append(nInd, ind)
|
||||
}
|
||||
} else {
|
||||
if isPtr {
|
||||
nInd.Set(val)
|
||||
} else {
|
||||
nInd.Set(ind)
|
||||
}
|
||||
}
|
||||
|
||||
nInds[i] = nInd
|
||||
}
|
||||
}
|
||||
|
||||
// query data and map to container
|
||||
func (o *rawSet) QueryRow(containers ...interface{}) error {
|
||||
var (
|
||||
refs = make([]interface{}, 0, len(containers))
|
||||
sInds []reflect.Value
|
||||
eTyps []reflect.Type
|
||||
sMi *modelInfo
|
||||
)
|
||||
structMode := false
|
||||
for _, container := range containers {
|
||||
val := reflect.ValueOf(container)
|
||||
ind := reflect.Indirect(val)
|
||||
|
||||
if val.Kind() != reflect.Ptr {
|
||||
panic(fmt.Errorf("<RawSeter.QueryRow> all args must be use ptr"))
|
||||
}
|
||||
|
||||
etyp := ind.Type()
|
||||
typ := etyp
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
}
|
||||
|
||||
sInds = append(sInds, ind)
|
||||
eTyps = append(eTyps, etyp)
|
||||
|
||||
if typ.Kind() == reflect.Struct && typ.String() != "time.Time" {
|
||||
if len(containers) > 1 {
|
||||
panic(fmt.Errorf("<RawSeter.QueryRow> now support one struct only. see #384"))
|
||||
}
|
||||
|
||||
structMode = true
|
||||
fn := getFullName(typ)
|
||||
if mi, ok := defaultModelCache.getByFullName(fn); ok {
|
||||
sMi = mi
|
||||
}
|
||||
} else {
|
||||
var ref interface{}
|
||||
refs = append(refs, &ref)
|
||||
}
|
||||
}
|
||||
|
||||
query := o.query
|
||||
o.orm.alias.DbBaser.ReplaceMarks(&query)
|
||||
|
||||
args := getFlatParams(nil, o.args, o.orm.alias.TZ)
|
||||
rows, err := o.orm.db.Query(query, args...)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return ErrNoRows
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
structTagMap := make(map[reflect.StructTag]map[string]string)
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
if rows.Next() {
|
||||
if structMode {
|
||||
columns, err := rows.Columns()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
columnsMp := make(map[string]interface{}, len(columns))
|
||||
|
||||
refs = make([]interface{}, 0, len(columns))
|
||||
for _, col := range columns {
|
||||
var ref interface{}
|
||||
columnsMp[col] = &ref
|
||||
refs = append(refs, &ref)
|
||||
}
|
||||
|
||||
if err := rows.Scan(refs...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ind := sInds[0]
|
||||
|
||||
if ind.Kind() == reflect.Ptr {
|
||||
if ind.IsNil() || !ind.IsValid() {
|
||||
ind.Set(reflect.New(eTyps[0].Elem()))
|
||||
}
|
||||
ind = ind.Elem()
|
||||
}
|
||||
|
||||
if sMi != nil {
|
||||
for _, col := range columns {
|
||||
if fi := sMi.fields.GetByColumn(col); fi != nil {
|
||||
value := reflect.ValueOf(columnsMp[col]).Elem().Interface()
|
||||
field := ind.FieldByIndex(fi.fieldIndex)
|
||||
if fi.fieldType&IsRelField > 0 {
|
||||
mf := reflect.New(fi.relModelInfo.addrField.Elem().Type())
|
||||
field.Set(mf)
|
||||
field = mf.Elem().FieldByIndex(fi.relModelInfo.fields.pk.fieldIndex)
|
||||
}
|
||||
if fi.isFielder {
|
||||
fd := field.Addr().Interface().(Fielder)
|
||||
err := fd.SetRaw(value)
|
||||
if err != nil {
|
||||
return errors.Errorf("set raw error:%s", err)
|
||||
}
|
||||
} else {
|
||||
o.setFieldValue(field, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// define recursive function
|
||||
var recursiveSetField func(rv reflect.Value)
|
||||
recursiveSetField = func(rv reflect.Value) {
|
||||
for i := 0; i < rv.NumField(); i++ {
|
||||
f := rv.Field(i)
|
||||
fe := rv.Type().Field(i)
|
||||
|
||||
// check if the field is a Struct
|
||||
// recursive the Struct type
|
||||
if fe.Type.Kind() == reflect.Struct {
|
||||
recursiveSetField(f)
|
||||
}
|
||||
|
||||
// thanks @Gazeboxu.
|
||||
tags := structTagMap[fe.Tag]
|
||||
if tags == nil {
|
||||
_, tags = parseStructTag(fe.Tag.Get(defaultStructTagName))
|
||||
structTagMap[fe.Tag] = tags
|
||||
}
|
||||
var col string
|
||||
if col = tags["column"]; col == "" {
|
||||
col = nameStrategyMap[nameStrategy](fe.Name)
|
||||
}
|
||||
if v, ok := columnsMp[col]; ok {
|
||||
value := reflect.ValueOf(v).Elem().Interface()
|
||||
o.setFieldValue(f, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// init call the recursive function
|
||||
recursiveSetField(ind)
|
||||
}
|
||||
|
||||
} else {
|
||||
if err := rows.Scan(refs...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nInds := make([]reflect.Value, len(sInds))
|
||||
o.loopSetRefs(refs, sInds, &nInds, eTyps, true)
|
||||
for i, sInd := range sInds {
|
||||
nInd := nInds[i]
|
||||
sInd.Set(nInd)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return ErrNoRows
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// query data rows and map to container
|
||||
func (o *rawSet) QueryRows(containers ...interface{}) (int64, error) {
|
||||
var (
|
||||
refs = make([]interface{}, 0, len(containers))
|
||||
sInds []reflect.Value
|
||||
eTyps []reflect.Type
|
||||
sMi *modelInfo
|
||||
)
|
||||
structMode := false
|
||||
for _, container := range containers {
|
||||
val := reflect.ValueOf(container)
|
||||
sInd := reflect.Indirect(val)
|
||||
if val.Kind() != reflect.Ptr || sInd.Kind() != reflect.Slice {
|
||||
panic(fmt.Errorf("<RawSeter.QueryRows> all args must be use ptr slice"))
|
||||
}
|
||||
|
||||
etyp := sInd.Type().Elem()
|
||||
typ := etyp
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
}
|
||||
|
||||
sInds = append(sInds, sInd)
|
||||
eTyps = append(eTyps, etyp)
|
||||
|
||||
if typ.Kind() == reflect.Struct && typ.String() != "time.Time" {
|
||||
if len(containers) > 1 {
|
||||
panic(fmt.Errorf("<RawSeter.QueryRow> now support one struct only. see #384"))
|
||||
}
|
||||
|
||||
structMode = true
|
||||
fn := getFullName(typ)
|
||||
if mi, ok := defaultModelCache.getByFullName(fn); ok {
|
||||
sMi = mi
|
||||
}
|
||||
} else {
|
||||
var ref interface{}
|
||||
refs = append(refs, &ref)
|
||||
}
|
||||
}
|
||||
|
||||
query := o.query
|
||||
o.orm.alias.DbBaser.ReplaceMarks(&query)
|
||||
|
||||
args := getFlatParams(nil, o.args, o.orm.alias.TZ)
|
||||
rows, err := o.orm.db.Query(query, args...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
var cnt int64
|
||||
nInds := make([]reflect.Value, len(sInds))
|
||||
sInd := sInds[0]
|
||||
|
||||
for rows.Next() {
|
||||
|
||||
if structMode {
|
||||
columns, err := rows.Columns()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
columnsMp := make(map[string]interface{}, len(columns))
|
||||
|
||||
refs = make([]interface{}, 0, len(columns))
|
||||
for _, col := range columns {
|
||||
var ref interface{}
|
||||
columnsMp[col] = &ref
|
||||
refs = append(refs, &ref)
|
||||
}
|
||||
|
||||
if err := rows.Scan(refs...); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if cnt == 0 && !sInd.IsNil() {
|
||||
sInd.Set(reflect.New(sInd.Type()).Elem())
|
||||
}
|
||||
|
||||
var ind reflect.Value
|
||||
if eTyps[0].Kind() == reflect.Ptr {
|
||||
ind = reflect.New(eTyps[0].Elem())
|
||||
} else {
|
||||
ind = reflect.New(eTyps[0])
|
||||
}
|
||||
|
||||
if ind.Kind() == reflect.Ptr {
|
||||
ind = ind.Elem()
|
||||
}
|
||||
|
||||
if sMi != nil {
|
||||
for _, col := range columns {
|
||||
if fi := sMi.fields.GetByColumn(col); fi != nil {
|
||||
value := reflect.ValueOf(columnsMp[col]).Elem().Interface()
|
||||
field := ind.FieldByIndex(fi.fieldIndex)
|
||||
if fi.fieldType&IsRelField > 0 {
|
||||
mf := reflect.New(fi.relModelInfo.addrField.Elem().Type())
|
||||
field.Set(mf)
|
||||
field = mf.Elem().FieldByIndex(fi.relModelInfo.fields.pk.fieldIndex)
|
||||
}
|
||||
if fi.isFielder {
|
||||
fd := field.Addr().Interface().(Fielder)
|
||||
err := fd.SetRaw(value)
|
||||
if err != nil {
|
||||
return 0, errors.Errorf("set raw error:%s", err)
|
||||
}
|
||||
} else {
|
||||
o.setFieldValue(field, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// define recursive function
|
||||
var recursiveSetField func(rv reflect.Value)
|
||||
recursiveSetField = func(rv reflect.Value) {
|
||||
for i := 0; i < rv.NumField(); i++ {
|
||||
f := rv.Field(i)
|
||||
fe := rv.Type().Field(i)
|
||||
|
||||
// check if the field is a Struct
|
||||
// recursive the Struct type
|
||||
if fe.Type.Kind() == reflect.Struct {
|
||||
recursiveSetField(f)
|
||||
}
|
||||
|
||||
_, tags := parseStructTag(fe.Tag.Get(defaultStructTagName))
|
||||
var col string
|
||||
if col = tags["column"]; col == "" {
|
||||
col = nameStrategyMap[nameStrategy](fe.Name)
|
||||
}
|
||||
if v, ok := columnsMp[col]; ok {
|
||||
value := reflect.ValueOf(v).Elem().Interface()
|
||||
o.setFieldValue(f, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// init call the recursive function
|
||||
recursiveSetField(ind)
|
||||
}
|
||||
|
||||
if eTyps[0].Kind() == reflect.Ptr {
|
||||
ind = ind.Addr()
|
||||
}
|
||||
|
||||
sInd = reflect.Append(sInd, ind)
|
||||
|
||||
} else {
|
||||
if err := rows.Scan(refs...); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
o.loopSetRefs(refs, sInds, &nInds, eTyps, cnt == 0)
|
||||
}
|
||||
|
||||
cnt++
|
||||
}
|
||||
|
||||
if cnt > 0 {
|
||||
if structMode {
|
||||
sInds[0].Set(sInd)
|
||||
} else {
|
||||
for i, sInd := range sInds {
|
||||
nInd := nInds[i]
|
||||
sInd.Set(nInd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cnt, nil
|
||||
}
|
||||
|
||||
func (o *rawSet) readValues(container interface{}, needCols []string) (int64, error) {
|
||||
var (
|
||||
maps []Params
|
||||
lists []ParamsList
|
||||
list ParamsList
|
||||
)
|
||||
|
||||
typ := 0
|
||||
switch container.(type) {
|
||||
case *[]Params:
|
||||
typ = 1
|
||||
case *[]ParamsList:
|
||||
typ = 2
|
||||
case *ParamsList:
|
||||
typ = 3
|
||||
default:
|
||||
panic(fmt.Errorf("<RawSeter> unsupport read values type `%T`", container))
|
||||
}
|
||||
|
||||
query := o.query
|
||||
o.orm.alias.DbBaser.ReplaceMarks(&query)
|
||||
|
||||
args := getFlatParams(nil, o.args, o.orm.alias.TZ)
|
||||
|
||||
var rs *sql.Rows
|
||||
rs, err := o.orm.db.Query(query, args...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
defer rs.Close()
|
||||
|
||||
var (
|
||||
refs []interface{}
|
||||
cnt int64
|
||||
cols []string
|
||||
indexs []int
|
||||
)
|
||||
|
||||
for rs.Next() {
|
||||
if cnt == 0 {
|
||||
columns, err := rs.Columns()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(needCols) > 0 {
|
||||
indexs = make([]int, 0, len(needCols))
|
||||
} else {
|
||||
indexs = make([]int, 0, len(columns))
|
||||
}
|
||||
|
||||
cols = columns
|
||||
refs = make([]interface{}, len(cols))
|
||||
for i := range refs {
|
||||
var ref sql.NullString
|
||||
refs[i] = &ref
|
||||
|
||||
if len(needCols) > 0 {
|
||||
for _, c := range needCols {
|
||||
if c == cols[i] {
|
||||
indexs = append(indexs, i)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
indexs = append(indexs, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := rs.Scan(refs...); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
switch typ {
|
||||
case 1:
|
||||
params := make(Params, len(cols))
|
||||
for _, i := range indexs {
|
||||
ref := refs[i]
|
||||
value := reflect.Indirect(reflect.ValueOf(ref)).Interface().(sql.NullString)
|
||||
if value.Valid {
|
||||
params[cols[i]] = value.String
|
||||
} else {
|
||||
params[cols[i]] = nil
|
||||
}
|
||||
}
|
||||
maps = append(maps, params)
|
||||
case 2:
|
||||
params := make(ParamsList, 0, len(cols))
|
||||
for _, i := range indexs {
|
||||
ref := refs[i]
|
||||
value := reflect.Indirect(reflect.ValueOf(ref)).Interface().(sql.NullString)
|
||||
if value.Valid {
|
||||
params = append(params, value.String)
|
||||
} else {
|
||||
params = append(params, nil)
|
||||
}
|
||||
}
|
||||
lists = append(lists, params)
|
||||
case 3:
|
||||
for _, i := range indexs {
|
||||
ref := refs[i]
|
||||
value := reflect.Indirect(reflect.ValueOf(ref)).Interface().(sql.NullString)
|
||||
if value.Valid {
|
||||
list = append(list, value.String)
|
||||
} else {
|
||||
list = append(list, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cnt++
|
||||
}
|
||||
|
||||
switch v := container.(type) {
|
||||
case *[]Params:
|
||||
*v = maps
|
||||
case *[]ParamsList:
|
||||
*v = lists
|
||||
case *ParamsList:
|
||||
*v = list
|
||||
}
|
||||
|
||||
return cnt, nil
|
||||
}
|
||||
|
||||
func (o *rawSet) queryRowsTo(container interface{}, keyCol, valueCol string) (int64, error) {
|
||||
var (
|
||||
maps Params
|
||||
ind *reflect.Value
|
||||
)
|
||||
|
||||
var typ int
|
||||
switch container.(type) {
|
||||
case *Params:
|
||||
typ = 1
|
||||
default:
|
||||
typ = 2
|
||||
vl := reflect.ValueOf(container)
|
||||
id := reflect.Indirect(vl)
|
||||
if vl.Kind() != reflect.Ptr || id.Kind() != reflect.Struct {
|
||||
panic(fmt.Errorf("<RawSeter> RowsTo unsupport type `%T` need ptr struct", container))
|
||||
}
|
||||
|
||||
ind = &id
|
||||
}
|
||||
|
||||
query := o.query
|
||||
o.orm.alias.DbBaser.ReplaceMarks(&query)
|
||||
|
||||
args := getFlatParams(nil, o.args, o.orm.alias.TZ)
|
||||
|
||||
rs, err := o.orm.db.Query(query, args...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
defer rs.Close()
|
||||
|
||||
var (
|
||||
refs []interface{}
|
||||
cnt int64
|
||||
cols []string
|
||||
)
|
||||
|
||||
var (
|
||||
keyIndex = -1
|
||||
valueIndex = -1
|
||||
)
|
||||
|
||||
for rs.Next() {
|
||||
if cnt == 0 {
|
||||
columns, err := rs.Columns()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
cols = columns
|
||||
refs = make([]interface{}, len(cols))
|
||||
for i := range refs {
|
||||
if keyCol == cols[i] {
|
||||
keyIndex = i
|
||||
}
|
||||
if typ == 1 || keyIndex == i {
|
||||
var ref sql.NullString
|
||||
refs[i] = &ref
|
||||
} else {
|
||||
var ref interface{}
|
||||
refs[i] = &ref
|
||||
}
|
||||
if valueCol == cols[i] {
|
||||
valueIndex = i
|
||||
}
|
||||
}
|
||||
if keyIndex == -1 || valueIndex == -1 {
|
||||
panic(fmt.Errorf("<RawSeter> RowsTo unknown key, value column name `%s: %s`", keyCol, valueCol))
|
||||
}
|
||||
}
|
||||
|
||||
if err := rs.Scan(refs...); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if cnt == 0 {
|
||||
switch typ {
|
||||
case 1:
|
||||
maps = make(Params)
|
||||
}
|
||||
}
|
||||
|
||||
key := reflect.Indirect(reflect.ValueOf(refs[keyIndex])).Interface().(sql.NullString).String
|
||||
|
||||
switch typ {
|
||||
case 1:
|
||||
value := reflect.Indirect(reflect.ValueOf(refs[valueIndex])).Interface().(sql.NullString)
|
||||
if value.Valid {
|
||||
maps[key] = value.String
|
||||
} else {
|
||||
maps[key] = nil
|
||||
}
|
||||
|
||||
default:
|
||||
if id := ind.FieldByName(camelString(key)); id.IsValid() {
|
||||
o.setFieldValue(id, reflect.ValueOf(refs[valueIndex]).Elem().Interface())
|
||||
}
|
||||
}
|
||||
|
||||
cnt++
|
||||
}
|
||||
|
||||
if typ == 1 {
|
||||
v, _ := container.(*Params)
|
||||
*v = maps
|
||||
}
|
||||
|
||||
return cnt, nil
|
||||
}
|
||||
|
||||
// query data to []map[string]interface
|
||||
func (o *rawSet) Values(container *[]Params, cols ...string) (int64, error) {
|
||||
return o.readValues(container, cols)
|
||||
}
|
||||
|
||||
// query data to [][]interface
|
||||
func (o *rawSet) ValuesList(container *[]ParamsList, cols ...string) (int64, error) {
|
||||
return o.readValues(container, cols)
|
||||
}
|
||||
|
||||
// query data to []interface
|
||||
func (o *rawSet) ValuesFlat(container *ParamsList, cols ...string) (int64, error) {
|
||||
return o.readValues(container, cols)
|
||||
}
|
||||
|
||||
// query all rows into map[string]interface with specify key and value column name.
|
||||
// keyCol = "name", valueCol = "value"
|
||||
// table data
|
||||
// name | value
|
||||
// total | 100
|
||||
// found | 200
|
||||
// to map[string]interface{}{
|
||||
// "total": 100,
|
||||
// "found": 200,
|
||||
// }
|
||||
func (o *rawSet) RowsToMap(result *Params, keyCol, valueCol string) (int64, error) {
|
||||
return o.queryRowsTo(result, keyCol, valueCol)
|
||||
}
|
||||
|
||||
// query all rows into struct with specify key and value column name.
|
||||
// keyCol = "name", valueCol = "value"
|
||||
// table data
|
||||
// name | value
|
||||
// total | 100
|
||||
// found | 200
|
||||
// to struct {
|
||||
// Total int
|
||||
// Found int
|
||||
// }
|
||||
func (o *rawSet) RowsToStruct(ptrStruct interface{}, keyCol, valueCol string) (int64, error) {
|
||||
return o.queryRowsTo(ptrStruct, keyCol, valueCol)
|
||||
}
|
||||
|
||||
// return prepared raw statement for used in times.
|
||||
func (o *rawSet) Prepare() (RawPreparer, error) {
|
||||
return newRawPreparer(o)
|
||||
}
|
||||
|
||||
func newRawSet(orm *ormBase, query string, args []interface{}) RawSeter {
|
||||
o := new(rawSet)
|
||||
o.query = query
|
||||
o.args = args
|
||||
o.orm = orm
|
||||
return o
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import "errors"
|
||||
|
||||
// QueryBuilder is the Query builder interface
|
||||
type QueryBuilder interface {
|
||||
Select(fields ...string) QueryBuilder
|
||||
ForUpdate() QueryBuilder
|
||||
From(tables ...string) QueryBuilder
|
||||
InnerJoin(table string) QueryBuilder
|
||||
LeftJoin(table string) QueryBuilder
|
||||
RightJoin(table string) QueryBuilder
|
||||
On(cond string) QueryBuilder
|
||||
Where(cond string) QueryBuilder
|
||||
And(cond string) QueryBuilder
|
||||
Or(cond string) QueryBuilder
|
||||
In(vals ...string) QueryBuilder
|
||||
OrderBy(fields ...string) QueryBuilder
|
||||
Asc() QueryBuilder
|
||||
Desc() QueryBuilder
|
||||
Limit(limit int) QueryBuilder
|
||||
Offset(offset int) QueryBuilder
|
||||
GroupBy(fields ...string) QueryBuilder
|
||||
Having(cond string) QueryBuilder
|
||||
Update(tables ...string) QueryBuilder
|
||||
Set(kv ...string) QueryBuilder
|
||||
Delete(tables ...string) QueryBuilder
|
||||
InsertInto(table string, fields ...string) QueryBuilder
|
||||
Values(vals ...string) QueryBuilder
|
||||
Subquery(sub string, alias string) string
|
||||
String() string
|
||||
}
|
||||
|
||||
// NewQueryBuilder return the QueryBuilder
|
||||
func NewQueryBuilder(driver string) (qb QueryBuilder, err error) {
|
||||
if driver == "mysql" {
|
||||
qb = new(MySQLQueryBuilder)
|
||||
} else if driver == "tidb" {
|
||||
qb = new(TiDBQueryBuilder)
|
||||
} else if driver == "postgres" {
|
||||
qb = new(PostgresQueryBuilder)
|
||||
} else if driver == "sqlite" {
|
||||
err = errors.New("sqlite query builder is not supported yet")
|
||||
} else {
|
||||
err = errors.New("unknown driver for query builder")
|
||||
}
|
||||
return
|
||||
}
|
@ -1,187 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CommaSpace is the separation
|
||||
const CommaSpace = ", "
|
||||
|
||||
// MySQLQueryBuilder is the SQL build
|
||||
type MySQLQueryBuilder struct {
|
||||
tokens []string
|
||||
}
|
||||
|
||||
// Select will join the fields
|
||||
func (qb *MySQLQueryBuilder) Select(fields ...string) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "SELECT", strings.Join(fields, CommaSpace))
|
||||
return qb
|
||||
}
|
||||
|
||||
// ForUpdate add the FOR UPDATE clause
|
||||
func (qb *MySQLQueryBuilder) ForUpdate() QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "FOR UPDATE")
|
||||
return qb
|
||||
}
|
||||
|
||||
// From join the tables
|
||||
func (qb *MySQLQueryBuilder) From(tables ...string) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "FROM", strings.Join(tables, CommaSpace))
|
||||
return qb
|
||||
}
|
||||
|
||||
// InnerJoin INNER JOIN the table
|
||||
func (qb *MySQLQueryBuilder) InnerJoin(table string) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "INNER JOIN", table)
|
||||
return qb
|
||||
}
|
||||
|
||||
// LeftJoin LEFT JOIN the table
|
||||
func (qb *MySQLQueryBuilder) LeftJoin(table string) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "LEFT JOIN", table)
|
||||
return qb
|
||||
}
|
||||
|
||||
// RightJoin RIGHT JOIN the table
|
||||
func (qb *MySQLQueryBuilder) RightJoin(table string) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "RIGHT JOIN", table)
|
||||
return qb
|
||||
}
|
||||
|
||||
// On join with on cond
|
||||
func (qb *MySQLQueryBuilder) On(cond string) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "ON", cond)
|
||||
return qb
|
||||
}
|
||||
|
||||
// Where join the Where cond
|
||||
func (qb *MySQLQueryBuilder) Where(cond string) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "WHERE", cond)
|
||||
return qb
|
||||
}
|
||||
|
||||
// And join the and cond
|
||||
func (qb *MySQLQueryBuilder) And(cond string) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "AND", cond)
|
||||
return qb
|
||||
}
|
||||
|
||||
// Or join the or cond
|
||||
func (qb *MySQLQueryBuilder) Or(cond string) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "OR", cond)
|
||||
return qb
|
||||
}
|
||||
|
||||
// In join the IN (vals)
|
||||
func (qb *MySQLQueryBuilder) In(vals ...string) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "IN", "(", strings.Join(vals, CommaSpace), ")")
|
||||
return qb
|
||||
}
|
||||
|
||||
// OrderBy join the Order by fields
|
||||
func (qb *MySQLQueryBuilder) OrderBy(fields ...string) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "ORDER BY", strings.Join(fields, CommaSpace))
|
||||
return qb
|
||||
}
|
||||
|
||||
// Asc join the asc
|
||||
func (qb *MySQLQueryBuilder) Asc() QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "ASC")
|
||||
return qb
|
||||
}
|
||||
|
||||
// Desc join the desc
|
||||
func (qb *MySQLQueryBuilder) Desc() QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "DESC")
|
||||
return qb
|
||||
}
|
||||
|
||||
// Limit join the limit num
|
||||
func (qb *MySQLQueryBuilder) Limit(limit int) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "LIMIT", strconv.Itoa(limit))
|
||||
return qb
|
||||
}
|
||||
|
||||
// Offset join the offset num
|
||||
func (qb *MySQLQueryBuilder) Offset(offset int) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "OFFSET", strconv.Itoa(offset))
|
||||
return qb
|
||||
}
|
||||
|
||||
// GroupBy join the Group by fields
|
||||
func (qb *MySQLQueryBuilder) GroupBy(fields ...string) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "GROUP BY", strings.Join(fields, CommaSpace))
|
||||
return qb
|
||||
}
|
||||
|
||||
// Having join the Having cond
|
||||
func (qb *MySQLQueryBuilder) Having(cond string) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "HAVING", cond)
|
||||
return qb
|
||||
}
|
||||
|
||||
// Update join the update table
|
||||
func (qb *MySQLQueryBuilder) Update(tables ...string) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "UPDATE", strings.Join(tables, CommaSpace))
|
||||
return qb
|
||||
}
|
||||
|
||||
// Set join the set kv
|
||||
func (qb *MySQLQueryBuilder) Set(kv ...string) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "SET", strings.Join(kv, CommaSpace))
|
||||
return qb
|
||||
}
|
||||
|
||||
// Delete join the Delete tables
|
||||
func (qb *MySQLQueryBuilder) Delete(tables ...string) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "DELETE")
|
||||
if len(tables) != 0 {
|
||||
qb.tokens = append(qb.tokens, strings.Join(tables, CommaSpace))
|
||||
}
|
||||
return qb
|
||||
}
|
||||
|
||||
// InsertInto join the insert SQL
|
||||
func (qb *MySQLQueryBuilder) InsertInto(table string, fields ...string) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "INSERT INTO", table)
|
||||
if len(fields) != 0 {
|
||||
fieldsStr := strings.Join(fields, CommaSpace)
|
||||
qb.tokens = append(qb.tokens, "(", fieldsStr, ")")
|
||||
}
|
||||
return qb
|
||||
}
|
||||
|
||||
// Values join the Values(vals)
|
||||
func (qb *MySQLQueryBuilder) Values(vals ...string) QueryBuilder {
|
||||
valsStr := strings.Join(vals, CommaSpace)
|
||||
qb.tokens = append(qb.tokens, "VALUES", "(", valsStr, ")")
|
||||
return qb
|
||||
}
|
||||
|
||||
// Subquery join the sub as alias
|
||||
func (qb *MySQLQueryBuilder) Subquery(sub string, alias string) string {
|
||||
return fmt.Sprintf("(%s) AS %s", sub, alias)
|
||||
}
|
||||
|
||||
// String join all tokens
|
||||
func (qb *MySQLQueryBuilder) String() string {
|
||||
s := strings.Join(qb.tokens, " ")
|
||||
qb.tokens = qb.tokens[:0]
|
||||
return s
|
||||
}
|
@ -1,219 +0,0 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var quote string = `"`
|
||||
|
||||
// PostgresQueryBuilder is the SQL build
|
||||
type PostgresQueryBuilder struct {
|
||||
tokens []string
|
||||
}
|
||||
|
||||
func processingStr(str []string) string {
|
||||
s := strings.Join(str, `","`)
|
||||
s = fmt.Sprintf("%s%s%s", quote, s, quote)
|
||||
return s
|
||||
}
|
||||
|
||||
// Select will join the fields
|
||||
func (qb *PostgresQueryBuilder) Select(fields ...string) QueryBuilder {
|
||||
var str string
|
||||
n := len(fields)
|
||||
|
||||
if fields[0] == "*" {
|
||||
str = "*"
|
||||
} else {
|
||||
for i := 0; i < n; i++ {
|
||||
sli := strings.Split(fields[i], ".")
|
||||
s := strings.Join(sli, `"."`)
|
||||
s = fmt.Sprintf("%s%s%s", quote, s, quote)
|
||||
if n == 1 || i == n-1 {
|
||||
str += s
|
||||
} else {
|
||||
str += s + ","
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
qb.tokens = append(qb.tokens, "SELECT", str)
|
||||
return qb
|
||||
}
|
||||
|
||||
// ForUpdate add the FOR UPDATE clause
|
||||
func (qb *PostgresQueryBuilder) ForUpdate() QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "FOR UPDATE")
|
||||
return qb
|
||||
}
|
||||
|
||||
// From join the tables
|
||||
func (qb *PostgresQueryBuilder) From(tables ...string) QueryBuilder {
|
||||
str := processingStr(tables)
|
||||
qb.tokens = append(qb.tokens, "FROM", str)
|
||||
return qb
|
||||
}
|
||||
|
||||
// InnerJoin INNER JOIN the table
|
||||
func (qb *PostgresQueryBuilder) InnerJoin(table string) QueryBuilder {
|
||||
str := fmt.Sprintf("%s%s%s", quote, table, quote)
|
||||
qb.tokens = append(qb.tokens, "INNER JOIN", str)
|
||||
return qb
|
||||
}
|
||||
|
||||
// LeftJoin LEFT JOIN the table
|
||||
func (qb *PostgresQueryBuilder) LeftJoin(table string) QueryBuilder {
|
||||
str := fmt.Sprintf("%s%s%s", quote, table, quote)
|
||||
qb.tokens = append(qb.tokens, "LEFT JOIN", str)
|
||||
return qb
|
||||
}
|
||||
|
||||
// RightJoin RIGHT JOIN the table
|
||||
func (qb *PostgresQueryBuilder) RightJoin(table string) QueryBuilder {
|
||||
str := fmt.Sprintf("%s%s%s", quote, table, quote)
|
||||
qb.tokens = append(qb.tokens, "RIGHT JOIN", str)
|
||||
return qb
|
||||
}
|
||||
|
||||
// On join with on cond
|
||||
func (qb *PostgresQueryBuilder) On(cond string) QueryBuilder {
|
||||
var str string
|
||||
cond = strings.Replace(cond, " ", "", -1)
|
||||
slice := strings.Split(cond, "=")
|
||||
for i := 0; i < len(slice); i++ {
|
||||
sli := strings.Split(slice[i], ".")
|
||||
s := strings.Join(sli, `"."`)
|
||||
s = fmt.Sprintf("%s%s%s", quote, s, quote)
|
||||
if i == 0 {
|
||||
str = s + " =" + " "
|
||||
} else {
|
||||
str += s
|
||||
}
|
||||
}
|
||||
|
||||
qb.tokens = append(qb.tokens, "ON", str)
|
||||
return qb
|
||||
}
|
||||
|
||||
// Where join the Where cond
|
||||
func (qb *PostgresQueryBuilder) Where(cond string) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "WHERE", cond)
|
||||
return qb
|
||||
}
|
||||
|
||||
// And join the and cond
|
||||
func (qb *PostgresQueryBuilder) And(cond string) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "AND", cond)
|
||||
return qb
|
||||
}
|
||||
|
||||
// Or join the or cond
|
||||
func (qb *PostgresQueryBuilder) Or(cond string) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "OR", cond)
|
||||
return qb
|
||||
}
|
||||
|
||||
// In join the IN (vals)
|
||||
func (qb *PostgresQueryBuilder) In(vals ...string) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "IN", "(", strings.Join(vals, CommaSpace), ")")
|
||||
return qb
|
||||
}
|
||||
|
||||
// OrderBy join the Order by fields
|
||||
func (qb *PostgresQueryBuilder) OrderBy(fields ...string) QueryBuilder {
|
||||
str := processingStr(fields)
|
||||
qb.tokens = append(qb.tokens, "ORDER BY", str)
|
||||
return qb
|
||||
}
|
||||
|
||||
// Asc join the asc
|
||||
func (qb *PostgresQueryBuilder) Asc() QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "ASC")
|
||||
return qb
|
||||
}
|
||||
|
||||
// Desc join the desc
|
||||
func (qb *PostgresQueryBuilder) Desc() QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "DESC")
|
||||
return qb
|
||||
}
|
||||
|
||||
// Limit join the limit num
|
||||
func (qb *PostgresQueryBuilder) Limit(limit int) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "LIMIT", strconv.Itoa(limit))
|
||||
return qb
|
||||
}
|
||||
|
||||
// Offset join the offset num
|
||||
func (qb *PostgresQueryBuilder) Offset(offset int) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "OFFSET", strconv.Itoa(offset))
|
||||
return qb
|
||||
}
|
||||
|
||||
// GroupBy join the Group by fields
|
||||
func (qb *PostgresQueryBuilder) GroupBy(fields ...string) QueryBuilder {
|
||||
str := processingStr(fields)
|
||||
qb.tokens = append(qb.tokens, "GROUP BY", str)
|
||||
return qb
|
||||
}
|
||||
|
||||
// Having join the Having cond
|
||||
func (qb *PostgresQueryBuilder) Having(cond string) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "HAVING", cond)
|
||||
return qb
|
||||
}
|
||||
|
||||
// Update join the update table
|
||||
func (qb *PostgresQueryBuilder) Update(tables ...string) QueryBuilder {
|
||||
str := processingStr(tables)
|
||||
qb.tokens = append(qb.tokens, "UPDATE", str)
|
||||
return qb
|
||||
}
|
||||
|
||||
// Set join the set kv
|
||||
func (qb *PostgresQueryBuilder) Set(kv ...string) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "SET", strings.Join(kv, CommaSpace))
|
||||
return qb
|
||||
}
|
||||
|
||||
// Delete join the Delete tables
|
||||
func (qb *PostgresQueryBuilder) Delete(tables ...string) QueryBuilder {
|
||||
qb.tokens = append(qb.tokens, "DELETE")
|
||||
if len(tables) != 0 {
|
||||
str := processingStr(tables)
|
||||
qb.tokens = append(qb.tokens, str)
|
||||
}
|
||||
return qb
|
||||
}
|
||||
|
||||
// InsertInto join the insert SQL
|
||||
func (qb *PostgresQueryBuilder) InsertInto(table string, fields ...string) QueryBuilder {
|
||||
str := fmt.Sprintf("%s%s%s", quote, table, quote)
|
||||
qb.tokens = append(qb.tokens, "INSERT INTO", str)
|
||||
if len(fields) != 0 {
|
||||
fieldsStr := strings.Join(fields, CommaSpace)
|
||||
qb.tokens = append(qb.tokens, "(", fieldsStr, ")")
|
||||
}
|
||||
return qb
|
||||
}
|
||||
|
||||
// Values join the Values(vals)
|
||||
func (qb *PostgresQueryBuilder) Values(vals ...string) QueryBuilder {
|
||||
valsStr := strings.Join(vals, CommaSpace)
|
||||
qb.tokens = append(qb.tokens, "VALUES", "(", valsStr, ")")
|
||||
return qb
|
||||
}
|
||||
|
||||
// Subquery join the sub as alias
|
||||
func (qb *PostgresQueryBuilder) Subquery(sub string, alias string) string {
|
||||
return fmt.Sprintf("(%s) AS %s", sub, alias)
|
||||
}
|
||||
|
||||
// String join all tokens
|
||||
func (qb *PostgresQueryBuilder) String() string {
|
||||
s := strings.Join(qb.tokens, " ")
|
||||
qb.tokens = qb.tokens[:0]
|
||||
return s
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
// Copyright 2015 TiDB Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
// TiDBQueryBuilder is the SQL build
|
||||
type TiDBQueryBuilder struct {
|
||||
MySQLQueryBuilder
|
||||
tokens []string
|
||||
}
|
@ -1,658 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm/clauses/order_clause"
|
||||
"github.com/beego/beego/v2/core/utils"
|
||||
)
|
||||
|
||||
// TableNaming is usually used by model
|
||||
// when you custom your table name, please implement this interfaces
|
||||
// for example:
|
||||
// type User struct {
|
||||
// ...
|
||||
// }
|
||||
// func (u *User) TableName() string {
|
||||
// return "USER_TABLE"
|
||||
// }
|
||||
type TableNameI interface {
|
||||
TableName() string
|
||||
}
|
||||
|
||||
// TableEngineI is usually used by model
|
||||
// when you want to use specific engine, like myisam, you can implement this interface
|
||||
// for example:
|
||||
// type User struct {
|
||||
// ...
|
||||
// }
|
||||
// func (u *User) TableEngine() string {
|
||||
// return "myisam"
|
||||
// }
|
||||
type TableEngineI interface {
|
||||
TableEngine() string
|
||||
}
|
||||
|
||||
// TableIndexI is usually used by model
|
||||
// when you want to create indexes, you can implement this interface
|
||||
// for example:
|
||||
// type User struct {
|
||||
// ...
|
||||
// }
|
||||
// func (u *User) TableIndex() [][]string {
|
||||
// return [][]string{{"Name"}}
|
||||
// }
|
||||
type TableIndexI interface {
|
||||
TableIndex() [][]string
|
||||
}
|
||||
|
||||
// TableUniqueI is usually used by model
|
||||
// when you want to create unique indexes, you can implement this interface
|
||||
// for example:
|
||||
// type User struct {
|
||||
// ...
|
||||
// }
|
||||
// func (u *User) TableUnique() [][]string {
|
||||
// return [][]string{{"Email"}}
|
||||
// }
|
||||
type TableUniqueI interface {
|
||||
TableUnique() [][]string
|
||||
}
|
||||
|
||||
// IsApplicableTableForDB if return false, we won't create table to this db
|
||||
type IsApplicableTableForDB interface {
|
||||
IsApplicableTableForDB(db string) bool
|
||||
}
|
||||
|
||||
// Driver define database driver
|
||||
type Driver interface {
|
||||
Name() string
|
||||
Type() DriverType
|
||||
}
|
||||
|
||||
// Fielder define field info
|
||||
type Fielder interface {
|
||||
String() string
|
||||
FieldType() int
|
||||
SetRaw(interface{}) error
|
||||
RawValue() interface{}
|
||||
}
|
||||
|
||||
type TxBeginner interface {
|
||||
// self control transaction
|
||||
Begin() (TxOrmer, error)
|
||||
BeginWithCtx(ctx context.Context) (TxOrmer, error)
|
||||
BeginWithOpts(opts *sql.TxOptions) (TxOrmer, error)
|
||||
BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error)
|
||||
|
||||
// closure control transaction
|
||||
DoTx(task func(ctx context.Context, txOrm TxOrmer) error) error
|
||||
DoTxWithCtx(ctx context.Context, task func(ctx context.Context, txOrm TxOrmer) error) error
|
||||
DoTxWithOpts(opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error
|
||||
DoTxWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error
|
||||
}
|
||||
|
||||
type TxCommitter interface {
|
||||
txEnder
|
||||
}
|
||||
|
||||
// transaction beginner
|
||||
type txer interface {
|
||||
Begin() (*sql.Tx, error)
|
||||
BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)
|
||||
}
|
||||
|
||||
// transaction ending
|
||||
type txEnder interface {
|
||||
Commit() error
|
||||
Rollback() error
|
||||
|
||||
// RollbackUnlessCommit if the transaction has been committed, do nothing, or transaction will be rollback
|
||||
// For example:
|
||||
// ```go
|
||||
// txOrm := orm.Begin()
|
||||
// defer txOrm.RollbackUnlessCommit()
|
||||
// err := txOrm.Insert() // do something
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// txOrm.Commit()
|
||||
// ```
|
||||
RollbackUnlessCommit() error
|
||||
}
|
||||
|
||||
// Data Manipulation Language
|
||||
type DML interface {
|
||||
// insert model data to database
|
||||
// for example:
|
||||
// user := new(User)
|
||||
// id, err = Ormer.Insert(user)
|
||||
// user must be a pointer and Insert will set user's pk field
|
||||
Insert(md interface{}) (int64, error)
|
||||
InsertWithCtx(ctx context.Context, md interface{}) (int64, error)
|
||||
// mysql:InsertOrUpdate(model) or InsertOrUpdate(model,"colu=colu+value")
|
||||
// if colu type is integer : can use(+-*/), string : convert(colu,"value")
|
||||
// postgres: InsertOrUpdate(model,"conflictColumnName") or InsertOrUpdate(model,"conflictColumnName","colu=colu+value")
|
||||
// if colu type is integer : can use(+-*/), string : colu || "value"
|
||||
InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error)
|
||||
InsertOrUpdateWithCtx(ctx context.Context, md interface{}, colConflitAndArgs ...string) (int64, error)
|
||||
// insert some models to database
|
||||
InsertMulti(bulk int, mds interface{}) (int64, error)
|
||||
InsertMultiWithCtx(ctx context.Context, bulk int, mds interface{}) (int64, error)
|
||||
// update model to database.
|
||||
// cols set the columns those want to update.
|
||||
// find model by Id(pk) field and update columns specified by fields, if cols is null then update all columns
|
||||
// for example:
|
||||
// user := User{Id: 2}
|
||||
// user.Langs = append(user.Langs, "zh-CN", "en-US")
|
||||
// user.Extra.Name = "beego"
|
||||
// user.Extra.Data = "orm"
|
||||
// num, err = Ormer.Update(&user, "Langs", "Extra")
|
||||
Update(md interface{}, cols ...string) (int64, error)
|
||||
UpdateWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error)
|
||||
// delete model in database
|
||||
Delete(md interface{}, cols ...string) (int64, error)
|
||||
DeleteWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error)
|
||||
|
||||
// return a raw query seter for raw sql string.
|
||||
// for example:
|
||||
// ormer.Raw("UPDATE `user` SET `user_name` = ? WHERE `user_name` = ?", "slene", "testing").Exec()
|
||||
// // update user testing's name to slene
|
||||
Raw(query string, args ...interface{}) RawSeter
|
||||
RawWithCtx(ctx context.Context, query string, args ...interface{}) RawSeter
|
||||
}
|
||||
|
||||
// Data Query Language
|
||||
type DQL interface {
|
||||
// read data to model
|
||||
// for example:
|
||||
// this will find User by Id field
|
||||
// u = &User{Id: user.Id}
|
||||
// err = Ormer.Read(u)
|
||||
// this will find User by UserName field
|
||||
// u = &User{UserName: "astaxie", Password: "pass"}
|
||||
// err = Ormer.Read(u, "UserName")
|
||||
Read(md interface{}, cols ...string) error
|
||||
ReadWithCtx(ctx context.Context, md interface{}, cols ...string) error
|
||||
|
||||
// Like Read(), but with "FOR UPDATE" clause, useful in transaction.
|
||||
// Some databases are not support this feature.
|
||||
ReadForUpdate(md interface{}, cols ...string) error
|
||||
ReadForUpdateWithCtx(ctx context.Context, md interface{}, cols ...string) error
|
||||
|
||||
// Try to read a row from the database, or insert one if it doesn't exist
|
||||
ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error)
|
||||
ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 string, cols ...string) (bool, int64, error)
|
||||
|
||||
// load related models to md model.
|
||||
// args are limit, offset int and order string.
|
||||
//
|
||||
// example:
|
||||
// Ormer.LoadRelated(post,"Tags")
|
||||
// for _,tag := range post.Tags{...}
|
||||
// hints.DefaultRelDepth useDefaultRelsDepth ; or depth 0
|
||||
// hints.RelDepth loadRelationDepth
|
||||
// hints.Limit limit default limit 1000
|
||||
// hints.Offset int offset default offset 0
|
||||
// hints.OrderBy string order for example : "-Id"
|
||||
// make sure the relation is defined in model struct tags.
|
||||
LoadRelated(md interface{}, name string, args ...utils.KV) (int64, error)
|
||||
LoadRelatedWithCtx(ctx context.Context, md interface{}, name string, args ...utils.KV) (int64, error)
|
||||
|
||||
// create a models to models queryer
|
||||
// for example:
|
||||
// post := Post{Id: 4}
|
||||
// m2m := Ormer.QueryM2M(&post, "Tags")
|
||||
QueryM2M(md interface{}, name string) QueryM2Mer
|
||||
// NOTE: this method is deprecated, context parameter will not take effect.
|
||||
// Use context.Context directly on methods with `WithCtx` suffix such as InsertWithCtx/UpdateWithCtx
|
||||
QueryM2MWithCtx(ctx context.Context, md interface{}, name string) QueryM2Mer
|
||||
|
||||
// return a QuerySeter for table operations.
|
||||
// table name can be string or struct.
|
||||
// e.g. QueryTable("user"), QueryTable(&user{}) or QueryTable((*User)(nil)),
|
||||
QueryTable(ptrStructOrTableName interface{}) QuerySeter
|
||||
// NOTE: this method is deprecated, context parameter will not take effect.
|
||||
// Use context.Context directly on methods with `WithCtx` suffix such as InsertWithCtx/UpdateWithCtx
|
||||
QueryTableWithCtx(ctx context.Context, ptrStructOrTableName interface{}) QuerySeter
|
||||
|
||||
DBStats() *sql.DBStats
|
||||
}
|
||||
|
||||
type DriverGetter interface {
|
||||
Driver() Driver
|
||||
}
|
||||
|
||||
type ormer interface {
|
||||
DQL
|
||||
DML
|
||||
DriverGetter
|
||||
}
|
||||
|
||||
// QueryExecutor wrapping for ormer
|
||||
type QueryExecutor interface {
|
||||
ormer
|
||||
}
|
||||
|
||||
type Ormer interface {
|
||||
QueryExecutor
|
||||
TxBeginner
|
||||
}
|
||||
|
||||
type TxOrmer interface {
|
||||
QueryExecutor
|
||||
TxCommitter
|
||||
}
|
||||
|
||||
// Inserter insert prepared statement
|
||||
type Inserter interface {
|
||||
Insert(interface{}) (int64, error)
|
||||
InsertWithCtx(context.Context, interface{}) (int64, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
// QuerySeter query seter
|
||||
type QuerySeter interface {
|
||||
// add condition expression to QuerySeter.
|
||||
// for example:
|
||||
// filter by UserName == 'slene'
|
||||
// qs.Filter("UserName", "slene")
|
||||
// sql : left outer join profile on t0.id1==t1.id2 where t1.age == 28
|
||||
// Filter("profile__Age", 28)
|
||||
// // time compare
|
||||
// qs.Filter("created", time.Now())
|
||||
Filter(string, ...interface{}) QuerySeter
|
||||
// add raw sql to querySeter.
|
||||
// for example:
|
||||
// qs.FilterRaw("user_id IN (SELECT id FROM profile WHERE age>=18)")
|
||||
// //sql-> WHERE user_id IN (SELECT id FROM profile WHERE age>=18)
|
||||
FilterRaw(string, string) QuerySeter
|
||||
// add NOT condition to querySeter.
|
||||
// have the same usage as Filter
|
||||
Exclude(string, ...interface{}) QuerySeter
|
||||
// set condition to QuerySeter.
|
||||
// sql's where condition
|
||||
// cond := orm.NewCondition()
|
||||
// cond1 := cond.And("profile__isnull", false).AndNot("status__in", 1).Or("profile__age__gt", 2000)
|
||||
// //sql-> WHERE T0.`profile_id` IS NOT NULL AND NOT T0.`Status` IN (?) OR T1.`age` > 2000
|
||||
// num, err := qs.SetCond(cond1).Count()
|
||||
SetCond(*Condition) QuerySeter
|
||||
// get condition from QuerySeter.
|
||||
// sql's where condition
|
||||
// cond := orm.NewCondition()
|
||||
// cond = cond.And("profile__isnull", false).AndNot("status__in", 1)
|
||||
// qs = qs.SetCond(cond)
|
||||
// cond = qs.GetCond()
|
||||
// cond := cond.Or("profile__age__gt", 2000)
|
||||
// //sql-> WHERE T0.`profile_id` IS NOT NULL AND NOT T0.`Status` IN (?) OR T1.`age` > 2000
|
||||
// num, err := qs.SetCond(cond).Count()
|
||||
GetCond() *Condition
|
||||
// add LIMIT value.
|
||||
// args[0] means offset, e.g. LIMIT num,offset.
|
||||
// if Limit <= 0 then Limit will be set to default limit ,eg 1000
|
||||
// if QuerySeter doesn't call Limit, the sql's Limit will be set to default limit, eg 1000
|
||||
// for example:
|
||||
// qs.Limit(10, 2)
|
||||
// // sql-> limit 10 offset 2
|
||||
Limit(limit interface{}, args ...interface{}) QuerySeter
|
||||
// add OFFSET value
|
||||
// same as Limit function's args[0]
|
||||
Offset(offset interface{}) QuerySeter
|
||||
// add GROUP BY expression
|
||||
// for example:
|
||||
// qs.GroupBy("id")
|
||||
GroupBy(exprs ...string) QuerySeter
|
||||
// add ORDER expression.
|
||||
// "column" means ASC, "-column" means DESC.
|
||||
// for example:
|
||||
// qs.OrderBy("-status")
|
||||
OrderBy(exprs ...string) QuerySeter
|
||||
// add ORDER expression by order clauses
|
||||
// for example:
|
||||
// OrderClauses(
|
||||
// order_clause.Clause(
|
||||
// order.Column("Id"),
|
||||
// order.SortAscending(),
|
||||
// ),
|
||||
// order_clause.Clause(
|
||||
// order.Column("status"),
|
||||
// order.SortDescending(),
|
||||
// ),
|
||||
// )
|
||||
// OrderClauses(order_clause.Clause(
|
||||
// order_clause.Column(`user__status`),
|
||||
// order_clause.SortDescending(),//default None
|
||||
// ))
|
||||
// OrderClauses(order_clause.Clause(
|
||||
// order_clause.Column(`random()`),
|
||||
// order_clause.SortNone(),//default None
|
||||
// order_clause.Raw(),//default false.if true, do not check field is valid or not
|
||||
// ))
|
||||
OrderClauses(orders ...*order_clause.Order) QuerySeter
|
||||
// add FORCE INDEX expression.
|
||||
// for example:
|
||||
// qs.ForceIndex(`idx_name1`,`idx_name2`)
|
||||
// ForceIndex, UseIndex , IgnoreIndex are mutually exclusive
|
||||
ForceIndex(indexes ...string) QuerySeter
|
||||
// add USE INDEX expression.
|
||||
// for example:
|
||||
// qs.UseIndex(`idx_name1`,`idx_name2`)
|
||||
// ForceIndex, UseIndex , IgnoreIndex are mutually exclusive
|
||||
UseIndex(indexes ...string) QuerySeter
|
||||
// add IGNORE INDEX expression.
|
||||
// for example:
|
||||
// qs.IgnoreIndex(`idx_name1`,`idx_name2`)
|
||||
// ForceIndex, UseIndex , IgnoreIndex are mutually exclusive
|
||||
IgnoreIndex(indexes ...string) QuerySeter
|
||||
// set relation model to query together.
|
||||
// it will query relation models and assign to parent model.
|
||||
// for example:
|
||||
// // will load all related fields use left join .
|
||||
// qs.RelatedSel().One(&user)
|
||||
// // will load related field only profile
|
||||
// qs.RelatedSel("profile").One(&user)
|
||||
// user.Profile.Age = 32
|
||||
RelatedSel(params ...interface{}) QuerySeter
|
||||
// Set Distinct
|
||||
// for example:
|
||||
// o.QueryTable("policy").Filter("Groups__Group__Users__User", user).
|
||||
// Distinct().
|
||||
// All(&permissions)
|
||||
Distinct() QuerySeter
|
||||
// set FOR UPDATE to query.
|
||||
// for example:
|
||||
// o.QueryTable("user").Filter("uid", uid).ForUpdate().All(&users)
|
||||
ForUpdate() QuerySeter
|
||||
// return QuerySeter execution result number
|
||||
// for example:
|
||||
// num, err = qs.Filter("profile__age__gt", 28).Count()
|
||||
Count() (int64, error)
|
||||
CountWithCtx(context.Context) (int64, error)
|
||||
// check result empty or not after QuerySeter executed
|
||||
// the same as QuerySeter.Count > 0
|
||||
Exist() bool
|
||||
ExistWithCtx(context.Context) bool
|
||||
// execute update with parameters
|
||||
// for example:
|
||||
// num, err = qs.Filter("user_name", "slene").Update(Params{
|
||||
// "Nums": ColValue(Col_Minus, 50),
|
||||
// }) // user slene's Nums will minus 50
|
||||
// num, err = qs.Filter("UserName", "slene").Update(Params{
|
||||
// "user_name": "slene2"
|
||||
// }) // user slene's name will change to slene2
|
||||
Update(values Params) (int64, error)
|
||||
UpdateWithCtx(ctx context.Context, values Params) (int64, error)
|
||||
// delete from table
|
||||
// for example:
|
||||
// num ,err = qs.Filter("user_name__in", "testing1", "testing2").Delete()
|
||||
// //delete two user who's name is testing1 or testing2
|
||||
Delete() (int64, error)
|
||||
DeleteWithCtx(context.Context) (int64, error)
|
||||
// return a insert queryer.
|
||||
// it can be used in times.
|
||||
// example:
|
||||
// i,err := sq.PrepareInsert()
|
||||
// num, err = i.Insert(&user1) // user table will add one record user1 at once
|
||||
// num, err = i.Insert(&user2) // user table will add one record user2 at once
|
||||
// err = i.Close() //don't forget call Close
|
||||
PrepareInsert() (Inserter, error)
|
||||
PrepareInsertWithCtx(context.Context) (Inserter, error)
|
||||
// query all data and map to containers.
|
||||
// cols means the columns when querying.
|
||||
// for example:
|
||||
// var users []*User
|
||||
// qs.All(&users) // users[0],users[1],users[2] ...
|
||||
All(container interface{}, cols ...string) (int64, error)
|
||||
AllWithCtx(ctx context.Context, container interface{}, cols ...string) (int64, error)
|
||||
// query one row data and map to containers.
|
||||
// cols means the columns when querying.
|
||||
// for example:
|
||||
// var user User
|
||||
// qs.One(&user) //user.UserName == "slene"
|
||||
One(container interface{}, cols ...string) error
|
||||
OneWithCtx(ctx context.Context, container interface{}, cols ...string) error
|
||||
// query all data and map to []map[string]interface.
|
||||
// expres means condition expression.
|
||||
// it converts data to []map[column]value.
|
||||
// for example:
|
||||
// var maps []Params
|
||||
// qs.Values(&maps) //maps[0]["UserName"]=="slene"
|
||||
Values(results *[]Params, exprs ...string) (int64, error)
|
||||
ValuesWithCtx(ctx context.Context, results *[]Params, exprs ...string) (int64, error)
|
||||
// query all data and map to [][]interface
|
||||
// it converts data to [][column_index]value
|
||||
// for example:
|
||||
// var list []ParamsList
|
||||
// qs.ValuesList(&list) // list[0][1] == "slene"
|
||||
ValuesList(results *[]ParamsList, exprs ...string) (int64, error)
|
||||
ValuesListWithCtx(ctx context.Context, results *[]ParamsList, exprs ...string) (int64, error)
|
||||
// query all data and map to []interface.
|
||||
// it's designed for one column record set, auto change to []value, not [][column]value.
|
||||
// for example:
|
||||
// var list ParamsList
|
||||
// qs.ValuesFlat(&list, "UserName") // list[0] == "slene"
|
||||
ValuesFlat(result *ParamsList, expr string) (int64, error)
|
||||
ValuesFlatWithCtx(ctx context.Context, result *ParamsList, expr string) (int64, error)
|
||||
// query all rows into map[string]interface with specify key and value column name.
|
||||
// keyCol = "name", valueCol = "value"
|
||||
// table data
|
||||
// name | value
|
||||
// total | 100
|
||||
// found | 200
|
||||
// to map[string]interface{}{
|
||||
// "total": 100,
|
||||
// "found": 200,
|
||||
// }
|
||||
RowsToMap(result *Params, keyCol, valueCol string) (int64, error)
|
||||
// query all rows into struct with specify key and value column name.
|
||||
// keyCol = "name", valueCol = "value"
|
||||
// table data
|
||||
// name | value
|
||||
// total | 100
|
||||
// found | 200
|
||||
// to struct {
|
||||
// Total int
|
||||
// Found int
|
||||
// }
|
||||
RowsToStruct(ptrStruct interface{}, keyCol, valueCol string) (int64, error)
|
||||
// aggregate func.
|
||||
// for example:
|
||||
// type result struct {
|
||||
// DeptName string
|
||||
// Total int
|
||||
// }
|
||||
// var res []result
|
||||
// o.QueryTable("dept_info").Aggregate("dept_name,sum(salary) as total").GroupBy("dept_name").All(&res)
|
||||
Aggregate(s string) QuerySeter
|
||||
}
|
||||
|
||||
// QueryM2Mer model to model query struct
|
||||
// all operations are on the m2m table only, will not affect the origin model table
|
||||
type QueryM2Mer interface {
|
||||
// add models to origin models when creating queryM2M.
|
||||
// example:
|
||||
// m2m := orm.QueryM2M(post,"Tag")
|
||||
// m2m.Add(&Tag1{},&Tag2{})
|
||||
// for _,tag := range post.Tags{}{ ... }
|
||||
// param could also be any of the follow
|
||||
// []*Tag{{Id:3,Name: "TestTag1"}, {Id:4,Name: "TestTag2"}}
|
||||
// &Tag{Id:5,Name: "TestTag3"}
|
||||
// []interface{}{&Tag{Id:6,Name: "TestTag4"}}
|
||||
// insert one or more rows to m2m table
|
||||
// make sure the relation is defined in post model struct tag.
|
||||
Add(...interface{}) (int64, error)
|
||||
AddWithCtx(context.Context, ...interface{}) (int64, error)
|
||||
// remove models following the origin model relationship
|
||||
// only delete rows from m2m table
|
||||
// for example:
|
||||
// tag3 := &Tag{Id:5,Name: "TestTag3"}
|
||||
// num, err = m2m.Remove(tag3)
|
||||
Remove(...interface{}) (int64, error)
|
||||
RemoveWithCtx(context.Context, ...interface{}) (int64, error)
|
||||
// check model is existed in relationship of origin model
|
||||
Exist(interface{}) bool
|
||||
ExistWithCtx(context.Context, interface{}) bool
|
||||
// clean all models in related of origin model
|
||||
Clear() (int64, error)
|
||||
ClearWithCtx(context.Context) (int64, error)
|
||||
// count all related models of origin model
|
||||
Count() (int64, error)
|
||||
CountWithCtx(context.Context) (int64, error)
|
||||
}
|
||||
|
||||
// RawPreparer raw query statement
|
||||
type RawPreparer interface {
|
||||
Exec(...interface{}) (sql.Result, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
// RawSeter raw query seter
|
||||
// create From Ormer.Raw
|
||||
// for example:
|
||||
// sql := fmt.Sprintf("SELECT %sid%s,%sname%s FROM %suser%s WHERE id = ?",Q,Q,Q,Q,Q,Q)
|
||||
// rs := Ormer.Raw(sql, 1)
|
||||
type RawSeter interface {
|
||||
// execute sql and get result
|
||||
Exec() (sql.Result, error)
|
||||
// query data and map to container
|
||||
// for example:
|
||||
// var name string
|
||||
// var id int
|
||||
// rs.QueryRow(&id,&name) // id==2 name=="slene"
|
||||
QueryRow(containers ...interface{}) error
|
||||
|
||||
// query data rows and map to container
|
||||
// var ids []int
|
||||
// var names []int
|
||||
// query = fmt.Sprintf("SELECT 'id','name' FROM %suser%s", Q, Q)
|
||||
// num, err = dORM.Raw(query).QueryRows(&ids,&names) // ids=>{1,2},names=>{"nobody","slene"}
|
||||
QueryRows(containers ...interface{}) (int64, error)
|
||||
SetArgs(...interface{}) RawSeter
|
||||
// query data to []map[string]interface
|
||||
// see QuerySeter's Values
|
||||
Values(container *[]Params, cols ...string) (int64, error)
|
||||
// query data to [][]interface
|
||||
// see QuerySeter's ValuesList
|
||||
ValuesList(container *[]ParamsList, cols ...string) (int64, error)
|
||||
// query data to []interface
|
||||
// see QuerySeter's ValuesFlat
|
||||
ValuesFlat(container *ParamsList, cols ...string) (int64, error)
|
||||
// query all rows into map[string]interface with specify key and value column name.
|
||||
// keyCol = "name", valueCol = "value"
|
||||
// table data
|
||||
// name | value
|
||||
// total | 100
|
||||
// found | 200
|
||||
// to map[string]interface{}{
|
||||
// "total": 100,
|
||||
// "found": 200,
|
||||
// }
|
||||
RowsToMap(result *Params, keyCol, valueCol string) (int64, error)
|
||||
// query all rows into struct with specify key and value column name.
|
||||
// keyCol = "name", valueCol = "value"
|
||||
// table data
|
||||
// name | value
|
||||
// total | 100
|
||||
// found | 200
|
||||
// to struct {
|
||||
// Total int
|
||||
// Found int
|
||||
// }
|
||||
RowsToStruct(ptrStruct interface{}, keyCol, valueCol string) (int64, error)
|
||||
|
||||
// return prepared raw statement for used in times.
|
||||
// for example:
|
||||
// pre, err := dORM.Raw("INSERT INTO tag (name) VALUES (?)").Prepare()
|
||||
// r, err := pre.Exec("name1") // INSERT INTO tag (name) VALUES (`name1`)
|
||||
Prepare() (RawPreparer, error)
|
||||
}
|
||||
|
||||
// stmtQuerier statement querier
|
||||
type stmtQuerier interface {
|
||||
Close() error
|
||||
Exec(args ...interface{}) (sql.Result, error)
|
||||
ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error)
|
||||
Query(args ...interface{}) (*sql.Rows, error)
|
||||
QueryContext(ctx context.Context, args ...interface{}) (*sql.Rows, error)
|
||||
QueryRow(args ...interface{}) *sql.Row
|
||||
QueryRowContext(ctx context.Context, args ...interface{}) *sql.Row
|
||||
}
|
||||
|
||||
// db querier
|
||||
type dbQuerier interface {
|
||||
Prepare(query string) (*sql.Stmt, error)
|
||||
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
|
||||
Exec(query string, args ...interface{}) (sql.Result, error)
|
||||
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
|
||||
Query(query string, args ...interface{}) (*sql.Rows, error)
|
||||
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
|
||||
QueryRow(query string, args ...interface{}) *sql.Row
|
||||
QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
|
||||
}
|
||||
|
||||
// type DB interface {
|
||||
// Begin() (*sql.Tx, error)
|
||||
// Prepare(query string) (stmtQuerier, error)
|
||||
// Exec(query string, args ...interface{}) (sql.Result, error)
|
||||
// Query(query string, args ...interface{}) (*sql.Rows, error)
|
||||
// QueryRow(query string, args ...interface{}) *sql.Row
|
||||
// }
|
||||
|
||||
// base database struct
|
||||
type dbBaser interface {
|
||||
Read(context.Context, dbQuerier, *modelInfo, reflect.Value, *time.Location, []string, bool) error
|
||||
ReadBatch(context.Context, dbQuerier, *querySet, *modelInfo, *Condition, interface{}, *time.Location, []string) (int64, error)
|
||||
Count(context.Context, dbQuerier, *querySet, *modelInfo, *Condition, *time.Location) (int64, error)
|
||||
ReadValues(context.Context, dbQuerier, *querySet, *modelInfo, *Condition, []string, interface{}, *time.Location) (int64, error)
|
||||
|
||||
Insert(context.Context, dbQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error)
|
||||
InsertOrUpdate(context.Context, dbQuerier, *modelInfo, reflect.Value, *alias, ...string) (int64, error)
|
||||
InsertMulti(context.Context, dbQuerier, *modelInfo, reflect.Value, int, *time.Location) (int64, error)
|
||||
InsertValue(context.Context, dbQuerier, *modelInfo, bool, []string, []interface{}) (int64, error)
|
||||
InsertStmt(context.Context, stmtQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error)
|
||||
|
||||
Update(context.Context, dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) (int64, error)
|
||||
UpdateBatch(context.Context, dbQuerier, *querySet, *modelInfo, *Condition, Params, *time.Location) (int64, error)
|
||||
|
||||
Delete(context.Context, dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) (int64, error)
|
||||
DeleteBatch(context.Context, dbQuerier, *querySet, *modelInfo, *Condition, *time.Location) (int64, error)
|
||||
|
||||
SupportUpdateJoin() bool
|
||||
OperatorSQL(string) string
|
||||
GenerateOperatorSQL(*modelInfo, *fieldInfo, string, []interface{}, *time.Location) (string, []interface{})
|
||||
GenerateOperatorLeftCol(*fieldInfo, string, *string)
|
||||
PrepareInsert(context.Context, dbQuerier, *modelInfo) (stmtQuerier, string, error)
|
||||
MaxLimit() uint64
|
||||
TableQuote() string
|
||||
ReplaceMarks(*string)
|
||||
HasReturningID(*modelInfo, *string) bool
|
||||
TimeFromDB(*time.Time, *time.Location)
|
||||
TimeToDB(*time.Time, *time.Location)
|
||||
DbTypes() map[string]string
|
||||
GetTables(dbQuerier) (map[string]bool, error)
|
||||
GetColumns(context.Context, dbQuerier, string) (map[string][3]string, error)
|
||||
ShowTablesQuery() string
|
||||
ShowColumnsQuery(string) string
|
||||
IndexExists(context.Context, dbQuerier, string, string) bool
|
||||
collectFieldValue(*modelInfo, *fieldInfo, reflect.Value, bool, *time.Location) (interface{}, error)
|
||||
setval(context.Context, dbQuerier, *modelInfo, []string) error
|
||||
|
||||
GenerateSpecifyIndex(tableName string, useIndex int, indexes []string) string
|
||||
}
|
@ -1,319 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type fn func(string) string
|
||||
|
||||
var (
|
||||
nameStrategyMap = map[string]fn{
|
||||
defaultNameStrategy: snakeString,
|
||||
SnakeAcronymNameStrategy: snakeStringWithAcronym,
|
||||
}
|
||||
defaultNameStrategy = "snakeString"
|
||||
SnakeAcronymNameStrategy = "snakeStringWithAcronym"
|
||||
nameStrategy = defaultNameStrategy
|
||||
)
|
||||
|
||||
// StrTo is the target string
|
||||
type StrTo string
|
||||
|
||||
// Set string
|
||||
func (f *StrTo) Set(v string) {
|
||||
if v != "" {
|
||||
*f = StrTo(v)
|
||||
} else {
|
||||
f.Clear()
|
||||
}
|
||||
}
|
||||
|
||||
// Clear string
|
||||
func (f *StrTo) Clear() {
|
||||
*f = StrTo(rune(0x1E))
|
||||
}
|
||||
|
||||
// Exist check string exist
|
||||
func (f StrTo) Exist() bool {
|
||||
return string(f) != string(rune(0x1E))
|
||||
}
|
||||
|
||||
// Bool string to bool
|
||||
func (f StrTo) Bool() (bool, error) {
|
||||
return strconv.ParseBool(f.String())
|
||||
}
|
||||
|
||||
// Float32 string to float32
|
||||
func (f StrTo) Float32() (float32, error) {
|
||||
v, err := strconv.ParseFloat(f.String(), 32)
|
||||
return float32(v), err
|
||||
}
|
||||
|
||||
// Float64 string to float64
|
||||
func (f StrTo) Float64() (float64, error) {
|
||||
return strconv.ParseFloat(f.String(), 64)
|
||||
}
|
||||
|
||||
// Int string to int
|
||||
func (f StrTo) Int() (int, error) {
|
||||
v, err := strconv.ParseInt(f.String(), 10, 32)
|
||||
return int(v), err
|
||||
}
|
||||
|
||||
// Int8 string to int8
|
||||
func (f StrTo) Int8() (int8, error) {
|
||||
v, err := strconv.ParseInt(f.String(), 10, 8)
|
||||
return int8(v), err
|
||||
}
|
||||
|
||||
// Int16 string to int16
|
||||
func (f StrTo) Int16() (int16, error) {
|
||||
v, err := strconv.ParseInt(f.String(), 10, 16)
|
||||
return int16(v), err
|
||||
}
|
||||
|
||||
// Int32 string to int32
|
||||
func (f StrTo) Int32() (int32, error) {
|
||||
v, err := strconv.ParseInt(f.String(), 10, 32)
|
||||
return int32(v), err
|
||||
}
|
||||
|
||||
// Int64 string to int64
|
||||
func (f StrTo) Int64() (int64, error) {
|
||||
v, err := strconv.ParseInt(f.String(), 10, 64)
|
||||
if err != nil {
|
||||
i := new(big.Int)
|
||||
ni, ok := i.SetString(f.String(), 10) // octal
|
||||
if !ok {
|
||||
return v, err
|
||||
}
|
||||
return ni.Int64(), nil
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
// Uint string to uint
|
||||
func (f StrTo) Uint() (uint, error) {
|
||||
v, err := strconv.ParseUint(f.String(), 10, 32)
|
||||
return uint(v), err
|
||||
}
|
||||
|
||||
// Uint8 string to uint8
|
||||
func (f StrTo) Uint8() (uint8, error) {
|
||||
v, err := strconv.ParseUint(f.String(), 10, 8)
|
||||
return uint8(v), err
|
||||
}
|
||||
|
||||
// Uint16 string to uint16
|
||||
func (f StrTo) Uint16() (uint16, error) {
|
||||
v, err := strconv.ParseUint(f.String(), 10, 16)
|
||||
return uint16(v), err
|
||||
}
|
||||
|
||||
// Uint32 string to uint32
|
||||
func (f StrTo) Uint32() (uint32, error) {
|
||||
v, err := strconv.ParseUint(f.String(), 10, 32)
|
||||
return uint32(v), err
|
||||
}
|
||||
|
||||
// Uint64 string to uint64
|
||||
func (f StrTo) Uint64() (uint64, error) {
|
||||
v, err := strconv.ParseUint(f.String(), 10, 64)
|
||||
if err != nil {
|
||||
i := new(big.Int)
|
||||
ni, ok := i.SetString(f.String(), 10)
|
||||
if !ok {
|
||||
return v, err
|
||||
}
|
||||
return ni.Uint64(), nil
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
// String string to string
|
||||
func (f StrTo) String() string {
|
||||
if f.Exist() {
|
||||
return string(f)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ToStr interface to string
|
||||
func ToStr(value interface{}, args ...int) (s string) {
|
||||
switch v := value.(type) {
|
||||
case bool:
|
||||
s = strconv.FormatBool(v)
|
||||
case float32:
|
||||
s = strconv.FormatFloat(float64(v), 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 32))
|
||||
case float64:
|
||||
s = strconv.FormatFloat(v, 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 64))
|
||||
case int:
|
||||
s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
|
||||
case int8:
|
||||
s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
|
||||
case int16:
|
||||
s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
|
||||
case int32:
|
||||
s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
|
||||
case int64:
|
||||
s = strconv.FormatInt(v, argInt(args).Get(0, 10))
|
||||
case uint:
|
||||
s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
|
||||
case uint8:
|
||||
s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
|
||||
case uint16:
|
||||
s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
|
||||
case uint32:
|
||||
s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
|
||||
case uint64:
|
||||
s = strconv.FormatUint(v, argInt(args).Get(0, 10))
|
||||
case string:
|
||||
s = v
|
||||
case []byte:
|
||||
s = string(v)
|
||||
default:
|
||||
s = fmt.Sprintf("%v", v)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// ToInt64 interface to int64
|
||||
func ToInt64(value interface{}) (d int64) {
|
||||
val := reflect.ValueOf(value)
|
||||
switch value.(type) {
|
||||
case int, int8, int16, int32, int64:
|
||||
d = val.Int()
|
||||
case uint, uint8, uint16, uint32, uint64:
|
||||
d = int64(val.Uint())
|
||||
default:
|
||||
panic(fmt.Errorf("ToInt64 need numeric not `%T`", value))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func snakeStringWithAcronym(s string) string {
|
||||
data := make([]byte, 0, len(s)*2)
|
||||
num := len(s)
|
||||
for i := 0; i < num; i++ {
|
||||
d := s[i]
|
||||
before := false
|
||||
after := false
|
||||
if i > 0 {
|
||||
before = s[i-1] >= 'a' && s[i-1] <= 'z'
|
||||
}
|
||||
if i+1 < num {
|
||||
after = s[i+1] >= 'a' && s[i+1] <= 'z'
|
||||
}
|
||||
if i > 0 && d >= 'A' && d <= 'Z' && (before || after) {
|
||||
data = append(data, '_')
|
||||
}
|
||||
data = append(data, d)
|
||||
}
|
||||
return strings.ToLower(string(data))
|
||||
}
|
||||
|
||||
// snake string, XxYy to xx_yy , XxYY to xx_y_y
|
||||
func snakeString(s string) string {
|
||||
data := make([]byte, 0, len(s)*2)
|
||||
j := false
|
||||
num := len(s)
|
||||
for i := 0; i < num; i++ {
|
||||
d := s[i]
|
||||
if i > 0 && d >= 'A' && d <= 'Z' && j {
|
||||
data = append(data, '_')
|
||||
}
|
||||
if d != '_' {
|
||||
j = true
|
||||
}
|
||||
data = append(data, d)
|
||||
}
|
||||
return strings.ToLower(string(data))
|
||||
}
|
||||
|
||||
// SetNameStrategy set different name strategy
|
||||
func SetNameStrategy(s string) {
|
||||
if SnakeAcronymNameStrategy != s {
|
||||
nameStrategy = defaultNameStrategy
|
||||
}
|
||||
nameStrategy = s
|
||||
}
|
||||
|
||||
// camel string, xx_yy to XxYy
|
||||
func camelString(s string) string {
|
||||
data := make([]byte, 0, len(s))
|
||||
flag, num := true, len(s)-1
|
||||
for i := 0; i <= num; i++ {
|
||||
d := s[i]
|
||||
if d == '_' {
|
||||
flag = true
|
||||
continue
|
||||
} else if flag {
|
||||
if d >= 'a' && d <= 'z' {
|
||||
d = d - 32
|
||||
}
|
||||
flag = false
|
||||
}
|
||||
data = append(data, d)
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
type argString []string
|
||||
|
||||
// get string by index from string slice
|
||||
func (a argString) Get(i int, args ...string) (r string) {
|
||||
if i >= 0 && i < len(a) {
|
||||
r = a[i]
|
||||
} else if len(args) > 0 {
|
||||
r = args[0]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type argInt []int
|
||||
|
||||
// get int by index from int slice
|
||||
func (a argInt) Get(i int, args ...int) (r int) {
|
||||
if i >= 0 && i < len(a) {
|
||||
r = a[i]
|
||||
}
|
||||
if len(args) > 0 {
|
||||
r = args[0]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// parse time to string with location
|
||||
func timeParse(dateString, format string) (time.Time, error) {
|
||||
tp, err := time.ParseInLocation(format, dateString, DefaultTimeLoc)
|
||||
return tp, err
|
||||
}
|
||||
|
||||
// get pointer indirect type
|
||||
func indirectType(v reflect.Type) reflect.Type {
|
||||
switch v.Kind() {
|
||||
case reflect.Ptr:
|
||||
return indirectType(v.Elem())
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
## logs
|
||||
|
||||
logs is a Go logs manager. It can use many logs adapters. The repo is inspired by `database/sql` .
|
||||
|
||||
## How to install?
|
||||
|
||||
go get github.com/beego/beego/v2/core/logs
|
||||
|
||||
## What adapters are supported?
|
||||
|
||||
As of now this logs support console, file,smtp and conn.
|
||||
|
||||
## How to use it?
|
||||
|
||||
First you must import it
|
||||
|
||||
```golang
|
||||
import (
|
||||
"github.com/beego/beego/v2/core/logs"
|
||||
)
|
||||
```
|
||||
|
||||
Then init a Log (example with console adapter)
|
||||
|
||||
```golang
|
||||
log := logs.NewLogger(10000)
|
||||
log.SetLogger("console", "")
|
||||
```
|
||||
|
||||
> the first params stand for how many channel
|
||||
|
||||
Use it like this:
|
||||
|
||||
```golang
|
||||
log.Trace("trace")
|
||||
log.Info("info")
|
||||
log.Warn("warning")
|
||||
log.Debug("debug")
|
||||
log.Critical("critical")
|
||||
```
|
||||
|
||||
## File adapter
|
||||
|
||||
Configure file adapter like this:
|
||||
|
||||
```golang
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", `{"filename":"test.log"}`)
|
||||
```
|
||||
|
||||
## Conn adapter
|
||||
|
||||
Configure like this:
|
||||
|
||||
```golang
|
||||
log := NewLogger(1000)
|
||||
log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`)
|
||||
log.Info("info")
|
||||
```
|
||||
|
||||
## Smtp adapter
|
||||
|
||||
Configure like this:
|
||||
|
||||
```golang
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`)
|
||||
log.Critical("sendmail critical")
|
||||
time.Sleep(time.Second * 30)
|
||||
```
|
@ -1,93 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
apacheFormatPattern = "%s - - [%s] \"%s %d %d\" %f %s %s"
|
||||
apacheFormat = "APACHE_FORMAT"
|
||||
jsonFormat = "JSON_FORMAT"
|
||||
)
|
||||
|
||||
// AccessLogRecord is astruct for holding access log data.
|
||||
type AccessLogRecord struct {
|
||||
RemoteAddr string `json:"remote_addr"`
|
||||
RequestTime time.Time `json:"request_time"`
|
||||
RequestMethod string `json:"request_method"`
|
||||
Request string `json:"request"`
|
||||
ServerProtocol string `json:"server_protocol"`
|
||||
Host string `json:"host"`
|
||||
Status int `json:"status"`
|
||||
BodyBytesSent int64 `json:"body_bytes_sent"`
|
||||
ElapsedTime time.Duration `json:"elapsed_time"`
|
||||
HTTPReferrer string `json:"http_referrer"`
|
||||
HTTPUserAgent string `json:"http_user_agent"`
|
||||
RemoteUser string `json:"remote_user"`
|
||||
}
|
||||
|
||||
func (r *AccessLogRecord) json() ([]byte, error) {
|
||||
buffer := &bytes.Buffer{}
|
||||
encoder := json.NewEncoder(buffer)
|
||||
disableEscapeHTML(encoder)
|
||||
|
||||
err := encoder.Encode(r)
|
||||
return buffer.Bytes(), err
|
||||
}
|
||||
|
||||
func disableEscapeHTML(i interface{}) {
|
||||
if e, ok := i.(interface {
|
||||
SetEscapeHTML(bool)
|
||||
}); ok {
|
||||
e.SetEscapeHTML(false)
|
||||
}
|
||||
}
|
||||
|
||||
// AccessLog - Format and print access log.
|
||||
func AccessLog(r *AccessLogRecord, format string) {
|
||||
msg := r.format(format)
|
||||
lm := &LogMsg{
|
||||
Msg: strings.TrimSpace(msg),
|
||||
When: time.Now(),
|
||||
Level: levelLoggerImpl,
|
||||
}
|
||||
beeLogger.writeMsg(lm)
|
||||
}
|
||||
|
||||
func (r *AccessLogRecord) format(format string) string {
|
||||
msg := ""
|
||||
switch format {
|
||||
case apacheFormat:
|
||||
timeFormatted := r.RequestTime.Format("02/Jan/2006 03:04:05")
|
||||
msg = fmt.Sprintf(apacheFormatPattern, r.RemoteAddr, timeFormatted, r.Request, r.Status, r.BodyBytesSent,
|
||||
r.ElapsedTime.Seconds(), r.HTTPReferrer, r.HTTPUserAgent)
|
||||
case jsonFormat:
|
||||
fallthrough
|
||||
default:
|
||||
jsonData, err := r.json()
|
||||
if err != nil {
|
||||
msg = fmt.Sprintf(`{"Error": "%s"}`, err)
|
||||
} else {
|
||||
msg = string(jsonData)
|
||||
}
|
||||
}
|
||||
return msg
|
||||
}
|
@ -1,141 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// connWriter implements LoggerInterface.
|
||||
// Writes messages in keep-live tcp connection.
|
||||
type connWriter struct {
|
||||
lg *logWriter
|
||||
innerWriter io.WriteCloser
|
||||
formatter LogFormatter
|
||||
Formatter string `json:"formatter"`
|
||||
ReconnectOnMsg bool `json:"reconnectOnMsg"`
|
||||
Reconnect bool `json:"reconnect"`
|
||||
Net string `json:"net"`
|
||||
Addr string `json:"addr"`
|
||||
Level int `json:"level"`
|
||||
}
|
||||
|
||||
// NewConn creates new ConnWrite returning as LoggerInterface.
|
||||
func NewConn() Logger {
|
||||
conn := new(connWriter)
|
||||
conn.Level = LevelTrace
|
||||
conn.formatter = conn
|
||||
return conn
|
||||
}
|
||||
|
||||
func (c *connWriter) Format(lm *LogMsg) string {
|
||||
return lm.OldStyleFormat()
|
||||
}
|
||||
|
||||
// Init initializes a connection writer with json config.
|
||||
// json config only needs they "level" key
|
||||
func (c *connWriter) Init(config string) error {
|
||||
res := json.Unmarshal([]byte(config), c)
|
||||
if res == nil && len(c.Formatter) > 0 {
|
||||
fmtr, ok := GetFormatter(c.Formatter)
|
||||
if !ok {
|
||||
return errors.New(fmt.Sprintf("the formatter with name: %s not found", c.Formatter))
|
||||
}
|
||||
c.formatter = fmtr
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (c *connWriter) SetFormatter(f LogFormatter) {
|
||||
c.formatter = f
|
||||
}
|
||||
|
||||
// WriteMsg writes message in connection.
|
||||
// If connection is down, try to re-connect.
|
||||
func (c *connWriter) WriteMsg(lm *LogMsg) error {
|
||||
if lm.Level > c.Level {
|
||||
return nil
|
||||
}
|
||||
if c.needToConnectOnMsg() {
|
||||
err := c.connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if c.ReconnectOnMsg {
|
||||
defer c.innerWriter.Close()
|
||||
}
|
||||
|
||||
msg := c.formatter.Format(lm)
|
||||
|
||||
_, err := c.lg.writeln(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flush implementing method. empty.
|
||||
func (c *connWriter) Flush() {
|
||||
}
|
||||
|
||||
// Destroy destroy connection writer and close tcp listener.
|
||||
func (c *connWriter) Destroy() {
|
||||
if c.innerWriter != nil {
|
||||
c.innerWriter.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *connWriter) connect() error {
|
||||
if c.innerWriter != nil {
|
||||
c.innerWriter.Close()
|
||||
c.innerWriter = nil
|
||||
}
|
||||
|
||||
conn, err := net.Dial(c.Net, c.Addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tcpConn, ok := conn.(*net.TCPConn); ok {
|
||||
tcpConn.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
c.innerWriter = conn
|
||||
c.lg = newLogWriter(conn)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *connWriter) needToConnectOnMsg() bool {
|
||||
if c.Reconnect {
|
||||
return true
|
||||
}
|
||||
|
||||
if c.innerWriter == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return c.ReconnectOnMsg
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register(AdapterConn, NewConn)
|
||||
}
|
@ -1,125 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/shiena/ansicolor"
|
||||
)
|
||||
|
||||
// brush is a color join function
|
||||
type brush func(string) string
|
||||
|
||||
// newBrush returns a fix color Brush
|
||||
func newBrush(color string) brush {
|
||||
pre := "\033["
|
||||
reset := "\033[0m"
|
||||
return func(text string) string {
|
||||
return pre + color + "m" + text + reset
|
||||
}
|
||||
}
|
||||
|
||||
var colors = []brush{
|
||||
newBrush("1;37"), // Emergency white
|
||||
newBrush("1;36"), // Alert cyan
|
||||
newBrush("1;35"), // Critical magenta
|
||||
newBrush("1;31"), // Error red
|
||||
newBrush("1;33"), // Warning yellow
|
||||
newBrush("1;32"), // Notice green
|
||||
newBrush("1;34"), // Informational blue
|
||||
newBrush("1;44"), // Debug Background blue
|
||||
}
|
||||
|
||||
// consoleWriter implements LoggerInterface and writes messages to terminal.
|
||||
type consoleWriter struct {
|
||||
lg *logWriter
|
||||
formatter LogFormatter
|
||||
Formatter string `json:"formatter"`
|
||||
Level int `json:"level"`
|
||||
Colorful bool `json:"color"` // this filed is useful only when system's terminal supports color
|
||||
}
|
||||
|
||||
func (c *consoleWriter) Format(lm *LogMsg) string {
|
||||
msg := lm.OldStyleFormat()
|
||||
if c.Colorful {
|
||||
msg = strings.Replace(msg, levelPrefix[lm.Level], colors[lm.Level](levelPrefix[lm.Level]), 1)
|
||||
}
|
||||
h, _, _ := formatTimeHeader(lm.When)
|
||||
return string(append(h, msg...))
|
||||
}
|
||||
|
||||
func (c *consoleWriter) SetFormatter(f LogFormatter) {
|
||||
c.formatter = f
|
||||
}
|
||||
|
||||
// NewConsole creates ConsoleWriter returning as LoggerInterface.
|
||||
func NewConsole() Logger {
|
||||
return newConsole()
|
||||
}
|
||||
|
||||
func newConsole() *consoleWriter {
|
||||
cw := &consoleWriter{
|
||||
lg: newLogWriter(ansicolor.NewAnsiColorWriter(os.Stdout)),
|
||||
Level: LevelDebug,
|
||||
Colorful: true,
|
||||
}
|
||||
cw.formatter = cw
|
||||
return cw
|
||||
}
|
||||
|
||||
// Init initianlizes the console logger.
|
||||
// jsonConfig must be in the format '{"level":LevelTrace}'
|
||||
func (c *consoleWriter) Init(config string) error {
|
||||
if len(config) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
res := json.Unmarshal([]byte(config), c)
|
||||
if res == nil && len(c.Formatter) > 0 {
|
||||
fmtr, ok := GetFormatter(c.Formatter)
|
||||
if !ok {
|
||||
return errors.New(fmt.Sprintf("the formatter with name: %s not found", c.Formatter))
|
||||
}
|
||||
c.formatter = fmtr
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// WriteMsg writes message in console.
|
||||
func (c *consoleWriter) WriteMsg(lm *LogMsg) error {
|
||||
if lm.Level > c.Level {
|
||||
return nil
|
||||
}
|
||||
msg := c.formatter.Format(lm)
|
||||
c.lg.writeln(msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroy implementing method. empty.
|
||||
func (c *consoleWriter) Destroy() {
|
||||
}
|
||||
|
||||
// Flush implementing method. empty.
|
||||
func (c *consoleWriter) Flush() {
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register(AdapterConsole, NewConsole)
|
||||
}
|
@ -1,442 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// fileLogWriter implements LoggerInterface.
|
||||
// Writes messages by lines limit, file size limit, or time frequency.
|
||||
type fileLogWriter struct {
|
||||
sync.RWMutex // write log order by order and atomic incr maxLinesCurLines and maxSizeCurSize
|
||||
|
||||
Rotate bool `json:"rotate"`
|
||||
Daily bool `json:"daily"`
|
||||
Hourly bool `json:"hourly"`
|
||||
|
||||
// The opened file
|
||||
Filename string `json:"filename"`
|
||||
fileWriter *os.File
|
||||
|
||||
// Rotate at line
|
||||
MaxLines int `json:"maxlines"`
|
||||
maxLinesCurLines int
|
||||
|
||||
MaxFiles int `json:"maxfiles"`
|
||||
MaxFilesCurFiles int
|
||||
|
||||
// Rotate at size
|
||||
MaxSize int `json:"maxsize"`
|
||||
maxSizeCurSize int
|
||||
|
||||
// Rotate daily
|
||||
MaxDays int64 `json:"maxdays"`
|
||||
dailyOpenDate int
|
||||
dailyOpenTime time.Time
|
||||
|
||||
// Rotate hourly
|
||||
MaxHours int64 `json:"maxhours"`
|
||||
hourlyOpenDate int
|
||||
hourlyOpenTime time.Time
|
||||
|
||||
Level int `json:"level"`
|
||||
// Permissions for log file
|
||||
Perm string `json:"perm"`
|
||||
// Permissions for directory if it is specified in FileName
|
||||
DirPerm string `json:"dirperm"`
|
||||
|
||||
RotatePerm string `json:"rotateperm"`
|
||||
|
||||
fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix
|
||||
|
||||
logFormatter LogFormatter
|
||||
Formatter string `json:"formatter"`
|
||||
}
|
||||
|
||||
// newFileWriter creates a FileLogWriter returning as LoggerInterface.
|
||||
func newFileWriter() Logger {
|
||||
w := &fileLogWriter{
|
||||
Daily: true,
|
||||
MaxDays: 7,
|
||||
Hourly: false,
|
||||
MaxHours: 168,
|
||||
Rotate: true,
|
||||
RotatePerm: "0440",
|
||||
Level: LevelTrace,
|
||||
Perm: "0660",
|
||||
DirPerm: "0770",
|
||||
MaxLines: 10000000,
|
||||
MaxFiles: 999,
|
||||
MaxSize: 1 << 28,
|
||||
}
|
||||
w.logFormatter = w
|
||||
return w
|
||||
}
|
||||
|
||||
func (*fileLogWriter) Format(lm *LogMsg) string {
|
||||
msg := lm.OldStyleFormat()
|
||||
hd, _, _ := formatTimeHeader(lm.When)
|
||||
msg = fmt.Sprintf("%s %s\n", string(hd), msg)
|
||||
return msg
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) SetFormatter(f LogFormatter) {
|
||||
w.logFormatter = f
|
||||
}
|
||||
|
||||
// Init file logger with json config.
|
||||
// jsonConfig like:
|
||||
// {
|
||||
// "filename":"logs/beego.log",
|
||||
// "maxLines":10000,
|
||||
// "maxsize":1024,
|
||||
// "daily":true,
|
||||
// "maxDays":15,
|
||||
// "rotate":true,
|
||||
// "perm":"0600"
|
||||
// }
|
||||
func (w *fileLogWriter) Init(config string) error {
|
||||
err := json.Unmarshal([]byte(config), w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if w.Filename == "" {
|
||||
return errors.New("jsonconfig must have filename")
|
||||
}
|
||||
w.suffix = filepath.Ext(w.Filename)
|
||||
w.fileNameOnly = strings.TrimSuffix(w.Filename, w.suffix)
|
||||
if w.suffix == "" {
|
||||
w.suffix = ".log"
|
||||
}
|
||||
|
||||
if len(w.Formatter) > 0 {
|
||||
fmtr, ok := GetFormatter(w.Formatter)
|
||||
if !ok {
|
||||
return fmt.Errorf("the formatter with name: %s not found", w.Formatter)
|
||||
}
|
||||
w.logFormatter = fmtr
|
||||
}
|
||||
err = w.startLogger()
|
||||
return err
|
||||
}
|
||||
|
||||
// start file logger. create log file and set to locker-inside file writer.
|
||||
func (w *fileLogWriter) startLogger() error {
|
||||
file, err := w.createLogFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if w.fileWriter != nil {
|
||||
w.fileWriter.Close()
|
||||
}
|
||||
w.fileWriter = file
|
||||
return w.initFd()
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) needRotateDaily(day int) bool {
|
||||
return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
|
||||
(w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
|
||||
(w.Daily && day != w.dailyOpenDate)
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) needRotateHourly(hour int) bool {
|
||||
return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
|
||||
(w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
|
||||
(w.Hourly && hour != w.hourlyOpenDate)
|
||||
}
|
||||
|
||||
// WriteMsg writes logger message into file.
|
||||
func (w *fileLogWriter) WriteMsg(lm *LogMsg) error {
|
||||
if lm.Level > w.Level {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, d, h := formatTimeHeader(lm.When)
|
||||
|
||||
msg := w.logFormatter.Format(lm)
|
||||
if w.Rotate {
|
||||
w.RLock()
|
||||
if w.needRotateHourly(h) {
|
||||
w.RUnlock()
|
||||
w.Lock()
|
||||
if w.needRotateHourly(h) {
|
||||
if err := w.doRotate(lm.When); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||
}
|
||||
}
|
||||
w.Unlock()
|
||||
} else if w.needRotateDaily(d) {
|
||||
w.RUnlock()
|
||||
w.Lock()
|
||||
if w.needRotateDaily(d) {
|
||||
if err := w.doRotate(lm.When); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||
}
|
||||
}
|
||||
w.Unlock()
|
||||
} else {
|
||||
w.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
w.Lock()
|
||||
_, err := w.fileWriter.Write([]byte(msg))
|
||||
if err == nil {
|
||||
w.maxLinesCurLines++
|
||||
w.maxSizeCurSize += len(msg)
|
||||
}
|
||||
w.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) createLogFile() (*os.File, error) {
|
||||
// Open the log file
|
||||
perm, err := strconv.ParseInt(w.Perm, 8, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dirperm, err := strconv.ParseInt(w.DirPerm, 8, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filepath := path.Dir(w.Filename)
|
||||
os.MkdirAll(filepath, os.FileMode(dirperm))
|
||||
|
||||
fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm))
|
||||
if err == nil {
|
||||
// Make sure file perm is user set perm cause of `os.OpenFile` will obey umask
|
||||
os.Chmod(w.Filename, os.FileMode(perm))
|
||||
}
|
||||
return fd, err
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) initFd() error {
|
||||
fd := w.fileWriter
|
||||
fInfo, err := fd.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get stat err: %s", err)
|
||||
}
|
||||
w.maxSizeCurSize = int(fInfo.Size())
|
||||
w.dailyOpenTime = time.Now()
|
||||
w.dailyOpenDate = w.dailyOpenTime.Day()
|
||||
w.hourlyOpenTime = time.Now()
|
||||
w.hourlyOpenDate = w.hourlyOpenTime.Hour()
|
||||
w.maxLinesCurLines = 0
|
||||
if w.Hourly {
|
||||
go w.hourlyRotate(w.hourlyOpenTime)
|
||||
} else if w.Daily {
|
||||
go w.dailyRotate(w.dailyOpenTime)
|
||||
}
|
||||
if fInfo.Size() > 0 && w.MaxLines > 0 {
|
||||
count, err := w.lines()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.maxLinesCurLines = count
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) dailyRotate(openTime time.Time) {
|
||||
y, m, d := openTime.Add(24 * time.Hour).Date()
|
||||
nextDay := time.Date(y, m, d, 0, 0, 0, 0, openTime.Location())
|
||||
tm := time.NewTimer(time.Duration(nextDay.UnixNano() - openTime.UnixNano() + 100))
|
||||
<-tm.C
|
||||
w.Lock()
|
||||
if w.needRotateDaily(time.Now().Day()) {
|
||||
if err := w.doRotate(time.Now()); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||
}
|
||||
}
|
||||
w.Unlock()
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) hourlyRotate(openTime time.Time) {
|
||||
y, m, d := openTime.Add(1 * time.Hour).Date()
|
||||
h, _, _ := openTime.Add(1 * time.Hour).Clock()
|
||||
nextHour := time.Date(y, m, d, h, 0, 0, 0, openTime.Location())
|
||||
tm := time.NewTimer(time.Duration(nextHour.UnixNano() - openTime.UnixNano() + 100))
|
||||
<-tm.C
|
||||
w.Lock()
|
||||
if w.needRotateHourly(time.Now().Hour()) {
|
||||
if err := w.doRotate(time.Now()); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||
}
|
||||
}
|
||||
w.Unlock()
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) lines() (int, error) {
|
||||
fd, err := os.Open(w.Filename)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
buf := make([]byte, 32768) // 32k
|
||||
count := 0
|
||||
lineSep := []byte{'\n'}
|
||||
|
||||
for {
|
||||
c, err := fd.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
return count, err
|
||||
}
|
||||
|
||||
count += bytes.Count(buf[:c], lineSep)
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// DoRotate means it needs to write logs into a new file.
|
||||
// new file name like xx.2013-01-01.log (daily) or xx.001.log (by line or size)
|
||||
func (w *fileLogWriter) doRotate(logTime time.Time) error {
|
||||
// file exists
|
||||
// Find the next available number
|
||||
num := w.MaxFilesCurFiles + 1
|
||||
fName := ""
|
||||
format := ""
|
||||
var openTime time.Time
|
||||
rotatePerm, err := strconv.ParseInt(w.RotatePerm, 8, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = os.Lstat(w.Filename)
|
||||
if err != nil {
|
||||
// even if the file is not exist or other ,we should RESTART the logger
|
||||
goto RESTART_LOGGER
|
||||
}
|
||||
|
||||
if w.Hourly {
|
||||
format = "2006010215"
|
||||
openTime = w.hourlyOpenTime
|
||||
} else if w.Daily {
|
||||
format = "2006-01-02"
|
||||
openTime = w.dailyOpenTime
|
||||
}
|
||||
|
||||
// only when one of them be setted, then the file would be splited
|
||||
if w.MaxLines > 0 || w.MaxSize > 0 {
|
||||
for ; err == nil && num <= w.MaxFiles; num++ {
|
||||
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format(format), num, w.suffix)
|
||||
_, err = os.Lstat(fName)
|
||||
}
|
||||
} else {
|
||||
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", openTime.Format(format), num, w.suffix)
|
||||
_, err = os.Lstat(fName)
|
||||
w.MaxFilesCurFiles = num
|
||||
}
|
||||
|
||||
// return error if the last file checked still existed
|
||||
if err == nil {
|
||||
return fmt.Errorf("Rotate: Cannot find free log number to rename %s", w.Filename)
|
||||
}
|
||||
|
||||
// close fileWriter before rename
|
||||
w.fileWriter.Close()
|
||||
|
||||
// Rename the file to its new found name
|
||||
// even if occurs error,we MUST guarantee to restart new logger
|
||||
err = os.Rename(w.Filename, fName)
|
||||
if err != nil {
|
||||
goto RESTART_LOGGER
|
||||
}
|
||||
|
||||
err = os.Chmod(fName, os.FileMode(rotatePerm))
|
||||
|
||||
RESTART_LOGGER:
|
||||
|
||||
startLoggerErr := w.startLogger()
|
||||
go w.deleteOldLog()
|
||||
|
||||
if startLoggerErr != nil {
|
||||
return fmt.Errorf("Rotate StartLogger: %s", startLoggerErr)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("Rotate: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) deleteOldLog() {
|
||||
dir := filepath.Dir(w.Filename)
|
||||
absolutePath, err := filepath.EvalSymlinks(w.Filename)
|
||||
if err == nil {
|
||||
dir = filepath.Dir(absolutePath)
|
||||
}
|
||||
filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to delete old log '%s', error: %v\n", path, r)
|
||||
}
|
||||
}()
|
||||
|
||||
if info == nil {
|
||||
return
|
||||
}
|
||||
if w.Hourly {
|
||||
if !info.IsDir() && info.ModTime().Add(1*time.Hour*time.Duration(w.MaxHours)).Before(time.Now()) {
|
||||
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
|
||||
strings.HasSuffix(filepath.Base(path), w.suffix) {
|
||||
os.Remove(path)
|
||||
}
|
||||
}
|
||||
} else if w.Daily {
|
||||
if !info.IsDir() && info.ModTime().Add(24*time.Hour*time.Duration(w.MaxDays)).Before(time.Now()) {
|
||||
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
|
||||
strings.HasSuffix(filepath.Base(path), w.suffix) {
|
||||
os.Remove(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// Destroy close the file description, close file writer.
|
||||
func (w *fileLogWriter) Destroy() {
|
||||
w.fileWriter.Close()
|
||||
}
|
||||
|
||||
// Flush flushes file logger.
|
||||
// there are no buffering messages in file logger in memory.
|
||||
// flush file means sync file from disk.
|
||||
func (w *fileLogWriter) Flush() {
|
||||
w.fileWriter.Sync()
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register(AdapterFile, newFileWriter)
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// JLWriter implements beego LoggerInterface and is used to send jiaoliao webhook
|
||||
type JLWriter struct {
|
||||
AuthorName string `json:"authorname"`
|
||||
Title string `json:"title"`
|
||||
WebhookURL string `json:"webhookurl"`
|
||||
RedirectURL string `json:"redirecturl,omitempty"`
|
||||
ImageURL string `json:"imageurl,omitempty"`
|
||||
Level int `json:"level"`
|
||||
|
||||
formatter LogFormatter
|
||||
Formatter string `json:"formatter"`
|
||||
}
|
||||
|
||||
// newJLWriter creates jiaoliao writer.
|
||||
func newJLWriter() Logger {
|
||||
res := &JLWriter{Level: LevelTrace}
|
||||
res.formatter = res
|
||||
return res
|
||||
}
|
||||
|
||||
// Init JLWriter with json config string
|
||||
func (s *JLWriter) Init(config string) error {
|
||||
res := json.Unmarshal([]byte(config), s)
|
||||
if res == nil && len(s.Formatter) > 0 {
|
||||
fmtr, ok := GetFormatter(s.Formatter)
|
||||
if !ok {
|
||||
return errors.New(fmt.Sprintf("the formatter with name: %s not found", s.Formatter))
|
||||
}
|
||||
s.formatter = fmtr
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *JLWriter) Format(lm *LogMsg) string {
|
||||
msg := lm.OldStyleFormat()
|
||||
msg = fmt.Sprintf("%s %s", lm.When.Format("2006-01-02 15:04:05"), msg)
|
||||
return msg
|
||||
}
|
||||
|
||||
func (s *JLWriter) SetFormatter(f LogFormatter) {
|
||||
s.formatter = f
|
||||
}
|
||||
|
||||
// WriteMsg writes message in smtp writer.
|
||||
// Sends an email with subject and only this message.
|
||||
func (s *JLWriter) WriteMsg(lm *LogMsg) error {
|
||||
if lm.Level > s.Level {
|
||||
return nil
|
||||
}
|
||||
|
||||
text := s.formatter.Format(lm)
|
||||
|
||||
form := url.Values{}
|
||||
form.Add("authorName", s.AuthorName)
|
||||
form.Add("title", s.Title)
|
||||
form.Add("text", text)
|
||||
if s.RedirectURL != "" {
|
||||
form.Add("redirectUrl", s.RedirectURL)
|
||||
}
|
||||
if s.ImageURL != "" {
|
||||
form.Add("imageUrl", s.ImageURL)
|
||||
}
|
||||
|
||||
resp, err := http.PostForm(s.WebhookURL, form)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flush implementing method. empty.
|
||||
func (s *JLWriter) Flush() {
|
||||
}
|
||||
|
||||
// Destroy implementing method. empty.
|
||||
func (s *JLWriter) Destroy() {
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register(AdapterJianLiao, newJLWriter)
|
||||
}
|
@ -1,782 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package logs provide a general log interface
|
||||
// Usage:
|
||||
//
|
||||
// import "github.com/beego/beego/v2/core/logs"
|
||||
//
|
||||
// log := NewLogger(10000)
|
||||
// log.SetLogger("console", "")
|
||||
//
|
||||
// > the first params stand for how many channel
|
||||
//
|
||||
// Use it like this:
|
||||
//
|
||||
// log.Trace("trace")
|
||||
// log.Info("info")
|
||||
// log.Warn("warning")
|
||||
// log.Debug("debug")
|
||||
// log.Critical("critical")
|
||||
//
|
||||
// more docs http://beego.vip/docs/module/logs.md
|
||||
package logs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// RFC5424 log message levels.
|
||||
const (
|
||||
LevelEmergency = iota
|
||||
LevelAlert
|
||||
LevelCritical
|
||||
LevelError
|
||||
LevelWarning
|
||||
LevelNotice
|
||||
LevelInformational
|
||||
LevelDebug
|
||||
)
|
||||
|
||||
// levelLogLogger is defined to implement log.Logger
|
||||
// the real log level will be LevelEmergency
|
||||
const levelLoggerImpl = -1
|
||||
|
||||
// Name for adapter with beego official support
|
||||
const (
|
||||
AdapterConsole = "console"
|
||||
AdapterFile = "file"
|
||||
AdapterMultiFile = "multifile"
|
||||
AdapterMail = "smtp"
|
||||
AdapterConn = "conn"
|
||||
AdapterEs = "es"
|
||||
AdapterJianLiao = "jianliao"
|
||||
AdapterSlack = "slack"
|
||||
AdapterAliLS = "alils"
|
||||
)
|
||||
|
||||
// Legacy log level constants to ensure backwards compatibility.
|
||||
const (
|
||||
LevelInfo = LevelInformational
|
||||
LevelTrace = LevelDebug
|
||||
LevelWarn = LevelWarning
|
||||
)
|
||||
|
||||
type newLoggerFunc func() Logger
|
||||
|
||||
// Logger defines the behavior of a log provider.
|
||||
type Logger interface {
|
||||
Init(config string) error
|
||||
WriteMsg(lm *LogMsg) error
|
||||
Destroy()
|
||||
Flush()
|
||||
SetFormatter(f LogFormatter)
|
||||
}
|
||||
|
||||
var (
|
||||
adapters = make(map[string]newLoggerFunc)
|
||||
levelPrefix = [LevelDebug + 1]string{"[M]", "[A]", "[C]", "[E]", "[W]", "[N]", "[I]", "[D]"}
|
||||
)
|
||||
|
||||
// Register makes a log provide available by the provided name.
|
||||
// If Register is called twice with the same name or if driver is nil,
|
||||
// it panics.
|
||||
func Register(name string, log newLoggerFunc) {
|
||||
if log == nil {
|
||||
panic("logs: Register provide is nil")
|
||||
}
|
||||
if _, dup := adapters[name]; dup {
|
||||
panic("logs: Register called twice for provider " + name)
|
||||
}
|
||||
adapters[name] = log
|
||||
}
|
||||
|
||||
// BeeLogger is default logger in beego application.
|
||||
// Can contain several providers and log message into all providers.
|
||||
type BeeLogger struct {
|
||||
lock sync.Mutex
|
||||
init bool
|
||||
enableFuncCallDepth bool
|
||||
enableFullFilePath bool
|
||||
asynchronous bool
|
||||
wg sync.WaitGroup
|
||||
level int
|
||||
loggerFuncCallDepth int
|
||||
prefix string
|
||||
msgChanLen int64
|
||||
msgChan chan *LogMsg
|
||||
signalChan chan string
|
||||
outputs []*nameLogger
|
||||
globalFormatter string
|
||||
}
|
||||
|
||||
const defaultAsyncMsgLen = 1e3
|
||||
|
||||
type nameLogger struct {
|
||||
Logger
|
||||
name string
|
||||
}
|
||||
|
||||
var logMsgPool *sync.Pool
|
||||
|
||||
// NewLogger returns a new BeeLogger.
|
||||
// channelLen: the number of messages in chan(used where asynchronous is true).
|
||||
// if the buffering chan is full, logger adapters write to file or other way.
|
||||
func NewLogger(channelLens ...int64) *BeeLogger {
|
||||
bl := new(BeeLogger)
|
||||
bl.level = LevelDebug
|
||||
bl.loggerFuncCallDepth = 3
|
||||
bl.msgChanLen = append(channelLens, 0)[0]
|
||||
if bl.msgChanLen <= 0 {
|
||||
bl.msgChanLen = defaultAsyncMsgLen
|
||||
}
|
||||
bl.signalChan = make(chan string, 1)
|
||||
bl.setLogger(AdapterConsole)
|
||||
return bl
|
||||
}
|
||||
|
||||
// Async sets the log to asynchronous and start the goroutine
|
||||
func (bl *BeeLogger) Async(msgLen ...int64) *BeeLogger {
|
||||
bl.lock.Lock()
|
||||
defer bl.lock.Unlock()
|
||||
if bl.asynchronous {
|
||||
return bl
|
||||
}
|
||||
bl.asynchronous = true
|
||||
if len(msgLen) > 0 && msgLen[0] > 0 {
|
||||
bl.msgChanLen = msgLen[0]
|
||||
}
|
||||
bl.msgChan = make(chan *LogMsg, bl.msgChanLen)
|
||||
logMsgPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &LogMsg{}
|
||||
},
|
||||
}
|
||||
bl.wg.Add(1)
|
||||
go bl.startLogger()
|
||||
return bl
|
||||
}
|
||||
|
||||
// SetLogger provides a given logger adapter into BeeLogger with config string.
|
||||
// config must in in JSON format like {"interval":360}}
|
||||
func (bl *BeeLogger) setLogger(adapterName string, configs ...string) error {
|
||||
config := append(configs, "{}")[0]
|
||||
for _, l := range bl.outputs {
|
||||
if l.name == adapterName {
|
||||
return fmt.Errorf("logs: duplicate adaptername %q (you have set this logger before)", adapterName)
|
||||
}
|
||||
}
|
||||
|
||||
logAdapter, ok := adapters[adapterName]
|
||||
if !ok {
|
||||
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName)
|
||||
}
|
||||
|
||||
lg := logAdapter()
|
||||
|
||||
// Global formatter overrides the default set formatter
|
||||
if len(bl.globalFormatter) > 0 {
|
||||
fmtr, ok := GetFormatter(bl.globalFormatter)
|
||||
if !ok {
|
||||
return errors.New(fmt.Sprintf("the formatter with name: %s not found", bl.globalFormatter))
|
||||
}
|
||||
lg.SetFormatter(fmtr)
|
||||
}
|
||||
|
||||
err := lg.Init(config)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "logs.BeeLogger.SetLogger: "+err.Error())
|
||||
return err
|
||||
}
|
||||
bl.outputs = append(bl.outputs, &nameLogger{name: adapterName, Logger: lg})
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetLogger provides a given logger adapter into BeeLogger with config string.
|
||||
// config must in in JSON format like {"interval":360}}
|
||||
func (bl *BeeLogger) SetLogger(adapterName string, configs ...string) error {
|
||||
bl.lock.Lock()
|
||||
defer bl.lock.Unlock()
|
||||
if !bl.init {
|
||||
bl.outputs = []*nameLogger{}
|
||||
bl.init = true
|
||||
}
|
||||
return bl.setLogger(adapterName, configs...)
|
||||
}
|
||||
|
||||
// DelLogger removes a logger adapter in BeeLogger.
|
||||
func (bl *BeeLogger) DelLogger(adapterName string) error {
|
||||
bl.lock.Lock()
|
||||
defer bl.lock.Unlock()
|
||||
outputs := []*nameLogger{}
|
||||
for _, lg := range bl.outputs {
|
||||
if lg.name == adapterName {
|
||||
lg.Destroy()
|
||||
} else {
|
||||
outputs = append(outputs, lg)
|
||||
}
|
||||
}
|
||||
if len(outputs) == len(bl.outputs) {
|
||||
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName)
|
||||
}
|
||||
bl.outputs = outputs
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) writeToLoggers(lm *LogMsg) {
|
||||
for _, l := range bl.outputs {
|
||||
err := l.WriteMsg(lm)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) Write(p []byte) (n int, err error) {
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
// writeMsg will always add a '\n' character
|
||||
if p[len(p)-1] == '\n' {
|
||||
p = p[0 : len(p)-1]
|
||||
}
|
||||
lm := &LogMsg{
|
||||
Msg: string(p),
|
||||
Level: levelLoggerImpl,
|
||||
When: time.Now(),
|
||||
}
|
||||
|
||||
// set levelLoggerImpl to ensure all log message will be write out
|
||||
err = bl.writeMsg(lm)
|
||||
if err == nil {
|
||||
return len(p), nil
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) writeMsg(lm *LogMsg) error {
|
||||
if !bl.init {
|
||||
bl.lock.Lock()
|
||||
bl.setLogger(AdapterConsole)
|
||||
bl.lock.Unlock()
|
||||
}
|
||||
|
||||
var (
|
||||
file string
|
||||
line int
|
||||
ok bool
|
||||
)
|
||||
|
||||
_, file, line, ok = runtime.Caller(bl.loggerFuncCallDepth)
|
||||
if !ok {
|
||||
file = "???"
|
||||
line = 0
|
||||
}
|
||||
lm.FilePath = file
|
||||
lm.LineNumber = line
|
||||
lm.Prefix = bl.prefix
|
||||
|
||||
lm.enableFullFilePath = bl.enableFullFilePath
|
||||
lm.enableFuncCallDepth = bl.enableFuncCallDepth
|
||||
|
||||
// set level info in front of filename info
|
||||
if lm.Level == levelLoggerImpl {
|
||||
// set to emergency to ensure all log will be print out correctly
|
||||
lm.Level = LevelEmergency
|
||||
}
|
||||
|
||||
if bl.asynchronous {
|
||||
logM := logMsgPool.Get().(*LogMsg)
|
||||
logM.Level = lm.Level
|
||||
logM.Msg = lm.Msg
|
||||
logM.When = lm.When
|
||||
logM.Args = lm.Args
|
||||
logM.FilePath = lm.FilePath
|
||||
logM.LineNumber = lm.LineNumber
|
||||
logM.Prefix = lm.Prefix
|
||||
if bl.outputs != nil {
|
||||
bl.msgChan <- lm
|
||||
} else {
|
||||
logMsgPool.Put(lm)
|
||||
}
|
||||
} else {
|
||||
bl.writeToLoggers(lm)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetLevel sets log message level.
|
||||
// If message level (such as LevelDebug) is higher than logger level (such as LevelWarning),
|
||||
// log providers will not be sent the message.
|
||||
func (bl *BeeLogger) SetLevel(l int) {
|
||||
bl.level = l
|
||||
}
|
||||
|
||||
// GetLevel Get Current log message level.
|
||||
func (bl *BeeLogger) GetLevel() int {
|
||||
return bl.level
|
||||
}
|
||||
|
||||
// SetLogFuncCallDepth set log funcCallDepth
|
||||
func (bl *BeeLogger) SetLogFuncCallDepth(d int) {
|
||||
bl.loggerFuncCallDepth = d
|
||||
}
|
||||
|
||||
// GetLogFuncCallDepth return log funcCallDepth for wrapper
|
||||
func (bl *BeeLogger) GetLogFuncCallDepth() int {
|
||||
return bl.loggerFuncCallDepth
|
||||
}
|
||||
|
||||
// EnableFuncCallDepth enable log funcCallDepth
|
||||
func (bl *BeeLogger) EnableFuncCallDepth(b bool) {
|
||||
bl.enableFuncCallDepth = b
|
||||
}
|
||||
|
||||
// set prefix
|
||||
func (bl *BeeLogger) SetPrefix(s string) {
|
||||
bl.prefix = s
|
||||
}
|
||||
|
||||
// start logger chan reading.
|
||||
// when chan is not empty, write logs.
|
||||
func (bl *BeeLogger) startLogger() {
|
||||
gameOver := false
|
||||
for {
|
||||
select {
|
||||
case bm := <-bl.msgChan:
|
||||
bl.writeToLoggers(bm)
|
||||
logMsgPool.Put(bm)
|
||||
case sg := <-bl.signalChan:
|
||||
// Now should only send "flush" or "close" to bl.signalChan
|
||||
bl.flush()
|
||||
if sg == "close" {
|
||||
for _, l := range bl.outputs {
|
||||
l.Destroy()
|
||||
}
|
||||
bl.outputs = nil
|
||||
gameOver = true
|
||||
}
|
||||
bl.wg.Done()
|
||||
}
|
||||
if gameOver {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) setGlobalFormatter(fmtter string) error {
|
||||
bl.globalFormatter = fmtter
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetGlobalFormatter sets the global formatter for all log adapters
|
||||
// don't forget to register the formatter by invoking RegisterFormatter
|
||||
func SetGlobalFormatter(fmtter string) error {
|
||||
return beeLogger.setGlobalFormatter(fmtter)
|
||||
}
|
||||
|
||||
// Emergency Log EMERGENCY level message.
|
||||
func (bl *BeeLogger) Emergency(format string, v ...interface{}) {
|
||||
if LevelEmergency > bl.level {
|
||||
return
|
||||
}
|
||||
|
||||
lm := &LogMsg{
|
||||
Level: LevelEmergency,
|
||||
Msg: format,
|
||||
When: time.Now(),
|
||||
}
|
||||
if len(v) > 0 {
|
||||
lm.Msg = fmt.Sprintf(lm.Msg, v...)
|
||||
}
|
||||
|
||||
bl.writeMsg(lm)
|
||||
}
|
||||
|
||||
// Alert Log ALERT level message.
|
||||
func (bl *BeeLogger) Alert(format string, v ...interface{}) {
|
||||
if LevelAlert > bl.level {
|
||||
return
|
||||
}
|
||||
|
||||
lm := &LogMsg{
|
||||
Level: LevelAlert,
|
||||
Msg: format,
|
||||
When: time.Now(),
|
||||
Args: v,
|
||||
}
|
||||
bl.writeMsg(lm)
|
||||
}
|
||||
|
||||
// Critical Log CRITICAL level message.
|
||||
func (bl *BeeLogger) Critical(format string, v ...interface{}) {
|
||||
if LevelCritical > bl.level {
|
||||
return
|
||||
}
|
||||
lm := &LogMsg{
|
||||
Level: LevelCritical,
|
||||
Msg: format,
|
||||
When: time.Now(),
|
||||
Args: v,
|
||||
}
|
||||
|
||||
bl.writeMsg(lm)
|
||||
}
|
||||
|
||||
// Error Log ERROR level message.
|
||||
func (bl *BeeLogger) Error(format string, v ...interface{}) {
|
||||
if LevelError > bl.level {
|
||||
return
|
||||
}
|
||||
lm := &LogMsg{
|
||||
Level: LevelError,
|
||||
Msg: format,
|
||||
When: time.Now(),
|
||||
Args: v,
|
||||
}
|
||||
|
||||
bl.writeMsg(lm)
|
||||
}
|
||||
|
||||
// Warning Log WARNING level message.
|
||||
func (bl *BeeLogger) Warning(format string, v ...interface{}) {
|
||||
if LevelWarn > bl.level {
|
||||
return
|
||||
}
|
||||
lm := &LogMsg{
|
||||
Level: LevelWarn,
|
||||
Msg: format,
|
||||
When: time.Now(),
|
||||
Args: v,
|
||||
}
|
||||
|
||||
bl.writeMsg(lm)
|
||||
}
|
||||
|
||||
// Notice Log NOTICE level message.
|
||||
func (bl *BeeLogger) Notice(format string, v ...interface{}) {
|
||||
if LevelNotice > bl.level {
|
||||
return
|
||||
}
|
||||
lm := &LogMsg{
|
||||
Level: LevelNotice,
|
||||
Msg: format,
|
||||
When: time.Now(),
|
||||
Args: v,
|
||||
}
|
||||
|
||||
bl.writeMsg(lm)
|
||||
}
|
||||
|
||||
// Informational Log INFORMATIONAL level message.
|
||||
func (bl *BeeLogger) Informational(format string, v ...interface{}) {
|
||||
if LevelInfo > bl.level {
|
||||
return
|
||||
}
|
||||
lm := &LogMsg{
|
||||
Level: LevelInfo,
|
||||
Msg: format,
|
||||
When: time.Now(),
|
||||
Args: v,
|
||||
}
|
||||
|
||||
bl.writeMsg(lm)
|
||||
}
|
||||
|
||||
// Debug Log DEBUG level message.
|
||||
func (bl *BeeLogger) Debug(format string, v ...interface{}) {
|
||||
if LevelDebug > bl.level {
|
||||
return
|
||||
}
|
||||
lm := &LogMsg{
|
||||
Level: LevelDebug,
|
||||
Msg: format,
|
||||
When: time.Now(),
|
||||
Args: v,
|
||||
}
|
||||
|
||||
bl.writeMsg(lm)
|
||||
}
|
||||
|
||||
// Warn Log WARN level message.
|
||||
// compatibility alias for Warning()
|
||||
func (bl *BeeLogger) Warn(format string, v ...interface{}) {
|
||||
if LevelWarn > bl.level {
|
||||
return
|
||||
}
|
||||
lm := &LogMsg{
|
||||
Level: LevelWarn,
|
||||
Msg: format,
|
||||
When: time.Now(),
|
||||
Args: v,
|
||||
}
|
||||
|
||||
bl.writeMsg(lm)
|
||||
}
|
||||
|
||||
// Info Log INFO level message.
|
||||
// compatibility alias for Informational()
|
||||
func (bl *BeeLogger) Info(format string, v ...interface{}) {
|
||||
if LevelInfo > bl.level {
|
||||
return
|
||||
}
|
||||
lm := &LogMsg{
|
||||
Level: LevelInfo,
|
||||
Msg: format,
|
||||
When: time.Now(),
|
||||
Args: v,
|
||||
}
|
||||
|
||||
bl.writeMsg(lm)
|
||||
}
|
||||
|
||||
// Trace Log TRACE level message.
|
||||
// compatibility alias for Debug()
|
||||
func (bl *BeeLogger) Trace(format string, v ...interface{}) {
|
||||
if LevelDebug > bl.level {
|
||||
return
|
||||
}
|
||||
lm := &LogMsg{
|
||||
Level: LevelDebug,
|
||||
Msg: format,
|
||||
When: time.Now(),
|
||||
Args: v,
|
||||
}
|
||||
|
||||
bl.writeMsg(lm)
|
||||
}
|
||||
|
||||
// Flush flush all chan data.
|
||||
func (bl *BeeLogger) Flush() {
|
||||
if bl.asynchronous {
|
||||
bl.signalChan <- "flush"
|
||||
bl.wg.Wait()
|
||||
bl.wg.Add(1)
|
||||
return
|
||||
}
|
||||
bl.flush()
|
||||
}
|
||||
|
||||
// Close close logger, flush all chan data and destroy all adapters in BeeLogger.
|
||||
func (bl *BeeLogger) Close() {
|
||||
if bl.asynchronous {
|
||||
bl.signalChan <- "close"
|
||||
bl.wg.Wait()
|
||||
close(bl.msgChan)
|
||||
} else {
|
||||
bl.flush()
|
||||
for _, l := range bl.outputs {
|
||||
l.Destroy()
|
||||
}
|
||||
bl.outputs = nil
|
||||
}
|
||||
close(bl.signalChan)
|
||||
}
|
||||
|
||||
// Reset close all outputs, and set bl.outputs to nil
|
||||
func (bl *BeeLogger) Reset() {
|
||||
bl.Flush()
|
||||
for _, l := range bl.outputs {
|
||||
l.Destroy()
|
||||
}
|
||||
bl.outputs = nil
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) flush() {
|
||||
if bl.asynchronous {
|
||||
for {
|
||||
if len(bl.msgChan) > 0 {
|
||||
bm := <-bl.msgChan
|
||||
bl.writeToLoggers(bm)
|
||||
logMsgPool.Put(bm)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, l := range bl.outputs {
|
||||
l.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// beeLogger references the used application logger.
|
||||
var beeLogger = NewLogger()
|
||||
|
||||
// GetBeeLogger returns the default BeeLogger
|
||||
func GetBeeLogger() *BeeLogger {
|
||||
return beeLogger
|
||||
}
|
||||
|
||||
var beeLoggerMap = struct {
|
||||
sync.RWMutex
|
||||
logs map[string]*log.Logger
|
||||
}{
|
||||
logs: map[string]*log.Logger{},
|
||||
}
|
||||
|
||||
// GetLogger returns the default BeeLogger
|
||||
func GetLogger(prefixes ...string) *log.Logger {
|
||||
prefix := append(prefixes, "")[0]
|
||||
if prefix != "" {
|
||||
prefix = fmt.Sprintf(`[%s] `, strings.ToUpper(prefix))
|
||||
}
|
||||
beeLoggerMap.RLock()
|
||||
l, ok := beeLoggerMap.logs[prefix]
|
||||
if ok {
|
||||
beeLoggerMap.RUnlock()
|
||||
return l
|
||||
}
|
||||
beeLoggerMap.RUnlock()
|
||||
beeLoggerMap.Lock()
|
||||
defer beeLoggerMap.Unlock()
|
||||
l, ok = beeLoggerMap.logs[prefix]
|
||||
if !ok {
|
||||
l = log.New(beeLogger, prefix, 0)
|
||||
beeLoggerMap.logs[prefix] = l
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// EnableFullFilePath enables full file path logging. Disabled by default
|
||||
// e.g "/home/Documents/GitHub/beego/mainapp/" instead of "mainapp"
|
||||
func EnableFullFilePath(b bool) {
|
||||
beeLogger.enableFullFilePath = b
|
||||
}
|
||||
|
||||
// Reset will remove all the adapter
|
||||
func Reset() {
|
||||
beeLogger.Reset()
|
||||
}
|
||||
|
||||
// Async set the beelogger with Async mode and hold msglen messages
|
||||
func Async(msgLen ...int64) *BeeLogger {
|
||||
return beeLogger.Async(msgLen...)
|
||||
}
|
||||
|
||||
// SetLevel sets the global log level used by the simple logger.
|
||||
func SetLevel(l int) {
|
||||
beeLogger.SetLevel(l)
|
||||
}
|
||||
|
||||
// SetPrefix sets the prefix
|
||||
func SetPrefix(s string) {
|
||||
beeLogger.SetPrefix(s)
|
||||
}
|
||||
|
||||
// EnableFuncCallDepth enable log funcCallDepth
|
||||
func EnableFuncCallDepth(b bool) {
|
||||
beeLogger.enableFuncCallDepth = b
|
||||
}
|
||||
|
||||
// SetLogFuncCall set the CallDepth, default is 4
|
||||
func SetLogFuncCall(b bool) {
|
||||
beeLogger.EnableFuncCallDepth(b)
|
||||
beeLogger.SetLogFuncCallDepth(3)
|
||||
}
|
||||
|
||||
// SetLogFuncCallDepth set log funcCallDepth
|
||||
func SetLogFuncCallDepth(d int) {
|
||||
beeLogger.loggerFuncCallDepth = d
|
||||
}
|
||||
|
||||
// SetLogger sets a new logger.
|
||||
func SetLogger(adapter string, config ...string) error {
|
||||
return beeLogger.SetLogger(adapter, config...)
|
||||
}
|
||||
|
||||
// Emergency logs a message at emergency level.
|
||||
func Emergency(f interface{}, v ...interface{}) {
|
||||
beeLogger.Emergency(formatPattern(f, v...), v...)
|
||||
}
|
||||
|
||||
// Alert logs a message at alert level.
|
||||
func Alert(f interface{}, v ...interface{}) {
|
||||
beeLogger.Alert(formatPattern(f, v...), v...)
|
||||
}
|
||||
|
||||
// Critical logs a message at critical level.
|
||||
func Critical(f interface{}, v ...interface{}) {
|
||||
beeLogger.Critical(formatPattern(f, v...), v...)
|
||||
}
|
||||
|
||||
// Error logs a message at error level.
|
||||
func Error(f interface{}, v ...interface{}) {
|
||||
beeLogger.Error(formatPattern(f, v...), v...)
|
||||
}
|
||||
|
||||
// Warning logs a message at warning level.
|
||||
func Warning(f interface{}, v ...interface{}) {
|
||||
beeLogger.Warn(formatPattern(f, v...), v...)
|
||||
}
|
||||
|
||||
// Warn compatibility alias for Warning()
|
||||
func Warn(f interface{}, v ...interface{}) {
|
||||
beeLogger.Warn(formatPattern(f, v...), v...)
|
||||
}
|
||||
|
||||
// Notice logs a message at notice level.
|
||||
func Notice(f interface{}, v ...interface{}) {
|
||||
beeLogger.Notice(formatPattern(f, v...), v...)
|
||||
}
|
||||
|
||||
// Informational logs a message at info level.
|
||||
func Informational(f interface{}, v ...interface{}) {
|
||||
beeLogger.Info(formatPattern(f, v...), v...)
|
||||
}
|
||||
|
||||
// Info compatibility alias for Warning()
|
||||
func Info(f interface{}, v ...interface{}) {
|
||||
beeLogger.Info(formatPattern(f, v...), v...)
|
||||
}
|
||||
|
||||
// Debug logs a message at debug level.
|
||||
func Debug(f interface{}, v ...interface{}) {
|
||||
beeLogger.Debug(formatPattern(f, v...), v...)
|
||||
}
|
||||
|
||||
// Trace logs a message at trace level.
|
||||
// compatibility alias for Warning()
|
||||
func Trace(f interface{}, v ...interface{}) {
|
||||
beeLogger.Trace(formatPattern(f, v...), v...)
|
||||
}
|
||||
|
||||
func formatPattern(f interface{}, v ...interface{}) string {
|
||||
var msg string
|
||||
switch f.(type) {
|
||||
case string:
|
||||
msg = f.(string)
|
||||
if len(v) == 0 {
|
||||
return msg
|
||||
}
|
||||
if !strings.Contains(msg, "%") {
|
||||
// do not contain format char
|
||||
msg += strings.Repeat(" %v", len(v))
|
||||
}
|
||||
default:
|
||||
msg = fmt.Sprint(f)
|
||||
if len(v) == 0 {
|
||||
return msg
|
||||
}
|
||||
msg += strings.Repeat(" %v", len(v))
|
||||
}
|
||||
return msg
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
// Copyright 2020
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LogMsg struct {
|
||||
Level int
|
||||
Msg string
|
||||
When time.Time
|
||||
FilePath string
|
||||
LineNumber int
|
||||
Args []interface{}
|
||||
Prefix string
|
||||
enableFullFilePath bool
|
||||
enableFuncCallDepth bool
|
||||
}
|
||||
|
||||
// OldStyleFormat you should never invoke this
|
||||
func (lm *LogMsg) OldStyleFormat() string {
|
||||
msg := lm.Msg
|
||||
|
||||
if len(lm.Args) > 0 {
|
||||
msg = fmt.Sprintf(lm.Msg, lm.Args...)
|
||||
}
|
||||
|
||||
msg = lm.Prefix + " " + msg
|
||||
|
||||
if lm.enableFuncCallDepth {
|
||||
filePath := lm.FilePath
|
||||
if !lm.enableFullFilePath {
|
||||
_, filePath = path.Split(filePath)
|
||||
}
|
||||
msg = fmt.Sprintf("[%s:%d] %s", filePath, lm.LineNumber, msg)
|
||||
}
|
||||
|
||||
msg = levelPrefix[lm.Level] + " " + msg
|
||||
return msg
|
||||
}
|
@ -1,178 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type logWriter struct {
|
||||
sync.Mutex
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
func newLogWriter(wr io.Writer) *logWriter {
|
||||
return &logWriter{writer: wr}
|
||||
}
|
||||
|
||||
func (lg *logWriter) writeln(msg string) (int, error) {
|
||||
lg.Lock()
|
||||
msg += "\n"
|
||||
n, err := lg.writer.Write([]byte(msg))
|
||||
lg.Unlock()
|
||||
return n, err
|
||||
}
|
||||
|
||||
const (
|
||||
y1 = `0123456789`
|
||||
y2 = `0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789`
|
||||
y3 = `0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999`
|
||||
y4 = `0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789`
|
||||
mo1 = `000000000111`
|
||||
mo2 = `123456789012`
|
||||
d1 = `0000000001111111111222222222233`
|
||||
d2 = `1234567890123456789012345678901`
|
||||
h1 = `000000000011111111112222`
|
||||
h2 = `012345678901234567890123`
|
||||
mi1 = `000000000011111111112222222222333333333344444444445555555555`
|
||||
mi2 = `012345678901234567890123456789012345678901234567890123456789`
|
||||
s1 = `000000000011111111112222222222333333333344444444445555555555`
|
||||
s2 = `012345678901234567890123456789012345678901234567890123456789`
|
||||
ns1 = `0123456789`
|
||||
)
|
||||
|
||||
func formatTimeHeader(when time.Time) ([]byte, int, int) {
|
||||
y, mo, d := when.Date()
|
||||
h, mi, s := when.Clock()
|
||||
ns := when.Nanosecond() / 1000000
|
||||
// len("2006/01/02 15:04:05.123 ")==24
|
||||
var buf [24]byte
|
||||
|
||||
buf[0] = y1[y/1000%10]
|
||||
buf[1] = y2[y/100]
|
||||
buf[2] = y3[y-y/100*100]
|
||||
buf[3] = y4[y-y/100*100]
|
||||
buf[4] = '/'
|
||||
buf[5] = mo1[mo-1]
|
||||
buf[6] = mo2[mo-1]
|
||||
buf[7] = '/'
|
||||
buf[8] = d1[d-1]
|
||||
buf[9] = d2[d-1]
|
||||
buf[10] = ' '
|
||||
buf[11] = h1[h]
|
||||
buf[12] = h2[h]
|
||||
buf[13] = ':'
|
||||
buf[14] = mi1[mi]
|
||||
buf[15] = mi2[mi]
|
||||
buf[16] = ':'
|
||||
buf[17] = s1[s]
|
||||
buf[18] = s2[s]
|
||||
buf[19] = '.'
|
||||
buf[20] = ns1[ns/100]
|
||||
buf[21] = ns1[ns%100/10]
|
||||
buf[22] = ns1[ns%10]
|
||||
|
||||
buf[23] = ' '
|
||||
|
||||
return buf[0:], d, h
|
||||
}
|
||||
|
||||
var (
|
||||
green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109})
|
||||
white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109})
|
||||
yellow = string([]byte{27, 91, 57, 55, 59, 52, 51, 109})
|
||||
red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109})
|
||||
blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109})
|
||||
magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109})
|
||||
cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109})
|
||||
|
||||
w32Green = string([]byte{27, 91, 52, 50, 109})
|
||||
w32White = string([]byte{27, 91, 52, 55, 109})
|
||||
w32Yellow = string([]byte{27, 91, 52, 51, 109})
|
||||
w32Red = string([]byte{27, 91, 52, 49, 109})
|
||||
w32Blue = string([]byte{27, 91, 52, 52, 109})
|
||||
w32Magenta = string([]byte{27, 91, 52, 53, 109})
|
||||
w32Cyan = string([]byte{27, 91, 52, 54, 109})
|
||||
|
||||
reset = string([]byte{27, 91, 48, 109})
|
||||
)
|
||||
|
||||
var (
|
||||
once sync.Once
|
||||
colorMap map[string]string
|
||||
)
|
||||
|
||||
func initColor() {
|
||||
if runtime.GOOS == "windows" {
|
||||
green = w32Green
|
||||
white = w32White
|
||||
yellow = w32Yellow
|
||||
red = w32Red
|
||||
blue = w32Blue
|
||||
magenta = w32Magenta
|
||||
cyan = w32Cyan
|
||||
}
|
||||
colorMap = map[string]string{
|
||||
// by color
|
||||
"green": green,
|
||||
"white": white,
|
||||
"yellow": yellow,
|
||||
"red": red,
|
||||
// by method
|
||||
"GET": blue,
|
||||
"POST": cyan,
|
||||
"PUT": yellow,
|
||||
"DELETE": red,
|
||||
"PATCH": green,
|
||||
"HEAD": magenta,
|
||||
"OPTIONS": white,
|
||||
}
|
||||
}
|
||||
|
||||
// ColorByStatus return color by http code
|
||||
// 2xx return Green
|
||||
// 3xx return White
|
||||
// 4xx return Yellow
|
||||
// 5xx return Red
|
||||
func ColorByStatus(code int) string {
|
||||
once.Do(initColor)
|
||||
switch {
|
||||
case code >= 200 && code < 300:
|
||||
return colorMap["green"]
|
||||
case code >= 300 && code < 400:
|
||||
return colorMap["white"]
|
||||
case code >= 400 && code < 500:
|
||||
return colorMap["yellow"]
|
||||
default:
|
||||
return colorMap["red"]
|
||||
}
|
||||
}
|
||||
|
||||
// ColorByMethod return color by http code
|
||||
func ColorByMethod(method string) string {
|
||||
once.Do(initColor)
|
||||
if c := colorMap[method]; c != "" {
|
||||
return c
|
||||
}
|
||||
return reset
|
||||
}
|
||||
|
||||
// ResetColor return reset color
|
||||
func ResetColor() string {
|
||||
return reset
|
||||
}
|
@ -1,132 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// A filesLogWriter manages several fileLogWriter
|
||||
// filesLogWriter will write logs to the file in json configuration and write the same level log to correspond file
|
||||
// means if the file name in configuration is project.log filesLogWriter will create project.error.log/project.debug.log
|
||||
// and write the error-level logs to project.error.log and write the debug-level logs to project.debug.log
|
||||
// the rotate attribute also acts like fileLogWriter
|
||||
type multiFileLogWriter struct {
|
||||
writers [LevelDebug + 1 + 1]*fileLogWriter // the last one for fullLogWriter
|
||||
fullLogWriter *fileLogWriter
|
||||
Separate []string `json:"separate"`
|
||||
}
|
||||
|
||||
var levelNames = [...]string{"emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"}
|
||||
|
||||
// Init file logger with json config.
|
||||
// jsonConfig like:
|
||||
// {
|
||||
// "filename":"logs/beego.log",
|
||||
// "maxLines":0,
|
||||
// "maxsize":0,
|
||||
// "daily":true,
|
||||
// "maxDays":15,
|
||||
// "rotate":true,
|
||||
// "perm":0600,
|
||||
// "separate":["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"],
|
||||
// }
|
||||
|
||||
func (f *multiFileLogWriter) Init(config string) error {
|
||||
writer := newFileWriter().(*fileLogWriter)
|
||||
err := writer.Init(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.fullLogWriter = writer
|
||||
f.writers[LevelDebug+1] = writer
|
||||
|
||||
// unmarshal "separate" field to f.Separate
|
||||
err = json.Unmarshal([]byte(config), f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonMap := map[string]interface{}{}
|
||||
err = json.Unmarshal([]byte(config), &jsonMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := LevelEmergency; i < LevelDebug+1; i++ {
|
||||
for _, v := range f.Separate {
|
||||
if v == levelNames[i] {
|
||||
jsonMap["filename"] = f.fullLogWriter.fileNameOnly + "." + levelNames[i] + f.fullLogWriter.suffix
|
||||
jsonMap["level"] = i
|
||||
bs, _ := json.Marshal(jsonMap)
|
||||
writer = newFileWriter().(*fileLogWriter)
|
||||
err := writer.Init(string(bs))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.writers[i] = writer
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*multiFileLogWriter) Format(lm *LogMsg) string {
|
||||
return lm.OldStyleFormat()
|
||||
}
|
||||
|
||||
func (f *multiFileLogWriter) SetFormatter(fmt LogFormatter) {
|
||||
f.fullLogWriter.SetFormatter(fmt)
|
||||
}
|
||||
|
||||
func (f *multiFileLogWriter) Destroy() {
|
||||
for i := 0; i < len(f.writers); i++ {
|
||||
if f.writers[i] != nil {
|
||||
f.writers[i].Destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *multiFileLogWriter) WriteMsg(lm *LogMsg) error {
|
||||
if f.fullLogWriter != nil {
|
||||
f.fullLogWriter.WriteMsg(lm)
|
||||
}
|
||||
for i := 0; i < len(f.writers)-1; i++ {
|
||||
if f.writers[i] != nil {
|
||||
if lm.Level == f.writers[i].Level {
|
||||
f.writers[i].WriteMsg(lm)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *multiFileLogWriter) Flush() {
|
||||
for i := 0; i < len(f.writers); i++ {
|
||||
if f.writers[i] != nil {
|
||||
f.writers[i].Flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// newFilesWriter create a FileLogWriter returning as LoggerInterface.
|
||||
func newFilesWriter() Logger {
|
||||
res := &multiFileLogWriter{}
|
||||
return res
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register(AdapterMultiFile, newFilesWriter)
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// SLACKWriter implements beego LoggerInterface and is used to send jiaoliao webhook
|
||||
type SLACKWriter struct {
|
||||
WebhookURL string `json:"webhookurl"`
|
||||
Level int `json:"level"`
|
||||
formatter LogFormatter
|
||||
Formatter string `json:"formatter"`
|
||||
}
|
||||
|
||||
// newSLACKWriter creates jiaoliao writer.
|
||||
func newSLACKWriter() Logger {
|
||||
res := &SLACKWriter{Level: LevelTrace}
|
||||
res.formatter = res
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *SLACKWriter) Format(lm *LogMsg) string {
|
||||
// text := fmt.Sprintf("{\"text\": \"%s\"}", msg)
|
||||
return lm.When.Format("2006-01-02 15:04:05") + " " + lm.OldStyleFormat()
|
||||
}
|
||||
|
||||
func (s *SLACKWriter) SetFormatter(f LogFormatter) {
|
||||
s.formatter = f
|
||||
}
|
||||
|
||||
// Init SLACKWriter with json config string
|
||||
func (s *SLACKWriter) Init(config string) error {
|
||||
res := json.Unmarshal([]byte(config), s)
|
||||
|
||||
if res == nil && len(s.Formatter) > 0 {
|
||||
fmtr, ok := GetFormatter(s.Formatter)
|
||||
if !ok {
|
||||
return errors.New(fmt.Sprintf("the formatter with name: %s not found", s.Formatter))
|
||||
}
|
||||
s.formatter = fmtr
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// WriteMsg write message in smtp writer.
|
||||
// Sends an email with subject and only this message.
|
||||
func (s *SLACKWriter) WriteMsg(lm *LogMsg) error {
|
||||
if lm.Level > s.Level {
|
||||
return nil
|
||||
}
|
||||
msg := s.Format(lm)
|
||||
m := make(map[string]string, 1)
|
||||
m["text"] = msg
|
||||
|
||||
body, _ := json.Marshal(m)
|
||||
// resp, err := http.PostForm(s.WebhookURL, form)
|
||||
resp, err := http.Post(s.WebhookURL, "application/json", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flush implementing method. empty.
|
||||
func (s *SLACKWriter) Flush() {
|
||||
}
|
||||
|
||||
// Destroy implementing method. empty.
|
||||
func (s *SLACKWriter) Destroy() {
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register(AdapterSlack, newSLACKWriter)
|
||||
}
|
@ -1,172 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/smtp"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// SMTPWriter implements LoggerInterface and is used to send emails via given SMTP-server.
|
||||
type SMTPWriter struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Host string `json:"host"`
|
||||
Subject string `json:"subject"`
|
||||
FromAddress string `json:"fromAddress"`
|
||||
RecipientAddresses []string `json:"sendTos"`
|
||||
Level int `json:"level"`
|
||||
formatter LogFormatter
|
||||
Formatter string `json:"formatter"`
|
||||
}
|
||||
|
||||
// NewSMTPWriter creates the smtp writer.
|
||||
func newSMTPWriter() Logger {
|
||||
res := &SMTPWriter{Level: LevelTrace}
|
||||
res.formatter = res
|
||||
return res
|
||||
}
|
||||
|
||||
// Init smtp writer with json config.
|
||||
// config like:
|
||||
// {
|
||||
// "username":"example@gmail.com",
|
||||
// "password:"password",
|
||||
// "host":"smtp.gmail.com:465",
|
||||
// "subject":"email title",
|
||||
// "fromAddress":"from@example.com",
|
||||
// "sendTos":["email1","email2"],
|
||||
// "level":LevelError
|
||||
// }
|
||||
func (s *SMTPWriter) Init(config string) error {
|
||||
res := json.Unmarshal([]byte(config), s)
|
||||
if res == nil && len(s.Formatter) > 0 {
|
||||
fmtr, ok := GetFormatter(s.Formatter)
|
||||
if !ok {
|
||||
return errors.New(fmt.Sprintf("the formatter with name: %s not found", s.Formatter))
|
||||
}
|
||||
s.formatter = fmtr
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *SMTPWriter) getSMTPAuth(host string) smtp.Auth {
|
||||
if len(strings.Trim(s.Username, " ")) == 0 && len(strings.Trim(s.Password, " ")) == 0 {
|
||||
return nil
|
||||
}
|
||||
return smtp.PlainAuth(
|
||||
"",
|
||||
s.Username,
|
||||
s.Password,
|
||||
host,
|
||||
)
|
||||
}
|
||||
|
||||
func (s *SMTPWriter) SetFormatter(f LogFormatter) {
|
||||
s.formatter = f
|
||||
}
|
||||
|
||||
func (s *SMTPWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAddress string, recipients []string, msgContent []byte) error {
|
||||
client, err := smtp.Dial(hostAddressWithPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
host, _, _ := net.SplitHostPort(hostAddressWithPort)
|
||||
tlsConn := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: host,
|
||||
}
|
||||
if err = client.StartTLS(tlsConn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if auth != nil {
|
||||
if err = client.Auth(auth); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = client.Mail(fromAddress); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, rec := range recipients {
|
||||
if err = client.Rcpt(rec); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
w, err := client.Data()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(msgContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return client.Quit()
|
||||
}
|
||||
|
||||
func (s *SMTPWriter) Format(lm *LogMsg) string {
|
||||
return lm.OldStyleFormat()
|
||||
}
|
||||
|
||||
// WriteMsg writes message in smtp writer.
|
||||
// Sends an email with subject and only this message.
|
||||
func (s *SMTPWriter) WriteMsg(lm *LogMsg) error {
|
||||
if lm.Level > s.Level {
|
||||
return nil
|
||||
}
|
||||
|
||||
hp := strings.Split(s.Host, ":")
|
||||
|
||||
// Set up authentication information.
|
||||
auth := s.getSMTPAuth(hp[0])
|
||||
|
||||
msg := s.Format(lm)
|
||||
|
||||
// Connect to the server, authenticate, set the sender and recipient,
|
||||
// and send the email all in one step.
|
||||
contentType := "Content-Type: text/plain" + "; charset=UTF-8"
|
||||
mailmsg := []byte("To: " + strings.Join(s.RecipientAddresses, ";") + "\r\nFrom: " + s.FromAddress + "<" + s.FromAddress +
|
||||
">\r\nSubject: " + s.Subject + "\r\n" + contentType + "\r\n\r\n" + fmt.Sprintf(".%s", lm.When.Format("2006-01-02 15:04:05")) + msg)
|
||||
|
||||
return s.sendMail(s.Host, auth, s.FromAddress, s.RecipientAddresses, mailmsg)
|
||||
}
|
||||
|
||||
// Flush implementing method. empty.
|
||||
func (s *SMTPWriter) Flush() {
|
||||
}
|
||||
|
||||
// Destroy implementing method. empty.
|
||||
func (s *SMTPWriter) Destroy() {
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register(AdapterMail, newSMTPWriter)
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// GetFuncName get function name
|
||||
func GetFuncName(i interface{}) string {
|
||||
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
|
||||
}
|
@ -1,478 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var (
|
||||
dunno = []byte("???")
|
||||
centerDot = []byte("·")
|
||||
dot = []byte(".")
|
||||
)
|
||||
|
||||
type pointerInfo struct {
|
||||
prev *pointerInfo
|
||||
n int
|
||||
addr uintptr
|
||||
pos int
|
||||
used []int
|
||||
}
|
||||
|
||||
// Display print the data in console
|
||||
func Display(data ...interface{}) {
|
||||
display(true, data...)
|
||||
}
|
||||
|
||||
// GetDisplayString return data print string
|
||||
func GetDisplayString(data ...interface{}) string {
|
||||
return display(false, data...)
|
||||
}
|
||||
|
||||
func display(displayed bool, data ...interface{}) string {
|
||||
pc, file, line, ok := runtime.Caller(2)
|
||||
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
fmt.Fprintf(buf, "[Debug] at %s() [%s:%d]\n", function(pc), file, line)
|
||||
|
||||
fmt.Fprintf(buf, "\n[Variables]\n")
|
||||
|
||||
for i := 0; i < len(data); i += 2 {
|
||||
output := fomateinfo(len(data[i].(string))+3, data[i+1])
|
||||
fmt.Fprintf(buf, "%s = %s", data[i], output)
|
||||
}
|
||||
|
||||
if displayed {
|
||||
log.Print(buf)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// return data dump and format bytes
|
||||
func fomateinfo(headlen int, data ...interface{}) []byte {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
if len(data) > 1 {
|
||||
fmt.Fprint(buf, " ")
|
||||
|
||||
fmt.Fprint(buf, "[")
|
||||
|
||||
fmt.Fprintln(buf)
|
||||
}
|
||||
|
||||
for k, v := range data {
|
||||
buf2 := new(bytes.Buffer)
|
||||
var pointers *pointerInfo
|
||||
interfaces := make([]reflect.Value, 0, 10)
|
||||
|
||||
printKeyValue(buf2, reflect.ValueOf(v), &pointers, &interfaces, nil, true, " ", 1)
|
||||
|
||||
if k < len(data)-1 {
|
||||
fmt.Fprint(buf2, ", ")
|
||||
}
|
||||
|
||||
fmt.Fprintln(buf2)
|
||||
|
||||
buf.Write(buf2.Bytes())
|
||||
}
|
||||
|
||||
if len(data) > 1 {
|
||||
fmt.Fprintln(buf)
|
||||
|
||||
fmt.Fprint(buf, " ")
|
||||
|
||||
fmt.Fprint(buf, "]")
|
||||
}
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// check data is golang basic type
|
||||
func isSimpleType(val reflect.Value, kind reflect.Kind, pointers **pointerInfo, interfaces *[]reflect.Value) bool {
|
||||
switch kind {
|
||||
case reflect.Bool:
|
||||
return true
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return true
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint, reflect.Uint32, reflect.Uint64:
|
||||
return true
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return true
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return true
|
||||
case reflect.String:
|
||||
return true
|
||||
case reflect.Chan:
|
||||
return true
|
||||
case reflect.Invalid:
|
||||
return true
|
||||
case reflect.Interface:
|
||||
for _, in := range *interfaces {
|
||||
if reflect.DeepEqual(in, val) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
case reflect.UnsafePointer:
|
||||
if val.IsNil() {
|
||||
return true
|
||||
}
|
||||
|
||||
elem := val.Elem()
|
||||
|
||||
if isSimpleType(elem, elem.Kind(), pointers, interfaces) {
|
||||
return true
|
||||
}
|
||||
|
||||
addr := val.Elem().UnsafeAddr()
|
||||
|
||||
for p := *pointers; p != nil; p = p.prev {
|
||||
if addr == p.addr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// dump value
|
||||
func printKeyValue(buf *bytes.Buffer, val reflect.Value, pointers **pointerInfo, interfaces *[]reflect.Value, structFilter func(string, string) bool, formatOutput bool, indent string, level int) {
|
||||
t := val.Kind()
|
||||
|
||||
switch t {
|
||||
case reflect.Bool:
|
||||
fmt.Fprint(buf, val.Bool())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
fmt.Fprint(buf, val.Int())
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint, reflect.Uint32, reflect.Uint64:
|
||||
fmt.Fprint(buf, val.Uint())
|
||||
case reflect.Float32, reflect.Float64:
|
||||
fmt.Fprint(buf, val.Float())
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
fmt.Fprint(buf, val.Complex())
|
||||
case reflect.UnsafePointer:
|
||||
fmt.Fprintf(buf, "unsafe.Pointer(0x%X)", val.Pointer())
|
||||
case reflect.Ptr:
|
||||
if val.IsNil() {
|
||||
fmt.Fprint(buf, "nil")
|
||||
return
|
||||
}
|
||||
|
||||
addr := val.Elem().UnsafeAddr()
|
||||
|
||||
for p := *pointers; p != nil; p = p.prev {
|
||||
if addr == p.addr {
|
||||
p.used = append(p.used, buf.Len())
|
||||
fmt.Fprintf(buf, "0x%X", addr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
*pointers = &pointerInfo{
|
||||
prev: *pointers,
|
||||
addr: addr,
|
||||
pos: buf.Len(),
|
||||
used: make([]int, 0),
|
||||
}
|
||||
|
||||
fmt.Fprint(buf, "&")
|
||||
|
||||
printKeyValue(buf, val.Elem(), pointers, interfaces, structFilter, formatOutput, indent, level)
|
||||
case reflect.String:
|
||||
fmt.Fprint(buf, "\"", val.String(), "\"")
|
||||
case reflect.Interface:
|
||||
value := val.Elem()
|
||||
|
||||
if !value.IsValid() {
|
||||
fmt.Fprint(buf, "nil")
|
||||
} else {
|
||||
for _, in := range *interfaces {
|
||||
if reflect.DeepEqual(in, val) {
|
||||
fmt.Fprint(buf, "repeat")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
*interfaces = append(*interfaces, val)
|
||||
|
||||
printKeyValue(buf, value, pointers, interfaces, structFilter, formatOutput, indent, level+1)
|
||||
}
|
||||
case reflect.Struct:
|
||||
t := val.Type()
|
||||
|
||||
fmt.Fprint(buf, t)
|
||||
fmt.Fprint(buf, "{")
|
||||
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
if formatOutput {
|
||||
fmt.Fprintln(buf)
|
||||
} else {
|
||||
fmt.Fprint(buf, " ")
|
||||
}
|
||||
|
||||
name := t.Field(i).Name
|
||||
|
||||
if formatOutput {
|
||||
for ind := 0; ind < level; ind++ {
|
||||
fmt.Fprint(buf, indent)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprint(buf, name)
|
||||
fmt.Fprint(buf, ": ")
|
||||
|
||||
if structFilter != nil && structFilter(t.String(), name) {
|
||||
fmt.Fprint(buf, "ignore")
|
||||
} else {
|
||||
printKeyValue(buf, val.Field(i), pointers, interfaces, structFilter, formatOutput, indent, level+1)
|
||||
}
|
||||
|
||||
fmt.Fprint(buf, ",")
|
||||
}
|
||||
|
||||
if formatOutput {
|
||||
fmt.Fprintln(buf)
|
||||
|
||||
for ind := 0; ind < level-1; ind++ {
|
||||
fmt.Fprint(buf, indent)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprint(buf, " ")
|
||||
}
|
||||
|
||||
fmt.Fprint(buf, "}")
|
||||
case reflect.Array, reflect.Slice:
|
||||
fmt.Fprint(buf, val.Type())
|
||||
fmt.Fprint(buf, "{")
|
||||
|
||||
allSimple := true
|
||||
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
elem := val.Index(i)
|
||||
|
||||
isSimple := isSimpleType(elem, elem.Kind(), pointers, interfaces)
|
||||
|
||||
if !isSimple {
|
||||
allSimple = false
|
||||
}
|
||||
|
||||
if formatOutput && !isSimple {
|
||||
fmt.Fprintln(buf)
|
||||
} else {
|
||||
fmt.Fprint(buf, " ")
|
||||
}
|
||||
|
||||
if formatOutput && !isSimple {
|
||||
for ind := 0; ind < level; ind++ {
|
||||
fmt.Fprint(buf, indent)
|
||||
}
|
||||
}
|
||||
|
||||
printKeyValue(buf, elem, pointers, interfaces, structFilter, formatOutput, indent, level+1)
|
||||
|
||||
if i != val.Len()-1 || !allSimple {
|
||||
fmt.Fprint(buf, ",")
|
||||
}
|
||||
}
|
||||
|
||||
if formatOutput && !allSimple {
|
||||
fmt.Fprintln(buf)
|
||||
|
||||
for ind := 0; ind < level-1; ind++ {
|
||||
fmt.Fprint(buf, indent)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprint(buf, " ")
|
||||
}
|
||||
|
||||
fmt.Fprint(buf, "}")
|
||||
case reflect.Map:
|
||||
t := val.Type()
|
||||
keys := val.MapKeys()
|
||||
|
||||
fmt.Fprint(buf, t)
|
||||
fmt.Fprint(buf, "{")
|
||||
|
||||
allSimple := true
|
||||
|
||||
for i := 0; i < len(keys); i++ {
|
||||
elem := val.MapIndex(keys[i])
|
||||
|
||||
isSimple := isSimpleType(elem, elem.Kind(), pointers, interfaces)
|
||||
|
||||
if !isSimple {
|
||||
allSimple = false
|
||||
}
|
||||
|
||||
if formatOutput && !isSimple {
|
||||
fmt.Fprintln(buf)
|
||||
} else {
|
||||
fmt.Fprint(buf, " ")
|
||||
}
|
||||
|
||||
if formatOutput && !isSimple {
|
||||
for ind := 0; ind <= level; ind++ {
|
||||
fmt.Fprint(buf, indent)
|
||||
}
|
||||
}
|
||||
|
||||
printKeyValue(buf, keys[i], pointers, interfaces, structFilter, formatOutput, indent, level+1)
|
||||
fmt.Fprint(buf, ": ")
|
||||
printKeyValue(buf, elem, pointers, interfaces, structFilter, formatOutput, indent, level+1)
|
||||
|
||||
if i != val.Len()-1 || !allSimple {
|
||||
fmt.Fprint(buf, ",")
|
||||
}
|
||||
}
|
||||
|
||||
if formatOutput && !allSimple {
|
||||
fmt.Fprintln(buf)
|
||||
|
||||
for ind := 0; ind < level-1; ind++ {
|
||||
fmt.Fprint(buf, indent)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprint(buf, " ")
|
||||
}
|
||||
|
||||
fmt.Fprint(buf, "}")
|
||||
case reflect.Chan:
|
||||
fmt.Fprint(buf, val.Type())
|
||||
case reflect.Invalid:
|
||||
fmt.Fprint(buf, "invalid")
|
||||
default:
|
||||
fmt.Fprint(buf, "unknow")
|
||||
}
|
||||
}
|
||||
|
||||
// PrintPointerInfo dump pointer value
|
||||
func PrintPointerInfo(buf *bytes.Buffer, headlen int, pointers *pointerInfo) {
|
||||
anyused := false
|
||||
pointerNum := 0
|
||||
|
||||
for p := pointers; p != nil; p = p.prev {
|
||||
if len(p.used) > 0 {
|
||||
anyused = true
|
||||
}
|
||||
pointerNum++
|
||||
p.n = pointerNum
|
||||
}
|
||||
|
||||
if anyused {
|
||||
pointerBufs := make([][]rune, pointerNum+1)
|
||||
|
||||
for i := 0; i < len(pointerBufs); i++ {
|
||||
pointerBuf := make([]rune, buf.Len()+headlen)
|
||||
|
||||
for j := 0; j < len(pointerBuf); j++ {
|
||||
pointerBuf[j] = ' '
|
||||
}
|
||||
|
||||
pointerBufs[i] = pointerBuf
|
||||
}
|
||||
|
||||
for pn := 0; pn <= pointerNum; pn++ {
|
||||
for p := pointers; p != nil; p = p.prev {
|
||||
if len(p.used) > 0 && p.n >= pn {
|
||||
if pn == p.n {
|
||||
pointerBufs[pn][p.pos+headlen] = '└'
|
||||
|
||||
maxpos := 0
|
||||
|
||||
for i, pos := range p.used {
|
||||
if i < len(p.used)-1 {
|
||||
pointerBufs[pn][pos+headlen] = '┴'
|
||||
} else {
|
||||
pointerBufs[pn][pos+headlen] = '┘'
|
||||
}
|
||||
|
||||
maxpos = pos
|
||||
}
|
||||
|
||||
for i := 0; i < maxpos-p.pos-1; i++ {
|
||||
if pointerBufs[pn][i+p.pos+headlen+1] == ' ' {
|
||||
pointerBufs[pn][i+p.pos+headlen+1] = '─'
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pointerBufs[pn][p.pos+headlen] = '│'
|
||||
|
||||
for _, pos := range p.used {
|
||||
if pointerBufs[pn][pos+headlen] == ' ' {
|
||||
pointerBufs[pn][pos+headlen] = '│'
|
||||
} else {
|
||||
pointerBufs[pn][pos+headlen] = '┼'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString(string(pointerBufs[pn]) + "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stack get stack bytes
|
||||
func Stack(skip int, indent string) []byte {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
for i := skip; ; i++ {
|
||||
pc, file, line, ok := runtime.Caller(i)
|
||||
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
buf.WriteString(indent)
|
||||
|
||||
fmt.Fprintf(buf, "at %s() [%s:%d]\n", function(pc), file, line)
|
||||
}
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// return the name of the function containing the PC if possible,
|
||||
func function(pc uintptr) []byte {
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
return dunno
|
||||
}
|
||||
name := []byte(fn.Name())
|
||||
// The name includes the path name to the package, which is unnecessary
|
||||
// since the file name is already included. Plus, it has center dots.
|
||||
// That is, we see
|
||||
// runtime/debug.*T·ptrmethod
|
||||
// and want
|
||||
// *T.ptrmethod
|
||||
if period := bytes.Index(name, dot); period >= 0 {
|
||||
name = name[period+1:]
|
||||
}
|
||||
name = bytes.Replace(name, centerDot, dot, -1)
|
||||
return name
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// SelfPath gets compiled executable file absolute path
|
||||
func SelfPath() string {
|
||||
path, _ := filepath.Abs(os.Args[0])
|
||||
return path
|
||||
}
|
||||
|
||||
// SelfDir gets compiled executable file directory
|
||||
func SelfDir() string {
|
||||
return filepath.Dir(SelfPath())
|
||||
}
|
||||
|
||||
// FileExists reports whether the named file or directory exists.
|
||||
func FileExists(name string) bool {
|
||||
if _, err := os.Stat(name); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SearchFile Search a file in paths.
|
||||
// this is often used in search config file in /etc ~/
|
||||
func SearchFile(filename string, paths ...string) (fullpath string, err error) {
|
||||
for _, path := range paths {
|
||||
if fullpath = filepath.Join(path, filename); FileExists(fullpath) {
|
||||
return
|
||||
}
|
||||
}
|
||||
err = errors.New(fullpath + " not found in paths")
|
||||
return
|
||||
}
|
||||
|
||||
// GrepFile like command grep -E
|
||||
// for example: GrepFile(`^hello`, "hello.txt")
|
||||
// \n is striped while read
|
||||
func GrepFile(patten string, filename string) (lines []string, err error) {
|
||||
re, err := regexp.Compile(patten)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fd, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
lines = make([]string, 0)
|
||||
reader := bufio.NewReader(fd)
|
||||
prefix := ""
|
||||
var isLongLine bool
|
||||
for {
|
||||
byteLine, isPrefix, er := reader.ReadLine()
|
||||
if er != nil && er != io.EOF {
|
||||
return nil, er
|
||||
}
|
||||
if er == io.EOF {
|
||||
break
|
||||
}
|
||||
line := string(byteLine)
|
||||
if isPrefix {
|
||||
prefix += line
|
||||
continue
|
||||
} else {
|
||||
isLongLine = true
|
||||
}
|
||||
|
||||
line = prefix + line
|
||||
if isLongLine {
|
||||
prefix = ""
|
||||
}
|
||||
if re.MatchString(line) {
|
||||
lines = append(lines, line)
|
||||
}
|
||||
}
|
||||
return lines, nil
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
// Copyright 2020 beego-dev
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
type KV interface {
|
||||
GetKey() interface{}
|
||||
GetValue() interface{}
|
||||
}
|
||||
|
||||
// SimpleKV is common structure to store key-value pairs.
|
||||
// When you need something like Pair, you can use this
|
||||
type SimpleKV struct {
|
||||
Key interface{}
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
var _ KV = new(SimpleKV)
|
||||
|
||||
func (s *SimpleKV) GetKey() interface{} {
|
||||
return s.Key
|
||||
}
|
||||
|
||||
func (s *SimpleKV) GetValue() interface{} {
|
||||
return s.Value
|
||||
}
|
||||
|
||||
// KVs interface
|
||||
type KVs interface {
|
||||
GetValueOr(key interface{}, defValue interface{}) interface{}
|
||||
Contains(key interface{}) bool
|
||||
IfContains(key interface{}, action func(value interface{})) KVs
|
||||
}
|
||||
|
||||
// SimpleKVs will store SimpleKV collection as map
|
||||
type SimpleKVs struct {
|
||||
kvs map[interface{}]interface{}
|
||||
}
|
||||
|
||||
var _ KVs = new(SimpleKVs)
|
||||
|
||||
// GetValueOr returns the value for a given key, if non-existent
|
||||
// it returns defValue
|
||||
func (kvs *SimpleKVs) GetValueOr(key interface{}, defValue interface{}) interface{} {
|
||||
v, ok := kvs.kvs[key]
|
||||
if ok {
|
||||
return v
|
||||
}
|
||||
return defValue
|
||||
}
|
||||
|
||||
// Contains checks if a key exists
|
||||
func (kvs *SimpleKVs) Contains(key interface{}) bool {
|
||||
_, ok := kvs.kvs[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
// IfContains invokes the action on a key if it exists
|
||||
func (kvs *SimpleKVs) IfContains(key interface{}, action func(value interface{})) KVs {
|
||||
v, ok := kvs.kvs[key]
|
||||
if ok {
|
||||
action(v)
|
||||
}
|
||||
return kvs
|
||||
}
|
||||
|
||||
// NewKVs creates the *KVs instance
|
||||
func NewKVs(kvs ...KV) KVs {
|
||||
res := &SimpleKVs{
|
||||
kvs: make(map[interface{}]interface{}, len(kvs)),
|
||||
}
|
||||
for _, kv := range kvs {
|
||||
res.kvs[kv.GetKey()] = kv.GetValue()
|
||||
}
|
||||
return res
|
||||
}
|
@ -1,424 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"net/mail"
|
||||
"net/smtp"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
maxLineLength = 76
|
||||
|
||||
upperhex = "0123456789ABCDEF"
|
||||
)
|
||||
|
||||
// Email is the type used for email messages
|
||||
type Email struct {
|
||||
Auth smtp.Auth
|
||||
Identity string `json:"identity"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
From string `json:"from"`
|
||||
To []string
|
||||
Bcc []string
|
||||
Cc []string
|
||||
Subject string
|
||||
Text string // Plaintext message (optional)
|
||||
HTML string // Html message (optional)
|
||||
Headers textproto.MIMEHeader
|
||||
Attachments []*Attachment
|
||||
ReadReceipt []string
|
||||
}
|
||||
|
||||
// Attachment is a struct representing an email attachment.
|
||||
// Based on the mime/multipart.FileHeader struct, Attachment contains the name, MIMEHeader, and content of the attachment in question
|
||||
type Attachment struct {
|
||||
Filename string
|
||||
Header textproto.MIMEHeader
|
||||
Content []byte
|
||||
}
|
||||
|
||||
// NewEMail create new Email struct with config json.
|
||||
// config json is followed from Email struct fields.
|
||||
func NewEMail(config string) *Email {
|
||||
e := new(Email)
|
||||
e.Headers = textproto.MIMEHeader{}
|
||||
err := json.Unmarshal([]byte(config), e)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// Bytes Make all send information to byte
|
||||
func (e *Email) Bytes() ([]byte, error) {
|
||||
buff := &bytes.Buffer{}
|
||||
w := multipart.NewWriter(buff)
|
||||
// Set the appropriate headers (overwriting any conflicts)
|
||||
// Leave out Bcc (only included in envelope headers)
|
||||
e.Headers.Set("To", strings.Join(e.To, ","))
|
||||
if e.Cc != nil {
|
||||
e.Headers.Set("Cc", strings.Join(e.Cc, ","))
|
||||
}
|
||||
e.Headers.Set("From", e.From)
|
||||
e.Headers.Set("Subject", e.Subject)
|
||||
if len(e.ReadReceipt) != 0 {
|
||||
e.Headers.Set("Disposition-Notification-To", strings.Join(e.ReadReceipt, ","))
|
||||
}
|
||||
e.Headers.Set("MIME-Version", "1.0")
|
||||
|
||||
// Write the envelope headers (including any custom headers)
|
||||
if err := headerToBytes(buff, e.Headers); err != nil {
|
||||
return nil, fmt.Errorf("Failed to render message headers: %s", err)
|
||||
}
|
||||
|
||||
e.Headers.Set("Content-Type", fmt.Sprintf("multipart/mixed;\r\n boundary=%s\r\n", w.Boundary()))
|
||||
fmt.Fprintf(buff, "%s:", "Content-Type")
|
||||
fmt.Fprintf(buff, " %s\r\n", fmt.Sprintf("multipart/mixed;\r\n boundary=%s\r\n", w.Boundary()))
|
||||
|
||||
// Start the multipart/mixed part
|
||||
fmt.Fprintf(buff, "--%s\r\n", w.Boundary())
|
||||
header := textproto.MIMEHeader{}
|
||||
// Check to see if there is a Text or HTML field
|
||||
if e.Text != "" || e.HTML != "" {
|
||||
subWriter := multipart.NewWriter(buff)
|
||||
// Create the multipart alternative part
|
||||
header.Set("Content-Type", fmt.Sprintf("multipart/alternative;\r\n boundary=%s\r\n", subWriter.Boundary()))
|
||||
// Write the header
|
||||
if err := headerToBytes(buff, header); err != nil {
|
||||
return nil, fmt.Errorf("Failed to render multipart message headers: %s", err)
|
||||
}
|
||||
// Create the body sections
|
||||
if e.Text != "" {
|
||||
header.Set("Content-Type", fmt.Sprintf("text/plain; charset=UTF-8"))
|
||||
header.Set("Content-Transfer-Encoding", "quoted-printable")
|
||||
if _, err := subWriter.CreatePart(header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Write the text
|
||||
if err := quotePrintEncode(buff, e.Text); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if e.HTML != "" {
|
||||
header.Set("Content-Type", fmt.Sprintf("text/html; charset=UTF-8"))
|
||||
header.Set("Content-Transfer-Encoding", "quoted-printable")
|
||||
if _, err := subWriter.CreatePart(header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Write the text
|
||||
if err := quotePrintEncode(buff, e.HTML); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := subWriter.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Create attachment part, if necessary
|
||||
for _, a := range e.Attachments {
|
||||
ap, err := w.CreatePart(a.Header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Write the base64Wrapped content to the part
|
||||
base64Wrap(ap, a.Content)
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buff.Bytes(), nil
|
||||
}
|
||||
|
||||
// AttachFile Add attach file to the send mail
|
||||
func (e *Email) AttachFile(args ...string) (a *Attachment, err error) {
|
||||
if len(args) < 1 || len(args) > 2 { // change && to ||
|
||||
err = errors.New("Must specify a file name and number of parameters can not exceed at least two")
|
||||
return
|
||||
}
|
||||
filename := args[0]
|
||||
id := ""
|
||||
if len(args) > 1 {
|
||||
id = args[1]
|
||||
}
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
ct := mime.TypeByExtension(filepath.Ext(filename))
|
||||
basename := path.Base(filename)
|
||||
return e.Attach(f, basename, ct, id)
|
||||
}
|
||||
|
||||
// Attach is used to attach content from an io.Reader to the email.
|
||||
// Parameters include an io.Reader, the desired filename for the attachment, and the Content-Type.
|
||||
func (e *Email) Attach(r io.Reader, filename string, args ...string) (a *Attachment, err error) {
|
||||
if len(args) < 1 || len(args) > 2 { // change && to ||
|
||||
err = errors.New("Must specify the file type and number of parameters can not exceed at least two")
|
||||
return
|
||||
}
|
||||
c := args[0] // Content-Type
|
||||
id := ""
|
||||
if len(args) > 1 {
|
||||
id = args[1] // Content-ID
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
if _, err = io.Copy(&buffer, r); err != nil {
|
||||
return
|
||||
}
|
||||
at := &Attachment{
|
||||
Filename: filename,
|
||||
Header: textproto.MIMEHeader{},
|
||||
Content: buffer.Bytes(),
|
||||
}
|
||||
// Get the Content-Type to be used in the MIMEHeader
|
||||
if c != "" {
|
||||
at.Header.Set("Content-Type", c)
|
||||
} else {
|
||||
// If the Content-Type is blank, set the Content-Type to "application/octet-stream"
|
||||
at.Header.Set("Content-Type", "application/octet-stream")
|
||||
}
|
||||
if id != "" {
|
||||
at.Header.Set("Content-Disposition", fmt.Sprintf("inline;\r\n filename=\"%s\"", filename))
|
||||
at.Header.Set("Content-ID", fmt.Sprintf("<%s>", id))
|
||||
} else {
|
||||
at.Header.Set("Content-Disposition", fmt.Sprintf("attachment;\r\n filename=\"%s\"", filename))
|
||||
}
|
||||
at.Header.Set("Content-Transfer-Encoding", "base64")
|
||||
e.Attachments = append(e.Attachments, at)
|
||||
return at, nil
|
||||
}
|
||||
|
||||
// Send will send out the mail
|
||||
func (e *Email) Send() error {
|
||||
if e.Auth == nil {
|
||||
e.Auth = smtp.PlainAuth(e.Identity, e.Username, e.Password, e.Host)
|
||||
}
|
||||
// Merge the To, Cc, and Bcc fields
|
||||
to := make([]string, 0, len(e.To)+len(e.Cc)+len(e.Bcc))
|
||||
to = append(append(append(to, e.To...), e.Cc...), e.Bcc...)
|
||||
// Check to make sure there is at least one recipient and one "From" address
|
||||
if len(to) == 0 {
|
||||
return errors.New("Must specify at least one To address")
|
||||
}
|
||||
|
||||
// Use the username if no From is provided
|
||||
if len(e.From) == 0 {
|
||||
e.From = e.Username
|
||||
}
|
||||
|
||||
from, err := mail.ParseAddress(e.From)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// use mail's RFC 2047 to encode any string
|
||||
e.Subject = qEncode("utf-8", e.Subject)
|
||||
|
||||
raw, err := e.Bytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return smtp.SendMail(e.Host+":"+strconv.Itoa(e.Port), e.Auth, from.Address, to, raw)
|
||||
}
|
||||
|
||||
// quotePrintEncode writes the quoted-printable text to the IO Writer (according to RFC 2045)
|
||||
func quotePrintEncode(w io.Writer, s string) error {
|
||||
var buf [3]byte
|
||||
mc := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
// We're assuming Unix style text formats as input (LF line break), and
|
||||
// quoted-printble uses CRLF line breaks. (Literal CRs will become
|
||||
// "=0D", but probably shouldn't be there to begin with!)
|
||||
if c == '\n' {
|
||||
io.WriteString(w, "\r\n")
|
||||
mc = 0
|
||||
continue
|
||||
}
|
||||
|
||||
var nextOut []byte
|
||||
if isPrintable(c) {
|
||||
nextOut = append(buf[:0], c)
|
||||
} else {
|
||||
nextOut = buf[:]
|
||||
qpEscape(nextOut, c)
|
||||
}
|
||||
|
||||
// Add a soft line break if the next (encoded) byte would push this line
|
||||
// to or past the limit.
|
||||
if mc+len(nextOut) >= maxLineLength {
|
||||
if _, err := io.WriteString(w, "=\r\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
mc = 0
|
||||
}
|
||||
|
||||
if _, err := w.Write(nextOut); err != nil {
|
||||
return err
|
||||
}
|
||||
mc += len(nextOut)
|
||||
}
|
||||
// No trailing end-of-line?? Soft line break, then. TODO: is this sane?
|
||||
if mc > 0 {
|
||||
io.WriteString(w, "=\r\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isPrintable returns true if the rune given is "printable" according to RFC 2045, false otherwise
|
||||
func isPrintable(c byte) bool {
|
||||
return (c >= '!' && c <= '<') || (c >= '>' && c <= '~') || (c == ' ' || c == '\n' || c == '\t')
|
||||
}
|
||||
|
||||
// qpEscape is a helper function for quotePrintEncode which escapes a
|
||||
// non-printable byte. Expects len(dest) == 3.
|
||||
func qpEscape(dest []byte, c byte) {
|
||||
const nums = "0123456789ABCDEF"
|
||||
dest[0] = '='
|
||||
dest[1] = nums[(c&0xf0)>>4]
|
||||
dest[2] = nums[(c & 0xf)]
|
||||
}
|
||||
|
||||
// headerToBytes enumerates the key and values in the header, and writes the results to the IO Writer
|
||||
func headerToBytes(w io.Writer, t textproto.MIMEHeader) error {
|
||||
for k, v := range t {
|
||||
// Write the header key
|
||||
_, err := fmt.Fprintf(w, "%s:", k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Write each value in the header
|
||||
for _, c := range v {
|
||||
_, err := fmt.Fprintf(w, " %s\r\n", c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// base64Wrap encodes the attachment content, and wraps it according to RFC 2045 standards (every 76 chars)
|
||||
// The output is then written to the specified io.Writer
|
||||
func base64Wrap(w io.Writer, b []byte) {
|
||||
// 57 raw bytes per 76-byte base64 line.
|
||||
const maxRaw = 57
|
||||
// Buffer for each line, including trailing CRLF.
|
||||
var buffer [maxLineLength + len("\r\n")]byte
|
||||
copy(buffer[maxLineLength:], "\r\n")
|
||||
// Process raw chunks until there's no longer enough to fill a line.
|
||||
for len(b) >= maxRaw {
|
||||
base64.StdEncoding.Encode(buffer[:], b[:maxRaw])
|
||||
w.Write(buffer[:])
|
||||
b = b[maxRaw:]
|
||||
}
|
||||
// Handle the last chunk of bytes.
|
||||
if len(b) > 0 {
|
||||
out := buffer[:base64.StdEncoding.EncodedLen(len(b))]
|
||||
base64.StdEncoding.Encode(out, b)
|
||||
out = append(out, "\r\n"...)
|
||||
w.Write(out)
|
||||
}
|
||||
}
|
||||
|
||||
// Encode returns the encoded-word form of s. If s is ASCII without special
|
||||
// characters, it is returned unchanged. The provided charset is the IANA
|
||||
// charset name of s. It is case insensitive.
|
||||
// RFC 2047 encoded-word
|
||||
func qEncode(charset, s string) string {
|
||||
if !needsEncoding(s) {
|
||||
return s
|
||||
}
|
||||
return encodeWord(charset, s)
|
||||
}
|
||||
|
||||
func needsEncoding(s string) bool {
|
||||
for _, b := range s {
|
||||
if (b < ' ' || b > '~') && b != '\t' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// encodeWord encodes a string into an encoded-word.
|
||||
func encodeWord(charset, s string) string {
|
||||
buf := getBuffer()
|
||||
|
||||
buf.WriteString("=?")
|
||||
buf.WriteString(charset)
|
||||
buf.WriteByte('?')
|
||||
buf.WriteByte('q')
|
||||
buf.WriteByte('?')
|
||||
|
||||
enc := make([]byte, 3)
|
||||
for i := 0; i < len(s); i++ {
|
||||
b := s[i]
|
||||
switch {
|
||||
case b == ' ':
|
||||
buf.WriteByte('_')
|
||||
case b <= '~' && b >= '!' && b != '=' && b != '?' && b != '_':
|
||||
buf.WriteByte(b)
|
||||
default:
|
||||
enc[0] = '='
|
||||
enc[1] = upperhex[b>>4]
|
||||
enc[2] = upperhex[b&0x0f]
|
||||
buf.Write(enc)
|
||||
}
|
||||
}
|
||||
buf.WriteString("?=")
|
||||
|
||||
es := buf.String()
|
||||
putBuffer(buf)
|
||||
return es
|
||||
}
|
||||
|
||||
var bufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return new(bytes.Buffer)
|
||||
},
|
||||
}
|
||||
|
||||
func getBuffer() *bytes.Buffer {
|
||||
return bufPool.Get().(*bytes.Buffer)
|
||||
}
|
||||
|
||||
func putBuffer(buf *bytes.Buffer) {
|
||||
if buf.Len() > 1024 {
|
||||
return
|
||||
}
|
||||
buf.Reset()
|
||||
bufPool.Put(buf)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue