337 lines
7.6 KiB
Go
337 lines
7.6 KiB
Go
package orm
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"go.mongodb.org/mongo-driver/v2/bson"
|
|
"log"
|
|
"reflect"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type Document struct {
|
|
// Created time. updated/added automatically.
|
|
Created time.Time `bson:"createdAt" json:"createdAt" tstype:"Date"`
|
|
// Modified time. updated/added automatically.
|
|
Modified time.Time `bson:"updatedAt" json:"updatedAt" tstype:"Date"`
|
|
model *Model `bson:"-"`
|
|
exists bool `bson:"-"`
|
|
self any `bson:"-"`
|
|
raw any `bson:"-"`
|
|
populatedFields map[string]bool `bson:"-"`
|
|
}
|
|
|
|
func (d *Document) UnmarshalJSON(bytes []byte) error {
|
|
var fiv interface{}
|
|
if err := json.Unmarshal(bytes, &fiv); err != nil {
|
|
return err
|
|
}
|
|
var err error
|
|
switch fiv.(type) {
|
|
case []interface{}:
|
|
tmpV := make(bson.A, 0)
|
|
err = json.Unmarshal(bytes, &tmpV)
|
|
typ := reflect.SliceOf(d.model.Type)
|
|
//d.SetSelf()
|
|
var arr []interface{}
|
|
reified := rerere(tmpV, typ, true)
|
|
if ta, ok := reified.(bson.A); ok {
|
|
arr = []interface{}(ta)
|
|
} else {
|
|
arr = reified.([]interface{})
|
|
}
|
|
fmt.Println(len(arr))
|
|
break
|
|
case map[string]interface{}:
|
|
tmpV := make(bson.M)
|
|
err = json.Unmarshal(bytes, &tmpV)
|
|
typ := reflect.PointerTo(d.model.Type)
|
|
self := reflect.ValueOf(d.self)
|
|
nself := reflect.NewAt(typ.Elem(), self.UnsafePointer())
|
|
reified := rerere(tmpV, typ, true)
|
|
nself.Elem().Set(reflect.ValueOf(reified).Elem())
|
|
d.self = nself.Interface()
|
|
break
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (d *Document) MarshalJSON() ([]byte, error) {
|
|
v := serializeIDs((d).self, true, d.populatedFields, "")
|
|
return json.Marshal(v)
|
|
}
|
|
|
|
type IDocument interface {
|
|
Append(field string, a ...interface{}) error
|
|
Pull(field string, a ...any) error
|
|
Swap(field string, i, j int) error
|
|
Delete() error
|
|
Remove() error
|
|
Save() error
|
|
SaveWith(opts *SaveOptions) error
|
|
Populate(fields ...string)
|
|
SetSelf(arg interface{})
|
|
getExists() bool
|
|
setExists(n bool)
|
|
setModified(Modified time.Time)
|
|
setCreated(Modified time.Time)
|
|
getModified() time.Time
|
|
getCreated() time.Time
|
|
serializeToStore() any
|
|
getModel() *Model
|
|
setModel(m Model)
|
|
markPopulated(field string)
|
|
markDepopulated(field string)
|
|
newPopulationMap()
|
|
getRaw() any
|
|
setRaw(raw any)
|
|
}
|
|
|
|
type SaveOptions struct {
|
|
SetTimestamps bool
|
|
}
|
|
|
|
func (d *Document) getCreated() time.Time {
|
|
return d.Created
|
|
}
|
|
|
|
func (d *Document) setCreated(Created time.Time) {
|
|
d.Created = Created
|
|
}
|
|
|
|
func (d *Document) getModified() time.Time {
|
|
return d.Modified
|
|
}
|
|
|
|
func (d *Document) setModified(Modified time.Time) {
|
|
d.Modified = Modified
|
|
}
|
|
|
|
// SetSelf - don't call this lol
|
|
func (d *Document) SetSelf(arg interface{}) {
|
|
d.self = arg
|
|
}
|
|
|
|
func (d *Document) getModel() *Model {
|
|
return d.model
|
|
}
|
|
|
|
func (d *Document) setModel(m Model) {
|
|
d.model = &m
|
|
}
|
|
|
|
func (d *Document) getExists() bool {
|
|
return d.exists
|
|
}
|
|
func (d *Document) setExists(n bool) {
|
|
d.exists = n
|
|
}
|
|
|
|
// Delete - deletes a model instance from the database
|
|
func (d *Document) Delete() error {
|
|
var err error
|
|
val := valueOf(d.self)
|
|
if val.Kind() == reflect.Slice {
|
|
for i := 0; i < val.Len(); i++ {
|
|
cur := val.Index(i)
|
|
if err = doDelete(d, cur.Interface()); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
} else {
|
|
return doDelete(d, d.self)
|
|
}
|
|
}
|
|
|
|
// Remove - alias for Delete
|
|
func (d *Document) Remove() error {
|
|
return d.Delete()
|
|
}
|
|
|
|
// SaveWith - updates this Model in the database,
|
|
// or inserts it if it doesn't exist, using the provided
|
|
// SaveOptions
|
|
func (d *Document) SaveWith(opts *SaveOptions) error {
|
|
val := valueOf(d.self)
|
|
if val.Kind() == reflect.Slice {
|
|
for i := 0; i < val.Len(); i++ {
|
|
cur := val.Index(i)
|
|
if err := doSave(d.model.getColl(), !d.exists, opts, cur.Interface()); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
} else {
|
|
return doSave(d.model.getColl(), !d.exists, opts, d.self)
|
|
}
|
|
}
|
|
|
|
// Save - updates this Model in the database,
|
|
// or inserts it if it doesn't exist, using
|
|
// default SaveOptions
|
|
func (d *Document) Save() error {
|
|
return d.SaveWith(&SaveOptions{
|
|
SetTimestamps: true,
|
|
})
|
|
}
|
|
|
|
func (d *Document) serializeToStore() any {
|
|
return serializeIDs((d).self, false, d.populatedFields, "")
|
|
}
|
|
|
|
// 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 (d *Document) Append(field string, a ...interface{}) error {
|
|
var d0 IDocument = d
|
|
d0.getCreated()
|
|
rv := reflect.ValueOf(d.self)
|
|
selfRef := rv
|
|
rt := reflect.TypeOf(d.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 (d *Document) Pull(field string, a ...any) error {
|
|
rv := reflect.ValueOf(d.self)
|
|
selfRef := rv
|
|
rt := reflect.TypeOf(d.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 {
|
|
inner:
|
|
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 inner
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Swap - swaps the elements at indexes `i` and `j` in the
|
|
// slice stored at `field`
|
|
func (d *Document) Swap(field string, i, j int) error {
|
|
rv := reflect.ValueOf(d.self)
|
|
selfRef := rv
|
|
rt := reflect.TypeOf(d.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
|
|
}
|
|
|
|
func (d *Document) Populate(fields ...string) {
|
|
_, cm, _ := ModelRegistry.HasByName(d.model.typeName)
|
|
|
|
if cm != nil {
|
|
rawDoc := d.raw
|
|
for _, field := range fields {
|
|
// 0 = fieldname, 1 = typename, 2 = bson name
|
|
|
|
r, _ := readFields(field, cm)
|
|
|
|
if r.exists {
|
|
// get self
|
|
// get ptr
|
|
// find
|
|
// unmarshal...
|
|
htt := r.HydratedType
|
|
if htt.Kind() == reflect.Pointer || htt.Kind() == reflect.Slice {
|
|
htt = htt.Elem()
|
|
}
|
|
if strings.HasSuffix(field, ".") || strings.HasPrefix(field, ".") {
|
|
log.Printf("WARN: invalid field name passed to Populate(). skipping...\n")
|
|
continue
|
|
}
|
|
|
|
tto := r.HydratedType
|
|
if tto.Kind() == reflect.Pointer || tto.Kind() == reflect.Slice {
|
|
tto = tto.Elem()
|
|
}
|
|
_, refColl, _ := ModelRegistry.HasByName(tto.Name())
|
|
var tmp1 interface{}
|
|
asIDocument, docOk := d.self.(IDocument)
|
|
if docOk {
|
|
asIDocument.markPopulated(field)
|
|
}
|
|
|
|
tmp1 = populate(r, refColl.collection, rawDoc, field, reflect.ValueOf(d.self).Interface())
|
|
d.self = tmp1
|
|
}
|
|
}
|
|
}
|
|
}
|