2024-09-01 16:17:48 -04:00
|
|
|
package orm
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
|
|
"go.mongodb.org/mongo-driver/mongo/options"
|
2024-09-14 02:09:38 -04:00
|
|
|
"reflect"
|
|
|
|
"unsafe"
|
2024-09-01 16:17:48 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
// Model - "base" struct for all queryable models
|
|
|
|
type Model struct {
|
2024-09-14 02:09:38 -04:00
|
|
|
typeName string `bson:"-"`
|
|
|
|
Idx int
|
|
|
|
Type reflect.Type
|
|
|
|
Collection string
|
|
|
|
References map[string]Reference
|
|
|
|
Indexes map[string][]InternalIndex
|
|
|
|
GridFSReferences map[string]GridFSReference
|
2024-09-03 00:14:12 -04:00
|
|
|
}
|
|
|
|
|
2024-09-02 19:32:39 -04:00
|
|
|
// 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...).
|
2024-09-01 16:17:48 -04:00
|
|
|
//
|
|
|
|
// and yes, those darn ugly ObjectIDs are supported :)
|
|
|
|
type HasID interface {
|
|
|
|
Id() any
|
|
|
|
SetId(id any)
|
|
|
|
}
|
2024-09-03 00:14:12 -04:00
|
|
|
|
2024-09-02 19:32:39 -04:00
|
|
|
type HasIDSlice []HasID
|
2024-09-01 16:17:48 -04:00
|
|
|
|
|
|
|
type IModel interface {
|
2024-09-05 14:29:35 -04:00
|
|
|
FindRaw(query interface{}, opts ...*options.FindOptions) (*mongo.Cursor, error)
|
|
|
|
Find(query interface{}, opts ...*options.FindOptions) (*Query, error)
|
2024-09-01 16:17:48 -04:00
|
|
|
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)
|
2024-09-14 02:09:38 -04:00
|
|
|
|
2024-09-05 17:53:04 -04:00
|
|
|
getColl() *mongo.Collection
|
|
|
|
getIdxs() []*mongo.IndexModel
|
|
|
|
getParsedIdxs() map[string][]InternalIndex
|
2024-09-12 17:25:01 -04:00
|
|
|
getTypeName() string
|
2024-09-01 16:17:48 -04:00
|
|
|
setTypeName(str string)
|
|
|
|
}
|
|
|
|
|
2024-09-12 17:25:01 -04:00
|
|
|
func (m *Model) getTypeName() string {
|
|
|
|
return m.typeName
|
|
|
|
}
|
|
|
|
|
2024-09-01 16:17:48 -04:00
|
|
|
func (m *Model) setTypeName(str string) {
|
|
|
|
m.typeName = str
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2024-09-02 19:32:39 -04:00
|
|
|
mi := make([]*mongo.IndexModel, 0)
|
2024-09-01 16:17:48 -04:00
|
|
|
if mpi := m.getParsedIdxs(); mpi != nil {
|
|
|
|
for _, v := range mpi {
|
|
|
|
for _, i := range v {
|
2024-09-02 19:32:39 -04:00
|
|
|
mi = append(mi, buildIndex(i))
|
2024-09-01 16:17:48 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-09-05 14:29:35 -04:00
|
|
|
// FindRaw - find documents satisfying `query` and return a plain mongo cursor.
|
|
|
|
func (m *Model) FindRaw(query interface{}, opts ...*options.FindOptions) (*mongo.Cursor, error) {
|
2024-09-01 16:17:48 -04:00
|
|
|
coll := m.getColl()
|
|
|
|
cursor, err := coll.Find(context.TODO(), query, opts...)
|
|
|
|
return cursor, err
|
|
|
|
}
|
|
|
|
|
2024-09-05 14:29:35 -04:00
|
|
|
// 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) {
|
2024-09-01 16:17:48 -04:00
|
|
|
qqn := ModelRegistry.new_(m.typeName)
|
2024-09-05 17:53:04 -04:00
|
|
|
qqt := reflect.SliceOf(reflect.TypeOf(qqn))
|
|
|
|
qqv := reflect.New(qqt)
|
|
|
|
qqv.Elem().Set(reflect.MakeSlice(qqt, 0, 0))
|
2024-09-01 16:17:48 -04:00
|
|
|
qq := &Query{
|
2024-09-12 17:48:45 -04:00
|
|
|
model: m,
|
|
|
|
collection: m.getColl(),
|
2024-09-01 16:17:48 -04:00
|
|
|
doc: qqv.Interface(),
|
2024-09-12 17:48:45 -04:00
|
|
|
op: OP_FIND_ALL,
|
2024-09-01 16:17:48 -04:00
|
|
|
}
|
2024-09-05 14:29:35 -04:00
|
|
|
q, err := m.FindRaw(query, opts...)
|
2024-09-14 02:09:38 -04:00
|
|
|
idoc := (*DocumentSlice)(unsafe.Pointer(qqv.Elem().UnsafeAddr()))
|
2024-09-01 16:17:48 -04:00
|
|
|
if err == nil {
|
|
|
|
rawRes := bson.A{}
|
|
|
|
err = q.All(context.TODO(), &rawRes)
|
|
|
|
if err == nil {
|
2024-09-14 02:09:38 -04:00
|
|
|
idoc.setExists(true)
|
2024-09-01 16:17:48 -04:00
|
|
|
}
|
|
|
|
qq.rawDoc = rawRes
|
|
|
|
err = q.All(context.TODO(), &qq.doc)
|
|
|
|
if err != nil {
|
|
|
|
qq.reOrganize()
|
|
|
|
err = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return qq, err
|
|
|
|
}
|
|
|
|
|
2024-09-05 14:29:35 -04:00
|
|
|
// FindPaged - Wrapper around FindAll with the Skip and Limit options populated.
|
|
|
|
// returns a pointer to a Query for further chaining.
|
2024-09-02 19:32:39 -04:00
|
|
|
func (m *Model) FindPaged(query interface{}, page int64, perPage int64, opts ...*options.FindOptions) (*Query, error) {
|
2024-09-01 16:17:48 -04:00
|
|
|
skipAmt := perPage * (page - 1)
|
|
|
|
if skipAmt < 0 {
|
|
|
|
skipAmt = 0
|
|
|
|
}
|
2024-09-02 19:32:39 -04:00
|
|
|
if len(opts) > 0 {
|
|
|
|
opts[0].SetSkip(skipAmt).SetLimit(perPage)
|
|
|
|
} else {
|
|
|
|
opts = append(opts, options.Find().SetSkip(skipAmt).SetLimit(perPage))
|
2024-09-01 16:17:48 -04:00
|
|
|
}
|
2024-09-05 14:29:35 -04:00
|
|
|
q, err := m.Find(query, opts...)
|
2024-09-12 17:48:45 -04:00
|
|
|
q.op = OP_FIND_PAGED
|
2024-09-01 16:17:48 -04:00
|
|
|
return q, err
|
|
|
|
}
|
|
|
|
|
2024-09-05 14:29:35 -04:00
|
|
|
// FindByID - find a single document by its _id field.
|
|
|
|
// Wrapper around FindOne with an ID query as its first argument
|
2024-09-01 16:17:48 -04:00
|
|
|
func (m *Model) FindByID(id interface{}) (*Query, error) {
|
|
|
|
return m.FindOne(bson.D{{"_id", id}})
|
|
|
|
}
|
|
|
|
|
2024-09-05 14:29:35 -04:00
|
|
|
// FindOne - find a single document satisfying `query`.
|
|
|
|
// returns a pointer to a Query for further chaining.
|
2024-09-01 16:17:48 -04:00
|
|
|
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)
|
2024-09-05 17:53:04 -04:00
|
|
|
qqn := ModelRegistry.new_(m.typeName)
|
2024-09-14 02:09:38 -04:00
|
|
|
idoc, ok := qqn.(IDocument)
|
|
|
|
if ok {
|
|
|
|
idoc.setExists(true)
|
|
|
|
}
|
|
|
|
|
2024-09-01 16:17:48 -04:00
|
|
|
qq := &Query{
|
2024-09-12 17:48:45 -04:00
|
|
|
collection: m.getColl(),
|
2024-09-01 16:17:48 -04:00
|
|
|
rawDoc: raw,
|
2024-09-14 02:09:38 -04:00
|
|
|
doc: idoc,
|
2024-09-12 17:48:45 -04:00
|
|
|
op: OP_FIND_ONE,
|
|
|
|
model: m,
|
2024-09-01 16:17:48 -04:00
|
|
|
}
|
|
|
|
qq.rawDoc = raw
|
|
|
|
err = rip.Decode(qq.doc)
|
|
|
|
if err != nil {
|
|
|
|
qq.reOrganize()
|
|
|
|
err = nil
|
|
|
|
}
|
2024-09-14 02:09:38 -04:00
|
|
|
idoc.setSelf(idoc)
|
2024-09-01 16:17:48 -04:00
|
|
|
return qq, err
|
|
|
|
}
|
|
|
|
|
2024-09-05 17:53:04 -04:00
|
|
|
func createBase(d any) (reflect.Value, int, string) {
|
2024-09-01 16:17:48 -04:00
|
|
|
var n string
|
2024-09-14 02:09:38 -04:00
|
|
|
var ri *Model
|
2024-09-01 16:17:48 -04:00
|
|
|
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))
|
|
|
|
}
|
2024-09-14 02:09:38 -04:00
|
|
|
ri.setTypeName(n)
|
|
|
|
r.Interface().(IDocument).setModel(*ri)
|
2024-09-05 17:53:04 -04:00
|
|
|
|
|
|
|
return r, i, n
|
|
|
|
}
|
|
|
|
|
2024-09-14 02:09:38 -04:00
|
|
|
// Create creates a new instance of a given Document
|
|
|
|
// type and returns a pointer to it.
|
2024-09-05 17:53:04 -04:00
|
|
|
func Create(d any) any {
|
2024-09-14 02:09:38 -04:00
|
|
|
r, _, n := createBase(d)
|
|
|
|
//df := r.Elem().Field(i)
|
|
|
|
dm := r.Interface().(IDocument)
|
|
|
|
dm.getModel().setTypeName(n)
|
2024-09-01 16:17:48 -04:00
|
|
|
what := r.Interface()
|
2024-09-14 02:09:38 -04:00
|
|
|
|
|
|
|
dm.setSelf(what)
|
|
|
|
//df.Set(reflect.ValueOf(dm))
|
2024-09-01 16:17:48 -04:00
|
|
|
return what
|
|
|
|
}
|
|
|
|
|
2024-09-05 17:53:04 -04:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2024-09-01 16:17:48 -04:00
|
|
|
func (m *Model) PrintMe() {
|
|
|
|
fmt.Printf("My name is %s !\n", nameOf(m))
|
|
|
|
}
|