package orm import ( "context" "fmt" "reflect" "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) // Model - "base" struct for all queryable models type Model struct { // Created time. updated/added automatically. Created time.Time `bson:"createdAt" json:"createdAt"` // Modified time. updated/added automatically. Modified time.Time `bson:"updatedAt" json:"updatedAt"` typeName string `bson:"-"` self any `bson:"-"` exists bool `bson:"-"` } func (m *Model) getCreated() time.Time { return m.Created } func (m *Model) setCreated(Created time.Time) { m.Created = Created } func (m *Model) getModified() time.Time { return m.Modified } func (m *Model) setModified(Modified time.Time) { m.Modified = Modified } // HasID is a simple interface that you must implement // in your models, using a pointer receiver. // This allows for more flexibility in cases where // your ID isn't an ObjectID (e.g., int, uint, string...). // // and yes, those darn ugly ObjectIDs are supported :) type HasID interface { Id() any SetId(id any) } type HasIDSlice []HasID type IModel interface { Append(field string, a ...interface{}) error Delete() error FindRaw(query interface{}, opts ...*options.FindOptions) (*mongo.Cursor, error) Find(query interface{}, opts ...*options.FindOptions) (*Query, error) FindByID(id interface{}) (*Query, error) FindOne(query interface{}, options ...*options.FindOneOptions) (*Query, error) FindPaged(query interface{}, page int64, perPage int64, options ...*options.FindOptions) (*Query, error) Pull(field string, a ...any) error Remove() error Save() error getColl() *mongo.Collection getIdxs() []*mongo.IndexModel getParsedIdxs() map[string][]InternalIndex serializeToStore() any setTypeName(str string) getExists() bool setExists(n bool) setModified(Modified time.Time) setCreated(Modified time.Time) getModified() time.Time getCreated() time.Time setSelf(arg interface{}) } func (m *Model) setTypeName(str string) { m.typeName = str } func (m *Model) setSelf(arg interface{}) { m.self = arg } func (m *Model) getExists() bool { return m.exists } func (m *Model) setExists(n bool) { m.exists = n } func (m *Model) getColl() *mongo.Collection { _, ri, ok := ModelRegistry.HasByName(m.typeName) if !ok { panic(fmt.Sprintf("the model '%s' has not been registered", m.typeName)) } return DB.Collection(ri.Collection) } func (m *Model) getIdxs() []*mongo.IndexModel { mi := make([]*mongo.IndexModel, 0) if mpi := m.getParsedIdxs(); mpi != nil { for _, v := range mpi { for _, i := range v { mi = append(mi, buildIndex(i)) } } return mi } return nil } func (m *Model) getParsedIdxs() map[string][]InternalIndex { _, ri, ok := ModelRegistry.HasByName(m.typeName) if !ok { panic(fmt.Sprintf("model '%s' not registered", m.typeName)) } return ri.Indexes } // FindRaw - find documents satisfying `query` and return a plain mongo cursor. func (m *Model) FindRaw(query interface{}, opts ...*options.FindOptions) (*mongo.Cursor, error) { coll := m.getColl() cursor, err := coll.Find(context.TODO(), query, opts...) return cursor, err } // Find - find all documents satisfying `query`. // returns a pointer to a Query for further chaining. func (m *Model) Find(query interface{}, opts ...*options.FindOptions) (*Query, error) { qqn := ModelRegistry.new_(m.typeName) qqt := reflect.SliceOf(reflect.TypeOf(qqn)) qqv := reflect.New(qqt) qqv.Elem().Set(reflect.MakeSlice(qqt, 0, 0)) qq := &Query{ Model: m, Collection: m.getColl(), doc: qqv.Interface(), Op: OP_FIND_ALL, } q, err := m.FindRaw(query, opts...) if err == nil { rawRes := bson.A{} err = q.All(context.TODO(), &rawRes) if err == nil { m.exists = true } qq.rawDoc = rawRes err = q.All(context.TODO(), &qq.doc) if err != nil { qq.reOrganize() err = nil } } return qq, err } // FindPaged - Wrapper around FindAll with the Skip and Limit options populated. // returns a pointer to a Query for further chaining. func (m *Model) FindPaged(query interface{}, page int64, perPage int64, opts ...*options.FindOptions) (*Query, error) { skipAmt := perPage * (page - 1) if skipAmt < 0 { skipAmt = 0 } if len(opts) > 0 { opts[0].SetSkip(skipAmt).SetLimit(perPage) } else { opts = append(opts, options.Find().SetSkip(skipAmt).SetLimit(perPage)) } q, err := m.Find(query, opts...) q.Op = OP_FIND_PAGED return q, err } // FindByID - find a single document by its _id field. // Wrapper around FindOne with an ID query as its first argument func (m *Model) FindByID(id interface{}) (*Query, error) { return m.FindOne(bson.D{{"_id", id}}) } // FindOne - find a single document satisfying `query`. // returns a pointer to a Query for further chaining. func (m *Model) FindOne(query interface{}, options ...*options.FindOneOptions) (*Query, error) { coll := m.getColl() rip := coll.FindOne(context.TODO(), query, options...) raw := bson.M{} err := rip.Decode(&raw) panik(err) m.exists = true qqn := ModelRegistry.new_(m.typeName) v := reflect.New(reflect.TypeOf(qqn)) v.Elem().Set(reflect.ValueOf(qqn)) qq := &Query{ Collection: m.getColl(), rawDoc: raw, doc: v.Elem().Interface(), Op: OP_FIND_ONE, Model: m, } qq.rawDoc = raw err = rip.Decode(qq.doc) if err != nil { qq.reOrganize() err = nil } m.self = qq.doc return qq, err } // Delete - deletes a model instance from the database func (m *Model) Delete() error { var err error val := valueOf(m.self) if val.Kind() == reflect.Slice { for i := 0; i < val.Len(); i++ { cur := val.Index(i) if err = doDelete(m, cur.Interface()); err != nil { return err } } return nil } else { return doDelete(m, m.self) } } // Remove - alias for Delete func (m *Model) Remove() error { return m.Delete() } // Append appends one or more items to `field`. // will error if this Model contains a reference // to multiple documents, or if `field` is not a // slice. func (m *Model) Append(field string, a ...interface{}) error { rv := reflect.ValueOf(m.self) selfRef := rv rt := reflect.TypeOf(m.self) if selfRef.Kind() == reflect.Pointer { selfRef = selfRef.Elem() rt = rt.Elem() } if err := checkStruct(selfRef); err != nil { return err } _, origV, err := getNested(field, selfRef) if err != nil { return err } origRef := makeSettable(*origV, (*origV).Interface()) fv := origRef if fv.Kind() == reflect.Pointer { fv = fv.Elem() } if fv.Kind() != reflect.Slice { return ErrNotASlice } for _, b := range a { val := reflect.ValueOf(incrementTagged(b)) fv.Set(reflect.Append(fv, val)) } return nil } // Pull - removes elements from the subdocument slice stored in `field`. func (m *Model) Pull(field string, a ...any) error { rv := reflect.ValueOf(m.self) selfRef := rv rt := reflect.TypeOf(m.self) if selfRef.Kind() == reflect.Pointer { selfRef = selfRef.Elem() rt = rt.Elem() } if err := checkStruct(selfRef); err != nil { return err } _, origV, err := getNested(field, selfRef) if err != nil { return err } origRef := makeSettable(*origV, (*origV).Interface()) fv := origRef if fv.Kind() == reflect.Pointer { fv = fv.Elem() } if fv.Kind() != reflect.Slice { return ErrNotASlice } outer: for _, b := range a { for i := 0; i < fv.Len(); i++ { if reflect.DeepEqual(b, fv.Index(i).Interface()) { fv.Set(pull(fv, i, fv.Index(i).Type())) break outer } } } return nil } // Swap - swaps the elements at indexes `i` and `j` in the // slice stored at `field` func (m *Model) Swap(field string, i, j int) error { rv := reflect.ValueOf(m.self) selfRef := rv rt := reflect.TypeOf(m.self) if selfRef.Kind() == reflect.Pointer { selfRef = selfRef.Elem() rt = rt.Elem() } if err := checkStruct(selfRef); err != nil { return err } _, origV, err := getNested(field, selfRef) if err != nil { return err } origRef := makeSettable(*origV, (*origV).Interface()) fv := origRef if fv.Kind() == reflect.Pointer { fv = fv.Elem() } if err = checkSlice(fv); err != nil { return err } if i >= fv.Len() || j >= fv.Len() { return ErrOutOfBounds } oi := fv.Index(i).Interface() oj := fv.Index(j).Interface() fv.Index(i).Set(reflect.ValueOf(oj)) fv.Index(j).Set(reflect.ValueOf(oi)) return nil } // Save - updates this Model in the database, // or inserts it if it doesn't exist func (m *Model) Save() error { val := valueOf(m.self) if val.Kind() == reflect.Slice { for i := 0; i < val.Len(); i++ { cur := val.Index(i) if err := doSave(m.getColl(), !m.exists, cur.Interface()); err != nil { return err } } return nil } else { return doSave(m.getColl(), !m.exists, m.self) } } func (m *Model) serializeToStore() any { return serializeIDs((m).self) } func createBase(d any) (reflect.Value, int, string) { var n string var ri *InternalModel var ok bool n, ri, ok = ModelRegistry.HasByName(nameOf(d)) if !ok { ModelRegistry.Model(d) n, ri, _ = ModelRegistry.Has(d) } t := ri.Type v := valueOf(d) i := ModelRegistry.Index(n) r := reflect.New(t) r.Elem().Set(v) if reflect.ValueOf(d).Kind() == reflect.Pointer { r.Elem().Set(reflect.ValueOf(d).Elem()) } else { r.Elem().Set(reflect.ValueOf(d)) } return r, i, n } // Create creates a new instance of a given model // and returns a pointer to it. func Create(d any) any { r, i, n := createBase(d) df := r.Elem().Field(i) dm := df.Interface().(Model) dm.typeName = n what := r.Interface() dm.self = what df.Set(reflect.ValueOf(dm)) return what } func CreateSlice[T any](d T) []*T { r, _, _ := createBase(d) rtype := r.Type() rslice := reflect.SliceOf(rtype) newItem := reflect.New(rslice) newItem.Elem().Set(reflect.MakeSlice(rslice, 0, 0)) return newItem.Elem().Interface().([]*T) } func (m *Model) PrintMe() { fmt.Printf("My name is %s !\n", nameOf(m)) }