add a new Append method to Model, handle misc errors, unexport some funcs + methods
This commit is contained in:
parent
f46aab2f5f
commit
3f8fded6e5
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
7
.idea/inspectionProfiles/profiles_settings.xml
Normal file
7
.idea/inspectionProfiles/profiles_settings.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="PROJECT_PROFILE" value="Default" />
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
17
.idea/material_theme_project_new.xml
Normal file
17
.idea/material_theme_project_new.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MaterialThemeProjectNewConfig">
|
||||
<option name="metadata">
|
||||
<MTProjectMetadataState>
|
||||
<option name="migrated" value="true" />
|
||||
<option name="pristineConfig" value="false" />
|
||||
<option name="userId" value="1fe2f249:191aa6d861d:-7ffe" />
|
||||
</MTProjectMetadataState>
|
||||
</option>
|
||||
<option name="titleBarState">
|
||||
<MTProjectTitleBarConfigState>
|
||||
<option name="overrideColor" value="false" />
|
||||
</MTProjectTitleBarConfigState>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
18
.idea/misc.xml
Normal file
18
.idea/misc.xml
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="excludedPrintfFunctions.xml">
|
||||
<option name="excludedDescriptors">
|
||||
<set>
|
||||
<PrintfFuncDescriptorState>
|
||||
<option name="importPath" value="fmt" />
|
||||
<option name="name" value="Errorf" />
|
||||
</PrintfFuncDescriptorState>
|
||||
</set>
|
||||
</option>
|
||||
<option name="excludedNames">
|
||||
<set>
|
||||
<option value="Errorf" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/orm.iml" filepath="$PROJECT_DIR$/.idea/orm.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
9
.idea/orm.iml
Normal file
9
.idea/orm.iml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"nuxt.isNuxtApp": false
|
||||
}
|
18
indexes.go
18
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,
|
||||
}
|
||||
|
113
model.go
113
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())
|
||||
_, 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
|
||||
|
@ -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)
|
||||
|
||||
}
|
||||
|
1
query.go
1
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
|
||||
}
|
||||
|
35
util.go
35
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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user