Exemplo n.º 1
0
// execPipeline executes the sepcified pipeline query.
func execPipeline(context interface{}, db *db.DB, q *query.Query, vars map[string]string, data map[string]interface{}, explain bool) (docs, []map[string]interface{}, error) {

	// I am returning commands as the second return value because if there
	// is an error I need to send how far we got back to the client. If not,
	// the user will not understand the error message.

	// We need to check to see if the last command is the extended $save command.
	commands := q.Commands
	l := len(q.Commands) - 1

	// Validate we have scripts to run.
	if l < 0 {
		return docs{}, commands, errors.New("Invalid pipeline script")
	}

	// If the last command is a $save, capture its value and remove
	// it from the pipeline.
	var save map[string]interface{}
	if v, exists := q.Commands[l]["$save"]; exists {
		if cmd, ok := v.(map[string]interface{}); ok {
			save = cmd
		}

		commands = q.Commands[0:l]
	}

	var agg string
	var pipeline []bson.M

	// Iterate over the commands and build the pipeline.
	for _, command := range commands {

		// Do we have variables to be substitued.
		if vars != nil {
			if err := ProcessVariables(context, command, vars, data); err != nil {
				return docs{}, commands, err
			}
		}

		// Add the operation to the slice for the pipeline.
		pipeline = append(pipeline, command)

		// Build a logable version of this pipeline.
		agg += mongo.Query(command) + ",\n"
	}

	// Are we being asked to execute the query on a view.
	if q.Collection == "view" {

		// Materialize the view for the query.
		viewCol, err := materializeView(context, db, q, vars)
		if err != nil {
			return docs{}, commands, err
		}

		// Defer clean up of the temporary collection.
		defer cleanupView(context, db, viewCol)
	}

	// Do we want the explain output.
	if explain {

		// Build the pipeline function for the execution for explain.
		var m bson.M
		f := func(c *mgo.Collection) error {
			log.Dev(context, "executePipeline", "MGO Explain :\ndb.%s.aggregate([\n%s])", c.Name, agg)
			return c.Pipe(pipeline).Explain(&m)
		}

		// Execute the pipeline.
		if err := db.ExecuteMGO(context, q.Collection, f); err != nil {
			return docs{}, commands, err
		}

		return docs{q.Name, []bson.M{m}}, commands, nil
	}

	// Set the default timeout for the session.
	timeout := 25 * time.Second
	if q.Timeout != "" {
		if d, err := time.ParseDuration(q.Timeout); err != nil {
			log.Dev(context, "executePipeline", "WARNING : Unable to Set Timeout[%s], using default.", q.Timeout)
		} else {
			timeout = d
		}
	}

	log.Dev(context, "executePipeline", "MGO Timeout Set[%s]", timeout)

	// Build the pipeline function for the execution.
	var results []bson.M
	f := func(c *mgo.Collection) error {
		log.Dev(context, "executePipeline", "MGO Started\ndb.%s.aggregate([\n%s])", c.Name, agg)
		err := c.Pipe(pipeline).All(&results)
		return err
	}

	// Set the channel to one because we might not be around
	// waiting for the result on timeouts.
	wait := make(chan error, 1)

	// Execute the pipeline.
	go func() {
		defer func() {
			if r := recover(); r != nil {
				log.Dev(context, "executePipeline", "******> Recovered from timing out")
			}
			log.Dev(context, "executePipeline", "MGO Response Complete")
		}()

		wait <- db.ExecuteMGOTimeout(context, timeout, q.Collection, f)
	}()

	// Did any errors occur.
	select {

	// Wait for the response from executing the pipeline.
	case err := <-wait:
		if err != nil {
			if _, ok := err.(*net.OpError); ok {
				log.Error(context, "executePipeline", err, "Timed out Network")
				return docs{}, commands, errors.New("Completed : Timed out executing commands")
			}

			log.Error(context, "executePipeline", err, "Completed")
			return docs{}, commands, err
		}

	// Wait to timeout the entire operation.
	case <-time.After(timeout):
		err := errors.New("Timedout executing commands")
		log.Error(context, "executePipeline", err, "Completed : Timed out Processing")
		return docs{}, commands, err
	}

	log.Dev(context, "executePipeline", "Completed")

	// If there were no results, return an empty array.
	if results == nil {
		return docs{q.Name, []bson.M{}}, commands, nil
	}

	// Perform any masking that is required.
	if err := processMasks(context, db, q.Collection, results); err != nil {
		return docs{}, commands, err
	}

	// Do we need to save the result.
	if save != nil {
		if err := saveResult(context, save, results, data); err != nil {
			return docs{}, commands, err
		}
	}

	return docs{q.Name, results}, commands, nil
}