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;
}
DocumentSourceChangeStreamTransform::DocumentSourceChangeStreamTransform(
    const boost::intrusive_ptr<ExpressionContext>& expCtx,
    const ServerGlobalParams::FeatureCompatibility::Version& fcv,
    BSONObj changeStreamSpec)
    : DocumentSource(expCtx),
      _changeStreamSpec(changeStreamSpec.getOwned()),
      _isIndependentOfAnyCollection(expCtx->ns.isCollectionlessAggregateNS()),
      _fcv(fcv) {

    _nsRegex.emplace(DocumentSourceChangeStream::getNsRegexForChangeStream(expCtx->ns));

    auto spec = DocumentSourceChangeStreamSpec::parse(IDLParserErrorContext("$changeStream"),
                                                      _changeStreamSpec);

    // If the change stream spec includes a resumeToken with a shard key, populate the document key
    // cache with the field paths.
    auto resumeAfter = spec.getResumeAfter();
    auto startAfter = spec.getStartAfter();
    if (resumeAfter || startAfter) {
        ResumeToken token = resumeAfter ? resumeAfter.get() : startAfter.get();
        ResumeTokenData tokenData = token.getData();

        if (!tokenData.documentKey.missing() && tokenData.uuid) {
            std::vector<FieldPath> docKeyFields;
            auto docKey = tokenData.documentKey.getDocument();

            auto iter = docKey.fieldIterator();
            while (iter.more()) {
                auto fieldPair = iter.next();
                docKeyFields.push_back(fieldPair.first);
            }

            // If the document key from the resume token has more than one field, that means it
            // includes the shard key and thus should never change.
            auto isFinal = docKey.size() > 1;

            _documentKeyCache[tokenData.uuid.get()] =
                DocumentKeyCacheEntry({docKeyFields, isFinal});
        }
    }
}
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;
}