2024-07-18 11:51:12 +02:00
|
|
|
package dbs
|
|
|
|
|
|
|
|
|
|
import (
|
2024-08-01 09:13:10 +02:00
|
|
|
"fmt"
|
2026-04-07 08:32:42 +02:00
|
|
|
"reflect"
|
2026-01-26 10:36:15 +01:00
|
|
|
"runtime/debug"
|
2024-08-01 09:13:10 +02:00
|
|
|
"strings"
|
|
|
|
|
|
2024-07-18 11:51:12 +02:00
|
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
|
|
|
)
|
|
|
|
|
|
2024-08-01 09:13:10 +02:00
|
|
|
type Operator int
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
LIKE Operator = iota
|
|
|
|
|
EXISTS
|
|
|
|
|
IN
|
|
|
|
|
GTE
|
|
|
|
|
LTE
|
|
|
|
|
LT
|
|
|
|
|
GT
|
|
|
|
|
EQUAL
|
2024-08-12 16:11:25 +02:00
|
|
|
NOT
|
2026-01-26 10:36:15 +01:00
|
|
|
ELEMMATCH
|
2026-01-27 10:59:38 +01:00
|
|
|
OR
|
2024-08-01 09:13:10 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var str = [...]string{
|
|
|
|
|
"like",
|
|
|
|
|
"exists",
|
|
|
|
|
"in",
|
|
|
|
|
"gte",
|
|
|
|
|
"lte",
|
|
|
|
|
"lt",
|
|
|
|
|
"gt",
|
|
|
|
|
"equal",
|
2024-08-12 16:11:25 +02:00
|
|
|
"not",
|
2026-01-26 10:36:15 +01:00
|
|
|
"elemMatch",
|
2026-03-04 15:39:17 +01:00
|
|
|
"or",
|
2024-08-01 09:13:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m Operator) String() string {
|
|
|
|
|
return str[m]
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-26 10:36:15 +01:00
|
|
|
func (m Operator) ToMongoOperator(k string, value interface{}) bson.M {
|
2024-08-12 16:11:25 +02:00
|
|
|
defer func() {
|
|
|
|
|
if r := recover(); r != nil {
|
2026-01-26 10:36:15 +01:00
|
|
|
fmt.Println("Recovered. Error:\n", r, debug.Stack())
|
2024-08-12 16:11:25 +02:00
|
|
|
}
|
|
|
|
|
}()
|
2026-03-04 15:57:47 +01:00
|
|
|
defaultValue := bson.M{k: bson.M{"$regex": m.ToValueOperator(StringToOperator(m.String()), value, false)}}
|
2024-08-01 09:13:10 +02:00
|
|
|
switch m {
|
|
|
|
|
case LIKE:
|
2026-03-04 15:57:47 +01:00
|
|
|
return bson.M{k: bson.M{"$regex": m.ToValueOperator(StringToOperator(m.String()), value, false)}}
|
2024-08-01 09:13:10 +02:00
|
|
|
case EXISTS:
|
2026-03-04 15:57:47 +01:00
|
|
|
return bson.M{k: bson.M{"$exists": m.ToValueOperator(StringToOperator(m.String()), value, false)}}
|
2024-08-01 09:13:10 +02:00
|
|
|
case IN:
|
2026-03-04 15:57:47 +01:00
|
|
|
return bson.M{k: bson.M{"$in": m.ToValueOperator(StringToOperator(m.String()), value, false)}}
|
2024-08-01 09:13:10 +02:00
|
|
|
case GTE:
|
2026-03-04 15:57:47 +01:00
|
|
|
return bson.M{k: bson.M{"$gte": m.ToValueOperator(StringToOperator(m.String()), value, false)}}
|
2024-08-01 09:13:10 +02:00
|
|
|
case GT:
|
2026-03-04 15:57:47 +01:00
|
|
|
return bson.M{k: bson.M{"$gt": m.ToValueOperator(StringToOperator(m.String()), value, false)}}
|
2024-08-01 09:13:10 +02:00
|
|
|
case LTE:
|
2026-03-04 15:57:47 +01:00
|
|
|
return bson.M{k: bson.M{"$lte": m.ToValueOperator(StringToOperator(m.String()), value, false)}}
|
2024-08-01 09:13:10 +02:00
|
|
|
case LT:
|
2026-03-04 15:57:47 +01:00
|
|
|
return bson.M{k: bson.M{"$lt": m.ToValueOperator(StringToOperator(m.String()), value, false)}}
|
2026-01-26 10:36:15 +01:00
|
|
|
case ELEMMATCH:
|
2026-03-04 15:57:47 +01:00
|
|
|
return bson.M{k: bson.M{"$elemMatch": m.ToValueOperator(StringToOperator(m.String()), value, false)}}
|
2024-08-01 09:13:10 +02:00
|
|
|
case EQUAL:
|
2026-01-26 10:36:15 +01:00
|
|
|
return bson.M{k: value}
|
2024-08-12 16:11:25 +02:00
|
|
|
case NOT:
|
2026-03-04 15:57:47 +01:00
|
|
|
return bson.M{"$not": m.ToValueOperator(StringToOperator(m.String()), value, false)}
|
2026-01-27 10:59:38 +01:00
|
|
|
case OR:
|
2026-03-04 15:57:47 +01:00
|
|
|
return bson.M{"$or": m.ToValueOperator(StringToOperator(m.String()), value, true)}
|
2024-08-01 09:13:10 +02:00
|
|
|
default:
|
2024-08-12 16:11:25 +02:00
|
|
|
return defaultValue
|
2024-08-02 13:10:27 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-26 10:36:15 +01:00
|
|
|
func StringToOperator(s string) Operator {
|
|
|
|
|
for i, v := range str {
|
|
|
|
|
if v == s {
|
|
|
|
|
return Operator(i)
|
2024-08-12 16:11:25 +02:00
|
|
|
}
|
2026-01-26 10:36:15 +01:00
|
|
|
}
|
|
|
|
|
return LIKE
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GetBson(filters *Filters) bson.D {
|
|
|
|
|
f := bson.D{}
|
|
|
|
|
orList := bson.A{}
|
|
|
|
|
andList := bson.A{}
|
|
|
|
|
if filters != nil {
|
|
|
|
|
for k, filter := range filters.Or {
|
2024-08-12 16:11:25 +02:00
|
|
|
for _, ff := range filter {
|
|
|
|
|
orList = append(orList, StringToOperator(ff.Operator).ToMongoOperator(k, ff.Value))
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-26 10:36:15 +01:00
|
|
|
for k, filter := range filters.And {
|
2024-08-12 16:11:25 +02:00
|
|
|
for _, ff := range filter {
|
|
|
|
|
andList = append(andList, StringToOperator(ff.Operator).ToMongoOperator(k, ff.Value))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if len(orList) > 0 && len(andList) == 0 {
|
2026-04-03 14:18:07 +02:00
|
|
|
f = bson.D{{Key: "$or", Value: orList}}
|
2024-08-12 16:11:25 +02:00
|
|
|
} else {
|
|
|
|
|
if len(orList) > 0 {
|
|
|
|
|
andList = append(andList, bson.M{"$or": orList})
|
|
|
|
|
}
|
2026-04-03 14:18:07 +02:00
|
|
|
f = bson.D{{Key: "$and", Value: andList}}
|
2024-08-12 16:11:25 +02:00
|
|
|
}
|
2024-08-01 09:13:10 +02:00
|
|
|
}
|
2026-01-26 10:36:15 +01:00
|
|
|
return f
|
2024-08-01 09:13:10 +02:00
|
|
|
}
|
|
|
|
|
|
2026-03-04 15:57:47 +01:00
|
|
|
func (m Operator) ToValueOperator(operator Operator, value interface{}, or bool) interface{} {
|
2026-01-27 10:59:38 +01:00
|
|
|
switch value := value.(type) {
|
2026-01-26 10:36:15 +01:00
|
|
|
case *Filters:
|
2026-03-04 15:57:47 +01:00
|
|
|
bson := GetBson(value)
|
|
|
|
|
if or {
|
|
|
|
|
for _, b := range bson {
|
|
|
|
|
if b.Key == "$or" {
|
|
|
|
|
return b.Value
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return bson
|
|
|
|
|
}
|
2026-01-26 10:36:15 +01:00
|
|
|
default:
|
|
|
|
|
if strings.TrimSpace(fmt.Sprintf("%v", value)) == "*" {
|
|
|
|
|
value = ""
|
|
|
|
|
}
|
|
|
|
|
if operator == LIKE {
|
|
|
|
|
return "(?i).*" + strings.TrimSpace(fmt.Sprintf("%v", value)) + ".*"
|
2024-08-01 09:13:10 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Filters struct {
|
2024-08-02 14:07:43 +02:00
|
|
|
And map[string][]Filter `json:"and"`
|
|
|
|
|
Or map[string][]Filter `json:"or"`
|
2024-08-01 09:13:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Filter struct {
|
|
|
|
|
Operator string `json:"operator,omitempty"`
|
|
|
|
|
Value interface{} `json:"value,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 08:32:42 +02:00
|
|
|
// FiltersFromFlatMap builds a *Filters from a map[string]interface{} whose structure
|
|
|
|
|
// mirrors the JSON form of Filters:
|
|
|
|
|
//
|
|
|
|
|
// {
|
|
|
|
|
// "and": { "name": [{"operator":"like","value":"foo"}] },
|
|
|
|
|
// "or": { "source": [{"operator":"equal","value":"bar"}] }
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// Keys inside "and"/"or" are json tag names; the function resolves each to its
|
|
|
|
|
// full dotted BSON path using the target struct. Unknown keys are kept as-is.
|
|
|
|
|
func FiltersFromFlatMap(flatMap map[string]interface{}, target interface{}) *Filters {
|
|
|
|
|
filters := &Filters{
|
|
|
|
|
And: make(map[string][]Filter),
|
|
|
|
|
Or: make(map[string][]Filter),
|
|
|
|
|
}
|
|
|
|
|
paths := jsonToBsonPaths(reflect.TypeOf(target), "")
|
|
|
|
|
resolve := func(jsonKey string) string {
|
|
|
|
|
if p, ok := paths[jsonKey]; ok {
|
|
|
|
|
return p
|
|
|
|
|
}
|
|
|
|
|
return jsonKey
|
|
|
|
|
}
|
|
|
|
|
parseFilters := func(raw interface{}) map[string][]Filter {
|
|
|
|
|
out := make(map[string][]Filter)
|
|
|
|
|
m, ok := raw.(map[string]interface{})
|
|
|
|
|
if !ok {
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
for jsonKey, val := range m {
|
|
|
|
|
bsonKey := resolve(jsonKey)
|
|
|
|
|
items, ok := val.([]interface{})
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
for _, item := range items {
|
|
|
|
|
entry, ok := item.(map[string]interface{})
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
f := Filter{}
|
|
|
|
|
if op, ok := entry["operator"].(string); ok {
|
|
|
|
|
f.Operator = op
|
|
|
|
|
}
|
|
|
|
|
if v, ok := entry["value"]; ok {
|
|
|
|
|
f.Value = v
|
|
|
|
|
}
|
|
|
|
|
out[bsonKey] = append(out[bsonKey], f)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
if and, ok := flatMap["and"]; ok {
|
|
|
|
|
filters.And = parseFilters(and)
|
|
|
|
|
}
|
|
|
|
|
if or, ok := flatMap["or"]; ok {
|
|
|
|
|
filters.Or = parseFilters(or)
|
|
|
|
|
}
|
|
|
|
|
return filters
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// jsonToBsonPaths recursively walks a struct type and returns a map of
|
|
|
|
|
// json_name → dotted_bson_path for every field reachable from that type.
|
|
|
|
|
//
|
|
|
|
|
// Anonymous embedded fields without any tag follow the BSON convention of this
|
|
|
|
|
// codebase: they are stored as a nested sub-document whose key is the lowercased
|
|
|
|
|
// struct type name (e.g. utils.AbstractObject → "abstractobject").
|
|
|
|
|
func jsonToBsonPaths(t reflect.Type, prefix string) map[string]string {
|
|
|
|
|
for t.Kind() == reflect.Ptr || t.Kind() == reflect.Slice {
|
|
|
|
|
t = t.Elem()
|
|
|
|
|
}
|
|
|
|
|
result := make(map[string]string)
|
|
|
|
|
if t.Kind() != reflect.Struct {
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
for i := 0; i < t.NumField(); i++ {
|
|
|
|
|
field := t.Field(i)
|
|
|
|
|
|
|
|
|
|
jsonTag := field.Tag.Get("json")
|
|
|
|
|
bsonTag := field.Tag.Get("bson")
|
|
|
|
|
jsonName := strings.Split(jsonTag, ",")[0]
|
|
|
|
|
bsonName := strings.Split(bsonTag, ",")[0]
|
|
|
|
|
|
|
|
|
|
// Anonymous embedded struct with no tags: use lowercase type name as BSON prefix.
|
|
|
|
|
if field.Anonymous && jsonName == "" && bsonName == "" {
|
|
|
|
|
ft := field.Type
|
|
|
|
|
for ft.Kind() == reflect.Ptr {
|
|
|
|
|
ft = ft.Elem()
|
|
|
|
|
}
|
|
|
|
|
if ft.Kind() == reflect.Struct {
|
|
|
|
|
embedPrefix := strings.ToLower(ft.Name())
|
|
|
|
|
if prefix != "" {
|
|
|
|
|
embedPrefix = prefix + "." + embedPrefix
|
|
|
|
|
}
|
|
|
|
|
for k, v := range jsonToBsonPaths(ft, embedPrefix) {
|
|
|
|
|
if _, exists := result[k]; !exists {
|
|
|
|
|
result[k] = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if jsonName == "" || jsonName == "-" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if bsonName == "" || bsonName == "-" {
|
|
|
|
|
bsonName = jsonName
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fullPath := bsonName
|
|
|
|
|
if prefix != "" {
|
|
|
|
|
fullPath = prefix + "." + bsonName
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result[jsonName] = fullPath
|
|
|
|
|
|
|
|
|
|
ft := field.Type
|
|
|
|
|
for ft.Kind() == reflect.Ptr || ft.Kind() == reflect.Slice {
|
|
|
|
|
ft = ft.Elem()
|
|
|
|
|
}
|
|
|
|
|
if ft.Kind() == reflect.Struct {
|
|
|
|
|
for k, v := range jsonToBsonPaths(ft, fullPath) {
|
|
|
|
|
if _, exists := result[k]; !exists {
|
|
|
|
|
result[k] = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-18 11:51:12 +02:00
|
|
|
type Input = map[string]interface{}
|
|
|
|
|
|
|
|
|
|
func InputToBson(i Input, isUpdate bool) bson.D {
|
|
|
|
|
input := bson.D{}
|
|
|
|
|
for k, v := range i {
|
|
|
|
|
if k == "id" {
|
|
|
|
|
input = append(input, bson.E{Key: "_id", Value: v})
|
|
|
|
|
} else {
|
|
|
|
|
input = append(input, bson.E{Key: k, Value: v})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if isUpdate {
|
|
|
|
|
return bson.D{{Key: "$set", Value: input}}
|
|
|
|
|
}
|
|
|
|
|
return input
|
|
|
|
|
}
|