- fix references in nested structs not being registered properly - rework struct tag parsing - remove debug logs - fix bug where an unrelated field with the same prefix as another population path is erroneously marked as populated
621 lines
14 KiB
Go
621 lines
14 KiB
Go
package orm
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"github.com/fatih/structtag"
|
|
"go.mongodb.org/mongo-driver/v2/bson"
|
|
"go.mongodb.org/mongo-driver/v2/mongo"
|
|
"reflect"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type Query struct {
|
|
collection *mongo.Collection
|
|
op string
|
|
model *Model
|
|
done bool
|
|
rawDoc any
|
|
doc any
|
|
}
|
|
|
|
const (
|
|
OP_FIND_ONE = "findOne"
|
|
OP_FIND_PAGED = "findPaged"
|
|
OP_FIND_ALL = "findAll"
|
|
OP_FIND = "find"
|
|
)
|
|
|
|
func populate(r Reference, rcoll string, rawDoc interface{}, d string, src interface{}) any {
|
|
rt := reflect.TypeOf(src)
|
|
rv := reflect.ValueOf(src)
|
|
srt := rt
|
|
if srt.Kind() == reflect.Pointer {
|
|
srt = rt.Elem()
|
|
}
|
|
if rv.Kind() == reflect.Pointer && reflect.ValueOf(src).IsNil() {
|
|
return src
|
|
}
|
|
if rv.Kind() != reflect.Pointer {
|
|
rv = reflect.New(rt)
|
|
rv.Elem().Set(reflect.ValueOf(src))
|
|
}
|
|
|
|
var fieldsMap [3]string
|
|
type bsonWhat struct {
|
|
What string
|
|
}
|
|
var isNotStructOrSlice bool
|
|
var w bsonWhat
|
|
switch rawDoc.(type) {
|
|
case bson.A:
|
|
w.What = "A"
|
|
case bson.D:
|
|
w.What = "D"
|
|
case bson.M:
|
|
w.What = "M"
|
|
default:
|
|
w.What = "-"
|
|
}
|
|
var next string
|
|
if len(strings.Split(d, ".")) > 1 {
|
|
next = strings.Join(strings.Split(d, ".")[1:], ".")
|
|
d = strings.Split(d, ".")[0]
|
|
} else {
|
|
next = d
|
|
}
|
|
var toReturn interface{}
|
|
switch w.What {
|
|
case "A":
|
|
rvs := reflect.MakeSlice(rt, 0, 0)
|
|
var rahh reflect.Value
|
|
if rv.Kind() == reflect.Ptr {
|
|
rahh = rv.Elem()
|
|
} else {
|
|
rahh = rv
|
|
}
|
|
for i, el := range rawDoc.(bson.A) {
|
|
it := rahh.Index(i)
|
|
popped := populate(r, rcoll, el, next, it.Interface())
|
|
if pidoc, pok := popped.(IDocument); pok {
|
|
pidoc.setRaw(el)
|
|
}
|
|
poppedVal := reflect.ValueOf(popped)
|
|
if poppedVal.Kind() == reflect.Pointer {
|
|
rvs = reflect.Append(rvs, poppedVal.Elem())
|
|
} else {
|
|
rvs = reflect.Append(rvs, poppedVal)
|
|
}
|
|
}
|
|
if rv.CanSet() {
|
|
rv.Set(rvs)
|
|
} else if rv.Kind() == reflect.Pointer {
|
|
rv.Elem().Set(rvs)
|
|
} else {
|
|
src = rvs.Interface()
|
|
toReturn = src
|
|
}
|
|
case "D":
|
|
loc := rawDoc.(bson.D)
|
|
nrd := bson.M{}
|
|
for _, el := range loc {
|
|
nrd[el.Key] = el.Value
|
|
}
|
|
rawDoc = nrd
|
|
fallthrough
|
|
case "M":
|
|
dd := rawDoc.(bson.M)
|
|
var sf reflect.Value
|
|
var rsf reflect.Value
|
|
if rv.Kind() == reflect.Pointer {
|
|
sf = rv.Elem().FieldByName(d)
|
|
} else {
|
|
sf = rv.FieldByName(d)
|
|
}
|
|
if rv.Kind() == reflect.Pointer {
|
|
rsf = rv.Elem().FieldByName(d)
|
|
} else {
|
|
rsf = rv.FieldByName(d)
|
|
}
|
|
|
|
var ff reflect.StructField
|
|
var ok bool
|
|
if rt.Kind() == reflect.Pointer {
|
|
ff, ok = rt.Elem().FieldByName(d)
|
|
} else {
|
|
ff, ok = rt.FieldByName(d)
|
|
}
|
|
|
|
if ok {
|
|
tag, err := structtag.Parse(string(ff.Tag))
|
|
if err == nil {
|
|
val, err2 := tag.Get("bson")
|
|
if err2 == nil && val.Name != "-" {
|
|
fttt := ff.Type
|
|
if fttt.Kind() == reflect.Pointer || fttt.Kind() == reflect.Slice {
|
|
fttt = fttt.Elem()
|
|
}
|
|
fieldsMap = [3]string{d, fttt.Name(), val.Name}
|
|
}
|
|
}
|
|
}
|
|
intermediate := populate(r, rcoll, dd[fieldsMap[2]], next, sf.Interface())
|
|
/*if iidoc, idocOk := intermediate.(IDocument); idocOk {
|
|
if (reflect.ValueOf(intermediate).CanAddr() && !reflect.ValueOf(intermediate).IsNil()) || !reflect.ValueOf(intermediate).IsZero() {
|
|
iiid, iok := intermediate.(HasID)
|
|
if intermediate != nil && iok && !reflect.ValueOf(iiid.Id()).IsZero() {
|
|
iidoc.setRaw(dd[fieldsMap[2]])
|
|
}
|
|
}
|
|
}*/
|
|
if rsf.CanSet() {
|
|
ival := reflect.ValueOf(intermediate)
|
|
if ival.Kind() != reflect.Pointer && rsf.Kind() == reflect.Pointer {
|
|
rsf.Set(ival.Elem())
|
|
} else if ival.Kind() == reflect.Pointer && rsf.Kind() != reflect.Pointer {
|
|
rsf.Set(ival.Elem())
|
|
} else {
|
|
rsf.Set(ival)
|
|
}
|
|
} else {
|
|
src = intermediate
|
|
}
|
|
default:
|
|
isNotStructOrSlice = true
|
|
tto := r.HydratedType
|
|
if tto.Kind() == reflect.Pointer || tto.Kind() == reflect.Slice {
|
|
tto = tto.Elem()
|
|
}
|
|
|
|
rawt := ModelRegistry.new_(tto.Name())
|
|
t := rawt.(IDocument)
|
|
q := bson.M{"_id": rawDoc}
|
|
reso := DB.Collection(rcoll).FindOne(context.TODO(), q)
|
|
if !errors.Is(reso.Err(), mongo.ErrNoDocuments) {
|
|
var anotherMap = make(bson.M)
|
|
reso.Decode(&anotherMap)
|
|
reflect.ValueOf(t).Elem().Set(reflect.ValueOf(rerere(anotherMap, tto, false)).Elem())
|
|
t.setRaw(anotherMap)
|
|
}
|
|
hatred := rv
|
|
if hatred.Kind() == reflect.Pointer {
|
|
hatred = hatred.Elem()
|
|
}
|
|
if hatred.CanSet() {
|
|
if reflect.ValueOf(t).Kind() == reflect.Pointer {
|
|
if hatred.Kind() == reflect.Pointer {
|
|
hatred.Set(reflect.ValueOf(t))
|
|
} else {
|
|
hatred.Set(reflect.ValueOf(t).Elem())
|
|
}
|
|
recover()
|
|
} else {
|
|
hatred.Set(reflect.ValueOf(t))
|
|
}
|
|
} else {
|
|
src = t
|
|
toReturn = src
|
|
}
|
|
t.SetSelf(t)
|
|
t.setExists(true)
|
|
}
|
|
|
|
if toReturn == nil {
|
|
sidoc, sok := rv.Interface().(IDocument)
|
|
if sok {
|
|
sidoc.SetSelf(rv.Interface())
|
|
if !isNotStructOrSlice {
|
|
sidoc.setRaw(rawDoc)
|
|
}
|
|
}
|
|
return rv.Interface()
|
|
}
|
|
sidoc, sok := src.(IDocument)
|
|
if sok {
|
|
sidoc.SetSelf(src)
|
|
if !isNotStructOrSlice {
|
|
sidoc.setRaw(rawDoc)
|
|
}
|
|
}
|
|
return src
|
|
}
|
|
|
|
// LoadFile - loads the contents of one or more files
|
|
// stored in gridFS into the fields named by `fields`.
|
|
//
|
|
// gridFS fields can be either a `string` or `[]byte`, and are
|
|
// tagged with `gridfs:"BUCKET,FILE_FORMAT`
|
|
// where:
|
|
// - `BUCKET` is the name of the bucket where the files are stored
|
|
// - `FILE_FORMAT` is any valid go template string that resolves to
|
|
// the unique file name.
|
|
// all exported values and methods present in the surrounding
|
|
// struct can be used in this template.
|
|
func (q *Query) LoadFile(fields ...string) *Query {
|
|
_, cm, _ := ModelRegistry.HasByName(q.model.typeName)
|
|
if cm != nil {
|
|
for _, field := range fields {
|
|
var r gridFSReference
|
|
hasAnnotated := false
|
|
for k2, v := range cm.gridFSReferences {
|
|
if strings.HasPrefix(k2, field) {
|
|
r = v
|
|
hasAnnotated = true
|
|
break
|
|
}
|
|
}
|
|
if hasAnnotated {
|
|
q.doc = gridFsLoad(q.doc, r, field)
|
|
}
|
|
}
|
|
}
|
|
return q
|
|
}
|
|
|
|
func readFields(field string, m *Model) (Reference, string) {
|
|
var r Reference
|
|
var keptKey string
|
|
if m == nil {
|
|
return r, keptKey
|
|
}
|
|
for k2, v := range m.references {
|
|
if strings.HasPrefix(k2, field) {
|
|
r = v
|
|
keptKey = k2
|
|
break
|
|
} else if _, ok := m.references[field]; !ok {
|
|
splitSegs := strings.Split(field, ".")
|
|
for ii := 0; ii < len(splitSegs)-1; ii++ {
|
|
mr, ok2 := m.references[splitSegs[ii]]
|
|
if ok2 {
|
|
_, sec, _ := ModelRegistry.HasByName(mr.Model)
|
|
if sec != nil {
|
|
refff, ok3 := sec.references[splitSegs[ii+1]]
|
|
if ok3 {
|
|
r = refff
|
|
keptKey = k2
|
|
break
|
|
} else {
|
|
joined := strings.Join(splitSegs[ii+1:], ".")
|
|
inner1, innerKey := readFields(joined, sec)
|
|
if inner1.exists {
|
|
r = inner1
|
|
keptKey = innerKey
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if ok {
|
|
r = m.references[field]
|
|
break
|
|
}
|
|
if keptKey != "" {
|
|
break
|
|
}
|
|
}
|
|
return r, keptKey
|
|
}
|
|
|
|
// Populate populates document references via reflection
|
|
func (q *Query) Populate(fields ...string) *Query {
|
|
vvv := reflect.ValueOf(q.doc)
|
|
if vvv.Kind() == reflect.Pointer {
|
|
vvv = vvv.Elem()
|
|
}
|
|
if vvv.Kind() == reflect.Slice {
|
|
typ := reflect.PointerTo(q.model.Type)
|
|
slic := reflect.New(reflect.SliceOf(typ))
|
|
for i := 0; i < vvv.Len(); i++ {
|
|
val2 := vvv.Index(i).Interface()
|
|
aid, docOk := val2.(IDocument)
|
|
if docOk {
|
|
rdoc := q.rawDoc.(bson.A)
|
|
aid.setRaw(rdoc[i])
|
|
aid.Populate(fields...)
|
|
if reflect.ValueOf(aid).Kind() == reflect.Pointer {
|
|
slic.Elem().Set(reflect.Append(slic.Elem(), reflect.ValueOf(aid)))
|
|
} else {
|
|
slic.Elem().Set(reflect.Append(slic, reflect.ValueOf(aid)))
|
|
}
|
|
}
|
|
}
|
|
q.doc = slic.Interface()
|
|
} else if asDoc, ok2 := q.doc.(IDocument); ok2 {
|
|
asDoc.setRaw(q.rawDoc)
|
|
asDoc.Populate(fields...)
|
|
}
|
|
return q
|
|
}
|
|
func (q *Query) reOrganize() {
|
|
var trvo reflect.Value
|
|
if arr, ok := q.rawDoc.(bson.A); ok {
|
|
typ := ModelRegistry[q.model.typeName].Type
|
|
if typ.Kind() != reflect.Pointer {
|
|
typ = reflect.PointerTo(typ)
|
|
}
|
|
slic := reflect.New(reflect.SliceOf(typ))
|
|
for _, v2 := range arr {
|
|
inter := reflect.ValueOf(rerere(v2, typ, false))
|
|
slic.Elem().Set(reflect.Append(slic.Elem(), inter))
|
|
}
|
|
trvo = slic.Elem()
|
|
} else {
|
|
trvo = reflect.ValueOf(rerere(q.rawDoc, reflect.TypeOf(q.doc), false))
|
|
}
|
|
|
|
resV := reflect.ValueOf(q.doc)
|
|
for {
|
|
if resV.Kind() == reflect.Pointer {
|
|
if resV.Elem().Kind() == reflect.Slice {
|
|
resV = resV.Elem()
|
|
} else {
|
|
break
|
|
}
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
if resV.CanSet() {
|
|
resV.Set(trvo)
|
|
} else {
|
|
resV.Elem().Set(trvo.Elem())
|
|
}
|
|
}
|
|
|
|
func rerere(input interface{}, resType reflect.Type, isJson bool) interface{} {
|
|
t := reflect.TypeOf(input)
|
|
v := reflect.ValueOf(input)
|
|
var key string
|
|
if isJson {
|
|
key = "json"
|
|
} else {
|
|
key = "bson"
|
|
}
|
|
if input == nil {
|
|
return nil
|
|
}
|
|
if v.Type().Kind() == reflect.Pointer {
|
|
v = v.Elem()
|
|
}
|
|
if t.Kind() == reflect.Pointer {
|
|
t = t.Elem()
|
|
}
|
|
|
|
if resType.Kind() == reflect.Pointer {
|
|
resType = resType.Elem()
|
|
}
|
|
var resV reflect.Value
|
|
var newInstance interface{}
|
|
if _, _, has := ModelRegistry.Has(reflect.New(resType).Elem().Interface()); has {
|
|
newInstance = Create(reflect.New(resType).Elem().Interface())
|
|
resV = reflect.ValueOf(newInstance)
|
|
} else {
|
|
newInstance = ModelRegistry.newForType(resType)
|
|
if newInstance == nil && resType.Kind() == reflect.Pointer {
|
|
newInstance = ModelRegistry.newForType(resType.Elem())
|
|
if newInstance == nil {
|
|
resV = reflect.New(resType)
|
|
} else {
|
|
resV = reflect.ValueOf(newInstance)
|
|
}
|
|
} else if newInstance != nil {
|
|
resV = reflect.ValueOf(newInstance)
|
|
} else {
|
|
resV = reflect.New(resType)
|
|
}
|
|
}
|
|
|
|
var rve = resV
|
|
if rve.Kind() == reflect.Pointer {
|
|
rve = resV.Elem()
|
|
}
|
|
|
|
if d, isD := v.Interface().(bson.D); isD {
|
|
m := bson.M{}
|
|
for _, el := range d {
|
|
m[el.Key] = el.Value
|
|
}
|
|
input = m
|
|
v = reflect.ValueOf(input)
|
|
}
|
|
|
|
switch resType.Kind() {
|
|
case reflect.Struct:
|
|
shouldBreak := false
|
|
mipmap, ok := v.Interface().(bson.M)
|
|
if !ok {
|
|
var omap map[string]interface{}
|
|
if omap, ok = v.Interface().(map[string]interface{}); ok {
|
|
mipmap = bson.M(omap)
|
|
}
|
|
}
|
|
if ok {
|
|
for i := 0; i < resType.NumField(); i++ {
|
|
ft := resType.Field(i)
|
|
fv := rve.Field(i)
|
|
if ft.Anonymous {
|
|
fv.Set(handleAnon(input, ft.Type, fv))
|
|
continue
|
|
}
|
|
tags, err := structtag.Parse(string(ft.Tag))
|
|
panik(err)
|
|
btag, err := tags.Get(key)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if btag.Name == "-" {
|
|
continue
|
|
}
|
|
intermediate := mipmap[btag.Name]
|
|
_, err = tags.Get("ref")
|
|
if err != nil {
|
|
var tmp interface{}
|
|
if ttmp, tok := intermediate.(bson.DateTime); tok {
|
|
tmp = ttmp.Time()
|
|
} else {
|
|
tmp = rerere(intermediate, ft.Type, isJson)
|
|
}
|
|
fuck := reflect.ValueOf(tmp)
|
|
if tmp != nil {
|
|
if fuck.Type().Kind() == reflect.Pointer {
|
|
fuck = fuck.Elem()
|
|
}
|
|
fv.Set(fuck)
|
|
}
|
|
shouldBreak = true
|
|
} else {
|
|
tt := ft.Type
|
|
if tt.Kind() == reflect.Pointer {
|
|
tt = tt.Elem()
|
|
}
|
|
tmp := rerere(intermediate, ft.Type, isJson)
|
|
if tmp != nil {
|
|
if reflect.ValueOf(tmp).Kind() == reflect.Pointer && fv.Kind() != reflect.Pointer {
|
|
fv.Set(reflect.ValueOf(tmp).Elem())
|
|
} else {
|
|
fv.Set(reflect.ValueOf(tmp))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if shouldBreak {
|
|
// break
|
|
}
|
|
} else {
|
|
nunu := ModelRegistry.new_(resType.Name())
|
|
ider, ok := nunu.(HasID)
|
|
if ok {
|
|
toConvert := reflect.ValueOf(ider.Id()).Type()
|
|
if v.Type() != toConvert {
|
|
if v.CanConvert(toConvert) {
|
|
ider.SetId(v.Convert(toConvert).Interface())
|
|
}
|
|
} else {
|
|
ider.SetId(v.Interface())
|
|
}
|
|
if reflect.ValueOf(ider).Kind() == reflect.Pointer {
|
|
nunu = reflect.ValueOf(ider).Elem().Interface()
|
|
}
|
|
rve.Set(reflect.ValueOf(nunu))
|
|
}
|
|
}
|
|
case reflect.Slice:
|
|
_, aOk := v.Interface().(bson.A)
|
|
var arr []interface{}
|
|
if !aOk {
|
|
arr = v.Interface().([]interface{})
|
|
} else {
|
|
arr = []interface{}(v.Interface().(bson.A))
|
|
}
|
|
for _, it := range arr {
|
|
if it != nil {
|
|
tmp := reflect.ValueOf(rerere(it, rve.Type().Elem(), isJson))
|
|
if tmp.Kind() == reflect.Pointer {
|
|
tmp = tmp.Elem()
|
|
}
|
|
rve.Set(reflect.Append(rve, tmp))
|
|
}
|
|
}
|
|
default:
|
|
if resType.AssignableTo(v.Type()) {
|
|
rve.Set(reflect.ValueOf(input))
|
|
} else {
|
|
switch rve.Interface().(type) {
|
|
case int, int32, int64, uint, uint32, uint64:
|
|
rve.Set(reflect.ValueOf(coerceInt(v, rve)))
|
|
}
|
|
}
|
|
}
|
|
|
|
return resV.Interface()
|
|
}
|
|
|
|
func handleAnon(raw interface{}, rtype reflect.Type, rval reflect.Value) reflect.Value {
|
|
f := rval
|
|
g := rtype
|
|
if rtype.Kind() == reflect.Pointer {
|
|
g = rtype.Elem()
|
|
}
|
|
if !f.CanSet() {
|
|
f = reflect.New(g)
|
|
f.Elem().Set(rval)
|
|
}
|
|
if rtype.Kind() != reflect.Struct {
|
|
return rval
|
|
}
|
|
|
|
for i := 0; i < rtype.NumField(); i++ {
|
|
typeField := rtype.Field(i)
|
|
valueField := f.Field(i)
|
|
tags, err := structtag.Parse(string(typeField.Tag))
|
|
if !typeField.IsExported() {
|
|
continue
|
|
}
|
|
if err == nil {
|
|
var btag *structtag.Tag
|
|
btag, err = tags.Get("bson")
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if btag.Name == "-" {
|
|
continue
|
|
}
|
|
amap, ok := raw.(bson.M)
|
|
if ok {
|
|
fval := amap[btag.Name]
|
|
if reflect.TypeOf(fval) == reflect.TypeFor[bson.DateTime]() {
|
|
fval = fval.(bson.DateTime).Time()
|
|
}
|
|
if valueField.Kind() == reflect.Pointer {
|
|
valueField.Elem().Set(reflect.ValueOf(fval))
|
|
} else {
|
|
if reflect.TypeOf(fval) == reflect.TypeFor[string]() && typeField.Type == reflect.TypeFor[time.Time]() {
|
|
tt, _ := time.Parse(time.RFC3339, fval.(string))
|
|
valueField.Set(reflect.ValueOf(tt))
|
|
} else {
|
|
valueField.Set(reflect.ValueOf(fval))
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return f
|
|
}
|
|
|
|
// JSON - marshals this Query's results into json format
|
|
func (q *Query) JSON() (string, error) {
|
|
res, err := json.MarshalIndent(q.doc, "", "\t")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(res[:]), nil
|
|
}
|
|
|
|
// Exec - executes the query and puts its results into the
|
|
// provided argument.
|
|
//
|
|
// Will panic if called more than once on the same Query instance.
|
|
func (q *Query) Exec(result interface{}) {
|
|
if q.done {
|
|
panic("Exec() has already been called!")
|
|
}
|
|
doc := reflect.ValueOf(q.doc)
|
|
if doc.Elem().Kind() == reflect.Slice {
|
|
for i := 0; i < doc.Elem().Len(); i++ {
|
|
cur := doc.Elem().Index(i)
|
|
imodel, ok := cur.Interface().(IDocument)
|
|
if ok {
|
|
imodel.setExists(true)
|
|
doc.Elem().Index(i).Set(reflect.ValueOf(imodel))
|
|
}
|
|
}
|
|
}
|
|
reflect.ValueOf(result).Elem().Set(reflect.ValueOf(q.doc).Elem())
|
|
q.done = true
|
|
}
|