diamond-orm/model.go

239 lines
5.8 KiB
Go

package orm
import (
"context"
"fmt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"reflect"
"unsafe"
)
// Model - "base" struct for all queryable models
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.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)
getColl() *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
}
func (m *Model) getColl() *mongo.Collection {
_, ri, ok := ModelRegistry.HasByName(m.typeName)
if !ok {
panic(fmt.Sprintf(errFmtModelNotRegistered, 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(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.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...)
idoc := (*DocumentSlice)(unsafe.Pointer(qqv.Elem().UnsafeAddr()))
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
}
}
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)
if err != nil {
return nil, err
}
qqn := ModelRegistry.new_(m.typeName)
idoc, ok := qqn.(IDocument)
if ok {
idoc.setExists(true)
}
qq := &Query{
collection: m.getColl(),
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
}
idoc.setSelf(idoc)
return qq, err
}
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(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))
}
ri.setTypeName(n)
r.Interface().(IDocument).setModel(*ri)
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
}
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))
}