diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..a55e7a1
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..dd4c951
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml
new file mode 100644
index 0000000..062a2a4
--- /dev/null
+++ b/.idea/material_theme_project_new.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..67ee188
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..0d8a8dd
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/orm.iml b/.idea/orm.iml
new file mode 100644
index 0000000..5e764c4
--- /dev/null
+++ b/.idea/orm.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..9092697
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "nuxt.isNuxtApp": false
+}
\ No newline at end of file
diff --git a/indexes.go b/indexes.go
index 344efcc..8443354 100644
--- a/indexes.go
+++ b/indexes.go
@@ -79,12 +79,12 @@ func scanIndex(src string) []InternalIndex {
switch tok {
case token.LBRACE:
if lb {
- goto panik
+ goto _panik
}
lb = true
case token.RBRACE:
if !lb || len(p.Fields) == 0 {
- goto panik
+ goto _panik
}
lb = false
case token.IDENT:
@@ -100,36 +100,36 @@ func scanIndex(src string) []InternalIndex {
p.appendOption(lit)
case token.PERIOD:
if p.getSticky() {
- goto panik
+ goto _panik
}
p.setSticky(true)
p.updateLastField()
case token.COMMA:
case token.COLON:
if lb {
- goto panik
+ goto _panik
}
case token.SEMICOLON:
if lb {
- goto panik
+ goto _panik
}
parsed = append(parsed, *p)
p = &InternalIndex{}
case token.EOF:
if lb {
- goto panik
+ goto _panik
}
return parsed
default:
- goto panik
+ goto _panik
}
}
-panik:
+_panik:
panic("parsing error in index expression!")
}
-func BuildIndex(i InternalIndex) *mongo.IndexModel {
+func buildIndex(i InternalIndex) *mongo.IndexModel {
idx := &mongo.IndexModel{
Keys: i.Fields,
}
diff --git a/model.go b/model.go
index 3b14e6f..3a28c95 100644
--- a/model.go
+++ b/model.go
@@ -19,20 +19,22 @@ type Model struct {
Created time.Time `bson:"createdAt" json:"createdAt"`
// Modified time. updated/added automatically.
Modified time.Time `bson:"updatedAt" json:"updatedAt"`
- typeName string `bson:"-" json:"-"`
- self any `bson:"-" json:"-"`
+ typeName string `bson:"-"`
+ self any `bson:"-"`
exists bool `bson:"-"`
}
-// HasID is a simple interface that you must implement.
-// This allows for more flexibility if your ID isn't an
-// ObjectID (e.g., int, uint, string...).
+// 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 {
Find(query interface{}, opts ...*options.FindOptions) (*mongo.Cursor, error)
@@ -46,12 +48,21 @@ type IModel interface {
Save() error
serializeToStore() primitive.M
setTypeName(str string)
+ getExists() bool
+ setExists(n bool)
}
func (m *Model) setTypeName(str string) {
m.typeName = str
}
+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 {
@@ -61,11 +72,11 @@ func (m *Model) getColl() *mongo.Collection {
}
func (m *Model) getIdxs() []*mongo.IndexModel {
- mi := []*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))
+ mi = append(mi, buildIndex(i))
}
}
return mi
@@ -116,15 +127,17 @@ func (m *Model) FindAll(query interface{}, opts ...*options.FindOptions) (*Query
return qq, err
}
-func (m *Model) FindPaged(query interface{}, page int64, perPage int64, options ...*options.FindOptions) (*Query, error) {
+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(options) > 0 {
- options[0].SetSkip(skipAmt).SetLimit(perPage)
+ if len(opts) > 0 {
+ opts[0].SetSkip(skipAmt).SetLimit(perPage)
+ } else {
+ opts = append(opts, options.Find().SetSkip(skipAmt).SetLimit(perPage))
}
- q, err := m.FindAll(query, options...)
+ q, err := m.FindAll(query, opts...)
q.Op = OP_FIND_PAGED
return q, err
}
@@ -133,12 +146,6 @@ func (m *Model) FindByID(id interface{}) (*Query, error) {
return m.FindOne(bson.D{{"_id", id}})
}
-// 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
-// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
-
-// ^ ^
-// ^ ^
-
func (m *Model) FindOne(query interface{}, options ...*options.FindOneOptions) (*Query, error) {
coll := m.getColl()
rip := coll.FindOne(context.TODO(), query, options...)
@@ -163,6 +170,62 @@ func (m *Model) FindOne(query interface{}, options ...*options.FindOneOptions) (
return qq, err
}
+func (m *Model) DeleteOne() error {
+ c := m.getColl()
+ if valueOf(m.self).Kind() == reflect.Slice {
+ }
+ id, ok := m.self.(HasID)
+ if !ok {
+ id2, ok2 := m.self.(HasIDSlice)
+ if !ok2 {
+ return fmt.Errorf("model '%s' is not registered", m.typeName)
+ }
+ _, err := c.DeleteOne(context.TODO(), bson.M{"_id": id2[0].Id()})
+ return err
+ } else {
+ _, err := c.DeleteOne(context.TODO(), bson.M{"_id": id.Id()})
+ return err
+ }
+}
+
+// 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)
+ ref := rv
+ rt := reflect.TypeOf(m.self)
+ if ref.Kind() == reflect.Pointer {
+ ref = ref.Elem()
+ rt = rt.Elem()
+ }
+ if ref.Kind() == reflect.Slice {
+ return fmt.Errorf("Cannot append to multiple documents!")
+ }
+ if ref.Kind() != reflect.Struct {
+ return fmt.Errorf("Current object is not a struct!")
+ }
+ _, ofv, err := getNested(field, ref)
+ if err != nil {
+ return err
+ }
+ oofv := makeSettable(*ofv, (*ofv).Interface())
+ fv := oofv
+ if fv.Kind() == reflect.Pointer {
+ fv = fv.Elem()
+ }
+ if fv.Kind() != reflect.Slice {
+ return fmt.Errorf("Current object is not a slice!")
+ }
+
+ for _, b := range a {
+ va := reflect.ValueOf(b)
+ fv.Set(reflect.Append(fv, va))
+ }
+ return nil
+}
+
func (m *Model) Save() error {
var err error
c := m.getColl()
@@ -175,8 +238,8 @@ func (m *Model) Save() error {
}
var asHasId = vp.Interface().(HasID)
(asHasId).Id()
- isNew := reflect.ValueOf(asHasId.Id()).IsZero() || !m.exists
- if isNew {
+ isNew := reflect.ValueOf(asHasId.Id()).IsZero() && !m.exists
+ if isNew || !m.exists {
m.Created = now
}
m.Modified = now
@@ -187,7 +250,7 @@ func (m *Model) Save() error {
return err
}
}
- if isNew {
+ if isNew || !m.exists {
nid := getLastInColl(c.Name(), asHasId.Id())
switch pnid := nid.(type) {
case uint:
@@ -214,8 +277,10 @@ func (m *Model) Save() error {
}
m.self = asHasId
- c.InsertOne(context.TODO(), m.serializeToStore())
- m.exists = true
+ _, err = c.InsertOne(context.TODO(), m.serializeToStore())
+ if err == nil {
+ m.exists = true
+ }
} else {
_, err = c.ReplaceOne(context.TODO(), bson.D{{Key: "_id", Value: m.self.(HasID).Id()}}, m.serializeToStore())
}
@@ -245,7 +310,7 @@ func serializeIDs(input interface{}) bson.M {
for i := 0; i < mv.NumField(); i++ {
fv := mv.Field(i)
ft := mt.Field(i)
- var dr reflect.Value = fv
+ var dr = fv
tag, err := structtag.Parse(string(mt.Field(i).Tag))
panik(err)
bbson, err := tag.Get("bson")
@@ -331,8 +396,8 @@ func serializeIDSlice(input []interface{}) bson.A {
return a
}
-// Create creates a new instance of a given model.
-// returns a pointer to the newly created model.
+// Create creates a new instance of a given model
+// and returns a pointer to it.
func Create(d any) any {
var n string
var ri *InternalModel
diff --git a/model_test.go b/model_test.go
index 142b4f2..328ff62 100644
--- a/model_test.go
+++ b/model_test.go
@@ -41,8 +41,6 @@ func TestPopulate(t *testing.T) {
err := bandDoc.Save()
assert.Equal(t, nil, err)
storyDoc.Author = author
- err = author.Save()
- assert.Equal(t, nil, err)
err = storyDoc.Save()
assert.Equal(t, nil, err)
assert.Greater(t, storyDoc.ID, int64(0))
@@ -50,7 +48,6 @@ func TestPopulate(t *testing.T) {
smodel := Create(story{}).(*story)
q, err := smodel.FindByID(storyDoc.ID)
assert.Equal(t, nil, err)
- //assert.Greater(t, smodel.ID, int64(0))
assert.NotPanics(t, func() {
foundDoc := &story{}
q.Populate("Author", "Chapters.Bands").Exec(foundDoc)
@@ -107,3 +104,23 @@ func TestModel_PopulateMulti(t *testing.T) {
query.Populate("Author", "Chapters.Bands").Exec(&final)
assert.Greater(t, len(final), 0)
}
+
+func TestModel_Append(t *testing.T) {
+ initTest()
+ bmodel := Create(band{}).(*band)
+ query, err := bmodel.FindByID(int64(1))
+ assert.Equal(t, nil, err)
+ fin := &band{}
+ query.Exec(fin)
+ assert.Greater(t, fin.ID, int64(0))
+
+ err = bmodel.Append("Characters", "Robert Trujillo")
+ assert.Equal(t, nil, err)
+ err = bmodel.Save()
+ assert.Equal(t, nil, err)
+ fin = &band{}
+ query, _ = bmodel.FindByID(int64(1))
+ query.Exec(fin)
+ assert.Greater(t, len(fin.Characters), 4)
+
+}
diff --git a/query.go b/query.go
index f4b1167..9a2b070 100644
--- a/query.go
+++ b/query.go
@@ -478,5 +478,6 @@ func (q *Query) Exec(result interface{}) {
panic("Exec() has already been called!")
}
reflect.ValueOf(result).Elem().Set(reflect.ValueOf(q.doc).Elem())
+ q.Model.self = q.doc
q.done = true
}
diff --git a/util.go b/util.go
index 2b70249..9b9804f 100644
--- a/util.go
+++ b/util.go
@@ -1,6 +1,10 @@
package orm
-import "reflect"
+import (
+ "fmt"
+ "reflect"
+ "strings"
+)
func panik(err error) {
if err != nil {
@@ -68,3 +72,32 @@ func coerceInt(input reflect.Value, dst reflect.Value) interface{} {
}
return nil
}
+
+func getNested(field string, value reflect.Value) (*reflect.Type, *reflect.Value, error) {
+ if strings.HasPrefix(field, ".") || strings.HasSuffix(field, ".") {
+ return nil, nil, fmt.Errorf("Malformed field name %s passed", field)
+ }
+ dots := strings.Split(field, ".")
+ if value.Kind() != reflect.Struct {
+ return nil, nil, fmt.Errorf("This value is not a struct!")
+ }
+ ref := value
+ if ref.Kind() == reflect.Pointer {
+ ref = ref.Elem()
+ }
+ fv := ref.FieldByName(dots[0])
+ ft := fv.Type()
+ if len(dots) > 1 {
+ return getNested(strings.Join(dots[1:], "."), fv)
+ } else {
+ return &ft, &fv, nil
+ }
+}
+func makeSettable(rval reflect.Value, value interface{}) reflect.Value {
+ if !rval.CanSet() {
+ nv := reflect.New(rval.Type())
+ nv.Elem().Set(reflect.ValueOf(value))
+ return nv
+ }
+ return rval
+}