Status explain(OperationContext* opCtx,
                   const OpMsgRequest& request,
                   ExplainOptions::Verbosity verbosity,
                   BSONObjBuilder* out) const override {
        std::string dbname = request.getDatabase().toString();
        const BSONObj& cmdObj = request.body;
        // Acquire locks. The RAII object is optional, because in the case of a view, the locks
        // need to be released.
        boost::optional<AutoGetCollectionForReadCommand> ctx;
        ctx.emplace(opCtx,
                    CommandHelpers::parseNsCollectionRequired(dbname, cmdObj),
                    AutoGetCollection::ViewMode::kViewsPermitted);
        const auto nss = ctx->getNss();

        // Parse the command BSON to a QueryRequest.
        const bool isExplain = true;
        auto qrStatus = QueryRequest::makeFromFindCommand(nss, cmdObj, isExplain);
        if (!qrStatus.isOK()) {
            return qrStatus.getStatus();
        }

        // Finish the parsing step by using the QueryRequest to create a CanonicalQuery.
        const ExtensionsCallbackReal extensionsCallback(opCtx, &nss);
        const boost::intrusive_ptr<ExpressionContext> expCtx;
        auto statusWithCQ =
            CanonicalQuery::canonicalize(opCtx,
                                         std::move(qrStatus.getValue()),
                                         expCtx,
                                         extensionsCallback,
                                         MatchExpressionParser::kAllowAllSpecialFeatures);
        if (!statusWithCQ.isOK()) {
            return statusWithCQ.getStatus();
        }
        std::unique_ptr<CanonicalQuery> cq = std::move(statusWithCQ.getValue());

        if (ctx->getView()) {
            // Relinquish locks. The aggregation command will re-acquire them.
            ctx.reset();

            // Convert the find command into an aggregation using $match (and other stages, as
            // necessary), if possible.
            const auto& qr = cq->getQueryRequest();
            auto viewAggregationCommand = qr.asAggregationCommand();
            if (!viewAggregationCommand.isOK())
                return viewAggregationCommand.getStatus();

            // Create the agg request equivalent of the find operation, with the explain verbosity
            // included.
            auto aggRequest = AggregationRequest::parseFromBSON(
                nss, viewAggregationCommand.getValue(), verbosity);
            if (!aggRequest.isOK()) {
                return aggRequest.getStatus();
            }

            try {
                return runAggregate(
                    opCtx, nss, aggRequest.getValue(), viewAggregationCommand.getValue(), *out);
            } catch (DBException& error) {
                if (error.code() == ErrorCodes::InvalidPipelineOperator) {
                    return {ErrorCodes::InvalidPipelineOperator,
                            str::stream() << "Unsupported in view pipeline: " << error.what()};
                }
                return error.toStatus();
            }
        }

        // The collection may be NULL. If so, getExecutor() should handle it by returning an
        // execution tree with an EOFStage.
        Collection* const collection = ctx->getCollection();

        // We have a parsed query. Time to get the execution plan for it.
        auto statusWithPlanExecutor = getExecutorFind(opCtx, collection, nss, std::move(cq));
        if (!statusWithPlanExecutor.isOK()) {
            return statusWithPlanExecutor.getStatus();
        }
        auto exec = std::move(statusWithPlanExecutor.getValue());

        // Got the execution tree. Explain it.
        Explain::explainStages(exec.get(), collection, verbosity, out);
        return Status::OK();
    }