summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBernard Gorman <bernard.gorman@gmail.com>2017-06-17 01:19:22 +0100
committerBernard Gorman <bernard.gorman@gmail.com>2017-06-17 16:12:32 +0100
commita5f0a84c79b6ce41fef33da920c62be0ecc8f07b (patch)
tree345557918938fe5de249aee2f8ad30684b80f65a /src
parent923ad3ba8160f2cd614e1258ef19294bd502af78 (diff)
downloadmongo-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.cpp16
-rw-r--r--src/mongo/db/pipeline/document_source_facet_test.cpp68
-rw-r--r--src/mongo/db/pipeline/pipeline.cpp69
-rw-r--r--src/mongo/db/pipeline/pipeline.h60
-rw-r--r--src/mongo/db/pipeline/pipeline_test.cpp9
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 {