425 lines
9.8 KiB
Go
425 lines
9.8 KiB
Go
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
|
|
getTypeName() string
|
|
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) getTypeName() string {
|
|
return m.typeName
|
|
}
|
|
|
|
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))
|
|
}
|