diff options
Diffstat (limited to 'src')
46 files changed, 277 insertions, 105 deletions
diff --git a/src/mongo/db/commands/run_aggregate.cpp b/src/mongo/db/commands/run_aggregate.cpp index e33a2c7db68..ef536849333 100644 --- a/src/mongo/db/commands/run_aggregate.cpp +++ b/src/mongo/db/commands/run_aggregate.cpp @@ -516,6 +516,22 @@ std::vector<std::unique_ptr<Pipeline, PipelineDeleter>> createExchangePipelinesI return pipelines; } + +/** + * Performs validations related to API versioning before running the aggregation command. + * Throws uassert if any of the validations fails + * - validation on each stage on the pipeline + * - validation on 'AggregateCommand' request + */ +void performAPIVersionChecks(const OperationContext* opCtx, + const AggregateCommand& request, + const LiteParsedPipeline& liteParsedPipeline) { + invariant(opCtx); + + liteParsedPipeline.validatePipelineStagesforAPIVersion(opCtx); + aggregation_request_helper::validateRequestForAPIVersion(opCtx, request); +} + } // namespace Status runAggregate(OperationContext* opCtx, @@ -534,14 +550,9 @@ Status runAggregate(OperationContext* opCtx, const BSONObj& cmdObj, const PrivilegeVector& privileges, rpc::ReplyBuilderInterface* result) { - // If 'apiStrict: true', validates that the pipeline does not contain stages which are not in - // this API version. - auto apiParameters = APIParameters::get(opCtx); - if (apiParameters.getAPIStrict().value_or(false)) { - auto apiVersion = apiParameters.getAPIVersion(); - invariant(apiVersion); - liteParsedPipeline.validatePipelineStagesIfAPIStrict(*apiVersion); - } + + // Performs API versioning checks. + performAPIVersionChecks(opCtx, request, liteParsedPipeline); // For operations on views, this will be the underlying namespace. NamespaceString nss = request.getNamespace(); diff --git a/src/mongo/db/pipeline/aggregate_command.idl b/src/mongo/db/pipeline/aggregate_command.idl index 10c3bf0b7e2..e904d2c6b31 100644 --- a/src/mongo/db/pipeline/aggregate_command.idl +++ b/src/mongo/db/pipeline/aggregate_command.idl @@ -134,7 +134,6 @@ commands: description: "An optional exchange specification for this request. If set it means that the request represents a producer running as a part of the exchange machinery. This is an internal option; we do not expect it to be set on requests from users or drivers." type: ExchangeSpec optional: true - unstable: true runtimeConstants: description: "A legacy way to specify constant variables available during execution. 'let' is now preferred." type: LegacyRuntimeConstants diff --git a/src/mongo/db/pipeline/aggregation_request_helper.cpp b/src/mongo/db/pipeline/aggregation_request_helper.cpp index a3b574566bd..971d1afb75e 100644 --- a/src/mongo/db/pipeline/aggregation_request_helper.cpp +++ b/src/mongo/db/pipeline/aggregation_request_helper.cpp @@ -179,6 +179,32 @@ void validate(const BSONObj& cmdObj, boost::optional<ExplainOptions::Verbosity> << "' option is not permitted in read-only mode.", (!hasAllowDiskUseElem || !storageGlobalParams.readOnly)); } + +void validateRequestForAPIVersion(const OperationContext* opCtx, const AggregateCommand& request) { + invariant(opCtx); + + auto apiParameters = APIParameters::get(opCtx); + bool apiStrict = apiParameters.getAPIStrict().value_or(false); + const auto apiVersion = apiParameters.getAPIVersion().value_or(""); + auto client = opCtx->getClient(); + + // An internal client could be one of the following : + // - Does not have any transport session + // - The transport session tag is internal + bool isInternalClient = + !client->session() || (client->session()->getTags() & transport::Session::kInternalClient); + + // Checks that the 'exchange' or 'fromMongos' option can only be specified by the internal + // client. + if ((request.getExchange() || request.getFromMongos()) && apiStrict && apiVersion == "1") { + uassert(ErrorCodes::APIStrictError, + str::stream() << "'exchange' and 'fromMongos' option cannot be specified with " + "'apiStrict: true' in API Version " + << apiVersion, + isInternalClient); + } +} + } // namespace aggregation_request_helper // Custom serializers/deserializers for AggregateCommand. diff --git a/src/mongo/db/pipeline/aggregation_request_helper.h b/src/mongo/db/pipeline/aggregation_request_helper.h index 9c021abdd4b..c4cb3adbc48 100644 --- a/src/mongo/db/pipeline/aggregation_request_helper.h +++ b/src/mongo/db/pipeline/aggregation_request_helper.h @@ -48,6 +48,7 @@ template <typename T> class StatusWith; class Document; class AggregateCommand; +class OperationContext; namespace aggregation_request_helper { @@ -111,6 +112,13 @@ NamespaceString parseNs(const std::string& dbname, const BSONObj& cmdObj); Document serializeToCommandDoc(const AggregateCommand& request); BSONObj serializeToCommandObj(const AggregateCommand& request); + +/** + * Validates if 'AggregateCommand' specs complies with API versioning. Throws uassert in case of + * any failure. + */ +void validateRequestForAPIVersion(const OperationContext* opCtx, const AggregateCommand& request); + } // namespace aggregation_request_helper /** diff --git a/src/mongo/db/pipeline/document_source.h b/src/mongo/db/pipeline/document_source.h index e4797743036..c0d50feadbe 100644 --- a/src/mongo/db/pipeline/document_source.h +++ b/src/mongo/db/pipeline/document_source.h @@ -86,16 +86,19 @@ class Document; * LiteParsedDocumentSourceDefault::parse, * DocumentSourceFoo::createFromBson); */ -#define REGISTER_DOCUMENT_SOURCE(key, liteParser, fullParser) \ - REGISTER_DOCUMENT_SOURCE_CONDITIONALLY(key, liteParser, fullParser, boost::none, true) +#define REGISTER_DOCUMENT_SOURCE(key, liteParser, fullParser, allowedWithApiStrict) \ + REGISTER_DOCUMENT_SOURCE_CONDITIONALLY( \ + key, liteParser, fullParser, allowedWithApiStrict, boost::none, true) /** * Like REGISTER_DOCUMENT_SOURCE, except the parser will only be enabled when FCV >= minVersion. * We store minVersion in the parserMap, so that changing FCV at runtime correctly enables/disables * the parser. */ -#define REGISTER_DOCUMENT_SOURCE_WITH_MIN_VERSION(key, liteParser, fullParser, minVersion) \ - REGISTER_DOCUMENT_SOURCE_CONDITIONALLY(key, liteParser, fullParser, minVersion, true) +#define REGISTER_DOCUMENT_SOURCE_WITH_MIN_VERSION( \ + key, liteParser, fullParser, allowedWithApiStrict, minVersion) \ + REGISTER_DOCUMENT_SOURCE_CONDITIONALLY( \ + key, liteParser, fullParser, allowedWithApiStrict, minVersion, true) /** * Like REGISTER_DOCUMENT_SOURCE_WITH_MIN_VERSION, except you can also specify a condition, @@ -109,13 +112,14 @@ class Document; * * This is the most general REGISTER_DOCUMENT_SOURCE* macro, which all others should delegate to. */ -#define REGISTER_DOCUMENT_SOURCE_CONDITIONALLY(key, liteParser, fullParser, minVersion, ...) \ - MONGO_INITIALIZER(addToDocSourceParserMap_##key)(InitializerContext*) { \ - if (!__VA_ARGS__) { \ - return; \ - } \ - LiteParsedDocumentSource::registerParser("$" #key, liteParser); \ - DocumentSource::registerParser("$" #key, fullParser, minVersion); \ +#define REGISTER_DOCUMENT_SOURCE_CONDITIONALLY( \ + key, liteParser, fullParser, allowedWithApiStrict, minVersion, ...) \ + MONGO_INITIALIZER(addToDocSourceParserMap_##key)(InitializerContext*) { \ + if (!__VA_ARGS__) { \ + return; \ + } \ + LiteParsedDocumentSource::registerParser("$" #key, liteParser, allowedWithApiStrict); \ + DocumentSource::registerParser("$" #key, fullParser, minVersion); \ } /** @@ -123,7 +127,12 @@ class Document; */ #define REGISTER_TEST_DOCUMENT_SOURCE(key, liteParser, fullParser) \ REGISTER_DOCUMENT_SOURCE_CONDITIONALLY( \ - key, liteParser, fullParser, boost::none, ::mongo::getTestCommandsEnabled()) + key, \ + liteParser, \ + fullParser, \ + LiteParsedDocumentSource::AllowedWithApiStrict::kInternal, \ + boost::none, \ + ::mongo::getTestCommandsEnabled()) class DocumentSource : public RefCountable { public: diff --git a/src/mongo/db/pipeline/document_source_add_fields.cpp b/src/mongo/db/pipeline/document_source_add_fields.cpp index 53d012cd258..ec245402c2a 100644 --- a/src/mongo/db/pipeline/document_source_add_fields.cpp +++ b/src/mongo/db/pipeline/document_source_add_fields.cpp @@ -42,10 +42,12 @@ using boost::intrusive_ptr; REGISTER_DOCUMENT_SOURCE(addFields, LiteParsedDocumentSourceDefault::parse, - DocumentSourceAddFields::createFromBson); + DocumentSourceAddFields::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); REGISTER_DOCUMENT_SOURCE(set, LiteParsedDocumentSourceDefault::parse, - DocumentSourceAddFields::createFromBson); + DocumentSourceAddFields::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); intrusive_ptr<DocumentSource> DocumentSourceAddFields::create( BSONObj addFieldsSpec, diff --git a/src/mongo/db/pipeline/document_source_bucket.cpp b/src/mongo/db/pipeline/document_source_bucket.cpp index d5761bf3c85..5fe098ae418 100644 --- a/src/mongo/db/pipeline/document_source_bucket.cpp +++ b/src/mongo/db/pipeline/document_source_bucket.cpp @@ -42,7 +42,8 @@ using std::vector; REGISTER_DOCUMENT_SOURCE(bucket, LiteParsedDocumentSourceDefault::parse, - DocumentSourceBucket::createFromBson); + DocumentSourceBucket::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); namespace { intrusive_ptr<ExpressionConstant> getExpressionConstant(ExpressionContext* const expCtx, diff --git a/src/mongo/db/pipeline/document_source_bucket_auto.cpp b/src/mongo/db/pipeline/document_source_bucket_auto.cpp index f5fea67e862..1ffac313f28 100644 --- a/src/mongo/db/pipeline/document_source_bucket_auto.cpp +++ b/src/mongo/db/pipeline/document_source_bucket_auto.cpp @@ -44,7 +44,8 @@ using std::vector; REGISTER_DOCUMENT_SOURCE(bucketAuto, LiteParsedDocumentSourceDefault::parse, - DocumentSourceBucketAuto::createFromBson); + DocumentSourceBucketAuto::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); namespace { diff --git a/src/mongo/db/pipeline/document_source_change_stream.cpp b/src/mongo/db/pipeline/document_source_change_stream.cpp index 78d510b625d..d1e51781de3 100644 --- a/src/mongo/db/pipeline/document_source_change_stream.cpp +++ b/src/mongo/db/pipeline/document_source_change_stream.cpp @@ -68,7 +68,8 @@ using std::vector; // will not serialize themselves. REGISTER_DOCUMENT_SOURCE(changeStream, DocumentSourceChangeStream::LiteParsed::parse, - DocumentSourceChangeStream::createFromBson); + DocumentSourceChangeStream::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); constexpr StringData DocumentSourceChangeStream::kDocumentKeyField; constexpr StringData DocumentSourceChangeStream::kFullDocumentBeforeChangeField; diff --git a/src/mongo/db/pipeline/document_source_coll_stats.cpp b/src/mongo/db/pipeline/document_source_coll_stats.cpp index 1c2d295b27b..30b7255e1ce 100644 --- a/src/mongo/db/pipeline/document_source_coll_stats.cpp +++ b/src/mongo/db/pipeline/document_source_coll_stats.cpp @@ -43,7 +43,8 @@ namespace mongo { REGISTER_DOCUMENT_SOURCE(collStats, DocumentSourceCollStats::LiteParsed::parse, - DocumentSourceCollStats::createFromBson); + DocumentSourceCollStats::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kNeverInVersion1); const char* DocumentSourceCollStats::getSourceName() const { return kStageName.rawData(); diff --git a/src/mongo/db/pipeline/document_source_count.cpp b/src/mongo/db/pipeline/document_source_count.cpp index 1107e2e6eeb..988bf414da3 100644 --- a/src/mongo/db/pipeline/document_source_count.cpp +++ b/src/mongo/db/pipeline/document_source_count.cpp @@ -45,7 +45,8 @@ using std::string; REGISTER_DOCUMENT_SOURCE(count, LiteParsedDocumentSourceDefault::parse, - DocumentSourceCount::createFromBson); + DocumentSourceCount::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); list<intrusive_ptr<DocumentSource>> DocumentSourceCount::createFromBson( BSONElement elem, const intrusive_ptr<ExpressionContext>& pExpCtx) { diff --git a/src/mongo/db/pipeline/document_source_current_op.cpp b/src/mongo/db/pipeline/document_source_current_op.cpp index e0d40c619c0..da1dda80226 100644 --- a/src/mongo/db/pipeline/document_source_current_op.cpp +++ b/src/mongo/db/pipeline/document_source_current_op.cpp @@ -54,7 +54,8 @@ using boost::intrusive_ptr; REGISTER_DOCUMENT_SOURCE(currentOp, DocumentSourceCurrentOp::LiteParsed::parse, - DocumentSourceCurrentOp::createFromBson); + DocumentSourceCurrentOp::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kNeverInVersion1); constexpr StringData DocumentSourceCurrentOp::kStageName; diff --git a/src/mongo/db/pipeline/document_source_facet.cpp b/src/mongo/db/pipeline/document_source_facet.cpp index 8c5e3da99e0..2cee70a250c 100644 --- a/src/mongo/db/pipeline/document_source_facet.cpp +++ b/src/mongo/db/pipeline/document_source_facet.cpp @@ -123,7 +123,8 @@ std::unique_ptr<DocumentSourceFacet::LiteParsed> DocumentSourceFacet::LiteParsed REGISTER_DOCUMENT_SOURCE(facet, DocumentSourceFacet::LiteParsed::parse, - DocumentSourceFacet::createFromBson); + DocumentSourceFacet::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); intrusive_ptr<DocumentSourceFacet> DocumentSourceFacet::create( std::vector<FacetPipeline> facetPipelines, diff --git a/src/mongo/db/pipeline/document_source_geo_near.cpp b/src/mongo/db/pipeline/document_source_geo_near.cpp index a682d8fbeaa..a5b49788551 100644 --- a/src/mongo/db/pipeline/document_source_geo_near.cpp +++ b/src/mongo/db/pipeline/document_source_geo_near.cpp @@ -46,7 +46,8 @@ constexpr StringData DocumentSourceGeoNear::kKeyFieldName; REGISTER_DOCUMENT_SOURCE(geoNear, LiteParsedDocumentSourceDefault::parse, - DocumentSourceGeoNear::createFromBson); + DocumentSourceGeoNear::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); Value DocumentSourceGeoNear::serialize(boost::optional<ExplainOptions::Verbosity> explain) const { MutableDocument result; diff --git a/src/mongo/db/pipeline/document_source_graph_lookup.cpp b/src/mongo/db/pipeline/document_source_graph_lookup.cpp index b9b10cf15a4..eca4b8b8211 100644 --- a/src/mongo/db/pipeline/document_source_graph_lookup.cpp +++ b/src/mongo/db/pipeline/document_source_graph_lookup.cpp @@ -111,7 +111,8 @@ std::unique_ptr<DocumentSourceGraphLookUp::LiteParsed> DocumentSourceGraphLookUp REGISTER_DOCUMENT_SOURCE(graphLookup, DocumentSourceGraphLookUp::LiteParsed::parse, - DocumentSourceGraphLookUp::createFromBson); + DocumentSourceGraphLookUp::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); const char* DocumentSourceGraphLookUp::getSourceName() const { return kStageName.rawData(); diff --git a/src/mongo/db/pipeline/document_source_group.cpp b/src/mongo/db/pipeline/document_source_group.cpp index f056395b8a3..7987265b028 100644 --- a/src/mongo/db/pipeline/document_source_group.cpp +++ b/src/mongo/db/pipeline/document_source_group.cpp @@ -126,7 +126,8 @@ constexpr StringData DocumentSourceGroup::kStageName; REGISTER_DOCUMENT_SOURCE(group, LiteParsedDocumentSourceDefault::parse, - DocumentSourceGroup::createFromBson); + DocumentSourceGroup::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); const char* DocumentSourceGroup::getSourceName() const { return kStageName.rawData(); diff --git a/src/mongo/db/pipeline/document_source_index_stats.cpp b/src/mongo/db/pipeline/document_source_index_stats.cpp index 5612135cd78..ad6d600e74d 100644 --- a/src/mongo/db/pipeline/document_source_index_stats.cpp +++ b/src/mongo/db/pipeline/document_source_index_stats.cpp @@ -41,7 +41,8 @@ using boost::intrusive_ptr; REGISTER_DOCUMENT_SOURCE(indexStats, DocumentSourceIndexStats::LiteParsed::parse, - DocumentSourceIndexStats::createFromBson); + DocumentSourceIndexStats::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kNeverInVersion1); const char* DocumentSourceIndexStats::getSourceName() const { return kStageName.rawData(); diff --git a/src/mongo/db/pipeline/document_source_internal_inhibit_optimization.cpp b/src/mongo/db/pipeline/document_source_internal_inhibit_optimization.cpp index f5729e4570a..10f10171f06 100644 --- a/src/mongo/db/pipeline/document_source_internal_inhibit_optimization.cpp +++ b/src/mongo/db/pipeline/document_source_internal_inhibit_optimization.cpp @@ -35,7 +35,8 @@ namespace mongo { REGISTER_DOCUMENT_SOURCE(_internalInhibitOptimization, LiteParsedDocumentSourceDefault::parse, - DocumentSourceInternalInhibitOptimization::createFromBson); + DocumentSourceInternalInhibitOptimization::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kNeverInVersion1); constexpr StringData DocumentSourceInternalInhibitOptimization::kStageName; diff --git a/src/mongo/db/pipeline/document_source_internal_split_pipeline.cpp b/src/mongo/db/pipeline/document_source_internal_split_pipeline.cpp index 4857fac3d58..c2721ff77d3 100644 --- a/src/mongo/db/pipeline/document_source_internal_split_pipeline.cpp +++ b/src/mongo/db/pipeline/document_source_internal_split_pipeline.cpp @@ -35,7 +35,8 @@ namespace mongo { REGISTER_DOCUMENT_SOURCE(_internalSplitPipeline, LiteParsedDocumentSourceDefault::parse, - DocumentSourceInternalSplitPipeline::createFromBson); + DocumentSourceInternalSplitPipeline::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kNeverInVersion1); constexpr StringData DocumentSourceInternalSplitPipeline::kStageName; diff --git a/src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp b/src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp index 6326ec32419..4abb46215a5 100644 --- a/src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp +++ b/src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp @@ -42,7 +42,8 @@ namespace mongo { REGISTER_DOCUMENT_SOURCE(_internalUnpackBucket, LiteParsedDocumentSourceDefault::parse, - DocumentSourceInternalUnpackBucket::createFromBson); + DocumentSourceInternalUnpackBucket::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kInternal); namespace { /** diff --git a/src/mongo/db/pipeline/document_source_limit.cpp b/src/mongo/db/pipeline/document_source_limit.cpp index ef08bc707ad..e6ef0d71405 100644 --- a/src/mongo/db/pipeline/document_source_limit.cpp +++ b/src/mongo/db/pipeline/document_source_limit.cpp @@ -48,7 +48,8 @@ DocumentSourceLimit::DocumentSourceLimit(const intrusive_ptr<ExpressionContext>& REGISTER_DOCUMENT_SOURCE(limit, LiteParsedDocumentSourceDefault::parse, - DocumentSourceLimit::createFromBson); + DocumentSourceLimit::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); constexpr StringData DocumentSourceLimit::kStageName; diff --git a/src/mongo/db/pipeline/document_source_list_local_sessions.cpp b/src/mongo/db/pipeline/document_source_list_local_sessions.cpp index 24efef4263a..f9c329ff65e 100644 --- a/src/mongo/db/pipeline/document_source_list_local_sessions.cpp +++ b/src/mongo/db/pipeline/document_source_list_local_sessions.cpp @@ -39,7 +39,8 @@ namespace mongo { REGISTER_DOCUMENT_SOURCE(listLocalSessions, DocumentSourceListLocalSessions::LiteParsed::parse, - DocumentSourceListLocalSessions::createFromBson); + DocumentSourceListLocalSessions::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kNeverInVersion1); DocumentSource::GetNextResult DocumentSourceListLocalSessions::doGetNext() { while (!_ids.empty()) { diff --git a/src/mongo/db/pipeline/document_source_list_sessions.cpp b/src/mongo/db/pipeline/document_source_list_sessions.cpp index 5eba38b1510..996856b3657 100644 --- a/src/mongo/db/pipeline/document_source_list_sessions.cpp +++ b/src/mongo/db/pipeline/document_source_list_sessions.cpp @@ -40,7 +40,8 @@ namespace mongo { REGISTER_DOCUMENT_SOURCE(listSessions, DocumentSourceListSessions::LiteParsed::parse, - DocumentSourceListSessions::createFromBson); + DocumentSourceListSessions::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kNeverInVersion1); boost::intrusive_ptr<DocumentSource> DocumentSourceListSessions::createFromBson( BSONElement elem, const boost::intrusive_ptr<ExpressionContext>& pExpCtx) { diff --git a/src/mongo/db/pipeline/document_source_lookup.cpp b/src/mongo/db/pipeline/document_source_lookup.cpp index 1961941287c..1b62deebf6e 100644 --- a/src/mongo/db/pipeline/document_source_lookup.cpp +++ b/src/mongo/db/pipeline/document_source_lookup.cpp @@ -253,7 +253,8 @@ PrivilegeVector DocumentSourceLookUp::LiteParsed::requiredPrivileges( REGISTER_DOCUMENT_SOURCE(lookup, DocumentSourceLookUp::LiteParsed::parse, - DocumentSourceLookUp::createFromBson); + DocumentSourceLookUp::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); const char* DocumentSourceLookUp::getSourceName() const { return kStageName.rawData(); diff --git a/src/mongo/db/pipeline/document_source_match.cpp b/src/mongo/db/pipeline/document_source_match.cpp index e64151cb77f..d292f7a64de 100644 --- a/src/mongo/db/pipeline/document_source_match.cpp +++ b/src/mongo/db/pipeline/document_source_match.cpp @@ -57,7 +57,8 @@ using std::vector; REGISTER_DOCUMENT_SOURCE(match, LiteParsedDocumentSourceDefault::parse, - DocumentSourceMatch::createFromBson); + DocumentSourceMatch::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); const char* DocumentSourceMatch::getSourceName() const { return kStageName.rawData(); diff --git a/src/mongo/db/pipeline/document_source_merge.cpp b/src/mongo/db/pipeline/document_source_merge.cpp index 72e3089bd71..36f652c66cd 100644 --- a/src/mongo/db/pipeline/document_source_merge.cpp +++ b/src/mongo/db/pipeline/document_source_merge.cpp @@ -49,7 +49,8 @@ using namespace fmt::literals; MONGO_FAIL_POINT_DEFINE(hangWhileBuildingDocumentSourceMergeBatch); REGISTER_DOCUMENT_SOURCE(merge, DocumentSourceMerge::LiteParsed::parse, - DocumentSourceMerge::createFromBson); + DocumentSourceMerge::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); namespace { using MergeStrategyDescriptor = DocumentSourceMerge::MergeStrategyDescriptor; diff --git a/src/mongo/db/pipeline/document_source_operation_metrics.cpp b/src/mongo/db/pipeline/document_source_operation_metrics.cpp index c2037bc2e12..5855894bf3b 100644 --- a/src/mongo/db/pipeline/document_source_operation_metrics.cpp +++ b/src/mongo/db/pipeline/document_source_operation_metrics.cpp @@ -43,7 +43,8 @@ using boost::intrusive_ptr; REGISTER_DOCUMENT_SOURCE(operationMetrics, DocumentSourceOperationMetrics::LiteParsed::parse, - DocumentSourceOperationMetrics::createFromBson); + DocumentSourceOperationMetrics::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kNeverInVersion1); const char* DocumentSourceOperationMetrics::getSourceName() const { return kStageName.rawData(); diff --git a/src/mongo/db/pipeline/document_source_out.cpp b/src/mongo/db/pipeline/document_source_out.cpp index bdb2991d146..1fef7bb2bd2 100644 --- a/src/mongo/db/pipeline/document_source_out.cpp +++ b/src/mongo/db/pipeline/document_source_out.cpp @@ -51,7 +51,8 @@ MONGO_FAIL_POINT_DEFINE(hangWhileBuildingDocumentSourceOutBatch); MONGO_FAIL_POINT_DEFINE(outWaitAfterTempCollectionCreation); REGISTER_DOCUMENT_SOURCE(out, DocumentSourceOut::LiteParsed::parse, - DocumentSourceOut::createFromBson); + DocumentSourceOut::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); DocumentSourceOut::~DocumentSourceOut() { DESTRUCTOR_GUARD( diff --git a/src/mongo/db/pipeline/document_source_plan_cache_stats.cpp b/src/mongo/db/pipeline/document_source_plan_cache_stats.cpp index 1d871a9521b..bfbe60b4ec8 100644 --- a/src/mongo/db/pipeline/document_source_plan_cache_stats.cpp +++ b/src/mongo/db/pipeline/document_source_plan_cache_stats.cpp @@ -35,7 +35,8 @@ namespace mongo { REGISTER_DOCUMENT_SOURCE(planCacheStats, DocumentSourcePlanCacheStats::LiteParsed::parse, - DocumentSourcePlanCacheStats::createFromBson); + DocumentSourcePlanCacheStats::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kNeverInVersion1); boost::intrusive_ptr<DocumentSource> DocumentSourcePlanCacheStats::createFromBson( BSONElement spec, const boost::intrusive_ptr<ExpressionContext>& pExpCtx) { diff --git a/src/mongo/db/pipeline/document_source_project.cpp b/src/mongo/db/pipeline/document_source_project.cpp index 6ea0e53e055..3630cae3440 100644 --- a/src/mongo/db/pipeline/document_source_project.cpp +++ b/src/mongo/db/pipeline/document_source_project.cpp @@ -45,11 +45,13 @@ using boost::intrusive_ptr; REGISTER_DOCUMENT_SOURCE(project, LiteParsedDocumentSourceDefault::parse, - DocumentSourceProject::createFromBson); + DocumentSourceProject::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); REGISTER_DOCUMENT_SOURCE(unset, LiteParsedDocumentSourceDefault::parse, - DocumentSourceProject::createFromBson); + DocumentSourceProject::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); namespace { BSONObj buildExclusionProjectionSpecification(const std::vector<BSONElement>& unsetSpec) { diff --git a/src/mongo/db/pipeline/document_source_redact.cpp b/src/mongo/db/pipeline/document_source_redact.cpp index efe6316c69b..8fcc1a6c826 100644 --- a/src/mongo/db/pipeline/document_source_redact.cpp +++ b/src/mongo/db/pipeline/document_source_redact.cpp @@ -51,7 +51,8 @@ DocumentSourceRedact::DocumentSourceRedact(const intrusive_ptr<ExpressionContext REGISTER_DOCUMENT_SOURCE(redact, LiteParsedDocumentSourceDefault::parse, - DocumentSourceRedact::createFromBson); + DocumentSourceRedact::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); const char* DocumentSourceRedact::getSourceName() const { return kStageName.rawData(); diff --git a/src/mongo/db/pipeline/document_source_replace_root.cpp b/src/mongo/db/pipeline/document_source_replace_root.cpp index 2d3e1de27af..084d7398fb9 100644 --- a/src/mongo/db/pipeline/document_source_replace_root.cpp +++ b/src/mongo/db/pipeline/document_source_replace_root.cpp @@ -76,10 +76,12 @@ Document ReplaceRootTransformation::applyTransformation(const Document& input) { REGISTER_DOCUMENT_SOURCE(replaceRoot, LiteParsedDocumentSourceDefault::parse, - DocumentSourceReplaceRoot::createFromBson); + DocumentSourceReplaceRoot::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); REGISTER_DOCUMENT_SOURCE(replaceWith, LiteParsedDocumentSourceDefault::parse, - DocumentSourceReplaceRoot::createFromBson); + DocumentSourceReplaceRoot::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); intrusive_ptr<DocumentSource> DocumentSourceReplaceRoot::createFromBson( BSONElement elem, const intrusive_ptr<ExpressionContext>& expCtx) { diff --git a/src/mongo/db/pipeline/document_source_sample.cpp b/src/mongo/db/pipeline/document_source_sample.cpp index bfb667f5983..57d5661c388 100644 --- a/src/mongo/db/pipeline/document_source_sample.cpp +++ b/src/mongo/db/pipeline/document_source_sample.cpp @@ -48,7 +48,8 @@ DocumentSourceSample::DocumentSourceSample(const intrusive_ptr<ExpressionContext REGISTER_DOCUMENT_SOURCE(sample, LiteParsedDocumentSourceDefault::parse, - DocumentSourceSample::createFromBson); + DocumentSourceSample::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); DocumentSource::GetNextResult DocumentSourceSample::doGetNext() { if (_size == 0) diff --git a/src/mongo/db/pipeline/document_source_set_window_fields.cpp b/src/mongo/db/pipeline/document_source_set_window_fields.cpp index a089596ccb6..d8d691eff34 100644 --- a/src/mongo/db/pipeline/document_source_set_window_fields.cpp +++ b/src/mongo/db/pipeline/document_source_set_window_fields.cpp @@ -49,6 +49,7 @@ REGISTER_DOCUMENT_SOURCE_CONDITIONALLY( setWindowFields, LiteParsedDocumentSourceDefault::parse, document_source_set_window_fields::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways, boost::none, ::mongo::feature_flags::gFeatureFlagWindowFunctions.isEnabledAndIgnoreFCV()); @@ -56,6 +57,7 @@ REGISTER_DOCUMENT_SOURCE_CONDITIONALLY( _internalSetWindowFields, LiteParsedDocumentSourceDefault::parse, DocumentSourceInternalSetWindowFields::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kInternal, boost::none, ::mongo::feature_flags::gFeatureFlagWindowFunctions.isEnabledAndIgnoreFCV()); diff --git a/src/mongo/db/pipeline/document_source_skip.cpp b/src/mongo/db/pipeline/document_source_skip.cpp index 33fff89bcef..b5c91d23059 100644 --- a/src/mongo/db/pipeline/document_source_skip.cpp +++ b/src/mongo/db/pipeline/document_source_skip.cpp @@ -49,7 +49,8 @@ DocumentSourceSkip::DocumentSourceSkip(const intrusive_ptr<ExpressionContext>& p REGISTER_DOCUMENT_SOURCE(skip, LiteParsedDocumentSourceDefault::parse, - DocumentSourceSkip::createFromBson); + DocumentSourceSkip::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); constexpr StringData DocumentSourceSkip::kStageName; diff --git a/src/mongo/db/pipeline/document_source_sort.cpp b/src/mongo/db/pipeline/document_source_sort.cpp index 3e5bdf80e6a..f27dc68c0dd 100644 --- a/src/mongo/db/pipeline/document_source_sort.cpp +++ b/src/mongo/db/pipeline/document_source_sort.cpp @@ -73,7 +73,8 @@ DocumentSourceSort::DocumentSourceSort(const boost::intrusive_ptr<ExpressionCont REGISTER_DOCUMENT_SOURCE(sort, LiteParsedDocumentSourceDefault::parse, - DocumentSourceSort::createFromBson); + DocumentSourceSort::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); DocumentSource::GetNextResult DocumentSourceSort::doGetNext() { if (!_populated) { diff --git a/src/mongo/db/pipeline/document_source_sort_by_count.cpp b/src/mongo/db/pipeline/document_source_sort_by_count.cpp index 5efce02e3a0..d35ec06f33e 100644 --- a/src/mongo/db/pipeline/document_source_sort_by_count.cpp +++ b/src/mongo/db/pipeline/document_source_sort_by_count.cpp @@ -44,7 +44,8 @@ using std::list; REGISTER_DOCUMENT_SOURCE(sortByCount, LiteParsedDocumentSourceDefault::parse, - DocumentSourceSortByCount::createFromBson); + DocumentSourceSortByCount::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); list<intrusive_ptr<DocumentSource>> DocumentSourceSortByCount::createFromBson( BSONElement elem, const intrusive_ptr<ExpressionContext>& pExpCtx) { diff --git a/src/mongo/db/pipeline/document_source_union_with.cpp b/src/mongo/db/pipeline/document_source_union_with.cpp index bb89ec69a0d..17570253cda 100644 --- a/src/mongo/db/pipeline/document_source_union_with.cpp +++ b/src/mongo/db/pipeline/document_source_union_with.cpp @@ -44,7 +44,8 @@ namespace mongo { REGISTER_DOCUMENT_SOURCE(unionWith, DocumentSourceUnionWith::LiteParsed::parse, - DocumentSourceUnionWith::createFromBson); + DocumentSourceUnionWith::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); namespace { std::unique_ptr<Pipeline, PipelineDeleter> buildPipelineFromViewDefinition( diff --git a/src/mongo/db/pipeline/document_source_unwind.cpp b/src/mongo/db/pipeline/document_source_unwind.cpp index 95816bbb68e..b08e6f7aec3 100644 --- a/src/mongo/db/pipeline/document_source_unwind.cpp +++ b/src/mongo/db/pipeline/document_source_unwind.cpp @@ -168,7 +168,8 @@ DocumentSourceUnwind::DocumentSourceUnwind(const intrusive_ptr<ExpressionContext REGISTER_DOCUMENT_SOURCE(unwind, LiteParsedDocumentSourceDefault::parse, - DocumentSourceUnwind::createFromBson); + DocumentSourceUnwind::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kAlways); const char* DocumentSourceUnwind::getSourceName() const { return kStageName.rawData(); diff --git a/src/mongo/db/pipeline/lite_parsed_document_source.cpp b/src/mongo/db/pipeline/lite_parsed_document_source.cpp index a21064b8387..7ef5d1399c6 100644 --- a/src/mongo/db/pipeline/lite_parsed_document_source.cpp +++ b/src/mongo/db/pipeline/lite_parsed_document_source.cpp @@ -42,12 +42,19 @@ namespace { // Empty vector used by LiteParsedDocumentSources which do not have a sub pipeline. inline static std::vector<LiteParsedPipeline> kNoSubPipeline = {}; -StringMap<Parser> parserMap; +struct LiteParserInfo { + Parser parser; + LiteParsedDocumentSource::AllowedWithApiStrict allowedWithApiStrict; +}; + +StringMap<LiteParserInfo> parserMap; } // namespace -void LiteParsedDocumentSource::registerParser(const std::string& name, Parser parser) { - parserMap[name] = parser; +void LiteParsedDocumentSource::registerParser(const std::string& name, + Parser parser, + AllowedWithApiStrict flag) { + parserMap[name] = {parser, flag}; // Initialize a counter for this document source to track how many times it is used. aggStageCounters.stageCounterMap[name] = std::make_unique<AggStageCounters::StageCounter>(name); } @@ -66,7 +73,17 @@ std::unique_ptr<LiteParsedDocumentSource> LiteParsedDocumentSource::parse( str::stream() << "Unrecognized pipeline stage name: '" << stageName << "'", it != parserMap.end()); - return it->second(nss, specElem); + return it->second.parser(nss, specElem); +} + +LiteParsedDocumentSource::AllowedWithApiStrict LiteParsedDocumentSource::getApiVersionAllowanceFlag( + std::string stageName) { + auto it = parserMap.find(stageName); + uassert(5407200, + str::stream() << "Unrecognized pipeline stage name: '" << stageName << "'", + it != parserMap.end()); + + return it->second.allowedWithApiStrict; } const std::vector<LiteParsedPipeline>& LiteParsedDocumentSource::getSubPipelines() const { diff --git a/src/mongo/db/pipeline/lite_parsed_document_source.h b/src/mongo/db/pipeline/lite_parsed_document_source.h index 38c01ab02c3..b289effe0fb 100644 --- a/src/mongo/db/pipeline/lite_parsed_document_source.h +++ b/src/mongo/db/pipeline/lite_parsed_document_source.h @@ -53,6 +53,18 @@ class LiteParsedPipeline; */ class LiteParsedDocumentSource { public: + /** + * Flags to mark stages with different allowance constrains when API versioning is enabled. + */ + enum class AllowedWithApiStrict { + // The stage is always allowed in the pipeline regardless of API versions. + kAlways, + // The stage is allowed only for internal client when 'apiStrict' is set to true. + kInternal, + // The stage is never allowed in API version '1' when 'apiStrict' is set to true. + kNeverInVersion1 + }; + LiteParsedDocumentSource(std::string parseTimeName) : _parseTimeName(std::move(parseTimeName)) {} @@ -71,11 +83,20 @@ public: /** * Registers a DocumentSource with a spec parsing function, so that when a stage with the given * name is encountered, it will call 'parser' to construct that stage's specification object. + * The flag 'allowedWithApiStrict' is used to control the allowance of the stage when + * 'apiStrict' is set to true. * * DO NOT call this method directly. Instead, use the REGISTER_DOCUMENT_SOURCE macro defined in * document_source.h. */ - static void registerParser(const std::string& name, Parser parser); + static void registerParser(const std::string& name, + Parser parser, + AllowedWithApiStrict allowedWithApiStrict); + + /** + * Returns the 'ApiVersionAllowanceFlag' flag value for the specified stage name. + */ + static AllowedWithApiStrict getApiVersionAllowanceFlag(std::string stageName); /** * Constructs a LiteParsedDocumentSource from the user-supplied BSON, or throws a diff --git a/src/mongo/db/pipeline/lite_parsed_pipeline.cpp b/src/mongo/db/pipeline/lite_parsed_pipeline.cpp index c737d837ba7..38619be9e78 100644 --- a/src/mongo/db/pipeline/lite_parsed_pipeline.cpp +++ b/src/mongo/db/pipeline/lite_parsed_pipeline.cpp @@ -124,18 +124,53 @@ void LiteParsedPipeline::tickGlobalStageCounters() const { } } -void LiteParsedPipeline::validatePipelineStagesIfAPIStrict(const std::string& version) const { +void LiteParsedPipeline::validatePipelineStagesforAPIVersion(const OperationContext* opCtx) const { + invariant(opCtx); + + using AllowanceFlags = LiteParsedDocumentSource::AllowedWithApiStrict; + + auto apiParameters = APIParameters::get(opCtx); + bool apiStrict = apiParameters.getAPIStrict().value_or(false); + + // These checks gets applied only when apiStrict is set to true. + if (!apiStrict) { + return; + } + + auto apiVersion = apiParameters.getAPIVersion().value_or(""); + auto client = opCtx->getClient(); + + // An internal client could be one of the following : + // - Does not have any transport session + // - The transport session tag is internal + bool isInternalClient = + !client->session() || (client->session()->getTags() & transport::Session::kInternalClient); + + for (auto&& stage : _stageSpecs) { - if (version == "1") { + const auto& stageName = stage->getParseTimeName(); + const auto& flag = LiteParsedDocumentSource::getApiVersionAllowanceFlag(stageName); + + // Checks that the stage is allowed in API version 1. + if (apiVersion == "1") { uassert(ErrorCodes::APIStrictError, - str::stream() << "stage " << stage->getParseTimeName() + str::stream() << "stage " << stageName << " is not allowed with 'apiStrict: true' in API Version " - << version, - isStageInAPIVersion1(stage->getParseTimeName())); + << apiVersion, + AllowanceFlags::kNeverInVersion1 != flag); + } - for (auto&& subPipeline : stage->getSubPipelines()) { - subPipeline.validatePipelineStagesIfAPIStrict(version); - } + // Checks that the internal stage can be specified only by the internal client. + if (AllowanceFlags::kInternal == flag) { + uassert(ErrorCodes::APIStrictError, + str::stream() << "Internal stage " << stageName + << " cannot be specified with 'apiStrict: true' in API Version " + << apiVersion, + isInternalClient); + } + + for (auto&& subPipeline : stage->getSubPipelines()) { + subPipeline.validatePipelineStagesforAPIVersion(opCtx); } } } diff --git a/src/mongo/db/pipeline/lite_parsed_pipeline.h b/src/mongo/db/pipeline/lite_parsed_pipeline.h index 00a01ff6ba5..49a1215862e 100644 --- a/src/mongo/db/pipeline/lite_parsed_pipeline.h +++ b/src/mongo/db/pipeline/lite_parsed_pipeline.h @@ -170,27 +170,31 @@ public: void tickGlobalStageCounters() const; /** - * Returns true if 'stageName' is in API Version 1. - */ - static bool isStageInAPIVersion1(const std::string& stageName) { - // These stages are excluded from API Version1 with 'apiStrict: true'. - static const stdx::unordered_set<std::string> stagesExcluded = {"$collStats", - "$currentOp", - "$indexStats", - "$listLocalSessions", - "$listSessions", - "$planCacheStats", - "$search", - "$searchBeta"}; - - return (stagesExcluded.find(stageName) == stagesExcluded.end()); - } + * Performs API versioning validations on the aggregate pipeline stages. + */ + void validatePipelineStagesforAPIVersion(const OperationContext* opCtx) const; + + /** + * Validates if 'AggregateCommand' specs complies with API versioning. Throws uassert in case of + * any failure. + */ + void validateRequestForAPIVersion(const OperationContext* opCtx, + const AggregateCommand& request) const; /** - * Throws 'APIStrictError' if the pipeline contains the stages which are not in API Version - * 'version'. + * Performs validations related to API versioning before running the aggregation command. + * Throws uassert if any of the validations fails + * - validation on each stage on the pipeline + * - validation on 'AggregateCommand' request */ - void validatePipelineStagesIfAPIStrict(const std::string& version) const; + void performAPIVersionChecks(const OperationContext* opCtx, + const AggregateCommand& request) const { + + invariant(opCtx); + + validatePipelineStagesforAPIVersion(opCtx); + validateRequestForAPIVersion(opCtx, request); + } private: std::vector<std::unique_ptr<LiteParsedDocumentSource>> _stageSpecs; diff --git a/src/mongo/db/views/view_catalog.cpp b/src/mongo/db/views/view_catalog.cpp index 0132c61ee2c..80cc04f8603 100644 --- a/src/mongo/db/views/view_catalog.cpp +++ b/src/mongo/db/views/view_catalog.cpp @@ -397,14 +397,8 @@ StatusWith<stdx::unordered_set<NamespaceString>> ViewCatalog::validatePipeline( const LiteParsedPipeline liteParsedPipeline(viewDef.viewOn(), viewDef.pipeline()); const auto involvedNamespaces = liteParsedPipeline.getInvolvedNamespaces(); - // If 'apiStrict: true', validates that the pipeline does not contain stages which are not in - // this API version. - auto apiParameters = APIParameters::get(opCtx); - if (apiParameters.getAPIStrict().value_or(false)) { - auto apiVersion = apiParameters.getAPIVersion(); - invariant(apiVersion); - liteParsedPipeline.validatePipelineStagesIfAPIStrict(*apiVersion); - } + // Perform API versioning validation checks on stages of the pipeline. + liteParsedPipeline.validatePipelineStagesforAPIVersion(opCtx); // Verify that this is a legitimate pipeline specification by making sure it parses // correctly. In order to parse a pipeline we need to resolve any namespaces involved to a diff --git a/src/mongo/s/query/cluster_aggregate.cpp b/src/mongo/s/query/cluster_aggregate.cpp index fa0d714d31c..1a2cdb8467f 100644 --- a/src/mongo/s/query/cluster_aggregate.cpp +++ b/src/mongo/s/query/cluster_aggregate.cpp @@ -188,6 +188,21 @@ void updateHostsTargetedMetrics(OperationContext* opCtx, } } +/** + * Performs validations related to API versioning before running the aggregation command. + * Throws uassert if any of the validations fails + * - validation on each stage on the pipeline + * - validation on 'AggregateCommand' request + */ +void performAPIVersionChecks(const OperationContext* opCtx, + const AggregateCommand& request, + const LiteParsedPipeline& liteParsedPipeline) { + invariant(opCtx); + + liteParsedPipeline.validatePipelineStagesforAPIVersion(opCtx); + aggregation_request_helper::validateRequestForAPIVersion(opCtx, request); +} + } // namespace Status ClusterAggregate::runAggregate(OperationContext* opCtx, @@ -204,14 +219,8 @@ Status ClusterAggregate::runAggregate(OperationContext* opCtx, const LiteParsedPipeline& liteParsedPipeline, const PrivilegeVector& privileges, BSONObjBuilder* result) { - // If 'apiStrict: true', validates that the pipeline does not contain stages which are not in - // this API version. - auto apiParameters = APIParameters::get(opCtx); - if (apiParameters.getAPIStrict().value_or(false)) { - auto apiVersion = apiParameters.getAPIVersion().value_or(""); - if (!apiVersion.empty()) - liteParsedPipeline.validatePipelineStagesIfAPIStrict(apiVersion); - } + // Perform API versioning validation checks. + performAPIVersionChecks(opCtx, request, liteParsedPipeline); uassert(51028, "Cannot specify exchange option to a mongos", !request.getExchange()); uassert(51143, diff --git a/src/mongo/s/query/document_source_merge_cursors.cpp b/src/mongo/s/query/document_source_merge_cursors.cpp index abc183b7cae..a08e966817a 100644 --- a/src/mongo/s/query/document_source_merge_cursors.cpp +++ b/src/mongo/s/query/document_source_merge_cursors.cpp @@ -40,7 +40,8 @@ namespace mongo { REGISTER_DOCUMENT_SOURCE(mergeCursors, LiteParsedDocumentSourceDefault::parse, - DocumentSourceMergeCursors::createFromBson); + DocumentSourceMergeCursors::createFromBson, + LiteParsedDocumentSource::AllowedWithApiStrict::kInternal); constexpr StringData DocumentSourceMergeCursors::kStageName; |