package orm import ( "context" "fmt" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" "reflect" ) // Model - type which contains "static" methods like // Find, FindOne, etc. type Model struct { Indexes map[string][]InternalIndex Type reflect.Type collection string gridFSReferences map[string]gridFSReference idx int references map[string]Reference typeName string `bson:"-"` } // 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 { FindRaw(query interface{}, opts *options.FindOptionsBuilder) (*mongo.Cursor, error) Find(query interface{}, opts *options.FindOptionsBuilder) (*Query, error) FindByID(id interface{}) (*Query, error) FindOne(query interface{}, options *options.FindOneOptionsBuilder) (*Query, error) FindPaged(query interface{}, page int64, perPage int64, options *options.FindOptionsBuilder) (*Query, error) Collection() *mongo.Collection getIdxs() []*mongo.IndexModel getParsedIdxs() map[string][]InternalIndex getTypeName() string setTypeName(str string) } func (m *Model) getTypeName() string { return m.typeName } func (m *Model) setTypeName(str string) { m.typeName = str } // Collection - returns the collection associated with this Model func (m *Model) Collection() *mongo.Collection { return DB.Collection(m.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(errFmtModelNotRegistered, m.typeName)) } return ri.Indexes } // FindRaw - find documents satisfying `query` and return a plain mongo cursor. func (m *Model) FindRaw(query interface{}, opts *options.FindOptionsBuilder) (*mongo.Cursor, error) { coll := m.Collection() if opts == nil { opts = options.Find() } var fo options.FindOptions for _, setter := range opts.Opts { _ = setter(&fo) } 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.FindOptionsBuilder) (*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.Collection(), doc: qqv.Interface(), op: OP_FIND_ALL, } q, err := m.FindRaw(query, opts) //idoc := (*DocumentSlice)(qqv.Elem().UnsafePointer()) idoc := make(DocumentSlice, 0) if err == nil { rawRes := bson.A{} err = q.All(context.TODO(), &rawRes) if err == nil { idoc.setExists(true) } qq.rawDoc = rawRes err = q.All(context.TODO(), &qq.doc) if err != nil { qq.reOrganize() err = nil } for i := 0; i < qqv.Elem().Len(); i++ { idoc = append(idoc, qqv.Elem().Index(i).Interface().(IDocument)) } for i, doc := range idoc { doc.setModel(*m) doc.SetSelf(doc) doc.setRaw(rawRes[i]) } } 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.FindOptionsBuilder) (*Query, error) { skipAmt := perPage * (page - 1) if skipAmt < 0 { skipAmt = 0 } opts.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}}, nil) } // FindOne - find a single document satisfying `query`. // returns a pointer to a Query for further chaining. func (m *Model) FindOne(query interface{}, options *options.FindOneOptionsBuilder) (*Query, error) { coll := m.Collection() rip := coll.FindOne(context.TODO(), query, options) raw := bson.M{} err := rip.Decode(&raw) if err != nil { return nil, err } qqn := ModelRegistry.new_(m.typeName) idoc, ok := qqn.(IDocument) idoc.setRaw(raw) qq := &Query{ collection: m.Collection(), rawDoc: raw, doc: idoc, op: OP_FIND_ONE, model: m, } qq.rawDoc = raw err = rip.Decode(qq.doc) if err != nil { qq.reOrganize() err = nil } if ok { idoc.setExists(true) idoc.setModel(*m) idoc.setRaw(raw) } idoc.SetSelf(idoc) return qq, err } func (m *Model) Count(query interface{}, options *options.CountOptionsBuilder) (int64, error) { coll := m.Collection() return coll.CountDocuments(context.TODO(), query, options) } func createBase(d any) (reflect.Value, int, string) { var n string var ri *Model 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(reflect.PointerTo(t)).Elem() r.Addr().Elem().Set(reflect.New(t)) r.Addr().Elem().Elem().Set(v) if reflect.ValueOf(d).Kind() == reflect.Pointer { r.Addr().Elem().Elem().Set(reflect.ValueOf(d).Elem()) } else { r.Addr().Elem().Elem().Set(reflect.ValueOf(d)) } ri.setTypeName(n) r.Interface().(IDocument).setModel(*ri) r.Interface().(IDocument).newPopulationMap() return r, i, n } // Create creates a new instance of a given Document // type and returns a pointer to it. func Create(d any) any { r, _, n := createBase(d) //df := r.Elem().Field(i) dm := r.Interface().(IDocument) dm.getModel().setTypeName(n) what := r.Interface() dm.SetSelf(what) //df.Set(reflect.ValueOf(dm)) return what } // CreateSlice - convenience method which creates a new slice // of type *T (where T is a type which embeds Document) and // returns it 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) }