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

        $command = [];
        $command['ns'] = $this->mongoCollection->getName();
        $command['initial'] = (object) $initial;
        $command['$reduce'] = $reduce;

        if (is_string($keys) || $keys instanceof \MongoCode) {
            $command['$keyf'] = $keys;
        } else {
            $command['key'] = $keys;
        }

        $command = array_merge($command, $commandOptions);

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

        if (isset($command['cond']) && is_array($command['cond'])) {
            $command['cond'] = (object) $command['cond'];
        }

        $database = $this->database;
        $result = $this->retry(function() use ($database, $command, $clientOptions) {
            return $database->command(['group' => $command], $clientOptions);
        });

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

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

        return $arrayIterator;
    }