Initial commit
This commit is contained in:
commit
91ef568a64
168
crud.go
Normal file
168
crud.go
Normal file
@ -0,0 +1,168 @@
|
||||
package mgocrud
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
mgo "gopkg.in/mgo.v2"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
)
|
||||
|
||||
// CreateDocument creates a document from specified model
|
||||
func CreateDocument(db *mgo.Database, m ModelInterface) error {
|
||||
m.PrepareInsert()
|
||||
|
||||
c := db.C(GetCollectionName(m))
|
||||
err := c.Insert(m)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// ReadDocument gets one document via its id
|
||||
func ReadDocument(db *mgo.Database, m ModelInterface, selector bson.M) error {
|
||||
c := db.C(GetCollectionName(m))
|
||||
|
||||
q := c.FindId(m.GetID())
|
||||
if selector != nil {
|
||||
q = q.Select(selector)
|
||||
}
|
||||
err := q.One(m)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// PipelineModifierFunction is a function to modify mongodb query
|
||||
type PipelineModifierFunction func(pipeline []bson.M) []bson.M
|
||||
|
||||
// ReadCollection gets the filtered collection of the model
|
||||
func ReadCollection(db *mgo.Database, results interface{}, filter bson.M, selector bson.M, offset int, limit int, sort []string, pipelineModifier PipelineModifierFunction) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("%v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
// get pointer to model (element of slice in results) to get collection name
|
||||
m := reflect.New(reflect.TypeOf(
|
||||
reflect.Indirect(reflect.ValueOf(results)).Interface(), // get indirection of slice pointer
|
||||
).Elem()).Interface().(ModelInterface) // it must be a ModelInterface here
|
||||
|
||||
c := db.C(GetCollectionName(m))
|
||||
|
||||
if pipelineModifier != nil {
|
||||
// search via pipeline
|
||||
|
||||
pipeline := []bson.M{}
|
||||
|
||||
if filter != nil {
|
||||
pipeline = append(pipeline, bson.M{
|
||||
"$match": filter,
|
||||
})
|
||||
}
|
||||
|
||||
if len(sort) > 0 {
|
||||
sortM := bson.M{}
|
||||
for _, s := range sort {
|
||||
if strings.HasPrefix(s, "-") {
|
||||
s = s[1:]
|
||||
sortM[s] = -1
|
||||
} else {
|
||||
sortM[s] = 1
|
||||
}
|
||||
}
|
||||
spew.Dump(sortM)
|
||||
pipeline = append(pipeline, bson.M{
|
||||
"$sort": sortM,
|
||||
})
|
||||
}
|
||||
|
||||
if offset > 0 {
|
||||
pipeline = append(pipeline, bson.M{
|
||||
"$skip": offset,
|
||||
})
|
||||
}
|
||||
if limit > 0 {
|
||||
pipeline = append(pipeline, bson.M{
|
||||
"$limit": limit,
|
||||
})
|
||||
}
|
||||
if selector != nil {
|
||||
pipeline = append(pipeline, bson.M{
|
||||
"$project": selector,
|
||||
})
|
||||
}
|
||||
|
||||
if pipelineModifier != nil {
|
||||
pipeline = pipelineModifier(pipeline)
|
||||
}
|
||||
|
||||
q := c.Pipe(pipeline).AllowDiskUse().Iter()
|
||||
err = q.All(results)
|
||||
} else {
|
||||
// search without pipe is faster
|
||||
|
||||
q := c.Find(filter)
|
||||
if selector != nil {
|
||||
q = q.Select(selector)
|
||||
}
|
||||
if len(sort) > 0 {
|
||||
q = q.Sort(sort...)
|
||||
}
|
||||
if offset > 0 {
|
||||
q = q.Skip(offset)
|
||||
}
|
||||
if limit > 0 {
|
||||
q = q.Limit(limit)
|
||||
}
|
||||
|
||||
err = q.All(results)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// ReadCollectionCount gets the count of elements in filtered collection
|
||||
func ReadCollectionCount(db *mgo.Database, m ModelInterface, filter bson.M) (count int, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("%v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
c := db.C(GetCollectionName(m))
|
||||
return c.Find(filter).Count()
|
||||
}
|
||||
|
||||
// UpdateDocument updates a document from specified model
|
||||
func UpdateDocument(db *mgo.Database, m ModelInterface, changes bson.M) error {
|
||||
m.PrepareUpdate()
|
||||
changes["updateTime"] = time.Now()
|
||||
|
||||
c := db.C(GetCollectionName(m))
|
||||
err := c.UpdateId(m.GetID(), bson.M{"$set": changes})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// UpsertDocument updates a document from specified model or inserts it, of not found
|
||||
func UpsertDocument(db *mgo.Database, m ModelInterface, changes bson.M) error {
|
||||
m.PrepareUpdate()
|
||||
changes["updateTime"] = time.Now()
|
||||
|
||||
c := db.C(GetCollectionName(m))
|
||||
_, err := c.Upsert(m, bson.M{"$set": changes})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteDocument deletes one document via its id
|
||||
func DeleteDocument(db *mgo.Database, m ModelInterface) error {
|
||||
c := db.C(GetCollectionName(m))
|
||||
|
||||
err := c.RemoveId(m.GetID())
|
||||
|
||||
return err
|
||||
}
|
65
interface.go
Normal file
65
interface.go
Normal file
@ -0,0 +1,65 @@
|
||||
package mgocrud
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
)
|
||||
|
||||
// ModelInterface is interface for mgo crud operations
|
||||
type ModelInterface interface {
|
||||
PrepareInsert()
|
||||
PrepareUpdate()
|
||||
GetID() *bson.ObjectId
|
||||
SetID(id *bson.ObjectId)
|
||||
GetIdentSelector() (selector bson.M)
|
||||
SetIdent()
|
||||
}
|
||||
|
||||
// Model is model with default fields
|
||||
type Model struct {
|
||||
ID *bson.ObjectId `json:"id,omitempty" bson:"_id" mapstructure:"id"`
|
||||
InsertTime *time.Time `json:"insertTime,omitempty" bson:"insertTime,omitempty" mapstructure:"Datum"`
|
||||
UpdateTime *time.Time `json:"updateTime,omitempty" bson:"updateTime,omitempty" mapstructure:"Datum"`
|
||||
Ident []string `json:"ident,omitempty" bson:"-" mapstructure:"-"`
|
||||
}
|
||||
|
||||
// GetCollectionName gets name from type
|
||||
func GetCollectionName(m ModelInterface) string {
|
||||
if i, ok := m.(interface {
|
||||
GetCollectionName() string
|
||||
}); ok {
|
||||
return i.GetCollectionName()
|
||||
}
|
||||
return strings.ToLower(
|
||||
reflect.TypeOf(
|
||||
reflect.Indirect(reflect.ValueOf(m)).Interface(),
|
||||
).Name())
|
||||
}
|
||||
|
||||
// PrepareInsert creates new bson id and insert time
|
||||
func (m *Model) PrepareInsert() {
|
||||
id := bson.NewObjectId()
|
||||
m.ID = &id
|
||||
now := time.Now()
|
||||
m.InsertTime = &now
|
||||
m.UpdateTime = &now
|
||||
}
|
||||
|
||||
// PrepareUpdate updates UpdateTime
|
||||
func (m *Model) PrepareUpdate() {
|
||||
now := time.Now()
|
||||
m.UpdateTime = &now
|
||||
}
|
||||
|
||||
// GetID gets object id
|
||||
func (m *Model) GetID() *bson.ObjectId {
|
||||
return m.ID
|
||||
}
|
||||
|
||||
// SetID sets object id
|
||||
func (m *Model) SetID(id *bson.ObjectId) {
|
||||
m.ID = id
|
||||
}
|
169
lookup.go
Normal file
169
lookup.go
Normal file
@ -0,0 +1,169 @@
|
||||
package mgocrud
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
mgo "gopkg.in/mgo.v2"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
)
|
||||
|
||||
// Lookup extends results with data for inline structs
|
||||
func Lookup(db *mgo.Database, structField string, results interface{}, selector bson.M) error {
|
||||
|
||||
t := reflect.TypeOf(results)
|
||||
v := reflect.ValueOf(results)
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
isSlice := false
|
||||
elIsPtr := false
|
||||
if t.Kind() == reflect.Slice {
|
||||
t = t.Elem()
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
elIsPtr = true
|
||||
}
|
||||
isSlice = true
|
||||
}
|
||||
|
||||
if t.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("%+v is not a struct", t)
|
||||
}
|
||||
|
||||
field, found := t.FieldByName(structField)
|
||||
if !found {
|
||||
return fmt.Errorf("struct field %s not found in struct %+v", structField, t)
|
||||
}
|
||||
fieldType := field.Type
|
||||
fieldIsPtr := false
|
||||
if fieldType.Kind() == reflect.Ptr {
|
||||
fieldType = fieldType.Elem()
|
||||
fieldIsPtr = true
|
||||
}
|
||||
|
||||
fieldID, found := t.FieldByName(structField + "ID")
|
||||
if !found {
|
||||
return fmt.Errorf("struct field %s not found in struct %+v", structField+"ID", t)
|
||||
}
|
||||
idType := fieldID.Type
|
||||
idIsPtr := false
|
||||
if idType.Kind() == reflect.Ptr {
|
||||
idType = idType.Elem()
|
||||
idIsPtr = true
|
||||
}
|
||||
|
||||
if idType != reflect.TypeOf(bson.ObjectId("")) {
|
||||
return fmt.Errorf("field %s in struct %+v is not bson.ObjectId", structField+"ID", t)
|
||||
}
|
||||
|
||||
objectIDs := make(map[bson.ObjectId]interface{})
|
||||
|
||||
// make slice for single elements to use generic code
|
||||
if !isSlice {
|
||||
tmpSlice := []interface{}{
|
||||
results,
|
||||
}
|
||||
v = reflect.ValueOf(tmpSlice)
|
||||
if reflect.TypeOf(results).Kind() == reflect.Ptr {
|
||||
elIsPtr = true
|
||||
}
|
||||
}
|
||||
|
||||
l := v.Len()
|
||||
for i := 0; i < l; i++ {
|
||||
elV := v.Index(i)
|
||||
if elIsPtr {
|
||||
elV = elV.Elem()
|
||||
}
|
||||
if elV.Kind() == reflect.Ptr {
|
||||
// needed if tmpSlice -> dont know why double ptr resolv
|
||||
elV = elV.Elem()
|
||||
}
|
||||
lookupID := elV.FieldByName(fieldID.Name)
|
||||
if idIsPtr {
|
||||
lookupID = lookupID.Elem()
|
||||
}
|
||||
objectIDs[lookupID.Interface().(bson.ObjectId)] = nil
|
||||
}
|
||||
|
||||
lArr := len(objectIDs)
|
||||
if lArr <= 0 {
|
||||
// no entries to map
|
||||
return nil
|
||||
}
|
||||
sArr := make([]bson.M, lArr, lArr)
|
||||
aI := 0
|
||||
for sID := range objectIDs {
|
||||
sArr[aI] = bson.M{
|
||||
"_id": sID,
|
||||
}
|
||||
aI++
|
||||
}
|
||||
sQuery := bson.M{
|
||||
"$or": sArr,
|
||||
}
|
||||
|
||||
slice := reflect.MakeSlice(reflect.SliceOf(fieldType), 0, 0)
|
||||
x := reflect.New(slice.Type())
|
||||
x.Elem().Set(slice)
|
||||
objectResults := x.Interface()
|
||||
objectSlice := x.Elem()
|
||||
|
||||
objectIDIsPtr := false
|
||||
objectIDField, found := fieldType.FieldByName("ID")
|
||||
objectIDType := objectIDField.Type
|
||||
if found && objectIDType.Kind() == reflect.Ptr {
|
||||
objectIDType = objectIDType.Elem()
|
||||
objectIDIsPtr = true
|
||||
}
|
||||
if !found || objectIDType != reflect.TypeOf(bson.ObjectId("")) {
|
||||
return fmt.Errorf("ID type in objects struct %+v is not bson.ObjectId", fieldType)
|
||||
}
|
||||
|
||||
err := ReadCollection(db, objectResults, sQuery, selector, 0, 0, nil, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// map IDs to object for better resolving
|
||||
oLen := objectSlice.Len()
|
||||
for i := 0; i < oLen; i++ {
|
||||
oID := objectSlice.Index(i).FieldByName("ID")
|
||||
if objectIDIsPtr {
|
||||
oID = oID.Elem()
|
||||
}
|
||||
objectIDs[oID.Interface().(bson.ObjectId)] = objectSlice.Index(i).Addr().Interface()
|
||||
}
|
||||
|
||||
for i := 0; i < l; i++ {
|
||||
elV := v.Index(i)
|
||||
if elIsPtr {
|
||||
elV = elV.Elem()
|
||||
}
|
||||
if elV.Kind() == reflect.Ptr {
|
||||
// needed if tmpSlice -> dont know why double ptr resolv
|
||||
elV = elV.Elem()
|
||||
}
|
||||
|
||||
oID := elV.FieldByName(structField + "ID")
|
||||
if idIsPtr {
|
||||
oID = oID.Elem()
|
||||
}
|
||||
objectID := oID.Interface().(bson.ObjectId)
|
||||
object := objectIDs[objectID]
|
||||
|
||||
field := elV.FieldByName(structField)
|
||||
if fieldIsPtr {
|
||||
field.Set(reflect.ValueOf(object))
|
||||
} else {
|
||||
field.Set(reflect.ValueOf(object).Elem())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
108
setup.go
Normal file
108
setup.go
Normal file
@ -0,0 +1,108 @@
|
||||
package mgocrud
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
mgo "gopkg.in/mgo.v2"
|
||||
)
|
||||
|
||||
// EnsureIndex ensured mongodb index reflecting model struct index tag
|
||||
func EnsureIndex(db *mgo.Database, m ModelInterface) error {
|
||||
col := db.C(GetCollectionName(m))
|
||||
|
||||
mType := reflect.TypeOf(m)
|
||||
|
||||
textFields := []string{}
|
||||
|
||||
var _indexWalk func(t reflect.Type, fieldbase string) error
|
||||
_indexWalk = func(t reflect.Type, fieldbase string) error {
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
if t.Kind() == reflect.Ptr || t.Kind() == reflect.Slice {
|
||||
t = t.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
if t.Kind() == reflect.Struct {
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
indexTag := field.Tag.Get("index")
|
||||
|
||||
bsonField := strings.Split(field.Tag.Get("bson"), ",")[0]
|
||||
if bsonField == "" {
|
||||
bsonField = strings.ToLower(field.Name)
|
||||
}
|
||||
|
||||
if bsonField != "-" {
|
||||
if indexTag != "" {
|
||||
index := mgo.Index{
|
||||
Background: false,
|
||||
Sparse: false,
|
||||
Unique: false,
|
||||
}
|
||||
for _, indexEl := range strings.Split(indexTag, ",") {
|
||||
switch {
|
||||
case indexEl == "single":
|
||||
index.Key = []string{fieldbase + bsonField}
|
||||
case indexEl == "unique":
|
||||
index.Unique = true
|
||||
case indexEl == "sparse":
|
||||
index.Sparse = true
|
||||
case indexEl == "background":
|
||||
index.Background = true
|
||||
case indexEl == "text":
|
||||
textFields = append(textFields, "$text:"+fieldbase+bsonField)
|
||||
default:
|
||||
return fmt.Errorf("invalid index tag for field %s in model %+v", bsonField, t)
|
||||
}
|
||||
|
||||
}
|
||||
if len(index.Key) > 0 {
|
||||
// fmt.Println(bsonField, index)
|
||||
fmt.Printf("ensure index on collection %s for field %s\n", GetCollectionName(m), bsonField)
|
||||
err := col.EnsureIndex(index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fBase := bsonField + "."
|
||||
if field.Anonymous {
|
||||
fBase = ""
|
||||
}
|
||||
err := _indexWalk(field.Type, fBase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err := _indexWalk(mType, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(textFields) > 0 {
|
||||
// fmt.Println("$text", textFields)
|
||||
fmt.Printf("ensure text index on collection %s for fields %v\n", GetCollectionName(m), textFields)
|
||||
err := col.EnsureIndex(mgo.Index{
|
||||
Name: "textindex",
|
||||
Key: textFields,
|
||||
DefaultLanguage: "german",
|
||||
Background: false,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
28
validator.go
Normal file
28
validator.go
Normal file
@ -0,0 +1,28 @@
|
||||
package mgocrud
|
||||
|
||||
import (
|
||||
validator "gopkg.in/go-playground/validator.v8"
|
||||
mgo "gopkg.in/mgo.v2"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
)
|
||||
|
||||
// ValidateObject validates object via validator tag and custom method
|
||||
func ValidateObject(db *mgo.Database, m ModelInterface, changes bson.M) error {
|
||||
// first validate via struct tag
|
||||
validator := validator.New(&validator.Config{
|
||||
TagName: "validator",
|
||||
})
|
||||
err := validator.Struct(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// next execute custom model validator if exists
|
||||
if i, ok := m.(interface {
|
||||
Validate(db *mgo.Database, changes bson.M) error
|
||||
}); ok {
|
||||
return i.Validate(db, changes)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user