parent
							
								
									e60fef13df
								
							
						
					
					
						commit
						b7e92a5055
					
				@ -0,0 +1,140 @@ | 
				
			||||
package common | 
				
			||||
 | 
				
			||||
import ( | 
				
			||||
	"reflect" | 
				
			||||
	"strconv" | 
				
			||||
	"strings" | 
				
			||||
 | 
				
			||||
	"github.com/mainnika/mongox-go-driver/mongox" | 
				
			||||
	"github.com/mainnika/mongox-go-driver/mongox/errors" | 
				
			||||
	"github.com/mainnika/mongox-go-driver/mongox/query" | 
				
			||||
	"go.mongodb.org/mongo-driver/bson/primitive" | 
				
			||||
	"go.mongodb.org/mongo-driver/mongo" | 
				
			||||
	"go.mongodb.org/mongo-driver/mongo/options" | 
				
			||||
) | 
				
			||||
 | 
				
			||||
func createSimpleLoad(db *mongox.Database, target interface{}, composed *query.Query) (cursor *mongo.Cursor, err error) { | 
				
			||||
 | 
				
			||||
	collection := db.GetCollectionOf(target) | 
				
			||||
	opts := options.Find() | 
				
			||||
 | 
				
			||||
	opts.Sort = composed.Sorter() | 
				
			||||
	opts.Limit = composed.Limiter() | 
				
			||||
	opts.Skip = composed.Skipper() | 
				
			||||
 | 
				
			||||
	return collection.Find(db.Context(), composed.M(), opts) | 
				
			||||
} | 
				
			||||
 | 
				
			||||
func createAggregateLoad(db *mongox.Database, target interface{}, composed *query.Query) (cursor *mongo.Cursor, err error) { | 
				
			||||
 | 
				
			||||
	collection := db.GetCollectionOf(target) | 
				
			||||
	opts := options.Aggregate() | 
				
			||||
 | 
				
			||||
	pipelineHead := primitive.A{primitive.M{"$match": composed.M()}} | 
				
			||||
	pipelineTail := primitive.A{} | 
				
			||||
 | 
				
			||||
	el := reflect.ValueOf(target).Elem() | 
				
			||||
	elType := el.Type() | 
				
			||||
	numField := elType.NumField() | 
				
			||||
	_, preloads := composed.Preloader() | 
				
			||||
 | 
				
			||||
	for i := 0; i < numField; i++ { | 
				
			||||
 | 
				
			||||
		field := elType.Field(i) | 
				
			||||
		tag := field.Tag | 
				
			||||
 | 
				
			||||
		preloadTag, ok := tag.Lookup("preload") | 
				
			||||
		if !ok { | 
				
			||||
			continue | 
				
			||||
		} | 
				
			||||
		jsonTag, ok := tag.Lookup("json") | 
				
			||||
		if jsonTag == "-" { | 
				
			||||
			return nil, errors.Malformedf("preload private field is impossible") | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		jsonData := strings.SplitN(jsonTag, ",", 2) | 
				
			||||
		jsonName := field.Name | 
				
			||||
		if len(jsonData) > 0 { | 
				
			||||
			jsonName = strings.TrimSpace(jsonData[0]) | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		preloadData := strings.Split(preloadTag, ",") | 
				
			||||
		if len(preloadData) == 0 { | 
				
			||||
			continue | 
				
			||||
		} | 
				
			||||
		if len(preloadData) == 1 { | 
				
			||||
			panic("there is no foreign field") | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		preloadName := strings.TrimSpace(preloadData[0]) | 
				
			||||
		if len(preloadName) == 0 { | 
				
			||||
			preloadName = jsonName | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		foreignField := strings.TrimSpace(preloadData[1]) | 
				
			||||
		if len(foreignField) == 0 { | 
				
			||||
			panic("there is no foreign field") | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		preloadLimiter := 100 | 
				
			||||
		if len(preloadData) > 2 { | 
				
			||||
 | 
				
			||||
			stringLimit := strings.TrimSpace(preloadData[2]) | 
				
			||||
			intLimit := preloadLimiter | 
				
			||||
 | 
				
			||||
			intLimit, err = strconv.Atoi(stringLimit) | 
				
			||||
			if err == nil { | 
				
			||||
				preloadLimiter = intLimit | 
				
			||||
			} | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		for _, preload := range preloads { | 
				
			||||
			if preload != preloadName { | 
				
			||||
				continue | 
				
			||||
			} | 
				
			||||
 | 
				
			||||
			isPtr := el.Field(i).Kind() == reflect.Ptr | 
				
			||||
			isSlice := el.Field(i).Kind() == reflect.Slice | 
				
			||||
			isIface := el.Field(i).CanInterface() | 
				
			||||
			if (!isPtr && !isSlice) || !isIface { | 
				
			||||
				continue | 
				
			||||
			} | 
				
			||||
 | 
				
			||||
			typ := el.Field(i).Type() | 
				
			||||
			lookupCollection := db.GetCollectionOf(reflect.Zero(typ).Interface()) | 
				
			||||
			lookupVars := primitive.M{"selector": "$_id"} | 
				
			||||
			lookupPipeline := primitive.A{ | 
				
			||||
				// todo: make match from composed query
 | 
				
			||||
				primitive.M{"$match": primitive.M{"$expr": primitive.M{"$eq": primitive.A{"$" + foreignField, "$$selector"}}}}, | 
				
			||||
			} | 
				
			||||
 | 
				
			||||
			if isSlice && preloadLimiter > 0 { | 
				
			||||
				lookupPipeline = append(lookupPipeline, primitive.M{"$limit": preloadLimiter}) | 
				
			||||
			} else if !isSlice { | 
				
			||||
				lookupPipeline = append(lookupPipeline, primitive.M{"$limit": 1}) | 
				
			||||
			} | 
				
			||||
 | 
				
			||||
			pipelineTail = append(pipelineTail, primitive.M{ | 
				
			||||
				"$lookup": primitive.M{ | 
				
			||||
					"from":     lookupCollection.Name(), | 
				
			||||
					"let":      lookupVars, | 
				
			||||
					"pipeline": lookupPipeline, | 
				
			||||
					"as":       jsonName, | 
				
			||||
				}, | 
				
			||||
			}) | 
				
			||||
 | 
				
			||||
			if isSlice { | 
				
			||||
				continue | 
				
			||||
			} | 
				
			||||
 | 
				
			||||
			pipelineTail = append(pipelineTail, primitive.M{ | 
				
			||||
				"$unwind": primitive.M{ | 
				
			||||
					"preserveNullAndEmptyArrays": true, | 
				
			||||
					"path":                       "$" + jsonName, | 
				
			||||
				}, | 
				
			||||
			}) | 
				
			||||
		} | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	return collection.Aggregate(db.Context(), append(pipelineHead, pipelineTail...), opts) | 
				
			||||
} | 
				
			||||
@ -0,0 +1,17 @@ | 
				
			||||
package query | 
				
			||||
 | 
				
			||||
// Preloader is a filter to skip the result
 | 
				
			||||
type Preloader interface { | 
				
			||||
	Preload() []string | 
				
			||||
} | 
				
			||||
 | 
				
			||||
// Preload is a simple implementation of the Skipper filter
 | 
				
			||||
type Preload []string | 
				
			||||
 | 
				
			||||
var _ Preloader = Preload{} | 
				
			||||
 | 
				
			||||
// Preload returns a preload list
 | 
				
			||||
func (l Preload) Preload() []string { | 
				
			||||
 | 
				
			||||
	return Preload(l) | 
				
			||||
} | 
				
			||||
					Loading…
					
					
				
		Reference in new issue