mdbc/delete_scope.go
2022-02-23 16:59:45 +08:00

340 lines
8.2 KiB
Go

package mdbc
import (
"context"
"errors"
"fmt"
"reflect"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type deleteAction string
const (
deleteOne deleteAction = "deleteOne"
deleteMany deleteAction = "deleteMany"
)
type DeleteScope struct {
scope *Scope
cw *ctxWrap
err error
upsert *bool
id interface{}
filter interface{}
action deleteAction
opts *options.DeleteOptions
result *mongo.DeleteResult
}
// SetContext 设置上下文
func (ds *DeleteScope) SetContext(ctx context.Context) *DeleteScope {
if ds.cw == nil {
ds.cw = &ctxWrap{}
}
ds.cw.ctx = ctx
return ds
}
// SetUpsert 设置upsert属性
func (ds *DeleteScope) SetUpsert(upsert bool) *DeleteScope {
ds.upsert = &upsert
return ds
}
// SetID 有设置ID优先基于ID删除 其次基于filter删除
func (ds *DeleteScope) SetID(id interface{}) *DeleteScope {
ds.id = id
ds.filter = bson.M{"_id": ds.id}
return ds
}
// SetIDs 指定_id列表的方式进行删除 是 bson.M{"_id": {$in: []array}} 的快捷键
// 如果参数 ids 为空 将报错
func (ds *DeleteScope) SetIDs(ids []string) *DeleteScope {
if len(ids) == 0 {
ds.err = &ErrFilterEmpty
return ds
}
ds.filter = bson.M{"_id": bson.M{"$in": ids}}
return ds
}
// SetDeleteOption 设置删除选项
func (ds *DeleteScope) SetDeleteOption(opts options.DeleteOptions) *DeleteScope {
ds.opts = &opts
return ds
}
// SetFilter 设置过滤条件
// filter建议为map,因为它不需要进行额外的类型转换
// 若设置 filter 为struct 则会将第一级不为零值的属性平铺转换为map
// 若设置 filter 为一个切片 但基于以下规则 获取不到任何值时 将触发PANIC
// 若 filter 传递一个数组切片 如果结构为 []string/[]number/[]ObjectID 将被解析成 "_id": {"$in": []interface{}}
// 若 filter 只传递一个参数(即filterFields 为空) []struct/[]*struct 将从struct元素的tag中抽取字段_id的bson标签组成id的数组切片
// 并组装成 "$in": []interface 然后将其解析为 "_id": {"$in": []interface{}}
// 当然 在传递 []struct/[]*struct 后 可以指定第二参数 filterFields 用于获取 struct的指定字段(确保可访问该字段)的集合 作为查询条件
// 生成规则是: 基于可访问到的字段名 获取字段值 并按照其 bson_tag 将其组合成 $in 操作[无bson_tag则按传入的字段]
// 示例结构: User struct: {Id int bson:_id} {Name string bson:name} {Age int bson: age}
// 示例: ds.Many([]A{{Id: 1, Name: "张三", Age: 20},{Id: 2, Name: "李四", Age: 22}}, "Id", "Age")
// 生成: filter := bson.M{"_id": {"$in": [1,2]}, "age": {"$in": [20, 22]}}
func (ds *DeleteScope) SetFilter(filter interface{}, filterFields ...string) *DeleteScope {
if filter == nil {
ds.filter = bson.M{}
return ds
}
// 空指针
vo := reflect.ValueOf(filter)
if vo.Kind() == reflect.Ptr || vo.Kind() == reflect.Map ||
vo.Kind() == reflect.Slice || vo.Kind() == reflect.Interface {
if vo.IsNil() {
ds.filter = bson.M{}
return ds
}
}
if vo.Kind() == reflect.Ptr {
vo = vo.Elem()
}
// 是一个结构体 剥离零值字段
if vo.Kind() == reflect.Struct {
ds.filter = Struct2MapOmitEmptyWithBsonTag(filter)
return ds
}
// 是一个map
if vo.Kind() == reflect.Map {
ds.filter = filter
return ds
}
// 对于数组切片的struct将获取字段的和ID相关的数组转$in操作
if vo.Kind() == reflect.Array || vo.Kind() == reflect.Slice {
var filterMap = make(map[string][]interface{})
// 对于数组切片的number,string,objectID
for i := 0; i < vo.Len(); i++ {
cvo := vo.Index(i)
if cvo.Kind() == reflect.Ptr {
cvo = cvo.Elem()
}
// 是数值类型
if isReflectNumber(cvo) {
filterMap["_id"] = append(filterMap["_id"], cvo.Interface())
continue
}
// 是字符串
if cvo.Kind() == reflect.String {
filterMap["_id"] = append(filterMap["_id"], cvo.Interface())
continue
}
// 来自mongo的ObjectID
if cvo.Type().String() == "primitive.ObjectID" {
filterMap["_id"] = append(filterMap["_id"], cvo.Interface())
continue
}
// 是struct 剥离字段
if cvo.Kind() == reflect.Struct {
if len(filterFields) == 0 {
for i := 0; i < cvo.Type().NumField(); i++ {
field := cvo.Type().Field(i)
if field.Tag.Get("bson") != "_id" {
continue
}
kk := cvo.FieldByName(field.Name)
if kk.IsValid() && !kk.IsZero() {
filterMap["_id"] = append(filterMap["_id"], kk.Interface())
}
}
} else {
for _, field := range filterFields {
bsonName := getBsonTagByReflectTypeField(cvo.Type(), field)
kk := cvo.FieldByName(field)
if kk.IsValid() && !kk.IsZero() {
filterMap[bsonName] = append(filterMap[bsonName], kk.Interface())
}
}
}
}
}
if len(filterMap) != 0 {
var f = bson.M{}
for key, val := range filterMap {
f[key] = bson.M{
"$in": val,
}
}
ds.filter = f
return ds
}
}
// 不符合参数接受条件 直接PANIC
panic("args invalid(required: map,struct,number slice,string slice,struct slice)")
}
func (ds *DeleteScope) optionAssembled() {
// 配置项被直接调用重写过
if ds.opts != nil {
return
}
ds.opts = new(options.DeleteOptions)
}
func (ds *DeleteScope) preCheck() {
var breakerTTL time.Duration
if ds.scope.breaker == nil {
breakerTTL = defaultBreakerTime
} else if ds.scope.breaker.ttl == 0 {
breakerTTL = defaultBreakerTime
} else {
breakerTTL = ds.scope.breaker.ttl
}
if ds.cw == nil {
ds.cw = &ctxWrap{}
}
if ds.cw.ctx == nil {
ds.cw.ctx, ds.cw.cancel = context.WithTimeout(context.Background(), breakerTTL)
}
if ds.filter == nil {
ds.err = &ErrFilterEmpty
return
}
}
func (ds *DeleteScope) assertErr() {
if ds.err == nil {
return
}
if errors.Is(ds.err, context.DeadlineExceeded) {
ds.err = &ErrRequestBroken
return
}
err, ok := ds.err.(mongo.CommandError)
if !ok {
return
}
if err.HasErrorMessage(context.DeadlineExceeded.Error()) {
ds.err = &ErrRequestBroken
}
}
func (ds *DeleteScope) getContext() context.Context {
return ds.cw.ctx
}
func (ds *DeleteScope) doClear() {
if ds.cw != nil && ds.cw.cancel != nil {
ds.cw.cancel()
}
ds.scope.execT = 0
ds.scope.debug = false
}
func (ds *DeleteScope) debug() {
if !ds.scope.debug && !ds.scope.debugWhenError {
return
}
debugger := &Debugger{
collection: ds.scope.tableName,
execT: ds.scope.execT,
action: ds,
}
// 当错误时输出
if ds.scope.debugWhenError {
if ds.err != nil {
debugger.errMsg = ds.err.Error()
debugger.ErrorString()
}
return
}
// 所有bug输出
if ds.scope.debug {
debugger.String()
}
}
func (ds *DeleteScope) doString() string {
builder := RegisterTimestampCodec(nil).Build()
filter, _ := bson.MarshalExtJSONWithRegistry(builder, ds.filter, true, true)
switch ds.action {
case deleteOne:
return fmt.Sprintf("deleteOne(%s)", string(filter))
case deleteMany:
return fmt.Sprintf("deleteMany(%s)", string(filter))
default:
panic("not support delete type")
}
}
func (ds *DeleteScope) doDelete(isMany bool) {
if isMany {
var starTime time.Time
if ds.scope.debug {
starTime = time.Now()
}
ds.result, ds.err = db.Collection(ds.scope.tableName).DeleteMany(ds.getContext(), ds.filter, ds.opts)
ds.assertErr()
if ds.scope.debug {
ds.action = deleteMany
ds.scope.execT = time.Since(starTime)
ds.debug()
}
return
}
var starTime time.Time
if ds.scope.debug {
starTime = time.Now()
}
ds.result, ds.err = db.Collection(ds.scope.tableName).DeleteOne(ds.getContext(), ds.filter, ds.opts)
ds.assertErr()
if ds.scope.debug {
ds.action = deleteOne
ds.scope.execT = time.Since(starTime)
ds.debug()
}
}
// One 删除单个文档 返回影响行数
func (ds *DeleteScope) One() (int64, error) {
defer ds.doClear()
ds.optionAssembled()
ds.preCheck()
if ds.err != nil {
return 0, ds.err
}
ds.doDelete(false)
if ds.err != nil {
return 0, ds.err
}
return ds.result.DeletedCount, nil
}
// Many 删除多个文档 返回影响行数
func (ds *DeleteScope) Many() (int64, error) {
defer ds.doClear()
ds.optionAssembled()
ds.preCheck()
if ds.err != nil {
return 0, ds.err
}
ds.doDelete(true)
if ds.err != nil {
return 0, ds.err
}
return ds.result.DeletedCount, nil
}