- 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
326 lines
7.9 KiB
Go
326 lines
7.9 KiB
Go
package orm
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/fatih/structtag"
|
|
"go.mongodb.org/mongo-driver/v2/bson"
|
|
"go.mongodb.org/mongo-driver/v2/mongo"
|
|
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
|
"reflect"
|
|
"time"
|
|
)
|
|
|
|
func serializeIDs(input interface{}, isJson bool, populated map[string]bool, parent string) interface{} {
|
|
var key string
|
|
if isJson {
|
|
key = "json"
|
|
} else {
|
|
key = "bson"
|
|
}
|
|
vp := reflect.ValueOf(input)
|
|
mt := reflect.TypeOf(input)
|
|
var ret interface{}
|
|
if vp.Kind() != reflect.Ptr {
|
|
if vp.CanAddr() {
|
|
vp = vp.Addr()
|
|
} else {
|
|
vp = makeSettable(vp, input)
|
|
}
|
|
}
|
|
|
|
if mt.Kind() == reflect.Pointer {
|
|
mt = mt.Elem()
|
|
}
|
|
getID := func(bbb interface{}) interface{} {
|
|
mptr := reflect.ValueOf(bbb)
|
|
if mptr.Kind() != reflect.Pointer {
|
|
mptr = makeSettable(mptr, bbb)
|
|
}
|
|
ifc, ok := mptr.Interface().(HasID)
|
|
if ok {
|
|
return ifc.Id()
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
switch vp.Elem().Kind() {
|
|
case reflect.Struct:
|
|
ret0 := bson.M{}
|
|
for i := 0; i < vp.Elem().NumField(); i++ {
|
|
fv := vp.Elem().Field(i)
|
|
ft := mt.Field(i)
|
|
var descent string
|
|
if parent != "" {
|
|
descent = parent + "." + ft.Name
|
|
} else {
|
|
descent = ft.Name
|
|
}
|
|
tag, err := structtag.Parse(string(ft.Tag))
|
|
panik(err)
|
|
bbson, err := tag.Get(key)
|
|
if err != nil || bbson.Name == "-" {
|
|
continue
|
|
}
|
|
if bbson.Name == "" {
|
|
marsh, _ := bson.Marshal(fv.Interface())
|
|
unmarsh := bson.M{}
|
|
bson.Unmarshal(marsh, &unmarsh)
|
|
for k, v := range unmarsh {
|
|
ret0[k] = v
|
|
}
|
|
} else {
|
|
_, terr := tag.Get("ref")
|
|
if reflect.ValueOf(fv.Interface()).Type().Kind() != reflect.Pointer {
|
|
vp1 := reflect.New(fv.Type())
|
|
vp1.Elem().Set(reflect.ValueOf(fv.Interface()))
|
|
fv.Set(vp1.Elem())
|
|
}
|
|
var ip bool
|
|
for k1, v1 := range populated {
|
|
if k1 == descent {
|
|
ip = v1
|
|
break
|
|
}
|
|
}
|
|
if terr == nil {
|
|
ifc, ok := fv.Interface().(HasID)
|
|
if fv.Kind() == reflect.Slice {
|
|
rarr := bson.A{}
|
|
for j := 0; j < fv.Len(); j++ {
|
|
if !isJson {
|
|
rarr = append(rarr, getID(fv.Index(j).Interface()))
|
|
} else {
|
|
if ip {
|
|
rarr = append(rarr, serializeIDs(fv.Index(j).Interface(), isJson, populated, descent))
|
|
} else {
|
|
rarr = append(rarr, getID(fv.Index(j).Interface()))
|
|
}
|
|
}
|
|
}
|
|
ret0[bbson.Name] = rarr
|
|
} else if !ok {
|
|
panic(fmt.Sprintf("referenced model slice at '%s.%s' does not implement HasID", nameOf(input), ft.Name))
|
|
} else {
|
|
if reflect.ValueOf(ifc).IsNil() || reflect.ValueOf(ifc.Id()).IsZero() {
|
|
ret0[bbson.Name] = nil
|
|
} else {
|
|
if !isJson {
|
|
ret0[bbson.Name] = ifc.Id()
|
|
} else {
|
|
if ip && bbson.Name != "-" {
|
|
ret0[bbson.Name] = serializeIDs(fv.Interface(), isJson, populated, descent)
|
|
} else if bbson.Name != "-" {
|
|
ret0[bbson.Name] = ifc.Id()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if fv.Type() == reflect.TypeFor[time.Time]() {
|
|
ret0[bbson.Name] = fv.Interface()
|
|
} else {
|
|
ret0[bbson.Name] = serializeIDs(fv.Interface(), isJson, populated, descent)
|
|
}
|
|
}
|
|
}
|
|
|
|
ret = ret0
|
|
}
|
|
case reflect.Slice:
|
|
ret0 := bson.A{}
|
|
for i := 0; i < vp.Elem().Len(); i++ {
|
|
|
|
ret0 = append(ret0, serializeIDs(vp.Elem().Index(i).Addr().Interface(), isJson, populated, parent))
|
|
}
|
|
ret = ret0
|
|
default:
|
|
ret = vp.Elem().Interface()
|
|
}
|
|
return ret
|
|
}
|
|
func doSave(c *mongo.Collection, isNew bool, opts *SaveOptions, arg interface{}) error {
|
|
var err error
|
|
d, ok := arg.(IDocument)
|
|
if !ok {
|
|
return fmt.Errorf(errFmtNotAModel, nameOf(arg))
|
|
}
|
|
d.SetSelf(d)
|
|
now := time.Now()
|
|
selfo := reflect.ValueOf(d)
|
|
vp := selfo
|
|
if vp.Kind() != reflect.Ptr {
|
|
vp = reflect.New(selfo.Type())
|
|
vp.Elem().Set(selfo)
|
|
}
|
|
var asHasId = vp.Interface().(HasID)
|
|
var asModel = vp.Interface().(IDocument)
|
|
if (isNew && reflect.ValueOf(asHasId.Id()).IsZero()) && opts.SetTimestamps {
|
|
d.setCreated(now)
|
|
}
|
|
if opts.SetTimestamps {
|
|
d.setModified(now)
|
|
}
|
|
idxs := d.getModel().getIdxs()
|
|
for _, i := range idxs {
|
|
_, err = c.Indexes().CreateOne(context.TODO(), *i)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if isNew {
|
|
nid := getLastInColl(c.Name(), asHasId.Id())
|
|
pnid := incrementInterface(nid)
|
|
if reflect.ValueOf(asHasId.Id()).IsZero() {
|
|
(asHasId).SetId(pnid)
|
|
}
|
|
incrementAll(asHasId)
|
|
_, im, _ := ModelRegistry.HasByName(asModel.getModel().getTypeName())
|
|
_ = gridFsSave(asHasId, *im)
|
|
|
|
_, err = c.InsertOne(context.TODO(), d.serializeToStore())
|
|
if err == nil {
|
|
d.setExists(true)
|
|
} else {
|
|
_, err = c.UpdateOne(context.TODO(), bson.D{{Key: "_id", Value: d.(HasID).Id()}}, bson.M{
|
|
"$set": d.serializeToStore(),
|
|
})
|
|
//_, err = c.ReplaceOne(context.TO_DO(), bson.D{{Key: "_id", Value: d.(HasID).Id()}}, d.serializeToStore())
|
|
}
|
|
} else {
|
|
//_, err = c.ReplaceOne(context.TO_DO(), bson.D{{Key: "_id", Value: d.(HasID).Id()}}, d.serializeToStore())
|
|
_, err = c.UpdateOne(context.TODO(), bson.D{{Key: "_id", Value: d.(HasID).Id()}}, bson.M{
|
|
"$set": d.serializeToStore(),
|
|
})
|
|
if errors.Is(err, mongo.ErrNoDocuments) {
|
|
_, err = c.InsertOne(context.TODO(), d.serializeToStore())
|
|
if err == nil {
|
|
d.setExists(true)
|
|
}
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
func doDelete(d *Document, arg interface{}) error {
|
|
self, ok := arg.(HasID)
|
|
|
|
if !ok {
|
|
return fmt.Errorf(errFmtNotHasID, nameOf(arg))
|
|
}
|
|
c := d.model.getColl()
|
|
_, err := c.DeleteOne(context.TODO(), bson.M{"_id": self.Id()})
|
|
if err == nil {
|
|
d.exists = false
|
|
err = gridFsDel(arg, *d.model)
|
|
}
|
|
return err
|
|
}
|
|
func incrementTagged(item interface{}) interface{} {
|
|
rv := reflect.ValueOf(item)
|
|
rt := reflect.TypeOf(item)
|
|
if rv.Kind() != reflect.Pointer {
|
|
rv = makeSettable(rv, item)
|
|
}
|
|
if rt.Kind() == reflect.Pointer {
|
|
rt = rt.Elem()
|
|
}
|
|
if rt.Kind() != reflect.Struct {
|
|
if rt.Kind() == reflect.Slice {
|
|
for i := 0; i < rv.Elem().Len(); i++ {
|
|
incrementTagged(rv.Elem().Index(i).Addr().Interface())
|
|
}
|
|
} else {
|
|
return item
|
|
}
|
|
}
|
|
for i := 0; i < rt.NumField(); i++ {
|
|
structField := rt.Field(i)
|
|
cur := rv.Elem().Field(i)
|
|
tags, err := structtag.Parse(string(structField.Tag))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
incTag, err := tags.Get("autoinc")
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
nid := getLastInColl(incTag.Name, cur.Interface())
|
|
if cur.IsZero() {
|
|
coerced := coerceInt(reflect.ValueOf(incrementInterface(nid)), cur)
|
|
if coerced != nil {
|
|
cur.Set(reflect.ValueOf(coerced))
|
|
} else {
|
|
cur.Set(reflect.ValueOf(incrementInterface(nid)))
|
|
}
|
|
counterColl := DB.Collection(COUNTER_COL)
|
|
counterColl.UpdateOne(context.TODO(), bson.M{"collection": incTag.Name}, bson.M{"$set": bson.M{"collection": incTag.Name, "current": cur.Interface()}}, options.UpdateOne().SetUpsert(true))
|
|
}
|
|
|
|
}
|
|
return rv.Elem().Interface()
|
|
}
|
|
|
|
func incrementAll(item interface{}) {
|
|
if item == nil {
|
|
return
|
|
}
|
|
vp := reflect.ValueOf(item)
|
|
el := vp
|
|
if vp.Kind() == reflect.Pointer {
|
|
el = vp.Elem()
|
|
}
|
|
if vp.Kind() == reflect.Pointer && vp.IsNil() {
|
|
return
|
|
}
|
|
vt := el.Type()
|
|
switch el.Kind() {
|
|
case reflect.Struct:
|
|
incrementTagged(item)
|
|
for i := 0; i < el.NumField(); i++ {
|
|
fv := el.Field(i)
|
|
fst := vt.Field(i)
|
|
if !fst.IsExported() {
|
|
continue
|
|
}
|
|
incrementAll(fv.Interface())
|
|
}
|
|
case reflect.Slice:
|
|
for i := 0; i < el.Len(); i++ {
|
|
incd := incrementTagged(el.Index(i).Addr().Interface())
|
|
if reflect.ValueOf(incd).Kind() == reflect.Pointer {
|
|
el.Index(i).Set(reflect.ValueOf(incd).Elem())
|
|
} else {
|
|
el.Index(i).Set(reflect.ValueOf(incd))
|
|
}
|
|
}
|
|
default:
|
|
}
|
|
}
|
|
|
|
func (d *Document) markPopulated(field string) {
|
|
d.newPopulationMap()
|
|
d.populatedFields[field] = true
|
|
}
|
|
|
|
func (d *Document) markDepopulated(field string) {
|
|
d.newPopulationMap()
|
|
d.populatedFields[field] = false
|
|
}
|
|
|
|
func (d *Document) newPopulationMap() {
|
|
if d.populatedFields == nil {
|
|
d.populatedFields = make(map[string]bool)
|
|
}
|
|
}
|
|
|
|
func (d *Document) setRaw(raw any) {
|
|
d.raw = raw
|
|
}
|
|
|
|
func (d *Document) getRaw() any {
|
|
return d.raw
|
|
}
|