Files
oc-lib/dbs/dbs.go
T

333 lines
8.3 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-06-02 10:50:42 +02:00
"regexp"
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),
}
2026-06-02 11:35:19 +02:00
paths := jsonToBsonPaths(reflect.TypeOf(target), "", "")
2026-04-07 08:32:42 +02:00
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{})
2026-06-02 13:47:41 +02:00
fmt.Println(jsonKey, val, ok, bsonKey)
2026-04-07 08:32:42 +02:00
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
2026-06-02 11:35:19 +02:00
// struct type name (e.g. utils.AbstractObject → "abstractobject"). Their JSON
// fields are promoted (flat), so bsonPrefix advances but jsonPrefix does not.
//
// For fields inside slices or maps, both the leaf json name and the full dotted
// json path (e.g. "instances.access_protocol") are registered as keys so callers
// can use either form unambiguously.
func jsonToBsonPaths(t reflect.Type, bsonPrefix string, jsonPrefix string) map[string]string {
2026-04-07 08:32:42 +02:00
for t.Kind() == reflect.Ptr || t.Kind() == reflect.Slice {
t = t.Elem()
}
2026-06-02 11:35:19 +02:00
if t.Kind() == reflect.Map {
t = t.Elem()
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
}
2026-04-07 08:32:42 +02:00
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.
2026-06-02 11:35:19 +02:00
// JSON fields are promoted so jsonPrefix stays the same.
2026-04-07 08:32:42 +02:00
if field.Anonymous && jsonName == "" && bsonName == "" {
ft := field.Type
for ft.Kind() == reflect.Ptr {
ft = ft.Elem()
}
if ft.Kind() == reflect.Struct {
2026-06-02 11:35:19 +02:00
embedBsonPrefix := strings.ToLower(ft.Name())
2026-06-02 10:50:42 +02:00
re := regexp.MustCompile(`\[[^\]]*\]`)
2026-06-02 11:35:19 +02:00
embedBsonPrefix = re.ReplaceAllString(embedBsonPrefix, "")
embedBsonPrefix = strings.ReplaceAll(embedBsonPrefix, "*", "")
if bsonPrefix != "" {
embedBsonPrefix = bsonPrefix + "." + embedBsonPrefix
2026-04-07 08:32:42 +02:00
}
2026-06-02 11:35:19 +02:00
for k, v := range jsonToBsonPaths(ft, embedBsonPrefix, jsonPrefix) {
2026-04-07 08:32:42 +02:00
if _, exists := result[k]; !exists {
result[k] = v
}
}
}
continue
}
if jsonName == "" || jsonName == "-" {
continue
}
if bsonName == "" || bsonName == "-" {
bsonName = jsonName
}
2026-06-02 11:35:19 +02:00
fullBsonPath := bsonName
if bsonPrefix != "" {
fullBsonPath = bsonPrefix + "." + bsonName
}
fullJsonPath := jsonName
if jsonPrefix != "" {
fullJsonPath = jsonPrefix + "." + jsonName
2026-04-07 08:32:42 +02:00
}
2026-06-02 11:35:19 +02:00
result[jsonName] = fullBsonPath
// Also register the full dotted JSON path so callers can use
// "instances.access_protocol" instead of just "access_protocol".
if fullJsonPath != jsonName {
if _, exists := result[fullJsonPath]; !exists {
result[fullJsonPath] = fullBsonPath
}
}
2026-04-07 08:32:42 +02:00
ft := field.Type
for ft.Kind() == reflect.Ptr || ft.Kind() == reflect.Slice {
ft = ft.Elem()
}
2026-06-02 11:35:19 +02:00
if ft.Kind() == reflect.Map {
ft = ft.Elem()
for ft.Kind() == reflect.Ptr {
ft = ft.Elem()
}
}
2026-04-07 08:32:42 +02:00
if ft.Kind() == reflect.Struct {
2026-06-02 11:35:19 +02:00
for k, v := range jsonToBsonPaths(ft, fullBsonPath, fullJsonPath) {
2026-04-07 08:32:42 +02:00
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
}