summaryrefslogtreecommitdiff
path: root/src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp')
-rw-r--r--src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp594
1 files changed, 131 insertions, 463 deletions
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 d38f5992eaf..272d7a1a80c 100644
--- a/src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp
+++ b/src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp
@@ -29,7 +29,6 @@
#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kQuery
-#include "mongo/db/pipeline/document_source_sequential_document_cache.h"
#include <algorithm>
#include <iterator>
@@ -56,7 +55,6 @@
#include "mongo/db/pipeline/document_source_sample.h"
#include "mongo/db/pipeline/document_source_single_document_transformation.h"
#include "mongo/db/pipeline/document_source_sort.h"
-#include "mongo/db/pipeline/document_source_streaming_group.h"
#include "mongo/db/pipeline/expression_context.h"
#include "mongo/db/pipeline/lite_parsed_document_source.h"
#include "mongo/db/query/query_planner_common.h"
@@ -232,7 +230,6 @@ boost::intrusive_ptr<DocumentSourceGroup> createBucketGroupForReorder(
void optimizePrefix(Pipeline::SourceContainer::iterator itr, Pipeline::SourceContainer* container) {
auto prefix = Pipeline::SourceContainer(container->begin(), itr);
Pipeline::optimizeContainer(&prefix);
- Pipeline::optimizeEachStage(&prefix);
container->erase(container->begin(), itr);
container->splice(itr, prefix);
}
@@ -246,39 +243,9 @@ DocumentSourceInternalUnpackBucket::DocumentSourceInternalUnpackBucket(
bool assumeNoMixedSchemaData)
: DocumentSource(kStageNameInternal, expCtx),
_assumeNoMixedSchemaData(assumeNoMixedSchemaData),
-
_bucketUnpacker(std::move(bucketUnpacker)),
_bucketMaxSpanSeconds{bucketMaxSpanSeconds} {}
-DocumentSourceInternalUnpackBucket::DocumentSourceInternalUnpackBucket(
- const boost::intrusive_ptr<ExpressionContext>& expCtx,
- BucketUnpacker bucketUnpacker,
- int bucketMaxSpanSeconds,
- const boost::optional<BSONObj>& eventFilterBson,
- const boost::optional<BSONObj>& wholeBucketFilterBson,
- bool assumeNoMixedSchemaData)
- : DocumentSourceInternalUnpackBucket(
- expCtx, std::move(bucketUnpacker), bucketMaxSpanSeconds, assumeNoMixedSchemaData) {
- if (eventFilterBson) {
- _eventFilterBson = eventFilterBson->getOwned();
- _eventFilter =
- uassertStatusOK(MatchExpressionParser::parse(_eventFilterBson,
- pExpCtx,
- ExtensionsCallbackNoop(),
- Pipeline::kAllowedMatcherFeatures));
- _eventFilterDeps = {};
- _eventFilter->addDependencies(&_eventFilterDeps);
- }
- if (wholeBucketFilterBson) {
- _wholeBucketFilterBson = wholeBucketFilterBson->getOwned();
- _wholeBucketFilter =
- uassertStatusOK(MatchExpressionParser::parse(_wholeBucketFilterBson,
- pExpCtx,
- ExtensionsCallbackNoop(),
- Pipeline::kAllowedMatcherFeatures));
- }
-}
-
boost::intrusive_ptr<DocumentSource> DocumentSourceInternalUnpackBucket::createFromBsonInternal(
BSONElement specElem, const boost::intrusive_ptr<ExpressionContext>& expCtx) {
uassert(5346500,
@@ -288,20 +255,14 @@ boost::intrusive_ptr<DocumentSource> DocumentSourceInternalUnpackBucket::createF
// If neither "include" nor "exclude" is specified, the default is "exclude": [] and
// if that's the case, no field will be added to 'bucketSpec.fieldSet' in the for-loop below.
+ BucketUnpacker::Behavior unpackerBehavior = BucketUnpacker::Behavior::kExclude;
BucketSpec bucketSpec;
- // Use extended-range support if any individual collection requires it, even if 'specElem'
- // doesn't mention this flag.
- if (expCtx->getRequiresTimeseriesExtendedRangeSupport()) {
- bucketSpec.setUsesExtendedRange(true);
- }
auto hasIncludeExclude = false;
auto hasTimeField = false;
auto hasBucketMaxSpanSeconds = false;
auto bucketMaxSpanSeconds = 0;
auto assumeClean = false;
std::vector<std::string> computedMetaProjFields;
- boost::optional<BSONObj> eventFilterBson;
- boost::optional<BSONObj> wholeBucketFilterBson;
for (auto&& elem : specElem.embeddedObject()) {
auto fieldName = elem.fieldNameStringData();
if (fieldName == kInclude || fieldName == kExclude) {
@@ -326,8 +287,8 @@ boost::intrusive_ptr<DocumentSource> DocumentSourceInternalUnpackBucket::createF
field.find('.') == std::string::npos);
bucketSpec.addIncludeExcludeField(field);
}
- bucketSpec.setBehavior(fieldName == kInclude ? BucketSpec::Behavior::kInclude
- : BucketSpec::Behavior::kExclude);
+ unpackerBehavior = fieldName == kInclude ? BucketUnpacker::Behavior::kInclude
+ : BucketUnpacker::Behavior::kExclude;
hasIncludeExclude = true;
} else if (fieldName == kAssumeNoMixedSchemaData) {
uassert(6067202,
@@ -389,24 +350,6 @@ boost::intrusive_ptr<DocumentSource> DocumentSourceInternalUnpackBucket::createF
<< " field must be a bool, got: " << elem.type(),
elem.type() == BSONType::Bool);
bucketSpec.includeMaxTimeAsMetadata = elem.boolean();
- } else if (fieldName == kUsesExtendedRange) {
- uassert(6646901,
- str::stream() << kUsesExtendedRange
- << " field must be a bool, got: " << elem.type(),
- elem.type() == BSONType::Bool);
- bucketSpec.setUsesExtendedRange(elem.boolean());
- } else if (fieldName == kEventFilter) {
- uassert(7026902,
- str::stream() << kEventFilter
- << " field must be an object, got: " << elem.type(),
- elem.type() == BSONType::Object);
- eventFilterBson = elem.Obj();
- } else if (fieldName == kWholeBucketFilter) {
- uassert(7026903,
- str::stream() << kWholeBucketFilter
- << " field must be an object, got: " << elem.type(),
- elem.type() == BSONType::Object);
- wholeBucketFilterBson = elem.Obj();
} else {
uasserted(5346506,
str::stream()
@@ -421,12 +364,11 @@ boost::intrusive_ptr<DocumentSource> DocumentSourceInternalUnpackBucket::createF
"The $_internalUnpackBucket stage requires a bucketMaxSpanSeconds parameter",
hasBucketMaxSpanSeconds);
- return make_intrusive<DocumentSourceInternalUnpackBucket>(expCtx,
- BucketUnpacker{std::move(bucketSpec)},
- bucketMaxSpanSeconds,
- eventFilterBson,
- wholeBucketFilterBson,
- assumeClean);
+ return make_intrusive<DocumentSourceInternalUnpackBucket>(
+ expCtx,
+ BucketUnpacker{std::move(bucketSpec), unpackerBehavior},
+ bucketMaxSpanSeconds,
+ assumeClean);
}
boost::intrusive_ptr<DocumentSource> DocumentSourceInternalUnpackBucket::createFromBsonExternal(
@@ -472,49 +414,39 @@ boost::intrusive_ptr<DocumentSource> DocumentSourceInternalUnpackBucket::createF
hasTimeField);
return make_intrusive<DocumentSourceInternalUnpackBucket>(
- expCtx, BucketUnpacker{std::move(bucketSpec)}, 3600, assumeClean);
+ expCtx,
+ BucketUnpacker{std::move(bucketSpec), BucketUnpacker::Behavior::kExclude},
+ 3600,
+ assumeClean);
}
-void DocumentSourceInternalUnpackBucket::serializeToArray(std::vector<Value>& array,
- const SerializationOptions& opts) const {
- auto explain = opts.verbosity;
-
+void DocumentSourceInternalUnpackBucket::serializeToArray(
+ std::vector<Value>& array, boost::optional<ExplainOptions::Verbosity> explain) const {
MutableDocument out;
auto behavior =
- _bucketUnpacker.behavior() == BucketSpec::Behavior::kInclude ? kInclude : kExclude;
+ _bucketUnpacker.behavior() == BucketUnpacker::Behavior::kInclude ? kInclude : kExclude;
const auto& spec = _bucketUnpacker.bucketSpec();
std::vector<Value> fields;
for (auto&& field : spec.fieldSet()) {
- fields.emplace_back(opts.serializeFieldPathFromString(field));
+ fields.emplace_back(field);
}
if (((_bucketUnpacker.includeMetaField() &&
- _bucketUnpacker.behavior() == BucketSpec::Behavior::kInclude) ||
+ _bucketUnpacker.behavior() == BucketUnpacker::Behavior::kInclude) ||
(!_bucketUnpacker.includeMetaField() &&
- _bucketUnpacker.behavior() == BucketSpec::Behavior::kExclude && spec.metaField())) &&
+ _bucketUnpacker.behavior() == BucketUnpacker::Behavior::kExclude && spec.metaField())) &&
std::find(spec.computedMetaProjFields().cbegin(),
spec.computedMetaProjFields().cend(),
*spec.metaField()) == spec.computedMetaProjFields().cend())
- fields.emplace_back(opts.serializeFieldPathFromString(*spec.metaField()));
+ fields.emplace_back(*spec.metaField());
out.addField(behavior, Value{std::move(fields)});
- out.addField(timeseries::kTimeFieldName,
- Value{opts.serializeFieldPathFromString(spec.timeField())});
+ out.addField(timeseries::kTimeFieldName, Value{spec.timeField()});
if (spec.metaField()) {
- out.addField(timeseries::kMetaFieldName,
- Value{opts.serializeFieldPathFromString(*spec.metaField())});
+ out.addField(timeseries::kMetaFieldName, Value{*spec.metaField()});
}
- out.addField(kBucketMaxSpanSeconds, opts.serializeLiteral(Value{_bucketMaxSpanSeconds}));
+ out.addField(kBucketMaxSpanSeconds, Value{_bucketMaxSpanSeconds});
if (_assumeNoMixedSchemaData)
- out.addField(kAssumeNoMixedSchemaData,
- opts.serializeLiteral(Value(_assumeNoMixedSchemaData)));
-
- if (spec.usesExtendedRange()) {
- // Include this flag so that 'explain' is more helpful.
- // But this is not so useful for communicating from one process to another,
- // because mongos and/or the primary shard don't know whether any other shard
- // has extended-range data.
- out.addField(kUsesExtendedRange, opts.serializeLiteral(Value{true}));
- }
+ out.addField(kAssumeNoMixedSchemaData, Value(_assumeNoMixedSchemaData));
if (!spec.computedMetaProjFields().empty())
out.addField("computedMetaProjFields", Value{[&] {
@@ -522,97 +454,51 @@ void DocumentSourceInternalUnpackBucket::serializeToArray(std::vector<Value>& ar
std::transform(spec.computedMetaProjFields().cbegin(),
spec.computedMetaProjFields().cend(),
std::back_inserter(compFields),
- [opts](auto&& projString) {
- return Value{
- opts.serializeFieldPathFromString(projString)};
- });
+ [](auto&& projString) { return Value{projString}; });
return compFields;
}()});
if (_bucketUnpacker.includeMinTimeAsMetadata()) {
- out.addField(kIncludeMinTimeAsMetadata,
- opts.serializeLiteral(Value{_bucketUnpacker.includeMinTimeAsMetadata()}));
+ out.addField(kIncludeMinTimeAsMetadata, Value{_bucketUnpacker.includeMinTimeAsMetadata()});
}
if (_bucketUnpacker.includeMaxTimeAsMetadata()) {
- out.addField(kIncludeMaxTimeAsMetadata,
- opts.serializeLiteral(Value{_bucketUnpacker.includeMaxTimeAsMetadata()}));
- }
-
- if (_wholeBucketFilter) {
- out.addField(kWholeBucketFilter, Value{_wholeBucketFilter->serialize(opts)});
- }
- if (_eventFilter) {
- out.addField(kEventFilter, Value{_eventFilter->serialize(opts)});
+ out.addField(kIncludeMaxTimeAsMetadata, Value{_bucketUnpacker.includeMaxTimeAsMetadata()});
}
if (!explain) {
array.push_back(Value(DOC(getSourceName() << out.freeze())));
if (_sampleSize) {
auto sampleSrc = DocumentSourceSample::create(pExpCtx, *_sampleSize);
- sampleSrc->serializeToArray(array, opts);
+ sampleSrc->serializeToArray(array);
}
} else {
if (_sampleSize) {
- out.addField("sample",
- opts.serializeLiteral(Value{static_cast<long long>(*_sampleSize)}));
- out.addField("bucketMaxCount", opts.serializeLiteral(Value{_bucketMaxCount}));
+ out.addField("sample", Value{static_cast<long long>(*_sampleSize)});
+ out.addField("bucketMaxCount", Value{_bucketMaxCount});
}
array.push_back(Value(DOC(getSourceName() << out.freeze())));
}
}
-boost::optional<Document> DocumentSourceInternalUnpackBucket::getNextMatchingMeasure() {
- while (_bucketUnpacker.hasNext()) {
- if (_eventFilter) {
- if (_unpackToBson) {
- auto measure = _bucketUnpacker.getNextBson();
- if (_bucketUnpacker.bucketMatchedQuery() || _eventFilter->matchesBSON(measure)) {
- return Document(measure);
- }
- } else {
- auto measure = _bucketUnpacker.getNext();
- // MatchExpression only takes BSON documents, so we have to make one. As an
- // optimization, only serialize the fields we need to do the match.
- BSONObj measureBson = _eventFilterDeps.needWholeDocument
- ? measure.toBson()
- : document_path_support::documentToBsonWithPaths(measure,
- _eventFilterDeps.fields);
- if (_bucketUnpacker.bucketMatchedQuery() ||
- _eventFilter->matchesBSON(measureBson)) {
- return measure;
- }
- }
- } else {
- return _bucketUnpacker.getNext();
- }
- }
- return {};
-}
-
DocumentSource::GetNextResult DocumentSourceInternalUnpackBucket::doGetNext() {
tassert(5521502, "calling doGetNext() when '_sampleSize' is set is disallowed", !_sampleSize);
// Otherwise, fallback to unpacking every measurement in all buckets until the child stage is
// exhausted.
- if (auto measure = getNextMatchingMeasure()) {
- return GetNextResult(std::move(*measure));
+ if (_bucketUnpacker.hasNext()) {
+ return _bucketUnpacker.getNext();
}
auto nextResult = pSource->getNext();
- while (nextResult.isAdvanced()) {
+ if (nextResult.isAdvanced()) {
auto bucket = nextResult.getDocument().toBson();
- auto bucketMatchedQuery = _wholeBucketFilter && _wholeBucketFilter->matchesBSON(bucket);
- _bucketUnpacker.reset(std::move(bucket), bucketMatchedQuery);
-
+ _bucketUnpacker.reset(std::move(bucket));
uassert(5346509,
str::stream() << "A bucket with _id "
<< _bucketUnpacker.bucket()[timeseries::kBucketIdFieldName].toString()
<< " contains an empty data region",
_bucketUnpacker.hasNext());
- if (auto measure = getNextMatchingMeasure()) {
- return GetNextResult(std::move(*measure));
- }
- nextResult = pSource->getNext();
+ return _bucketUnpacker.getNext();
}
return nextResult;
@@ -624,7 +510,7 @@ bool DocumentSourceInternalUnpackBucket::pushDownComputedMetaProjection(
if (std::next(itr) == container->end()) {
return nextStageWasRemoved;
}
- if (!_bucketUnpacker.getMetaField() || !_bucketUnpacker.includeMetaField()) {
+ if (!_bucketUnpacker.bucketSpec().metaField()) {
return nextStageWasRemoved;
}
@@ -676,8 +562,9 @@ void DocumentSourceInternalUnpackBucket::internalizeProject(const BSONObj& proje
// Update '_bucketUnpacker' state with the new fields and behavior.
auto spec = _bucketUnpacker.bucketSpec();
spec.setFieldSet(fields);
- spec.setBehavior(isInclusion ? BucketSpec::Behavior::kInclude : BucketSpec::Behavior::kExclude);
- _bucketUnpacker.setBucketSpec(std::move(spec));
+ _bucketUnpacker.setBucketSpecAndBehavior(std::move(spec),
+ isInclusion ? BucketUnpacker::Behavior::kInclude
+ : BucketUnpacker::Behavior::kExclude);
}
std::pair<BSONObj, bool> DocumentSourceInternalUnpackBucket::extractOrBuildProjectToInternalize(
@@ -689,8 +576,7 @@ std::pair<BSONObj, bool> DocumentSourceInternalUnpackBucket::extractOrBuildProje
// Check for a viable inclusion $project after the $_internalUnpackBucket.
auto [existingProj, isInclusion] = getIncludeExcludeProjectAndType(std::next(itr)->get());
- if (!_eventFilter && isInclusion && !existingProj.isEmpty() &&
- canInternalizeProjectObj(existingProj)) {
+ if (isInclusion && !existingProj.isEmpty() && canInternalizeProjectObj(existingProj)) {
container->erase(std::next(itr));
return {existingProj, isInclusion};
}
@@ -698,7 +584,8 @@ std::pair<BSONObj, bool> DocumentSourceInternalUnpackBucket::extractOrBuildProje
// Attempt to get an inclusion $project representing the root-level dependencies of the pipeline
// after the $_internalUnpackBucket. If this $project is not empty, then the dependency set was
// finite.
- auto deps = getRestPipelineDependencies(itr, container, true /* includeEventFilter */);
+ Pipeline::SourceContainer restOfPipeline(std::next(itr), container->end());
+ auto deps = Pipeline::getDependenciesForContainer(pExpCtx, restOfPipeline, boost::none);
if (auto dependencyProj =
deps.toProjectionWithoutMetadata(DepsTracker::TruncateToRootLevel::yes);
!dependencyProj.isEmpty()) {
@@ -706,7 +593,7 @@ std::pair<BSONObj, bool> DocumentSourceInternalUnpackBucket::extractOrBuildProje
}
// Check for a viable exclusion $project after the $_internalUnpackBucket.
- if (!_eventFilter && !existingProj.isEmpty() && canInternalizeProjectObj(existingProj)) {
+ if (!existingProj.isEmpty() && canInternalizeProjectObj(existingProj)) {
container->erase(std::next(itr));
return {existingProj, isInclusion};
}
@@ -714,7 +601,8 @@ std::pair<BSONObj, bool> DocumentSourceInternalUnpackBucket::extractOrBuildProje
return {BSONObj{}, false};
}
-BucketSpec::BucketPredicate DocumentSourceInternalUnpackBucket::createPredicatesOnBucketLevelField(
+std::unique_ptr<MatchExpression>
+DocumentSourceInternalUnpackBucket::createPredicatesOnBucketLevelField(
const MatchExpression* matchExpr) const {
return BucketSpec::createPredicatesOnBucketLevelField(
matchExpr,
@@ -743,102 +631,58 @@ std::pair<BSONObj, bool> DocumentSourceInternalUnpackBucket::extractProjectForPu
std::pair<bool, Pipeline::SourceContainer::iterator>
DocumentSourceInternalUnpackBucket::rewriteGroupByMinMax(Pipeline::SourceContainer::iterator itr,
Pipeline::SourceContainer* container) {
- // The computed min/max for each bucket uses the default collation. If the collation of the
- // query doesn't match the default we cannot rely on the computed values as they might differ
- // (e.g. numeric and lexicographic collations compare "5" and "10" in opposite order).
- // NB: Unfortuntealy, this means we have to forgo the optimization even if the source field is
- // numeric and not affected by the collation as we cannot know the data type until runtime.
- if (pExpCtx->collationMatchesDefault == ExpressionContext::CollationMatchesDefault::kNo) {
- return {};
- }
-
const auto* groupPtr = dynamic_cast<DocumentSourceGroup*>(std::next(itr)->get());
if (groupPtr == nullptr) {
return {};
}
- if (!_bucketUnpacker.bucketSpec().metaField()) {
- return {};
- }
- const auto& metaField = *_bucketUnpacker.bucketSpec().metaField();
-
const auto& idFields = groupPtr->getIdFields();
-
- // Currently, we only support simple group key. TODO: SERVER-68811. Allow rewrites of object
- // group key if all its fields depend on the metaField only.
- if (idFields.size() != 1) {
+ if (idFields.size() != 1 || !_bucketUnpacker.bucketSpec().metaField().has_value()) {
return {};
}
const auto& exprId = idFields.cbegin()->second;
const auto* exprIdPath = dynamic_cast<const ExpressionFieldPath*>(exprId.get());
-
- // Currently, we only support group key expression of the form {_id : "<path>"}.
- // TODO: SERVER-68811. Allow rewrites if expression is constant.
if (exprIdPath == nullptr) {
return {};
}
const auto& idPath = exprIdPath->getFieldPath();
- // {The path must be at or under the metaField for this re-write to be correct (and the zero
- // component is always CURRENT).}
- if (idPath.getPathLength() < 2 || idPath.getFieldName(1) != metaField) {
+ if (idPath.getPathLength() < 2 ||
+ idPath.getFieldName(1) != _bucketUnpacker.bucketSpec().metaField().get()) {
return {};
}
- std::vector<AccumulationStatement> accumulationStatementsBucket;
+ bool suitable = true;
+ std::vector<AccumulationStatement> accumulationStatements;
for (const AccumulationStatement& stmt : groupPtr->getAccumulatedFields()) {
- const auto& op = stmt.expr.name;
- // If _any_ of the accumulators aren't $min/$max we won't perform the re-write (some other
- // accs might be re-writable in terms of the bucket controls, we just haven't invested into
- // implementing them).
- if (op != "$min" && op != "$max") {
- return {};
- }
-
- const auto* exprArgPath =
- dynamic_cast<const ExpressionFieldPath*>(stmt.expr.argument.get());
-
- // This is either a const or a compound expression. While some such expressions (e.g: {$min:
- // {$add:
- // ['$a', 2]}}) could be re-written in terms of the min/max on the control fields, in
- // general we cannot do it (e.g. {$min: {$add: ['$a', '$b']}}), so we block the re-write.
- if (!exprArgPath) {
- return {};
- }
-
- // Path can have a single component if it's using $$CURRENT or a similar variable. We don't
- // support these.
- const auto& path = exprArgPath->getFieldPath();
- if (path.getPathLength() <= 1) {
- return {};
- }
- const auto& accFieldName = path.getFieldName(1);
+ const auto op = stmt.expr.name;
+ const bool isMin = op == "$min";
+ const bool isMax = op == "$max";
- // Rewrite not valid for the timeField because control.min.time contains a rounded-down time
- // and not the actual min time of events in the bucket.
- if (accFieldName == _bucketUnpacker.bucketSpec().timeField()) {
- return {};
+ // Rewrite is valid only for min and max aggregates.
+ if (!isMin && !isMax) {
+ suitable = false;
+ break;
}
- // Build the paths for the bucket-level fields.
- std::ostringstream os;
- if (accFieldName == metaField) {
- // Update aggregates to reference the meta field.
- os << timeseries::kBucketMetaFieldName;
-
- for (size_t index = 2; index < path.getPathLength(); index++) {
- os << "." << path.getFieldName(index);
+ const auto* exprArg = stmt.expr.argument.get();
+ if (const auto* exprArgPath = dynamic_cast<const ExpressionFieldPath*>(exprArg)) {
+ const auto& path = exprArgPath->getFieldPath();
+ if (path.getPathLength() <= 1 ||
+ path.getFieldName(1) == _bucketUnpacker.bucketSpec().timeField()) {
+ // Rewrite not valid for time field. We want to eliminate the bucket
+ // unpack stage here.
+ suitable = false;
+ break;
}
- } else {
+
// Update aggregates to reference the control field.
- const auto op = stmt.expr.name;
- if (op == "$min") {
+ std::ostringstream os;
+ if (isMin) {
os << timeseries::kControlMinFieldNamePrefix;
- } else if (op == "$max") {
- os << timeseries::kControlMaxFieldNamePrefix;
} else {
- MONGO_UNREACHABLE;
+ os << timeseries::kControlMaxFieldNamePrefix;
}
for (size_t index = 1; index < path.getPathLength(); index++) {
@@ -847,55 +691,45 @@ DocumentSourceInternalUnpackBucket::rewriteGroupByMinMax(Pipeline::SourceContain
}
os << path.getFieldName(index);
}
- }
- // Re-create the accumulator using the bucket-level paths.
- const auto& newExpr = ExpressionFieldPath::createPathFromString(
- pExpCtx.get(), os.str(), pExpCtx->variablesParseState);
-
- AccumulationExpression accExpr = stmt.expr;
- accExpr.argument = newExpr;
- accumulationStatementsBucket.emplace_back(stmt.fieldName, std::move(accExpr));
- }
+ const auto& newExpr = ExpressionFieldPath::createPathFromString(
+ pExpCtx.get(), os.str(), pExpCtx->variablesParseState);
- // Re-create the group key using the bucket-level path.
- std::ostringstream os;
- os << timeseries::kBucketMetaFieldName;
- for (size_t index = 2; index < idPath.getPathLength(); index++) {
- os << "." << idPath.getFieldName(index);
+ AccumulationExpression accExpr = stmt.expr;
+ accExpr.argument = newExpr;
+ accumulationStatements.emplace_back(stmt.fieldName, std::move(accExpr));
+ }
}
- auto exprIdBucket = ExpressionFieldPath::createPathFromString(
- pExpCtx.get(), os.str(), pExpCtx->variablesParseState);
- boost::intrusive_ptr<Expression> rewrittenIdExpression;
- const auto& idFieldNames = groupPtr->getIdFieldNames();
- if (idFieldNames.empty()) {
- rewrittenIdExpression = exprIdBucket;
- } else {
- // idFieldNames can only have size 1 here since we only support simple group key.
- // TODO: SERVER-68811. Allow rewrites of object group key if all its fields depend on the
- // metaField only.
- rewrittenIdExpression =
- ExpressionObject::create(pExpCtx.get(), {{idFieldNames[0], exprIdBucket}});
- }
+ if (suitable) {
+ std::ostringstream os;
+ os << timeseries::kBucketMetaFieldName;
+ for (size_t index = 2; index < idPath.getPathLength(); index++) {
+ os << "." << idPath.getFieldName(index);
+ }
+ auto exprId1 = ExpressionFieldPath::createPathFromString(
+ pExpCtx.get(), os.str(), pExpCtx->variablesParseState);
- auto newGroup = DocumentSourceGroup::create(pExpCtx,
- std::move(rewrittenIdExpression),
- std::move(accumulationStatementsBucket),
- groupPtr->getMaxMemoryUsageBytes());
+ auto newGroup = DocumentSourceGroup::create(pExpCtx,
+ std::move(exprId1),
+ std::move(accumulationStatements),
+ groupPtr->getMaxMemoryUsageBytes());
- // Replace the current stage (DocumentSourceInternalUnpackBucket) and the following group stage
- // with the new group.
- container->erase(std::next(itr));
- *itr = std::move(newGroup);
+ // Erase current stage and following group stage, and replace with updated
+ // group.
+ container->erase(std::next(itr));
+ *itr = std::move(newGroup);
- if (itr == container->begin()) {
- // Optimize the new group stage.
- return {true, itr};
- } else {
- // Give chance to the previous stage to optimize against the new group stage.
- return {true, std::prev(itr)};
+ if (itr == container->begin()) {
+ // Optimize group stage.
+ return {true, itr};
+ } else {
+ // Give chance of the previous stage to optimize against group stage.
+ return {true, std::prev(itr)};
+ }
}
+
+ return {};
}
bool DocumentSourceInternalUnpackBucket::haveComputedMetaField() const {
@@ -904,68 +738,6 @@ bool DocumentSourceInternalUnpackBucket::haveComputedMetaField() const {
_bucketUnpacker.bucketSpec().metaField().get());
}
-bool DocumentSourceInternalUnpackBucket::enableStreamingGroupIfPossible(
- Pipeline::SourceContainer::iterator itr, Pipeline::SourceContainer* container) {
- // skip unpack stage
- itr = std::next(itr);
-
- FieldPath timeField = _bucketUnpacker.bucketSpec().timeField();
- DocumentSourceGroup* groupStage = nullptr;
- bool isSortedOnTime = false;
- for (; itr != container->end(); ++itr) {
- if (auto groupStagePtr = dynamic_cast<DocumentSourceGroup*>(itr->get())) {
- groupStage = groupStagePtr;
- break;
- }
- if (auto sortStagePtr = dynamic_cast<DocumentSourceSort*>(itr->get())) {
- isSortedOnTime = sortStagePtr->getSortKeyPattern().front().fieldPath == timeField;
- } else if (!itr->get()->constraints().preservesOrderAndMetadata) {
- // If this is after the sort, the sort is invalidated. If it's before the sort, there's
- // no harm in keeping the boolean false.
- isSortedOnTime = false;
- }
- // We modify time field, so we can't proceed with optimization. It may be possible to
- // proceed in some cases if the modification happens before the sort, but we won't worry
- // about or bother with those - in large part because it is risky that it will change the
- // type away from a date into something with more difficult/subtle semantics.
- if (itr->get()->getModifiedPaths().canModify(timeField)) {
- return false;
- }
- }
-
- if (groupStage == nullptr || !isSortedOnTime) {
- return false;
- }
-
- const auto& idFields = groupStage->getMutableIdFields();
- std::vector<size_t> monotonicIdFields;
- for (size_t i = 0; i < idFields.size(); ++i) {
- // To enable streaming, we need id field expression to be clustered, so that all documents
- // with the same value of this id field are in a single continious cluster. However this
- // property is hard to check for, so we check for monotonicity instead, which is stronger.
- idFields[i]->optimize(); // We optimize here to make use of constant folding.
- auto monotonicState = idFields[i]->getMonotonicState(timeField);
-
- // We don't add monotonic::State::Constant id fields, because they are useless when
- // determining if a group batch is finished.
- if (monotonicState == monotonic::State::Increasing ||
- monotonicState == monotonic::State::Decreasing) {
- monotonicIdFields.push_back(i);
- }
- }
- if (monotonicIdFields.empty()) {
- return false;
- }
-
- *itr =
- DocumentSourceStreamingGroup::create(pExpCtx,
- groupStage->getIdExpression(),
- std::move(monotonicIdFields),
- std::move(groupStage->getMutableAccumulatedFields()),
- groupStage->getMaxMemoryUsageBytes());
- return true;
-}
-
template <TopBottomSense sense, bool single>
bool extractFromAcc(const AccumulatorN* acc,
const boost::intrusive_ptr<Expression>& init,
@@ -1208,55 +980,6 @@ bool DocumentSourceInternalUnpackBucket::optimizeLastpoint(Pipeline::SourceConta
tryInsertBucketLevelSortAndGroup(AccumulatorDocumentsNeeded::kLastDocument);
}
-
-bool findSequentialDocumentCache(Pipeline::SourceContainer::iterator start,
- Pipeline::SourceContainer::iterator end) {
- while (start != end && !dynamic_cast<DocumentSourceSequentialDocumentCache*>(start->get())) {
- start = std::next(start);
- }
- return start != end;
-}
-
-Pipeline::SourceContainer::iterator DocumentSourceInternalUnpackBucket::optimizeAtRestOfPipeline(
- Pipeline::SourceContainer::iterator itr, Pipeline::SourceContainer* container) {
- if (itr == container->end()) {
- return itr;
- }
-
- invariant(*itr == this);
- Pipeline::SourceContainer::iterator unpackBucket = itr;
-
- itr = std::next(itr);
-
- try {
- while (itr != container->end()) {
- if (itr == unpackBucket) {
- itr = std::next(itr);
- if (itr == container->end())
- break;
- }
- itr = (*itr).get()->optimizeAt(itr, container);
- }
- } catch (DBException& ex) {
- ex.addContext("Failed to optimize pipeline");
- throw;
- }
-
- return itr;
-}
-
-DepsTracker DocumentSourceInternalUnpackBucket::getRestPipelineDependencies(
- Pipeline::SourceContainer::iterator itr,
- Pipeline::SourceContainer* container,
- bool includeEventFilter) const {
- auto deps = Pipeline::getDependenciesForContainer(
- pExpCtx, Pipeline::SourceContainer{std::next(itr), container->end()}, boost::none);
- if (_eventFilter && includeEventFilter) {
- _eventFilter->addDependencies(&deps);
- }
- return deps;
-}
-
Pipeline::SourceContainer::iterator DocumentSourceInternalUnpackBucket::doOptimizeAt(
Pipeline::SourceContainer::iterator itr, Pipeline::SourceContainer* container) {
invariant(*itr == this);
@@ -1270,8 +993,7 @@ Pipeline::SourceContainer::iterator DocumentSourceInternalUnpackBucket::doOptimi
bool haveComputedMetaField = this->haveComputedMetaField();
// Before any other rewrites for the current stage, consider reordering with $sort.
- if (auto sortPtr = dynamic_cast<DocumentSourceSort*>(std::next(itr)->get());
- sortPtr && !_eventFilter) {
+ if (auto sortPtr = dynamic_cast<DocumentSourceSort*>(std::next(itr)->get())) {
if (auto metaField = _bucketUnpacker.bucketSpec().metaField();
metaField && !haveComputedMetaField) {
if (checkMetadataSortReorder(sortPtr->getSortKeyPattern(), metaField.get())) {
@@ -1302,8 +1024,7 @@ Pipeline::SourceContainer::iterator DocumentSourceInternalUnpackBucket::doOptimi
}
// Attempt to push geoNear on the metaField past $_internalUnpackBucket.
- if (auto nextNear = dynamic_cast<DocumentSourceGeoNear*>(std::next(itr)->get());
- nextNear && !_eventFilter) {
+ if (auto nextNear = dynamic_cast<DocumentSourceGeoNear*>(std::next(itr)->get())) {
// Currently we only support geo indexes on the meta field, and we enforce this by
// requiring the key field to be set so we can check before we try to look up indexes.
auto keyField = nextNear->getKeyField();
@@ -1337,34 +1058,23 @@ Pipeline::SourceContainer::iterator DocumentSourceInternalUnpackBucket::doOptimi
}
}
- // OptimizeAt the pipeline after this stage to merge $match stages and push them forward.
+ // Optimize the pipeline after this stage to merge $match stages and push them forward, and to
+ // take advantage of $expr rewrite optimizations.
if (!_optimizedEndOfPipeline) {
_optimizedEndOfPipeline = true;
if (std::next(itr) == container->end()) {
return container->end();
}
-
- auto cacheFound = findSequentialDocumentCache(itr, container->end());
- if (cacheFound) {
- // We want to call optimizeAt() on the rest of the pipeline first, and exit this
- // function since any calls to optimize() will interfere with the
- // sequentialDocumentCache's ability to properly place itself or abandon.
- return DocumentSourceInternalUnpackBucket::optimizeAtRestOfPipeline(itr, container);
- } else {
- if (auto nextStage = dynamic_cast<DocumentSourceGeoNear*>(std::next(itr)->get())) {
- // If the end of the pipeline starts with a $geoNear stage, make sure it gets
- // optimized in a context where it knows there are other stages before it. It will
- // split itself up into separate $match and $sort stages. But it doesn't split
- // itself up when it's the first stage, because it expects to use a special
- // DocumentSouceGeoNearCursor plan.
- nextStage->optimizeAt(std::next(itr), container);
- }
- // We want to optimize the rest of the pipeline to ensure the stages are in their
- // optimal position and expressions have been optimized to allow for certain rewrites.
- Pipeline::optimizeEndOfPipeline(itr, container);
+ if (auto nextStage = dynamic_cast<DocumentSourceGeoNear*>(std::next(itr)->get())) {
+ // If the end of the pipeline starts with a $geoNear stage, make sure it gets optimized
+ // in a context where it knows there are other stages before it. It will split itself
+ // up into separate $match and $sort stages. But it doesn't split itself up when it's
+ // the first stage, because it expects to use a special DocumentSouceGeoNearCursor plan.
+ nextStage->optimizeAt(std::next(itr), container);
}
+ Pipeline::optimizeEndOfPipeline(itr, container);
if (std::next(itr) == container->end()) {
return container->end();
} else {
@@ -1373,8 +1083,7 @@ Pipeline::SourceContainer::iterator DocumentSourceInternalUnpackBucket::doOptimi
return itr;
}
}
-
- if (!_eventFilter) {
+ {
// Check if we can avoid unpacking if we have a group stage with min/max aggregates.
auto [success, result] = rewriteGroupByMinMax(itr, container);
if (success) {
@@ -1385,12 +1094,13 @@ Pipeline::SourceContainer::iterator DocumentSourceInternalUnpackBucket::doOptimi
{
// Check if the rest of the pipeline needs any fields. For example we might only be
// interested in $count.
- auto deps = getRestPipelineDependencies(itr, container, true /* includeEventFilter */);
+ auto deps = Pipeline::getDependenciesForContainer(
+ pExpCtx, Pipeline::SourceContainer{std::next(itr), container->end()}, boost::none);
if (deps.hasNoRequirements()) {
- _bucketUnpacker.setBucketSpec({_bucketUnpacker.bucketSpec().timeField(),
- _bucketUnpacker.bucketSpec().metaField(),
- {},
- BucketSpec::Behavior::kInclude});
+ _bucketUnpacker.setBucketSpecAndBehavior({_bucketUnpacker.bucketSpec().timeField(),
+ _bucketUnpacker.bucketSpec().metaField(),
+ {}},
+ BucketUnpacker::Behavior::kInclude);
// Keep going for next optimization.
}
@@ -1407,71 +1117,31 @@ Pipeline::SourceContainer::iterator DocumentSourceInternalUnpackBucket::doOptimi
// Attempt to optimize last-point type queries.
if (feature_flags::gfeatureFlagLastPointQuery.isEnabled(
serverGlobalParams.featureCompatibility) &&
- !_triedLastpointRewrite && !_eventFilter && optimizeLastpoint(itr, container)) {
+ !_triedLastpointRewrite && optimizeLastpoint(itr, container)) {
_triedLastpointRewrite = true;
// If we are able to rewrite the aggregation, give the resulting pipeline a chance to
// perform further optimizations.
return container->begin();
};
- // Attempt to map predicates on bucketed fields to the predicates on the control field.
- if (auto nextMatch = dynamic_cast<DocumentSourceMatch*>(std::next(itr)->get())) {
+ // Attempt to map predicates on bucketed fields to predicates on the control field.
+ if (auto nextMatch = dynamic_cast<DocumentSourceMatch*>(std::next(itr)->get());
+ nextMatch && !_triedBucketLevelFieldsPredicatesPushdown) {
+ _triedBucketLevelFieldsPredicatesPushdown = true;
- // Merge multiple following $match stages.
- auto itrToMatch = std::next(itr);
- while (std::next(itrToMatch) != container->end() &&
- dynamic_cast<DocumentSourceMatch*>(std::next(itrToMatch)->get())) {
- nextMatch->doOptimizeAt(itrToMatch, container);
- }
-
- auto predicates = createPredicatesOnBucketLevelField(nextMatch->getMatchExpression());
-
- // Try to create a tight bucket predicate to perform bucket level matching.
- if (predicates.tightPredicate) {
- _wholeBucketFilterBson = predicates.tightPredicate->serialize();
- _wholeBucketFilter =
- uassertStatusOK(MatchExpressionParser::parse(_wholeBucketFilterBson,
- pExpCtx,
- ExtensionsCallbackNoop(),
- Pipeline::kAllowedMatcherFeatures));
- _wholeBucketFilter = MatchExpression::optimize(std::move(_wholeBucketFilter));
- }
-
- // Push the original event predicate into the unpacking stage.
- _eventFilterBson = nextMatch->getQuery().getOwned();
- _eventFilter =
- uassertStatusOK(MatchExpressionParser::parse(_eventFilterBson,
- pExpCtx,
- ExtensionsCallbackNoop(),
- Pipeline::kAllowedMatcherFeatures));
- _eventFilter = MatchExpression::optimize(std::move(_eventFilter));
- _eventFilterDeps = {};
- _eventFilter->addDependencies(&_eventFilterDeps);
- container->erase(std::next(itr));
-
- // If the $match is not followed by other stages referencing fields (e.g. $count), we can
- // unpack directly to BSON so that data doesn't need to be materialized to Document.
- auto deps = getRestPipelineDependencies(itr, container, false /* includeEventFilter */);
- if (deps.fields.empty()) {
- _unpackToBson = true;
- }
-
- // Create a loose bucket predicate and push it before the unpacking stage.
- if (predicates.loosePredicate) {
- container->insert(
- itr, DocumentSourceMatch::create(predicates.loosePredicate->serialize(), pExpCtx));
+ if (auto match = createPredicatesOnBucketLevelField(nextMatch->getMatchExpression())) {
+ BSONObjBuilder bob;
+ match->serialize(&bob);
+ container->insert(itr, DocumentSourceMatch::create(bob.obj(), pExpCtx));
// Give other stages a chance to optimize with the new $match.
return std::prev(itr) == container->begin() ? std::prev(itr)
: std::prev(std::prev(itr));
}
-
- // We have removed a $match after this stage, so we try to optimize this stage again.
- return itr;
}
// Attempt to push down a $project on the metaField past $_internalUnpackBucket.
- if (!_eventFilter && !haveComputedMetaField) {
+ if (!haveComputedMetaField) {
if (auto [metaProject, deleteRemainder] = extractProjectForPushDown(std::next(itr)->get());
!metaProject.isEmpty()) {
container->insert(itr,
@@ -1490,7 +1160,7 @@ Pipeline::SourceContainer::iterator DocumentSourceInternalUnpackBucket::doOptimi
// Attempt to extract computed meta projections from subsequent $project, $addFields, or $set
// and push them before the $_internalunpackBucket.
- if (!_eventFilter && pushDownComputedMetaProjection(itr, container)) {
+ if (pushDownComputedMetaProjection(itr, container)) {
// We've pushed down and removed a stage after this one. Try to optimize the new stage.
return std::prev(itr) == container->begin() ? std::prev(itr) : std::prev(std::prev(itr));
}
@@ -1509,8 +1179,6 @@ Pipeline::SourceContainer::iterator DocumentSourceInternalUnpackBucket::doOptimi
}
}
- enableStreamingGroupIfPossible(itr, container);
-
return container->end();
}
@@ -1519,8 +1187,8 @@ DocumentSource::GetModPathsReturn DocumentSourceInternalUnpackBucket::getModifie
StringMap<std::string> renames;
renames.emplace(*_bucketUnpacker.bucketSpec().metaField(),
timeseries::kBucketMetaFieldName);
- return {GetModPathsReturn::Type::kAllExcept, OrderedPathSet{}, std::move(renames)};
+ return {GetModPathsReturn::Type::kAllExcept, std::set<std::string>{}, std::move(renames)};
}
- return {GetModPathsReturn::Type::kAllPaths, OrderedPathSet{}, {}};
+ return {GetModPathsReturn::Type::kAllPaths, std::set<std::string>{}, {}};
}
} // namespace mongo