mgocrud/lookup.go

169 lines
3.6 KiB
Go

package mgocrud
import (
"fmt"
"reflect"
"gopkg.in/mgo.v2/bson"
)
// Lookup extends results with data for inline structs
func (db *MgoDatabase) Lookup(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)
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 := db.ReadCollection(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
}