list<intrusive_ptr<DocumentSource>> DocumentSourceChangeStream::createFromBson(
    BSONElement elem, const intrusive_ptr<ExpressionContext>& expCtx) {
    // TODO: Add sharding support here (SERVER-29141).
    uassert(
        40470, "The $changeStream stage is not supported on sharded systems.", !expCtx->inRouter);
    uassert(40471,
            "Only default collation is allowed when using a $changeStream stage.",
            !expCtx->getCollator());

    auto replCoord = repl::ReplicationCoordinator::get(expCtx->opCtx);
    uassert(40573, "The $changeStream stage is only supported on replica sets", replCoord);
    Timestamp startFrom = replCoord->getLastCommittedOpTime().getTimestamp();

    intrusive_ptr<DocumentSourceCheckResumeToken> resumeStage = nullptr;
    auto spec = DocumentSourceChangeStreamSpec::parse(IDLParserErrorContext("$changeStream"),
                                                      elem.embeddedObject());
    if (auto resumeAfter = spec.getResumeAfter()) {
        ResumeToken token = resumeAfter.get();
        startFrom = token.getTimestamp();
        DocumentSourceCheckResumeTokenSpec spec;
        spec.setResumeToken(std::move(token));
        resumeStage = DocumentSourceCheckResumeToken::create(expCtx, std::move(spec));
    }
    const bool changeStreamIsResuming = resumeStage != nullptr;

    auto fullDocOption = spec.getFullDocument();
    uassert(40575,
            str::stream() << "unrecognized value for the 'fullDocument' option to the "
                             "$changeStream stage. Expected \"default\" or "
                             "\"updateLookup\", got \""
                          << fullDocOption
                          << "\"",
            fullDocOption == "updateLookup"_sd || fullDocOption == "default"_sd);
    const bool shouldLookupPostImage = (fullDocOption == "updateLookup"_sd);

    auto oplogMatch = DocumentSourceOplogMatch::create(
        buildMatchFilter(expCtx->ns, startFrom, changeStreamIsResuming), expCtx);
    auto transformation = createTransformationStage(elem.embeddedObject(), expCtx);
    list<intrusive_ptr<DocumentSource>> stages = {oplogMatch, transformation};
    if (resumeStage) {
        stages.push_back(resumeStage);
    }
    auto closeCursorSource = DocumentSourceCloseCursor::create(expCtx);
    stages.push_back(closeCursorSource);
    if (shouldLookupPostImage) {
        stages.push_back(DocumentSourceLookupChangePostImage::create(expCtx));
    }
    return stages;
}
list<intrusive_ptr<DocumentSource>> DocumentSourceChangeStream::createFromBson(
    BSONElement elem, const intrusive_ptr<ExpressionContext>& expCtx) {
    // TODO: Add sharding support here (SERVER-29141).
    uassert(
        40470, "The $changeStream stage is not supported on sharded systems.", !expCtx->inMongos);
    uassert(40471,
            "Only default collation is allowed when using a $changeStream stage.",
            !expCtx->getCollator());

    auto replCoord = repl::ReplicationCoordinator::get(expCtx->opCtx);
    uassert(40573,
            "The $changeStream stage is only supported on replica sets",
            replCoord &&
                replCoord->getReplicationMode() == repl::ReplicationCoordinator::Mode::modeReplSet);
    Timestamp startFrom = replCoord->getMyLastAppliedOpTime().getTimestamp();

    intrusive_ptr<DocumentSource> resumeStage = nullptr;
    auto spec = DocumentSourceChangeStreamSpec::parse(IDLParserErrorContext("$changeStream"),
                                                      elem.embeddedObject());
    if (auto resumeAfter = spec.getResumeAfter()) {
        ResumeToken token = resumeAfter.get();
        auto resumeNamespace = UUIDCatalog::get(expCtx->opCtx).lookupNSSByUUID(token.getUuid());
        uassert(40615,
                "The resume token UUID does not exist. Has the collection been dropped?",
                !resumeNamespace.isEmpty());
        startFrom = token.getTimestamp();
        if (expCtx->needsMerge) {
            DocumentSourceShardCheckResumabilitySpec spec;
            spec.setResumeToken(std::move(token));
            resumeStage = DocumentSourceShardCheckResumability::create(expCtx, std::move(spec));
        } else {
            DocumentSourceEnsureResumeTokenPresentSpec spec;
            spec.setResumeToken(std::move(token));
            resumeStage = DocumentSourceEnsureResumeTokenPresent::create(expCtx, std::move(spec));
        }
    }
    const bool changeStreamIsResuming = resumeStage != nullptr;

    auto fullDocOption = spec.getFullDocument();
    uassert(40575,
            str::stream() << "unrecognized value for the 'fullDocument' option to the "
                             "$changeStream stage. Expected \"default\" or "
                             "\"updateLookup\", got \""
                          << fullDocOption
                          << "\"",
            fullDocOption == "updateLookup"_sd || fullDocOption == "default"_sd);
    const bool shouldLookupPostImage = (fullDocOption == "updateLookup"_sd);

    auto oplogMatch = DocumentSourceOplogMatch::create(
        buildMatchFilter(expCtx->ns, startFrom, changeStreamIsResuming), expCtx);
    auto transformation = createTransformationStage(elem.embeddedObject(), expCtx);
    list<intrusive_ptr<DocumentSource>> stages = {oplogMatch, transformation};
    if (resumeStage) {
        stages.push_back(resumeStage);
    }
    auto closeCursorSource = DocumentSourceCloseCursor::create(expCtx);
    stages.push_back(closeCursorSource);
    if (shouldLookupPostImage) {
        stages.push_back(DocumentSourceLookupChangePostImage::create(expCtx));
    }
    return stages;
}