diff options
-rw-r--r-- | src/mongo/db/database_name.h | 2 | ||||
-rw-r--r-- | src/mongo/db/namespace_string.h | 4 | ||||
-rw-r--r-- | src/mongo/db/pipeline/aggregation_context_fixture.h | 10 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_graph_lookup.cpp | 10 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_graph_lookup_test.cpp | 84 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_lookup.cpp | 16 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_lookup_test.cpp | 129 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_merge.cpp | 8 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_merge_cursors_test.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_merge_spec.cpp | 3 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_merge_test.cpp | 62 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_out.cpp | 17 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_out.h | 2 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_out_test.cpp | 61 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_union_with.cpp | 12 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_union_with_test.cpp | 71 |
16 files changed, 394 insertions, 99 deletions
diff --git a/src/mongo/db/database_name.h b/src/mongo/db/database_name.h index c2099fc654e..a4a549eb75a 100644 --- a/src/mongo/db/database_name.h +++ b/src/mongo/db/database_name.h @@ -73,7 +73,7 @@ public: static DatabaseName createSystemTenantDbName(StringData dbString); - boost::optional<TenantId> tenantId() const { + const boost::optional<TenantId>& tenantId() const { return _tenantId; } diff --git a/src/mongo/db/namespace_string.h b/src/mongo/db/namespace_string.h index 692d768dcca..687fb431bb1 100644 --- a/src/mongo/db/namespace_string.h +++ b/src/mongo/db/namespace_string.h @@ -327,7 +327,7 @@ public: Allow, // Deprecated }; - boost::optional<TenantId> tenantId() const { + const boost::optional<TenantId>& tenantId() const { return _dbName.tenantId(); } @@ -336,7 +336,7 @@ public: return StringData(_dbName.toString()); } - DatabaseName dbName() const { + const DatabaseName& dbName() const { return _dbName; } diff --git a/src/mongo/db/pipeline/aggregation_context_fixture.h b/src/mongo/db/pipeline/aggregation_context_fixture.h index a4dfcfeb769..76cc01a40c4 100644 --- a/src/mongo/db/pipeline/aggregation_context_fixture.h +++ b/src/mongo/db/pipeline/aggregation_context_fixture.h @@ -46,7 +46,7 @@ namespace mongo { class AggregationContextFixture : public ServiceContextTest { public: AggregationContextFixture() - : AggregationContextFixture(NamespaceString("unittests.pipeline_test")) {} + : AggregationContextFixture(NamespaceString(boost::none, "unittests", "pipeline_test")) {} AggregationContextFixture(NamespaceString nss) { auto service = getServiceContext(); @@ -75,4 +75,12 @@ private: ServiceContext::UniqueOperationContext _opCtx; boost::intrusive_ptr<ExpressionContextForTest> _expCtx; }; + +class ServerlessAggregationContextFixture : public AggregationContextFixture { +public: + ServerlessAggregationContextFixture() + : AggregationContextFixture( + NamespaceString(TenantId(OID::gen()), "unittests", "pipeline_test")) {} +}; + } // namespace mongo diff --git a/src/mongo/db/pipeline/document_source_graph_lookup.cpp b/src/mongo/db/pipeline/document_source_graph_lookup.cpp index 006a84c68b3..5957299c224 100644 --- a/src/mongo/db/pipeline/document_source_graph_lookup.cpp +++ b/src/mongo/db/pipeline/document_source_graph_lookup.cpp @@ -61,7 +61,7 @@ namespace { // // {from: {db: "local", coll: "system.tenantMigration.oplogView"}, ...}. NamespaceString parseGraphLookupFromAndResolveNamespace(const BSONElement& elem, - StringData defaultDb) { + const DatabaseName& defaultDb) { // The object syntax only works for 'local.system.tenantMigration.oplogView' which is not a user // namespace so object type is omitted from the error message below. uassert(ErrorCodes::FailedToParse, @@ -79,6 +79,7 @@ NamespaceString parseGraphLookupFromAndResolveNamespace(const BSONElement& elem, // Valdate the db and coll names. auto spec = NamespaceSpec::parse({elem.fieldNameStringData()}, elem.embeddedObject()); + // TODO SERVER-62491 Use system tenantId to construct nss. auto nss = NamespaceString(spec.getDb().value_or(""), spec.getColl().value_or("")); uassert(ErrorCodes::FailedToParse, str::stream() @@ -109,7 +110,7 @@ std::unique_ptr<DocumentSourceGraphLookUp::LiteParsed> DocumentSourceGraphLookUp fromElement); return std::make_unique<LiteParsed>( - spec.fieldName(), parseGraphLookupFromAndResolveNamespace(fromElement, nss.db())); + spec.fieldName(), parseGraphLookupFromAndResolveNamespace(fromElement, nss.dbName())); } REGISTER_DOCUMENT_SOURCE(graphLookup, @@ -548,9 +549,10 @@ void DocumentSourceGraphLookUp::checkMemoryUsage() { void DocumentSourceGraphLookUp::serializeToArray( std::vector<Value>& array, boost::optional<ExplainOptions::Verbosity> explain) const { + // Do not include tenantId in serialized 'from' namespace. auto fromValue = (pExpCtx->ns.db() == _from.db()) ? Value(_from.coll()) - : Value(Document{{"db", _from.db()}, {"coll", _from.coll()}}); + : Value(Document{{"db", _from.dbName().db()}, {"coll", _from.coll()}}); // Serialize default options. MutableDocument spec(DOC("from" << fromValue << "as" << _as.fullPath() << "connectToField" @@ -750,7 +752,7 @@ intrusive_ptr<DocumentSource> DocumentSourceGraphLookUp::createFromBson( } if (argName == "from") { - from = parseGraphLookupFromAndResolveNamespace(argument, expCtx->ns.db().toString()); + from = parseGraphLookupFromAndResolveNamespace(argument, expCtx->ns.dbName()); } else if (argName == "as") { as = argument.String(); } else if (argName == "connectFromField") { diff --git a/src/mongo/db/pipeline/document_source_graph_lookup_test.cpp b/src/mongo/db/pipeline/document_source_graph_lookup_test.cpp index 26345aa16ab..852ce00d519 100644 --- a/src/mongo/db/pipeline/document_source_graph_lookup_test.cpp +++ b/src/mongo/db/pipeline/document_source_graph_lookup_test.cpp @@ -82,6 +82,7 @@ private: // system.tenantMigration.oplogView} can be round tripped. TEST_F(DocumentSourceGraphLookUpTest, LookupReParseSerializedStageWithFromDBAndColl) { auto expCtx = getExpCtx(); + // TODO SERVER-62491 Use system tenantId for nss 'local.system.tenantMigration.oplogView'. NamespaceString fromNs("local", "system.tenantMigration.oplogView"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -125,7 +126,7 @@ TEST_F(DocumentSourceGraphLookUpTest, LookupReParseSerializedStageWithFromDBAndC // local.system.tenantMigration.oplogView. TEST_F(DocumentSourceGraphLookUpTest, RejectsPipelineFromDBAndCollNotLocalDBOrRsOplogView) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -143,7 +144,7 @@ TEST_F(DocumentSourceGraphLookUpTest, RejectsPipelineFromDBAndCollNotLocalDBOrRs // not "system.tenantMigration.oplogView". TEST_F(DocumentSourceGraphLookUpTest, RejectsPipelineFromDBAndCollNotRsOplogView) { auto expCtx = getExpCtx(); - NamespaceString fromNs("local", "coll"); + NamespaceString fromNs(boost::none, "local", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -161,7 +162,7 @@ TEST_F(DocumentSourceGraphLookUpTest, RejectsPipelineFromDBAndCollNotRsOplogView // "system.tenantMigration.oplogView" but "db" is not "local". TEST_F(DocumentSourceGraphLookUpTest, RejectsPipelineFromDBAndCollNotLocalDB) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "system.tenantMigration.oplogView"); + NamespaceString fromNs(boost::none, "test", "system.tenantMigration.oplogView"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -184,7 +185,7 @@ TEST_F(DocumentSourceGraphLookUpTest, std::deque<DocumentSource::GetNextResult> fromContents{Document{{"to", 0}}}; - NamespaceString fromNs("test", "graph_lookup"); + NamespaceString fromNs(boost::none, "test", "graph_lookup"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); expCtx->mongoProcessInterface = std::make_shared<MockMongoInterface>(std::move(fromContents)); @@ -213,7 +214,7 @@ TEST_F(DocumentSourceGraphLookUpTest, std::deque<DocumentSource::GetNextResult> fromContents{ Document{{"_id", "a"_sd}, {"to", 0}, {"from", 1}}, Document{{"to", 1}}}; - NamespaceString fromNs("test", "graph_lookup"); + NamespaceString fromNs(boost::none, "test", "graph_lookup"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); expCtx->mongoProcessInterface = std::make_shared<MockMongoInterface>(std::move(fromContents)); @@ -242,7 +243,7 @@ TEST_F(DocumentSourceGraphLookUpTest, std::deque<DocumentSource::GetNextResult> fromContents{Document{{"to", 0}}}; - NamespaceString fromNs("test", "graph_lookup"); + NamespaceString fromNs(boost::none, "test", "graph_lookup"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); expCtx->mongoProcessInterface = std::make_shared<MockMongoInterface>(std::move(fromContents)); @@ -286,7 +287,7 @@ TEST_F(DocumentSourceGraphLookUpTest, std::deque<DocumentSource::GetNextResult> fromContents{ Document(to1), Document(to2), Document(to0from1), Document(to0from2)}; - NamespaceString fromNs("test", "graph_lookup"); + NamespaceString fromNs(boost::none, "test", "graph_lookup"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); expCtx->mongoProcessInterface = std::make_shared<MockMongoInterface>(std::move(fromContents)); @@ -350,7 +351,7 @@ TEST_F(DocumentSourceGraphLookUpTest, ShouldPropagatePauses) { std::deque<DocumentSource::GetNextResult> fromContents{ Document{{"_id", "a"_sd}, {"to", 0}, {"from", 1}}, Document{{"_id", "b"_sd}, {"to", 1}}}; - NamespaceString fromNs("test", "foreign"); + NamespaceString fromNs(boost::none, "test", "foreign"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); expCtx->mongoProcessInterface = std::make_shared<MockMongoInterface>(std::move(fromContents)); @@ -418,7 +419,7 @@ TEST_F(DocumentSourceGraphLookUpTest, ShouldPropagatePausesWhileUnwinding) { std::deque<DocumentSource::GetNextResult> fromContents{ Document{{"_id", "a"_sd}, {"to", 0}, {"from", 1}}, Document{{"_id", "b"_sd}, {"to", 1}}}; - NamespaceString fromNs("test", "foreign"); + NamespaceString fromNs(boost::none, "test", "foreign"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -484,7 +485,7 @@ TEST_F(DocumentSourceGraphLookUpTest, ShouldPropagatePausesWhileUnwinding) { TEST_F(DocumentSourceGraphLookUpTest, GraphLookupShouldReportAsFieldIsModified) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "foreign"); + NamespaceString fromNs(boost::none, "test", "foreign"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); expCtx->mongoProcessInterface = @@ -509,7 +510,7 @@ TEST_F(DocumentSourceGraphLookUpTest, GraphLookupShouldReportAsFieldIsModified) TEST_F(DocumentSourceGraphLookUpTest, GraphLookupShouldReportFieldsModifiedByAbsorbedUnwind) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "foreign"); + NamespaceString fromNs(boost::none, "test", "foreign"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); expCtx->mongoProcessInterface = @@ -541,7 +542,7 @@ TEST_F(DocumentSourceGraphLookUpTest, GraphLookupWithComparisonExpressionForStar auto inputMock = DocumentSourceMock::createForTest(Document({{"_id", 0}, {"a", 1}, {"b", 2}}), expCtx); - NamespaceString fromNs("test", "foreign"); + NamespaceString fromNs(boost::none, "test", "foreign"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); std::deque<DocumentSource::GetNextResult> fromContents{Document{{"_id", 0}, {"to", true}}, @@ -606,7 +607,7 @@ TEST_F(DocumentSourceGraphLookUpTest, ShouldExpandArraysAtEndOfConnectFromField) Document(middle3), Document(sinkDoc)}; - NamespaceString fromNs("test", "graph_lookup"); + NamespaceString fromNs(boost::none, "test", "graph_lookup"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); expCtx->mongoProcessInterface = std::make_shared<MockMongoInterface>(std::move(fromContents)); @@ -679,7 +680,7 @@ TEST_F(DocumentSourceGraphLookUpTest, ShouldNotExpandArraysWithinArraysAtEndOfCo std::deque<DocumentSource::GetNextResult> fromContents{ Document(startDoc), Document(target1), Document(target2), Document(soloDoc)}; - NamespaceString fromNs("test", "graph_lookup"); + NamespaceString fromNs(boost::none, "test", "graph_lookup"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); expCtx->mongoProcessInterface = std::make_shared<MockMongoInterface>(std::move(fromContents)); @@ -713,5 +714,60 @@ TEST_F(DocumentSourceGraphLookUpTest, ShouldNotExpandArraysWithinArraysAtEndOfCo ASSERT(graphLookupStage->getNext().isEOF()); } + +using DocumentSourceUnionWithServerlessTest = ServerlessAggregationContextFixture; + +TEST_F(DocumentSourceUnionWithServerlessTest, + LiteParsedDocumentSourceLookupContainsExpectedNamespacesInServerless) { + auto expCtx = getExpCtx(); + auto originalBSON = BSON("$graphLookup" << BSON("from" + << "foo" + << "startWith" + << "$x" + << "connectFromField" + << "id" + << "connectToField" + << "id" + << "as" + << "connections")); + + std::vector<BSONObj> pipeline; + NamespaceString nss(expCtx->ns.dbName(), "testColl"); + auto liteParsedLookup = + DocumentSourceGraphLookUp::LiteParsed::parse(nss, originalBSON.firstElement()); + auto namespaceSet = liteParsedLookup->getInvolvedNamespaces(); + ASSERT_EQ(1, namespaceSet.size()); + ASSERT_EQ(1ul, namespaceSet.count(NamespaceString(expCtx->ns.dbName(), "foo"))); +} + +TEST_F(DocumentSourceUnionWithServerlessTest, + CreateFromBSONContainsExpectedNamespacesInServerless) { + auto expCtx = getExpCtx(); + auto tenantId = expCtx->ns.tenantId(); + ASSERT(tenantId); + + NamespaceString graphLookupNs(expCtx->ns.dbName(), "foo"); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {graphLookupNs.coll().toString(), {graphLookupNs, std::vector<BSONObj>()}}}); + + auto spec = BSON("$graphLookup" << BSON("from" + << "foo" + << "startWith" + << "$x" + << "connectFromField" + << "id" + << "connectToField" + << "id" + << "as" + << "connections")); + auto graphLookupStage = DocumentSourceGraphLookUp::createFromBson(spec.firstElement(), expCtx); + + auto pipeline = + Pipeline::create({DocumentSourceMock::createForTest(expCtx), graphLookupStage}, expCtx); + auto involvedNssSet = pipeline->getInvolvedCollections(); + ASSERT_EQ(involvedNssSet.size(), 1UL); + ASSERT_EQ(1ul, involvedNssSet.count(graphLookupNs)); +} + } // namespace } // namespace mongo diff --git a/src/mongo/db/pipeline/document_source_lookup.cpp b/src/mongo/db/pipeline/document_source_lookup.cpp index ee4c9414f24..f29df62a1a0 100644 --- a/src/mongo/db/pipeline/document_source_lookup.cpp +++ b/src/mongo/db/pipeline/document_source_lookup.cpp @@ -89,7 +89,8 @@ void lookupPipeValidator(const Pipeline& pipeline) { // {from: {db: "config", coll: "cache.chunks.*"}, ...} or // {from: {db: "local", coll: "oplog.rs"}, ...} or // {from: {db: "local", coll: "tenantMigration.oplogView"}, ...} . -NamespaceString parseLookupFromAndResolveNamespace(const BSONElement& elem, StringData defaultDb) { +NamespaceString parseLookupFromAndResolveNamespace(const BSONElement& elem, + const DatabaseName& defaultDb) { // The object syntax only works for 'cache.chunks.*', 'local.oplog.rs', and // 'local.tenantMigration.oplogViewwhich' which are not user namespaces so object type is // omitted from the error message below. @@ -104,6 +105,7 @@ NamespaceString parseLookupFromAndResolveNamespace(const BSONElement& elem, Stri // Valdate the db and coll names. auto spec = NamespaceSpec::parse({elem.fieldNameStringData()}, elem.embeddedObject()); + // TODO SERVER-62491 Use system tenantId to construct nss if running in serverless. auto nss = NamespaceString(spec.getDb().value_or(""), spec.getColl().value_or("")); uassert( ErrorCodes::FailedToParse, @@ -309,9 +311,9 @@ std::unique_ptr<DocumentSourceLookUp::LiteParsed> DocumentSourceLookUp::LitePars NamespaceString fromNss; if (!fromElement) { validateLookupCollectionlessPipeline(pipelineElem); - fromNss = NamespaceString::makeCollectionlessAggregateNSS(nss.db()); + fromNss = NamespaceString::makeCollectionlessAggregateNSS(nss.dbName()); } else { - fromNss = parseLookupFromAndResolveNamespace(fromElement, nss.db()); + fromNss = parseLookupFromAndResolveNamespace(fromElement, nss.dbName()); } uassert(ErrorCodes::InvalidNamespace, str::stream() << "invalid $lookup namespace: " << fromNss.ns(), @@ -1025,9 +1027,11 @@ void DocumentSourceLookUp::serializeToArray( std::vector<Value>& array, boost::optional<ExplainOptions::Verbosity> explain) const { // Support alternative $lookup from config.cache.chunks* namespaces. + // + // Do not include the tenantId in serialized 'from' namespace. auto fromValue = (pExpCtx->ns.db() == _fromNs.db()) ? Value(_fromNs.coll()) - : Value(Document{{"db", _fromNs.db()}, {"coll", _fromNs.coll()}}); + : Value(Document{{"db", _fromNs.dbName().db()}, {"coll", _fromNs.coll()}}); MutableDocument output( Document{{getSourceName(), Document{{"from", fromValue}, {"as", _as.fullPath()}}}}); @@ -1208,7 +1212,7 @@ boost::intrusive_ptr<DocumentSource> DocumentSourceLookUp::createFromBson( } if (argName == kFromField) { - fromNs = parseLookupFromAndResolveNamespace(argument, pExpCtx->ns.db()); + fromNs = parseLookupFromAndResolveNamespace(argument, pExpCtx->ns.dbName()); continue; } @@ -1241,7 +1245,7 @@ boost::intrusive_ptr<DocumentSource> DocumentSourceLookUp::createFromBson( if (fromNs.ns().empty()) { validateLookupCollectionlessPipeline(pipeline); - fromNs = NamespaceString::makeCollectionlessAggregateNSS(pExpCtx->ns.db()); + fromNs = NamespaceString::makeCollectionlessAggregateNSS(pExpCtx->ns.dbName()); } uassert(ErrorCodes::FailedToParse, "must specify 'as' field for a $lookup", !as.empty()); diff --git a/src/mongo/db/pipeline/document_source_lookup_test.cpp b/src/mongo/db/pipeline/document_source_lookup_test.cpp index a565fb60c26..82a0b6bbd61 100644 --- a/src/mongo/db/pipeline/document_source_lookup_test.cpp +++ b/src/mongo/db/pipeline/document_source_lookup_test.cpp @@ -87,7 +87,7 @@ public: // confirms that variables defined in the ExpressionContext are captured by the $lookup stage. TEST_F(DocumentSourceLookUpTest, PreservesParentPipelineLetVariables) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -110,7 +110,7 @@ TEST_F(DocumentSourceLookUpTest, PreservesParentPipelineLetVariables) { TEST_F(DocumentSourceLookUpTest, AcceptsPipelineSyntax) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -128,7 +128,7 @@ TEST_F(DocumentSourceLookUpTest, AcceptsPipelineSyntax) { TEST_F(DocumentSourceLookUpTest, AcceptsPipelineWithLetSyntax) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -153,7 +153,7 @@ TEST_F(DocumentSourceLookUpTest, AcceptsPipelineWithLetSyntax) { TEST_F(DocumentSourceLookUpTest, LookupEmptyPipelineDoesntUseDiskAndIsOKInATransaction) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -175,7 +175,7 @@ TEST_F(DocumentSourceLookUpTest, LookupEmptyPipelineDoesntUseDiskAndIsOKInATrans TEST_F(DocumentSourceLookUpTest, LookupWithOutInPipelineNotAllowed) { auto ERROR_CODE_OUT_BANNED_IN_LOOKUP = 51047; auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); ASSERT_THROWS_CODE( @@ -208,19 +208,19 @@ TEST_F(DocumentSourceLookUpTest, LiteParsedDocumentSourceLookupContainsExpectedN << "as" << "lookup1")); - NamespaceString nss("test.test"); + NamespaceString nss(boost::none, "test.test"); std::vector<BSONObj> pipeline; auto liteParsedLookup = DocumentSourceLookUp::LiteParsed::parse(nss, stageSpec.firstElement()); auto namespaceSet = liteParsedLookup->getInvolvedNamespaces(); - ASSERT_EQ(1ul, namespaceSet.count(NamespaceString("test.namespace1"))); - ASSERT_EQ(1ul, namespaceSet.count(NamespaceString("test.namespace2"))); + ASSERT_EQ(1ul, namespaceSet.count(NamespaceString(boost::none, "test.namespace1"))); + ASSERT_EQ(1ul, namespaceSet.count(NamespaceString(boost::none, "test.namespace2"))); ASSERT_EQ(2ul, namespaceSet.size()); } TEST_F(DocumentSourceLookUpTest, RejectLookupWhenDepthLimitIsExceeded) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -241,7 +241,7 @@ TEST_F(DocumentSourceLookUpTest, RejectLookupWhenDepthLimitIsExceeded) { TEST_F(ReplDocumentSourceLookUpTest, RejectsPipelineWithChangeStreamStage) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -257,7 +257,7 @@ TEST_F(ReplDocumentSourceLookUpTest, RejectsPipelineWithChangeStreamStage) { TEST_F(ReplDocumentSourceLookUpTest, RejectsSubPipelineWithChangeStreamStage) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -275,7 +275,7 @@ TEST_F(ReplDocumentSourceLookUpTest, RejectsSubPipelineWithChangeStreamStage) { TEST_F(DocumentSourceLookUpTest, AcceptsLocalFieldForeignFieldAndPipelineSyntax) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -302,7 +302,7 @@ TEST_F(DocumentSourceLookUpTest, AcceptsLocalFieldForeignFieldAndPipelineSyntax) TEST_F(DocumentSourceLookUpTest, AcceptsLocalFieldForeignFieldAndPipelineWithLetSyntax) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -335,7 +335,7 @@ TEST_F(DocumentSourceLookUpTest, AcceptsLocalFieldForeignFieldAndPipelineWithLet TEST_F(DocumentSourceLookUpTest, RejectsLocalFieldForeignFieldWhenLetIsSpecified) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -358,7 +358,7 @@ TEST_F(DocumentSourceLookUpTest, RejectsLocalFieldForeignFieldWhenLetIsSpecified TEST_F(DocumentSourceLookUpTest, RejectsInvalidLetVariableName) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -410,7 +410,7 @@ TEST_F(DocumentSourceLookUpTest, RejectsInvalidLetVariableName) { TEST_F(DocumentSourceLookUpTest, ShouldBeAbleToReParseSerializedStage) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -468,7 +468,7 @@ TEST_F(DocumentSourceLookUpTest, ShouldBeAbleToReParseSerializedStage) { TEST_F(DocumentSourceLookUpTest, ShouldBeAbleToReParseSerializedStageWithFieldsAndPipeline) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -535,7 +535,7 @@ TEST_F(DocumentSourceLookUpTest, ShouldBeAbleToReParseSerializedStageWithFieldsA // be round tripped. TEST_F(DocumentSourceLookUpTest, LookupReParseSerializedStageWithFromDBAndColl) { auto expCtx = getExpCtx(); - NamespaceString fromNs("config", "cache.chunks.test.foo"); + NamespaceString fromNs(boost::none, "config", "cache.chunks.test.foo"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -573,7 +573,7 @@ TEST_F(DocumentSourceLookUpTest, LookupReParseSerializedStageWithFromDBAndColl) // can be round tripped. TEST_F(DocumentSourceLookUpTest, LookupWithLetReParseSerializedStageWithFromDBAndColl) { auto expCtx = getExpCtx(); - NamespaceString fromNs("config", "cache.chunks.test.foo"); + NamespaceString fromNs(boost::none, "config", "cache.chunks.test.foo"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -612,7 +612,7 @@ TEST_F(DocumentSourceLookUpTest, LookupWithLetReParseSerializedStageWithFromDBAn // Tests that $lookup with 'collation' can be round tripped. TEST_F(DocumentSourceLookUpTest, LookupReParseSerializedStageWithCollation) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -661,7 +661,7 @@ TEST_F(DocumentSourceLookUpTest, LookupReParseSerializedStageWithCollation) { // config.cache.chunks*. TEST_F(DocumentSourceLookUpTest, RejectsPipelineFromDBAndCollWithBadDBAndColl) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -678,7 +678,7 @@ TEST_F(DocumentSourceLookUpTest, RejectsPipelineFromDBAndCollWithBadDBAndColl) { // "cache.chunks.*" but "db" is not "config". TEST_F(DocumentSourceLookUpTest, RejectsPipelineFromDBAndCollWithBadColl) { auto expCtx = getExpCtx(); - NamespaceString fromNs("config", "coll"); + NamespaceString fromNs(boost::none, "config", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -695,7 +695,7 @@ TEST_F(DocumentSourceLookUpTest, RejectsPipelineFromDBAndCollWithBadColl) { // not "cache.chunks.*". TEST_F(DocumentSourceLookUpTest, RejectsPipelineFromDBAndCollWithBadDB) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "cache.chunks.test.foo"); + NamespaceString fromNs(boost::none, "test", "cache.chunks.test.foo"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -713,7 +713,7 @@ TEST_F(DocumentSourceLookUpTest, RejectsPipelineFromDBAndCollWithBadDB) { // syntax. TEST_F(DocumentSourceLookUpTest, FromDBAndCollDistributedPlanLogic) { auto expCtx = getExpCtx(); - NamespaceString fromNs("config", "cache.chunks.test.foo"); + NamespaceString fromNs(boost::none, "config", "cache.chunks.test.foo"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -738,7 +738,7 @@ TEST_F(DocumentSourceLookUpTest, FromDBAndCollDistributedPlanLogic) { // $lookup with from: <string> syntax. TEST_F(DocumentSourceLookUpTest, LookupDistributedPlanLogic) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -844,7 +844,7 @@ private: TEST_F(DocumentSourceLookUpTest, ShouldPropagatePauses) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "foreign"); + NamespaceString fromNs(boost::none, "test", "foreign"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -895,7 +895,7 @@ TEST_F(DocumentSourceLookUpTest, ShouldPropagatePauses) { TEST_F(DocumentSourceLookUpTest, ShouldPropagatePausesWhileUnwinding) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "foreign"); + NamespaceString fromNs(boost::none, "test", "foreign"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -950,7 +950,7 @@ TEST_F(DocumentSourceLookUpTest, ShouldPropagatePausesWhileUnwinding) { TEST_F(DocumentSourceLookUpTest, LookupReportsAsFieldIsModified) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "foreign"); + NamespaceString fromNs(boost::none, "test", "foreign"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -973,7 +973,7 @@ TEST_F(DocumentSourceLookUpTest, LookupReportsAsFieldIsModified) { TEST_F(DocumentSourceLookUpTest, LookupReportsFieldsModifiedByAbsorbedUnwind) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "foreign"); + NamespaceString fromNs(boost::none, "test", "foreign"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -1007,7 +1007,7 @@ BSONObj sequentialCacheStageObj(const StringData status = "kBuilding"_sd, TEST_F(DocumentSourceLookUpTest, ShouldCacheNonCorrelatedSubPipelinePrefix) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -1036,7 +1036,7 @@ TEST_F(DocumentSourceLookUpTest, ShouldCacheNonCorrelatedSubPipelinePrefix) { TEST_F(DocumentSourceLookUpTest, ShouldDiscoverVariablesReferencedInFacetPipelineAfterAnExhaustiveAllStage) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -1078,7 +1078,7 @@ TEST_F(DocumentSourceLookUpTest, TEST_F(DocumentSourceLookUpTest, ExprEmbeddedInMatchExpressionShouldBeOptimized) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -1118,7 +1118,7 @@ TEST_F(DocumentSourceLookUpTest, ExprEmbeddedInMatchExpressionShouldBeOptimized) TEST_F(DocumentSourceLookUpTest, ShouldIgnoreLocalVariablesShadowingLetVariablesWhenFindingNonCorrelatedPrefix) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -1154,7 +1154,7 @@ TEST_F(DocumentSourceLookUpTest, TEST_F(DocumentSourceLookUpTest, ShouldInsertCacheBeforeCorrelatedNestedLookup) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -1192,7 +1192,7 @@ TEST_F(DocumentSourceLookUpTest, ShouldInsertCacheBeforeCorrelatedNestedLookup) TEST_F(DocumentSourceLookUpTest, ShouldIgnoreNestedLookupLetVariablesShadowingOuterLookupLetVariablesWhenFindingPrefix) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -1226,7 +1226,7 @@ TEST_F(DocumentSourceLookUpTest, TEST_F(DocumentSourceLookUpTest, ShouldCacheEntirePipelineIfNonCorrelated) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -1259,7 +1259,7 @@ TEST_F(DocumentSourceLookUpTest, ShouldCacheEntirePipelineIfNonCorrelated) { TEST_F(DocumentSourceLookUpTest, ShouldReplaceNonCorrelatedPrefixWithCacheAfterFirstSubPipelineIteration) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -1333,7 +1333,7 @@ TEST_F(DocumentSourceLookUpTest, TEST_F(DocumentSourceLookUpTest, ShouldAbandonCacheIfMaxSizeIsExceededAfterFirstSubPipelineIteration) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -1399,7 +1399,7 @@ TEST_F(DocumentSourceLookUpTest, TEST_F(DocumentSourceLookUpTest, ShouldNotCacheIfCorrelatedStageIsAbsorbedIntoPlanExecutor) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -1426,5 +1426,56 @@ TEST_F(DocumentSourceLookUpTest, ShouldNotCacheIfCorrelatedStageIsAbsorbedIntoPl ASSERT_VALUE_EQ(Value(subPipeline->writeExplainOps(kExplain)), Value(BSONArray(expectedPipe))); } +using DocumentSourceLookUpServerlessTest = ServerlessAggregationContextFixture; + +TEST_F(DocumentSourceLookUpServerlessTest, + LiteParsedDocumentSourceLookupContainsExpectedNamespacesInServerless) { + auto expCtx = getExpCtx(); + + auto stageSpec = + BSON("$lookup" << BSON("from" + << "namespace1" + << "pipeline" + << BSON_ARRAY(BSON( + "$lookup" + << BSON("from" + << "namespace2" + << "as" + << "lookup2" + << "pipeline" + << BSON_ARRAY(BSON("$match" << BSON("x" << 1)))))) + << "as" + << "lookup1")); + + NamespaceString nss(expCtx->ns.dbName(), "testColl"); + std::vector<BSONObj> pipeline; + auto liteParsedLookup = DocumentSourceLookUp::LiteParsed::parse(nss, stageSpec.firstElement()); + auto namespaceSet = liteParsedLookup->getInvolvedNamespaces(); + + ASSERT_EQ(1ul, namespaceSet.count(NamespaceString(expCtx->ns.dbName(), "namespace1"))); + ASSERT_EQ(1ul, namespaceSet.count(NamespaceString(expCtx->ns.dbName(), "namespace2"))); + ASSERT_EQ(2ul, namespaceSet.size()); +} + +TEST_F(DocumentSourceLookUpServerlessTest, CreateFromBSONContainsExpectedNamespacesInServerless) { + auto expCtx = getExpCtx(); + ASSERT(expCtx->ns.tenantId()); + + NamespaceString fromNs(expCtx->ns.dbName(), "coll"); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); + + auto docSource = DocumentSourceLookUp::createFromBson( + BSON("$lookup" << BSON("from" + << "coll" + << "pipeline" << BSON_ARRAY(BSON("$match" << BSON("x" << 1))) << "as" + << "as")) + .firstElement(), + expCtx); + auto lookupStage = static_cast<DocumentSourceLookUp*>(docSource.get()); + ASSERT(lookupStage); + ASSERT_EQ(lookupStage->getFromNs(), NamespaceString(expCtx->ns.dbName(), "coll")); +} + } // namespace } // namespace mongo diff --git a/src/mongo/db/pipeline/document_source_merge.cpp b/src/mongo/db/pipeline/document_source_merge.cpp index a9ae914c2c0..ae413052ddf 100644 --- a/src/mongo/db/pipeline/document_source_merge.cpp +++ b/src/mongo/db/pipeline/document_source_merge.cpp @@ -257,7 +257,7 @@ BSONObj extractMergeOnFieldsFromDoc(const Document& doc, const std::set<FieldPat * explicitly specified, it will be defaulted to 'defaultDb'. */ DocumentSourceMergeSpec parseMergeSpecAndResolveTargetNamespace(const BSONElement& spec, - StringData defaultDb) { + const DatabaseName& defaultDb) { NamespaceString targetNss; DocumentSourceMergeSpec mergeSpec; @@ -278,7 +278,7 @@ DocumentSourceMergeSpec parseMergeSpecAndResolveTargetNamespace(const BSONElemen // target namespace collection is empty, we'll use the default database name as a target // database, and the provided namespace value as a collection name. targetNss = {defaultDb, targetNss.ns()}; - } else if (targetNss.db().empty()) { + } else if (targetNss.dbName().db().empty()) { // Use the default database name if it wasn't specified explicilty. targetNss = {defaultDb, targetNss.coll()}; } @@ -316,7 +316,7 @@ std::unique_ptr<DocumentSourceMerge::LiteParsed> DocumentSourceMerge::LiteParsed typeName(spec.type())), spec.type() == BSONType::String || spec.type() == BSONType::Object); - auto mergeSpec = parseMergeSpecAndResolveTargetNamespace(spec, nss.db()); + auto mergeSpec = parseMergeSpecAndResolveTargetNamespace(spec, nss.dbName()); auto targetNss = mergeSpec.getTargetNss(); uassert(ErrorCodes::InvalidNamespace, @@ -451,7 +451,7 @@ boost::intrusive_ptr<DocumentSource> DocumentSourceMerge::createFromBson( "{} only supports a string or object argument, not {}"_format(kStageName, spec.type()), spec.type() == BSONType::String || spec.type() == BSONType::Object); - auto mergeSpec = parseMergeSpecAndResolveTargetNamespace(spec, expCtx->ns.db()); + auto mergeSpec = parseMergeSpecAndResolveTargetNamespace(spec, expCtx->ns.dbName()); auto targetNss = mergeSpec.getTargetNss(); auto whenMatched = mergeSpec.getWhenMatched() ? mergeSpec.getWhenMatched()->mode : kDefaultWhenMatched; diff --git a/src/mongo/db/pipeline/document_source_merge_cursors_test.cpp b/src/mongo/db/pipeline/document_source_merge_cursors_test.cpp index 61b2aef0c97..f01b449025d 100644 --- a/src/mongo/db/pipeline/document_source_merge_cursors_test.cpp +++ b/src/mongo/db/pipeline/document_source_merge_cursors_test.cpp @@ -71,7 +71,7 @@ const std::vector<HostAndPort> kTestShardHosts = {HostAndPort("FakeShard1Host", HostAndPort("FakeShard2Host", 12345), HostAndPort("FakeShard3Host", 12345)}; -const NamespaceString kTestNss = NamespaceString("test.mergeCursors"_sd); +const NamespaceString kTestNss = NamespaceString(boost::none, "test.mergeCursors"_sd); const HostAndPort kTestHost = HostAndPort("localhost:27017"_sd); const CursorId kExhaustedCursorID = 0; diff --git a/src/mongo/db/pipeline/document_source_merge_spec.cpp b/src/mongo/db/pipeline/document_source_merge_spec.cpp index 41926a7cfd9..7f8356435bf 100644 --- a/src/mongo/db/pipeline/document_source_merge_spec.cpp +++ b/src/mongo/db/pipeline/document_source_merge_spec.cpp @@ -41,6 +41,7 @@ namespace mongo { using namespace fmt::literals; +// TODO SERVER-66708 Ensure the correct tenantId is passed when deserializing the merge target nss. NamespaceString mergeTargetNssParseFromBSON(const BSONElement& elem) { uassert(51178, "{} 'into' field must be either a string or an object, " @@ -67,7 +68,7 @@ NamespaceString mergeTargetNssParseFromBSON(const BSONElement& elem) { void mergeTargetNssSerializeToBSON(const NamespaceString& targetNss, StringData fieldName, BSONObjBuilder* bob) { - bob->append(fieldName, BSON("db" << targetNss.db() << "coll" << targetNss.coll())); + bob->append(fieldName, BSON("db" << targetNss.dbName().db() << "coll" << targetNss.coll())); } std::vector<std::string> mergeOnFieldsParseFromBSON(const BSONElement& elem) { diff --git a/src/mongo/db/pipeline/document_source_merge_test.cpp b/src/mongo/db/pipeline/document_source_merge_test.cpp index e7d87707900..8404baa69e5 100644 --- a/src/mongo/db/pipeline/document_source_merge_test.cpp +++ b/src/mongo/db/pipeline/document_source_merge_test.cpp @@ -35,6 +35,7 @@ #include "mongo/db/exec/document_value/document_value_test_util.h" #include "mongo/db/pipeline/aggregation_context_fixture.h" #include "mongo/db/pipeline/document_source_merge.h" +#include "mongo/db/pipeline/document_source_mock.h" #include "mongo/db/pipeline/process_interface/non_shardsvr_process_interface.h" namespace mongo { @@ -96,7 +97,7 @@ public: TEST_F(DocumentSourceMergeTest, CorrectlyParsesIfMergeSpecIsString) { const auto& defaultDb = getExpCtx()->ns.db(); - const auto& targetColl = "target_collection"; + const std::string targetColl = "target_collection"; auto spec = BSON("$merge" << targetColl); auto mergeStage = createMergeStage(spec); ASSERT(mergeStage); @@ -106,7 +107,7 @@ TEST_F(DocumentSourceMergeTest, CorrectlyParsesIfMergeSpecIsString) { TEST_F(DocumentSourceMergeTest, CorrectlyParsesIfIntoIsString) { const auto& defaultDb = getExpCtx()->ns.db(); - const auto& targetColl = "target_collection"; + const std::string targetColl = "target_collection"; auto spec = BSON("$merge" << BSON("into" << targetColl)); auto mergeStage = createMergeStage(spec); ASSERT(mergeStage); @@ -116,8 +117,8 @@ TEST_F(DocumentSourceMergeTest, CorrectlyParsesIfIntoIsString) { TEST_F(DocumentSourceMergeTest, CorrectlyParsesIfIntoIsObject) { const auto& defaultDb = getExpCtx()->ns.db(); - const auto& targetDb = "target_db"; - const auto& targetColl = "target_collection"; + const std::string targetDb = "target_db"; + const std::string targetColl = "target_collection"; auto spec = BSON("$merge" << BSON("into" << BSON("coll" << targetColl))); auto mergeStage = createMergeStage(spec); ASSERT(mergeStage); @@ -950,5 +951,58 @@ TEST_F(DocumentSourceMergeTest, FailsToParseIfOnFieldHaveDuplicates) { ASSERT_THROWS_CODE(createMergeStage(spec), AssertionException, 31465); } +using DocumentSourceMergeServerlessTest = ServerlessAggregationContextFixture; + +TEST_F(DocumentSourceMergeServerlessTest, + LiteParsedDocumentSourceLookupContainsExpectedNamespacesInServerless) { + const std::string targetColl = "target_collection"; + + auto tenantId = TenantId(OID::gen()); + NamespaceString nss(tenantId, "test", "testColl"); + std::vector<BSONObj> pipeline; + + auto stageSpec = BSON("$merge" << targetColl); + auto liteParsedLookup = DocumentSourceMerge::LiteParsed::parse(nss, stageSpec.firstElement()); + auto namespaceSet = liteParsedLookup->getInvolvedNamespaces(); + ASSERT_EQ(1, namespaceSet.size()); + ASSERT_EQ(1ul, namespaceSet.count(NamespaceString(tenantId, "test", "target_collection"))); + + // TODO SERVER-66708 Add a test case once IDL parsed objects have access to tenantId. +} + +TEST_F(DocumentSourceMergeServerlessTest, CreateFromBSONContainsExpectedNamespacesInServerless) { + auto expCtx = getExpCtx(); + ASSERT(expCtx->ns.tenantId()); + + const std::string targetColl = "target_collection"; + + // Pass collection name as a string. + auto spec = BSON("$merge" << targetColl); + auto mergeStage = DocumentSourceMerge::createFromBson(spec.firstElement(), expCtx); + auto mergeSource = static_cast<DocumentSourceMerge*>(mergeStage.get()); + ASSERT(mergeStage); + ASSERT(mergeSource->getOutputNs().tenantId()); + ASSERT_EQ(*mergeSource->getOutputNs().tenantId(), *expCtx->ns.tenantId()); + + // Assert the tenantId is not included in the serialized namespace. + auto serialized = mergeSource->serialize().getDocument(); + auto expectedDoc = Document{{"db", expCtx->ns.dbName().db()}, {"coll", targetColl}}; + ASSERT_DOCUMENT_EQ(serialized["$merge"][kIntoFieldName].getDocument(), expectedDoc); + + // Pass collection name as an object. + spec = BSON("$merge" << BSON("into" << BSON("coll" << targetColl))); + mergeStage = DocumentSourceMerge::createFromBson(spec.firstElement(), expCtx); + mergeSource = static_cast<DocumentSourceMerge*>(mergeStage.get()); + ASSERT(mergeSource); + ASSERT(mergeSource->getOutputNs().tenantId()); + ASSERT_EQ(*mergeSource->getOutputNs().tenantId(), *expCtx->ns.tenantId()); + + // Assert the tenantId is not included in the serialized namespace. + serialized = mergeSource->serialize().getDocument(); + ASSERT_DOCUMENT_EQ(serialized["$merge"][kIntoFieldName].getDocument(), expectedDoc); + + // TODO SERVER-66708 Add a test case once IDL parsed objects have access to tenantId. +} + } // namespace } // namespace mongo diff --git a/src/mongo/db/pipeline/document_source_out.cpp b/src/mongo/db/pipeline/document_source_out.cpp index 43e55d5da91..cafce106b28 100644 --- a/src/mongo/db/pipeline/document_source_out.cpp +++ b/src/mongo/db/pipeline/document_source_out.cpp @@ -76,7 +76,7 @@ DocumentSourceOut::~DocumentSourceOut() { } NamespaceString DocumentSourceOut::parseNsFromElem(const BSONElement& spec, - const StringData& defaultDB) { + const DatabaseName& defaultDB) { if (spec.type() == BSONType::String) { return NamespaceString(defaultDB, spec.valueStringData()); } else if (spec.type() == BSONType::Object) { @@ -85,7 +85,7 @@ NamespaceString DocumentSourceOut::parseNsFromElem(const BSONElement& spec, str::stream() << "If an object is passed to " << kStageName << " it must have exactly 2 fields: 'db' and 'coll'", nsObj.nFields() == 2 && nsObj.hasField("coll") && nsObj.hasField("db")); - return NamespaceString(nsObj["db"].String(), nsObj["coll"].String()); + return NamespaceString(defaultDB.tenantId(), nsObj["db"].String(), nsObj["coll"].String()); } else { uassert(16990, "{} only supports a string or object argument, but found {}"_format( @@ -98,7 +98,7 @@ NamespaceString DocumentSourceOut::parseNsFromElem(const BSONElement& spec, std::unique_ptr<DocumentSourceOut::LiteParsed> DocumentSourceOut::LiteParsed::parse( const NamespaceString& nss, const BSONElement& spec) { - NamespaceString targetNss = parseNsFromElem(spec, nss.db()); + NamespaceString targetNss = parseNsFromElem(spec, nss.dbName()); uassert(ErrorCodes::InvalidNamespace, "Invalid {} target namespace, {}"_format(kStageName, targetNss.ns()), targetNss.isValid()); @@ -113,7 +113,8 @@ void DocumentSourceOut::initialize() { // to be the target collection once we are done. // Note that this temporary collection name is used by MongoMirror and thus should not be // changed without consultation. - _tempNs = NamespaceString(str::stream() << outputNs.db() << ".tmp.agg_out." << UUID::gen()); + _tempNs = NamespaceString(str::stream() + << outputNs.dbName().toString() << ".tmp.agg_out." << UUID::gen()); // Save the original collection options and index specs so we can check they didn't change // during computation. @@ -138,7 +139,7 @@ void DocumentSourceOut::initialize() { cmd.appendElementsUnique(_originalOutOptions); pExpCtx->mongoProcessInterface->createCollection( - pExpCtx->opCtx, _tempNs.db().toString(), cmd.done()); + pExpCtx->opCtx, _tempNs.dbName().toString(), cmd.done()); } CurOpFailpointHelpers::waitWhileFailPointEnabled( @@ -203,12 +204,14 @@ boost::intrusive_ptr<DocumentSource> DocumentSourceOut::create( boost::intrusive_ptr<DocumentSource> DocumentSourceOut::createFromBson( BSONElement elem, const boost::intrusive_ptr<ExpressionContext>& expCtx) { - auto targetNS = parseNsFromElem(elem, expCtx->ns.db()); + auto targetNS = parseNsFromElem(elem, expCtx->ns.dbName()); return create(targetNS, expCtx); } Value DocumentSourceOut::serialize(boost::optional<ExplainOptions::Verbosity> explain) const { - return Value(DOC(kStageName << DOC("db" << _outputNs.db() << "coll" << _outputNs.coll()))); + // Do not include the tenantId in the serialized 'outputNs'. + return Value( + DOC(kStageName << DOC("db" << _outputNs.dbName().db() << "coll" << _outputNs.coll()))); } void DocumentSourceOut::waitWhileFailPointEnabled() { diff --git a/src/mongo/db/pipeline/document_source_out.h b/src/mongo/db/pipeline/document_source_out.h index 64dda167eb3..88883cb6231 100644 --- a/src/mongo/db/pipeline/document_source_out.h +++ b/src/mongo/db/pipeline/document_source_out.h @@ -115,7 +115,7 @@ private: const boost::intrusive_ptr<ExpressionContext>& expCtx) : DocumentSourceWriter(kStageName.rawData(), std::move(outputNs), expCtx) {} - static NamespaceString parseNsFromElem(const BSONElement& spec, const StringData& defaultDB); + static NamespaceString parseNsFromElem(const BSONElement& spec, const DatabaseName& defaultDB); void initialize() override; diff --git a/src/mongo/db/pipeline/document_source_out_test.cpp b/src/mongo/db/pipeline/document_source_out_test.cpp index aef9be321f0..abeb2ebca8d 100644 --- a/src/mongo/db/pipeline/document_source_out_test.cpp +++ b/src/mongo/db/pipeline/document_source_out_test.cpp @@ -114,5 +114,66 @@ TEST_F(DocumentSourceOutTest, SerializeToString) { ASSERT_EQ(reSerialized["$out"]["coll"].getStringData(), "some_collection"); } +using DocumentSourceOutServerlessTest = ServerlessAggregationContextFixture; + +TEST_F(DocumentSourceOutServerlessTest, + LiteParsedDocumentSourceLookupContainsExpectedNamespacesInServerless) { + auto tenantId = TenantId(OID::gen()); + NamespaceString nss(tenantId, "test", "testColl"); + std::vector<BSONObj> pipeline; + + auto stageSpec = BSON("$out" + << "some_collection"); + auto liteParsedLookup = DocumentSourceOut::LiteParsed::parse(nss, stageSpec.firstElement()); + auto namespaceSet = liteParsedLookup->getInvolvedNamespaces(); + ASSERT_EQ(1, namespaceSet.size()); + ASSERT_EQ(1ul, namespaceSet.count(NamespaceString(tenantId, "test", "some_collection"))); + + // The tenantId for the outputNs should be the same as that on the expCtx despite outputting + // into different dbs. + stageSpec = BSON("$out" << BSON("db" + << "target_db" + << "coll" + << "some_collection")); + liteParsedLookup = DocumentSourceOut::LiteParsed::parse(nss, stageSpec.firstElement()); + namespaceSet = liteParsedLookup->getInvolvedNamespaces(); + ASSERT_EQ(1, namespaceSet.size()); + ASSERT_EQ(1ul, namespaceSet.count(NamespaceString(tenantId, "target_db", "some_collection"))); +} + +TEST_F(DocumentSourceOutServerlessTest, CreateFromBSONContainsExpectedNamespacesInServerless) { + auto expCtx = getExpCtx(); + ASSERT(expCtx->ns.tenantId()); + auto defaultDb = expCtx->ns.dbName(); + + const std::string targetColl = "target_collection"; + auto spec = BSON("$out" << targetColl); + auto outStage = DocumentSourceOut::createFromBson(spec.firstElement(), expCtx); + auto outSource = static_cast<DocumentSourceOut*>(outStage.get()); + ASSERT(outSource); + ASSERT_EQ(outSource->getOutputNs(), NamespaceString(defaultDb, targetColl)); + + // Assert the tenantId is not included in the serialized namespace. + auto serialized = outSource->serialize().getDocument(); + auto expectedDoc = Document{{"db", expCtx->ns.dbName().db()}, {"coll", targetColl}}; + ASSERT_DOCUMENT_EQ(serialized["$out"].getDocument(), expectedDoc); + + // The tenantId for the outputNs should be the same as that on the expCtx despite outputting + // into different dbs. + const std::string targetDb = "target_db"; + spec = BSON("$out" << BSON("db" << targetDb << "coll" << targetColl)); + outStage = DocumentSourceOut::createFromBson(spec.firstElement(), expCtx); + outSource = static_cast<DocumentSourceOut*>(outStage.get()); + ASSERT(outSource); + ASSERT(outSource->getOutputNs().tenantId()); + ASSERT_EQ(*outSource->getOutputNs().tenantId(), *expCtx->ns.tenantId()); + ASSERT_EQ(outSource->getOutputNs().dbName().db(), targetDb); + + // Assert the tenantId is not included in the serialized namespace. + serialized = outSource->serialize().getDocument(); + expectedDoc = Document{{"db", targetDb}, {"coll", targetColl}}; + ASSERT_DOCUMENT_EQ(serialized["$out"].getDocument(), expectedDoc); +} + } // namespace } // namespace mongo diff --git a/src/mongo/db/pipeline/document_source_union_with.cpp b/src/mongo/db/pipeline/document_source_union_with.cpp index 435cd0b1c6e..e232fe41d75 100644 --- a/src/mongo/db/pipeline/document_source_union_with.cpp +++ b/src/mongo/db/pipeline/document_source_union_with.cpp @@ -127,16 +127,16 @@ std::unique_ptr<DocumentSourceUnionWith::LiteParsed> DocumentSourceUnionWith::Li NamespaceString unionNss; boost::optional<LiteParsedPipeline> liteParsedPipeline; if (spec.type() == BSONType::String) { - unionNss = NamespaceString(nss.db(), spec.valueStringData()); + unionNss = NamespaceString(nss.dbName(), spec.valueStringData()); } else { auto unionWithSpec = UnionWithSpec::parse(IDLParserErrorContext(kStageName), spec.embeddedObject()); if (unionWithSpec.getColl()) { - unionNss = NamespaceString(nss.db(), *unionWithSpec.getColl()); + unionNss = NamespaceString(nss.dbName(), *unionWithSpec.getColl()); } else { // If no collection specified, it must have $documents as first field in pipeline. validateUnionWithCollectionlessPipeline(unionWithSpec.getPipeline()); - unionNss = NamespaceString::makeCollectionlessAggregateNSS(nss.db()); + unionNss = NamespaceString::makeCollectionlessAggregateNSS(nss.dbName()); } // Recursively lite parse the nested pipeline, if one exists. @@ -185,16 +185,16 @@ boost::intrusive_ptr<DocumentSource> DocumentSourceUnionWith::createFromBson( NamespaceString unionNss; std::vector<BSONObj> pipeline; if (elem.type() == BSONType::String) { - unionNss = NamespaceString(expCtx->ns.db().toString(), elem.valueStringData()); + unionNss = NamespaceString(expCtx->ns.dbName(), elem.valueStringData()); } else { auto unionWithSpec = UnionWithSpec::parse(IDLParserErrorContext(kStageName), elem.embeddedObject()); if (unionWithSpec.getColl()) { - unionNss = NamespaceString(expCtx->ns.db().toString(), *unionWithSpec.getColl()); + unionNss = NamespaceString(expCtx->ns.dbName(), *unionWithSpec.getColl()); } else { // if no collection specified, it must have $documents as first field in pipeline validateUnionWithCollectionlessPipeline(unionWithSpec.getPipeline()); - unionNss = NamespaceString::makeCollectionlessAggregateNSS(expCtx->ns.db()); + unionNss = NamespaceString::makeCollectionlessAggregateNSS(expCtx->ns.dbName()); } pipeline = unionWithSpec.getPipeline().value_or(std::vector<BSONObj>{}); } diff --git a/src/mongo/db/pipeline/document_source_union_with_test.cpp b/src/mongo/db/pipeline/document_source_union_with_test.cpp index ec2749edd0f..04f440fa91a 100644 --- a/src/mongo/db/pipeline/document_source_union_with_test.cpp +++ b/src/mongo/db/pipeline/document_source_union_with_test.cpp @@ -175,7 +175,7 @@ TEST_F(DocumentSourceUnionWithTest, UnionsWithNonEmptySubPipelines) { TEST_F(DocumentSourceUnionWithTest, SerializeAndParseWithPipeline) { auto expCtx = getExpCtx(); - NamespaceString nsToUnionWith(expCtx->ns.db(), "coll"); + NamespaceString nsToUnionWith(expCtx->ns.dbName(), "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {nsToUnionWith.coll().toString(), {nsToUnionWith, std::vector<BSONObj>()}}}); auto bson = @@ -195,7 +195,7 @@ TEST_F(DocumentSourceUnionWithTest, SerializeAndParseWithPipeline) { TEST_F(DocumentSourceUnionWithTest, SerializeAndParseWithoutPipeline) { auto expCtx = getExpCtx(); - NamespaceString nsToUnionWith(expCtx->ns.db(), "coll"); + NamespaceString nsToUnionWith(expCtx->ns.dbName(), "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {nsToUnionWith.coll().toString(), {nsToUnionWith, std::vector<BSONObj>()}}}); auto bson = BSON("$unionWith" << nsToUnionWith.coll()); @@ -214,7 +214,7 @@ TEST_F(DocumentSourceUnionWithTest, SerializeAndParseWithoutPipeline) { TEST_F(DocumentSourceUnionWithTest, SerializeAndParseWithoutPipelineExtraSubobject) { auto expCtx = getExpCtx(); - NamespaceString nsToUnionWith(expCtx->ns.db(), "coll"); + NamespaceString nsToUnionWith(expCtx->ns.dbName(), "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {nsToUnionWith.coll().toString(), {nsToUnionWith, std::vector<BSONObj>()}}}); auto bson = BSON("$unionWith" << BSON("coll" << nsToUnionWith.coll())); @@ -233,7 +233,7 @@ TEST_F(DocumentSourceUnionWithTest, SerializeAndParseWithoutPipelineExtraSubobje TEST_F(DocumentSourceUnionWithTest, ParseErrors) { auto expCtx = getExpCtx(); - NamespaceString nsToUnionWith(expCtx->ns.db(), "coll"); + NamespaceString nsToUnionWith(expCtx->ns.dbName(), "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {nsToUnionWith.coll().toString(), {nsToUnionWith, std::vector<BSONObj>()}}}); ASSERT_THROWS_CODE( @@ -395,7 +395,7 @@ TEST_F(DocumentSourceUnionWithTest, DependencyAnalysisReportsReferencedFieldsBef TEST_F(DocumentSourceUnionWithTest, RespectsViewDefinition) { auto expCtx = getExpCtx(); - NamespaceString nsToUnionWith(expCtx->ns.db(), "coll"); + NamespaceString nsToUnionWith(expCtx->ns.dbName(), "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {nsToUnionWith.coll().toString(), {nsToUnionWith, std::vector<BSONObj>{fromjson("{$match: {_id: {$mod: [2, 0]}}}")}}}}); @@ -427,8 +427,8 @@ TEST_F(DocumentSourceUnionWithTest, RespectsViewDefinition) { TEST_F(DocumentSourceUnionWithTest, ConcatenatesViewDefinitionToPipeline) { auto expCtx = getExpCtx(); - NamespaceString viewNsToUnionWith(expCtx->ns.db(), "view"); - NamespaceString nsToUnionWith(expCtx->ns.db(), "coll"); + NamespaceString viewNsToUnionWith(expCtx->ns.dbName(), "view"); + NamespaceString nsToUnionWith(expCtx->ns.dbName(), "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {viewNsToUnionWith.coll().toString(), {nsToUnionWith, std::vector<BSONObj>{fromjson("{$match: {_id: {$mod: [2, 0]}}}")}}}}); @@ -465,7 +465,7 @@ TEST_F(DocumentSourceUnionWithTest, ConcatenatesViewDefinitionToPipeline) { TEST_F(DocumentSourceUnionWithTest, RejectUnionWhenDepthLimitIsExceeded) { auto expCtx = getExpCtx(); - NamespaceString fromNs("test", "coll"); + NamespaceString fromNs(boost::none, "test", "coll"); expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); @@ -591,5 +591,60 @@ TEST_F(DocumentSourceUnionWithTest, StricterConstraintsFromSubSubPipelineAreInhe StageConstraints::UnionRequirement::kAllowed); ASSERT_TRUE(unionStage.constraints(Pipeline::SplitState::kUnsplit) == expectedConstraints); } + +using DocumentSourceUnionWithServerlessTest = ServerlessAggregationContextFixture; + +TEST_F(DocumentSourceUnionWithServerlessTest, + LiteParsedDocumentSourceLookupContainsExpectedNamespacesInServerless) { + auto tenantId = TenantId(OID::gen()); + NamespaceString nss(tenantId, "test", "testColl"); + std::vector<BSONObj> pipeline; + + auto stageSpec = BSON("$unionWith" + << "some_coll"); + auto liteParsedLookup = + DocumentSourceUnionWith::LiteParsed::parse(nss, stageSpec.firstElement()); + auto namespaceSet = liteParsedLookup->getInvolvedNamespaces(); + ASSERT_EQ(1, namespaceSet.size()); + ASSERT_EQ(1ul, namespaceSet.count(NamespaceString(tenantId, "test", "some_coll"))); + + stageSpec = BSON("$unionWith" << BSON("coll" + << "some_coll" + << "pipeline" << BSONArray())); + liteParsedLookup = DocumentSourceUnionWith::LiteParsed::parse(nss, stageSpec.firstElement()); + namespaceSet = liteParsedLookup->getInvolvedNamespaces(); + ASSERT_EQ(1, namespaceSet.size()); + ASSERT_EQ(1ul, namespaceSet.count(NamespaceString(tenantId, "test", "some_coll"))); +} + +TEST_F(DocumentSourceUnionWithServerlessTest, + CreateFromBSONContainsExpectedNamespacesInServerless) { + auto expCtx = getExpCtx(); + ASSERT(expCtx->ns.tenantId()); + + NamespaceString unionWithNs(expCtx->ns.tenantId(), "test", "some_coll"); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {unionWithNs.coll().toString(), {unionWithNs, std::vector<BSONObj>()}}}); + + auto spec = BSON("$unionWith" + << "some_coll"); + auto unionWithStage = DocumentSourceUnionWith::createFromBson(spec.firstElement(), expCtx); + auto pipeline = + Pipeline::create({DocumentSourceMock::createForTest(expCtx), unionWithStage}, expCtx); + auto involvedNssSet = pipeline->getInvolvedCollections(); + ASSERT_EQ(involvedNssSet.size(), 1UL); + ASSERT_EQ(1ul, involvedNssSet.count(unionWithNs)); + + spec = BSON("$unionWith" << BSON("coll" + << "some_coll" + << "pipeline" << BSONArray())); + unionWithStage = DocumentSourceUnionWith::createFromBson(spec.firstElement(), expCtx); + pipeline = + Pipeline::create({DocumentSourceMock::createForTest(expCtx), unionWithStage}, expCtx); + involvedNssSet = pipeline->getInvolvedCollections(); + ASSERT_EQ(involvedNssSet.size(), 1UL); + ASSERT_EQ(1ul, involvedNssSet.count(unionWithNs)); +} + } // namespace } // namespace mongo |