protected function doMapReduce($map, $reduce, $out, array $query, array $options)
    {
        list($commandOptions, $clientOptions) = isset($options['socketTimeoutMS']) || isset($options['timeout'])
            ? $this->splitCommandAndClientOptions($options)
            : [$options, []];

        $command = [];
        $command['mapreduce'] = $this->mongoCollection->getName();
        $command['map'] = $map;
        $command['reduce'] = $reduce;
        $command['query'] = (object) $query;
        $command['out'] = $out;
        $command = array_merge($command, $commandOptions);

        foreach (['map', 'reduce', 'finalize'] as $key) {
            if (isset($command[$key]) && is_string($command[$key])) {
                $command[$key] = new \MongoCode($command[$key]);
            }
        }

        $result = $this->database->command($command, $clientOptions);

        if (empty($result['ok'])) {
            throw new ResultException($result);
        }

        if (isset($result['result']) && is_string($result['result'])) {
            return $this->database->selectCollection($result['result'])->find();
        }

        if (isset($result['result']) && is_array($result['result']) &&
            isset($result['result']['db'], $result['result']['collection'])) {
            return $this->database->getConnection()
                ->selectCollection($result['result']['db'], $result['result']['collection'])
                ->find();
        }

        $arrayIterator = new ArrayIterator(isset($result['results']) ? $result['results'] : []);
        $arrayIterator->setCommandResult($result);

        return $arrayIterator;
    }