340 lines
8.2 KiB
Go
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
|
|
}
|