diff options
author | Bernard Gorman <bernard.gorman@gmail.com> | 2017-06-17 01:19:22 +0100 |
---|---|---|
committer | Bernard Gorman <bernard.gorman@gmail.com> | 2017-06-17 16:12:32 +0100 |
commit | a5f0a84c79b6ce41fef33da920c62be0ecc8f07b (patch) | |
tree | 345557918938fe5de249aee2f8ad30684b80f65a /src | |
parent | 923ad3ba8160f2cd614e1258ef19294bd502af78 (diff) | |
download | mongo-a5f0a84c79b6ce41fef33da920c62be0ecc8f07b.tar.gz |
SERVER-19318 Do not validate $facet subpipelines against top-level pipeline namespace
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/db/pipeline/document_source_facet.cpp | 16 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_facet_test.cpp | 68 | ||||
-rw-r--r-- | src/mongo/db/pipeline/pipeline.cpp | 69 | ||||
-rw-r--r-- | src/mongo/db/pipeline/pipeline.h | 60 | ||||
-rw-r--r-- | src/mongo/db/pipeline/pipeline_test.cpp | 9 |
5 files changed, 160 insertions, 62 deletions
diff --git a/src/mongo/db/pipeline/document_source_facet.cpp b/src/mongo/db/pipeline/document_source_facet.cpp index c2248189a3f..222d78e69c3 100644 --- a/src/mongo/db/pipeline/document_source_facet.cpp +++ b/src/mongo/db/pipeline/document_source_facet.cpp @@ -270,21 +270,7 @@ intrusive_ptr<DocumentSource> DocumentSourceFacet::createFromBson( for (auto&& rawFacet : extractRawPipelines(elem)) { const auto facetName = rawFacet.first; - auto pipeline = uassertStatusOK(Pipeline::parse(rawFacet.second, expCtx)); - - uassert(40172, - str::stream() << "sub-pipeline in $facet stage cannot be empty: " << facetName, - !pipeline->getSources().empty()); - - // Disallow any stages that need to be the first stage in the pipeline. - for (auto&& stage : pipeline->getSources()) { - if (stage->isInitialSource()) { - uasserted(40173, - str::stream() << stage->getSourceName() - << " is not allowed to be used within a $facet stage: " - << elem.toString()); - } - } + auto pipeline = uassertStatusOK(Pipeline::parseFacetPipeline(rawFacet.second, expCtx)); facetPipelines.emplace_back(facetName, std::move(pipeline)); } diff --git a/src/mongo/db/pipeline/document_source_facet_test.cpp b/src/mongo/db/pipeline/document_source_facet_test.cpp index da1f035374f..a41b3a2b1ce 100644 --- a/src/mongo/db/pipeline/document_source_facet_test.cpp +++ b/src/mongo/db/pipeline/document_source_facet_test.cpp @@ -121,6 +121,15 @@ TEST_F(DocumentSourceFacetTest, ShouldRejectFacetsWithStagesThatMustBeTheFirstSt ASSERT_THROWS(DocumentSourceFacet::createFromBson(spec.firstElement(), ctx), UserException); } +TEST_F(DocumentSourceFacetTest, ShouldSucceedWhenNamespaceIsCollectionless) { + auto ctx = getExpCtx(); + auto spec = fromjson("{$facet: {a: [{$match: {}}]}}"); + + ctx->ns = NamespaceString::makeCollectionlessAggregateNSS("unittests"); + + ASSERT_TRUE(DocumentSourceFacet::createFromBson(spec.firstElement(), ctx).get()); +} + TEST_F(DocumentSourceFacetTest, ShouldRejectFacetsContainingAnOutStage) { auto ctx = getExpCtx(); auto spec = BSON("$facet" << BSON("a" << BSON_ARRAY(BSON("$out" @@ -192,7 +201,7 @@ TEST_F(DocumentSourceFacetTest, SingleFacetShouldReceiveAllDocuments) { auto dummy = DocumentSourcePassthrough::create(); - auto statusWithPipeline = Pipeline::create({dummy}, ctx); + auto statusWithPipeline = Pipeline::createFacetPipeline({dummy}, ctx); ASSERT_OK(statusWithPipeline.getStatus()); auto pipeline = std::move(statusWithPipeline.getValue()); @@ -220,10 +229,10 @@ TEST_F(DocumentSourceFacetTest, MultipleFacetsShouldSeeTheSameDocuments) { auto mock = DocumentSourceMock::create(inputs); auto firstDummy = DocumentSourcePassthrough::create(); - auto firstPipeline = uassertStatusOK(Pipeline::create({firstDummy}, ctx)); + auto firstPipeline = uassertStatusOK(Pipeline::createFacetPipeline({firstDummy}, ctx)); auto secondDummy = DocumentSourcePassthrough::create(); - auto secondPipeline = uassertStatusOK(Pipeline::create({secondDummy}, ctx)); + auto secondPipeline = uassertStatusOK(Pipeline::createFacetPipeline({secondDummy}, ctx)); std::vector<DocumentSourceFacet::FacetPipeline> facets; facets.emplace_back("first", std::move(firstPipeline)); @@ -259,10 +268,10 @@ TEST_F(DocumentSourceFacetTest, auto mock = DocumentSourceMock::create(inputs); auto passthrough = DocumentSourcePassthrough::create(); - auto passthroughPipe = uassertStatusOK(Pipeline::create({passthrough}, ctx)); + auto passthroughPipe = uassertStatusOK(Pipeline::createFacetPipeline({passthrough}, ctx)); auto limit = DocumentSourceLimit::create(ctx, 1); - auto limitedPipe = uassertStatusOK(Pipeline::create({limit}, ctx)); + auto limitedPipe = uassertStatusOK(Pipeline::createFacetPipeline({limit}, ctx)); std::vector<DocumentSourceFacet::FacetPipeline> facets; facets.emplace_back("all", std::move(passthroughPipe)); @@ -298,7 +307,7 @@ TEST_F(DocumentSourceFacetTest, ShouldBeAbleToEvaluateMultipleStagesWithinOneSub auto firstDummy = DocumentSourcePassthrough::create(); auto secondDummy = DocumentSourcePassthrough::create(); - auto pipeline = uassertStatusOK(Pipeline::create({firstDummy, secondDummy}, ctx)); + auto pipeline = uassertStatusOK(Pipeline::createFacetPipeline({firstDummy, secondDummy}, ctx)); std::vector<DocumentSourceFacet::FacetPipeline> facets; facets.emplace_back("subPipe", std::move(pipeline)); @@ -317,9 +326,9 @@ TEST_F(DocumentSourceFacetTest, ShouldPropagateDisposeThroughToSource) { auto mockSource = DocumentSourceMock::create(); auto firstDummy = DocumentSourcePassthrough::create(); - auto firstPipe = uassertStatusOK(Pipeline::create({firstDummy}, ctx)); + auto firstPipe = uassertStatusOK(Pipeline::createFacetPipeline({firstDummy}, ctx)); auto secondDummy = DocumentSourcePassthrough::create(); - auto secondPipe = uassertStatusOK(Pipeline::create({secondDummy}, ctx)); + auto secondPipe = uassertStatusOK(Pipeline::createFacetPipeline({secondDummy}, ctx)); std::vector<DocumentSourceFacet::FacetPipeline> facets; facets.emplace_back("firstPipe", std::move(firstPipe)); @@ -340,7 +349,7 @@ DEATH_TEST_F(DocumentSourceFacetTest, auto mock = DocumentSourceMock::create(DocumentSource::GetNextResult::makePauseExecution()); auto firstDummy = DocumentSourcePassthrough::create(); - auto pipeline = uassertStatusOK(Pipeline::create({firstDummy}, ctx)); + auto pipeline = uassertStatusOK(Pipeline::createFacetPipeline({firstDummy}, ctx)); std::vector<DocumentSourceFacet::FacetPipeline> facets; facets.emplace_back("subPipe", std::move(pipeline)); @@ -364,10 +373,10 @@ TEST_F(DocumentSourceFacetTest, ShouldBeAbleToReParseSerializedStage) { // skippedTwo: [{$skip: 2}] // }} auto firstSkip = DocumentSourceSkip::create(ctx, 1); - auto firstPipeline = uassertStatusOK(Pipeline::create({firstSkip}, ctx)); + auto firstPipeline = uassertStatusOK(Pipeline::createFacetPipeline({firstSkip}, ctx)); auto secondSkip = DocumentSourceSkip::create(ctx, 2); - auto secondPipeline = uassertStatusOK(Pipeline::create({secondSkip}, ctx)); + auto secondPipeline = uassertStatusOK(Pipeline::createFacetPipeline({secondSkip}, ctx)); std::vector<DocumentSourceFacet::FacetPipeline> facets; facets.emplace_back("skippedOne", std::move(firstPipeline)); @@ -407,7 +416,7 @@ TEST_F(DocumentSourceFacetTest, ShouldOptimizeInnerPipelines) { auto ctx = getExpCtx(); auto dummy = DocumentSourcePassthrough::create(); - auto pipeline = unittest::assertGet(Pipeline::create({dummy}, ctx)); + auto pipeline = unittest::assertGet(Pipeline::createFacetPipeline({dummy}, ctx)); std::vector<DocumentSourceFacet::FacetPipeline> facets; facets.emplace_back("subPipe", std::move(pipeline)); @@ -422,10 +431,10 @@ TEST_F(DocumentSourceFacetTest, ShouldPropogateDetachingAndReattachingOfOpCtx) { auto ctx = getExpCtx(); auto firstDummy = DocumentSourcePassthrough::create(); - auto firstPipeline = unittest::assertGet(Pipeline::create({firstDummy}, ctx)); + auto firstPipeline = unittest::assertGet(Pipeline::createFacetPipeline({firstDummy}, ctx)); auto secondDummy = DocumentSourcePassthrough::create(); - auto secondPipeline = unittest::assertGet(Pipeline::create({secondDummy}, ctx)); + auto secondPipeline = unittest::assertGet(Pipeline::createFacetPipeline({secondDummy}, ctx)); std::vector<DocumentSourceFacet::FacetPipeline> facets; facets.emplace_back("one", std::move(firstPipeline)); @@ -479,7 +488,7 @@ TEST_F(DocumentSourceFacetTest, ShouldUnionDependenciesOfInnerPipelines) { auto ctx = getExpCtx(); auto needsA = DocumentSourceNeedsA::create(); - auto firstPipeline = unittest::assertGet(Pipeline::create({needsA}, ctx)); + auto firstPipeline = unittest::assertGet(Pipeline::createFacetPipeline({needsA}, ctx)); auto firstPipelineDeps = firstPipeline->getDependencies(DepsTracker::MetadataAvailable::kNoMetadata); @@ -488,7 +497,7 @@ TEST_F(DocumentSourceFacetTest, ShouldUnionDependenciesOfInnerPipelines) { ASSERT_EQ(firstPipelineDeps.fields.count("a"), 1UL); auto needsB = DocumentSourceNeedsB::create(); - auto secondPipeline = unittest::assertGet(Pipeline::create({needsB}, ctx)); + auto secondPipeline = unittest::assertGet(Pipeline::createFacetPipeline({needsB}, ctx)); auto secondPipelineDeps = secondPipeline->getDependencies(DepsTracker::MetadataAvailable::kNoMetadata); @@ -528,10 +537,11 @@ TEST_F(DocumentSourceFacetTest, ShouldRequireWholeDocumentIfAnyPipelineRequiresW auto ctx = getExpCtx(); auto needsA = DocumentSourceNeedsA::create(); - auto firstPipeline = unittest::assertGet(Pipeline::create({needsA}, ctx)); + auto firstPipeline = unittest::assertGet(Pipeline::createFacetPipeline({needsA}, ctx)); auto needsWholeDocument = DocumentSourceNeedsWholeDocument::create(); - auto secondPipeline = unittest::assertGet(Pipeline::create({needsWholeDocument}, ctx)); + auto secondPipeline = + unittest::assertGet(Pipeline::createFacetPipeline({needsWholeDocument}, ctx)); std::vector<DocumentSourceFacet::FacetPipeline> facets; facets.emplace_back("needsA", std::move(firstPipeline)); @@ -562,13 +572,14 @@ TEST_F(DocumentSourceFacetTest, ShouldRequireTextScoreIfAnyPipelineRequiresTextS auto ctx = getExpCtx(); auto needsA = DocumentSourceNeedsA::create(); - auto firstPipeline = unittest::assertGet(Pipeline::create({needsA}, ctx)); + auto firstPipeline = unittest::assertGet(Pipeline::createFacetPipeline({needsA}, ctx)); auto needsWholeDocument = DocumentSourceNeedsWholeDocument::create(); - auto secondPipeline = unittest::assertGet(Pipeline::create({needsWholeDocument}, ctx)); + auto secondPipeline = + unittest::assertGet(Pipeline::createFacetPipeline({needsWholeDocument}, ctx)); auto needsTextScore = DocumentSourceNeedsOnlyTextScore::create(); - auto thirdPipeline = unittest::assertGet(Pipeline::create({needsTextScore}, ctx)); + auto thirdPipeline = unittest::assertGet(Pipeline::createFacetPipeline({needsTextScore}, ctx)); std::vector<DocumentSourceFacet::FacetPipeline> facets; facets.emplace_back("needsA", std::move(firstPipeline)); @@ -586,10 +597,10 @@ TEST_F(DocumentSourceFacetTest, ShouldThrowIfAnyPipelineRequiresTextScoreButItIs auto ctx = getExpCtx(); auto needsA = DocumentSourceNeedsA::create(); - auto firstPipeline = unittest::assertGet(Pipeline::create({needsA}, ctx)); + auto firstPipeline = unittest::assertGet(Pipeline::createFacetPipeline({needsA}, ctx)); auto needsTextScore = DocumentSourceNeedsOnlyTextScore::create(); - auto secondPipeline = unittest::assertGet(Pipeline::create({needsTextScore}, ctx)); + auto secondPipeline = unittest::assertGet(Pipeline::createFacetPipeline({needsTextScore}, ctx)); std::vector<DocumentSourceFacet::FacetPipeline> facets; facets.emplace_back("needsA", std::move(firstPipeline)); @@ -618,10 +629,11 @@ TEST_F(DocumentSourceFacetTest, ShouldRequirePrimaryShardIfAnyStageRequiresPrima auto ctx = getExpCtx(); auto passthrough = DocumentSourcePassthrough::create(); - auto firstPipeline = unittest::assertGet(Pipeline::create({passthrough}, ctx)); + auto firstPipeline = unittest::assertGet(Pipeline::createFacetPipeline({passthrough}, ctx)); auto needsPrimaryShard = DocumentSourceNeedsPrimaryShard::create(); - auto secondPipeline = unittest::assertGet(Pipeline::create({needsPrimaryShard}, ctx)); + auto secondPipeline = + unittest::assertGet(Pipeline::createFacetPipeline({needsPrimaryShard}, ctx)); std::vector<DocumentSourceFacet::FacetPipeline> facets; facets.emplace_back("passthrough", std::move(firstPipeline)); @@ -635,10 +647,12 @@ TEST_F(DocumentSourceFacetTest, ShouldNotRequirePrimaryShardIfNoStagesRequiresPr auto ctx = getExpCtx(); auto firstPassthrough = DocumentSourcePassthrough::create(); - auto firstPipeline = unittest::assertGet(Pipeline::create({firstPassthrough}, ctx)); + auto firstPipeline = + unittest::assertGet(Pipeline::createFacetPipeline({firstPassthrough}, ctx)); auto secondPassthrough = DocumentSourcePassthrough::create(); - auto secondPipeline = unittest::assertGet(Pipeline::create({secondPassthrough}, ctx)); + auto secondPipeline = + unittest::assertGet(Pipeline::createFacetPipeline({secondPassthrough}, ctx)); std::vector<DocumentSourceFacet::FacetPipeline> facets; facets.emplace_back("first", std::move(firstPipeline)); diff --git a/src/mongo/db/pipeline/pipeline.cpp b/src/mongo/db/pipeline/pipeline.cpp index 01171b86e6d..63dae04e325 100644 --- a/src/mongo/db/pipeline/pipeline.cpp +++ b/src/mongo/db/pipeline/pipeline.cpp @@ -63,7 +63,7 @@ namespace dps = ::mongo::dotted_path_support; Pipeline::Pipeline(const intrusive_ptr<ExpressionContext>& pTheCtx) : pCtx(pTheCtx) {} Pipeline::Pipeline(SourceContainer stages, const intrusive_ptr<ExpressionContext>& expCtx) - : _sources(stages), pCtx(expCtx) {} + : _sources(std::move(stages)), pCtx(expCtx) {} Pipeline::~Pipeline() { invariant(_disposed); @@ -71,29 +71,47 @@ Pipeline::~Pipeline() { StatusWith<std::unique_ptr<Pipeline, Pipeline::Deleter>> Pipeline::parse( const std::vector<BSONObj>& rawPipeline, const intrusive_ptr<ExpressionContext>& expCtx) { - std::unique_ptr<Pipeline, Pipeline::Deleter> pipeline(new Pipeline(expCtx), - Pipeline::Deleter(expCtx->opCtx)); + return parseTopLevelOrFacetPipeline(rawPipeline, expCtx, false); +} + +StatusWith<std::unique_ptr<Pipeline, Pipeline::Deleter>> Pipeline::parseFacetPipeline( + const std::vector<BSONObj>& rawPipeline, const intrusive_ptr<ExpressionContext>& expCtx) { + return parseTopLevelOrFacetPipeline(rawPipeline, expCtx, true); +} + +StatusWith<std::unique_ptr<Pipeline, Pipeline::Deleter>> Pipeline::parseTopLevelOrFacetPipeline( + const std::vector<BSONObj>& rawPipeline, + const intrusive_ptr<ExpressionContext>& expCtx, + const bool isFacetPipeline) { + + SourceContainer stages; for (auto&& stageObj : rawPipeline) { auto parsedSources = DocumentSource::parse(expCtx, stageObj); - pipeline->_sources.insert( - pipeline->_sources.end(), parsedSources.begin(), parsedSources.end()); + stages.insert(stages.end(), parsedSources.begin(), parsedSources.end()); } - auto status = pipeline->validate(); - if (!status.isOK()) { - return status; - } - - pipeline->stitch(); - return std::move(pipeline); + return createTopLevelOrFacetPipeline(std::move(stages), expCtx, isFacetPipeline); } StatusWith<std::unique_ptr<Pipeline, Pipeline::Deleter>> Pipeline::create( SourceContainer stages, const intrusive_ptr<ExpressionContext>& expCtx) { - std::unique_ptr<Pipeline, Pipeline::Deleter> pipeline(new Pipeline(stages, expCtx), + return createTopLevelOrFacetPipeline(std::move(stages), expCtx, false); +} + +StatusWith<std::unique_ptr<Pipeline, Pipeline::Deleter>> Pipeline::createFacetPipeline( + SourceContainer stages, const intrusive_ptr<ExpressionContext>& expCtx) { + return createTopLevelOrFacetPipeline(std::move(stages), expCtx, true); +} + +StatusWith<std::unique_ptr<Pipeline, Pipeline::Deleter>> Pipeline::createTopLevelOrFacetPipeline( + SourceContainer stages, + const intrusive_ptr<ExpressionContext>& expCtx, + const bool isFacetPipeline) { + std::unique_ptr<Pipeline, Pipeline::Deleter> pipeline(new Pipeline(std::move(stages), expCtx), Pipeline::Deleter(expCtx->opCtx)); - auto status = pipeline->validate(); + auto status = + (isFacetPipeline ? pipeline->validateFacetPipeline() : pipeline->validatePipeline()); if (!status.isOK()) { return status; } @@ -102,7 +120,7 @@ StatusWith<std::unique_ptr<Pipeline, Pipeline::Deleter>> Pipeline::create( return std::move(pipeline); } -Status Pipeline::validate() const { +Status Pipeline::validatePipeline() const { // Verify that the specified namespace is valid for the initial stage of this pipeline. const NamespaceString& nss = pCtx->ns; @@ -130,7 +148,26 @@ Status Pipeline::validate() const { } } - // Verify that all stages of the pipeline are in legal positions. + // Verify that each stage is in a legal position within the pipeline. + return ensureAllStagesAreInLegalPositions(); +} + +Status Pipeline::validateFacetPipeline() const { + if (_sources.empty()) { + return {ErrorCodes::BadValue, "sub-pipeline in $facet stage cannot be empty."}; + } else if (_sources.front()->isInitialSource()) { + return {ErrorCodes::BadValue, + str::stream() << _sources.front()->getSourceName() + << " is not allowed to be used within a $facet stage."}; + } + + // Facet pipelines cannot have any stages which are initial sources. We've already validated the + // first stage, and the 'ensureAllStagesAreInLegalPositions' method checks that there are no + // initial sources in positions 1...N, so we can just return its result directly. + return ensureAllStagesAreInLegalPositions(); +} + +Status Pipeline::ensureAllStagesAreInLegalPositions() const { size_t i = 0; for (auto&& stage : _sources) { if (stage->isInitialSource() && i != 0) { diff --git a/src/mongo/db/pipeline/pipeline.h b/src/mongo/db/pipeline/pipeline.h index 8a7f4211490..11f3bd916f1 100644 --- a/src/mongo/db/pipeline/pipeline.h +++ b/src/mongo/db/pipeline/pipeline.h @@ -97,9 +97,9 @@ public: }; /** - * Parses a Pipeline from a BSONElement representing a list of DocumentSources. Returns a non-OK - * status if it failed to parse. The returned pipeline is not optimized, but the caller may - * convert it to an optimized pipeline by calling optimizePipeline(). + * Parses a Pipeline from a vector of BSONObjs. Returns a non-OK status if it failed to parse. + * The returned pipeline is not optimized, but the caller may convert it to an optimized + * pipeline by calling optimizePipeline(). * * It is illegal to create a pipeline using an ExpressionContext which contains a collation that * will not be used during execution of the pipeline. Doing so may cause comparisons made during @@ -110,6 +110,17 @@ public: const boost::intrusive_ptr<ExpressionContext>& expCtx); /** + * Parses a $facet Pipeline from a vector of BSONObjs. Validation checks which are only relevant + * to top-level pipelines are skipped, and additional checks applicable to $facet pipelines are + * performed. Returns a non-OK status if it failed to parse. The returned pipeline is not + * optimized, but the caller may convert it to an optimized pipeline by calling + * optimizePipeline(). + */ + static StatusWith<std::unique_ptr<Pipeline, Pipeline::Deleter>> parseFacetPipeline( + const std::vector<BSONObj>& rawPipeline, + const boost::intrusive_ptr<ExpressionContext>& expCtx); + + /** * Creates a Pipeline from an existing SourceContainer. * * Returns a non-OK status if any stage is in an invalid position. For example, if an $out stage @@ -119,6 +130,15 @@ public: SourceContainer sources, const boost::intrusive_ptr<ExpressionContext>& expCtx); /** + * Creates a $facet Pipeline from an existing SourceContainer. + * + * Returns a non-OK status if any stage is invalid. For example, if the pipeline is empty or if + * any stage is an initial source. + */ + static StatusWith<std::unique_ptr<Pipeline, Pipeline::Deleter>> createFacetPipeline( + SourceContainer sources, const boost::intrusive_ptr<ExpressionContext>& expCtx); + + /** * Returns true if the provided aggregation command has a $out stage. */ static bool aggSupportsWriteConcern(const BSONObj& cmd); @@ -234,6 +254,24 @@ private: friend class Optimizations::Sharded; + /** + * Used by both Pipeline::parse() and Pipeline::parseFacetPipeline() to build and validate the + * pipeline. + */ + static StatusWith<std::unique_ptr<Pipeline, Pipeline::Deleter>> parseTopLevelOrFacetPipeline( + const std::vector<BSONObj>& rawPipeline, + const boost::intrusive_ptr<ExpressionContext>& expCtx, + const bool isFacetPipeline); + + /** + * Used by both Pipeline::create() and Pipeline::createFacetPipeline() to build and validate the + * pipeline. + */ + static StatusWith<std::unique_ptr<Pipeline, Pipeline::Deleter>> createTopLevelOrFacetPipeline( + SourceContainer sources, + const boost::intrusive_ptr<ExpressionContext>& expCtx, + const bool isSubPipeline); + Pipeline(const boost::intrusive_ptr<ExpressionContext>& pCtx); Pipeline(SourceContainer stages, const boost::intrusive_ptr<ExpressionContext>& pCtx); @@ -257,7 +295,21 @@ private: * if an $out stage is present then it must come last in the pipeline, while initial stages such * as $indexStats must be at the start. */ - Status validate() const; + Status validatePipeline() const; + + /** + * Returns a non-OK status if the $facet pipeline fails any of a set of semantic checks. For + * example, the pipeline cannot be empty and may not contain any initial stages. + */ + Status validateFacetPipeline() const; + + /** + * Helper method which validates that each stage in pipeline is in a legal position. For + * example, $out must be at the end, while a $match stage with a text query must be at the + * start. Note that this method accepts an initial source as the first stage, which is illegal + * for $facet pipelines. + */ + Status ensureAllStagesAreInLegalPositions() const; SourceContainer _sources; diff --git a/src/mongo/db/pipeline/pipeline_test.cpp b/src/mongo/db/pipeline/pipeline_test.cpp index ce5437cc454..e44b681da2c 100644 --- a/src/mongo/db/pipeline/pipeline_test.cpp +++ b/src/mongo/db/pipeline/pipeline_test.cpp @@ -1349,6 +1349,15 @@ TEST_F(PipelineInitialSourceNSTest, CollectionNSNotValidIfInitialStageIsCollecti ASSERT_NOT_OK(Pipeline::create({collectionlessSource}, ctx).getStatus()); } +TEST_F(PipelineInitialSourceNSTest, AggregateOneNSValidForFacetPipelineRegardlessOfInitialStage) { + const std::vector<BSONObj> rawPipeline = {fromjson("{$match: {}}")}; + auto ctx = getExpCtx(); + + ctx->ns = NamespaceString::makeCollectionlessAggregateNSS("unittests"); + + ASSERT_OK(Pipeline::parseFacetPipeline(rawPipeline, ctx).getStatus()); +} + } // namespace Namespaces namespace Dependencies { |