Files
oc-lib/dbs/dbs.go

299 lines
7.0 KiB
Go
Raw Normal View History

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
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",
"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 {
defer func() {
if r := recover(); r != nil {
2026-01-26 10:36:15 +01:00
fmt.Println("Recovered. Error:\n", r, debug.Stack())
}
}()
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}
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:
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)
}
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 {
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 {
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}}
} 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-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
}