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" "strings" "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 || k1 == parent { ip = v1 break } else if strings.HasPrefix(k1, descent) { ip = v1 } } 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 }